diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..f640b535c --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/PKHeX.Core/Editing/Applicators/BallApplicator.cs b/PKHeX.Core/Editing/Applicators/BallApplicator.cs index 9a7d75f7e..fcedf685b 100644 --- a/PKHeX.Core/Editing/Applicators/BallApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/BallApplicator.cs @@ -3,160 +3,159 @@ using System.Linq; using static PKHeX.Core.Ball; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains logic to apply a new value to a . +/// +public static class BallApplicator { /// - /// Contains logic to apply a new value to a . + /// Gets all balls that are legal for the input . /// - public static class BallApplicator + /// + /// Requires checking the for every that is tried. + /// + /// Pokémon to retrieve a list of valid balls for. + /// Enumerable list of values that the is legal with. + public static IEnumerable GetLegalBalls(PKM pk) { - /// - /// Gets all balls that are legal for the input . - /// - /// - /// Requires checking the for every that is tried. - /// - /// Pokémon to retrieve a list of valid balls for. - /// Enumerable list of values that the is legal with. - public static IEnumerable 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; - } - } - - /// - /// Applies a random legal ball value if any exist. - /// - /// - /// Requires checking the for every that is tried. - /// - /// Pokémon to modify. - public static int ApplyBallLegalRandom(PKM pkm) - { - var balls = GetBallListFromColor(pkm).ToArray(); - Util.Shuffle(balls.AsSpan()); - return ApplyFirstLegalBall(pkm, balls); - } - - /// - /// Applies a legal ball value if any exist, ordered by color. - /// - /// - /// Requires checking the for every that is tried. - /// - /// Pokémon to modify. - public static int ApplyBallLegalByColor(PKM pkm) - { - var balls = GetBallListFromColor(pkm); - return ApplyFirstLegalBall(pkm, balls); - } - - /// - /// Applies a random ball value in a cyclical manner. - /// - /// Pokémon to modify. - 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 balls) - { - foreach (var b in balls) - { - pkm.Ball = (int)b; - if (new LegalityAnalysis(pkm).Valid) - break; - } - return pkm.Ball; - } - - private static IEnumerable GetBallList(int ball) - { - var balls = BallList; - var currentBall = (Ball)ball; - return GetCircularOnce(balls, currentBall); - } - - private static IEnumerable 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 GetCircularOnce(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); - } - } - - /// - /// Priority Match ball IDs that match the color ID in descending order - /// - private static readonly Dictionary 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 }, - }; - - /// - /// Personal Data color IDs - /// - 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; } } + + /// + /// Applies a random legal ball value if any exist. + /// + /// + /// Requires checking the for every that is tried. + /// + /// Pokémon to modify. + public static int ApplyBallLegalRandom(PKM pk) + { + var balls = GetBallListFromColor(pk).ToArray(); + Util.Shuffle(balls.AsSpan()); + return ApplyFirstLegalBall(pk, balls); + } + + /// + /// Applies a legal ball value if any exist, ordered by color. + /// + /// + /// Requires checking the for every that is tried. + /// + /// Pokémon to modify. + public static int ApplyBallLegalByColor(PKM pk) + { + var balls = GetBallListFromColor(pk); + return ApplyFirstLegalBall(pk, balls); + } + + /// + /// Applies a random ball value in a cyclical manner. + /// + /// Pokémon to modify. + 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 balls) + { + foreach (var b in balls) + { + pk.Ball = (int)b; + if (new LegalityAnalysis(pk).Valid) + break; + } + return pk.Ball; + } + + private static IEnumerable GetBallList(int ball) + { + var balls = BallList; + var currentBall = (Ball)ball; + return GetCircularOnce(balls, currentBall); + } + + private static IEnumerable 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 GetCircularOnce(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); + } + } + + /// + /// Priority Match ball IDs that match the color ID in descending order + /// + private static readonly Dictionary 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 }, + }; + + /// + /// Personal Data color IDs + /// + private enum PersonalColor : byte + { + Red, + Blue, + Yellow, + Green, + Black, + + Brown, + Purple, + Gray, + White, + Pink, + } } diff --git a/PKHeX.Core/Editing/Applicators/CatchRateApplicator.cs b/PKHeX.Core/Editing/Applicators/CatchRateApplicator.cs index 48b570af9..8a4554355 100644 --- a/PKHeX.Core/Editing/Applicators/CatchRateApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/CatchRateApplicator.cs @@ -1,39 +1,38 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for applying a value. +/// +public static class CatchRateApplicator { - /// - /// Logic for applying a value. - /// - 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; } } } diff --git a/PKHeX.Core/Editing/Applicators/GenderApplicator.cs b/PKHeX.Core/Editing/Applicators/GenderApplicator.cs index 4ce8920c6..5ed2c0f3a 100644 --- a/PKHeX.Core/Editing/Applicators/GenderApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/GenderApplicator.cs @@ -1,76 +1,75 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class GenderApplicator { - public static class GenderApplicator + /// + /// Sets the value, with special consideration for the values which derive the value. + /// + /// Pokémon to modify. + /// Desired value to set. + /// Has special logic for an unspecified gender. + public static void SetSaneGender(this PKM pk, int gender) { - /// - /// Sets the value, with special consideration for the values which derive the value. - /// - /// Pokémon to modify. - /// Desired value to set. - /// Has special logic for an unspecified gender. - public static void SetSaneGender(this PKM pk, int gender) + int g = gender == -1 ? pk.GetSaneGender() : gender; + pk.SetGender(g); + } + + /// + /// Sets the value, with special consideration for the values which derive the value. + /// + /// Pokémon to modify. + /// Desired value to set. + 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); } - - /// - /// Sets the value, with special consideration for the values which derive the value. - /// - /// Pokémon to modify. - /// Desired value to set. - 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; } - - /// - /// Sanity checks the provided value, and returns a sane value. - /// - /// - /// Most-legal value - 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; - } - - /// - /// Updates the for a Generation 1/2 format . - /// - /// Pokémon to modify. - /// Desired . - 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; } } + + /// + /// Sanity checks the provided value, and returns a sane value. + /// + /// + /// Most-legal value + 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; + } + + /// + /// Updates the for a Generation 1/2 format . + /// + /// Pokémon to modify. + /// Desired . + public static void SetAttackIVFromGender(this PKM pk, int gender) + { + var rnd = Util.Rand; + while (pk.Gender != gender) + pk.IV_ATK = rnd.Next(16); + } } diff --git a/PKHeX.Core/Editing/Applicators/HiddenPowerApplicator.cs b/PKHeX.Core/Editing/Applicators/HiddenPowerApplicator.cs index a309676a1..f18bada92 100644 --- a/PKHeX.Core/Editing/Applicators/HiddenPowerApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/HiddenPowerApplicator.cs @@ -1,27 +1,26 @@ using System; -namespace PKHeX.Core -{ - public static class HiddenPowerApplicator - { - /// - /// Sets the to match a provided . - /// - /// Pokémon to modify. - /// Desired Hidden Power typing. - public static void SetHiddenPower(this PKM pk, int hiddenPowerType) - { - Span IVs = stackalloc int[6]; - pk.GetIVs(IVs); - HiddenPower.SetIVsForType(hiddenPowerType, IVs, pk.Format); - pk.SetIVs(IVs); - } +namespace PKHeX.Core; - /// - /// Sets the to match a provided . - /// - /// Pokémon to modify. - /// Desired Hidden Power typing. - public static void SetHiddenPower(this PKM pk, MoveType hiddenPowerType) => pk.SetHiddenPower((int)hiddenPowerType); +public static class HiddenPowerApplicator +{ + /// + /// Sets the to match a provided . + /// + /// Pokémon to modify. + /// Desired Hidden Power typing. + public static void SetHiddenPower(this PKM pk, int hiddenPowerType) + { + Span IVs = stackalloc int[6]; + pk.GetIVs(IVs); + HiddenPower.SetIVsForType(hiddenPowerType, IVs, pk.Format); + pk.SetIVs(IVs); } + + /// + /// Sets the to match a provided . + /// + /// Pokémon to modify. + /// Desired Hidden Power typing. + public static void SetHiddenPower(this PKM pk, MoveType hiddenPowerType) => pk.SetHiddenPower((int)hiddenPowerType); } diff --git a/PKHeX.Core/Editing/Applicators/MarkingApplicator.cs b/PKHeX.Core/Editing/Applicators/MarkingApplicator.cs index 80ca5cf88..cf73b9ffa 100644 --- a/PKHeX.Core/Editing/Applicators/MarkingApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/MarkingApplicator.cs @@ -1,69 +1,68 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for modifying the . +/// +public static class MarkingApplicator { /// - /// Logic for modifying the . + /// Default when applying markings. /// - public static class MarkingApplicator + // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global + public static Func> MarkingMethod { get; set; } = FlagHighLow; + + /// + /// Sets the to indicate flawless (or near-flawless) . + /// + /// Pokémon to modify. + public static void SetMarkings(this PKM pk) { - /// - /// Default when applying markings. - /// - // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - public static Func> MarkingMethod { get; set; } = FlagHighLow; + if (pk.MarkingCount < 6) + return; // insufficient marking indexes - /// - /// Sets the to indicate flawless (or near-flawless) . - /// - /// Pokémon to modify. - 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)); + } + + /// + /// Toggles the marking at a given index. + /// + /// Pokémon to modify. + /// Marking index to toggle + /// Current marking value + 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 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)); - } - - /// - /// Toggles the marking at a given index. - /// - /// Pokémon to modify. - /// Marking index to toggle - /// Current marking value - 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 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, - }; - } } } diff --git a/PKHeX.Core/Editing/Applicators/MemoryApplicator.cs b/PKHeX.Core/Editing/Applicators/MemoryApplicator.cs index c4ea24d80..1cfedaecf 100644 --- a/PKHeX.Core/Editing/Applicators/MemoryApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/MemoryApplicator.cs @@ -1,51 +1,50 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for modifying the Memory parameters of a . +/// +public static class MemoryApplicator { /// - /// Logic for modifying the Memory parameters of a . + /// Sets all Memory related data to the default value (zero). /// - public static class MemoryApplicator + /// Pokémon to modify. + public static void ClearMemories(this PKM pk) { - /// - /// Sets all Memory related data to the default value (zero). - /// - /// Pokémon to modify. - 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(); + } - /// - /// Sets the Memory details to a Hatched Egg's memories. - /// - /// Pokémon to modify. - public static void SetHatchMemory6(this PKM pk) + /// + /// Sets the Memory details to a Hatched Egg's memories. + /// + /// Pokémon to modify. + 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; + } - /// - /// Sets a random memory specific to locality. - /// - /// Pokémon to modify. - 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); - } + /// + /// Sets a random memory specific to locality. + /// + /// Pokémon to modify. + 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); } } diff --git a/PKHeX.Core/Editing/Applicators/MoveApplicator.cs b/PKHeX.Core/Editing/Applicators/MoveApplicator.cs index 2115b21b1..81da670b0 100644 --- a/PKHeX.Core/Editing/Applicators/MoveApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/MoveApplicator.cs @@ -1,79 +1,78 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for applying a moveset to a . +/// +public static class MoveApplicator { /// - /// Logic for applying a moveset to a . + /// Sets the individual PP Up count values depending if a Move is present in the move's slot or not. /// - public static class MoveApplicator + /// Pokémon to modify. + /// to use (if already known). Will fetch the current if not provided. + public static void SetMaximumPPUps(this PKM pk, int[] moves) { - /// - /// Sets the individual PP Up count values depending if a Move is present in the move's slot or not. - /// - /// Pokémon to modify. - /// to use (if already known). Will fetch the current if not provided. - 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; - } - - /// - /// Sets the individual PP Up count values depending if a Move is present in the move slot or not. - /// - /// Pokémon to modify. - public static void SetMaximumPPUps(this PKM pk) => pk.SetMaximumPPUps(pk.Moves); - - /// - /// Updates the and updates the current PP counts. - /// - /// Pokémon to modify. - /// to set. Will be resized if 4 entries are not present. - /// Option to maximize PP Ups - 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(); - } - - /// - /// Updates the individual PP count values for each move slot based on the maximum possible value. - /// - /// Pokémon to modify. - /// to use (if already known). Will fetch the current if not provided. - public static void SetMaximumPPCurrent(this PKM pk, ReadOnlySpan 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); - } - - /// - /// Updates the individual PP count values for each move slot based on the maximum possible value. - /// - /// Pokémon to modify. - public static void SetMaximumPPCurrent(this PKM pk) => pk.SetMaximumPPCurrent(pk.Moves); - - /// - /// Refreshes the Move PP for the desired move. - /// - /// Pokémon to modify. - /// Move PP to refresh. - public static void SetSuggestedMovePP(this PKM pk, int index) => pk.HealPPIndex(index); + pk.SetMaximumPPCurrent(moves); + static int GetPPUpCount(int moveID) => moveID > 0 ? 3 : 0; } + + /// + /// Sets the individual PP Up count values depending if a Move is present in the move slot or not. + /// + /// Pokémon to modify. + public static void SetMaximumPPUps(this PKM pk) => pk.SetMaximumPPUps(pk.Moves); + + /// + /// Updates the and updates the current PP counts. + /// + /// Pokémon to modify. + /// to set. Will be resized if 4 entries are not present. + /// Option to maximize PP Ups + 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(); + } + + /// + /// Updates the individual PP count values for each move slot based on the maximum possible value. + /// + /// Pokémon to modify. + /// to use (if already known). Will fetch the current if not provided. + public static void SetMaximumPPCurrent(this PKM pk, ReadOnlySpan 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); + } + + /// + /// Updates the individual PP count values for each move slot based on the maximum possible value. + /// + /// Pokémon to modify. + public static void SetMaximumPPCurrent(this PKM pk) => pk.SetMaximumPPCurrent(pk.Moves); + + /// + /// Refreshes the Move PP for the desired move. + /// + /// Pokémon to modify. + /// Move PP to refresh. + public static void SetSuggestedMovePP(this PKM pk, int index) => pk.HealPPIndex(index); } diff --git a/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs b/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs index 0af1f3d98..03fcd674e 100644 --- a/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/MoveSetApplicator.cs @@ -2,120 +2,119 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for getting valid movesets. +/// +public static class MoveSetApplicator { /// - /// Logic for getting valid movesets. + /// Gets a moveset for the provided data. /// - public static class MoveSetApplicator + /// PKM to generate for + /// Full movepool & shuffling + /// 4 moves + public static int[] GetMoveSet(this PKM pk, bool random = false) { - /// - /// Gets a moveset for the provided data. - /// - /// PKM to generate for - /// Full movepool & shuffling - /// 4 moves - 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); + } - /// - /// Gets a moveset for the provided data. - /// - /// Precomputed optional - /// Full movepool & shuffling - /// 4 moves - public static int[] GetMoveSet(this LegalityAnalysis la, bool random = false) - { - int[] m = la.GetSuggestedCurrentMoves(random ? MoveSourceType.All : MoveSourceType.Encounter); + /// + /// Gets a moveset for the provided data. + /// + /// Precomputed optional + /// Full movepool & shuffling + /// 4 moves + 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; + } + + /// + /// Fetches based on the provided . + /// + /// Pokémon to modify. + /// Encounter the relearn moves should be suggested for. If not provided, will try to detect it via legality analysis. + /// best suited for the current data. + public static IReadOnlyList GetSuggestedRelearnMoves(this PKM pk, IEncounterTemplate? enc = null) => GetSuggestedRelearnMoves(new LegalityAnalysis(pk), enc); + + /// + /// Fetches based on the provided . + /// + /// which contains parsed information pertaining to legality. + /// Encounter the relearn moves should be suggested for. If not provided, will try to detect it via legality analysis. + /// best suited for the current data. + public static IReadOnlyList 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 }; + } } - /// - /// Fetches based on the provided . - /// - /// Pokémon to modify. - /// Encounter the relearn moves should be suggested for. If not provided, will try to detect it via legality analysis. - /// best suited for the current data. - public static IReadOnlyList GetSuggestedRelearnMoves(this PKM pk, IEncounterTemplate? enc = null) => GetSuggestedRelearnMoves(new LegalityAnalysis(pk), enc); - - /// - /// Fetches based on the provided . - /// - /// which contains parsed information pertaining to legality. - /// Encounter the relearn moves should be suggested for. If not provided, will try to detect it via legality analysis. - /// best suited for the current data. - public static IReadOnlyList 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; } } diff --git a/PKHeX.Core/Editing/Applicators/RibbonApplicator.cs b/PKHeX.Core/Editing/Applicators/RibbonApplicator.cs index 3bc2fbd0b..2ffaf1258 100644 --- a/PKHeX.Core/Editing/Applicators/RibbonApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/RibbonApplicator.cs @@ -1,241 +1,241 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for applying ribbons. +/// +public static class RibbonApplicator { + private static List GetAllRibbonNames(PKM pk) => RibbonInfo.GetRibbonInfo(pk).Select(z => z.Name).ToList(); + /// - /// Logic for applying ribbons. + /// Gets a list of valid ribbons for the . /// - public static class RibbonApplicator + /// Entity to fetch the list for. + /// All ribbon names. + /// List of all valid ribbon names. + public static IReadOnlyList GetValidRibbons(PKM pk, IList allRibbons) { - private static List GetAllRibbonNames(PKM pkm) => RibbonInfo.GetRibbonInfo(pkm).Select(z => z.Name).ToList(); + var clone = pk.Clone(); + return SetAllValidRibbons(allRibbons, clone); + } - /// - /// Gets a list of valid ribbons for the . - /// - /// Entity to fetch the list for. - /// All ribbon names. - /// List of all valid ribbon names. - public static IReadOnlyList GetValidRibbons(PKM pkm, IList allRibbons) + /// + /// Gets a list of valid ribbons for the . + /// + /// Entity to fetch the list for. + /// List of all valid ribbon names. + public static IReadOnlyList GetValidRibbons(PKM pk) + { + var names = GetAllRibbonNames(pk); + return GetValidRibbons(pk, names); + } + + /// + /// Gets a list of valid ribbons for the that can be removed. + /// + /// Entity to fetch the list for. + /// All ribbon names. + /// List of all removable ribbon names. + public static IReadOnlyList GetRemovableRibbons(PKM pk, IList allRibbons) + { + var clone = pk.Clone(); + return RemoveAllValidRibbons(allRibbons, clone); + } + + /// + /// Gets a list of valid ribbons for the that can be removed. + /// + /// Entity to fetch the list for. + /// List of all removable ribbon names. + public static IReadOnlyList GetRemovableRibbons(PKM pk) + { + var names = GetAllRibbonNames(pk); + return GetRemovableRibbons(pk, names); + } + + /// + /// Sets all valid ribbons to the . + /// + /// Entity to set ribbons for. + /// True if any ribbons were applied. + 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); + } + + /// + /// Sets all valid ribbons to the . + /// + /// Entity to set ribbons for. + /// Ribbon names to try setting. + /// True if any ribbons were applied. + public static bool SetAllValidRibbons(PKM pk, List ribNames) + { + var list = SetAllValidRibbons(ribNames, pk); + return list.Count != 0; + } + + private static IReadOnlyList SetAllValidRibbons(IList allRibbons, PKM pk) + { + var la = new LegalityAnalysis(pk); + var valid = new List(); + + while (TryApplyAllRibbons(pk, la, allRibbons, valid) != 0) { - var pk = pkm.Clone(); - return SetAllValidRibbons(allRibbons, pk); + // Repeat the operation until no more ribbons are set. } - /// - /// Gets a list of valid ribbons for the . - /// - /// Entity to fetch the list for. - /// List of all valid ribbon names. - public static IReadOnlyList GetValidRibbons(PKM pkm) + // Ribbon Deadlock + if (pk is IRibbonSetCommon6 c6) + InvertDeadlockContest(c6, la, true); + + return valid; + } + + /// + /// Sets all valid ribbons to the . + /// + /// Entity to set ribbons for. + /// True if any ribbons were removed. + public static bool RemoveAllValidRibbons(PKM pk) + { + var ribNames = GetAllRibbonNames(pk); + return RemoveAllValidRibbons(pk, ribNames); + } + + /// + /// Sets all valid ribbons to the . + /// + /// Entity to set ribbons for. + /// Ribbon names to try setting. + /// True if any ribbons were removed. + public static bool RemoveAllValidRibbons(PKM pk, List ribNames) + { + var list = RemoveAllValidRibbons(ribNames, pk); + return list.Count != 0; + } + + private static IReadOnlyList RemoveAllValidRibbons(IList allRibbons, PKM pk) + { + var la = new LegalityAnalysis(pk); + var valid = new List(); + + // 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. } - /// - /// Gets a list of valid ribbons for the that can be removed. - /// - /// Entity to fetch the list for. - /// All ribbon names. - /// List of all removable ribbon names. - public static IReadOnlyList GetRemovableRibbons(PKM pkm, IList allRibbons) + return valid; + } + + private static int TryApplyAllRibbons(PKM pk, LegalityAnalysis la, IList allRibbons, ICollection valid) + { + int applied = 0; + for (int i = 0; i < allRibbons.Count;) { - var pk = pkm.Clone(); - return RemoveAllValidRibbons(allRibbons, pk); - } - - /// - /// Gets a list of valid ribbons for the that can be removed. - /// - /// Entity to fetch the list for. - /// List of all removable ribbon names. - public static IReadOnlyList GetRemovableRibbons(PKM pkm) - { - var names = GetAllRibbonNames(pkm); - return GetRemovableRibbons(pkm, names); - } - - /// - /// Sets all valid ribbons to the . - /// - /// Entity to set ribbons for. - /// True if any ribbons were applied. - 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); - } - - /// - /// Sets all valid ribbons to the . - /// - /// Entity to set ribbons for. - /// Ribbon names to try setting. - /// True if any ribbons were applied. - public static bool SetAllValidRibbons(PKM pkm, List ribNames) - { - var list = SetAllValidRibbons(ribNames, pkm); - return list.Count != 0; - } - - private static IReadOnlyList SetAllValidRibbons(IList allRibbons, PKM pk) - { - var la = new LegalityAnalysis(pk); - var valid = new List(); - - 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; - } - - /// - /// Sets all valid ribbons to the . - /// - /// Entity to set ribbons for. - /// True if any ribbons were removed. - public static bool RemoveAllValidRibbons(PKM pkm) - { - var ribNames = GetAllRibbonNames(pkm); - return RemoveAllValidRibbons(pkm, ribNames); - } - - /// - /// Sets all valid ribbons to the . - /// - /// Entity to set ribbons for. - /// Ribbon names to try setting. - /// True if any ribbons were removed. - public static bool RemoveAllValidRibbons(PKM pkm, List ribNames) - { - var list = RemoveAllValidRibbons(ribNames, pkm); - return list.Count != 0; - } - - private static IReadOnlyList RemoveAllValidRibbons(IList allRibbons, PKM pk) - { - var la = new LegalityAnalysis(pk); - var valid = new List(); - - // 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 allRibbons, ICollection 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 allRibbons, ICollection 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 allRibbons, ICollection 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; + } } diff --git a/PKHeX.Core/Editing/Applicators/TechnicalRecordApplicator.cs b/PKHeX.Core/Editing/Applicators/TechnicalRecordApplicator.cs index f37ba557f..44b19bb04 100644 --- a/PKHeX.Core/Editing/Applicators/TechnicalRecordApplicator.cs +++ b/PKHeX.Core/Editing/Applicators/TechnicalRecordApplicator.cs @@ -1,65 +1,64 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for modifying the Technical Record flags of a . +/// +public static class TechnicalRecordApplicator { /// - /// Logic for modifying the Technical Record flags of a . + /// Sets the Technical Record flags for the . /// - public static class TechnicalRecordApplicator + /// Pokémon to modify. + /// Value to set for the record. + /// Max record to set. + public static void SetRecordFlags(this ITechRecord8 pk, bool value, int max = 100) { - /// - /// Sets the Technical Record flags for the . - /// - /// Pokémon to modify. - /// Value to set for the record. - /// Max record to set. - public static void SetRecordFlags(this ITechRecord8 pk, bool value, int max = 100) + for (int i = 0; i < max; i++) + pk.SetMoveRecordFlag(i, value); + } + + /// + /// Clears the Technical Record flags for the . + /// + /// Pokémon to modify. + public static void ClearRecordFlags(this ITechRecord8 pk) => pk.SetRecordFlags(false, 112); + + /// + /// Sets the Technical Record flags for the based on the current moves. + /// + /// Pokémon to modify. + /// Moves to set flags for. If a move is not a Technical Record, it is skipped. + public static void SetRecordFlags(this ITechRecord8 pk, IEnumerable 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); } + } - /// - /// Clears the Technical Record flags for the . - /// - /// Pokémon to modify. - public static void ClearRecordFlags(this ITechRecord8 pk) => pk.SetRecordFlags(false, 112); - - /// - /// Sets the Technical Record flags for the based on the current moves. - /// - /// Pokémon to modify. - /// Moves to set flags for. If a move is not a Technical Record, it is skipped. - public static void SetRecordFlags(this ITechRecord8 pk, IEnumerable moves) + /// + /// Sets all the Technical Record flags for the if they are permitted to be learned in-game. + /// + /// Pokémon to modify. + 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); - } - } - - /// - /// Sets all the Technical Record flags for the if they are permitted to be learned in-game. - /// - /// Pokémon to modify. - 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); } } } diff --git a/PKHeX.Core/Editing/Bulk/BatchEditing.cs b/PKHeX.Core/Editing/Bulk/BatchEditing.cs index ffa80fea5..5edc6eb6b 100644 --- a/PKHeX.Core/Editing/Bulk/BatchEditing.cs +++ b/PKHeX.Core/Editing/Bulk/BatchEditing.cs @@ -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; + +/// +/// Logic for editing many with user provided list. +/// +public static class BatchEditing { - /// - /// Logic for editing many with user provided list. - /// - 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), + }; + + /// + /// Extra properties to show in the list of selectable properties (GUI) + /// + public static readonly List CustomProperties = new() + { + PROP_LEGAL, PROP_TYPENAME, PROP_RIBBONS, PROP_CONTESTSTATS, PROP_MOVEMASTERY, + IdentifierContains, nameof(ISlotInfo.Slot), nameof(SlotInfoBox.Box), + }; + + /// + /// Property names, indexed by . + /// + public static string[][] Properties => GetProperties.Value; + + private static readonly Lazy GetProperties = new(() => GetPropArray(Types, CustomProperties)); + + private static readonly Dictionary[] Props = GetPropertyDictionaries(Types); + + private static Dictionary[] GetPropertyDictionaries(IReadOnlyList types) + { + var result = new Dictionary[types.Count]; + for (int i = 0; i < types.Count; i++) + result[i] = GetPropertyDictionary(types[i], ReflectUtil.GetAllPropertyInfoPublic); + return result; + } + + private static Dictionary GetPropertyDictionary(Type type, Func> selector) + { + var dict = new Dictionary(); + 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; + } - /// - /// Extra properties to show in the list of selectable properties (GUI) - /// - public static readonly List 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 types, IReadOnlyList 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), - }; - - /// - /// Property names, indexed by . - /// - public static string[][] Properties => GetProperties.Value; - - private static readonly Lazy GetProperties = new(() => GetPropArray(Types, CustomProperties)); - - private static readonly Dictionary[] Props = GetPropertyDictionaries(Types); - - private static Dictionary[] GetPropertyDictionaries(IReadOnlyList types) - { - var result = new Dictionary[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 GetPropertyDictionary(Type type, Func> selector) + // Properties for any PKM + // Properties shared by all PKM + var first = p[0]; + var any = new HashSet(first); + var all = new HashSet(first); + for (int i = 1; i < p.Length; i++) { - var dict = new Dictionary(); - 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); + /// + /// Tries to fetch the property from the cache of available properties. + /// + /// Pokémon to check + /// Property Name to check + /// Property Info retrieved (if any). + /// True if has property, false if does not. + 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 types, IReadOnlyList extra) + /// + /// Tries to fetch the property from the cache of available properties. + /// + /// Type to check + /// Property Name to check + /// Property Info retrieved (if any). + /// True if has property, false if does not. + 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(first); - var all = new HashSet(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); + } - /// - /// Tries to fetch the property from the cache of available properties. - /// - /// Pokémon to check - /// Property Name to check - /// Property Info retrieved (if any). - /// True if has property, false if does not. - public static bool TryGetHasProperty(PKM pk, string name, [NotNullWhen(true)] out PropertyInfo? pi) + /// + /// Gets a list of types that implement the requested . + /// + public static IEnumerable GetTypesImplementing(string property) + { + for (int i = 0; i < Types.Length; i++) { - var type = pk.GetType(); - return TryGetHasProperty(type, name, out pi); - } - - /// - /// Tries to fetch the property from the cache of available properties. - /// - /// Type to check - /// Property Name to check - /// Property Info retrieved (if any). - /// True if has property, false if does not. - 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); - } - - /// - /// Gets a list of types that implement the requested . - /// - public static IEnumerable 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}"; - } - } - - /// - /// Gets the type of the property using the saved cache of properties. - /// - /// Property Name to fetch the type for - /// Type index (within . Leave empty (0) for a nonspecific format. - /// Short name of the property's type. - 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; - } - - /// - /// Initializes the list with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index. - /// - /// Instructions to initialize. - public static void ScreenStrings(IEnumerable 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); - } - } - - /// - /// Initializes the with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index. - /// - /// Instruction to initialize. - 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; - } - } - - /// - /// Checks if the object is filtered by the provided . - /// - /// Filters which must be satisfied. - /// Object to check. - /// True if matches all filters. - public static bool IsFilterMatch(IEnumerable filters, PKM pk) => filters.All(z => IsFilterMatch(z, pk, Props[Array.IndexOf(Types, pk.GetType())])); - - /// - /// Checks if the object is filtered by the provided . - /// - /// Filters which must be satisfied. - /// Object to check. - /// True if matches all filters. - public static bool IsFilterMatchMeta(IEnumerable 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; - } - - /// - /// Checks if the object is filtered by the provided . - /// - /// Filters which must be satisfied. - /// Object to check. - /// True if matches all filters. - public static bool IsFilterMatch(IEnumerable 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; - } - - /// - /// Tries to modify the . - /// - /// Object to modify. - /// Filters which must be satisfied prior to any modifications being made. - /// Modifications to perform on the . - /// Result of the attempted modification. - public static bool TryModify(PKM pk, IEnumerable filters, IEnumerable modifications) - { - var result = TryModifyPKM(pk, filters, modifications); - return result == ModifyResult.Modified; - } - - /// - /// Tries to modify the . - /// - /// Command Filter - /// Filters which must be satisfied prior to any modifications being made. - /// Modifications to perform on the . - /// Result of the attempted modification. - internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable filters, IEnumerable 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; - } - - /// - /// Sets the if the should be filtered due to the provided. - /// - /// Command Filter - /// Pokémon to check. - /// PropertyInfo cache (optional) - /// True if filtered, else false. - private static ModifyResult SetPKMProperty(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary 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; - } - - /// - /// Checks if the should be filtered due to the provided. - /// - /// Command Filter - /// Pokémon to check. - /// PropertyInfo cache (optional) - /// True if filter matches, else false. - private static bool IsFilterMatch(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary 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); - } - - /// - /// Checks if the should be filtered due to the provided. - /// - /// Command Filter - /// Pokémon to check. - /// PropertyInfo cache (optional) - /// True if filter matches, else false. - private static bool IsFilterMatch(StringInstruction cmd, PKM pk, IReadOnlyDictionary props) - { - var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName)); - if (match != null) - return match.IsFiltered(pk, cmd); - return IsPropertyFiltered(cmd, pk, props); - } - - /// - /// Checks if the should be filtered due to the provided. - /// - /// Command Filter - /// Pokémon to check. - /// PropertyInfo cache - /// True if filtered, else false. - private static bool IsPropertyFiltered(StringInstruction cmd, PKM pk, IReadOnlyDictionary props) - { - if (!props.TryGetValue(cmd.PropertyName, out var pi)) - return false; - if (!pi.CanRead) - return false; - return pi.IsValueEqual(pk, cmd.PropertyValue) == cmd.Evaluator; - } - - /// - /// Sets the data with a suggested value based on its . - /// - /// Property to modify. - /// Cached info storing Legal data. - /// Suggestion string which starts with - 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; - } - - /// - /// Sets the byte array property to a specified value. - /// - /// Pokémon to modify. - /// Modification - 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)); - } - } - - /// - /// Sets the property to a non-specific smart value. - /// - /// Pokémon to modify. - /// Modification - /// True if modified, false if no modifications done. - 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; - } - - /// - /// Sets the IV(s) to a random value. - /// - /// Pokémon to modify. - /// Modification - 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}"; } } + + /// + /// Gets the type of the property using the saved cache of properties. + /// + /// Property Name to fetch the type for + /// Type index (within . Leave empty (0) for a nonspecific format. + /// Short name of the property's type. + 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; + } + + /// + /// Initializes the list with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index. + /// + /// Instructions to initialize. + public static void ScreenStrings(IEnumerable 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); + } + } + + /// + /// Initializes the with a context-sensitive value. If the provided value is a string, it will attempt to convert that string to its corresponding index. + /// + /// Instruction to initialize. + 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; + } + } + + /// + /// Checks if the object is filtered by the provided . + /// + /// Filters which must be satisfied. + /// Object to check. + /// True if matches all filters. + public static bool IsFilterMatch(IEnumerable filters, PKM pk) => filters.All(z => IsFilterMatch(z, pk, Props[Array.IndexOf(Types, pk.GetType())])); + + /// + /// Checks if the object is filtered by the provided . + /// + /// Filters which must be satisfied. + /// Object to check. + /// True if matches all filters. + public static bool IsFilterMatchMeta(IEnumerable 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; + } + + /// + /// Checks if the object is filtered by the provided . + /// + /// Filters which must be satisfied. + /// Object to check. + /// True if matches all filters. + public static bool IsFilterMatch(IEnumerable 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; + } + + /// + /// Tries to modify the . + /// + /// Object to modify. + /// Filters which must be satisfied prior to any modifications being made. + /// Modifications to perform on the . + /// Result of the attempted modification. + public static bool TryModify(PKM pk, IEnumerable filters, IEnumerable modifications) + { + var result = TryModifyPKM(pk, filters, modifications); + return result == ModifyResult.Modified; + } + + /// + /// Tries to modify the . + /// + /// Command Filter + /// Filters which must be satisfied prior to any modifications being made. + /// Modifications to perform on the . + /// Result of the attempted modification. + internal static ModifyResult TryModifyPKM(PKM pk, IEnumerable filters, IEnumerable 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; + } + + /// + /// Sets the if the should be filtered due to the provided. + /// + /// Command Filter + /// Pokémon to check. + /// PropertyInfo cache (optional) + /// True if filtered, else false. + private static ModifyResult SetPKMProperty(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary 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; + } + + /// + /// Checks if the should be filtered due to the provided. + /// + /// Command Filter + /// Pokémon to check. + /// PropertyInfo cache (optional) + /// True if filter matches, else false. + private static bool IsFilterMatch(StringInstruction cmd, BatchInfo info, IReadOnlyDictionary 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); + } + + /// + /// Checks if the should be filtered due to the provided. + /// + /// Command Filter + /// Pokémon to check. + /// PropertyInfo cache (optional) + /// True if filter matches, else false. + private static bool IsFilterMatch(StringInstruction cmd, PKM pk, IReadOnlyDictionary props) + { + var match = BatchFilters.FilterMods.Find(z => z.IsMatch(cmd.PropertyName)); + if (match != null) + return match.IsFiltered(pk, cmd); + return IsPropertyFiltered(cmd, pk, props); + } + + /// + /// Checks if the should be filtered due to the provided. + /// + /// Command Filter + /// Pokémon to check. + /// PropertyInfo cache + /// True if filtered, else false. + private static bool IsPropertyFiltered(StringInstruction cmd, PKM pk, IReadOnlyDictionary props) + { + if (!props.TryGetValue(cmd.PropertyName, out var pi)) + return false; + if (!pi.CanRead) + return false; + return pi.IsValueEqual(pk, cmd.PropertyValue) == cmd.Evaluator; + } + + /// + /// Sets the data with a suggested value based on its . + /// + /// Property to modify. + /// Cached info storing Legal data. + /// Suggestion string which starts with + 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; + } + + /// + /// Sets the byte array property to a specified value. + /// + /// Pokémon to modify. + /// Modification + 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)); + } + } + + /// + /// Sets the property to a non-specific smart value. + /// + /// Pokémon to modify. + /// Modification + /// True if modified, false if no modifications done. + 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; + } + + /// + /// Sets the IV(s) to a random value. + /// + /// Pokémon to modify. + /// Modification + 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)); + } } diff --git a/PKHeX.Core/Editing/Bulk/BatchEditor.cs b/PKHeX.Core/Editing/Bulk/BatchEditor.cs index ed6b5ac84..668166541 100644 --- a/PKHeX.Core/Editing/Bulk/BatchEditor.cs +++ b/PKHeX.Core/Editing/Bulk/BatchEditor.cs @@ -4,83 +4,82 @@ using System.Linq; using static PKHeX.Core.MessageStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Carries out a batch edit and contains information summarizing the results. +/// +public sealed class BatchEditor { + private int Modified { get; set; } + private int Iterated { get; set; } + private int Failed { get; set; } + /// - /// Carries out a batch edit and contains information summarizing the results. + /// Tries to modify the . /// - public sealed class BatchEditor + /// Object to modify. + /// Filters which must be satisfied prior to any modifications being made. + /// Modifications to perform on the . + /// Result of the attempted modification. + public bool Process(PKM pk, IEnumerable filters, IEnumerable modifications) { - private int Modified { get; set; } - private int Iterated { get; set; } - private int Failed { get; set; } - - /// - /// Tries to modify the . - /// - /// Object to modify. - /// Filters which must be satisfied prior to any modifications being made. - /// Modifications to perform on the . - /// Result of the attempted modification. - public bool Process(PKM pkm, IEnumerable filters, IEnumerable 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; } - /// - /// Gets a message indicating the overall result of all modifications performed across multiple Batch Edit jobs. - /// - /// Collection of modifications. - /// Friendly (multi-line) string indicating the result of the batch edits. - public string GetEditorResults(ICollection 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; + } + + /// + /// Gets a message indicating the overall result of all modifications performed across multiple Batch Edit jobs. + /// + /// Collection of modifications. + /// Friendly (multi-line) string indicating the result of the batch edits. + public string GetEditorResults(ICollection 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 lines, IEnumerable 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 lines, IEnumerable 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; } } diff --git a/PKHeX.Core/Editing/Bulk/BatchFilters.cs b/PKHeX.Core/Editing/Bulk/BatchFilters.cs index b32113aa7..4d244d35e 100644 --- a/PKHeX.Core/Editing/Bulk/BatchFilters.cs +++ b/PKHeX.Core/Editing/Bulk/BatchFilters.cs @@ -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 FilterMods = new() { - public static readonly List 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 FilterMeta = new() - { - new MetaFilter(IdentifierContains, - (obj, cmd) => obj is SlotCache s && s.Identify().Contains(cmd.PropertyValue) == cmd.Evaluator), + public static readonly List 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), + }; } diff --git a/PKHeX.Core/Editing/Bulk/BatchInfo.cs b/PKHeX.Core/Editing/Bulk/BatchInfo.cs index a0a2534d9..54a991fdd 100644 --- a/PKHeX.Core/Editing/Bulk/BatchInfo.cs +++ b/PKHeX.Core/Editing/Bulk/BatchInfo.cs @@ -1,19 +1,18 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information wrapper used for Batch Editing to apply suggested values. +/// +public sealed class BatchInfo { - /// - /// Information wrapper used for Batch Editing to apply suggested values. - /// - 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 SuggestedRelearn => Legality.GetSuggestedRelearnMoves(); - } + public bool Legal => Legality.Valid; + internal IReadOnlyList SuggestedRelearn => Legality.GetSuggestedRelearnMoves(); } diff --git a/PKHeX.Core/Editing/Bulk/BatchMods.cs b/PKHeX.Core/Editing/Bulk/BatchMods.cs index d4992ba5e..624216e13 100644 --- a/PKHeX.Core/Editing/Bulk/BatchMods.cs +++ b/PKHeX.Core/Editing/Bulk/BatchMods.cs @@ -3,95 +3,94 @@ 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 SuggestionMods = new() { - public static readonly List SuggestionMods = new() - { - // Interface Specific - new TypeSuggestion(nameof(ICombatPower.Stat_CP), p => p.ResetCP()), - new TypeSuggestion(nameof(IScaledSizeValue.HeightAbsolute), p => p.ResetHeight()), - new TypeSuggestion(nameof(IScaledSizeValue.WeightAbsolute), p => p.ResetWeight()), - new TypeSuggestion(nameof(Extensions.HyperTrainClear), p => p.HyperTrainClear()), - new TypeSuggestion(nameof(Extensions.ClearGeoLocationData), p => p.ClearGeoLocationData()), - new TypeSuggestion(nameof(Extensions.AwakeningClear), p => p.AwakeningClear()), - new TypeSuggestion(nameof(Extensions.AwakeningMax), p => p.AwakeningMax()), - new TypeSuggestion(nameof(GanbaruExtensions.ClearGanbaruValues), p => p.ClearGanbaruValues()), - new TypeSuggestion(nameof(GanbaruExtensions.SetSuggestedGanbaruValues), p => p.SetSuggestedGanbaruValues((PKM)p)), + // Interface Specific + new TypeSuggestion(nameof(ICombatPower.Stat_CP), p => p.ResetCP()), + new TypeSuggestion(nameof(IScaledSizeValue.HeightAbsolute), p => p.ResetHeight()), + new TypeSuggestion(nameof(IScaledSizeValue.WeightAbsolute), p => p.ResetWeight()), + new TypeSuggestion(nameof(Extensions.HyperTrainClear), p => p.HyperTrainClear()), + new TypeSuggestion(nameof(Extensions.ClearGeoLocationData), p => p.ClearGeoLocationData()), + new TypeSuggestion(nameof(Extensions.AwakeningClear), p => p.AwakeningClear()), + new TypeSuggestion(nameof(Extensions.AwakeningMax), p => p.AwakeningMax()), + new TypeSuggestion(nameof(GanbaruExtensions.ClearGanbaruValues), p => p.ClearGanbaruValues()), + new TypeSuggestion(nameof(GanbaruExtensions.SetSuggestedGanbaruValues), p => p.SetSuggestedGanbaruValues((PKM)p)), - // Date Copy - new TypeSuggestion(nameof(PKM.EggMetDate), p => p.EggMetDate = p.MetDate), - new TypeSuggestion(nameof(PKM.MetDate), p => p.MetDate = p.EggMetDate), + // Date Copy + new TypeSuggestion(nameof(PKM.EggMetDate), p => p.EggMetDate = p.MetDate), + new TypeSuggestion(nameof(PKM.MetDate), p => p.MetDate = p.EggMetDate), - new TypeSuggestion(nameof(PKM.Nature), p => p.Format >= 8, p => p.Nature = p.StatNature), - new TypeSuggestion(nameof(PKM.StatNature), p => p.Format >= 8, p => p.StatNature = p.Nature), - new TypeSuggestion(nameof(PKM.Stats), p => p.ResetPartyStats()), - new TypeSuggestion(nameof(PKM.Ball), p => BallApplicator.ApplyBallLegalByColor(p)), - new TypeSuggestion(nameof(PKM.Heal), p => p.Heal()), - new TypeSuggestion(nameof(PKM.HealPP), p => p.HealPP()), - new TypeSuggestion(nameof(IHyperTrain.HyperTrainFlags), p => p.SetSuggestedHyperTrainingData()), + new TypeSuggestion(nameof(PKM.Nature), p => p.Format >= 8, p => p.Nature = p.StatNature), + new TypeSuggestion(nameof(PKM.StatNature), p => p.Format >= 8, p => p.StatNature = p.Nature), + new TypeSuggestion(nameof(PKM.Stats), p => p.ResetPartyStats()), + new TypeSuggestion(nameof(PKM.Ball), p => BallApplicator.ApplyBallLegalByColor(p)), + new TypeSuggestion(nameof(PKM.Heal), p => p.Heal()), + new TypeSuggestion(nameof(PKM.HealPP), p => p.HealPP()), + new TypeSuggestion(nameof(IHyperTrain.HyperTrainFlags), p => p.SetSuggestedHyperTrainingData()), - new TypeSuggestion(nameof(PKM.Move1_PP), p => p.SetSuggestedMovePP(0)), - new TypeSuggestion(nameof(PKM.Move2_PP), p => p.SetSuggestedMovePP(1)), - new TypeSuggestion(nameof(PKM.Move3_PP), p => p.SetSuggestedMovePP(2)), - new TypeSuggestion(nameof(PKM.Move4_PP), p => p.SetSuggestedMovePP(3)), + new TypeSuggestion(nameof(PKM.Move1_PP), p => p.SetSuggestedMovePP(0)), + new TypeSuggestion(nameof(PKM.Move2_PP), p => p.SetSuggestedMovePP(1)), + new TypeSuggestion(nameof(PKM.Move3_PP), p => p.SetSuggestedMovePP(2)), + new TypeSuggestion(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 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 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 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 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, + }; } diff --git a/PKHeX.Core/Editing/Bulk/ComplexSet/ComplexFilter.cs b/PKHeX.Core/Editing/Bulk/ComplexSet/ComplexFilter.cs index efc98373d..b16f7e445 100644 --- a/PKHeX.Core/Editing/Bulk/ComplexSet/ComplexFilter.cs +++ b/PKHeX.Core/Editing/Bulk/ComplexSet/ComplexFilter.cs @@ -1,26 +1,25 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +public sealed class ComplexFilter : IComplexFilter { - /// - public sealed class ComplexFilter : IComplexFilter + private readonly string Property; + private readonly Func FilterPKM; + private readonly Func FilterBulk; + + public ComplexFilter( + string property, + Func filterPkm, + Func filterBulk) { - private readonly string Property; - private readonly Func FilterPKM; - private readonly Func FilterBulk; - - public ComplexFilter( - string property, - Func filterPkm, - Func 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); } diff --git a/PKHeX.Core/Editing/Bulk/ComplexSet/ComplexSet.cs b/PKHeX.Core/Editing/Bulk/ComplexSet/ComplexSet.cs index 165e7b4b5..a388862d2 100644 --- a/PKHeX.Core/Editing/Bulk/ComplexSet/ComplexSet.cs +++ b/PKHeX.Core/Editing/Bulk/ComplexSet/ComplexSet.cs @@ -1,24 +1,23 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +public sealed class ComplexSet : IComplexSet { - /// - public sealed class ComplexSet : IComplexSet + public readonly string PropertyName; + public readonly Func IsValueCompatible = _ => true; + private readonly Action Action; + + public ComplexSet(string propertyName, Action modify) { - public readonly string PropertyName; - public readonly Func IsValueCompatible = _ => true; - private readonly Action Action; - - public ComplexSet(string propertyName, Action modify) - { - PropertyName = propertyName; - Action = modify; - } - - public ComplexSet(string propertyName, Func criteria, Action 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 criteria, Action 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); } diff --git a/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilter.cs b/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilter.cs index ae43cc5f4..f605a347f 100644 --- a/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilter.cs +++ b/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilter.cs @@ -1,12 +1,11 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Complex filter of data based on a string value. +/// +public interface IComplexFilter { - /// - /// Complex filter of data based on a string value. - /// - 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); } diff --git a/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilterMeta.cs b/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilterMeta.cs index c21e88ad6..aed61f80f 100644 --- a/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilterMeta.cs +++ b/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexFilterMeta.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Complex filter of data based on a string value. +/// +public interface IComplexFilterMeta { - /// - /// Complex filter of data based on a string value. - /// - public interface IComplexFilterMeta - { - bool IsMatch(string prop); - bool IsFiltered(object cache, StringInstruction value); - } + bool IsMatch(string prop); + bool IsFiltered(object cache, StringInstruction value); } diff --git a/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexSet.cs b/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexSet.cs index 1042dee85..a5626f853 100644 --- a/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexSet.cs +++ b/PKHeX.Core/Editing/Bulk/ComplexSet/IComplexSet.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Complex modification of data to a string value. +/// +public interface IComplexSet { - /// - /// Complex modification of data to a string value. - /// - 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); } diff --git a/PKHeX.Core/Editing/Bulk/ComplexSet/MetaFilter.cs b/PKHeX.Core/Editing/Bulk/ComplexSet/MetaFilter.cs index 0d8de2ead..c6d0da6a3 100644 --- a/PKHeX.Core/Editing/Bulk/ComplexSet/MetaFilter.cs +++ b/PKHeX.Core/Editing/Bulk/ComplexSet/MetaFilter.cs @@ -1,22 +1,21 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +public sealed class MetaFilter : IComplexFilterMeta { - /// - public sealed class MetaFilter : IComplexFilterMeta + private readonly string Property; + private readonly Func FilterPKM; + + public MetaFilter( + string property, + Func filterPkm) { - private readonly string Property; - private readonly Func FilterPKM; - - public MetaFilter( - string property, - Func 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); } diff --git a/PKHeX.Core/Editing/Bulk/ModifyResult.cs b/PKHeX.Core/Editing/Bulk/ModifyResult.cs index b4ef63bc0..ddcdcb352 100644 --- a/PKHeX.Core/Editing/Bulk/ModifyResult.cs +++ b/PKHeX.Core/Editing/Bulk/ModifyResult.cs @@ -1,28 +1,27 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Batch Editor Modification result for an individual . +/// +public enum ModifyResult { /// - /// Batch Editor Modification result for an individual . + /// The has invalid data and is not a suitable candidate for modification. /// - public enum ModifyResult - { - /// - /// The has invalid data and is not a suitable candidate for modification. - /// - Invalid, + Invalid, - /// - /// An error was occurred while iterating modifications for this . - /// - Error, + /// + /// An error was occurred while iterating modifications for this . + /// + Error, - /// - /// The was skipped due to a matching Filter. - /// - Filtered, + /// + /// The was skipped due to a matching Filter. + /// + Filtered, - /// - /// The was modified. - /// - Modified, - } + /// + /// The was modified. + /// + Modified, } diff --git a/PKHeX.Core/Editing/Bulk/StringInstruction.cs b/PKHeX.Core/Editing/Bulk/StringInstruction.cs index b63ebfc44..37dffe9cd 100644 --- a/PKHeX.Core/Editing/Bulk/StringInstruction.cs +++ b/PKHeX.Core/Editing/Bulk/StringInstruction.cs @@ -2,123 +2,122 @@ using System.Collections.Generic; using System.Diagnostics; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Batch Editing instruction +/// +/// +/// Can be a filter (skip), or a modification instruction (modify) +/// +/// +/// +/// +public sealed class StringInstruction { + public string PropertyName { get; } + public string PropertyValue { get; private set; } + + /// True if ==, false if != + 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 Prefixes = new[] { Apply, Require, Exclude }; + private const char Exclude = '!'; + private const char Require = '='; + private const char Apply = '.'; + private const char SplitRange = ','; + /// - /// Batch Editing instruction + /// Character which divides a property and a value. /// /// - /// Can be a filter (skip), or a modification instruction (modify) + /// Example: + /// =Species=1 + /// The second = is the split. /// - /// - /// - /// - 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); - /// True if ==, false if != - 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 Prefixes = new[] { Apply, Require, Exclude }; - private const char Exclude = '!'; - private const char Require = '='; - private const char Apply = '.'; - private const char SplitRange = ','; - - /// - /// Character which divides a property and a value. - /// - /// - /// Example: - /// =Species=1 - /// The second = is the split. - /// - 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 GetFilters(IEnumerable 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 GetFilters(IEnumerable lines) + public static IEnumerable GetInstructions(IEnumerable 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 GetInstructions(IEnumerable 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); } } } diff --git a/PKHeX.Core/Editing/Bulk/StringInstructionSet.cs b/PKHeX.Core/Editing/Bulk/StringInstructionSet.cs index 59d9c0472..dc0a8063b 100644 --- a/PKHeX.Core/Editing/Bulk/StringInstructionSet.cs +++ b/PKHeX.Core/Editing/Bulk/StringInstructionSet.cs @@ -1,38 +1,38 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Processes input of strings into a list of valid Filters and Instructions. +/// +public sealed class StringInstructionSet { - /// - /// Processes input of strings into a list of valid Filters and Instructions. - /// - public sealed class StringInstructionSet + public readonly IReadOnlyList Filters; + public readonly IReadOnlyList Instructions; + + private const string SetSeparator = ";"; + + public StringInstructionSet(IReadOnlyList filters, IReadOnlyList instructions) { - public readonly IReadOnlyList Filters; - public readonly IReadOnlyList Instructions; + Filters = filters; + Instructions = instructions; + } - private const string SetSeparator = ";"; + public StringInstructionSet(ICollection set) + { + Filters = StringInstruction.GetFilters(set).ToList(); + Instructions = StringInstruction.GetInstructions(set).ToList(); + } - public StringInstructionSet(IReadOnlyList filters, IReadOnlyList instructions) + public static IEnumerable GetBatchSets(IList lines) + { + int start = 0; + while (start < lines.Count) { - Filters = filters; - Instructions = instructions; - } - - public StringInstructionSet(ICollection set) - { - Filters = StringInstruction.GetFilters(set).ToList(); - Instructions = StringInstruction.GetInstructions(set).ToList(); - } - - public static IEnumerable GetBatchSets(IList 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); } } } diff --git a/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs b/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs index 0b88cd228..fc2c21742 100644 --- a/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs +++ b/PKHeX.Core/Editing/Bulk/Suggestion/BatchModifications.cs @@ -1,119 +1,117 @@ -using System; -using System.Globalization; +using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Modifications using legality. +/// +internal static class BatchModifications { - /// - /// Modifications using legality. - /// - 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; + } - /// - /// Sets the provided moves in a random order. - /// - /// Pokémon to modify. - /// Moves to apply. - public static ModifyResult SetMoves(PKM pk, ReadOnlySpan 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 evs = stackalloc int[6]; - EffortValues.SetMax(evs, pk); - pk.SetEVs(evs); - return ModifyResult.Modified; - } + /// + /// Sets the provided moves in a random order. + /// + /// Pokémon to modify. + /// Moves to apply. + public static ModifyResult SetMoves(PKM pk, ReadOnlySpan moves) + { + pk.SetMoves(moves); + pk.HealPP(); + return ModifyResult.Modified; + } - /// - /// Sets the contests stats as requested. - /// - /// Pokémon to modify. - /// Legality Information matched to. - /// Option to apply with - 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 evs = stackalloc int[6]; + EffortValues.SetMax(evs, pk); + pk.SetEVs(evs); + return ModifyResult.Modified; + } + + /// + /// Sets the contests stats as requested. + /// + /// Pokémon to modify. + /// Legality Information matched to. + /// Option to apply with + 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; } } diff --git a/PKHeX.Core/Editing/Bulk/Suggestion/ComplexSuggestion.cs b/PKHeX.Core/Editing/Bulk/Suggestion/ComplexSuggestion.cs index a34ed0b4c..93330bf4e 100644 --- a/PKHeX.Core/Editing/Bulk/Suggestion/ComplexSuggestion.cs +++ b/PKHeX.Core/Editing/Bulk/Suggestion/ComplexSuggestion.cs @@ -1,38 +1,37 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +public sealed class ComplexSuggestion : ISuggestModification { - /// - public sealed class ComplexSuggestion : ISuggestModification + public readonly string Keyword; + public readonly Func Criteria = _ => true; + public readonly Func Action; + + public ComplexSuggestion( + string keyword, + Func criteria, + Func action) : this(keyword, action) { - public readonly string Keyword; - public readonly Func Criteria = _ => true; - public readonly Func Action; + Criteria = criteria; + } - public ComplexSuggestion( - string keyword, - Func criteria, - Func action) : this(keyword, action) - { - Criteria = criteria; - } + public ComplexSuggestion( + string keyword, + Func action) + { + Keyword = keyword; + Action = action; + } - public ComplexSuggestion( - string keyword, - Func 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); } } diff --git a/PKHeX.Core/Editing/Bulk/Suggestion/ISuggestModification.cs b/PKHeX.Core/Editing/Bulk/Suggestion/ISuggestModification.cs index 7b5b5374e..45ce8f07b 100644 --- a/PKHeX.Core/Editing/Bulk/Suggestion/ISuggestModification.cs +++ b/PKHeX.Core/Editing/Bulk/Suggestion/ISuggestModification.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Modifies a property to have a "correct" value based on derived legality. +/// +public interface ISuggestModification { - /// - /// Modifies a property to have a "correct" value based on derived legality. - /// - 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); } diff --git a/PKHeX.Core/Editing/Bulk/Suggestion/TypeSuggestion.cs b/PKHeX.Core/Editing/Bulk/Suggestion/TypeSuggestion.cs index 979fd9595..47a943daf 100644 --- a/PKHeX.Core/Editing/Bulk/Suggestion/TypeSuggestion.cs +++ b/PKHeX.Core/Editing/Bulk/Suggestion/TypeSuggestion.cs @@ -1,40 +1,39 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Specific (or not) type +public sealed class TypeSuggestion : ISuggestModification { - /// - /// Specific (or not) type - public sealed class TypeSuggestion : ISuggestModification + public readonly string Keyword; + public readonly Action Action; + public readonly Func Criteria = _ => true; + + public TypeSuggestion(string keyword, Action action) { - public readonly string Keyword; - public readonly Action Action; - public readonly Func Criteria = _ => true; + Keyword = keyword; + Action = (pk, _) => action(pk); + } - public TypeSuggestion(string keyword, Action action) - { - Keyword = keyword; - Action = (pkm, _) => action(pkm); - } + public TypeSuggestion(string keyword, Func criteria, Action action) : this(keyword, action) + { + Criteria = criteria; + } - public TypeSuggestion(string keyword, Func criteria, Action 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; } } diff --git a/PKHeX.Core/Editing/CommonEdits.cs b/PKHeX.Core/Editing/CommonEdits.cs index f015bab34..f63cfcb58 100644 --- a/PKHeX.Core/Editing/CommonEdits.cs +++ b/PKHeX.Core/Editing/CommonEdits.cs @@ -1,446 +1,444 @@ -using System; +using System; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains extension logic for modifying data. +/// +public static class CommonEdits { /// - /// Contains extension logic for modifying data. + /// Setting which enables/disables automatic manipulation of when importing from a . /// - public static class CommonEdits + public static bool ShowdownSetIVMarkings { get; set; } = true; + + /// + /// Setting which causes the to the in Gen8+ formats. + /// + public static bool ShowdownSetBehaviorNature { get; set; } + + /// + /// Sets the to the provided value. + /// + /// Pokémon to modify. + /// to set. If no nickname is provided, the is set to the default value for its current language and format. + public static void SetNickname(this PKM pk, string nick) { - /// - /// Setting which enables/disables automatic manipulation of when importing from a . - /// - public static bool ShowdownSetIVMarkings { get; set; } = true; - - /// - /// Setting which causes the to the in Gen8+ formats. - /// - public static bool ShowdownSetBehaviorNature { get; set; } - - /// - /// Sets the to the provided value. - /// - /// Pokémon to modify. - /// to set. If no nickname is provided, the is set to the default value for its current language and format. - 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; - } - - /// - /// Clears the to the default value. - /// - /// - 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; - } - - /// - /// Sets the value by sanity checking the provided against the possible pool of abilities. - /// - /// Pokémon to modify. - /// Desired to set. - 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); - } - - /// - /// Sets the value based on the provided ability index (0-2) - /// - /// Pokémon to modify. - /// Desired (shifted by 1) to set. - 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); - } - - /// - /// Sets a Random value. The is not updated if the value should match the instead. - /// - /// Accounts for Wurmple evolutions. - /// Pokémon to modify. - 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(); - } - - /// - /// Sets the derived value. - /// - /// Pokémon to modify. - /// Desired state to set. - /// - public static bool SetIsShiny(this PKM pk, bool shiny) => shiny ? SetShiny(pk) : pk.SetUnshiny(); - - /// - /// Makes a shiny. - /// - /// Pokémon to modify. - /// Shiny type to force. Only use Always* or Random - /// Returns true if the data was modified. - 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; - } - - /// - /// Makes a not-shiny. - /// - /// Pokémon to modify. - /// Returns true if the data was modified. - public static bool SetUnshiny(this PKM pk) - { - if (!pk.IsShiny) - return false; - - pk.SetPIDGender(pk.Gender); - return true; - } - - /// - /// Sets the value, with special consideration for the values which derive the value. - /// - /// Pokémon to modify. - /// Desired value to set. - 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; - } - - /// - /// Copies details to the . - /// - /// Pokémon to modify. - /// details to copy from. - 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(); - } - - /// - /// Sets the value depending on the current format and the provided item index & format. - /// - /// Pokémon to modify. - /// Held Item to apply - /// Format required for importing - 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; - } - - /// - /// Sets one of the based on its index within the array. - /// - /// Pokémon to modify. - /// Index to set to - /// Value to set - 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)), - }; - - /// - /// Sets one of the based on its index within the array. - /// - /// Pokémon to modify. - /// Index to set to - /// Value to set - 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)), - }; - - /// - /// Fetches the highest value the provided index can be while considering others. - /// - /// Pokémon to modify. - /// Index to fetch for - /// Highest value the value can be. - 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); - } - - /// - /// Fetches the highest value the provided . - /// - /// Pokémon to modify. - /// Index to fetch for - /// Causes the returned value to be dropped down -1 if the value is already at a maximum. - /// Highest value the value can be. - 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; - } - - /// - /// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game. - /// - /// PKM to apply hatch details to - /// Re-hatch already hatched inputs - 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; + } + + /// + /// Clears the to the default value. + /// + /// + 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; + } + + /// + /// Sets the value by sanity checking the provided against the possible pool of abilities. + /// + /// Pokémon to modify. + /// Desired to set. + 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); + } + + /// + /// Sets the value based on the provided ability index (0-2) + /// + /// Pokémon to modify. + /// Desired (shifted by 1) to set. + 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); + } + + /// + /// Sets a Random value. The is not updated if the value should match the instead. + /// + /// Accounts for Wurmple evolutions. + /// Pokémon to modify. + public static void SetRandomEC(this PKM pk) + { + int gen = pk.Generation; + if (gen is 3 or 4 or 5) + { + pk.EncryptionConstant = pk.PID; + return; } - /// - /// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game. - /// - /// PKM to apply hatch details to - /// Game the egg originated from - /// Game the egg is currently present on - 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(); + } + + /// + /// Sets the derived value. + /// + /// Pokémon to modify. + /// Desired state to set. + public static bool SetIsShiny(this PKM pk, bool shiny) => shiny ? SetShiny(pk) : pk.SetUnshiny(); + + /// + /// Makes a shiny. + /// + /// Pokémon to modify. + /// Shiny type to force. Only use Always* or Random + /// Returns true if the data was modified. + 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; } - /// - /// Maximizes the . If the , the hatch counter is set to 1. - /// - /// PKM to apply hatch details to - public static void MaximizeFriendship(this PKM pk) + do { pk.SetShiny(); } + while (!type.IsValid(pk)); + + return true; + } + + /// + /// Makes a not-shiny. + /// + /// Pokémon to modify. + /// Returns true if the data was modified. + public static bool SetUnshiny(this PKM pk) + { + if (!pk.IsShiny) + return false; + + pk.SetPIDGender(pk.Gender); + return true; + } + + /// + /// Sets the value, with special consideration for the values which derive the value. + /// + /// Pokémon to modify. + /// Desired value to set. + 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; + } + + /// + /// Copies details to the . + /// + /// Pokémon to modify. + /// details to copy from. + 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; } - - /// - /// Maximizes the . If the , the is ignored. - /// - /// PKM to apply hatch details to - 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; } - /// - /// Sets the to its default value. - /// - /// Pokémon to modify. - /// Precomputed optional - 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); } - /// - /// Sets the to its default value. - /// - /// Pokémon to modify. - 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 = { "+", "++", "+++", "++++" }; - - /// - /// Gets the Potential evaluation of the input . - /// - /// Pokémon to analyze. - /// Returned value is unicode or not - /// Potential string - 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 - /// - /// Gets the Location Name for the - /// - /// PKM to fetch data for - /// Location requested is the egg obtained location, not met location. - /// Location string - 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(); + } + + /// + /// Sets the value depending on the current format and the provided item index & format. + /// + /// Pokémon to modify. + /// Held Item to apply + /// Format required for importing + 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; + } + + /// + /// Sets one of the based on its index within the array. + /// + /// Pokémon to modify. + /// Index to set to + /// Value to set + 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)), + }; + + /// + /// Sets one of the based on its index within the array. + /// + /// Pokémon to modify. + /// Index to set to + /// Value to set + 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)), + }; + + /// + /// Fetches the highest value the provided index can be while considering others. + /// + /// Pokémon to modify. + /// Index to fetch for + /// Highest value the value can be. + 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); + } + + /// + /// Fetches the highest value the provided . + /// + /// Pokémon to modify. + /// Index to fetch for + /// Causes the returned value to be dropped down -1 if the value is already at a maximum. + /// Highest value the value can be. + 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; + } + + /// + /// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game. + /// + /// PKM to apply hatch details to + /// Re-hatch already hatched inputs + 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(); + } + + /// + /// Force hatches a PKM by applying the current species name and a valid Met Location from the origin game. + /// + /// PKM to apply hatch details to + /// Game the egg originated from + /// Game the egg is currently present on + 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; + } + + /// + /// Maximizes the . If the , the hatch counter is set to 1. + /// + /// PKM to apply hatch details to + 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(); + } + + /// + /// Maximizes the . If the , the is ignored. + /// + /// PKM to apply hatch details to + public static void MaximizeLevel(this PKM pk) + { + if (pk.IsEgg) + return; + pk.CurrentLevel = 100; + if (pk is ICombatPower pb) + pb.ResetCP(); + } + + /// + /// Sets the to its default value. + /// + /// Pokémon to modify. + /// Precomputed optional + 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(); + } + + /// + /// Sets the to its default value. + /// + /// Pokémon to modify. + public static void SetDefaultNickname(this PKM pk) => pk.SetDefaultNickname(new LegalityAnalysis(pk)); + + private static readonly string[] PotentialUnicode = { "★☆☆☆", "★★☆☆", "★★★☆", "★★★★" }; + private static readonly string[] PotentialNoUnicode = { "+", "++", "+++", "++++" }; + + /// + /// Gets the Potential evaluation of the input . + /// + /// Pokémon to analyze. + /// Returned value is unicode or not + /// Potential string + public static string GetPotentialString(this PKM pk, bool unicode = true) + { + var arr = unicode ? PotentialUnicode : PotentialNoUnicode; + return arr[pk.PotentialRating]; + } + + // Extensions + /// + /// Gets the Location Name for the + /// + /// PKM to fetch data for + /// Location requested is the egg obtained location, not met location. + /// Location string + 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); } } diff --git a/PKHeX.Core/Editing/Database/TrainerDatabase.cs b/PKHeX.Core/Editing/Database/TrainerDatabase.cs index e93da8d85..f871f4a76 100644 --- a/PKHeX.Core/Editing/Database/TrainerDatabase.cs +++ b/PKHeX.Core/Editing/Database/TrainerDatabase.cs @@ -114,11 +114,11 @@ public void Register(ITrainerInfo trainer) } /// - /// Adds the trainer details of the to the . + /// Adds the trainer details of the to the . /// - /// Pokémon with Trainer details to add. + /// Pokémon with Trainer details to add. /// A copy of the object will be made to prevent modifications, just in case. - public void RegisterCopy(PKM pkm) => Register(GetTrainerReference(pkm)); + public void RegisterCopy(PKM pk) => Register(GetTrainerReference(pk)); /// /// Adds the trainer details of the to the . @@ -127,16 +127,16 @@ public void Register(ITrainerInfo trainer) /// A copy of the object will be made to prevent modifications, just in case. 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(); diff --git a/PKHeX.Core/Editing/HiddenPower.cs b/PKHeX.Core/Editing/HiddenPower.cs index c5ab6ffc7..61eeeb539 100644 --- a/PKHeX.Core/Editing/HiddenPower.cs +++ b/PKHeX.Core/Editing/HiddenPower.cs @@ -1,217 +1,216 @@ using System; using System.Runtime.CompilerServices; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for calculating a Hidden Power Type based on IVs and generation-format. +/// +public static class HiddenPower { /// - /// Logic for calculating a Hidden Power Type based on IVs and generation-format. + /// Gets the current Hidden Power Type of the input for the requested format generation. /// - public static class HiddenPower + /// Current IVs + /// Hidden Power Type of the + /// Generation format + public static int GetType(ReadOnlySpan IVs, int format) { - /// - /// Gets the current Hidden Power Type of the input for the requested format generation. - /// - /// Current IVs - /// Hidden Power Type of the - /// Generation format - public static int GetType(ReadOnlySpan IVs, int format) - { - if (format <= 2) - return GetTypeGB(IVs); - return GetType(IVs); - } - - /// - /// Gets the current Hidden Power Type of the input for Generations 3+ - /// - /// Current IVs - /// Hidden Power Type of the - public static int GetType(ReadOnlySpan IVs) - { - int hp = 0; - for (int i = 0; i < 6; i++) - hp |= (IVs[i] & 1) << i; - hp *= 0xF; - hp /= 0x3F; - return hp; - } - - /// - /// Gets the current Hidden Power Type of the input for Generations 1 & 2 - /// - /// Current IVs - /// Hidden Power Type of the - public static int GetTypeGB(ReadOnlySpan IVs) - { - var atk = IVs[1]; - var def = IVs[2]; - return ((atk & 3) << 2) | (def & 3); - } - - /// - /// Modifies the provided to have the requested for Generations 1 & 2 - /// - /// Hidden Power Type - /// Current IVs - /// True if the Hidden Power of the is obtained, with or without modifications - public static bool SetTypeGB(int hiddenPowerType, Span IVs) - { - IVs[1] = (IVs[1] & ~3) | (hiddenPowerType >> 2); - IVs[2] = (IVs[2] & ~3) | (hiddenPowerType & 3); - return true; - } - - /// - /// Modifies the provided to have the requested . - /// - /// Hidden Power Type - /// Current IVs (6 total) - /// Generation format - /// True if the Hidden Power of the is obtained, with or without modifications - public static bool SetIVsForType(int hiddenPowerType, Span IVs, int format) - { - if (format <= 2) - return SetTypeGB(hiddenPowerType, IVs); - return SetIVsForType(hiddenPowerType, IVs); - } - - /// - /// Sets the to the requested for Generation 3+ game formats. - /// - /// Hidden Power Type - /// Current IVs (6 total) - /// True if the Hidden Power of the is obtained, with or without modifications - public static bool SetIVsForType(int hpVal, Span 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 scratch = stackalloc int[IVs.Length]; - Span 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 original, Span ivs, Span best) - { - const int max = 31; - - // Get a list of indexes that can be mutated - Span 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 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(ref T a, ref T b) => (a, b) = (b, a); - - /// Calculate the Hidden Power Type of the entered IVs. - /// Hidden Power Type - /// Individual Values (H/A/B/S/C/D) - /// Generation specific format - public static void SetIVs(int type, Span 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); - } - } - - /// - /// Hidden Power IV values (even or odd) to achieve a specified Hidden Power Type - /// - /// - /// 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) - /// - 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); } + + /// + /// Gets the current Hidden Power Type of the input for Generations 3+ + /// + /// Current IVs + /// Hidden Power Type of the + public static int GetType(ReadOnlySpan IVs) + { + int hp = 0; + for (int i = 0; i < 6; i++) + hp |= (IVs[i] & 1) << i; + hp *= 0xF; + hp /= 0x3F; + return hp; + } + + /// + /// Gets the current Hidden Power Type of the input for Generations 1 & 2 + /// + /// Current IVs + /// Hidden Power Type of the + public static int GetTypeGB(ReadOnlySpan IVs) + { + var atk = IVs[1]; + var def = IVs[2]; + return ((atk & 3) << 2) | (def & 3); + } + + /// + /// Modifies the provided to have the requested for Generations 1 & 2 + /// + /// Hidden Power Type + /// Current IVs + /// True if the Hidden Power of the is obtained, with or without modifications + public static bool SetTypeGB(int hiddenPowerType, Span IVs) + { + IVs[1] = (IVs[1] & ~3) | (hiddenPowerType >> 2); + IVs[2] = (IVs[2] & ~3) | (hiddenPowerType & 3); + return true; + } + + /// + /// Modifies the provided to have the requested . + /// + /// Hidden Power Type + /// Current IVs (6 total) + /// Generation format + /// True if the Hidden Power of the is obtained, with or without modifications + public static bool SetIVsForType(int hiddenPowerType, Span IVs, int format) + { + if (format <= 2) + return SetTypeGB(hiddenPowerType, IVs); + return SetIVsForType(hiddenPowerType, IVs); + } + + /// + /// Sets the to the requested for Generation 3+ game formats. + /// + /// Hidden Power Type + /// Current IVs (6 total) + /// True if the Hidden Power of the is obtained, with or without modifications + public static bool SetIVsForType(int hpVal, Span 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 scratch = stackalloc int[IVs.Length]; + Span 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 original, Span ivs, Span best) + { + const int max = 31; + + // Get a list of indexes that can be mutated + Span 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 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(ref T a, ref T b) => (a, b) = (b, a); + + /// Calculate the Hidden Power Type of the entered IVs. + /// Hidden Power Type + /// Individual Values (H/A/B/S/C/D) + /// Generation specific format + public static void SetIVs(int type, Span 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); + } + } + + /// + /// Hidden Power IV values (even or odd) to achieve a specified Hidden Power Type + /// + /// + /// 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) + /// + 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 + }; } diff --git a/PKHeX.Core/Editing/IBattleTemplate.cs b/PKHeX.Core/Editing/IBattleTemplate.cs index 17f04882f..8944f5ba7 100644 --- a/PKHeX.Core/Editing/IBattleTemplate.cs +++ b/PKHeX.Core/Editing/IBattleTemplate.cs @@ -1,73 +1,72 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface containing details relevant for battling. +/// +public interface IBattleTemplate : ISpeciesForm, IGigantamax, INature { /// - /// Interface containing details relevant for battling. + /// of the Set entity it is specific to. /// - public interface IBattleTemplate : ISpeciesForm, IGigantamax, INature - { - /// - /// of the Set entity it is specific to. - /// - int Format { get; } + int Format { get; } - /// - /// of the Set entity. - /// - string Nickname { get; } + /// + /// of the Set entity. + /// + string Nickname { get; } - /// - /// name of the Set entity. - /// - int Gender { get; } + /// + /// name of the Set entity. + /// + int Gender { get; } - /// - /// of the Set entity. - /// - int HeldItem { get; } + /// + /// of the Set entity. + /// + int HeldItem { get; } - /// - /// of the Set entity. - /// - int Ability { get; } + /// + /// of the Set entity. + /// + int Ability { get; } - /// - /// of the Set entity. - /// - int Level { get; } + /// + /// of the Set entity. + /// + int Level { get; } - /// - /// of the Set entity. - /// - bool Shiny { get; } + /// + /// of the Set entity. + /// + bool Shiny { get; } - /// - /// of the Set entity. - /// - int Friendship { get; } + /// + /// of the Set entity. + /// + int Friendship { get; } - /// - /// name of the Set entity, stored in PKHeX style (instead of Showdown's) - /// - string FormName { get; } + /// + /// name of the Set entity, stored in PKHeX style (instead of Showdown's) + /// + string FormName { get; } - /// - /// of the Set entity. - /// - int HiddenPowerType { get; } + /// + /// of the Set entity. + /// + int HiddenPowerType { get; } - /// - /// of the Set entity. - /// - int[] EVs { get; } + /// + /// of the Set entity. + /// + int[] EVs { get; } - /// - /// of the Set entity. - /// - int[] IVs { get; } + /// + /// of the Set entity. + /// + int[] IVs { get; } - /// - /// of the Set entity. - /// - int[] Moves { get; } - } + /// + /// of the Set entity. + /// + int[] Moves { get; } } diff --git a/PKHeX.Core/Editing/IPKMView.cs b/PKHeX.Core/Editing/IPKMView.cs index 640874eea..59e4feef8 100644 --- a/PKHeX.Core/Editing/IPKMView.cs +++ b/PKHeX.Core/Editing/IPKMView.cs @@ -1,48 +1,47 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Simple interface representing a viewer. +/// +public interface IPKMView { /// - /// Simple interface representing a viewer. + /// Fetches the currently loaded data from the viewer. /// - public interface IPKMView - { - /// - /// Fetches the currently loaded data from the viewer. - /// - PKM Data { get; } + PKM Data { get; } - /// - /// Indicates if the Viewer supports using Unicode characters or not. - /// - bool Unicode { get; } + /// + /// Indicates if the Viewer supports using Unicode characters or not. + /// + bool Unicode { get; } - /// - /// Indicates if the Viewer is providing extra flexibility or not. - /// - bool HaX { get; } + /// + /// Indicates if the Viewer is providing extra flexibility or not. + /// + bool HaX { get; } - /// - /// Indicates if the Viewer's controls are changing their values and should avoid triggering other updates. - /// - bool ChangingFields { get; set; } + /// + /// Indicates if the Viewer's controls are changing their values and should avoid triggering other updates. + /// + bool ChangingFields { get; set; } - /// - /// Fetches the currently loaded data from the viewer by finishing any pending changes or auto-modifications. - /// - /// Cause the viewer to do extra actions to force validation of its children. - /// Prepared data from the viewer. - PKM PreparePKM(bool click = true); + /// + /// Fetches the currently loaded data from the viewer by finishing any pending changes or auto-modifications. + /// + /// Cause the viewer to do extra actions to force validation of its children. + /// Prepared data from the viewer. + PKM PreparePKM(bool click = true); - /// - /// Indicates if the currently loaded data is ready for exporting. - /// - bool EditsComplete { get; } + /// + /// Indicates if the currently loaded data is ready for exporting. + /// + bool EditsComplete { get; } - /// - /// Loads a given data to the viewer. - /// - /// Pokémon data to load. - /// Cause the viewer to give focus to itself. - /// Cause the viewer to skip converting the data. Faster if it is known that the format is the same as the previous format. - void PopulateFields(PKM pk, bool focus = true, bool skipConversionCheck = false); - } + /// + /// Loads a given data to the viewer. + /// + /// Pokémon data to load. + /// Cause the viewer to give focus to itself. + /// Cause the viewer to skip converting the data. Faster if it is known that the format is the same as the previous format. + void PopulateFields(PKM pk, bool focus = true, bool skipConversionCheck = false); } diff --git a/PKHeX.Core/Editing/IPlugin.cs b/PKHeX.Core/Editing/IPlugin.cs index 598bbd813..0cfaa3902 100644 --- a/PKHeX.Core/Editing/IPlugin.cs +++ b/PKHeX.Core/Editing/IPlugin.cs @@ -1,41 +1,40 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Plugin interface used by an editor to notify third-party code providers. +/// +public interface IPlugin { /// - /// Plugin interface used by an editor to notify third-party code providers. + /// Plugin Name. /// - public interface IPlugin - { - /// - /// Plugin Name. - /// - string Name { get; } + string Name { get; } - /// - /// Plugin Loading Priority (lowest is initialized first). - /// - int Priority { get; } + /// + /// Plugin Loading Priority (lowest is initialized first). + /// + int Priority { get; } - /// - /// Entry point for the parent to initialize the plugin with provided arguments. - /// - /// Arguments containing objects useful for initializing the plugin. - void Initialize(params object[] args); + /// + /// Entry point for the parent to initialize the plugin with provided arguments. + /// + /// Arguments containing objects useful for initializing the plugin. + void Initialize(params object[] args); - /// - /// Notifies the plugin that a save file was just loaded. - /// - void NotifySaveLoaded(); + /// + /// Notifies the plugin that a save file was just loaded. + /// + void NotifySaveLoaded(); - /// - /// Attempts to load a file using the plugin. - /// - /// Path to file to be loaded. - /// True if the plugin has handled the file. - bool TryLoadFile(string filePath); + /// + /// Attempts to load a file using the plugin. + /// + /// Path to file to be loaded. + /// True if the plugin has handled the file. + bool TryLoadFile(string filePath); - /// - /// Retrieves the object which can provide a . - /// - ISaveFileProvider SaveFileEditor { get; } - } + /// + /// Retrieves the object which can provide a . + /// + ISaveFileProvider SaveFileEditor { get; } } diff --git a/PKHeX.Core/Editing/ISaveFileProvider.cs b/PKHeX.Core/Editing/ISaveFileProvider.cs index 83c527817..7b177e931 100644 --- a/PKHeX.Core/Editing/ISaveFileProvider.cs +++ b/PKHeX.Core/Editing/ISaveFileProvider.cs @@ -1,23 +1,22 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Simple interface representing a Save File viewer. +/// +public interface ISaveFileProvider { /// - /// Simple interface representing a Save File viewer. + /// Retrieves the save file the has control over. /// - public interface ISaveFileProvider - { - /// - /// Retrieves the save file the has control over. - /// - SaveFile SAV { get; } + SaveFile SAV { get; } - /// - /// Retrieves the current box the has control over. - /// - int CurrentBox { get; } + /// + /// Retrieves the current box the has control over. + /// + int CurrentBox { get; } - /// - /// Triggers a refresh of any individual view slots. - /// - void ReloadSlots(); - } + /// + /// Triggers a refresh of any individual view slots. + /// + void ReloadSlots(); } diff --git a/PKHeX.Core/Editing/ISpriteBuilder.cs b/PKHeX.Core/Editing/ISpriteBuilder.cs index 77f02e40d..82ecfb0f2 100644 --- a/PKHeX.Core/Editing/ISpriteBuilder.cs +++ b/PKHeX.Core/Editing/ISpriteBuilder.cs @@ -1,17 +1,16 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface ISpriteBuilder { - public interface ISpriteBuilder - { - 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); } diff --git a/PKHeX.Core/Editing/PKM/EntitySuggestionUtil.cs b/PKHeX.Core/Editing/PKM/EntitySuggestionUtil.cs index d0909cdbf..40334089a 100644 --- a/PKHeX.Core/Editing/PKM/EntitySuggestionUtil.cs +++ b/PKHeX.Core/Editing/PKM/EntitySuggestionUtil.cs @@ -2,26 +2,25 @@ using System.Linq; using static PKHeX.Core.MessageStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Utility for editing a +/// +public static class EntitySuggestionUtil { - /// - /// Utility for editing a - /// - public static class EntitySuggestionUtil + public static List GetMetLocationSuggestionMessage(PKM pk, int level, int location, int minimumLevel) { - public static List GetMetLocationSuggestionMessage(PKM pkm, int level, int location, int minimumLevel) + var suggestion = new List { MsgPKMSuggestionStart }; + if (pk.Format >= 3) { - var suggestion = new List { 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; } } diff --git a/PKHeX.Core/Editing/PKM/EntitySummary.cs b/PKHeX.Core/Editing/PKM/EntitySummary.cs index c14330867..306256922 100644 --- a/PKHeX.Core/Editing/PKM/EntitySummary.cs +++ b/PKHeX.Core/Editing/PKM/EntitySummary.cs @@ -1,126 +1,125 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Bindable summary object that can fetch strings that summarize a . +/// +public class EntitySummary // do NOT seal, allow inheritance { - /// - /// Bindable summary object that can fetch strings that summarize a . - /// - public class EntitySummary // do NOT seal, allow inheritance + private static readonly IReadOnlyList 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 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); - } - - /// - /// Safely fetches the string from the array. - /// - /// Array of strings - /// Index to fetch - /// Null if array is null - private static string Get(IReadOnlyList arr, int val) => (uint)val < arr.Count ? arr[val] : string.Empty; - private static string GetSpan(ReadOnlySpan arr, int val) => (uint)val < arr.Length ? arr[val] : string.Empty; + pk = p; + Strings = strings; + Stats = pk.GetStats(pk.PersonalInfo); } + + /// + /// Safely fetches the string from the array. + /// + /// Array of strings + /// Index to fetch + /// Null if array is null + private static string Get(IReadOnlyList arr, int val) => (uint)val < arr.Count ? arr[val] : string.Empty; + private static string GetSpan(ReadOnlySpan arr, int val) => (uint)val < arr.Length ? arr[val] : string.Empty; } diff --git a/PKHeX.Core/Editing/PKM/EntityTemplates.cs b/PKHeX.Core/Editing/PKM/EntityTemplates.cs index 09abc70ab..8de96cf6a 100644 --- a/PKHeX.Core/Editing/PKM/EntityTemplates.cs +++ b/PKHeX.Core/Editing/PKM/EntityTemplates.cs @@ -1,78 +1,77 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for filling in template data for objects. +/// +public static class EntityTemplates { /// - /// Logic for filling in template data for objects. + /// Applies junk data to a , which is preferable to a completely empty entity. /// - public static class EntityTemplates + /// Blank data + /// Trainer info to apply + public static void TemplateFields(PKM pk, ITrainerInfo tr) { - /// - /// Applies junk data to a , which is preferable to a completely empty entity. - /// - /// Blank data - /// Trainer info to apply - 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; + } } diff --git a/PKHeX.Core/Editing/PKM/QR/QRMessageUtil.cs b/PKHeX.Core/Editing/PKM/QR/QRMessageUtil.cs index 5913bb59c..507e7a6ac 100644 --- a/PKHeX.Core/Editing/PKM/QR/QRMessageUtil.cs +++ b/PKHeX.Core/Editing/PKM/QR/QRMessageUtil.cs @@ -1,111 +1,113 @@ using System; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// QR Message reading & writing logic +/// +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; + /// - /// QR Message reading & writing logic + /// Gets the data from the message that is encoded in a QR. /// - public static class QRMessageUtil + /// QR Message + /// Preferred to expect. + /// Decoded object, null if invalid. + 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); + } - /// - /// Gets the data from the message that is encoded in a QR. - /// - /// QR Message - /// Preferred to expect. - /// Decoded object, null if invalid. - public static PKM? GetPKM(string message, EntityContext context) + /// + /// Gets a QR Message from the input data. + /// + /// Pokémon to encode + /// QR Message + 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); + } + + /// + /// Gets a QR Message from the input data. + /// + /// Data to encode + /// QR Message + public static string GetMessage(byte[] payload) => string.Concat(payload.Select(z => (char) z)); + + /// + /// Gets a QR Message from the input data. + /// + /// Gift data to encode + /// QR Message + 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); } - - /// - /// Gets a QR Message from the input data. - /// - /// Pokémon to encode - /// QR Message - 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); - } - - /// - /// Gets a QR Message from the input data. - /// - /// Data to encode - /// QR Message - public static string GetMessage(byte[] payload) => string.Concat(payload.Select(z => (char) z)); - - /// - /// Gets a QR Message from the input data. - /// - /// Gift data to encode - /// QR Message - 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; - } } -} \ No newline at end of file + + 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; + } +} diff --git a/PKHeX.Core/Editing/PKM/QR/QRPK7.cs b/PKHeX.Core/Editing/PKM/QR/QRPK7.cs index fe41181fe..a6d2aaad1 100644 --- a/PKHeX.Core/Editing/PKM/QR/QRPK7.cs +++ b/PKHeX.Core/Editing/PKM/QR/QRPK7.cs @@ -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]; + + /// + /// Converts the to a rough PKM. + /// + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + /// + /// Converts the to a rough PKM. + /// + 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]; - - /// - /// Converts the to a rough PKM. - /// - public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted); - - /// - /// Converts the to a rough PKM. - /// - 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; } } diff --git a/PKHeX.Core/Editing/PKM/QR/QRPKM.cs b/PKHeX.Core/Editing/PKM/QR/QRPKM.cs index 01a98fb92..75c5d836f 100644 --- a/PKHeX.Core/Editing/PKM/QR/QRPKM.cs +++ b/PKHeX.Core/Editing/PKM/QR/QRPKM.cs @@ -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 + /// + /// Summarizes the details of a into multiple lines for display in an image. + /// + /// Pokémon to generate details for. + /// Lines representing a Header, Moves, and IVs/EVs + public static string[] GetQRLines(this PKM pk) { - /// - /// Summarizes the details of a into multiple lines for display in an image. - /// - /// Pokémon to generate details for. - /// Lines representing a Header, Moves, and IVs/EVs - 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 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 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]; } } diff --git a/PKHeX.Core/Editing/Program/AutoLoadSetting.cs b/PKHeX.Core/Editing/Program/AutoLoadSetting.cs index d6cb0f109..ae3940656 100644 --- a/PKHeX.Core/Editing/Program/AutoLoadSetting.cs +++ b/PKHeX.Core/Editing/Program/AutoLoadSetting.cs @@ -1,23 +1,22 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Option to load a save file automatically to an editing environment. +/// +public enum AutoLoadSetting { /// - /// 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. /// - public enum AutoLoadSetting - { - /// - /// Doesn't auto load a save file, and instead uses a fake save file data. - /// - Disabled, + Disabled, - /// - /// Loads the most recently created Save File in the usual backup locations. - /// - RecentBackup, + /// + /// Loads the most recently created Save File in the usual backup locations. + /// + RecentBackup, - /// - /// Loads the most recently opened Save File path. - /// - LastLoaded, - } + /// + /// Loads the most recently opened Save File path. + /// + LastLoaded, } diff --git a/PKHeX.Core/Editing/Program/IStartupSettings.cs b/PKHeX.Core/Editing/Program/IStartupSettings.cs index 8729c1a2c..b206c0d56 100644 --- a/PKHeX.Core/Editing/Program/IStartupSettings.cs +++ b/PKHeX.Core/Editing/Program/IStartupSettings.cs @@ -1,25 +1,24 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Settings used for starting up an editing environment. +/// +public interface IStartupSettings { /// - /// 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. /// - public interface IStartupSettings - { - /// - /// Save File version to start the environment with if a preexisting save file has not been chosen. - /// - GameVersion DefaultSaveVersion { get; } + GameVersion DefaultSaveVersion { get; } - /// - /// Method to load the environment's initial save file. - /// - AutoLoadSetting AutoLoadSaveOnStartup { get; } + /// + /// Method to load the environment's initial save file. + /// + AutoLoadSetting AutoLoadSaveOnStartup { get; } - /// - /// List of recently loaded save file paths. - /// - List RecentlyLoaded { get; } - } + /// + /// List of recently loaded save file paths. + /// + List RecentlyLoaded { get; } } diff --git a/PKHeX.Core/Editing/Program/StartupArguments.cs b/PKHeX.Core/Editing/Program/StartupArguments.cs index df1bb6b1e..d29f290fa 100644 --- a/PKHeX.Core/Editing/Program/StartupArguments.cs +++ b/PKHeX.Core/Editing/Program/StartupArguments.cs @@ -3,126 +3,123 @@ using System.IO; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic object that assembles parameters used for starting up an editing environment. +/// +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 Extra = new(); + /// - /// Logic object that assembles parameters used for starting up an editing environment. + /// Step 1: Reads in command line arguments. /// - public sealed class StartupArguments + public void ReadArguments(IEnumerable 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 Extra = new(); - - /// - /// Step 1: Reads in command line arguments. - /// - public void ReadArguments(IEnumerable 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); } } - - /// - /// Step 2: Reads settings config. - /// - 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 GetMostRecentlyLoaded(IEnumerable 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 } + + /// + /// Step 2: Reads settings config. + /// + 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 GetMostRecentlyLoaded(IEnumerable 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 } diff --git a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipModify.cs b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipModify.cs index 439ae1f3a..aec92cd23 100644 --- a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipModify.cs +++ b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipModify.cs @@ -20,4 +20,4 @@ public override int Execute(SaveFile sav, BoxManipParam param) var (start, stop, _) = param; return sav.ModifyBoxes(Action, start, stop); } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipSortComplex.cs b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipSortComplex.cs index 9db7ae95c..b1050a6aa 100644 --- a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipSortComplex.cs +++ b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipSortComplex.cs @@ -25,4 +25,4 @@ public override int Execute(SaveFile sav, BoxManipParam param) var (start, stop, reverse) = param; return sav.SortBoxes(start, stop, Method, reverse); } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipType.cs b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipType.cs index 54255268e..aa5e57e86 100644 --- a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipType.cs +++ b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipType.cs @@ -46,4 +46,4 @@ public enum BoxManipType ModifyRemoveNicknames, ModifyRemoveItem, ModifyHeal, -} \ No newline at end of file +} diff --git a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipUtil.cs b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipUtil.cs index c3b243e7b..2c3908695 100644 --- a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipUtil.cs +++ b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipUtil.cs @@ -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 + /// + /// Grouped categories of different . + /// + public static readonly IReadOnlyList[] ManipCategories = { - /// - /// Grouped categories of different . - /// - public static readonly IReadOnlyList[] 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", + }; - /// - /// Gets a reference that carries out the action of the requested . - /// - /// Manipulation type. - /// Reference to . - public static IBoxManip GetManip(this BoxManipType type) => ManipCategories.SelectMany(c => c).First(m => m.Type == type); + /// + /// Gets a reference that carries out the action of the requested . + /// + /// Manipulation type. + /// Reference to . + public static IBoxManip GetManip(this BoxManipType type) => ManipCategories.SelectMany(c => c).First(m => m.Type == type); - /// - /// Gets the corresponding name from for the requested . - /// - /// Manipulation type. - /// Category Name - public static string? GetManipCategoryName(this BoxManipType type) + /// + /// Gets the corresponding name from for the requested . + /// + /// Manipulation type. + /// Category Name + 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; + } - /// - /// Gets the corresponding name from for the requested . - /// - /// Manipulation type. - /// Category Name - public static string? GetManipCategoryName(this IBoxManip manip) + /// + /// Gets the corresponding name from for the requested . + /// + /// Manipulation type. + /// Category Name + 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; } } diff --git a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipulator.cs b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipulator.cs index 22b74c778..3004cf652 100644 --- a/PKHeX.Core/Editing/Saves/BoxManip/BoxManipulator.cs +++ b/PKHeX.Core/Editing/Saves/BoxManip/BoxManipulator.cs @@ -1,73 +1,71 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Manipulates boxes of a . +/// +public abstract class BoxManipulator { + protected abstract SaveFile SAV { get; } + /// - /// Manipulates boxes of a . + /// Executes the provided with the provided parameters. /// - public abstract class BoxManipulator + /// Manipulation to perform on the box data. + /// Single box to modify; if is set, this param is ignored. + /// Indicates if all boxes are to be manipulated, or just one box. + /// Manipulation action should be inverted (criteria) or reversed (sort). + /// True if operation succeeded, false if no changes made. + 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; - /// - /// Executes the provided with the provided parameters. - /// - /// Manipulation to perform on the box data. - /// Single box to modify; if is set, this param is ignored. - /// Indicates if all boxes are to be manipulated, or just one box. - /// Manipulation action should be inverted (criteria) or reversed (sort). - /// True if operation succeeded, false if no changes made. - 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; - } - - /// - /// Executes the provided with the provided parameters. - /// - /// Manipulation to perform on the box data. - /// Single box to modify; if is set, this param is ignored. - /// Indicates if all boxes are to be manipulated, or just one box. - /// Manipulation action should be inverted (criteria) or reversed (sort). - /// True if operation succeeded, false if no changes made. - public bool Execute(BoxManipType type, int box, bool allBoxes, bool reverse = false) - { - var manip = type.GetManip(); - return Execute(manip, box, allBoxes, reverse); - } - - /// - /// Sanity check for modifying the box data. - /// - /// Start box - /// End box - /// Message asking about the operation. - /// Message indicating the operation cannot be performed. - /// - protected virtual bool CanManipulateRegion(int start, int end, string prompt, string fail) => !SAV.IsAnySlotLockedInBox(start, end); - - /// - /// Called if the operation modified any data. - /// - /// Optional message to show if applicable. - /// Indicates if all boxes were manipulated, or just one box. - /// Count of manipulated slots - 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; } + + /// + /// Executes the provided with the provided parameters. + /// + /// Manipulation to perform on the box data. + /// Single box to modify; if is set, this param is ignored. + /// Indicates if all boxes are to be manipulated, or just one box. + /// Manipulation action should be inverted (criteria) or reversed (sort). + /// True if operation succeeded, false if no changes made. + public bool Execute(BoxManipType type, int box, bool allBoxes, bool reverse = false) + { + var manip = type.GetManip(); + return Execute(manip, box, allBoxes, reverse); + } + + /// + /// Sanity check for modifying the box data. + /// + /// Start box + /// End box + /// Message asking about the operation. + /// Message indicating the operation cannot be performed. + protected virtual bool CanManipulateRegion(int start, int end, string prompt, string fail) => !SAV.IsAnySlotLockedInBox(start, end); + + /// + /// Called if the operation modified any data. + /// + /// Optional message to show if applicable. + /// Indicates if all boxes were manipulated, or just one box. + /// Count of manipulated slots + protected abstract void FinishBoxManipulation(string message, bool all, int count); } diff --git a/PKHeX.Core/Editing/Saves/Editors/EventOld/EventLabelCollection.cs b/PKHeX.Core/Editing/Saves/Editors/EventOld/EventLabelCollection.cs index 654da6462..c3f221119 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventOld/EventLabelCollection.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventOld/EventLabelCollection.cs @@ -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 Work; + public readonly IReadOnlyList Flag; + + public EventLabelCollection(string game, int maxFlag = int.MaxValue, int maxValue = int.MaxValue) { - public readonly IReadOnlyList Work; - public readonly IReadOnlyList 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 GetFlags(IReadOnlyCollection strings, int maxValue) - { - var result = new List(strings.Count); - var processed = new HashSet(); - 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 GetValues(IReadOnlyCollection strings, int maxValue) - { - var result = new List(strings.Count); - var processed = new HashSet(); - 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 GetPredefinedArray(string combined) - { - var result = new List {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 GetFlags(IReadOnlyCollection strings, int maxValue) { - public readonly IReadOnlyList Work; - public readonly IReadOnlyList Flag; - public readonly IReadOnlyList System; - - public EventLabelCollectionSystem(string game, int maxFlag = int.MaxValue, int maxSystem = int.MaxValue, int maxValue = int.MaxValue) + var result = new List(strings.Count); + var processed = new HashSet(); + 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 GetFlags(IReadOnlyCollection strings, int maxValue) - { - var result = new List(strings.Count); - var processed = new HashSet(); - 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 GetValues(IReadOnlyCollection strings, int maxValue) - { - var result = new List(strings.Count); - var processed = new HashSet(); - 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 GetPredefinedArray(string combined) - { - var result = new List { 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 GetValues(IReadOnlyCollection strings, int maxValue) + { + var result = new List(strings.Count); + var processed = new HashSet(); + 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 PredefinedValues) : NamedEventValue(Name, Index, Type); - - public sealed record NamedEventConst(string Name, ushort Value) + private static IReadOnlyList GetPredefinedArray(string combined) { - public bool IsCustom => Value == CustomMagicValue; - public const ushort CustomMagicValue = ushort.MaxValue; + var result = new List {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 Work; + public readonly IReadOnlyList Flag; + public readonly IReadOnlyList 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 GetFlags(IReadOnlyCollection strings, int maxValue) + { + var result = new List(strings.Count); + var processed = new HashSet(); + 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 GetValues(IReadOnlyCollection strings, int maxValue) + { + var result = new List(strings.Count); + var processed = new HashSet(); + 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 GetPredefinedArray(string combined) + { + var result = new List { 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 PredefinedValues) : NamedEventValue(Name, Index, Type); + +public sealed record NamedEventConst(string Name, ushort Value) +{ + public bool IsCustom => Value == CustomMagicValue; + public const ushort CustomMagicValue = ushort.MaxValue; } diff --git a/PKHeX.Core/Editing/Saves/Editors/EventUnlock/EventUnlocker8b.cs b/PKHeX.Core/Editing/Saves/Editors/EventUnlock/EventUnlocker8b.cs index 2943b268b..164d1e3d2 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventUnlock/EventUnlocker8b.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventUnlock/EventUnlocker8b.cs @@ -5,10 +5,10 @@ public class EventUnlocker8b : EventUnlocker 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 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 void UnlockSpiritomb() 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 void UnlockZones() 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 void RespawnRoamer() 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 } } diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff.cs index c9279f020..491406899 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff.cs @@ -36,7 +36,7 @@ public EventBlockDiff(string f1, string f2) 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 @@ private void Diff(T s1, T s2) public IReadOnlyList 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 { "Flags: ON", "=========" }; list.AddRange(fOn); diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff8b.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff8b.cs index b31ab0e28..bae7b9303 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff8b.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/Diff/EventWorkDiff8b.cs @@ -46,9 +46,9 @@ private void Diff(SAV8BS s1, SAV8BS s2) 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; } diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventFlag.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventFlag.cs index 6e7b2f9d1..ec1405428 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventFlag.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventFlag.cs @@ -1,16 +1,15 @@ using System.Collections.Generic; -namespace PKHeX.Core -{ - /// - /// Event Flag that toggles certain features / entities on and off. - /// - public sealed class EventFlag : EventVar - { - public bool Flag; +namespace PKHeX.Core; - public EventFlag(int index, EventVarType t, IReadOnlyList pieces) : base(index, t, pieces[1]) - { - } +/// +/// Event Flag that toggles certain features / entities on and off. +/// +public sealed class EventFlag : EventVar +{ + public bool Flag; + + public EventFlag(int index, EventVarType t, IReadOnlyList pieces) : base(index, t, pieces[1]) + { } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventVar.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventVar.cs index 9dca35bf5..12b8c5c54 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventVar.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventVar.cs @@ -1,35 +1,34 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Event variable used to determine game events. +/// +public abstract class EventVar { /// - /// Event variable used to determine game events. + /// Name of event variable /// - public abstract class EventVar + public readonly string Name; + + /// + /// Type of event variable + /// + public readonly EventVarType Type; + + /// + /// Raw index within the event variable (type) region. + /// + public int RawIndex; + + /// + /// Unpacked structure's index. + /// + public readonly int RelativeIndex; + + protected EventVar(int index, EventVarType t, string name) { - /// - /// Name of event variable - /// - public readonly string Name; - - /// - /// Type of event variable - /// - public readonly EventVarType Type; - - /// - /// Raw index within the event variable (type) region. - /// - public int RawIndex; - - /// - /// Unpacked structure's index. - /// - public readonly int RelativeIndex; - - protected EventVar(int index, EventVarType t, string name) - { - RelativeIndex = index; - Type = t; - Name = name; - } + RelativeIndex = index; + Type = t; + Name = name; } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventVarGroup.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventVarGroup.cs index 3af3535eb..92f1c6f74 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventVarGroup.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventVarGroup.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; -namespace PKHeX.Core -{ - public sealed class EventVarGroup - { - public readonly EventVarType Type; - public readonly List Vars = new(); +namespace PKHeX.Core; - public EventVarGroup(EventVarType type) => Type = type; - } -} \ No newline at end of file +public sealed class EventVarGroup +{ + public readonly EventVarType Type; + public readonly List Vars = new(); + + public EventVarGroup(EventVarType type) => Type = type; +} diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWork.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWork.cs index 430613924..6e7289604 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWork.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWork.cs @@ -1,32 +1,31 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Event number storage for more complex logic events. +/// +/// +public sealed class EventWork : EventVar where T : struct { - /// - /// Event number storage for more complex logic events. - /// - /// - public sealed class EventWork : EventVar where T : struct + public T Value; + public readonly IList Options = new List { new() }; + + public EventWork(int index, EventVarType t, IReadOnlyList pieces) : base(index, t, pieces[1]) { - public T Value; - public readonly IList Options = new List { new() }; + if (pieces.Count < 3) + return; - public EventWork(int index, EventVarType t, IReadOnlyList 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)); } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWorkUtil.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWorkUtil.cs index 30a4fa252..b87c71c88 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWorkUtil.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWorkUtil.cs @@ -4,148 +4,147 @@ using System.Diagnostics.CodeAnalysis; using static PKHeX.Core.MessageStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides utility for various logic. +/// +public static class EventWorkUtil { - /// - /// Provides utility for various logic. - /// - public static class EventWorkUtil + private static readonly Dictionary TypeDict = new() { - private static readonly Dictionary 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; } - /// - /// Parses and converts into values with the provided . - /// - /// Lines to parse - /// Object constructor - /// Converted lines grouped together by - public static List GetVars(IEnumerable lines, Func constructor) - { - var list = new List(); - 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; - } - - /// - /// Compares a and object of the same type to find changes. - /// - /// Data before the event was triggered - /// Data after the event was triggered - /// List of flags that were turned on - /// List of flags that were turned off - public static void DiffSavesFlag(IEventFlag before, IEventFlag after, List on, List 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); - } - } - - /// - /// Compares a and object of the same type to find changes. - /// - /// Data before the event was triggered - /// Data after the event was triggered - /// List of flags that were turned on - /// List of flags that were turned off - public static void DiffSavesSystem(ISystemFlag before, ISystemFlag after, List on, List 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); - } - } - - /// - /// Compares a and object of the same type to find changes. - /// - /// Type of value used by - /// Data before the event was triggered - /// Data after the event was triggered - /// values that changed - /// Summary of the value change - public static void DiffSavesWork(IEventWork before, IEventWork after, List changed, List changes) where T : unmanaged, IEquatable - { - 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 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; + } + + /// + /// Parses and converts into values with the provided . + /// + /// Lines to parse + /// Object constructor + /// Converted lines grouped together by + public static List GetVars(IEnumerable lines, Func constructor) + { + var list = new List(); + 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; + } + + /// + /// Compares a and object of the same type to find changes. + /// + /// Data before the event was triggered + /// Data after the event was triggered + /// List of flags that were turned on + /// List of flags that were turned off + public static void DiffSavesFlag(IEventFlag before, IEventFlag after, List on, List 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); } } + + /// + /// Compares a and object of the same type to find changes. + /// + /// Data before the event was triggered + /// Data after the event was triggered + /// List of flags that were turned on + /// List of flags that were turned off + public static void DiffSavesSystem(ISystemFlag before, ISystemFlag after, List on, List 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); + } + } + + /// + /// Compares a and object of the same type to find changes. + /// + /// Type of value used by + /// Data before the event was triggered + /// Data after the event was triggered + /// values that changed + /// Summary of the value change + public static void DiffSavesWork(IEventWork before, IEventWork after, List changed, List changes) where T : unmanaged, IEquatable + { + 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 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; + } } diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWorkVal.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWorkVal.cs index 8683e223a..e9cb7b9ea 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWorkVal.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/EventWorkVal.cs @@ -1,25 +1,24 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Represents a known value for a of type . +/// +public sealed class EventWorkVal { - /// - /// Represents a known value for a of type . - /// - 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; } -} \ No newline at end of file + + public EventWorkVal(string text, int val) + { + Text = text; + Value = val; + } +} diff --git a/PKHeX.Core/Editing/Saves/Editors/EventWork/SplitEventEditor.cs b/PKHeX.Core/Editing/Saves/Editors/EventWork/SplitEventEditor.cs index ca0259cb5..c94386fae 100644 --- a/PKHeX.Core/Editing/Saves/Editors/EventWork/SplitEventEditor.cs +++ b/PKHeX.Core/Editing/Saves/Editors/EventWork/SplitEventEditor.cs @@ -1,66 +1,65 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Editor object that unpacks into flags & work groups, and handles value get/set operations. +/// +/// +public sealed class SplitEventEditor where T : struct { - /// - /// Editor object that unpacks into flags & work groups, and handles value get/set operations. - /// - /// - public sealed class SplitEventEditor where T : struct + public readonly IList Work; + public readonly IList Flag; + public readonly IEventVar Block; + + public SplitEventEditor(IEventVar block, IEnumerable work, IEnumerable flag) { - public readonly IList Work; - public readonly IList Flag; - public readonly IEventVar 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(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 block, IEnumerable work, IEnumerable 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(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)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)item).Value = block.GetWork(item.RawIndex); } } - - /// - /// Writes all of the updated event values back to the block. - /// - 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)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) + } + } + + /// + /// Writes all of the updated event values back to the block. + /// + 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)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); } } } diff --git a/PKHeX.Core/Editing/Saves/Management/INamedFolderPath.cs b/PKHeX.Core/Editing/Saves/Management/INamedFolderPath.cs index 3803c188a..474c735d5 100644 --- a/PKHeX.Core/Editing/Saves/Management/INamedFolderPath.cs +++ b/PKHeX.Core/Editing/Saves/Management/INamedFolderPath.cs @@ -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; } } diff --git a/PKHeX.Core/Editing/Saves/Management/SavePreview.cs b/PKHeX.Core/Editing/Saves/Management/SavePreview.cs index f7fc9c849..ca05b61c1 100644 --- a/PKHeX.Core/Editing/Saves/Management/SavePreview.cs +++ b/PKHeX.Core/Editing/Saves/Management/SavePreview.cs @@ -1,49 +1,49 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Bindable Summary of a for use in a sortable table. +/// +public sealed class SavePreview { - /// - /// Bindable Summary of a for use in a sortable table. - /// - 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 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 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); } diff --git a/PKHeX.Core/Editing/Saves/Slots/BoxEdit.cs b/PKHeX.Core/Editing/Saves/Slots/BoxEdit.cs index ef17300b9..9d8a937d6 100644 --- a/PKHeX.Core/Editing/Saves/Slots/BoxEdit.cs +++ b/PKHeX.Core/Editing/Saves/Slots/BoxEdit.cs @@ -1,58 +1,57 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Represents a Box Editor that loads the contents for easy manipulation. +/// +public sealed class BoxEdit { - /// - /// Represents a Box Editor that loads the contents for easy manipulation. - /// - 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; + } } diff --git a/PKHeX.Core/Editing/Saves/Slots/Extensions.cs b/PKHeX.Core/Editing/Saves/Slots/Extensions.cs index 7aafb5fd2..1d06bb142 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Extensions.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Extensions.cs @@ -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 GetAllPKM(this SaveFile sav) { - public static IReadOnlyList GetAllPKM(this SaveFile sav) - { - var result = new List(); - if (sav.HasBox) - result.AddRange(sav.BoxData); - if (sav.HasParty) - result.AddRange(sav.PartyData); + var result = new List(); + 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 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 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 None = new(); - public static PKM[] GetExtraPKM(this SaveFile sav, IList slots) + private static List 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 GetExtraSlots2(SAV2 sav) + { + return new List { - 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 GetExtraSlots(this SaveFile sav, bool all = false) + private static List GetExtraSlots3(SAV3 sav) + { + if (sav is not SAV3FRLG) + return None; + return new List { - var slots = GetExtraSlotsUnsafe(sav, all); - for (int i = 0; i < slots.Count;) + new(sav.Large, 0, 0x3C98) {Type = StorageSlotType.Daycare}, + }; + } + + private static List GetExtraSlots4(SAV4 sav) + { + var list = new List + { + 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 GetExtraSlots5(SAV5 sav) + { + return new List + { + 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 GetExtraSlots6XY(SAV6XY sav) + { + return new List + { + 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 GetExtraSlots6AO(SAV6AO sav) + { + return new List + { + 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 GetExtraSlots7(SAV7 sav, bool all) + { + var list = new List + { + 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 None = new(); + if (!all) + return list; - private static List 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 GetExtraSlots7b(SAV7b sav) + { + return new List { - 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 GetExtraSlots8(ISaveBlock8Main sav) + { + var fused = sav.Fused; + var dc = sav.Daycare; + var list = new List + { + 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 GetExtraSlots2(SAV2 sav) + if (sav is SAV8SWSH {SaveRevision: >= 2} s8) { - return new List - { - 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 GetExtraSlots3(SAV3 sav) + return list; + } + + private static List GetExtraSlots8b(SAV8BS sav) + { + return new List { - if (sav is not SAV3FRLG) - return None; - return new List - { - 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 GetExtraSlots4(SAV4 sav) - { - var list = new List - { - 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 GetExtraSlots5(SAV5 sav) - { - return new List - { - 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 GetExtraSlots6XY(SAV6XY sav) - { - return new List - { - 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 GetExtraSlots6AO(SAV6AO sav) - { - return new List - { - 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 GetExtraSlots7(SAV7 sav, bool all) - { - var list = new List - { - 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 GetExtraSlots7b(SAV7b sav) - { - return new List - { - new(sav.Data, 0, sav.Blocks.GetBlockOffset(BelugaBlockIndex.Daycare) + 8, true) {Type = StorageSlotType.Daycare}, - }; - } - - private static List GetExtraSlots8(ISaveBlock8Main sav) - { - var fused = sav.Fused; - var dc = sav.Daycare; - var list = new List - { - 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 GetExtraSlots8b(SAV8BS sav) - { - return new List - { - 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 GetExtraSlots8a(SAV8LA sav) - { - return new List(); - } + private static List GetExtraSlots8a(SAV8LA sav) + { + return new List(); } } diff --git a/PKHeX.Core/Editing/Saves/Slots/ISlotViewer.cs b/PKHeX.Core/Editing/Saves/Slots/ISlotViewer.cs index 70817c451..af7ccc003 100644 --- a/PKHeX.Core/Editing/Saves/Slots/ISlotViewer.cs +++ b/PKHeX.Core/Editing/Saves/Slots/ISlotViewer.cs @@ -1,54 +1,51 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Slot Viewer that shows many slots of data. +/// +/// Object that displays the slot. +public interface ISlotViewer { /// - /// Slot Viewer that shows many slots of data. + /// Current index the viewer is viewing. /// - /// Object that displays the slot. - public interface ISlotViewer - { - /// - /// Current index the viewer is viewing. - /// - int ViewIndex { get; } + int ViewIndex { get; } - /// - /// Notification that the slot is no longer the last interacted slot. - /// - /// Last interacted slot - void NotifySlotOld(ISlotInfo previous); + /// + /// Notification that the slot is no longer the last interacted slot. + /// + /// Last interacted slot + void NotifySlotOld(ISlotInfo previous); - /// - /// Notification that the has just been interacted with. - /// - /// Last interacted slot - /// Last interacted slot interaction type - /// Last interacted slot interaction data - void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pkm); + /// + /// Notification that the has just been interacted with. + /// + /// Last interacted slot + /// Last interacted slot interaction type + /// Last interacted slot interaction data + void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pk); - /// - /// Gets the information for the requested slot's view picture. - /// - /// - /// - ISlotInfo GetSlotData(T view); + /// + /// Gets the information for the requested slot's view picture. + /// + /// + ISlotInfo GetSlotData(T view); - /// - /// Gets the index of the view within the 's list of slots. - /// - /// - /// - int GetViewIndex(ISlotInfo slot); + /// + /// Gets the index of the view within the 's list of slots. + /// + /// + int GetViewIndex(ISlotInfo slot); - /// - /// List of views the is showing. - /// - IList SlotPictureBoxes { get; } + /// + /// List of views the is showing. + /// + IList SlotPictureBoxes { get; } - /// - /// Save data the is showing data from. - /// - SaveFile SAV { get; } - } -} \ No newline at end of file + /// + /// Save data the is showing data from. + /// + SaveFile SAV { get; } +} diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/ISlotInfo.cs b/PKHeX.Core/Editing/Saves/Slots/Info/ISlotInfo.cs index a5706021e..c1ddb3659 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/ISlotInfo.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/ISlotInfo.cs @@ -1,48 +1,47 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Data representing info for an individual slot. +/// +public interface ISlotInfo { /// - /// Data representing info for an individual slot. + /// Indicates the type of format the slot originates. Useful for legality purposes. /// - public interface ISlotInfo - { - /// - /// Indicates the type of format the slot originates. Useful for legality purposes. - /// - SlotOrigin Origin { get; } + SlotOrigin Origin { get; } - /// - /// Differentiating slot number from other infos of the same type. - /// - int Slot { get; } + /// + /// Differentiating slot number from other infos of the same type. + /// + int Slot { get; } - /// - /// Indicates if this slot can write to the requested . - /// - /// Save file to try writing to. - /// True if can write to - bool CanWriteTo(SaveFile sav); + /// + /// Indicates if this slot can write to the requested . + /// + /// Save file to try writing to. + /// True if can write to + bool CanWriteTo(SaveFile sav); - /// - /// Checks if the can be written to the for this slot. - /// - /// Save file to try writing to. - /// Entity data to try writing. - /// True if can write to - WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pkm); + /// + /// Checks if the can be written to the for this slot. + /// + /// Save file to try writing to. + /// Entity data to try writing. + /// True if can write to + WriteBlockedMessage CanWriteTo(SaveFile sav, PKM pk); - /// - /// Tries writing the to the . - /// - /// Save file to try writing to. - /// Entity data to try writing. - /// Setting to use when importing the data - /// Returns false if it did not succeed. - bool WriteTo(SaveFile sav, PKM pkm, PKMImportSetting setting = PKMImportSetting.UseDefault); + /// + /// Tries writing the to the . + /// + /// Save file to try writing to. + /// Entity data to try writing. + /// Setting to use when importing the data + /// Returns false if it did not succeed. + bool WriteTo(SaveFile sav, PKM pk, PKMImportSetting setting = PKMImportSetting.UseDefault); - /// - /// Reads a from the . - /// - /// Save file to read from. - PKM Read(SaveFile sav); - } + /// + /// Reads a from the . + /// + /// Save file to read from. + PKM Read(SaveFile sav); } diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotCache.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotCache.cs index faaabeb17..0030b7031 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotCache.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotCache.cs @@ -1,95 +1,94 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains slot data and metadata indicating where the originated from. +/// +public sealed class SlotCache : IComparable { /// - /// Contains slot data and metadata indicating where the originated from. + /// Information regarding how the was obtained. /// - public sealed class SlotCache : IComparable + public readonly ISlotInfo Source; + + /// + /// Save File reference that obtained the . + /// + public readonly SaveFile SAV; + + /// + /// Data that was loaded. + /// + public readonly PKM Entity; + + private static readonly FakeSaveFile NoSaveFile = new(); + + public SlotCache(SlotInfoFile source, PKM entity) { - /// - /// Information regarding how the was obtained. - /// - public readonly ISlotInfo Source; + Source = source; + Entity = entity; + SAV = NoSaveFile; + } - /// - /// Save File reference that obtained the . - /// - public readonly SaveFile SAV; + public SlotCache(ISlotInfo source, PKM entity, SaveFile sav) + { + Source = source; + Entity = entity; + SAV = sav; + } - /// - /// Data that was loaded. - /// - 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); } } diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoBox.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoBox.cs index e97c52f18..e5983a19e 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoBox.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoBox.cs @@ -1,20 +1,19 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Box Data +/// +public sealed record SlotInfoBox(int Box, int Slot) : ISlotInfo { - /// - /// Box Data - /// - 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); } diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoFile.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoFile.cs index 899ed10a6..264593d5d 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoFile.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoFile.cs @@ -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; } diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs index 6ada2d3ff..fa23e1b93 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoLoader.cs @@ -2,160 +2,159 @@ 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 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 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 db, ITrainerInfo dest, ICollection 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 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 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 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 db) - { - if (sav.HasBox) - AddBoxData(sav, db); - - if (sav.HasParty) - AddPartyData(sav, db); - - AddExtraData(sav, db); - } - - public static void AddFromLocalFile(string file, ICollection db, ITrainerInfo dest, ICollection 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 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 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 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 db, ITrainerInfo dest, ICollection 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 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 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 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 db) + { + if (sav.HasBox) + AddBoxData(sav, db); + + if (sav.HasParty) + AddPartyData(sav, db); + + AddExtraData(sav, db); + } + + public static void AddFromLocalFile(string file, ICollection db, ITrainerInfo dest, ICollection 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 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 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 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 } diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoMisc.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoMisc.cs index c8016b5c8..144059b1c 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoMisc.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoMisc.cs @@ -1,36 +1,35 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Miscellaneous origination +/// +public sealed record SlotInfoMisc(byte[] Data, int Slot, int Offset, bool PartyFormat = false) : ISlotInfo { - /// - /// Miscellaneous origination - /// - 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; } -} \ No newline at end of file + + public PKM Read(SaveFile sav) + { + return PartyFormat ? sav.GetPartySlot(Data, Offset) : sav.GetStoredSlot(Data, Offset); + } +} diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoParty.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoParty.cs index ccd621011..72992e06e 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoParty.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotInfoParty.cs @@ -1,33 +1,32 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Party Data +/// +public sealed record SlotInfoParty(int Slot) : ISlotInfo { - /// - /// Party Data - /// - 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); } diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/SlotOrigin.cs b/PKHeX.Core/Editing/Saves/Slots/Info/SlotOrigin.cs index 18f5d646f..ad7a5d9c1 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/SlotOrigin.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/SlotOrigin.cs @@ -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, } diff --git a/PKHeX.Core/Editing/Saves/Slots/Info/WriteBlockedMessage.cs b/PKHeX.Core/Editing/Saves/Slots/Info/WriteBlockedMessage.cs index 8d89bbfbe..c6f83eeae 100644 --- a/PKHeX.Core/Editing/Saves/Slots/Info/WriteBlockedMessage.cs +++ b/PKHeX.Core/Editing/Saves/Slots/Info/WriteBlockedMessage.cs @@ -1,13 +1,12 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Message enumeration indicating why a Write Operation would be blocked for a given +/// +public enum WriteBlockedMessage { - /// - /// Message enumeration indicating why a Write Operation would be blocked for a given - /// - public enum WriteBlockedMessage - { - None, - InvalidPartyConfiguration, - IncompatibleFormat, - InvalidDestination, - } -} \ No newline at end of file + None, + InvalidPartyConfiguration, + IncompatibleFormat, + InvalidDestination, +} diff --git a/PKHeX.Core/Editing/Saves/Slots/SlotChangelog.cs b/PKHeX.Core/Editing/Saves/Slots/SlotChangelog.cs index d6f6946d8..5f579611b 100644 --- a/PKHeX.Core/Editing/Saves/Slots/SlotChangelog.cs +++ b/PKHeX.Core/Editing/Saves/Slots/SlotChangelog.cs @@ -1,84 +1,83 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Tracks slot changes and provides the ability to revert a change. +/// +public sealed class SlotChangelog { - /// - /// Tracks slot changes and provides the ability to revert a change. - /// - public sealed class SlotChangelog + private readonly SaveFile SAV; + private readonly Stack UndoStack = new(); + private readonly Stack 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 UndoStack = new(); - private readonly Stack 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 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); } -} \ No newline at end of file + + 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 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); + } +} diff --git a/PKHeX.Core/Editing/Saves/Slots/SlotEditor.cs b/PKHeX.Core/Editing/Saves/Slots/SlotEditor.cs index 1ab653d66..9ec32e4ac 100644 --- a/PKHeX.Core/Editing/Saves/Slots/SlotEditor.cs +++ b/PKHeX.Core/Editing/Saves/Slots/SlotEditor.cs @@ -1,119 +1,118 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Facilitates interaction with a or other data location's slot data. +/// +public sealed class SlotEditor { - /// - /// Facilitates interaction with a or other data location's slot data. - /// - public sealed class SlotEditor + private readonly SaveFile SAV; + public readonly SlotChangelog Changelog; + public readonly SlotPublisher Publisher; + + public SlotEditor(SaveFile sav) { - private readonly SaveFile SAV; - public readonly SlotChangelog Changelog; - public readonly SlotPublisher Publisher; + SAV = sav; + Changelog = new SlotChangelog(sav); + Publisher = new SlotPublisher(); + } - public SlotEditor(SaveFile sav) - { - SAV = sav; - Changelog = new SlotChangelog(sav); - Publisher = new SlotPublisher(); - } + 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); + /// + /// Gets data from a slot. + /// + /// Slot to retrieve from. + /// Operation succeeded or not via enum value. + public PKM Get(ISlotInfo slot) + { + // Reading from a slot is always allowed. + var pk = slot.Read(SAV); + NotifySlotChanged(slot, SlotTouchType.Get, pk); + return pk; + } - /// - /// Gets data from a slot. - /// - /// Slot to retrieve from. - /// Operation succeeded or not via enum value. - public PKM Get(ISlotInfo slot) - { - // Reading from a slot is always allowed. - var pk = slot.Read(SAV); - NotifySlotChanged(slot, SlotTouchType.Get, pk); - return pk; - } + /// + /// Sets data to a slot. + /// + /// Slot to be set to. + /// Data to set. + /// Type of slot action + /// Operation succeeded or not via enum value. + public SlotTouchResult Set(ISlotInfo slot, PKM pk, SlotTouchType type = SlotTouchType.Set) + { + if (!slot.CanWriteTo(SAV)) + return SlotTouchResult.FailWrite; - /// - /// Sets data to a slot. - /// - /// Slot to be set to. - /// Data to set. - /// Type of slot action - /// Operation succeeded or not via enum value. - 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; - } + /// + /// Deletes a slot. + /// + /// Slot to be deleted. + /// Operation succeeded or not via enum value. + public SlotTouchResult Delete(ISlotInfo slot) + { + if (!slot.CanWriteTo(SAV)) + return SlotTouchResult.FailDelete; - /// - /// Deletes a slot. - /// - /// Slot to be deleted. - /// Operation succeeded or not via enum value. - 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; - } + /// + /// Swaps two slots. + /// + /// Source slot to be switched with . + /// Destination slot to be switched with . + /// Operation succeeded or not via enum value. + public SlotTouchResult Swap(ISlotInfo source, ISlotInfo dest) + { + if (!source.CanWriteTo(SAV)) + return SlotTouchResult.FailSource; + if (!dest.CanWriteTo(SAV)) + return SlotTouchResult.FailDestination; - /// - /// Swaps two slots. - /// - /// Source slot to be switched with . - /// Destination slot to be switched with . - /// Operation succeeded or not via enum value. - 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)); } } diff --git a/PKHeX.Core/Editing/Saves/Slots/SlotPublisher.cs b/PKHeX.Core/Editing/Saves/Slots/SlotPublisher.cs index 788471da1..c82178050 100644 --- a/PKHeX.Core/Editing/Saves/Slots/SlotPublisher.cs +++ b/PKHeX.Core/Editing/Saves/Slots/SlotPublisher.cs @@ -1,50 +1,49 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pushes slot update notifications out to all subscribers. +/// +public sealed class SlotPublisher { /// - /// Pushes slot update notifications out to all subscribers. + /// All instances that provide a view on individual content. /// - public sealed class SlotPublisher + public List> Subscribers { get; } = new(); + + public ISlotInfo? Previous { get; private set; } + public SlotTouchType PreviousType { get; private set; } = SlotTouchType.None; + public PKM? PreviousEntity { get; private set; } + + /// + /// Notifies all with the latest slot change details. + /// + /// Last interacted slot + /// Last interacted slot interaction type + /// Last interacted slot interaction data + public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pk) { - /// - /// All instances that provide a view on individual content. - /// - public List> 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 sub, ISlotInfo slot, SlotTouchType type, PKM pk) + { + if (Previous != null) + sub.NotifySlotOld(Previous); - /// - /// Notifies all with the latest slot change details. - /// - /// Last interacted slot - /// Last interacted slot interaction type - /// Last interacted slot interaction data - 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 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 sub) - { - if (Previous == null || PreviousEntity == null) - return; - ResetView(sub, Previous, PreviousType, PreviousEntity); - } + public void ResetView(ISlotViewer sub) + { + if (Previous == null || PreviousEntity == null) + return; + ResetView(sub, Previous, PreviousType, PreviousEntity); } } diff --git a/PKHeX.Core/Editing/Saves/Slots/SlotTouchResult.cs b/PKHeX.Core/Editing/Saves/Slots/SlotTouchResult.cs index b312e9e0b..d21a04814 100644 --- a/PKHeX.Core/Editing/Saves/Slots/SlotTouchResult.cs +++ b/PKHeX.Core/Editing/Saves/Slots/SlotTouchResult.cs @@ -1,38 +1,37 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Result indicators for modifying a Slot within a or other data location. +/// +public enum SlotTouchResult { /// - /// Result indicators for modifying a Slot within a or other data location. + /// Slot interaction was successful. /// - public enum SlotTouchResult - { - /// - /// Slot interaction was successful. - /// - Success, + Success, - /// - /// Slot interaction failed to do anything. - /// - FailNone, + /// + /// Slot interaction failed to do anything. + /// + FailNone, - /// - /// Slot interaction failed to apply the data. - /// - FailWrite, + /// + /// Slot interaction failed to apply the data. + /// + FailWrite, - /// - /// Slot interaction failed to delete the data. - /// - FailDelete, + /// + /// Slot interaction failed to delete the data. + /// + FailDelete, - /// - /// Slot interaction failed due to a bad/unmodifiable source. - /// - FailSource, + /// + /// Slot interaction failed due to a bad/unmodifiable source. + /// + FailSource, - /// - /// Slot interaction failed due to a bad/unmodifiable destination. - /// - FailDestination, - } -} \ No newline at end of file + /// + /// Slot interaction failed due to a bad/unmodifiable destination. + /// + FailDestination, +} diff --git a/PKHeX.Core/Editing/Saves/Slots/SlotTouchType.cs b/PKHeX.Core/Editing/Saves/Slots/SlotTouchType.cs index 854272ac5..a38e86ab3 100644 --- a/PKHeX.Core/Editing/Saves/Slots/SlotTouchType.cs +++ b/PKHeX.Core/Editing/Saves/Slots/SlotTouchType.cs @@ -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; - } -} \ No newline at end of file +public enum SlotTouchType +{ + None, + Get, + Set, + Delete, + Swap, +} + +public static class SlotTouchTypeUtil +{ + public static bool IsContentChange(this SlotTouchType t) => t > SlotTouchType.Get; +} diff --git a/PKHeX.Core/Editing/Saves/Slots/SlotViewInfo.cs b/PKHeX.Core/Editing/Saves/Slots/SlotViewInfo.cs index 7749741ba..b0dac7521 100644 --- a/PKHeX.Core/Editing/Saves/Slots/SlotViewInfo.cs +++ b/PKHeX.Core/Editing/Saves/Slots/SlotViewInfo.cs @@ -1,39 +1,38 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Tuple containing data for a and the originating +/// +/// +public sealed class SlotViewInfo : IEquatable { - /// - /// Tuple containing data for a and the originating - /// - /// - public sealed class SlotViewInfo : IEquatable + public readonly ISlotInfo Slot; + public readonly ISlotViewer 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 view) { - public readonly ISlotInfo Slot; - public readonly ISlotViewer 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 view) - { - Slot = slot; - View = view; - } - - private bool Equals(SlotViewInfo 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 other && Equals(other)); - public override int GetHashCode() => (Slot.GetHashCode() * 397) ^ View.GetHashCode(); - bool IEquatable.Equals(T other) => other != null && Equals(other); + Slot = slot; + View = view; } -} \ No newline at end of file + + private bool Equals(SlotViewInfo 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 other && Equals(other)); + public override int GetHashCode() => (Slot.GetHashCode() * 397) ^ View.GetHashCode(); + bool IEquatable.Equals(T other) => other != null && Equals(other); +} diff --git a/PKHeX.Core/Editing/Saves/Slots/StorageSlotType.cs b/PKHeX.Core/Editing/Saves/Slots/StorageSlotType.cs index a97be434f..b3424fa49 100644 --- a/PKHeX.Core/Editing/Saves/Slots/StorageSlotType.cs +++ b/PKHeX.Core/Editing/Saves/Slots/StorageSlotType.cs @@ -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, } diff --git a/PKHeX.Core/Editing/Showdown/ShowdownParsing.cs b/PKHeX.Core/Editing/Showdown/ShowdownParsing.cs index 78f837c41..50af0c87a 100644 --- a/PKHeX.Core/Editing/Showdown/ShowdownParsing.cs +++ b/PKHeX.Core/Editing/Showdown/ShowdownParsing.cs @@ -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; + +/// +/// Logic for parsing details for objects. +/// +public static class ShowdownParsing { + private static readonly string[] genderForms = { "", "F", "" }; + /// - /// Logic for parsing details for objects. + /// Gets the Form ID from the input . /// - public static class ShowdownParsing + /// + /// + /// Species ID the form belongs to + /// Format the form name should appear in + /// Zero (base form) if no form matches the input string. + public static int GetFormFromString(string name, GameStrings strings, int species, int format) { - private static readonly string[] genderForms = { "", "F", "" }; + if (name.Length == 0) + return 0; - /// - /// Gets the Form ID from the input . - /// - /// - /// - /// Species ID the form belongs to - /// Format the form name should appear in - /// Zero (base form) if no form matches the input string. - 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))); + } + + /// + /// Converts a Form ID to string. + /// + /// + /// + /// + /// + 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"; + + /// + /// Converts the PKHeX standard form name to Showdown's form name. + /// + /// Species ID + /// PKHeX form name + 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))); - } - - /// - /// Converts a Form ID to string. - /// - /// - /// - /// - /// - /// - 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"; - - /// - /// Converts the PKHeX standard form name to Showdown's form name. - /// - /// Species ID - /// PKHeX form name - 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, }; } - /// - /// Converts the Showdown form name to PKHeX's form name. - /// - /// Species ID - /// Showdown form name - /// Showdown ability ID - 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(' ', '-'), + }; + } - /// - /// Fetches data from the input . - /// - /// Raw lines containing numerous multi-line set data. - /// objects until is consumed. - public static IEnumerable GetShowdownSets(IEnumerable lines) + /// + /// Converts the Showdown form name to PKHeX's form name. + /// + /// Species ID + /// Showdown form name + /// Showdown ability ID + 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(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, + }; + } + + /// + /// Fetches data from the input . + /// + /// Raw lines containing numerous multi-line set data. + /// objects until is consumed. + public static IEnumerable GetShowdownSets(IEnumerable 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(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); + } - /// - /// Converts the data into an importable set format for Pokémon Showdown. - /// - /// PKM to convert to string - /// Multi line set data - public static string GetShowdownText(PKM pkm) - { - if (pkm.Species == 0) - return string.Empty; - return new ShowdownSet(pkm).Text; - } + /// + /// Converts the data into an importable set format for Pokémon Showdown. + /// + /// PKM to convert to string + /// Multi line set data + public static string GetShowdownText(PKM pk) + { + if (pk.Species == 0) + return string.Empty; + return new ShowdownSet(pk).Text; + } - /// - /// Fetches ShowdownSet lines from the input data. - /// - /// Pokémon data to summarize. - /// Consumable list of lines. - public static IEnumerable GetShowdownSets(IEnumerable data) => data.Where(p => p.Species != 0).Select(GetShowdownText); + /// + /// Fetches ShowdownSet lines from the input data. + /// + /// Pokémon data to summarize. + /// Consumable list of lines. + public static IEnumerable GetShowdownSets(IEnumerable data) => data.Where(p => p.Species != 0).Select(GetShowdownText); - /// - /// Fetches ShowdownSet lines from the input data, and combines it into one string. - /// - /// Pokémon data to summarize. - /// Splitter between each set. - /// Single string containing all lines. - public static string GetShowdownSets(IEnumerable data, string separator) => string.Join(separator, GetShowdownSets(data)); + /// + /// Fetches ShowdownSet lines from the input data, and combines it into one string. + /// + /// Pokémon data to summarize. + /// Splitter between each set. + /// Single string containing all lines. + public static string GetShowdownSets(IEnumerable data, string separator) => string.Join(separator, GetShowdownSets(data)); - /// - /// Gets a localized string preview of the provided . - /// - /// Pokémon data - /// Language code - /// Multi-line string - 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); - } + /// + /// Gets a localized string preview of the provided . + /// + /// Pokémon data + /// Language code + /// Multi-line string + 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); } } diff --git a/PKHeX.Core/Editing/Showdown/ShowdownSet.cs b/PKHeX.Core/Editing/Showdown/ShowdownSet.cs index 3f0e0dc4a..da1f31b95 100644 --- a/PKHeX.Core/Editing/Showdown/ShowdownSet.cs +++ b/PKHeX.Core/Editing/Showdown/ShowdownSet.cs @@ -2,628 +2,630 @@ using System.Collections.Generic; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for exporting and importing data in Pokémon Showdown's text format. +/// +public sealed class ShowdownSet : IBattleTemplate { + private static readonly string[] StatNames = { "HP", "Atk", "Def", "SpA", "SpD", "Spe" }; + private static readonly string[] Splitters = {"\r\n", "\n"}; + private static readonly string[] StatSplitters = { " / ", " " }; + private static readonly string[] LineSplit = {": "}; + private static readonly string[] ItemSplit = {" @ "}; + private static readonly char[] ParenJunk = { '[', ']', '(', ')' }; + private static readonly ushort[] DashedSpecies = {782, 783, 784, 250, 032, 029}; // Kommo-o, Ho-Oh, Nidoran-M, Nidoran-F + private const int MAX_SPECIES = (int)MAX_COUNT - 1; + private static readonly GameStrings DefaultStrings = GameInfo.GetStrings(GameLanguage.DefaultLanguage); + + /// + public int Species { get; private set; } = -1; + + /// + public int Format { get; private set; } = RecentTrainerCache.Format; + + /// + public string Nickname { get; set; } = string.Empty; + + /// + public int Gender { get; private set; } = -1; + + /// + public int HeldItem { get; private set; } + + /// + public int Ability { get; private set; } = -1; + + /// + public int Level { get; private set; } = 100; + + /// + public bool Shiny { get; private set; } + + /// + public int Friendship { get; private set; } = 255; + + /// + public int Nature { get; set; } = -1; + + /// + public string FormName { get; private set; } = string.Empty; + + /// + public int Form { get; private set; } + + /// + public int[] EVs { get; private set; } = {00, 00, 00, 00, 00, 00}; + + /// + public int[] IVs { get; private set; } = {31, 31, 31, 31, 31, 31}; + + /// + public int HiddenPowerType { get; set; } = -1; + + /// + public int[] Moves { get; } = {0, 0, 0, 0}; + + /// + public bool CanGigantamax { get; set; } + /// - /// Logic for exporting and importing data in Pokémon Showdown's text format. + /// Any lines that failed to be parsed. /// - public sealed class ShowdownSet : IBattleTemplate + public readonly List InvalidLines = new(); + + private GameStrings Strings { get; set; } = DefaultStrings; + + private int[] IVsSpeedFirst => new[] {IVs[0], IVs[1], IVs[2], IVs[5], IVs[3], IVs[4]}; + private int[] IVsSpeedLast => new[] {IVs[0], IVs[1], IVs[2], IVs[4], IVs[5], IVs[3]}; + private int[] EVsSpeedFirst => new[] {EVs[0], EVs[1], EVs[2], EVs[5], EVs[3], EVs[4]}; + private int[] EVsSpeedLast => new[] {EVs[0], EVs[1], EVs[2], EVs[4], EVs[5], EVs[3]}; + + /// + /// Loads a new from the input string. + /// + /// Single-line string which will be split before loading. + public ShowdownSet(string input) : this(input.Split(Splitters, 0)) { } + + /// + /// Loads a new from the input string. + /// + /// Enumerable list of lines. + public ShowdownSet(IEnumerable lines) => LoadLines(lines); + + private void LoadLines(IEnumerable lines) { - private static readonly string[] StatNames = { "HP", "Atk", "Def", "SpA", "SpD", "Spe" }; - private static readonly string[] Splitters = {"\r\n", "\n"}; - private static readonly string[] StatSplitters = { " / ", " " }; - private static readonly string[] LineSplit = {": "}; - private static readonly string[] ItemSplit = {" @ "}; - private static readonly char[] ParenJunk = { '[', ']', '(', ')' }; - private static readonly ushort[] DashedSpecies = {782, 783, 784, 250, 032, 029}; // Kommo-o, Ho-Oh, Nidoran-M, Nidoran-F - private const int MAX_SPECIES = (int)MAX_COUNT - 1; - private static readonly GameStrings DefaultStrings = GameInfo.GetStrings(GameLanguage.DefaultLanguage); + ParseLines(lines); - /// - public int Species { get; private set; } = -1; + FormName = ShowdownParsing.SetShowdownFormName(Species, FormName, Ability); + Form = ShowdownParsing.GetFormFromString(FormName, Strings, Species, Format); - /// - public int Format { get; private set; } = RecentTrainerCache.Format; + // Handle edge case with fixed-gender forms. + if (Species is (int) Meowstic or (int) Indeedee) + ReviseGenderedForms(); + } - /// - public string Nickname { get; set; } = string.Empty; - - /// - public int Gender { get; private set; } = -1; - - /// - public int HeldItem { get; private set; } - - /// - public int Ability { get; private set; } = -1; - - /// - public int Level { get; private set; } = 100; - - /// - public bool Shiny { get; private set; } - - /// - public int Friendship { get; private set; } = 255; - - /// - public int Nature { get; set; } = -1; - - /// - public string FormName { get; private set; } = string.Empty; - - /// - public int Form { get; private set; } - - /// - public int[] EVs { get; private set; } = {00, 00, 00, 00, 00, 00}; - - /// - public int[] IVs { get; private set; } = {31, 31, 31, 31, 31, 31}; - - /// - public int HiddenPowerType { get; set; } = -1; - - /// - public int[] Moves { get; } = {0, 0, 0, 0}; - - /// - public bool CanGigantamax { get; set; } - - /// - /// Any lines that failed to be parsed. - /// - public readonly List InvalidLines = new(); - - private GameStrings Strings { get; set; } = DefaultStrings; - - private int[] IVsSpeedFirst => new[] {IVs[0], IVs[1], IVs[2], IVs[5], IVs[3], IVs[4]}; - private int[] IVsSpeedLast => new[] {IVs[0], IVs[1], IVs[2], IVs[4], IVs[5], IVs[3]}; - private int[] EVsSpeedFirst => new[] {EVs[0], EVs[1], EVs[2], EVs[5], EVs[3], EVs[4]}; - private int[] EVsSpeedLast => new[] {EVs[0], EVs[1], EVs[2], EVs[4], EVs[5], EVs[3]}; - - /// - /// Loads a new from the input string. - /// - /// Single-line string which will be split before loading. - public ShowdownSet(string input) : this(input.Split(Splitters, 0)) { } - - /// - /// Loads a new from the input string. - /// - /// Enumerable list of lines. - public ShowdownSet(IEnumerable lines) => LoadLines(lines); - - private void LoadLines(IEnumerable lines) + private static IEnumerable GetSanitizedLines(IEnumerable lines) + { + foreach (var line in lines) { - ParseLines(lines); - - FormName = ShowdownParsing.SetShowdownFormName(Species, FormName, Ability); - Form = ShowdownParsing.GetFormFromString(FormName, Strings, Species, Format); - - // Handle edge case with fixed-gender forms. - if (Species is (int) Meowstic or (int) Indeedee) - ReviseGenderedForms(); - } - - private static IEnumerable GetSanitizedLines(IEnumerable lines) - { - foreach (var line in lines) - { - var trim = line.Trim(); - if (trim.Length <= 2) - continue; - - // Sanitize apostrophes & dashes - if (trim.IndexOf('\'') != -1) - trim = trim.Replace('\'', '’'); - if (trim.IndexOf('–') != -1) - trim = trim.Replace('–', '-'); - yield return trim; - } - } - - private void ReviseGenderedForms() - { - if (Gender == 1) // Recognized with (F) - { - FormName = "F"; - Form = 1; - } - else - { - FormName = Form == 1 ? "F" : "M"; - Gender = Form; - } - } - - private const int MaxMoveCount = 4; - - private void ParseLines(IEnumerable lines) - { - lines = GetSanitizedLines(lines); - using var e = lines.GetEnumerator(); - if (!e.MoveNext()) - return; - - ParseFirstLine(e.Current!); - int movectr = 0; - while (e.MoveNext()) - { - var line = e.Current!; - if (string.IsNullOrWhiteSpace(line)) - continue; - - if (line[0] == '-') - { - string moveString = ParseLineMove(line); - int move = StringUtil.FindIndexIgnoreCase(Strings.movelist, moveString); - if (move < 0) - InvalidLines.Add($"Unknown Move: {moveString}"); - else if (Array.IndexOf(Moves, move) != -1) - InvalidLines.Add($"Duplicate Move: {moveString}"); - else - Moves[movectr++] = move; - - if (movectr == MaxMoveCount) - return; // End of moves, end of set data - continue; - } - - if (movectr != 0) - break; - - var split = line.Split(LineSplit, StringSplitOptions.None); - var valid = split.Length == 1 - ? ParseSingle(line) // Nature - : ParseEntry(split[0].Trim(), split[1].Trim()); - if (!valid) - InvalidLines.Add(line); - } - } - - private bool ParseSingle(string identifier) - { - if (!identifier.EndsWith("Nature")) - return false; - var naturestr = identifier.Split(' ')[0].Trim(); - return (Nature = StringUtil.FindIndexIgnoreCase(Strings.natures, naturestr)) >= 0; - } - - private bool ParseEntry(string identifier, string value) - { - switch (identifier) - { - case "Ability" or "Trait": return (Ability = StringUtil.FindIndexIgnoreCase(Strings.abilitylist, value)) >= 0; - case "Shiny": return Shiny = StringUtil.IsMatchIgnoreCase("Yes", value); - case "Gigantamax": return CanGigantamax = StringUtil.IsMatchIgnoreCase("Yes", value); - case "Nature": return (Nature = StringUtil.FindIndexIgnoreCase(Strings.natures, value)) >= 0; - case "EV" or "EVs": ParseLineEVs(value); return true; - case "IV" or "IVs": ParseLineIVs(value); return true; - case "Level": - { - if (!int.TryParse(value.Trim(), out int val)) - return false; - Level = val; - return true; - } - case "Friendship" or "Happiness": - { - if (!int.TryParse(value.Trim(), out int val)) - return false; - Friendship = val; - return true; - } - default: - return false; - } - } - - /// - /// Gets the standard Text representation of the set details. - /// - public string Text => GetText(); - - /// - /// Gets the localized Text representation of the set details. - /// - /// 2 character language code - public string LocalizedText(string lang) => LocalizedText(GameLanguage.GetLanguageIndex(lang)); - - /// - /// Gets the localized Text representation of the set details. - /// - /// Language ID - private string LocalizedText(int lang) - { - var strings = GameInfo.GetStrings(lang); - return GetText(strings); - } - - private string GetText(GameStrings? strings = null) - { - if (Species is <= 0 or > MAX_SPECIES) - return string.Empty; - - if (strings != null) - Strings = strings; - - var result = GetSetLines(); - return string.Join(Environment.NewLine, result); - } - - public List GetSetLines() - { - var result = new List(); - - // First Line: Name, Nickname, Gender, Item - var form = ShowdownParsing.GetShowdownFormName(Species, FormName); - result.Add(GetStringFirstLine(form)); - - // IVs - var ivs = GetStringStats(IVsSpeedLast, Format < 3 ? 15 : 31); - if (ivs.Count > 0) - result.Add($"IVs: {string.Join(" / ", ivs)}"); - - // EVs - var evs = GetStringStats(EVsSpeedLast, 0); - if (evs.Count > 0) - result.Add($"EVs: {string.Join(" / ", evs)}"); - - // Secondary Stats - if ((uint)Ability < Strings.Ability.Count) - result.Add($"Ability: {Strings.Ability[Ability]}"); - if (Level != 100) - result.Add($"Level: {Level}"); - if (CanGigantamax) - result.Add("Gigantamax: Yes"); - if (Shiny) - result.Add("Shiny: Yes"); - - if ((uint)Nature < Strings.Natures.Count) - result.Add($"{Strings.Natures[Nature]} Nature"); - - // Moves - result.AddRange(GetStringMoves()); - return result; - } - - private string GetStringFirstLine(string form) - { - string specForm = Strings.Species[Species]; - if (form.Length != 0) - specForm += $"-{form.Replace("Mega ", "Mega-")}"; - else if (Species == (int)NidoranM) - specForm = specForm.Replace("♂", "-M"); - else if (Species == (int)NidoranF) - specForm = specForm.Replace("♀", "-F"); - - string result = GetSpeciesNickname(specForm); - - // omit genderless or nonspecific - if (Gender is 1) - result += " (F)"; - else if (Gender is 0) - result += " (M)"; - - if (HeldItem > 0) - { - var items = Strings.GetItemStrings(Format); - if ((uint)HeldItem < items.Length) - result += $" @ {items[HeldItem]}"; - } - return result; - } - - private string GetSpeciesNickname(string specForm) - { - if (Nickname.Length == 0) - return specForm; - bool isNicknamed = SpeciesName.IsNicknamedAnyLanguage(Species, Nickname, Format); - if (!isNicknamed) - return specForm; - return $"{Nickname} ({specForm})"; - } - - private static IList GetStringStats(ReadOnlySpan stats, int ignore) - { - var result = new List(); - for (int i = 0; i < stats.Length; i++) - { - if (stats[i] == ignore) - continue; // ignore unused stats - result.Add($"{stats[i]} {StatNames[i]}"); - } - return result; - } - - private IEnumerable GetStringMoves() - { - var moves = Strings.Move; - foreach (int move in Moves) - { - if (move == 0 || (uint)move >= moves.Count) - continue; - - if (move == 237) // Hidden Power - { - yield return $"- {moves[move]} [{Strings.Types[1 + HiddenPowerType]}]"; - continue; - } - - yield return $"- {moves[move]}"; - } - } - - /// - /// Converts the data into an importable set format for Pokémon Showdown. - /// - /// PKM to convert to string - /// New ShowdownSet object representing the input - public ShowdownSet(PKM pkm) - { - if (pkm.Species <= 0) - return; - - Format = pkm.Format; - - Nickname = pkm.Nickname; - Species = pkm.Species; - HeldItem = pkm.HeldItem; - Ability = pkm.Ability; - EVs = pkm.EVs; - IVs = pkm.IVs; - Moves = pkm.Moves; - Nature = pkm.StatNature; - Gender = pkm.Gender < 2 ? pkm.Gender : 2; - Friendship = pkm.CurrentFriendship; - Level = Experience.GetLevel(pkm.EXP, pkm.PersonalInfo.EXPGrowth); - Shiny = pkm.IsShiny; - - if (pkm is IGigantamax g) - CanGigantamax = g.CanGigantamax; - - HiddenPowerType = HiddenPower.GetType(IVs, Format); - if (pkm is IHyperTrain h) - { - for (int i = 0; i < 6; i++) - { - if (h.IsHyperTrained(i)) - IVs[i] = pkm.MaxIV; - } - } - - FormName = ShowdownParsing.GetStringFromForm(Form = pkm.Form, Strings, Species, Format); - } - - private void ParseFirstLine(string first) - { - if (first.Contains(" @ ")) - { - string[] pieces = first.Split(ItemSplit, StringSplitOptions.None); - string itemName = pieces[^1].Trim(); - - ParseItemName(itemName); - ParseFirstLineNoItem(pieces[0]); - } - else - { - ParseFirstLineNoItem(first); - } - } - - private void ParseItemName(string itemName) - { - if (TrySetItem(Format)) - return; - if (TrySetItem(3)) - return; - if (TrySetItem(2)) - return; - InvalidLines.Add($"Unknown Item: {itemName}"); - - bool TrySetItem(int format) - { - var items = Strings.GetItemStrings(format); - int item = StringUtil.FindIndexIgnoreCase(items, itemName); - if (item < 0) - return false; - HeldItem = item; - Format = format; - return true; - } - } - - private void ParseFirstLineNoItem(string line) - { - // Gender Detection - if (line.EndsWith("(M)")) - { - line = line[..^3]; - Gender = 0; - } - else if (line.EndsWith("(F)")) - { - line = line[..^3]; - Gender = 1; - } - - // Nickname Detection - if (line.IndexOf('(') != -1 && line.IndexOf(')') != -1) - ParseSpeciesNickname(line); - else - ParseSpeciesForm(line); - } - - private const string Gmax = "-Gmax"; - - private bool ParseSpeciesForm(string speciesLine) - { - speciesLine = speciesLine.Trim(); - if (speciesLine.Length == 0) - return false; - - if (speciesLine.EndsWith(Gmax)) - { - CanGigantamax = true; - speciesLine = speciesLine[..^Gmax.Length]; - } - - if ((Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine)) >= 0) // success, nothing else! - return true; - - // Form string present. - int end = speciesLine.LastIndexOf('-'); - if (end < 0) - return false; - - Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine[..end]); - FormName = speciesLine[(end + 1)..]; - - if (Species >= 0) - return true; - - // failure to parse, check edge cases - foreach (var e in DashedSpecies) - { - var sn = Strings.Species[e]; - if (!speciesLine.StartsWith(sn.Replace("♂", "-M").Replace("♀", "-F"))) - continue; - Species = e; - FormName = speciesLine[sn.Length..]; - return true; - } - - // Version Megas - end = speciesLine.LastIndexOf('-', Math.Max(0, end - 1)); - if (end < 0) - return false; - Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine[..end]); - FormName = speciesLine[(end + 1)..]; - - return Species >= 0; - } - - private void ParseSpeciesNickname(string line) - { - int index = line.LastIndexOf('('); - string species, nickname; - if (index > 1) // parenthesis value after: Nickname (Species), correct. - { - nickname = line[..index].Trim(); - species = line[index..].Trim(); - species = RemoveAll(species, ParenJunk); // Trim out excess data - } - else // parenthesis value before: (Species) Nickname, incorrect - { - int start = index + 1; - int end = line.IndexOf(')'); - var tmp = line[start..end]; - if (end < line.Length - 2) - { - nickname = line[(end + 2)..]; - species = tmp; - } - else // (Species), or garbage - { - species = tmp; - nickname = string.Empty; - } - } - - if (ParseSpeciesForm(species)) - Nickname = nickname; - else if (ParseSpeciesForm(nickname)) - Nickname = species; - } - - private string ParseLineMove(string line) - { - const int hiddenPower = 237; - string moveString = line[(line[1] == ' ' ? 2 : 1)..].Split('/')[0].Trim(); - if (!moveString.StartsWith(Strings.Move[hiddenPower])) // Hidden Power - return moveString; // regular move - - if (moveString.Length <= 13) - return Strings.Move[hiddenPower]; - - // Defined Hidden Power - string type = moveString[13..]; - type = RemoveAll(type, ParenJunk); // Trim out excess data - int hpVal = StringUtil.FindIndexIgnoreCase(Strings.types, type) - 1; // Get HP Type - - HiddenPowerType = hpVal; - if (!Array.TrueForAll(IVs, z => z == 31)) - { - if (!HiddenPower.SetIVsForType(hpVal, IVs, Format)) - InvalidLines.Add($"Invalid IVs for Hidden Power Type: {type}"); - } - else if (hpVal >= 0) - { - HiddenPower.SetIVs(hpVal, IVs, Format); // Alter IVs - } - else - { - InvalidLines.Add($"Invalid Hidden Power Type: {type}"); - } - return Strings.Move[hiddenPower]; - } - - private void ParseLineEVs(string line) - { - var list = SplitLineStats(line); - if ((list.Length & 1) == 1) - InvalidLines.Add("Unknown EV input."); - for (int i = 0; i < list.Length / 2; i++) - { - int pos = i * 2; - int index = StringUtil.FindIndexIgnoreCase(StatNames, list[pos + 1]); - if (index >= 0 && ushort.TryParse(list[pos + 0], out var EV)) - EVs[index] = EV; - else - InvalidLines.Add($"Unknown EV stat: {list[pos]}"); - } - EVs = EVsSpeedFirst; - } - - private void ParseLineIVs(string line) - { - var list = SplitLineStats(line); - if ((list.Length & 1) == 1) - InvalidLines.Add("Unknown IV input."); - for (int i = 0; i < list.Length / 2; i++) - { - int pos = i * 2; - int index = StringUtil.FindIndexIgnoreCase(StatNames, list[pos + 1]); - if (index >= 0 && byte.TryParse(list[pos + 0], out var iv)) - IVs[index] = iv; - else - InvalidLines.Add($"Unknown IV stat: {list[pos]}"); - } - IVs = IVsSpeedFirst; - } - - private static string RemoveAll(string original, char[] remove) - { - Span result = stackalloc char[original.Length]; - int ctr = 0; - foreach (var c in original) - { - if (Array.IndexOf(remove, c) == -1) - result[ctr++] = c; - } - if (ctr == original.Length) - return original; - return new string(result[..ctr].ToArray()); - } - - private static string[] SplitLineStats(string line) - { - // Because people think they can type sets out... - return line - .Replace("SAtk", "SpA").Replace("Sp Atk", "SpA") - .Replace("SDef", "SpD").Replace("Sp Def", "SpD") - .Replace("Spd", "Spe").Replace("Speed", "Spe").Split(StatSplitters, StringSplitOptions.None); + var trim = line.Trim(); + if (trim.Length <= 2) + continue; + + // Sanitize apostrophes & dashes + if (trim.IndexOf('\'') != -1) + trim = trim.Replace('\'', '’'); + if (trim.IndexOf('–') != -1) + trim = trim.Replace('–', '-'); + yield return trim; } } + + private void ReviseGenderedForms() + { + if (Gender == 1) // Recognized with (F) + { + FormName = "F"; + Form = 1; + } + else + { + FormName = Form == 1 ? "F" : "M"; + Gender = Form; + } + } + + private const int MaxMoveCount = 4; + + private void ParseLines(IEnumerable lines) + { + lines = GetSanitizedLines(lines); + using var e = lines.GetEnumerator(); + if (!e.MoveNext()) + return; + + ParseFirstLine(e.Current!); + int movectr = 0; + while (e.MoveNext()) + { + var line = e.Current!; + if (string.IsNullOrWhiteSpace(line)) + continue; + + if (line[0] == '-') + { + string moveString = ParseLineMove(line); + int move = StringUtil.FindIndexIgnoreCase(Strings.movelist, moveString); + if (move < 0) + InvalidLines.Add($"Unknown Move: {moveString}"); + else if (Array.IndexOf(Moves, move) != -1) + InvalidLines.Add($"Duplicate Move: {moveString}"); + else + Moves[movectr++] = move; + + if (movectr == MaxMoveCount) + return; // End of moves, end of set data + continue; + } + + if (movectr != 0) + break; + + var split = line.Split(LineSplit, StringSplitOptions.None); + var valid = split.Length == 1 + ? ParseSingle(line) // Nature + : ParseEntry(split[0].Trim(), split[1].Trim()); + if (!valid) + InvalidLines.Add(line); + } + } + + private bool ParseSingle(string identifier) + { + if (!identifier.EndsWith("Nature", StringComparison.Ordinal)) + return false; + var naturestr = identifier.Split(' ')[0].Trim(); + return (Nature = StringUtil.FindIndexIgnoreCase(Strings.natures, naturestr)) >= 0; + } + + private bool ParseEntry(string identifier, string value) + { + switch (identifier) + { + case "Ability" or "Trait": return (Ability = StringUtil.FindIndexIgnoreCase(Strings.abilitylist, value)) >= 0; + case "Shiny": return Shiny = StringUtil.IsMatchIgnoreCase("Yes", value); + case "Gigantamax": return CanGigantamax = StringUtil.IsMatchIgnoreCase("Yes", value); + case "Nature": return (Nature = StringUtil.FindIndexIgnoreCase(Strings.natures, value)) >= 0; + case "EV" or "EVs": ParseLineEVs(value); return true; + case "IV" or "IVs": ParseLineIVs(value); return true; + case "Level": + { + if (!int.TryParse(value.Trim(), out int val)) + return false; + Level = val; + return true; + } + case "Friendship" or "Happiness": + { + if (!int.TryParse(value.Trim(), out int val)) + return false; + Friendship = val; + return true; + } + default: + return false; + } + } + + /// + /// Gets the standard Text representation of the set details. + /// + public string Text => GetText(); + + /// + /// Gets the localized Text representation of the set details. + /// + /// 2 character language code + public string LocalizedText(string lang) => LocalizedText(GameLanguage.GetLanguageIndex(lang)); + + /// + /// Gets the localized Text representation of the set details. + /// + /// Language ID + private string LocalizedText(int lang) + { + var strings = GameInfo.GetStrings(lang); + return GetText(strings); + } + + private string GetText(GameStrings? strings = null) + { + if (Species is <= 0 or > MAX_SPECIES) + return string.Empty; + + if (strings != null) + Strings = strings; + + var result = GetSetLines(); + return string.Join(Environment.NewLine, result); + } + + public List GetSetLines() + { + var result = new List(); + + // First Line: Name, Nickname, Gender, Item + var form = ShowdownParsing.GetShowdownFormName(Species, FormName); + result.Add(GetStringFirstLine(form)); + + // IVs + var ivs = GetStringStats(IVsSpeedLast, Format < 3 ? 15 : 31); + if (ivs.Count > 0) + result.Add($"IVs: {string.Join(" / ", ivs)}"); + + // EVs + var evs = GetStringStats(EVsSpeedLast, 0); + if (evs.Count > 0) + result.Add($"EVs: {string.Join(" / ", evs)}"); + + // Secondary Stats + if ((uint)Ability < Strings.Ability.Count) + result.Add($"Ability: {Strings.Ability[Ability]}"); + if (Level != 100) + result.Add($"Level: {Level}"); + if (CanGigantamax) + result.Add("Gigantamax: Yes"); + if (Shiny) + result.Add("Shiny: Yes"); + + if ((uint)Nature < Strings.Natures.Count) + result.Add($"{Strings.Natures[Nature]} Nature"); + + // Moves + result.AddRange(GetStringMoves()); + return result; + } + + private string GetStringFirstLine(string form) + { + string specForm = Strings.Species[Species]; + if (form.Length != 0) + specForm += $"-{form.Replace("Mega ", "Mega-")}"; + else if (Species == (int)NidoranM) + specForm = specForm.Replace("♂", "-M"); + else if (Species == (int)NidoranF) + specForm = specForm.Replace("♀", "-F"); + + string result = GetSpeciesNickname(specForm); + + // omit genderless or nonspecific + if (Gender is 1) + result += " (F)"; + else if (Gender is 0) + result += " (M)"; + + if (HeldItem > 0) + { + var items = Strings.GetItemStrings(Format); + if ((uint)HeldItem < items.Length) + result += $" @ {items[HeldItem]}"; + } + return result; + } + + private string GetSpeciesNickname(string specForm) + { + if (Nickname.Length == 0) + return specForm; + bool isNicknamed = SpeciesName.IsNicknamedAnyLanguage(Species, Nickname, Format); + if (!isNicknamed) + return specForm; + return $"{Nickname} ({specForm})"; + } + + private static IList GetStringStats(ReadOnlySpan stats, int ignore) + { + var result = new List(); + for (int i = 0; i < stats.Length; i++) + { + if (stats[i] == ignore) + continue; // ignore unused stats + result.Add($"{stats[i]} {StatNames[i]}"); + } + return result; + } + + private IEnumerable GetStringMoves() + { + var moves = Strings.Move; + foreach (int move in Moves) + { + if (move == 0 || (uint)move >= moves.Count) + continue; + + if (move == 237) // Hidden Power + { + yield return $"- {moves[move]} [{Strings.Types[1 + HiddenPowerType]}]"; + continue; + } + + yield return $"- {moves[move]}"; + } + } + + /// + /// Converts the data into an importable set format for Pokémon Showdown. + /// + /// PKM to convert to string + /// New ShowdownSet object representing the input + public ShowdownSet(PKM pk) + { + if (pk.Species <= 0) + return; + + Format = pk.Format; + + Nickname = pk.Nickname; + Species = pk.Species; + HeldItem = pk.HeldItem; + Ability = pk.Ability; + EVs = pk.EVs; + IVs = pk.IVs; + Moves = pk.Moves; + Nature = pk.StatNature; + Gender = pk.Gender < 2 ? pk.Gender : 2; + Friendship = pk.CurrentFriendship; + Level = Experience.GetLevel(pk.EXP, pk.PersonalInfo.EXPGrowth); + Shiny = pk.IsShiny; + + if (pk is IGigantamax g) + CanGigantamax = g.CanGigantamax; + + HiddenPowerType = HiddenPower.GetType(IVs, Format); + if (pk is IHyperTrain h) + { + for (int i = 0; i < 6; i++) + { + if (h.IsHyperTrained(i)) + IVs[i] = pk.MaxIV; + } + } + + FormName = ShowdownParsing.GetStringFromForm(Form = pk.Form, Strings, Species, Format); + } + + private void ParseFirstLine(string first) + { + if (first.Contains(" @ ")) + { + string[] pieces = first.Split(ItemSplit, StringSplitOptions.None); + string itemName = pieces[^1].Trim(); + + ParseItemName(itemName); + ParseFirstLineNoItem(pieces[0]); + } + else + { + ParseFirstLineNoItem(first); + } + } + + private void ParseItemName(string itemName) + { + if (TrySetItem(Format)) + return; + if (TrySetItem(3)) + return; + if (TrySetItem(2)) + return; + InvalidLines.Add($"Unknown Item: {itemName}"); + + bool TrySetItem(int format) + { + var items = Strings.GetItemStrings(format); + int item = StringUtil.FindIndexIgnoreCase(items, itemName); + if (item < 0) + return false; + HeldItem = item; + Format = format; + return true; + } + } + + private void ParseFirstLineNoItem(string line) + { + // Gender Detection + if (line.EndsWith("(M)", StringComparison.Ordinal)) + { + line = line[..^3]; + Gender = 0; + } + else if (line.EndsWith("(F)", StringComparison.Ordinal)) + { + line = line[..^3]; + Gender = 1; + } + + // Nickname Detection + if (line.IndexOf('(') != -1 && line.IndexOf(')') != -1) + ParseSpeciesNickname(line); + else + ParseSpeciesForm(line); + } + + private const string Gmax = "-Gmax"; + + private bool ParseSpeciesForm(string speciesLine) + { + speciesLine = speciesLine.Trim(); + if (speciesLine.Length == 0) + return false; + + if (speciesLine.EndsWith(Gmax, StringComparison.Ordinal)) + { + CanGigantamax = true; + speciesLine = speciesLine[..^Gmax.Length]; + } + + if ((Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine)) >= 0) // success, nothing else! + return true; + + // Form string present. + int end = speciesLine.LastIndexOf('-'); + if (end < 0) + return false; + + Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine[..end]); + FormName = speciesLine[(end + 1)..]; + + if (Species >= 0) + return true; + + // failure to parse, check edge cases + foreach (var e in DashedSpecies) + { + var sn = Strings.Species[e]; + if (!speciesLine.StartsWith(sn.Replace("♂", "-M").Replace("♀", "-F"), StringComparison.Ordinal)) + continue; + Species = e; + FormName = speciesLine[sn.Length..]; + return true; + } + + // Version Megas + end = speciesLine.LastIndexOf('-', Math.Max(0, end - 1)); + if (end < 0) + return false; + Species = StringUtil.FindIndexIgnoreCase(Strings.specieslist, speciesLine[..end]); + FormName = speciesLine[(end + 1)..]; + + return Species >= 0; + } + + private void ParseSpeciesNickname(string line) + { + int index = line.LastIndexOf('('); + string species, nickname; + if (index > 1) // parenthesis value after: Nickname (Species), correct. + { + nickname = line[..index].Trim(); + species = line[index..].Trim(); + species = RemoveAll(species, ParenJunk); // Trim out excess data + } + else // parenthesis value before: (Species) Nickname, incorrect + { + int start = index + 1; + int end = line.IndexOf(')'); + var tmp = line[start..end]; + if (end < line.Length - 2) + { + nickname = line[(end + 2)..]; + species = tmp; + } + else // (Species), or garbage + { + species = tmp; + nickname = string.Empty; + } + } + + if (ParseSpeciesForm(species)) + Nickname = nickname; + else if (ParseSpeciesForm(nickname)) + Nickname = species; + } + + private string ParseLineMove(string line) + { + string moveString = line[(line[1] == ' ' ? 2 : 1)..].Split('/')[0].Trim(); + + const int hiddenPower = 237; + var hiddenPowerName = Strings.Move[hiddenPower]; + if (!moveString.StartsWith(hiddenPowerName, StringComparison.Ordinal)) // Hidden Power + return moveString; // regular move + + // Assumes English... + if (moveString.Length <= 13) + return hiddenPowerName; + + // Defined Hidden Power + string type = moveString[13..]; + type = RemoveAll(type, ParenJunk); // Trim out excess data + int hpVal = StringUtil.FindIndexIgnoreCase(Strings.types, type) - 1; // Get HP Type + + HiddenPowerType = hpVal; + if (!Array.TrueForAll(IVs, z => z == 31)) + { + if (!HiddenPower.SetIVsForType(hpVal, IVs, Format)) + InvalidLines.Add($"Invalid IVs for Hidden Power Type: {type}"); + } + else if (hpVal >= 0) + { + HiddenPower.SetIVs(hpVal, IVs, Format); // Alter IVs + } + else + { + InvalidLines.Add($"Invalid Hidden Power Type: {type}"); + } + return Strings.Move[hiddenPower]; + } + + private void ParseLineEVs(string line) + { + var list = SplitLineStats(line); + if ((list.Length & 1) == 1) + InvalidLines.Add("Unknown EV input."); + for (int i = 0; i < list.Length / 2; i++) + { + int pos = i * 2; + int index = StringUtil.FindIndexIgnoreCase(StatNames, list[pos + 1]); + if (index >= 0 && ushort.TryParse(list[pos + 0], out var EV)) + EVs[index] = EV; + else + InvalidLines.Add($"Unknown EV stat: {list[pos]}"); + } + EVs = EVsSpeedFirst; + } + + private void ParseLineIVs(string line) + { + var list = SplitLineStats(line); + if ((list.Length & 1) == 1) + InvalidLines.Add("Unknown IV input."); + for (int i = 0; i < list.Length / 2; i++) + { + int pos = i * 2; + int index = StringUtil.FindIndexIgnoreCase(StatNames, list[pos + 1]); + if (index >= 0 && byte.TryParse(list[pos + 0], out var iv)) + IVs[index] = iv; + else + InvalidLines.Add($"Unknown IV stat: {list[pos]}"); + } + IVs = IVsSpeedFirst; + } + + private static string RemoveAll(string original, char[] remove) + { + Span result = stackalloc char[original.Length]; + int ctr = 0; + foreach (var c in original) + { + if (Array.IndexOf(remove, c) == -1) + result[ctr++] = c; + } + if (ctr == original.Length) + return original; + return new string(result[..ctr].ToArray()); + } + + private static string[] SplitLineStats(string line) + { + // Because people think they can type sets out... + return line + .Replace("SAtk", "SpA").Replace("Sp Atk", "SpA") + .Replace("SDef", "SpD").Replace("Sp Def", "SpD") + .Replace("Spd", "Spe").Replace("Speed", "Spe").Split(StatSplitters, StringSplitOptions.None); + } } diff --git a/PKHeX.Core/Editing/WurmpleUtil.cs b/PKHeX.Core/Editing/WurmpleUtil.cs index 34951f119..0b2f01da7 100644 --- a/PKHeX.Core/Editing/WurmpleUtil.cs +++ b/PKHeX.Core/Editing/WurmpleUtil.cs @@ -1,59 +1,58 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Indicates or coerces values pertaining to and its branched evolutions. +/// +public static class WurmpleUtil { /// - /// Indicates or coerces values pertaining to and its branched evolutions. + /// Gets the Wurmple Evolution Value for a given /// - public static class WurmpleUtil + /// Encryption Constant + /// Wurmple Evolution Value + public static uint GetWurmpleEvoVal(uint encryptionConstant) { - /// - /// Gets the Wurmple Evolution Value for a given - /// - /// Encryption Constant - /// Wurmple Evolution Value - public static uint GetWurmpleEvoVal(uint encryptionConstant) - { - var evoVal = encryptionConstant >> 16; - return evoVal % 10 / 5; - } + var evoVal = encryptionConstant >> 16; + return evoVal % 10 / 5; + } - /// - /// Gets the evo chain of Wurmple - /// - /// Current species - /// -1 if not a Wurmple Evo, 0 if Silcoon chain, 1 if Cascoon chain - 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 - } + /// + /// Gets the evo chain of Wurmple + /// + /// Current species + /// -1 if not a Wurmple Evo, 0 if Silcoon chain, 1 if Cascoon chain + 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 + } - /// - /// Gets the Wurmple for a given Evolution Value - /// - /// Wurmple Evolution Value - /// 0 = Silcoon, 1 = Cascoon - /// Encryption Constant - public static uint GetWurmpleEncryptionConstant(int evoVal) - { - uint result; - var rnd = Util.Rand; - do result = rnd.Rand32(); - while (evoVal != GetWurmpleEvoVal(result)); - return result; - } + /// + /// Gets the Wurmple for a given Evolution Value + /// + /// Wurmple Evolution Value + /// 0 = Silcoon, 1 = Cascoon + /// Encryption Constant + public static uint GetWurmpleEncryptionConstant(int evoVal) + { + uint result; + var rnd = Util.Rand; + do result = rnd.Rand32(); + while (evoVal != GetWurmpleEvoVal(result)); + return result; + } - /// - /// Checks to see if the input , with species being that of Wurmple's evo chain, is valid. - /// - /// Pokémon data - /// True if valid, false if invalid - public static bool IsWurmpleEvoValid(PKM pkm) - { - uint evoVal = GetWurmpleEvoVal(pkm.EncryptionConstant); - int wIndex = GetWurmpleEvoGroup(pkm.Species); - return evoVal == wIndex; - } + /// + /// Checks to see if the input , with species being that of Wurmple's evo chain, is valid. + /// + /// Pokémon data + /// True if valid, false if invalid + public static bool IsWurmpleEvoValid(PKM pk) + { + uint evoVal = GetWurmpleEvoVal(pk.EncryptionConstant); + int wIndex = GetWurmpleEvoGroup(pk.Species); + return evoVal == wIndex; } } diff --git a/PKHeX.Core/Game/Enums/Ability.cs b/PKHeX.Core/Game/Enums/Ability.cs index af0953199..15c15673b 100644 --- a/PKHeX.Core/Game/Enums/Ability.cs +++ b/PKHeX.Core/Game/Enums/Ability.cs @@ -1,278 +1,277 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Ability IDs for the corresponding English ability name. +/// +public enum Ability { - /// - /// Ability IDs for the corresponding English ability name. - /// - 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, } diff --git a/PKHeX.Core/Game/Enums/Ball.cs b/PKHeX.Core/Game/Enums/Ball.cs index 71565de87..4b49a5f09 100644 --- a/PKHeX.Core/Game/Enums/Ball.cs +++ b/PKHeX.Core/Game/Enums/Ball.cs @@ -1,65 +1,64 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Ball IDs for the corresponding English ball name. +/// +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 { /// - /// Ball IDs for the corresponding English ball name. + /// Checks if the is an Apricorn Ball (HG/SS) /// - 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 - { - /// - /// Checks if the is an Apricorn Ball (HG/SS) - /// - /// Ball ID - /// True if Apricorn, false if not. - public static bool IsApricornBall(this Ball ball) => ball is >= Ball.Fast and <= Ball.Moon; - } + /// Ball ID + /// True if Apricorn, false if not. + public static bool IsApricornBall(this Ball ball) => ball is >= Ball.Fast and <= Ball.Moon; } diff --git a/PKHeX.Core/Game/Enums/GCVersion.cs b/PKHeX.Core/Game/Enums/GCVersion.cs index 36d8abb12..49afc97df 100644 --- a/PKHeX.Core/Game/Enums/GCVersion.cs +++ b/PKHeX.Core/Game/Enums/GCVersion.cs @@ -1,51 +1,50 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// analogues used by Colosseum/XD instead of the main-series values. +/// +public enum GCVersion : byte +{ + None = 0, + FR = 1, + LG = 2, + S = 8, + R = 9, + E = 10, + CXD = 11, +} + +public static class GCVersionExtensions { /// - /// analogues used by Colosseum/XD instead of the main-series values. + /// Translates a main-series to the corresponding value. /// - public enum GCVersion : byte + /// Version ID while present in the main-series games + /// Version ID while present in the GameCube games + 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 + /// + /// Translates a to the corresponding main-series value. + /// + /// Version ID while present in the GameCube games + /// Version ID while present in the main-series games + public static GameVersion GetG3VersionID(this GCVersion gcVersion) => gcVersion switch { - /// - /// Translates a main-series to the corresponding value. - /// - /// Version ID while present in the main-series games - /// Version ID while present in the GameCube games - 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, - }; - - /// - /// Translates a to the corresponding main-series value. - /// - /// Version ID while present in the GameCube games - /// Version ID while present in the main-series games - 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, + }; } diff --git a/PKHeX.Core/Game/Enums/GameVersion.cs b/PKHeX.Core/Game/Enums/GameVersion.cs index a5c54d1ca..54eb992f4 100644 --- a/PKHeX.Core/Game/Enums/GameVersion.cs +++ b/PKHeX.Core/Game/Enums/GameVersion.cs @@ -1,485 +1,484 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Game Version ID enum shared between actual Version IDs and lumped version groupings. +/// +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 /// - /// Game Version ID enum shared between actual Version IDs and lumped version groupings. + /// Pokémon Sapphire (GBA) /// - 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 - /// - /// Pokémon Sapphire (GBA) - /// - S = 1, - - /// - /// Pokémon Ruby (GBA) - /// - R = 2, - - /// - /// Pokémon Emerald (GBA) - /// - E = 3, - - /// - /// Pokémon FireRed (GBA) - /// - FR = 4, - - /// - /// Pokémon LeafGreen (GBA) - /// - LG = 5, - - /// - /// Pokémon Colosseum & Pokémon XD (GameCube) - /// - CXD = 15, - #endregion - - #region Gen4 - /// - /// Pokémon Diamond (NDS) - /// - D = 10, - - /// - /// Pokémon Pearl (NDS) - /// - P = 11, - - /// - /// Pokémon Platinum (NDS) - /// - Pt = 12, - - /// - /// Pokémon HeartGold (NDS) - /// - HG = 7, - - /// - /// Pokémon SoulSilver (NDS) - /// - SS = 8, - #endregion - - #region Gen5 - /// - /// Pokémon White (NDS) - /// - W = 20, - - /// - /// Pokémon Black (NDS) - /// - B = 21, - - /// - /// Pokémon White 2 (NDS) - /// - W2 = 22, - - /// - /// Pokémon Black 2 (NDS) - /// - B2 = 23, - #endregion - - #region Gen6 - /// - /// Pokémon X (3DS) - /// - X = 24, - - /// - /// Pokémon Y (3DS) - /// - Y = 25, - - /// - /// Pokémon Alpha Sapphire (3DS) - /// - AS = 26, - - /// - /// Pokémon Omega Ruby (3DS) - /// - OR = 27, - #endregion - - #region Gen7 - /// - /// Pokémon Sun (3DS) - /// - SN = 30, - - /// - /// Pokémon Moon (3DS) - /// - MN = 31, - - /// - /// Pokémon Ultra Sun (3DS) - /// - US = 32, - - /// - /// Pokémon Ultra Moon (3DS) - /// - UM = 33, - #endregion - - /// - /// Pokémon GO (GO -> Let's Go/HOME transfers) - /// - GO = 34, - - #region Virtual Console (3DS) Gen1 - /// - /// Pokémon Red (3DS Virtual Console) - /// - RD = 35, - - /// - /// Pokémon Green[JP]/Blue[INT] (3DS Virtual Console) - /// - GN = 36, - - /// - /// Pokémon Blue[JP] (3DS Virtual Console) - /// - BU = 37, - - /// - /// Pokémon Yellow [JP] (3DS Virtual Console) - /// - YW = 38, - #endregion - - #region Virtual Console (3DS) Gen2 - /// - /// Pokémon Gold (3DS Virtual Console) - /// - GD = 39, - - /// - /// Pokémon Silver (3DS Virtual Console) - /// - SI = 40, - - /// - /// Pokémon Crystal (3DS Virtual Console) - /// - C = 41, - #endregion - - #region Nintendo Switch - /// - /// Pokémon: Let's Go, Pikachu! (NX) - /// - GP = 42, - - /// - /// Pokémon: Let's Go, Eevee! (NX) - /// - GE = 43, - - /// - /// Pokémon Sword (NX) - /// - SW = 44, - - /// - /// Pokémon Shield (NX) - /// - SH = 45, - - // HOME = 46, - - /// - /// Pokémon Legends: Arceus (NX) - /// - PLA = 47, - - /// - /// Pokémon Brilliant Diamond (NX) - /// - BD = 48, - - /// - /// Pokémon Shining Pearl (NX) - /// - 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) - /// - /// Pokémon Red & Blue [] identifier. - /// - /// - /// - /// - RB, - - /// - /// Pokémon Red/Blue/Yellow [] identifier. - /// - /// - /// - /// - /// - RBY, - - /// - /// Pokémon Gold & Silver [] identifier. - /// - /// - /// - GS, - - /// - /// Pokémon Gold/Silver/Crystal [] identifier. - /// - /// - /// - /// - GSC, - - /// - /// Pokémon Ruby & Sapphire [] identifier. - /// - /// - /// - RS, - - /// - /// Pokémon Ruby/Sapphire/Emerald [] identifier. - /// - /// - /// - /// - RSE, - - /// - /// Pokémon FireRed/LeafGreen [] identifier. - /// - /// - /// - FRLG, - - /// - /// Pokémon Box Ruby & Sapphire [] identifier. - /// - RSBOX, - - /// - /// Pokémon Colosseum [] identifier. - /// - /// - /// Also used to mark Colosseum-only origin data as this game shares a version ID with - COLO, - - /// - /// Pokémon XD [] identifier. - /// - /// - /// Also used to mark XD-only origin data as this game shares a version ID with - XD, - - /// - /// Pokémon Diamond & Pearl [] identifier. - /// - /// - /// - DP, - - /// - /// Pokémon Diamond/Pearl/Platinum version group. - /// - /// Used to lump data from the associated games as data assets are shared. - /// - /// - /// - DPPt, - - /// - /// Pokémon HeartGold & SoulSilver [] identifier. - /// - /// - /// - HGSS, - - /// - /// Pokémon Battle Revolution [] identifier. - /// - BATREV, - - /// - /// Pokémon Black & White version group. - /// - /// Used to lump data from the associated games as data assets are shared. - /// - /// - BW, - - /// - /// Pokémon Black 2 & White 2 version group. - /// - /// Used to lump data from the associated games as data assets are shared. - /// - /// - B2W2, - - /// - /// Pokémon X & Y - /// - /// Used to lump data from the associated games as data assets are shared. - /// - /// - XY, - - /// - /// Pokémon Omega Ruby & Alpha Sapphire Demo [] identifier. - /// - /// - ORASDEMO, - - /// - /// Pokémon Omega Ruby & Alpha Sapphire version group. - /// - /// Used to lump data from the associated games as data assets are shared. - /// - /// - ORAS, - - /// - /// Pokémon Sun & Moon - /// - /// Used to lump data from the associated games as data assets are shared. - /// - /// - SM, - - /// - /// Pokémon Ultra Sun & Ultra Moon - /// - /// Used to lump data from the associated games as data assets are shared. - /// - /// - USUM, - - /// - /// Pokémon Let's Go Pikachu & Eevee - /// - /// Used to lump data from the associated games as data assets are shared. - /// - /// - GG, - - /// - /// Pokémon Sword & Shield - /// - /// Used to lump data from the associated games as data assets are shared. - /// - /// - SWSH, - - /// - /// Pokémon Brilliant Diamond & Shining Pearl - /// - /// Used to lump data from the associated games as data assets are shared. - /// - /// - BDSP, - - /// - /// Generation 1 Games - /// - /// - Gen1, - - /// - /// Generation 2 Games - /// - /// - Gen2, - - /// - /// Generation 3 Games - /// - /// - /// - Gen3, - - /// - /// Generation 4 Games - /// - /// - /// - Gen4, - - /// - /// Generation 5 Games - /// - /// - /// - Gen5, - - /// - /// Generation 6 Games - /// - /// - /// - Gen6, - - /// - /// Generation 7 Games on the Nintendo 3DS - /// - /// - /// - Gen7, - - /// - /// Generation 7 Games on the Nintendo Switch - /// - /// - /// - Gen7b, - - /// - /// Generation 8 Games - /// - /// - /// - /// - Gen8, - - /// - /// Pocket Monsters Stadium data origin identifier - /// - StadiumJ, - - /// - /// Pokémon Stadium data origin identifier - /// - Stadium, - - /// - /// Pokémon Stadium 2 data origin identifier - /// - Stadium2, - #endregion - } + S = 1, + + /// + /// Pokémon Ruby (GBA) + /// + R = 2, + + /// + /// Pokémon Emerald (GBA) + /// + E = 3, + + /// + /// Pokémon FireRed (GBA) + /// + FR = 4, + + /// + /// Pokémon LeafGreen (GBA) + /// + LG = 5, + + /// + /// Pokémon Colosseum & Pokémon XD (GameCube) + /// + CXD = 15, + #endregion + + #region Gen4 + /// + /// Pokémon Diamond (NDS) + /// + D = 10, + + /// + /// Pokémon Pearl (NDS) + /// + P = 11, + + /// + /// Pokémon Platinum (NDS) + /// + Pt = 12, + + /// + /// Pokémon HeartGold (NDS) + /// + HG = 7, + + /// + /// Pokémon SoulSilver (NDS) + /// + SS = 8, + #endregion + + #region Gen5 + /// + /// Pokémon White (NDS) + /// + W = 20, + + /// + /// Pokémon Black (NDS) + /// + B = 21, + + /// + /// Pokémon White 2 (NDS) + /// + W2 = 22, + + /// + /// Pokémon Black 2 (NDS) + /// + B2 = 23, + #endregion + + #region Gen6 + /// + /// Pokémon X (3DS) + /// + X = 24, + + /// + /// Pokémon Y (3DS) + /// + Y = 25, + + /// + /// Pokémon Alpha Sapphire (3DS) + /// + AS = 26, + + /// + /// Pokémon Omega Ruby (3DS) + /// + OR = 27, + #endregion + + #region Gen7 + /// + /// Pokémon Sun (3DS) + /// + SN = 30, + + /// + /// Pokémon Moon (3DS) + /// + MN = 31, + + /// + /// Pokémon Ultra Sun (3DS) + /// + US = 32, + + /// + /// Pokémon Ultra Moon (3DS) + /// + UM = 33, + #endregion + + /// + /// Pokémon GO (GO -> Let's Go/HOME transfers) + /// + GO = 34, + + #region Virtual Console (3DS) Gen1 + /// + /// Pokémon Red (3DS Virtual Console) + /// + RD = 35, + + /// + /// Pokémon Green[JP]/Blue[INT] (3DS Virtual Console) + /// + GN = 36, + + /// + /// Pokémon Blue[JP] (3DS Virtual Console) + /// + BU = 37, + + /// + /// Pokémon Yellow [JP] (3DS Virtual Console) + /// + YW = 38, + #endregion + + #region Virtual Console (3DS) Gen2 + /// + /// Pokémon Gold (3DS Virtual Console) + /// + GD = 39, + + /// + /// Pokémon Silver (3DS Virtual Console) + /// + SI = 40, + + /// + /// Pokémon Crystal (3DS Virtual Console) + /// + C = 41, + #endregion + + #region Nintendo Switch + /// + /// Pokémon: Let's Go, Pikachu! (NX) + /// + GP = 42, + + /// + /// Pokémon: Let's Go, Eevee! (NX) + /// + GE = 43, + + /// + /// Pokémon Sword (NX) + /// + SW = 44, + + /// + /// Pokémon Shield (NX) + /// + SH = 45, + + // HOME = 46, + + /// + /// Pokémon Legends: Arceus (NX) + /// + PLA = 47, + + /// + /// Pokémon Brilliant Diamond (NX) + /// + BD = 48, + + /// + /// Pokémon Shining Pearl (NX) + /// + 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) + /// + /// Pokémon Red & Blue [] identifier. + /// + /// + /// + /// + RB, + + /// + /// Pokémon Red/Blue/Yellow [] identifier. + /// + /// + /// + /// + /// + RBY, + + /// + /// Pokémon Gold & Silver [] identifier. + /// + /// + /// + GS, + + /// + /// Pokémon Gold/Silver/Crystal [] identifier. + /// + /// + /// + /// + GSC, + + /// + /// Pokémon Ruby & Sapphire [] identifier. + /// + /// + /// + RS, + + /// + /// Pokémon Ruby/Sapphire/Emerald [] identifier. + /// + /// + /// + /// + RSE, + + /// + /// Pokémon FireRed/LeafGreen [] identifier. + /// + /// + /// + FRLG, + + /// + /// Pokémon Box Ruby & Sapphire [] identifier. + /// + RSBOX, + + /// + /// Pokémon Colosseum [] identifier. + /// + /// + /// Also used to mark Colosseum-only origin data as this game shares a version ID with + COLO, + + /// + /// Pokémon XD [] identifier. + /// + /// + /// Also used to mark XD-only origin data as this game shares a version ID with + XD, + + /// + /// Pokémon Diamond & Pearl [] identifier. + /// + /// + /// + DP, + + /// + /// Pokémon Diamond/Pearl/Platinum version group. + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + /// + DPPt, + + /// + /// Pokémon HeartGold & SoulSilver [] identifier. + /// + /// + /// + HGSS, + + /// + /// Pokémon Battle Revolution [] identifier. + /// + BATREV, + + /// + /// Pokémon Black & White version group. + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + BW, + + /// + /// Pokémon Black 2 & White 2 version group. + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + B2W2, + + /// + /// Pokémon X & Y + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + XY, + + /// + /// Pokémon Omega Ruby & Alpha Sapphire Demo [] identifier. + /// + /// + ORASDEMO, + + /// + /// Pokémon Omega Ruby & Alpha Sapphire version group. + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + ORAS, + + /// + /// Pokémon Sun & Moon + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + SM, + + /// + /// Pokémon Ultra Sun & Ultra Moon + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + USUM, + + /// + /// Pokémon Let's Go Pikachu & Eevee + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + GG, + + /// + /// Pokémon Sword & Shield + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + SWSH, + + /// + /// Pokémon Brilliant Diamond & Shining Pearl + /// + /// Used to lump data from the associated games as data assets are shared. + /// + /// + BDSP, + + /// + /// Generation 1 Games + /// + /// + Gen1, + + /// + /// Generation 2 Games + /// + /// + Gen2, + + /// + /// Generation 3 Games + /// + /// + /// + Gen3, + + /// + /// Generation 4 Games + /// + /// + /// + Gen4, + + /// + /// Generation 5 Games + /// + /// + /// + Gen5, + + /// + /// Generation 6 Games + /// + /// + /// + Gen6, + + /// + /// Generation 7 Games on the Nintendo 3DS + /// + /// + /// + Gen7, + + /// + /// Generation 7 Games on the Nintendo Switch + /// + /// + /// + Gen7b, + + /// + /// Generation 8 Games + /// + /// + /// + /// + Gen8, + + /// + /// Pocket Monsters Stadium data origin identifier + /// + StadiumJ, + + /// + /// Pokémon Stadium data origin identifier + /// + Stadium, + + /// + /// Pokémon Stadium 2 data origin identifier + /// + Stadium2, + #endregion } diff --git a/PKHeX.Core/Game/Enums/Gender.cs b/PKHeX.Core/Game/Enums/Gender.cs index ff3e7c1c2..66d88cdce 100644 --- a/PKHeX.Core/Game/Enums/Gender.cs +++ b/PKHeX.Core/Game/Enums/Gender.cs @@ -1,15 +1,14 @@ -namespace PKHeX.Core -{ - /// - /// Gender a can have - /// - /// provided to function for Encounter template values - public enum Gender : byte - { - Male = 0, - Female = 1, +namespace PKHeX.Core; - Genderless = 2, - Random = Genderless, - } +/// +/// Gender a can have +/// +/// provided to function for Encounter template values +public enum Gender : byte +{ + Male = 0, + Female = 1, + + Genderless = 2, + Random = Genderless, } diff --git a/PKHeX.Core/Game/Enums/LanguageGC.cs b/PKHeX.Core/Game/Enums/LanguageGC.cs index 20adbf836..68ade1067 100644 --- a/PKHeX.Core/Game/Enums/LanguageGC.cs +++ b/PKHeX.Core/Game/Enums/LanguageGC.cs @@ -1,50 +1,49 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Game Language IDs +/// +public enum LanguageGC : byte { /// - /// Game Language IDs + /// Undefined Language ID, usually indicative of a value not being set. /// - public enum LanguageGC : byte - { - /// - /// Undefined Language ID, usually indicative of a value not being set. - /// - /// Gen5 Japanese In-game Trades happen to not have their Language value set, and express Language=0. - Hacked = 0, + /// Gen5 Japanese In-game Trades happen to not have their Language value set, and express Language=0. + Hacked = 0, - /// - /// Japanese (日本語) - /// - Japanese = 1, + /// + /// Japanese (日本語) + /// + Japanese = 1, - /// - /// English (US/UK/AU) - /// - English = 2, + /// + /// English (US/UK/AU) + /// + English = 2, - /// - /// German (Deutsch) - /// - German = 3, + /// + /// German (Deutsch) + /// + German = 3, - /// - /// French (Français) - /// - French = 4, + /// + /// French (Français) + /// + French = 4, - /// - /// Italian (Italiano) - /// - Italian = 5, + /// + /// Italian (Italiano) + /// + Italian = 5, - /// - /// Spanish (Español) - /// - Spanish = 6, + /// + /// Spanish (Español) + /// + Spanish = 6, - /// - /// Unused Language ID - /// - /// Was reserved for Korean in Gen3 but never utilized. - UNUSED_6 = 7, - } -} \ No newline at end of file + /// + /// Unused Language ID + /// + /// Was reserved for Korean in Gen3 but never utilized. + UNUSED_6 = 7, +} diff --git a/PKHeX.Core/Game/Enums/LanguageID.cs b/PKHeX.Core/Game/Enums/LanguageID.cs index de44757fa..741b7247b 100644 --- a/PKHeX.Core/Game/Enums/LanguageID.cs +++ b/PKHeX.Core/Game/Enums/LanguageID.cs @@ -1,65 +1,64 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contiguous series Game Language IDs +/// +public enum LanguageID : byte { /// - /// Contiguous series Game Language IDs + /// Undefined Language ID, usually indicative of a value not being set. /// - public enum LanguageID : byte - { - /// - /// Undefined Language ID, usually indicative of a value not being set. - /// - /// Gen5 Japanese In-game Trades happen to not have their Language value set, and express Language=0. - Hacked = 0, + /// Gen5 Japanese In-game Trades happen to not have their Language value set, and express Language=0. + Hacked = 0, - /// - /// Japanese (日本語) - /// - Japanese = 1, + /// + /// Japanese (日本語) + /// + Japanese = 1, - /// - /// English (US/UK/AU) - /// - English = 2, + /// + /// English (US/UK/AU) + /// + English = 2, - /// - /// French (Français) - /// - French = 3, + /// + /// French (Français) + /// + French = 3, - /// - /// Italian (Italiano) - /// - Italian = 4, + /// + /// Italian (Italiano) + /// + Italian = 4, - /// - /// German (Deutsch) - /// - German = 5, + /// + /// German (Deutsch) + /// + German = 5, - /// - /// Unused Language ID - /// - /// Was reserved for Korean in Gen3 but never utilized. - UNUSED_6 = 6, + /// + /// Unused Language ID + /// + /// Was reserved for Korean in Gen3 but never utilized. + UNUSED_6 = 6, - /// - /// Spanish (Español) - /// - Spanish = 7, + /// + /// Spanish (Español) + /// + Spanish = 7, - /// - /// Korean (한국어) - /// - Korean = 8, + /// + /// Korean (한국어) + /// + Korean = 8, - /// - /// Chinese Simplified (简体中文) - /// - ChineseS = 9, + /// + /// Chinese Simplified (简体中文) + /// + ChineseS = 9, - /// - /// Chinese Traditional (繁體中文) - /// - ChineseT = 10, - } + /// + /// Chinese Traditional (繁體中文) + /// + ChineseT = 10, } diff --git a/PKHeX.Core/Game/Enums/Move.cs b/PKHeX.Core/Game/Enums/Move.cs index 3a1f52363..8ff74850f 100644 --- a/PKHeX.Core/Game/Enums/Move.cs +++ b/PKHeX.Core/Game/Enums/Move.cs @@ -1,861 +1,860 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Move IDs for the corresponding English move name. +/// +public enum Move { - /// - /// Move IDs for the corresponding English move name. - /// - public enum Move - { - None, - Pound, - KarateChop, - DoubleSlap, - CometPunch, - MegaPunch, - PayDay, - FirePunch, - IcePunch, - ThunderPunch, - Scratch, - ViseGrip, - Guillotine, - RazorWind, - SwordsDance, - Cut, - Gust, - WingAttack, - Whirlwind, - Fly, - Bind, - Slam, - VineWhip, - Stomp, - DoubleKick, - MegaKick, - JumpKick, - RollingKick, - SandAttack, - Headbutt, - HornAttack, - FuryAttack, - HornDrill, - Tackle, - BodySlam, - Wrap, - TakeDown, - Thrash, - DoubleEdge, - TailWhip, - PoisonSting, - Twineedle, - PinMissile, - Leer, - Bite, - Growl, - Roar, - Sing, - Supersonic, - SonicBoom, - Disable, - Acid, - Ember, - Flamethrower, - Mist, - WaterGun, - HydroPump, - Surf, - IceBeam, - Blizzard, - Psybeam, - BubbleBeam, - AuroraBeam, - HyperBeam, - Peck, - DrillPeck, - Submission, - LowKick, - Counter, - SeismicToss, - Strength, - Absorb, - MegaDrain, - LeechSeed, - Growth, - RazorLeaf, - SolarBeam, - PoisonPowder, - StunSpore, - SleepPowder, - PetalDance, - StringShot, - DragonRage, - FireSpin, - ThunderShock, - Thunderbolt, - ThunderWave, - Thunder, - RockThrow, - Earthquake, - Fissure, - Dig, - Toxic, - Confusion, - Psychic, - Hypnosis, - Meditate, - Agility, - QuickAttack, - Rage, - Teleport, - NightShade, - Mimic, - Screech, - DoubleTeam, - Recover, - Harden, - Minimize, - Smokescreen, - ConfuseRay, - Withdraw, - DefenseCurl, - Barrier, - LightScreen, - Haze, - Reflect, - FocusEnergy, - Bide, - Metronome, - MirrorMove, - SelfDestruct, - EggBomb, - Lick, - Smog, - Sludge, - BoneClub, - FireBlast, - Waterfall, - Clamp, - Swift, - SkullBash, - SpikeCannon, - Constrict, - Amnesia, - Kinesis, - SoftBoiled, - HighJumpKick, - Glare, - DreamEater, - PoisonGas, - Barrage, - LeechLife, - LovelyKiss, - SkyAttack, - Transform, - Bubble, - DizzyPunch, - Spore, - Flash, - Psywave, - Splash, - AcidArmor, - Crabhammer, - Explosion, - FurySwipes, - Bonemerang, - Rest, - RockSlide, - HyperFang, - Sharpen, - Conversion, - TriAttack, - SuperFang, - Slash, - Substitute, - Struggle, - Sketch, - TripleKick, - Thief, - SpiderWeb, - MindReader, - Nightmare, - FlameWheel, - Snore, - Curse, - Flail, - Conversion2, - Aeroblast, - CottonSpore, - Reversal, - Spite, - PowderSnow, - Protect, - MachPunch, - ScaryFace, - FeintAttack, - SweetKiss, - BellyDrum, - SludgeBomb, - MudSlap, - Octazooka, - Spikes, - ZapCannon, - Foresight, - DestinyBond, - PerishSong, - IcyWind, - Detect, - BoneRush, - LockOn, - Outrage, - Sandstorm, - GigaDrain, - Endure, - Charm, - Rollout, - FalseSwipe, - Swagger, - MilkDrink, - Spark, - FuryCutter, - SteelWing, - MeanLook, - Attract, - SleepTalk, - HealBell, - Return, - Present, - Frustration, - Safeguard, - PainSplit, - SacredFire, - Magnitude, - DynamicPunch, - Megahorn, - DragonBreath, - BatonPass, - Encore, - Pursuit, - RapidSpin, - SweetScent, - IronTail, - MetalClaw, - VitalThrow, - MorningSun, - Synthesis, - Moonlight, - HiddenPower, - CrossChop, - Twister, - RainDance, - SunnyDay, - Crunch, - MirrorCoat, - PsychUp, - ExtremeSpeed, - AncientPower, - ShadowBall, - FutureSight, - RockSmash, - Whirlpool, - BeatUp, - FakeOut, - Uproar, - Stockpile, - SpitUp, - Swallow, - HeatWave, - Hail, - Torment, - Flatter, - WillOWisp, - Memento, - Facade, - FocusPunch, - SmellingSalts, - FollowMe, - NaturePower, - Charge, - Taunt, - HelpingHand, - Trick, - RolePlay, - Wish, - Assist, - Ingrain, - Superpower, - MagicCoat, - Recycle, - Revenge, - BrickBreak, - Yawn, - KnockOff, - Endeavor, - Eruption, - SkillSwap, - Imprison, - Refresh, - Grudge, - Snatch, - SecretPower, - Dive, - ArmThrust, - Camouflage, - TailGlow, - LusterPurge, - MistBall, - FeatherDance, - TeeterDance, - BlazeKick, - MudSport, - IceBall, - NeedleArm, - SlackOff, - HyperVoice, - PoisonFang, - CrushClaw, - BlastBurn, - HydroCannon, - MeteorMash, - Astonish, - WeatherBall, - Aromatherapy, - FakeTears, - AirCutter, - Overheat, - OdorSleuth, - RockTomb, - SilverWind, - MetalSound, - GrassWhistle, - Tickle, - CosmicPower, - WaterSpout, - SignalBeam, - ShadowPunch, - Extrasensory, - SkyUppercut, - SandTomb, - SheerCold, - MuddyWater, - BulletSeed, - AerialAce, - IcicleSpear, - IronDefense, - Block, - Howl, - DragonClaw, - FrenzyPlant, - BulkUp, - Bounce, - MudShot, - PoisonTail, - Covet, - VoltTackle, - MagicalLeaf, - WaterSport, - CalmMind, - LeafBlade, - DragonDance, - RockBlast, - ShockWave, - WaterPulse, - DoomDesire, - PsychoBoost, - Roost, - Gravity, - MiracleEye, - WakeUpSlap, - HammerArm, - GyroBall, - HealingWish, - Brine, - NaturalGift, - Feint, - Pluck, - Tailwind, - Acupressure, - MetalBurst, - Uturn, - CloseCombat, - Payback, - Assurance, - Embargo, - Fling, - PsychoShift, - TrumpCard, - HealBlock, - WringOut, - PowerTrick, - GastroAcid, - LuckyChant, - MeFirst, - Copycat, - PowerSwap, - GuardSwap, - Punishment, - LastResort, - WorrySeed, - SuckerPunch, - ToxicSpikes, - HeartSwap, - AquaRing, - MagnetRise, - FlareBlitz, - ForcePalm, - AuraSphere, - RockPolish, - PoisonJab, - DarkPulse, - NightSlash, - AquaTail, - SeedBomb, - AirSlash, - XScissor, - BugBuzz, - DragonPulse, - DragonRush, - PowerGem, - DrainPunch, - VacuumWave, - FocusBlast, - EnergyBall, - BraveBird, - EarthPower, - Switcheroo, - GigaImpact, - NastyPlot, - BulletPunch, - Avalanche, - IceShard, - ShadowClaw, - ThunderFang, - IceFang, - FireFang, - ShadowSneak, - MudBomb, - PsychoCut, - ZenHeadbutt, - MirrorShot, - FlashCannon, - RockClimb, - Defog, - TrickRoom, - DracoMeteor, - Discharge, - LavaPlume, - LeafStorm, - PowerWhip, - RockWrecker, - CrossPoison, - GunkShot, - IronHead, - MagnetBomb, - StoneEdge, - Captivate, - StealthRock, - GrassKnot, - Chatter, - Judgment, - BugBite, - ChargeBeam, - WoodHammer, - AquaJet, - AttackOrder, - DefendOrder, - HealOrder, - HeadSmash, - DoubleHit, - RoarofTime, - SpacialRend, - LunarDance, - CrushGrip, - MagmaStorm, - DarkVoid, - SeedFlare, - OminousWind, - ShadowForce, - HoneClaws, - WideGuard, - GuardSplit, - PowerSplit, - WonderRoom, - Psyshock, - Venoshock, - Autotomize, - RagePowder, - Telekinesis, - MagicRoom, - SmackDown, - StormThrow, - FlameBurst, - SludgeWave, - QuiverDance, - HeavySlam, - Synchronoise, - ElectroBall, - Soak, - FlameCharge, - Coil, - LowSweep, - AcidSpray, - FoulPlay, - SimpleBeam, - Entrainment, - AfterYou, - Round, - EchoedVoice, - ChipAway, - ClearSmog, - StoredPower, - QuickGuard, - AllySwitch, - Scald, - ShellSmash, - HealPulse, - Hex, - SkyDrop, - ShiftGear, - CircleThrow, - Incinerate, - Quash, - Acrobatics, - ReflectType, - Retaliate, - FinalGambit, - Bestow, - Inferno, - WaterPledge, - FirePledge, - GrassPledge, - VoltSwitch, - StruggleBug, - Bulldoze, - FrostBreath, - DragonTail, - WorkUp, - Electroweb, - WildCharge, - DrillRun, - DualChop, - HeartStamp, - HornLeech, - SacredSword, - RazorShell, - HeatCrash, - LeafTornado, - Steamroller, - CottonGuard, - NightDaze, - Psystrike, - TailSlap, - Hurricane, - HeadCharge, - GearGrind, - SearingShot, - TechnoBlast, - RelicSong, - SecretSword, - Glaciate, - BoltStrike, - BlueFlare, - FieryDance, - FreezeShock, - IceBurn, - Snarl, - IcicleCrash, - Vcreate, - FusionFlare, - FusionBolt, - FlyingPress, - MatBlock, - Belch, - Rototiller, - StickyWeb, - FellStinger, - PhantomForce, - TrickorTreat, - NobleRoar, - IonDeluge, - ParabolicCharge, - ForestsCurse, - PetalBlizzard, - FreezeDry, - DisarmingVoice, - PartingShot, - TopsyTurvy, - DrainingKiss, - CraftyShield, - FlowerShield, - GrassyTerrain, - MistyTerrain, - Electrify, - PlayRough, - FairyWind, - Moonblast, - Boomburst, - FairyLock, - KingsShield, - PlayNice, - Confide, - DiamondStorm, - SteamEruption, - HyperspaceHole, - WaterShuriken, - MysticalFire, - SpikyShield, - AromaticMist, - EerieImpulse, - VenomDrench, - Powder, - Geomancy, - MagneticFlux, - HappyHour, - ElectricTerrain, - DazzlingGleam, - Celebrate, - HoldHands, - BabyDollEyes, - Nuzzle, - HoldBack, - Infestation, - PowerUpPunch, - OblivionWing, - ThousandArrows, - ThousandWaves, - LandsWrath, - LightofRuin, - OriginPulse, - PrecipiceBlades, - DragonAscent, - HyperspaceFury, - BreakneckBlitzP, - BreakneckBlitzS, - AllOutPummelingP, - AllOutPummelingS, - SupersonicSkystrikeP, - SupersonicSkystrikeS, - AcidDownpourP, - AcidDownpourS, - TectonicRageP, - TectonicRageS, - ContinentalCrushP, - ContinentalCrushS, - SavageSpinOutP, - SavageSpinOutS, - NeverEndingNightmareP, - NeverEndingNightmareS, - CorkscrewCrashP, - CorkscrewCrashS, - InfernoOverdriveP, - InfernoOverdriveS, - HydroVortexP, - HydroVortexS, - BloomDoomP, - BloomDoomS, - GigavoltHavocP, - GigavoltHavocS, - ShatteredPsycheP, - ShatteredPsycheS, - SubzeroSlammerP, - SubzeroSlammerS, - DevastatingDrakeP, - DevastatingDrakeS, - BlackHoleEclipseP, - BlackHoleEclipseS, - TwinkleTackleP, - TwinkleTackleS, - Catastropika, - ShoreUp, - FirstImpression, - BanefulBunker, - SpiritShackle, - DarkestLariat, - SparklingAria, - IceHammer, - FloralHealing, - HighHorsepower, - StrengthSap, - SolarBlade, - Leafage, - Spotlight, - ToxicThread, - LaserFocus, - GearUp, - ThroatChop, - PollenPuff, - AnchorShot, - PsychicTerrain, - Lunge, - FireLash, - PowerTrip, - BurnUp, - SpeedSwap, - SmartStrike, - Purify, - RevelationDance, - CoreEnforcer, - TropKick, - Instruct, - BeakBlast, - ClangingScales, - DragonHammer, - BrutalSwing, - AuroraVeil, - SinisterArrowRaid, - MaliciousMoonsault, - OceanicOperetta, - GuardianofAlola, - SoulStealing7StarStrike, - StokedSparksurfer, - PulverizingPancake, - ExtremeEvoboost, - GenesisSupernova, - ShellTrap, - FleurCannon, - PsychicFangs, - StompingTantrum, - ShadowBone, - Accelerock, - Liquidation, - PrismaticLaser, - SpectralThief, - SunsteelStrike, - MoongeistBeam, - TearfulLook, - ZingZap, - NaturesMadness, - MultiAttack, - TenMVoltThunderbolt, - MindBlown, - PlasmaFists, - PhotonGeyser, - LightThatBurnstheSky, - SearingSunrazeSmash, - MenacingMoonrazeMaelstrom, - LetsSnuggleForever, - SplinteredStormshards, - ClangorousSoulblaze, - ZippyZap, - SplishySplash, - FloatyFall, - PikaPapow, - BouncyBubble, - BuzzyBuzz, - SizzlySlide, - GlitzyGlow, - BaddyBad, - SappySeed, - FreezyFrost, - SparklySwirl, - VeeveeVolley, - DoubleIronBash, - MaxGuard, - DynamaxCannon, - SnipeShot, - JawLock, - StuffCheeks, - NoRetreat, - TarShot, - MagicPowder, - DragonDarts, - Teatime, - Octolock, - BoltBeak, - FishiousRend, - CourtChange, - MaxFlare, - MaxFlutterby, - MaxLightning, - MaxStrike, - MaxKnuckle, - MaxPhantasm, - MaxHailstorm, - MaxOoze, - MaxGeyser, - MaxAirstream, - MaxStarfall, - MaxWyrmwind, - MaxMindstorm, - MaxRockfall, - MaxQuake, - MaxDarkness, - MaxOvergrowth, - MaxSteelspike, - ClangorousSoul, - BodyPress, - Decorate, - DrumBeating, - SnapTrap, - PyroBall, - BehemothBlade, - BehemothBash, - AuraWheel, - BreakingSwipe, - BranchPoke, - Overdrive, - AppleAcid, - GravApple, - SpiritBreak, - StrangeSteam, - LifeDew, - Obstruct, - FalseSurrender, - MeteorAssault, - Eternabeam, - SteelBeam, - ExpandingForce, - SteelRoller, - ScaleShot, - MeteorBeam, - ShellSideArm, - MistyExplosion, - GrassyGlide, - RisingVoltage, - TerrainPulse, - SkitterSmack, - BurningJealousy, - LashOut, - Poltergeist, - CorrosiveGas, - Coaching, - FlipTurn, - TripleAxel, - DualWingbeat, - ScorchingSands, - JungleHealing, - WickedBlow, - SurgingStrikes, - ThunderCage, - DragonEnergy, - FreezingGlare, - FieryWrath, - ThunderousKick, - GlacialLance, - AstralBarrage, - EerieSpell, - DireClaw, - PsyshieldBash, - PowerShift, - StoneAxe, - SpringtideStorm, - MysticalPower, - RagingFury, - WaveCrash, - Chloroblast, - MountainGale, - VictoryDance, - HeadlongRush, - BarbBarrage, - EsperWing, - BitterMalice, - Shelter, - TripleArrows, - InfernalParade, - CeaselessEdge, - BleakwindStorm, - WildboltStorm, - SandsearStorm, - LunarBlessing, - TakeHeart, - MAX_COUNT, - } + None, + Pound, + KarateChop, + DoubleSlap, + CometPunch, + MegaPunch, + PayDay, + FirePunch, + IcePunch, + ThunderPunch, + Scratch, + ViseGrip, + Guillotine, + RazorWind, + SwordsDance, + Cut, + Gust, + WingAttack, + Whirlwind, + Fly, + Bind, + Slam, + VineWhip, + Stomp, + DoubleKick, + MegaKick, + JumpKick, + RollingKick, + SandAttack, + Headbutt, + HornAttack, + FuryAttack, + HornDrill, + Tackle, + BodySlam, + Wrap, + TakeDown, + Thrash, + DoubleEdge, + TailWhip, + PoisonSting, + Twineedle, + PinMissile, + Leer, + Bite, + Growl, + Roar, + Sing, + Supersonic, + SonicBoom, + Disable, + Acid, + Ember, + Flamethrower, + Mist, + WaterGun, + HydroPump, + Surf, + IceBeam, + Blizzard, + Psybeam, + BubbleBeam, + AuroraBeam, + HyperBeam, + Peck, + DrillPeck, + Submission, + LowKick, + Counter, + SeismicToss, + Strength, + Absorb, + MegaDrain, + LeechSeed, + Growth, + RazorLeaf, + SolarBeam, + PoisonPowder, + StunSpore, + SleepPowder, + PetalDance, + StringShot, + DragonRage, + FireSpin, + ThunderShock, + Thunderbolt, + ThunderWave, + Thunder, + RockThrow, + Earthquake, + Fissure, + Dig, + Toxic, + Confusion, + Psychic, + Hypnosis, + Meditate, + Agility, + QuickAttack, + Rage, + Teleport, + NightShade, + Mimic, + Screech, + DoubleTeam, + Recover, + Harden, + Minimize, + Smokescreen, + ConfuseRay, + Withdraw, + DefenseCurl, + Barrier, + LightScreen, + Haze, + Reflect, + FocusEnergy, + Bide, + Metronome, + MirrorMove, + SelfDestruct, + EggBomb, + Lick, + Smog, + Sludge, + BoneClub, + FireBlast, + Waterfall, + Clamp, + Swift, + SkullBash, + SpikeCannon, + Constrict, + Amnesia, + Kinesis, + SoftBoiled, + HighJumpKick, + Glare, + DreamEater, + PoisonGas, + Barrage, + LeechLife, + LovelyKiss, + SkyAttack, + Transform, + Bubble, + DizzyPunch, + Spore, + Flash, + Psywave, + Splash, + AcidArmor, + Crabhammer, + Explosion, + FurySwipes, + Bonemerang, + Rest, + RockSlide, + HyperFang, + Sharpen, + Conversion, + TriAttack, + SuperFang, + Slash, + Substitute, + Struggle, + Sketch, + TripleKick, + Thief, + SpiderWeb, + MindReader, + Nightmare, + FlameWheel, + Snore, + Curse, + Flail, + Conversion2, + Aeroblast, + CottonSpore, + Reversal, + Spite, + PowderSnow, + Protect, + MachPunch, + ScaryFace, + FeintAttack, + SweetKiss, + BellyDrum, + SludgeBomb, + MudSlap, + Octazooka, + Spikes, + ZapCannon, + Foresight, + DestinyBond, + PerishSong, + IcyWind, + Detect, + BoneRush, + LockOn, + Outrage, + Sandstorm, + GigaDrain, + Endure, + Charm, + Rollout, + FalseSwipe, + Swagger, + MilkDrink, + Spark, + FuryCutter, + SteelWing, + MeanLook, + Attract, + SleepTalk, + HealBell, + Return, + Present, + Frustration, + Safeguard, + PainSplit, + SacredFire, + Magnitude, + DynamicPunch, + Megahorn, + DragonBreath, + BatonPass, + Encore, + Pursuit, + RapidSpin, + SweetScent, + IronTail, + MetalClaw, + VitalThrow, + MorningSun, + Synthesis, + Moonlight, + HiddenPower, + CrossChop, + Twister, + RainDance, + SunnyDay, + Crunch, + MirrorCoat, + PsychUp, + ExtremeSpeed, + AncientPower, + ShadowBall, + FutureSight, + RockSmash, + Whirlpool, + BeatUp, + FakeOut, + Uproar, + Stockpile, + SpitUp, + Swallow, + HeatWave, + Hail, + Torment, + Flatter, + WillOWisp, + Memento, + Facade, + FocusPunch, + SmellingSalts, + FollowMe, + NaturePower, + Charge, + Taunt, + HelpingHand, + Trick, + RolePlay, + Wish, + Assist, + Ingrain, + Superpower, + MagicCoat, + Recycle, + Revenge, + BrickBreak, + Yawn, + KnockOff, + Endeavor, + Eruption, + SkillSwap, + Imprison, + Refresh, + Grudge, + Snatch, + SecretPower, + Dive, + ArmThrust, + Camouflage, + TailGlow, + LusterPurge, + MistBall, + FeatherDance, + TeeterDance, + BlazeKick, + MudSport, + IceBall, + NeedleArm, + SlackOff, + HyperVoice, + PoisonFang, + CrushClaw, + BlastBurn, + HydroCannon, + MeteorMash, + Astonish, + WeatherBall, + Aromatherapy, + FakeTears, + AirCutter, + Overheat, + OdorSleuth, + RockTomb, + SilverWind, + MetalSound, + GrassWhistle, + Tickle, + CosmicPower, + WaterSpout, + SignalBeam, + ShadowPunch, + Extrasensory, + SkyUppercut, + SandTomb, + SheerCold, + MuddyWater, + BulletSeed, + AerialAce, + IcicleSpear, + IronDefense, + Block, + Howl, + DragonClaw, + FrenzyPlant, + BulkUp, + Bounce, + MudShot, + PoisonTail, + Covet, + VoltTackle, + MagicalLeaf, + WaterSport, + CalmMind, + LeafBlade, + DragonDance, + RockBlast, + ShockWave, + WaterPulse, + DoomDesire, + PsychoBoost, + Roost, + Gravity, + MiracleEye, + WakeUpSlap, + HammerArm, + GyroBall, + HealingWish, + Brine, + NaturalGift, + Feint, + Pluck, + Tailwind, + Acupressure, + MetalBurst, + Uturn, + CloseCombat, + Payback, + Assurance, + Embargo, + Fling, + PsychoShift, + TrumpCard, + HealBlock, + WringOut, + PowerTrick, + GastroAcid, + LuckyChant, + MeFirst, + Copycat, + PowerSwap, + GuardSwap, + Punishment, + LastResort, + WorrySeed, + SuckerPunch, + ToxicSpikes, + HeartSwap, + AquaRing, + MagnetRise, + FlareBlitz, + ForcePalm, + AuraSphere, + RockPolish, + PoisonJab, + DarkPulse, + NightSlash, + AquaTail, + SeedBomb, + AirSlash, + XScissor, + BugBuzz, + DragonPulse, + DragonRush, + PowerGem, + DrainPunch, + VacuumWave, + FocusBlast, + EnergyBall, + BraveBird, + EarthPower, + Switcheroo, + GigaImpact, + NastyPlot, + BulletPunch, + Avalanche, + IceShard, + ShadowClaw, + ThunderFang, + IceFang, + FireFang, + ShadowSneak, + MudBomb, + PsychoCut, + ZenHeadbutt, + MirrorShot, + FlashCannon, + RockClimb, + Defog, + TrickRoom, + DracoMeteor, + Discharge, + LavaPlume, + LeafStorm, + PowerWhip, + RockWrecker, + CrossPoison, + GunkShot, + IronHead, + MagnetBomb, + StoneEdge, + Captivate, + StealthRock, + GrassKnot, + Chatter, + Judgment, + BugBite, + ChargeBeam, + WoodHammer, + AquaJet, + AttackOrder, + DefendOrder, + HealOrder, + HeadSmash, + DoubleHit, + RoarofTime, + SpacialRend, + LunarDance, + CrushGrip, + MagmaStorm, + DarkVoid, + SeedFlare, + OminousWind, + ShadowForce, + HoneClaws, + WideGuard, + GuardSplit, + PowerSplit, + WonderRoom, + Psyshock, + Venoshock, + Autotomize, + RagePowder, + Telekinesis, + MagicRoom, + SmackDown, + StormThrow, + FlameBurst, + SludgeWave, + QuiverDance, + HeavySlam, + Synchronoise, + ElectroBall, + Soak, + FlameCharge, + Coil, + LowSweep, + AcidSpray, + FoulPlay, + SimpleBeam, + Entrainment, + AfterYou, + Round, + EchoedVoice, + ChipAway, + ClearSmog, + StoredPower, + QuickGuard, + AllySwitch, + Scald, + ShellSmash, + HealPulse, + Hex, + SkyDrop, + ShiftGear, + CircleThrow, + Incinerate, + Quash, + Acrobatics, + ReflectType, + Retaliate, + FinalGambit, + Bestow, + Inferno, + WaterPledge, + FirePledge, + GrassPledge, + VoltSwitch, + StruggleBug, + Bulldoze, + FrostBreath, + DragonTail, + WorkUp, + Electroweb, + WildCharge, + DrillRun, + DualChop, + HeartStamp, + HornLeech, + SacredSword, + RazorShell, + HeatCrash, + LeafTornado, + Steamroller, + CottonGuard, + NightDaze, + Psystrike, + TailSlap, + Hurricane, + HeadCharge, + GearGrind, + SearingShot, + TechnoBlast, + RelicSong, + SecretSword, + Glaciate, + BoltStrike, + BlueFlare, + FieryDance, + FreezeShock, + IceBurn, + Snarl, + IcicleCrash, + Vcreate, + FusionFlare, + FusionBolt, + FlyingPress, + MatBlock, + Belch, + Rototiller, + StickyWeb, + FellStinger, + PhantomForce, + TrickorTreat, + NobleRoar, + IonDeluge, + ParabolicCharge, + ForestsCurse, + PetalBlizzard, + FreezeDry, + DisarmingVoice, + PartingShot, + TopsyTurvy, + DrainingKiss, + CraftyShield, + FlowerShield, + GrassyTerrain, + MistyTerrain, + Electrify, + PlayRough, + FairyWind, + Moonblast, + Boomburst, + FairyLock, + KingsShield, + PlayNice, + Confide, + DiamondStorm, + SteamEruption, + HyperspaceHole, + WaterShuriken, + MysticalFire, + SpikyShield, + AromaticMist, + EerieImpulse, + VenomDrench, + Powder, + Geomancy, + MagneticFlux, + HappyHour, + ElectricTerrain, + DazzlingGleam, + Celebrate, + HoldHands, + BabyDollEyes, + Nuzzle, + HoldBack, + Infestation, + PowerUpPunch, + OblivionWing, + ThousandArrows, + ThousandWaves, + LandsWrath, + LightofRuin, + OriginPulse, + PrecipiceBlades, + DragonAscent, + HyperspaceFury, + BreakneckBlitzP, + BreakneckBlitzS, + AllOutPummelingP, + AllOutPummelingS, + SupersonicSkystrikeP, + SupersonicSkystrikeS, + AcidDownpourP, + AcidDownpourS, + TectonicRageP, + TectonicRageS, + ContinentalCrushP, + ContinentalCrushS, + SavageSpinOutP, + SavageSpinOutS, + NeverEndingNightmareP, + NeverEndingNightmareS, + CorkscrewCrashP, + CorkscrewCrashS, + InfernoOverdriveP, + InfernoOverdriveS, + HydroVortexP, + HydroVortexS, + BloomDoomP, + BloomDoomS, + GigavoltHavocP, + GigavoltHavocS, + ShatteredPsycheP, + ShatteredPsycheS, + SubzeroSlammerP, + SubzeroSlammerS, + DevastatingDrakeP, + DevastatingDrakeS, + BlackHoleEclipseP, + BlackHoleEclipseS, + TwinkleTackleP, + TwinkleTackleS, + Catastropika, + ShoreUp, + FirstImpression, + BanefulBunker, + SpiritShackle, + DarkestLariat, + SparklingAria, + IceHammer, + FloralHealing, + HighHorsepower, + StrengthSap, + SolarBlade, + Leafage, + Spotlight, + ToxicThread, + LaserFocus, + GearUp, + ThroatChop, + PollenPuff, + AnchorShot, + PsychicTerrain, + Lunge, + FireLash, + PowerTrip, + BurnUp, + SpeedSwap, + SmartStrike, + Purify, + RevelationDance, + CoreEnforcer, + TropKick, + Instruct, + BeakBlast, + ClangingScales, + DragonHammer, + BrutalSwing, + AuroraVeil, + SinisterArrowRaid, + MaliciousMoonsault, + OceanicOperetta, + GuardianofAlola, + SoulStealing7StarStrike, + StokedSparksurfer, + PulverizingPancake, + ExtremeEvoboost, + GenesisSupernova, + ShellTrap, + FleurCannon, + PsychicFangs, + StompingTantrum, + ShadowBone, + Accelerock, + Liquidation, + PrismaticLaser, + SpectralThief, + SunsteelStrike, + MoongeistBeam, + TearfulLook, + ZingZap, + NaturesMadness, + MultiAttack, + TenMVoltThunderbolt, + MindBlown, + PlasmaFists, + PhotonGeyser, + LightThatBurnstheSky, + SearingSunrazeSmash, + MenacingMoonrazeMaelstrom, + LetsSnuggleForever, + SplinteredStormshards, + ClangorousSoulblaze, + ZippyZap, + SplishySplash, + FloatyFall, + PikaPapow, + BouncyBubble, + BuzzyBuzz, + SizzlySlide, + GlitzyGlow, + BaddyBad, + SappySeed, + FreezyFrost, + SparklySwirl, + VeeveeVolley, + DoubleIronBash, + MaxGuard, + DynamaxCannon, + SnipeShot, + JawLock, + StuffCheeks, + NoRetreat, + TarShot, + MagicPowder, + DragonDarts, + Teatime, + Octolock, + BoltBeak, + FishiousRend, + CourtChange, + MaxFlare, + MaxFlutterby, + MaxLightning, + MaxStrike, + MaxKnuckle, + MaxPhantasm, + MaxHailstorm, + MaxOoze, + MaxGeyser, + MaxAirstream, + MaxStarfall, + MaxWyrmwind, + MaxMindstorm, + MaxRockfall, + MaxQuake, + MaxDarkness, + MaxOvergrowth, + MaxSteelspike, + ClangorousSoul, + BodyPress, + Decorate, + DrumBeating, + SnapTrap, + PyroBall, + BehemothBlade, + BehemothBash, + AuraWheel, + BreakingSwipe, + BranchPoke, + Overdrive, + AppleAcid, + GravApple, + SpiritBreak, + StrangeSteam, + LifeDew, + Obstruct, + FalseSurrender, + MeteorAssault, + Eternabeam, + SteelBeam, + ExpandingForce, + SteelRoller, + ScaleShot, + MeteorBeam, + ShellSideArm, + MistyExplosion, + GrassyGlide, + RisingVoltage, + TerrainPulse, + SkitterSmack, + BurningJealousy, + LashOut, + Poltergeist, + CorrosiveGas, + Coaching, + FlipTurn, + TripleAxel, + DualWingbeat, + ScorchingSands, + JungleHealing, + WickedBlow, + SurgingStrikes, + ThunderCage, + DragonEnergy, + FreezingGlare, + FieryWrath, + ThunderousKick, + GlacialLance, + AstralBarrage, + EerieSpell, + DireClaw, + PsyshieldBash, + PowerShift, + StoneAxe, + SpringtideStorm, + MysticalPower, + RagingFury, + WaveCrash, + Chloroblast, + MountainGale, + VictoryDance, + HeadlongRush, + BarbBarrage, + EsperWing, + BitterMalice, + Shelter, + TripleArrows, + InfernalParade, + CeaselessEdge, + BleakwindStorm, + WildboltStorm, + SandsearStorm, + LunarBlessing, + TakeHeart, + MAX_COUNT, } diff --git a/PKHeX.Core/Game/Enums/MoveType.cs b/PKHeX.Core/Game/Enums/MoveType.cs index 694382e3e..05d86999a 100644 --- a/PKHeX.Core/Game/Enums/MoveType.cs +++ b/PKHeX.Core/Game/Enums/MoveType.cs @@ -1,49 +1,48 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Elemental type a move has; additionally, types a can have. +/// +public enum MoveType : sbyte { - /// - /// Elemental type a move has; additionally, types a can have. - /// - 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; } } diff --git a/PKHeX.Core/Game/Enums/Nature.cs b/PKHeX.Core/Game/Enums/Nature.cs index b024b976b..ff7ae0b03 100644 --- a/PKHeX.Core/Game/Enums/Nature.cs +++ b/PKHeX.Core/Game/Enums/Nature.cs @@ -1,49 +1,48 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Nature ID values for the corresponding English nature name. +/// +public enum Nature : byte { - /// - /// Nature ID values for the corresponding English nature name. - /// - 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; } diff --git a/PKHeX.Core/Game/Enums/Region3DSIndex.cs b/PKHeX.Core/Game/Enums/Region3DSIndex.cs index 3d04a6e12..2f9a2fab8 100644 --- a/PKHeX.Core/Game/Enums/Region3DSIndex.cs +++ b/PKHeX.Core/Game/Enums/Region3DSIndex.cs @@ -1,16 +1,15 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// 3DS Console Region Identifiers used for Generation 6 & 7 Mystery Gifts +/// +public enum Region3DSIndex : byte { - /// - /// 3DS Console Region Identifiers used for Generation 6 & 7 Mystery Gifts - /// - 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, } diff --git a/PKHeX.Core/Game/Enums/Species.cs b/PKHeX.Core/Game/Enums/Species.cs index 19d72b04a..de4ce3bc2 100644 --- a/PKHeX.Core/Game/Enums/Species.cs +++ b/PKHeX.Core/Game/Enums/Species.cs @@ -1,916 +1,915 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Species IDs for the corresponding English species name. +/// +public enum Species : ushort { - /// - /// Species IDs for the corresponding English species name. - /// - public enum Species : ushort - { - None, - Bulbasaur, - Ivysaur, - Venusaur, - Charmander, - Charmeleon, - Charizard, - Squirtle, - Wartortle, - Blastoise, - Caterpie, - Metapod, - Butterfree, - Weedle, - Kakuna, - Beedrill, - Pidgey, - Pidgeotto, - Pidgeot, - Rattata, - Raticate, - Spearow, - Fearow, - Ekans, - Arbok, - Pikachu, - Raichu, - Sandshrew, - Sandslash, - NidoranF, - Nidorina, - Nidoqueen, - NidoranM, - Nidorino, - Nidoking, - Clefairy, - Clefable, - Vulpix, - Ninetales, - Jigglypuff, - Wigglytuff, - Zubat, - Golbat, - Oddish, - Gloom, - Vileplume, - Paras, - Parasect, - Venonat, - Venomoth, - Diglett, - Dugtrio, - Meowth, - Persian, - Psyduck, - Golduck, - Mankey, - Primeape, - Growlithe, - Arcanine, - Poliwag, - Poliwhirl, - Poliwrath, - Abra, - Kadabra, - Alakazam, - Machop, - Machoke, - Machamp, - Bellsprout, - Weepinbell, - Victreebel, - Tentacool, - Tentacruel, - Geodude, - Graveler, - Golem, - Ponyta, - Rapidash, - Slowpoke, - Slowbro, - Magnemite, - Magneton, - Farfetchd, - Doduo, - Dodrio, - Seel, - Dewgong, - Grimer, - Muk, - Shellder, - Cloyster, - Gastly, - Haunter, - Gengar, - Onix, - Drowzee, - Hypno, - Krabby, - Kingler, - Voltorb, - Electrode, - Exeggcute, - Exeggutor, - Cubone, - Marowak, - Hitmonlee, - Hitmonchan, - Lickitung, - Koffing, - Weezing, - Rhyhorn, - Rhydon, - Chansey, - Tangela, - Kangaskhan, - Horsea, - Seadra, - Goldeen, - Seaking, - Staryu, - Starmie, - MrMime, - Scyther, - Jynx, - Electabuzz, - Magmar, - Pinsir, - Tauros, - Magikarp, - Gyarados, - Lapras, - Ditto, - Eevee, - Vaporeon, - Jolteon, - Flareon, - Porygon, - Omanyte, - Omastar, - Kabuto, - Kabutops, - Aerodactyl, - Snorlax, - Articuno, - Zapdos, - Moltres, - Dratini, - Dragonair, - Dragonite, - Mewtwo, - Mew, - Chikorita, - Bayleef, - Meganium, - Cyndaquil, - Quilava, - Typhlosion, - Totodile, - Croconaw, - Feraligatr, - Sentret, - Furret, - Hoothoot, - Noctowl, - Ledyba, - Ledian, - Spinarak, - Ariados, - Crobat, - Chinchou, - Lanturn, - Pichu, - Cleffa, - Igglybuff, - Togepi, - Togetic, - Natu, - Xatu, - Mareep, - Flaaffy, - Ampharos, - Bellossom, - Marill, - Azumarill, - Sudowoodo, - Politoed, - Hoppip, - Skiploom, - Jumpluff, - Aipom, - Sunkern, - Sunflora, - Yanma, - Wooper, - Quagsire, - Espeon, - Umbreon, - Murkrow, - Slowking, - Misdreavus, - Unown, - Wobbuffet, - Girafarig, - Pineco, - Forretress, - Dunsparce, - Gligar, - Steelix, - Snubbull, - Granbull, - Qwilfish, - Scizor, - Shuckle, - Heracross, - Sneasel, - Teddiursa, - Ursaring, - Slugma, - Magcargo, - Swinub, - Piloswine, - Corsola, - Remoraid, - Octillery, - Delibird, - Mantine, - Skarmory, - Houndour, - Houndoom, - Kingdra, - Phanpy, - Donphan, - Porygon2, - Stantler, - Smeargle, - Tyrogue, - Hitmontop, - Smoochum, - Elekid, - Magby, - Miltank, - Blissey, - Raikou, - Entei, - Suicune, - Larvitar, - Pupitar, - Tyranitar, - Lugia, - HoOh, - Celebi, - Treecko, - Grovyle, - Sceptile, - Torchic, - Combusken, - Blaziken, - Mudkip, - Marshtomp, - Swampert, - Poochyena, - Mightyena, - Zigzagoon, - Linoone, - Wurmple, - Silcoon, - Beautifly, - Cascoon, - Dustox, - Lotad, - Lombre, - Ludicolo, - Seedot, - Nuzleaf, - Shiftry, - Taillow, - Swellow, - Wingull, - Pelipper, - Ralts, - Kirlia, - Gardevoir, - Surskit, - Masquerain, - Shroomish, - Breloom, - Slakoth, - Vigoroth, - Slaking, - Nincada, - Ninjask, - Shedinja, - Whismur, - Loudred, - Exploud, - Makuhita, - Hariyama, - Azurill, - Nosepass, - Skitty, - Delcatty, - Sableye, - Mawile, - Aron, - Lairon, - Aggron, - Meditite, - Medicham, - Electrike, - Manectric, - Plusle, - Minun, - Volbeat, - Illumise, - Roselia, - Gulpin, - Swalot, - Carvanha, - Sharpedo, - Wailmer, - Wailord, - Numel, - Camerupt, - Torkoal, - Spoink, - Grumpig, - Spinda, - Trapinch, - Vibrava, - Flygon, - Cacnea, - Cacturne, - Swablu, - Altaria, - Zangoose, - Seviper, - Lunatone, - Solrock, - Barboach, - Whiscash, - Corphish, - Crawdaunt, - Baltoy, - Claydol, - Lileep, - Cradily, - Anorith, - Armaldo, - Feebas, - Milotic, - Castform, - Kecleon, - Shuppet, - Banette, - Duskull, - Dusclops, - Tropius, - Chimecho, - Absol, - Wynaut, - Snorunt, - Glalie, - Spheal, - Sealeo, - Walrein, - Clamperl, - Huntail, - Gorebyss, - Relicanth, - Luvdisc, - Bagon, - Shelgon, - Salamence, - Beldum, - Metang, - Metagross, - Regirock, - Regice, - Registeel, - Latias, - Latios, - Kyogre, - Groudon, - Rayquaza, - Jirachi, - Deoxys, - Turtwig, - Grotle, - Torterra, - Chimchar, - Monferno, - Infernape, - Piplup, - Prinplup, - Empoleon, - Starly, - Staravia, - Staraptor, - Bidoof, - Bibarel, - Kricketot, - Kricketune, - Shinx, - Luxio, - Luxray, - Budew, - Roserade, - Cranidos, - Rampardos, - Shieldon, - Bastiodon, - Burmy, - Wormadam, - Mothim, - Combee, - Vespiquen, - Pachirisu, - Buizel, - Floatzel, - Cherubi, - Cherrim, - Shellos, - Gastrodon, - Ambipom, - Drifloon, - Drifblim, - Buneary, - Lopunny, - Mismagius, - Honchkrow, - Glameow, - Purugly, - Chingling, - Stunky, - Skuntank, - Bronzor, - Bronzong, - Bonsly, - MimeJr, - Happiny, - Chatot, - Spiritomb, - Gible, - Gabite, - Garchomp, - Munchlax, - Riolu, - Lucario, - Hippopotas, - Hippowdon, - Skorupi, - Drapion, - Croagunk, - Toxicroak, - Carnivine, - Finneon, - Lumineon, - Mantyke, - Snover, - Abomasnow, - Weavile, - Magnezone, - Lickilicky, - Rhyperior, - Tangrowth, - Electivire, - Magmortar, - Togekiss, - Yanmega, - Leafeon, - Glaceon, - Gliscor, - Mamoswine, - PorygonZ, - Gallade, - Probopass, - Dusknoir, - Froslass, - Rotom, - Uxie, - Mesprit, - Azelf, - Dialga, - Palkia, - Heatran, - Regigigas, - Giratina, - Cresselia, - Phione, - Manaphy, - Darkrai, - Shaymin, - Arceus, - Victini, - Snivy, - Servine, - Serperior, - Tepig, - Pignite, - Emboar, - Oshawott, - Dewott, - Samurott, - Patrat, - Watchog, - Lillipup, - Herdier, - Stoutland, - Purrloin, - Liepard, - Pansage, - Simisage, - Pansear, - Simisear, - Panpour, - Simipour, - Munna, - Musharna, - Pidove, - Tranquill, - Unfezant, - Blitzle, - Zebstrika, - Roggenrola, - Boldore, - Gigalith, - Woobat, - Swoobat, - Drilbur, - Excadrill, - Audino, - Timburr, - Gurdurr, - Conkeldurr, - Tympole, - Palpitoad, - Seismitoad, - Throh, - Sawk, - Sewaddle, - Swadloon, - Leavanny, - Venipede, - Whirlipede, - Scolipede, - Cottonee, - Whimsicott, - Petilil, - Lilligant, - Basculin, - Sandile, - Krokorok, - Krookodile, - Darumaka, - Darmanitan, - Maractus, - Dwebble, - Crustle, - Scraggy, - Scrafty, - Sigilyph, - Yamask, - Cofagrigus, - Tirtouga, - Carracosta, - Archen, - Archeops, - Trubbish, - Garbodor, - Zorua, - Zoroark, - Minccino, - Cinccino, - Gothita, - Gothorita, - Gothitelle, - Solosis, - Duosion, - Reuniclus, - Ducklett, - Swanna, - Vanillite, - Vanillish, - Vanilluxe, - Deerling, - Sawsbuck, - Emolga, - Karrablast, - Escavalier, - Foongus, - Amoonguss, - Frillish, - Jellicent, - Alomomola, - Joltik, - Galvantula, - Ferroseed, - Ferrothorn, - Klink, - Klang, - Klinklang, - Tynamo, - Eelektrik, - Eelektross, - Elgyem, - Beheeyem, - Litwick, - Lampent, - Chandelure, - Axew, - Fraxure, - Haxorus, - Cubchoo, - Beartic, - Cryogonal, - Shelmet, - Accelgor, - Stunfisk, - Mienfoo, - Mienshao, - Druddigon, - Golett, - Golurk, - Pawniard, - Bisharp, - Bouffalant, - Rufflet, - Braviary, - Vullaby, - Mandibuzz, - Heatmor, - Durant, - Deino, - Zweilous, - Hydreigon, - Larvesta, - Volcarona, - Cobalion, - Terrakion, - Virizion, - Tornadus, - Thundurus, - Reshiram, - Zekrom, - Landorus, - Kyurem, - Keldeo, - Meloetta, - Genesect, - Chespin, - Quilladin, - Chesnaught, - Fennekin, - Braixen, - Delphox, - Froakie, - Frogadier, - Greninja, - Bunnelby, - Diggersby, - Fletchling, - Fletchinder, - Talonflame, - Scatterbug, - Spewpa, - Vivillon, - Litleo, - Pyroar, - Flabébé, - Floette, - Florges, - Skiddo, - Gogoat, - Pancham, - Pangoro, - Furfrou, - Espurr, - Meowstic, - Honedge, - Doublade, - Aegislash, - Spritzee, - Aromatisse, - Swirlix, - Slurpuff, - Inkay, - Malamar, - Binacle, - Barbaracle, - Skrelp, - Dragalge, - Clauncher, - Clawitzer, - Helioptile, - Heliolisk, - Tyrunt, - Tyrantrum, - Amaura, - Aurorus, - Sylveon, - Hawlucha, - Dedenne, - Carbink, - Goomy, - Sliggoo, - Goodra, - Klefki, - Phantump, - Trevenant, - Pumpkaboo, - Gourgeist, - Bergmite, - Avalugg, - Noibat, - Noivern, - Xerneas, - Yveltal, - Zygarde, - Diancie, - Hoopa, - Volcanion, - Rowlet, - Dartrix, - Decidueye, - Litten, - Torracat, - Incineroar, - Popplio, - Brionne, - Primarina, - Pikipek, - Trumbeak, - Toucannon, - Yungoos, - Gumshoos, - Grubbin, - Charjabug, - Vikavolt, - Crabrawler, - Crabominable, - Oricorio, - Cutiefly, - Ribombee, - Rockruff, - Lycanroc, - Wishiwashi, - Mareanie, - Toxapex, - Mudbray, - Mudsdale, - Dewpider, - Araquanid, - Fomantis, - Lurantis, - Morelull, - Shiinotic, - Salandit, - Salazzle, - Stufful, - Bewear, - Bounsweet, - Steenee, - Tsareena, - Comfey, - Oranguru, - Passimian, - Wimpod, - Golisopod, - Sandygast, - Palossand, - Pyukumuku, - TypeNull, - Silvally, - Minior, - Komala, - Turtonator, - Togedemaru, - Mimikyu, - Bruxish, - Drampa, - Dhelmise, - Jangmoo, - Hakamoo, - Kommoo, - TapuKoko, - TapuLele, - TapuBulu, - TapuFini, - Cosmog, - Cosmoem, - Solgaleo, - Lunala, - Nihilego, - Buzzwole, - Pheromosa, - Xurkitree, - Celesteela, - Kartana, - Guzzlord, - Necrozma, - Magearna, - Marshadow, - Poipole, - Naganadel, - Stakataka, - Blacephalon, - Zeraora, - Meltan, - Melmetal, - Grookey, - Thwackey, - Rillaboom, - Scorbunny, - Raboot, - Cinderace, - Sobble, - Drizzile, - Inteleon, - Skwovet, - Greedent, - Rookidee, - Corvisquire, - Corviknight, - Blipbug, - Dottler, - Orbeetle, - Nickit, - Thievul, - Gossifleur, - Eldegoss, - Wooloo, - Dubwool, - Chewtle, - Drednaw, - Yamper, - Boltund, - Rolycoly, - Carkol, - Coalossal, - Applin, - Flapple, - Appletun, - Silicobra, - Sandaconda, - Cramorant, - Arrokuda, - Barraskewda, - Toxel, - Toxtricity, - Sizzlipede, - Centiskorch, - Clobbopus, - Grapploct, - Sinistea, - Polteageist, - Hatenna, - Hattrem, - Hatterene, - Impidimp, - Morgrem, - Grimmsnarl, - Obstagoon, - Perrserker, - Cursola, - Sirfetchd, - MrRime, - Runerigus, - Milcery, - Alcremie, - Falinks, - Pincurchin, - Snom, - Frosmoth, - Stonjourner, - Eiscue, - Indeedee, - Morpeko, - Cufant, - Copperajah, - Dracozolt, - Arctozolt, - Dracovish, - Arctovish, - Duraludon, - Dreepy, - Drakloak, - Dragapult, - Zacian, - Zamazenta, - Eternatus, - Kubfu, - Urshifu, - Zarude, - Regieleki, - Regidrago, - Glastrier, - Spectrier, - Calyrex, - Wyrdeer, - Kleavor, - Ursaluna, - Basculegion, - Sneasler, - Overqwil, - Enamorus, - MAX_COUNT, - } + None, + Bulbasaur, + Ivysaur, + Venusaur, + Charmander, + Charmeleon, + Charizard, + Squirtle, + Wartortle, + Blastoise, + Caterpie, + Metapod, + Butterfree, + Weedle, + Kakuna, + Beedrill, + Pidgey, + Pidgeotto, + Pidgeot, + Rattata, + Raticate, + Spearow, + Fearow, + Ekans, + Arbok, + Pikachu, + Raichu, + Sandshrew, + Sandslash, + NidoranF, + Nidorina, + Nidoqueen, + NidoranM, + Nidorino, + Nidoking, + Clefairy, + Clefable, + Vulpix, + Ninetales, + Jigglypuff, + Wigglytuff, + Zubat, + Golbat, + Oddish, + Gloom, + Vileplume, + Paras, + Parasect, + Venonat, + Venomoth, + Diglett, + Dugtrio, + Meowth, + Persian, + Psyduck, + Golduck, + Mankey, + Primeape, + Growlithe, + Arcanine, + Poliwag, + Poliwhirl, + Poliwrath, + Abra, + Kadabra, + Alakazam, + Machop, + Machoke, + Machamp, + Bellsprout, + Weepinbell, + Victreebel, + Tentacool, + Tentacruel, + Geodude, + Graveler, + Golem, + Ponyta, + Rapidash, + Slowpoke, + Slowbro, + Magnemite, + Magneton, + Farfetchd, + Doduo, + Dodrio, + Seel, + Dewgong, + Grimer, + Muk, + Shellder, + Cloyster, + Gastly, + Haunter, + Gengar, + Onix, + Drowzee, + Hypno, + Krabby, + Kingler, + Voltorb, + Electrode, + Exeggcute, + Exeggutor, + Cubone, + Marowak, + Hitmonlee, + Hitmonchan, + Lickitung, + Koffing, + Weezing, + Rhyhorn, + Rhydon, + Chansey, + Tangela, + Kangaskhan, + Horsea, + Seadra, + Goldeen, + Seaking, + Staryu, + Starmie, + MrMime, + Scyther, + Jynx, + Electabuzz, + Magmar, + Pinsir, + Tauros, + Magikarp, + Gyarados, + Lapras, + Ditto, + Eevee, + Vaporeon, + Jolteon, + Flareon, + Porygon, + Omanyte, + Omastar, + Kabuto, + Kabutops, + Aerodactyl, + Snorlax, + Articuno, + Zapdos, + Moltres, + Dratini, + Dragonair, + Dragonite, + Mewtwo, + Mew, + Chikorita, + Bayleef, + Meganium, + Cyndaquil, + Quilava, + Typhlosion, + Totodile, + Croconaw, + Feraligatr, + Sentret, + Furret, + Hoothoot, + Noctowl, + Ledyba, + Ledian, + Spinarak, + Ariados, + Crobat, + Chinchou, + Lanturn, + Pichu, + Cleffa, + Igglybuff, + Togepi, + Togetic, + Natu, + Xatu, + Mareep, + Flaaffy, + Ampharos, + Bellossom, + Marill, + Azumarill, + Sudowoodo, + Politoed, + Hoppip, + Skiploom, + Jumpluff, + Aipom, + Sunkern, + Sunflora, + Yanma, + Wooper, + Quagsire, + Espeon, + Umbreon, + Murkrow, + Slowking, + Misdreavus, + Unown, + Wobbuffet, + Girafarig, + Pineco, + Forretress, + Dunsparce, + Gligar, + Steelix, + Snubbull, + Granbull, + Qwilfish, + Scizor, + Shuckle, + Heracross, + Sneasel, + Teddiursa, + Ursaring, + Slugma, + Magcargo, + Swinub, + Piloswine, + Corsola, + Remoraid, + Octillery, + Delibird, + Mantine, + Skarmory, + Houndour, + Houndoom, + Kingdra, + Phanpy, + Donphan, + Porygon2, + Stantler, + Smeargle, + Tyrogue, + Hitmontop, + Smoochum, + Elekid, + Magby, + Miltank, + Blissey, + Raikou, + Entei, + Suicune, + Larvitar, + Pupitar, + Tyranitar, + Lugia, + HoOh, + Celebi, + Treecko, + Grovyle, + Sceptile, + Torchic, + Combusken, + Blaziken, + Mudkip, + Marshtomp, + Swampert, + Poochyena, + Mightyena, + Zigzagoon, + Linoone, + Wurmple, + Silcoon, + Beautifly, + Cascoon, + Dustox, + Lotad, + Lombre, + Ludicolo, + Seedot, + Nuzleaf, + Shiftry, + Taillow, + Swellow, + Wingull, + Pelipper, + Ralts, + Kirlia, + Gardevoir, + Surskit, + Masquerain, + Shroomish, + Breloom, + Slakoth, + Vigoroth, + Slaking, + Nincada, + Ninjask, + Shedinja, + Whismur, + Loudred, + Exploud, + Makuhita, + Hariyama, + Azurill, + Nosepass, + Skitty, + Delcatty, + Sableye, + Mawile, + Aron, + Lairon, + Aggron, + Meditite, + Medicham, + Electrike, + Manectric, + Plusle, + Minun, + Volbeat, + Illumise, + Roselia, + Gulpin, + Swalot, + Carvanha, + Sharpedo, + Wailmer, + Wailord, + Numel, + Camerupt, + Torkoal, + Spoink, + Grumpig, + Spinda, + Trapinch, + Vibrava, + Flygon, + Cacnea, + Cacturne, + Swablu, + Altaria, + Zangoose, + Seviper, + Lunatone, + Solrock, + Barboach, + Whiscash, + Corphish, + Crawdaunt, + Baltoy, + Claydol, + Lileep, + Cradily, + Anorith, + Armaldo, + Feebas, + Milotic, + Castform, + Kecleon, + Shuppet, + Banette, + Duskull, + Dusclops, + Tropius, + Chimecho, + Absol, + Wynaut, + Snorunt, + Glalie, + Spheal, + Sealeo, + Walrein, + Clamperl, + Huntail, + Gorebyss, + Relicanth, + Luvdisc, + Bagon, + Shelgon, + Salamence, + Beldum, + Metang, + Metagross, + Regirock, + Regice, + Registeel, + Latias, + Latios, + Kyogre, + Groudon, + Rayquaza, + Jirachi, + Deoxys, + Turtwig, + Grotle, + Torterra, + Chimchar, + Monferno, + Infernape, + Piplup, + Prinplup, + Empoleon, + Starly, + Staravia, + Staraptor, + Bidoof, + Bibarel, + Kricketot, + Kricketune, + Shinx, + Luxio, + Luxray, + Budew, + Roserade, + Cranidos, + Rampardos, + Shieldon, + Bastiodon, + Burmy, + Wormadam, + Mothim, + Combee, + Vespiquen, + Pachirisu, + Buizel, + Floatzel, + Cherubi, + Cherrim, + Shellos, + Gastrodon, + Ambipom, + Drifloon, + Drifblim, + Buneary, + Lopunny, + Mismagius, + Honchkrow, + Glameow, + Purugly, + Chingling, + Stunky, + Skuntank, + Bronzor, + Bronzong, + Bonsly, + MimeJr, + Happiny, + Chatot, + Spiritomb, + Gible, + Gabite, + Garchomp, + Munchlax, + Riolu, + Lucario, + Hippopotas, + Hippowdon, + Skorupi, + Drapion, + Croagunk, + Toxicroak, + Carnivine, + Finneon, + Lumineon, + Mantyke, + Snover, + Abomasnow, + Weavile, + Magnezone, + Lickilicky, + Rhyperior, + Tangrowth, + Electivire, + Magmortar, + Togekiss, + Yanmega, + Leafeon, + Glaceon, + Gliscor, + Mamoswine, + PorygonZ, + Gallade, + Probopass, + Dusknoir, + Froslass, + Rotom, + Uxie, + Mesprit, + Azelf, + Dialga, + Palkia, + Heatran, + Regigigas, + Giratina, + Cresselia, + Phione, + Manaphy, + Darkrai, + Shaymin, + Arceus, + Victini, + Snivy, + Servine, + Serperior, + Tepig, + Pignite, + Emboar, + Oshawott, + Dewott, + Samurott, + Patrat, + Watchog, + Lillipup, + Herdier, + Stoutland, + Purrloin, + Liepard, + Pansage, + Simisage, + Pansear, + Simisear, + Panpour, + Simipour, + Munna, + Musharna, + Pidove, + Tranquill, + Unfezant, + Blitzle, + Zebstrika, + Roggenrola, + Boldore, + Gigalith, + Woobat, + Swoobat, + Drilbur, + Excadrill, + Audino, + Timburr, + Gurdurr, + Conkeldurr, + Tympole, + Palpitoad, + Seismitoad, + Throh, + Sawk, + Sewaddle, + Swadloon, + Leavanny, + Venipede, + Whirlipede, + Scolipede, + Cottonee, + Whimsicott, + Petilil, + Lilligant, + Basculin, + Sandile, + Krokorok, + Krookodile, + Darumaka, + Darmanitan, + Maractus, + Dwebble, + Crustle, + Scraggy, + Scrafty, + Sigilyph, + Yamask, + Cofagrigus, + Tirtouga, + Carracosta, + Archen, + Archeops, + Trubbish, + Garbodor, + Zorua, + Zoroark, + Minccino, + Cinccino, + Gothita, + Gothorita, + Gothitelle, + Solosis, + Duosion, + Reuniclus, + Ducklett, + Swanna, + Vanillite, + Vanillish, + Vanilluxe, + Deerling, + Sawsbuck, + Emolga, + Karrablast, + Escavalier, + Foongus, + Amoonguss, + Frillish, + Jellicent, + Alomomola, + Joltik, + Galvantula, + Ferroseed, + Ferrothorn, + Klink, + Klang, + Klinklang, + Tynamo, + Eelektrik, + Eelektross, + Elgyem, + Beheeyem, + Litwick, + Lampent, + Chandelure, + Axew, + Fraxure, + Haxorus, + Cubchoo, + Beartic, + Cryogonal, + Shelmet, + Accelgor, + Stunfisk, + Mienfoo, + Mienshao, + Druddigon, + Golett, + Golurk, + Pawniard, + Bisharp, + Bouffalant, + Rufflet, + Braviary, + Vullaby, + Mandibuzz, + Heatmor, + Durant, + Deino, + Zweilous, + Hydreigon, + Larvesta, + Volcarona, + Cobalion, + Terrakion, + Virizion, + Tornadus, + Thundurus, + Reshiram, + Zekrom, + Landorus, + Kyurem, + Keldeo, + Meloetta, + Genesect, + Chespin, + Quilladin, + Chesnaught, + Fennekin, + Braixen, + Delphox, + Froakie, + Frogadier, + Greninja, + Bunnelby, + Diggersby, + Fletchling, + Fletchinder, + Talonflame, + Scatterbug, + Spewpa, + Vivillon, + Litleo, + Pyroar, + Flabébé, + Floette, + Florges, + Skiddo, + Gogoat, + Pancham, + Pangoro, + Furfrou, + Espurr, + Meowstic, + Honedge, + Doublade, + Aegislash, + Spritzee, + Aromatisse, + Swirlix, + Slurpuff, + Inkay, + Malamar, + Binacle, + Barbaracle, + Skrelp, + Dragalge, + Clauncher, + Clawitzer, + Helioptile, + Heliolisk, + Tyrunt, + Tyrantrum, + Amaura, + Aurorus, + Sylveon, + Hawlucha, + Dedenne, + Carbink, + Goomy, + Sliggoo, + Goodra, + Klefki, + Phantump, + Trevenant, + Pumpkaboo, + Gourgeist, + Bergmite, + Avalugg, + Noibat, + Noivern, + Xerneas, + Yveltal, + Zygarde, + Diancie, + Hoopa, + Volcanion, + Rowlet, + Dartrix, + Decidueye, + Litten, + Torracat, + Incineroar, + Popplio, + Brionne, + Primarina, + Pikipek, + Trumbeak, + Toucannon, + Yungoos, + Gumshoos, + Grubbin, + Charjabug, + Vikavolt, + Crabrawler, + Crabominable, + Oricorio, + Cutiefly, + Ribombee, + Rockruff, + Lycanroc, + Wishiwashi, + Mareanie, + Toxapex, + Mudbray, + Mudsdale, + Dewpider, + Araquanid, + Fomantis, + Lurantis, + Morelull, + Shiinotic, + Salandit, + Salazzle, + Stufful, + Bewear, + Bounsweet, + Steenee, + Tsareena, + Comfey, + Oranguru, + Passimian, + Wimpod, + Golisopod, + Sandygast, + Palossand, + Pyukumuku, + TypeNull, + Silvally, + Minior, + Komala, + Turtonator, + Togedemaru, + Mimikyu, + Bruxish, + Drampa, + Dhelmise, + Jangmoo, + Hakamoo, + Kommoo, + TapuKoko, + TapuLele, + TapuBulu, + TapuFini, + Cosmog, + Cosmoem, + Solgaleo, + Lunala, + Nihilego, + Buzzwole, + Pheromosa, + Xurkitree, + Celesteela, + Kartana, + Guzzlord, + Necrozma, + Magearna, + Marshadow, + Poipole, + Naganadel, + Stakataka, + Blacephalon, + Zeraora, + Meltan, + Melmetal, + Grookey, + Thwackey, + Rillaboom, + Scorbunny, + Raboot, + Cinderace, + Sobble, + Drizzile, + Inteleon, + Skwovet, + Greedent, + Rookidee, + Corvisquire, + Corviknight, + Blipbug, + Dottler, + Orbeetle, + Nickit, + Thievul, + Gossifleur, + Eldegoss, + Wooloo, + Dubwool, + Chewtle, + Drednaw, + Yamper, + Boltund, + Rolycoly, + Carkol, + Coalossal, + Applin, + Flapple, + Appletun, + Silicobra, + Sandaconda, + Cramorant, + Arrokuda, + Barraskewda, + Toxel, + Toxtricity, + Sizzlipede, + Centiskorch, + Clobbopus, + Grapploct, + Sinistea, + Polteageist, + Hatenna, + Hattrem, + Hatterene, + Impidimp, + Morgrem, + Grimmsnarl, + Obstagoon, + Perrserker, + Cursola, + Sirfetchd, + MrRime, + Runerigus, + Milcery, + Alcremie, + Falinks, + Pincurchin, + Snom, + Frosmoth, + Stonjourner, + Eiscue, + Indeedee, + Morpeko, + Cufant, + Copperajah, + Dracozolt, + Arctozolt, + Dracovish, + Arctovish, + Duraludon, + Dreepy, + Drakloak, + Dragapult, + Zacian, + Zamazenta, + Eternatus, + Kubfu, + Urshifu, + Zarude, + Regieleki, + Regidrago, + Glastrier, + Spectrier, + Calyrex, + Wyrdeer, + Kleavor, + Ursaluna, + Basculegion, + Sneasler, + Overqwil, + Enamorus, + MAX_COUNT, } diff --git a/PKHeX.Core/Game/GameStrings/FilteredGameDataSource.cs b/PKHeX.Core/Game/GameStrings/FilteredGameDataSource.cs index a8408547c..583cf99cd 100644 --- a/PKHeX.Core/Game/GameStrings/FilteredGameDataSource.cs +++ b/PKHeX.Core/Game/GameStrings/FilteredGameDataSource.cs @@ -2,105 +2,104 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// sensitive provider for data sources. +/// +public sealed class FilteredGameDataSource { - /// - /// sensitive provider for data sources. - /// - 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(); - } - - 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(); } - private static IEnumerable 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 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 Moves; - public readonly IReadOnlyList Balls; - public readonly IReadOnlyList Games; - public readonly IReadOnlyList Items; - public readonly IReadOnlyList Species; - public readonly IReadOnlyList Languages; - public readonly IReadOnlyList Abilities; - public readonly IReadOnlyList Natures; - public readonly IReadOnlyList G4GroundTiles; - public readonly IReadOnlyList ConsoleRegions = GameDataSource.Regions; - - public IReadOnlyList GetAbilityList(PKM pkm) - { - var abilities = pkm.PersonalInfo.Abilities; - int format = pkm.Format; - return GetAbilityList(abilities, format); - } - - public IReadOnlyList GetAbilityList(IReadOnlyList 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 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 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 Moves; + public readonly IReadOnlyList Balls; + public readonly IReadOnlyList Games; + public readonly IReadOnlyList Items; + public readonly IReadOnlyList Species; + public readonly IReadOnlyList Languages; + public readonly IReadOnlyList Abilities; + public readonly IReadOnlyList Natures; + public readonly IReadOnlyList G4GroundTiles; + public readonly IReadOnlyList ConsoleRegions = GameDataSource.Regions; + + public IReadOnlyList GetAbilityList(PKM pk) + { + var abilities = pk.PersonalInfo.Abilities; + int format = pk.Format; + return GetAbilityList(abilities, format); + } + + public IReadOnlyList GetAbilityList(IReadOnlyList 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)" }; } diff --git a/PKHeX.Core/Game/GameStrings/GameDataSource.cs b/PKHeX.Core/Game/GameStrings/GameDataSource.cs index 2ca40b089..eea2fb65a 100644 --- a/PKHeX.Core/Game/GameStrings/GameDataSource.cs +++ b/PKHeX.Core/Game/GameStrings/GameDataSource.cs @@ -1,127 +1,126 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Bundles raw string inputs into lists that can be used in data binding. +/// +public sealed class GameDataSource { - /// - /// Bundles raw string inputs into lists that can be used in data binding. - /// - public sealed class GameDataSource + public static readonly IReadOnlyList Regions = new List { - public static readonly IReadOnlyList Regions = new List + 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 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(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); + } + + /// Strings that this object's lists were generated with. + public readonly GameStrings Strings; + + /// Contains Met Data lists to source lists from. + public readonly MetDataSource Met; + + /// Represents "(None)", localized to this object's language strings. + public readonly ComboItem Empty; + + public readonly IReadOnlyList SpeciesDataSource; + public readonly IReadOnlyList BallDataSource; + public readonly IReadOnlyList NatureDataSource; + public readonly IReadOnlyList AbilityDataSource; + public readonly IReadOnlyList VersionDataSource; + public readonly IReadOnlyList LegalMoveDataSource; + public readonly IReadOnlyList HaXMoveDataSource; + public readonly IReadOnlyList GroundTileDataSource; + + private static IReadOnlyList GetBalls(string[] itemList) + { + // ignores Poke/Great/Ultra + ReadOnlySpan 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 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 GetVersionList(GameStrings s) + { + var list = s.gamelist; + ReadOnlySpan 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 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 GetItemDataSource(GameVersion game, int generation, IReadOnlyList 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(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); - } - - /// Strings that this object's lists were generated with. - public readonly GameStrings Strings; - - /// Contains Met Data lists to source lists from. - public readonly MetDataSource Met; - - /// Represents "(None)", localized to this object's language strings. - public readonly ComboItem Empty; - - public readonly IReadOnlyList SpeciesDataSource; - public readonly IReadOnlyList BallDataSource; - public readonly IReadOnlyList NatureDataSource; - public readonly IReadOnlyList AbilityDataSource; - public readonly IReadOnlyList VersionDataSource; - public readonly IReadOnlyList LegalMoveDataSource; - public readonly IReadOnlyList HaXMoveDataSource; - public readonly IReadOnlyList GroundTileDataSource; - - private static IReadOnlyList GetBalls(string[] itemList) - { - // ignores Poke/Great/Ultra - ReadOnlySpan 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 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 GetVersionList(GameStrings s) - { - var list = s.gamelist; - ReadOnlySpan 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 GetItemDataSource(GameVersion game, int generation, IReadOnlyList allowed, bool HaX = false) - { - var items = Strings.GetItemStrings(generation, game); - return HaX ? Util.GetCBList(items) : Util.GetCBList(items, allowed); - } - - public static IReadOnlyList LanguageDataSource(int gen) - { - var languages = new List(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 LanguageDataSource(int gen) + { + var languages = new List(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; } } diff --git a/PKHeX.Core/Game/GameStrings/GameInfo.cs b/PKHeX.Core/Game/GameStrings/GameInfo.cs index 37453a0b8..e90c049f7 100644 --- a/PKHeX.Core/Game/GameStrings/GameInfo.cs +++ b/PKHeX.Core/Game/GameStrings/GameInfo.cs @@ -1,83 +1,82 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Shared instance for fetching data +/// +public static class GameInfo { - /// - /// Shared instance for fetching data - /// - public static class GameInfo + private static readonly GameStrings?[] Languages = new GameStrings[GameLanguage.LanguageCount]; + + public static string CurrentLanguage { get; set; } = GameLanguage.DefaultLanguage; + public static readonly IReadOnlyList GenderSymbolUnicode = new[] {"♂", "♀", "-"}; + public static readonly IReadOnlyList GenderSymbolASCII = new[] {"M", "F", "-"}; + private static GameStrings _strings = GetStrings(CurrentLanguage); + + public static GameStrings GetStrings(string lang) { - private static readonly GameStrings?[] Languages = new GameStrings[GameLanguage.LanguageCount]; + int index = GameLanguage.GetLanguageIndex(lang); + return GetStrings(index); + } - public static string CurrentLanguage { get; set; } = GameLanguage.DefaultLanguage; - public static readonly IReadOnlyList GenderSymbolUnicode = new[] {"♂", "♀", "-"}; - public static readonly IReadOnlyList GenderSymbolASCII = new[] {"M", "F", "-"}; - private static GameStrings _strings = GetStrings(CurrentLanguage); + public static GameStrings GetStrings(int index) + { + return Languages[index] ??= new GameStrings(GameLanguage.Language2Char(index)); + } - public static GameStrings GetStrings(string lang) - { - int index = GameLanguage.GetLanguageIndex(lang); - return GetStrings(index); - } + public static GameStrings Strings + { + get => _strings; + set => Sources = new GameDataSource(_strings = value); + } - public static GameStrings GetStrings(int index) - { - return Languages[index] ??= new GameStrings(GameLanguage.Language2Char(index)); - } + public static GameDataSource Sources { get; private set; } = new(_strings); + public static FilteredGameDataSource FilteredSources { get; set; } = new(FakeSaveFile.Default, Sources); - public static GameStrings Strings - { - get => _strings; - set => Sources = new GameDataSource(_strings = value); - } + public static string GetVersionName(GameVersion version) + { + var list = (ComboItem[]) VersionDataSource; + var first = System.Array.Find(list, z => z.Value == (int) version); + return first == null ? version.ToString() : first.Text; + } - public static GameDataSource Sources { get; private set; } = new(_strings); - public static FilteredGameDataSource FilteredSources { get; set; } = new(FakeSaveFile.Default, Sources); + // DataSource providing + public static IReadOnlyList ItemDataSource => FilteredSources.Items; + public static IReadOnlyList SpeciesDataSource => Sources.SpeciesDataSource; + public static IReadOnlyList BallDataSource => Sources.BallDataSource; + public static IReadOnlyList NatureDataSource => Sources.NatureDataSource; + public static IReadOnlyList AbilityDataSource => Sources.AbilityDataSource; + public static IReadOnlyList VersionDataSource => Sources.VersionDataSource; + public static IReadOnlyList MoveDataSource => Sources.HaXMoveDataSource; + public static IReadOnlyList GroundTileDataSource => Sources.GroundTileDataSource; + public static IReadOnlyList Regions => GameDataSource.Regions; - public static string GetVersionName(GameVersion version) - { - var list = (ComboItem[]) VersionDataSource; - var first = System.Array.Find(list, z => z.Value == (int) version); - return first == null ? version.ToString() : first.Text; - } + public static IReadOnlyList LanguageDataSource(int gen) => GameDataSource.LanguageDataSource(gen); - // DataSource providing - public static IReadOnlyList ItemDataSource => FilteredSources.Items; - public static IReadOnlyList SpeciesDataSource => Sources.SpeciesDataSource; - public static IReadOnlyList BallDataSource => Sources.BallDataSource; - public static IReadOnlyList NatureDataSource => Sources.NatureDataSource; - public static IReadOnlyList AbilityDataSource => Sources.AbilityDataSource; - public static IReadOnlyList VersionDataSource => Sources.VersionDataSource; - public static IReadOnlyList MoveDataSource => Sources.HaXMoveDataSource; - public static IReadOnlyList GroundTileDataSource => Sources.GroundTileDataSource; - public static IReadOnlyList Regions => GameDataSource.Regions; + /// + /// Gets the location name for the specified parameters. + /// + /// Location is from the + /// Location value + /// Current + /// of origin + /// Current GameVersion (only applicable for differentiation) + /// Location name + public static string GetLocationName(bool isEggLocation, int location, int format, int generation, GameVersion version) + { + return Strings.GetLocationName(isEggLocation, location, format, generation, version); + } - public static IReadOnlyList LanguageDataSource(int gen) => GameDataSource.LanguageDataSource(gen); - - /// - /// Gets the location name for the specified parameters. - /// - /// Location is from the - /// Location value - /// Current - /// of origin - /// Current GameVersion (only applicable for differentiation) - /// Location name - public static string GetLocationName(bool isEggLocation, int location, int format, int generation, GameVersion version) - { - return Strings.GetLocationName(isEggLocation, location, format, generation, version); - } - - /// - /// Gets the location list for a specific version, which can retrieve either met locations or egg locations. - /// - /// Version to retrieve for - /// Current format context - /// Egg Locations are to be retrieved instead of regular Met Locations - /// Consumable list of met locations - public static IReadOnlyList GetLocationList(GameVersion version, EntityContext context, bool egg = false) - { - return Sources.Met.GetLocationList(version, context, egg); - } + /// + /// Gets the location list for a specific version, which can retrieve either met locations or egg locations. + /// + /// Version to retrieve for + /// Current format context + /// Egg Locations are to be retrieved instead of regular Met Locations + /// Consumable list of met locations + public static IReadOnlyList GetLocationList(GameVersion version, EntityContext context, bool egg = false) + { + return Sources.Met.GetLocationList(version, context, egg); } } diff --git a/PKHeX.Core/Game/GameStrings/GameLanguage.cs b/PKHeX.Core/Game/GameStrings/GameLanguage.cs index 61fef2f46..582e8acff 100644 --- a/PKHeX.Core/Game/GameStrings/GameLanguage.cs +++ b/PKHeX.Core/Game/GameStrings/GameLanguage.cs @@ -1,99 +1,98 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Language code & string asset loading. +/// +public static class GameLanguage +{ + public const string DefaultLanguage = "en"; // English + public static int DefaultLanguageIndex => Array.IndexOf(LanguageCodes, DefaultLanguage); + public static string Language2Char(int lang) => lang > LanguageCodes.Length ? DefaultLanguage : LanguageCodes[lang]; + + public static int LanguageCount => LanguageCodes.Length; + + /// + /// Gets the language from the requested 2-char code. + /// + /// 2 character language code + /// Index of the language code; if not a valid language code, returns the . + public static int GetLanguageIndex(string lang) + { + int l = Array.IndexOf(LanguageCodes, lang); + return l < 0 ? DefaultLanguageIndex : l; + } + + /// + /// Language codes supported for loading string resources + /// + /// + private static readonly string[] LanguageCodes = { "ja", "en", "fr", "it", "de", "es", "ko", "zh", "zh2" }; + + /// + /// Pokétransporter location names, ordered per index of + /// + private static readonly string[] ptransp = { "ポケシフター", "Poké Transfer", "Poké Fret", "Pokétrasporto", "Poképorter", "Pokétransfer", "포케시프터", "宝可传送", "寶可傳送" }; + + public static string GetTransporterName(int index) + { + if ((uint)index >= ptransp.Length) + index = 2; + return ptransp[index]; + } + + public static string GetTransporterName(string lang) => GetTransporterName(GetLanguageIndex(lang)); + + public static string[] GetStrings(string ident, string lang, string type = "text") + { + string[] data = Util.GetStringList(ident, lang, type); + if (data.Length == 0) + data = Util.GetStringList(ident, DefaultLanguage, type); + + return data; + } +} + +public enum ProgramLanguage { /// - /// Language code & string asset loading. + /// Japanese /// - public static class GameLanguage - { - public const string DefaultLanguage = "en"; // English - public static int DefaultLanguageIndex => Array.IndexOf(LanguageCodes, DefaultLanguage); - public static string Language2Char(int lang) => lang > LanguageCodes.Length ? DefaultLanguage : LanguageCodes[lang]; + 日本語, - public static int LanguageCount => LanguageCodes.Length; + /// + /// English + /// + English, - /// - /// Gets the language from the requested 2-char code. - /// - /// 2 character language code - /// Index of the language code; if not a valid language code, returns the . - public static int GetLanguageIndex(string lang) - { - int l = Array.IndexOf(LanguageCodes, lang); - return l < 0 ? DefaultLanguageIndex : l; - } + /// + /// French + /// + Français, - /// - /// Language codes supported for loading string resources - /// - /// - private static readonly string[] LanguageCodes = { "ja", "en", "fr", "it", "de", "es", "ko", "zh", "zh2" }; + /// + /// Italian + /// + Italiano, - /// - /// Pokétransporter location names, ordered per index of - /// - private static readonly string[] ptransp = { "ポケシフター", "Poké Transfer", "Poké Fret", "Pokétrasporto", "Poképorter", "Pokétransfer", "포케시프터", "宝可传送", "寶可傳送" }; + /// + /// German + /// + Deutsch, - public static string GetTransporterName(int index) - { - if ((uint)index >= ptransp.Length) - index = 2; - return ptransp[index]; - } + /// + /// Spanish + /// + Español, - public static string GetTransporterName(string lang) => GetTransporterName(GetLanguageIndex(lang)); + /// + /// Korean + /// + 한국어, - public static string[] GetStrings(string ident, string lang, string type = "text") - { - string[] data = Util.GetStringList(ident, lang, type); - if (data.Length == 0) - data = Util.GetStringList(ident, DefaultLanguage, type); - - return data; - } - } - - public enum ProgramLanguage - { - /// - /// Japanese - /// - 日本語, - - /// - /// English - /// - English, - - /// - /// French - /// - Français, - - /// - /// Italian - /// - Italiano, - - /// - /// German - /// - Deutsch, - - /// - /// Spanish - /// - Español, - - /// - /// Korean - /// - 한국어, - - /// - /// Chinese - /// - 中文, - } -} \ No newline at end of file + /// + /// Chinese + /// + 中文, +} diff --git a/PKHeX.Core/Game/GameStrings/GameStrings.cs b/PKHeX.Core/Game/GameStrings/GameStrings.cs index 3e596d774..e579efd58 100644 --- a/PKHeX.Core/Game/GameStrings/GameStrings.cs +++ b/PKHeX.Core/Game/GameStrings/GameStrings.cs @@ -1,558 +1,558 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Repository of localized game strings for a given . +/// +public sealed class GameStrings : IBasicStrings { + // PKM Info + public readonly string[] specieslist, movelist, itemlist, abilitylist, types, natures, forms, + memories, genloc, feeling6, feeling8, intensity, + trainingbags, trainingstage, characteristics, + groundtiletypes, balllist, gamelist, pokeblocks, ribbons; + + private readonly string[] g4items, g3coloitems, g3xditems, g3items, g2items, g1items; + + // Met Locations + public readonly string[] metGSC_00000, metRSEFRLG_00000, metCXD_00000; + public readonly string[] metHGSS_00000, metHGSS_02000, metHGSS_03000; + public readonly string[] metBW2_00000, metBW2_30000, metBW2_40000, metBW2_60000; + public readonly string[] metXY_00000, metXY_30000, metXY_40000, metXY_60000; + public readonly string[] metSM_00000, metSM_30000, metSM_40000, metSM_60000; + public readonly string[] metGG_00000, metGG_30000, metGG_40000, metGG_60000; + public readonly string[] metSWSH_00000, metSWSH_30000, metSWSH_40000, metSWSH_60000; + public readonly string[] metBDSP_00000, metBDSP_30000, metBDSP_40000, metBDSP_60000; + public readonly string[] metLA_00000, metLA_30000, metLA_40000, metLA_60000; + + // Misc + public readonly string[] wallpapernames, puffs, walkercourses; + public readonly string[] uggoods, ugspheres, ugtraps, ugtreasures; + private readonly string lang; + private readonly int LanguageIndex; + + public string EggName { get; } + public IReadOnlyList Species => specieslist; + public IReadOnlyList Item => itemlist; + public IReadOnlyList Move => movelist; + public IReadOnlyList Ability => abilitylist; + public IReadOnlyList Types => types; + public IReadOnlyList Natures => natures; + + private string[] Get(string ident) => GameLanguage.GetStrings(ident, lang); + private const string NPC = "NPC"; + /// - /// Repository of localized game strings for a given . + /// Item IDs that correspond to the value. /// - public sealed class GameStrings : IBasicStrings + private static readonly ushort[] Items_Ball = { - // PKM Info - public readonly string[] specieslist, movelist, itemlist, abilitylist, types, natures, forms, - memories, genloc, feeling6, feeling8, intensity, - trainingbags, trainingstage, characteristics, - groundtiletypes, balllist, gamelist, pokeblocks, ribbons; + 0000, 0001, 0002, 0003, 0004, 0005, 0006, 0007, 0008, 0009, + 0010, 0011, 0012, 0013, 0014, 0015, 0016, 0492, 0493, 0494, + 0495, 0496, 0497, 0498, 0499, 0576, 0851, + 1785, 1710, 1711, + 1712, 1713, 1746, 1747, 1748, 1749, 1750, 1771, + }; - private readonly string[] g4items, g3coloitems, g3xditems, g3items, g2items, g1items; + public GameStrings(string l) + { + lang = l; + LanguageIndex = GameLanguage.GetLanguageIndex(l); + ribbons = Get("ribbons"); - // Met Locations - public readonly string[] metGSC_00000, metRSEFRLG_00000, metCXD_00000; - public readonly string[] metHGSS_00000, metHGSS_02000, metHGSS_03000; - public readonly string[] metBW2_00000, metBW2_30000, metBW2_40000, metBW2_60000; - public readonly string[] metXY_00000, metXY_30000, metXY_40000, metXY_60000; - public readonly string[] metSM_00000, metSM_30000, metSM_40000, metSM_60000; - public readonly string[] metGG_00000, metGG_30000, metGG_40000, metGG_60000; - public readonly string[] metSWSH_00000, metSWSH_30000, metSWSH_40000, metSWSH_60000; - public readonly string[] metBDSP_00000, metBDSP_30000, metBDSP_40000, metBDSP_60000; - public readonly string[] metLA_00000, metLA_30000, metLA_40000, metLA_60000; + // Past Generation strings + g3items = Get("ItemsG3"); + g3coloitems = GetG3CXD(g3items, "ItemsG3Colosseum"); + g3xditems = GetG3CXD(g3items, "ItemsG3XD"); - // Misc - public readonly string[] wallpapernames, puffs, walkercourses; - public readonly string[] uggoods, ugspheres, ugtraps, ugtreasures; - private readonly string lang; - private readonly int LanguageIndex; + g2items = Get("ItemsG2"); + g1items = Get("ItemsG1"); + metRSEFRLG_00000 = Get("rsefrlg_00000"); + metGSC_00000 = Get("gsc_00000"); - public string EggName { get; } - public IReadOnlyList Species => specieslist; - public IReadOnlyList Item => itemlist; - public IReadOnlyList Move => movelist; - public IReadOnlyList Ability => abilitylist; - public IReadOnlyList Types => types; - public IReadOnlyList Natures => natures; + metCXD_00000 = Get("cxd_00000"); + SanitizeMetStringsCXD(metCXD_00000); - private string[] Get(string ident) => GameLanguage.GetStrings(ident, lang); - private const string NPC = "NPC"; + // Current Generation strings + natures = Util.GetNaturesList(l); + types = Get("types"); + abilitylist = Get("abilities"); - /// - /// Item IDs that correspond to the value. - /// - private static readonly ushort[] Items_Ball = + movelist = Get("moves"); + string[] ps = { "P", "S" }; // Distinguish Physical/Special + for (int i = 622; i < 658; i++) + movelist[i] += $" ({ps[i % 2]})"; + + itemlist = Get("items"); + characteristics = Get("character"); + specieslist = Get("species"); + wallpapernames = Get("wallpaper"); + groundtiletypes = Get("groundtile"); + gamelist = Get("games"); + + balllist = new string[Items_Ball.Length]; + for (int i = 0; i < balllist.Length; i++) + balllist[i] = itemlist[Items_Ball[i]]; + + pokeblocks = Get("pokeblock"); + forms = Get("forms"); + memories = Get("memories"); + feeling6 = Get("feeling6"); + feeling8 = Get("feeling"); + intensity = Get("intensity"); + genloc = Get("genloc"); + trainingbags = Get("trainingbag"); + trainingstage = Get("supertraining"); + puffs = Get("puff"); + + walkercourses = Get("hgss_walkercourses"); + + uggoods = Get("dppt_uggoods"); + ugspheres = Get("dppt_ugspheres"); + ugtraps = Get("dppt_ugtraps"); + ugtreasures = Get("dppt_ugtreasures"); + + EggName = specieslist[0]; + metHGSS_00000 = Get("hgss_00000"); + metHGSS_02000 = Get("hgss_02000"); + metHGSS_03000 = Get("hgss_03000"); + metBW2_00000 = Get("bw2_00000"); + metBW2_30000 = Get("bw2_30000"); + metBW2_40000 = Get("bw2_40000"); + metBW2_60000 = Get("bw2_60000"); + metXY_00000 = Get("xy_00000"); + metXY_30000 = Get("xy_30000"); + metXY_40000 = Get("xy_40000"); + metXY_60000 = Get("xy_60000"); + metSM_00000 = Get("sm_00000"); + metSM_30000 = Get("sm_30000"); + metSM_40000 = Get("sm_40000"); + metSM_60000 = Get("sm_60000"); + + metGG_00000 = Get("gg_00000"); + metGG_30000 = metSM_30000; + metGG_40000 = Get("gg_40000"); + metGG_60000 = metSM_60000; + + metSWSH_00000 = Get("swsh_00000"); + metSWSH_30000 = Get("swsh_30000"); + metSWSH_40000 = Get("swsh_40000"); + metSWSH_60000 = Get("swsh_60000"); + + metLA_00000 = Get("la_00000"); + metLA_30000 = Get("la_30000"); + metLA_40000 = Get("la_40000"); + metLA_60000 = Get("la_60000"); + + metBDSP_00000 = Get("bdsp_00000"); + metBDSP_30000 = Get("bdsp_30000"); + metBDSP_40000 = Get("bdsp_40000"); + metBDSP_60000 = Get("bdsp_60000"); + + Sanitize(); + + g4items = (string[])itemlist.Clone(); + Get("mail4").CopyTo(g4items, 137); + } + + private string[] GetG3CXD(string[] arr, string fileName) + { + string[] item500 = Get(fileName); + var result = new string[500 + item500.Length]; + for (int i = arr.Length; i < result.Length; i++) + result[i] = $"UNUSED {i}"; + arr.CopyTo(result, 0); + item500.CopyTo(result, 500); + return result; + } + + private static void SanitizeMetStringsCXD(string[] cxd) + { + // Less than 10% of met location values are unique. + // Just mark them with the ID if they aren't empty. + for (int i = 0; i < 227; i++) { - 0000, 0001, 0002, 0003, 0004, 0005, 0006, 0007, 0008, 0009, - 0010, 0011, 0012, 0013, 0014, 0015, 0016, 0492, 0493, 0494, - 0495, 0496, 0497, 0498, 0499, 0576, 0851, - 1785, 1710, 1711, - 1712, 1713, 1746, 1747, 1748, 1749, 1750, 1771, - }; + var str = cxd[i]; + if (str.Length != 0) + cxd[i] = $"{str} [{i:000}]"; + } + } - public GameStrings(string l) + private void Sanitize() + { + SanitizeItemNames(); + SanitizeMetLocations(); + + // De-duplicate the Calyrex ability names + abilitylist[(int)Core.Ability.AsOneI] += $" ({specieslist[(int)Core.Species.Glastrier]})"; + abilitylist[(int)Core.Ability.AsOneG] += $" ({specieslist[(int)Core.Species.Spectrier]})"; + + // Replace the Egg Name with ---; egg name already stored to eggname + specieslist[0] = "---"; + // Fix (None) tags + var none = $"({itemlist[0]})"; + abilitylist[0] = itemlist[0] = movelist[0] = metXY_00000[0] = metBW2_00000[0] = metHGSS_00000[0] = metCXD_00000[0] = puffs[0] = none; + } + + private void SanitizeItemNames() + { + // Fix Item Names (Duplicate entries) + var HM06 = itemlist[425]; + var HM0 = HM06[..^1]; // language ambiguous! + itemlist[426] = $"{HM0}7 (G4)"; + itemlist[427] = $"{HM0}8 (G4)"; + itemlist[456] += " (HG/SS)"; // S.S. Ticket + itemlist[736] += " (OR/AS)"; // S.S. Ticket + itemlist[463] += " (DPPt)"; // Storage Key + itemlist[734] += " (OR/AS)"; // Storage Key + itemlist[476] += " (HG/SS)"; // Basement Key + itemlist[723] += " (OR/AS)"; // Basement Key + itemlist[621] += " (M)"; // Xtransceiver + itemlist[626] += " (F)"; // Xtransceiver + itemlist[629] += " (2)"; // DNA Splicers + itemlist[637] += " (2)"; // Dropped Item + itemlist[707] += " (2)"; // Travel Trunk + itemlist[713] += " (2)"; // Alt Bike + itemlist[714] += " (2)"; // Holo Caster + itemlist[729] += " (1)"; // Meteorite + itemlist[740] += " (2)"; // Contest Costume + itemlist[751] += " (2)"; // Meteorite + itemlist[771] += " (3)"; // Meteorite + itemlist[772] += " (4)"; // Meteorite + itemlist[842] += " (SM)"; // Fishing Rod + itemlist[945] += " (2)"; // Used Solarizer + itemlist[946] += " (2)"; // Used Lunarizer + + itemlist[873] += " (GP/GE)"; // S.S. Ticket + itemlist[459] += " (HG/SS)"; // Parcel + itemlist[467] += " (Pt)"; // Secret Key + itemlist[475] += " (HG/SS)"; // Card Key + itemlist[894] += " (GP)"; // Leaf Letter + itemlist[895] += " (GE)"; // Leaf Letter + + // some languages have same names for other items! + itemlist[878] += " (GP/GE)"; // Lift Key (Elevator Key=700) + itemlist[479] += " (HG/SS)"; // Lost Item (Dropped Item=636) + + // Append Z-Crystal flagging + foreach (var i in Legal.Pouch_ZCrystal_USUM) + itemlist[i] += " [Z]"; + + itemlist[0121] += " (1)"; // Pokémon Box Link + itemlist[1075] += " (2)"; // Pokémon Box Link + + itemlist[1080] += " (SW/SH)"; // Fishing Rod + + itemlist[1081] += " (1)"; // Rotom Bike + itemlist[1266] += " (2)"; // Rotom Bike + itemlist[1585] += " (3)"; // Rotom Bike + itemlist[1586] += " (4)"; // Rotom Bike + + itemlist[1590] += " (1)"; // Reins of Unity + itemlist[1591] += " (2)"; // Reins of Unity + itemlist[1607] += " (3)"; // Reins of Unity + + for (int i = 12; i <= 29; i++) // Differentiate DNA Samples + g3coloitems[500 + i] += $" ({i - 11:00})"; + // differentiate G3 Card Key from Colo + g3coloitems[500 + 10] += " (COLO)"; + + SanitizeItemsLA(itemlist); + + if (lang is "fr") { - lang = l; - LanguageIndex = GameLanguage.GetLanguageIndex(l); - ribbons = Get("ribbons"); - - // Past Generation strings - g3items = Get("ItemsG3"); - g3coloitems = GetG3CXD(g3items, "ItemsG3Colosseum"); - g3xditems = GetG3CXD(g3items, "ItemsG3XD"); - - g2items = Get("ItemsG2"); - g1items = Get("ItemsG1"); - metRSEFRLG_00000 = Get("rsefrlg_00000"); - metGSC_00000 = Get("gsc_00000"); - - metCXD_00000 = Get("cxd_00000"); - SanitizeMetStringsCXD(metCXD_00000); - - // Current Generation strings - natures = Util.GetNaturesList(l); - types = Get("types"); - abilitylist = Get("abilities"); - - movelist = Get("moves"); - string[] ps = { "P", "S" }; // Distinguish Physical/Special - for (int i = 622; i < 658; i++) - movelist[i] += $" ({ps[i % 2]})"; - - itemlist = Get("items"); - characteristics = Get("character"); - specieslist = Get("species"); - wallpapernames = Get("wallpaper"); - groundtiletypes = Get("groundtile"); - gamelist = Get("games"); - - balllist = new string[Items_Ball.Length]; - for (int i = 0; i < balllist.Length; i++) - balllist[i] = itemlist[Items_Ball[i]]; - - pokeblocks = Get("pokeblock"); - forms = Get("forms"); - memories = Get("memories"); - feeling6 = Get("feeling6"); - feeling8 = Get("feeling"); - intensity = Get("intensity"); - genloc = Get("genloc"); - trainingbags = Get("trainingbag"); - trainingstage = Get("supertraining"); - puffs = Get("puff"); - - walkercourses = Get("hgss_walkercourses"); - - uggoods = Get("dppt_uggoods"); - ugspheres = Get("dppt_ugspheres"); - ugtraps = Get("dppt_ugtraps"); - ugtreasures = Get("dppt_ugtreasures"); - - EggName = specieslist[0]; - metHGSS_00000 = Get("hgss_00000"); - metHGSS_02000 = Get("hgss_02000"); - metHGSS_03000 = Get("hgss_03000"); - metBW2_00000 = Get("bw2_00000"); - metBW2_30000 = Get("bw2_30000"); - metBW2_40000 = Get("bw2_40000"); - metBW2_60000 = Get("bw2_60000"); - metXY_00000 = Get("xy_00000"); - metXY_30000 = Get("xy_30000"); - metXY_40000 = Get("xy_40000"); - metXY_60000 = Get("xy_60000"); - metSM_00000 = Get("sm_00000"); - metSM_30000 = Get("sm_30000"); - metSM_40000 = Get("sm_40000"); - metSM_60000 = Get("sm_60000"); - - metGG_00000 = Get("gg_00000"); - metGG_30000 = metSM_30000; - metGG_40000 = Get("gg_40000"); - metGG_60000 = metSM_60000; - - metSWSH_00000 = Get("swsh_00000"); - metSWSH_30000 = Get("swsh_30000"); - metSWSH_40000 = Get("swsh_40000"); - metSWSH_60000 = Get("swsh_60000"); - - metLA_00000 = Get("la_00000"); - metLA_30000 = Get("la_30000"); - metLA_40000 = Get("la_40000"); - metLA_60000 = Get("la_60000"); - - metBDSP_00000 = Get("bdsp_00000"); - metBDSP_30000 = Get("bdsp_30000"); - metBDSP_40000 = Get("bdsp_40000"); - metBDSP_60000 = Get("bdsp_60000"); - - Sanitize(); - - g4items = (string[])itemlist.Clone(); - Get("mail4").CopyTo(g4items, 137); + itemlist[1681] += " (LA)"; // Galet Noir dup with 617 (Dark Stone | Black Tumblestone) + } + else if (lang is "ja") + { + itemlist[1693] += " (LA)"; // むしよけスプレー dup with 79 (Repel) + itemlist[1716] += " (LA)"; // ビビリだま dup with 847 (Adrenaline Orb | Scatter Bang) + itemlist[1717] += " (LA)"; // けむりだま dup with 228 (Smoke Ball | Smoke Bomb) } - private string[] GetG3CXD(string[] arr, string fileName) + itemlist[464] += " (G4)"; // Secret Medicine + itemlist[1763] += " (LA)"; // Secret Medicine + } + + private static void SanitizeItemsLA(string[] items) + { + // Recipes + items[1784] += " (~)"; // Gigaton Ball + items[1783] += " (~)"; // Leaden Ball + items[1753] += " (~)"; // Heavy Ball + items[1752] += " (~)"; // Jet Ball + items[1751] += " (~)"; // Wing Ball + items[1731] += " (~)"; // Twice-Spiced Radish + items[1730] += " (~)"; // Choice Dumpling + items[1729] += " (~)"; // Swap Snack + items[1677] += " (~)"; // Aux Powerguard + items[1676] += " (~)"; // Aux Evasion + items[1675] += " (~)"; // Dire Hit + items[1674] += " (~)"; // Aux Guard + items[1673] += " (~)"; // Aux Power + items[1671] += " (~)"; // Stealth Spray + items[1670] += " (~)"; // Max Elixir + items[1669] += " (~)"; // Max Ether + items[1668] += " (~)"; // Max Revive + items[1667] += " (~)"; // Revive + items[1666] += " (~)"; // Full Heal + items[1665] += " (~)"; // Jubilife Muffin + items[1664] += " (~)"; // Old Gateau + items[1663] += " (~)"; // Superb Remedy + items[1662] += " (~)"; // Fine Remedy + items[1661] += " (~)"; // Remedy + items[1660] += " (~)"; // Full Restore + items[1659] += " (~)"; // Max Potion + items[1658] += " (~)"; // Hyper Potion + items[1657] += " (~)"; // Super Potion + items[1656] += " (~)"; // Potion + items[1655] += " (~)"; // Salt Cake + items[1654] += " (~)"; // Bean Cake + items[1653] += " (~)"; // Grain Cake + items[1652] += " (~)"; // Honey Cake + items[1650] += " (~)"; // Mushroom Cake + items[1649] += " (~)"; // Star Piece + items[1648] += " (~)"; // Sticky Glob + items[1647] += " (~)"; // Scatter Bang + items[1646] += " (~)"; // Smoke Bomb + items[1644] += " (~)"; // Pokéshi Doll + items[1643] += " (~)"; // Feather Ball + items[1642] += " (~)"; // Ultra Ball + items[1641] += " (~)"; // Great Ball + items[1640] += " (~)"; // Poké Ball + + // Items + items[1616] += " (LA)"; // Dire Hit + items[1689] += " (LA)"; // Snowball + items[1710] += " (LA)"; // Poké Ball + items[1711] += " (LA)"; // Great Ball + items[1712] += " (LA)"; // Ultra Ball + items[1748] += " (LA)"; // Heavy Ball + + // Key Items + items[1622] += " (-)"; // Poké Ball + items[1765] += " (1)"; // Lost Satchel + items[1766] += " (2)"; // Lost Satchel + items[1767] += " (3)"; // Lost Satchel + items[1768] += " (4)"; // Lost Satchel + items[1769] += " (5)"; // Lost Satchel + } + + private void SanitizeMetLocations() + { + // Fix up some of the Location strings to make them more descriptive + SanitizeMetG4HGSS(); + SanitizeMetG5BW(); + SanitizeMetG6XY(); + SanitizeMetG7SM(); + SanitizeMetG8SWSH(); + SanitizeMetG8BDSP(); + SanitizeMetG8PLA(); + + if (lang is "es" or "it") { - string[] item500 = Get(fileName); - var result = new string[500 + item500.Length]; - for (int i = arr.Length; i < result.Length; i++) - result[i] = $"UNUSED {i}"; - arr.CopyTo(result, 0); - item500.CopyTo(result, 500); - return result; + // Campeonato Mundial duplicates + for (int i = 28; i < 35; i++) + metXY_40000[i] += " (-)"; + + // Evento de Videojuegos -- first as duplicate + metXY_40000[35] += " (-)"; + metSM_40000[38] += " (-)"; + metGG_40000[27] += " (-)"; } - private static void SanitizeMetStringsCXD(string[] cxd) + if (lang == "ko") { - // Less than 10% of met location values are unique. - // Just mark them with the ID if they aren't empty. - for (int i = 0; i < 227; i++) - { - var str = cxd[i]; - if (str.Length != 0) - cxd[i] = $"{str} [{i:000}]"; - } + // Pokémon Ranger duplicate (should be Ranger Union) + metBW2_40000[71] += " (-)"; + } + } + + private void SanitizeMetG4HGSS() + { + metHGSS_00000[054] += " (DP/Pt)"; // Victory Road + metHGSS_00000[221] += " (HG/SS)"; // Victory Road + + // German language duplicate; handle for all since it can be confused. + metHGSS_00000[104] += " (DP/Pt)"; // Vista Lighthouse + metHGSS_00000[212] += " (HG/SS)"; // Lighthouse + + metHGSS_02000[1] += $" ({NPC})"; // Anything from an NPC + metHGSS_02000[2] += $" ({EggName})"; // Egg From Link Trade + } + + private void SanitizeMetG5BW() + { + metBW2_00000[36] = $"{metBW2_00000[84]}/{metBW2_00000[36]}"; // Cold Storage in BW = PWT in BW2 + metBW2_00000[40] += " (B/W)"; // Victory Road in BW + metBW2_00000[134] += " (B2/W2)"; // Victory Road in B2W2 + // BW2 Entries from 76 to 105 are for Entralink in BW + for (int i = 76; i < 106; i++) + metBW2_00000[i] += "●"; + + // Collision between 40002 (legal) and 00002 (illegal) "Faraway place" + if (metBW2_00000[2] == metBW2_40000[2]) + metBW2_00000[2] += " (2)"; + + for (int i = 97; i < 109; i++) + metBW2_40000[i] += $" ({i - 97})"; + + // Localize the Poketransfer to the language (30001) + metBW2_30000[1] = GameLanguage.GetTransporterName(LanguageIndex); + metBW2_30000[2] += $" ({NPC})"; // Anything from an NPC + metBW2_30000[3] += $" ({EggName})"; // Link Trade (Egg) + + // Zorua/Zoroark events + metBW2_30000[10] = $"{specieslist[251]} ({specieslist[570]} 1)"; // Celebi's Zorua Event + metBW2_30000[11] = $"{specieslist[251]} ({specieslist[570]} 2)"; // Celebi's Zorua Event + metBW2_30000[12] = $"{specieslist[571]} (1)"; // Zoroark + metBW2_30000[13] = $"{specieslist[571]} (2)"; // Zoroark + + metBW2_60000[3] += $" ({EggName})"; // Egg Treasure Hunter/Breeder, whatever... + } + + private void SanitizeMetG6XY() + { + metXY_00000[104] += " (X/Y)"; // Victory Road + metXY_00000[106] += " (X/Y)"; // Pokémon League + metXY_00000[202] += " (OR/AS)"; // Pokémon League + metXY_00000[298] += " (OR/AS)"; // Victory Road + metXY_30000[1] += $" ({NPC})"; // Anything from an NPC + metXY_30000[2] += $" ({EggName})"; // Egg From Link Trade + + for (int i = 63; i <= 69; i++) + metXY_40000[i] += $" ({i - 62})"; + } + + private void SanitizeMetG7SM() + { + // Sun/Moon duplicates -- elaborate! + for (int i = 6; i < metSM_00000.Length; i += 2) + { + if (i is >= 194 and < 198) + continue; // Skip Island Names (unused) + var nextLoc = metSM_00000[i + 1]; + if (nextLoc.Length == 0) + continue; + metSM_00000[i + 1] = string.Empty; + metSM_00000[i] += $" ({nextLoc})"; + } + metSM_00000[32] += " (2)"; + metSM_00000[102] += " (2)"; + + metSM_30000[1] += $" ({NPC})"; // Anything from an NPC + metSM_30000[2] += $" ({EggName})"; // Egg From Link Trade + for (int i = 3; i <= 6; i++) // distinguish first set of regions (unused) from second (used) + metSM_30000[i] += " (-)"; + + for (int i = 59; i < 66; i++) // distinguish Event year duplicates + metSM_40000[i] += " (-)"; + + for (int i = 48; i < 55; i++) // distinguish Event year duplicates + metGG_40000[i] += " (-)"; + } + + private void SanitizeMetG8SWSH() + { + // SW/SH duplicates -- elaborate! + for (int i = 88; i < metSWSH_00000.Length; i += 2) + { + var nextLoc = metSWSH_00000[i + 1]; + if (nextLoc.Length == 0) + continue; + metSWSH_00000[i + 1] = string.Empty; + metSWSH_00000[i] += $" ({nextLoc})"; } - private void Sanitize() + metSWSH_30000[1] += $" ({NPC})"; // Anything from an NPC + metSWSH_30000[2] += $" ({EggName})"; // Egg From Link Trade + for (int i = 3; i <= 6; i++) // distinguish first set of regions (unused) from second (used) + metSWSH_30000[i] += " (-)"; + metSWSH_30000[19] += " (?)"; // Kanto for the third time + + for (int i = 55; i < 61; i++) // distinguish Event year duplicates + metSWSH_40000[i] += " (-)"; + metSWSH_40000[30] += " (-)"; // a Video game Event (in spanish etc) -- duplicate with line 39 + metSWSH_40000[53] += " (-)"; // a Pokémon event -- duplicate with line 37 + + metSWSH_40000[81] += " (-)"; // Pokémon GO -- duplicate with 30000's entry + metSWSH_40000[86] += " (-)"; // Pokémon HOME -- duplicate with 30000's entry + // metSWSH_30000[12] += " (-)"; // Pokémon GO -- duplicate with 40000's entry + // metSWSH_30000[18] += " (-)"; // Pokémon HOME -- duplicate with 40000's entry + } + + private void SanitizeMetG8BDSP() + { + metBDSP_30000[1] += $" ({NPC})"; // Anything from an NPC + metBDSP_30000[2] += $" ({EggName})"; // Egg From Link Trade + + Deduplicate(metBDSP_00000, 00000); + Deduplicate(metBDSP_30000, 30000); + Deduplicate(metBDSP_40000, 40000); + Deduplicate(metBDSP_60000, 60000); + } + + private void SanitizeMetG8PLA() + { + metLA_00000[31] += " (2)"; // in Floaro Gardens + metLA_30000[1] += $" ({NPC})"; // Anything from an NPC + metLA_30000[2] += $" ({EggName})"; // Egg From Link Trade + for (int i = 3; i <= 6; i++) // distinguish first set of regions (unused) from second (used) + metLA_30000[i] += " (-)"; + metLA_30000[19] += " (?)"; // Kanto for the third time + + metLA_40000[30] += " (-)"; // a Video game Event (in spanish etc) -- duplicate with line 39 + metLA_40000[53] += " (-)"; // a Pokémon event -- duplicate with line 37 + + metLA_40000[81] += " (-)"; // Pokémon GO -- duplicate with 30000's entry + metLA_40000[86] += " (-)"; // Pokémon HOME -- duplicate with 30000's entry + // metLA_30000[12] += " (-)"; // Pokémon GO -- duplicate with 40000's entry + // metLA_30000[18] += " (-)"; // Pokémon HOME -- duplicate with 40000's entry + + for (int i = 55; i <= 60; i++) // distinguish second set of YYYY Event from the first + metLA_40000[i] += " (-)"; + + if (lang is "es") { - SanitizeItemNames(); - SanitizeMetLocations(); - - // De-duplicate the Calyrex ability names - abilitylist[(int)Core.Ability.AsOneI] += $" ({specieslist[(int)Core.Species.Glastrier]})"; - abilitylist[(int)Core.Ability.AsOneG] += $" ({specieslist[(int)Core.Species.Spectrier]})"; - - // Replace the Egg Name with ---; egg name already stored to eggname - specieslist[0] = "---"; - // Fix (None) tags - var none = $"({itemlist[0]})"; - abilitylist[0] = itemlist[0] = movelist[0] = metXY_00000[0] = metBW2_00000[0] = metHGSS_00000[0] = metCXD_00000[0] = puffs[0] = none; + // en un lugar misterioso + metLA_00000[2] += " (2)"; // in a mystery zone + metLA_00000[4] += " (4)"; // in a faraway place } - - private void SanitizeItemNames() + else if (lang is "ja") { - // Fix Item Names (Duplicate entries) - var HM06 = itemlist[425]; - var HM0 = HM06[..^1]; // language ambiguous! - itemlist[426] = $"{HM0}7 (G4)"; - itemlist[427] = $"{HM0}8 (G4)"; - itemlist[456] += " (HG/SS)"; // S.S. Ticket - itemlist[736] += " (OR/AS)"; // S.S. Ticket - itemlist[463] += " (DPPt)"; // Storage Key - itemlist[734] += " (OR/AS)"; // Storage Key - itemlist[476] += " (HG/SS)"; // Basement Key - itemlist[723] += " (OR/AS)"; // Basement Key - itemlist[621] += " (M)"; // Xtransceiver - itemlist[626] += " (F)"; // Xtransceiver - itemlist[629] += " (2)"; // DNA Splicers - itemlist[637] += " (2)"; // Dropped Item - itemlist[707] += " (2)"; // Travel Trunk - itemlist[713] += " (2)"; // Alt Bike - itemlist[714] += " (2)"; // Holo Caster - itemlist[729] += " (1)"; // Meteorite - itemlist[740] += " (2)"; // Contest Costume - itemlist[751] += " (2)"; // Meteorite - itemlist[771] += " (3)"; // Meteorite - itemlist[772] += " (4)"; // Meteorite - itemlist[842] += " (SM)"; // Fishing Rod - itemlist[945] += " (2)"; // Used Solarizer - itemlist[946] += " (2)"; // Used Lunarizer - - itemlist[873] += " (GP/GE)"; // S.S. Ticket - itemlist[459] += " (HG/SS)"; // Parcel - itemlist[467] += " (Pt)"; // Secret Key - itemlist[475] += " (HG/SS)"; // Card Key - itemlist[894] += " (GP)"; // Leaf Letter - itemlist[895] += " (GE)"; // Leaf Letter - - // some languages have same names for other items! - itemlist[878] += " (GP/GE)"; // Lift Key (Elevator Key=700) - itemlist[479] += " (HG/SS)"; // Lost Item (Dropped Item=636) - - // Append Z-Crystal flagging - foreach (var i in Legal.Pouch_ZCrystal_USUM) - itemlist[i] += " [Z]"; - - itemlist[0121] += " (1)"; // Pokémon Box Link - itemlist[1075] += " (2)"; // Pokémon Box Link - - itemlist[1080] += " (SW/SH)"; // Fishing Rod - - itemlist[1081] += " (1)"; // Rotom Bike - itemlist[1266] += " (2)"; // Rotom Bike - itemlist[1585] += " (3)"; // Rotom Bike - itemlist[1586] += " (4)"; // Rotom Bike - - itemlist[1590] += " (1)"; // Reins of Unity - itemlist[1591] += " (2)"; // Reins of Unity - itemlist[1607] += " (3)"; // Reins of Unity - - for (int i = 12; i <= 29; i++) // Differentiate DNA Samples - g3coloitems[500 + i] += $" ({i - 11:00})"; - // differentiate G3 Card Key from Colo - g3coloitems[500 + 10] += " (COLO)"; - - SanitizeItemsLA(itemlist); - - if (lang is "fr") - { - itemlist[1681] += " (LA)"; // Galet Noir dup with 617 (Dark Stone | Black Tumblestone) - } - else if (lang is "ja") - { - itemlist[1693] += " (LA)"; // むしよけスプレー dup with 79 (Repel) - itemlist[1716] += " (LA)"; // ビビリだま dup with 847 (Adrenaline Orb | Scatter Bang) - itemlist[1717] += " (LA)"; // けむりだま dup with 228 (Smoke Ball | Smoke Bomb) - } - - itemlist[464] += " (G4)"; // Secret Medicine - itemlist[1763] += " (LA)"; // Secret Medicine + // ひょうざんのいくさば + metLA_00000[099] += " (099)"; // along the Arena’s Approach + metLA_00000[142] += " (142)"; // at Icepeak Arena } - - private static void SanitizeItemsLA(string[] items) + else if (lang is "fr" or "it") { - // Recipes - items[1784] += " (~)"; // Gigaton Ball - items[1783] += " (~)"; // Leaden Ball - items[1753] += " (~)"; // Heavy Ball - items[1752] += " (~)"; // Jet Ball - items[1751] += " (~)"; // Wing Ball - items[1731] += " (~)"; // Twice-Spiced Radish - items[1730] += " (~)"; // Choice Dumpling - items[1729] += " (~)"; // Swap Snack - items[1677] += " (~)"; // Aux Powerguard - items[1676] += " (~)"; // Aux Evasion - items[1675] += " (~)"; // Dire Hit - items[1674] += " (~)"; // Aux Guard - items[1673] += " (~)"; // Aux Power - items[1671] += " (~)"; // Stealth Spray - items[1670] += " (~)"; // Max Elixir - items[1669] += " (~)"; // Max Ether - items[1668] += " (~)"; // Max Revive - items[1667] += " (~)"; // Revive - items[1666] += " (~)"; // Full Heal - items[1665] += " (~)"; // Jubilife Muffin - items[1664] += " (~)"; // Old Gateau - items[1663] += " (~)"; // Superb Remedy - items[1662] += " (~)"; // Fine Remedy - items[1661] += " (~)"; // Remedy - items[1660] += " (~)"; // Full Restore - items[1659] += " (~)"; // Max Potion - items[1658] += " (~)"; // Hyper Potion - items[1657] += " (~)"; // Super Potion - items[1656] += " (~)"; // Potion - items[1655] += " (~)"; // Salt Cake - items[1654] += " (~)"; // Bean Cake - items[1653] += " (~)"; // Grain Cake - items[1652] += " (~)"; // Honey Cake - items[1650] += " (~)"; // Mushroom Cake - items[1649] += " (~)"; // Star Piece - items[1648] += " (~)"; // Sticky Glob - items[1647] += " (~)"; // Scatter Bang - items[1646] += " (~)"; // Smoke Bomb - items[1644] += " (~)"; // Pokéshi Doll - items[1643] += " (~)"; // Feather Ball - items[1642] += " (~)"; // Ultra Ball - items[1641] += " (~)"; // Great Ball - items[1640] += " (~)"; // Poké Ball - - // Items - items[1616] += " (LA)"; // Dire Hit - items[1689] += " (LA)"; // Snowball - items[1710] += " (LA)"; // Poké Ball - items[1711] += " (LA)"; // Great Ball - items[1712] += " (LA)"; // Ultra Ball - items[1748] += " (LA)"; // Heavy Ball - - // Key Items - items[1622] += " (-)"; // Poké Ball - items[1765] += " (1)"; // Lost Satchel - items[1766] += " (2)"; // Lost Satchel - items[1767] += " (3)"; // Lost Satchel - items[1768] += " (4)"; // Lost Satchel - items[1769] += " (5)"; // Lost Satchel + // Final four locations are not nouns, rather the same location reference (at the...) as prior entries. + metLA_00000[152] += " (152)"; // Galaxy Hall + metLA_00000[153] += " (153)"; // Front Gate + metLA_00000[154] += " (154)"; // Farm + metLA_00000[155] += " (155)"; // Training Grounds } + } - private void SanitizeMetLocations() + private static void Deduplicate(string[] arr, int group) + { + var counts = new Dictionary(); + + foreach (var s in arr) { - // Fix up some of the Location strings to make them more descriptive - SanitizeMetG4HGSS(); - SanitizeMetG5BW(); - SanitizeMetG6XY(); - SanitizeMetG7SM(); - SanitizeMetG8SWSH(); - SanitizeMetG8BDSP(); - SanitizeMetG8PLA(); - - if (lang is "es" or "it") - { - // Campeonato Mundial duplicates - for (int i = 28; i < 35; i++) - metXY_40000[i] += " (-)"; - - // Evento de Videojuegos -- first as duplicate - metXY_40000[35] += " (-)"; - metSM_40000[38] += " (-)"; - metGG_40000[27] += " (-)"; - } - - if (lang == "ko") - { - // Pokémon Ranger duplicate (should be Ranger Union) - metBW2_40000[71] += " (-)"; - } + counts.TryGetValue(s, out var value); + counts[s] = value + 1; } - private void SanitizeMetG4HGSS() - { - metHGSS_00000[054] += " (DP/Pt)"; // Victory Road - metHGSS_00000[221] += " (HG/SS)"; // Victory Road - - // German language duplicate; handle for all since it can be confused. - metHGSS_00000[104] += " (DP/Pt)"; // Vista Lighthouse - metHGSS_00000[212] += " (HG/SS)"; // Lighthouse - - metHGSS_02000[1] += $" ({NPC})"; // Anything from an NPC - metHGSS_02000[2] += $" ({EggName})"; // Egg From Link Trade - } - - private void SanitizeMetG5BW() - { - metBW2_00000[36] = $"{metBW2_00000[84]}/{metBW2_00000[36]}"; // Cold Storage in BW = PWT in BW2 - metBW2_00000[40] += " (B/W)"; // Victory Road in BW - metBW2_00000[134] += " (B2/W2)"; // Victory Road in B2W2 - // BW2 Entries from 76 to 105 are for Entralink in BW - for (int i = 76; i < 106; i++) - metBW2_00000[i] += "●"; - - // Collision between 40002 (legal) and 00002 (illegal) "Faraway place" - if (metBW2_00000[2] == metBW2_40000[2]) - metBW2_00000[2] += " (2)"; - - for (int i = 97; i < 109; i++) - metBW2_40000[i] += $" ({i - 97})"; - - // Localize the Poketransfer to the language (30001) - metBW2_30000[1] = GameLanguage.GetTransporterName(LanguageIndex); - metBW2_30000[2] += $" ({NPC})"; // Anything from an NPC - metBW2_30000[3] += $" ({EggName})"; // Link Trade (Egg) - - // Zorua/Zoroark events - metBW2_30000[10] = $"{specieslist[251]} ({specieslist[570]} 1)"; // Celebi's Zorua Event - metBW2_30000[11] = $"{specieslist[251]} ({specieslist[570]} 2)"; // Celebi's Zorua Event - metBW2_30000[12] = $"{specieslist[571]} (1)"; // Zoroark - metBW2_30000[13] = $"{specieslist[571]} (2)"; // Zoroark - - metBW2_60000[3] += $" ({EggName})"; // Egg Treasure Hunter/Breeder, whatever... - } - - private void SanitizeMetG6XY() - { - metXY_00000[104] += " (X/Y)"; // Victory Road - metXY_00000[106] += " (X/Y)"; // Pokémon League - metXY_00000[202] += " (OR/AS)"; // Pokémon League - metXY_00000[298] += " (OR/AS)"; // Victory Road - metXY_30000[1] += $" ({NPC})"; // Anything from an NPC - metXY_30000[2] += $" ({EggName})"; // Egg From Link Trade - - for (int i = 63; i <= 69; i++) - metXY_40000[i] += $" ({i - 62})"; - } - - private void SanitizeMetG7SM() - { - // Sun/Moon duplicates -- elaborate! - for (int i = 6; i < metSM_00000.Length; i += 2) - { - if (i is >= 194 and < 198) - continue; // Skip Island Names (unused) - var nextLoc = metSM_00000[i + 1]; - if (nextLoc.Length == 0) - continue; - metSM_00000[i + 1] = string.Empty; - metSM_00000[i] += $" ({nextLoc})"; - } - metSM_00000[32] += " (2)"; - metSM_00000[102] += " (2)"; - - metSM_30000[1] += $" ({NPC})"; // Anything from an NPC - metSM_30000[2] += $" ({EggName})"; // Egg From Link Trade - for (int i = 3; i <= 6; i++) // distinguish first set of regions (unused) from second (used) - metSM_30000[i] += " (-)"; - - for (int i = 59; i < 66; i++) // distinguish Event year duplicates - metSM_40000[i] += " (-)"; - - for (int i = 48; i < 55; i++) // distinguish Event year duplicates - metGG_40000[i] += " (-)"; - } - - private void SanitizeMetG8SWSH() - { - // SW/SH duplicates -- elaborate! - for (int i = 88; i < metSWSH_00000.Length; i += 2) - { - var nextLoc = metSWSH_00000[i + 1]; - if (nextLoc.Length == 0) - continue; - metSWSH_00000[i + 1] = string.Empty; - metSWSH_00000[i] += $" ({nextLoc})"; - } - - metSWSH_30000[1] += $" ({NPC})"; // Anything from an NPC - metSWSH_30000[2] += $" ({EggName})"; // Egg From Link Trade - for (int i = 3; i <= 6; i++) // distinguish first set of regions (unused) from second (used) - metSWSH_30000[i] += " (-)"; - metSWSH_30000[19] += " (?)"; // Kanto for the third time - - for (int i = 55; i < 61; i++) // distinguish Event year duplicates - metSWSH_40000[i] += " (-)"; - metSWSH_40000[30] += " (-)"; // a Video game Event (in spanish etc) -- duplicate with line 39 - metSWSH_40000[53] += " (-)"; // a Pokémon event -- duplicate with line 37 - - metSWSH_40000[81] += " (-)"; // Pokémon GO -- duplicate with 30000's entry - metSWSH_40000[86] += " (-)"; // Pokémon HOME -- duplicate with 30000's entry - // metSWSH_30000[12] += " (-)"; // Pokémon GO -- duplicate with 40000's entry - // metSWSH_30000[18] += " (-)"; // Pokémon HOME -- duplicate with 40000's entry - } - - private void SanitizeMetG8BDSP() - { - metBDSP_30000[1] += $" ({NPC})"; // Anything from an NPC - metBDSP_30000[2] += $" ({EggName})"; // Egg From Link Trade - - Deduplicate(metBDSP_00000, 00000); - Deduplicate(metBDSP_30000, 30000); - Deduplicate(metBDSP_40000, 40000); - Deduplicate(metBDSP_60000, 60000); - } - - private void SanitizeMetG8PLA() - { - metLA_00000[31] += " (2)"; // in Floaro Gardens - metLA_30000[1] += $" ({NPC})"; // Anything from an NPC - metLA_30000[2] += $" ({EggName})"; // Egg From Link Trade - for (int i = 3; i <= 6; i++) // distinguish first set of regions (unused) from second (used) - metLA_30000[i] += " (-)"; - metLA_30000[19] += " (?)"; // Kanto for the third time - - metLA_40000[30] += " (-)"; // a Video game Event (in spanish etc) -- duplicate with line 39 - metLA_40000[53] += " (-)"; // a Pokémon event -- duplicate with line 37 - - metLA_40000[81] += " (-)"; // Pokémon GO -- duplicate with 30000's entry - metLA_40000[86] += " (-)"; // Pokémon HOME -- duplicate with 30000's entry - // metLA_30000[12] += " (-)"; // Pokémon GO -- duplicate with 40000's entry - // metLA_30000[18] += " (-)"; // Pokémon HOME -- duplicate with 40000's entry - - for (int i = 55; i <= 60; i++) // distinguish second set of YYYY Event from the first - metLA_40000[i] += " (-)"; - - if (lang is "es") - { - // en un lugar misterioso - metLA_00000[2] += " (2)"; // in a mystery zone - metLA_00000[4] += " (4)"; // in a faraway place - } - else if (lang is "ja") - { - // ひょうざんのいくさば - metLA_00000[099] += " (099)"; // along the Arena’s Approach - metLA_00000[142] += " (142)"; // at Icepeak Arena - } - else if (lang is "fr" or "it") - { - // Final four locations are not nouns, rather the same location reference (at the...) as prior entries. - metLA_00000[152] += " (152)"; // Galaxy Hall - metLA_00000[153] += " (153)"; // Front Gate - metLA_00000[154] += " (154)"; // Farm - metLA_00000[155] += " (155)"; // Training Grounds - } - } - - private static void Deduplicate(string[] arr, int group) - { - var counts = new Dictionary(); - - foreach (var s in arr) - { - counts.TryGetValue(s, out var value); - counts[s] = value + 1; - } - #if !DEBUG var maxCounts = new Dictionary(counts); #endif - for (var i = arr.Length - 1; i >= 0; i--) - { + for (var i = arr.Length - 1; i >= 0; i--) + { #if DEBUG - arr[i] += $" ({group + i:00000})"; + arr[i] += $" ({group + i:00000})"; #else var s = arr[i]; var count = counts[s]--; @@ -566,205 +566,204 @@ private static void Deduplicate(string[] arr, int group) }; arr[i] += string.Format(format, count); #endif - } } - - public string[] GetItemStrings(int generation, GameVersion game = GameVersion.Any) => generation switch - { - 0 => Array.Empty(), - 1 => g1items, - 2 => g2items, - 3 => GetItemStrings3(game), - 4 => g4items, // mail names changed 4->5 - 8 when game is GameVersion.BD or GameVersion.SP or GameVersion.BDSP => GetItemStrings8b(), - _ => itemlist, - }; - - private string[] GetItemStrings8b() - { - // Item Indexes - var clone = (string[])itemlist.Clone(); - var tm = clone[419][..2]; - for (int i = 420; i <= 427; i++) - clone[i] = $"{tm}{i - 420 + 93}"; - - clone[618] += "(-)"; // TM93 - clone[619] += "(-)"; // TM94 - clone[620] += "(-)"; // TM95 - clone[690] += "(-)"; // TM96 - clone[691] += "(-)"; // TM97 - clone[692] += "(-)"; // TM98 - clone[693] += "(-)"; // TM99 - clone[694] += "(-)"; // TM100 - return clone; - } - - private string[] GetItemStrings3(GameVersion game) - { - switch (game) - { - case GameVersion.COLO: - return g3coloitems; - case GameVersion.XD: - return g3xditems; - default: - if (EReaderBerrySettings.IsEnigma) - return g3items; - - var g3ItemsWithEBerry = (string[])g3items.Clone(); - g3ItemsWithEBerry[175] = EReaderBerrySettings.DisplayName; - return g3ItemsWithEBerry; - } - } - - /// - /// Gets the location name for the specified parameters. - /// - /// Location is from the - /// Location value - /// Current - /// of origin - /// Current GameVersion (only applicable for differentiation) - /// Location name. May be an empty string if no location name is known for that location value. - public string GetLocationName(bool isEggLocation, int location, int format, int generation, GameVersion version) - { - int gen = -1; - int bankID = 0; - - if (format == 1) - { - if (location == 0) - return string.Empty; - format = 3; // Legality binaries have Location IDs that were manually remapped to Gen3 location IDs. - } - - if (format == 2) - { - gen = 2; - } - else if (format == 3) - { - gen = 3; - } - else if (generation == 4 && (isEggLocation || format == 4)) // 4 - { - const int size = 1000; - bankID = location / size; - gen = 4; - location %= size; - } - else // 5-7+ - { - const int size = 10000; - bankID = location / size; - - int g = generation; - if (g >= 5) - gen = g; - else if (format >= 5) - gen = format; - - location %= size; - } - - var bank = GetLocationNames(gen, version, bankID); - if ((uint)location >= bank.Count) - return string.Empty; - return bank[location]; - } - - /// - /// Gets the location names array for a specified generation. - /// - /// Generation to get location names for. - /// Version of origin - /// BankID used to choose the text bank. - /// List of location names. - public IReadOnlyList GetLocationNames(int gen, GameVersion version, int bankID = 0) => gen switch - { - 2 => metGSC_00000, - 3 => GameVersion.CXD.Contains(version) ? metCXD_00000 : metRSEFRLG_00000, - 4 => GetLocationNames4(bankID), - 5 => GetLocationNames5(bankID), - 6 => GetLocationNames6(bankID), - 7 => GameVersion.Gen7b.Contains(version) ? GetLocationNames7GG(bankID) : GetLocationNames7(bankID), - - 8 when version is GameVersion.PLA => GetLocationNames8a(bankID), - 8 when GameVersion.BDSP.Contains(version) => GetLocationNames8b(bankID), - 8 => GetLocationNames8(bankID), - - _ => Array.Empty(), - }; - - private IReadOnlyList GetLocationNames4(int bankID) => bankID switch - { - 0 => metHGSS_00000, - 2 => metHGSS_02000, - 3 => metHGSS_03000, - _ => Array.Empty(), - }; - - public IReadOnlyList GetLocationNames5(int bankID) => bankID switch - { - 0 => metBW2_00000, - 3 => metBW2_30000, - 4 => metBW2_40000, - 6 => metBW2_60000, - _ => Array.Empty(), - }; - - public IReadOnlyList GetLocationNames6(int bankID) => bankID switch - { - 0 => metXY_00000, - 3 => metXY_30000, - 4 => metXY_40000, - 6 => metXY_60000, - _ => Array.Empty(), - }; - - public IReadOnlyList GetLocationNames7(int bankID) => bankID switch - { - 0 => metSM_00000, - 3 => metSM_30000, - 4 => metSM_40000, - 6 => metSM_60000, - _ => Array.Empty(), - }; - - public IReadOnlyList GetLocationNames7GG(int bankID) => bankID switch - { - 0 => metGG_00000, - 3 => metGG_30000, - 4 => metGG_40000, - 6 => metGG_60000, - _ => Array.Empty(), - }; - - public IReadOnlyList GetLocationNames8(int bankID) => bankID switch - { - 0 => metSWSH_00000, - 3 => metSWSH_30000, - 4 => metSWSH_40000, - 6 => metSWSH_60000, - _ => Array.Empty(), - }; - - public IReadOnlyList GetLocationNames8a(int bankID) => bankID switch - { - 0 => metLA_00000, - 3 => metLA_30000, - 4 => metLA_40000, - 6 => metLA_60000, - _ => Array.Empty(), - }; - - public IReadOnlyList GetLocationNames8b(int bankID) => bankID switch - { - 0 => metBDSP_00000, - 3 => metBDSP_30000, - 4 => metBDSP_40000, - 6 => metBDSP_60000, - _ => Array.Empty(), - }; } + + public string[] GetItemStrings(int generation, GameVersion game = GameVersion.Any) => generation switch + { + 0 => Array.Empty(), + 1 => g1items, + 2 => g2items, + 3 => GetItemStrings3(game), + 4 => g4items, // mail names changed 4->5 + 8 when game is GameVersion.BD or GameVersion.SP or GameVersion.BDSP => GetItemStrings8b(), + _ => itemlist, + }; + + private string[] GetItemStrings8b() + { + // Item Indexes + var clone = (string[])itemlist.Clone(); + var tm = clone[419][..2]; + for (int i = 420; i <= 427; i++) + clone[i] = $"{tm}{i - 420 + 93}"; + + clone[618] += "(-)"; // TM93 + clone[619] += "(-)"; // TM94 + clone[620] += "(-)"; // TM95 + clone[690] += "(-)"; // TM96 + clone[691] += "(-)"; // TM97 + clone[692] += "(-)"; // TM98 + clone[693] += "(-)"; // TM99 + clone[694] += "(-)"; // TM100 + return clone; + } + + private string[] GetItemStrings3(GameVersion game) + { + switch (game) + { + case GameVersion.COLO: + return g3coloitems; + case GameVersion.XD: + return g3xditems; + default: + if (EReaderBerrySettings.IsEnigma) + return g3items; + + var g3ItemsWithEBerry = (string[])g3items.Clone(); + g3ItemsWithEBerry[175] = EReaderBerrySettings.DisplayName; + return g3ItemsWithEBerry; + } + } + + /// + /// Gets the location name for the specified parameters. + /// + /// Location is from the + /// Location value + /// Current + /// of origin + /// Current GameVersion (only applicable for differentiation) + /// Location name. May be an empty string if no location name is known for that location value. + public string GetLocationName(bool isEggLocation, int location, int format, int generation, GameVersion version) + { + int gen = -1; + int bankID = 0; + + if (format == 1) + { + if (location == 0) + return string.Empty; + format = 3; // Legality binaries have Location IDs that were manually remapped to Gen3 location IDs. + } + + if (format == 2) + { + gen = 2; + } + else if (format == 3) + { + gen = 3; + } + else if (generation == 4 && (isEggLocation || format == 4)) // 4 + { + const int size = 1000; + bankID = location / size; + gen = 4; + location %= size; + } + else // 5-7+ + { + const int size = 10000; + bankID = location / size; + + int g = generation; + if (g >= 5) + gen = g; + else if (format >= 5) + gen = format; + + location %= size; + } + + var bank = GetLocationNames(gen, version, bankID); + if ((uint)location >= bank.Count) + return string.Empty; + return bank[location]; + } + + /// + /// Gets the location names array for a specified generation. + /// + /// Generation to get location names for. + /// Version of origin + /// BankID used to choose the text bank. + /// List of location names. + public IReadOnlyList GetLocationNames(int gen, GameVersion version, int bankID = 0) => gen switch + { + 2 => metGSC_00000, + 3 => GameVersion.CXD.Contains(version) ? metCXD_00000 : metRSEFRLG_00000, + 4 => GetLocationNames4(bankID), + 5 => GetLocationNames5(bankID), + 6 => GetLocationNames6(bankID), + 7 => GameVersion.Gen7b.Contains(version) ? GetLocationNames7GG(bankID) : GetLocationNames7(bankID), + + 8 when version is GameVersion.PLA => GetLocationNames8a(bankID), + 8 when GameVersion.BDSP.Contains(version) => GetLocationNames8b(bankID), + 8 => GetLocationNames8(bankID), + + _ => Array.Empty(), + }; + + private IReadOnlyList GetLocationNames4(int bankID) => bankID switch + { + 0 => metHGSS_00000, + 2 => metHGSS_02000, + 3 => metHGSS_03000, + _ => Array.Empty(), + }; + + public IReadOnlyList GetLocationNames5(int bankID) => bankID switch + { + 0 => metBW2_00000, + 3 => metBW2_30000, + 4 => metBW2_40000, + 6 => metBW2_60000, + _ => Array.Empty(), + }; + + public IReadOnlyList GetLocationNames6(int bankID) => bankID switch + { + 0 => metXY_00000, + 3 => metXY_30000, + 4 => metXY_40000, + 6 => metXY_60000, + _ => Array.Empty(), + }; + + public IReadOnlyList GetLocationNames7(int bankID) => bankID switch + { + 0 => metSM_00000, + 3 => metSM_30000, + 4 => metSM_40000, + 6 => metSM_60000, + _ => Array.Empty(), + }; + + public IReadOnlyList GetLocationNames7GG(int bankID) => bankID switch + { + 0 => metGG_00000, + 3 => metGG_30000, + 4 => metGG_40000, + 6 => metGG_60000, + _ => Array.Empty(), + }; + + public IReadOnlyList GetLocationNames8(int bankID) => bankID switch + { + 0 => metSWSH_00000, + 3 => metSWSH_30000, + 4 => metSWSH_40000, + 6 => metSWSH_60000, + _ => Array.Empty(), + }; + + public IReadOnlyList GetLocationNames8a(int bankID) => bankID switch + { + 0 => metLA_00000, + 3 => metLA_30000, + 4 => metLA_40000, + 6 => metLA_60000, + _ => Array.Empty(), + }; + + public IReadOnlyList GetLocationNames8b(int bankID) => bankID switch + { + 0 => metBDSP_00000, + 3 => metBDSP_30000, + 4 => metBDSP_40000, + 6 => metBDSP_60000, + _ => Array.Empty(), + }; } diff --git a/PKHeX.Core/Game/GameStrings/GeoLocation.cs b/PKHeX.Core/Game/GameStrings/GeoLocation.cs index f9e6cbea1..73e912304 100644 --- a/PKHeX.Core/Game/GameStrings/GeoLocation.cs +++ b/PKHeX.Core/Game/GameStrings/GeoLocation.cs @@ -1,140 +1,139 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Geolocation Utility for Generation 6/7 (3DS) Earth location values. +/// +public static class GeoLocation { + private static readonly string[]?[] CountryList = GetCountryList(); + internal static readonly string[] lang_geo = { "ja", "en", "fr", "de", "it", "es", "zh", "ko" }; + private static readonly string[]?[]?[] RegionList = new string[CountryList.Length][][]; + /// - /// Geolocation Utility for Generation 6/7 (3DS) Earth location values. + /// Returns the index of which the is in the country/region list. /// - public static class GeoLocation + public static int GetLanguageIndex(string language) => Array.IndexOf(lang_geo, language); + private static int GetLanguageIndex(LanguageID language) => GetLanguageIndex(language.GetLanguage2CharName()); + + private const string INVALID = nameof(INVALID); + + private static string[]?[] GetCountryList() { - private static readonly string[]?[] CountryList = GetCountryList(); - internal static readonly string[] lang_geo = { "ja", "en", "fr", "de", "it", "es", "zh", "ko" }; - private static readonly string[]?[]?[] RegionList = new string[CountryList.Length][][]; + var input = Util.GetStringList("countries"); + return UnpackList(input); + } - /// - /// Returns the index of which the is in the country/region list. - /// - public static int GetLanguageIndex(string language) => Array.IndexOf(lang_geo, language); - private static int GetLanguageIndex(LanguageID language) => GetLanguageIndex(language.GetLanguage2CharName()); + private static string[]?[] GetRegionList(int country) + { + var input = Util.GetStringList($"sr_{country:000}"); + return UnpackList(input); + } - private const string INVALID = nameof(INVALID); - - private static string[]?[] GetCountryList() + private static string[]?[] UnpackList(string[] input) + { + var last = GetEntry(input[^1], out var lastIndex); + string[]?[] list = new string[lastIndex+1][]; + list[lastIndex] = last; + foreach (var line in input) { - var input = Util.GetStringList("countries"); - return UnpackList(input); + var entry = GetEntry(line, out var index); + list[index] = entry; } + return list; + } - private static string[]?[] GetRegionList(int country) - { - var input = Util.GetStringList($"sr_{country:000}"); - return UnpackList(input); - } + private static string[] GetEntry(string line, out int index) + { + var entries = line.Split(','); + index = int.Parse(entries[0]); + return entries; + } - private static string[]?[] UnpackList(string[] input) - { - var last = GetEntry(input[^1], out var lastIndex); - string[]?[] list = new string[lastIndex+1][]; - list[lastIndex] = last; - foreach (var line in input) - { - var entry = GetEntry(line, out var index); - list[index] = entry; - } - return list; - } - - private static string[] GetEntry(string line, out int index) - { - var entries = line.Split(','); - index = int.Parse(entries[0]); - return entries; - } - - private static string GetCountryName(int country, int l) - { - if (l < 0) - return INVALID; - if ((uint)country >= CountryList.Length) - return INVALID; - var countryNames = CountryList[country]; - if (countryNames is not null && l < countryNames.Length) - return countryNames[l + 1]; + private static string GetCountryName(int country, int l) + { + if (l < 0) return INVALID; - } - - private static string GetRegionName(int country, int region, int l) - { - if (l < 0) - return INVALID; - if ((uint)country >= RegionList.Length) - return INVALID; - var regionNames = RegionList[country] ??= GetRegionList(country); - if ((uint)region >= regionNames.Length) - return INVALID; - var localized = regionNames[region]; - if (localized is not null && l < localized.Length) - return localized[l + 1]; + if ((uint)country >= CountryList.Length) return INVALID; - } + var countryNames = CountryList[country]; + if (countryNames is not null && l < countryNames.Length) + return countryNames[l + 1]; + return INVALID; + } - /// - /// Gets an array of all country names for the requested . - /// - public static string[]? GetCountryList(string language) - { - int index = GetLanguageIndex(language); - return CountryList[index]; - } + private static string GetRegionName(int country, int region, int l) + { + if (l < 0) + return INVALID; + if ((uint)country >= RegionList.Length) + return INVALID; + var regionNames = RegionList[country] ??= GetRegionList(country); + if ((uint)region >= regionNames.Length) + return INVALID; + var localized = regionNames[region]; + if (localized is not null && l < localized.Length) + return localized[l + 1]; + return INVALID; + } - /// - /// Gets the Country string for a given Country ID - /// - /// Language ID - /// Country ID - /// Country ID string - public static string GetCountryName(string language, int country) => GetCountryName(country, GetLanguageIndex(language)); + /// + /// Gets an array of all country names for the requested . + /// + public static string[]? GetCountryList(string language) + { + int index = GetLanguageIndex(language); + return CountryList[index]; + } - /// - /// Gets the Region string for a specified country ID. - /// - /// Language ID - /// Country ID - /// Region ID - /// Region ID string - public static string GetRegionName(string language, int country, int region) => GetRegionName(country, region, GetLanguageIndex(language)); + /// + /// Gets the Country string for a given Country ID + /// + /// Language ID + /// Country ID + /// Country ID string + public static string GetCountryName(string language, int country) => GetCountryName(country, GetLanguageIndex(language)); - /// - /// Gets the Country string for a given Country ID - /// - /// Language ID - /// Country ID - /// Country ID string - public static string GetCountryName(LanguageID language, int country) => GetCountryName(country, GetLanguageIndex(language)); + /// + /// Gets the Region string for a specified country ID. + /// + /// Language ID + /// Country ID + /// Region ID + /// Region ID string + public static string GetRegionName(string language, int country, int region) => GetRegionName(country, region, GetLanguageIndex(language)); - /// - /// Gets the Region string for a specified country ID. - /// - /// Language ID - /// Country ID - /// Region ID - /// Region ID string - public static string GetRegionName(LanguageID language, int country, int region) => GetRegionName(country, region, GetLanguageIndex(language)); + /// + /// Gets the Country string for a given Country ID + /// + /// Language ID + /// Country ID + /// Country ID string + public static string GetCountryName(LanguageID language, int country) => GetCountryName(country, GetLanguageIndex(language)); - /// - /// Gets Country and Region strings for corresponding IDs and language. - /// - /// Country ID - /// Region ID - /// Language ID - /// Tuple containing country and region - public static (string Country, string Region) GetCountryRegionText(int country, int region, string language) - { - // Get Language we're fetching for - int lang = Array.IndexOf(lang_geo, language); - var countryName = GetCountryName(country, lang); - var regionName = GetRegionName(country, region, lang); - return (countryName, regionName); - } + /// + /// Gets the Region string for a specified country ID. + /// + /// Language ID + /// Country ID + /// Region ID + /// Region ID string + public static string GetRegionName(LanguageID language, int country, int region) => GetRegionName(country, region, GetLanguageIndex(language)); + + /// + /// Gets Country and Region strings for corresponding IDs and language. + /// + /// Country ID + /// Region ID + /// Language ID + /// Tuple containing country and region + public static (string Country, string Region) GetCountryRegionText(int country, int region, string language) + { + // Get Language we're fetching for + int lang = Array.IndexOf(lang_geo, language); + var countryName = GetCountryName(country, lang); + var regionName = GetRegionName(country, region, lang); + return (countryName, regionName); } } diff --git a/PKHeX.Core/Game/GameStrings/MemoryArgType.cs b/PKHeX.Core/Game/GameStrings/MemoryArgType.cs index 574da5c57..5397a8547 100644 --- a/PKHeX.Core/Game/GameStrings/MemoryArgType.cs +++ b/PKHeX.Core/Game/GameStrings/MemoryArgType.cs @@ -1,12 +1,11 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum MemoryArgType : byte { - public enum MemoryArgType : byte - { - None, - GeneralLocation, - SpecificLocation, - Species, - Move, - Item, - } -} \ No newline at end of file + None, + GeneralLocation, + SpecificLocation, + Species, + Move, + Item, +} diff --git a/PKHeX.Core/Game/GameStrings/MemoryStrings.cs b/PKHeX.Core/Game/GameStrings/MemoryStrings.cs index 134417378..1cfd1930f 100644 --- a/PKHeX.Core/Game/GameStrings/MemoryStrings.cs +++ b/PKHeX.Core/Game/GameStrings/MemoryStrings.cs @@ -2,69 +2,68 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Localizing Memory strings +/// +/// +/// and parameters build strings. +/// +public sealed class MemoryStrings { - /// - /// Localizing Memory strings - /// - /// - /// and parameters build strings. - /// - public sealed class MemoryStrings + private readonly GameStrings s; + + public MemoryStrings(GameStrings strings) { - private readonly GameStrings s; - - public MemoryStrings(GameStrings strings) - { - s = strings; - memories = new Lazy>(GetMemories); - none = new Lazy>(() => Util.GetCBList(new[] {string.Empty})); - species = new Lazy>(() => Util.GetCBList(s.specieslist)); - item6 = new Lazy>(() => GetItems(6)); - item8 = new Lazy>(() => GetItems(8)); - genloc = new Lazy>(() => Util.GetCBList(s.genloc)); - moves = new Lazy>(() => Util.GetCBList(s.movelist)); - specific = new Lazy>(() => Util.GetCBList(s.metXY_00000, Locations6.Met0)); - } - - private List GetItems(int memoryGen) - { - var permit = Memories.GetMemoryItemParams(memoryGen); - var asInt = permit.ToArray(); - return Util.GetCBList(s.itemlist, asInt); - } - - private readonly Lazy> memories; - private readonly Lazy> none, species, item6, item8, genloc, moves, specific; - - public List Memory => memories.Value; - public List None => none.Value; - public List Moves => moves.Value; - public List Items6 => item6.Value; - public List Items8 => item8.Value; - public List GeneralLocations => genloc.Value; - public List SpecificLocations => specific.Value; - public List Species => species.Value; - - private List GetMemories() - { - var mems = s.memories.AsSpan(0); - var list = new List {new(mems[0], 0)}; // none at top - Util.AddCBWithOffset(list, mems[1..], 1); // sorted the rest - return list; - } - - public ReadOnlySpan GetMemoryQualities() => s.intensity; - public ReadOnlySpan GetMemoryFeelings(int memoryGen) => memoryGen >= 8 ? s.feeling8 : s.feeling6; - - public List GetArgumentStrings(MemoryArgType type, int memoryGen) => type switch - { - MemoryArgType.Species => Species, - MemoryArgType.GeneralLocation => GeneralLocations, - MemoryArgType.Item => memoryGen == 6 ? Items6 : Items8, - MemoryArgType.Move => Moves, - MemoryArgType.SpecificLocation => SpecificLocations, - _ => None, - }; + s = strings; + memories = new Lazy>(GetMemories); + none = new Lazy>(() => Util.GetCBList(new[] {string.Empty})); + species = new Lazy>(() => Util.GetCBList(s.specieslist)); + item6 = new Lazy>(() => GetItems(6)); + item8 = new Lazy>(() => GetItems(8)); + genloc = new Lazy>(() => Util.GetCBList(s.genloc)); + moves = new Lazy>(() => Util.GetCBList(s.movelist)); + specific = new Lazy>(() => Util.GetCBList(s.metXY_00000, Locations6.Met0)); } + + private List GetItems(int memoryGen) + { + var permit = Memories.GetMemoryItemParams(memoryGen); + var asInt = permit.ToArray(); + return Util.GetCBList(s.itemlist, asInt); + } + + private readonly Lazy> memories; + private readonly Lazy> none, species, item6, item8, genloc, moves, specific; + + public List Memory => memories.Value; + public List None => none.Value; + public List Moves => moves.Value; + public List Items6 => item6.Value; + public List Items8 => item8.Value; + public List GeneralLocations => genloc.Value; + public List SpecificLocations => specific.Value; + public List Species => species.Value; + + private List GetMemories() + { + var mems = s.memories.AsSpan(0); + var list = new List {new(mems[0], 0)}; // none at top + Util.AddCBWithOffset(list, mems[1..], 1); // sorted the rest + return list; + } + + public ReadOnlySpan GetMemoryQualities() => s.intensity; + public ReadOnlySpan GetMemoryFeelings(int memoryGen) => memoryGen >= 8 ? s.feeling8 : s.feeling6; + + public List GetArgumentStrings(MemoryArgType type, int memoryGen) => type switch + { + MemoryArgType.Species => Species, + MemoryArgType.GeneralLocation => GeneralLocations, + MemoryArgType.Item => memoryGen == 6 ? Items6 : Items8, + MemoryArgType.Move => Moves, + MemoryArgType.SpecificLocation => SpecificLocations, + _ => None, + }; } diff --git a/PKHeX.Core/Game/GameStrings/MetDataSource.cs b/PKHeX.Core/Game/GameStrings/MetDataSource.cs index ce321af3e..d48f7b15d 100644 --- a/PKHeX.Core/Game/GameStrings/MetDataSource.cs +++ b/PKHeX.Core/Game/GameStrings/MetDataSource.cs @@ -2,300 +2,299 @@ using System.Collections.Generic; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Cached copies of Met Location lists +/// +public sealed class MetDataSource { - /// - /// Cached copies of Met Location lists - /// - public sealed class MetDataSource + private readonly List MetGen2; + private readonly List MetGen3; + private readonly List MetGen3CXD; + private readonly List MetGen4; + private readonly List MetGen5; + private readonly List MetGen6; + private readonly List MetGen7; + private readonly List MetGen7GG; + private readonly List MetGen8; + private readonly List MetGen8a; + private readonly List MetGen8b; + + private IReadOnlyList? MetGen4Transfer; + private IReadOnlyList? MetGen5Transfer; + + public MetDataSource(GameStrings s) { - private readonly List MetGen2; - private readonly List MetGen3; - private readonly List MetGen3CXD; - private readonly List MetGen4; - private readonly List MetGen5; - private readonly List MetGen6; - private readonly List MetGen7; - private readonly List MetGen7GG; - private readonly List MetGen8; - private readonly List MetGen8a; - private readonly List MetGen8b; + MetGen2 = CreateGen2(s); + MetGen3 = CreateGen3(s); + MetGen3CXD = CreateGen3CXD(s); + MetGen4 = CreateGen4(s); + MetGen5 = CreateGen5(s); + MetGen6 = CreateGen6(s); + MetGen7 = CreateGen7(s); + MetGen7GG = CreateGen7GG(s); + MetGen8 = CreateGen8(s); + MetGen8a = CreateGen8a(s); + MetGen8b = CreateGen8b(s); + } - private IReadOnlyList? MetGen4Transfer; - private IReadOnlyList? MetGen5Transfer; + private static List CreateGen2(GameStrings s) + { + var locations = Util.GetCBList(s.metGSC_00000.AsSpan(0, 0x5F)); + Util.AddCBWithOffset(locations, s.metGSC_00000.AsSpan(0x7E, 2), 0x7E); + return locations; + } - public MetDataSource(GameStrings s) + private static List CreateGen3(GameStrings s) + { + var locations = Util.GetCBList(s.metRSEFRLG_00000.AsSpan(0, 213)); + Util.AddCBWithOffset(locations, s.metRSEFRLG_00000.AsSpan(253, 3), 253); + return locations; + } + + private static List CreateGen3CXD(GameStrings s) + { + var list = Util.GetCBList(s.metCXD_00000); + list.RemoveAll(z => z.Text.Length == 0); + return list; + } + + private static List CreateGen4(GameStrings s) + { + var locations = Util.GetCBList(s.metHGSS_00000, 0); + Util.AddCBWithOffset(locations, s.metHGSS_02000, 2000, Locations.Daycare4); + Util.AddCBWithOffset(locations, s.metHGSS_02000, 2000, Locations.LinkTrade4); + Util.AddCBWithOffset(locations, s.metHGSS_03000, 3000, Locations.Ranger4); + Util.AddCBWithOffset(locations, s.metHGSS_00000, 0000, Locations4.Met0); + Util.AddCBWithOffset(locations, s.metHGSS_02000, 2000, Locations4.Met2); + Util.AddCBWithOffset(locations, s.metHGSS_03000, 3000, Locations4.Met3); + return locations; + } + + private IReadOnlyList CreateGen4Transfer() + { + // Pal Park to front + var met = MetGen4.ToArray(); + var index = Array.FindIndex(met, z => z.Value == Locations.Transfer3); + var pal = met[index]; + Array.Copy(met, 0, met, 1, index); + met[0] = pal; + return met; + } + + private static List CreateGen5(GameStrings s) + { + var locations = Util.GetCBList(s.metBW2_00000, 0); + Util.AddCBWithOffset(locations, s.metBW2_60000, 60000, Locations.Daycare5); + Util.AddCBWithOffset(locations, s.metBW2_30000, 30000, Locations.LinkTrade5); + Util.AddCBWithOffset(locations, s.metBW2_00000, 00000, Locations5.Met0); + Util.AddCBWithOffset(locations, s.metBW2_30000, 30000, Locations5.Met3); + Util.AddCBWithOffset(locations, s.metBW2_40000, 40000, Locations5.Met4); + Util.AddCBWithOffset(locations, s.metBW2_60000, 60000, Locations5.Met6); + return locations; + } + + private IReadOnlyList CreateGen5Transfer() + { + // PokéTransfer to front + var met = MetGen5.ToArray(); + var index = Array.FindIndex(met, z => z.Value == Locations.Transfer4); + var xfr = met[index]; + Array.Copy(met, 0, met, 1, index); + met[0] = xfr; + return met; + } + + private static List CreateGen6(GameStrings s) + { + var locations = Util.GetCBList(s.metXY_00000, 0); + Util.AddCBWithOffset(locations, s.metXY_60000, 60000, Locations.Daycare5); + Util.AddCBWithOffset(locations, s.metXY_30000, 30000, Locations.LinkTrade6); + Util.AddCBWithOffset(locations, s.metXY_00000, 00000, Locations6.Met0); + Util.AddCBWithOffset(locations, s.metXY_30000, 30000, Locations6.Met3); + Util.AddCBWithOffset(locations, s.metXY_40000, 40000, Locations6.Met4); + Util.AddCBWithOffset(locations, s.metXY_60000, 60000, Locations6.Met6); + return locations; + } + + private static List CreateGen7(GameStrings s) + { + var locations = Util.GetCBList(s.metSM_00000, 0); + Util.AddCBWithOffset(locations, s.metSM_60000, 60000, Locations.Daycare5); + Util.AddCBWithOffset(locations, s.metSM_30000, 30000, Locations.LinkTrade6); + Util.AddCBWithOffset(locations, s.metSM_00000, 00000, Locations7.Met0); + Util.AddCBWithOffset(locations, s.metSM_30000, 30000, Locations7.Met3); + Util.AddCBWithOffset(locations, s.metSM_40000, 40000, Locations7.Met4); + Util.AddCBWithOffset(locations, s.metSM_60000, 60000, Locations7.Met6); + return locations; + } + + private static List CreateGen7GG(GameStrings s) + { + var locations = Util.GetCBList(s.metGG_00000, 0); + Util.AddCBWithOffset(locations, s.metGG_60000, 60000, Locations.Daycare5); + Util.AddCBWithOffset(locations, s.metGG_30000, 30000, Locations.LinkTrade6); + Util.AddCBWithOffset(locations, s.metGG_00000, 00000, Locations7b.Met0); + Util.AddCBWithOffset(locations, s.metGG_30000, 30000, Locations7b.Met3); + Util.AddCBWithOffset(locations, s.metGG_40000, 40000, Locations7b.Met4); + Util.AddCBWithOffset(locations, s.metGG_60000, 60000, Locations7b.Met6); + return locations; + } + + private static List CreateGen8(GameStrings s) + { + var locations = Util.GetCBList(s.metSWSH_00000, 0); + Util.AddCBWithOffset(locations, s.metSWSH_60000, 60000, Locations.Daycare5); + Util.AddCBWithOffset(locations, s.metSWSH_30000, 30000, Locations.LinkTrade6); + Util.AddCBWithOffset(locations, s.metSWSH_00000, 00000, Locations8.Met0); + Util.AddCBWithOffset(locations, s.metSWSH_30000, 30000, Locations8.Met3); + Util.AddCBWithOffset(locations, s.metSWSH_40000, 40000, Locations8.Met4); + Util.AddCBWithOffset(locations, s.metSWSH_60000, 60000, Locations8.Met6); + + // Add in the BDSP+PLA magic met locations. + locations.Add(new ComboItem($"{s.EggName} (BD/SP)", Locations.HOME_SWSHBDSPEgg)); + locations.Add(new ComboItem(s.gamelist[(int)BD], Locations.HOME_SWBD)); + locations.Add(new ComboItem(s.gamelist[(int)SP], Locations.HOME_SHSP)); + locations.Add(new ComboItem(s.gamelist[(int)PLA], Locations.HOME_SWLA)); + + return locations; + } + + private static List CreateGen8a(GameStrings s) + { + var locations = Util.GetCBList(s.metLA_00000, 0); + Util.AddCBWithOffset(locations, s.metLA_30000, 30000, Locations.LinkTrade6); + Util.AddCBWithOffset(locations, s.metLA_00000, 00000, Locations8a.Met0); + Util.AddCBWithOffset(locations, s.metLA_30000, 30000, Locations8a.Met3); + Util.AddCBWithOffset(locations, s.metLA_40000, 40000, Locations8a.Met4); + Util.AddCBWithOffset(locations, s.metLA_60000, 60000, Locations8a.Met6); + + // Add in the BDSP+PLA magic met locations. + locations.Add(new ComboItem($"{s.EggName} (BD/SP)", Locations.HOME_SWSHBDSPEgg)); + locations.Add(new ComboItem(s.gamelist[(int)BD], Locations.HOME_SWBD)); + locations.Add(new ComboItem(s.gamelist[(int)SP], Locations.HOME_SHSP)); + locations.Add(new ComboItem(s.gamelist[(int)PLA], Locations.HOME_SWLA)); + + return locations; + } + + private static List CreateGen8b(GameStrings s) + { + // Manually add invalid (-1) location from SWSH as ID 65535 + var locations = new List { new(s.metSWSH_00000[0], Locations.Default8bNone) }; + Util.AddCBWithOffset(locations, s.metBDSP_60000, 60000, Locations.Daycare5); + Util.AddCBWithOffset(locations, s.metBDSP_30000, 30000, Locations.LinkTrade6); + Util.AddCBWithOffset(locations, s.metBDSP_00000, 00000, Locations8b.Met0); + Util.AddCBWithOffset(locations, s.metBDSP_30000, 30000, Locations8b.Met3); + Util.AddCBWithOffset(locations, s.metBDSP_40000, 40000, Locations8b.Met4); + Util.AddCBWithOffset(locations, s.metBDSP_60000, 60000, Locations8b.Met6); + return locations; + } + + /// + /// Fetches a Met Location list for a that has been transferred away from and overwritten. + /// + /// Origin version + /// Current format context + /// True if an egg location list, false if a regular met location list + /// Met location list + public IReadOnlyList GetLocationList(GameVersion version, EntityContext context, bool egg = false) + { + if (context == EntityContext.Gen2) + return MetGen2; + + IReadOnlyList result; + if (egg && version < W && context.Generation() >= 5) + result = MetGen4; + else + result = GetLocationListInternal(version, context); + + // Insert the BDSP none location if the format requires it. + if (context is EntityContext.Gen8b && !BDSP.Contains(version)) { - MetGen2 = CreateGen2(s); - MetGen3 = CreateGen3(s); - MetGen3CXD = CreateGen3CXD(s); - MetGen4 = CreateGen4(s); - MetGen5 = CreateGen5(s); - MetGen6 = CreateGen6(s); - MetGen7 = CreateGen7(s); - MetGen7GG = CreateGen7GG(s); - MetGen8 = CreateGen8(s); - MetGen8a = CreateGen8a(s); - MetGen8b = CreateGen8b(s); + var list = new List(result.Count + 1); + list.AddRange(result); + list.Insert(1, new ComboItem($"{list[0].Text} (BD/SP)", Locations.Default8bNone)); + result = list; } - private static List CreateGen2(GameStrings s) + return result; + } + + private IReadOnlyList GetLocationListInternal(GameVersion version, EntityContext context) + { + return version switch { - var locations = Util.GetCBList(s.metGSC_00000.AsSpan(0, 0x5F)); - Util.AddCBWithOffset(locations, s.metGSC_00000.AsSpan(0x7E, 2), 0x7E); - return locations; + CXD when context == EntityContext.Gen3 => MetGen3CXD, + R or S when context == EntityContext.Gen3 => Partition1(MetGen3, z => z <= 87), // Ferry + E when context == EntityContext.Gen3 => Partition1(MetGen3, z => z is <= 87 or (>= 197 and <= 212)), // Trainer Hill + FR or LG when context == EntityContext.Gen3 => Partition1(MetGen3, z => z is > 87 and < 197), // Celadon Dept. + D or P when context == EntityContext.Gen4 => Partition2(MetGen4, z => z <= 111, 4), // Battle Park + Pt when context == EntityContext.Gen4 => Partition2(MetGen4, z => z <= 125, 4), // Rock Peak Ruins + HG or SS when context == EntityContext.Gen4 => Partition2(MetGen4, z => z is > 125 and < 234, 4), // Celadon Dept. + + B or W => MetGen5, + B2 or W2 => Partition2(MetGen5, z => z <= 116), // Abyssal Ruins + X or Y => Partition2(MetGen6, z => z <= 168), // Unknown Dungeon + OR or AS => Partition2(MetGen6, z => z is > 168 and <= 354), // Secret Base + SN or MN => Partition2(MetGen7, z => z < 200), // Outer Cape + US or UM + or RD or BU or GN or YW + or GD or SI or C => Partition2(MetGen7, z => z < 234), // Dividing Peak Tunnel + GP or GE or GO => Partition2(MetGen7GG, z => z <= 54), // Pokémon League + SW or SH => Partition2(MetGen8, z => z < 400), + BD or SP => Partition2(MetGen8b, z => z < 628), + PLA => Partition2(MetGen8a, z => z < 512), + _ => new List(GetLocationListModified(version, context)), + }; + + static IReadOnlyList Partition1(IReadOnlyList list, Func criteria) + { + var result = new ComboItem[list.Count]; + return GetOrderedList(list, result, criteria); } - private static List CreateGen3(GameStrings s) + static IReadOnlyList GetOrderedList(IReadOnlyList list, ComboItem[] result, + Func criteria, int start = 0) { - var locations = Util.GetCBList(s.metRSEFRLG_00000.AsSpan(0, 213)); - Util.AddCBWithOffset(locations, s.metRSEFRLG_00000.AsSpan(253, 3), 253); - return locations; - } - - private static List CreateGen3CXD(GameStrings s) - { - var list = Util.GetCBList(s.metCXD_00000); - list.RemoveAll(z => z.Text.Length == 0); - return list; - } - - private static List CreateGen4(GameStrings s) - { - var locations = Util.GetCBList(s.metHGSS_00000, 0); - Util.AddCBWithOffset(locations, s.metHGSS_02000, 2000, Locations.Daycare4); - Util.AddCBWithOffset(locations, s.metHGSS_02000, 2000, Locations.LinkTrade4); - Util.AddCBWithOffset(locations, s.metHGSS_03000, 3000, Locations.Ranger4); - Util.AddCBWithOffset(locations, s.metHGSS_00000, 0000, Locations4.Met0); - Util.AddCBWithOffset(locations, s.metHGSS_02000, 2000, Locations4.Met2); - Util.AddCBWithOffset(locations, s.metHGSS_03000, 3000, Locations4.Met3); - return locations; - } - - private IReadOnlyList CreateGen4Transfer() - { - // Pal Park to front - var met = MetGen4.ToArray(); - var index = Array.FindIndex(met, z => z.Value == Locations.Transfer3); - var pal = met[index]; - Array.Copy(met, 0, met, 1, index); - met[0] = pal; - return met; - } - - private static List CreateGen5(GameStrings s) - { - var locations = Util.GetCBList(s.metBW2_00000, 0); - Util.AddCBWithOffset(locations, s.metBW2_60000, 60000, Locations.Daycare5); - Util.AddCBWithOffset(locations, s.metBW2_30000, 30000, Locations.LinkTrade5); - Util.AddCBWithOffset(locations, s.metBW2_00000, 00000, Locations5.Met0); - Util.AddCBWithOffset(locations, s.metBW2_30000, 30000, Locations5.Met3); - Util.AddCBWithOffset(locations, s.metBW2_40000, 40000, Locations5.Met4); - Util.AddCBWithOffset(locations, s.metBW2_60000, 60000, Locations5.Met6); - return locations; - } - - private IReadOnlyList CreateGen5Transfer() - { - // PokéTransfer to front - var met = MetGen5.ToArray(); - var index = Array.FindIndex(met, z => z.Value == Locations.Transfer4); - var xfr = met[index]; - Array.Copy(met, 0, met, 1, index); - met[0] = xfr; - return met; - } - - private static List CreateGen6(GameStrings s) - { - var locations = Util.GetCBList(s.metXY_00000, 0); - Util.AddCBWithOffset(locations, s.metXY_60000, 60000, Locations.Daycare5); - Util.AddCBWithOffset(locations, s.metXY_30000, 30000, Locations.LinkTrade6); - Util.AddCBWithOffset(locations, s.metXY_00000, 00000, Locations6.Met0); - Util.AddCBWithOffset(locations, s.metXY_30000, 30000, Locations6.Met3); - Util.AddCBWithOffset(locations, s.metXY_40000, 40000, Locations6.Met4); - Util.AddCBWithOffset(locations, s.metXY_60000, 60000, Locations6.Met6); - return locations; - } - - private static List CreateGen7(GameStrings s) - { - var locations = Util.GetCBList(s.metSM_00000, 0); - Util.AddCBWithOffset(locations, s.metSM_60000, 60000, Locations.Daycare5); - Util.AddCBWithOffset(locations, s.metSM_30000, 30000, Locations.LinkTrade6); - Util.AddCBWithOffset(locations, s.metSM_00000, 00000, Locations7.Met0); - Util.AddCBWithOffset(locations, s.metSM_30000, 30000, Locations7.Met3); - Util.AddCBWithOffset(locations, s.metSM_40000, 40000, Locations7.Met4); - Util.AddCBWithOffset(locations, s.metSM_60000, 60000, Locations7.Met6); - return locations; - } - - private static List CreateGen7GG(GameStrings s) - { - var locations = Util.GetCBList(s.metGG_00000, 0); - Util.AddCBWithOffset(locations, s.metGG_60000, 60000, Locations.Daycare5); - Util.AddCBWithOffset(locations, s.metGG_30000, 30000, Locations.LinkTrade6); - Util.AddCBWithOffset(locations, s.metGG_00000, 00000, Locations7b.Met0); - Util.AddCBWithOffset(locations, s.metGG_30000, 30000, Locations7b.Met3); - Util.AddCBWithOffset(locations, s.metGG_40000, 40000, Locations7b.Met4); - Util.AddCBWithOffset(locations, s.metGG_60000, 60000, Locations7b.Met6); - return locations; - } - - private static List CreateGen8(GameStrings s) - { - var locations = Util.GetCBList(s.metSWSH_00000, 0); - Util.AddCBWithOffset(locations, s.metSWSH_60000, 60000, Locations.Daycare5); - Util.AddCBWithOffset(locations, s.metSWSH_30000, 30000, Locations.LinkTrade6); - Util.AddCBWithOffset(locations, s.metSWSH_00000, 00000, Locations8.Met0); - Util.AddCBWithOffset(locations, s.metSWSH_30000, 30000, Locations8.Met3); - Util.AddCBWithOffset(locations, s.metSWSH_40000, 40000, Locations8.Met4); - Util.AddCBWithOffset(locations, s.metSWSH_60000, 60000, Locations8.Met6); - - // Add in the BDSP+PLA magic met locations. - locations.Add(new ComboItem($"{s.EggName} (BD/SP)", Locations.HOME_SWSHBDSPEgg)); - locations.Add(new ComboItem(s.gamelist[(int)BD], Locations.HOME_SWBD)); - locations.Add(new ComboItem(s.gamelist[(int)SP], Locations.HOME_SHSP)); - locations.Add(new ComboItem(s.gamelist[(int)PLA], Locations.HOME_SWLA)); - - return locations; - } - - private static List CreateGen8a(GameStrings s) - { - var locations = Util.GetCBList(s.metLA_00000, 0); - Util.AddCBWithOffset(locations, s.metLA_30000, 30000, Locations.LinkTrade6); - Util.AddCBWithOffset(locations, s.metLA_00000, 00000, Locations8a.Met0); - Util.AddCBWithOffset(locations, s.metLA_30000, 30000, Locations8a.Met3); - Util.AddCBWithOffset(locations, s.metLA_40000, 40000, Locations8a.Met4); - Util.AddCBWithOffset(locations, s.metLA_60000, 60000, Locations8a.Met6); - - // Add in the BDSP+PLA magic met locations. - locations.Add(new ComboItem($"{s.EggName} (BD/SP)", Locations.HOME_SWSHBDSPEgg)); - locations.Add(new ComboItem(s.gamelist[(int)BD], Locations.HOME_SWBD)); - locations.Add(new ComboItem(s.gamelist[(int)SP], Locations.HOME_SHSP)); - locations.Add(new ComboItem(s.gamelist[(int)PLA], Locations.HOME_SWLA)); - - return locations; - } - - private static List CreateGen8b(GameStrings s) - { - // Manually add invalid (-1) location from SWSH as ID 65535 - var locations = new List { new(s.metSWSH_00000[0], Locations.Default8bNone) }; - Util.AddCBWithOffset(locations, s.metBDSP_60000, 60000, Locations.Daycare5); - Util.AddCBWithOffset(locations, s.metBDSP_30000, 30000, Locations.LinkTrade6); - Util.AddCBWithOffset(locations, s.metBDSP_00000, 00000, Locations8b.Met0); - Util.AddCBWithOffset(locations, s.metBDSP_30000, 30000, Locations8b.Met3); - Util.AddCBWithOffset(locations, s.metBDSP_40000, 40000, Locations8b.Met4); - Util.AddCBWithOffset(locations, s.metBDSP_60000, 60000, Locations8b.Met6); - return locations; - } - - /// - /// Fetches a Met Location list for a that has been transferred away from and overwritten. - /// - /// Origin version - /// Current format context - /// True if an egg location list, false if a regular met location list - /// Met location list - public IReadOnlyList GetLocationList(GameVersion version, EntityContext context, bool egg = false) - { - if (context == EntityContext.Gen2) - return MetGen2; - - IReadOnlyList result; - if (egg && version < W && context.Generation() >= 5) - result = MetGen4; - else - result = GetLocationListInternal(version, context); - - // Insert the BDSP none location if the format requires it. - if (context is EntityContext.Gen8b && !BDSP.Contains(version)) + // store values that match criteria at the next available position of the array + // store non-matches starting at the end. reverse before returning + int end = list.Count - 1; + for (var index = start; index < list.Count; index++) { - var list = new List(result.Count + 1); - list.AddRange(result); - list.Insert(1, new ComboItem($"{list[0].Text} (BD/SP)", Locations.Default8bNone)); - result = list; + var item = list[index]; + if (criteria(item.Value)) + result[start++] = item; + else + result[end--] = item; } + // since the non-matches are reversed in order, we swap them back since we know where they end up at. + Array.Reverse(result, start, list.Count - start); return result; } - private IReadOnlyList GetLocationListInternal(GameVersion version, EntityContext context) + static IReadOnlyList Partition2(IReadOnlyList list, Func criteria, + int keepFirst = 3) { - return version switch - { - CXD when context == EntityContext.Gen3 => MetGen3CXD, - R or S when context == EntityContext.Gen3 => Partition1(MetGen3, z => z <= 87), // Ferry - E when context == EntityContext.Gen3 => Partition1(MetGen3, z => z is <= 87 or >= 197 and <= 212), // Trainer Hill - FR or LG when context == EntityContext.Gen3 => Partition1(MetGen3, z => z is > 87 and < 197), // Celadon Dept. - D or P when context == EntityContext.Gen4 => Partition2(MetGen4, z => z <= 111, 4), // Battle Park - Pt when context == EntityContext.Gen4 => Partition2(MetGen4, z => z <= 125, 4), // Rock Peak Ruins - HG or SS when context == EntityContext.Gen4 => Partition2(MetGen4, z => z is > 125 and < 234, 4), // Celadon Dept. - - B or W => MetGen5, - B2 or W2 => Partition2(MetGen5, z => z <= 116), // Abyssal Ruins - X or Y => Partition2(MetGen6, z => z <= 168), // Unknown Dungeon - OR or AS => Partition2(MetGen6, z => z is > 168 and <= 354), // Secret Base - SN or MN => Partition2(MetGen7, z => z < 200), // Outer Cape - US or UM - or RD or BU or GN or YW - or GD or SI or C => Partition2(MetGen7, z => z < 234), // Dividing Peak Tunnel - GP or GE or GO => Partition2(MetGen7GG, z => z <= 54), // Pokémon League - SW or SH => Partition2(MetGen8, z => z < 400), - BD or SP => Partition2(MetGen8b, z => z < 628), - PLA => Partition2(MetGen8a, z => z < 512), - _ => new List(GetLocationListModified(version, context)), - }; - - static IReadOnlyList Partition1(IReadOnlyList list, Func criteria) - { - var result = new ComboItem[list.Count]; - return GetOrderedList(list, result, criteria); - } - - static IReadOnlyList GetOrderedList(IReadOnlyList list, ComboItem[] result, - Func criteria, int start = 0) - { - // store values that match criteria at the next available position of the array - // store non-matches starting at the end. reverse before returning - int end = list.Count - 1; - for (var index = start; index < list.Count; index++) - { - var item = list[index]; - if (criteria(item.Value)) - result[start++] = item; - else - result[end--] = item; - } - - // since the non-matches are reversed in order, we swap them back since we know where they end up at. - Array.Reverse(result, start, list.Count - start); - return result; - } - - static IReadOnlyList Partition2(IReadOnlyList list, Func criteria, - int keepFirst = 3) - { - var result = new ComboItem[list.Count]; - for (int i = 0; i < keepFirst; i++) - result[i] = list[i]; - return GetOrderedList(list, result, criteria, keepFirst); - } + var result = new ComboItem[list.Count]; + for (int i = 0; i < keepFirst; i++) + result[i] = list[i]; + return GetOrderedList(list, result, criteria, keepFirst); } - - /// - /// Fetches a Met Location list for a that has been transferred away from and overwritten. - /// - /// Origin version - /// Current format context - /// Met location list - private IReadOnlyList GetLocationListModified(GameVersion version, EntityContext context) => version switch - { - <= CXD when context == EntityContext.Gen4 => MetGen4Transfer ??= CreateGen4Transfer(), - < X when context.Generation() >= 5 => MetGen5Transfer ??= CreateGen5Transfer(), - _ => Array.Empty(), - }; } + + /// + /// Fetches a Met Location list for a that has been transferred away from and overwritten. + /// + /// Origin version + /// Current format context + /// Met location list + private IReadOnlyList GetLocationListModified(GameVersion version, EntityContext context) => version switch + { + <= CXD when context == EntityContext.Gen4 => MetGen4Transfer ??= CreateGen4Transfer(), + < X when context.Generation() >= 5 => MetGen5Transfer ??= CreateGen5Transfer(), + _ => Array.Empty(), + }; } diff --git a/PKHeX.Core/Game/GameUtil.cs b/PKHeX.Core/Game/GameUtil.cs index d1fcc8a09..2254a6b87 100644 --- a/PKHeX.Core/Game/GameUtil.cs +++ b/PKHeX.Core/Game/GameUtil.cs @@ -3,238 +3,237 @@ using System.Linq; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Utility class for logic. +/// +public static class GameUtil { /// - /// Utility class for logic. + /// All possible values a can have. /// - public static class GameUtil + /// Ordered roughly by most recent games first. + public static readonly GameVersion[] GameVersions = GetValidGameVersions(); + + private static GameVersion[] GetValidGameVersions() { - /// - /// All possible values a can have. - /// - /// Ordered roughly by most recent games first. - public static readonly GameVersion[] GameVersions = GetValidGameVersions(); + var all = (GameVersion[])Enum.GetValues(typeof(GameVersion)); + var valid = Array.FindAll(all, IsValidSavedVersion); + Array.Reverse(valid); + return valid; + } - private static GameVersion[] GetValidGameVersions() + /// + /// Indicates if the value is a value used by the games or is an aggregate indicator. + /// + /// Game to check + public static bool IsValidSavedVersion(this GameVersion game) => game is > 0 and <= HighestGameID; + + /// + /// Most recent game ID utilized by official games. + /// + internal const GameVersion HighestGameID = RB - 1; + + /// Determines the Version Grouping of an input Version ID + /// Version of which to determine the group + /// Version Group Identifier or Invalid if type cannot be determined. + public static GameVersion GetMetLocationVersionGroup(GameVersion version) => version switch + { + // Side games + CXD => CXD, + GO => GO, + + // VC Transfers + RD or BU or YW or GN or GD or SI or C => USUM, + + // Gen2 -- PK2 + GS or GSC => GSC, + + // Gen3 + R or S => RS, + E => E, + FR or LG => FR, + + // Gen4 + D or P => DP, + Pt => Pt, + HG or SS => HGSS, + + // Gen5 + B or W => BW, + B2 or W2 => B2W2, + + // Gen6 + X or Y => XY, + OR or AS => ORAS, + + // Gen7 + SN or MN => SM, + US or UM => USUM, + GP or GE => GG, + + // Gen8 + SW or SH => SWSH, + BD or SP => BDSP, + PLA => PLA, + _ => Invalid, + }; + + /// + /// Gets a Version ID from the end of that Generation + /// + /// Generation ID + /// Version ID from requested generation. If none, return . + public static GameVersion GetVersion(int generation) => generation switch + { + 1 => RBY, + 2 => C, + 3 => E, + 4 => SS, + 5 => W2, + 6 => AS, + 7 => UM, + 8 => SH, + _ => Invalid, + }; + + /// + /// Gets the Generation the belongs to. + /// + /// Game to retrieve the generation for + /// Generation ID + public static int GetGeneration(this GameVersion game) + { + if (Gen1.Contains(game)) return 1; + if (Gen2.Contains(game)) return 2; + if (Gen3.Contains(game)) return 3; + if (Gen4.Contains(game)) return 4; + if (Gen5.Contains(game)) return 5; + if (Gen6.Contains(game)) return 6; + if (Gen7.Contains(game)) return 7; + if (Gen7b.Contains(game)) return 7; + if (Gen8.Contains(game)) return 8; + return -1; + } + + /// + /// Gets the Generation the belongs to. + /// + /// Game to retrieve the generation for + /// Generation ID + public static int GetMaxSpeciesID(this GameVersion game) + { + if (Gen1.Contains(game)) return Legal.MaxSpeciesID_1; + if (Gen2.Contains(game)) return Legal.MaxSpeciesID_2; + if (Gen3.Contains(game)) return Legal.MaxSpeciesID_3; + if (Gen4.Contains(game)) return Legal.MaxSpeciesID_4; + if (Gen5.Contains(game)) return Legal.MaxSpeciesID_5; + if (Gen6.Contains(game)) return Legal.MaxSpeciesID_6; + if (Gen7b.Contains(game)) return Legal.MaxSpeciesID_7b; + if (Gen7.Contains(game)) { - var all = (GameVersion[])Enum.GetValues(typeof(GameVersion)); - var valid = Array.FindAll(all, IsValidSavedVersion); - Array.Reverse(valid); - return valid; - } - - /// - /// Indicates if the value is a value used by the games or is an aggregate indicator. - /// - /// Game to check - public static bool IsValidSavedVersion(this GameVersion game) => game is > 0 and <= HighestGameID; - - /// - /// Most recent game ID utilized by official games. - /// - internal const GameVersion HighestGameID = RB - 1; - - /// Determines the Version Grouping of an input Version ID - /// Version of which to determine the group - /// Version Group Identifier or Invalid if type cannot be determined. - public static GameVersion GetMetLocationVersionGroup(GameVersion version) => version switch - { - // Side games - CXD => CXD, - GO => GO, - - // VC Transfers - RD or BU or YW or GN or GD or SI or C => USUM, - - // Gen2 -- PK2 - GS or GSC => GSC, - - // Gen3 - R or S => RS, - E => E, - FR or LG => FR, - - // Gen4 - D or P => DP, - Pt => Pt, - HG or SS => HGSS, - - // Gen5 - B or W => BW, - B2 or W2 => B2W2, - - // Gen6 - X or Y => XY, - OR or AS => ORAS, - - // Gen7 - SN or MN => SM, - US or UM => USUM, - GP or GE => GG, - - // Gen8 - SW or SH => SWSH, - BD or SP => BDSP, - PLA => PLA, - _ => Invalid, - }; - - /// - /// Gets a Version ID from the end of that Generation - /// - /// Generation ID - /// Version ID from requested generation. If none, return . - public static GameVersion GetVersion(int generation) => generation switch - { - 1 => RBY, - 2 => C, - 3 => E, - 4 => SS, - 5 => W2, - 6 => AS, - 7 => UM, - 8 => SH, - _ => Invalid, - }; - - /// - /// Gets the Generation the belongs to. - /// - /// Game to retrieve the generation for - /// Generation ID - public static int GetGeneration(this GameVersion game) - { - if (Gen1.Contains(game)) return 1; - if (Gen2.Contains(game)) return 2; - if (Gen3.Contains(game)) return 3; - if (Gen4.Contains(game)) return 4; - if (Gen5.Contains(game)) return 5; - if (Gen6.Contains(game)) return 6; - if (Gen7.Contains(game)) return 7; - if (Gen7b.Contains(game)) return 7; - if (Gen8.Contains(game)) return 8; - return -1; - } - - /// - /// Gets the Generation the belongs to. - /// - /// Game to retrieve the generation for - /// Generation ID - public static int GetMaxSpeciesID(this GameVersion game) - { - if (Gen1.Contains(game)) return Legal.MaxSpeciesID_1; - if (Gen2.Contains(game)) return Legal.MaxSpeciesID_2; - if (Gen3.Contains(game)) return Legal.MaxSpeciesID_3; - if (Gen4.Contains(game)) return Legal.MaxSpeciesID_4; - if (Gen5.Contains(game)) return Legal.MaxSpeciesID_5; - if (Gen6.Contains(game)) return Legal.MaxSpeciesID_6; - if (Gen7b.Contains(game)) return Legal.MaxSpeciesID_7b; - if (Gen7.Contains(game)) - { - if (SM.Contains(game)) - return Legal.MaxSpeciesID_7; - if (USUM.Contains(game)) - return Legal.MaxSpeciesID_7_USUM; + if (SM.Contains(game)) + return Legal.MaxSpeciesID_7; + if (USUM.Contains(game)) return Legal.MaxSpeciesID_7_USUM; - } - if (PLA == game) return Legal.MaxSpeciesID_8a; - if (BDSP.Contains(game)) return Legal.MaxSpeciesID_8b; - if (Gen8.Contains(game)) return Legal.MaxSpeciesID_8; - return -1; + return Legal.MaxSpeciesID_7_USUM; } + if (PLA == game) return Legal.MaxSpeciesID_8a; + if (BDSP.Contains(game)) return Legal.MaxSpeciesID_8b; + if (Gen8.Contains(game)) return Legal.MaxSpeciesID_8; + return -1; + } - /// - /// Checks if the version (or subset versions) is equivalent to . - /// - /// Version (set) - /// Individual version - public static bool Contains(this GameVersion g1, int g2) => g1.Contains((GameVersion) g2); + /// + /// Checks if the version (or subset versions) is equivalent to . + /// + /// Version (set) + /// Individual version + public static bool Contains(this GameVersion g1, int g2) => g1.Contains((GameVersion) g2); - /// - /// Checks if the version (or subset versions) is equivalent to . - /// - /// Version (set) - /// Individual version - public static bool Contains(this GameVersion g1, GameVersion g2) + /// + /// Checks if the version (or subset versions) is equivalent to . + /// + /// Version (set) + /// Individual version + public static bool Contains(this GameVersion g1, GameVersion g2) + { + if (g1 == g2 || g1 == Any) + return true; + + return g1 switch { - if (g1 == g2 || g1 == Any) - return true; + RB => g2 is RD or BU or GN, + RBY or Stadium => RB.Contains(g2) || g2 == YW, + Gen1 => RBY.Contains(g2) || g2 == Stadium, - return g1 switch - { - RB => g2 is RD or BU or GN, - RBY or Stadium => RB.Contains(g2) || g2 == YW, - Gen1 => RBY.Contains(g2) || g2 == Stadium, + GS => g2 is GD or SI, + GSC or Stadium2 => GS.Contains(g2) || g2 == C, + Gen2 => GSC.Contains(g2) || g2 == Stadium2, - GS => g2 is GD or SI, - GSC or Stadium2 => GS.Contains(g2) || g2 == C, - Gen2 => GSC.Contains(g2) || g2 == Stadium2, + RS => g2 is R or S, + RSE => RS.Contains(g2) || g2 == E, + FRLG => g2 is FR or LG, + COLO or XD => g2 == CXD, + CXD => g2 is COLO or XD, + RSBOX => RS.Contains(g2) || g2 == E || FRLG.Contains(g2), + Gen3 => RSE.Contains(g2) || FRLG.Contains(g2) || CXD.Contains(g2) || g2 == RSBOX, - RS => g2 is R or S, - RSE => RS.Contains(g2) || g2 == E, - FRLG => g2 is FR or LG, - COLO or XD => g2 == CXD, - CXD => g2 is COLO or XD, - RSBOX => RS.Contains(g2) || g2 == E || FRLG.Contains(g2), - Gen3 => RSE.Contains(g2) || FRLG.Contains(g2) || CXD.Contains(g2) || g2 == RSBOX, + DP => g2 is D or P, + HGSS => g2 is HG or SS, + DPPt => DP.Contains(g2) || g2 == Pt, + BATREV => DP.Contains(g2) || g2 == Pt || HGSS.Contains(g2), + Gen4 => DPPt.Contains(g2) || HGSS.Contains(g2) || g2 == BATREV, - DP => g2 is D or P, - HGSS => g2 is HG or SS, - DPPt => DP.Contains(g2) || g2 == Pt, - BATREV => DP.Contains(g2) || g2 == Pt || HGSS.Contains(g2), - Gen4 => DPPt.Contains(g2) || HGSS.Contains(g2) || g2 == BATREV, + BW => g2 is B or W, + B2W2 => g2 is B2 or W2, + Gen5 => BW.Contains(g2) || B2W2.Contains(g2), - BW => g2 is B or W, - B2W2 => g2 is B2 or W2, - Gen5 => BW.Contains(g2) || B2W2.Contains(g2), + XY => g2 is X or Y, + ORAS => g2 is OR or AS, - XY => g2 is X or Y, - ORAS => g2 is OR or AS, + Gen6 => XY.Contains(g2) || ORAS.Contains(g2), + SM => g2 is SN or MN, + USUM => g2 is US or UM, + GG => g2 is GP or GE, + Gen7 => SM.Contains(g2) || USUM.Contains(g2), + Gen7b => GG.Contains(g2) || GO == g2, - Gen6 => XY.Contains(g2) || ORAS.Contains(g2), - SM => g2 is SN or MN, - USUM => g2 is US or UM, - GG => g2 is GP or GE, - Gen7 => SM.Contains(g2) || USUM.Contains(g2), - Gen7b => GG.Contains(g2) || GO == g2, + SWSH => g2 is SW or SH, + BDSP => g2 is BD or SP, + Gen8 => SWSH.Contains(g2) || BDSP.Contains(g2) || PLA == g2, + _ => false, + }; + } - SWSH => g2 is SW or SH, - BDSP => g2 is BD or SP, - Gen8 => SWSH.Contains(g2) || BDSP.Contains(g2) || PLA == g2, - _ => false, - }; - } + /// + /// List of possible values within the provided . + /// + /// Generation to look within + /// + public static GameVersion[] GetVersionsInGeneration(int generation, int pkVersion) + { + if (Gen7b.Contains(pkVersion)) + return new[] {GO, GP, GE}; + return Array.FindAll(GameVersions, z => z.GetGeneration() == generation); + } - /// - /// List of possible values within the provided . - /// - /// Generation to look within - /// - public static GameVersion[] GetVersionsInGeneration(int generation, int pkVersion) - { - if (Gen7b.Contains(pkVersion)) - return new[] {GO, GP, GE}; - return Array.FindAll(GameVersions, z => z.GetGeneration() == generation); - } - - /// - /// List of possible values within the provided criteria. - /// - /// Criteria for retrieving versions - /// Generation format minimum (necessary for the CXD/Gen4 swap etc) - public static IEnumerable GetVersionsWithinRange(IGameValueLimit obj, int generation = -1) - { - if (obj.MaxGameID == Legal.MaxGameID_7b) // edge case - return new[] {GO, GP, GE}; - var versions = GameVersions - .Where(version => (GameVersion)obj.MinGameID <= version && version <= (GameVersion)obj.MaxGameID); - if (generation < 0) - return versions; - if (obj.MaxGameID == Legal.MaxGameID_7 && generation == 7) - versions = versions.Where(version => version != GO); - return versions.Where(version => version.GetGeneration() <= generation); - } + /// + /// List of possible values within the provided criteria. + /// + /// Criteria for retrieving versions + /// Generation format minimum (necessary for the CXD/Gen4 swap etc) + public static IEnumerable GetVersionsWithinRange(IGameValueLimit obj, int generation = -1) + { + if (obj.MaxGameID == Legal.MaxGameID_7b) // edge case + return new[] {GO, GP, GE}; + var versions = GameVersions + .Where(version => (GameVersion)obj.MinGameID <= version && version <= (GameVersion)obj.MaxGameID); + if (generation < 0) + return versions; + if (obj.MaxGameID == Legal.MaxGameID_7 && generation == 7) + versions = versions.Where(version => version != GO); + return versions.Where(version => version.GetGeneration() <= generation); } } diff --git a/PKHeX.Core/Game/IBasicStrings.cs b/PKHeX.Core/Game/IBasicStrings.cs index 84445df84..bf601d50f 100644 --- a/PKHeX.Core/Game/IBasicStrings.cs +++ b/PKHeX.Core/Game/IBasicStrings.cs @@ -1,22 +1,21 @@ using System.Collections.Generic; -namespace PKHeX.Core -{ - /// - /// String providing interface with minimal support for game strings. - /// - public interface IBasicStrings - { - IReadOnlyList Species { get; } - IReadOnlyList Item { get; } - IReadOnlyList Move { get; } - IReadOnlyList Ability { get; } - IReadOnlyList Types { get; } - IReadOnlyList Natures { get; } +namespace PKHeX.Core; - /// - /// Name an Egg has when obtained on this language. - /// - string EggName { get; } - } +/// +/// String providing interface with minimal support for game strings. +/// +public interface IBasicStrings +{ + IReadOnlyList Species { get; } + IReadOnlyList Item { get; } + IReadOnlyList Move { get; } + IReadOnlyList Ability { get; } + IReadOnlyList Types { get; } + IReadOnlyList Natures { get; } + + /// + /// Name an Egg has when obtained on this language. + /// + string EggName { get; } } diff --git a/PKHeX.Core/Game/Locations/Locations.cs b/PKHeX.Core/Game/Locations/Locations.cs index b2a9c9a03..09b728a0f 100644 --- a/PKHeX.Core/Game/Locations/Locations.cs +++ b/PKHeX.Core/Game/Locations/Locations.cs @@ -1,184 +1,183 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Decoration and logic for Met Location IDs +/// +public static class Locations { - /// - /// Decoration and logic for Met Location IDs - /// - public static class Locations + public const int LinkTrade4 = 2002; + public const int LinkTrade5 = 30003; + public const int LinkTrade6 = 30002; + + public const int Daycare4 = 2000; + public const int Daycare5 = 60002; + public const int Daycare8b = 60010; + + public const int LinkTrade2NPC = 126; + public const int LinkTrade3NPC = 254; + public const int LinkTrade4NPC = 2001; + public const int LinkTrade5NPC = 30002; + public const int LinkTrade6NPC = 30001; + + public const int Breeder5 = 60003; + public const int Breeder6 = 60004; + + public const int PokeWalker4 = 233; + public const int Ranger4 = 3001; + public const int Faraway4 = 3002; + + /// Goldenrod City in + public const int HatchLocationC = 16; + + /// Route 117 in + public const int HatchLocationRSE = 32; + + /// Four Island in + public const int HatchLocationFRLG = 146; + + /// Solaceon Town in + public const int HatchLocationDPPt = 4; + + /// Route 34 in + public const int HatchLocationHGSS = 182; + + /// Skyarrow Bridge in + public const int HatchLocation5 = 64; + + /// Route 7 in + public const int HatchLocation6XY = 38; + + /// Battle Resort in + public const int HatchLocation6AO = 318; + + /// Paniola Ranch in + public const int HatchLocation7 = 78; + + /// Route 5 in + public const int HatchLocation8 = 40; + + /// Solaceon Town in + public const int HatchLocation8b = 446; + + /// Generation 1 -> Generation 7 Transfer Location (Kanto) + public const int Transfer1 = 30013; + + /// Generation 2 -> Generation 7 Transfer Location (Johto) + public const int Transfer2 = 30017; + + /// Generation 3 -> Generation 4 Transfer Location (Pal Park) + public const int Transfer3 = 0x37; + + /// Generation 4 -> Generation 5 Transfer Location (Poké Transporter) + public const int Transfer4 = 30001; + + /// Generation 4 -> Generation 5 Transfer Location (Crown Celebi - Event not activated in Gen 5) + public const int Transfer4_CelebiUnused = 30010; + + /// Generation 4 -> Generation 5 Transfer Location (Crown Celebi - Event activated in Gen 5) + public const int Transfer4_CelebiUsed = 30011; + + /// Generation 4 -> Generation 5 Transfer Location (Crown Beast - Event not activated in Gen 5) + public const int Transfer4_CrownUnused = 30012; + + /// Generation 4 -> Generation 5 Transfer Location (Crown Beast - Event activated in Gen 5) + public const int Transfer4_CrownUsed = 30013; + + /// Generation 6 Gift from Pokémon Link + public const int LinkGift6 = 30011; + + /// Generation 7 Poké Pelago + public const int Pelago7 = 30016; + + /// Generation 7 Transfer from GO to Pokémon LGP/E's GO Park + public const int GO7 = 50; + + /// Generation 8 Transfer from GO to Pokémon HOME + public const int GO8 = 30012; + + /// Generation 8 Gift from Pokémon HOME + public const int HOME8 = 30018; + + public const int BugCatchingContest4 = 207; + + public const int HOME_SHSP = 59998; // SP traded to (SW)SH + public const int HOME_SWBD = 59999; // BD traded to SW(SH) + public const int HOME_SWLA = 60000; // PLA traded to SW(SH) + public const int HOME_SWSHBDSPEgg = 65534; // -2 = 8bNone-1.. + public const int Default8bNone = 65535; + + public static int GetVersionSWSH(int ver) => (GameVersion)ver switch { - public const int LinkTrade4 = 2002; - public const int LinkTrade5 = 30003; - public const int LinkTrade6 = 30002; + GameVersion.PLA => (int)GameVersion.SW, + GameVersion.BD => (int)GameVersion.SW, + GameVersion.SP => (int)GameVersion.SH, + _ => ver, + }; - public const int Daycare4 = 2000; - public const int Daycare5 = 60002; - public const int Daycare8b = 60010; + public static int GetMetSWSH(int loc, int ver) => (GameVersion)ver switch + { + GameVersion.PLA => HOME_SWLA, + GameVersion.BD => HOME_SWBD, + GameVersion.SP => HOME_SHSP, + _ => loc, + }; - public const int LinkTrade2NPC = 126; - public const int LinkTrade3NPC = 254; - public const int LinkTrade4NPC = 2001; - public const int LinkTrade5NPC = 30002; - public const int LinkTrade6NPC = 30001; + public static bool IsValidMetBDSP(int loc, int ver) => loc switch + { + HOME_SHSP when ver == (int)GameVersion.SH => true, + HOME_SWBD when ver == (int)GameVersion.SW => true, + _ => false, + }; - public const int Breeder5 = 60003; - public const int Breeder6 = 60004; + public static int TradedEggLocationNPC(int generation) => generation switch + { + 1 => LinkTrade2NPC, + 2 => LinkTrade2NPC, + 3 => LinkTrade3NPC, + 4 => LinkTrade4NPC, + 5 => LinkTrade5NPC, + _ => LinkTrade6NPC, + }; - public const int PokeWalker4 = 233; - public const int Ranger4 = 3001; - public const int Faraway4 = 3002; + public static int TradedEggLocation(int generation, GameVersion ver) => generation switch + { + 4 => LinkTrade4, + 5 => LinkTrade5, + 8 when GameVersion.BDSP.Contains(ver) => LinkTrade6NPC, + _ => LinkTrade6, + }; - /// Goldenrod City in - public const int HatchLocationC = 16; + public static bool IsPtHGSSLocation(int location) => location is > 111 and < 2000; + public static bool IsPtHGSSLocationEgg(int location) => location is > 2010 and < 3000; + public static bool IsEventLocation3(int location) => location is 255; + public static bool IsEventLocation4(int location) => location is >= 3000 and <= 3076; + public static bool IsEventLocation5(int location) => location is > 40000 and < 50000; - /// Route 117 in - public const int HatchLocationRSE = 32; + private const int SafariLocation_RSE = 57; + private const int SafariLocation_FRLG = 136; + private const int SafariLocation_HGSS = 202; + private const int MarshLocation_DPPt = 52; + public static bool IsSafariZoneLocation3(int loc) => loc is SafariLocation_RSE or SafariLocation_FRLG; + public static bool IsSafariZoneLocation4(int loc) => loc is MarshLocation_DPPt or SafariLocation_HGSS; + public static bool IsSafariZoneLocation8b(int loc) => loc is (>= 219 and <= 224); - /// Four Island in - public const int HatchLocationFRLG = 146; - - /// Solaceon Town in - public const int HatchLocationDPPt = 4; - - /// Route 34 in - public const int HatchLocationHGSS = 182; - - /// Skyarrow Bridge in - public const int HatchLocation5 = 64; - - /// Route 7 in - public const int HatchLocation6XY = 38; - - /// Battle Resort in - public const int HatchLocation6AO = 318; - - /// Paniola Ranch in - public const int HatchLocation7 = 78; - - /// Route 5 in - public const int HatchLocation8 = 40; - - /// Solaceon Town in - public const int HatchLocation8b = 446; - - /// Generation 1 -> Generation 7 Transfer Location (Kanto) - public const int Transfer1 = 30013; - - /// Generation 2 -> Generation 7 Transfer Location (Johto) - public const int Transfer2 = 30017; - - /// Generation 3 -> Generation 4 Transfer Location (Pal Park) - public const int Transfer3 = 0x37; - - /// Generation 4 -> Generation 5 Transfer Location (Poké Transporter) - public const int Transfer4 = 30001; - - /// Generation 4 -> Generation 5 Transfer Location (Crown Celebi - Event not activated in Gen 5) - public const int Transfer4_CelebiUnused = 30010; - - /// Generation 4 -> Generation 5 Transfer Location (Crown Celebi - Event activated in Gen 5) - public const int Transfer4_CelebiUsed = 30011; - - /// Generation 4 -> Generation 5 Transfer Location (Crown Beast - Event not activated in Gen 5) - public const int Transfer4_CrownUnused = 30012; - - /// Generation 4 -> Generation 5 Transfer Location (Crown Beast - Event activated in Gen 5) - public const int Transfer4_CrownUsed = 30013; - - /// Generation 6 Gift from Pokémon Link - public const int LinkGift6 = 30011; - - /// Generation 7 Poké Pelago - public const int Pelago7 = 30016; - - /// Generation 7 Transfer from GO to Pokémon LGP/E's GO Park - public const int GO7 = 50; - - /// Generation 8 Transfer from GO to Pokémon HOME - public const int GO8 = 30012; - - /// Generation 8 Gift from Pokémon HOME - public const int HOME8 = 30018; - - public const int BugCatchingContest4 = 207; - - public const int HOME_SHSP = 59998; // SP traded to (SW)SH - public const int HOME_SWBD = 59999; // BD traded to SW(SH) - public const int HOME_SWLA = 60000; // PLA traded to SW(SH) - public const int HOME_SWSHBDSPEgg = 65534; // -2 = 8bNone-1.. - public const int Default8bNone = 65535; - - public static int GetVersionSWSH(int ver) => (GameVersion)ver switch - { - GameVersion.PLA => (int)GameVersion.SW, - GameVersion.BD => (int)GameVersion.SW, - GameVersion.SP => (int)GameVersion.SH, - _ => ver, - }; - - public static int GetMetSWSH(int loc, int ver) => (GameVersion)ver switch - { - GameVersion.PLA => HOME_SWLA, - GameVersion.BD => HOME_SWBD, - GameVersion.SP => HOME_SHSP, - _ => loc, - }; - - public static bool IsValidMetBDSP(int loc, int ver) => loc switch - { - HOME_SHSP when ver == (int)GameVersion.SH => true, - HOME_SWBD when ver == (int)GameVersion.SW => true, - _ => false, - }; - - public static int TradedEggLocationNPC(int generation) => generation switch - { - 1 => LinkTrade2NPC, - 2 => LinkTrade2NPC, - 3 => LinkTrade3NPC, - 4 => LinkTrade4NPC, - 5 => LinkTrade5NPC, - _ => LinkTrade6NPC, - }; - - public static int TradedEggLocation(int generation, GameVersion ver) => generation switch - { - 4 => LinkTrade4, - 5 => LinkTrade5, - 8 when GameVersion.BDSP.Contains(ver) => LinkTrade6NPC, - _ => LinkTrade6, - }; - - public static bool IsPtHGSSLocation(int location) => location is > 111 and < 2000; - public static bool IsPtHGSSLocationEgg(int location) => location is > 2010 and < 3000; - public static bool IsEventLocation3(int location) => location is 255; - public static bool IsEventLocation4(int location) => location is >= 3000 and <= 3076; - public static bool IsEventLocation5(int location) => location is > 40000 and < 50000; - - private const int SafariLocation_RSE = 57; - private const int SafariLocation_FRLG = 136; - private const int SafariLocation_HGSS = 202; - private const int MarshLocation_DPPt = 52; - public static bool IsSafariZoneLocation3(int loc) => loc is SafariLocation_RSE or SafariLocation_FRLG; - public static bool IsSafariZoneLocation4(int loc) => loc is MarshLocation_DPPt or SafariLocation_HGSS; - public static bool IsSafariZoneLocation8b(int loc) => loc is (>= 219 and <= 224); - - public static bool IsEggLocationBred4(int loc, GameVersion ver) - { - if (loc is Daycare4 or LinkTrade4) - return true; - return loc == Faraway4 && ver is GameVersion.Pt or GameVersion.HG or GameVersion.SS; - } - - public static bool IsEggLocationBred5(int loc) => loc is Daycare5 or LinkTrade5; - public static bool IsEggLocationBred6(int loc) => loc is Daycare5 or LinkTrade6; - public static bool IsEggLocationBred8b(int loc) => loc is Daycare8b or LinkTrade6NPC; - - public static int GetDaycareLocation(int generation, GameVersion version) => generation switch - { - 1 or 2 or 3 => 0, - 4 => Daycare4, - 5 => Daycare5, - 8 when version is GameVersion.BD or GameVersion.SP => Daycare8b, - _ => Daycare5, - }; + public static bool IsEggLocationBred4(int loc, GameVersion ver) + { + if (loc is Daycare4 or LinkTrade4) + return true; + return loc == Faraway4 && ver is GameVersion.Pt or GameVersion.HG or GameVersion.SS; } + + public static bool IsEggLocationBred5(int loc) => loc is Daycare5 or LinkTrade5; + public static bool IsEggLocationBred6(int loc) => loc is Daycare5 or LinkTrade6; + public static bool IsEggLocationBred8b(int loc) => loc is Daycare8b or LinkTrade6NPC; + + public static int GetDaycareLocation(int generation, GameVersion version) => generation switch + { + 1 or 2 or 3 => 0, + 4 => Daycare4, + 5 => Daycare5, + 8 when version is GameVersion.BD or GameVersion.SP => Daycare8b, + _ => Daycare5, + }; } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea.cs b/PKHeX.Core/Legality/Areas/EncounterArea.cs index 69758ba49..d52ecc4a2 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea.cs @@ -1,33 +1,32 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Represents an Area where can be encountered, which contains a Location ID and data. +/// +public abstract record EncounterArea(GameVersion Version) : IVersion { + public int Location { get; protected init; } + public SlotType Type { get; protected init; } = SlotType.Any; + protected abstract IReadOnlyList Raw { get; } + /// - /// Represents an Area where can be encountered, which contains a Location ID and data. + /// Gets the slots contained in the area that match the provided data. /// - public abstract record EncounterArea(GameVersion Version) : IVersion - { - public int Location { get; protected init; } - public SlotType Type { get; protected init; } = SlotType.Any; - protected abstract IReadOnlyList Raw { get; } + /// Pokémon Data + /// Evolution lineage + /// Enumerable list of encounters + public abstract IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain); - /// - /// Gets the slots contained in the area that match the provided data. - /// - /// Pokémon Data - /// Evolution lineage - /// Enumerable list of encounters - public abstract IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain); + /// + /// Checks if the provided met location ID matches the parameters for the area. + /// + /// Met Location ID + /// True if possibly originated from this area, false otherwise. + public virtual bool IsMatchLocation(int location) => Location == location; - /// - /// Checks if the provided met location ID matches the parameters for the area. - /// - /// Met Location ID - /// True if possibly originated from this area, false otherwise. - public virtual bool IsMatchLocation(int location) => Location == location; - - public bool HasSpecies(int species) => Raw.Any(z => z.Species == species); - public IEnumerable GetSpecies(EvoCriteria[] chain) => Raw.Where(z => chain.Any(c => z.Species == c.Species)); - } + public bool HasSpecies(int species) => Raw.Any(z => z.Species == species); + public IEnumerable GetSpecies(EvoCriteria[] chain) => Raw.Where(z => chain.Any(c => z.Species == c.Species)); } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea1.cs b/PKHeX.Core/Legality/Areas/EncounterArea1.cs index 1677756b0..b8ac6dc91 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea1.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea1.cs @@ -1,74 +1,73 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea1 : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea1 : EncounterArea + public readonly int Rate; + public readonly EncounterSlot1[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public static EncounterArea1[] GetAreas(BinLinkerAccessor input, GameVersion game) { - public readonly int Rate; - public readonly EncounterSlot1[] Slots; + var result = new EncounterArea1[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea1(input[i], game); + return result; + } - protected override IReadOnlyList Raw => Slots; + private EncounterArea1(ReadOnlySpan data, GameVersion game) : base(game) + { + Location = data[0]; + // 1 byte unused + Type = (SlotType)data[2]; + Rate = data[3]; - public static EncounterArea1[] GetAreas(BinLinkerAccessor input, GameVersion game) + var next = data[4..]; + int count = next.Length / 4; + var slots = new EncounterSlot1[count]; + for (int i = 0; i < slots.Length; i++) { - var result = new EncounterArea1[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea1(input[i], game); - return result; + const int size = 4; + var entry = next.Slice(i * size, size); + byte max = entry[3]; + byte min = entry[2]; + byte slotNum = entry[1]; + byte species = entry[0]; + slots[i] = new EncounterSlot1(this, species, min, max, slotNum); } + Slots = slots; + } - private EncounterArea1(ReadOnlySpan data, GameVersion game) : base(game) + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + int rate = pk is PK1 pk1 ? pk1.Catch_Rate : -1; + foreach (var slot in Slots) { - Location = data[0]; - // 1 byte unused - Type = (SlotType)data[2]; - Rate = data[3]; - - var next = data[4..]; - int count = next.Length / 4; - var slots = new EncounterSlot1[count]; - for (int i = 0; i < slots.Length; i++) + foreach (var evo in chain) { - const int size = 4; - var entry = next.Slice(i * size, size); - byte max = entry[3]; - byte min = entry[2]; - byte slotNum = entry[1]; - byte species = entry[0]; - slots[i] = new EncounterSlot1(this, species, min, max, slotNum); - } - Slots = slots; - } + if (slot.Species != evo.Species) + continue; - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - int rate = pkm is PK1 pk1 ? pk1.Catch_Rate : -1; - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.LevelMin > evo.LevelMax) - break; - if (slot.Form != evo.Form) - break; - - if (rate != -1) - { - var expect = (slot.Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB)[slot.Species].CatchRate; - if (expect != rate && !(ParseSettings.AllowGen1Tradeback && GBRestrictions.IsTradebackCatchRate(rate))) - break; - } - yield return slot; + if (slot.LevelMin > evo.LevelMax) break; + if (slot.Form != evo.Form) + break; + + if (rate != -1) + { + var expect = (slot.Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB)[slot.Species].CatchRate; + if (expect != rate && !(ParseSettings.AllowGen1Tradeback && GBRestrictions.IsTradebackCatchRate(rate))) + break; } + yield return slot; + break; } } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea2.cs b/PKHeX.Core/Legality/Areas/EncounterArea2.cs index 6eacb78d0..0ce557fbc 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea2.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea2.cs @@ -1,135 +1,134 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea2 : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea2 : EncounterArea + private static readonly byte[] BCC_SlotRates = { 20, 20, 10, 10, 05, 05, 10, 10, 05, 05 }; + private static readonly byte[] RatesGrass = { 30, 30, 20, 10, 5, 4, 1 }; + private static readonly byte[] RatesSurf = { 60, 30, 10 }; + + internal readonly EncounterTime Time; + public readonly byte Rate; + public readonly IReadOnlyList Rates; + public readonly EncounterSlot2[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public static EncounterArea2[] GetAreas(BinLinkerAccessor input, GameVersion game) { - private static readonly byte[] BCC_SlotRates = { 20, 20, 10, 10, 05, 05, 10, 10, 05, 05 }; - private static readonly byte[] RatesGrass = { 30, 30, 20, 10, 5, 4, 1 }; - private static readonly byte[] RatesSurf = { 60, 30, 10 }; + var result = new EncounterArea2[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea2(input[i], game); + return result; + } - internal readonly EncounterTime Time; - public readonly byte Rate; - public readonly IReadOnlyList Rates; - public readonly EncounterSlot2[] Slots; + private EncounterArea2(ReadOnlySpan data, GameVersion game) : base(game) + { + Location = data[0]; + Time = (EncounterTime)data[1]; + var type = (Type = (SlotType)data[2]) & (SlotType)0xF; + Rate = data[3]; - protected override IReadOnlyList Raw => Slots; - - public static EncounterArea2[] GetAreas(BinLinkerAccessor input, GameVersion game) + var next = data[4..]; + if (type is > SlotType.Surf and not SlotType.BugContest) // Not Grass/Surf { - var result = new EncounterArea2[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea2(input[i], game); - return result; + const int size = 5; + int count = next.Length / size; + Rates = next[..count].ToArray(); + Slots = ReadSlots(next[count..], count); } - - private EncounterArea2(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = data[0]; - Time = (EncounterTime)data[1]; - var type = (Type = (SlotType)data[2]) & (SlotType)0xF; - Rate = data[3]; - - var next = data[4..]; - if (type is > SlotType.Surf and not SlotType.BugContest) // Not Grass/Surf - { - const int size = 5; - int count = next.Length / size; - Rates = next[..count].ToArray(); - Slots = ReadSlots(next[count..], count); - } - else - { - const int size = 4; - int count = next.Length / size; - Rates = type switch - { - SlotType.BugContest => BCC_SlotRates, - SlotType.Grass => RatesGrass, - _ => RatesSurf, - }; - Slots = ReadSlots(next, count); - } - } - - private EncounterSlot2[] ReadSlots(ReadOnlySpan data, int count) + else { const int size = 4; - var slots = new EncounterSlot2[count]; - for (int i = 0; i < slots.Length; i++) + int count = next.Length / size; + Rates = type switch { - var entry = data.Slice(i * size, size); - byte max = entry[3]; - byte min = entry[2]; - byte slotNum = entry[1]; - byte species = entry[0]; - slots[i] = new EncounterSlot2(this, species, min, max, slotNum); - } - return slots; + SlotType.BugContest => BCC_SlotRates, + SlotType.Grass => RatesGrass, + _ => RatesSurf, + }; + Slots = ReadSlots(next, count); } + } - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) + private EncounterSlot2[] ReadSlots(ReadOnlySpan data, int count) + { + const int size = 4; + var slots = new EncounterSlot2[count]; + for (int i = 0; i < slots.Length; i++) { - if (pkm is not ICaughtData2 {CaughtData: not 0} pk2) - return GetSlotsFuzzy(chain); - - if (pk2.Met_Location != Location) - return Array.Empty(); - return GetSlotsSpecificLevelTime(chain, pk2.Met_TimeOfDay, pk2.Met_Level); + var entry = data.Slice(i * size, size); + byte max = entry[3]; + byte min = entry[2]; + byte slotNum = entry[1]; + byte species = entry[0]; + slots[i] = new EncounterSlot2(this, species, min, max, slotNum); } + return slots; + } - private IEnumerable GetSlotsSpecificLevelTime(EvoCriteria[] chain, int time, int lvl) + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + if (pk is not ICaughtData2 {CaughtData: not 0} pk2) + return GetSlotsFuzzy(chain); + + if (pk2.Met_Location != Location) + return Array.Empty(); + return GetSlotsSpecificLevelTime(chain, pk2.Met_TimeOfDay, pk2.Met_Level); + } + + private IEnumerable GetSlotsSpecificLevelTime(EvoCriteria[] chain, int time, int lvl) + { + foreach (var slot in Slots) { - foreach (var slot in Slots) + foreach (var evo in chain) { - foreach (var evo in chain) + if (slot.Species != evo.Species) + continue; + + if (slot.Form != evo.Form) { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - { - if (slot.Species != (int)Species.Unown || evo.Form >= 26) // Don't yield !? forms - break; - } - - if (!slot.IsLevelWithinRange(lvl)) + if (slot.Species != (int)Species.Unown || evo.Form >= 26) // Don't yield !? forms break; - - if (!Time.Contains(time)) - break; - - yield return slot; - break; } + + if (!slot.IsLevelWithinRange(lvl)) + break; + + if (!Time.Contains(time)) + break; + + yield return slot; + break; } } + } - private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) + private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) + { + foreach (var slot in Slots) { - foreach (var slot in Slots) + foreach (var evo in chain) { - foreach (var evo in chain) + if (slot.Species != evo.Species) + continue; + + if (slot.Form != evo.Form) { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - { - if (slot.Species != (int) Species.Unown || evo.Form >= 26) // Don't yield !? forms - break; - } - if (slot.LevelMin > evo.LevelMax) + if (slot.Species != (int) Species.Unown || evo.Form >= 26) // Don't yield !? forms break; - - yield return slot; - break; } + if (slot.LevelMin > evo.LevelMax) + break; + + yield return slot; + break; } } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea3.cs b/PKHeX.Core/Legality/Areas/EncounterArea3.cs index 8bc96ccee..fe34564d1 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea3.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea3.cs @@ -2,164 +2,163 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea3 : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea3 : EncounterArea + public readonly int Rate; + public readonly EncounterSlot3[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public static EncounterArea3[] GetAreas(BinLinkerAccessor input, GameVersion game) { - public readonly int Rate; - public readonly EncounterSlot3[] Slots; + var result = new EncounterArea3[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea3(input[i], game); + return result; + } - protected override IReadOnlyList Raw => Slots; + public static EncounterArea3[] GetAreasSwarm(BinLinkerAccessor input, GameVersion game) + { + var result = new EncounterArea3[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea3(input[i], game, SlotType.Swarm | SlotType.Grass); + return result; + } - public static EncounterArea3[] GetAreas(BinLinkerAccessor input, GameVersion game) + private EncounterArea3(ReadOnlySpan data, GameVersion game) : base(game) + { + Location = data[0] | (data[1] << 8); + Type = (SlotType)data[2]; + Rate = data[3]; + + Slots = ReadRegularSlots(data); + } + + private EncounterArea3(ReadOnlySpan data, GameVersion game, SlotType type) : base(game) + { + Location = data[0] | (data[1] << 8); + Type = type; + Rate = data[3]; + + Slots = ReadSwarmSlots(data); + } + + private EncounterSlot3[] ReadRegularSlots(ReadOnlySpan data) + { + const int size = 10; + int count = (data.Length - 4) / size; + var slots = new EncounterSlot3[count]; + for (int i = 0; i < slots.Length; i++) { - var result = new EncounterArea3[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea3(input[i], game); - return result; + int offset = 4 + (size * i); + var entry = data.Slice(offset, size); + slots[i] = ReadRegularSlot(entry); } - public static EncounterArea3[] GetAreasSwarm(BinLinkerAccessor input, GameVersion game) + return slots; + } + + private EncounterSlot3 ReadRegularSlot(ReadOnlySpan entry) + { + ushort species = ReadUInt16LittleEndian(entry); + byte form = entry[2]; + byte slotNum = entry[3]; + byte min = entry[4]; + byte max = entry[5]; + + byte mpi = entry[6]; + byte mpc = entry[7]; + byte sti = entry[8]; + byte stc = entry[9]; + return new EncounterSlot3(this, species, form, min, max, slotNum, mpi, mpc, sti, stc); + } + + private EncounterSlot3[] ReadSwarmSlots(ReadOnlySpan data) + { + const int size = 14; + int count = (data.Length - 4) / size; + var slots = new EncounterSlot3[count]; + for (int i = 0; i < slots.Length; i++) { - var result = new EncounterArea3[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea3(input[i], game, SlotType.Swarm | SlotType.Grass); - return result; + int offset = 4 + (size * i); + var entry = data.Slice(offset, size); + slots[i] = ReadSwarmSlot(entry); } - private EncounterArea3(ReadOnlySpan data, GameVersion game) : base(game) + return slots; + } + + private EncounterSlot3Swarm ReadSwarmSlot(ReadOnlySpan entry) + { + ushort species = ReadUInt16LittleEndian(entry); + // form always 0 + byte slotNum = entry[3]; + byte min = entry[4]; + byte max = entry[5]; + + int[] moves = { - Location = data[0] | (data[1] << 8); - Type = (SlotType)data[2]; - Rate = data[3]; + ReadUInt16LittleEndian(entry[06..]), + ReadUInt16LittleEndian(entry[08..]), + ReadUInt16LittleEndian(entry[10..]), + ReadUInt16LittleEndian(entry[12..]), + }; - Slots = ReadRegularSlots(data); - } + return new EncounterSlot3Swarm(this, species, min, max, slotNum, moves); + } - private EncounterArea3(ReadOnlySpan data, GameVersion game, SlotType type) : base(game) + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + if (pk.Format != 3) // Met Location and Met Level are changed on PK3->PK4 + return GetSlotsFuzzy(chain); + if (pk.Met_Location != Location) + return Array.Empty(); + return GetSlotsMatching(chain, pk.Met_Level); + } + + private IEnumerable GetSlotsMatching(EvoCriteria[] chain, int lvl) + { + foreach (var slot in Slots) { - Location = data[0] | (data[1] << 8); - Type = type; - Rate = data[3]; - - Slots = ReadSwarmSlots(data); - } - - private EncounterSlot3[] ReadRegularSlots(ReadOnlySpan data) - { - const int size = 10; - int count = (data.Length - 4) / size; - var slots = new EncounterSlot3[count]; - for (int i = 0; i < slots.Length; i++) + foreach (var evo in chain) { - int offset = 4 + (size * i); - var entry = data.Slice(offset, size); - slots[i] = ReadRegularSlot(entry); - } + if (slot.Species != evo.Species) + continue; - return slots; - } - - private EncounterSlot3 ReadRegularSlot(ReadOnlySpan entry) - { - ushort species = ReadUInt16LittleEndian(entry); - byte form = entry[2]; - byte slotNum = entry[3]; - byte min = entry[4]; - byte max = entry[5]; - - byte mpi = entry[6]; - byte mpc = entry[7]; - byte sti = entry[8]; - byte stc = entry[9]; - return new EncounterSlot3(this, species, form, min, max, slotNum, mpi, mpc, sti, stc); - } - - private EncounterSlot3[] ReadSwarmSlots(ReadOnlySpan data) - { - const int size = 14; - int count = (data.Length - 4) / size; - var slots = new EncounterSlot3[count]; - for (int i = 0; i < slots.Length; i++) - { - int offset = 4 + (size * i); - var entry = data.Slice(offset, size); - slots[i] = ReadSwarmSlot(entry); - } - - return slots; - } - - private EncounterSlot3Swarm ReadSwarmSlot(ReadOnlySpan entry) - { - ushort species = ReadUInt16LittleEndian(entry); - // form always 0 - byte slotNum = entry[3]; - byte min = entry[4]; - byte max = entry[5]; - - int[] moves = - { - ReadUInt16LittleEndian(entry[06..]), - ReadUInt16LittleEndian(entry[08..]), - ReadUInt16LittleEndian(entry[10..]), - ReadUInt16LittleEndian(entry[12..]), - }; - - return new EncounterSlot3Swarm(this, species, min, max, slotNum, moves); - } - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - if (pkm.Format != 3) // Met Location and Met Level are changed on PK3->PK4 - return GetSlotsFuzzy(chain); - if (pkm.Met_Location != Location) - return Array.Empty(); - return GetSlotsMatching(chain, pkm.Met_Level); - } - - private IEnumerable GetSlotsMatching(EvoCriteria[] chain, int lvl) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - break; - if (!slot.IsLevelWithinRange(lvl)) - break; - - yield return slot; + if (slot.Form != evo.Form) break; - } - } - } - - private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - break; - if (slot.LevelMin > evo.LevelMax) - break; - - yield return slot; + if (!slot.IsLevelWithinRange(lvl)) break; - } + + yield return slot; + break; } } } -} \ No newline at end of file + + private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) + { + foreach (var slot in Slots) + { + foreach (var evo in chain) + { + if (slot.Species != evo.Species) + continue; + + if (slot.Form != evo.Form) + break; + if (slot.LevelMin > evo.LevelMax) + break; + + yield return slot; + break; + } + } + } +} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs b/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs index 8efe31319..65c2768be 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea3XD.cs @@ -1,76 +1,75 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea3XD : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea3XD : EncounterArea + public readonly EncounterSlot3PokeSpot[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public EncounterArea3XD(int loc, ushort s0, byte l0, ushort s1, byte l1, ushort s2, byte l2) : base(GameVersion.XD) { - public readonly EncounterSlot3PokeSpot[] Slots; - - protected override IReadOnlyList Raw => Slots; - - public EncounterArea3XD(int loc, ushort s0, byte l0, ushort s1, byte l1, ushort s2, byte l2) : base(GameVersion.XD) + Location = loc; + Type = SlotType.Grass; + Slots = new[] { - Location = loc; - Type = SlotType.Grass; - Slots = new[] + new EncounterSlot3PokeSpot(this, s0, 10, l0, 0), + new EncounterSlot3PokeSpot(this, s1, 10, l1, 1), + new EncounterSlot3PokeSpot(this, s2, 10, l2, 2), + }; + } + + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + if (pk.Format != 3) // Met Location and Met Level are changed on PK3->PK4 + return GetSlotsFuzzy(chain); + if (pk.Met_Location != Location) + return Array.Empty(); + return GetSlotsMatching(chain, pk.Met_Level); + } + + private IEnumerable GetSlotsMatching(EvoCriteria[] chain, int lvl) + { + foreach (var slot in Slots) + { + foreach (var evo in chain) { - new EncounterSlot3PokeSpot(this, s0, 10, l0, 0), - new EncounterSlot3PokeSpot(this, s1, 10, l1, 1), - new EncounterSlot3PokeSpot(this, s2, 10, l2, 2), - }; - } + if (slot.Species != evo.Species) + continue; - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - if (pkm.Format != 3) // Met Location and Met Level are changed on PK3->PK4 - return GetSlotsFuzzy(chain); - if (pkm.Met_Location != Location) - return Array.Empty(); - return GetSlotsMatching(chain, pkm.Met_Level); - } - - private IEnumerable GetSlotsMatching(EvoCriteria[] chain, int lvl) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form) - break; - if (!slot.IsLevelWithinRange(lvl)) - break; - - yield return slot; + if (slot.Form != evo.Form) break; - } + if (!slot.IsLevelWithinRange(lvl)) + break; + + yield return slot; + break; } } + } - private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) + private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) + { + foreach (var slot in Slots) { - foreach (var slot in Slots) + foreach (var evo in chain) { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; + if (slot.Species != evo.Species) + continue; - if (slot.Form != evo.Form) - break; - if (slot.LevelMin > evo.LevelMax) - break; - - yield return slot; + if (slot.Form != evo.Form) break; - } + if (slot.LevelMin > evo.LevelMax) + break; + + yield return slot; + break; } } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea4.cs b/PKHeX.Core/Legality/Areas/EncounterArea4.cs index f6ac8c422..b07560616 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea4.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea4.cs @@ -2,169 +2,168 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea4 : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea4 : EncounterArea + public readonly int Rate; + public readonly GroundTileAllowed GroundTile; + public readonly EncounterSlot4[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public static EncounterArea4[] GetAreas(BinLinkerAccessor input, GameVersion game) { - public readonly int Rate; - public readonly GroundTilePermission GroundTile; - public readonly EncounterSlot4[] Slots; + var result = new EncounterArea4[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea4(input[i], game); + return result; + } - protected override IReadOnlyList Raw => Slots; + private EncounterArea4(ReadOnlySpan data, GameVersion game) : base(game) + { + Location = ReadUInt16LittleEndian(data); + Type = (SlotType)data[2]; + Rate = data[3]; + // although GroundTilePermission flags are 32bit, none have values > 16bit. + GroundTile = (GroundTileAllowed)ReadUInt16LittleEndian(data[4..]); - public static EncounterArea4[] GetAreas(BinLinkerAccessor input, GameVersion game) + Slots = ReadRegularSlots(data); + } + + private EncounterSlot4[] ReadRegularSlots(ReadOnlySpan data) + { + const int size = 10; + int count = (data.Length - 6) / size; + var slots = new EncounterSlot4[count]; + for (int i = 0; i < slots.Length; i++) { - var result = new EncounterArea4[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea4(input[i], game); - return result; + int offset = 6 + (size * i); + var entry = data.Slice(offset, size); + slots[i] = ReadRegularSlot(entry); } - private EncounterArea4(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = ReadUInt16LittleEndian(data); - Type = (SlotType)data[2]; - Rate = data[3]; - // although GroundTilePermission flags are 32bit, none have values > 16bit. - GroundTile = (GroundTilePermission)ReadUInt16LittleEndian(data[4..]); + return slots; + } - Slots = ReadRegularSlots(data); - } + private EncounterSlot4 ReadRegularSlot(ReadOnlySpan entry) + { + ushort species = ReadUInt16LittleEndian(entry); + byte form = entry[2]; + byte slotNum = entry[3]; + byte min = entry[4]; + byte max = entry[5]; + byte mpi = entry[6]; + byte mpc = entry[7]; + byte sti = entry[8]; + byte stc = entry[9]; + return new EncounterSlot4(this, species, form, min, max, slotNum, mpi, mpc, sti, stc); + } - private EncounterSlot4[] ReadRegularSlots(ReadOnlySpan data) + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + if (pk.Format != 4) // Met Location and Met Level are changed on PK4->PK5 + return GetSlotsFuzzy(chain); + if (pk.Met_Location != Location) + return Array.Empty(); + return GetSlotsMatching(chain, pk.Met_Level, pk); + } + + private IEnumerable GetSlotsMatching(EvoCriteria[] chain, int lvl, PKM pk) + { + foreach (var slot in Slots) { - const int size = 10; - int count = (data.Length - 6) / size; - var slots = new EncounterSlot4[count]; - for (int i = 0; i < slots.Length; i++) + foreach (var evo in chain) { - int offset = 6 + (size * i); - var entry = data.Slice(offset, size); - slots[i] = ReadRegularSlot(entry); - } + if (slot.Species != evo.Species) + continue; - return slots; - } - - private EncounterSlot4 ReadRegularSlot(ReadOnlySpan entry) - { - ushort species = ReadUInt16LittleEndian(entry); - byte form = entry[2]; - byte slotNum = entry[3]; - byte min = entry[4]; - byte max = entry[5]; - byte mpi = entry[6]; - byte mpc = entry[7]; - byte sti = entry[8]; - byte stc = entry[9]; - return new EncounterSlot4(this, species, form, min, max, slotNum, mpi, mpc, sti, stc); - } - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - if (pkm.Format != 4) // Met Location and Met Level are changed on PK4->PK5 - return GetSlotsFuzzy(chain); - if (pkm.Met_Location != Location) - return Array.Empty(); - return GetSlotsMatching(chain, pkm.Met_Level, pkm); - } - - private IEnumerable GetSlotsMatching(EvoCriteria[] chain, int lvl, PKM pk) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) + if (slot.Form != evo.Form && slot.Species is not (int)Species.Burmy) { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form && slot.Species is not (int)Species.Burmy) - { - // Unown forms are random, not specific form IDs - if (!slot.IsRandomUnspecificForm) - break; - } - if (!slot.IsLevelWithinRange(lvl)) + // Unown forms are random, not specific form IDs + if (!slot.IsRandomUnspecificForm) break; - - if (Type is SlotType.HoneyTree && IsInaccessibleHoneySlotLocation(slot, pk)) - break; - - yield return slot; - break; } - } - } - - private static bool IsInaccessibleHoneySlotLocation(EncounterSlot4 slot, PKM pk) - { - // A/B/C tables, only Munchlax is a 'C' encounter, and A/B are accessible from any tree. - // C table encounters are only available from 4 trees, which are determined by TID/SID of the save file. - if (slot.Species is not (int)Species.Munchlax) - return false; - - // We didn't encode the honey tree index to the encounter slot resource. - // Check if any of the slot's location doesn't match any of the groupC trees' area location ID. - var location = pk.Met_Location; - var trees = SAV4Sinnoh.CalculateMunchlaxTrees(pk.TID, pk.SID); - return LocationID_HoneyTree[trees.Tree1] != location - && LocationID_HoneyTree[trees.Tree2] != location - && LocationID_HoneyTree[trees.Tree3] != location - && LocationID_HoneyTree[trees.Tree4] != location; - } - - private static readonly byte[] LocationID_HoneyTree = - { - 20, // 00 Route 205 Floaroma - 20, // 01 Route 205 Eterna - 21, // 02 Route 206 - 22, // 03 Route 207 - 23, // 04 Route 208 - 24, // 05 Route 209 - 25, // 06 Route 210 Solaceon - 25, // 07 Route 210 Celestic - 26, // 08 Route 211 - 27, // 09 Route 212 Hearthome - 27, // 10 Route 212 Pastoria - 28, // 11 Route 213 - 29, // 12 Route 214 - 30, // 13 Route 215 - 33, // 14 Route 218 - 36, // 15 Route 221 - 37, // 16 Route 222 - 47, // 17 Valley Windworks - 48, // 18 Eterna Forest - 49, // 19 Fuego Ironworks - 58, // 20 Floaroma Meadow - }; - - // original met level cannot be inferred - private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (slot.Form != evo.Form && slot.Species is not (int)Species.Burmy) - { - // Unown forms are random, not specific form IDs - if (!slot.IsRandomUnspecificForm) - break; - } - if (slot.LevelMin > evo.LevelMax) - break; - - yield return slot; + if (!slot.IsLevelWithinRange(lvl)) break; - } + + if (Type is SlotType.HoneyTree && IsInaccessibleHoneySlotLocation(slot, pk)) + break; + + yield return slot; + break; } } } -} \ No newline at end of file + + private static bool IsInaccessibleHoneySlotLocation(EncounterSlot4 slot, PKM pk) + { + // A/B/C tables, only Munchlax is a 'C' encounter, and A/B are accessible from any tree. + // C table encounters are only available from 4 trees, which are determined by TID/SID of the save file. + if (slot.Species is not (int)Species.Munchlax) + return false; + + // We didn't encode the honey tree index to the encounter slot resource. + // Check if any of the slot's location doesn't match any of the groupC trees' area location ID. + var location = pk.Met_Location; + var trees = SAV4Sinnoh.CalculateMunchlaxTrees(pk.TID, pk.SID); + return LocationID_HoneyTree[trees.Tree1] != location + && LocationID_HoneyTree[trees.Tree2] != location + && LocationID_HoneyTree[trees.Tree3] != location + && LocationID_HoneyTree[trees.Tree4] != location; + } + + private static readonly byte[] LocationID_HoneyTree = + { + 20, // 00 Route 205 Floaroma + 20, // 01 Route 205 Eterna + 21, // 02 Route 206 + 22, // 03 Route 207 + 23, // 04 Route 208 + 24, // 05 Route 209 + 25, // 06 Route 210 Solaceon + 25, // 07 Route 210 Celestic + 26, // 08 Route 211 + 27, // 09 Route 212 Hearthome + 27, // 10 Route 212 Pastoria + 28, // 11 Route 213 + 29, // 12 Route 214 + 30, // 13 Route 215 + 33, // 14 Route 218 + 36, // 15 Route 221 + 37, // 16 Route 222 + 47, // 17 Valley Windworks + 48, // 18 Eterna Forest + 49, // 19 Fuego Ironworks + 58, // 20 Floaroma Meadow + }; + + // original met level cannot be inferred + private IEnumerable GetSlotsFuzzy(EvoCriteria[] chain) + { + foreach (var slot in Slots) + { + foreach (var evo in chain) + { + if (slot.Species != evo.Species) + continue; + + if (slot.Form != evo.Form && slot.Species is not (int)Species.Burmy) + { + // Unown forms are random, not specific form IDs + if (!slot.IsRandomUnspecificForm) + break; + } + if (slot.LevelMin > evo.LevelMax) + break; + + yield return slot; + break; + } + } + } +} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea5.cs b/PKHeX.Core/Legality/Areas/EncounterArea5.cs index 00bcf7e5d..91c172936 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea5.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea5.cs @@ -2,79 +2,78 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea5 : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea5 : EncounterArea + public readonly EncounterSlot5[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public static EncounterArea5[] GetAreas(BinLinkerAccessor input, GameVersion game) { - public readonly EncounterSlot5[] Slots; + var result = new EncounterArea5[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea5(input[i], game); + return result; + } - protected override IReadOnlyList Raw => Slots; + private EncounterArea5(ReadOnlySpan data, GameVersion game) : base(game) + { + Location = ReadUInt16LittleEndian(data); + Type = (SlotType)data[2]; - public static EncounterArea5[] GetAreas(BinLinkerAccessor input, GameVersion game) + Slots = ReadSlots(data); + } + + private EncounterSlot5[] ReadSlots(ReadOnlySpan data) + { + const int size = 4; + int count = (data.Length - 4) / size; + var slots = new EncounterSlot5[count]; + for (int i = 0; i < slots.Length; i++) { - var result = new EncounterArea5[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea5(input[i], game); - return result; + int offset = 4 + (size * i); + var entry = data.Slice(offset, size); + slots[i] = ReadSlot(entry); } - private EncounterArea5(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = ReadUInt16LittleEndian(data); - Type = (SlotType)data[2]; + return slots; + } - Slots = ReadSlots(data); - } + private EncounterSlot5 ReadSlot(ReadOnlySpan entry) + { + ushort species = ReadUInt16LittleEndian(entry); + byte form = (byte)(species >> 11); + species &= 0x3FF; + byte min = entry[2]; + byte max = entry[3]; + return new EncounterSlot5(this, species, form, min, max); + } - private EncounterSlot5[] ReadSlots(ReadOnlySpan data) + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + foreach (var slot in Slots) { - const int size = 4; - int count = (data.Length - 4) / size; - var slots = new EncounterSlot5[count]; - for (int i = 0; i < slots.Length; i++) + foreach (var evo in chain) { - int offset = 4 + (size * i); - var entry = data.Slice(offset, size); - slots[i] = ReadSlot(entry); - } + if (slot.Species != evo.Species) + continue; - return slots; - } - - private EncounterSlot5 ReadSlot(ReadOnlySpan entry) - { - ushort species = ReadUInt16LittleEndian(entry); - byte form = (byte)(species >> 11); - species &= 0x3FF; - byte min = entry[2]; - byte max = entry[3]; - return new EncounterSlot5(this, species, form, min, max); - } - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(pkm.Met_Level)) - break; - - // Deerling and Sawsbuck can change forms when seasons change, thus can be any of the [0,3] form values. - // no other wild forms can change - if (slot.Form != evo.Form && slot.Species is not ((int)Species.Deerling or (int)Species.Sawsbuck)) - break; - - yield return slot; + if (!slot.IsLevelWithinRange(pk.Met_Level)) break; - } + + // Deerling and Sawsbuck can change forms when seasons change, thus can be any of the [0,3] form values. + // no other wild forms can change + if (slot.Form != evo.Form && slot.Species is not ((int)Species.Deerling or (int)Species.Sawsbuck)) + break; + + yield return slot; + break; } } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs b/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs index 974daf748..4345b6a10 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea6AO.cs @@ -2,90 +2,89 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea6AO : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea6AO : EncounterArea + public readonly EncounterSlot6AO[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public static EncounterArea6AO[] GetAreas(BinLinkerAccessor input, GameVersion game) { - public readonly EncounterSlot6AO[] Slots; + var result = new EncounterArea6AO[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea6AO(input[i], game); + return result; + } - protected override IReadOnlyList Raw => Slots; + private EncounterArea6AO(ReadOnlySpan data, GameVersion game) : base(game) + { + Location = ReadInt16LittleEndian(data); + Type = (SlotType)data[2]; - public static EncounterArea6AO[] GetAreas(BinLinkerAccessor input, GameVersion game) + Slots = ReadSlots(data); + } + + private EncounterSlot6AO[] ReadSlots(ReadOnlySpan data) + { + const int size = 4; + int count = (data.Length - 4) / size; + var slots = new EncounterSlot6AO[count]; + for (int i = 0; i < slots.Length; i++) { - var result = new EncounterArea6AO[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea6AO(input[i], game); - return result; + int offset = 4 + (size * i); + var entry = data.Slice(offset, size); + slots[i] = ReadSlot(entry); } - private EncounterArea6AO(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = ReadInt16LittleEndian(data); - Type = (SlotType)data[2]; + return slots; + } - Slots = ReadSlots(data); - } + private EncounterSlot6AO ReadSlot(ReadOnlySpan entry) + { + ushort species = ReadUInt16LittleEndian(entry); + byte form = (byte)(species >> 11); + species &= 0x3FF; + byte min = entry[2]; + byte max = entry[3]; + return new EncounterSlot6AO(this, species, form, min, max); + } - private EncounterSlot6AO[] ReadSlots(ReadOnlySpan data) + private const int FluteBoostMin = 4; // White Flute decreases levels. + private const int FluteBoostMax = 4; // Black Flute increases levels. + private const int DexNavBoost = 30; // Maximum DexNav chain + + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + foreach (var slot in Slots) { - const int size = 4; - int count = (data.Length - 4) / size; - var slots = new EncounterSlot6AO[count]; - for (int i = 0; i < slots.Length; i++) + foreach (var evo in chain) { - int offset = 4 + (size * i); - var entry = data.Slice(offset, size); - slots[i] = ReadSlot(entry); - } + if (slot.Species != evo.Species) + continue; - return slots; - } - - private EncounterSlot6AO ReadSlot(ReadOnlySpan entry) - { - ushort species = ReadUInt16LittleEndian(entry); - byte form = (byte)(species >> 11); - species &= 0x3FF; - byte min = entry[2]; - byte max = entry[3]; - return new EncounterSlot6AO(this, species, form, min, max); - } - - private const int FluteBoostMin = 4; // White Flute decreases levels. - private const int FluteBoostMax = 4; // Black Flute increases levels. - private const int DexNavBoost = 30; // Maximum DexNav chain - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - var boostMax = Type != SlotType.Rock_Smash ? DexNavBoost : FluteBoostMax; - const int boostMin = FluteBoostMin; - if (!slot.IsLevelWithinRange(pkm.Met_Level, boostMin, boostMax)) - break; - - if (slot.Form != evo.Form && !slot.IsRandomUnspecificForm) - break; - - // Track some metadata about how this slot was matched. - var clone = slot with - { - WhiteFlute = evo.LevelMin < slot.LevelMin, - BlackFlute = evo.LevelMin > slot.LevelMax && evo.LevelMin <= slot.LevelMax + FluteBoostMax, - DexNav = slot.CanDexNav && (evo.LevelMin != slot.LevelMax || pkm.RelearnMove1 != 0 || pkm.AbilityNumber == 4), - }; - yield return clone; + var boostMax = Type != SlotType.Rock_Smash ? DexNavBoost : FluteBoostMax; + const int boostMin = FluteBoostMin; + if (!slot.IsLevelWithinRange(pk.Met_Level, boostMin, boostMax)) break; - } + + if (slot.Form != evo.Form && !slot.IsRandomUnspecificForm) + break; + + // Track some metadata about how this slot was matched. + var clone = slot with + { + WhiteFlute = evo.LevelMin < slot.LevelMin, + BlackFlute = evo.LevelMin > slot.LevelMax && evo.LevelMin <= slot.LevelMax + FluteBoostMax, + DexNav = slot.CanDexNav && (evo.LevelMin != slot.LevelMax || pk.RelearnMove1 != 0 || pk.AbilityNumber == 4), + }; + yield return clone; + break; } } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea6XY.cs b/PKHeX.Core/Legality/Areas/EncounterArea6XY.cs index 395a1ab23..eb8fc1138 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea6XY.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea6XY.cs @@ -2,158 +2,157 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea6XY : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea6XY : EncounterArea + public readonly EncounterSlot6XY[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public static EncounterArea6XY[] GetAreas(BinLinkerAccessor input, GameVersion game, EncounterArea6XY safari) { - public readonly EncounterSlot6XY[] Slots; + int count = input.Length; + var result = new EncounterArea6XY[count + 1]; + for (int i = 0; i < count; i++) + result[i] = new EncounterArea6XY(input[i], game); + result[^1] = safari; + return result; + } - protected override IReadOnlyList Raw => Slots; + public EncounterArea6XY() : base(GameVersion.XY) + { + Location = 148; // Friend Safari + Type = SlotType.FriendSafari; - public static EncounterArea6XY[] GetAreas(BinLinkerAccessor input, GameVersion game, EncounterArea6XY safari) + Slots = LoadSafariSlots(); + } + + private EncounterArea6XY(ReadOnlySpan data, GameVersion game) : base(game) + { + Location = data[0] | (data[1] << 8); + Type = (SlotType)data[2]; + + Slots = ReadSlots(data); + } + + private EncounterSlot6XY[] LoadSafariSlots() + { + const int SpeciesFormSlots = 4; + + // Single form species + Span species = stackalloc ushort[] { - int count = input.Length; - var result = new EncounterArea6XY[count + 1]; - for (int i = 0; i < count; i++) - result[i] = new EncounterArea6XY(input[i], game); - result[^1] = safari; - return result; + 002, 005, 008, 012, 014, 016, 021, 025, 027, 035, + 038, 039, 043, 044, 046, 049, 049, 051, 056, 058, + 061, 063, 067, 077, 082, 083, 084, 087, 089, 091, + 095, 096, 098, 101, 105, 112, 113, 114, 125, 126, + 127, 130, 131, 132, 133, 148, 163, 165, 168, 175, + 178, 184, 190, 191, 194, 195, 202, 203, 205, 206, + 209, 213, 214, 215, 215, 216, 218, 219, 221, 222, + 224, 225, 227, 231, 235, 236, 247, 262, 267, 268, + 274, 281, 284, 286, 290, 294, 297, 299, 302, 303, + 303, 307, 310, 313, 314, 317, 323, 326, 328, 332, + 336, 342, 352, 353, 356, 357, 359, 361, 363, 372, + 375, 400, 404, 415, 417, 419, 423, 426, 437, 442, + 444, 447, 452, 454, 459, 506, 510, 511, 513, 515, + 517, 520, 523, 525, 527, 530, 531, 536, 538, 539, + 541, 544, 548, 551, 556, 557, 561, 569, 572, 575, + 578, 581, 586, 587, 596, 597, 600, 608, 611, 614, + 618, 619, 621, 623, 624, 627, 629, 636, 651, 654, + 657, 660, 662, 662, 668, 673, 674, 677, 682, 684, + 686, 689, 694, 701, 702, 702, 705, 707, 708, 710, + 712, 714, + }; + + var slots = new EncounterSlot6XY[species.Length + SpeciesFormSlots]; + int i = 0; + for (; i < species.Length; i++) + slots[i] = new EncounterSlot6XY(this, species[i], 0, 30, 30); + + // Floette has 3 separate forms (RBY) + slots[i++] = new EncounterSlot6XY(this, (int)Species.Floette, 0, 30, 30); + slots[i++] = new EncounterSlot6XY(this, (int)Species.Floette, 1, 30, 30); + slots[i++] = new EncounterSlot6XY(this, (int)Species.Floette, 3, 30, 30); + + // Region Random Vivillon + slots[i] = new EncounterSlot6XY(this, (int)Species.Vivillon, EncounterSlot.FormVivillon, 30, 30); + return slots; + } + + private EncounterSlot6XY[] ReadSlots(ReadOnlySpan data) + { + const int size = 4; + int count = (data.Length - 4) / size; + var slots = new EncounterSlot6XY[count]; + for (int i = 0; i < slots.Length; i++) + { + int offset = 4 + (size * i); + var entry = data.Slice(offset, size); + ushort species = ReadUInt16LittleEndian(entry); + byte form = (byte)(species >> 11); + species &= 0x3FF; + byte min = entry[2]; + byte max = entry[3]; + slots[i] = new EncounterSlot6XY(this, species, form, min, max); } - public EncounterArea6XY() : base(GameVersion.XY) + return slots; + } + + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + foreach (var slot in Slots) { - Location = 148; // Friend Safari - Type = SlotType.FriendSafari; - - Slots = LoadSafariSlots(); - } - - private EncounterArea6XY(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = data[0] | (data[1] << 8); - Type = (SlotType)data[2]; - - Slots = ReadSlots(data); - } - - private EncounterSlot6XY[] LoadSafariSlots() - { - const int SpeciesFormSlots = 4; - - // Single form species - Span species = stackalloc ushort[] + foreach (var evo in chain) { - 002, 005, 008, 012, 014, 016, 021, 025, 027, 035, - 038, 039, 043, 044, 046, 049, 049, 051, 056, 058, - 061, 063, 067, 077, 082, 083, 084, 087, 089, 091, - 095, 096, 098, 101, 105, 112, 113, 114, 125, 126, - 127, 130, 131, 132, 133, 148, 163, 165, 168, 175, - 178, 184, 190, 191, 194, 195, 202, 203, 205, 206, - 209, 213, 214, 215, 215, 216, 218, 219, 221, 222, - 224, 225, 227, 231, 235, 236, 247, 262, 267, 268, - 274, 281, 284, 286, 290, 294, 297, 299, 302, 303, - 303, 307, 310, 313, 314, 317, 323, 326, 328, 332, - 336, 342, 352, 353, 356, 357, 359, 361, 363, 372, - 375, 400, 404, 415, 417, 419, 423, 426, 437, 442, - 444, 447, 452, 454, 459, 506, 510, 511, 513, 515, - 517, 520, 523, 525, 527, 530, 531, 536, 538, 539, - 541, 544, 548, 551, 556, 557, 561, 569, 572, 575, - 578, 581, 586, 587, 596, 597, 600, 608, 611, 614, - 618, 619, 621, 623, 624, 627, 629, 636, 651, 654, - 657, 660, 662, 662, 668, 673, 674, 677, 682, 684, - 686, 689, 694, 701, 702, 702, 705, 707, 708, 710, - 712, 714, - }; + if (slot.Species != evo.Species) + continue; - var slots = new EncounterSlot6XY[species.Length + SpeciesFormSlots]; - int i = 0; - for (; i < species.Length; i++) - slots[i] = new EncounterSlot6XY(this, species[i], 0, 30, 30); + if (!slot.IsLevelWithinRange(pk.Met_Level)) + break; - // Floette has 3 separate forms (RBY) - slots[i++] = new EncounterSlot6XY(this, (int)Species.Floette, 0, 30, 30); - slots[i++] = new EncounterSlot6XY(this, (int)Species.Floette, 1, 30, 30); - slots[i++] = new EncounterSlot6XY(this, (int)Species.Floette, 3, 30, 30); - - // Region Random Vivillon - slots[i] = new EncounterSlot6XY(this, (int)Species.Vivillon, EncounterSlot.FormVivillon, 30, 30); - return slots; - } - - private EncounterSlot6XY[] ReadSlots(ReadOnlySpan data) - { - const int size = 4; - int count = (data.Length - 4) / size; - var slots = new EncounterSlot6XY[count]; - for (int i = 0; i < slots.Length; i++) - { - int offset = 4 + (size * i); - var entry = data.Slice(offset, size); - ushort species = ReadUInt16LittleEndian(entry); - byte form = (byte)(species >> 11); - species &= 0x3FF; - byte min = entry[2]; - byte max = entry[3]; - slots[i] = new EncounterSlot6XY(this, species, form, min, max); - } - - return slots; - } - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) + if (slot.Form != evo.Form && !slot.IsRandomUnspecificForm && slot.Species is not ((int)Species.Burmy or (int)Species.Furfrou)) { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(pkm.Met_Level)) + // Only slot that can be form-mismatched via Pressure is Flabébé + if (slot.Species != (int)Species.Flabébé) break; - if (slot.Form != evo.Form && !slot.IsRandomUnspecificForm && slot.Species is not ((int)Species.Burmy or (int)Species.Furfrou)) - { - // Only slot that can be form-mismatched via Pressure is Flabébé - if (slot.Species != (int)Species.Flabébé) - break; - - var maxLevel = slot.LevelMax; - if (!ExistsPressureSlot(evo, ref maxLevel)) - break; - - if (maxLevel != pkm.Met_Level) - break; - - yield return slot.CreatePressureFormCopy(evo.Form); + var maxLevel = slot.LevelMax; + if (!ExistsPressureSlot(evo, ref maxLevel)) break; - } - yield return slot; + if (maxLevel != pk.Met_Level) + break; + + yield return slot.CreatePressureFormCopy(evo.Form); break; } - } - } - private bool ExistsPressureSlot(EvoCriteria evo, ref byte level) - { - bool existsForm = false; - foreach (var z in Slots) - { - if (z.Species != evo.Species) - continue; - if (z.Form == evo.Form) - continue; - if (z.LevelMax < level) - continue; - level = z.LevelMax; - existsForm = true; + yield return slot; + break; } - return existsForm; } } + + private bool ExistsPressureSlot(EvoCriteria evo, ref byte level) + { + bool existsForm = false; + foreach (var z in Slots) + { + if (z.Species != evo.Species) + continue; + if (z.Form == evo.Form) + continue; + if (z.LevelMax < level) + continue; + level = z.LevelMax; + existsForm = true; + } + return existsForm; + } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea7.cs b/PKHeX.Core/Legality/Areas/EncounterArea7.cs index 70d965b28..065920132 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea7.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea7.cs @@ -2,80 +2,79 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea7 : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea7 : EncounterArea + public readonly EncounterSlot7[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public static EncounterArea7[] GetAreas(BinLinkerAccessor input, GameVersion game) { - public readonly EncounterSlot7[] Slots; + var result = new EncounterArea7[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea7(input[i], game); + return result; + } - protected override IReadOnlyList Raw => Slots; + private EncounterArea7(ReadOnlySpan data, GameVersion game) : base(game) + { + Location = data[0] | (data[1] << 8); + Type = (SlotType)data[2]; - public static EncounterArea7[] GetAreas(BinLinkerAccessor input, GameVersion game) + Slots = ReadSlots(data); + } + + private EncounterSlot7[] ReadSlots(ReadOnlySpan data) + { + const int size = 4; + int count = (data.Length - 4) / size; + var slots = new EncounterSlot7[count]; + for (int i = 0; i < slots.Length; i++) { - var result = new EncounterArea7[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea7(input[i], game); - return result; + int offset = 4 + (size * i); + var entry = data.Slice(offset, size); + slots[i] = ReadSlot(entry); } - private EncounterArea7(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = data[0] | (data[1] << 8); - Type = (SlotType)data[2]; + return slots; + } - Slots = ReadSlots(data); - } + private EncounterSlot7 ReadSlot(ReadOnlySpan entry) + { + ushort species = ReadUInt16LittleEndian(entry); + byte form = (byte)(species >> 11); + species &= 0x3FF; + byte min = entry[2]; + byte max = entry[3]; + return new EncounterSlot7(this, species, form, min, max); + } - private EncounterSlot7[] ReadSlots(ReadOnlySpan data) + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + foreach (var slot in Slots) { - const int size = 4; - int count = (data.Length - 4) / size; - var slots = new EncounterSlot7[count]; - for (int i = 0; i < slots.Length; i++) + foreach (var evo in chain) { - int offset = 4 + (size * i); - var entry = data.Slice(offset, size); - slots[i] = ReadSlot(entry); - } + if (slot.Species != evo.Species) + continue; - return slots; - } - - private EncounterSlot7 ReadSlot(ReadOnlySpan entry) - { - ushort species = ReadUInt16LittleEndian(entry); - byte form = (byte)(species >> 11); - species &= 0x3FF; - byte min = entry[2]; - byte max = entry[3]; - return new EncounterSlot7(this, species, form, min, max); - } - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(pkm.Met_Level)) - break; - - if (slot.Form != evo.Form && slot.Species is not ((int)Species.Furfrou or (int)Species.Oricorio)) - { - if (!slot.IsRandomUnspecificForm) // Minior, etc - break; - } - - yield return slot; + if (!slot.IsLevelWithinRange(pk.Met_Level)) break; + + if (slot.Form != evo.Form && slot.Species is not ((int)Species.Furfrou or (int)Species.Oricorio)) + { + if (!slot.IsRandomUnspecificForm) // Minior, etc + break; } + + yield return slot; + break; } } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea7b.cs b/PKHeX.Core/Legality/Areas/EncounterArea7b.cs index dceae8026..db5bee063 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea7b.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea7b.cs @@ -1,76 +1,75 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea7b : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea7b : EncounterArea + public readonly EncounterSlot7b[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public static EncounterArea7b[] GetAreas(BinLinkerAccessor input, GameVersion game) { - public readonly EncounterSlot7b[] Slots; + var result = new EncounterArea7b[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea7b(input[i], game); + return result; + } - protected override IReadOnlyList Raw => Slots; + private EncounterArea7b(ReadOnlySpan data, GameVersion game) : base(game) + { + Location = data[0] | (data[1] << 8); + Slots = ReadSlots(data); + } - public static EncounterArea7b[] GetAreas(BinLinkerAccessor input, GameVersion game) + private EncounterSlot7b[] ReadSlots(ReadOnlySpan data) + { + const int size = 4; + int count = (data.Length - 2) / size; + var slots = new EncounterSlot7b[count]; + for (int i = 0; i < slots.Length; i++) { - var result = new EncounterArea7b[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea7b(input[i], game); - return result; + int offset = 2 + (size * i); + var entry = data.Slice(offset, size); + slots[i] = ReadSlot(entry); } - private EncounterArea7b(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = data[0] | (data[1] << 8); - Slots = ReadSlots(data); - } + return slots; + } - private EncounterSlot7b[] ReadSlots(ReadOnlySpan data) + private EncounterSlot7b ReadSlot(ReadOnlySpan entry) + { + int species = entry[0]; // always < 255; only original 151 + // form is always 0 + byte min = entry[2]; + byte max = entry[3]; + return new EncounterSlot7b(this, species, min, max); + } + + private const int CatchComboBonus = 1; + + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + foreach (var slot in Slots) { - const int size = 4; - int count = (data.Length - 2) / size; - var slots = new EncounterSlot7b[count]; - for (int i = 0; i < slots.Length; i++) + foreach (var evo in chain) { - int offset = 2 + (size * i); - var entry = data.Slice(offset, size); - slots[i] = ReadSlot(entry); - } + if (slot.Species != evo.Species) + continue; - return slots; - } - - private EncounterSlot7b ReadSlot(ReadOnlySpan entry) - { - int species = entry[0]; // always < 255; only original 151 - // form is always 0 - byte min = entry[2]; - byte max = entry[3]; - return new EncounterSlot7b(this, species, min, max); - } - - private const int CatchComboBonus = 1; - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - var met = pkm.Met_Level; - if (!slot.IsLevelWithinRange(met, 0, CatchComboBonus)) - break; - if (slot.Form != evo.Form) - break; - - yield return slot; + var met = pk.Met_Level; + if (!slot.IsLevelWithinRange(met, 0, CatchComboBonus)) break; - } + if (slot.Form != evo.Form) + break; + + yield return slot; + break; } } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea7g.cs b/PKHeX.Core/Legality/Areas/EncounterArea7g.cs index f89ebbbea..7a6248437 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea7g.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea7g.cs @@ -2,109 +2,108 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area for +/// +public sealed record EncounterArea7g : EncounterArea, ISpeciesForm { - /// - /// - /// encounter area for - /// - public sealed record EncounterArea7g : EncounterArea, ISpeciesForm + /// Species for the area + /// Due to how the encounter data is packaged by PKHeX, each species-form is grouped together. + public int Species { get; } + /// Form of the Species + public int Form { get; } + public readonly EncounterSlot7GO[] Slots; + + protected override IReadOnlyList Raw => Slots; + + private EncounterArea7g(ushort species, byte form, EncounterSlot7GO[] slots) : base(GameVersion.GO) { - /// Species for the area - /// Due to how the encounter data is packaged by PKHeX, each species-form is grouped together. - public int Species { get; } - /// Form of the Species - public int Form { get; } - public readonly EncounterSlot7GO[] Slots; + Species = species; + Form = form; + Location = Locations.GO7; + Slots = slots; + } - protected override IReadOnlyList Raw => Slots; + internal static EncounterArea7g[] GetArea(BinLinkerAccessor data) + { + var areas = new EncounterArea7g[data.Length]; + for (int i = 0; i < areas.Length; i++) + areas[i] = GetArea(data[i]); + return areas; + } - private EncounterArea7g(ushort species, byte form, EncounterSlot7GO[] slots) : base(GameVersion.GO) + private const int meta = 4; + private const int entrySize = (2 * sizeof(int)) + 2; + + private static EncounterArea7g GetArea(ReadOnlySpan data) + { + var species = ReadUInt16LittleEndian(data); + var form = data[2]; + //var import = (EntityFormatDetected)data[3]; + + data = data[meta..]; + var result = new EncounterSlot7GO[data.Length / entrySize]; + var area = new EncounterArea7g(species, form, result); + for (int i = 0; i < result.Length; i++) { - Species = species; - Form = form; - Location = Locations.GO7; - Slots = slots; + var offset = i * entrySize; + var entry = data.Slice(offset, entrySize); + result[i] = ReadSlot(entry, area, species, form); } - internal static EncounterArea7g[] GetArea(BinLinkerAccessor data) + return area; + } + + private static EncounterSlot7GO ReadSlot(ReadOnlySpan entry, EncounterArea7g area, ushort species, byte form) + { + int start = ReadInt32LittleEndian(entry); + int end = ReadInt32LittleEndian(entry[4..]); + var sg = entry[8]; + var shiny = (Shiny)(sg & 0x3F); + var gender = (Gender)(sg >> 6); + var type = (PogoType)entry[9]; + return new EncounterSlot7GO(area, species, form, start, end, shiny, gender, type); + } + + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + // Find the first chain that has slots defined. + // Since it is possible to evolve before transferring, we only need the highest evolution species possible. + // PoGoEncTool has already extrapolated the evolutions to separate encounters! + var sf = Array.Find(chain, z => z.Species == Species && z.Form == Form); + if (sf == default) + yield break; + + var stamp = EncounterSlotGO.GetTimeStamp(pk.Met_Year + 2000, pk.Met_Month, pk.Met_Day); + var met = Math.Max(sf.LevelMin, pk.Met_Level); + EncounterSlot7GO? deferredIV = null; + + foreach (var slot in Slots) { - var areas = new EncounterArea7g[data.Length]; - for (int i = 0; i < areas.Length; i++) - areas[i] = GetArea(data[i]); - return areas; - } + if (!slot.IsLevelWithinRange(met)) + continue; + //if (!slot.IsBallValid(ball)) -- can have any of the in-game balls due to re-capture + // continue; + if (!slot.Shiny.IsValid(pk)) + continue; + //if (slot.Gender != Gender.Random && (int) slot.Gender != pk.Gender) + // continue; + if (!slot.IsWithinStartEnd(stamp)) + continue; - private const int meta = 4; - private const int entrySize = (2 * sizeof(int)) + 2; - - private static EncounterArea7g GetArea(ReadOnlySpan data) - { - var species = ReadUInt16LittleEndian(data); - var form = data[2]; - //var import = (EntityFormatDetected)data[3]; - - data = data[meta..]; - var result = new EncounterSlot7GO[data.Length / entrySize]; - var area = new EncounterArea7g(species, form, result); - for (int i = 0; i < result.Length; i++) + if (!slot.GetIVsValid(pk)) { - var offset = i * entrySize; - var entry = data.Slice(offset, entrySize); - result[i] = ReadSlot(entry, area, species, form); + deferredIV ??= slot; + continue; } - return area; + yield return slot; } - private static EncounterSlot7GO ReadSlot(ReadOnlySpan entry, EncounterArea7g area, ushort species, byte form) - { - int start = ReadInt32LittleEndian(entry); - int end = ReadInt32LittleEndian(entry[4..]); - var sg = entry[8]; - var shiny = (Shiny)(sg & 0x3F); - var gender = (Gender)(sg >> 6); - var type = (PogoType)entry[9]; - return new EncounterSlot7GO(area, species, form, start, end, shiny, gender, type); - } - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - // Find the first chain that has slots defined. - // Since it is possible to evolve before transferring, we only need the highest evolution species possible. - // PoGoEncTool has already extrapolated the evolutions to separate encounters! - var sf = Array.Find(chain, z => z.Species == Species && z.Form == Form); - if (sf == default) - yield break; - - var stamp = EncounterSlotGO.GetTimeStamp(pkm.Met_Year + 2000, pkm.Met_Month, pkm.Met_Day); - var met = Math.Max(sf.LevelMin, pkm.Met_Level); - EncounterSlot7GO? deferredIV = null; - - foreach (var slot in Slots) - { - if (!slot.IsLevelWithinRange(met)) - continue; - //if (!slot.IsBallValid(ball)) -- can have any of the in-game balls due to re-capture - // continue; - if (!slot.Shiny.IsValid(pkm)) - continue; - //if (slot.Gender != Gender.Random && (int) slot.Gender != pkm.Gender) - // continue; - if (!slot.IsWithinStartEnd(stamp)) - continue; - - if (!slot.GetIVsValid(pkm)) - { - deferredIV ??= slot; - continue; - } - - yield return slot; - } - - if (deferredIV != null) - yield return deferredIV; - } + if (deferredIV != null) + yield return deferredIV; } } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8.cs b/PKHeX.Core/Legality/Areas/EncounterArea8.cs index e137566d3..65473495f 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea8.cs @@ -5,480 +5,479 @@ using static PKHeX.Core.AreaSlotType8; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea8 : EncounterArea { - /// + public readonly EncounterSlot8[] Slots; + + protected override IReadOnlyList Raw => Slots; /// - /// encounter area + /// Slots from this area can cross over to another area, resulting in a different met location. /// - public sealed record EncounterArea8 : EncounterArea + /// + /// Should only be true if it is a Symbol (visible) encounter. + /// + public readonly bool PermitCrossover; + + public override bool IsMatchLocation(int location) { - public readonly EncounterSlot8[] Slots; - - protected override IReadOnlyList Raw => Slots; - /// - /// Slots from this area can cross over to another area, resulting in a different met location. - /// - /// - /// Should only be true if it is a Symbol (visible) encounter. - /// - public readonly bool PermitCrossover; - - public override bool IsMatchLocation(int location) - { - if (Location == location) - return true; - - if (!PermitCrossover) - return false; - - // Get all other areas that the Location can bleed encounters to - if (!ConnectingArea8.TryGetValue(Location, out var others)) - return false; - - // Check if any of the other areas are the met location - return others.Contains((byte)location); - } - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - var metLocation = pkm.Met_Location; - // wild area gets boosted up to level 60 post-game - var met = pkm.Met_Level; - bool isBoosted = met == BoostLevel && IsBoostedArea60(Location); - if (isBoosted) - return GetBoostedMatches(chain, metLocation); - return GetUnboostedMatches(chain, met, metLocation); - } - - private IEnumerable GetUnboostedMatches(EvoCriteria[] chain, int metLevel, int metLocation) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(metLevel)) - break; - - if (slot.Form != evo.Form && slot.Species is not (int)Species.Rotom) - break; - - if (slot.Weather is Heavy_Fog && IsWildArea8(Location)) - break; - - if (Location != metLocation && !CanCrossoverTo(Location, metLocation, slot.SlotType)) - break; - - yield return slot; - break; - } - } - } - - private IEnumerable GetBoostedMatches(EvoCriteria[] chain, int metLocation) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - // Ignore max met level comparison; we already know it is permissible to boost to level 60. - if (slot.LevelMin > BoostLevel) - break; // Can't downlevel, only boost to 60. - - if (slot.Form != evo.Form && slot.Species is not (int)Species.Rotom) - break; - - if (Location != metLocation && !CanCrossoverTo(Location, metLocation, slot.SlotType)) - break; - - yield return slot; - break; - } - } - } - - private static bool CanCrossoverTo(int fromLocation, int toLocation, AreaSlotType8 type) - { - if (!type.CanCrossover()) - return false; + if (Location == location) return true; - } - public const int BoostLevel = 60; + if (!PermitCrossover) + return false; - public static bool IsWildArea(int location) => IsWildArea8(location) || IsWildArea8Armor(location) || IsWildArea8Crown(location); - public static bool IsBoostedArea60(int location) => IsWildArea(location); - public static bool IsBoostedArea60Fog(int location) => IsWildArea8(location); // IoA doesn't have fog restriction by badges, and all Crown stuff is above 60. + // Get all other areas that the Location can bleed encounters to + if (!ConnectingArea8.TryGetValue(Location, out var others)) + return false; - public static bool IsWildArea8(int location) => location is >= 122 and <= 154; // Rolling Fields -> Lake of Outrage - public static bool IsWildArea8Armor(int location) => location is >= 164 and <= 194; // Fields of Honor -> Honeycalm Island - public static bool IsWildArea8Crown(int location) => location is >= 204 and <= 234 and not 206; // Slippery Slope -> Dyna Tree Hill, skip Freezington + // Check if any of the other areas are the met location + return others.Contains((byte)location); + } - // Location, and areas that it can feed encounters to. - public static readonly IReadOnlyDictionary> ConnectingArea8 = new Dictionary> + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + var metLocation = pk.Met_Location; + // wild area gets boosted up to level 60 post-game + var met = pk.Met_Level; + bool isBoosted = met == BoostLevel && IsBoostedArea60(Location); + if (isBoosted) + return GetBoostedMatches(chain, metLocation); + return GetUnboostedMatches(chain, met, metLocation); + } + + private IEnumerable GetUnboostedMatches(EvoCriteria[] chain, int metLevel, int metLocation) + { + foreach (var slot in Slots) { - // Route 3 - // City of Motostoke - {28, new byte[] {20}}, - - // Rolling Fields - // Dappled Grove, East Lake Axewell, West Lake Axewell - // Also connects to South Lake Miloch but too much of a stretch - {122, new byte[] {124, 128, 130}}, - - // Dappled Grove - // Rolling Fields, Watchtower Ruins - {124, new byte[] {122, 126}}, - - // Watchtower Ruins - // Dappled Grove, West Lake Axewell - {126, new byte[] {124, 130}}, - - // East Lake Axewell - // Rolling Fields, West Lake Axewell, Axew's Eye, North Lake Miloch - {128, new byte[] {122, 130, 132, 138}}, - - // West Lake Axewell - // Rolling Fields, Watchtower Ruins, East Lake Axewell, Axew's Eye - {130, new byte[] {122, 126, 128, 132}}, - - // Axew's Eye - // East Lake Axewell, West Lake Axewell - {132, new byte[] {128, 130}}, - - // South Lake Miloch - // Giant's Seat, North Lake Miloch - {134, new byte[] {136, 138}}, - - // Giant's Seat - // South Lake Miloch, North Lake Miloch - {136, new byte[] {134, 138}}, - - // North Lake Miloch - // East Lake Axewell, South Lake Miloch, Giant's Seat - // Also connects to Motostoke Riverbank but too much of a stretch - {138, new byte[] {134, 136}}, - - // Motostoke Riverbank - // Bridge Field - {140, new byte[] {142}}, - - // Bridge Field - // Motostoke Riverbank, Stony Wilderness - {142, new byte[] {140, 144}}, - - // Stony Wilderness - // Bridge Field, Dusty Bowl, Giant's Mirror, Giant's Cap - {144, new byte[] {142, 146, 148, 152}}, - - // Dusty Bowl - // Stony Wilderness, Giant's Mirror, Hammerlocke Hills - {146, new byte[] {144, 148, 150}}, - - // Giant's Mirror - // Stony Wilderness, Dusty Bowl, Hammerlocke Hills - {148, new byte[] {144, 146, 148}}, - - // Hammerlocke Hills - // Dusty Bowl, Giant's Mirror, Giant's Cap - {150, new byte[] {146, 148, 152}}, - - // Giant's Cap - // Stony Wilderness, Giant's Cap - // Also connects to Lake of Outrage but too much of a stretch - {152, new byte[] {144, 150}}, - - // Lake of Outrage is just itself. - - // Challenge Beach - // Soothing Wetlands, Courageous Cavern - {170, new byte[] {166, 176}}, - - // Challenge Road - // Brawler's Cave - {174, new byte[] {172}}, - - // Courageous Cavern - // Loop Lagoon - {176, new byte[] {178}}, - - // Warm-Up Tunnel - // Training Lowlands, Potbottom Desert - {182, new byte[] {180, 184}}, - - // Workout Sea - // Fields of Honor - {186, new byte[] {164}}, - - // Stepping-Stone Sea - // Fields of Honor - {188, new byte[] {170}}, - - // Insular Sea - // Honeycalm Sea - {190, new byte[] {192}}, - - // Honeycalm Sea - // Honeycalm Island - {192, new byte[] {194}}, - - // Frostpoint Field - // Freezington - {208, new byte[] {206}}, - - // Old Cemetery - // Giant’s Bed - {212, new byte[] {210}}, - - // Roaring-Sea Caves - // Giant’s Foot - {224, new byte[] {222}}, - - // Ballimere Lake - // Lakeside Cave - {230, new byte[] {232}}, - }; - - /// - /// Location IDs matched with possible weather types. Unlisted locations may only have Normal weather. - /// - internal static readonly Dictionary WeatherbyArea = new() - { - { 68, Intense_Sun }, // Route 6 - { 88, Snowing }, // Route 8 (Steamdrift Way) - { 90, Snowing }, // Route 9 - { 92, Snowing }, // Route 9 (Circhester Bay) - { 94, Overcast }, // Route 9 (Outer Spikemuth) - { 106, Snowstorm }, // Route 10 - { 122, All }, // Rolling Fields - { 124, All }, // Dappled Grove - { 126, All }, // Watchtower Ruins - { 128, All }, // East Lake Axewell - { 130, All }, // West Lake Axewell - { 132, All }, // Axew's Eye - { 134, All }, // South Lake Miloch - { 136, All }, // Giant's Seat - { 138, All }, // North Lake Miloch - { 140, All }, // Motostoke Riverbank - { 142, All }, // Bridge Field - { 144, All }, // Stony Wilderness - { 146, All }, // Dusty Bowl - { 148, All }, // Giant's Mirror - { 150, All }, // Hammerlocke Hills - { 152, All }, // Giant's Cap - { 154, All }, // Lake of Outrage - { 164, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Fields of Honor - { 166, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Soothing Wetlands - { 168, All_IoA }, // Forest of Focus - { 170, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Challenge Beach - { 174, All_IoA }, // Challenge Road - { 178, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Loop Lagoon - { 180, All_IoA }, // Training Lowlands - { 184, Normal | Overcast | Raining | Sandstorm | Intense_Sun | Heavy_Fog }, // Potbottom Desert - { 186, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Workout Sea - { 188, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Stepping-Stone Sea - { 190, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Insular Sea - { 192, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Honeycalm Sea - { 194, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Honeycalm Island - { 204, Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Slippery Slope - { 208, Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Frostpoint Field - { 210, All_CT }, // Giant's Bed - { 212, All_CT }, // Old Cemetery - { 214, Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Snowslide Slope - { 216, Overcast }, // Tunnel to the Top - { 218, Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Path to the Peak - { 222, All_CT }, // Giant's Foot - { 224, Overcast }, // Roaring-Sea Caves - { 226, No_Sun_Sand }, // Frigid Sea - { 228, All_CT }, // Three-Point Pass - { 230, All_Ballimere }, // Ballimere Lake - { 232, Overcast }, // Lakeside Cave - }; - - /// - /// Weather types that may bleed into each location from adjacent locations for standard symbol encounter slots. - /// - internal static readonly Dictionary WeatherBleedSymbol = new() - { - { 166, All_IoA }, // Soothing Wetlands from Forest of Focus - { 170, All_IoA }, // Challenge Beach from Forest of Focus - { 182, All_IoA }, // Warm-Up Tunnel from Training Lowlands - { 208, All_CT }, // Frostpoint Field from Giant's Bed - { 216, Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Tunnel to the Top from Path to the Peak - { 224, All_CT }, // Roaring-Sea Caves from Three-Point Pass - { 232, All_Ballimere }, // Lakeside Cave from Ballimere Lake - { 230, All_CT }, // Ballimere Lake from Giant's Bed - }; - - /// - /// Weather types that may bleed into each location from adjacent locations for surfing symbol encounter slots. - /// - private static readonly Dictionary WeatherBleedSymbolSurfing = new() - { - { 192, All_IoA }, // Honeycalm Sea from Training Lowlands - { 224, All_CT }, // Roaring-Sea Caves from Giant's Foot - }; - - /// - /// Weather types that may bleed into each location from adjacent locations for Sharpedo symbol encounter slots. - /// - private static readonly Dictionary WeatherBleedSymbolSharpedo = new() - { - { 192, All_IoA }, // Honeycalm Sea from Training Lowlands - }; - - /// - /// Weather types that may bleed into each location from adjacent locations, for standard hidden grass encounter slots. - /// - private static readonly Dictionary WeatherBleedHiddenGrass = new() - { - { 166, All_IoA }, // Soothing Wetlands from Forest of Focus - { 170, All_IoA }, // Challenge Beach from Forest of Focus - { 208, All_CT }, // Frostpoint Field from Giant's Bed - { 230, All_CT }, // Ballimere Lake from Giant's Bed - }; - - public static bool IsCrossoverBleedPossible(AreaSlotType8 type, int fromLocation, int toLocation) => true; - - public static bool IsWeatherBleedPossible(AreaSlotType8 type, AreaWeather8 permit, int location) => type switch - { - SymbolMain or SymbolMain2 or SymbolMain3 => WeatherBleedSymbol .TryGetValue(location, out var weather) && weather.HasFlag(permit), - HiddenMain or HiddenMain2 => WeatherBleedHiddenGrass .TryGetValue(location, out var weather) && weather.HasFlag(permit), - Surfing => WeatherBleedSymbolSurfing .TryGetValue(location, out var weather) && weather.HasFlag(permit), - Sharpedo => WeatherBleedSymbolSharpedo.TryGetValue(location, out var weather) && weather.HasFlag(permit), - _ => false, - }; - - public static EncounterArea8[] GetAreas(BinLinkerAccessor input, GameVersion game, bool symbol = false) - { - var result = new EncounterArea8[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea8(input[i], symbol, game); - return result; - } - - private EncounterArea8(ReadOnlySpan areaData, bool symbol, GameVersion game) : base(game) - { - PermitCrossover = symbol; - Location = areaData[0]; - Slots = ReadSlots(areaData, areaData[1]); - } - - private EncounterSlot8[] ReadSlots(ReadOnlySpan areaData, byte slotCount) - { - var slots = new EncounterSlot8[slotCount]; - - int ctr = 0; - int ofs = 2; - do + foreach (var evo in chain) { - // Read area metadata - var meta = areaData.Slice(ofs, 6); - var flags = (AreaWeather8) ReadUInt16LittleEndian(meta); - var min = meta[2]; - var max = meta[3]; - var count = meta[4]; - var slotType = (AreaSlotType8)meta[5]; - ofs += 6; + if (slot.Species != evo.Species) + continue; - // Read slots - const int bpe = 2; - for (int i = 0; i < count; i++, ctr++, ofs += bpe) - { - var entry = areaData.Slice(ofs, bpe); - var species = ReadUInt16LittleEndian(entry); - byte form = (byte)(species >> 11); - species &= 0x3FF; - slots[ctr] = new EncounterSlot8(this, species, form, min, max, flags, slotType); - } - } while (ctr != slots.Length); + if (!slot.IsLevelWithinRange(metLevel)) + break; - return slots; + if (slot.Form != evo.Form && slot.Species is not (int)Species.Rotom) + break; + + if (slot.Weather is Heavy_Fog && IsWildArea8(Location)) + break; + + if (Location != metLocation && !CanCrossoverTo(Location, metLocation, slot.SlotType)) + break; + + yield return slot; + break; + } } } - /// - /// Encounter Conditions for - /// - /// Values above are for Shaking/Fishing hidden encounters only. - [Flags] - public enum AreaWeather8 : ushort + private IEnumerable GetBoostedMatches(EvoCriteria[] chain, int metLocation) { - None, - Normal = 1, - Overcast = 1 << 1, - Raining = 1 << 2, - Thunderstorm = 1 << 3, - Intense_Sun = 1 << 4, - Snowing = 1 << 5, - Snowstorm = 1 << 6, - Sandstorm = 1 << 7, - Heavy_Fog = 1 << 8, - - All = Normal | Overcast | Raining | Thunderstorm | Intense_Sun | Snowing | Snowstorm | Sandstorm | Heavy_Fog, - Stormy = Raining | Thunderstorm, - Icy = Snowing | Snowstorm, - All_IoA = Normal | Overcast | Stormy | Intense_Sun | Sandstorm | Heavy_Fog, // IoA can have everything but snow - All_CT = Normal | Overcast | Stormy | Intense_Sun | Icy | Heavy_Fog, // CT can have everything but sand - No_Sun_Sand = Normal | Overcast | Stormy | Icy | Heavy_Fog, // Everything but sand and sun - All_Ballimere = Normal | Overcast | Stormy | Intense_Sun | Snowing | Heavy_Fog, // All Ballimere Lake weather - - Shaking_Trees = 1 << 9, - Fishing = 1 << 10, - - NotWeather = Shaking_Trees | Fishing, - } - - public static class AreaWeather8Extensions - { - public static bool IsMarkCompatible(this AreaWeather8 weather, IRibbonSetMark8 m) + foreach (var slot in Slots) { - if (m.RibbonMarkCloudy) return (weather & Overcast) != 0; - if (m.RibbonMarkRainy) return (weather & Raining) != 0; - if (m.RibbonMarkStormy) return (weather & Thunderstorm) != 0; - if (m.RibbonMarkSnowy) return (weather & Snowing) != 0; - if (m.RibbonMarkBlizzard) return (weather & Snowstorm) != 0; - if (m.RibbonMarkDry) return (weather & Intense_Sun) != 0; - if (m.RibbonMarkSandstorm) return (weather & Sandstorm) != 0; - if (m.RibbonMarkMisty) return (weather & Heavy_Fog) != 0; - return true; // no mark / etc is fine; check later. + foreach (var evo in chain) + { + if (slot.Species != evo.Species) + continue; + + // Ignore max met level comparison; we already know it is permissible to boost to level 60. + if (slot.LevelMin > BoostLevel) + break; // Can't downlevel, only boost to 60. + + if (slot.Form != evo.Form && slot.Species is not (int)Species.Rotom) + break; + + if (Location != metLocation && !CanCrossoverTo(Location, metLocation, slot.SlotType)) + break; + + yield return slot; + break; + } } } - /// - /// Encounter Slot Types for - /// - public enum AreaSlotType8 : byte + private static bool CanCrossoverTo(int fromLocation, int toLocation, AreaSlotType8 type) { - SymbolMain, - SymbolMain2, - SymbolMain3, - - HiddenMain, // Both HiddenMain tables include the tree/fishing slots for the area. - HiddenMain2, - - Surfing, - Surfing2, - Sky, - Sky2, - Ground, - Ground2, - Sharpedo, - - OnlyFishing, // More restricted hidden table that ignores the weather slots like grass Tentacool. - Inaccessible, // Shouldn't show up since these tables are not dumped. + if (!type.CanCrossover()) + return false; + return true; } - public static class AreaSlotType8Extensions + public const int BoostLevel = 60; + + public static bool IsWildArea(int location) => IsWildArea8(location) || IsWildArea8Armor(location) || IsWildArea8Crown(location); + public static bool IsBoostedArea60(int location) => IsWildArea(location); + public static bool IsBoostedArea60Fog(int location) => IsWildArea8(location); // IoA doesn't have fog restriction by badges, and all Crown stuff is above 60. + + public static bool IsWildArea8(int location) => location is >= 122 and <= 154; // Rolling Fields -> Lake of Outrage + public static bool IsWildArea8Armor(int location) => location is >= 164 and <= 194; // Fields of Honor -> Honeycalm Island + public static bool IsWildArea8Crown(int location) => location is >= 204 and <= 234 and not 206; // Slippery Slope -> Dyna Tree Hill, skip Freezington + + // Location, and areas that it can feed encounters to. + public static readonly IReadOnlyDictionary> ConnectingArea8 = new Dictionary> { - public static bool CanCrossover(this AreaSlotType8 type) => type is not (HiddenMain or HiddenMain2 or OnlyFishing); - public static bool CanEncounterViaFishing(this AreaSlotType8 type, AreaWeather8 weather) => type is OnlyFishing || weather.HasFlag(Fishing); - public static bool CanEncounterViaCurry(this AreaSlotType8 type) => type is HiddenMain or HiddenMain2; + // Route 3 + // City of Motostoke + {28, new byte[] {20}}, + + // Rolling Fields + // Dappled Grove, East Lake Axewell, West Lake Axewell + // Also connects to South Lake Miloch but too much of a stretch + {122, new byte[] {124, 128, 130}}, + + // Dappled Grove + // Rolling Fields, Watchtower Ruins + {124, new byte[] {122, 126}}, + + // Watchtower Ruins + // Dappled Grove, West Lake Axewell + {126, new byte[] {124, 130}}, + + // East Lake Axewell + // Rolling Fields, West Lake Axewell, Axew's Eye, North Lake Miloch + {128, new byte[] {122, 130, 132, 138}}, + + // West Lake Axewell + // Rolling Fields, Watchtower Ruins, East Lake Axewell, Axew's Eye + {130, new byte[] {122, 126, 128, 132}}, + + // Axew's Eye + // East Lake Axewell, West Lake Axewell + {132, new byte[] {128, 130}}, + + // South Lake Miloch + // Giant's Seat, North Lake Miloch + {134, new byte[] {136, 138}}, + + // Giant's Seat + // South Lake Miloch, North Lake Miloch + {136, new byte[] {134, 138}}, + + // North Lake Miloch + // East Lake Axewell, South Lake Miloch, Giant's Seat + // Also connects to Motostoke Riverbank but too much of a stretch + {138, new byte[] {134, 136}}, + + // Motostoke Riverbank + // Bridge Field + {140, new byte[] {142}}, + + // Bridge Field + // Motostoke Riverbank, Stony Wilderness + {142, new byte[] {140, 144}}, + + // Stony Wilderness + // Bridge Field, Dusty Bowl, Giant's Mirror, Giant's Cap + {144, new byte[] {142, 146, 148, 152}}, + + // Dusty Bowl + // Stony Wilderness, Giant's Mirror, Hammerlocke Hills + {146, new byte[] {144, 148, 150}}, + + // Giant's Mirror + // Stony Wilderness, Dusty Bowl, Hammerlocke Hills + {148, new byte[] {144, 146, 148}}, + + // Hammerlocke Hills + // Dusty Bowl, Giant's Mirror, Giant's Cap + {150, new byte[] {146, 148, 152}}, + + // Giant's Cap + // Stony Wilderness, Giant's Cap + // Also connects to Lake of Outrage but too much of a stretch + {152, new byte[] {144, 150}}, + + // Lake of Outrage is just itself. + + // Challenge Beach + // Soothing Wetlands, Courageous Cavern + {170, new byte[] {166, 176}}, + + // Challenge Road + // Brawler's Cave + {174, new byte[] {172}}, + + // Courageous Cavern + // Loop Lagoon + {176, new byte[] {178}}, + + // Warm-Up Tunnel + // Training Lowlands, Potbottom Desert + {182, new byte[] {180, 184}}, + + // Workout Sea + // Fields of Honor + {186, new byte[] {164}}, + + // Stepping-Stone Sea + // Fields of Honor + {188, new byte[] {170}}, + + // Insular Sea + // Honeycalm Sea + {190, new byte[] {192}}, + + // Honeycalm Sea + // Honeycalm Island + {192, new byte[] {194}}, + + // Frostpoint Field + // Freezington + {208, new byte[] {206}}, + + // Old Cemetery + // Giant’s Bed + {212, new byte[] {210}}, + + // Roaring-Sea Caves + // Giant’s Foot + {224, new byte[] {222}}, + + // Ballimere Lake + // Lakeside Cave + {230, new byte[] {232}}, + }; + + /// + /// Location IDs matched with possible weather types. Unlisted locations may only have Normal weather. + /// + internal static readonly Dictionary WeatherbyArea = new() + { + { 68, Intense_Sun }, // Route 6 + { 88, Snowing }, // Route 8 (Steamdrift Way) + { 90, Snowing }, // Route 9 + { 92, Snowing }, // Route 9 (Circhester Bay) + { 94, Overcast }, // Route 9 (Outer Spikemuth) + { 106, Snowstorm }, // Route 10 + { 122, All }, // Rolling Fields + { 124, All }, // Dappled Grove + { 126, All }, // Watchtower Ruins + { 128, All }, // East Lake Axewell + { 130, All }, // West Lake Axewell + { 132, All }, // Axew's Eye + { 134, All }, // South Lake Miloch + { 136, All }, // Giant's Seat + { 138, All }, // North Lake Miloch + { 140, All }, // Motostoke Riverbank + { 142, All }, // Bridge Field + { 144, All }, // Stony Wilderness + { 146, All }, // Dusty Bowl + { 148, All }, // Giant's Mirror + { 150, All }, // Hammerlocke Hills + { 152, All }, // Giant's Cap + { 154, All }, // Lake of Outrage + { 164, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Fields of Honor + { 166, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Soothing Wetlands + { 168, All_IoA }, // Forest of Focus + { 170, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Challenge Beach + { 174, All_IoA }, // Challenge Road + { 178, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Loop Lagoon + { 180, All_IoA }, // Training Lowlands + { 184, Normal | Overcast | Raining | Sandstorm | Intense_Sun | Heavy_Fog }, // Potbottom Desert + { 186, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Workout Sea + { 188, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Stepping-Stone Sea + { 190, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Insular Sea + { 192, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Honeycalm Sea + { 194, Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Honeycalm Island + { 204, Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Slippery Slope + { 208, Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Frostpoint Field + { 210, All_CT }, // Giant's Bed + { 212, All_CT }, // Old Cemetery + { 214, Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Snowslide Slope + { 216, Overcast }, // Tunnel to the Top + { 218, Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Path to the Peak + { 222, All_CT }, // Giant's Foot + { 224, Overcast }, // Roaring-Sea Caves + { 226, No_Sun_Sand }, // Frigid Sea + { 228, All_CT }, // Three-Point Pass + { 230, All_Ballimere }, // Ballimere Lake + { 232, Overcast }, // Lakeside Cave + }; + + /// + /// Weather types that may bleed into each location from adjacent locations for standard symbol encounter slots. + /// + internal static readonly Dictionary WeatherBleedSymbol = new() + { + { 166, All_IoA }, // Soothing Wetlands from Forest of Focus + { 170, All_IoA }, // Challenge Beach from Forest of Focus + { 182, All_IoA }, // Warm-Up Tunnel from Training Lowlands + { 208, All_CT }, // Frostpoint Field from Giant's Bed + { 216, Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Tunnel to the Top from Path to the Peak + { 224, All_CT }, // Roaring-Sea Caves from Three-Point Pass + { 232, All_Ballimere }, // Lakeside Cave from Ballimere Lake + { 230, All_CT }, // Ballimere Lake from Giant's Bed + }; + + /// + /// Weather types that may bleed into each location from adjacent locations for surfing symbol encounter slots. + /// + private static readonly Dictionary WeatherBleedSymbolSurfing = new() + { + { 192, All_IoA }, // Honeycalm Sea from Training Lowlands + { 224, All_CT }, // Roaring-Sea Caves from Giant's Foot + }; + + /// + /// Weather types that may bleed into each location from adjacent locations for Sharpedo symbol encounter slots. + /// + private static readonly Dictionary WeatherBleedSymbolSharpedo = new() + { + { 192, All_IoA }, // Honeycalm Sea from Training Lowlands + }; + + /// + /// Weather types that may bleed into each location from adjacent locations, for standard hidden grass encounter slots. + /// + private static readonly Dictionary WeatherBleedHiddenGrass = new() + { + { 166, All_IoA }, // Soothing Wetlands from Forest of Focus + { 170, All_IoA }, // Challenge Beach from Forest of Focus + { 208, All_CT }, // Frostpoint Field from Giant's Bed + { 230, All_CT }, // Ballimere Lake from Giant's Bed + }; + + public static bool IsCrossoverBleedPossible(AreaSlotType8 type, int fromLocation, int toLocation) => true; + + public static bool IsWeatherBleedPossible(AreaSlotType8 type, AreaWeather8 permit, int location) => type switch + { + SymbolMain or SymbolMain2 or SymbolMain3 => WeatherBleedSymbol .TryGetValue(location, out var weather) && weather.HasFlag(permit), + HiddenMain or HiddenMain2 => WeatherBleedHiddenGrass .TryGetValue(location, out var weather) && weather.HasFlag(permit), + Surfing => WeatherBleedSymbolSurfing .TryGetValue(location, out var weather) && weather.HasFlag(permit), + Sharpedo => WeatherBleedSymbolSharpedo.TryGetValue(location, out var weather) && weather.HasFlag(permit), + _ => false, + }; + + public static EncounterArea8[] GetAreas(BinLinkerAccessor input, GameVersion game, bool symbol = false) + { + var result = new EncounterArea8[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea8(input[i], symbol, game); + return result; + } + + private EncounterArea8(ReadOnlySpan areaData, bool symbol, GameVersion game) : base(game) + { + PermitCrossover = symbol; + Location = areaData[0]; + Slots = ReadSlots(areaData, areaData[1]); + } + + private EncounterSlot8[] ReadSlots(ReadOnlySpan areaData, byte slotCount) + { + var slots = new EncounterSlot8[slotCount]; + + int ctr = 0; + int ofs = 2; + do + { + // Read area metadata + var meta = areaData.Slice(ofs, 6); + var flags = (AreaWeather8) ReadUInt16LittleEndian(meta); + var min = meta[2]; + var max = meta[3]; + var count = meta[4]; + var slotType = (AreaSlotType8)meta[5]; + ofs += 6; + + // Read slots + const int bpe = 2; + for (int i = 0; i < count; i++, ctr++, ofs += bpe) + { + var entry = areaData.Slice(ofs, bpe); + var species = ReadUInt16LittleEndian(entry); + byte form = (byte)(species >> 11); + species &= 0x3FF; + slots[ctr] = new EncounterSlot8(this, species, form, min, max, flags, slotType); + } + } while (ctr != slots.Length); + + return slots; } } + +/// +/// Encounter Conditions for +/// +/// Values above are for Shaking/Fishing hidden encounters only. +[Flags] +public enum AreaWeather8 : ushort +{ + None, + Normal = 1, + Overcast = 1 << 1, + Raining = 1 << 2, + Thunderstorm = 1 << 3, + Intense_Sun = 1 << 4, + Snowing = 1 << 5, + Snowstorm = 1 << 6, + Sandstorm = 1 << 7, + Heavy_Fog = 1 << 8, + + All = Normal | Overcast | Raining | Thunderstorm | Intense_Sun | Snowing | Snowstorm | Sandstorm | Heavy_Fog, + Stormy = Raining | Thunderstorm, + Icy = Snowing | Snowstorm, + All_IoA = Normal | Overcast | Stormy | Intense_Sun | Sandstorm | Heavy_Fog, // IoA can have everything but snow + All_CT = Normal | Overcast | Stormy | Intense_Sun | Icy | Heavy_Fog, // CT can have everything but sand + No_Sun_Sand = Normal | Overcast | Stormy | Icy | Heavy_Fog, // Everything but sand and sun + All_Ballimere = Normal | Overcast | Stormy | Intense_Sun | Snowing | Heavy_Fog, // All Ballimere Lake weather + + Shaking_Trees = 1 << 9, + Fishing = 1 << 10, + + NotWeather = Shaking_Trees | Fishing, +} + +public static class AreaWeather8Extensions +{ + public static bool IsMarkCompatible(this AreaWeather8 weather, IRibbonSetMark8 m) + { + if (m.RibbonMarkCloudy) return (weather & Overcast) != 0; + if (m.RibbonMarkRainy) return (weather & Raining) != 0; + if (m.RibbonMarkStormy) return (weather & Thunderstorm) != 0; + if (m.RibbonMarkSnowy) return (weather & Snowing) != 0; + if (m.RibbonMarkBlizzard) return (weather & Snowstorm) != 0; + if (m.RibbonMarkDry) return (weather & Intense_Sun) != 0; + if (m.RibbonMarkSandstorm) return (weather & Sandstorm) != 0; + if (m.RibbonMarkMisty) return (weather & Heavy_Fog) != 0; + return true; // no mark / etc is fine; check later. + } +} + +/// +/// Encounter Slot Types for +/// +public enum AreaSlotType8 : byte +{ + SymbolMain, + SymbolMain2, + SymbolMain3, + + HiddenMain, // Both HiddenMain tables include the tree/fishing slots for the area. + HiddenMain2, + + Surfing, + Surfing2, + Sky, + Sky2, + Ground, + Ground2, + Sharpedo, + + OnlyFishing, // More restricted hidden table that ignores the weather slots like grass Tentacool. + Inaccessible, // Shouldn't show up since these tables are not dumped. +} + +public static class AreaSlotType8Extensions +{ + public static bool CanCrossover(this AreaSlotType8 type) => type is not (HiddenMain or HiddenMain2 or OnlyFishing); + public static bool CanEncounterViaFishing(this AreaSlotType8 type, AreaWeather8 weather) => type is OnlyFishing || weather.HasFlag(Fishing); + public static bool CanEncounterViaCurry(this AreaSlotType8 type) => type is HiddenMain or HiddenMain2; +} diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8a.cs b/PKHeX.Core/Legality/Areas/EncounterArea8a.cs index 8274ce213..00b4c1ac3 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8a.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea8a.cs @@ -20,7 +20,7 @@ public override bool IsMatchLocation(int location) return Array.IndexOf(Locations, (byte)location) != -1; } - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) => GetMatches(chain, pkm.Met_Level); + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) => GetMatches(chain, pk.Met_Level); private IEnumerable GetMatches(EvoCriteria[] chain, int metLevel) { diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8b.cs b/PKHeX.Core/Legality/Areas/EncounterArea8b.cs index d2f54b05f..9e07af9d7 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8b.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea8b.cs @@ -2,148 +2,147 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area +/// +public sealed record EncounterArea8b : EncounterArea { - /// - /// - /// encounter area - /// - public sealed record EncounterArea8b : EncounterArea + public readonly EncounterSlot8b[] Slots; + + protected override IReadOnlyList Raw => Slots; + + public static EncounterArea8b[] GetAreas(BinLinkerAccessor input, GameVersion game) { - public readonly EncounterSlot8b[] Slots; - - protected override IReadOnlyList Raw => Slots; - - public static EncounterArea8b[] GetAreas(BinLinkerAccessor input, GameVersion game) - { - var result = new EncounterArea8b[input.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = new EncounterArea8b(input[i], game); - return result; - } - - private EncounterArea8b(ReadOnlySpan data, GameVersion game) : base(game) - { - Location = data[0] | (data[1] << 8); - Type = (SlotType)data[2]; - - Slots = ReadSlots(data); - } - - private EncounterSlot8b[] ReadSlots(ReadOnlySpan data) - { - const int size = 4; - int count = (data.Length - 4) / size; - var slots = new EncounterSlot8b[count]; - for (int i = 0; i < slots.Length; i++) - { - int offset = 4 + (size * i); - var entry = data.Slice(offset, size); - slots[i] = ReadSlot(entry); - } - return slots; - } - - private EncounterSlot8b ReadSlot(ReadOnlySpan data) - { - ushort species = ReadUInt16LittleEndian(data); - byte form = (byte)(species >> 11); - species &= 0x3FF; - byte min = data[2]; - byte max = data[3]; - return new EncounterSlot8b(this, species, form, min, max); - } - - public override bool IsMatchLocation(int location) - { - if (base.IsMatchLocation(location)) - return true; - return CanCrossoverTo(location); - } - - private bool CanCrossoverTo(int location) - { - if (Type is SlotType.Surf) - { - return Location switch - { - 486 => location is 167, // Route 223 -> Pokémon League - 167 => location is 486, // Pokémon League -> Route 223 - 420 => location is 489, // Route 229 -> Route 230 - 489 => location is 420, // Route 230 -> Route 229 - - // All other crossover surf locations are identical slot lists. - _ => false, - }; - } - - // No crossover - return false; - } - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - foreach (var slot in Slots) - { - foreach (var evo in chain) - { - if (slot.Species != evo.Species) - continue; - - if (!slot.IsLevelWithinRange(pkm.Met_Level)) - break; - - if (slot.Form != evo.Form && slot.Species is not (int)Species.Burmy) - break; - - if (Type is SlotType.HoneyTree && IsInaccessibleHoneySlotLocation(slot, pkm)) - break; - - yield return slot; - break; - } - } - } - private static bool IsInaccessibleHoneySlotLocation(EncounterSlot8b slot, PKM pk) - { - // A/B/C tables, only Munchlax is a 'C' encounter, and A/B are accessible from any tree. - // C table encounters are only available from 4 trees, which are determined by TID/SID of the save file. - if (slot.Species is not (int)Species.Munchlax) - return false; - - // We didn't encode the honey tree index to the encounter slot resource. - // Check if any of the slot's location doesn't match any of the groupC trees' area location ID. - var location = pk.Met_Location; - var trees = SAV4Sinnoh.CalculateMunchlaxTrees(pk.TID, pk.SID); - return LocationID_HoneyTree[trees.Tree1] != location - && LocationID_HoneyTree[trees.Tree2] != location - && LocationID_HoneyTree[trees.Tree3] != location - && LocationID_HoneyTree[trees.Tree4] != location; - } - - private static readonly ushort[] LocationID_HoneyTree = - { - 359, // 00 Route 205 Floaroma - 361, // 01 Route 205 Eterna - 362, // 02 Route 206 - 364, // 03 Route 207 - 365, // 04 Route 208 - 367, // 05 Route 209 - 373, // 06 Route 210 Solaceon - 375, // 07 Route 210 Celestic - 378, // 08 Route 211 - 379, // 09 Route 212 Hearthome - 383, // 10 Route 212 Pastoria - 385, // 11 Route 213 - 392, // 12 Route 214 - 394, // 13 Route 215 - 400, // 14 Route 218 - 404, // 15 Route 221 - 407, // 16 Route 222 - 197, // 17 Valley Windworks - 199, // 18 Eterna Forest - 201, // 19 Fuego Ironworks - 253, // 20 Floaroma Meadow - }; + var result = new EncounterArea8b[input.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = new EncounterArea8b(input[i], game); + return result; } + + private EncounterArea8b(ReadOnlySpan data, GameVersion game) : base(game) + { + Location = data[0] | (data[1] << 8); + Type = (SlotType)data[2]; + + Slots = ReadSlots(data); + } + + private EncounterSlot8b[] ReadSlots(ReadOnlySpan data) + { + const int size = 4; + int count = (data.Length - 4) / size; + var slots = new EncounterSlot8b[count]; + for (int i = 0; i < slots.Length; i++) + { + int offset = 4 + (size * i); + var entry = data.Slice(offset, size); + slots[i] = ReadSlot(entry); + } + return slots; + } + + private EncounterSlot8b ReadSlot(ReadOnlySpan data) + { + ushort species = ReadUInt16LittleEndian(data); + byte form = (byte)(species >> 11); + species &= 0x3FF; + byte min = data[2]; + byte max = data[3]; + return new EncounterSlot8b(this, species, form, min, max); + } + + public override bool IsMatchLocation(int location) + { + if (base.IsMatchLocation(location)) + return true; + return CanCrossoverTo(location); + } + + private bool CanCrossoverTo(int location) + { + if (Type is SlotType.Surf) + { + return Location switch + { + 486 => location is 167, // Route 223 -> Pokémon League + 167 => location is 486, // Pokémon League -> Route 223 + 420 => location is 489, // Route 229 -> Route 230 + 489 => location is 420, // Route 230 -> Route 229 + + // All other crossover surf locations are identical slot lists. + _ => false, + }; + } + + // No crossover + return false; + } + + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + foreach (var slot in Slots) + { + foreach (var evo in chain) + { + if (slot.Species != evo.Species) + continue; + + if (!slot.IsLevelWithinRange(pk.Met_Level)) + break; + + if (slot.Form != evo.Form && slot.Species is not (int)Species.Burmy) + break; + + if (Type is SlotType.HoneyTree && IsInaccessibleHoneySlotLocation(slot, pk)) + break; + + yield return slot; + break; + } + } + } + private static bool IsInaccessibleHoneySlotLocation(EncounterSlot8b slot, PKM pk) + { + // A/B/C tables, only Munchlax is a 'C' encounter, and A/B are accessible from any tree. + // C table encounters are only available from 4 trees, which are determined by TID/SID of the save file. + if (slot.Species is not (int)Species.Munchlax) + return false; + + // We didn't encode the honey tree index to the encounter slot resource. + // Check if any of the slot's location doesn't match any of the groupC trees' area location ID. + var location = pk.Met_Location; + var trees = SAV4Sinnoh.CalculateMunchlaxTrees(pk.TID, pk.SID); + return LocationID_HoneyTree[trees.Tree1] != location + && LocationID_HoneyTree[trees.Tree2] != location + && LocationID_HoneyTree[trees.Tree3] != location + && LocationID_HoneyTree[trees.Tree4] != location; + } + + private static readonly ushort[] LocationID_HoneyTree = + { + 359, // 00 Route 205 Floaroma + 361, // 01 Route 205 Eterna + 362, // 02 Route 206 + 364, // 03 Route 207 + 365, // 04 Route 208 + 367, // 05 Route 209 + 373, // 06 Route 210 Solaceon + 375, // 07 Route 210 Celestic + 378, // 08 Route 211 + 379, // 09 Route 212 Hearthome + 383, // 10 Route 212 Pastoria + 385, // 11 Route 213 + 392, // 12 Route 214 + 394, // 13 Route 215 + 400, // 14 Route 218 + 404, // 15 Route 221 + 407, // 16 Route 222 + 197, // 17 Valley Windworks + 199, // 18 Eterna Forest + 201, // 19 Fuego Ironworks + 253, // 20 Floaroma Meadow + }; } diff --git a/PKHeX.Core/Legality/Areas/EncounterArea8g.cs b/PKHeX.Core/Legality/Areas/EncounterArea8g.cs index fbdb9df7e..a385a91e4 100644 --- a/PKHeX.Core/Legality/Areas/EncounterArea8g.cs +++ b/PKHeX.Core/Legality/Areas/EncounterArea8g.cs @@ -2,131 +2,130 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// encounter area for direct-to-HOME transfers. +/// +public sealed record EncounterArea8g : EncounterArea, ISpeciesForm { - /// - /// - /// encounter area for direct-to-HOME transfers. - /// - public sealed record EncounterArea8g : EncounterArea, ISpeciesForm + /// Species for the area + /// Due to how the encounter data is packaged by PKHeX, each species-form is grouped together. + public int Species { get; } + /// Form of the Species + public int Form { get; } + public readonly EncounterSlot8GO[] Slots; + + protected override IReadOnlyList Raw => Slots; + + private EncounterArea8g(ushort species, byte form, EncounterSlot8GO[] slots) : base(GameVersion.GO) { - /// Species for the area - /// Due to how the encounter data is packaged by PKHeX, each species-form is grouped together. - public int Species { get; } - /// Form of the Species - public int Form { get; } - public readonly EncounterSlot8GO[] Slots; + Species = species; + Form = form; + Location = Locations.GO8; + Slots = slots; + } - protected override IReadOnlyList Raw => Slots; + internal static EncounterArea8g[] GetArea(BinLinkerAccessor data) + { + var areas = new EncounterArea8g[data.Length]; + for (int i = 0; i < areas.Length; i++) + areas[i] = GetArea(data[i]); + return areas; + } - private EncounterArea8g(ushort species, byte form, EncounterSlot8GO[] slots) : base(GameVersion.GO) + private const int meta = 4; + private const int entrySize = (2 * sizeof(int)) + 2; + + private static EncounterArea8g GetArea(ReadOnlySpan data) + { + var species = ReadUInt16LittleEndian(data); + var form = data[2]; + var import = (PogoImportFormat)data[3]; + + data = data[meta..]; + var result = new EncounterSlot8GO[data.Length / entrySize]; + var area = new EncounterArea8g(species, form, result); + for (int i = 0; i < result.Length; i++) { - Species = species; - Form = form; - Location = Locations.GO8; - Slots = slots; + var offset = i * entrySize; + var entry = data.Slice(offset, entrySize); + result[i] = ReadSlot(entry, area, species, form, import); } - internal static EncounterArea8g[] GetArea(BinLinkerAccessor data) + return area; + } + + private static EncounterSlot8GO ReadSlot(ReadOnlySpan entry, EncounterArea8g area, ushort species, byte form, PogoImportFormat format) + { + int start = ReadInt32LittleEndian(entry); + int end = ReadInt32LittleEndian(entry[4..]); + var sg = entry[8]; + var shiny = (Shiny)(sg & 0x3F); + var gender = (Gender)(sg >> 6); + var type = (PogoType)entry[9]; + return new EncounterSlot8GO(area, species, form, start, end, shiny, gender, type, format); + } + + public override IEnumerable GetMatchingSlots(PKM pk, EvoCriteria[] chain) + { + if (pk.TSV == 0) // HOME doesn't assign TSV=0 to accounts. + yield break; + + // Find the first chain that has slots defined. + // Since it is possible to evolve before transferring, we only need the highest evolution species possible. + // PoGoEncTool has already extrapolated the evolutions to separate encounters! + var sf = FindCriteriaToIterate(pk, chain); + if (sf == default) + yield break; + + var species = pk.Species; + var ball = (Ball)pk.Ball; + var met = Math.Max(sf.LevelMin, pk.Met_Level); + EncounterSlot8GO? deferredIV = null; + + foreach (var slot in Slots) { - var areas = new EncounterArea8g[data.Length]; - for (int i = 0; i < areas.Length; i++) - areas[i] = GetArea(data[i]); - return areas; - } + if (!slot.IsLevelWithinRange(met)) + continue; + if (!slot.IsBallValid(ball, species)) + continue; + if (!slot.Shiny.IsValid(pk)) + continue; + if (slot.Gender != Gender.Random && (int)slot.Gender != pk.Gender) + continue; - private const int meta = 4; - private const int entrySize = (2 * sizeof(int)) + 2; - - private static EncounterArea8g GetArea(ReadOnlySpan data) - { - var species = ReadUInt16LittleEndian(data); - var form = data[2]; - var import = (PogoImportFormat)data[3]; - - data = data[meta..]; - var result = new EncounterSlot8GO[data.Length / entrySize]; - var area = new EncounterArea8g(species, form, result); - for (int i = 0; i < result.Length; i++) + if (!slot.GetIVsValid(pk)) { - var offset = i * entrySize; - var entry = data.Slice(offset, entrySize); - result[i] = ReadSlot(entry, area, species, form, import); + deferredIV ??= slot; + continue; } - return area; + yield return slot; } - private static EncounterSlot8GO ReadSlot(ReadOnlySpan entry, EncounterArea8g area, ushort species, byte form, PogoImportFormat format) + if (deferredIV != null) + yield return deferredIV; + } + + private EvoCriteria FindCriteriaToIterate(PKM pk, EvoCriteria[] chain) + { + foreach (var evo in chain) { - int start = ReadInt32LittleEndian(entry); - int end = ReadInt32LittleEndian(entry[4..]); - var sg = entry[8]; - var shiny = (Shiny)(sg & 0x3F); - var gender = (Gender)(sg >> 6); - var type = (PogoType)entry[9]; - return new EncounterSlot8GO(area, species, form, start, end, shiny, gender, type, format); - } - - public override IEnumerable GetMatchingSlots(PKM pkm, EvoCriteria[] chain) - { - if (pkm.TSV == 0) // HOME doesn't assign TSV=0 to accounts. - yield break; - - // Find the first chain that has slots defined. - // Since it is possible to evolve before transferring, we only need the highest evolution species possible. - // PoGoEncTool has already extrapolated the evolutions to separate encounters! - var sf = FindCriteriaToIterate(pkm, chain); - if (sf == default) - yield break; - - var species = pkm.Species; - var ball = (Ball)pkm.Ball; - var met = Math.Max(sf.LevelMin, pkm.Met_Level); - EncounterSlot8GO? deferredIV = null; - - foreach (var slot in Slots) - { - if (!slot.IsLevelWithinRange(met)) - continue; - if (!slot.IsBallValid(ball, species)) - continue; - if (!slot.Shiny.IsValid(pkm)) - continue; - if (slot.Gender != Gender.Random && (int)slot.Gender != pkm.Gender) - continue; - - if (!slot.GetIVsValid(pkm)) - { - deferredIV ??= slot; - continue; - } - - yield return slot; - } - - if (deferredIV != null) - yield return deferredIV; - } - - private EvoCriteria FindCriteriaToIterate(PKM pkm, EvoCriteria[] chain) - { - foreach (var evo in chain) - { - if (evo.Species != Species) - continue; - - if (evo.Form == Form) - return evo; - - // Check for form mismatches - if (FormInfo.IsFormChangeable(Species, Form, evo.Form, pkm.Format)) - return evo; - if (Species == (int)Core.Species.Burmy) - return evo; - break; - } - return default; + if (evo.Species != Species) + continue; + + if (evo.Form == Form) + return evo; + + // Check for form mismatches + if (FormInfo.IsFormChangeable(Species, Form, evo.Form, pk.Format)) + return evo; + if (Species == (int)Core.Species.Burmy) + return evo; + break; } + return default; } } diff --git a/PKHeX.Core/Legality/Breeding.cs b/PKHeX.Core/Legality/Breeding.cs index 0c50e8141..a34e06615 100644 --- a/PKHeX.Core/Legality/Breeding.cs +++ b/PKHeX.Core/Legality/Breeding.cs @@ -3,209 +3,208 @@ using static PKHeX.Core.GameVersion; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic related to breeding. +/// +public static class Breeding { /// - /// Logic related to breeding. + /// Checks if the game has a Daycare, and returns true if it does. /// - public static class Breeding + /// Version ID to check for. + public static bool CanGameGenerateEggs(GameVersion game) => GamesWithEggs.Contains(game); + + private static readonly HashSet GamesWithEggs = new() { - /// - /// Checks if the game has a Daycare, and returns true if it does. - /// - /// Version ID to check for. - public static bool CanGameGenerateEggs(GameVersion game) => GamesWithEggs.Contains(game); + GD, SI, C, + R, S, E, FR, LG, + D, P, Pt, HG, SS, + B, W, B2, W2, + X, Y, OR, AS, + SN, MN, US, UM, + SW, SH, BD, SP, - private static readonly HashSet GamesWithEggs = new() - { - GD, SI, C, - R, S, E, FR, LG, - D, P, Pt, HG, SS, - B, W, B2, W2, - X, Y, OR, AS, - SN, MN, US, UM, - SW, SH, BD, SP, + GS, + }; - GS, - }; + /// + /// Species that have special handling for breeding. + /// + internal static readonly HashSet MixedGenderBreeding = new() + { + (int)NidoranF, + (int)NidoranM, - /// - /// Species that have special handling for breeding. - /// - internal static readonly HashSet MixedGenderBreeding = new() - { - (int)NidoranF, - (int)NidoranM, + (int)Volbeat, + (int)Illumise, - (int)Volbeat, - (int)Illumise, + (int)Indeedee, // male/female + }; - (int)Indeedee, // male/female - }; - - /// - /// Checks if the can be born with inherited moves from the parents. - /// - /// Entity species ID - /// True if can inherit moves, false if cannot. - internal static bool GetCanInheritMoves(int species) - { - if (Legal.FixedGenderFromBiGender.Contains(species)) // Nincada -> Shedinja loses gender causing 'false', edge case - return true; - var pi = PKX.Personal[species]; - if (!pi.Genderless && !pi.OnlyMale) - return true; - if (MixedGenderBreeding.Contains(species)) - return true; - return false; - } - - private static readonly HashSet SplitBreed_3 = new() - { - // Incense - (int)Marill, (int)Azumarill, - (int)Wobbuffet, - }; - - /// - /// Species that can yield a different baby species when bred. - /// - private static readonly HashSet SplitBreed = new(SplitBreed_3) - { - // Incense - (int)Chansey, (int)Blissey, - (int)MrMime, (int)MrRime, - (int)Snorlax, - (int)Sudowoodo, - (int)Mantine, - (int)Roselia, (int)Roserade, - (int)Chimecho, - }; - - internal static ICollection GetSplitBreedGeneration(int generation) => generation switch - { - 3 => SplitBreed_3, - 4 or 5 or 6 or 7 or 8 => SplitBreed, - _ => Array.Empty(), - }; - - /// - /// Checks if the can be obtained from a daycare egg. - /// - /// Chained with the other 2 overloads for incremental checks with different parameters. - /// Current species - public static bool CanHatchAsEgg(int species) => !NoHatchFromEgg.Contains(species); - - /// - /// Checks if the - can exist as a hatched egg in the requested . - /// - /// Chained with the other 2 overloads for incremental checks with different parameters. - /// Current species - /// Current form - /// Generation of origin - public static bool CanHatchAsEgg(int species, int form, int generation) - { - if (form == 0) - return true; - - if (FormInfo.IsTotemForm(species, form, generation)) - return false; - - if (FormInfo.IsLordForm(species, form, generation)) - return false; - - return IsBreedableForm(species, form); - } - - /// - /// Some species can have forms that cannot exist as egg (event/special forms). Same idea as - /// - /// True if can be bred. - private static bool IsBreedableForm(int species, int form) => species switch - { - (int)Pikachu or (int)Eevee => false, // can't get these forms as egg - (int)Pichu => false, // can't get Spiky Ear Pichu eggs - (int)Floette when form == 5 => false, // can't get Eternal Flower from egg - (int)Sinistea or (int)Polteageist => false, // can't get Antique eggs - _ => true, - }; - - /// - /// Checks if the - can exist as a hatched egg in the requested . - /// - /// Chained with the other 2 overloads for incremental checks with different parameters. - /// Current species - /// Current form - /// Game of origin - public static bool CanHatchAsEgg(int species, int form, GameVersion game) - { - // Sanity check form for origin - var pt = GameData.GetPersonal(game); - if ((uint)species > pt.MaxSpeciesID) - return false; - - var entry = pt.GetFormEntry(species, form); - if (!entry.IsPresentInGame) - return false; - return form < entry.FormCount || (species == (int)Rotom && form <= 5); - } - - /// - /// Species that cannot hatch from an egg. - /// - private static readonly HashSet NoHatchFromEgg = new() - { - // Gen1 - (int)Ditto, - (int)Articuno, (int)Zapdos, (int)Moltres, - (int)Mewtwo, (int)Mew, - - // Gen2 - (int)Unown, - (int)Raikou, (int)Entei, (int)Suicune, - (int)Lugia, (int)HoOh, (int)Celebi, - - // Gen3 - (int)Regirock, (int)Regice, (int)Registeel, - (int)Latias, (int)Latios, - (int)Kyogre, (int)Groudon, (int)Rayquaza, - (int)Jirachi, (int)Deoxys, - - // Gen4 - (int)Uxie, (int)Mesprit, (int)Azelf, - (int)Dialga, (int)Palkia, (int)Heatran, - (int)Regigigas, (int)Giratina, (int)Cresselia, - (int)Manaphy, (int)Darkrai, (int)Shaymin, (int)Arceus, - - // Gen5 - (int)Victini, - (int)Cobalion, (int)Terrakion, (int)Virizion, - (int)Tornadus, (int)Thundurus, - (int)Reshiram, (int)Zekrom, - (int)Landorus, (int)Kyurem, - (int)Keldeo, (int)Meloetta, (int)Genesect, - - // Gen6 - (int)Xerneas, (int)Yveltal, (int)Zygarde, - (int)Diancie, (int)Hoopa, (int)Volcanion, - - // Gen7 - (int)TypeNull, (int)Silvally, - (int)TapuKoko, (int)TapuLele, (int)TapuBulu, (int)TapuFini, - (int)Cosmog, (int)Cosmoem, (int)Solgaleo, (int)Lunala, - (int)Nihilego, (int)Buzzwole, (int)Pheromosa, (int)Xurkitree, (int)Celesteela, (int)Kartana, (int)Guzzlord, (int)Necrozma, - (int)Magearna, (int)Marshadow, - (int)Poipole, (int)Naganadel, (int)Stakataka, (int)Blacephalon, (int)Zeraora, - - (int)Meltan, (int)Melmetal, - - // Gen8 - (int)Dracozolt, (int)Arctozolt, (int)Dracovish, (int)Arctovish, - (int)Zacian, (int)Zamazenta, (int)Eternatus, - (int)Kubfu, (int)Urshifu, (int)Zarude, - (int)Regieleki, (int)Regidrago, - (int)Glastrier, (int)Spectrier, (int)Calyrex, - (int)Enamorus, - }; + /// + /// Checks if the can be born with inherited moves from the parents. + /// + /// Entity species ID + /// True if can inherit moves, false if cannot. + internal static bool GetCanInheritMoves(int species) + { + if (Legal.FixedGenderFromBiGender.Contains(species)) // Nincada -> Shedinja loses gender causing 'false', edge case + return true; + var pi = PKX.Personal[species]; + if (!pi.Genderless && !pi.OnlyMale) + return true; + if (MixedGenderBreeding.Contains(species)) + return true; + return false; } + + private static readonly HashSet SplitBreed_3 = new() + { + // Incense + (int)Marill, (int)Azumarill, + (int)Wobbuffet, + }; + + /// + /// Species that can yield a different baby species when bred. + /// + private static readonly HashSet SplitBreed = new(SplitBreed_3) + { + // Incense + (int)Chansey, (int)Blissey, + (int)MrMime, (int)MrRime, + (int)Snorlax, + (int)Sudowoodo, + (int)Mantine, + (int)Roselia, (int)Roserade, + (int)Chimecho, + }; + + internal static ICollection GetSplitBreedGeneration(int generation) => generation switch + { + 3 => SplitBreed_3, + 4 or 5 or 6 or 7 or 8 => SplitBreed, + _ => Array.Empty(), + }; + + /// + /// Checks if the can be obtained from a daycare egg. + /// + /// Chained with the other 2 overloads for incremental checks with different parameters. + /// Current species + public static bool CanHatchAsEgg(int species) => !NoHatchFromEgg.Contains(species); + + /// + /// Checks if the - can exist as a hatched egg in the requested . + /// + /// Chained with the other 2 overloads for incremental checks with different parameters. + /// Current species + /// Current form + /// Generation of origin + public static bool CanHatchAsEgg(int species, int form, int generation) + { + if (form == 0) + return true; + + if (FormInfo.IsTotemForm(species, form, generation)) + return false; + + if (FormInfo.IsLordForm(species, form, generation)) + return false; + + return IsBreedableForm(species, form); + } + + /// + /// Some species can have forms that cannot exist as egg (event/special forms). Same idea as + /// + /// True if can be bred. + private static bool IsBreedableForm(int species, int form) => species switch + { + (int)Pikachu or (int)Eevee => false, // can't get these forms as egg + (int)Pichu => false, // can't get Spiky Ear Pichu eggs + (int)Floette when form == 5 => false, // can't get Eternal Flower from egg + (int)Sinistea or (int)Polteageist => false, // can't get Antique eggs + _ => true, + }; + + /// + /// Checks if the - can exist as a hatched egg in the requested . + /// + /// Chained with the other 2 overloads for incremental checks with different parameters. + /// Current species + /// Current form + /// Game of origin + public static bool CanHatchAsEgg(int species, int form, GameVersion game) + { + // Sanity check form for origin + var pt = GameData.GetPersonal(game); + if ((uint)species > pt.MaxSpeciesID) + return false; + + var entry = pt.GetFormEntry(species, form); + if (!entry.IsPresentInGame) + return false; + return form < entry.FormCount || (species == (int)Rotom && form <= 5); + } + + /// + /// Species that cannot hatch from an egg. + /// + private static readonly HashSet NoHatchFromEgg = new() + { + // Gen1 + (int)Ditto, + (int)Articuno, (int)Zapdos, (int)Moltres, + (int)Mewtwo, (int)Mew, + + // Gen2 + (int)Unown, + (int)Raikou, (int)Entei, (int)Suicune, + (int)Lugia, (int)HoOh, (int)Celebi, + + // Gen3 + (int)Regirock, (int)Regice, (int)Registeel, + (int)Latias, (int)Latios, + (int)Kyogre, (int)Groudon, (int)Rayquaza, + (int)Jirachi, (int)Deoxys, + + // Gen4 + (int)Uxie, (int)Mesprit, (int)Azelf, + (int)Dialga, (int)Palkia, (int)Heatran, + (int)Regigigas, (int)Giratina, (int)Cresselia, + (int)Manaphy, (int)Darkrai, (int)Shaymin, (int)Arceus, + + // Gen5 + (int)Victini, + (int)Cobalion, (int)Terrakion, (int)Virizion, + (int)Tornadus, (int)Thundurus, + (int)Reshiram, (int)Zekrom, + (int)Landorus, (int)Kyurem, + (int)Keldeo, (int)Meloetta, (int)Genesect, + + // Gen6 + (int)Xerneas, (int)Yveltal, (int)Zygarde, + (int)Diancie, (int)Hoopa, (int)Volcanion, + + // Gen7 + (int)TypeNull, (int)Silvally, + (int)TapuKoko, (int)TapuLele, (int)TapuBulu, (int)TapuFini, + (int)Cosmog, (int)Cosmoem, (int)Solgaleo, (int)Lunala, + (int)Nihilego, (int)Buzzwole, (int)Pheromosa, (int)Xurkitree, (int)Celesteela, (int)Kartana, (int)Guzzlord, (int)Necrozma, + (int)Magearna, (int)Marshadow, + (int)Poipole, (int)Naganadel, (int)Stakataka, (int)Blacephalon, (int)Zeraora, + + (int)Meltan, (int)Melmetal, + + // Gen8 + (int)Dracozolt, (int)Arctozolt, (int)Dracovish, (int)Arctovish, + (int)Zacian, (int)Zamazenta, (int)Eternatus, + (int)Kubfu, (int)Urshifu, (int)Zarude, + (int)Regieleki, (int)Regidrago, + (int)Glastrier, (int)Spectrier, (int)Calyrex, + (int)Enamorus, + }; } diff --git a/PKHeX.Core/Legality/BulkAnalysis.cs b/PKHeX.Core/Legality/BulkAnalysis.cs index e7c20a6e0..18df1015a 100644 --- a/PKHeX.Core/Legality/BulkAnalysis.cs +++ b/PKHeX.Core/Legality/BulkAnalysis.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -6,377 +6,376 @@ using PKHeX.Core.Searching; using static PKHeX.Core.CheckIdentifier; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Analyzes content within a for overall legality analysis. +/// +public sealed class BulkAnalysis { - /// - /// Analyzes content within a for overall legality analysis. - /// - public sealed class BulkAnalysis + public readonly IReadOnlyList AllData; + public readonly IReadOnlyList AllAnalysis; + public readonly ITrainerInfo Trainer; + public readonly List Parse = new(); + public readonly Dictionary Trackers = new(); + public readonly bool Valid; + + private readonly IBulkAnalysisSettings Settings; + private readonly bool[] CloneFlags; + + public BulkAnalysis(SaveFile sav, IBulkAnalysisSettings settings) { - public readonly IReadOnlyList AllData; - public readonly IReadOnlyList AllAnalysis; - public readonly ITrainerInfo Trainer; - public readonly List Parse = new(); - public readonly Dictionary Trackers = new(); - public readonly bool Valid; + Trainer = sav; + Settings = settings; + var list = new List(sav.BoxSlotCount + (sav.HasParty ? 6 : 0) + 5); + SlotInfoLoader.AddFromSaveFile(sav, list); + list.RemoveAll(IsEmptyData); + AllData = list; + AllAnalysis = GetIndividualAnalysis(AllData); + CloneFlags = new bool[AllData.Count]; - private readonly IBulkAnalysisSettings Settings; - private readonly bool[] CloneFlags; + ScanAll(); + Valid = Parse.Any(z => !z.Valid); + } - public BulkAnalysis(SaveFile sav, IBulkAnalysisSettings settings) + // Remove things that aren't actual stored data, or already flagged by legality checks. + private static bool IsEmptyData(SlotCache obj) + { + var pk = obj.Entity; + if ((uint)(pk.Species - 1) >= pk.MaxSpeciesID) + return true; + if (!pk.ChecksumValid) + return true; + return false; + } + + private void ScanAll() + { + CheckClones(); + if (Trainer.Generation <= 2) + return; + + CheckIDReuse(); + CheckPIDReuse(); + if (Trainer.Generation >= 6) { - Trainer = sav; - Settings = settings; - var list = new List(sav.BoxSlotCount + (sav.HasParty ? 6 : 0) + 5); - SlotInfoLoader.AddFromSaveFile(sav, list); - list.RemoveAll(IsEmptyData); - AllData = list; - AllAnalysis = GetIndividualAnalysis(AllData); - CloneFlags = new bool[AllData.Count]; - - Valid = ScanAll(); + CheckECReuse(); + if (Settings.CheckActiveHandler) + CheckHandlerFlag(); } - // Remove things that aren't actual stored data, or already flagged by legality checks. - private static bool IsEmptyData(SlotCache obj) - { - var pkm = obj.Entity; - if ((uint)(pkm.Species - 1) >= pkm.MaxSpeciesID) - return true; - if (!pkm.ChecksumValid) - return true; - return false; - } + CheckDuplicateOwnedGifts(); + } - private bool ScanAll() - { - CheckClones(); - if (Trainer.Generation <= 2) - return Parse.All(z => z.Valid); + private static string GetSummary(SlotCache entry) => $"[{entry.Identify()}] {entry.Entity.FileName}"; - CheckIDReuse(); - CheckPIDReuse(); - if (Trainer.Generation >= 6) + private void AddLine(SlotCache first, SlotCache second, string msg, CheckIdentifier i, Severity s = Severity.Invalid) + { + var c = $"{msg}{Environment.NewLine}{GetSummary(first)}{Environment.NewLine}{GetSummary(second)}"; + var chk = new CheckResult(s, c, i); + Parse.Add(chk); + } + + private void AddLine(SlotCache first, string msg, CheckIdentifier i, Severity s = Severity.Invalid) + { + var c = $"{msg}{Environment.NewLine}{GetSummary(first)}"; + var chk = new CheckResult(s, c, i); + Parse.Add(chk); + } + + private void CheckClones() + { + var dict = new Dictionary(); + for (int i = 0; i < AllData.Count; i++) + { + var cs = AllData[i]; + var ca = AllAnalysis[i]; + Debug.Assert(cs.Entity.Format == Trainer.Generation); + + // Check the upload tracker to see if there's any duplication. + if (cs.Entity is IHomeTrack home) { - CheckECReuse(); - if (Settings.CheckActiveHandler) - CheckHandlerFlag(); - } - - CheckDuplicateOwnedGifts(); - return Parse.All(z => z.Valid); - } - - private static string GetSummary(SlotCache entry) => $"[{entry.Identify()}] {entry.Entity.FileName}"; - - private void AddLine(SlotCache first, SlotCache second, string msg, CheckIdentifier i, Severity s = Severity.Invalid) - { - var c = $"{msg}{Environment.NewLine}{GetSummary(first)}{Environment.NewLine}{GetSummary(second)}"; - var chk = new CheckResult(s, c, i); - Parse.Add(chk); - } - - private void AddLine(SlotCache first, string msg, CheckIdentifier i, Severity s = Severity.Invalid) - { - var c = $"{msg}{Environment.NewLine}{GetSummary(first)}"; - var chk = new CheckResult(s, c, i); - Parse.Add(chk); - } - - private void CheckClones() - { - var dict = new Dictionary(); - for (int i = 0; i < AllData.Count; i++) - { - var cs = AllData[i]; - var ca = AllAnalysis[i]; - Debug.Assert(cs.Entity.Format == Trainer.Generation); - - // Check the upload tracker to see if there's any duplication. - if (cs.Entity is IHomeTrack home) + if (home.Tracker != 0) { - if (home.Tracker != 0) - { - var tracker = home.Tracker; - if (Trackers.TryGetValue(tracker, out var clone)) - AddLine(cs, clone!, "Clone detected (Duplicate Tracker).", Encounter); - else - Trackers.Add(tracker, cs); - } - else if (ca.Info.Generation is (< 8 and not -1)) - { - AddLine(cs, "Missing tracker.", Encounter); - } + var tracker = home.Tracker; + if (Trackers.TryGetValue(tracker, out var clone)) + AddLine(cs, clone!, "Clone detected (Duplicate Tracker).", Encounter); + else + Trackers.Add(tracker, cs); } - - // Hash Details like EC/IV to see if there's any duplication. - var identity = SearchUtil.HashByDetails(cs.Entity); - if (!dict.TryGetValue(identity, out var ps)) + else if (ca.Info.Generation is (< 8 and not -1)) { - dict.Add(identity, cs); - continue; - } - - CloneFlags[i] = true; - AddLine(ps!, cs, "Clone detected (Details).", Encounter); - } - } - - private void CheckDuplicateOwnedGifts() - { - var combined = new CombinedReference[AllData.Count]; - for (int i = 0; i < combined.Length; i++) - combined[i] = new CombinedReference(AllData[i], AllAnalysis[i]); - - var dupes = combined.Where(z => - z.Analysis.Info.Generation >= 3 - && z.Analysis.EncounterMatch is MysteryGift {EggEncounter: true} && !z.Slot.Entity.WasTradedEgg) - .GroupBy(z => ((MysteryGift)z.Analysis.EncounterMatch).CardTitle); - - foreach (var dupe in dupes) - { - var tidGroup = dupe.GroupBy(z => z.Slot.Entity.TID | (z.Slot.Entity.SID << 16)) - .Select(z => z.ToList()) - .Where(z => z.Count >= 2).ToList(); - if (tidGroup.Count == 0) - continue; - - var first = tidGroup[0][0].Slot; - var second = tidGroup[0][1].Slot; - AddLine(first, second, $"Receipt of the same egg mystery gifts detected: {dupe.Key}", Encounter); - } - } - - private void CheckECReuse() - { - var dict = new Dictionary(); - for (int i = 0; i < AllData.Count; i++) - { - if (CloneFlags[i]) - continue; // already flagged - var cp = AllData[i]; - var ca = AllAnalysis[i]; - Debug.Assert(cp.Entity.Format >= 6); - var id = cp.Entity.EncryptionConstant; - - var cr = new CombinedReference(cp, ca); - if (!dict.TryGetValue(id, out var pa) || pa is null) - { - dict.Add(id, cr); - continue; - } - VerifyECShare(pa, cr); - } - } - - private void CheckPIDReuse() - { - var dict = new Dictionary(); - for (int i = 0; i < AllData.Count; i++) - { - if (CloneFlags[i]) - continue; // already flagged - var cp = AllData[i]; - var ca = AllAnalysis[i]; - bool g345 = ca.Info.Generation is 3 or 4 or 5; - var id = g345 ? cp.Entity.EncryptionConstant : cp.Entity.PID; - - var cr = new CombinedReference(cp, ca); - if (!dict.TryGetValue(id, out var pr) || pr is null) - { - dict.Add(id, cr); - continue; - } - VerifyPIDShare(pr, cr); - } - } - - private void CheckHandlerFlag() - { - for (var i = 0; i < AllData.Count; i++) - { - if (!AllAnalysis[i].Valid) - continue; - var cs = AllData[i]; - var pk = cs.Entity; - var tr = cs.SAV; - var withOT = tr.IsFromTrainer(pk); - var flag = pk.CurrentHandler; - var expect = withOT ? 0 : 1; - if (flag != expect) - AddLine(cs, LegalityCheckStrings.LTransferCurrentHandlerInvalid, CheckIdentifier.Trainer); - - if (flag == 1) - { - if (pk.HT_Name != tr.OT) - AddLine(cs, LegalityCheckStrings.LTransferHTMismatchName, CheckIdentifier.Trainer); - if (pk is IHandlerLanguage h && h.HT_Language != tr.Language) - AddLine(cs, LegalityCheckStrings.LTransferHTMismatchLanguage, CheckIdentifier.Trainer); + AddLine(cs, "Missing tracker.", Encounter); } } - } - private sealed record CombinedReference(SlotCache Slot, LegalityAnalysis Analysis); - - private void CheckIDReuse() - { - var dict = new Dictionary(); - for (int i = 0; i < AllData.Count; i++) + // Hash Details like EC/IV to see if there's any duplication. + var identity = SearchUtil.HashByDetails(cs.Entity); + if (!dict.TryGetValue(identity, out var ps)) { - if (CloneFlags[i]) - continue; // already flagged - var cs = AllData[i]; - var ca = AllAnalysis[i]; - var id = cs.Entity.TID + (cs.Entity.SID << 16); - Debug.Assert(cs.Entity.TID <= ushort.MaxValue); - - if (!dict.TryGetValue(id, out var pr) || pr is null) - { - var r = new CombinedReference(cs, ca); - dict.Add(id, r); - continue; - } - - var pa = pr.Analysis; - // ignore GB era collisions - // a 16bit TID can reasonably occur for multiple trainers, and versions - if (ca.Info.Generation <= 2 && pa.Info.Generation <= 2) - continue; - - var ps = pr.Slot; - if (VerifyIDReuse(ps, pa, cs, ca)) - continue; - - // egg encounters can be traded before hatching - // store the current loop pkm if it's a better reference - if (ps.Entity.WasTradedEgg && !cs.Entity.WasTradedEgg) - dict[id] = new CombinedReference(cs, ca); - } - } - - private void VerifyECShare(CombinedReference pr, CombinedReference cr) - { - var (ps, pa) = pr; - var (cs, ca) = cr; - - const CheckIdentifier ident = PID; - int gen = pa.Info.Generation; - bool gbaNDS = gen is 3 or 4 or 5; - - if (!gbaNDS) - { - if (ca.Info.Generation != gen) - { - AddLine(ps, cs, "EC sharing across generations detected.", ident); - return; - } - AddLine(ps, cs, "EC sharing for 3DS-onward origin detected.", ident); - return; + dict.Add(identity, cs); + continue; } - // eggs/mystery gifts shouldn't share with wild encounters - var cenc = ca.Info.EncounterMatch; - bool eggMysteryCurrent = cenc is EncounterEgg or MysteryGift; - var penc = pa.Info.EncounterMatch; - bool eggMysteryPrevious = penc is EncounterEgg or MysteryGift; + CloneFlags[i] = true; + AddLine(ps!, cs, "Clone detected (Details).", Encounter); + } + } - if (eggMysteryCurrent != eggMysteryPrevious) + private void CheckDuplicateOwnedGifts() + { + var combined = new CombinedReference[AllData.Count]; + for (int i = 0; i < combined.Length; i++) + combined[i] = new CombinedReference(AllData[i], AllAnalysis[i]); + + var dupes = combined.Where(z => + z.Analysis.Info.Generation >= 3 + && z.Analysis.EncounterMatch is MysteryGift {EggEncounter: true} && !z.Slot.Entity.WasTradedEgg) + .GroupBy(z => ((MysteryGift)z.Analysis.EncounterMatch).CardTitle); + + foreach (var dupe in dupes) + { + var tidGroup = dupe.GroupBy(z => z.Slot.Entity.TID | (z.Slot.Entity.SID << 16)) + .Select(z => z.ToList()) + .Where(z => z.Count >= 2).ToList(); + if (tidGroup.Count == 0) + continue; + + var first = tidGroup[0][0].Slot; + var second = tidGroup[0][1].Slot; + AddLine(first, second, $"Receipt of the same egg mystery gifts detected: {dupe.Key}", Encounter); + } + } + + private void CheckECReuse() + { + var dict = new Dictionary(); + for (int i = 0; i < AllData.Count; i++) + { + if (CloneFlags[i]) + continue; // already flagged + var cp = AllData[i]; + var ca = AllAnalysis[i]; + Debug.Assert(cp.Entity.Format >= 6); + var id = cp.Entity.EncryptionConstant; + + var cr = new CombinedReference(cp, ca); + if (!dict.TryGetValue(id, out var pa) || pa is null) { - AddLine(ps, cs, "EC sharing across RNG encounters detected.", ident); + dict.Add(id, cr); + continue; + } + VerifyECShare(pa, cr); + } + } + + private void CheckPIDReuse() + { + var dict = new Dictionary(); + for (int i = 0; i < AllData.Count; i++) + { + if (CloneFlags[i]) + continue; // already flagged + var cp = AllData[i]; + var ca = AllAnalysis[i]; + bool g345 = ca.Info.Generation is 3 or 4 or 5; + var id = g345 ? cp.Entity.EncryptionConstant : cp.Entity.PID; + + var cr = new CombinedReference(cp, ca); + if (!dict.TryGetValue(id, out var pr) || pr is null) + { + dict.Add(id, cr); + continue; + } + VerifyPIDShare(pr, cr); + } + } + + private void CheckHandlerFlag() + { + for (var i = 0; i < AllData.Count; i++) + { + if (!AllAnalysis[i].Valid) + continue; + var cs = AllData[i]; + var pk = cs.Entity; + var tr = cs.SAV; + var withOT = tr.IsFromTrainer(pk); + var flag = pk.CurrentHandler; + var expect = withOT ? 0 : 1; + if (flag != expect) + AddLine(cs, LegalityCheckStrings.LTransferCurrentHandlerInvalid, CheckIdentifier.Trainer); + + if (flag == 1) + { + if (pk.HT_Name != tr.OT) + AddLine(cs, LegalityCheckStrings.LTransferHTMismatchName, CheckIdentifier.Trainer); + if (pk is IHandlerLanguage h && h.HT_Language != tr.Language) + AddLine(cs, LegalityCheckStrings.LTransferHTMismatchLanguage, CheckIdentifier.Trainer); } } + } - private void VerifyPIDShare(CombinedReference pr, CombinedReference cr) + private sealed record CombinedReference(SlotCache Slot, LegalityAnalysis Analysis); + + private void CheckIDReuse() + { + var dict = new Dictionary(); + for (int i = 0; i < AllData.Count; i++) { - var ps = pr.Slot; + if (CloneFlags[i]) + continue; // already flagged + var cs = AllData[i]; + var ca = AllAnalysis[i]; + var id = cs.Entity.TID + (cs.Entity.SID << 16); + Debug.Assert(cs.Entity.TID <= ushort.MaxValue); + + if (!dict.TryGetValue(id, out var pr) || pr is null) + { + var r = new CombinedReference(cs, ca); + dict.Add(id, r); + continue; + } + var pa = pr.Analysis; - var cs = cr.Slot; - var ca = cr.Analysis; - const CheckIdentifier ident = PID; - int gen = pa.Info.Generation; + // ignore GB era collisions + // a 16bit TID can reasonably occur for multiple trainers, and versions + if (ca.Info.Generation <= 2 && pa.Info.Generation <= 2) + continue; + var ps = pr.Slot; + if (VerifyIDReuse(ps, pa, cs, ca)) + continue; + + // egg encounters can be traded before hatching + // store the current loop pk if it's a better reference + if (ps.Entity.WasTradedEgg && !cs.Entity.WasTradedEgg) + dict[id] = new CombinedReference(cs, ca); + } + } + + private void VerifyECShare(CombinedReference pr, CombinedReference cr) + { + var (ps, pa) = pr; + var (cs, ca) = cr; + + const CheckIdentifier ident = PID; + int gen = pa.Info.Generation; + bool gbaNDS = gen is 3 or 4 or 5; + + if (!gbaNDS) + { if (ca.Info.Generation != gen) { - AddLine(ps, cs, "PID sharing across generations detected.", ident); + AddLine(ps, cs, "EC sharing across generations detected.", ident); return; } - - bool gbaNDS = gen is 3 or 4 or 5; - if (!gbaNDS) - { - AddLine(ps, cs, "PID sharing for 3DS-onward origin detected.", ident); - return; - } - - // eggs/mystery gifts shouldn't share with wild encounters - var cenc = ca.Info.EncounterMatch; - bool eggMysteryCurrent = cenc is EncounterEgg or MysteryGift; - var penc = pa.Info.EncounterMatch; - bool eggMysteryPrevious = penc is EncounterEgg or MysteryGift; - - if (eggMysteryCurrent != eggMysteryPrevious) - { - AddLine(ps, cs, "PID sharing across RNG encounters detected.", ident); - } + AddLine(ps, cs, "EC sharing for 3DS-onward origin detected.", ident); + return; } - private bool VerifyIDReuse(SlotCache ps, LegalityAnalysis pa, SlotCache cs, LegalityAnalysis ca) + // eggs/mystery gifts shouldn't share with wild encounters + var cenc = ca.Info.EncounterMatch; + bool eggMysteryCurrent = cenc is EncounterEgg or MysteryGift; + var penc = pa.Info.EncounterMatch; + bool eggMysteryPrevious = penc is EncounterEgg or MysteryGift; + + if (eggMysteryCurrent != eggMysteryPrevious) { - if (pa.EncounterMatch is MysteryGift {EggEncounter: false}) - return false; - if (ca.EncounterMatch is MysteryGift {EggEncounter: false}) - return false; + AddLine(ps, cs, "EC sharing across RNG encounters detected.", ident); + } + } - const CheckIdentifier ident = CheckIdentifier.Trainer; + private void VerifyPIDShare(CombinedReference pr, CombinedReference cr) + { + var ps = pr.Slot; + var pa = pr.Analysis; + var cs = cr.Slot; + var ca = cr.Analysis; + const CheckIdentifier ident = PID; + int gen = pa.Info.Generation; - var pp = ps.Entity; - var cp = cs.Entity; + if (ca.Info.Generation != gen) + { + AddLine(ps, cs, "PID sharing across generations detected.", ident); + return; + } - // 32bit ID-SID should only occur for one generation - // Trainer-ID-SID should only occur for one version - if (IsSharedVersion(pp, pa, cp, ca)) - { - AddLine(ps, cs, "TID sharing across versions detected.", ident); - return true; - } + bool gbaNDS = gen is 3 or 4 or 5; + if (!gbaNDS) + { + AddLine(ps, cs, "PID sharing for 3DS-onward origin detected.", ident); + return; + } - // ID-SID should only occur for one Trainer name - if (pp.OT_Name != cp.OT_Name) - { - var severity = ca.Info.Generation == 4 ? Severity.Fishy : Severity.Invalid; - AddLine(ps, cs, "TID sharing across different trainer names detected.", ident, severity); - } + // eggs/mystery gifts shouldn't share with wild encounters + var cenc = ca.Info.EncounterMatch; + bool eggMysteryCurrent = cenc is EncounterEgg or MysteryGift; + var penc = pa.Info.EncounterMatch; + bool eggMysteryPrevious = penc is EncounterEgg or MysteryGift; + if (eggMysteryCurrent != eggMysteryPrevious) + { + AddLine(ps, cs, "PID sharing across RNG encounters detected.", ident); + } + } + + private bool VerifyIDReuse(SlotCache ps, LegalityAnalysis pa, SlotCache cs, LegalityAnalysis ca) + { + if (pa.EncounterMatch is MysteryGift {EggEncounter: false}) + return false; + if (ca.EncounterMatch is MysteryGift {EggEncounter: false}) return false; - } - private static bool IsSharedVersion(PKM pp, LegalityAnalysis pa, PKM cp, LegalityAnalysis ca) + const CheckIdentifier ident = CheckIdentifier.Trainer; + + var pp = ps.Entity; + var cp = cs.Entity; + + // 32bit ID-SID should only occur for one generation + // Trainer-ID-SID should only occur for one version + if (IsSharedVersion(pp, pa, cp, ca)) { - if (pp.Version == cp.Version) - return false; - - // Traded eggs retain the original version ID, only on the same generation - if (pa.Info.Generation != ca.Info.Generation) - return false; - - if (pa.EncounterMatch.EggEncounter && pp.WasTradedEgg) - return false; // version doesn't update on trade - if (ca.EncounterMatch.EggEncounter && cp.WasTradedEgg) - return false; // version doesn't update on trade - + AddLine(ps, cs, "TID sharing across versions detected.", ident); return true; } - private static IReadOnlyList GetIndividualAnalysis(IReadOnlyList pkms) + // ID-SID should only occur for one Trainer name + if (pp.OT_Name != cp.OT_Name) { - var results = new LegalityAnalysis[pkms.Count]; - for (int i = 0; i < pkms.Count; i++) - { - var entry = pkms[i]; - var pk = entry.Entity; - results[i] = new LegalityAnalysis(pk); - } - return results; + var severity = ca.Info.Generation == 4 ? Severity.Fishy : Severity.Invalid; + AddLine(ps, cs, "TID sharing across different trainer names detected.", ident, severity); } + + return false; + } + + private static bool IsSharedVersion(PKM pp, LegalityAnalysis pa, PKM cp, LegalityAnalysis ca) + { + if (pp.Version == cp.Version) + return false; + + // Traded eggs retain the original version ID, only on the same generation + if (pa.Info.Generation != ca.Info.Generation) + return false; + + if (pa.EncounterMatch.EggEncounter && pp.WasTradedEgg) + return false; // version doesn't update on trade + if (ca.EncounterMatch.EggEncounter && cp.WasTradedEgg) + return false; // version doesn't update on trade + + return true; + } + + private static IReadOnlyList GetIndividualAnalysis(IReadOnlyList pkms) + { + var results = new LegalityAnalysis[pkms.Count]; + for (int i = 0; i < pkms.Count; i++) + { + var entry = pkms[i]; + var pk = entry.Entity; + results[i] = new LegalityAnalysis(pk); + } + return results; } } diff --git a/PKHeX.Core/Legality/BulkGenerator.cs b/PKHeX.Core/Legality/BulkGenerator.cs index bd69ebdcb..53059d632 100644 --- a/PKHeX.Core/Legality/BulkGenerator.cs +++ b/PKHeX.Core/Legality/BulkGenerator.cs @@ -2,67 +2,66 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for generating a large amount of data. +/// +public static class BulkGenerator { - /// - /// Logic for generating a large amount of data. - /// - public static class BulkGenerator + public static List GetLivingDex(this SaveFile sav) { - public static List GetLivingDex(this SaveFile sav) - { - var speciesToGenerate = Enumerable.Range(1, sav.MaxSpeciesID); - return GetLivingDex(sav, speciesToGenerate); - } + var speciesToGenerate = Enumerable.Range(1, sav.MaxSpeciesID); + return GetLivingDex(sav, speciesToGenerate); + } - private static List GetLivingDex(SaveFile sav, IEnumerable speciesToGenerate) - { - return sav.GetLivingDex(speciesToGenerate, sav.BlankPKM); - } + private static List GetLivingDex(SaveFile sav, IEnumerable speciesToGenerate) + { + return sav.GetLivingDex(speciesToGenerate, sav.BlankPKM); + } - public static List GetLivingDex(this ITrainerInfo tr, IEnumerable speciesToGenerate, PKM blank) + public static List GetLivingDex(this ITrainerInfo tr, IEnumerable speciesToGenerate, PKM blank) + { + var result = new List(); + var destType = blank.GetType(); + foreach (var s in speciesToGenerate) { - var result = new List(); - var destType = blank.GetType(); - foreach (var s in speciesToGenerate) + var pk = blank.Clone(); + pk.Species = s; + pk.Gender = pk.GetSaneGender(); + + var pi = pk.PersonalInfo; + for (int f = 0; f < pi.FormCount; f++) { - var pk = blank.Clone(); - pk.Species = s; - pk.Gender = pk.GetSaneGender(); - - var pi = pk.PersonalInfo; - for (int f = 0; f < pi.FormCount; f++) - { - var entry = tr.GetLivingEntry(pk, s, f, destType); - if (entry == null) - continue; - result.Add(entry); - } + var entry = tr.GetLivingEntry(pk, s, f, destType); + if (entry == null) + continue; + result.Add(entry); } - - return result; } - public static PKM? GetLivingEntry(this ITrainerInfo tr, PKM template, int species, int form, Type destType) - { - template.Species = species; - template.Form = form; - template.Gender = template.GetSaneGender(); + return result; + } - var f = EncounterMovesetGenerator.GeneratePKMs(template, tr).FirstOrDefault(); - if (f == null) - return null; + public static PKM? GetLivingEntry(this ITrainerInfo tr, PKM template, int species, int form, Type destType) + { + template.Species = species; + template.Form = form; + template.Gender = template.GetSaneGender(); - var result = EntityConverter.ConvertToType(f, destType, out _); - if (result == null) - return null; + var f = EncounterMovesetGenerator.GeneratePKMs(template, tr).FirstOrDefault(); + if (f == null) + return null; - result.Species = species; - result.Form = form; - result.CurrentLevel = 100; + var result = EntityConverter.ConvertToType(f, destType, out _); + if (result == null) + return null; - result.Heal(); - return result; - } + result.Species = species; + result.Form = form; + result.CurrentLevel = 100; + + result.Heal(); + return result; } } diff --git a/PKHeX.Core/Legality/Core.cs b/PKHeX.Core/Legality/Core.cs index 7d331ea59..81429c2ea 100644 --- a/PKHeX.Core/Legality/Core.cs +++ b/PKHeX.Core/Legality/Core.cs @@ -1,302 +1,301 @@ using System; using static PKHeX.Core.BinLinkerAccessor; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + // Gen 1 + internal static readonly Learnset[] LevelUpRB = LearnsetReader.GetArray(Util.GetBinaryResource("lvlmove_rb.pkl"), MaxSpeciesID_1); + internal static readonly Learnset[] LevelUpY = LearnsetReader.GetArray(Util.GetBinaryResource("lvlmove_y.pkl"), MaxSpeciesID_1); + + // Gen 2 + internal static readonly EggMoves2[] EggMovesGS = EggMoves2.GetArray(Util.GetBinaryResource("eggmove_gs.pkl"), MaxSpeciesID_2); + internal static readonly Learnset[] LevelUpGS = LearnsetReader.GetArray(Util.GetBinaryResource("lvlmove_gs.pkl"), MaxSpeciesID_2); + internal static readonly EggMoves2[] EggMovesC = EggMoves2.GetArray(Util.GetBinaryResource("eggmove_c.pkl"), MaxSpeciesID_2); + internal static readonly Learnset[] LevelUpC = LearnsetReader.GetArray(Util.GetBinaryResource("lvlmove_c.pkl"), MaxSpeciesID_2); + + // Gen 3 + internal static readonly Learnset[] LevelUpE = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_e.pkl"), "em")); + internal static readonly Learnset[] LevelUpRS = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_rs.pkl"), "rs")); + internal static readonly Learnset[] LevelUpFR = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_fr.pkl"), "fr")); + internal static readonly Learnset[] LevelUpLG = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_lg.pkl"), "lg")); + internal static readonly EggMoves6[] EggMovesRS = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_rs.pkl"), "rs")); + + // Gen 4 + internal static readonly Learnset[] LevelUpDP = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_dp.pkl"), "dp")); + internal static readonly Learnset[] LevelUpPt = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_pt.pkl"), "pt")); + internal static readonly Learnset[] LevelUpHGSS = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_hgss.pkl"), "hs")); + internal static readonly EggMoves6[] EggMovesDPPt = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_dppt.pkl"), "dp")); + internal static readonly EggMoves6[] EggMovesHGSS = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_hgss.pkl"), "hs")); + + // Gen 5 + internal static readonly Learnset[] LevelUpBW = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_bw.pkl"), "51")); + internal static readonly Learnset[] LevelUpB2W2 = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_b2w2.pkl"), "52")); + internal static readonly EggMoves6[] EggMovesBW = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_bw.pkl"), "bw")); + + // Gen 6 + internal static readonly EggMoves6[] EggMovesXY = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_xy.pkl"), "xy")); + internal static readonly Learnset[] LevelUpXY = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_xy.pkl"), "xy")); + internal static readonly EggMoves6[] EggMovesAO = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_ao.pkl"), "ao")); + internal static readonly Learnset[] LevelUpAO = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_ao.pkl"), "ao")); + + // Gen 7 + internal static readonly EggMoves7[] EggMovesSM = EggMoves7.GetArray(Get(Util.GetBinaryResource("eggmove_sm.pkl"), "sm")); + internal static readonly Learnset[] LevelUpSM = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_sm.pkl"), "sm")); + internal static readonly EggMoves7[] EggMovesUSUM = EggMoves7.GetArray(Get(Util.GetBinaryResource("eggmove_uu.pkl"), "uu")); + internal static readonly Learnset[] LevelUpUSUM = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_uu.pkl"), "uu")); + internal static readonly Learnset[] LevelUpGG = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_gg.pkl"), "gg")); + + // Gen 8 + internal static readonly EggMoves7[] EggMovesSWSH = EggMoves7.GetArray(Get(Util.GetBinaryResource("eggmove_swsh.pkl"), "ss")); + internal static readonly Learnset[] LevelUpSWSH = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_swsh.pkl"), "ss")); + internal static readonly EggMoves6[] EggMovesBDSP = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_bdsp.pkl"), "bs")); + internal static readonly Learnset[] LevelUpBDSP = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_bdsp.pkl"), "bs")); + internal static readonly Learnset[] LevelUpLA = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_la.pkl"), "la")); + internal static readonly Learnset[] MasteryLA = LearnsetReader.GetArray(Get(Util.GetBinaryResource("mastery_la.pkl"), "la")); + + internal static int GetMaxSpeciesOrigin(PKM pk) { - // Gen 1 - internal static readonly Learnset[] LevelUpRB = LearnsetReader.GetArray(Util.GetBinaryResource("lvlmove_rb.pkl"), MaxSpeciesID_1); - internal static readonly Learnset[] LevelUpY = LearnsetReader.GetArray(Util.GetBinaryResource("lvlmove_y.pkl"), MaxSpeciesID_1); - - // Gen 2 - internal static readonly EggMoves2[] EggMovesGS = EggMoves2.GetArray(Util.GetBinaryResource("eggmove_gs.pkl"), MaxSpeciesID_2); - internal static readonly Learnset[] LevelUpGS = LearnsetReader.GetArray(Util.GetBinaryResource("lvlmove_gs.pkl"), MaxSpeciesID_2); - internal static readonly EggMoves2[] EggMovesC = EggMoves2.GetArray(Util.GetBinaryResource("eggmove_c.pkl"), MaxSpeciesID_2); - internal static readonly Learnset[] LevelUpC = LearnsetReader.GetArray(Util.GetBinaryResource("lvlmove_c.pkl"), MaxSpeciesID_2); - - // Gen 3 - internal static readonly Learnset[] LevelUpE = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_e.pkl"), "em")); - internal static readonly Learnset[] LevelUpRS = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_rs.pkl"), "rs")); - internal static readonly Learnset[] LevelUpFR = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_fr.pkl"), "fr")); - internal static readonly Learnset[] LevelUpLG = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_lg.pkl"), "lg")); - internal static readonly EggMoves6[] EggMovesRS = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_rs.pkl"), "rs")); - - // Gen 4 - internal static readonly Learnset[] LevelUpDP = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_dp.pkl"), "dp")); - internal static readonly Learnset[] LevelUpPt = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_pt.pkl"), "pt")); - internal static readonly Learnset[] LevelUpHGSS = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_hgss.pkl"), "hs")); - internal static readonly EggMoves6[] EggMovesDPPt = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_dppt.pkl"), "dp")); - internal static readonly EggMoves6[] EggMovesHGSS = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_hgss.pkl"), "hs")); - - // Gen 5 - internal static readonly Learnset[] LevelUpBW = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_bw.pkl"), "51")); - internal static readonly Learnset[] LevelUpB2W2 = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_b2w2.pkl"), "52")); - internal static readonly EggMoves6[] EggMovesBW = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_bw.pkl"), "bw")); - - // Gen 6 - internal static readonly EggMoves6[] EggMovesXY = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_xy.pkl"), "xy")); - internal static readonly Learnset[] LevelUpXY = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_xy.pkl"), "xy")); - internal static readonly EggMoves6[] EggMovesAO = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_ao.pkl"), "ao")); - internal static readonly Learnset[] LevelUpAO = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_ao.pkl"), "ao")); - - // Gen 7 - internal static readonly EggMoves7[] EggMovesSM = EggMoves7.GetArray(Get(Util.GetBinaryResource("eggmove_sm.pkl"), "sm")); - internal static readonly Learnset[] LevelUpSM = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_sm.pkl"), "sm")); - internal static readonly EggMoves7[] EggMovesUSUM = EggMoves7.GetArray(Get(Util.GetBinaryResource("eggmove_uu.pkl"), "uu")); - internal static readonly Learnset[] LevelUpUSUM = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_uu.pkl"), "uu")); - internal static readonly Learnset[] LevelUpGG = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_gg.pkl"), "gg")); - - // Gen 8 - internal static readonly EggMoves7[] EggMovesSWSH = EggMoves7.GetArray(Get(Util.GetBinaryResource("eggmove_swsh.pkl"), "ss")); - internal static readonly Learnset[] LevelUpSWSH = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_swsh.pkl"), "ss")); - internal static readonly EggMoves6[] EggMovesBDSP = EggMoves6.GetArray(Get(Util.GetBinaryResource("eggmove_bdsp.pkl"), "bs")); - internal static readonly Learnset[] LevelUpBDSP = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_bdsp.pkl"), "bs")); - internal static readonly Learnset[] LevelUpLA = LearnsetReader.GetArray(Get(Util.GetBinaryResource("lvlmove_la.pkl"), "la")); - internal static readonly Learnset[] MasteryLA = LearnsetReader.GetArray(Get(Util.GetBinaryResource("mastery_la.pkl"), "la")); - - internal static int GetMaxSpeciesOrigin(PKM pkm) - { - if (pkm.Format == 1) - return GetMaxSpeciesOrigin(1); - if (pkm.Format == 2 || pkm.VC) - return GetMaxSpeciesOrigin(2); - return GetMaxSpeciesOrigin(pkm.Generation); - } - - internal static int GetMaxSpeciesOrigin(int generation, GameVersion version) => generation switch - { - 1 => MaxSpeciesID_1, - 2 => MaxSpeciesID_2, - 3 => MaxSpeciesID_3, - 4 => MaxSpeciesID_4, - 5 => MaxSpeciesID_5, - 6 => MaxSpeciesID_6, - 7 when GameVersion.GG.Contains(version) => MaxSpeciesID_7b, - 7 when GameVersion.USUM.Contains(version) => MaxSpeciesID_7_USUM, - 7 => MaxSpeciesID_7, - 8 when version is GameVersion.PLA => MaxSpeciesID_8a, - 8 when GameVersion.BDSP.Contains(version) => MaxSpeciesID_8b, - 8 => MaxSpeciesID_8_R2, - _ => -1, - }; - - internal static int GetMaxSpeciesOrigin(EntityContext context) => context switch - { - EntityContext.Gen1 => MaxSpeciesID_1, - EntityContext.Gen2 => MaxSpeciesID_2, - EntityContext.Gen3 => MaxSpeciesID_3, - EntityContext.Gen4 => MaxSpeciesID_4, - EntityContext.Gen5 => MaxSpeciesID_5, - EntityContext.Gen6 => MaxSpeciesID_6, - EntityContext.Gen7 => MaxSpeciesID_7_USUM, - EntityContext.Gen8 => MaxSpeciesID_8_R2, - - EntityContext.Gen7b => MaxSpeciesID_7b, - EntityContext.Gen8a => MaxSpeciesID_8a, - EntityContext.Gen8b => MaxSpeciesID_8b, - _ => -1, - }; - - internal static int GetMaxSpeciesOrigin(int generation) => generation switch - { - 1 => MaxSpeciesID_1, - 2 => MaxSpeciesID_2, - 3 => MaxSpeciesID_3, - 4 => MaxSpeciesID_4, - 5 => MaxSpeciesID_5, - 6 => MaxSpeciesID_6, - 7 => MaxSpeciesID_7b, - 8 => MaxSpeciesID_8a, - _ => -1, - }; - - internal static int GetDebutGeneration(int species) => species switch - { - <= MaxSpeciesID_1 => 1, - <= MaxSpeciesID_2 => 2, - <= MaxSpeciesID_3 => 3, - <= MaxSpeciesID_4 => 4, - <= MaxSpeciesID_5 => 5, - <= MaxSpeciesID_6 => 6, - <= MaxSpeciesID_7b => 7, - <= MaxSpeciesID_8a => 8, - _ => -1, - }; - - internal static int GetMaxLanguageID(int generation) => generation switch - { - 1 => (int) LanguageID.Spanish, // 1-7 except 6 - 3 => (int) LanguageID.Spanish, // 1-7 except 6 - 2 => (int) LanguageID.Korean, - 4 => (int) LanguageID.Korean, - 5 => (int) LanguageID.Korean, - 6 => (int) LanguageID.Korean, - 7 => (int) LanguageID.ChineseT, - 8 => (int) LanguageID.ChineseT, - _ => -1, - }; - - internal static int GetMaxMoveID(int generation) => generation switch - { - 1 => MaxMoveID_1, - 2 => MaxMoveID_2, - 3 => MaxMoveID_3, - 4 => MaxMoveID_4, - 5 => MaxMoveID_5, - 6 => MaxMoveID_6_AO, - 7 => MaxMoveID_7b, - 8 => MaxMoveID_8a, - _ => -1, - }; - - internal const GameVersion NONE = GameVersion.Invalid; - internal static readonly LearnVersion LearnNONE = new(-1); - - internal static bool HasVisitedB2W2(this PKM pkm, int species) => pkm.InhabitedGeneration(5, species); - internal static bool HasVisitedORAS(this PKM pkm, int species) => pkm.InhabitedGeneration(6, species) && (pkm.AO || !pkm.IsUntraded); - internal static bool HasVisitedUSUM(this PKM pkm, int species) => pkm.InhabitedGeneration(7, species) && (pkm.USUM || !pkm.IsUntraded); - - internal static bool HasVisitedSWSH(this PKM pkm, EvoCriteria[] evos) - { - if (pkm.SWSH) - return true; - if (pkm.IsUntraded) - return false; - if (pkm.BDSP && pkm.Species is (int)Species.Spinda or (int)Species.Nincada) - return false; - - var pt = PersonalTable.SWSH; - foreach (var evo in evos) - { - if (pt.IsPresentInGame(evo.Species, evo.Form)) - return true; - } - return false; - } - - internal static bool HasVisitedBDSP(this PKM pkm, EvoCriteria[] evos) - { - if (pkm.BDSP) - return true; - if (pkm.IsUntraded) - return false; - if (pkm.Species is (int)Species.Spinda or (int)Species.Nincada) - return false; - - var pt = PersonalTable.BDSP; - foreach (var evo in evos) - { - if (pt.IsPresentInGame(evo.Species, evo.Form)) - return true; - } - return false; - } - - internal static bool HasVisitedLA(this PKM pkm, EvoCriteria[] evos) - { - if (pkm.LA) - return true; - if (pkm.IsUntraded) - return false; - - var pt = PersonalTable.LA; - foreach (var evo in evos) - { - if (pt.IsPresentInGame(evo.Species, evo.Form)) - return true; - } - return false; - } - - /// - /// Checks if the moveset is restricted to only a specific version. - /// - /// Entity to check - internal static (bool IsRestricted, GameVersion Game) IsMovesetRestricted(this PKM pkm) => pkm switch - { - PB7 => (true, GameVersion.GP), - PA8 => (true, GameVersion.PLA), - PB8 => (true, GameVersion.BD), - PK8 when pkm.Version > (int)GameVersion.SH => (true, GameVersion.SH), // Permit past generation moves. - - IBattleVersion { BattleVersion: not 0 } bv => (true, (GameVersion)bv.BattleVersion), - _ when pkm.IsUntraded => (true, (GameVersion)pkm.Version), - _ => (false, GameVersion.Any), - }; - - /// - /// Checks if the relearn moves should be wiped. - /// - /// Already checked for generations < 8. - /// Entity to check - internal static bool IsOriginalMovesetDeleted(this PKM pkm) - { - if (pkm is PA8 {LA: false} or PB8 {BDSP: false}) - return true; - if (pkm.IsNative) - { - if (pkm is PK8 {LA: true} or PK8 {BDSP: true}) - return true; - return false; - } - - if (pkm is IBattleVersion { BattleVersion: not 0 }) - return true; - - return false; - } - - /// - /// Indicates if PP Ups are available for use. - /// - /// Entity to check - public static bool IsPPUpAvailable(PKM pkm) - { - return pkm is not PA8; - } - - public static int GetMaxLengthOT(int generation, LanguageID language) => language switch - { - LanguageID.ChineseS or LanguageID.ChineseT => 6, - LanguageID.Japanese or LanguageID.Korean => generation >= 6 ? 6 : 5, - _ => generation >= 6 ? 12 : 7, - }; - - public static int GetMaxLengthNickname(int generation, LanguageID language) => language switch - { - LanguageID.ChineseS or LanguageID.ChineseT => 6, - LanguageID.Japanese or LanguageID.Korean => generation >= 6 ? 6 : 5, - _ => generation >= 6 ? 12 : 10, - }; - - public static bool GetIsFixedIVSequenceValidSkipRand(ReadOnlySpan IVs, PKM pkm, int max = 31) - { - for (int i = 0; i < 6; i++) - { - if ((uint) IVs[i] > max) // random - continue; - if (IVs[i] != pkm.GetIV(i)) - return false; - } - return true; - } - - public static bool GetIsFixedIVSequenceValidNoRand(ReadOnlySpan IVs, PKM pkm) - { - for (int i = 0; i < 6; i++) - { - if (IVs[i] != pkm.GetIV(i)) - return false; - } - return true; - } - - public static bool IsMetAsEgg(PKM pkm) => pkm switch - { - PA8 or PK8 => pkm.Egg_Location is not 0 || (pkm.BDSP && pkm.Egg_Day is not 0), - PB8 pb8 => pb8.Egg_Location is not Locations.Default8bNone, - _ => pkm.Egg_Location is not 0, - }; + if (pk.Format == 1) + return GetMaxSpeciesOrigin(1); + if (pk.Format == 2 || pk.VC) + return GetMaxSpeciesOrigin(2); + return GetMaxSpeciesOrigin(pk.Generation); } + + internal static int GetMaxSpeciesOrigin(int generation, GameVersion version) => generation switch + { + 1 => MaxSpeciesID_1, + 2 => MaxSpeciesID_2, + 3 => MaxSpeciesID_3, + 4 => MaxSpeciesID_4, + 5 => MaxSpeciesID_5, + 6 => MaxSpeciesID_6, + 7 when GameVersion.GG.Contains(version) => MaxSpeciesID_7b, + 7 when GameVersion.USUM.Contains(version) => MaxSpeciesID_7_USUM, + 7 => MaxSpeciesID_7, + 8 when version is GameVersion.PLA => MaxSpeciesID_8a, + 8 when GameVersion.BDSP.Contains(version) => MaxSpeciesID_8b, + 8 => MaxSpeciesID_8_R2, + _ => -1, + }; + + internal static int GetMaxSpeciesOrigin(EntityContext context) => context switch + { + EntityContext.Gen1 => MaxSpeciesID_1, + EntityContext.Gen2 => MaxSpeciesID_2, + EntityContext.Gen3 => MaxSpeciesID_3, + EntityContext.Gen4 => MaxSpeciesID_4, + EntityContext.Gen5 => MaxSpeciesID_5, + EntityContext.Gen6 => MaxSpeciesID_6, + EntityContext.Gen7 => MaxSpeciesID_7_USUM, + EntityContext.Gen8 => MaxSpeciesID_8_R2, + + EntityContext.Gen7b => MaxSpeciesID_7b, + EntityContext.Gen8a => MaxSpeciesID_8a, + EntityContext.Gen8b => MaxSpeciesID_8b, + _ => -1, + }; + + internal static int GetMaxSpeciesOrigin(int generation) => generation switch + { + 1 => MaxSpeciesID_1, + 2 => MaxSpeciesID_2, + 3 => MaxSpeciesID_3, + 4 => MaxSpeciesID_4, + 5 => MaxSpeciesID_5, + 6 => MaxSpeciesID_6, + 7 => MaxSpeciesID_7b, + 8 => MaxSpeciesID_8a, + _ => -1, + }; + + internal static int GetDebutGeneration(int species) => species switch + { + <= MaxSpeciesID_1 => 1, + <= MaxSpeciesID_2 => 2, + <= MaxSpeciesID_3 => 3, + <= MaxSpeciesID_4 => 4, + <= MaxSpeciesID_5 => 5, + <= MaxSpeciesID_6 => 6, + <= MaxSpeciesID_7b => 7, + <= MaxSpeciesID_8a => 8, + _ => -1, + }; + + internal static int GetMaxLanguageID(int generation) => generation switch + { + 1 => (int) LanguageID.Spanish, // 1-7 except 6 + 3 => (int) LanguageID.Spanish, // 1-7 except 6 + 2 => (int) LanguageID.Korean, + 4 => (int) LanguageID.Korean, + 5 => (int) LanguageID.Korean, + 6 => (int) LanguageID.Korean, + 7 => (int) LanguageID.ChineseT, + 8 => (int) LanguageID.ChineseT, + _ => -1, + }; + + internal static int GetMaxMoveID(int generation) => generation switch + { + 1 => MaxMoveID_1, + 2 => MaxMoveID_2, + 3 => MaxMoveID_3, + 4 => MaxMoveID_4, + 5 => MaxMoveID_5, + 6 => MaxMoveID_6_AO, + 7 => MaxMoveID_7b, + 8 => MaxMoveID_8a, + _ => -1, + }; + + internal const GameVersion NONE = GameVersion.Invalid; + internal static readonly LearnVersion LearnNONE = new(-1); + + internal static bool HasVisitedB2W2(this PKM pk, int species) => pk.InhabitedGeneration(5, species); + internal static bool HasVisitedORAS(this PKM pk, int species) => pk.InhabitedGeneration(6, species) && (pk.AO || !pk.IsUntraded); + internal static bool HasVisitedUSUM(this PKM pk, int species) => pk.InhabitedGeneration(7, species) && (pk.USUM || !pk.IsUntraded); + + internal static bool HasVisitedSWSH(this PKM pk, EvoCriteria[] evos) + { + if (pk.SWSH) + return true; + if (pk.IsUntraded) + return false; + if (pk.BDSP && pk.Species is (int)Species.Spinda or (int)Species.Nincada) + return false; + + var pt = PersonalTable.SWSH; + foreach (var evo in evos) + { + if (pt.IsPresentInGame(evo.Species, evo.Form)) + return true; + } + return false; + } + + internal static bool HasVisitedBDSP(this PKM pk, EvoCriteria[] evos) + { + if (pk.BDSP) + return true; + if (pk.IsUntraded) + return false; + if (pk.Species is (int)Species.Spinda or (int)Species.Nincada) + return false; + + var pt = PersonalTable.BDSP; + foreach (var evo in evos) + { + if (pt.IsPresentInGame(evo.Species, evo.Form)) + return true; + } + return false; + } + + internal static bool HasVisitedLA(this PKM pk, EvoCriteria[] evos) + { + if (pk.LA) + return true; + if (pk.IsUntraded) + return false; + + var pt = PersonalTable.LA; + foreach (var evo in evos) + { + if (pt.IsPresentInGame(evo.Species, evo.Form)) + return true; + } + return false; + } + + /// + /// Checks if the moveset is restricted to only a specific version. + /// + /// Entity to check + internal static (bool IsRestricted, GameVersion Game) IsMovesetRestricted(this PKM pk) => pk switch + { + PB7 => (true, GameVersion.GP), + PA8 => (true, GameVersion.PLA), + PB8 => (true, GameVersion.BD), + PK8 when pk.Version > (int)GameVersion.SH => (true, GameVersion.SH), // Permit past generation moves. + + IBattleVersion { BattleVersion: not 0 } bv => (true, (GameVersion)bv.BattleVersion), + _ when pk.IsUntraded => (true, (GameVersion)pk.Version), + _ => (false, GameVersion.Any), + }; + + /// + /// Checks if the relearn moves should be wiped. + /// + /// Already checked for generations < 8. + /// Entity to check + internal static bool IsOriginalMovesetDeleted(this PKM pk) + { + if (pk is PA8 {LA: false} or PB8 {BDSP: false}) + return true; + if (pk.IsNative) + { + if (pk is PK8 {LA: true} or PK8 {BDSP: true}) + return true; + return false; + } + + if (pk is IBattleVersion { BattleVersion: not 0 }) + return true; + + return false; + } + + /// + /// Indicates if PP Ups are available for use. + /// + /// Entity to check + public static bool IsPPUpAvailable(PKM pk) + { + return pk is not PA8; + } + + public static int GetMaxLengthOT(int generation, LanguageID language) => language switch + { + LanguageID.ChineseS or LanguageID.ChineseT => 6, + LanguageID.Japanese or LanguageID.Korean => generation >= 6 ? 6 : 5, + _ => generation >= 6 ? 12 : 7, + }; + + public static int GetMaxLengthNickname(int generation, LanguageID language) => language switch + { + LanguageID.ChineseS or LanguageID.ChineseT => 6, + LanguageID.Japanese or LanguageID.Korean => generation >= 6 ? 6 : 5, + _ => generation >= 6 ? 12 : 10, + }; + + public static bool GetIsFixedIVSequenceValidSkipRand(ReadOnlySpan IVs, PKM pk, int max = 31) + { + for (int i = 0; i < 6; i++) + { + if ((uint) IVs[i] > max) // random + continue; + if (IVs[i] != pk.GetIV(i)) + return false; + } + return true; + } + + public static bool GetIsFixedIVSequenceValidNoRand(ReadOnlySpan IVs, PKM pk) + { + for (int i = 0; i < 6; i++) + { + if (IVs[i] != pk.GetIV(i)) + return false; + } + return true; + } + + public static bool IsMetAsEgg(PKM pk) => pk switch + { + PA8 or PK8 => pk.Egg_Location is not 0 || (pk.BDSP && pk.Egg_Day is not 0), + PB8 pb8 => pb8.Egg_Location is not Locations.Default8bNone, + _ => pk.Egg_Location is not 0, + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/EncounterEvent.cs b/PKHeX.Core/Legality/Encounters/Data/EncounterEvent.cs index 59c2dfb08..f4e1a29e0 100644 --- a/PKHeX.Core/Legality/Encounters/Data/EncounterEvent.cs +++ b/PKHeX.Core/Legality/Encounters/Data/EncounterEvent.cs @@ -5,137 +5,136 @@ using static PKHeX.Core.EncountersWC3; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class EncounterEvent { - public static class EncounterEvent + /// Event Database for Generation 3 + public static IReadOnlyList MGDB_G3 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 4 + public static IReadOnlyList MGDB_G4 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 5 + public static IReadOnlyList MGDB_G5 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 6 + public static IReadOnlyList MGDB_G6 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 7 + public static IReadOnlyList MGDB_G7 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 7 + public static IReadOnlyList MGDB_G7GG { get; private set; } = Array.Empty(); + + /// Event Database for Generation 8 + public static IReadOnlyList MGDB_G8 { get; private set; } = Array.Empty(); + + /// Event Database for Generation 8 + public static IReadOnlyList MGDB_G8A { get; private set; } = Array.Empty(); + + /// Event Database for Generation 8 + public static IReadOnlyList MGDB_G8B { get; private set; } = Array.Empty(); + + /// Indicates if the databases are initialized. + public static bool Initialized => MGDB_G3.Count != 0; + + private static PCD[] GetPCDDB(ReadOnlySpan bin) => Get(bin, PCD.Size, d => new PCD(d)); + private static PGF[] GetPGFDB(ReadOnlySpan bin) => Get(bin, PGF.Size, d => new PGF(d)); + + private static WC6[] GetWC6DB(ReadOnlySpan wc6bin, ReadOnlySpan wc6full) => WC6Full.GetArray(wc6full, wc6bin); + private static WC7[] GetWC7DB(ReadOnlySpan wc7bin, ReadOnlySpan wc7full) => WC7Full.GetArray(wc7full, wc7bin); + + private static WB7[] GetWB7DB(ReadOnlySpan bin) => Get(bin, WB7.SizeFull, d => new WB7(d)); + private static WC8[] GetWC8DB(ReadOnlySpan bin) => Get(bin, WC8.Size, d => new WC8(d)); + private static WB8[] GetWB8DB(ReadOnlySpan bin) => Get(bin, WB8.Size, d => new WB8(d)); + private static WA8[] GetWA8DB(ReadOnlySpan bin) => Get(bin, WA8.Size, d => new WA8(d)); + + private static T[] Get(ReadOnlySpan bin, int size, Func ctor) { - /// Event Database for Generation 3 - public static IReadOnlyList MGDB_G3 { get; private set; } = Array.Empty(); - - /// Event Database for Generation 4 - public static IReadOnlyList MGDB_G4 { get; private set; } = Array.Empty(); - - /// Event Database for Generation 5 - public static IReadOnlyList MGDB_G5 { get; private set; } = Array.Empty(); - - /// Event Database for Generation 6 - public static IReadOnlyList MGDB_G6 { get; private set; } = Array.Empty(); - - /// Event Database for Generation 7 - public static IReadOnlyList MGDB_G7 { get; private set; } = Array.Empty(); - - /// Event Database for Generation 7 - public static IReadOnlyList MGDB_G7GG { get; private set; } = Array.Empty(); - - /// Event Database for Generation 8 - public static IReadOnlyList MGDB_G8 { get; private set; } = Array.Empty(); - - /// Event Database for Generation 8 - public static IReadOnlyList MGDB_G8A { get; private set; } = Array.Empty(); - - /// Event Database for Generation 8 - public static IReadOnlyList MGDB_G8B { get; private set; } = Array.Empty(); - - /// Indicates if the databases are initialized. - public static bool Initialized => MGDB_G3.Count != 0; - - private static PCD[] GetPCDDB(ReadOnlySpan bin) => Get(bin, PCD.Size, d => new PCD(d)); - private static PGF[] GetPGFDB(ReadOnlySpan bin) => Get(bin, PGF.Size, d => new PGF(d)); - - private static WC6[] GetWC6DB(ReadOnlySpan wc6bin, ReadOnlySpan wc6full) => WC6Full.GetArray(wc6full, wc6bin); - private static WC7[] GetWC7DB(ReadOnlySpan wc7bin, ReadOnlySpan wc7full) => WC7Full.GetArray(wc7full, wc7bin); - - private static WB7[] GetWB7DB(ReadOnlySpan bin) => Get(bin, WB7.SizeFull, d => new WB7(d)); - private static WC8[] GetWC8DB(ReadOnlySpan bin) => Get(bin, WC8.Size, d => new WC8(d)); - private static WB8[] GetWB8DB(ReadOnlySpan bin) => Get(bin, WB8.Size, d => new WB8(d)); - private static WA8[] GetWA8DB(ReadOnlySpan bin) => Get(bin, WA8.Size, d => new WA8(d)); - - private static T[] Get(ReadOnlySpan bin, int size, Func ctor) + var result = new T[bin.Length / size]; + System.Diagnostics.Debug.Assert(result.Length * size == bin.Length); + for (int i = 0; i < result.Length; i++) { - var result = new T[bin.Length / size]; - System.Diagnostics.Debug.Assert(result.Length * size == bin.Length); - for (int i = 0; i < result.Length; i++) + var offset = i * size; + var slice = bin.Slice(offset, size).ToArray(); + result[i] = ctor(slice); + } + return result; + } + + public static void RefreshMGDB(params string[] paths) + { + ICollection g4 = GetPCDDB(Util.GetBinaryResource("wc4.pkl")); + ICollection g5 = GetPGFDB(Util.GetBinaryResource("pgf.pkl")); + ICollection g6 = GetWC6DB(Util.GetBinaryResource("wc6.pkl"), Util.GetBinaryResource("wc6full.pkl")); + ICollection g7 = GetWC7DB(Util.GetBinaryResource("wc7.pkl"), Util.GetBinaryResource("wc7full.pkl")); + ICollection b7 = GetWB7DB(Util.GetBinaryResource("wb7full.pkl")); + ICollection g8 = GetWC8DB(Util.GetBinaryResource("wc8.pkl")); + ICollection b8 = GetWB8DB(Util.GetBinaryResource("wb8.pkl")); + ICollection a8 = GetWA8DB(Util.GetBinaryResource("wa8.pkl")); + + foreach (var gift in paths.Where(Directory.Exists).SelectMany(MysteryUtil.GetGiftsFromFolder)) + { + static void AddOrExpand(ref ICollection arr, T obj) { - var offset = i * size; - var slice = bin.Slice(offset, size).ToArray(); - result[i] = ctor(slice); + if (arr is HashSet h) + h.Add(obj); + else + arr = new HashSet(arr) {obj}; } + switch (gift) + { + case PCD pcd: AddOrExpand(ref g4, pcd); continue; + case PGF pgf: AddOrExpand(ref g5, pgf); continue; + case WC6 wc6: AddOrExpand(ref g6, wc6); continue; + case WC7 wc7: AddOrExpand(ref g7, wc7); continue; + case WB7 wb7: AddOrExpand(ref b7, wb7); continue; + case WC8 wc8: AddOrExpand(ref g8, wc8); continue; + case WB8 wb8: AddOrExpand(ref b8, wb8); continue; + case WA8 wa8: AddOrExpand(ref a8, wa8); continue; + } + } + + static T[] SetArray(ICollection arr) + { + if (arr is T[] x) + return x; + + // rather than use Linq to build an array, just do it the quick way directly. + var result = new T[arr.Count]; + ((HashSet)arr).CopyTo(result, 0); return result; } - public static void RefreshMGDB(params string[] paths) + MGDB_G3 = Encounter_WC3; // hardcoded + MGDB_G4 = SetArray(g4); + MGDB_G5 = SetArray(g5); + MGDB_G6 = SetArray(g6); + MGDB_G7 = SetArray(g7); + MGDB_G7GG = SetArray(b7); + MGDB_G8 = SetArray(g8); + MGDB_G8A = SetArray(a8); + MGDB_G8B = SetArray(b8); + } + + public static IEnumerable GetAllEvents(bool sorted = true) + { + var regular = new IReadOnlyList[] { - ICollection g4 = GetPCDDB(Util.GetBinaryResource("wc4.pkl")); - ICollection g5 = GetPGFDB(Util.GetBinaryResource("pgf.pkl")); - ICollection g6 = GetWC6DB(Util.GetBinaryResource("wc6.pkl"), Util.GetBinaryResource("wc6full.pkl")); - ICollection g7 = GetWC7DB(Util.GetBinaryResource("wc7.pkl"), Util.GetBinaryResource("wc7full.pkl")); - ICollection b7 = GetWB7DB(Util.GetBinaryResource("wb7full.pkl")); - ICollection g8 = GetWC8DB(Util.GetBinaryResource("wc8.pkl")); - ICollection b8 = GetWB8DB(Util.GetBinaryResource("wb8.pkl")); - ICollection a8 = GetWA8DB(Util.GetBinaryResource("wa8.pkl")); - - foreach (var gift in paths.Where(Directory.Exists).SelectMany(MysteryUtil.GetGiftsFromFolder)) - { - static void AddOrExpand(ref ICollection arr, T obj) - { - if (arr is HashSet h) - h.Add(obj); - else - arr = new HashSet(arr) {obj}; - } - switch (gift) - { - case PCD pcd: AddOrExpand(ref g4, pcd); continue; - case PGF pgf: AddOrExpand(ref g5, pgf); continue; - case WC6 wc6: AddOrExpand(ref g6, wc6); continue; - case WC7 wc7: AddOrExpand(ref g7, wc7); continue; - case WB7 wb7: AddOrExpand(ref b7, wb7); continue; - case WC8 wc8: AddOrExpand(ref g8, wc8); continue; - case WB8 wb8: AddOrExpand(ref b8, wb8); continue; - case WA8 wa8: AddOrExpand(ref a8, wa8); continue; - } - } - - static T[] SetArray(ICollection arr) - { - if (arr is T[] x) - return x; - - // rather than use Linq to build an array, just do it the quick way directly. - var result = new T[arr.Count]; - ((HashSet)arr).CopyTo(result, 0); - return result; - } - - MGDB_G3 = Encounter_WC3; // hardcoded - MGDB_G4 = SetArray(g4); - MGDB_G5 = SetArray(g5); - MGDB_G6 = SetArray(g6); - MGDB_G7 = SetArray(g7); - MGDB_G7GG = SetArray(b7); - MGDB_G8 = SetArray(g8); - MGDB_G8A = SetArray(a8); - MGDB_G8B = SetArray(b8); - } - - public static IEnumerable GetAllEvents(bool sorted = true) - { - var regular = new IReadOnlyList[] - { - MGDB_G4, - MGDB_G5, - MGDB_G6, - MGDB_G7, - MGDB_G7GG, - MGDB_G8, - MGDB_G8A, - MGDB_G8B, - }.SelectMany(z => z); - regular = regular.Where(mg => !mg.IsItem && mg.IsEntity && mg.Species > 0); - var result = MGDB_G3.Concat(regular); - if (sorted) - result = result.OrderBy(mg => mg.Species); - return result; - } + MGDB_G4, + MGDB_G5, + MGDB_G6, + MGDB_G7, + MGDB_G7GG, + MGDB_G8, + MGDB_G8A, + MGDB_G8B, + }.SelectMany(z => z); + regular = regular.Where(mg => !mg.IsItem && mg.IsEntity && mg.Species > 0); + var result = MGDB_G3.Concat(regular); + if (sorted) + result = result.OrderBy(mg => mg.Species); + return result; } } diff --git a/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs b/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs index a46edd518..772405286 100644 --- a/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs +++ b/PKHeX.Core/Legality/Encounters/Data/EncounterUtil.cs @@ -1,104 +1,103 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Miscellaneous setup utility for legality checking data sources. +/// +internal static class EncounterUtil { + internal static BinLinkerAccessor Get(string resource, string ident) => BinLinkerAccessor.Get(Util.GetBinaryResource($"encounter_{resource}.pkl"), ident); + /// - /// Miscellaneous setup utility for legality checking data sources. + /// Gets the relevant objects that appear in the relevant game. /// - internal static class EncounterUtil + /// Table of valid encounters that appear for the game pairing + /// Game to filter for + /// Array of encounter objects that can be encountered in the input game + internal static T[] GetEncounters(T[] source, GameVersion game) where T : EncounterStatic { - internal static BinLinkerAccessor Get(string resource, string ident) => BinLinkerAccessor.Get(Util.GetBinaryResource($"encounter_{resource}.pkl"), ident); + return Array.FindAll(source, s => s.Version.Contains(game)); + } - /// - /// Gets the relevant objects that appear in the relevant game. - /// - /// Table of valid encounters that appear for the game pairing - /// Game to filter for - /// Array of encounter objects that can be encountered in the input game - internal static T[] GetEncounters(T[] source, GameVersion game) where T : EncounterStatic + internal static T? GetMinByLevel(EvoCriteria[] chain, IEnumerable possible) where T : class, IEncounterTemplate + { + // MinBy grading: prefer species-form match, select lowest min level encounter. + // Minimum allocation :) + T? result = null; + int min = int.MaxValue; + + foreach (var enc in possible) { - return Array.FindAll(source, s => s.Version.Contains(game)); - } - - internal static T? GetMinByLevel(EvoCriteria[] chain, IEnumerable possible) where T : class, IEncounterTemplate - { - // MinBy grading: prefer species-form match, select lowest min level encounter. - // Minimum allocation :) - T? result = null; - int min = int.MaxValue; - - foreach (var enc in possible) + int m = int.MaxValue; + foreach (var evo in chain) { - int m = int.MaxValue; - foreach (var evo in chain) - { - bool specDiff = enc.Species != evo.Species || enc.Form != evo.Form; - var val = (Convert.ToInt32(specDiff) << 16) | enc.LevelMin; - if (val < m) - m = val; - } - - if (m >= min) - continue; - min = m; - result = enc; + bool specDiff = enc.Species != evo.Species || enc.Form != evo.Form; + var val = (Convert.ToInt32(specDiff) << 16) | enc.LevelMin; + if (val < m) + m = val; } - return result; + if (m >= min) + continue; + min = m; + result = enc; } - /// - /// Loads the language string lists into the objects. - /// - /// Encounter template type - /// Trade templates - /// Localization strings, grouped by language. - /// - /// The first half of strings in the language resource array are - /// The second half of strings in the language resource strings are - /// - internal static void MarkEncounterTradeStrings(T[] table, ReadOnlySpan strings) where T : EncounterTrade - { - uint languageCount = (uint)strings[1].Length / 2; - for (uint i = 0; i < languageCount; i++) - { - var t = table[i]; - t.Nicknames = GetNamesForLanguage(strings, i); - t.TrainerNames = GetNamesForLanguage(strings, languageCount + i); - } - } + return result; + } - /// - /// Loads the language string lists into the objects. - /// - /// Encounter template type - /// Trade templates - /// Localization strings, grouped by language. - internal static void MarkEncounterTradeNicknames(T[] table, ReadOnlySpan strings) where T : EncounterTrade + /// + /// Loads the language string lists into the objects. + /// + /// Encounter template type + /// Trade templates + /// Localization strings, grouped by language. + /// + /// The first half of strings in the language resource array are + /// The second half of strings in the language resource strings are + /// + internal static void MarkEncounterTradeStrings(T[] table, ReadOnlySpan strings) where T : EncounterTrade + { + uint languageCount = (uint)strings[1].Length / 2; + for (uint i = 0; i < languageCount; i++) { - for (uint i = 0; i < table.Length; i++) - { - var t = table[i]; - t.Nicknames = GetNamesForLanguage(strings, i); - } - } - - /// - /// Grabs the localized names for individual templates for all languages from the specified of the list. - /// - /// Arrays of strings grouped by language - /// Index to grab from the language arrays - /// Row of localized strings for the template. - private static string[] GetNamesForLanguage(ReadOnlySpan names, uint index) - { - var result = new string[names.Length]; - for (int i = 0; i < result.Length; i++) - { - var arr = names[i]; - result[i] = index < arr.Length ? arr[index] : string.Empty; - } - return result; + var t = table[i]; + t.Nicknames = GetNamesForLanguage(strings, i); + t.TrainerNames = GetNamesForLanguage(strings, languageCount + i); } } + + /// + /// Loads the language string lists into the objects. + /// + /// Encounter template type + /// Trade templates + /// Localization strings, grouped by language. + internal static void MarkEncounterTradeNicknames(T[] table, ReadOnlySpan strings) where T : EncounterTrade + { + for (uint i = 0; i < table.Length; i++) + { + var t = table[i]; + t.Nicknames = GetNamesForLanguage(strings, i); + } + } + + /// + /// Grabs the localized names for individual templates for all languages from the specified of the list. + /// + /// Arrays of strings grouped by language + /// Index to grab from the language arrays + /// Row of localized strings for the template. + private static string[] GetNamesForLanguage(ReadOnlySpan names, uint index) + { + var result = new string[names.Length]; + for (int i = 0; i < result.Length; i++) + { + var arr = names[i]; + result[i] = index < arr.Length ? arr[index] : string.Empty; + } + return result; + } } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters1.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters1.cs index 57ef2d938..1ac2f3884 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters1.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters1.cs @@ -2,162 +2,161 @@ using static PKHeX.Core.EncounterGBLanguage; using static PKHeX.Core.EncounterUtil; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 1 Encounters +/// +internal static class Encounters1 { - /// - /// Generation 1 Encounters - /// - internal static class Encounters1 + internal static readonly EncounterArea1[] SlotsRD = EncounterArea1.GetAreas(Get("red", "g1"), RD); + internal static readonly EncounterArea1[] SlotsGN = EncounterArea1.GetAreas(Get("blue", "g1"), GN); + internal static readonly EncounterArea1[] SlotsYW = EncounterArea1.GetAreas(Get("yellow", "g1"), YW); + internal static readonly EncounterArea1[] SlotsBU = EncounterArea1.GetAreas(Get("blue_jp", "g1"), BU); + internal static readonly EncounterArea1[] SlotsRBY = ArrayUtil.ConcatAll(SlotsRD, SlotsGN, SlotsYW); + internal static readonly EncounterArea1[] SlotsRGBY = ArrayUtil.ConcatAll(SlotsRBY, SlotsBU); + + static Encounters1() => MarkEncounterTradeNicknames(TradeGift_RBY, TradeGift_RBY_OTs); + + internal static readonly EncounterStatic1[] StaticRBY = { - internal static readonly EncounterArea1[] SlotsRD = EncounterArea1.GetAreas(Get("red", "g1"), RD); - internal static readonly EncounterArea1[] SlotsGN = EncounterArea1.GetAreas(Get("blue", "g1"), GN); - internal static readonly EncounterArea1[] SlotsYW = EncounterArea1.GetAreas(Get("yellow", "g1"), YW); - internal static readonly EncounterArea1[] SlotsBU = EncounterArea1.GetAreas(Get("blue_jp", "g1"), BU); - internal static readonly EncounterArea1[] SlotsRBY = ArrayUtil.ConcatAll(SlotsRD, SlotsGN, SlotsYW); - internal static readonly EncounterArea1[] SlotsRGBY = ArrayUtil.ConcatAll(SlotsRBY, SlotsBU); + // GameVersion is RBY for Pokemon with the same catch rate and initial moves in all games + // If there are any differences in moves or catch rate, they will be defined as different encounters (GameVersion) + new(001, 05, RBY), // Bulbasaur + new(004, 05, RBY), // Charmander + new(007, 05, RBY), // Squirtle + new(025, 05, YW), // Pikachu - static Encounters1() => MarkEncounterTradeNicknames(TradeGift_RBY, TradeGift_RBY_OTs); + // Game Corner + new(030, 17, RB), // Nidorina (Red Game Corner) + new(033, 17, RB), // Nidorino (Blue[EN] / Green[JP] Game Corner) + new(035, 08, RBY), // Clefairy (Red Game Corner) + new(036, 24, BU), // Clefable (Blue[JP] Game Corner) + new(037, 18, RBY), // Vulpix (Yellow Game Corner) + new(040, 22, RBY), // Wigglytuff (Yellow Game Corner) + new(063, 06, RB), // Abra (Blue[EN] / Green[JP] Game Corner) + new(116, 18, BU), // Horsea (Blue[JP] Game Corner) + new(123, 25, RBY), // Scyther (Red Game Corner) + new(127, 20, RB), // Pinsir (Blue[EN] / Green[JP] Game Corner) + new(127, 30, YW), // Pinsir (Yellow Game Corner) (Different initial moves) + new(137, 18, RB), // Porygon (Blue[EN] / Green[JP] Game Corner) + new(147, 18, RBY), // Dratini (Red Game Corner) + new(148, 30, BU), // Dragonair (Blue[JP] Game Corner) + new(025, 12, BU), // Pikachu (Blue[JP] Game Corner) (Different catch rate) - internal static readonly EncounterStatic1[] StaticRBY = - { - // GameVersion is RBY for Pokemon with the same catch rate and initial moves in all games - // If there are any differences in moves or catch rate, they will be defined as different encounters (GameVersion) - new(001, 05, RBY), // Bulbasaur - new(004, 05, RBY), // Charmander - new(007, 05, RBY), // Squirtle - new(025, 05, YW), // Pikachu + // Lower level less ideal matches; best match is from above. + // new(035, 12), // Clefairy (Blue[EN] / Green[JP] Game Corner) + // new(063, 09), // Abra (Red Game Corner) + // new(063, 08), // Abra (Blue[JP] Game Corner) + // new(063, 15), // Abra (Yellow Game Corner) + // new(123, 30), // Scyther (Yellow Game Corner) + // new(137, 22), // Porygon (Blue[JP] Game Corner) + // new(137, 26), // Porygon (Red Game Corner) + // new(137, 26), // Porygon (Yellow Game Corner) + // new(147, 24), // Dratini (Blue[EN] / Green[JP] Game Corner) - // Game Corner - new(030, 17, RB), // Nidorina (Red Game Corner) - new(033, 17, RB), // Nidorino (Blue[EN] / Green[JP] Game Corner) - new(035, 08, RBY), // Clefairy (Red Game Corner) - new(036, 24, BU), // Clefable (Blue[JP] Game Corner) - new(037, 18, RBY), // Vulpix (Yellow Game Corner) - new(040, 22, RBY), // Wigglytuff (Yellow Game Corner) - new(063, 06, RB), // Abra (Blue[EN] / Green[JP] Game Corner) - new(116, 18, BU), // Horsea (Blue[JP] Game Corner) - new(123, 25, RBY), // Scyther (Red Game Corner) - new(127, 20, RB), // Pinsir (Blue[EN] / Green[JP] Game Corner) - new(127, 30, YW), // Pinsir (Yellow Game Corner) (Different initial moves) - new(137, 18, RB), // Porygon (Blue[EN] / Green[JP] Game Corner) - new(147, 18, RBY), // Dratini (Red Game Corner) - new(148, 30, BU), // Dragonair (Blue[JP] Game Corner) - new(025, 12, BU), // Pikachu (Blue[JP] Game Corner) (Different catch rate) + new(129, 05, RBY), // Magikarp + new(143, 30, RBY), // Snorlax + new(106, 30, RBY), // Hitmonlee + new(107, 30, RBY), // Hitmonchan - // Lower level less ideal matches; best match is from above. - // new(035, 12), // Clefairy (Blue[EN] / Green[JP] Game Corner) - // new(063, 09), // Abra (Red Game Corner) - // new(063, 08), // Abra (Blue[JP] Game Corner) - // new(063, 15), // Abra (Yellow Game Corner) - // new(123, 30), // Scyther (Yellow Game Corner) - // new(137, 22), // Porygon (Blue[JP] Game Corner) - // new(137, 26), // Porygon (Red Game Corner) - // new(137, 26), // Porygon (Yellow Game Corner) - // new(147, 24), // Dratini (Blue[EN] / Green[JP] Game Corner) + new(131, 15, RBY), // Lapras + new(138, 30, RBY), // Omanyte + new(140, 30, RBY), // Kabuto + new(142, 30, RBY), // Aerodactyl - new(129, 05, RBY), // Magikarp - new(143, 30, RBY), // Snorlax - new(106, 30, RBY), // Hitmonlee - new(107, 30, RBY), // Hitmonchan + new(144, 50, RBY), // Articuno + new(145, 50, RBY), // Zapdos + new(146, 50, RBY), // Moltres - new(131, 15, RBY), // Lapras - new(138, 30, RBY), // Omanyte - new(140, 30, RBY), // Kabuto - new(142, 30, RBY), // Aerodactyl + new(150, 70, RBY), // Mewtwo - new(144, 50, RBY), // Articuno - new(145, 50, RBY), // Zapdos - new(146, 50, RBY), // Moltres + new(133, 25, RB) {Moves = new [] {(int)Move.Tackle, (int)Move.SandAttack}}, // Eevee + new(133, 25, YW) {Moves = new [] {(int)Move.TailWhip, (int)Move.SandAttack, (int)Move.Growl, (int)Move.QuickAttack}}, // Eevee (Different initial moves) - new(150, 70, RBY), // Mewtwo + new(100, 40, RBY), // Voltorb (Power Plant) + new(101, 43, RBY), // Electrode (Power Plant) - new(133, 25, RB) {Moves = new [] {(int)Move.Tackle, (int)Move.SandAttack}}, // Eevee - new(133, 25, YW) {Moves = new [] {(int)Move.TailWhip, (int)Move.SandAttack, (int)Move.Growl, (int)Move.QuickAttack}}, // Eevee (Different initial moves) + // Yellow Only -- duplicate encounters with a higher level + // new(001, 10, YW), // Bulbasaur (Cerulean City) + // new(004, 10, YW), // Charmander (Route 24) + // new(007, 10, YW), // Squirtle (Vermillion City) + }; - new(100, 40, RBY), // Voltorb (Power Plant) - new(101, 43, RBY), // Electrode (Power Plant) + internal static readonly EncounterTrade1[] TradeGift_RBY = + { + new(122, RB, 06, 05), // Mr. Mime - Abra + new(032, RB, 02 ), // Nidoran♂ - Nidoran♀ + new(030, RB, 16 ), // Nidorina - Nidorino + new(108, RB, 15 ), // Lickitung - Slowbro + new(124, RB, 15, 10), // Jynx - Poliwhirl + new(083, RB, 02 ), // Farfetch’d - Spearow + new(101, RB, 03 ), // Electrode - Raichu + new(114, RB, 13, 05), // Tangela - Venonat + new(086, RB, 28, 05), // Seel - Ponyta - // Yellow Only -- duplicate encounters with a higher level - // new(001, 10, YW), // Bulbasaur (Cerulean City) - // new(004, 10, YW), // Charmander (Route 24) - // new(007, 10, YW), // Squirtle (Vermillion City) - }; + new(122, YW, 08, 06), // Mr. Mime - Clefairy + new(067, YW, 16, 05) { EvolveOnTrade = true }, // Machoke - Cubone + new(051, YW, 15, 05), // Dugtrio - Lickitung + new(047, YW, 13, 05), // Parasect - Tangel + new(112, YW, 15, 10), // Rhydon - Golduck + new(087, YW, 15, 05), // Dewgong - Growlithe + new(089, YW, 25, 05), // Muk - Kangaskhan - internal static readonly EncounterTrade1[] TradeGift_RBY = - { - new(122, RB, 06, 05), // Mr. Mime - Abra - new(032, RB, 02 ), // Nidoran♂ - Nidoran♀ - new(030, RB, 16 ), // Nidorina - Nidorino - new(108, RB, 15 ), // Lickitung - Slowbro - new(124, RB, 15, 10), // Jynx - Poliwhirl - new(083, RB, 02 ), // Farfetch’d - Spearow - new(101, RB, 03 ), // Electrode - Raichu - new(114, RB, 13, 05), // Tangela - Venonat - new(086, RB, 28, 05), // Seel - Ponyta + new(122, BU, 03 ), // Mr. Mime - Jigglypuff + new(029, BU, 02 ), // Nidoran♀ - Nidoran♂ + new(060, BU, 02 ), // Poliwag - Rattata + new(115, BU, 15, 10), // Kangaskhan - Rhydon + new(128, BU, 28, 18), // Tauros - Persian + new(093, BU, 28, 14) { EvolveOnTrade = true }, // Haunter - Machop->Machoke + new(083, BU, 02 ), // Farfetch’d - Wild Pidgey + new(075, BU, 16, 15) { EvolveOnTrade = true }, // Graveler - Abra->Kadabra + new(079, BU, 22, 05), // Slowpoke - Seel + new(098, BU, 15, 05), // Krabby - Growlithe + }; - new(122, YW, 08, 06), // Mr. Mime - Clefairy - new(067, YW, 16, 05) { EvolveOnTrade = true }, // Machoke - Cubone - new(051, YW, 15, 05), // Dugtrio - Lickitung - new(047, YW, 13, 05), // Parasect - Tangel - new(112, YW, 15, 10), // Rhydon - Golduck - new(087, YW, 15, 05), // Dewgong - Growlithe - new(089, YW, 25, 05), // Muk - Kangaskhan + private const string tradeRBY = "traderby"; + private static readonly string[][] TradeGift_RBY_OTs = Util.GetLanguageStrings7(tradeRBY); - new(122, BU, 03 ), // Mr. Mime - Jigglypuff - new(029, BU, 02 ), // Nidoran♀ - Nidoran♂ - new(060, BU, 02 ), // Poliwag - Rattata - new(115, BU, 15, 10), // Kangaskhan - Rhydon - new(128, BU, 28, 18), // Tauros - Persian - new(093, BU, 28, 14) { EvolveOnTrade = true }, // Haunter - Machop->Machoke - new(083, BU, 02 ), // Farfetch’d - Wild Pidgey - new(075, BU, 16, 15) { EvolveOnTrade = true }, // Graveler - Abra->Kadabra - new(079, BU, 22, 05), // Slowpoke - Seel - new(098, BU, 15, 05), // Krabby - Growlithe - }; + private static readonly int[] Flawless15 = { 15, 15, 15, 15, 15, 15 }; + private static readonly int[] Yoshira = { 5, 10, 1, 12, 5, 5 }; + private static readonly string[] YoshiOT = { "YOSHIRA", "YOSHIRB", "YOSHIBA", "YOSHIBB" }; + private static readonly string[] TourOT = { "LINKE", "LINKW", "LUIGE", "LUIGW", "LUIGIC", "YOSHIC" }; + private static readonly string[] StadiumOT_Int = { "STADIUM", "STADE", "STADIO", "ESTADIO" }; + private const string StadiumOT_JPN = "スタジアム"; - private const string tradeRBY = "traderby"; - private static readonly string[][] TradeGift_RBY_OTs = Util.GetLanguageStrings7(tradeRBY); + internal static readonly EncounterStatic1E[] StaticEventsVC = + { + // Event Mew + new(151, 5, RBY) { IVs = Flawless15, TID = 22796, OT_Name = "GF", Language = International }, + new(151, 5, RBY) { IVs = Flawless15, TID = 22796, OT_Name = "ゲーフリ" }, + }; - private static readonly int[] Flawless15 = { 15, 15, 15, 15, 15, 15 }; - private static readonly int[] Yoshira = { 5, 10, 1, 12, 5, 5 }; - private static readonly string[] YoshiOT = { "YOSHIRA", "YOSHIRB", "YOSHIBA", "YOSHIBB" }; - private static readonly string[] TourOT = { "LINKE", "LINKW", "LUIGE", "LUIGW", "LUIGIC", "YOSHIC" }; - private static readonly string[] StadiumOT_Int = { "STADIUM", "STADE", "STADIO", "ESTADIO" }; - private const string StadiumOT_JPN = "スタジアム"; + internal static readonly EncounterStatic1E[] StaticEventsGB = + { + // Stadium 1 (International) + new(001, 05, Stadium) {Moves = new[] {033, 045}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Bulbasaur + new(004, 05, Stadium) {Moves = new[] {010, 043}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Charmander + new(007, 05, Stadium) {Moves = new[] {033, 045}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Squirtle + new(106, 20, Stadium) {Moves = new[] {024, 096}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Hitmonlee + new(107, 20, Stadium) {Moves = new[] {004, 097}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Hitmonchan + new(133, 25, Stadium) {Moves = new[] {033, 039}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Eevee + new(138, 20, Stadium) {Moves = new[] {055, 110}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Omanyte + new(140, 20, Stadium) {Moves = new[] {010, 106}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Kabuto + new(054, 15, Stadium) {Moves = new[] {133, 010}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Psyduck (Amnesia) - internal static readonly EncounterStatic1E[] StaticEventsVC = - { - // Event Mew - new(151, 5, RBY) { IVs = Flawless15, TID = 22796, OT_Name = "GF", Language = International }, - new(151, 5, RBY) { IVs = Flawless15, TID = 22796, OT_Name = "ゲーフリ" }, - }; + // Stadium 2 (Japan) + new(001, 05, Stadium) {Moves = new[] {033, 045}, TID = 1999, OT_Name = StadiumOT_JPN}, // Bulbasaur + new(004, 05, Stadium) {Moves = new[] {010, 043}, TID = 1999, OT_Name = StadiumOT_JPN}, // Charmander + new(007, 05, Stadium) {Moves = new[] {033, 045}, TID = 1999, OT_Name = StadiumOT_JPN}, // Squirtle + new(106, 20, Stadium) {Moves = new[] {024, 096}, TID = 1999, OT_Name = StadiumOT_JPN}, // Hitmonlee + new(107, 20, Stadium) {Moves = new[] {004, 097}, TID = 1999, OT_Name = StadiumOT_JPN}, // Hitmonchan + new(133, 25, Stadium) {Moves = new[] {033, 039}, TID = 1999, OT_Name = StadiumOT_JPN}, // Eevee + new(138, 20, Stadium) {Moves = new[] {055, 110}, TID = 1999, OT_Name = StadiumOT_JPN}, // Omanyte + new(140, 20, Stadium) {Moves = new[] {010, 106}, TID = 1999, OT_Name = StadiumOT_JPN}, // Kabuto + new(054, 15, Stadium) {Moves = new[] {133, 010}, TID = 1999, OT_Name = StadiumOT_JPN}, // Psyduck (Amnesia) - internal static readonly EncounterStatic1E[] StaticEventsGB = - { - // Stadium 1 (International) - new(001, 05, Stadium) {Moves = new[] {033, 045}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Bulbasaur - new(004, 05, Stadium) {Moves = new[] {010, 043}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Charmander - new(007, 05, Stadium) {Moves = new[] {033, 045}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Squirtle - new(106, 20, Stadium) {Moves = new[] {024, 096}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Hitmonlee - new(107, 20, Stadium) {Moves = new[] {004, 097}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Hitmonchan - new(133, 25, Stadium) {Moves = new[] {033, 039}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Eevee - new(138, 20, Stadium) {Moves = new[] {055, 110}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Omanyte - new(140, 20, Stadium) {Moves = new[] {010, 106}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Kabuto - new(054, 15, Stadium) {Moves = new[] {133, 010}, TID = 2000, OT_Names = StadiumOT_Int, Language = International}, // Psyduck (Amnesia) - - // Stadium 2 (Japan) - new(001, 05, Stadium) {Moves = new[] {033, 045}, TID = 1999, OT_Name = StadiumOT_JPN}, // Bulbasaur - new(004, 05, Stadium) {Moves = new[] {010, 043}, TID = 1999, OT_Name = StadiumOT_JPN}, // Charmander - new(007, 05, Stadium) {Moves = new[] {033, 045}, TID = 1999, OT_Name = StadiumOT_JPN}, // Squirtle - new(106, 20, Stadium) {Moves = new[] {024, 096}, TID = 1999, OT_Name = StadiumOT_JPN}, // Hitmonlee - new(107, 20, Stadium) {Moves = new[] {004, 097}, TID = 1999, OT_Name = StadiumOT_JPN}, // Hitmonchan - new(133, 25, Stadium) {Moves = new[] {033, 039}, TID = 1999, OT_Name = StadiumOT_JPN}, // Eevee - new(138, 20, Stadium) {Moves = new[] {055, 110}, TID = 1999, OT_Name = StadiumOT_JPN}, // Omanyte - new(140, 20, Stadium) {Moves = new[] {010, 106}, TID = 1999, OT_Name = StadiumOT_JPN}, // Kabuto - new(054, 15, Stadium) {Moves = new[] {133, 010}, TID = 1999, OT_Name = StadiumOT_JPN}, // Psyduck (Amnesia) - - new(151, 5, RB) {IVs = Yoshira, OT_Names = YoshiOT, Language = International }, // Yoshira Mew Events - new(151, 5, RB) {IVs = Yoshira, OT_Names = TourOT, Language = International }, // Pokémon 2000 Stadium Tour Mew - }; - } + new(151, 5, RB) {IVs = Yoshira, OT_Names = YoshiOT, Language = International }, // Yoshira Mew Events + new(151, 5, RB) {IVs = Yoshira, OT_Names = TourOT, Language = International }, // Pokémon 2000 Stadium Tour Mew + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters2.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters2.cs index bd1996502..c443947e3 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters2.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters2.cs @@ -3,383 +3,382 @@ using static PKHeX.Core.GameVersion; using static PKHeX.Core.EncounterGBLanguage; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 2 Encounters +/// +internal static class Encounters2 { - /// - /// Generation 2 Encounters - /// - internal static class Encounters2 + internal static readonly EncounterArea2[] SlotsGD = EncounterArea2.GetAreas(Get("gold", "g2"), GD); + internal static readonly EncounterArea2[] SlotsSV = EncounterArea2.GetAreas(Get("silver", "g2"), SI); + internal static readonly EncounterArea2[] SlotsC = EncounterArea2.GetAreas(Get("crystal", "g2"), C); + + internal static readonly EncounterArea2[] SlotsGS = ArrayUtil.ConcatAll(SlotsGD, SlotsSV); + internal static readonly EncounterArea2[] SlotsGSC = ArrayUtil.ConcatAll(SlotsGS, SlotsC); + + static Encounters2() => MarkEncounterTradeStrings(TradeGift_GSC, TradeGift_GSC_OTs); + + private static readonly EncounterStatic2[] Encounter_GSC_Common = { - internal static readonly EncounterArea2[] SlotsGD = EncounterArea2.GetAreas(Get("gold", "g2"), GD); - internal static readonly EncounterArea2[] SlotsSV = EncounterArea2.GetAreas(Get("silver", "g2"), SI); - internal static readonly EncounterArea2[] SlotsC = EncounterArea2.GetAreas(Get("crystal", "g2"), C); - - internal static readonly EncounterArea2[] SlotsGS = ArrayUtil.ConcatAll(SlotsGD, SlotsSV); - internal static readonly EncounterArea2[] SlotsGSC = ArrayUtil.ConcatAll(SlotsGS, SlotsC); - - static Encounters2() => MarkEncounterTradeStrings(TradeGift_GSC, TradeGift_GSC_OTs); - - private static readonly EncounterStatic2[] Encounter_GSC_Common = - { - new(152, 05, GSC) { Location = 001 }, // Chikorita @ New Bark Town - new(155, 05, GSC) { Location = 001 }, // Cyndaquil @ New Bark Town - new(158, 05, GSC) { Location = 001 }, // Totodile @ New Bark Town - - new(175, 05, GSC) { EggLocation = 256 }, // Togepi - new(131, 20, GSC) { Location = 010 }, // Lapras @ Union Cave - new(133, 20, GSC) { Location = 016 }, // Eevee @ Goldenrod City - - new(185, 20, GSC) { Location = 020 }, // Sudowoodo @ Route 36 - new(236, 10, GSC) { Location = 035 }, // Tyrogue @ Mt. Mortar - - new(130, 30, GSC) { Location = 038, Shiny = Shiny.Always, Gender = 0, IVs = new []{0, 14, 10, 10, 10, 10} }, // Gyarados @ Lake of Rage (forcing shiny IVs result in always Male) - new(074, 21, GSC) { Location = 036 }, // Geodude @ Rocket Hideout (Mahogany Town) - new(109, 21, GSC) { Location = 036 }, // Koffing @ Rocket Hideout (Mahogany Town) - new(100, 23, GSC) { Location = 036 }, // Voltorb @ Rocket Hideout (Mahogany Town) - new(101, 23, GSC) { Location = 036 }, // Electrode @ Rocket Hideout (Mahogany Town) - new(143, 50, GSC) { Location = 061 }, // Snorlax @ Vermillion City - - new(211, 05, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Old Rod) - new(211, 20, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Good Rod) - new(211, 40, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Super Rod) - }; - - private static readonly EncounterStatic2[] Encounter_GS_Exclusive = - { - new(245, 40, GS), // Suicune - - new(249, 70, GD), // Lugia @ Whirl Islands - new(249, 40, SI), // Lugia @ Whirl Islands - - new(250, 40, GD), // Ho-Oh @ Tin Tower - new(250, 70, SI), // Ho-Oh @ Tin Tower - - new(137, 15, GS), // Porygon @ Celadon Game Corner - new(133, 15, GS), // Eevee @ Celadon Game Corner - new(122, 15, GS), // Mr. Mime @ Celadon Game Corner - - new(063, 10, GS), // Abra @ Goldenrod City (Game Corner) - new(147, 10, GS), // Dratini @ Goldenrod City (Game Corner) - new(023, 10, GS), // Ekans @ Goldenrod City (Game Corner) (Gold) - new(027, 10, GS), // Sandshrew @ Goldenrod City (Game Corner) (Silver) - - new(223, 05, GS), // Remoraid Swarm @ Route 44 (Old Rod) - new(223, 20, GS), // Remoraid Swarm @ Route 44 (Good Rod) - new(223, 40, GS), // Remoraid Swarm @ Route 44 (Super Rod) - }; - - private static readonly EncounterStatic2[] Encounter_C_Exclusive = - { - new(245, 40, C) { Location = 023 }, // Suicune @ Tin Tower - - new EncounterStatic2Odd(172) {Moves = new[] {(int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch}}, // Pichu - new EncounterStatic2Odd(173) {Moves = new[] {(int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch}}, // Cleffa - new EncounterStatic2Odd(174) {Moves = new[] {(int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch}}, // Igglybuff - new EncounterStatic2Odd(236) {Moves = new[] {(int)Move.Tackle, (int)Move.DizzyPunch}}, // Tyrogue - new EncounterStatic2Odd(238) {Moves = new[] {(int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch}}, // Smoochum - new EncounterStatic2Odd(239) {Moves = new[] {(int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch}}, // Elekid - new EncounterStatic2Odd(240) {Moves = new[] {(int)Move.Ember, (int)Move.DizzyPunch}}, // Magby - - new(147, 15, C) { Location = 042, Moves = new[] {(int)Move.ExtremeSpeed, (int)Move.Wrap, (int)Move.ThunderWave, (int)Move.Twister} }, // Dratini ExtremeSpeed - - new(249, 60, C) { Location = 031 }, // Lugia @ Whirl Islands - new(250, 60, C) { Location = 023 }, // Ho-Oh @ Tin Tower - - new(137, 15, C) { Location = 071 }, // Porygon @ Celadon Game Corner - new(025, 25, C) { Location = 071 }, // Pikachu @ Celadon Game Corner - new(246, 40, C) { Location = 071 }, // Larvitar @ Celadon Game Corner - - new(063, 05, C) { Location = 016 }, // Abra @ Goldenrod City (Game Corner) - new(104, 15, C) { Location = 016 }, // Cubone @ Goldenrod City (Game Corner) - new(202, 15, C) { Location = 016 }, // Wobbuffet @ Goldenrod City (Game Corner) - }; - - private static readonly EncounterStatic2[] Encounter_GSC_Roam = - { - new EncounterStatic2Roam(243, 40, GSC), // Raikou - new EncounterStatic2Roam(244, 40, GSC), // Entei - new EncounterStatic2Roam(245, 40, GS), // Suicune - }; - - private static readonly EncounterStatic2[] Encounter_GS = ArrayUtil.ConcatAll(Encounter_GSC_Common, Encounter_GS_Exclusive, Encounter_GSC_Roam); - private static readonly EncounterStatic2[] Encounter_C = ArrayUtil.ConcatAll(Encounter_GSC_Common, Encounter_C_Exclusive, Encounter_GSC_Roam.AsSpan(0, 2)); - private static readonly EncounterStatic2[] Encounter_GSC = ArrayUtil.ConcatAll(Encounter_GSC_Common, Encounter_GS_Exclusive, Encounter_C_Exclusive, Encounter_GSC_Roam); - - internal static readonly EncounterTrade2[] TradeGift_GSC = - { - new(095, 03, 48926) { Gender = 0, IVs = new[] {08, 09, 06, 06, 06, 06} }, // Onix @ Violet City for Bellsprout [wild] - new(066, 05, 37460) { Gender = 1, IVs = new[] {12, 03, 07, 06, 06, 06} }, // Machop @ Goldenrod City for Drowzee [wild 9, hatched egg 5] - new(100, 05, 29189) { Gender = 2, IVs = new[] {08, 09, 08, 08, 08, 08} }, // Voltorb @ Olivine City for Krabby [egg] - new(112, 10, 00283) { Gender = 1, IVs = new[] {12, 07, 07, 06, 06, 06} }, // Rhydon @ Blackthorn City for Dragonair [wild] - new(142, 05, 26491) { Gender = 0, IVs = new[] {08, 09, 06, 06, 06, 06}, OTGender = 1}, // Aerodactyl @ Route 14 for Chansey [egg] - new(078, 14, 15616) { Gender = 0, IVs = new[] {08, 09, 06, 06, 06, 06} }, // Rapidash @ Pewter City for Gloom [wild] - - new(085, 10, 00283) { Gender = 1, IVs = new[] {12, 07, 07, 06, 06, 06}, OTGender = 1}, // Dodrio @ Blackthorn City for Dragonair [wild] - new(178, 15, 15616) { Gender = 0, IVs = new[] {08, 09, 06, 08, 06, 06} }, // Xatu @ Pewter City for Haunter [wild] - new(082, 05, 50082) { Gender = 2, IVs = new[] {08, 09, 06, 06, 06, 06} }, // Magneton @ Power Plant for Dugtrio [traded for Lickitung] - - new(021, 10, 01001) { Moves = new[] {64,45,43} }, // Spearow @ Goldenrod City for free - new(213, 15, 00518), // Shuckle @ Cianwood City for free - }; - - private const string tradeGSC = "tradegsc"; - private static readonly string[][] TradeGift_GSC_OTs = Util.GetLanguageStrings8(tradeGSC); - - internal static readonly EncounterStatic2[] StaticGSC = Encounter_GSC; - internal static readonly EncounterStatic2[] StaticGS = Encounter_GS; - internal static readonly EncounterStatic2[] StaticC = Encounter_C; - - internal static readonly EncounterStatic2E[] StaticEventsVC = - { - new(251, 30, C) { Location = 014, Language = EncounterGBLanguage.Any }, // Celebi @ Ilex Forest (VC) - }; - - private static readonly int[] Farfetchd = {226, 14, 97, 163}; - private static readonly int[] Gligar = {89, 68, 17}; - private static readonly string[] PCNYx = {"PCNYa", "PCNYb", "PCNYc", "PCNYd"}; - - internal static readonly EncounterStatic2E[] StaticEventsGB = - { - // Stadium 2 Baton Pass Farfetch'd - new(083, 05, C) {Moves = Farfetchd, Location = 127, TID = 2000, OT_Name = "スタジアム"}, - new(083, 05, C) {Moves = Farfetchd, Location = 127, TID = 2000, OT_Name = "Stadium", Language = International}, - new(083, 05, C) {Moves = Farfetchd, Location = 127, TID = 2001, OT_Names = new[]{"Stade", "Stadion", "Stadio", "Estadio"}, Language = International}, - - // Stadium 2 Earthquake Gligar - new(207, 05, C) {Moves = Gligar, Location = 127, TID = 2000, OT_Name = "スタジアム"}, - new(207, 05, C) {Moves = Gligar, Location = 127, TID = 2000, OT_Name = "Stadium", Language = International}, - new(207, 05, C) {Moves = Gligar, Location = 127, TID = 2001, OT_Names = new[]{"Stade", "Stadion", "Stadio", "Estadio"}, Language = International}, - - //New York Pokémon Center Events - - // Legendary Beasts (November 22 to 29, 2001) - new(243, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Raikou - new(244, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Entei - new(245, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Suicune - - // Legendary Birds (November 30 to December 6, 2001) - new(144, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Articuno - new(145, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Zapdos - new(146, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Moltres - - // Christmas Week (December 21 to 27, 2001) - new(225, 05, GS) {OT_Names = PCNYx, Moves = new[] {006}, EggLocation = 256, EggCycles = 10, Language = International}, // Pay Day Delibird - new(251, 05, C) {OT_Names = PCNYx, Location = 127, Language = International}, // Celebi - - // The Initial Three Set (December 28, 2001 to January 31, 2002) - new(001, 05, GS) {OT_Names = PCNYx, Moves = new[] {246}, EggLocation = 256, EggCycles = 10, Language = International}, // Bulbasaur Ancientpower - new(004, 05, GS) {OT_Names = PCNYx, Moves = new[] {242}, EggLocation = 256, EggCycles = 10, Language = International}, // Charmander Crunch - new(007, 05, GS) {OT_Names = PCNYx, Moves = new[] {192}, EggLocation = 256, EggCycles = 10, Language = International}, // Squirtle Zap Cannon - new(152, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Chikorita Petal Dance - new(155, 05, GS) {OT_Names = PCNYx, Moves = new[] {038}, EggLocation = 256, EggCycles = 10, Language = International}, // Cyndaquil Double-Edge - new(158, 05, GS) {OT_Names = PCNYx, Moves = new[] {066}, EggLocation = 256, EggCycles = 10, Language = International}, // Totodile Submission - - // Valentine Week (February 1 to 14, 2002) - new(029, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (F) Lovely Kiss - new(029, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (F) Sweet Kiss - new(032, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (M) Lovely Kiss - new(032, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (M) Sweet Kiss - new(069, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Bellsprout Lovely Kiss - new(069, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Bellsprout Sweet Kiss - - // Swarm Week (February 22 to March 14, 2002) - new(183, 05, GS) {OT_Names = PCNYx, Moves = new[] {056}, EggLocation = 256, EggCycles = 10, Language = International}, // Marill Hydro Pump - new(193, 05, GS) {OT_Names = PCNYx, Moves = new[] {211}, EggLocation = 256, EggCycles = 10, Language = International}, // Yanma Steel Wing - new(206, 05, GS) {OT_Names = PCNYx, Moves = new[] {032}, EggLocation = 256, EggCycles = 10, Language = International}, // Dunsparce Horn Drill - new(209, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Snubbull Lovely Kiss - new(211, 05, GS) {OT_Names = PCNYx, Moves = new[] {038}, EggLocation = 256, EggCycles = 10, Language = International}, // Qwilfish Double-Edge - new(223, 05, GS) {OT_Names = PCNYx, Moves = new[] {133}, EggLocation = 256, EggCycles = 10, Language = International}, // Remoraid Amnesia - - // Shiny RBY Starters (March 15 to 21, 2002) - new(003, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Venusaur - new(006, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Charizard - new(009, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Blastoise - - // Babies Week (March 22 to April 11, 2002) - new(172, 05, GS) {OT_Names = PCNYx, Moves = new[] {047}, EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Sing - new(173, 05, GS) {OT_Names = PCNYx, Moves = new[] {129}, EggLocation = 256, EggCycles = 10, Language = International}, // Cleffa Swift - new(174, 05, GS) {OT_Names = PCNYx, Moves = new[] {102}, EggLocation = 256, EggCycles = 10, Language = International}, // Igglybuff Mimic - new(238, 05, GS) {OT_Names = PCNYx, Moves = new[] {118}, EggLocation = 256, EggCycles = 10, Language = International}, // Smoochum Metronome - new(239, 05, GS) {OT_Names = PCNYx, Moves = new[] {228}, EggLocation = 256, EggCycles = 10, Language = International}, // Elekid Pursuit - new(240, 05, GS) {OT_Names = PCNYx, Moves = new[] {185}, EggLocation = 256, EggCycles = 10, Language = International}, // Magby Faint Attack - - // Spring Into Spring (April 12 to May 4, 2002) - new(054, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Psyduck Petal Dance - new(152, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Chikorita Petal Dance - new(172, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Petal Dance - new(173, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Cleffa Petal Dance - new(174, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Igglybuff Petal Dance - new(238, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Smoochum Petal Dance - - // Baby Weeks (May 5 to June 7, 2002) - new(194, 05, GS) {Moves = new[] {187}, EggLocation = 256, EggCycles = 10, Language = International}, // Wooper Belly Drum - - // Tropical Promotion to Summer Festival 1 (June 8 to 21, 2002) - new(060, 05, GS) {OT_Names = PCNYx, Moves = new[] {074}, EggLocation = 256, EggCycles = 10, Language = International}, // Poliwag Growth - new(116, 05, GS) {OT_Names = PCNYx, Moves = new[] {114}, EggLocation = 256, EggCycles = 10, Language = International}, // Horsea Haze - new(118, 05, GS) {OT_Names = PCNYx, Moves = new[] {014}, EggLocation = 256, EggCycles = 10, Language = International}, // Goldeen Swords Dance - new(129, 05, GS) {OT_Names = PCNYx, Moves = new[] {179}, EggLocation = 256, EggCycles = 10, Language = International}, // Magikarp Reversal - new(183, 05, GS) {OT_Names = PCNYx, Moves = new[] {146}, EggLocation = 256, EggCycles = 10, Language = International}, // Marill Dizzy Punch - - // Tropical Promotion to Summer Festival 2 (July 12 to August 8, 2002) - new(054, 05, GS) {OT_Names = PCNYx, Moves = new[] {161}, EggLocation = 256, EggCycles = 10, Language = International}, // Psyduck Tri Attack - new(072, 05, GS) {OT_Names = PCNYx, Moves = new[] {109}, EggLocation = 256, EggCycles = 10, Language = International}, // Tentacool Confuse Ray - new(131, 05, GS) {OT_Names = PCNYx, Moves = new[] {044}, EggLocation = 256, EggCycles = 10, Language = International}, // Lapras Bite - new(170, 05, GS) {OT_Names = PCNYx, Moves = new[] {113}, EggLocation = 256, EggCycles = 10, Language = International}, // Chinchou Light Screen - new(223, 05, GS) {OT_Names = PCNYx, Moves = new[] {054}, EggLocation = 256, EggCycles = 10, Language = International}, // Remoraid Mist - new(226, 05, GS) {OT_Names = PCNYx, Moves = new[] {016}, EggLocation = 256, EggCycles = 10, Language = International}, // Mantine Gust - - // Safari Week (August 9 to 29, 2002) - new(029, 05, GS) {OT_Names = PCNYx, Moves = new[] {236}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (F) Moonlight - new(032, 05, GS) {OT_Names = PCNYx, Moves = new[] {234}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (M) Morning Sun - new(113, 05, GS) {OT_Names = PCNYx, Moves = new[] {230}, EggLocation = 256, EggCycles = 10, Language = International}, // Chansey Sweet Scent - new(115, 05, GS) {OT_Names = PCNYx, Moves = new[] {185}, EggLocation = 256, EggCycles = 10, Language = International}, // Kangaskhan Faint Attack - new(128, 05, GS) {OT_Names = PCNYx, Moves = new[] {098}, EggLocation = 256, EggCycles = 10, Language = International}, // Tauros Quick Attack - new(147, 05, GS) {OT_Names = PCNYx, Moves = new[] {056}, EggLocation = 256, EggCycles = 10, Language = International}, // Dratini Hydro Pump - - // Sky Week (August 30 to September 26, 2002) - new(021, 05, GS) {OT_Names = PCNYx, Moves = new[] {049}, EggLocation = 256, EggCycles = 10, Language = International}, // Spearow SonicBoom - new(083, 05, GS) {OT_Names = PCNYx, Moves = new[] {210}, EggLocation = 256, EggCycles = 10, Language = International}, // Farfetch'd Fury Cutter - new(084, 05, GS) {OT_Names = PCNYx, Moves = new[] {067}, EggLocation = 256, EggCycles = 10, Language = International}, // Doduo Low Kick - new(177, 05, GS) {OT_Names = PCNYx, Moves = new[] {219}, EggLocation = 256, EggCycles = 10, Language = International}, // Natu Safeguard - new(198, 05, GS) {OT_Names = PCNYx, Moves = new[] {251}, EggLocation = 256, EggCycles = 10, Language = International}, // Murkrow Beat Up - new(227, 05, GS) {OT_Names = PCNYx, Moves = new[] {210}, EggLocation = 256, EggCycles = 10, Language = International}, // Skarmory Fury Cutter - - // The Kanto Initial Three Pokémon (September 27 to October 3, 2002) - new(150, 05, C) {OT_Names = PCNYx, CurrentLevel = 70, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Mewtwo - - // Power Plant Pokémon (October 4 to October 10, 2002) - new(172, 05, GS) {OT_Names = PCNYx, Moves = new[] {146}, EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Dizzy Punch - new(081, 05, GS) {OT_Names = PCNYx, Moves = new[] {097}, EggLocation = 256, EggCycles = 10, Language = International}, // Magnemite Agility - new(239, 05, GS) {OT_Names = PCNYx, Moves = new[] {146}, EggLocation = 256, EggCycles = 10, Language = International}, // Elekid Dizzy Punch - new(100, 05, GS) {OT_Names = PCNYx, Moves = new[] {097}, EggLocation = 256, EggCycles = 10, Language = International}, // Voltorb Agility - - // Scary Face Pokémon (October 25 to October 31, 2002) - new(173, 05, GS) {OT_Names = PCNYx, Moves = new[] {184}, EggLocation = 256, EggCycles = 10, Language = International}, // Cleffa Scary Face - new(174, 05, GS) {OT_Names = PCNYx, Moves = new[] {184}, EggLocation = 256, EggCycles = 10, Language = International}, // Igglybuff Scary Face - new(183, 05, GS) {OT_Names = PCNYx, Moves = new[] {184}, EggLocation = 256, EggCycles = 10, Language = International}, // Marill Scary Face - new(172, 05, GS) {OT_Names = PCNYx, Moves = new[] {184}, EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Scary Face - new(194, 05, GS) {OT_Names = PCNYx, Moves = new[] {184}, EggLocation = 256, EggCycles = 10, Language = International}, // Wooper Scary Face - - // Silver Cave (November 1 to November 7, 2002) - new(114, 05, GS) {OT_Names = PCNYx, Moves = new[] {235}, EggLocation = 256, EggCycles = 10, Language = International}, // Tangela Synthesis - new(077, 05, GS) {OT_Names = PCNYx, Moves = new[] {067}, EggLocation = 256, EggCycles = 10, Language = International}, // Ponyta Low Kick - new(200, 05, GS) {OT_Names = PCNYx, Moves = new[] {095}, EggLocation = 256, EggCycles = 10, Language = International}, // Misdreavus Hypnosis - new(246, 05, GS) {OT_Names = PCNYx, Moves = new[] {099}, EggLocation = 256, EggCycles = 10, Language = International}, // Larvitar Rage - - // Union Cave Pokémon (November 8 to 14, 2002) - new(120, 05, GS) {OT_Names = PCNYx, Moves = new[] {239}, EggLocation = 256, EggCycles = 10, Language = International}, // Staryu Twister - new(098, 05, GS) {OT_Names = PCNYx, Moves = new[] {232}, EggLocation = 256, EggCycles = 10, Language = International}, // Krabby Metal Claw - new(095, 05, GS) {OT_Names = PCNYx, Moves = new[] {159}, EggLocation = 256, EggCycles = 10, Language = International}, // Onix Sharpen - new(131, 05, GS) {OT_Names = PCNYx, Moves = new[] {248}, EggLocation = 256, EggCycles = 10, Language = International}, // Lapras Future Sight - - // Johto Legendary (November 15 to 21, 2002) - new(250, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Ho-Oh - new(249, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Lugia - - // Celebi Present SP (November 22 to 28, 2002) - new(151, 05, C) {OT_Names = PCNYx, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Mew - - // Psychic Type Pokémon (November 29 to December 5, 2002) - new(063, 05, GS) {OT_Names = PCNYx, Moves = new[] {193}, EggLocation = 256, EggCycles = 10, Language = International}, // Abra Foresight - new(096, 05, GS) {OT_Names = PCNYx, Moves = new[] {133}, EggLocation = 256, EggCycles = 10, Language = International}, // Drowzee Amnesia - new(102, 05, GS) {OT_Names = PCNYx, Moves = new[] {230}, EggLocation = 256, EggCycles = 10, Language = International}, // Exeggcute Sweet Scent - new(122, 05, GS) {OT_Names = PCNYx, Moves = new[] {170}, EggLocation = 256, EggCycles = 10, Language = International}, // Mr. Mime Mind Reader - - // The Johto Initial Three Pokémon (December 6 to 12, 2002) - new(154, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Meganium - new(157, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Typhlosion - new(160, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Feraligatr - - // Rock Tunnel Pokémon (December 13 to December 19, 2002) - new(074, 05, GS) {OT_Names = PCNYx, Moves = new[] {229}, EggLocation = 256, EggCycles = 10, Language = International}, // Geodude Rapid Spin - new(041, 05, GS) {OT_Names = PCNYx, Moves = new[] {175}, EggLocation = 256, EggCycles = 10, Language = International}, // Zubat Flail - new(066, 05, GS) {OT_Names = PCNYx, Moves = new[] {037}, EggLocation = 256, EggCycles = 10, Language = International}, // Machop Thrash - new(104, 05, GS) {OT_Names = PCNYx, Moves = new[] {031}, EggLocation = 256, EggCycles = 10, Language = International}, // Cubone Fury Attack - - // Ice Type Pokémon (December 20 to 26, 2002) - new(225, 05, GS) {OT_Names = PCNYx, Moves = new[] {191}, EggLocation = 256, EggCycles = 10, Language = International}, // Delibird Spikes - new(086, 05, GS) {OT_Names = PCNYx, Moves = new[] {175}, EggLocation = 256, EggCycles = 10, Language = International}, // Seel Flail - new(220, 05, GS) {OT_Names = PCNYx, Moves = new[] {018}, EggLocation = 256, EggCycles = 10, Language = International}, // Swinub Whirlwind - - // Pokémon that Appear at Night only (December 27, 2002 to January 2, 2003) - new(163, 05, GS) {OT_Names = PCNYx, Moves = new[] {101}, EggLocation = 256, EggCycles = 10, Language = International}, // Hoothoot Night Shade - new(215, 05, GS) {OT_Names = PCNYx, Moves = new[] {236}, EggLocation = 256, EggCycles = 10, Language = International}, // Sneasel Moonlight - - // Grass Type ( January 3 to 9, 2003) - new(191, 05, GS) {OT_Names = PCNYx, Moves = new[] {150}, EggLocation = 256, EggCycles = 10, Language = International}, // Sunkern Splash - new(046, 05, GS) {OT_Names = PCNYx, Moves = new[] {235}, EggLocation = 256, EggCycles = 10, Language = International}, // Paras Synthesis - new(187, 05, GS) {OT_Names = PCNYx, Moves = new[] {097}, EggLocation = 256, EggCycles = 10, Language = International}, // Hoppip Agility - new(043, 05, GS) {OT_Names = PCNYx, Moves = new[] {073}, EggLocation = 256, EggCycles = 10, Language = International}, // Oddish Leech Seed - - // Normal Pokémon (January 10 to 16, 2003) - new(161, 05, GS) {OT_Names = PCNYx, Moves = new[] {146}, EggLocation = 256, EggCycles = 10, Language = International}, // Sentret Dizzy Punch - new(234, 05, GS) {OT_Names = PCNYx, Moves = new[] {219}, EggLocation = 256, EggCycles = 10, Language = International}, // Stantler Safeguard - new(241, 05, GS) {OT_Names = PCNYx, Moves = new[] {025}, EggLocation = 256, EggCycles = 10, Language = International}, // Miltank Mega Kick - new(190, 05, GS) {OT_Names = PCNYx, Moves = new[] {102}, EggLocation = 256, EggCycles = 10, Language = International}, // Aipom Mimic - new(108, 05, GS) {OT_Names = PCNYx, Moves = new[] {003}, EggLocation = 256, EggCycles = 10, Language = International}, // Lickitung DoubleSlap - new(143, 05, GS) {OT_Names = PCNYx, Moves = new[] {150}, EggLocation = 256, EggCycles = 10, Language = International}, // Snorlax Splash - - // Mt. Mortar (January 24 to 30, 2003) - new(066, 05, GS) {OT_Names = PCNYx, Moves = new[] {206}, EggLocation = 256, EggCycles = 10, Language = International}, // Machop False Swipe - new(129, 05, GS) {OT_Names = PCNYx, Moves = new[] {145}, EggLocation = 256, EggCycles = 10, Language = International}, // Magikarp Bubble - new(236, 05, GS) {OT_Names = PCNYx, Moves = new[] {099}, EggLocation = 256, EggCycles = 10, Language = International}, // Tyrogue Rage - - // Dark Cave Pokémon (January 31 to February 6, 2003) - new(206, 05, GS) {OT_Names = PCNYx, Moves = new[] {031}, EggLocation = 256, EggCycles = 10, Language = International}, // Dunsparce Fury Attack - new(202, 05, GS) {OT_Names = PCNYx, Moves = new[] {102}, EggLocation = 256, EggCycles = 10, Language = International}, // Wobbuffet Mimic - new(231, 05, GS) {OT_Names = PCNYx, Moves = new[] {071}, EggLocation = 256, EggCycles = 10, Language = International}, // Phanpy Absorb - new(216, 05, GS) {OT_Names = PCNYx, Moves = new[] {230}, EggLocation = 256, EggCycles = 10, Language = International}, // Teddiursa Sweet Scent - - // Valentine's Day Special (February 7 to 13, 2003) - new(060, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Poliwag Sweet Kiss - new(060, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Poliwag Lovely Kiss - new(143, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Snorlax Sweet Kiss - new(143, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Snorlax Lovely Kiss - - // Rare Pokémon (February 21 to 27, 2003) - new(140, 05, GS) {OT_Names = PCNYx, Moves = new[] {088}, EggLocation = 256, EggCycles = 10, Language = International}, // Kabuto Rock Throw - new(138, 05, GS) {OT_Names = PCNYx, Moves = new[] {088}, EggLocation = 256, EggCycles = 10, Language = International}, // Omanyte Rock Throw - new(142, 05, GS) {OT_Names = PCNYx, Moves = new[] {088}, EggLocation = 256, EggCycles = 10, Language = International}, // Aerodactyl Rock Throw - new(137, 05, GS) {OT_Names = PCNYx, Moves = new[] {112}, EggLocation = 256, EggCycles = 10, Language = International}, // Porygon Barrier - new(133, 05, GS) {OT_Names = PCNYx, Moves = new[] {074}, EggLocation = 256, EggCycles = 10, Language = International}, // Eevee Growth - new(185, 05, GS) {OT_Names = PCNYx, Moves = new[] {164}, EggLocation = 256, EggCycles = 10, Language = International}, // Sudowoodo Substitute - - // Bug Type Pokémon (February 28 to March 6, 2003) - new(123, 05, GS) {OT_Names = PCNYx, Moves = new[] {049}, EggLocation = 256, EggCycles = 10, Language = International}, // Scyther SonicBoom - new(214, 05, GS) {OT_Names = PCNYx, Moves = new[] {069}, EggLocation = 256, EggCycles = 10, Language = International}, // Heracross Seismic Toss - new(127, 05, GS) {OT_Names = PCNYx, Moves = new[] {088}, EggLocation = 256, EggCycles = 10, Language = International}, // Pinsir Rock Throw - new(165, 05, GS) {OT_Names = PCNYx, Moves = new[] {112}, EggLocation = 256, EggCycles = 10, Language = International}, // Ledyba Barrier - new(167, 05, GS) {OT_Names = PCNYx, Moves = new[] {074}, EggLocation = 256, EggCycles = 10, Language = International}, // Spinarak Growth - new(193, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Yanma Sweet Kiss - new(204, 05, GS) {OT_Names = PCNYx, Moves = new[] {164}, EggLocation = 256, EggCycles = 10, Language = International}, // Pineco Substitute - - // Japanese Only (all below) - new(251, 30, GSC) {Location = 014}, // Celebi @ Ilex Forest (GBC) - - // Gen2 Events - // Egg Cycles Subject to Change. OTs for Eggs are unknown. - // Pokémon Center Mystery Egg #1 (December 15, 2001 to January 14, 2002) - new(152, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Chikorita Petal Dance - new(172, 05, GSC) {Moves = new[] {047}, EggLocation = 256, EggCycles = 10}, // Pichu Sing - new(173, 05, GSC) {Moves = new[] {129}, EggLocation = 256, EggCycles = 10}, // Cleffa Swift - new(194, 05, GSC) {Moves = new[] {187}, EggLocation = 256, EggCycles = 10}, // Wooper Belly Drum - new(231, 05, GSC) {Moves = new[] {227}, EggLocation = 256, EggCycles = 10}, // Phanpy Encore - new(238, 05, GSC) {Moves = new[] {118}, EggLocation = 256, EggCycles = 10}, // Smoochum Metronome - - // Pokémon Center Mystery Egg #2 (March 16 to April 7, 2002) - new(054, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Psyduck Petal Dance - new(152, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Chikorita Petal Dance - new(172, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Pichu Petal Dance - new(173, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Cleffa Petal Dance - new(174, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Igglybuff Petal Dance - new(238, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Smoochum Petal Dance - - // Pokémon Center Mystery Egg #3 (April 27 to May 12, 2002) - new(001, 05, GSC) {Moves = new[] {246}, EggLocation = 256, EggCycles = 10}, // Bulbasaur Ancientpower - new(004, 05, GSC) {Moves = new[] {242}, EggLocation = 256, EggCycles = 10}, // Charmander Crunch - new(158, 05, GSC) {Moves = new[] {066}, EggLocation = 256, EggCycles = 10}, // Totodile Submission - new(163, 05, GSC) {Moves = new[] {101}, EggLocation = 256, EggCycles = 10}, // Hoot-Hoot Night Shade - }; - } + new(152, 05, GSC) { Location = 001 }, // Chikorita @ New Bark Town + new(155, 05, GSC) { Location = 001 }, // Cyndaquil @ New Bark Town + new(158, 05, GSC) { Location = 001 }, // Totodile @ New Bark Town + + new(175, 05, GSC) { EggLocation = 256 }, // Togepi + new(131, 20, GSC) { Location = 010 }, // Lapras @ Union Cave + new(133, 20, GSC) { Location = 016 }, // Eevee @ Goldenrod City + + new(185, 20, GSC) { Location = 020 }, // Sudowoodo @ Route 36 + new(236, 10, GSC) { Location = 035 }, // Tyrogue @ Mt. Mortar + + new(130, 30, GSC) { Location = 038, Shiny = Shiny.Always, Gender = 0, IVs = new []{0, 14, 10, 10, 10, 10} }, // Gyarados @ Lake of Rage (forcing shiny IVs result in always Male) + new(074, 21, GSC) { Location = 036 }, // Geodude @ Rocket Hideout (Mahogany Town) + new(109, 21, GSC) { Location = 036 }, // Koffing @ Rocket Hideout (Mahogany Town) + new(100, 23, GSC) { Location = 036 }, // Voltorb @ Rocket Hideout (Mahogany Town) + new(101, 23, GSC) { Location = 036 }, // Electrode @ Rocket Hideout (Mahogany Town) + new(143, 50, GSC) { Location = 061 }, // Snorlax @ Vermillion City + + new(211, 05, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Old Rod) + new(211, 20, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Good Rod) + new(211, 40, GSC) { Location = 008 }, // Qwilfish Swarm @ Route 32 (Super Rod) + }; + + private static readonly EncounterStatic2[] Encounter_GS_Exclusive = + { + new(245, 40, GS), // Suicune + + new(249, 70, GD), // Lugia @ Whirl Islands + new(249, 40, SI), // Lugia @ Whirl Islands + + new(250, 40, GD), // Ho-Oh @ Tin Tower + new(250, 70, SI), // Ho-Oh @ Tin Tower + + new(137, 15, GS), // Porygon @ Celadon Game Corner + new(133, 15, GS), // Eevee @ Celadon Game Corner + new(122, 15, GS), // Mr. Mime @ Celadon Game Corner + + new(063, 10, GS), // Abra @ Goldenrod City (Game Corner) + new(147, 10, GS), // Dratini @ Goldenrod City (Game Corner) + new(023, 10, GS), // Ekans @ Goldenrod City (Game Corner) (Gold) + new(027, 10, GS), // Sandshrew @ Goldenrod City (Game Corner) (Silver) + + new(223, 05, GS), // Remoraid Swarm @ Route 44 (Old Rod) + new(223, 20, GS), // Remoraid Swarm @ Route 44 (Good Rod) + new(223, 40, GS), // Remoraid Swarm @ Route 44 (Super Rod) + }; + + private static readonly EncounterStatic2[] Encounter_C_Exclusive = + { + new(245, 40, C) { Location = 023 }, // Suicune @ Tin Tower + + new EncounterStatic2Odd(172) {Moves = new[] {(int)Move.ThunderShock,(int)Move.Charm, (int)Move.DizzyPunch}}, // Pichu + new EncounterStatic2Odd(173) {Moves = new[] {(int)Move.Pound, (int)Move.Charm, (int)Move.DizzyPunch}}, // Cleffa + new EncounterStatic2Odd(174) {Moves = new[] {(int)Move.Sing, (int)Move.Charm, (int)Move.DizzyPunch}}, // Igglybuff + new EncounterStatic2Odd(236) {Moves = new[] {(int)Move.Tackle, (int)Move.DizzyPunch}}, // Tyrogue + new EncounterStatic2Odd(238) {Moves = new[] {(int)Move.Pound, (int)Move.Lick, (int)Move.DizzyPunch}}, // Smoochum + new EncounterStatic2Odd(239) {Moves = new[] {(int)Move.QuickAttack, (int)Move.Leer, (int)Move.DizzyPunch}}, // Elekid + new EncounterStatic2Odd(240) {Moves = new[] {(int)Move.Ember, (int)Move.DizzyPunch}}, // Magby + + new(147, 15, C) { Location = 042, Moves = new[] {(int)Move.ExtremeSpeed, (int)Move.Wrap, (int)Move.ThunderWave, (int)Move.Twister} }, // Dratini ExtremeSpeed + + new(249, 60, C) { Location = 031 }, // Lugia @ Whirl Islands + new(250, 60, C) { Location = 023 }, // Ho-Oh @ Tin Tower + + new(137, 15, C) { Location = 071 }, // Porygon @ Celadon Game Corner + new(025, 25, C) { Location = 071 }, // Pikachu @ Celadon Game Corner + new(246, 40, C) { Location = 071 }, // Larvitar @ Celadon Game Corner + + new(063, 05, C) { Location = 016 }, // Abra @ Goldenrod City (Game Corner) + new(104, 15, C) { Location = 016 }, // Cubone @ Goldenrod City (Game Corner) + new(202, 15, C) { Location = 016 }, // Wobbuffet @ Goldenrod City (Game Corner) + }; + + private static readonly EncounterStatic2[] Encounter_GSC_Roam = + { + new EncounterStatic2Roam(243, 40, GSC), // Raikou + new EncounterStatic2Roam(244, 40, GSC), // Entei + new EncounterStatic2Roam(245, 40, GS), // Suicune + }; + + private static readonly EncounterStatic2[] Encounter_GS = ArrayUtil.ConcatAll(Encounter_GSC_Common, Encounter_GS_Exclusive, Encounter_GSC_Roam); + private static readonly EncounterStatic2[] Encounter_C = ArrayUtil.ConcatAll(Encounter_GSC_Common, Encounter_C_Exclusive, Encounter_GSC_Roam.AsSpan(0, 2)); + private static readonly EncounterStatic2[] Encounter_GSC = ArrayUtil.ConcatAll(Encounter_GSC_Common, Encounter_GS_Exclusive, Encounter_C_Exclusive, Encounter_GSC_Roam); + + internal static readonly EncounterTrade2[] TradeGift_GSC = + { + new(095, 03, 48926) { Gender = 0, IVs = new[] {08, 09, 06, 06, 06, 06} }, // Onix @ Violet City for Bellsprout [wild] + new(066, 05, 37460) { Gender = 1, IVs = new[] {12, 03, 07, 06, 06, 06} }, // Machop @ Goldenrod City for Drowzee [wild 9, hatched egg 5] + new(100, 05, 29189) { Gender = 2, IVs = new[] {08, 09, 08, 08, 08, 08} }, // Voltorb @ Olivine City for Krabby [egg] + new(112, 10, 00283) { Gender = 1, IVs = new[] {12, 07, 07, 06, 06, 06} }, // Rhydon @ Blackthorn City for Dragonair [wild] + new(142, 05, 26491) { Gender = 0, IVs = new[] {08, 09, 06, 06, 06, 06}, OTGender = 1}, // Aerodactyl @ Route 14 for Chansey [egg] + new(078, 14, 15616) { Gender = 0, IVs = new[] {08, 09, 06, 06, 06, 06} }, // Rapidash @ Pewter City for Gloom [wild] + + new(085, 10, 00283) { Gender = 1, IVs = new[] {12, 07, 07, 06, 06, 06}, OTGender = 1}, // Dodrio @ Blackthorn City for Dragonair [wild] + new(178, 15, 15616) { Gender = 0, IVs = new[] {08, 09, 06, 08, 06, 06} }, // Xatu @ Pewter City for Haunter [wild] + new(082, 05, 50082) { Gender = 2, IVs = new[] {08, 09, 06, 06, 06, 06} }, // Magneton @ Power Plant for Dugtrio [traded for Lickitung] + + new(021, 10, 01001) { Moves = new[] {64,45,43} }, // Spearow @ Goldenrod City for free + new(213, 15, 00518), // Shuckle @ Cianwood City for free + }; + + private const string tradeGSC = "tradegsc"; + private static readonly string[][] TradeGift_GSC_OTs = Util.GetLanguageStrings8(tradeGSC); + + internal static readonly EncounterStatic2[] StaticGSC = Encounter_GSC; + internal static readonly EncounterStatic2[] StaticGS = Encounter_GS; + internal static readonly EncounterStatic2[] StaticC = Encounter_C; + + internal static readonly EncounterStatic2E[] StaticEventsVC = + { + new(251, 30, C) { Location = 014, Language = EncounterGBLanguage.Any }, // Celebi @ Ilex Forest (VC) + }; + + private static readonly int[] Farfetchd = {226, 14, 97, 163}; + private static readonly int[] Gligar = {89, 68, 17}; + private static readonly string[] PCNYx = {"PCNYa", "PCNYb", "PCNYc", "PCNYd"}; + + internal static readonly EncounterStatic2E[] StaticEventsGB = + { + // Stadium 2 Baton Pass Farfetch'd + new(083, 05, C) {Moves = Farfetchd, Location = 127, TID = 2000, OT_Name = "スタジアム"}, + new(083, 05, C) {Moves = Farfetchd, Location = 127, TID = 2000, OT_Name = "Stadium", Language = International}, + new(083, 05, C) {Moves = Farfetchd, Location = 127, TID = 2001, OT_Names = new[]{"Stade", "Stadion", "Stadio", "Estadio"}, Language = International}, + + // Stadium 2 Earthquake Gligar + new(207, 05, C) {Moves = Gligar, Location = 127, TID = 2000, OT_Name = "スタジアム"}, + new(207, 05, C) {Moves = Gligar, Location = 127, TID = 2000, OT_Name = "Stadium", Language = International}, + new(207, 05, C) {Moves = Gligar, Location = 127, TID = 2001, OT_Names = new[]{"Stade", "Stadion", "Stadio", "Estadio"}, Language = International}, + + //New York Pokémon Center Events + + // Legendary Beasts (November 22 to 29, 2001) + new(243, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Raikou + new(244, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Entei + new(245, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Suicune + + // Legendary Birds (November 30 to December 6, 2001) + new(144, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Articuno + new(145, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Zapdos + new(146, 05, C) {OT_Names = PCNYx, CurrentLevel = 50, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Moltres + + // Christmas Week (December 21 to 27, 2001) + new(225, 05, GS) {OT_Names = PCNYx, Moves = new[] {006}, EggLocation = 256, EggCycles = 10, Language = International}, // Pay Day Delibird + new(251, 05, C) {OT_Names = PCNYx, Location = 127, Language = International}, // Celebi + + // The Initial Three Set (December 28, 2001 to January 31, 2002) + new(001, 05, GS) {OT_Names = PCNYx, Moves = new[] {246}, EggLocation = 256, EggCycles = 10, Language = International}, // Bulbasaur Ancientpower + new(004, 05, GS) {OT_Names = PCNYx, Moves = new[] {242}, EggLocation = 256, EggCycles = 10, Language = International}, // Charmander Crunch + new(007, 05, GS) {OT_Names = PCNYx, Moves = new[] {192}, EggLocation = 256, EggCycles = 10, Language = International}, // Squirtle Zap Cannon + new(152, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Chikorita Petal Dance + new(155, 05, GS) {OT_Names = PCNYx, Moves = new[] {038}, EggLocation = 256, EggCycles = 10, Language = International}, // Cyndaquil Double-Edge + new(158, 05, GS) {OT_Names = PCNYx, Moves = new[] {066}, EggLocation = 256, EggCycles = 10, Language = International}, // Totodile Submission + + // Valentine Week (February 1 to 14, 2002) + new(029, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (F) Lovely Kiss + new(029, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (F) Sweet Kiss + new(032, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (M) Lovely Kiss + new(032, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (M) Sweet Kiss + new(069, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Bellsprout Lovely Kiss + new(069, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Bellsprout Sweet Kiss + + // Swarm Week (February 22 to March 14, 2002) + new(183, 05, GS) {OT_Names = PCNYx, Moves = new[] {056}, EggLocation = 256, EggCycles = 10, Language = International}, // Marill Hydro Pump + new(193, 05, GS) {OT_Names = PCNYx, Moves = new[] {211}, EggLocation = 256, EggCycles = 10, Language = International}, // Yanma Steel Wing + new(206, 05, GS) {OT_Names = PCNYx, Moves = new[] {032}, EggLocation = 256, EggCycles = 10, Language = International}, // Dunsparce Horn Drill + new(209, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Snubbull Lovely Kiss + new(211, 05, GS) {OT_Names = PCNYx, Moves = new[] {038}, EggLocation = 256, EggCycles = 10, Language = International}, // Qwilfish Double-Edge + new(223, 05, GS) {OT_Names = PCNYx, Moves = new[] {133}, EggLocation = 256, EggCycles = 10, Language = International}, // Remoraid Amnesia + + // Shiny RBY Starters (March 15 to 21, 2002) + new(003, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Venusaur + new(006, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Charizard + new(009, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Blastoise + + // Babies Week (March 22 to April 11, 2002) + new(172, 05, GS) {OT_Names = PCNYx, Moves = new[] {047}, EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Sing + new(173, 05, GS) {OT_Names = PCNYx, Moves = new[] {129}, EggLocation = 256, EggCycles = 10, Language = International}, // Cleffa Swift + new(174, 05, GS) {OT_Names = PCNYx, Moves = new[] {102}, EggLocation = 256, EggCycles = 10, Language = International}, // Igglybuff Mimic + new(238, 05, GS) {OT_Names = PCNYx, Moves = new[] {118}, EggLocation = 256, EggCycles = 10, Language = International}, // Smoochum Metronome + new(239, 05, GS) {OT_Names = PCNYx, Moves = new[] {228}, EggLocation = 256, EggCycles = 10, Language = International}, // Elekid Pursuit + new(240, 05, GS) {OT_Names = PCNYx, Moves = new[] {185}, EggLocation = 256, EggCycles = 10, Language = International}, // Magby Faint Attack + + // Spring Into Spring (April 12 to May 4, 2002) + new(054, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Psyduck Petal Dance + new(152, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Chikorita Petal Dance + new(172, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Petal Dance + new(173, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Cleffa Petal Dance + new(174, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Igglybuff Petal Dance + new(238, 05, GS) {OT_Names = PCNYx, Moves = new[] {080}, EggLocation = 256, EggCycles = 10, Language = International}, // Smoochum Petal Dance + + // Baby Weeks (May 5 to June 7, 2002) + new(194, 05, GS) {Moves = new[] {187}, EggLocation = 256, EggCycles = 10, Language = International}, // Wooper Belly Drum + + // Tropical Promotion to Summer Festival 1 (June 8 to 21, 2002) + new(060, 05, GS) {OT_Names = PCNYx, Moves = new[] {074}, EggLocation = 256, EggCycles = 10, Language = International}, // Poliwag Growth + new(116, 05, GS) {OT_Names = PCNYx, Moves = new[] {114}, EggLocation = 256, EggCycles = 10, Language = International}, // Horsea Haze + new(118, 05, GS) {OT_Names = PCNYx, Moves = new[] {014}, EggLocation = 256, EggCycles = 10, Language = International}, // Goldeen Swords Dance + new(129, 05, GS) {OT_Names = PCNYx, Moves = new[] {179}, EggLocation = 256, EggCycles = 10, Language = International}, // Magikarp Reversal + new(183, 05, GS) {OT_Names = PCNYx, Moves = new[] {146}, EggLocation = 256, EggCycles = 10, Language = International}, // Marill Dizzy Punch + + // Tropical Promotion to Summer Festival 2 (July 12 to August 8, 2002) + new(054, 05, GS) {OT_Names = PCNYx, Moves = new[] {161}, EggLocation = 256, EggCycles = 10, Language = International}, // Psyduck Tri Attack + new(072, 05, GS) {OT_Names = PCNYx, Moves = new[] {109}, EggLocation = 256, EggCycles = 10, Language = International}, // Tentacool Confuse Ray + new(131, 05, GS) {OT_Names = PCNYx, Moves = new[] {044}, EggLocation = 256, EggCycles = 10, Language = International}, // Lapras Bite + new(170, 05, GS) {OT_Names = PCNYx, Moves = new[] {113}, EggLocation = 256, EggCycles = 10, Language = International}, // Chinchou Light Screen + new(223, 05, GS) {OT_Names = PCNYx, Moves = new[] {054}, EggLocation = 256, EggCycles = 10, Language = International}, // Remoraid Mist + new(226, 05, GS) {OT_Names = PCNYx, Moves = new[] {016}, EggLocation = 256, EggCycles = 10, Language = International}, // Mantine Gust + + // Safari Week (August 9 to 29, 2002) + new(029, 05, GS) {OT_Names = PCNYx, Moves = new[] {236}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (F) Moonlight + new(032, 05, GS) {OT_Names = PCNYx, Moves = new[] {234}, EggLocation = 256, EggCycles = 10, Language = International}, // Nidoran (M) Morning Sun + new(113, 05, GS) {OT_Names = PCNYx, Moves = new[] {230}, EggLocation = 256, EggCycles = 10, Language = International}, // Chansey Sweet Scent + new(115, 05, GS) {OT_Names = PCNYx, Moves = new[] {185}, EggLocation = 256, EggCycles = 10, Language = International}, // Kangaskhan Faint Attack + new(128, 05, GS) {OT_Names = PCNYx, Moves = new[] {098}, EggLocation = 256, EggCycles = 10, Language = International}, // Tauros Quick Attack + new(147, 05, GS) {OT_Names = PCNYx, Moves = new[] {056}, EggLocation = 256, EggCycles = 10, Language = International}, // Dratini Hydro Pump + + // Sky Week (August 30 to September 26, 2002) + new(021, 05, GS) {OT_Names = PCNYx, Moves = new[] {049}, EggLocation = 256, EggCycles = 10, Language = International}, // Spearow SonicBoom + new(083, 05, GS) {OT_Names = PCNYx, Moves = new[] {210}, EggLocation = 256, EggCycles = 10, Language = International}, // Farfetch'd Fury Cutter + new(084, 05, GS) {OT_Names = PCNYx, Moves = new[] {067}, EggLocation = 256, EggCycles = 10, Language = International}, // Doduo Low Kick + new(177, 05, GS) {OT_Names = PCNYx, Moves = new[] {219}, EggLocation = 256, EggCycles = 10, Language = International}, // Natu Safeguard + new(198, 05, GS) {OT_Names = PCNYx, Moves = new[] {251}, EggLocation = 256, EggCycles = 10, Language = International}, // Murkrow Beat Up + new(227, 05, GS) {OT_Names = PCNYx, Moves = new[] {210}, EggLocation = 256, EggCycles = 10, Language = International}, // Skarmory Fury Cutter + + // The Kanto Initial Three Pokémon (September 27 to October 3, 2002) + new(150, 05, C) {OT_Names = PCNYx, CurrentLevel = 70, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Mewtwo + + // Power Plant Pokémon (October 4 to October 10, 2002) + new(172, 05, GS) {OT_Names = PCNYx, Moves = new[] {146}, EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Dizzy Punch + new(081, 05, GS) {OT_Names = PCNYx, Moves = new[] {097}, EggLocation = 256, EggCycles = 10, Language = International}, // Magnemite Agility + new(239, 05, GS) {OT_Names = PCNYx, Moves = new[] {146}, EggLocation = 256, EggCycles = 10, Language = International}, // Elekid Dizzy Punch + new(100, 05, GS) {OT_Names = PCNYx, Moves = new[] {097}, EggLocation = 256, EggCycles = 10, Language = International}, // Voltorb Agility + + // Scary Face Pokémon (October 25 to October 31, 2002) + new(173, 05, GS) {OT_Names = PCNYx, Moves = new[] {184}, EggLocation = 256, EggCycles = 10, Language = International}, // Cleffa Scary Face + new(174, 05, GS) {OT_Names = PCNYx, Moves = new[] {184}, EggLocation = 256, EggCycles = 10, Language = International}, // Igglybuff Scary Face + new(183, 05, GS) {OT_Names = PCNYx, Moves = new[] {184}, EggLocation = 256, EggCycles = 10, Language = International}, // Marill Scary Face + new(172, 05, GS) {OT_Names = PCNYx, Moves = new[] {184}, EggLocation = 256, EggCycles = 10, Language = International}, // Pichu Scary Face + new(194, 05, GS) {OT_Names = PCNYx, Moves = new[] {184}, EggLocation = 256, EggCycles = 10, Language = International}, // Wooper Scary Face + + // Silver Cave (November 1 to November 7, 2002) + new(114, 05, GS) {OT_Names = PCNYx, Moves = new[] {235}, EggLocation = 256, EggCycles = 10, Language = International}, // Tangela Synthesis + new(077, 05, GS) {OT_Names = PCNYx, Moves = new[] {067}, EggLocation = 256, EggCycles = 10, Language = International}, // Ponyta Low Kick + new(200, 05, GS) {OT_Names = PCNYx, Moves = new[] {095}, EggLocation = 256, EggCycles = 10, Language = International}, // Misdreavus Hypnosis + new(246, 05, GS) {OT_Names = PCNYx, Moves = new[] {099}, EggLocation = 256, EggCycles = 10, Language = International}, // Larvitar Rage + + // Union Cave Pokémon (November 8 to 14, 2002) + new(120, 05, GS) {OT_Names = PCNYx, Moves = new[] {239}, EggLocation = 256, EggCycles = 10, Language = International}, // Staryu Twister + new(098, 05, GS) {OT_Names = PCNYx, Moves = new[] {232}, EggLocation = 256, EggCycles = 10, Language = International}, // Krabby Metal Claw + new(095, 05, GS) {OT_Names = PCNYx, Moves = new[] {159}, EggLocation = 256, EggCycles = 10, Language = International}, // Onix Sharpen + new(131, 05, GS) {OT_Names = PCNYx, Moves = new[] {248}, EggLocation = 256, EggCycles = 10, Language = International}, // Lapras Future Sight + + // Johto Legendary (November 15 to 21, 2002) + new(250, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Ho-Oh + new(249, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Lugia + + // Celebi Present SP (November 22 to 28, 2002) + new(151, 05, C) {OT_Names = PCNYx, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Mew + + // Psychic Type Pokémon (November 29 to December 5, 2002) + new(063, 05, GS) {OT_Names = PCNYx, Moves = new[] {193}, EggLocation = 256, EggCycles = 10, Language = International}, // Abra Foresight + new(096, 05, GS) {OT_Names = PCNYx, Moves = new[] {133}, EggLocation = 256, EggCycles = 10, Language = International}, // Drowzee Amnesia + new(102, 05, GS) {OT_Names = PCNYx, Moves = new[] {230}, EggLocation = 256, EggCycles = 10, Language = International}, // Exeggcute Sweet Scent + new(122, 05, GS) {OT_Names = PCNYx, Moves = new[] {170}, EggLocation = 256, EggCycles = 10, Language = International}, // Mr. Mime Mind Reader + + // The Johto Initial Three Pokémon (December 6 to 12, 2002) + new(154, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Meganium + new(157, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Typhlosion + new(160, 05, C) {OT_Names = PCNYx, CurrentLevel = 40, Shiny = Shiny.Always, Location = 127, Language = International}, // Shiny Feraligatr + + // Rock Tunnel Pokémon (December 13 to December 19, 2002) + new(074, 05, GS) {OT_Names = PCNYx, Moves = new[] {229}, EggLocation = 256, EggCycles = 10, Language = International}, // Geodude Rapid Spin + new(041, 05, GS) {OT_Names = PCNYx, Moves = new[] {175}, EggLocation = 256, EggCycles = 10, Language = International}, // Zubat Flail + new(066, 05, GS) {OT_Names = PCNYx, Moves = new[] {037}, EggLocation = 256, EggCycles = 10, Language = International}, // Machop Thrash + new(104, 05, GS) {OT_Names = PCNYx, Moves = new[] {031}, EggLocation = 256, EggCycles = 10, Language = International}, // Cubone Fury Attack + + // Ice Type Pokémon (December 20 to 26, 2002) + new(225, 05, GS) {OT_Names = PCNYx, Moves = new[] {191}, EggLocation = 256, EggCycles = 10, Language = International}, // Delibird Spikes + new(086, 05, GS) {OT_Names = PCNYx, Moves = new[] {175}, EggLocation = 256, EggCycles = 10, Language = International}, // Seel Flail + new(220, 05, GS) {OT_Names = PCNYx, Moves = new[] {018}, EggLocation = 256, EggCycles = 10, Language = International}, // Swinub Whirlwind + + // Pokémon that Appear at Night only (December 27, 2002 to January 2, 2003) + new(163, 05, GS) {OT_Names = PCNYx, Moves = new[] {101}, EggLocation = 256, EggCycles = 10, Language = International}, // Hoothoot Night Shade + new(215, 05, GS) {OT_Names = PCNYx, Moves = new[] {236}, EggLocation = 256, EggCycles = 10, Language = International}, // Sneasel Moonlight + + // Grass Type ( January 3 to 9, 2003) + new(191, 05, GS) {OT_Names = PCNYx, Moves = new[] {150}, EggLocation = 256, EggCycles = 10, Language = International}, // Sunkern Splash + new(046, 05, GS) {OT_Names = PCNYx, Moves = new[] {235}, EggLocation = 256, EggCycles = 10, Language = International}, // Paras Synthesis + new(187, 05, GS) {OT_Names = PCNYx, Moves = new[] {097}, EggLocation = 256, EggCycles = 10, Language = International}, // Hoppip Agility + new(043, 05, GS) {OT_Names = PCNYx, Moves = new[] {073}, EggLocation = 256, EggCycles = 10, Language = International}, // Oddish Leech Seed + + // Normal Pokémon (January 10 to 16, 2003) + new(161, 05, GS) {OT_Names = PCNYx, Moves = new[] {146}, EggLocation = 256, EggCycles = 10, Language = International}, // Sentret Dizzy Punch + new(234, 05, GS) {OT_Names = PCNYx, Moves = new[] {219}, EggLocation = 256, EggCycles = 10, Language = International}, // Stantler Safeguard + new(241, 05, GS) {OT_Names = PCNYx, Moves = new[] {025}, EggLocation = 256, EggCycles = 10, Language = International}, // Miltank Mega Kick + new(190, 05, GS) {OT_Names = PCNYx, Moves = new[] {102}, EggLocation = 256, EggCycles = 10, Language = International}, // Aipom Mimic + new(108, 05, GS) {OT_Names = PCNYx, Moves = new[] {003}, EggLocation = 256, EggCycles = 10, Language = International}, // Lickitung DoubleSlap + new(143, 05, GS) {OT_Names = PCNYx, Moves = new[] {150}, EggLocation = 256, EggCycles = 10, Language = International}, // Snorlax Splash + + // Mt. Mortar (January 24 to 30, 2003) + new(066, 05, GS) {OT_Names = PCNYx, Moves = new[] {206}, EggLocation = 256, EggCycles = 10, Language = International}, // Machop False Swipe + new(129, 05, GS) {OT_Names = PCNYx, Moves = new[] {145}, EggLocation = 256, EggCycles = 10, Language = International}, // Magikarp Bubble + new(236, 05, GS) {OT_Names = PCNYx, Moves = new[] {099}, EggLocation = 256, EggCycles = 10, Language = International}, // Tyrogue Rage + + // Dark Cave Pokémon (January 31 to February 6, 2003) + new(206, 05, GS) {OT_Names = PCNYx, Moves = new[] {031}, EggLocation = 256, EggCycles = 10, Language = International}, // Dunsparce Fury Attack + new(202, 05, GS) {OT_Names = PCNYx, Moves = new[] {102}, EggLocation = 256, EggCycles = 10, Language = International}, // Wobbuffet Mimic + new(231, 05, GS) {OT_Names = PCNYx, Moves = new[] {071}, EggLocation = 256, EggCycles = 10, Language = International}, // Phanpy Absorb + new(216, 05, GS) {OT_Names = PCNYx, Moves = new[] {230}, EggLocation = 256, EggCycles = 10, Language = International}, // Teddiursa Sweet Scent + + // Valentine's Day Special (February 7 to 13, 2003) + new(060, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Poliwag Sweet Kiss + new(060, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Poliwag Lovely Kiss + new(143, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Snorlax Sweet Kiss + new(143, 05, GS) {OT_Names = PCNYx, Moves = new[] {142}, EggLocation = 256, EggCycles = 10, Language = International}, // Snorlax Lovely Kiss + + // Rare Pokémon (February 21 to 27, 2003) + new(140, 05, GS) {OT_Names = PCNYx, Moves = new[] {088}, EggLocation = 256, EggCycles = 10, Language = International}, // Kabuto Rock Throw + new(138, 05, GS) {OT_Names = PCNYx, Moves = new[] {088}, EggLocation = 256, EggCycles = 10, Language = International}, // Omanyte Rock Throw + new(142, 05, GS) {OT_Names = PCNYx, Moves = new[] {088}, EggLocation = 256, EggCycles = 10, Language = International}, // Aerodactyl Rock Throw + new(137, 05, GS) {OT_Names = PCNYx, Moves = new[] {112}, EggLocation = 256, EggCycles = 10, Language = International}, // Porygon Barrier + new(133, 05, GS) {OT_Names = PCNYx, Moves = new[] {074}, EggLocation = 256, EggCycles = 10, Language = International}, // Eevee Growth + new(185, 05, GS) {OT_Names = PCNYx, Moves = new[] {164}, EggLocation = 256, EggCycles = 10, Language = International}, // Sudowoodo Substitute + + // Bug Type Pokémon (February 28 to March 6, 2003) + new(123, 05, GS) {OT_Names = PCNYx, Moves = new[] {049}, EggLocation = 256, EggCycles = 10, Language = International}, // Scyther SonicBoom + new(214, 05, GS) {OT_Names = PCNYx, Moves = new[] {069}, EggLocation = 256, EggCycles = 10, Language = International}, // Heracross Seismic Toss + new(127, 05, GS) {OT_Names = PCNYx, Moves = new[] {088}, EggLocation = 256, EggCycles = 10, Language = International}, // Pinsir Rock Throw + new(165, 05, GS) {OT_Names = PCNYx, Moves = new[] {112}, EggLocation = 256, EggCycles = 10, Language = International}, // Ledyba Barrier + new(167, 05, GS) {OT_Names = PCNYx, Moves = new[] {074}, EggLocation = 256, EggCycles = 10, Language = International}, // Spinarak Growth + new(193, 05, GS) {OT_Names = PCNYx, Moves = new[] {186}, EggLocation = 256, EggCycles = 10, Language = International}, // Yanma Sweet Kiss + new(204, 05, GS) {OT_Names = PCNYx, Moves = new[] {164}, EggLocation = 256, EggCycles = 10, Language = International}, // Pineco Substitute + + // Japanese Only (all below) + new(251, 30, GSC) {Location = 014}, // Celebi @ Ilex Forest (GBC) + + // Gen2 Events + // Egg Cycles Subject to Change. OTs for Eggs are unknown. + // Pokémon Center Mystery Egg #1 (December 15, 2001 to January 14, 2002) + new(152, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Chikorita Petal Dance + new(172, 05, GSC) {Moves = new[] {047}, EggLocation = 256, EggCycles = 10}, // Pichu Sing + new(173, 05, GSC) {Moves = new[] {129}, EggLocation = 256, EggCycles = 10}, // Cleffa Swift + new(194, 05, GSC) {Moves = new[] {187}, EggLocation = 256, EggCycles = 10}, // Wooper Belly Drum + new(231, 05, GSC) {Moves = new[] {227}, EggLocation = 256, EggCycles = 10}, // Phanpy Encore + new(238, 05, GSC) {Moves = new[] {118}, EggLocation = 256, EggCycles = 10}, // Smoochum Metronome + + // Pokémon Center Mystery Egg #2 (March 16 to April 7, 2002) + new(054, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Psyduck Petal Dance + new(152, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Chikorita Petal Dance + new(172, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Pichu Petal Dance + new(173, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Cleffa Petal Dance + new(174, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Igglybuff Petal Dance + new(238, 05, GSC) {Moves = new[] {080}, EggLocation = 256, EggCycles = 10}, // Smoochum Petal Dance + + // Pokémon Center Mystery Egg #3 (April 27 to May 12, 2002) + new(001, 05, GSC) {Moves = new[] {246}, EggLocation = 256, EggCycles = 10}, // Bulbasaur Ancientpower + new(004, 05, GSC) {Moves = new[] {242}, EggLocation = 256, EggCycles = 10}, // Charmander Crunch + new(158, 05, GSC) {Moves = new[] {066}, EggLocation = 256, EggCycles = 10}, // Totodile Submission + new(163, 05, GSC) {Moves = new[] {101}, EggLocation = 256, EggCycles = 10}, // Hoot-Hoot Night Shade + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs index bf9176cd7..51bc4a68d 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters3.cs @@ -2,190 +2,189 @@ using static PKHeX.Core.GameVersion; using static PKHeX.Core.AbilityPermission; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 Encounters +/// +internal static class Encounters3 { - /// - /// Generation 3 Encounters - /// - internal static class Encounters3 + private static readonly EncounterArea3[] SlotsSwarmRSE = GetSwarm("rse_swarm", "rs", RSE); + internal static readonly EncounterArea3[] SlotsR = ArrayUtil.ConcatAll(GetRegular("r", "ru", R), SlotsSwarmRSE); + internal static readonly EncounterArea3[] SlotsS = ArrayUtil.ConcatAll(GetRegular("s", "sa", S), SlotsSwarmRSE); + internal static readonly EncounterArea3[] SlotsE = ArrayUtil.ConcatAll(GetRegular("e", "em", E), SlotsSwarmRSE); + internal static readonly EncounterArea3[] SlotsFR = GetRegular("fr", "fr", FR); + internal static readonly EncounterArea3[] SlotsLG = GetRegular("lg", "lg", LG); + + private static EncounterArea3[] GetRegular(string resource, string ident, GameVersion game) => EncounterArea3.GetAreas(Get(resource, ident), game); + private static EncounterArea3[] GetSwarm(string resource, string ident, GameVersion game) => EncounterArea3.GetAreasSwarm(Get(resource, ident), game); + + static Encounters3() { - private static readonly EncounterArea3[] SlotsSwarmRSE = GetSwarm("rse_swarm", "rs", RSE); - internal static readonly EncounterArea3[] SlotsR = ArrayUtil.ConcatAll(GetRegular("r", "ru", R), SlotsSwarmRSE); - internal static readonly EncounterArea3[] SlotsS = ArrayUtil.ConcatAll(GetRegular("s", "sa", S), SlotsSwarmRSE); - internal static readonly EncounterArea3[] SlotsE = ArrayUtil.ConcatAll(GetRegular("e", "em", E), SlotsSwarmRSE); - internal static readonly EncounterArea3[] SlotsFR = GetRegular("fr", "fr", FR); - internal static readonly EncounterArea3[] SlotsLG = GetRegular("lg", "lg", LG); - - private static EncounterArea3[] GetRegular(string resource, string ident, GameVersion game) => EncounterArea3.GetAreas(Get(resource, ident), game); - private static EncounterArea3[] GetSwarm(string resource, string ident, GameVersion game) => EncounterArea3.GetAreasSwarm(Get(resource, ident), game); - - static Encounters3() - { - MarkEncounterTradeStrings(TradeGift_RSE, TradeRSE); - MarkEncounterTradeStrings(TradeGift_FRLG, TradeFRLG); - } - - private static readonly EncounterStatic3[] Encounter_RSE_Roam = - { - new(380, 40, S) { Roaming = true, Location = 016 }, // Latias - new(380, 40, E) { Roaming = true, Location = 016 }, // Latias - new(381, 40, R) { Roaming = true, Location = 016 }, // Latios - new(381, 40, E) { Roaming = true, Location = 016 }, // Latios - }; - - private static readonly EncounterStatic3[] Encounter_RSE_Regular = - { - // Starters - new(152, 05, E ) { Gift = true, Location = 000 }, // Chikorita @ Littleroot Town - new(155, 05, E ) { Gift = true, Location = 000 }, // Cyndaquil - new(158, 05, E ) { Gift = true, Location = 000 }, // Totodile - new(252, 05, RSE) { Gift = true, Location = 016 }, // Treecko @ Route 101 - new(255, 05, RSE) { Gift = true, Location = 016 }, // Torchic - new(258, 05, RSE) { Gift = true, Location = 016 }, // Mudkip - - // Fossil @ Rustboro City - new(345, 20, RSE) { Gift = true, Location = 010 }, // Lileep - new(347, 20, RSE) { Gift = true, Location = 010 }, // Anorith - - // Gift - new(351, 25, RSE) { Gift = true, Location = 034 }, // Castform @ Weather Institute - new(374, 05, RSE) { Gift = true, Location = 013 }, // Beldum @ Mossdeep City - new(360, 05, RSE) { Gift = true, EggLocation = 253 }, // Wynaut Egg - - // Stationary - new(352, 30, RSE) { Location = 034 }, // Kecleon @ Route 119 - new(352, 30, RSE) { Location = 035 }, // Kecleon @ Route 120 - new(101, 30, RS ) { Location = 066 }, // Electrode @ Hideout (R:Magma Hideout/S:Aqua Hideout) - new(101, 30, E ) { Location = 197 }, // Electrode @ Aqua Hideout - new(185, 40, E ) { Location = 058 }, // Sudowoodo @ Battle Frontier - - // Stationary Lengendary - new(377, 40, RSE) { Location = 082 }, // Regirock @ Desert Ruins - new(378, 40, RSE) { Location = 081 }, // Regice @ Island Cave - new(379, 40, RSE) { Location = 083 }, // Registeel @ Ancient Tomb - new(380, 50, R ) { Location = 073 }, // Latias @ Southern Island - new(380, 50, E) { Location = 073, Fateful = true }, // Latias @ Southern Island - new(381, 50, S ) { Location = 073 }, // Latios @ Southern Island - new(381, 50, E) { Location = 073, Fateful = true }, // Latios @ Southern Island - new(382, 45, S ) { Location = 072 }, // Kyogre @ Cave of Origin - new(382, 70, E) { Location = 203 }, // Kyogre @ Marine Cave - new(383, 45, R ) { Location = 072 }, // Groudon @ Cave of Origin - new(383, 70, E) { Location = 205 }, // Groudon @ Terra Cave - new(384, 70, RSE) { Location = 085 }, // Rayquaza @ Sky Pillar - - // Event - new(151, 30, E) { Location = 201, Fateful = true }, // Mew @ Faraway Island (Unreleased outside of Japan) - new(249, 70, E) { Location = 211, Fateful = true }, // Lugia @ Navel Rock - new(250, 70, E) { Location = 211, Fateful = true }, // Ho-Oh @ Navel Rock - new(386, 30, E) { Location = 200, Fateful = true, Form = 3 }, // Deoxys @ Birth Island - }; - - private static readonly EncounterStatic3[] Encounter_FRLG_Roam = - { - new(243, 50, FRLG) { Roaming = true, Location = 16 }, // Raikou - new(244, 50, FRLG) { Roaming = true, Location = 16 }, // Entei - new(245, 50, FRLG) { Roaming = true, Location = 16 }, // Suicune - }; - - private static readonly EncounterStatic3[] Encounter_FRLG_Stationary = - { - // Starters @ Pallet Town - new(001, 05, FRLG) { Gift = true, Location = 088 }, // Bulbasaur - new(004, 05, FRLG) { Gift = true, Location = 088 }, // Charmander - new(007, 05, FRLG) { Gift = true, Location = 088 }, // Squirtle - - // Fossil @ Cinnabar Island - new(138, 05, FRLG) { Gift = true, Location = 096 }, // Omanyte - new(140, 05, FRLG) { Gift = true, Location = 096 }, // Kabuto - new(142, 05, FRLG) { Gift = true, Location = 096 }, // Aerodactyl - - // Gift - new(106, 25, FRLG) { Gift = true, Location = 098 }, // Hitmonlee @ Saffron City - new(107, 25, FRLG) { Gift = true, Location = 098 }, // Hitmonchan @ Saffron City - new(129, 05, FRLG) { Gift = true, Location = 099 }, // Magikarp @ Route 4 - new(131, 25, FRLG) { Gift = true, Location = 134 }, // Lapras @ Silph Co. - new(133, 25, FRLG) { Gift = true, Location = 094 }, // Eevee @ Celadon City - new(175, 05, FRLG) { Gift = true, EggLocation = 253 }, // Togepi Egg - - // Celadon City Game Corner - new(063, 09, FR) { Gift = true, Location = 94 }, // Abra - new(035, 08, FR) { Gift = true, Location = 94 }, // Clefairy - new(123, 25, FR) { Gift = true, Location = 94 }, // Scyther - new(147, 18, FR) { Gift = true, Location = 94 }, // Dratini - new(137, 26, FR) { Gift = true, Location = 94 }, // Porygon - - new(063, 07, LG) { Gift = true, Location = 94 }, // Abra - new(035, 12, LG) { Gift = true, Location = 94 }, // Clefairy - new(127, 18, LG) { Gift = true, Location = 94 }, // Pinsir - new(147, 24, LG) { Gift = true, Location = 94 }, // Dratini - new(137, 18, LG) { Gift = true, Location = 94 }, // Porygon - - // Stationary - new(143, 30, FRLG) { Location = 112 }, // Snorlax @ Route 12 - new(143, 30, FRLG) { Location = 116 }, // Snorlax @ Route 16 - new(101, 34, FRLG) { Location = 142 }, // Electrode @ Power Plant - new(097, 30, FRLG) { Location = 176 }, // Hypno @ Berry Forest - - // Stationary Legendary - new(144, 50, FRLG) { Location = 139 }, // Articuno @ Seafoam Islands - new(145, 50, FRLG) { Location = 142 }, // Zapdos @ Power Plant - new(146, 50, FRLG) { Location = 175 }, // Moltres @ Mt. Ember. - new(150, 70, FRLG) { Location = 141 }, // Mewtwo @ Cerulean Cave - - // Event - new(249, 70, FRLG) { Location = 174, Fateful = true }, // Lugia @ Navel Rock - new(250, 70, FRLG) { Location = 174, Fateful = true }, // Ho-Oh @ Navel Rock - new(386, 30, FR ) { Location = 187, Fateful = true, Form = 1 }, // Deoxys @ Birth Island - new(386, 30, LG) { Location = 187, Fateful = true, Form = 2 }, // Deoxys @ Birth Island - }; - - private static readonly EncounterStatic3[] Encounter_RSE = ArrayUtil.ConcatAll(Encounter_RSE_Roam, Encounter_RSE_Regular); - private static readonly EncounterStatic3[] Encounter_FRLG = ArrayUtil.ConcatAll(Encounter_FRLG_Roam, Encounter_FRLG_Stationary); - - private static readonly byte[] TradeContest_Cool = { 30, 05, 05, 05, 05, 10 }; - private static readonly byte[] TradeContest_Beauty = { 05, 30, 05, 05, 05, 10 }; - private static readonly byte[] TradeContest_Cute = { 05, 05, 30, 05, 05, 10 }; - private static readonly byte[] TradeContest_Clever = { 05, 05, 05, 30, 05, 10 }; - private static readonly byte[] TradeContest_Tough = { 05, 05, 05, 05, 30, 10 }; - - internal static readonly EncounterTrade3[] TradeGift_RSE = - { - new(RS, 0x00009C40, 296, 05) { Ability = OnlySecond, TID = 49562, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {5,5,4,4,4,4}, Contest = TradeContest_Tough }, // Slakoth (Level 5 Breeding) -> Makuhita - new(RS, 0x498A2E17, 300, 03) { Ability = OnlyFirst, TID = 02259, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {5,4,4,5,4,4}, Contest = TradeContest_Cute }, // Pikachu (Level 3 Viridian Forest) -> Skitty - new(RS, 0x4C970B7F, 222, 21) { Ability = OnlySecond, TID = 50183, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {4,4,5,4,4,5}, Contest = TradeContest_Beauty }, // Bellossom (Level 21 Oddish -> Gloom -> Bellossom) -> Corsola - new(E , 0x00000084, 273, 04) { Ability = OnlySecond, TID = 38726, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {5,4,5,4,4,4}, Contest = TradeContest_Cool }, // Ralts (Level 4 Route 102) -> Seedot - new(E , 0x0000006F, 311, 05) { Ability = OnlyFirst, TID = 08460, SID = 00001, OTGender = 0, Gender = 1, IVs = new[] {4,4,4,5,5,4}, Contest = TradeContest_Cute }, // Volbeat (Level 5 Breeding) -> Plusle - new(E , 0x0000007F, 116, 05) { Ability = OnlyFirst, TID = 46285, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {5,4,4,4,5,4}, Contest = TradeContest_Tough }, // Bagon (Level 5 Breeding) -> Horsea* - new(E , 0x0000008B, 052, 03) { Ability = OnlyFirst, TID = 25945, SID = 00001, OTGender = 1, Gender = 0, IVs = new[] {4,5,4,5,4,4}, Contest = TradeContest_Clever }, // Skitty (Level 3 Trade)-> Meowth* - // If Pokémon with * is evolved in a Generation IV or V game, its Ability will become its second Ability. - }; - - internal static readonly EncounterTrade3[] TradeGift_FRLG = - { - new(FRLG, 0x00009CAE, 122, 05) { Ability = OnlyFirst, TID = 01985, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,15,17,24,23,22}, Contest = TradeContest_Clever }, // Abra (Level 5 Breeding) -> Mr. Mime - new(FR , 0x4C970B89, 029, 05) { Ability = OnlyFirst, TID = 63184, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {22,18,25,19,15,22}, Contest = TradeContest_Tough }, // Nidoran♀ - new( LG, 0x4C970B9E, 032, 05) { Ability = OnlyFirst, TID = 63184, SID = 00000, OTGender = 1, Gender = 0, IVs = new[] {19,25,18,22,22,15}, Contest = TradeContest_Cool }, // Nidoran♂ * - new(FR , 0x00EECA15, 030, 16) { Ability = OnlyFirst, TID = 13637, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {22,25,18,19,22,15}, Contest = TradeContest_Cute }, // Nidorina * - new( LG, 0x00EECA19, 033, 16) { Ability = OnlyFirst, TID = 13637, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {19,18,25,22,15,22}, Contest = TradeContest_Tough }, // Nidorino * - new(FR , 0x451308AB, 108, 25) { Ability = OnlyFirst, TID = 01239, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {24,19,21,15,23,21}, Contest = TradeContest_Tough }, // Golduck (Level 25) -> Lickitung * - new( LG, 0x451308AB, 108, 25) { Ability = OnlyFirst, TID = 01239, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {24,19,21,15,23,21}, Contest = TradeContest_Tough }, // Slowbro (Level 25) -> Lickitung * - new(FRLG, 0x498A2E1D, 124, 20) { Ability = OnlyFirst, TID = 36728, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {18,17,18,22,25,21}, Contest = TradeContest_Beauty }, // Poliwhirl (Level 20) -> Jynx - new(FRLG, 0x151943D7, 083, 03) { Ability = OnlyFirst, TID = 08810, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,25,21,24,15,20}, Contest = TradeContest_Cool }, // Spearow (Level 3 Capture) -> Farfetch'd - new(FRLG, 0x06341016, 101, 03) { Ability = OnlySecond, TID = 50298, SID = 00000, OTGender = 0, Gender = 2, IVs = new[] {19,16,18,25,25,19}, Contest = TradeContest_Cool }, // Raichu (Level 3) -> Electrode - new(FRLG, 0x5C77ECFA, 114, 05) { Ability = OnlyFirst, TID = 60042, SID = 00000, OTGender = 1, Gender = 0, IVs = new[] {22,17,25,16,23,20}, Contest = TradeContest_Cute }, // Venonat (Level 5 Breeding) -> Tangela - new(FRLG, 0x482CAC89, 086, 05) { Ability = OnlyFirst, TID = 09853, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {24,15,22,16,23,22}, Contest = TradeContest_Tough }, // Ponyta (Level 5 Breeding) -> Seel * - // If Pokémon with * is evolved in a Generation IV or V game, its Ability will become its second Ability. - }; - - private const string tradeRSE = "traderse"; - private const string tradeFRLG = "tradefrlg"; - private static readonly string[][] TradeRSE = Util.GetLanguageStrings7(tradeRSE); - private static readonly string[][] TradeFRLG = Util.GetLanguageStrings7(tradeFRLG); - - internal static readonly EncounterStatic3[] StaticR = GetEncounters(Encounter_RSE, R); - internal static readonly EncounterStatic3[] StaticS = GetEncounters(Encounter_RSE, S); - internal static readonly EncounterStatic3[] StaticE = GetEncounters(Encounter_RSE, E); - internal static readonly EncounterStatic3[] StaticFR = GetEncounters(Encounter_FRLG, FR); - internal static readonly EncounterStatic3[] StaticLG = GetEncounters(Encounter_FRLG, LG); + MarkEncounterTradeStrings(TradeGift_RSE, TradeRSE); + MarkEncounterTradeStrings(TradeGift_FRLG, TradeFRLG); } + + private static readonly EncounterStatic3[] Encounter_RSE_Roam = + { + new(380, 40, S) { Roaming = true, Location = 016 }, // Latias + new(380, 40, E) { Roaming = true, Location = 016 }, // Latias + new(381, 40, R) { Roaming = true, Location = 016 }, // Latios + new(381, 40, E) { Roaming = true, Location = 016 }, // Latios + }; + + private static readonly EncounterStatic3[] Encounter_RSE_Regular = + { + // Starters + new(152, 05, E ) { Gift = true, Location = 000 }, // Chikorita @ Littleroot Town + new(155, 05, E ) { Gift = true, Location = 000 }, // Cyndaquil + new(158, 05, E ) { Gift = true, Location = 000 }, // Totodile + new(252, 05, RSE) { Gift = true, Location = 016 }, // Treecko @ Route 101 + new(255, 05, RSE) { Gift = true, Location = 016 }, // Torchic + new(258, 05, RSE) { Gift = true, Location = 016 }, // Mudkip + + // Fossil @ Rustboro City + new(345, 20, RSE) { Gift = true, Location = 010 }, // Lileep + new(347, 20, RSE) { Gift = true, Location = 010 }, // Anorith + + // Gift + new(351, 25, RSE) { Gift = true, Location = 034 }, // Castform @ Weather Institute + new(374, 05, RSE) { Gift = true, Location = 013 }, // Beldum @ Mossdeep City + new(360, 05, RSE) { Gift = true, EggLocation = 253 }, // Wynaut Egg + + // Stationary + new(352, 30, RSE) { Location = 034 }, // Kecleon @ Route 119 + new(352, 30, RSE) { Location = 035 }, // Kecleon @ Route 120 + new(101, 30, RS ) { Location = 066 }, // Electrode @ Hideout (R:Magma Hideout/S:Aqua Hideout) + new(101, 30, E ) { Location = 197 }, // Electrode @ Aqua Hideout + new(185, 40, E ) { Location = 058 }, // Sudowoodo @ Battle Frontier + + // Stationary Lengendary + new(377, 40, RSE) { Location = 082 }, // Regirock @ Desert Ruins + new(378, 40, RSE) { Location = 081 }, // Regice @ Island Cave + new(379, 40, RSE) { Location = 083 }, // Registeel @ Ancient Tomb + new(380, 50, R ) { Location = 073 }, // Latias @ Southern Island + new(380, 50, E) { Location = 073, Fateful = true }, // Latias @ Southern Island + new(381, 50, S ) { Location = 073 }, // Latios @ Southern Island + new(381, 50, E) { Location = 073, Fateful = true }, // Latios @ Southern Island + new(382, 45, S ) { Location = 072 }, // Kyogre @ Cave of Origin + new(382, 70, E) { Location = 203 }, // Kyogre @ Marine Cave + new(383, 45, R ) { Location = 072 }, // Groudon @ Cave of Origin + new(383, 70, E) { Location = 205 }, // Groudon @ Terra Cave + new(384, 70, RSE) { Location = 085 }, // Rayquaza @ Sky Pillar + + // Event + new(151, 30, E) { Location = 201, Fateful = true }, // Mew @ Faraway Island (Unreleased outside of Japan) + new(249, 70, E) { Location = 211, Fateful = true }, // Lugia @ Navel Rock + new(250, 70, E) { Location = 211, Fateful = true }, // Ho-Oh @ Navel Rock + new(386, 30, E) { Location = 200, Fateful = true, Form = 3 }, // Deoxys @ Birth Island + }; + + private static readonly EncounterStatic3[] Encounter_FRLG_Roam = + { + new(243, 50, FRLG) { Roaming = true, Location = 16 }, // Raikou + new(244, 50, FRLG) { Roaming = true, Location = 16 }, // Entei + new(245, 50, FRLG) { Roaming = true, Location = 16 }, // Suicune + }; + + private static readonly EncounterStatic3[] Encounter_FRLG_Stationary = + { + // Starters @ Pallet Town + new(001, 05, FRLG) { Gift = true, Location = 088 }, // Bulbasaur + new(004, 05, FRLG) { Gift = true, Location = 088 }, // Charmander + new(007, 05, FRLG) { Gift = true, Location = 088 }, // Squirtle + + // Fossil @ Cinnabar Island + new(138, 05, FRLG) { Gift = true, Location = 096 }, // Omanyte + new(140, 05, FRLG) { Gift = true, Location = 096 }, // Kabuto + new(142, 05, FRLG) { Gift = true, Location = 096 }, // Aerodactyl + + // Gift + new(106, 25, FRLG) { Gift = true, Location = 098 }, // Hitmonlee @ Saffron City + new(107, 25, FRLG) { Gift = true, Location = 098 }, // Hitmonchan @ Saffron City + new(129, 05, FRLG) { Gift = true, Location = 099 }, // Magikarp @ Route 4 + new(131, 25, FRLG) { Gift = true, Location = 134 }, // Lapras @ Silph Co. + new(133, 25, FRLG) { Gift = true, Location = 094 }, // Eevee @ Celadon City + new(175, 05, FRLG) { Gift = true, EggLocation = 253 }, // Togepi Egg + + // Celadon City Game Corner + new(063, 09, FR) { Gift = true, Location = 94 }, // Abra + new(035, 08, FR) { Gift = true, Location = 94 }, // Clefairy + new(123, 25, FR) { Gift = true, Location = 94 }, // Scyther + new(147, 18, FR) { Gift = true, Location = 94 }, // Dratini + new(137, 26, FR) { Gift = true, Location = 94 }, // Porygon + + new(063, 07, LG) { Gift = true, Location = 94 }, // Abra + new(035, 12, LG) { Gift = true, Location = 94 }, // Clefairy + new(127, 18, LG) { Gift = true, Location = 94 }, // Pinsir + new(147, 24, LG) { Gift = true, Location = 94 }, // Dratini + new(137, 18, LG) { Gift = true, Location = 94 }, // Porygon + + // Stationary + new(143, 30, FRLG) { Location = 112 }, // Snorlax @ Route 12 + new(143, 30, FRLG) { Location = 116 }, // Snorlax @ Route 16 + new(101, 34, FRLG) { Location = 142 }, // Electrode @ Power Plant + new(097, 30, FRLG) { Location = 176 }, // Hypno @ Berry Forest + + // Stationary Legendary + new(144, 50, FRLG) { Location = 139 }, // Articuno @ Seafoam Islands + new(145, 50, FRLG) { Location = 142 }, // Zapdos @ Power Plant + new(146, 50, FRLG) { Location = 175 }, // Moltres @ Mt. Ember. + new(150, 70, FRLG) { Location = 141 }, // Mewtwo @ Cerulean Cave + + // Event + new(249, 70, FRLG) { Location = 174, Fateful = true }, // Lugia @ Navel Rock + new(250, 70, FRLG) { Location = 174, Fateful = true }, // Ho-Oh @ Navel Rock + new(386, 30, FR ) { Location = 187, Fateful = true, Form = 1 }, // Deoxys @ Birth Island + new(386, 30, LG) { Location = 187, Fateful = true, Form = 2 }, // Deoxys @ Birth Island + }; + + private static readonly EncounterStatic3[] Encounter_RSE = ArrayUtil.ConcatAll(Encounter_RSE_Roam, Encounter_RSE_Regular); + private static readonly EncounterStatic3[] Encounter_FRLG = ArrayUtil.ConcatAll(Encounter_FRLG_Roam, Encounter_FRLG_Stationary); + + private static readonly byte[] TradeContest_Cool = { 30, 05, 05, 05, 05, 10 }; + private static readonly byte[] TradeContest_Beauty = { 05, 30, 05, 05, 05, 10 }; + private static readonly byte[] TradeContest_Cute = { 05, 05, 30, 05, 05, 10 }; + private static readonly byte[] TradeContest_Clever = { 05, 05, 05, 30, 05, 10 }; + private static readonly byte[] TradeContest_Tough = { 05, 05, 05, 05, 30, 10 }; + + internal static readonly EncounterTrade3[] TradeGift_RSE = + { + new(RS, 0x00009C40, 296, 05) { Ability = OnlySecond, TID = 49562, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {5,5,4,4,4,4}, Contest = TradeContest_Tough }, // Slakoth (Level 5 Breeding) -> Makuhita + new(RS, 0x498A2E17, 300, 03) { Ability = OnlyFirst, TID = 02259, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {5,4,4,5,4,4}, Contest = TradeContest_Cute }, // Pikachu (Level 3 Viridian Forest) -> Skitty + new(RS, 0x4C970B7F, 222, 21) { Ability = OnlySecond, TID = 50183, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {4,4,5,4,4,5}, Contest = TradeContest_Beauty }, // Bellossom (Level 21 Oddish -> Gloom -> Bellossom) -> Corsola + new(E , 0x00000084, 273, 04) { Ability = OnlySecond, TID = 38726, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {5,4,5,4,4,4}, Contest = TradeContest_Cool }, // Ralts (Level 4 Route 102) -> Seedot + new(E , 0x0000006F, 311, 05) { Ability = OnlyFirst, TID = 08460, SID = 00001, OTGender = 0, Gender = 1, IVs = new[] {4,4,4,5,5,4}, Contest = TradeContest_Cute }, // Volbeat (Level 5 Breeding) -> Plusle + new(E , 0x0000007F, 116, 05) { Ability = OnlyFirst, TID = 46285, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {5,4,4,4,5,4}, Contest = TradeContest_Tough }, // Bagon (Level 5 Breeding) -> Horsea* + new(E , 0x0000008B, 052, 03) { Ability = OnlyFirst, TID = 25945, SID = 00001, OTGender = 1, Gender = 0, IVs = new[] {4,5,4,5,4,4}, Contest = TradeContest_Clever }, // Skitty (Level 3 Trade)-> Meowth* + // If Pokémon with * is evolved in a Generation IV or V game, its Ability will become its second Ability. + }; + + internal static readonly EncounterTrade3[] TradeGift_FRLG = + { + new(FRLG, 0x00009CAE, 122, 05) { Ability = OnlyFirst, TID = 01985, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,15,17,24,23,22}, Contest = TradeContest_Clever }, // Abra (Level 5 Breeding) -> Mr. Mime + new(FR , 0x4C970B89, 029, 05) { Ability = OnlyFirst, TID = 63184, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {22,18,25,19,15,22}, Contest = TradeContest_Tough }, // Nidoran♀ + new( LG, 0x4C970B9E, 032, 05) { Ability = OnlyFirst, TID = 63184, SID = 00000, OTGender = 1, Gender = 0, IVs = new[] {19,25,18,22,22,15}, Contest = TradeContest_Cool }, // Nidoran♂ * + new(FR , 0x00EECA15, 030, 16) { Ability = OnlyFirst, TID = 13637, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {22,25,18,19,22,15}, Contest = TradeContest_Cute }, // Nidorina * + new( LG, 0x00EECA19, 033, 16) { Ability = OnlyFirst, TID = 13637, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {19,18,25,22,15,22}, Contest = TradeContest_Tough }, // Nidorino * + new(FR , 0x451308AB, 108, 25) { Ability = OnlyFirst, TID = 01239, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {24,19,21,15,23,21}, Contest = TradeContest_Tough }, // Golduck (Level 25) -> Lickitung * + new( LG, 0x451308AB, 108, 25) { Ability = OnlyFirst, TID = 01239, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {24,19,21,15,23,21}, Contest = TradeContest_Tough }, // Slowbro (Level 25) -> Lickitung * + new(FRLG, 0x498A2E1D, 124, 20) { Ability = OnlyFirst, TID = 36728, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {18,17,18,22,25,21}, Contest = TradeContest_Beauty }, // Poliwhirl (Level 20) -> Jynx + new(FRLG, 0x151943D7, 083, 03) { Ability = OnlyFirst, TID = 08810, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,25,21,24,15,20}, Contest = TradeContest_Cool }, // Spearow (Level 3 Capture) -> Farfetch'd + new(FRLG, 0x06341016, 101, 03) { Ability = OnlySecond, TID = 50298, SID = 00000, OTGender = 0, Gender = 2, IVs = new[] {19,16,18,25,25,19}, Contest = TradeContest_Cool }, // Raichu (Level 3) -> Electrode + new(FRLG, 0x5C77ECFA, 114, 05) { Ability = OnlyFirst, TID = 60042, SID = 00000, OTGender = 1, Gender = 0, IVs = new[] {22,17,25,16,23,20}, Contest = TradeContest_Cute }, // Venonat (Level 5 Breeding) -> Tangela + new(FRLG, 0x482CAC89, 086, 05) { Ability = OnlyFirst, TID = 09853, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {24,15,22,16,23,22}, Contest = TradeContest_Tough }, // Ponyta (Level 5 Breeding) -> Seel * + // If Pokémon with * is evolved in a Generation IV or V game, its Ability will become its second Ability. + }; + + private const string tradeRSE = "traderse"; + private const string tradeFRLG = "tradefrlg"; + private static readonly string[][] TradeRSE = Util.GetLanguageStrings7(tradeRSE); + private static readonly string[][] TradeFRLG = Util.GetLanguageStrings7(tradeFRLG); + + internal static readonly EncounterStatic3[] StaticR = GetEncounters(Encounter_RSE, R); + internal static readonly EncounterStatic3[] StaticS = GetEncounters(Encounter_RSE, S); + internal static readonly EncounterStatic3[] StaticE = GetEncounters(Encounter_RSE, E); + internal static readonly EncounterStatic3[] StaticFR = GetEncounters(Encounter_FRLG, FR); + internal static readonly EncounterStatic3[] StaticLG = GetEncounters(Encounter_FRLG, LG); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters3GC.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters3GC.cs index d6eb9615c..05081146a 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters3GC.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters3GC.cs @@ -1,225 +1,224 @@ using static PKHeX.Core.Encounters3Teams; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class Encounters3GC { - internal static class Encounters3GC + #region Colosseum + + private static readonly EncounterStatic3[] Encounter_ColoGift = { - #region Colosseum + // Colosseum Starters: Gender locked to male + new(196, 25, GameVersion.COLO) { Gift = true, Location = 254, Gender = 0 }, // Espeon + new(197, 26, GameVersion.COLO) { Gift = true, Location = 254, Gender = 0, Moves = new[] {044} }, // Umbreon (Bite) + }; - private static readonly EncounterStatic3[] Encounter_ColoGift = - { - // Colosseum Starters: Gender locked to male - new(196, 25, GameVersion.COLO) { Gift = true, Location = 254, Gender = 0 }, // Espeon - new(197, 26, GameVersion.COLO) { Gift = true, Location = 254, Gender = 0, Moves = new[] {044} }, // Umbreon (Bite) - }; + private static readonly EncounterStaticShadow[] Encounter_Colo = + { + new(GameVersion.COLO, 01, 03000, ColoMakuhita) { Species = 296, Level = 30, Moves = new[] {193,116,233,238}, Location = 005 }, // Makuhita: Miror B.Peon Trudly @ Phenac City - private static readonly EncounterStaticShadow[] Encounter_Colo = - { - new(GameVersion.COLO, 01, 03000, ColoMakuhita) { Species = 296, Level = 30, Moves = new[] {193,116,233,238}, Location = 005 }, // Makuhita: Miror B.Peon Trudly @ Phenac City + new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new[] {241,235,075,034}, Location = 003 }, // Bayleef: Cipher Peon Verde @ Phenac City + new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new[] {241,235,075,034}, Location = 069 }, // Bayleef: Cipher Peon Verde @ Shadow PKMN Lab + new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new[] {241,235,075,034}, Location = 115 }, // Bayleef: Cipher Peon Verde @ Realgam Tower + new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new[] {241,235,075,034}, Location = 132 }, // Bayleef: Cipher Peon Verde @ Snagem Hideout + new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new[] {241,108,091,172}, Location = 003 }, // Quilava: Cipher Peon Rosso @ Phenac City + new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new[] {241,108,091,172}, Location = 069 }, // Quilava: Cipher Peon Rosso @ Shadow PKMN Lab + new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new[] {241,108,091,172}, Location = 115 }, // Quilava: Cipher Peon Rosso @ Realgam Tower + new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new[] {241,108,091,172}, Location = 132 }, // Quilava: Cipher Peon Rosso @ Snagem Hideout + new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new[] {240,184,044,057}, Location = 003 }, // Croconaw: Cipher Peon Bluno @ Phenac City + new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new[] {240,184,044,057}, Location = 069 }, // Croconaw: Cipher Peon Bluno @ Shadow PKMN Lab + new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new[] {240,184,044,057}, Location = 115 }, // Croconaw: Cipher Peon Bluno @ Realgam Tower + new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new[] {240,184,044,057}, Location = 132 }, // Croconaw: Cipher Peon Bluno @ Snagem Hideout + new(GameVersion.COLO, 05, 03000, First) { Species = 164, Level = 30, Moves = new[] {211,095,115,019}, Location = 015 }, // Noctowl: Rider Nover @ Pyrite Town + new(GameVersion.COLO, 06, 03000, First) { Species = 180, Level = 30, Moves = new[] {085,086,178,084}, Location = 015 }, // Flaaffy: St.Performer Diogo @ Pyrite Town + new(GameVersion.COLO, 07, 03000, First) { Species = 188, Level = 30, Moves = new[] {235,079,178,072}, Location = 015 }, // Skiploom: Rider Leba @ Pyrite Town + new(GameVersion.COLO, 08, 04000, First) { Species = 195, Level = 30, Moves = new[] {341,133,021,057}, Location = 015 }, // Quagsire: Bandana Guy Divel @ Pyrite Town + new(GameVersion.COLO, 09, 04000, First) { Species = 200, Level = 30, Moves = new[] {060,109,212,247}, Location = 015 }, // Misdreavus: Rider Vant @ Pyrite Town + new(GameVersion.COLO, 10, 05000, First) { Species = 193, Level = 33, Moves = new[] {197,048,049,253}, Location = 025 }, // Yanma: Cipher Peon Nore @ Pyrite Bldg + new(GameVersion.COLO, 10, 05000, First) { Species = 193, Level = 33, Moves = new[] {197,048,049,253}, Location = 132 }, // Yanma: Cipher Peon Nore @ Snagem Hideout + new(GameVersion.COLO, 11, 05000, First) { Species = 162, Level = 33, Moves = new[] {231,270,098,070}, Location = 015 }, // Furret: Rogue Cail @ Pyrite Town + new(GameVersion.COLO, 12, 04000, First) { Species = 218, Level = 30, Moves = new[] {241,281,088,053}, Location = 015 }, // Slugma: Roller Boy Lon @ Pyrite Town + new(GameVersion.COLO, 13, 04000, First) { Species = 223, Level = 20, Moves = new[] {061,199,060,062}, Location = 028 }, // Remoraid: Miror B.Peon Reath @ Pyrite Bldg + new(GameVersion.COLO, 13, 04000, First) { Species = 223, Level = 20, Moves = new[] {061,199,060,062}, Location = 030 }, // Remoraid: Miror B.Peon Reath @ Pyrite Cave + new(GameVersion.COLO, 14, 05000, First) { Species = 226, Level = 33, Moves = new[] {017,048,061,036}, Location = 028 }, // Mantine: Miror B.Peon Ferma @ Pyrite Bldg + new(GameVersion.COLO, 14, 05000, First) { Species = 226, Level = 33, Moves = new[] {017,048,061,036}, Location = 030 }, // Mantine: Miror B.Peon Ferma @ Pyrite Cave + new(GameVersion.COLO, 15, 05000, First) { Species = 211, Level = 33, Moves = new[] {042,107,040,057}, Location = 015 }, // Qwilfish: Hunter Doken @ Pyrite Bldg + new(GameVersion.COLO, 16, 05000, First) { Species = 307, Level = 33, Moves = new[] {197,347,093,136}, Location = 031 }, // Meditite: Rider Twan @ Pyrite Cave + new(GameVersion.COLO, 17, 05000, First) { Species = 206, Level = 33, Moves = new[] {180,137,281,036}, Location = 029 }, // Dunsparce: Rider Sosh @ Pyrite Cave + new(GameVersion.COLO, 18, 05000, First) { Species = 333, Level = 33, Moves = new[] {119,047,219,019}, Location = 032 }, // Swablu: Hunter Zalo @ Pyrite Cave + new(GameVersion.COLO, 19, 10000, First) { Species = 185, Level = 35, Moves = new[] {175,335,067,157}, Location = 104 }, // Sudowoodo: Cipher Admin Miror B. @ Realgam Tower + new(GameVersion.COLO, 19, 10000, First) { Species = 185, Level = 35, Moves = new[] {175,335,067,157}, Location = 125 }, // Sudowoodo: Cipher Admin Miror B. @ Deep Colosseum + new(GameVersion.COLO, 19, 10000, First) { Species = 185, Level = 35, Moves = new[] {175,335,067,157}, Location = 030 }, // Sudowoodo: Cipher Admin Miror B. @ Pyrite Cave + new(GameVersion.COLO, 20, 06000, First) { Species = 237, Level = 38, Moves = new[] {097,116,167,229}, Location = 039 }, // Hitmontop: Cipher Peon Skrub @ Agate Village + new(GameVersion.COLO, 20, 06000, First) { Species = 237, Level = 38, Moves = new[] {097,116,167,229}, Location = 132 }, // Hitmontop: Cipher Peon Skrub @ Snagem Hideout + new(GameVersion.COLO, 20, 06000, First) { Species = 237, Level = 38, Moves = new[] {097,116,167,229}, Location = 068 }, // Hitmontop: Cipher Peon Skrub @ Shadow PKMN Lab + new(GameVersion.COLO, 21, 13000, First) { Species = 244, Level = 40, Moves = new[] {241,043,044,126}, Location = 106 }, // Entei: Cipher Admin Dakim @ Realgam Tower + new(GameVersion.COLO, 21, 13000, First) { Species = 244, Level = 40, Moves = new[] {241,043,044,126}, Location = 125 }, // Entei: Cipher Admin Dakim @ Deep Colosseum + new(GameVersion.COLO, 21, 13000, First) { Species = 244, Level = 40, Moves = new[] {241,043,044,126}, Location = 076 }, // Entei: Cipher Admin Dakim @ Mt. Battle + new(GameVersion.COLO, 22, 06000, First) { Species = 166, Level = 40, Moves = new[] {226,219,048,004}, Location = 047 }, // Ledian: Cipher Peon Kloak @ The Under + new(GameVersion.COLO, 22, 06000, First) { Species = 166, Level = 40, Moves = new[] {226,219,048,004}, Location = 132 }, // Ledian: Cipher Peon Kloak @ Snagem Hideout + new(GameVersion.COLO, 23, 13000, First) { Species = 245, Level = 40, Moves = new[] {240,043,016,057}, Location = 110 }, // Suicune (Surf): Cipher Admin Venus @ Realgam Tower + new(GameVersion.COLO, 23, 13000, First) { Species = 245, Level = 40, Moves = new[] {240,043,016,056}, Location = 125 }, // Suicune (Hydro Pump): Cipher Admin Venus @ Deep Colosseum + new(GameVersion.COLO, 23, 13000, First) { Species = 245, Level = 40, Moves = new[] {240,043,016,057}, Location = 055 }, // Suicune (Surf): Cipher Admin Venus @ The Under + new(GameVersion.COLO, 24, 06000, Gligar) { Species = 207, Level = 43, Moves = new[] {185,028,040,163}, Location = 058 }, // Gligar: Hunter Frena @ The Under Subway + new(GameVersion.COLO, 24, 06000, Gligar) { Species = 207, Level = 43, Moves = new[] {185,028,040,163}, Location = 133 }, // Gligar: Hunter Frena @ Snagem Hideout + new(GameVersion.COLO, 25, 06000, First) { Species = 234, Level = 43, Moves = new[] {310,095,043,036}, Location = 058 }, // Stantler: Chaser Liaks @ The Under Subway + new(GameVersion.COLO, 25, 06000, First) { Species = 234, Level = 43, Moves = new[] {310,095,043,036}, Location = 133 }, // Stantler: Chaser Liaks @ Snagem Hideout + new(GameVersion.COLO, 25, 06000, First) { Species = 221, Level = 43, Moves = new[] {203,316,091,059}, Location = 058 }, // Piloswine: Bodybuilder Lonia @ The Under Subway + new(GameVersion.COLO, 26, 06000, First) { Species = 221, Level = 43, Moves = new[] {203,316,091,059}, Location = 134 }, // Piloswine: Bodybuilder Lonia @ Snagem Hideout + new(GameVersion.COLO, 27, 06000, First) { Species = 215, Level = 43, Moves = new[] {185,103,154,196}, Location = 058 }, // Sneasel: Rider Nelis @ The Under Subway + new(GameVersion.COLO, 27, 06000, First) { Species = 215, Level = 43, Moves = new[] {185,103,154,196}, Location = 134 }, // Sneasel: Rider Nelis @ Snagem Hideout + new(GameVersion.COLO, 28, 06000, First) { Species = 190, Level = 43, Moves = new[] {226,321,154,129}, Location = 067 }, // Aipom: Cipher Peon Cole @ Shadow PKMN Lab + new(GameVersion.COLO, 29, 06000, Murkrow) { Species = 198, Level = 43, Moves = new[] {185,212,101,019}, Location = 067 }, // Murkrow: Cipher Peon Lare @ Shadow PKMN Lab + new(GameVersion.COLO, 30, 06000, First) { Species = 205, Level = 43, Moves = new[] {153,182,117,229}, Location = 067 }, // Forretress: Cipher Peon Vana @ Shadow PKMN Lab + new(GameVersion.COLO, 31, 06000, First) { Species = 210, Level = 43, Moves = new[] {044,184,046,070}, Location = 069 }, // Granbull: Cipher Peon Tanie @ Shadow PKMN Lab + new(GameVersion.COLO, 32, 06000, First) { Species = 329, Level = 43, Moves = new[] {242,103,328,225}, Location = 068 }, // Vibrava: Cipher Peon Remil @ Shadow PKMN Lab + new(GameVersion.COLO, 33, 06000, First) { Species = 168, Level = 43, Moves = new[] {169,184,141,188}, Location = 069 }, // Ariados: Cipher Peon Lesar @ Shadow PKMN Lab - new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new[] {241,235,075,034}, Location = 003 }, // Bayleef: Cipher Peon Verde @ Phenac City - new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new[] {241,235,075,034}, Location = 069 }, // Bayleef: Cipher Peon Verde @ Shadow PKMN Lab - new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new[] {241,235,075,034}, Location = 115 }, // Bayleef: Cipher Peon Verde @ Realgam Tower - new(GameVersion.COLO, 02, 03000, First) { Species = 153, Level = 30, Moves = new[] {241,235,075,034}, Location = 132 }, // Bayleef: Cipher Peon Verde @ Snagem Hideout - new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new[] {241,108,091,172}, Location = 003 }, // Quilava: Cipher Peon Rosso @ Phenac City - new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new[] {241,108,091,172}, Location = 069 }, // Quilava: Cipher Peon Rosso @ Shadow PKMN Lab - new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new[] {241,108,091,172}, Location = 115 }, // Quilava: Cipher Peon Rosso @ Realgam Tower - new(GameVersion.COLO, 03, 03000, First) { Species = 156, Level = 30, Moves = new[] {241,108,091,172}, Location = 132 }, // Quilava: Cipher Peon Rosso @ Snagem Hideout - new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new[] {240,184,044,057}, Location = 003 }, // Croconaw: Cipher Peon Bluno @ Phenac City - new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new[] {240,184,044,057}, Location = 069 }, // Croconaw: Cipher Peon Bluno @ Shadow PKMN Lab - new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new[] {240,184,044,057}, Location = 115 }, // Croconaw: Cipher Peon Bluno @ Realgam Tower - new(GameVersion.COLO, 04, 03000, First) { Species = 159, Level = 30, Moves = new[] {240,184,044,057}, Location = 132 }, // Croconaw: Cipher Peon Bluno @ Snagem Hideout - new(GameVersion.COLO, 05, 03000, First) { Species = 164, Level = 30, Moves = new[] {211,095,115,019}, Location = 015 }, // Noctowl: Rider Nover @ Pyrite Town - new(GameVersion.COLO, 06, 03000, First) { Species = 180, Level = 30, Moves = new[] {085,086,178,084}, Location = 015 }, // Flaaffy: St.Performer Diogo @ Pyrite Town - new(GameVersion.COLO, 07, 03000, First) { Species = 188, Level = 30, Moves = new[] {235,079,178,072}, Location = 015 }, // Skiploom: Rider Leba @ Pyrite Town - new(GameVersion.COLO, 08, 04000, First) { Species = 195, Level = 30, Moves = new[] {341,133,021,057}, Location = 015 }, // Quagsire: Bandana Guy Divel @ Pyrite Town - new(GameVersion.COLO, 09, 04000, First) { Species = 200, Level = 30, Moves = new[] {060,109,212,247}, Location = 015 }, // Misdreavus: Rider Vant @ Pyrite Town - new(GameVersion.COLO, 10, 05000, First) { Species = 193, Level = 33, Moves = new[] {197,048,049,253}, Location = 025 }, // Yanma: Cipher Peon Nore @ Pyrite Bldg - new(GameVersion.COLO, 10, 05000, First) { Species = 193, Level = 33, Moves = new[] {197,048,049,253}, Location = 132 }, // Yanma: Cipher Peon Nore @ Snagem Hideout - new(GameVersion.COLO, 11, 05000, First) { Species = 162, Level = 33, Moves = new[] {231,270,098,070}, Location = 015 }, // Furret: Rogue Cail @ Pyrite Town - new(GameVersion.COLO, 12, 04000, First) { Species = 218, Level = 30, Moves = new[] {241,281,088,053}, Location = 015 }, // Slugma: Roller Boy Lon @ Pyrite Town - new(GameVersion.COLO, 13, 04000, First) { Species = 223, Level = 20, Moves = new[] {061,199,060,062}, Location = 028 }, // Remoraid: Miror B.Peon Reath @ Pyrite Bldg - new(GameVersion.COLO, 13, 04000, First) { Species = 223, Level = 20, Moves = new[] {061,199,060,062}, Location = 030 }, // Remoraid: Miror B.Peon Reath @ Pyrite Cave - new(GameVersion.COLO, 14, 05000, First) { Species = 226, Level = 33, Moves = new[] {017,048,061,036}, Location = 028 }, // Mantine: Miror B.Peon Ferma @ Pyrite Bldg - new(GameVersion.COLO, 14, 05000, First) { Species = 226, Level = 33, Moves = new[] {017,048,061,036}, Location = 030 }, // Mantine: Miror B.Peon Ferma @ Pyrite Cave - new(GameVersion.COLO, 15, 05000, First) { Species = 211, Level = 33, Moves = new[] {042,107,040,057}, Location = 015 }, // Qwilfish: Hunter Doken @ Pyrite Bldg - new(GameVersion.COLO, 16, 05000, First) { Species = 307, Level = 33, Moves = new[] {197,347,093,136}, Location = 031 }, // Meditite: Rider Twan @ Pyrite Cave - new(GameVersion.COLO, 17, 05000, First) { Species = 206, Level = 33, Moves = new[] {180,137,281,036}, Location = 029 }, // Dunsparce: Rider Sosh @ Pyrite Cave - new(GameVersion.COLO, 18, 05000, First) { Species = 333, Level = 33, Moves = new[] {119,047,219,019}, Location = 032 }, // Swablu: Hunter Zalo @ Pyrite Cave - new(GameVersion.COLO, 19, 10000, First) { Species = 185, Level = 35, Moves = new[] {175,335,067,157}, Location = 104 }, // Sudowoodo: Cipher Admin Miror B. @ Realgam Tower - new(GameVersion.COLO, 19, 10000, First) { Species = 185, Level = 35, Moves = new[] {175,335,067,157}, Location = 125 }, // Sudowoodo: Cipher Admin Miror B. @ Deep Colosseum - new(GameVersion.COLO, 19, 10000, First) { Species = 185, Level = 35, Moves = new[] {175,335,067,157}, Location = 030 }, // Sudowoodo: Cipher Admin Miror B. @ Pyrite Cave - new(GameVersion.COLO, 20, 06000, First) { Species = 237, Level = 38, Moves = new[] {097,116,167,229}, Location = 039 }, // Hitmontop: Cipher Peon Skrub @ Agate Village - new(GameVersion.COLO, 20, 06000, First) { Species = 237, Level = 38, Moves = new[] {097,116,167,229}, Location = 132 }, // Hitmontop: Cipher Peon Skrub @ Snagem Hideout - new(GameVersion.COLO, 20, 06000, First) { Species = 237, Level = 38, Moves = new[] {097,116,167,229}, Location = 068 }, // Hitmontop: Cipher Peon Skrub @ Shadow PKMN Lab - new(GameVersion.COLO, 21, 13000, First) { Species = 244, Level = 40, Moves = new[] {241,043,044,126}, Location = 106 }, // Entei: Cipher Admin Dakim @ Realgam Tower - new(GameVersion.COLO, 21, 13000, First) { Species = 244, Level = 40, Moves = new[] {241,043,044,126}, Location = 125 }, // Entei: Cipher Admin Dakim @ Deep Colosseum - new(GameVersion.COLO, 21, 13000, First) { Species = 244, Level = 40, Moves = new[] {241,043,044,126}, Location = 076 }, // Entei: Cipher Admin Dakim @ Mt. Battle - new(GameVersion.COLO, 22, 06000, First) { Species = 166, Level = 40, Moves = new[] {226,219,048,004}, Location = 047 }, // Ledian: Cipher Peon Kloak @ The Under - new(GameVersion.COLO, 22, 06000, First) { Species = 166, Level = 40, Moves = new[] {226,219,048,004}, Location = 132 }, // Ledian: Cipher Peon Kloak @ Snagem Hideout - new(GameVersion.COLO, 23, 13000, First) { Species = 245, Level = 40, Moves = new[] {240,043,016,057}, Location = 110 }, // Suicune (Surf): Cipher Admin Venus @ Realgam Tower - new(GameVersion.COLO, 23, 13000, First) { Species = 245, Level = 40, Moves = new[] {240,043,016,056}, Location = 125 }, // Suicune (Hydro Pump): Cipher Admin Venus @ Deep Colosseum - new(GameVersion.COLO, 23, 13000, First) { Species = 245, Level = 40, Moves = new[] {240,043,016,057}, Location = 055 }, // Suicune (Surf): Cipher Admin Venus @ The Under - new(GameVersion.COLO, 24, 06000, Gligar) { Species = 207, Level = 43, Moves = new[] {185,028,040,163}, Location = 058 }, // Gligar: Hunter Frena @ The Under Subway - new(GameVersion.COLO, 24, 06000, Gligar) { Species = 207, Level = 43, Moves = new[] {185,028,040,163}, Location = 133 }, // Gligar: Hunter Frena @ Snagem Hideout - new(GameVersion.COLO, 25, 06000, First) { Species = 234, Level = 43, Moves = new[] {310,095,043,036}, Location = 058 }, // Stantler: Chaser Liaks @ The Under Subway - new(GameVersion.COLO, 25, 06000, First) { Species = 234, Level = 43, Moves = new[] {310,095,043,036}, Location = 133 }, // Stantler: Chaser Liaks @ Snagem Hideout - new(GameVersion.COLO, 25, 06000, First) { Species = 221, Level = 43, Moves = new[] {203,316,091,059}, Location = 058 }, // Piloswine: Bodybuilder Lonia @ The Under Subway - new(GameVersion.COLO, 26, 06000, First) { Species = 221, Level = 43, Moves = new[] {203,316,091,059}, Location = 134 }, // Piloswine: Bodybuilder Lonia @ Snagem Hideout - new(GameVersion.COLO, 27, 06000, First) { Species = 215, Level = 43, Moves = new[] {185,103,154,196}, Location = 058 }, // Sneasel: Rider Nelis @ The Under Subway - new(GameVersion.COLO, 27, 06000, First) { Species = 215, Level = 43, Moves = new[] {185,103,154,196}, Location = 134 }, // Sneasel: Rider Nelis @ Snagem Hideout - new(GameVersion.COLO, 28, 06000, First) { Species = 190, Level = 43, Moves = new[] {226,321,154,129}, Location = 067 }, // Aipom: Cipher Peon Cole @ Shadow PKMN Lab - new(GameVersion.COLO, 29, 06000, Murkrow) { Species = 198, Level = 43, Moves = new[] {185,212,101,019}, Location = 067 }, // Murkrow: Cipher Peon Lare @ Shadow PKMN Lab - new(GameVersion.COLO, 30, 06000, First) { Species = 205, Level = 43, Moves = new[] {153,182,117,229}, Location = 067 }, // Forretress: Cipher Peon Vana @ Shadow PKMN Lab - new(GameVersion.COLO, 31, 06000, First) { Species = 210, Level = 43, Moves = new[] {044,184,046,070}, Location = 069 }, // Granbull: Cipher Peon Tanie @ Shadow PKMN Lab - new(GameVersion.COLO, 32, 06000, First) { Species = 329, Level = 43, Moves = new[] {242,103,328,225}, Location = 068 }, // Vibrava: Cipher Peon Remil @ Shadow PKMN Lab - new(GameVersion.COLO, 33, 06000, First) { Species = 168, Level = 43, Moves = new[] {169,184,141,188}, Location = 069 }, // Ariados: Cipher Peon Lesar @ Shadow PKMN Lab + new(GameVersion.COLO, 34, 13000, First) { Species = 243, Level = 40, Moves = new[] {240,043,098,087}, Location = 113 }, // Raikou: Cipher Admin Ein @ Realgam Tower + new(GameVersion.COLO, 34, 13000, First) { Species = 243, Level = 40, Moves = new[] {240,043,098,087}, Location = 125 }, // Raikou: Cipher Admin Ein @ Deep Colosseum + new(GameVersion.COLO, 34, 13000, First) { Species = 243, Level = 40, Moves = new[] {240,043,098,087}, Location = 069 }, // Raikou: Cipher Admin Ein @ Shadow PKMN Lab - new(GameVersion.COLO, 34, 13000, First) { Species = 243, Level = 40, Moves = new[] {240,043,098,087}, Location = 113 }, // Raikou: Cipher Admin Ein @ Realgam Tower - new(GameVersion.COLO, 34, 13000, First) { Species = 243, Level = 40, Moves = new[] {240,043,098,087}, Location = 125 }, // Raikou: Cipher Admin Ein @ Deep Colosseum - new(GameVersion.COLO, 34, 13000, First) { Species = 243, Level = 40, Moves = new[] {240,043,098,087}, Location = 069 }, // Raikou: Cipher Admin Ein @ Shadow PKMN Lab + new(GameVersion.COLO, 35, 07000, First) { Species = 192, Level = 45, Moves = new[] {241,074,275,076}, Location = 109 }, // Sunflora: Cipher Peon Baila @ Realgam Tower + new(GameVersion.COLO, 35, 07000, First) { Species = 192, Level = 45, Moves = new[] {241,074,275,076}, Location = 132 }, // Sunflora: Cipher Peon Baila @ Snagem Hideout + new(GameVersion.COLO, 36, 07000, First) { Species = 225, Level = 45, Moves = new[] {059,213,217,019}, Location = 109 }, // Delibird: Cipher Peon Arton @ Realgam Tower + new(GameVersion.COLO, 36, 07000, First) { Species = 225, Level = 45, Moves = new[] {059,213,217,019}, Location = 132 }, // Delibird: Cipher Peon Arton @ Snagem Hideout + new(GameVersion.COLO, 37, 07000, Heracross) { Species = 214, Level = 45, Moves = new[] {179,203,068,280}, Location = 111 }, // Heracross: Cipher Peon Dioge @ Realgam Tower + new(GameVersion.COLO, 37, 07000, Heracross) { Species = 214, Level = 45, Moves = new[] {179,203,068,280}, Location = 132 }, // Heracross: Cipher Peon Dioge @ Snagem Hideout + new(GameVersion.COLO, 38, 13000, First) { Species = 227, Level = 47, Moves = new[] {065,319,314,211}, Location = 117 }, // Skarmory: Snagem Head Gonzap @ Realgam Tower + new(GameVersion.COLO, 38, 13000, First) { Species = 227, Level = 47, Moves = new[] {065,319,314,211}, Location = 133 }, // Skarmory: Snagem Head Gonzap @ Snagem Hideout - new(GameVersion.COLO, 35, 07000, First) { Species = 192, Level = 45, Moves = new[] {241,074,275,076}, Location = 109 }, // Sunflora: Cipher Peon Baila @ Realgam Tower - new(GameVersion.COLO, 35, 07000, First) { Species = 192, Level = 45, Moves = new[] {241,074,275,076}, Location = 132 }, // Sunflora: Cipher Peon Baila @ Snagem Hideout - new(GameVersion.COLO, 36, 07000, First) { Species = 225, Level = 45, Moves = new[] {059,213,217,019}, Location = 109 }, // Delibird: Cipher Peon Arton @ Realgam Tower - new(GameVersion.COLO, 36, 07000, First) { Species = 225, Level = 45, Moves = new[] {059,213,217,019}, Location = 132 }, // Delibird: Cipher Peon Arton @ Snagem Hideout - new(GameVersion.COLO, 37, 07000, Heracross) { Species = 214, Level = 45, Moves = new[] {179,203,068,280}, Location = 111 }, // Heracross: Cipher Peon Dioge @ Realgam Tower - new(GameVersion.COLO, 37, 07000, Heracross) { Species = 214, Level = 45, Moves = new[] {179,203,068,280}, Location = 132 }, // Heracross: Cipher Peon Dioge @ Snagem Hideout - new(GameVersion.COLO, 38, 13000, First) { Species = 227, Level = 47, Moves = new[] {065,319,314,211}, Location = 117 }, // Skarmory: Snagem Head Gonzap @ Realgam Tower - new(GameVersion.COLO, 38, 13000, First) { Species = 227, Level = 47, Moves = new[] {065,319,314,211}, Location = 133 }, // Skarmory: Snagem Head Gonzap @ Snagem Hideout + new(GameVersion.COLO, 39, 07000, First) { Species = 241, Level = 48, Moves = new[] {208,111,205,034}, Location = 118 }, // Miltank: Bodybuilder Jomas @ Tower Colosseum + new(GameVersion.COLO, 40, 07000, First) { Species = 359, Level = 48, Moves = new[] {195,014,163,185}, Location = 118 }, // Absol: Rider Delan @ Tower Colosseum + new(GameVersion.COLO, 41, 07000, First) { Species = 229, Level = 48, Moves = new[] {185,336,123,053}, Location = 118 }, // Houndoom: Cipher Peon Nella @ Tower Colosseum + new(GameVersion.COLO, 42, 07000, First) { Species = 357, Level = 49, Moves = new[] {076,235,345,019}, Location = 118 }, // Tropius: Cipher Peon Ston @ Tower Colosseum + new(GameVersion.COLO, 43, 15000, First) { Species = 376, Level = 50, Moves = new[] {063,334,232,094}, Location = 118 }, // Metagross: Cipher Nascour @ Tower Colosseum + new(GameVersion.COLO, 44, 20000, First) { Species = 248, Level = 55, Moves = new[] {242,087,157,059}, Location = 118 }, // Tyranitar: Cipher Head Evice @ Tower Colosseum - new(GameVersion.COLO, 39, 07000, First) { Species = 241, Level = 48, Moves = new[] {208,111,205,034}, Location = 118 }, // Miltank: Bodybuilder Jomas @ Tower Colosseum - new(GameVersion.COLO, 40, 07000, First) { Species = 359, Level = 48, Moves = new[] {195,014,163,185}, Location = 118 }, // Absol: Rider Delan @ Tower Colosseum - new(GameVersion.COLO, 41, 07000, First) { Species = 229, Level = 48, Moves = new[] {185,336,123,053}, Location = 118 }, // Houndoom: Cipher Peon Nella @ Tower Colosseum - new(GameVersion.COLO, 42, 07000, First) { Species = 357, Level = 49, Moves = new[] {076,235,345,019}, Location = 118 }, // Tropius: Cipher Peon Ston @ Tower Colosseum - new(GameVersion.COLO, 43, 15000, First) { Species = 376, Level = 50, Moves = new[] {063,334,232,094}, Location = 118 }, // Metagross: Cipher Nascour @ Tower Colosseum - new(GameVersion.COLO, 44, 20000, First) { Species = 248, Level = 55, Moves = new[] {242,087,157,059}, Location = 118 }, // Tyranitar: Cipher Head Evice @ Tower Colosseum + new(GameVersion.COLO, 55, 07000, First) { Species = 235, Level = 45, Moves = new[] {166,039,003,231}, Location = 132 }, // Smeargle: Team Snagem Biden @ Snagem Hideout + new(GameVersion.COLO, 56, 07000, Ursaring) { Species = 217, Level = 45, Moves = new[] {185,313,122,163}, Location = 132 }, // Ursaring: Team Snagem Agrev @ Snagem Hideout + new(GameVersion.COLO, 57, 07000, First) { Species = 213, Level = 45, Moves = new[] {219,227,156,117}, Location = 125 }, // Shuckle: Deep King Agnol @ Deep Colosseum - new(GameVersion.COLO, 55, 07000, First) { Species = 235, Level = 45, Moves = new[] {166,039,003,231}, Location = 132 }, // Smeargle: Team Snagem Biden @ Snagem Hideout - new(GameVersion.COLO, 56, 07000, Ursaring) { Species = 217, Level = 45, Moves = new[] {185,313,122,163}, Location = 132 }, // Ursaring: Team Snagem Agrev @ Snagem Hideout - new(GameVersion.COLO, 57, 07000, First) { Species = 213, Level = 45, Moves = new[] {219,227,156,117}, Location = 125 }, // Shuckle: Deep King Agnol @ Deep Colosseum + new(GameVersion.COLO, 67, 05000, First) { Species = 176, Level = 20, Moves = new[] {118,204,186,281}, Location = 001 }, // Togetic: Cipher Peon Fein @ Outskirt Stand - new(GameVersion.COLO, 67, 05000, First) { Species = 176, Level = 20, Moves = new[] {118,204,186,281}, Location = 001 }, // Togetic: Cipher Peon Fein @ Outskirt Stand + new(GameVersion.COLO, 00, 00000, CTogepi) { Species = 175, Level = 20, Moves = new[] {118,204,186,281}, Location = 128, IVs = EncounterStaticShadow.EReaderEmpty }, // Togepi: Chaser ボデス @ Card e Room (Japanese games only) + new(GameVersion.COLO, 00, 00000, CMareep) { Species = 179, Level = 37, Moves = new[] {087,084,086,178}, Location = 128, IVs = EncounterStaticShadow.EReaderEmpty }, // Mareep: Hunter ホル @ Card e Room (Japanese games only) + new(GameVersion.COLO, 00, 00000, CScizor) { Species = 212, Level = 50, Moves = new[] {210,232,014,163}, Location = 128, IVs = EncounterStaticShadow.EReaderEmpty }, // Scizor: Bodybuilder ワーバン @ Card e Room (Japanese games only) + }; + #endregion - new(GameVersion.COLO, 00, 00000, CTogepi) { Species = 175, Level = 20, Moves = new[] {118,204,186,281}, Location = 128, IVs = EncounterStaticShadow.EReaderEmpty }, // Togepi: Chaser ボデス @ Card e Room (Japanese games only) - new(GameVersion.COLO, 00, 00000, CMareep) { Species = 179, Level = 37, Moves = new[] {087,084,086,178}, Location = 128, IVs = EncounterStaticShadow.EReaderEmpty }, // Mareep: Hunter ホル @ Card e Room (Japanese games only) - new(GameVersion.COLO, 00, 00000, CScizor) { Species = 212, Level = 50, Moves = new[] {210,232,014,163}, Location = 128, IVs = EncounterStaticShadow.EReaderEmpty }, // Scizor: Bodybuilder ワーバン @ Card e Room (Japanese games only) - }; - #endregion + #region XD - #region XD + private static readonly EncounterStatic3[] Encounter_XDGift = + { + new(133, 10, GameVersion.XD) { Fateful = true, Gift = true, Location = 000, Moves = new[] {044} }, // Eevee (Bite) + new(152, 05, GameVersion.XD) { Fateful = true, Gift = true, Location = 016, Moves = new[] {246,033,045,338} }, // Chikorita + new(155, 05, GameVersion.XD) { Fateful = true, Gift = true, Location = 016, Moves = new[] {179,033,043,307} }, // Cyndaquil + new(158, 05, GameVersion.XD) { Fateful = true, Gift = true, Location = 016, Moves = new[] {242,010,043,308} }, // Totodile + }; - private static readonly EncounterStatic3[] Encounter_XDGift = - { - new(133, 10, GameVersion.XD) { Fateful = true, Gift = true, Location = 000, Moves = new[] {044} }, // Eevee (Bite) - new(152, 05, GameVersion.XD) { Fateful = true, Gift = true, Location = 016, Moves = new[] {246,033,045,338} }, // Chikorita - new(155, 05, GameVersion.XD) { Fateful = true, Gift = true, Location = 016, Moves = new[] {179,033,043,307} }, // Cyndaquil - new(158, 05, GameVersion.XD) { Fateful = true, Gift = true, Location = 016, Moves = new[] {242,010,043,308} }, // Totodile - }; + private static readonly EncounterStaticShadow[] Encounter_XD = + { + new(GameVersion.XD, 01, 03000, First) { Fateful = true, Species = 216, Level = 11, Moves = new[] {216,287,122,232}, Location = 143, Gift = true }, // Teddiursa: Cipher Peon Naps @ Pokémon HQ Lab -- treat as Gift as it can only be captured in a Poké Ball + new(GameVersion.XD, 02, 02000, Vulpix) { Fateful = true, Species = 037, Level = 18, Moves = new[] {257,204,052,091}, Location = 109 }, // Vulpix: Cipher Peon Mesin @ ONBS Building + new(GameVersion.XD, 03, 01500, Spheal) { Fateful = true, Species = 363, Level = 17, Moves = new[] {062,204,055,189}, Location = 011 }, // Spheal: Cipher Peon Blusix @ Cipher Lab + new(GameVersion.XD, 03, 01500, Spheal) { Fateful = true, Species = 363, Level = 17, Moves = new[] {062,204,055,189}, Location = 100 }, // Spheal: Cipher Peon Blusix @ Phenac City + new(GameVersion.XD, 04, 01500, First) { Fateful = true, Species = 343, Level = 17, Moves = new[] {317,287,189,060}, Location = 011 }, // Baltoy: Cipher Peon Browsix @ Cipher Lab + new(GameVersion.XD, 04, 01500, First) { Fateful = true, Species = 343, Level = 17, Moves = new[] {317,287,189,060}, Location = 096 }, // Baltoy: Cipher Peon Browsix @ Phenac City + new(GameVersion.XD, 05, 01500, First) { Fateful = true, Species = 179, Level = 17, Moves = new[] {034,215,084,086}, Location = 011 }, // Mareep: Cipher Peon Yellosix @ Cipher Lab + new(GameVersion.XD, 05, 01500, First) { Fateful = true, Species = 179, Level = 17, Moves = new[] {034,215,084,086}, Location = 096 }, // Mareep: Cipher Peon Yellosix @ Phenac City + new(GameVersion.XD, 06, 01500, Gulpin) { Fateful = true, Species = 316, Level = 17, Moves = new[] {351,047,124,092}, Location = 011 }, // Gulpin: Cipher Peon Purpsix @ Cipher Lab + new(GameVersion.XD, 06, 01500, Gulpin) { Fateful = true, Species = 316, Level = 17, Moves = new[] {351,047,124,092}, Location = 100 }, // Gulpin: Cipher Peon Purpsix @ Phenac City + new(GameVersion.XD, 07, 01500, Seedot) { Fateful = true, Species = 273, Level = 17, Moves = new[] {202,287,331,290}, Location = 011 }, // Seedot: Cipher Peon Greesix @ Cipher Lab + new(GameVersion.XD, 07, 01500, Seedot) { Fateful = true, Species = 273, Level = 17, Moves = new[] {202,287,331,290}, Location = 100 }, // Seedot: Cipher Peon Greesix @ Phenac City + new(GameVersion.XD, 08, 01500, Spinarak) { Fateful = true, Species = 167, Level = 14, Moves = new[] {091,287,324,101}, Location = 010 }, // Spinarak: Cipher Peon Nexir @ Cipher Lab + new(GameVersion.XD, 09, 01500, Numel) { Fateful = true, Species = 322, Level = 14, Moves = new[] {036,204,091,052}, Location = 009 }, // Numel: Cipher Peon Solox @ Cipher Lab + new(GameVersion.XD, 10, 01700, First) { Fateful = true, Species = 318, Level = 15, Moves = new[] {352,287,184,044}, Location = 008 }, // Carvanha: Cipher Peon Cabol @ Cipher Lab + new(GameVersion.XD, 11, 03000, Roselia) { Fateful = true, Species = 315, Level = 22, Moves = new[] {345,186,320,073}, Location = 094 }, // Roselia: Cipher Peon Fasin @ Phenac City + new(GameVersion.XD, 12, 02500, Delcatty) { Fateful = true, Species = 301, Level = 18, Moves = new[] {290,186,213,351}, Location = 008 }, // Delcatty: Cipher Admin Lovrina @ Cipher Lab + new(GameVersion.XD, 13, 04000, Nosepass) { Fateful = true, Species = 299, Level = 26, Moves = new[] {085,270,086,157}, Location = 090 }, // Nosepass: Wanderer Miror B. @ Poké Spots + new(GameVersion.XD, 14, 01500, First) { Fateful = true, Species = 228, Level = 17, Moves = new[] {185,204,052,046}, Location = 100 }, // Houndour: Cipher Peon Resix @ Phenac City + new(GameVersion.XD, 14, 01500, First) { Fateful = true, Species = 228, Level = 17, Moves = new[] {185,204,052,046}, Location = 011 }, // Houndour: Cipher Peon Resix @ Cipher Lab + new(GameVersion.XD, 15, 02000, Makuhita) { Fateful = true, Species = 296, Level = 18, Moves = new[] {280,287,292,317}, Location = 109 }, // Makuhita: Cipher Peon Torkin @ ONBS Building + new(GameVersion.XD, 16, 02200, Duskull) { Fateful = true, Species = 355, Level = 19, Moves = new[] {247,270,310,109}, Location = 110 }, // Duskull: Cipher Peon Lobar @ ONBS Building + new(GameVersion.XD, 17, 02200, Ralts) { Fateful = true, Species = 280, Level = 20, Moves = new[] {351,047,115,093}, Location = 119 }, // Ralts: Cipher Peon Feldas @ ONBS Building + new(GameVersion.XD, 18, 02500, Mawile) { Fateful = true, Species = 303, Level = 22, Moves = new[] {206,047,011,334}, Location = 111 }, // Mawile: Cipher Cmdr Exol @ ONBS Building + new(GameVersion.XD, 19, 02500, Snorunt) { Fateful = true, Species = 361, Level = 20, Moves = new[] {352,047,044,196}, Location = 097 }, // Snorunt: Cipher Peon Exinn @ Phenac City + new(GameVersion.XD, 20, 02500, Pineco) { Fateful = true, Species = 204, Level = 20, Moves = new[] {042,287,191,068}, Location = 096 }, // Pineco: Cipher Peon Gonrap @ Phenac City + new(GameVersion.XD, 21, 02500, Swinub) { Fateful = true, Species = 220, Level = 22, Moves = new[] {246,204,054,341}, Location = 100 }, // Swinub: Cipher Peon Greck @ Phenac City + new(GameVersion.XD, 22, 02500, Natu) { Fateful = true, Species = 177, Level = 22, Moves = new[] {248,226,101,332}, Location = 094 }, // Natu: Cipher Peon Eloin @ Phenac City + new(GameVersion.XD, 23, 01800, Shroomish) { Fateful = true, Species = 285, Level = 15, Moves = new[] {206,287,072,078}, Location = 008 }, // Shroomish: Cipher R&D Klots @ Cipher Lab + new(GameVersion.XD, 24, 03500, Meowth) { Fateful = true, Species = 052, Level = 22, Moves = new[] {163,047,006,044}, Location = 094 }, // Meowth: Cipher Peon Fostin @ Phenac City + new(GameVersion.XD, 25, 04500, Spearow) { Fateful = true, Species = 021, Level = 22, Moves = new[] {206,226,043,332}, Location = 107 }, // Spearow: Cipher Peon Ezin @ Phenac Stadium + new(GameVersion.XD, 26, 03000, Grimer) { Fateful = true, Species = 088, Level = 23, Moves = new[] {188,270,325,107}, Location = 107 }, // Grimer: Cipher Peon Faltly @ Phenac Stadium + new(GameVersion.XD, 27, 03500, Seel) { Fateful = true, Species = 086, Level = 23, Moves = new[] {057,270,219,058}, Location = 107 }, // Seel: Cipher Peon Egrog @ Phenac Stadium + new(GameVersion.XD, 28, 05000, Lunatone) { Fateful = true, Species = 337, Level = 25, Moves = new[] {094,226,240,317}, Location = 107 }, // Lunatone: Cipher Admin Snattle @ Phenac Stadium + new(GameVersion.XD, 29, 02500, Voltorb) { Fateful = true, Species = 100, Level = 19, Moves = new[] {243,287,209,129}, Location = 092 }, // Voltorb: Wanderer Miror B. @ Cave Poké Spot + new(GameVersion.XD, 30, 05000, First) { Fateful = true, Species = 335, Level = 28, Moves = new[] {280,287,068,306}, Location = 071 }, // Zangoose: Thug Zook @ Cipher Key Lair + new(GameVersion.XD, 31, 04000, Growlithe) { Fateful = true, Species = 058, Level = 28, Moves = new[] {053,204,044,036}, Location = 064 }, // Growlithe: Cipher Peon Humah @ Cipher Key Lair + new(GameVersion.XD, 32, 04000, Paras) { Fateful = true, Species = 046, Level = 28, Moves = new[] {147,287,163,206}, Location = 064 }, // Paras: Cipher Peon Humah @ Cipher Key Lair + new(GameVersion.XD, 33, 04000, First) { Fateful = true, Species = 090, Level = 29, Moves = new[] {036,287,057,062}, Location = 065 }, // Shellder: Cipher Peon Gorog @ Cipher Key Lair + new(GameVersion.XD, 34, 04500, First) { Fateful = true, Species = 015, Level = 30, Moves = new[] {188,226,041,014}, Location = 066 }, // Beedrill: Cipher Peon Lok @ Cipher Key Lair + new(GameVersion.XD, 35, 04000, Pidgeotto) { Fateful = true, Species = 017, Level = 30, Moves = new[] {017,287,211,297}, Location = 066 }, // Pidgeotto: Cipher Peon Lok @ Cipher Key Lair + new(GameVersion.XD, 36, 04000, Butterfree){ Fateful = true, Species = 012, Level = 30, Moves = new[] {094,234,079,332}, Location = 067 }, // Butterfree: Cipher Peon Targ @ Cipher Key Lair + new(GameVersion.XD, 37, 04000, Tangela) { Fateful = true, Species = 114, Level = 30, Moves = new[] {076,234,241,275}, Location = 067 }, // Tangela: Cipher Peon Targ @ Cipher Key Lair + new(GameVersion.XD, 38, 06000, Raticate) { Fateful = true, Species = 020, Level = 34, Moves = new[] {162,287,184,158}, Location = 076 }, // Raticate: Chaser Furgy @ Citadark Isle + new(GameVersion.XD, 39, 04000, Venomoth) { Fateful = true, Species = 049, Level = 32, Moves = new[] {318,287,164,094}, Location = 070 }, // Venomoth: Cipher Peon Angic @ Cipher Key Lair + new(GameVersion.XD, 40, 04000, Weepinbell){ Fateful = true, Species = 070, Level = 32, Moves = new[] {345,234,188,230}, Location = 070 }, // Weepinbell: Cipher Peon Angic @ Cipher Key Lair + new(GameVersion.XD, 41, 05000, Arbok) { Fateful = true, Species = 024, Level = 33, Moves = new[] {188,287,137,044}, Location = 070 }, // Arbok: Cipher Peon Smarton @ Cipher Key Lair + new(GameVersion.XD, 42, 06000, Primeape) { Fateful = true, Species = 057, Level = 34, Moves = new[] {238,270,116,179}, Location = 069 }, // Primeape: Cipher Admin Gorigan @ Cipher Key Lair + new(GameVersion.XD, 43, 05500, Hypno) { Fateful = true, Species = 097, Level = 34, Moves = new[] {094,226,096,247}, Location = 069 }, // Hypno: Cipher Admin Gorigan @ Cipher Key Lair + new(GameVersion.XD, 44, 06500, Golduck) { Fateful = true, Species = 055, Level = 33, Moves = new[] {127,204,244,280}, Location = 088 }, // Golduck: Navigator Abson @ Citadark Isle + new(GameVersion.XD, 45, 07000, Sableye) { Fateful = true, Species = 302, Level = 33, Moves = new[] {247,270,185,105}, Location = 088 }, // Sableye: Navigator Abson @ Citadark Isle + new(GameVersion.XD, 46, 04500, Magneton) { Fateful = true, Species = 082, Level = 30, Moves = new[] {038,287,240,087}, Location = 067 }, // Magneton: Cipher Peon Snidle @ Cipher Key Lair + new(GameVersion.XD, 47, 08000, Dodrio) { Fateful = true, Species = 085, Level = 34, Moves = new[] {065,226,097,161}, Location = 076 }, // Dodrio: Chaser Furgy @ Citadark Isle + new(GameVersion.XD, 48, 05500, Farfetchd) { Fateful = true, Species = 083, Level = 36, Moves = new[] {163,226,014,332}, Location = 076 }, // Farfetch'd: Cipher Admin Lovrina @ Citadark Isle + new(GameVersion.XD, 49, 06500, Altaria) { Fateful = true, Species = 334, Level = 36, Moves = new[] {225,215,076,332}, Location = 076 }, // Altaria: Cipher Admin Lovrina @ Citadark Isle + new(GameVersion.XD, 50, 06000, Kangaskhan){ Fateful = true, Species = 115, Level = 35, Moves = new[] {089,047,039,146}, Location = 085 }, // Kangaskhan: Cipher Peon Litnar @ Citadark Isle + new(GameVersion.XD, 51, 07000, Banette) { Fateful = true, Species = 354, Level = 37, Moves = new[] {185,270,247,174}, Location = 085 }, // Banette: Cipher Peon Litnar @ Citadark Isle + new(GameVersion.XD, 52, 07000, Magmar) { Fateful = true, Species = 126, Level = 36, Moves = new[] {126,266,238,009}, Location = 077 }, // Magmar: Cipher Peon Grupel @ Citadark Isle + new(GameVersion.XD, 53, 07000, Pinsir) { Fateful = true, Species = 127, Level = 35, Moves = new[] {012,270,206,066}, Location = 077 }, // Pinsir: Cipher Peon Grupel @ Citadark Isle + new(GameVersion.XD, 54, 05500, Magcargo) { Fateful = true, Species = 219, Level = 38, Moves = new[] {257,287,089,053}, Location = 080 }, // Magcargo: Cipher Peon Kolest @ Citadark Isle + new(GameVersion.XD, 55, 06000, Rapidash) { Fateful = true, Species = 078, Level = 40, Moves = new[] {076,226,241,053}, Location = 080 }, // Rapidash: Cipher Peon Kolest @ Citadark Isle + new(GameVersion.XD, 56, 06000, Hitmonchan){ Fateful = true, Species = 107, Level = 38, Moves = new[] {005,270,170,327}, Location = 081 }, // Hitmonchan: Cipher Peon Karbon @ Citadark Isle + new(GameVersion.XD, 57, 07000, Hitmonlee) { Fateful = true, Species = 106, Level = 38, Moves = new[] {136,287,170,025}, Location = 081 }, // Hitmonlee: Cipher Peon Petro @ Citadark Isle + new(GameVersion.XD, 58, 05000, Lickitung) { Fateful = true, Species = 108, Level = 38, Moves = new[] {038,270,111,205}, Location = 084 }, // Lickitung: Cipher Peon Geftal @ Citadark Isle + new(GameVersion.XD, 59, 08000, Scyther) { Fateful = true, Species = 123, Level = 40, Moves = new[] {013,234,318,163}, Location = 084 }, // Scyther: Cipher Peon Leden @ Citadark Isle + new(GameVersion.XD, 60, 04000, Chansey) { Fateful = true, Species = 113, Level = 39, Moves = new[] {085,186,135,285}, Location = 084 }, // Chansey: Cipher Peon Leden @ Citadark Isle + new(GameVersion.XD, 60, 04000, Chansey) { Fateful = true, Species = 113, Level = 39, Moves = new[] {085,186,135,285}, Location = 087 }, // Chansey: Cipher Peon Leden @ Citadark Isle + new(GameVersion.XD, 61, 07500, Solrock) { Fateful = true, Species = 338, Level = 41, Moves = new[] {094,226,241,322}, Location = 087 }, // Solrock: Cipher Admin Snattle @ Citadark Isle + new(GameVersion.XD, 62, 07500, Starmie) { Fateful = true, Species = 121, Level = 41, Moves = new[] {127,287,058,105}, Location = 087 }, // Starmie: Cipher Admin Snattle @ Citadark Isle + new(GameVersion.XD, 63, 07000, Electabuzz){ Fateful = true, Species = 125, Level = 43, Moves = new[] {238,266,086,085}, Location = 087 }, // Electabuzz: Cipher Admin Ardos @ Citadark Isle + new(GameVersion.XD, 64, 07000, First) { Fateful = true, Species = 277, Level = 43, Moves = new[] {143,226,097,263}, Location = 087 }, // Swellow: Cipher Admin Ardos @ Citadark Isle + new(GameVersion.XD, 65, 09000, Snorlax) { Fateful = true, Species = 143, Level = 43, Moves = new[] {090,287,174,034}, Location = 087 }, // Snorlax: Cipher Admin Ardos @ Citadark Isle + new(GameVersion.XD, 66, 07500, Poliwrath) { Fateful = true, Species = 062, Level = 42, Moves = new[] {056,270,240,280}, Location = 087 }, // Poliwrath: Cipher Admin Gorigan @ Citadark Isle + new(GameVersion.XD, 67, 06500, MrMime) { Fateful = true, Species = 122, Level = 42, Moves = new[] {094,266,227,009}, Location = 087 }, // Mr. Mime: Cipher Admin Gorigan @ Citadark Isle + new(GameVersion.XD, 68, 05000, Dugtrio) { Fateful = true, Species = 051, Level = 40, Moves = new[] {089,204,201,161}, Location = 075 }, // Dugtrio: Cipher Peon Kolax @ Citadark Isle + new(GameVersion.XD, 69, 07000, Manectric) { Fateful = true, Species = 310, Level = 44, Moves = new[] {087,287,240,044}, Location = 073 }, // Manectric: Cipher Admin Eldes @ Citadark Isle + new(GameVersion.XD, 70, 09000, Salamence) { Fateful = true, Species = 373, Level = 50, Moves = new[] {337,287,349,332}, Location = 073 }, // Salamence: Cipher Admin Eldes @ Citadark Isle + new(GameVersion.XD, 71, 06500, Marowak) { Fateful = true, Species = 105, Level = 44, Moves = new[] {089,047,014,157}, Location = 073 }, // Marowak: Cipher Admin Eldes @ Citadark Isle + new(GameVersion.XD, 72, 06000, Lapras) { Fateful = true, Species = 131, Level = 44, Moves = new[] {056,215,240,059}, Location = 073 }, // Lapras: Cipher Admin Eldes @ Citadark Isle + new(GameVersion.XD, 73, 12000, First) { Fateful = true, Species = 249, Level = 50, Moves = new[] {354,297,089,056}, Location = 074 }, // Lugia: Grand Master Greevil @ Citadark Isle + new(GameVersion.XD, 74, 10000, Zapdos) { Fateful = true, Species = 145, Level = 50, Moves = new[] {326,226,319,085}, Location = 074 }, // Zapdos: Grand Master Greevil @ Citadark Isle + new(GameVersion.XD, 75, 10000, Moltres) { Fateful = true, Species = 146, Level = 50, Moves = new[] {326,234,261,053}, Location = 074 }, // Moltres: Grand Master Greevil @ Citadark Isle + new(GameVersion.XD, 76, 10000, Articuno) { Fateful = true, Species = 144, Level = 50, Moves = new[] {326,215,114,058}, Location = 074 }, // Articuno: Grand Master Greevil @ Citadark Isle + new(GameVersion.XD, 77, 09000, Tauros) { Fateful = true, Species = 128, Level = 46, Moves = new[] {089,287,039,034}, Location = 074 }, // Tauros: Grand Master Greevil @ Citadark Isle + new(GameVersion.XD, 78, 07000, First) { Fateful = true, Species = 112, Level = 46, Moves = new[] {224,270,184,089}, Location = 074 }, // Rhydon: Grand Master Greevil @ Citadark Isle + new(GameVersion.XD, 79, 09000, Exeggutor) { Fateful = true, Species = 103, Level = 46, Moves = new[] {094,287,095,246}, Location = 074 }, // Exeggutor: Grand Master Greevil @ Citadark Isle + new(GameVersion.XD, 80, 09000, Dragonite) { Fateful = true, Species = 149, Level = 55, Moves = new[] {063,215,349,089}, Location = 162 }, // Dragonite: Wanderer Miror B. @ Gateon Port + new(GameVersion.XD, 81, 04500, First) { Fateful = true, Species = 175, Level = 25, Moves = new[] {266,161,246,270}, Location = 164, Gift = true }, // Togepi: Pokémon Trainer Hordel @ Outskirt Stand + new(GameVersion.XD, 82, 02500, Poochyena) { Fateful = true, Species = 261, Level = 10, Moves = new[] {091,215,305,336}, Location = 162 }, // Poochyena: Bodybuilder Kilen @ Gateon Port + new(GameVersion.XD, 83, 02500, Ledyba) { Fateful = true, Species = 165, Level = 10, Moves = new[] {060,287,332,048}, Location = 153 }, // Ledyba: Casual Guy Cyle @ Gateon Port + }; - private static readonly EncounterStaticShadow[] Encounter_XD = - { - new(GameVersion.XD, 01, 03000, First) { Fateful = true, Species = 216, Level = 11, Moves = new[] {216,287,122,232}, Location = 143, Gift = true }, // Teddiursa: Cipher Peon Naps @ Pokémon HQ Lab -- treat as Gift as it can only be captured in a Poké Ball - new(GameVersion.XD, 02, 02000, Vulpix) { Fateful = true, Species = 037, Level = 18, Moves = new[] {257,204,052,091}, Location = 109 }, // Vulpix: Cipher Peon Mesin @ ONBS Building - new(GameVersion.XD, 03, 01500, Spheal) { Fateful = true, Species = 363, Level = 17, Moves = new[] {062,204,055,189}, Location = 011 }, // Spheal: Cipher Peon Blusix @ Cipher Lab - new(GameVersion.XD, 03, 01500, Spheal) { Fateful = true, Species = 363, Level = 17, Moves = new[] {062,204,055,189}, Location = 100 }, // Spheal: Cipher Peon Blusix @ Phenac City - new(GameVersion.XD, 04, 01500, First) { Fateful = true, Species = 343, Level = 17, Moves = new[] {317,287,189,060}, Location = 011 }, // Baltoy: Cipher Peon Browsix @ Cipher Lab - new(GameVersion.XD, 04, 01500, First) { Fateful = true, Species = 343, Level = 17, Moves = new[] {317,287,189,060}, Location = 096 }, // Baltoy: Cipher Peon Browsix @ Phenac City - new(GameVersion.XD, 05, 01500, First) { Fateful = true, Species = 179, Level = 17, Moves = new[] {034,215,084,086}, Location = 011 }, // Mareep: Cipher Peon Yellosix @ Cipher Lab - new(GameVersion.XD, 05, 01500, First) { Fateful = true, Species = 179, Level = 17, Moves = new[] {034,215,084,086}, Location = 096 }, // Mareep: Cipher Peon Yellosix @ Phenac City - new(GameVersion.XD, 06, 01500, Gulpin) { Fateful = true, Species = 316, Level = 17, Moves = new[] {351,047,124,092}, Location = 011 }, // Gulpin: Cipher Peon Purpsix @ Cipher Lab - new(GameVersion.XD, 06, 01500, Gulpin) { Fateful = true, Species = 316, Level = 17, Moves = new[] {351,047,124,092}, Location = 100 }, // Gulpin: Cipher Peon Purpsix @ Phenac City - new(GameVersion.XD, 07, 01500, Seedot) { Fateful = true, Species = 273, Level = 17, Moves = new[] {202,287,331,290}, Location = 011 }, // Seedot: Cipher Peon Greesix @ Cipher Lab - new(GameVersion.XD, 07, 01500, Seedot) { Fateful = true, Species = 273, Level = 17, Moves = new[] {202,287,331,290}, Location = 100 }, // Seedot: Cipher Peon Greesix @ Phenac City - new(GameVersion.XD, 08, 01500, Spinarak) { Fateful = true, Species = 167, Level = 14, Moves = new[] {091,287,324,101}, Location = 010 }, // Spinarak: Cipher Peon Nexir @ Cipher Lab - new(GameVersion.XD, 09, 01500, Numel) { Fateful = true, Species = 322, Level = 14, Moves = new[] {036,204,091,052}, Location = 009 }, // Numel: Cipher Peon Solox @ Cipher Lab - new(GameVersion.XD, 10, 01700, First) { Fateful = true, Species = 318, Level = 15, Moves = new[] {352,287,184,044}, Location = 008 }, // Carvanha: Cipher Peon Cabol @ Cipher Lab - new(GameVersion.XD, 11, 03000, Roselia) { Fateful = true, Species = 315, Level = 22, Moves = new[] {345,186,320,073}, Location = 094 }, // Roselia: Cipher Peon Fasin @ Phenac City - new(GameVersion.XD, 12, 02500, Delcatty) { Fateful = true, Species = 301, Level = 18, Moves = new[] {290,186,213,351}, Location = 008 }, // Delcatty: Cipher Admin Lovrina @ Cipher Lab - new(GameVersion.XD, 13, 04000, Nosepass) { Fateful = true, Species = 299, Level = 26, Moves = new[] {085,270,086,157}, Location = 090 }, // Nosepass: Wanderer Miror B. @ Poké Spots - new(GameVersion.XD, 14, 01500, First) { Fateful = true, Species = 228, Level = 17, Moves = new[] {185,204,052,046}, Location = 100 }, // Houndour: Cipher Peon Resix @ Phenac City - new(GameVersion.XD, 14, 01500, First) { Fateful = true, Species = 228, Level = 17, Moves = new[] {185,204,052,046}, Location = 011 }, // Houndour: Cipher Peon Resix @ Cipher Lab - new(GameVersion.XD, 15, 02000, Makuhita) { Fateful = true, Species = 296, Level = 18, Moves = new[] {280,287,292,317}, Location = 109 }, // Makuhita: Cipher Peon Torkin @ ONBS Building - new(GameVersion.XD, 16, 02200, Duskull) { Fateful = true, Species = 355, Level = 19, Moves = new[] {247,270,310,109}, Location = 110 }, // Duskull: Cipher Peon Lobar @ ONBS Building - new(GameVersion.XD, 17, 02200, Ralts) { Fateful = true, Species = 280, Level = 20, Moves = new[] {351,047,115,093}, Location = 119 }, // Ralts: Cipher Peon Feldas @ ONBS Building - new(GameVersion.XD, 18, 02500, Mawile) { Fateful = true, Species = 303, Level = 22, Moves = new[] {206,047,011,334}, Location = 111 }, // Mawile: Cipher Cmdr Exol @ ONBS Building - new(GameVersion.XD, 19, 02500, Snorunt) { Fateful = true, Species = 361, Level = 20, Moves = new[] {352,047,044,196}, Location = 097 }, // Snorunt: Cipher Peon Exinn @ Phenac City - new(GameVersion.XD, 20, 02500, Pineco) { Fateful = true, Species = 204, Level = 20, Moves = new[] {042,287,191,068}, Location = 096 }, // Pineco: Cipher Peon Gonrap @ Phenac City - new(GameVersion.XD, 21, 02500, Swinub) { Fateful = true, Species = 220, Level = 22, Moves = new[] {246,204,054,341}, Location = 100 }, // Swinub: Cipher Peon Greck @ Phenac City - new(GameVersion.XD, 22, 02500, Natu) { Fateful = true, Species = 177, Level = 22, Moves = new[] {248,226,101,332}, Location = 094 }, // Natu: Cipher Peon Eloin @ Phenac City - new(GameVersion.XD, 23, 01800, Shroomish) { Fateful = true, Species = 285, Level = 15, Moves = new[] {206,287,072,078}, Location = 008 }, // Shroomish: Cipher R&D Klots @ Cipher Lab - new(GameVersion.XD, 24, 03500, Meowth) { Fateful = true, Species = 052, Level = 22, Moves = new[] {163,047,006,044}, Location = 094 }, // Meowth: Cipher Peon Fostin @ Phenac City - new(GameVersion.XD, 25, 04500, Spearow) { Fateful = true, Species = 021, Level = 22, Moves = new[] {206,226,043,332}, Location = 107 }, // Spearow: Cipher Peon Ezin @ Phenac Stadium - new(GameVersion.XD, 26, 03000, Grimer) { Fateful = true, Species = 088, Level = 23, Moves = new[] {188,270,325,107}, Location = 107 }, // Grimer: Cipher Peon Faltly @ Phenac Stadium - new(GameVersion.XD, 27, 03500, Seel) { Fateful = true, Species = 086, Level = 23, Moves = new[] {057,270,219,058}, Location = 107 }, // Seel: Cipher Peon Egrog @ Phenac Stadium - new(GameVersion.XD, 28, 05000, Lunatone) { Fateful = true, Species = 337, Level = 25, Moves = new[] {094,226,240,317}, Location = 107 }, // Lunatone: Cipher Admin Snattle @ Phenac Stadium - new(GameVersion.XD, 29, 02500, Voltorb) { Fateful = true, Species = 100, Level = 19, Moves = new[] {243,287,209,129}, Location = 092 }, // Voltorb: Wanderer Miror B. @ Cave Poké Spot - new(GameVersion.XD, 30, 05000, First) { Fateful = true, Species = 335, Level = 28, Moves = new[] {280,287,068,306}, Location = 071 }, // Zangoose: Thug Zook @ Cipher Key Lair - new(GameVersion.XD, 31, 04000, Growlithe) { Fateful = true, Species = 058, Level = 28, Moves = new[] {053,204,044,036}, Location = 064 }, // Growlithe: Cipher Peon Humah @ Cipher Key Lair - new(GameVersion.XD, 32, 04000, Paras) { Fateful = true, Species = 046, Level = 28, Moves = new[] {147,287,163,206}, Location = 064 }, // Paras: Cipher Peon Humah @ Cipher Key Lair - new(GameVersion.XD, 33, 04000, First) { Fateful = true, Species = 090, Level = 29, Moves = new[] {036,287,057,062}, Location = 065 }, // Shellder: Cipher Peon Gorog @ Cipher Key Lair - new(GameVersion.XD, 34, 04500, First) { Fateful = true, Species = 015, Level = 30, Moves = new[] {188,226,041,014}, Location = 066 }, // Beedrill: Cipher Peon Lok @ Cipher Key Lair - new(GameVersion.XD, 35, 04000, Pidgeotto) { Fateful = true, Species = 017, Level = 30, Moves = new[] {017,287,211,297}, Location = 066 }, // Pidgeotto: Cipher Peon Lok @ Cipher Key Lair - new(GameVersion.XD, 36, 04000, Butterfree){ Fateful = true, Species = 012, Level = 30, Moves = new[] {094,234,079,332}, Location = 067 }, // Butterfree: Cipher Peon Targ @ Cipher Key Lair - new(GameVersion.XD, 37, 04000, Tangela) { Fateful = true, Species = 114, Level = 30, Moves = new[] {076,234,241,275}, Location = 067 }, // Tangela: Cipher Peon Targ @ Cipher Key Lair - new(GameVersion.XD, 38, 06000, Raticate) { Fateful = true, Species = 020, Level = 34, Moves = new[] {162,287,184,158}, Location = 076 }, // Raticate: Chaser Furgy @ Citadark Isle - new(GameVersion.XD, 39, 04000, Venomoth) { Fateful = true, Species = 049, Level = 32, Moves = new[] {318,287,164,094}, Location = 070 }, // Venomoth: Cipher Peon Angic @ Cipher Key Lair - new(GameVersion.XD, 40, 04000, Weepinbell){ Fateful = true, Species = 070, Level = 32, Moves = new[] {345,234,188,230}, Location = 070 }, // Weepinbell: Cipher Peon Angic @ Cipher Key Lair - new(GameVersion.XD, 41, 05000, Arbok) { Fateful = true, Species = 024, Level = 33, Moves = new[] {188,287,137,044}, Location = 070 }, // Arbok: Cipher Peon Smarton @ Cipher Key Lair - new(GameVersion.XD, 42, 06000, Primeape) { Fateful = true, Species = 057, Level = 34, Moves = new[] {238,270,116,179}, Location = 069 }, // Primeape: Cipher Admin Gorigan @ Cipher Key Lair - new(GameVersion.XD, 43, 05500, Hypno) { Fateful = true, Species = 097, Level = 34, Moves = new[] {094,226,096,247}, Location = 069 }, // Hypno: Cipher Admin Gorigan @ Cipher Key Lair - new(GameVersion.XD, 44, 06500, Golduck) { Fateful = true, Species = 055, Level = 33, Moves = new[] {127,204,244,280}, Location = 088 }, // Golduck: Navigator Abson @ Citadark Isle - new(GameVersion.XD, 45, 07000, Sableye) { Fateful = true, Species = 302, Level = 33, Moves = new[] {247,270,185,105}, Location = 088 }, // Sableye: Navigator Abson @ Citadark Isle - new(GameVersion.XD, 46, 04500, Magneton) { Fateful = true, Species = 082, Level = 30, Moves = new[] {038,287,240,087}, Location = 067 }, // Magneton: Cipher Peon Snidle @ Cipher Key Lair - new(GameVersion.XD, 47, 08000, Dodrio) { Fateful = true, Species = 085, Level = 34, Moves = new[] {065,226,097,161}, Location = 076 }, // Dodrio: Chaser Furgy @ Citadark Isle - new(GameVersion.XD, 48, 05500, Farfetchd) { Fateful = true, Species = 083, Level = 36, Moves = new[] {163,226,014,332}, Location = 076 }, // Farfetch'd: Cipher Admin Lovrina @ Citadark Isle - new(GameVersion.XD, 49, 06500, Altaria) { Fateful = true, Species = 334, Level = 36, Moves = new[] {225,215,076,332}, Location = 076 }, // Altaria: Cipher Admin Lovrina @ Citadark Isle - new(GameVersion.XD, 50, 06000, Kangaskhan){ Fateful = true, Species = 115, Level = 35, Moves = new[] {089,047,039,146}, Location = 085 }, // Kangaskhan: Cipher Peon Litnar @ Citadark Isle - new(GameVersion.XD, 51, 07000, Banette) { Fateful = true, Species = 354, Level = 37, Moves = new[] {185,270,247,174}, Location = 085 }, // Banette: Cipher Peon Litnar @ Citadark Isle - new(GameVersion.XD, 52, 07000, Magmar) { Fateful = true, Species = 126, Level = 36, Moves = new[] {126,266,238,009}, Location = 077 }, // Magmar: Cipher Peon Grupel @ Citadark Isle - new(GameVersion.XD, 53, 07000, Pinsir) { Fateful = true, Species = 127, Level = 35, Moves = new[] {012,270,206,066}, Location = 077 }, // Pinsir: Cipher Peon Grupel @ Citadark Isle - new(GameVersion.XD, 54, 05500, Magcargo) { Fateful = true, Species = 219, Level = 38, Moves = new[] {257,287,089,053}, Location = 080 }, // Magcargo: Cipher Peon Kolest @ Citadark Isle - new(GameVersion.XD, 55, 06000, Rapidash) { Fateful = true, Species = 078, Level = 40, Moves = new[] {076,226,241,053}, Location = 080 }, // Rapidash: Cipher Peon Kolest @ Citadark Isle - new(GameVersion.XD, 56, 06000, Hitmonchan){ Fateful = true, Species = 107, Level = 38, Moves = new[] {005,270,170,327}, Location = 081 }, // Hitmonchan: Cipher Peon Karbon @ Citadark Isle - new(GameVersion.XD, 57, 07000, Hitmonlee) { Fateful = true, Species = 106, Level = 38, Moves = new[] {136,287,170,025}, Location = 081 }, // Hitmonlee: Cipher Peon Petro @ Citadark Isle - new(GameVersion.XD, 58, 05000, Lickitung) { Fateful = true, Species = 108, Level = 38, Moves = new[] {038,270,111,205}, Location = 084 }, // Lickitung: Cipher Peon Geftal @ Citadark Isle - new(GameVersion.XD, 59, 08000, Scyther) { Fateful = true, Species = 123, Level = 40, Moves = new[] {013,234,318,163}, Location = 084 }, // Scyther: Cipher Peon Leden @ Citadark Isle - new(GameVersion.XD, 60, 04000, Chansey) { Fateful = true, Species = 113, Level = 39, Moves = new[] {085,186,135,285}, Location = 084 }, // Chansey: Cipher Peon Leden @ Citadark Isle - new(GameVersion.XD, 60, 04000, Chansey) { Fateful = true, Species = 113, Level = 39, Moves = new[] {085,186,135,285}, Location = 087 }, // Chansey: Cipher Peon Leden @ Citadark Isle - new(GameVersion.XD, 61, 07500, Solrock) { Fateful = true, Species = 338, Level = 41, Moves = new[] {094,226,241,322}, Location = 087 }, // Solrock: Cipher Admin Snattle @ Citadark Isle - new(GameVersion.XD, 62, 07500, Starmie) { Fateful = true, Species = 121, Level = 41, Moves = new[] {127,287,058,105}, Location = 087 }, // Starmie: Cipher Admin Snattle @ Citadark Isle - new(GameVersion.XD, 63, 07000, Electabuzz){ Fateful = true, Species = 125, Level = 43, Moves = new[] {238,266,086,085}, Location = 087 }, // Electabuzz: Cipher Admin Ardos @ Citadark Isle - new(GameVersion.XD, 64, 07000, First) { Fateful = true, Species = 277, Level = 43, Moves = new[] {143,226,097,263}, Location = 087 }, // Swellow: Cipher Admin Ardos @ Citadark Isle - new(GameVersion.XD, 65, 09000, Snorlax) { Fateful = true, Species = 143, Level = 43, Moves = new[] {090,287,174,034}, Location = 087 }, // Snorlax: Cipher Admin Ardos @ Citadark Isle - new(GameVersion.XD, 66, 07500, Poliwrath) { Fateful = true, Species = 062, Level = 42, Moves = new[] {056,270,240,280}, Location = 087 }, // Poliwrath: Cipher Admin Gorigan @ Citadark Isle - new(GameVersion.XD, 67, 06500, MrMime) { Fateful = true, Species = 122, Level = 42, Moves = new[] {094,266,227,009}, Location = 087 }, // Mr. Mime: Cipher Admin Gorigan @ Citadark Isle - new(GameVersion.XD, 68, 05000, Dugtrio) { Fateful = true, Species = 051, Level = 40, Moves = new[] {089,204,201,161}, Location = 075 }, // Dugtrio: Cipher Peon Kolax @ Citadark Isle - new(GameVersion.XD, 69, 07000, Manectric) { Fateful = true, Species = 310, Level = 44, Moves = new[] {087,287,240,044}, Location = 073 }, // Manectric: Cipher Admin Eldes @ Citadark Isle - new(GameVersion.XD, 70, 09000, Salamence) { Fateful = true, Species = 373, Level = 50, Moves = new[] {337,287,349,332}, Location = 073 }, // Salamence: Cipher Admin Eldes @ Citadark Isle - new(GameVersion.XD, 71, 06500, Marowak) { Fateful = true, Species = 105, Level = 44, Moves = new[] {089,047,014,157}, Location = 073 }, // Marowak: Cipher Admin Eldes @ Citadark Isle - new(GameVersion.XD, 72, 06000, Lapras) { Fateful = true, Species = 131, Level = 44, Moves = new[] {056,215,240,059}, Location = 073 }, // Lapras: Cipher Admin Eldes @ Citadark Isle - new(GameVersion.XD, 73, 12000, First) { Fateful = true, Species = 249, Level = 50, Moves = new[] {354,297,089,056}, Location = 074 }, // Lugia: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 74, 10000, Zapdos) { Fateful = true, Species = 145, Level = 50, Moves = new[] {326,226,319,085}, Location = 074 }, // Zapdos: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 75, 10000, Moltres) { Fateful = true, Species = 146, Level = 50, Moves = new[] {326,234,261,053}, Location = 074 }, // Moltres: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 76, 10000, Articuno) { Fateful = true, Species = 144, Level = 50, Moves = new[] {326,215,114,058}, Location = 074 }, // Articuno: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 77, 09000, Tauros) { Fateful = true, Species = 128, Level = 46, Moves = new[] {089,287,039,034}, Location = 074 }, // Tauros: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 78, 07000, First) { Fateful = true, Species = 112, Level = 46, Moves = new[] {224,270,184,089}, Location = 074 }, // Rhydon: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 79, 09000, Exeggutor) { Fateful = true, Species = 103, Level = 46, Moves = new[] {094,287,095,246}, Location = 074 }, // Exeggutor: Grand Master Greevil @ Citadark Isle - new(GameVersion.XD, 80, 09000, Dragonite) { Fateful = true, Species = 149, Level = 55, Moves = new[] {063,215,349,089}, Location = 162 }, // Dragonite: Wanderer Miror B. @ Gateon Port - new(GameVersion.XD, 81, 04500, First) { Fateful = true, Species = 175, Level = 25, Moves = new[] {266,161,246,270}, Location = 164, Gift = true }, // Togepi: Pokémon Trainer Hordel @ Outskirt Stand - new(GameVersion.XD, 82, 02500, Poochyena) { Fateful = true, Species = 261, Level = 10, Moves = new[] {091,215,305,336}, Location = 162 }, // Poochyena: Bodybuilder Kilen @ Gateon Port - new(GameVersion.XD, 83, 02500, Ledyba) { Fateful = true, Species = 165, Level = 10, Moves = new[] {060,287,332,048}, Location = 153 }, // Ledyba: Casual Guy Cyle @ Gateon Port - }; + internal static readonly EncounterArea3XD[] SlotsXD = + { + new(90, 027, 23, 207, 20, 328, 20), // Rock (Sandshrew, Gligar, Trapinch) + new(91, 187, 20, 231, 20, 283, 20), // Oasis (Hoppip, Phanpy, Surskit) + new(92, 041, 21, 304, 21, 194, 21), // Cave (Zubat, Aron, Wooper) + }; - internal static readonly EncounterArea3XD[] SlotsXD = - { - new(90, 027, 23, 207, 20, 328, 20), // Rock (Sandshrew, Gligar, Trapinch) - new(91, 187, 20, 231, 20, 283, 20), // Oasis (Hoppip, Phanpy, Surskit) - new(92, 041, 21, 304, 21, 194, 21), // Cave (Zubat, Aron, Wooper) - }; + internal static readonly EncounterStatic[] Encounter_CXD = ArrayUtil.ConcatAll(Encounter_ColoGift, Encounter_Colo, Encounter_XDGift, Encounter_XD); - internal static readonly EncounterStatic[] Encounter_CXD = ArrayUtil.ConcatAll(Encounter_ColoGift, Encounter_Colo, Encounter_XDGift, Encounter_XD); - - #endregion - } + #endregion } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters3Shadow.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters3Shadow.cs index 6c3d38e31..14bc1bffe 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters3Shadow.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters3Shadow.cs @@ -1,1085 +1,1084 @@ // ReSharper disable StringLiteralTypo -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Team listings for that have a shadow Pokémon afterwards. +/// +public static class Encounters3Shadow { - /// - /// Team listings for that have a shadow Pokémon afterwards. - /// - public static class Encounters3Shadow - { - #region Colosseum + #region Colosseum - public static readonly TeamLock CMakuhita = new( - 296, // Makuhita - new[] { - new NPCLock(355, 24, 0, 127), // Duskull (M) (Quirky) - new NPCLock(167, 00, 1, 127), // Spinarak (F) (Hardy) + public static readonly TeamLock CMakuhita = new( + 296, // Makuhita + new[] { + new NPCLock(355, 24, 0, 127), // Duskull (M) (Quirky) + new NPCLock(167, 00, 1, 127), // Spinarak (F) (Hardy) }); - public static readonly TeamLock CGligar = new( - 207, // Gligar - new[] { - new NPCLock(216, 12, 0, 127), // Teddiursa (M) (Serious) - new NPCLock(039, 06, 1, 191), // Jigglypuff (F) (Docile) - new NPCLock(285, 18, 0, 127), // Shroomish (M) (Bashful) + public static readonly TeamLock CGligar = new( + 207, // Gligar + new[] { + new NPCLock(216, 12, 0, 127), // Teddiursa (M) (Serious) + new NPCLock(039, 06, 1, 191), // Jigglypuff (F) (Docile) + new NPCLock(285, 18, 0, 127), // Shroomish (M) (Bashful) }); - public static readonly TeamLock CMurkrow = new( - 198, // Murkrow - new[] { - new NPCLock(318, 06, 0, 127), // Carvanha (M) (Docile) - new NPCLock(274, 12, 1, 127), // Nuzleaf (F) (Serious) - new NPCLock(228, 18, 0, 127), // Houndour (M) (Bashful) + public static readonly TeamLock CMurkrow = new( + 198, // Murkrow + new[] { + new NPCLock(318, 06, 0, 127), // Carvanha (M) (Docile) + new NPCLock(274, 12, 1, 127), // Nuzleaf (F) (Serious) + new NPCLock(228, 18, 0, 127), // Houndour (M) (Bashful) }); - public static readonly TeamLock CHeracross = new( - 214, // Heracross - new[] { - new NPCLock(284, 00, 0, 127), // Masquerain (M) (Hardy) - new NPCLock(168, 00, 1, 127), // Ariados (F) (Hardy) + public static readonly TeamLock CHeracross = new( + 214, // Heracross + new[] { + new NPCLock(284, 00, 0, 127), // Masquerain (M) (Hardy) + new NPCLock(168, 00, 1, 127), // Ariados (F) (Hardy) }); - public static readonly TeamLock CUrsaring = new( - 217, // Ursaring - new[] { - new NPCLock(067, 20, 1, 063), // Machoke (F) (Calm) - new NPCLock(259, 16, 0, 031), // Marshtomp (M) (Mild) - new NPCLock(275, 21, 1, 127), // Shiftry (F) (Gentle) - }); + public static readonly TeamLock CUrsaring = new( + 217, // Ursaring + new[] { + new NPCLock(067, 20, 1, 063), // Machoke (F) (Calm) + new NPCLock(259, 16, 0, 031), // Marshtomp (M) (Mild) + new NPCLock(275, 21, 1, 127), // Shiftry (F) (Gentle) + }); - #endregion + #endregion - #region E-Reader + #region E-Reader - public static readonly TeamLock ETogepi = new( - 175, // Togepi - new[] { - new NPCLock(302, 23, 0, 127), // Sableye (M) (Careful) - new NPCLock(088, 08, 0, 127), // Grimer (M) (Impish) - new NPCLock(316, 24, 0, 127), // Gulpin (M) (Quirky) - new NPCLock(175, 22, 1, 031), // Togepi (F) (Sassy) -- itself! + public static readonly TeamLock ETogepi = new( + 175, // Togepi + new[] { + new NPCLock(302, 23, 0, 127), // Sableye (M) (Careful) + new NPCLock(088, 08, 0, 127), // Grimer (M) (Impish) + new NPCLock(316, 24, 0, 127), // Gulpin (M) (Quirky) + new NPCLock(175, 22, 1, 031), // Togepi (F) (Sassy) -- itself! }); - public static readonly TeamLock EMareep = new( - 179, // Mareep - new[] { - new NPCLock(300, 04, 1, 191), // Skitty (F) (Naughty) - new NPCLock(211, 10, 1, 127), // Qwilfish (F) (Timid) - new NPCLock(355, 12, 1, 127), // Duskull (F) (Serious) - new NPCLock(179, 16, 1, 127), // Mareep (F) (Mild) -- itself! + public static readonly TeamLock EMareep = new( + 179, // Mareep + new[] { + new NPCLock(300, 04, 1, 191), // Skitty (F) (Naughty) + new NPCLock(211, 10, 1, 127), // Qwilfish (F) (Timid) + new NPCLock(355, 12, 1, 127), // Duskull (F) (Serious) + new NPCLock(179, 16, 1, 127), // Mareep (F) (Mild) -- itself! }); - public static readonly TeamLock EScizor = new( - 212, // Scizor - new[] { - new NPCLock(198, 13, 1, 191), // Murkrow (F) (Jolly) - new NPCLock(344, 02, 2, 255), // Claydol (-) (Brave) - new NPCLock(208, 03, 0, 127), // Steelix (M) (Adamant) - new NPCLock(212, 11, 0, 127), // Scizor (M) (Hasty) -- itself! + public static readonly TeamLock EScizor = new( + 212, // Scizor + new[] { + new NPCLock(198, 13, 1, 191), // Murkrow (F) (Jolly) + new NPCLock(344, 02, 2, 255), // Claydol (-) (Brave) + new NPCLock(208, 03, 0, 127), // Steelix (M) (Adamant) + new NPCLock(212, 11, 0, 127), // Scizor (M) (Hasty) -- itself! }); - #endregion - - #region XD + #endregion + + #region XD - public static readonly TeamLock XRalts = new( - 280, // Ralts - new[] { - new NPCLock(064, 00, 0, 063), // Kadabra (M) (Hardy) - new NPCLock(180, 06, 1, 127), // Flaaffy (F) (Docile) - new NPCLock(288, 18, 0, 127), // Vigoroth (M) (Bashful) + public static readonly TeamLock XRalts = new( + 280, // Ralts + new[] { + new NPCLock(064, 00, 0, 063), // Kadabra (M) (Hardy) + new NPCLock(180, 06, 1, 127), // Flaaffy (F) (Docile) + new NPCLock(288, 18, 0, 127), // Vigoroth (M) (Bashful) }); - public static readonly TeamLock XPoochyena = new( - 261, // Poochyena - new[] { - new NPCLock(041, 12, 1, 127), // Zubat (F) (Serious) - }); + public static readonly TeamLock XPoochyena = new( + 261, // Poochyena + new[] { + new NPCLock(041, 12, 1, 127), // Zubat (F) (Serious) + }); - public static readonly TeamLock XLedyba = new( - 165, // Ledyba - new[] { - new NPCLock(276, 00, 1, 127), // Taillow (F) (Hardy) + public static readonly TeamLock XLedyba = new( + 165, // Ledyba + new[] { + new NPCLock(276, 00, 1, 127), // Taillow (F) (Hardy) }); - - public static readonly TeamLock XSphealCipherLab = new( - 363, // Spheal - "Cipher Lab", - new[] { - new NPCLock(116, 24, 0, 063), // Horsea (M) (Quirky) - new NPCLock(118, 12, 1, 127), // Goldeen (F) (Serious) + + public static readonly TeamLock XSphealCipherLab = new( + 363, // Spheal + "Cipher Lab", + new[] { + new NPCLock(116, 24, 0, 063), // Horsea (M) (Quirky) + new NPCLock(118, 12, 1, 127), // Goldeen (F) (Serious) }); - public static readonly TeamLock XSphealPhenacCityandPost = new( - 363, // Spheal - "Phenac City and Post", - new[] { - new NPCLock(116, 24, 0, 063), // Horsea (M) (Quirky) - new NPCLock(118, 12, 1, 127), // Goldeen (F) (Serious) - new NPCLock(374, 00, 2, 255), // Beldum (-) (Hardy) + public static readonly TeamLock XSphealPhenacCityandPost = new( + 363, // Spheal + "Phenac City and Post", + new[] { + new NPCLock(116, 24, 0, 063), // Horsea (M) (Quirky) + new NPCLock(118, 12, 1, 127), // Goldeen (F) (Serious) + new NPCLock(374, 00, 2, 255), // Beldum (-) (Hardy) }); - public static readonly TeamLock XGulpin = new( - 316, // Gulpin - new[] { - new NPCLock(109, 12, 1, 127), // Koffing (F) (Serious) - new NPCLock(088, 06, 0, 127), // Grimer (M) (Docile) + public static readonly TeamLock XGulpin = new( + 316, // Gulpin + new[] { + new NPCLock(109, 12, 1, 127), // Koffing (F) (Serious) + new NPCLock(088, 06, 0, 127), // Grimer (M) (Docile) }); - public static readonly TeamLock XSeedotCipherLab = new( - 273, // Seedot - "Cipher Lab", - new[] { - new NPCLock(043, 06, 0, 127), // Oddish (M) (Docile) - new NPCLock(331, 24, 1, 127), // Cacnea (F) (Quirky) - new NPCLock(285, 18, 1, 127), // Shroomish (F) (Bashful) - new NPCLock(270, 00, 0, 127), // Lotad (M) (Hardy) - new NPCLock(204, 12, 0, 127), // Pineco (M) (Serious) + public static readonly TeamLock XSeedotCipherLab = new( + 273, // Seedot + "Cipher Lab", + new[] { + new NPCLock(043, 06, 0, 127), // Oddish (M) (Docile) + new NPCLock(331, 24, 1, 127), // Cacnea (F) (Quirky) + new NPCLock(285, 18, 1, 127), // Shroomish (F) (Bashful) + new NPCLock(270, 00, 0, 127), // Lotad (M) (Hardy) + new NPCLock(204, 12, 0, 127), // Pineco (M) (Serious) }); - public static readonly TeamLock XSeedotPhenacCity = new( - 273, // Seedot - "Phenac City", - new[] { - new NPCLock(043, 06, 0, 127), // Oddish (M) (Docile) - new NPCLock(331, 24, 1, 127), // Cacnea (F) (Quirky) - new NPCLock(285, 00, 1, 127), // Shroomish (F) (Hardy) - new NPCLock(270, 00, 1, 127), // Lotad (F) (Hardy) - new NPCLock(204, 06, 0, 127), // Pineco (M) (Docile) + public static readonly TeamLock XSeedotPhenacCity = new( + 273, // Seedot + "Phenac City", + new[] { + new NPCLock(043, 06, 0, 127), // Oddish (M) (Docile) + new NPCLock(331, 24, 1, 127), // Cacnea (F) (Quirky) + new NPCLock(285, 00, 1, 127), // Shroomish (F) (Hardy) + new NPCLock(270, 00, 1, 127), // Lotad (F) (Hardy) + new NPCLock(204, 06, 0, 127), // Pineco (M) (Docile) }); - public static readonly TeamLock XSeedotPost = new( - 273, // Seedot - "Post", - new[] { - new NPCLock(045, 06, 0, 127), // Vileplume (M) (Docile) - new NPCLock(332, 24, 1, 127), // Cacturne (F) (Quirky) - new NPCLock(286, 00, 1, 127), // Breloom (F) (Hardy) - new NPCLock(271, 00, 0, 127), // Lombre (M) (Hardy) - new NPCLock(205, 12, 0, 127), // Forretress (M) (Serious) + public static readonly TeamLock XSeedotPost = new( + 273, // Seedot + "Post", + new[] { + new NPCLock(045, 06, 0, 127), // Vileplume (M) (Docile) + new NPCLock(332, 24, 1, 127), // Cacturne (F) (Quirky) + new NPCLock(286, 00, 1, 127), // Breloom (F) (Hardy) + new NPCLock(271, 00, 0, 127), // Lombre (M) (Hardy) + new NPCLock(205, 12, 0, 127), // Forretress (M) (Serious) }); - public static readonly TeamLock XSpinarak = new( - 167, // Spinarak - new[] { - new NPCLock(220, 12, 1, 127), // Swinub (F) (Serious) - new NPCLock(353, 06, 0, 127), // Shuppet (M) (Docile) + public static readonly TeamLock XSpinarak = new( + 167, // Spinarak + new[] { + new NPCLock(220, 12, 1, 127), // Swinub (F) (Serious) + new NPCLock(353, 06, 0, 127), // Shuppet (M) (Docile) }); - public static readonly TeamLock XNumel = new( - 322, // Numel - new[] { - new NPCLock(280, 06, 0, 127), // Ralts (M) (Docile) - new NPCLock(100, 00, 2, 255), // Voltorb (-) (Hardy) - new NPCLock(371, 24, 1, 127), // Bagon (F) (Quirky) + public static readonly TeamLock XNumel = new( + 322, // Numel + new[] { + new NPCLock(280, 06, 0, 127), // Ralts (M) (Docile) + new NPCLock(100, 00, 2, 255), // Voltorb (-) (Hardy) + new NPCLock(371, 24, 1, 127), // Bagon (F) (Quirky) }); - public static readonly TeamLock XShroomish = new( - 285, // Shroomish - new[] { - new NPCLock(209, 24, 1, 191), // Snubbull (F) (Quirky) - new NPCLock(352, 00, 1, 127), // Kecleon (F) (Hardy) + public static readonly TeamLock XShroomish = new( + 285, // Shroomish + new[] { + new NPCLock(209, 24, 1, 191), // Snubbull (F) (Quirky) + new NPCLock(352, 00, 1, 127), // Kecleon (F) (Hardy) }); - public static readonly TeamLock XDelcatty = new( - 301, // Delcatty - new[] { - new NPCLock(370, 06, 1, 191), // Luvdisc (F) (Docile) - new NPCLock(267, 00, 0, 127), // Beautifly (M) (Hardy) - new NPCLock(315, 24, 0, 127), // Roselia (M) (Quirky) + public static readonly TeamLock XDelcatty = new( + 301, // Delcatty + new[] { + new NPCLock(370, 06, 1, 191), // Luvdisc (F) (Docile) + new NPCLock(267, 00, 0, 127), // Beautifly (M) (Hardy) + new NPCLock(315, 24, 0, 127), // Roselia (M) (Quirky) }); - public static readonly TeamLock XVoltorb = new( - 100, // Voltorb - new[] { - new NPCLock(271, 00, 0, 127), // Lombre (M) (Hardy) - new NPCLock(271, 18, 0, 127), // Lombre (M) (Bashful) - new NPCLock(271, 12, 1, 127), // Lombre (F) (Serious) + public static readonly TeamLock XVoltorb = new( + 100, // Voltorb + new[] { + new NPCLock(271, 00, 0, 127), // Lombre (M) (Hardy) + new NPCLock(271, 18, 0, 127), // Lombre (M) (Bashful) + new NPCLock(271, 12, 1, 127), // Lombre (F) (Serious) }); - public static readonly TeamLock XMakuhita = new( - 296, // Makuhita - new[] { - new NPCLock(352, 06, 0, 127), // Kecleon (M) (Docile) - new NPCLock(283, 18, 1, 127), // Surskit (F) (Bashful) + public static readonly TeamLock XMakuhita = new( + 296, // Makuhita + new[] { + new NPCLock(352, 06, 0, 127), // Kecleon (M) (Docile) + new NPCLock(283, 18, 1, 127), // Surskit (F) (Bashful) }); - public static readonly TeamLock XVulpix = new( - 037, // Vulpix - new[] { - new NPCLock(167, 00, 0, 127), // Spinarak (M) (Hardy) - new NPCLock(267, 06, 1, 127), // Beautifly (F) (Docile) - new NPCLock(269, 18, 0, 127), // Dustox (M) (Bashful) + public static readonly TeamLock XVulpix = new( + 037, // Vulpix + new[] { + new NPCLock(167, 00, 0, 127), // Spinarak (M) (Hardy) + new NPCLock(267, 06, 1, 127), // Beautifly (F) (Docile) + new NPCLock(269, 18, 0, 127), // Dustox (M) (Bashful) }); - public static readonly TeamLock XDuskull = new( - 355, // Duskull - new[] { - new NPCLock(215, 12, 0, 127), // Sneasel (M) (Serious) - new NPCLock(193, 18, 1, 127), // Yanma (F) (Bashful) - new NPCLock(200, 24, 0, 127), // Misdreavus (M) (Quirky) + public static readonly TeamLock XDuskull = new( + 355, // Duskull + new[] { + new NPCLock(215, 12, 0, 127), // Sneasel (M) (Serious) + new NPCLock(193, 18, 1, 127), // Yanma (F) (Bashful) + new NPCLock(200, 24, 0, 127), // Misdreavus (M) (Quirky) }); - public static readonly TeamLock XMawile = new( - 303, // Mawile - new[] { - new NPCLock(294, 06, 0, 127), // Loudred (M) (Docile) - new NPCLock(203, 18, 1, 127), // Girafarig (F) (Bashful) + public static readonly TeamLock XMawile = new( + 303, // Mawile + new[] { + new NPCLock(294, 06, 0, 127), // Loudred (M) (Docile) + new NPCLock(203, 18, 1, 127), // Girafarig (F) (Bashful) }); - public static readonly TeamLock XSnorunt = new( - 361, // Snorunt - new[] { - new NPCLock(336, 06, 1, 127), // Seviper (F) (Docile) + public static readonly TeamLock XSnorunt = new( + 361, // Snorunt + new[] { + new NPCLock(336, 06, 1, 127), // Seviper (F) (Docile) }); - public static readonly TeamLock XPineco = new( - 204, // Pineco - new[] { - new NPCLock(198, 06, 0, 127), // Murkrow (M) (Docile) + public static readonly TeamLock XPineco = new( + 204, // Pineco + new[] { + new NPCLock(198, 06, 0, 127), // Murkrow (M) (Docile) }); - public static readonly TeamLock XNatu = new( - 177, // Natu - new[] { - new NPCLock(281, 00, 0, 127), // Kirlia (M) (Hardy) - new NPCLock(264, 00, 1, 127), // Linoone (F) (Hardy) + public static readonly TeamLock XNatu = new( + 177, // Natu + new[] { + new NPCLock(281, 00, 0, 127), // Kirlia (M) (Hardy) + new NPCLock(264, 00, 1, 127), // Linoone (F) (Hardy) }); - public static readonly TeamLock XRoselia = new( - 315, // Roselia - new[] { - new NPCLock(223, 06, 0, 127), // Remoraid (M) (Docile) - new NPCLock(042, 18, 0, 127), // Golbat (M) (Bashful) + public static readonly TeamLock XRoselia = new( + 315, // Roselia + new[] { + new NPCLock(223, 06, 0, 127), // Remoraid (M) (Docile) + new NPCLock(042, 18, 0, 127), // Golbat (M) (Bashful) }); - public static readonly TeamLock XMeowth = new( - 052, // Meowth - new[] { - new NPCLock(064, 06, 0, 063), // Kadabra (M) (Docile) - new NPCLock(215, 00, 1, 127), // Sneasel (F) (Hardy) - new NPCLock(200, 18, 1, 127), // Misdreavus (F) (Bashful) + public static readonly TeamLock XMeowth = new( + 052, // Meowth + new[] { + new NPCLock(064, 06, 0, 063), // Kadabra (M) (Docile) + new NPCLock(215, 00, 1, 127), // Sneasel (F) (Hardy) + new NPCLock(200, 18, 1, 127), // Misdreavus (F) (Bashful) }); - public static readonly TeamLock XSwinub = new( - 220, // Swinub - new[] { - new NPCLock(324, 18, 1, 127), // Torkoal (F) (Bashful) - new NPCLock(274, 00, 0, 127), // Nuzleaf (M) (Hardy) + public static readonly TeamLock XSwinub = new( + 220, // Swinub + new[] { + new NPCLock(324, 18, 1, 127), // Torkoal (F) (Bashful) + new NPCLock(274, 00, 0, 127), // Nuzleaf (M) (Hardy) }); - public static readonly TeamLock XSpearow = new( - 021, // Spearow - new[] { - new NPCLock(279, 18, 0, 127), // Pelipper (M) (Bashful) - new NPCLock(309, 06, 1, 127), // Electrike (F) (Docile) + public static readonly TeamLock XSpearow = new( + 021, // Spearow + new[] { + new NPCLock(279, 18, 0, 127), // Pelipper (M) (Bashful) + new NPCLock(309, 06, 1, 127), // Electrike (F) (Docile) }); - public static readonly TeamLock XGrimer = new( - 088, // Grimer - new[] { - new NPCLock(358, 12, 0, 127), // Chimecho (M) (Serious) - new NPCLock(234, 18, 0, 127), // Stantler (M) (Bashful) + public static readonly TeamLock XGrimer = new( + 088, // Grimer + new[] { + new NPCLock(358, 12, 0, 127), // Chimecho (M) (Serious) + new NPCLock(234, 18, 0, 127), // Stantler (M) (Bashful) }); - public static readonly TeamLock XSeel = new( - 086, // Seel - new[] { - new NPCLock(163, 06, 0, 127), // Hoothoot (M) (Docile) - new NPCLock(075, 18, 0, 127), // Graveler (M) (Bashful) - new NPCLock(316, 18, 1, 127), // Gulpin (F) (Bashful) + public static readonly TeamLock XSeel = new( + 086, // Seel + new[] { + new NPCLock(163, 06, 0, 127), // Hoothoot (M) (Docile) + new NPCLock(075, 18, 0, 127), // Graveler (M) (Bashful) + new NPCLock(316, 18, 1, 127), // Gulpin (F) (Bashful) }); - public static readonly TeamLock XLunatone = new( - 337, // Lunatone - new[] { - new NPCLock(171, 00, 1, 127), // Lanturn (F) (Hardy) - new NPCLock(195, 18, 0, 127), // Quagsire (M) (Bashful) + public static readonly TeamLock XLunatone = new( + 337, // Lunatone + new[] { + new NPCLock(171, 00, 1, 127), // Lanturn (F) (Hardy) + new NPCLock(195, 18, 0, 127), // Quagsire (M) (Bashful) }); - public static readonly TeamLock XNosepass = new( - 299, // Nosepass - new[] { - new NPCLock(271, 00, 0, 127), // Lombre (M) (Hardy) - new NPCLock(271, 18, 0, 127), // Lombre (M) (Bashful) - new NPCLock(271, 12, 1, 127), // Lombre (F) (Serious) + public static readonly TeamLock XNosepass = new( + 299, // Nosepass + new[] { + new NPCLock(271, 00, 0, 127), // Lombre (M) (Hardy) + new NPCLock(271, 18, 0, 127), // Lombre (M) (Bashful) + new NPCLock(271, 12, 1, 127), // Lombre (F) (Serious) }); - public static readonly TeamLock XParas = new( - 046, // Paras - new[] { - new NPCLock(336, 24, 0, 127), // Seviper (M) (Quirky) - new NPCLock(198, 06, 1, 127), // Murkrow (F) (Docile) + public static readonly TeamLock XParas = new( + 046, // Paras + new[] { + new NPCLock(336, 24, 0, 127), // Seviper (M) (Quirky) + new NPCLock(198, 06, 1, 127), // Murkrow (F) (Docile) }); - public static readonly TeamLock XGrowlithe = new( - 058, // Growlithe - new[] { - new NPCLock(336, 24, 0, 127), // Seviper (M) (Quirky) - new NPCLock(198, 06, 1, 127), // Murkrow (F) (Docile) - new NPCLock(046), // Shadow Paras + public static readonly TeamLock XGrowlithe = new( + 058, // Growlithe + new[] { + new NPCLock(336, 24, 0, 127), // Seviper (M) (Quirky) + new NPCLock(198, 06, 1, 127), // Murkrow (F) (Docile) + new NPCLock(046), // Shadow Paras }); - public static readonly TeamLock XGrowlitheParasSeen = new( - 058, // Growlithe - "Paras Seen", - new[] { - new NPCLock(336, 24, 0, 127), // Seviper (M) (Quirky) - new NPCLock(198, 06, 1, 127), // Murkrow (F) (Docile) - new NPCLock(046, true), // Shadow Paras (Seen) + public static readonly TeamLock XGrowlitheParasSeen = new( + 058, // Growlithe + "Paras Seen", + new[] { + new NPCLock(336, 24, 0, 127), // Seviper (M) (Quirky) + new NPCLock(198, 06, 1, 127), // Murkrow (F) (Docile) + new NPCLock(046, true), // Shadow Paras (Seen) }); - public static readonly TeamLock XPidgeotto = new( - 017, // Pidgeotto - new[] { - new NPCLock(015), // Shadow Beedrill - new NPCLock(162, 12, 0, 127), // Furret (M) (Serious) - new NPCLock(176, 18, 0, 031), // Togetic (M) (Bashful) + public static readonly TeamLock XPidgeotto = new( + 017, // Pidgeotto + new[] { + new NPCLock(015), // Shadow Beedrill + new NPCLock(162, 12, 0, 127), // Furret (M) (Serious) + new NPCLock(176, 18, 0, 031), // Togetic (M) (Bashful) }); - public static readonly TeamLock XPidgeottoBeedrillSeen = new( - 017, // Pidgeotto - "Beedrill Seen", - new[] { - new NPCLock(015, true), // Shadow Beedrill (Seen) - new NPCLock(162, 12, 0, 127), // Furret (M) (Serious) - new NPCLock(176, 18, 0, 031), // Togetic (M) (Bashful) - }); - - public static readonly TeamLock XTangela = new( - 114, // Tangela - new[] { - new NPCLock(038, 12, 1, 191), // Ninetales (F) (Serious) - new NPCLock(189, 06, 0, 127), // Jumpluff (M) (Docile) - new NPCLock(184, 00, 1, 127), // Azumarill (F) (Hardy) - }); - - public static readonly TeamLock XButterfree = new( - 012, // Butterfree - new[] { - new NPCLock(038, 12, 1, 191), // Ninetales (F) (Serious) - new NPCLock(189, 06, 0, 127), // Jumpluff (M) (Docile) - new NPCLock(184, 00, 1, 127), // Azumarill (F) (Hardy) - new NPCLock(114), // Shadow Tangela - }); - - public static readonly TeamLock XButterfreeTangelaSeen = new( - 012, // Butterfree - "Tangela Seen", - new[] { - new NPCLock(038, 12, 1, 191), // Ninetales (F) (Serious) - new NPCLock(189, 06, 0, 127), // Jumpluff (M) (Docile) - new NPCLock(184, 00, 1, 127), // Azumarill (F) (Hardy) - new NPCLock(114, true), // Shadow Tangela (Seen) - }); - - public static readonly TeamLock XMagneton = new( - 082, // Magneton - new[] { - new NPCLock(292, 18, 2, 255), // Shedinja (-) (Bashful) - new NPCLock(202, 00, 0, 127), // Wobbuffet (M) (Hardy) - new NPCLock(329, 12, 1, 127), // Vibrava (F) (Serious) - }); - - public static readonly TeamLock XVenomoth = new( - 049, // Venomoth - new[] { - new NPCLock(055, 18, 1, 127), // Golduck (F) (Bashful) - new NPCLock(237, 24, 0, 000), // Hitmontop (M) (Quirky) - new NPCLock(297, 12, 0, 063), // Hariyama (M) (Serious) - }); - - public static readonly TeamLock XWeepinbell = new( - 070, // Weepinbell - new[] { - new NPCLock(055, 18, 1, 127), // Golduck (F) (Bashful) - new NPCLock(237, 24, 0, 000), // Hitmontop (M) (Quirky) - new NPCLock(297, 12, 0, 063), // Hariyama (M) (Serious) - new NPCLock(049), // Shadow Venomoth - }); - - public static readonly TeamLock XWeepinbellVenomothSeen = new( - 070, // Weepinbell - "Venomoth Seen", - new[] { - new NPCLock(055, 18, 1, 127), // Golduck (F) (Bashful) - new NPCLock(237, 24, 0, 000), // Hitmontop (M) (Quirky) - new NPCLock(297, 12, 0, 063), // Hariyama (M) (Serious) - new NPCLock(049, true), // Shadow Venomoth (Seen) - }); - - public static readonly TeamLock XArbok = new( - 024, // Arbok - new[] { - new NPCLock(367, 06, 0, 127), // Huntail (M) (Docile) - new NPCLock(332, 00, 1, 127), // Cacturne (F) (Hardy) - new NPCLock(110, 12, 1, 127), // Weezing (F) (Serious) - new NPCLock(217, 18, 1, 127), // Ursaring (F) (Bashful) - }); - - public static readonly TeamLock XPrimeape = new( - 057, // Primeape - new[] { - new NPCLock(305, 18, 1, 127), // Lairon (F) (Bashful) - new NPCLock(364, 12, 1, 127), // Sealeo (F) (Serious) - new NPCLock(199, 06, 1, 127), // Slowking (F) (Docile) - new NPCLock(217, 24, 0, 127), // Ursaring (M) (Quirky) - }); - - public static readonly TeamLock XHypno = new( - 097, // Hypno - new[] { - new NPCLock(305, 18, 1, 127), // Lairon (F) (Bashful) - new NPCLock(364, 12, 1, 127), // Sealeo (F) (Serious) - new NPCLock(199, 06, 1, 127), // Slowking (F) (Docile) - new NPCLock(217, 24, 0, 127), // Ursaring (M) (Quirky) - new NPCLock(057), // Shadow Primeape - }); - - public static readonly TeamLock XHypnoPrimeapeSeen = new( - 097, // Hypno - "Primeape Seen", - new[] { - new NPCLock(305, 18, 1, 127), // Lairon (F) (Bashful) - new NPCLock(364, 12, 1, 127), // Sealeo (F) (Serious) - new NPCLock(199, 06, 1, 127), // Slowking (F) (Docile) - new NPCLock(217, 24, 0, 127), // Ursaring (M) (Quirky) - new NPCLock(057, true), // Shadow Primeape (Seen) - }); - - public static readonly TeamLock XGolduck = new( - 055, // Golduck - new[] { - new NPCLock(342, 24, 0, 127), // Crawdaunt (M) (Quirky) - new NPCLock(279, 06, 1, 127), // Pelipper (F) (Docile) - new NPCLock(226, 18, 1, 127), // Mantine (F) (Bashful) - }); - - public static readonly TeamLock XSableye = new( - 302, // Sableye - new[] { - new NPCLock(342, 24, 0, 127), // Crawdaunt (M) (Quirky) - new NPCLock(279, 06, 1, 127), // Pelipper (F) (Docile) - new NPCLock(226, 18, 1, 127), // Mantine (F) (Bashful) - new NPCLock(055), // Shadow Golduck - }); - - public static readonly TeamLock XSableyeGolduckSeen = new( - 302, // Sableye - "Golduck Seen", - new[] { - new NPCLock(342, 24, 0, 127), // Crawdaunt (M) (Quirky) - new NPCLock(279, 06, 1, 127), // Pelipper (F) (Docile) - new NPCLock(226, 18, 1, 127), // Mantine (F) (Bashful) - new NPCLock(055, true), // Shadow Golduck (Seen) - }); - - public static readonly TeamLock XDodrio = new( - 085, // Dodrio - new[] { - new NPCLock(178, 18, 1, 127), // Xatu (F) (Bashful) - }); - - public static readonly TeamLock XRaticate = new( - 020, // Raticate - new[] { - new NPCLock(178, 18, 1, 127), // Xatu (F) (Bashful) - new NPCLock(085), // Shadow Dodrio - new NPCLock(340, 18, 0, 127), // Whiscash (M) (Bashful) - }); - - public static readonly TeamLock XRaticateDodrioSeen = new( - 020, // Raticate - "Dodrio Seen", - new[] { - new NPCLock(178, 18, 1, 127), // Xatu (F) (Bashful) - new NPCLock(085, true), // Shadow Dodrio (Seen) - new NPCLock(340, 18, 0, 127), // Whiscash (M) (Bashful) - }); - - public static readonly TeamLock XFarfetchd = new( - 083, // Farfetch’d - new[] { - new NPCLock(282, 12, 0, 127), // Gardevoir (M) (Serious) - new NPCLock(368, 00, 1, 127), // Gorebyss (F) (Hardy) - new NPCLock(315, 24, 0, 127), // Roselia (M) (Quirky) - }); - - public static readonly TeamLock XAltaria = new( - 334, // Altaria - new[] { - new NPCLock(282, 12, 0, 127), // Gardevoir (M) (Serious) - new NPCLock(368, 00, 1, 127), // Gorebyss (F) (Hardy) - new NPCLock(315, 24, 0, 127), // Roselia (M) (Quirky) - new NPCLock(083), // Shadow Farfetch’d - }); - - public static readonly TeamLock XAltariaFarfetchdSeen = new( - 334, // Altaria - "Farfetch'd Seen", - new[] { - new NPCLock(282, 12, 0, 127), // Gardevoir (M) (Serious) - new NPCLock(368, 00, 1, 127), // Gorebyss (F) (Hardy) - new NPCLock(315, 24, 0, 127), // Roselia (M) (Quirky) - new NPCLock(083, true), // Shadow Farfetch’d (Seen) - }); - - public static readonly TeamLock XKangaskhan = new( - 115, // Kangaskhan - new[] { - new NPCLock(101, 00, 2, 255), // Electrode (-) (Hardy) - new NPCLock(200, 18, 1, 127), // Misdreavus (F) (Bashful) - new NPCLock(344, 12, 2, 255), // Claydol (-) (Serious) - }); - - public static readonly TeamLock XBanette = new( - 354, // Banette - new[] { - new NPCLock(101, 00, 2, 255), // Electrode (-) (Hardy) - new NPCLock(200, 18, 1, 127), // Misdreavus (F) (Bashful) - new NPCLock(344, 12, 2, 255), // Claydol (-) (Serious) - new NPCLock(115), // Shadow Kangaskhan - }); - - public static readonly TeamLock XBanetteKangaskhanSeen = new( - 354, // Banette - "Kangaskhan Seen", - new[] { - new NPCLock(101, 00, 2, 255), // Electrode (-) (Hardy) - new NPCLock(200, 18, 1, 127), // Misdreavus (F) (Bashful) - new NPCLock(344, 12, 2, 255), // Claydol (-) (Serious) - new NPCLock(115, true), // Shadow Kangaskhan (Seen) - }); - - public static readonly TeamLock XMagmar = new( - 126, // Magmar - new[] { - new NPCLock(229, 18, 0, 127), // Houndoom (M) (Bashful) - new NPCLock(038, 18, 0, 191), // Ninetales (M) (Bashful) - new NPCLock(045, 00, 1, 127), // Vileplume (F) (Hardy) - }); - - public static readonly TeamLock XPinsir = new( - 127, // Pinsir - new[] { - new NPCLock(229, 18, 0, 127), // Houndoom (M) (Bashful) - new NPCLock(038, 18, 0, 191), // Ninetales (M) (Bashful) - new NPCLock(045, 00, 1, 127), // Vileplume (F) (Hardy) - new NPCLock(126), // Shadow Magmar - }); - - public static readonly TeamLock XPinsirMagmarSeen = new( - 127, // Pinsir - "Magmar Seen", - new[] { - new NPCLock(229, 18, 0, 127), // Houndoom (M) (Bashful) - new NPCLock(038, 18, 0, 191), // Ninetales (M) (Bashful) - new NPCLock(045, 00, 1, 127), // Vileplume (F) (Hardy) - new NPCLock(126, true), // Shadow Magmar (Seen) - }); - - public static readonly TeamLock XRapidash = new( - 078, // Rapidash - new[] { - new NPCLock(323, 24, 0, 127), // Camerupt (M) (Quirky) - new NPCLock(110, 06, 0, 127), // Weezing (M) (Docile) - new NPCLock(089, 12, 1, 127), // Muk (F) (Serious) - }); - - public static readonly TeamLock XMagcargo = new( - 219, // Magcargo - new[] { - new NPCLock(323, 24, 0, 127), // Camerupt (M) (Quirky) - new NPCLock(110, 06, 0, 127), // Weezing (M) (Docile) - new NPCLock(089, 12, 1, 127), // Muk (F) (Serious) - new NPCLock(078), // Shadow Rapidash - }); - - public static readonly TeamLock XMagcargoRapidashSeen = new( - 219, // Magcargo - "Rapidash Seen", - new[] { - new NPCLock(323, 24, 0, 127), // Camerupt (M) (Quirky) - new NPCLock(110, 06, 0, 127), // Weezing (M) (Docile) - new NPCLock(089, 12, 1, 127), // Muk (F) (Serious) - new NPCLock(078, true), // Shadow Rapidash (Seen) - }); - - public static readonly TeamLock XHitmonchan = new( - 107, // Hitmonchan - new[] { - new NPCLock(308, 24, 0, 127), // Medicham (M) (Quirky) - new NPCLock(076, 06, 1, 127), // Golem (F) (Docile) - new NPCLock(178, 18, 1, 127), // Xatu (F) (Bashful) - }); - - public static readonly TeamLock XHitmonlee = new( - 106, // Hitmonlee - new[] { - new NPCLock(326, 18, 0, 127), // Grumpig (M) (Bashful) - new NPCLock(227, 12, 1, 127), // Skarmory (F) (Serious) - new NPCLock(375, 06, 2, 255), // Metang (-) (Docile) - new NPCLock(297, 24, 1, 063), // Hariyama (F) (Quirky) - }); - - public static readonly TeamLock XLickitung = new( - 108, // Lickitung - new[] { - new NPCLock(171, 24, 0, 127), // Lanturn (M) (Quirky) - new NPCLock(082, 06, 2, 255), // Magneton (-) (Docile) - }); - - public static readonly TeamLock XScyther = new( - 123, // Scyther - new[] - { - new NPCLock(234, 06, 1, 127), // Stantler (F) (Docile) - new NPCLock(295, 24, 0, 127), // Exploud (M) (Quirky) - }); - - public static readonly TeamLock XChansey = new( - 113, // Chansey - new[] { - new NPCLock(234, 06, 1, 127), // Stantler (F) (Docile) - new NPCLock(295, 24, 0, 127), // Exploud (M) (Quirky) - new NPCLock(123), // Shadow Scyther - }); - - public static readonly TeamLock XChanseyScytherSeen = new( - 113, // Chansey - "Scyther Seen", - new[] { - new NPCLock(234, 06, 1, 127), // Stantler (F) (Docile) - new NPCLock(295, 24, 0, 127), // Exploud (M) (Quirky) - new NPCLock(123, true), // Shadow Scyther (Seen) - }); - - public static readonly TeamLock XSolrock = new( - 338, // Solrock - new[] { - new NPCLock(375, 24, 2, 255), // Metang (-) (Quirky) - new NPCLock(195, 06, 0, 127), // Quagsire (M) (Docile) - new NPCLock(212, 00, 1, 127), // Scizor (F) (Hardy) - }); - - public static readonly TeamLock XStarmie = new( - 121, // Starmie - new[] { - new NPCLock(375, 24, 2, 255), // Metang (-) (Quirky) - new NPCLock(195, 06, 0, 127), // Quagsire (M) (Docile) - new NPCLock(212, 00, 1, 127), // Scizor (F) (Hardy) - new NPCLock(338), // Shadow Solrock - new NPCLock(351, 18, 0, 127), // Castform (M) (Bashful) - }); - - public static readonly TeamLock XStarmieSolrockSeen = new( - 121, // Starmie - "Solrock Seen", - new[] { - new NPCLock(375, 24, 2, 255), // Metang (-) (Quirky) - new NPCLock(195, 06, 0, 127), // Quagsire (M) (Docile) - new NPCLock(212, 00, 1, 127), // Scizor (F) (Hardy) - new NPCLock(338, true), // Shadow Solrock (Seen) - new NPCLock(351, 18, 0, 127), // Castform (M) (Bashful) - }); - - public static readonly TeamLock XElectabuzz = new( - 125, // Electabuzz - new[] { - new NPCLock(277), // Shadow Swellow - new NPCLock(065, 24, 0, 063), // Alakazam (M) (Quirky) - new NPCLock(230, 6, 1, 127), // Kingdra (F) (Docile) - new NPCLock(214, 18, 1, 127), // Heracross (F) (Bashful) - }); - - public static readonly TeamLock XElectabuzzSwellowSeen = new( - 125, // Electabuzz - "Swellow Seen", - new[] { - new NPCLock(277, true), // Shadow Swellow (Seen) - new NPCLock(065, 24, 0, 063), // Alakazam (M) (Quirky) - new NPCLock(230, 6, 1, 127), // Kingdra (F) (Docile) - new NPCLock(214, 18, 1, 127), // Heracross (F) (Bashful) - }); - - public static readonly TeamLock XSnorlax = new( - 143, // Snorlax - new[] { - new NPCLock(277), // Shadow Swellow - new NPCLock(065, 24, 0, 063), // Alakazam (M) (Quirky) - new NPCLock(230, 6, 1, 127), // Kingdra (F) (Docile) - new NPCLock(214, 18, 1, 127), // Heracross (F) (Bashful) - new NPCLock(125), // Shadow Electabuzz - }); - - public static readonly TeamLock XSnorlaxSwellowSeen = new( - 143, // Snorlax - "Swellow Seen", - new[] { - new NPCLock(277, true), // Shadow Swellow (Seen) - new NPCLock(065, 24, 0, 063), // Alakazam (M) (Quirky) - new NPCLock(230, 6, 1, 127), // Kingdra (F) (Docile) - new NPCLock(214, 18, 1, 127), // Heracross (F) (Bashful) - new NPCLock(125), // Shadow Electabuzz - }); - - public static readonly TeamLock XSnorlaxSwellowElectabuzzSeen = new( - 143, // Snorlax - "Swellow & Electabuzz Seen", - new[] { - new NPCLock(277, true), // Shadow Swellow (Seen) - new NPCLock(065, 24, 0, 063), // Alakazam (M) (Quirky) - new NPCLock(230, 6, 1, 127), // Kingdra (F) (Docile) - new NPCLock(214, 18, 1, 127), // Heracross (F) (Bashful) - new NPCLock(125, true), // Shadow Electabuzz - }); - - public static readonly TeamLock XPoliwrath = new( - 062, // Poliwrath - new[] { - new NPCLock(199, 18, 0, 127), // Slowking (M) (Bashful) - new NPCLock(217, 18, 0, 127), // Ursaring (M) (Bashful) - new NPCLock(306, 24, 0, 127), // Aggron (M) (Quirky) - new NPCLock(365, 06, 1, 127), // Walrein (F) (Docile) - }); - - public static readonly TeamLock XMrMime = new( - 122, // Mr. Mime - new[] { - new NPCLock(199, 18, 0, 127), // Slowking (M) (Bashful) - new NPCLock(217, 18, 0, 127), // Ursaring (M) (Bashful) - new NPCLock(306, 24, 0, 127), // Aggron (M) (Quirky) - new NPCLock(365, 06, 1, 127), // Walrein (F) (Docile) - new NPCLock(062), // Shadow Poliwrath - }); - - public static readonly TeamLock XMrMimePoliwrathSeen = new( - 122, // Mr. Mime - "Poliwrath Seen", - new[] { - new NPCLock(199, 18, 0, 127), // Slowking (M) (Bashful) - new NPCLock(217, 18, 0, 127), // Ursaring (M) (Bashful) - new NPCLock(306, 24, 0, 127), // Aggron (M) (Quirky) - new NPCLock(365, 06, 1, 127), // Walrein (F) (Docile) - new NPCLock(062, true), // Shadow Poliwrath (Seen) - }); - - public static readonly TeamLock XDugtrio = new( - 051, // Dugtrio - new[] { - new NPCLock(362, 00, 0, 127), // Glalie (M) (Hardy) - new NPCLock(181, 18, 0, 127), // Ampharos (M) (Bashful) - new NPCLock(286, 06, 1, 127), // Breloom (F) (Docile) - new NPCLock(232, 12, 0, 127), // Donphan (M) (Serious) - }); - - public static readonly TeamLock XManectric = new( - 310, // Manectric - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - }); - - public static readonly TeamLock XSalamence = new( - 373, // Salamence - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - new NPCLock(310), // Shadow Manectric - }); - - public static readonly TeamLock XMarowak = new( - 105, // Marowak - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - new NPCLock(310), // Shadow Manectric - new NPCLock(373), // Shadow Salamence - new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) - }); - - public static readonly TeamLock XLapras = new( - 131, // Lapras - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - new NPCLock(310), // Shadow Manectric - new NPCLock(373), // Shadow Salamence - new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) - new NPCLock(105), // Shadow Marowak - }); - - public static readonly TeamLock XSalamenceManectricSeen = new( - 373, // Salamence - "Manectric Seen", - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - new NPCLock(310, true), // Shadow Manectric (Seen) - }); - - public static readonly TeamLock XMarowakManectricSeen = new( - 105, // Marowak - "Manectric Seen", - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - new NPCLock(310, true), // Shadow Manectric (Seen) - new NPCLock(373), // Shadow Salamence - new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) - }); - - public static readonly TeamLock XMarowakManectricSalamenceSeen = new( - 105, // Marowak - "Manectric & Salamence Seen", - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - new NPCLock(310, true), // Shadow Manectric (Seen) - new NPCLock(373, true), // Shadow Salamence (Seen) - new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) - }); - - public static readonly TeamLock XLaprasManectricSeen = new( - 131, // Lapras - "Manectric Seen", - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - new NPCLock(310, true), // Shadow Manectric (Seen) - new NPCLock(373), // Shadow Salamence - new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) - new NPCLock(105), // Shadow Marowak - }); - - public static readonly TeamLock XLaprasManectricSalamenceSeen = new( - 131, // Lapras - "Manectric & Salamence Seen", - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - new NPCLock(310, true), // Shadow Manectric (Seen) - new NPCLock(373, true), // Shadow Salamence (Seen) - new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) - new NPCLock(105), // Shadow Marowak - }); - - public static readonly TeamLock XLaprasManectricMarowakSeen = new( - 131, // Lapras - "Manectric & Marowak Seen", - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - new NPCLock(310, true), // Shadow Manectric (Seen) - new NPCLock(373), // Shadow Salamence - new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) - new NPCLock(105, true), // Shadow Marowak (Seen) - }); - - public static readonly TeamLock XLaprasManectricSalamenceMarowakSeen = new( - 131, // Lapras - "Manectric & Salamence & Marowak Seen", - new[] { - new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) - new NPCLock(310, true), // Shadow Manectric (Seen) - new NPCLock(373, true), // Shadow Salamence (Seen) - new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) - new NPCLock(105, true), // Shadow Marowak (Seen) - }); - - public static readonly TeamLock XMoltres = new( - 146, // Moltres - new[] { - new NPCLock(112), // Shadow Rhydon - }); - - public static readonly TeamLock XExeggutor = new( - 103, // Exeggutor - new[] { - new NPCLock(112), // Shadow Rhydon - new NPCLock(146), // Shadow Moltres - }); - - public static readonly TeamLock XTauros = new( - 128, // Tauros - new[] { - new NPCLock(112), // Shadow Rhydon - new NPCLock(146), // Shadow Moltres - new NPCLock(103), // Shadow Exeggutor - }); - - public static readonly TeamLock XArticuno = new( - 144, // Articuno - new[] { - new NPCLock(112), // Shadow Rhydon - new NPCLock(146), // Shadow Moltres - new NPCLock(103), // Shadow Exeggutor - new NPCLock(128), // Shadow Tauros - }); - - public static readonly TeamLock XZapdos = new( - 145, // Zapdos - new[] { - new NPCLock(112), // Shadow Rhydon - new NPCLock(146), // Shadow Moltres - new NPCLock(103), // Shadow Exeggutor - new NPCLock(128), // Shadow Tauros - new NPCLock(144), // Shadow Articuno - }); - - public static readonly TeamLock XExeggutorRhydonMoltresSeen = new( - 103, // Exeggutor - "Rhydon & Moltres Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - }); - - public static readonly TeamLock XTaurosRhydonMoltresSeen = new( - 128, // Tauros - "Rhydon & Moltres Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103), // Shadow Exeggutor - }); - - public static readonly TeamLock XTaurosRhydonMoltresExeggutorSeen = new( - 128, // Tauros - "Rhydon & Moltres & Exeggutor Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103, true), // Shadow Exeggutor (Seen) - }); - - public static readonly TeamLock XArticunoRhydonMoltresSeen = new( - 144, // Articuno - "Rhydon & Moltres Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103), // Shadow Exeggutor - new NPCLock(128), // Shadow Tauros - }); - - public static readonly TeamLock XArticunoRhydonMoltresTaurosSeen = new( - 144, // Articuno - "Rhydon & Moltres & Tauros Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103), // Shadow Exeggutor - new NPCLock(128, true), // Shadow Tauros (Seen) - }); - - public static readonly TeamLock XArticunoRhydonMoltresExeggutorSeen = new( - 144, // Articuno - "Rhydon & Moltres & Exeggutor Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103, true), // Shadow Exeggutor (Seen) - new NPCLock(128), // Shadow Tauros - }); - - public static readonly TeamLock XArticunoRhydonMoltresExeggutorTaurosSeen = new( - 144, // Articuno - "Rhydon & Moltres & Exeggutor & Tauros Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103, true), // Shadow Exeggutor (Seen) - new NPCLock(128, true), // Shadow Tauros (Seen) - }); - - public static readonly TeamLock XZapdosRhydonMoltresSeen = new( - 145, // Zapdos - "Rhydon & Moltres Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103), // Shadow Exeggutor - new NPCLock(128), // Shadow Tauros - new NPCLock(144), // Shadow Articuno - }); - - public static readonly TeamLock XZapdosRhydonMoltresTaurosSeen = new( - 145, // Zapdos - "Rhydon & Moltres & Tauros Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103), // Shadow Exeggutor - new NPCLock(128, true), // Shadow Tauros (Seen) - new NPCLock(144), // Shadow Articuno - }); - - public static readonly TeamLock XZapdosRhydonMoltresArticunoSeen = new( - 145, // Zapdos - "Rhydon & Moltres & Articuno Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103), // Shadow Exeggutor - new NPCLock(128), // Shadow Tauros - new NPCLock(144, true), // Shadow Articuno (Seen) - }); - - public static readonly TeamLock XZapdosRhydonMoltresExeggutorSeen = new( - 145, // Zapdos - "Rhydon & Moltres & Exeggutor Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103, true), // Shadow Exeggutor (Seen) - new NPCLock(128), // Shadow Tauros - new NPCLock(144), // Shadow Articuno - }); - - public static readonly TeamLock XZapdosRhydonMoltresTaurosArticunoSeen = new( - 145, // Zapdos - "Rhydon & Moltres & Tauros & Articuno Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103), // Shadow Exeggutor - new NPCLock(128, true), // Shadow Tauros (Seen) - new NPCLock(144, true), // Shadow Articuno (Seen) - }); - - public static readonly TeamLock XZapdosRhydonMoltresExeggutorTaurosSeen = new( - 145, // Zapdos - "Rhydon & Moltres & Exeggutor & Tauros Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103, true), // Shadow Exeggutor (Seen) - new NPCLock(128, true), // Shadow Tauros (Seen) - new NPCLock(144), // Shadow Articuno - }); - - public static readonly TeamLock XZapdosRhydonMoltresExeggutorArticunoSeen = new( - 145, // Zapdos - "Rhydon & Moltres & Exeggutor & Articuno Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103, true), // Shadow Exeggutor (Seen) - new NPCLock(128), // Shadow Tauros - new NPCLock(144, true), // Shadow Articuno (Seen) - }); - - public static readonly TeamLock XZapdosRhydonMoltresExeggutorTaurosArticunoSeen = new( - 145, // Zapdos - "Rhydon & Moltres & Exeggutor & Tauros & Articuno Seen", - new[] { - new NPCLock(112, true), // Shadow Rhydon (Seen) - new NPCLock(146, true), // Shadow Moltres (Seen) - new NPCLock(103, true), // Shadow Exeggutor (Seen) - new NPCLock(128, true), // Shadow Tauros (Seen) - new NPCLock(144, true), // Shadow Articuno (Seen) - }); - - public static readonly TeamLock XDragonite = new( - 149, // Dragonite - new[] { - new NPCLock(272, 00, 0, 127), // Ludicolo (M) (Hardy) - new NPCLock(272, 18, 0, 127), // Ludicolo (M) (Bashful) - new NPCLock(272, 12, 1, 127), // Ludicolo (F) (Serious) - new NPCLock(272, 12, 1, 127), // Ludicolo (F) (Serious) - new NPCLock(272, 00, 0, 127), // Ludicolo (M) (Hardy) - }); - - #endregion - } + public static readonly TeamLock XPidgeottoBeedrillSeen = new( + 017, // Pidgeotto + "Beedrill Seen", + new[] { + new NPCLock(015, true), // Shadow Beedrill (Seen) + new NPCLock(162, 12, 0, 127), // Furret (M) (Serious) + new NPCLock(176, 18, 0, 031), // Togetic (M) (Bashful) + }); + + public static readonly TeamLock XTangela = new( + 114, // Tangela + new[] { + new NPCLock(038, 12, 1, 191), // Ninetales (F) (Serious) + new NPCLock(189, 06, 0, 127), // Jumpluff (M) (Docile) + new NPCLock(184, 00, 1, 127), // Azumarill (F) (Hardy) + }); + + public static readonly TeamLock XButterfree = new( + 012, // Butterfree + new[] { + new NPCLock(038, 12, 1, 191), // Ninetales (F) (Serious) + new NPCLock(189, 06, 0, 127), // Jumpluff (M) (Docile) + new NPCLock(184, 00, 1, 127), // Azumarill (F) (Hardy) + new NPCLock(114), // Shadow Tangela + }); + + public static readonly TeamLock XButterfreeTangelaSeen = new( + 012, // Butterfree + "Tangela Seen", + new[] { + new NPCLock(038, 12, 1, 191), // Ninetales (F) (Serious) + new NPCLock(189, 06, 0, 127), // Jumpluff (M) (Docile) + new NPCLock(184, 00, 1, 127), // Azumarill (F) (Hardy) + new NPCLock(114, true), // Shadow Tangela (Seen) + }); + + public static readonly TeamLock XMagneton = new( + 082, // Magneton + new[] { + new NPCLock(292, 18, 2, 255), // Shedinja (-) (Bashful) + new NPCLock(202, 00, 0, 127), // Wobbuffet (M) (Hardy) + new NPCLock(329, 12, 1, 127), // Vibrava (F) (Serious) + }); + + public static readonly TeamLock XVenomoth = new( + 049, // Venomoth + new[] { + new NPCLock(055, 18, 1, 127), // Golduck (F) (Bashful) + new NPCLock(237, 24, 0, 000), // Hitmontop (M) (Quirky) + new NPCLock(297, 12, 0, 063), // Hariyama (M) (Serious) + }); + + public static readonly TeamLock XWeepinbell = new( + 070, // Weepinbell + new[] { + new NPCLock(055, 18, 1, 127), // Golduck (F) (Bashful) + new NPCLock(237, 24, 0, 000), // Hitmontop (M) (Quirky) + new NPCLock(297, 12, 0, 063), // Hariyama (M) (Serious) + new NPCLock(049), // Shadow Venomoth + }); + + public static readonly TeamLock XWeepinbellVenomothSeen = new( + 070, // Weepinbell + "Venomoth Seen", + new[] { + new NPCLock(055, 18, 1, 127), // Golduck (F) (Bashful) + new NPCLock(237, 24, 0, 000), // Hitmontop (M) (Quirky) + new NPCLock(297, 12, 0, 063), // Hariyama (M) (Serious) + new NPCLock(049, true), // Shadow Venomoth (Seen) + }); + + public static readonly TeamLock XArbok = new( + 024, // Arbok + new[] { + new NPCLock(367, 06, 0, 127), // Huntail (M) (Docile) + new NPCLock(332, 00, 1, 127), // Cacturne (F) (Hardy) + new NPCLock(110, 12, 1, 127), // Weezing (F) (Serious) + new NPCLock(217, 18, 1, 127), // Ursaring (F) (Bashful) + }); + + public static readonly TeamLock XPrimeape = new( + 057, // Primeape + new[] { + new NPCLock(305, 18, 1, 127), // Lairon (F) (Bashful) + new NPCLock(364, 12, 1, 127), // Sealeo (F) (Serious) + new NPCLock(199, 06, 1, 127), // Slowking (F) (Docile) + new NPCLock(217, 24, 0, 127), // Ursaring (M) (Quirky) + }); + + public static readonly TeamLock XHypno = new( + 097, // Hypno + new[] { + new NPCLock(305, 18, 1, 127), // Lairon (F) (Bashful) + new NPCLock(364, 12, 1, 127), // Sealeo (F) (Serious) + new NPCLock(199, 06, 1, 127), // Slowking (F) (Docile) + new NPCLock(217, 24, 0, 127), // Ursaring (M) (Quirky) + new NPCLock(057), // Shadow Primeape + }); + + public static readonly TeamLock XHypnoPrimeapeSeen = new( + 097, // Hypno + "Primeape Seen", + new[] { + new NPCLock(305, 18, 1, 127), // Lairon (F) (Bashful) + new NPCLock(364, 12, 1, 127), // Sealeo (F) (Serious) + new NPCLock(199, 06, 1, 127), // Slowking (F) (Docile) + new NPCLock(217, 24, 0, 127), // Ursaring (M) (Quirky) + new NPCLock(057, true), // Shadow Primeape (Seen) + }); + + public static readonly TeamLock XGolduck = new( + 055, // Golduck + new[] { + new NPCLock(342, 24, 0, 127), // Crawdaunt (M) (Quirky) + new NPCLock(279, 06, 1, 127), // Pelipper (F) (Docile) + new NPCLock(226, 18, 1, 127), // Mantine (F) (Bashful) + }); + + public static readonly TeamLock XSableye = new( + 302, // Sableye + new[] { + new NPCLock(342, 24, 0, 127), // Crawdaunt (M) (Quirky) + new NPCLock(279, 06, 1, 127), // Pelipper (F) (Docile) + new NPCLock(226, 18, 1, 127), // Mantine (F) (Bashful) + new NPCLock(055), // Shadow Golduck + }); + + public static readonly TeamLock XSableyeGolduckSeen = new( + 302, // Sableye + "Golduck Seen", + new[] { + new NPCLock(342, 24, 0, 127), // Crawdaunt (M) (Quirky) + new NPCLock(279, 06, 1, 127), // Pelipper (F) (Docile) + new NPCLock(226, 18, 1, 127), // Mantine (F) (Bashful) + new NPCLock(055, true), // Shadow Golduck (Seen) + }); + + public static readonly TeamLock XDodrio = new( + 085, // Dodrio + new[] { + new NPCLock(178, 18, 1, 127), // Xatu (F) (Bashful) + }); + + public static readonly TeamLock XRaticate = new( + 020, // Raticate + new[] { + new NPCLock(178, 18, 1, 127), // Xatu (F) (Bashful) + new NPCLock(085), // Shadow Dodrio + new NPCLock(340, 18, 0, 127), // Whiscash (M) (Bashful) + }); + + public static readonly TeamLock XRaticateDodrioSeen = new( + 020, // Raticate + "Dodrio Seen", + new[] { + new NPCLock(178, 18, 1, 127), // Xatu (F) (Bashful) + new NPCLock(085, true), // Shadow Dodrio (Seen) + new NPCLock(340, 18, 0, 127), // Whiscash (M) (Bashful) + }); + + public static readonly TeamLock XFarfetchd = new( + 083, // Farfetch’d + new[] { + new NPCLock(282, 12, 0, 127), // Gardevoir (M) (Serious) + new NPCLock(368, 00, 1, 127), // Gorebyss (F) (Hardy) + new NPCLock(315, 24, 0, 127), // Roselia (M) (Quirky) + }); + + public static readonly TeamLock XAltaria = new( + 334, // Altaria + new[] { + new NPCLock(282, 12, 0, 127), // Gardevoir (M) (Serious) + new NPCLock(368, 00, 1, 127), // Gorebyss (F) (Hardy) + new NPCLock(315, 24, 0, 127), // Roselia (M) (Quirky) + new NPCLock(083), // Shadow Farfetch’d + }); + + public static readonly TeamLock XAltariaFarfetchdSeen = new( + 334, // Altaria + "Farfetch'd Seen", + new[] { + new NPCLock(282, 12, 0, 127), // Gardevoir (M) (Serious) + new NPCLock(368, 00, 1, 127), // Gorebyss (F) (Hardy) + new NPCLock(315, 24, 0, 127), // Roselia (M) (Quirky) + new NPCLock(083, true), // Shadow Farfetch’d (Seen) + }); + + public static readonly TeamLock XKangaskhan = new( + 115, // Kangaskhan + new[] { + new NPCLock(101, 00, 2, 255), // Electrode (-) (Hardy) + new NPCLock(200, 18, 1, 127), // Misdreavus (F) (Bashful) + new NPCLock(344, 12, 2, 255), // Claydol (-) (Serious) + }); + + public static readonly TeamLock XBanette = new( + 354, // Banette + new[] { + new NPCLock(101, 00, 2, 255), // Electrode (-) (Hardy) + new NPCLock(200, 18, 1, 127), // Misdreavus (F) (Bashful) + new NPCLock(344, 12, 2, 255), // Claydol (-) (Serious) + new NPCLock(115), // Shadow Kangaskhan + }); + + public static readonly TeamLock XBanetteKangaskhanSeen = new( + 354, // Banette + "Kangaskhan Seen", + new[] { + new NPCLock(101, 00, 2, 255), // Electrode (-) (Hardy) + new NPCLock(200, 18, 1, 127), // Misdreavus (F) (Bashful) + new NPCLock(344, 12, 2, 255), // Claydol (-) (Serious) + new NPCLock(115, true), // Shadow Kangaskhan (Seen) + }); + + public static readonly TeamLock XMagmar = new( + 126, // Magmar + new[] { + new NPCLock(229, 18, 0, 127), // Houndoom (M) (Bashful) + new NPCLock(038, 18, 0, 191), // Ninetales (M) (Bashful) + new NPCLock(045, 00, 1, 127), // Vileplume (F) (Hardy) + }); + + public static readonly TeamLock XPinsir = new( + 127, // Pinsir + new[] { + new NPCLock(229, 18, 0, 127), // Houndoom (M) (Bashful) + new NPCLock(038, 18, 0, 191), // Ninetales (M) (Bashful) + new NPCLock(045, 00, 1, 127), // Vileplume (F) (Hardy) + new NPCLock(126), // Shadow Magmar + }); + + public static readonly TeamLock XPinsirMagmarSeen = new( + 127, // Pinsir + "Magmar Seen", + new[] { + new NPCLock(229, 18, 0, 127), // Houndoom (M) (Bashful) + new NPCLock(038, 18, 0, 191), // Ninetales (M) (Bashful) + new NPCLock(045, 00, 1, 127), // Vileplume (F) (Hardy) + new NPCLock(126, true), // Shadow Magmar (Seen) + }); + + public static readonly TeamLock XRapidash = new( + 078, // Rapidash + new[] { + new NPCLock(323, 24, 0, 127), // Camerupt (M) (Quirky) + new NPCLock(110, 06, 0, 127), // Weezing (M) (Docile) + new NPCLock(089, 12, 1, 127), // Muk (F) (Serious) + }); + + public static readonly TeamLock XMagcargo = new( + 219, // Magcargo + new[] { + new NPCLock(323, 24, 0, 127), // Camerupt (M) (Quirky) + new NPCLock(110, 06, 0, 127), // Weezing (M) (Docile) + new NPCLock(089, 12, 1, 127), // Muk (F) (Serious) + new NPCLock(078), // Shadow Rapidash + }); + + public static readonly TeamLock XMagcargoRapidashSeen = new( + 219, // Magcargo + "Rapidash Seen", + new[] { + new NPCLock(323, 24, 0, 127), // Camerupt (M) (Quirky) + new NPCLock(110, 06, 0, 127), // Weezing (M) (Docile) + new NPCLock(089, 12, 1, 127), // Muk (F) (Serious) + new NPCLock(078, true), // Shadow Rapidash (Seen) + }); + + public static readonly TeamLock XHitmonchan = new( + 107, // Hitmonchan + new[] { + new NPCLock(308, 24, 0, 127), // Medicham (M) (Quirky) + new NPCLock(076, 06, 1, 127), // Golem (F) (Docile) + new NPCLock(178, 18, 1, 127), // Xatu (F) (Bashful) + }); + + public static readonly TeamLock XHitmonlee = new( + 106, // Hitmonlee + new[] { + new NPCLock(326, 18, 0, 127), // Grumpig (M) (Bashful) + new NPCLock(227, 12, 1, 127), // Skarmory (F) (Serious) + new NPCLock(375, 06, 2, 255), // Metang (-) (Docile) + new NPCLock(297, 24, 1, 063), // Hariyama (F) (Quirky) + }); + + public static readonly TeamLock XLickitung = new( + 108, // Lickitung + new[] { + new NPCLock(171, 24, 0, 127), // Lanturn (M) (Quirky) + new NPCLock(082, 06, 2, 255), // Magneton (-) (Docile) + }); + + public static readonly TeamLock XScyther = new( + 123, // Scyther + new[] + { + new NPCLock(234, 06, 1, 127), // Stantler (F) (Docile) + new NPCLock(295, 24, 0, 127), // Exploud (M) (Quirky) + }); + + public static readonly TeamLock XChansey = new( + 113, // Chansey + new[] { + new NPCLock(234, 06, 1, 127), // Stantler (F) (Docile) + new NPCLock(295, 24, 0, 127), // Exploud (M) (Quirky) + new NPCLock(123), // Shadow Scyther + }); + + public static readonly TeamLock XChanseyScytherSeen = new( + 113, // Chansey + "Scyther Seen", + new[] { + new NPCLock(234, 06, 1, 127), // Stantler (F) (Docile) + new NPCLock(295, 24, 0, 127), // Exploud (M) (Quirky) + new NPCLock(123, true), // Shadow Scyther (Seen) + }); + + public static readonly TeamLock XSolrock = new( + 338, // Solrock + new[] { + new NPCLock(375, 24, 2, 255), // Metang (-) (Quirky) + new NPCLock(195, 06, 0, 127), // Quagsire (M) (Docile) + new NPCLock(212, 00, 1, 127), // Scizor (F) (Hardy) + }); + + public static readonly TeamLock XStarmie = new( + 121, // Starmie + new[] { + new NPCLock(375, 24, 2, 255), // Metang (-) (Quirky) + new NPCLock(195, 06, 0, 127), // Quagsire (M) (Docile) + new NPCLock(212, 00, 1, 127), // Scizor (F) (Hardy) + new NPCLock(338), // Shadow Solrock + new NPCLock(351, 18, 0, 127), // Castform (M) (Bashful) + }); + + public static readonly TeamLock XStarmieSolrockSeen = new( + 121, // Starmie + "Solrock Seen", + new[] { + new NPCLock(375, 24, 2, 255), // Metang (-) (Quirky) + new NPCLock(195, 06, 0, 127), // Quagsire (M) (Docile) + new NPCLock(212, 00, 1, 127), // Scizor (F) (Hardy) + new NPCLock(338, true), // Shadow Solrock (Seen) + new NPCLock(351, 18, 0, 127), // Castform (M) (Bashful) + }); + + public static readonly TeamLock XElectabuzz = new( + 125, // Electabuzz + new[] { + new NPCLock(277), // Shadow Swellow + new NPCLock(065, 24, 0, 063), // Alakazam (M) (Quirky) + new NPCLock(230, 6, 1, 127), // Kingdra (F) (Docile) + new NPCLock(214, 18, 1, 127), // Heracross (F) (Bashful) + }); + + public static readonly TeamLock XElectabuzzSwellowSeen = new( + 125, // Electabuzz + "Swellow Seen", + new[] { + new NPCLock(277, true), // Shadow Swellow (Seen) + new NPCLock(065, 24, 0, 063), // Alakazam (M) (Quirky) + new NPCLock(230, 6, 1, 127), // Kingdra (F) (Docile) + new NPCLock(214, 18, 1, 127), // Heracross (F) (Bashful) + }); + + public static readonly TeamLock XSnorlax = new( + 143, // Snorlax + new[] { + new NPCLock(277), // Shadow Swellow + new NPCLock(065, 24, 0, 063), // Alakazam (M) (Quirky) + new NPCLock(230, 6, 1, 127), // Kingdra (F) (Docile) + new NPCLock(214, 18, 1, 127), // Heracross (F) (Bashful) + new NPCLock(125), // Shadow Electabuzz + }); + + public static readonly TeamLock XSnorlaxSwellowSeen = new( + 143, // Snorlax + "Swellow Seen", + new[] { + new NPCLock(277, true), // Shadow Swellow (Seen) + new NPCLock(065, 24, 0, 063), // Alakazam (M) (Quirky) + new NPCLock(230, 6, 1, 127), // Kingdra (F) (Docile) + new NPCLock(214, 18, 1, 127), // Heracross (F) (Bashful) + new NPCLock(125), // Shadow Electabuzz + }); + + public static readonly TeamLock XSnorlaxSwellowElectabuzzSeen = new( + 143, // Snorlax + "Swellow & Electabuzz Seen", + new[] { + new NPCLock(277, true), // Shadow Swellow (Seen) + new NPCLock(065, 24, 0, 063), // Alakazam (M) (Quirky) + new NPCLock(230, 6, 1, 127), // Kingdra (F) (Docile) + new NPCLock(214, 18, 1, 127), // Heracross (F) (Bashful) + new NPCLock(125, true), // Shadow Electabuzz + }); + + public static readonly TeamLock XPoliwrath = new( + 062, // Poliwrath + new[] { + new NPCLock(199, 18, 0, 127), // Slowking (M) (Bashful) + new NPCLock(217, 18, 0, 127), // Ursaring (M) (Bashful) + new NPCLock(306, 24, 0, 127), // Aggron (M) (Quirky) + new NPCLock(365, 06, 1, 127), // Walrein (F) (Docile) + }); + + public static readonly TeamLock XMrMime = new( + 122, // Mr. Mime + new[] { + new NPCLock(199, 18, 0, 127), // Slowking (M) (Bashful) + new NPCLock(217, 18, 0, 127), // Ursaring (M) (Bashful) + new NPCLock(306, 24, 0, 127), // Aggron (M) (Quirky) + new NPCLock(365, 06, 1, 127), // Walrein (F) (Docile) + new NPCLock(062), // Shadow Poliwrath + }); + + public static readonly TeamLock XMrMimePoliwrathSeen = new( + 122, // Mr. Mime + "Poliwrath Seen", + new[] { + new NPCLock(199, 18, 0, 127), // Slowking (M) (Bashful) + new NPCLock(217, 18, 0, 127), // Ursaring (M) (Bashful) + new NPCLock(306, 24, 0, 127), // Aggron (M) (Quirky) + new NPCLock(365, 06, 1, 127), // Walrein (F) (Docile) + new NPCLock(062, true), // Shadow Poliwrath (Seen) + }); + + public static readonly TeamLock XDugtrio = new( + 051, // Dugtrio + new[] { + new NPCLock(362, 00, 0, 127), // Glalie (M) (Hardy) + new NPCLock(181, 18, 0, 127), // Ampharos (M) (Bashful) + new NPCLock(286, 06, 1, 127), // Breloom (F) (Docile) + new NPCLock(232, 12, 0, 127), // Donphan (M) (Serious) + }); + + public static readonly TeamLock XManectric = new( + 310, // Manectric + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + }); + + public static readonly TeamLock XSalamence = new( + 373, // Salamence + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + new NPCLock(310), // Shadow Manectric + }); + + public static readonly TeamLock XMarowak = new( + 105, // Marowak + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + new NPCLock(310), // Shadow Manectric + new NPCLock(373), // Shadow Salamence + new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) + }); + + public static readonly TeamLock XLapras = new( + 131, // Lapras + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + new NPCLock(310), // Shadow Manectric + new NPCLock(373), // Shadow Salamence + new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) + new NPCLock(105), // Shadow Marowak + }); + + public static readonly TeamLock XSalamenceManectricSeen = new( + 373, // Salamence + "Manectric Seen", + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + new NPCLock(310, true), // Shadow Manectric (Seen) + }); + + public static readonly TeamLock XMarowakManectricSeen = new( + 105, // Marowak + "Manectric Seen", + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + new NPCLock(310, true), // Shadow Manectric (Seen) + new NPCLock(373), // Shadow Salamence + new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) + }); + + public static readonly TeamLock XMarowakManectricSalamenceSeen = new( + 105, // Marowak + "Manectric & Salamence Seen", + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + new NPCLock(310, true), // Shadow Manectric (Seen) + new NPCLock(373, true), // Shadow Salamence (Seen) + new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) + }); + + public static readonly TeamLock XLaprasManectricSeen = new( + 131, // Lapras + "Manectric Seen", + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + new NPCLock(310, true), // Shadow Manectric (Seen) + new NPCLock(373), // Shadow Salamence + new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) + new NPCLock(105), // Shadow Marowak + }); + + public static readonly TeamLock XLaprasManectricSalamenceSeen = new( + 131, // Lapras + "Manectric & Salamence Seen", + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + new NPCLock(310, true), // Shadow Manectric (Seen) + new NPCLock(373, true), // Shadow Salamence (Seen) + new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) + new NPCLock(105), // Shadow Marowak + }); + + public static readonly TeamLock XLaprasManectricMarowakSeen = new( + 131, // Lapras + "Manectric & Marowak Seen", + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + new NPCLock(310, true), // Shadow Manectric (Seen) + new NPCLock(373), // Shadow Salamence + new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) + new NPCLock(105, true), // Shadow Marowak (Seen) + }); + + public static readonly TeamLock XLaprasManectricSalamenceMarowakSeen = new( + 131, // Lapras + "Manectric & Salamence & Marowak Seen", + new[] { + new NPCLock(291, 06, 1, 127), // Ninjask (F) (Docile) + new NPCLock(310, true), // Shadow Manectric (Seen) + new NPCLock(373, true), // Shadow Salamence (Seen) + new NPCLock(330, 24, 0, 127), // Flygon (M) (Quirky) + new NPCLock(105, true), // Shadow Marowak (Seen) + }); + + public static readonly TeamLock XMoltres = new( + 146, // Moltres + new[] { + new NPCLock(112), // Shadow Rhydon + }); + + public static readonly TeamLock XExeggutor = new( + 103, // Exeggutor + new[] { + new NPCLock(112), // Shadow Rhydon + new NPCLock(146), // Shadow Moltres + }); + + public static readonly TeamLock XTauros = new( + 128, // Tauros + new[] { + new NPCLock(112), // Shadow Rhydon + new NPCLock(146), // Shadow Moltres + new NPCLock(103), // Shadow Exeggutor + }); + + public static readonly TeamLock XArticuno = new( + 144, // Articuno + new[] { + new NPCLock(112), // Shadow Rhydon + new NPCLock(146), // Shadow Moltres + new NPCLock(103), // Shadow Exeggutor + new NPCLock(128), // Shadow Tauros + }); + + public static readonly TeamLock XZapdos = new( + 145, // Zapdos + new[] { + new NPCLock(112), // Shadow Rhydon + new NPCLock(146), // Shadow Moltres + new NPCLock(103), // Shadow Exeggutor + new NPCLock(128), // Shadow Tauros + new NPCLock(144), // Shadow Articuno + }); + + public static readonly TeamLock XExeggutorRhydonMoltresSeen = new( + 103, // Exeggutor + "Rhydon & Moltres Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + }); + + public static readonly TeamLock XTaurosRhydonMoltresSeen = new( + 128, // Tauros + "Rhydon & Moltres Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103), // Shadow Exeggutor + }); + + public static readonly TeamLock XTaurosRhydonMoltresExeggutorSeen = new( + 128, // Tauros + "Rhydon & Moltres & Exeggutor Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103, true), // Shadow Exeggutor (Seen) + }); + + public static readonly TeamLock XArticunoRhydonMoltresSeen = new( + 144, // Articuno + "Rhydon & Moltres Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103), // Shadow Exeggutor + new NPCLock(128), // Shadow Tauros + }); + + public static readonly TeamLock XArticunoRhydonMoltresTaurosSeen = new( + 144, // Articuno + "Rhydon & Moltres & Tauros Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103), // Shadow Exeggutor + new NPCLock(128, true), // Shadow Tauros (Seen) + }); + + public static readonly TeamLock XArticunoRhydonMoltresExeggutorSeen = new( + 144, // Articuno + "Rhydon & Moltres & Exeggutor Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103, true), // Shadow Exeggutor (Seen) + new NPCLock(128), // Shadow Tauros + }); + + public static readonly TeamLock XArticunoRhydonMoltresExeggutorTaurosSeen = new( + 144, // Articuno + "Rhydon & Moltres & Exeggutor & Tauros Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103, true), // Shadow Exeggutor (Seen) + new NPCLock(128, true), // Shadow Tauros (Seen) + }); + + public static readonly TeamLock XZapdosRhydonMoltresSeen = new( + 145, // Zapdos + "Rhydon & Moltres Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103), // Shadow Exeggutor + new NPCLock(128), // Shadow Tauros + new NPCLock(144), // Shadow Articuno + }); + + public static readonly TeamLock XZapdosRhydonMoltresTaurosSeen = new( + 145, // Zapdos + "Rhydon & Moltres & Tauros Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103), // Shadow Exeggutor + new NPCLock(128, true), // Shadow Tauros (Seen) + new NPCLock(144), // Shadow Articuno + }); + + public static readonly TeamLock XZapdosRhydonMoltresArticunoSeen = new( + 145, // Zapdos + "Rhydon & Moltres & Articuno Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103), // Shadow Exeggutor + new NPCLock(128), // Shadow Tauros + new NPCLock(144, true), // Shadow Articuno (Seen) + }); + + public static readonly TeamLock XZapdosRhydonMoltresExeggutorSeen = new( + 145, // Zapdos + "Rhydon & Moltres & Exeggutor Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103, true), // Shadow Exeggutor (Seen) + new NPCLock(128), // Shadow Tauros + new NPCLock(144), // Shadow Articuno + }); + + public static readonly TeamLock XZapdosRhydonMoltresTaurosArticunoSeen = new( + 145, // Zapdos + "Rhydon & Moltres & Tauros & Articuno Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103), // Shadow Exeggutor + new NPCLock(128, true), // Shadow Tauros (Seen) + new NPCLock(144, true), // Shadow Articuno (Seen) + }); + + public static readonly TeamLock XZapdosRhydonMoltresExeggutorTaurosSeen = new( + 145, // Zapdos + "Rhydon & Moltres & Exeggutor & Tauros Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103, true), // Shadow Exeggutor (Seen) + new NPCLock(128, true), // Shadow Tauros (Seen) + new NPCLock(144), // Shadow Articuno + }); + + public static readonly TeamLock XZapdosRhydonMoltresExeggutorArticunoSeen = new( + 145, // Zapdos + "Rhydon & Moltres & Exeggutor & Articuno Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103, true), // Shadow Exeggutor (Seen) + new NPCLock(128), // Shadow Tauros + new NPCLock(144, true), // Shadow Articuno (Seen) + }); + + public static readonly TeamLock XZapdosRhydonMoltresExeggutorTaurosArticunoSeen = new( + 145, // Zapdos + "Rhydon & Moltres & Exeggutor & Tauros & Articuno Seen", + new[] { + new NPCLock(112, true), // Shadow Rhydon (Seen) + new NPCLock(146, true), // Shadow Moltres (Seen) + new NPCLock(103, true), // Shadow Exeggutor (Seen) + new NPCLock(128, true), // Shadow Tauros (Seen) + new NPCLock(144, true), // Shadow Articuno (Seen) + }); + + public static readonly TeamLock XDragonite = new( + 149, // Dragonite + new[] { + new NPCLock(272, 00, 0, 127), // Ludicolo (M) (Hardy) + new NPCLock(272, 18, 0, 127), // Ludicolo (M) (Bashful) + new NPCLock(272, 12, 1, 127), // Ludicolo (F) (Serious) + new NPCLock(272, 12, 1, 127), // Ludicolo (F) (Serious) + new NPCLock(272, 00, 0, 127), // Ludicolo (M) (Hardy) + }); + + #endregion } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters3Teams.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters3Teams.cs index 3026542ba..8ede13bd3 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters3Teams.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters3Teams.cs @@ -1,99 +1,98 @@ using System; using static PKHeX.Core.Encounters3Shadow; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Permutations of teams, where a team may have multiple shadow Pokémon or alternate/re-battle scenarios. +/// +public static class Encounters3Teams { - /// - /// Permutations of teams, where a team may have multiple shadow Pokémon or alternate/re-battle scenarios. - /// - public static class Encounters3Teams - { - public static readonly TeamLock[] First = Array.Empty(); + public static readonly TeamLock[] First = Array.Empty(); - // Colo - public static readonly TeamLock[] ColoMakuhita = { CMakuhita }; - public static readonly TeamLock[] Gligar = { CGligar }; - public static readonly TeamLock[] Murkrow = { CMurkrow }; - public static readonly TeamLock[] Heracross = { CHeracross }; - public static readonly TeamLock[] Ursaring = { CUrsaring }; + // Colo + public static readonly TeamLock[] ColoMakuhita = { CMakuhita }; + public static readonly TeamLock[] Gligar = { CGligar }; + public static readonly TeamLock[] Murkrow = { CMurkrow }; + public static readonly TeamLock[] Heracross = { CHeracross }; + public static readonly TeamLock[] Ursaring = { CUrsaring }; - // E-Reader - public static readonly TeamLock[] CTogepi = {ETogepi}; - public static readonly TeamLock[] CMareep = {EMareep}; - public static readonly TeamLock[] CScizor = {EScizor}; + // E-Reader + public static readonly TeamLock[] CTogepi = {ETogepi}; + public static readonly TeamLock[] CMareep = {EMareep}; + public static readonly TeamLock[] CScizor = {EScizor}; - // XD - public static readonly TeamLock[] Spheal = { XSphealCipherLab, XSphealPhenacCityandPost }; - public static readonly TeamLock[] Seedot = { XSeedotCipherLab, XSeedotPhenacCity, XSeedotPost }; + // XD + public static readonly TeamLock[] Spheal = { XSphealCipherLab, XSphealPhenacCityandPost }; + public static readonly TeamLock[] Seedot = { XSeedotCipherLab, XSeedotPhenacCity, XSeedotPost }; - public static readonly TeamLock[] Ralts = { XRalts }; - public static readonly TeamLock[] Poochyena = { XPoochyena }; - public static readonly TeamLock[] Ledyba = { XLedyba }; - public static readonly TeamLock[] Gulpin = { XGulpin }; - public static readonly TeamLock[] Spinarak = { XSpinarak }; - public static readonly TeamLock[] Numel = { XNumel }; - public static readonly TeamLock[] Shroomish = { XShroomish }; - public static readonly TeamLock[] Delcatty = { XDelcatty }; - public static readonly TeamLock[] Voltorb = { XVoltorb }; - public static readonly TeamLock[] Makuhita = { XMakuhita }; - public static readonly TeamLock[] Vulpix = { XVulpix }; - public static readonly TeamLock[] Duskull = { XDuskull }; - public static readonly TeamLock[] Mawile = { XMawile }; - public static readonly TeamLock[] Snorunt = { XSnorunt }; - public static readonly TeamLock[] Pineco = { XPineco }; - public static readonly TeamLock[] Natu = { XNatu }; - public static readonly TeamLock[] Roselia = { XRoselia }; - public static readonly TeamLock[] Meowth = { XMeowth }; - public static readonly TeamLock[] Swinub = { XSwinub }; - public static readonly TeamLock[] Spearow = { XSpearow }; - public static readonly TeamLock[] Grimer = { XGrimer }; - public static readonly TeamLock[] Seel = { XSeel }; - public static readonly TeamLock[] Lunatone = { XLunatone }; - public static readonly TeamLock[] Nosepass = { XNosepass }; - public static readonly TeamLock[] Paras = { XParas }; - public static readonly TeamLock[] Growlithe = { XGrowlithe, XGrowlitheParasSeen }; - public static readonly TeamLock[] Pidgeotto = { XPidgeotto, XPidgeottoBeedrillSeen }; - public static readonly TeamLock[] Tangela = { XTangela }; - public static readonly TeamLock[] Butterfree = { XButterfree, XButterfreeTangelaSeen }; - public static readonly TeamLock[] Magneton = { XMagneton }; - public static readonly TeamLock[] Venomoth = { XVenomoth }; - public static readonly TeamLock[] Weepinbell = { XWeepinbell, XWeepinbellVenomothSeen }; - public static readonly TeamLock[] Arbok = { XArbok }; - public static readonly TeamLock[] Primeape = { XPrimeape }; - public static readonly TeamLock[] Hypno = { XHypno, XHypnoPrimeapeSeen }; - public static readonly TeamLock[] Golduck = { XGolduck }; - public static readonly TeamLock[] Sableye = { XSableye, XSableyeGolduckSeen }; - public static readonly TeamLock[] Dodrio = { XDodrio }; - public static readonly TeamLock[] Raticate = { XRaticate, XRaticateDodrioSeen }; - public static readonly TeamLock[] Farfetchd = { XFarfetchd }; - public static readonly TeamLock[] Altaria = { XAltaria, XAltariaFarfetchdSeen }; - public static readonly TeamLock[] Kangaskhan = { XKangaskhan }; - public static readonly TeamLock[] Banette = { XBanette, XBanetteKangaskhanSeen }; - public static readonly TeamLock[] Magmar = { XMagmar }; - public static readonly TeamLock[] Pinsir = { XPinsir, XPinsirMagmarSeen }; - public static readonly TeamLock[] Rapidash = { XRapidash }; - public static readonly TeamLock[] Magcargo = { XMagcargo, XMagcargoRapidashSeen }; - public static readonly TeamLock[] Hitmonchan = { XHitmonchan }; - public static readonly TeamLock[] Hitmonlee = { XHitmonlee }; - public static readonly TeamLock[] Lickitung = { XLickitung }; - public static readonly TeamLock[] Scyther = { XScyther }; - public static readonly TeamLock[] Chansey = { XChansey, XChanseyScytherSeen }; - public static readonly TeamLock[] Solrock = { XSolrock }; - public static readonly TeamLock[] Starmie = { XStarmie, XStarmieSolrockSeen }; - public static readonly TeamLock[] Electabuzz = { XElectabuzz, XElectabuzzSwellowSeen }; - public static readonly TeamLock[] Snorlax = { XSnorlax, XSnorlaxSwellowSeen, XSnorlaxSwellowElectabuzzSeen }; - public static readonly TeamLock[] Poliwrath = { XPoliwrath }; - public static readonly TeamLock[] MrMime = { XMrMime, XMrMimePoliwrathSeen }; - public static readonly TeamLock[] Dugtrio = { XDugtrio }; - public static readonly TeamLock[] Manectric = { XManectric }; - public static readonly TeamLock[] Salamence = { XSalamence, XSalamenceManectricSeen }; - public static readonly TeamLock[] Marowak = { XMarowak, XMarowakManectricSeen, XMarowakManectricSalamenceSeen }; - public static readonly TeamLock[] Lapras = { XLapras, XLaprasManectricSeen, XLaprasManectricSalamenceSeen, XLaprasManectricMarowakSeen, XLaprasManectricSalamenceMarowakSeen }; - public static readonly TeamLock[] Moltres = { XMoltres }; - public static readonly TeamLock[] Exeggutor = { XExeggutor, XExeggutorRhydonMoltresSeen }; - public static readonly TeamLock[] Tauros = { XTauros, XTaurosRhydonMoltresSeen, XTaurosRhydonMoltresExeggutorSeen }; - public static readonly TeamLock[] Articuno = { XArticuno, XArticunoRhydonMoltresSeen, XArticunoRhydonMoltresTaurosSeen, XArticunoRhydonMoltresExeggutorSeen, XArticunoRhydonMoltresExeggutorTaurosSeen }; - public static readonly TeamLock[] Zapdos = { XZapdos, XZapdosRhydonMoltresSeen, XZapdosRhydonMoltresTaurosSeen, XZapdosRhydonMoltresArticunoSeen, XZapdosRhydonMoltresExeggutorSeen, XZapdosRhydonMoltresTaurosArticunoSeen, XZapdosRhydonMoltresExeggutorTaurosSeen, XZapdosRhydonMoltresExeggutorArticunoSeen, XZapdosRhydonMoltresExeggutorTaurosArticunoSeen }; - public static readonly TeamLock[] Dragonite = { XDragonite }; - } + public static readonly TeamLock[] Ralts = { XRalts }; + public static readonly TeamLock[] Poochyena = { XPoochyena }; + public static readonly TeamLock[] Ledyba = { XLedyba }; + public static readonly TeamLock[] Gulpin = { XGulpin }; + public static readonly TeamLock[] Spinarak = { XSpinarak }; + public static readonly TeamLock[] Numel = { XNumel }; + public static readonly TeamLock[] Shroomish = { XShroomish }; + public static readonly TeamLock[] Delcatty = { XDelcatty }; + public static readonly TeamLock[] Voltorb = { XVoltorb }; + public static readonly TeamLock[] Makuhita = { XMakuhita }; + public static readonly TeamLock[] Vulpix = { XVulpix }; + public static readonly TeamLock[] Duskull = { XDuskull }; + public static readonly TeamLock[] Mawile = { XMawile }; + public static readonly TeamLock[] Snorunt = { XSnorunt }; + public static readonly TeamLock[] Pineco = { XPineco }; + public static readonly TeamLock[] Natu = { XNatu }; + public static readonly TeamLock[] Roselia = { XRoselia }; + public static readonly TeamLock[] Meowth = { XMeowth }; + public static readonly TeamLock[] Swinub = { XSwinub }; + public static readonly TeamLock[] Spearow = { XSpearow }; + public static readonly TeamLock[] Grimer = { XGrimer }; + public static readonly TeamLock[] Seel = { XSeel }; + public static readonly TeamLock[] Lunatone = { XLunatone }; + public static readonly TeamLock[] Nosepass = { XNosepass }; + public static readonly TeamLock[] Paras = { XParas }; + public static readonly TeamLock[] Growlithe = { XGrowlithe, XGrowlitheParasSeen }; + public static readonly TeamLock[] Pidgeotto = { XPidgeotto, XPidgeottoBeedrillSeen }; + public static readonly TeamLock[] Tangela = { XTangela }; + public static readonly TeamLock[] Butterfree = { XButterfree, XButterfreeTangelaSeen }; + public static readonly TeamLock[] Magneton = { XMagneton }; + public static readonly TeamLock[] Venomoth = { XVenomoth }; + public static readonly TeamLock[] Weepinbell = { XWeepinbell, XWeepinbellVenomothSeen }; + public static readonly TeamLock[] Arbok = { XArbok }; + public static readonly TeamLock[] Primeape = { XPrimeape }; + public static readonly TeamLock[] Hypno = { XHypno, XHypnoPrimeapeSeen }; + public static readonly TeamLock[] Golduck = { XGolduck }; + public static readonly TeamLock[] Sableye = { XSableye, XSableyeGolduckSeen }; + public static readonly TeamLock[] Dodrio = { XDodrio }; + public static readonly TeamLock[] Raticate = { XRaticate, XRaticateDodrioSeen }; + public static readonly TeamLock[] Farfetchd = { XFarfetchd }; + public static readonly TeamLock[] Altaria = { XAltaria, XAltariaFarfetchdSeen }; + public static readonly TeamLock[] Kangaskhan = { XKangaskhan }; + public static readonly TeamLock[] Banette = { XBanette, XBanetteKangaskhanSeen }; + public static readonly TeamLock[] Magmar = { XMagmar }; + public static readonly TeamLock[] Pinsir = { XPinsir, XPinsirMagmarSeen }; + public static readonly TeamLock[] Rapidash = { XRapidash }; + public static readonly TeamLock[] Magcargo = { XMagcargo, XMagcargoRapidashSeen }; + public static readonly TeamLock[] Hitmonchan = { XHitmonchan }; + public static readonly TeamLock[] Hitmonlee = { XHitmonlee }; + public static readonly TeamLock[] Lickitung = { XLickitung }; + public static readonly TeamLock[] Scyther = { XScyther }; + public static readonly TeamLock[] Chansey = { XChansey, XChanseyScytherSeen }; + public static readonly TeamLock[] Solrock = { XSolrock }; + public static readonly TeamLock[] Starmie = { XStarmie, XStarmieSolrockSeen }; + public static readonly TeamLock[] Electabuzz = { XElectabuzz, XElectabuzzSwellowSeen }; + public static readonly TeamLock[] Snorlax = { XSnorlax, XSnorlaxSwellowSeen, XSnorlaxSwellowElectabuzzSeen }; + public static readonly TeamLock[] Poliwrath = { XPoliwrath }; + public static readonly TeamLock[] MrMime = { XMrMime, XMrMimePoliwrathSeen }; + public static readonly TeamLock[] Dugtrio = { XDugtrio }; + public static readonly TeamLock[] Manectric = { XManectric }; + public static readonly TeamLock[] Salamence = { XSalamence, XSalamenceManectricSeen }; + public static readonly TeamLock[] Marowak = { XMarowak, XMarowakManectricSeen, XMarowakManectricSalamenceSeen }; + public static readonly TeamLock[] Lapras = { XLapras, XLaprasManectricSeen, XLaprasManectricSalamenceSeen, XLaprasManectricMarowakSeen, XLaprasManectricSalamenceMarowakSeen }; + public static readonly TeamLock[] Moltres = { XMoltres }; + public static readonly TeamLock[] Exeggutor = { XExeggutor, XExeggutorRhydonMoltresSeen }; + public static readonly TeamLock[] Tauros = { XTauros, XTaurosRhydonMoltresSeen, XTaurosRhydonMoltresExeggutorSeen }; + public static readonly TeamLock[] Articuno = { XArticuno, XArticunoRhydonMoltresSeen, XArticunoRhydonMoltresTaurosSeen, XArticunoRhydonMoltresExeggutorSeen, XArticunoRhydonMoltresExeggutorTaurosSeen }; + public static readonly TeamLock[] Zapdos = { XZapdos, XZapdosRhydonMoltresSeen, XZapdosRhydonMoltresTaurosSeen, XZapdosRhydonMoltresArticunoSeen, XZapdosRhydonMoltresExeggutorSeen, XZapdosRhydonMoltresTaurosArticunoSeen, XZapdosRhydonMoltresExeggutorTaurosSeen, XZapdosRhydonMoltresExeggutorArticunoSeen, XZapdosRhydonMoltresExeggutorTaurosArticunoSeen }; + public static readonly TeamLock[] Dragonite = { XDragonite }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs index 50dc6d8a9..f2184ca2d 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters4.cs @@ -1,439 +1,438 @@ using static PKHeX.Core.EncounterUtil; using static PKHeX.Core.GameVersion; -using static PKHeX.Core.GroundTilePermission; +using static PKHeX.Core.GroundTileAllowed; using static PKHeX.Core.AbilityPermission; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 Encounters +/// +internal static class Encounters4 { - /// - /// Generation 4 Encounters - /// - internal static class Encounters4 + internal static readonly EncounterArea4[] SlotsD = EncounterArea4.GetAreas(Get("d", "da"), D); + internal static readonly EncounterArea4[] SlotsP = EncounterArea4.GetAreas(Get("p", "pe"), P); + internal static readonly EncounterArea4[] SlotsPt = EncounterArea4.GetAreas(Get("pt", "pt"), Pt); + internal static readonly EncounterArea4[] SlotsHG = EncounterArea4.GetAreas(Get("hg", "hg"), HG); + internal static readonly EncounterArea4[] SlotsSS = EncounterArea4.GetAreas(Get("ss", "ss"), SS); + + static Encounters4() { - internal static readonly EncounterArea4[] SlotsD = EncounterArea4.GetAreas(Get("d", "da"), D); - internal static readonly EncounterArea4[] SlotsP = EncounterArea4.GetAreas(Get("p", "pe"), P); - internal static readonly EncounterArea4[] SlotsPt = EncounterArea4.GetAreas(Get("pt", "pt"), Pt); - internal static readonly EncounterArea4[] SlotsHG = EncounterArea4.GetAreas(Get("hg", "hg"), HG); - internal static readonly EncounterArea4[] SlotsSS = EncounterArea4.GetAreas(Get("ss", "ss"), SS); - - static Encounters4() - { - MarkEncounterTradeStrings(TradeGift_DPPt, TradeDPPt); - MarkEncounterTradeStrings(TradeGift_HGSS, TradeHGSS); - } - - #region Pokéwalker Encounter - // all pkm are in Poke Ball and have a met location of "PokeWalker" - private static readonly EncounterStatic4Pokewalker[] Encounter_PokeWalker = - { - // Some pkm has a pre-level move, an egg move or even a special move, it might be also available via HM/TM/Tutor - // Johto/Kanto Courses - new(084, 1, 08), // Doduo - new(115, 1, 08), // Kangaskhan - new(029, 1, 05), // Nidoran♀ - new(032, 0, 05), // Nidoran♂ - new(016, 0, 05), // Pidgey - new(161, 1, 05), // Sentret - new(202, 1, 15), // Wobbuffet - new(069, 1, 08), // Bellsprout - new(046, 1, 06), // Paras - new(048, 0, 06), // Venonat - new(021, 0, 05), // Spearow - new(043, 1, 05), // Oddish - new(095, 0, 09), // Onix - new(240, 0, 09) { Moves = new[]{241} }, // Magby: Sunny Day - new(066, 1, 07), // Machop - new(077, 1, 07), // Ponyta - new(074, 1, 08) { Moves = new[]{189} }, // Geodude: Mud-Slap - new(163, 1, 06), // Hoothoot - new(054, 1, 10), // Psyduck - new(120, 2, 10), // Staryu - new(060, 0, 08), // Poliwag - new(079, 0, 08), // Slowpoke - new(191, 1, 06), // Sunkern - new(194, 0, 06), // Wooper - new(081, 2, 11), // Magnemite - new(239, 0, 11) { Moves = new[]{009} }, // Elekid: Thunder Punch - new(081, 2, 08), // Magnemite - new(198, 1, 11), // Murkrow - new(019, 1, 07), // Rattata - new(163, 1, 07), // Hoothoot - new(092, 1, 15) { Moves = new[]{194} }, // Gastly: Destiny Bond - new(238, 1, 12) { Moves = new[]{419} }, // Smoochum: Avalanche - new(092, 1, 10), // Gastly - new(095, 0, 10), // Onix - new(041, 0, 08), // Zubat - new(066, 0, 08), // Machop - new(060, 1, 15) { Moves = new[]{187} }, // Poliwag: Belly Drum - new(147, 1, 10), // Dratini - new(090, 1, 12), // Shellder - new(098, 0, 12) { Moves = new[]{152} }, // Krabby: Crabhammer - new(072, 1, 09), // Tentacool - new(118, 1, 09), // Goldeen - new(063, 1, 15), // Abra - new(100, 2, 15), // Voltorb - new(088, 0, 13), // Grimer - new(109, 1, 13) { Moves = new[]{120} }, // Koffing: Self-Destruct - new(019, 1, 16), // Rattata - new(162, 0, 15), // Furret - // Hoenn Courses - new(264, 1, 30), // Linoone - new(300, 1, 30), // Skitty - new(313, 0, 25), // Volbeat - new(314, 1, 25), // Illumise - new(263, 1, 17), // Zigzagoon - new(265, 1, 15), // Wurmple - new(298, 1, 20), // Azurill - new(320, 1, 31), // Wailmer - new(116, 1, 20), // Horsea - new(318, 1, 26), // Carvanha - new(118, 1, 22) { Moves = new[]{401} }, // Goldeen: Aqua Tail - new(129, 1, 15), // Magikarp - new(218, 1, 31), // Slugma - new(307, 0, 32), // Meditite - new(111, 0, 25), // Rhyhorn - new(228, 0, 27), // Houndour - new(074, 0, 29), // Geodude - new(077, 1, 19), // Ponyta - new(351, 1, 30), // Castform - new(352, 0, 30), // Kecleon - new(203, 1, 28), // Girafarig - new(234, 1, 28), // Stantler - new(044, 1, 14), // Gloom - new(070, 0, 13), // Weepinbell - new(105, 1, 30) { Moves = new[]{037} }, // Marowak: Thrash - new(128, 0, 30), // Tauros - new(042, 0, 33), // Golbat - new(177, 1, 24), // Natu - new(066, 0, 13) { Moves = new[]{418} }, // Machop: Bullet Punch - new(092, 1, 15), // Gastly - // Sinnoh Courses - new(415, 0, 30), // Combee - new(439, 0, 29), // Mime Jr. - new(403, 1, 33), // Shinx - new(406, 0, 30), // Budew - new(399, 1, 13), // Bidoof - new(401, 0, 15), // Kricketot - new(361, 1, 28), // Snorunt - new(459, 0, 31) { Moves = new[]{452} }, // Snover: Wood Hammer - new(215, 0, 28) { Moves = new[]{306} }, // Sneasel: Crash Claw - new(436, 2, 20), // Bronzor - new(179, 1, 15), // Mareep - new(220, 1, 16), // Swinub - new(357, 1, 35), // Tropius - new(438, 0, 30), // Bonsly - new(114, 1, 30), // Tangela - new(400, 1, 30), // Bibarel - new(102, 1, 17), // Exeggcute - new(179, 0, 19), // Mareep - new(200, 1, 32) { Moves = new[]{194} }, // Misdreavus: Destiny Bond - new(433, 0, 22) { Moves = new[]{105} }, // Chingling: Recover - new(093, 0, 25), // Haunter - new(418, 0, 28) { Moves = new[]{226} }, // Buizel: Baton Pass - new(170, 1, 17), // Chinchou - new(223, 1, 19), // Remoraid - new(422, 1, 30) { Moves = new[]{243} }, // Shellos: Mirror Coat - new(456, 1, 26), // Finneon - new(086, 1, 27), // Seel - new(129, 1, 30), // Magikarp - new(054, 1, 22) { Moves = new[]{281} }, // Psyduck: Yawn - new(090, 0, 20), // Shellder - new(025, 1, 30), // Pikachu - new(417, 1, 33) { Moves = new[]{175} }, // Pachirisu: Flail - new(035, 1, 31), // Clefairy - new(039, 1, 30), // Jigglypuff - new(183, 1, 25), // Marill - new(187, 1, 25), // Hoppip - new(442, 0, 31), // Spiritomb - new(446, 0, 33), // Munchlax - new(349, 0, 30), // Feebas - new(433, 1, 26), // Chingling - new(042, 0, 33), // Golbat - new(164, 1, 30), // Noctowl - // Special Courses - new(120, 2, 18) { Moves = new[]{113} }, // Staryu: Light Screen - new(224, 1, 19) { Moves = new[]{324} }, // Octillery: Signal Beam - new(116, 0, 15), // Horsea - new(222, 1, 16), // Corsola - new(170, 1, 12), // Chinchou - new(223, 0, 14), // Remoraid - new(035, 0, 08) { Moves = new[]{236} }, // Clefairy: Moonlight - new(039, 0, 10), // Jigglypuff - new(041, 0, 09), // Zubat - new(163, 1, 06), // Hoothoot - new(074, 0, 05), // Geodude - new(095, 1, 05) { Moves = new[]{088} }, // Onix: Rock Throw - new(025, 0, 15) { Moves = new[]{019} }, // Pikachu: Fly - new(025, 1, 14) { Moves = new[]{057} }, // Pikachu: Surf - new(025, 1, 12) { Moves = new[]{344, 252} }, // Pikachu: Volt Tackle, Fake Out - new(025, 0, 13) { Moves = new[]{175} }, // Pikachu: Flail - new(025, 0, 10), // Pikachu - new(025, 1, 10), // Pikachu - new(302, 1, 15), // Sableye - new(441, 0, 15), // Chatot - new(025, 1, 10), // Pikachu - new(453, 0, 10), // Croagunk - new(417, 0, 05), // Pachirisu - new(427, 1, 05), // Buneary - new(133, 0, 10), // Eevee - new(255, 0, 10), // Torchic - new(061, 1, 15) { Moves = new[]{003} }, // Poliwhirl: Double Slap - new(279, 0, 15), // Pelipper - new(025, 1, 08), // Pikachu - new(052, 0, 10), // Meowth - new(374, 2, 05) { Moves = new[]{428,334,442} }, // Beldum: Zen Headbutt, Iron Defense & Iron Head. - new(446, 0, 05) { Moves = new[]{120} }, // Munchlax: Self-Destruct - new(116, 0, 05) { Moves = new[]{330} }, // Horsea: Muddy Water - new(355, 0, 05) { Moves = new[]{286} }, // Duskull: Imprison - new(129, 0, 05) { Moves = new[]{340} }, // Magikarp: Bounce - new(436, 2, 05) { Moves = new[]{433} }, // Bronzor: Trick Room - new(239, 0, 05) { Moves = new[]{9}}, // Elekid: Thunder Punch (can be tutored) - new(240, 0, 05) { Moves = new[]{7}}, // Magby: Fire Punch (can be tutored) - new(238, 1, 05) { Moves = new[]{8}}, // Smoochum: Ice Punch (can be tutored) - new(440, 1, 05) { Moves = new[]{215}}, // Happiny: Heal Bell - new(173, 1, 05) { Moves = new[]{118}}, // Cleffa: Metronome - new(174, 0, 05) { Moves = new[]{273}}, // Igglybuff: Wish - }; - #endregion - #region Static Encounter/Gift Tables - private static readonly EncounterStatic4[] Encounter_DPPt = - { - // Starters - new(DP) { Gift = true, Species = 387, Level = 5, Location = 076, GroundTile = Max_DP }, // Turtwig @ Lake Verity - new(DP) { Gift = true, Species = 390, Level = 5, Location = 076, GroundTile = Max_DP }, // Chimchar - new(DP) { Gift = true, Species = 393, Level = 5, Location = 076, GroundTile = Max_DP }, // Piplup - new(Pt) { Gift = true, Species = 387, Level = 5, Location = 016, GroundTile = Max_Pt }, // Turtwig @ Route 201 - new(Pt) { Gift = true, Species = 390, Level = 5, Location = 016, GroundTile = Max_Pt }, // Chimchar - new(Pt) { Gift = true, Species = 393, Level = 5, Location = 016, GroundTile = Max_Pt }, // Piplup - - // Fossil @ Mining Museum - new(DP) { Gift = true, Species = 138, Level = 20, Location = 094, GroundTile = Max_DP }, // Omanyte - new(DP) { Gift = true, Species = 140, Level = 20, Location = 094, GroundTile = Max_DP }, // Kabuto - new(DP) { Gift = true, Species = 142, Level = 20, Location = 094, GroundTile = Max_DP }, // Aerodactyl - new(DP) { Gift = true, Species = 345, Level = 20, Location = 094, GroundTile = Max_DP }, // Lileep - new(DP) { Gift = true, Species = 347, Level = 20, Location = 094, GroundTile = Max_DP }, // Anorith - new(DP) { Gift = true, Species = 408, Level = 20, Location = 094, GroundTile = Max_DP }, // Cranidos - new(DP) { Gift = true, Species = 410, Level = 20, Location = 094, GroundTile = Max_DP }, // Shieldon - new(Pt) { Gift = true, Species = 138, Level = 20, Location = 094, GroundTile = Max_Pt }, // Omanyte - new(Pt) { Gift = true, Species = 140, Level = 20, Location = 094, GroundTile = Max_Pt }, // Kabuto - new(Pt) { Gift = true, Species = 142, Level = 20, Location = 094, GroundTile = Max_Pt }, // Aerodactyl - new(Pt) { Gift = true, Species = 345, Level = 20, Location = 094, GroundTile = Max_Pt }, // Lileep - new(Pt) { Gift = true, Species = 347, Level = 20, Location = 094, GroundTile = Max_Pt }, // Anorith - new(Pt) { Gift = true, Species = 408, Level = 20, Location = 094, GroundTile = Max_Pt }, // Cranidos - new(Pt) { Gift = true, Species = 410, Level = 20, Location = 094, GroundTile = Max_Pt }, // Shieldon - - // Gift - new(DP) { Gift = true, Species = 133, Level = 05, Location = 010, GroundTile = Max_DP }, // Eevee @ Hearthome City - new(Pt) { Gift = true, Species = 133, Level = 20, Location = 010, GroundTile = Max_Pt }, // Eevee @ Hearthome City - new(Pt) { Gift = true, Species = 137, Level = 25, Location = 012, GroundTile = Max_Pt }, // Porygon @ Veilstone City - new(Pt) { Gift = true, Species = 175, Level = 01, EggLocation = 2011 }, // Togepi Egg from Cynthia - new(DP) { Gift = true, Species = 440, Level = 01, EggLocation = 2009 }, // Happiny Egg from Traveling Man - new(DPPt) { Gift = true, Species = 447, Level = 01, EggLocation = 2010 }, // Riolu Egg from Riley - - // Stationary - new(DP) { Species = 425, Level = 22, Location = 47 }, // Drifloon @ Valley Windworks - new(Pt) { Species = 425, Level = 15, Location = 47 }, // Drifloon @ Valley Windworks - new(DP) { Species = 479, Level = 15, Location = 70, GroundTile = Building }, // Rotom @ Old Chateau - new(Pt) { Species = 479, Level = 20, Location = 70, GroundTile = Building }, // Rotom @ Old Chateau - new(DPPt) { Species = 442, Level = 25, Location = 24 }, // Spiritomb @ Route 209 - - // Stationary Legendary - new(Pt) { Species = 377, Level = 30, Location = 125, GroundTile = Cave }, // Regirock @ Rock Peak Ruins - new(Pt) { Species = 378, Level = 30, Location = 124, GroundTile = Cave }, // Regice @ Iceberg Ruins - new(Pt) { Species = 379, Level = 30, Location = 123, GroundTile = Cave }, // Registeel @ Iron Ruins - new(DPPt) { Species = 480, Level = 50, Location = 089, GroundTile = Cave }, // Uxie @ Acuity Cavern - new(DPPt) { Species = 482, Level = 50, Location = 088, GroundTile = Cave }, // Azelf @ Valor Cavern - new(D ) { Species = 483, Level = 47, Location = 051, GroundTile = Rock }, // Dialga @ Spear Pillar - new( P) { Species = 484, Level = 47, Location = 051, GroundTile = Rock }, // Palkia @ Spear Pillar - new(Pt) { Species = 483, Level = 70, Location = 051, GroundTile = Rock }, // Dialga @ Spear Pillar - new(Pt) { Species = 484, Level = 70, Location = 051, GroundTile = Rock }, // Palkia @ Spear Pillar - new(DP) { Species = 485, Level = 70, Location = 084, GroundTile = Cave }, // Heatran @ Stark Mountain - new(Pt) { Species = 485, Level = 50, Location = 084, GroundTile = Cave }, // Heatran @ Stark Mountain - new(DP) { Species = 486, Level = 70, Location = 064, GroundTile = Cave }, // Regigigas @ Snowpoint Temple - new(Pt) { Species = 486, Level = 01, Location = 064, GroundTile = Cave }, // Regigigas @ Snowpoint Temple - new(DP) { Species = 487, Level = 70, Location = 062, GroundTile = Cave, Form = 0 }, // Giratina @ Turnback Cave - new(Pt) { Species = 487, Level = 47, Location = 062, GroundTile = Cave, Form = 0 }, // Giratina @ Turnback Cave - new(Pt) { Species = 487, Level = 47, Location = 117, GroundTile = Distortion, Form = 1, HeldItem = 112 }, // Giratina @ Distortion World - - // Event - //new(DP) { Species = 491, Level = 40, Location = 079, GroundTile = Grass }, // Darkrai @ Newmoon Island (Unreleased in Diamond and Pearl) - new(Pt) { Species = 491, Level = 50, Location = 079, GroundTile = Grass }, // Darkrai @ Newmoon Island - new(Pt) { Species = 492, Form = 0, Level = 30, Location = 063, Fateful = true }, // Shaymin @ Flower Paradise - //new(DP) { Species = 492, Form = 0, Level = 30, Location = 063, Fateful = false }, // Shaymin @ Flower Paradise (Unreleased in Diamond and Pearl) - //new(DPPt) { Species = 493, Form = 0, Level = 80, Location = 086, GroundTile = Cave }, // Arceus @ Hall of Origin (Unreleased) - - // Roamers - new(DPPt) { Roaming = true, Species = 481, Level = 50, GroundTile = Grass | Water }, // Mesprit - new(DPPt) { Roaming = true, Species = 488, Level = 50, GroundTile = Grass | Water }, // Cresselia - new(Pt) { Roaming = true, Species = 144, Level = 60, GroundTile = Grass | Water }, // Articuno - new(Pt) { Roaming = true, Species = 145, Level = 60, GroundTile = Grass | Water }, // Zapdos - new(Pt) { Roaming = true, Species = 146, Level = 60, GroundTile = Grass | Water }, // Moltres - }; - - private static readonly EncounterStatic4[] Encounter_HGSS = - { - // Starters - new(HGSS) { Gift = true, Species = 001, Level = 05, Location = 138, GroundTile = Max_Pt }, // Bulbasaur @ Pallet Town - new(HGSS) { Gift = true, Species = 004, Level = 05, Location = 138, GroundTile = Max_Pt }, // Charmander - new(HGSS) { Gift = true, Species = 007, Level = 05, Location = 138, GroundTile = Max_Pt }, // Squirtle - new(HGSS) { Gift = true, Species = 152, Level = 05, Location = 126, GroundTile = Max_DP }, // Chikorita @ New Bark Town - new(HGSS) { Gift = true, Species = 155, Level = 05, Location = 126, GroundTile = Max_DP }, // Cyndaquil - new(HGSS) { Gift = true, Species = 158, Level = 05, Location = 126, GroundTile = Max_DP }, // Totodile - new(HGSS) { Gift = true, Species = 252, Level = 05, Location = 148, GroundTile = Max_Pt }, // Treecko @ Saffron City - new(HGSS) { Gift = true, Species = 255, Level = 05, Location = 148, GroundTile = Max_Pt }, // Torchic - new(HGSS) { Gift = true, Species = 258, Level = 05, Location = 148, GroundTile = Max_Pt }, // Mudkip - - // Fossils @ Pewter City - new(HGSS) { Gift = true, Species = 138, Level = 20, Location = 140, GroundTile = Max_Pt }, // Omanyte - new(HGSS) { Gift = true, Species = 140, Level = 20, Location = 140, GroundTile = Max_Pt }, // Kabuto - new(HGSS) { Gift = true, Species = 142, Level = 20, Location = 140, GroundTile = Max_Pt }, // Aerodactyl - new(HGSS) { Gift = true, Species = 345, Level = 20, Location = 140, GroundTile = Max_Pt }, // Lileep - new(HGSS) { Gift = true, Species = 347, Level = 20, Location = 140, GroundTile = Max_Pt }, // Anorith - new(HGSS) { Gift = true, Species = 408, Level = 20, Location = 140, GroundTile = Max_Pt }, // Cranidos - new(HGSS) { Gift = true, Species = 410, Level = 20, Location = 140, GroundTile = Max_Pt }, // Shieldon - - // Gift - new(HGSS) { Gift = true, Species = 072, Level = 15, Location = 130, GroundTile = Max_Pt }, // Tentacool @ Cianwood City - new(HGSS) { Gift = true, Species = 133, Level = 05, Location = 131, GroundTile = Max_Pt }, // Eevee @ Goldenrod City - new(HGSS) { Gift = true, Species = 147, Level = 15, Location = 222, GroundTile = Max_Pt, Moves = new[] {245} }, // Dratini @ Dragon's Den (ExtremeSpeed) - new(HGSS) { Gift = true, Species = 236, Level = 10, Location = 216, GroundTile = Max_Pt }, // Tyrogue @ Mt. Mortar - new(HGSS) { Gift = true, Species = 175, Level = 01, EggLocation = 2013, Moves = new[] {326} }, // Togepi Egg from Mr. Pokemon (Extrasensory as Egg move) - new(HGSS) { Gift = true, Species = 179, Level = 01, EggLocation = 2014 }, // Mareep Egg from Primo - new(HGSS) { Gift = true, Species = 194, Level = 01, EggLocation = 2014 }, // Wooper Egg from Primo - new(HGSS) { Gift = true, Species = 218, Level = 01, EggLocation = 2014 }, // Slugma Egg from Primo - - // Celadon City Game Corner - new(HGSS) { Gift = true, Species = 122, Level = 15, Location = 144, GroundTile = Max_Pt }, // Mr. Mime - new(HGSS) { Gift = true, Species = 133, Level = 15, Location = 144, GroundTile = Max_Pt }, // Eevee - new(HGSS) { Gift = true, Species = 137, Level = 15, Location = 144, GroundTile = Max_Pt }, // Porygon - - // Goldenrod City Game Corner - new(HGSS) { Gift = true, Species = 063, Level = 15, Location = 131, GroundTile = Max_Pt }, // Abra - new(HG ) { Gift = true, Species = 023, Level = 15, Location = 131, GroundTile = Max_Pt }, // Ekans - new( SS) { Gift = true, Species = 027, Level = 15, Location = 131, GroundTile = Max_Pt }, // Sandshrew - new(HGSS) { Gift = true, Species = 147, Level = 15, Location = 131, GroundTile = Max_Pt }, // Dratini - - // Team Rocket HQ Trap Floor - new(HGSS) { Species = 100, Level = 23, Location = 213, GroundTile = Building }, // Voltorb - new(HGSS) { Species = 074, Level = 21, Location = 213, GroundTile = Building }, // Geodude - new(HGSS) { Species = 109, Level = 21, Location = 213, GroundTile = Building }, // Koffing - - // Stationary - new(HGSS) { Species = 130, Level = 30, Location = 135, GroundTile = Water, Shiny = Shiny.Always }, // Gyarados @ Lake of Rage - new(HGSS) { Species = 131, Level = 20, Location = 210, GroundTile = Water }, // Lapras @ Union Cave Friday Only - new(HGSS) { Species = 101, Level = 23, Location = 213, GroundTile = Building }, // Electrode @ Team Rocket HQ - new(HGSS) { Species = 143, Level = 50, Location = 159 }, // Snorlax @ Route 11 - new(HGSS) { Species = 143, Level = 50, Location = 160 }, // Snorlax @ Route 12 - new(HGSS) { Species = 185, Level = 20, Location = 184 }, // Sudowoodo @ Route 36, Encounter does not have type - - new(HGSS) // Spiky-Eared Pichu @ Ilex Forest - { - Species = 172, - Level = 30, - Gender = 1, - Form = 1, - Nature = Nature.Naughty, - Location = 214, - Moves = new[] { 344, 270, 207, 220 }, - GroundTile = Max_Pt, - Shiny = Shiny.Never, - }, - - // Stationary Legendary - new(HGSS) { Species = 144, Level = 50, Location = 203, GroundTile = Cave }, // Articuno @ Seafoam Islands - new(HGSS) { Species = 145, Level = 50, Location = 158 }, // Zapdos @ Route 10 - new(HGSS) { Species = 146, Level = 50, Location = 219, GroundTile = Cave }, // Moltres @ Mt. Silver Cave - new(HGSS) { Species = 150, Level = 70, Location = 199, GroundTile = Cave }, // Mewtwo @ Cerulean Cave - new(HGSS) { Species = 245, Level = 40, Location = 173 }, // Suicune @ Route 25 - new(HGSS) { Species = 245, Level = 40, Location = 206, GroundTile = Cave }, // Suicune @ Burned Tower - new( SS) { Species = 249, Level = 45, Location = 218, GroundTile = Water }, // Lugia @ Whirl Islands - new(HG ) { Species = 249, Level = 70, Location = 218, GroundTile = Water }, // Lugia @ Whirl Islands - new(HG ) { Species = 250, Level = 45, Location = 205, GroundTile = Building }, // Ho-Oh @ Bell Tower - new( SS) { Species = 250, Level = 70, Location = 205, GroundTile = Building }, // Ho-Oh @ Bell Tower - new( SS) { Species = 380, Level = 40, Location = 140, GroundTile = Building }, // Latias @ Pewter City - new(HG ) { Species = 381, Level = 40, Location = 140, GroundTile = Building }, // Latios @ Pewter City - new(HG ) { Species = 382, Level = 50, Location = 232, GroundTile = Cave }, // Kyogre @ Embedded Tower - new( SS) { Species = 383, Level = 50, Location = 232, GroundTile = Cave }, // Groudon @ Embedded Tower - new(HGSS) { Species = 384, Level = 50, Location = 232, GroundTile = Cave }, // Rayquaza @ Embedded Tower - new(HGSS) { Species = 483, Level = 01, Location = 231, Gift = true, GroundTile = Max_Pt }, // Dialga @ Sinjoh Ruins - new(HGSS) { Species = 484, Level = 01, Location = 231, Gift = true, GroundTile = Max_Pt }, // Palkia @ Sinjoh Ruins - new(HGSS) { Species = 487, Level = 01, Location = 231, Gift = true, GroundTile = Max_Pt, Form = 1, HeldItem = 112 }, // Giratina @ Sinjoh Ruins - - // Johto Roamers - new(HGSS) { Roaming = true, Species = 243, Level = 40, GroundTile = Grass | Water }, // Raikou - new(HGSS) { Roaming = true, Species = 244, Level = 40, GroundTile = Grass | Water }, // Entei - - // Kanto Roamers - new(HG ) { Roaming = true, Species = 380, Level = 35, GroundTile = Grass | Water }, // Latias - new( SS) { Roaming = true, Species = 381, Level = 35, GroundTile = Grass | Water }, // Latios - }; - #endregion - #region Trade Tables - - private static readonly EncounterTrade4[] RanchGifts = - { - new EncounterTrade4RanchGift(323975838, 025, 18) { Moves = new[] {447,085,148,104}, TID = 1000, SID = 19840, OTGender = 1, MetLocation = 0068, Gender = 0, Ability = OnlyFirst, CurrentLevel = 20 }, // Pikachu - new EncounterTrade4RanchGift(323977664, 037, 16) { Moves = new[] {412,109,053,219}, TID = 1000, SID = 21150, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlyFirst, CurrentLevel = 30 }, // Vulpix - new EncounterTrade4RanchGift(323975579, 077, 13) { Moves = new[] {036,033,039,052}, TID = 1000, SID = 01123, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlySecond, CurrentLevel = 16 }, // Ponyta - new EncounterTrade4RanchGift(323975564, 108, 34) { Moves = new[] {076,111,014,205}, TID = 1000, SID = 03050, OTGender = 1, MetLocation = 0077, Gender = 0, Ability = OnlyFirst, CurrentLevel = 40 }, // Lickitung - new EncounterTrade4RanchGift(323977579, 114, 01) { Moves = new[] {437,438,079,246}, TID = 1000, SID = 49497, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlySecond }, // Tangela - new EncounterTrade4RanchGift(323977675, 133, 16) { Moves = new[] {363,270,098,247}, TID = 1000, SID = 47710, OTGender = 1, MetLocation = 0068, Gender = 0, Ability = OnlySecond, CurrentLevel = 30 }, // Eevee - new EncounterTrade4RanchGift(323977588, 142, 20) { Moves = new[] {363,089,444,332}, TID = 1000, SID = 43066, OTGender = 1, MetLocation = 0094, Gender = 0, Ability = OnlyFirst, CurrentLevel = 50 }, // Aerodactyl - new EncounterTrade4RanchGift(232975554, 193, 22) { Moves = new[] {318,095,246,138}, TID = 1000, SID = 42301, OTGender = 1, MetLocation = 0052, Gender = 0, Ability = OnlyFirst, CurrentLevel = 45, Ball = 5 }, // Yanma - new EncounterTrade4RanchGift(323975570, 241, 16) { Moves = new[] {208,215,360,359}, TID = 1000, SID = 02707, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlyFirst, CurrentLevel = 48 }, // Miltank - new EncounterTrade4RanchGift(323975563, 285, 22) { Moves = new[] {402,147,206,078}, TID = 1000, SID = 02788, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlySecond, CurrentLevel = 45, Ball = 5 }, // Shroomish - new EncounterTrade4RanchGift(323975559, 320, 30) { Moves = new[] {156,323,133,058}, TID = 1000, SID = 27046, OTGender = 1, MetLocation = 0038, Gender = 0, Ability = OnlySecond, CurrentLevel = 45 }, // Wailmer - new EncounterTrade4RanchGift(323977657, 360, 01) { Moves = new[] {204,150,227,000}, TID = 1000, SID = 01788, OTGender = 1, MetLocation = 0004, Gender = 0, Ability = OnlySecond, EggLocation = 2000 }, // Wynaut - new EncounterTrade4RanchGift(323975563, 397, 02) { Moves = new[] {355,017,283,018}, TID = 1000, SID = 59298, OTGender = 1, MetLocation = 0016, Gender = 0, Ability = OnlySecond, CurrentLevel = 23 }, // Staravia - new EncounterTrade4RanchGift(323970584, 415, 05) { Moves = new[] {230,016,000,000}, TID = 1000, SID = 54140, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlyFirst, CurrentLevel = 20 }, // Combee - new EncounterTrade4RanchGift(323977539, 417, 09) { Moves = new[] {447,045,351,098}, TID = 1000, SID = 18830, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlySecond, CurrentLevel = 10 }, // Pachirisu - new EncounterTrade4RanchGift(323974107, 422, 20) { Moves = new[] {363,352,426,104}, TID = 1000, SID = 39272, OTGender = 1, MetLocation = 0028, Gender = 0, Ability = OnlySecond, CurrentLevel = 25, Form = 1 }, // Shellos - new EncounterTrade4RanchGift(323977566, 427, 10) { Moves = new[] {204,193,409,098}, TID = 1000, SID = 31045, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlyFirst, CurrentLevel = 16 }, // Buneary - new EncounterTrade4RanchGift(323975579, 453, 22) { Moves = new[] {310,207,426,389}, TID = 1000, SID = 41342, OTGender = 1, MetLocation = 0052, Gender = 0, Ability = OnlySecond, CurrentLevel = 31, Ball = 5 }, // Croagunk - new EncounterTrade4RanchGift(323977566, 456, 15) { Moves = new[] {213,352,219,392}, TID = 1000, SID = 48348, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlyFirst, CurrentLevel = 35 }, // Finneon - new EncounterTrade4RanchGift(323975582, 459, 32) { Moves = new[] {452,420,275,059}, TID = 1000, SID = 23360, OTGender = 1, MetLocation = 0031, Gender = 0, Ability = OnlyFirst, CurrentLevel = 41 }, // Snover - new EncounterTrade4RanchSpecial(151, 50) { Moves = new[] {235,216,095,100}, TID = 1000, SID = 59228, OTGender = 1, Ball = 0x10, Gender = 2 }, // Mew - new EncounterTrade4RanchSpecial(489, 01) { Moves = new[] {447,240,156,057}, TID = 1000, SID = 09248, OTGender = 1, Ball = 0x10, Gender = 2, CurrentLevel = 50, EggLocation = 3000 }, // Phione - }; - - private static readonly EncounterTrade4PID[] TradeGift_DPPtIngame = - { - new(DPPt, 0x0000008E, 063, 01) { Ability = OnlyFirst, TID = 25643, SID = 00000, OTGender = 1, Gender = 0, IVs = new[] {15,15,15,20,25,25} }, // Machop -> Abra - new(DPPt, 0x00000867, 441, 01) { Ability = OnlySecond, TID = 44142, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {15,20,15,25,25,15}, Contest = 20 }, // Buizel -> Chatot - new(DPPt, 0x00000088, 093, 35) { Ability = OnlyFirst, TID = 19248, SID = 00000, OTGender = 1, Gender = 0, IVs = new[] {20,25,15,25,15,15} }, // Medicham (35 from Route 217) -> Haunter - new(DPPt, 0x0000045C, 129, 01) { Ability = OnlyFirst, TID = 53277, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {15,25,15,20,25,15} }, // Finneon -> Magikarp - }; - - internal static readonly EncounterTrade4[] TradeGift_DPPt = ArrayUtil.ConcatAll(TradeGift_DPPtIngame, RanchGifts); - - internal static readonly EncounterTrade4PID[] TradeGift_HGSS = - { - new(HGSS, 0x000025EF, 095, 01) { Ability = OnlySecond, TID = 48926, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {25,20,25,15,15,15} }, // Bellsprout -> Onix - new(HGSS, 0x00002310, 066, 01) { Ability = OnlyFirst, TID = 37460, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {15,25,20,20,15,15} }, // Drowzee -> Machop - new(HGSS, 0x000001DB, 100, 01) { Ability = OnlySecond, TID = 29189, SID = 00000, OTGender = 0, Gender = 2, IVs = new[] {15,20,15,25,25,15} }, // Krabby -> Voltorb - new(HGSS, 0x0001FC0A, 085, 15) { Ability = OnlyFirst, TID = 00283, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {20,20,20,15,15,15} }, // Dragonair (15 from DPPt) -> Dodrio - new(HGSS, 0x0000D136, 082, 19) { Ability = OnlyFirst, TID = 50082, SID = 00000, OTGender = 0, Gender = 2, IVs = new[] {15,20,15,20,20,20} }, // Dugtrio (19 from Diglett's Cave) -> Magneton - new(HGSS, 0x000034E4, 178, 16) { Ability = OnlyFirst, TID = 15616, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {15,20,15,20,20,20} }, // Haunter (16 from Old Chateau) -> Xatu - new(HGSS, 0x00485876, 025, 02) { Ability = OnlyFirst, TID = 33038, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {20,25,18,31,25,13} }, // Pikachu - new(HGSS, 0x0012B6D4, 374, 31) { Ability = OnlyFirst, TID = 23478, SID = 00000, OTGender = 0, Gender = 2, IVs = new[] {28,29,24,23,24,25} }, // Forretress -> Beldum - new(HGSS, 0x0012971C, 111, 01) { Ability = OnlyFirst, TID = 06845, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {22,31,13,00,22,09}, Moves = new[]{422} }, // Bonsly -> Rhyhorn - new(HGSS, 0x00101596, 208, 01) { Ability = OnlyFirst, TID = 26491, SID = 00000, OTGender = 1, Gender = 0, IVs = new[] {08,30,28,06,18,20}}, // Any -> Steelix - - //Gift - new(HGSS, 0x00006B5E, 021, 20) { Ability = OnlyFirst, TID = 01001, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {15,20,15,20,20,20}, MetLocation = 183, Moves = new[]{043,031,228,332} },// Webster's Spearow - new(HGSS, 0x000214D7, 213, 20) { Ability = OnlySecond, TID = 04336, SID = 00001, OTGender = 0, Gender = 0, IVs = new[] {15,20,15,20,20,20}, MetLocation = 130, Moves = new[]{132,117,227,219} },// Kirk's Shuckle - }; - - private const string tradeDPPt = "tradedppt"; - private const string tradeHGSS = "tradehgss"; - private static readonly string[][] TradeDPPt = Util.GetLanguageStrings8(tradeDPPt); - private static readonly string[][] TradeHGSS = Util.GetLanguageStrings8(tradeHGSS); - #endregion - - internal static readonly EncounterStatic4[] StaticD = GetEncounters(Encounter_DPPt, D); - internal static readonly EncounterStatic4[] StaticP = GetEncounters(Encounter_DPPt, P); - internal static readonly EncounterStatic4[] StaticPt = GetEncounters(Encounter_DPPt, Pt); - internal static readonly EncounterStatic[] StaticHG = GetEncounters(ArrayUtil.ConcatAll(Encounter_HGSS, Encounter_PokeWalker), HG); - internal static readonly EncounterStatic[] StaticSS = GetEncounters(ArrayUtil.ConcatAll(Encounter_HGSS, Encounter_PokeWalker), SS); + MarkEncounterTradeStrings(TradeGift_DPPt, TradeDPPt); + MarkEncounterTradeStrings(TradeGift_HGSS, TradeHGSS); } + + #region Pokéwalker Encounter + // all pk are in Poke Ball and have a met location of "PokeWalker" + private static readonly EncounterStatic4Pokewalker[] Encounter_PokeWalker = + { + // Some pk has a pre-level move, an egg move or even a special move, it might be also available via HM/TM/Tutor + // Johto/Kanto Courses + new(084, 1, 08), // Doduo + new(115, 1, 08), // Kangaskhan + new(029, 1, 05), // Nidoran♀ + new(032, 0, 05), // Nidoran♂ + new(016, 0, 05), // Pidgey + new(161, 1, 05), // Sentret + new(202, 1, 15), // Wobbuffet + new(069, 1, 08), // Bellsprout + new(046, 1, 06), // Paras + new(048, 0, 06), // Venonat + new(021, 0, 05), // Spearow + new(043, 1, 05), // Oddish + new(095, 0, 09), // Onix + new(240, 0, 09) { Moves = new[]{241} }, // Magby: Sunny Day + new(066, 1, 07), // Machop + new(077, 1, 07), // Ponyta + new(074, 1, 08) { Moves = new[]{189} }, // Geodude: Mud-Slap + new(163, 1, 06), // Hoothoot + new(054, 1, 10), // Psyduck + new(120, 2, 10), // Staryu + new(060, 0, 08), // Poliwag + new(079, 0, 08), // Slowpoke + new(191, 1, 06), // Sunkern + new(194, 0, 06), // Wooper + new(081, 2, 11), // Magnemite + new(239, 0, 11) { Moves = new[]{009} }, // Elekid: Thunder Punch + new(081, 2, 08), // Magnemite + new(198, 1, 11), // Murkrow + new(019, 1, 07), // Rattata + new(163, 1, 07), // Hoothoot + new(092, 1, 15) { Moves = new[]{194} }, // Gastly: Destiny Bond + new(238, 1, 12) { Moves = new[]{419} }, // Smoochum: Avalanche + new(092, 1, 10), // Gastly + new(095, 0, 10), // Onix + new(041, 0, 08), // Zubat + new(066, 0, 08), // Machop + new(060, 1, 15) { Moves = new[]{187} }, // Poliwag: Belly Drum + new(147, 1, 10), // Dratini + new(090, 1, 12), // Shellder + new(098, 0, 12) { Moves = new[]{152} }, // Krabby: Crabhammer + new(072, 1, 09), // Tentacool + new(118, 1, 09), // Goldeen + new(063, 1, 15), // Abra + new(100, 2, 15), // Voltorb + new(088, 0, 13), // Grimer + new(109, 1, 13) { Moves = new[]{120} }, // Koffing: Self-Destruct + new(019, 1, 16), // Rattata + new(162, 0, 15), // Furret + // Hoenn Courses + new(264, 1, 30), // Linoone + new(300, 1, 30), // Skitty + new(313, 0, 25), // Volbeat + new(314, 1, 25), // Illumise + new(263, 1, 17), // Zigzagoon + new(265, 1, 15), // Wurmple + new(298, 1, 20), // Azurill + new(320, 1, 31), // Wailmer + new(116, 1, 20), // Horsea + new(318, 1, 26), // Carvanha + new(118, 1, 22) { Moves = new[]{401} }, // Goldeen: Aqua Tail + new(129, 1, 15), // Magikarp + new(218, 1, 31), // Slugma + new(307, 0, 32), // Meditite + new(111, 0, 25), // Rhyhorn + new(228, 0, 27), // Houndour + new(074, 0, 29), // Geodude + new(077, 1, 19), // Ponyta + new(351, 1, 30), // Castform + new(352, 0, 30), // Kecleon + new(203, 1, 28), // Girafarig + new(234, 1, 28), // Stantler + new(044, 1, 14), // Gloom + new(070, 0, 13), // Weepinbell + new(105, 1, 30) { Moves = new[]{037} }, // Marowak: Thrash + new(128, 0, 30), // Tauros + new(042, 0, 33), // Golbat + new(177, 1, 24), // Natu + new(066, 0, 13) { Moves = new[]{418} }, // Machop: Bullet Punch + new(092, 1, 15), // Gastly + // Sinnoh Courses + new(415, 0, 30), // Combee + new(439, 0, 29), // Mime Jr. + new(403, 1, 33), // Shinx + new(406, 0, 30), // Budew + new(399, 1, 13), // Bidoof + new(401, 0, 15), // Kricketot + new(361, 1, 28), // Snorunt + new(459, 0, 31) { Moves = new[]{452} }, // Snover: Wood Hammer + new(215, 0, 28) { Moves = new[]{306} }, // Sneasel: Crash Claw + new(436, 2, 20), // Bronzor + new(179, 1, 15), // Mareep + new(220, 1, 16), // Swinub + new(357, 1, 35), // Tropius + new(438, 0, 30), // Bonsly + new(114, 1, 30), // Tangela + new(400, 1, 30), // Bibarel + new(102, 1, 17), // Exeggcute + new(179, 0, 19), // Mareep + new(200, 1, 32) { Moves = new[]{194} }, // Misdreavus: Destiny Bond + new(433, 0, 22) { Moves = new[]{105} }, // Chingling: Recover + new(093, 0, 25), // Haunter + new(418, 0, 28) { Moves = new[]{226} }, // Buizel: Baton Pass + new(170, 1, 17), // Chinchou + new(223, 1, 19), // Remoraid + new(422, 1, 30) { Moves = new[]{243} }, // Shellos: Mirror Coat + new(456, 1, 26), // Finneon + new(086, 1, 27), // Seel + new(129, 1, 30), // Magikarp + new(054, 1, 22) { Moves = new[]{281} }, // Psyduck: Yawn + new(090, 0, 20), // Shellder + new(025, 1, 30), // Pikachu + new(417, 1, 33) { Moves = new[]{175} }, // Pachirisu: Flail + new(035, 1, 31), // Clefairy + new(039, 1, 30), // Jigglypuff + new(183, 1, 25), // Marill + new(187, 1, 25), // Hoppip + new(442, 0, 31), // Spiritomb + new(446, 0, 33), // Munchlax + new(349, 0, 30), // Feebas + new(433, 1, 26), // Chingling + new(042, 0, 33), // Golbat + new(164, 1, 30), // Noctowl + // Special Courses + new(120, 2, 18) { Moves = new[]{113} }, // Staryu: Light Screen + new(224, 1, 19) { Moves = new[]{324} }, // Octillery: Signal Beam + new(116, 0, 15), // Horsea + new(222, 1, 16), // Corsola + new(170, 1, 12), // Chinchou + new(223, 0, 14), // Remoraid + new(035, 0, 08) { Moves = new[]{236} }, // Clefairy: Moonlight + new(039, 0, 10), // Jigglypuff + new(041, 0, 09), // Zubat + new(163, 1, 06), // Hoothoot + new(074, 0, 05), // Geodude + new(095, 1, 05) { Moves = new[]{088} }, // Onix: Rock Throw + new(025, 0, 15) { Moves = new[]{019} }, // Pikachu: Fly + new(025, 1, 14) { Moves = new[]{057} }, // Pikachu: Surf + new(025, 1, 12) { Moves = new[]{344, 252} }, // Pikachu: Volt Tackle, Fake Out + new(025, 0, 13) { Moves = new[]{175} }, // Pikachu: Flail + new(025, 0, 10), // Pikachu + new(025, 1, 10), // Pikachu + new(302, 1, 15), // Sableye + new(441, 0, 15), // Chatot + new(025, 1, 10), // Pikachu + new(453, 0, 10), // Croagunk + new(417, 0, 05), // Pachirisu + new(427, 1, 05), // Buneary + new(133, 0, 10), // Eevee + new(255, 0, 10), // Torchic + new(061, 1, 15) { Moves = new[]{003} }, // Poliwhirl: Double Slap + new(279, 0, 15), // Pelipper + new(025, 1, 08), // Pikachu + new(052, 0, 10), // Meowth + new(374, 2, 05) { Moves = new[]{428,334,442} }, // Beldum: Zen Headbutt, Iron Defense & Iron Head. + new(446, 0, 05) { Moves = new[]{120} }, // Munchlax: Self-Destruct + new(116, 0, 05) { Moves = new[]{330} }, // Horsea: Muddy Water + new(355, 0, 05) { Moves = new[]{286} }, // Duskull: Imprison + new(129, 0, 05) { Moves = new[]{340} }, // Magikarp: Bounce + new(436, 2, 05) { Moves = new[]{433} }, // Bronzor: Trick Room + new(239, 0, 05) { Moves = new[]{9}}, // Elekid: Thunder Punch (can be tutored) + new(240, 0, 05) { Moves = new[]{7}}, // Magby: Fire Punch (can be tutored) + new(238, 1, 05) { Moves = new[]{8}}, // Smoochum: Ice Punch (can be tutored) + new(440, 1, 05) { Moves = new[]{215}}, // Happiny: Heal Bell + new(173, 1, 05) { Moves = new[]{118}}, // Cleffa: Metronome + new(174, 0, 05) { Moves = new[]{273}}, // Igglybuff: Wish + }; + #endregion + #region Static Encounter/Gift Tables + private static readonly EncounterStatic4[] Encounter_DPPt = + { + // Starters + new(DP) { Gift = true, Species = 387, Level = 5, Location = 076, GroundTile = Max_DP }, // Turtwig @ Lake Verity + new(DP) { Gift = true, Species = 390, Level = 5, Location = 076, GroundTile = Max_DP }, // Chimchar + new(DP) { Gift = true, Species = 393, Level = 5, Location = 076, GroundTile = Max_DP }, // Piplup + new(Pt) { Gift = true, Species = 387, Level = 5, Location = 016, GroundTile = Max_Pt }, // Turtwig @ Route 201 + new(Pt) { Gift = true, Species = 390, Level = 5, Location = 016, GroundTile = Max_Pt }, // Chimchar + new(Pt) { Gift = true, Species = 393, Level = 5, Location = 016, GroundTile = Max_Pt }, // Piplup + + // Fossil @ Mining Museum + new(DP) { Gift = true, Species = 138, Level = 20, Location = 094, GroundTile = Max_DP }, // Omanyte + new(DP) { Gift = true, Species = 140, Level = 20, Location = 094, GroundTile = Max_DP }, // Kabuto + new(DP) { Gift = true, Species = 142, Level = 20, Location = 094, GroundTile = Max_DP }, // Aerodactyl + new(DP) { Gift = true, Species = 345, Level = 20, Location = 094, GroundTile = Max_DP }, // Lileep + new(DP) { Gift = true, Species = 347, Level = 20, Location = 094, GroundTile = Max_DP }, // Anorith + new(DP) { Gift = true, Species = 408, Level = 20, Location = 094, GroundTile = Max_DP }, // Cranidos + new(DP) { Gift = true, Species = 410, Level = 20, Location = 094, GroundTile = Max_DP }, // Shieldon + new(Pt) { Gift = true, Species = 138, Level = 20, Location = 094, GroundTile = Max_Pt }, // Omanyte + new(Pt) { Gift = true, Species = 140, Level = 20, Location = 094, GroundTile = Max_Pt }, // Kabuto + new(Pt) { Gift = true, Species = 142, Level = 20, Location = 094, GroundTile = Max_Pt }, // Aerodactyl + new(Pt) { Gift = true, Species = 345, Level = 20, Location = 094, GroundTile = Max_Pt }, // Lileep + new(Pt) { Gift = true, Species = 347, Level = 20, Location = 094, GroundTile = Max_Pt }, // Anorith + new(Pt) { Gift = true, Species = 408, Level = 20, Location = 094, GroundTile = Max_Pt }, // Cranidos + new(Pt) { Gift = true, Species = 410, Level = 20, Location = 094, GroundTile = Max_Pt }, // Shieldon + + // Gift + new(DP) { Gift = true, Species = 133, Level = 05, Location = 010, GroundTile = Max_DP }, // Eevee @ Hearthome City + new(Pt) { Gift = true, Species = 133, Level = 20, Location = 010, GroundTile = Max_Pt }, // Eevee @ Hearthome City + new(Pt) { Gift = true, Species = 137, Level = 25, Location = 012, GroundTile = Max_Pt }, // Porygon @ Veilstone City + new(Pt) { Gift = true, Species = 175, Level = 01, EggLocation = 2011 }, // Togepi Egg from Cynthia + new(DP) { Gift = true, Species = 440, Level = 01, EggLocation = 2009 }, // Happiny Egg from Traveling Man + new(DPPt) { Gift = true, Species = 447, Level = 01, EggLocation = 2010 }, // Riolu Egg from Riley + + // Stationary + new(DP) { Species = 425, Level = 22, Location = 47 }, // Drifloon @ Valley Windworks + new(Pt) { Species = 425, Level = 15, Location = 47 }, // Drifloon @ Valley Windworks + new(DP) { Species = 479, Level = 15, Location = 70, GroundTile = Building }, // Rotom @ Old Chateau + new(Pt) { Species = 479, Level = 20, Location = 70, GroundTile = Building }, // Rotom @ Old Chateau + new(DPPt) { Species = 442, Level = 25, Location = 24 }, // Spiritomb @ Route 209 + + // Stationary Legendary + new(Pt) { Species = 377, Level = 30, Location = 125, GroundTile = Cave }, // Regirock @ Rock Peak Ruins + new(Pt) { Species = 378, Level = 30, Location = 124, GroundTile = Cave }, // Regice @ Iceberg Ruins + new(Pt) { Species = 379, Level = 30, Location = 123, GroundTile = Cave }, // Registeel @ Iron Ruins + new(DPPt) { Species = 480, Level = 50, Location = 089, GroundTile = Cave }, // Uxie @ Acuity Cavern + new(DPPt) { Species = 482, Level = 50, Location = 088, GroundTile = Cave }, // Azelf @ Valor Cavern + new(D ) { Species = 483, Level = 47, Location = 051, GroundTile = Rock }, // Dialga @ Spear Pillar + new( P) { Species = 484, Level = 47, Location = 051, GroundTile = Rock }, // Palkia @ Spear Pillar + new(Pt) { Species = 483, Level = 70, Location = 051, GroundTile = Rock }, // Dialga @ Spear Pillar + new(Pt) { Species = 484, Level = 70, Location = 051, GroundTile = Rock }, // Palkia @ Spear Pillar + new(DP) { Species = 485, Level = 70, Location = 084, GroundTile = Cave }, // Heatran @ Stark Mountain + new(Pt) { Species = 485, Level = 50, Location = 084, GroundTile = Cave }, // Heatran @ Stark Mountain + new(DP) { Species = 486, Level = 70, Location = 064, GroundTile = Cave }, // Regigigas @ Snowpoint Temple + new(Pt) { Species = 486, Level = 01, Location = 064, GroundTile = Cave }, // Regigigas @ Snowpoint Temple + new(DP) { Species = 487, Level = 70, Location = 062, GroundTile = Cave, Form = 0 }, // Giratina @ Turnback Cave + new(Pt) { Species = 487, Level = 47, Location = 062, GroundTile = Cave, Form = 0 }, // Giratina @ Turnback Cave + new(Pt) { Species = 487, Level = 47, Location = 117, GroundTile = Distortion, Form = 1, HeldItem = 112 }, // Giratina @ Distortion World + + // Event + //new(DP) { Species = 491, Level = 40, Location = 079, GroundTile = Grass }, // Darkrai @ Newmoon Island (Unreleased in Diamond and Pearl) + new(Pt) { Species = 491, Level = 50, Location = 079, GroundTile = Grass }, // Darkrai @ Newmoon Island + new(Pt) { Species = 492, Form = 0, Level = 30, Location = 063, Fateful = true }, // Shaymin @ Flower Paradise + //new(DP) { Species = 492, Form = 0, Level = 30, Location = 063, Fateful = false }, // Shaymin @ Flower Paradise (Unreleased in Diamond and Pearl) + //new(DPPt) { Species = 493, Form = 0, Level = 80, Location = 086, GroundTile = Cave }, // Arceus @ Hall of Origin (Unreleased) + + // Roamers + new(DPPt) { Roaming = true, Species = 481, Level = 50, GroundTile = Grass | Water }, // Mesprit + new(DPPt) { Roaming = true, Species = 488, Level = 50, GroundTile = Grass | Water }, // Cresselia + new(Pt) { Roaming = true, Species = 144, Level = 60, GroundTile = Grass | Water }, // Articuno + new(Pt) { Roaming = true, Species = 145, Level = 60, GroundTile = Grass | Water }, // Zapdos + new(Pt) { Roaming = true, Species = 146, Level = 60, GroundTile = Grass | Water }, // Moltres + }; + + private static readonly EncounterStatic4[] Encounter_HGSS = + { + // Starters + new(HGSS) { Gift = true, Species = 001, Level = 05, Location = 138, GroundTile = Max_Pt }, // Bulbasaur @ Pallet Town + new(HGSS) { Gift = true, Species = 004, Level = 05, Location = 138, GroundTile = Max_Pt }, // Charmander + new(HGSS) { Gift = true, Species = 007, Level = 05, Location = 138, GroundTile = Max_Pt }, // Squirtle + new(HGSS) { Gift = true, Species = 152, Level = 05, Location = 126, GroundTile = Max_DP }, // Chikorita @ New Bark Town + new(HGSS) { Gift = true, Species = 155, Level = 05, Location = 126, GroundTile = Max_DP }, // Cyndaquil + new(HGSS) { Gift = true, Species = 158, Level = 05, Location = 126, GroundTile = Max_DP }, // Totodile + new(HGSS) { Gift = true, Species = 252, Level = 05, Location = 148, GroundTile = Max_Pt }, // Treecko @ Saffron City + new(HGSS) { Gift = true, Species = 255, Level = 05, Location = 148, GroundTile = Max_Pt }, // Torchic + new(HGSS) { Gift = true, Species = 258, Level = 05, Location = 148, GroundTile = Max_Pt }, // Mudkip + + // Fossils @ Pewter City + new(HGSS) { Gift = true, Species = 138, Level = 20, Location = 140, GroundTile = Max_Pt }, // Omanyte + new(HGSS) { Gift = true, Species = 140, Level = 20, Location = 140, GroundTile = Max_Pt }, // Kabuto + new(HGSS) { Gift = true, Species = 142, Level = 20, Location = 140, GroundTile = Max_Pt }, // Aerodactyl + new(HGSS) { Gift = true, Species = 345, Level = 20, Location = 140, GroundTile = Max_Pt }, // Lileep + new(HGSS) { Gift = true, Species = 347, Level = 20, Location = 140, GroundTile = Max_Pt }, // Anorith + new(HGSS) { Gift = true, Species = 408, Level = 20, Location = 140, GroundTile = Max_Pt }, // Cranidos + new(HGSS) { Gift = true, Species = 410, Level = 20, Location = 140, GroundTile = Max_Pt }, // Shieldon + + // Gift + new(HGSS) { Gift = true, Species = 072, Level = 15, Location = 130, GroundTile = Max_Pt }, // Tentacool @ Cianwood City + new(HGSS) { Gift = true, Species = 133, Level = 05, Location = 131, GroundTile = Max_Pt }, // Eevee @ Goldenrod City + new(HGSS) { Gift = true, Species = 147, Level = 15, Location = 222, GroundTile = Max_Pt, Moves = new[] {245} }, // Dratini @ Dragon's Den (ExtremeSpeed) + new(HGSS) { Gift = true, Species = 236, Level = 10, Location = 216, GroundTile = Max_Pt }, // Tyrogue @ Mt. Mortar + new(HGSS) { Gift = true, Species = 175, Level = 01, EggLocation = 2013, Moves = new[] {326} }, // Togepi Egg from Mr. Pokemon (Extrasensory as Egg move) + new(HGSS) { Gift = true, Species = 179, Level = 01, EggLocation = 2014 }, // Mareep Egg from Primo + new(HGSS) { Gift = true, Species = 194, Level = 01, EggLocation = 2014 }, // Wooper Egg from Primo + new(HGSS) { Gift = true, Species = 218, Level = 01, EggLocation = 2014 }, // Slugma Egg from Primo + + // Celadon City Game Corner + new(HGSS) { Gift = true, Species = 122, Level = 15, Location = 144, GroundTile = Max_Pt }, // Mr. Mime + new(HGSS) { Gift = true, Species = 133, Level = 15, Location = 144, GroundTile = Max_Pt }, // Eevee + new(HGSS) { Gift = true, Species = 137, Level = 15, Location = 144, GroundTile = Max_Pt }, // Porygon + + // Goldenrod City Game Corner + new(HGSS) { Gift = true, Species = 063, Level = 15, Location = 131, GroundTile = Max_Pt }, // Abra + new(HG ) { Gift = true, Species = 023, Level = 15, Location = 131, GroundTile = Max_Pt }, // Ekans + new( SS) { Gift = true, Species = 027, Level = 15, Location = 131, GroundTile = Max_Pt }, // Sandshrew + new(HGSS) { Gift = true, Species = 147, Level = 15, Location = 131, GroundTile = Max_Pt }, // Dratini + + // Team Rocket HQ Trap Floor + new(HGSS) { Species = 100, Level = 23, Location = 213, GroundTile = Building }, // Voltorb + new(HGSS) { Species = 074, Level = 21, Location = 213, GroundTile = Building }, // Geodude + new(HGSS) { Species = 109, Level = 21, Location = 213, GroundTile = Building }, // Koffing + + // Stationary + new(HGSS) { Species = 130, Level = 30, Location = 135, GroundTile = Water, Shiny = Shiny.Always }, // Gyarados @ Lake of Rage + new(HGSS) { Species = 131, Level = 20, Location = 210, GroundTile = Water }, // Lapras @ Union Cave Friday Only + new(HGSS) { Species = 101, Level = 23, Location = 213, GroundTile = Building }, // Electrode @ Team Rocket HQ + new(HGSS) { Species = 143, Level = 50, Location = 159 }, // Snorlax @ Route 11 + new(HGSS) { Species = 143, Level = 50, Location = 160 }, // Snorlax @ Route 12 + new(HGSS) { Species = 185, Level = 20, Location = 184 }, // Sudowoodo @ Route 36, Encounter does not have type + + new(HGSS) // Spiky-Eared Pichu @ Ilex Forest + { + Species = 172, + Level = 30, + Gender = 1, + Form = 1, + Nature = Nature.Naughty, + Location = 214, + Moves = new[] { 344, 270, 207, 220 }, + GroundTile = Max_Pt, + Shiny = Shiny.Never, + }, + + // Stationary Legendary + new(HGSS) { Species = 144, Level = 50, Location = 203, GroundTile = Cave }, // Articuno @ Seafoam Islands + new(HGSS) { Species = 145, Level = 50, Location = 158 }, // Zapdos @ Route 10 + new(HGSS) { Species = 146, Level = 50, Location = 219, GroundTile = Cave }, // Moltres @ Mt. Silver Cave + new(HGSS) { Species = 150, Level = 70, Location = 199, GroundTile = Cave }, // Mewtwo @ Cerulean Cave + new(HGSS) { Species = 245, Level = 40, Location = 173 }, // Suicune @ Route 25 + new(HGSS) { Species = 245, Level = 40, Location = 206, GroundTile = Cave }, // Suicune @ Burned Tower + new( SS) { Species = 249, Level = 45, Location = 218, GroundTile = Water }, // Lugia @ Whirl Islands + new(HG ) { Species = 249, Level = 70, Location = 218, GroundTile = Water }, // Lugia @ Whirl Islands + new(HG ) { Species = 250, Level = 45, Location = 205, GroundTile = Building }, // Ho-Oh @ Bell Tower + new( SS) { Species = 250, Level = 70, Location = 205, GroundTile = Building }, // Ho-Oh @ Bell Tower + new( SS) { Species = 380, Level = 40, Location = 140, GroundTile = Building }, // Latias @ Pewter City + new(HG ) { Species = 381, Level = 40, Location = 140, GroundTile = Building }, // Latios @ Pewter City + new(HG ) { Species = 382, Level = 50, Location = 232, GroundTile = Cave }, // Kyogre @ Embedded Tower + new( SS) { Species = 383, Level = 50, Location = 232, GroundTile = Cave }, // Groudon @ Embedded Tower + new(HGSS) { Species = 384, Level = 50, Location = 232, GroundTile = Cave }, // Rayquaza @ Embedded Tower + new(HGSS) { Species = 483, Level = 01, Location = 231, Gift = true, GroundTile = Max_Pt }, // Dialga @ Sinjoh Ruins + new(HGSS) { Species = 484, Level = 01, Location = 231, Gift = true, GroundTile = Max_Pt }, // Palkia @ Sinjoh Ruins + new(HGSS) { Species = 487, Level = 01, Location = 231, Gift = true, GroundTile = Max_Pt, Form = 1, HeldItem = 112 }, // Giratina @ Sinjoh Ruins + + // Johto Roamers + new(HGSS) { Roaming = true, Species = 243, Level = 40, GroundTile = Grass | Water }, // Raikou + new(HGSS) { Roaming = true, Species = 244, Level = 40, GroundTile = Grass | Water }, // Entei + + // Kanto Roamers + new(HG ) { Roaming = true, Species = 380, Level = 35, GroundTile = Grass | Water }, // Latias + new( SS) { Roaming = true, Species = 381, Level = 35, GroundTile = Grass | Water }, // Latios + }; + #endregion + #region Trade Tables + + private static readonly EncounterTrade4[] RanchGifts = + { + new EncounterTrade4RanchGift(323975838, 025, 18) { Moves = new[] {447,085,148,104}, TID = 1000, SID = 19840, OTGender = 1, MetLocation = 0068, Gender = 0, Ability = OnlyFirst, CurrentLevel = 20 }, // Pikachu + new EncounterTrade4RanchGift(323977664, 037, 16) { Moves = new[] {412,109,053,219}, TID = 1000, SID = 21150, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlyFirst, CurrentLevel = 30 }, // Vulpix + new EncounterTrade4RanchGift(323975579, 077, 13) { Moves = new[] {036,033,039,052}, TID = 1000, SID = 01123, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlySecond, CurrentLevel = 16 }, // Ponyta + new EncounterTrade4RanchGift(323975564, 108, 34) { Moves = new[] {076,111,014,205}, TID = 1000, SID = 03050, OTGender = 1, MetLocation = 0077, Gender = 0, Ability = OnlyFirst, CurrentLevel = 40 }, // Lickitung + new EncounterTrade4RanchGift(323977579, 114, 01) { Moves = new[] {437,438,079,246}, TID = 1000, SID = 49497, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlySecond }, // Tangela + new EncounterTrade4RanchGift(323977675, 133, 16) { Moves = new[] {363,270,098,247}, TID = 1000, SID = 47710, OTGender = 1, MetLocation = 0068, Gender = 0, Ability = OnlySecond, CurrentLevel = 30 }, // Eevee + new EncounterTrade4RanchGift(323977588, 142, 20) { Moves = new[] {363,089,444,332}, TID = 1000, SID = 43066, OTGender = 1, MetLocation = 0094, Gender = 0, Ability = OnlyFirst, CurrentLevel = 50 }, // Aerodactyl + new EncounterTrade4RanchGift(232975554, 193, 22) { Moves = new[] {318,095,246,138}, TID = 1000, SID = 42301, OTGender = 1, MetLocation = 0052, Gender = 0, Ability = OnlyFirst, CurrentLevel = 45, Ball = 5 }, // Yanma + new EncounterTrade4RanchGift(323975570, 241, 16) { Moves = new[] {208,215,360,359}, TID = 1000, SID = 02707, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlyFirst, CurrentLevel = 48 }, // Miltank + new EncounterTrade4RanchGift(323975563, 285, 22) { Moves = new[] {402,147,206,078}, TID = 1000, SID = 02788, OTGender = 1, MetLocation = 3000, Gender = 0, Ability = OnlySecond, CurrentLevel = 45, Ball = 5 }, // Shroomish + new EncounterTrade4RanchGift(323975559, 320, 30) { Moves = new[] {156,323,133,058}, TID = 1000, SID = 27046, OTGender = 1, MetLocation = 0038, Gender = 0, Ability = OnlySecond, CurrentLevel = 45 }, // Wailmer + new EncounterTrade4RanchGift(323977657, 360, 01) { Moves = new[] {204,150,227,000}, TID = 1000, SID = 01788, OTGender = 1, MetLocation = 0004, Gender = 0, Ability = OnlySecond, EggLocation = 2000 }, // Wynaut + new EncounterTrade4RanchGift(323975563, 397, 02) { Moves = new[] {355,017,283,018}, TID = 1000, SID = 59298, OTGender = 1, MetLocation = 0016, Gender = 0, Ability = OnlySecond, CurrentLevel = 23 }, // Staravia + new EncounterTrade4RanchGift(323970584, 415, 05) { Moves = new[] {230,016,000,000}, TID = 1000, SID = 54140, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlyFirst, CurrentLevel = 20 }, // Combee + new EncounterTrade4RanchGift(323977539, 417, 09) { Moves = new[] {447,045,351,098}, TID = 1000, SID = 18830, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlySecond, CurrentLevel = 10 }, // Pachirisu + new EncounterTrade4RanchGift(323974107, 422, 20) { Moves = new[] {363,352,426,104}, TID = 1000, SID = 39272, OTGender = 1, MetLocation = 0028, Gender = 0, Ability = OnlySecond, CurrentLevel = 25, Form = 1 }, // Shellos + new EncounterTrade4RanchGift(323977566, 427, 10) { Moves = new[] {204,193,409,098}, TID = 1000, SID = 31045, OTGender = 1, MetLocation = 3000, Gender = 1, Ability = OnlyFirst, CurrentLevel = 16 }, // Buneary + new EncounterTrade4RanchGift(323975579, 453, 22) { Moves = new[] {310,207,426,389}, TID = 1000, SID = 41342, OTGender = 1, MetLocation = 0052, Gender = 0, Ability = OnlySecond, CurrentLevel = 31, Ball = 5 }, // Croagunk + new EncounterTrade4RanchGift(323977566, 456, 15) { Moves = new[] {213,352,219,392}, TID = 1000, SID = 48348, OTGender = 1, MetLocation = 0020, Gender = 1, Ability = OnlyFirst, CurrentLevel = 35 }, // Finneon + new EncounterTrade4RanchGift(323975582, 459, 32) { Moves = new[] {452,420,275,059}, TID = 1000, SID = 23360, OTGender = 1, MetLocation = 0031, Gender = 0, Ability = OnlyFirst, CurrentLevel = 41 }, // Snover + new EncounterTrade4RanchSpecial(151, 50) { Moves = new[] {235,216,095,100}, TID = 1000, SID = 59228, OTGender = 1, Ball = 0x10, Gender = 2 }, // Mew + new EncounterTrade4RanchSpecial(489, 01) { Moves = new[] {447,240,156,057}, TID = 1000, SID = 09248, OTGender = 1, Ball = 0x10, Gender = 2, CurrentLevel = 50, EggLocation = 3000 }, // Phione + }; + + private static readonly EncounterTrade4PID[] TradeGift_DPPtIngame = + { + new(DPPt, 0x0000008E, 063, 01) { Ability = OnlyFirst, TID = 25643, SID = 00000, OTGender = 1, Gender = 0, IVs = new[] {15,15,15,20,25,25} }, // Machop -> Abra + new(DPPt, 0x00000867, 441, 01) { Ability = OnlySecond, TID = 44142, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {15,20,15,25,25,15}, Contest = 20 }, // Buizel -> Chatot + new(DPPt, 0x00000088, 093, 35) { Ability = OnlyFirst, TID = 19248, SID = 00000, OTGender = 1, Gender = 0, IVs = new[] {20,25,15,25,15,15} }, // Medicham (35 from Route 217) -> Haunter + new(DPPt, 0x0000045C, 129, 01) { Ability = OnlyFirst, TID = 53277, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {15,25,15,20,25,15} }, // Finneon -> Magikarp + }; + + internal static readonly EncounterTrade4[] TradeGift_DPPt = ArrayUtil.ConcatAll(TradeGift_DPPtIngame, RanchGifts); + + internal static readonly EncounterTrade4PID[] TradeGift_HGSS = + { + new(HGSS, 0x000025EF, 095, 01) { Ability = OnlySecond, TID = 48926, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {25,20,25,15,15,15} }, // Bellsprout -> Onix + new(HGSS, 0x00002310, 066, 01) { Ability = OnlyFirst, TID = 37460, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {15,25,20,20,15,15} }, // Drowzee -> Machop + new(HGSS, 0x000001DB, 100, 01) { Ability = OnlySecond, TID = 29189, SID = 00000, OTGender = 0, Gender = 2, IVs = new[] {15,20,15,25,25,15} }, // Krabby -> Voltorb + new(HGSS, 0x0001FC0A, 085, 15) { Ability = OnlyFirst, TID = 00283, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {20,20,20,15,15,15} }, // Dragonair (15 from DPPt) -> Dodrio + new(HGSS, 0x0000D136, 082, 19) { Ability = OnlyFirst, TID = 50082, SID = 00000, OTGender = 0, Gender = 2, IVs = new[] {15,20,15,20,20,20} }, // Dugtrio (19 from Diglett's Cave) -> Magneton + new(HGSS, 0x000034E4, 178, 16) { Ability = OnlyFirst, TID = 15616, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {15,20,15,20,20,20} }, // Haunter (16 from Old Chateau) -> Xatu + new(HGSS, 0x00485876, 025, 02) { Ability = OnlyFirst, TID = 33038, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {20,25,18,31,25,13} }, // Pikachu + new(HGSS, 0x0012B6D4, 374, 31) { Ability = OnlyFirst, TID = 23478, SID = 00000, OTGender = 0, Gender = 2, IVs = new[] {28,29,24,23,24,25} }, // Forretress -> Beldum + new(HGSS, 0x0012971C, 111, 01) { Ability = OnlyFirst, TID = 06845, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {22,31,13,00,22,09}, Moves = new[]{422} }, // Bonsly -> Rhyhorn + new(HGSS, 0x00101596, 208, 01) { Ability = OnlyFirst, TID = 26491, SID = 00000, OTGender = 1, Gender = 0, IVs = new[] {08,30,28,06,18,20}}, // Any -> Steelix + + //Gift + new(HGSS, 0x00006B5E, 021, 20) { Ability = OnlyFirst, TID = 01001, SID = 00000, OTGender = 0, Gender = 1, IVs = new[] {15,20,15,20,20,20}, MetLocation = 183, Moves = new[]{043,031,228,332} },// Webster's Spearow + new(HGSS, 0x000214D7, 213, 20) { Ability = OnlySecond, TID = 04336, SID = 00001, OTGender = 0, Gender = 0, IVs = new[] {15,20,15,20,20,20}, MetLocation = 130, Moves = new[]{132,117,227,219} },// Kirk's Shuckle + }; + + private const string tradeDPPt = "tradedppt"; + private const string tradeHGSS = "tradehgss"; + private static readonly string[][] TradeDPPt = Util.GetLanguageStrings8(tradeDPPt); + private static readonly string[][] TradeHGSS = Util.GetLanguageStrings8(tradeHGSS); + #endregion + + internal static readonly EncounterStatic4[] StaticD = GetEncounters(Encounter_DPPt, D); + internal static readonly EncounterStatic4[] StaticP = GetEncounters(Encounter_DPPt, P); + internal static readonly EncounterStatic4[] StaticPt = GetEncounters(Encounter_DPPt, Pt); + internal static readonly EncounterStatic[] StaticHG = GetEncounters(ArrayUtil.ConcatAll(Encounter_HGSS, Encounter_PokeWalker), HG); + internal static readonly EncounterStatic[] StaticSS = GetEncounters(ArrayUtil.ConcatAll(Encounter_HGSS, Encounter_PokeWalker), SS); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters5.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters5.cs index b2eb1cf54..c20c048a5 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters5.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters5.cs @@ -2,618 +2,617 @@ using static PKHeX.Core.GameVersion; using static PKHeX.Core.AbilityPermission; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 Encounters +/// +public static class Encounters5 { - /// - /// Generation 5 Encounters - /// - public static class Encounters5 + internal static readonly EncounterArea5[] SlotsB = EncounterArea5.GetAreas(Get("b", "51"), B); + internal static readonly EncounterArea5[] SlotsW = EncounterArea5.GetAreas(Get("w", "51"), W); + internal static readonly EncounterArea5[] SlotsB2 = EncounterArea5.GetAreas(Get("b2", "52"), B2); + internal static readonly EncounterArea5[] SlotsW2 = EncounterArea5.GetAreas(Get("w2", "52"), W2); + + static Encounters5() { - internal static readonly EncounterArea5[] SlotsB = EncounterArea5.GetAreas(Get("b", "51"), B); - internal static readonly EncounterArea5[] SlotsW = EncounterArea5.GetAreas(Get("w", "51"), W); - internal static readonly EncounterArea5[] SlotsB2 = EncounterArea5.GetAreas(Get("b2", "52"), B2); - internal static readonly EncounterArea5[] SlotsW2 = EncounterArea5.GetAreas(Get("w2", "52"), W2); - - static Encounters5() - { - MarkEncounterTradeStrings(TradeGift_BW, TradeBW); - MarkEncounterTradeStrings(TradeGift_B2W2_Regular, TradeB2W2); - } - - #region Dream Radar Tables - - private static readonly EncounterStatic5DR[] Encounter_DreamRadar = - { - new(079, 0), // Slowpoke - new(120, 0), // Staryu - new(137, 0), // Porygon - new(163, 0), // Hoothoot - new(174, 0), // Igglybuff - new(175, 0), // Togepi - new(213, 0), // Shuckle - new(238, 0), // Smoochum - new(249, 0), // Lugia (SoulSilver cart) - new(250, 0), // Ho-Oh (HeartGold cart) - new(280, 0), // Ralts - new(333, 0), // Swablu - new(374, 0), // Beldum - new(425, 0), // Drifloon - new(436, 0), // Bronzor - new(442, 0), // Spiritomb - new(447, 0), // Riolu - new(479, 0, Any12), // Rotom (no HA) - new(483, 0), // Dialga (Diamond cart) - new(484, 0), // Palkia (Pearl cart) - new(487, 0), // Giratina (Platinum cart) - new(517, 0), // Munna - new(561, 0), // Sigilyph - new(641, 1), // Therian Tornadus - new(642, 1), // Therian Thundurus - new(645, 1), // Therian Landorus - }; - - #endregion - #region DreamWorld Encounter - - public static readonly EncounterStatic5[] DreamWorld_Common = DreamWorldEntry.GetArray(Gen5, new DreamWorldEntry[] - { - // Pleasant Forest - new(019, 10, 098, 382, 231), // Rattata - new(043, 10, 230, 298, 202), // Oddish - new(069, 10, 022, 235, 402), // Bellsprout - new(077, 10, 033, 037, 257), // Ponyta - new(083, 10, 210, 355, 348), // Farfetch'd - new(084, 10, 045, 175, 355), // Doduo - new(102, 10, 140, 235, 202), // Exeggcute - new(108, 10, 122, 214, 431), // Lickitung - new(114, 10, 079, 073, 402), // Tangela - new(115, 10, 252, 068, 409), // Kangaskhan - new(161, 10, 010, 203, 343), // Sentret - new(179, 10, 084, 115, 351), // Mareep - new(191, 10, 072, 230, 414), // Sunkern - new(234, 10, 033, 050, 285), // Stantler - new(261, 10, 336, 305, 399), // Poochyena - new(283, 10, 145, 056, 202), // Surskit - new(399, 10, 033, 401, 290), // Bidoof - new(403, 10, 268, 393, 400), // Shinx - new(431, 10, 252, 372, 290), // Glameow - new(054, 10, 346, 227, 362), // Psyduck - new(058, 10, 044, 034, 203), // Growlithe - new(123, 10, 098, 226, 366), // Scyther - new(128, 10, 099, 231, 431), // Tauros - new(183, 10, 111, 453, 008), // Marill - new(185, 10, 175, 205, 272), // Sudowoodo - new(203, 10, 093, 243, 285), // Girafarig - new(241, 10, 111, 174, 231), // Miltank - new(263, 10, 033, 271, 387), // Zigzagoon - new(427, 10, 193, 252, 409), // Buneary - new(037, 10, 046, 257, 399), // Vulpix - new(060, 10, 095, 054, 214), // Poliwag - new(177, 10, 101, 297, 202), // Natu - new(239, 10, 084, 238, 393), // Elekid - new(300, 10, 193, 321, 445), // Skitty - - // Windswept Sky - new(016, 10, 016, 211, 290), // Pidgey - new(021, 10, 064, 185, 211), // Spearow - new(041, 10, 048, 095, 162), // Zubat - new(142, 10, 044, 372, 446), // Aerodactyl - new(165, 10, 004, 450, 009), // Ledyba - new(187, 10, 235, 227, 340), // Hoppip - new(193, 10, 098, 364, 202), // Yanma - new(198, 10, 064, 109, 355), // Murkrow - new(207, 10, 028, 364, 366), // Gligar - new(225, 10, 217, 420, 264), // Delibird - new(276, 10, 064, 203, 413), // Taillow - new(397, 14, 017, 297, 366), // Staravia - new(227, 10, 064, 065, 355), // Skarmory - new(357, 10, 016, 073, 318), // Tropius - - // Sparkling Sea - new(086, 10, 029, 333, 214), // Seel - new(090, 10, 110, 112, 196), // Shellder - new(116, 10, 145, 190, 362), // Horsea - new(118, 10, 064, 060, 352), // Goldeen - new(129, 10, 150, 175, 340), // Magikarp - new(138, 10, 044, 330, 196), // Omanyte - new(140, 10, 071, 175, 446), // Kabuto - new(170, 10, 086, 133, 351), // Chinchou - new(194, 10, 055, 034, 401), // Wooper - new(211, 10, 040, 453, 290), // Qwilfish - new(223, 10, 199, 350, 362), // Remoraid - new(226, 10, 048, 243, 314), // Mantine - new(320, 10, 055, 214, 340), // Wailmer - new(339, 10, 189, 214, 209), // Barboach - new(366, 10, 250, 445, 392), // Clamperl - new(369, 10, 055, 214, 414), // Relicanth - new(370, 10, 204, 300, 196), // Luvdisc - new(418, 10, 346, 163, 352), // Buizel - new(456, 10, 213, 186, 352), // Finneon - new(072, 10, 048, 367, 202), // Tentacool - new(318, 10, 044, 037, 399), // Carvanha - new(341, 10, 106, 232, 283), // Corphish - new(345, 10, 051, 243, 202), // Lileep - new(347, 10, 010, 446, 440), // Anorith - new(349, 10, 150, 445, 243), // Feebas - new(131, 10, 109, 032, 196), // Lapras - new(147, 10, 086, 352, 225), // Dratini - - // Spooky Manor - new(092, 10, 095, 050, 482), // Gastly - new(096, 10, 095, 427, 409), // Drowzee - new(122, 10, 112, 298, 285), // Mr. Mime - new(167, 10, 040, 527, 450), // Spinarak - new(200, 10, 149, 194, 517), // Misdreavus - new(228, 10, 336, 364, 399), // Houndour - new(325, 10, 149, 285, 278), // Spoink - new(353, 10, 101, 194, 220), // Shuppet - new(355, 10, 050, 220, 271), // Duskull - new(358, 10, 035, 095, 304), // Chimecho - new(434, 10, 103, 492, 389), // Stunky - new(209, 10, 204, 370, 038), // Snubbull - new(235, 10, 166, 445, 214), // Smeargle - new(313, 10, 148, 271, 366), // Volbeat - new(314, 10, 204, 313, 366), // Illumise - new(063, 10, 100, 285, 356), // Abra - - // Rugged Mountain - new(066, 10, 067, 418, 270), // Machop - new(081, 10, 319, 278, 356), // Magnemite - new(109, 10, 123, 399, 482), // Koffing - new(218, 10, 052, 517, 257), // Slugma - new(246, 10, 044, 399, 446), // Larvitar - new(324, 10, 052, 090, 446), // Torkoal - new(328, 10, 044, 324, 202), // Trapinch - new(331, 10, 071, 298, 009), // Cacnea - new(412, 10, 182, 450, 173), // Burmy - new(449, 10, 044, 254, 276), // Hippopotas - new(240, 10, 052, 009, 257), // Magby - new(322, 10, 052, 034, 257), // Numel - new(359, 10, 364, 224, 276), // Absol - new(453, 10, 040, 409, 441), // Croagunk - new(236, 10, 252, 364, 183), // Tyrogue - new(371, 10, 044, 349, 200), // Bagon - - // Icy Cave - new(027, 10, 028, 068, 162), // Sandshrew - new(074, 10, 111, 446, 431), // Geodude - new(095, 10, 020, 446, 431), // Onix - new(100, 10, 268, 324, 363), // Voltorb - new(104, 10, 125, 195, 067), // Cubone - new(293, 10, 253, 283, 428), // Whismur - new(304, 10, 106, 283, 457), // Aron - new(337, 10, 093, 414, 236), // Lunatone - new(338, 10, 093, 428, 234), // Solrock - new(343, 10, 229, 356, 428), // Baltoy - new(459, 10, 075, 419, 202), // Snover - new(050, 10, 028, 251, 446), // Diglett - new(215, 10, 269, 008, 067), // Sneasel - new(361, 10, 181, 311, 352), // Snorunt - new(220, 10, 316, 246, 333), // Swinub - new(443, 10, 082, 200, 203), // Gible - - // Dream Park - new(046, 10, 078, 440, 235), // Paras - new(204, 10, 120, 390, 356), // Pineco - new(265, 10, 040, 450, 173), // Wurmple - new(273, 10, 074, 331, 492), // Seedot - new(287, 10, 281, 400, 389), // Slakoth - new(290, 10, 141, 203, 400), // Nincada - new(311, 10, 086, 435, 324), // Plusle - new(312, 10, 086, 435, 324), // Minun - new(316, 10, 139, 151, 202), // Gulpin - new(352, 10, 185, 285, 513), // Kecleon - new(401, 10, 522, 283, 253), // Kricketot - new(420, 10, 073, 505, 331), // Cherubi - new(455, 10, 044, 476, 380), // Carnivine - new(023, 10, 040, 251, 399), // Ekans - new(175, 10, 118, 381, 253), // Togepi - new(190, 10, 010, 252, 007), // Aipom - new(285, 10, 078, 331, 264), // Shroomish - new(315, 10, 074, 079, 129), // Roselia - new(113, 10, 045, 068, 270), // Chansey - new(127, 10, 011, 370, 382), // Pinsir - new(133, 10, 028, 204, 129), // Eevee - new(143, 10, 133, 007, 278), // Snorlax - new(214, 10, 030, 175, 264), // Heracross - - // Pokémon Café Forest - new(061, 25, 240, 114, 352), // Poliwhirl - new(133, 10, 270, 204, 129), // Eevee - new(235, 10, 166, 445, 214), // Smeargle - new(412, 10, 182, 450, 173), // Burmy - - // PGL - new(212, 10, 211, Gender: 0), // Scizor - new(445, 48, Gender: 0), // Garchomp - new(149, 55, 245, Gender: 0), // Dragonite - new(248, 55, 069, Gender: 0), // Tyranitar - new(376, 45, 038, Gender: 2), // Metagross - }); - - public static readonly EncounterStatic5[] DreamWorld_BW = DreamWorldEntry.GetArray(BW, new DreamWorldEntry[] - { - // Pleasant Forest - new(029, 10, 010, 389, 162), // Nidoran♀ - new(032, 10, 064, 068, 162), // Nidoran♂ - new(174, 10, 047, 313, 270), // Igglybuff - new(187, 10, 235, 270, 331), // Hoppip - new(270, 10, 071, 073, 352), // Lotad - new(276, 10, 064, 119, 366), // Taillow - new(309, 10, 086, 423, 324), // Electrike - new(351, 10, 052, 466, 352), // Castform - new(417, 10, 098, 343, 351), // Pachirisu - - // Windswept Sky - new(012, 10, 093, 355, 314), // Butterfree - new(163, 10, 193, 101, 278), // Hoothoot - new(278, 10, 055, 239, 351), // Wingull - new(333, 10, 064, 297, 355), // Swablu - new(425, 10, 107, 095, 285), // Drifloon - new(441, 10, 119, 417, 272), // Chatot - - // Sparkling Sea - new(079, 10, 281, 335, 362), // Slowpoke - new(098, 10, 011, 133, 290), // Krabby - new(119, 33, 352, 214, 203), // Seaking - new(120, 10, 055, 278, 196), // Staryu - new(222, 10, 145, 109, 446), // Corsola - new(422, 10, 189, 281, 290, Form: 0), // Shellos-West - new(422, 10, 189, 281, 290, Form: 1), // Shellos-East - - // Spooky Manor - new(202, 15, 243, 204, 227), // Wobbuffet - new(238, 10, 186, 445, 285), // Smoochum - new(303, 10, 313, 424, 008), // Mawile - new(307, 10, 096, 409, 203), // Meditite - new(436, 10, 095, 285, 356), // Bronzor - new(052, 10, 010, 095, 290), // Meowth - new(479, 10, 086, 351, 324), // Rotom - new(280, 10, 093, 194, 270), // Ralts - new(302, 10, 193, 389, 180), // Sableye - new(442, 10, 180, 220, 196), // Spiritomb - - // Rugged Mountain - new(056, 10, 067, 179, 009), // Mankey - new(111, 10, 030, 068, 038), // Rhyhorn - new(231, 10, 175, 484, 402), // Phanpy - new(451, 10, 044, 097, 401), // Skorupi - new(216, 10, 313, 242, 264), // Teddiursa - new(296, 10, 292, 270, 008), // Makuhita - new(327, 10, 383, 252, 276), // Spinda - new(374, 10, 036, 428, 442), // Beldum - new(447, 10, 203, 418, 264), // Riolu - - // Icy Cave - new(173, 10, 227, 312, 214), // Cleffa - new(213, 10, 227, 270, 504), // Shuckle - new(299, 10, 033, 446, 246), // Nosepass - new(363, 10, 181, 090, 401), // Spheal - new(408, 10, 029, 442, 007), // Cranidos - new(206, 10, 111, 277, 446), // Dunsparce - new(410, 10, 182, 068, 090), // Shieldon - - // Dream Park - new(048, 10, 050, 226, 285), // Venonat - new(088, 10, 139, 114, 425), // Grimer - new(415, 10, 016, 366, 314), // Combee - new(015, 10, 031, 314, 210), // Beedrill - new(335, 10, 098, 458, 067), // Zangoose - new(336, 10, 044, 034, 401), // Seviper - - // PGL - new(134, 10, Gender: 0), // Vaporeon - new(135, 10, Gender: 0), // Jolteon - new(136, 10, Gender: 0), // Flareon - new(196, 10, Gender: 0), // Espeon - new(197, 10, Gender: 0), // Umbreon - new(470, 10, Gender: 0), // Leafeon - new(471, 10, Gender: 0), // Glaceon - new(001, 10, Gender: 0), // Bulbasaur - new(004, 10, Gender: 0), // Charmander - new(007, 10, Gender: 0), // Squirtle - new(453, 10, Gender: 0), // Croagunk - new(387, 10, Gender: 0), // Turtwig - new(390, 10, Gender: 0), // Chimchar - new(393, 10, Gender: 0), // Piplup - new(493, 100), // Arceus - new(252, 10, Gender: 0), // Treecko - new(255, 10, Gender: 0), // Torchic - new(258, 10, Gender: 0), // Mudkip - new(468, 10, 217, Gender: 0), // Togekiss - new(473, 34, Gender: 0), // Mamoswine - new(137, 10), // Porygon - new(384, 50), // Rayquaza - new(354, 37, 538, Gender: 1), // Banette - new(453, 10, 398, Gender: 0), // Croagunk - new(334, 35, 206, Gender: 0), // Altaria - new(242, 10), // Blissey - new(448, 10, 418, Gender: 0), // Lucario - new(189, 27, 206, Gender: 0), // Jumpluff - }); - - public static readonly EncounterStatic5[] DreamWorld_B2W2 = DreamWorldEntry.GetArray(B2W2, new DreamWorldEntry[] - { - // Pleasant Forest - new(535, 10, 496, 414, 352), // Tympole - new(546, 10, 073, 227, 388), // Cottonee - new(548, 10, 079, 204, 230), // Petilil - new(588, 10, 203, 224, 450), // Karrablast - new(616, 10, 051, 226, 227), // Shelmet - new(545, 30, 342, 390, 276), // Scolipede - - // Windswept Sky - new(519, 10, 016, 095, 234), // Pidove - new(561, 10, 095, 500, 257), // Sigilyph - new(580, 10, 432, 362, 382), // Ducklett - new(587, 10, 098, 403, 204), // Emolga - - // Sparkling Sea - new(550, 10, 029, 097, 428, Form: 0), // Basculin-Red - new(550, 10, 029, 097, 428, Form: 1), // Basculin-Blue - new(594, 10, 392, 243, 220), // Alomomola - new(618, 10, 189, 174, 281), // Stunfisk - new(564, 10, 205, 175, 334), // Tirtouga - - // Spooky Manor - new(605, 10, 377, 112, 417), // Elgyem - new(624, 10, 210, 427, 389), // Pawniard - new(596, 36, 486, 050, 228), // Galvantula - new(578, 32, 105, 286, 271), // Duosion - new(622, 10, 205, 007, 009), // Golett - - // Rugged Mountain - new(631, 10, 510, 257, 202), // Heatmor - new(632, 10, 210, 203, 422), // Durant - new(556, 10, 042, 073, 191), // Maractus - new(558, 34, 157, 068, 400), // Crustle - new(553, 40, 242, 068, 212), // Krookodile - - // Icy Cave - new(529, 10, 229, 319, 431), // Drilbur - new(621, 10, 044, 424, 389), // Druddigon - new(525, 25, 479, 174, 484), // Boldore - new(583, 35, 429, 420, 286), // Vanillish - new(600, 38, 451, 356, 393), // Klang - new(610, 10, 082, 068, 400), // Axew - - // Dream Park - new(531, 10, 270, 227, 281), // Audino - new(538, 10, 020, 008, 276), // Throh - new(539, 10, 249, 009, 530), // Sawk - new(559, 10, 067, 252, 409), // Scraggy - new(533, 25, 067, 183, 409), // Gurdurr - - // PGL - new(575, 32, 243, Gender: 0), // Gothorita - new(025, 10, 029, Gender: 0), // Pikachu - new(511, 10, 437, Gender: 0), // Pansage - new(513, 10, 257, Gender: 0), // Pansear - new(515, 10, 056, Gender: 0), // Panpour - new(387, 10, 254, Gender: 0), // Turtwig - new(390, 10, 252, Gender: 0), // Chimchar - new(393, 10, 297, Gender: 0), // Piplup - new(575, 32, 286, Gender: 0), // Gothorita - }); - - #endregion - #region Static Encounter/Gift Tables - private static readonly EncounterStatic5[] Encounter_BW = - { - // Starters @ Nuvema Town - new(BW) { Gift = true, Species = 495, Level = 05, Location = 004 }, // Snivy - new(BW) { Gift = true, Species = 498, Level = 05, Location = 004 }, // Tepig - new(BW) { Gift = true, Species = 501, Level = 05, Location = 004 }, // Oshawott - - // Fossils @ Nacrene City - new(BW) { Gift = true, Species = 138, Level = 25, Location = 007 }, // Omanyte - new(BW) { Gift = true, Species = 140, Level = 25, Location = 007 }, // Kabuto - new(BW) { Gift = true, Species = 142, Level = 25, Location = 007 }, // Aerodactyl - new(BW) { Gift = true, Species = 345, Level = 25, Location = 007 }, // Lileep - new(BW) { Gift = true, Species = 347, Level = 25, Location = 007 }, // Anorith - new(BW) { Gift = true, Species = 408, Level = 25, Location = 007 }, // Cranidos - new(BW) { Gift = true, Species = 410, Level = 25, Location = 007 }, // Shieldon - new(BW) { Gift = true, Species = 564, Level = 25, Location = 007 }, // Tirtouga - new(BW) { Gift = true, Species = 566, Level = 25, Location = 007 }, // Archen - - // Gift - new(BW) { Gift = true, Species = 511, Level = 10, Location = 032 }, // Pansage @ Dreamyard - new(BW) { Gift = true, Species = 513, Level = 10, Location = 032 }, // Pansear - new(BW) { Gift = true, Species = 515, Level = 10, Location = 032 }, // Panpour - new(BW) { Gift = true, Species = 129, Level = 05, Location = 068 }, // Magikarp @ Marvelous Bridge - new(BW) { Gift = true, Species = 636, Level = 01, EggLocation = 60003 }, // Larvesta Egg from Treasure Hunter - - // Stationary - new(BW) { Species = 518, Level = 50, Location = 032, Ability = OnlyHidden }, // Musharna @ Dreamyard Friday Only - new(BW) { Species = 590, Level = 20, Location = 019 }, // Foongus @ Route 6 - new(BW) { Species = 590, Level = 30, Location = 023 }, // Foongus @ Route 10 - new(BW) { Species = 591, Level = 40, Location = 023 }, // Amoonguss @ Route 10 - new(BW) { Species = 555, Level = 35, Location = 034, Ability = OnlyHidden }, // HA Darmanitan @ Desert Resort - new(BW) { Species = 637, Level = 70, Location = 035 }, // Volcarona @ Relic Castle - - // Stationary Legendary - new(BW) { Species = 638, Level = 42, Location = 074 }, // Cobalion @ Guidance Chamber - new(BW) { Species = 639, Level = 42, Location = 073 }, // Terrakion @ Trial Chamber - new(BW) { Species = 640, Level = 42, Location = 055 }, // Virizion @ Rumination Field - new(B ) { Species = 643, Level = 50, Location = 045, Shiny = Shiny.Never }, // Reshiram @ N's Castle - new(B ) { Species = 643, Level = 50, Location = 039, Shiny = Shiny.Never }, // Reshiram @ Dragonspiral Tower - new( W) { Species = 644, Level = 50, Location = 045, Shiny = Shiny.Never }, // Zekrom @ N's Castle - new( W) { Species = 644, Level = 50, Location = 039, Shiny = Shiny.Never }, // Zekrom @ Dragonspiral Tower - new(BW) { Species = 645, Level = 70, Location = 070 }, // Landorus @ Abundant Shrine - new(BW) { Species = 646, Level = 75, Location = 061 }, // Kyurem @ Giant Chasm - - // Event - new(BW) { Species = 494, Level = 15, Location = 062, Shiny = Shiny.Never}, // Victini @ Liberty Garden - new(BW) { Species = 570, Level = 10, Location = 008, Shiny = Shiny.Never, Gender = 0 }, // Zorua @ Castelia City - new(BW) { Species = 571, Level = 25, Location = 072, Shiny = Shiny.Never, Gender = 1 }, // Zoroark @ Lostlorn Forest - - // Roamer - new(B ) { Roaming = true, Species = 641, Level = 40, Location = 25 }, // Tornadus - new( W) { Roaming = true, Species = 642, Level = 40, Location = 25 }, // Thundurus - }; - - private static readonly EncounterStatic5[] Encounter_B2W2_Regular = - { - // Starters @ Aspertia City - new(B2W2) { Gift = true, Species = 495, Level = 05, Location = 117 }, // Snivy - new(B2W2) { Gift = true, Species = 498, Level = 05, Location = 117 }, // Tepig - new(B2W2) { Gift = true, Species = 501, Level = 05, Location = 117 }, // Oshawott - - // Fossils @ Nacrene City - new(B2W2) { Gift = true, Species = 138, Level = 25, Location = 007 }, // Omanyte - new(B2W2) { Gift = true, Species = 140, Level = 25, Location = 007 }, // Kabuto - new(B2W2) { Gift = true, Species = 142, Level = 25, Location = 007 }, // Aerodactyl - new(B2W2) { Gift = true, Species = 345, Level = 25, Location = 007 }, // Lileep - new(B2W2) { Gift = true, Species = 347, Level = 25, Location = 007 }, // Anorith - new(B2W2) { Gift = true, Species = 408, Level = 25, Location = 007 }, // Cranidos - new(B2W2) { Gift = true, Species = 410, Level = 25, Location = 007 }, // Shieldon - new(B2W2) { Gift = true, Species = 564, Level = 25, Location = 007 }, // Tirtouga - new(B2W2) { Gift = true, Species = 566, Level = 25, Location = 007 }, // Archen - - // Gift - new(B2W2) { Gift = true, Species = 133, Level = 10, Location = 008, Ability = OnlyHidden }, // HA Eevee @ Castelia City - new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 0 }, // HA Deerling @ Route 6 - new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 1 }, // HA Deerling @ Route 6 - new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 2 }, // HA Deerling @ Route 6 - new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 3 }, // HA Deerling @ Route 6 - new(B2 ) { Gift = true, Species = 443, Level = 01, Location = 122, Shiny = Shiny.Always, Gender = 0 }, // Shiny Gible @ Floccesy Town - new( W2) { Gift = true, Species = 147, Level = 01, Location = 122, Shiny = Shiny.Always, Gender = 0 }, // Shiny Dratini @ Floccesy Town - new(B2W2) { Gift = true, Species = 129, Level = 05, Location = 068 } , // Magikarp @ Marvelous Bridge - new(B2W2) { Gift = true, Species = 440, Level = 01, EggLocation = 60003 }, // Happiny Egg from PKMN Breeder - - // Stationary - new(B2W2) { Species = 590, Level = 29, Location = 019 }, // Foongus @ Route 6 - new(B2W2) { Species = 591, Level = 43, Location = 024 }, // Amoonguss @ Route 11 - new(B2W2) { Species = 591, Level = 47, Location = 127 }, // Amoonguss @ Route 22 - new(B2W2) { Species = 591, Level = 56, Location = 128 }, // Amoonguss @ Route 23 - new(B2 ) { Species = 593, Level = 40, Location = 071, Ability = OnlyHidden, Gender = 0 }, // HA Jellicent @ Undella Bay Mon Only - new( W2) { Species = 593, Level = 40, Location = 071, Ability = OnlyHidden, Gender = 1 }, // HA Jellicent @ Undella Bay Thurs Only - new(B2W2) { Species = 593, Level = 40, Location = 071 }, // HA Jellicent @ Undella Bay EncounterSlot collision - new( W2) { Species = 628, Level = 25, Location = 017, Ability = OnlyHidden, Gender = 0 }, // HA Braviary @ Route 4 Mon Only - new(B2 ) { Species = 630, Level = 25, Location = 017, Ability = OnlyHidden, Gender = 1 }, // HA Mandibuzz @ Route 4 Thurs Only - new(B2W2) { Species = 637, Level = 35, Location = 035 }, // Volcarona @ Relic Castle - new(B2W2) { Species = 637, Level = 65, Location = 035 }, // Volcarona @ Relic Castle - new(B2W2) { Species = 558, Level = 42, Location = 141 }, // Crustle @ Seaside Cave - new(B2W2) { Species = 612, Level = 60, Location = 147, Shiny = Shiny.Always}, // Haxorus @ Nature Preserve - - // Stationary Legendary - new(B2W2) { Species = 377, Level = 65, Location = 150 }, // Regirock @ Rock Peak Chamber - new(B2W2) { Species = 378, Level = 65, Location = 151 }, // Regice @ Iceberg Chamber - new(B2W2) { Species = 379, Level = 65, Location = 152 }, // Registeel @ Iron Chamber - new( W2) { Species = 380, Level = 68, Location = 032 }, // Latias @ Dreamyard - new(B2 ) { Species = 381, Level = 68, Location = 032 }, // Latios @ Dreamyard - new(B2W2) { Species = 480, Level = 65, Location = 007 }, // Uxie @ Nacrene City - new(B2W2) { Species = 481, Level = 65, Location = 056 }, // Mesprit @ Celestial Tower - new(B2W2) { Species = 482, Level = 65, Location = 128 }, // Azelf @ Route 23 - new(B2W2) { Species = 485, Level = 68, Location = 132 }, // Heatran @ Reversal Mountain - new(B2W2) { Species = 486, Level = 68, Location = 038 }, // Regigigas @ Twist Mountain - new(B2W2) { Species = 488, Level = 68, Location = 068 }, // Cresselia @ Marvelous Bridge - new(B2W2) { Species = 638, Level = 45, Location = 026 }, // Cobalion @ Route 13 - new(B2W2) { Species = 638, Level = 65, Location = 026 }, // Cobalion @ Route 13 - new(B2W2) { Species = 639, Level = 45, Location = 127 }, // Terrakion @ Route 22 - new(B2W2) { Species = 639, Level = 65, Location = 127 }, // Terrakion @ Route 22 - new(B2W2) { Species = 640, Level = 45, Location = 024 }, // Virizion @ Route 11 - new(B2W2) { Species = 640, Level = 65, Location = 024 }, // Virizion @ Route 11 - new( W2) { Species = 643, Level = 70, Location = 039, Shiny = Shiny.Never }, // Reshiram @ Dragonspiral Tower - new(B2 ) { Species = 644, Level = 70, Location = 039, Shiny = Shiny.Never }, // Zekrom @ Dragonspiral Tower - new(B2W2) { Species = 646, Level = 70, Location = 061, Form = 0 }, // Kyurem @ Giant Chasm - }; - - private static readonly EncounterStatic5N[] Encounter_B2W2_N = - { - // N's Pokemon - new(0xFF01007F) { Species = 509, Level = 07, Location = 015, Ability = OnlySecond, Nature = Nature.Timid }, // Purloin @ Route 2 - new(0xFF01007F) { Species = 519, Level = 13, Location = 033, Ability = OnlySecond, Nature = Nature.Sassy }, // Pidove @ Pinwheel Forest - new(0xFF00003F) { Species = 532, Level = 13, Location = 033, Ability = OnlyFirst, Nature = Nature.Rash }, // Timburr @ Pinwheel Forest - new(0xFF01007F) { Species = 535, Level = 13, Location = 033, Ability = OnlySecond, Nature = Nature.Modest }, // Tympole @ Pinwheel Forest - new(0xFF00007F) { Species = 527, Level = 55, Location = 053, Ability = OnlyFirst, Nature = Nature.Timid }, // Woobat @ Wellspring Cave - new(0xFF01007F) { Species = 551, Level = 22, Location = 034, Ability = OnlySecond, Nature = Nature.Docile }, // Sandile @ Desert Resort - new(0xFF00007F) { Species = 554, Level = 22, Location = 034, Ability = OnlyFirst, Nature = Nature.Naive }, // Darumaka @ Desert Resort - new(0xFF00007F) { Species = 555, Level = 35, Location = 034, Ability = OnlyHidden, Nature = Nature.Calm }, // Darmanitan @ Desert Resort - new(0xFF00007F) { Species = 559, Level = 22, Location = 034, Ability = OnlyFirst, Nature = Nature.Lax }, // Scraggy @ Desert Resort - new(0xFF01007F) { Species = 561, Level = 22, Location = 034, Ability = OnlySecond, Nature = Nature.Gentle }, // Sigilyph @ Desert Resort - new(0xFF00007F) { Species = 525, Level = 28, Location = 037, Ability = OnlyFirst, Nature = Nature.Naive }, // Boldore @ Chargestone Cave - new(0xFF01007F) { Species = 595, Level = 28, Location = 037, Ability = OnlySecond, Nature = Nature.Docile }, // Joltik @ Chargestone Cave - new(0xFF00007F) { Species = 597, Level = 28, Location = 037, Ability = OnlyFirst, Nature = Nature.Bashful }, // Ferroseed @ Chargestone Cave - new(0xFF000000) { Species = 599, Level = 28, Location = 037, Ability = OnlyFirst, Nature = Nature.Rash }, // Klink @ Chargestone Cave - new(0xFF00001F) { Species = 570, Level = 25, Location = 010, Ability = OnlyFirst, Nature = Nature.Hasty, Gift = true }, // N's Zorua @ Driftveil City - }; - - private static readonly EncounterStatic5[] Encounter_B2W2 = ArrayUtil.ConcatAll(Encounter_B2W2_Regular, Encounter_B2W2_N, Encounter_DreamRadar); - - #endregion - #region Trade Tables - - internal static readonly EncounterTrade5PID[] TradeGift_BW = - { - new(B , 0x64000000) { Species = 548, Level = 15, Ability = OnlyFirst, TID = 39922, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {20,20,20,20,31,20}, Nature = Nature.Modest }, // Petilil - new( W, 0x6400007E) { Species = 546, Level = 15, Ability = OnlyFirst, TID = 39922, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {20,20,20,20,31,20}, Nature = Nature.Modest }, // Cottonee - new(B , 0x9400007F) { Species = 550, Level = 25, Ability = OnlyFirst, TID = 27646, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,31,20,20,20,20}, Nature = Nature.Adamant, Form = 0 }, // Basculin-Red - new( W, 0x9400007F) { Species = 550, Level = 25, Ability = OnlyFirst, TID = 27646, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,31,20,20,20,20}, Nature = Nature.Adamant, Form = 1 }, // Basculin-Blue - new(BW, 0xD400007F) { Species = 587, Level = 30, Ability = OnlyFirst, TID = 11195, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,20,31,20,20,20}, Nature = Nature.Lax }, // Emolga - new(BW, 0x2A000000) { Species = 479, Level = 60, Ability = OnlyFirst, TID = 54673, SID = 00000, OTGender = 1, Gender = 2, IVs = new[] {20,20,20,20,20,31}, Nature = Nature.Gentle }, // Rotom - new(BW, 0x6200001F) { Species = 446, Level = 60, Ability = OnlySecond, TID = 40217, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {31,20,20,20,20,20}, Nature = Nature.Serious }, // Munchlax - }; - - internal static readonly EncounterTrade5[] TradeGift_B2W2_Regular = - { - new(B2 ) { Species = 548, Level = 20, Ability = OnlySecond, TID = 65217, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {20,20,20,20,31,20}, Nature = Nature.Timid }, // Petilil - new( W2) { Species = 546, Level = 20, Ability = OnlyFirst, TID = 05720, SID = 00001, OTGender = 0, Gender = 0, IVs = new[] {20,20,20,20,31,20}, Nature = Nature.Modest }, // Cottonee - new(B2W2) { Species = 526, Level = 35, Ability = OnlyFirst, TID = 11195, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,31,20,20,20,20}, Nature = Nature.Adamant, IsNicknamed = false }, // Gigalith - new(B2W2) { Species = 465, Level = 45, Ability = OnlyFirst, TID = 27658, SID = 00001, OTGender = 0, Gender = 0, IVs = new[] {31,20,20,20,20,20}, Nature = Nature.Hardy }, // Tangrowth - new(B2W2) { Species = 479, Level = 60, Ability = OnlyFirst, TID = 54673, SID = 00000, OTGender = 1, Gender = 2, IVs = new[] {20,20,20,20,20,31}, Nature = Nature.Calm }, // Rotom - new(B2W2) { Species = 424, Level = 40, Ability = OnlySecond, TID = 17074, SID = 00001, OTGender = 1, Gender = 0, IVs = new[] {20,20,20,31,20,20}, Nature = Nature.Jolly }, // Ambipom - new(B2W2) { Species = 065, Level = 40, Ability = OnlyFirst, TID = 17074, SID = 00001, OTGender = 1, Gender = 0, IVs = new[] {20,20,20,31,20,20}, Nature = Nature.Timid }, // Alakazam - }; - - internal const int YancyTID = 10303; - internal const int CurtisTID = 54118; - private static readonly string[] TradeOT_B2W2_F = { string.Empty, "ルリ", "Yancy", "Brenda", "Lilì", "Sabine", string.Empty, "Belinda", "루리" }; - private static readonly string[] TradeOT_B2W2_M = { string.Empty, "テツ", "Curtis", "Julien", "Dadi", "Markus", string.Empty, "Julián", "철권" }; - - private static readonly EncounterTrade5[] TradeGift_B2W2_YancyCurtis = - { - // Player is Male - new(B2W2) { Species = 052, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Meowth - new(B2W2) { Species = 202, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Wobbuffet - new(B2W2) { Species = 280, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Ralts - new(B2W2) { Species = 410, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Shieldon - new(B2W2) { Species = 111, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Rhyhorn - new(B2W2) { Species = 422, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F, Form = 0 }, // Shellos-West - new(B2W2) { Species = 303, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Mawile - new(B2W2) { Species = 442, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Spiritomb - new(B2W2) { Species = 143, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Snorlax - new(B2W2) { Species = 216, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Teddiursa - new(B2W2) { Species = 327, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Spinda - new(B2W2) { Species = 175, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Togepi - - // Player is Female - new(B2W2) { Species = 056, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Mankey - new(B2W2) { Species = 202, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Wobbuffet - new(B2W2) { Species = 280, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Ralts - new(B2W2) { Species = 408, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Cranidos - new(B2W2) { Species = 111, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Rhyhorn - new(B2W2) { Species = 422, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M, Form = 1 }, // Shellos-East - new(B2W2) { Species = 302, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Sableye - new(B2W2) { Species = 442, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Spiritomb - new(B2W2) { Species = 143, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Snorlax - new(B2W2) { Species = 231, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Phanpy - new(B2W2) { Species = 327, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Spinda - new(B2W2) { Species = 175, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Togepi - }; - - private const string tradeBW = "tradebw"; - private const string tradeB2W2 = "tradeb2w2"; - private static readonly string[][] TradeBW = Util.GetLanguageStrings8(tradeBW); - private static readonly string[][] TradeB2W2 = Util.GetLanguageStrings8(tradeB2W2); - - internal static readonly EncounterTrade5[] TradeGift_B2W2 = ArrayUtil.ConcatAll(TradeGift_B2W2_Regular, TradeGift_B2W2_YancyCurtis); - - #endregion - - internal static readonly EncounterStatic5[] StaticB = ArrayUtil.ConcatAll(GetEncounters(Encounter_BW, B), DreamWorld_Common, DreamWorld_BW); - internal static readonly EncounterStatic5[] StaticW = ArrayUtil.ConcatAll(GetEncounters(Encounter_BW, W), DreamWorld_Common, DreamWorld_BW); - internal static readonly EncounterStatic5[] StaticB2 = ArrayUtil.ConcatAll(GetEncounters(Encounter_B2W2, B2), DreamWorld_Common, DreamWorld_B2W2); - internal static readonly EncounterStatic5[] StaticW2 = ArrayUtil.ConcatAll(GetEncounters(Encounter_B2W2, W2), DreamWorld_Common, DreamWorld_B2W2); + MarkEncounterTradeStrings(TradeGift_BW, TradeBW); + MarkEncounterTradeStrings(TradeGift_B2W2_Regular, TradeB2W2); } + + #region Dream Radar Tables + + private static readonly EncounterStatic5DR[] Encounter_DreamRadar = + { + new(079, 0), // Slowpoke + new(120, 0), // Staryu + new(137, 0), // Porygon + new(163, 0), // Hoothoot + new(174, 0), // Igglybuff + new(175, 0), // Togepi + new(213, 0), // Shuckle + new(238, 0), // Smoochum + new(249, 0), // Lugia (SoulSilver cart) + new(250, 0), // Ho-Oh (HeartGold cart) + new(280, 0), // Ralts + new(333, 0), // Swablu + new(374, 0), // Beldum + new(425, 0), // Drifloon + new(436, 0), // Bronzor + new(442, 0), // Spiritomb + new(447, 0), // Riolu + new(479, 0, Any12), // Rotom (no HA) + new(483, 0), // Dialga (Diamond cart) + new(484, 0), // Palkia (Pearl cart) + new(487, 0), // Giratina (Platinum cart) + new(517, 0), // Munna + new(561, 0), // Sigilyph + new(641, 1), // Therian Tornadus + new(642, 1), // Therian Thundurus + new(645, 1), // Therian Landorus + }; + + #endregion + #region DreamWorld Encounter + + public static readonly EncounterStatic5[] DreamWorld_Common = DreamWorldEntry.GetArray(Gen5, new DreamWorldEntry[] + { + // Pleasant Forest + new(019, 10, 098, 382, 231), // Rattata + new(043, 10, 230, 298, 202), // Oddish + new(069, 10, 022, 235, 402), // Bellsprout + new(077, 10, 033, 037, 257), // Ponyta + new(083, 10, 210, 355, 348), // Farfetch'd + new(084, 10, 045, 175, 355), // Doduo + new(102, 10, 140, 235, 202), // Exeggcute + new(108, 10, 122, 214, 431), // Lickitung + new(114, 10, 079, 073, 402), // Tangela + new(115, 10, 252, 068, 409), // Kangaskhan + new(161, 10, 010, 203, 343), // Sentret + new(179, 10, 084, 115, 351), // Mareep + new(191, 10, 072, 230, 414), // Sunkern + new(234, 10, 033, 050, 285), // Stantler + new(261, 10, 336, 305, 399), // Poochyena + new(283, 10, 145, 056, 202), // Surskit + new(399, 10, 033, 401, 290), // Bidoof + new(403, 10, 268, 393, 400), // Shinx + new(431, 10, 252, 372, 290), // Glameow + new(054, 10, 346, 227, 362), // Psyduck + new(058, 10, 044, 034, 203), // Growlithe + new(123, 10, 098, 226, 366), // Scyther + new(128, 10, 099, 231, 431), // Tauros + new(183, 10, 111, 453, 008), // Marill + new(185, 10, 175, 205, 272), // Sudowoodo + new(203, 10, 093, 243, 285), // Girafarig + new(241, 10, 111, 174, 231), // Miltank + new(263, 10, 033, 271, 387), // Zigzagoon + new(427, 10, 193, 252, 409), // Buneary + new(037, 10, 046, 257, 399), // Vulpix + new(060, 10, 095, 054, 214), // Poliwag + new(177, 10, 101, 297, 202), // Natu + new(239, 10, 084, 238, 393), // Elekid + new(300, 10, 193, 321, 445), // Skitty + + // Windswept Sky + new(016, 10, 016, 211, 290), // Pidgey + new(021, 10, 064, 185, 211), // Spearow + new(041, 10, 048, 095, 162), // Zubat + new(142, 10, 044, 372, 446), // Aerodactyl + new(165, 10, 004, 450, 009), // Ledyba + new(187, 10, 235, 227, 340), // Hoppip + new(193, 10, 098, 364, 202), // Yanma + new(198, 10, 064, 109, 355), // Murkrow + new(207, 10, 028, 364, 366), // Gligar + new(225, 10, 217, 420, 264), // Delibird + new(276, 10, 064, 203, 413), // Taillow + new(397, 14, 017, 297, 366), // Staravia + new(227, 10, 064, 065, 355), // Skarmory + new(357, 10, 016, 073, 318), // Tropius + + // Sparkling Sea + new(086, 10, 029, 333, 214), // Seel + new(090, 10, 110, 112, 196), // Shellder + new(116, 10, 145, 190, 362), // Horsea + new(118, 10, 064, 060, 352), // Goldeen + new(129, 10, 150, 175, 340), // Magikarp + new(138, 10, 044, 330, 196), // Omanyte + new(140, 10, 071, 175, 446), // Kabuto + new(170, 10, 086, 133, 351), // Chinchou + new(194, 10, 055, 034, 401), // Wooper + new(211, 10, 040, 453, 290), // Qwilfish + new(223, 10, 199, 350, 362), // Remoraid + new(226, 10, 048, 243, 314), // Mantine + new(320, 10, 055, 214, 340), // Wailmer + new(339, 10, 189, 214, 209), // Barboach + new(366, 10, 250, 445, 392), // Clamperl + new(369, 10, 055, 214, 414), // Relicanth + new(370, 10, 204, 300, 196), // Luvdisc + new(418, 10, 346, 163, 352), // Buizel + new(456, 10, 213, 186, 352), // Finneon + new(072, 10, 048, 367, 202), // Tentacool + new(318, 10, 044, 037, 399), // Carvanha + new(341, 10, 106, 232, 283), // Corphish + new(345, 10, 051, 243, 202), // Lileep + new(347, 10, 010, 446, 440), // Anorith + new(349, 10, 150, 445, 243), // Feebas + new(131, 10, 109, 032, 196), // Lapras + new(147, 10, 086, 352, 225), // Dratini + + // Spooky Manor + new(092, 10, 095, 050, 482), // Gastly + new(096, 10, 095, 427, 409), // Drowzee + new(122, 10, 112, 298, 285), // Mr. Mime + new(167, 10, 040, 527, 450), // Spinarak + new(200, 10, 149, 194, 517), // Misdreavus + new(228, 10, 336, 364, 399), // Houndour + new(325, 10, 149, 285, 278), // Spoink + new(353, 10, 101, 194, 220), // Shuppet + new(355, 10, 050, 220, 271), // Duskull + new(358, 10, 035, 095, 304), // Chimecho + new(434, 10, 103, 492, 389), // Stunky + new(209, 10, 204, 370, 038), // Snubbull + new(235, 10, 166, 445, 214), // Smeargle + new(313, 10, 148, 271, 366), // Volbeat + new(314, 10, 204, 313, 366), // Illumise + new(063, 10, 100, 285, 356), // Abra + + // Rugged Mountain + new(066, 10, 067, 418, 270), // Machop + new(081, 10, 319, 278, 356), // Magnemite + new(109, 10, 123, 399, 482), // Koffing + new(218, 10, 052, 517, 257), // Slugma + new(246, 10, 044, 399, 446), // Larvitar + new(324, 10, 052, 090, 446), // Torkoal + new(328, 10, 044, 324, 202), // Trapinch + new(331, 10, 071, 298, 009), // Cacnea + new(412, 10, 182, 450, 173), // Burmy + new(449, 10, 044, 254, 276), // Hippopotas + new(240, 10, 052, 009, 257), // Magby + new(322, 10, 052, 034, 257), // Numel + new(359, 10, 364, 224, 276), // Absol + new(453, 10, 040, 409, 441), // Croagunk + new(236, 10, 252, 364, 183), // Tyrogue + new(371, 10, 044, 349, 200), // Bagon + + // Icy Cave + new(027, 10, 028, 068, 162), // Sandshrew + new(074, 10, 111, 446, 431), // Geodude + new(095, 10, 020, 446, 431), // Onix + new(100, 10, 268, 324, 363), // Voltorb + new(104, 10, 125, 195, 067), // Cubone + new(293, 10, 253, 283, 428), // Whismur + new(304, 10, 106, 283, 457), // Aron + new(337, 10, 093, 414, 236), // Lunatone + new(338, 10, 093, 428, 234), // Solrock + new(343, 10, 229, 356, 428), // Baltoy + new(459, 10, 075, 419, 202), // Snover + new(050, 10, 028, 251, 446), // Diglett + new(215, 10, 269, 008, 067), // Sneasel + new(361, 10, 181, 311, 352), // Snorunt + new(220, 10, 316, 246, 333), // Swinub + new(443, 10, 082, 200, 203), // Gible + + // Dream Park + new(046, 10, 078, 440, 235), // Paras + new(204, 10, 120, 390, 356), // Pineco + new(265, 10, 040, 450, 173), // Wurmple + new(273, 10, 074, 331, 492), // Seedot + new(287, 10, 281, 400, 389), // Slakoth + new(290, 10, 141, 203, 400), // Nincada + new(311, 10, 086, 435, 324), // Plusle + new(312, 10, 086, 435, 324), // Minun + new(316, 10, 139, 151, 202), // Gulpin + new(352, 10, 185, 285, 513), // Kecleon + new(401, 10, 522, 283, 253), // Kricketot + new(420, 10, 073, 505, 331), // Cherubi + new(455, 10, 044, 476, 380), // Carnivine + new(023, 10, 040, 251, 399), // Ekans + new(175, 10, 118, 381, 253), // Togepi + new(190, 10, 010, 252, 007), // Aipom + new(285, 10, 078, 331, 264), // Shroomish + new(315, 10, 074, 079, 129), // Roselia + new(113, 10, 045, 068, 270), // Chansey + new(127, 10, 011, 370, 382), // Pinsir + new(133, 10, 028, 204, 129), // Eevee + new(143, 10, 133, 007, 278), // Snorlax + new(214, 10, 030, 175, 264), // Heracross + + // Pokémon Café Forest + new(061, 25, 240, 114, 352), // Poliwhirl + new(133, 10, 270, 204, 129), // Eevee + new(235, 10, 166, 445, 214), // Smeargle + new(412, 10, 182, 450, 173), // Burmy + + // PGL + new(212, 10, 211, Gender: 0), // Scizor + new(445, 48, Gender: 0), // Garchomp + new(149, 55, 245, Gender: 0), // Dragonite + new(248, 55, 069, Gender: 0), // Tyranitar + new(376, 45, 038, Gender: 2), // Metagross + }); + + public static readonly EncounterStatic5[] DreamWorld_BW = DreamWorldEntry.GetArray(BW, new DreamWorldEntry[] + { + // Pleasant Forest + new(029, 10, 010, 389, 162), // Nidoran♀ + new(032, 10, 064, 068, 162), // Nidoran♂ + new(174, 10, 047, 313, 270), // Igglybuff + new(187, 10, 235, 270, 331), // Hoppip + new(270, 10, 071, 073, 352), // Lotad + new(276, 10, 064, 119, 366), // Taillow + new(309, 10, 086, 423, 324), // Electrike + new(351, 10, 052, 466, 352), // Castform + new(417, 10, 098, 343, 351), // Pachirisu + + // Windswept Sky + new(012, 10, 093, 355, 314), // Butterfree + new(163, 10, 193, 101, 278), // Hoothoot + new(278, 10, 055, 239, 351), // Wingull + new(333, 10, 064, 297, 355), // Swablu + new(425, 10, 107, 095, 285), // Drifloon + new(441, 10, 119, 417, 272), // Chatot + + // Sparkling Sea + new(079, 10, 281, 335, 362), // Slowpoke + new(098, 10, 011, 133, 290), // Krabby + new(119, 33, 352, 214, 203), // Seaking + new(120, 10, 055, 278, 196), // Staryu + new(222, 10, 145, 109, 446), // Corsola + new(422, 10, 189, 281, 290, Form: 0), // Shellos-West + new(422, 10, 189, 281, 290, Form: 1), // Shellos-East + + // Spooky Manor + new(202, 15, 243, 204, 227), // Wobbuffet + new(238, 10, 186, 445, 285), // Smoochum + new(303, 10, 313, 424, 008), // Mawile + new(307, 10, 096, 409, 203), // Meditite + new(436, 10, 095, 285, 356), // Bronzor + new(052, 10, 010, 095, 290), // Meowth + new(479, 10, 086, 351, 324), // Rotom + new(280, 10, 093, 194, 270), // Ralts + new(302, 10, 193, 389, 180), // Sableye + new(442, 10, 180, 220, 196), // Spiritomb + + // Rugged Mountain + new(056, 10, 067, 179, 009), // Mankey + new(111, 10, 030, 068, 038), // Rhyhorn + new(231, 10, 175, 484, 402), // Phanpy + new(451, 10, 044, 097, 401), // Skorupi + new(216, 10, 313, 242, 264), // Teddiursa + new(296, 10, 292, 270, 008), // Makuhita + new(327, 10, 383, 252, 276), // Spinda + new(374, 10, 036, 428, 442), // Beldum + new(447, 10, 203, 418, 264), // Riolu + + // Icy Cave + new(173, 10, 227, 312, 214), // Cleffa + new(213, 10, 227, 270, 504), // Shuckle + new(299, 10, 033, 446, 246), // Nosepass + new(363, 10, 181, 090, 401), // Spheal + new(408, 10, 029, 442, 007), // Cranidos + new(206, 10, 111, 277, 446), // Dunsparce + new(410, 10, 182, 068, 090), // Shieldon + + // Dream Park + new(048, 10, 050, 226, 285), // Venonat + new(088, 10, 139, 114, 425), // Grimer + new(415, 10, 016, 366, 314), // Combee + new(015, 10, 031, 314, 210), // Beedrill + new(335, 10, 098, 458, 067), // Zangoose + new(336, 10, 044, 034, 401), // Seviper + + // PGL + new(134, 10, Gender: 0), // Vaporeon + new(135, 10, Gender: 0), // Jolteon + new(136, 10, Gender: 0), // Flareon + new(196, 10, Gender: 0), // Espeon + new(197, 10, Gender: 0), // Umbreon + new(470, 10, Gender: 0), // Leafeon + new(471, 10, Gender: 0), // Glaceon + new(001, 10, Gender: 0), // Bulbasaur + new(004, 10, Gender: 0), // Charmander + new(007, 10, Gender: 0), // Squirtle + new(453, 10, Gender: 0), // Croagunk + new(387, 10, Gender: 0), // Turtwig + new(390, 10, Gender: 0), // Chimchar + new(393, 10, Gender: 0), // Piplup + new(493, 100), // Arceus + new(252, 10, Gender: 0), // Treecko + new(255, 10, Gender: 0), // Torchic + new(258, 10, Gender: 0), // Mudkip + new(468, 10, 217, Gender: 0), // Togekiss + new(473, 34, Gender: 0), // Mamoswine + new(137, 10), // Porygon + new(384, 50), // Rayquaza + new(354, 37, 538, Gender: 1), // Banette + new(453, 10, 398, Gender: 0), // Croagunk + new(334, 35, 206, Gender: 0), // Altaria + new(242, 10), // Blissey + new(448, 10, 418, Gender: 0), // Lucario + new(189, 27, 206, Gender: 0), // Jumpluff + }); + + public static readonly EncounterStatic5[] DreamWorld_B2W2 = DreamWorldEntry.GetArray(B2W2, new DreamWorldEntry[] + { + // Pleasant Forest + new(535, 10, 496, 414, 352), // Tympole + new(546, 10, 073, 227, 388), // Cottonee + new(548, 10, 079, 204, 230), // Petilil + new(588, 10, 203, 224, 450), // Karrablast + new(616, 10, 051, 226, 227), // Shelmet + new(545, 30, 342, 390, 276), // Scolipede + + // Windswept Sky + new(519, 10, 016, 095, 234), // Pidove + new(561, 10, 095, 500, 257), // Sigilyph + new(580, 10, 432, 362, 382), // Ducklett + new(587, 10, 098, 403, 204), // Emolga + + // Sparkling Sea + new(550, 10, 029, 097, 428, Form: 0), // Basculin-Red + new(550, 10, 029, 097, 428, Form: 1), // Basculin-Blue + new(594, 10, 392, 243, 220), // Alomomola + new(618, 10, 189, 174, 281), // Stunfisk + new(564, 10, 205, 175, 334), // Tirtouga + + // Spooky Manor + new(605, 10, 377, 112, 417), // Elgyem + new(624, 10, 210, 427, 389), // Pawniard + new(596, 36, 486, 050, 228), // Galvantula + new(578, 32, 105, 286, 271), // Duosion + new(622, 10, 205, 007, 009), // Golett + + // Rugged Mountain + new(631, 10, 510, 257, 202), // Heatmor + new(632, 10, 210, 203, 422), // Durant + new(556, 10, 042, 073, 191), // Maractus + new(558, 34, 157, 068, 400), // Crustle + new(553, 40, 242, 068, 212), // Krookodile + + // Icy Cave + new(529, 10, 229, 319, 431), // Drilbur + new(621, 10, 044, 424, 389), // Druddigon + new(525, 25, 479, 174, 484), // Boldore + new(583, 35, 429, 420, 286), // Vanillish + new(600, 38, 451, 356, 393), // Klang + new(610, 10, 082, 068, 400), // Axew + + // Dream Park + new(531, 10, 270, 227, 281), // Audino + new(538, 10, 020, 008, 276), // Throh + new(539, 10, 249, 009, 530), // Sawk + new(559, 10, 067, 252, 409), // Scraggy + new(533, 25, 067, 183, 409), // Gurdurr + + // PGL + new(575, 32, 243, Gender: 0), // Gothorita + new(025, 10, 029, Gender: 0), // Pikachu + new(511, 10, 437, Gender: 0), // Pansage + new(513, 10, 257, Gender: 0), // Pansear + new(515, 10, 056, Gender: 0), // Panpour + new(387, 10, 254, Gender: 0), // Turtwig + new(390, 10, 252, Gender: 0), // Chimchar + new(393, 10, 297, Gender: 0), // Piplup + new(575, 32, 286, Gender: 0), // Gothorita + }); + + #endregion + #region Static Encounter/Gift Tables + private static readonly EncounterStatic5[] Encounter_BW = + { + // Starters @ Nuvema Town + new(BW) { Gift = true, Species = 495, Level = 05, Location = 004 }, // Snivy + new(BW) { Gift = true, Species = 498, Level = 05, Location = 004 }, // Tepig + new(BW) { Gift = true, Species = 501, Level = 05, Location = 004 }, // Oshawott + + // Fossils @ Nacrene City + new(BW) { Gift = true, Species = 138, Level = 25, Location = 007 }, // Omanyte + new(BW) { Gift = true, Species = 140, Level = 25, Location = 007 }, // Kabuto + new(BW) { Gift = true, Species = 142, Level = 25, Location = 007 }, // Aerodactyl + new(BW) { Gift = true, Species = 345, Level = 25, Location = 007 }, // Lileep + new(BW) { Gift = true, Species = 347, Level = 25, Location = 007 }, // Anorith + new(BW) { Gift = true, Species = 408, Level = 25, Location = 007 }, // Cranidos + new(BW) { Gift = true, Species = 410, Level = 25, Location = 007 }, // Shieldon + new(BW) { Gift = true, Species = 564, Level = 25, Location = 007 }, // Tirtouga + new(BW) { Gift = true, Species = 566, Level = 25, Location = 007 }, // Archen + + // Gift + new(BW) { Gift = true, Species = 511, Level = 10, Location = 032 }, // Pansage @ Dreamyard + new(BW) { Gift = true, Species = 513, Level = 10, Location = 032 }, // Pansear + new(BW) { Gift = true, Species = 515, Level = 10, Location = 032 }, // Panpour + new(BW) { Gift = true, Species = 129, Level = 05, Location = 068 }, // Magikarp @ Marvelous Bridge + new(BW) { Gift = true, Species = 636, Level = 01, EggLocation = 60003 }, // Larvesta Egg from Treasure Hunter + + // Stationary + new(BW) { Species = 518, Level = 50, Location = 032, Ability = OnlyHidden }, // Musharna @ Dreamyard Friday Only + new(BW) { Species = 590, Level = 20, Location = 019 }, // Foongus @ Route 6 + new(BW) { Species = 590, Level = 30, Location = 023 }, // Foongus @ Route 10 + new(BW) { Species = 591, Level = 40, Location = 023 }, // Amoonguss @ Route 10 + new(BW) { Species = 555, Level = 35, Location = 034, Ability = OnlyHidden }, // HA Darmanitan @ Desert Resort + new(BW) { Species = 637, Level = 70, Location = 035 }, // Volcarona @ Relic Castle + + // Stationary Legendary + new(BW) { Species = 638, Level = 42, Location = 074 }, // Cobalion @ Guidance Chamber + new(BW) { Species = 639, Level = 42, Location = 073 }, // Terrakion @ Trial Chamber + new(BW) { Species = 640, Level = 42, Location = 055 }, // Virizion @ Rumination Field + new(B ) { Species = 643, Level = 50, Location = 045, Shiny = Shiny.Never }, // Reshiram @ N's Castle + new(B ) { Species = 643, Level = 50, Location = 039, Shiny = Shiny.Never }, // Reshiram @ Dragonspiral Tower + new( W) { Species = 644, Level = 50, Location = 045, Shiny = Shiny.Never }, // Zekrom @ N's Castle + new( W) { Species = 644, Level = 50, Location = 039, Shiny = Shiny.Never }, // Zekrom @ Dragonspiral Tower + new(BW) { Species = 645, Level = 70, Location = 070 }, // Landorus @ Abundant Shrine + new(BW) { Species = 646, Level = 75, Location = 061 }, // Kyurem @ Giant Chasm + + // Event + new(BW) { Species = 494, Level = 15, Location = 062, Shiny = Shiny.Never}, // Victini @ Liberty Garden + new(BW) { Species = 570, Level = 10, Location = 008, Shiny = Shiny.Never, Gender = 0 }, // Zorua @ Castelia City + new(BW) { Species = 571, Level = 25, Location = 072, Shiny = Shiny.Never, Gender = 1 }, // Zoroark @ Lostlorn Forest + + // Roamer + new(B ) { Roaming = true, Species = 641, Level = 40, Location = 25 }, // Tornadus + new( W) { Roaming = true, Species = 642, Level = 40, Location = 25 }, // Thundurus + }; + + private static readonly EncounterStatic5[] Encounter_B2W2_Regular = + { + // Starters @ Aspertia City + new(B2W2) { Gift = true, Species = 495, Level = 05, Location = 117 }, // Snivy + new(B2W2) { Gift = true, Species = 498, Level = 05, Location = 117 }, // Tepig + new(B2W2) { Gift = true, Species = 501, Level = 05, Location = 117 }, // Oshawott + + // Fossils @ Nacrene City + new(B2W2) { Gift = true, Species = 138, Level = 25, Location = 007 }, // Omanyte + new(B2W2) { Gift = true, Species = 140, Level = 25, Location = 007 }, // Kabuto + new(B2W2) { Gift = true, Species = 142, Level = 25, Location = 007 }, // Aerodactyl + new(B2W2) { Gift = true, Species = 345, Level = 25, Location = 007 }, // Lileep + new(B2W2) { Gift = true, Species = 347, Level = 25, Location = 007 }, // Anorith + new(B2W2) { Gift = true, Species = 408, Level = 25, Location = 007 }, // Cranidos + new(B2W2) { Gift = true, Species = 410, Level = 25, Location = 007 }, // Shieldon + new(B2W2) { Gift = true, Species = 564, Level = 25, Location = 007 }, // Tirtouga + new(B2W2) { Gift = true, Species = 566, Level = 25, Location = 007 }, // Archen + + // Gift + new(B2W2) { Gift = true, Species = 133, Level = 10, Location = 008, Ability = OnlyHidden }, // HA Eevee @ Castelia City + new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 0 }, // HA Deerling @ Route 6 + new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 1 }, // HA Deerling @ Route 6 + new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 2 }, // HA Deerling @ Route 6 + new(B2W2) { Gift = true, Species = 585, Level = 30, Location = 019, Ability = OnlyHidden, Form = 3 }, // HA Deerling @ Route 6 + new(B2 ) { Gift = true, Species = 443, Level = 01, Location = 122, Shiny = Shiny.Always, Gender = 0 }, // Shiny Gible @ Floccesy Town + new( W2) { Gift = true, Species = 147, Level = 01, Location = 122, Shiny = Shiny.Always, Gender = 0 }, // Shiny Dratini @ Floccesy Town + new(B2W2) { Gift = true, Species = 129, Level = 05, Location = 068 } , // Magikarp @ Marvelous Bridge + new(B2W2) { Gift = true, Species = 440, Level = 01, EggLocation = 60003 }, // Happiny Egg from PKMN Breeder + + // Stationary + new(B2W2) { Species = 590, Level = 29, Location = 019 }, // Foongus @ Route 6 + new(B2W2) { Species = 591, Level = 43, Location = 024 }, // Amoonguss @ Route 11 + new(B2W2) { Species = 591, Level = 47, Location = 127 }, // Amoonguss @ Route 22 + new(B2W2) { Species = 591, Level = 56, Location = 128 }, // Amoonguss @ Route 23 + new(B2 ) { Species = 593, Level = 40, Location = 071, Ability = OnlyHidden, Gender = 0 }, // HA Jellicent @ Undella Bay Mon Only + new( W2) { Species = 593, Level = 40, Location = 071, Ability = OnlyHidden, Gender = 1 }, // HA Jellicent @ Undella Bay Thurs Only + new(B2W2) { Species = 593, Level = 40, Location = 071 }, // HA Jellicent @ Undella Bay EncounterSlot collision + new( W2) { Species = 628, Level = 25, Location = 017, Ability = OnlyHidden, Gender = 0 }, // HA Braviary @ Route 4 Mon Only + new(B2 ) { Species = 630, Level = 25, Location = 017, Ability = OnlyHidden, Gender = 1 }, // HA Mandibuzz @ Route 4 Thurs Only + new(B2W2) { Species = 637, Level = 35, Location = 035 }, // Volcarona @ Relic Castle + new(B2W2) { Species = 637, Level = 65, Location = 035 }, // Volcarona @ Relic Castle + new(B2W2) { Species = 558, Level = 42, Location = 141 }, // Crustle @ Seaside Cave + new(B2W2) { Species = 612, Level = 60, Location = 147, Shiny = Shiny.Always}, // Haxorus @ Nature Preserve + + // Stationary Legendary + new(B2W2) { Species = 377, Level = 65, Location = 150 }, // Regirock @ Rock Peak Chamber + new(B2W2) { Species = 378, Level = 65, Location = 151 }, // Regice @ Iceberg Chamber + new(B2W2) { Species = 379, Level = 65, Location = 152 }, // Registeel @ Iron Chamber + new( W2) { Species = 380, Level = 68, Location = 032 }, // Latias @ Dreamyard + new(B2 ) { Species = 381, Level = 68, Location = 032 }, // Latios @ Dreamyard + new(B2W2) { Species = 480, Level = 65, Location = 007 }, // Uxie @ Nacrene City + new(B2W2) { Species = 481, Level = 65, Location = 056 }, // Mesprit @ Celestial Tower + new(B2W2) { Species = 482, Level = 65, Location = 128 }, // Azelf @ Route 23 + new(B2W2) { Species = 485, Level = 68, Location = 132 }, // Heatran @ Reversal Mountain + new(B2W2) { Species = 486, Level = 68, Location = 038 }, // Regigigas @ Twist Mountain + new(B2W2) { Species = 488, Level = 68, Location = 068 }, // Cresselia @ Marvelous Bridge + new(B2W2) { Species = 638, Level = 45, Location = 026 }, // Cobalion @ Route 13 + new(B2W2) { Species = 638, Level = 65, Location = 026 }, // Cobalion @ Route 13 + new(B2W2) { Species = 639, Level = 45, Location = 127 }, // Terrakion @ Route 22 + new(B2W2) { Species = 639, Level = 65, Location = 127 }, // Terrakion @ Route 22 + new(B2W2) { Species = 640, Level = 45, Location = 024 }, // Virizion @ Route 11 + new(B2W2) { Species = 640, Level = 65, Location = 024 }, // Virizion @ Route 11 + new( W2) { Species = 643, Level = 70, Location = 039, Shiny = Shiny.Never }, // Reshiram @ Dragonspiral Tower + new(B2 ) { Species = 644, Level = 70, Location = 039, Shiny = Shiny.Never }, // Zekrom @ Dragonspiral Tower + new(B2W2) { Species = 646, Level = 70, Location = 061, Form = 0 }, // Kyurem @ Giant Chasm + }; + + private static readonly EncounterStatic5N[] Encounter_B2W2_N = + { + // N's Pokemon + new(0xFF01007F) { Species = 509, Level = 07, Location = 015, Ability = OnlySecond, Nature = Nature.Timid }, // Purloin @ Route 2 + new(0xFF01007F) { Species = 519, Level = 13, Location = 033, Ability = OnlySecond, Nature = Nature.Sassy }, // Pidove @ Pinwheel Forest + new(0xFF00003F) { Species = 532, Level = 13, Location = 033, Ability = OnlyFirst, Nature = Nature.Rash }, // Timburr @ Pinwheel Forest + new(0xFF01007F) { Species = 535, Level = 13, Location = 033, Ability = OnlySecond, Nature = Nature.Modest }, // Tympole @ Pinwheel Forest + new(0xFF00007F) { Species = 527, Level = 55, Location = 053, Ability = OnlyFirst, Nature = Nature.Timid }, // Woobat @ Wellspring Cave + new(0xFF01007F) { Species = 551, Level = 22, Location = 034, Ability = OnlySecond, Nature = Nature.Docile }, // Sandile @ Desert Resort + new(0xFF00007F) { Species = 554, Level = 22, Location = 034, Ability = OnlyFirst, Nature = Nature.Naive }, // Darumaka @ Desert Resort + new(0xFF00007F) { Species = 555, Level = 35, Location = 034, Ability = OnlyHidden, Nature = Nature.Calm }, // Darmanitan @ Desert Resort + new(0xFF00007F) { Species = 559, Level = 22, Location = 034, Ability = OnlyFirst, Nature = Nature.Lax }, // Scraggy @ Desert Resort + new(0xFF01007F) { Species = 561, Level = 22, Location = 034, Ability = OnlySecond, Nature = Nature.Gentle }, // Sigilyph @ Desert Resort + new(0xFF00007F) { Species = 525, Level = 28, Location = 037, Ability = OnlyFirst, Nature = Nature.Naive }, // Boldore @ Chargestone Cave + new(0xFF01007F) { Species = 595, Level = 28, Location = 037, Ability = OnlySecond, Nature = Nature.Docile }, // Joltik @ Chargestone Cave + new(0xFF00007F) { Species = 597, Level = 28, Location = 037, Ability = OnlyFirst, Nature = Nature.Bashful }, // Ferroseed @ Chargestone Cave + new(0xFF000000) { Species = 599, Level = 28, Location = 037, Ability = OnlyFirst, Nature = Nature.Rash }, // Klink @ Chargestone Cave + new(0xFF00001F) { Species = 570, Level = 25, Location = 010, Ability = OnlyFirst, Nature = Nature.Hasty, Gift = true }, // N's Zorua @ Driftveil City + }; + + private static readonly EncounterStatic5[] Encounter_B2W2 = ArrayUtil.ConcatAll(Encounter_B2W2_Regular, Encounter_B2W2_N, Encounter_DreamRadar); + + #endregion + #region Trade Tables + + internal static readonly EncounterTrade5PID[] TradeGift_BW = + { + new(B , 0x64000000) { Species = 548, Level = 15, Ability = OnlyFirst, TID = 39922, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {20,20,20,20,31,20}, Nature = Nature.Modest }, // Petilil + new( W, 0x6400007E) { Species = 546, Level = 15, Ability = OnlyFirst, TID = 39922, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {20,20,20,20,31,20}, Nature = Nature.Modest }, // Cottonee + new(B , 0x9400007F) { Species = 550, Level = 25, Ability = OnlyFirst, TID = 27646, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,31,20,20,20,20}, Nature = Nature.Adamant, Form = 0 }, // Basculin-Red + new( W, 0x9400007F) { Species = 550, Level = 25, Ability = OnlyFirst, TID = 27646, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,31,20,20,20,20}, Nature = Nature.Adamant, Form = 1 }, // Basculin-Blue + new(BW, 0xD400007F) { Species = 587, Level = 30, Ability = OnlyFirst, TID = 11195, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,20,31,20,20,20}, Nature = Nature.Lax }, // Emolga + new(BW, 0x2A000000) { Species = 479, Level = 60, Ability = OnlyFirst, TID = 54673, SID = 00000, OTGender = 1, Gender = 2, IVs = new[] {20,20,20,20,20,31}, Nature = Nature.Gentle }, // Rotom + new(BW, 0x6200001F) { Species = 446, Level = 60, Ability = OnlySecond, TID = 40217, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {31,20,20,20,20,20}, Nature = Nature.Serious }, // Munchlax + }; + + internal static readonly EncounterTrade5[] TradeGift_B2W2_Regular = + { + new(B2 ) { Species = 548, Level = 20, Ability = OnlySecond, TID = 65217, SID = 00000, OTGender = 1, Gender = 1, IVs = new[] {20,20,20,20,31,20}, Nature = Nature.Timid }, // Petilil + new( W2) { Species = 546, Level = 20, Ability = OnlyFirst, TID = 05720, SID = 00001, OTGender = 0, Gender = 0, IVs = new[] {20,20,20,20,31,20}, Nature = Nature.Modest }, // Cottonee + new(B2W2) { Species = 526, Level = 35, Ability = OnlyFirst, TID = 11195, SID = 00000, OTGender = 0, Gender = 0, IVs = new[] {20,31,20,20,20,20}, Nature = Nature.Adamant, IsNicknamed = false }, // Gigalith + new(B2W2) { Species = 465, Level = 45, Ability = OnlyFirst, TID = 27658, SID = 00001, OTGender = 0, Gender = 0, IVs = new[] {31,20,20,20,20,20}, Nature = Nature.Hardy }, // Tangrowth + new(B2W2) { Species = 479, Level = 60, Ability = OnlyFirst, TID = 54673, SID = 00000, OTGender = 1, Gender = 2, IVs = new[] {20,20,20,20,20,31}, Nature = Nature.Calm }, // Rotom + new(B2W2) { Species = 424, Level = 40, Ability = OnlySecond, TID = 17074, SID = 00001, OTGender = 1, Gender = 0, IVs = new[] {20,20,20,31,20,20}, Nature = Nature.Jolly }, // Ambipom + new(B2W2) { Species = 065, Level = 40, Ability = OnlyFirst, TID = 17074, SID = 00001, OTGender = 1, Gender = 0, IVs = new[] {20,20,20,31,20,20}, Nature = Nature.Timid }, // Alakazam + }; + + internal const int YancyTID = 10303; + internal const int CurtisTID = 54118; + private static readonly string[] TradeOT_B2W2_F = { string.Empty, "ルリ", "Yancy", "Brenda", "Lilì", "Sabine", string.Empty, "Belinda", "루리" }; + private static readonly string[] TradeOT_B2W2_M = { string.Empty, "テツ", "Curtis", "Julien", "Dadi", "Markus", string.Empty, "Julián", "철권" }; + + private static readonly EncounterTrade5[] TradeGift_B2W2_YancyCurtis = + { + // Player is Male + new(B2W2) { Species = 052, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Meowth + new(B2W2) { Species = 202, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Wobbuffet + new(B2W2) { Species = 280, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Ralts + new(B2W2) { Species = 410, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Shieldon + new(B2W2) { Species = 111, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Rhyhorn + new(B2W2) { Species = 422, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F, Form = 0 }, // Shellos-West + new(B2W2) { Species = 303, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Mawile + new(B2W2) { Species = 442, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Spiritomb + new(B2W2) { Species = 143, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Snorlax + new(B2W2) { Species = 216, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Teddiursa + new(B2W2) { Species = 327, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Spinda + new(B2W2) { Species = 175, Level = 50, Ability = OnlyHidden, TID = 10303, SID = 00000, OTGender = 1, TrainerNames = TradeOT_B2W2_F }, // Togepi + + // Player is Female + new(B2W2) { Species = 056, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Mankey + new(B2W2) { Species = 202, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Wobbuffet + new(B2W2) { Species = 280, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Ralts + new(B2W2) { Species = 408, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Cranidos + new(B2W2) { Species = 111, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Rhyhorn + new(B2W2) { Species = 422, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M, Form = 1 }, // Shellos-East + new(B2W2) { Species = 302, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Sableye + new(B2W2) { Species = 442, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Spiritomb + new(B2W2) { Species = 143, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Snorlax + new(B2W2) { Species = 231, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Phanpy + new(B2W2) { Species = 327, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Spinda + new(B2W2) { Species = 175, Level = 50, Ability = OnlyHidden, TID = 54118, SID = 00000, OTGender = 0, TrainerNames = TradeOT_B2W2_M }, // Togepi + }; + + private const string tradeBW = "tradebw"; + private const string tradeB2W2 = "tradeb2w2"; + private static readonly string[][] TradeBW = Util.GetLanguageStrings8(tradeBW); + private static readonly string[][] TradeB2W2 = Util.GetLanguageStrings8(tradeB2W2); + + internal static readonly EncounterTrade5[] TradeGift_B2W2 = ArrayUtil.ConcatAll(TradeGift_B2W2_Regular, TradeGift_B2W2_YancyCurtis); + + #endregion + + internal static readonly EncounterStatic5[] StaticB = ArrayUtil.ConcatAll(GetEncounters(Encounter_BW, B), DreamWorld_Common, DreamWorld_BW); + internal static readonly EncounterStatic5[] StaticW = ArrayUtil.ConcatAll(GetEncounters(Encounter_BW, W), DreamWorld_Common, DreamWorld_BW); + internal static readonly EncounterStatic5[] StaticB2 = ArrayUtil.ConcatAll(GetEncounters(Encounter_B2W2, B2), DreamWorld_Common, DreamWorld_B2W2); + internal static readonly EncounterStatic5[] StaticW2 = ArrayUtil.ConcatAll(GetEncounters(Encounter_B2W2, W2), DreamWorld_Common, DreamWorld_B2W2); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters6.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters6.cs index a799a488d..3aeaa532f 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters6.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters6.cs @@ -2,251 +2,250 @@ using static PKHeX.Core.GameVersion; using static PKHeX.Core.AbilityPermission; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 6 Encounters +/// +internal static class Encounters6 { - /// - /// Generation 6 Encounters - /// - internal static class Encounters6 + private static readonly EncounterArea6XY FriendSafari = new(); + internal static readonly EncounterArea6XY[] SlotsX = EncounterArea6XY.GetAreas(Get("x", "xy"), X, FriendSafari); + internal static readonly EncounterArea6XY[] SlotsY = EncounterArea6XY.GetAreas(Get("y", "xy"), Y, FriendSafari); + internal static readonly EncounterArea6AO[] SlotsA = EncounterArea6AO.GetAreas(Get("as", "ao"), AS); + internal static readonly EncounterArea6AO[] SlotsO = EncounterArea6AO.GetAreas(Get("or", "ao"), OR); + + static Encounters6() { - private static readonly EncounterArea6XY FriendSafari = new(); - internal static readonly EncounterArea6XY[] SlotsX = EncounterArea6XY.GetAreas(Get("x", "xy"), X, FriendSafari); - internal static readonly EncounterArea6XY[] SlotsY = EncounterArea6XY.GetAreas(Get("y", "xy"), Y, FriendSafari); - internal static readonly EncounterArea6AO[] SlotsA = EncounterArea6AO.GetAreas(Get("as", "ao"), AS); - internal static readonly EncounterArea6AO[] SlotsO = EncounterArea6AO.GetAreas(Get("or", "ao"), OR); - - static Encounters6() - { - MarkEncounterTradeStrings(TradeGift_XY, TradeXY); - MarkEncounterTradeStrings(TradeGift_AO, TradeAO); - } - - private const string tradeXY = "tradexy"; - private const string tradeAO = "tradeao"; - private static readonly string[][] TradeXY = Util.GetLanguageStrings8(tradeXY); - private static readonly string[][] TradeAO = Util.GetLanguageStrings8(tradeAO); - - #region Static Encounter/Gift Tables - private static readonly EncounterStatic6[] Encounter_XY = - { - // Kalos Starters @ Aquacorde Town - new(XY) { Gift = true, Species = 650, Level = 5, Location = 10 }, // Chespin - new(XY) { Gift = true, Species = 653, Level = 5, Location = 10 }, // Fennekin - new(XY) { Gift = true, Species = 656, Level = 5, Location = 10 }, // Froakie - - // Kanto Starters @ Lumiose City - new(XY) { Gift = true, Species = 1, Level = 10, Location = 22 }, // Bulbasaur - new(XY) { Gift = true, Species = 4, Level = 10, Location = 22 }, // Charmander - new(XY) { Gift = true, Species = 7, Level = 10, Location = 22 }, // Squirtle - - // Fossils @ Ambrette Town - new(XY) { Gift = true, Species = 138, Level = 20, Location = 44 }, // Omanyte - new(XY) { Gift = true, Species = 140, Level = 20, Location = 44 }, // Kabuto - new(XY) { Gift = true, Species = 142, Level = 20, Location = 44 }, // Aerodactyl - new(XY) { Gift = true, Species = 345, Level = 20, Location = 44 }, // Lileep - new(XY) { Gift = true, Species = 347, Level = 20, Location = 44 }, // Anorith - new(XY) { Gift = true, Species = 408, Level = 20, Location = 44 }, // Cranidos - new(XY) { Gift = true, Species = 410, Level = 20, Location = 44 }, // Shieldon - new(XY) { Gift = true, Species = 564, Level = 20, Location = 44 }, // Tirtouga - new(XY) { Gift = true, Species = 566, Level = 20, Location = 44 }, // Archen - new(XY) { Gift = true, Species = 696, Level = 20, Location = 44 }, // Tyrunt - new(XY) { Gift = true, Species = 698, Level = 20, Location = 44 }, // Amaura - - // Gift - new(XY) { Gift = true, Species = 448, Level = 32, Location = 60, Ability = OnlyFirst, IVs = new[] {06,25,16,31,25,19}, Nature = Nature.Hasty, Gender = 0, Shiny = Shiny.Never }, // Lucario - new(XY) { Gift = true, Species = 131, Level = 30, Location = 62, Ability = OnlyFirst, IVs = new[] {31,20,20,20,20,20}, Nature = Nature.Docile }, // Lapras - - // Stationary - new(XY) { Species = 143, Level = 15, Location = 038, Shiny = Shiny.Never }, // Snorlax - - // Shaking Trash Cans @ Lost Hotel - new(XY) { Species = 568, Level = 35, Location = 142 }, // Trubbish - new(XY) { Species = 569, Level = 36, Location = 142 }, // Garbodor - new(XY) { Species = 569, Level = 37, Location = 142 }, // Garbodor - new(XY) { Species = 569, Level = 38, Location = 142 }, // Garbodor - new(XY) { Species = 479, Level = 38, Location = 142 }, // Rotom - - // Shaking Trash Cans @ Pokemon Village - new(XY) { Species = 569, Level = 46, Location = 98 }, // Garbodor - new(XY) { Species = 569, Level = 47, Location = 98 }, // Garbodor - new(XY) { Species = 569, Level = 48, Location = 98 }, // Garbodor - new(XY) { Species = 569, Level = 49, Location = 98 }, // Garbodor - new(XY) { Species = 569, Level = 50, Location = 98 }, // Garbodor - new(XY) { Species = 354, Level = 46, Location = 98 }, // Banette - new(XY) { Species = 354, Level = 47, Location = 98 }, // Banette - new(XY) { Species = 354, Level = 48, Location = 98 }, // Banette - new(XY) { Species = 354, Level = 49, Location = 98 }, // Banette - new(XY) { Species = 354, Level = 50, Location = 98 }, // Banette - - // Stationary Legendary - new(X ) { Species = 716, Level = 50, Location = 138, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Xerneas - new( Y) { Species = 717, Level = 50, Location = 138, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Yveltal - new(XY) { Species = 718, Level = 70, Location = 140, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(XY) { Species = 150, Level = 70, Location = 168, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Mewtwo - new(XY) { Species = 144, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Articuno - new(XY) { Species = 145, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zapdos - new(XY) { Species = 146, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Moltres - }; - - private static readonly EncounterStatic6 BaseCosplay = new(ORAS) - { - Location = 178, // Or 180, 186, 194 - Species = 025, - Form = 6, - Level = 20, - Gender = 1, - Ability = OnlyHidden, - FlawlessIVCount = 3, - CNT_Cool = 70, - CNT_Beauty = 70, - CNT_Cute = 70, - CNT_Tough = 70, - CNT_Smart = 70, - Gift = true, - Shiny = Shiny.Never, - }; - - private static readonly EncounterStatic6[] Encounter_AO_Regular = - { - // Starters @ Route 101 - new(ORAS) { Gift = true, Species = 252, Level = 5, Location = 204 }, // Treeko - new(ORAS) { Gift = true, Species = 255, Level = 5, Location = 204 }, // Torchic - new(ORAS) { Gift = true, Species = 258, Level = 5, Location = 204 }, // Mudkip - - new(ORAS) { Gift = true, Species = 152, Level = 5, Location = 204 }, // Chikorita - new(ORAS) { Gift = true, Species = 155, Level = 5, Location = 204 }, // Cyndaquil - new(ORAS) { Gift = true, Species = 158, Level = 5, Location = 204 }, // Totodile - - new(ORAS) { Gift = true, Species = 387, Level = 5, Location = 204 }, // Turtwig - new(ORAS) { Gift = true, Species = 390, Level = 5, Location = 204 }, // Chimchar - new(ORAS) { Gift = true, Species = 393, Level = 5, Location = 204 }, // Piplup - - new(ORAS) { Gift = true, Species = 495, Level = 5, Location = 204 }, // Snivy - new(ORAS) { Gift = true, Species = 498, Level = 5, Location = 204 }, // Tepig - new(ORAS) { Gift = true, Species = 501, Level = 5, Location = 204 }, // Oshawott - - // Fossils @ Rustboro City - new(ORAS) { Gift = true, Species = 138, Level = 20, Location = 190 }, // Omanyte - new(ORAS) { Gift = true, Species = 140, Level = 20, Location = 190 }, // Kabuto - new(ORAS) { Gift = true, Species = 142, Level = 20, Location = 190 }, // Aerodactyl - new(ORAS) { Gift = true, Species = 345, Level = 20, Location = 190 }, // Lileep - new(ORAS) { Gift = true, Species = 347, Level = 20, Location = 190 }, // Anorith - new(ORAS) { Gift = true, Species = 408, Level = 20, Location = 190 }, // Cranidos - new(ORAS) { Gift = true, Species = 410, Level = 20, Location = 190 }, // Shieldon - new(ORAS) { Gift = true, Species = 564, Level = 20, Location = 190 }, // Tirtouga - new(ORAS) { Gift = true, Species = 566, Level = 20, Location = 190 }, // Archen - new(ORAS) { Gift = true, Species = 696, Level = 20, Location = 190 }, // Tyrunt - new(ORAS) { Gift = true, Species = 698, Level = 20, Location = 190 }, // Amaura - - // Hot Springs Eggs - new(ORAS) { Gift = true, Species = 360, Level = 1, EggLocation = 60004, Ability = OnlyFirst, EggCycles = 70 }, // Wynaut - new(ORAS) { Gift = true, Species = 175, Level = 1, EggLocation = 60004, Ability = OnlyFirst, EggCycles = 70 }, // Togepi - - // Gift - new(ORAS) { Species = 374, Level = 01, Location = 196, Ability = OnlyFirst, Gift = true, IVs = new[] {-1,-1,31,-1,-1,31} }, // Beldum - new(ORAS) { Species = 351, Level = 30, Location = 240, Ability = OnlyFirst, Gift = true, IVs = new[] {-1,-1,-1,-1,31,-1}, CNT_Beauty = 100, Gender = 1, Nature = Nature.Lax }, // Castform - new(ORAS) { Species = 319, Level = 40, Location = 318, Ability = OnlyFirst, Gift = true, Gender = 1, Nature = Nature.Adamant }, // Sharpedo - new(ORAS) { Species = 323, Level = 40, Location = 318, Ability = OnlyFirst, Gift = true, Gender = 1, Nature = Nature.Quiet }, // Camerupt - new( AS) { Species = 380, Level = 30, Location = 320, Ability = OnlyFirst, Gift = true, FlawlessIVCount = 3 }, // Latias - new(OR ) { Species = 381, Level = 30, Location = 320, Ability = OnlyFirst, Gift = true, FlawlessIVCount = 3 }, // Latios - - // Stationary Legendary - new(ORAS) { Species = 377, Level = 40, Location = 278, FlawlessIVCount = 3 }, // Regirock - new(ORAS) { Species = 378, Level = 40, Location = 306, FlawlessIVCount = 3 }, // Regice - new(ORAS) { Species = 379, Level = 40, Location = 308, FlawlessIVCount = 3 }, // Registeel - new(ORAS) { Species = 486, Level = 50, Location = 306, FlawlessIVCount = 3 }, // Regigigas - new( AS) { Species = 382, Level = 45, Location = 296, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Kyogre - new(OR ) { Species = 383, Level = 45, Location = 296, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Groudon - new(ORAS) { Species = 384, Level = 70, Location = 316, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Rayquaza - new(ORAS) { Species = 386, Level = 80, Location = 316, Shiny = Shiny.Never, FlawlessIVCount = 3, Fateful = true }, // Deoxys - - // Hoopa Rings - new( AS) { Species = 249, Level = 50, Location = 304, FlawlessIVCount = 3 }, // Lugia - new(OR ) { Species = 250, Level = 50, Location = 304, FlawlessIVCount = 3 }, // Ho-Oh - new( AS) { Species = 483, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Dialga - new(OR ) { Species = 484, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Palkia - new( AS) { Species = 644, Level = 50, Location = 340, FlawlessIVCount = 3 }, // Zekrom - new(OR ) { Species = 643, Level = 50, Location = 340, FlawlessIVCount = 3 }, // Reshiram - new( AS) { Species = 642, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Thundurus - new(OR ) { Species = 641, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Tornadus - new(ORAS) { Species = 243, Level = 50, Location = 334, FlawlessIVCount = 3 }, // Raikou - new(ORAS) { Species = 244, Level = 50, Location = 334, FlawlessIVCount = 3 }, // Entei - new(ORAS) { Species = 245, Level = 50, Location = 334, FlawlessIVCount = 3 }, // Suicune - new(ORAS) { Species = 480, Level = 50, Location = 338, FlawlessIVCount = 3 }, // Uxie - new(ORAS) { Species = 481, Level = 50, Location = 338, FlawlessIVCount = 3 }, // Mesprit - new(ORAS) { Species = 482, Level = 50, Location = 338, FlawlessIVCount = 3 }, // Azelf - new(ORAS) { Species = 485, Level = 50, Location = 312, FlawlessIVCount = 3 }, // Heatran - new(ORAS) { Species = 487, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Giratina - new(ORAS) { Species = 488, Level = 50, Location = 344, FlawlessIVCount = 3 }, // Cresselia - new(ORAS) { Species = 638, Level = 50, Location = 336, FlawlessIVCount = 3 }, // Cobalion - new(ORAS) { Species = 639, Level = 50, Location = 336, FlawlessIVCount = 3 }, // Terrakion - new(ORAS) { Species = 640, Level = 50, Location = 336, FlawlessIVCount = 3 }, // Virizion - new(ORAS) { Species = 645, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Landorus - new(ORAS) { Species = 646, Level = 50, Location = 342, FlawlessIVCount = 3 }, // Kyurem - - // Devon Scope Kecleon - //new EncounterStatic { Species = 352, Level = 30, Location = 240 }, // Kecleon @ Route 119 -- dexnav encounter slot collision; prefer EncounterSlot - //new EncounterStatic { Species = 352, Level = 30, Location = 242 }, // Kecleon @ Route 120 -- dexnav encounter slot collision; prefer EncounterSlot - new(ORAS) { Species = 352, Level = 40, Location = 176, Gender = 1 }, // Kecleon @ Lavaridge - new(ORAS) { Species = 352, Level = 45, Location = 196, Ability = OnlyHidden }, // Kecleon @ Mossdeep City - - // Eon Ticket Lati@s - new( AS) { Species = 381, Level = 30, Location = 320, FlawlessIVCount = 3 }, // Latios - new(OR ) { Species = 380, Level = 30, Location = 320, FlawlessIVCount = 3 }, // Latias - - // Stationary - new( AS) { Species = 101, Level = 40, Location = 292 }, // Electrode - new(OR ) { Species = 101, Level = 40, Location = 314 }, // Electrode - new(ORAS) { Species = 100, Level = 20, Location = 302 }, // Voltorb @ Route 119 - new(ORAS) { Species = 442, Level = 50, Location = 304 }, // Spiritomb @ Route 120 - - // Soaring in the Sky - new(ORAS) { Species = 198, Level = 45, Location = 348 }, // Murkrow - new(ORAS) { Species = 276, Level = 40, Location = 348 }, // Taillow - new(ORAS) { Species = 278, Level = 40, Location = 348 }, // Wingull - new(ORAS) { Species = 279, Level = 40, Location = 348 }, // Pelipper - new(ORAS) { Species = 333, Level = 40, Location = 348 }, // Swablu - new(ORAS) { Species = 425, Level = 45, Location = 348 }, // Drifloon - new(ORAS) { Species = 628, Level = 45, Location = 348 }, // Braviary - - BaseCosplay with {Form = 1, Moves = new [] {098, 486, 086, (int)Move.MeteorMash}}, // Rock Star - BaseCosplay with {Form = 2, Moves = new [] {098, 486, 086, (int)Move.IcicleCrash}}, // Belle - BaseCosplay with {Form = 3, Moves = new [] {098, 486, 086, (int)Move.DrainingKiss}}, // Pop Star - BaseCosplay with {Form = 4, Moves = new [] {098, 486, 086, (int)Move.ElectricTerrain}}, // Ph.D. - BaseCosplay with {Form = 5, Moves = new [] {098, 486, 086, (int)Move.FlyingPress}}, // Libre - BaseCosplay, // Cosplay, same 3 level up moves. - }; - - private static readonly EncounterStatic6[] Encounter_AO = Encounter_AO_Regular; - - #endregion - #region Trade Tables - internal static readonly EncounterTrade6[] TradeGift_XY = - { - new(XY, 01,3,23,049) { Species = 129, Level = 05, Ability = OnlyFirst, TID = 44285, IVs = new[] {-1,31,-1,-1,31,-1}, Gender = 0, Nature = Nature.Adamant }, // Magikarp - new(XY, 10,3,00,000) { Species = 133, Level = 05, Ability = OnlyFirst, TID = 29294, Gender = 1, Nature = Nature.Docile }, // Eevee - - new(XY, 15,4,13,017) { Species = 083, Level = 10, Ability = OnlyFirst, TID = 00185, IVs = new[] {-1,-1,-1,31,-1,-1}, Gender = 0, Nature = Nature.Jolly }, // Farfetch'd - new(XY, 17,5,08,025) { Species = 208, Level = 20, Ability = OnlyFirst, TID = 19250, IVs = new[] {-1,-1,31,-1,-1,-1}, Gender = 1, Nature = Nature.Impish }, // Steelix - new(XY, 18,7,20,709) { Species = 625, Level = 50, Ability = OnlyFirst, TID = 03447, IVs = new[] {-1,31,-1,-1,-1,-1}, Gender = 0, Nature = Nature.Adamant }, // Bisharp - - new(XY, 02,3,11,005) { Species = 656, Level = 05, Ability = OnlyFirst, TID = 00037, IVs = new[] {20,20,20,31,20,20}, Gender = 0, Nature = Nature.Jolly }, // Froakie - new(XY, 02,3,09,005) { Species = 650, Level = 05, Ability = OnlyFirst, TID = 00037, IVs = new[] {20,31,20,20,20,20}, Gender = 0, Nature = Nature.Adamant }, // Chespin - new(XY, 02,3,18,005) { Species = 653, Level = 05, Ability = OnlyFirst, TID = 00037, IVs = new[] {20,20,20,20,31,20}, Gender = 0, Nature = Nature.Modest }, // Fennekin - new(XY, 51,4,04,033) { Species = 280, Level = 05, Ability = OnlyFirst, TID = 37110, IVs = new[] {20,20,20,31,31,20}, Gender = 1, Nature = Nature.Modest, IsNicknamed = false }, // Ralts - }; - - internal static readonly EncounterTrade6[] TradeGift_AO = - { - new(ORAS, 01,3,05,040) { Species = 296, Level = 09, Ability = OnlySecond, TID = 30724, IVs = new[] {-1,31,-1,-1,-1,-1}, Gender = 0, Nature = Nature.Brave }, // Makuhita - new(ORAS, 34,3,13,176) { Species = 300, Level = 30, Ability = OnlyFirst, TID = 03239, IVs = new[] {-1,-1,-1,31,-1,-1}, Gender = 1, Nature = Nature.Naughty }, // Skitty - new(ORAS, 07,4,10,319) { Species = 222, Level = 50, Ability = OnlyHidden, TID = 00325, IVs = new[] {31,-1,-1,-1,-1,31}, Gender = 1, Nature = Nature.Calm }, // Corsola - }; - #endregion - - internal static readonly EncounterStatic6[] StaticX = GetEncounters(Encounter_XY, X); - internal static readonly EncounterStatic6[] StaticY = GetEncounters(Encounter_XY, Y); - internal static readonly EncounterStatic6[] StaticA = GetEncounters(Encounter_AO, AS); - internal static readonly EncounterStatic6[] StaticO = GetEncounters(Encounter_AO, OR); + MarkEncounterTradeStrings(TradeGift_XY, TradeXY); + MarkEncounterTradeStrings(TradeGift_AO, TradeAO); } + + private const string tradeXY = "tradexy"; + private const string tradeAO = "tradeao"; + private static readonly string[][] TradeXY = Util.GetLanguageStrings8(tradeXY); + private static readonly string[][] TradeAO = Util.GetLanguageStrings8(tradeAO); + + #region Static Encounter/Gift Tables + private static readonly EncounterStatic6[] Encounter_XY = + { + // Kalos Starters @ Aquacorde Town + new(XY) { Gift = true, Species = 650, Level = 5, Location = 10 }, // Chespin + new(XY) { Gift = true, Species = 653, Level = 5, Location = 10 }, // Fennekin + new(XY) { Gift = true, Species = 656, Level = 5, Location = 10 }, // Froakie + + // Kanto Starters @ Lumiose City + new(XY) { Gift = true, Species = 1, Level = 10, Location = 22 }, // Bulbasaur + new(XY) { Gift = true, Species = 4, Level = 10, Location = 22 }, // Charmander + new(XY) { Gift = true, Species = 7, Level = 10, Location = 22 }, // Squirtle + + // Fossils @ Ambrette Town + new(XY) { Gift = true, Species = 138, Level = 20, Location = 44 }, // Omanyte + new(XY) { Gift = true, Species = 140, Level = 20, Location = 44 }, // Kabuto + new(XY) { Gift = true, Species = 142, Level = 20, Location = 44 }, // Aerodactyl + new(XY) { Gift = true, Species = 345, Level = 20, Location = 44 }, // Lileep + new(XY) { Gift = true, Species = 347, Level = 20, Location = 44 }, // Anorith + new(XY) { Gift = true, Species = 408, Level = 20, Location = 44 }, // Cranidos + new(XY) { Gift = true, Species = 410, Level = 20, Location = 44 }, // Shieldon + new(XY) { Gift = true, Species = 564, Level = 20, Location = 44 }, // Tirtouga + new(XY) { Gift = true, Species = 566, Level = 20, Location = 44 }, // Archen + new(XY) { Gift = true, Species = 696, Level = 20, Location = 44 }, // Tyrunt + new(XY) { Gift = true, Species = 698, Level = 20, Location = 44 }, // Amaura + + // Gift + new(XY) { Gift = true, Species = 448, Level = 32, Location = 60, Ability = OnlyFirst, IVs = new[] {06,25,16,31,25,19}, Nature = Nature.Hasty, Gender = 0, Shiny = Shiny.Never }, // Lucario + new(XY) { Gift = true, Species = 131, Level = 30, Location = 62, Ability = OnlyFirst, IVs = new[] {31,20,20,20,20,20}, Nature = Nature.Docile }, // Lapras + + // Stationary + new(XY) { Species = 143, Level = 15, Location = 038, Shiny = Shiny.Never }, // Snorlax + + // Shaking Trash Cans @ Lost Hotel + new(XY) { Species = 568, Level = 35, Location = 142 }, // Trubbish + new(XY) { Species = 569, Level = 36, Location = 142 }, // Garbodor + new(XY) { Species = 569, Level = 37, Location = 142 }, // Garbodor + new(XY) { Species = 569, Level = 38, Location = 142 }, // Garbodor + new(XY) { Species = 479, Level = 38, Location = 142 }, // Rotom + + // Shaking Trash Cans @ Pokemon Village + new(XY) { Species = 569, Level = 46, Location = 98 }, // Garbodor + new(XY) { Species = 569, Level = 47, Location = 98 }, // Garbodor + new(XY) { Species = 569, Level = 48, Location = 98 }, // Garbodor + new(XY) { Species = 569, Level = 49, Location = 98 }, // Garbodor + new(XY) { Species = 569, Level = 50, Location = 98 }, // Garbodor + new(XY) { Species = 354, Level = 46, Location = 98 }, // Banette + new(XY) { Species = 354, Level = 47, Location = 98 }, // Banette + new(XY) { Species = 354, Level = 48, Location = 98 }, // Banette + new(XY) { Species = 354, Level = 49, Location = 98 }, // Banette + new(XY) { Species = 354, Level = 50, Location = 98 }, // Banette + + // Stationary Legendary + new(X ) { Species = 716, Level = 50, Location = 138, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Xerneas + new( Y) { Species = 717, Level = 50, Location = 138, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Yveltal + new(XY) { Species = 718, Level = 70, Location = 140, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(XY) { Species = 150, Level = 70, Location = 168, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Mewtwo + new(XY) { Species = 144, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Articuno + new(XY) { Species = 145, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zapdos + new(XY) { Species = 146, Level = 70, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Moltres + }; + + private static readonly EncounterStatic6 BaseCosplay = new(ORAS) + { + Location = 178, // Or 180, 186, 194 + Species = 025, + Form = 6, + Level = 20, + Gender = 1, + Ability = OnlyHidden, + FlawlessIVCount = 3, + CNT_Cool = 70, + CNT_Beauty = 70, + CNT_Cute = 70, + CNT_Tough = 70, + CNT_Smart = 70, + Gift = true, + Shiny = Shiny.Never, + }; + + private static readonly EncounterStatic6[] Encounter_AO_Regular = + { + // Starters @ Route 101 + new(ORAS) { Gift = true, Species = 252, Level = 5, Location = 204 }, // Treeko + new(ORAS) { Gift = true, Species = 255, Level = 5, Location = 204 }, // Torchic + new(ORAS) { Gift = true, Species = 258, Level = 5, Location = 204 }, // Mudkip + + new(ORAS) { Gift = true, Species = 152, Level = 5, Location = 204 }, // Chikorita + new(ORAS) { Gift = true, Species = 155, Level = 5, Location = 204 }, // Cyndaquil + new(ORAS) { Gift = true, Species = 158, Level = 5, Location = 204 }, // Totodile + + new(ORAS) { Gift = true, Species = 387, Level = 5, Location = 204 }, // Turtwig + new(ORAS) { Gift = true, Species = 390, Level = 5, Location = 204 }, // Chimchar + new(ORAS) { Gift = true, Species = 393, Level = 5, Location = 204 }, // Piplup + + new(ORAS) { Gift = true, Species = 495, Level = 5, Location = 204 }, // Snivy + new(ORAS) { Gift = true, Species = 498, Level = 5, Location = 204 }, // Tepig + new(ORAS) { Gift = true, Species = 501, Level = 5, Location = 204 }, // Oshawott + + // Fossils @ Rustboro City + new(ORAS) { Gift = true, Species = 138, Level = 20, Location = 190 }, // Omanyte + new(ORAS) { Gift = true, Species = 140, Level = 20, Location = 190 }, // Kabuto + new(ORAS) { Gift = true, Species = 142, Level = 20, Location = 190 }, // Aerodactyl + new(ORAS) { Gift = true, Species = 345, Level = 20, Location = 190 }, // Lileep + new(ORAS) { Gift = true, Species = 347, Level = 20, Location = 190 }, // Anorith + new(ORAS) { Gift = true, Species = 408, Level = 20, Location = 190 }, // Cranidos + new(ORAS) { Gift = true, Species = 410, Level = 20, Location = 190 }, // Shieldon + new(ORAS) { Gift = true, Species = 564, Level = 20, Location = 190 }, // Tirtouga + new(ORAS) { Gift = true, Species = 566, Level = 20, Location = 190 }, // Archen + new(ORAS) { Gift = true, Species = 696, Level = 20, Location = 190 }, // Tyrunt + new(ORAS) { Gift = true, Species = 698, Level = 20, Location = 190 }, // Amaura + + // Hot Springs Eggs + new(ORAS) { Gift = true, Species = 360, Level = 1, EggLocation = 60004, Ability = OnlyFirst, EggCycles = 70 }, // Wynaut + new(ORAS) { Gift = true, Species = 175, Level = 1, EggLocation = 60004, Ability = OnlyFirst, EggCycles = 70 }, // Togepi + + // Gift + new(ORAS) { Species = 374, Level = 01, Location = 196, Ability = OnlyFirst, Gift = true, IVs = new[] {-1,-1,31,-1,-1,31} }, // Beldum + new(ORAS) { Species = 351, Level = 30, Location = 240, Ability = OnlyFirst, Gift = true, IVs = new[] {-1,-1,-1,-1,31,-1}, CNT_Beauty = 100, Gender = 1, Nature = Nature.Lax }, // Castform + new(ORAS) { Species = 319, Level = 40, Location = 318, Ability = OnlyFirst, Gift = true, Gender = 1, Nature = Nature.Adamant }, // Sharpedo + new(ORAS) { Species = 323, Level = 40, Location = 318, Ability = OnlyFirst, Gift = true, Gender = 1, Nature = Nature.Quiet }, // Camerupt + new( AS) { Species = 380, Level = 30, Location = 320, Ability = OnlyFirst, Gift = true, FlawlessIVCount = 3 }, // Latias + new(OR ) { Species = 381, Level = 30, Location = 320, Ability = OnlyFirst, Gift = true, FlawlessIVCount = 3 }, // Latios + + // Stationary Legendary + new(ORAS) { Species = 377, Level = 40, Location = 278, FlawlessIVCount = 3 }, // Regirock + new(ORAS) { Species = 378, Level = 40, Location = 306, FlawlessIVCount = 3 }, // Regice + new(ORAS) { Species = 379, Level = 40, Location = 308, FlawlessIVCount = 3 }, // Registeel + new(ORAS) { Species = 486, Level = 50, Location = 306, FlawlessIVCount = 3 }, // Regigigas + new( AS) { Species = 382, Level = 45, Location = 296, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Kyogre + new(OR ) { Species = 383, Level = 45, Location = 296, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Groudon + new(ORAS) { Species = 384, Level = 70, Location = 316, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Rayquaza + new(ORAS) { Species = 386, Level = 80, Location = 316, Shiny = Shiny.Never, FlawlessIVCount = 3, Fateful = true }, // Deoxys + + // Hoopa Rings + new( AS) { Species = 249, Level = 50, Location = 304, FlawlessIVCount = 3 }, // Lugia + new(OR ) { Species = 250, Level = 50, Location = 304, FlawlessIVCount = 3 }, // Ho-Oh + new( AS) { Species = 483, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Dialga + new(OR ) { Species = 484, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Palkia + new( AS) { Species = 644, Level = 50, Location = 340, FlawlessIVCount = 3 }, // Zekrom + new(OR ) { Species = 643, Level = 50, Location = 340, FlawlessIVCount = 3 }, // Reshiram + new( AS) { Species = 642, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Thundurus + new(OR ) { Species = 641, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Tornadus + new(ORAS) { Species = 243, Level = 50, Location = 334, FlawlessIVCount = 3 }, // Raikou + new(ORAS) { Species = 244, Level = 50, Location = 334, FlawlessIVCount = 3 }, // Entei + new(ORAS) { Species = 245, Level = 50, Location = 334, FlawlessIVCount = 3 }, // Suicune + new(ORAS) { Species = 480, Level = 50, Location = 338, FlawlessIVCount = 3 }, // Uxie + new(ORAS) { Species = 481, Level = 50, Location = 338, FlawlessIVCount = 3 }, // Mesprit + new(ORAS) { Species = 482, Level = 50, Location = 338, FlawlessIVCount = 3 }, // Azelf + new(ORAS) { Species = 485, Level = 50, Location = 312, FlawlessIVCount = 3 }, // Heatran + new(ORAS) { Species = 487, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Giratina + new(ORAS) { Species = 488, Level = 50, Location = 344, FlawlessIVCount = 3 }, // Cresselia + new(ORAS) { Species = 638, Level = 50, Location = 336, FlawlessIVCount = 3 }, // Cobalion + new(ORAS) { Species = 639, Level = 50, Location = 336, FlawlessIVCount = 3 }, // Terrakion + new(ORAS) { Species = 640, Level = 50, Location = 336, FlawlessIVCount = 3 }, // Virizion + new(ORAS) { Species = 645, Level = 50, Location = 348, FlawlessIVCount = 3 }, // Landorus + new(ORAS) { Species = 646, Level = 50, Location = 342, FlawlessIVCount = 3 }, // Kyurem + + // Devon Scope Kecleon + //new EncounterStatic { Species = 352, Level = 30, Location = 240 }, // Kecleon @ Route 119 -- dexnav encounter slot collision; prefer EncounterSlot + //new EncounterStatic { Species = 352, Level = 30, Location = 242 }, // Kecleon @ Route 120 -- dexnav encounter slot collision; prefer EncounterSlot + new(ORAS) { Species = 352, Level = 40, Location = 176, Gender = 1 }, // Kecleon @ Lavaridge + new(ORAS) { Species = 352, Level = 45, Location = 196, Ability = OnlyHidden }, // Kecleon @ Mossdeep City + + // Eon Ticket Lati@s + new( AS) { Species = 381, Level = 30, Location = 320, FlawlessIVCount = 3 }, // Latios + new(OR ) { Species = 380, Level = 30, Location = 320, FlawlessIVCount = 3 }, // Latias + + // Stationary + new( AS) { Species = 101, Level = 40, Location = 292 }, // Electrode + new(OR ) { Species = 101, Level = 40, Location = 314 }, // Electrode + new(ORAS) { Species = 100, Level = 20, Location = 302 }, // Voltorb @ Route 119 + new(ORAS) { Species = 442, Level = 50, Location = 304 }, // Spiritomb @ Route 120 + + // Soaring in the Sky + new(ORAS) { Species = 198, Level = 45, Location = 348 }, // Murkrow + new(ORAS) { Species = 276, Level = 40, Location = 348 }, // Taillow + new(ORAS) { Species = 278, Level = 40, Location = 348 }, // Wingull + new(ORAS) { Species = 279, Level = 40, Location = 348 }, // Pelipper + new(ORAS) { Species = 333, Level = 40, Location = 348 }, // Swablu + new(ORAS) { Species = 425, Level = 45, Location = 348 }, // Drifloon + new(ORAS) { Species = 628, Level = 45, Location = 348 }, // Braviary + + BaseCosplay with {Form = 1, Moves = new [] {098, 486, 086, (int)Move.MeteorMash}}, // Rock Star + BaseCosplay with {Form = 2, Moves = new [] {098, 486, 086, (int)Move.IcicleCrash}}, // Belle + BaseCosplay with {Form = 3, Moves = new [] {098, 486, 086, (int)Move.DrainingKiss}}, // Pop Star + BaseCosplay with {Form = 4, Moves = new [] {098, 486, 086, (int)Move.ElectricTerrain}}, // Ph.D. + BaseCosplay with {Form = 5, Moves = new [] {098, 486, 086, (int)Move.FlyingPress}}, // Libre + BaseCosplay, // Cosplay, same 3 level up moves. + }; + + private static readonly EncounterStatic6[] Encounter_AO = Encounter_AO_Regular; + + #endregion + #region Trade Tables + internal static readonly EncounterTrade6[] TradeGift_XY = + { + new(XY, 01,3,23,049) { Species = 129, Level = 05, Ability = OnlyFirst, TID = 44285, IVs = new[] {-1,31,-1,-1,31,-1}, Gender = 0, Nature = Nature.Adamant }, // Magikarp + new(XY, 10,3,00,000) { Species = 133, Level = 05, Ability = OnlyFirst, TID = 29294, Gender = 1, Nature = Nature.Docile }, // Eevee + + new(XY, 15,4,13,017) { Species = 083, Level = 10, Ability = OnlyFirst, TID = 00185, IVs = new[] {-1,-1,-1,31,-1,-1}, Gender = 0, Nature = Nature.Jolly }, // Farfetch'd + new(XY, 17,5,08,025) { Species = 208, Level = 20, Ability = OnlyFirst, TID = 19250, IVs = new[] {-1,-1,31,-1,-1,-1}, Gender = 1, Nature = Nature.Impish }, // Steelix + new(XY, 18,7,20,709) { Species = 625, Level = 50, Ability = OnlyFirst, TID = 03447, IVs = new[] {-1,31,-1,-1,-1,-1}, Gender = 0, Nature = Nature.Adamant }, // Bisharp + + new(XY, 02,3,11,005) { Species = 656, Level = 05, Ability = OnlyFirst, TID = 00037, IVs = new[] {20,20,20,31,20,20}, Gender = 0, Nature = Nature.Jolly }, // Froakie + new(XY, 02,3,09,005) { Species = 650, Level = 05, Ability = OnlyFirst, TID = 00037, IVs = new[] {20,31,20,20,20,20}, Gender = 0, Nature = Nature.Adamant }, // Chespin + new(XY, 02,3,18,005) { Species = 653, Level = 05, Ability = OnlyFirst, TID = 00037, IVs = new[] {20,20,20,20,31,20}, Gender = 0, Nature = Nature.Modest }, // Fennekin + new(XY, 51,4,04,033) { Species = 280, Level = 05, Ability = OnlyFirst, TID = 37110, IVs = new[] {20,20,20,31,31,20}, Gender = 1, Nature = Nature.Modest, IsNicknamed = false }, // Ralts + }; + + internal static readonly EncounterTrade6[] TradeGift_AO = + { + new(ORAS, 01,3,05,040) { Species = 296, Level = 09, Ability = OnlySecond, TID = 30724, IVs = new[] {-1,31,-1,-1,-1,-1}, Gender = 0, Nature = Nature.Brave }, // Makuhita + new(ORAS, 34,3,13,176) { Species = 300, Level = 30, Ability = OnlyFirst, TID = 03239, IVs = new[] {-1,-1,-1,31,-1,-1}, Gender = 1, Nature = Nature.Naughty }, // Skitty + new(ORAS, 07,4,10,319) { Species = 222, Level = 50, Ability = OnlyHidden, TID = 00325, IVs = new[] {31,-1,-1,-1,-1,31}, Gender = 1, Nature = Nature.Calm }, // Corsola + }; + #endregion + + internal static readonly EncounterStatic6[] StaticX = GetEncounters(Encounter_XY, X); + internal static readonly EncounterStatic6[] StaticY = GetEncounters(Encounter_XY, Y); + internal static readonly EncounterStatic6[] StaticA = GetEncounters(Encounter_AO, AS); + internal static readonly EncounterStatic6[] StaticO = GetEncounters(Encounter_AO, OR); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters7.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters7.cs index 526698902..516216aa0 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters7.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters7.cs @@ -2,361 +2,360 @@ using static PKHeX.Core.GameVersion; using static PKHeX.Core.AbilityPermission; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 Encounters +/// +internal static class Encounters7 { - /// - /// Generation 7 Encounters - /// - internal static class Encounters7 + internal static readonly EncounterArea7[] SlotsSN = EncounterArea7.GetAreas(Get("sn", "sm"), SN); + internal static readonly EncounterArea7[] SlotsMN = EncounterArea7.GetAreas(Get("mn", "sm"), MN); + internal static readonly EncounterArea7[] SlotsUS = EncounterArea7.GetAreas(Get("us", "uu"), US); + internal static readonly EncounterArea7[] SlotsUM = EncounterArea7.GetAreas(Get("um", "uu"), UM); + + static Encounters7() { - internal static readonly EncounterArea7[] SlotsSN = EncounterArea7.GetAreas(Get("sn", "sm"), SN); - internal static readonly EncounterArea7[] SlotsMN = EncounterArea7.GetAreas(Get("mn", "sm"), MN); - internal static readonly EncounterArea7[] SlotsUS = EncounterArea7.GetAreas(Get("us", "uu"), US); - internal static readonly EncounterArea7[] SlotsUM = EncounterArea7.GetAreas(Get("um", "uu"), UM); - - static Encounters7() - { - MarkEncounterTradeStrings(TradeGift_SM, TradeSM); - MarkEncounterTradeStrings(TradeGift_USUM, TradeUSUM); - } - - private static readonly EncounterStatic7[] Encounter_SM = // @ a\1\5\5 - { - // Gifts - 0.bin - new(SM) { Gift = true, Species = 722, Level = 5, Location = 024 }, // Rowlet - new(SM) { Gift = true, Species = 725, Level = 5, Location = 024 }, // Litten - new(SM) { Gift = true, Species = 728, Level = 5, Location = 024 }, // Popplio - new(SM) { Gift = true, Species = 138, Level = 15, Location = 058 }, // Omanyte - new(SM) { Gift = true, Species = 140, Level = 15, Location = 058 }, // Kabuto - // new(SM) { Gift = true, Species = 142, Level = 15, Location = 058 }, // Aerodactyl - new(SM) { Gift = true, Species = 345, Level = 15, Location = 058 }, // Lileep - new(SM) { Gift = true, Species = 347, Level = 15, Location = 058 }, // Anorith - new(SM) { Gift = true, Species = 408, Level = 15, Location = 058 }, // Cranidos - new(SM) { Gift = true, Species = 410, Level = 15, Location = 058 }, // Shieldon - new(SM) { Gift = true, Species = 564, Level = 15, Location = 058 }, // Tirtouga - new(SM) { Gift = true, Species = 566, Level = 15, Location = 058 }, // Archen - new(SM) { Gift = true, Species = 696, Level = 15, Location = 058 }, // Tyrunt - new(SM) { Gift = true, Species = 698, Level = 15, Location = 058 }, // Amaura - new(SM) { Gift = true, Species = 137, Level = 30, Location = 116 }, // Porygon @ Route 15 - new(SM) { Gift = true, Species = 133, Level = 1, EggLocation = 60002 }, // Eevee @ Nursery helpers - new(SM) { Gift = true, Species = 772, Level = 40, Location = 188, FlawlessIVCount = 3 }, // Type: Null - new(SN) { Gift = true, Species = 789, Level = 5, Location = 142, Shiny = Shiny.Never, Ability = OnlySecond, FlawlessIVCount = 3 }, // Cosmog - new(MN) { Gift = true, Species = 789, Level = 5, Location = 144, Shiny = Shiny.Never, Ability = OnlySecond, FlawlessIVCount = 3 }, // Cosmog - new(SM) { Gift = true, Species = 142, Level = 40, Location = 172 }, // Aerodactyl @ Seafolk Village - - new(SM) { Gift = true, Species = 718, Form = 0, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 1, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 2, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 3, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - - new(SM) { Gift = true, Species = 718, Form = 0, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 1, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 2, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - new(SM) { Gift = true, Species = 718, Form = 3, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde - - new(SM) // Magearna (Bottle Cap) 00 FF - { - Gift = true, Species = 801, Level = 50, Location = 40001, Shiny = Shiny.Never, FlawlessIVCount = 3, HeldItem = 795, Ability = OnlySecond, - Fateful = true, Relearn = new [] {705, 430, 381, 270}, Ball = 0x10, // Cherish - }, - - // Static Encounters - 1.bin - new(SM) { Species = 746, Level = 17, Location = 086, Shiny = Shiny.Never, Ability = OnlyFirst }, // Wishiwashi - new(SM) { Species = 746, Level = 18, Location = 086, Shiny = Shiny.Never, Ability = OnlyFirst }, // Wishiwashi - - new(SN) { Species = 791, Level = 55, Location = 176, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[]{713, 322, 242, 428} }, // Solgaleo - new(MN) { Species = 792, Level = 55, Location = 178, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[]{714, 322, 539, 247} }, // Lunala - - new(SM) { Species = 785, Level = 60, Location = 030, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Koko - new(SM) { Species = 786, Level = 60, Location = 092, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Lele - new(SM) { Species = 787, Level = 60, Location = 140, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Bulu - new(SM) { Species = 788, Level = 60, Location = 180, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Fini - - new(SM) { Species = 793, Level = 55, Location = 082, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Nihilego @ Wela Volcano Park - new(SM) { Species = 793, Level = 55, Location = 100, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Nihilego @ Diglett’s Tunnel - new(SN) { Species = 794, Level = 65, Location = 040, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Buzzwole @ Melemele Meadow - new(MN) { Species = 795, Level = 60, Location = 046, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Pheromosa @ Verdant Cavern (Trial Site) - new(SM) { Species = 796, Level = 65, Location = 090, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Xurkitree @ Lush Jungle - new(SM) { Species = 796, Level = 65, Location = 076, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Xurkitree @ Memorial Hill - new(SN) { Species = 798, Level = 60, Location = 134, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Malie Garden - new(SN) { Species = 798, Level = 60, Location = 120, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Route 17 - new(MN) { Species = 797, Level = 65, Location = 124, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Haina Desert - new(MN) { Species = 797, Level = 65, Location = 134, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Malie Garden - new(SM) { Species = 799, Level = 70, Location = 182, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Guzzlord @ Resolution Cave - new(SM) { Species = 800, Level = 75, Location = 036, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Necrozma @ Ten Carat Hill (Farthest Hollow) - - // QR Scan: Su/M/Tu/W/Th/F/Sa - // Melemele Island - new(SM) { Species = 155, Level = 12, Location = 010, Relearn = new[]{024, 052, 108, 043} }, // Cyndaquil @ Route 3 - new(SM) { Species = 158, Level = 12, Location = 042, Relearn = new[]{232, 099, 055, 043} }, // Totodile @ Seaward Cave - new(SM) { Species = 633, Level = 13, Location = 034, Relearn = new[]{372, 029, 044, 116} }, // Deino @ Ten Carat Hill - new(SM) { Species = 116, Level = 18, Location = 014, Relearn = new[]{225, 239, 055, 043} }, // Horsea @ Kala'e Bay - new(SM) { Species = 599, Level = 08, Location = 020, Relearn = new[]{268, 011, 000, 000} }, // Klink @ Hau'oli City - new(SM) { Species = 152, Level = 10, Location = 012, Relearn = new[]{073, 077, 075, 045} }, // Chikorita @ Route 2 - new(SM) { Species = 607, Level = 10, Location = 038, Relearn = new[]{051, 109, 083, 123} }, // Litwick @ Hau'oli Cemetery - - // Akala Island - new(SM) { Species = 574, Level = 17, Location = 054, Relearn = new[]{399, 060, 003, 313} }, // Gothita @ Route 6 - new(SM) { Species = 363, Level = 19, Location = 056, Relearn = new[]{392, 362, 301, 227} }, // Spheal @ Route 7 - new(SM) { Species = 404, Level = 20, Location = 058, Relearn = new[]{598, 044, 209, 268} }, // Luxio @ Route 8 - new(SM) { Species = 679, Level = 23, Location = 094, Relearn = new[]{194, 332, 425, 475} }, // Honedge @ Akala Outskirts - new(SM) { Species = 543, Level = 14, Location = 050, Relearn = new[]{390, 228, 103, 040} }, // Venipede @ Route 4 - new(SM) { Species = 069, Level = 16, Location = 052, Relearn = new[]{491, 077, 079, 035} }, // Bellsprout @ Route 5 - new(SM) { Species = 183, Level = 17, Location = 086, Relearn = new[]{453, 270, 061, 205} }, // Marill @ Brooklet Hill - - // Ula'ula Island - new(SM) { Species = 111, Level = 30, Location = 138, Relearn = new[]{130, 350, 498, 523} }, // Rhyhorn @ Blush Mountain - new(SM) { Species = 220, Level = 31, Location = 114, Relearn = new[]{573, 036, 420, 196} }, // Swinub @ Tapu Village - new(SM) { Species = 578, Level = 33, Location = 118, Relearn = new[]{101, 248, 283, 473} }, // Duosion @ Route 16 - new(SM) { Species = 315, Level = 34, Location = 128, Relearn = new[]{437, 275, 230, 390} }, // Roselia @ Ula'ula Meadow - new(SM) { Species = 397, Level = 27, Location = 106, Relearn = new[]{355, 018, 283, 104} }, // Staravia @ Route 10 - new(SM) { Species = 288, Level = 27, Location = 108, Relearn = new[]{359, 498, 163, 203} }, // Vigoroth @ Route 11 - new(SM) { Species = 610, Level = 28, Location = 136, Relearn = new[]{231, 337, 206, 163} }, // Axew @ Mount Hokulani - - // Poni Island - new(SM) { Species = 604, Level = 55, Location = 164, Relearn = new[]{435, 051, 029, 306} }, // Eelektross @ Poni Grove - new(SM) { Species = 534, Level = 57, Location = 166, Relearn = new[]{409, 276, 264, 444} }, // Conkeldurr @ Poni Plains - new(SM) { Species = 468, Level = 59, Location = 170, Relearn = new[]{248, 403, 396, 245} }, // Togekiss @ Poni Gauntlet - new(SM) { Species = 542, Level = 57, Location = 156, Relearn = new[]{382, 437, 014, 494} }, // Leavanny @ Poni Meadow - new(SM) { Species = 497, Level = 43, Location = 184, Relearn = new[]{137, 489, 348, 021} }, // Serperior @ Exeggutor Island - new(SM) { Species = 503, Level = 43, Location = 158, Relearn = new[]{362, 227, 453, 279} }, // Samurott @ Poni Wilds - new(SM) { Species = 500, Level = 43, Location = 160, Relearn = new[]{276, 053, 372, 535} }, // Emboar @ Ancient Poni Path - }; - - internal static readonly EncounterTrade7[] TradeGift_SM = // @ a\1\5\5 - { - // Trades - 4.bin - new(SM) { Species = 066, Form = 0, Level = 09, Ability = OnlySecond, TID = 00410, SID = 00000, IVs = new[] {-1,31,-1,-1,-1,-1}, OTGender = 1, Gender = 0, Nature = Nature.Brave }, // Machop - new(SM) { Species = 761, Form = 0, Level = 16, Ability = OnlyFirst, TID = 20683, SID = 00009, IVs = new[] {-1,31,-1,-1,-1,-1}, OTGender = 0, Gender = 1, Nature = Nature.Adamant }, // Bounsweet - new(SM) { Species = 061, Form = 0, Level = 22, Ability = OnlySecond, TID = 01092, SID = 00009, IVs = new[] {31,-1,-1,-1,-1,-1}, OTGender = 1, Gender = 1, Nature = Nature.Naughty }, // Poliwhirl - new(SM) { Species = 440, Form = 0, Level = 27, Ability = OnlySecond, TID = 10913, SID = 00000, IVs = new[] {-1,-1,-1,-1,31,-1}, OTGender = 1, Gender = 1, Nature = Nature.Calm }, // Happiny - new(SM) { Species = 075, Form = 1, Level = 32, Ability = OnlyFirst, TID = 20778, SID = 00009, IVs = new[] {-1,-1,31,-1,-1,-1}, OTGender = 0, Gender = 0, Nature = Nature.Impish, EvolveOnTrade = true }, // Graveler-1 - new(SM) { Species = 762, Form = 0, Level = 43, Ability = OnlyFirst, TID = 20679, SID = 00009, IVs = new[] {-1,-1,-1,-1,-1,31}, OTGender = 1, Gender = 1, Nature = Nature.Careful }, // Steenee - new(SM) { Species = 663, Form = 0, Level = 59, Ability = OnlyHidden, TID = 56734, SID = 00008, IVs = new[] {-1,-1,-1,31,-1,-1}, OTGender = 0, Gender = 0, Nature = Nature.Jolly }, // Talonflame - }; - - private static readonly EncounterStatic7[] Encounter_USUM = - { - new(USUM) { Gift = true, Species = 722, Level = 05, Location = 008 }, // Rowlet - new(USUM) { Gift = true, Species = 725, Level = 05, Location = 008 }, // Litten - new(USUM) { Gift = true, Species = 728, Level = 05, Location = 008 }, // Popplio - new(USUM) { Gift = true, Species = 138, Level = 15, Location = 058 }, // Omanyte - new(USUM) { Gift = true, Species = 140, Level = 15, Location = 058 }, // Kabuto - // new(USUM) { Gift = true, Species = 142, Level = 15, Location = 058 }, // Aerodactyl - new(USUM) { Gift = true, Species = 345, Level = 15, Location = 058 }, // Lileep - new(USUM) { Gift = true, Species = 347, Level = 15, Location = 058 }, // Anorith - new(USUM) { Gift = true, Species = 408, Level = 15, Location = 058 }, // Cranidos - new(USUM) { Gift = true, Species = 410, Level = 15, Location = 058 }, // Shieldon - new(USUM) { Gift = true, Species = 564, Level = 15, Location = 058 }, // Tirtouga - new(USUM) { Gift = true, Species = 566, Level = 15, Location = 058 }, // Archen - new(USUM) { Gift = true, Species = 696, Level = 15, Location = 058 }, // Tyrunt - new(USUM) { Gift = true, Species = 698, Level = 15, Location = 058 }, // Amaura - new(USUM) { Gift = true, Species = 133, Level = 01, EggLocation = 60002 }, // Eevee @ Nursery helpers - new(USUM) { Gift = true, Species = 137, Level = 30, Location = 116 }, // Porygon @ Route 15 - new(USUM) { Gift = true, Species = 772, Level = 60, Location = 188, FlawlessIVCount = 3 }, // Type: Null @ Aether Paradise - new(USUM) { Gift = true, Species = 772, Level = 60, Location = 160, FlawlessIVCount = 3 }, // Type: Null @ Ancient Poni Path - new(US ) { Gift = true, Species = 789, Level = 05, Location = 142, FlawlessIVCount = 3, Shiny = Shiny.Never, Ability = OnlySecond }, // Cosmog @ Lake of the Sunne - new( UM) { Gift = true, Species = 789, Level = 05, Location = 144, FlawlessIVCount = 3, Shiny = Shiny.Never, Ability = OnlySecond }, // Cosmog @ Lake of the Moone - new(USUM) { Gift = true, Species = 142, Level = 40, Location = 172 }, // Aerodactyl @ Seafolk Village - new(USUM) { Gift = true, Species = 025, Level = 40, Location = 070, FlawlessIVCount = 3, HeldItem = 796, Relearn = new[] {57,0,0,0} }, // Pikachu @ Heahea City - new(USUM) { Gift = true, Species = 803, Level = 40, Location = 208, FlawlessIVCount = 3 }, // Poipole @ Megalo Tower - new(USUM) { Gift = true, Species = 803, Level = 40, Location = 206, FlawlessIVCount = 3 }, // Poipole @ Ultra Megalopolis - - // Totem-Sized Gifts @ Heahea Beach - new(US ) { Gift = true, Species = 735, Level = 20, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Gumshoos - new( UM) { Gift = true, Species = 020, Level = 20, Ability = OnlyHidden, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Raticate - new(US ) { Gift = true, Species = 105, Level = 25, Ability = OnlyHidden, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Marowak - new( UM) { Gift = true, Species = 752, Level = 25, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Araquanid - new(US ) { Gift = true, Species = 754, Level = 30, Ability = OnlySecond, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Lurantis - new( UM) { Gift = true, Species = 758, Level = 30, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Salazzle - new(US ) { Gift = true, Species = 738, Level = 35, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Vikavolt - new( UM) { Gift = true, Species = 777, Level = 35, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Togedemaru - new(USUM) { Gift = true, Species = 778, Level = 40, Ability = OnlyFirst, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Mimikyu - new(US ) { Gift = true, Species = 743, Level = 50, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Ribombee - new( UM) { Gift = true, Species = 784, Level = 50, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Kommo-o - - new(USUM) { Gift = true, Species = 718, Level = 63, Ability = OnlyFirst, Location = 118, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde (10%) @ Route 16 - - new(USUM) // Magearna (Bottle Cap) - { - Gift = true, Species = 801, Level = 50, Location = 40001, Shiny = Shiny.Never, FlawlessIVCount = 3, HeldItem = 795, Ability = OnlySecond, - Fateful = true, Relearn = new [] {705, 430, 381, 270}, Ball = 0x10, // Cherish - }, - - new(USUM) { Gift = true, Species = 718, Form = 0, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (50%) - new(USUM) { Gift = true, Species = 718, Form = 1, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (10%) - new(USUM) { Gift = true, Species = 718, Form = 2, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (10%-C) - new(USUM) { Gift = true, Species = 718, Form = 3, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (50%-C) - - new(US ) { Species = 791, Level = 60, Location = 028, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new[] {713,322,242,428} }, // Solgaleo @ Mahalo Trail (Plank Bridge) - new( UM) { Species = 792, Level = 60, Location = 028, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new[] {714,322,539,585} }, // Lunala @ Mahalo Trail (Plank Bridge) - - // QR Scan: Su/M/Tu/W/Th/F/Sa - // Melemele Island - new(USUM) { Species = 004, Level = 12, Location = 010, Relearn = new[] {068,108,052,010} }, // Charmander @ Route 3 - new(USUM) { Species = 007, Level = 12, Location = 042, Relearn = new[] {453,110,055,033} }, // Squirtle @ Seaward Cave - new(USUM) { Species = 095, Level = 14, Location = 034, Relearn = new[] {563,099,317,088} }, // Onix @ Ten Carat Hill - new(USUM) { Species = 116, Level = 18, Location = 014, Relearn = new[] {352,239,055,043} }, // Horsea @ Kala'e Bay - new(USUM) { Species = 664, Level = 09, Location = 020, Relearn = new[] {476,081,078,033}, Form = EncounterStatic.FormVivillon }, // Scatterbug @ Hau'oli City - new(USUM) { Species = 001, Level = 10, Location = 012, Relearn = new[] {580,022,045,033} }, // Bulbasaur @ Route 2 - new(USUM) { Species = 607, Level = 09, Location = 038, Relearn = new[] {203,052,083,123} }, // Litwick @ Hau'oli Cemetery - - // Akala Island - new(USUM) { Species = 280, Level = 17, Location = 054, Relearn = new[] {581,345,381,574} }, // Ralts @ Route 6 - new(USUM) { Species = 363, Level = 19, Location = 056, Relearn = new[] {187,362,301,227} }, // Spheal @ Route 7 - new(USUM) { Species = 256, Level = 20, Location = 058, Relearn = new[] {067,488,064,028} }, // Combusken @ Route 8 - new(USUM) { Species = 679, Level = 24, Location = 094, Relearn = new[] {469,332,425,475} }, // Honedge @ Akala Outskirts - new(USUM) { Species = 015, Level = 14, Location = 050, Relearn = new[] {099,031,041,000} }, // Beedrill @ Route 4 - new(USUM) { Species = 253, Level = 16, Location = 052, Relearn = new[] {580,072,098,071} }, // Grovyle @ Route 5 - new(USUM) { Species = 259, Level = 17, Location = 086, Relearn = new[] {068,193,189,055} }, // Marshtomp @ Brooklet Hill - - // Ula'ula Island - new(USUM) { Species = 111, Level = 32, Location = 138, Relearn = new[] {470,350,498,523} }, // Rhyhorn @ Blush Mountain - new(USUM) { Species = 220, Level = 33, Location = 114, Relearn = new[] {333,036,420,196} }, // Swinub @ Tapu Village - new(USUM) { Species = 394, Level = 35, Location = 118, Relearn = new[] {681,362,031,117} }, // Prinplup @ Route 16 - new(USUM) { Species = 388, Level = 36, Location = 128, Relearn = new[] {484,073,072,044} }, // Grotle @ Ula'ula Meadow - new(USUM) { Species = 018, Level = 29, Location = 106, Relearn = new[] {211,297,239,098} }, // Pidgeot @ Route 10 - new(USUM) { Species = 391, Level = 29, Location = 108, Relearn = new[] {612,172,154,259} }, // Monferno @ Route 11 - new(USUM) { Species = 610, Level = 30, Location = 136, Relearn = new[] {068,337,206,163} }, // Axew @ Mount Hokulani - - // Poni Island - new(USUM) { Species = 604, Level = 55, Location = 164, Relearn = new[] {242,435,029,306} }, // Eelektross @ Poni Grove - new(USUM) { Species = 306, Level = 57, Location = 166, Relearn = new[] {179,484,038,334} }, // Aggron @ Poni Plains - new(USUM) { Species = 479, Level = 61, Location = 170, Relearn = new[] {268,506,486,164} }, // Rotom @ Poni Gauntlet - new(USUM) { Species = 542, Level = 57, Location = 156, Relearn = new[] {580,437,014,494} }, // Leavanny @ Poni Meadow - new(USUM) { Species = 652, Level = 45, Location = 184, Relearn = new[] {191,341,402,596} }, // Chesnaught @ Exeggutor Island - new(USUM) { Species = 658, Level = 44, Location = 158, Relearn = new[] {516,164,185,594} }, // Greninja @ Poni Wilds - new(USUM) { Species = 655, Level = 44, Location = 160, Relearn = new[] {273,473,113,595} }, // Delphox @ Ancient Poni Path - - new(USUM) { Species = 785, Level = 60, Location = 030, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Tapu Koko @ Ruins of Conflict - new(USUM) { Species = 786, Level = 60, Location = 092, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Tapu Lele @ Ruins of Life - new(USUM) { Species = 787, Level = 60, Location = 140, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Tapu Bulu @ Ruins of Abundance - new(USUM) { Species = 788, Level = 60, Location = 180, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Tapu Fini @ Ruins of Hope - - new(USUM) { Species = 023, Level = 10, Location = 012, Ability = OnlyFirst }, // Ekans @ Route 2 - - new(USUM) { Species = 127, Level = 42, Location = 184, Shiny = Shiny.Never }, // Pinsir @ Exeggutor Island - new(USUM) { Species = 127, Level = 43, Location = 184, Shiny = Shiny.Never }, // Pinsir @ Exeggutor Island - new(USUM) { Species = 800, Level = 65, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new[] {722,334,408,400}, HeldItem = 923 }, // Necrozma @ Mount Lanakila - - // Legendaries - new(USUM) { Species = 144, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {246,573,115,258} }, // Articuno - new(USUM) { Species = 145, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {246,435,365,240} }, // Zapdos - new(USUM) { Species = 146, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {246,053,403,241} }, // Moltres - new(USUM) { Species = 150, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {094,105,129,427} }, // Mewtwo - new(US ) { Species = 243, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Raikou - new( UM) { Species = 244, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {023,044,207,436} }, // Entei - new(USUM) { Species = 245, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {061,062,054,240} }, // Suicune - new( UM) { Species = 249, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {285,177,326,246} }, // Lugia - new(US ) { Species = 250, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {682,221,326,246}, HeldItem = 044 }, // Ho-Oh - new(USUM) { Species = 377, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Regirock - new(USUM) { Species = 378, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Regice - new(USUM) { Species = 379, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Registeel - new( UM) { Species = 380, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {296,406,375,273}, Gender = 1 }, // Latias - new(US ) { Species = 381, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {295,406,375,225}, Gender = 0 }, // Latios - new( UM) { Species = 382, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {058,618,347,330} }, // Kyogre - new(US ) { Species = 383, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {089,619,339,076} }, // Groudon - new(USUM) { Species = 384, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Rayquaza - new(USUM) { Species = 480, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {326,281,133,129} }, // Uxie - new(USUM) { Species = 481, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {326,204,248,129} }, // Mesprit - new(USUM) { Species = 482, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {326,417,253,129} }, // Azelf - new(US ) { Species = 483, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Dialga - new( UM) { Species = 484, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Palkia - new(US ) { Species = 485, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Heatran - new( UM) { Species = 486, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {428,279,146,109} }, // Regigigas - new(USUM) { Species = 487, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {467,396,414,337} }, // Giratina - new(USUM) { Species = 488, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 1 }, // Cresselia - new(USUM) { Species = 638, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {533,014,098,442} }, // Cobalion - new(USUM) { Species = 639, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {533,014,157,444} }, // Terrakion - new(USUM) { Species = 640, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {533,014,202,348} }, // Virizion - new(US ) { Species = 641, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Tornadus - new( UM) { Species = 642, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Thundurus - new(US ) { Species = 643, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Reshiram - new( UM) { Species = 644, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Zekrom - new(USUM) { Species = 645, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Landorus - new(USUM) { Species = 646, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kyurem - new(US ) { Species = 716, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {601,532,400,585} }, // Xerneas - new( UM) { Species = 717, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {613,399,566,094} }, // Yveltal - new(USUM) { Species = 718, Level = 60, Location = 182, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new[] {616,137,219,225} }, // Zygarde @ Resolution Cave - - // Ultra Space Wilds - new(USUM) { Species = 334, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Altaria - new(USUM) { Species = 469, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Yanmega - new(USUM) { Species = 561, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Sigilyph - new(USUM) { Species = 581, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Swanna - new(USUM) { Species = 277, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Swellow - new(USUM) { Species = 452, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Drapion - new(USUM) { Species = 531, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Audino - new(USUM) { Species = 695, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Heliolisk - new(USUM) { Species = 274, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Nuzleaf - new(USUM) { Species = 326, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Grumpig - new(USUM) { Species = 460, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Abomasnow - new(USUM) { Species = 308, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Medicham - new(USUM) { Species = 450, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Hippowdon - new(USUM) { Species = 558, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Crustle - new(USUM) { Species = 219, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Magcargo - new(USUM) { Species = 689, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Barbaracle - new(USUM) { Species = 271, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Lombre - new(USUM) { Species = 618, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Stunfisk - new(USUM) { Species = 419, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Floatzel - new(USUM) { Species = 195, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Quagsire - - // Ultra Beasts - new(USUM) { Species = 793, Level = 60, Location = 190, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {408,491,446,243} }, // Nihilego @ Ultra Deep Sea - new(US ) { Species = 794, Level = 60, Location = 218, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Buzzwole @ Ultra Jungle - new( UM) { Species = 795, Level = 60, Location = 214, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Pheromosa @ Ultra Desert - new(USUM) { Species = 796, Level = 60, Location = 210, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Xurkitree @ Ultra Plant - new( UM) { Species = 797, Level = 60, Location = 212, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Ultra Crater - new(US ) { Species = 798, Level = 60, Location = 216, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Ultra Forest - new(USUM) { Species = 799, Level = 60, Location = 220, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Guzzlord @ Ultra Ruin - new( UM) { Species = 805, Level = 60, Location = 164, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Stakataka @ Poni Grove - new(US ) { Species = 806, Level = 60, Location = 164, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Blacephalon @ Poni Grove - - // Ditto Five - new(USUM) { Species = 132, Level = 29, Location = 060, IVs = new[] {-1,-1,31,00,30,-1}, Nature = Nature.Bold }, // Ditto @ Route 9 - new(USUM) { Species = 132, Level = 29, Location = 072, IVs = new[] {-1,-1,30,31,30,-1}, Nature = Nature.Jolly }, // Ditto @ Konikoni City - new(USUM) { Species = 132, Level = 29, Location = 072, IVs = new[] {-1,31,30,30,-1,-1}, Nature = Nature.Adamant }, // Ditto @ Konikoni City - new(USUM) { Species = 132, Level = 29, Location = 072, IVs = new[] {-1,00,-1,-1,31,30}, Nature = Nature.Modest }, // Ditto @ Konikoni City - new(USUM) { Species = 132, Level = 29, Location = 072, IVs = new[] {-1,30,-1,31,-1,30}, Nature = Nature.Timid }, // Ditto @ Konikoni City - - // Miscellaneous Static - new(USUM) { Species = 760, Level = 28, Location = 020, Shiny = Shiny.Never }, // Bewear @ Hau’oli City (Shopping District) - new(USUM) { Species = 097, Level = 29, Location = 020, Shiny = Shiny.Never, Relearn = new[] {095,171,139,029} }, // Hypno @ Hau'oli City Police Station - new(USUM) { Species = 097, Level = 29, Location = 020, Shiny = Shiny.Never, Relearn = new[] {417,060,050,139} }, // Hypno @ Hau'oli City Police Station - new(USUM) { Species = 097, Level = 29, Location = 020, Shiny = Shiny.Never, Relearn = new[] {093,050,001,096} }, // Hypno @ Hau'oli City Police Station - new(USUM) { Species = 092, Level = 19, Location = 230, Shiny = Shiny.Never, Relearn = new[] {174,109,122,101} }, // Gastly @ Route 1 (Trainers’ School) - new(USUM) { Species = 425, Level = 19, Location = 230, Shiny = Shiny.Never, Relearn = new[] {310,132,016,371} }, // Drifloon @ Route 1 (Trainers’ School) - new( UM) { Species = 769, Level = 30, Location = 116, Shiny = Shiny.Never, Relearn = new[] {310,523,072,328} }, // Sandygast @ Route 15 - new(USUM) { Species = 592, Level = 34, Location = 126, Shiny = Shiny.Never, Gender = 1 }, // Frillish @ Route 14 - new(USUM) { Species = 101, Level = 60, Location = 224, Ability = OnlyFirst, Shiny = Shiny.Never }, // Electrode @ Team Rocket's Castle - - // Crabrawler in Berry Piles - new(USUM) { Species = 739, Level = 25, Location = 106 }, // Route 10 - new(USUM) { Species = 739, Level = 28, Location = 110 }, // Ula'ula Beach - new(USUM) { Species = 739, Level = 31, Location = 118 }, // Route 16 - new(USUM) { Species = 739, Level = 32, Location = 120 }, // Route 17 - }; - - internal static readonly EncounterTrade7[] TradeGift_USUM = - { - // Trades - 4.bin - new(USUM) { Species = 701, Form = 0, Level = 08, Ability = OnlySecond, TID = 00410, SID = 00000, IVs = new[] {-1,31,-1,-1,-1,-1}, OTGender = 1, Gender = 0, Nature = Nature.Brave }, // Hawlucha - new(USUM) { Species = 714, Form = 0, Level = 19, Ability = OnlyFirst, TID = 20683, SID = 00009, IVs = new[] {-1,-1,-1,-1,31,-1}, OTGender = 0, Gender = 0, Nature = Nature.Modest }, // Noibat - new(USUM) { Species = 339, Form = 0, Level = 21, Ability = OnlySecond, TID = 01092, SID = 00009, IVs = new[] {31,-1,-1,-1,-1,-1}, OTGender = 0, Gender = 1, Nature = Nature.Naughty }, // Barboach - new(USUM) { Species = 024, Form = 0, Level = 22, Ability = OnlyFirst, TID = 10913, SID = 00000, IVs = new[] {-1,-1,31,-1,-1,-1}, OTGender = 1, Gender = 1, Nature = Nature.Impish }, // Arbok - new(USUM) { Species = 708, Form = 0, Level = 33, Ability = OnlyFirst, TID = 20778, SID = 00009, IVs = new[] {-1,-1,-1,-1,-1,31}, OTGender = 0, Gender = 0, Nature = Nature.Calm, EvolveOnTrade = true }, // Phantump - new(USUM) { Species = 422, Form = 0, Level = 44, Ability = OnlySecond, TID = 20679, SID = 00009, IVs = new[] {-1,-1,31,-1,-1,-1}, OTGender = 1, Gender = 1, Nature = Nature.Quiet }, // Shellos - new(USUM) { Species = 128, Form = 0, Level = 59, Ability = OnlyFirst, TID = 56734, SID = 00008, IVs = new[] {-1,-1,-1,31,-1,-1}, OTGender = 0, Gender = 0, Nature = Nature.Jolly }, // Tauros - }; - - private const string tradeSM = "tradesm"; - private const string tradeUSUM = "tradeusum"; - private static readonly string[][] TradeSM = Util.GetLanguageStrings10(tradeSM); - private static readonly string[][] TradeUSUM = Util.GetLanguageStrings10(tradeUSUM); - - internal static readonly EncounterStatic7[] StaticSN = GetEncounters(Encounter_SM, SN); - internal static readonly EncounterStatic7[] StaticMN = GetEncounters(Encounter_SM, MN); - internal static readonly EncounterStatic7[] StaticUS = GetEncounters(Encounter_USUM, US); - internal static readonly EncounterStatic7[] StaticUM = GetEncounters(Encounter_USUM, UM); + MarkEncounterTradeStrings(TradeGift_SM, TradeSM); + MarkEncounterTradeStrings(TradeGift_USUM, TradeUSUM); } + + private static readonly EncounterStatic7[] Encounter_SM = // @ a\1\5\5 + { + // Gifts - 0.bin + new(SM) { Gift = true, Species = 722, Level = 5, Location = 024 }, // Rowlet + new(SM) { Gift = true, Species = 725, Level = 5, Location = 024 }, // Litten + new(SM) { Gift = true, Species = 728, Level = 5, Location = 024 }, // Popplio + new(SM) { Gift = true, Species = 138, Level = 15, Location = 058 }, // Omanyte + new(SM) { Gift = true, Species = 140, Level = 15, Location = 058 }, // Kabuto + // new(SM) { Gift = true, Species = 142, Level = 15, Location = 058 }, // Aerodactyl + new(SM) { Gift = true, Species = 345, Level = 15, Location = 058 }, // Lileep + new(SM) { Gift = true, Species = 347, Level = 15, Location = 058 }, // Anorith + new(SM) { Gift = true, Species = 408, Level = 15, Location = 058 }, // Cranidos + new(SM) { Gift = true, Species = 410, Level = 15, Location = 058 }, // Shieldon + new(SM) { Gift = true, Species = 564, Level = 15, Location = 058 }, // Tirtouga + new(SM) { Gift = true, Species = 566, Level = 15, Location = 058 }, // Archen + new(SM) { Gift = true, Species = 696, Level = 15, Location = 058 }, // Tyrunt + new(SM) { Gift = true, Species = 698, Level = 15, Location = 058 }, // Amaura + new(SM) { Gift = true, Species = 137, Level = 30, Location = 116 }, // Porygon @ Route 15 + new(SM) { Gift = true, Species = 133, Level = 1, EggLocation = 60002 }, // Eevee @ Nursery helpers + new(SM) { Gift = true, Species = 772, Level = 40, Location = 188, FlawlessIVCount = 3 }, // Type: Null + new(SN) { Gift = true, Species = 789, Level = 5, Location = 142, Shiny = Shiny.Never, Ability = OnlySecond, FlawlessIVCount = 3 }, // Cosmog + new(MN) { Gift = true, Species = 789, Level = 5, Location = 144, Shiny = Shiny.Never, Ability = OnlySecond, FlawlessIVCount = 3 }, // Cosmog + new(SM) { Gift = true, Species = 142, Level = 40, Location = 172 }, // Aerodactyl @ Seafolk Village + + new(SM) { Gift = true, Species = 718, Form = 0, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { Gift = true, Species = 718, Form = 1, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { Gift = true, Species = 718, Form = 2, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { Gift = true, Species = 718, Form = 3, Level = 30, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + + new(SM) { Gift = true, Species = 718, Form = 0, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { Gift = true, Species = 718, Form = 1, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { Gift = true, Species = 718, Form = 2, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + new(SM) { Gift = true, Species = 718, Form = 3, Level = 50, Location = 118, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde + + new(SM) // Magearna (Bottle Cap) 00 FF + { + Gift = true, Species = 801, Level = 50, Location = 40001, Shiny = Shiny.Never, FlawlessIVCount = 3, HeldItem = 795, Ability = OnlySecond, + Fateful = true, Relearn = new [] {705, 430, 381, 270}, Ball = 0x10, // Cherish + }, + + // Static Encounters - 1.bin + new(SM) { Species = 746, Level = 17, Location = 086, Shiny = Shiny.Never, Ability = OnlyFirst }, // Wishiwashi + new(SM) { Species = 746, Level = 18, Location = 086, Shiny = Shiny.Never, Ability = OnlyFirst }, // Wishiwashi + + new(SN) { Species = 791, Level = 55, Location = 176, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[]{713, 322, 242, 428} }, // Solgaleo + new(MN) { Species = 792, Level = 55, Location = 178, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[]{714, 322, 539, 247} }, // Lunala + + new(SM) { Species = 785, Level = 60, Location = 030, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Koko + new(SM) { Species = 786, Level = 60, Location = 092, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Lele + new(SM) { Species = 787, Level = 60, Location = 140, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Bulu + new(SM) { Species = 788, Level = 60, Location = 180, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Tapu Fini + + new(SM) { Species = 793, Level = 55, Location = 082, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Nihilego @ Wela Volcano Park + new(SM) { Species = 793, Level = 55, Location = 100, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Nihilego @ Diglett’s Tunnel + new(SN) { Species = 794, Level = 65, Location = 040, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Buzzwole @ Melemele Meadow + new(MN) { Species = 795, Level = 60, Location = 046, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Pheromosa @ Verdant Cavern (Trial Site) + new(SM) { Species = 796, Level = 65, Location = 090, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Xurkitree @ Lush Jungle + new(SM) { Species = 796, Level = 65, Location = 076, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Xurkitree @ Memorial Hill + new(SN) { Species = 798, Level = 60, Location = 134, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Malie Garden + new(SN) { Species = 798, Level = 60, Location = 120, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Route 17 + new(MN) { Species = 797, Level = 65, Location = 124, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Haina Desert + new(MN) { Species = 797, Level = 65, Location = 134, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Malie Garden + new(SM) { Species = 799, Level = 70, Location = 182, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Guzzlord @ Resolution Cave + new(SM) { Species = 800, Level = 75, Location = 036, Shiny = Shiny.Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Necrozma @ Ten Carat Hill (Farthest Hollow) + + // QR Scan: Su/M/Tu/W/Th/F/Sa + // Melemele Island + new(SM) { Species = 155, Level = 12, Location = 010, Relearn = new[]{024, 052, 108, 043} }, // Cyndaquil @ Route 3 + new(SM) { Species = 158, Level = 12, Location = 042, Relearn = new[]{232, 099, 055, 043} }, // Totodile @ Seaward Cave + new(SM) { Species = 633, Level = 13, Location = 034, Relearn = new[]{372, 029, 044, 116} }, // Deino @ Ten Carat Hill + new(SM) { Species = 116, Level = 18, Location = 014, Relearn = new[]{225, 239, 055, 043} }, // Horsea @ Kala'e Bay + new(SM) { Species = 599, Level = 08, Location = 020, Relearn = new[]{268, 011, 000, 000} }, // Klink @ Hau'oli City + new(SM) { Species = 152, Level = 10, Location = 012, Relearn = new[]{073, 077, 075, 045} }, // Chikorita @ Route 2 + new(SM) { Species = 607, Level = 10, Location = 038, Relearn = new[]{051, 109, 083, 123} }, // Litwick @ Hau'oli Cemetery + + // Akala Island + new(SM) { Species = 574, Level = 17, Location = 054, Relearn = new[]{399, 060, 003, 313} }, // Gothita @ Route 6 + new(SM) { Species = 363, Level = 19, Location = 056, Relearn = new[]{392, 362, 301, 227} }, // Spheal @ Route 7 + new(SM) { Species = 404, Level = 20, Location = 058, Relearn = new[]{598, 044, 209, 268} }, // Luxio @ Route 8 + new(SM) { Species = 679, Level = 23, Location = 094, Relearn = new[]{194, 332, 425, 475} }, // Honedge @ Akala Outskirts + new(SM) { Species = 543, Level = 14, Location = 050, Relearn = new[]{390, 228, 103, 040} }, // Venipede @ Route 4 + new(SM) { Species = 069, Level = 16, Location = 052, Relearn = new[]{491, 077, 079, 035} }, // Bellsprout @ Route 5 + new(SM) { Species = 183, Level = 17, Location = 086, Relearn = new[]{453, 270, 061, 205} }, // Marill @ Brooklet Hill + + // Ula'ula Island + new(SM) { Species = 111, Level = 30, Location = 138, Relearn = new[]{130, 350, 498, 523} }, // Rhyhorn @ Blush Mountain + new(SM) { Species = 220, Level = 31, Location = 114, Relearn = new[]{573, 036, 420, 196} }, // Swinub @ Tapu Village + new(SM) { Species = 578, Level = 33, Location = 118, Relearn = new[]{101, 248, 283, 473} }, // Duosion @ Route 16 + new(SM) { Species = 315, Level = 34, Location = 128, Relearn = new[]{437, 275, 230, 390} }, // Roselia @ Ula'ula Meadow + new(SM) { Species = 397, Level = 27, Location = 106, Relearn = new[]{355, 018, 283, 104} }, // Staravia @ Route 10 + new(SM) { Species = 288, Level = 27, Location = 108, Relearn = new[]{359, 498, 163, 203} }, // Vigoroth @ Route 11 + new(SM) { Species = 610, Level = 28, Location = 136, Relearn = new[]{231, 337, 206, 163} }, // Axew @ Mount Hokulani + + // Poni Island + new(SM) { Species = 604, Level = 55, Location = 164, Relearn = new[]{435, 051, 029, 306} }, // Eelektross @ Poni Grove + new(SM) { Species = 534, Level = 57, Location = 166, Relearn = new[]{409, 276, 264, 444} }, // Conkeldurr @ Poni Plains + new(SM) { Species = 468, Level = 59, Location = 170, Relearn = new[]{248, 403, 396, 245} }, // Togekiss @ Poni Gauntlet + new(SM) { Species = 542, Level = 57, Location = 156, Relearn = new[]{382, 437, 014, 494} }, // Leavanny @ Poni Meadow + new(SM) { Species = 497, Level = 43, Location = 184, Relearn = new[]{137, 489, 348, 021} }, // Serperior @ Exeggutor Island + new(SM) { Species = 503, Level = 43, Location = 158, Relearn = new[]{362, 227, 453, 279} }, // Samurott @ Poni Wilds + new(SM) { Species = 500, Level = 43, Location = 160, Relearn = new[]{276, 053, 372, 535} }, // Emboar @ Ancient Poni Path + }; + + internal static readonly EncounterTrade7[] TradeGift_SM = // @ a\1\5\5 + { + // Trades - 4.bin + new(SM) { Species = 066, Form = 0, Level = 09, Ability = OnlySecond, TID = 00410, SID = 00000, IVs = new[] {-1,31,-1,-1,-1,-1}, OTGender = 1, Gender = 0, Nature = Nature.Brave }, // Machop + new(SM) { Species = 761, Form = 0, Level = 16, Ability = OnlyFirst, TID = 20683, SID = 00009, IVs = new[] {-1,31,-1,-1,-1,-1}, OTGender = 0, Gender = 1, Nature = Nature.Adamant }, // Bounsweet + new(SM) { Species = 061, Form = 0, Level = 22, Ability = OnlySecond, TID = 01092, SID = 00009, IVs = new[] {31,-1,-1,-1,-1,-1}, OTGender = 1, Gender = 1, Nature = Nature.Naughty }, // Poliwhirl + new(SM) { Species = 440, Form = 0, Level = 27, Ability = OnlySecond, TID = 10913, SID = 00000, IVs = new[] {-1,-1,-1,-1,31,-1}, OTGender = 1, Gender = 1, Nature = Nature.Calm }, // Happiny + new(SM) { Species = 075, Form = 1, Level = 32, Ability = OnlyFirst, TID = 20778, SID = 00009, IVs = new[] {-1,-1,31,-1,-1,-1}, OTGender = 0, Gender = 0, Nature = Nature.Impish, EvolveOnTrade = true }, // Graveler-1 + new(SM) { Species = 762, Form = 0, Level = 43, Ability = OnlyFirst, TID = 20679, SID = 00009, IVs = new[] {-1,-1,-1,-1,-1,31}, OTGender = 1, Gender = 1, Nature = Nature.Careful }, // Steenee + new(SM) { Species = 663, Form = 0, Level = 59, Ability = OnlyHidden, TID = 56734, SID = 00008, IVs = new[] {-1,-1,-1,31,-1,-1}, OTGender = 0, Gender = 0, Nature = Nature.Jolly }, // Talonflame + }; + + private static readonly EncounterStatic7[] Encounter_USUM = + { + new(USUM) { Gift = true, Species = 722, Level = 05, Location = 008 }, // Rowlet + new(USUM) { Gift = true, Species = 725, Level = 05, Location = 008 }, // Litten + new(USUM) { Gift = true, Species = 728, Level = 05, Location = 008 }, // Popplio + new(USUM) { Gift = true, Species = 138, Level = 15, Location = 058 }, // Omanyte + new(USUM) { Gift = true, Species = 140, Level = 15, Location = 058 }, // Kabuto + // new(USUM) { Gift = true, Species = 142, Level = 15, Location = 058 }, // Aerodactyl + new(USUM) { Gift = true, Species = 345, Level = 15, Location = 058 }, // Lileep + new(USUM) { Gift = true, Species = 347, Level = 15, Location = 058 }, // Anorith + new(USUM) { Gift = true, Species = 408, Level = 15, Location = 058 }, // Cranidos + new(USUM) { Gift = true, Species = 410, Level = 15, Location = 058 }, // Shieldon + new(USUM) { Gift = true, Species = 564, Level = 15, Location = 058 }, // Tirtouga + new(USUM) { Gift = true, Species = 566, Level = 15, Location = 058 }, // Archen + new(USUM) { Gift = true, Species = 696, Level = 15, Location = 058 }, // Tyrunt + new(USUM) { Gift = true, Species = 698, Level = 15, Location = 058 }, // Amaura + new(USUM) { Gift = true, Species = 133, Level = 01, EggLocation = 60002 }, // Eevee @ Nursery helpers + new(USUM) { Gift = true, Species = 137, Level = 30, Location = 116 }, // Porygon @ Route 15 + new(USUM) { Gift = true, Species = 772, Level = 60, Location = 188, FlawlessIVCount = 3 }, // Type: Null @ Aether Paradise + new(USUM) { Gift = true, Species = 772, Level = 60, Location = 160, FlawlessIVCount = 3 }, // Type: Null @ Ancient Poni Path + new(US ) { Gift = true, Species = 789, Level = 05, Location = 142, FlawlessIVCount = 3, Shiny = Shiny.Never, Ability = OnlySecond }, // Cosmog @ Lake of the Sunne + new( UM) { Gift = true, Species = 789, Level = 05, Location = 144, FlawlessIVCount = 3, Shiny = Shiny.Never, Ability = OnlySecond }, // Cosmog @ Lake of the Moone + new(USUM) { Gift = true, Species = 142, Level = 40, Location = 172 }, // Aerodactyl @ Seafolk Village + new(USUM) { Gift = true, Species = 025, Level = 40, Location = 070, FlawlessIVCount = 3, HeldItem = 796, Relearn = new[] {57,0,0,0} }, // Pikachu @ Heahea City + new(USUM) { Gift = true, Species = 803, Level = 40, Location = 208, FlawlessIVCount = 3 }, // Poipole @ Megalo Tower + new(USUM) { Gift = true, Species = 803, Level = 40, Location = 206, FlawlessIVCount = 3 }, // Poipole @ Ultra Megalopolis + + // Totem-Sized Gifts @ Heahea Beach + new(US ) { Gift = true, Species = 735, Level = 20, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Gumshoos + new( UM) { Gift = true, Species = 020, Level = 20, Ability = OnlyHidden, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Raticate + new(US ) { Gift = true, Species = 105, Level = 25, Ability = OnlyHidden, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Marowak + new( UM) { Gift = true, Species = 752, Level = 25, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Araquanid + new(US ) { Gift = true, Species = 754, Level = 30, Ability = OnlySecond, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Lurantis + new( UM) { Gift = true, Species = 758, Level = 30, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Salazzle + new(US ) { Gift = true, Species = 738, Level = 35, Ability = OnlyFirst, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Vikavolt + new( UM) { Gift = true, Species = 777, Level = 35, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Togedemaru + new(USUM) { Gift = true, Species = 778, Level = 40, Ability = OnlyFirst, Location = 202, Form = 2, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Mimikyu + new(US ) { Gift = true, Species = 743, Level = 50, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Ribombee + new( UM) { Gift = true, Species = 784, Level = 50, Ability = OnlyHidden, Location = 202, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Kommo-o + + new(USUM) { Gift = true, Species = 718, Level = 63, Ability = OnlyFirst, Location = 118, Form = 1, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Zygarde (10%) @ Route 16 + + new(USUM) // Magearna (Bottle Cap) + { + Gift = true, Species = 801, Level = 50, Location = 40001, Shiny = Shiny.Never, FlawlessIVCount = 3, HeldItem = 795, Ability = OnlySecond, + Fateful = true, Relearn = new [] {705, 430, 381, 270}, Ball = 0x10, // Cherish + }, + + new(USUM) { Gift = true, Species = 718, Form = 0, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (50%) + new(USUM) { Gift = true, Species = 718, Form = 1, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (10%) + new(USUM) { Gift = true, Species = 718, Form = 2, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (10%-C) + new(USUM) { Gift = true, Species = 718, Form = 3, Level = 50, Shiny = Shiny.Never, Location = 118, FlawlessIVCount = 3 }, // Zygarde (50%-C) + + new(US ) { Species = 791, Level = 60, Location = 028, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new[] {713,322,242,428} }, // Solgaleo @ Mahalo Trail (Plank Bridge) + new( UM) { Species = 792, Level = 60, Location = 028, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new[] {714,322,539,585} }, // Lunala @ Mahalo Trail (Plank Bridge) + + // QR Scan: Su/M/Tu/W/Th/F/Sa + // Melemele Island + new(USUM) { Species = 004, Level = 12, Location = 010, Relearn = new[] {068,108,052,010} }, // Charmander @ Route 3 + new(USUM) { Species = 007, Level = 12, Location = 042, Relearn = new[] {453,110,055,033} }, // Squirtle @ Seaward Cave + new(USUM) { Species = 095, Level = 14, Location = 034, Relearn = new[] {563,099,317,088} }, // Onix @ Ten Carat Hill + new(USUM) { Species = 116, Level = 18, Location = 014, Relearn = new[] {352,239,055,043} }, // Horsea @ Kala'e Bay + new(USUM) { Species = 664, Level = 09, Location = 020, Relearn = new[] {476,081,078,033}, Form = EncounterStatic.FormVivillon }, // Scatterbug @ Hau'oli City + new(USUM) { Species = 001, Level = 10, Location = 012, Relearn = new[] {580,022,045,033} }, // Bulbasaur @ Route 2 + new(USUM) { Species = 607, Level = 09, Location = 038, Relearn = new[] {203,052,083,123} }, // Litwick @ Hau'oli Cemetery + + // Akala Island + new(USUM) { Species = 280, Level = 17, Location = 054, Relearn = new[] {581,345,381,574} }, // Ralts @ Route 6 + new(USUM) { Species = 363, Level = 19, Location = 056, Relearn = new[] {187,362,301,227} }, // Spheal @ Route 7 + new(USUM) { Species = 256, Level = 20, Location = 058, Relearn = new[] {067,488,064,028} }, // Combusken @ Route 8 + new(USUM) { Species = 679, Level = 24, Location = 094, Relearn = new[] {469,332,425,475} }, // Honedge @ Akala Outskirts + new(USUM) { Species = 015, Level = 14, Location = 050, Relearn = new[] {099,031,041,000} }, // Beedrill @ Route 4 + new(USUM) { Species = 253, Level = 16, Location = 052, Relearn = new[] {580,072,098,071} }, // Grovyle @ Route 5 + new(USUM) { Species = 259, Level = 17, Location = 086, Relearn = new[] {068,193,189,055} }, // Marshtomp @ Brooklet Hill + + // Ula'ula Island + new(USUM) { Species = 111, Level = 32, Location = 138, Relearn = new[] {470,350,498,523} }, // Rhyhorn @ Blush Mountain + new(USUM) { Species = 220, Level = 33, Location = 114, Relearn = new[] {333,036,420,196} }, // Swinub @ Tapu Village + new(USUM) { Species = 394, Level = 35, Location = 118, Relearn = new[] {681,362,031,117} }, // Prinplup @ Route 16 + new(USUM) { Species = 388, Level = 36, Location = 128, Relearn = new[] {484,073,072,044} }, // Grotle @ Ula'ula Meadow + new(USUM) { Species = 018, Level = 29, Location = 106, Relearn = new[] {211,297,239,098} }, // Pidgeot @ Route 10 + new(USUM) { Species = 391, Level = 29, Location = 108, Relearn = new[] {612,172,154,259} }, // Monferno @ Route 11 + new(USUM) { Species = 610, Level = 30, Location = 136, Relearn = new[] {068,337,206,163} }, // Axew @ Mount Hokulani + + // Poni Island + new(USUM) { Species = 604, Level = 55, Location = 164, Relearn = new[] {242,435,029,306} }, // Eelektross @ Poni Grove + new(USUM) { Species = 306, Level = 57, Location = 166, Relearn = new[] {179,484,038,334} }, // Aggron @ Poni Plains + new(USUM) { Species = 479, Level = 61, Location = 170, Relearn = new[] {268,506,486,164} }, // Rotom @ Poni Gauntlet + new(USUM) { Species = 542, Level = 57, Location = 156, Relearn = new[] {580,437,014,494} }, // Leavanny @ Poni Meadow + new(USUM) { Species = 652, Level = 45, Location = 184, Relearn = new[] {191,341,402,596} }, // Chesnaught @ Exeggutor Island + new(USUM) { Species = 658, Level = 44, Location = 158, Relearn = new[] {516,164,185,594} }, // Greninja @ Poni Wilds + new(USUM) { Species = 655, Level = 44, Location = 160, Relearn = new[] {273,473,113,595} }, // Delphox @ Ancient Poni Path + + new(USUM) { Species = 785, Level = 60, Location = 030, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Tapu Koko @ Ruins of Conflict + new(USUM) { Species = 786, Level = 60, Location = 092, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Tapu Lele @ Ruins of Life + new(USUM) { Species = 787, Level = 60, Location = 140, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Tapu Bulu @ Ruins of Abundance + new(USUM) { Species = 788, Level = 60, Location = 180, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3 }, // Tapu Fini @ Ruins of Hope + + new(USUM) { Species = 023, Level = 10, Location = 012, Ability = OnlyFirst }, // Ekans @ Route 2 + + new(USUM) { Species = 127, Level = 42, Location = 184, Shiny = Shiny.Never }, // Pinsir @ Exeggutor Island + new(USUM) { Species = 127, Level = 43, Location = 184, Shiny = Shiny.Never }, // Pinsir @ Exeggutor Island + new(USUM) { Species = 800, Level = 65, Location = 146, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new[] {722,334,408,400}, HeldItem = 923 }, // Necrozma @ Mount Lanakila + + // Legendaries + new(USUM) { Species = 144, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {246,573,115,258} }, // Articuno + new(USUM) { Species = 145, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {246,435,365,240} }, // Zapdos + new(USUM) { Species = 146, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {246,053,403,241} }, // Moltres + new(USUM) { Species = 150, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {094,105,129,427} }, // Mewtwo + new(US ) { Species = 243, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Raikou + new( UM) { Species = 244, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {023,044,207,436} }, // Entei + new(USUM) { Species = 245, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {061,062,054,240} }, // Suicune + new( UM) { Species = 249, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {285,177,326,246} }, // Lugia + new(US ) { Species = 250, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {682,221,326,246}, HeldItem = 044 }, // Ho-Oh + new(USUM) { Species = 377, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Regirock + new(USUM) { Species = 378, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Regice + new(USUM) { Species = 379, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Registeel + new( UM) { Species = 380, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {296,406,375,273}, Gender = 1 }, // Latias + new(US ) { Species = 381, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {295,406,375,225}, Gender = 0 }, // Latios + new( UM) { Species = 382, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {058,618,347,330} }, // Kyogre + new(US ) { Species = 383, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {089,619,339,076} }, // Groudon + new(USUM) { Species = 384, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Rayquaza + new(USUM) { Species = 480, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {326,281,133,129} }, // Uxie + new(USUM) { Species = 481, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {326,204,248,129} }, // Mesprit + new(USUM) { Species = 482, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {326,417,253,129} }, // Azelf + new(US ) { Species = 483, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Dialga + new( UM) { Species = 484, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Palkia + new(US ) { Species = 485, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Heatran + new( UM) { Species = 486, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {428,279,146,109} }, // Regigigas + new(USUM) { Species = 487, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {467,396,414,337} }, // Giratina + new(USUM) { Species = 488, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 1 }, // Cresselia + new(USUM) { Species = 638, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {533,014,098,442} }, // Cobalion + new(USUM) { Species = 639, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {533,014,157,444} }, // Terrakion + new(USUM) { Species = 640, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {533,014,202,348} }, // Virizion + new(US ) { Species = 641, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Tornadus + new( UM) { Species = 642, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Thundurus + new(US ) { Species = 643, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Reshiram + new( UM) { Species = 644, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Zekrom + new(USUM) { Species = 645, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Gender = 0 }, // Landorus + new(USUM) { Species = 646, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kyurem + new(US ) { Species = 716, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {601,532,400,585} }, // Xerneas + new( UM) { Species = 717, Level = 60, Location = 222, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {613,399,566,094} }, // Yveltal + new(USUM) { Species = 718, Level = 60, Location = 182, Ability = OnlyFirst, Shiny = Shiny.Never, FlawlessIVCount = 3, Relearn = new[] {616,137,219,225} }, // Zygarde @ Resolution Cave + + // Ultra Space Wilds + new(USUM) { Species = 334, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Altaria + new(USUM) { Species = 469, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Yanmega + new(USUM) { Species = 561, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Sigilyph + new(USUM) { Species = 581, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Swanna + new(USUM) { Species = 277, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Swellow + new(USUM) { Species = 452, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Drapion + new(USUM) { Species = 531, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Audino + new(USUM) { Species = 695, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Heliolisk + new(USUM) { Species = 274, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Nuzleaf + new(USUM) { Species = 326, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Grumpig + new(USUM) { Species = 460, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Abomasnow + new(USUM) { Species = 308, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Medicham + new(USUM) { Species = 450, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Hippowdon + new(USUM) { Species = 558, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Crustle + new(USUM) { Species = 219, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Magcargo + new(USUM) { Species = 689, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Barbaracle + new(USUM) { Species = 271, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Lombre + new(USUM) { Species = 618, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Stunfisk + new(USUM) { Species = 419, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Floatzel + new(USUM) { Species = 195, Level = 60, Location = 222, FlawlessIVCount = 3 }, // Quagsire + + // Ultra Beasts + new(USUM) { Species = 793, Level = 60, Location = 190, Ability = OnlyFirst, FlawlessIVCount = 3, Relearn = new[] {408,491,446,243} }, // Nihilego @ Ultra Deep Sea + new(US ) { Species = 794, Level = 60, Location = 218, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Buzzwole @ Ultra Jungle + new( UM) { Species = 795, Level = 60, Location = 214, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Pheromosa @ Ultra Desert + new(USUM) { Species = 796, Level = 60, Location = 210, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Xurkitree @ Ultra Plant + new( UM) { Species = 797, Level = 60, Location = 212, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Celesteela @ Ultra Crater + new(US ) { Species = 798, Level = 60, Location = 216, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Kartana @ Ultra Forest + new(USUM) { Species = 799, Level = 60, Location = 220, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Guzzlord @ Ultra Ruin + new( UM) { Species = 805, Level = 60, Location = 164, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Stakataka @ Poni Grove + new(US ) { Species = 806, Level = 60, Location = 164, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Blacephalon @ Poni Grove + + // Ditto Five + new(USUM) { Species = 132, Level = 29, Location = 060, IVs = new[] {-1,-1,31,00,30,-1}, Nature = Nature.Bold }, // Ditto @ Route 9 + new(USUM) { Species = 132, Level = 29, Location = 072, IVs = new[] {-1,-1,30,31,30,-1}, Nature = Nature.Jolly }, // Ditto @ Konikoni City + new(USUM) { Species = 132, Level = 29, Location = 072, IVs = new[] {-1,31,30,30,-1,-1}, Nature = Nature.Adamant }, // Ditto @ Konikoni City + new(USUM) { Species = 132, Level = 29, Location = 072, IVs = new[] {-1,00,-1,-1,31,30}, Nature = Nature.Modest }, // Ditto @ Konikoni City + new(USUM) { Species = 132, Level = 29, Location = 072, IVs = new[] {-1,30,-1,31,-1,30}, Nature = Nature.Timid }, // Ditto @ Konikoni City + + // Miscellaneous Static + new(USUM) { Species = 760, Level = 28, Location = 020, Shiny = Shiny.Never }, // Bewear @ Hau’oli City (Shopping District) + new(USUM) { Species = 097, Level = 29, Location = 020, Shiny = Shiny.Never, Relearn = new[] {095,171,139,029} }, // Hypno @ Hau'oli City Police Station + new(USUM) { Species = 097, Level = 29, Location = 020, Shiny = Shiny.Never, Relearn = new[] {417,060,050,139} }, // Hypno @ Hau'oli City Police Station + new(USUM) { Species = 097, Level = 29, Location = 020, Shiny = Shiny.Never, Relearn = new[] {093,050,001,096} }, // Hypno @ Hau'oli City Police Station + new(USUM) { Species = 092, Level = 19, Location = 230, Shiny = Shiny.Never, Relearn = new[] {174,109,122,101} }, // Gastly @ Route 1 (Trainers’ School) + new(USUM) { Species = 425, Level = 19, Location = 230, Shiny = Shiny.Never, Relearn = new[] {310,132,016,371} }, // Drifloon @ Route 1 (Trainers’ School) + new( UM) { Species = 769, Level = 30, Location = 116, Shiny = Shiny.Never, Relearn = new[] {310,523,072,328} }, // Sandygast @ Route 15 + new(USUM) { Species = 592, Level = 34, Location = 126, Shiny = Shiny.Never, Gender = 1 }, // Frillish @ Route 14 + new(USUM) { Species = 101, Level = 60, Location = 224, Ability = OnlyFirst, Shiny = Shiny.Never }, // Electrode @ Team Rocket's Castle + + // Crabrawler in Berry Piles + new(USUM) { Species = 739, Level = 25, Location = 106 }, // Route 10 + new(USUM) { Species = 739, Level = 28, Location = 110 }, // Ula'ula Beach + new(USUM) { Species = 739, Level = 31, Location = 118 }, // Route 16 + new(USUM) { Species = 739, Level = 32, Location = 120 }, // Route 17 + }; + + internal static readonly EncounterTrade7[] TradeGift_USUM = + { + // Trades - 4.bin + new(USUM) { Species = 701, Form = 0, Level = 08, Ability = OnlySecond, TID = 00410, SID = 00000, IVs = new[] {-1,31,-1,-1,-1,-1}, OTGender = 1, Gender = 0, Nature = Nature.Brave }, // Hawlucha + new(USUM) { Species = 714, Form = 0, Level = 19, Ability = OnlyFirst, TID = 20683, SID = 00009, IVs = new[] {-1,-1,-1,-1,31,-1}, OTGender = 0, Gender = 0, Nature = Nature.Modest }, // Noibat + new(USUM) { Species = 339, Form = 0, Level = 21, Ability = OnlySecond, TID = 01092, SID = 00009, IVs = new[] {31,-1,-1,-1,-1,-1}, OTGender = 0, Gender = 1, Nature = Nature.Naughty }, // Barboach + new(USUM) { Species = 024, Form = 0, Level = 22, Ability = OnlyFirst, TID = 10913, SID = 00000, IVs = new[] {-1,-1,31,-1,-1,-1}, OTGender = 1, Gender = 1, Nature = Nature.Impish }, // Arbok + new(USUM) { Species = 708, Form = 0, Level = 33, Ability = OnlyFirst, TID = 20778, SID = 00009, IVs = new[] {-1,-1,-1,-1,-1,31}, OTGender = 0, Gender = 0, Nature = Nature.Calm, EvolveOnTrade = true }, // Phantump + new(USUM) { Species = 422, Form = 0, Level = 44, Ability = OnlySecond, TID = 20679, SID = 00009, IVs = new[] {-1,-1,31,-1,-1,-1}, OTGender = 1, Gender = 1, Nature = Nature.Quiet }, // Shellos + new(USUM) { Species = 128, Form = 0, Level = 59, Ability = OnlyFirst, TID = 56734, SID = 00008, IVs = new[] {-1,-1,-1,31,-1,-1}, OTGender = 0, Gender = 0, Nature = Nature.Jolly }, // Tauros + }; + + private const string tradeSM = "tradesm"; + private const string tradeUSUM = "tradeusum"; + private static readonly string[][] TradeSM = Util.GetLanguageStrings10(tradeSM); + private static readonly string[][] TradeUSUM = Util.GetLanguageStrings10(tradeUSUM); + + internal static readonly EncounterStatic7[] StaticSN = GetEncounters(Encounter_SM, SN); + internal static readonly EncounterStatic7[] StaticMN = GetEncounters(Encounter_SM, MN); + internal static readonly EncounterStatic7[] StaticUS = GetEncounters(Encounter_USUM, US); + internal static readonly EncounterStatic7[] StaticUM = GetEncounters(Encounter_USUM, UM); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters7b.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters7b.cs index 7d3d641bf..b848be9f1 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters7b.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters7b.cs @@ -1,71 +1,70 @@ using static PKHeX.Core.EncounterUtil; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class Encounters7b { - internal static class Encounters7b + internal static readonly EncounterArea7b[] SlotsGP = EncounterArea7b.GetAreas(Get("gp", "gg"), GP); + internal static readonly EncounterArea7b[] SlotsGE = EncounterArea7b.GetAreas(Get("ge", "gg"), GE); + + private static readonly EncounterStatic7b[] Encounter_GG = { - internal static readonly EncounterArea7b[] SlotsGP = EncounterArea7b.GetAreas(Get("gp", "gg"), GP); - internal static readonly EncounterArea7b[] SlotsGE = EncounterArea7b.GetAreas(Get("ge", "gg"), GE); + // encounters + new(GG) { Species = 144, Level = 50, Location = 44, FlawlessIVCount = 3 }, // Articuno @ Seafoam Islands + new(GG) { Species = 145, Level = 50, Location = 42, FlawlessIVCount = 3 }, // Zapdos @ Power Plant + new(GG) { Species = 146, Level = 50, Location = 45, FlawlessIVCount = 3 }, // Moltres @ Victory Road + new(GG) { Species = 150, Level = 70, Location = 46, FlawlessIVCount = 3 }, // Mewtwo @ Cerulean Cave + new(GG) { Species = 143, Level = 34, Location = 14, FlawlessIVCount = 3 }, // Snorlax @ Route 12 + new(GG) { Species = 143, Level = 34, Location = 18, FlawlessIVCount = 3 }, // Snorlax @ Route 16 + // unused new EncounterStatic7b { Species = 100, Level = 42, Location = 42, FlawlessIVCount = 3 }, // Voltorb @ Power Plant + // collision new EncounterStatic7b { Species = 101, Level = 42, Location = 42, FlawlessIVCount = 3 }, // Electrode @ Power Plant - private static readonly EncounterStatic7b[] Encounter_GG = - { - // encounters - new(GG) { Species = 144, Level = 50, Location = 44, FlawlessIVCount = 3 }, // Articuno @ Seafoam Islands - new(GG) { Species = 145, Level = 50, Location = 42, FlawlessIVCount = 3 }, // Zapdos @ Power Plant - new(GG) { Species = 146, Level = 50, Location = 45, FlawlessIVCount = 3 }, // Moltres @ Victory Road - new(GG) { Species = 150, Level = 70, Location = 46, FlawlessIVCount = 3 }, // Mewtwo @ Cerulean Cave - new(GG) { Species = 143, Level = 34, Location = 14, FlawlessIVCount = 3 }, // Snorlax @ Route 12 - new(GG) { Species = 143, Level = 34, Location = 18, FlawlessIVCount = 3 }, // Snorlax @ Route 16 - // unused new EncounterStatic7b { Species = 100, Level = 42, Location = 42, FlawlessIVCount = 3 }, // Voltorb @ Power Plant - // collision new EncounterStatic7b { Species = 101, Level = 42, Location = 42, FlawlessIVCount = 3 }, // Electrode @ Power Plant + // gifts + new(GP) { Species = 025, Level = 05, Location = 28, Gift = true, IVs = new[] {31,31,31,31,31,31}, Shiny = Shiny.Never, Form = 8 }, // Pikachu @ Pallet Town + new(GE) { Species = 133, Level = 05, Location = 28, Gift = true, IVs = new[] {31,31,31,31,31,31}, Shiny = Shiny.Never, Form = 1 }, // Eevee @ Pallet Town - // gifts - new(GP) { Species = 025, Level = 05, Location = 28, Gift = true, IVs = new[] {31,31,31,31,31,31}, Shiny = Shiny.Never, Form = 8 }, // Pikachu @ Pallet Town - new(GE) { Species = 133, Level = 05, Location = 28, Gift = true, IVs = new[] {31,31,31,31,31,31}, Shiny = Shiny.Never, Form = 1 }, // Eevee @ Pallet Town + new(GG) { Species = 129, Level = 05, Location = 06, Gift = true, IVs = new[] {30,31,25,30,25,25} }, // Magikarp @ Route 4 - new(GG) { Species = 129, Level = 05, Location = 06, Gift = true, IVs = new[] {30,31,25,30,25,25} }, // Magikarp @ Route 4 + // unused new EncounterStatic7b { Species = 133, Level = 30, Location = 34, Gift = true }, // Eevee @ Celadon City + new(GG) { Species = 131, Level = 34, Location = 52, Gift = true, IVs = new[] {31,25,25,25,30,30} }, // Lapras @ Saffron City (Silph Co. Employee, inside) + new(GG) { Species = 106, Level = 30, Location = 38, Gift = true, IVs = new[] {25,30,25,31,25,30} }, // Hitmonlee @ Saffron City (Karate Master) + new(GG) { Species = 107, Level = 30, Location = 38, Gift = true, IVs = new[] {25,31,30,25,25,30} }, // Hitmonchan @ Saffron City (Karate Master) + new(GG) { Species = 140, Level = 44, Location = 36, Gift = true, FlawlessIVCount = 3 }, // Kabuto @ Cinnabar Island (Cinnabar Pokémon Lab) + new(GG) { Species = 138, Level = 44, Location = 36, Gift = true, FlawlessIVCount = 3 }, // Omanyte @ Cinnabar Island (Cinnabar Pokémon Lab) + new(GG) { Species = 142, Level = 44, Location = 36, Gift = true, FlawlessIVCount = 3 }, // Aerodactyl @ Cinnabar Island (Cinnabar Pokémon Lab) + new(GG) { Species = 001, Level = 12, Location = 31, Gift = true, IVs = new[] {31,25,30,25,25,30} }, // Bulbasaur @ Cerulean City + new(GG) { Species = 004, Level = 14, Location = 26, Gift = true, IVs = new[] {25,30,25,31,30,25} }, // Charmander @ Route 24 + new(GG) { Species = 007, Level = 16, Location = 33, Gift = true, IVs = new[] {25,25,30,25,31,30} }, // Squirtle @ Vermillion City + new(GG) { Species = 137, Level = 34, Location = 38, Gift = true, IVs = new[] {25,25,30,25,31,30} }, // Porygon @ Saffron City (Silph Co. Employee, outside) + new(GP) { Species = 053, Level = 16, Location = 33, Gift = true, IVs = new[] {30,30,25,31,25,25} }, // Persian @ Vermillion City (Outside Fan Club) + new(GE) { Species = 059, Level = 16, Location = 33, Gift = true, IVs = new[] {25,30,25,31,30,25} }, // Arcanine @ Vermillion City (Outside Fan Club) + }; - // unused new EncounterStatic7b { Species = 133, Level = 30, Location = 34, Gift = true }, // Eevee @ Celadon City - new(GG) { Species = 131, Level = 34, Location = 52, Gift = true, IVs = new[] {31,25,25,25,30,30} }, // Lapras @ Saffron City (Silph Co. Employee, inside) - new(GG) { Species = 106, Level = 30, Location = 38, Gift = true, IVs = new[] {25,30,25,31,25,30} }, // Hitmonlee @ Saffron City (Karate Master) - new(GG) { Species = 107, Level = 30, Location = 38, Gift = true, IVs = new[] {25,31,30,25,25,30} }, // Hitmonchan @ Saffron City (Karate Master) - new(GG) { Species = 140, Level = 44, Location = 36, Gift = true, FlawlessIVCount = 3 }, // Kabuto @ Cinnabar Island (Cinnabar Pokémon Lab) - new(GG) { Species = 138, Level = 44, Location = 36, Gift = true, FlawlessIVCount = 3 }, // Omanyte @ Cinnabar Island (Cinnabar Pokémon Lab) - new(GG) { Species = 142, Level = 44, Location = 36, Gift = true, FlawlessIVCount = 3 }, // Aerodactyl @ Cinnabar Island (Cinnabar Pokémon Lab) - new(GG) { Species = 001, Level = 12, Location = 31, Gift = true, IVs = new[] {31,25,30,25,25,30} }, // Bulbasaur @ Cerulean City - new(GG) { Species = 004, Level = 14, Location = 26, Gift = true, IVs = new[] {25,30,25,31,30,25} }, // Charmander @ Route 24 - new(GG) { Species = 007, Level = 16, Location = 33, Gift = true, IVs = new[] {25,25,30,25,31,30} }, // Squirtle @ Vermillion City - new(GG) { Species = 137, Level = 34, Location = 38, Gift = true, IVs = new[] {25,25,30,25,31,30} }, // Porygon @ Saffron City (Silph Co. Employee, outside) - new(GP) { Species = 053, Level = 16, Location = 33, Gift = true, IVs = new[] {30,30,25,31,25,25} }, // Persian @ Vermillion City (Outside Fan Club) - new(GE) { Species = 059, Level = 16, Location = 33, Gift = true, IVs = new[] {25,30,25,31,30,25} }, // Arcanine @ Vermillion City (Outside Fan Club) - }; + private static readonly string[] T1 = { string.Empty, "ミニコ", "Tatianna", "BarbaRatatta", "Addoloratta", "Barbaratt", string.Empty, "Tatiana", "미니꼬", "小幂妮", "小幂妮" }; + private static readonly string[] T2 = { string.Empty, "ボーアイス", "Nicholice", "Iceman-4L0L4", "Goffreddo", "Eisper", string.Empty, "Gelasio", "보아이스", "露冰冰", "露冰冰" }; + private static readonly string[] T3 = { string.Empty, "レディダグ", "Diggette", "Taupilady", "Lady Glett", "Digga", string.Empty, "Glenda", "레이디그다", "蒂淑", "蒂淑" }; + private static readonly string[] T4 = { string.Empty, "ワルモン", "Darko", "AlolaZeDark", "Mattetro", "Bösbert", string.Empty, "Sinesio", "나뻐기", "达怀丹", "达怀丹" }; + private static readonly string[] T5 = { string.Empty, "エリッチ", "Psytrice", "TopDeTonCœur", "Chulia", "Assana", string.Empty, "Menchu", "엘리츄", "晶莹丘", "晶莹丘" }; + private static readonly string[] T6 = { string.Empty, "ジェンガラ", "Genmar", "OSS-Dandy7", "Mr. Owak", "Knoggelius", string.Empty, "Mario", "젠구리", "申史加拉", "申史加拉" }; + private static readonly string[] T7 = { string.Empty, "マニシ", "Exemann", "Koko-fan", "Exechiele", "Einrich", string.Empty, "Gunter", "마니시", "艾浩舒", "艾浩舒" }; + private static readonly string[] T8 = { string.Empty, "コツブ", "Higeo", "Montagnou", "George", "Karstein", string.Empty, "Georgie", "산돌", "科布", "科布" }; - private static readonly string[] T1 = { string.Empty, "ミニコ", "Tatianna", "BarbaRatatta", "Addoloratta", "Barbaratt", string.Empty, "Tatiana", "미니꼬", "小幂妮", "小幂妮" }; - private static readonly string[] T2 = { string.Empty, "ボーアイス", "Nicholice", "Iceman-4L0L4", "Goffreddo", "Eisper", string.Empty, "Gelasio", "보아이스", "露冰冰", "露冰冰" }; - private static readonly string[] T3 = { string.Empty, "レディダグ", "Diggette", "Taupilady", "Lady Glett", "Digga", string.Empty, "Glenda", "레이디그다", "蒂淑", "蒂淑" }; - private static readonly string[] T4 = { string.Empty, "ワルモン", "Darko", "AlolaZeDark", "Mattetro", "Bösbert", string.Empty, "Sinesio", "나뻐기", "达怀丹", "达怀丹" }; - private static readonly string[] T5 = { string.Empty, "エリッチ", "Psytrice", "TopDeTonCœur", "Chulia", "Assana", string.Empty, "Menchu", "엘리츄", "晶莹丘", "晶莹丘" }; - private static readonly string[] T6 = { string.Empty, "ジェンガラ", "Genmar", "OSS-Dandy7", "Mr. Owak", "Knoggelius", string.Empty, "Mario", "젠구리", "申史加拉", "申史加拉" }; - private static readonly string[] T7 = { string.Empty, "マニシ", "Exemann", "Koko-fan", "Exechiele", "Einrich", string.Empty, "Gunter", "마니시", "艾浩舒", "艾浩舒" }; - private static readonly string[] T8 = { string.Empty, "コツブ", "Higeo", "Montagnou", "George", "Karstein", string.Empty, "Georgie", "산돌", "科布", "科布" }; + internal static readonly EncounterTrade7b[] TradeGift_GG = + { + // Random candy values! They can be zero so no impact on legality even though statistically rare. + new(GG) { Species = 019, Form = 1, Level = 12, TrainerNames = T1, TID7 = 121106, OTGender = 1, IVs = new[] {31,31,-1,-1,-1,-1} }, // Rattata @ Cerulean City, AV rand [0-5) + new(GP) { Species = 027, Form = 1, Level = 27, TrainerNames = T2, TID7 = 703019, OTGender = 0, IVs = new[] {-1,31,31,-1,-1,-1} }, // Sandshrew @ Celadon City, AV rand [0-5) + new(GE) { Species = 037, Form = 1, Level = 27, TrainerNames = T2, TID7 = 703019, OTGender = 0, IVs = new[] {-1,-1,-1,31,31,-1} }, // Vulpix @ Celadon City, AV rand [0-5) + new(GG) { Species = 050, Form = 1, Level = 25, TrainerNames = T3, TID7 = 520159, OTGender = 1, IVs = new[] {-1,31,-1,31,-1,-1} }, // Diglett @ Lavender Town, AV rand [0-5) + new(GE) { Species = 052, Form = 1, Level = 44, TrainerNames = T4, TID7 = 000219, OTGender = 0, IVs = new[] {31,-1,-1,31,-1,-1} }, // Meowth @ Cinnabar Island, AV rand [0-10) + new(GP) { Species = 088, Form = 1, Level = 44, TrainerNames = T4, TID7 = 000219, OTGender = 0, IVs = new[] {31,31,-1,-1,-1,-1} }, // Grimer @ Cinnabar Island, AV rand [0-10) + new(GG) { Species = 026, Form = 1, Level = 30, TrainerNames = T5, TID7 = 940711, OTGender = 1, IVs = new[] {-1,-1,-1,31,31,-1} }, // Raichu @ Saffron City, AV rand [0-10) + new(GG) { Species = 105, Form = 1, Level = 38, TrainerNames = T6, TID7 = 102595, OTGender = 0, IVs = new[] {-1,31,31,-1,-1,-1} }, // Marowak @ Fuchsia City, AV rand [0-10) + new(GG) { Species = 103, Form = 1, Level = 46, TrainerNames = T7, TID7 = 060310, OTGender = 0, IVs = new[] {-1,31,-1,-1,31,-1} }, // Exeggutor @ Indigo Plateau, AV rand [0-15) + new(GG) { Species = 074, Form = 1, Level = 16, TrainerNames = T8, TID7 = 551873, OTGender = 0, IVs = new[] {31,31,-1,-1,-1,-1} }, // Geodude @ Vermilion City, AV rand [0-5) + }; - internal static readonly EncounterTrade7b[] TradeGift_GG = - { - // Random candy values! They can be zero so no impact on legality even though statistically rare. - new(GG) { Species = 019, Form = 1, Level = 12, TrainerNames = T1, TID7 = 121106, OTGender = 1, IVs = new[] {31,31,-1,-1,-1,-1} }, // Rattata @ Cerulean City, AV rand [0-5) - new(GP) { Species = 027, Form = 1, Level = 27, TrainerNames = T2, TID7 = 703019, OTGender = 0, IVs = new[] {-1,31,31,-1,-1,-1} }, // Sandshrew @ Celadon City, AV rand [0-5) - new(GE) { Species = 037, Form = 1, Level = 27, TrainerNames = T2, TID7 = 703019, OTGender = 0, IVs = new[] {-1,-1,-1,31,31,-1} }, // Vulpix @ Celadon City, AV rand [0-5) - new(GG) { Species = 050, Form = 1, Level = 25, TrainerNames = T3, TID7 = 520159, OTGender = 1, IVs = new[] {-1,31,-1,31,-1,-1} }, // Diglett @ Lavender Town, AV rand [0-5) - new(GE) { Species = 052, Form = 1, Level = 44, TrainerNames = T4, TID7 = 000219, OTGender = 0, IVs = new[] {31,-1,-1,31,-1,-1} }, // Meowth @ Cinnabar Island, AV rand [0-10) - new(GP) { Species = 088, Form = 1, Level = 44, TrainerNames = T4, TID7 = 000219, OTGender = 0, IVs = new[] {31,31,-1,-1,-1,-1} }, // Grimer @ Cinnabar Island, AV rand [0-10) - new(GG) { Species = 026, Form = 1, Level = 30, TrainerNames = T5, TID7 = 940711, OTGender = 1, IVs = new[] {-1,-1,-1,31,31,-1} }, // Raichu @ Saffron City, AV rand [0-10) - new(GG) { Species = 105, Form = 1, Level = 38, TrainerNames = T6, TID7 = 102595, OTGender = 0, IVs = new[] {-1,31,31,-1,-1,-1} }, // Marowak @ Fuchsia City, AV rand [0-10) - new(GG) { Species = 103, Form = 1, Level = 46, TrainerNames = T7, TID7 = 060310, OTGender = 0, IVs = new[] {-1,31,-1,-1,31,-1} }, // Exeggutor @ Indigo Plateau, AV rand [0-15) - new(GG) { Species = 074, Form = 1, Level = 16, TrainerNames = T8, TID7 = 551873, OTGender = 0, IVs = new[] {31,31,-1,-1,-1,-1} }, // Geodude @ Vermilion City, AV rand [0-5) - }; - - internal static readonly EncounterStatic7b[] StaticGP = GetEncounters(Encounter_GG, GP); - internal static readonly EncounterStatic7b[] StaticGE = GetEncounters(Encounter_GG, GE); - } + internal static readonly EncounterStatic7b[] StaticGP = GetEncounters(Encounter_GG, GP); + internal static readonly EncounterStatic7b[] StaticGE = GetEncounters(Encounter_GG, GE); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters8.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters8.cs index d9ae0d06b..8585bf234 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters8.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters8.cs @@ -6,798 +6,797 @@ using static PKHeX.Core.Encounters8Nest; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 Encounters +/// +internal static class Encounters8 { - /// - /// Generation 8 Encounters - /// - internal static class Encounters8 + private static readonly EncounterArea8[] SlotsSW_Symbol = EncounterArea8.GetAreas(Get("sw_symbol", "sw"), SW, true); + private static readonly EncounterArea8[] SlotsSH_Symbol = EncounterArea8.GetAreas(Get("sh_symbol", "sh"), SH, true); + private static readonly EncounterArea8[] SlotsSW_Hidden = EncounterArea8.GetAreas(Get("sw_hidden", "sw"), SW); + private static readonly EncounterArea8[] SlotsSH_Hidden = EncounterArea8.GetAreas(Get("sh_hidden", "sh"), SH); + + internal static readonly EncounterArea8[] SlotsSW = ArrayUtil.ConcatAll(SlotsSW_Symbol, SlotsSW_Hidden); + internal static readonly EncounterArea8[] SlotsSH = ArrayUtil.ConcatAll(SlotsSH_Symbol, SlotsSH_Hidden); + + static Encounters8() { - private static readonly EncounterArea8[] SlotsSW_Symbol = EncounterArea8.GetAreas(Get("sw_symbol", "sw"), SW, true); - private static readonly EncounterArea8[] SlotsSH_Symbol = EncounterArea8.GetAreas(Get("sh_symbol", "sh"), SH, true); - private static readonly EncounterArea8[] SlotsSW_Hidden = EncounterArea8.GetAreas(Get("sw_hidden", "sw"), SW); - private static readonly EncounterArea8[] SlotsSH_Hidden = EncounterArea8.GetAreas(Get("sh_hidden", "sh"), SH); + foreach (var t in TradeGift_R1) + t.TrainerNames = TradeOT_R1; - internal static readonly EncounterArea8[] SlotsSW = ArrayUtil.ConcatAll(SlotsSW_Symbol, SlotsSW_Hidden); - internal static readonly EncounterArea8[] SlotsSH = ArrayUtil.ConcatAll(SlotsSH_Symbol, SlotsSH_Hidden); - - static Encounters8() - { - foreach (var t in TradeGift_R1) - t.TrainerNames = TradeOT_R1; - - MarkEncounterTradeStrings(TradeGift_SWSH, TradeSWSH); - } - - private static readonly EncounterStatic8[] Encounter_SWSH = - { - // gifts - new(SWSH) { Gift = true, Species = 810, Shiny = Never, Level = 05, Location = 006 }, // Grookey - new(SWSH) { Gift = true, Species = 813, Shiny = Never, Level = 05, Location = 006 }, // Scorbunny - new(SWSH) { Gift = true, Species = 816, Shiny = Never, Level = 05, Location = 006 }, // Sobble - - new(SWSH) { Gift = true, Species = 772, Shiny = Never, Level = 50, Location = 158, FlawlessIVCount = 3 }, // Type: Null - new(SWSH) { Gift = true, Species = 848, Shiny = Never, Level = 01, Location = 040, IVs = new[]{-1,31,-1,-1,31,-1}, Ball = 11 }, // Toxel, Attack flawless - - new(SWSH) { Gift = true, Species = 880, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Dracozolt @ Route 6 - new(SWSH) { Gift = true, Species = 881, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Arctozolt @ Route 6 - new(SWSH) { Gift = true, Species = 882, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Dracovish @ Route 6 - new(SWSH) { Gift = true, Species = 883, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Arctovish @ Route 6 - - new(SWSH) { Gift = true, Species = 004, Shiny = Never, Level = 05, Location = 006, FlawlessIVCount = 3, CanGigantamax = true, Ability = OnlyFirst }, // Charmander - new(SWSH) { Gift = true, Species = 025, Shiny = Never, Level = 10, Location = 156, FlawlessIVCount = 6, CanGigantamax = true }, // Pikachu - new(SWSH) { Gift = true, Species = 133, Shiny = Never, Level = 10, Location = 156, FlawlessIVCount = 6, CanGigantamax = true }, // Eevee - - // DLC gifts - new(SWSH) { Gift = true, Species = 001, Level = 05, Location = 196, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, CanGigantamax = true }, // Bulbasaur - new(SWSH) { Gift = true, Species = 007, Level = 05, Location = 196, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, CanGigantamax = true }, // Squirtle - new(SWSH) { Gift = true, Species = 137, Level = 25, Location = 196, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Porygon - new(SWSH) { Gift = true, Species = 891, Level = 10, Location = 196, Shiny = Never, FlawlessIVCount = 3 }, // Kubfu - - new(SWSH) { Gift = true, Species = 079, Level = 10, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Slowpoke - new(SWSH) { Gift = true, Species = 722, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Rowlet - new(SWSH) { Gift = true, Species = 725, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Litten - new(SWSH) { Gift = true, Species = 728, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Popplio - new(SWSH) { Gift = true, Species = 026, Level = 30, Location = 164, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, Form = 01 }, // Raichu-1 - new(SWSH) { Gift = true, Species = 027, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Sandshrew-1 - new(SWSH) { Gift = true, Species = 037, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Vulpix-1 - new(SWSH) { Gift = true, Species = 052, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Meowth-1 - new(SWSH) { Gift = true, Species = 103, Level = 30, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Exeggutor-1 - new(SWSH) { Gift = true, Species = 105, Level = 30, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Marowak-1 - new(SWSH) { Gift = true, Species = 050, Level = 20, Location = 164, Shiny = Never, Ability = OnlyHidden, Gender = 0, Nature = Nature.Jolly, FlawlessIVCount = 6, Form = 01 }, // Diglett-1 - - new(SWSH) { Gift = true, Species = 789, Level = 05, Location = 206, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Cosmog - new(SWSH) { Gift = true, Species = 803, Level = 20, Location = 244, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Ball = 26 }, // Poipole - - // Technically a gift, but copies ball from Calyrex. - new(SWSH) { Species = 896, Level = 75, Location = 220, ScriptedNoMarks = true, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Relearn = new[] {556,0,0,0} }, // Glastrier - new(SWSH) { Species = 897, Level = 75, Location = 220, ScriptedNoMarks = true, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Relearn = new[] {247,0,0,0} }, // Spectrier - - #region Static Part 1 - // encounters - new(SW ) { Species = 888, Level = 70, Location = 66, ScriptedNoMarks = true, Moves = new[] {533,014,442,242}, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Zacian - new( SH) { Species = 889, Level = 70, Location = 66, ScriptedNoMarks = true, Moves = new[] {163,242,442,334}, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Zamazenta - new(SWSH) { Species = 890, Level = 60, Location = 66, ScriptedNoMarks = true, Moves = new[] {440,406,053,744}, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Eternatus-1 (reverts to form 0) - - // Motostoke Stadium Static Encounters - new(SWSH) { Species = 037, Level = 24, Location = 24, ScriptedNoMarks = true }, // Vulpix at Motostoke Stadium - //new( SH) { Species = 058, Level = 24, Location = 24, ScriptedNoMarks = true }, // Growlithe at Motostoke Stadium (both versions have Vulpix) - new(SWSH) { Species = 607, Level = 25, Location = 24, ScriptedNoMarks = true }, // Litwick at Motostoke Stadium - new(SWSH) { Species = 850, Level = 25, Location = 24, ScriptedNoMarks = true, FlawlessIVCount = 3 }, // Sizzlipede at Motostoke Stadium - - new(SWSH) { Species = 618, Level = 25, Location = 054, Moves = new[] {389,319,279,341}, Form = 01, Ability = OnlyFirst }, // Stunfisk in Galar Mine No. 2 - new(SWSH) { Species = 618, Level = 48, Location = 008, Moves = new[] {779,330,340,334}, Form = 01 }, // Stunfisk in the Slumbering Weald - new(SWSH) { Species = 527, Level = 16, Location = 030, Moves = new[] {000,000,000,000} }, // Woobat in Galar Mine - new(SWSH) { Species = 838, Level = 18, Location = 030, Moves = new[] {488,397,229,033} }, // Carkol in Galar Mine - new(SWSH) { Species = 834, Level = 24, Location = 054, Moves = new[] {317,029,055,044} }, // Drednaw in Galar Mine No. 2 - new(SWSH) { Species = 423, Level = 50, Location = 054, Moves = new[] {240,414,330,246}, FlawlessIVCount = 3, Form = 01 }, // Gastrodon in Galar Mine No. 2 - new(SWSH) { Species = 859, Level = 31, Location = 076, ScriptedNoMarks = true, Moves = new[] {259,389,207,372} }, // Impidimp in Glimwood Tangle - new(SWSH) { Species = 860, Level = 38, Location = 076, ScriptedNoMarks = true, Moves = new[] {793,399,259,389} }, // Morgrem in Glimwood Tangle - new(SWSH) { Species = 835, Level = 08, Location = 018, Moves = new[] {039,033,609,000} }, // Yamper on Route 2 - new(SWSH) { Species = 834, Level = 50, Location = 018, Moves = new[] {710,746,068,317}, FlawlessIVCount = 3 }, // Drednaw on Route 2 - new(SWSH) { Species = 833, Level = 08, Location = 018, Moves = new[] {044,055,000,000} }, // Chewtle on Route 2 - new(SWSH) { Species = 131, Level = 55, Location = 018, Moves = new[] {056,240,058,034}, FlawlessIVCount = 3 }, // Lapras on Route 2 - new(SWSH) { Species = 862, Level = 50, Location = 018, Moves = new[] {269,068,792,184} }, // Obstagoon on Route 2 - new(SWSH) { Species = 822, Level = 18, Location = 028, Moves = new[] {681,468,031,365}, Shiny = Never }, // Corvisquire on Route 3 - new(SWSH) { Species = 050, Level = 17, Location = 032, Moves = new[] {523,189,310,045} }, // Diglett on Route 4 - new(SWSH) { Species = 830, Level = 22, Location = 040, Moves = new[] {178,496,075,047} }, // Eldegoss on Route 5 - new(SWSH) { Species = 558, Level = 40, Location = 086, Moves = new[] {404,350,446,157} }, // Crustle on Route 8 - new(SWSH) { Species = 870, Level = 40, Location = 086, Moves = new[] {748,660,179,203} }, // Falinks on Route 8 - new(SWSH) { Species = 362, Level = 55, Location = 090, Moves = new[] {573,329,104,182}, FlawlessIVCount = 3, Weather = Snowing }, // Glalie on Route 9 - new(SWSH) { Species = 853, Level = 50, Location = 092, Moves = new[] {753,576,276,179}, Weather = Snowing }, // Grapploct on Route 9 (in Circhester Bay) - //new(SWSH) { Species = 822, Level = 35, Location = -1, Moves = new[] {065,184,269,365} }, // Corvisquire - new(SWSH) { Species = 614, Level = 55, Location = 106, Moves = new[] {276,059,156,329}, Weather = Snowstorm }, // Beartic on Route 10 - new(SWSH) { Species = 460, Level = 55, Location = 106, Moves = new[] {008,059,452,275}, Weather = Snowstorm }, // Abomasnow on Route 10 - new(SWSH) { Species = 342, Level = 50, Location = 034, Moves = new[] {242,014,534,400}, FlawlessIVCount = 3 }, // Crawdaunt in the town of Turffield - #endregion - - #region Static Part 2 - // Some of these may be crossover cases. For now, just log the locations they can show up in and re-categorize later. - new(SWSH) { Species = 095, Level = 26, Location = 122, Weather = All }, // Onix in the Rolling Fields - new(SWSH) { Species = 291, Level = 15, Location = 122, Weather = All }, // Ninjask in the Rolling Fields - new(SWSH) { Species = 660, Level = 15, Location = 122, Weather = All }, // Diggersby in the Rolling Fields - new(SWSH) { Species = 832, Level = 65, Location = 122, Weather = All }, // Dubwool in the Rolling Fields - new(SWSH) { Species = 416, Level = 26, Location = 122 }, // Vespiquen in the Rolling Fields - new(SWSH) { Species = 675, Level = 32, Location = 122, Weather = Normal | Overcast | Stormy | Intense_Sun }, // Pangoro in the Rolling Fields - new(SWSH) { Species = 315, Level = 15, Location = 122, Weather = Normal | Overcast | Raining | Intense_Sun | Snowing | Sandstorm | Heavy_Fog }, // Roselia in the Rolling Fields - new(SWSH) { Species = 750, Level = 31, Location = 122, Weather = Normal | Overcast | Intense_Sun | Sandstorm | Heavy_Fog }, // Mudsdale in the Rolling Fields - new(SWSH) { Species = 310, Level = 26, Location = 122, Weather = Thunderstorm }, // Manectric in the Rolling Fields - new(SWSH) { Species = 025, Level = 15, Location = 122, Weather = Thunderstorm }, // Pikachu in the Rolling Fields - new(SWSH) { Species = 660, Level = 26, Location = 122, Weather = Overcast | Intense_Sun | Icy | Sandstorm}, // Diggersby in the Rolling Fields - new(SWSH) { Species = 281, Level = 26, Location = 122, Weather = Heavy_Fog }, // Kirlia in the Rolling Fields - new(SWSH) { Species = 282, Level = 32, Location = 122, Weather = Heavy_Fog }, // Gardevoir in the Rolling Fields - new(SWSH) { Species = 439, Level = 15, Location = 122, Weather = Snowstorm }, // Mime Jr. in the Rolling Fields - new(SWSH) { Species = 221, Level = 33, Location = 122, Weather = Icy }, // Piloswine in the Rolling Fields - new(SWSH) { Species = 221, Level = 33, Location = 122, Weather = Icy }, // Piloswine in the Rolling Fields - new(SWSH) { Species = 558, Level = 34, Location = 122, Weather = Sandstorm }, // Crustle in the Rolling Fields - new(SWSH) { Species = 093, Level = 31, Location = 122, Weather = Stormy }, // Haunter in the Rolling Fields - new EncounterStatic8S(SWSH) { Species = 279, Level = 26, Locations = new[] {122, 128, 138}, Weather = Stormy }, // Pelipper in the Rolling Fields, North Lake Miloch, East Lake Axewell - new(SWSH) { Species = 760, Level = 34, Location = 124, Weather = All }, // Bewear in the Dappled Grove - new(SWSH) { Species = 826, Level = 65, Location = 124, Weather = All }, // Orbeetle in the Dappled Grove - new(SWSH) { Species = 045, Level = 36, Location = 124, Weather = Normal | Overcast | Heavy_Fog }, // Vileplume in the Dappled Grove - new(SW ) { Species = 275, Level = 34, Location = 124, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Shiftry in the Dappled Grove - new( SH) { Species = 272, Level = 34, Location = 124, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Ludicolo in the Dappled Grove - new(SWSH) { Species = 675, Level = 32, Location = 124, Weather = Intense_Sun | Icy | Sandstorm }, // Pangoro in the Dappled Grove - new(SWSH) { Species = 537, Level = 36, Location = 124, Weather = Stormy }, // Seismitoad in the Dappled Grove - new(SWSH) { Species = 583, Level = 36, Location = 124, Weather = Icy }, // Vanillish in the Dappled Grove - new(SWSH) { Species = 344, Level = 36, Location = 124, Weather = Intense_Sun | Sandstorm }, // Claydol in the Dappled Grove - new(SWSH) { Species = 426, Level = 34, Location = 126, Weather = Normal | Intense_Sun | Snowing }, // Drifblim at Watchtower Ruins - new(SWSH) { Species = 823, Level = 65, Location = 126, Weather = All }, // Corviknight at Watchtower Ruins - new(SWSH) { Species = 093, Level = 34, Location = 126, Weather = Overcast | Stormy | Snowstorm | Sandstorm | Heavy_Fog }, // Haunter at Watchtower Ruins - new EncounterStatic8S(SWSH) { Species = 356, Level = 40, Locations = new[] {126, 130}, Weather = Overcast | Stormy | Heavy_Fog }, // Dusclops at Watchtower Ruins, West Lake Axewell - new EncounterStatic8S(SWSH) { Species = 362, Level = 40, Locations = new[] {126, 130}, Weather = Icy }, // Glalie at Watchtower Ruins, West Lake Axewell - new EncounterStatic8S(SWSH) { Species = 623, Level = 40, Locations = new[] {126, 130}, Weather = Normal | Intense_Sun | Sandstorm }, // Golurk at Watchtower Ruins, West Lake Axewell - new(SWSH) { Species = 569, Level = 36, Location = 128, Weather = Normal | Overcast | Stormy }, // Garbodor at East Lake Axewell - new(SWSH) { Species = 119, Level = 46, Location = 128, Weather = Normal | Overcast | Intense_Sun | Sandstorm }, // Seaking at East Lake Axewell - new(SWSH) { Species = 279, Level = 46, Location = 128, Weather = Normal | Overcast | Stormy | Intense_Sun }, // Pelipper at East Lake Axewell - new(SWSH) { Species = 110, Level = 65, Location = 128, Form = 01, Weather = All }, // Weezing at East Lake Axewell - new(SWSH) { Species = 221, Level = 36, Location = 128, Weather = Icy }, // Piloswine at East Lake Axewell - new(SWSH) { Species = 750, Level = 36, Location = 128, Weather = Intense_Sun | Sandstorm }, // Mudsdale at East Lake Axewell - new(SWSH) { Species = 437, Level = 36, Location = 128, Weather = Heavy_Fog }, // Bronzong at East Lake Axewell - new EncounterStatic8S(SWSH) { Species = 091, Level = 46, Locations = new[] {128, 130}, Weather = Normal | Heavy_Fog }, // Cloyster at East/West Lake Axewell - new EncounterStatic8S(SWSH) { Species = 584, Level = 47, Locations = new[] {128, 130, 134, 138, 142}, Weather = Icy }, // Vanilluxe at North/East/South/West Lake Miloch/Axewell, Bridge Field - new EncounterStatic8S(SWSH) { Species = 130, Level = 56, Locations = new[] {128, 130, 142, 146}, Weather = All }, // Gyarados in East/West Lake Axewell, Bridge Field, Dusty Bowl - new EncounterStatic8S(SWSH) { Species = 178, Level = 26, Locations = new[] {128, 138}, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Xatu at East Lake Axewell, North Lake Miloch - new EncounterStatic8S(SWSH) { Species = 593, Level = 46, Locations = new[] {128, 138, 154}, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Jellicent at East Lake Axewell, North Lake Miloch, Lake of Outrage - new EncounterStatic8S(SWSH) { Species = 171, Level = 46, Locations = new[] {128, 154}, Weather = Normal | Thunderstorm | Heavy_Fog }, // Lanturn at East Lake Axewell, the Lake of Outrage - new(SWSH) { Species = 195, Level = 15, Location = 130, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Quagsire at West Lake Axewell - new(SWSH) { Species = 099, Level = 28, Location = 130 }, // Kingler at West Lake Axewell - new(SWSH) { Species = 660, Level = 15, Location = 130, Weather = Intense_Sun | Icy | Sandstorm }, // Diggersby at West Lake Axewell - new(SWSH) { Species = 834, Level = 65, Location = 130, Weather = All }, // Drednaw at West Lake Axewell - new(SWSH) { Species = 279, Level = 28, Location = 130, Weather = Stormy }, // Pelipper at West Lake Axewell - new(SWSH) { Species = 536, Level = 28, Location = 130, Weather = Overcast | Icy | Sandstorm | Heavy_Fog }, // Palpitoad at West Lake Axewell - new(SWSH) { Species = 660, Level = 28, Location = 130, Weather = Intense_Sun }, // Diggersby at West Lake Axewell - new(SWSH) { Species = 853, Level = 56, Location = 130, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Grapploct at West Lake Axewell - new(SWSH) { Species = 593, Level = 46, Location = 130, Weather = Overcast | Raining | Heavy_Fog }, // Jellicent at West Lake Axewell - new EncounterStatic8S(SWSH) { Species = 119, Level = 46, Locations = new[] {130, 142}, Weather = Normal | Overcast | Sandstorm }, // Seaking at West Lake Axewell, Bridge Field - new EncounterStatic8S(SWSH) { Species = 131, Level = 56, Locations = new[] {130, 134, 138, 154}, Weather = Normal | Stormy | Icy | Heavy_Fog }, // Lapras at North/East/South Lake Miloch/Axewell, the Lake of Outrage - new(SWSH) { Species = 612, Level = 60, Location = 132, Ability = OnlyFirst, Weather = Normal | Overcast | Raining | Intense_Sun | Sandstorm | Heavy_Fog }, // Haxorus on Axew’s Eye - new(SWSH) { Species = 845, Level = 65, Location = 132, Weather = All }, // Cramorant on Axew’s Eye - new(SWSH) { Species = 537, Level = 60, Location = 132, Weather = Thunderstorm }, // Seismitoad on Axew’s Eye - new(SWSH) { Species = 460, Level = 60, Location = 132, Weather = Icy }, // Abomasnow on Axew’s Eye - new(SWSH) { Species = 067, Level = 26, Location = 134, Weather = All }, // Machoke at South Lake Miloch - new(SWSH) { Species = 828, Level = 65, Location = 134, Weather = All }, // Thievul at South Lake Miloch - new(SWSH) { Species = 119, Level = 46, Location = 134, Weather = Normal | Overcast | Stormy | Sandstorm }, // Seaking at South Lake Miloch - new(SWSH) { Species = 435, Level = 34, Location = 134, Weather = Normal | Overcast | Icy | Sandstorm | Heavy_Fog }, // Skuntank at South Lake Miloch - new(SWSH) { Species = 099, Level = 31, Location = 134, Weather = Normal | Thunderstorm }, // Kingler at South Lake Miloch - new(SWSH) { Species = 342, Level = 31, Location = 134, Weather = Normal | Thunderstorm }, // Crawdaunt at South Lake Miloch - new(SWSH) { Species = 171, Level = 46, Location = 134, Weather = Thunderstorm }, // Lanturn at South Lake Miloch - new(SWSH) { Species = 224, Level = 46, Location = 134, Weather = Normal | Intense_Sun | Sandstorm | Heavy_Fog }, // Octillery at South Lake Miloch - new(SWSH) { Species = 340, Level = 46, Location = 134, Weather = Normal | Thunderstorm | Intense_Sun | Sandstorm }, // Whiscash at South Lake Miloch - new(SWSH) { Species = 536, Level = 34, Location = 134, Weather = Stormy }, // Palpitoad at South Lake Miloch - new(SWSH) { Species = 558, Level = 34, Location = 134, Weather = Intense_Sun }, // Crustle at South Lake Miloch - new(SWSH) { Species = 067, Level = 31, Location = 134, Weather = Overcast | Intense_Sun | Icy | Sandstorm }, // Machoke at South Lake Miloch - new(SWSH) { Species = 426, Level = 31, Location = 134, Weather = Heavy_Fog }, // Drifblim at South Lake Miloch - new(SWSH) { Species = 596, Level = 46, Location = 134, Weather = All }, // Galvantula at South Lake Miloch - new EncounterStatic8S(SWSH) { Species = 426, Level = 46, Locations = new[] {134, 138}, Weather = Normal | Overcast | Snowstorm | Heavy_Fog }, // Drifblim at North/South Lake Miloch - new EncounterStatic8S(SWSH) { Species = 130, Level = 60, Locations = new[] {134, 138, 154 }, Weather = Normal | Overcast | Stormy | Intense_Sun | Icy | Sandstorm }, // Gyarados at North/South Lake Miloch, the Lake of Outrage - new EncounterStatic8S(SWSH) { Species = 593, Level = 46, Locations = new[] {134, 142}, Weather = Raining | Heavy_Fog }, // Jellicent at South Lake Miloch, Bridge Field - new EncounterStatic8S(SWSH) { Species = 350, Level = 60, Locations = new[] {134, 154}, Gender = 0, Ability = OnlyFirst, Weather = Heavy_Fog }, // Milotic at South Lake Miloch, the Lake of Outrage - new(SWSH) { Species = 208, Level = 50, Location = 136, Weather = All }, // Steelix near the Giant’s Seat - new(SWSH) { Species = 738, Level = 46, Location = 136, Weather = Normal | Overcast | Intense_Sun | Sandstorm }, // Vikavolt near the Giant’s Seat - new(SWSH) { Species = 112, Level = 46, Location = 136 }, // Rhydon near the Giant’s Seat - new(SWSH) { Species = 625, Level = 52, Location = 136, Weather = All }, // Bisharp near the Giant’s Seat - new(SWSH) { Species = 884, Level = 65, Location = 136, Weather = All }, // Duraludon near the Giant’s Seat - new(SWSH) { Species = 437, Level = 46, Location = 136, Weather = Overcast | Raining }, // Bronzong near the Giant’s Seat - new(SWSH) { Species = 460, Level = 46, Location = 136, Weather = Icy }, // Abomasnow near the Giant’s Seat - new(SWSH) { Species = 750, Level = 46, Location = 136, Weather = Intense_Sun }, // Mudsdale near the Giant’s Seat - new(SWSH) { Species = 623, Level = 46, Location = 136, Weather = Sandstorm }, // Golurk near the Giant’s Seat - new(SWSH) { Species = 356, Level = 46, Location = 136, Weather = Heavy_Fog }, // Dusclops near the Giant’s Seat - new(SWSH) { Species = 518, Level = 46, Location = 136, Weather = Heavy_Fog }, // Musharna near the Giant’s Seat - new(SWSH) { Species = 362, Level = 46, Location = 136, Weather = Icy }, // Glalie near the Giant’s Seat - new(SWSH) { Species = 596, Level = 46, Location = 136, Weather = Raining }, // Galvantula near the Giant’s Seat - new(SWSH) { Species = 823, Level = 50, Location = 138, Weather = All }, // Corviknight at North Lake Miloch - new(SWSH) { Species = 510, Level = 28, Location = 138, Weather = All }, // Liepard at North Lake Miloch - new(SWSH) { Species = 119, Level = 46, Location = 138, Weather = Stormy | Sandstorm }, // Seaking at North Lake Miloch - new(SWSH) { Species = 448, Level = 36, Location = 138 }, // Lucario at North Lake Miloch - new(SWSH) { Species = 340, Level = 46, Location = 138, Weather = Normal | Overcast | Thunderstorm | Intense_Sun | Sandstorm }, // Whiscash at North Lake Miloch - new(SWSH) { Species = 836, Level = 65, Location = 138, Weather = All }, // Boltund at North Lake Miloch - new(SWSH) { Species = 537, Level = 36, Location = 138, Weather = Overcast | Thunderstorm }, // Seismitoad at North Lake Miloch - new(SWSH) { Species = 435, Level = 36, Location = 138, Weather = Raining | Intense_Sun | Sandstorm }, // Skuntank at North Lake Miloch - new(SWSH) { Species = 583, Level = 36, Location = 138, Weather = Icy }, // Vanillish at North Lake Miloch - new(SWSH) { Species = 426, Level = 36, Location = 138, Weather = Heavy_Fog }, // Drifblim at North Lake Miloch - new EncounterStatic8S(SWSH) { Species = 130, Level = 56, Locations = new[] {138, 154}, Weather = Overcast | Intense_Sun | Sandstorm }, // Gyarados in North Lake Miloch, Lake of Outrage - new(SWSH) { Species = 625, Level = 52, Location = 140, Weather = Snowstorm }, // Bisharp at the Motostoke Riverbank - new(SWSH) { Species = 143, Level = 36, Location = 140, Weather = Normal | Overcast | Stormy | Intense_Sun | Icy | Sandstorm }, // Snorlax at the Motostoke Riverbank - new(SWSH) { Species = 452, Level = 40, Location = 140, Weather = Normal | Stormy | Intense_Sun | Sandstorm }, // Drapion at the Motostoke Riverbank - new(SWSH) { Species = 561, Level = 36, Location = 140, Weather = All }, // Sigilyph at the Motostoke Riverbank - new(SWSH) { Species = 534, Level = 55, Location = 140, Ability = OnlyFirst, Weather = Normal | Overcast | Stormy | Snowing | Heavy_Fog }, // Conkeldurr at the Motostoke Riverbank - new(SWSH) { Species = 320, Level = 56, Location = 140, Weather = All }, // Wailmer at the Motostoke Riverbank - new(SWSH) { Species = 561, Level = 40, Location = 140, Weather = Normal | Overcast | Heavy_Fog }, // Sigilyph at the Motostoke Riverbank - new(SWSH) { Species = 830, Level = 65, Location = 140, Weather = All }, // Eldegoss at the Motostoke Riverbank - new(SWSH) { Species = 036, Level = 36, Location = 140, Weather = Heavy_Fog }, // Clefable at the Motostoke Riverbank - new(SWSH) { Species = 743, Level = 40, Location = 140, Weather = Overcast | Icy | Heavy_Fog }, // Ribombee at the Motostoke Riverbank - new(SWSH) { Species = 112, Level = 55, Location = 140, Weather = Intense_Sun | Sandstorm }, // Rhydon at the Motostoke Riverbank - new(SWSH) { Species = 823, Level = 40, Location = 140, Weather = Stormy | Intense_Sun | Icy | Sandstorm }, // Corviknight at the Motostoke Riverbank - new EncounterStatic8S(SWSH) { Species = 760, Level = 40, Locations = new[] {140, 142}, Weather = Thunderstorm | Sandstorm }, // Bewear in Bridge Field, Motostoke Riverbank - new EncounterStatic8S(SWSH) { Species = 264, Level = 40, Locations = new[] {140, 142}, Form = 01, Weather = All }, // Linoone at the Motostoke Riverbank, Bridge Field - new(SWSH) { Species = 569, Level = 40, Location = 142, Weather = All }, // Garbodor in Bridge Field - new(SWSH) { Species = 279, Level = 46, Location = 142, Weather = Intense_Sun }, // Pelipper in Bridge Field - new(SWSH) { Species = 743, Level = 40, Location = 142, Weather = Normal | Overcast | Intense_Sun | Heavy_Fog }, // Ribombee in Bridge Field - new(SWSH) { Species = 475, Level = 60, Location = 142, Weather = Normal | Overcast | Stormy | Intense_Sun | Sandstorm }, // Gallade in Bridge Field - new(SWSH) { Species = 606, Level = 42, Location = 142, Weather = Normal | Overcast | Icy | Heavy_Fog }, // Beheeyem in Bridge Field - new(SWSH) { Species = 715, Level = 50, Location = 142, Weather = Normal | Overcast | Stormy | Intense_Sun | Snowstorm | Sandstorm | Heavy_Fog }, // Noivern in Bridge Field - new(SWSH) { Species = 537, Level = 46, Location = 142, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Seismitoad in Bridge Field - new(SWSH) { Species = 768, Level = 50, Location = 142, Weather = Normal | Stormy }, // Golisopod in Bridge Field - new(SWSH) { Species = 760, Level = 42, Location = 142, Weather = Normal | Stormy | Icy | Sandstorm | Heavy_Fog }, // Bewear in Bridge Field - new(SWSH) { Species = 820, Level = 42, Location = 142, Weather = All }, // Greedent in Bridge Field - new(SWSH) { Species = 862, Level = 65, Location = 142, Weather = All }, // Obstagoon in Bridge Field - new(SWSH) { Species = 537, Level = 36, Location = 142, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Seismitoad in Bridge Field - new(SWSH) { Species = 614, Level = 60, Location = 142, Weather = Snowing }, // Beartic in Bridge Field - new(SWSH) { Species = 461, Level = 60, Location = 142, Weather = Snowstorm }, // Weavile in Bridge Field - new(SWSH) { Species = 518, Level = 60, Location = 142, Weather = Heavy_Fog }, // Musharna in Bridge Field - new(SWSH) { Species = 437, Level = 42, Location = 142, Weather = Stormy }, // Bronzong in Bridge Field - new(SWSH) { Species = 344, Level = 42, Location = 142, Weather = Intense_Sun | Sandstorm }, // Claydol in Bridge Field - new(SWSH) { Species = 452, Level = 50, Location = 142, Weather = Overcast }, // Drapion in Bridge Field - new(SWSH) { Species = 164, Level = 50, Location = 142, Weather = Snowing }, // Noctowl in Bridge Field - new(SWSH) { Species = 760, Level = 46, Location = 142, Weather = Intense_Sun | Sandstorm }, // Bewear in Bridge Field - new(SWSH) { Species = 675, Level = 42, Location = 142, Weather = Overcast | Intense_Sun }, // Pangoro in Bridge Field - new(SWSH) { Species = 584, Level = 50, Location = 142, Weather = Icy }, // Vanilluxe in Bridge Field - new(SWSH) { Species = 112, Level = 50, Location = 142, Weather = Intense_Sun | Sandstorm }, // Rhydon in Bridge Field - new(SWSH) { Species = 778, Level = 50, Location = 142, Weather = Heavy_Fog }, // Mimikyu in Bridge Field - new EncounterStatic8S(SWSH) { Species = 598, Level = 40, Locations = new[] {142, 144}, Weather = All }, // Ferrothorn in Bridge Field, Stony Wilderness - new(SWSH) { Species = 344, Level = 42, Location = 144, Weather = Normal | Overcast | Intense_Sun | Sandstorm }, // Claydol in the Stony Wilderness - new(SWSH) { Species = 437, Level = 42, Location = 144, Weather = Stormy | Icy | Heavy_Fog }, // Bronzong in Stony Wilderness - new(SWSH) { Species = 477, Level = 60, Location = 144, Weather = All }, // Dusknoir in the Stony Wilderness - new(SWSH) { Species = 623, Level = 43, Location = 144, Weather = All }, // Golurk in the Stony Wilderness - new(SWSH) { Species = 561, Level = 40, Location = 144, Weather = Normal | Stormy | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Sigilyph in the Stony Wilderness - new(SWSH) { Species = 558, Level = 34, Location = 144, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Crustle in the Stony Wilderness - new(SWSH) { Species = 112, Level = 41, Location = 144, Weather = All }, // Rhydon in the Stony Wilderness - new(SWSH) { Species = 763, Level = 36, Location = 144, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Tsareena in the Stony Wilderness - new(SWSH) { Species = 861, Level = 65, Location = 144, Gender = 0, Weather = All }, // Grimmsnarl in the Stony Wilderness - new(SWSH) { Species = 521, Level = 40, Location = 144, Weather = Overcast }, // Unfezant in the Stony Wilderness - new(SWSH) { Species = 752, Level = 34, Location = 144, Weather = Raining }, // Araquanid in the Stony Wilderness - new(SWSH) { Species = 750, Level = 41, Location = 146, Weather = Normal | Intense_Sun | Sandstorm }, // Mudsdale in Dusty Bowl - new(SWSH) { Species = 185, Level = 41, Location = 146, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Sudowoodo in Dusty Bowl - new(SWSH) { Species = 437, Level = 41, Location = 146, Weather = Normal | Stormy | Icy | Heavy_Fog }, // Bronzong in Dusty Bowl - new(SW ) { Species = 784, Level = 60, Location = 146, Ability = OnlyFirst, Weather = Normal | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Kommo-o in Dusty Bowl - new( SH) { Species = 248, Level = 60, Location = 146, Weather = Normal | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Tyranitar in Dusty Bowl - new(SWSH) { Species = 213, Level = 34, Location = 146, Weather = All }, // Shuckle in Dusty Bowl - new(SWSH) { Species = 330, Level = 51, Location = 146, Weather = Normal | Sandstorm }, // Flygon in Dusty Bowl - new(SWSH) { Species = 526, Level = 51, Location = 146, Weather = Normal | Intense_Sun }, // Gigalith in Dusty Bowl - new(SWSH) { Species = 844, Level = 65, Location = 146, Weather = All }, // Sandaconda in Dusty Bowl - new(SWSH) { Species = 537, Level = 41, Location = 146, Weather = Stormy }, // Seismitoad in Dusty Bowl - new(SWSH) { Species = 435, Level = 41, Location = 146, Weather = Overcast }, // Skuntank in Dusty Bowl - new(SWSH) { Species = 221, Level = 41, Location = 146, Weather = Icy }, // Piloswine in Dusty Bowl - new(SWSH) { Species = 356, Level = 41, Location = 146, Weather = Heavy_Fog }, // Dusclops in Dusty Bowl - new(SWSH) { Species = 344, Level = 41, Location = 146, Weather = Overcast | Intense_Sun | Sandstorm }, // Claydol in Dusty Bowl - new(SWSH) { Species = 689, Level = 60, Location = 146, Weather = Overcast | Stormy }, // Barbaracle in Dusty Bowl - new(SWSH) { Species = 561, Level = 51, Location = 146, Weather = Overcast | Stormy | Intense_Sun | Icy | Heavy_Fog }, // Sigilyph in Dusty Bowl - new(SWSH) { Species = 623, Level = 51, Location = 146, Weather = Overcast | Stormy | Icy | Sandstorm | Heavy_Fog }, // Golurk in Dusty Bowl - new EncounterStatic8S(SWSH) { Species = 423, Level = 56, Locations = new[] {146, 148}, Form = 01, Weather = All }, // Gastrodon in Dusty Bowl, Giant’s Mirror - new(SWSH) { Species = 208, Level = 50, Location = 148, Weather = All }, // Steelix around the Giant’s Mirror - new(SWSH) { Species = 068, Level = 60, Location = 148, Ability = OnlyFirst, Weather = All }, // Machamp around the Giant’s Mirror - new(SWSH) { Species = 182, Level = 41, Location = 148, Weather = Normal | Intense_Sun | Heavy_Fog }, // Bellossom around the Giant’s Mirror - new(SWSH) { Species = 521, Level = 41, Location = 148, Weather = Normal | Overcast | Intense_Sun }, // Unfezant around the Giant’s Mirror - new(SWSH) { Species = 045, Level = 41, Location = 148, Weather = Overcast | Stormy | Icy | Sandstorm }, // Vileplume around the Giant’s Mirror - new(SWSH) { Species = 863, Level = 65, Location = 148, Weather = All }, // Perrserker around the Giant’s Mirror - new(SWSH) { Species = 537, Level = 60, Location = 148, Weather = Raining }, // Seismitoad around the Giant’s Mirror - new(SWSH) { Species = 460, Level = 60, Location = 148, Weather = Snowing }, // Abomasnow around the Giant’s Mirror - new(SWSH) { Species = 178, Level = 41, Location = 148, Weather = Raining | Icy | Sandstorm | Heavy_Fog }, // Xatu around the Giant’s Mirror - new(SWSH) { Species = 701, Level = 36, Location = 150, Weather = All }, // Hawlucha on the Hammerlocke Hills - new(SWSH) { Species = 711, Level = 41, Location = 150, Weather = All }, // Gourgeist on the Hammerlocke Hills - new(SWSH) { Species = 879, Level = 65, Location = 150, Weather = All }, // Copperajah on the Hammerlocke Hills - new(SWSH) { Species = 600, Level = 46, Location = 150, Weather = Normal | Overcast | Raining | Snowing }, // Klang on the Hammerlocke Hills - new(SWSH) { Species = 823, Level = 38, Location = 150, Weather = All }, // Corviknight on the Hammerlocke Hills - new(SWSH) { Species = 045, Level = 41, Location = 150, Weather = Overcast | Stormy | Icy | Sandstorm }, // Vileplume on the Hammerlocke Hills - new(SWSH) { Species = 601, Level = 49, Location = 150, Weather = Thunderstorm | Intense_Sun | Snowstorm | Sandstorm | Heavy_Fog }, // Klinklang on the Hammerlocke Hills - new(SWSH) { Species = 407, Level = 41, Location = 150, Weather = Overcast | Heavy_Fog }, // Roserade on the Hammerlocke Hills - new(SWSH) { Species = 460, Level = 41, Location = 150, Weather = Icy}, // Abomasnow on the Hammerlocke Hills - //new(SWSH) { Species = 510, Level = 31, Location = -1, }, // Liepard - new(SWSH) { Species = 768, Level = 60, Location = 152, Weather = Raining }, // Golisopod near the Giant’s Cap - new(SWSH) { Species = 614, Level = 60, Location = 152, Weather = Snowing }, // Beartic near the Giant’s Cap - new(SWSH) { Species = 530, Level = 46, Location = 152, Weather = Intense_Sun | Sandstorm }, // Excadrill near the Giant’s Cap - new(SWSH) { Species = 362, Level = 46, Location = 152, Weather = Icy }, // Glalie near the Giant’s Cap - new(SWSH) { Species = 537, Level = 46, Location = 152, Weather = Raining }, // Seismitoad near the Giant’s Cap - new(SWSH) { Species = 681, Level = 58, Location = 152, Weather = Heavy_Fog }, // Aegislash near the Giant’s Cap - new(SWSH) { Species = 094, Level = 60, Location = 152, Weather = Normal | Overcast | Thunderstorm | Intense_Sun | Snowstorm | Sandstorm | Heavy_Fog }, // Gengar near the Giant’s Cap - new(SWSH) { Species = 823, Level = 39, Location = 152, Weather = All }, // Corviknight near the Giant’s Cap - new(SWSH) { Species = 573, Level = 46, Location = 152, Weather = Normal | Overcast | Heavy_Fog }, // Cinccino near the Giant’s Cap - new(SWSH) { Species = 826, Level = 41, Location = 152, Weather = All }, // Orbeetle near the Giant’s Cap - new(SWSH) { Species = 834, Level = 36, Location = 152, Weather = All }, // Drednaw near the Giant’s Cap - new(SWSH) { Species = 680, Level = 56, Location = 152, Weather = Normal | Overcast | Stormy | Intense_Sun | Icy | Sandstorm }, // Doublade near the Giant’s Cap - new(SWSH) { Species = 839, Level = 65, Location = 152, Weather = All }, // Coalossal near the Giant’s Cap - new(SWSH) { Species = 853, Level = 56, Location = 154, Weather = All }, // Grapploct at the Lake of Outrage - new(SWSH) { Species = 282, Level = 60, Location = 154, Weather = Normal | Heavy_Fog }, // Gardevoir at the Lake of Outrage - new(SWSH) { Species = 470, Level = 56, Location = 154, Weather = Normal }, // Leafeon at the Lake of Outrage - new(SWSH) { Species = 119, Level = 46, Location = 154, Weather = Overcast | Intense_Sun | Sandstorm }, // Seaking at Lake of Outrage - new(SWSH) { Species = 858, Level = 65, Location = 154, Gender = 1, Weather = All }, // Hatterene at the Lake of Outrage - new(SWSH) { Species = 112, Level = 60, Location = 154, Weather = Sandstorm }, // Rhydon at the Lake of Outrage - new(SWSH) { Species = 609, Level = 60, Location = 154, Weather = Intense_Sun }, // Chandelure at the Lake of Outrage - new(SWSH) { Species = 713, Level = 60, Location = 154, Weather = Icy }, // Avalugg at the Lake of Outrage - new(SWSH) { Species = 756, Level = 60, Location = 154, Weather = Overcast | Stormy }, // Shiinotic at the Lake of Outrage - new(SWSH) { Species = 134, Level = 56, Location = 154, Weather = Raining }, // Vaporeon at the Lake of Outrage - new(SWSH) { Species = 135, Level = 56, Location = 154, Weather = Thunderstorm }, // Jolteon at the Lake of Outrage - new(SWSH) { Species = 196, Level = 56, Location = 154, Weather = Overcast }, // Espeon at the Lake of Outrage - new(SWSH) { Species = 471, Level = 56, Location = 154, Weather = Icy }, // Glaceon at the Lake of Outrage - new(SWSH) { Species = 136, Level = 56, Location = 154, Weather = Intense_Sun }, // Flareon at the Lake of Outrage - new(SWSH) { Species = 197, Level = 56, Location = 154, Weather = Sandstorm }, // Umbreon at the Lake of Outrage - new(SWSH) { Species = 700, Level = 56, Location = 154, Weather = Heavy_Fog }, // Sylveon at the Lake of Outrage - #endregion - - #region R1 Static Encounters - new(SWSH) { Species = 079, Level = 12, Location = 016, ScriptedNoMarks = true, Form = 01, Shiny = Never }, // Slowpoke-1 at Wedgehurst Station - new(SWSH) { Species = 321, Level = 80, Location = 186, Weather = All_IoA }, // Wailord in the Workout Sea - - new(SWSH) { Species = 748, Level = 20, Location = 164, Weather = Normal | Heavy_Fog }, // Toxapex in the Fields of Honor - new(SWSH) { Species = 099, Level = 20, Location = 164, Weather = Normal | Overcast | Stormy | Intense_Sun }, // Kingler in the Fields of Honor - new(SWSH) { Species = 834, Level = 20, Location = 164, Weather = Intense_Sun }, // Drednaw in the Fields of Honor - new(SWSH) { Species = 764, Level = 15, Location = 164 }, // Comfey in the Fields of Honor - new(SWSH) { Species = 744, Level = 15, Location = 164, Weather = Normal | Intense_Sun }, // Rockruff in the Fields of Honor - new(SWSH) { Species = 121, Level = 20, Location = 164, Weather = Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Starmie in the Fields of Honor - new(SWSH) { Species = 428, Level = 22, Location = 164, Weather = Normal | Intense_Sun }, // Lopunny in the Fields of Honor - new EncounterStatic8S(SWSH) { Species = 687, Level = 26, Locations = new[] {164, 166}, Weather = Overcast | Raining }, // Malamar in the Fields of Honor, Soothing Wetlands - new EncounterStatic8S(SWSH) { Species = 404, Level = 20, Locations = new[] {164, 166}, Weather = Thunderstorm }, // Luxio in the Fields of Honor, Soothing Wetlands - new EncounterStatic8S(SWSH) { Species = 570, Level = 15, Locations = new[] {164, 166}, Weather = Overcast | Heavy_Fog }, // Zorua in the Fields of Honor, Soothing Wetlands - new EncounterStatic8S(SWSH) { Species = 040, Level = 27, Locations = new[] {164, 166}, Weather = Heavy_Fog }, // Wigglytuff in the Fields of Honor, Soothing Wetlands - new EncounterStatic8S(SWSH) { Species = 183, Level = 15, Locations = new[] {164, 166}, Weather = Raining }, // Marill in the Fields of Honor, Soothing Wetlands - new EncounterStatic8S(SWSH) { Species = 662, Level = 20, Locations = new[] {164, 166}, Weather = Intense_Sun }, // Fletchinder in the Fields of Honor, in the Soothing Wetlands - new EncounterStatic8S(SWSH) { Species = 064, Level = 20, Locations = new[] {164, 166}, Weather = Heavy_Fog }, // Kadabra in the Fields of Honor, in the Soothing Wetlands - new EncounterStatic8S(SWSH) { Species = 025, Level = 15, Locations = new[] {164, 166}, Weather = Thunderstorm }, // Pikachu in the Fields of Honor, in the Soothing Wetlands - new(SWSH) { Species = 834, Level = 20, Location = 166, Weather = Normal | Overcast | Sandstorm | Intense_Sun }, // Drednaw in the Soothing Wetlands - new(SWSH) { Species = 764, Level = 15, Location = 166, Weather = Normal | Intense_Sun | Heavy_Fog }, // Comfey in the Soothing Wetlands - new(SWSH) { Species = 744, Level = 15, Location = 166 }, // Rockruff in the Soothing Wetlands - new(SWSH) { Species = 195, Level = 20, Location = 166, Weather = Normal | Overcast | Raining | Intense_Sun | Heavy_Fog }, // Quagsire in the Soothing Wetlands - new(SWSH) { Species = 626, Level = 20, Location = 166, Weather = Normal | Overcast | Stormy | Intense_Sun }, // Bouffalant in the Soothing Wetlands - new(SWSH) { Species = 242, Level = 22, Location = 166, Weather = Heavy_Fog }, // Blissey in the Soothing Wetlands - new(SWSH) { Species = 452, Level = 22, Location = 166, Weather = Normal | Overcast | Raining }, // Drapion in the Soothing Wetlands - new(SWSH) { Species = 463, Level = 27, Location = 166, Weather = Normal | Raining }, // Lickilicky in the Soothing Wetlands - new(SWSH) { Species = 405, Level = 32, Location = 166, Weather = Thunderstorm }, // Luxray in the Soothing Wetlands - new(SWSH) { Species = 428, Level = 22, Location = 166 }, // Lopunny in the Soothing Wetlands - new(SWSH) { Species = 186, Level = 32, Location = 166, Weather = Stormy }, // Politoed in the Soothing Wetlands - new(SWSH) { Species = 061, Level = 20, Location = 166, Weather = Stormy | Heavy_Fog }, // Poliwhirl in the Soothing Wetlands - new(SWSH) { Species = 549, Level = 22, Location = 166, Weather = Intense_Sun }, // Lilligant in the Soothing Wetlands - new(SW ) { Species = 559, Level = 20, Location = 166, Weather = Overcast }, // Scraggy in the Soothing Wetlands - new( SH) { Species = 453, Level = 20, Location = 166, Weather = Overcast }, // Croagunk in the Soothing Wetlands - new(SWSH) { Species = 663, Level = 32, Location = 166, Weather = Intense_Sun }, // Talonflame in the Soothing Wetlands - new EncounterStatic8S(SWSH) { Species = 026, Level = 26, Locations = new[] {166, 168}, Weather = Thunderstorm }, // Raichu in the Soothing Wetlands, in the Forest of Focus - new EncounterStatic8S(SWSH) { Species = 184, Level = 21, Locations = new[] {166, 168}, Weather = Heavy_Fog }, // Azumarill in the Soothing Wetlands, in the Forest of Focus - new EncounterStatic8S(SWSH) { Species = 587, Level = 20, Locations = new[] {166, 168}, Weather = All_IoA }, // Emolga in the Soothing Wetlands (c), Forest of Focus - new EncounterStatic8S(SWSH) { Species = 847, Level = 42, Locations = new[] {166, 170}, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Barraskewda in the Soothing Wetlands, Challenge Beach - //new(SWSH) { Species = 834, Level = 21, Location = -1 }, // Drednaw - //new(SWSH) { Species = 768, Level = 26, Location = -1 }, // Golisopod - new(SWSH) { Species = 025, Level = 22, Location = 168, Weather = Normal | Overcast | Stormy }, // Pikachu in the Forest of Focus - new(SW ) { Species = 766, Level = 26, Location = 168 }, // Passimian in the Forest of Focus - new( SH) { Species = 765, Level = 26, Location = 168 }, // Oranguru in the Forest of Focus - new(SWSH) { Species = 342, Level = 26, Location = 168, Weather = Overcast | Stormy }, // Crawdaunt in the Forest of Focus - new(SWSH) { Species = 040, Level = 26, Location = 168, Weather = Heavy_Fog }, // Wigglytuff in the Forest of Focus - new(SWSH) { Species = 028, Level = 26, Location = 168, Weather = Sandstorm }, // Sandslash in the Forest of Focus - new(SWSH) { Species = 589, Level = 32, Location = 168, Weather = Sandstorm }, // Escavalier in the Forest of Focus - new(SWSH) { Species = 104, Level = 20, Location = 168, Weather = Sandstorm }, // Cubone in the Forest of Focus - new(SWSH) { Species = 545, Level = 32, Location = 168, Weather = Overcast }, // Scolipede in the Forest of Focus - new(SW ) { Species = 127, Level = 26, Location = 168, Weather = Intense_Sun }, // Pinsir in the Forest of Focus - new( SH) { Species = 214, Level = 26, Location = 168, Weather = Intense_Sun }, // Heracross in the Forest of Focus - new(SWSH) { Species = 636, Level = 15, Location = 168, Weather = Intense_Sun }, // Larvesta in the Forest of Focus - new(SWSH) { Species = 465, Level = 36, Location = 168, Weather = Intense_Sun }, // Tangrowth in the Forest of Focus - new(SW ) { Species = 616, Level = 20, Location = 168, Weather = Stormy }, // Shelmet in the Forest of Focus - new( SH) { Species = 704, Level = 20, Location = 168, Weather = Stormy }, // Goomy in the Forest of Focus - new(SWSH) { Species = 172, Level = 20, Location = 168, Weather = Thunderstorm }, // Pichu in the Forest of Focus - new(SWSH) { Species = 845, Level = 20, Location = 168, Weather = Normal | Raining | Intense_Sun | Heavy_Fog }, // Cramorant in the Forest of Focus - new(SWSH) { Species = 617, Level = 32, Location = 168, Weather = Raining }, // Accelgor in the Forest of Focus - new(SWSH) { Species = 055, Level = 26, Location = 168, Weather = Raining }, // Golduck in the Forest of Focus - new(SWSH) { Species = 591, Level = 26, Location = 168, Weather = Normal | Overcast | Raining | Intense_Sun }, // Amoonguss in the Forest of Focus - new(SWSH) { Species = 764, Level = 22, Location = 168, Weather = Intense_Sun | Heavy_Fog }, // Comfey in the Forest of Focus - new(SWSH) { Species = 570, Level = 22, Location = 168, Weather = Heavy_Fog }, // Zorua in the Forest of Focus - new(SWSH) { Species = 039, Level = 20, Location = 168, Weather = Heavy_Fog }, // Jigglypuff in the Forest of Focus - new(SWSH) { Species = 847, Level = 42, Location = 168, Weather = Stormy | Heavy_Fog }, // Barraskewda in the Forest of Focus - new(SWSH) { Species = 340, Level = 42, Location = 168, Weather = Normal | Overcast | Intense_Sun | Sandstorm }, // Whiscash in the Forest of Focus - new EncounterStatic8S(SWSH) { Species = 754, Level = 27, Locations = new[] {168, 170}, Weather = Intense_Sun }, // Lurantis in the Forest of Focus, on Challenge Beach - new EncounterStatic8S(SWSH) { Species = 282, Level = 36, Locations = new[] {168, 180}, Weather = Heavy_Fog }, // Gardevoir in the Forest of Focus, Training Lowlands - //new(SWSH) { Species = 475, Level = 20, Location = -1 }, // Gallade - //new(SWSH) { Species = 625, Level = 20, Location = -1 }, // Bisharp - //new(SWSH) { Species = 082, Level = 27, Location = -1 }, // Magneton - //new(SWSH) { Species = 105, Level = 20, Location = -1 }, // Marowak - new(SWSH) { Species = 183, Level = 15, Location = 170 }, // Marill on Challenge Beach - new(SWSH) { Species = 462, Level = 36, Location = 170, Weather = Thunderstorm }, // Magnezone on Challenge Beach - new(SWSH) { Species = 026, Level = 29, Location = 170, Weather = Thunderstorm }, // Raichu on Challenge Beach - new(SWSH) { Species = 637, Level = 42, Location = 170, Weather = Intense_Sun }, // Volcarona on Challenge Beach - new(SWSH) { Species = 687, Level = 29, Location = 170, Weather = Overcast | Raining }, // Malamar on Challenge Beach - new(SWSH) { Species = 428, Level = 27, Location = 170, Weather = Normal | Sandstorm | Heavy_Fog }, // Lopunny on Challenge Beach - new(SWSH) { Species = 452, Level = 27, Location = 170, Weather = Overcast | Raining }, // Drapion on Challenge Beach - //new(SWSH) { Species = 558, Level = 20, Location = -1 }, // Crustle - new(SWSH) { Species = 764, Level = 25, Location = 170, Weather = Normal | Intense_Sun | Heavy_Fog }, // Comfey on Challenge Beach - new(SWSH) { Species = 877, Level = 25, Location = 170, Weather = Normal | Overcast | Stormy }, // Morpeko on Challenge Beach - new(SWSH) { Species = 834, Level = 26, Location = 170, Weather = Normal | Overcast | Thunderstorm | Intense_Sun }, // Drednaw on Challenge Beach - new(SWSH) { Species = 040, Level = 29, Location = 170, Weather = Heavy_Fog }, // Wigglytuff on Challenge Beach - new(SWSH) { Species = 528, Level = 27, Location = 170, Weather = Overcast }, // Swoobat on Challenge Beach - new(SWSH) { Species = 279, Level = 26, Location = 170, Weather = All_IoA }, // Pelipper on Challenge Beach - new(SWSH) { Species = 082, Level = 26, Location = 170, Weather = Thunderstorm }, // Magneton on Challenge Beach - new(SWSH) { Species = 426, Level = 26, Location = 170, Weather = Overcast | Heavy_Fog }, // Drifblim on Challenge Beach - new(SWSH) { Species = 768, Level = 36, Location = 170, Weather = Raining }, // Golisopod on Challenge Beach - new(SWSH) { Species = 662, Level = 26, Location = 170, Weather = Intense_Sun }, // Fletchinder on Challenge Beach - new(SWSH) { Species = 342, Level = 27, Location = 170, Weather = Overcast | Raining }, // Crawdaunt on Challenge Beach - new(SWSH) { Species = 184, Level = 27, Location = 170, Weather = Heavy_Fog }, // Azumarill on Challenge Beach - new(SWSH) { Species = 549, Level = 26, Location = 170, Weather = Intense_Sun }, // Lilligant on Challenge Beach - new(SWSH) { Species = 845, Level = 24, Location = 170, Weather = Normal | Overcast | Raining | Intense_Sun | Heavy_Fog }, // Cramorant on Challenge Beach - new(SWSH) { Species = 055, Level = 27, Location = 170, Ability = OnlySecond, Weather = Thunderstorm | Intense_Sun }, // Golduck on Challenge Beach - new(SWSH) { Species = 702, Level = 25, Location = 170, Weather = Normal | Stormy }, // Dedenne on Challenge Beach - //new(SWSH) { Species = 113, Level = 27, Location = -1 }, // Chansey - new(SWSH) { Species = 405, Level = 36, Location = 170, Weather = Thunderstorm }, // Luxray on Challenge Beach - new(SWSH) { Species = 099, Level = 26, Location = 170, Weather = Normal | Raining | Sandstorm | Intense_Sun }, // Kingler on Challenge Beach - new(SWSH) { Species = 121, Level = 26, Location = 170, Weather = Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Starmie on Challenge Beach - new(SWSH) { Species = 748, Level = 26, Location = 170, Weather = Overcast | Stormy | Heavy_Fog }, // Toxapex on Challenge Beach - new(SWSH) { Species = 224, Level = 45, Location = 170, Weather = Normal | Intense_Sun }, // Octillery on Challenge Beach - new(SWSH) { Species = 171, Level = 42, Location = 170, Weather = Thunderstorm | Heavy_Fog }, // Lanturn on Challenge Beach - new(SWSH) { Species = 593, Level = 42, Location = 170, Weather = Overcast | Raining | Heavy_Fog }, // Jellicent on Challenge Beach - new EncounterStatic8S(SWSH) { Species = 342, Level = 42, Locations = new[] {170, 180}, Weather = Overcast }, // Crawdaunt on Challenge Beach, Training Lowlands - new(SWSH) { Species = 091, Level = 42, Location = 170, Weather = Raining | Heavy_Fog }, // Cloyster on Challenge Beach - new(SWSH) { Species = 130, Level = 50, Location = 170, Weather = Normal | Raining | Intense_Sun }, // Gyarados on Challenge Beach - new(SWSH) { Species = 062, Level = 36, Location = 172 }, // Poliwrath in Brawlers’ Cave - new(SWSH) { Species = 294, Level = 26, Location = 172 }, // Loudred in Brawlers’ Cave - new(SWSH) { Species = 528, Level = 26, Location = 172 }, // Swoobat in Brawlers’ Cave - new(SWSH) { Species = 621, Level = 36, Location = 172 }, // Druddigon in Brawlers’ Cave - new(SWSH) { Species = 055, Level = 26, Location = 172 }, // Golduck in Brawlers’ Cave - new(SWSH) { Species = 526, Level = 42, Location = 172 }, // Gigalith in Brawlers’ Cave - new EncounterStatic8S(SW ) { Species = 744, Level = 22, Locations = new[] {172, 174}, Weather = Normal | Overcast }, // Rockruff on Challenge Road, Brawlers' Cave (c) - new EncounterStatic8S( SH) { Species = 744, Level = 22, Locations = new[] {172, 174}, Weather = Normal | Overcast | Intense_Sun | Heavy_Fog }, // Rockruff on Challenge Road, Brawlers' Cave (c) - new EncounterStatic8S(SW ) { Species = 560, Level = 26, Locations = new[] {172, 174, 180}, Weather = Stormy }, // Scrafty on Challenge Road, Brawlers’ Cave (c), Training Lowlands - new EncounterStatic8S( SH) { Species = 454, Level = 26, Locations = new[] {172, 174, 180}, Weather = Stormy }, // Toxicroak on Challenge Road, Brawlers’ Cave (c), Training Lowlands - new EncounterStatic8S(SWSH) { Species = 558, Level = 26, Locations = new[] {172, 174, 180}, Weather = Sandstorm }, // Crustle on Challenge Road, Brawlers’ Cave (c), Training Lowlands - new EncounterStatic8S(SWSH) { Species = 340, Level = 42, Locations = new[] {172, 176} }, // Whiscash in Courageous Cavern, Brawlers' Cave - new(SWSH) { Species = 620, Level = 28, Location = 174 }, // Mienshao on Challenge Road - new(SWSH) { Species = 625, Level = 36, Location = 174, Weather = Overcast }, // Bisharp on Challenge Road - new(SWSH) { Species = 758, Level = 28, Location = 174, Gender = 1, Weather = Intense_Sun }, // Salazzle on Challenge Road - new(SWSH) { Species = 475, Level = 32, Location = 174, Weather = Heavy_Fog }, // Gallade on Challenge Road - new(SWSH) { Species = 745, Level = 32, Location = 174 }, // Lycanroc on Challenge Road - new(SWSH) { Species = 745, Level = 32, Location = 174, Form = 01, Weather = Overcast }, // Lycanroc-1 on Challenge Road - new(SWSH) { Species = 212, Level = 40, Location = 174, Weather = Sandstorm }, // Scizor on Challenge Road - new(SW ) { Species = 127, Level = 26, Location = 174, Weather = Intense_Sun }, // Pinsir on Challenge Road - new( SH) { Species = 214, Level = 26, Location = 174, Weather = Intense_Sun }, // Heracross on Challenge Road - new(SW ) { Species = 782, Level = 22, Location = 174, Weather = Intense_Sun | Sandstorm | Heavy_Fog }, // Jangmo-o on Challenge Road - new(SWSH) { Species = 227, Level = 26, Location = 174, Weather = Normal | Raining | Intense_Sun | Sandstorm }, // Skarmory on Challenge Road - new(SWSH) { Species = 426, Level = 26, Location = 174, Weather = Heavy_Fog }, // Drifblim on Challenge Road - new(SW ) { Species = 628, Level = 26, Location = 174, Weather = Overcast }, // Braviary on Challenge Road - new( SH) { Species = 630, Level = 26, Location = 174, Weather = Overcast }, // Mandibuzz on Challenge Road - new(SWSH) { Species = 082, Level = 26, Location = 174, Weather = Thunderstorm }, // Magneton on Challenge Road - new EncounterStatic8S(SWSH) { Species = 507, Level = 28, Locations = new[] {174, 180}, Weather = Normal | Heavy_Fog }, // Herdier on Challenge Road, Training Lowlands - new(SWSH) { Species = 558, Level = 28, Location = 176 }, // Crustle in Courageous Cavern - new(SWSH) { Species = 768, Level = 32, Location = 176 }, // Golisopod in Courageous Cavern - new(SWSH) { Species = 528, Level = 28, Location = 176 }, // Swoobat in Courageous Cavern - new(SWSH) { Species = 834, Level = 28, Location = 176 }, // Drednaw in Courageous Cavern - new(SWSH) { Species = 621, Level = 42, Location = 176 }, // Druddigon in Courageous Cavern - new(SWSH) { Species = 847, Level = 42, Location = 176 }, // Barraskewda in the Courageous Cavern - new(SWSH) { Species = 073, Level = 42, Location = 176 }, // Tentacruel in Courageous Cavern - //new(SWSH) { Species = 526, Level = 42, Location = -1 }, // Gigalith - //new(SWSH) { Species = 113, Level = 30, Location = -1 }, // Chansey - //new(SWSH) { Species = 768, Level = 32, Location = -1 }, // Golisopod - //new(SWSH) { Species = 558, Level = 30, Location = -1 }, // Crustle - new(SWSH) { Species = 593, Level = 42, Location = 178, Weather = Overcast | Heavy_Fog }, // Jellicent in Loop Lagoon - new(SWSH) { Species = 687, Level = 32, Location = 178, Weather = Overcast | Raining }, // Malamar in Loop Lagoon - new(SWSH) { Species = 040, Level = 32, Location = 178, Weather = Heavy_Fog }, // Wigglytuff in Loop Lagoon - new(SWSH) { Species = 404, Level = 30, Location = 178, Weather = Thunderstorm }, // Luxio in Loop Lagoon - new(SWSH) { Species = 834, Level = 30, Location = 178, Weather = Normal | Intense_Sun }, // Drednaw in Loop Lagoon - new(SWSH) { Species = 871, Level = 22, Location = 178, Weather = Normal | Stormy | Heavy_Fog }, // Pincurchin in Loop Lagoon - new(SWSH) { Species = 748, Level = 26, Location = 178, Weather = Overcast | Raining | Heavy_Fog }, // Toxapex in Loop Lagoon - new(SWSH) { Species = 853, Level = 32, Location = 178, Weather = Normal | Overcast | Intense_Sun | Heavy_Fog }, // Grapploct in Loop Lagoon - new(SWSH) { Species = 770, Level = 32, Location = 178, Weather = Overcast | Heavy_Fog }, // Palossand in Loop Lagoon - new(SWSH) { Species = 065, Level = 50, Location = 178, Weather = Normal | Raining }, // Alakazam in Loop Lagoon - new(SWSH) { Species = 571, Level = 50, Location = 178, Weather = Overcast | Heavy_Fog }, // Zoroark in Loop Lagoon - new(SWSH) { Species = 462, Level = 50, Location = 178, Weather = Thunderstorm }, // Magnezone in Loop Lagoon - new(SWSH) { Species = 744, Level = 40, Location = 178 }, // Rockruff in Loop Lagoon - new(SWSH) { Species = 636, Level = 40, Location = 178, Weather = Intense_Sun }, // Larvesta in Loop Lagoon - new(SWSH) { Species = 279, Level = 42, Location = 178, Weather = Raining }, // Pelipper in Loop Lagoon - new(SWSH) { Species = 405, Level = 50, Location = 178, Weather = Thunderstorm }, // Luxray in Loop Lagoon - new(SWSH) { Species = 663, Level = 50, Location = 178, Weather = Intense_Sun }, // Talonflame in Loop Lagoon - new(SWSH) { Species = 508, Level = 42, Location = 180 }, // Stoutland in the Training Lowlands - new(SWSH) { Species = 625, Level = 36, Location = 180, Weather = Overcast }, // Bisharp in the Training Lowlands - new(SWSH) { Species = 405, Level = 36, Location = 180, Weather = Thunderstorm }, // Luxray in the Training Lowlands - new(SWSH) { Species = 663, Level = 36, Location = 180, Weather = Intense_Sun }, // Talonflame in the Training Lowlands - new(SWSH) { Species = 040, Level = 30, Location = 180, Weather = Heavy_Fog }, // Wigglytuff in the Training Lowlands - new(SWSH) { Species = 099, Level = 28, Location = 180, Weather = Normal | Overcast | Stormy | Intense_Sun | Sandstorm }, // Kingler in the Training Lowlands - new(SWSH) { Species = 115, Level = 32, Location = 180, Weather = Normal | Overcast }, // Kangaskhan in the Training Lowlands - new(SWSH) { Species = 123, Level = 28, Location = 180, Weather = Normal | Intense_Sun }, // Scyther in the Training Lowlands - new(SWSH) { Species = 404, Level = 28, Location = 180, Weather = Thunderstorm }, // Luxio in the Training Lowlands - new(SWSH) { Species = 764, Level = 28, Location = 180, Weather = Heavy_Fog }, // Comfey in the Training Lowlands - new(SWSH) { Species = 452, Level = 28, Location = 180, Weather = Overcast | Intense_Sun }, // Drapion in the Training Lowlands - new(SWSH) { Species = 279, Level = 28, Location = 180, Weather = Raining }, // Pelipper in the Training Lowlands - new(SW ) { Species = 127, Level = 28, Location = 180, Weather = Normal | Intense_Sun }, // Pinsir in the Training Lowlands - new( SH) { Species = 214, Level = 28, Location = 180, Weather = Normal | Intense_Sun }, // Heracross in the Training Lowlands - new(SWSH) { Species = 528, Level = 28, Location = 180, Weather = Overcast }, // Swoobat in the Training Lowlands - new(SWSH) { Species = 241, Level = 28, Location = 180, Weather = Normal | Overcast | Intense_Sun }, // Miltank in the Training Lowlands - new(SWSH) { Species = 082, Level = 28, Location = 180, Weather = Thunderstorm }, // Magneton in the Training Lowlands - new(SWSH) { Species = 662, Level = 28, Location = 180, Weather = Intense_Sun }, // Fletchinder in the Training Lowlands - new(SWSH) { Species = 227, Level = 26, Location = 180, Weather = Sandstorm }, // Skarmory in the Training Lowlands - new(SW ) { Species = 782, Level = 22, Location = 180, Weather = Sandstorm }, // Jangmo-o in Training Lowlands - new(SWSH) { Species = 128, Level = 28, Location = 180, Weather = All_IoA }, // Tauros in the Training Lowlands - new(SWSH) { Species = 687, Level = 28, Location = 180, Weather = Overcast | Raining }, // Malamar in the Training Lowlands - new(SWSH) { Species = 549, Level = 28, Location = 180, Weather = Intense_Sun }, // Lilligant in the Training Lowlands - new(SWSH) { Species = 426, Level = 28, Location = 180, Weather = Heavy_Fog }, // Drifblim in the Training Lowlands - new(SWSH) { Species = 055, Level = 26, Location = 180, Weather = Normal | Overcast | Stormy | Intense_Sun }, // Golduck in the Training Lowlands - new(SWSH) { Species = 184, Level = 26, Location = 180, Weather = Heavy_Fog }, // Azumarill in the Training Lowlands - new(SWSH) { Species = 617, Level = 36, Location = 180, Weather = Thunderstorm }, // Accelgor in the Training Lowlands - new(SWSH) { Species = 212, Level = 42, Location = 180, Weather = Sandstorm }, // Scizor in the Training Lowlands - new(SWSH) { Species = 589, Level = 36, Location = 180, Weather = Sandstorm }, // Escavalier in the Training Lowlands - new(SWSH) { Species = 616, Level = 26, Location = 180, Weather = Raining }, // Shelmet in the Training Lowlands - new(SWSH) { Species = 588, Level = 26, Location = 180, Weather = Overcast }, // Karrablast in the Training Lowlands - new(SWSH) { Species = 847, Level = 42, Location = 180, Weather = Normal | Stormy | Intense_Sun | Sandstorm | Heavy_Fog }, // Barraskewda in the Training Lowlands - new(SWSH) { Species = 553, Level = 50, Location = 184, Weather = Overcast | Raining }, // Krookodile in the Potbottom Desert - new(SWSH) { Species = 464, Level = 50, Location = 184, Weather = Normal | Intense_Sun | Sandstorm | Heavy_Fog }, // Rhyperior in the Potbottom Desert - new(SWSH) { Species = 105, Level = 42, Location = 184, Weather = Normal | Intense_Sun | Heavy_Fog }, // Marowak in the Potbottom Desert - new(SWSH) { Species = 552, Level = 42, Location = 184, Weather = Overcast | Raining }, // Krokorok in the Potbottom Desert - new(SWSH) { Species = 112, Level = 42, Location = 184, Weather = Normal | Intense_Sun | Sandstorm }, // Rhydon in the Potbottom Desert - new(SWSH) { Species = 324, Level = 42, Location = 184, Weather = Intense_Sun | Heavy_Fog }, // Torkoal in the Potbottom Desert - new(SWSH) { Species = 844, Level = 42, Location = 184, Weather = Normal | Intense_Sun | Heavy_Fog }, // Sandaconda in the Potbottom Desert - new(SWSH) { Species = 637, Level = 50, Location = 184, Weather = Intense_Sun }, // Volcarona in the Potbottom Desert - new(SWSH) { Species = 028, Level = 42, Location = 184, Weather = Sandstorm }, // Sandslash in the Potbottom Desert - new(SW ) { Species = 628, Level = 42, Location = 184, Weather = Normal | Overcast | Raining | Sandstorm | Intense_Sun | Heavy_Fog }, // Braviary in the Potbottom Desert - new( SH) { Species = 630, Level = 42, Location = 184, Weather = Normal | Overcast | Raining | Sandstorm | Intense_Sun | Heavy_Fog }, // Mandibuzz in the Potbottom Desert - new(SWSH) { Species = 479, Level = 50, Location = 186, FlawlessIVCount = 3 }, // Rotom in the Workout Sea - new(SWSH) { Species = 479, Level = 50, Location = 186, Moves = new[] {435,506,268}, Form = 01, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-1 in the Workout Sea - new(SWSH) { Species = 479, Level = 50, Location = 186, Moves = new[] {435,506,268}, Form = 02, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-2 in the Workout Sea - new(SWSH) { Species = 479, Level = 50, Location = 186, Moves = new[] {435,506,268}, Form = 03, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-3 in the Workout Sea - new(SWSH) { Species = 479, Level = 50, Location = 186, Moves = new[] {435,506,268}, Form = 04, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-4 in the Workout Sea - new(SWSH) { Species = 479, Level = 50, Location = 186, Moves = new[] {435,506,268}, Form = 05, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-5 in the Workout Sea - new(SWSH) { Species = 132, Level = 50, Location = 186, FlawlessIVCount = 3 }, // Ditto in the Workout Sea - //new(SWSH) { Species = 242, Level = 50, Location = -1 }, // Blissey - new(SWSH) { Species = 103, Level = 50, Location = 190, Weather = Normal | Raining | Intense_Sun }, // Exeggutor in the Insular Sea - new(SWSH) { Species = 571, Level = 50, Location = 190, Weather = Overcast }, // Zoroark in the Insular Sea - new(SWSH) { Species = 462, Level = 50, Location = 190, Weather = Thunderstorm }, // Magnezone in the Insular Sea - new(SWSH) { Species = 637, Level = 50, Location = 190, Weather = Intense_Sun }, // Volcarona in the Insular Sea - new(SWSH) { Species = 279, Level = 45, Location = 190, Weather = Overcast | Stormy }, // Pelipper in the Insular Sea - new(SWSH) { Species = 065, Level = 50, Location = 190, Weather = Heavy_Fog }, // Alakazam in the Insular Sea - new EncounterStatic8S(SWSH) { Species = 764, Level = 50, Locations = new[] {190, 194}, Weather = Heavy_Fog }, // Comfey in the Insular Sea, Honeycalm Sea - new(SWSH) { Species = 230, Level = 60, Location = 192, Weather = Thunderstorm }, // Kingdra in the Honeycalm Sea - new(SWSH) { Species = 117, Level = 45, Location = 192, Weather = Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Seadra in the Honeycalm Sea - new(SWSH) { Species = 549, Level = 45, Location = 194, Weather = Normal | Intense_Sun }, // Lilligant on Honeycalm Island - new(SWSH) { Species = 415, Level = 40, Location = 194, Weather = Overcast | Stormy }, // Combee on Honeycalm Island - #endregion - - #region R2 Static Encounters - new EncounterStatic8S(SWSH) { Species = 144, Level = 70, Locations = new[] {208, 210, 212, 214}, Moves = new[] {821,542,427,375}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Weather = All_CT }, // Articuno-1 in the Crown Tundra - new EncounterStatic8S(SWSH) { Species = 145, Level = 70, Locations = new[] {122, 124, 126, 128, 130}, Moves = new[] {823,065,179,116}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Weather = All }, // Zapdos-1 in a Wild Area - new EncounterStatic8S(SWSH) { Species = 146, Level = 70, Locations = new[] {164, 166, 170, 178, 186, 188, 190, 192}, Moves = new[] {822,542,389,417}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Weather = All_IoA }, // Moltres-1 on the Isle of Armor - new(SWSH) { Species = 377, Level = 70, Location = 236, ScriptedNoMarks = true, Moves = new[] {276,444,359,174}, FlawlessIVCount = 3, Ability = OnlyFirst }, // Regirock - new(SWSH) { Species = 378, Level = 70, Location = 238, ScriptedNoMarks = true, Moves = new[] {058,192,133,196}, FlawlessIVCount = 3, Ability = OnlyFirst }, // Regice - new(SWSH) { Species = 379, Level = 70, Location = 240, ScriptedNoMarks = true, Moves = new[] {484,430,334,451}, FlawlessIVCount = 3, Ability = OnlyFirst }, // Registeel - new(SWSH) { Species = 894, Level = 70, Location = 242, ScriptedNoMarks = true, Moves = new[] {819,527,245,393}, FlawlessIVCount = 3, Ability = OnlyFirst }, // Regieleki - new(SWSH) { Species = 895, Level = 70, Location = 242, ScriptedNoMarks = true, Moves = new[] {820,337,359,673}, FlawlessIVCount = 3, Ability = OnlyFirst }, // Regidrago - new(SWSH) { Species = 486, Level =100, Location = 210, ScriptedNoMarks = true, Moves = new[] {416,428,359,462}, FlawlessIVCount = 3, Ability = OnlyFirst, DynamaxLevel = 10 }, // Regigigas in the Giant’s Bed - new(SWSH) { Species = 638, Level = 70, Location = 226, FlawlessIVCount = 3, Ability = OnlyFirst, Weather = No_Sun_Sand }, // Cobalion at the Frigid Sea - new(SWSH) { Species = 639, Level = 70, Location = 232, FlawlessIVCount = 3, Ability = OnlyFirst, Weather = Overcast }, // Terrakion in Lakeside Cavern - new(SWSH) { Species = 640, Level = 70, Location = 210, FlawlessIVCount = 3, Ability = OnlyFirst, Weather = All_CT }, // Virizion at Giant's Bed - new(SWSH) { Species = 647, Level = 65, Location = 230, Moves = new[] {548,533,014,056}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Fateful = true, Weather = All_Ballimere }, // Keldeo-1 at Ballimere Lake - //new(SWSH) { Species = 896, Level = 75, Location = -1, Moves = new[] {556,037,419,023}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Glastrier - //new(SWSH) { Species = 897, Level = 75, Location = -1, Moves = new[] {247,037,506,024}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Spectrier - new(SWSH) { Species = 898, Level = 80, Location = 220, Moves = new[] {202,094,473,505}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, ScriptedNoMarks = true }, // Calyrex - new(SWSH) { Species = 442, Level = 72, Location = 230, FlawlessIVCount = 3, Ability = OnlyHidden, Weather = All_Ballimere }, // Spiritomb at Ballimere Lake - - // suspected unused or uncatchable - //new(SWSH) { Species = 803, Level = 60, Location = -1, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Poipole - //new(SWSH) { Species = 789, Level = 60, Location = -1, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Cosmog - //new(SWSH) { Species = 494, Level = 70, Location = -1, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Victini - - new(SWSH) { Species = 473, Level = 65, Location = 204, Weather = Normal | Overcast | Intense_Sun | Icy }, // Mamoswine on Slippery Slope - new(SWSH) { Species = 460, Level = 65, Location = 204, Weather = Snowing }, // Abomasnow on Slippery Slope - new(SWSH) { Species = 698, Level = 60, Location = 204, Weather = Normal | Overcast | Icy }, // Amaura on Slippery Slope - new(SWSH) { Species = 333, Level = 60, Location = 204, Weather = Overcast | Snowing }, // Swablu on Slippery Slope - new(SWSH) { Species = 124, Level = 62, Location = 204, Weather = Icy | Heavy_Fog }, // Jynx on Slippery Slope - new(SWSH) { Species = 857, Level = 62, Location = 204, Weather = Heavy_Fog }, // Hattrem on Slippery Slope - new(SWSH) { Species = 478, Level = 63, Location = 204, Weather = Snowstorm }, // Froslass on Slippery Slope - new(SWSH) { Species = 362, Level = 63, Location = 204, Weather = Snowstorm }, // Glalie on Slippery Slope - new(SWSH) { Species = 467, Level = 65, Location = 204, Weather = Intense_Sun }, // Magmortar on Slippery Slope - new(SWSH) { Species = 143, Level = 65, Location = 204, Weather = Normal | Intense_Sun }, // Snorlax on Slippery Slope - new(SWSH) { Species = 872, Level = 60, Location = 204, Weather = Normal | Heavy_Fog }, // Snom on Slippery Slope - new EncounterStatic8S(SWSH) { Species = 832, Level = 63, Locations = new[] {204, 208}, Weather = Normal | Intense_Sun }, // Dubwool on Slippery Slope, Frostpoint Field - new EncounterStatic8S(SW ) { Species = 576, Level = 65, Locations = new[] {204, 208}, Weather = Heavy_Fog }, // Gothitelle on Slippery Slope, Frostpoint Field - new EncounterStatic8S( SH) { Species = 579, Level = 65, Locations = new[] {204, 208}, Weather = Heavy_Fog }, // Reuniclus on Slippery Slope, Frostpoint Field - new EncounterStatic8S(SWSH) { Species = 461, Level = 63, Locations = new[] {204, 208}, Weather = Overcast }, // Weavile on Slippery Slope, Frostpoint Field - new EncounterStatic8S(SWSH) { Species = 531, Level = 62, Locations = new[] {204, 208}, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Audino on Slippery Slope, Frostpoint Field - new EncounterStatic8S(SWSH) { Species = 615, Level = 62, Locations = new[] {204, 208, 210}, Weather = Icy }, // Cryogonal on Slippery Slope, Frostpoint Field, Giant’s Bed - new EncounterStatic8S(SWSH) { Species = 778, Level = 62, Locations = new[] {204, 208, 210, 212}, Weather = Heavy_Fog }, // Mimikyu on Slippery Slope, Frostpoint Field, Giant’s Bed, Old Cemetery - new EncounterStatic8S(SWSH) { Species = 126, Level = 62, Locations = new[] {204, 210}, Weather = Intense_Sun }, // Magmar on Slippery Slope, Giant’s Bed - new EncounterStatic8S(SWSH) { Species = 861, Level = 65, Locations = new[] {204, 210}, Weather = Heavy_Fog }, // Grimmsnarl on Slippery Slope, Giant’s Bed - new EncounterStatic8S(SWSH) { Species = 709, Level = 63, Locations = new[] {204, 210, 212}, Weather = Overcast }, // Trevenant on Slippery Slope, Giant's Bed, Old Cemetery - new(SWSH) { Species = 124, Level = 62, Location = 208, Weather = Snowing | Heavy_Fog }, // Jynx in Frostpoint Field - new(SWSH) { Species = 460, Level = 65, Location = 208, Weather = Normal | Overcast | Intense_Sun | Snowing }, // Abomasnow in Frostpoint Field - new(SWSH) { Species = 698, Level = 60, Location = 208, Weather = Normal | Icy }, // Amaura in Frostpoint Field - new(SWSH) { Species = 133, Level = 60, Location = 208, Weather = Snowstorm }, // Eevee in Frostpoint Field - new(SWSH) { Species = 029, Level = 60, Location = 208, Weather = Normal | Overcast | Intense_Sun | Icy }, // Nidoran♀ in Frostpoint Field - new(SWSH) { Species = 032, Level = 60, Location = 208, Weather = Normal | Overcast | Intense_Sun | Icy }, // Nidoran♂ in Frostpoint Field - new(SWSH) { Species = 359, Level = 62, Location = 208, Weather = Snowstorm }, // Absol in Frostpoint Field - new(SWSH) { Species = 143, Level = 65, Location = 208, Weather = Normal | Stormy | Intense_Sun | Overcast }, // Snorlax in Frostpoint Field - new EncounterStatic8S(SWSH) { Species = 584, Level = 65, Locations = new[] {208, 210}, Weather = Icy }, // Vanilluxe in Frostpoint Field, Giant’s Bed - new EncounterStatic8S(SWSH) { Species = 333, Level = 60, Locations = new[] {208, 210}, Weather = Overcast }, // Swablu in Frostpoint Field, Giant’s Bed - new EncounterStatic8S(SWSH) { Species = 034, Level = 65, Locations = new[] {208, 210}, Weather = No_Sun_Sand }, // Nidoking in Frostpoint Field, Giant’s Bed - new EncounterStatic8S(SWSH) { Species = 030, Level = 63, Locations = new[] {208, 210}, Weather = All_CT }, // Nidorina in Frostpoint Field (c), in the Giant’s Bed - new EncounterStatic8S(SWSH) { Species = 858, Level = 65, Locations = new[] {208, 210}, Weather = Heavy_Fog }, // Hatterene in Frostpoint Field, Giant’s Bed - new EncounterStatic8S(SWSH) { Species = 437, Level = 65, Locations = new[] {208, 222}, Weather = Normal | Overcast }, // Bronzong in Frostpoint Field (c), Giant’s Foot - new(SWSH) { Species = 029, Level = 60, Location = 210, Weather = Normal | Stormy | Intense_Sun }, // Nidoran♀ in the Giant’s Bed - new(SWSH) { Species = 832, Level = 63, Location = 210 }, // Dubwool in the Giant’s Bed - new(SW ) { Species = 874, Level = 63, Location = 210, Weather = All_CT }, // Stonjourner in the Giant’s Bed - new( SH) { Species = 143, Level = 65, Location = 210, Weather = All_CT }, // Snorlax in the Giant’s Bed - new(SWSH) { Species = 142, Level = 65, Location = 210, Weather = All_CT }, // Aerodactyl in the Giant’s Bed - new(SWSH) { Species = 133, Level = 60, Location = 210 }, // Eevee in the Giant’s Bed - new(SWSH) { Species = 470, Level = 63, Location = 210 }, // Leafeon in the Giant’s Bed - new(SWSH) { Species = 033, Level = 63, Location = 210, Weather = All_CT }, // Nidorino in the Giant’s Bed - new(SWSH) { Species = 534, Level = 65, Location = 210 }, // Conkeldurr in the Giant’s Bed - new(SWSH) { Species = 820, Level = 65, Location = 210, Weather = No_Sun_Sand }, // Greedent in the Giant’s Bed - new(SWSH) { Species = 031, Level = 65, Location = 210, Weather = Normal | Overcast | Raining | Intense_Sun | Icy | Heavy_Fog }, // Nidoqueen in the Giant’s Bed - new(SWSH) { Species = 862, Level = 65, Location = 210, Weather = Overcast | Raining }, // Obstagoon in the Giant’s Bed - new(SWSH) { Species = 609, Level = 65, Location = 210, Weather = Overcast | Heavy_Fog }, // Chandelure in the Giant’s Bed - new(SWSH) { Species = 752, Level = 65, Location = 210, Weather = Stormy }, // Araquanid in the Giant’s Bed - new(SWSH) { Species = 134, Level = 63, Location = 210, Weather = Raining }, // Vaporeon in the Giant’s Bed - new(SWSH) { Species = 596, Level = 63, Location = 210, Weather = Thunderstorm }, // Galvantula in the Giant’s Bed - new(SWSH) { Species = 466, Level = 65, Location = 210, Weather = Thunderstorm }, // Electivire in the Giant’s Bed - new(SWSH) { Species = 135, Level = 63, Location = 210, Weather = Thunderstorm }, // Jolteon in the Giant’s Bed - new(SWSH) { Species = 125, Level = 63, Location = 210, Weather = Thunderstorm }, // Electabuzz in the Giant’s Bed - new(SWSH) { Species = 467, Level = 63, Location = 210, Weather = Intense_Sun }, // Magmortar in the Giant’s Bed - new(SWSH) { Species = 631, Level = 63, Location = 210, Weather = Intense_Sun }, // Heatmor in the Giant’s Bed - new(SWSH) { Species = 632, Level = 63, Location = 210, Weather = Intense_Sun }, // Durant in the Giant’s Bed - new(SWSH) { Species = 136, Level = 63, Location = 210, Weather = Intense_Sun }, // Flareon in the Giant’s Bed - new(SWSH) { Species = 197, Level = 63, Location = 210, Weather = Overcast }, // Umbreon in the Giant’s Bed - new(SWSH) { Species = 196, Level = 63, Location = 210, Weather = Snowing }, // Espeon in the Giant’s Bed - new(SWSH) { Species = 359, Level = 65, Location = 210, Weather = Snowstorm }, // Absol in the Giant’s Bed - new(SWSH) { Species = 471, Level = 63, Location = 210, Weather = Snowstorm }, // Glaceon in the Giant’s Bed - new(SWSH) { Species = 700, Level = 63, Location = 210, Weather = Heavy_Fog }, // Sylveon in the Giant’s Bed - new(SWSH) { Species = 036, Level = 63, Location = 210, Weather = Heavy_Fog }, // Clefable in the Giant’s Bed - new(SWSH) { Species = 340, Level = 65, Location = 210, Weather = All_CT }, // Whiscash in the Giant’s Bed - new EncounterStatic8S(SWSH) { Species = 855, Level = 63, Locations = new[] {210, 212}, Weather = Normal | Stormy | Intense_Sun | Snowstorm | Heavy_Fog }, // Polteageist in the Giant’s Bed, Old Cemetery - new EncounterStatic8S(SWSH) { Species = 887, Level = 65, Locations = new[] {210, 212}, Weather = Normal | Overcast | Stormy | Intense_Sun | Snowing }, // Dragapult in the Giant’s Bed, Old Cemetery - new EncounterStatic8S(SWSH) { Species = 478, Level = 65, Locations = new[] {210, 212, 214}, Weather = Icy }, // Froslass in the Giant’s Bed, Old Cemetery, Snowslide Slope - new EncounterStatic8S(SWSH) { Species = 437, Level = 63, Locations = new[] {210, 214}, Weather = All_CT }, // Bronzong in the Giant’s Bed, Snowslide Slope (c) - new EncounterStatic8S(SWSH) { Species = 362, Level = 65, Locations = new[] {210, 214}, Weather = Icy }, // Glalie in the Giant’s Bed, Snowslide Slope - new EncounterStatic8S(SWSH) { Species = 334, Level = 65, Locations = new[] {210, 218, 222, 226, 230}, Weather = Overcast }, // Altaria in the Giant’s Bed, Path to the Peak, Giant’s Foot, Frigid Sea, Ballimere Lake - new EncounterStatic8S(SWSH) { Species = 344, Level = 65, Locations = new[] {210, 222}, Weather = Overcast | Stormy | Intense_Sun | Icy | Heavy_Fog }, // Claydol in the Giant’s Bed, Giant’s Foot - new EncounterStatic8S(SWSH) { Species = 531, Level = 62, Locations = new[] {210, 222, 230}, Weather = All_CT }, // Audino in the Giant’s Bed, Giant’s Foot - new EncounterStatic8S(SWSH) { Species = 130, Level = 67, Locations = new[] {210, 230}, Weather = Normal | Overcast | Stormy | Intense_Sun | Icy }, // Gyarados in the Giant’s Bed, Ballimere Lake - new EncounterStatic8S(SWSH) { Species = 350, Level = 67, Locations = new[] {210, 230}, Weather = Heavy_Fog }, // Milotic in the Giant’s Bed, Ballimere Lake - new( SH) { Species = 078, Level = 67, Location = 212, Form = 01, Weather = Heavy_Fog }, // Rapidash-1 in the Old Cemetery - new(SWSH) { Species = 872, Level = 62, Location = 214, Weather = Normal | Overcast }, // Snom on Snowslide Slope - new(SWSH) { Species = 698, Level = 62, Location = 214, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Amaura on Snowslide Slope - new(SWSH) { Species = 621, Level = 65, Location = 214, Weather = Normal | Intense_Sun }, // Druddigon on Snowslide Slope - new(SWSH) { Species = 832, Level = 65, Location = 214, Weather = Normal | Intense_Sun }, // Dubwool on Snowslide Slope - new(SWSH) { Species = 699, Level = 65, Location = 214, Weather = Normal | Overcast | Icy | Heavy_Fog }, // Aurorus on Snowslide Slope - new(SWSH) { Species = 376, Level = 68, Location = 214, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Metagross on Snowslide Slope - new(SWSH) { Species = 461, Level = 65, Location = 214, Weather = Overcast }, // Weavile on Snowslide Slope - new(SWSH) { Species = 709, Level = 65, Location = 214, Weather = Overcast }, // Trevenant on Snowslide Slope - new(SWSH) { Species = 467, Level = 67, Location = 214, Weather = Intense_Sun }, // Magmortar on Snowslide Slope - new(SWSH) { Species = 362, Level = 67, Location = 214, Weather = Icy }, // Glalie on Snowslide Slope - new EncounterStatic8S(SWSH) { Species = 375, Level = 63, Locations = new[] {214, 216 }, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Metang on Snowslide Slope, Tunnel to the Top (c) - new EncounterStatic8S(SWSH) { Species = 615, Level = 65, Locations = new[] {214, 222}, Weather = Icy }, // Cryogonal on Snowslide Slope, Giant’s Foot - new EncounterStatic8S(SWSH) { Species = 614, Level = 67, Locations = new[] {214, 226, 228}, Weather = Icy }, // Beartic on Snowslide Slope, Frigid Sea, Three-Point Pass - new EncounterStatic8S(SWSH) { Species = 126, Level = 65, Locations = new[] {214, 230}, Weather = Intense_Sun }, // Magmar on Snowslide Slope, Ballimere Lake - new(SWSH) { Species = 584, Level = 67, Location = 214, Weather = Icy }, // Vanilluxe on Snowslide Slope - new(SW ) { Species = 555, Level = 67, Location = 214, Form = 02, Weather = Snowstorm }, // Darmanitan-2 on Snowslide Slope - new(SWSH) { Species = 861, Level = 67, Location = 214, Weather = Heavy_Fog }, // Grimmsnarl on Snowslide Slope - new EncounterStatic8S(SWSH) { Species = 359, Level = 67, Locations = new[] {214, 218, 222}, Weather = Snowstorm }, // Absol on Snowslide Slope, Path to the Peak, Giant’s Foot - new EncounterStatic8S(SWSH) { Species = 778, Level = 65, Locations = new[] {214, 222, 230}, Weather = Heavy_Fog }, // Mimikyu on Snowslide Slope, Giant’s Foot, Ballimere Lake - new(SWSH) { Species = 036, Level = 65, Location = 214, Weather = Heavy_Fog }, // Clefable on Snowslide Slope - new(SWSH) { Species = 036, Level = 65, Location = 216, Weather = Overcast }, // Clefable in the Tunnel to the Top - new(SWSH) { Species = 621, Level = 65, Location = 216, Weather = Overcast }, // Druddigon in the Tunnel to the Top - new(SWSH) { Species = 478, Level = 65, Location = 216, Weather = Overcast }, // Froslass in the Tunnel to the Top - new(SW ) { Species = 371, Level = 65, Location = 216, Weather = Overcast }, // Bagon in the Tunnel to the Top - new( SH) { Species = 443, Level = 65, Location = 216, Weather = Overcast }, // Gible in the Tunnel to the Top - new(SW ) { Species = 373, Level = 68, Location = 216, Weather = Overcast }, // Salamence in the Tunnel to the Top - new( SH) { Species = 445, Level = 68, Location = 216, Weather = Overcast }, // Garchomp in the Tunnel to the Top - new(SWSH) { Species = 703, Level = 65, Location = 216, Weather = Overcast }, // Carbink in the Tunnel to the Top - new EncounterStatic8S(SWSH) { Species = 041, Level = 63, Locations = new[] {216, 224}, Weather = Overcast }, // Zubat in the Tunnel to the Top, Roaring-Sea Caves - new(SWSH) { Species = 042, Level = 65, Location = 216, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Golbat in the Tunnel to the Top - new(SWSH) { Species = 873, Level = 65, Location = 218, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Frosmoth on the Path to the Peak - new(SW ) { Species = 373, Level = 68, Location = 218, Weather = Intense_Sun }, // Salamence on the Path to the Peak - new( SH) { Species = 445, Level = 68, Location = 218, Weather = Intense_Sun }, // Garchomp on the Path to the Peak - new(SWSH) { Species = 621, Level = 65, Location = 218 }, // Druddigon on the Path to the Peak - new(SWSH) { Species = 851, Level = 67, Location = 222, Weather = Normal | Intense_Sun }, // Centiskorch at the Giant’s Foot - new(SWSH) { Species = 879, Level = 67, Location = 222, Weather = Overcast | Stormy | Icy | Heavy_Fog }, // Copperajah at the Giant’s Foot - new(SWSH) { Species = 534, Level = 67, Location = 222 }, // Conkeldurr at the Giant’s Foot - new(SW ) { Species = 138, Level = 63, Location = 222, Weather = All_CT }, // Omanyte at the Giant’s Foot - new( SH) { Species = 140, Level = 63, Location = 222, Weather = All_CT }, // Kabuto at the Giant’s Foot - new(SWSH) { Species = 566, Level = 63, Location = 222, Weather = All_CT }, // Archen at the Giant’s Foot - new(SWSH) { Species = 126, Level = 65, Location = 222, Weather = Intense_Sun }, // Magmar at the Giant’s Foot - new EncounterStatic8S(SWSH) { Species = 752, Level = 67, Locations = new[] {222, 230}, Weather = Raining }, // Araquanid at Ballimere Lake, Giant’s Foot - new EncounterStatic8S(SWSH) { Species = 125, Level = 65, Locations = new[] {222, 230}, Weather = Thunderstorm }, // Electabuzz at the Giant’s Foot, Ballimere Lake - //new(SWSH) { Species = 567, Level = 67, Location = -1 }, // Archeops - new(SW ) { Species = 635, Level = 68, Location = 224, Weather = No_Sun_Sand }, // Hydreigon in Roaring-Sea Caves, weather from Frigid Sea - new( SH) { Species = 248, Level = 68, Location = 224, Weather = No_Sun_Sand }, // Tyranitar in Roaring-Sea Caves, weather from Frigid Sea - new(SWSH) { Species = 448, Level = 67, Location = 224, Weather = Overcast }, // Lucario in Roaring-Sea Caves - new(SWSH) { Species = 042, Level = 65, Location = 224, Weather = Overcast }, // Golbat in the Roaring-Sea Caves - new( SH) { Species = 141, Level = 68, Location = 224, Weather = Overcast }, // Kabutops in Roaring-Sea Caves - new(SW ) { Species = 139, Level = 68, Location = 224, Weather = Overcast }, // Omastar in Roaring-Sea Caves - new(SWSH) { Species = 363, Level = 63, Location = 226, Weather = No_Sun_Sand }, // Spheal at the Frigid Sea - new(SWSH) { Species = 364, Level = 65, Location = 226, Weather = No_Sun_Sand }, // Sealeo at the Frigid Sea - new(SWSH) { Species = 564, Level = 63, Location = 226, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Tirtouga at the Frigid Sea - new(SWSH) { Species = 713, Level = 65, Location = 226, Weather = No_Sun_Sand }, // Avalugg at the Frigid Sea - new(SWSH) { Species = 365, Level = 68, Location = 226, Weather = Normal | Overcast | Icy | Heavy_Fog }, // Walrein at the Frigid Sea - new(SWSH) { Species = 565, Level = 67, Location = 226, Weather = Normal | Stormy | Intense_Sun }, // Carracosta at the Frigid Sea - new(SWSH) { Species = 871, Level = 65, Location = 226, Weather = Thunderstorm }, // Pincurchin at the Frigid Sea - new( SH) { Species = 875, Level = 65, Location = 226, Weather = No_Sun_Sand }, // Eiscue at the Frigid Sea - new EncounterStatic8S(SWSH) { Species = 623, Level = 65, Locations = new[] {226, 228}, Weather = All_CT }, // Golurk at the Frigid Sea (c), Three-Point Pass - new EncounterStatic8S(SWSH) { Species = 467, Level = 68, Locations = new[] {226, 230}, Weather = Intense_Sun }, // Magmortar at Frigid Sea (c), Ballimere Lake - new EncounterStatic8S(SWSH) { Species = 466, Level = 68, Locations = new[] {226, 228, 230}, Weather = Thunderstorm }, // Electivire at the Frigid Sea, Three-Point Pass, Ballimere Lake - new EncounterStatic8S(SWSH) { Species = 858, Level = 67, Locations = new[] {226, 230}, Weather = Heavy_Fog }, // Hatterene at the Frigid Sea, Ballimere Lake - new(SWSH) { Species = 887, Level = 68, Location = 228, Weather = Normal | Overcast | Raining | Intense_Sun | Icy | Heavy_Fog }, // Dragapult in Three-Point Pass - new(SWSH) { Species = 531, Level = 62, Location = 230, Weather = Normal | Overcast | Stormy | Intense_Sun | Snowing }, // Audino at Ballimere Lake - new(SWSH) { Species = 584, Level = 67, Location = 230, Weather = Snowing }, // Vanilluxe at Ballimere Lake - new(SWSH) { Species = 823, Level = 68, Location = 230, Weather = Normal | Icy }, // Corviknight at Ballimere Lake - new(SWSH) { Species = 862, Level = 68, Location = 230, Weather = Overcast }, // Obstagoon at Ballimere Lake - new(SWSH) { Species = 715, Level = 67, Location = 230, Weather = Overcast | Raining }, // Noivern at Ballimere Lake - new(SWSH) { Species = 547, Level = 65, Location = 230, Weather = Normal | Raining }, // Whimsicott at Ballimere Lake - new(SWSH) { Species = 836, Level = 67, Location = 230, Weather = Normal | Stormy | Snowing | Heavy_Fog }, // Boltund at Ballimere Lake - new(SWSH) { Species = 830, Level = 65, Location = 230, Weather = Raining | Intense_Sun }, // Eldegoss at Ballimere Lake - new(SW ) { Species = 876, Level = 65, Location = 230, Weather = Normal | Heavy_Fog }, // Indeedee at Ballimere Lake - new( SH) { Species = 876, Level = 65, Location = 230, Form = 01, Weather = Normal | Heavy_Fog }, // Indeedee-1 at Ballimere Lake - new(SWSH) { Species = 696, Level = 63, Location = 230, Weather = All_CT }, // Tyrunt at Ballimere Lake - new(SWSH) { Species = 213, Level = 65, Location = 230, Weather = Normal | Intense_Sun }, // Shuckle at Ballimere Lake - new(SWSH) { Species = 820, Level = 68, Location = 230, Weather = All_Ballimere }, // Greedent at Ballimere Lake - new(SWSH) { Species = 877, Level = 65, Location = 230, Weather = Overcast | Thunderstorm }, // Morpeko at Ballimere Lake - new(SWSH) { Species = 596, Level = 67, Location = 230, Weather = Thunderstorm }, // Galvantula at Ballimere Lake - new(SWSH) { Species = 839, Level = 68, Location = 230, Weather = Normal | Thunderstorm | Intense_Sun | Snowing | Heavy_Fog }, // Coalossal at Ballimere Lake - new(SWSH) { Species = 697, Level = 69, Location = 230, Weather = All_Ballimere }, // Tyrantrum at Ballimere Lake - new(SWSH) { Species = 531, Level = 65, Location = 230, Weather = Heavy_Fog }, // Audino at Ballimere Lake - new(SWSH) { Species = 304, Level = 63, Location = 230, Weather = All_Ballimere }, // Aron at Ballimere Lake - new(SWSH) { Species = 149, Level = 70, Location = 230, Weather = Raining | Thunderstorm }, // Dragonite at Ballimere Lake - new(SWSH) { Species = 348, Level = 67, Location = 230, Weather = Raining | Thunderstorm }, // Armaldo at Ballimere Lake - new(SWSH) { Species = 347, Level = 63, Location = 230, Weather = All_Ballimere }, // Anorith at Ballimere Lake - new(SWSH) { Species = 369, Level = 65, Location = 230, Weather = Normal | Overcast | Stormy | Intense_Sun | Snowing }, // Relicanth at Ballimere Lake - new(SWSH) { Species = 147, Level = 63, Location = 230, Weather = Raining | Heavy_Fog }, // Dratini at Ballimere Lake - new(SWSH) { Species = 148, Level = 65, Location = 230, Weather = Thunderstorm | Heavy_Fog }, // Dragonair at Ballimere Lake - new(SWSH) { Species = 615, Level = 65, Location = 230, Weather = Icy }, // Cryogonal at Ballimere Lake - new(SWSH) { Species = 715, Level = 67, Location = 232, Weather = Overcast }, // Noivern in Lakeside Cave - new(SWSH) { Species = 306, Level = 68, Location = 232, Weather = Overcast }, // Aggron in Lakeside Cave - new(SWSH) { Species = 598, Level = 67, Location = 232, Weather = All_Ballimere }, // Ferrothorn in Lakeside Cave - new(SWSH) { Species = 305, Level = 63, Location = 232, Weather = Overcast }, // Lairon in Lakeside Cave - new(SWSH) { Species = 839, Level = 68, Location = 232, Weather = Overcast }, // Coalossal in Lakeside Cave - new(SWSH) { Species = 820, Level = 68, Location = 234, Weather = All_Ballimere }, // Greedent at Dyna Tree Hill - #endregion - }; - - private const string tradeSWSH = "tradeswsh"; - private static readonly string[][] TradeSWSH = Util.GetLanguageStrings10(tradeSWSH, "zh2"); - private static readonly string[] TradeOT_R1 = { string.Empty, "チホコ", "Regina", "Régiona", "Regionalia", "Regine", string.Empty, "Tatiana", "지민", "易蒂", "易蒂" }; - private static readonly int[] TradeIVs = {15, 15, 15, 15, 15, 15}; - - private static readonly EncounterTrade8[] TradeGift_Regular = - { - new(SWSH, 052,18,08,000,04,5) { Ability = OnlySecond, TID7 = 263455, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Timid, Relearn = new[] {387,000,000,000} }, // Meowth - new(SWSH, 819,10,01,044,01,2) { Ability = OnlyFirst, TID7 = 648753, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 0, Nature = Nature.Mild }, // Skwovet - new(SWSH, 546,23,11,000,09,5) { Ability = OnlyFirst, TID7 = 101154, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 1, Nature = Nature.Modest }, // Cottonee - new(SWSH, 175,25,02,010,10,6) { Ability = OnlySecond, TID7 = 109591, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 0, Nature = Nature.Timid, Relearn = new[] {791,000,000,000} }, // Togepi - new(SW , 856,30,09,859,08,3) { Ability = OnlySecond, TID7 = 101101, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 1, Nature = Nature.Quiet }, // Hatenna - new( SH, 859,30,43,000,07,6) { Ability = OnlyFirst, TID7 = 256081, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Brave, Relearn = new[] {252,000,000,000} }, // Impidimp - new(SWSH, 562,35,16,310,15,5) { Ability = OnlyFirst, TID7 = 102534, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 1, Gender = 0, Nature = Nature.Bold, Relearn = new[] {261,000,000,000} }, // Yamask - new(SW , 538,37,17,129,20,7) { Ability = OnlySecond, TID7 = 768945, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 0, Gender = 0, Nature = Nature.Adamant }, // Throh - new( SH, 539,37,17,129,14,6) { Ability = OnlyFirst, TID7 = 881426, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 0, Gender = 0, Nature = Nature.Adamant }, // Sawk - new(SWSH, 122,40,56,000,12,4) { Ability = OnlyFirst, TID7 = 891846, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Calm }, // Mr. Mime - new(SWSH, 884,50,15,038,06,2) { Ability = OnlySecond, TID7 = 101141, IVs = TradeIVs, DynamaxLevel = 3, OTGender = 0, Gender = 0, Nature = Nature.Adamant, Relearn = new[] {400,000,000,000} }, // Duraludon - }; - - private static readonly EncounterTrade8[] TradeGift_R1 = - { - new(SWSH, 052,15,01,033,04,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {387,000,000,000} }, // Meowth - new(SW , 083,15,01,013,10,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {098,000,000,000} }, // Farfetch’d - new( SH, 222,15,01,069,12,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {457,000,000,000} }, // Corsola - new( SH, 077,15,01,047,06,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {234,000,000,000} }, // Ponyta - new(SWSH, 122,15,01,005,04,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {252,000,000,000} }, // Mr. Mime - new(SW , 554,15,01,040,12,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {326,000,000,000} }, // Darumaka - new(SWSH, 263,15,01,045,04,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {245,000,000,000} }, // Zigzagoon - new(SWSH, 618,15,01,050,05,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {281,000,000,000} }, // Stunfisk - new(SWSH, 110,15,01,040,12,2, Random) { Ability = Any12H, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {220,000,000,000} }, // Weezing - new(SWSH, 103,15,01,038,06,2, Random) { TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {246,000,000,000}, Form = 1 }, // Exeggutor-1 - new(SWSH, 105,15,01,038,06,2, Random) { TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {174,000,000,000}, Form = 1 }, // Marowak-1 - }; - - internal static readonly EncounterTrade8[] TradeGift_SWSH = ArrayUtil.ConcatAll(TradeGift_Regular, TradeGift_R1); - - internal static readonly EncounterStatic[] StaticSW = ArrayUtil.ConcatAll(Nest_Common, Nest_SW, Nest_SH, Dist_DLC2, Dist_DLC1, Dist_Base, GetEncounters(Crystal_SWSH, SW), DynAdv_SWSH, GetEncounters(Encounter_SWSH, SW)); - internal static readonly EncounterStatic[] StaticSH = ArrayUtil.ConcatAll(Nest_Common, Nest_SW, Nest_SH, Dist_DLC2, Dist_DLC1, Dist_Base, GetEncounters(Crystal_SWSH, SH), DynAdv_SWSH, GetEncounters(Encounter_SWSH, SH)); + MarkEncounterTradeStrings(TradeGift_SWSH, TradeSWSH); } + + private static readonly EncounterStatic8[] Encounter_SWSH = + { + // gifts + new(SWSH) { Gift = true, Species = 810, Shiny = Never, Level = 05, Location = 006 }, // Grookey + new(SWSH) { Gift = true, Species = 813, Shiny = Never, Level = 05, Location = 006 }, // Scorbunny + new(SWSH) { Gift = true, Species = 816, Shiny = Never, Level = 05, Location = 006 }, // Sobble + + new(SWSH) { Gift = true, Species = 772, Shiny = Never, Level = 50, Location = 158, FlawlessIVCount = 3 }, // Type: Null + new(SWSH) { Gift = true, Species = 848, Shiny = Never, Level = 01, Location = 040, IVs = new[]{-1,31,-1,-1,31,-1}, Ball = 11 }, // Toxel, Attack flawless + + new(SWSH) { Gift = true, Species = 880, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Dracozolt @ Route 6 + new(SWSH) { Gift = true, Species = 881, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Arctozolt @ Route 6 + new(SWSH) { Gift = true, Species = 882, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Dracovish @ Route 6 + new(SWSH) { Gift = true, Species = 883, FlawlessIVCount = 3, Level = 10, Location = 068 }, // Arctovish @ Route 6 + + new(SWSH) { Gift = true, Species = 004, Shiny = Never, Level = 05, Location = 006, FlawlessIVCount = 3, CanGigantamax = true, Ability = OnlyFirst }, // Charmander + new(SWSH) { Gift = true, Species = 025, Shiny = Never, Level = 10, Location = 156, FlawlessIVCount = 6, CanGigantamax = true }, // Pikachu + new(SWSH) { Gift = true, Species = 133, Shiny = Never, Level = 10, Location = 156, FlawlessIVCount = 6, CanGigantamax = true }, // Eevee + + // DLC gifts + new(SWSH) { Gift = true, Species = 001, Level = 05, Location = 196, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, CanGigantamax = true }, // Bulbasaur + new(SWSH) { Gift = true, Species = 007, Level = 05, Location = 196, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, CanGigantamax = true }, // Squirtle + new(SWSH) { Gift = true, Species = 137, Level = 25, Location = 196, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Porygon + new(SWSH) { Gift = true, Species = 891, Level = 10, Location = 196, Shiny = Never, FlawlessIVCount = 3 }, // Kubfu + + new(SWSH) { Gift = true, Species = 079, Level = 10, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Slowpoke + new(SWSH) { Gift = true, Species = 722, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Rowlet + new(SWSH) { Gift = true, Species = 725, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Litten + new(SWSH) { Gift = true, Species = 728, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3 }, // Popplio + new(SWSH) { Gift = true, Species = 026, Level = 30, Location = 164, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3, Form = 01 }, // Raichu-1 + new(SWSH) { Gift = true, Species = 027, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Sandshrew-1 + new(SWSH) { Gift = true, Species = 037, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Vulpix-1 + new(SWSH) { Gift = true, Species = 052, Level = 05, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Meowth-1 + new(SWSH) { Gift = true, Species = 103, Level = 30, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Exeggutor-1 + new(SWSH) { Gift = true, Species = 105, Level = 30, Location = 164, Shiny = Never, Ability = OnlyHidden, FlawlessIVCount = 3, Form = 01 }, // Marowak-1 + new(SWSH) { Gift = true, Species = 050, Level = 20, Location = 164, Shiny = Never, Ability = OnlyHidden, Gender = 0, Nature = Nature.Jolly, FlawlessIVCount = 6, Form = 01 }, // Diglett-1 + + new(SWSH) { Gift = true, Species = 789, Level = 05, Location = 206, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Cosmog + new(SWSH) { Gift = true, Species = 803, Level = 20, Location = 244, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Ball = 26 }, // Poipole + + // Technically a gift, but copies ball from Calyrex. + new(SWSH) { Species = 896, Level = 75, Location = 220, ScriptedNoMarks = true, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Relearn = new[] {556,0,0,0} }, // Glastrier + new(SWSH) { Species = 897, Level = 75, Location = 220, ScriptedNoMarks = true, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Relearn = new[] {247,0,0,0} }, // Spectrier + + #region Static Part 1 + // encounters + new(SW ) { Species = 888, Level = 70, Location = 66, ScriptedNoMarks = true, Moves = new[] {533,014,442,242}, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Zacian + new( SH) { Species = 889, Level = 70, Location = 66, ScriptedNoMarks = true, Moves = new[] {163,242,442,334}, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Zamazenta + new(SWSH) { Species = 890, Level = 60, Location = 66, ScriptedNoMarks = true, Moves = new[] {440,406,053,744}, Shiny = Never, Ability = OnlyFirst, FlawlessIVCount = 3 }, // Eternatus-1 (reverts to form 0) + + // Motostoke Stadium Static Encounters + new(SWSH) { Species = 037, Level = 24, Location = 24, ScriptedNoMarks = true }, // Vulpix at Motostoke Stadium + //new( SH) { Species = 058, Level = 24, Location = 24, ScriptedNoMarks = true }, // Growlithe at Motostoke Stadium (both versions have Vulpix) + new(SWSH) { Species = 607, Level = 25, Location = 24, ScriptedNoMarks = true }, // Litwick at Motostoke Stadium + new(SWSH) { Species = 850, Level = 25, Location = 24, ScriptedNoMarks = true, FlawlessIVCount = 3 }, // Sizzlipede at Motostoke Stadium + + new(SWSH) { Species = 618, Level = 25, Location = 054, Moves = new[] {389,319,279,341}, Form = 01, Ability = OnlyFirst }, // Stunfisk in Galar Mine No. 2 + new(SWSH) { Species = 618, Level = 48, Location = 008, Moves = new[] {779,330,340,334}, Form = 01 }, // Stunfisk in the Slumbering Weald + new(SWSH) { Species = 527, Level = 16, Location = 030, Moves = new[] {000,000,000,000} }, // Woobat in Galar Mine + new(SWSH) { Species = 838, Level = 18, Location = 030, Moves = new[] {488,397,229,033} }, // Carkol in Galar Mine + new(SWSH) { Species = 834, Level = 24, Location = 054, Moves = new[] {317,029,055,044} }, // Drednaw in Galar Mine No. 2 + new(SWSH) { Species = 423, Level = 50, Location = 054, Moves = new[] {240,414,330,246}, FlawlessIVCount = 3, Form = 01 }, // Gastrodon in Galar Mine No. 2 + new(SWSH) { Species = 859, Level = 31, Location = 076, ScriptedNoMarks = true, Moves = new[] {259,389,207,372} }, // Impidimp in Glimwood Tangle + new(SWSH) { Species = 860, Level = 38, Location = 076, ScriptedNoMarks = true, Moves = new[] {793,399,259,389} }, // Morgrem in Glimwood Tangle + new(SWSH) { Species = 835, Level = 08, Location = 018, Moves = new[] {039,033,609,000} }, // Yamper on Route 2 + new(SWSH) { Species = 834, Level = 50, Location = 018, Moves = new[] {710,746,068,317}, FlawlessIVCount = 3 }, // Drednaw on Route 2 + new(SWSH) { Species = 833, Level = 08, Location = 018, Moves = new[] {044,055,000,000} }, // Chewtle on Route 2 + new(SWSH) { Species = 131, Level = 55, Location = 018, Moves = new[] {056,240,058,034}, FlawlessIVCount = 3 }, // Lapras on Route 2 + new(SWSH) { Species = 862, Level = 50, Location = 018, Moves = new[] {269,068,792,184} }, // Obstagoon on Route 2 + new(SWSH) { Species = 822, Level = 18, Location = 028, Moves = new[] {681,468,031,365}, Shiny = Never }, // Corvisquire on Route 3 + new(SWSH) { Species = 050, Level = 17, Location = 032, Moves = new[] {523,189,310,045} }, // Diglett on Route 4 + new(SWSH) { Species = 830, Level = 22, Location = 040, Moves = new[] {178,496,075,047} }, // Eldegoss on Route 5 + new(SWSH) { Species = 558, Level = 40, Location = 086, Moves = new[] {404,350,446,157} }, // Crustle on Route 8 + new(SWSH) { Species = 870, Level = 40, Location = 086, Moves = new[] {748,660,179,203} }, // Falinks on Route 8 + new(SWSH) { Species = 362, Level = 55, Location = 090, Moves = new[] {573,329,104,182}, FlawlessIVCount = 3, Weather = Snowing }, // Glalie on Route 9 + new(SWSH) { Species = 853, Level = 50, Location = 092, Moves = new[] {753,576,276,179}, Weather = Snowing }, // Grapploct on Route 9 (in Circhester Bay) + //new(SWSH) { Species = 822, Level = 35, Location = -1, Moves = new[] {065,184,269,365} }, // Corvisquire + new(SWSH) { Species = 614, Level = 55, Location = 106, Moves = new[] {276,059,156,329}, Weather = Snowstorm }, // Beartic on Route 10 + new(SWSH) { Species = 460, Level = 55, Location = 106, Moves = new[] {008,059,452,275}, Weather = Snowstorm }, // Abomasnow on Route 10 + new(SWSH) { Species = 342, Level = 50, Location = 034, Moves = new[] {242,014,534,400}, FlawlessIVCount = 3 }, // Crawdaunt in the town of Turffield + #endregion + + #region Static Part 2 + // Some of these may be crossover cases. For now, just log the locations they can show up in and re-categorize later. + new(SWSH) { Species = 095, Level = 26, Location = 122, Weather = All }, // Onix in the Rolling Fields + new(SWSH) { Species = 291, Level = 15, Location = 122, Weather = All }, // Ninjask in the Rolling Fields + new(SWSH) { Species = 660, Level = 15, Location = 122, Weather = All }, // Diggersby in the Rolling Fields + new(SWSH) { Species = 832, Level = 65, Location = 122, Weather = All }, // Dubwool in the Rolling Fields + new(SWSH) { Species = 416, Level = 26, Location = 122 }, // Vespiquen in the Rolling Fields + new(SWSH) { Species = 675, Level = 32, Location = 122, Weather = Normal | Overcast | Stormy | Intense_Sun }, // Pangoro in the Rolling Fields + new(SWSH) { Species = 315, Level = 15, Location = 122, Weather = Normal | Overcast | Raining | Intense_Sun | Snowing | Sandstorm | Heavy_Fog }, // Roselia in the Rolling Fields + new(SWSH) { Species = 750, Level = 31, Location = 122, Weather = Normal | Overcast | Intense_Sun | Sandstorm | Heavy_Fog }, // Mudsdale in the Rolling Fields + new(SWSH) { Species = 310, Level = 26, Location = 122, Weather = Thunderstorm }, // Manectric in the Rolling Fields + new(SWSH) { Species = 025, Level = 15, Location = 122, Weather = Thunderstorm }, // Pikachu in the Rolling Fields + new(SWSH) { Species = 660, Level = 26, Location = 122, Weather = Overcast | Intense_Sun | Icy | Sandstorm}, // Diggersby in the Rolling Fields + new(SWSH) { Species = 281, Level = 26, Location = 122, Weather = Heavy_Fog }, // Kirlia in the Rolling Fields + new(SWSH) { Species = 282, Level = 32, Location = 122, Weather = Heavy_Fog }, // Gardevoir in the Rolling Fields + new(SWSH) { Species = 439, Level = 15, Location = 122, Weather = Snowstorm }, // Mime Jr. in the Rolling Fields + new(SWSH) { Species = 221, Level = 33, Location = 122, Weather = Icy }, // Piloswine in the Rolling Fields + new(SWSH) { Species = 221, Level = 33, Location = 122, Weather = Icy }, // Piloswine in the Rolling Fields + new(SWSH) { Species = 558, Level = 34, Location = 122, Weather = Sandstorm }, // Crustle in the Rolling Fields + new(SWSH) { Species = 093, Level = 31, Location = 122, Weather = Stormy }, // Haunter in the Rolling Fields + new EncounterStatic8S(SWSH) { Species = 279, Level = 26, Locations = new[] {122, 128, 138}, Weather = Stormy }, // Pelipper in the Rolling Fields, North Lake Miloch, East Lake Axewell + new(SWSH) { Species = 760, Level = 34, Location = 124, Weather = All }, // Bewear in the Dappled Grove + new(SWSH) { Species = 826, Level = 65, Location = 124, Weather = All }, // Orbeetle in the Dappled Grove + new(SWSH) { Species = 045, Level = 36, Location = 124, Weather = Normal | Overcast | Heavy_Fog }, // Vileplume in the Dappled Grove + new(SW ) { Species = 275, Level = 34, Location = 124, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Shiftry in the Dappled Grove + new( SH) { Species = 272, Level = 34, Location = 124, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Ludicolo in the Dappled Grove + new(SWSH) { Species = 675, Level = 32, Location = 124, Weather = Intense_Sun | Icy | Sandstorm }, // Pangoro in the Dappled Grove + new(SWSH) { Species = 537, Level = 36, Location = 124, Weather = Stormy }, // Seismitoad in the Dappled Grove + new(SWSH) { Species = 583, Level = 36, Location = 124, Weather = Icy }, // Vanillish in the Dappled Grove + new(SWSH) { Species = 344, Level = 36, Location = 124, Weather = Intense_Sun | Sandstorm }, // Claydol in the Dappled Grove + new(SWSH) { Species = 426, Level = 34, Location = 126, Weather = Normal | Intense_Sun | Snowing }, // Drifblim at Watchtower Ruins + new(SWSH) { Species = 823, Level = 65, Location = 126, Weather = All }, // Corviknight at Watchtower Ruins + new(SWSH) { Species = 093, Level = 34, Location = 126, Weather = Overcast | Stormy | Snowstorm | Sandstorm | Heavy_Fog }, // Haunter at Watchtower Ruins + new EncounterStatic8S(SWSH) { Species = 356, Level = 40, Locations = new[] {126, 130}, Weather = Overcast | Stormy | Heavy_Fog }, // Dusclops at Watchtower Ruins, West Lake Axewell + new EncounterStatic8S(SWSH) { Species = 362, Level = 40, Locations = new[] {126, 130}, Weather = Icy }, // Glalie at Watchtower Ruins, West Lake Axewell + new EncounterStatic8S(SWSH) { Species = 623, Level = 40, Locations = new[] {126, 130}, Weather = Normal | Intense_Sun | Sandstorm }, // Golurk at Watchtower Ruins, West Lake Axewell + new(SWSH) { Species = 569, Level = 36, Location = 128, Weather = Normal | Overcast | Stormy }, // Garbodor at East Lake Axewell + new(SWSH) { Species = 119, Level = 46, Location = 128, Weather = Normal | Overcast | Intense_Sun | Sandstorm }, // Seaking at East Lake Axewell + new(SWSH) { Species = 279, Level = 46, Location = 128, Weather = Normal | Overcast | Stormy | Intense_Sun }, // Pelipper at East Lake Axewell + new(SWSH) { Species = 110, Level = 65, Location = 128, Form = 01, Weather = All }, // Weezing at East Lake Axewell + new(SWSH) { Species = 221, Level = 36, Location = 128, Weather = Icy }, // Piloswine at East Lake Axewell + new(SWSH) { Species = 750, Level = 36, Location = 128, Weather = Intense_Sun | Sandstorm }, // Mudsdale at East Lake Axewell + new(SWSH) { Species = 437, Level = 36, Location = 128, Weather = Heavy_Fog }, // Bronzong at East Lake Axewell + new EncounterStatic8S(SWSH) { Species = 091, Level = 46, Locations = new[] {128, 130}, Weather = Normal | Heavy_Fog }, // Cloyster at East/West Lake Axewell + new EncounterStatic8S(SWSH) { Species = 584, Level = 47, Locations = new[] {128, 130, 134, 138, 142}, Weather = Icy }, // Vanilluxe at North/East/South/West Lake Miloch/Axewell, Bridge Field + new EncounterStatic8S(SWSH) { Species = 130, Level = 56, Locations = new[] {128, 130, 142, 146}, Weather = All }, // Gyarados in East/West Lake Axewell, Bridge Field, Dusty Bowl + new EncounterStatic8S(SWSH) { Species = 178, Level = 26, Locations = new[] {128, 138}, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Xatu at East Lake Axewell, North Lake Miloch + new EncounterStatic8S(SWSH) { Species = 593, Level = 46, Locations = new[] {128, 138, 154}, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Jellicent at East Lake Axewell, North Lake Miloch, Lake of Outrage + new EncounterStatic8S(SWSH) { Species = 171, Level = 46, Locations = new[] {128, 154}, Weather = Normal | Thunderstorm | Heavy_Fog }, // Lanturn at East Lake Axewell, the Lake of Outrage + new(SWSH) { Species = 195, Level = 15, Location = 130, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Quagsire at West Lake Axewell + new(SWSH) { Species = 099, Level = 28, Location = 130 }, // Kingler at West Lake Axewell + new(SWSH) { Species = 660, Level = 15, Location = 130, Weather = Intense_Sun | Icy | Sandstorm }, // Diggersby at West Lake Axewell + new(SWSH) { Species = 834, Level = 65, Location = 130, Weather = All }, // Drednaw at West Lake Axewell + new(SWSH) { Species = 279, Level = 28, Location = 130, Weather = Stormy }, // Pelipper at West Lake Axewell + new(SWSH) { Species = 536, Level = 28, Location = 130, Weather = Overcast | Icy | Sandstorm | Heavy_Fog }, // Palpitoad at West Lake Axewell + new(SWSH) { Species = 660, Level = 28, Location = 130, Weather = Intense_Sun }, // Diggersby at West Lake Axewell + new(SWSH) { Species = 853, Level = 56, Location = 130, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Grapploct at West Lake Axewell + new(SWSH) { Species = 593, Level = 46, Location = 130, Weather = Overcast | Raining | Heavy_Fog }, // Jellicent at West Lake Axewell + new EncounterStatic8S(SWSH) { Species = 119, Level = 46, Locations = new[] {130, 142}, Weather = Normal | Overcast | Sandstorm }, // Seaking at West Lake Axewell, Bridge Field + new EncounterStatic8S(SWSH) { Species = 131, Level = 56, Locations = new[] {130, 134, 138, 154}, Weather = Normal | Stormy | Icy | Heavy_Fog }, // Lapras at North/East/South Lake Miloch/Axewell, the Lake of Outrage + new(SWSH) { Species = 612, Level = 60, Location = 132, Ability = OnlyFirst, Weather = Normal | Overcast | Raining | Intense_Sun | Sandstorm | Heavy_Fog }, // Haxorus on Axew’s Eye + new(SWSH) { Species = 845, Level = 65, Location = 132, Weather = All }, // Cramorant on Axew’s Eye + new(SWSH) { Species = 537, Level = 60, Location = 132, Weather = Thunderstorm }, // Seismitoad on Axew’s Eye + new(SWSH) { Species = 460, Level = 60, Location = 132, Weather = Icy }, // Abomasnow on Axew’s Eye + new(SWSH) { Species = 067, Level = 26, Location = 134, Weather = All }, // Machoke at South Lake Miloch + new(SWSH) { Species = 828, Level = 65, Location = 134, Weather = All }, // Thievul at South Lake Miloch + new(SWSH) { Species = 119, Level = 46, Location = 134, Weather = Normal | Overcast | Stormy | Sandstorm }, // Seaking at South Lake Miloch + new(SWSH) { Species = 435, Level = 34, Location = 134, Weather = Normal | Overcast | Icy | Sandstorm | Heavy_Fog }, // Skuntank at South Lake Miloch + new(SWSH) { Species = 099, Level = 31, Location = 134, Weather = Normal | Thunderstorm }, // Kingler at South Lake Miloch + new(SWSH) { Species = 342, Level = 31, Location = 134, Weather = Normal | Thunderstorm }, // Crawdaunt at South Lake Miloch + new(SWSH) { Species = 171, Level = 46, Location = 134, Weather = Thunderstorm }, // Lanturn at South Lake Miloch + new(SWSH) { Species = 224, Level = 46, Location = 134, Weather = Normal | Intense_Sun | Sandstorm | Heavy_Fog }, // Octillery at South Lake Miloch + new(SWSH) { Species = 340, Level = 46, Location = 134, Weather = Normal | Thunderstorm | Intense_Sun | Sandstorm }, // Whiscash at South Lake Miloch + new(SWSH) { Species = 536, Level = 34, Location = 134, Weather = Stormy }, // Palpitoad at South Lake Miloch + new(SWSH) { Species = 558, Level = 34, Location = 134, Weather = Intense_Sun }, // Crustle at South Lake Miloch + new(SWSH) { Species = 067, Level = 31, Location = 134, Weather = Overcast | Intense_Sun | Icy | Sandstorm }, // Machoke at South Lake Miloch + new(SWSH) { Species = 426, Level = 31, Location = 134, Weather = Heavy_Fog }, // Drifblim at South Lake Miloch + new(SWSH) { Species = 596, Level = 46, Location = 134, Weather = All }, // Galvantula at South Lake Miloch + new EncounterStatic8S(SWSH) { Species = 426, Level = 46, Locations = new[] {134, 138}, Weather = Normal | Overcast | Snowstorm | Heavy_Fog }, // Drifblim at North/South Lake Miloch + new EncounterStatic8S(SWSH) { Species = 130, Level = 60, Locations = new[] {134, 138, 154 }, Weather = Normal | Overcast | Stormy | Intense_Sun | Icy | Sandstorm }, // Gyarados at North/South Lake Miloch, the Lake of Outrage + new EncounterStatic8S(SWSH) { Species = 593, Level = 46, Locations = new[] {134, 142}, Weather = Raining | Heavy_Fog }, // Jellicent at South Lake Miloch, Bridge Field + new EncounterStatic8S(SWSH) { Species = 350, Level = 60, Locations = new[] {134, 154}, Gender = 0, Ability = OnlyFirst, Weather = Heavy_Fog }, // Milotic at South Lake Miloch, the Lake of Outrage + new(SWSH) { Species = 208, Level = 50, Location = 136, Weather = All }, // Steelix near the Giant’s Seat + new(SWSH) { Species = 738, Level = 46, Location = 136, Weather = Normal | Overcast | Intense_Sun | Sandstorm }, // Vikavolt near the Giant’s Seat + new(SWSH) { Species = 112, Level = 46, Location = 136 }, // Rhydon near the Giant’s Seat + new(SWSH) { Species = 625, Level = 52, Location = 136, Weather = All }, // Bisharp near the Giant’s Seat + new(SWSH) { Species = 884, Level = 65, Location = 136, Weather = All }, // Duraludon near the Giant’s Seat + new(SWSH) { Species = 437, Level = 46, Location = 136, Weather = Overcast | Raining }, // Bronzong near the Giant’s Seat + new(SWSH) { Species = 460, Level = 46, Location = 136, Weather = Icy }, // Abomasnow near the Giant’s Seat + new(SWSH) { Species = 750, Level = 46, Location = 136, Weather = Intense_Sun }, // Mudsdale near the Giant’s Seat + new(SWSH) { Species = 623, Level = 46, Location = 136, Weather = Sandstorm }, // Golurk near the Giant’s Seat + new(SWSH) { Species = 356, Level = 46, Location = 136, Weather = Heavy_Fog }, // Dusclops near the Giant’s Seat + new(SWSH) { Species = 518, Level = 46, Location = 136, Weather = Heavy_Fog }, // Musharna near the Giant’s Seat + new(SWSH) { Species = 362, Level = 46, Location = 136, Weather = Icy }, // Glalie near the Giant’s Seat + new(SWSH) { Species = 596, Level = 46, Location = 136, Weather = Raining }, // Galvantula near the Giant’s Seat + new(SWSH) { Species = 823, Level = 50, Location = 138, Weather = All }, // Corviknight at North Lake Miloch + new(SWSH) { Species = 510, Level = 28, Location = 138, Weather = All }, // Liepard at North Lake Miloch + new(SWSH) { Species = 119, Level = 46, Location = 138, Weather = Stormy | Sandstorm }, // Seaking at North Lake Miloch + new(SWSH) { Species = 448, Level = 36, Location = 138 }, // Lucario at North Lake Miloch + new(SWSH) { Species = 340, Level = 46, Location = 138, Weather = Normal | Overcast | Thunderstorm | Intense_Sun | Sandstorm }, // Whiscash at North Lake Miloch + new(SWSH) { Species = 836, Level = 65, Location = 138, Weather = All }, // Boltund at North Lake Miloch + new(SWSH) { Species = 537, Level = 36, Location = 138, Weather = Overcast | Thunderstorm }, // Seismitoad at North Lake Miloch + new(SWSH) { Species = 435, Level = 36, Location = 138, Weather = Raining | Intense_Sun | Sandstorm }, // Skuntank at North Lake Miloch + new(SWSH) { Species = 583, Level = 36, Location = 138, Weather = Icy }, // Vanillish at North Lake Miloch + new(SWSH) { Species = 426, Level = 36, Location = 138, Weather = Heavy_Fog }, // Drifblim at North Lake Miloch + new EncounterStatic8S(SWSH) { Species = 130, Level = 56, Locations = new[] {138, 154}, Weather = Overcast | Intense_Sun | Sandstorm }, // Gyarados in North Lake Miloch, Lake of Outrage + new(SWSH) { Species = 625, Level = 52, Location = 140, Weather = Snowstorm }, // Bisharp at the Motostoke Riverbank + new(SWSH) { Species = 143, Level = 36, Location = 140, Weather = Normal | Overcast | Stormy | Intense_Sun | Icy | Sandstorm }, // Snorlax at the Motostoke Riverbank + new(SWSH) { Species = 452, Level = 40, Location = 140, Weather = Normal | Stormy | Intense_Sun | Sandstorm }, // Drapion at the Motostoke Riverbank + new(SWSH) { Species = 561, Level = 36, Location = 140, Weather = All }, // Sigilyph at the Motostoke Riverbank + new(SWSH) { Species = 534, Level = 55, Location = 140, Ability = OnlyFirst, Weather = Normal | Overcast | Stormy | Snowing | Heavy_Fog }, // Conkeldurr at the Motostoke Riverbank + new(SWSH) { Species = 320, Level = 56, Location = 140, Weather = All }, // Wailmer at the Motostoke Riverbank + new(SWSH) { Species = 561, Level = 40, Location = 140, Weather = Normal | Overcast | Heavy_Fog }, // Sigilyph at the Motostoke Riverbank + new(SWSH) { Species = 830, Level = 65, Location = 140, Weather = All }, // Eldegoss at the Motostoke Riverbank + new(SWSH) { Species = 036, Level = 36, Location = 140, Weather = Heavy_Fog }, // Clefable at the Motostoke Riverbank + new(SWSH) { Species = 743, Level = 40, Location = 140, Weather = Overcast | Icy | Heavy_Fog }, // Ribombee at the Motostoke Riverbank + new(SWSH) { Species = 112, Level = 55, Location = 140, Weather = Intense_Sun | Sandstorm }, // Rhydon at the Motostoke Riverbank + new(SWSH) { Species = 823, Level = 40, Location = 140, Weather = Stormy | Intense_Sun | Icy | Sandstorm }, // Corviknight at the Motostoke Riverbank + new EncounterStatic8S(SWSH) { Species = 760, Level = 40, Locations = new[] {140, 142}, Weather = Thunderstorm | Sandstorm }, // Bewear in Bridge Field, Motostoke Riverbank + new EncounterStatic8S(SWSH) { Species = 264, Level = 40, Locations = new[] {140, 142}, Form = 01, Weather = All }, // Linoone at the Motostoke Riverbank, Bridge Field + new(SWSH) { Species = 569, Level = 40, Location = 142, Weather = All }, // Garbodor in Bridge Field + new(SWSH) { Species = 279, Level = 46, Location = 142, Weather = Intense_Sun }, // Pelipper in Bridge Field + new(SWSH) { Species = 743, Level = 40, Location = 142, Weather = Normal | Overcast | Intense_Sun | Heavy_Fog }, // Ribombee in Bridge Field + new(SWSH) { Species = 475, Level = 60, Location = 142, Weather = Normal | Overcast | Stormy | Intense_Sun | Sandstorm }, // Gallade in Bridge Field + new(SWSH) { Species = 606, Level = 42, Location = 142, Weather = Normal | Overcast | Icy | Heavy_Fog }, // Beheeyem in Bridge Field + new(SWSH) { Species = 715, Level = 50, Location = 142, Weather = Normal | Overcast | Stormy | Intense_Sun | Snowstorm | Sandstorm | Heavy_Fog }, // Noivern in Bridge Field + new(SWSH) { Species = 537, Level = 46, Location = 142, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Seismitoad in Bridge Field + new(SWSH) { Species = 768, Level = 50, Location = 142, Weather = Normal | Stormy }, // Golisopod in Bridge Field + new(SWSH) { Species = 760, Level = 42, Location = 142, Weather = Normal | Stormy | Icy | Sandstorm | Heavy_Fog }, // Bewear in Bridge Field + new(SWSH) { Species = 820, Level = 42, Location = 142, Weather = All }, // Greedent in Bridge Field + new(SWSH) { Species = 862, Level = 65, Location = 142, Weather = All }, // Obstagoon in Bridge Field + new(SWSH) { Species = 537, Level = 36, Location = 142, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Seismitoad in Bridge Field + new(SWSH) { Species = 614, Level = 60, Location = 142, Weather = Snowing }, // Beartic in Bridge Field + new(SWSH) { Species = 461, Level = 60, Location = 142, Weather = Snowstorm }, // Weavile in Bridge Field + new(SWSH) { Species = 518, Level = 60, Location = 142, Weather = Heavy_Fog }, // Musharna in Bridge Field + new(SWSH) { Species = 437, Level = 42, Location = 142, Weather = Stormy }, // Bronzong in Bridge Field + new(SWSH) { Species = 344, Level = 42, Location = 142, Weather = Intense_Sun | Sandstorm }, // Claydol in Bridge Field + new(SWSH) { Species = 452, Level = 50, Location = 142, Weather = Overcast }, // Drapion in Bridge Field + new(SWSH) { Species = 164, Level = 50, Location = 142, Weather = Snowing }, // Noctowl in Bridge Field + new(SWSH) { Species = 760, Level = 46, Location = 142, Weather = Intense_Sun | Sandstorm }, // Bewear in Bridge Field + new(SWSH) { Species = 675, Level = 42, Location = 142, Weather = Overcast | Intense_Sun }, // Pangoro in Bridge Field + new(SWSH) { Species = 584, Level = 50, Location = 142, Weather = Icy }, // Vanilluxe in Bridge Field + new(SWSH) { Species = 112, Level = 50, Location = 142, Weather = Intense_Sun | Sandstorm }, // Rhydon in Bridge Field + new(SWSH) { Species = 778, Level = 50, Location = 142, Weather = Heavy_Fog }, // Mimikyu in Bridge Field + new EncounterStatic8S(SWSH) { Species = 598, Level = 40, Locations = new[] {142, 144}, Weather = All }, // Ferrothorn in Bridge Field, Stony Wilderness + new(SWSH) { Species = 344, Level = 42, Location = 144, Weather = Normal | Overcast | Intense_Sun | Sandstorm }, // Claydol in the Stony Wilderness + new(SWSH) { Species = 437, Level = 42, Location = 144, Weather = Stormy | Icy | Heavy_Fog }, // Bronzong in Stony Wilderness + new(SWSH) { Species = 477, Level = 60, Location = 144, Weather = All }, // Dusknoir in the Stony Wilderness + new(SWSH) { Species = 623, Level = 43, Location = 144, Weather = All }, // Golurk in the Stony Wilderness + new(SWSH) { Species = 561, Level = 40, Location = 144, Weather = Normal | Stormy | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Sigilyph in the Stony Wilderness + new(SWSH) { Species = 558, Level = 34, Location = 144, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Crustle in the Stony Wilderness + new(SWSH) { Species = 112, Level = 41, Location = 144, Weather = All }, // Rhydon in the Stony Wilderness + new(SWSH) { Species = 763, Level = 36, Location = 144, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Tsareena in the Stony Wilderness + new(SWSH) { Species = 861, Level = 65, Location = 144, Gender = 0, Weather = All }, // Grimmsnarl in the Stony Wilderness + new(SWSH) { Species = 521, Level = 40, Location = 144, Weather = Overcast }, // Unfezant in the Stony Wilderness + new(SWSH) { Species = 752, Level = 34, Location = 144, Weather = Raining }, // Araquanid in the Stony Wilderness + new(SWSH) { Species = 750, Level = 41, Location = 146, Weather = Normal | Intense_Sun | Sandstorm }, // Mudsdale in Dusty Bowl + new(SWSH) { Species = 185, Level = 41, Location = 146, Weather = Normal | Overcast | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Sudowoodo in Dusty Bowl + new(SWSH) { Species = 437, Level = 41, Location = 146, Weather = Normal | Stormy | Icy | Heavy_Fog }, // Bronzong in Dusty Bowl + new(SW ) { Species = 784, Level = 60, Location = 146, Ability = OnlyFirst, Weather = Normal | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Kommo-o in Dusty Bowl + new( SH) { Species = 248, Level = 60, Location = 146, Weather = Normal | Intense_Sun | Icy | Sandstorm | Heavy_Fog }, // Tyranitar in Dusty Bowl + new(SWSH) { Species = 213, Level = 34, Location = 146, Weather = All }, // Shuckle in Dusty Bowl + new(SWSH) { Species = 330, Level = 51, Location = 146, Weather = Normal | Sandstorm }, // Flygon in Dusty Bowl + new(SWSH) { Species = 526, Level = 51, Location = 146, Weather = Normal | Intense_Sun }, // Gigalith in Dusty Bowl + new(SWSH) { Species = 844, Level = 65, Location = 146, Weather = All }, // Sandaconda in Dusty Bowl + new(SWSH) { Species = 537, Level = 41, Location = 146, Weather = Stormy }, // Seismitoad in Dusty Bowl + new(SWSH) { Species = 435, Level = 41, Location = 146, Weather = Overcast }, // Skuntank in Dusty Bowl + new(SWSH) { Species = 221, Level = 41, Location = 146, Weather = Icy }, // Piloswine in Dusty Bowl + new(SWSH) { Species = 356, Level = 41, Location = 146, Weather = Heavy_Fog }, // Dusclops in Dusty Bowl + new(SWSH) { Species = 344, Level = 41, Location = 146, Weather = Overcast | Intense_Sun | Sandstorm }, // Claydol in Dusty Bowl + new(SWSH) { Species = 689, Level = 60, Location = 146, Weather = Overcast | Stormy }, // Barbaracle in Dusty Bowl + new(SWSH) { Species = 561, Level = 51, Location = 146, Weather = Overcast | Stormy | Intense_Sun | Icy | Heavy_Fog }, // Sigilyph in Dusty Bowl + new(SWSH) { Species = 623, Level = 51, Location = 146, Weather = Overcast | Stormy | Icy | Sandstorm | Heavy_Fog }, // Golurk in Dusty Bowl + new EncounterStatic8S(SWSH) { Species = 423, Level = 56, Locations = new[] {146, 148}, Form = 01, Weather = All }, // Gastrodon in Dusty Bowl, Giant’s Mirror + new(SWSH) { Species = 208, Level = 50, Location = 148, Weather = All }, // Steelix around the Giant’s Mirror + new(SWSH) { Species = 068, Level = 60, Location = 148, Ability = OnlyFirst, Weather = All }, // Machamp around the Giant’s Mirror + new(SWSH) { Species = 182, Level = 41, Location = 148, Weather = Normal | Intense_Sun | Heavy_Fog }, // Bellossom around the Giant’s Mirror + new(SWSH) { Species = 521, Level = 41, Location = 148, Weather = Normal | Overcast | Intense_Sun }, // Unfezant around the Giant’s Mirror + new(SWSH) { Species = 045, Level = 41, Location = 148, Weather = Overcast | Stormy | Icy | Sandstorm }, // Vileplume around the Giant’s Mirror + new(SWSH) { Species = 863, Level = 65, Location = 148, Weather = All }, // Perrserker around the Giant’s Mirror + new(SWSH) { Species = 537, Level = 60, Location = 148, Weather = Raining }, // Seismitoad around the Giant’s Mirror + new(SWSH) { Species = 460, Level = 60, Location = 148, Weather = Snowing }, // Abomasnow around the Giant’s Mirror + new(SWSH) { Species = 178, Level = 41, Location = 148, Weather = Raining | Icy | Sandstorm | Heavy_Fog }, // Xatu around the Giant’s Mirror + new(SWSH) { Species = 701, Level = 36, Location = 150, Weather = All }, // Hawlucha on the Hammerlocke Hills + new(SWSH) { Species = 711, Level = 41, Location = 150, Weather = All }, // Gourgeist on the Hammerlocke Hills + new(SWSH) { Species = 879, Level = 65, Location = 150, Weather = All }, // Copperajah on the Hammerlocke Hills + new(SWSH) { Species = 600, Level = 46, Location = 150, Weather = Normal | Overcast | Raining | Snowing }, // Klang on the Hammerlocke Hills + new(SWSH) { Species = 823, Level = 38, Location = 150, Weather = All }, // Corviknight on the Hammerlocke Hills + new(SWSH) { Species = 045, Level = 41, Location = 150, Weather = Overcast | Stormy | Icy | Sandstorm }, // Vileplume on the Hammerlocke Hills + new(SWSH) { Species = 601, Level = 49, Location = 150, Weather = Thunderstorm | Intense_Sun | Snowstorm | Sandstorm | Heavy_Fog }, // Klinklang on the Hammerlocke Hills + new(SWSH) { Species = 407, Level = 41, Location = 150, Weather = Overcast | Heavy_Fog }, // Roserade on the Hammerlocke Hills + new(SWSH) { Species = 460, Level = 41, Location = 150, Weather = Icy}, // Abomasnow on the Hammerlocke Hills + //new(SWSH) { Species = 510, Level = 31, Location = -1, }, // Liepard + new(SWSH) { Species = 768, Level = 60, Location = 152, Weather = Raining }, // Golisopod near the Giant’s Cap + new(SWSH) { Species = 614, Level = 60, Location = 152, Weather = Snowing }, // Beartic near the Giant’s Cap + new(SWSH) { Species = 530, Level = 46, Location = 152, Weather = Intense_Sun | Sandstorm }, // Excadrill near the Giant’s Cap + new(SWSH) { Species = 362, Level = 46, Location = 152, Weather = Icy }, // Glalie near the Giant’s Cap + new(SWSH) { Species = 537, Level = 46, Location = 152, Weather = Raining }, // Seismitoad near the Giant’s Cap + new(SWSH) { Species = 681, Level = 58, Location = 152, Weather = Heavy_Fog }, // Aegislash near the Giant’s Cap + new(SWSH) { Species = 094, Level = 60, Location = 152, Weather = Normal | Overcast | Thunderstorm | Intense_Sun | Snowstorm | Sandstorm | Heavy_Fog }, // Gengar near the Giant’s Cap + new(SWSH) { Species = 823, Level = 39, Location = 152, Weather = All }, // Corviknight near the Giant’s Cap + new(SWSH) { Species = 573, Level = 46, Location = 152, Weather = Normal | Overcast | Heavy_Fog }, // Cinccino near the Giant’s Cap + new(SWSH) { Species = 826, Level = 41, Location = 152, Weather = All }, // Orbeetle near the Giant’s Cap + new(SWSH) { Species = 834, Level = 36, Location = 152, Weather = All }, // Drednaw near the Giant’s Cap + new(SWSH) { Species = 680, Level = 56, Location = 152, Weather = Normal | Overcast | Stormy | Intense_Sun | Icy | Sandstorm }, // Doublade near the Giant’s Cap + new(SWSH) { Species = 839, Level = 65, Location = 152, Weather = All }, // Coalossal near the Giant’s Cap + new(SWSH) { Species = 853, Level = 56, Location = 154, Weather = All }, // Grapploct at the Lake of Outrage + new(SWSH) { Species = 282, Level = 60, Location = 154, Weather = Normal | Heavy_Fog }, // Gardevoir at the Lake of Outrage + new(SWSH) { Species = 470, Level = 56, Location = 154, Weather = Normal }, // Leafeon at the Lake of Outrage + new(SWSH) { Species = 119, Level = 46, Location = 154, Weather = Overcast | Intense_Sun | Sandstorm }, // Seaking at Lake of Outrage + new(SWSH) { Species = 858, Level = 65, Location = 154, Gender = 1, Weather = All }, // Hatterene at the Lake of Outrage + new(SWSH) { Species = 112, Level = 60, Location = 154, Weather = Sandstorm }, // Rhydon at the Lake of Outrage + new(SWSH) { Species = 609, Level = 60, Location = 154, Weather = Intense_Sun }, // Chandelure at the Lake of Outrage + new(SWSH) { Species = 713, Level = 60, Location = 154, Weather = Icy }, // Avalugg at the Lake of Outrage + new(SWSH) { Species = 756, Level = 60, Location = 154, Weather = Overcast | Stormy }, // Shiinotic at the Lake of Outrage + new(SWSH) { Species = 134, Level = 56, Location = 154, Weather = Raining }, // Vaporeon at the Lake of Outrage + new(SWSH) { Species = 135, Level = 56, Location = 154, Weather = Thunderstorm }, // Jolteon at the Lake of Outrage + new(SWSH) { Species = 196, Level = 56, Location = 154, Weather = Overcast }, // Espeon at the Lake of Outrage + new(SWSH) { Species = 471, Level = 56, Location = 154, Weather = Icy }, // Glaceon at the Lake of Outrage + new(SWSH) { Species = 136, Level = 56, Location = 154, Weather = Intense_Sun }, // Flareon at the Lake of Outrage + new(SWSH) { Species = 197, Level = 56, Location = 154, Weather = Sandstorm }, // Umbreon at the Lake of Outrage + new(SWSH) { Species = 700, Level = 56, Location = 154, Weather = Heavy_Fog }, // Sylveon at the Lake of Outrage + #endregion + + #region R1 Static Encounters + new(SWSH) { Species = 079, Level = 12, Location = 016, ScriptedNoMarks = true, Form = 01, Shiny = Never }, // Slowpoke-1 at Wedgehurst Station + new(SWSH) { Species = 321, Level = 80, Location = 186, Weather = All_IoA }, // Wailord in the Workout Sea + + new(SWSH) { Species = 748, Level = 20, Location = 164, Weather = Normal | Heavy_Fog }, // Toxapex in the Fields of Honor + new(SWSH) { Species = 099, Level = 20, Location = 164, Weather = Normal | Overcast | Stormy | Intense_Sun }, // Kingler in the Fields of Honor + new(SWSH) { Species = 834, Level = 20, Location = 164, Weather = Intense_Sun }, // Drednaw in the Fields of Honor + new(SWSH) { Species = 764, Level = 15, Location = 164 }, // Comfey in the Fields of Honor + new(SWSH) { Species = 744, Level = 15, Location = 164, Weather = Normal | Intense_Sun }, // Rockruff in the Fields of Honor + new(SWSH) { Species = 121, Level = 20, Location = 164, Weather = Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Starmie in the Fields of Honor + new(SWSH) { Species = 428, Level = 22, Location = 164, Weather = Normal | Intense_Sun }, // Lopunny in the Fields of Honor + new EncounterStatic8S(SWSH) { Species = 687, Level = 26, Locations = new[] {164, 166}, Weather = Overcast | Raining }, // Malamar in the Fields of Honor, Soothing Wetlands + new EncounterStatic8S(SWSH) { Species = 404, Level = 20, Locations = new[] {164, 166}, Weather = Thunderstorm }, // Luxio in the Fields of Honor, Soothing Wetlands + new EncounterStatic8S(SWSH) { Species = 570, Level = 15, Locations = new[] {164, 166}, Weather = Overcast | Heavy_Fog }, // Zorua in the Fields of Honor, Soothing Wetlands + new EncounterStatic8S(SWSH) { Species = 040, Level = 27, Locations = new[] {164, 166}, Weather = Heavy_Fog }, // Wigglytuff in the Fields of Honor, Soothing Wetlands + new EncounterStatic8S(SWSH) { Species = 183, Level = 15, Locations = new[] {164, 166}, Weather = Raining }, // Marill in the Fields of Honor, Soothing Wetlands + new EncounterStatic8S(SWSH) { Species = 662, Level = 20, Locations = new[] {164, 166}, Weather = Intense_Sun }, // Fletchinder in the Fields of Honor, in the Soothing Wetlands + new EncounterStatic8S(SWSH) { Species = 064, Level = 20, Locations = new[] {164, 166}, Weather = Heavy_Fog }, // Kadabra in the Fields of Honor, in the Soothing Wetlands + new EncounterStatic8S(SWSH) { Species = 025, Level = 15, Locations = new[] {164, 166}, Weather = Thunderstorm }, // Pikachu in the Fields of Honor, in the Soothing Wetlands + new(SWSH) { Species = 834, Level = 20, Location = 166, Weather = Normal | Overcast | Sandstorm | Intense_Sun }, // Drednaw in the Soothing Wetlands + new(SWSH) { Species = 764, Level = 15, Location = 166, Weather = Normal | Intense_Sun | Heavy_Fog }, // Comfey in the Soothing Wetlands + new(SWSH) { Species = 744, Level = 15, Location = 166 }, // Rockruff in the Soothing Wetlands + new(SWSH) { Species = 195, Level = 20, Location = 166, Weather = Normal | Overcast | Raining | Intense_Sun | Heavy_Fog }, // Quagsire in the Soothing Wetlands + new(SWSH) { Species = 626, Level = 20, Location = 166, Weather = Normal | Overcast | Stormy | Intense_Sun }, // Bouffalant in the Soothing Wetlands + new(SWSH) { Species = 242, Level = 22, Location = 166, Weather = Heavy_Fog }, // Blissey in the Soothing Wetlands + new(SWSH) { Species = 452, Level = 22, Location = 166, Weather = Normal | Overcast | Raining }, // Drapion in the Soothing Wetlands + new(SWSH) { Species = 463, Level = 27, Location = 166, Weather = Normal | Raining }, // Lickilicky in the Soothing Wetlands + new(SWSH) { Species = 405, Level = 32, Location = 166, Weather = Thunderstorm }, // Luxray in the Soothing Wetlands + new(SWSH) { Species = 428, Level = 22, Location = 166 }, // Lopunny in the Soothing Wetlands + new(SWSH) { Species = 186, Level = 32, Location = 166, Weather = Stormy }, // Politoed in the Soothing Wetlands + new(SWSH) { Species = 061, Level = 20, Location = 166, Weather = Stormy | Heavy_Fog }, // Poliwhirl in the Soothing Wetlands + new(SWSH) { Species = 549, Level = 22, Location = 166, Weather = Intense_Sun }, // Lilligant in the Soothing Wetlands + new(SW ) { Species = 559, Level = 20, Location = 166, Weather = Overcast }, // Scraggy in the Soothing Wetlands + new( SH) { Species = 453, Level = 20, Location = 166, Weather = Overcast }, // Croagunk in the Soothing Wetlands + new(SWSH) { Species = 663, Level = 32, Location = 166, Weather = Intense_Sun }, // Talonflame in the Soothing Wetlands + new EncounterStatic8S(SWSH) { Species = 026, Level = 26, Locations = new[] {166, 168}, Weather = Thunderstorm }, // Raichu in the Soothing Wetlands, in the Forest of Focus + new EncounterStatic8S(SWSH) { Species = 184, Level = 21, Locations = new[] {166, 168}, Weather = Heavy_Fog }, // Azumarill in the Soothing Wetlands, in the Forest of Focus + new EncounterStatic8S(SWSH) { Species = 587, Level = 20, Locations = new[] {166, 168}, Weather = All_IoA }, // Emolga in the Soothing Wetlands (c), Forest of Focus + new EncounterStatic8S(SWSH) { Species = 847, Level = 42, Locations = new[] {166, 170}, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Barraskewda in the Soothing Wetlands, Challenge Beach + //new(SWSH) { Species = 834, Level = 21, Location = -1 }, // Drednaw + //new(SWSH) { Species = 768, Level = 26, Location = -1 }, // Golisopod + new(SWSH) { Species = 025, Level = 22, Location = 168, Weather = Normal | Overcast | Stormy }, // Pikachu in the Forest of Focus + new(SW ) { Species = 766, Level = 26, Location = 168 }, // Passimian in the Forest of Focus + new( SH) { Species = 765, Level = 26, Location = 168 }, // Oranguru in the Forest of Focus + new(SWSH) { Species = 342, Level = 26, Location = 168, Weather = Overcast | Stormy }, // Crawdaunt in the Forest of Focus + new(SWSH) { Species = 040, Level = 26, Location = 168, Weather = Heavy_Fog }, // Wigglytuff in the Forest of Focus + new(SWSH) { Species = 028, Level = 26, Location = 168, Weather = Sandstorm }, // Sandslash in the Forest of Focus + new(SWSH) { Species = 589, Level = 32, Location = 168, Weather = Sandstorm }, // Escavalier in the Forest of Focus + new(SWSH) { Species = 104, Level = 20, Location = 168, Weather = Sandstorm }, // Cubone in the Forest of Focus + new(SWSH) { Species = 545, Level = 32, Location = 168, Weather = Overcast }, // Scolipede in the Forest of Focus + new(SW ) { Species = 127, Level = 26, Location = 168, Weather = Intense_Sun }, // Pinsir in the Forest of Focus + new( SH) { Species = 214, Level = 26, Location = 168, Weather = Intense_Sun }, // Heracross in the Forest of Focus + new(SWSH) { Species = 636, Level = 15, Location = 168, Weather = Intense_Sun }, // Larvesta in the Forest of Focus + new(SWSH) { Species = 465, Level = 36, Location = 168, Weather = Intense_Sun }, // Tangrowth in the Forest of Focus + new(SW ) { Species = 616, Level = 20, Location = 168, Weather = Stormy }, // Shelmet in the Forest of Focus + new( SH) { Species = 704, Level = 20, Location = 168, Weather = Stormy }, // Goomy in the Forest of Focus + new(SWSH) { Species = 172, Level = 20, Location = 168, Weather = Thunderstorm }, // Pichu in the Forest of Focus + new(SWSH) { Species = 845, Level = 20, Location = 168, Weather = Normal | Raining | Intense_Sun | Heavy_Fog }, // Cramorant in the Forest of Focus + new(SWSH) { Species = 617, Level = 32, Location = 168, Weather = Raining }, // Accelgor in the Forest of Focus + new(SWSH) { Species = 055, Level = 26, Location = 168, Weather = Raining }, // Golduck in the Forest of Focus + new(SWSH) { Species = 591, Level = 26, Location = 168, Weather = Normal | Overcast | Raining | Intense_Sun }, // Amoonguss in the Forest of Focus + new(SWSH) { Species = 764, Level = 22, Location = 168, Weather = Intense_Sun | Heavy_Fog }, // Comfey in the Forest of Focus + new(SWSH) { Species = 570, Level = 22, Location = 168, Weather = Heavy_Fog }, // Zorua in the Forest of Focus + new(SWSH) { Species = 039, Level = 20, Location = 168, Weather = Heavy_Fog }, // Jigglypuff in the Forest of Focus + new(SWSH) { Species = 847, Level = 42, Location = 168, Weather = Stormy | Heavy_Fog }, // Barraskewda in the Forest of Focus + new(SWSH) { Species = 340, Level = 42, Location = 168, Weather = Normal | Overcast | Intense_Sun | Sandstorm }, // Whiscash in the Forest of Focus + new EncounterStatic8S(SWSH) { Species = 754, Level = 27, Locations = new[] {168, 170}, Weather = Intense_Sun }, // Lurantis in the Forest of Focus, on Challenge Beach + new EncounterStatic8S(SWSH) { Species = 282, Level = 36, Locations = new[] {168, 180}, Weather = Heavy_Fog }, // Gardevoir in the Forest of Focus, Training Lowlands + //new(SWSH) { Species = 475, Level = 20, Location = -1 }, // Gallade + //new(SWSH) { Species = 625, Level = 20, Location = -1 }, // Bisharp + //new(SWSH) { Species = 082, Level = 27, Location = -1 }, // Magneton + //new(SWSH) { Species = 105, Level = 20, Location = -1 }, // Marowak + new(SWSH) { Species = 183, Level = 15, Location = 170 }, // Marill on Challenge Beach + new(SWSH) { Species = 462, Level = 36, Location = 170, Weather = Thunderstorm }, // Magnezone on Challenge Beach + new(SWSH) { Species = 026, Level = 29, Location = 170, Weather = Thunderstorm }, // Raichu on Challenge Beach + new(SWSH) { Species = 637, Level = 42, Location = 170, Weather = Intense_Sun }, // Volcarona on Challenge Beach + new(SWSH) { Species = 687, Level = 29, Location = 170, Weather = Overcast | Raining }, // Malamar on Challenge Beach + new(SWSH) { Species = 428, Level = 27, Location = 170, Weather = Normal | Sandstorm | Heavy_Fog }, // Lopunny on Challenge Beach + new(SWSH) { Species = 452, Level = 27, Location = 170, Weather = Overcast | Raining }, // Drapion on Challenge Beach + //new(SWSH) { Species = 558, Level = 20, Location = -1 }, // Crustle + new(SWSH) { Species = 764, Level = 25, Location = 170, Weather = Normal | Intense_Sun | Heavy_Fog }, // Comfey on Challenge Beach + new(SWSH) { Species = 877, Level = 25, Location = 170, Weather = Normal | Overcast | Stormy }, // Morpeko on Challenge Beach + new(SWSH) { Species = 834, Level = 26, Location = 170, Weather = Normal | Overcast | Thunderstorm | Intense_Sun }, // Drednaw on Challenge Beach + new(SWSH) { Species = 040, Level = 29, Location = 170, Weather = Heavy_Fog }, // Wigglytuff on Challenge Beach + new(SWSH) { Species = 528, Level = 27, Location = 170, Weather = Overcast }, // Swoobat on Challenge Beach + new(SWSH) { Species = 279, Level = 26, Location = 170, Weather = All_IoA }, // Pelipper on Challenge Beach + new(SWSH) { Species = 082, Level = 26, Location = 170, Weather = Thunderstorm }, // Magneton on Challenge Beach + new(SWSH) { Species = 426, Level = 26, Location = 170, Weather = Overcast | Heavy_Fog }, // Drifblim on Challenge Beach + new(SWSH) { Species = 768, Level = 36, Location = 170, Weather = Raining }, // Golisopod on Challenge Beach + new(SWSH) { Species = 662, Level = 26, Location = 170, Weather = Intense_Sun }, // Fletchinder on Challenge Beach + new(SWSH) { Species = 342, Level = 27, Location = 170, Weather = Overcast | Raining }, // Crawdaunt on Challenge Beach + new(SWSH) { Species = 184, Level = 27, Location = 170, Weather = Heavy_Fog }, // Azumarill on Challenge Beach + new(SWSH) { Species = 549, Level = 26, Location = 170, Weather = Intense_Sun }, // Lilligant on Challenge Beach + new(SWSH) { Species = 845, Level = 24, Location = 170, Weather = Normal | Overcast | Raining | Intense_Sun | Heavy_Fog }, // Cramorant on Challenge Beach + new(SWSH) { Species = 055, Level = 27, Location = 170, Ability = OnlySecond, Weather = Thunderstorm | Intense_Sun }, // Golduck on Challenge Beach + new(SWSH) { Species = 702, Level = 25, Location = 170, Weather = Normal | Stormy }, // Dedenne on Challenge Beach + //new(SWSH) { Species = 113, Level = 27, Location = -1 }, // Chansey + new(SWSH) { Species = 405, Level = 36, Location = 170, Weather = Thunderstorm }, // Luxray on Challenge Beach + new(SWSH) { Species = 099, Level = 26, Location = 170, Weather = Normal | Raining | Sandstorm | Intense_Sun }, // Kingler on Challenge Beach + new(SWSH) { Species = 121, Level = 26, Location = 170, Weather = Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Starmie on Challenge Beach + new(SWSH) { Species = 748, Level = 26, Location = 170, Weather = Overcast | Stormy | Heavy_Fog }, // Toxapex on Challenge Beach + new(SWSH) { Species = 224, Level = 45, Location = 170, Weather = Normal | Intense_Sun }, // Octillery on Challenge Beach + new(SWSH) { Species = 171, Level = 42, Location = 170, Weather = Thunderstorm | Heavy_Fog }, // Lanturn on Challenge Beach + new(SWSH) { Species = 593, Level = 42, Location = 170, Weather = Overcast | Raining | Heavy_Fog }, // Jellicent on Challenge Beach + new EncounterStatic8S(SWSH) { Species = 342, Level = 42, Locations = new[] {170, 180}, Weather = Overcast }, // Crawdaunt on Challenge Beach, Training Lowlands + new(SWSH) { Species = 091, Level = 42, Location = 170, Weather = Raining | Heavy_Fog }, // Cloyster on Challenge Beach + new(SWSH) { Species = 130, Level = 50, Location = 170, Weather = Normal | Raining | Intense_Sun }, // Gyarados on Challenge Beach + new(SWSH) { Species = 062, Level = 36, Location = 172 }, // Poliwrath in Brawlers’ Cave + new(SWSH) { Species = 294, Level = 26, Location = 172 }, // Loudred in Brawlers’ Cave + new(SWSH) { Species = 528, Level = 26, Location = 172 }, // Swoobat in Brawlers’ Cave + new(SWSH) { Species = 621, Level = 36, Location = 172 }, // Druddigon in Brawlers’ Cave + new(SWSH) { Species = 055, Level = 26, Location = 172 }, // Golduck in Brawlers’ Cave + new(SWSH) { Species = 526, Level = 42, Location = 172 }, // Gigalith in Brawlers’ Cave + new EncounterStatic8S(SW ) { Species = 744, Level = 22, Locations = new[] {172, 174}, Weather = Normal | Overcast }, // Rockruff on Challenge Road, Brawlers' Cave (c) + new EncounterStatic8S( SH) { Species = 744, Level = 22, Locations = new[] {172, 174}, Weather = Normal | Overcast | Intense_Sun | Heavy_Fog }, // Rockruff on Challenge Road, Brawlers' Cave (c) + new EncounterStatic8S(SW ) { Species = 560, Level = 26, Locations = new[] {172, 174, 180}, Weather = Stormy }, // Scrafty on Challenge Road, Brawlers’ Cave (c), Training Lowlands + new EncounterStatic8S( SH) { Species = 454, Level = 26, Locations = new[] {172, 174, 180}, Weather = Stormy }, // Toxicroak on Challenge Road, Brawlers’ Cave (c), Training Lowlands + new EncounterStatic8S(SWSH) { Species = 558, Level = 26, Locations = new[] {172, 174, 180}, Weather = Sandstorm }, // Crustle on Challenge Road, Brawlers’ Cave (c), Training Lowlands + new EncounterStatic8S(SWSH) { Species = 340, Level = 42, Locations = new[] {172, 176} }, // Whiscash in Courageous Cavern, Brawlers' Cave + new(SWSH) { Species = 620, Level = 28, Location = 174 }, // Mienshao on Challenge Road + new(SWSH) { Species = 625, Level = 36, Location = 174, Weather = Overcast }, // Bisharp on Challenge Road + new(SWSH) { Species = 758, Level = 28, Location = 174, Gender = 1, Weather = Intense_Sun }, // Salazzle on Challenge Road + new(SWSH) { Species = 475, Level = 32, Location = 174, Weather = Heavy_Fog }, // Gallade on Challenge Road + new(SWSH) { Species = 745, Level = 32, Location = 174 }, // Lycanroc on Challenge Road + new(SWSH) { Species = 745, Level = 32, Location = 174, Form = 01, Weather = Overcast }, // Lycanroc-1 on Challenge Road + new(SWSH) { Species = 212, Level = 40, Location = 174, Weather = Sandstorm }, // Scizor on Challenge Road + new(SW ) { Species = 127, Level = 26, Location = 174, Weather = Intense_Sun }, // Pinsir on Challenge Road + new( SH) { Species = 214, Level = 26, Location = 174, Weather = Intense_Sun }, // Heracross on Challenge Road + new(SW ) { Species = 782, Level = 22, Location = 174, Weather = Intense_Sun | Sandstorm | Heavy_Fog }, // Jangmo-o on Challenge Road + new(SWSH) { Species = 227, Level = 26, Location = 174, Weather = Normal | Raining | Intense_Sun | Sandstorm }, // Skarmory on Challenge Road + new(SWSH) { Species = 426, Level = 26, Location = 174, Weather = Heavy_Fog }, // Drifblim on Challenge Road + new(SW ) { Species = 628, Level = 26, Location = 174, Weather = Overcast }, // Braviary on Challenge Road + new( SH) { Species = 630, Level = 26, Location = 174, Weather = Overcast }, // Mandibuzz on Challenge Road + new(SWSH) { Species = 082, Level = 26, Location = 174, Weather = Thunderstorm }, // Magneton on Challenge Road + new EncounterStatic8S(SWSH) { Species = 507, Level = 28, Locations = new[] {174, 180}, Weather = Normal | Heavy_Fog }, // Herdier on Challenge Road, Training Lowlands + new(SWSH) { Species = 558, Level = 28, Location = 176 }, // Crustle in Courageous Cavern + new(SWSH) { Species = 768, Level = 32, Location = 176 }, // Golisopod in Courageous Cavern + new(SWSH) { Species = 528, Level = 28, Location = 176 }, // Swoobat in Courageous Cavern + new(SWSH) { Species = 834, Level = 28, Location = 176 }, // Drednaw in Courageous Cavern + new(SWSH) { Species = 621, Level = 42, Location = 176 }, // Druddigon in Courageous Cavern + new(SWSH) { Species = 847, Level = 42, Location = 176 }, // Barraskewda in the Courageous Cavern + new(SWSH) { Species = 073, Level = 42, Location = 176 }, // Tentacruel in Courageous Cavern + //new(SWSH) { Species = 526, Level = 42, Location = -1 }, // Gigalith + //new(SWSH) { Species = 113, Level = 30, Location = -1 }, // Chansey + //new(SWSH) { Species = 768, Level = 32, Location = -1 }, // Golisopod + //new(SWSH) { Species = 558, Level = 30, Location = -1 }, // Crustle + new(SWSH) { Species = 593, Level = 42, Location = 178, Weather = Overcast | Heavy_Fog }, // Jellicent in Loop Lagoon + new(SWSH) { Species = 687, Level = 32, Location = 178, Weather = Overcast | Raining }, // Malamar in Loop Lagoon + new(SWSH) { Species = 040, Level = 32, Location = 178, Weather = Heavy_Fog }, // Wigglytuff in Loop Lagoon + new(SWSH) { Species = 404, Level = 30, Location = 178, Weather = Thunderstorm }, // Luxio in Loop Lagoon + new(SWSH) { Species = 834, Level = 30, Location = 178, Weather = Normal | Intense_Sun }, // Drednaw in Loop Lagoon + new(SWSH) { Species = 871, Level = 22, Location = 178, Weather = Normal | Stormy | Heavy_Fog }, // Pincurchin in Loop Lagoon + new(SWSH) { Species = 748, Level = 26, Location = 178, Weather = Overcast | Raining | Heavy_Fog }, // Toxapex in Loop Lagoon + new(SWSH) { Species = 853, Level = 32, Location = 178, Weather = Normal | Overcast | Intense_Sun | Heavy_Fog }, // Grapploct in Loop Lagoon + new(SWSH) { Species = 770, Level = 32, Location = 178, Weather = Overcast | Heavy_Fog }, // Palossand in Loop Lagoon + new(SWSH) { Species = 065, Level = 50, Location = 178, Weather = Normal | Raining }, // Alakazam in Loop Lagoon + new(SWSH) { Species = 571, Level = 50, Location = 178, Weather = Overcast | Heavy_Fog }, // Zoroark in Loop Lagoon + new(SWSH) { Species = 462, Level = 50, Location = 178, Weather = Thunderstorm }, // Magnezone in Loop Lagoon + new(SWSH) { Species = 744, Level = 40, Location = 178 }, // Rockruff in Loop Lagoon + new(SWSH) { Species = 636, Level = 40, Location = 178, Weather = Intense_Sun }, // Larvesta in Loop Lagoon + new(SWSH) { Species = 279, Level = 42, Location = 178, Weather = Raining }, // Pelipper in Loop Lagoon + new(SWSH) { Species = 405, Level = 50, Location = 178, Weather = Thunderstorm }, // Luxray in Loop Lagoon + new(SWSH) { Species = 663, Level = 50, Location = 178, Weather = Intense_Sun }, // Talonflame in Loop Lagoon + new(SWSH) { Species = 508, Level = 42, Location = 180 }, // Stoutland in the Training Lowlands + new(SWSH) { Species = 625, Level = 36, Location = 180, Weather = Overcast }, // Bisharp in the Training Lowlands + new(SWSH) { Species = 405, Level = 36, Location = 180, Weather = Thunderstorm }, // Luxray in the Training Lowlands + new(SWSH) { Species = 663, Level = 36, Location = 180, Weather = Intense_Sun }, // Talonflame in the Training Lowlands + new(SWSH) { Species = 040, Level = 30, Location = 180, Weather = Heavy_Fog }, // Wigglytuff in the Training Lowlands + new(SWSH) { Species = 099, Level = 28, Location = 180, Weather = Normal | Overcast | Stormy | Intense_Sun | Sandstorm }, // Kingler in the Training Lowlands + new(SWSH) { Species = 115, Level = 32, Location = 180, Weather = Normal | Overcast }, // Kangaskhan in the Training Lowlands + new(SWSH) { Species = 123, Level = 28, Location = 180, Weather = Normal | Intense_Sun }, // Scyther in the Training Lowlands + new(SWSH) { Species = 404, Level = 28, Location = 180, Weather = Thunderstorm }, // Luxio in the Training Lowlands + new(SWSH) { Species = 764, Level = 28, Location = 180, Weather = Heavy_Fog }, // Comfey in the Training Lowlands + new(SWSH) { Species = 452, Level = 28, Location = 180, Weather = Overcast | Intense_Sun }, // Drapion in the Training Lowlands + new(SWSH) { Species = 279, Level = 28, Location = 180, Weather = Raining }, // Pelipper in the Training Lowlands + new(SW ) { Species = 127, Level = 28, Location = 180, Weather = Normal | Intense_Sun }, // Pinsir in the Training Lowlands + new( SH) { Species = 214, Level = 28, Location = 180, Weather = Normal | Intense_Sun }, // Heracross in the Training Lowlands + new(SWSH) { Species = 528, Level = 28, Location = 180, Weather = Overcast }, // Swoobat in the Training Lowlands + new(SWSH) { Species = 241, Level = 28, Location = 180, Weather = Normal | Overcast | Intense_Sun }, // Miltank in the Training Lowlands + new(SWSH) { Species = 082, Level = 28, Location = 180, Weather = Thunderstorm }, // Magneton in the Training Lowlands + new(SWSH) { Species = 662, Level = 28, Location = 180, Weather = Intense_Sun }, // Fletchinder in the Training Lowlands + new(SWSH) { Species = 227, Level = 26, Location = 180, Weather = Sandstorm }, // Skarmory in the Training Lowlands + new(SW ) { Species = 782, Level = 22, Location = 180, Weather = Sandstorm }, // Jangmo-o in Training Lowlands + new(SWSH) { Species = 128, Level = 28, Location = 180, Weather = All_IoA }, // Tauros in the Training Lowlands + new(SWSH) { Species = 687, Level = 28, Location = 180, Weather = Overcast | Raining }, // Malamar in the Training Lowlands + new(SWSH) { Species = 549, Level = 28, Location = 180, Weather = Intense_Sun }, // Lilligant in the Training Lowlands + new(SWSH) { Species = 426, Level = 28, Location = 180, Weather = Heavy_Fog }, // Drifblim in the Training Lowlands + new(SWSH) { Species = 055, Level = 26, Location = 180, Weather = Normal | Overcast | Stormy | Intense_Sun }, // Golduck in the Training Lowlands + new(SWSH) { Species = 184, Level = 26, Location = 180, Weather = Heavy_Fog }, // Azumarill in the Training Lowlands + new(SWSH) { Species = 617, Level = 36, Location = 180, Weather = Thunderstorm }, // Accelgor in the Training Lowlands + new(SWSH) { Species = 212, Level = 42, Location = 180, Weather = Sandstorm }, // Scizor in the Training Lowlands + new(SWSH) { Species = 589, Level = 36, Location = 180, Weather = Sandstorm }, // Escavalier in the Training Lowlands + new(SWSH) { Species = 616, Level = 26, Location = 180, Weather = Raining }, // Shelmet in the Training Lowlands + new(SWSH) { Species = 588, Level = 26, Location = 180, Weather = Overcast }, // Karrablast in the Training Lowlands + new(SWSH) { Species = 847, Level = 42, Location = 180, Weather = Normal | Stormy | Intense_Sun | Sandstorm | Heavy_Fog }, // Barraskewda in the Training Lowlands + new(SWSH) { Species = 553, Level = 50, Location = 184, Weather = Overcast | Raining }, // Krookodile in the Potbottom Desert + new(SWSH) { Species = 464, Level = 50, Location = 184, Weather = Normal | Intense_Sun | Sandstorm | Heavy_Fog }, // Rhyperior in the Potbottom Desert + new(SWSH) { Species = 105, Level = 42, Location = 184, Weather = Normal | Intense_Sun | Heavy_Fog }, // Marowak in the Potbottom Desert + new(SWSH) { Species = 552, Level = 42, Location = 184, Weather = Overcast | Raining }, // Krokorok in the Potbottom Desert + new(SWSH) { Species = 112, Level = 42, Location = 184, Weather = Normal | Intense_Sun | Sandstorm }, // Rhydon in the Potbottom Desert + new(SWSH) { Species = 324, Level = 42, Location = 184, Weather = Intense_Sun | Heavy_Fog }, // Torkoal in the Potbottom Desert + new(SWSH) { Species = 844, Level = 42, Location = 184, Weather = Normal | Intense_Sun | Heavy_Fog }, // Sandaconda in the Potbottom Desert + new(SWSH) { Species = 637, Level = 50, Location = 184, Weather = Intense_Sun }, // Volcarona in the Potbottom Desert + new(SWSH) { Species = 028, Level = 42, Location = 184, Weather = Sandstorm }, // Sandslash in the Potbottom Desert + new(SW ) { Species = 628, Level = 42, Location = 184, Weather = Normal | Overcast | Raining | Sandstorm | Intense_Sun | Heavy_Fog }, // Braviary in the Potbottom Desert + new( SH) { Species = 630, Level = 42, Location = 184, Weather = Normal | Overcast | Raining | Sandstorm | Intense_Sun | Heavy_Fog }, // Mandibuzz in the Potbottom Desert + new(SWSH) { Species = 479, Level = 50, Location = 186, FlawlessIVCount = 3 }, // Rotom in the Workout Sea + new(SWSH) { Species = 479, Level = 50, Location = 186, Moves = new[] {435,506,268}, Form = 01, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-1 in the Workout Sea + new(SWSH) { Species = 479, Level = 50, Location = 186, Moves = new[] {435,506,268}, Form = 02, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-2 in the Workout Sea + new(SWSH) { Species = 479, Level = 50, Location = 186, Moves = new[] {435,506,268}, Form = 03, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-3 in the Workout Sea + new(SWSH) { Species = 479, Level = 50, Location = 186, Moves = new[] {435,506,268}, Form = 04, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-4 in the Workout Sea + new(SWSH) { Species = 479, Level = 50, Location = 186, Moves = new[] {435,506,268}, Form = 05, Weather = Normal | Stormy | Intense_Sun | Heavy_Fog }, // Rotom-5 in the Workout Sea + new(SWSH) { Species = 132, Level = 50, Location = 186, FlawlessIVCount = 3 }, // Ditto in the Workout Sea + //new(SWSH) { Species = 242, Level = 50, Location = -1 }, // Blissey + new(SWSH) { Species = 103, Level = 50, Location = 190, Weather = Normal | Raining | Intense_Sun }, // Exeggutor in the Insular Sea + new(SWSH) { Species = 571, Level = 50, Location = 190, Weather = Overcast }, // Zoroark in the Insular Sea + new(SWSH) { Species = 462, Level = 50, Location = 190, Weather = Thunderstorm }, // Magnezone in the Insular Sea + new(SWSH) { Species = 637, Level = 50, Location = 190, Weather = Intense_Sun }, // Volcarona in the Insular Sea + new(SWSH) { Species = 279, Level = 45, Location = 190, Weather = Overcast | Stormy }, // Pelipper in the Insular Sea + new(SWSH) { Species = 065, Level = 50, Location = 190, Weather = Heavy_Fog }, // Alakazam in the Insular Sea + new EncounterStatic8S(SWSH) { Species = 764, Level = 50, Locations = new[] {190, 194}, Weather = Heavy_Fog }, // Comfey in the Insular Sea, Honeycalm Sea + new(SWSH) { Species = 230, Level = 60, Location = 192, Weather = Thunderstorm }, // Kingdra in the Honeycalm Sea + new(SWSH) { Species = 117, Level = 45, Location = 192, Weather = Normal | Overcast | Stormy | Intense_Sun | Heavy_Fog }, // Seadra in the Honeycalm Sea + new(SWSH) { Species = 549, Level = 45, Location = 194, Weather = Normal | Intense_Sun }, // Lilligant on Honeycalm Island + new(SWSH) { Species = 415, Level = 40, Location = 194, Weather = Overcast | Stormy }, // Combee on Honeycalm Island + #endregion + + #region R2 Static Encounters + new EncounterStatic8S(SWSH) { Species = 144, Level = 70, Locations = new[] {208, 210, 212, 214}, Moves = new[] {821,542,427,375}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Weather = All_CT }, // Articuno-1 in the Crown Tundra + new EncounterStatic8S(SWSH) { Species = 145, Level = 70, Locations = new[] {122, 124, 126, 128, 130}, Moves = new[] {823,065,179,116}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Weather = All }, // Zapdos-1 in a Wild Area + new EncounterStatic8S(SWSH) { Species = 146, Level = 70, Locations = new[] {164, 166, 170, 178, 186, 188, 190, 192}, Moves = new[] {822,542,389,417}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Weather = All_IoA }, // Moltres-1 on the Isle of Armor + new(SWSH) { Species = 377, Level = 70, Location = 236, ScriptedNoMarks = true, Moves = new[] {276,444,359,174}, FlawlessIVCount = 3, Ability = OnlyFirst }, // Regirock + new(SWSH) { Species = 378, Level = 70, Location = 238, ScriptedNoMarks = true, Moves = new[] {058,192,133,196}, FlawlessIVCount = 3, Ability = OnlyFirst }, // Regice + new(SWSH) { Species = 379, Level = 70, Location = 240, ScriptedNoMarks = true, Moves = new[] {484,430,334,451}, FlawlessIVCount = 3, Ability = OnlyFirst }, // Registeel + new(SWSH) { Species = 894, Level = 70, Location = 242, ScriptedNoMarks = true, Moves = new[] {819,527,245,393}, FlawlessIVCount = 3, Ability = OnlyFirst }, // Regieleki + new(SWSH) { Species = 895, Level = 70, Location = 242, ScriptedNoMarks = true, Moves = new[] {820,337,359,673}, FlawlessIVCount = 3, Ability = OnlyFirst }, // Regidrago + new(SWSH) { Species = 486, Level =100, Location = 210, ScriptedNoMarks = true, Moves = new[] {416,428,359,462}, FlawlessIVCount = 3, Ability = OnlyFirst, DynamaxLevel = 10 }, // Regigigas in the Giant’s Bed + new(SWSH) { Species = 638, Level = 70, Location = 226, FlawlessIVCount = 3, Ability = OnlyFirst, Weather = No_Sun_Sand }, // Cobalion at the Frigid Sea + new(SWSH) { Species = 639, Level = 70, Location = 232, FlawlessIVCount = 3, Ability = OnlyFirst, Weather = Overcast }, // Terrakion in Lakeside Cavern + new(SWSH) { Species = 640, Level = 70, Location = 210, FlawlessIVCount = 3, Ability = OnlyFirst, Weather = All_CT }, // Virizion at Giant's Bed + new(SWSH) { Species = 647, Level = 65, Location = 230, Moves = new[] {548,533,014,056}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, Form = 01, Fateful = true, Weather = All_Ballimere }, // Keldeo-1 at Ballimere Lake + //new(SWSH) { Species = 896, Level = 75, Location = -1, Moves = new[] {556,037,419,023}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Glastrier + //new(SWSH) { Species = 897, Level = 75, Location = -1, Moves = new[] {247,037,506,024}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Spectrier + new(SWSH) { Species = 898, Level = 80, Location = 220, Moves = new[] {202,094,473,505}, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst, ScriptedNoMarks = true }, // Calyrex + new(SWSH) { Species = 442, Level = 72, Location = 230, FlawlessIVCount = 3, Ability = OnlyHidden, Weather = All_Ballimere }, // Spiritomb at Ballimere Lake + + // suspected unused or uncatchable + //new(SWSH) { Species = 803, Level = 60, Location = -1, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Poipole + //new(SWSH) { Species = 789, Level = 60, Location = -1, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Cosmog + //new(SWSH) { Species = 494, Level = 70, Location = -1, FlawlessIVCount = 3, Shiny = Never, Ability = OnlyFirst }, // Victini + + new(SWSH) { Species = 473, Level = 65, Location = 204, Weather = Normal | Overcast | Intense_Sun | Icy }, // Mamoswine on Slippery Slope + new(SWSH) { Species = 460, Level = 65, Location = 204, Weather = Snowing }, // Abomasnow on Slippery Slope + new(SWSH) { Species = 698, Level = 60, Location = 204, Weather = Normal | Overcast | Icy }, // Amaura on Slippery Slope + new(SWSH) { Species = 333, Level = 60, Location = 204, Weather = Overcast | Snowing }, // Swablu on Slippery Slope + new(SWSH) { Species = 124, Level = 62, Location = 204, Weather = Icy | Heavy_Fog }, // Jynx on Slippery Slope + new(SWSH) { Species = 857, Level = 62, Location = 204, Weather = Heavy_Fog }, // Hattrem on Slippery Slope + new(SWSH) { Species = 478, Level = 63, Location = 204, Weather = Snowstorm }, // Froslass on Slippery Slope + new(SWSH) { Species = 362, Level = 63, Location = 204, Weather = Snowstorm }, // Glalie on Slippery Slope + new(SWSH) { Species = 467, Level = 65, Location = 204, Weather = Intense_Sun }, // Magmortar on Slippery Slope + new(SWSH) { Species = 143, Level = 65, Location = 204, Weather = Normal | Intense_Sun }, // Snorlax on Slippery Slope + new(SWSH) { Species = 872, Level = 60, Location = 204, Weather = Normal | Heavy_Fog }, // Snom on Slippery Slope + new EncounterStatic8S(SWSH) { Species = 832, Level = 63, Locations = new[] {204, 208}, Weather = Normal | Intense_Sun }, // Dubwool on Slippery Slope, Frostpoint Field + new EncounterStatic8S(SW ) { Species = 576, Level = 65, Locations = new[] {204, 208}, Weather = Heavy_Fog }, // Gothitelle on Slippery Slope, Frostpoint Field + new EncounterStatic8S( SH) { Species = 579, Level = 65, Locations = new[] {204, 208}, Weather = Heavy_Fog }, // Reuniclus on Slippery Slope, Frostpoint Field + new EncounterStatic8S(SWSH) { Species = 461, Level = 63, Locations = new[] {204, 208}, Weather = Overcast }, // Weavile on Slippery Slope, Frostpoint Field + new EncounterStatic8S(SWSH) { Species = 531, Level = 62, Locations = new[] {204, 208}, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Audino on Slippery Slope, Frostpoint Field + new EncounterStatic8S(SWSH) { Species = 615, Level = 62, Locations = new[] {204, 208, 210}, Weather = Icy }, // Cryogonal on Slippery Slope, Frostpoint Field, Giant’s Bed + new EncounterStatic8S(SWSH) { Species = 778, Level = 62, Locations = new[] {204, 208, 210, 212}, Weather = Heavy_Fog }, // Mimikyu on Slippery Slope, Frostpoint Field, Giant’s Bed, Old Cemetery + new EncounterStatic8S(SWSH) { Species = 126, Level = 62, Locations = new[] {204, 210}, Weather = Intense_Sun }, // Magmar on Slippery Slope, Giant’s Bed + new EncounterStatic8S(SWSH) { Species = 861, Level = 65, Locations = new[] {204, 210}, Weather = Heavy_Fog }, // Grimmsnarl on Slippery Slope, Giant’s Bed + new EncounterStatic8S(SWSH) { Species = 709, Level = 63, Locations = new[] {204, 210, 212}, Weather = Overcast }, // Trevenant on Slippery Slope, Giant's Bed, Old Cemetery + new(SWSH) { Species = 124, Level = 62, Location = 208, Weather = Snowing | Heavy_Fog }, // Jynx in Frostpoint Field + new(SWSH) { Species = 460, Level = 65, Location = 208, Weather = Normal | Overcast | Intense_Sun | Snowing }, // Abomasnow in Frostpoint Field + new(SWSH) { Species = 698, Level = 60, Location = 208, Weather = Normal | Icy }, // Amaura in Frostpoint Field + new(SWSH) { Species = 133, Level = 60, Location = 208, Weather = Snowstorm }, // Eevee in Frostpoint Field + new(SWSH) { Species = 029, Level = 60, Location = 208, Weather = Normal | Overcast | Intense_Sun | Icy }, // Nidoran♀ in Frostpoint Field + new(SWSH) { Species = 032, Level = 60, Location = 208, Weather = Normal | Overcast | Intense_Sun | Icy }, // Nidoran♂ in Frostpoint Field + new(SWSH) { Species = 359, Level = 62, Location = 208, Weather = Snowstorm }, // Absol in Frostpoint Field + new(SWSH) { Species = 143, Level = 65, Location = 208, Weather = Normal | Stormy | Intense_Sun | Overcast }, // Snorlax in Frostpoint Field + new EncounterStatic8S(SWSH) { Species = 584, Level = 65, Locations = new[] {208, 210}, Weather = Icy }, // Vanilluxe in Frostpoint Field, Giant’s Bed + new EncounterStatic8S(SWSH) { Species = 333, Level = 60, Locations = new[] {208, 210}, Weather = Overcast }, // Swablu in Frostpoint Field, Giant’s Bed + new EncounterStatic8S(SWSH) { Species = 034, Level = 65, Locations = new[] {208, 210}, Weather = No_Sun_Sand }, // Nidoking in Frostpoint Field, Giant’s Bed + new EncounterStatic8S(SWSH) { Species = 030, Level = 63, Locations = new[] {208, 210}, Weather = All_CT }, // Nidorina in Frostpoint Field (c), in the Giant’s Bed + new EncounterStatic8S(SWSH) { Species = 858, Level = 65, Locations = new[] {208, 210}, Weather = Heavy_Fog }, // Hatterene in Frostpoint Field, Giant’s Bed + new EncounterStatic8S(SWSH) { Species = 437, Level = 65, Locations = new[] {208, 222}, Weather = Normal | Overcast }, // Bronzong in Frostpoint Field (c), Giant’s Foot + new(SWSH) { Species = 029, Level = 60, Location = 210, Weather = Normal | Stormy | Intense_Sun }, // Nidoran♀ in the Giant’s Bed + new(SWSH) { Species = 832, Level = 63, Location = 210 }, // Dubwool in the Giant’s Bed + new(SW ) { Species = 874, Level = 63, Location = 210, Weather = All_CT }, // Stonjourner in the Giant’s Bed + new( SH) { Species = 143, Level = 65, Location = 210, Weather = All_CT }, // Snorlax in the Giant’s Bed + new(SWSH) { Species = 142, Level = 65, Location = 210, Weather = All_CT }, // Aerodactyl in the Giant’s Bed + new(SWSH) { Species = 133, Level = 60, Location = 210 }, // Eevee in the Giant’s Bed + new(SWSH) { Species = 470, Level = 63, Location = 210 }, // Leafeon in the Giant’s Bed + new(SWSH) { Species = 033, Level = 63, Location = 210, Weather = All_CT }, // Nidorino in the Giant’s Bed + new(SWSH) { Species = 534, Level = 65, Location = 210 }, // Conkeldurr in the Giant’s Bed + new(SWSH) { Species = 820, Level = 65, Location = 210, Weather = No_Sun_Sand }, // Greedent in the Giant’s Bed + new(SWSH) { Species = 031, Level = 65, Location = 210, Weather = Normal | Overcast | Raining | Intense_Sun | Icy | Heavy_Fog }, // Nidoqueen in the Giant’s Bed + new(SWSH) { Species = 862, Level = 65, Location = 210, Weather = Overcast | Raining }, // Obstagoon in the Giant’s Bed + new(SWSH) { Species = 609, Level = 65, Location = 210, Weather = Overcast | Heavy_Fog }, // Chandelure in the Giant’s Bed + new(SWSH) { Species = 752, Level = 65, Location = 210, Weather = Stormy }, // Araquanid in the Giant’s Bed + new(SWSH) { Species = 134, Level = 63, Location = 210, Weather = Raining }, // Vaporeon in the Giant’s Bed + new(SWSH) { Species = 596, Level = 63, Location = 210, Weather = Thunderstorm }, // Galvantula in the Giant’s Bed + new(SWSH) { Species = 466, Level = 65, Location = 210, Weather = Thunderstorm }, // Electivire in the Giant’s Bed + new(SWSH) { Species = 135, Level = 63, Location = 210, Weather = Thunderstorm }, // Jolteon in the Giant’s Bed + new(SWSH) { Species = 125, Level = 63, Location = 210, Weather = Thunderstorm }, // Electabuzz in the Giant’s Bed + new(SWSH) { Species = 467, Level = 63, Location = 210, Weather = Intense_Sun }, // Magmortar in the Giant’s Bed + new(SWSH) { Species = 631, Level = 63, Location = 210, Weather = Intense_Sun }, // Heatmor in the Giant’s Bed + new(SWSH) { Species = 632, Level = 63, Location = 210, Weather = Intense_Sun }, // Durant in the Giant’s Bed + new(SWSH) { Species = 136, Level = 63, Location = 210, Weather = Intense_Sun }, // Flareon in the Giant’s Bed + new(SWSH) { Species = 197, Level = 63, Location = 210, Weather = Overcast }, // Umbreon in the Giant’s Bed + new(SWSH) { Species = 196, Level = 63, Location = 210, Weather = Snowing }, // Espeon in the Giant’s Bed + new(SWSH) { Species = 359, Level = 65, Location = 210, Weather = Snowstorm }, // Absol in the Giant’s Bed + new(SWSH) { Species = 471, Level = 63, Location = 210, Weather = Snowstorm }, // Glaceon in the Giant’s Bed + new(SWSH) { Species = 700, Level = 63, Location = 210, Weather = Heavy_Fog }, // Sylveon in the Giant’s Bed + new(SWSH) { Species = 036, Level = 63, Location = 210, Weather = Heavy_Fog }, // Clefable in the Giant’s Bed + new(SWSH) { Species = 340, Level = 65, Location = 210, Weather = All_CT }, // Whiscash in the Giant’s Bed + new EncounterStatic8S(SWSH) { Species = 855, Level = 63, Locations = new[] {210, 212}, Weather = Normal | Stormy | Intense_Sun | Snowstorm | Heavy_Fog }, // Polteageist in the Giant’s Bed, Old Cemetery + new EncounterStatic8S(SWSH) { Species = 887, Level = 65, Locations = new[] {210, 212}, Weather = Normal | Overcast | Stormy | Intense_Sun | Snowing }, // Dragapult in the Giant’s Bed, Old Cemetery + new EncounterStatic8S(SWSH) { Species = 478, Level = 65, Locations = new[] {210, 212, 214}, Weather = Icy }, // Froslass in the Giant’s Bed, Old Cemetery, Snowslide Slope + new EncounterStatic8S(SWSH) { Species = 437, Level = 63, Locations = new[] {210, 214}, Weather = All_CT }, // Bronzong in the Giant’s Bed, Snowslide Slope (c) + new EncounterStatic8S(SWSH) { Species = 362, Level = 65, Locations = new[] {210, 214}, Weather = Icy }, // Glalie in the Giant’s Bed, Snowslide Slope + new EncounterStatic8S(SWSH) { Species = 334, Level = 65, Locations = new[] {210, 218, 222, 226, 230}, Weather = Overcast }, // Altaria in the Giant’s Bed, Path to the Peak, Giant’s Foot, Frigid Sea, Ballimere Lake + new EncounterStatic8S(SWSH) { Species = 344, Level = 65, Locations = new[] {210, 222}, Weather = Overcast | Stormy | Intense_Sun | Icy | Heavy_Fog }, // Claydol in the Giant’s Bed, Giant’s Foot + new EncounterStatic8S(SWSH) { Species = 531, Level = 62, Locations = new[] {210, 222, 230}, Weather = All_CT }, // Audino in the Giant’s Bed, Giant’s Foot + new EncounterStatic8S(SWSH) { Species = 130, Level = 67, Locations = new[] {210, 230}, Weather = Normal | Overcast | Stormy | Intense_Sun | Icy }, // Gyarados in the Giant’s Bed, Ballimere Lake + new EncounterStatic8S(SWSH) { Species = 350, Level = 67, Locations = new[] {210, 230}, Weather = Heavy_Fog }, // Milotic in the Giant’s Bed, Ballimere Lake + new( SH) { Species = 078, Level = 67, Location = 212, Form = 01, Weather = Heavy_Fog }, // Rapidash-1 in the Old Cemetery + new(SWSH) { Species = 872, Level = 62, Location = 214, Weather = Normal | Overcast }, // Snom on Snowslide Slope + new(SWSH) { Species = 698, Level = 62, Location = 214, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Amaura on Snowslide Slope + new(SWSH) { Species = 621, Level = 65, Location = 214, Weather = Normal | Intense_Sun }, // Druddigon on Snowslide Slope + new(SWSH) { Species = 832, Level = 65, Location = 214, Weather = Normal | Intense_Sun }, // Dubwool on Snowslide Slope + new(SWSH) { Species = 699, Level = 65, Location = 214, Weather = Normal | Overcast | Icy | Heavy_Fog }, // Aurorus on Snowslide Slope + new(SWSH) { Species = 376, Level = 68, Location = 214, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Metagross on Snowslide Slope + new(SWSH) { Species = 461, Level = 65, Location = 214, Weather = Overcast }, // Weavile on Snowslide Slope + new(SWSH) { Species = 709, Level = 65, Location = 214, Weather = Overcast }, // Trevenant on Snowslide Slope + new(SWSH) { Species = 467, Level = 67, Location = 214, Weather = Intense_Sun }, // Magmortar on Snowslide Slope + new(SWSH) { Species = 362, Level = 67, Location = 214, Weather = Icy }, // Glalie on Snowslide Slope + new EncounterStatic8S(SWSH) { Species = 375, Level = 63, Locations = new[] {214, 216 }, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Metang on Snowslide Slope, Tunnel to the Top (c) + new EncounterStatic8S(SWSH) { Species = 615, Level = 65, Locations = new[] {214, 222}, Weather = Icy }, // Cryogonal on Snowslide Slope, Giant’s Foot + new EncounterStatic8S(SWSH) { Species = 614, Level = 67, Locations = new[] {214, 226, 228}, Weather = Icy }, // Beartic on Snowslide Slope, Frigid Sea, Three-Point Pass + new EncounterStatic8S(SWSH) { Species = 126, Level = 65, Locations = new[] {214, 230}, Weather = Intense_Sun }, // Magmar on Snowslide Slope, Ballimere Lake + new(SWSH) { Species = 584, Level = 67, Location = 214, Weather = Icy }, // Vanilluxe on Snowslide Slope + new(SW ) { Species = 555, Level = 67, Location = 214, Form = 02, Weather = Snowstorm }, // Darmanitan-2 on Snowslide Slope + new(SWSH) { Species = 861, Level = 67, Location = 214, Weather = Heavy_Fog }, // Grimmsnarl on Snowslide Slope + new EncounterStatic8S(SWSH) { Species = 359, Level = 67, Locations = new[] {214, 218, 222}, Weather = Snowstorm }, // Absol on Snowslide Slope, Path to the Peak, Giant’s Foot + new EncounterStatic8S(SWSH) { Species = 778, Level = 65, Locations = new[] {214, 222, 230}, Weather = Heavy_Fog }, // Mimikyu on Snowslide Slope, Giant’s Foot, Ballimere Lake + new(SWSH) { Species = 036, Level = 65, Location = 214, Weather = Heavy_Fog }, // Clefable on Snowslide Slope + new(SWSH) { Species = 036, Level = 65, Location = 216, Weather = Overcast }, // Clefable in the Tunnel to the Top + new(SWSH) { Species = 621, Level = 65, Location = 216, Weather = Overcast }, // Druddigon in the Tunnel to the Top + new(SWSH) { Species = 478, Level = 65, Location = 216, Weather = Overcast }, // Froslass in the Tunnel to the Top + new(SW ) { Species = 371, Level = 65, Location = 216, Weather = Overcast }, // Bagon in the Tunnel to the Top + new( SH) { Species = 443, Level = 65, Location = 216, Weather = Overcast }, // Gible in the Tunnel to the Top + new(SW ) { Species = 373, Level = 68, Location = 216, Weather = Overcast }, // Salamence in the Tunnel to the Top + new( SH) { Species = 445, Level = 68, Location = 216, Weather = Overcast }, // Garchomp in the Tunnel to the Top + new(SWSH) { Species = 703, Level = 65, Location = 216, Weather = Overcast }, // Carbink in the Tunnel to the Top + new EncounterStatic8S(SWSH) { Species = 041, Level = 63, Locations = new[] {216, 224}, Weather = Overcast }, // Zubat in the Tunnel to the Top, Roaring-Sea Caves + new(SWSH) { Species = 042, Level = 65, Location = 216, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Golbat in the Tunnel to the Top + new(SWSH) { Species = 873, Level = 65, Location = 218, Weather = Normal | Overcast | Intense_Sun | Icy | Heavy_Fog }, // Frosmoth on the Path to the Peak + new(SW ) { Species = 373, Level = 68, Location = 218, Weather = Intense_Sun }, // Salamence on the Path to the Peak + new( SH) { Species = 445, Level = 68, Location = 218, Weather = Intense_Sun }, // Garchomp on the Path to the Peak + new(SWSH) { Species = 621, Level = 65, Location = 218 }, // Druddigon on the Path to the Peak + new(SWSH) { Species = 851, Level = 67, Location = 222, Weather = Normal | Intense_Sun }, // Centiskorch at the Giant’s Foot + new(SWSH) { Species = 879, Level = 67, Location = 222, Weather = Overcast | Stormy | Icy | Heavy_Fog }, // Copperajah at the Giant’s Foot + new(SWSH) { Species = 534, Level = 67, Location = 222 }, // Conkeldurr at the Giant’s Foot + new(SW ) { Species = 138, Level = 63, Location = 222, Weather = All_CT }, // Omanyte at the Giant’s Foot + new( SH) { Species = 140, Level = 63, Location = 222, Weather = All_CT }, // Kabuto at the Giant’s Foot + new(SWSH) { Species = 566, Level = 63, Location = 222, Weather = All_CT }, // Archen at the Giant’s Foot + new(SWSH) { Species = 126, Level = 65, Location = 222, Weather = Intense_Sun }, // Magmar at the Giant’s Foot + new EncounterStatic8S(SWSH) { Species = 752, Level = 67, Locations = new[] {222, 230}, Weather = Raining }, // Araquanid at Ballimere Lake, Giant’s Foot + new EncounterStatic8S(SWSH) { Species = 125, Level = 65, Locations = new[] {222, 230}, Weather = Thunderstorm }, // Electabuzz at the Giant’s Foot, Ballimere Lake + //new(SWSH) { Species = 567, Level = 67, Location = -1 }, // Archeops + new(SW ) { Species = 635, Level = 68, Location = 224, Weather = No_Sun_Sand }, // Hydreigon in Roaring-Sea Caves, weather from Frigid Sea + new( SH) { Species = 248, Level = 68, Location = 224, Weather = No_Sun_Sand }, // Tyranitar in Roaring-Sea Caves, weather from Frigid Sea + new(SWSH) { Species = 448, Level = 67, Location = 224, Weather = Overcast }, // Lucario in Roaring-Sea Caves + new(SWSH) { Species = 042, Level = 65, Location = 224, Weather = Overcast }, // Golbat in the Roaring-Sea Caves + new( SH) { Species = 141, Level = 68, Location = 224, Weather = Overcast }, // Kabutops in Roaring-Sea Caves + new(SW ) { Species = 139, Level = 68, Location = 224, Weather = Overcast }, // Omastar in Roaring-Sea Caves + new(SWSH) { Species = 363, Level = 63, Location = 226, Weather = No_Sun_Sand }, // Spheal at the Frigid Sea + new(SWSH) { Species = 364, Level = 65, Location = 226, Weather = No_Sun_Sand }, // Sealeo at the Frigid Sea + new(SWSH) { Species = 564, Level = 63, Location = 226, Weather = Normal | Overcast | Stormy | Heavy_Fog }, // Tirtouga at the Frigid Sea + new(SWSH) { Species = 713, Level = 65, Location = 226, Weather = No_Sun_Sand }, // Avalugg at the Frigid Sea + new(SWSH) { Species = 365, Level = 68, Location = 226, Weather = Normal | Overcast | Icy | Heavy_Fog }, // Walrein at the Frigid Sea + new(SWSH) { Species = 565, Level = 67, Location = 226, Weather = Normal | Stormy | Intense_Sun }, // Carracosta at the Frigid Sea + new(SWSH) { Species = 871, Level = 65, Location = 226, Weather = Thunderstorm }, // Pincurchin at the Frigid Sea + new( SH) { Species = 875, Level = 65, Location = 226, Weather = No_Sun_Sand }, // Eiscue at the Frigid Sea + new EncounterStatic8S(SWSH) { Species = 623, Level = 65, Locations = new[] {226, 228}, Weather = All_CT }, // Golurk at the Frigid Sea (c), Three-Point Pass + new EncounterStatic8S(SWSH) { Species = 467, Level = 68, Locations = new[] {226, 230}, Weather = Intense_Sun }, // Magmortar at Frigid Sea (c), Ballimere Lake + new EncounterStatic8S(SWSH) { Species = 466, Level = 68, Locations = new[] {226, 228, 230}, Weather = Thunderstorm }, // Electivire at the Frigid Sea, Three-Point Pass, Ballimere Lake + new EncounterStatic8S(SWSH) { Species = 858, Level = 67, Locations = new[] {226, 230}, Weather = Heavy_Fog }, // Hatterene at the Frigid Sea, Ballimere Lake + new(SWSH) { Species = 887, Level = 68, Location = 228, Weather = Normal | Overcast | Raining | Intense_Sun | Icy | Heavy_Fog }, // Dragapult in Three-Point Pass + new(SWSH) { Species = 531, Level = 62, Location = 230, Weather = Normal | Overcast | Stormy | Intense_Sun | Snowing }, // Audino at Ballimere Lake + new(SWSH) { Species = 584, Level = 67, Location = 230, Weather = Snowing }, // Vanilluxe at Ballimere Lake + new(SWSH) { Species = 823, Level = 68, Location = 230, Weather = Normal | Icy }, // Corviknight at Ballimere Lake + new(SWSH) { Species = 862, Level = 68, Location = 230, Weather = Overcast }, // Obstagoon at Ballimere Lake + new(SWSH) { Species = 715, Level = 67, Location = 230, Weather = Overcast | Raining }, // Noivern at Ballimere Lake + new(SWSH) { Species = 547, Level = 65, Location = 230, Weather = Normal | Raining }, // Whimsicott at Ballimere Lake + new(SWSH) { Species = 836, Level = 67, Location = 230, Weather = Normal | Stormy | Snowing | Heavy_Fog }, // Boltund at Ballimere Lake + new(SWSH) { Species = 830, Level = 65, Location = 230, Weather = Raining | Intense_Sun }, // Eldegoss at Ballimere Lake + new(SW ) { Species = 876, Level = 65, Location = 230, Weather = Normal | Heavy_Fog }, // Indeedee at Ballimere Lake + new( SH) { Species = 876, Level = 65, Location = 230, Form = 01, Weather = Normal | Heavy_Fog }, // Indeedee-1 at Ballimere Lake + new(SWSH) { Species = 696, Level = 63, Location = 230, Weather = All_CT }, // Tyrunt at Ballimere Lake + new(SWSH) { Species = 213, Level = 65, Location = 230, Weather = Normal | Intense_Sun }, // Shuckle at Ballimere Lake + new(SWSH) { Species = 820, Level = 68, Location = 230, Weather = All_Ballimere }, // Greedent at Ballimere Lake + new(SWSH) { Species = 877, Level = 65, Location = 230, Weather = Overcast | Thunderstorm }, // Morpeko at Ballimere Lake + new(SWSH) { Species = 596, Level = 67, Location = 230, Weather = Thunderstorm }, // Galvantula at Ballimere Lake + new(SWSH) { Species = 839, Level = 68, Location = 230, Weather = Normal | Thunderstorm | Intense_Sun | Snowing | Heavy_Fog }, // Coalossal at Ballimere Lake + new(SWSH) { Species = 697, Level = 69, Location = 230, Weather = All_Ballimere }, // Tyrantrum at Ballimere Lake + new(SWSH) { Species = 531, Level = 65, Location = 230, Weather = Heavy_Fog }, // Audino at Ballimere Lake + new(SWSH) { Species = 304, Level = 63, Location = 230, Weather = All_Ballimere }, // Aron at Ballimere Lake + new(SWSH) { Species = 149, Level = 70, Location = 230, Weather = Raining | Thunderstorm }, // Dragonite at Ballimere Lake + new(SWSH) { Species = 348, Level = 67, Location = 230, Weather = Raining | Thunderstorm }, // Armaldo at Ballimere Lake + new(SWSH) { Species = 347, Level = 63, Location = 230, Weather = All_Ballimere }, // Anorith at Ballimere Lake + new(SWSH) { Species = 369, Level = 65, Location = 230, Weather = Normal | Overcast | Stormy | Intense_Sun | Snowing }, // Relicanth at Ballimere Lake + new(SWSH) { Species = 147, Level = 63, Location = 230, Weather = Raining | Heavy_Fog }, // Dratini at Ballimere Lake + new(SWSH) { Species = 148, Level = 65, Location = 230, Weather = Thunderstorm | Heavy_Fog }, // Dragonair at Ballimere Lake + new(SWSH) { Species = 615, Level = 65, Location = 230, Weather = Icy }, // Cryogonal at Ballimere Lake + new(SWSH) { Species = 715, Level = 67, Location = 232, Weather = Overcast }, // Noivern in Lakeside Cave + new(SWSH) { Species = 306, Level = 68, Location = 232, Weather = Overcast }, // Aggron in Lakeside Cave + new(SWSH) { Species = 598, Level = 67, Location = 232, Weather = All_Ballimere }, // Ferrothorn in Lakeside Cave + new(SWSH) { Species = 305, Level = 63, Location = 232, Weather = Overcast }, // Lairon in Lakeside Cave + new(SWSH) { Species = 839, Level = 68, Location = 232, Weather = Overcast }, // Coalossal in Lakeside Cave + new(SWSH) { Species = 820, Level = 68, Location = 234, Weather = All_Ballimere }, // Greedent at Dyna Tree Hill + #endregion + }; + + private const string tradeSWSH = "tradeswsh"; + private static readonly string[][] TradeSWSH = Util.GetLanguageStrings10(tradeSWSH, "zh2"); + private static readonly string[] TradeOT_R1 = { string.Empty, "チホコ", "Regina", "Régiona", "Regionalia", "Regine", string.Empty, "Tatiana", "지민", "易蒂", "易蒂" }; + private static readonly int[] TradeIVs = {15, 15, 15, 15, 15, 15}; + + private static readonly EncounterTrade8[] TradeGift_Regular = + { + new(SWSH, 052,18,08,000,04,5) { Ability = OnlySecond, TID7 = 263455, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Timid, Relearn = new[] {387,000,000,000} }, // Meowth + new(SWSH, 819,10,01,044,01,2) { Ability = OnlyFirst, TID7 = 648753, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 0, Nature = Nature.Mild }, // Skwovet + new(SWSH, 546,23,11,000,09,5) { Ability = OnlyFirst, TID7 = 101154, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 1, Nature = Nature.Modest }, // Cottonee + new(SWSH, 175,25,02,010,10,6) { Ability = OnlySecond, TID7 = 109591, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 1, Gender = 0, Nature = Nature.Timid, Relearn = new[] {791,000,000,000} }, // Togepi + new(SW , 856,30,09,859,08,3) { Ability = OnlySecond, TID7 = 101101, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 1, Nature = Nature.Quiet }, // Hatenna + new( SH, 859,30,43,000,07,6) { Ability = OnlyFirst, TID7 = 256081, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Brave, Relearn = new[] {252,000,000,000} }, // Impidimp + new(SWSH, 562,35,16,310,15,5) { Ability = OnlyFirst, TID7 = 102534, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 1, Gender = 0, Nature = Nature.Bold, Relearn = new[] {261,000,000,000} }, // Yamask + new(SW , 538,37,17,129,20,7) { Ability = OnlySecond, TID7 = 768945, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 0, Gender = 0, Nature = Nature.Adamant }, // Throh + new( SH, 539,37,17,129,14,6) { Ability = OnlyFirst, TID7 = 881426, IVs = TradeIVs, DynamaxLevel = 2, OTGender = 0, Gender = 0, Nature = Nature.Adamant }, // Sawk + new(SWSH, 122,40,56,000,12,4) { Ability = OnlyFirst, TID7 = 891846, IVs = TradeIVs, DynamaxLevel = 1, OTGender = 0, Gender = 0, Nature = Nature.Calm }, // Mr. Mime + new(SWSH, 884,50,15,038,06,2) { Ability = OnlySecond, TID7 = 101141, IVs = TradeIVs, DynamaxLevel = 3, OTGender = 0, Gender = 0, Nature = Nature.Adamant, Relearn = new[] {400,000,000,000} }, // Duraludon + }; + + private static readonly EncounterTrade8[] TradeGift_R1 = + { + new(SWSH, 052,15,01,033,04,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {387,000,000,000} }, // Meowth + new(SW , 083,15,01,013,10,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {098,000,000,000} }, // Farfetch’d + new( SH, 222,15,01,069,12,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {457,000,000,000} }, // Corsola + new( SH, 077,15,01,047,06,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {234,000,000,000} }, // Ponyta + new(SWSH, 122,15,01,005,04,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {252,000,000,000} }, // Mr. Mime + new(SW , 554,15,01,040,12,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {326,000,000,000} }, // Darumaka + new(SWSH, 263,15,01,045,04,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {245,000,000,000} }, // Zigzagoon + new(SWSH, 618,15,01,050,05,2, Random) { Ability = OnlyHidden, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {281,000,000,000} }, // Stunfisk + new(SWSH, 110,15,01,040,12,2, Random) { Ability = Any12H, TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {220,000,000,000} }, // Weezing + new(SWSH, 103,15,01,038,06,2, Random) { TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {246,000,000,000}, Form = 1 }, // Exeggutor-1 + new(SWSH, 105,15,01,038,06,2, Random) { TID7 = 101141, FlawlessIVCount = 3, DynamaxLevel = 5, OTGender = 1, IsNicknamed = false, Relearn = new[] {174,000,000,000}, Form = 1 }, // Marowak-1 + }; + + internal static readonly EncounterTrade8[] TradeGift_SWSH = ArrayUtil.ConcatAll(TradeGift_Regular, TradeGift_R1); + + internal static readonly EncounterStatic[] StaticSW = ArrayUtil.ConcatAll(Nest_Common, Nest_SW, Nest_SH, Dist_DLC2, Dist_DLC1, Dist_Base, GetEncounters(Crystal_SWSH, SW), DynAdv_SWSH, GetEncounters(Encounter_SWSH, SW)); + internal static readonly EncounterStatic[] StaticSH = ArrayUtil.ConcatAll(Nest_Common, Nest_SW, Nest_SH, Dist_DLC2, Dist_DLC1, Dist_Base, GetEncounters(Crystal_SWSH, SH), DynAdv_SWSH, GetEncounters(Encounter_SWSH, SH)); } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters8Nest.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters8Nest.cs index e5d0416e0..f578617e0 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters8Nest.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters8Nest.cs @@ -2,3044 +2,3043 @@ using System.Collections.Generic; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static partial class Encounters8Nest { - internal static partial class Encounters8Nest + #region Nest + // Location IDs for each Nest group + private const byte Nest000 = 00; + private const byte Nest001 = 01; + private const byte Nest002 = 02; + private const byte Nest003 = 03; + private const byte Nest004 = 04; + private const byte Nest005 = 05; + private const byte Nest006 = 06; + private const byte Nest007 = 07; + private const byte Nest008 = 08; + private const byte Nest009 = 09; + private const byte Nest010 = 10; + private const byte Nest011 = 11; + private const byte Nest012 = 12; + private const byte Nest013 = 13; + private const byte Nest014 = 14; + private const byte Nest015 = 15; + private const byte Nest016 = 16; + private const byte Nest017 = 17; + private const byte Nest018 = 18; + private const byte Nest019 = 19; + private const byte Nest020 = 20; + private const byte Nest021 = 21; + private const byte Nest022 = 22; + private const byte Nest023 = 23; + private const byte Nest024 = 24; + private const byte Nest025 = 25; + private const byte Nest026 = 26; + private const byte Nest027 = 27; + private const byte Nest028 = 28; + private const byte Nest029 = 29; + private const byte Nest030 = 30; + private const byte Nest031 = 31; + private const byte Nest032 = 32; + private const byte Nest033 = 33; + private const byte Nest034 = 34; + private const byte Nest035 = 35; + private const byte Nest036 = 36; + private const byte Nest037 = 37; + private const byte Nest038 = 38; + private const byte Nest039 = 39; + private const byte Nest040 = 40; + private const byte Nest041 = 41; + private const byte Nest042 = 42; + private const byte Nest043 = 43; + private const byte Nest044 = 44; + private const byte Nest045 = 45; + private const byte Nest046 = 46; + private const byte Nest047 = 47; + private const byte Nest048 = 48; + private const byte Nest049 = 49; + private const byte Nest050 = 50; + private const byte Nest051 = 51; + private const byte Nest052 = 52; + private const byte Nest053 = 53; + private const byte Nest054 = 54; + private const byte Nest055 = 55; + private const byte Nest056 = 56; + private const byte Nest057 = 57; + private const byte Nest058 = 58; + private const byte Nest059 = 59; + private const byte Nest060 = 60; + private const byte Nest061 = 61; + private const byte Nest062 = 62; + private const byte Nest063 = 63; + private const byte Nest064 = 64; + private const byte Nest065 = 65; + private const byte Nest066 = 66; + private const byte Nest067 = 67; + private const byte Nest068 = 68; + private const byte Nest069 = 69; + private const byte Nest070 = 70; + private const byte Nest071 = 71; + private const byte Nest072 = 72; + private const byte Nest073 = 73; + private const byte Nest074 = 74; + private const byte Nest075 = 75; + private const byte Nest076 = 76; + private const byte Nest077 = 77; + private const byte Nest078 = 78; + private const byte Nest079 = 79; + private const byte Nest080 = 80; + private const byte Nest081 = 81; + private const byte Nest082 = 82; + private const byte Nest083 = 83; + private const byte Nest084 = 84; + private const byte Nest085 = 85; + private const byte Nest086 = 86; + private const byte Nest087 = 87; + private const byte Nest088 = 88; + private const byte Nest089 = 89; + private const byte Nest090 = 90; + private const byte Nest091 = 91; + private const byte Nest092 = 92; + //private const byte Nest093 = 93; + //private const byte Nest094 = 94; + //private const byte Nest095 = 95; + //private const byte Nest096 = 96; + //private const byte Nest097 = 97; + private const byte Nest098 = 98; + private const byte Nest099 = 99; + private const byte Nest100 = 100; + private const byte Nest101 = 101; + private const byte Nest102 = 102; + private const byte Nest103 = 103; + private const byte Nest104 = 104; + private const byte Nest105 = 105; + private const byte Nest106 = 106; + private const byte Nest107 = 107; + private const byte Nest108 = 108; + private const byte Nest109 = 109; + private const byte Nest110 = 110; + private const byte Nest111 = 111; + private const byte Nest112 = 112; + private const byte Nest113 = 113; + private const byte Nest114 = 114; + private const byte Nest115 = 115; + private const byte Nest116 = 116; + private const byte Nest117 = 117; + private const byte Nest118 = 118; + private const byte Nest119 = 119; + private const byte Nest120 = 120; + private const byte Nest121 = 121; + private const byte Nest122 = 122; + private const byte Nest123 = 123; + private const byte Nest124 = 124; + private const byte Nest125 = 125; + private const byte Nest126 = 126; + private const byte Nest127 = 127; + private const byte Nest128 = 128; + private const byte Nest129 = 129; + private const byte Nest130 = 130; + private const byte Nest131 = 131; + private const byte Nest132 = 132; + private const byte Nest133 = 133; + private const byte Nest134 = 134; + private const byte Nest135 = 135; + private const byte Nest136 = 136; + private const byte Nest137 = 137; + private const byte Nest138 = 138; + private const byte Nest139 = 139; + private const byte Nest140 = 140; + private const byte Nest141 = 141; + private const byte Nest142 = 142; + private const byte Nest143 = 143; + private const byte Nest144 = 144; + private const byte Nest145 = 145; + private const byte Nest146 = 146; + private const byte Nest147 = 147; + private const byte Nest148 = 148; + private const byte Nest149 = 149; + private const byte Nest150 = 150; + private const byte Nest151 = 151; + private const byte Nest152 = 152; + private const byte Nest153 = 153; + private const byte Nest154 = 154; + private const byte Nest155 = 155; + private const byte Nest156 = 156; + private const byte Nest157 = 157; + private const byte Nest158 = 158; + private const byte Nest159 = 159; + private const byte Nest160 = 160; + private const byte Nest161 = 161; + private const byte Nest162 = 162; + private const byte Nest163 = 163; + private const byte Nest164 = 164; + private const byte Nest165 = 165; + private const byte Nest166 = 166; + private const byte Nest167 = 167; + private const byte Nest168 = 168; + private const byte Nest169 = 169; + private const byte Nest170 = 170; + private const byte Nest171 = 171; + private const byte Nest172 = 172; + private const byte Nest173 = 173; + private const byte Nest174 = 174; + private const byte Nest175 = 175; + private const byte Nest176 = 176; + private const byte Nest177 = 177; + private const byte Nest178 = 178; + private const byte Nest179 = 179; + private const byte Nest180 = 180; + private const byte Nest181 = 181; + private const byte Nest182 = 182; + private const byte Nest183 = 183; + private const byte Nest184 = 184; + private const byte Nest185 = 185; + private const byte Nest186 = 186; + private const byte Nest187 = 187; + private const byte Nest188 = 188; + private const byte Nest189 = 189; + private const byte Nest190 = 190; + private const byte Nest191 = 191; + private const byte Nest192 = 192; + private const byte Nest193 = 193; + private const byte Nest194 = 194; + private const byte Nest195 = 195; + private const byte Nest196 = 196; + + internal static readonly IReadOnlyList> NestLocations = new [] { - #region Nest - // Location IDs for each Nest group - private const byte Nest000 = 00; - private const byte Nest001 = 01; - private const byte Nest002 = 02; - private const byte Nest003 = 03; - private const byte Nest004 = 04; - private const byte Nest005 = 05; - private const byte Nest006 = 06; - private const byte Nest007 = 07; - private const byte Nest008 = 08; - private const byte Nest009 = 09; - private const byte Nest010 = 10; - private const byte Nest011 = 11; - private const byte Nest012 = 12; - private const byte Nest013 = 13; - private const byte Nest014 = 14; - private const byte Nest015 = 15; - private const byte Nest016 = 16; - private const byte Nest017 = 17; - private const byte Nest018 = 18; - private const byte Nest019 = 19; - private const byte Nest020 = 20; - private const byte Nest021 = 21; - private const byte Nest022 = 22; - private const byte Nest023 = 23; - private const byte Nest024 = 24; - private const byte Nest025 = 25; - private const byte Nest026 = 26; - private const byte Nest027 = 27; - private const byte Nest028 = 28; - private const byte Nest029 = 29; - private const byte Nest030 = 30; - private const byte Nest031 = 31; - private const byte Nest032 = 32; - private const byte Nest033 = 33; - private const byte Nest034 = 34; - private const byte Nest035 = 35; - private const byte Nest036 = 36; - private const byte Nest037 = 37; - private const byte Nest038 = 38; - private const byte Nest039 = 39; - private const byte Nest040 = 40; - private const byte Nest041 = 41; - private const byte Nest042 = 42; - private const byte Nest043 = 43; - private const byte Nest044 = 44; - private const byte Nest045 = 45; - private const byte Nest046 = 46; - private const byte Nest047 = 47; - private const byte Nest048 = 48; - private const byte Nest049 = 49; - private const byte Nest050 = 50; - private const byte Nest051 = 51; - private const byte Nest052 = 52; - private const byte Nest053 = 53; - private const byte Nest054 = 54; - private const byte Nest055 = 55; - private const byte Nest056 = 56; - private const byte Nest057 = 57; - private const byte Nest058 = 58; - private const byte Nest059 = 59; - private const byte Nest060 = 60; - private const byte Nest061 = 61; - private const byte Nest062 = 62; - private const byte Nest063 = 63; - private const byte Nest064 = 64; - private const byte Nest065 = 65; - private const byte Nest066 = 66; - private const byte Nest067 = 67; - private const byte Nest068 = 68; - private const byte Nest069 = 69; - private const byte Nest070 = 70; - private const byte Nest071 = 71; - private const byte Nest072 = 72; - private const byte Nest073 = 73; - private const byte Nest074 = 74; - private const byte Nest075 = 75; - private const byte Nest076 = 76; - private const byte Nest077 = 77; - private const byte Nest078 = 78; - private const byte Nest079 = 79; - private const byte Nest080 = 80; - private const byte Nest081 = 81; - private const byte Nest082 = 82; - private const byte Nest083 = 83; - private const byte Nest084 = 84; - private const byte Nest085 = 85; - private const byte Nest086 = 86; - private const byte Nest087 = 87; - private const byte Nest088 = 88; - private const byte Nest089 = 89; - private const byte Nest090 = 90; - private const byte Nest091 = 91; - private const byte Nest092 = 92; - //private const byte Nest093 = 93; - //private const byte Nest094 = 94; - //private const byte Nest095 = 95; - //private const byte Nest096 = 96; - //private const byte Nest097 = 97; - private const byte Nest098 = 98; - private const byte Nest099 = 99; - private const byte Nest100 = 100; - private const byte Nest101 = 101; - private const byte Nest102 = 102; - private const byte Nest103 = 103; - private const byte Nest104 = 104; - private const byte Nest105 = 105; - private const byte Nest106 = 106; - private const byte Nest107 = 107; - private const byte Nest108 = 108; - private const byte Nest109 = 109; - private const byte Nest110 = 110; - private const byte Nest111 = 111; - private const byte Nest112 = 112; - private const byte Nest113 = 113; - private const byte Nest114 = 114; - private const byte Nest115 = 115; - private const byte Nest116 = 116; - private const byte Nest117 = 117; - private const byte Nest118 = 118; - private const byte Nest119 = 119; - private const byte Nest120 = 120; - private const byte Nest121 = 121; - private const byte Nest122 = 122; - private const byte Nest123 = 123; - private const byte Nest124 = 124; - private const byte Nest125 = 125; - private const byte Nest126 = 126; - private const byte Nest127 = 127; - private const byte Nest128 = 128; - private const byte Nest129 = 129; - private const byte Nest130 = 130; - private const byte Nest131 = 131; - private const byte Nest132 = 132; - private const byte Nest133 = 133; - private const byte Nest134 = 134; - private const byte Nest135 = 135; - private const byte Nest136 = 136; - private const byte Nest137 = 137; - private const byte Nest138 = 138; - private const byte Nest139 = 139; - private const byte Nest140 = 140; - private const byte Nest141 = 141; - private const byte Nest142 = 142; - private const byte Nest143 = 143; - private const byte Nest144 = 144; - private const byte Nest145 = 145; - private const byte Nest146 = 146; - private const byte Nest147 = 147; - private const byte Nest148 = 148; - private const byte Nest149 = 149; - private const byte Nest150 = 150; - private const byte Nest151 = 151; - private const byte Nest152 = 152; - private const byte Nest153 = 153; - private const byte Nest154 = 154; - private const byte Nest155 = 155; - private const byte Nest156 = 156; - private const byte Nest157 = 157; - private const byte Nest158 = 158; - private const byte Nest159 = 159; - private const byte Nest160 = 160; - private const byte Nest161 = 161; - private const byte Nest162 = 162; - private const byte Nest163 = 163; - private const byte Nest164 = 164; - private const byte Nest165 = 165; - private const byte Nest166 = 166; - private const byte Nest167 = 167; - private const byte Nest168 = 168; - private const byte Nest169 = 169; - private const byte Nest170 = 170; - private const byte Nest171 = 171; - private const byte Nest172 = 172; - private const byte Nest173 = 173; - private const byte Nest174 = 174; - private const byte Nest175 = 175; - private const byte Nest176 = 176; - private const byte Nest177 = 177; - private const byte Nest178 = 178; - private const byte Nest179 = 179; - private const byte Nest180 = 180; - private const byte Nest181 = 181; - private const byte Nest182 = 182; - private const byte Nest183 = 183; - private const byte Nest184 = 184; - private const byte Nest185 = 185; - private const byte Nest186 = 186; - private const byte Nest187 = 187; - private const byte Nest188 = 188; - private const byte Nest189 = 189; - private const byte Nest190 = 190; - private const byte Nest191 = 191; - private const byte Nest192 = 192; - private const byte Nest193 = 193; - private const byte Nest194 = 194; - private const byte Nest195 = 195; - private const byte Nest196 = 196; + new byte[] {144, 134, 122}, // 000 : Stony Wilderness, South Lake Miloch, Rolling Fields + new byte[] {144, 126}, // 001 : Stony Wilderness, Watchtower Ruins + new byte[] {144, 122}, // 002 : Stony Wilderness, Rolling Fields + new byte[] {142, 124, 122}, // 003 : Bridge Field, Dappled Grove, Rolling Fields + new byte[] {142, 134}, // 004 : Bridge Field, South Lake Miloch + new byte[] {144, 126}, // 005 : Stony Wilderness, Watchtower Ruins + new byte[] {128, 130}, // 006 : East Lake Axewell, West Lake Axewell + new byte[] {154, 142, 134}, // 007 : Lake of Outrage, Bridge Field, South Lake Miloch + new byte[] {146, 130}, // 008 : Dusty Bowl, West Lake Axewell + new byte[] {146, 138}, // 009 : Dusty Bowl, North Lake Miloch + new byte[] {146, 136}, // 010 : Dusty Bowl, Giants Seat + new byte[] {150, 144, 136}, // 011 : Hammerlocke Hills, Stony Wilderness, Giants Seat + new byte[] {142}, // 012 : Bridge Field + new byte[] {150, 144, 140}, // 013 : Hammerlocke Hills, Stony Wilderness, Motostoke Riverbank + new byte[] {146, 136}, // 014 : Dusty Bowl, Giants Seat + new byte[] {142, 122}, // 015 : Bridge Field, Rolling Fields + new byte[] {146}, // 016 : Dusty Bowl + new byte[] {154, 152, 144}, // 017 : Lake of Outrage, Giants Cap, Stony Wilderness + new byte[] {150, 144}, // 018 : Hammerlocke Hills, Stony Wilderness + new byte[] {146}, // 019 : Dusty Bowl + new byte[] {146}, // 020 : Dusty Bowl + new byte[] {144}, // 021 : Stony Wilderness + new byte[] {150, 152}, // 022 : Hammerlocke Hills, Giants Cap + new byte[] {152, 140}, // 023 : Giants Cap, Motostoke Riverbank + new byte[] {154, 148}, // 024 : Lake of Outrage, Giants Mirror + new byte[] {124}, // 025 : Dappled Grove + new byte[] {148, 144, 142}, // 026 : Giants Mirror, Stony Wilderness, Bridge Field + new byte[] {148, 124, 146}, // 027 : Giants Mirror, Dappled Grove AND Dusty Bowl (Giant's Mirror load-line overlap) + new byte[] {138, 128}, // 028 : North Lake Miloch, East Lake Axewell + new byte[] {150, 152, 140}, // 029 : Hammerlocke Hills, Giants Cap, Motostoke Riverbank + new byte[] {128, 122}, // 030 : East Lake Axewell, Rolling Fields + new byte[] {150, 152}, // 031 : Hammerlocke Hills, Giants Cap + new byte[] {150, 122}, // 032 : Hammerlocke Hills, Rolling Fields + new byte[] {154, 142}, // 033 : Lake of Outrage, Bridge Field + new byte[] {144, 130}, // 034 : Stony Wilderness, West Lake Axewell + new byte[] {142, 146, 148}, // 035 : Bridge Field, Dusty Bowl, Giants Mirror + new byte[] {122}, // 036 : Rolling Fields + new byte[] {132}, // 037 : Axew's Eye + new byte[] {128, 122}, // 038 : East Lake Axewell, Rolling Fields + new byte[] {144, 142, 140}, // 039 : Stony Wilderness, Bridge Field, Motostoke Riverbank + new byte[] {134, 138}, // 040 : South Lake Miloch, North Lake Miloch + new byte[] {148, 130}, // 041 : Giants Mirror, West Lake Axewell + new byte[] {148, 144, 134, 146}, // 042 : Giants Mirror, Stony Wilderness, South Lake Miloch AND Dusty Bowl (Giant's Mirror load-line overlap) + new byte[] {154, 142, 128, 130}, // 043 : Lake of Outrage, Bridge Field, East Lake Axewell, West Lake Axewell + new byte[] {150, 136}, // 044 : Hammerlocke Hills, Giants Seat + new byte[] {142, 134, 122}, // 045 : Bridge Field, South Lake Miloch, Rolling Fields + new byte[] {126}, // 046 : Watchtower Ruins + new byte[] {146, 138, 122, 134}, // 047 : Dusty Bowl, North Lake Miloch, Rolling Fields, South Lake Miloch + new byte[] {146, 136}, // 048 : Dusty Bowl, Giants Seat + new byte[] {144, 140, 126}, // 049 : Stony Wilderness, Motostoke Riverbank, Watchtower Ruins + new byte[] {144, 136, 122}, // 050 : Stony Wilderness, Giants Seat, Rolling Fields + new byte[] {146, 142, 122}, // 051 : Dusty Bowl, Bridge Field, Rolling Fields + new byte[] {150}, // 052 : Hammerlocke Hills + new byte[] {146, 144}, // 053 : Dusty Bowl, Stony Wilderness + new byte[] {152, 146, 144}, // 054 : Giants Cap, Dusty Bowl, Stony Wilderness + new byte[] {154, 140}, // 055 : Lake of Outrage, Motostoke Riverbank + new byte[] {150}, // 056 : Hammerlocke Hills + new byte[] {124}, // 057 : Dappled Grove + new byte[] {144, 142, 124}, // 058 : Stony Wilderness, Bridge Field, Dappled Grove + new byte[] {152, 140, 138}, // 059 : Giants Cap, Motostoke Riverbank, North Lake Miloch + new byte[] {150, 128}, // 060 : Hammerlocke Hills, East Lake Axewell + new byte[] {150, 122}, // 061 : Hammerlocke Hills, Rolling Fields + new byte[] {144, 142, 130}, // 062 : Stony Wilderness, Bridge Field, West Lake Axewell + new byte[] {132, 122}, // 063 : Axew's Eye, Rolling Fields + new byte[] {142, 140, 128, 122}, // 064 : Bridge Field, Motostoke Riverbank, East Lake Axewell, Rolling Fields + new byte[] {144}, // 065 : Stony Wilderness + new byte[] {148}, // 066 : Giants Mirror + new byte[] {150}, // 067 : Hammerlocke Hills + new byte[] {148}, // 068 : Giants Mirror + new byte[] {148}, // 069 : Giants Mirror + new byte[] {152}, // 070 : Giants Cap + new byte[] {148}, // 071 : Giants Mirror + new byte[] {150}, // 072 : Hammerlocke Hills + new byte[] {154}, // 073 : Lake of Outrage + new byte[] {146, 130}, // 074 : Dusty Bowl, West Lake Axewell + new byte[] {138, 134}, // 075 : North Lake Miloch, South Lake Miloch + new byte[] {154}, // 076 : Lake of Outrage + new byte[] {152}, // 077 : Giants Cap + new byte[] {124}, // 078 : Dappled Grove + new byte[] {144}, // 079 : Stony Wilderness + new byte[] {144}, // 080 : Stony Wilderness + new byte[] {142}, // 081 : Bridge Field + new byte[] {136}, // 082 : Giants Seat + new byte[] {136}, // 083 : Giants Seat + new byte[] {144}, // 084 : Stony Wilderness + new byte[] {128}, // 085 : East Lake Axewell + new byte[] {142}, // 086 : Bridge Field + new byte[] {146}, // 087 : Dusty Bowl + new byte[] {152}, // 088 : Giants Cap + new byte[] {122}, // 089 : Rolling Fields + new byte[] {130, 134}, // 090 : West Lake Axewell, South Lake Miloch + new byte[] {142, 124}, // 091 : Bridge Field, Dappled Grove + new byte[] {146}, // 092 : Dusty Bowl + Array.Empty(), // 093 : None + Array.Empty(), // 094 : None + Array.Empty(), // 095 : None + Array.Empty(), // 096 : None + Array.Empty(), // 097 : None + new byte[] {164, 166, 188, 190}, // 098 : Fields of Honor, Soothing Wetlands, Stepping-Stone Sea, Insular Sea + new byte[] {164, 166, 188, 190}, // 099 : Fields of Honor, Soothing Wetlands, Stepping-Stone Sea, Insular Sea + new byte[] {166, 176, 180}, // 100 : Soothing Wetlands, Courageous Cavern, Training Lowlands + new byte[] {166, 176, 180}, // 101 : Soothing Wetlands, Courageous Cavern, Training Lowlands + new byte[] {170, 176, 184, 188}, // 102 : Challenge Beach, Courageous Cavern, Potbottom Desert, Stepping-Stone Sea + new byte[] {170, 176, 188}, // 103 : Challenge Beach, Courageous Cavern, Stepping-Stone Sea + new byte[] {164, 168, 170, 192}, // 104 : Fields of Honor, Forest of Focus, Challenge Beach, Honeycalm Sea + new byte[] {164, 168, 170, 192}, // 105 : Fields of Honor, Forest of Focus, Challenge Beach, Honeycalm Sea + new byte[] {174, 178, 186, 188}, // 106 : Challenge Road, Loop Lagoon, Workout Sea, Stepping-Stone Sea + new byte[] {174, 178, 186, 188}, // 107 : Challenge Road, Loop Lagoon, Workout Sea, Stepping-Stone Sea + new byte[] {164, 168, 180, 186}, // 108 : Fields of Honor, Forest of Focus, Training Lowlands, Workout Sea + new byte[] {164, 168, 186}, // 109 : Fields of Honor, Forest of Focus, Workout Sea + new byte[] {164, 166, 180, 190}, // 110 : Fields of Honor, Soothing Wetlands, Training Lowlands, Insular Sea + new byte[] {164, 166, 180, 190}, // 111 : Fields of Honor, Soothing Wetlands, Training Lowlands, Insular Sea + new byte[] {164, 170, 178, 184}, // 112 : Fields of Honor, Challenge Beach, Loop Lagoon, Potbottom Desert + new byte[] {164, 170, 178, 184}, // 113 : Fields of Honor, Challenge Beach, Loop Lagoon, Potbottom Desert + new byte[] {164, 180, 184}, // 114 : Fields of Honor, Training Lowlands, Potbottom Desert + new byte[] {164, 180, 184}, // 115 : Fields of Honor, Training Lowlands, Potbottom Desert + new byte[] {166, 170, 180, 188}, // 116 : Soothing Wetlands, Challenge Beach, Training Lowlands, Stepping-Stone Sea + new byte[] {166, 170, 180, 188}, // 117 : Soothing Wetlands, Challenge Beach, Training Lowlands, Stepping-Stone Sea + new byte[] {166, 168, 180, 188}, // 118 : Soothing Wetlands, Forest of Focus, Training Lowlands, Stepping-Stone Sea + new byte[] {166, 168, 180, 188}, // 119 : Soothing Wetlands, Forest of Focus, Training Lowlands, Stepping-Stone Sea + new byte[] {166, 174, 186, 192}, // 120 : Soothing Wetlands, Challenge Road, Workout Sea, Honeycalm Sea + new byte[] {166, 174, 186, 192}, // 121 : Soothing Wetlands, Challenge Road, Workout Sea, Honeycalm Sea + new byte[] {164, 170, 174, 192}, // 122 : Fields of Honor, Challenge Beach, Challenge Road, Honeycalm Sea + new byte[] {164, 170, 174, 192}, // 123 : Fields of Honor, Challenge Beach, Challenge Road, Honeycalm Sea + new byte[] {164, 166, 168, 190}, // 124 : Fields of Honor, Soothing Wetlands, Forest of Focus, Insular Sea + new byte[] {164, 166, 168, 190}, // 125 : Fields of Honor, Soothing Wetlands, Forest of Focus, Insular Sea + new byte[] {170, 176, 188}, // 126 : Challenge Beach, Courageous Cavern, Stepping-Stone Sea + new byte[] {170, 176, 188}, // 127 : Challenge Beach, Courageous Cavern, Stepping-Stone Sea + new byte[] {172, 176, 188}, // 128 : Brawlers' Cave, Courageous Cavern, Stepping-Stone Sea + new byte[] {172, 176, 188}, // 129 : Brawlers' Cave, Courageous Cavern, Stepping-Stone Sea + new byte[] {178, 186, 192}, // 130 : Loop Lagoon, Workout Sea, Honeycalm Sea + new byte[] {186, 192}, // 131 : Workout Sea, Honeycalm Sea + new byte[] {164, 166, 176}, // 132 : Fields of Honor, Soothing Wetlands, Courageous Cavern + new byte[] {164, 166, 176}, // 133 : Fields of Honor, Soothing Wetlands, Courageous Cavern + new byte[] {166, 168, 170, 176}, // 134 : Soothing Wetlands, Forest of Focus, Challenge Beach, Courageous Cavern + new byte[] {166, 168, 170}, // 135 : Soothing Wetlands, Forest of Focus, Challenge Beach + new byte[] {164, 170, 178, 190}, // 136 : Fields of Honor, Challenge Beach, Loop Lagoon, Insular Sea + new byte[] {164, 170, 178}, // 137 : Fields of Honor, Challenge Beach, Loop Lagoon + new byte[] {186, 188, 190, 192}, // 138 : Workout Sea, Stepping-Stone Sea, Insular Sea, Honeycalm Sea + new byte[] {186, 188, 190, 192}, // 139 : Workout Sea, Stepping-Stone Sea, Insular Sea, Honeycalm Sea + Array.Empty(), // 140 : None + Array.Empty(), // 141 : None + new byte[] {194}, // 142 : Honeycalm Island + new byte[] {194}, // 143 : Honeycalm Island + new byte[] {168, 180}, // 144 : Forest of Focus, Training Lowlands + new byte[] {186, 188}, // 145 : Workout Sea, Stepping-Stone Sea + new byte[] {190}, // 146 : Insular Sea + new byte[] {176}, // 147 : Courageous Cavern + new byte[] {180}, // 148 : Training Lowlands + new byte[] {184}, // 149 : Potbottom Desert + new byte[] {178}, // 150 : Loop Lagoon + new byte[] {186}, // 151 : Workout Sea + new byte[] {186}, // 152 : Workout Sea + new byte[] {168, 180}, // 153 : Forest of Focus, Training Lowlands + new byte[] {186, 188}, // 154 : Workout Sea, Stepping-Stone Sea + new byte[] {174}, // 155 : Challenge Road + new byte[] {174}, // 156 : Challenge Road - internal static readonly IReadOnlyList> NestLocations = new [] - { - new byte[] {144, 134, 122}, // 000 : Stony Wilderness, South Lake Miloch, Rolling Fields - new byte[] {144, 126}, // 001 : Stony Wilderness, Watchtower Ruins - new byte[] {144, 122}, // 002 : Stony Wilderness, Rolling Fields - new byte[] {142, 124, 122}, // 003 : Bridge Field, Dappled Grove, Rolling Fields - new byte[] {142, 134}, // 004 : Bridge Field, South Lake Miloch - new byte[] {144, 126}, // 005 : Stony Wilderness, Watchtower Ruins - new byte[] {128, 130}, // 006 : East Lake Axewell, West Lake Axewell - new byte[] {154, 142, 134}, // 007 : Lake of Outrage, Bridge Field, South Lake Miloch - new byte[] {146, 130}, // 008 : Dusty Bowl, West Lake Axewell - new byte[] {146, 138}, // 009 : Dusty Bowl, North Lake Miloch - new byte[] {146, 136}, // 010 : Dusty Bowl, Giants Seat - new byte[] {150, 144, 136}, // 011 : Hammerlocke Hills, Stony Wilderness, Giants Seat - new byte[] {142}, // 012 : Bridge Field - new byte[] {150, 144, 140}, // 013 : Hammerlocke Hills, Stony Wilderness, Motostoke Riverbank - new byte[] {146, 136}, // 014 : Dusty Bowl, Giants Seat - new byte[] {142, 122}, // 015 : Bridge Field, Rolling Fields - new byte[] {146}, // 016 : Dusty Bowl - new byte[] {154, 152, 144}, // 017 : Lake of Outrage, Giants Cap, Stony Wilderness - new byte[] {150, 144}, // 018 : Hammerlocke Hills, Stony Wilderness - new byte[] {146}, // 019 : Dusty Bowl - new byte[] {146}, // 020 : Dusty Bowl - new byte[] {144}, // 021 : Stony Wilderness - new byte[] {150, 152}, // 022 : Hammerlocke Hills, Giants Cap - new byte[] {152, 140}, // 023 : Giants Cap, Motostoke Riverbank - new byte[] {154, 148}, // 024 : Lake of Outrage, Giants Mirror - new byte[] {124}, // 025 : Dappled Grove - new byte[] {148, 144, 142}, // 026 : Giants Mirror, Stony Wilderness, Bridge Field - new byte[] {148, 124, 146}, // 027 : Giants Mirror, Dappled Grove AND Dusty Bowl (Giant's Mirror load-line overlap) - new byte[] {138, 128}, // 028 : North Lake Miloch, East Lake Axewell - new byte[] {150, 152, 140}, // 029 : Hammerlocke Hills, Giants Cap, Motostoke Riverbank - new byte[] {128, 122}, // 030 : East Lake Axewell, Rolling Fields - new byte[] {150, 152}, // 031 : Hammerlocke Hills, Giants Cap - new byte[] {150, 122}, // 032 : Hammerlocke Hills, Rolling Fields - new byte[] {154, 142}, // 033 : Lake of Outrage, Bridge Field - new byte[] {144, 130}, // 034 : Stony Wilderness, West Lake Axewell - new byte[] {142, 146, 148}, // 035 : Bridge Field, Dusty Bowl, Giants Mirror - new byte[] {122}, // 036 : Rolling Fields - new byte[] {132}, // 037 : Axew's Eye - new byte[] {128, 122}, // 038 : East Lake Axewell, Rolling Fields - new byte[] {144, 142, 140}, // 039 : Stony Wilderness, Bridge Field, Motostoke Riverbank - new byte[] {134, 138}, // 040 : South Lake Miloch, North Lake Miloch - new byte[] {148, 130}, // 041 : Giants Mirror, West Lake Axewell - new byte[] {148, 144, 134, 146}, // 042 : Giants Mirror, Stony Wilderness, South Lake Miloch AND Dusty Bowl (Giant's Mirror load-line overlap) - new byte[] {154, 142, 128, 130}, // 043 : Lake of Outrage, Bridge Field, East Lake Axewell, West Lake Axewell - new byte[] {150, 136}, // 044 : Hammerlocke Hills, Giants Seat - new byte[] {142, 134, 122}, // 045 : Bridge Field, South Lake Miloch, Rolling Fields - new byte[] {126}, // 046 : Watchtower Ruins - new byte[] {146, 138, 122, 134}, // 047 : Dusty Bowl, North Lake Miloch, Rolling Fields, South Lake Miloch - new byte[] {146, 136}, // 048 : Dusty Bowl, Giants Seat - new byte[] {144, 140, 126}, // 049 : Stony Wilderness, Motostoke Riverbank, Watchtower Ruins - new byte[] {144, 136, 122}, // 050 : Stony Wilderness, Giants Seat, Rolling Fields - new byte[] {146, 142, 122}, // 051 : Dusty Bowl, Bridge Field, Rolling Fields - new byte[] {150}, // 052 : Hammerlocke Hills - new byte[] {146, 144}, // 053 : Dusty Bowl, Stony Wilderness - new byte[] {152, 146, 144}, // 054 : Giants Cap, Dusty Bowl, Stony Wilderness - new byte[] {154, 140}, // 055 : Lake of Outrage, Motostoke Riverbank - new byte[] {150}, // 056 : Hammerlocke Hills - new byte[] {124}, // 057 : Dappled Grove - new byte[] {144, 142, 124}, // 058 : Stony Wilderness, Bridge Field, Dappled Grove - new byte[] {152, 140, 138}, // 059 : Giants Cap, Motostoke Riverbank, North Lake Miloch - new byte[] {150, 128}, // 060 : Hammerlocke Hills, East Lake Axewell - new byte[] {150, 122}, // 061 : Hammerlocke Hills, Rolling Fields - new byte[] {144, 142, 130}, // 062 : Stony Wilderness, Bridge Field, West Lake Axewell - new byte[] {132, 122}, // 063 : Axew's Eye, Rolling Fields - new byte[] {142, 140, 128, 122}, // 064 : Bridge Field, Motostoke Riverbank, East Lake Axewell, Rolling Fields - new byte[] {144}, // 065 : Stony Wilderness - new byte[] {148}, // 066 : Giants Mirror - new byte[] {150}, // 067 : Hammerlocke Hills - new byte[] {148}, // 068 : Giants Mirror - new byte[] {148}, // 069 : Giants Mirror - new byte[] {152}, // 070 : Giants Cap - new byte[] {148}, // 071 : Giants Mirror - new byte[] {150}, // 072 : Hammerlocke Hills - new byte[] {154}, // 073 : Lake of Outrage - new byte[] {146, 130}, // 074 : Dusty Bowl, West Lake Axewell - new byte[] {138, 134}, // 075 : North Lake Miloch, South Lake Miloch - new byte[] {154}, // 076 : Lake of Outrage - new byte[] {152}, // 077 : Giants Cap - new byte[] {124}, // 078 : Dappled Grove - new byte[] {144}, // 079 : Stony Wilderness - new byte[] {144}, // 080 : Stony Wilderness - new byte[] {142}, // 081 : Bridge Field - new byte[] {136}, // 082 : Giants Seat - new byte[] {136}, // 083 : Giants Seat - new byte[] {144}, // 084 : Stony Wilderness - new byte[] {128}, // 085 : East Lake Axewell - new byte[] {142}, // 086 : Bridge Field - new byte[] {146}, // 087 : Dusty Bowl - new byte[] {152}, // 088 : Giants Cap - new byte[] {122}, // 089 : Rolling Fields - new byte[] {130, 134}, // 090 : West Lake Axewell, South Lake Miloch - new byte[] {142, 124}, // 091 : Bridge Field, Dappled Grove - new byte[] {146}, // 092 : Dusty Bowl - Array.Empty(), // 093 : None - Array.Empty(), // 094 : None - Array.Empty(), // 095 : None - Array.Empty(), // 096 : None - Array.Empty(), // 097 : None - new byte[] {164, 166, 188, 190}, // 098 : Fields of Honor, Soothing Wetlands, Stepping-Stone Sea, Insular Sea - new byte[] {164, 166, 188, 190}, // 099 : Fields of Honor, Soothing Wetlands, Stepping-Stone Sea, Insular Sea - new byte[] {166, 176, 180}, // 100 : Soothing Wetlands, Courageous Cavern, Training Lowlands - new byte[] {166, 176, 180}, // 101 : Soothing Wetlands, Courageous Cavern, Training Lowlands - new byte[] {170, 176, 184, 188}, // 102 : Challenge Beach, Courageous Cavern, Potbottom Desert, Stepping-Stone Sea - new byte[] {170, 176, 188}, // 103 : Challenge Beach, Courageous Cavern, Stepping-Stone Sea - new byte[] {164, 168, 170, 192}, // 104 : Fields of Honor, Forest of Focus, Challenge Beach, Honeycalm Sea - new byte[] {164, 168, 170, 192}, // 105 : Fields of Honor, Forest of Focus, Challenge Beach, Honeycalm Sea - new byte[] {174, 178, 186, 188}, // 106 : Challenge Road, Loop Lagoon, Workout Sea, Stepping-Stone Sea - new byte[] {174, 178, 186, 188}, // 107 : Challenge Road, Loop Lagoon, Workout Sea, Stepping-Stone Sea - new byte[] {164, 168, 180, 186}, // 108 : Fields of Honor, Forest of Focus, Training Lowlands, Workout Sea - new byte[] {164, 168, 186}, // 109 : Fields of Honor, Forest of Focus, Workout Sea - new byte[] {164, 166, 180, 190}, // 110 : Fields of Honor, Soothing Wetlands, Training Lowlands, Insular Sea - new byte[] {164, 166, 180, 190}, // 111 : Fields of Honor, Soothing Wetlands, Training Lowlands, Insular Sea - new byte[] {164, 170, 178, 184}, // 112 : Fields of Honor, Challenge Beach, Loop Lagoon, Potbottom Desert - new byte[] {164, 170, 178, 184}, // 113 : Fields of Honor, Challenge Beach, Loop Lagoon, Potbottom Desert - new byte[] {164, 180, 184}, // 114 : Fields of Honor, Training Lowlands, Potbottom Desert - new byte[] {164, 180, 184}, // 115 : Fields of Honor, Training Lowlands, Potbottom Desert - new byte[] {166, 170, 180, 188}, // 116 : Soothing Wetlands, Challenge Beach, Training Lowlands, Stepping-Stone Sea - new byte[] {166, 170, 180, 188}, // 117 : Soothing Wetlands, Challenge Beach, Training Lowlands, Stepping-Stone Sea - new byte[] {166, 168, 180, 188}, // 118 : Soothing Wetlands, Forest of Focus, Training Lowlands, Stepping-Stone Sea - new byte[] {166, 168, 180, 188}, // 119 : Soothing Wetlands, Forest of Focus, Training Lowlands, Stepping-Stone Sea - new byte[] {166, 174, 186, 192}, // 120 : Soothing Wetlands, Challenge Road, Workout Sea, Honeycalm Sea - new byte[] {166, 174, 186, 192}, // 121 : Soothing Wetlands, Challenge Road, Workout Sea, Honeycalm Sea - new byte[] {164, 170, 174, 192}, // 122 : Fields of Honor, Challenge Beach, Challenge Road, Honeycalm Sea - new byte[] {164, 170, 174, 192}, // 123 : Fields of Honor, Challenge Beach, Challenge Road, Honeycalm Sea - new byte[] {164, 166, 168, 190}, // 124 : Fields of Honor, Soothing Wetlands, Forest of Focus, Insular Sea - new byte[] {164, 166, 168, 190}, // 125 : Fields of Honor, Soothing Wetlands, Forest of Focus, Insular Sea - new byte[] {170, 176, 188}, // 126 : Challenge Beach, Courageous Cavern, Stepping-Stone Sea - new byte[] {170, 176, 188}, // 127 : Challenge Beach, Courageous Cavern, Stepping-Stone Sea - new byte[] {172, 176, 188}, // 128 : Brawlers' Cave, Courageous Cavern, Stepping-Stone Sea - new byte[] {172, 176, 188}, // 129 : Brawlers' Cave, Courageous Cavern, Stepping-Stone Sea - new byte[] {178, 186, 192}, // 130 : Loop Lagoon, Workout Sea, Honeycalm Sea - new byte[] {186, 192}, // 131 : Workout Sea, Honeycalm Sea - new byte[] {164, 166, 176}, // 132 : Fields of Honor, Soothing Wetlands, Courageous Cavern - new byte[] {164, 166, 176}, // 133 : Fields of Honor, Soothing Wetlands, Courageous Cavern - new byte[] {166, 168, 170, 176}, // 134 : Soothing Wetlands, Forest of Focus, Challenge Beach, Courageous Cavern - new byte[] {166, 168, 170}, // 135 : Soothing Wetlands, Forest of Focus, Challenge Beach - new byte[] {164, 170, 178, 190}, // 136 : Fields of Honor, Challenge Beach, Loop Lagoon, Insular Sea - new byte[] {164, 170, 178}, // 137 : Fields of Honor, Challenge Beach, Loop Lagoon - new byte[] {186, 188, 190, 192}, // 138 : Workout Sea, Stepping-Stone Sea, Insular Sea, Honeycalm Sea - new byte[] {186, 188, 190, 192}, // 139 : Workout Sea, Stepping-Stone Sea, Insular Sea, Honeycalm Sea - Array.Empty(), // 140 : None - Array.Empty(), // 141 : None - new byte[] {194}, // 142 : Honeycalm Island - new byte[] {194}, // 143 : Honeycalm Island - new byte[] {168, 180}, // 144 : Forest of Focus, Training Lowlands - new byte[] {186, 188}, // 145 : Workout Sea, Stepping-Stone Sea - new byte[] {190}, // 146 : Insular Sea - new byte[] {176}, // 147 : Courageous Cavern - new byte[] {180}, // 148 : Training Lowlands - new byte[] {184}, // 149 : Potbottom Desert - new byte[] {178}, // 150 : Loop Lagoon - new byte[] {186}, // 151 : Workout Sea - new byte[] {186}, // 152 : Workout Sea - new byte[] {168, 180}, // 153 : Forest of Focus, Training Lowlands - new byte[] {186, 188}, // 154 : Workout Sea, Stepping-Stone Sea - new byte[] {174}, // 155 : Challenge Road - new byte[] {174}, // 156 : Challenge Road + new byte[] {204,210,222,230}, // 157 : Slippery Slope, Giant's Bed, Giant's Foot, Ballimere Lake + new byte[] {204,210,222,230}, // 158 : Slippery Slope, Giant's Bed, Giant's Foot, Ballimere Lake + new byte[] {210,214,222,230}, // 159 : Giant's Bed, Snowslide Slope, Giant's Foot, Ballimere Lake + new byte[] {210,214,222,230}, // 160 : Giant's Bed, Snowslide Slope, Giant's Foot, Ballimere Lake + new byte[] {210,222,226,230}, // 161 : Giant's Bed, Giant's Foot, Frigid Sea, Ballimere Lake + new byte[] {210,222,226,230}, // 162 : Giant's Bed, Giant's Foot, Frigid Sea, Ballimere Lake + new byte[] {208,210,226,228,230},// 163 : Frostpoint Field, Giant's Bed, Frigid Sea, Three-Point Pass, Ballimere Lake + new byte[] {208,210,226,228,230},// 164 : Frostpoint Field, Giant's Bed, Frigid Sea, Three-Point Pass, Ballimere Lake + new byte[] {204,210,220,222,230},// 165 : Slippery Slope, Giant's Bed, Crown Shrine, Giant's Foot, Ballimere Lake + new byte[] {204,210,220,222,230},// 166 : Slippery Slope, Giant's Bed, Crown Shrine, Giant's Foot, Ballimere Lake + new byte[] {204,214,226}, // 167 : Slippery Slope, Snowslide Slope, Frigid Sea + new byte[] {204,214,226}, // 168 : Slippery Slope, Snowslide Slope, Frigid Sea + new byte[] {210,226}, // 169 : Giant's Bed, Frigid Sea + new byte[] {210,226}, // 170 : Giant's Bed, Frigid Sea + new byte[] {208,210,214,226,230},// 171 : Frostpoint Field, Giant's Bed, Snowslide Slope, Frigid Sea, Ballimere Lake + new byte[] {208,210,214,226,230},// 172 : Frostpoint Field, Giant's Bed, Snowslide Slope, Frigid Sea, Ballimere Lake + new byte[] {210,226,230}, // 173 : Giant's Bed, Frigid Sea, Ballimere Lake + new byte[] {210,226,230}, // 174 : Giant's Bed, Frigid Sea, Ballimere Lake + new byte[] {208,210,226,230,234},// 175 : Frostpoint Field, Giant's Bed, Frigid Sea, Ballimere Lake, Dyna Tree Hill + new byte[] {208,210,226,230,234},// 176 : Frostpoint Field, Giant's Bed, Frigid Sea, Ballimere Lake, Dyna Tree Hill + new byte[] {210,214,218,230}, // 177 : Giant's Bed, Snowslide Slope, Path to the Peak, Ballimere Lake + new byte[] {210,214,218,230}, // 178 : Giant's Bed, Snowslide Slope, Path to the Peak, Ballimere Lake + new byte[] {204,210,214,230}, // 179 : Slippery Slope, Giant's Bed, Snowslide Slope, Ballimere Lake + new byte[] {204,210,214,230}, // 180 : Slippery Slope, Giant's Bed, Snowslide Slope, Ballimere Lake + new byte[] {204,212,222,226,230},// 181 : Slippery Slope, Old Cemetery, Giant's Foot, Frigid Sea, Ballimere Lake + new byte[] {204,212,222,226,230},// 182 : Slippery Slope, Old Cemetery, Giant's Foot, Frigid Sea, Ballimere Lake + new byte[] {210,218,226,228,230},// 183 : Giant's Bed, Path to the Peak, Frigid Sea, Three-Point Pass, Ballimere Lake + new byte[] {210,218,226,228,230},// 184 : Giant's Bed, Path to the Peak, Frigid Sea, Three-Point Pass, Ballimere Lake + new byte[] {208,210,214,222,226},// 185 : Frostpoint Field, Giant's Bed, Snowslide Slope, Giant's Foot, Frigid Sea + new byte[] {208,210,214,222,226},// 186 : Frostpoint Field, Giant's Bed, Snowslide Slope, Giant's Foot, Frigid Sea + new byte[] {210,214,218,226}, // 187 : Giant's Bed, Snowslide Slope, Path to the Peak, Frigid Sea + new byte[] {210,214,218,226}, // 188 : Giant's Bed, Snowslide Slope, Path to the Peak, Frigid Sea + new byte[] {208,210,214,226,230},// 189 : Frostpoint Field, Giant's Bed, Snowslide Slope, Frigid Sea, Ballimere Lake + new byte[] {208,210,214,226,230},// 190 : Frostpoint Field, Giant's Bed, Snowslide Slope, Frigid Sea, Ballimere Lake + new byte[] {210,212,230}, // 191 : Giant's Bed, Old Cemetery, Ballimere Lake + new byte[] {210,212,230}, // 192 : Giant's Bed, Old Cemetery, Ballimere Lake + new byte[] {230}, // 193 : Ballimere Lake + new byte[] {230}, // 194 : Ballimere Lake + new byte[] {214}, // 195 : Snowslide Slope + new byte[] {214}, // 196 : Snowslide Slope + }; - new byte[] {204,210,222,230}, // 157 : Slippery Slope, Giant's Bed, Giant's Foot, Ballimere Lake - new byte[] {204,210,222,230}, // 158 : Slippery Slope, Giant's Bed, Giant's Foot, Ballimere Lake - new byte[] {210,214,222,230}, // 159 : Giant's Bed, Snowslide Slope, Giant's Foot, Ballimere Lake - new byte[] {210,214,222,230}, // 160 : Giant's Bed, Snowslide Slope, Giant's Foot, Ballimere Lake - new byte[] {210,222,226,230}, // 161 : Giant's Bed, Giant's Foot, Frigid Sea, Ballimere Lake - new byte[] {210,222,226,230}, // 162 : Giant's Bed, Giant's Foot, Frigid Sea, Ballimere Lake - new byte[] {208,210,226,228,230},// 163 : Frostpoint Field, Giant's Bed, Frigid Sea, Three-Point Pass, Ballimere Lake - new byte[] {208,210,226,228,230},// 164 : Frostpoint Field, Giant's Bed, Frigid Sea, Three-Point Pass, Ballimere Lake - new byte[] {204,210,220,222,230},// 165 : Slippery Slope, Giant's Bed, Crown Shrine, Giant's Foot, Ballimere Lake - new byte[] {204,210,220,222,230},// 166 : Slippery Slope, Giant's Bed, Crown Shrine, Giant's Foot, Ballimere Lake - new byte[] {204,214,226}, // 167 : Slippery Slope, Snowslide Slope, Frigid Sea - new byte[] {204,214,226}, // 168 : Slippery Slope, Snowslide Slope, Frigid Sea - new byte[] {210,226}, // 169 : Giant's Bed, Frigid Sea - new byte[] {210,226}, // 170 : Giant's Bed, Frigid Sea - new byte[] {208,210,214,226,230},// 171 : Frostpoint Field, Giant's Bed, Snowslide Slope, Frigid Sea, Ballimere Lake - new byte[] {208,210,214,226,230},// 172 : Frostpoint Field, Giant's Bed, Snowslide Slope, Frigid Sea, Ballimere Lake - new byte[] {210,226,230}, // 173 : Giant's Bed, Frigid Sea, Ballimere Lake - new byte[] {210,226,230}, // 174 : Giant's Bed, Frigid Sea, Ballimere Lake - new byte[] {208,210,226,230,234},// 175 : Frostpoint Field, Giant's Bed, Frigid Sea, Ballimere Lake, Dyna Tree Hill - new byte[] {208,210,226,230,234},// 176 : Frostpoint Field, Giant's Bed, Frigid Sea, Ballimere Lake, Dyna Tree Hill - new byte[] {210,214,218,230}, // 177 : Giant's Bed, Snowslide Slope, Path to the Peak, Ballimere Lake - new byte[] {210,214,218,230}, // 178 : Giant's Bed, Snowslide Slope, Path to the Peak, Ballimere Lake - new byte[] {204,210,214,230}, // 179 : Slippery Slope, Giant's Bed, Snowslide Slope, Ballimere Lake - new byte[] {204,210,214,230}, // 180 : Slippery Slope, Giant's Bed, Snowslide Slope, Ballimere Lake - new byte[] {204,212,222,226,230},// 181 : Slippery Slope, Old Cemetery, Giant's Foot, Frigid Sea, Ballimere Lake - new byte[] {204,212,222,226,230},// 182 : Slippery Slope, Old Cemetery, Giant's Foot, Frigid Sea, Ballimere Lake - new byte[] {210,218,226,228,230},// 183 : Giant's Bed, Path to the Peak, Frigid Sea, Three-Point Pass, Ballimere Lake - new byte[] {210,218,226,228,230},// 184 : Giant's Bed, Path to the Peak, Frigid Sea, Three-Point Pass, Ballimere Lake - new byte[] {208,210,214,222,226},// 185 : Frostpoint Field, Giant's Bed, Snowslide Slope, Giant's Foot, Frigid Sea - new byte[] {208,210,214,222,226},// 186 : Frostpoint Field, Giant's Bed, Snowslide Slope, Giant's Foot, Frigid Sea - new byte[] {210,214,218,226}, // 187 : Giant's Bed, Snowslide Slope, Path to the Peak, Frigid Sea - new byte[] {210,214,218,226}, // 188 : Giant's Bed, Snowslide Slope, Path to the Peak, Frigid Sea - new byte[] {208,210,214,226,230},// 189 : Frostpoint Field, Giant's Bed, Snowslide Slope, Frigid Sea, Ballimere Lake - new byte[] {208,210,214,226,230},// 190 : Frostpoint Field, Giant's Bed, Snowslide Slope, Frigid Sea, Ballimere Lake - new byte[] {210,212,230}, // 191 : Giant's Bed, Old Cemetery, Ballimere Lake - new byte[] {210,212,230}, // 192 : Giant's Bed, Old Cemetery, Ballimere Lake - new byte[] {230}, // 193 : Ballimere Lake - new byte[] {230}, // 194 : Ballimere Lake - new byte[] {214}, // 195 : Snowslide Slope - new byte[] {214}, // 196 : Snowslide Slope - }; + /// + /// Location IDs containing Dens that cannot be accessed without Rotom Bike's Water Mode. + /// + internal static readonly HashSet InaccessibleRank12DistributionLocations = new() {154,178,186,188,190,192,194,226,228,230,234}; // Areas that are entirely restricted to water - /// - /// Location IDs containing Dens that cannot be accessed without Rotom Bike's Water Mode. - /// - internal static readonly HashSet InaccessibleRank12DistributionLocations = new() {154,178,186,188,190,192,194,226,228,230,234}; // Areas that are entirely restricted to water + /// + /// Location IDs containing Dens that cannot be accessed without Rotom Bike's Water Mode. + /// + internal static readonly Dictionary InaccessibleRank12Nests = new() + { + {128, new byte[] {6,43}}, // East Lake Axewell + {130, new byte[] {6,41,43}}, // West Lake Axewell + {132, new byte[] {37,63}}, // Axew's Eye + {134, new byte[] {7,40,75,90}}, // South Lake Miloch + {138, new byte[] {40,75}}, // North Lake Miloch + {142, new byte[] {7,43}}, // Bridge Field + {146, new byte[] {8,74}}, // Dusty Bowl + {148, new byte[] {41,66}}, // Giant's Mirror + {154, new byte[] {7,17,24,33,43,55,73,76}}, // Lake of Outrage + {164, new byte[] {136,137}}, // Fields of Honor + {168, new byte[] {124,125,134,135,144,153}}, // Forest of Focus + {170, new byte[] {126,127}}, // Challenge Beach + {176, new byte[] {132,133}}, // Courageous Cavern + {178, new byte[] {106,107,112,113,130,136,137,150}}, // Loop Lagoon + {180, new byte[] {116,117}}, // Training Lowlands + {186, new byte[] {106,107,108,109,120,121,130,131,138,139,145,151,152,154}}, // Workout Sea + {188, new byte[] {98,99,102,103,106,107,116,117,118,119,126,127,128,129,138,139,145,154}}, // Stepping-Stone Sea + {190, new byte[] {98,99,110,111,124,125,136,138,139,146}}, // Insular Sea + {192, new byte[] {104,105,120,121,122,123,130,131,138,139}}, // Honeycalm Sea + {194, new byte[] {142,143}}, // Honeycalm Island + {210, new byte[] {169,170,183,184}}, // Giant's Bed + {222, new byte[] {181,182,185,186}}, // Giant's Foot + {226, new byte[] {161,162,163,164,167,168,169,170,171,172,173,174,175,176,181,182,183,184,185,186,187,188,189,190}}, // Frigid Sea + {228, new byte[] {163,164,183,184}}, // Three-Point Pass + {230, new byte[] {157,158,159,160,161,162,163,164,165,166,171,172,173,174,175,176,177,178,179,180,181,182,183,184,189,190,191,192,193,194}}, // Ballimere Lake + {234, new byte[] {175,176}}, // Dyna Tree Hill - /// - /// Location IDs containing Dens that cannot be accessed without Rotom Bike's Water Mode. - /// - internal static readonly Dictionary InaccessibleRank12Nests = new() - { - {128, new byte[] {6,43}}, // East Lake Axewell - {130, new byte[] {6,41,43}}, // West Lake Axewell - {132, new byte[] {37,63}}, // Axew's Eye - {134, new byte[] {7,40,75,90}}, // South Lake Miloch - {138, new byte[] {40,75}}, // North Lake Miloch - {142, new byte[] {7,43}}, // Bridge Field - {146, new byte[] {8,74}}, // Dusty Bowl - {148, new byte[] {41,66}}, // Giant's Mirror - {154, new byte[] {7,17,24,33,43,55,73,76}}, // Lake of Outrage - {164, new byte[] {136,137}}, // Fields of Honor - {168, new byte[] {124,125,134,135,144,153}}, // Forest of Focus - {170, new byte[] {126,127}}, // Challenge Beach - {176, new byte[] {132,133}}, // Courageous Cavern - {178, new byte[] {106,107,112,113,130,136,137,150}}, // Loop Lagoon - {180, new byte[] {116,117}}, // Training Lowlands - {186, new byte[] {106,107,108,109,120,121,130,131,138,139,145,151,152,154}}, // Workout Sea - {188, new byte[] {98,99,102,103,106,107,116,117,118,119,126,127,128,129,138,139,145,154}}, // Stepping-Stone Sea - {190, new byte[] {98,99,110,111,124,125,136,138,139,146}}, // Insular Sea - {192, new byte[] {104,105,120,121,122,123,130,131,138,139}}, // Honeycalm Sea - {194, new byte[] {142,143}}, // Honeycalm Island - {210, new byte[] {169,170,183,184}}, // Giant's Bed - {222, new byte[] {181,182,185,186}}, // Giant's Foot - {226, new byte[] {161,162,163,164,167,168,169,170,171,172,173,174,175,176,181,182,183,184,185,186,187,188,189,190}}, // Frigid Sea - {228, new byte[] {163,164,183,184}}, // Three-Point Pass - {230, new byte[] {157,158,159,160,161,162,163,164,165,166,171,172,173,174,175,176,177,178,179,180,181,182,183,184,189,190,191,192,193,194}}, // Ballimere Lake - {234, new byte[] {175,176}}, // Dyna Tree Hill + {162, new byte[] {6,7,37,40,41,43,66,73,75,76,130,131,138,139,142,143,145,146,150,151,152,154,169,170,193,194}}, // Completely inaccessible + }; - {162, new byte[] {6,7,37,40,41,43,66,73,75,76,130,131,138,139,142,143,145,146,150,151,152,154,169,170,193,194}}, // Completely inaccessible - }; + // Abilities Allowed + private const AbilityPermission A0 = AbilityPermission.OnlyFirst; + private const AbilityPermission A1 = AbilityPermission.OnlySecond; + private const AbilityPermission A2 = AbilityPermission.OnlyHidden; + private const AbilityPermission A3 = AbilityPermission.Any12; + private const AbilityPermission A4 = AbilityPermission.Any12H; - // Abilities Allowed - private const AbilityPermission A0 = AbilityPermission.OnlyFirst; - private const AbilityPermission A1 = AbilityPermission.OnlySecond; - private const AbilityPermission A2 = AbilityPermission.OnlyHidden; - private const AbilityPermission A3 = AbilityPermission.Any12; - private const AbilityPermission A4 = AbilityPermission.Any12H; + internal const int SharedNest = 162; + internal const int Watchtower = 126; + internal const int MaxLair = 244; - internal const int SharedNest = 162; - internal const int Watchtower = 126; - internal const int MaxLair = 244; + internal static readonly EncounterStatic8N[] Nest_Common = + { + new(Nest000,0,0,1,SWSH) { Species = 236, Ability = A3 }, // Tyrogue + new(Nest000,0,0,1,SWSH) { Species = 066, Ability = A3 }, // Machop + new(Nest000,0,1,1,SWSH) { Species = 532, Ability = A3 }, // Timburr + new(Nest000,1,2,2,SWSH) { Species = 067, Ability = A3 }, // Machoke + new(Nest000,1,2,2,SWSH) { Species = 533, Ability = A3 }, // Gurdurr + new(Nest000,4,4,4,SWSH) { Species = 068, Ability = A4 }, // Machamp + new(Nest001,0,0,1,SWSH) { Species = 280, Ability = A3 }, // Ralts + new(Nest001,0,0,1,SWSH) { Species = 517, Ability = A3 }, // Munna + new(Nest001,0,1,1,SWSH) { Species = 677, Ability = A3 }, // Espurr + new(Nest001,0,1,1,SWSH) { Species = 605, Ability = A3 }, // Elgyem + new(Nest001,1,2,2,SWSH) { Species = 281, Ability = A3 }, // Kirlia + new(Nest001,2,4,4,SWSH) { Species = 518, Ability = A3 }, // Musharna + new(Nest001,4,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir + new(Nest002,0,0,1,SWSH) { Species = 438, Ability = A3 }, // Bonsly + new(Nest002,0,1,1,SWSH) { Species = 557, Ability = A3 }, // Dwebble + new(Nest002,1,2,2,SWSH) { Species = 111, Ability = A3 }, // Rhyhorn + new(Nest002,1,2,2,SWSH) { Species = 525, Ability = A3 }, // Boldore + new(Nest002,2,3,3,SWSH) { Species = 689, Ability = A3 }, // Barbaracle + new(Nest002,2,4,4,SWSH) { Species = 112, Ability = A3 }, // Rhydon + new(Nest002,2,4,4,SWSH) { Species = 185, Ability = A3 }, // Sudowoodo + new(Nest002,4,4,4,SWSH) { Species = 213, Ability = A4 }, // Shuckle + new(Nest003,0,0,1,SWSH) { Species = 010, Ability = A3 }, // Caterpie + new(Nest003,0,0,1,SWSH) { Species = 736, Ability = A3 }, // Grubbin + new(Nest003,0,1,1,SWSH) { Species = 290, Ability = A3 }, // Nincada + new(Nest003,0,1,1,SWSH) { Species = 595, Ability = A3 }, // Joltik + new(Nest003,1,2,2,SWSH) { Species = 011, Ability = A3 }, // Metapod + new(Nest003,1,2,2,SWSH) { Species = 632, Ability = A3 }, // Durant + new(Nest003,2,3,3,SWSH) { Species = 737, Ability = A3 }, // Charjabug + new(Nest003,2,4,4,SWSH) { Species = 291, Ability = A3 }, // Ninjask + new(Nest003,2,4,4,SWSH) { Species = 012, Ability = A3 }, // Butterfree + new(Nest003,3,4,4,SWSH) { Species = 596, Ability = A4 }, // Galvantula + new(Nest003,4,4,4,SWSH) { Species = 738, Ability = A4 }, // Vikavolt + new(Nest003,4,4,4,SWSH) { Species = 632, Ability = A4 }, // Durant + new(Nest004,0,0,1,SWSH) { Species = 010, Ability = A3 }, // Caterpie + new(Nest004,0,0,1,SWSH) { Species = 415, Ability = A3 }, // Combee + new(Nest004,0,1,1,SWSH) { Species = 742, Ability = A3 }, // Cutiefly + new(Nest004,0,1,1,SWSH) { Species = 824, Ability = A3 }, // Blipbug + new(Nest004,1,2,2,SWSH) { Species = 595, Ability = A3 }, // Joltik + new(Nest004,1,2,2,SWSH) { Species = 011, Ability = A3 }, // Metapod + new(Nest004,2,3,3,SWSH) { Species = 825, Ability = A3 }, // Dottler + new(Nest004,2,4,4,SWSH) { Species = 596, Ability = A3 }, // Galvantula + new(Nest004,2,4,4,SWSH) { Species = 012, Ability = A3 }, // Butterfree + new(Nest004,3,4,4,SWSH) { Species = 743, Ability = A4 }, // Ribombee + new(Nest004,4,4,4,SWSH) { Species = 416, Ability = A4 }, // Vespiquen + new(Nest004,4,4,4,SWSH) { Species = 826, Ability = A4 }, // Orbeetle + new(Nest005,0,0,1,SWSH) { Species = 092, Ability = A3 }, // Gastly + new(Nest005,0,0,1,SWSH) { Species = 355, Ability = A3 }, // Duskull + new(Nest005,0,1,1,SWSH) { Species = 425, Ability = A3 }, // Drifloon + new(Nest005,0,1,1,SWSH) { Species = 708, Ability = A3 }, // Phantump + new(Nest005,0,1,1,SWSH) { Species = 592, Ability = A3 }, // Frillish + new(Nest005,1,2,2,SWSH) { Species = 710, Ability = A3 }, // Pumpkaboo + new(Nest005,2,3,3,SWSH) { Species = 093, Ability = A3 }, // Haunter + new(Nest005,2,4,4,SWSH) { Species = 356, Ability = A3 }, // Dusclops + new(Nest005,2,4,4,SWSH) { Species = 426, Ability = A3 }, // Drifblim + new(Nest005,3,4,4,SWSH) { Species = 709, Ability = A4 }, // Trevenant + new(Nest005,4,4,4,SWSH) { Species = 711, Ability = A4 }, // Gourgeist + new(Nest005,4,4,4,SWSH) { Species = 593, Ability = A4 }, // Jellicent + //new(Nest006,0,0,1,SWSH) { Species = 129, Ability = A3 }, // Magikarp + //new(Nest006,0,0,1,SWSH) { Species = 458, Ability = A3 }, // Mantyke + new(Nest006,2,2,2,SWSH) { Species = 320, Ability = A3 }, // Wailmer + new(Nest006,2,3,3,SWSH) { Species = 224, Ability = A3 }, // Octillery + new(Nest006,2,4,4,SWSH) { Species = 226, Ability = A3 }, // Mantine + new(Nest006,2,4,4,SWSH) { Species = 171, Ability = A3 }, // Lanturn + new(Nest006,3,4,4,SWSH) { Species = 321, Ability = A4 }, // Wailord + new(Nest006,4,4,4,SWSH) { Species = 746, Ability = A4 }, // Wishiwashi + new(Nest006,4,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados + //new(Nest007,0,0,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle + //new(Nest007,0,0,1,SWSH) { Species = 846, Ability = A3 }, // Arrokuda + //new(Nest007,0,1,1,SWSH) { Species = 422, Ability = A3, Form = 1 }, // Shellos-1 + //new(Nest007,0,1,1,SWSH) { Species = 751, Ability = A3 }, // Dewpider + new(Nest007,2,2,2,SWSH) { Species = 320, Ability = A3 }, // Wailmer + new(Nest007,2,3,3,SWSH) { Species = 746, Ability = A3 }, // Wishiwashi + new(Nest007,2,4,4,SWSH) { Species = 834, Ability = A3 }, // Drednaw + new(Nest007,2,4,4,SWSH) { Species = 847, Ability = A3 }, // Barraskewda + new(Nest007,3,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid + new(Nest007,4,4,4,SWSH) { Species = 423, Ability = A4, Form = 1 }, // Gastrodon-1 + new(Nest007,4,4,4,SWSH) { Species = 321, Ability = A4 }, // Wailord + new(Nest008,0,0,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle + new(Nest008,0,0,1,SWSH) { Species = 194, Ability = A3 }, // Wooper + new(Nest008,0,1,1,SWSH) { Species = 535, Ability = A3 }, // Tympole + new(Nest008,0,1,1,SWSH) { Species = 341, Ability = A3 }, // Corphish + new(Nest008,1,2,2,SWSH) { Species = 536, Ability = A3 }, // Palpitoad + new(Nest008,2,3,3,SWSH) { Species = 834, Ability = A3 }, // Drednaw + new(Nest008,2,4,4,SWSH) { Species = 195, Ability = A3 }, // Quagsire + new(Nest008,2,4,4,SWSH) { Species = 771, Ability = A3 }, // Pyukumuku + new(Nest008,3,4,4,SWSH) { Species = 091, Ability = A4 }, // Cloyster + new(Nest008,4,4,4,SWSH) { Species = 537, Ability = A4 }, // Seismitoad + new(Nest008,4,4,4,SWSH) { Species = 342, Ability = A4 }, // Crawdaunt + new(Nest009,0,0,1,SWSH) { Species = 236, Ability = A3 }, // Tyrogue + new(Nest009,0,0,1,SWSH) { Species = 759, Ability = A3 }, // Stufful + new(Nest009,0,1,1,SWSH) { Species = 852, Ability = A3 }, // Clobbopus + new(Nest009,0,1,1,SWSH) { Species = 674, Ability = A3 }, // Pancham + new(Nest009,2,4,4,SWSH) { Species = 760, Ability = A3 }, // Bewear + new(Nest009,2,4,4,SWSH) { Species = 675, Ability = A3 }, // Pangoro + new(Nest009,2,4,4,SWSH) { Species = 701, Ability = A3 }, // Hawlucha + new(Nest009,4,4,4,SWSH) { Species = 853, Ability = A4 }, // Grapploct + new(Nest009,4,4,4,SWSH) { Species = 870, Ability = A4 }, // Falinks + new(Nest010,0,0,1,SWSH) { Species = 599, Ability = A3 }, // Klink + new(Nest010,0,0,1,SWSH) { Species = 052, Ability = A3, Form = 2 }, // Meowth-2 + new(Nest010,0,1,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor + new(Nest010,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed + new(Nest010,1,1,2,SWSH) { Species = 624, Ability = A3 }, // Pawniard + new(Nest010,1,2,2,SWSH) { Species = 878, Ability = A3 }, // Cufant + new(Nest010,2,4,4,SWSH) { Species = 600, Ability = A3 }, // Klang + new(Nest010,2,4,4,SWSH) { Species = 863, Ability = A3 }, // Perrserker + new(Nest010,2,4,4,SWSH) { Species = 437, Ability = A3 }, // Bronzong + new(Nest010,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp + new(Nest010,4,4,4,SWSH) { Species = 601, Ability = A4 }, // Klinklang + new(Nest010,4,4,4,SWSH) { Species = 879, Ability = A4 }, // Copperajah + new(Nest011,0,0,1,SWSH) { Species = 599, Ability = A3 }, // Klink + new(Nest011,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor + new(Nest011,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed + new(Nest011,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard + new(Nest011,1,1,2,SWSH) { Species = 599, Ability = A3 }, // Klink + new(Nest011,1,2,2,SWSH) { Species = 436, Ability = A3 }, // Bronzor + new(Nest011,2,4,4,SWSH) { Species = 208, Ability = A3 }, // Steelix + new(Nest011,2,4,4,SWSH) { Species = 598, Ability = A3 }, // Ferrothorn + new(Nest011,2,4,4,SWSH) { Species = 437, Ability = A3 }, // Bronzong + new(Nest011,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp + new(Nest011,4,4,4,SWSH) { Species = 777, Ability = A4 }, // Togedemaru + new(Nest012,0,0,1,SWSH) { Species = 439, Ability = A3 }, // Mime Jr. + new(Nest012,0,0,1,SWSH) { Species = 824, Ability = A3 }, // Blipbug + new(Nest012,2,3,3,SWSH) { Species = 561, Ability = A3 }, // Sigilyph + new(Nest012,2,3,3,SWSH) { Species = 178, Ability = A3 }, // Xatu + new(Nest012,4,4,4,SWSH) { Species = 858, Ability = A4 }, // Hatterene + new(Nest013,0,0,1,SWSH) { Species = 439, Ability = A3 }, // Mime Jr. + new(Nest013,0,0,1,SWSH) { Species = 360, Ability = A3 }, // Wynaut + new(Nest013,0,1,1,SWSH) { Species = 177, Ability = A3 }, // Natu + new(Nest013,0,1,1,SWSH) { Species = 343, Ability = A3 }, // Baltoy + new(Nest013,1,1,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor + new(Nest013,1,3,3,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 + new(Nest013,2,3,3,SWSH) { Species = 561, Ability = A3 }, // Sigilyph + new(Nest013,2,3,3,SWSH) { Species = 178, Ability = A3 }, // Xatu + new(Nest013,3,4,4,SWSH) { Species = 344, Ability = A4 }, // Claydol + new(Nest013,4,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime + new(Nest013,4,4,4,SWSH) { Species = 202, Ability = A4 }, // Wobbuffet + new(Nest014,0,0,1,SWSH) { Species = 837, Ability = A3 }, // Rolycoly + new(Nest014,0,1,1,SWSH) { Species = 688, Ability = A3 }, // Binacle + new(Nest014,1,1,1,SWSH) { Species = 838, Ability = A3 }, // Carkol + new(Nest014,1,2,2,SWSH) { Species = 525, Ability = A3 }, // Boldore + new(Nest014,2,3,3,SWSH) { Species = 558, Ability = A3 }, // Crustle + new(Nest014,2,4,4,SWSH) { Species = 689, Ability = A3 }, // Barbaracle + new(Nest014,4,4,4,SWSH) { Species = 464, Ability = A4 }, // Rhyperior + new(Nest015,0,0,1,SWSH) { Species = 050, Ability = A3 }, // Diglett + new(Nest015,0,0,1,SWSH) { Species = 749, Ability = A3 }, // Mudbray + new(Nest015,0,1,1,SWSH) { Species = 290, Ability = A3 }, // Nincada + new(Nest015,0,1,1,SWSH) { Species = 529, Ability = A3 }, // Drilbur + new(Nest015,1,1,1,SWSH) { Species = 095, Ability = A3 }, // Onix + new(Nest015,1,2,2,SWSH) { Species = 339, Ability = A3 }, // Barboach + new(Nest015,2,3,3,SWSH) { Species = 208, Ability = A3 }, // Steelix + new(Nest015,2,4,4,SWSH) { Species = 340, Ability = A3 }, // Whiscash + new(Nest015,2,4,4,SWSH) { Species = 660, Ability = A3 }, // Diggersby + new(Nest015,3,4,4,SWSH) { Species = 051, Ability = A4 }, // Dugtrio + new(Nest015,4,4,4,SWSH) { Species = 530, Ability = A4 }, // Excadrill + new(Nest015,4,4,4,SWSH) { Species = 750, Ability = A4 }, // Mudsdale + new(Nest016,0,0,1,SWSH) { Species = 843, Ability = A3 }, // Silicobra + new(Nest016,0,0,1,SWSH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 + new(Nest016,0,1,1,SWSH) { Species = 449, Ability = A3 }, // Hippopotas + new(Nest016,1,2,2,SWSH) { Species = 221, Ability = A3 }, // Piloswine + new(Nest016,4,4,4,SWSH) { Species = 867, Ability = A3 }, // Runerigus + new(Nest016,4,4,4,SWSH) { Species = 844, Ability = A4 }, // Sandaconda + new(Nest017,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede + new(Nest017,0,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit + new(Nest017,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick + new(Nest017,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal + new(Nest018,0,0,1,SWSH) { Species = 757, Ability = A3 }, // Salandit + new(Nest018,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick + new(Nest018,1,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit + new(Nest018,4,4,4,SWSH) { Species = 609, Ability = A4 }, // Chandelure + new(Nest019,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede + new(Nest019,0,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit + new(Nest019,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick + new(Nest019,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal + new(Nest019,3,4,4,SWSH) { Species = 851, Ability = A4 }, // Centiskorch + new(Nest019,4,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal + new(Nest020,0,0,1,SWSH) { Species = 582, Ability = A3 }, // Vanillite + new(Nest020,0,0,1,SWSH) { Species = 220, Ability = A3 }, // Swinub + new(Nest020,0,1,1,SWSH) { Species = 459, Ability = A3 }, // Snover + new(Nest020,0,1,1,SWSH) { Species = 712, Ability = A3 }, // Bergmite + new(Nest020,1,1,1,SWSH) { Species = 225, Ability = A3 }, // Delibird + new(Nest020,1,2,2,SWSH) { Species = 583, Ability = A3 }, // Vanillish + new(Nest020,2,3,3,SWSH) { Species = 221, Ability = A3 }, // Piloswine + new(Nest020,2,4,4,SWSH) { Species = 713, Ability = A3 }, // Avalugg + new(Nest020,2,4,4,SWSH) { Species = 460, Ability = A3 }, // Abomasnow + new(Nest020,3,4,4,SWSH) { Species = 091, Ability = A4 }, // Cloyster + new(Nest020,4,4,4,SWSH) { Species = 584, Ability = A4 }, // Vanilluxe + new(Nest020,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras + new(Nest021,0,0,1,SWSH) { Species = 220, Ability = A3 }, // Swinub + new(Nest021,0,0,1,SWSH) { Species = 613, Ability = A3 }, // Cubchoo + new(Nest021,0,1,1,SWSH) { Species = 872, Ability = A3 }, // Snom + new(Nest021,0,1,1,SWSH) { Species = 215, Ability = A3 }, // Sneasel + new(Nest021,1,1,1,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 + new(Nest021,1,2,2,SWSH) { Species = 221, Ability = A3 }, // Piloswine + new(Nest021,2,3,3,SWSH) { Species = 091, Ability = A3 }, // Cloyster + new(Nest021,2,4,4,SWSH) { Species = 614, Ability = A3 }, // Beartic + new(Nest021,2,4,4,SWSH) { Species = 866, Ability = A3 }, // Mr. Rime + new(Nest021,3,4,4,SWSH) { Species = 473, Ability = A4 }, // Mamoswine + new(Nest021,4,4,4,SWSH) { Species = 873, Ability = A4 }, // Frosmoth + new(Nest021,4,4,4,SWSH) { Species = 461, Ability = A4 }, // Weavile + new(Nest022,0,0,1,SWSH) { Species = 361, Ability = A3 }, // Snorunt + new(Nest022,0,0,1,SWSH) { Species = 872, Ability = A3 }, // Snom + new(Nest022,0,1,1,SWSH) { Species = 215, Ability = A3 }, // Sneasel + new(Nest022,1,1,2,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 + new(Nest022,1,2,3,SWSH) { Species = 459, Ability = A3 }, // Snover + new(Nest022,2,3,3,SWSH) { Species = 460, Ability = A3 }, // Abomasnow + new(Nest022,2,4,4,SWSH) { Species = 362, Ability = A3 }, // Glalie + new(Nest022,2,4,4,SWSH) { Species = 866, Ability = A3 }, // Mr. Rime + new(Nest022,3,4,4,SWSH) { Species = 873, Ability = A4 }, // Frosmoth + new(Nest022,4,4,4,SWSH) { Species = 478, Ability = A4 }, // Froslass + new(Nest023,0,0,1,SWSH) { Species = 172, Ability = A3 }, // Pichu + new(Nest023,0,0,1,SWSH) { Species = 309, Ability = A3 }, // Electrike + new(Nest023,0,1,1,SWSH) { Species = 595, Ability = A3 }, // Joltik + new(Nest023,0,1,1,SWSH) { Species = 170, Ability = A3 }, // Chinchou + new(Nest023,1,1,2,SWSH) { Species = 737, Ability = A3 }, // Charjabug + new(Nest023,1,2,3,SWSH) { Species = 025, Ability = A3 }, // Pikachu + new(Nest023,2,3,3,SWSH) { Species = 025, Ability = A3 }, // Pikachu + new(Nest023,2,4,4,SWSH) { Species = 310, Ability = A3 }, // Manectric + new(Nest023,2,4,4,SWSH) { Species = 171, Ability = A3 }, // Lanturn + new(Nest023,3,4,4,SWSH) { Species = 596, Ability = A4 }, // Galvantula + new(Nest023,4,4,4,SWSH) { Species = 738, Ability = A4 }, // Vikavolt + new(Nest023,4,4,4,SWSH) { Species = 026, Ability = A4 }, // Raichu + new(Nest024,0,0,1,SWSH) { Species = 835, Ability = A3 }, // Yamper + new(Nest024,0,0,1,SWSH) { Species = 694, Ability = A3 }, // Helioptile + new(Nest024,0,1,1,SWSH) { Species = 848, Ability = A3 }, // Toxel + new(Nest024,0,1,1,SWSH) { Species = 170, Ability = A3 }, // Chinchou + new(Nest024,1,1,2,SWSH) { Species = 025, Ability = A3 }, // Pikachu + new(Nest024,1,2,3,SWSH) { Species = 171, Ability = A3 }, // Lanturn + new(Nest024,2,3,3,SWSH) { Species = 836, Ability = A3 }, // Boltund + new(Nest024,2,4,4,SWSH) { Species = 695, Ability = A3 }, // Heliolisk + new(Nest024,2,4,4,SWSH) { Species = 849, Ability = A3 }, // Toxtricity + new(Nest024,3,4,4,SWSH) { Species = 871, Ability = A4 }, // Pincurchin + new(Nest024,4,4,4,SWSH) { Species = 777, Ability = A4 }, // Togedemaru + new(Nest024,4,4,4,SWSH) { Species = 877, Ability = A4 }, // Morpeko + new(Nest025,0,0,1,SWSH) { Species = 406, Ability = A3 }, // Budew + new(Nest025,0,1,1,SWSH) { Species = 761, Ability = A3 }, // Bounsweet + new(Nest025,0,1,1,SWSH) { Species = 043, Ability = A3 }, // Oddish + new(Nest025,1,2,3,SWSH) { Species = 315, Ability = A3 }, // Roselia + new(Nest025,2,3,3,SWSH) { Species = 044, Ability = A3 }, // Gloom + new(Nest025,2,4,4,SWSH) { Species = 762, Ability = A3 }, // Steenee + new(Nest025,3,4,4,SWSH) { Species = 763, Ability = A4 }, // Tsareena + new(Nest025,4,4,4,SWSH) { Species = 045, Ability = A4 }, // Vileplume + new(Nest025,4,4,4,SWSH) { Species = 182, Ability = A4 }, // Bellossom + new(Nest026,0,0,1,SWSH) { Species = 406, Ability = A3 }, // Budew + new(Nest026,0,0,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur + new(Nest026,0,1,1,SWSH) { Species = 546, Ability = A3 }, // Cottonee + new(Nest026,0,1,1,SWSH) { Species = 840, Ability = A3 }, // Applin + new(Nest026,1,1,2,SWSH) { Species = 420, Ability = A3 }, // Cherubi + new(Nest026,1,2,2,SWSH) { Species = 315, Ability = A3 }, // Roselia + new(Nest026,2,3,3,SWSH) { Species = 597, Ability = A3 }, // Ferroseed + new(Nest026,2,4,4,SWSH) { Species = 598, Ability = A3 }, // Ferrothorn + new(Nest026,2,4,4,SWSH) { Species = 421, Ability = A3 }, // Cherrim + new(Nest026,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss + new(Nest026,4,4,4,SWSH) { Species = 547, Ability = A4 }, // Whimsicott + new(Nest027,0,0,1,SWSH) { Species = 710, Ability = A3, Form = 1 }, // Pumpkaboo-1 + new(Nest027,0,0,1,SWSH) { Species = 708, Ability = A3 }, // Phantump + new(Nest027,0,1,1,SWSH) { Species = 710, Ability = A3 }, // Pumpkaboo + new(Nest027,0,1,1,SWSH) { Species = 755, Ability = A3 }, // Morelull + new(Nest027,1,1,2,SWSH) { Species = 710, Ability = A3, Form = 2 }, // Pumpkaboo-2 + new(Nest027,1,2,2,SWSH) { Species = 315, Ability = A3 }, // Roselia + new(Nest027,2,3,3,SWSH) { Species = 756, Ability = A3 }, // Shiinotic + new(Nest027,2,4,4,SWSH) { Species = 556, Ability = A3 }, // Maractus + new(Nest027,2,4,4,SWSH) { Species = 709, Ability = A3 }, // Trevenant + new(Nest027,3,4,4,SWSH) { Species = 711, Ability = A4 }, // Gourgeist + new(Nest027,4,4,4,SWSH) { Species = 781, Ability = A4 }, // Dhelmise + new(Nest027,4,4,4,SWSH) { Species = 710, Ability = A4, Form = 3 }, // Pumpkaboo-3 + new(Nest028,0,0,1,SWSH) { Species = 434, Ability = A3 }, // Stunky + new(Nest028,0,0,1,SWSH) { Species = 568, Ability = A3 }, // Trubbish + new(Nest028,0,1,1,SWSH) { Species = 451, Ability = A3 }, // Skorupi + new(Nest028,1,2,2,SWSH) { Species = 315, Ability = A3 }, // Roselia + new(Nest028,2,3,3,SWSH) { Species = 211, Ability = A3 }, // Qwilfish + new(Nest028,2,4,4,SWSH) { Species = 452, Ability = A3 }, // Drapion + new(Nest028,2,4,4,SWSH) { Species = 045, Ability = A3 }, // Vileplume + new(Nest028,4,4,4,SWSH) { Species = 569, Ability = A4 }, // Garbodor + new(Nest029,0,0,1,SWSH) { Species = 848, Ability = A3 }, // Toxel + new(Nest029,0,0,1,SWSH) { Species = 092, Ability = A3 }, // Gastly + new(Nest029,0,1,1,SWSH) { Species = 451, Ability = A3 }, // Skorupi + new(Nest029,0,1,1,SWSH) { Species = 043, Ability = A3 }, // Oddish + new(Nest029,1,1,2,SWSH) { Species = 044, Ability = A3 }, // Gloom + new(Nest029,1,2,2,SWSH) { Species = 093, Ability = A3 }, // Haunter + new(Nest029,2,3,3,SWSH) { Species = 109, Ability = A3 }, // Koffing + new(Nest029,2,4,4,SWSH) { Species = 211, Ability = A3 }, // Qwilfish + new(Nest029,2,4,4,SWSH) { Species = 045, Ability = A3 }, // Vileplume + new(Nest029,3,4,4,SWSH) { Species = 315, Ability = A4 }, // Roselia + new(Nest029,4,4,4,SWSH) { Species = 849, Ability = A4 }, // Toxtricity + new(Nest029,4,4,4,SWSH) { Species = 110, Ability = A4, Form = 1 }, // Weezing-1 + new(Nest030,0,0,1,SWSH) { Species = 519, Ability = A3 }, // Pidove + new(Nest030,0,0,1,SWSH) { Species = 163, Ability = A3 }, // Hoothoot + new(Nest030,0,1,1,SWSH) { Species = 177, Ability = A3 }, // Natu + new(Nest030,1,1,2,SWSH) { Species = 527, Ability = A3 }, // Woobat + new(Nest030,1,2,2,SWSH) { Species = 520, Ability = A3 }, // Tranquill + new(Nest030,2,3,3,SWSH) { Species = 521, Ability = A3 }, // Unfezant + new(Nest030,2,4,4,SWSH) { Species = 164, Ability = A3 }, // Noctowl + new(Nest030,2,4,4,SWSH) { Species = 528, Ability = A3 }, // Swoobat + new(Nest030,3,4,4,SWSH) { Species = 178, Ability = A4 }, // Xatu + new(Nest030,4,4,4,SWSH) { Species = 561, Ability = A4 }, // Sigilyph + new(Nest031,0,0,1,SWSH) { Species = 821, Ability = A3 }, // Rookidee + new(Nest031,0,0,1,SWSH) { Species = 714, Ability = A3 }, // Noibat + new(Nest031,0,1,1,SWSH) { Species = 278, Ability = A3 }, // Wingull + new(Nest031,0,1,1,SWSH) { Species = 177, Ability = A3 }, // Natu + new(Nest031,1,1,2,SWSH) { Species = 425, Ability = A3 }, // Drifloon + new(Nest031,1,2,2,SWSH) { Species = 822, Ability = A3 }, // Corvisquire + new(Nest031,2,3,3,SWSH) { Species = 426, Ability = A3 }, // Drifblim + new(Nest031,2,4,4,SWSH) { Species = 279, Ability = A3 }, // Pelipper + new(Nest031,2,4,4,SWSH) { Species = 178, Ability = A3 }, // Xatu + new(Nest031,3,4,4,SWSH) { Species = 823, Ability = A4 }, // Corviknight + new(Nest031,4,4,4,SWSH) { Species = 701, Ability = A4 }, // Hawlucha + new(Nest031,4,4,4,SWSH) { Species = 845, Ability = A4 }, // Cramorant + new(Nest032,0,0,1,SWSH) { Species = 173, Ability = A3 }, // Cleffa + new(Nest032,0,0,1,SWSH) { Species = 175, Ability = A3 }, // Togepi + new(Nest032,0,1,1,SWSH) { Species = 742, Ability = A3 }, // Cutiefly + new(Nest032,1,1,2,SWSH) { Species = 035, Ability = A3 }, // Clefairy + new(Nest032,1,2,2,SWSH) { Species = 755, Ability = A3 }, // Morelull + new(Nest032,2,3,3,SWSH) { Species = 176, Ability = A3 }, // Togetic + new(Nest032,2,4,4,SWSH) { Species = 036, Ability = A3 }, // Clefable + new(Nest032,2,4,4,SWSH) { Species = 743, Ability = A3 }, // Ribombee + new(Nest032,3,4,4,SWSH) { Species = 756, Ability = A4 }, // Shiinotic + new(Nest032,4,4,4,SWSH) { Species = 468, Ability = A4 }, // Togekiss + new(Nest033,0,0,1,SWSH) { Species = 439, Ability = A3 }, // Mime Jr. + new(Nest033,0,0,1,SWSH) { Species = 868, Ability = A3 }, // Milcery + new(Nest033,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp + new(Nest033,0,1,1,SWSH) { Species = 280, Ability = A3 }, // Ralts + new(Nest033,1,1,2,SWSH) { Species = 035, Ability = A3 }, // Clefairy + new(Nest033,1,2,2,SWSH) { Species = 281, Ability = A3 }, // Kirlia + new(Nest033,2,3,3,SWSH) { Species = 860, Ability = A3 }, // Morgrem + new(Nest033,2,4,4,SWSH) { Species = 036, Ability = A3 }, // Clefable + new(Nest033,2,4,4,SWSH) { Species = 282, Ability = A3 }, // Gardevoir + new(Nest033,3,4,4,SWSH) { Species = 869, Ability = A4 }, // Alcremie + new(Nest033,4,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl + new(Nest034,0,0,1,SWSH) { Species = 509, Ability = A3 }, // Purrloin + new(Nest034,0,0,1,SWSH) { Species = 434, Ability = A3 }, // Stunky + new(Nest034,0,1,1,SWSH) { Species = 215, Ability = A3 }, // Sneasel + new(Nest034,0,1,1,SWSH) { Species = 686, Ability = A3 }, // Inkay + new(Nest034,1,1,2,SWSH) { Species = 624, Ability = A3 }, // Pawniard + new(Nest034,1,2,2,SWSH) { Species = 510, Ability = A3 }, // Liepard + new(Nest034,2,3,3,SWSH) { Species = 435, Ability = A3 }, // Skuntank + new(Nest034,2,4,4,SWSH) { Species = 461, Ability = A3 }, // Weavile + new(Nest034,2,4,4,SWSH) { Species = 687, Ability = A3 }, // Malamar + new(Nest034,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp + new(Nest034,4,4,4,SWSH) { Species = 342, Ability = A4 }, // Crawdaunt + new(Nest035,0,0,1,SWSH) { Species = 827, Ability = A3 }, // Nickit + new(Nest035,0,0,1,SWSH) { Species = 263, Ability = A3, Form = 1 }, // Zigzagoon-1 + new(Nest035,0,1,1,SWSH) { Species = 509, Ability = A3 }, // Purrloin + new(Nest035,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp + new(Nest035,1,2,2,SWSH) { Species = 828, Ability = A3 }, // Thievul + new(Nest035,2,3,3,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 + new(Nest035,2,4,4,SWSH) { Species = 860, Ability = A3 }, // Morgrem + new(Nest035,2,4,4,SWSH) { Species = 861, Ability = A3 }, // Grimmsnarl + new(Nest035,4,4,4,SWSH) { Species = 862, Ability = A4 }, // Obstagoon + new(Nest036,0,0,1,SWSH) { Species = 714, Ability = A3 }, // Noibat + new(Nest036,0,1,1,SWSH) { Species = 714, Ability = A3 }, // Noibat + new(Nest036,1,2,2,SWSH) { Species = 329, Ability = A3 }, // Vibrava + //new(Nest037,0,0,1,SWSH) { Species = 714, Ability = A3 }, // Noibat + //new(Nest037,0,0,1,SWSH) { Species = 840, Ability = A3 }, // Applin + //new(Nest037,0,1,1,SWSH) { Species = 885, Ability = A3 }, // Dreepy + //new(Nest037,1,1,2,SWSH) { Species = 714, Ability = A3 }, // Noibat + new(Nest037,2,2,2,SWSH) { Species = 840, Ability = A3 }, // Applin + new(Nest037,2,3,3,SWSH) { Species = 886, Ability = A3 }, // Drakloak + new(Nest037,2,4,4,SWSH) { Species = 715, Ability = A3 }, // Noivern + new(Nest037,4,4,4,SWSH) { Species = 887, Ability = A4 }, // Dragapult + new(Nest038,0,0,1,SWSH) { Species = 659, Ability = A3 }, // Bunnelby + new(Nest038,0,0,1,SWSH) { Species = 163, Ability = A3 }, // Hoothoot + new(Nest038,0,1,1,SWSH) { Species = 519, Ability = A3 }, // Pidove + new(Nest038,0,1,1,SWSH) { Species = 572, Ability = A3 }, // Minccino + new(Nest038,1,1,2,SWSH) { Species = 694, Ability = A3 }, // Helioptile + new(Nest038,1,2,2,SWSH) { Species = 759, Ability = A3 }, // Stufful + new(Nest038,2,3,3,SWSH) { Species = 660, Ability = A3 }, // Diggersby + new(Nest038,2,4,4,SWSH) { Species = 164, Ability = A3 }, // Noctowl + new(Nest038,2,4,4,SWSH) { Species = 521, Ability = A3 }, // Unfezant + new(Nest038,3,4,4,SWSH) { Species = 695, Ability = A4 }, // Heliolisk + new(Nest038,4,4,4,SWSH) { Species = 573, Ability = A4 }, // Cinccino + new(Nest038,4,4,4,SWSH) { Species = 760, Ability = A4 }, // Bewear + new(Nest039,0,0,1,SWSH) { Species = 819, Ability = A3 }, // Skwovet + new(Nest039,0,0,1,SWSH) { Species = 831, Ability = A3 }, // Wooloo + new(Nest039,0,1,1,SWSH) { Species = 263, Ability = A3, Form = 1 }, // Zigzagoon-1 + new(Nest039,0,1,1,SWSH) { Species = 446, Ability = A3 }, // Munchlax + new(Nest039,1,2,2,SWSH) { Species = 820, Ability = A3 }, // Greedent + new(Nest039,2,3,3,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 + new(Nest039,2,4,4,SWSH) { Species = 820, Ability = A3 }, // Greedent + new(Nest039,2,4,4,SWSH) { Species = 832, Ability = A3 }, // Dubwool + new(Nest039,3,4,4,SWSH) { Species = 660, Ability = A4 }, // Diggersby + new(Nest039,4,4,4,SWSH) { Species = 143, Ability = A4 }, // Snorlax + //new(Nest040,0,0,1,SWSH) { Species = 535, Ability = A3 }, // Tympole + //new(Nest040,0,0,1,SWSH) { Species = 090, Ability = A3 }, // Shellder + //new(Nest040,0,1,1,SWSH) { Species = 170, Ability = A3 }, // Chinchou + new(Nest040,2,2,2,SWSH) { Species = 846, Ability = A3 }, // Arrokuda + new(Nest040,2,4,4,SWSH) { Species = 171, Ability = A3 }, // Lanturn + new(Nest040,4,4,4,SWSH) { Species = 847, Ability = A4 }, // Barraskewda + //new(Nest041,0,0,1,SWSH) { Species = 422, Ability = A3, Form = 1 }, // Shellos-1 + //new(Nest041,0,0,1,SWSH) { Species = 098, Ability = A3 }, // Krabby + //new(Nest041,0,1,1,SWSH) { Species = 341, Ability = A3 }, // Corphish + //new(Nest041,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle + //new(Nest041,1,1,2,SWSH) { Species = 688, Ability = A3 }, // Binacle + new(Nest041,2,2,2,SWSH) { Species = 771, Ability = A3 }, // Pyukumuku + new(Nest041,2,3,3,SWSH) { Species = 099, Ability = A3 }, // Kingler + new(Nest041,2,4,4,SWSH) { Species = 342, Ability = A3 }, // Crawdaunt + new(Nest041,2,4,4,SWSH) { Species = 689, Ability = A3 }, // Barbaracle + new(Nest041,3,4,4,SWSH) { Species = 423, Ability = A4, Form = 1 }, // Gastrodon-1 + new(Nest041,4,4,4,SWSH) { Species = 593, Ability = A4 }, // Jellicent + new(Nest041,4,4,4,SWSH) { Species = 834, Ability = A4 }, // Drednaw + new(Nest042,0,0,1,SWSH) { Species = 092, Ability = A3 }, // Gastly + new(Nest042,0,0,1,SWSH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 + new(Nest042,0,1,1,SWSH) { Species = 854, Ability = A3 }, // Sinistea + new(Nest042,0,1,1,SWSH) { Species = 355, Ability = A3 }, // Duskull + new(Nest042,1,2,2,SWSH) { Species = 093, Ability = A3 }, // Haunter + new(Nest042,2,3,3,SWSH) { Species = 356, Ability = A3 }, // Dusclops + new(Nest042,4,4,4,SWSH) { Species = 477, Ability = A4 }, // Dusknoir + new(Nest042,4,4,4,SWSH) { Species = 094, Ability = A4 }, // Gengar + //new(Nest043,0,0,1,SWSH) { Species = 129, Ability = A3 }, // Magikarp + //new(Nest043,0,0,1,SWSH) { Species = 349, Ability = A3 }, // Feebas + //new(Nest043,0,1,1,SWSH) { Species = 846, Ability = A3 }, // Arrokuda + //new(Nest043,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle + new(Nest043,2,2,2,SWSH) { Species = 747, Ability = A3 }, // Mareanie + new(Nest043,2,3,3,SWSH) { Species = 211, Ability = A3 }, // Qwilfish + new(Nest043,2,4,4,SWSH) { Species = 748, Ability = A3 }, // Toxapex + new(Nest043,3,4,4,SWSH) { Species = 771, Ability = A4 }, // Pyukumuku + new(Nest043,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados + new(Nest043,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras + new(Nest043,4,4,4,SWSH) { Species = 350, Ability = A4 }, // Milotic + new(Nest044,0,0,1,SWSH) { Species = 447, Ability = A3 }, // Riolu + new(Nest044,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor + new(Nest044,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard + new(Nest044,0,1,1,SWSH) { Species = 599, Ability = A3 }, // Klink + new(Nest044,1,2,2,SWSH) { Species = 095, Ability = A3 }, // Onix + new(Nest044,2,4,4,SWSH) { Species = 437, Ability = A3 }, // Bronzong + new(Nest044,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp + new(Nest044,3,4,4,SWSH) { Species = 208, Ability = A4 }, // Steelix + new(Nest044,4,4,4,SWSH) { Species = 601, Ability = A4 }, // Klinklang + new(Nest044,4,4,4,SWSH) { Species = 448, Ability = A4 }, // Lucario + new(Nest045,0,0,1,SWSH) { Species = 767, Ability = A3 }, // Wimpod + new(Nest045,0,0,1,SWSH) { Species = 824, Ability = A3 }, // Blipbug + new(Nest045,0,1,1,SWSH) { Species = 751, Ability = A3 }, // Dewpider + new(Nest045,1,2,2,SWSH) { Species = 557, Ability = A3 }, // Dwebble + new(Nest045,2,3,3,SWSH) { Species = 825, Ability = A3 }, // Dottler + new(Nest045,2,4,4,SWSH) { Species = 826, Ability = A3 }, // Orbeetle + new(Nest045,3,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid + new(Nest045,3,4,4,SWSH) { Species = 768, Ability = A4 }, // Golisopod + new(Nest045,4,4,4,SWSH) { Species = 292, Ability = A4 }, // Shedinja + new(Nest046,0,0,1,SWSH) { Species = 679, Ability = A3 }, // Honedge + new(Nest046,0,0,1,SWSH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 + new(Nest046,0,1,1,SWSH) { Species = 854, Ability = A3 }, // Sinistea + new(Nest046,0,1,1,SWSH) { Species = 425, Ability = A3 }, // Drifloon + new(Nest046,1,2,2,SWSH) { Species = 680, Ability = A3 }, // Doublade + new(Nest046,2,3,3,SWSH) { Species = 426, Ability = A3 }, // Drifblim + new(Nest046,3,4,4,SWSH) { Species = 855, Ability = A4 }, // Polteageist + new(Nest046,4,4,4,SWSH) { Species = 867, Ability = A4 }, // Runerigus + new(Nest046,4,4,4,SWSH) { Species = 681, Ability = A4 }, // Aegislash + new(Nest047,0,0,1,SWSH) { Species = 447, Ability = A3 }, // Riolu + new(Nest047,0,0,1,SWSH) { Species = 066, Ability = A3 }, // Machop + new(Nest047,0,1,1,SWSH) { Species = 759, Ability = A3 }, // Stufful + new(Nest047,1,2,2,SWSH) { Species = 760, Ability = A3 }, // Bewear + new(Nest047,1,3,3,SWSH) { Species = 870, Ability = A3 }, // Falinks + new(Nest047,2,3,3,SWSH) { Species = 067, Ability = A3 }, // Machoke + new(Nest047,3,4,4,SWSH) { Species = 068, Ability = A4 }, // Machamp + new(Nest047,4,4,4,SWSH) { Species = 448, Ability = A4 }, // Lucario + new(Nest047,4,4,4,SWSH) { Species = 475, Ability = A4 }, // Gallade + new(Nest048,0,0,1,SWSH) { Species = 052, Ability = A3, Form = 2 }, // Meowth-2 + new(Nest048,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor + new(Nest048,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard + new(Nest048,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed + new(Nest048,1,2,2,SWSH) { Species = 679, Ability = A3 }, // Honedge + new(Nest048,1,2,2,SWSH) { Species = 437, Ability = A3 }, // Bronzong + new(Nest048,3,4,4,SWSH) { Species = 863, Ability = A4 }, // Perrserker + new(Nest048,2,4,4,SWSH) { Species = 598, Ability = A3 }, // Ferrothorn + new(Nest048,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp + new(Nest048,3,4,4,SWSH) { Species = 618, Ability = A4, Form = 1 }, // Stunfisk-1 + new(Nest048,4,4,4,SWSH) { Species = 879, Ability = A4 }, // Copperajah + new(Nest048,4,4,4,SWSH) { Species = 884, Ability = A4 }, // Duraludon + new(Nest049,0,0,1,SWSH) { Species = 686, Ability = A3 }, // Inkay + new(Nest049,0,0,1,SWSH) { Species = 280, Ability = A3 }, // Ralts + new(Nest049,0,1,1,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 + new(Nest049,0,1,1,SWSH) { Species = 527, Ability = A3 }, // Woobat + new(Nest049,1,2,2,SWSH) { Species = 856, Ability = A3 }, // Hatenna + new(Nest049,1,2,2,SWSH) { Species = 857, Ability = A3 }, // Hattrem + new(Nest049,2,3,3,SWSH) { Species = 281, Ability = A3 }, // Kirlia + new(Nest049,2,4,4,SWSH) { Species = 528, Ability = A3 }, // Swoobat + new(Nest049,3,4,4,SWSH) { Species = 858, Ability = A4 }, // Hatterene + new(Nest049,3,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime + new(Nest049,4,4,4,SWSH) { Species = 687, Ability = A4 }, // Malamar + new(Nest049,4,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir + new(Nest050,0,0,1,SWSH) { Species = 557, Ability = A3 }, // Dwebble + new(Nest050,0,0,1,SWSH) { Species = 438, Ability = A3 }, // Bonsly + new(Nest050,0,1,1,SWSH) { Species = 837, Ability = A3 }, // Rolycoly + new(Nest050,1,2,2,SWSH) { Species = 838, Ability = A3 }, // Carkol + new(Nest050,2,4,4,SWSH) { Species = 095, Ability = A3 }, // Onix + new(Nest050,3,4,4,SWSH) { Species = 558, Ability = A4 }, // Crustle + new(Nest050,3,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal + new(Nest050,4,4,4,SWSH) { Species = 208, Ability = A4 }, // Steelix + new(Nest051,0,0,1,SWSH) { Species = 194, Ability = A3 }, // Wooper + new(Nest051,0,0,1,SWSH) { Species = 339, Ability = A3 }, // Barboach + new(Nest051,0,1,1,SWSH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 + new(Nest051,0,1,1,SWSH) { Species = 622, Ability = A3 }, // Golett + new(Nest051,1,2,2,SWSH) { Species = 536, Ability = A3 }, // Palpitoad + new(Nest051,1,2,2,SWSH) { Species = 195, Ability = A3 }, // Quagsire + new(Nest051,2,3,3,SWSH) { Species = 618, Ability = A3, Form = 1 }, // Stunfisk-1 + new(Nest051,2,4,4,SWSH) { Species = 623, Ability = A3 }, // Golurk + new(Nest051,3,4,4,SWSH) { Species = 423, Ability = A4, Form = 1 }, // Gastrodon-1 + new(Nest051,3,4,4,SWSH) { Species = 537, Ability = A4 }, // Seismitoad + new(Nest051,4,4,4,SWSH) { Species = 867, Ability = A4 }, // Runerigus + new(Nest051,4,4,4,SWSH) { Species = 464, Ability = A4 }, // Rhyperior + new(Nest052,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede + new(Nest052,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick + new(Nest052,0,1,1,SWSH) { Species = 004, Ability = A3 }, // Charmander + new(Nest052,1,2,2,SWSH) { Species = 005, Ability = A3 }, // Charmeleon + new(Nest052,2,3,3,SWSH) { Species = 631, Ability = A3 }, // Heatmor + new(Nest052,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal + new(Nest052,3,4,4,SWSH) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle + new(Nest052,4,4,4,SWSH) { Species = 851, Ability = A4 }, // Centiskorch + new(Nest052,4,4,4,SWSH) { Species = 006, Ability = A4 }, // Charizard + new(Nest053,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede + new(Nest053,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick + new(Nest053,0,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit + new(Nest053,1,2,2,SWSH) { Species = 838, Ability = A3 }, // Carkol + new(Nest053,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal + new(Nest053,3,4,4,SWSH) { Species = 609, Ability = A4 }, // Chandelure + new(Nest053,4,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal + new(Nest054,0,0,1,SWSH) { Species = 582, Ability = A3 }, // Vanillite + new(Nest054,0,1,1,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 + new(Nest054,0,1,1,SWSH) { Species = 712, Ability = A3 }, // Bergmite + new(Nest054,1,2,2,SWSH) { Species = 361, Ability = A3 }, // Snorunt + new(Nest054,1,2,2,SWSH) { Species = 225, Ability = A3 }, // Delibird + new(Nest054,2,3,3,SWSH) { Species = 713, Ability = A3 }, // Avalugg + new(Nest054,2,4,4,SWSH) { Species = 362, Ability = A3 }, // Glalie + new(Nest054,3,4,4,SWSH) { Species = 584, Ability = A4 }, // Vanilluxe + new(Nest054,3,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime + new(Nest054,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras + new(Nest055,0,0,1,SWSH) { Species = 835, Ability = A3 }, // Yamper + new(Nest055,0,0,1,SWSH) { Species = 848, Ability = A3 }, // Toxel + new(Nest055,0,1,1,SWSH) { Species = 025, Ability = A3 }, // Pikachu + new(Nest055,0,1,1,SWSH) { Species = 595, Ability = A3 }, // Joltik + new(Nest055,1,2,2,SWSH) { Species = 170, Ability = A3 }, // Chinchou + new(Nest055,1,2,2,SWSH) { Species = 171, Ability = A3 }, // Lanturn + new(Nest055,2,4,4,SWSH) { Species = 836, Ability = A3 }, // Boltund + new(Nest055,2,4,4,SWSH) { Species = 849, Ability = A3 }, // Toxtricity + new(Nest055,3,4,4,SWSH) { Species = 871, Ability = A4 }, // Pincurchin + new(Nest055,3,4,4,SWSH) { Species = 596, Ability = A4 }, // Galvantula + new(Nest055,4,4,4,SWSH) { Species = 777, Ability = A4 }, // Togedemaru + new(Nest055,4,4,4,SWSH) { Species = 877, Ability = A4 }, // Morpeko + new(Nest056,0,0,1,SWSH) { Species = 172, Ability = A3 }, // Pichu + new(Nest056,0,0,1,SWSH) { Species = 309, Ability = A3 }, // Electrike + new(Nest056,0,1,1,SWSH) { Species = 848, Ability = A3 }, // Toxel + new(Nest056,0,1,1,SWSH) { Species = 694, Ability = A3 }, // Helioptile + new(Nest056,1,2,2,SWSH) { Species = 595, Ability = A3 }, // Joltik + new(Nest056,1,2,2,SWSH) { Species = 025, Ability = A3 }, // Pikachu + new(Nest056,2,4,4,SWSH) { Species = 025, Ability = A3 }, // Pikachu + new(Nest056,2,4,4,SWSH) { Species = 479, Ability = A3, Form = 5 }, // Rotom-5 + new(Nest056,3,4,4,SWSH) { Species = 479, Ability = A4, Form = 4 }, // Rotom-4 + new(Nest056,3,4,4,SWSH) { Species = 479, Ability = A4, Form = 3 }, // Rotom-3 + new(Nest056,4,4,4,SWSH) { Species = 479, Ability = A4, Form = 2 }, // Rotom-2 + new(Nest056,4,4,4,SWSH) { Species = 479, Ability = A4, Form = 1 }, // Rotom-1 + new(Nest057,0,0,1,SWSH) { Species = 406, Ability = A3 }, // Budew + new(Nest057,0,1,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur + new(Nest057,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed + new(Nest057,1,2,2,SWSH) { Species = 840, Ability = A3 }, // Applin + new(Nest057,2,4,4,SWSH) { Species = 315, Ability = A3 }, // Roselia + new(Nest057,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss + new(Nest057,3,4,4,SWSH) { Species = 598, Ability = A4 }, // Ferrothorn + new(Nest057,4,4,4,SWSH) { Species = 407, Ability = A4 }, // Roserade + new(Nest058,0,0,1,SWSH) { Species = 420, Ability = A3 }, // Cherubi + new(Nest058,0,1,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur + new(Nest058,0,1,1,SWSH) { Species = 546, Ability = A3 }, // Cottonee + new(Nest058,1,2,2,SWSH) { Species = 755, Ability = A3 }, // Morelull + new(Nest058,2,4,4,SWSH) { Species = 421, Ability = A3 }, // Cherrim + new(Nest058,2,4,4,SWSH) { Species = 756, Ability = A3 }, // Shiinotic + new(Nest058,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss + new(Nest058,3,4,4,SWSH) { Species = 547, Ability = A4 }, // Whimsicott + new(Nest058,4,4,4,SWSH) { Species = 781, Ability = A4 }, // Dhelmise + new(Nest059,0,0,1,SWSH) { Species = 434, Ability = A3 }, // Stunky + new(Nest059,0,0,1,SWSH) { Species = 568, Ability = A3 }, // Trubbish + new(Nest059,0,1,1,SWSH) { Species = 451, Ability = A3 }, // Skorupi + new(Nest059,0,1,1,SWSH) { Species = 109, Ability = A3 }, // Koffing + new(Nest059,1,2,2,SWSH) { Species = 848, Ability = A3 }, // Toxel + new(Nest059,2,4,4,SWSH) { Species = 569, Ability = A3 }, // Garbodor + new(Nest059,2,4,4,SWSH) { Species = 452, Ability = A3 }, // Drapion + new(Nest059,3,4,4,SWSH) { Species = 849, Ability = A4 }, // Toxtricity + new(Nest059,3,4,4,SWSH) { Species = 435, Ability = A4 }, // Skuntank + new(Nest059,4,4,4,SWSH) { Species = 110, Ability = A4, Form = 1 }, // Weezing-1 + new(Nest060,0,0,1,SWSH) { Species = 177, Ability = A3 }, // Natu + new(Nest060,0,0,1,SWSH) { Species = 163, Ability = A3 }, // Hoothoot + new(Nest060,0,1,1,SWSH) { Species = 821, Ability = A3 }, // Rookidee + new(Nest060,0,1,1,SWSH) { Species = 278, Ability = A3 }, // Wingull + new(Nest060,1,2,2,SWSH) { Species = 012, Ability = A3 }, // Butterfree + new(Nest060,1,2,2,SWSH) { Species = 822, Ability = A3 }, // Corvisquire + new(Nest060,2,4,4,SWSH) { Species = 164, Ability = A3 }, // Noctowl + new(Nest060,2,4,4,SWSH) { Species = 279, Ability = A3 }, // Pelipper + new(Nest060,3,4,4,SWSH) { Species = 178, Ability = A4 }, // Xatu + new(Nest060,3,4,4,SWSH) { Species = 701, Ability = A4 }, // Hawlucha + new(Nest060,4,4,4,SWSH) { Species = 823, Ability = A4 }, // Corviknight + new(Nest060,4,4,4,SWSH) { Species = 225, Ability = A4 }, // Delibird + new(Nest061,0,0,1,SWSH) { Species = 175, Ability = A3 }, // Togepi + new(Nest061,0,0,1,SWSH) { Species = 755, Ability = A3 }, // Morelull + new(Nest061,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp + new(Nest061,0,1,1,SWSH) { Species = 280, Ability = A3 }, // Ralts + new(Nest061,1,2,2,SWSH) { Species = 176, Ability = A3 }, // Togetic + new(Nest061,1,2,2,SWSH) { Species = 756, Ability = A3 }, // Shiinotic + new(Nest061,2,4,4,SWSH) { Species = 860, Ability = A3 }, // Morgrem + new(Nest061,3,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir + new(Nest061,3,4,4,SWSH) { Species = 468, Ability = A4 }, // Togekiss + new(Nest061,4,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl + new(Nest061,4,4,4,SWSH) { Species = 778, Ability = A4 }, // Mimikyu + new(Nest062,0,0,1,SWSH) { Species = 827, Ability = A3 }, // Nickit + new(Nest062,0,0,1,SWSH) { Species = 263, Ability = A3, Form = 1 }, // Zigzagoon-1 + new(Nest062,0,1,1,SWSH) { Species = 215, Ability = A3 }, // Sneasel + new(Nest062,1,2,2,SWSH) { Species = 510, Ability = A3 }, // Liepard + new(Nest062,1,2,2,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 + new(Nest062,2,4,4,SWSH) { Species = 828, Ability = A3 }, // Thievul + new(Nest062,2,4,4,SWSH) { Species = 675, Ability = A3 }, // Pangoro + new(Nest062,3,4,4,SWSH) { Species = 461, Ability = A4 }, // Weavile + new(Nest062,4,4,4,SWSH) { Species = 862, Ability = A4 }, // Obstagoon + new(Nest063,0,0,1,SWSH) { Species = 840, Ability = A3 }, // Applin + new(Nest063,1,2,2,SWSH) { Species = 885, Ability = A3 }, // Dreepy + new(Nest063,3,4,4,SWSH) { Species = 886, Ability = A4 }, // Drakloak + new(Nest063,4,4,4,SWSH) { Species = 887, Ability = A4 }, // Dragapult + new(Nest064,0,0,1,SWSH) { Species = 659, Ability = A3 }, // Bunnelby + new(Nest064,0,0,1,SWSH) { Species = 519, Ability = A3 }, // Pidove + new(Nest064,0,1,1,SWSH) { Species = 819, Ability = A3 }, // Skwovet + new(Nest064,0,1,1,SWSH) { Species = 133, Ability = A3 }, // Eevee + new(Nest064,1,2,2,SWSH) { Species = 520, Ability = A3 }, // Tranquill + new(Nest064,1,2,2,SWSH) { Species = 831, Ability = A3 }, // Wooloo + new(Nest064,2,4,4,SWSH) { Species = 521, Ability = A3 }, // Unfezant + new(Nest064,2,4,4,SWSH) { Species = 832, Ability = A3 }, // Dubwool + new(Nest064,4,4,4,SWSH) { Species = 133, Ability = A4 }, // Eevee + new(Nest064,4,4,4,SWSH) { Species = 143, Ability = A4 }, // Snorlax + new(Nest065,0,0,1,SWSH) { Species = 132, Ability = A3 }, // Ditto + new(Nest065,0,1,2,SWSH) { Species = 132, Ability = A3 }, // Ditto + new(Nest065,1,2,3,SWSH) { Species = 132, Ability = A3 }, // Ditto + new(Nest065,2,3,3,SWSH) { Species = 132, Ability = A3 }, // Ditto + new(Nest065,3,4,4,SWSH) { Species = 132, Ability = A4 }, // Ditto + new(Nest065,4,4,4,SWSH) { Species = 132, Ability = A4 }, // Ditto + //new(Nest066,0,0,1,SWSH) { Species = 458, Ability = A3 }, // Mantyke + //new(Nest066,0,0,1,SWSH) { Species = 341, Ability = A3 }, // Corphish + //new(Nest066,0,1,1,SWSH) { Species = 846, Ability = A3 }, // Arrokuda + //new(Nest066,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle + new(Nest066,2,2,2,SWSH) { Species = 747, Ability = A3 }, // Mareanie + new(Nest066,2,3,3,SWSH) { Species = 342, Ability = A3 }, // Crawdaunt + new(Nest066,2,4,4,SWSH) { Species = 748, Ability = A3 }, // Toxapex + new(Nest066,3,4,4,SWSH) { Species = 771, Ability = A4 }, // Pyukumuku + new(Nest066,3,4,4,SWSH) { Species = 226, Ability = A4 }, // Mantine + new(Nest066,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras + new(Nest066,4,4,4,SWSH) { Species = 134, Ability = A4 }, // Vaporeon + new(Nest067,0,0,1,SWSH) { Species = 686, Ability = A3 }, // Inkay + new(Nest067,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor + new(Nest067,0,1,1,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 + new(Nest067,0,1,1,SWSH) { Species = 527, Ability = A3 }, // Woobat + new(Nest067,1,2,2,SWSH) { Species = 856, Ability = A3 }, // Hatenna + new(Nest067,1,2,2,SWSH) { Species = 857, Ability = A3 }, // Hattrem + new(Nest067,2,3,3,SWSH) { Species = 437, Ability = A3 }, // Bronzong + new(Nest067,2,4,4,SWSH) { Species = 528, Ability = A3 }, // Swoobat + new(Nest067,3,4,4,SWSH) { Species = 687, Ability = A4 }, // Malamar + new(Nest067,3,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime + new(Nest067,4,4,4,SWSH) { Species = 858, Ability = A4 }, // Hatterene + new(Nest067,4,4,4,SWSH) { Species = 196, Ability = A4 }, // Espeon + new(Nest068,0,0,1,SWSH) { Species = 827, Ability = A3 }, // Nickit + new(Nest068,0,0,1,SWSH) { Species = 263, Ability = A3, Form = 1 }, // Zigzagoon-1 + new(Nest068,0,1,1,SWSH) { Species = 686, Ability = A3 }, // Inkay + new(Nest068,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard + new(Nest068,1,2,2,SWSH) { Species = 510, Ability = A3 }, // Liepard + new(Nest068,1,2,2,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 + new(Nest068,2,4,4,SWSH) { Species = 828, Ability = A3 }, // Thievul + new(Nest068,2,4,4,SWSH) { Species = 675, Ability = A3 }, // Pangoro + new(Nest068,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp + new(Nest068,3,4,4,SWSH) { Species = 687, Ability = A4 }, // Malamar + new(Nest068,4,4,4,SWSH) { Species = 862, Ability = A4 }, // Obstagoon + new(Nest068,4,4,4,SWSH) { Species = 197, Ability = A4 }, // Umbreon + new(Nest069,0,0,1,SWSH) { Species = 420, Ability = A3 }, // Cherubi + new(Nest069,0,0,1,SWSH) { Species = 761, Ability = A3 }, // Bounsweet + new(Nest069,0,1,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur + new(Nest069,0,1,1,SWSH) { Species = 546, Ability = A3 }, // Cottonee + new(Nest069,1,2,2,SWSH) { Species = 762, Ability = A3 }, // Steenee + new(Nest069,1,2,2,SWSH) { Species = 597, Ability = A3 }, // Ferroseed + new(Nest069,2,4,4,SWSH) { Species = 421, Ability = A3 }, // Cherrim + new(Nest069,2,4,4,SWSH) { Species = 598, Ability = A3 }, // Ferrothorn + new(Nest069,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss + new(Nest069,3,4,4,SWSH) { Species = 763, Ability = A4 }, // Tsareena + new(Nest069,4,4,4,SWSH) { Species = 547, Ability = A4 }, // Whimsicott + new(Nest069,4,4,4,SWSH) { Species = 470, Ability = A4 }, // Leafeon + new(Nest070,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede + new(Nest070,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick + new(Nest070,1,2,2,SWSH) { Species = 838, Ability = A3 }, // Carkol + new(Nest070,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal + new(Nest070,3,4,4,SWSH) { Species = 059, Ability = A4 }, // Arcanine + new(Nest070,3,4,4,SWSH) { Species = 038, Ability = A4 }, // Ninetales + new(Nest070,4,4,4,SWSH) { Species = 609, Ability = A4 }, // Chandelure + new(Nest070,4,4,4,SWSH) { Species = 136, Ability = A4 }, // Flareon + new(Nest071,0,0,1,SWSH) { Species = 835, Ability = A3 }, // Yamper + new(Nest071,0,0,1,SWSH) { Species = 848, Ability = A3 }, // Toxel + new(Nest071,0,1,1,SWSH) { Species = 025, Ability = A3 }, // Pikachu + new(Nest071,0,1,1,SWSH) { Species = 694, Ability = A3 }, // Helioptile + new(Nest071,1,2,2,SWSH) { Species = 170, Ability = A3 }, // Chinchou + new(Nest071,1,2,2,SWSH) { Species = 171, Ability = A3 }, // Lanturn + new(Nest071,2,4,4,SWSH) { Species = 836, Ability = A3 }, // Boltund + new(Nest071,2,4,4,SWSH) { Species = 849, Ability = A3 }, // Toxtricity + new(Nest071,3,4,4,SWSH) { Species = 695, Ability = A4 }, // Heliolisk + new(Nest071,3,4,4,SWSH) { Species = 738, Ability = A4 }, // Vikavolt + new(Nest071,4,4,4,SWSH) { Species = 025, Ability = A4 }, // Pikachu + new(Nest071,4,4,4,SWSH) { Species = 135, Ability = A4 }, // Jolteon + new(Nest072,0,0,1,SWSH) { Species = 582, Ability = A3 }, // Vanillite + new(Nest072,0,0,1,SWSH) { Species = 872, Ability = A3 }, // Snom + new(Nest072,0,1,1,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 + new(Nest072,0,1,1,SWSH) { Species = 712, Ability = A3 }, // Bergmite + new(Nest072,1,2,2,SWSH) { Species = 361, Ability = A3 }, // Snorunt + new(Nest072,1,2,2,SWSH) { Species = 583, Ability = A3 }, // Vanillish + new(Nest072,2,3,3,SWSH) { Species = 713, Ability = A3 }, // Avalugg + new(Nest072,2,4,4,SWSH) { Species = 873, Ability = A3 }, // Frosmoth + new(Nest072,3,4,4,SWSH) { Species = 584, Ability = A4 }, // Vanilluxe + new(Nest072,3,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime + new(Nest072,4,4,4,SWSH) { Species = 478, Ability = A4 }, // Froslass + new(Nest072,4,4,4,SWSH) { Species = 471, Ability = A4 }, // Glaceon + //new(Nest073,0,0,1,SWSH) { Species = 175, Ability = A3 }, // Togepi + //new(Nest073,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp + //new(Nest073,0,1,1,SWSH) { Species = 280, Ability = A3 }, // Ralts + new(Nest073,2,2,2,SWSH) { Species = 176, Ability = A3 }, // Togetic + new(Nest073,2,2,2,SWSH) { Species = 860, Ability = A3 }, // Morgrem + new(Nest073,2,4,4,SWSH) { Species = 868, Ability = A3 }, // Milcery + new(Nest073,3,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir + new(Nest073,3,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl + new(Nest073,4,4,4,SWSH) { Species = 468, Ability = A4 }, // Togekiss + new(Nest073,4,4,4,SWSH) { Species = 700, Ability = A4 }, // Sylveon + new(Nest074,0,0,1,SWSH) { Species = 129, Ability = A3 }, // Magikarp + new(Nest074,0,0,1,SWSH) { Species = 751, Ability = A3 }, // Dewpider + new(Nest074,0,1,1,SWSH) { Species = 194, Ability = A3 }, // Wooper + new(Nest074,0,1,1,SWSH) { Species = 339, Ability = A3 }, // Barboach + new(Nest074,1,2,2,SWSH) { Species = 098, Ability = A3 }, // Krabby + new(Nest074,1,2,2,SWSH) { Species = 746, Ability = A3 }, // Wishiwashi + new(Nest074,2,3,3,SWSH) { Species = 099, Ability = A3 }, // Kingler + new(Nest074,2,4,4,SWSH) { Species = 340, Ability = A3 }, // Whiscash + new(Nest074,3,4,4,SWSH) { Species = 211, Ability = A4 }, // Qwilfish + new(Nest074,3,4,4,SWSH) { Species = 195, Ability = A4 }, // Quagsire + new(Nest074,4,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid + new(Nest074,4,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados + //new(Nest075,0,0,1,SWSH) { Species = 458, Ability = A3 }, // Mantyke + //new(Nest075,0,0,1,SWSH) { Species = 223, Ability = A3 }, // Remoraid + //new(Nest075,0,1,1,SWSH) { Species = 320, Ability = A3 }, // Wailmer + //new(Nest075,0,1,1,SWSH) { Species = 688, Ability = A3 }, // Binacle + new(Nest075,2,2,2,SWSH) { Species = 098, Ability = A3 }, // Krabby + new(Nest075,2,2,2,SWSH) { Species = 771, Ability = A3 }, // Pyukumuku + new(Nest075,2,3,3,SWSH) { Species = 099, Ability = A3 }, // Kingler + new(Nest075,3,4,4,SWSH) { Species = 211, Ability = A4 }, // Qwilfish + new(Nest075,3,4,4,SWSH) { Species = 224, Ability = A4 }, // Octillery + new(Nest075,4,4,4,SWSH) { Species = 321, Ability = A4 }, // Wailord + new(Nest075,4,4,4,SWSH) { Species = 226, Ability = A4 }, // Mantine + //new(Nest076,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede + //new(Nest076,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick + //new(Nest076,0,1,1,SWSH) { Species = 004, Ability = A3 }, // Charmander + new(Nest076,2,2,2,SWSH) { Species = 005, Ability = A3 }, // Charmeleon + new(Nest076,2,3,3,SWSH) { Species = 631, Ability = A3 }, // Heatmor + new(Nest076,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal + new(Nest076,3,4,4,SWSH) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle + new(Nest076,4,4,4,SWSH) { Species = 851, Ability = A4 }, // Centiskorch + new(Nest076,4,4,4,SWSH) { Species = 006, Ability = A4, CanGigantamax = true }, // Charizard + new(Nest077,0,0,1,SWSH) { Species = 129, Ability = A3 }, // Magikarp + new(Nest077,0,0,1,SWSH) { Species = 846, Ability = A3 }, // Arrokuda + new(Nest077,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle + new(Nest077,0,1,1,SWSH) { Species = 098, Ability = A3 }, // Krabby + new(Nest077,1,2,2,SWSH) { Species = 771, Ability = A3 }, // Pyukumuku + new(Nest077,2,3,3,SWSH) { Species = 211, Ability = A3 }, // Qwilfish + new(Nest077,2,4,4,SWSH) { Species = 099, Ability = A3 }, // Kingler + new(Nest077,3,4,4,SWSH) { Species = 746, Ability = A4 }, // Wishiwashi + new(Nest077,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados + new(Nest077,4,4,4,SWSH) { Species = 423, Ability = A4, Form = 1 }, // Gastrodon-1 + new(Nest077,4,4,4,SWSH) { Species = 834, Ability = A4, CanGigantamax = true }, // Drednaw + new(Nest078,0,0,1,SWSH) { Species = 406, Ability = A3 }, // Budew + new(Nest078,0,1,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur + new(Nest078,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed + new(Nest078,1,2,2,SWSH) { Species = 840, Ability = A3 }, // Applin + new(Nest078,2,4,4,SWSH) { Species = 315, Ability = A3 }, // Roselia + new(Nest078,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss + new(Nest078,3,4,4,SWSH) { Species = 598, Ability = A4 }, // Ferrothorn + new(Nest078,4,4,4,SWSH) { Species = 407, Ability = A4 }, // Roserade + new(Nest079,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede + new(Nest079,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick + new(Nest079,0,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit + new(Nest079,1,2,2,SWSH) { Species = 838, Ability = A3 }, // Carkol + new(Nest079,1,2,2,SWSH) { Species = 608, Ability = A3 }, // Lampent + new(Nest079,2,3,3,SWSH) { Species = 631, Ability = A3 }, // Heatmor + new(Nest079,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal + new(Nest079,3,4,4,SWSH) { Species = 609, Ability = A4 }, // Chandelure + new(Nest079,4,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal + new(Nest079,4,4,4,SWSH) { Species = 851, Ability = A4, CanGigantamax = true }, // Centiskorch + new(Nest081,0,0,1,SWSH) { Species = 175, Ability = A3 }, // Togepi + new(Nest081,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp + new(Nest081,0,1,1,SWSH) { Species = 280, Ability = A3 }, // Ralts + new(Nest081,1,2,2,SWSH) { Species = 176, Ability = A3 }, // Togetic + new(Nest081,1,2,2,SWSH) { Species = 756, Ability = A3 }, // Shiinotic + new(Nest081,2,3,3,SWSH) { Species = 860, Ability = A3 }, // Morgrem + new(Nest081,3,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir + new(Nest081,3,4,4,SWSH) { Species = 468, Ability = A4 }, // Togekiss + new(Nest081,4,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl + new(Nest081,4,4,4,SWSH) { Species = 869, Ability = A4, CanGigantamax = true }, // Alcremie + new(Nest083,0,0,1,SWSH) { Species = 447, Ability = A3 }, // Riolu + new(Nest083,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor + new(Nest083,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard + new(Nest083,0,1,1,SWSH) { Species = 599, Ability = A3 }, // Klink + new(Nest083,1,2,2,SWSH) { Species = 095, Ability = A3 }, // Onix + new(Nest083,2,4,4,SWSH) { Species = 437, Ability = A3 }, // Bronzong + new(Nest083,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp + new(Nest083,3,4,4,SWSH) { Species = 208, Ability = A4 }, // Steelix + new(Nest083,4,4,4,SWSH) { Species = 601, Ability = A4 }, // Klinklang + new(Nest083,4,4,4,SWSH) { Species = 884, Ability = A4, CanGigantamax = true }, // Duraludon + new(Nest084,0,0,1,SWSH) { Species = 052, Ability = A3, Form = 2 }, // Meowth-2 + new(Nest084,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor + new(Nest084,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard + new(Nest084,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed + new(Nest084,1,2,2,SWSH) { Species = 679, Ability = A3 }, // Honedge + new(Nest084,1,2,2,SWSH) { Species = 437, Ability = A3 }, // Bronzong + new(Nest084,2,3,3,SWSH) { Species = 863, Ability = A3 }, // Perrserker + new(Nest084,2,4,4,SWSH) { Species = 598, Ability = A3 }, // Ferrothorn + new(Nest084,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp + new(Nest084,3,4,4,SWSH) { Species = 618, Ability = A4, Form = 1 }, // Stunfisk-1 + new(Nest084,4,4,4,SWSH) { Species = 884, Ability = A4 }, // Duraludon + new(Nest084,4,4,4,SWSH) { Species = 879, Ability = A4, CanGigantamax = true }, // Copperajah + new(Nest085,0,0,1,SWSH) { Species = 434, Ability = A3 }, // Stunky + new(Nest085,0,0,1,SWSH) { Species = 568, Ability = A3 }, // Trubbish + new(Nest085,0,1,1,SWSH) { Species = 451, Ability = A3 }, // Skorupi + new(Nest085,0,1,1,SWSH) { Species = 109, Ability = A3 }, // Koffing + new(Nest085,1,2,2,SWSH) { Species = 848, Ability = A3 }, // Toxel + new(Nest085,2,3,3,SWSH) { Species = 452, Ability = A3 }, // Drapion + new(Nest085,2,4,4,SWSH) { Species = 849, Ability = A3 }, // Toxtricity + new(Nest085,3,4,4,SWSH) { Species = 435, Ability = A4 }, // Skuntank + new(Nest085,3,4,4,SWSH) { Species = 110, Ability = A4, Form = 1 }, // Weezing-1 + new(Nest085,4,4,4,SWSH) { Species = 569, Ability = A4, CanGigantamax = true }, // Garbodor + new(Nest086,0,0,1,SWSH) { Species = 175, Ability = A3 }, // Togepi + new(Nest086,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp + new(Nest086,0,1,1,SWSH) { Species = 280, Ability = A3 }, // Ralts + new(Nest086,1,2,2,SWSH) { Species = 176, Ability = A3 }, // Togetic + new(Nest086,1,2,2,SWSH) { Species = 860, Ability = A3 }, // Morgrem + new(Nest086,2,4,4,SWSH) { Species = 868, Ability = A3 }, // Milcery + new(Nest086,3,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir + new(Nest086,3,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl + new(Nest086,4,4,4,SWSH) { Species = 468, Ability = A4 }, // Togekiss + new(Nest086,4,4,4,SWSH) { Species = 858, Ability = A4, CanGigantamax = true }, // Hatterene + new(Nest087,0,0,1,SWSH) { Species = 827, Ability = A3 }, // Nickit + new(Nest087,0,0,1,SWSH) { Species = 263, Ability = A3, Form = 1 }, // Zigzagoon-1 + new(Nest087,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp + new(Nest087,1,2,2,SWSH) { Species = 510, Ability = A3 }, // Liepard + new(Nest087,1,2,2,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 + new(Nest087,2,3,3,SWSH) { Species = 860, Ability = A3 }, // Morgrem + new(Nest087,2,4,4,SWSH) { Species = 828, Ability = A3 }, // Thievul + new(Nest087,3,4,4,SWSH) { Species = 675, Ability = A4 }, // Pangoro + new(Nest087,4,4,4,SWSH) { Species = 861, Ability = A4, CanGigantamax = true }, // Grimmsnarl + new(Nest088,0,0,1,SWSH) { Species = 177, Ability = A3 }, // Natu + new(Nest088,0,0,1,SWSH) { Species = 163, Ability = A3 }, // Hoothoot + new(Nest088,0,1,1,SWSH) { Species = 821, Ability = A3 }, // Rookidee + new(Nest088,0,1,1,SWSH) { Species = 278, Ability = A3 }, // Wingull + new(Nest088,1,2,2,SWSH) { Species = 012, Ability = A3 }, // Butterfree + new(Nest088,1,2,2,SWSH) { Species = 822, Ability = A3 }, // Corvisquire + new(Nest088,2,3,3,SWSH) { Species = 164, Ability = A3 }, // Noctowl + new(Nest088,2,4,4,SWSH) { Species = 279, Ability = A3 }, // Pelipper + new(Nest088,3,4,4,SWSH) { Species = 178, Ability = A4 }, // Xatu + new(Nest088,3,4,4,SWSH) { Species = 701, Ability = A4 }, // Hawlucha + new(Nest088,4,4,4,SWSH) { Species = 561, Ability = A4 }, // Sigilyph + new(Nest088,4,4,4,SWSH) { Species = 823, Ability = A4, CanGigantamax = true }, // Corviknight + new(Nest089,0,0,1,SWSH) { Species = 767, Ability = A3 }, // Wimpod + new(Nest089,0,0,1,SWSH) { Species = 824, Ability = A3 }, // Blipbug + new(Nest089,0,1,1,SWSH) { Species = 751, Ability = A3 }, // Dewpider + new(Nest089,1,2,2,SWSH) { Species = 557, Ability = A3 }, // Dwebble + new(Nest089,2,3,3,SWSH) { Species = 825, Ability = A3 }, // Dottler + new(Nest089,2,4,4,SWSH) { Species = 826, Ability = A3 }, // Orbeetle + new(Nest089,3,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid + new(Nest089,3,4,4,SWSH) { Species = 768, Ability = A4 }, // Golisopod + new(Nest089,0,4,4,SWSH) { Species = 012, Ability = A4, CanGigantamax = true }, // Butterfree + new(Nest090,0,0,1,SWSH) { Species = 341, Ability = A3 }, // Corphish + new(Nest090,0,0,1,SWSH) { Species = 098, Ability = A3 }, // Krabby + new(Nest090,0,1,1,SWSH) { Species = 846, Ability = A3 }, // Arrokuda + new(Nest090,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle + new(Nest090,1,2,2,SWSH) { Species = 747, Ability = A3 }, // Mareanie + new(Nest090,2,3,3,SWSH) { Species = 342, Ability = A3 }, // Crawdaunt + new(Nest090,2,4,4,SWSH) { Species = 748, Ability = A3 }, // Toxapex + new(Nest090,3,4,4,SWSH) { Species = 771, Ability = A4 }, // Pyukumuku + new(Nest090,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados + new(Nest090,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras + new(Nest090,1,4,4,SWSH) { Species = 099, Ability = A4, CanGigantamax = true }, // Kingler + new(Nest091,0,0,1,SWSH) { Species = 767, Ability = A3 }, // Wimpod + new(Nest091,0,0,1,SWSH) { Species = 824, Ability = A3 }, // Blipbug + new(Nest091,0,1,1,SWSH) { Species = 751, Ability = A3 }, // Dewpider + new(Nest091,1,2,2,SWSH) { Species = 557, Ability = A3 }, // Dwebble + new(Nest091,2,3,3,SWSH) { Species = 825, Ability = A3 }, // Dottler + new(Nest091,2,4,4,SWSH) { Species = 826, Ability = A3 }, // Orbeetle + new(Nest091,3,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid + new(Nest091,3,4,4,SWSH) { Species = 768, Ability = A4 }, // Golisopod + new(Nest091,2,4,4,SWSH) { Species = 826, Ability = A4, CanGigantamax = true }, // Orbeetle + new(Nest092,0,0,1,SWSH) { Species = 194, Ability = A3 }, // Wooper + new(Nest092,0,0,1,SWSH) { Species = 339, Ability = A3 }, // Barboach + new(Nest092,0,1,1,SWSH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 + new(Nest092,0,1,1,SWSH) { Species = 622, Ability = A3 }, // Golett + new(Nest092,1,2,2,SWSH) { Species = 536, Ability = A3 }, // Palpitoad + new(Nest092,1,2,2,SWSH) { Species = 195, Ability = A3 }, // Quagsire + new(Nest092,2,3,3,SWSH) { Species = 618, Ability = A3, Form = 1 }, // Stunfisk-1 + new(Nest092,2,4,4,SWSH) { Species = 623, Ability = A3 }, // Golurk + new(Nest092,3,4,4,SWSH) { Species = 423, Ability = A4, Form = 1 }, // Gastrodon-1 + new(Nest092,3,4,4,SWSH) { Species = 537, Ability = A4 }, // Seismitoad + new(Nest092,4,4,4,SWSH) { Species = 464, Ability = A4 }, // Rhyperior + new(Nest092,3,4,4,SWSH) { Species = 844, Ability = A4, CanGigantamax = true }, // Sandaconda + new(Nest098,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest098,0,1,1,SWSH) { Species = 174, Ability = A3 }, // Igglybuff + new(Nest098,0,1,1,SWSH) { Species = 506, Ability = A3 }, // Lillipup + new(Nest098,1,2,2,SWSH) { Species = 427, Ability = A3 }, // Buneary + new(Nest098,1,2,2,SWSH) { Species = 039, Ability = A3 }, // Jigglypuff + new(Nest098,2,3,3,SWSH) { Species = 039, Ability = A3 }, // Jigglypuff + new(Nest098,2,3,3,SWSH) { Species = 507, Ability = A3 }, // Herdier + new(Nest098,3,4,4,SWSH) { Species = 428, Ability = A4 }, // Lopunny + new(Nest098,3,4,4,SWSH) { Species = 040, Ability = A4 }, // Wigglytuff + new(Nest098,4,4,4,SWSH) { Species = 206, Ability = A4 }, // Dunsparce + new(Nest098,4,4,4,SWSH) { Species = 508, Ability = A4 }, // Stoutland + new(Nest099,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest099,0,1,1,SWSH) { Species = 506, Ability = A2 }, // Lillipup + new(Nest099,0,1,1,SWSH) { Species = 759, Ability = A2 }, // Stufful + new(Nest099,1,2,2,SWSH) { Species = 039, Ability = A2 }, // Jigglypuff + new(Nest099,1,2,2,SWSH) { Species = 427, Ability = A2 }, // Buneary + new(Nest099,2,3,3,SWSH) { Species = 039, Ability = A2 }, // Jigglypuff + new(Nest099,2,3,3,SWSH) { Species = 206, Ability = A2 }, // Dunsparce + new(Nest099,3,4,4,SWSH) { Species = 832, Ability = A2 }, // Dubwool + new(Nest099,3,4,4,SWSH) { Species = 428, Ability = A2 }, // Lopunny + new(Nest099,3,4,4,SWSH) { Species = 508, Ability = A2 }, // Stoutland + new(Nest099,4,4,4,SWSH) { Species = 760, Ability = A2 }, // Bewear + new(Nest099,4,4,4,SWSH) { Species = 040, Ability = A2 }, // Wigglytuff + new(Nest100,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest100,0,1,1,SWSH) { Species = 293, Ability = A3 }, // Whismur + new(Nest100,0,1,1,SWSH) { Species = 108, Ability = A3 }, // Lickitung + new(Nest100,1,2,2,SWSH) { Species = 241, Ability = A3 }, // Miltank + new(Nest100,1,2,2,SWSH) { Species = 294, Ability = A3 }, // Loudred + new(Nest100,2,3,3,SWSH) { Species = 294, Ability = A3 }, // Loudred + new(Nest100,2,3,3,SWSH) { Species = 108, Ability = A3 }, // Lickitung + new(Nest100,3,4,4,SWSH) { Species = 241, Ability = A4 }, // Miltank + new(Nest100,3,4,4,SWSH) { Species = 626, Ability = A4 }, // Bouffalant + new(Nest100,3,4,4,SWSH) { Species = 128, Ability = A4 }, // Tauros + new(Nest100,4,4,4,SWSH) { Species = 295, Ability = A4 }, // Exploud + new(Nest100,4,4,4,SWSH) { Species = 463, Ability = A4 }, // Lickilicky + new(Nest101,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest101,0,1,1,SWSH) { Species = 293, Ability = A2 }, // Whismur + new(Nest101,0,1,1,SWSH) { Species = 128, Ability = A2 }, // Tauros + new(Nest101,1,2,2,SWSH) { Species = 108, Ability = A2 }, // Lickitung + new(Nest101,1,2,2,SWSH) { Species = 241, Ability = A2 }, // Miltank + new(Nest101,2,3,3,SWSH) { Species = 241, Ability = A2 }, // Miltank + new(Nest101,2,3,3,SWSH) { Species = 626, Ability = A2 }, // Bouffalant + new(Nest101,3,4,4,SWSH) { Species = 128, Ability = A2 }, // Tauros + new(Nest101,3,4,4,SWSH) { Species = 295, Ability = A2 }, // Exploud + new(Nest101,3,4,4,SWSH) { Species = 573, Ability = A2 }, // Cinccino + new(Nest101,4,4,4,SWSH) { Species = 295, Ability = A2 }, // Exploud + new(Nest101,4,4,4,SWSH) { Species = 463, Ability = A2 }, // Lickilicky + new(Nest102,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest102,0,1,1,SWSH) { Species = 027, Ability = A3 }, // Sandshrew + new(Nest102,0,1,1,SWSH) { Species = 551, Ability = A3 }, // Sandile + new(Nest102,1,2,2,SWSH) { Species = 104, Ability = A3 }, // Cubone + new(Nest102,1,2,2,SWSH) { Species = 027, Ability = A3 }, // Sandshrew + new(Nest102,2,3,3,SWSH) { Species = 552, Ability = A3 }, // Krokorok + new(Nest102,2,3,3,SWSH) { Species = 028, Ability = A3 }, // Sandslash + new(Nest102,3,4,4,SWSH) { Species = 844, Ability = A4 }, // Sandaconda + new(Nest102,3,4,4,SWSH) { Species = 028, Ability = A4 }, // Sandslash + new(Nest102,3,4,4,SWSH) { Species = 105, Ability = A4 }, // Marowak + new(Nest102,4,4,4,SWSH) { Species = 553, Ability = A4 }, // Krookodile + new(Nest102,4,4,4,SWSH) { Species = 115, Ability = A4 }, // Kangaskhan + new(Nest103,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest103,0,1,1,SWSH) { Species = 027, Ability = A2 }, // Sandshrew + new(Nest103,0,1,1,SWSH) { Species = 104, Ability = A2 }, // Cubone + new(Nest103,1,2,2,SWSH) { Species = 328, Ability = A2 }, // Trapinch + new(Nest103,2,3,3,SWSH) { Species = 552, Ability = A2 }, // Krokorok + new(Nest103,2,3,3,SWSH) { Species = 028, Ability = A2 }, // Sandslash + new(Nest103,3,4,4,SWSH) { Species = 105, Ability = A2 }, // Marowak + new(Nest103,3,4,4,SWSH) { Species = 553, Ability = A2 }, // Krookodile + new(Nest103,3,4,4,SWSH) { Species = 115, Ability = A2 }, // Kangaskhan + new(Nest103,4,4,4,SWSH) { Species = 330, Ability = A2 }, // Flygon + new(Nest103,4,4,4,SWSH) { Species = 623, Ability = A2 }, // Golurk + new(Nest104,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest104,0,1,1,SWSH) { Species = 702, Ability = A3 }, // Dedenne + new(Nest104,0,1,1,SWSH) { Species = 081, Ability = A3 }, // Magnemite + new(Nest104,1,2,2,SWSH) { Species = 403, Ability = A3 }, // Shinx + new(Nest104,1,2,2,SWSH) { Species = 877, Ability = A3 }, // Morpeko + new(Nest104,2,3,3,SWSH) { Species = 702, Ability = A3 }, // Dedenne + new(Nest104,2,3,3,SWSH) { Species = 404, Ability = A3 }, // Luxio + new(Nest104,3,4,4,SWSH) { Species = 702, Ability = A4 }, // Dedenne + new(Nest104,3,4,4,SWSH) { Species = 082, Ability = A4 }, // Magneton + new(Nest104,3,4,4,SWSH) { Species = 871, Ability = A4 }, // Pincurchin + new(Nest104,4,4,4,SWSH) { Species = 405, Ability = A4 }, // Luxray + new(Nest104,4,4,4,SWSH) { Species = 462, Ability = A4 }, // Magnezone + new(Nest105,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest105,0,1,1,SWSH) { Species = 403, Ability = A2 }, // Shinx + new(Nest105,0,1,1,SWSH) { Species = 172, Ability = A2 }, // Pichu + new(Nest105,1,2,2,SWSH) { Species = 025, Ability = A2 }, // Pikachu + new(Nest105,1,2,2,SWSH) { Species = 871, Ability = A2 }, // Pincurchin + new(Nest105,2,3,3,SWSH) { Species = 404, Ability = A2 }, // Luxio + new(Nest105,2,3,3,SWSH) { Species = 026, Ability = A2 }, // Raichu + new(Nest105,3,4,4,SWSH) { Species = 836, Ability = A2 }, // Boltund + new(Nest105,3,4,4,SWSH) { Species = 702, Ability = A2 }, // Dedenne + new(Nest105,3,4,4,SWSH) { Species = 310, Ability = A2 }, // Manectric + new(Nest105,4,4,4,SWSH) { Species = 405, Ability = A2 }, // Luxray + new(Nest105,4,4,4,SWSH) { Species = 462, Ability = A2 }, // Magnezone + new(Nest106,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest106,0,1,1,SWSH) { Species = 661, Ability = A3 }, // Fletchling + new(Nest106,0,1,1,SWSH) { Species = 527, Ability = A3 }, // Woobat + new(Nest106,1,2,2,SWSH) { Species = 587, Ability = A3 }, // Emolga + new(Nest106,2,3,3,SWSH) { Species = 662, Ability = A3 }, // Fletchinder + new(Nest106,3,4,4,SWSH) { Species = 587, Ability = A4 }, // Emolga + new(Nest106,3,4,4,SWSH) { Species = 528, Ability = A4 }, // Swoobat + new(Nest106,4,4,4,SWSH) { Species = 663, Ability = A4 }, // Talonflame + new(Nest107,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest107,0,1,1,SWSH) { Species = 163, Ability = A2 }, // Hoothoot + new(Nest107,0,1,1,SWSH) { Species = 519, Ability = A2 }, // Pidove + new(Nest107,1,2,2,SWSH) { Species = 520, Ability = A2 }, // Tranquill + new(Nest107,2,3,3,SWSH) { Species = 528, Ability = A2 }, // Swoobat + new(Nest107,2,3,3,SWSH) { Species = 164, Ability = A2 }, // Noctowl + new(Nest107,3,4,4,SWSH) { Species = 521, Ability = A2 }, // Unfezant + new(Nest107,3,4,4,SWSH) { Species = 663, Ability = A2 }, // Talonflame + new(Nest107,3,4,4,SWSH) { Species = 587, Ability = A2 }, // Emolga + new(Nest107,4,4,4,SWSH) { Species = 663, Ability = A2 }, // Talonflame + new(Nest108,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest108,0,1,1,SWSH) { Species = 557, Ability = A3 }, // Dwebble + new(Nest108,1,2,2,SWSH) { Species = 825, Ability = A3 }, // Dottler + new(Nest108,2,3,3,SWSH) { Species = 558, Ability = A3 }, // Crustle + new(Nest108,3,4,4,SWSH) { Species = 123, Ability = A4 }, // Scyther + new(Nest108,4,4,4,SWSH) { Species = 826, Ability = A4 }, // Orbeetle + new(Nest108,4,4,4,SWSH) { Species = 212, Ability = A4 }, // Scizor + new(Nest109,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest109,0,1,1,SWSH) { Species = 123, Ability = A2 }, // Scyther + new(Nest109,1,2,2,SWSH) { Species = 213, Ability = A2 }, // Shuckle + new(Nest109,1,2,2,SWSH) { Species = 544, Ability = A2 }, // Whirlipede + new(Nest109,2,3,3,SWSH) { Species = 123, Ability = A2 }, // Scyther + new(Nest109,2,3,3,SWSH) { Species = 558, Ability = A2 }, // Crustle + new(Nest109,3,4,4,SWSH) { Species = 545, Ability = A2 }, // Scolipede + new(Nest109,3,4,4,SWSH) { Species = 617, Ability = A2 }, // Accelgor + new(Nest109,3,4,4,SWSH) { Species = 589, Ability = A2 }, // Escavalier + new(Nest109,4,4,4,SWSH) { Species = 212, Ability = A2 }, // Scizor + new(Nest110,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest110,0,1,1,SWSH) { Species = 590, Ability = A3 }, // Foongus + new(Nest110,0,1,1,SWSH) { Species = 753, Ability = A3 }, // Fomantis + new(Nest110,1,2,2,SWSH) { Species = 548, Ability = A3 }, // Petilil + new(Nest110,1,2,2,SWSH) { Species = 754, Ability = A3 }, // Lurantis + new(Nest110,2,3,3,SWSH) { Species = 591, Ability = A3 }, // Amoonguss + new(Nest110,2,3,3,SWSH) { Species = 114, Ability = A3 }, // Tangela + new(Nest110,3,4,4,SWSH) { Species = 549, Ability = A4 }, // Lilligant + new(Nest110,3,4,4,SWSH) { Species = 754, Ability = A4 }, // Lurantis + new(Nest110,4,4,4,SWSH) { Species = 591, Ability = A4 }, // Amoonguss + new(Nest110,4,4,4,SWSH) { Species = 465, Ability = A4 }, // Tangrowth + new(Nest111,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest111,0,1,1,SWSH) { Species = 114, Ability = A2 }, // Tangela + new(Nest111,0,1,1,SWSH) { Species = 753, Ability = A2 }, // Fomantis + new(Nest111,1,2,2,SWSH) { Species = 590, Ability = A2 }, // Foongus + new(Nest111,1,2,2,SWSH) { Species = 754, Ability = A2 }, // Lurantis + new(Nest111,2,3,3,SWSH) { Species = 556, Ability = A2 }, // Maractus + new(Nest111,2,3,3,SWSH) { Species = 549, Ability = A2 }, // Lilligant + new(Nest111,3,4,4,SWSH) { Species = 754, Ability = A2 }, // Lurantis + new(Nest111,3,4,4,SWSH) { Species = 591, Ability = A2 }, // Amoonguss + new(Nest111,3,4,4,SWSH) { Species = 465, Ability = A2 }, // Tangrowth + new(Nest111,4,4,4,SWSH) { Species = 549, Ability = A2 }, // Lilligant + new(Nest111,4,4,4,SWSH) { Species = 460, Ability = A2 }, // Abomasnow + new(Nest112,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest112,0,1,1,SWSH) { Species = 661, Ability = A3 }, // Fletchling + new(Nest112,0,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit + new(Nest112,1,2,2,SWSH) { Species = 636, Ability = A3 }, // Larvesta + new(Nest112,1,2,2,SWSH) { Species = 757, Ability = A3, Gender = 1 }, // Salandit + new(Nest112,2,3,3,SWSH) { Species = 662, Ability = A3 }, // Fletchinder + new(Nest112,2,3,3,SWSH) { Species = 636, Ability = A3 }, // Larvesta + new(Nest112,3,4,4,SWSH) { Species = 324, Ability = A4 }, // Torkoal + new(Nest112,3,4,4,SWSH) { Species = 663, Ability = A4 }, // Talonflame + new(Nest112,3,4,4,SWSH) { Species = 758, Ability = A4 }, // Salazzle + new(Nest112,4,4,4,SWSH) { Species = 324, Ability = A4 }, // Torkoal + new(Nest112,4,4,4,SWSH) { Species = 637, Ability = A4 }, // Volcarona + new(Nest113,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest113,0,1,1,SWSH) { Species = 636, Ability = A2 }, // Larvesta + new(Nest113,0,1,1,SWSH) { Species = 607, Ability = A2 }, // Litwick + new(Nest113,1,2,2,SWSH) { Species = 636, Ability = A2 }, // Larvesta + new(Nest113,1,2,2,SWSH) { Species = 757, Ability = A2, Gender = 1 }, // Salandit + new(Nest113,2,3,3,SWSH) { Species = 324, Ability = A2 }, // Torkoal + new(Nest113,2,3,3,SWSH) { Species = 758, Ability = A2 }, // Salazzle + new(Nest113,3,4,4,SWSH) { Species = 663, Ability = A2 }, // Talonflame + new(Nest113,3,4,4,SWSH) { Species = 609, Ability = A2 }, // Chandelure + new(Nest113,3,4,4,SWSH) { Species = 637, Ability = A2 }, // Volcarona + new(Nest113,4,4,4,SWSH) { Species = 006, Ability = A2 }, // Charizard + new(Nest114,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest114,0,1,1,SWSH) { Species = 524, Ability = A3 }, // Roggenrola + new(Nest114,0,1,1,SWSH) { Species = 111, Ability = A3 }, // Rhyhorn + new(Nest114,1,2,2,SWSH) { Species = 744, Ability = A3 }, // Rockruff + new(Nest114,1,2,2,SWSH) { Species = 525, Ability = A3 }, // Boldore + new(Nest114,2,3,3,SWSH) { Species = 112, Ability = A3 }, // Rhydon + new(Nest114,2,3,3,SWSH) { Species = 558, Ability = A3 }, // Crustle + new(Nest114,3,4,4,SWSH) { Species = 112, Ability = A4 }, // Rhydon + new(Nest114,3,4,4,SWSH) { Species = 526, Ability = A4 }, // Gigalith + new(Nest114,3,4,4,SWSH) { Species = 558, Ability = A4 }, // Crustle + new(Nest114,4,4,4,SWSH) { Species = 464, Ability = A4 }, // Rhyperior + new(Nest115,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest115,0,1,1,SWSH) { Species = 744, Ability = A2 }, // Rockruff + new(Nest115,0,1,1,SWSH) { Species = 438, Ability = A2 }, // Bonsly + new(Nest115,1,2,2,SWSH) { Species = 111, Ability = A2 }, // Rhyhorn + new(Nest115,1,2,2,SWSH) { Species = 744, Ability = A2 }, // Rockruff + new(Nest115,2,3,3,SWSH) { Species = 112, Ability = A2 }, // Rhydon + new(Nest115,2,3,3,SWSH) { Species = 213, Ability = A2 }, // Shuckle + new(Nest115,3,4,4,SWSH) { Species = 185, Ability = A2 }, // Sudowoodo + new(Nest115,3,4,4,SWSH) { Species = 526, Ability = A2 }, // Gigalith + new(Nest115,4,4,4,SWSH) { Species = 558, Ability = A2 }, // Crustle + new(Nest115,4,4,4,SWSH) { Species = 464, Ability = A2 }, // Rhyperior + new(Nest116,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest116,0,1,1,SWSH) { Species = 102, Ability = A3 }, // Exeggcute + new(Nest116,0,1,1,SWSH) { Species = 063, Ability = A3 }, // Abra + new(Nest116,1,2,2,SWSH) { Species = 280, Ability = A3 }, // Ralts + new(Nest116,1,2,2,SWSH) { Species = 064, Ability = A3 }, // Kadabra + new(Nest116,2,3,3,SWSH) { Species = 281, Ability = A3 }, // Kirlia + new(Nest116,3,4,4,SWSH) { Species = 103, Ability = A4 }, // Exeggutor + new(Nest116,3,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir + new(Nest116,4,4,4,SWSH) { Species = 065, Ability = A4 }, // Alakazam + new(Nest116,4,4,4,SWSH) { Species = 121, Ability = A4 }, // Starmie + new(Nest117,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest117,0,1,1,SWSH) { Species = 605, Ability = A2 }, // Elgyem + new(Nest117,0,1,1,SWSH) { Species = 063, Ability = A2 }, // Abra + new(Nest117,1,2,2,SWSH) { Species = 079, Ability = A2, Form = 1 }, // Slowpoke-1 + new(Nest117,1,2,2,SWSH) { Species = 605, Ability = A2 }, // Elgyem + new(Nest117,2,3,3,SWSH) { Species = 079, Ability = A2, Form = 1 }, // Slowpoke-1 + new(Nest117,3,4,4,SWSH) { Species = 518, Ability = A2 }, // Musharna + new(Nest117,3,4,4,SWSH) { Species = 606, Ability = A2 }, // Beheeyem + new(Nest117,4,4,4,SWSH) { Species = 065, Ability = A2 }, // Alakazam + new(Nest118,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest118,0,1,1,SWSH) { Species = 543, Ability = A3 }, // Venipede + new(Nest118,0,1,1,SWSH) { Species = 451, Ability = A3 }, // Skorupi + new(Nest118,1,2,2,SWSH) { Species = 072, Ability = A3 }, // Tentacool + new(Nest118,2,3,3,SWSH) { Species = 544, Ability = A3 }, // Whirlipede + new(Nest118,3,4,4,SWSH) { Species = 452, Ability = A4 }, // Drapion + new(Nest118,3,4,4,SWSH) { Species = 073, Ability = A4 }, // Tentacruel + new(Nest118,4,4,4,SWSH) { Species = 073, Ability = A4 }, // Tentacruel + new(Nest118,4,4,4,SWSH) { Species = 545, Ability = A4 }, // Scolipede + new(Nest119,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest119,0,1,1,SWSH) { Species = 747, Ability = A2 }, // Mareanie + new(Nest119,0,1,1,SWSH) { Species = 211, Ability = A2 }, // Qwilfish + new(Nest119,1,2,2,SWSH) { Species = 544, Ability = A2 }, // Whirlipede + new(Nest119,2,3,3,SWSH) { Species = 211, Ability = A2 }, // Qwilfish + new(Nest119,2,3,3,SWSH) { Species = 591, Ability = A2 }, // Amoonguss + new(Nest119,3,4,4,SWSH) { Species = 748, Ability = A2 }, // Toxapex + new(Nest119,3,4,4,SWSH) { Species = 545, Ability = A2 }, // Scolipede + new(Nest119,3,4,4,SWSH) { Species = 452, Ability = A2 }, // Drapion + new(Nest119,4,4,4,SWSH) { Species = 110, Ability = A2, Form = 1 }, // Weezing-1 + new(Nest119,4,4,4,SWSH) { Species = 545, Ability = A2 }, // Scolipede + new(Nest120,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest120,0,1,1,SWSH) { Species = 318, Ability = A3 }, // Carvanha + new(Nest120,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard + new(Nest120,1,2,2,SWSH) { Species = 318, Ability = A3 }, // Carvanha + new(Nest120,1,2,2,SWSH) { Species = 570, Ability = A3 }, // Zorua + new(Nest120,2,3,3,SWSH) { Species = 319, Ability = A3 }, // Sharpedo + new(Nest120,2,3,3,SWSH) { Species = 687, Ability = A3 }, // Malamar + new(Nest120,3,4,4,SWSH) { Species = 452, Ability = A4 }, // Drapion + new(Nest120,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp + new(Nest120,3,4,4,SWSH) { Species = 687, Ability = A4 }, // Malamar + new(Nest120,4,4,4,SWSH) { Species = 319, Ability = A4 }, // Sharpedo + new(Nest120,4,4,4,SWSH) { Species = 571, Ability = A4 }, // Zoroark + new(Nest121,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest121,0,1,1,SWSH) { Species = 570, Ability = A2 }, // Zorua + new(Nest121,0,1,1,SWSH) { Species = 318, Ability = A2 }, // Carvanha + new(Nest121,1,2,2,SWSH) { Species = 570, Ability = A2 }, // Zorua + new(Nest121,1,2,2,SWSH) { Species = 686, Ability = A2 }, // Inkay + new(Nest121,2,3,3,SWSH) { Species = 552, Ability = A2 }, // Krokorok + new(Nest121,2,3,3,SWSH) { Species = 687, Ability = A2 }, // Malamar + new(Nest121,3,4,4,SWSH) { Species = 828, Ability = A2 }, // Thievul + new(Nest121,3,4,4,SWSH) { Species = 571, Ability = A2 }, // Zoroark + new(Nest121,3,4,4,SWSH) { Species = 319, Ability = A2 }, // Sharpedo + new(Nest121,4,4,4,SWSH) { Species = 510, Ability = A2 }, // Liepard + new(Nest121,4,4,4,SWSH) { Species = 553, Ability = A2 }, // Krookodile + new(Nest122,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest122,0,1,1,SWSH) { Species = 619, Ability = A3 }, // Mienfoo + new(Nest122,0,1,1,SWSH) { Species = 852, Ability = A3 }, // Clobbopus + new(Nest122,1,2,2,SWSH) { Species = 619, Ability = A3 }, // Mienfoo + new(Nest122,3,4,4,SWSH) { Species = 620, Ability = A4 }, // Mienshao + new(Nest122,4,4,4,SWSH) { Species = 853, Ability = A4 }, // Grapploct + new(Nest122,4,4,4,SWSH) { Species = 620, Ability = A4 }, // Mienshao + new(Nest123,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest123,0,1,1,SWSH) { Species = 619, Ability = A2 }, // Mienfoo + new(Nest123,1,2,2,SWSH) { Species = 620, Ability = A2 }, // Mienshao + new(Nest123,2,3,3,SWSH) { Species = 870, Ability = A2 }, // Falinks + new(Nest123,3,4,4,SWSH) { Species = 620, Ability = A2 }, // Mienshao + new(Nest123,4,4,4,SWSH) { Species = 853, Ability = A2 }, // Grapploct + new(Nest124,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest124,0,1,1,SWSH) { Species = 174, Ability = A3 }, // Igglybuff + new(Nest124,0,1,1,SWSH) { Species = 298, Ability = A3 }, // Azurill + new(Nest124,1,2,2,SWSH) { Species = 764, Ability = A3 }, // Comfey + new(Nest124,1,2,2,SWSH) { Species = 039, Ability = A3 }, // Jigglypuff + new(Nest124,2,3,3,SWSH) { Species = 183, Ability = A3 }, // Marill + new(Nest124,2,3,3,SWSH) { Species = 764, Ability = A3 }, // Comfey + new(Nest124,3,4,4,SWSH) { Species = 707, Ability = A4 }, // Klefki + new(Nest124,3,4,4,SWSH) { Species = 184, Ability = A4 }, // Azumarill + new(Nest124,3,4,4,SWSH) { Species = 040, Ability = A4 }, // Wigglytuff + new(Nest124,4,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir + new(Nest124,4,4,4,SWSH) { Species = 764, Ability = A4 }, // Comfey + new(Nest125,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest125,0,1,1,SWSH) { Species = 173, Ability = A2 }, // Cleffa + new(Nest125,0,1,1,SWSH) { Species = 755, Ability = A2 }, // Morelull + new(Nest125,1,2,2,SWSH) { Species = 183, Ability = A2 }, // Marill + new(Nest125,1,2,2,SWSH) { Species = 035, Ability = A2 }, // Clefairy + new(Nest125,2,3,3,SWSH) { Species = 281, Ability = A2 }, // Kirlia + new(Nest125,2,3,3,SWSH) { Species = 707, Ability = A2 }, // Klefki + new(Nest125,3,4,4,SWSH) { Species = 764, Ability = A2 }, // Comfey + new(Nest125,3,4,4,SWSH) { Species = 036, Ability = A2 }, // Clefable + new(Nest125,3,4,4,SWSH) { Species = 282, Ability = A2 }, // Gardevoir + new(Nest125,4,4,4,SWSH) { Species = 756, Ability = A2 }, // Shiinotic + new(Nest125,4,4,4,SWSH) { Species = 184, Ability = A2 }, // Azumarill + new(Nest126,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest126,0,1,1,SWSH) { Species = 769, Ability = A3 }, // Sandygast + new(Nest126,0,1,1,SWSH) { Species = 592, Ability = A3 }, // Frillish + new(Nest126,1,2,2,SWSH) { Species = 104, Ability = A3 }, // Cubone + new(Nest126,1,2,2,SWSH) { Species = 425, Ability = A3 }, // Drifloon + new(Nest126,2,3,3,SWSH) { Species = 593, Ability = A3 }, // Jellicent + new(Nest126,2,3,3,SWSH) { Species = 426, Ability = A3 }, // Drifblim + new(Nest126,3,4,4,SWSH) { Species = 770, Ability = A4 }, // Palossand + new(Nest126,3,4,4,SWSH) { Species = 593, Ability = A4 }, // Jellicent + new(Nest126,3,4,4,SWSH) { Species = 426, Ability = A4 }, // Drifblim + new(Nest126,4,4,4,SWSH) { Species = 105, Ability = A4 }, // Marowak + new(Nest126,4,4,4,SWSH) { Species = 770, Ability = A4 }, // Palossand + new(Nest127,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest127,0,1,1,SWSH) { Species = 769, Ability = A2 }, // Sandygast + new(Nest127,0,1,1,SWSH) { Species = 592, Ability = A2 }, // Frillish + new(Nest127,1,2,2,SWSH) { Species = 769, Ability = A2 }, // Sandygast + new(Nest127,1,2,2,SWSH) { Species = 425, Ability = A2 }, // Drifloon + new(Nest127,2,3,3,SWSH) { Species = 593, Ability = A2 }, // Jellicent + new(Nest127,2,3,3,SWSH) { Species = 426, Ability = A2 }, // Drifblim + new(Nest127,3,4,4,SWSH) { Species = 711, Ability = A2 }, // Gourgeist + new(Nest127,3,4,4,SWSH) { Species = 711, Ability = A2, Form = 1 }, // Gourgeist-1 + new(Nest127,3,4,4,SWSH) { Species = 711, Ability = A2, Form = 2 }, // Gourgeist-2 + new(Nest127,4,4,4,SWSH) { Species = 711, Ability = A2, Form = 3 }, // Gourgeist-3 + new(Nest128,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest128,0,1,1,SWSH) { Species = 707, Ability = A3 }, // Klefki + new(Nest128,0,1,1,SWSH) { Species = 081, Ability = A3 }, // Magnemite + new(Nest128,1,2,2,SWSH) { Species = 624, Ability = A3 }, // Pawniard + new(Nest128,1,2,2,SWSH) { Species = 081, Ability = A3 }, // Magnemite + new(Nest128,2,3,3,SWSH) { Species = 227, Ability = A3 }, // Skarmory + new(Nest128,2,3,3,SWSH) { Species = 082, Ability = A3 }, // Magneton + new(Nest128,3,4,4,SWSH) { Species = 082, Ability = A4 }, // Magneton + new(Nest128,3,4,4,SWSH) { Species = 707, Ability = A4 }, // Klefki + new(Nest128,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp + new(Nest128,4,4,4,SWSH) { Species = 462, Ability = A4 }, // Magnezone + new(Nest128,4,4,4,SWSH) { Species = 227, Ability = A4 }, // Skarmory + new(Nest129,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest129,0,1,1,SWSH) { Species = 081, Ability = A2 }, // Magnemite + new(Nest129,0,1,1,SWSH) { Species = 227, Ability = A2 }, // Skarmory + new(Nest129,1,2,2,SWSH) { Species = 436, Ability = A2 }, // Bronzor + new(Nest129,1,2,2,SWSH) { Species = 052, Ability = A2, Form = 2 }, // Meowth-2 + new(Nest129,2,3,3,SWSH) { Species = 082, Ability = A2 }, // Magneton + new(Nest129,2,3,3,SWSH) { Species = 601, Ability = A2 }, // Klinklang + new(Nest129,3,4,4,SWSH) { Species = 227, Ability = A2 }, // Skarmory + new(Nest129,3,4,4,SWSH) { Species = 437, Ability = A2 }, // Bronzong + new(Nest129,3,4,4,SWSH) { Species = 863, Ability = A2 }, // Perrserker + new(Nest129,4,4,4,SWSH) { Species = 448, Ability = A2 }, // Lucario + new(Nest129,4,4,4,SWSH) { Species = 625, Ability = A2 }, // Bisharp + new(Nest130,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + //new(Nest130,0,1,1,SWSH) { Species = 116, Ability = A3 }, // Horsea + new(Nest130,2,2,2,SWSH) { Species = 840, Ability = A3 }, // Applin + new(Nest130,1,2,2,SWSH) { Species = 117, Ability = A3 }, // Seadra + new(Nest130,2,3,3,SWSH) { Species = 621, Ability = A3 }, // Druddigon + new(Nest130,3,4,4,SWSH) { Species = 621, Ability = A4 }, // Druddigon + new(Nest130,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados + new(Nest130,4,4,4,SWSH) { Species = 230, Ability = A4 }, // Kingdra + new(Nest131,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + //new(Nest131,0,1,1,SWSH) { Species = 116, Ability = A2 }, // Horsea + //new(Nest131,0,1,1,SWSH) { Species = 621, Ability = A2 }, // Druddigon + new(Nest131,2,3,3,SWSH) { Species = 117, Ability = A2 }, // Seadra + new(Nest131,3,4,4,SWSH) { Species = 621, Ability = A2 }, // Druddigon + new(Nest131,3,4,4,SWSH) { Species = 715, Ability = A2 }, // Noivern + new(Nest131,4,4,4,SWSH) { Species = 230, Ability = A2 }, // Kingdra + new(Nest132,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest132,0,1,1,SWSH) { Species = 060, Ability = A3 }, // Poliwag + new(Nest132,0,1,1,SWSH) { Species = 194, Ability = A3 }, // Wooper + new(Nest132,1,2,2,SWSH) { Species = 118, Ability = A3 }, // Goldeen + new(Nest132,1,2,2,SWSH) { Species = 061, Ability = A3 }, // Poliwhirl + new(Nest132,2,3,3,SWSH) { Species = 342, Ability = A3 }, // Crawdaunt + new(Nest132,2,3,3,SWSH) { Species = 061, Ability = A3 }, // Poliwhirl + new(Nest132,3,4,4,SWSH) { Species = 119, Ability = A4 }, // Seaking + new(Nest132,3,4,4,SWSH) { Species = 342, Ability = A4 }, // Crawdaunt + new(Nest132,3,4,4,SWSH) { Species = 195, Ability = A4 }, // Quagsire + new(Nest132,4,4,4,SWSH) { Species = 062, Ability = A4 }, // Poliwrath + new(Nest132,4,4,4,SWSH) { Species = 186, Ability = A4 }, // Politoed + new(Nest133,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest133,0,1,1,SWSH) { Species = 341, Ability = A2 }, // Corphish + new(Nest133,0,1,1,SWSH) { Species = 751, Ability = A2 }, // Dewpider + new(Nest133,1,2,2,SWSH) { Species = 118, Ability = A2 }, // Goldeen + new(Nest133,1,2,2,SWSH) { Species = 061, Ability = A2 }, // Poliwhirl + new(Nest133,2,3,3,SWSH) { Species = 342, Ability = A2 }, // Crawdaunt + new(Nest133,2,3,3,SWSH) { Species = 195, Ability = A2 }, // Quagsire + new(Nest133,3,4,4,SWSH) { Species = 119, Ability = A2 }, // Seaking + new(Nest133,3,4,4,SWSH) { Species = 062, Ability = A2 }, // Poliwrath + new(Nest133,3,4,4,SWSH) { Species = 342, Ability = A2 }, // Crawdaunt + new(Nest133,4,4,4,SWSH) { Species = 752, Ability = A2 }, // Araquanid + new(Nest133,4,4,4,SWSH) { Species = 186, Ability = A2 }, // Politoed + new(Nest134,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest134,0,1,1,SWSH) { Species = 054, Ability = A3 }, // Psyduck + new(Nest134,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle + new(Nest134,1,2,2,SWSH) { Species = 846, Ability = A3 }, // Arrokuda + new(Nest134,1,2,2,SWSH) { Species = 339, Ability = A3 }, // Barboach + new(Nest134,2,3,3,SWSH) { Species = 055, Ability = A3 }, // Golduck + new(Nest134,2,3,3,SWSH) { Species = 845, Ability = A3 }, // Cramorant + new(Nest134,3,4,4,SWSH) { Species = 055, Ability = A4 }, // Golduck + new(Nest134,3,4,4,SWSH) { Species = 847, Ability = A4 }, // Barraskewda + new(Nest134,3,4,4,SWSH) { Species = 834, Ability = A4 }, // Drednaw + new(Nest134,4,4,4,SWSH) { Species = 340, Ability = A4 }, // Whiscash + new(Nest134,4,4,4,SWSH) { Species = 055, Ability = A4 }, // Golduck + new(Nest135,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest135,0,1,1,SWSH) { Species = 846, Ability = A2 }, // Arrokuda + new(Nest135,0,1,1,SWSH) { Species = 535, Ability = A2 }, // Tympole + new(Nest135,1,2,2,SWSH) { Species = 054, Ability = A2 }, // Psyduck + new(Nest135,1,2,2,SWSH) { Species = 536, Ability = A2 }, // Palpitoad + new(Nest135,2,3,3,SWSH) { Species = 055, Ability = A2 }, // Golduck + new(Nest135,2,3,3,SWSH) { Species = 340, Ability = A2 }, // Whiscash + new(Nest135,3,4,4,SWSH) { Species = 055, Ability = A2 }, // Golduck + new(Nest135,3,4,4,SWSH) { Species = 847, Ability = A2 }, // Barraskewda + new(Nest135,3,4,4,SWSH) { Species = 537, Ability = A2 }, // Seismitoad + new(Nest135,4,4,4,SWSH) { Species = 130, Ability = A2 }, // Gyarados + new(Nest136,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest136,0,1,1,SWSH) { Species = 072, Ability = A3 }, // Tentacool + new(Nest136,0,1,1,SWSH) { Species = 098, Ability = A3 }, // Krabby + new(Nest136,1,2,2,SWSH) { Species = 072, Ability = A3 }, // Tentacool + new(Nest136,1,2,2,SWSH) { Species = 223, Ability = A3 }, // Remoraid + new(Nest136,2,3,3,SWSH) { Species = 073, Ability = A3 }, // Tentacruel + new(Nest136,2,3,3,SWSH) { Species = 746, Ability = A3 }, // Wishiwashi + new(Nest136,3,4,4,SWSH) { Species = 224, Ability = A4 }, // Octillery + new(Nest136,3,4,4,SWSH) { Species = 226, Ability = A4 }, // Mantine + new(Nest136,3,4,4,SWSH) { Species = 099, Ability = A4 }, // Kingler + new(Nest136,4,4,4,SWSH) { Species = 091, Ability = A4 }, // Cloyster + new(Nest136,4,4,4,SWSH) { Species = 073, Ability = A4 }, // Tentacruel + new(Nest137,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest137,0,1,1,SWSH) { Species = 090, Ability = A2 }, // Shellder + new(Nest137,0,1,1,SWSH) { Species = 688, Ability = A2 }, // Binacle + new(Nest137,1,2,2,SWSH) { Species = 747, Ability = A2 }, // Mareanie + new(Nest137,1,2,2,SWSH) { Species = 223, Ability = A2 }, // Remoraid + new(Nest137,2,3,3,SWSH) { Species = 073, Ability = A2 }, // Tentacruel + new(Nest137,2,3,3,SWSH) { Species = 771, Ability = A2 }, // Pyukumuku + new(Nest137,3,4,4,SWSH) { Species = 224, Ability = A2 }, // Octillery + new(Nest137,3,4,4,SWSH) { Species = 226, Ability = A2 }, // Mantine + new(Nest137,3,4,4,SWSH) { Species = 689, Ability = A2 }, // Barbaracle + new(Nest137,4,4,4,SWSH) { Species = 091, Ability = A2 }, // Cloyster + new(Nest137,4,4,4,SWSH) { Species = 748, Ability = A2 }, // Toxapex + new(Nest138,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + //new(Nest138,0,1,1,SWSH) { Species = 170, Ability = A3 }, // Chinchou + new(Nest138,2,2,2,SWSH) { Species = 120, Ability = A3 }, // Staryu + new(Nest138,2,3,3,SWSH) { Species = 320, Ability = A3 }, // Wailmer + new(Nest138,2,3,3,SWSH) { Species = 746, Ability = A3 }, // Wishiwashi + new(Nest138,3,4,4,SWSH) { Species = 321, Ability = A4 }, // Wailord + new(Nest138,3,4,4,SWSH) { Species = 171, Ability = A4 }, // Lanturn + new(Nest138,3,4,4,SWSH) { Species = 121, Ability = A4 }, // Starmie + new(Nest138,4,4,4,SWSH) { Species = 319, Ability = A4 }, // Sharpedo + new(Nest139,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + //new(Nest139,0,1,1,SWSH) { Species = 120, Ability = A2 }, // Staryu + new(Nest139,2,2,2,SWSH) { Species = 320, Ability = A2 }, // Wailmer + new(Nest139,2,2,2,SWSH) { Species = 279, Ability = A2 }, // Pelipper + new(Nest139,2,3,3,SWSH) { Species = 171, Ability = A2 }, // Lanturn + new(Nest139,2,3,3,SWSH) { Species = 117, Ability = A2 }, // Seadra + new(Nest139,3,4,4,SWSH) { Species = 171, Ability = A2 }, // Lanturn + new(Nest139,3,4,4,SWSH) { Species = 121, Ability = A2 }, // Starmie + new(Nest139,4,4,4,SWSH) { Species = 319, Ability = A2 }, // Sharpedo + new(Nest140,0,0,1,SWSH) { Species = 440, Ability = A3 }, // Happiny + new(Nest140,0,1,1,SWSH) { Species = 440, Ability = A3 }, // Happiny + new(Nest140,1,2,2,SWSH) { Species = 440, Ability = A3 }, // Happiny + new(Nest140,2,3,3,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest140,3,4,4,SWSH) { Species = 113, Ability = A4 }, // Chansey + new(Nest140,4,4,4,SWSH) { Species = 242, Ability = A4 }, // Blissey + new(Nest141,0,0,1,SWSH) { Species = 113, Ability = A2 }, // Chansey + new(Nest141,0,1,1,SWSH) { Species = 113, Ability = A2 }, // Chansey + new(Nest141,1,2,2,SWSH) { Species = 113, Ability = A2 }, // Chansey + new(Nest141,2,3,3,SWSH) { Species = 113, Ability = A2 }, // Chansey + new(Nest141,3,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest141,4,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest142,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + //new(Nest142,0,1,1,SWSH) { Species = 415, Ability = A3 }, // Combee + new(Nest142,2,2,2,SWSH) { Species = 415, Ability = A3 }, // Combee + new(Nest142,2,3,3,SWSH) { Species = 415, Ability = A3 }, // Combee + new(Nest142,3,4,4,SWSH) { Species = 416, Ability = A4 }, // Vespiquen + new(Nest142,4,4,4,SWSH) { Species = 416, Ability = A4 }, // Vespiquen + new(Nest143,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + //new(Nest143,0,1,1,SWSH) { Species = 415, Ability = A2, Gender = 1 }, // Combee + new(Nest143,2,2,2,SWSH) { Species = 415, Ability = A2, Gender = 1 }, // Combee + new(Nest143,2,3,3,SWSH) { Species = 416, Ability = A2 }, // Vespiquen + new(Nest143,3,4,4,SWSH) { Species = 416, Ability = A2 }, // Vespiquen + new(Nest143,4,4,4,SWSH) { Species = 416, Ability = A2 }, // Vespiquen + new(Nest144,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest144,0,1,1,SWSH) { Species = 590, Ability = A2 }, // Foongus + new(Nest144,0,1,1,SWSH) { Species = 102, Ability = A2 }, // Exeggcute + new(Nest144,1,2,2,SWSH) { Species = 114, Ability = A2 }, // Tangela + new(Nest144,1,2,2,SWSH) { Species = 315, Ability = A2 }, // Roselia + new(Nest144,2,3,3,SWSH) { Species = 114, Ability = A2 }, // Tangela + new(Nest144,2,3,3,SWSH) { Species = 315, Ability = A2 }, // Roselia + new(Nest144,3,4,4,SWSH) { Species = 103, Ability = A2 }, // Exeggutor + new(Nest144,3,4,4,SWSH) { Species = 003, Ability = A2 }, // Venusaur + new(Nest144,3,4,4,SWSH) { Species = 465, Ability = A2 }, // Tangrowth + new(Nest144,4,4,4,SWSH) { Species = 407, Ability = A2 }, // Roserade + new(Nest144,4,4,4,SWSH) { Species = 003, Ability = A2, CanGigantamax = true }, // Venusaur + new(Nest145,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + //new(Nest145,0,1,1,SWSH) { Species = 129, Ability = A2 }, // Magikarp + //new(Nest145,0,1,1,SWSH) { Species = 072, Ability = A2 }, // Tentacool + new(Nest145,2,2,2,SWSH) { Species = 120, Ability = A2 }, // Staryu + new(Nest145,2,2,2,SWSH) { Species = 688, Ability = A2 }, // Binacle + new(Nest145,2,3,3,SWSH) { Species = 073, Ability = A2 }, // Tentacruel + new(Nest145,2,3,3,SWSH) { Species = 130, Ability = A2 }, // Gyarados + new(Nest145,3,4,4,SWSH) { Species = 073, Ability = A2 }, // Tentacruel + new(Nest145,3,4,4,SWSH) { Species = 130, Ability = A2 }, // Gyarados + new(Nest145,3,4,4,SWSH) { Species = 121, Ability = A2 }, // Starmie + new(Nest145,4,4,4,SWSH) { Species = 689, Ability = A2 }, // Barbaracle + new(Nest145,4,4,4,SWSH) { Species = 009, Ability = A2, CanGigantamax = true }, // Blastoise + new(Nest146,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + //new(Nest146,0,1,1,SWSH) { Species = 098, Ability = A2 }, // Krabby + //new(Nest146,0,1,1,SWSH) { Species = 688, Ability = A2 }, // Binacle + new(Nest146,2,2,2,SWSH) { Species = 072, Ability = A2 }, // Tentacool + new(Nest146,2,2,2,SWSH) { Species = 223, Ability = A2 }, // Remoraid + new(Nest146,2,3,3,SWSH) { Species = 073, Ability = A2 }, // Tentacruel + new(Nest146,2,3,3,SWSH) { Species = 224, Ability = A2 }, // Octillery + new(Nest146,3,4,4,SWSH) { Species = 713, Ability = A2 }, // Avalugg + new(Nest146,3,4,4,SWSH) { Species = 614, Ability = A2 }, // Beartic + new(Nest146,3,4,4,SWSH) { Species = 099, Ability = A2 }, // Kingler + new(Nest146,4,4,4,SWSH) { Species = 091, Ability = A2 }, // Cloyster + new(Nest146,4,4,4,SWSH) { Species = 099, Ability = A2, CanGigantamax = true }, // Kingler + new(Nest147,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest147,0,1,1,SWSH) { Species = 833, Ability = A2 }, // Chewtle + new(Nest147,0,1,1,SWSH) { Species = 054, Ability = A2 }, // Psyduck + new(Nest147,1,2,2,SWSH) { Species = 339, Ability = A2 }, // Barboach + new(Nest147,2,3,3,SWSH) { Species = 055, Ability = A2 }, // Golduck + new(Nest147,2,3,3,SWSH) { Species = 845, Ability = A2 }, // Cramorant + new(Nest147,3,4,4,SWSH) { Species = 055, Ability = A2 }, // Golduck + new(Nest147,3,4,4,SWSH) { Species = 847, Ability = A2 }, // Barraskewda + new(Nest147,4,4,4,SWSH) { Species = 340, Ability = A2 }, // Whiscash + new(Nest147,4,4,4,SWSH) { Species = 834, Ability = A2, CanGigantamax = true }, // Drednaw + new(Nest148,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest148,0,1,1,SWSH) { Species = 824, Ability = A2 }, // Blipbug + new(Nest148,0,1,1,SWSH) { Species = 742, Ability = A2 }, // Cutiefly + new(Nest148,1,2,2,SWSH) { Species = 595, Ability = A2 }, // Joltik + new(Nest148,2,3,3,SWSH) { Species = 825, Ability = A2 }, // Dottler + new(Nest148,2,3,3,SWSH) { Species = 291, Ability = A2 }, // Ninjask + new(Nest148,3,4,4,SWSH) { Species = 826, Ability = A2 }, // Orbeetle + new(Nest148,3,4,4,SWSH) { Species = 596, Ability = A2 }, // Galvantula + new(Nest148,3,4,4,SWSH) { Species = 743, Ability = A2 }, // Ribombee + new(Nest148,4,4,4,SWSH) { Species = 291, Ability = A2 }, // Ninjask + new(Nest148,4,4,4,SWSH) { Species = 826, Ability = A2, CanGigantamax = true }, // Orbeetle + new(Nest149,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest149,0,1,1,SWSH) { Species = 843, Ability = A2 }, // Silicobra + new(Nest149,0,1,1,SWSH) { Species = 529, Ability = A2 }, // Drilbur + new(Nest149,1,2,2,SWSH) { Species = 843, Ability = A2 }, // Silicobra + new(Nest149,1,2,2,SWSH) { Species = 529, Ability = A2 }, // Drilbur + new(Nest149,2,3,3,SWSH) { Species = 028, Ability = A2 }, // Sandslash + new(Nest149,2,3,3,SWSH) { Species = 552, Ability = A2 }, // Krokorok + new(Nest149,3,4,4,SWSH) { Species = 844, Ability = A2 }, // Sandaconda + new(Nest149,3,4,4,SWSH) { Species = 553, Ability = A2 }, // Krookodile + new(Nest149,3,4,4,SWSH) { Species = 530, Ability = A2 }, // Excadrill + new(Nest149,4,4,4,SWSH) { Species = 553, Ability = A2 }, // Krookodile + new(Nest149,4,4,4,SWSH) { Species = 844, Ability = A2, CanGigantamax = true }, // Sandaconda + new(Nest150,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + //new(Nest150,0,1,1,SWSH) { Species = 840, Ability = A2 }, // Applin + //new(Nest150,0,1,1,SWSH) { Species = 420, Ability = A2 }, // Cherubi (DLC1) + //new(Nest150,0,1,1,SWSH) { Species = 761, Ability = A2 }, // Bounsweet (DLC2) + new(Nest150,2,2,2,SWSH) { Species = 420, Ability = A2 }, // Cherubi + new(Nest150,2,2,2,SWSH) { Species = 840, Ability = A2 }, // Applin + new(Nest150,2,3,3,SWSH) { Species = 762, Ability = A2 }, // Steenee + new(Nest150,3,4,4,SWSH) { Species = 820, Ability = A2 }, // Greedent + new(Nest150,4,4,4,SWSH) { Species = 763, Ability = A2 }, // Tsareena + new(Nest151,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + //new(Nest151,0,0,1,SWSH) { Species = 132, Ability = A3 }, // Ditto + //new(Nest151,0,1,2,SWSH) { Species = 132, Ability = A3 }, // Ditto + new(Nest151,2,2,3,SWSH) { Species = 132, Ability = A3 }, // Ditto + new(Nest151,2,3,3,SWSH) { Species = 132, Ability = A3 }, // Ditto + new(Nest151,2,3,3,SWSH) { Species = 132, Ability = A4 }, // Ditto + new(Nest151,3,4,4,SWSH) { Species = 132, Ability = A4 }, // Ditto + new(Nest151,4,4,4,SWSH) { Species = 132, Ability = A4 }, // Ditto + new(Nest152,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + //new(Nest152,0,0,1,SWSH) { Species = 132, Ability = A2 }, // Ditto + //new(Nest152,0,1,2,SWSH) { Species = 132, Ability = A2 }, // Ditto + new(Nest152,2,2,3,SWSH) { Species = 132, Ability = A2 }, // Ditto + new(Nest152,2,3,3,SWSH) { Species = 132, Ability = A2 }, // Ditto + new(Nest152,3,4,4,SWSH) { Species = 132, Ability = A2 }, // Ditto + new(Nest152,4,4,4,SWSH) { Species = 132, Ability = A2 }, // Ditto + new(Nest153,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest153,0,1,1,SWSH) { Species = 590, Ability = A3 }, // Foongus + new(Nest153,0,1,1,SWSH) { Species = 102, Ability = A3 }, // Exeggcute + new(Nest153,1,2,2,SWSH) { Species = 753, Ability = A3 }, // Fomantis + new(Nest153,1,2,2,SWSH) { Species = 114, Ability = A3 }, // Tangela + new(Nest153,2,3,3,SWSH) { Species = 754, Ability = A3 }, // Lurantis + new(Nest153,2,3,3,SWSH) { Species = 102, Ability = A3 }, // Exeggcute + new(Nest153,3,4,4,SWSH) { Species = 103, Ability = A4 }, // Exeggutor + new(Nest153,3,4,4,SWSH) { Species = 591, Ability = A4 }, // Amoonguss + new(Nest153,3,4,4,SWSH) { Species = 754, Ability = A4 }, // Lurantis + new(Nest153,4,4,4,SWSH) { Species = 465, Ability = A4 }, // Tangrowth + new(Nest153,4,4,4,SWSH) { Species = 003, Ability = A4 }, // Venusaur + new(Nest154,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + //new(Nest154,0,1,1,SWSH) { Species = 129, Ability = A3 }, // Magikarp + //new(Nest154,0,1,1,SWSH) { Species = 072, Ability = A3 }, // Tentacool + new(Nest154,2,2,2,SWSH) { Species = 120, Ability = A3 }, // Staryu + new(Nest154,1,2,2,SWSH) { Species = 090, Ability = A3 }, // Shellder + new(Nest154,2,3,3,SWSH) { Species = 073, Ability = A3 }, // Tentacruel + new(Nest154,2,3,3,SWSH) { Species = 130, Ability = A3 }, // Gyarados + new(Nest154,3,4,4,SWSH) { Species = 073, Ability = A4 }, // Tentacruel + new(Nest154,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados + new(Nest154,3,4,4,SWSH) { Species = 121, Ability = A4 }, // Starmie + new(Nest154,4,4,4,SWSH) { Species = 091, Ability = A4 }, // Cloyster + new(Nest154,4,4,4,SWSH) { Species = 009, Ability = A4 }, // Blastoise + new(Nest155,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey + new(Nest155,0,1,1,SWSH) { Species = 744, Ability = A3 }, // Rockruff + new(Nest155,1,2,2,SWSH) { Species = 744, Ability = A3 }, // Rockruff + new(Nest155,2,3,3,SWSH) { Species = 744, Ability = A3 }, // Rockruff + new(Nest155,2,3,3,SWSH) { Species = 744, Ability = A3, Form = 1 }, // Rockruff-1 + new(Nest155,3,4,4,SWSH) { Species = 745, Ability = A4 }, // Lycanroc + new(Nest155,3,4,4,SWSH) { Species = 745, Ability = A4, Form = 1 }, // Lycanroc-1 + new(Nest155,4,4,4,SWSH) { Species = 745, Ability = A4, Form = 2 }, // Lycanroc-2 + new(Nest156,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey + new(Nest156,0,1,1,SWSH) { Species = 744, Ability = A2 }, // Rockruff + new(Nest156,1,2,2,SWSH) { Species = 744, Ability = A2 }, // Rockruff + new(Nest156,2,3,3,SWSH) { Species = 744, Ability = A2 }, // Rockruff + new(Nest156,2,3,3,SWSH) { Species = 744, Ability = A2, Form = 1 }, // Rockruff-1 + new(Nest156,3,3,4,SWSH) { Species = 745, Ability = A2 }, // Lycanroc + new(Nest156,3,3,4,SWSH) { Species = 745, Ability = A2, Form = 1 }, // Lycanroc-1 + new(Nest156,4,4,4,SWSH) { Species = 745, Ability = A2 }, // Lycanroc + new(Nest156,4,4,4,SWSH) { Species = 745, Ability = A2, Form = 1 }, // Lycanroc-1 + new(Nest156,3,4,4,SWSH) { Species = 745, Ability = A2, Form = 2 }, // Lycanroc-2 + new(Nest157,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest157,0,1,1,SWSH) { Species = 333, Ability = A3 }, // Swablu + new(Nest157,0,1,1,SWSH) { Species = 831, Ability = A3 }, // Wooloo + new(Nest157,1,2,2,SWSH) { Species = 333, Ability = A3 }, // Swablu + new(Nest157,1,2,2,SWSH) { Species = 446, Ability = A3 }, // Munchlax + new(Nest157,2,3,3,SWSH) { Species = 820, Ability = A3 }, // Greedent + new(Nest157,2,3,3,SWSH) { Species = 832, Ability = A3 }, // Dubwool + new(Nest157,3,4,4,SWSH) { Species = 334, Ability = A4 }, // Altaria + new(Nest157,3,4,4,SWSH) { Species = 832, Ability = A4 }, // Dubwool + new(Nest157,4,4,4,SWSH) { Species = 143, Ability = A4 }, // Snorlax + new(Nest158,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest158,0,1,2,SWSH) { Species = 333, Ability = A2 }, // Swablu + new(Nest158,0,1,2,SWSH) { Species = 819, Ability = A2 }, // Skwovet + new(Nest158,1,2,3,SWSH) { Species = 333, Ability = A2 }, // Swablu + new(Nest158,1,2,3,SWSH) { Species = 820, Ability = A2 }, // Greedent + new(Nest158,2,3,4,SWSH) { Species = 820, Ability = A2 }, // Greedent + new(Nest158,2,3,4,SWSH) { Species = 832, Ability = A2 }, // Dubwool + new(Nest158,3,4,5,SWSH) { Species = 334, Ability = A2 }, // Altaria + new(Nest158,3,4,5,SWSH) { Species = 832, Ability = A2 }, // Dubwool + new(Nest158,4,4,5,SWSH) { Species = 143, Ability = A2 }, // Snorlax + new(Nest158,4,4,5,SWSH) { Species = 143, Ability = A2, CanGigantamax = true }, // Snorlax + new(Nest159,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest159,0,1,1,SWSH) { Species = 240, Ability = A3 }, // Magby + new(Nest159,0,1,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede + new(Nest159,1,2,2,SWSH) { Species = 240, Ability = A3 }, // Magby + new(Nest159,1,2,2,SWSH) { Species = 631, Ability = A3 }, // Heatmor + new(Nest159,2,3,3,SWSH) { Species = 608, Ability = A3 }, // Lampent + new(Nest159,2,3,3,SWSH) { Species = 631, Ability = A3 }, // Heatmor + new(Nest159,3,4,4,SWSH) { Species = 126, Ability = A4 }, // Magmar + new(Nest159,3,4,4,SWSH) { Species = 851, Ability = A4 }, // Centiskorch + new(Nest159,3,4,4,SWSH) { Species = 609, Ability = A4 }, // Chandelure + new(Nest159,4,4,4,SWSH) { Species = 126, Ability = A4 }, // Magmar + new(Nest159,4,4,4,SWSH) { Species = 467, Ability = A4 }, // Magmortar + new(Nest160,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest160,0,1,2,SWSH) { Species = 240, Ability = A2 }, // Magby + new(Nest160,1,2,3,SWSH) { Species = 126, Ability = A2 }, // Magmar + new(Nest160,1,2,3,SWSH) { Species = 631, Ability = A2 }, // Heatmor + new(Nest160,2,3,4,SWSH) { Species = 126, Ability = A2 }, // Magmar + new(Nest160,2,3,4,SWSH) { Species = 851, Ability = A2 }, // Centiskorch + new(Nest160,3,4,5,SWSH) { Species = 609, Ability = A2 }, // Chandelure + new(Nest160,3,4,5,SWSH) { Species = 467, Ability = A2 }, // Magmortar + new(Nest160,4,4,5,SWSH) { Species = 467, Ability = A2 }, // Magmortar + new(Nest160,4,4,5,SWSH) { Species = 851, Ability = A2, CanGigantamax = true }, // Centiskorch + new(Nest161,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest161,0,1,1,SWSH) { Species = 349, Ability = A3 }, // Feebas + new(Nest161,1,2,2,SWSH) { Species = 349, Ability = A3 }, // Feebas + new(Nest161,2,3,3,SWSH) { Species = 340, Ability = A3 }, // Whiscash + new(Nest161,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados + new(Nest161,4,4,4,SWSH) { Species = 350, Ability = A4 }, // Milotic + new(Nest161,4,4,4,SWSH) { Species = 369, Ability = A4 }, // Relicanth + new(Nest162,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest162,0,1,2,SWSH) { Species = 349, Ability = A2 }, // Feebas + new(Nest162,1,2,3,SWSH) { Species = 349, Ability = A2 }, // Feebas + new(Nest162,1,2,3,SWSH) { Species = 369, Ability = A2 }, // Relicanth + new(Nest162,2,3,4,SWSH) { Species = 099, Ability = A2 }, // Kingler + new(Nest162,3,4,5,SWSH) { Species = 369, Ability = A2 }, // Relicanth + new(Nest162,3,4,5,SWSH) { Species = 350, Ability = A2 }, // Milotic + new(Nest162,4,4,5,SWSH) { Species = 130, Ability = A2 }, // Gyarados + new(Nest162,4,4,5,SWSH) { Species = 099, Ability = A2, CanGigantamax = true }, // Kingler + new(Nest163,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest163,0,1,1,SWSH) { Species = 239, Ability = A3 }, // Elekid + new(Nest163,0,1,1,SWSH) { Species = 595, Ability = A3 }, // Joltik + new(Nest163,1,2,2,SWSH) { Species = 239, Ability = A3 }, // Elekid + new(Nest163,1,2,2,SWSH) { Species = 871, Ability = A3 }, // Pincurchin + new(Nest163,2,3,3,SWSH) { Species = 125, Ability = A3 }, // Electabuzz + new(Nest163,2,3,3,SWSH) { Species = 778, Ability = A3 }, // Mimikyu + new(Nest163,3,4,4,SWSH) { Species = 596, Ability = A4 }, // Galvantula + new(Nest163,3,4,4,SWSH) { Species = 871, Ability = A4 }, // Pincurchin + new(Nest163,3,4,4,SWSH) { Species = 836, Ability = A4 }, // Boltund + new(Nest163,4,4,4,SWSH) { Species = 125, Ability = A4 }, // Electabuzz + new(Nest163,4,4,4,SWSH) { Species = 466, Ability = A4 }, // Electivire + new(Nest164,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest164,0,1,2,SWSH) { Species = 239, Ability = A2 }, // Elekid + new(Nest164,1,2,3,SWSH) { Species = 702, Ability = A2 }, // Dedenne + new(Nest164,1,2,3,SWSH) { Species = 596, Ability = A2 }, // Galvantula + new(Nest164,2,3,4,SWSH) { Species = 125, Ability = A2 }, // Electabuzz + new(Nest164,2,3,4,SWSH) { Species = 836, Ability = A2 }, // Boltund + new(Nest164,3,4,5,SWSH) { Species = 871, Ability = A2 }, // Pincurchin + new(Nest164,3,4,5,SWSH) { Species = 466, Ability = A2 }, // Electivire + new(Nest164,4,4,5,SWSH) { Species = 466, Ability = A2 }, // Electivire + new(Nest165,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest165,0,1,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur + new(Nest165,1,2,2,SWSH) { Species = 347, Ability = A3 }, // Anorith + new(Nest165,1,2,2,SWSH) { Species = 345, Ability = A3 }, // Lileep + new(Nest165,2,3,3,SWSH) { Species = 830, Ability = A3 }, // Eldegoss + new(Nest165,3,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid + new(Nest165,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss + new(Nest165,4,4,4,SWSH) { Species = 598, Ability = A4 }, // Ferrothorn + new(Nest166,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest166,0,1,2,SWSH) { Species = 347, Ability = A2 }, // Anorith + new(Nest166,0,1,2,SWSH) { Species = 345, Ability = A2 }, // Lileep + new(Nest166,1,2,3,SWSH) { Species = 347, Ability = A2 }, // Anorith + new(Nest166,1,2,3,SWSH) { Species = 345, Ability = A2 }, // Lileep + new(Nest166,2,3,4,SWSH) { Species = 752, Ability = A2 }, // Araquanid + new(Nest166,2,3,4,SWSH) { Species = 012, Ability = A2 }, // Butterfree + new(Nest166,3,4,5,SWSH) { Species = 348, Ability = A2 }, // Armaldo + new(Nest166,3,4,5,SWSH) { Species = 346, Ability = A2 }, // Cradily + new(Nest166,3,4,5,SWSH) { Species = 830, Ability = A2 }, // Eldegoss + new(Nest166,4,4,5,SWSH) { Species = 012, Ability = A2, CanGigantamax = true }, // Butterfree + new(Nest167,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest167,0,1,1,SWSH) { Species = 238, Ability = A3 }, // Smoochum + new(Nest167,1,2,2,SWSH) { Species = 238, Ability = A3 }, // Smoochum + new(Nest167,1,2,2,SWSH) { Species = 698, Ability = A3 }, // Amaura + new(Nest167,2,3,3,SWSH) { Species = 221, Ability = A3 }, // Piloswine + new(Nest167,2,3,3,SWSH) { Species = 460, Ability = A3 }, // Abomasnow + new(Nest167,3,4,4,SWSH) { Species = 124, Ability = A4 }, // Jynx + new(Nest167,3,4,4,SWSH) { Species = 873, Ability = A4 }, // Frosmoth + new(Nest167,4,4,4,SWSH) { Species = 699, Ability = A4 }, // Aurorus + new(Nest167,4,4,4,SWSH) { Species = 362, Ability = A4 }, // Glalie + new(Nest168,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest168,0,1,2,SWSH) { Species = 361, Ability = A2 }, // Snorunt + new(Nest168,1,2,3,SWSH) { Species = 238, Ability = A2 }, // Smoochum + new(Nest168,1,2,3,SWSH) { Species = 698, Ability = A2 }, // Amaura + new(Nest168,2,3,4,SWSH) { Species = 362, Ability = A2 }, // Glalie + new(Nest168,2,3,4,SWSH) { Species = 460, Ability = A2 }, // Abomasnow + new(Nest168,3,4,5,SWSH) { Species = 124, Ability = A2 }, // Jynx + new(Nest168,3,4,5,SWSH) { Species = 873, Ability = A2 }, // Frosmoth + new(Nest168,4,4,5,SWSH) { Species = 699, Ability = A2 }, // Aurorus + new(Nest168,4,4,5,SWSH) { Species = 473, Ability = A2 }, // Mamoswine + new(Nest169,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + //new(Nest169,0,1,1,SWSH) { Species = 363, Ability = A3 }, // Spheal + new(Nest169,2,2,2,SWSH) { Species = 363, Ability = A3 }, // Spheal + new(Nest169,2,3,3,SWSH) { Species = 364, Ability = A3 }, // Sealeo + new(Nest169,2,3,3,SWSH) { Species = 615, Ability = A3 }, // Cryogonal + new(Nest169,3,4,4,SWSH) { Species = 584, Ability = A4 }, // Vanilluxe + new(Nest169,3,4,4,SWSH) { Species = 614, Ability = A4 }, // Beartic + new(Nest169,3,4,4,SWSH) { Species = 365, Ability = A4 }, // Walrein + new(Nest169,4,4,4,SWSH) { Species = 713, Ability = A4 }, // Avalugg + new(Nest169,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras + new(Nest170,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + //new(Nest170,0,1,2,SWSH) { Species = 131, Ability = A2 }, // Lapras + //new(Nest170,0,1,2,SWSH) { Species = 363, Ability = A2 }, // Spheal + new(Nest170,2,2,3,SWSH) { Species = 364, Ability = A2 }, // Sealeo + new(Nest170,2,3,4,SWSH) { Species = 713, Ability = A2 }, // Avalugg + new(Nest170,2,3,4,SWSH) { Species = 615, Ability = A2 }, // Cryogonal + new(Nest170,3,4,5,SWSH) { Species = 365, Ability = A2 }, // Walrein + new(Nest170,3,4,5,SWSH) { Species = 131, Ability = A2 }, // Lapras + new(Nest170,3,4,5,SWSH) { Species = 584, Ability = A2 }, // Vanilluxe + new(Nest170,4,4,5,SWSH) { Species = 365, Ability = A2 }, // Walrein + new(Nest171,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest171,0,1,1,SWSH) { Species = 532, Ability = A3 }, // Timburr + new(Nest171,0,1,1,SWSH) { Species = 622, Ability = A3 }, // Golett + new(Nest171,1,2,2,SWSH) { Species = 622, Ability = A3 }, // Golett + new(Nest171,1,2,2,SWSH) { Species = 838, Ability = A3 }, // Carkol + new(Nest171,2,3,3,SWSH) { Species = 533, Ability = A3 }, // Gurdurr + new(Nest171,2,3,3,SWSH) { Species = 623, Ability = A3 }, // Golurk + new(Nest171,3,4,4,SWSH) { Species = 534, Ability = A4 }, // Conkeldurr + new(Nest171,3,4,4,SWSH) { Species = 623, Ability = A4 }, // Golurk + new(Nest171,3,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal + new(Nest171,4,4,4,SWSH) { Species = 623, Ability = A4 }, // Golurk + new(Nest171,4,4,4,SWSH) { Species = 534, Ability = A4 }, // Conkeldurr + new(Nest172,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest172,0,1,2,SWSH) { Species = 870, Ability = A2 }, // Falinks + new(Nest172,0,1,2,SWSH) { Species = 236, Ability = A2 }, // Tyrogue + new(Nest172,1,2,3,SWSH) { Species = 533, Ability = A2 }, // Gurdurr + new(Nest172,2,3,4,SWSH) { Species = 870, Ability = A2 }, // Falinks + new(Nest172,2,3,4,SWSH) { Species = 623, Ability = A2 }, // Golurk + new(Nest172,3,4,5,SWSH) { Species = 534, Ability = A2 }, // Conkeldurr + new(Nest172,4,4,5,SWSH) { Species = 237, Ability = A2 }, // Hitmontop + new(Nest173,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest173,0,1,1,SWSH) { Species = 041, Ability = A3 }, // Zubat + new(Nest173,1,2,2,SWSH) { Species = 029, Ability = A3 }, // Nidoran♀ + new(Nest173,1,2,2,SWSH) { Species = 032, Ability = A3 }, // Nidoran♂ + new(Nest173,2,3,3,SWSH) { Species = 030, Ability = A3 }, // Nidorina + new(Nest173,2,3,3,SWSH) { Species = 033, Ability = A3 }, // Nidorino + new(Nest173,3,4,4,SWSH) { Species = 042, Ability = A4 }, // Golbat + new(Nest173,4,4,4,SWSH) { Species = 031, Ability = A4 }, // Nidoqueen + new(Nest173,4,4,4,SWSH) { Species = 034, Ability = A4 }, // Nidoking + new(Nest174,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest174,0,1,2,SWSH) { Species = 041, Ability = A2 }, // Zubat + new(Nest174,0,1,2,SWSH) { Species = 568, Ability = A2 }, // Trubbish + new(Nest174,1,2,3,SWSH) { Species = 079, Ability = A2, Form = 1 }, // Slowpoke-1 + new(Nest174,2,3,4,SWSH) { Species = 042, Ability = A2 }, // Golbat + new(Nest174,2,3,4,SWSH) { Species = 569, Ability = A2 }, // Garbodor + new(Nest174,3,4,5,SWSH) { Species = 031, Ability = A2 }, // Nidoqueen + new(Nest174,3,4,5,SWSH) { Species = 034, Ability = A2 }, // Nidoking + new(Nest174,4,4,5,SWSH) { Species = 169, Ability = A2 }, // Crobat + new(Nest174,4,4,5,SWSH) { Species = 569, Ability = A2, CanGigantamax = true }, // Garbodor + new(Nest175,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest175,0,1,1,SWSH) { Species = 041, Ability = A3 }, // Zubat + new(Nest175,0,1,1,SWSH) { Species = 714, Ability = A3 }, // Noibat + new(Nest175,1,2,2,SWSH) { Species = 333, Ability = A3 }, // Swablu + new(Nest175,1,2,2,SWSH) { Species = 042, Ability = A3 }, // Golbat + new(Nest175,2,3,3,SWSH) { Species = 042, Ability = A3 }, // Golbat + new(Nest175,2,3,3,SWSH) { Species = 822, Ability = A3 }, // Corvisquire + new(Nest175,3,4,4,SWSH) { Species = 042, Ability = A4 }, // Golbat + new(Nest175,3,4,4,SWSH) { Species = 334, Ability = A4 }, // Altaria + new(Nest175,3,4,4,SWSH) { Species = 715, Ability = A4 }, // Noivern + new(Nest175,4,4,4,SWSH) { Species = 823, Ability = A4 }, // Corviknight + new(Nest175,4,4,4,SWSH) { Species = 169, Ability = A4 }, // Crobat + new(Nest176,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest176,0,1,2,SWSH) { Species = 041, Ability = A2 }, // Zubat + new(Nest176,0,1,2,SWSH) { Species = 527, Ability = A2 }, // Woobat + new(Nest176,1,2,3,SWSH) { Species = 822, Ability = A2 }, // Corvisquire + new(Nest176,1,2,3,SWSH) { Species = 042, Ability = A2 }, // Golbat + new(Nest176,2,3,4,SWSH) { Species = 528, Ability = A2 }, // Swoobat + new(Nest176,2,3,4,SWSH) { Species = 823, Ability = A2 }, // Corviknight + new(Nest176,3,4,5,SWSH) { Species = 142, Ability = A2 }, // Aerodactyl + new(Nest176,3,4,5,SWSH) { Species = 334, Ability = A2 }, // Altaria + new(Nest176,3,4,5,SWSH) { Species = 169, Ability = A2 }, // Crobat + new(Nest176,4,4,5,SWSH) { Species = 715, Ability = A2 }, // Noivern + new(Nest176,4,4,5,SWSH) { Species = 823, Ability = A2, CanGigantamax = true }, // Corviknight + new(Nest177,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest177,0,1,1,SWSH) { Species = 439, Ability = A3 }, // Mime Jr. + new(Nest177,1,2,2,SWSH) { Species = 436, Ability = A3 }, // Bronzor + new(Nest177,1,2,2,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 + new(Nest177,2,3,3,SWSH) { Species = 344, Ability = A3 }, // Claydol + new(Nest177,4,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime + new(Nest177,4,4,4,SWSH) { Species = 437, Ability = A4 }, // Bronzong + new(Nest178,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest178,1,2,3,SWSH) { Species = 122, Ability = A2, Form = 1 }, // Mr. Mime-1 + new(Nest178,1,2,3,SWSH) { Species = 079, Ability = A2, Form = 1 }, // Slowpoke-1 + new(Nest178,2,3,4,SWSH) { Species = 375, Ability = A2 }, // Metang + new(Nest178,3,4,5,SWSH) { Species = 866, Ability = A2 }, // Mr. Rime + new(Nest178,4,4,5,SWSH) { Species = 376, Ability = A2 }, // Metagross + new(Nest179,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest179,0,1,1,SWSH) { Species = 304, Ability = A3 }, // Aron + new(Nest179,1,2,2,SWSH) { Species = 304, Ability = A3 }, // Aron + new(Nest179,2,3,3,SWSH) { Species = 305, Ability = A3 }, // Lairon + new(Nest179,3,4,4,SWSH) { Species = 305, Ability = A4 }, // Lairon + new(Nest179,3,4,4,SWSH) { Species = 703, Ability = A4 }, // Carbink + new(Nest179,4,4,4,SWSH) { Species = 306, Ability = A4 }, // Aggron + new(Nest179,4,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal + new(Nest180,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest180,0,1,2,SWSH) { Species = 304, Ability = A2 }, // Aron + new(Nest180,1,2,3,SWSH) { Species = 305, Ability = A2 }, // Lairon + new(Nest180,2,3,4,SWSH) { Species = 213, Ability = A2 }, // Shuckle + new(Nest180,3,4,5,SWSH) { Species = 839, Ability = A2 }, // Coalossal + new(Nest180,3,4,5,SWSH) { Species = 306, Ability = A2 }, // Aggron + new(Nest180,4,4,5,SWSH) { Species = 306, Ability = A2 }, // Aggron + new(Nest181,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest181,0,1,1,SWSH) { Species = 885, Ability = A3 }, // Dreepy + new(Nest181,0,1,1,SWSH) { Species = 708, Ability = A3 }, // Phantump + new(Nest181,1,2,2,SWSH) { Species = 778, Ability = A3 }, // Mimikyu + new(Nest181,1,2,2,SWSH) { Species = 361, Ability = A3 }, // Snorunt + new(Nest181,2,3,3,SWSH) { Species = 886, Ability = A3 }, // Drakloak + new(Nest181,2,3,3,SWSH) { Species = 778, Ability = A3 }, // Mimikyu + new(Nest181,3,4,4,SWSH) { Species = 362, Ability = A4 }, // Glalie + new(Nest181,3,4,4,SWSH) { Species = 478, Ability = A4 }, // Froslass + new(Nest181,4,4,4,SWSH) { Species = 709, Ability = A4 }, // Trevenant + new(Nest181,4,4,4,SWSH) { Species = 778, Ability = A4 }, // Mimikyu + new(Nest182,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest182,0,1,2,SWSH) { Species = 885, Ability = A2 }, // Dreepy + new(Nest182,1,2,3,SWSH) { Species = 885, Ability = A2 }, // Dreepy + new(Nest182,2,3,4,SWSH) { Species = 709, Ability = A2 }, // Trevenant + new(Nest182,3,4,5,SWSH) { Species = 887, Ability = A2 }, // Dragapult + new(Nest182,4,4,5,SWSH) { Species = 887, Ability = A2 }, // Dragapult + new(Nest183,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest183,0,1,1,SWSH) { Species = 621, Ability = A3 }, // Druddigon + new(Nest183,1,2,2,SWSH) { Species = 696, Ability = A3 }, // Tyrunt + new(Nest183,2,3,3,SWSH) { Species = 147, Ability = A3 }, // Dratini + new(Nest183,3,4,4,SWSH) { Species = 621, Ability = A4 }, // Druddigon + new(Nest183,3,4,4,SWSH) { Species = 697, Ability = A4 }, // Tyrantrum + new(Nest184,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest184,0,1,2,SWSH) { Species = 884, Ability = A2 }, // Duraludon + new(Nest184,1,2,3,SWSH) { Species = 696, Ability = A2 }, // Tyrunt + new(Nest184,2,3,4,SWSH) { Species = 884, Ability = A2 }, // Duraludon + new(Nest184,3,4,5,SWSH) { Species = 149, Ability = A2 }, // Dragonite + new(Nest184,3,4,5,SWSH) { Species = 697, Ability = A2 }, // Tyrantrum + new(Nest184,4,4,5,SWSH) { Species = 884, Ability = A2, CanGigantamax = true }, // Duraludon + new(Nest185,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest185,0,1,1,SWSH) { Species = 215, Ability = A3 }, // Sneasel + new(Nest185,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp + new(Nest185,1,2,2,SWSH) { Species = 859, Ability = A3 }, // Impidimp + new(Nest185,1,2,2,SWSH) { Species = 860, Ability = A3 }, // Morgrem + new(Nest185,2,3,3,SWSH) { Species = 215, Ability = A3 }, // Sneasel + new(Nest185,2,3,3,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 + new(Nest185,3,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl + new(Nest185,3,4,4,SWSH) { Species = 359, Ability = A4 }, // Absol + new(Nest185,3,4,4,SWSH) { Species = 862, Ability = A4 }, // Obstagoon + new(Nest185,4,4,4,SWSH) { Species = 359, Ability = A4 }, // Absol + new(Nest185,4,4,4,SWSH) { Species = 461, Ability = A4 }, // Weavile + new(Nest186,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest186,0,1,2,SWSH) { Species = 859, Ability = A2 }, // Impidimp + new(Nest186,0,1,2,SWSH) { Species = 359, Ability = A2 }, // Absol + new(Nest186,1,2,3,SWSH) { Species = 215, Ability = A2 }, // Sneasel + new(Nest186,2,3,4,SWSH) { Species = 828, Ability = A2 }, // Thievul + new(Nest186,2,3,4,SWSH) { Species = 510, Ability = A2 }, // Liepard + new(Nest186,3,4,5,SWSH) { Species = 359, Ability = A2 }, // Absol + new(Nest186,3,4,5,SWSH) { Species = 861, Ability = A2 }, // Grimmsnarl + new(Nest186,3,4,5,SWSH) { Species = 461, Ability = A2 }, // Weavile + new(Nest186,4,4,5,SWSH) { Species = 359, Ability = A2 }, // Absol + new(Nest186,4,4,5,SWSH) { Species = 861, Ability = A2, CanGigantamax = true }, // Grimmsnarl + new(Nest187,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest187,0,1,1,SWSH) { Species = 304, Ability = A3 }, // Aron + new(Nest187,0,1,1,SWSH) { Species = 632, Ability = A3 }, // Durant + new(Nest187,1,2,2,SWSH) { Species = 304, Ability = A3 }, // Aron + new(Nest187,1,2,2,SWSH) { Species = 374, Ability = A3 }, // Beldum + new(Nest187,2,3,3,SWSH) { Species = 305, Ability = A3 }, // Lairon + new(Nest187,2,3,3,SWSH) { Species = 375, Ability = A3 }, // Metang + new(Nest187,3,4,4,SWSH) { Species = 823, Ability = A4 }, // Corviknight + new(Nest187,3,4,4,SWSH) { Species = 632, Ability = A4 }, // Durant + new(Nest187,3,4,4,SWSH) { Species = 879, Ability = A4 }, // Copperajah + new(Nest187,4,4,4,SWSH) { Species = 306, Ability = A4 }, // Aggron + new(Nest188,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest188,0,1,2,SWSH) { Species = 304, Ability = A2 }, // Aron + new(Nest188,0,1,2,SWSH) { Species = 052, Ability = A2, Form = 2 }, // Meowth-2 + new(Nest188,1,2,3,SWSH) { Species = 632, Ability = A2 }, // Durant + new(Nest188,1,2,3,SWSH) { Species = 305, Ability = A2 }, // Lairon + new(Nest188,2,3,4,SWSH) { Species = 863, Ability = A2 }, // Perrserker + new(Nest188,3,4,5,SWSH) { Species = 879, Ability = A2 }, // Copperajah + new(Nest188,3,4,5,SWSH) { Species = 306, Ability = A2 }, // Aggron + new(Nest188,3,4,5,SWSH) { Species = 376, Ability = A2 }, // Metagross + new(Nest188,4,4,5,SWSH) { Species = 376, Ability = A2 }, // Metagross + new(Nest188,4,4,5,SWSH) { Species = 879, Ability = A2, CanGigantamax = true }, // Copperajah + new(Nest189,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest189,0,1,1,SWSH) { Species = 173, Ability = A3 }, // Cleffa + new(Nest189,0,1,1,SWSH) { Species = 703, Ability = A3 }, // Carbink + new(Nest189,1,2,2,SWSH) { Species = 856, Ability = A3 }, // Hatenna + new(Nest189,1,2,2,SWSH) { Species = 173, Ability = A3 }, // Cleffa + new(Nest189,2,3,3,SWSH) { Species = 857, Ability = A3 }, // Hattrem + new(Nest189,2,3,3,SWSH) { Species = 035, Ability = A3 }, // Clefairy + new(Nest189,3,4,4,SWSH) { Species = 703, Ability = A4 }, // Carbink + new(Nest189,3,4,4,SWSH) { Species = 036, Ability = A4 }, // Clefable + new(Nest189,4,4,4,SWSH) { Species = 547, Ability = A4 }, // Whimsicott + new(Nest189,4,4,4,SWSH) { Species = 858, Ability = A4 }, // Hatterene + new(Nest190,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest190,0,1,2,SWSH) { Species = 703, Ability = A2 }, // Carbink + new(Nest190,0,1,2,SWSH) { Species = 546, Ability = A2 }, // Cottonee + new(Nest190,1,2,3,SWSH) { Species = 035, Ability = A2 }, // Clefairy + new(Nest190,1,2,3,SWSH) { Species = 703, Ability = A2 }, // Carbink + new(Nest190,2,3,4,SWSH) { Species = 703, Ability = A2 }, // Carbink + new(Nest190,2,3,4,SWSH) { Species = 547, Ability = A2 }, // Whimsicott + new(Nest190,3,4,5,SWSH) { Species = 110, Ability = A2, Form = 1 }, // Weezing-1 + new(Nest190,3,4,5,SWSH) { Species = 858, Ability = A2 }, // Hatterene + new(Nest190,3,4,5,SWSH) { Species = 036, Ability = A2 }, // Clefable + new(Nest190,4,4,5,SWSH) { Species = 110, Ability = A2, Form = 1 }, // Weezing-1 + new(Nest190,4,4,5,SWSH) { Species = 858, Ability = A2, CanGigantamax = true }, // Hatterene + new(Nest191,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest191,0,1,1,SWSH) { Species = 854, Ability = A3 }, // Sinistea + new(Nest191,1,2,2,SWSH) { Species = 854, Ability = A3 }, // Sinistea + new(Nest191,2,3,3,SWSH) { Species = 854, Ability = A3 }, // Sinistea + new(Nest191,3,4,4,SWSH) { Species = 854, Ability = A4 }, // Sinistea + new(Nest191,4,4,4,SWSH) { Species = 854, Ability = A4 }, // Sinistea + new(Nest191,2,4,4,SWSH) { Species = 854, Ability = A4, Form = 1 }, // Sinistea-1 + new(Nest192,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest192,0,0,2,SWSH) { Species = 854, Ability = A2 }, // Sinistea + new(Nest192,1,1,2,SWSH) { Species = 854, Ability = A2 }, // Sinistea + new(Nest192,1,1,3,SWSH) { Species = 854, Ability = A2 }, // Sinistea + new(Nest192,2,2,4,SWSH) { Species = 854, Ability = A2 }, // Sinistea + new(Nest192,3,3,4,SWSH) { Species = 854, Ability = A2 }, // Sinistea + new(Nest192,0,3,5,SWSH) { Species = 854, Ability = A2, Form = 1 }, // Sinistea-1 + new(Nest192,4,4,5,SWSH) { Species = 855, Ability = A2 }, // Polteageist + new(Nest192,4,4,5,SWSH) { Species = 855, Ability = A2, Form = 1 }, // Polteageist-1 + new(Nest192,4,4,5,SWSH) { Species = 869, Ability = A2, CanGigantamax = true }, // Alcremie + new(Nest193,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + //new(Nest193,0,1,1,SWSH) { Species = 133, Ability = A3 }, // Eevee + new(Nest193,2,4,2,SWSH) { Species = 133, Ability = A3 }, // Eevee + new(Nest193,2,4,3,SWSH) { Species = 133, Ability = A3 }, // Eevee + new(Nest193,2,4,4,SWSH) { Species = 136, Ability = A3 }, // Flareon + new(Nest193,2,4,4,SWSH) { Species = 135, Ability = A3 }, // Jolteon + new(Nest193,2,4,4,SWSH) { Species = 134, Ability = A3 }, // Vaporeon + new(Nest193,2,4,4,SWSH) { Species = 196, Ability = A4 }, // Espeon + new(Nest193,2,4,4,SWSH) { Species = 197, Ability = A4 }, // Umbreon + new(Nest193,2,4,4,SWSH) { Species = 470, Ability = A4 }, // Leafeon + new(Nest193,2,4,4,SWSH) { Species = 471, Ability = A4 }, // Glaceon + new(Nest193,2,4,4,SWSH) { Species = 700, Ability = A4 }, // Sylveon + new(Nest194,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + //new(Nest194,0,1,2,SWSH) { Species = 133, Ability = A2 }, // Eevee + new(Nest194,2,4,3,SWSH) { Species = 133, Ability = A2 }, // Eevee + new(Nest194,2,4,4,SWSH) { Species = 133, Ability = A2, Gender = 1 }, // Eevee + new(Nest194,2,4,5,SWSH) { Species = 136, Ability = A2 }, // Flareon + new(Nest194,2,4,5,SWSH) { Species = 135, Ability = A2 }, // Jolteon + new(Nest194,2,4,5,SWSH) { Species = 134, Ability = A2 }, // Vaporeon + new(Nest194,2,4,5,SWSH) { Species = 196, Ability = A2 }, // Espeon + new(Nest194,2,4,5,SWSH) { Species = 197, Ability = A2 }, // Umbreon + new(Nest194,2,4,5,SWSH) { Species = 470, Ability = A2 }, // Leafeon + new(Nest194,2,4,5,SWSH) { Species = 471, Ability = A2 }, // Glaceon + new(Nest194,2,4,5,SWSH) { Species = 700, Ability = A2 }, // Sylveon + new(Nest195,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino + new(Nest195,1,2,2,SWSH) { Species = 696, Ability = A3 }, // Tyrunt + new(Nest195,1,2,2,SWSH) { Species = 698, Ability = A3 }, // Amaura + new(Nest195,2,3,3,SWSH) { Species = 348, Ability = A3 }, // Armaldo + new(Nest195,2,3,3,SWSH) { Species = 346, Ability = A3 }, // Cradily + new(Nest195,4,4,4,SWSH) { Species = 142, Ability = A4 }, // Aerodactyl + new(Nest196,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird + new(Nest196,2,2,3,SWSH) { Species = 142, Ability = A2 }, // Aerodactyl + new(Nest196,3,4,5,SWSH) { Species = 142, Ability = A2 }, // Aerodactyl + new(Nest196,4,4,5,SWSH) { Species = 880, Ability = A2 }, // Dracozolt + new(Nest196,4,4,5,SWSH) { Species = 882, Ability = A2 }, // Dracovish + new(Nest196,3,4,5,SWSH) { Species = 881, Ability = A2 }, // Arctozolt + new(Nest196,3,4,5,SWSH) { Species = 883, Ability = A2 }, // Arctovish + }; - internal static readonly EncounterStatic8N[] Nest_Common = - { - new(Nest000,0,0,1,SWSH) { Species = 236, Ability = A3 }, // Tyrogue - new(Nest000,0,0,1,SWSH) { Species = 066, Ability = A3 }, // Machop - new(Nest000,0,1,1,SWSH) { Species = 532, Ability = A3 }, // Timburr - new(Nest000,1,2,2,SWSH) { Species = 067, Ability = A3 }, // Machoke - new(Nest000,1,2,2,SWSH) { Species = 533, Ability = A3 }, // Gurdurr - new(Nest000,4,4,4,SWSH) { Species = 068, Ability = A4 }, // Machamp - new(Nest001,0,0,1,SWSH) { Species = 280, Ability = A3 }, // Ralts - new(Nest001,0,0,1,SWSH) { Species = 517, Ability = A3 }, // Munna - new(Nest001,0,1,1,SWSH) { Species = 677, Ability = A3 }, // Espurr - new(Nest001,0,1,1,SWSH) { Species = 605, Ability = A3 }, // Elgyem - new(Nest001,1,2,2,SWSH) { Species = 281, Ability = A3 }, // Kirlia - new(Nest001,2,4,4,SWSH) { Species = 518, Ability = A3 }, // Musharna - new(Nest001,4,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir - new(Nest002,0,0,1,SWSH) { Species = 438, Ability = A3 }, // Bonsly - new(Nest002,0,1,1,SWSH) { Species = 557, Ability = A3 }, // Dwebble - new(Nest002,1,2,2,SWSH) { Species = 111, Ability = A3 }, // Rhyhorn - new(Nest002,1,2,2,SWSH) { Species = 525, Ability = A3 }, // Boldore - new(Nest002,2,3,3,SWSH) { Species = 689, Ability = A3 }, // Barbaracle - new(Nest002,2,4,4,SWSH) { Species = 112, Ability = A3 }, // Rhydon - new(Nest002,2,4,4,SWSH) { Species = 185, Ability = A3 }, // Sudowoodo - new(Nest002,4,4,4,SWSH) { Species = 213, Ability = A4 }, // Shuckle - new(Nest003,0,0,1,SWSH) { Species = 010, Ability = A3 }, // Caterpie - new(Nest003,0,0,1,SWSH) { Species = 736, Ability = A3 }, // Grubbin - new(Nest003,0,1,1,SWSH) { Species = 290, Ability = A3 }, // Nincada - new(Nest003,0,1,1,SWSH) { Species = 595, Ability = A3 }, // Joltik - new(Nest003,1,2,2,SWSH) { Species = 011, Ability = A3 }, // Metapod - new(Nest003,1,2,2,SWSH) { Species = 632, Ability = A3 }, // Durant - new(Nest003,2,3,3,SWSH) { Species = 737, Ability = A3 }, // Charjabug - new(Nest003,2,4,4,SWSH) { Species = 291, Ability = A3 }, // Ninjask - new(Nest003,2,4,4,SWSH) { Species = 012, Ability = A3 }, // Butterfree - new(Nest003,3,4,4,SWSH) { Species = 596, Ability = A4 }, // Galvantula - new(Nest003,4,4,4,SWSH) { Species = 738, Ability = A4 }, // Vikavolt - new(Nest003,4,4,4,SWSH) { Species = 632, Ability = A4 }, // Durant - new(Nest004,0,0,1,SWSH) { Species = 010, Ability = A3 }, // Caterpie - new(Nest004,0,0,1,SWSH) { Species = 415, Ability = A3 }, // Combee - new(Nest004,0,1,1,SWSH) { Species = 742, Ability = A3 }, // Cutiefly - new(Nest004,0,1,1,SWSH) { Species = 824, Ability = A3 }, // Blipbug - new(Nest004,1,2,2,SWSH) { Species = 595, Ability = A3 }, // Joltik - new(Nest004,1,2,2,SWSH) { Species = 011, Ability = A3 }, // Metapod - new(Nest004,2,3,3,SWSH) { Species = 825, Ability = A3 }, // Dottler - new(Nest004,2,4,4,SWSH) { Species = 596, Ability = A3 }, // Galvantula - new(Nest004,2,4,4,SWSH) { Species = 012, Ability = A3 }, // Butterfree - new(Nest004,3,4,4,SWSH) { Species = 743, Ability = A4 }, // Ribombee - new(Nest004,4,4,4,SWSH) { Species = 416, Ability = A4 }, // Vespiquen - new(Nest004,4,4,4,SWSH) { Species = 826, Ability = A4 }, // Orbeetle - new(Nest005,0,0,1,SWSH) { Species = 092, Ability = A3 }, // Gastly - new(Nest005,0,0,1,SWSH) { Species = 355, Ability = A3 }, // Duskull - new(Nest005,0,1,1,SWSH) { Species = 425, Ability = A3 }, // Drifloon - new(Nest005,0,1,1,SWSH) { Species = 708, Ability = A3 }, // Phantump - new(Nest005,0,1,1,SWSH) { Species = 592, Ability = A3 }, // Frillish - new(Nest005,1,2,2,SWSH) { Species = 710, Ability = A3 }, // Pumpkaboo - new(Nest005,2,3,3,SWSH) { Species = 093, Ability = A3 }, // Haunter - new(Nest005,2,4,4,SWSH) { Species = 356, Ability = A3 }, // Dusclops - new(Nest005,2,4,4,SWSH) { Species = 426, Ability = A3 }, // Drifblim - new(Nest005,3,4,4,SWSH) { Species = 709, Ability = A4 }, // Trevenant - new(Nest005,4,4,4,SWSH) { Species = 711, Ability = A4 }, // Gourgeist - new(Nest005,4,4,4,SWSH) { Species = 593, Ability = A4 }, // Jellicent - //new(Nest006,0,0,1,SWSH) { Species = 129, Ability = A3 }, // Magikarp - //new(Nest006,0,0,1,SWSH) { Species = 458, Ability = A3 }, // Mantyke - new(Nest006,2,2,2,SWSH) { Species = 320, Ability = A3 }, // Wailmer - new(Nest006,2,3,3,SWSH) { Species = 224, Ability = A3 }, // Octillery - new(Nest006,2,4,4,SWSH) { Species = 226, Ability = A3 }, // Mantine - new(Nest006,2,4,4,SWSH) { Species = 171, Ability = A3 }, // Lanturn - new(Nest006,3,4,4,SWSH) { Species = 321, Ability = A4 }, // Wailord - new(Nest006,4,4,4,SWSH) { Species = 746, Ability = A4 }, // Wishiwashi - new(Nest006,4,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados - //new(Nest007,0,0,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle - //new(Nest007,0,0,1,SWSH) { Species = 846, Ability = A3 }, // Arrokuda - //new(Nest007,0,1,1,SWSH) { Species = 422, Ability = A3, Form = 1 }, // Shellos-1 - //new(Nest007,0,1,1,SWSH) { Species = 751, Ability = A3 }, // Dewpider - new(Nest007,2,2,2,SWSH) { Species = 320, Ability = A3 }, // Wailmer - new(Nest007,2,3,3,SWSH) { Species = 746, Ability = A3 }, // Wishiwashi - new(Nest007,2,4,4,SWSH) { Species = 834, Ability = A3 }, // Drednaw - new(Nest007,2,4,4,SWSH) { Species = 847, Ability = A3 }, // Barraskewda - new(Nest007,3,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid - new(Nest007,4,4,4,SWSH) { Species = 423, Ability = A4, Form = 1 }, // Gastrodon-1 - new(Nest007,4,4,4,SWSH) { Species = 321, Ability = A4 }, // Wailord - new(Nest008,0,0,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle - new(Nest008,0,0,1,SWSH) { Species = 194, Ability = A3 }, // Wooper - new(Nest008,0,1,1,SWSH) { Species = 535, Ability = A3 }, // Tympole - new(Nest008,0,1,1,SWSH) { Species = 341, Ability = A3 }, // Corphish - new(Nest008,1,2,2,SWSH) { Species = 536, Ability = A3 }, // Palpitoad - new(Nest008,2,3,3,SWSH) { Species = 834, Ability = A3 }, // Drednaw - new(Nest008,2,4,4,SWSH) { Species = 195, Ability = A3 }, // Quagsire - new(Nest008,2,4,4,SWSH) { Species = 771, Ability = A3 }, // Pyukumuku - new(Nest008,3,4,4,SWSH) { Species = 091, Ability = A4 }, // Cloyster - new(Nest008,4,4,4,SWSH) { Species = 537, Ability = A4 }, // Seismitoad - new(Nest008,4,4,4,SWSH) { Species = 342, Ability = A4 }, // Crawdaunt - new(Nest009,0,0,1,SWSH) { Species = 236, Ability = A3 }, // Tyrogue - new(Nest009,0,0,1,SWSH) { Species = 759, Ability = A3 }, // Stufful - new(Nest009,0,1,1,SWSH) { Species = 852, Ability = A3 }, // Clobbopus - new(Nest009,0,1,1,SWSH) { Species = 674, Ability = A3 }, // Pancham - new(Nest009,2,4,4,SWSH) { Species = 760, Ability = A3 }, // Bewear - new(Nest009,2,4,4,SWSH) { Species = 675, Ability = A3 }, // Pangoro - new(Nest009,2,4,4,SWSH) { Species = 701, Ability = A3 }, // Hawlucha - new(Nest009,4,4,4,SWSH) { Species = 853, Ability = A4 }, // Grapploct - new(Nest009,4,4,4,SWSH) { Species = 870, Ability = A4 }, // Falinks - new(Nest010,0,0,1,SWSH) { Species = 599, Ability = A3 }, // Klink - new(Nest010,0,0,1,SWSH) { Species = 052, Ability = A3, Form = 2 }, // Meowth-2 - new(Nest010,0,1,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor - new(Nest010,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed - new(Nest010,1,1,2,SWSH) { Species = 624, Ability = A3 }, // Pawniard - new(Nest010,1,2,2,SWSH) { Species = 878, Ability = A3 }, // Cufant - new(Nest010,2,4,4,SWSH) { Species = 600, Ability = A3 }, // Klang - new(Nest010,2,4,4,SWSH) { Species = 863, Ability = A3 }, // Perrserker - new(Nest010,2,4,4,SWSH) { Species = 437, Ability = A3 }, // Bronzong - new(Nest010,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp - new(Nest010,4,4,4,SWSH) { Species = 601, Ability = A4 }, // Klinklang - new(Nest010,4,4,4,SWSH) { Species = 879, Ability = A4 }, // Copperajah - new(Nest011,0,0,1,SWSH) { Species = 599, Ability = A3 }, // Klink - new(Nest011,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor - new(Nest011,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed - new(Nest011,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard - new(Nest011,1,1,2,SWSH) { Species = 599, Ability = A3 }, // Klink - new(Nest011,1,2,2,SWSH) { Species = 436, Ability = A3 }, // Bronzor - new(Nest011,2,4,4,SWSH) { Species = 208, Ability = A3 }, // Steelix - new(Nest011,2,4,4,SWSH) { Species = 598, Ability = A3 }, // Ferrothorn - new(Nest011,2,4,4,SWSH) { Species = 437, Ability = A3 }, // Bronzong - new(Nest011,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp - new(Nest011,4,4,4,SWSH) { Species = 777, Ability = A4 }, // Togedemaru - new(Nest012,0,0,1,SWSH) { Species = 439, Ability = A3 }, // Mime Jr. - new(Nest012,0,0,1,SWSH) { Species = 824, Ability = A3 }, // Blipbug - new(Nest012,2,3,3,SWSH) { Species = 561, Ability = A3 }, // Sigilyph - new(Nest012,2,3,3,SWSH) { Species = 178, Ability = A3 }, // Xatu - new(Nest012,4,4,4,SWSH) { Species = 858, Ability = A4 }, // Hatterene - new(Nest013,0,0,1,SWSH) { Species = 439, Ability = A3 }, // Mime Jr. - new(Nest013,0,0,1,SWSH) { Species = 360, Ability = A3 }, // Wynaut - new(Nest013,0,1,1,SWSH) { Species = 177, Ability = A3 }, // Natu - new(Nest013,0,1,1,SWSH) { Species = 343, Ability = A3 }, // Baltoy - new(Nest013,1,1,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor - new(Nest013,1,3,3,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 - new(Nest013,2,3,3,SWSH) { Species = 561, Ability = A3 }, // Sigilyph - new(Nest013,2,3,3,SWSH) { Species = 178, Ability = A3 }, // Xatu - new(Nest013,3,4,4,SWSH) { Species = 344, Ability = A4 }, // Claydol - new(Nest013,4,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime - new(Nest013,4,4,4,SWSH) { Species = 202, Ability = A4 }, // Wobbuffet - new(Nest014,0,0,1,SWSH) { Species = 837, Ability = A3 }, // Rolycoly - new(Nest014,0,1,1,SWSH) { Species = 688, Ability = A3 }, // Binacle - new(Nest014,1,1,1,SWSH) { Species = 838, Ability = A3 }, // Carkol - new(Nest014,1,2,2,SWSH) { Species = 525, Ability = A3 }, // Boldore - new(Nest014,2,3,3,SWSH) { Species = 558, Ability = A3 }, // Crustle - new(Nest014,2,4,4,SWSH) { Species = 689, Ability = A3 }, // Barbaracle - new(Nest014,4,4,4,SWSH) { Species = 464, Ability = A4 }, // Rhyperior - new(Nest015,0,0,1,SWSH) { Species = 050, Ability = A3 }, // Diglett - new(Nest015,0,0,1,SWSH) { Species = 749, Ability = A3 }, // Mudbray - new(Nest015,0,1,1,SWSH) { Species = 290, Ability = A3 }, // Nincada - new(Nest015,0,1,1,SWSH) { Species = 529, Ability = A3 }, // Drilbur - new(Nest015,1,1,1,SWSH) { Species = 095, Ability = A3 }, // Onix - new(Nest015,1,2,2,SWSH) { Species = 339, Ability = A3 }, // Barboach - new(Nest015,2,3,3,SWSH) { Species = 208, Ability = A3 }, // Steelix - new(Nest015,2,4,4,SWSH) { Species = 340, Ability = A3 }, // Whiscash - new(Nest015,2,4,4,SWSH) { Species = 660, Ability = A3 }, // Diggersby - new(Nest015,3,4,4,SWSH) { Species = 051, Ability = A4 }, // Dugtrio - new(Nest015,4,4,4,SWSH) { Species = 530, Ability = A4 }, // Excadrill - new(Nest015,4,4,4,SWSH) { Species = 750, Ability = A4 }, // Mudsdale - new(Nest016,0,0,1,SWSH) { Species = 843, Ability = A3 }, // Silicobra - new(Nest016,0,0,1,SWSH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 - new(Nest016,0,1,1,SWSH) { Species = 449, Ability = A3 }, // Hippopotas - new(Nest016,1,2,2,SWSH) { Species = 221, Ability = A3 }, // Piloswine - new(Nest016,4,4,4,SWSH) { Species = 867, Ability = A3 }, // Runerigus - new(Nest016,4,4,4,SWSH) { Species = 844, Ability = A4 }, // Sandaconda - new(Nest017,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede - new(Nest017,0,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit - new(Nest017,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick - new(Nest017,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal - new(Nest018,0,0,1,SWSH) { Species = 757, Ability = A3 }, // Salandit - new(Nest018,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick - new(Nest018,1,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit - new(Nest018,4,4,4,SWSH) { Species = 609, Ability = A4 }, // Chandelure - new(Nest019,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede - new(Nest019,0,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit - new(Nest019,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick - new(Nest019,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal - new(Nest019,3,4,4,SWSH) { Species = 851, Ability = A4 }, // Centiskorch - new(Nest019,4,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal - new(Nest020,0,0,1,SWSH) { Species = 582, Ability = A3 }, // Vanillite - new(Nest020,0,0,1,SWSH) { Species = 220, Ability = A3 }, // Swinub - new(Nest020,0,1,1,SWSH) { Species = 459, Ability = A3 }, // Snover - new(Nest020,0,1,1,SWSH) { Species = 712, Ability = A3 }, // Bergmite - new(Nest020,1,1,1,SWSH) { Species = 225, Ability = A3 }, // Delibird - new(Nest020,1,2,2,SWSH) { Species = 583, Ability = A3 }, // Vanillish - new(Nest020,2,3,3,SWSH) { Species = 221, Ability = A3 }, // Piloswine - new(Nest020,2,4,4,SWSH) { Species = 713, Ability = A3 }, // Avalugg - new(Nest020,2,4,4,SWSH) { Species = 460, Ability = A3 }, // Abomasnow - new(Nest020,3,4,4,SWSH) { Species = 091, Ability = A4 }, // Cloyster - new(Nest020,4,4,4,SWSH) { Species = 584, Ability = A4 }, // Vanilluxe - new(Nest020,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras - new(Nest021,0,0,1,SWSH) { Species = 220, Ability = A3 }, // Swinub - new(Nest021,0,0,1,SWSH) { Species = 613, Ability = A3 }, // Cubchoo - new(Nest021,0,1,1,SWSH) { Species = 872, Ability = A3 }, // Snom - new(Nest021,0,1,1,SWSH) { Species = 215, Ability = A3 }, // Sneasel - new(Nest021,1,1,1,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 - new(Nest021,1,2,2,SWSH) { Species = 221, Ability = A3 }, // Piloswine - new(Nest021,2,3,3,SWSH) { Species = 091, Ability = A3 }, // Cloyster - new(Nest021,2,4,4,SWSH) { Species = 614, Ability = A3 }, // Beartic - new(Nest021,2,4,4,SWSH) { Species = 866, Ability = A3 }, // Mr. Rime - new(Nest021,3,4,4,SWSH) { Species = 473, Ability = A4 }, // Mamoswine - new(Nest021,4,4,4,SWSH) { Species = 873, Ability = A4 }, // Frosmoth - new(Nest021,4,4,4,SWSH) { Species = 461, Ability = A4 }, // Weavile - new(Nest022,0,0,1,SWSH) { Species = 361, Ability = A3 }, // Snorunt - new(Nest022,0,0,1,SWSH) { Species = 872, Ability = A3 }, // Snom - new(Nest022,0,1,1,SWSH) { Species = 215, Ability = A3 }, // Sneasel - new(Nest022,1,1,2,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 - new(Nest022,1,2,3,SWSH) { Species = 459, Ability = A3 }, // Snover - new(Nest022,2,3,3,SWSH) { Species = 460, Ability = A3 }, // Abomasnow - new(Nest022,2,4,4,SWSH) { Species = 362, Ability = A3 }, // Glalie - new(Nest022,2,4,4,SWSH) { Species = 866, Ability = A3 }, // Mr. Rime - new(Nest022,3,4,4,SWSH) { Species = 873, Ability = A4 }, // Frosmoth - new(Nest022,4,4,4,SWSH) { Species = 478, Ability = A4 }, // Froslass - new(Nest023,0,0,1,SWSH) { Species = 172, Ability = A3 }, // Pichu - new(Nest023,0,0,1,SWSH) { Species = 309, Ability = A3 }, // Electrike - new(Nest023,0,1,1,SWSH) { Species = 595, Ability = A3 }, // Joltik - new(Nest023,0,1,1,SWSH) { Species = 170, Ability = A3 }, // Chinchou - new(Nest023,1,1,2,SWSH) { Species = 737, Ability = A3 }, // Charjabug - new(Nest023,1,2,3,SWSH) { Species = 025, Ability = A3 }, // Pikachu - new(Nest023,2,3,3,SWSH) { Species = 025, Ability = A3 }, // Pikachu - new(Nest023,2,4,4,SWSH) { Species = 310, Ability = A3 }, // Manectric - new(Nest023,2,4,4,SWSH) { Species = 171, Ability = A3 }, // Lanturn - new(Nest023,3,4,4,SWSH) { Species = 596, Ability = A4 }, // Galvantula - new(Nest023,4,4,4,SWSH) { Species = 738, Ability = A4 }, // Vikavolt - new(Nest023,4,4,4,SWSH) { Species = 026, Ability = A4 }, // Raichu - new(Nest024,0,0,1,SWSH) { Species = 835, Ability = A3 }, // Yamper - new(Nest024,0,0,1,SWSH) { Species = 694, Ability = A3 }, // Helioptile - new(Nest024,0,1,1,SWSH) { Species = 848, Ability = A3 }, // Toxel - new(Nest024,0,1,1,SWSH) { Species = 170, Ability = A3 }, // Chinchou - new(Nest024,1,1,2,SWSH) { Species = 025, Ability = A3 }, // Pikachu - new(Nest024,1,2,3,SWSH) { Species = 171, Ability = A3 }, // Lanturn - new(Nest024,2,3,3,SWSH) { Species = 836, Ability = A3 }, // Boltund - new(Nest024,2,4,4,SWSH) { Species = 695, Ability = A3 }, // Heliolisk - new(Nest024,2,4,4,SWSH) { Species = 849, Ability = A3 }, // Toxtricity - new(Nest024,3,4,4,SWSH) { Species = 871, Ability = A4 }, // Pincurchin - new(Nest024,4,4,4,SWSH) { Species = 777, Ability = A4 }, // Togedemaru - new(Nest024,4,4,4,SWSH) { Species = 877, Ability = A4 }, // Morpeko - new(Nest025,0,0,1,SWSH) { Species = 406, Ability = A3 }, // Budew - new(Nest025,0,1,1,SWSH) { Species = 761, Ability = A3 }, // Bounsweet - new(Nest025,0,1,1,SWSH) { Species = 043, Ability = A3 }, // Oddish - new(Nest025,1,2,3,SWSH) { Species = 315, Ability = A3 }, // Roselia - new(Nest025,2,3,3,SWSH) { Species = 044, Ability = A3 }, // Gloom - new(Nest025,2,4,4,SWSH) { Species = 762, Ability = A3 }, // Steenee - new(Nest025,3,4,4,SWSH) { Species = 763, Ability = A4 }, // Tsareena - new(Nest025,4,4,4,SWSH) { Species = 045, Ability = A4 }, // Vileplume - new(Nest025,4,4,4,SWSH) { Species = 182, Ability = A4 }, // Bellossom - new(Nest026,0,0,1,SWSH) { Species = 406, Ability = A3 }, // Budew - new(Nest026,0,0,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur - new(Nest026,0,1,1,SWSH) { Species = 546, Ability = A3 }, // Cottonee - new(Nest026,0,1,1,SWSH) { Species = 840, Ability = A3 }, // Applin - new(Nest026,1,1,2,SWSH) { Species = 420, Ability = A3 }, // Cherubi - new(Nest026,1,2,2,SWSH) { Species = 315, Ability = A3 }, // Roselia - new(Nest026,2,3,3,SWSH) { Species = 597, Ability = A3 }, // Ferroseed - new(Nest026,2,4,4,SWSH) { Species = 598, Ability = A3 }, // Ferrothorn - new(Nest026,2,4,4,SWSH) { Species = 421, Ability = A3 }, // Cherrim - new(Nest026,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss - new(Nest026,4,4,4,SWSH) { Species = 547, Ability = A4 }, // Whimsicott - new(Nest027,0,0,1,SWSH) { Species = 710, Ability = A3, Form = 1 }, // Pumpkaboo-1 - new(Nest027,0,0,1,SWSH) { Species = 708, Ability = A3 }, // Phantump - new(Nest027,0,1,1,SWSH) { Species = 710, Ability = A3 }, // Pumpkaboo - new(Nest027,0,1,1,SWSH) { Species = 755, Ability = A3 }, // Morelull - new(Nest027,1,1,2,SWSH) { Species = 710, Ability = A3, Form = 2 }, // Pumpkaboo-2 - new(Nest027,1,2,2,SWSH) { Species = 315, Ability = A3 }, // Roselia - new(Nest027,2,3,3,SWSH) { Species = 756, Ability = A3 }, // Shiinotic - new(Nest027,2,4,4,SWSH) { Species = 556, Ability = A3 }, // Maractus - new(Nest027,2,4,4,SWSH) { Species = 709, Ability = A3 }, // Trevenant - new(Nest027,3,4,4,SWSH) { Species = 711, Ability = A4 }, // Gourgeist - new(Nest027,4,4,4,SWSH) { Species = 781, Ability = A4 }, // Dhelmise - new(Nest027,4,4,4,SWSH) { Species = 710, Ability = A4, Form = 3 }, // Pumpkaboo-3 - new(Nest028,0,0,1,SWSH) { Species = 434, Ability = A3 }, // Stunky - new(Nest028,0,0,1,SWSH) { Species = 568, Ability = A3 }, // Trubbish - new(Nest028,0,1,1,SWSH) { Species = 451, Ability = A3 }, // Skorupi - new(Nest028,1,2,2,SWSH) { Species = 315, Ability = A3 }, // Roselia - new(Nest028,2,3,3,SWSH) { Species = 211, Ability = A3 }, // Qwilfish - new(Nest028,2,4,4,SWSH) { Species = 452, Ability = A3 }, // Drapion - new(Nest028,2,4,4,SWSH) { Species = 045, Ability = A3 }, // Vileplume - new(Nest028,4,4,4,SWSH) { Species = 569, Ability = A4 }, // Garbodor - new(Nest029,0,0,1,SWSH) { Species = 848, Ability = A3 }, // Toxel - new(Nest029,0,0,1,SWSH) { Species = 092, Ability = A3 }, // Gastly - new(Nest029,0,1,1,SWSH) { Species = 451, Ability = A3 }, // Skorupi - new(Nest029,0,1,1,SWSH) { Species = 043, Ability = A3 }, // Oddish - new(Nest029,1,1,2,SWSH) { Species = 044, Ability = A3 }, // Gloom - new(Nest029,1,2,2,SWSH) { Species = 093, Ability = A3 }, // Haunter - new(Nest029,2,3,3,SWSH) { Species = 109, Ability = A3 }, // Koffing - new(Nest029,2,4,4,SWSH) { Species = 211, Ability = A3 }, // Qwilfish - new(Nest029,2,4,4,SWSH) { Species = 045, Ability = A3 }, // Vileplume - new(Nest029,3,4,4,SWSH) { Species = 315, Ability = A4 }, // Roselia - new(Nest029,4,4,4,SWSH) { Species = 849, Ability = A4 }, // Toxtricity - new(Nest029,4,4,4,SWSH) { Species = 110, Ability = A4, Form = 1 }, // Weezing-1 - new(Nest030,0,0,1,SWSH) { Species = 519, Ability = A3 }, // Pidove - new(Nest030,0,0,1,SWSH) { Species = 163, Ability = A3 }, // Hoothoot - new(Nest030,0,1,1,SWSH) { Species = 177, Ability = A3 }, // Natu - new(Nest030,1,1,2,SWSH) { Species = 527, Ability = A3 }, // Woobat - new(Nest030,1,2,2,SWSH) { Species = 520, Ability = A3 }, // Tranquill - new(Nest030,2,3,3,SWSH) { Species = 521, Ability = A3 }, // Unfezant - new(Nest030,2,4,4,SWSH) { Species = 164, Ability = A3 }, // Noctowl - new(Nest030,2,4,4,SWSH) { Species = 528, Ability = A3 }, // Swoobat - new(Nest030,3,4,4,SWSH) { Species = 178, Ability = A4 }, // Xatu - new(Nest030,4,4,4,SWSH) { Species = 561, Ability = A4 }, // Sigilyph - new(Nest031,0,0,1,SWSH) { Species = 821, Ability = A3 }, // Rookidee - new(Nest031,0,0,1,SWSH) { Species = 714, Ability = A3 }, // Noibat - new(Nest031,0,1,1,SWSH) { Species = 278, Ability = A3 }, // Wingull - new(Nest031,0,1,1,SWSH) { Species = 177, Ability = A3 }, // Natu - new(Nest031,1,1,2,SWSH) { Species = 425, Ability = A3 }, // Drifloon - new(Nest031,1,2,2,SWSH) { Species = 822, Ability = A3 }, // Corvisquire - new(Nest031,2,3,3,SWSH) { Species = 426, Ability = A3 }, // Drifblim - new(Nest031,2,4,4,SWSH) { Species = 279, Ability = A3 }, // Pelipper - new(Nest031,2,4,4,SWSH) { Species = 178, Ability = A3 }, // Xatu - new(Nest031,3,4,4,SWSH) { Species = 823, Ability = A4 }, // Corviknight - new(Nest031,4,4,4,SWSH) { Species = 701, Ability = A4 }, // Hawlucha - new(Nest031,4,4,4,SWSH) { Species = 845, Ability = A4 }, // Cramorant - new(Nest032,0,0,1,SWSH) { Species = 173, Ability = A3 }, // Cleffa - new(Nest032,0,0,1,SWSH) { Species = 175, Ability = A3 }, // Togepi - new(Nest032,0,1,1,SWSH) { Species = 742, Ability = A3 }, // Cutiefly - new(Nest032,1,1,2,SWSH) { Species = 035, Ability = A3 }, // Clefairy - new(Nest032,1,2,2,SWSH) { Species = 755, Ability = A3 }, // Morelull - new(Nest032,2,3,3,SWSH) { Species = 176, Ability = A3 }, // Togetic - new(Nest032,2,4,4,SWSH) { Species = 036, Ability = A3 }, // Clefable - new(Nest032,2,4,4,SWSH) { Species = 743, Ability = A3 }, // Ribombee - new(Nest032,3,4,4,SWSH) { Species = 756, Ability = A4 }, // Shiinotic - new(Nest032,4,4,4,SWSH) { Species = 468, Ability = A4 }, // Togekiss - new(Nest033,0,0,1,SWSH) { Species = 439, Ability = A3 }, // Mime Jr. - new(Nest033,0,0,1,SWSH) { Species = 868, Ability = A3 }, // Milcery - new(Nest033,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp - new(Nest033,0,1,1,SWSH) { Species = 280, Ability = A3 }, // Ralts - new(Nest033,1,1,2,SWSH) { Species = 035, Ability = A3 }, // Clefairy - new(Nest033,1,2,2,SWSH) { Species = 281, Ability = A3 }, // Kirlia - new(Nest033,2,3,3,SWSH) { Species = 860, Ability = A3 }, // Morgrem - new(Nest033,2,4,4,SWSH) { Species = 036, Ability = A3 }, // Clefable - new(Nest033,2,4,4,SWSH) { Species = 282, Ability = A3 }, // Gardevoir - new(Nest033,3,4,4,SWSH) { Species = 869, Ability = A4 }, // Alcremie - new(Nest033,4,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl - new(Nest034,0,0,1,SWSH) { Species = 509, Ability = A3 }, // Purrloin - new(Nest034,0,0,1,SWSH) { Species = 434, Ability = A3 }, // Stunky - new(Nest034,0,1,1,SWSH) { Species = 215, Ability = A3 }, // Sneasel - new(Nest034,0,1,1,SWSH) { Species = 686, Ability = A3 }, // Inkay - new(Nest034,1,1,2,SWSH) { Species = 624, Ability = A3 }, // Pawniard - new(Nest034,1,2,2,SWSH) { Species = 510, Ability = A3 }, // Liepard - new(Nest034,2,3,3,SWSH) { Species = 435, Ability = A3 }, // Skuntank - new(Nest034,2,4,4,SWSH) { Species = 461, Ability = A3 }, // Weavile - new(Nest034,2,4,4,SWSH) { Species = 687, Ability = A3 }, // Malamar - new(Nest034,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp - new(Nest034,4,4,4,SWSH) { Species = 342, Ability = A4 }, // Crawdaunt - new(Nest035,0,0,1,SWSH) { Species = 827, Ability = A3 }, // Nickit - new(Nest035,0,0,1,SWSH) { Species = 263, Ability = A3, Form = 1 }, // Zigzagoon-1 - new(Nest035,0,1,1,SWSH) { Species = 509, Ability = A3 }, // Purrloin - new(Nest035,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp - new(Nest035,1,2,2,SWSH) { Species = 828, Ability = A3 }, // Thievul - new(Nest035,2,3,3,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 - new(Nest035,2,4,4,SWSH) { Species = 860, Ability = A3 }, // Morgrem - new(Nest035,2,4,4,SWSH) { Species = 861, Ability = A3 }, // Grimmsnarl - new(Nest035,4,4,4,SWSH) { Species = 862, Ability = A4 }, // Obstagoon - new(Nest036,0,0,1,SWSH) { Species = 714, Ability = A3 }, // Noibat - new(Nest036,0,1,1,SWSH) { Species = 714, Ability = A3 }, // Noibat - new(Nest036,1,2,2,SWSH) { Species = 329, Ability = A3 }, // Vibrava - //new(Nest037,0,0,1,SWSH) { Species = 714, Ability = A3 }, // Noibat - //new(Nest037,0,0,1,SWSH) { Species = 840, Ability = A3 }, // Applin - //new(Nest037,0,1,1,SWSH) { Species = 885, Ability = A3 }, // Dreepy - //new(Nest037,1,1,2,SWSH) { Species = 714, Ability = A3 }, // Noibat - new(Nest037,2,2,2,SWSH) { Species = 840, Ability = A3 }, // Applin - new(Nest037,2,3,3,SWSH) { Species = 886, Ability = A3 }, // Drakloak - new(Nest037,2,4,4,SWSH) { Species = 715, Ability = A3 }, // Noivern - new(Nest037,4,4,4,SWSH) { Species = 887, Ability = A4 }, // Dragapult - new(Nest038,0,0,1,SWSH) { Species = 659, Ability = A3 }, // Bunnelby - new(Nest038,0,0,1,SWSH) { Species = 163, Ability = A3 }, // Hoothoot - new(Nest038,0,1,1,SWSH) { Species = 519, Ability = A3 }, // Pidove - new(Nest038,0,1,1,SWSH) { Species = 572, Ability = A3 }, // Minccino - new(Nest038,1,1,2,SWSH) { Species = 694, Ability = A3 }, // Helioptile - new(Nest038,1,2,2,SWSH) { Species = 759, Ability = A3 }, // Stufful - new(Nest038,2,3,3,SWSH) { Species = 660, Ability = A3 }, // Diggersby - new(Nest038,2,4,4,SWSH) { Species = 164, Ability = A3 }, // Noctowl - new(Nest038,2,4,4,SWSH) { Species = 521, Ability = A3 }, // Unfezant - new(Nest038,3,4,4,SWSH) { Species = 695, Ability = A4 }, // Heliolisk - new(Nest038,4,4,4,SWSH) { Species = 573, Ability = A4 }, // Cinccino - new(Nest038,4,4,4,SWSH) { Species = 760, Ability = A4 }, // Bewear - new(Nest039,0,0,1,SWSH) { Species = 819, Ability = A3 }, // Skwovet - new(Nest039,0,0,1,SWSH) { Species = 831, Ability = A3 }, // Wooloo - new(Nest039,0,1,1,SWSH) { Species = 263, Ability = A3, Form = 1 }, // Zigzagoon-1 - new(Nest039,0,1,1,SWSH) { Species = 446, Ability = A3 }, // Munchlax - new(Nest039,1,2,2,SWSH) { Species = 820, Ability = A3 }, // Greedent - new(Nest039,2,3,3,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 - new(Nest039,2,4,4,SWSH) { Species = 820, Ability = A3 }, // Greedent - new(Nest039,2,4,4,SWSH) { Species = 832, Ability = A3 }, // Dubwool - new(Nest039,3,4,4,SWSH) { Species = 660, Ability = A4 }, // Diggersby - new(Nest039,4,4,4,SWSH) { Species = 143, Ability = A4 }, // Snorlax - //new(Nest040,0,0,1,SWSH) { Species = 535, Ability = A3 }, // Tympole - //new(Nest040,0,0,1,SWSH) { Species = 090, Ability = A3 }, // Shellder - //new(Nest040,0,1,1,SWSH) { Species = 170, Ability = A3 }, // Chinchou - new(Nest040,2,2,2,SWSH) { Species = 846, Ability = A3 }, // Arrokuda - new(Nest040,2,4,4,SWSH) { Species = 171, Ability = A3 }, // Lanturn - new(Nest040,4,4,4,SWSH) { Species = 847, Ability = A4 }, // Barraskewda - //new(Nest041,0,0,1,SWSH) { Species = 422, Ability = A3, Form = 1 }, // Shellos-1 - //new(Nest041,0,0,1,SWSH) { Species = 098, Ability = A3 }, // Krabby - //new(Nest041,0,1,1,SWSH) { Species = 341, Ability = A3 }, // Corphish - //new(Nest041,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle - //new(Nest041,1,1,2,SWSH) { Species = 688, Ability = A3 }, // Binacle - new(Nest041,2,2,2,SWSH) { Species = 771, Ability = A3 }, // Pyukumuku - new(Nest041,2,3,3,SWSH) { Species = 099, Ability = A3 }, // Kingler - new(Nest041,2,4,4,SWSH) { Species = 342, Ability = A3 }, // Crawdaunt - new(Nest041,2,4,4,SWSH) { Species = 689, Ability = A3 }, // Barbaracle - new(Nest041,3,4,4,SWSH) { Species = 423, Ability = A4, Form = 1 }, // Gastrodon-1 - new(Nest041,4,4,4,SWSH) { Species = 593, Ability = A4 }, // Jellicent - new(Nest041,4,4,4,SWSH) { Species = 834, Ability = A4 }, // Drednaw - new(Nest042,0,0,1,SWSH) { Species = 092, Ability = A3 }, // Gastly - new(Nest042,0,0,1,SWSH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 - new(Nest042,0,1,1,SWSH) { Species = 854, Ability = A3 }, // Sinistea - new(Nest042,0,1,1,SWSH) { Species = 355, Ability = A3 }, // Duskull - new(Nest042,1,2,2,SWSH) { Species = 093, Ability = A3 }, // Haunter - new(Nest042,2,3,3,SWSH) { Species = 356, Ability = A3 }, // Dusclops - new(Nest042,4,4,4,SWSH) { Species = 477, Ability = A4 }, // Dusknoir - new(Nest042,4,4,4,SWSH) { Species = 094, Ability = A4 }, // Gengar - //new(Nest043,0,0,1,SWSH) { Species = 129, Ability = A3 }, // Magikarp - //new(Nest043,0,0,1,SWSH) { Species = 349, Ability = A3 }, // Feebas - //new(Nest043,0,1,1,SWSH) { Species = 846, Ability = A3 }, // Arrokuda - //new(Nest043,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle - new(Nest043,2,2,2,SWSH) { Species = 747, Ability = A3 }, // Mareanie - new(Nest043,2,3,3,SWSH) { Species = 211, Ability = A3 }, // Qwilfish - new(Nest043,2,4,4,SWSH) { Species = 748, Ability = A3 }, // Toxapex - new(Nest043,3,4,4,SWSH) { Species = 771, Ability = A4 }, // Pyukumuku - new(Nest043,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados - new(Nest043,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras - new(Nest043,4,4,4,SWSH) { Species = 350, Ability = A4 }, // Milotic - new(Nest044,0,0,1,SWSH) { Species = 447, Ability = A3 }, // Riolu - new(Nest044,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor - new(Nest044,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard - new(Nest044,0,1,1,SWSH) { Species = 599, Ability = A3 }, // Klink - new(Nest044,1,2,2,SWSH) { Species = 095, Ability = A3 }, // Onix - new(Nest044,2,4,4,SWSH) { Species = 437, Ability = A3 }, // Bronzong - new(Nest044,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp - new(Nest044,3,4,4,SWSH) { Species = 208, Ability = A4 }, // Steelix - new(Nest044,4,4,4,SWSH) { Species = 601, Ability = A4 }, // Klinklang - new(Nest044,4,4,4,SWSH) { Species = 448, Ability = A4 }, // Lucario - new(Nest045,0,0,1,SWSH) { Species = 767, Ability = A3 }, // Wimpod - new(Nest045,0,0,1,SWSH) { Species = 824, Ability = A3 }, // Blipbug - new(Nest045,0,1,1,SWSH) { Species = 751, Ability = A3 }, // Dewpider - new(Nest045,1,2,2,SWSH) { Species = 557, Ability = A3 }, // Dwebble - new(Nest045,2,3,3,SWSH) { Species = 825, Ability = A3 }, // Dottler - new(Nest045,2,4,4,SWSH) { Species = 826, Ability = A3 }, // Orbeetle - new(Nest045,3,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid - new(Nest045,3,4,4,SWSH) { Species = 768, Ability = A4 }, // Golisopod - new(Nest045,4,4,4,SWSH) { Species = 292, Ability = A4 }, // Shedinja - new(Nest046,0,0,1,SWSH) { Species = 679, Ability = A3 }, // Honedge - new(Nest046,0,0,1,SWSH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 - new(Nest046,0,1,1,SWSH) { Species = 854, Ability = A3 }, // Sinistea - new(Nest046,0,1,1,SWSH) { Species = 425, Ability = A3 }, // Drifloon - new(Nest046,1,2,2,SWSH) { Species = 680, Ability = A3 }, // Doublade - new(Nest046,2,3,3,SWSH) { Species = 426, Ability = A3 }, // Drifblim - new(Nest046,3,4,4,SWSH) { Species = 855, Ability = A4 }, // Polteageist - new(Nest046,4,4,4,SWSH) { Species = 867, Ability = A4 }, // Runerigus - new(Nest046,4,4,4,SWSH) { Species = 681, Ability = A4 }, // Aegislash - new(Nest047,0,0,1,SWSH) { Species = 447, Ability = A3 }, // Riolu - new(Nest047,0,0,1,SWSH) { Species = 066, Ability = A3 }, // Machop - new(Nest047,0,1,1,SWSH) { Species = 759, Ability = A3 }, // Stufful - new(Nest047,1,2,2,SWSH) { Species = 760, Ability = A3 }, // Bewear - new(Nest047,1,3,3,SWSH) { Species = 870, Ability = A3 }, // Falinks - new(Nest047,2,3,3,SWSH) { Species = 067, Ability = A3 }, // Machoke - new(Nest047,3,4,4,SWSH) { Species = 068, Ability = A4 }, // Machamp - new(Nest047,4,4,4,SWSH) { Species = 448, Ability = A4 }, // Lucario - new(Nest047,4,4,4,SWSH) { Species = 475, Ability = A4 }, // Gallade - new(Nest048,0,0,1,SWSH) { Species = 052, Ability = A3, Form = 2 }, // Meowth-2 - new(Nest048,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor - new(Nest048,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard - new(Nest048,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed - new(Nest048,1,2,2,SWSH) { Species = 679, Ability = A3 }, // Honedge - new(Nest048,1,2,2,SWSH) { Species = 437, Ability = A3 }, // Bronzong - new(Nest048,3,4,4,SWSH) { Species = 863, Ability = A4 }, // Perrserker - new(Nest048,2,4,4,SWSH) { Species = 598, Ability = A3 }, // Ferrothorn - new(Nest048,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp - new(Nest048,3,4,4,SWSH) { Species = 618, Ability = A4, Form = 1 }, // Stunfisk-1 - new(Nest048,4,4,4,SWSH) { Species = 879, Ability = A4 }, // Copperajah - new(Nest048,4,4,4,SWSH) { Species = 884, Ability = A4 }, // Duraludon - new(Nest049,0,0,1,SWSH) { Species = 686, Ability = A3 }, // Inkay - new(Nest049,0,0,1,SWSH) { Species = 280, Ability = A3 }, // Ralts - new(Nest049,0,1,1,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 - new(Nest049,0,1,1,SWSH) { Species = 527, Ability = A3 }, // Woobat - new(Nest049,1,2,2,SWSH) { Species = 856, Ability = A3 }, // Hatenna - new(Nest049,1,2,2,SWSH) { Species = 857, Ability = A3 }, // Hattrem - new(Nest049,2,3,3,SWSH) { Species = 281, Ability = A3 }, // Kirlia - new(Nest049,2,4,4,SWSH) { Species = 528, Ability = A3 }, // Swoobat - new(Nest049,3,4,4,SWSH) { Species = 858, Ability = A4 }, // Hatterene - new(Nest049,3,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime - new(Nest049,4,4,4,SWSH) { Species = 687, Ability = A4 }, // Malamar - new(Nest049,4,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir - new(Nest050,0,0,1,SWSH) { Species = 557, Ability = A3 }, // Dwebble - new(Nest050,0,0,1,SWSH) { Species = 438, Ability = A3 }, // Bonsly - new(Nest050,0,1,1,SWSH) { Species = 837, Ability = A3 }, // Rolycoly - new(Nest050,1,2,2,SWSH) { Species = 838, Ability = A3 }, // Carkol - new(Nest050,2,4,4,SWSH) { Species = 095, Ability = A3 }, // Onix - new(Nest050,3,4,4,SWSH) { Species = 558, Ability = A4 }, // Crustle - new(Nest050,3,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal - new(Nest050,4,4,4,SWSH) { Species = 208, Ability = A4 }, // Steelix - new(Nest051,0,0,1,SWSH) { Species = 194, Ability = A3 }, // Wooper - new(Nest051,0,0,1,SWSH) { Species = 339, Ability = A3 }, // Barboach - new(Nest051,0,1,1,SWSH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 - new(Nest051,0,1,1,SWSH) { Species = 622, Ability = A3 }, // Golett - new(Nest051,1,2,2,SWSH) { Species = 536, Ability = A3 }, // Palpitoad - new(Nest051,1,2,2,SWSH) { Species = 195, Ability = A3 }, // Quagsire - new(Nest051,2,3,3,SWSH) { Species = 618, Ability = A3, Form = 1 }, // Stunfisk-1 - new(Nest051,2,4,4,SWSH) { Species = 623, Ability = A3 }, // Golurk - new(Nest051,3,4,4,SWSH) { Species = 423, Ability = A4, Form = 1 }, // Gastrodon-1 - new(Nest051,3,4,4,SWSH) { Species = 537, Ability = A4 }, // Seismitoad - new(Nest051,4,4,4,SWSH) { Species = 867, Ability = A4 }, // Runerigus - new(Nest051,4,4,4,SWSH) { Species = 464, Ability = A4 }, // Rhyperior - new(Nest052,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede - new(Nest052,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick - new(Nest052,0,1,1,SWSH) { Species = 004, Ability = A3 }, // Charmander - new(Nest052,1,2,2,SWSH) { Species = 005, Ability = A3 }, // Charmeleon - new(Nest052,2,3,3,SWSH) { Species = 631, Ability = A3 }, // Heatmor - new(Nest052,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal - new(Nest052,3,4,4,SWSH) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle - new(Nest052,4,4,4,SWSH) { Species = 851, Ability = A4 }, // Centiskorch - new(Nest052,4,4,4,SWSH) { Species = 006, Ability = A4 }, // Charizard - new(Nest053,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede - new(Nest053,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick - new(Nest053,0,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit - new(Nest053,1,2,2,SWSH) { Species = 838, Ability = A3 }, // Carkol - new(Nest053,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal - new(Nest053,3,4,4,SWSH) { Species = 609, Ability = A4 }, // Chandelure - new(Nest053,4,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal - new(Nest054,0,0,1,SWSH) { Species = 582, Ability = A3 }, // Vanillite - new(Nest054,0,1,1,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 - new(Nest054,0,1,1,SWSH) { Species = 712, Ability = A3 }, // Bergmite - new(Nest054,1,2,2,SWSH) { Species = 361, Ability = A3 }, // Snorunt - new(Nest054,1,2,2,SWSH) { Species = 225, Ability = A3 }, // Delibird - new(Nest054,2,3,3,SWSH) { Species = 713, Ability = A3 }, // Avalugg - new(Nest054,2,4,4,SWSH) { Species = 362, Ability = A3 }, // Glalie - new(Nest054,3,4,4,SWSH) { Species = 584, Ability = A4 }, // Vanilluxe - new(Nest054,3,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime - new(Nest054,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras - new(Nest055,0,0,1,SWSH) { Species = 835, Ability = A3 }, // Yamper - new(Nest055,0,0,1,SWSH) { Species = 848, Ability = A3 }, // Toxel - new(Nest055,0,1,1,SWSH) { Species = 025, Ability = A3 }, // Pikachu - new(Nest055,0,1,1,SWSH) { Species = 595, Ability = A3 }, // Joltik - new(Nest055,1,2,2,SWSH) { Species = 170, Ability = A3 }, // Chinchou - new(Nest055,1,2,2,SWSH) { Species = 171, Ability = A3 }, // Lanturn - new(Nest055,2,4,4,SWSH) { Species = 836, Ability = A3 }, // Boltund - new(Nest055,2,4,4,SWSH) { Species = 849, Ability = A3 }, // Toxtricity - new(Nest055,3,4,4,SWSH) { Species = 871, Ability = A4 }, // Pincurchin - new(Nest055,3,4,4,SWSH) { Species = 596, Ability = A4 }, // Galvantula - new(Nest055,4,4,4,SWSH) { Species = 777, Ability = A4 }, // Togedemaru - new(Nest055,4,4,4,SWSH) { Species = 877, Ability = A4 }, // Morpeko - new(Nest056,0,0,1,SWSH) { Species = 172, Ability = A3 }, // Pichu - new(Nest056,0,0,1,SWSH) { Species = 309, Ability = A3 }, // Electrike - new(Nest056,0,1,1,SWSH) { Species = 848, Ability = A3 }, // Toxel - new(Nest056,0,1,1,SWSH) { Species = 694, Ability = A3 }, // Helioptile - new(Nest056,1,2,2,SWSH) { Species = 595, Ability = A3 }, // Joltik - new(Nest056,1,2,2,SWSH) { Species = 025, Ability = A3 }, // Pikachu - new(Nest056,2,4,4,SWSH) { Species = 025, Ability = A3 }, // Pikachu - new(Nest056,2,4,4,SWSH) { Species = 479, Ability = A3, Form = 5 }, // Rotom-5 - new(Nest056,3,4,4,SWSH) { Species = 479, Ability = A4, Form = 4 }, // Rotom-4 - new(Nest056,3,4,4,SWSH) { Species = 479, Ability = A4, Form = 3 }, // Rotom-3 - new(Nest056,4,4,4,SWSH) { Species = 479, Ability = A4, Form = 2 }, // Rotom-2 - new(Nest056,4,4,4,SWSH) { Species = 479, Ability = A4, Form = 1 }, // Rotom-1 - new(Nest057,0,0,1,SWSH) { Species = 406, Ability = A3 }, // Budew - new(Nest057,0,1,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur - new(Nest057,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed - new(Nest057,1,2,2,SWSH) { Species = 840, Ability = A3 }, // Applin - new(Nest057,2,4,4,SWSH) { Species = 315, Ability = A3 }, // Roselia - new(Nest057,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss - new(Nest057,3,4,4,SWSH) { Species = 598, Ability = A4 }, // Ferrothorn - new(Nest057,4,4,4,SWSH) { Species = 407, Ability = A4 }, // Roserade - new(Nest058,0,0,1,SWSH) { Species = 420, Ability = A3 }, // Cherubi - new(Nest058,0,1,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur - new(Nest058,0,1,1,SWSH) { Species = 546, Ability = A3 }, // Cottonee - new(Nest058,1,2,2,SWSH) { Species = 755, Ability = A3 }, // Morelull - new(Nest058,2,4,4,SWSH) { Species = 421, Ability = A3 }, // Cherrim - new(Nest058,2,4,4,SWSH) { Species = 756, Ability = A3 }, // Shiinotic - new(Nest058,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss - new(Nest058,3,4,4,SWSH) { Species = 547, Ability = A4 }, // Whimsicott - new(Nest058,4,4,4,SWSH) { Species = 781, Ability = A4 }, // Dhelmise - new(Nest059,0,0,1,SWSH) { Species = 434, Ability = A3 }, // Stunky - new(Nest059,0,0,1,SWSH) { Species = 568, Ability = A3 }, // Trubbish - new(Nest059,0,1,1,SWSH) { Species = 451, Ability = A3 }, // Skorupi - new(Nest059,0,1,1,SWSH) { Species = 109, Ability = A3 }, // Koffing - new(Nest059,1,2,2,SWSH) { Species = 848, Ability = A3 }, // Toxel - new(Nest059,2,4,4,SWSH) { Species = 569, Ability = A3 }, // Garbodor - new(Nest059,2,4,4,SWSH) { Species = 452, Ability = A3 }, // Drapion - new(Nest059,3,4,4,SWSH) { Species = 849, Ability = A4 }, // Toxtricity - new(Nest059,3,4,4,SWSH) { Species = 435, Ability = A4 }, // Skuntank - new(Nest059,4,4,4,SWSH) { Species = 110, Ability = A4, Form = 1 }, // Weezing-1 - new(Nest060,0,0,1,SWSH) { Species = 177, Ability = A3 }, // Natu - new(Nest060,0,0,1,SWSH) { Species = 163, Ability = A3 }, // Hoothoot - new(Nest060,0,1,1,SWSH) { Species = 821, Ability = A3 }, // Rookidee - new(Nest060,0,1,1,SWSH) { Species = 278, Ability = A3 }, // Wingull - new(Nest060,1,2,2,SWSH) { Species = 012, Ability = A3 }, // Butterfree - new(Nest060,1,2,2,SWSH) { Species = 822, Ability = A3 }, // Corvisquire - new(Nest060,2,4,4,SWSH) { Species = 164, Ability = A3 }, // Noctowl - new(Nest060,2,4,4,SWSH) { Species = 279, Ability = A3 }, // Pelipper - new(Nest060,3,4,4,SWSH) { Species = 178, Ability = A4 }, // Xatu - new(Nest060,3,4,4,SWSH) { Species = 701, Ability = A4 }, // Hawlucha - new(Nest060,4,4,4,SWSH) { Species = 823, Ability = A4 }, // Corviknight - new(Nest060,4,4,4,SWSH) { Species = 225, Ability = A4 }, // Delibird - new(Nest061,0,0,1,SWSH) { Species = 175, Ability = A3 }, // Togepi - new(Nest061,0,0,1,SWSH) { Species = 755, Ability = A3 }, // Morelull - new(Nest061,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp - new(Nest061,0,1,1,SWSH) { Species = 280, Ability = A3 }, // Ralts - new(Nest061,1,2,2,SWSH) { Species = 176, Ability = A3 }, // Togetic - new(Nest061,1,2,2,SWSH) { Species = 756, Ability = A3 }, // Shiinotic - new(Nest061,2,4,4,SWSH) { Species = 860, Ability = A3 }, // Morgrem - new(Nest061,3,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir - new(Nest061,3,4,4,SWSH) { Species = 468, Ability = A4 }, // Togekiss - new(Nest061,4,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl - new(Nest061,4,4,4,SWSH) { Species = 778, Ability = A4 }, // Mimikyu - new(Nest062,0,0,1,SWSH) { Species = 827, Ability = A3 }, // Nickit - new(Nest062,0,0,1,SWSH) { Species = 263, Ability = A3, Form = 1 }, // Zigzagoon-1 - new(Nest062,0,1,1,SWSH) { Species = 215, Ability = A3 }, // Sneasel - new(Nest062,1,2,2,SWSH) { Species = 510, Ability = A3 }, // Liepard - new(Nest062,1,2,2,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 - new(Nest062,2,4,4,SWSH) { Species = 828, Ability = A3 }, // Thievul - new(Nest062,2,4,4,SWSH) { Species = 675, Ability = A3 }, // Pangoro - new(Nest062,3,4,4,SWSH) { Species = 461, Ability = A4 }, // Weavile - new(Nest062,4,4,4,SWSH) { Species = 862, Ability = A4 }, // Obstagoon - new(Nest063,0,0,1,SWSH) { Species = 840, Ability = A3 }, // Applin - new(Nest063,1,2,2,SWSH) { Species = 885, Ability = A3 }, // Dreepy - new(Nest063,3,4,4,SWSH) { Species = 886, Ability = A4 }, // Drakloak - new(Nest063,4,4,4,SWSH) { Species = 887, Ability = A4 }, // Dragapult - new(Nest064,0,0,1,SWSH) { Species = 659, Ability = A3 }, // Bunnelby - new(Nest064,0,0,1,SWSH) { Species = 519, Ability = A3 }, // Pidove - new(Nest064,0,1,1,SWSH) { Species = 819, Ability = A3 }, // Skwovet - new(Nest064,0,1,1,SWSH) { Species = 133, Ability = A3 }, // Eevee - new(Nest064,1,2,2,SWSH) { Species = 520, Ability = A3 }, // Tranquill - new(Nest064,1,2,2,SWSH) { Species = 831, Ability = A3 }, // Wooloo - new(Nest064,2,4,4,SWSH) { Species = 521, Ability = A3 }, // Unfezant - new(Nest064,2,4,4,SWSH) { Species = 832, Ability = A3 }, // Dubwool - new(Nest064,4,4,4,SWSH) { Species = 133, Ability = A4 }, // Eevee - new(Nest064,4,4,4,SWSH) { Species = 143, Ability = A4 }, // Snorlax - new(Nest065,0,0,1,SWSH) { Species = 132, Ability = A3 }, // Ditto - new(Nest065,0,1,2,SWSH) { Species = 132, Ability = A3 }, // Ditto - new(Nest065,1,2,3,SWSH) { Species = 132, Ability = A3 }, // Ditto - new(Nest065,2,3,3,SWSH) { Species = 132, Ability = A3 }, // Ditto - new(Nest065,3,4,4,SWSH) { Species = 132, Ability = A4 }, // Ditto - new(Nest065,4,4,4,SWSH) { Species = 132, Ability = A4 }, // Ditto - //new(Nest066,0,0,1,SWSH) { Species = 458, Ability = A3 }, // Mantyke - //new(Nest066,0,0,1,SWSH) { Species = 341, Ability = A3 }, // Corphish - //new(Nest066,0,1,1,SWSH) { Species = 846, Ability = A3 }, // Arrokuda - //new(Nest066,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle - new(Nest066,2,2,2,SWSH) { Species = 747, Ability = A3 }, // Mareanie - new(Nest066,2,3,3,SWSH) { Species = 342, Ability = A3 }, // Crawdaunt - new(Nest066,2,4,4,SWSH) { Species = 748, Ability = A3 }, // Toxapex - new(Nest066,3,4,4,SWSH) { Species = 771, Ability = A4 }, // Pyukumuku - new(Nest066,3,4,4,SWSH) { Species = 226, Ability = A4 }, // Mantine - new(Nest066,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras - new(Nest066,4,4,4,SWSH) { Species = 134, Ability = A4 }, // Vaporeon - new(Nest067,0,0,1,SWSH) { Species = 686, Ability = A3 }, // Inkay - new(Nest067,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor - new(Nest067,0,1,1,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 - new(Nest067,0,1,1,SWSH) { Species = 527, Ability = A3 }, // Woobat - new(Nest067,1,2,2,SWSH) { Species = 856, Ability = A3 }, // Hatenna - new(Nest067,1,2,2,SWSH) { Species = 857, Ability = A3 }, // Hattrem - new(Nest067,2,3,3,SWSH) { Species = 437, Ability = A3 }, // Bronzong - new(Nest067,2,4,4,SWSH) { Species = 528, Ability = A3 }, // Swoobat - new(Nest067,3,4,4,SWSH) { Species = 687, Ability = A4 }, // Malamar - new(Nest067,3,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime - new(Nest067,4,4,4,SWSH) { Species = 858, Ability = A4 }, // Hatterene - new(Nest067,4,4,4,SWSH) { Species = 196, Ability = A4 }, // Espeon - new(Nest068,0,0,1,SWSH) { Species = 827, Ability = A3 }, // Nickit - new(Nest068,0,0,1,SWSH) { Species = 263, Ability = A3, Form = 1 }, // Zigzagoon-1 - new(Nest068,0,1,1,SWSH) { Species = 686, Ability = A3 }, // Inkay - new(Nest068,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard - new(Nest068,1,2,2,SWSH) { Species = 510, Ability = A3 }, // Liepard - new(Nest068,1,2,2,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 - new(Nest068,2,4,4,SWSH) { Species = 828, Ability = A3 }, // Thievul - new(Nest068,2,4,4,SWSH) { Species = 675, Ability = A3 }, // Pangoro - new(Nest068,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp - new(Nest068,3,4,4,SWSH) { Species = 687, Ability = A4 }, // Malamar - new(Nest068,4,4,4,SWSH) { Species = 862, Ability = A4 }, // Obstagoon - new(Nest068,4,4,4,SWSH) { Species = 197, Ability = A4 }, // Umbreon - new(Nest069,0,0,1,SWSH) { Species = 420, Ability = A3 }, // Cherubi - new(Nest069,0,0,1,SWSH) { Species = 761, Ability = A3 }, // Bounsweet - new(Nest069,0,1,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur - new(Nest069,0,1,1,SWSH) { Species = 546, Ability = A3 }, // Cottonee - new(Nest069,1,2,2,SWSH) { Species = 762, Ability = A3 }, // Steenee - new(Nest069,1,2,2,SWSH) { Species = 597, Ability = A3 }, // Ferroseed - new(Nest069,2,4,4,SWSH) { Species = 421, Ability = A3 }, // Cherrim - new(Nest069,2,4,4,SWSH) { Species = 598, Ability = A3 }, // Ferrothorn - new(Nest069,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss - new(Nest069,3,4,4,SWSH) { Species = 763, Ability = A4 }, // Tsareena - new(Nest069,4,4,4,SWSH) { Species = 547, Ability = A4 }, // Whimsicott - new(Nest069,4,4,4,SWSH) { Species = 470, Ability = A4 }, // Leafeon - new(Nest070,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede - new(Nest070,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick - new(Nest070,1,2,2,SWSH) { Species = 838, Ability = A3 }, // Carkol - new(Nest070,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal - new(Nest070,3,4,4,SWSH) { Species = 059, Ability = A4 }, // Arcanine - new(Nest070,3,4,4,SWSH) { Species = 038, Ability = A4 }, // Ninetales - new(Nest070,4,4,4,SWSH) { Species = 609, Ability = A4 }, // Chandelure - new(Nest070,4,4,4,SWSH) { Species = 136, Ability = A4 }, // Flareon - new(Nest071,0,0,1,SWSH) { Species = 835, Ability = A3 }, // Yamper - new(Nest071,0,0,1,SWSH) { Species = 848, Ability = A3 }, // Toxel - new(Nest071,0,1,1,SWSH) { Species = 025, Ability = A3 }, // Pikachu - new(Nest071,0,1,1,SWSH) { Species = 694, Ability = A3 }, // Helioptile - new(Nest071,1,2,2,SWSH) { Species = 170, Ability = A3 }, // Chinchou - new(Nest071,1,2,2,SWSH) { Species = 171, Ability = A3 }, // Lanturn - new(Nest071,2,4,4,SWSH) { Species = 836, Ability = A3 }, // Boltund - new(Nest071,2,4,4,SWSH) { Species = 849, Ability = A3 }, // Toxtricity - new(Nest071,3,4,4,SWSH) { Species = 695, Ability = A4 }, // Heliolisk - new(Nest071,3,4,4,SWSH) { Species = 738, Ability = A4 }, // Vikavolt - new(Nest071,4,4,4,SWSH) { Species = 025, Ability = A4 }, // Pikachu - new(Nest071,4,4,4,SWSH) { Species = 135, Ability = A4 }, // Jolteon - new(Nest072,0,0,1,SWSH) { Species = 582, Ability = A3 }, // Vanillite - new(Nest072,0,0,1,SWSH) { Species = 872, Ability = A3 }, // Snom - new(Nest072,0,1,1,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 - new(Nest072,0,1,1,SWSH) { Species = 712, Ability = A3 }, // Bergmite - new(Nest072,1,2,2,SWSH) { Species = 361, Ability = A3 }, // Snorunt - new(Nest072,1,2,2,SWSH) { Species = 583, Ability = A3 }, // Vanillish - new(Nest072,2,3,3,SWSH) { Species = 713, Ability = A3 }, // Avalugg - new(Nest072,2,4,4,SWSH) { Species = 873, Ability = A3 }, // Frosmoth - new(Nest072,3,4,4,SWSH) { Species = 584, Ability = A4 }, // Vanilluxe - new(Nest072,3,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime - new(Nest072,4,4,4,SWSH) { Species = 478, Ability = A4 }, // Froslass - new(Nest072,4,4,4,SWSH) { Species = 471, Ability = A4 }, // Glaceon - //new(Nest073,0,0,1,SWSH) { Species = 175, Ability = A3 }, // Togepi - //new(Nest073,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp - //new(Nest073,0,1,1,SWSH) { Species = 280, Ability = A3 }, // Ralts - new(Nest073,2,2,2,SWSH) { Species = 176, Ability = A3 }, // Togetic - new(Nest073,2,2,2,SWSH) { Species = 860, Ability = A3 }, // Morgrem - new(Nest073,2,4,4,SWSH) { Species = 868, Ability = A3 }, // Milcery - new(Nest073,3,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir - new(Nest073,3,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl - new(Nest073,4,4,4,SWSH) { Species = 468, Ability = A4 }, // Togekiss - new(Nest073,4,4,4,SWSH) { Species = 700, Ability = A4 }, // Sylveon - new(Nest074,0,0,1,SWSH) { Species = 129, Ability = A3 }, // Magikarp - new(Nest074,0,0,1,SWSH) { Species = 751, Ability = A3 }, // Dewpider - new(Nest074,0,1,1,SWSH) { Species = 194, Ability = A3 }, // Wooper - new(Nest074,0,1,1,SWSH) { Species = 339, Ability = A3 }, // Barboach - new(Nest074,1,2,2,SWSH) { Species = 098, Ability = A3 }, // Krabby - new(Nest074,1,2,2,SWSH) { Species = 746, Ability = A3 }, // Wishiwashi - new(Nest074,2,3,3,SWSH) { Species = 099, Ability = A3 }, // Kingler - new(Nest074,2,4,4,SWSH) { Species = 340, Ability = A3 }, // Whiscash - new(Nest074,3,4,4,SWSH) { Species = 211, Ability = A4 }, // Qwilfish - new(Nest074,3,4,4,SWSH) { Species = 195, Ability = A4 }, // Quagsire - new(Nest074,4,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid - new(Nest074,4,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados - //new(Nest075,0,0,1,SWSH) { Species = 458, Ability = A3 }, // Mantyke - //new(Nest075,0,0,1,SWSH) { Species = 223, Ability = A3 }, // Remoraid - //new(Nest075,0,1,1,SWSH) { Species = 320, Ability = A3 }, // Wailmer - //new(Nest075,0,1,1,SWSH) { Species = 688, Ability = A3 }, // Binacle - new(Nest075,2,2,2,SWSH) { Species = 098, Ability = A3 }, // Krabby - new(Nest075,2,2,2,SWSH) { Species = 771, Ability = A3 }, // Pyukumuku - new(Nest075,2,3,3,SWSH) { Species = 099, Ability = A3 }, // Kingler - new(Nest075,3,4,4,SWSH) { Species = 211, Ability = A4 }, // Qwilfish - new(Nest075,3,4,4,SWSH) { Species = 224, Ability = A4 }, // Octillery - new(Nest075,4,4,4,SWSH) { Species = 321, Ability = A4 }, // Wailord - new(Nest075,4,4,4,SWSH) { Species = 226, Ability = A4 }, // Mantine - //new(Nest076,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede - //new(Nest076,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick - //new(Nest076,0,1,1,SWSH) { Species = 004, Ability = A3 }, // Charmander - new(Nest076,2,2,2,SWSH) { Species = 005, Ability = A3 }, // Charmeleon - new(Nest076,2,3,3,SWSH) { Species = 631, Ability = A3 }, // Heatmor - new(Nest076,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal - new(Nest076,3,4,4,SWSH) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle - new(Nest076,4,4,4,SWSH) { Species = 851, Ability = A4 }, // Centiskorch - new(Nest076,4,4,4,SWSH) { Species = 006, Ability = A4, CanGigantamax = true }, // Charizard - new(Nest077,0,0,1,SWSH) { Species = 129, Ability = A3 }, // Magikarp - new(Nest077,0,0,1,SWSH) { Species = 846, Ability = A3 }, // Arrokuda - new(Nest077,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle - new(Nest077,0,1,1,SWSH) { Species = 098, Ability = A3 }, // Krabby - new(Nest077,1,2,2,SWSH) { Species = 771, Ability = A3 }, // Pyukumuku - new(Nest077,2,3,3,SWSH) { Species = 211, Ability = A3 }, // Qwilfish - new(Nest077,2,4,4,SWSH) { Species = 099, Ability = A3 }, // Kingler - new(Nest077,3,4,4,SWSH) { Species = 746, Ability = A4 }, // Wishiwashi - new(Nest077,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados - new(Nest077,4,4,4,SWSH) { Species = 423, Ability = A4, Form = 1 }, // Gastrodon-1 - new(Nest077,4,4,4,SWSH) { Species = 834, Ability = A4, CanGigantamax = true }, // Drednaw - new(Nest078,0,0,1,SWSH) { Species = 406, Ability = A3 }, // Budew - new(Nest078,0,1,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur - new(Nest078,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed - new(Nest078,1,2,2,SWSH) { Species = 840, Ability = A3 }, // Applin - new(Nest078,2,4,4,SWSH) { Species = 315, Ability = A3 }, // Roselia - new(Nest078,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss - new(Nest078,3,4,4,SWSH) { Species = 598, Ability = A4 }, // Ferrothorn - new(Nest078,4,4,4,SWSH) { Species = 407, Ability = A4 }, // Roserade - new(Nest079,0,0,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede - new(Nest079,0,1,1,SWSH) { Species = 607, Ability = A3 }, // Litwick - new(Nest079,0,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit - new(Nest079,1,2,2,SWSH) { Species = 838, Ability = A3 }, // Carkol - new(Nest079,1,2,2,SWSH) { Species = 608, Ability = A3 }, // Lampent - new(Nest079,2,3,3,SWSH) { Species = 631, Ability = A3 }, // Heatmor - new(Nest079,2,4,4,SWSH) { Species = 324, Ability = A3 }, // Torkoal - new(Nest079,3,4,4,SWSH) { Species = 609, Ability = A4 }, // Chandelure - new(Nest079,4,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal - new(Nest079,4,4,4,SWSH) { Species = 851, Ability = A4, CanGigantamax = true }, // Centiskorch - new(Nest081,0,0,1,SWSH) { Species = 175, Ability = A3 }, // Togepi - new(Nest081,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp - new(Nest081,0,1,1,SWSH) { Species = 280, Ability = A3 }, // Ralts - new(Nest081,1,2,2,SWSH) { Species = 176, Ability = A3 }, // Togetic - new(Nest081,1,2,2,SWSH) { Species = 756, Ability = A3 }, // Shiinotic - new(Nest081,2,3,3,SWSH) { Species = 860, Ability = A3 }, // Morgrem - new(Nest081,3,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir - new(Nest081,3,4,4,SWSH) { Species = 468, Ability = A4 }, // Togekiss - new(Nest081,4,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl - new(Nest081,4,4,4,SWSH) { Species = 869, Ability = A4, CanGigantamax = true }, // Alcremie - new(Nest083,0,0,1,SWSH) { Species = 447, Ability = A3 }, // Riolu - new(Nest083,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor - new(Nest083,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard - new(Nest083,0,1,1,SWSH) { Species = 599, Ability = A3 }, // Klink - new(Nest083,1,2,2,SWSH) { Species = 095, Ability = A3 }, // Onix - new(Nest083,2,4,4,SWSH) { Species = 437, Ability = A3 }, // Bronzong - new(Nest083,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp - new(Nest083,3,4,4,SWSH) { Species = 208, Ability = A4 }, // Steelix - new(Nest083,4,4,4,SWSH) { Species = 601, Ability = A4 }, // Klinklang - new(Nest083,4,4,4,SWSH) { Species = 884, Ability = A4, CanGigantamax = true }, // Duraludon - new(Nest084,0,0,1,SWSH) { Species = 052, Ability = A3, Form = 2 }, // Meowth-2 - new(Nest084,0,0,1,SWSH) { Species = 436, Ability = A3 }, // Bronzor - new(Nest084,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard - new(Nest084,0,1,1,SWSH) { Species = 597, Ability = A3 }, // Ferroseed - new(Nest084,1,2,2,SWSH) { Species = 679, Ability = A3 }, // Honedge - new(Nest084,1,2,2,SWSH) { Species = 437, Ability = A3 }, // Bronzong - new(Nest084,2,3,3,SWSH) { Species = 863, Ability = A3 }, // Perrserker - new(Nest084,2,4,4,SWSH) { Species = 598, Ability = A3 }, // Ferrothorn - new(Nest084,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp - new(Nest084,3,4,4,SWSH) { Species = 618, Ability = A4, Form = 1 }, // Stunfisk-1 - new(Nest084,4,4,4,SWSH) { Species = 884, Ability = A4 }, // Duraludon - new(Nest084,4,4,4,SWSH) { Species = 879, Ability = A4, CanGigantamax = true }, // Copperajah - new(Nest085,0,0,1,SWSH) { Species = 434, Ability = A3 }, // Stunky - new(Nest085,0,0,1,SWSH) { Species = 568, Ability = A3 }, // Trubbish - new(Nest085,0,1,1,SWSH) { Species = 451, Ability = A3 }, // Skorupi - new(Nest085,0,1,1,SWSH) { Species = 109, Ability = A3 }, // Koffing - new(Nest085,1,2,2,SWSH) { Species = 848, Ability = A3 }, // Toxel - new(Nest085,2,3,3,SWSH) { Species = 452, Ability = A3 }, // Drapion - new(Nest085,2,4,4,SWSH) { Species = 849, Ability = A3 }, // Toxtricity - new(Nest085,3,4,4,SWSH) { Species = 435, Ability = A4 }, // Skuntank - new(Nest085,3,4,4,SWSH) { Species = 110, Ability = A4, Form = 1 }, // Weezing-1 - new(Nest085,4,4,4,SWSH) { Species = 569, Ability = A4, CanGigantamax = true }, // Garbodor - new(Nest086,0,0,1,SWSH) { Species = 175, Ability = A3 }, // Togepi - new(Nest086,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp - new(Nest086,0,1,1,SWSH) { Species = 280, Ability = A3 }, // Ralts - new(Nest086,1,2,2,SWSH) { Species = 176, Ability = A3 }, // Togetic - new(Nest086,1,2,2,SWSH) { Species = 860, Ability = A3 }, // Morgrem - new(Nest086,2,4,4,SWSH) { Species = 868, Ability = A3 }, // Milcery - new(Nest086,3,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir - new(Nest086,3,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl - new(Nest086,4,4,4,SWSH) { Species = 468, Ability = A4 }, // Togekiss - new(Nest086,4,4,4,SWSH) { Species = 858, Ability = A4, CanGigantamax = true }, // Hatterene - new(Nest087,0,0,1,SWSH) { Species = 827, Ability = A3 }, // Nickit - new(Nest087,0,0,1,SWSH) { Species = 263, Ability = A3, Form = 1 }, // Zigzagoon-1 - new(Nest087,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp - new(Nest087,1,2,2,SWSH) { Species = 510, Ability = A3 }, // Liepard - new(Nest087,1,2,2,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 - new(Nest087,2,3,3,SWSH) { Species = 860, Ability = A3 }, // Morgrem - new(Nest087,2,4,4,SWSH) { Species = 828, Ability = A3 }, // Thievul - new(Nest087,3,4,4,SWSH) { Species = 675, Ability = A4 }, // Pangoro - new(Nest087,4,4,4,SWSH) { Species = 861, Ability = A4, CanGigantamax = true }, // Grimmsnarl - new(Nest088,0,0,1,SWSH) { Species = 177, Ability = A3 }, // Natu - new(Nest088,0,0,1,SWSH) { Species = 163, Ability = A3 }, // Hoothoot - new(Nest088,0,1,1,SWSH) { Species = 821, Ability = A3 }, // Rookidee - new(Nest088,0,1,1,SWSH) { Species = 278, Ability = A3 }, // Wingull - new(Nest088,1,2,2,SWSH) { Species = 012, Ability = A3 }, // Butterfree - new(Nest088,1,2,2,SWSH) { Species = 822, Ability = A3 }, // Corvisquire - new(Nest088,2,3,3,SWSH) { Species = 164, Ability = A3 }, // Noctowl - new(Nest088,2,4,4,SWSH) { Species = 279, Ability = A3 }, // Pelipper - new(Nest088,3,4,4,SWSH) { Species = 178, Ability = A4 }, // Xatu - new(Nest088,3,4,4,SWSH) { Species = 701, Ability = A4 }, // Hawlucha - new(Nest088,4,4,4,SWSH) { Species = 561, Ability = A4 }, // Sigilyph - new(Nest088,4,4,4,SWSH) { Species = 823, Ability = A4, CanGigantamax = true }, // Corviknight - new(Nest089,0,0,1,SWSH) { Species = 767, Ability = A3 }, // Wimpod - new(Nest089,0,0,1,SWSH) { Species = 824, Ability = A3 }, // Blipbug - new(Nest089,0,1,1,SWSH) { Species = 751, Ability = A3 }, // Dewpider - new(Nest089,1,2,2,SWSH) { Species = 557, Ability = A3 }, // Dwebble - new(Nest089,2,3,3,SWSH) { Species = 825, Ability = A3 }, // Dottler - new(Nest089,2,4,4,SWSH) { Species = 826, Ability = A3 }, // Orbeetle - new(Nest089,3,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid - new(Nest089,3,4,4,SWSH) { Species = 768, Ability = A4 }, // Golisopod - new(Nest089,0,4,4,SWSH) { Species = 012, Ability = A4, CanGigantamax = true }, // Butterfree - new(Nest090,0,0,1,SWSH) { Species = 341, Ability = A3 }, // Corphish - new(Nest090,0,0,1,SWSH) { Species = 098, Ability = A3 }, // Krabby - new(Nest090,0,1,1,SWSH) { Species = 846, Ability = A3 }, // Arrokuda - new(Nest090,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle - new(Nest090,1,2,2,SWSH) { Species = 747, Ability = A3 }, // Mareanie - new(Nest090,2,3,3,SWSH) { Species = 342, Ability = A3 }, // Crawdaunt - new(Nest090,2,4,4,SWSH) { Species = 748, Ability = A3 }, // Toxapex - new(Nest090,3,4,4,SWSH) { Species = 771, Ability = A4 }, // Pyukumuku - new(Nest090,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados - new(Nest090,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras - new(Nest090,1,4,4,SWSH) { Species = 099, Ability = A4, CanGigantamax = true }, // Kingler - new(Nest091,0,0,1,SWSH) { Species = 767, Ability = A3 }, // Wimpod - new(Nest091,0,0,1,SWSH) { Species = 824, Ability = A3 }, // Blipbug - new(Nest091,0,1,1,SWSH) { Species = 751, Ability = A3 }, // Dewpider - new(Nest091,1,2,2,SWSH) { Species = 557, Ability = A3 }, // Dwebble - new(Nest091,2,3,3,SWSH) { Species = 825, Ability = A3 }, // Dottler - new(Nest091,2,4,4,SWSH) { Species = 826, Ability = A3 }, // Orbeetle - new(Nest091,3,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid - new(Nest091,3,4,4,SWSH) { Species = 768, Ability = A4 }, // Golisopod - new(Nest091,2,4,4,SWSH) { Species = 826, Ability = A4, CanGigantamax = true }, // Orbeetle - new(Nest092,0,0,1,SWSH) { Species = 194, Ability = A3 }, // Wooper - new(Nest092,0,0,1,SWSH) { Species = 339, Ability = A3 }, // Barboach - new(Nest092,0,1,1,SWSH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 - new(Nest092,0,1,1,SWSH) { Species = 622, Ability = A3 }, // Golett - new(Nest092,1,2,2,SWSH) { Species = 536, Ability = A3 }, // Palpitoad - new(Nest092,1,2,2,SWSH) { Species = 195, Ability = A3 }, // Quagsire - new(Nest092,2,3,3,SWSH) { Species = 618, Ability = A3, Form = 1 }, // Stunfisk-1 - new(Nest092,2,4,4,SWSH) { Species = 623, Ability = A3 }, // Golurk - new(Nest092,3,4,4,SWSH) { Species = 423, Ability = A4, Form = 1 }, // Gastrodon-1 - new(Nest092,3,4,4,SWSH) { Species = 537, Ability = A4 }, // Seismitoad - new(Nest092,4,4,4,SWSH) { Species = 464, Ability = A4 }, // Rhyperior - new(Nest092,3,4,4,SWSH) { Species = 844, Ability = A4, CanGigantamax = true }, // Sandaconda - new(Nest098,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest098,0,1,1,SWSH) { Species = 174, Ability = A3 }, // Igglybuff - new(Nest098,0,1,1,SWSH) { Species = 506, Ability = A3 }, // Lillipup - new(Nest098,1,2,2,SWSH) { Species = 427, Ability = A3 }, // Buneary - new(Nest098,1,2,2,SWSH) { Species = 039, Ability = A3 }, // Jigglypuff - new(Nest098,2,3,3,SWSH) { Species = 039, Ability = A3 }, // Jigglypuff - new(Nest098,2,3,3,SWSH) { Species = 507, Ability = A3 }, // Herdier - new(Nest098,3,4,4,SWSH) { Species = 428, Ability = A4 }, // Lopunny - new(Nest098,3,4,4,SWSH) { Species = 040, Ability = A4 }, // Wigglytuff - new(Nest098,4,4,4,SWSH) { Species = 206, Ability = A4 }, // Dunsparce - new(Nest098,4,4,4,SWSH) { Species = 508, Ability = A4 }, // Stoutland - new(Nest099,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest099,0,1,1,SWSH) { Species = 506, Ability = A2 }, // Lillipup - new(Nest099,0,1,1,SWSH) { Species = 759, Ability = A2 }, // Stufful - new(Nest099,1,2,2,SWSH) { Species = 039, Ability = A2 }, // Jigglypuff - new(Nest099,1,2,2,SWSH) { Species = 427, Ability = A2 }, // Buneary - new(Nest099,2,3,3,SWSH) { Species = 039, Ability = A2 }, // Jigglypuff - new(Nest099,2,3,3,SWSH) { Species = 206, Ability = A2 }, // Dunsparce - new(Nest099,3,4,4,SWSH) { Species = 832, Ability = A2 }, // Dubwool - new(Nest099,3,4,4,SWSH) { Species = 428, Ability = A2 }, // Lopunny - new(Nest099,3,4,4,SWSH) { Species = 508, Ability = A2 }, // Stoutland - new(Nest099,4,4,4,SWSH) { Species = 760, Ability = A2 }, // Bewear - new(Nest099,4,4,4,SWSH) { Species = 040, Ability = A2 }, // Wigglytuff - new(Nest100,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest100,0,1,1,SWSH) { Species = 293, Ability = A3 }, // Whismur - new(Nest100,0,1,1,SWSH) { Species = 108, Ability = A3 }, // Lickitung - new(Nest100,1,2,2,SWSH) { Species = 241, Ability = A3 }, // Miltank - new(Nest100,1,2,2,SWSH) { Species = 294, Ability = A3 }, // Loudred - new(Nest100,2,3,3,SWSH) { Species = 294, Ability = A3 }, // Loudred - new(Nest100,2,3,3,SWSH) { Species = 108, Ability = A3 }, // Lickitung - new(Nest100,3,4,4,SWSH) { Species = 241, Ability = A4 }, // Miltank - new(Nest100,3,4,4,SWSH) { Species = 626, Ability = A4 }, // Bouffalant - new(Nest100,3,4,4,SWSH) { Species = 128, Ability = A4 }, // Tauros - new(Nest100,4,4,4,SWSH) { Species = 295, Ability = A4 }, // Exploud - new(Nest100,4,4,4,SWSH) { Species = 463, Ability = A4 }, // Lickilicky - new(Nest101,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest101,0,1,1,SWSH) { Species = 293, Ability = A2 }, // Whismur - new(Nest101,0,1,1,SWSH) { Species = 128, Ability = A2 }, // Tauros - new(Nest101,1,2,2,SWSH) { Species = 108, Ability = A2 }, // Lickitung - new(Nest101,1,2,2,SWSH) { Species = 241, Ability = A2 }, // Miltank - new(Nest101,2,3,3,SWSH) { Species = 241, Ability = A2 }, // Miltank - new(Nest101,2,3,3,SWSH) { Species = 626, Ability = A2 }, // Bouffalant - new(Nest101,3,4,4,SWSH) { Species = 128, Ability = A2 }, // Tauros - new(Nest101,3,4,4,SWSH) { Species = 295, Ability = A2 }, // Exploud - new(Nest101,3,4,4,SWSH) { Species = 573, Ability = A2 }, // Cinccino - new(Nest101,4,4,4,SWSH) { Species = 295, Ability = A2 }, // Exploud - new(Nest101,4,4,4,SWSH) { Species = 463, Ability = A2 }, // Lickilicky - new(Nest102,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest102,0,1,1,SWSH) { Species = 027, Ability = A3 }, // Sandshrew - new(Nest102,0,1,1,SWSH) { Species = 551, Ability = A3 }, // Sandile - new(Nest102,1,2,2,SWSH) { Species = 104, Ability = A3 }, // Cubone - new(Nest102,1,2,2,SWSH) { Species = 027, Ability = A3 }, // Sandshrew - new(Nest102,2,3,3,SWSH) { Species = 552, Ability = A3 }, // Krokorok - new(Nest102,2,3,3,SWSH) { Species = 028, Ability = A3 }, // Sandslash - new(Nest102,3,4,4,SWSH) { Species = 844, Ability = A4 }, // Sandaconda - new(Nest102,3,4,4,SWSH) { Species = 028, Ability = A4 }, // Sandslash - new(Nest102,3,4,4,SWSH) { Species = 105, Ability = A4 }, // Marowak - new(Nest102,4,4,4,SWSH) { Species = 553, Ability = A4 }, // Krookodile - new(Nest102,4,4,4,SWSH) { Species = 115, Ability = A4 }, // Kangaskhan - new(Nest103,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest103,0,1,1,SWSH) { Species = 027, Ability = A2 }, // Sandshrew - new(Nest103,0,1,1,SWSH) { Species = 104, Ability = A2 }, // Cubone - new(Nest103,1,2,2,SWSH) { Species = 328, Ability = A2 }, // Trapinch - new(Nest103,2,3,3,SWSH) { Species = 552, Ability = A2 }, // Krokorok - new(Nest103,2,3,3,SWSH) { Species = 028, Ability = A2 }, // Sandslash - new(Nest103,3,4,4,SWSH) { Species = 105, Ability = A2 }, // Marowak - new(Nest103,3,4,4,SWSH) { Species = 553, Ability = A2 }, // Krookodile - new(Nest103,3,4,4,SWSH) { Species = 115, Ability = A2 }, // Kangaskhan - new(Nest103,4,4,4,SWSH) { Species = 330, Ability = A2 }, // Flygon - new(Nest103,4,4,4,SWSH) { Species = 623, Ability = A2 }, // Golurk - new(Nest104,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest104,0,1,1,SWSH) { Species = 702, Ability = A3 }, // Dedenne - new(Nest104,0,1,1,SWSH) { Species = 081, Ability = A3 }, // Magnemite - new(Nest104,1,2,2,SWSH) { Species = 403, Ability = A3 }, // Shinx - new(Nest104,1,2,2,SWSH) { Species = 877, Ability = A3 }, // Morpeko - new(Nest104,2,3,3,SWSH) { Species = 702, Ability = A3 }, // Dedenne - new(Nest104,2,3,3,SWSH) { Species = 404, Ability = A3 }, // Luxio - new(Nest104,3,4,4,SWSH) { Species = 702, Ability = A4 }, // Dedenne - new(Nest104,3,4,4,SWSH) { Species = 082, Ability = A4 }, // Magneton - new(Nest104,3,4,4,SWSH) { Species = 871, Ability = A4 }, // Pincurchin - new(Nest104,4,4,4,SWSH) { Species = 405, Ability = A4 }, // Luxray - new(Nest104,4,4,4,SWSH) { Species = 462, Ability = A4 }, // Magnezone - new(Nest105,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest105,0,1,1,SWSH) { Species = 403, Ability = A2 }, // Shinx - new(Nest105,0,1,1,SWSH) { Species = 172, Ability = A2 }, // Pichu - new(Nest105,1,2,2,SWSH) { Species = 025, Ability = A2 }, // Pikachu - new(Nest105,1,2,2,SWSH) { Species = 871, Ability = A2 }, // Pincurchin - new(Nest105,2,3,3,SWSH) { Species = 404, Ability = A2 }, // Luxio - new(Nest105,2,3,3,SWSH) { Species = 026, Ability = A2 }, // Raichu - new(Nest105,3,4,4,SWSH) { Species = 836, Ability = A2 }, // Boltund - new(Nest105,3,4,4,SWSH) { Species = 702, Ability = A2 }, // Dedenne - new(Nest105,3,4,4,SWSH) { Species = 310, Ability = A2 }, // Manectric - new(Nest105,4,4,4,SWSH) { Species = 405, Ability = A2 }, // Luxray - new(Nest105,4,4,4,SWSH) { Species = 462, Ability = A2 }, // Magnezone - new(Nest106,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest106,0,1,1,SWSH) { Species = 661, Ability = A3 }, // Fletchling - new(Nest106,0,1,1,SWSH) { Species = 527, Ability = A3 }, // Woobat - new(Nest106,1,2,2,SWSH) { Species = 587, Ability = A3 }, // Emolga - new(Nest106,2,3,3,SWSH) { Species = 662, Ability = A3 }, // Fletchinder - new(Nest106,3,4,4,SWSH) { Species = 587, Ability = A4 }, // Emolga - new(Nest106,3,4,4,SWSH) { Species = 528, Ability = A4 }, // Swoobat - new(Nest106,4,4,4,SWSH) { Species = 663, Ability = A4 }, // Talonflame - new(Nest107,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest107,0,1,1,SWSH) { Species = 163, Ability = A2 }, // Hoothoot - new(Nest107,0,1,1,SWSH) { Species = 519, Ability = A2 }, // Pidove - new(Nest107,1,2,2,SWSH) { Species = 520, Ability = A2 }, // Tranquill - new(Nest107,2,3,3,SWSH) { Species = 528, Ability = A2 }, // Swoobat - new(Nest107,2,3,3,SWSH) { Species = 164, Ability = A2 }, // Noctowl - new(Nest107,3,4,4,SWSH) { Species = 521, Ability = A2 }, // Unfezant - new(Nest107,3,4,4,SWSH) { Species = 663, Ability = A2 }, // Talonflame - new(Nest107,3,4,4,SWSH) { Species = 587, Ability = A2 }, // Emolga - new(Nest107,4,4,4,SWSH) { Species = 663, Ability = A2 }, // Talonflame - new(Nest108,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest108,0,1,1,SWSH) { Species = 557, Ability = A3 }, // Dwebble - new(Nest108,1,2,2,SWSH) { Species = 825, Ability = A3 }, // Dottler - new(Nest108,2,3,3,SWSH) { Species = 558, Ability = A3 }, // Crustle - new(Nest108,3,4,4,SWSH) { Species = 123, Ability = A4 }, // Scyther - new(Nest108,4,4,4,SWSH) { Species = 826, Ability = A4 }, // Orbeetle - new(Nest108,4,4,4,SWSH) { Species = 212, Ability = A4 }, // Scizor - new(Nest109,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest109,0,1,1,SWSH) { Species = 123, Ability = A2 }, // Scyther - new(Nest109,1,2,2,SWSH) { Species = 213, Ability = A2 }, // Shuckle - new(Nest109,1,2,2,SWSH) { Species = 544, Ability = A2 }, // Whirlipede - new(Nest109,2,3,3,SWSH) { Species = 123, Ability = A2 }, // Scyther - new(Nest109,2,3,3,SWSH) { Species = 558, Ability = A2 }, // Crustle - new(Nest109,3,4,4,SWSH) { Species = 545, Ability = A2 }, // Scolipede - new(Nest109,3,4,4,SWSH) { Species = 617, Ability = A2 }, // Accelgor - new(Nest109,3,4,4,SWSH) { Species = 589, Ability = A2 }, // Escavalier - new(Nest109,4,4,4,SWSH) { Species = 212, Ability = A2 }, // Scizor - new(Nest110,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest110,0,1,1,SWSH) { Species = 590, Ability = A3 }, // Foongus - new(Nest110,0,1,1,SWSH) { Species = 753, Ability = A3 }, // Fomantis - new(Nest110,1,2,2,SWSH) { Species = 548, Ability = A3 }, // Petilil - new(Nest110,1,2,2,SWSH) { Species = 754, Ability = A3 }, // Lurantis - new(Nest110,2,3,3,SWSH) { Species = 591, Ability = A3 }, // Amoonguss - new(Nest110,2,3,3,SWSH) { Species = 114, Ability = A3 }, // Tangela - new(Nest110,3,4,4,SWSH) { Species = 549, Ability = A4 }, // Lilligant - new(Nest110,3,4,4,SWSH) { Species = 754, Ability = A4 }, // Lurantis - new(Nest110,4,4,4,SWSH) { Species = 591, Ability = A4 }, // Amoonguss - new(Nest110,4,4,4,SWSH) { Species = 465, Ability = A4 }, // Tangrowth - new(Nest111,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest111,0,1,1,SWSH) { Species = 114, Ability = A2 }, // Tangela - new(Nest111,0,1,1,SWSH) { Species = 753, Ability = A2 }, // Fomantis - new(Nest111,1,2,2,SWSH) { Species = 590, Ability = A2 }, // Foongus - new(Nest111,1,2,2,SWSH) { Species = 754, Ability = A2 }, // Lurantis - new(Nest111,2,3,3,SWSH) { Species = 556, Ability = A2 }, // Maractus - new(Nest111,2,3,3,SWSH) { Species = 549, Ability = A2 }, // Lilligant - new(Nest111,3,4,4,SWSH) { Species = 754, Ability = A2 }, // Lurantis - new(Nest111,3,4,4,SWSH) { Species = 591, Ability = A2 }, // Amoonguss - new(Nest111,3,4,4,SWSH) { Species = 465, Ability = A2 }, // Tangrowth - new(Nest111,4,4,4,SWSH) { Species = 549, Ability = A2 }, // Lilligant - new(Nest111,4,4,4,SWSH) { Species = 460, Ability = A2 }, // Abomasnow - new(Nest112,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest112,0,1,1,SWSH) { Species = 661, Ability = A3 }, // Fletchling - new(Nest112,0,1,1,SWSH) { Species = 757, Ability = A3 }, // Salandit - new(Nest112,1,2,2,SWSH) { Species = 636, Ability = A3 }, // Larvesta - new(Nest112,1,2,2,SWSH) { Species = 757, Ability = A3, Gender = 1 }, // Salandit - new(Nest112,2,3,3,SWSH) { Species = 662, Ability = A3 }, // Fletchinder - new(Nest112,2,3,3,SWSH) { Species = 636, Ability = A3 }, // Larvesta - new(Nest112,3,4,4,SWSH) { Species = 324, Ability = A4 }, // Torkoal - new(Nest112,3,4,4,SWSH) { Species = 663, Ability = A4 }, // Talonflame - new(Nest112,3,4,4,SWSH) { Species = 758, Ability = A4 }, // Salazzle - new(Nest112,4,4,4,SWSH) { Species = 324, Ability = A4 }, // Torkoal - new(Nest112,4,4,4,SWSH) { Species = 637, Ability = A4 }, // Volcarona - new(Nest113,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest113,0,1,1,SWSH) { Species = 636, Ability = A2 }, // Larvesta - new(Nest113,0,1,1,SWSH) { Species = 607, Ability = A2 }, // Litwick - new(Nest113,1,2,2,SWSH) { Species = 636, Ability = A2 }, // Larvesta - new(Nest113,1,2,2,SWSH) { Species = 757, Ability = A2, Gender = 1 }, // Salandit - new(Nest113,2,3,3,SWSH) { Species = 324, Ability = A2 }, // Torkoal - new(Nest113,2,3,3,SWSH) { Species = 758, Ability = A2 }, // Salazzle - new(Nest113,3,4,4,SWSH) { Species = 663, Ability = A2 }, // Talonflame - new(Nest113,3,4,4,SWSH) { Species = 609, Ability = A2 }, // Chandelure - new(Nest113,3,4,4,SWSH) { Species = 637, Ability = A2 }, // Volcarona - new(Nest113,4,4,4,SWSH) { Species = 006, Ability = A2 }, // Charizard - new(Nest114,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest114,0,1,1,SWSH) { Species = 524, Ability = A3 }, // Roggenrola - new(Nest114,0,1,1,SWSH) { Species = 111, Ability = A3 }, // Rhyhorn - new(Nest114,1,2,2,SWSH) { Species = 744, Ability = A3 }, // Rockruff - new(Nest114,1,2,2,SWSH) { Species = 525, Ability = A3 }, // Boldore - new(Nest114,2,3,3,SWSH) { Species = 112, Ability = A3 }, // Rhydon - new(Nest114,2,3,3,SWSH) { Species = 558, Ability = A3 }, // Crustle - new(Nest114,3,4,4,SWSH) { Species = 112, Ability = A4 }, // Rhydon - new(Nest114,3,4,4,SWSH) { Species = 526, Ability = A4 }, // Gigalith - new(Nest114,3,4,4,SWSH) { Species = 558, Ability = A4 }, // Crustle - new(Nest114,4,4,4,SWSH) { Species = 464, Ability = A4 }, // Rhyperior - new(Nest115,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest115,0,1,1,SWSH) { Species = 744, Ability = A2 }, // Rockruff - new(Nest115,0,1,1,SWSH) { Species = 438, Ability = A2 }, // Bonsly - new(Nest115,1,2,2,SWSH) { Species = 111, Ability = A2 }, // Rhyhorn - new(Nest115,1,2,2,SWSH) { Species = 744, Ability = A2 }, // Rockruff - new(Nest115,2,3,3,SWSH) { Species = 112, Ability = A2 }, // Rhydon - new(Nest115,2,3,3,SWSH) { Species = 213, Ability = A2 }, // Shuckle - new(Nest115,3,4,4,SWSH) { Species = 185, Ability = A2 }, // Sudowoodo - new(Nest115,3,4,4,SWSH) { Species = 526, Ability = A2 }, // Gigalith - new(Nest115,4,4,4,SWSH) { Species = 558, Ability = A2 }, // Crustle - new(Nest115,4,4,4,SWSH) { Species = 464, Ability = A2 }, // Rhyperior - new(Nest116,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest116,0,1,1,SWSH) { Species = 102, Ability = A3 }, // Exeggcute - new(Nest116,0,1,1,SWSH) { Species = 063, Ability = A3 }, // Abra - new(Nest116,1,2,2,SWSH) { Species = 280, Ability = A3 }, // Ralts - new(Nest116,1,2,2,SWSH) { Species = 064, Ability = A3 }, // Kadabra - new(Nest116,2,3,3,SWSH) { Species = 281, Ability = A3 }, // Kirlia - new(Nest116,3,4,4,SWSH) { Species = 103, Ability = A4 }, // Exeggutor - new(Nest116,3,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir - new(Nest116,4,4,4,SWSH) { Species = 065, Ability = A4 }, // Alakazam - new(Nest116,4,4,4,SWSH) { Species = 121, Ability = A4 }, // Starmie - new(Nest117,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest117,0,1,1,SWSH) { Species = 605, Ability = A2 }, // Elgyem - new(Nest117,0,1,1,SWSH) { Species = 063, Ability = A2 }, // Abra - new(Nest117,1,2,2,SWSH) { Species = 079, Ability = A2, Form = 1 }, // Slowpoke-1 - new(Nest117,1,2,2,SWSH) { Species = 605, Ability = A2 }, // Elgyem - new(Nest117,2,3,3,SWSH) { Species = 079, Ability = A2, Form = 1 }, // Slowpoke-1 - new(Nest117,3,4,4,SWSH) { Species = 518, Ability = A2 }, // Musharna - new(Nest117,3,4,4,SWSH) { Species = 606, Ability = A2 }, // Beheeyem - new(Nest117,4,4,4,SWSH) { Species = 065, Ability = A2 }, // Alakazam - new(Nest118,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest118,0,1,1,SWSH) { Species = 543, Ability = A3 }, // Venipede - new(Nest118,0,1,1,SWSH) { Species = 451, Ability = A3 }, // Skorupi - new(Nest118,1,2,2,SWSH) { Species = 072, Ability = A3 }, // Tentacool - new(Nest118,2,3,3,SWSH) { Species = 544, Ability = A3 }, // Whirlipede - new(Nest118,3,4,4,SWSH) { Species = 452, Ability = A4 }, // Drapion - new(Nest118,3,4,4,SWSH) { Species = 073, Ability = A4 }, // Tentacruel - new(Nest118,4,4,4,SWSH) { Species = 073, Ability = A4 }, // Tentacruel - new(Nest118,4,4,4,SWSH) { Species = 545, Ability = A4 }, // Scolipede - new(Nest119,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest119,0,1,1,SWSH) { Species = 747, Ability = A2 }, // Mareanie - new(Nest119,0,1,1,SWSH) { Species = 211, Ability = A2 }, // Qwilfish - new(Nest119,1,2,2,SWSH) { Species = 544, Ability = A2 }, // Whirlipede - new(Nest119,2,3,3,SWSH) { Species = 211, Ability = A2 }, // Qwilfish - new(Nest119,2,3,3,SWSH) { Species = 591, Ability = A2 }, // Amoonguss - new(Nest119,3,4,4,SWSH) { Species = 748, Ability = A2 }, // Toxapex - new(Nest119,3,4,4,SWSH) { Species = 545, Ability = A2 }, // Scolipede - new(Nest119,3,4,4,SWSH) { Species = 452, Ability = A2 }, // Drapion - new(Nest119,4,4,4,SWSH) { Species = 110, Ability = A2, Form = 1 }, // Weezing-1 - new(Nest119,4,4,4,SWSH) { Species = 545, Ability = A2 }, // Scolipede - new(Nest120,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest120,0,1,1,SWSH) { Species = 318, Ability = A3 }, // Carvanha - new(Nest120,0,1,1,SWSH) { Species = 624, Ability = A3 }, // Pawniard - new(Nest120,1,2,2,SWSH) { Species = 318, Ability = A3 }, // Carvanha - new(Nest120,1,2,2,SWSH) { Species = 570, Ability = A3 }, // Zorua - new(Nest120,2,3,3,SWSH) { Species = 319, Ability = A3 }, // Sharpedo - new(Nest120,2,3,3,SWSH) { Species = 687, Ability = A3 }, // Malamar - new(Nest120,3,4,4,SWSH) { Species = 452, Ability = A4 }, // Drapion - new(Nest120,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp - new(Nest120,3,4,4,SWSH) { Species = 687, Ability = A4 }, // Malamar - new(Nest120,4,4,4,SWSH) { Species = 319, Ability = A4 }, // Sharpedo - new(Nest120,4,4,4,SWSH) { Species = 571, Ability = A4 }, // Zoroark - new(Nest121,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest121,0,1,1,SWSH) { Species = 570, Ability = A2 }, // Zorua - new(Nest121,0,1,1,SWSH) { Species = 318, Ability = A2 }, // Carvanha - new(Nest121,1,2,2,SWSH) { Species = 570, Ability = A2 }, // Zorua - new(Nest121,1,2,2,SWSH) { Species = 686, Ability = A2 }, // Inkay - new(Nest121,2,3,3,SWSH) { Species = 552, Ability = A2 }, // Krokorok - new(Nest121,2,3,3,SWSH) { Species = 687, Ability = A2 }, // Malamar - new(Nest121,3,4,4,SWSH) { Species = 828, Ability = A2 }, // Thievul - new(Nest121,3,4,4,SWSH) { Species = 571, Ability = A2 }, // Zoroark - new(Nest121,3,4,4,SWSH) { Species = 319, Ability = A2 }, // Sharpedo - new(Nest121,4,4,4,SWSH) { Species = 510, Ability = A2 }, // Liepard - new(Nest121,4,4,4,SWSH) { Species = 553, Ability = A2 }, // Krookodile - new(Nest122,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest122,0,1,1,SWSH) { Species = 619, Ability = A3 }, // Mienfoo - new(Nest122,0,1,1,SWSH) { Species = 852, Ability = A3 }, // Clobbopus - new(Nest122,1,2,2,SWSH) { Species = 619, Ability = A3 }, // Mienfoo - new(Nest122,3,4,4,SWSH) { Species = 620, Ability = A4 }, // Mienshao - new(Nest122,4,4,4,SWSH) { Species = 853, Ability = A4 }, // Grapploct - new(Nest122,4,4,4,SWSH) { Species = 620, Ability = A4 }, // Mienshao - new(Nest123,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest123,0,1,1,SWSH) { Species = 619, Ability = A2 }, // Mienfoo - new(Nest123,1,2,2,SWSH) { Species = 620, Ability = A2 }, // Mienshao - new(Nest123,2,3,3,SWSH) { Species = 870, Ability = A2 }, // Falinks - new(Nest123,3,4,4,SWSH) { Species = 620, Ability = A2 }, // Mienshao - new(Nest123,4,4,4,SWSH) { Species = 853, Ability = A2 }, // Grapploct - new(Nest124,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest124,0,1,1,SWSH) { Species = 174, Ability = A3 }, // Igglybuff - new(Nest124,0,1,1,SWSH) { Species = 298, Ability = A3 }, // Azurill - new(Nest124,1,2,2,SWSH) { Species = 764, Ability = A3 }, // Comfey - new(Nest124,1,2,2,SWSH) { Species = 039, Ability = A3 }, // Jigglypuff - new(Nest124,2,3,3,SWSH) { Species = 183, Ability = A3 }, // Marill - new(Nest124,2,3,3,SWSH) { Species = 764, Ability = A3 }, // Comfey - new(Nest124,3,4,4,SWSH) { Species = 707, Ability = A4 }, // Klefki - new(Nest124,3,4,4,SWSH) { Species = 184, Ability = A4 }, // Azumarill - new(Nest124,3,4,4,SWSH) { Species = 040, Ability = A4 }, // Wigglytuff - new(Nest124,4,4,4,SWSH) { Species = 282, Ability = A4 }, // Gardevoir - new(Nest124,4,4,4,SWSH) { Species = 764, Ability = A4 }, // Comfey - new(Nest125,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest125,0,1,1,SWSH) { Species = 173, Ability = A2 }, // Cleffa - new(Nest125,0,1,1,SWSH) { Species = 755, Ability = A2 }, // Morelull - new(Nest125,1,2,2,SWSH) { Species = 183, Ability = A2 }, // Marill - new(Nest125,1,2,2,SWSH) { Species = 035, Ability = A2 }, // Clefairy - new(Nest125,2,3,3,SWSH) { Species = 281, Ability = A2 }, // Kirlia - new(Nest125,2,3,3,SWSH) { Species = 707, Ability = A2 }, // Klefki - new(Nest125,3,4,4,SWSH) { Species = 764, Ability = A2 }, // Comfey - new(Nest125,3,4,4,SWSH) { Species = 036, Ability = A2 }, // Clefable - new(Nest125,3,4,4,SWSH) { Species = 282, Ability = A2 }, // Gardevoir - new(Nest125,4,4,4,SWSH) { Species = 756, Ability = A2 }, // Shiinotic - new(Nest125,4,4,4,SWSH) { Species = 184, Ability = A2 }, // Azumarill - new(Nest126,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest126,0,1,1,SWSH) { Species = 769, Ability = A3 }, // Sandygast - new(Nest126,0,1,1,SWSH) { Species = 592, Ability = A3 }, // Frillish - new(Nest126,1,2,2,SWSH) { Species = 104, Ability = A3 }, // Cubone - new(Nest126,1,2,2,SWSH) { Species = 425, Ability = A3 }, // Drifloon - new(Nest126,2,3,3,SWSH) { Species = 593, Ability = A3 }, // Jellicent - new(Nest126,2,3,3,SWSH) { Species = 426, Ability = A3 }, // Drifblim - new(Nest126,3,4,4,SWSH) { Species = 770, Ability = A4 }, // Palossand - new(Nest126,3,4,4,SWSH) { Species = 593, Ability = A4 }, // Jellicent - new(Nest126,3,4,4,SWSH) { Species = 426, Ability = A4 }, // Drifblim - new(Nest126,4,4,4,SWSH) { Species = 105, Ability = A4 }, // Marowak - new(Nest126,4,4,4,SWSH) { Species = 770, Ability = A4 }, // Palossand - new(Nest127,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest127,0,1,1,SWSH) { Species = 769, Ability = A2 }, // Sandygast - new(Nest127,0,1,1,SWSH) { Species = 592, Ability = A2 }, // Frillish - new(Nest127,1,2,2,SWSH) { Species = 769, Ability = A2 }, // Sandygast - new(Nest127,1,2,2,SWSH) { Species = 425, Ability = A2 }, // Drifloon - new(Nest127,2,3,3,SWSH) { Species = 593, Ability = A2 }, // Jellicent - new(Nest127,2,3,3,SWSH) { Species = 426, Ability = A2 }, // Drifblim - new(Nest127,3,4,4,SWSH) { Species = 711, Ability = A2 }, // Gourgeist - new(Nest127,3,4,4,SWSH) { Species = 711, Ability = A2, Form = 1 }, // Gourgeist-1 - new(Nest127,3,4,4,SWSH) { Species = 711, Ability = A2, Form = 2 }, // Gourgeist-2 - new(Nest127,4,4,4,SWSH) { Species = 711, Ability = A2, Form = 3 }, // Gourgeist-3 - new(Nest128,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest128,0,1,1,SWSH) { Species = 707, Ability = A3 }, // Klefki - new(Nest128,0,1,1,SWSH) { Species = 081, Ability = A3 }, // Magnemite - new(Nest128,1,2,2,SWSH) { Species = 624, Ability = A3 }, // Pawniard - new(Nest128,1,2,2,SWSH) { Species = 081, Ability = A3 }, // Magnemite - new(Nest128,2,3,3,SWSH) { Species = 227, Ability = A3 }, // Skarmory - new(Nest128,2,3,3,SWSH) { Species = 082, Ability = A3 }, // Magneton - new(Nest128,3,4,4,SWSH) { Species = 082, Ability = A4 }, // Magneton - new(Nest128,3,4,4,SWSH) { Species = 707, Ability = A4 }, // Klefki - new(Nest128,3,4,4,SWSH) { Species = 625, Ability = A4 }, // Bisharp - new(Nest128,4,4,4,SWSH) { Species = 462, Ability = A4 }, // Magnezone - new(Nest128,4,4,4,SWSH) { Species = 227, Ability = A4 }, // Skarmory - new(Nest129,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest129,0,1,1,SWSH) { Species = 081, Ability = A2 }, // Magnemite - new(Nest129,0,1,1,SWSH) { Species = 227, Ability = A2 }, // Skarmory - new(Nest129,1,2,2,SWSH) { Species = 436, Ability = A2 }, // Bronzor - new(Nest129,1,2,2,SWSH) { Species = 052, Ability = A2, Form = 2 }, // Meowth-2 - new(Nest129,2,3,3,SWSH) { Species = 082, Ability = A2 }, // Magneton - new(Nest129,2,3,3,SWSH) { Species = 601, Ability = A2 }, // Klinklang - new(Nest129,3,4,4,SWSH) { Species = 227, Ability = A2 }, // Skarmory - new(Nest129,3,4,4,SWSH) { Species = 437, Ability = A2 }, // Bronzong - new(Nest129,3,4,4,SWSH) { Species = 863, Ability = A2 }, // Perrserker - new(Nest129,4,4,4,SWSH) { Species = 448, Ability = A2 }, // Lucario - new(Nest129,4,4,4,SWSH) { Species = 625, Ability = A2 }, // Bisharp - new(Nest130,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - //new(Nest130,0,1,1,SWSH) { Species = 116, Ability = A3 }, // Horsea - new(Nest130,2,2,2,SWSH) { Species = 840, Ability = A3 }, // Applin - new(Nest130,1,2,2,SWSH) { Species = 117, Ability = A3 }, // Seadra - new(Nest130,2,3,3,SWSH) { Species = 621, Ability = A3 }, // Druddigon - new(Nest130,3,4,4,SWSH) { Species = 621, Ability = A4 }, // Druddigon - new(Nest130,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados - new(Nest130,4,4,4,SWSH) { Species = 230, Ability = A4 }, // Kingdra - new(Nest131,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - //new(Nest131,0,1,1,SWSH) { Species = 116, Ability = A2 }, // Horsea - //new(Nest131,0,1,1,SWSH) { Species = 621, Ability = A2 }, // Druddigon - new(Nest131,2,3,3,SWSH) { Species = 117, Ability = A2 }, // Seadra - new(Nest131,3,4,4,SWSH) { Species = 621, Ability = A2 }, // Druddigon - new(Nest131,3,4,4,SWSH) { Species = 715, Ability = A2 }, // Noivern - new(Nest131,4,4,4,SWSH) { Species = 230, Ability = A2 }, // Kingdra - new(Nest132,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest132,0,1,1,SWSH) { Species = 060, Ability = A3 }, // Poliwag - new(Nest132,0,1,1,SWSH) { Species = 194, Ability = A3 }, // Wooper - new(Nest132,1,2,2,SWSH) { Species = 118, Ability = A3 }, // Goldeen - new(Nest132,1,2,2,SWSH) { Species = 061, Ability = A3 }, // Poliwhirl - new(Nest132,2,3,3,SWSH) { Species = 342, Ability = A3 }, // Crawdaunt - new(Nest132,2,3,3,SWSH) { Species = 061, Ability = A3 }, // Poliwhirl - new(Nest132,3,4,4,SWSH) { Species = 119, Ability = A4 }, // Seaking - new(Nest132,3,4,4,SWSH) { Species = 342, Ability = A4 }, // Crawdaunt - new(Nest132,3,4,4,SWSH) { Species = 195, Ability = A4 }, // Quagsire - new(Nest132,4,4,4,SWSH) { Species = 062, Ability = A4 }, // Poliwrath - new(Nest132,4,4,4,SWSH) { Species = 186, Ability = A4 }, // Politoed - new(Nest133,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest133,0,1,1,SWSH) { Species = 341, Ability = A2 }, // Corphish - new(Nest133,0,1,1,SWSH) { Species = 751, Ability = A2 }, // Dewpider - new(Nest133,1,2,2,SWSH) { Species = 118, Ability = A2 }, // Goldeen - new(Nest133,1,2,2,SWSH) { Species = 061, Ability = A2 }, // Poliwhirl - new(Nest133,2,3,3,SWSH) { Species = 342, Ability = A2 }, // Crawdaunt - new(Nest133,2,3,3,SWSH) { Species = 195, Ability = A2 }, // Quagsire - new(Nest133,3,4,4,SWSH) { Species = 119, Ability = A2 }, // Seaking - new(Nest133,3,4,4,SWSH) { Species = 062, Ability = A2 }, // Poliwrath - new(Nest133,3,4,4,SWSH) { Species = 342, Ability = A2 }, // Crawdaunt - new(Nest133,4,4,4,SWSH) { Species = 752, Ability = A2 }, // Araquanid - new(Nest133,4,4,4,SWSH) { Species = 186, Ability = A2 }, // Politoed - new(Nest134,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest134,0,1,1,SWSH) { Species = 054, Ability = A3 }, // Psyduck - new(Nest134,0,1,1,SWSH) { Species = 833, Ability = A3 }, // Chewtle - new(Nest134,1,2,2,SWSH) { Species = 846, Ability = A3 }, // Arrokuda - new(Nest134,1,2,2,SWSH) { Species = 339, Ability = A3 }, // Barboach - new(Nest134,2,3,3,SWSH) { Species = 055, Ability = A3 }, // Golduck - new(Nest134,2,3,3,SWSH) { Species = 845, Ability = A3 }, // Cramorant - new(Nest134,3,4,4,SWSH) { Species = 055, Ability = A4 }, // Golduck - new(Nest134,3,4,4,SWSH) { Species = 847, Ability = A4 }, // Barraskewda - new(Nest134,3,4,4,SWSH) { Species = 834, Ability = A4 }, // Drednaw - new(Nest134,4,4,4,SWSH) { Species = 340, Ability = A4 }, // Whiscash - new(Nest134,4,4,4,SWSH) { Species = 055, Ability = A4 }, // Golduck - new(Nest135,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest135,0,1,1,SWSH) { Species = 846, Ability = A2 }, // Arrokuda - new(Nest135,0,1,1,SWSH) { Species = 535, Ability = A2 }, // Tympole - new(Nest135,1,2,2,SWSH) { Species = 054, Ability = A2 }, // Psyduck - new(Nest135,1,2,2,SWSH) { Species = 536, Ability = A2 }, // Palpitoad - new(Nest135,2,3,3,SWSH) { Species = 055, Ability = A2 }, // Golduck - new(Nest135,2,3,3,SWSH) { Species = 340, Ability = A2 }, // Whiscash - new(Nest135,3,4,4,SWSH) { Species = 055, Ability = A2 }, // Golduck - new(Nest135,3,4,4,SWSH) { Species = 847, Ability = A2 }, // Barraskewda - new(Nest135,3,4,4,SWSH) { Species = 537, Ability = A2 }, // Seismitoad - new(Nest135,4,4,4,SWSH) { Species = 130, Ability = A2 }, // Gyarados - new(Nest136,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest136,0,1,1,SWSH) { Species = 072, Ability = A3 }, // Tentacool - new(Nest136,0,1,1,SWSH) { Species = 098, Ability = A3 }, // Krabby - new(Nest136,1,2,2,SWSH) { Species = 072, Ability = A3 }, // Tentacool - new(Nest136,1,2,2,SWSH) { Species = 223, Ability = A3 }, // Remoraid - new(Nest136,2,3,3,SWSH) { Species = 073, Ability = A3 }, // Tentacruel - new(Nest136,2,3,3,SWSH) { Species = 746, Ability = A3 }, // Wishiwashi - new(Nest136,3,4,4,SWSH) { Species = 224, Ability = A4 }, // Octillery - new(Nest136,3,4,4,SWSH) { Species = 226, Ability = A4 }, // Mantine - new(Nest136,3,4,4,SWSH) { Species = 099, Ability = A4 }, // Kingler - new(Nest136,4,4,4,SWSH) { Species = 091, Ability = A4 }, // Cloyster - new(Nest136,4,4,4,SWSH) { Species = 073, Ability = A4 }, // Tentacruel - new(Nest137,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest137,0,1,1,SWSH) { Species = 090, Ability = A2 }, // Shellder - new(Nest137,0,1,1,SWSH) { Species = 688, Ability = A2 }, // Binacle - new(Nest137,1,2,2,SWSH) { Species = 747, Ability = A2 }, // Mareanie - new(Nest137,1,2,2,SWSH) { Species = 223, Ability = A2 }, // Remoraid - new(Nest137,2,3,3,SWSH) { Species = 073, Ability = A2 }, // Tentacruel - new(Nest137,2,3,3,SWSH) { Species = 771, Ability = A2 }, // Pyukumuku - new(Nest137,3,4,4,SWSH) { Species = 224, Ability = A2 }, // Octillery - new(Nest137,3,4,4,SWSH) { Species = 226, Ability = A2 }, // Mantine - new(Nest137,3,4,4,SWSH) { Species = 689, Ability = A2 }, // Barbaracle - new(Nest137,4,4,4,SWSH) { Species = 091, Ability = A2 }, // Cloyster - new(Nest137,4,4,4,SWSH) { Species = 748, Ability = A2 }, // Toxapex - new(Nest138,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - //new(Nest138,0,1,1,SWSH) { Species = 170, Ability = A3 }, // Chinchou - new(Nest138,2,2,2,SWSH) { Species = 120, Ability = A3 }, // Staryu - new(Nest138,2,3,3,SWSH) { Species = 320, Ability = A3 }, // Wailmer - new(Nest138,2,3,3,SWSH) { Species = 746, Ability = A3 }, // Wishiwashi - new(Nest138,3,4,4,SWSH) { Species = 321, Ability = A4 }, // Wailord - new(Nest138,3,4,4,SWSH) { Species = 171, Ability = A4 }, // Lanturn - new(Nest138,3,4,4,SWSH) { Species = 121, Ability = A4 }, // Starmie - new(Nest138,4,4,4,SWSH) { Species = 319, Ability = A4 }, // Sharpedo - new(Nest139,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - //new(Nest139,0,1,1,SWSH) { Species = 120, Ability = A2 }, // Staryu - new(Nest139,2,2,2,SWSH) { Species = 320, Ability = A2 }, // Wailmer - new(Nest139,2,2,2,SWSH) { Species = 279, Ability = A2 }, // Pelipper - new(Nest139,2,3,3,SWSH) { Species = 171, Ability = A2 }, // Lanturn - new(Nest139,2,3,3,SWSH) { Species = 117, Ability = A2 }, // Seadra - new(Nest139,3,4,4,SWSH) { Species = 171, Ability = A2 }, // Lanturn - new(Nest139,3,4,4,SWSH) { Species = 121, Ability = A2 }, // Starmie - new(Nest139,4,4,4,SWSH) { Species = 319, Ability = A2 }, // Sharpedo - new(Nest140,0,0,1,SWSH) { Species = 440, Ability = A3 }, // Happiny - new(Nest140,0,1,1,SWSH) { Species = 440, Ability = A3 }, // Happiny - new(Nest140,1,2,2,SWSH) { Species = 440, Ability = A3 }, // Happiny - new(Nest140,2,3,3,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest140,3,4,4,SWSH) { Species = 113, Ability = A4 }, // Chansey - new(Nest140,4,4,4,SWSH) { Species = 242, Ability = A4 }, // Blissey - new(Nest141,0,0,1,SWSH) { Species = 113, Ability = A2 }, // Chansey - new(Nest141,0,1,1,SWSH) { Species = 113, Ability = A2 }, // Chansey - new(Nest141,1,2,2,SWSH) { Species = 113, Ability = A2 }, // Chansey - new(Nest141,2,3,3,SWSH) { Species = 113, Ability = A2 }, // Chansey - new(Nest141,3,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest141,4,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest142,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - //new(Nest142,0,1,1,SWSH) { Species = 415, Ability = A3 }, // Combee - new(Nest142,2,2,2,SWSH) { Species = 415, Ability = A3 }, // Combee - new(Nest142,2,3,3,SWSH) { Species = 415, Ability = A3 }, // Combee - new(Nest142,3,4,4,SWSH) { Species = 416, Ability = A4 }, // Vespiquen - new(Nest142,4,4,4,SWSH) { Species = 416, Ability = A4 }, // Vespiquen - new(Nest143,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - //new(Nest143,0,1,1,SWSH) { Species = 415, Ability = A2, Gender = 1 }, // Combee - new(Nest143,2,2,2,SWSH) { Species = 415, Ability = A2, Gender = 1 }, // Combee - new(Nest143,2,3,3,SWSH) { Species = 416, Ability = A2 }, // Vespiquen - new(Nest143,3,4,4,SWSH) { Species = 416, Ability = A2 }, // Vespiquen - new(Nest143,4,4,4,SWSH) { Species = 416, Ability = A2 }, // Vespiquen - new(Nest144,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest144,0,1,1,SWSH) { Species = 590, Ability = A2 }, // Foongus - new(Nest144,0,1,1,SWSH) { Species = 102, Ability = A2 }, // Exeggcute - new(Nest144,1,2,2,SWSH) { Species = 114, Ability = A2 }, // Tangela - new(Nest144,1,2,2,SWSH) { Species = 315, Ability = A2 }, // Roselia - new(Nest144,2,3,3,SWSH) { Species = 114, Ability = A2 }, // Tangela - new(Nest144,2,3,3,SWSH) { Species = 315, Ability = A2 }, // Roselia - new(Nest144,3,4,4,SWSH) { Species = 103, Ability = A2 }, // Exeggutor - new(Nest144,3,4,4,SWSH) { Species = 003, Ability = A2 }, // Venusaur - new(Nest144,3,4,4,SWSH) { Species = 465, Ability = A2 }, // Tangrowth - new(Nest144,4,4,4,SWSH) { Species = 407, Ability = A2 }, // Roserade - new(Nest144,4,4,4,SWSH) { Species = 003, Ability = A2, CanGigantamax = true }, // Venusaur - new(Nest145,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - //new(Nest145,0,1,1,SWSH) { Species = 129, Ability = A2 }, // Magikarp - //new(Nest145,0,1,1,SWSH) { Species = 072, Ability = A2 }, // Tentacool - new(Nest145,2,2,2,SWSH) { Species = 120, Ability = A2 }, // Staryu - new(Nest145,2,2,2,SWSH) { Species = 688, Ability = A2 }, // Binacle - new(Nest145,2,3,3,SWSH) { Species = 073, Ability = A2 }, // Tentacruel - new(Nest145,2,3,3,SWSH) { Species = 130, Ability = A2 }, // Gyarados - new(Nest145,3,4,4,SWSH) { Species = 073, Ability = A2 }, // Tentacruel - new(Nest145,3,4,4,SWSH) { Species = 130, Ability = A2 }, // Gyarados - new(Nest145,3,4,4,SWSH) { Species = 121, Ability = A2 }, // Starmie - new(Nest145,4,4,4,SWSH) { Species = 689, Ability = A2 }, // Barbaracle - new(Nest145,4,4,4,SWSH) { Species = 009, Ability = A2, CanGigantamax = true }, // Blastoise - new(Nest146,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - //new(Nest146,0,1,1,SWSH) { Species = 098, Ability = A2 }, // Krabby - //new(Nest146,0,1,1,SWSH) { Species = 688, Ability = A2 }, // Binacle - new(Nest146,2,2,2,SWSH) { Species = 072, Ability = A2 }, // Tentacool - new(Nest146,2,2,2,SWSH) { Species = 223, Ability = A2 }, // Remoraid - new(Nest146,2,3,3,SWSH) { Species = 073, Ability = A2 }, // Tentacruel - new(Nest146,2,3,3,SWSH) { Species = 224, Ability = A2 }, // Octillery - new(Nest146,3,4,4,SWSH) { Species = 713, Ability = A2 }, // Avalugg - new(Nest146,3,4,4,SWSH) { Species = 614, Ability = A2 }, // Beartic - new(Nest146,3,4,4,SWSH) { Species = 099, Ability = A2 }, // Kingler - new(Nest146,4,4,4,SWSH) { Species = 091, Ability = A2 }, // Cloyster - new(Nest146,4,4,4,SWSH) { Species = 099, Ability = A2, CanGigantamax = true }, // Kingler - new(Nest147,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest147,0,1,1,SWSH) { Species = 833, Ability = A2 }, // Chewtle - new(Nest147,0,1,1,SWSH) { Species = 054, Ability = A2 }, // Psyduck - new(Nest147,1,2,2,SWSH) { Species = 339, Ability = A2 }, // Barboach - new(Nest147,2,3,3,SWSH) { Species = 055, Ability = A2 }, // Golduck - new(Nest147,2,3,3,SWSH) { Species = 845, Ability = A2 }, // Cramorant - new(Nest147,3,4,4,SWSH) { Species = 055, Ability = A2 }, // Golduck - new(Nest147,3,4,4,SWSH) { Species = 847, Ability = A2 }, // Barraskewda - new(Nest147,4,4,4,SWSH) { Species = 340, Ability = A2 }, // Whiscash - new(Nest147,4,4,4,SWSH) { Species = 834, Ability = A2, CanGigantamax = true }, // Drednaw - new(Nest148,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest148,0,1,1,SWSH) { Species = 824, Ability = A2 }, // Blipbug - new(Nest148,0,1,1,SWSH) { Species = 742, Ability = A2 }, // Cutiefly - new(Nest148,1,2,2,SWSH) { Species = 595, Ability = A2 }, // Joltik - new(Nest148,2,3,3,SWSH) { Species = 825, Ability = A2 }, // Dottler - new(Nest148,2,3,3,SWSH) { Species = 291, Ability = A2 }, // Ninjask - new(Nest148,3,4,4,SWSH) { Species = 826, Ability = A2 }, // Orbeetle - new(Nest148,3,4,4,SWSH) { Species = 596, Ability = A2 }, // Galvantula - new(Nest148,3,4,4,SWSH) { Species = 743, Ability = A2 }, // Ribombee - new(Nest148,4,4,4,SWSH) { Species = 291, Ability = A2 }, // Ninjask - new(Nest148,4,4,4,SWSH) { Species = 826, Ability = A2, CanGigantamax = true }, // Orbeetle - new(Nest149,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest149,0,1,1,SWSH) { Species = 843, Ability = A2 }, // Silicobra - new(Nest149,0,1,1,SWSH) { Species = 529, Ability = A2 }, // Drilbur - new(Nest149,1,2,2,SWSH) { Species = 843, Ability = A2 }, // Silicobra - new(Nest149,1,2,2,SWSH) { Species = 529, Ability = A2 }, // Drilbur - new(Nest149,2,3,3,SWSH) { Species = 028, Ability = A2 }, // Sandslash - new(Nest149,2,3,3,SWSH) { Species = 552, Ability = A2 }, // Krokorok - new(Nest149,3,4,4,SWSH) { Species = 844, Ability = A2 }, // Sandaconda - new(Nest149,3,4,4,SWSH) { Species = 553, Ability = A2 }, // Krookodile - new(Nest149,3,4,4,SWSH) { Species = 530, Ability = A2 }, // Excadrill - new(Nest149,4,4,4,SWSH) { Species = 553, Ability = A2 }, // Krookodile - new(Nest149,4,4,4,SWSH) { Species = 844, Ability = A2, CanGigantamax = true }, // Sandaconda - new(Nest150,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - //new(Nest150,0,1,1,SWSH) { Species = 840, Ability = A2 }, // Applin - //new(Nest150,0,1,1,SWSH) { Species = 420, Ability = A2 }, // Cherubi (DLC1) - //new(Nest150,0,1,1,SWSH) { Species = 761, Ability = A2 }, // Bounsweet (DLC2) - new(Nest150,2,2,2,SWSH) { Species = 420, Ability = A2 }, // Cherubi - new(Nest150,2,2,2,SWSH) { Species = 840, Ability = A2 }, // Applin - new(Nest150,2,3,3,SWSH) { Species = 762, Ability = A2 }, // Steenee - new(Nest150,3,4,4,SWSH) { Species = 820, Ability = A2 }, // Greedent - new(Nest150,4,4,4,SWSH) { Species = 763, Ability = A2 }, // Tsareena - new(Nest151,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - //new(Nest151,0,0,1,SWSH) { Species = 132, Ability = A3 }, // Ditto - //new(Nest151,0,1,2,SWSH) { Species = 132, Ability = A3 }, // Ditto - new(Nest151,2,2,3,SWSH) { Species = 132, Ability = A3 }, // Ditto - new(Nest151,2,3,3,SWSH) { Species = 132, Ability = A3 }, // Ditto - new(Nest151,2,3,3,SWSH) { Species = 132, Ability = A4 }, // Ditto - new(Nest151,3,4,4,SWSH) { Species = 132, Ability = A4 }, // Ditto - new(Nest151,4,4,4,SWSH) { Species = 132, Ability = A4 }, // Ditto - new(Nest152,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - //new(Nest152,0,0,1,SWSH) { Species = 132, Ability = A2 }, // Ditto - //new(Nest152,0,1,2,SWSH) { Species = 132, Ability = A2 }, // Ditto - new(Nest152,2,2,3,SWSH) { Species = 132, Ability = A2 }, // Ditto - new(Nest152,2,3,3,SWSH) { Species = 132, Ability = A2 }, // Ditto - new(Nest152,3,4,4,SWSH) { Species = 132, Ability = A2 }, // Ditto - new(Nest152,4,4,4,SWSH) { Species = 132, Ability = A2 }, // Ditto - new(Nest153,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest153,0,1,1,SWSH) { Species = 590, Ability = A3 }, // Foongus - new(Nest153,0,1,1,SWSH) { Species = 102, Ability = A3 }, // Exeggcute - new(Nest153,1,2,2,SWSH) { Species = 753, Ability = A3 }, // Fomantis - new(Nest153,1,2,2,SWSH) { Species = 114, Ability = A3 }, // Tangela - new(Nest153,2,3,3,SWSH) { Species = 754, Ability = A3 }, // Lurantis - new(Nest153,2,3,3,SWSH) { Species = 102, Ability = A3 }, // Exeggcute - new(Nest153,3,4,4,SWSH) { Species = 103, Ability = A4 }, // Exeggutor - new(Nest153,3,4,4,SWSH) { Species = 591, Ability = A4 }, // Amoonguss - new(Nest153,3,4,4,SWSH) { Species = 754, Ability = A4 }, // Lurantis - new(Nest153,4,4,4,SWSH) { Species = 465, Ability = A4 }, // Tangrowth - new(Nest153,4,4,4,SWSH) { Species = 003, Ability = A4 }, // Venusaur - new(Nest154,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - //new(Nest154,0,1,1,SWSH) { Species = 129, Ability = A3 }, // Magikarp - //new(Nest154,0,1,1,SWSH) { Species = 072, Ability = A3 }, // Tentacool - new(Nest154,2,2,2,SWSH) { Species = 120, Ability = A3 }, // Staryu - new(Nest154,1,2,2,SWSH) { Species = 090, Ability = A3 }, // Shellder - new(Nest154,2,3,3,SWSH) { Species = 073, Ability = A3 }, // Tentacruel - new(Nest154,2,3,3,SWSH) { Species = 130, Ability = A3 }, // Gyarados - new(Nest154,3,4,4,SWSH) { Species = 073, Ability = A4 }, // Tentacruel - new(Nest154,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados - new(Nest154,3,4,4,SWSH) { Species = 121, Ability = A4 }, // Starmie - new(Nest154,4,4,4,SWSH) { Species = 091, Ability = A4 }, // Cloyster - new(Nest154,4,4,4,SWSH) { Species = 009, Ability = A4 }, // Blastoise - new(Nest155,2,4,4,SWSH) { Species = 113, Ability = A3 }, // Chansey - new(Nest155,0,1,1,SWSH) { Species = 744, Ability = A3 }, // Rockruff - new(Nest155,1,2,2,SWSH) { Species = 744, Ability = A3 }, // Rockruff - new(Nest155,2,3,3,SWSH) { Species = 744, Ability = A3 }, // Rockruff - new(Nest155,2,3,3,SWSH) { Species = 744, Ability = A3, Form = 1 }, // Rockruff-1 - new(Nest155,3,4,4,SWSH) { Species = 745, Ability = A4 }, // Lycanroc - new(Nest155,3,4,4,SWSH) { Species = 745, Ability = A4, Form = 1 }, // Lycanroc-1 - new(Nest155,4,4,4,SWSH) { Species = 745, Ability = A4, Form = 2 }, // Lycanroc-2 - new(Nest156,2,4,4,SWSH) { Species = 242, Ability = A2 }, // Blissey - new(Nest156,0,1,1,SWSH) { Species = 744, Ability = A2 }, // Rockruff - new(Nest156,1,2,2,SWSH) { Species = 744, Ability = A2 }, // Rockruff - new(Nest156,2,3,3,SWSH) { Species = 744, Ability = A2 }, // Rockruff - new(Nest156,2,3,3,SWSH) { Species = 744, Ability = A2, Form = 1 }, // Rockruff-1 - new(Nest156,3,3,4,SWSH) { Species = 745, Ability = A2 }, // Lycanroc - new(Nest156,3,3,4,SWSH) { Species = 745, Ability = A2, Form = 1 }, // Lycanroc-1 - new(Nest156,4,4,4,SWSH) { Species = 745, Ability = A2 }, // Lycanroc - new(Nest156,4,4,4,SWSH) { Species = 745, Ability = A2, Form = 1 }, // Lycanroc-1 - new(Nest156,3,4,4,SWSH) { Species = 745, Ability = A2, Form = 2 }, // Lycanroc-2 - new(Nest157,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest157,0,1,1,SWSH) { Species = 333, Ability = A3 }, // Swablu - new(Nest157,0,1,1,SWSH) { Species = 831, Ability = A3 }, // Wooloo - new(Nest157,1,2,2,SWSH) { Species = 333, Ability = A3 }, // Swablu - new(Nest157,1,2,2,SWSH) { Species = 446, Ability = A3 }, // Munchlax - new(Nest157,2,3,3,SWSH) { Species = 820, Ability = A3 }, // Greedent - new(Nest157,2,3,3,SWSH) { Species = 832, Ability = A3 }, // Dubwool - new(Nest157,3,4,4,SWSH) { Species = 334, Ability = A4 }, // Altaria - new(Nest157,3,4,4,SWSH) { Species = 832, Ability = A4 }, // Dubwool - new(Nest157,4,4,4,SWSH) { Species = 143, Ability = A4 }, // Snorlax - new(Nest158,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest158,0,1,2,SWSH) { Species = 333, Ability = A2 }, // Swablu - new(Nest158,0,1,2,SWSH) { Species = 819, Ability = A2 }, // Skwovet - new(Nest158,1,2,3,SWSH) { Species = 333, Ability = A2 }, // Swablu - new(Nest158,1,2,3,SWSH) { Species = 820, Ability = A2 }, // Greedent - new(Nest158,2,3,4,SWSH) { Species = 820, Ability = A2 }, // Greedent - new(Nest158,2,3,4,SWSH) { Species = 832, Ability = A2 }, // Dubwool - new(Nest158,3,4,5,SWSH) { Species = 334, Ability = A2 }, // Altaria - new(Nest158,3,4,5,SWSH) { Species = 832, Ability = A2 }, // Dubwool - new(Nest158,4,4,5,SWSH) { Species = 143, Ability = A2 }, // Snorlax - new(Nest158,4,4,5,SWSH) { Species = 143, Ability = A2, CanGigantamax = true }, // Snorlax - new(Nest159,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest159,0,1,1,SWSH) { Species = 240, Ability = A3 }, // Magby - new(Nest159,0,1,1,SWSH) { Species = 850, Ability = A3 }, // Sizzlipede - new(Nest159,1,2,2,SWSH) { Species = 240, Ability = A3 }, // Magby - new(Nest159,1,2,2,SWSH) { Species = 631, Ability = A3 }, // Heatmor - new(Nest159,2,3,3,SWSH) { Species = 608, Ability = A3 }, // Lampent - new(Nest159,2,3,3,SWSH) { Species = 631, Ability = A3 }, // Heatmor - new(Nest159,3,4,4,SWSH) { Species = 126, Ability = A4 }, // Magmar - new(Nest159,3,4,4,SWSH) { Species = 851, Ability = A4 }, // Centiskorch - new(Nest159,3,4,4,SWSH) { Species = 609, Ability = A4 }, // Chandelure - new(Nest159,4,4,4,SWSH) { Species = 126, Ability = A4 }, // Magmar - new(Nest159,4,4,4,SWSH) { Species = 467, Ability = A4 }, // Magmortar - new(Nest160,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest160,0,1,2,SWSH) { Species = 240, Ability = A2 }, // Magby - new(Nest160,1,2,3,SWSH) { Species = 126, Ability = A2 }, // Magmar - new(Nest160,1,2,3,SWSH) { Species = 631, Ability = A2 }, // Heatmor - new(Nest160,2,3,4,SWSH) { Species = 126, Ability = A2 }, // Magmar - new(Nest160,2,3,4,SWSH) { Species = 851, Ability = A2 }, // Centiskorch - new(Nest160,3,4,5,SWSH) { Species = 609, Ability = A2 }, // Chandelure - new(Nest160,3,4,5,SWSH) { Species = 467, Ability = A2 }, // Magmortar - new(Nest160,4,4,5,SWSH) { Species = 467, Ability = A2 }, // Magmortar - new(Nest160,4,4,5,SWSH) { Species = 851, Ability = A2, CanGigantamax = true }, // Centiskorch - new(Nest161,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest161,0,1,1,SWSH) { Species = 349, Ability = A3 }, // Feebas - new(Nest161,1,2,2,SWSH) { Species = 349, Ability = A3 }, // Feebas - new(Nest161,2,3,3,SWSH) { Species = 340, Ability = A3 }, // Whiscash - new(Nest161,3,4,4,SWSH) { Species = 130, Ability = A4 }, // Gyarados - new(Nest161,4,4,4,SWSH) { Species = 350, Ability = A4 }, // Milotic - new(Nest161,4,4,4,SWSH) { Species = 369, Ability = A4 }, // Relicanth - new(Nest162,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest162,0,1,2,SWSH) { Species = 349, Ability = A2 }, // Feebas - new(Nest162,1,2,3,SWSH) { Species = 349, Ability = A2 }, // Feebas - new(Nest162,1,2,3,SWSH) { Species = 369, Ability = A2 }, // Relicanth - new(Nest162,2,3,4,SWSH) { Species = 099, Ability = A2 }, // Kingler - new(Nest162,3,4,5,SWSH) { Species = 369, Ability = A2 }, // Relicanth - new(Nest162,3,4,5,SWSH) { Species = 350, Ability = A2 }, // Milotic - new(Nest162,4,4,5,SWSH) { Species = 130, Ability = A2 }, // Gyarados - new(Nest162,4,4,5,SWSH) { Species = 099, Ability = A2, CanGigantamax = true }, // Kingler - new(Nest163,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest163,0,1,1,SWSH) { Species = 239, Ability = A3 }, // Elekid - new(Nest163,0,1,1,SWSH) { Species = 595, Ability = A3 }, // Joltik - new(Nest163,1,2,2,SWSH) { Species = 239, Ability = A3 }, // Elekid - new(Nest163,1,2,2,SWSH) { Species = 871, Ability = A3 }, // Pincurchin - new(Nest163,2,3,3,SWSH) { Species = 125, Ability = A3 }, // Electabuzz - new(Nest163,2,3,3,SWSH) { Species = 778, Ability = A3 }, // Mimikyu - new(Nest163,3,4,4,SWSH) { Species = 596, Ability = A4 }, // Galvantula - new(Nest163,3,4,4,SWSH) { Species = 871, Ability = A4 }, // Pincurchin - new(Nest163,3,4,4,SWSH) { Species = 836, Ability = A4 }, // Boltund - new(Nest163,4,4,4,SWSH) { Species = 125, Ability = A4 }, // Electabuzz - new(Nest163,4,4,4,SWSH) { Species = 466, Ability = A4 }, // Electivire - new(Nest164,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest164,0,1,2,SWSH) { Species = 239, Ability = A2 }, // Elekid - new(Nest164,1,2,3,SWSH) { Species = 702, Ability = A2 }, // Dedenne - new(Nest164,1,2,3,SWSH) { Species = 596, Ability = A2 }, // Galvantula - new(Nest164,2,3,4,SWSH) { Species = 125, Ability = A2 }, // Electabuzz - new(Nest164,2,3,4,SWSH) { Species = 836, Ability = A2 }, // Boltund - new(Nest164,3,4,5,SWSH) { Species = 871, Ability = A2 }, // Pincurchin - new(Nest164,3,4,5,SWSH) { Species = 466, Ability = A2 }, // Electivire - new(Nest164,4,4,5,SWSH) { Species = 466, Ability = A2 }, // Electivire - new(Nest165,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest165,0,1,1,SWSH) { Species = 829, Ability = A3 }, // Gossifleur - new(Nest165,1,2,2,SWSH) { Species = 347, Ability = A3 }, // Anorith - new(Nest165,1,2,2,SWSH) { Species = 345, Ability = A3 }, // Lileep - new(Nest165,2,3,3,SWSH) { Species = 830, Ability = A3 }, // Eldegoss - new(Nest165,3,4,4,SWSH) { Species = 752, Ability = A4 }, // Araquanid - new(Nest165,3,4,4,SWSH) { Species = 830, Ability = A4 }, // Eldegoss - new(Nest165,4,4,4,SWSH) { Species = 598, Ability = A4 }, // Ferrothorn - new(Nest166,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest166,0,1,2,SWSH) { Species = 347, Ability = A2 }, // Anorith - new(Nest166,0,1,2,SWSH) { Species = 345, Ability = A2 }, // Lileep - new(Nest166,1,2,3,SWSH) { Species = 347, Ability = A2 }, // Anorith - new(Nest166,1,2,3,SWSH) { Species = 345, Ability = A2 }, // Lileep - new(Nest166,2,3,4,SWSH) { Species = 752, Ability = A2 }, // Araquanid - new(Nest166,2,3,4,SWSH) { Species = 012, Ability = A2 }, // Butterfree - new(Nest166,3,4,5,SWSH) { Species = 348, Ability = A2 }, // Armaldo - new(Nest166,3,4,5,SWSH) { Species = 346, Ability = A2 }, // Cradily - new(Nest166,3,4,5,SWSH) { Species = 830, Ability = A2 }, // Eldegoss - new(Nest166,4,4,5,SWSH) { Species = 012, Ability = A2, CanGigantamax = true }, // Butterfree - new(Nest167,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest167,0,1,1,SWSH) { Species = 238, Ability = A3 }, // Smoochum - new(Nest167,1,2,2,SWSH) { Species = 238, Ability = A3 }, // Smoochum - new(Nest167,1,2,2,SWSH) { Species = 698, Ability = A3 }, // Amaura - new(Nest167,2,3,3,SWSH) { Species = 221, Ability = A3 }, // Piloswine - new(Nest167,2,3,3,SWSH) { Species = 460, Ability = A3 }, // Abomasnow - new(Nest167,3,4,4,SWSH) { Species = 124, Ability = A4 }, // Jynx - new(Nest167,3,4,4,SWSH) { Species = 873, Ability = A4 }, // Frosmoth - new(Nest167,4,4,4,SWSH) { Species = 699, Ability = A4 }, // Aurorus - new(Nest167,4,4,4,SWSH) { Species = 362, Ability = A4 }, // Glalie - new(Nest168,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest168,0,1,2,SWSH) { Species = 361, Ability = A2 }, // Snorunt - new(Nest168,1,2,3,SWSH) { Species = 238, Ability = A2 }, // Smoochum - new(Nest168,1,2,3,SWSH) { Species = 698, Ability = A2 }, // Amaura - new(Nest168,2,3,4,SWSH) { Species = 362, Ability = A2 }, // Glalie - new(Nest168,2,3,4,SWSH) { Species = 460, Ability = A2 }, // Abomasnow - new(Nest168,3,4,5,SWSH) { Species = 124, Ability = A2 }, // Jynx - new(Nest168,3,4,5,SWSH) { Species = 873, Ability = A2 }, // Frosmoth - new(Nest168,4,4,5,SWSH) { Species = 699, Ability = A2 }, // Aurorus - new(Nest168,4,4,5,SWSH) { Species = 473, Ability = A2 }, // Mamoswine - new(Nest169,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - //new(Nest169,0,1,1,SWSH) { Species = 363, Ability = A3 }, // Spheal - new(Nest169,2,2,2,SWSH) { Species = 363, Ability = A3 }, // Spheal - new(Nest169,2,3,3,SWSH) { Species = 364, Ability = A3 }, // Sealeo - new(Nest169,2,3,3,SWSH) { Species = 615, Ability = A3 }, // Cryogonal - new(Nest169,3,4,4,SWSH) { Species = 584, Ability = A4 }, // Vanilluxe - new(Nest169,3,4,4,SWSH) { Species = 614, Ability = A4 }, // Beartic - new(Nest169,3,4,4,SWSH) { Species = 365, Ability = A4 }, // Walrein - new(Nest169,4,4,4,SWSH) { Species = 713, Ability = A4 }, // Avalugg - new(Nest169,4,4,4,SWSH) { Species = 131, Ability = A4 }, // Lapras - new(Nest170,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - //new(Nest170,0,1,2,SWSH) { Species = 131, Ability = A2 }, // Lapras - //new(Nest170,0,1,2,SWSH) { Species = 363, Ability = A2 }, // Spheal - new(Nest170,2,2,3,SWSH) { Species = 364, Ability = A2 }, // Sealeo - new(Nest170,2,3,4,SWSH) { Species = 713, Ability = A2 }, // Avalugg - new(Nest170,2,3,4,SWSH) { Species = 615, Ability = A2 }, // Cryogonal - new(Nest170,3,4,5,SWSH) { Species = 365, Ability = A2 }, // Walrein - new(Nest170,3,4,5,SWSH) { Species = 131, Ability = A2 }, // Lapras - new(Nest170,3,4,5,SWSH) { Species = 584, Ability = A2 }, // Vanilluxe - new(Nest170,4,4,5,SWSH) { Species = 365, Ability = A2 }, // Walrein - new(Nest171,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest171,0,1,1,SWSH) { Species = 532, Ability = A3 }, // Timburr - new(Nest171,0,1,1,SWSH) { Species = 622, Ability = A3 }, // Golett - new(Nest171,1,2,2,SWSH) { Species = 622, Ability = A3 }, // Golett - new(Nest171,1,2,2,SWSH) { Species = 838, Ability = A3 }, // Carkol - new(Nest171,2,3,3,SWSH) { Species = 533, Ability = A3 }, // Gurdurr - new(Nest171,2,3,3,SWSH) { Species = 623, Ability = A3 }, // Golurk - new(Nest171,3,4,4,SWSH) { Species = 534, Ability = A4 }, // Conkeldurr - new(Nest171,3,4,4,SWSH) { Species = 623, Ability = A4 }, // Golurk - new(Nest171,3,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal - new(Nest171,4,4,4,SWSH) { Species = 623, Ability = A4 }, // Golurk - new(Nest171,4,4,4,SWSH) { Species = 534, Ability = A4 }, // Conkeldurr - new(Nest172,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest172,0,1,2,SWSH) { Species = 870, Ability = A2 }, // Falinks - new(Nest172,0,1,2,SWSH) { Species = 236, Ability = A2 }, // Tyrogue - new(Nest172,1,2,3,SWSH) { Species = 533, Ability = A2 }, // Gurdurr - new(Nest172,2,3,4,SWSH) { Species = 870, Ability = A2 }, // Falinks - new(Nest172,2,3,4,SWSH) { Species = 623, Ability = A2 }, // Golurk - new(Nest172,3,4,5,SWSH) { Species = 534, Ability = A2 }, // Conkeldurr - new(Nest172,4,4,5,SWSH) { Species = 237, Ability = A2 }, // Hitmontop - new(Nest173,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest173,0,1,1,SWSH) { Species = 041, Ability = A3 }, // Zubat - new(Nest173,1,2,2,SWSH) { Species = 029, Ability = A3 }, // Nidoran♀ - new(Nest173,1,2,2,SWSH) { Species = 032, Ability = A3 }, // Nidoran♂ - new(Nest173,2,3,3,SWSH) { Species = 030, Ability = A3 }, // Nidorina - new(Nest173,2,3,3,SWSH) { Species = 033, Ability = A3 }, // Nidorino - new(Nest173,3,4,4,SWSH) { Species = 042, Ability = A4 }, // Golbat - new(Nest173,4,4,4,SWSH) { Species = 031, Ability = A4 }, // Nidoqueen - new(Nest173,4,4,4,SWSH) { Species = 034, Ability = A4 }, // Nidoking - new(Nest174,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest174,0,1,2,SWSH) { Species = 041, Ability = A2 }, // Zubat - new(Nest174,0,1,2,SWSH) { Species = 568, Ability = A2 }, // Trubbish - new(Nest174,1,2,3,SWSH) { Species = 079, Ability = A2, Form = 1 }, // Slowpoke-1 - new(Nest174,2,3,4,SWSH) { Species = 042, Ability = A2 }, // Golbat - new(Nest174,2,3,4,SWSH) { Species = 569, Ability = A2 }, // Garbodor - new(Nest174,3,4,5,SWSH) { Species = 031, Ability = A2 }, // Nidoqueen - new(Nest174,3,4,5,SWSH) { Species = 034, Ability = A2 }, // Nidoking - new(Nest174,4,4,5,SWSH) { Species = 169, Ability = A2 }, // Crobat - new(Nest174,4,4,5,SWSH) { Species = 569, Ability = A2, CanGigantamax = true }, // Garbodor - new(Nest175,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest175,0,1,1,SWSH) { Species = 041, Ability = A3 }, // Zubat - new(Nest175,0,1,1,SWSH) { Species = 714, Ability = A3 }, // Noibat - new(Nest175,1,2,2,SWSH) { Species = 333, Ability = A3 }, // Swablu - new(Nest175,1,2,2,SWSH) { Species = 042, Ability = A3 }, // Golbat - new(Nest175,2,3,3,SWSH) { Species = 042, Ability = A3 }, // Golbat - new(Nest175,2,3,3,SWSH) { Species = 822, Ability = A3 }, // Corvisquire - new(Nest175,3,4,4,SWSH) { Species = 042, Ability = A4 }, // Golbat - new(Nest175,3,4,4,SWSH) { Species = 334, Ability = A4 }, // Altaria - new(Nest175,3,4,4,SWSH) { Species = 715, Ability = A4 }, // Noivern - new(Nest175,4,4,4,SWSH) { Species = 823, Ability = A4 }, // Corviknight - new(Nest175,4,4,4,SWSH) { Species = 169, Ability = A4 }, // Crobat - new(Nest176,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest176,0,1,2,SWSH) { Species = 041, Ability = A2 }, // Zubat - new(Nest176,0,1,2,SWSH) { Species = 527, Ability = A2 }, // Woobat - new(Nest176,1,2,3,SWSH) { Species = 822, Ability = A2 }, // Corvisquire - new(Nest176,1,2,3,SWSH) { Species = 042, Ability = A2 }, // Golbat - new(Nest176,2,3,4,SWSH) { Species = 528, Ability = A2 }, // Swoobat - new(Nest176,2,3,4,SWSH) { Species = 823, Ability = A2 }, // Corviknight - new(Nest176,3,4,5,SWSH) { Species = 142, Ability = A2 }, // Aerodactyl - new(Nest176,3,4,5,SWSH) { Species = 334, Ability = A2 }, // Altaria - new(Nest176,3,4,5,SWSH) { Species = 169, Ability = A2 }, // Crobat - new(Nest176,4,4,5,SWSH) { Species = 715, Ability = A2 }, // Noivern - new(Nest176,4,4,5,SWSH) { Species = 823, Ability = A2, CanGigantamax = true }, // Corviknight - new(Nest177,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest177,0,1,1,SWSH) { Species = 439, Ability = A3 }, // Mime Jr. - new(Nest177,1,2,2,SWSH) { Species = 436, Ability = A3 }, // Bronzor - new(Nest177,1,2,2,SWSH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 - new(Nest177,2,3,3,SWSH) { Species = 344, Ability = A3 }, // Claydol - new(Nest177,4,4,4,SWSH) { Species = 866, Ability = A4 }, // Mr. Rime - new(Nest177,4,4,4,SWSH) { Species = 437, Ability = A4 }, // Bronzong - new(Nest178,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest178,1,2,3,SWSH) { Species = 122, Ability = A2, Form = 1 }, // Mr. Mime-1 - new(Nest178,1,2,3,SWSH) { Species = 079, Ability = A2, Form = 1 }, // Slowpoke-1 - new(Nest178,2,3,4,SWSH) { Species = 375, Ability = A2 }, // Metang - new(Nest178,3,4,5,SWSH) { Species = 866, Ability = A2 }, // Mr. Rime - new(Nest178,4,4,5,SWSH) { Species = 376, Ability = A2 }, // Metagross - new(Nest179,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest179,0,1,1,SWSH) { Species = 304, Ability = A3 }, // Aron - new(Nest179,1,2,2,SWSH) { Species = 304, Ability = A3 }, // Aron - new(Nest179,2,3,3,SWSH) { Species = 305, Ability = A3 }, // Lairon - new(Nest179,3,4,4,SWSH) { Species = 305, Ability = A4 }, // Lairon - new(Nest179,3,4,4,SWSH) { Species = 703, Ability = A4 }, // Carbink - new(Nest179,4,4,4,SWSH) { Species = 306, Ability = A4 }, // Aggron - new(Nest179,4,4,4,SWSH) { Species = 839, Ability = A4 }, // Coalossal - new(Nest180,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest180,0,1,2,SWSH) { Species = 304, Ability = A2 }, // Aron - new(Nest180,1,2,3,SWSH) { Species = 305, Ability = A2 }, // Lairon - new(Nest180,2,3,4,SWSH) { Species = 213, Ability = A2 }, // Shuckle - new(Nest180,3,4,5,SWSH) { Species = 839, Ability = A2 }, // Coalossal - new(Nest180,3,4,5,SWSH) { Species = 306, Ability = A2 }, // Aggron - new(Nest180,4,4,5,SWSH) { Species = 306, Ability = A2 }, // Aggron - new(Nest181,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest181,0,1,1,SWSH) { Species = 885, Ability = A3 }, // Dreepy - new(Nest181,0,1,1,SWSH) { Species = 708, Ability = A3 }, // Phantump - new(Nest181,1,2,2,SWSH) { Species = 778, Ability = A3 }, // Mimikyu - new(Nest181,1,2,2,SWSH) { Species = 361, Ability = A3 }, // Snorunt - new(Nest181,2,3,3,SWSH) { Species = 886, Ability = A3 }, // Drakloak - new(Nest181,2,3,3,SWSH) { Species = 778, Ability = A3 }, // Mimikyu - new(Nest181,3,4,4,SWSH) { Species = 362, Ability = A4 }, // Glalie - new(Nest181,3,4,4,SWSH) { Species = 478, Ability = A4 }, // Froslass - new(Nest181,4,4,4,SWSH) { Species = 709, Ability = A4 }, // Trevenant - new(Nest181,4,4,4,SWSH) { Species = 778, Ability = A4 }, // Mimikyu - new(Nest182,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest182,0,1,2,SWSH) { Species = 885, Ability = A2 }, // Dreepy - new(Nest182,1,2,3,SWSH) { Species = 885, Ability = A2 }, // Dreepy - new(Nest182,2,3,4,SWSH) { Species = 709, Ability = A2 }, // Trevenant - new(Nest182,3,4,5,SWSH) { Species = 887, Ability = A2 }, // Dragapult - new(Nest182,4,4,5,SWSH) { Species = 887, Ability = A2 }, // Dragapult - new(Nest183,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest183,0,1,1,SWSH) { Species = 621, Ability = A3 }, // Druddigon - new(Nest183,1,2,2,SWSH) { Species = 696, Ability = A3 }, // Tyrunt - new(Nest183,2,3,3,SWSH) { Species = 147, Ability = A3 }, // Dratini - new(Nest183,3,4,4,SWSH) { Species = 621, Ability = A4 }, // Druddigon - new(Nest183,3,4,4,SWSH) { Species = 697, Ability = A4 }, // Tyrantrum - new(Nest184,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest184,0,1,2,SWSH) { Species = 884, Ability = A2 }, // Duraludon - new(Nest184,1,2,3,SWSH) { Species = 696, Ability = A2 }, // Tyrunt - new(Nest184,2,3,4,SWSH) { Species = 884, Ability = A2 }, // Duraludon - new(Nest184,3,4,5,SWSH) { Species = 149, Ability = A2 }, // Dragonite - new(Nest184,3,4,5,SWSH) { Species = 697, Ability = A2 }, // Tyrantrum - new(Nest184,4,4,5,SWSH) { Species = 884, Ability = A2, CanGigantamax = true }, // Duraludon - new(Nest185,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest185,0,1,1,SWSH) { Species = 215, Ability = A3 }, // Sneasel - new(Nest185,0,1,1,SWSH) { Species = 859, Ability = A3 }, // Impidimp - new(Nest185,1,2,2,SWSH) { Species = 859, Ability = A3 }, // Impidimp - new(Nest185,1,2,2,SWSH) { Species = 860, Ability = A3 }, // Morgrem - new(Nest185,2,3,3,SWSH) { Species = 215, Ability = A3 }, // Sneasel - new(Nest185,2,3,3,SWSH) { Species = 264, Ability = A3, Form = 1 }, // Linoone-1 - new(Nest185,3,4,4,SWSH) { Species = 861, Ability = A4 }, // Grimmsnarl - new(Nest185,3,4,4,SWSH) { Species = 359, Ability = A4 }, // Absol - new(Nest185,3,4,4,SWSH) { Species = 862, Ability = A4 }, // Obstagoon - new(Nest185,4,4,4,SWSH) { Species = 359, Ability = A4 }, // Absol - new(Nest185,4,4,4,SWSH) { Species = 461, Ability = A4 }, // Weavile - new(Nest186,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest186,0,1,2,SWSH) { Species = 859, Ability = A2 }, // Impidimp - new(Nest186,0,1,2,SWSH) { Species = 359, Ability = A2 }, // Absol - new(Nest186,1,2,3,SWSH) { Species = 215, Ability = A2 }, // Sneasel - new(Nest186,2,3,4,SWSH) { Species = 828, Ability = A2 }, // Thievul - new(Nest186,2,3,4,SWSH) { Species = 510, Ability = A2 }, // Liepard - new(Nest186,3,4,5,SWSH) { Species = 359, Ability = A2 }, // Absol - new(Nest186,3,4,5,SWSH) { Species = 861, Ability = A2 }, // Grimmsnarl - new(Nest186,3,4,5,SWSH) { Species = 461, Ability = A2 }, // Weavile - new(Nest186,4,4,5,SWSH) { Species = 359, Ability = A2 }, // Absol - new(Nest186,4,4,5,SWSH) { Species = 861, Ability = A2, CanGigantamax = true }, // Grimmsnarl - new(Nest187,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest187,0,1,1,SWSH) { Species = 304, Ability = A3 }, // Aron - new(Nest187,0,1,1,SWSH) { Species = 632, Ability = A3 }, // Durant - new(Nest187,1,2,2,SWSH) { Species = 304, Ability = A3 }, // Aron - new(Nest187,1,2,2,SWSH) { Species = 374, Ability = A3 }, // Beldum - new(Nest187,2,3,3,SWSH) { Species = 305, Ability = A3 }, // Lairon - new(Nest187,2,3,3,SWSH) { Species = 375, Ability = A3 }, // Metang - new(Nest187,3,4,4,SWSH) { Species = 823, Ability = A4 }, // Corviknight - new(Nest187,3,4,4,SWSH) { Species = 632, Ability = A4 }, // Durant - new(Nest187,3,4,4,SWSH) { Species = 879, Ability = A4 }, // Copperajah - new(Nest187,4,4,4,SWSH) { Species = 306, Ability = A4 }, // Aggron - new(Nest188,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest188,0,1,2,SWSH) { Species = 304, Ability = A2 }, // Aron - new(Nest188,0,1,2,SWSH) { Species = 052, Ability = A2, Form = 2 }, // Meowth-2 - new(Nest188,1,2,3,SWSH) { Species = 632, Ability = A2 }, // Durant - new(Nest188,1,2,3,SWSH) { Species = 305, Ability = A2 }, // Lairon - new(Nest188,2,3,4,SWSH) { Species = 863, Ability = A2 }, // Perrserker - new(Nest188,3,4,5,SWSH) { Species = 879, Ability = A2 }, // Copperajah - new(Nest188,3,4,5,SWSH) { Species = 306, Ability = A2 }, // Aggron - new(Nest188,3,4,5,SWSH) { Species = 376, Ability = A2 }, // Metagross - new(Nest188,4,4,5,SWSH) { Species = 376, Ability = A2 }, // Metagross - new(Nest188,4,4,5,SWSH) { Species = 879, Ability = A2, CanGigantamax = true }, // Copperajah - new(Nest189,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest189,0,1,1,SWSH) { Species = 173, Ability = A3 }, // Cleffa - new(Nest189,0,1,1,SWSH) { Species = 703, Ability = A3 }, // Carbink - new(Nest189,1,2,2,SWSH) { Species = 856, Ability = A3 }, // Hatenna - new(Nest189,1,2,2,SWSH) { Species = 173, Ability = A3 }, // Cleffa - new(Nest189,2,3,3,SWSH) { Species = 857, Ability = A3 }, // Hattrem - new(Nest189,2,3,3,SWSH) { Species = 035, Ability = A3 }, // Clefairy - new(Nest189,3,4,4,SWSH) { Species = 703, Ability = A4 }, // Carbink - new(Nest189,3,4,4,SWSH) { Species = 036, Ability = A4 }, // Clefable - new(Nest189,4,4,4,SWSH) { Species = 547, Ability = A4 }, // Whimsicott - new(Nest189,4,4,4,SWSH) { Species = 858, Ability = A4 }, // Hatterene - new(Nest190,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest190,0,1,2,SWSH) { Species = 703, Ability = A2 }, // Carbink - new(Nest190,0,1,2,SWSH) { Species = 546, Ability = A2 }, // Cottonee - new(Nest190,1,2,3,SWSH) { Species = 035, Ability = A2 }, // Clefairy - new(Nest190,1,2,3,SWSH) { Species = 703, Ability = A2 }, // Carbink - new(Nest190,2,3,4,SWSH) { Species = 703, Ability = A2 }, // Carbink - new(Nest190,2,3,4,SWSH) { Species = 547, Ability = A2 }, // Whimsicott - new(Nest190,3,4,5,SWSH) { Species = 110, Ability = A2, Form = 1 }, // Weezing-1 - new(Nest190,3,4,5,SWSH) { Species = 858, Ability = A2 }, // Hatterene - new(Nest190,3,4,5,SWSH) { Species = 036, Ability = A2 }, // Clefable - new(Nest190,4,4,5,SWSH) { Species = 110, Ability = A2, Form = 1 }, // Weezing-1 - new(Nest190,4,4,5,SWSH) { Species = 858, Ability = A2, CanGigantamax = true }, // Hatterene - new(Nest191,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest191,0,1,1,SWSH) { Species = 854, Ability = A3 }, // Sinistea - new(Nest191,1,2,2,SWSH) { Species = 854, Ability = A3 }, // Sinistea - new(Nest191,2,3,3,SWSH) { Species = 854, Ability = A3 }, // Sinistea - new(Nest191,3,4,4,SWSH) { Species = 854, Ability = A4 }, // Sinistea - new(Nest191,4,4,4,SWSH) { Species = 854, Ability = A4 }, // Sinistea - new(Nest191,2,4,4,SWSH) { Species = 854, Ability = A4, Form = 1 }, // Sinistea-1 - new(Nest192,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest192,0,0,2,SWSH) { Species = 854, Ability = A2 }, // Sinistea - new(Nest192,1,1,2,SWSH) { Species = 854, Ability = A2 }, // Sinistea - new(Nest192,1,1,3,SWSH) { Species = 854, Ability = A2 }, // Sinistea - new(Nest192,2,2,4,SWSH) { Species = 854, Ability = A2 }, // Sinistea - new(Nest192,3,3,4,SWSH) { Species = 854, Ability = A2 }, // Sinistea - new(Nest192,0,3,5,SWSH) { Species = 854, Ability = A2, Form = 1 }, // Sinistea-1 - new(Nest192,4,4,5,SWSH) { Species = 855, Ability = A2 }, // Polteageist - new(Nest192,4,4,5,SWSH) { Species = 855, Ability = A2, Form = 1 }, // Polteageist-1 - new(Nest192,4,4,5,SWSH) { Species = 869, Ability = A2, CanGigantamax = true }, // Alcremie - new(Nest193,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - //new(Nest193,0,1,1,SWSH) { Species = 133, Ability = A3 }, // Eevee - new(Nest193,2,4,2,SWSH) { Species = 133, Ability = A3 }, // Eevee - new(Nest193,2,4,3,SWSH) { Species = 133, Ability = A3 }, // Eevee - new(Nest193,2,4,4,SWSH) { Species = 136, Ability = A3 }, // Flareon - new(Nest193,2,4,4,SWSH) { Species = 135, Ability = A3 }, // Jolteon - new(Nest193,2,4,4,SWSH) { Species = 134, Ability = A3 }, // Vaporeon - new(Nest193,2,4,4,SWSH) { Species = 196, Ability = A4 }, // Espeon - new(Nest193,2,4,4,SWSH) { Species = 197, Ability = A4 }, // Umbreon - new(Nest193,2,4,4,SWSH) { Species = 470, Ability = A4 }, // Leafeon - new(Nest193,2,4,4,SWSH) { Species = 471, Ability = A4 }, // Glaceon - new(Nest193,2,4,4,SWSH) { Species = 700, Ability = A4 }, // Sylveon - new(Nest194,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - //new(Nest194,0,1,2,SWSH) { Species = 133, Ability = A2 }, // Eevee - new(Nest194,2,4,3,SWSH) { Species = 133, Ability = A2 }, // Eevee - new(Nest194,2,4,4,SWSH) { Species = 133, Ability = A2, Gender = 1 }, // Eevee - new(Nest194,2,4,5,SWSH) { Species = 136, Ability = A2 }, // Flareon - new(Nest194,2,4,5,SWSH) { Species = 135, Ability = A2 }, // Jolteon - new(Nest194,2,4,5,SWSH) { Species = 134, Ability = A2 }, // Vaporeon - new(Nest194,2,4,5,SWSH) { Species = 196, Ability = A2 }, // Espeon - new(Nest194,2,4,5,SWSH) { Species = 197, Ability = A2 }, // Umbreon - new(Nest194,2,4,5,SWSH) { Species = 470, Ability = A2 }, // Leafeon - new(Nest194,2,4,5,SWSH) { Species = 471, Ability = A2 }, // Glaceon - new(Nest194,2,4,5,SWSH) { Species = 700, Ability = A2 }, // Sylveon - new(Nest195,2,4,4,SWSH) { Species = 531, Ability = A4 }, // Audino - new(Nest195,1,2,2,SWSH) { Species = 696, Ability = A3 }, // Tyrunt - new(Nest195,1,2,2,SWSH) { Species = 698, Ability = A3 }, // Amaura - new(Nest195,2,3,3,SWSH) { Species = 348, Ability = A3 }, // Armaldo - new(Nest195,2,3,3,SWSH) { Species = 346, Ability = A3 }, // Cradily - new(Nest195,4,4,4,SWSH) { Species = 142, Ability = A4 }, // Aerodactyl - new(Nest196,2,4,5,SWSH) { Species = 225, Ability = A2 }, // Delibird - new(Nest196,2,2,3,SWSH) { Species = 142, Ability = A2 }, // Aerodactyl - new(Nest196,3,4,5,SWSH) { Species = 142, Ability = A2 }, // Aerodactyl - new(Nest196,4,4,5,SWSH) { Species = 880, Ability = A2 }, // Dracozolt - new(Nest196,4,4,5,SWSH) { Species = 882, Ability = A2 }, // Dracovish - new(Nest196,3,4,5,SWSH) { Species = 881, Ability = A2 }, // Arctozolt - new(Nest196,3,4,5,SWSH) { Species = 883, Ability = A2 }, // Arctovish - }; + internal static readonly EncounterStatic8N[] Nest_SW = + { + new(Nest000,0,1,1,SW) { Species = 559, Ability = A3 }, // Scraggy + new(Nest000,2,3,3,SW) { Species = 106, Ability = A3 }, // Hitmonlee + new(Nest000,2,4,4,SW) { Species = 107, Ability = A3 }, // Hitmonchan + new(Nest000,2,4,4,SW) { Species = 560, Ability = A3 }, // Scrafty + new(Nest000,3,4,4,SW) { Species = 534, Ability = A4 }, // Conkeldurr + new(Nest000,4,4,4,SW) { Species = 237, Ability = A4 }, // Hitmontop + new(Nest001,0,1,1,SW) { Species = 574, Ability = A3 }, // Gothita + new(Nest001,2,3,3,SW) { Species = 678, Ability = A3, Gender = 0 }, // Meowstic + new(Nest001,2,3,3,SW) { Species = 575, Ability = A3 }, // Gothorita + new(Nest001,3,4,4,SW) { Species = 576, Ability = A4 }, // Gothitelle + new(Nest001,4,4,4,SW) { Species = 338, Ability = A4 }, // Solrock + new(Nest002,0,0,1,SW) { Species = 524, Ability = A3 }, // Roggenrola + new(Nest002,0,1,1,SW) { Species = 688, Ability = A3 }, // Binacle + new(Nest002,3,4,4,SW) { Species = 558, Ability = A4 }, // Crustle + new(Nest002,4,4,4,SW) { Species = 526, Ability = A4 }, // Gigalith + //new(Nest006,0,1,1,SW) { Species = 223, Ability = A3 }, // Remoraid + //new(Nest006,0,1,1,SW) { Species = 170, Ability = A3 }, // Chinchou + new(Nest006,2,2,2,SW) { Species = 550, Ability = A3 }, // Basculin + new(Nest007,2,2,2,SW) { Species = 550, Ability = A3 }, // Basculin + new(Nest008,1,1,2,SW) { Species = 090, Ability = A3 }, // Shellder + new(Nest009,1,1,2,SW) { Species = 083, Ability = A3, Form = 1 }, // Farfetch’d-1 + new(Nest009,1,2,2,SW) { Species = 539, Ability = A3 }, // Sawk + new(Nest009,3,4,4,SW) { Species = 865, Ability = A4 }, // Sirfetch’d + new(Nest011,4,4,4,SW) { Species = 303, Ability = A4 }, // Mawile + new(Nest012,0,1,1,SW) { Species = 177, Ability = A3 }, // Natu + new(Nest012,0,1,1,SW) { Species = 856, Ability = A3 }, // Hatenna + new(Nest012,1,1,2,SW) { Species = 825, Ability = A3 }, // Dottler + new(Nest012,1,3,2,SW) { Species = 857, Ability = A3 }, // Hattrem + new(Nest012,2,4,4,SW) { Species = 876, Ability = A3, Gender = 0 }, // Indeedee + new(Nest012,3,4,4,SW) { Species = 561, Ability = A4 }, // Sigilyph + new(Nest012,4,4,4,SW) { Species = 826, Ability = A4 }, // Orbeetle + new(Nest013,2,4,4,SW) { Species = 876, Ability = A3, Gender = 0 }, // Indeedee + new(Nest014,0,0,1,SW) { Species = 524, Ability = A3 }, // Roggenrola + new(Nest014,0,1,1,SW) { Species = 557, Ability = A3 }, // Dwebble + new(Nest014,2,4,4,SW) { Species = 095, Ability = A3 }, // Onix + new(Nest014,3,4,4,SW) { Species = 839, Ability = A4 }, // Coalossal + new(Nest014,4,4,4,SW) { Species = 526, Ability = A4 }, // Gigalith + new(Nest016,0,1,1,SW) { Species = 220, Ability = A3 }, // Swinub + new(Nest016,1,1,1,SW) { Species = 328, Ability = A3 }, // Trapinch + new(Nest016,2,3,3,SW) { Species = 329, Ability = A3 }, // Vibrava + new(Nest016,2,4,4,SW) { Species = 618, Ability = A3, Form = 1 }, // Stunfisk-1 + new(Nest016,3,4,4,SW) { Species = 450, Ability = A4 }, // Hippowdon + new(Nest016,4,4,4,SW) { Species = 330, Ability = A4 }, // Flygon + new(Nest017,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix + new(Nest017,1,1,1,SW) { Species = 554, Ability = A3, Form = 1 }, // Darumaka-1 + new(Nest017,1,2,2,SW) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle + new(Nest017,2,3,3,SW) { Species = 608, Ability = A3 }, // Lampent + new(Nest017,2,4,4,SW) { Species = 038, Ability = A3 }, // Ninetales + new(Nest017,3,4,4,SW) { Species = 851, Ability = A4 }, // Centiskorch + new(Nest017,4,4,4,SW) { Species = 631, Ability = A4 }, // Heatmor + new(Nest017,4,4,4,SW) { Species = 555, Ability = A4, Form = 2 }, // Darmanitan-2 + new(Nest018,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix + new(Nest018,0,1,1,SW) { Species = 037, Ability = A3 }, // Vulpix + new(Nest018,1,2,2,SW) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle + new(Nest018,2,3,3,SW) { Species = 608, Ability = A3 }, // Lampent + new(Nest018,2,4,4,SW) { Species = 038, Ability = A3 }, // Ninetales + new(Nest018,2,4,4,SW) { Species = 324, Ability = A3 }, // Torkoal + new(Nest018,3,4,4,SW) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle + new(Nest018,4,4,4,SW) { Species = 776, Ability = A4 }, // Turtonator + new(Nest019,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix + new(Nest019,1,1,1,SW) { Species = 554, Ability = A3, Form = 1 }, // Darumaka-1 + new(Nest019,1,2,2,SW) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle + new(Nest019,2,3,3,SW) { Species = 838, Ability = A3 }, // Carkol + new(Nest019,2,4,4,SW) { Species = 038, Ability = A3 }, // Ninetales + new(Nest019,4,4,4,SW) { Species = 555, Ability = A4, Form = 2 }, // Darmanitan-2 + new(Nest022,0,1,1,SW) { Species = 554, Ability = A3, Form = 1 }, // Darumaka-1 + new(Nest022,4,4,4,SW) { Species = 555, Ability = A4, Form = 2 }, // Darmanitan-2 + new(Nest025,0,0,1,SW) { Species = 273, Ability = A3 }, // Seedot + new(Nest025,1,1,2,SW) { Species = 274, Ability = A3 }, // Nuzleaf + new(Nest025,2,4,4,SW) { Species = 275, Ability = A3 }, // Shiftry + new(Nest026,4,4,4,SW) { Species = 841, Ability = A4 }, // Flapple + new(Nest028,0,1,1,SW) { Species = 747, Ability = A3 }, // Mareanie + new(Nest028,1,1,2,SW) { Species = 043, Ability = A3 }, // Oddish + new(Nest028,3,4,4,SW) { Species = 748, Ability = A4 }, // Toxapex + new(Nest028,4,4,4,SW) { Species = 435, Ability = A4 }, // Skuntank + new(Nest030,0,1,1,SW) { Species = 627, Ability = A3 }, // Rufflet + new(Nest030,4,4,4,SW) { Species = 628, Ability = A4 }, // Braviary + new(Nest032,0,1,1,SW) { Species = 684, Ability = A3 }, // Swirlix + new(Nest032,4,4,4,SW) { Species = 685, Ability = A4 }, // Slurpuff + new(Nest033,4,4,4,SW) { Species = 303, Ability = A4 }, // Mawile + new(Nest034,4,4,4,SW) { Species = 275, Ability = A4 }, // Shiftry + new(Nest035,1,1,2,SW) { Species = 633, Ability = A3 }, // Deino + new(Nest035,3,4,4,SW) { Species = 634, Ability = A4 }, // Zweilous + new(Nest035,4,4,4,SW) { Species = 635, Ability = A4 }, // Hydreigon + new(Nest036,0,0,1,SW) { Species = 328, Ability = A3 }, // Trapinch + new(Nest036,0,1,1,SW) { Species = 610, Ability = A3 }, // Axew + new(Nest036,1,1,2,SW) { Species = 782, Ability = A3 }, // Jangmo-o + new(Nest036,2,3,3,SW) { Species = 783, Ability = A3 }, // Hakamo-o + new(Nest036,2,4,4,SW) { Species = 611, Ability = A3 }, // Fraxure + new(Nest036,2,4,4,SW) { Species = 612, Ability = A3 }, // Haxorus + new(Nest036,3,4,4,SW) { Species = 330, Ability = A4 }, // Flygon + new(Nest036,4,4,4,SW) { Species = 776, Ability = A4 }, // Turtonator + new(Nest036,4,4,4,SW) { Species = 784, Ability = A4 }, // Kommo-o + //new(Nest037,0,1,1,SW) { Species = 782, Ability = A3 }, // Jangmo-o + new(Nest037,2,4,4,SW) { Species = 783, Ability = A3 }, // Hakamo-o + new(Nest037,3,4,4,SW) { Species = 784, Ability = A4 }, // Kommo-o + new(Nest037,4,4,4,SW) { Species = 841, Ability = A4 }, // Flapple + new(Nest039,1,1,2,SW) { Species = 876, Ability = A3, Gender = 0 }, // Indeedee + new(Nest039,4,4,4,SW) { Species = 628, Ability = A4 }, // Braviary + //new(Nest040,0,1,1,SW) { Species = 747, Ability = A3 }, // Mareanie + //new(Nest040,1,1,2,SW) { Species = 536, Ability = A3 }, // Palpitoad + new(Nest040,2,3,3,SW) { Species = 091, Ability = A3 }, // Cloyster + new(Nest040,2,4,4,SW) { Species = 746, Ability = A3 }, // Wishiwashi + new(Nest040,3,4,4,SW) { Species = 537, Ability = A4 }, // Seismitoad + new(Nest040,4,4,4,SW) { Species = 748, Ability = A4 }, // Toxapex + new(Nest042,1,3,2,SW) { Species = 710, Ability = A3 }, // Pumpkaboo + new(Nest042,4,4,4,SW) { Species = 867, Ability = A3 }, // Runerigus + new(Nest042,3,4,4,SW) { Species = 855, Ability = A4 }, // Polteageist + new(Nest042,3,4,4,SW) { Species = 711, Ability = A4 }, // Gourgeist + new(Nest043,2,2,2,SW) { Species = 550, Ability = A3 }, // Basculin + new(Nest044,1,2,2,SW) { Species = 632, Ability = A3 }, // Durant + new(Nest044,2,3,3,SW) { Species = 600, Ability = A3 }, // Klang + new(Nest045,0,1,1,SW) { Species = 588, Ability = A3 }, // Karrablast + new(Nest045,1,2,2,SW) { Species = 616, Ability = A3 }, // Shelmet + new(Nest045,4,4,4,SW) { Species = 589, Ability = A4 }, // Escavalier + new(Nest046,1,3,3,SW) { Species = 710, Ability = A3 }, // Pumpkaboo + new(Nest046,2,4,4,SW) { Species = 711, Ability = A3 }, // Gourgeist + new(Nest046,3,4,4,SW) { Species = 711, Ability = A4 }, // Gourgeist + new(Nest047,0,1,1,SW) { Species = 559, Ability = A3 }, // Scraggy + new(Nest047,2,4,4,SW) { Species = 560, Ability = A3 }, // Scrafty + new(Nest047,3,4,4,SW) { Species = 766, Ability = A4 }, // Passimian + new(Nest050,0,1,1,SW) { Species = 688, Ability = A3 }, // Binacle + new(Nest050,1,2,2,SW) { Species = 185, Ability = A3 }, // Sudowoodo + new(Nest050,2,3,3,SW) { Species = 689, Ability = A3 }, // Barbaracle + new(Nest050,4,4,4,SW) { Species = 874, Ability = A4 }, // Stonjourner + new(Nest052,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix + new(Nest052,1,2,2,SW) { Species = 038, Ability = A3 }, // Ninetales + new(Nest052,3,4,4,SW) { Species = 038, Ability = A4 }, // Ninetales + new(Nest053,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix + new(Nest053,1,2,2,SW) { Species = 608, Ability = A3 }, // Lampent + new(Nest053,2,3,3,SW) { Species = 631, Ability = A3 }, // Heatmor + new(Nest053,3,4,4,SW) { Species = 038, Ability = A4 }, // Ninetales + new(Nest053,4,4,4,SW) { Species = 776, Ability = A4 }, // Turtonator + new(Nest054,0,0,1,SW) { Species = 554, Ability = A3, Form = 1 }, // Darumaka-1 + new(Nest054,4,4,4,SW) { Species = 555, Ability = A4, Form = 2 }, // Darmanitan-2 + new(Nest057,0,0,1,SW) { Species = 273, Ability = A3 }, // Seedot + new(Nest057,1,2,2,SW) { Species = 274, Ability = A3 }, // Nuzleaf + new(Nest057,2,4,4,SW) { Species = 275, Ability = A3 }, // Shiftry + new(Nest057,4,4,4,SW) { Species = 841, Ability = A4 }, // Flapple + new(Nest058,0,0,1,SW) { Species = 273, Ability = A3 }, // Seedot + new(Nest058,1,2,2,SW) { Species = 274, Ability = A3 }, // Nuzleaf + new(Nest058,4,4,4,SW) { Species = 275, Ability = A4 }, // Shiftry + new(Nest059,1,2,2,SW) { Species = 747, Ability = A3 }, // Mareanie + new(Nest059,4,4,4,SW) { Species = 748, Ability = A4 }, // Toxapex + new(Nest061,2,4,4,SW) { Species = 303, Ability = A3 }, // Mawile + new(Nest062,0,1,1,SW) { Species = 559, Ability = A3 }, // Scraggy + new(Nest062,3,4,4,SW) { Species = 560, Ability = A4 }, // Scrafty + new(Nest062,4,4,4,SW) { Species = 635, Ability = A4 }, // Hydreigon + new(Nest063,0,0,1,SW) { Species = 328, Ability = A3 }, // Trapinch + new(Nest063,0,1,1,SW) { Species = 610, Ability = A3 }, // Axew + new(Nest063,0,1,1,SW) { Species = 782, Ability = A3 }, // Jangmo-o + new(Nest063,1,2,2,SW) { Species = 611, Ability = A3 }, // Fraxure + new(Nest063,2,4,4,SW) { Species = 783, Ability = A3 }, // Hakamo-o + new(Nest063,2,4,4,SW) { Species = 776, Ability = A3 }, // Turtonator + new(Nest063,3,4,4,SW) { Species = 784, Ability = A4 }, // Kommo-o + new(Nest063,4,4,4,SW) { Species = 612, Ability = A4 }, // Haxorus + new(Nest064,3,4,4,SW) { Species = 628, Ability = A4 }, // Braviary + new(Nest064,3,4,4,SW) { Species = 876, Ability = A4, Gender = 0 }, // Indeedee + new(Nest066,2,2,2,SW) { Species = 550, Ability = A3 }, // Basculin + new(Nest070,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix + new(Nest070,0,1,1,SW) { Species = 037, Ability = A3 }, // Vulpix + new(Nest070,1,2,2,SW) { Species = 608, Ability = A3 }, // Lampent + new(Nest070,2,3,3,SW) { Species = 631, Ability = A3 }, // Heatmor + //new(Nest073,0,0,1,SW) { Species = 684, Ability = A3 }, // Swirlix + new(Nest073,2,4,4,SW) { Species = 685, Ability = A3 }, // Slurpuff + new(Nest075,2,4,4,SW) { Species = 550, Ability = A3 }, // Basculin + //new(Nest076,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix + new(Nest076,2,2,2,SW) { Species = 038, Ability = A3 }, // Ninetales + new(Nest076,3,4,4,SW) { Species = 038, Ability = A4 }, // Ninetales + new(Nest077,1,2,2,SW) { Species = 550, Ability = A3 }, // Basculin + new(Nest078,0,0,1,SW) { Species = 273, Ability = A3 }, // Seedot + new(Nest078,1,2,2,SW) { Species = 274, Ability = A3 }, // Nuzleaf + new(Nest078,2,4,4,SW) { Species = 275, Ability = A3 }, // Shiftry + new(Nest078,4,4,4,SW) { Species = 841, Ability = A4, CanGigantamax = true }, // Flapple + new(Nest079,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix + new(Nest079,3,4,4,SW) { Species = 038, Ability = A4 }, // Ninetales + new(Nest080,0,0,1,SW) { Species = 447, Ability = A3 }, // Riolu + new(Nest080,0,0,1,SW) { Species = 066, Ability = A3 }, // Machop + new(Nest080,0,1,1,SW) { Species = 759, Ability = A3 }, // Stufful + new(Nest080,0,1,1,SW) { Species = 083, Ability = A3, Form = 1 }, // Farfetch’d-1 + new(Nest080,1,2,2,SW) { Species = 760, Ability = A3 }, // Bewear + new(Nest080,1,3,3,SW) { Species = 067, Ability = A3 }, // Machoke + new(Nest080,2,3,3,SW) { Species = 870, Ability = A3 }, // Falinks + new(Nest080,2,4,4,SW) { Species = 701, Ability = A3 }, // Hawlucha + new(Nest080,3,4,4,SW) { Species = 448, Ability = A4 }, // Lucario + new(Nest080,3,4,4,SW) { Species = 475, Ability = A4 }, // Gallade + new(Nest080,4,4,4,SW) { Species = 865, Ability = A4 }, // Sirfetch’d + new(Nest080,4,4,4,SW) { Species = 068, Ability = A4, CanGigantamax = true }, // Machamp + new(Nest081,0,0,1,SW) { Species = 755, Ability = A3 }, // Morelull + new(Nest081,2,4,4,SW) { Species = 303, Ability = A3 }, // Mawile + new(Nest082,0,0,1,SW) { Species = 557, Ability = A3 }, // Dwebble + new(Nest082,0,0,1,SW) { Species = 438, Ability = A3 }, // Bonsly + new(Nest082,0,1,1,SW) { Species = 837, Ability = A3 }, // Rolycoly + new(Nest082,0,1,1,SW) { Species = 688, Ability = A3 }, // Binacle + new(Nest082,1,2,2,SW) { Species = 838, Ability = A3 }, // Carkol + new(Nest082,1,2,2,SW) { Species = 185, Ability = A3 }, // Sudowoodo + new(Nest082,2,3,3,SW) { Species = 689, Ability = A3 }, // Barbaracle + new(Nest082,2,4,4,SW) { Species = 095, Ability = A3 }, // Onix + new(Nest082,3,4,4,SW) { Species = 558, Ability = A4 }, // Crustle + new(Nest082,3,4,4,SW) { Species = 208, Ability = A4 }, // Steelix + new(Nest082,4,4,4,SW) { Species = 874, Ability = A4 }, // Stonjourner + new(Nest082,4,4,4,SW) { Species = 839, Ability = A4, CanGigantamax = true }, // Coalossal + new(Nest083,1,2,2,SW) { Species = 632, Ability = A3 }, // Durant + new(Nest083,2,3,3,SW) { Species = 600, Ability = A3 }, // Klang + new(Nest085,1,2,2,SW) { Species = 747, Ability = A3 }, // Mareanie + new(Nest085,4,4,4,SW) { Species = 748, Ability = A4 }, // Toxapex + new(Nest086,0,0,1,SW) { Species = 684, Ability = A3 }, // Swirlix + new(Nest086,2,3,3,SW) { Species = 685, Ability = A3 }, // Slurpuff + new(Nest087,0,1,1,SW) { Species = 559, Ability = A3 }, // Scraggy + new(Nest087,3,4,4,SW) { Species = 560, Ability = A4 }, // Scrafty + new(Nest087,4,4,4,SW) { Species = 635, Ability = A4 }, // Hydreigon + new(Nest089,0,1,1,SW) { Species = 588, Ability = A3 }, // Karrablast + new(Nest089,1,2,2,SW) { Species = 616, Ability = A3 }, // Shelmet + new(Nest089,4,4,4,SW) { Species = 589, Ability = A4 }, // Escavalier + new(Nest090,1,2,2,SW) { Species = 550, Ability = A3 }, // Basculin + new(Nest091,0,1,1,SW) { Species = 588, Ability = A3 }, // Karrablast + new(Nest091,1,2,2,SW) { Species = 616, Ability = A3 }, // Shelmet + new(Nest091,4,4,4,SW) { Species = 589, Ability = A4 }, // Escavalier + new(Nest106,1,2,2,SW) { Species = 627, Ability = A3 }, // Rufflet + new(Nest106,3,4,4,SW) { Species = 628, Ability = A4 }, // Braviary + new(Nest106,4,4,4,SW) { Species = 628, Ability = A4 }, // Braviary + new(Nest107,1,2,2,SW) { Species = 627, Ability = A2 }, // Rufflet + new(Nest107,4,4,4,SW) { Species = 628, Ability = A2 }, // Braviary + new(Nest108,0,1,1,SW) { Species = 127, Ability = A3 }, // Pinsir + new(Nest108,1,2,2,SW) { Species = 127, Ability = A3 }, // Pinsir + new(Nest108,2,3,3,SW) { Species = 127, Ability = A3 }, // Pinsir + new(Nest108,3,4,4,SW) { Species = 127, Ability = A4 }, // Pinsir + new(Nest109,0,1,1,SW) { Species = 127, Ability = A2 }, // Pinsir + new(Nest109,4,4,4,SW) { Species = 127, Ability = A2 }, // Pinsir + new(Nest113,4,4,4,SW) { Species = 038, Ability = A2 }, // Ninetales + new(Nest114,4,4,4,SW) { Species = 745, Ability = A4 }, // Lycanroc + new(Nest115,3,4,4,SW) { Species = 745, Ability = A2 }, // Lycanroc + new(Nest116,2,3,3,SW) { Species = 064, Ability = A3 }, // Kadabra + new(Nest117,2,3,3,SW) { Species = 064, Ability = A2 }, // Kadabra + new(Nest117,3,4,4,SW) { Species = 876, Ability = A2, Gender = 0 }, // Indeedee + new(Nest117,4,4,4,SW) { Species = 678, Ability = A2, Gender = 0 }, // Meowstic + new(Nest122,1,2,2,SW) { Species = 559, Ability = A3 }, // Scraggy + new(Nest122,2,3,3,SW) { Species = 766, Ability = A3 }, // Passimian + new(Nest122,2,3,3,SW) { Species = 560, Ability = A3 }, // Scrafty + new(Nest122,3,4,4,SW) { Species = 560, Ability = A4 }, // Scrafty + new(Nest123,0,1,1,SW) { Species = 559, Ability = A2 }, // Scraggy + new(Nest123,1,2,2,SW) { Species = 539, Ability = A2 }, // Sawk + new(Nest123,2,3,3,SW) { Species = 766, Ability = A2 }, // Passimian + new(Nest123,3,4,4,SW) { Species = 539, Ability = A2 }, // Sawk + new(Nest123,3,4,4,SW) { Species = 560, Ability = A2 }, // Scrafty + new(Nest123,4,4,4,SW) { Species = 865, Ability = A2 }, // Sirfetch’d + new(Nest127,4,4,4,SW) { Species = 770, Ability = A2 }, // Palossand + //new(Nest130,0,1,1,SW) { Species = 782, Ability = A3 }, // Jangmo-o + new(Nest130,2,3,3,SW) { Species = 783, Ability = A3 }, // Hakamo-o + new(Nest130,3,4,4,SW) { Species = 841, Ability = A4 }, // Flapple + new(Nest130,4,4,4,SW) { Species = 784, Ability = A4 }, // Kommo-o + new(Nest131,2,2,2,SW) { Species = 776, Ability = A2 }, // Turtonator + new(Nest131,2,2,2,SW) { Species = 782, Ability = A2 }, // Jangmo-o + new(Nest131,2,3,3,SW) { Species = 783, Ability = A2 }, // Hakamo-o + new(Nest131,3,4,4,SW) { Species = 776, Ability = A2 }, // Turtonator + new(Nest131,4,4,4,SW) { Species = 784, Ability = A2 }, // Kommo-o + new(Nest135,4,4,4,SW) { Species = 550, Ability = A2 }, // Basculin + //new(Nest138,0,1,1,SW) { Species = 692, Ability = A3 }, // Clauncher + new(Nest138,2,2,2,SW) { Species = 692, Ability = A3 }, // Clauncher + new(Nest138,4,4,4,SW) { Species = 693, Ability = A4 }, // Clawitzer + //new(Nest139,0,1,1,SW) { Species = 692, Ability = A2 }, // Clauncher + new(Nest139,3,4,4,SW) { Species = 693, Ability = A2 }, // Clawitzer + new(Nest139,4,4,4,SW) { Species = 693, Ability = A2 }, // Clawitzer + new(Nest147,1,2,2,SW) { Species = 550, Ability = A2 }, // Basculin + new(Nest147,3,4,4,SW) { Species = 550, Ability = A2 }, // Basculin + new(Nest148,1,2,2,SW) { Species = 127, Ability = A2 }, // Pinsir + new(Nest150,2,3,3,SW) { Species = 841, Ability = A2 }, // Flapple + new(Nest150,3,4,4,SW) { Species = 841, Ability = A2 }, // Flapple + new(Nest150,4,4,4,SW) { Species = 841, Ability = A2, CanGigantamax = true }, // Flapple + new(Nest155,4,4,4,SW) { Species = 745, Ability = A4 }, // Lycanroc + new(Nest160,3,4,5,SW) { Species = 038, Ability = A2 }, // Ninetales + new(Nest161,0,1,1,SW) { Species = 138, Ability = A3 }, // Omanyte + new(Nest161,1,2,2,SW) { Species = 138, Ability = A3 }, // Omanyte + new(Nest161,2,3,3,SW) { Species = 550, Ability = A3 }, // Basculin + new(Nest161,3,4,4,SW) { Species = 139, Ability = A4 }, // Omastar + new(Nest161,3,4,4,SW) { Species = 550, Ability = A4 }, // Basculin + new(Nest162,0,1,2,SW) { Species = 138, Ability = A2 }, // Omanyte + new(Nest162,2,3,4,SW) { Species = 550, Ability = A2 }, // Basculin + new(Nest162,3,4,5,SW) { Species = 139, Ability = A2 }, // Omastar + new(Nest164,3,4,5,SW) { Species = 849, Ability = A2 }, // Toxtricity + new(Nest164,4,4,5,SW) { Species = 849, Ability = A2, CanGigantamax = true }, // Toxtricity + new(Nest165,0,1,1,SW) { Species = 347, Ability = A3 }, // Anorith + new(Nest165,2,3,3,SW) { Species = 347, Ability = A3 }, // Anorith + new(Nest165,3,4,4,SW) { Species = 348, Ability = A4 }, // Armaldo + new(Nest165,4,4,4,SW) { Species = 346, Ability = A4 }, // Cradily + new(Nest166,4,4,5,SW) { Species = 348, Ability = A2 }, // Armaldo + new(Nest167,0,1,1,SW) { Species = 554, Ability = A3, Form = 1 }, // Darumaka-1 + new(Nest167,3,4,4,SW) { Species = 555, Ability = A4, Form = 2 }, // Darmanitan-2 + new(Nest168,0,1,2,SW) { Species = 554, Ability = A2, Form = 1 }, // Darumaka-1 + new(Nest168,3,4,5,SW) { Species = 555, Ability = A2, Form = 2 }, // Darmanitan-2 + //new(Nest169,0,1,1,SW) { Species = 613, Ability = A3 }, // Cubchoo + new(Nest169,2,2,2,SW) { Species = 712, Ability = A3 }, // Bergmite + new(Nest170,4,4,5,SW) { Species = 131, Ability = A2 }, // Lapras + new(Nest172,1,2,3,SW) { Species = 083, Ability = A2, Form = 1 }, // Farfetch’d-1 + new(Nest172,3,4,5,SW) { Species = 865, Ability = A2 }, // Sirfetch’d + new(Nest172,3,4,5,SW) { Species = 106, Ability = A2 }, // Hitmonlee + new(Nest172,4,4,5,SW) { Species = 068, Ability = A2, CanGigantamax = true }, // Machamp + new(Nest173,0,1,1,SW) { Species = 029, Ability = A3 }, // Nidoran♀ + new(Nest173,3,4,4,SW) { Species = 031, Ability = A4 }, // Nidoqueen + new(Nest174,1,2,3,SW) { Species = 030, Ability = A2 }, // Nidorina + new(Nest177,0,1,1,SW) { Species = 343, Ability = A3 }, // Baltoy + new(Nest177,2,3,3,SW) { Species = 575, Ability = A3 }, // Gothorita + new(Nest177,3,4,4,SW) { Species = 876, Ability = A4, Gender = 0 }, // Indeedee + new(Nest177,3,4,4,SW) { Species = 576, Ability = A4 }, // Gothitelle + new(Nest177,3,4,4,SW) { Species = 344, Ability = A4 }, // Claydol + new(Nest178,0,1,2,SW) { Species = 876, Ability = A2, Gender = 0 }, // Indeedee + new(Nest178,0,1,2,SW) { Species = 574, Ability = A2 }, // Gothita + new(Nest178,2,3,4,SW) { Species = 876, Ability = A2, Gender = 0 }, // Indeedee + new(Nest178,3,4,5,SW) { Species = 876, Ability = A2, Gender = 0 }, // Indeedee + new(Nest178,3,4,5,SW) { Species = 576, Ability = A2 }, // Gothitelle + new(Nest178,4,4,5,SW) { Species = 576, Ability = A2 }, // Gothitelle + new(Nest179,0,1,1,SW) { Species = 874, Ability = A3 }, // Stonjourner + new(Nest179,1,2,2,SW) { Species = 874, Ability = A3 }, // Stonjourner + new(Nest179,2,3,3,SW) { Species = 874, Ability = A3 }, // Stonjourner + new(Nest179,3,4,4,SW) { Species = 303, Ability = A4 }, // Mawile + new(Nest180,0,1,2,SW) { Species = 303, Ability = A2 }, // Mawile + new(Nest180,2,3,4,SW) { Species = 303, Ability = A2 }, // Mawile + new(Nest180,3,4,5,SW) { Species = 303, Ability = A2 }, // Mawile + new(Nest180,4,4,5,SW) { Species = 839, Ability = A2, CanGigantamax = true }, // Coalossal + new(Nest181,3,4,4,SW) { Species = 303, Ability = A4 }, // Mawile + new(Nest182,1,2,3,SW) { Species = 093, Ability = A2 }, // Haunter + new(Nest182,2,3,4,SW) { Species = 303, Ability = A2 }, // Mawile + new(Nest182,3,4,5,SW) { Species = 709, Ability = A2 }, // Trevenant + new(Nest182,3,4,5,SW) { Species = 303, Ability = A2 }, // Mawile + new(Nest182,4,4,5,SW) { Species = 094, Ability = A2 }, // Gengar + new(Nest183,0,1,1,SW) { Species = 371, Ability = A3 }, // Bagon + new(Nest183,1,2,2,SW) { Species = 371, Ability = A3 }, // Bagon + new(Nest183,2,3,3,SW) { Species = 372, Ability = A3 }, // Shelgon + new(Nest183,3,4,4,SW) { Species = 372, Ability = A4 }, // Shelgon + new(Nest183,4,4,4,SW) { Species = 373, Ability = A4 }, // Salamence + new(Nest184,0,1,2,SW) { Species = 371, Ability = A2 }, // Bagon + new(Nest184,1,2,3,SW) { Species = 776, Ability = A2 }, // Turtonator + new(Nest184,2,3,4,SW) { Species = 372, Ability = A2 }, // Shelgon + new(Nest184,3,4,5,SW) { Species = 373, Ability = A2 }, // Salamence + new(Nest184,4,4,5,SW) { Species = 373, Ability = A2 }, // Salamence + new(Nest192,3,4,5,SW) { Species = 854, Ability = A2 }, // Sinistea + new(Nest195,0,1,1,SW) { Species = 138, Ability = A3 }, // Omanyte + new(Nest195,0,1,1,SW) { Species = 347, Ability = A3 }, // Anorith + new(Nest195,3,4,4,SW) { Species = 139, Ability = A4 }, // Omastar + new(Nest195,3,4,4,SW) { Species = 348, Ability = A4 }, // Armaldo + new(Nest195,3,4,4,SW) { Species = 697, Ability = A4 }, // Tyrantrum + new(Nest195,4,4,4,SW) { Species = 699, Ability = A4 }, // Aurorus + new(Nest196,0,1,2,SW) { Species = 138, Ability = A2 }, // Omanyte + new(Nest196,2,2,2,SW) { Species = 139, Ability = A2 }, // Omastar + new(Nest196,2,2,3,SW) { Species = 881, Ability = A2 }, // Arctozolt + new(Nest196,2,3,4,SW) { Species = 880, Ability = A2 }, // Dracozolt + new(Nest196,3,3,4,SW) { Species = 882, Ability = A2 }, // Dracovish + }; - internal static readonly EncounterStatic8N[] Nest_SW = - { - new(Nest000,0,1,1,SW) { Species = 559, Ability = A3 }, // Scraggy - new(Nest000,2,3,3,SW) { Species = 106, Ability = A3 }, // Hitmonlee - new(Nest000,2,4,4,SW) { Species = 107, Ability = A3 }, // Hitmonchan - new(Nest000,2,4,4,SW) { Species = 560, Ability = A3 }, // Scrafty - new(Nest000,3,4,4,SW) { Species = 534, Ability = A4 }, // Conkeldurr - new(Nest000,4,4,4,SW) { Species = 237, Ability = A4 }, // Hitmontop - new(Nest001,0,1,1,SW) { Species = 574, Ability = A3 }, // Gothita - new(Nest001,2,3,3,SW) { Species = 678, Ability = A3, Gender = 0 }, // Meowstic - new(Nest001,2,3,3,SW) { Species = 575, Ability = A3 }, // Gothorita - new(Nest001,3,4,4,SW) { Species = 576, Ability = A4 }, // Gothitelle - new(Nest001,4,4,4,SW) { Species = 338, Ability = A4 }, // Solrock - new(Nest002,0,0,1,SW) { Species = 524, Ability = A3 }, // Roggenrola - new(Nest002,0,1,1,SW) { Species = 688, Ability = A3 }, // Binacle - new(Nest002,3,4,4,SW) { Species = 558, Ability = A4 }, // Crustle - new(Nest002,4,4,4,SW) { Species = 526, Ability = A4 }, // Gigalith - //new(Nest006,0,1,1,SW) { Species = 223, Ability = A3 }, // Remoraid - //new(Nest006,0,1,1,SW) { Species = 170, Ability = A3 }, // Chinchou - new(Nest006,2,2,2,SW) { Species = 550, Ability = A3 }, // Basculin - new(Nest007,2,2,2,SW) { Species = 550, Ability = A3 }, // Basculin - new(Nest008,1,1,2,SW) { Species = 090, Ability = A3 }, // Shellder - new(Nest009,1,1,2,SW) { Species = 083, Ability = A3, Form = 1 }, // Farfetch’d-1 - new(Nest009,1,2,2,SW) { Species = 539, Ability = A3 }, // Sawk - new(Nest009,3,4,4,SW) { Species = 865, Ability = A4 }, // Sirfetch’d - new(Nest011,4,4,4,SW) { Species = 303, Ability = A4 }, // Mawile - new(Nest012,0,1,1,SW) { Species = 177, Ability = A3 }, // Natu - new(Nest012,0,1,1,SW) { Species = 856, Ability = A3 }, // Hatenna - new(Nest012,1,1,2,SW) { Species = 825, Ability = A3 }, // Dottler - new(Nest012,1,3,2,SW) { Species = 857, Ability = A3 }, // Hattrem - new(Nest012,2,4,4,SW) { Species = 876, Ability = A3, Gender = 0 }, // Indeedee - new(Nest012,3,4,4,SW) { Species = 561, Ability = A4 }, // Sigilyph - new(Nest012,4,4,4,SW) { Species = 826, Ability = A4 }, // Orbeetle - new(Nest013,2,4,4,SW) { Species = 876, Ability = A3, Gender = 0 }, // Indeedee - new(Nest014,0,0,1,SW) { Species = 524, Ability = A3 }, // Roggenrola - new(Nest014,0,1,1,SW) { Species = 557, Ability = A3 }, // Dwebble - new(Nest014,2,4,4,SW) { Species = 095, Ability = A3 }, // Onix - new(Nest014,3,4,4,SW) { Species = 839, Ability = A4 }, // Coalossal - new(Nest014,4,4,4,SW) { Species = 526, Ability = A4 }, // Gigalith - new(Nest016,0,1,1,SW) { Species = 220, Ability = A3 }, // Swinub - new(Nest016,1,1,1,SW) { Species = 328, Ability = A3 }, // Trapinch - new(Nest016,2,3,3,SW) { Species = 329, Ability = A3 }, // Vibrava - new(Nest016,2,4,4,SW) { Species = 618, Ability = A3, Form = 1 }, // Stunfisk-1 - new(Nest016,3,4,4,SW) { Species = 450, Ability = A4 }, // Hippowdon - new(Nest016,4,4,4,SW) { Species = 330, Ability = A4 }, // Flygon - new(Nest017,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix - new(Nest017,1,1,1,SW) { Species = 554, Ability = A3, Form = 1 }, // Darumaka-1 - new(Nest017,1,2,2,SW) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle - new(Nest017,2,3,3,SW) { Species = 608, Ability = A3 }, // Lampent - new(Nest017,2,4,4,SW) { Species = 038, Ability = A3 }, // Ninetales - new(Nest017,3,4,4,SW) { Species = 851, Ability = A4 }, // Centiskorch - new(Nest017,4,4,4,SW) { Species = 631, Ability = A4 }, // Heatmor - new(Nest017,4,4,4,SW) { Species = 555, Ability = A4, Form = 2 }, // Darmanitan-2 - new(Nest018,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix - new(Nest018,0,1,1,SW) { Species = 037, Ability = A3 }, // Vulpix - new(Nest018,1,2,2,SW) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle - new(Nest018,2,3,3,SW) { Species = 608, Ability = A3 }, // Lampent - new(Nest018,2,4,4,SW) { Species = 038, Ability = A3 }, // Ninetales - new(Nest018,2,4,4,SW) { Species = 324, Ability = A3 }, // Torkoal - new(Nest018,3,4,4,SW) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle - new(Nest018,4,4,4,SW) { Species = 776, Ability = A4 }, // Turtonator - new(Nest019,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix - new(Nest019,1,1,1,SW) { Species = 554, Ability = A3, Form = 1 }, // Darumaka-1 - new(Nest019,1,2,2,SW) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle - new(Nest019,2,3,3,SW) { Species = 838, Ability = A3 }, // Carkol - new(Nest019,2,4,4,SW) { Species = 038, Ability = A3 }, // Ninetales - new(Nest019,4,4,4,SW) { Species = 555, Ability = A4, Form = 2 }, // Darmanitan-2 - new(Nest022,0,1,1,SW) { Species = 554, Ability = A3, Form = 1 }, // Darumaka-1 - new(Nest022,4,4,4,SW) { Species = 555, Ability = A4, Form = 2 }, // Darmanitan-2 - new(Nest025,0,0,1,SW) { Species = 273, Ability = A3 }, // Seedot - new(Nest025,1,1,2,SW) { Species = 274, Ability = A3 }, // Nuzleaf - new(Nest025,2,4,4,SW) { Species = 275, Ability = A3 }, // Shiftry - new(Nest026,4,4,4,SW) { Species = 841, Ability = A4 }, // Flapple - new(Nest028,0,1,1,SW) { Species = 747, Ability = A3 }, // Mareanie - new(Nest028,1,1,2,SW) { Species = 043, Ability = A3 }, // Oddish - new(Nest028,3,4,4,SW) { Species = 748, Ability = A4 }, // Toxapex - new(Nest028,4,4,4,SW) { Species = 435, Ability = A4 }, // Skuntank - new(Nest030,0,1,1,SW) { Species = 627, Ability = A3 }, // Rufflet - new(Nest030,4,4,4,SW) { Species = 628, Ability = A4 }, // Braviary - new(Nest032,0,1,1,SW) { Species = 684, Ability = A3 }, // Swirlix - new(Nest032,4,4,4,SW) { Species = 685, Ability = A4 }, // Slurpuff - new(Nest033,4,4,4,SW) { Species = 303, Ability = A4 }, // Mawile - new(Nest034,4,4,4,SW) { Species = 275, Ability = A4 }, // Shiftry - new(Nest035,1,1,2,SW) { Species = 633, Ability = A3 }, // Deino - new(Nest035,3,4,4,SW) { Species = 634, Ability = A4 }, // Zweilous - new(Nest035,4,4,4,SW) { Species = 635, Ability = A4 }, // Hydreigon - new(Nest036,0,0,1,SW) { Species = 328, Ability = A3 }, // Trapinch - new(Nest036,0,1,1,SW) { Species = 610, Ability = A3 }, // Axew - new(Nest036,1,1,2,SW) { Species = 782, Ability = A3 }, // Jangmo-o - new(Nest036,2,3,3,SW) { Species = 783, Ability = A3 }, // Hakamo-o - new(Nest036,2,4,4,SW) { Species = 611, Ability = A3 }, // Fraxure - new(Nest036,2,4,4,SW) { Species = 612, Ability = A3 }, // Haxorus - new(Nest036,3,4,4,SW) { Species = 330, Ability = A4 }, // Flygon - new(Nest036,4,4,4,SW) { Species = 776, Ability = A4 }, // Turtonator - new(Nest036,4,4,4,SW) { Species = 784, Ability = A4 }, // Kommo-o - //new(Nest037,0,1,1,SW) { Species = 782, Ability = A3 }, // Jangmo-o - new(Nest037,2,4,4,SW) { Species = 783, Ability = A3 }, // Hakamo-o - new(Nest037,3,4,4,SW) { Species = 784, Ability = A4 }, // Kommo-o - new(Nest037,4,4,4,SW) { Species = 841, Ability = A4 }, // Flapple - new(Nest039,1,1,2,SW) { Species = 876, Ability = A3, Gender = 0 }, // Indeedee - new(Nest039,4,4,4,SW) { Species = 628, Ability = A4 }, // Braviary - //new(Nest040,0,1,1,SW) { Species = 747, Ability = A3 }, // Mareanie - //new(Nest040,1,1,2,SW) { Species = 536, Ability = A3 }, // Palpitoad - new(Nest040,2,3,3,SW) { Species = 091, Ability = A3 }, // Cloyster - new(Nest040,2,4,4,SW) { Species = 746, Ability = A3 }, // Wishiwashi - new(Nest040,3,4,4,SW) { Species = 537, Ability = A4 }, // Seismitoad - new(Nest040,4,4,4,SW) { Species = 748, Ability = A4 }, // Toxapex - new(Nest042,1,3,2,SW) { Species = 710, Ability = A3 }, // Pumpkaboo - new(Nest042,4,4,4,SW) { Species = 867, Ability = A3 }, // Runerigus - new(Nest042,3,4,4,SW) { Species = 855, Ability = A4 }, // Polteageist - new(Nest042,3,4,4,SW) { Species = 711, Ability = A4 }, // Gourgeist - new(Nest043,2,2,2,SW) { Species = 550, Ability = A3 }, // Basculin - new(Nest044,1,2,2,SW) { Species = 632, Ability = A3 }, // Durant - new(Nest044,2,3,3,SW) { Species = 600, Ability = A3 }, // Klang - new(Nest045,0,1,1,SW) { Species = 588, Ability = A3 }, // Karrablast - new(Nest045,1,2,2,SW) { Species = 616, Ability = A3 }, // Shelmet - new(Nest045,4,4,4,SW) { Species = 589, Ability = A4 }, // Escavalier - new(Nest046,1,3,3,SW) { Species = 710, Ability = A3 }, // Pumpkaboo - new(Nest046,2,4,4,SW) { Species = 711, Ability = A3 }, // Gourgeist - new(Nest046,3,4,4,SW) { Species = 711, Ability = A4 }, // Gourgeist - new(Nest047,0,1,1,SW) { Species = 559, Ability = A3 }, // Scraggy - new(Nest047,2,4,4,SW) { Species = 560, Ability = A3 }, // Scrafty - new(Nest047,3,4,4,SW) { Species = 766, Ability = A4 }, // Passimian - new(Nest050,0,1,1,SW) { Species = 688, Ability = A3 }, // Binacle - new(Nest050,1,2,2,SW) { Species = 185, Ability = A3 }, // Sudowoodo - new(Nest050,2,3,3,SW) { Species = 689, Ability = A3 }, // Barbaracle - new(Nest050,4,4,4,SW) { Species = 874, Ability = A4 }, // Stonjourner - new(Nest052,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix - new(Nest052,1,2,2,SW) { Species = 038, Ability = A3 }, // Ninetales - new(Nest052,3,4,4,SW) { Species = 038, Ability = A4 }, // Ninetales - new(Nest053,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix - new(Nest053,1,2,2,SW) { Species = 608, Ability = A3 }, // Lampent - new(Nest053,2,3,3,SW) { Species = 631, Ability = A3 }, // Heatmor - new(Nest053,3,4,4,SW) { Species = 038, Ability = A4 }, // Ninetales - new(Nest053,4,4,4,SW) { Species = 776, Ability = A4 }, // Turtonator - new(Nest054,0,0,1,SW) { Species = 554, Ability = A3, Form = 1 }, // Darumaka-1 - new(Nest054,4,4,4,SW) { Species = 555, Ability = A4, Form = 2 }, // Darmanitan-2 - new(Nest057,0,0,1,SW) { Species = 273, Ability = A3 }, // Seedot - new(Nest057,1,2,2,SW) { Species = 274, Ability = A3 }, // Nuzleaf - new(Nest057,2,4,4,SW) { Species = 275, Ability = A3 }, // Shiftry - new(Nest057,4,4,4,SW) { Species = 841, Ability = A4 }, // Flapple - new(Nest058,0,0,1,SW) { Species = 273, Ability = A3 }, // Seedot - new(Nest058,1,2,2,SW) { Species = 274, Ability = A3 }, // Nuzleaf - new(Nest058,4,4,4,SW) { Species = 275, Ability = A4 }, // Shiftry - new(Nest059,1,2,2,SW) { Species = 747, Ability = A3 }, // Mareanie - new(Nest059,4,4,4,SW) { Species = 748, Ability = A4 }, // Toxapex - new(Nest061,2,4,4,SW) { Species = 303, Ability = A3 }, // Mawile - new(Nest062,0,1,1,SW) { Species = 559, Ability = A3 }, // Scraggy - new(Nest062,3,4,4,SW) { Species = 560, Ability = A4 }, // Scrafty - new(Nest062,4,4,4,SW) { Species = 635, Ability = A4 }, // Hydreigon - new(Nest063,0,0,1,SW) { Species = 328, Ability = A3 }, // Trapinch - new(Nest063,0,1,1,SW) { Species = 610, Ability = A3 }, // Axew - new(Nest063,0,1,1,SW) { Species = 782, Ability = A3 }, // Jangmo-o - new(Nest063,1,2,2,SW) { Species = 611, Ability = A3 }, // Fraxure - new(Nest063,2,4,4,SW) { Species = 783, Ability = A3 }, // Hakamo-o - new(Nest063,2,4,4,SW) { Species = 776, Ability = A3 }, // Turtonator - new(Nest063,3,4,4,SW) { Species = 784, Ability = A4 }, // Kommo-o - new(Nest063,4,4,4,SW) { Species = 612, Ability = A4 }, // Haxorus - new(Nest064,3,4,4,SW) { Species = 628, Ability = A4 }, // Braviary - new(Nest064,3,4,4,SW) { Species = 876, Ability = A4, Gender = 0 }, // Indeedee - new(Nest066,2,2,2,SW) { Species = 550, Ability = A3 }, // Basculin - new(Nest070,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix - new(Nest070,0,1,1,SW) { Species = 037, Ability = A3 }, // Vulpix - new(Nest070,1,2,2,SW) { Species = 608, Ability = A3 }, // Lampent - new(Nest070,2,3,3,SW) { Species = 631, Ability = A3 }, // Heatmor - //new(Nest073,0,0,1,SW) { Species = 684, Ability = A3 }, // Swirlix - new(Nest073,2,4,4,SW) { Species = 685, Ability = A3 }, // Slurpuff - new(Nest075,2,4,4,SW) { Species = 550, Ability = A3 }, // Basculin - //new(Nest076,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix - new(Nest076,2,2,2,SW) { Species = 038, Ability = A3 }, // Ninetales - new(Nest076,3,4,4,SW) { Species = 038, Ability = A4 }, // Ninetales - new(Nest077,1,2,2,SW) { Species = 550, Ability = A3 }, // Basculin - new(Nest078,0,0,1,SW) { Species = 273, Ability = A3 }, // Seedot - new(Nest078,1,2,2,SW) { Species = 274, Ability = A3 }, // Nuzleaf - new(Nest078,2,4,4,SW) { Species = 275, Ability = A3 }, // Shiftry - new(Nest078,4,4,4,SW) { Species = 841, Ability = A4, CanGigantamax = true }, // Flapple - new(Nest079,0,0,1,SW) { Species = 037, Ability = A3 }, // Vulpix - new(Nest079,3,4,4,SW) { Species = 038, Ability = A4 }, // Ninetales - new(Nest080,0,0,1,SW) { Species = 447, Ability = A3 }, // Riolu - new(Nest080,0,0,1,SW) { Species = 066, Ability = A3 }, // Machop - new(Nest080,0,1,1,SW) { Species = 759, Ability = A3 }, // Stufful - new(Nest080,0,1,1,SW) { Species = 083, Ability = A3, Form = 1 }, // Farfetch’d-1 - new(Nest080,1,2,2,SW) { Species = 760, Ability = A3 }, // Bewear - new(Nest080,1,3,3,SW) { Species = 067, Ability = A3 }, // Machoke - new(Nest080,2,3,3,SW) { Species = 870, Ability = A3 }, // Falinks - new(Nest080,2,4,4,SW) { Species = 701, Ability = A3 }, // Hawlucha - new(Nest080,3,4,4,SW) { Species = 448, Ability = A4 }, // Lucario - new(Nest080,3,4,4,SW) { Species = 475, Ability = A4 }, // Gallade - new(Nest080,4,4,4,SW) { Species = 865, Ability = A4 }, // Sirfetch’d - new(Nest080,4,4,4,SW) { Species = 068, Ability = A4, CanGigantamax = true }, // Machamp - new(Nest081,0,0,1,SW) { Species = 755, Ability = A3 }, // Morelull - new(Nest081,2,4,4,SW) { Species = 303, Ability = A3 }, // Mawile - new(Nest082,0,0,1,SW) { Species = 557, Ability = A3 }, // Dwebble - new(Nest082,0,0,1,SW) { Species = 438, Ability = A3 }, // Bonsly - new(Nest082,0,1,1,SW) { Species = 837, Ability = A3 }, // Rolycoly - new(Nest082,0,1,1,SW) { Species = 688, Ability = A3 }, // Binacle - new(Nest082,1,2,2,SW) { Species = 838, Ability = A3 }, // Carkol - new(Nest082,1,2,2,SW) { Species = 185, Ability = A3 }, // Sudowoodo - new(Nest082,2,3,3,SW) { Species = 689, Ability = A3 }, // Barbaracle - new(Nest082,2,4,4,SW) { Species = 095, Ability = A3 }, // Onix - new(Nest082,3,4,4,SW) { Species = 558, Ability = A4 }, // Crustle - new(Nest082,3,4,4,SW) { Species = 208, Ability = A4 }, // Steelix - new(Nest082,4,4,4,SW) { Species = 874, Ability = A4 }, // Stonjourner - new(Nest082,4,4,4,SW) { Species = 839, Ability = A4, CanGigantamax = true }, // Coalossal - new(Nest083,1,2,2,SW) { Species = 632, Ability = A3 }, // Durant - new(Nest083,2,3,3,SW) { Species = 600, Ability = A3 }, // Klang - new(Nest085,1,2,2,SW) { Species = 747, Ability = A3 }, // Mareanie - new(Nest085,4,4,4,SW) { Species = 748, Ability = A4 }, // Toxapex - new(Nest086,0,0,1,SW) { Species = 684, Ability = A3 }, // Swirlix - new(Nest086,2,3,3,SW) { Species = 685, Ability = A3 }, // Slurpuff - new(Nest087,0,1,1,SW) { Species = 559, Ability = A3 }, // Scraggy - new(Nest087,3,4,4,SW) { Species = 560, Ability = A4 }, // Scrafty - new(Nest087,4,4,4,SW) { Species = 635, Ability = A4 }, // Hydreigon - new(Nest089,0,1,1,SW) { Species = 588, Ability = A3 }, // Karrablast - new(Nest089,1,2,2,SW) { Species = 616, Ability = A3 }, // Shelmet - new(Nest089,4,4,4,SW) { Species = 589, Ability = A4 }, // Escavalier - new(Nest090,1,2,2,SW) { Species = 550, Ability = A3 }, // Basculin - new(Nest091,0,1,1,SW) { Species = 588, Ability = A3 }, // Karrablast - new(Nest091,1,2,2,SW) { Species = 616, Ability = A3 }, // Shelmet - new(Nest091,4,4,4,SW) { Species = 589, Ability = A4 }, // Escavalier - new(Nest106,1,2,2,SW) { Species = 627, Ability = A3 }, // Rufflet - new(Nest106,3,4,4,SW) { Species = 628, Ability = A4 }, // Braviary - new(Nest106,4,4,4,SW) { Species = 628, Ability = A4 }, // Braviary - new(Nest107,1,2,2,SW) { Species = 627, Ability = A2 }, // Rufflet - new(Nest107,4,4,4,SW) { Species = 628, Ability = A2 }, // Braviary - new(Nest108,0,1,1,SW) { Species = 127, Ability = A3 }, // Pinsir - new(Nest108,1,2,2,SW) { Species = 127, Ability = A3 }, // Pinsir - new(Nest108,2,3,3,SW) { Species = 127, Ability = A3 }, // Pinsir - new(Nest108,3,4,4,SW) { Species = 127, Ability = A4 }, // Pinsir - new(Nest109,0,1,1,SW) { Species = 127, Ability = A2 }, // Pinsir - new(Nest109,4,4,4,SW) { Species = 127, Ability = A2 }, // Pinsir - new(Nest113,4,4,4,SW) { Species = 038, Ability = A2 }, // Ninetales - new(Nest114,4,4,4,SW) { Species = 745, Ability = A4 }, // Lycanroc - new(Nest115,3,4,4,SW) { Species = 745, Ability = A2 }, // Lycanroc - new(Nest116,2,3,3,SW) { Species = 064, Ability = A3 }, // Kadabra - new(Nest117,2,3,3,SW) { Species = 064, Ability = A2 }, // Kadabra - new(Nest117,3,4,4,SW) { Species = 876, Ability = A2, Gender = 0 }, // Indeedee - new(Nest117,4,4,4,SW) { Species = 678, Ability = A2, Gender = 0 }, // Meowstic - new(Nest122,1,2,2,SW) { Species = 559, Ability = A3 }, // Scraggy - new(Nest122,2,3,3,SW) { Species = 766, Ability = A3 }, // Passimian - new(Nest122,2,3,3,SW) { Species = 560, Ability = A3 }, // Scrafty - new(Nest122,3,4,4,SW) { Species = 560, Ability = A4 }, // Scrafty - new(Nest123,0,1,1,SW) { Species = 559, Ability = A2 }, // Scraggy - new(Nest123,1,2,2,SW) { Species = 539, Ability = A2 }, // Sawk - new(Nest123,2,3,3,SW) { Species = 766, Ability = A2 }, // Passimian - new(Nest123,3,4,4,SW) { Species = 539, Ability = A2 }, // Sawk - new(Nest123,3,4,4,SW) { Species = 560, Ability = A2 }, // Scrafty - new(Nest123,4,4,4,SW) { Species = 865, Ability = A2 }, // Sirfetch’d - new(Nest127,4,4,4,SW) { Species = 770, Ability = A2 }, // Palossand - //new(Nest130,0,1,1,SW) { Species = 782, Ability = A3 }, // Jangmo-o - new(Nest130,2,3,3,SW) { Species = 783, Ability = A3 }, // Hakamo-o - new(Nest130,3,4,4,SW) { Species = 841, Ability = A4 }, // Flapple - new(Nest130,4,4,4,SW) { Species = 784, Ability = A4 }, // Kommo-o - new(Nest131,2,2,2,SW) { Species = 776, Ability = A2 }, // Turtonator - new(Nest131,2,2,2,SW) { Species = 782, Ability = A2 }, // Jangmo-o - new(Nest131,2,3,3,SW) { Species = 783, Ability = A2 }, // Hakamo-o - new(Nest131,3,4,4,SW) { Species = 776, Ability = A2 }, // Turtonator - new(Nest131,4,4,4,SW) { Species = 784, Ability = A2 }, // Kommo-o - new(Nest135,4,4,4,SW) { Species = 550, Ability = A2 }, // Basculin - //new(Nest138,0,1,1,SW) { Species = 692, Ability = A3 }, // Clauncher - new(Nest138,2,2,2,SW) { Species = 692, Ability = A3 }, // Clauncher - new(Nest138,4,4,4,SW) { Species = 693, Ability = A4 }, // Clawitzer - //new(Nest139,0,1,1,SW) { Species = 692, Ability = A2 }, // Clauncher - new(Nest139,3,4,4,SW) { Species = 693, Ability = A2 }, // Clawitzer - new(Nest139,4,4,4,SW) { Species = 693, Ability = A2 }, // Clawitzer - new(Nest147,1,2,2,SW) { Species = 550, Ability = A2 }, // Basculin - new(Nest147,3,4,4,SW) { Species = 550, Ability = A2 }, // Basculin - new(Nest148,1,2,2,SW) { Species = 127, Ability = A2 }, // Pinsir - new(Nest150,2,3,3,SW) { Species = 841, Ability = A2 }, // Flapple - new(Nest150,3,4,4,SW) { Species = 841, Ability = A2 }, // Flapple - new(Nest150,4,4,4,SW) { Species = 841, Ability = A2, CanGigantamax = true }, // Flapple - new(Nest155,4,4,4,SW) { Species = 745, Ability = A4 }, // Lycanroc - new(Nest160,3,4,5,SW) { Species = 038, Ability = A2 }, // Ninetales - new(Nest161,0,1,1,SW) { Species = 138, Ability = A3 }, // Omanyte - new(Nest161,1,2,2,SW) { Species = 138, Ability = A3 }, // Omanyte - new(Nest161,2,3,3,SW) { Species = 550, Ability = A3 }, // Basculin - new(Nest161,3,4,4,SW) { Species = 139, Ability = A4 }, // Omastar - new(Nest161,3,4,4,SW) { Species = 550, Ability = A4 }, // Basculin - new(Nest162,0,1,2,SW) { Species = 138, Ability = A2 }, // Omanyte - new(Nest162,2,3,4,SW) { Species = 550, Ability = A2 }, // Basculin - new(Nest162,3,4,5,SW) { Species = 139, Ability = A2 }, // Omastar - new(Nest164,3,4,5,SW) { Species = 849, Ability = A2 }, // Toxtricity - new(Nest164,4,4,5,SW) { Species = 849, Ability = A2, CanGigantamax = true }, // Toxtricity - new(Nest165,0,1,1,SW) { Species = 347, Ability = A3 }, // Anorith - new(Nest165,2,3,3,SW) { Species = 347, Ability = A3 }, // Anorith - new(Nest165,3,4,4,SW) { Species = 348, Ability = A4 }, // Armaldo - new(Nest165,4,4,4,SW) { Species = 346, Ability = A4 }, // Cradily - new(Nest166,4,4,5,SW) { Species = 348, Ability = A2 }, // Armaldo - new(Nest167,0,1,1,SW) { Species = 554, Ability = A3, Form = 1 }, // Darumaka-1 - new(Nest167,3,4,4,SW) { Species = 555, Ability = A4, Form = 2 }, // Darmanitan-2 - new(Nest168,0,1,2,SW) { Species = 554, Ability = A2, Form = 1 }, // Darumaka-1 - new(Nest168,3,4,5,SW) { Species = 555, Ability = A2, Form = 2 }, // Darmanitan-2 - //new(Nest169,0,1,1,SW) { Species = 613, Ability = A3 }, // Cubchoo - new(Nest169,2,2,2,SW) { Species = 712, Ability = A3 }, // Bergmite - new(Nest170,4,4,5,SW) { Species = 131, Ability = A2 }, // Lapras - new(Nest172,1,2,3,SW) { Species = 083, Ability = A2, Form = 1 }, // Farfetch’d-1 - new(Nest172,3,4,5,SW) { Species = 865, Ability = A2 }, // Sirfetch’d - new(Nest172,3,4,5,SW) { Species = 106, Ability = A2 }, // Hitmonlee - new(Nest172,4,4,5,SW) { Species = 068, Ability = A2, CanGigantamax = true }, // Machamp - new(Nest173,0,1,1,SW) { Species = 029, Ability = A3 }, // Nidoran♀ - new(Nest173,3,4,4,SW) { Species = 031, Ability = A4 }, // Nidoqueen - new(Nest174,1,2,3,SW) { Species = 030, Ability = A2 }, // Nidorina - new(Nest177,0,1,1,SW) { Species = 343, Ability = A3 }, // Baltoy - new(Nest177,2,3,3,SW) { Species = 575, Ability = A3 }, // Gothorita - new(Nest177,3,4,4,SW) { Species = 876, Ability = A4, Gender = 0 }, // Indeedee - new(Nest177,3,4,4,SW) { Species = 576, Ability = A4 }, // Gothitelle - new(Nest177,3,4,4,SW) { Species = 344, Ability = A4 }, // Claydol - new(Nest178,0,1,2,SW) { Species = 876, Ability = A2, Gender = 0 }, // Indeedee - new(Nest178,0,1,2,SW) { Species = 574, Ability = A2 }, // Gothita - new(Nest178,2,3,4,SW) { Species = 876, Ability = A2, Gender = 0 }, // Indeedee - new(Nest178,3,4,5,SW) { Species = 876, Ability = A2, Gender = 0 }, // Indeedee - new(Nest178,3,4,5,SW) { Species = 576, Ability = A2 }, // Gothitelle - new(Nest178,4,4,5,SW) { Species = 576, Ability = A2 }, // Gothitelle - new(Nest179,0,1,1,SW) { Species = 874, Ability = A3 }, // Stonjourner - new(Nest179,1,2,2,SW) { Species = 874, Ability = A3 }, // Stonjourner - new(Nest179,2,3,3,SW) { Species = 874, Ability = A3 }, // Stonjourner - new(Nest179,3,4,4,SW) { Species = 303, Ability = A4 }, // Mawile - new(Nest180,0,1,2,SW) { Species = 303, Ability = A2 }, // Mawile - new(Nest180,2,3,4,SW) { Species = 303, Ability = A2 }, // Mawile - new(Nest180,3,4,5,SW) { Species = 303, Ability = A2 }, // Mawile - new(Nest180,4,4,5,SW) { Species = 839, Ability = A2, CanGigantamax = true }, // Coalossal - new(Nest181,3,4,4,SW) { Species = 303, Ability = A4 }, // Mawile - new(Nest182,1,2,3,SW) { Species = 093, Ability = A2 }, // Haunter - new(Nest182,2,3,4,SW) { Species = 303, Ability = A2 }, // Mawile - new(Nest182,3,4,5,SW) { Species = 709, Ability = A2 }, // Trevenant - new(Nest182,3,4,5,SW) { Species = 303, Ability = A2 }, // Mawile - new(Nest182,4,4,5,SW) { Species = 094, Ability = A2 }, // Gengar - new(Nest183,0,1,1,SW) { Species = 371, Ability = A3 }, // Bagon - new(Nest183,1,2,2,SW) { Species = 371, Ability = A3 }, // Bagon - new(Nest183,2,3,3,SW) { Species = 372, Ability = A3 }, // Shelgon - new(Nest183,3,4,4,SW) { Species = 372, Ability = A4 }, // Shelgon - new(Nest183,4,4,4,SW) { Species = 373, Ability = A4 }, // Salamence - new(Nest184,0,1,2,SW) { Species = 371, Ability = A2 }, // Bagon - new(Nest184,1,2,3,SW) { Species = 776, Ability = A2 }, // Turtonator - new(Nest184,2,3,4,SW) { Species = 372, Ability = A2 }, // Shelgon - new(Nest184,3,4,5,SW) { Species = 373, Ability = A2 }, // Salamence - new(Nest184,4,4,5,SW) { Species = 373, Ability = A2 }, // Salamence - new(Nest192,3,4,5,SW) { Species = 854, Ability = A2 }, // Sinistea - new(Nest195,0,1,1,SW) { Species = 138, Ability = A3 }, // Omanyte - new(Nest195,0,1,1,SW) { Species = 347, Ability = A3 }, // Anorith - new(Nest195,3,4,4,SW) { Species = 139, Ability = A4 }, // Omastar - new(Nest195,3,4,4,SW) { Species = 348, Ability = A4 }, // Armaldo - new(Nest195,3,4,4,SW) { Species = 697, Ability = A4 }, // Tyrantrum - new(Nest195,4,4,4,SW) { Species = 699, Ability = A4 }, // Aurorus - new(Nest196,0,1,2,SW) { Species = 138, Ability = A2 }, // Omanyte - new(Nest196,2,2,2,SW) { Species = 139, Ability = A2 }, // Omastar - new(Nest196,2,2,3,SW) { Species = 881, Ability = A2 }, // Arctozolt - new(Nest196,2,3,4,SW) { Species = 880, Ability = A2 }, // Dracozolt - new(Nest196,3,3,4,SW) { Species = 882, Ability = A2 }, // Dracovish - }; - - internal static readonly EncounterStatic8N[] Nest_SH = - { - new(Nest000,0,1,1,SH) { Species = 453, Ability = A3 }, // Croagunk - new(Nest000,2,3,3,SH) { Species = 107, Ability = A3 }, // Hitmonchan - new(Nest000,2,4,4,SH) { Species = 106, Ability = A3 }, // Hitmonlee - new(Nest000,2,4,4,SH) { Species = 454, Ability = A3 }, // Toxicroak - new(Nest000,3,4,4,SH) { Species = 237, Ability = A4 }, // Hitmontop - new(Nest000,4,4,4,SH) { Species = 534, Ability = A4 }, // Conkeldurr - new(Nest001,0,1,1,SH) { Species = 577, Ability = A3 }, // Solosis - new(Nest001,2,3,3,SH) { Species = 678, Ability = A3, Gender = 1, Form = 1 }, // Meowstic-1 - new(Nest001,2,3,3,SH) { Species = 578, Ability = A3 }, // Duosion - new(Nest001,3,4,4,SH) { Species = 579, Ability = A4 }, // Reuniclus - new(Nest001,4,4,4,SH) { Species = 337, Ability = A4 }, // Lunatone - new(Nest002,0,0,1,SH) { Species = 688, Ability = A3 }, // Binacle - new(Nest002,0,1,1,SH) { Species = 524, Ability = A3 }, // Roggenrola - new(Nest002,3,4,4,SH) { Species = 526, Ability = A4 }, // Gigalith - new(Nest002,4,4,4,SH) { Species = 558, Ability = A4 }, // Crustle - //new(Nest006,0,1,2,SH) { Species = 223, Ability = A3 }, // Remoraid - //new(Nest006,0,1,2,SH) { Species = 170, Ability = A3 }, // Chinchou - new(Nest006,2,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 - new(Nest007,2,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 - new(Nest008,1,1,1,SH) { Species = 090, Ability = A3 }, // Shellder - new(Nest009,1,1,2,SH) { Species = 759, Ability = A3 }, // Stufful - new(Nest009,1,2,2,SH) { Species = 538, Ability = A3 }, // Throh - new(Nest009,3,4,4,SH) { Species = 760, Ability = A4 }, // Bewear - new(Nest011,4,4,4,SH) { Species = 208, Ability = A4 }, // Steelix - new(Nest012,0,1,2,SH) { Species = 177, Ability = A3 }, // Natu - new(Nest012,0,1,2,SH) { Species = 856, Ability = A3 }, // Hatenna - new(Nest012,1,1,2,SH) { Species = 077, Ability = A3, Form = 1 }, // Ponyta-1 - new(Nest012,1,3,3,SH) { Species = 857, Ability = A3 }, // Hattrem - new(Nest012,2,4,4,SH) { Species = 876, Ability = A3, Gender = 1, Form = 1 }, // Indeedee-1 - new(Nest012,3,4,4,SH) { Species = 765, Ability = A4 }, // Oranguru - new(Nest012,4,4,4,SH) { Species = 078, Ability = A4, Form = 1 }, // Rapidash-1 - new(Nest013,2,4,4,SH) { Species = 876, Ability = A3, Gender = 1, Form = 1 }, // Indeedee-1 - new(Nest014,0,0,1,SH) { Species = 557, Ability = A3 }, // Dwebble - new(Nest014,0,1,1,SH) { Species = 524, Ability = A3 }, // Roggenrola - new(Nest014,2,4,4,SH) { Species = 839, Ability = A3 }, // Coalossal - new(Nest014,3,4,4,SH) { Species = 526, Ability = A4 }, // Gigalith - new(Nest014,4,4,4,SH) { Species = 095, Ability = A4 }, // Onix - new(Nest016,0,1,1,SH) { Species = 328, Ability = A3 }, // Trapinch - new(Nest016,1,1,1,SH) { Species = 220, Ability = A3 }, // Swinub - new(Nest016,2,3,3,SH) { Species = 618, Ability = A3, Form = 1 }, // Stunfisk-1 - new(Nest016,2,4,4,SH) { Species = 329, Ability = A3 }, // Vibrava - new(Nest016,3,4,4,SH) { Species = 330, Ability = A4 }, // Flygon - new(Nest016,4,4,4,SH) { Species = 450, Ability = A4 }, // Hippowdon - new(Nest017,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe - new(Nest017,1,1,1,SH) { Species = 631, Ability = A3 }, // Heatmor - new(Nest017,1,2,2,SH) { Species = 608, Ability = A3 }, // Lampent - new(Nest017,2,3,3,SH) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle - new(Nest017,2,4,4,SH) { Species = 059, Ability = A3 }, // Arcanine - new(Nest017,3,4,4,SH) { Species = 631, Ability = A4 }, // Heatmor - new(Nest017,4,4,4,SH) { Species = 851, Ability = A4 }, // Centiskorch - new(Nest017,4,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine - new(Nest018,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe - new(Nest018,0,1,1,SH) { Species = 058, Ability = A3 }, // Growlithe - new(Nest018,1,2,2,SH) { Species = 608, Ability = A3 }, // Lampent - new(Nest018,2,3,3,SH) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle - new(Nest018,2,4,4,SH) { Species = 059, Ability = A3 }, // Arcanine - new(Nest018,2,4,4,SH) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle - new(Nest018,3,4,4,SH) { Species = 324, Ability = A4 }, // Torkoal - new(Nest018,4,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine - new(Nest019,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe - new(Nest019,1,1,1,SH) { Species = 324, Ability = A3 }, // Torkoal - new(Nest019,1,2,2,SH) { Species = 838, Ability = A3 }, // Carkol - new(Nest019,2,3,3,SH) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle - new(Nest019,2,4,4,SH) { Species = 059, Ability = A3 }, // Arcanine - new(Nest019,4,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine - new(Nest022,0,1,1,SH) { Species = 225, Ability = A3 }, // Delibird - new(Nest022,4,4,4,SH) { Species = 875, Ability = A4 }, // Eiscue - new(Nest025,0,0,1,SH) { Species = 270, Ability = A3 }, // Lotad - new(Nest025,1,1,2,SH) { Species = 271, Ability = A3 }, // Lombre - new(Nest025,2,4,4,SH) { Species = 272, Ability = A3 }, // Ludicolo - new(Nest026,4,4,4,SH) { Species = 842, Ability = A4 }, // Appletun - new(Nest028,0,1,1,SH) { Species = 043, Ability = A3 }, // Oddish - new(Nest028,1,1,1,SH) { Species = 747, Ability = A3 }, // Mareanie - new(Nest028,3,4,4,SH) { Species = 435, Ability = A4 }, // Skuntank - new(Nest028,4,4,4,SH) { Species = 748, Ability = A4 }, // Toxapex - new(Nest030,0,1,1,SH) { Species = 629, Ability = A3 }, // Vullaby - new(Nest030,4,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz - new(Nest032,0,1,1,SH) { Species = 682, Ability = A3 }, // Spritzee - new(Nest032,4,4,4,SH) { Species = 683, Ability = A4 }, // Aromatisse - new(Nest033,4,4,4,SH) { Species = 078, Ability = A4, Form = 1 }, // Rapidash-1 - new(Nest034,4,4,4,SH) { Species = 302, Ability = A4 }, // Sableye - new(Nest035,1,1,2,SH) { Species = 629, Ability = A3 }, // Vullaby - new(Nest035,3,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz - new(Nest035,4,4,4,SH) { Species = 248, Ability = A4 }, // Tyranitar - new(Nest036,0,0,1,SH) { Species = 610, Ability = A3 }, // Axew - new(Nest036,0,1,1,SH) { Species = 328, Ability = A3 }, // Trapinch - new(Nest036,1,1,2,SH) { Species = 704, Ability = A3 }, // Goomy - new(Nest036,2,3,3,SH) { Species = 611, Ability = A3 }, // Fraxure - new(Nest036,2,4,4,SH) { Species = 705, Ability = A3 }, // Sliggoo - new(Nest036,2,4,4,SH) { Species = 330, Ability = A3 }, // Flygon - new(Nest036,3,4,4,SH) { Species = 612, Ability = A4 }, // Haxorus - new(Nest036,4,4,4,SH) { Species = 780, Ability = A4 }, // Drampa - new(Nest036,4,4,4,SH) { Species = 706, Ability = A4 }, // Goodra - //new(Nest037,0,1,1,SH) { Species = 704, Ability = A3 }, // Goomy - new(Nest037,2,4,4,SH) { Species = 705, Ability = A3 }, // Sliggoo - new(Nest037,3,4,4,SH) { Species = 706, Ability = A4 }, // Goodra - new(Nest037,4,4,4,SH) { Species = 842, Ability = A4 }, // Appletun - new(Nest039,1,1,2,SH) { Species = 876, Ability = A3, Gender = 1, Form = 1 }, // Indeedee-1 - new(Nest039,4,4,4,SH) { Species = 765, Ability = A4 }, // Oranguru - //new(Nest040,0,1,1,SH) { Species = 536, Ability = A3 }, // Palpitoad - //new(Nest040,1,1,2,SH) { Species = 747, Ability = A3 }, // Mareanie - new(Nest040,2,3,3,SH) { Species = 748, Ability = A3 }, // Toxapex - new(Nest040,2,4,4,SH) { Species = 091, Ability = A3 }, // Cloyster - new(Nest040,3,4,4,SH) { Species = 746, Ability = A4 }, // Wishiwashi - new(Nest040,4,4,4,SH) { Species = 537, Ability = A4 }, // Seismitoad - new(Nest042,1,3,3,SH) { Species = 222, Ability = A3, Form = 1 }, // Corsola-1 - new(Nest042,4,4,4,SH) { Species = 302, Ability = A3 }, // Sableye - new(Nest042,3,4,4,SH) { Species = 867, Ability = A4 }, // Runerigus - new(Nest042,3,4,4,SH) { Species = 864, Ability = A4 }, // Cursola - new(Nest043,2,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 - new(Nest044,1,2,2,SH) { Species = 600, Ability = A3 }, // Klang - new(Nest044,2,3,3,SH) { Species = 632, Ability = A3 }, // Durant - new(Nest045,0,1,1,SH) { Species = 616, Ability = A3 }, // Shelmet - new(Nest045,1,2,2,SH) { Species = 588, Ability = A3 }, // Karrablast - new(Nest045,4,4,4,SH) { Species = 617, Ability = A4 }, // Accelgor - new(Nest046,1,3,3,SH) { Species = 222, Ability = A3, Form = 1 }, // Corsola-1 - new(Nest046,2,4,4,SH) { Species = 302, Ability = A3 }, // Sableye - new(Nest046,3,4,4,SH) { Species = 864, Ability = A4 }, // Cursola - new(Nest047,0,1,1,SH) { Species = 453, Ability = A3 }, // Croagunk - new(Nest047,2,4,4,SH) { Species = 454, Ability = A3 }, // Toxicroak - new(Nest047,3,4,4,SH) { Species = 701, Ability = A4 }, // Hawlucha - new(Nest050,0,1,1,SH) { Species = 524, Ability = A3 }, // Roggenrola - new(Nest050,1,2,2,SH) { Species = 246, Ability = A3 }, // Larvitar - new(Nest050,2,3,3,SH) { Species = 247, Ability = A3 }, // Pupitar - new(Nest050,4,4,4,SH) { Species = 248, Ability = A4 }, // Tyranitar - new(Nest052,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe - new(Nest052,1,2,2,SH) { Species = 631, Ability = A3 }, // Heatmor - new(Nest052,3,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine - new(Nest053,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe - new(Nest053,1,2,2,SH) { Species = 631, Ability = A3 }, // Heatmor - new(Nest053,2,3,3,SH) { Species = 608, Ability = A3 }, // Lampent - new(Nest053,3,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine - new(Nest053,4,4,4,SH) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle - new(Nest054,0,0,1,SH) { Species = 613, Ability = A3 }, // Cubchoo - new(Nest054,4,4,4,SH) { Species = 875, Ability = A4 }, // Eiscue - new(Nest057,0,0,1,SH) { Species = 270, Ability = A3 }, // Lotad - new(Nest057,1,2,2,SH) { Species = 271, Ability = A3 }, // Lombre - new(Nest057,2,4,4,SH) { Species = 272, Ability = A3 }, // Ludicolo - new(Nest057,4,4,4,SH) { Species = 842, Ability = A4 }, // Appletun - new(Nest058,0,0,1,SH) { Species = 270, Ability = A3 }, // Lotad - new(Nest058,1,2,2,SH) { Species = 271, Ability = A3 }, // Lombre - new(Nest058,4,4,4,SH) { Species = 272, Ability = A4 }, // Ludicolo - new(Nest059,1,2,2,SH) { Species = 757, Ability = A3 }, // Salandit - new(Nest059,4,4,4,SH) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle - new(Nest061,2,4,4,SH) { Species = 078, Ability = A3, Form = 1 }, // Rapidash-1 - new(Nest062,0,1,1,SH) { Species = 629, Ability = A3 }, // Vullaby - new(Nest062,3,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz - new(Nest062,4,4,4,SH) { Species = 248, Ability = A4 }, // Tyranitar - new(Nest063,0,0,1,SH) { Species = 610, Ability = A3 }, // Axew - new(Nest063,0,1,1,SH) { Species = 328, Ability = A3 }, // Trapinch - new(Nest063,0,1,1,SH) { Species = 704, Ability = A3 }, // Goomy - new(Nest063,1,2,2,SH) { Species = 329, Ability = A3 }, // Vibrava - new(Nest063,2,4,4,SH) { Species = 705, Ability = A3 }, // Sliggoo - new(Nest063,2,4,4,SH) { Species = 780, Ability = A3 }, // Drampa - new(Nest063,3,4,4,SH) { Species = 706, Ability = A4 }, // Goodra - new(Nest063,4,4,4,SH) { Species = 330, Ability = A4 }, // Flygon - new(Nest064,3,4,4,SH) { Species = 765, Ability = A4 }, // Oranguru - new(Nest064,3,4,4,SH) { Species = 876, Ability = A4, Gender = 1, Form = 1 }, // Indeedee-1 - new(Nest066,2,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 - new(Nest070,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe - new(Nest070,0,1,1,SH) { Species = 058, Ability = A3 }, // Growlithe - new(Nest070,1,2,2,SH) { Species = 631, Ability = A3 }, // Heatmor - new(Nest070,2,3,3,SH) { Species = 608, Ability = A3 }, // Lampent - //new(Nest073,0,0,1,SH) { Species = 682, Ability = A3 }, // Spritzee - new(Nest073,2,4,4,SH) { Species = 683, Ability = A3 }, // Aromatisse - new(Nest075,2,4,4,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 - //new(Nest076,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe - new(Nest076,2,2,2,SH) { Species = 631, Ability = A3 }, // Heatmor - new(Nest076,3,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine - new(Nest077,1,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 - new(Nest078,0,0,1,SH) { Species = 270, Ability = A3 }, // Lotad - new(Nest078,1,2,2,SH) { Species = 271, Ability = A3 }, // Lombre - new(Nest078,2,4,4,SH) { Species = 272, Ability = A3 }, // Ludicolo - new(Nest078,4,4,4,SH) { Species = 842, Ability = A4, CanGigantamax = true }, // Appletun - new(Nest079,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe - new(Nest079,3,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine - new(Nest080,0,0,1,SH) { Species = 679, Ability = A3 }, // Honedge - new(Nest080,0,0,1,SH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 - new(Nest080,0,1,1,SH) { Species = 854, Ability = A3 }, // Sinistea - new(Nest080,0,1,1,SH) { Species = 092, Ability = A3 }, // Gastly - new(Nest080,1,2,2,SH) { Species = 680, Ability = A3 }, // Doublade - new(Nest080,1,3,3,SH) { Species = 222, Ability = A3, Form = 1 }, // Corsola-1 - new(Nest080,2,3,3,SH) { Species = 093, Ability = A3 }, // Haunter - new(Nest080,2,4,4,SH) { Species = 302, Ability = A3 }, // Sableye - new(Nest080,3,4,4,SH) { Species = 855, Ability = A4 }, // Polteageist - new(Nest080,3,4,4,SH) { Species = 864, Ability = A4 }, // Cursola - new(Nest080,4,4,4,SH) { Species = 867, Ability = A4 }, // Runerigus - new(Nest080,4,4,4,SH) { Species = 094, Ability = A4, CanGigantamax = true }, // Gengar - new(Nest081,0,0,1,SH) { Species = 077, Ability = A3, Form = 1 }, // Ponyta-1 - new(Nest081,2,4,4,SH) { Species = 078, Ability = A3, Form = 1 }, // Rapidash-1 - new(Nest082,0,0,1,SH) { Species = 582, Ability = A3 }, // Vanillite - new(Nest082,0,0,1,SH) { Species = 613, Ability = A3 }, // Cubchoo - new(Nest082,0,1,1,SH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 - new(Nest082,0,1,1,SH) { Species = 712, Ability = A3 }, // Bergmite - new(Nest082,1,2,2,SH) { Species = 361, Ability = A3 }, // Snorunt - new(Nest082,1,2,2,SH) { Species = 225, Ability = A3 }, // Delibird - new(Nest082,2,3,3,SH) { Species = 713, Ability = A3 }, // Avalugg - new(Nest082,2,4,4,SH) { Species = 362, Ability = A3 }, // Glalie - new(Nest082,3,4,4,SH) { Species = 584, Ability = A4 }, // Vanilluxe - new(Nest082,3,4,4,SH) { Species = 866, Ability = A4 }, // Mr. Rime - new(Nest082,4,4,4,SH) { Species = 875, Ability = A4 }, // Eiscue - new(Nest082,4,4,4,SH) { Species = 131, Ability = A4, CanGigantamax = true }, // Lapras - new(Nest083,1,2,2,SH) { Species = 600, Ability = A3 }, // Klang - new(Nest083,2,3,3,SH) { Species = 632, Ability = A3 }, // Durant - new(Nest085,1,2,2,SH) { Species = 757, Ability = A3 }, // Salandit - new(Nest085,4,4,4,SH) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle - new(Nest086,0,0,1,SH) { Species = 682, Ability = A3 }, // Spritzee - new(Nest086,2,3,3,SH) { Species = 683, Ability = A3 }, // Aromatisse - new(Nest087,0,1,1,SH) { Species = 629, Ability = A3 }, // Vullaby - new(Nest087,3,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz - new(Nest087,4,4,4,SH) { Species = 248, Ability = A4 }, // Tyranitar - new(Nest089,0,1,1,SH) { Species = 616, Ability = A3 }, // Shelmet - new(Nest089,1,2,2,SH) { Species = 588, Ability = A3 }, // Karrablast - new(Nest089,4,4,4,SH) { Species = 617, Ability = A4 }, // Accelgor - new(Nest090,1,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 - new(Nest091,0,1,1,SH) { Species = 616, Ability = A3 }, // Shelmet - new(Nest091,1,2,2,SH) { Species = 588, Ability = A3 }, // Karrablast - new(Nest091,4,4,4,SH) { Species = 617, Ability = A4 }, // Accelgor - new(Nest106,1,2,2,SH) { Species = 629, Ability = A3 }, // Vullaby - new(Nest106,3,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz - new(Nest106,4,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz - new(Nest107,1,2,2,SH) { Species = 629, Ability = A2 }, // Vullaby - new(Nest107,4,4,4,SH) { Species = 630, Ability = A2 }, // Mandibuzz - new(Nest108,0,1,1,SH) { Species = 214, Ability = A3 }, // Heracross - new(Nest108,1,2,2,SH) { Species = 214, Ability = A3 }, // Heracross - new(Nest108,2,3,3,SH) { Species = 214, Ability = A3 }, // Heracross - new(Nest108,3,4,4,SH) { Species = 214, Ability = A4 }, // Heracross - new(Nest109,0,1,1,SH) { Species = 214, Ability = A2 }, // Heracross - new(Nest109,4,4,4,SH) { Species = 214, Ability = A2 }, // Heracross - new(Nest113,4,4,4,SH) { Species = 059, Ability = A2 }, // Arcanine - new(Nest114,4,4,4,SH) { Species = 745, Ability = A4, Form = 1 }, // Lycanroc-1 - new(Nest115,3,4,4,SH) { Species = 745, Ability = A2, Form = 1 }, // Lycanroc-1 - new(Nest116,2,3,3,SH) { Species = 765, Ability = A3 }, // Oranguru - new(Nest117,2,3,3,SH) { Species = 765, Ability = A2 }, // Oranguru - new(Nest117,3,4,4,SH) { Species = 876, Ability = A2, Gender = 1, Form = 1 }, // Indeedee-1 - new(Nest117,4,4,4,SH) { Species = 678, Ability = A2, Gender = 1, Form = 1 }, // Meowstic-1 - new(Nest122,1,2,2,SH) { Species = 453, Ability = A3 }, // Croagunk - new(Nest122,2,3,3,SH) { Species = 853, Ability = A3 }, // Grapploct - new(Nest122,2,3,3,SH) { Species = 454, Ability = A3 }, // Toxicroak - new(Nest122,3,4,4,SH) { Species = 454, Ability = A4 }, // Toxicroak - new(Nest123,0,1,1,SH) { Species = 453, Ability = A2 }, // Croagunk - new(Nest123,1,2,2,SH) { Species = 538, Ability = A2 }, // Throh - new(Nest123,2,3,3,SH) { Species = 620, Ability = A2 }, // Mienshao - new(Nest123,3,4,4,SH) { Species = 538, Ability = A2 }, // Throh - new(Nest123,3,4,4,SH) { Species = 454, Ability = A2 }, // Toxicroak - new(Nest123,4,4,4,SH) { Species = 870, Ability = A2 }, // Falinks - new(Nest127,4,4,4,SH) { Species = 864, Ability = A2 }, // Cursola - //new(Nest130,0,1,1,SH) { Species = 704, Ability = A3 }, // Goomy - new(Nest130,2,3,3,SH) { Species = 705, Ability = A3 }, // Sliggoo - new(Nest130,3,4,4,SH) { Species = 842, Ability = A4 }, // Appletun - new(Nest130,4,4,4,SH) { Species = 706, Ability = A4 }, // Goodra - new(Nest131,2,2,2,SH) { Species = 780, Ability = A2 }, // Drampa - new(Nest131,2,2,2,SH) { Species = 704, Ability = A2 }, // Goomy - new(Nest131,2,3,3,SH) { Species = 705, Ability = A2 }, // Sliggoo - new(Nest131,3,4,4,SH) { Species = 780, Ability = A2 }, // Drampa - new(Nest131,4,4,4,SH) { Species = 706, Ability = A2 }, // Goodra - new(Nest135,4,4,4,SH) { Species = 550, Ability = A2, Form = 1 }, // Basculin-1 - //new(Nest138,0,1,1,SH) { Species = 690, Ability = A3 }, // Skrelp - new(Nest138,2,2,2,SH) { Species = 690, Ability = A3 }, // Skrelp - new(Nest138,4,4,4,SH) { Species = 691, Ability = A4 }, // Dragalge - //new(Nest139,0,1,1,SH) { Species = 690, Ability = A2 }, // Skrelp - new(Nest139,3,4,4,SH) { Species = 691, Ability = A2 }, // Dragalge - new(Nest139,4,4,4,SH) { Species = 691, Ability = A2 }, // Dragalge - new(Nest147,1,2,2,SH) { Species = 550, Ability = A2, Form = 1 }, // Basculin-1 - new(Nest147,3,4,4,SH) { Species = 550, Ability = A2, Form = 1 }, // Basculin-1 - new(Nest148,1,2,2,SH) { Species = 214, Ability = A2 }, // Heracross - new(Nest150,2,3,3,SH) { Species = 842, Ability = A2 }, // Appletun - new(Nest150,3,4,4,SH) { Species = 842, Ability = A2 }, // Appletun - new(Nest150,4,4,4,SH) { Species = 842, Ability = A2, CanGigantamax = true }, // Appletun - new(Nest155,4,4,4,SH) { Species = 745, Ability = A4, Form = 1 }, // Lycanroc-1 - new(Nest160,3,4,5,SH) { Species = 059, Ability = A2 }, // Arcanine - new(Nest161,0,1,1,SH) { Species = 140, Ability = A3 }, // Kabuto - new(Nest161,1,2,2,SH) { Species = 140, Ability = A3 }, // Kabuto - new(Nest161,2,3,3,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 - new(Nest161,3,4,4,SH) { Species = 141, Ability = A4 }, // Kabutops - new(Nest161,3,4,4,SH) { Species = 550, Ability = A4, Form = 1 }, // Basculin-1 - new(Nest162,0,1,2,SH) { Species = 140, Ability = A2 }, // Kabuto - new(Nest162,2,3,4,SH) { Species = 550, Ability = A2, Form = 1 }, // Basculin-1 - new(Nest162,3,4,5,SH) { Species = 141, Ability = A2 }, // Kabutops - new(Nest164,3,4,5,SH) { Species = 849, Ability = A2, Form = 1 }, // Toxtricity-1 - new(Nest164,4,4,5,SH) { Species = 849, Ability = A2, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(Nest165,0,1,1,SH) { Species = 345, Ability = A3 }, // Lileep - new(Nest165,2,3,3,SH) { Species = 345, Ability = A3 }, // Lileep - new(Nest165,3,4,4,SH) { Species = 346, Ability = A4 }, // Cradily - new(Nest165,4,4,4,SH) { Species = 348, Ability = A4 }, // Armaldo - new(Nest166,4,4,5,SH) { Species = 346, Ability = A2 }, // Cradily - new(Nest167,0,1,1,SH) { Species = 220, Ability = A3 }, // Swinub - //new(Nest169,0,1,1,SH) { Species = 875, Ability = A3 }, // Eiscue - new(Nest169,2,2,2,SH) { Species = 875, Ability = A3 }, // Eiscue - new(Nest170,4,4,5,SH) { Species = 131, Ability = A2, CanGigantamax = true }, // Lapras - new(Nest172,3,4,5,SH) { Species = 107, Ability = A2 }, // Hitmonchan - new(Nest172,4,4,5,SH) { Species = 068, Ability = A2 }, // Machamp - new(Nest173,0,1,1,SH) { Species = 032, Ability = A3 }, // Nidoran♂ - new(Nest173,3,4,4,SH) { Species = 034, Ability = A4 }, // Nidoking - new(Nest174,1,2,3,SH) { Species = 033, Ability = A2 }, // Nidorino - new(Nest177,0,1,1,SH) { Species = 077, Ability = A3, Form = 1 }, // Ponyta-1 - new(Nest177,2,3,3,SH) { Species = 578, Ability = A3 }, // Duosion - new(Nest177,3,4,4,SH) { Species = 876, Ability = A4, Gender = 1, Form = 1 }, // Indeedee-1 - new(Nest177,3,4,4,SH) { Species = 579, Ability = A4 }, // Reuniclus - new(Nest177,3,4,4,SH) { Species = 078, Ability = A4, Form = 1 }, // Rapidash-1 - new(Nest178,0,1,2,SH) { Species = 876, Ability = A2, Gender = 1, Form = 1 }, // Indeedee-1 - new(Nest178,0,1,2,SH) { Species = 577, Ability = A2 }, // Solosis - new(Nest178,2,3,4,SH) { Species = 876, Ability = A2, Gender = 1, Form = 1 }, // Indeedee-1 - new(Nest178,3,4,5,SH) { Species = 876, Ability = A2, Gender = 1, Form = 1 }, // Indeedee-1 - new(Nest178,3,4,5,SH) { Species = 579, Ability = A2 }, // Reuniclus - new(Nest178,4,4,5,SH) { Species = 579, Ability = A2 }, // Reuniclus - new(Nest179,0,1,1,SH) { Species = 837, Ability = A3 }, // Rolycoly - new(Nest179,1,2,2,SH) { Species = 838, Ability = A3 }, // Carkol - new(Nest179,2,3,3,SH) { Species = 838, Ability = A3 }, // Carkol - new(Nest179,3,4,4,SH) { Species = 302, Ability = A4 }, // Sableye - new(Nest180,0,1,2,SH) { Species = 302, Ability = A2 }, // Sableye - new(Nest180,2,3,4,SH) { Species = 302, Ability = A2 }, // Sableye - new(Nest180,3,4,5,SH) { Species = 302, Ability = A2 }, // Sableye - new(Nest180,4,4,5,SH) { Species = 839, Ability = A2 }, // Coalossal - new(Nest181,3,4,4,SH) { Species = 302, Ability = A4 }, // Sableye - new(Nest182,1,2,3,SH) { Species = 222, Ability = A2, Form = 1 }, // Corsola-1 - new(Nest182,2,3,4,SH) { Species = 302, Ability = A2 }, // Sableye - new(Nest182,3,4,5,SH) { Species = 864, Ability = A2 }, // Cursola - new(Nest182,3,4,5,SH) { Species = 302, Ability = A2 }, // Sableye - new(Nest182,4,4,5,SH) { Species = 094, Ability = A2, CanGigantamax = true }, // Gengar - new(Nest183,0,1,1,SH) { Species = 443, Ability = A3 }, // Gible - new(Nest183,1,2,2,SH) { Species = 443, Ability = A3 }, // Gible - new(Nest183,2,3,3,SH) { Species = 444, Ability = A3 }, // Gabite - new(Nest183,3,4,4,SH) { Species = 444, Ability = A4 }, // Gabite - new(Nest183,4,4,4,SH) { Species = 445, Ability = A4 }, // Garchomp - new(Nest184,0,1,2,SH) { Species = 443, Ability = A2 }, // Gible - new(Nest184,1,2,3,SH) { Species = 780, Ability = A2 }, // Drampa - new(Nest184,2,3,4,SH) { Species = 444, Ability = A2 }, // Gabite - new(Nest184,3,4,5,SH) { Species = 445, Ability = A2 }, // Garchomp - new(Nest184,4,4,5,SH) { Species = 445, Ability = A2 }, // Garchomp - new(Nest192,3,4,5,SH) { Species = 869, Ability = A2 }, // Alcremie - new(Nest195,0,1,1,SH) { Species = 140, Ability = A3 }, // Kabuto - new(Nest195,0,1,1,SH) { Species = 345, Ability = A3 }, // Lileep - new(Nest195,3,4,4,SH) { Species = 141, Ability = A4 }, // Kabutops - new(Nest195,3,4,4,SH) { Species = 346, Ability = A4 }, // Cradily - new(Nest195,3,4,4,SH) { Species = 699, Ability = A4 }, // Aurorus - new(Nest195,4,4,4,SH) { Species = 697, Ability = A4 }, // Tyrantrum - new(Nest196,0,1,2,SH) { Species = 140, Ability = A2 }, // Kabuto - new(Nest196,2,2,2,SH) { Species = 141, Ability = A2 }, // Kabutops - new(Nest196,2,2,3,SH) { Species = 883, Ability = A2 }, // Arctovish - new(Nest196,2,3,4,SH) { Species = 882, Ability = A2 }, // Dracovish - new(Nest196,3,3,4,SH) { Species = 880, Ability = A2 }, // Dracozolt - }; - #endregion - } + internal static readonly EncounterStatic8N[] Nest_SH = + { + new(Nest000,0,1,1,SH) { Species = 453, Ability = A3 }, // Croagunk + new(Nest000,2,3,3,SH) { Species = 107, Ability = A3 }, // Hitmonchan + new(Nest000,2,4,4,SH) { Species = 106, Ability = A3 }, // Hitmonlee + new(Nest000,2,4,4,SH) { Species = 454, Ability = A3 }, // Toxicroak + new(Nest000,3,4,4,SH) { Species = 237, Ability = A4 }, // Hitmontop + new(Nest000,4,4,4,SH) { Species = 534, Ability = A4 }, // Conkeldurr + new(Nest001,0,1,1,SH) { Species = 577, Ability = A3 }, // Solosis + new(Nest001,2,3,3,SH) { Species = 678, Ability = A3, Gender = 1, Form = 1 }, // Meowstic-1 + new(Nest001,2,3,3,SH) { Species = 578, Ability = A3 }, // Duosion + new(Nest001,3,4,4,SH) { Species = 579, Ability = A4 }, // Reuniclus + new(Nest001,4,4,4,SH) { Species = 337, Ability = A4 }, // Lunatone + new(Nest002,0,0,1,SH) { Species = 688, Ability = A3 }, // Binacle + new(Nest002,0,1,1,SH) { Species = 524, Ability = A3 }, // Roggenrola + new(Nest002,3,4,4,SH) { Species = 526, Ability = A4 }, // Gigalith + new(Nest002,4,4,4,SH) { Species = 558, Ability = A4 }, // Crustle + //new(Nest006,0,1,2,SH) { Species = 223, Ability = A3 }, // Remoraid + //new(Nest006,0,1,2,SH) { Species = 170, Ability = A3 }, // Chinchou + new(Nest006,2,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 + new(Nest007,2,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 + new(Nest008,1,1,1,SH) { Species = 090, Ability = A3 }, // Shellder + new(Nest009,1,1,2,SH) { Species = 759, Ability = A3 }, // Stufful + new(Nest009,1,2,2,SH) { Species = 538, Ability = A3 }, // Throh + new(Nest009,3,4,4,SH) { Species = 760, Ability = A4 }, // Bewear + new(Nest011,4,4,4,SH) { Species = 208, Ability = A4 }, // Steelix + new(Nest012,0,1,2,SH) { Species = 177, Ability = A3 }, // Natu + new(Nest012,0,1,2,SH) { Species = 856, Ability = A3 }, // Hatenna + new(Nest012,1,1,2,SH) { Species = 077, Ability = A3, Form = 1 }, // Ponyta-1 + new(Nest012,1,3,3,SH) { Species = 857, Ability = A3 }, // Hattrem + new(Nest012,2,4,4,SH) { Species = 876, Ability = A3, Gender = 1, Form = 1 }, // Indeedee-1 + new(Nest012,3,4,4,SH) { Species = 765, Ability = A4 }, // Oranguru + new(Nest012,4,4,4,SH) { Species = 078, Ability = A4, Form = 1 }, // Rapidash-1 + new(Nest013,2,4,4,SH) { Species = 876, Ability = A3, Gender = 1, Form = 1 }, // Indeedee-1 + new(Nest014,0,0,1,SH) { Species = 557, Ability = A3 }, // Dwebble + new(Nest014,0,1,1,SH) { Species = 524, Ability = A3 }, // Roggenrola + new(Nest014,2,4,4,SH) { Species = 839, Ability = A3 }, // Coalossal + new(Nest014,3,4,4,SH) { Species = 526, Ability = A4 }, // Gigalith + new(Nest014,4,4,4,SH) { Species = 095, Ability = A4 }, // Onix + new(Nest016,0,1,1,SH) { Species = 328, Ability = A3 }, // Trapinch + new(Nest016,1,1,1,SH) { Species = 220, Ability = A3 }, // Swinub + new(Nest016,2,3,3,SH) { Species = 618, Ability = A3, Form = 1 }, // Stunfisk-1 + new(Nest016,2,4,4,SH) { Species = 329, Ability = A3 }, // Vibrava + new(Nest016,3,4,4,SH) { Species = 330, Ability = A4 }, // Flygon + new(Nest016,4,4,4,SH) { Species = 450, Ability = A4 }, // Hippowdon + new(Nest017,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe + new(Nest017,1,1,1,SH) { Species = 631, Ability = A3 }, // Heatmor + new(Nest017,1,2,2,SH) { Species = 608, Ability = A3 }, // Lampent + new(Nest017,2,3,3,SH) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle + new(Nest017,2,4,4,SH) { Species = 059, Ability = A3 }, // Arcanine + new(Nest017,3,4,4,SH) { Species = 631, Ability = A4 }, // Heatmor + new(Nest017,4,4,4,SH) { Species = 851, Ability = A4 }, // Centiskorch + new(Nest017,4,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine + new(Nest018,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe + new(Nest018,0,1,1,SH) { Species = 058, Ability = A3 }, // Growlithe + new(Nest018,1,2,2,SH) { Species = 608, Ability = A3 }, // Lampent + new(Nest018,2,3,3,SH) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle + new(Nest018,2,4,4,SH) { Species = 059, Ability = A3 }, // Arcanine + new(Nest018,2,4,4,SH) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle + new(Nest018,3,4,4,SH) { Species = 324, Ability = A4 }, // Torkoal + new(Nest018,4,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine + new(Nest019,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe + new(Nest019,1,1,1,SH) { Species = 324, Ability = A3 }, // Torkoal + new(Nest019,1,2,2,SH) { Species = 838, Ability = A3 }, // Carkol + new(Nest019,2,3,3,SH) { Species = 758, Ability = A3, Gender = 1 }, // Salazzle + new(Nest019,2,4,4,SH) { Species = 059, Ability = A3 }, // Arcanine + new(Nest019,4,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine + new(Nest022,0,1,1,SH) { Species = 225, Ability = A3 }, // Delibird + new(Nest022,4,4,4,SH) { Species = 875, Ability = A4 }, // Eiscue + new(Nest025,0,0,1,SH) { Species = 270, Ability = A3 }, // Lotad + new(Nest025,1,1,2,SH) { Species = 271, Ability = A3 }, // Lombre + new(Nest025,2,4,4,SH) { Species = 272, Ability = A3 }, // Ludicolo + new(Nest026,4,4,4,SH) { Species = 842, Ability = A4 }, // Appletun + new(Nest028,0,1,1,SH) { Species = 043, Ability = A3 }, // Oddish + new(Nest028,1,1,1,SH) { Species = 747, Ability = A3 }, // Mareanie + new(Nest028,3,4,4,SH) { Species = 435, Ability = A4 }, // Skuntank + new(Nest028,4,4,4,SH) { Species = 748, Ability = A4 }, // Toxapex + new(Nest030,0,1,1,SH) { Species = 629, Ability = A3 }, // Vullaby + new(Nest030,4,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz + new(Nest032,0,1,1,SH) { Species = 682, Ability = A3 }, // Spritzee + new(Nest032,4,4,4,SH) { Species = 683, Ability = A4 }, // Aromatisse + new(Nest033,4,4,4,SH) { Species = 078, Ability = A4, Form = 1 }, // Rapidash-1 + new(Nest034,4,4,4,SH) { Species = 302, Ability = A4 }, // Sableye + new(Nest035,1,1,2,SH) { Species = 629, Ability = A3 }, // Vullaby + new(Nest035,3,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz + new(Nest035,4,4,4,SH) { Species = 248, Ability = A4 }, // Tyranitar + new(Nest036,0,0,1,SH) { Species = 610, Ability = A3 }, // Axew + new(Nest036,0,1,1,SH) { Species = 328, Ability = A3 }, // Trapinch + new(Nest036,1,1,2,SH) { Species = 704, Ability = A3 }, // Goomy + new(Nest036,2,3,3,SH) { Species = 611, Ability = A3 }, // Fraxure + new(Nest036,2,4,4,SH) { Species = 705, Ability = A3 }, // Sliggoo + new(Nest036,2,4,4,SH) { Species = 330, Ability = A3 }, // Flygon + new(Nest036,3,4,4,SH) { Species = 612, Ability = A4 }, // Haxorus + new(Nest036,4,4,4,SH) { Species = 780, Ability = A4 }, // Drampa + new(Nest036,4,4,4,SH) { Species = 706, Ability = A4 }, // Goodra + //new(Nest037,0,1,1,SH) { Species = 704, Ability = A3 }, // Goomy + new(Nest037,2,4,4,SH) { Species = 705, Ability = A3 }, // Sliggoo + new(Nest037,3,4,4,SH) { Species = 706, Ability = A4 }, // Goodra + new(Nest037,4,4,4,SH) { Species = 842, Ability = A4 }, // Appletun + new(Nest039,1,1,2,SH) { Species = 876, Ability = A3, Gender = 1, Form = 1 }, // Indeedee-1 + new(Nest039,4,4,4,SH) { Species = 765, Ability = A4 }, // Oranguru + //new(Nest040,0,1,1,SH) { Species = 536, Ability = A3 }, // Palpitoad + //new(Nest040,1,1,2,SH) { Species = 747, Ability = A3 }, // Mareanie + new(Nest040,2,3,3,SH) { Species = 748, Ability = A3 }, // Toxapex + new(Nest040,2,4,4,SH) { Species = 091, Ability = A3 }, // Cloyster + new(Nest040,3,4,4,SH) { Species = 746, Ability = A4 }, // Wishiwashi + new(Nest040,4,4,4,SH) { Species = 537, Ability = A4 }, // Seismitoad + new(Nest042,1,3,3,SH) { Species = 222, Ability = A3, Form = 1 }, // Corsola-1 + new(Nest042,4,4,4,SH) { Species = 302, Ability = A3 }, // Sableye + new(Nest042,3,4,4,SH) { Species = 867, Ability = A4 }, // Runerigus + new(Nest042,3,4,4,SH) { Species = 864, Ability = A4 }, // Cursola + new(Nest043,2,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 + new(Nest044,1,2,2,SH) { Species = 600, Ability = A3 }, // Klang + new(Nest044,2,3,3,SH) { Species = 632, Ability = A3 }, // Durant + new(Nest045,0,1,1,SH) { Species = 616, Ability = A3 }, // Shelmet + new(Nest045,1,2,2,SH) { Species = 588, Ability = A3 }, // Karrablast + new(Nest045,4,4,4,SH) { Species = 617, Ability = A4 }, // Accelgor + new(Nest046,1,3,3,SH) { Species = 222, Ability = A3, Form = 1 }, // Corsola-1 + new(Nest046,2,4,4,SH) { Species = 302, Ability = A3 }, // Sableye + new(Nest046,3,4,4,SH) { Species = 864, Ability = A4 }, // Cursola + new(Nest047,0,1,1,SH) { Species = 453, Ability = A3 }, // Croagunk + new(Nest047,2,4,4,SH) { Species = 454, Ability = A3 }, // Toxicroak + new(Nest047,3,4,4,SH) { Species = 701, Ability = A4 }, // Hawlucha + new(Nest050,0,1,1,SH) { Species = 524, Ability = A3 }, // Roggenrola + new(Nest050,1,2,2,SH) { Species = 246, Ability = A3 }, // Larvitar + new(Nest050,2,3,3,SH) { Species = 247, Ability = A3 }, // Pupitar + new(Nest050,4,4,4,SH) { Species = 248, Ability = A4 }, // Tyranitar + new(Nest052,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe + new(Nest052,1,2,2,SH) { Species = 631, Ability = A3 }, // Heatmor + new(Nest052,3,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine + new(Nest053,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe + new(Nest053,1,2,2,SH) { Species = 631, Ability = A3 }, // Heatmor + new(Nest053,2,3,3,SH) { Species = 608, Ability = A3 }, // Lampent + new(Nest053,3,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine + new(Nest053,4,4,4,SH) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle + new(Nest054,0,0,1,SH) { Species = 613, Ability = A3 }, // Cubchoo + new(Nest054,4,4,4,SH) { Species = 875, Ability = A4 }, // Eiscue + new(Nest057,0,0,1,SH) { Species = 270, Ability = A3 }, // Lotad + new(Nest057,1,2,2,SH) { Species = 271, Ability = A3 }, // Lombre + new(Nest057,2,4,4,SH) { Species = 272, Ability = A3 }, // Ludicolo + new(Nest057,4,4,4,SH) { Species = 842, Ability = A4 }, // Appletun + new(Nest058,0,0,1,SH) { Species = 270, Ability = A3 }, // Lotad + new(Nest058,1,2,2,SH) { Species = 271, Ability = A3 }, // Lombre + new(Nest058,4,4,4,SH) { Species = 272, Ability = A4 }, // Ludicolo + new(Nest059,1,2,2,SH) { Species = 757, Ability = A3 }, // Salandit + new(Nest059,4,4,4,SH) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle + new(Nest061,2,4,4,SH) { Species = 078, Ability = A3, Form = 1 }, // Rapidash-1 + new(Nest062,0,1,1,SH) { Species = 629, Ability = A3 }, // Vullaby + new(Nest062,3,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz + new(Nest062,4,4,4,SH) { Species = 248, Ability = A4 }, // Tyranitar + new(Nest063,0,0,1,SH) { Species = 610, Ability = A3 }, // Axew + new(Nest063,0,1,1,SH) { Species = 328, Ability = A3 }, // Trapinch + new(Nest063,0,1,1,SH) { Species = 704, Ability = A3 }, // Goomy + new(Nest063,1,2,2,SH) { Species = 329, Ability = A3 }, // Vibrava + new(Nest063,2,4,4,SH) { Species = 705, Ability = A3 }, // Sliggoo + new(Nest063,2,4,4,SH) { Species = 780, Ability = A3 }, // Drampa + new(Nest063,3,4,4,SH) { Species = 706, Ability = A4 }, // Goodra + new(Nest063,4,4,4,SH) { Species = 330, Ability = A4 }, // Flygon + new(Nest064,3,4,4,SH) { Species = 765, Ability = A4 }, // Oranguru + new(Nest064,3,4,4,SH) { Species = 876, Ability = A4, Gender = 1, Form = 1 }, // Indeedee-1 + new(Nest066,2,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 + new(Nest070,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe + new(Nest070,0,1,1,SH) { Species = 058, Ability = A3 }, // Growlithe + new(Nest070,1,2,2,SH) { Species = 631, Ability = A3 }, // Heatmor + new(Nest070,2,3,3,SH) { Species = 608, Ability = A3 }, // Lampent + //new(Nest073,0,0,1,SH) { Species = 682, Ability = A3 }, // Spritzee + new(Nest073,2,4,4,SH) { Species = 683, Ability = A3 }, // Aromatisse + new(Nest075,2,4,4,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 + //new(Nest076,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe + new(Nest076,2,2,2,SH) { Species = 631, Ability = A3 }, // Heatmor + new(Nest076,3,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine + new(Nest077,1,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 + new(Nest078,0,0,1,SH) { Species = 270, Ability = A3 }, // Lotad + new(Nest078,1,2,2,SH) { Species = 271, Ability = A3 }, // Lombre + new(Nest078,2,4,4,SH) { Species = 272, Ability = A3 }, // Ludicolo + new(Nest078,4,4,4,SH) { Species = 842, Ability = A4, CanGigantamax = true }, // Appletun + new(Nest079,0,0,1,SH) { Species = 058, Ability = A3 }, // Growlithe + new(Nest079,3,4,4,SH) { Species = 059, Ability = A4 }, // Arcanine + new(Nest080,0,0,1,SH) { Species = 679, Ability = A3 }, // Honedge + new(Nest080,0,0,1,SH) { Species = 562, Ability = A3, Form = 1 }, // Yamask-1 + new(Nest080,0,1,1,SH) { Species = 854, Ability = A3 }, // Sinistea + new(Nest080,0,1,1,SH) { Species = 092, Ability = A3 }, // Gastly + new(Nest080,1,2,2,SH) { Species = 680, Ability = A3 }, // Doublade + new(Nest080,1,3,3,SH) { Species = 222, Ability = A3, Form = 1 }, // Corsola-1 + new(Nest080,2,3,3,SH) { Species = 093, Ability = A3 }, // Haunter + new(Nest080,2,4,4,SH) { Species = 302, Ability = A3 }, // Sableye + new(Nest080,3,4,4,SH) { Species = 855, Ability = A4 }, // Polteageist + new(Nest080,3,4,4,SH) { Species = 864, Ability = A4 }, // Cursola + new(Nest080,4,4,4,SH) { Species = 867, Ability = A4 }, // Runerigus + new(Nest080,4,4,4,SH) { Species = 094, Ability = A4, CanGigantamax = true }, // Gengar + new(Nest081,0,0,1,SH) { Species = 077, Ability = A3, Form = 1 }, // Ponyta-1 + new(Nest081,2,4,4,SH) { Species = 078, Ability = A3, Form = 1 }, // Rapidash-1 + new(Nest082,0,0,1,SH) { Species = 582, Ability = A3 }, // Vanillite + new(Nest082,0,0,1,SH) { Species = 613, Ability = A3 }, // Cubchoo + new(Nest082,0,1,1,SH) { Species = 122, Ability = A3, Form = 1 }, // Mr. Mime-1 + new(Nest082,0,1,1,SH) { Species = 712, Ability = A3 }, // Bergmite + new(Nest082,1,2,2,SH) { Species = 361, Ability = A3 }, // Snorunt + new(Nest082,1,2,2,SH) { Species = 225, Ability = A3 }, // Delibird + new(Nest082,2,3,3,SH) { Species = 713, Ability = A3 }, // Avalugg + new(Nest082,2,4,4,SH) { Species = 362, Ability = A3 }, // Glalie + new(Nest082,3,4,4,SH) { Species = 584, Ability = A4 }, // Vanilluxe + new(Nest082,3,4,4,SH) { Species = 866, Ability = A4 }, // Mr. Rime + new(Nest082,4,4,4,SH) { Species = 875, Ability = A4 }, // Eiscue + new(Nest082,4,4,4,SH) { Species = 131, Ability = A4, CanGigantamax = true }, // Lapras + new(Nest083,1,2,2,SH) { Species = 600, Ability = A3 }, // Klang + new(Nest083,2,3,3,SH) { Species = 632, Ability = A3 }, // Durant + new(Nest085,1,2,2,SH) { Species = 757, Ability = A3 }, // Salandit + new(Nest085,4,4,4,SH) { Species = 758, Ability = A4, Gender = 1 }, // Salazzle + new(Nest086,0,0,1,SH) { Species = 682, Ability = A3 }, // Spritzee + new(Nest086,2,3,3,SH) { Species = 683, Ability = A3 }, // Aromatisse + new(Nest087,0,1,1,SH) { Species = 629, Ability = A3 }, // Vullaby + new(Nest087,3,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz + new(Nest087,4,4,4,SH) { Species = 248, Ability = A4 }, // Tyranitar + new(Nest089,0,1,1,SH) { Species = 616, Ability = A3 }, // Shelmet + new(Nest089,1,2,2,SH) { Species = 588, Ability = A3 }, // Karrablast + new(Nest089,4,4,4,SH) { Species = 617, Ability = A4 }, // Accelgor + new(Nest090,1,2,2,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 + new(Nest091,0,1,1,SH) { Species = 616, Ability = A3 }, // Shelmet + new(Nest091,1,2,2,SH) { Species = 588, Ability = A3 }, // Karrablast + new(Nest091,4,4,4,SH) { Species = 617, Ability = A4 }, // Accelgor + new(Nest106,1,2,2,SH) { Species = 629, Ability = A3 }, // Vullaby + new(Nest106,3,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz + new(Nest106,4,4,4,SH) { Species = 630, Ability = A4 }, // Mandibuzz + new(Nest107,1,2,2,SH) { Species = 629, Ability = A2 }, // Vullaby + new(Nest107,4,4,4,SH) { Species = 630, Ability = A2 }, // Mandibuzz + new(Nest108,0,1,1,SH) { Species = 214, Ability = A3 }, // Heracross + new(Nest108,1,2,2,SH) { Species = 214, Ability = A3 }, // Heracross + new(Nest108,2,3,3,SH) { Species = 214, Ability = A3 }, // Heracross + new(Nest108,3,4,4,SH) { Species = 214, Ability = A4 }, // Heracross + new(Nest109,0,1,1,SH) { Species = 214, Ability = A2 }, // Heracross + new(Nest109,4,4,4,SH) { Species = 214, Ability = A2 }, // Heracross + new(Nest113,4,4,4,SH) { Species = 059, Ability = A2 }, // Arcanine + new(Nest114,4,4,4,SH) { Species = 745, Ability = A4, Form = 1 }, // Lycanroc-1 + new(Nest115,3,4,4,SH) { Species = 745, Ability = A2, Form = 1 }, // Lycanroc-1 + new(Nest116,2,3,3,SH) { Species = 765, Ability = A3 }, // Oranguru + new(Nest117,2,3,3,SH) { Species = 765, Ability = A2 }, // Oranguru + new(Nest117,3,4,4,SH) { Species = 876, Ability = A2, Gender = 1, Form = 1 }, // Indeedee-1 + new(Nest117,4,4,4,SH) { Species = 678, Ability = A2, Gender = 1, Form = 1 }, // Meowstic-1 + new(Nest122,1,2,2,SH) { Species = 453, Ability = A3 }, // Croagunk + new(Nest122,2,3,3,SH) { Species = 853, Ability = A3 }, // Grapploct + new(Nest122,2,3,3,SH) { Species = 454, Ability = A3 }, // Toxicroak + new(Nest122,3,4,4,SH) { Species = 454, Ability = A4 }, // Toxicroak + new(Nest123,0,1,1,SH) { Species = 453, Ability = A2 }, // Croagunk + new(Nest123,1,2,2,SH) { Species = 538, Ability = A2 }, // Throh + new(Nest123,2,3,3,SH) { Species = 620, Ability = A2 }, // Mienshao + new(Nest123,3,4,4,SH) { Species = 538, Ability = A2 }, // Throh + new(Nest123,3,4,4,SH) { Species = 454, Ability = A2 }, // Toxicroak + new(Nest123,4,4,4,SH) { Species = 870, Ability = A2 }, // Falinks + new(Nest127,4,4,4,SH) { Species = 864, Ability = A2 }, // Cursola + //new(Nest130,0,1,1,SH) { Species = 704, Ability = A3 }, // Goomy + new(Nest130,2,3,3,SH) { Species = 705, Ability = A3 }, // Sliggoo + new(Nest130,3,4,4,SH) { Species = 842, Ability = A4 }, // Appletun + new(Nest130,4,4,4,SH) { Species = 706, Ability = A4 }, // Goodra + new(Nest131,2,2,2,SH) { Species = 780, Ability = A2 }, // Drampa + new(Nest131,2,2,2,SH) { Species = 704, Ability = A2 }, // Goomy + new(Nest131,2,3,3,SH) { Species = 705, Ability = A2 }, // Sliggoo + new(Nest131,3,4,4,SH) { Species = 780, Ability = A2 }, // Drampa + new(Nest131,4,4,4,SH) { Species = 706, Ability = A2 }, // Goodra + new(Nest135,4,4,4,SH) { Species = 550, Ability = A2, Form = 1 }, // Basculin-1 + //new(Nest138,0,1,1,SH) { Species = 690, Ability = A3 }, // Skrelp + new(Nest138,2,2,2,SH) { Species = 690, Ability = A3 }, // Skrelp + new(Nest138,4,4,4,SH) { Species = 691, Ability = A4 }, // Dragalge + //new(Nest139,0,1,1,SH) { Species = 690, Ability = A2 }, // Skrelp + new(Nest139,3,4,4,SH) { Species = 691, Ability = A2 }, // Dragalge + new(Nest139,4,4,4,SH) { Species = 691, Ability = A2 }, // Dragalge + new(Nest147,1,2,2,SH) { Species = 550, Ability = A2, Form = 1 }, // Basculin-1 + new(Nest147,3,4,4,SH) { Species = 550, Ability = A2, Form = 1 }, // Basculin-1 + new(Nest148,1,2,2,SH) { Species = 214, Ability = A2 }, // Heracross + new(Nest150,2,3,3,SH) { Species = 842, Ability = A2 }, // Appletun + new(Nest150,3,4,4,SH) { Species = 842, Ability = A2 }, // Appletun + new(Nest150,4,4,4,SH) { Species = 842, Ability = A2, CanGigantamax = true }, // Appletun + new(Nest155,4,4,4,SH) { Species = 745, Ability = A4, Form = 1 }, // Lycanroc-1 + new(Nest160,3,4,5,SH) { Species = 059, Ability = A2 }, // Arcanine + new(Nest161,0,1,1,SH) { Species = 140, Ability = A3 }, // Kabuto + new(Nest161,1,2,2,SH) { Species = 140, Ability = A3 }, // Kabuto + new(Nest161,2,3,3,SH) { Species = 550, Ability = A3, Form = 1 }, // Basculin-1 + new(Nest161,3,4,4,SH) { Species = 141, Ability = A4 }, // Kabutops + new(Nest161,3,4,4,SH) { Species = 550, Ability = A4, Form = 1 }, // Basculin-1 + new(Nest162,0,1,2,SH) { Species = 140, Ability = A2 }, // Kabuto + new(Nest162,2,3,4,SH) { Species = 550, Ability = A2, Form = 1 }, // Basculin-1 + new(Nest162,3,4,5,SH) { Species = 141, Ability = A2 }, // Kabutops + new(Nest164,3,4,5,SH) { Species = 849, Ability = A2, Form = 1 }, // Toxtricity-1 + new(Nest164,4,4,5,SH) { Species = 849, Ability = A2, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(Nest165,0,1,1,SH) { Species = 345, Ability = A3 }, // Lileep + new(Nest165,2,3,3,SH) { Species = 345, Ability = A3 }, // Lileep + new(Nest165,3,4,4,SH) { Species = 346, Ability = A4 }, // Cradily + new(Nest165,4,4,4,SH) { Species = 348, Ability = A4 }, // Armaldo + new(Nest166,4,4,5,SH) { Species = 346, Ability = A2 }, // Cradily + new(Nest167,0,1,1,SH) { Species = 220, Ability = A3 }, // Swinub + //new(Nest169,0,1,1,SH) { Species = 875, Ability = A3 }, // Eiscue + new(Nest169,2,2,2,SH) { Species = 875, Ability = A3 }, // Eiscue + new(Nest170,4,4,5,SH) { Species = 131, Ability = A2, CanGigantamax = true }, // Lapras + new(Nest172,3,4,5,SH) { Species = 107, Ability = A2 }, // Hitmonchan + new(Nest172,4,4,5,SH) { Species = 068, Ability = A2 }, // Machamp + new(Nest173,0,1,1,SH) { Species = 032, Ability = A3 }, // Nidoran♂ + new(Nest173,3,4,4,SH) { Species = 034, Ability = A4 }, // Nidoking + new(Nest174,1,2,3,SH) { Species = 033, Ability = A2 }, // Nidorino + new(Nest177,0,1,1,SH) { Species = 077, Ability = A3, Form = 1 }, // Ponyta-1 + new(Nest177,2,3,3,SH) { Species = 578, Ability = A3 }, // Duosion + new(Nest177,3,4,4,SH) { Species = 876, Ability = A4, Gender = 1, Form = 1 }, // Indeedee-1 + new(Nest177,3,4,4,SH) { Species = 579, Ability = A4 }, // Reuniclus + new(Nest177,3,4,4,SH) { Species = 078, Ability = A4, Form = 1 }, // Rapidash-1 + new(Nest178,0,1,2,SH) { Species = 876, Ability = A2, Gender = 1, Form = 1 }, // Indeedee-1 + new(Nest178,0,1,2,SH) { Species = 577, Ability = A2 }, // Solosis + new(Nest178,2,3,4,SH) { Species = 876, Ability = A2, Gender = 1, Form = 1 }, // Indeedee-1 + new(Nest178,3,4,5,SH) { Species = 876, Ability = A2, Gender = 1, Form = 1 }, // Indeedee-1 + new(Nest178,3,4,5,SH) { Species = 579, Ability = A2 }, // Reuniclus + new(Nest178,4,4,5,SH) { Species = 579, Ability = A2 }, // Reuniclus + new(Nest179,0,1,1,SH) { Species = 837, Ability = A3 }, // Rolycoly + new(Nest179,1,2,2,SH) { Species = 838, Ability = A3 }, // Carkol + new(Nest179,2,3,3,SH) { Species = 838, Ability = A3 }, // Carkol + new(Nest179,3,4,4,SH) { Species = 302, Ability = A4 }, // Sableye + new(Nest180,0,1,2,SH) { Species = 302, Ability = A2 }, // Sableye + new(Nest180,2,3,4,SH) { Species = 302, Ability = A2 }, // Sableye + new(Nest180,3,4,5,SH) { Species = 302, Ability = A2 }, // Sableye + new(Nest180,4,4,5,SH) { Species = 839, Ability = A2 }, // Coalossal + new(Nest181,3,4,4,SH) { Species = 302, Ability = A4 }, // Sableye + new(Nest182,1,2,3,SH) { Species = 222, Ability = A2, Form = 1 }, // Corsola-1 + new(Nest182,2,3,4,SH) { Species = 302, Ability = A2 }, // Sableye + new(Nest182,3,4,5,SH) { Species = 864, Ability = A2 }, // Cursola + new(Nest182,3,4,5,SH) { Species = 302, Ability = A2 }, // Sableye + new(Nest182,4,4,5,SH) { Species = 094, Ability = A2, CanGigantamax = true }, // Gengar + new(Nest183,0,1,1,SH) { Species = 443, Ability = A3 }, // Gible + new(Nest183,1,2,2,SH) { Species = 443, Ability = A3 }, // Gible + new(Nest183,2,3,3,SH) { Species = 444, Ability = A3 }, // Gabite + new(Nest183,3,4,4,SH) { Species = 444, Ability = A4 }, // Gabite + new(Nest183,4,4,4,SH) { Species = 445, Ability = A4 }, // Garchomp + new(Nest184,0,1,2,SH) { Species = 443, Ability = A2 }, // Gible + new(Nest184,1,2,3,SH) { Species = 780, Ability = A2 }, // Drampa + new(Nest184,2,3,4,SH) { Species = 444, Ability = A2 }, // Gabite + new(Nest184,3,4,5,SH) { Species = 445, Ability = A2 }, // Garchomp + new(Nest184,4,4,5,SH) { Species = 445, Ability = A2 }, // Garchomp + new(Nest192,3,4,5,SH) { Species = 869, Ability = A2 }, // Alcremie + new(Nest195,0,1,1,SH) { Species = 140, Ability = A3 }, // Kabuto + new(Nest195,0,1,1,SH) { Species = 345, Ability = A3 }, // Lileep + new(Nest195,3,4,4,SH) { Species = 141, Ability = A4 }, // Kabutops + new(Nest195,3,4,4,SH) { Species = 346, Ability = A4 }, // Cradily + new(Nest195,3,4,4,SH) { Species = 699, Ability = A4 }, // Aurorus + new(Nest195,4,4,4,SH) { Species = 697, Ability = A4 }, // Tyrantrum + new(Nest196,0,1,2,SH) { Species = 140, Ability = A2 }, // Kabuto + new(Nest196,2,2,2,SH) { Species = 141, Ability = A2 }, // Kabutops + new(Nest196,2,2,3,SH) { Species = 883, Ability = A2 }, // Arctovish + new(Nest196,2,3,4,SH) { Species = 882, Ability = A2 }, // Dracovish + new(Nest196,3,3,4,SH) { Species = 880, Ability = A2 }, // Dracozolt + }; + #endregion } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters8NestCrystal.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters8NestCrystal.cs index c977edb01..564438d57 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters8NestCrystal.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters8NestCrystal.cs @@ -1,27 +1,26 @@ -using static PKHeX.Core.GameVersion; +using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +// Dynamax Crystal Distribution Nest Encounters (BCAT) +internal static partial class Encounters8Nest { - // Dynamax Crystal Distribution Nest Encounters (BCAT) - internal static partial class Encounters8Nest + #region Dynamax Crystal Distributions + internal static readonly EncounterStatic8NC[] Crystal_SWSH = { - #region Dynamax Crystal Distributions - internal static readonly EncounterStatic8NC[] Crystal_SWSH = - { - new(SWSH) { Species = 782, Level = 16, Ability = A3, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 2, Moves = new[] {033,029,525,043} }, // ★And458 Jangmo-o - new(SWSH) { Species = 246, Level = 16, Ability = A3, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 2, Moves = new[] {033,157,371,044} }, // ★And15 Larvitar - new(SWSH) { Species = 823, Level = 50, Ability = A2, Location = 126, IVs = new[] {31,31,31,-1,-1,31}, DynamaxLevel = 5, Moves = new[] {065,442,034,796}, CanGigantamax = true }, // ★And337 Gigantamax Corviknight - new(SWSH) { Species = 875, Level = 15, Ability = A3, Location = 126, IVs = new[] {31,31,-1,31,-1,-1}, DynamaxLevel = 2, Moves = new[] {181,311,054,556} }, // ★And603 Eiscue - new(SWSH) { Species = 874, Level = 15, Ability = A3, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 2, Moves = new[] {397,317,335,157} }, // ★And390 Stonjourner - new(SWSH) { Species = 879, Level = 35, Ability = A3, Location = 126, IVs = new[] {31,31,-1, 0,31,-1}, DynamaxLevel = 4, Moves = new[] {484,174,776,583}, CanGigantamax = true }, // ★Sgr6879 Gigantamax Copperajah - new(SWSH) { Species = 851, Level = 35, Ability = A2, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 5, Moves = new[] {680,679,489,438}, CanGigantamax = true }, // ★Sgr6859 Gigantamax Centiskorch - new(SW ) { Species = 842, Level = 40, Ability = A0, Location = 126, IVs = new[] {31,-1,31,-1,31,-1}, DynamaxLevel = 5, Moves = new[] {787,412,406,076}, CanGigantamax = true }, // ★Sgr6913 Gigantamax Appletun - new( SH) { Species = 841, Level = 40, Ability = A0, Location = 126, IVs = new[] {31,31,-1,31,-1,-1}, DynamaxLevel = 5, Moves = new[] {788,491,412,406}, CanGigantamax = true }, // ★Sgr6913 Gigantamax Flapple - new(SWSH) { Species = 844, Level = 40, Ability = A0, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 5, Moves = new[] {523,776,489,157}, CanGigantamax = true }, // ★Sgr7348 Gigantamax Sandaconda - new(SWSH) { Species = 884, Level = 40, Ability = A2, Location = 126, IVs = new[] {31,-1,-1,31,31,-1}, DynamaxLevel = 5, Moves = new[] {796,063,784,319}, CanGigantamax = true }, // ★Sgr7121 Gigantamax Duraludon - new(SWSH) { Species = 025, Level = 25, Ability = A2, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 5, Moves = new[] {606,273,104,085}, CanGigantamax = true }, // ★Sgr6746 Gigantamax Pikachu - new(SWSH) { Species = 133, Level = 25, Ability = A2, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 5, Moves = new[] {606,273,038,129}, CanGigantamax = true }, // ★Sgr7194 Gigantamax Eevee - }; - #endregion - } + new(SWSH) { Species = 782, Level = 16, Ability = A3, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 2, Moves = new[] {033,029,525,043} }, // ★And458 Jangmo-o + new(SWSH) { Species = 246, Level = 16, Ability = A3, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 2, Moves = new[] {033,157,371,044} }, // ★And15 Larvitar + new(SWSH) { Species = 823, Level = 50, Ability = A2, Location = 126, IVs = new[] {31,31,31,-1,-1,31}, DynamaxLevel = 5, Moves = new[] {065,442,034,796}, CanGigantamax = true }, // ★And337 Gigantamax Corviknight + new(SWSH) { Species = 875, Level = 15, Ability = A3, Location = 126, IVs = new[] {31,31,-1,31,-1,-1}, DynamaxLevel = 2, Moves = new[] {181,311,054,556} }, // ★And603 Eiscue + new(SWSH) { Species = 874, Level = 15, Ability = A3, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 2, Moves = new[] {397,317,335,157} }, // ★And390 Stonjourner + new(SWSH) { Species = 879, Level = 35, Ability = A3, Location = 126, IVs = new[] {31,31,-1, 0,31,-1}, DynamaxLevel = 4, Moves = new[] {484,174,776,583}, CanGigantamax = true }, // ★Sgr6879 Gigantamax Copperajah + new(SWSH) { Species = 851, Level = 35, Ability = A2, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 5, Moves = new[] {680,679,489,438}, CanGigantamax = true }, // ★Sgr6859 Gigantamax Centiskorch + new(SW ) { Species = 842, Level = 40, Ability = A0, Location = 126, IVs = new[] {31,-1,31,-1,31,-1}, DynamaxLevel = 5, Moves = new[] {787,412,406,076}, CanGigantamax = true }, // ★Sgr6913 Gigantamax Appletun + new( SH) { Species = 841, Level = 40, Ability = A0, Location = 126, IVs = new[] {31,31,-1,31,-1,-1}, DynamaxLevel = 5, Moves = new[] {788,491,412,406}, CanGigantamax = true }, // ★Sgr6913 Gigantamax Flapple + new(SWSH) { Species = 844, Level = 40, Ability = A0, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 5, Moves = new[] {523,776,489,157}, CanGigantamax = true }, // ★Sgr7348 Gigantamax Sandaconda + new(SWSH) { Species = 884, Level = 40, Ability = A2, Location = 126, IVs = new[] {31,-1,-1,31,31,-1}, DynamaxLevel = 5, Moves = new[] {796,063,784,319}, CanGigantamax = true }, // ★Sgr7121 Gigantamax Duraludon + new(SWSH) { Species = 025, Level = 25, Ability = A2, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 5, Moves = new[] {606,273,104,085}, CanGigantamax = true }, // ★Sgr6746 Gigantamax Pikachu + new(SWSH) { Species = 133, Level = 25, Ability = A2, Location = 126, IVs = new[] {31,31,31,-1,-1,-1}, DynamaxLevel = 5, Moves = new[] {606,273,038,129}, CanGigantamax = true }, // ★Sgr7194 Gigantamax Eevee + }; + #endregion } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistBase.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistBase.cs index 9402aa8b9..a25c042d9 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistBase.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistBase.cs @@ -1,665 +1,664 @@ using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +// Distribution Nest Encounters (BCAT) +internal static partial class Encounters8Nest { - // Distribution Nest Encounters (BCAT) - internal static partial class Encounters8Nest + /// + /// Nest distribution raids for available during base game era. + /// + internal static readonly EncounterStatic8ND[] Dist_Base = { - /// - /// Nest distribution raids for available during base game era. - /// - internal static readonly EncounterStatic8ND[] Dist_Base = - { - new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 252, 044, 010, 364 }, Index = 23, CanGigantamax = true }, // Meowth - new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 044, 010, 364 }, Index = 23, CanGigantamax = true }, // Meowth - new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 252, 044, 269, 154 }, Index = 23, CanGigantamax = true }, // Meowth - new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 044, 269, 154 }, Index = 23, CanGigantamax = true }, // Meowth - new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 252, 044, 417, 163 }, Index = 23, CanGigantamax = true }, // Meowth - new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 044, 417, 163 }, Index = 23, CanGigantamax = true }, // Meowth - new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 252, 583, 417, 163 }, Index = 23, CanGigantamax = true }, // Meowth - new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 417, 163 }, Index = 23, CanGigantamax = true }, // Meowth - new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 252, 583, 417, 034 }, Index = 23, CanGigantamax = true }, // Meowth - new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 417, 034 }, Index = 23, CanGigantamax = true }, // Meowth + new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 252, 044, 010, 364 }, Index = 23, CanGigantamax = true }, // Meowth + new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 044, 010, 364 }, Index = 23, CanGigantamax = true }, // Meowth + new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 252, 044, 269, 154 }, Index = 23, CanGigantamax = true }, // Meowth + new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 044, 269, 154 }, Index = 23, CanGigantamax = true }, // Meowth + new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 252, 044, 417, 163 }, Index = 23, CanGigantamax = true }, // Meowth + new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 044, 417, 163 }, Index = 23, CanGigantamax = true }, // Meowth + new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 252, 583, 417, 163 }, Index = 23, CanGigantamax = true }, // Meowth + new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 417, 163 }, Index = 23, CanGigantamax = true }, // Meowth + new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 252, 583, 417, 034 }, Index = 23, CanGigantamax = true }, // Meowth + new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 417, 034 }, Index = 23, CanGigantamax = true }, // Meowth - new(17,01,1) { Species = 133, Ability = A4, Moves = new[]{ 098, 270, 608, 028 }, Index = 22, CanGigantamax = true }, // Eevee - new(30,03,2) { Species = 133, Ability = A4, Moves = new[]{ 129, 270, 608, 044 }, Index = 22, CanGigantamax = true }, // Eevee - new(40,05,3) { Species = 133, Ability = A4, Moves = new[]{ 036, 270, 608, 044 }, Index = 22, CanGigantamax = true }, // Eevee - new(40,05,3) { Species = 133, Ability = A4, Moves = new[]{ 036, 270, 608, 231 }, Index = 22, CanGigantamax = true }, // Eevee - new(50,08,4) { Species = 133, Ability = A4, Moves = new[]{ 038, 270, 204, 044 }, Index = 22, CanGigantamax = true }, // Eevee - new(50,08,4) { Species = 133, Ability = A4, Moves = new[]{ 038, 203, 204, 231 }, Index = 22, CanGigantamax = true }, // Eevee - new(60,10,5) { Species = 133, Ability = A4, Moves = new[]{ 038, 270, 204, 231 }, Index = 22, CanGigantamax = true }, // Eevee - new(60,10,5) { Species = 133, Ability = A4, Moves = new[]{ 387, 203, 204, 231 }, Index = 22, CanGigantamax = true }, // Eevee + new(17,01,1) { Species = 133, Ability = A4, Moves = new[]{ 098, 270, 608, 028 }, Index = 22, CanGigantamax = true }, // Eevee + new(30,03,2) { Species = 133, Ability = A4, Moves = new[]{ 129, 270, 608, 044 }, Index = 22, CanGigantamax = true }, // Eevee + new(40,05,3) { Species = 133, Ability = A4, Moves = new[]{ 036, 270, 608, 044 }, Index = 22, CanGigantamax = true }, // Eevee + new(40,05,3) { Species = 133, Ability = A4, Moves = new[]{ 036, 270, 608, 231 }, Index = 22, CanGigantamax = true }, // Eevee + new(50,08,4) { Species = 133, Ability = A4, Moves = new[]{ 038, 270, 204, 044 }, Index = 22, CanGigantamax = true }, // Eevee + new(50,08,4) { Species = 133, Ability = A4, Moves = new[]{ 038, 203, 204, 231 }, Index = 22, CanGigantamax = true }, // Eevee + new(60,10,5) { Species = 133, Ability = A4, Moves = new[]{ 038, 270, 204, 231 }, Index = 22, CanGigantamax = true }, // Eevee + new(60,10,5) { Species = 133, Ability = A4, Moves = new[]{ 387, 203, 204, 231 }, Index = 22, CanGigantamax = true }, // Eevee - new(17,01,1) { Species = 025, Ability = A4, Moves = new[]{ 084, 104, 486, 364 }, Index = 21, CanGigantamax = true }, // Pikachu - new(30,03,2) { Species = 025, Ability = A4, Moves = new[]{ 021, 209, 097, 364 }, Index = 21, CanGigantamax = true }, // Pikachu - new(40,05,3) { Species = 025, Ability = A4, Moves = new[]{ 021, 113, 085, 364 }, Index = 21, CanGigantamax = true }, // Pikachu - new(50,08,4) { Species = 025, Ability = A4, Moves = new[]{ 087, 113, 085, 364 }, Index = 21, CanGigantamax = true }, // Pikachu - new(50,08,4) { Species = 025, Ability = A4, Moves = new[]{ 087, 113, 057, 085 }, Index = 21, CanGigantamax = true }, // Pikachu - new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 087, 113, 057, 364 }, Index = 21, CanGigantamax = true }, // Pikachu - new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 087, 113, 057, 344 }, Index = 21, CanGigantamax = true }, // Pikachu + new(17,01,1) { Species = 025, Ability = A4, Moves = new[]{ 084, 104, 486, 364 }, Index = 21, CanGigantamax = true }, // Pikachu + new(30,03,2) { Species = 025, Ability = A4, Moves = new[]{ 021, 209, 097, 364 }, Index = 21, CanGigantamax = true }, // Pikachu + new(40,05,3) { Species = 025, Ability = A4, Moves = new[]{ 021, 113, 085, 364 }, Index = 21, CanGigantamax = true }, // Pikachu + new(50,08,4) { Species = 025, Ability = A4, Moves = new[]{ 087, 113, 085, 364 }, Index = 21, CanGigantamax = true }, // Pikachu + new(50,08,4) { Species = 025, Ability = A4, Moves = new[]{ 087, 113, 057, 085 }, Index = 21, CanGigantamax = true }, // Pikachu + new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 087, 113, 057, 364 }, Index = 21, CanGigantamax = true }, // Pikachu + new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 087, 113, 057, 344 }, Index = 21, CanGigantamax = true }, // Pikachu - new(17,01,1) { Species = 246, Ability = A4, Moves = new[]{ 157, 044, 184, 033 }, Index = 20 }, // Larvitar - new(17,01,1) { Species = 529, Ability = A4, Moves = new[]{ 189, 232, 010, 468 }, Index = 20 }, // Drilbur - new(17,01,1) { Species = 546, Ability = A4, Moves = new[]{ 584, 078, 075, 071 }, Index = 20 }, // Cottonee - new(17,01,1) { Species = 885, Ability = A4, Moves = new[]{ 611, 098, 310, 044 }, Index = 20 }, // Dreepy - new(17,01,1) { Species = 175, Ability = A4, Moves = new[]{ 204, 577, 113, 791 }, Index = 20 }, // Togepi - new(30,03,2) { Species = 247, Ability = A4, Moves = new[]{ 157, 242, 334, 707 }, Index = 20 }, // Pupitar - new(30,03,2) { Species = 529, Ability = A4, Moves = new[]{ 189, 232, 157, 306 }, Index = 20 }, // Drilbur - new(30,03,2) { Species = 546, Ability = A4, Moves = new[]{ 202, 075, 204, 077 }, Index = 20 }, // Cottonee - new(30,03,2) { Species = 886, Ability = A4, Moves = new[]{ 097, 506, 372, 458 }, Index = 20 }, // Drakloak - new(30,03,2) { Species = 176, Ability = A4, Moves = new[]{ 584, 038, 113, 791 }, Index = 20 }, // Togetic - new(40,05,3) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 334, 089 }, Index = 20 }, // Tyranitar - new(40,05,3) { Species = 530, Ability = A4, Moves = new[]{ 529, 232, 157, 032 }, Index = 20 }, // Excadrill - new(40,05,3) { Species = 547, Ability = A4, Moves = new[]{ 283, 585, 077, 366 }, Index = 20 }, // Whimsicott - new(40,05,3) { Species = 887, Ability = A4, Moves = new[]{ 751, 506, 349, 458 }, Index = 20 }, // Dragapult - new(40,05,3) { Species = 468, Ability = A4, Moves = new[]{ 605, 038, 246, 403 }, Index = 20 }, // Togekiss - new(50,08,4) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 442, 089 }, Index = 20 }, // Tyranitar - new(50,08,4) { Species = 530, Ability = A4, Moves = new[]{ 529, 442, 157, 032 }, Index = 20 }, // Excadrill - new(50,08,4) { Species = 547, Ability = A4, Moves = new[]{ 283, 585, 073, 366 }, Index = 20 }, // Whimsicott - new(50,08,4) { Species = 887, Ability = A4, Moves = new[]{ 751, 506, 349, 211 }, Index = 20 }, // Dragapult - new(50,08,4) { Species = 468, Ability = A4, Moves = new[]{ 605, 219, 246, 403 }, Index = 20 }, // Togekiss - new(60,10,5) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 442, 276 }, Index = 20 }, // Tyranitar - new(60,10,5) { Species = 530, Ability = A4, Moves = new[]{ 089, 442, 157, 032 }, Index = 20 }, // Excadrill - new(60,10,5) { Species = 547, Ability = A4, Moves = new[]{ 538, 585, 073, 366 }, Index = 20 }, // Whimsicott - new(60,10,5) { Species = 887, Ability = A4, Moves = new[]{ 751, 566, 349, 211 }, Index = 20 }, // Dragapult - new(60,10,5) { Species = 468, Ability = A4, Moves = new[]{ 605, 219, 246, 053 }, Index = 20 }, // Togekiss - new(17,01,1,SW) { Species = 479, Ability = A4, Moves = new[]{ 104, 315, 084, 109 }, Index = 20, Form = 1 }, // Rotom-1 - new(30,03,2,SW) { Species = 479, Ability = A4, Moves = new[]{ 104, 315, 085, 109 }, Index = 20, Form = 1 }, // Rotom-1 - new(40,05,3,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 085, 506 }, Index = 20, Form = 1 }, // Rotom-1 - new(50,08,4,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 085, 261 }, Index = 20, Form = 1 }, // Rotom-1 - new(60,10,5,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 435, 261 }, Index = 20, Form = 1 }, // Rotom-1 - new(17,01,1,SH) { Species = 479, Ability = A4, Moves = new[]{ 104, 056, 084, 109 }, Index = 20, Form = 2 }, // Rotom-2 - new(30,03,2,SH) { Species = 479, Ability = A4, Moves = new[]{ 104, 056, 085, 109 }, Index = 20, Form = 2 }, // Rotom-2 - new(40,05,3,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 435, 085, 056 }, Index = 20, Form = 2 }, // Rotom-2 - new(50,08,4,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 435, 247, 056 }, Index = 20, Form = 2 }, // Rotom-2 - new(60,10,5,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 056, 247, 261 }, Index = 20, Form = 2 }, // Rotom-2 + new(17,01,1) { Species = 246, Ability = A4, Moves = new[]{ 157, 044, 184, 033 }, Index = 20 }, // Larvitar + new(17,01,1) { Species = 529, Ability = A4, Moves = new[]{ 189, 232, 010, 468 }, Index = 20 }, // Drilbur + new(17,01,1) { Species = 546, Ability = A4, Moves = new[]{ 584, 078, 075, 071 }, Index = 20 }, // Cottonee + new(17,01,1) { Species = 885, Ability = A4, Moves = new[]{ 611, 098, 310, 044 }, Index = 20 }, // Dreepy + new(17,01,1) { Species = 175, Ability = A4, Moves = new[]{ 204, 577, 113, 791 }, Index = 20 }, // Togepi + new(30,03,2) { Species = 247, Ability = A4, Moves = new[]{ 157, 242, 334, 707 }, Index = 20 }, // Pupitar + new(30,03,2) { Species = 529, Ability = A4, Moves = new[]{ 189, 232, 157, 306 }, Index = 20 }, // Drilbur + new(30,03,2) { Species = 546, Ability = A4, Moves = new[]{ 202, 075, 204, 077 }, Index = 20 }, // Cottonee + new(30,03,2) { Species = 886, Ability = A4, Moves = new[]{ 097, 506, 372, 458 }, Index = 20 }, // Drakloak + new(30,03,2) { Species = 176, Ability = A4, Moves = new[]{ 584, 038, 113, 791 }, Index = 20 }, // Togetic + new(40,05,3) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 334, 089 }, Index = 20 }, // Tyranitar + new(40,05,3) { Species = 530, Ability = A4, Moves = new[]{ 529, 232, 157, 032 }, Index = 20 }, // Excadrill + new(40,05,3) { Species = 547, Ability = A4, Moves = new[]{ 283, 585, 077, 366 }, Index = 20 }, // Whimsicott + new(40,05,3) { Species = 887, Ability = A4, Moves = new[]{ 751, 506, 349, 458 }, Index = 20 }, // Dragapult + new(40,05,3) { Species = 468, Ability = A4, Moves = new[]{ 605, 038, 246, 403 }, Index = 20 }, // Togekiss + new(50,08,4) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 442, 089 }, Index = 20 }, // Tyranitar + new(50,08,4) { Species = 530, Ability = A4, Moves = new[]{ 529, 442, 157, 032 }, Index = 20 }, // Excadrill + new(50,08,4) { Species = 547, Ability = A4, Moves = new[]{ 283, 585, 073, 366 }, Index = 20 }, // Whimsicott + new(50,08,4) { Species = 887, Ability = A4, Moves = new[]{ 751, 506, 349, 211 }, Index = 20 }, // Dragapult + new(50,08,4) { Species = 468, Ability = A4, Moves = new[]{ 605, 219, 246, 403 }, Index = 20 }, // Togekiss + new(60,10,5) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 442, 276 }, Index = 20 }, // Tyranitar + new(60,10,5) { Species = 530, Ability = A4, Moves = new[]{ 089, 442, 157, 032 }, Index = 20 }, // Excadrill + new(60,10,5) { Species = 547, Ability = A4, Moves = new[]{ 538, 585, 073, 366 }, Index = 20 }, // Whimsicott + new(60,10,5) { Species = 887, Ability = A4, Moves = new[]{ 751, 566, 349, 211 }, Index = 20 }, // Dragapult + new(60,10,5) { Species = 468, Ability = A4, Moves = new[]{ 605, 219, 246, 053 }, Index = 20 }, // Togekiss + new(17,01,1,SW) { Species = 479, Ability = A4, Moves = new[]{ 104, 315, 084, 109 }, Index = 20, Form = 1 }, // Rotom-1 + new(30,03,2,SW) { Species = 479, Ability = A4, Moves = new[]{ 104, 315, 085, 109 }, Index = 20, Form = 1 }, // Rotom-1 + new(40,05,3,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 085, 506 }, Index = 20, Form = 1 }, // Rotom-1 + new(50,08,4,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 085, 261 }, Index = 20, Form = 1 }, // Rotom-1 + new(60,10,5,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 435, 261 }, Index = 20, Form = 1 }, // Rotom-1 + new(17,01,1,SH) { Species = 479, Ability = A4, Moves = new[]{ 104, 056, 084, 109 }, Index = 20, Form = 2 }, // Rotom-2 + new(30,03,2,SH) { Species = 479, Ability = A4, Moves = new[]{ 104, 056, 085, 109 }, Index = 20, Form = 2 }, // Rotom-2 + new(40,05,3,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 435, 085, 056 }, Index = 20, Form = 2 }, // Rotom-2 + new(50,08,4,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 435, 247, 056 }, Index = 20, Form = 2 }, // Rotom-2 + new(60,10,5,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 056, 247, 261 }, Index = 20, Form = 2 }, // Rotom-2 - new(17,01,1) { Species = 246, Ability = A4, Moves = new[]{ 157, 044, 184, 033 }, Index = 19 }, // Larvitar - new(17,01,1) { Species = 546, Ability = A4, Moves = new[]{ 584, 078, 075, 071 }, Index = 19 }, // Cottonee - new(17,01,1) { Species = 885, Ability = A4, Moves = new[]{ 611, 098, 310, 044 }, Index = 19 }, // Dreepy - new(17,01,1) { Species = 175, Ability = A4, Moves = new[]{ 204, 577, 113, 791 }, Index = 19 }, // Togepi - new(30,03,2) { Species = 247, Ability = A4, Moves = new[]{ 157, 242, 334, 707 }, Index = 19 }, // Pupitar - new(30,03,2) { Species = 529, Ability = A4, Moves = new[]{ 189, 232, 157, 306 }, Index = 19 }, // Drilbur - new(30,03,2) { Species = 546, Ability = A4, Moves = new[]{ 202, 075, 204, 077 }, Index = 19 }, // Cottonee - new(30,03,2) { Species = 886, Ability = A4, Moves = new[]{ 097, 506, 372, 458 }, Index = 19 }, // Drakloak - new(30,03,2) { Species = 176, Ability = A4, Moves = new[]{ 584, 038, 113, 791 }, Index = 19 }, // Togetic - new(40,05,3) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 334, 089 }, Index = 19 }, // Tyranitar - new(40,05,3) { Species = 530, Ability = A4, Moves = new[]{ 529, 232, 157, 032 }, Index = 19 }, // Excadrill - new(40,05,3) { Species = 547, Ability = A4, Moves = new[]{ 283, 585, 077, 366 }, Index = 19 }, // Whimsicott - new(40,05,3) { Species = 887, Ability = A4, Moves = new[]{ 751, 506, 349, 458 }, Index = 19 }, // Dragapult - new(40,05,3) { Species = 468, Ability = A4, Moves = new[]{ 605, 038, 246, 403 }, Index = 19 }, // Togekiss - new(50,08,4) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 442, 089 }, Index = 19 }, // Tyranitar - new(50,08,4) { Species = 530, Ability = A4, Moves = new[]{ 529, 442, 157, 032 }, Index = 19 }, // Excadrill - new(50,08,4) { Species = 547, Ability = A4, Moves = new[]{ 283, 585, 073, 366 }, Index = 19 }, // Whimsicott - new(50,08,4) { Species = 887, Ability = A4, Moves = new[]{ 751, 506, 349, 211 }, Index = 19 }, // Dragapult - new(50,08,4) { Species = 468, Ability = A4, Moves = new[]{ 605, 219, 246, 403 }, Index = 19 }, // Togekiss - new(60,10,5) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 442, 276 }, Index = 19 }, // Tyranitar - new(60,10,5) { Species = 530, Ability = A4, Moves = new[]{ 089, 442, 157, 032 }, Index = 19 }, // Excadrill - new(60,10,5) { Species = 547, Ability = A4, Moves = new[]{ 538, 585, 073, 366 }, Index = 19 }, // Whimsicott - new(60,10,5) { Species = 887, Ability = A4, Moves = new[]{ 751, 566, 349, 211 }, Index = 19 }, // Dragapult - new(60,10,5) { Species = 468, Ability = A4, Moves = new[]{ 605, 219, 246, 053 }, Index = 19 }, // Togekiss - new(17,01,1,SW) { Species = 479, Ability = A4, Moves = new[]{ 104, 315, 084, 109 }, Index = 19, Form = 1 }, // Rotom-1 - new(17,01,1,SW) { Species = 529, Ability = A4, Moves = new[]{ 189, 232, 010, 468 }, Index = 19 }, // Drilbur - new(30,03,2,SW) { Species = 479, Ability = A4, Moves = new[]{ 104, 315, 085, 109 }, Index = 19, Form = 1 }, // Rotom-1 - new(40,05,3,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 085, 506 }, Index = 19, Form = 1 }, // Rotom-1 - new(50,08,4,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 085, 261 }, Index = 19, Form = 1 }, // Rotom-1 - new(60,10,5,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 435, 261 }, Index = 19, Form = 1 }, // Rotom-1 - new(17,01,1,SH) { Species = 479, Ability = A4, Moves = new[]{ 104, 435, 084, 109 }, Index = 19, Form = 2 }, // Rotom-2 - //new(17,01,1,SH) { Species = 529, Ability = A4, Moves = new[]{ 189, 232, 056, 468 }, Index = 19 }, // Drilbur - From initial revision: treat this as illegal. - new(30,03,2,SH) { Species = 479, Ability = A4, Moves = new[]{ 104, 056, 085, 109 }, Index = 19, Form = 2 }, // Rotom-2 - new(40,05,3,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 435, 085, 056 }, Index = 19, Form = 2 }, // Rotom-2 - new(50,08,4,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 435, 247, 056 }, Index = 19, Form = 2 }, // Rotom-2 - new(60,10,5,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 056, 247, 261 }, Index = 19, Form = 2 }, // Rotom-2 + new(17,01,1) { Species = 246, Ability = A4, Moves = new[]{ 157, 044, 184, 033 }, Index = 19 }, // Larvitar + new(17,01,1) { Species = 546, Ability = A4, Moves = new[]{ 584, 078, 075, 071 }, Index = 19 }, // Cottonee + new(17,01,1) { Species = 885, Ability = A4, Moves = new[]{ 611, 098, 310, 044 }, Index = 19 }, // Dreepy + new(17,01,1) { Species = 175, Ability = A4, Moves = new[]{ 204, 577, 113, 791 }, Index = 19 }, // Togepi + new(30,03,2) { Species = 247, Ability = A4, Moves = new[]{ 157, 242, 334, 707 }, Index = 19 }, // Pupitar + new(30,03,2) { Species = 529, Ability = A4, Moves = new[]{ 189, 232, 157, 306 }, Index = 19 }, // Drilbur + new(30,03,2) { Species = 546, Ability = A4, Moves = new[]{ 202, 075, 204, 077 }, Index = 19 }, // Cottonee + new(30,03,2) { Species = 886, Ability = A4, Moves = new[]{ 097, 506, 372, 458 }, Index = 19 }, // Drakloak + new(30,03,2) { Species = 176, Ability = A4, Moves = new[]{ 584, 038, 113, 791 }, Index = 19 }, // Togetic + new(40,05,3) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 334, 089 }, Index = 19 }, // Tyranitar + new(40,05,3) { Species = 530, Ability = A4, Moves = new[]{ 529, 232, 157, 032 }, Index = 19 }, // Excadrill + new(40,05,3) { Species = 547, Ability = A4, Moves = new[]{ 283, 585, 077, 366 }, Index = 19 }, // Whimsicott + new(40,05,3) { Species = 887, Ability = A4, Moves = new[]{ 751, 506, 349, 458 }, Index = 19 }, // Dragapult + new(40,05,3) { Species = 468, Ability = A4, Moves = new[]{ 605, 038, 246, 403 }, Index = 19 }, // Togekiss + new(50,08,4) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 442, 089 }, Index = 19 }, // Tyranitar + new(50,08,4) { Species = 530, Ability = A4, Moves = new[]{ 529, 442, 157, 032 }, Index = 19 }, // Excadrill + new(50,08,4) { Species = 547, Ability = A4, Moves = new[]{ 283, 585, 073, 366 }, Index = 19 }, // Whimsicott + new(50,08,4) { Species = 887, Ability = A4, Moves = new[]{ 751, 506, 349, 211 }, Index = 19 }, // Dragapult + new(50,08,4) { Species = 468, Ability = A4, Moves = new[]{ 605, 219, 246, 403 }, Index = 19 }, // Togekiss + new(60,10,5) { Species = 248, Ability = A4, Moves = new[]{ 444, 242, 442, 276 }, Index = 19 }, // Tyranitar + new(60,10,5) { Species = 530, Ability = A4, Moves = new[]{ 089, 442, 157, 032 }, Index = 19 }, // Excadrill + new(60,10,5) { Species = 547, Ability = A4, Moves = new[]{ 538, 585, 073, 366 }, Index = 19 }, // Whimsicott + new(60,10,5) { Species = 887, Ability = A4, Moves = new[]{ 751, 566, 349, 211 }, Index = 19 }, // Dragapult + new(60,10,5) { Species = 468, Ability = A4, Moves = new[]{ 605, 219, 246, 053 }, Index = 19 }, // Togekiss + new(17,01,1,SW) { Species = 479, Ability = A4, Moves = new[]{ 104, 315, 084, 109 }, Index = 19, Form = 1 }, // Rotom-1 + new(17,01,1,SW) { Species = 529, Ability = A4, Moves = new[]{ 189, 232, 010, 468 }, Index = 19 }, // Drilbur + new(30,03,2,SW) { Species = 479, Ability = A4, Moves = new[]{ 104, 315, 085, 109 }, Index = 19, Form = 1 }, // Rotom-1 + new(40,05,3,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 085, 506 }, Index = 19, Form = 1 }, // Rotom-1 + new(50,08,4,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 085, 261 }, Index = 19, Form = 1 }, // Rotom-1 + new(60,10,5,SW) { Species = 479, Ability = A4, Moves = new[]{ 521, 315, 435, 261 }, Index = 19, Form = 1 }, // Rotom-1 + new(17,01,1,SH) { Species = 479, Ability = A4, Moves = new[]{ 104, 435, 084, 109 }, Index = 19, Form = 2 }, // Rotom-2 + //new(17,01,1,SH) { Species = 529, Ability = A4, Moves = new[]{ 189, 232, 056, 468 }, Index = 19 }, // Drilbur - From initial revision: treat this as illegal. + new(30,03,2,SH) { Species = 479, Ability = A4, Moves = new[]{ 104, 056, 085, 109 }, Index = 19, Form = 2 }, // Rotom-2 + new(40,05,3,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 435, 085, 056 }, Index = 19, Form = 2 }, // Rotom-2 + new(50,08,4,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 435, 247, 056 }, Index = 19, Form = 2 }, // Rotom-2 + new(60,10,5,SH) { Species = 479, Ability = A4, Moves = new[]{ 521, 056, 247, 261 }, Index = 19, Form = 2 }, // Rotom-2 - new(17,01,1) { Species = 878, Ability = A4, Moves = new[]{ 091, 249, 205, 523 }, Index = 17 }, // Cufant - new(17,01,1) { Species = 568, Ability = A4, Moves = new[]{ 001, 499, 491, 133 }, Index = 17 }, // Trubbish - new(17,01,1) { Species = 004, Ability = A4, Moves = new[]{ 424, 052, 108, 225 }, Index = 17 }, // Charmander - new(17,01,1) { Species = 884, Ability = A4, Moves = new[]{ 468, 249, 043, 232 }, Index = 17 }, // Duraludon - new(30,03,2) { Species = 878, Ability = A4, Moves = new[]{ 334, 091, 205, 523 }, Index = 17 }, // Cufant - new(30,03,2) { Species = 568, Ability = A4, Moves = new[]{ 036, 499, 124, 133 }, Index = 17 }, // Trubbish - new(30,03,2) { Species = 884, Ability = A4, Moves = new[]{ 468, 249, 784, 232 }, Index = 17 }, // Duraludon - new(30,03,2) { Species = 005, Ability = A4, Moves = new[]{ 053, 163, 108, 225 }, Index = 17 }, // Charmeleon - new(40,05,2) { Species = 848, Ability = A4, Moves = new[]{ 609, 051, 175, 715 }, Index = 17 }, // Toxel - new(40,05,2) { Species = 458, Ability = A4, Moves = new[]{ 403, 061, 469, 503 }, Index = 17 }, // Mantyke - new(40,05,3) { Species = 406, Ability = A4, Moves = new[]{ 071, 074, 078, 188 }, Index = 17 }, // Budew - new(40,05,3) { Species = 236, Ability = A4, Moves = new[]{ 280, 157, 252, 116 }, Index = 17 }, // Tyrogue - new(40,05,3) { Species = 438, Ability = A4, Moves = new[]{ 317, 389, 157, 313 }, Index = 17 }, // Bonsly - new(40,05,3) { Species = 447, Ability = A4, Moves = new[]{ 014, 009, 232, 249 }, Index = 17 }, // Riolu - new(40,05,3) { Species = 446, Ability = A4, Moves = new[]{ 034, 089, 044, 122 }, Index = 17 }, // Munchlax - new(40,05,3) { Species = 439, Ability = A4, Moves = new[]{ 389, 060, 182, 085 }, Index = 17 }, // Mime Jr. - new(50,08,4) { Species = 175, Ability = A4, Moves = new[]{ 605, 219, 246, 053 }, Index = 17 }, // Togepi - new(50,08,4) { Species = 360, Ability = A4, Moves = new[]{ 068, 243, 204, 133 }, Index = 17 }, // Wynaut - new(50,08,4) { Species = 173, Ability = A4, Moves = new[]{ 574, 005, 113, 034 }, Index = 17 }, // Cleffa - new(50,08,4) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 076, 257 }, Index = 17, CanGigantamax = true }, // Charizard - new(50,08,4) { Species = 172, Ability = A4, Moves = new[]{ 583, 417, 085, 186 }, Index = 17 }, // Pichu - new(50,08,4) { Species = 884, Ability = A4, Moves = new[]{ 337, 430, 784, 776 }, Index = 17, CanGigantamax = true }, // Duraludon - new(60,10,5) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 17 }, // Ditto - new(60,10,5,SW) { Species = 879, Ability = A4, Moves = new[]{ 442, 583, 438, 089 }, Index = 17, CanGigantamax = true }, // Copperajah - new(60,10,5,SW) { Species = 884, Ability = A4, Moves = new[]{ 430, 406, 085, 334 }, Index = 17, CanGigantamax = true }, // Duraludon - new(60,10,5,SH) { Species = 569, Ability = A4, Moves = new[]{ 441, 499, 402, 707 }, Index = 17, CanGigantamax = true }, // Garbodor - new(60,10,5,SH) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 019, 411 }, Index = 17, CanGigantamax = true }, // Charizard + new(17,01,1) { Species = 878, Ability = A4, Moves = new[]{ 091, 249, 205, 523 }, Index = 17 }, // Cufant + new(17,01,1) { Species = 568, Ability = A4, Moves = new[]{ 001, 499, 491, 133 }, Index = 17 }, // Trubbish + new(17,01,1) { Species = 004, Ability = A4, Moves = new[]{ 424, 052, 108, 225 }, Index = 17 }, // Charmander + new(17,01,1) { Species = 884, Ability = A4, Moves = new[]{ 468, 249, 043, 232 }, Index = 17 }, // Duraludon + new(30,03,2) { Species = 878, Ability = A4, Moves = new[]{ 334, 091, 205, 523 }, Index = 17 }, // Cufant + new(30,03,2) { Species = 568, Ability = A4, Moves = new[]{ 036, 499, 124, 133 }, Index = 17 }, // Trubbish + new(30,03,2) { Species = 884, Ability = A4, Moves = new[]{ 468, 249, 784, 232 }, Index = 17 }, // Duraludon + new(30,03,2) { Species = 005, Ability = A4, Moves = new[]{ 053, 163, 108, 225 }, Index = 17 }, // Charmeleon + new(40,05,2) { Species = 848, Ability = A4, Moves = new[]{ 609, 051, 175, 715 }, Index = 17 }, // Toxel + new(40,05,2) { Species = 458, Ability = A4, Moves = new[]{ 403, 061, 469, 503 }, Index = 17 }, // Mantyke + new(40,05,3) { Species = 406, Ability = A4, Moves = new[]{ 071, 074, 078, 188 }, Index = 17 }, // Budew + new(40,05,3) { Species = 236, Ability = A4, Moves = new[]{ 280, 157, 252, 116 }, Index = 17 }, // Tyrogue + new(40,05,3) { Species = 438, Ability = A4, Moves = new[]{ 317, 389, 157, 313 }, Index = 17 }, // Bonsly + new(40,05,3) { Species = 447, Ability = A4, Moves = new[]{ 014, 009, 232, 249 }, Index = 17 }, // Riolu + new(40,05,3) { Species = 446, Ability = A4, Moves = new[]{ 034, 089, 044, 122 }, Index = 17 }, // Munchlax + new(40,05,3) { Species = 439, Ability = A4, Moves = new[]{ 389, 060, 182, 085 }, Index = 17 }, // Mime Jr. + new(50,08,4) { Species = 175, Ability = A4, Moves = new[]{ 605, 219, 246, 053 }, Index = 17 }, // Togepi + new(50,08,4) { Species = 360, Ability = A4, Moves = new[]{ 068, 243, 204, 133 }, Index = 17 }, // Wynaut + new(50,08,4) { Species = 173, Ability = A4, Moves = new[]{ 574, 005, 113, 034 }, Index = 17 }, // Cleffa + new(50,08,4) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 076, 257 }, Index = 17, CanGigantamax = true }, // Charizard + new(50,08,4) { Species = 172, Ability = A4, Moves = new[]{ 583, 417, 085, 186 }, Index = 17 }, // Pichu + new(50,08,4) { Species = 884, Ability = A4, Moves = new[]{ 337, 430, 784, 776 }, Index = 17, CanGigantamax = true }, // Duraludon + new(60,10,5) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 17 }, // Ditto + new(60,10,5,SW) { Species = 879, Ability = A4, Moves = new[]{ 442, 583, 438, 089 }, Index = 17, CanGigantamax = true }, // Copperajah + new(60,10,5,SW) { Species = 884, Ability = A4, Moves = new[]{ 430, 406, 085, 334 }, Index = 17, CanGigantamax = true }, // Duraludon + new(60,10,5,SH) { Species = 569, Ability = A4, Moves = new[]{ 441, 499, 402, 707 }, Index = 17, CanGigantamax = true }, // Garbodor + new(60,10,5,SH) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 019, 411 }, Index = 17, CanGigantamax = true }, // Charizard - new(17,01,1) { Species = 878, Ability = A4, Moves = new[]{ 091, 249, 205, 523 }, Index = 16 }, // Cufant - new(17,01,1) { Species = 568, Ability = A4, Moves = new[]{ 001, 499, 491, 133 }, Index = 16 }, // Trubbish - new(17,01,1) { Species = 004, Ability = A4, Moves = new[]{ 424, 052, 108, 225 }, Index = 16 }, // Charmander - new(17,01,1) { Species = 884, Ability = A4, Moves = new[]{ 468, 249, 043, 232 }, Index = 16 }, // Duraludon - new(30,03,2) { Species = 878, Ability = A4, Moves = new[]{ 334, 091, 205, 523 }, Index = 16 }, // Cufant - new(30,03,2) { Species = 568, Ability = A4, Moves = new[]{ 036, 499, 124, 133 }, Index = 16 }, // Trubbish - new(30,03,2) { Species = 005, Ability = A4, Moves = new[]{ 053, 163, 108, 225 }, Index = 16 }, // Charmeleon - new(30,03,2) { Species = 884, Ability = A4, Moves = new[]{ 468, 249, 784, 232 }, Index = 16 }, // Duraludon - new(40,05,3) { Species = 879, Ability = A4, Moves = new[]{ 334, 070, 442, 523 }, Index = 16, CanGigantamax = true }, // Copperajah - new(40,05,3) { Species = 569, Ability = A4, Moves = new[]{ 188, 499, 034, 707 }, Index = 16, CanGigantamax = true }, // Garbodor - new(40,05,3) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 108, 225 }, Index = 16, CanGigantamax = true }, // Charizard - new(40,05,3) { Species = 884, Ability = A4, Moves = new[]{ 442, 555, 784, 334 }, Index = 16, CanGigantamax = true }, // Duraludon - new(50,08,4) { Species = 879, Ability = A4, Moves = new[]{ 667, 442, 438, 523 }, Index = 16, CanGigantamax = true }, // Copperajah - new(50,08,4) { Species = 569, Ability = A4, Moves = new[]{ 441, 499, 402, 707 }, Index = 16, CanGigantamax = true }, // Garbodor - new(50,08,4) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 076, 257 }, Index = 16, CanGigantamax = true }, // Charizard - new(50,08,4) { Species = 884, Ability = A4, Moves = new[]{ 337, 430, 784, 776 }, Index = 16, CanGigantamax = true }, // Duraludon - new(60,10,5,SW) { Species = 879, Ability = A4, Moves = new[]{ 442, 583, 438, 089 }, Index = 16, CanGigantamax = true }, // Copperajah - new(60,10,5,SW) { Species = 884, Ability = A4, Moves = new[]{ 430, 406, 085, 334 }, Index = 16, CanGigantamax = true }, // Duraludon - new(60,10,5,SH) { Species = 569, Ability = A4, Moves = new[]{ 441, 409, 402, 707 }, Index = 16, CanGigantamax = true }, // Garbodor - new(60,10,5,SH) { Species = 006, Ability = A4, Moves = new[]{ 257, 403, 406, 411 }, Index = 16, CanGigantamax = true, Shiny = Shiny.Never }, // Charizard + new(17,01,1) { Species = 878, Ability = A4, Moves = new[]{ 091, 249, 205, 523 }, Index = 16 }, // Cufant + new(17,01,1) { Species = 568, Ability = A4, Moves = new[]{ 001, 499, 491, 133 }, Index = 16 }, // Trubbish + new(17,01,1) { Species = 004, Ability = A4, Moves = new[]{ 424, 052, 108, 225 }, Index = 16 }, // Charmander + new(17,01,1) { Species = 884, Ability = A4, Moves = new[]{ 468, 249, 043, 232 }, Index = 16 }, // Duraludon + new(30,03,2) { Species = 878, Ability = A4, Moves = new[]{ 334, 091, 205, 523 }, Index = 16 }, // Cufant + new(30,03,2) { Species = 568, Ability = A4, Moves = new[]{ 036, 499, 124, 133 }, Index = 16 }, // Trubbish + new(30,03,2) { Species = 005, Ability = A4, Moves = new[]{ 053, 163, 108, 225 }, Index = 16 }, // Charmeleon + new(30,03,2) { Species = 884, Ability = A4, Moves = new[]{ 468, 249, 784, 232 }, Index = 16 }, // Duraludon + new(40,05,3) { Species = 879, Ability = A4, Moves = new[]{ 334, 070, 442, 523 }, Index = 16, CanGigantamax = true }, // Copperajah + new(40,05,3) { Species = 569, Ability = A4, Moves = new[]{ 188, 499, 034, 707 }, Index = 16, CanGigantamax = true }, // Garbodor + new(40,05,3) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 108, 225 }, Index = 16, CanGigantamax = true }, // Charizard + new(40,05,3) { Species = 884, Ability = A4, Moves = new[]{ 442, 555, 784, 334 }, Index = 16, CanGigantamax = true }, // Duraludon + new(50,08,4) { Species = 879, Ability = A4, Moves = new[]{ 667, 442, 438, 523 }, Index = 16, CanGigantamax = true }, // Copperajah + new(50,08,4) { Species = 569, Ability = A4, Moves = new[]{ 441, 499, 402, 707 }, Index = 16, CanGigantamax = true }, // Garbodor + new(50,08,4) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 076, 257 }, Index = 16, CanGigantamax = true }, // Charizard + new(50,08,4) { Species = 884, Ability = A4, Moves = new[]{ 337, 430, 784, 776 }, Index = 16, CanGigantamax = true }, // Duraludon + new(60,10,5,SW) { Species = 879, Ability = A4, Moves = new[]{ 442, 583, 438, 089 }, Index = 16, CanGigantamax = true }, // Copperajah + new(60,10,5,SW) { Species = 884, Ability = A4, Moves = new[]{ 430, 406, 085, 334 }, Index = 16, CanGigantamax = true }, // Duraludon + new(60,10,5,SH) { Species = 569, Ability = A4, Moves = new[]{ 441, 409, 402, 707 }, Index = 16, CanGigantamax = true }, // Garbodor + new(60,10,5,SH) { Species = 006, Ability = A4, Moves = new[]{ 257, 403, 406, 411 }, Index = 16, CanGigantamax = true, Shiny = Shiny.Never }, // Charizard - new(17,01,1) { Species = 588, Ability = A4, Moves = new[]{ 064, 043, 210, 491 }, Index = 15 }, // Karrablast - new(17,01,1) { Species = 616, Ability = A4, Moves = new[]{ 071, 051, 174, 522 }, Index = 15 }, // Shelmet - new(17,01,1) { Species = 092, Ability = A4, Moves = new[]{ 122, 109, 095, 371 }, Index = 15 }, // Gastly - new(17,01,1) { Species = 871, Ability = A4, Moves = new[]{ 084, 064, 055, 031 }, Index = 15 }, // Pincurchin - new(17,01,1) { Species = 066, Ability = A4, Moves = new[]{ 067, 043, 116, 279 }, Index = 15 }, // Machop - new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 15 }, // Snorlax - new(30,03,2) { Species = 093, Ability = A4, Moves = new[]{ 325, 095, 122, 101 }, Index = 15 }, // Haunter - new(30,03,2) { Species = 871, Ability = A4, Moves = new[]{ 209, 061, 086, 506 }, Index = 15 }, // Pincurchin - new(30,03,2) { Species = 067, Ability = A4, Moves = new[]{ 067, 490, 282, 233 }, Index = 15 }, // Machoke - new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 15, CanGigantamax = true }, // Snorlax - new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 506, 188, 085, 261 }, Index = 15, CanGigantamax = true }, // Gengar - new(40,05,3) { Species = 871, Ability = A2, Moves = new[]{ 085, 503, 398, 716 }, Index = 15 }, // Pincurchin - new(40,05,3) { Species = 068, Ability = A4, Moves = new[]{ 279, 667, 008, 157 }, Index = 15, CanGigantamax = true }, // Machamp - new(50,08,4) { Species = 617, Ability = A2, Moves = new[]{ 405, 522, 188, 202 }, Index = 15 }, // Accelgor - new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 15, CanGigantamax = true }, // Snorlax - new(50,08,4) { Species = 589, Ability = A2, Moves = new[]{ 442, 224, 529, 398 }, Index = 15 }, // Escavalier - new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 411, 605 }, Index = 15, CanGigantamax = true }, // Gengar - new(50,08,4) { Species = 871, Ability = A2, Moves = new[]{ 435, 330, 474, 367 }, Index = 15 }, // Pincurchin - new(50,08,4) { Species = 068, Ability = A4, Moves = new[]{ 280, 444, 371, 523 }, Index = 15, CanGigantamax = true }, // Machamp - new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 15, CanGigantamax = true }, // Snorlax - new(17,01,1,SW) { Species = 222, Ability = A4, Moves = new[]{ 033, 106, 310, 050 }, Index = 15, Form = 1 }, // Corsola-1 - new(30,03,2,SW) { Species = 077, Ability = A4, Moves = new[]{ 093, 584, 060, 023 }, Index = 15, Form = 1 }, // Ponyta-1 - new(30,03,2,SW) { Species = 222, Ability = A4, Moves = new[]{ 310, 050, 246, 506 }, Index = 15, Form = 1 }, // Corsola-1 - new(40,05,3,SW) { Species = 077, Ability = A2, Moves = new[]{ 340, 023, 428, 583 }, Index = 15, Form = 1 }, // Ponyta-1 - new(40,05,3,SW) { Species = 222, Ability = A2, Moves = new[]{ 506, 408, 503, 261 }, Index = 15, Form = 1 }, // Corsola-1 - new(60,10,5,SW) { Species = 765, Ability = A2, Moves = new[]{ 492, 094, 085, 247 }, Index = 15 }, // Oranguru - new(60,10,5,SW) { Species = 876, Ability = A2, Moves = new[]{ 094, 595, 605, 304 }, Index = 15, Form = 1 }, // Indeedee-1 - new(60,10,5,SW) { Species = 630, Ability = A2, Moves = new[]{ 403, 555, 492, 211 }, Index = 15 }, // Mandibuzz - new(60,10,5,SW) { Species = 078, Ability = A2, Moves = new[]{ 428, 583, 224, 340 }, Index = 15, Form = 1 }, // Rapidash-1 - new(60,10,5,SW) { Species = 068, Ability = A4, Moves = new[]{ 238, 007, 008, 089 }, Index = 15, CanGigantamax = true }, // Machamp - new(17,01,1,SH) { Species = 554, Ability = A4, Moves = new[]{ 033, 181, 044, 419 }, Index = 15, Form = 1 }, // Darumaka-1 - new(30,03,2,SH) { Species = 083, Ability = A4, Moves = new[]{ 064, 028, 249, 693 }, Index = 15, Form = 1 }, // Farfetch’d-1 - new(30,03,2,SH) { Species = 554, Ability = A4, Moves = new[]{ 423, 029, 424, 280 }, Index = 15, Form = 1 }, // Darumaka-1 - new(40,05,3,SH) { Species = 083, Ability = A2, Moves = new[]{ 280, 693, 348, 413 }, Index = 15, Form = 1 }, // Farfetch’d-1 - new(40,05,3,SH) { Species = 554, Ability = A2, Moves = new[]{ 008, 007, 428, 276 }, Index = 15, Form = 1 }, // Darumaka-1 - new(60,10,5,SH) { Species = 766, Ability = A2, Moves = new[]{ 370, 157, 523, 231 }, Index = 15 }, // Passimian - new(60,10,5,SH) { Species = 876, Ability = A2, Moves = new[]{ 094, 595, 605, 247 }, Index = 15 }, // Indeedee - new(60,10,5,SH) { Species = 628, Ability = A2, Moves = new[]{ 413, 276, 442, 157 }, Index = 15 }, // Braviary - new(60,10,5,SH) { Species = 865, Ability = A2, Moves = new[]{ 370, 413, 211, 675 }, Index = 15 }, // Sirfetch’d - new(60,10,5,SH) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 094, 196 }, Index = 15, CanGigantamax = true }, // Gengar + new(17,01,1) { Species = 588, Ability = A4, Moves = new[]{ 064, 043, 210, 491 }, Index = 15 }, // Karrablast + new(17,01,1) { Species = 616, Ability = A4, Moves = new[]{ 071, 051, 174, 522 }, Index = 15 }, // Shelmet + new(17,01,1) { Species = 092, Ability = A4, Moves = new[]{ 122, 109, 095, 371 }, Index = 15 }, // Gastly + new(17,01,1) { Species = 871, Ability = A4, Moves = new[]{ 084, 064, 055, 031 }, Index = 15 }, // Pincurchin + new(17,01,1) { Species = 066, Ability = A4, Moves = new[]{ 067, 043, 116, 279 }, Index = 15 }, // Machop + new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 15 }, // Snorlax + new(30,03,2) { Species = 093, Ability = A4, Moves = new[]{ 325, 095, 122, 101 }, Index = 15 }, // Haunter + new(30,03,2) { Species = 871, Ability = A4, Moves = new[]{ 209, 061, 086, 506 }, Index = 15 }, // Pincurchin + new(30,03,2) { Species = 067, Ability = A4, Moves = new[]{ 067, 490, 282, 233 }, Index = 15 }, // Machoke + new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 15, CanGigantamax = true }, // Snorlax + new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 506, 188, 085, 261 }, Index = 15, CanGigantamax = true }, // Gengar + new(40,05,3) { Species = 871, Ability = A2, Moves = new[]{ 085, 503, 398, 716 }, Index = 15 }, // Pincurchin + new(40,05,3) { Species = 068, Ability = A4, Moves = new[]{ 279, 667, 008, 157 }, Index = 15, CanGigantamax = true }, // Machamp + new(50,08,4) { Species = 617, Ability = A2, Moves = new[]{ 405, 522, 188, 202 }, Index = 15 }, // Accelgor + new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 15, CanGigantamax = true }, // Snorlax + new(50,08,4) { Species = 589, Ability = A2, Moves = new[]{ 442, 224, 529, 398 }, Index = 15 }, // Escavalier + new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 411, 605 }, Index = 15, CanGigantamax = true }, // Gengar + new(50,08,4) { Species = 871, Ability = A2, Moves = new[]{ 435, 330, 474, 367 }, Index = 15 }, // Pincurchin + new(50,08,4) { Species = 068, Ability = A4, Moves = new[]{ 280, 444, 371, 523 }, Index = 15, CanGigantamax = true }, // Machamp + new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 15, CanGigantamax = true }, // Snorlax + new(17,01,1,SW) { Species = 222, Ability = A4, Moves = new[]{ 033, 106, 310, 050 }, Index = 15, Form = 1 }, // Corsola-1 + new(30,03,2,SW) { Species = 077, Ability = A4, Moves = new[]{ 093, 584, 060, 023 }, Index = 15, Form = 1 }, // Ponyta-1 + new(30,03,2,SW) { Species = 222, Ability = A4, Moves = new[]{ 310, 050, 246, 506 }, Index = 15, Form = 1 }, // Corsola-1 + new(40,05,3,SW) { Species = 077, Ability = A2, Moves = new[]{ 340, 023, 428, 583 }, Index = 15, Form = 1 }, // Ponyta-1 + new(40,05,3,SW) { Species = 222, Ability = A2, Moves = new[]{ 506, 408, 503, 261 }, Index = 15, Form = 1 }, // Corsola-1 + new(60,10,5,SW) { Species = 765, Ability = A2, Moves = new[]{ 492, 094, 085, 247 }, Index = 15 }, // Oranguru + new(60,10,5,SW) { Species = 876, Ability = A2, Moves = new[]{ 094, 595, 605, 304 }, Index = 15, Form = 1 }, // Indeedee-1 + new(60,10,5,SW) { Species = 630, Ability = A2, Moves = new[]{ 403, 555, 492, 211 }, Index = 15 }, // Mandibuzz + new(60,10,5,SW) { Species = 078, Ability = A2, Moves = new[]{ 428, 583, 224, 340 }, Index = 15, Form = 1 }, // Rapidash-1 + new(60,10,5,SW) { Species = 068, Ability = A4, Moves = new[]{ 238, 007, 008, 089 }, Index = 15, CanGigantamax = true }, // Machamp + new(17,01,1,SH) { Species = 554, Ability = A4, Moves = new[]{ 033, 181, 044, 419 }, Index = 15, Form = 1 }, // Darumaka-1 + new(30,03,2,SH) { Species = 083, Ability = A4, Moves = new[]{ 064, 028, 249, 693 }, Index = 15, Form = 1 }, // Farfetch’d-1 + new(30,03,2,SH) { Species = 554, Ability = A4, Moves = new[]{ 423, 029, 424, 280 }, Index = 15, Form = 1 }, // Darumaka-1 + new(40,05,3,SH) { Species = 083, Ability = A2, Moves = new[]{ 280, 693, 348, 413 }, Index = 15, Form = 1 }, // Farfetch’d-1 + new(40,05,3,SH) { Species = 554, Ability = A2, Moves = new[]{ 008, 007, 428, 276 }, Index = 15, Form = 1 }, // Darumaka-1 + new(60,10,5,SH) { Species = 766, Ability = A2, Moves = new[]{ 370, 157, 523, 231 }, Index = 15 }, // Passimian + new(60,10,5,SH) { Species = 876, Ability = A2, Moves = new[]{ 094, 595, 605, 247 }, Index = 15 }, // Indeedee + new(60,10,5,SH) { Species = 628, Ability = A2, Moves = new[]{ 413, 276, 442, 157 }, Index = 15 }, // Braviary + new(60,10,5,SH) { Species = 865, Ability = A2, Moves = new[]{ 370, 413, 211, 675 }, Index = 15 }, // Sirfetch’d + new(60,10,5,SH) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 094, 196 }, Index = 15, CanGigantamax = true }, // Gengar - new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 14 }, // Munchlax - new(17,01,1) { Species = 092, Ability = A4, Moves = new[]{ 122, 109, 095, 371 }, Index = 14 }, // Gastly - new(17,01,1) { Species = 066, Ability = A4, Moves = new[]{ 067, 043, 116, 279 }, Index = 14 }, // Machop - new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 14 }, // Snorlax - new(30,03,2) { Species = 093, Ability = A4, Moves = new[]{ 325, 095, 122, 101 }, Index = 14 }, // Haunter - new(30,03,2) { Species = 067, Ability = A4, Moves = new[]{ 067, 490, 282, 233 }, Index = 14 }, // Machoke - new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 14 }, // Snorlax - new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 14, CanGigantamax = true }, // Snorlax - new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 506, 188, 085, 261 }, Index = 14 }, // Gengar - new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 506, 188, 085, 261 }, Index = 14, CanGigantamax = true }, // Gengar - new(40,05,3) { Species = 068, Ability = A4, Moves = new[]{ 279, 667, 008, 157 }, Index = 14 }, // Machamp - new(40,05,3) { Species = 068, Ability = A4, Moves = new[]{ 279, 667, 008, 157 }, Index = 14, CanGigantamax = true }, // Machamp - new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 14 }, // Snorlax - new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 14, CanGigantamax = true }, // Snorlax - new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 411, 605 }, Index = 14 }, // Gengar - new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 411, 605 }, Index = 14, CanGigantamax = true }, // Gengar - new(50,08,4) { Species = 068, Ability = A4, Moves = new[]{ 280, 444, 371, 523 }, Index = 14 }, // Machamp - new(50,08,4) { Species = 068, Ability = A4, Moves = new[]{ 280, 444, 371, 523 }, Index = 14, CanGigantamax = true }, // Machamp - new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 14 }, // Snorlax - new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 14, CanGigantamax = true }, // Snorlax - new(60,10,5,SW) { Species = 068, Ability = A4, Moves = new[]{ 238, 007, 008, 089 }, Index = 14 }, // Machamp - new(60,10,5,SW) { Species = 068, Ability = A4, Moves = new[]{ 238, 007, 008, 089 }, Index = 14, CanGigantamax = true }, // Machamp - new(60,10,5,SH) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 094, 196 }, Index = 14 }, // Gengar - new(60,10,5,SH) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 094, 196 }, Index = 14, CanGigantamax = true }, // Gengar + new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 14 }, // Munchlax + new(17,01,1) { Species = 092, Ability = A4, Moves = new[]{ 122, 109, 095, 371 }, Index = 14 }, // Gastly + new(17,01,1) { Species = 066, Ability = A4, Moves = new[]{ 067, 043, 116, 279 }, Index = 14 }, // Machop + new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 14 }, // Snorlax + new(30,03,2) { Species = 093, Ability = A4, Moves = new[]{ 325, 095, 122, 101 }, Index = 14 }, // Haunter + new(30,03,2) { Species = 067, Ability = A4, Moves = new[]{ 067, 490, 282, 233 }, Index = 14 }, // Machoke + new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 14 }, // Snorlax + new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 14, CanGigantamax = true }, // Snorlax + new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 506, 188, 085, 261 }, Index = 14 }, // Gengar + new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 506, 188, 085, 261 }, Index = 14, CanGigantamax = true }, // Gengar + new(40,05,3) { Species = 068, Ability = A4, Moves = new[]{ 279, 667, 008, 157 }, Index = 14 }, // Machamp + new(40,05,3) { Species = 068, Ability = A4, Moves = new[]{ 279, 667, 008, 157 }, Index = 14, CanGigantamax = true }, // Machamp + new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 14 }, // Snorlax + new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 14, CanGigantamax = true }, // Snorlax + new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 411, 605 }, Index = 14 }, // Gengar + new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 411, 605 }, Index = 14, CanGigantamax = true }, // Gengar + new(50,08,4) { Species = 068, Ability = A4, Moves = new[]{ 280, 444, 371, 523 }, Index = 14 }, // Machamp + new(50,08,4) { Species = 068, Ability = A4, Moves = new[]{ 280, 444, 371, 523 }, Index = 14, CanGigantamax = true }, // Machamp + new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 14 }, // Snorlax + new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 14, CanGigantamax = true }, // Snorlax + new(60,10,5,SW) { Species = 068, Ability = A4, Moves = new[]{ 238, 007, 008, 089 }, Index = 14 }, // Machamp + new(60,10,5,SW) { Species = 068, Ability = A4, Moves = new[]{ 238, 007, 008, 089 }, Index = 14, CanGigantamax = true }, // Machamp + new(60,10,5,SH) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 094, 196 }, Index = 14 }, // Gengar + new(60,10,5,SH) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 094, 196 }, Index = 14, CanGigantamax = true }, // Gengar - new(17,01,1) { Species = 001, Ability = A4, Moves = new[]{ 033, 045, 022, 074 }, Index = 12 }, // Bulbasaur - new(17,01,1) { Species = 004, Ability = A4, Moves = new[]{ 010, 045, 052, 108 }, Index = 12 }, // Charmander - new(17,01,1) { Species = 007, Ability = A4, Moves = new[]{ 033, 039, 055, 110 }, Index = 12 }, // Squirtle - new(17,01,1) { Species = 848, Ability = A4, Moves = new[]{ 609, 051, 496, 715 }, Index = 12 }, // Toxel - new(30,03,2) { Species = 001, Ability = A4, Moves = new[]{ 033, 022, 073, 075 }, Index = 12 }, // Bulbasaur - new(30,03,2) { Species = 004, Ability = A4, Moves = new[]{ 010, 052, 225, 424 }, Index = 12 }, // Charmander - new(30,03,2) { Species = 007, Ability = A4, Moves = new[]{ 033, 055, 044, 352 }, Index = 12 }, // Squirtle - new(40,05,3) { Species = 001, Ability = A4, Moves = new[]{ 073, 075, 077, 402 }, Index = 12 }, // Bulbasaur - new(40,05,3) { Species = 004, Ability = A4, Moves = new[]{ 424, 225, 163, 108 }, Index = 12 }, // Charmander - new(40,05,3) { Species = 007, Ability = A4, Moves = new[]{ 055, 229, 044, 352 }, Index = 12 }, // Squirtle - new(50,08,4) { Species = 002, Ability = A4, Moves = new[]{ 188, 412, 075, 034 }, Index = 12 }, // Ivysaur - new(50,08,4) { Species = 005, Ability = A4, Moves = new[]{ 257, 242, 009, 053 }, Index = 12 }, // Charmeleon - new(50,08,4) { Species = 008, Ability = A4, Moves = new[]{ 330, 396, 503, 428 }, Index = 12 }, // Wartortle - //new(100,10,6) { Species = 150, Ability = A0, Moves = new[]{ 540, 053, 396, 059 }, Index = 12, Shiny = Shiny.Never }, // Mewtwo - //new(100,10,6) { Species = 150, Ability = A0, Moves = new[]{ 428, 007, 089, 280 }, Index = 12, Shiny = Shiny.Never }, // Mewtwo - //new(100,10,6) { Species = 150, Ability = A0, Moves = new[]{ 540, 126, 411, 059 }, Index = 12, Shiny = Shiny.Never }, // Mewtwo - new(17,01,1,SW) { Species = 098, Ability = A4, Moves = new[]{ 055, 043, 106, 232 }, Index = 12 }, // Krabby - new(17,01,1,SW) { Species = 859, Ability = A4, Moves = new[]{ 044, 260, 590, 372 }, Index = 12 }, // Impidimp - new(30,03,2,SW) { Species = 099, Ability = A4, Moves = new[]{ 232, 341, 061, 023 }, Index = 12, CanGigantamax = true }, // Kingler - new(30,03,2,SW) { Species = 859, Ability = A4, Moves = new[]{ 389, 577, 260, 279 }, Index = 12 }, // Impidimp - new(30,03,2,SW) { Species = 849, Ability = A4, Moves = new[]{ 084, 209, 268, 175 }, Index = 12 }, // Toxtricity - new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 12, CanGigantamax = true }, // Kingler - new(40,05,3,SW) { Species = 860, Ability = A4, Moves = new[]{ 492, 577, 421, 141 }, Index = 12 }, // Morgrem - new(40,05,3,SW) { Species = 849, Ability = A4, Moves = new[]{ 085, 474, 496, 103 }, Index = 12, CanGigantamax = true }, // Toxtricity - new(50,08,4,SW) { Species = 099, Ability = A4, Moves = new[]{ 359, 667, 157, 534 }, Index = 12, CanGigantamax = true }, // Kingler - new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 12, CanGigantamax = true }, // Grimmsnarl - new(50,08,4,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 12, CanGigantamax = true }, // Toxtricity - new(60,10,5,SW) { Species = 099, Ability = A4, Moves = new[]{ 152, 667, 157, 404 }, Index = 12, CanGigantamax = true }, // Kingler - new(60,10,5,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 409, 007 }, Index = 12, CanGigantamax = true }, // Grimmsnarl - new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 12, CanGigantamax = true }, // Toxtricity - new(17,01,1,SH) { Species = 825, Ability = A4, Moves = new[]{ 093, 522, 113, 115 }, Index = 12 }, // Dottler - new(17,01,1,SH) { Species = 856, Ability = A4, Moves = new[]{ 093, 589, 791, 574 }, Index = 12 }, // Hatenna - new(30,03,2,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 496, 095 }, Index = 12, CanGigantamax = true }, // Orbeetle - new(30,03,2,SH) { Species = 856, Ability = A4, Moves = new[]{ 605, 060, 345, 347 }, Index = 12 }, // Hatenna - new(30,03,2,SH) { Species = 849, Ability = A4, Moves = new[]{ 599, 209, 268, 175 }, Index = 12, Form = 1 }, // Toxtricity-1 - new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 202, 109 }, Index = 12, CanGigantamax = true }, // Orbeetle - new(40,05,3,SH) { Species = 857, Ability = A4, Moves = new[]{ 605, 345, 399, 500 }, Index = 12 }, // Hattrem - new(40,05,3,SH) { Species = 849, Ability = A4, Moves = new[]{ 085, 599, 496, 103 }, Index = 12, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(50,08,4,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 12, CanGigantamax = true }, // Orbeetle - new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 12, CanGigantamax = true }, // Hatterene - new(50,08,4,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 12, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(60,10,5,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 247, 347 }, Index = 12, CanGigantamax = true }, // Orbeetle - new(60,10,5,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 500 }, Index = 12, CanGigantamax = true }, // Hatterene - new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 12, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(17,01,1) { Species = 001, Ability = A4, Moves = new[]{ 033, 045, 022, 074 }, Index = 12 }, // Bulbasaur + new(17,01,1) { Species = 004, Ability = A4, Moves = new[]{ 010, 045, 052, 108 }, Index = 12 }, // Charmander + new(17,01,1) { Species = 007, Ability = A4, Moves = new[]{ 033, 039, 055, 110 }, Index = 12 }, // Squirtle + new(17,01,1) { Species = 848, Ability = A4, Moves = new[]{ 609, 051, 496, 715 }, Index = 12 }, // Toxel + new(30,03,2) { Species = 001, Ability = A4, Moves = new[]{ 033, 022, 073, 075 }, Index = 12 }, // Bulbasaur + new(30,03,2) { Species = 004, Ability = A4, Moves = new[]{ 010, 052, 225, 424 }, Index = 12 }, // Charmander + new(30,03,2) { Species = 007, Ability = A4, Moves = new[]{ 033, 055, 044, 352 }, Index = 12 }, // Squirtle + new(40,05,3) { Species = 001, Ability = A4, Moves = new[]{ 073, 075, 077, 402 }, Index = 12 }, // Bulbasaur + new(40,05,3) { Species = 004, Ability = A4, Moves = new[]{ 424, 225, 163, 108 }, Index = 12 }, // Charmander + new(40,05,3) { Species = 007, Ability = A4, Moves = new[]{ 055, 229, 044, 352 }, Index = 12 }, // Squirtle + new(50,08,4) { Species = 002, Ability = A4, Moves = new[]{ 188, 412, 075, 034 }, Index = 12 }, // Ivysaur + new(50,08,4) { Species = 005, Ability = A4, Moves = new[]{ 257, 242, 009, 053 }, Index = 12 }, // Charmeleon + new(50,08,4) { Species = 008, Ability = A4, Moves = new[]{ 330, 396, 503, 428 }, Index = 12 }, // Wartortle + //new(100,10,6) { Species = 150, Ability = A0, Moves = new[]{ 540, 053, 396, 059 }, Index = 12, Shiny = Shiny.Never }, // Mewtwo + //new(100,10,6) { Species = 150, Ability = A0, Moves = new[]{ 428, 007, 089, 280 }, Index = 12, Shiny = Shiny.Never }, // Mewtwo + //new(100,10,6) { Species = 150, Ability = A0, Moves = new[]{ 540, 126, 411, 059 }, Index = 12, Shiny = Shiny.Never }, // Mewtwo + new(17,01,1,SW) { Species = 098, Ability = A4, Moves = new[]{ 055, 043, 106, 232 }, Index = 12 }, // Krabby + new(17,01,1,SW) { Species = 859, Ability = A4, Moves = new[]{ 044, 260, 590, 372 }, Index = 12 }, // Impidimp + new(30,03,2,SW) { Species = 099, Ability = A4, Moves = new[]{ 232, 341, 061, 023 }, Index = 12, CanGigantamax = true }, // Kingler + new(30,03,2,SW) { Species = 859, Ability = A4, Moves = new[]{ 389, 577, 260, 279 }, Index = 12 }, // Impidimp + new(30,03,2,SW) { Species = 849, Ability = A4, Moves = new[]{ 084, 209, 268, 175 }, Index = 12 }, // Toxtricity + new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 12, CanGigantamax = true }, // Kingler + new(40,05,3,SW) { Species = 860, Ability = A4, Moves = new[]{ 492, 577, 421, 141 }, Index = 12 }, // Morgrem + new(40,05,3,SW) { Species = 849, Ability = A4, Moves = new[]{ 085, 474, 496, 103 }, Index = 12, CanGigantamax = true }, // Toxtricity + new(50,08,4,SW) { Species = 099, Ability = A4, Moves = new[]{ 359, 667, 157, 534 }, Index = 12, CanGigantamax = true }, // Kingler + new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 12, CanGigantamax = true }, // Grimmsnarl + new(50,08,4,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 12, CanGigantamax = true }, // Toxtricity + new(60,10,5,SW) { Species = 099, Ability = A4, Moves = new[]{ 152, 667, 157, 404 }, Index = 12, CanGigantamax = true }, // Kingler + new(60,10,5,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 409, 007 }, Index = 12, CanGigantamax = true }, // Grimmsnarl + new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 12, CanGigantamax = true }, // Toxtricity + new(17,01,1,SH) { Species = 825, Ability = A4, Moves = new[]{ 093, 522, 113, 115 }, Index = 12 }, // Dottler + new(17,01,1,SH) { Species = 856, Ability = A4, Moves = new[]{ 093, 589, 791, 574 }, Index = 12 }, // Hatenna + new(30,03,2,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 496, 095 }, Index = 12, CanGigantamax = true }, // Orbeetle + new(30,03,2,SH) { Species = 856, Ability = A4, Moves = new[]{ 605, 060, 345, 347 }, Index = 12 }, // Hatenna + new(30,03,2,SH) { Species = 849, Ability = A4, Moves = new[]{ 599, 209, 268, 175 }, Index = 12, Form = 1 }, // Toxtricity-1 + new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 202, 109 }, Index = 12, CanGigantamax = true }, // Orbeetle + new(40,05,3,SH) { Species = 857, Ability = A4, Moves = new[]{ 605, 345, 399, 500 }, Index = 12 }, // Hattrem + new(40,05,3,SH) { Species = 849, Ability = A4, Moves = new[]{ 085, 599, 496, 103 }, Index = 12, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(50,08,4,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 12, CanGigantamax = true }, // Orbeetle + new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 12, CanGigantamax = true }, // Hatterene + new(50,08,4,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 12, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(60,10,5,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 247, 347 }, Index = 12, CanGigantamax = true }, // Orbeetle + new(60,10,5,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 500 }, Index = 12, CanGigantamax = true }, // Hatterene + new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 12, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(17,01,1) { Species = 848, Ability = A4, Moves = new[]{ 609, 051, 496, 715 }, Index = 11 }, // Toxel - new(17,01,1,SW) { Species = 098, Ability = A4, Moves = new[]{ 055, 043, 106, 232 }, Index = 11 }, // Krabby - new(17,01,1,SW) { Species = 859, Ability = A4, Moves = new[]{ 044, 260, 590, 372 }, Index = 11 }, // Impidimp - new(30,03,2,SW) { Species = 099, Ability = A4, Moves = new[]{ 232, 341, 061, 023 }, Index = 11 }, // Kingler - new(30,03,2,SW) { Species = 099, Ability = A4, Moves = new[]{ 232, 341, 061, 023 }, Index = 11, CanGigantamax = true }, // Kingler - new(30,03,2,SW) { Species = 859, Ability = A4, Moves = new[]{ 389, 577, 260, 279 }, Index = 11 }, // Impidimp - new(30,03,2,SW) { Species = 849, Ability = A4, Moves = new[]{ 474, 209, 268, 175 }, Index = 11 }, // Toxtricity - new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 11 }, // Kingler - new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 11, CanGigantamax = true }, // Kingler - new(40,05,3,SW) { Species = 860, Ability = A4, Moves = new[]{ 492, 577, 421, 141 }, Index = 11 }, // Morgrem - new(40,05,3,SW) { Species = 849, Ability = A4, Moves = new[]{ 085, 474, 496, 103 }, Index = 11 }, // Toxtricity - new(40,05,3,SW) { Species = 849, Ability = A4, Moves = new[]{ 085, 474, 496, 103 }, Index = 11, CanGigantamax = true }, // Toxtricity - new(50,08,4,SW) { Species = 099, Ability = A4, Moves = new[]{ 359, 667, 157, 534 }, Index = 11 }, // Kingler - new(50,08,4,SW) { Species = 099, Ability = A4, Moves = new[]{ 359, 667, 157, 534 }, Index = 11, CanGigantamax = true }, // Kingler - new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 11 }, // Grimmsnarl - new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 11, CanGigantamax = true }, // Grimmsnarl - new(50,08,4,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 11 }, // Toxtricity - new(50,08,4,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 11, CanGigantamax = true }, // Toxtricity - new(60,10,5,SW) { Species = 099, Ability = A4, Moves = new[]{ 152, 667, 157, 404 }, Index = 11 }, // Kingler - new(60,10,5,SW) { Species = 099, Ability = A4, Moves = new[]{ 152, 667, 157, 404 }, Index = 11, CanGigantamax = true }, // Kingler - new(60,10,5,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 409, 007 }, Index = 11 }, // Grimmsnarl - new(60,10,5,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 409, 007 }, Index = 11, CanGigantamax = true }, // Grimmsnarl - new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 11 }, // Toxtricity - new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 11, CanGigantamax = true }, // Toxtricity - new(17,01,1,SH) { Species = 825, Ability = A4, Moves = new[]{ 093, 522, 113, 115 }, Index = 11 }, // Dottler - new(17,01,1,SH) { Species = 856, Ability = A4, Moves = new[]{ 093, 589, 791, 574 }, Index = 11 }, // Hatenna - new(30,03,2,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 496, 095 }, Index = 11 }, // Orbeetle - new(30,03,2,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 496, 095 }, Index = 11, CanGigantamax = true }, // Orbeetle - new(30,03,2,SH) { Species = 856, Ability = A4, Moves = new[]{ 605, 060, 345, 347 }, Index = 11 }, // Hatenna - new(30,03,2,SH) { Species = 849, Ability = A4, Moves = new[]{ 599, 209, 268, 175 }, Index = 11, Form = 1 }, // Toxtricity-1 - new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 202, 109 }, Index = 11 }, // Orbeetle - new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 202, 109 }, Index = 11, CanGigantamax = true }, // Orbeetle - new(40,05,3,SH) { Species = 857, Ability = A4, Moves = new[]{ 605, 345, 399, 500 }, Index = 11 }, // Hattrem - new(40,05,3,SH) { Species = 849, Ability = A4, Moves = new[]{ 085, 599, 496, 103 }, Index = 11, Form = 1 }, // Toxtricity-1 - new(40,05,3,SH) { Species = 849, Ability = A4, Moves = new[]{ 085, 599, 496, 103 }, Index = 11, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(50,08,4,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 11 }, // Orbeetle - new(50,08,4,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 11, CanGigantamax = true }, // Orbeetle - new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 11 }, // Hatterene - new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 11, CanGigantamax = true }, // Hatterene - new(50,08,4,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 11, Form = 1 }, // Toxtricity-1 - new(50,08,4,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 11, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(60,10,5,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 247, 347 }, Index = 11 }, // Orbeetle - new(60,10,5,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 247, 347 }, Index = 11, CanGigantamax = true }, // Orbeetle - new(60,10,5,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 500 }, Index = 11 }, // Hatterene - new(60,10,5,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 500 }, Index = 11, CanGigantamax = true }, // Hatterene - new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 11, Form = 1 }, // Toxtricity-1 - new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 11, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(17,01,1) { Species = 848, Ability = A4, Moves = new[]{ 609, 051, 496, 715 }, Index = 11 }, // Toxel + new(17,01,1,SW) { Species = 098, Ability = A4, Moves = new[]{ 055, 043, 106, 232 }, Index = 11 }, // Krabby + new(17,01,1,SW) { Species = 859, Ability = A4, Moves = new[]{ 044, 260, 590, 372 }, Index = 11 }, // Impidimp + new(30,03,2,SW) { Species = 099, Ability = A4, Moves = new[]{ 232, 341, 061, 023 }, Index = 11 }, // Kingler + new(30,03,2,SW) { Species = 099, Ability = A4, Moves = new[]{ 232, 341, 061, 023 }, Index = 11, CanGigantamax = true }, // Kingler + new(30,03,2,SW) { Species = 859, Ability = A4, Moves = new[]{ 389, 577, 260, 279 }, Index = 11 }, // Impidimp + new(30,03,2,SW) { Species = 849, Ability = A4, Moves = new[]{ 474, 209, 268, 175 }, Index = 11 }, // Toxtricity + new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 11 }, // Kingler + new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 11, CanGigantamax = true }, // Kingler + new(40,05,3,SW) { Species = 860, Ability = A4, Moves = new[]{ 492, 577, 421, 141 }, Index = 11 }, // Morgrem + new(40,05,3,SW) { Species = 849, Ability = A4, Moves = new[]{ 085, 474, 496, 103 }, Index = 11 }, // Toxtricity + new(40,05,3,SW) { Species = 849, Ability = A4, Moves = new[]{ 085, 474, 496, 103 }, Index = 11, CanGigantamax = true }, // Toxtricity + new(50,08,4,SW) { Species = 099, Ability = A4, Moves = new[]{ 359, 667, 157, 534 }, Index = 11 }, // Kingler + new(50,08,4,SW) { Species = 099, Ability = A4, Moves = new[]{ 359, 667, 157, 534 }, Index = 11, CanGigantamax = true }, // Kingler + new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 11 }, // Grimmsnarl + new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 11, CanGigantamax = true }, // Grimmsnarl + new(50,08,4,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 11 }, // Toxtricity + new(50,08,4,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 11, CanGigantamax = true }, // Toxtricity + new(60,10,5,SW) { Species = 099, Ability = A4, Moves = new[]{ 152, 667, 157, 404 }, Index = 11 }, // Kingler + new(60,10,5,SW) { Species = 099, Ability = A4, Moves = new[]{ 152, 667, 157, 404 }, Index = 11, CanGigantamax = true }, // Kingler + new(60,10,5,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 409, 007 }, Index = 11 }, // Grimmsnarl + new(60,10,5,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 409, 007 }, Index = 11, CanGigantamax = true }, // Grimmsnarl + new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 11 }, // Toxtricity + new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 11, CanGigantamax = true }, // Toxtricity + new(17,01,1,SH) { Species = 825, Ability = A4, Moves = new[]{ 093, 522, 113, 115 }, Index = 11 }, // Dottler + new(17,01,1,SH) { Species = 856, Ability = A4, Moves = new[]{ 093, 589, 791, 574 }, Index = 11 }, // Hatenna + new(30,03,2,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 496, 095 }, Index = 11 }, // Orbeetle + new(30,03,2,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 496, 095 }, Index = 11, CanGigantamax = true }, // Orbeetle + new(30,03,2,SH) { Species = 856, Ability = A4, Moves = new[]{ 605, 060, 345, 347 }, Index = 11 }, // Hatenna + new(30,03,2,SH) { Species = 849, Ability = A4, Moves = new[]{ 599, 209, 268, 175 }, Index = 11, Form = 1 }, // Toxtricity-1 + new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 202, 109 }, Index = 11 }, // Orbeetle + new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 202, 109 }, Index = 11, CanGigantamax = true }, // Orbeetle + new(40,05,3,SH) { Species = 857, Ability = A4, Moves = new[]{ 605, 345, 399, 500 }, Index = 11 }, // Hattrem + new(40,05,3,SH) { Species = 849, Ability = A4, Moves = new[]{ 085, 599, 496, 103 }, Index = 11, Form = 1 }, // Toxtricity-1 + new(40,05,3,SH) { Species = 849, Ability = A4, Moves = new[]{ 085, 599, 496, 103 }, Index = 11, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(50,08,4,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 11 }, // Orbeetle + new(50,08,4,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 11, CanGigantamax = true }, // Orbeetle + new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 11 }, // Hatterene + new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 11, CanGigantamax = true }, // Hatterene + new(50,08,4,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 11, Form = 1 }, // Toxtricity-1 + new(50,08,4,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 11, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(60,10,5,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 247, 347 }, Index = 11 }, // Orbeetle + new(60,10,5,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 247, 347 }, Index = 11, CanGigantamax = true }, // Orbeetle + new(60,10,5,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 500 }, Index = 11 }, // Hatterene + new(60,10,5,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 500 }, Index = 11, CanGigantamax = true }, // Hatterene + new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 11, Form = 1 }, // Toxtricity-1 + new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 188, 506 }, Index = 11, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(17,01,1) { Species = 868, Ability = A4, Moves = new[]{ 033, 186, 577, 496 }, Index = 10, CanGigantamax = true }, // Milcery - new(30,03,2) { Species = 868, Ability = A4, Moves = new[]{ 577, 186, 263, 500 }, Index = 10, CanGigantamax = true }, // Milcery - new(40,05,3) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 213 }, Index = 10, CanGigantamax = true }, // Milcery - new(50,08,4) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 10, CanGigantamax = true }, // Milcery - new(60,10,5) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 10, CanGigantamax = true }, // Milcery - new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 10, CanGigantamax = true }, // Kingler - new(40,05,3,SW) { Species = 860, Ability = A4, Moves = new[]{ 492, 577, 421, 141 }, Index = 10 }, // Morgrem - new(40,05,3,SW) { Species = 849, Ability = A4, Moves = new[]{ 085, 474, 496, 103 }, Index = 10, CanGigantamax = true }, // Toxtricity - new(50,08,4,SW) { Species = 099, Ability = A4, Moves = new[]{ 359, 667, 157, 534 }, Index = 10, CanGigantamax = true }, // Kingler - new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 10, CanGigantamax = true }, // Grimmsnarl - new(50,08,4,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 10, CanGigantamax = true }, // Toxtricity - new(60,10,5,SW) { Species = 099, Ability = A4, Moves = new[]{ 152, 667, 157, 404 }, Index = 10, CanGigantamax = true }, // Kingler - new(60,10,5,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 409, 007 }, Index = 10, CanGigantamax = true }, // Grimmsnarl - new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 482, 506 }, Index = 10, CanGigantamax = true }, // Toxtricity - new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 522, 060, 109, 202 }, Index = 10, CanGigantamax = true }, // Orbeetle - new(40,05,3,SH) { Species = 857, Ability = A4, Moves = new[]{ 605, 345, 399, 500 }, Index = 10 }, // Hattrem - new(40,05,3,SH) { Species = 849, Ability = A4, Moves = new[]{ 085, 599, 496, 103 }, Index = 10, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(50,08,4,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 10, CanGigantamax = true }, // Orbeetle - new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 10, CanGigantamax = true }, // Hatterene - new(50,08,4,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 10, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(60,10,5,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 247, 347 }, Index = 10, CanGigantamax = true }, // Orbeetle - new(60,10,5,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 500 }, Index = 10, CanGigantamax = true }, // Hatterene - new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 482, 506 }, Index = 10, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(17,01,1) { Species = 868, Ability = A4, Moves = new[]{ 033, 186, 577, 496 }, Index = 10, CanGigantamax = true }, // Milcery + new(30,03,2) { Species = 868, Ability = A4, Moves = new[]{ 577, 186, 263, 500 }, Index = 10, CanGigantamax = true }, // Milcery + new(40,05,3) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 213 }, Index = 10, CanGigantamax = true }, // Milcery + new(50,08,4) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 10, CanGigantamax = true }, // Milcery + new(60,10,5) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 10, CanGigantamax = true }, // Milcery + new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 10, CanGigantamax = true }, // Kingler + new(40,05,3,SW) { Species = 860, Ability = A4, Moves = new[]{ 492, 577, 421, 141 }, Index = 10 }, // Morgrem + new(40,05,3,SW) { Species = 849, Ability = A4, Moves = new[]{ 085, 474, 496, 103 }, Index = 10, CanGigantamax = true }, // Toxtricity + new(50,08,4,SW) { Species = 099, Ability = A4, Moves = new[]{ 359, 667, 157, 534 }, Index = 10, CanGigantamax = true }, // Kingler + new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 10, CanGigantamax = true }, // Grimmsnarl + new(50,08,4,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 10, CanGigantamax = true }, // Toxtricity + new(60,10,5,SW) { Species = 099, Ability = A4, Moves = new[]{ 152, 667, 157, 404 }, Index = 10, CanGigantamax = true }, // Kingler + new(60,10,5,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 409, 007 }, Index = 10, CanGigantamax = true }, // Grimmsnarl + new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 482, 506 }, Index = 10, CanGigantamax = true }, // Toxtricity + new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 522, 060, 109, 202 }, Index = 10, CanGigantamax = true }, // Orbeetle + new(40,05,3,SH) { Species = 857, Ability = A4, Moves = new[]{ 605, 345, 399, 500 }, Index = 10 }, // Hattrem + new(40,05,3,SH) { Species = 849, Ability = A4, Moves = new[]{ 085, 599, 496, 103 }, Index = 10, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(50,08,4,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 10, CanGigantamax = true }, // Orbeetle + new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 10, CanGigantamax = true }, // Hatterene + new(50,08,4,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 10, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(60,10,5,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 247, 347 }, Index = 10, CanGigantamax = true }, // Orbeetle + new(60,10,5,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 500 }, Index = 10, CanGigantamax = true }, // Hatterene + new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 586, 482, 506 }, Index = 10, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(17,01,1) { Species = 868, Ability = A4, Moves = new[]{ 033, 186, 577, 496 }, Index = 9, CanGigantamax = true }, // Milcery - new(30,03,2) { Species = 868, Ability = A4, Moves = new[]{ 577, 186, 263, 500 }, Index = 9, CanGigantamax = true }, // Milcery - new(40,05,3) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 213 }, Index = 9, CanGigantamax = true }, // Milcery - new(50,08,4) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 9, CanGigantamax = true }, // Milcery - new(60,10,5) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 9, CanGigantamax = true }, // Milcery - new(40,05,3,SW) { Species = 841, Ability = A4, Moves = new[]{ 788, 406, 512, 491 }, Index = 9, CanGigantamax = true }, // Flapple - new(40,05,3,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 510, 479, 488 }, Index = 9, CanGigantamax = true }, // Coalossal - new(50,08,4,SW) { Species = 841, Ability = A4, Moves = new[]{ 788, 407, 491, 334 }, Index = 9, CanGigantamax = true }, // Flapple - new(50,08,4,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 261 }, Index = 9, CanGigantamax = true }, // Coalossal - new(60,10,5,SW) { Species = 841, Ability = A4, Moves = new[]{ 407, 788, 512, 349 }, Index = 9, CanGigantamax = true }, // Flapple - new(60,10,5,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 523 }, Index = 9, CanGigantamax = true }, // Coalossal - new(40,05,3,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 9, CanGigantamax = true }, // Appletun - new(40,05,3,SH) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 034 }, Index = 9, CanGigantamax = true }, // Lapras - new(50,08,4,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 202, 406, 089 }, Index = 9, CanGigantamax = true }, // Appletun - new(50,08,4,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 058, 246, 523 }, Index = 9, CanGigantamax = true }, // Lapras - new(60,10,5,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 406, 412, 089 }, Index = 9, CanGigantamax = true }, // Appletun - new(60,10,5,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 573, 329 }, Index = 9, CanGigantamax = true }, // Lapras + new(17,01,1) { Species = 868, Ability = A4, Moves = new[]{ 033, 186, 577, 496 }, Index = 9, CanGigantamax = true }, // Milcery + new(30,03,2) { Species = 868, Ability = A4, Moves = new[]{ 577, 186, 263, 500 }, Index = 9, CanGigantamax = true }, // Milcery + new(40,05,3) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 213 }, Index = 9, CanGigantamax = true }, // Milcery + new(50,08,4) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 9, CanGigantamax = true }, // Milcery + new(60,10,5) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 9, CanGigantamax = true }, // Milcery + new(40,05,3,SW) { Species = 841, Ability = A4, Moves = new[]{ 788, 406, 512, 491 }, Index = 9, CanGigantamax = true }, // Flapple + new(40,05,3,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 510, 479, 488 }, Index = 9, CanGigantamax = true }, // Coalossal + new(50,08,4,SW) { Species = 841, Ability = A4, Moves = new[]{ 788, 407, 491, 334 }, Index = 9, CanGigantamax = true }, // Flapple + new(50,08,4,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 261 }, Index = 9, CanGigantamax = true }, // Coalossal + new(60,10,5,SW) { Species = 841, Ability = A4, Moves = new[]{ 407, 788, 512, 349 }, Index = 9, CanGigantamax = true }, // Flapple + new(60,10,5,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 523 }, Index = 9, CanGigantamax = true }, // Coalossal + new(40,05,3,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 9, CanGigantamax = true }, // Appletun + new(40,05,3,SH) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 034 }, Index = 9, CanGigantamax = true }, // Lapras + new(50,08,4,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 202, 406, 089 }, Index = 9, CanGigantamax = true }, // Appletun + new(50,08,4,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 058, 246, 523 }, Index = 9, CanGigantamax = true }, // Lapras + new(60,10,5,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 406, 412, 089 }, Index = 9, CanGigantamax = true }, // Appletun + new(60,10,5,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 573, 329 }, Index = 9, CanGigantamax = true }, // Lapras - new(17,01,1) { Species = 840, Ability = A4, Moves = new[]{ 110, 310, 389, 213 }, Index = 8 }, // Applin - new(17,01,1) { Species = 840, Ability = A2, Moves = new[]{ 110, 310, 389, 213 }, Index = 8 }, // Applin - new(17,01,1) { Species = 868, Ability = A4, Moves = new[]{ 577, 033, 186, 263 }, Index = 8 }, // Milcery - new(17,01,1) { Species = 868, Ability = A2, Moves = new[]{ 577, 033, 186, 263 }, Index = 8 }, // Milcery - new(30,03,2) { Species = 869, Ability = A4, Moves = new[]{ 577, 213, 033, 186 }, Index = 8, CanGigantamax = true }, // Alcremie - new(17,01,1,SW) { Species = 837, Ability = A4, Moves = new[]{ 479, 033, 108, 189 }, Index = 8 }, // Rolycoly - new(17,01,1,SW) { Species = 837, Ability = A2, Moves = new[]{ 479, 033, 108, 189 }, Index = 8 }, // Rolycoly - new(30,03,2,SW) { Species = 841, Ability = A4, Moves = new[]{ 406, 491, 017, 225 }, Index = 8 }, // Flapple - new(30,03,2,SW) { Species = 841, Ability = A2, Moves = new[]{ 406, 491, 017, 225 }, Index = 8 }, // Flapple - new(30,03,2,SW) { Species = 838, Ability = A4, Moves = new[]{ 246, 510, 479, 189 }, Index = 8 }, // Carkol - new(30,03,2,SW) { Species = 838, Ability = A2, Moves = new[]{ 246, 510, 479, 189 }, Index = 8 }, // Carkol - new(40,05,3,SW) { Species = 841, Ability = A4, Moves = new[]{ 788, 406, 512, 491 }, Index = 8 }, // Flapple - new(40,05,3,SW) { Species = 841, Ability = A4, Moves = new[]{ 788, 406, 512, 491 }, Index = 8, CanGigantamax = true }, // Flapple - new(40,05,3,SW) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 202, 186 }, Index = 8, Form = 3, CanGigantamax = true }, // Alcremie-3 - new(40,05,3,SW) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 202, 186 }, Index = 8, Form = 4, CanGigantamax = true }, // Alcremie-4 - new(40,05,3,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 510, 479, 488 }, Index = 8 }, // Coalossal - new(40,05,3,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 510, 479, 488 }, Index = 8, CanGigantamax = true }, // Coalossal - new(50,08,4,SW) { Species = 841, Ability = A4, Moves = new[]{ 407, 788, 491, 334 }, Index = 8 }, // Flapple - new(50,08,4,SW) { Species = 841, Ability = A4, Moves = new[]{ 407, 788, 491, 334 }, Index = 8, CanGigantamax = true }, // Flapple - new(50,08,4,SW) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 094, 151 }, Index = 8, Form = 1, CanGigantamax = true }, // Alcremie-1 - new(50,08,4,SW) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 094, 151 }, Index = 8, Form = 2, CanGigantamax = true }, // Alcremie-2 - new(50,08,4,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 261 }, Index = 8 }, // Coalossal - new(50,08,4,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 261 }, Index = 8, CanGigantamax = true }, // Coalossal - new(60,10,5,SW) { Species = 841, Ability = A4, Moves = new[]{ 407, 788, 512, 349 }, Index = 8 }, // Flapple - new(60,10,5,SW) { Species = 841, Ability = A4, Moves = new[]{ 407, 788, 512, 349 }, Index = 8, CanGigantamax = true }, // Flapple - new(60,10,5,SW) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 595, 500 }, Index = 8, Form = 5, CanGigantamax = true }, // Alcremie-5 - new(60,10,5,SW) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 595, 500 }, Index = 8, Form = 6, CanGigantamax = true }, // Alcremie-6 - new(60,10,5,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 523 }, Index = 8 }, // Coalossal - new(60,10,5,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 523 }, Index = 8, CanGigantamax = true }, // Coalossal - new(17,01,1,SH) { Species = 131, Ability = A4, Moves = new[]{ 055, 496, 045, 047 }, Index = 8 }, // Lapras - new(17,01,1,SH) { Species = 131, Ability = A2, Moves = new[]{ 055, 496, 045, 047 }, Index = 8 }, // Lapras - new(30,03,2,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 029, 389, 073 }, Index = 8 }, // Appletun - new(30,03,2,SH) { Species = 842, Ability = A2, Moves = new[]{ 787, 029, 389, 073 }, Index = 8 }, // Appletun - new(30,03,2,SH) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 047 }, Index = 8 }, // Lapras - new(30,03,2,SH) { Species = 131, Ability = A2, Moves = new[]{ 352, 420, 109, 047 }, Index = 8 }, // Lapras - new(40,05,3,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 8 }, // Appletun - new(40,05,3,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 8, CanGigantamax = true }, // Appletun - new(40,05,3,SH) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 202, 186 }, Index = 8, Form = 1, CanGigantamax = true }, // Alcremie-1 - new(40,05,3,SH) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 202, 186 }, Index = 8, Form = 2, CanGigantamax = true }, // Alcremie-2 - new(40,05,3,SH) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 034 }, Index = 8 }, // Lapras - new(40,05,3,SH) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 034 }, Index = 8, CanGigantamax = true }, // Lapras - new(50,08,4,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 202, 406, 089 }, Index = 8 }, // Appletun - new(50,08,4,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 202, 406, 089 }, Index = 8, CanGigantamax = true }, // Appletun - new(50,08,4,SH) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 094, 151 }, Index = 8, Form = 7, CanGigantamax = true }, // Alcremie-7 - new(50,08,4,SH) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 094, 151 }, Index = 8, Form = 8, CanGigantamax = true }, // Alcremie-8 - new(50,08,4,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 058, 246, 523 }, Index = 8 }, // Lapras - new(50,08,4,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 058, 246, 523 }, Index = 8, CanGigantamax = true }, // Lapras - new(60,10,5,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 406, 412, 089 }, Index = 8 }, // Appletun - new(60,10,5,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 406, 412, 089 }, Index = 8, CanGigantamax = true }, // Appletun - new(60,10,5,SH) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 595, 500 }, Index = 8, Form = 3, CanGigantamax = true }, // Alcremie-3 - new(60,10,5,SH) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 595, 500 }, Index = 8, Form = 4, CanGigantamax = true }, // Alcremie-4 - new(60,10,5,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 573, 329 }, Index = 8 }, // Lapras - new(60,10,5,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 573, 329 }, Index = 8, CanGigantamax = true }, // Lapras + new(17,01,1) { Species = 840, Ability = A4, Moves = new[]{ 110, 310, 389, 213 }, Index = 8 }, // Applin + new(17,01,1) { Species = 840, Ability = A2, Moves = new[]{ 110, 310, 389, 213 }, Index = 8 }, // Applin + new(17,01,1) { Species = 868, Ability = A4, Moves = new[]{ 577, 033, 186, 263 }, Index = 8 }, // Milcery + new(17,01,1) { Species = 868, Ability = A2, Moves = new[]{ 577, 033, 186, 263 }, Index = 8 }, // Milcery + new(30,03,2) { Species = 869, Ability = A4, Moves = new[]{ 577, 213, 033, 186 }, Index = 8, CanGigantamax = true }, // Alcremie + new(17,01,1,SW) { Species = 837, Ability = A4, Moves = new[]{ 479, 033, 108, 189 }, Index = 8 }, // Rolycoly + new(17,01,1,SW) { Species = 837, Ability = A2, Moves = new[]{ 479, 033, 108, 189 }, Index = 8 }, // Rolycoly + new(30,03,2,SW) { Species = 841, Ability = A4, Moves = new[]{ 406, 491, 017, 225 }, Index = 8 }, // Flapple + new(30,03,2,SW) { Species = 841, Ability = A2, Moves = new[]{ 406, 491, 017, 225 }, Index = 8 }, // Flapple + new(30,03,2,SW) { Species = 838, Ability = A4, Moves = new[]{ 246, 510, 479, 189 }, Index = 8 }, // Carkol + new(30,03,2,SW) { Species = 838, Ability = A2, Moves = new[]{ 246, 510, 479, 189 }, Index = 8 }, // Carkol + new(40,05,3,SW) { Species = 841, Ability = A4, Moves = new[]{ 788, 406, 512, 491 }, Index = 8 }, // Flapple + new(40,05,3,SW) { Species = 841, Ability = A4, Moves = new[]{ 788, 406, 512, 491 }, Index = 8, CanGigantamax = true }, // Flapple + new(40,05,3,SW) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 202, 186 }, Index = 8, Form = 3, CanGigantamax = true }, // Alcremie-3 + new(40,05,3,SW) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 202, 186 }, Index = 8, Form = 4, CanGigantamax = true }, // Alcremie-4 + new(40,05,3,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 510, 479, 488 }, Index = 8 }, // Coalossal + new(40,05,3,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 510, 479, 488 }, Index = 8, CanGigantamax = true }, // Coalossal + new(50,08,4,SW) { Species = 841, Ability = A4, Moves = new[]{ 407, 788, 491, 334 }, Index = 8 }, // Flapple + new(50,08,4,SW) { Species = 841, Ability = A4, Moves = new[]{ 407, 788, 491, 334 }, Index = 8, CanGigantamax = true }, // Flapple + new(50,08,4,SW) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 094, 151 }, Index = 8, Form = 1, CanGigantamax = true }, // Alcremie-1 + new(50,08,4,SW) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 094, 151 }, Index = 8, Form = 2, CanGigantamax = true }, // Alcremie-2 + new(50,08,4,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 261 }, Index = 8 }, // Coalossal + new(50,08,4,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 261 }, Index = 8, CanGigantamax = true }, // Coalossal + new(60,10,5,SW) { Species = 841, Ability = A4, Moves = new[]{ 407, 788, 512, 349 }, Index = 8 }, // Flapple + new(60,10,5,SW) { Species = 841, Ability = A4, Moves = new[]{ 407, 788, 512, 349 }, Index = 8, CanGigantamax = true }, // Flapple + new(60,10,5,SW) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 595, 500 }, Index = 8, Form = 5, CanGigantamax = true }, // Alcremie-5 + new(60,10,5,SW) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 595, 500 }, Index = 8, Form = 6, CanGigantamax = true }, // Alcremie-6 + new(60,10,5,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 523 }, Index = 8 }, // Coalossal + new(60,10,5,SW) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 523 }, Index = 8, CanGigantamax = true }, // Coalossal + new(17,01,1,SH) { Species = 131, Ability = A4, Moves = new[]{ 055, 496, 045, 047 }, Index = 8 }, // Lapras + new(17,01,1,SH) { Species = 131, Ability = A2, Moves = new[]{ 055, 496, 045, 047 }, Index = 8 }, // Lapras + new(30,03,2,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 029, 389, 073 }, Index = 8 }, // Appletun + new(30,03,2,SH) { Species = 842, Ability = A2, Moves = new[]{ 787, 029, 389, 073 }, Index = 8 }, // Appletun + new(30,03,2,SH) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 047 }, Index = 8 }, // Lapras + new(30,03,2,SH) { Species = 131, Ability = A2, Moves = new[]{ 352, 420, 109, 047 }, Index = 8 }, // Lapras + new(40,05,3,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 8 }, // Appletun + new(40,05,3,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 8, CanGigantamax = true }, // Appletun + new(40,05,3,SH) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 202, 186 }, Index = 8, Form = 1, CanGigantamax = true }, // Alcremie-1 + new(40,05,3,SH) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 202, 186 }, Index = 8, Form = 2, CanGigantamax = true }, // Alcremie-2 + new(40,05,3,SH) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 034 }, Index = 8 }, // Lapras + new(40,05,3,SH) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 034 }, Index = 8, CanGigantamax = true }, // Lapras + new(50,08,4,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 202, 406, 089 }, Index = 8 }, // Appletun + new(50,08,4,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 202, 406, 089 }, Index = 8, CanGigantamax = true }, // Appletun + new(50,08,4,SH) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 094, 151 }, Index = 8, Form = 7, CanGigantamax = true }, // Alcremie-7 + new(50,08,4,SH) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 094, 151 }, Index = 8, Form = 8, CanGigantamax = true }, // Alcremie-8 + new(50,08,4,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 058, 246, 523 }, Index = 8 }, // Lapras + new(50,08,4,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 058, 246, 523 }, Index = 8, CanGigantamax = true }, // Lapras + new(60,10,5,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 406, 412, 089 }, Index = 8 }, // Appletun + new(60,10,5,SH) { Species = 842, Ability = A4, Moves = new[]{ 787, 406, 412, 089 }, Index = 8, CanGigantamax = true }, // Appletun + new(60,10,5,SH) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 595, 500 }, Index = 8, Form = 3, CanGigantamax = true }, // Alcremie-3 + new(60,10,5,SH) { Species = 869, Ability = A4, Moves = new[]{ 605, 202, 595, 500 }, Index = 8, Form = 4, CanGigantamax = true }, // Alcremie-4 + new(60,10,5,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 573, 329 }, Index = 8 }, // Lapras + new(60,10,5,SH) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 573, 329 }, Index = 8, CanGigantamax = true }, // Lapras - new(17,01,1) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 000, 000 }, Index = 6, Shiny = Shiny.Always }, // Magikarp - new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 6 }, // Munchlax - new(17,01,1) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 000, 000 }, Index = 6 }, // Magikarp - new(30,03,2) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6, Shiny = Shiny.Always }, // Magikarp - new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 6 }, // Snorlax - new(30,03,2) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6 }, // Magikarp - new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 6, CanGigantamax = true }, // Butterfree - new(40,05,3) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6, Shiny = Shiny.Always }, // Magikarp - new(40,05,3) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6 }, // Magikarp - new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 6, CanGigantamax = true }, // Snorlax - new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 6, CanGigantamax = true }, // Butterfree - new(50,08,4) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6, Shiny = Shiny.Always }, // Magikarp - new(50,08,4) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6 }, // Magikarp - new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 6, CanGigantamax = true }, // Snorlax - new(60,10,5) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 340 }, Index = 6, Shiny = Shiny.Always }, // Magikarp - new(70,10,5) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 340 }, Index = 6 }, // Magikarp - new(70,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 6, CanGigantamax = true }, // Snorlax - new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 6, CanGigantamax = true }, // Butterfree - new(17,01,1,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 189, 103 }, Index = 6 }, // Silicobra - new(17,01,1,SW) { Species = 833, Ability = A4, Moves = new[]{ 055, 044, 033, 213 }, Index = 6 }, // Chewtle - new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 6, CanGigantamax = true }, // Butterfree - new(30,03,2,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 029, 137 }, Index = 6 }, // Silicobra - new(30,03,2,SW) { Species = 834, Ability = A4, Moves = new[]{ 317, 242, 055, 334 }, Index = 6 }, // Drednaw - new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 6, CanGigantamax = true }, // Sandaconda - new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 6, CanGigantamax = true }, // Drednaw - new(50,08,4,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 6, CanGigantamax = true }, // Sandaconda - new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 6, CanGigantamax = true }, // Drednaw - new(60,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 6, CanGigantamax = true }, // Butterfree - new(70,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 6, CanGigantamax = true }, // Sandaconda - new(70,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 6, CanGigantamax = true }, // Drednaw - new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 6, CanGigantamax = true }, // Butterfree - new(17,01,1,SH) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 116, 064 }, Index = 6 }, // Rookidee - new(17,01,1,SH) { Species = 850, Ability = A4, Moves = new[]{ 044, 172, 141, 693 }, Index = 6 }, // Sizzlipede - new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 6, CanGigantamax = true }, // Butterfree - new(30,03,2,SH) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 179, 468 }, Index = 6 }, // Corvisquire - new(30,03,2,SH) { Species = 851, Ability = A4, Moves = new[]{ 172, 242, 404, 257 }, Index = 6 }, // Centiskorch - new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 6, CanGigantamax = true }, // Corviknight - new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 6, CanGigantamax = true }, // Centiskorch - new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 6, CanGigantamax = true }, // Corviknight - new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 6, CanGigantamax = true }, // Centiskorch - new(60,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 6, CanGigantamax = true }, // Butterfree - new(70,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 6, CanGigantamax = true }, // Corviknight - new(70,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 6, CanGigantamax = true }, // Centiskorch + new(17,01,1) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 000, 000 }, Index = 6, Shiny = Shiny.Always }, // Magikarp + new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 6 }, // Munchlax + new(17,01,1) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 000, 000 }, Index = 6 }, // Magikarp + new(30,03,2) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6, Shiny = Shiny.Always }, // Magikarp + new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 6 }, // Snorlax + new(30,03,2) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6 }, // Magikarp + new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 6, CanGigantamax = true }, // Butterfree + new(40,05,3) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6, Shiny = Shiny.Always }, // Magikarp + new(40,05,3) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6 }, // Magikarp + new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 6, CanGigantamax = true }, // Snorlax + new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 6, CanGigantamax = true }, // Butterfree + new(50,08,4) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6, Shiny = Shiny.Always }, // Magikarp + new(50,08,4) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 6 }, // Magikarp + new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 6, CanGigantamax = true }, // Snorlax + new(60,10,5) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 340 }, Index = 6, Shiny = Shiny.Always }, // Magikarp + new(70,10,5) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 340 }, Index = 6 }, // Magikarp + new(70,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 6, CanGigantamax = true }, // Snorlax + new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 6, CanGigantamax = true }, // Butterfree + new(17,01,1,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 189, 103 }, Index = 6 }, // Silicobra + new(17,01,1,SW) { Species = 833, Ability = A4, Moves = new[]{ 055, 044, 033, 213 }, Index = 6 }, // Chewtle + new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 6, CanGigantamax = true }, // Butterfree + new(30,03,2,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 029, 137 }, Index = 6 }, // Silicobra + new(30,03,2,SW) { Species = 834, Ability = A4, Moves = new[]{ 317, 242, 055, 334 }, Index = 6 }, // Drednaw + new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 6, CanGigantamax = true }, // Sandaconda + new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 6, CanGigantamax = true }, // Drednaw + new(50,08,4,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 6, CanGigantamax = true }, // Sandaconda + new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 6, CanGigantamax = true }, // Drednaw + new(60,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 6, CanGigantamax = true }, // Butterfree + new(70,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 6, CanGigantamax = true }, // Sandaconda + new(70,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 6, CanGigantamax = true }, // Drednaw + new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 6, CanGigantamax = true }, // Butterfree + new(17,01,1,SH) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 116, 064 }, Index = 6 }, // Rookidee + new(17,01,1,SH) { Species = 850, Ability = A4, Moves = new[]{ 044, 172, 141, 693 }, Index = 6 }, // Sizzlipede + new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 6, CanGigantamax = true }, // Butterfree + new(30,03,2,SH) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 179, 468 }, Index = 6 }, // Corvisquire + new(30,03,2,SH) { Species = 851, Ability = A4, Moves = new[]{ 172, 242, 404, 257 }, Index = 6 }, // Centiskorch + new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 6, CanGigantamax = true }, // Corviknight + new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 6, CanGigantamax = true }, // Centiskorch + new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 6, CanGigantamax = true }, // Corviknight + new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 6, CanGigantamax = true }, // Centiskorch + new(60,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 6, CanGigantamax = true }, // Butterfree + new(70,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 6, CanGigantamax = true }, // Corviknight + new(70,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 6, CanGigantamax = true }, // Centiskorch - new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 5 }, // Munchlax - new(17,01,1) { Species = 446, Ability = A2, Moves = new[]{ 033, 044, 122, 111 }, Index = 5 }, // Munchlax - new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 5 }, // Snorlax - new(30,03,2) { Species = 143, Ability = A2, Moves = new[]{ 034, 242, 118, 111 }, Index = 5 }, // Snorlax - new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 5, CanGigantamax = true }, // Butterfree - new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 5 }, // Snorlax - new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 5, CanGigantamax = true }, // Snorlax - new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 5, CanGigantamax = true }, // Butterfree - new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 5 }, // Snorlax - new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 5, CanGigantamax = true }, // Snorlax - new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 5 }, // Snorlax - new(70,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 5, CanGigantamax = true }, // Snorlax - new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 5 }, // Butterfree - new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 5, CanGigantamax = true }, // Butterfree - new(17,01,1,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 189, 103 }, Index = 5 }, // Silicobra - new(17,01,1,SW) { Species = 833, Ability = A4, Moves = new[]{ 055, 044, 033, 213 }, Index = 5 }, // Chewtle - new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 5, CanGigantamax = true }, // Butterfree - new(30,03,2,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 029, 137 }, Index = 5 }, // Silicobra - new(30,03,2,SW) { Species = 834, Ability = A4, Moves = new[]{ 317, 242, 055, 334 }, Index = 5 }, // Drednaw - new(30,03,2,SW) { Species = 834, Ability = A2, Moves = new[]{ 317, 242, 055, 334 }, Index = 5 }, // Drednaw - new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 5, CanGigantamax = true }, // Sandaconda - new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 5 }, // Drednaw - new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 5, CanGigantamax = true }, // Drednaw - new(50,08,4,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 5, CanGigantamax = true }, // Sandaconda - new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 5 }, // Drednaw - new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 5, CanGigantamax = true }, // Drednaw - new(60,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 5, CanGigantamax = true }, // Butterfree - new(70,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 5, CanGigantamax = true }, // Sandaconda - new(60,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 5 }, // Drednaw - new(70,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 5, CanGigantamax = true }, // Drednaw - new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 5 }, // Butterfree - new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 5, CanGigantamax = true }, // Butterfree - new(17,01,1,SH) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 116, 064 }, Index = 5 }, // Rookidee - new(17,01,1,SH) { Species = 850, Ability = A4, Moves = new[]{ 044, 172, 141, 693 }, Index = 5 }, // Sizzlipede - new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 5, CanGigantamax = true }, // Butterfree - new(30,03,2,SH) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 179, 468 }, Index = 5 }, // Corvisquire - new(30,03,2,SH) { Species = 851, Ability = A4, Moves = new[]{ 172, 242, 404, 257 }, Index = 5 }, // Centiskorch - new(30,03,2,SH) { Species = 851, Ability = A2, Moves = new[]{ 172, 242, 404, 257 }, Index = 5 }, // Centiskorch - new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 5, CanGigantamax = true }, // Corviknight - new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 5 }, // Centiskorch - new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 5, CanGigantamax = true }, // Centiskorch - new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 5, CanGigantamax = true }, // Corviknight - new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 5 }, // Centiskorch - new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 5, CanGigantamax = true }, // Centiskorch - new(60,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 5, CanGigantamax = true }, // Butterfree - new(70,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 5, CanGigantamax = true }, // Corviknight - new(60,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 5 }, // Centiskorch - new(70,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 5, CanGigantamax = true }, // Centiskorch + new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 5 }, // Munchlax + new(17,01,1) { Species = 446, Ability = A2, Moves = new[]{ 033, 044, 122, 111 }, Index = 5 }, // Munchlax + new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 5 }, // Snorlax + new(30,03,2) { Species = 143, Ability = A2, Moves = new[]{ 034, 242, 118, 111 }, Index = 5 }, // Snorlax + new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 5, CanGigantamax = true }, // Butterfree + new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 5 }, // Snorlax + new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 5, CanGigantamax = true }, // Snorlax + new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 5, CanGigantamax = true }, // Butterfree + new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 5 }, // Snorlax + new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 5, CanGigantamax = true }, // Snorlax + new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 5 }, // Snorlax + new(70,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 5, CanGigantamax = true }, // Snorlax + new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 5 }, // Butterfree + new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 5, CanGigantamax = true }, // Butterfree + new(17,01,1,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 189, 103 }, Index = 5 }, // Silicobra + new(17,01,1,SW) { Species = 833, Ability = A4, Moves = new[]{ 055, 044, 033, 213 }, Index = 5 }, // Chewtle + new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 5, CanGigantamax = true }, // Butterfree + new(30,03,2,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 029, 137 }, Index = 5 }, // Silicobra + new(30,03,2,SW) { Species = 834, Ability = A4, Moves = new[]{ 317, 242, 055, 334 }, Index = 5 }, // Drednaw + new(30,03,2,SW) { Species = 834, Ability = A2, Moves = new[]{ 317, 242, 055, 334 }, Index = 5 }, // Drednaw + new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 5, CanGigantamax = true }, // Sandaconda + new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 5 }, // Drednaw + new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 5, CanGigantamax = true }, // Drednaw + new(50,08,4,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 5, CanGigantamax = true }, // Sandaconda + new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 5 }, // Drednaw + new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 5, CanGigantamax = true }, // Drednaw + new(60,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 5, CanGigantamax = true }, // Butterfree + new(70,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 5, CanGigantamax = true }, // Sandaconda + new(60,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 5 }, // Drednaw + new(70,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 5, CanGigantamax = true }, // Drednaw + new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 5 }, // Butterfree + new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 5, CanGigantamax = true }, // Butterfree + new(17,01,1,SH) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 116, 064 }, Index = 5 }, // Rookidee + new(17,01,1,SH) { Species = 850, Ability = A4, Moves = new[]{ 044, 172, 141, 693 }, Index = 5 }, // Sizzlipede + new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 5, CanGigantamax = true }, // Butterfree + new(30,03,2,SH) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 179, 468 }, Index = 5 }, // Corvisquire + new(30,03,2,SH) { Species = 851, Ability = A4, Moves = new[]{ 172, 242, 404, 257 }, Index = 5 }, // Centiskorch + new(30,03,2,SH) { Species = 851, Ability = A2, Moves = new[]{ 172, 242, 404, 257 }, Index = 5 }, // Centiskorch + new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 5, CanGigantamax = true }, // Corviknight + new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 5 }, // Centiskorch + new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 5, CanGigantamax = true }, // Centiskorch + new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 5, CanGigantamax = true }, // Corviknight + new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 5 }, // Centiskorch + new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 5, CanGigantamax = true }, // Centiskorch + new(60,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 5, CanGigantamax = true }, // Butterfree + new(70,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 5, CanGigantamax = true }, // Corviknight + new(60,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 5 }, // Centiskorch + new(70,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 5, CanGigantamax = true }, // Centiskorch - new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 3 }, // Munchlax - new(17,01,1) { Species = 225, Ability = A4, Moves = new[]{ 217, 229, 098, 420 }, Index = 3 }, // Delibird - new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 3 }, // Snorlax - new(30,03,2) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 034, 693 }, Index = 3 }, // Delibird - new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 3, CanGigantamax = true }, // Butterfree - new(40,05,3) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 280, 196 }, Index = 3 }, // Delibird - new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 3, CanGigantamax = true }, // Snorlax - new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 3, CanGigantamax = true }, // Butterfree - new(50,08,4) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 034, 280 }, Index = 3 }, // Delibird - new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 3, CanGigantamax = true }, // Snorlax - new(70,10,5) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 065, 280 }, Index = 3 }, // Delibird - new(70,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 3, CanGigantamax = true }, // Snorlax - new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 3 }, // Butterfree - new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 3, CanGigantamax = true }, // Butterfree - new(17,01,1,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 189, 103 }, Index = 3 }, // Silicobra - new(17,01,1,SW) { Species = 833, Ability = A4, Moves = new[]{ 055, 044, 033, 213 }, Index = 3 }, // Chewtle - new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 3, CanGigantamax = true }, // Butterfree - new(30,03,2,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 029, 137 }, Index = 3 }, // Silicobra - new(30,03,2,SW) { Species = 834, Ability = A4, Moves = new[]{ 317, 242, 055, 334 }, Index = 3 }, // Drednaw - new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 3, CanGigantamax = true }, // Sandaconda - new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 3 }, // Drednaw - new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 3, CanGigantamax = true }, // Drednaw - new(50,08,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 3, CanGigantamax = true }, // Sandaconda - new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 3 }, // Drednaw - new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 3, CanGigantamax = true }, // Drednaw - new(60,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 3, CanGigantamax = true }, // Butterfree - new(70,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 3, CanGigantamax = true }, // Sandaconda - new(60,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 3 }, // Drednaw - new(70,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 3, CanGigantamax = true }, // Drednaw - new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 3 }, // Butterfree - new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 3, CanGigantamax = true }, // Butterfree - new(17,01,1,SH) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 116, 064 }, Index = 3 }, // Rookidee - new(17,01,1,SH) { Species = 850, Ability = A4, Moves = new[]{ 044, 172, 141, 693 }, Index = 3 }, // Sizzlipede - new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 3, CanGigantamax = true }, // Butterfree - new(30,03,2,SH) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 179, 468 }, Index = 3 }, // Corvisquire - new(30,03,2,SH) { Species = 851, Ability = A4, Moves = new[]{ 172, 242, 404, 257 }, Index = 3 }, // Centiskorch - new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 3, CanGigantamax = true }, // Corviknight - new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 3 }, // Centiskorch - new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 3, CanGigantamax = true }, // Centiskorch - new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 3, CanGigantamax = true }, // Corviknight - new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 3 }, // Centiskorch - new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 3, CanGigantamax = true }, // Centiskorch - new(60,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 3, CanGigantamax = true }, // Butterfree - new(70,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 3, CanGigantamax = true }, // Corviknight - new(60,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 3 }, // Centiskorch - new(70,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 3, CanGigantamax = true }, // Centiskorch + new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 3 }, // Munchlax + new(17,01,1) { Species = 225, Ability = A4, Moves = new[]{ 217, 229, 098, 420 }, Index = 3 }, // Delibird + new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 3 }, // Snorlax + new(30,03,2) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 034, 693 }, Index = 3 }, // Delibird + new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 3, CanGigantamax = true }, // Butterfree + new(40,05,3) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 280, 196 }, Index = 3 }, // Delibird + new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 3, CanGigantamax = true }, // Snorlax + new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 3, CanGigantamax = true }, // Butterfree + new(50,08,4) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 034, 280 }, Index = 3 }, // Delibird + new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 3, CanGigantamax = true }, // Snorlax + new(70,10,5) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 065, 280 }, Index = 3 }, // Delibird + new(70,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 3, CanGigantamax = true }, // Snorlax + new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 3 }, // Butterfree + new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 3, CanGigantamax = true }, // Butterfree + new(17,01,1,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 189, 103 }, Index = 3 }, // Silicobra + new(17,01,1,SW) { Species = 833, Ability = A4, Moves = new[]{ 055, 044, 033, 213 }, Index = 3 }, // Chewtle + new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 3, CanGigantamax = true }, // Butterfree + new(30,03,2,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 029, 137 }, Index = 3 }, // Silicobra + new(30,03,2,SW) { Species = 834, Ability = A4, Moves = new[]{ 317, 242, 055, 334 }, Index = 3 }, // Drednaw + new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 3, CanGigantamax = true }, // Sandaconda + new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 3 }, // Drednaw + new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 3, CanGigantamax = true }, // Drednaw + new(50,08,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 3, CanGigantamax = true }, // Sandaconda + new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 3 }, // Drednaw + new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 3, CanGigantamax = true }, // Drednaw + new(60,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 3, CanGigantamax = true }, // Butterfree + new(70,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 3, CanGigantamax = true }, // Sandaconda + new(60,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 3 }, // Drednaw + new(70,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 3, CanGigantamax = true }, // Drednaw + new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 3 }, // Butterfree + new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 3, CanGigantamax = true }, // Butterfree + new(17,01,1,SH) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 116, 064 }, Index = 3 }, // Rookidee + new(17,01,1,SH) { Species = 850, Ability = A4, Moves = new[]{ 044, 172, 141, 693 }, Index = 3 }, // Sizzlipede + new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 3, CanGigantamax = true }, // Butterfree + new(30,03,2,SH) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 179, 468 }, Index = 3 }, // Corvisquire + new(30,03,2,SH) { Species = 851, Ability = A4, Moves = new[]{ 172, 242, 404, 257 }, Index = 3 }, // Centiskorch + new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 3, CanGigantamax = true }, // Corviknight + new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 3 }, // Centiskorch + new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 3, CanGigantamax = true }, // Centiskorch + new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 3, CanGigantamax = true }, // Corviknight + new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 3 }, // Centiskorch + new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 3, CanGigantamax = true }, // Centiskorch + new(60,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 3, CanGigantamax = true }, // Butterfree + new(70,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 3, CanGigantamax = true }, // Corviknight + new(60,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 3 }, // Centiskorch + new(70,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 3, CanGigantamax = true }, // Centiskorch - new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 2 }, // Munchlax - new(17,01,1) { Species = 446, Ability = A2, Moves = new[]{ 033, 044, 122, 111 }, Index = 2 }, // Munchlax - new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 2 }, // Snorlax - new(30,03,2) { Species = 143, Ability = A2, Moves = new[]{ 034, 242, 118, 111 }, Index = 2 }, // Snorlax - new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 2, CanGigantamax = true }, // Butterfree - new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 2 }, // Snorlax - new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 2, CanGigantamax = true }, // Snorlax - new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 2, CanGigantamax = true }, // Butterfree - new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 2 }, // Snorlax - new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 2, CanGigantamax = true }, // Snorlax - new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 2 }, // Snorlax - new(70,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 2, CanGigantamax = true }, // Snorlax - new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 2 }, // Butterfree - new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 2, CanGigantamax = true }, // Butterfree - new(17,01,1,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 189, 103 }, Index = 2 }, // Silicobra - new(17,01,1,SW) { Species = 833, Ability = A4, Moves = new[]{ 055, 044, 033, 213 }, Index = 2 }, // Chewtle - new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 2, CanGigantamax = true }, // Butterfree - new(30,03,2,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 029, 137 }, Index = 2 }, // Silicobra - new(30,03,2,SW) { Species = 834, Ability = A4, Moves = new[]{ 317, 242, 055, 334 }, Index = 2 }, // Drednaw - new(30,03,2,SW) { Species = 834, Ability = A2, Moves = new[]{ 317, 242, 055, 334 }, Index = 2 }, // Drednaw - new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 2, CanGigantamax = true }, // Sandaconda - new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 2 }, // Drednaw - new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 2, CanGigantamax = true }, // Drednaw - new(50,08,4,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 2, CanGigantamax = true }, // Sandaconda - new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 2 }, // Drednaw - new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 2, CanGigantamax = true }, // Drednaw - new(60,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 2, CanGigantamax = true }, // Butterfree - new(70,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 2, CanGigantamax = true }, // Sandaconda - new(60,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 2 }, // Drednaw - new(70,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 2, CanGigantamax = true }, // Drednaw - new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 2 }, // Butterfree - new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 2, CanGigantamax = true }, // Butterfree - new(17,01,1,SH) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 116, 064 }, Index = 2 }, // Rookidee - new(17,01,1,SH) { Species = 850, Ability = A4, Moves = new[]{ 044, 172, 404, 693 }, Index = 2 }, // Sizzlipede - new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 2, CanGigantamax = true }, // Butterfree - new(30,03,2,SH) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 179, 468 }, Index = 2 }, // Corvisquire - new(30,03,2,SH) { Species = 851, Ability = A4, Moves = new[]{ 172, 242, 404, 257 }, Index = 2 }, // Centiskorch - new(30,03,2,SH) { Species = 851, Ability = A2, Moves = new[]{ 172, 242, 404, 257 }, Index = 2 }, // Centiskorch - new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 2, CanGigantamax = true }, // Corviknight - new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 2 }, // Centiskorch - new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 2, CanGigantamax = true }, // Centiskorch - new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 2, CanGigantamax = true }, // Corviknight - new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 2 }, // Centiskorch - new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 2, CanGigantamax = true }, // Centiskorch - new(60,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 2, CanGigantamax = true }, // Butterfree - new(70,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 2, CanGigantamax = true }, // Corviknight - new(60,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 2 }, // Centiskorch - new(70,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 2, CanGigantamax = true }, // Centiskorch + new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 2 }, // Munchlax + new(17,01,1) { Species = 446, Ability = A2, Moves = new[]{ 033, 044, 122, 111 }, Index = 2 }, // Munchlax + new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 2 }, // Snorlax + new(30,03,2) { Species = 143, Ability = A2, Moves = new[]{ 034, 242, 118, 111 }, Index = 2 }, // Snorlax + new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 2, CanGigantamax = true }, // Butterfree + new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 2 }, // Snorlax + new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 2, CanGigantamax = true }, // Snorlax + new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 2, CanGigantamax = true }, // Butterfree + new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 2 }, // Snorlax + new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 2, CanGigantamax = true }, // Snorlax + new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 2 }, // Snorlax + new(70,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 2, CanGigantamax = true }, // Snorlax + new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 2 }, // Butterfree + new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 2, CanGigantamax = true }, // Butterfree + new(17,01,1,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 189, 103 }, Index = 2 }, // Silicobra + new(17,01,1,SW) { Species = 833, Ability = A4, Moves = new[]{ 055, 044, 033, 213 }, Index = 2 }, // Chewtle + new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 2, CanGigantamax = true }, // Butterfree + new(30,03,2,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 029, 137 }, Index = 2 }, // Silicobra + new(30,03,2,SW) { Species = 834, Ability = A4, Moves = new[]{ 317, 242, 055, 334 }, Index = 2 }, // Drednaw + new(30,03,2,SW) { Species = 834, Ability = A2, Moves = new[]{ 317, 242, 055, 334 }, Index = 2 }, // Drednaw + new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 2, CanGigantamax = true }, // Sandaconda + new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 2 }, // Drednaw + new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 2, CanGigantamax = true }, // Drednaw + new(50,08,4,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 2, CanGigantamax = true }, // Sandaconda + new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 2 }, // Drednaw + new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 2, CanGigantamax = true }, // Drednaw + new(60,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 2, CanGigantamax = true }, // Butterfree + new(70,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 2, CanGigantamax = true }, // Sandaconda + new(60,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 2 }, // Drednaw + new(70,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 2, CanGigantamax = true }, // Drednaw + new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 2 }, // Butterfree + new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 2, CanGigantamax = true }, // Butterfree + new(17,01,1,SH) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 116, 064 }, Index = 2 }, // Rookidee + new(17,01,1,SH) { Species = 850, Ability = A4, Moves = new[]{ 044, 172, 404, 693 }, Index = 2 }, // Sizzlipede + new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 2, CanGigantamax = true }, // Butterfree + new(30,03,2,SH) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 179, 468 }, Index = 2 }, // Corvisquire + new(30,03,2,SH) { Species = 851, Ability = A4, Moves = new[]{ 172, 242, 404, 257 }, Index = 2 }, // Centiskorch + new(30,03,2,SH) { Species = 851, Ability = A2, Moves = new[]{ 172, 242, 404, 257 }, Index = 2 }, // Centiskorch + new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 2, CanGigantamax = true }, // Corviknight + new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 2 }, // Centiskorch + new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 2, CanGigantamax = true }, // Centiskorch + new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 2, CanGigantamax = true }, // Corviknight + new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 2 }, // Centiskorch + new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 2, CanGigantamax = true }, // Centiskorch + new(60,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 2, CanGigantamax = true }, // Butterfree + new(70,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 2, CanGigantamax = true }, // Corviknight + new(60,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 2 }, // Centiskorch + new(70,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 2, CanGigantamax = true }, // Centiskorch - new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 1 }, // Butterfree - new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 1, CanGigantamax = true }, // Butterfree - new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 1 }, // Butterfree - new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 1, CanGigantamax = true }, // Butterfree - new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 1 }, // Butterfree - new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 1, CanGigantamax = true }, // Butterfree - new(17,01,1,SW) { Species = 843, Ability = A2, Moves = new[]{ 693, 523, 189, 103 }, Index = 1 }, // Silicobra - new(17,01,1,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 189, 103 }, Index = 1 }, // Silicobra - new(17,01,1,SW) { Species = 833, Ability = A2, Moves = new[]{ 055, 044, 033, 213 }, Index = 1 }, // Chewtle - new(17,01,1,SW) { Species = 833, Ability = A4, Moves = new[]{ 055, 044, 033, 213 }, Index = 1 }, // Chewtle - new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 1 }, // Butterfree - new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 1, CanGigantamax = true }, // Butterfree - new(30,03,2,SW) { Species = 843, Ability = A2, Moves = new[]{ 693, 523, 029, 137 }, Index = 1 }, // Silicobra - new(30,03,2,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 029, 137 }, Index = 1 }, // Silicobra - new(30,03,2,SW) { Species = 834, Ability = A2, Moves = new[]{ 317, 242, 055, 334 }, Index = 1 }, // Drednaw - new(30,03,2,SW) { Species = 834, Ability = A4, Moves = new[]{ 317, 242, 055, 334 }, Index = 1 }, // Drednaw - new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 1 }, // Sandaconda - new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 1, CanGigantamax = true }, // Sandaconda - new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 1 }, // Drednaw - new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 1, CanGigantamax = true }, // Drednaw - new(50,08,4,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 1 }, // Sandaconda - new(50,08,4,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 1, CanGigantamax = true }, // Sandaconda - new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 1 }, // Drednaw - new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 1, CanGigantamax = true }, // Drednaw - new(60,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 1 }, // Butterfree - new(70,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 1, CanGigantamax = true }, // Butterfree - new(60,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 1 }, // Sandaconda - new(70,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 1, CanGigantamax = true }, // Sandaconda - new(60,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 1 }, // Drednaw - new(70,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 1, CanGigantamax = true }, // Drednaw - new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 1 }, // Butterfree - new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 1, CanGigantamax = true }, // Butterfree - new(17,01,1,SH) { Species = 821, Ability = A2, Moves = new[]{ 365, 031, 526, 064 }, Index = 1 }, // Rookidee - new(17,01,1,SH) { Species = 821, Ability = A4, Moves = new[]{ 365, 031, 526, 064 }, Index = 1 }, // Rookidee - new(17,01,1,SH) { Species = 850, Ability = A2, Moves = new[]{ 044, 172, 450, 693 }, Index = 1 }, // Sizzlipede - new(17,01,1,SH) { Species = 850, Ability = A4, Moves = new[]{ 044, 172, 450, 693 }, Index = 1 }, // Sizzlipede - new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 1 }, // Butterfree - new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 1, CanGigantamax = true }, // Butterfree - new(30,03,2,SH) { Species = 822, Ability = A2, Moves = new[]{ 365, 263, 179, 468 }, Index = 1 }, // Corvisquire - new(30,03,2,SH) { Species = 822, Ability = A4, Moves = new[]{ 365, 263, 179, 468 }, Index = 1 }, // Corvisquire - new(30,03,2,SH) { Species = 851, Ability = A2, Moves = new[]{ 172, 242, 450, 257 }, Index = 1 }, // Centiskorch - new(30,03,2,SH) { Species = 851, Ability = A4, Moves = new[]{ 172, 242, 450, 257 }, Index = 1 }, // Centiskorch - new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 1 }, // Corviknight - new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 1, CanGigantamax = true }, // Corviknight - new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 1 }, // Centiskorch - new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 1, CanGigantamax = true }, // Centiskorch - new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 1 }, // Corviknight - new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 1, CanGigantamax = true }, // Corviknight - new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 1 }, // Centiskorch - new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 1, CanGigantamax = true }, // Centiskorch - new(60,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 1 }, // Butterfree - new(70,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 1, CanGigantamax = true }, // Butterfree - new(60,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 1 }, // Corviknight - new(70,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 1, CanGigantamax = true }, // Corviknight - new(60,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 1 }, // Centiskorch - new(70,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 1, CanGigantamax = true }, // Centiskorch - }; - } + new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 1 }, // Butterfree + new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 676, 403, 202, 527 }, Index = 1, CanGigantamax = true }, // Butterfree + new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 1 }, // Butterfree + new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 1, CanGigantamax = true }, // Butterfree + new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 1 }, // Butterfree + new(17,01,1,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 1, CanGigantamax = true }, // Butterfree + new(17,01,1,SW) { Species = 843, Ability = A2, Moves = new[]{ 693, 523, 189, 103 }, Index = 1 }, // Silicobra + new(17,01,1,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 189, 103 }, Index = 1 }, // Silicobra + new(17,01,1,SW) { Species = 833, Ability = A2, Moves = new[]{ 055, 044, 033, 213 }, Index = 1 }, // Chewtle + new(17,01,1,SW) { Species = 833, Ability = A4, Moves = new[]{ 055, 044, 033, 213 }, Index = 1 }, // Chewtle + new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 1 }, // Butterfree + new(30,03,2,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 079 }, Index = 1, CanGigantamax = true }, // Butterfree + new(30,03,2,SW) { Species = 843, Ability = A2, Moves = new[]{ 693, 523, 029, 137 }, Index = 1 }, // Silicobra + new(30,03,2,SW) { Species = 843, Ability = A4, Moves = new[]{ 693, 523, 029, 137 }, Index = 1 }, // Silicobra + new(30,03,2,SW) { Species = 834, Ability = A2, Moves = new[]{ 317, 242, 055, 334 }, Index = 1 }, // Drednaw + new(30,03,2,SW) { Species = 834, Ability = A4, Moves = new[]{ 317, 242, 055, 334 }, Index = 1 }, // Drednaw + new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 1 }, // Sandaconda + new(40,05,3,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 407, 424 }, Index = 1, CanGigantamax = true }, // Sandaconda + new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 1 }, // Drednaw + new(40,05,3,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 034, 317 }, Index = 1, CanGigantamax = true }, // Drednaw + new(50,08,4,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 1 }, // Sandaconda + new(50,08,4,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 1, CanGigantamax = true }, // Sandaconda + new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 1 }, // Drednaw + new(50,08,4,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 350, 523 }, Index = 1, CanGigantamax = true }, // Drednaw + new(60,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 1 }, // Butterfree + new(70,10,5,SW) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 542, 202 }, Index = 1, CanGigantamax = true }, // Butterfree + new(60,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 1 }, // Sandaconda + new(70,10,5,SW) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 342, 328 }, Index = 1, CanGigantamax = true }, // Sandaconda + new(60,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 1 }, // Drednaw + new(70,10,5,SW) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 1, CanGigantamax = true }, // Drednaw + new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 1 }, // Butterfree + new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 1, CanGigantamax = true }, // Butterfree + new(17,01,1,SH) { Species = 821, Ability = A2, Moves = new[]{ 365, 031, 526, 064 }, Index = 1 }, // Rookidee + new(17,01,1,SH) { Species = 821, Ability = A4, Moves = new[]{ 365, 031, 526, 064 }, Index = 1 }, // Rookidee + new(17,01,1,SH) { Species = 850, Ability = A2, Moves = new[]{ 044, 172, 450, 693 }, Index = 1 }, // Sizzlipede + new(17,01,1,SH) { Species = 850, Ability = A4, Moves = new[]{ 044, 172, 450, 693 }, Index = 1 }, // Sizzlipede + new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 1 }, // Butterfree + new(30,03,2,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 081, 077 }, Index = 1, CanGigantamax = true }, // Butterfree + new(30,03,2,SH) { Species = 822, Ability = A2, Moves = new[]{ 365, 263, 179, 468 }, Index = 1 }, // Corvisquire + new(30,03,2,SH) { Species = 822, Ability = A4, Moves = new[]{ 365, 263, 179, 468 }, Index = 1 }, // Corvisquire + new(30,03,2,SH) { Species = 851, Ability = A2, Moves = new[]{ 172, 242, 450, 257 }, Index = 1 }, // Centiskorch + new(30,03,2,SH) { Species = 851, Ability = A4, Moves = new[]{ 172, 242, 450, 257 }, Index = 1 }, // Centiskorch + new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 1 }, // Corviknight + new(40,05,3,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 211, 034, 103 }, Index = 1, CanGigantamax = true }, // Corviknight + new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 1 }, // Centiskorch + new(40,05,3,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 438, 053, 489 }, Index = 1, CanGigantamax = true }, // Centiskorch + new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 1 }, // Corviknight + new(50,08,4,SH) { Species = 823, Ability = A4, Moves = new[]{ 065, 442, 034, 334 }, Index = 1, CanGigantamax = true }, // Corviknight + new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 1 }, // Centiskorch + new(50,08,4,SH) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 242 }, Index = 1, CanGigantamax = true }, // Centiskorch + new(60,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 1 }, // Butterfree + new(70,10,5,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 094, 403, 079 }, Index = 1, CanGigantamax = true }, // Butterfree + new(60,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 1 }, // Corviknight + new(70,10,5,SH) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 249, 103 }, Index = 1, CanGigantamax = true }, // Corviknight + new(60,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 1 }, // Centiskorch + new(70,10,5,SH) { Species = 851, Ability = A4, Moves = new[]{ 679, 257, 083, 438 }, Index = 1, CanGigantamax = true }, // Centiskorch + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistDLC1.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistDLC1.cs index bcda1f64b..8e43ee482 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistDLC1.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistDLC1.cs @@ -1,297 +1,296 @@ using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +// Distribution Nest Encounters (BCAT) +internal static partial class Encounters8Nest { - // Distribution Nest Encounters (BCAT) - internal static partial class Encounters8Nest + /// + /// Nest distribution raids for available after Isle of Armor expansion and before Crown Tundra. + /// + internal static readonly EncounterStatic8ND[] Dist_DLC1 = { - /// - /// Nest distribution raids for available after Isle of Armor expansion and before Crown Tundra. - /// - internal static readonly EncounterStatic8ND[] Dist_DLC1 = - { - new(17,01,1) { Species = 093, Ability = A4, Moves = new[]{ 371, 122, 095, 325 }, Index = 39 }, // Haunter - new(17,01,1) { Species = 425, Ability = A4, Moves = new[]{ 016, 506, 310, 371 }, Index = 39 }, // Drifloon - new(17,01,1) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 043, 506 }, Index = 39 }, // Duskull - new(17,01,1) { Species = 859, Ability = A4, Moves = new[]{ 372, 313, 260, 044 }, Index = 39 }, // Impidimp - new(17,01,1) { Species = 633, Ability = A4, Moves = new[]{ 225, 033, 399, 044 }, Index = 39 }, // Deino - new(17,01,1) { Species = 877, Ability = A4, Moves = new[]{ 084, 098, 681, 043 }, Index = 39 }, // Morpeko - new(30,03,2) { Species = 094, Ability = A4, Moves = new[]{ 371, 389, 095, 325 }, Index = 39, CanGigantamax = true }, // Gengar - new(30,03,2) { Species = 426, Ability = A4, Moves = new[]{ 016, 247, 310, 371 }, Index = 39 }, // Drifblim - new(30,03,2) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 371, 506 }, Index = 39 }, // Duskull - new(30,03,2) { Species = 859, Ability = A4, Moves = new[]{ 259, 389, 207, 044 }, Index = 39 }, // Impidimp - new(30,03,2) { Species = 633, Ability = A4, Moves = new[]{ 225, 021, 399, 029 }, Index = 39 }, // Deino - new(30,03,2) { Species = 877, Ability = A4, Moves = new[]{ 209, 098, 044, 043 }, Index = 39 }, // Morpeko - new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 506, 389, 095, 325 }, Index = 39, CanGigantamax = true }, // Gengar - new(40,05,3) { Species = 426, Ability = A4, Moves = new[]{ 016, 247, 360, 371 }, Index = 39 }, // Drifblim - new(40,05,3) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 371, 157 }, Index = 39 }, // Dusknoir - new(40,05,3) { Species = 860, Ability = A4, Moves = new[]{ 417, 793, 421, 399 }, Index = 39 }, // Morgrem - new(40,05,3) { Species = 633, Ability = A4, Moves = new[]{ 406, 021, 399, 423 }, Index = 39 }, // Deino - new(40,05,3) { Species = 877, Ability = A4, Moves = new[]{ 209, 098, 044, 402 }, Index = 39 }, // Morpeko - new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 399, 094, 085 }, Index = 39, CanGigantamax = true }, // Gengar - new(50,08,4) { Species = 426, Ability = A4, Moves = new[]{ 366, 247, 360, 371 }, Index = 39 }, // Drifblim - new(50,08,4) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 157 }, Index = 39 }, // Dusknoir - new(50,08,4) { Species = 861, Ability = A4, Moves = new[]{ 789, 492, 421, 399 }, Index = 39, CanGigantamax = true }, // Grimmsnarl - new(50,08,4) { Species = 634, Ability = A4, Moves = new[]{ 406, 304, 399, 423 }, Index = 39 }, // Zweilous - new(50,08,4) { Species = 877, Ability = A4, Moves = new[]{ 783, 098, 242, 402 }, Index = 39 }, // Morpeko - new(60,10,5) { Species = 094, Ability = A4, Moves = new[]{ 247, 399, 605, 085 }, Index = 39, CanGigantamax = true }, // Gengar - new(60,10,5) { Species = 426, Ability = A4, Moves = new[]{ 366, 247, 360, 693 }, Index = 39 }, // Drifblim - new(60,10,5) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 089 }, Index = 39 }, // Dusknoir - new(60,10,5) { Species = 861, Ability = A4, Moves = new[]{ 789, 492, 421, 417 }, Index = 39, CanGigantamax = true }, // Grimmsnarl - new(60,10,5) { Species = 635, Ability = A4, Moves = new[]{ 406, 304, 399, 056 }, Index = 39 }, // Hydreigon - new(60,10,5) { Species = 877, Ability = A4, Moves = new[]{ 783, 037, 242, 402 }, Index = 39 }, // Morpeko + new(17,01,1) { Species = 093, Ability = A4, Moves = new[]{ 371, 122, 095, 325 }, Index = 39 }, // Haunter + new(17,01,1) { Species = 425, Ability = A4, Moves = new[]{ 016, 506, 310, 371 }, Index = 39 }, // Drifloon + new(17,01,1) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 043, 506 }, Index = 39 }, // Duskull + new(17,01,1) { Species = 859, Ability = A4, Moves = new[]{ 372, 313, 260, 044 }, Index = 39 }, // Impidimp + new(17,01,1) { Species = 633, Ability = A4, Moves = new[]{ 225, 033, 399, 044 }, Index = 39 }, // Deino + new(17,01,1) { Species = 877, Ability = A4, Moves = new[]{ 084, 098, 681, 043 }, Index = 39 }, // Morpeko + new(30,03,2) { Species = 094, Ability = A4, Moves = new[]{ 371, 389, 095, 325 }, Index = 39, CanGigantamax = true }, // Gengar + new(30,03,2) { Species = 426, Ability = A4, Moves = new[]{ 016, 247, 310, 371 }, Index = 39 }, // Drifblim + new(30,03,2) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 371, 506 }, Index = 39 }, // Duskull + new(30,03,2) { Species = 859, Ability = A4, Moves = new[]{ 259, 389, 207, 044 }, Index = 39 }, // Impidimp + new(30,03,2) { Species = 633, Ability = A4, Moves = new[]{ 225, 021, 399, 029 }, Index = 39 }, // Deino + new(30,03,2) { Species = 877, Ability = A4, Moves = new[]{ 209, 098, 044, 043 }, Index = 39 }, // Morpeko + new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 506, 389, 095, 325 }, Index = 39, CanGigantamax = true }, // Gengar + new(40,05,3) { Species = 426, Ability = A4, Moves = new[]{ 016, 247, 360, 371 }, Index = 39 }, // Drifblim + new(40,05,3) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 371, 157 }, Index = 39 }, // Dusknoir + new(40,05,3) { Species = 860, Ability = A4, Moves = new[]{ 417, 793, 421, 399 }, Index = 39 }, // Morgrem + new(40,05,3) { Species = 633, Ability = A4, Moves = new[]{ 406, 021, 399, 423 }, Index = 39 }, // Deino + new(40,05,3) { Species = 877, Ability = A4, Moves = new[]{ 209, 098, 044, 402 }, Index = 39 }, // Morpeko + new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 399, 094, 085 }, Index = 39, CanGigantamax = true }, // Gengar + new(50,08,4) { Species = 426, Ability = A4, Moves = new[]{ 366, 247, 360, 371 }, Index = 39 }, // Drifblim + new(50,08,4) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 157 }, Index = 39 }, // Dusknoir + new(50,08,4) { Species = 861, Ability = A4, Moves = new[]{ 789, 492, 421, 399 }, Index = 39, CanGigantamax = true }, // Grimmsnarl + new(50,08,4) { Species = 634, Ability = A4, Moves = new[]{ 406, 304, 399, 423 }, Index = 39 }, // Zweilous + new(50,08,4) { Species = 877, Ability = A4, Moves = new[]{ 783, 098, 242, 402 }, Index = 39 }, // Morpeko + new(60,10,5) { Species = 094, Ability = A4, Moves = new[]{ 247, 399, 605, 085 }, Index = 39, CanGigantamax = true }, // Gengar + new(60,10,5) { Species = 426, Ability = A4, Moves = new[]{ 366, 247, 360, 693 }, Index = 39 }, // Drifblim + new(60,10,5) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 089 }, Index = 39 }, // Dusknoir + new(60,10,5) { Species = 861, Ability = A4, Moves = new[]{ 789, 492, 421, 417 }, Index = 39, CanGigantamax = true }, // Grimmsnarl + new(60,10,5) { Species = 635, Ability = A4, Moves = new[]{ 406, 304, 399, 056 }, Index = 39 }, // Hydreigon + new(60,10,5) { Species = 877, Ability = A4, Moves = new[]{ 783, 037, 242, 402 }, Index = 39 }, // Morpeko - new(17,01,1) { Species = 036, Ability = A4, Moves = new[]{ 574, 001, 204, 045 }, Index = 37 }, // Clefable - new(17,01,1) { Species = 040, Ability = A4, Moves = new[]{ 497, 574, 001, 111 }, Index = 37 }, // Wigglytuff - new(17,01,1) { Species = 044, Ability = A4, Moves = new[]{ 078, 079, 230, 051 }, Index = 37 }, // Gloom - new(17,01,1) { Species = 518, Ability = A4, Moves = new[]{ 060, 236, 111, 000 }, Index = 37 }, // Musharna - new(17,01,1) { Species = 547, Ability = A4, Moves = new[]{ 412, 585, 073, 178 }, Index = 37 }, // Whimsicott - new(17,01,1) { Species = 549, Ability = A4, Moves = new[]{ 412, 241, 437, 263 }, Index = 37 }, // Lilligant - new(30,03,2) { Species = 036, Ability = A4, Moves = new[]{ 574, 001, 236, 045 }, Index = 37 }, // Clefable - new(30,03,2) { Species = 040, Ability = A4, Moves = new[]{ 497, 574, 001, 034 }, Index = 37 }, // Wigglytuff - new(30,03,2) { Species = 182, Ability = A4, Moves = new[]{ 585, 572, 236, 051 }, Index = 37 }, // Bellossom - new(30,03,2) { Species = 518, Ability = A4, Moves = new[]{ 428, 060, 236, 111 }, Index = 37 }, // Musharna - new(30,03,2) { Species = 547, Ability = A4, Moves = new[]{ 412, 585, 073, 178 }, Index = 37 }, // Whimsicott - new(30,03,2) { Species = 549, Ability = A4, Moves = new[]{ 412, 241, 437, 263 }, Index = 37 }, // Lilligant - new(40,05,3) { Species = 036, Ability = A4, Moves = new[]{ 585, 309, 236, 345 }, Index = 37 }, // Clefable - new(40,05,3) { Species = 040, Ability = A4, Moves = new[]{ 304, 583, 360, 034 }, Index = 37 }, // Wigglytuff - new(40,05,3) { Species = 182, Ability = A4, Moves = new[]{ 585, 572, 236, 051 }, Index = 37 }, // Bellossom - new(40,05,3) { Species = 518, Ability = A4, Moves = new[]{ 585, 094, 236, 360 }, Index = 37 }, // Musharna - new(40,05,3) { Species = 547, Ability = A4, Moves = new[]{ 412, 585, 073, 366 }, Index = 37 }, // Whimsicott - new(40,05,3) { Species = 549, Ability = A4, Moves = new[]{ 412, 241, 437, 263 }, Index = 37 }, // Lilligant - new(50,08,4) { Species = 036, Ability = A4, Moves = new[]{ 585, 309, 236, 345 }, Index = 37 }, // Clefable - new(50,08,4) { Species = 040, Ability = A4, Moves = new[]{ 304, 583, 360, 034 }, Index = 37 }, // Wigglytuff - new(50,08,4) { Species = 182, Ability = A4, Moves = new[]{ 585, 572, 236, 051 }, Index = 37 }, // Bellossom - new(50,08,4) { Species = 518, Ability = A4, Moves = new[]{ 585, 094, 236, 360 }, Index = 37 }, // Musharna - new(50,08,4) { Species = 547, Ability = A4, Moves = new[]{ 538, 585, 073, 366 }, Index = 37 }, // Whimsicott - new(50,08,4) { Species = 549, Ability = A4, Moves = new[]{ 412, 241, 437, 263 }, Index = 37 }, // Lilligant - new(60,10,5) { Species = 036, Ability = A4, Moves = new[]{ 585, 309, 236, 345 }, Index = 37 }, // Clefable - new(60,10,5) { Species = 040, Ability = A4, Moves = new[]{ 304, 583, 360, 034 }, Index = 37 }, // Wigglytuff - new(60,10,5) { Species = 182, Ability = A4, Moves = new[]{ 585, 572, 236, 051 }, Index = 37 }, // Bellossom - new(60,10,5) { Species = 518, Ability = A4, Moves = new[]{ 585, 094, 236, 360 }, Index = 37 }, // Musharna - new(60,10,5) { Species = 036, Ability = A4, Moves = new[]{ 585, 309, 236, 345 }, Index = 37, Shiny = Shiny.Always }, // Clefable - new(60,10,5) { Species = 549, Ability = A4, Moves = new[]{ 412, 241, 437, 263 }, Index = 37 }, // Lilligant + new(17,01,1) { Species = 036, Ability = A4, Moves = new[]{ 574, 001, 204, 045 }, Index = 37 }, // Clefable + new(17,01,1) { Species = 040, Ability = A4, Moves = new[]{ 497, 574, 001, 111 }, Index = 37 }, // Wigglytuff + new(17,01,1) { Species = 044, Ability = A4, Moves = new[]{ 078, 079, 230, 051 }, Index = 37 }, // Gloom + new(17,01,1) { Species = 518, Ability = A4, Moves = new[]{ 060, 236, 111, 000 }, Index = 37 }, // Musharna + new(17,01,1) { Species = 547, Ability = A4, Moves = new[]{ 412, 585, 073, 178 }, Index = 37 }, // Whimsicott + new(17,01,1) { Species = 549, Ability = A4, Moves = new[]{ 412, 241, 437, 263 }, Index = 37 }, // Lilligant + new(30,03,2) { Species = 036, Ability = A4, Moves = new[]{ 574, 001, 236, 045 }, Index = 37 }, // Clefable + new(30,03,2) { Species = 040, Ability = A4, Moves = new[]{ 497, 574, 001, 034 }, Index = 37 }, // Wigglytuff + new(30,03,2) { Species = 182, Ability = A4, Moves = new[]{ 585, 572, 236, 051 }, Index = 37 }, // Bellossom + new(30,03,2) { Species = 518, Ability = A4, Moves = new[]{ 428, 060, 236, 111 }, Index = 37 }, // Musharna + new(30,03,2) { Species = 547, Ability = A4, Moves = new[]{ 412, 585, 073, 178 }, Index = 37 }, // Whimsicott + new(30,03,2) { Species = 549, Ability = A4, Moves = new[]{ 412, 241, 437, 263 }, Index = 37 }, // Lilligant + new(40,05,3) { Species = 036, Ability = A4, Moves = new[]{ 585, 309, 236, 345 }, Index = 37 }, // Clefable + new(40,05,3) { Species = 040, Ability = A4, Moves = new[]{ 304, 583, 360, 034 }, Index = 37 }, // Wigglytuff + new(40,05,3) { Species = 182, Ability = A4, Moves = new[]{ 585, 572, 236, 051 }, Index = 37 }, // Bellossom + new(40,05,3) { Species = 518, Ability = A4, Moves = new[]{ 585, 094, 236, 360 }, Index = 37 }, // Musharna + new(40,05,3) { Species = 547, Ability = A4, Moves = new[]{ 412, 585, 073, 366 }, Index = 37 }, // Whimsicott + new(40,05,3) { Species = 549, Ability = A4, Moves = new[]{ 412, 241, 437, 263 }, Index = 37 }, // Lilligant + new(50,08,4) { Species = 036, Ability = A4, Moves = new[]{ 585, 309, 236, 345 }, Index = 37 }, // Clefable + new(50,08,4) { Species = 040, Ability = A4, Moves = new[]{ 304, 583, 360, 034 }, Index = 37 }, // Wigglytuff + new(50,08,4) { Species = 182, Ability = A4, Moves = new[]{ 585, 572, 236, 051 }, Index = 37 }, // Bellossom + new(50,08,4) { Species = 518, Ability = A4, Moves = new[]{ 585, 094, 236, 360 }, Index = 37 }, // Musharna + new(50,08,4) { Species = 547, Ability = A4, Moves = new[]{ 538, 585, 073, 366 }, Index = 37 }, // Whimsicott + new(50,08,4) { Species = 549, Ability = A4, Moves = new[]{ 412, 241, 437, 263 }, Index = 37 }, // Lilligant + new(60,10,5) { Species = 036, Ability = A4, Moves = new[]{ 585, 309, 236, 345 }, Index = 37 }, // Clefable + new(60,10,5) { Species = 040, Ability = A4, Moves = new[]{ 304, 583, 360, 034 }, Index = 37 }, // Wigglytuff + new(60,10,5) { Species = 182, Ability = A4, Moves = new[]{ 585, 572, 236, 051 }, Index = 37 }, // Bellossom + new(60,10,5) { Species = 518, Ability = A4, Moves = new[]{ 585, 094, 236, 360 }, Index = 37 }, // Musharna + new(60,10,5) { Species = 036, Ability = A4, Moves = new[]{ 585, 309, 236, 345 }, Index = 37, Shiny = Shiny.Always }, // Clefable + new(60,10,5) { Species = 549, Ability = A4, Moves = new[]{ 412, 241, 437, 263 }, Index = 37 }, // Lilligant - new(17,01,1) { Species = 848, Ability = A4, Moves = new[]{ 609, 051, 496, 715 }, Index = 36 }, // Toxel - new(17,01,1) { Species = 835, Ability = A4, Moves = new[]{ 609, 033, 044, 046 }, Index = 36 }, // Yamper - new(17,01,1) { Species = 695, Ability = A4, Moves = new[]{ 085, 098, 001, 189 }, Index = 36 }, // Heliolisk - new(17,01,1) { Species = 840, Ability = A4, Moves = new[]{ 110, 310, 000, 000 }, Index = 36 }, // Applin - new(17,01,1) { Species = 597, Ability = A4, Moves = new[]{ 033, 042, 232, 106 }, Index = 36 }, // Ferroseed - new(17,01,1) { Species = 829, Ability = A4, Moves = new[]{ 075, 496, 229, 670 }, Index = 36 }, // Gossifleur - new(30,03,2) { Species = 836, Ability = A4, Moves = new[]{ 609, 209, 204, 706 }, Index = 36 }, // Boltund - new(30,03,2) { Species = 695, Ability = A4, Moves = new[]{ 085, 098, 001, 189 }, Index = 36 }, // Heliolisk - new(30,03,2) { Species = 597, Ability = A4, Moves = new[]{ 442, 042, 232, 106 }, Index = 36 }, // Ferroseed - new(30,03,2) { Species = 830, Ability = A4, Moves = new[]{ 536, 496, 229, 670 }, Index = 36 }, // Eldegoss - new(40,05,3) { Species = 836, Ability = A4, Moves = new[]{ 609, 209, 424, 706 }, Index = 36 }, // Boltund - new(40,05,3) { Species = 695, Ability = A4, Moves = new[]{ 085, 098, 447, 189 }, Index = 36 }, // Heliolisk - new(40,05,3) { Species = 598, Ability = A4, Moves = new[]{ 442, 438, 398, 106 }, Index = 36 }, // Ferrothorn - new(40,05,3) { Species = 830, Ability = A4, Moves = new[]{ 536, 304, 229, 670 }, Index = 36 }, // Eldegoss - new(50,08,4) { Species = 836, Ability = A4, Moves = new[]{ 609, 528, 424, 706 }, Index = 36 }, // Boltund - new(50,08,4) { Species = 695, Ability = A4, Moves = new[]{ 085, 304, 447, 523 }, Index = 36 }, // Heliolisk - new(50,08,4) { Species = 598, Ability = A4, Moves = new[]{ 360, 438, 398, 014 }, Index = 36 }, // Ferrothorn - new(50,08,4) { Species = 830, Ability = A4, Moves = new[]{ 536, 304, 229, 437 }, Index = 36 }, // Eldegoss - new(60,10,5) { Species = 836, Ability = A4, Moves = new[]{ 609, 528, 424, 706 }, Index = 36 }, // Boltund - new(60,10,5) { Species = 695, Ability = A4, Moves = new[]{ 085, 304, 447, 523 }, Index = 36 }, // Heliolisk - new(60,10,5) { Species = 598, Ability = A4, Moves = new[]{ 360, 438, 398, 014 }, Index = 36 }, // Ferrothorn - new(60,10,5) { Species = 830, Ability = A4, Moves = new[]{ 536, 304, 063, 437 }, Index = 36 }, // Eldegoss - new(30,03,2,SW) { Species = 849, Ability = A4, Moves = new[]{ 351, 506, 491, 103 }, Index = 36, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(30,03,2,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 310, 029 }, Index = 36, CanGigantamax = true }, // Appletun - new(40,05,3,SW) { Species = 849, Ability = A4, Moves = new[]{ 435, 506, 398, 103 }, Index = 36, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(40,05,3,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 029 }, Index = 36, CanGigantamax = true }, // Appletun - new(50,08,4,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 398, 586 }, Index = 36, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(50,08,4,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 36, CanGigantamax = true }, // Appletun - new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 586 }, Index = 36, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(60,10,5,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 034, 406, 523 }, Index = 36, CanGigantamax = true }, // Appletun - new(30,03,2,SH) { Species = 849, Ability = A4, Moves = new[]{ 351, 506, 491, 103 }, Index = 36, CanGigantamax = true }, // Toxtricity - new(30,03,2,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 073, 491, 184 }, Index = 36, CanGigantamax = true }, // Flapple - new(40,05,3,SH) { Species = 849, Ability = A4, Moves = new[]{ 435, 506, 474, 103 }, Index = 36, CanGigantamax = true }, // Toxtricity - new(40,05,3,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 788, 491, 184 }, Index = 36, CanGigantamax = true }, // Flapple - new(50,08,4,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 586 }, Index = 36, CanGigantamax = true }, // Toxtricity - new(50,08,4,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 788, 491, 263 }, Index = 36, CanGigantamax = true }, // Flapple - new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 586 }, Index = 36, CanGigantamax = true }, // Toxtricity - new(60,10,5,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 788, 491, 263 }, Index = 36, CanGigantamax = true }, // Flapple + new(17,01,1) { Species = 848, Ability = A4, Moves = new[]{ 609, 051, 496, 715 }, Index = 36 }, // Toxel + new(17,01,1) { Species = 835, Ability = A4, Moves = new[]{ 609, 033, 044, 046 }, Index = 36 }, // Yamper + new(17,01,1) { Species = 695, Ability = A4, Moves = new[]{ 085, 098, 001, 189 }, Index = 36 }, // Heliolisk + new(17,01,1) { Species = 840, Ability = A4, Moves = new[]{ 110, 310, 000, 000 }, Index = 36 }, // Applin + new(17,01,1) { Species = 597, Ability = A4, Moves = new[]{ 033, 042, 232, 106 }, Index = 36 }, // Ferroseed + new(17,01,1) { Species = 829, Ability = A4, Moves = new[]{ 075, 496, 229, 670 }, Index = 36 }, // Gossifleur + new(30,03,2) { Species = 836, Ability = A4, Moves = new[]{ 609, 209, 204, 706 }, Index = 36 }, // Boltund + new(30,03,2) { Species = 695, Ability = A4, Moves = new[]{ 085, 098, 001, 189 }, Index = 36 }, // Heliolisk + new(30,03,2) { Species = 597, Ability = A4, Moves = new[]{ 442, 042, 232, 106 }, Index = 36 }, // Ferroseed + new(30,03,2) { Species = 830, Ability = A4, Moves = new[]{ 536, 496, 229, 670 }, Index = 36 }, // Eldegoss + new(40,05,3) { Species = 836, Ability = A4, Moves = new[]{ 609, 209, 424, 706 }, Index = 36 }, // Boltund + new(40,05,3) { Species = 695, Ability = A4, Moves = new[]{ 085, 098, 447, 189 }, Index = 36 }, // Heliolisk + new(40,05,3) { Species = 598, Ability = A4, Moves = new[]{ 442, 438, 398, 106 }, Index = 36 }, // Ferrothorn + new(40,05,3) { Species = 830, Ability = A4, Moves = new[]{ 536, 304, 229, 670 }, Index = 36 }, // Eldegoss + new(50,08,4) { Species = 836, Ability = A4, Moves = new[]{ 609, 528, 424, 706 }, Index = 36 }, // Boltund + new(50,08,4) { Species = 695, Ability = A4, Moves = new[]{ 085, 304, 447, 523 }, Index = 36 }, // Heliolisk + new(50,08,4) { Species = 598, Ability = A4, Moves = new[]{ 360, 438, 398, 014 }, Index = 36 }, // Ferrothorn + new(50,08,4) { Species = 830, Ability = A4, Moves = new[]{ 536, 304, 229, 437 }, Index = 36 }, // Eldegoss + new(60,10,5) { Species = 836, Ability = A4, Moves = new[]{ 609, 528, 424, 706 }, Index = 36 }, // Boltund + new(60,10,5) { Species = 695, Ability = A4, Moves = new[]{ 085, 304, 447, 523 }, Index = 36 }, // Heliolisk + new(60,10,5) { Species = 598, Ability = A4, Moves = new[]{ 360, 438, 398, 014 }, Index = 36 }, // Ferrothorn + new(60,10,5) { Species = 830, Ability = A4, Moves = new[]{ 536, 304, 063, 437 }, Index = 36 }, // Eldegoss + new(30,03,2,SW) { Species = 849, Ability = A4, Moves = new[]{ 351, 506, 491, 103 }, Index = 36, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(30,03,2,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 310, 029 }, Index = 36, CanGigantamax = true }, // Appletun + new(40,05,3,SW) { Species = 849, Ability = A4, Moves = new[]{ 435, 506, 398, 103 }, Index = 36, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(40,05,3,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 029 }, Index = 36, CanGigantamax = true }, // Appletun + new(50,08,4,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 398, 586 }, Index = 36, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(50,08,4,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 36, CanGigantamax = true }, // Appletun + new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 586 }, Index = 36, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(60,10,5,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 034, 406, 523 }, Index = 36, CanGigantamax = true }, // Appletun + new(30,03,2,SH) { Species = 849, Ability = A4, Moves = new[]{ 351, 506, 491, 103 }, Index = 36, CanGigantamax = true }, // Toxtricity + new(30,03,2,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 073, 491, 184 }, Index = 36, CanGigantamax = true }, // Flapple + new(40,05,3,SH) { Species = 849, Ability = A4, Moves = new[]{ 435, 506, 474, 103 }, Index = 36, CanGigantamax = true }, // Toxtricity + new(40,05,3,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 788, 491, 184 }, Index = 36, CanGigantamax = true }, // Flapple + new(50,08,4,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 586 }, Index = 36, CanGigantamax = true }, // Toxtricity + new(50,08,4,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 788, 491, 263 }, Index = 36, CanGigantamax = true }, // Flapple + new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 586 }, Index = 36, CanGigantamax = true }, // Toxtricity + new(60,10,5,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 788, 491, 263 }, Index = 36, CanGigantamax = true }, // Flapple - new(17,01,1) { Species = 025, Ability = A4, Moves = new[]{ 084, 098, 204, 086 }, Index = 34 }, // Pikachu - new(17,01,1) { Species = 026, Ability = A4, Moves = new[]{ 009, 129, 280, 204 }, Index = 34 }, // Raichu - new(17,01,1) { Species = 026, Ability = A4, Moves = new[]{ 009, 129, 280, 204 }, Index = 34, Form = 1 }, // Raichu-1 - new(17,01,1) { Species = 172, Ability = A4, Moves = new[]{ 589, 609, 085, 186 }, Index = 34 }, // Pichu - new(17,01,1) { Species = 778, Ability = A4, Moves = new[]{ 086, 452, 425, 010 }, Index = 34 }, // Mimikyu - new(30,03,2) { Species = 025, Ability = A4, Moves = new[]{ 209, 097, 204, 086 }, Index = 34 }, // Pikachu - new(30,03,2) { Species = 026, Ability = A4, Moves = new[]{ 009, 129, 280, 204 }, Index = 34 }, // Raichu - new(30,03,2) { Species = 026, Ability = A4, Moves = new[]{ 009, 129, 280, 204 }, Index = 34, Form = 1 }, // Raichu-1 - new(30,03,2) { Species = 172, Ability = A4, Moves = new[]{ 204, 609, 085, 186 }, Index = 34 }, // Pichu - new(30,03,2) { Species = 778, Ability = A4, Moves = new[]{ 086, 452, 425, 608 }, Index = 34 }, // Mimikyu - new(40,05,3) { Species = 025, Ability = A4, Moves = new[]{ 085, 231, 583, 086 }, Index = 34 }, // Pikachu - new(40,05,3) { Species = 026, Ability = A4, Moves = new[]{ 085, 034, 411, 583 }, Index = 34 }, // Raichu - new(40,05,3) { Species = 026, Ability = A4, Moves = new[]{ 085, 034, 057, 583 }, Index = 34, Form = 1 }, // Raichu-1 - new(40,05,3) { Species = 172, Ability = A4, Moves = new[]{ 204, 609, 085, 583 }, Index = 34 }, // Pichu - new(40,05,3) { Species = 778, Ability = A4, Moves = new[]{ 085, 452, 421, 608 }, Index = 34 }, // Mimikyu - new(50,08,4) { Species = 025, Ability = A4, Moves = new[]{ 087, 231, 583, 086 }, Index = 34 }, // Pikachu - new(50,08,4) { Species = 026, Ability = A4, Moves = new[]{ 087, 034, 411, 583 }, Index = 34 }, // Raichu - new(50,08,4) { Species = 026, Ability = A4, Moves = new[]{ 087, 034, 057, 583 }, Index = 34, Form = 1 }, // Raichu-1 - new(50,08,4) { Species = 172, Ability = A4, Moves = new[]{ 253, 609, 085, 583 }, Index = 34 }, // Pichu - new(50,08,4) { Species = 778, Ability = A4, Moves = new[]{ 085, 452, 261, 204 }, Index = 34 }, // Mimikyu - new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 344, 231, 583, 086 }, Index = 34 }, // Pikachu - new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 344, 231, 583, 086 }, Index = 34, Shiny = Shiny.Always }, // Pikachu - new(60,10,5) { Species = 026, Ability = A4, Moves = new[]{ 087, 034, 411, 583 }, Index = 34 }, // Raichu - new(60,10,5) { Species = 026, Ability = A4, Moves = new[]{ 087, 034, 057, 583 }, Index = 34, Form = 1 }, // Raichu-1 - new(60,10,5) { Species = 172, Ability = A4, Moves = new[]{ 253, 609, 085, 583 }, Index = 34 }, // Pichu - new(60,10,5) { Species = 778, Ability = A4, Moves = new[]{ 087, 452, 261, 583 }, Index = 34 }, // Mimikyu + new(17,01,1) { Species = 025, Ability = A4, Moves = new[]{ 084, 098, 204, 086 }, Index = 34 }, // Pikachu + new(17,01,1) { Species = 026, Ability = A4, Moves = new[]{ 009, 129, 280, 204 }, Index = 34 }, // Raichu + new(17,01,1) { Species = 026, Ability = A4, Moves = new[]{ 009, 129, 280, 204 }, Index = 34, Form = 1 }, // Raichu-1 + new(17,01,1) { Species = 172, Ability = A4, Moves = new[]{ 589, 609, 085, 186 }, Index = 34 }, // Pichu + new(17,01,1) { Species = 778, Ability = A4, Moves = new[]{ 086, 452, 425, 010 }, Index = 34 }, // Mimikyu + new(30,03,2) { Species = 025, Ability = A4, Moves = new[]{ 209, 097, 204, 086 }, Index = 34 }, // Pikachu + new(30,03,2) { Species = 026, Ability = A4, Moves = new[]{ 009, 129, 280, 204 }, Index = 34 }, // Raichu + new(30,03,2) { Species = 026, Ability = A4, Moves = new[]{ 009, 129, 280, 204 }, Index = 34, Form = 1 }, // Raichu-1 + new(30,03,2) { Species = 172, Ability = A4, Moves = new[]{ 204, 609, 085, 186 }, Index = 34 }, // Pichu + new(30,03,2) { Species = 778, Ability = A4, Moves = new[]{ 086, 452, 425, 608 }, Index = 34 }, // Mimikyu + new(40,05,3) { Species = 025, Ability = A4, Moves = new[]{ 085, 231, 583, 086 }, Index = 34 }, // Pikachu + new(40,05,3) { Species = 026, Ability = A4, Moves = new[]{ 085, 034, 411, 583 }, Index = 34 }, // Raichu + new(40,05,3) { Species = 026, Ability = A4, Moves = new[]{ 085, 034, 057, 583 }, Index = 34, Form = 1 }, // Raichu-1 + new(40,05,3) { Species = 172, Ability = A4, Moves = new[]{ 204, 609, 085, 583 }, Index = 34 }, // Pichu + new(40,05,3) { Species = 778, Ability = A4, Moves = new[]{ 085, 452, 421, 608 }, Index = 34 }, // Mimikyu + new(50,08,4) { Species = 025, Ability = A4, Moves = new[]{ 087, 231, 583, 086 }, Index = 34 }, // Pikachu + new(50,08,4) { Species = 026, Ability = A4, Moves = new[]{ 087, 034, 411, 583 }, Index = 34 }, // Raichu + new(50,08,4) { Species = 026, Ability = A4, Moves = new[]{ 087, 034, 057, 583 }, Index = 34, Form = 1 }, // Raichu-1 + new(50,08,4) { Species = 172, Ability = A4, Moves = new[]{ 253, 609, 085, 583 }, Index = 34 }, // Pichu + new(50,08,4) { Species = 778, Ability = A4, Moves = new[]{ 085, 452, 261, 204 }, Index = 34 }, // Mimikyu + new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 344, 231, 583, 086 }, Index = 34 }, // Pikachu + new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 344, 231, 583, 086 }, Index = 34, Shiny = Shiny.Always }, // Pikachu + new(60,10,5) { Species = 026, Ability = A4, Moves = new[]{ 087, 034, 411, 583 }, Index = 34 }, // Raichu + new(60,10,5) { Species = 026, Ability = A4, Moves = new[]{ 087, 034, 057, 583 }, Index = 34, Form = 1 }, // Raichu-1 + new(60,10,5) { Species = 172, Ability = A4, Moves = new[]{ 253, 609, 085, 583 }, Index = 34 }, // Pichu + new(60,10,5) { Species = 778, Ability = A4, Moves = new[]{ 087, 452, 261, 583 }, Index = 34 }, // Mimikyu - new(17,01,1) { Species = 833, Ability = A4, Moves = new[]{ 055, 033, 044, 240 }, Index = 33 }, // Chewtle - new(17,01,1) { Species = 349, Ability = A4, Moves = new[]{ 150, 033, 175, 057 }, Index = 33 }, // Feebas - new(17,01,1) { Species = 194, Ability = A4, Moves = new[]{ 341, 021, 039, 055 }, Index = 33 }, // Wooper - new(17,01,1) { Species = 843, Ability = A4, Moves = new[]{ 028, 035, 523, 693 }, Index = 33 }, // Silicobra - new(17,01,1) { Species = 449, Ability = A4, Moves = new[]{ 341, 328, 044, 033 }, Index = 33 }, // Hippopotas - new(17,01,1) { Species = 422, Ability = A4, Moves = new[]{ 352, 106, 189, 055 }, Index = 33, Form = 1 }, // Shellos-1 - new(30,03,2) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 317, 055 }, Index = 33, CanGigantamax = true }, // Drednaw - new(30,03,2) { Species = 349, Ability = A4, Moves = new[]{ 057, 033, 175, 150 }, Index = 33 }, // Feebas - new(30,03,2) { Species = 195, Ability = A4, Moves = new[]{ 341, 021, 401, 055 }, Index = 33 }, // Quagsire - new(30,03,2) { Species = 843, Ability = A4, Moves = new[]{ 091, 029, 523, 693 }, Index = 33 }, // Silicobra - new(30,03,2) { Species = 449, Ability = A4, Moves = new[]{ 341, 036, 044, 242 }, Index = 33 }, // Hippopotas - new(30,03,2) { Species = 423, Ability = A4, Moves = new[]{ 189, 352, 246, 106 }, Index = 33, Form = 1 }, // Gastrodon-1 - new(40,05,3) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 317, 055 }, Index = 33, CanGigantamax = true }, // Drednaw - new(40,05,3) { Species = 350, Ability = A4, Moves = new[]{ 057, 239, 034, 574 }, Index = 33 }, // Milotic - new(40,05,3) { Species = 195, Ability = A4, Moves = new[]{ 341, 021, 401, 005 }, Index = 33 }, // Quagsire - new(40,05,3) { Species = 844, Ability = A4, Moves = new[]{ 693, 523, 201, 091 }, Index = 33, CanGigantamax = true }, // Sandaconda - new(40,05,3) { Species = 450, Ability = A4, Moves = new[]{ 341, 422, 036, 242 }, Index = 33 }, // Hippowdon - new(40,05,3) { Species = 423, Ability = A4, Moves = new[]{ 414, 352, 246, 106 }, Index = 33, Form = 1 }, // Gastrodon-1 - new(50,08,4) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 33, CanGigantamax = true }, // Drednaw - new(50,08,4) { Species = 350, Ability = A4, Moves = new[]{ 057, 231, 034, 574 }, Index = 33 }, // Milotic - new(50,08,4) { Species = 195, Ability = A4, Moves = new[]{ 341, 280, 401, 005 }, Index = 33 }, // Quagsire - new(50,08,4) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 201, 091 }, Index = 33, CanGigantamax = true }, // Sandaconda - new(50,08,4) { Species = 450, Ability = A4, Moves = new[]{ 089, 422, 036, 242 }, Index = 33 }, // Hippowdon - new(50,08,4) { Species = 423, Ability = A4, Moves = new[]{ 414, 503, 311, 106 }, Index = 33, Form = 1 }, // Gastrodon-1 - new(60,10,5) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 33, CanGigantamax = true }, // Drednaw - new(60,10,5) { Species = 350, Ability = A4, Moves = new[]{ 503, 231, 034, 574 }, Index = 33 }, // Milotic - new(60,10,5) { Species = 195, Ability = A4, Moves = new[]{ 089, 280, 401, 005 }, Index = 33 }, // Quagsire - new(60,10,5) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 201, 091 }, Index = 33, CanGigantamax = true }, // Sandaconda - new(60,10,5) { Species = 450, Ability = A4, Moves = new[]{ 089, 422, 231, 242 }, Index = 33 }, // Hippowdon - new(60,10,5) { Species = 423, Ability = A4, Moves = new[]{ 414, 503, 311, 352 }, Index = 33, Form = 1 }, // Gastrodon-1 + new(17,01,1) { Species = 833, Ability = A4, Moves = new[]{ 055, 033, 044, 240 }, Index = 33 }, // Chewtle + new(17,01,1) { Species = 349, Ability = A4, Moves = new[]{ 150, 033, 175, 057 }, Index = 33 }, // Feebas + new(17,01,1) { Species = 194, Ability = A4, Moves = new[]{ 341, 021, 039, 055 }, Index = 33 }, // Wooper + new(17,01,1) { Species = 843, Ability = A4, Moves = new[]{ 028, 035, 523, 693 }, Index = 33 }, // Silicobra + new(17,01,1) { Species = 449, Ability = A4, Moves = new[]{ 341, 328, 044, 033 }, Index = 33 }, // Hippopotas + new(17,01,1) { Species = 422, Ability = A4, Moves = new[]{ 352, 106, 189, 055 }, Index = 33, Form = 1 }, // Shellos-1 + new(30,03,2) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 317, 055 }, Index = 33, CanGigantamax = true }, // Drednaw + new(30,03,2) { Species = 349, Ability = A4, Moves = new[]{ 057, 033, 175, 150 }, Index = 33 }, // Feebas + new(30,03,2) { Species = 195, Ability = A4, Moves = new[]{ 341, 021, 401, 055 }, Index = 33 }, // Quagsire + new(30,03,2) { Species = 843, Ability = A4, Moves = new[]{ 091, 029, 523, 693 }, Index = 33 }, // Silicobra + new(30,03,2) { Species = 449, Ability = A4, Moves = new[]{ 341, 036, 044, 242 }, Index = 33 }, // Hippopotas + new(30,03,2) { Species = 423, Ability = A4, Moves = new[]{ 189, 352, 246, 106 }, Index = 33, Form = 1 }, // Gastrodon-1 + new(40,05,3) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 317, 055 }, Index = 33, CanGigantamax = true }, // Drednaw + new(40,05,3) { Species = 350, Ability = A4, Moves = new[]{ 057, 239, 034, 574 }, Index = 33 }, // Milotic + new(40,05,3) { Species = 195, Ability = A4, Moves = new[]{ 341, 021, 401, 005 }, Index = 33 }, // Quagsire + new(40,05,3) { Species = 844, Ability = A4, Moves = new[]{ 693, 523, 201, 091 }, Index = 33, CanGigantamax = true }, // Sandaconda + new(40,05,3) { Species = 450, Ability = A4, Moves = new[]{ 341, 422, 036, 242 }, Index = 33 }, // Hippowdon + new(40,05,3) { Species = 423, Ability = A4, Moves = new[]{ 414, 352, 246, 106 }, Index = 33, Form = 1 }, // Gastrodon-1 + new(50,08,4) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 33, CanGigantamax = true }, // Drednaw + new(50,08,4) { Species = 350, Ability = A4, Moves = new[]{ 057, 231, 034, 574 }, Index = 33 }, // Milotic + new(50,08,4) { Species = 195, Ability = A4, Moves = new[]{ 341, 280, 401, 005 }, Index = 33 }, // Quagsire + new(50,08,4) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 201, 091 }, Index = 33, CanGigantamax = true }, // Sandaconda + new(50,08,4) { Species = 450, Ability = A4, Moves = new[]{ 089, 422, 036, 242 }, Index = 33 }, // Hippowdon + new(50,08,4) { Species = 423, Ability = A4, Moves = new[]{ 414, 503, 311, 106 }, Index = 33, Form = 1 }, // Gastrodon-1 + new(60,10,5) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 33, CanGigantamax = true }, // Drednaw + new(60,10,5) { Species = 350, Ability = A4, Moves = new[]{ 503, 231, 034, 574 }, Index = 33 }, // Milotic + new(60,10,5) { Species = 195, Ability = A4, Moves = new[]{ 089, 280, 401, 005 }, Index = 33 }, // Quagsire + new(60,10,5) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 201, 091 }, Index = 33, CanGigantamax = true }, // Sandaconda + new(60,10,5) { Species = 450, Ability = A4, Moves = new[]{ 089, 422, 231, 242 }, Index = 33 }, // Hippowdon + new(60,10,5) { Species = 423, Ability = A4, Moves = new[]{ 414, 503, 311, 352 }, Index = 33, Form = 1 }, // Gastrodon-1 - new(17,01,1) { Species = 320, Ability = A4, Moves = new[]{ 362, 034, 310, 054 }, Index = 31 }, // Wailmer - new(17,01,1) { Species = 098, Ability = A4, Moves = new[]{ 055, 341, 043, 232 }, Index = 31 }, // Krabby - new(17,01,1) { Species = 771, Ability = A4, Moves = new[]{ 240, 219, 213, 269 }, Index = 31 }, // Pyukumuku - new(17,01,1) { Species = 592, Ability = A4, Moves = new[]{ 352, 101, 071, 240 }, Index = 31 }, // Frillish - new(17,01,1) { Species = 458, Ability = A4, Moves = new[]{ 033, 017, 352, 469 }, Index = 31 }, // Mantyke - new(17,01,1) { Species = 318, Ability = A4, Moves = new[]{ 453, 305, 116, 044 }, Index = 31 }, // Carvanha - new(30,03,2) { Species = 320, Ability = A4, Moves = new[]{ 362, 034, 310, 054 }, Index = 31 }, // Wailmer - new(30,03,2) { Species = 098, Ability = A4, Moves = new[]{ 061, 341, 023, 232 }, Index = 31 }, // Krabby - new(30,03,2) { Species = 771, Ability = A4, Moves = new[]{ 240, 219, 213, 174 }, Index = 31 }, // Pyukumuku - new(30,03,2) { Species = 592, Ability = A4, Moves = new[]{ 362, 101, 071, 240 }, Index = 31 }, // Frillish - new(30,03,2) { Species = 458, Ability = A4, Moves = new[]{ 029, 017, 061, 469 }, Index = 31 }, // Mantyke - new(30,03,2) { Species = 319, Ability = A4, Moves = new[]{ 453, 400, 423, 044 }, Index = 31 }, // Sharpedo - new(40,05,3) { Species = 321, Ability = A4, Moves = new[]{ 362, 034, 340, 568 }, Index = 31 }, // Wailord - new(40,05,3) { Species = 099, Ability = A4, Moves = new[]{ 534, 341, 021, 014 }, Index = 31 }, // Kingler - new(40,05,3) { Species = 771, Ability = A4, Moves = new[]{ 240, 219, 213, 174 }, Index = 31 }, // Pyukumuku - new(40,05,3) { Species = 593, Ability = A4, Moves = new[]{ 362, 247, 071, 151 }, Index = 31 }, // Jellicent - new(40,05,3) { Species = 226, Ability = A4, Moves = new[]{ 029, 403, 060, 331 }, Index = 31 }, // Mantine - new(40,05,3) { Species = 319, Ability = A4, Moves = new[]{ 453, 242, 423, 044 }, Index = 31 }, // Sharpedo - new(50,08,4) { Species = 321, Ability = A4, Moves = new[]{ 056, 034, 340, 133 }, Index = 31 }, // Wailord - new(50,08,4) { Species = 099, Ability = A4, Moves = new[]{ 534, 341, 359, 014 }, Index = 31, CanGigantamax = true }, // Kingler - new(50,08,4) { Species = 771, Ability = A4, Moves = new[]{ 240, 219, 213, 174 }, Index = 31 }, // Pyukumuku - new(50,08,4) { Species = 593, Ability = A4, Moves = new[]{ 056, 247, 071, 151 }, Index = 31 }, // Jellicent - new(50,08,4) { Species = 226, Ability = A4, Moves = new[]{ 036, 403, 060, 331 }, Index = 31 }, // Mantine - new(50,08,4) { Species = 319, Ability = A4, Moves = new[]{ 057, 242, 423, 044 }, Index = 31 }, // Sharpedo - new(60,10,5) { Species = 321, Ability = A4, Moves = new[]{ 503, 034, 340, 133 }, Index = 31, Shiny = Shiny.Always }, // Wailord - new(60,10,5) { Species = 321, Ability = A4, Moves = new[]{ 056, 034, 340, 133 }, Index = 31 }, // Wailord - new(60,10,5) { Species = 771, Ability = A4, Moves = new[]{ 092, 599, 213, 174 }, Index = 31 }, // Pyukumuku - new(60,10,5) { Species = 593, Ability = A4, Moves = new[]{ 056, 058, 605, 433 }, Index = 31 }, // Jellicent - new(60,10,5) { Species = 226, Ability = A4, Moves = new[]{ 036, 403, 060, 331 }, Index = 31 }, // Mantine - new(60,10,5) { Species = 319, Ability = A4, Moves = new[]{ 057, 242, 423, 305 }, Index = 31 }, // Sharpedo + new(17,01,1) { Species = 320, Ability = A4, Moves = new[]{ 362, 034, 310, 054 }, Index = 31 }, // Wailmer + new(17,01,1) { Species = 098, Ability = A4, Moves = new[]{ 055, 341, 043, 232 }, Index = 31 }, // Krabby + new(17,01,1) { Species = 771, Ability = A4, Moves = new[]{ 240, 219, 213, 269 }, Index = 31 }, // Pyukumuku + new(17,01,1) { Species = 592, Ability = A4, Moves = new[]{ 352, 101, 071, 240 }, Index = 31 }, // Frillish + new(17,01,1) { Species = 458, Ability = A4, Moves = new[]{ 033, 017, 352, 469 }, Index = 31 }, // Mantyke + new(17,01,1) { Species = 318, Ability = A4, Moves = new[]{ 453, 305, 116, 044 }, Index = 31 }, // Carvanha + new(30,03,2) { Species = 320, Ability = A4, Moves = new[]{ 362, 034, 310, 054 }, Index = 31 }, // Wailmer + new(30,03,2) { Species = 098, Ability = A4, Moves = new[]{ 061, 341, 023, 232 }, Index = 31 }, // Krabby + new(30,03,2) { Species = 771, Ability = A4, Moves = new[]{ 240, 219, 213, 174 }, Index = 31 }, // Pyukumuku + new(30,03,2) { Species = 592, Ability = A4, Moves = new[]{ 362, 101, 071, 240 }, Index = 31 }, // Frillish + new(30,03,2) { Species = 458, Ability = A4, Moves = new[]{ 029, 017, 061, 469 }, Index = 31 }, // Mantyke + new(30,03,2) { Species = 319, Ability = A4, Moves = new[]{ 453, 400, 423, 044 }, Index = 31 }, // Sharpedo + new(40,05,3) { Species = 321, Ability = A4, Moves = new[]{ 362, 034, 340, 568 }, Index = 31 }, // Wailord + new(40,05,3) { Species = 099, Ability = A4, Moves = new[]{ 534, 341, 021, 014 }, Index = 31 }, // Kingler + new(40,05,3) { Species = 771, Ability = A4, Moves = new[]{ 240, 219, 213, 174 }, Index = 31 }, // Pyukumuku + new(40,05,3) { Species = 593, Ability = A4, Moves = new[]{ 362, 247, 071, 151 }, Index = 31 }, // Jellicent + new(40,05,3) { Species = 226, Ability = A4, Moves = new[]{ 029, 403, 060, 331 }, Index = 31 }, // Mantine + new(40,05,3) { Species = 319, Ability = A4, Moves = new[]{ 453, 242, 423, 044 }, Index = 31 }, // Sharpedo + new(50,08,4) { Species = 321, Ability = A4, Moves = new[]{ 056, 034, 340, 133 }, Index = 31 }, // Wailord + new(50,08,4) { Species = 099, Ability = A4, Moves = new[]{ 534, 341, 359, 014 }, Index = 31, CanGigantamax = true }, // Kingler + new(50,08,4) { Species = 771, Ability = A4, Moves = new[]{ 240, 219, 213, 174 }, Index = 31 }, // Pyukumuku + new(50,08,4) { Species = 593, Ability = A4, Moves = new[]{ 056, 247, 071, 151 }, Index = 31 }, // Jellicent + new(50,08,4) { Species = 226, Ability = A4, Moves = new[]{ 036, 403, 060, 331 }, Index = 31 }, // Mantine + new(50,08,4) { Species = 319, Ability = A4, Moves = new[]{ 057, 242, 423, 044 }, Index = 31 }, // Sharpedo + new(60,10,5) { Species = 321, Ability = A4, Moves = new[]{ 503, 034, 340, 133 }, Index = 31, Shiny = Shiny.Always }, // Wailord + new(60,10,5) { Species = 321, Ability = A4, Moves = new[]{ 056, 034, 340, 133 }, Index = 31 }, // Wailord + new(60,10,5) { Species = 771, Ability = A4, Moves = new[]{ 092, 599, 213, 174 }, Index = 31 }, // Pyukumuku + new(60,10,5) { Species = 593, Ability = A4, Moves = new[]{ 056, 058, 605, 433 }, Index = 31 }, // Jellicent + new(60,10,5) { Species = 226, Ability = A4, Moves = new[]{ 036, 403, 060, 331 }, Index = 31 }, // Mantine + new(60,10,5) { Species = 319, Ability = A4, Moves = new[]{ 057, 242, 423, 305 }, Index = 31 }, // Sharpedo - new(17,01,1) { Species = 878, Ability = A4, Moves = new[]{ 523, 205, 045, 249 }, Index = 27 }, // Cufant - new(17,01,1) { Species = 208, Ability = A4, Moves = new[]{ 242, 442, 106, 422 }, Index = 27 }, // Steelix - new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 232, 006, 242, 045 }, Index = 27, Form = 2 }, // Meowth-2 - new(17,01,1) { Species = 837, Ability = A4, Moves = new[]{ 229, 261, 479, 108 }, Index = 27 }, // Rolycoly - new(17,01,1) { Species = 111, Ability = A4, Moves = new[]{ 479, 523, 196, 182 }, Index = 27 }, // Rhyhorn - new(17,01,1) { Species = 095, Ability = A4, Moves = new[]{ 174, 225, 034, 106 }, Index = 27 }, // Onix - new(30,03,2) { Species = 878, Ability = A4, Moves = new[]{ 523, 023, 334, 249 }, Index = 27 }, // Cufant - new(30,03,2) { Species = 208, Ability = A4, Moves = new[]{ 157, 442, 328, 422 }, Index = 27 }, // Steelix - new(30,03,2) { Species = 863, Ability = A4, Moves = new[]{ 442, 006, 242, 269 }, Index = 27 }, // Perrserker - new(30,03,2) { Species = 838, Ability = A4, Moves = new[]{ 229, 488, 157, 108 }, Index = 27 }, // Carkol - new(30,03,2) { Species = 111, Ability = A4, Moves = new[]{ 350, 523, 196, 182 }, Index = 27 }, // Rhyhorn - new(30,03,2) { Species = 095, Ability = A4, Moves = new[]{ 776, 225, 034, 106 }, Index = 27 }, // Onix - new(40,05,3) { Species = 879, Ability = A4, Moves = new[]{ 070, 523, 334, 442 }, Index = 27, CanGigantamax = true }, // Copperajah - new(40,05,3) { Species = 208, Ability = A4, Moves = new[]{ 157, 442, 328, 422 }, Index = 27 }, // Steelix - new(40,05,3) { Species = 863, Ability = A4, Moves = new[]{ 442, 006, 154, 269 }, Index = 27 }, // Perrserker - new(40,05,3) { Species = 839, Ability = A4, Moves = new[]{ 025, 488, 157, 108 }, Index = 27, CanGigantamax = true }, // Coalossal - new(40,05,3) { Species = 112, Ability = A4, Moves = new[]{ 036, 529, 008, 182 }, Index = 27 }, // Rhydon - new(40,05,3) { Species = 095, Ability = A4, Moves = new[]{ 776, 225, 021, 201 }, Index = 27 }, // Onix - new(50,08,4) { Species = 879, Ability = A4, Moves = new[]{ 070, 523, 334, 442 }, Index = 27, CanGigantamax = true }, // Copperajah - new(50,08,4) { Species = 208, Ability = A4, Moves = new[]{ 157, 231, 328, 422 }, Index = 27 }, // Steelix - new(50,08,4) { Species = 863, Ability = A4, Moves = new[]{ 442, 583, 154, 269 }, Index = 27 }, // Perrserker - new(50,08,4) { Species = 839, Ability = A4, Moves = new[]{ 025, 488, 157, 115 }, Index = 27, CanGigantamax = true }, // Coalossal - new(50,08,4) { Species = 464, Ability = A4, Moves = new[]{ 350, 089, 008, 182 }, Index = 27 }, // Rhyperior - new(50,08,4) { Species = 095, Ability = A4, Moves = new[]{ 776, 225, 784, 201 }, Index = 27 }, // Onix - new(60,10,5) { Species = 879, Ability = A4, Moves = new[]{ 276, 089, 583, 442 }, Index = 27, CanGigantamax = true }, // Copperajah - new(60,10,5) { Species = 208, Ability = A4, Moves = new[]{ 038, 231, 529, 422 }, Index = 27 }, // Steelix - new(60,10,5) { Species = 863, Ability = A4, Moves = new[]{ 442, 583, 370, 269 }, Index = 27 }, // Perrserker - new(60,10,5) { Species = 839, Ability = A4, Moves = new[]{ 076, 682, 157, 115 }, Index = 27, CanGigantamax = true }, // Coalossal - new(60,10,5) { Species = 464, Ability = A4, Moves = new[]{ 444, 089, 008, 224 }, Index = 27 }, // Rhyperior - new(60,10,5) { Species = 095, Ability = A4, Moves = new[]{ 776, 444, 784, 201 }, Index = 27 }, // Onix + new(17,01,1) { Species = 878, Ability = A4, Moves = new[]{ 523, 205, 045, 249 }, Index = 27 }, // Cufant + new(17,01,1) { Species = 208, Ability = A4, Moves = new[]{ 242, 442, 106, 422 }, Index = 27 }, // Steelix + new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 232, 006, 242, 045 }, Index = 27, Form = 2 }, // Meowth-2 + new(17,01,1) { Species = 837, Ability = A4, Moves = new[]{ 229, 261, 479, 108 }, Index = 27 }, // Rolycoly + new(17,01,1) { Species = 111, Ability = A4, Moves = new[]{ 479, 523, 196, 182 }, Index = 27 }, // Rhyhorn + new(17,01,1) { Species = 095, Ability = A4, Moves = new[]{ 174, 225, 034, 106 }, Index = 27 }, // Onix + new(30,03,2) { Species = 878, Ability = A4, Moves = new[]{ 523, 023, 334, 249 }, Index = 27 }, // Cufant + new(30,03,2) { Species = 208, Ability = A4, Moves = new[]{ 157, 442, 328, 422 }, Index = 27 }, // Steelix + new(30,03,2) { Species = 863, Ability = A4, Moves = new[]{ 442, 006, 242, 269 }, Index = 27 }, // Perrserker + new(30,03,2) { Species = 838, Ability = A4, Moves = new[]{ 229, 488, 157, 108 }, Index = 27 }, // Carkol + new(30,03,2) { Species = 111, Ability = A4, Moves = new[]{ 350, 523, 196, 182 }, Index = 27 }, // Rhyhorn + new(30,03,2) { Species = 095, Ability = A4, Moves = new[]{ 776, 225, 034, 106 }, Index = 27 }, // Onix + new(40,05,3) { Species = 879, Ability = A4, Moves = new[]{ 070, 523, 334, 442 }, Index = 27, CanGigantamax = true }, // Copperajah + new(40,05,3) { Species = 208, Ability = A4, Moves = new[]{ 157, 442, 328, 422 }, Index = 27 }, // Steelix + new(40,05,3) { Species = 863, Ability = A4, Moves = new[]{ 442, 006, 154, 269 }, Index = 27 }, // Perrserker + new(40,05,3) { Species = 839, Ability = A4, Moves = new[]{ 025, 488, 157, 108 }, Index = 27, CanGigantamax = true }, // Coalossal + new(40,05,3) { Species = 112, Ability = A4, Moves = new[]{ 036, 529, 008, 182 }, Index = 27 }, // Rhydon + new(40,05,3) { Species = 095, Ability = A4, Moves = new[]{ 776, 225, 021, 201 }, Index = 27 }, // Onix + new(50,08,4) { Species = 879, Ability = A4, Moves = new[]{ 070, 523, 334, 442 }, Index = 27, CanGigantamax = true }, // Copperajah + new(50,08,4) { Species = 208, Ability = A4, Moves = new[]{ 157, 231, 328, 422 }, Index = 27 }, // Steelix + new(50,08,4) { Species = 863, Ability = A4, Moves = new[]{ 442, 583, 154, 269 }, Index = 27 }, // Perrserker + new(50,08,4) { Species = 839, Ability = A4, Moves = new[]{ 025, 488, 157, 115 }, Index = 27, CanGigantamax = true }, // Coalossal + new(50,08,4) { Species = 464, Ability = A4, Moves = new[]{ 350, 089, 008, 182 }, Index = 27 }, // Rhyperior + new(50,08,4) { Species = 095, Ability = A4, Moves = new[]{ 776, 225, 784, 201 }, Index = 27 }, // Onix + new(60,10,5) { Species = 879, Ability = A4, Moves = new[]{ 276, 089, 583, 442 }, Index = 27, CanGigantamax = true }, // Copperajah + new(60,10,5) { Species = 208, Ability = A4, Moves = new[]{ 038, 231, 529, 422 }, Index = 27 }, // Steelix + new(60,10,5) { Species = 863, Ability = A4, Moves = new[]{ 442, 583, 370, 269 }, Index = 27 }, // Perrserker + new(60,10,5) { Species = 839, Ability = A4, Moves = new[]{ 076, 682, 157, 115 }, Index = 27, CanGigantamax = true }, // Coalossal + new(60,10,5) { Species = 464, Ability = A4, Moves = new[]{ 444, 089, 008, 224 }, Index = 27 }, // Rhyperior + new(60,10,5) { Species = 095, Ability = A4, Moves = new[]{ 776, 444, 784, 201 }, Index = 27 }, // Onix - new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 044, 280, 523 }, Index = 26, CanGigantamax = true }, // Snorlax - new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 26, CanGigantamax = true }, // Snorlax - new(17,01,1,SW) { Species = 869, Ability = A4, Moves = new[]{ 033, 186, 577, 230 }, Index = 26, CanGigantamax = true }, // Alcremie - new(30,03,2,SW) { Species = 851, Ability = A4, Moves = new[]{ 044, 172, 489, 693 }, Index = 26, CanGigantamax = true }, // Centiskorch - new(30,03,2,SW) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 047 }, Index = 26, CanGigantamax = true }, // Lapras - new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 26, CanGigantamax = true }, // Kingler - new(40,05,3,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 26, CanGigantamax = true }, // Appletun - new(40,05,3,SW) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 044 }, Index = 26, CanGigantamax = true }, // Centiskorch - new(50,08,4,SW) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 269, 103 }, Index = 26, CanGigantamax = true }, // Corviknight - new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 26, CanGigantamax = true }, // Grimmsnarl - new(50,08,4,SW) { Species = 569, Ability = A4, Moves = new[]{ 188, 499, 034, 707 }, Index = 26, CanGigantamax = true }, // Garbodor - new(50,08,4,SW) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 105, 500 }, Index = 26, CanGigantamax = true }, // Alcremie - new(60,10,5,SW) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 058, 329 }, Index = 26, CanGigantamax = true }, // Lapras - new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 26, CanGigantamax = true }, // Toxtricity - new(60,10,5,SW) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 094, 196 }, Index = 26, CanGigantamax = true }, // Gengar - new(60,10,5,SW) { Species = 884, Ability = A4, Moves = new[]{ 430, 406, 085, 334 }, Index = 26, CanGigantamax = true }, // Duraludon - new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 26, CanGigantamax = true }, // Butterfree - new(30,03,2,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 496, 095 }, Index = 26, CanGigantamax = true }, // Orbeetle - new(30,03,2,SH) { Species = 068, Ability = A4, Moves = new[]{ 523, 490, 279, 233 }, Index = 26, CanGigantamax = true }, // Machamp - new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 26, CanGigantamax = true }, // Orbeetle - new(40,05,3,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 788, 491, 334 }, Index = 26, CanGigantamax = true }, // Flapple - new(40,05,3,SH) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 201, 091 }, Index = 26, CanGigantamax = true }, // Sandaconda - new(50,08,4,SH) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 26, CanGigantamax = true }, // Drednaw - new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 26, CanGigantamax = true }, // Hatterene - new(50,08,4,SH) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 076, 257 }, Index = 26, CanGigantamax = true }, // Charizard - new(50,08,4,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 26, CanGigantamax = true }, // Butterfree - new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 26, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(60,10,5,SH) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 523 }, Index = 26, CanGigantamax = true }, // Coalossal - new(60,10,5,SH) { Species = 068, Ability = A4, Moves = new[]{ 238, 007, 008, 089 }, Index = 26, CanGigantamax = true }, // Machamp - new(60,10,5,SH) { Species = 879, Ability = A4, Moves = new[]{ 442, 583, 438, 089 }, Index = 26, CanGigantamax = true }, // Copperajah + new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 044, 280, 523 }, Index = 26, CanGigantamax = true }, // Snorlax + new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 26, CanGigantamax = true }, // Snorlax + new(17,01,1,SW) { Species = 869, Ability = A4, Moves = new[]{ 033, 186, 577, 230 }, Index = 26, CanGigantamax = true }, // Alcremie + new(30,03,2,SW) { Species = 851, Ability = A4, Moves = new[]{ 044, 172, 489, 693 }, Index = 26, CanGigantamax = true }, // Centiskorch + new(30,03,2,SW) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 047 }, Index = 26, CanGigantamax = true }, // Lapras + new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 26, CanGigantamax = true }, // Kingler + new(40,05,3,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 26, CanGigantamax = true }, // Appletun + new(40,05,3,SW) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 044 }, Index = 26, CanGigantamax = true }, // Centiskorch + new(50,08,4,SW) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 269, 103 }, Index = 26, CanGigantamax = true }, // Corviknight + new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 26, CanGigantamax = true }, // Grimmsnarl + new(50,08,4,SW) { Species = 569, Ability = A4, Moves = new[]{ 188, 499, 034, 707 }, Index = 26, CanGigantamax = true }, // Garbodor + new(50,08,4,SW) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 105, 500 }, Index = 26, CanGigantamax = true }, // Alcremie + new(60,10,5,SW) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 058, 329 }, Index = 26, CanGigantamax = true }, // Lapras + new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 26, CanGigantamax = true }, // Toxtricity + new(60,10,5,SW) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 094, 196 }, Index = 26, CanGigantamax = true }, // Gengar + new(60,10,5,SW) { Species = 884, Ability = A4, Moves = new[]{ 430, 406, 085, 334 }, Index = 26, CanGigantamax = true }, // Duraludon + new(17,01,1,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 26, CanGigantamax = true }, // Butterfree + new(30,03,2,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 060, 496, 095 }, Index = 26, CanGigantamax = true }, // Orbeetle + new(30,03,2,SH) { Species = 068, Ability = A4, Moves = new[]{ 523, 490, 279, 233 }, Index = 26, CanGigantamax = true }, // Machamp + new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 26, CanGigantamax = true }, // Orbeetle + new(40,05,3,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 788, 491, 334 }, Index = 26, CanGigantamax = true }, // Flapple + new(40,05,3,SH) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 201, 091 }, Index = 26, CanGigantamax = true }, // Sandaconda + new(50,08,4,SH) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 26, CanGigantamax = true }, // Drednaw + new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 26, CanGigantamax = true }, // Hatterene + new(50,08,4,SH) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 076, 257 }, Index = 26, CanGigantamax = true }, // Charizard + new(50,08,4,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 26, CanGigantamax = true }, // Butterfree + new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 26, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(60,10,5,SH) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 523 }, Index = 26, CanGigantamax = true }, // Coalossal + new(60,10,5,SH) { Species = 068, Ability = A4, Moves = new[]{ 238, 007, 008, 089 }, Index = 26, CanGigantamax = true }, // Machamp + new(60,10,5,SH) { Species = 879, Ability = A4, Moves = new[]{ 442, 583, 438, 089 }, Index = 26, CanGigantamax = true }, // Copperajah - new(17,01,1) { Species = 143, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 25, CanGigantamax = true }, // Snorlax - //new(40,05,3) { Species = 807, Ability = A0, Moves = new[]{ 085, 007, 512, 280 }, Index = 25, Shiny = Shiny.Never }, // Zeraora - //new(50,08,4) { Species = 807, Ability = A0, Moves = new[]{ 085, 007, 200, 370 }, Index = 25, Shiny = Shiny.Never }, // Zeraora - //new(60,10,5) { Species = 807, Ability = A0, Moves = new[]{ 009, 299, 200, 370 }, Index = 25, Shiny = Shiny.Never }, // Zeraora - //new(100,10,6) { Species = 807, Ability = A0, Moves = new[]{ 435, 299, 200, 370 }, Index = 25, Shiny = Shiny.Always }, // Zeraora - new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 25, CanGigantamax = true }, // Snorlax - new(30,03,2,SW) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 047 }, Index = 25, CanGigantamax = true }, // Lapras - new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 25, CanGigantamax = true }, // Kingler - new(40,05,3,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 25, CanGigantamax = true }, // Appletun - new(40,05,3,SW) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 044 }, Index = 25, CanGigantamax = true }, // Centiskorch - new(50,08,4,SW) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 269, 103 }, Index = 25, CanGigantamax = true }, // Corviknight - new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 25, CanGigantamax = true }, // Grimmsnarl - new(50,08,4,SW) { Species = 569, Ability = A4, Moves = new[]{ 188, 499, 034, 707 }, Index = 25, CanGigantamax = true }, // Garbodor - new(50,08,4,SW) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 105, 500 }, Index = 25, CanGigantamax = true }, // Alcremie - new(60,10,5,SW) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 058, 329 }, Index = 25, CanGigantamax = true }, // Lapras - new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 25, CanGigantamax = true }, // Toxtricity - new(60,10,5,SW) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 094, 196 }, Index = 25, CanGigantamax = true }, // Gengar - new(60,10,5,SW) { Species = 884, Ability = A4, Moves = new[]{ 430, 406, 085, 334 }, Index = 25, CanGigantamax = true }, // Duraludon - new(30,03,2,SH) { Species = 068, Ability = A4, Moves = new[]{ 523, 490, 279, 233 }, Index = 25, CanGigantamax = true }, // Machamp - new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 25, CanGigantamax = true }, // Orbeetle - new(40,05,3,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 788, 491, 334 }, Index = 25, CanGigantamax = true }, // Flapple - new(40,05,3,SH) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 201, 091 }, Index = 25, CanGigantamax = true }, // Sandaconda - new(50,08,4,SH) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 25, CanGigantamax = true }, // Drednaw - new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 25, CanGigantamax = true }, // Hatterene - new(50,08,4,SH) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 076, 257 }, Index = 25, CanGigantamax = true }, // Charizard - new(50,08,4,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 25, CanGigantamax = true }, // Butterfree - new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 25, Form = 1, CanGigantamax = true }, // Toxtricity-1 - new(60,10,5,SH) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 523 }, Index = 25, CanGigantamax = true }, // Coalossal - new(60,10,5,SH) { Species = 068, Ability = A4, Moves = new[]{ 238, 007, 008, 089 }, Index = 25, CanGigantamax = true }, // Machamp - new(60,10,5,SH) { Species = 879, Ability = A4, Moves = new[]{ 442, 583, 438, 089 }, Index = 25, CanGigantamax = true }, // Copperajah - }; - } + new(17,01,1) { Species = 143, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 25, CanGigantamax = true }, // Snorlax + //new(40,05,3) { Species = 807, Ability = A0, Moves = new[]{ 085, 007, 512, 280 }, Index = 25, Shiny = Shiny.Never }, // Zeraora + //new(50,08,4) { Species = 807, Ability = A0, Moves = new[]{ 085, 007, 200, 370 }, Index = 25, Shiny = Shiny.Never }, // Zeraora + //new(60,10,5) { Species = 807, Ability = A0, Moves = new[]{ 009, 299, 200, 370 }, Index = 25, Shiny = Shiny.Never }, // Zeraora + //new(100,10,6) { Species = 807, Ability = A0, Moves = new[]{ 435, 299, 200, 370 }, Index = 25, Shiny = Shiny.Always }, // Zeraora + new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 25, CanGigantamax = true }, // Snorlax + new(30,03,2,SW) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 047 }, Index = 25, CanGigantamax = true }, // Lapras + new(40,05,3,SW) { Species = 099, Ability = A4, Moves = new[]{ 534, 232, 023, 106 }, Index = 25, CanGigantamax = true }, // Kingler + new(40,05,3,SW) { Species = 842, Ability = A4, Moves = new[]{ 787, 496, 406, 523 }, Index = 25, CanGigantamax = true }, // Appletun + new(40,05,3,SW) { Species = 851, Ability = A4, Moves = new[]{ 141, 424, 422, 044 }, Index = 25, CanGigantamax = true }, // Centiskorch + new(50,08,4,SW) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 269, 103 }, Index = 25, CanGigantamax = true }, // Corviknight + new(50,08,4,SW) { Species = 861, Ability = A4, Moves = new[]{ 789, 793, 280, 409 }, Index = 25, CanGigantamax = true }, // Grimmsnarl + new(50,08,4,SW) { Species = 569, Ability = A4, Moves = new[]{ 188, 499, 034, 707 }, Index = 25, CanGigantamax = true }, // Garbodor + new(50,08,4,SW) { Species = 869, Ability = A4, Moves = new[]{ 577, 605, 105, 500 }, Index = 25, CanGigantamax = true }, // Alcremie + new(60,10,5,SW) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 058, 329 }, Index = 25, CanGigantamax = true }, // Lapras + new(60,10,5,SW) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 474, 409 }, Index = 25, CanGigantamax = true }, // Toxtricity + new(60,10,5,SW) { Species = 094, Ability = A4, Moves = new[]{ 247, 482, 094, 196 }, Index = 25, CanGigantamax = true }, // Gengar + new(60,10,5,SW) { Species = 884, Ability = A4, Moves = new[]{ 430, 406, 085, 334 }, Index = 25, CanGigantamax = true }, // Duraludon + new(30,03,2,SH) { Species = 068, Ability = A4, Moves = new[]{ 523, 490, 279, 233 }, Index = 25, CanGigantamax = true }, // Machamp + new(40,05,3,SH) { Species = 826, Ability = A4, Moves = new[]{ 405, 094, 202, 247 }, Index = 25, CanGigantamax = true }, // Orbeetle + new(40,05,3,SH) { Species = 841, Ability = A4, Moves = new[]{ 406, 788, 491, 334 }, Index = 25, CanGigantamax = true }, // Flapple + new(40,05,3,SH) { Species = 844, Ability = A4, Moves = new[]{ 693, 529, 201, 091 }, Index = 25, CanGigantamax = true }, // Sandaconda + new(50,08,4,SH) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 25, CanGigantamax = true }, // Drednaw + new(50,08,4,SH) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 25, CanGigantamax = true }, // Hatterene + new(50,08,4,SH) { Species = 006, Ability = A4, Moves = new[]{ 053, 403, 076, 257 }, Index = 25, CanGigantamax = true }, // Charizard + new(50,08,4,SH) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 25, CanGigantamax = true }, // Butterfree + new(60,10,5,SH) { Species = 849, Ability = A4, Moves = new[]{ 786, 506, 599, 409 }, Index = 25, Form = 1, CanGigantamax = true }, // Toxtricity-1 + new(60,10,5,SH) { Species = 839, Ability = A4, Moves = new[]{ 246, 053, 157, 523 }, Index = 25, CanGigantamax = true }, // Coalossal + new(60,10,5,SH) { Species = 068, Ability = A4, Moves = new[]{ 238, 007, 008, 089 }, Index = 25, CanGigantamax = true }, // Machamp + new(60,10,5,SH) { Species = 879, Ability = A4, Moves = new[]{ 442, 583, 438, 089 }, Index = 25, CanGigantamax = true }, // Copperajah + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistDLC2.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistDLC2.cs index 129c00d23..aca3d504d 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistDLC2.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters8NestDistDLC2.cs @@ -1,855 +1,854 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +// Distribution Nest Encounters (BCAT) +internal static partial class Encounters8Nest { - // Distribution Nest Encounters (BCAT) - internal static partial class Encounters8Nest + /// + /// Nest distribution raids for available after Crown Tundra expansion. + /// + internal static readonly EncounterStatic8ND[] Dist_DLC2 = { - /// - /// Nest distribution raids for available after Crown Tundra expansion. - /// - internal static readonly EncounterStatic8ND[] Dist_DLC2 = - { - new(17,01,1) { Species = 824, Ability = A4, Moves = new[]{ 522, 000, 000, 000 }, Index = 106 }, // Blipbug - new(30,03,2) { Species = 833, Ability = A4, Moves = new[]{ 055, 033, 044, 029 }, Index = 106 }, // Chewtle - new(40,05,3) { Species = 832, Ability = A4, Moves = new[]{ 036, 024, 428, 086 }, Index = 106 }, // Dubwool - new(50,08,4) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 034, 681 }, Index = 106 }, // Corviknight - //new(60,10,5) { Species = 892, Ability = A0, Moves = new[]{ 555, 370, 389, 398 }, Index = 106, CanGigantamax = true }, // Urshifu - //new(60,10,5) { Species = 892, Ability = A0, Moves = new[]{ 710, 370, 009, 512 }, Index = 106, Form = 1, CanGigantamax = true }, // Urshifu-1 + new(17,01,1) { Species = 824, Ability = A4, Moves = new[]{ 522, 000, 000, 000 }, Index = 106 }, // Blipbug + new(30,03,2) { Species = 833, Ability = A4, Moves = new[]{ 055, 033, 044, 029 }, Index = 106 }, // Chewtle + new(40,05,3) { Species = 832, Ability = A4, Moves = new[]{ 036, 024, 428, 086 }, Index = 106 }, // Dubwool + new(50,08,4) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 034, 681 }, Index = 106 }, // Corviknight + //new(60,10,5) { Species = 892, Ability = A0, Moves = new[]{ 555, 370, 389, 398 }, Index = 106, CanGigantamax = true }, // Urshifu + //new(60,10,5) { Species = 892, Ability = A0, Moves = new[]{ 710, 370, 009, 512 }, Index = 106, Form = 1, CanGigantamax = true }, // Urshifu-1 - new(17,01,1) { Species = 090, Ability = A4, Moves = new[]{ 420, 056, 033, 250 }, Index = 104 }, // Shellder - new(17,01,1) { Species = 090, Ability = A4, Moves = new[]{ 420, 057, 033, 710 }, Index = 104 }, // Shellder - new(30,03,2) { Species = 090, Ability = A4, Moves = new[]{ 062, 056, 033, 250 }, Index = 104 }, // Shellder - new(30,03,2) { Species = 090, Ability = A4, Moves = new[]{ 062, 057, 033, 710 }, Index = 104 }, // Shellder - new(40,05,3) { Species = 090, Ability = A4, Moves = new[]{ 062, 056, 504, 534 }, Index = 104 }, // Shellder - new(40,05,3) { Species = 090, Ability = A4, Moves = new[]{ 062, 057, 161, 710 }, Index = 104 }, // Shellder - new(50,08,4) { Species = 090, Ability = A4, Moves = new[]{ 058, 057, 504, 534 }, Index = 104 }, // Shellder - new(50,08,4) { Species = 090, Ability = A4, Moves = new[]{ 058, 057, 161, 710 }, Index = 104 }, // Shellder - new(60,10,5) { Species = 090, Ability = A4, Moves = new[]{ 058, 057, 504, 534 }, Index = 104, Shiny = Shiny.Always }, // Shellder - new(60,10,5) { Species = 090, Ability = A4, Moves = new[]{ 058, 057, 161, 710 }, Index = 104 }, // Shellder - new(60,10,5) { Species = 090, Ability = A4, Moves = new[]{ 058, 057, 504, 534 }, Index = 104 }, // Shellder + new(17,01,1) { Species = 090, Ability = A4, Moves = new[]{ 420, 056, 033, 250 }, Index = 104 }, // Shellder + new(17,01,1) { Species = 090, Ability = A4, Moves = new[]{ 420, 057, 033, 710 }, Index = 104 }, // Shellder + new(30,03,2) { Species = 090, Ability = A4, Moves = new[]{ 062, 056, 033, 250 }, Index = 104 }, // Shellder + new(30,03,2) { Species = 090, Ability = A4, Moves = new[]{ 062, 057, 033, 710 }, Index = 104 }, // Shellder + new(40,05,3) { Species = 090, Ability = A4, Moves = new[]{ 062, 056, 504, 534 }, Index = 104 }, // Shellder + new(40,05,3) { Species = 090, Ability = A4, Moves = new[]{ 062, 057, 161, 710 }, Index = 104 }, // Shellder + new(50,08,4) { Species = 090, Ability = A4, Moves = new[]{ 058, 057, 504, 534 }, Index = 104 }, // Shellder + new(50,08,4) { Species = 090, Ability = A4, Moves = new[]{ 058, 057, 161, 710 }, Index = 104 }, // Shellder + new(60,10,5) { Species = 090, Ability = A4, Moves = new[]{ 058, 057, 504, 534 }, Index = 104, Shiny = Shiny.Always }, // Shellder + new(60,10,5) { Species = 090, Ability = A4, Moves = new[]{ 058, 057, 161, 710 }, Index = 104 }, // Shellder + new(60,10,5) { Species = 090, Ability = A4, Moves = new[]{ 058, 057, 504, 534 }, Index = 104 }, // Shellder - new(17,01,1) { Species = 438, Ability = A4, Moves = new[]{ 088, 383, 175, 313 }, Index = 102 }, // Bonsly - new(30,03,2) { Species = 438, Ability = A4, Moves = new[]{ 088, 317, 175, 313 }, Index = 102 }, // Bonsly - new(40,05,3) { Species = 438, Ability = A4, Moves = new[]{ 317, 389, 157, 313 }, Index = 102 }, // Bonsly - new(50,08,4) { Species = 185, Ability = A4, Moves = new[]{ 452, 359, 157, 389 }, Index = 102 }, // Sudowoodo - new(60,10,5) { Species = 185, Ability = A4, Moves = new[]{ 452, 444, 038, 389 }, Index = 102, Shiny = Shiny.Always }, // Sudowoodo - new(60,10,5) { Species = 185, Ability = A4, Moves = new[]{ 452, 444, 038, 389 }, Index = 102 }, // Sudowoodo + new(17,01,1) { Species = 438, Ability = A4, Moves = new[]{ 088, 383, 175, 313 }, Index = 102 }, // Bonsly + new(30,03,2) { Species = 438, Ability = A4, Moves = new[]{ 088, 317, 175, 313 }, Index = 102 }, // Bonsly + new(40,05,3) { Species = 438, Ability = A4, Moves = new[]{ 317, 389, 157, 313 }, Index = 102 }, // Bonsly + new(50,08,4) { Species = 185, Ability = A4, Moves = new[]{ 452, 359, 157, 389 }, Index = 102 }, // Sudowoodo + new(60,10,5) { Species = 185, Ability = A4, Moves = new[]{ 452, 444, 038, 389 }, Index = 102, Shiny = Shiny.Always }, // Sudowoodo + new(60,10,5) { Species = 185, Ability = A4, Moves = new[]{ 452, 444, 038, 389 }, Index = 102 }, // Sudowoodo - new(17,01,1) { Species = 696, Ability = A4, Moves = new[]{ 246, 033, 525, 046 }, Index = 100 }, // Tyrunt - new(17,01,1) { Species = 564, Ability = A4, Moves = new[]{ 453, 414, 246, 044 }, Index = 100 }, // Tirtouga - new(17,01,1) { Species = 566, Ability = A4, Moves = new[]{ 017, 246, 225, 414 }, Index = 100 }, // Archen - new(17,01,1) { Species = 698, Ability = A4, Moves = new[]{ 181, 086, 246, 196 }, Index = 100 }, // Amaura - new(30,03,2) { Species = 696, Ability = A4, Moves = new[]{ 246, 523, 525, 044 }, Index = 100 }, // Tyrunt - new(30,03,2) { Species = 564, Ability = A4, Moves = new[]{ 453, 414, 246, 242 }, Index = 100 }, // Tirtouga - new(30,03,2) { Species = 566, Ability = A4, Moves = new[]{ 017, 246, 225, 414 }, Index = 100 }, // Archen - new(30,03,2) { Species = 698, Ability = A4, Moves = new[]{ 062, 086, 246, 196 }, Index = 100 }, // Amaura - new(40,05,3) { Species = 697, Ability = A4, Moves = new[]{ 444, 523, 337, 231 }, Index = 100 }, // Tyrantrum - new(40,05,3) { Species = 565, Ability = A4, Moves = new[]{ 453, 414, 246, 231 }, Index = 100 }, // Carracosta - new(40,05,3) { Species = 567, Ability = A4, Moves = new[]{ 403, 157, 337, 414 }, Index = 100 }, // Archeops - new(40,05,3) { Species = 699, Ability = A4, Moves = new[]{ 059, 086, 444, 304 }, Index = 100 }, // Aurorus - new(50,08,4) { Species = 697, Ability = A4, Moves = new[]{ 444, 089, 337, 231 }, Index = 100 }, // Tyrantrum - new(50,08,4) { Species = 565, Ability = A4, Moves = new[]{ 056, 414, 246, 231 }, Index = 100 }, // Carracosta - new(50,08,4) { Species = 567, Ability = A4, Moves = new[]{ 403, 444, 337, 414 }, Index = 100 }, // Archeops - new(50,08,4) { Species = 699, Ability = A4, Moves = new[]{ 059, 094, 444, 304 }, Index = 100 }, // Aurorus - new(60,10,5) { Species = 697, Ability = A4, Moves = new[]{ 457, 089, 406, 231 }, Index = 100, Shiny = Shiny.Always }, // Tyrantrum - new(60,10,5) { Species = 697, Ability = A4, Moves = new[]{ 444, 089, 406, 231 }, Index = 100 }, // Tyrantrum - new(60,10,5) { Species = 565, Ability = A4, Moves = new[]{ 056, 444, 089, 231 }, Index = 100 }, // Carracosta - new(60,10,5) { Species = 567, Ability = A4, Moves = new[]{ 403, 444, 406, 414 }, Index = 100 }, // Archeops - new(60,10,5) { Species = 699, Ability = A4, Moves = new[]{ 059, 573, 444, 304 }, Index = 100 }, // Aurorus + new(17,01,1) { Species = 696, Ability = A4, Moves = new[]{ 246, 033, 525, 046 }, Index = 100 }, // Tyrunt + new(17,01,1) { Species = 564, Ability = A4, Moves = new[]{ 453, 414, 246, 044 }, Index = 100 }, // Tirtouga + new(17,01,1) { Species = 566, Ability = A4, Moves = new[]{ 017, 246, 225, 414 }, Index = 100 }, // Archen + new(17,01,1) { Species = 698, Ability = A4, Moves = new[]{ 181, 086, 246, 196 }, Index = 100 }, // Amaura + new(30,03,2) { Species = 696, Ability = A4, Moves = new[]{ 246, 523, 525, 044 }, Index = 100 }, // Tyrunt + new(30,03,2) { Species = 564, Ability = A4, Moves = new[]{ 453, 414, 246, 242 }, Index = 100 }, // Tirtouga + new(30,03,2) { Species = 566, Ability = A4, Moves = new[]{ 017, 246, 225, 414 }, Index = 100 }, // Archen + new(30,03,2) { Species = 698, Ability = A4, Moves = new[]{ 062, 086, 246, 196 }, Index = 100 }, // Amaura + new(40,05,3) { Species = 697, Ability = A4, Moves = new[]{ 444, 523, 337, 231 }, Index = 100 }, // Tyrantrum + new(40,05,3) { Species = 565, Ability = A4, Moves = new[]{ 453, 414, 246, 231 }, Index = 100 }, // Carracosta + new(40,05,3) { Species = 567, Ability = A4, Moves = new[]{ 403, 157, 337, 414 }, Index = 100 }, // Archeops + new(40,05,3) { Species = 699, Ability = A4, Moves = new[]{ 059, 086, 444, 304 }, Index = 100 }, // Aurorus + new(50,08,4) { Species = 697, Ability = A4, Moves = new[]{ 444, 089, 337, 231 }, Index = 100 }, // Tyrantrum + new(50,08,4) { Species = 565, Ability = A4, Moves = new[]{ 056, 414, 246, 231 }, Index = 100 }, // Carracosta + new(50,08,4) { Species = 567, Ability = A4, Moves = new[]{ 403, 444, 337, 414 }, Index = 100 }, // Archeops + new(50,08,4) { Species = 699, Ability = A4, Moves = new[]{ 059, 094, 444, 304 }, Index = 100 }, // Aurorus + new(60,10,5) { Species = 697, Ability = A4, Moves = new[]{ 457, 089, 406, 231 }, Index = 100, Shiny = Shiny.Always }, // Tyrantrum + new(60,10,5) { Species = 697, Ability = A4, Moves = new[]{ 444, 089, 406, 231 }, Index = 100 }, // Tyrantrum + new(60,10,5) { Species = 565, Ability = A4, Moves = new[]{ 056, 444, 089, 231 }, Index = 100 }, // Carracosta + new(60,10,5) { Species = 567, Ability = A4, Moves = new[]{ 403, 444, 406, 414 }, Index = 100 }, // Archeops + new(60,10,5) { Species = 699, Ability = A4, Moves = new[]{ 059, 573, 444, 304 }, Index = 100 }, // Aurorus - new(50,08,4) { Species = 003, Ability = A4, Moves = new[]{ 572, 188, 414, 200 }, Index = 98, CanGigantamax = true }, // Venusaur - new(50,08,4) { Species = 006, Ability = A4, Moves = new[]{ 257, 076, 542, 406 }, Index = 98, CanGigantamax = true }, // Charizard - new(50,08,4) { Species = 009, Ability = A4, Moves = new[]{ 057, 059, 430, 089 }, Index = 98, CanGigantamax = true }, // Blastoise - new(80,10,5) { Species = 003, Ability = A4, Moves = new[]{ 572, 188, 414, 200 }, Index = 98, CanGigantamax = true }, // Venusaur - new(80,10,5) { Species = 006, Ability = A4, Moves = new[]{ 257, 076, 542, 406 }, Index = 98, CanGigantamax = true }, // Charizard - new(80,10,5) { Species = 009, Ability = A4, Moves = new[]{ 057, 059, 430, 089 }, Index = 98, CanGigantamax = true }, // Blastoise + new(50,08,4) { Species = 003, Ability = A4, Moves = new[]{ 572, 188, 414, 200 }, Index = 98, CanGigantamax = true }, // Venusaur + new(50,08,4) { Species = 006, Ability = A4, Moves = new[]{ 257, 076, 542, 406 }, Index = 98, CanGigantamax = true }, // Charizard + new(50,08,4) { Species = 009, Ability = A4, Moves = new[]{ 057, 059, 430, 089 }, Index = 98, CanGigantamax = true }, // Blastoise + new(80,10,5) { Species = 003, Ability = A4, Moves = new[]{ 572, 188, 414, 200 }, Index = 98, CanGigantamax = true }, // Venusaur + new(80,10,5) { Species = 006, Ability = A4, Moves = new[]{ 257, 076, 542, 406 }, Index = 98, CanGigantamax = true }, // Charizard + new(80,10,5) { Species = 009, Ability = A4, Moves = new[]{ 057, 059, 430, 089 }, Index = 98, CanGigantamax = true }, // Blastoise - new(17,01,1) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 000, 000 }, Index = 95 }, // Magikarp - new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 95 }, // Meowth - new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 95 }, // Meowth - new(17,01,1) { Species = 438, Ability = A4, Moves = new[]{ 088, 383, 175, 313 }, Index = 95 }, // Bonsly - new(17,01,1) { Species = 554, Ability = A4, Moves = new[]{ 052, 044, 033, 526 }, Index = 95 }, // Darumaka - new(30,03,2) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 000, 000 }, Index = 95 }, // Magikarp - new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 95 }, // Meowth - new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 95 }, // Meowth - new(30,03,2) { Species = 438, Ability = A4, Moves = new[]{ 088, 317, 175, 313 }, Index = 95 }, // Bonsly - new(30,03,2) { Species = 554, Ability = A4, Moves = new[]{ 007, 044, 157, 029 }, Index = 95 }, // Darumaka - new(40,05,3) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 95 }, // Magikarp - new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 95 }, // Meowth - new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 95 }, // Meowth - new(40,05,3) { Species = 438, Ability = A4, Moves = new[]{ 317, 389, 157, 313 }, Index = 95 }, // Bonsly - new(40,05,3) { Species = 555, Ability = A4, Moves = new[]{ 359, 276, 157, 442 }, Index = 95 }, // Darmanitan - new(50,08,4) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 95 }, // Magikarp - new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 95 }, // Meowth - new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 95 }, // Meowth - new(50,08,4) { Species = 185, Ability = A4, Moves = new[]{ 452, 359, 157, 389 }, Index = 95 }, // Sudowoodo - new(50,08,4) { Species = 555, Ability = A4, Moves = new[]{ 394, 276, 157, 442 }, Index = 95 }, // Darmanitan - new(60,10,5) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 340 }, Index = 95, Shiny = Shiny.Always }, // Magikarp - new(60,10,5) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 340 }, Index = 95 }, // Magikarp - new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 95 }, // Meowth - new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 95 }, // Meowth - new(60,10,5) { Species = 185, Ability = A4, Moves = new[]{ 452, 444, 038, 389 }, Index = 95 }, // Sudowoodo - new(60,10,5) { Species = 555, Ability = A4, Moves = new[]{ 394, 276, 089, 442 }, Index = 95 }, // Darmanitan + new(17,01,1) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 000, 000 }, Index = 95 }, // Magikarp + new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 95 }, // Meowth + new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 95 }, // Meowth + new(17,01,1) { Species = 438, Ability = A4, Moves = new[]{ 088, 383, 175, 313 }, Index = 95 }, // Bonsly + new(17,01,1) { Species = 554, Ability = A4, Moves = new[]{ 052, 044, 033, 526 }, Index = 95 }, // Darumaka + new(30,03,2) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 000, 000 }, Index = 95 }, // Magikarp + new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 95 }, // Meowth + new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 95 }, // Meowth + new(30,03,2) { Species = 438, Ability = A4, Moves = new[]{ 088, 317, 175, 313 }, Index = 95 }, // Bonsly + new(30,03,2) { Species = 554, Ability = A4, Moves = new[]{ 007, 044, 157, 029 }, Index = 95 }, // Darumaka + new(40,05,3) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 95 }, // Magikarp + new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 95 }, // Meowth + new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 95 }, // Meowth + new(40,05,3) { Species = 438, Ability = A4, Moves = new[]{ 317, 389, 157, 313 }, Index = 95 }, // Bonsly + new(40,05,3) { Species = 555, Ability = A4, Moves = new[]{ 359, 276, 157, 442 }, Index = 95 }, // Darmanitan + new(50,08,4) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 000 }, Index = 95 }, // Magikarp + new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 95 }, // Meowth + new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 95 }, // Meowth + new(50,08,4) { Species = 185, Ability = A4, Moves = new[]{ 452, 359, 157, 389 }, Index = 95 }, // Sudowoodo + new(50,08,4) { Species = 555, Ability = A4, Moves = new[]{ 394, 276, 157, 442 }, Index = 95 }, // Darmanitan + new(60,10,5) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 340 }, Index = 95, Shiny = Shiny.Always }, // Magikarp + new(60,10,5) { Species = 129, Ability = A4, Moves = new[]{ 150, 033, 175, 340 }, Index = 95 }, // Magikarp + new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 95 }, // Meowth + new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 95 }, // Meowth + new(60,10,5) { Species = 185, Ability = A4, Moves = new[]{ 452, 444, 038, 389 }, Index = 95 }, // Sudowoodo + new(60,10,5) { Species = 555, Ability = A4, Moves = new[]{ 394, 276, 089, 442 }, Index = 95 }, // Darmanitan - new(17,01,1) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 034, 372 }, Index = 93 }, // Delibird - new(17,01,1) { Species = 121, Ability = A4, Moves = new[]{ 057, 408, 055, 129 }, Index = 93 }, // Starmie - new(17,01,1) { Species = 615, Ability = A4, Moves = new[]{ 196, 020, 229, 420 }, Index = 93 }, // Cryogonal - new(30,03,2) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 034, 693 }, Index = 93 }, // Delibird - new(30,03,2) { Species = 121, Ability = A4, Moves = new[]{ 057, 408, 094, 129 }, Index = 93 }, // Starmie - new(30,03,2) { Species = 615, Ability = A4, Moves = new[]{ 400, 062, 229, 246 }, Index = 93 }, // Cryogonal - new(40,05,3) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 280, 196 }, Index = 93 }, // Delibird - new(40,05,3) { Species = 121, Ability = A4, Moves = new[]{ 056, 408, 094, 129 }, Index = 93 }, // Starmie - new(40,05,3) { Species = 615, Ability = A4, Moves = new[]{ 400, 062, 573, 246 }, Index = 93 }, // Cryogonal - new(50,08,4) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 034, 280 }, Index = 93 }, // Delibird - new(50,08,4) { Species = 121, Ability = A4, Moves = new[]{ 056, 408, 094, 605 }, Index = 93 }, // Starmie - new(50,08,4) { Species = 615, Ability = A4, Moves = new[]{ 400, 058, 573, 430 }, Index = 93 }, // Cryogonal - new(60,10,5) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 065, 280 }, Index = 93, Shiny = Shiny.Always }, // Delibird - new(60,10,5) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 065, 280 }, Index = 93 }, // Delibird - new(60,10,5) { Species = 121, Ability = A4, Moves = new[]{ 056, 800, 094, 605 }, Index = 93 }, // Starmie - new(60,10,5) { Species = 615, Ability = A4, Moves = new[]{ 400, 329, 573, 430 }, Index = 93 }, // Cryogonal + new(17,01,1) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 034, 372 }, Index = 93 }, // Delibird + new(17,01,1) { Species = 121, Ability = A4, Moves = new[]{ 057, 408, 055, 129 }, Index = 93 }, // Starmie + new(17,01,1) { Species = 615, Ability = A4, Moves = new[]{ 196, 020, 229, 420 }, Index = 93 }, // Cryogonal + new(30,03,2) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 034, 693 }, Index = 93 }, // Delibird + new(30,03,2) { Species = 121, Ability = A4, Moves = new[]{ 057, 408, 094, 129 }, Index = 93 }, // Starmie + new(30,03,2) { Species = 615, Ability = A4, Moves = new[]{ 400, 062, 229, 246 }, Index = 93 }, // Cryogonal + new(40,05,3) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 280, 196 }, Index = 93 }, // Delibird + new(40,05,3) { Species = 121, Ability = A4, Moves = new[]{ 056, 408, 094, 129 }, Index = 93 }, // Starmie + new(40,05,3) { Species = 615, Ability = A4, Moves = new[]{ 400, 062, 573, 246 }, Index = 93 }, // Cryogonal + new(50,08,4) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 034, 280 }, Index = 93 }, // Delibird + new(50,08,4) { Species = 121, Ability = A4, Moves = new[]{ 056, 408, 094, 605 }, Index = 93 }, // Starmie + new(50,08,4) { Species = 615, Ability = A4, Moves = new[]{ 400, 058, 573, 430 }, Index = 93 }, // Cryogonal + new(60,10,5) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 065, 280 }, Index = 93, Shiny = Shiny.Always }, // Delibird + new(60,10,5) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 065, 280 }, Index = 93 }, // Delibird + new(60,10,5) { Species = 121, Ability = A4, Moves = new[]{ 056, 800, 094, 605 }, Index = 93 }, // Starmie + new(60,10,5) { Species = 615, Ability = A4, Moves = new[]{ 400, 329, 573, 430 }, Index = 93 }, // Cryogonal - new(17,01,1) { Species = 133, Ability = A4, Moves = new[]{ 033, 098, 039, 608 }, Index = 91 }, // Eevee - new(30,03,2) { Species = 133, Ability = A4, Moves = new[]{ 129, 098, 039, 608 }, Index = 91 }, // Eevee - new(40,05,3) { Species = 133, Ability = A4, Moves = new[]{ 129, 098, 231, 608 }, Index = 91 }, // Eevee - new(40,05,3) { Species = 134, Ability = A4, Moves = new[]{ 352, 058, 330, 304 }, Index = 91 }, // Vaporeon - new(40,05,3) { Species = 135, Ability = A4, Moves = new[]{ 422, 086, 247, 129 }, Index = 91 }, // Jolteon - new(40,05,3) { Species = 136, Ability = A4, Moves = new[]{ 436, 098, 276, 044 }, Index = 91 }, // Flareon - new(40,05,3) { Species = 196, Ability = A4, Moves = new[]{ 060, 605, 231, 098 }, Index = 91 }, // Espeon - new(40,05,3) { Species = 197, Ability = A4, Moves = new[]{ 555, 044, 247, 098 }, Index = 91 }, // Umbreon - new(40,05,3) { Species = 470, Ability = A4, Moves = new[]{ 202, 098, 073, 231 }, Index = 91 }, // Leafeon - new(40,05,3) { Species = 471, Ability = A4, Moves = new[]{ 573, 059, 247, 129 }, Index = 91 }, // Glaceon - new(40,05,3) { Species = 700, Ability = A4, Moves = new[]{ 574, 595, 129, 605 }, Index = 91 }, // Sylveon - new(50,08,4) { Species = 133, Ability = A4, Moves = new[]{ 129, 500, 231, 204 }, Index = 91 }, // Eevee - new(50,08,4) { Species = 134, Ability = A4, Moves = new[]{ 056, 058, 330, 304 }, Index = 91 }, // Vaporeon - new(50,08,4) { Species = 135, Ability = A4, Moves = new[]{ 087, 086, 247, 129 }, Index = 91 }, // Jolteon - new(50,08,4) { Species = 136, Ability = A4, Moves = new[]{ 394, 098, 276, 044 }, Index = 91 }, // Flareon - new(50,08,4) { Species = 196, Ability = A4, Moves = new[]{ 094, 605, 231, 098 }, Index = 91 }, // Espeon - new(50,08,4) { Species = 197, Ability = A4, Moves = new[]{ 555, 492, 247, 098 }, Index = 91 }, // Umbreon - new(50,08,4) { Species = 470, Ability = A4, Moves = new[]{ 348, 098, 073, 231 }, Index = 91 }, // Leafeon - new(50,08,4) { Species = 471, Ability = A4, Moves = new[]{ 573, 059, 247, 311 }, Index = 91 }, // Glaceon - new(50,08,4) { Species = 700, Ability = A4, Moves = new[]{ 585, 595, 129, 605 }, Index = 91 }, // Sylveon - new(60,10,5) { Species = 133, Ability = A4, Moves = new[]{ 387, 500, 231, 204 }, Index = 91, Shiny = Shiny.Always }, // Eevee - new(60,10,5) { Species = 134, Ability = A4, Moves = new[]{ 056, 058, 503, 304 }, Index = 91 }, // Vaporeon - new(60,10,5) { Species = 135, Ability = A4, Moves = new[]{ 087, 085, 247, 129 }, Index = 91 }, // Jolteon - new(60,10,5) { Species = 136, Ability = A4, Moves = new[]{ 394, 231, 276, 044 }, Index = 91 }, // Flareon - new(60,10,5) { Species = 196, Ability = A4, Moves = new[]{ 094, 605, 231, 129 }, Index = 91 }, // Espeon - new(60,10,5) { Species = 197, Ability = A4, Moves = new[]{ 555, 492, 247, 304 }, Index = 91 }, // Umbreon - new(60,10,5) { Species = 470, Ability = A4, Moves = new[]{ 348, 311, 073, 231 }, Index = 91 }, // Leafeon - new(60,10,5) { Species = 471, Ability = A4, Moves = new[]{ 573, 059, 247, 304 }, Index = 91 }, // Glaceon - new(60,10,5) { Species = 700, Ability = A4, Moves = new[]{ 585, 595, 304, 605 }, Index = 91 }, // Sylveon - new(60,10,5) { Species = 133, Ability = A4, Moves = new[]{ 387, 500, 231, 204 }, Index = 91, CanGigantamax = true }, // Eevee + new(17,01,1) { Species = 133, Ability = A4, Moves = new[]{ 033, 098, 039, 608 }, Index = 91 }, // Eevee + new(30,03,2) { Species = 133, Ability = A4, Moves = new[]{ 129, 098, 039, 608 }, Index = 91 }, // Eevee + new(40,05,3) { Species = 133, Ability = A4, Moves = new[]{ 129, 098, 231, 608 }, Index = 91 }, // Eevee + new(40,05,3) { Species = 134, Ability = A4, Moves = new[]{ 352, 058, 330, 304 }, Index = 91 }, // Vaporeon + new(40,05,3) { Species = 135, Ability = A4, Moves = new[]{ 422, 086, 247, 129 }, Index = 91 }, // Jolteon + new(40,05,3) { Species = 136, Ability = A4, Moves = new[]{ 436, 098, 276, 044 }, Index = 91 }, // Flareon + new(40,05,3) { Species = 196, Ability = A4, Moves = new[]{ 060, 605, 231, 098 }, Index = 91 }, // Espeon + new(40,05,3) { Species = 197, Ability = A4, Moves = new[]{ 555, 044, 247, 098 }, Index = 91 }, // Umbreon + new(40,05,3) { Species = 470, Ability = A4, Moves = new[]{ 202, 098, 073, 231 }, Index = 91 }, // Leafeon + new(40,05,3) { Species = 471, Ability = A4, Moves = new[]{ 573, 059, 247, 129 }, Index = 91 }, // Glaceon + new(40,05,3) { Species = 700, Ability = A4, Moves = new[]{ 574, 595, 129, 605 }, Index = 91 }, // Sylveon + new(50,08,4) { Species = 133, Ability = A4, Moves = new[]{ 129, 500, 231, 204 }, Index = 91 }, // Eevee + new(50,08,4) { Species = 134, Ability = A4, Moves = new[]{ 056, 058, 330, 304 }, Index = 91 }, // Vaporeon + new(50,08,4) { Species = 135, Ability = A4, Moves = new[]{ 087, 086, 247, 129 }, Index = 91 }, // Jolteon + new(50,08,4) { Species = 136, Ability = A4, Moves = new[]{ 394, 098, 276, 044 }, Index = 91 }, // Flareon + new(50,08,4) { Species = 196, Ability = A4, Moves = new[]{ 094, 605, 231, 098 }, Index = 91 }, // Espeon + new(50,08,4) { Species = 197, Ability = A4, Moves = new[]{ 555, 492, 247, 098 }, Index = 91 }, // Umbreon + new(50,08,4) { Species = 470, Ability = A4, Moves = new[]{ 348, 098, 073, 231 }, Index = 91 }, // Leafeon + new(50,08,4) { Species = 471, Ability = A4, Moves = new[]{ 573, 059, 247, 311 }, Index = 91 }, // Glaceon + new(50,08,4) { Species = 700, Ability = A4, Moves = new[]{ 585, 595, 129, 605 }, Index = 91 }, // Sylveon + new(60,10,5) { Species = 133, Ability = A4, Moves = new[]{ 387, 500, 231, 204 }, Index = 91, Shiny = Shiny.Always }, // Eevee + new(60,10,5) { Species = 134, Ability = A4, Moves = new[]{ 056, 058, 503, 304 }, Index = 91 }, // Vaporeon + new(60,10,5) { Species = 135, Ability = A4, Moves = new[]{ 087, 085, 247, 129 }, Index = 91 }, // Jolteon + new(60,10,5) { Species = 136, Ability = A4, Moves = new[]{ 394, 231, 276, 044 }, Index = 91 }, // Flareon + new(60,10,5) { Species = 196, Ability = A4, Moves = new[]{ 094, 605, 231, 129 }, Index = 91 }, // Espeon + new(60,10,5) { Species = 197, Ability = A4, Moves = new[]{ 555, 492, 247, 304 }, Index = 91 }, // Umbreon + new(60,10,5) { Species = 470, Ability = A4, Moves = new[]{ 348, 311, 073, 231 }, Index = 91 }, // Leafeon + new(60,10,5) { Species = 471, Ability = A4, Moves = new[]{ 573, 059, 247, 304 }, Index = 91 }, // Glaceon + new(60,10,5) { Species = 700, Ability = A4, Moves = new[]{ 585, 595, 304, 605 }, Index = 91 }, // Sylveon + new(60,10,5) { Species = 133, Ability = A4, Moves = new[]{ 387, 500, 231, 204 }, Index = 91, CanGigantamax = true }, // Eevee - new(17,01,1) { Species = 570, Ability = A4, Moves = new[]{ 468, 247, 010, 043 }, Index = 89 }, // Zorua - new(17,01,1) { Species = 302, Ability = A4, Moves = new[]{ 252, 010, 425, 555 }, Index = 89 }, // Sableye - new(17,01,1) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 043, 506 }, Index = 89 }, // Duskull - new(17,01,1) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 043, 681 }, Index = 89 }, // Rookidee - new(17,01,1) { Species = 827, Ability = A4, Moves = new[]{ 555, 098, 251, 468 }, Index = 89 }, // Nickit - new(30,03,2) { Species = 571, Ability = A4, Moves = new[]{ 400, 247, 279, 304 }, Index = 89 }, // Zoroark - new(30,03,2) { Species = 302, Ability = A4, Moves = new[]{ 252, 094, 425, 555 }, Index = 89 }, // Sableye - new(30,03,2) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 371, 506 }, Index = 89 }, // Duskull - new(30,03,2) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 279, 681 }, Index = 89 }, // Corvisquire - new(30,03,2) { Species = 828, Ability = A4, Moves = new[]{ 555, 098, 251, 583 }, Index = 89 }, // Thievul - new(40,05,3) { Species = 571, Ability = A4, Moves = new[]{ 400, 247, 411, 304 }, Index = 89 }, // Zoroark - new(40,05,3) { Species = 302, Ability = A4, Moves = new[]{ 252, 261, 247, 555 }, Index = 89 }, // Sableye - new(40,05,3) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 371, 157 }, Index = 89 }, // Dusknoir - new(40,05,3) { Species = 823, Ability = A4, Moves = new[]{ 403, 442, 034, 681 }, Index = 89 }, // Corviknight - new(40,05,3) { Species = 828, Ability = A4, Moves = new[]{ 555, 098, 094, 583 }, Index = 89 }, // Thievul - new(50,08,4) { Species = 571, Ability = A4, Moves = new[]{ 539, 247, 411, 304 }, Index = 89 }, // Zoroark - new(50,08,4) { Species = 302, Ability = A4, Moves = new[]{ 605, 261, 247, 555 }, Index = 89 }, // Sableye - new(50,08,4) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 157 }, Index = 89 }, // Dusknoir - new(50,08,4) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 034, 681 }, Index = 89 }, // Corviknight - new(50,08,4) { Species = 828, Ability = A4, Moves = new[]{ 555, 341, 094, 583 }, Index = 89 }, // Thievul - new(60,10,5) { Species = 571, Ability = A4, Moves = new[]{ 539, 247, 411, 492 }, Index = 89, Shiny = Shiny.Always }, // Zoroark - new(60,10,5) { Species = 571, Ability = A4, Moves = new[]{ 539, 247, 411, 492 }, Index = 89 }, // Zoroark - new(60,10,5) { Species = 302, Ability = A4, Moves = new[]{ 605, 261, 247, 492 }, Index = 89 }, // Sableye - new(60,10,5) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 089 }, Index = 89 }, // Dusknoir - new(60,10,5) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 776, 372 }, Index = 89 }, // Corviknight - new(60,10,5) { Species = 828, Ability = A4, Moves = new[]{ 555, 492, 094, 583 }, Index = 89 }, // Thievul + new(17,01,1) { Species = 570, Ability = A4, Moves = new[]{ 468, 247, 010, 043 }, Index = 89 }, // Zorua + new(17,01,1) { Species = 302, Ability = A4, Moves = new[]{ 252, 010, 425, 555 }, Index = 89 }, // Sableye + new(17,01,1) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 043, 506 }, Index = 89 }, // Duskull + new(17,01,1) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 043, 681 }, Index = 89 }, // Rookidee + new(17,01,1) { Species = 827, Ability = A4, Moves = new[]{ 555, 098, 251, 468 }, Index = 89 }, // Nickit + new(30,03,2) { Species = 571, Ability = A4, Moves = new[]{ 400, 247, 279, 304 }, Index = 89 }, // Zoroark + new(30,03,2) { Species = 302, Ability = A4, Moves = new[]{ 252, 094, 425, 555 }, Index = 89 }, // Sableye + new(30,03,2) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 371, 506 }, Index = 89 }, // Duskull + new(30,03,2) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 279, 681 }, Index = 89 }, // Corvisquire + new(30,03,2) { Species = 828, Ability = A4, Moves = new[]{ 555, 098, 251, 583 }, Index = 89 }, // Thievul + new(40,05,3) { Species = 571, Ability = A4, Moves = new[]{ 400, 247, 411, 304 }, Index = 89 }, // Zoroark + new(40,05,3) { Species = 302, Ability = A4, Moves = new[]{ 252, 261, 247, 555 }, Index = 89 }, // Sableye + new(40,05,3) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 371, 157 }, Index = 89 }, // Dusknoir + new(40,05,3) { Species = 823, Ability = A4, Moves = new[]{ 403, 442, 034, 681 }, Index = 89 }, // Corviknight + new(40,05,3) { Species = 828, Ability = A4, Moves = new[]{ 555, 098, 094, 583 }, Index = 89 }, // Thievul + new(50,08,4) { Species = 571, Ability = A4, Moves = new[]{ 539, 247, 411, 304 }, Index = 89 }, // Zoroark + new(50,08,4) { Species = 302, Ability = A4, Moves = new[]{ 605, 261, 247, 555 }, Index = 89 }, // Sableye + new(50,08,4) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 157 }, Index = 89 }, // Dusknoir + new(50,08,4) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 034, 681 }, Index = 89 }, // Corviknight + new(50,08,4) { Species = 828, Ability = A4, Moves = new[]{ 555, 341, 094, 583 }, Index = 89 }, // Thievul + new(60,10,5) { Species = 571, Ability = A4, Moves = new[]{ 539, 247, 411, 492 }, Index = 89, Shiny = Shiny.Always }, // Zoroark + new(60,10,5) { Species = 571, Ability = A4, Moves = new[]{ 539, 247, 411, 492 }, Index = 89 }, // Zoroark + new(60,10,5) { Species = 302, Ability = A4, Moves = new[]{ 605, 261, 247, 492 }, Index = 89 }, // Sableye + new(60,10,5) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 089 }, Index = 89 }, // Dusknoir + new(60,10,5) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 776, 372 }, Index = 89 }, // Corviknight + new(60,10,5) { Species = 828, Ability = A4, Moves = new[]{ 555, 492, 094, 583 }, Index = 89 }, // Thievul - new(17,01,1) { Species = 722, Ability = A4, Moves = new[]{ 064, 075, 389, 129 }, Index = 87 }, // Rowlet - new(17,01,1) { Species = 725, Ability = A4, Moves = new[]{ 052, 006, 044, 421 }, Index = 87 }, // Litten - new(17,01,1) { Species = 728, Ability = A4, Moves = new[]{ 574, 001, 453, 196 }, Index = 87 }, // Popplio - new(30,03,2) { Species = 722, Ability = A4, Moves = new[]{ 064, 348, 389, 129 }, Index = 87 }, // Rowlet - new(30,03,2) { Species = 725, Ability = A4, Moves = new[]{ 053, 006, 044, 421 }, Index = 87 }, // Litten - new(30,03,2) { Species = 728, Ability = A4, Moves = new[]{ 574, 061, 453, 196 }, Index = 87 }, // Popplio - new(40,05,3) { Species = 722, Ability = A4, Moves = new[]{ 413, 348, 389, 129 }, Index = 87 }, // Rowlet - new(40,05,3) { Species = 725, Ability = A4, Moves = new[]{ 394, 006, 044, 421 }, Index = 87 }, // Litten - new(40,05,3) { Species = 728, Ability = A4, Moves = new[]{ 585, 056, 453, 196 }, Index = 87 }, // Popplio - new(50,08,4) { Species = 722, Ability = A4, Moves = new[]{ 413, 348, 389, 412 }, Index = 87 }, // Rowlet - new(50,08,4) { Species = 725, Ability = A4, Moves = new[]{ 394, 006, 279, 421 }, Index = 87 }, // Litten - new(50,08,4) { Species = 728, Ability = A4, Moves = new[]{ 585, 056, 453, 059 }, Index = 87 }, // Popplio - new(60,10,5) { Species = 722, Ability = A4, Moves = new[]{ 413, 348, 421, 412 }, Index = 87 }, // Rowlet - new(60,10,5) { Species = 725, Ability = A4, Moves = new[]{ 394, 006, 279, 242 }, Index = 87 }, // Litten - new(60,10,5) { Species = 728, Ability = A4, Moves = new[]{ 585, 056, 453, 058 }, Index = 87 }, // Popplio + new(17,01,1) { Species = 722, Ability = A4, Moves = new[]{ 064, 075, 389, 129 }, Index = 87 }, // Rowlet + new(17,01,1) { Species = 725, Ability = A4, Moves = new[]{ 052, 006, 044, 421 }, Index = 87 }, // Litten + new(17,01,1) { Species = 728, Ability = A4, Moves = new[]{ 574, 001, 453, 196 }, Index = 87 }, // Popplio + new(30,03,2) { Species = 722, Ability = A4, Moves = new[]{ 064, 348, 389, 129 }, Index = 87 }, // Rowlet + new(30,03,2) { Species = 725, Ability = A4, Moves = new[]{ 053, 006, 044, 421 }, Index = 87 }, // Litten + new(30,03,2) { Species = 728, Ability = A4, Moves = new[]{ 574, 061, 453, 196 }, Index = 87 }, // Popplio + new(40,05,3) { Species = 722, Ability = A4, Moves = new[]{ 413, 348, 389, 129 }, Index = 87 }, // Rowlet + new(40,05,3) { Species = 725, Ability = A4, Moves = new[]{ 394, 006, 044, 421 }, Index = 87 }, // Litten + new(40,05,3) { Species = 728, Ability = A4, Moves = new[]{ 585, 056, 453, 196 }, Index = 87 }, // Popplio + new(50,08,4) { Species = 722, Ability = A4, Moves = new[]{ 413, 348, 389, 412 }, Index = 87 }, // Rowlet + new(50,08,4) { Species = 725, Ability = A4, Moves = new[]{ 394, 006, 279, 421 }, Index = 87 }, // Litten + new(50,08,4) { Species = 728, Ability = A4, Moves = new[]{ 585, 056, 453, 059 }, Index = 87 }, // Popplio + new(60,10,5) { Species = 722, Ability = A4, Moves = new[]{ 413, 348, 421, 412 }, Index = 87 }, // Rowlet + new(60,10,5) { Species = 725, Ability = A4, Moves = new[]{ 394, 006, 279, 242 }, Index = 87 }, // Litten + new(60,10,5) { Species = 728, Ability = A4, Moves = new[]{ 585, 056, 453, 058 }, Index = 87 }, // Popplio - new(17,01,1) { Species = 337, Ability = A4, Moves = new[]{ 585, 033, 093, 088 }, Index = 85 }, // Lunatone - new(17,01,1) { Species = 338, Ability = A4, Moves = new[]{ 394, 033, 093, 088 }, Index = 85 }, // Solrock - new(30,03,2) { Species = 337, Ability = A4, Moves = new[]{ 585, 129, 094, 088 }, Index = 85 }, // Lunatone - new(30,03,2) { Species = 338, Ability = A4, Moves = new[]{ 394, 129, 094, 088 }, Index = 85 }, // Solrock - new(40,05,3) { Species = 337, Ability = A4, Moves = new[]{ 585, 129, 094, 157 }, Index = 85 }, // Lunatone - new(40,05,3) { Species = 338, Ability = A4, Moves = new[]{ 394, 129, 094, 157 }, Index = 85 }, // Solrock - new(50,08,4) { Species = 337, Ability = A4, Moves = new[]{ 585, 058, 094, 157 }, Index = 85 }, // Lunatone - new(50,08,4) { Species = 338, Ability = A4, Moves = new[]{ 394, 076, 094, 157 }, Index = 85 }, // Solrock - new(60,10,5) { Species = 337, Ability = A4, Moves = new[]{ 585, 058, 094, 444 }, Index = 85, Shiny = Shiny.Always }, // Lunatone - new(60,10,5) { Species = 337, Ability = A4, Moves = new[]{ 585, 058, 094, 444 }, Index = 85 }, // Lunatone - new(60,10,5) { Species = 338, Ability = A4, Moves = new[]{ 394, 076, 094, 444 }, Index = 85, Shiny = Shiny.Always }, // Solrock - new(60,10,5) { Species = 338, Ability = A4, Moves = new[]{ 394, 076, 094, 444 }, Index = 85 }, // Solrock + new(17,01,1) { Species = 337, Ability = A4, Moves = new[]{ 585, 033, 093, 088 }, Index = 85 }, // Lunatone + new(17,01,1) { Species = 338, Ability = A4, Moves = new[]{ 394, 033, 093, 088 }, Index = 85 }, // Solrock + new(30,03,2) { Species = 337, Ability = A4, Moves = new[]{ 585, 129, 094, 088 }, Index = 85 }, // Lunatone + new(30,03,2) { Species = 338, Ability = A4, Moves = new[]{ 394, 129, 094, 088 }, Index = 85 }, // Solrock + new(40,05,3) { Species = 337, Ability = A4, Moves = new[]{ 585, 129, 094, 157 }, Index = 85 }, // Lunatone + new(40,05,3) { Species = 338, Ability = A4, Moves = new[]{ 394, 129, 094, 157 }, Index = 85 }, // Solrock + new(50,08,4) { Species = 337, Ability = A4, Moves = new[]{ 585, 058, 094, 157 }, Index = 85 }, // Lunatone + new(50,08,4) { Species = 338, Ability = A4, Moves = new[]{ 394, 076, 094, 157 }, Index = 85 }, // Solrock + new(60,10,5) { Species = 337, Ability = A4, Moves = new[]{ 585, 058, 094, 444 }, Index = 85, Shiny = Shiny.Always }, // Lunatone + new(60,10,5) { Species = 337, Ability = A4, Moves = new[]{ 585, 058, 094, 444 }, Index = 85 }, // Lunatone + new(60,10,5) { Species = 338, Ability = A4, Moves = new[]{ 394, 076, 094, 444 }, Index = 85, Shiny = Shiny.Always }, // Solrock + new(60,10,5) { Species = 338, Ability = A4, Moves = new[]{ 394, 076, 094, 444 }, Index = 85 }, // Solrock - new(17,01,1) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 001 }, Index = 83 }, // Cinccino - new(17,01,1) { Species = 333, Ability = A4, Moves = new[]{ 574, 064, 257, 031 }, Index = 83 }, // Swablu - new(17,01,1) { Species = 479, Ability = A4, Moves = new[]{ 437, 104, 310, 084 }, Index = 83, Form = 5 }, // Rotom-5 - new(17,01,1) { Species = 767, Ability = A4, Moves = new[]{ 522, 057, 111, 028 }, Index = 83 }, // Wimpod - new(30,03,2) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 583 }, Index = 83 }, // Cinccino - new(30,03,2) { Species = 333, Ability = A4, Moves = new[]{ 583, 225, 257, 058 }, Index = 83 }, // Swablu - new(30,03,2) { Species = 479, Ability = A4, Moves = new[]{ 437, 399, 310, 084 }, Index = 83, Form = 5 }, // Rotom-5 - new(30,03,2) { Species = 767, Ability = A4, Moves = new[]{ 522, 057, 111, 028 }, Index = 83 }, // Wimpod - new(40,05,3) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 441 }, Index = 83 }, // Cinccino - new(40,05,3) { Species = 334, Ability = A4, Moves = new[]{ 583, 784, 083, 058 }, Index = 83 }, // Altaria - new(40,05,3) { Species = 479, Ability = A4, Moves = new[]{ 437, 399, 506, 351 }, Index = 83, Form = 5 }, // Rotom-5 - new(40,05,3) { Species = 767, Ability = A4, Moves = new[]{ 522, 057, 372, 028 }, Index = 83 }, // Wimpod - new(50,08,4) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 086 }, Index = 83 }, // Cinccino - new(50,08,4) { Species = 334, Ability = A4, Moves = new[]{ 585, 784, 083, 058 }, Index = 83 }, // Altaria - new(50,08,4) { Species = 479, Ability = A4, Moves = new[]{ 437, 399, 247, 085 }, Index = 83, Form = 5 }, // Rotom-5 - new(50,08,4) { Species = 767, Ability = A4, Moves = new[]{ 522, 057, 372, 341 }, Index = 83 }, // Wimpod - new(60,10,5) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 813 }, Index = 83, Shiny = Shiny.Always }, // Cinccino - new(60,10,5) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 813 }, Index = 83 }, // Cinccino - new(60,10,5) { Species = 334, Ability = A4, Moves = new[]{ 585, 784, 542, 058 }, Index = 83 }, // Altaria - new(60,10,5) { Species = 479, Ability = A4, Moves = new[]{ 437, 399, 261, 085 }, Index = 83, Form = 5 }, // Rotom-5 - new(60,10,5) { Species = 767, Ability = A4, Moves = new[]{ 806, 057, 372, 341 }, Index = 83 }, // Wimpod + new(17,01,1) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 001 }, Index = 83 }, // Cinccino + new(17,01,1) { Species = 333, Ability = A4, Moves = new[]{ 574, 064, 257, 031 }, Index = 83 }, // Swablu + new(17,01,1) { Species = 479, Ability = A4, Moves = new[]{ 437, 104, 310, 084 }, Index = 83, Form = 5 }, // Rotom-5 + new(17,01,1) { Species = 767, Ability = A4, Moves = new[]{ 522, 057, 111, 028 }, Index = 83 }, // Wimpod + new(30,03,2) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 583 }, Index = 83 }, // Cinccino + new(30,03,2) { Species = 333, Ability = A4, Moves = new[]{ 583, 225, 257, 058 }, Index = 83 }, // Swablu + new(30,03,2) { Species = 479, Ability = A4, Moves = new[]{ 437, 399, 310, 084 }, Index = 83, Form = 5 }, // Rotom-5 + new(30,03,2) { Species = 767, Ability = A4, Moves = new[]{ 522, 057, 111, 028 }, Index = 83 }, // Wimpod + new(40,05,3) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 441 }, Index = 83 }, // Cinccino + new(40,05,3) { Species = 334, Ability = A4, Moves = new[]{ 583, 784, 083, 058 }, Index = 83 }, // Altaria + new(40,05,3) { Species = 479, Ability = A4, Moves = new[]{ 437, 399, 506, 351 }, Index = 83, Form = 5 }, // Rotom-5 + new(40,05,3) { Species = 767, Ability = A4, Moves = new[]{ 522, 057, 372, 028 }, Index = 83 }, // Wimpod + new(50,08,4) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 086 }, Index = 83 }, // Cinccino + new(50,08,4) { Species = 334, Ability = A4, Moves = new[]{ 585, 784, 083, 058 }, Index = 83 }, // Altaria + new(50,08,4) { Species = 479, Ability = A4, Moves = new[]{ 437, 399, 247, 085 }, Index = 83, Form = 5 }, // Rotom-5 + new(50,08,4) { Species = 767, Ability = A4, Moves = new[]{ 522, 057, 372, 341 }, Index = 83 }, // Wimpod + new(60,10,5) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 813 }, Index = 83, Shiny = Shiny.Always }, // Cinccino + new(60,10,5) { Species = 573, Ability = A4, Moves = new[]{ 350, 541, 331, 813 }, Index = 83 }, // Cinccino + new(60,10,5) { Species = 334, Ability = A4, Moves = new[]{ 585, 784, 542, 058 }, Index = 83 }, // Altaria + new(60,10,5) { Species = 479, Ability = A4, Moves = new[]{ 437, 399, 261, 085 }, Index = 83, Form = 5 }, // Rotom-5 + new(60,10,5) { Species = 767, Ability = A4, Moves = new[]{ 806, 057, 372, 341 }, Index = 83 }, // Wimpod - new(17,01,1) { Species = 092, Ability = A4, Moves = new[]{ 122, 605, 474, 009 }, Index = 81 }, // Gastly - new(17,01,1) { Species = 607, Ability = A4, Moves = new[]{ 052, 506, 123, 109 }, Index = 81 }, // Litwick - new(17,01,1) { Species = 425, Ability = A4, Moves = new[]{ 310, 016, 107, 506 }, Index = 81 }, // Drifloon - new(30,03,2) { Species = 093, Ability = A4, Moves = new[]{ 325, 605, 474, 009 }, Index = 81 }, // Haunter - new(30,03,2) { Species = 607, Ability = A4, Moves = new[]{ 083, 506, 123, 261 }, Index = 81 }, // Litwick - new(30,03,2) { Species = 426, Ability = A4, Moves = new[]{ 668, 016, 261, 506 }, Index = 81 }, // Drifblim - new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 325, 605, 474, 087 }, Index = 81 }, // Gengar - new(40,05,3) { Species = 607, Ability = A4, Moves = new[]{ 517, 247, 123, 094 }, Index = 81 }, // Litwick - new(40,05,3) { Species = 426, Ability = A4, Moves = new[]{ 668, 086, 261, 506 }, Index = 81 }, // Drifblim - new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 605, 482, 087 }, Index = 81 }, // Gengar - new(50,08,4) { Species = 609, Ability = A4, Moves = new[]{ 315, 247, 123, 094 }, Index = 81 }, // Chandelure - new(50,08,4) { Species = 426, Ability = A4, Moves = new[]{ 668, 086, 261, 247 }, Index = 81 }, // Drifblim - new(60,10,5) { Species = 094, Ability = A4, Moves = new[]{ 247, 605, 482, 261 }, Index = 81, CanGigantamax = true }, // Gengar - new(60,10,5) { Species = 094, Ability = A4, Moves = new[]{ 247, 605, 482, 261 }, Index = 81 }, // Gengar - new(60,10,5) { Species = 609, Ability = A4, Moves = new[]{ 315, 247, 399, 094 }, Index = 81, Shiny = Shiny.Always }, // Chandelure - new(60,10,5) { Species = 609, Ability = A4, Moves = new[]{ 315, 247, 399, 094 }, Index = 81 }, // Chandelure - new(60,10,5) { Species = 426, Ability = A4, Moves = new[]{ 668, 371, 261, 247 }, Index = 81 }, // Drifblim + new(17,01,1) { Species = 092, Ability = A4, Moves = new[]{ 122, 605, 474, 009 }, Index = 81 }, // Gastly + new(17,01,1) { Species = 607, Ability = A4, Moves = new[]{ 052, 506, 123, 109 }, Index = 81 }, // Litwick + new(17,01,1) { Species = 425, Ability = A4, Moves = new[]{ 310, 016, 107, 506 }, Index = 81 }, // Drifloon + new(30,03,2) { Species = 093, Ability = A4, Moves = new[]{ 325, 605, 474, 009 }, Index = 81 }, // Haunter + new(30,03,2) { Species = 607, Ability = A4, Moves = new[]{ 083, 506, 123, 261 }, Index = 81 }, // Litwick + new(30,03,2) { Species = 426, Ability = A4, Moves = new[]{ 668, 016, 261, 506 }, Index = 81 }, // Drifblim + new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 325, 605, 474, 087 }, Index = 81 }, // Gengar + new(40,05,3) { Species = 607, Ability = A4, Moves = new[]{ 517, 247, 123, 094 }, Index = 81 }, // Litwick + new(40,05,3) { Species = 426, Ability = A4, Moves = new[]{ 668, 086, 261, 506 }, Index = 81 }, // Drifblim + new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 605, 482, 087 }, Index = 81 }, // Gengar + new(50,08,4) { Species = 609, Ability = A4, Moves = new[]{ 315, 247, 123, 094 }, Index = 81 }, // Chandelure + new(50,08,4) { Species = 426, Ability = A4, Moves = new[]{ 668, 086, 261, 247 }, Index = 81 }, // Drifblim + new(60,10,5) { Species = 094, Ability = A4, Moves = new[]{ 247, 605, 482, 261 }, Index = 81, CanGigantamax = true }, // Gengar + new(60,10,5) { Species = 094, Ability = A4, Moves = new[]{ 247, 605, 482, 261 }, Index = 81 }, // Gengar + new(60,10,5) { Species = 609, Ability = A4, Moves = new[]{ 315, 247, 399, 094 }, Index = 81, Shiny = Shiny.Always }, // Chandelure + new(60,10,5) { Species = 609, Ability = A4, Moves = new[]{ 315, 247, 399, 094 }, Index = 81 }, // Chandelure + new(60,10,5) { Species = 426, Ability = A4, Moves = new[]{ 668, 371, 261, 247 }, Index = 81 }, // Drifblim - new(17,01,1) { Species = 582, Ability = A4, Moves = new[]{ 419, 106, 263, 310 }, Index = 79 }, // Vanillite - new(17,01,1) { Species = 118, Ability = A4, Moves = new[]{ 030, 039, 352, 064 }, Index = 79 }, // Goldeen - new(17,01,1) { Species = 127, Ability = A4, Moves = new[]{ 458, 693, 157, 069 }, Index = 79 }, // Pinsir - new(17,01,1) { Species = 214, Ability = A4, Moves = new[]{ 280, 031, 089, 332 }, Index = 79 }, // Heracross - new(17,01,1) { Species = 290, Ability = A4, Moves = new[]{ 189, 206, 028, 010 }, Index = 79 }, // Nincada - new(17,01,1) { Species = 479, Ability = A4, Moves = new[]{ 403, 084, 310, 104 }, Index = 79, Form = 4 }, // Rotom-4 - new(30,03,2) { Species = 582, Ability = A4, Moves = new[]{ 419, 430, 263, 310 }, Index = 79 }, // Vanillite - new(30,03,2) { Species = 118, Ability = A4, Moves = new[]{ 030, 398, 352, 064 }, Index = 79 }, // Goldeen - new(30,03,2) { Species = 127, Ability = A4, Moves = new[]{ 458, 675, 157, 069 }, Index = 79 }, // Pinsir - new(30,03,2) { Species = 214, Ability = A4, Moves = new[]{ 280, 030, 089, 332 }, Index = 79 }, // Heracross - new(30,03,2) { Species = 291, Ability = A4, Moves = new[]{ 232, 210, 403, 010 }, Index = 79 }, // Ninjask - new(30,03,2) { Species = 479, Ability = A4, Moves = new[]{ 403, 351, 310, 104 }, Index = 79, Form = 4 }, // Rotom-4 - new(40,05,3) { Species = 583, Ability = A4, Moves = new[]{ 419, 430, 304, 310 }, Index = 79 }, // Vanillish - new(40,05,3) { Species = 119, Ability = A4, Moves = new[]{ 030, 224, 352, 529 }, Index = 79 }, // Seaking - new(40,05,3) { Species = 127, Ability = A4, Moves = new[]{ 404, 675, 157, 280 }, Index = 79 }, // Pinsir - new(40,05,3) { Species = 214, Ability = A4, Moves = new[]{ 280, 042, 089, 157 }, Index = 79 }, // Heracross - new(40,05,3) { Species = 291, Ability = A4, Moves = new[]{ 232, 210, 403, 104 }, Index = 79 }, // Ninjask - new(40,05,3) { Species = 479, Ability = A4, Moves = new[]{ 403, 085, 310, 399 }, Index = 79, Form = 4 }, // Rotom-4 - new(50,08,4) { Species = 584, Ability = A4, Moves = new[]{ 573, 430, 304, 058 }, Index = 79 }, // Vanilluxe - new(50,08,4) { Species = 119, Ability = A4, Moves = new[]{ 030, 224, 503, 529 }, Index = 79 }, // Seaking - new(50,08,4) { Species = 127, Ability = A4, Moves = new[]{ 404, 675, 157, 276 }, Index = 79 }, // Pinsir - new(50,08,4) { Species = 214, Ability = A4, Moves = new[]{ 280, 331, 089, 157 }, Index = 79 }, // Heracross - new(50,08,4) { Species = 291, Ability = A4, Moves = new[]{ 232, 210, 403, 163 }, Index = 79 }, // Ninjask - new(50,08,4) { Species = 479, Ability = A4, Moves = new[]{ 403, 085, 506, 399 }, Index = 79, Form = 4 }, // Rotom-4 - new(60,10,5) { Species = 584, Ability = A4, Moves = new[]{ 573, 430, 304, 059 }, Index = 79, Shiny = Shiny.Always }, // Vanilluxe - new(60,10,5) { Species = 119, Ability = A4, Moves = new[]{ 032, 224, 503, 529 }, Index = 79 }, // Seaking - new(60,10,5) { Species = 127, Ability = A4, Moves = new[]{ 404, 675, 317, 276 }, Index = 79 }, // Pinsir - new(60,10,5) { Species = 214, Ability = A4, Moves = new[]{ 370, 331, 089, 157 }, Index = 79 }, // Heracross - new(60,10,5) { Species = 291, Ability = A4, Moves = new[]{ 232, 404, 403, 163 }, Index = 79 }, // Ninjask - new(60,10,5) { Species = 479, Ability = A4, Moves = new[]{ 403, 085, 247, 399 }, Index = 79, Form = 4 }, // Rotom-4 + new(17,01,1) { Species = 582, Ability = A4, Moves = new[]{ 419, 106, 263, 310 }, Index = 79 }, // Vanillite + new(17,01,1) { Species = 118, Ability = A4, Moves = new[]{ 030, 039, 352, 064 }, Index = 79 }, // Goldeen + new(17,01,1) { Species = 127, Ability = A4, Moves = new[]{ 458, 693, 157, 069 }, Index = 79 }, // Pinsir + new(17,01,1) { Species = 214, Ability = A4, Moves = new[]{ 280, 031, 089, 332 }, Index = 79 }, // Heracross + new(17,01,1) { Species = 290, Ability = A4, Moves = new[]{ 189, 206, 028, 010 }, Index = 79 }, // Nincada + new(17,01,1) { Species = 479, Ability = A4, Moves = new[]{ 403, 084, 310, 104 }, Index = 79, Form = 4 }, // Rotom-4 + new(30,03,2) { Species = 582, Ability = A4, Moves = new[]{ 419, 430, 263, 310 }, Index = 79 }, // Vanillite + new(30,03,2) { Species = 118, Ability = A4, Moves = new[]{ 030, 398, 352, 064 }, Index = 79 }, // Goldeen + new(30,03,2) { Species = 127, Ability = A4, Moves = new[]{ 458, 675, 157, 069 }, Index = 79 }, // Pinsir + new(30,03,2) { Species = 214, Ability = A4, Moves = new[]{ 280, 030, 089, 332 }, Index = 79 }, // Heracross + new(30,03,2) { Species = 291, Ability = A4, Moves = new[]{ 232, 210, 403, 010 }, Index = 79 }, // Ninjask + new(30,03,2) { Species = 479, Ability = A4, Moves = new[]{ 403, 351, 310, 104 }, Index = 79, Form = 4 }, // Rotom-4 + new(40,05,3) { Species = 583, Ability = A4, Moves = new[]{ 419, 430, 304, 310 }, Index = 79 }, // Vanillish + new(40,05,3) { Species = 119, Ability = A4, Moves = new[]{ 030, 224, 352, 529 }, Index = 79 }, // Seaking + new(40,05,3) { Species = 127, Ability = A4, Moves = new[]{ 404, 675, 157, 280 }, Index = 79 }, // Pinsir + new(40,05,3) { Species = 214, Ability = A4, Moves = new[]{ 280, 042, 089, 157 }, Index = 79 }, // Heracross + new(40,05,3) { Species = 291, Ability = A4, Moves = new[]{ 232, 210, 403, 104 }, Index = 79 }, // Ninjask + new(40,05,3) { Species = 479, Ability = A4, Moves = new[]{ 403, 085, 310, 399 }, Index = 79, Form = 4 }, // Rotom-4 + new(50,08,4) { Species = 584, Ability = A4, Moves = new[]{ 573, 430, 304, 058 }, Index = 79 }, // Vanilluxe + new(50,08,4) { Species = 119, Ability = A4, Moves = new[]{ 030, 224, 503, 529 }, Index = 79 }, // Seaking + new(50,08,4) { Species = 127, Ability = A4, Moves = new[]{ 404, 675, 157, 276 }, Index = 79 }, // Pinsir + new(50,08,4) { Species = 214, Ability = A4, Moves = new[]{ 280, 331, 089, 157 }, Index = 79 }, // Heracross + new(50,08,4) { Species = 291, Ability = A4, Moves = new[]{ 232, 210, 403, 163 }, Index = 79 }, // Ninjask + new(50,08,4) { Species = 479, Ability = A4, Moves = new[]{ 403, 085, 506, 399 }, Index = 79, Form = 4 }, // Rotom-4 + new(60,10,5) { Species = 584, Ability = A4, Moves = new[]{ 573, 430, 304, 059 }, Index = 79, Shiny = Shiny.Always }, // Vanilluxe + new(60,10,5) { Species = 119, Ability = A4, Moves = new[]{ 032, 224, 503, 529 }, Index = 79 }, // Seaking + new(60,10,5) { Species = 127, Ability = A4, Moves = new[]{ 404, 675, 317, 276 }, Index = 79 }, // Pinsir + new(60,10,5) { Species = 214, Ability = A4, Moves = new[]{ 370, 331, 089, 157 }, Index = 79 }, // Heracross + new(60,10,5) { Species = 291, Ability = A4, Moves = new[]{ 232, 404, 403, 163 }, Index = 79 }, // Ninjask + new(60,10,5) { Species = 479, Ability = A4, Moves = new[]{ 403, 085, 247, 399 }, Index = 79, Form = 4 }, // Rotom-4 - new(17,01,1) { Species = 138, Ability = A4, Moves = new[]{ 055, 028, 205, 020 }, Index = 77 }, // Omanyte - new(17,01,1) { Species = 140, Ability = A4, Moves = new[]{ 453, 028, 263, 010 }, Index = 77 }, // Kabuto - new(17,01,1) { Species = 142, Ability = A4, Moves = new[]{ 246, 414, 044, 017 }, Index = 77 }, // Aerodactyl - new(30,03,2) { Species = 138, Ability = A4, Moves = new[]{ 055, 341, 246, 020 }, Index = 77 }, // Omanyte - new(30,03,2) { Species = 140, Ability = A4, Moves = new[]{ 453, 246, 263, 058 }, Index = 77 }, // Kabuto - new(30,03,2) { Species = 142, Ability = A4, Moves = new[]{ 157, 414, 242, 017 }, Index = 77 }, // Aerodactyl - new(40,05,3) { Species = 138, Ability = A4, Moves = new[]{ 362, 414, 246, 196 }, Index = 77 }, // Omanyte - new(40,05,3) { Species = 140, Ability = A4, Moves = new[]{ 362, 246, 263, 059 }, Index = 77 }, // Kabuto - new(40,05,3) { Species = 142, Ability = A4, Moves = new[]{ 157, 414, 422, 017 }, Index = 77 }, // Aerodactyl - new(50,08,4) { Species = 138, Ability = A4, Moves = new[]{ 057, 414, 246, 058 }, Index = 77 }, // Omanyte - new(50,08,4) { Species = 140, Ability = A4, Moves = new[]{ 710, 246, 141, 059 }, Index = 77 }, // Kabuto - new(50,08,4) { Species = 142, Ability = A4, Moves = new[]{ 444, 414, 422, 542 }, Index = 77 }, // Aerodactyl - new(60,10,5) { Species = 138, Ability = A4, Moves = new[]{ 056, 414, 246, 058 }, Index = 77, Shiny = Shiny.Always }, // Omanyte - new(60,10,5) { Species = 138, Ability = A4, Moves = new[]{ 056, 414, 246, 058 }, Index = 77 }, // Omanyte - new(60,10,5) { Species = 140, Ability = A4, Moves = new[]{ 710, 444, 141, 059 }, Index = 77 }, // Kabuto - new(60,10,5) { Species = 142, Ability = A4, Moves = new[]{ 444, 089, 422, 542 }, Index = 77 }, // Aerodactyl + new(17,01,1) { Species = 138, Ability = A4, Moves = new[]{ 055, 028, 205, 020 }, Index = 77 }, // Omanyte + new(17,01,1) { Species = 140, Ability = A4, Moves = new[]{ 453, 028, 263, 010 }, Index = 77 }, // Kabuto + new(17,01,1) { Species = 142, Ability = A4, Moves = new[]{ 246, 414, 044, 017 }, Index = 77 }, // Aerodactyl + new(30,03,2) { Species = 138, Ability = A4, Moves = new[]{ 055, 341, 246, 020 }, Index = 77 }, // Omanyte + new(30,03,2) { Species = 140, Ability = A4, Moves = new[]{ 453, 246, 263, 058 }, Index = 77 }, // Kabuto + new(30,03,2) { Species = 142, Ability = A4, Moves = new[]{ 157, 414, 242, 017 }, Index = 77 }, // Aerodactyl + new(40,05,3) { Species = 138, Ability = A4, Moves = new[]{ 362, 414, 246, 196 }, Index = 77 }, // Omanyte + new(40,05,3) { Species = 140, Ability = A4, Moves = new[]{ 362, 246, 263, 059 }, Index = 77 }, // Kabuto + new(40,05,3) { Species = 142, Ability = A4, Moves = new[]{ 157, 414, 422, 017 }, Index = 77 }, // Aerodactyl + new(50,08,4) { Species = 138, Ability = A4, Moves = new[]{ 057, 414, 246, 058 }, Index = 77 }, // Omanyte + new(50,08,4) { Species = 140, Ability = A4, Moves = new[]{ 710, 246, 141, 059 }, Index = 77 }, // Kabuto + new(50,08,4) { Species = 142, Ability = A4, Moves = new[]{ 444, 414, 422, 542 }, Index = 77 }, // Aerodactyl + new(60,10,5) { Species = 138, Ability = A4, Moves = new[]{ 056, 414, 246, 058 }, Index = 77, Shiny = Shiny.Always }, // Omanyte + new(60,10,5) { Species = 138, Ability = A4, Moves = new[]{ 056, 414, 246, 058 }, Index = 77 }, // Omanyte + new(60,10,5) { Species = 140, Ability = A4, Moves = new[]{ 710, 444, 141, 059 }, Index = 77 }, // Kabuto + new(60,10,5) { Species = 142, Ability = A4, Moves = new[]{ 444, 089, 422, 542 }, Index = 77 }, // Aerodactyl - new(17,01,1) { Species = 878, Ability = A4, Moves = new[]{ 523, 249, 033, 045 }, Index = 75 }, // Cufant - new(17,01,1) { Species = 109, Ability = A4, Moves = new[]{ 188, 372, 139, 108 }, Index = 75 }, // Koffing - new(17,01,1) { Species = 202, Ability = A4, Moves = new[]{ 243, 227, 068, 204 }, Index = 75 }, // Wobbuffet - new(17,01,1) { Species = 868, Ability = A4, Moves = new[]{ 033, 186, 577, 496 }, Index = 75 }, // Milcery - new(17,01,1) { Species = 004, Ability = A4, Moves = new[]{ 052, 225, 010, 108 }, Index = 75 }, // Charmander - new(30,03,2) { Species = 878, Ability = A4, Moves = new[]{ 523, 249, 023, 045 }, Index = 75 }, // Cufant - new(30,03,2) { Species = 109, Ability = A4, Moves = new[]{ 188, 372, 123, 108 }, Index = 75 }, // Koffing - new(30,03,2) { Species = 202, Ability = A4, Moves = new[]{ 243, 227, 068, 204 }, Index = 75 }, // Wobbuffet - new(30,03,2) { Species = 868, Ability = A4, Moves = new[]{ 577, 186, 263, 500 }, Index = 75 }, // Milcery - new(30,03,2) { Species = 005, Ability = A4, Moves = new[]{ 225, 512, 242, 053 }, Index = 75 }, // Charmeleon - new(40,05,3) { Species = 879, Ability = A4, Moves = new[]{ 484, 583, 070, 249 }, Index = 75, CanGigantamax = true }, // Copperajah - new(40,05,3) { Species = 109, Ability = A4, Moves = new[]{ 482, 372, 053, 085 }, Index = 75 }, // Koffing - new(40,05,3) { Species = 202, Ability = A4, Moves = new[]{ 243, 227, 068, 204 }, Index = 75 }, // Wobbuffet - new(40,05,3) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 213 }, Index = 75 }, // Milcery - new(40,05,3) { Species = 006, Ability = A4, Moves = new[]{ 337, 403, 280, 257 }, Index = 75, CanGigantamax = true }, // Charizard - new(50,08,4) { Species = 879, Ability = A4, Moves = new[]{ 484, 583, 070, 438 }, Index = 75, CanGigantamax = true }, // Copperajah - new(50,08,4) { Species = 109, Ability = A4, Moves = new[]{ 482, 399, 053, 085 }, Index = 75 }, // Koffing - new(50,08,4) { Species = 202, Ability = A4, Moves = new[]{ 243, 227, 068, 204 }, Index = 75 }, // Wobbuffet - new(50,08,4) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 75 }, // Milcery - new(50,08,4) { Species = 006, Ability = A4, Moves = new[]{ 337, 403, 411, 257 }, Index = 75, CanGigantamax = true }, // Charizard - new(60,10,5) { Species = 879, Ability = A4, Moves = new[]{ 484, 583, 089, 438 }, Index = 75, CanGigantamax = true, Shiny = Shiny.Always }, // Copperajah - new(60,10,5) { Species = 879, Ability = A4, Moves = new[]{ 484, 583, 089, 438 }, Index = 75, CanGigantamax = true }, // Copperajah - new(60,10,5) { Species = 109, Ability = A4, Moves = new[]{ 482, 399, 053, 087 }, Index = 75 }, // Koffing - new(60,10,5) { Species = 202, Ability = A4, Moves = new[]{ 243, 227, 068, 204 }, Index = 75 }, // Wobbuffet - new(60,10,5) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 75 }, // Milcery - new(60,10,5) { Species = 006, Ability = A4, Moves = new[]{ 406, 403, 411, 257 }, Index = 75, CanGigantamax = true }, // Charizard + new(17,01,1) { Species = 878, Ability = A4, Moves = new[]{ 523, 249, 033, 045 }, Index = 75 }, // Cufant + new(17,01,1) { Species = 109, Ability = A4, Moves = new[]{ 188, 372, 139, 108 }, Index = 75 }, // Koffing + new(17,01,1) { Species = 202, Ability = A4, Moves = new[]{ 243, 227, 068, 204 }, Index = 75 }, // Wobbuffet + new(17,01,1) { Species = 868, Ability = A4, Moves = new[]{ 033, 186, 577, 496 }, Index = 75 }, // Milcery + new(17,01,1) { Species = 004, Ability = A4, Moves = new[]{ 052, 225, 010, 108 }, Index = 75 }, // Charmander + new(30,03,2) { Species = 878, Ability = A4, Moves = new[]{ 523, 249, 023, 045 }, Index = 75 }, // Cufant + new(30,03,2) { Species = 109, Ability = A4, Moves = new[]{ 188, 372, 123, 108 }, Index = 75 }, // Koffing + new(30,03,2) { Species = 202, Ability = A4, Moves = new[]{ 243, 227, 068, 204 }, Index = 75 }, // Wobbuffet + new(30,03,2) { Species = 868, Ability = A4, Moves = new[]{ 577, 186, 263, 500 }, Index = 75 }, // Milcery + new(30,03,2) { Species = 005, Ability = A4, Moves = new[]{ 225, 512, 242, 053 }, Index = 75 }, // Charmeleon + new(40,05,3) { Species = 879, Ability = A4, Moves = new[]{ 484, 583, 070, 249 }, Index = 75, CanGigantamax = true }, // Copperajah + new(40,05,3) { Species = 109, Ability = A4, Moves = new[]{ 482, 372, 053, 085 }, Index = 75 }, // Koffing + new(40,05,3) { Species = 202, Ability = A4, Moves = new[]{ 243, 227, 068, 204 }, Index = 75 }, // Wobbuffet + new(40,05,3) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 213 }, Index = 75 }, // Milcery + new(40,05,3) { Species = 006, Ability = A4, Moves = new[]{ 337, 403, 280, 257 }, Index = 75, CanGigantamax = true }, // Charizard + new(50,08,4) { Species = 879, Ability = A4, Moves = new[]{ 484, 583, 070, 438 }, Index = 75, CanGigantamax = true }, // Copperajah + new(50,08,4) { Species = 109, Ability = A4, Moves = new[]{ 482, 399, 053, 085 }, Index = 75 }, // Koffing + new(50,08,4) { Species = 202, Ability = A4, Moves = new[]{ 243, 227, 068, 204 }, Index = 75 }, // Wobbuffet + new(50,08,4) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 75 }, // Milcery + new(50,08,4) { Species = 006, Ability = A4, Moves = new[]{ 337, 403, 411, 257 }, Index = 75, CanGigantamax = true }, // Charizard + new(60,10,5) { Species = 879, Ability = A4, Moves = new[]{ 484, 583, 089, 438 }, Index = 75, CanGigantamax = true, Shiny = Shiny.Always }, // Copperajah + new(60,10,5) { Species = 879, Ability = A4, Moves = new[]{ 484, 583, 089, 438 }, Index = 75, CanGigantamax = true }, // Copperajah + new(60,10,5) { Species = 109, Ability = A4, Moves = new[]{ 482, 399, 053, 087 }, Index = 75 }, // Koffing + new(60,10,5) { Species = 202, Ability = A4, Moves = new[]{ 243, 227, 068, 204 }, Index = 75 }, // Wobbuffet + new(60,10,5) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 75 }, // Milcery + new(60,10,5) { Species = 006, Ability = A4, Moves = new[]{ 406, 403, 411, 257 }, Index = 75, CanGigantamax = true }, // Charizard - new(17,01,1) { Species = 852, Ability = A4, Moves = new[]{ 371, 249, 362, 364 }, Index = 73 }, // Clobbopus - new(17,01,1) { Species = 223, Ability = A4, Moves = new[]{ 055, 062, 060, 129 }, Index = 73 }, // Remoraid - new(17,01,1) { Species = 686, Ability = A4, Moves = new[]{ 371, 060, 035, 095 }, Index = 73 }, // Inkay - new(30,03,2) { Species = 852, Ability = A4, Moves = new[]{ 371, 066, 362, 364 }, Index = 73 }, // Clobbopus - new(30,03,2) { Species = 223, Ability = A4, Moves = new[]{ 061, 062, 060, 129 }, Index = 73 }, // Remoraid - new(30,03,2) { Species = 686, Ability = A4, Moves = new[]{ 400, 060, 035, 095 }, Index = 73 }, // Inkay - new(40,05,3) { Species = 853, Ability = A4, Moves = new[]{ 693, 276, 190, 034 }, Index = 73 }, // Grapploct - new(40,05,3) { Species = 224, Ability = A4, Moves = new[]{ 503, 058, 086, 129 }, Index = 73 }, // Octillery - new(40,05,3) { Species = 687, Ability = A4, Moves = new[]{ 492, 060, 035, 095 }, Index = 73 }, // Malamar - new(50,08,4) { Species = 853, Ability = A4, Moves = new[]{ 576, 276, 008, 034 }, Index = 73 }, // Grapploct - new(50,08,4) { Species = 224, Ability = A4, Moves = new[]{ 056, 058, 086, 129 }, Index = 73 }, // Octillery - new(50,08,4) { Species = 687, Ability = A4, Moves = new[]{ 492, 427, 163, 085 }, Index = 73 }, // Malamar - new(60,10,5) { Species = 853, Ability = A4, Moves = new[]{ 576, 276, 008, 707 }, Index = 73, Shiny = Shiny.Always }, // Grapploct - new(60,10,5) { Species = 853, Ability = A4, Moves = new[]{ 576, 276, 008, 707 }, Index = 73 }, // Grapploct - new(60,10,5) { Species = 224, Ability = A4, Moves = new[]{ 056, 058, 086, 053 }, Index = 73 }, // Octillery - new(60,10,5) { Species = 687, Ability = A4, Moves = new[]{ 492, 094, 157, 085 }, Index = 73 }, // Malamar + new(17,01,1) { Species = 852, Ability = A4, Moves = new[]{ 371, 249, 362, 364 }, Index = 73 }, // Clobbopus + new(17,01,1) { Species = 223, Ability = A4, Moves = new[]{ 055, 062, 060, 129 }, Index = 73 }, // Remoraid + new(17,01,1) { Species = 686, Ability = A4, Moves = new[]{ 371, 060, 035, 095 }, Index = 73 }, // Inkay + new(30,03,2) { Species = 852, Ability = A4, Moves = new[]{ 371, 066, 362, 364 }, Index = 73 }, // Clobbopus + new(30,03,2) { Species = 223, Ability = A4, Moves = new[]{ 061, 062, 060, 129 }, Index = 73 }, // Remoraid + new(30,03,2) { Species = 686, Ability = A4, Moves = new[]{ 400, 060, 035, 095 }, Index = 73 }, // Inkay + new(40,05,3) { Species = 853, Ability = A4, Moves = new[]{ 693, 276, 190, 034 }, Index = 73 }, // Grapploct + new(40,05,3) { Species = 224, Ability = A4, Moves = new[]{ 503, 058, 086, 129 }, Index = 73 }, // Octillery + new(40,05,3) { Species = 687, Ability = A4, Moves = new[]{ 492, 060, 035, 095 }, Index = 73 }, // Malamar + new(50,08,4) { Species = 853, Ability = A4, Moves = new[]{ 576, 276, 008, 034 }, Index = 73 }, // Grapploct + new(50,08,4) { Species = 224, Ability = A4, Moves = new[]{ 056, 058, 086, 129 }, Index = 73 }, // Octillery + new(50,08,4) { Species = 687, Ability = A4, Moves = new[]{ 492, 427, 163, 085 }, Index = 73 }, // Malamar + new(60,10,5) { Species = 853, Ability = A4, Moves = new[]{ 576, 276, 008, 707 }, Index = 73, Shiny = Shiny.Always }, // Grapploct + new(60,10,5) { Species = 853, Ability = A4, Moves = new[]{ 576, 276, 008, 707 }, Index = 73 }, // Grapploct + new(60,10,5) { Species = 224, Ability = A4, Moves = new[]{ 056, 058, 086, 053 }, Index = 73 }, // Octillery + new(60,10,5) { Species = 687, Ability = A4, Moves = new[]{ 492, 094, 157, 085 }, Index = 73 }, // Malamar - new(17,01,1) { Species = 002, Ability = A4, Moves = new[]{ 075, 077, 033, 079 }, Index = 71 }, // Ivysaur - new(17,01,1) { Species = 060, Ability = A4, Moves = new[]{ 055, 095, 001, 341 }, Index = 71 }, // Poliwag - new(17,01,1) { Species = 453, Ability = A4, Moves = new[]{ 040, 279, 189, 372 }, Index = 71 }, // Croagunk - new(17,01,1) { Species = 535, Ability = A4, Moves = new[]{ 497, 341, 045, 051 }, Index = 71 }, // Tympole - new(30,03,2) { Species = 002, Ability = A4, Moves = new[]{ 402, 077, 033, 036 }, Index = 71 }, // Ivysaur - new(30,03,2) { Species = 061, Ability = A4, Moves = new[]{ 061, 095, 001, 341 }, Index = 71 }, // Poliwhirl - new(30,03,2) { Species = 453, Ability = A4, Moves = new[]{ 474, 279, 189, 372 }, Index = 71 }, // Croagunk - new(30,03,2) { Species = 536, Ability = A4, Moves = new[]{ 061, 341, 175, 051 }, Index = 71 }, // Palpitoad - new(40,05,3) { Species = 003, Ability = A4, Moves = new[]{ 402, 188, 414, 036 }, Index = 71 }, // Venusaur - new(40,05,3) { Species = 186, Ability = A4, Moves = new[]{ 056, 411, 034, 341 }, Index = 71 }, // Politoed - new(40,05,3) { Species = 453, Ability = A4, Moves = new[]{ 092, 279, 404, 372 }, Index = 71 }, // Croagunk - new(40,05,3) { Species = 537, Ability = A4, Moves = new[]{ 503, 341, 438, 051 }, Index = 71 }, // Seismitoad - new(50,08,4) { Species = 003, Ability = A4, Moves = new[]{ 438, 188, 414, 036 }, Index = 71 }, // Venusaur - new(50,08,4) { Species = 186, Ability = A4, Moves = new[]{ 056, 411, 034, 414 }, Index = 71 }, // Politoed - new(50,08,4) { Species = 453, Ability = A4, Moves = new[]{ 188, 067, 404, 372 }, Index = 71 }, // Croagunk - new(50,08,4) { Species = 537, Ability = A4, Moves = new[]{ 503, 341, 438, 398 }, Index = 71 }, // Seismitoad - new(60,10,5) { Species = 003, Ability = A4, Moves = new[]{ 438, 188, 414, 034 }, Index = 71, CanGigantamax = true }, // Venusaur - new(60,10,5) { Species = 003, Ability = A4, Moves = new[]{ 438, 188, 414, 034 }, Index = 71 }, // Venusaur - new(60,10,5) { Species = 186, Ability = A4, Moves = new[]{ 056, 311, 034, 414 }, Index = 71, Shiny = Shiny.Always }, // Politoed - new(60,10,5) { Species = 186, Ability = A4, Moves = new[]{ 056, 311, 034, 414 }, Index = 71 }, // Politoed - new(60,10,5) { Species = 453, Ability = A4, Moves = new[]{ 188, 067, 404, 247 }, Index = 71 }, // Croagunk - new(60,10,5) { Species = 537, Ability = A4, Moves = new[]{ 503, 089, 438, 398 }, Index = 71 }, // Seismitoad + new(17,01,1) { Species = 002, Ability = A4, Moves = new[]{ 075, 077, 033, 079 }, Index = 71 }, // Ivysaur + new(17,01,1) { Species = 060, Ability = A4, Moves = new[]{ 055, 095, 001, 341 }, Index = 71 }, // Poliwag + new(17,01,1) { Species = 453, Ability = A4, Moves = new[]{ 040, 279, 189, 372 }, Index = 71 }, // Croagunk + new(17,01,1) { Species = 535, Ability = A4, Moves = new[]{ 497, 341, 045, 051 }, Index = 71 }, // Tympole + new(30,03,2) { Species = 002, Ability = A4, Moves = new[]{ 402, 077, 033, 036 }, Index = 71 }, // Ivysaur + new(30,03,2) { Species = 061, Ability = A4, Moves = new[]{ 061, 095, 001, 341 }, Index = 71 }, // Poliwhirl + new(30,03,2) { Species = 453, Ability = A4, Moves = new[]{ 474, 279, 189, 372 }, Index = 71 }, // Croagunk + new(30,03,2) { Species = 536, Ability = A4, Moves = new[]{ 061, 341, 175, 051 }, Index = 71 }, // Palpitoad + new(40,05,3) { Species = 003, Ability = A4, Moves = new[]{ 402, 188, 414, 036 }, Index = 71 }, // Venusaur + new(40,05,3) { Species = 186, Ability = A4, Moves = new[]{ 056, 411, 034, 341 }, Index = 71 }, // Politoed + new(40,05,3) { Species = 453, Ability = A4, Moves = new[]{ 092, 279, 404, 372 }, Index = 71 }, // Croagunk + new(40,05,3) { Species = 537, Ability = A4, Moves = new[]{ 503, 341, 438, 051 }, Index = 71 }, // Seismitoad + new(50,08,4) { Species = 003, Ability = A4, Moves = new[]{ 438, 188, 414, 036 }, Index = 71 }, // Venusaur + new(50,08,4) { Species = 186, Ability = A4, Moves = new[]{ 056, 411, 034, 414 }, Index = 71 }, // Politoed + new(50,08,4) { Species = 453, Ability = A4, Moves = new[]{ 188, 067, 404, 372 }, Index = 71 }, // Croagunk + new(50,08,4) { Species = 537, Ability = A4, Moves = new[]{ 503, 341, 438, 398 }, Index = 71 }, // Seismitoad + new(60,10,5) { Species = 003, Ability = A4, Moves = new[]{ 438, 188, 414, 034 }, Index = 71, CanGigantamax = true }, // Venusaur + new(60,10,5) { Species = 003, Ability = A4, Moves = new[]{ 438, 188, 414, 034 }, Index = 71 }, // Venusaur + new(60,10,5) { Species = 186, Ability = A4, Moves = new[]{ 056, 311, 034, 414 }, Index = 71, Shiny = Shiny.Always }, // Politoed + new(60,10,5) { Species = 186, Ability = A4, Moves = new[]{ 056, 311, 034, 414 }, Index = 71 }, // Politoed + new(60,10,5) { Species = 453, Ability = A4, Moves = new[]{ 188, 067, 404, 247 }, Index = 71 }, // Croagunk + new(60,10,5) { Species = 537, Ability = A4, Moves = new[]{ 503, 089, 438, 398 }, Index = 71 }, // Seismitoad - new(17,01,1) { Species = 831, Ability = A4, Moves = new[]{ 029, 024, 045, 033 }, Index = 69 }, // Wooloo - new(30,03,2) { Species = 831, Ability = A4, Moves = new[]{ 029, 024, 528, 033 }, Index = 69 }, // Wooloo - new(30,03,2) { Species = 832, Ability = A4, Moves = new[]{ 036, 024, 528, 371 }, Index = 69 }, // Dubwool - new(40,05,3) { Species = 831, Ability = A4, Moves = new[]{ 029, 179, 528, 024 }, Index = 69 }, // Wooloo - new(40,05,3) { Species = 832, Ability = A4, Moves = new[]{ 036, 179, 528, 371 }, Index = 69 }, // Dubwool - new(50,08,4) { Species = 831, Ability = A4, Moves = new[]{ 029, 179, 528, 371 }, Index = 69 }, // Wooloo - new(50,08,4) { Species = 832, Ability = A4, Moves = new[]{ 036, 179, 528, 024 }, Index = 69 }, // Dubwool - new(60,10,5) { Species = 831, Ability = A4, Moves = new[]{ 038, 179, 086, 371 }, Index = 69, Shiny = Shiny.Always }, // Wooloo - new(60,10,5) { Species = 831, Ability = A4, Moves = new[]{ 038, 179, 086, 371 }, Index = 69 }, // Wooloo - new(60,10,5) { Species = 832, Ability = A4, Moves = new[]{ 776, 038, 086, 371 }, Index = 69 }, // Dubwool + new(17,01,1) { Species = 831, Ability = A4, Moves = new[]{ 029, 024, 045, 033 }, Index = 69 }, // Wooloo + new(30,03,2) { Species = 831, Ability = A4, Moves = new[]{ 029, 024, 528, 033 }, Index = 69 }, // Wooloo + new(30,03,2) { Species = 832, Ability = A4, Moves = new[]{ 036, 024, 528, 371 }, Index = 69 }, // Dubwool + new(40,05,3) { Species = 831, Ability = A4, Moves = new[]{ 029, 179, 528, 024 }, Index = 69 }, // Wooloo + new(40,05,3) { Species = 832, Ability = A4, Moves = new[]{ 036, 179, 528, 371 }, Index = 69 }, // Dubwool + new(50,08,4) { Species = 831, Ability = A4, Moves = new[]{ 029, 179, 528, 371 }, Index = 69 }, // Wooloo + new(50,08,4) { Species = 832, Ability = A4, Moves = new[]{ 036, 179, 528, 024 }, Index = 69 }, // Dubwool + new(60,10,5) { Species = 831, Ability = A4, Moves = new[]{ 038, 179, 086, 371 }, Index = 69, Shiny = Shiny.Always }, // Wooloo + new(60,10,5) { Species = 831, Ability = A4, Moves = new[]{ 038, 179, 086, 371 }, Index = 69 }, // Wooloo + new(60,10,5) { Species = 832, Ability = A4, Moves = new[]{ 776, 038, 086, 371 }, Index = 69 }, // Dubwool - new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2 }, // Meowth-2 - new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 196, 675 }, Index = 67, Form = 1 }, // Meowth-1 - new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 67 }, // Meowth - new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 67 }, // Meowth - new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2 }, // Meowth-2 - new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 196, 675 }, Index = 67, Form = 1 }, // Meowth-1 - new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 67 }, // Meowth - new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 67 }, // Meowth - new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2 }, // Meowth-2 - new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 196, 675 }, Index = 67, Form = 1 }, // Meowth-1 - new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 67 }, // Meowth - new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 67 }, // Meowth - new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2 }, // Meowth-2 - new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 196, 675 }, Index = 67, Form = 1 }, // Meowth-1 - new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 67 }, // Meowth - new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 67 }, // Meowth - new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2, Shiny = Shiny.Always }, // Meowth-2 - new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2 }, // Meowth-2 - new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 196, 675 }, Index = 67, Form = 1 }, // Meowth-1 - new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 67 }, // Meowth - new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 67 }, // Meowth - new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 421, 034 }, Index = 67, CanGigantamax = true }, // Meowth + new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2 }, // Meowth-2 + new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 196, 675 }, Index = 67, Form = 1 }, // Meowth-1 + new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 67 }, // Meowth + new(17,01,1) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 67 }, // Meowth + new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2 }, // Meowth-2 + new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 196, 675 }, Index = 67, Form = 1 }, // Meowth-1 + new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 67 }, // Meowth + new(30,03,2) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 67 }, // Meowth + new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2 }, // Meowth-2 + new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 196, 675 }, Index = 67, Form = 1 }, // Meowth-1 + new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 67 }, // Meowth + new(40,05,3) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 67 }, // Meowth + new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2 }, // Meowth-2 + new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 196, 675 }, Index = 67, Form = 1 }, // Meowth-1 + new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 67 }, // Meowth + new(50,08,4) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 67 }, // Meowth + new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2, Shiny = Shiny.Always }, // Meowth-2 + new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 232, 442, 583 }, Index = 67, Form = 2 }, // Meowth-2 + new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 196, 675 }, Index = 67, Form = 1 }, // Meowth-1 + new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 492, 402, 247 }, Index = 67 }, // Meowth + new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 441, 087, 231 }, Index = 67 }, // Meowth + new(60,10,5) { Species = 052, Ability = A4, Moves = new[]{ 006, 583, 421, 034 }, Index = 67, CanGigantamax = true }, // Meowth - new(17,01,1) { Species = 875, Ability = A4, Moves = new[]{ 181, 362, 033, 311 }, Index = 65 }, // Eiscue - new(30,03,2) { Species = 875, Ability = A4, Moves = new[]{ 196, 362, 033, 029 }, Index = 65 }, // Eiscue - new(40,05,3) { Species = 875, Ability = A4, Moves = new[]{ 008, 057, 263, 029 }, Index = 65 }, // Eiscue - new(50,08,4) { Species = 875, Ability = A4, Moves = new[]{ 333, 057, 428, 029 }, Index = 65 }, // Eiscue - new(60,10,5) { Species = 875, Ability = A4, Moves = new[]{ 333, 710, 442, 029 }, Index = 65, Shiny = Shiny.Always }, // Eiscue - new(60,10,5) { Species = 875, Ability = A4, Moves = new[]{ 333, 710, 442, 029 }, Index = 65 }, // Eiscue + new(17,01,1) { Species = 875, Ability = A4, Moves = new[]{ 181, 362, 033, 311 }, Index = 65 }, // Eiscue + new(30,03,2) { Species = 875, Ability = A4, Moves = new[]{ 196, 362, 033, 029 }, Index = 65 }, // Eiscue + new(40,05,3) { Species = 875, Ability = A4, Moves = new[]{ 008, 057, 263, 029 }, Index = 65 }, // Eiscue + new(50,08,4) { Species = 875, Ability = A4, Moves = new[]{ 333, 057, 428, 029 }, Index = 65 }, // Eiscue + new(60,10,5) { Species = 875, Ability = A4, Moves = new[]{ 333, 710, 442, 029 }, Index = 65, Shiny = Shiny.Always }, // Eiscue + new(60,10,5) { Species = 875, Ability = A4, Moves = new[]{ 333, 710, 442, 029 }, Index = 65 }, // Eiscue - new(17,01,1) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 64 }, // Ditto - new(17,01,1) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 043, 681 }, Index = 64 }, // Rookidee - new(17,01,1) { Species = 833, Ability = A4, Moves = new[]{ 055, 033, 044, 240 }, Index = 64 }, // Chewtle - new(17,01,1) { Species = 824, Ability = A4, Moves = new[]{ 522, 000, 000, 000 }, Index = 64 }, // Blipbug - new(17,01,1) { Species = 850, Ability = A4, Moves = new[]{ 172, 044, 035, 052 }, Index = 64 }, // Sizzlipede - new(17,01,1) { Species = 831, Ability = A4, Moves = new[]{ 033, 024, 029, 045 }, Index = 64 }, // Wooloo - new(30,03,2) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 64 }, // Ditto - new(30,03,2) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 279, 681 }, Index = 64 }, // Corvisquire - new(30,03,2) { Species = 833, Ability = A4, Moves = new[]{ 055, 033, 044, 029 }, Index = 64 }, // Chewtle - new(30,03,2) { Species = 825, Ability = A4, Moves = new[]{ 522, 263, 371, 247 }, Index = 64 }, // Dottler - new(30,03,2) { Species = 850, Ability = A4, Moves = new[]{ 172, 044, 035, 052 }, Index = 64 }, // Sizzlipede - new(30,03,2) { Species = 831, Ability = A4, Moves = new[]{ 036, 024, 029, 086 }, Index = 64 }, // Wooloo - new(40,05,3) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 64 }, // Ditto - new(40,05,3) { Species = 823, Ability = A4, Moves = new[]{ 403, 442, 034, 681 }, Index = 64 }, // Corviknight - new(40,05,3) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 317, 055 }, Index = 64 }, // Drednaw - new(40,05,3) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 371, 247 }, Index = 64 }, // Orbeetle - new(40,05,3) { Species = 851, Ability = A4, Moves = new[]{ 424, 404, 422, 044 }, Index = 64 }, // Centiskorch - new(40,05,3) { Species = 832, Ability = A4, Moves = new[]{ 036, 024, 428, 086 }, Index = 64 }, // Dubwool - new(50,08,4) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 64 }, // Ditto - new(50,08,4) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 034, 681 }, Index = 64 }, // Corviknight - new(50,08,4) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 64 }, // Drednaw - new(50,08,4) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 776, 247 }, Index = 64 }, // Orbeetle - new(50,08,4) { Species = 851, Ability = A4, Moves = new[]{ 680, 404, 422, 044 }, Index = 64 }, // Centiskorch - new(50,08,4) { Species = 832, Ability = A4, Moves = new[]{ 038, 024, 428, 086 }, Index = 64 }, // Dubwool - new(60,10,5) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 64 }, // Ditto - new(60,10,5) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 776, 372 }, Index = 64 }, // Corviknight - new(60,10,5) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 64 }, // Drednaw - new(60,10,5) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 776, 412 }, Index = 64 }, // Orbeetle - new(60,10,5) { Species = 851, Ability = A4, Moves = new[]{ 680, 679, 422, 044 }, Index = 64 }, // Centiskorch - new(60,10,5) { Species = 832, Ability = A4, Moves = new[]{ 038, 776, 428, 086 }, Index = 64 }, // Dubwool + new(17,01,1) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 64 }, // Ditto + new(17,01,1) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 043, 681 }, Index = 64 }, // Rookidee + new(17,01,1) { Species = 833, Ability = A4, Moves = new[]{ 055, 033, 044, 240 }, Index = 64 }, // Chewtle + new(17,01,1) { Species = 824, Ability = A4, Moves = new[]{ 522, 000, 000, 000 }, Index = 64 }, // Blipbug + new(17,01,1) { Species = 850, Ability = A4, Moves = new[]{ 172, 044, 035, 052 }, Index = 64 }, // Sizzlipede + new(17,01,1) { Species = 831, Ability = A4, Moves = new[]{ 033, 024, 029, 045 }, Index = 64 }, // Wooloo + new(30,03,2) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 64 }, // Ditto + new(30,03,2) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 279, 681 }, Index = 64 }, // Corvisquire + new(30,03,2) { Species = 833, Ability = A4, Moves = new[]{ 055, 033, 044, 029 }, Index = 64 }, // Chewtle + new(30,03,2) { Species = 825, Ability = A4, Moves = new[]{ 522, 263, 371, 247 }, Index = 64 }, // Dottler + new(30,03,2) { Species = 850, Ability = A4, Moves = new[]{ 172, 044, 035, 052 }, Index = 64 }, // Sizzlipede + new(30,03,2) { Species = 831, Ability = A4, Moves = new[]{ 036, 024, 029, 086 }, Index = 64 }, // Wooloo + new(40,05,3) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 64 }, // Ditto + new(40,05,3) { Species = 823, Ability = A4, Moves = new[]{ 403, 442, 034, 681 }, Index = 64 }, // Corviknight + new(40,05,3) { Species = 834, Ability = A4, Moves = new[]{ 157, 534, 317, 055 }, Index = 64 }, // Drednaw + new(40,05,3) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 371, 247 }, Index = 64 }, // Orbeetle + new(40,05,3) { Species = 851, Ability = A4, Moves = new[]{ 424, 404, 422, 044 }, Index = 64 }, // Centiskorch + new(40,05,3) { Species = 832, Ability = A4, Moves = new[]{ 036, 024, 428, 086 }, Index = 64 }, // Dubwool + new(50,08,4) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 64 }, // Ditto + new(50,08,4) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 034, 681 }, Index = 64 }, // Corviknight + new(50,08,4) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 64 }, // Drednaw + new(50,08,4) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 776, 247 }, Index = 64 }, // Orbeetle + new(50,08,4) { Species = 851, Ability = A4, Moves = new[]{ 680, 404, 422, 044 }, Index = 64 }, // Centiskorch + new(50,08,4) { Species = 832, Ability = A4, Moves = new[]{ 038, 024, 428, 086 }, Index = 64 }, // Dubwool + new(60,10,5) { Species = 132, Ability = A4, Moves = new[]{ 144, 000, 000, 000 }, Index = 64 }, // Ditto + new(60,10,5) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 776, 372 }, Index = 64 }, // Corviknight + new(60,10,5) { Species = 834, Ability = A4, Moves = new[]{ 157, 710, 317, 334 }, Index = 64 }, // Drednaw + new(60,10,5) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 776, 412 }, Index = 64 }, // Orbeetle + new(60,10,5) { Species = 851, Ability = A4, Moves = new[]{ 680, 679, 422, 044 }, Index = 64 }, // Centiskorch + new(60,10,5) { Species = 832, Ability = A4, Moves = new[]{ 038, 776, 428, 086 }, Index = 64 }, // Dubwool - new(17,01,1) { Species = 183, Ability = A4, Moves = new[]{ 061, 204, 111, 205 }, Index = 63 }, // Marill - new(17,01,1) { Species = 427, Ability = A4, Moves = new[]{ 098, 608, 150, 111 }, Index = 63 }, // Buneary - new(17,01,1) { Species = 659, Ability = A4, Moves = new[]{ 098, 189, 280, 341 }, Index = 63 }, // Bunnelby - new(30,03,2) { Species = 184, Ability = A4, Moves = new[]{ 401, 583, 280, 205 }, Index = 63 }, // Azumarill - new(30,03,2) { Species = 428, Ability = A4, Moves = new[]{ 024, 204, 029, 111 }, Index = 63 }, // Lopunny - new(30,03,2) { Species = 660, Ability = A4, Moves = new[]{ 098, 523, 280, 341 }, Index = 63 }, // Diggersby - new(40,05,3) { Species = 184, Ability = A4, Moves = new[]{ 056, 583, 280, 205 }, Index = 63 }, // Azumarill - new(40,05,3) { Species = 428, Ability = A4, Moves = new[]{ 024, 204, 029, 129 }, Index = 63 }, // Lopunny - new(40,05,3) { Species = 660, Ability = A4, Moves = new[]{ 005, 523, 280, 036 }, Index = 63 }, // Diggersby - new(50,08,4) { Species = 184, Ability = A4, Moves = new[]{ 710, 583, 276, 205 }, Index = 63 }, // Azumarill - new(50,08,4) { Species = 428, Ability = A4, Moves = new[]{ 024, 583, 025, 129 }, Index = 63 }, // Lopunny - new(50,08,4) { Species = 660, Ability = A4, Moves = new[]{ 005, 089, 280, 162 }, Index = 63 }, // Diggersby - new(60,10,5) { Species = 184, Ability = A4, Moves = new[]{ 710, 583, 276, 523 }, Index = 63 }, // Azumarill - new(60,10,5) { Species = 184, Ability = A4, Moves = new[]{ 710, 583, 276, 523 }, Index = 63, Shiny = Shiny.Always }, // Azumarill - new(60,10,5) { Species = 428, Ability = A4, Moves = new[]{ 136, 583, 025, 693 }, Index = 63 }, // Lopunny - new(60,10,5) { Species = 660, Ability = A4, Moves = new[]{ 416, 089, 359, 162 }, Index = 63 }, // Diggersby - //new(60,10,5) { Species = 815, Ability = A2, Moves = new[]{ 780, 442, 279, 555 }, Index = 63, CanGigantamax = true }, // Cinderace + new(17,01,1) { Species = 183, Ability = A4, Moves = new[]{ 061, 204, 111, 205 }, Index = 63 }, // Marill + new(17,01,1) { Species = 427, Ability = A4, Moves = new[]{ 098, 608, 150, 111 }, Index = 63 }, // Buneary + new(17,01,1) { Species = 659, Ability = A4, Moves = new[]{ 098, 189, 280, 341 }, Index = 63 }, // Bunnelby + new(30,03,2) { Species = 184, Ability = A4, Moves = new[]{ 401, 583, 280, 205 }, Index = 63 }, // Azumarill + new(30,03,2) { Species = 428, Ability = A4, Moves = new[]{ 024, 204, 029, 111 }, Index = 63 }, // Lopunny + new(30,03,2) { Species = 660, Ability = A4, Moves = new[]{ 098, 523, 280, 341 }, Index = 63 }, // Diggersby + new(40,05,3) { Species = 184, Ability = A4, Moves = new[]{ 056, 583, 280, 205 }, Index = 63 }, // Azumarill + new(40,05,3) { Species = 428, Ability = A4, Moves = new[]{ 024, 204, 029, 129 }, Index = 63 }, // Lopunny + new(40,05,3) { Species = 660, Ability = A4, Moves = new[]{ 005, 523, 280, 036 }, Index = 63 }, // Diggersby + new(50,08,4) { Species = 184, Ability = A4, Moves = new[]{ 710, 583, 276, 205 }, Index = 63 }, // Azumarill + new(50,08,4) { Species = 428, Ability = A4, Moves = new[]{ 024, 583, 025, 129 }, Index = 63 }, // Lopunny + new(50,08,4) { Species = 660, Ability = A4, Moves = new[]{ 005, 089, 280, 162 }, Index = 63 }, // Diggersby + new(60,10,5) { Species = 184, Ability = A4, Moves = new[]{ 710, 583, 276, 523 }, Index = 63 }, // Azumarill + new(60,10,5) { Species = 184, Ability = A4, Moves = new[]{ 710, 583, 276, 523 }, Index = 63, Shiny = Shiny.Always }, // Azumarill + new(60,10,5) { Species = 428, Ability = A4, Moves = new[]{ 136, 583, 025, 693 }, Index = 63 }, // Lopunny + new(60,10,5) { Species = 660, Ability = A4, Moves = new[]{ 416, 089, 359, 162 }, Index = 63 }, // Diggersby + //new(60,10,5) { Species = 815, Ability = A2, Moves = new[]{ 780, 442, 279, 555 }, Index = 63, CanGigantamax = true }, // Cinderace - //new(17,01,1) { Species = 129, Ability = A4, Moves = new[]{ 150, 000, 000, 000 }, Index = 62 }, // Magikarp - //new(30,03,2) { Species = 129, Ability = A4, Moves = new[]{ 150, 000, 000, 000 }, Index = 62 }, // Magikarp - //new(40,05,3) { Species = 129, Ability = A4, Moves = new[]{ 150, 000, 000, 000 }, Index = 62 }, // Magikarp - //new(50,08,4) { Species = 129, Ability = A4, Moves = new[]{ 150, 000, 000, 000 }, Index = 62 }, // Magikarp - //new(60,10,5) { Species = 129, Ability = A4, Moves = new[]{ 150, 000, 000, 000 }, Index = 62 }, // Magikarp + //new(17,01,1) { Species = 129, Ability = A4, Moves = new[]{ 150, 000, 000, 000 }, Index = 62 }, // Magikarp + //new(30,03,2) { Species = 129, Ability = A4, Moves = new[]{ 150, 000, 000, 000 }, Index = 62 }, // Magikarp + //new(40,05,3) { Species = 129, Ability = A4, Moves = new[]{ 150, 000, 000, 000 }, Index = 62 }, // Magikarp + //new(50,08,4) { Species = 129, Ability = A4, Moves = new[]{ 150, 000, 000, 000 }, Index = 62 }, // Magikarp + //new(60,10,5) { Species = 129, Ability = A4, Moves = new[]{ 150, 000, 000, 000 }, Index = 62 }, // Magikarp - new(17,01,1) { Species = 043, Ability = A4, Moves = new[]{ 331, 236, 051, 074 }, Index = 60 }, // Oddish - new(17,01,1) { Species = 420, Ability = A4, Moves = new[]{ 234, 572, 670, 033 }, Index = 60 }, // Cherubi - new(17,01,1) { Species = 549, Ability = A4, Moves = new[]{ 412, 298, 345, 263 }, Index = 60 }, // Lilligant - new(17,01,1) { Species = 753, Ability = A4, Moves = new[]{ 210, 074, 075, 275 }, Index = 60 }, // Fomantis - new(17,01,1) { Species = 764, Ability = A4, Moves = new[]{ 345, 579, 035, 074 }, Index = 60 }, // Comfey - new(30,03,2) { Species = 045, Ability = A4, Moves = new[]{ 572, 236, 051, 496 }, Index = 60 }, // Vileplume - new(30,03,2) { Species = 182, Ability = A4, Moves = new[]{ 572, 483, 074, 605 }, Index = 60 }, // Bellossom - new(30,03,2) { Species = 421, Ability = A4, Moves = new[]{ 579, 345, 670, 033 }, Index = 60 }, // Cherrim - new(30,03,2) { Species = 549, Ability = A4, Moves = new[]{ 412, 298, 345, 263 }, Index = 60 }, // Lilligant - new(30,03,2) { Species = 753, Ability = A4, Moves = new[]{ 210, 163, 075, 230 }, Index = 60 }, // Fomantis - new(30,03,2) { Species = 764, Ability = A4, Moves = new[]{ 345, 579, 035, 583 }, Index = 60 }, // Comfey - new(40,05,3) { Species = 045, Ability = A4, Moves = new[]{ 572, 585, 051, 496 }, Index = 60 }, // Vileplume - new(40,05,3) { Species = 182, Ability = A4, Moves = new[]{ 572, 483, 077, 605 }, Index = 60 }, // Bellossom - new(40,05,3) { Species = 421, Ability = A4, Moves = new[]{ 579, 345, 583, 036 }, Index = 60 }, // Cherrim - new(40,05,3) { Species = 549, Ability = A4, Moves = new[]{ 572, 298, 345, 483 }, Index = 60 }, // Lilligant - new(40,05,3) { Species = 754, Ability = A4, Moves = new[]{ 210, 572, 400, 530 }, Index = 60 }, // Lurantis - new(40,05,3) { Species = 764, Ability = A4, Moves = new[]{ 572, 579, 035, 583 }, Index = 60 }, // Comfey - new(50,08,4) { Species = 045, Ability = A4, Moves = new[]{ 572, 585, 092, 496 }, Index = 60 }, // Vileplume - new(50,08,4) { Species = 182, Ability = A4, Moves = new[]{ 080, 483, 051, 605 }, Index = 60 }, // Bellossom - new(50,08,4) { Species = 421, Ability = A4, Moves = new[]{ 579, 572, 583, 676 }, Index = 60 }, // Cherrim - new(50,08,4) { Species = 549, Ability = A4, Moves = new[]{ 572, 298, 241, 676 }, Index = 60 }, // Lilligant - new(50,08,4) { Species = 754, Ability = A4, Moves = new[]{ 404, 572, 400, 530 }, Index = 60 }, // Lurantis - new(50,08,4) { Species = 764, Ability = A4, Moves = new[]{ 572, 579, 035, 583 }, Index = 60 }, // Comfey - new(60,10,5) { Species = 045, Ability = A4, Moves = new[]{ 572, 585, 092, 034 }, Index = 60 }, // Vileplume - new(60,10,5) { Species = 549, Ability = A4, Moves = new[]{ 080, 298, 241, 676 }, Index = 60, Shiny = Shiny.Always }, // Lilligant - new(60,10,5) { Species = 421, Ability = A4, Moves = new[]{ 579, 572, 605, 676 }, Index = 60 }, // Cherrim - new(60,10,5) { Species = 549, Ability = A4, Moves = new[]{ 080, 298, 241, 676 }, Index = 60 }, // Lilligant - new(60,10,5) { Species = 754, Ability = A4, Moves = new[]{ 404, 572, 398, 530 }, Index = 60 }, // Lurantis - new(60,10,5) { Species = 764, Ability = A4, Moves = new[]{ 572, 579, 447, 583 }, Index = 60 }, // Comfey + new(17,01,1) { Species = 043, Ability = A4, Moves = new[]{ 331, 236, 051, 074 }, Index = 60 }, // Oddish + new(17,01,1) { Species = 420, Ability = A4, Moves = new[]{ 234, 572, 670, 033 }, Index = 60 }, // Cherubi + new(17,01,1) { Species = 549, Ability = A4, Moves = new[]{ 412, 298, 345, 263 }, Index = 60 }, // Lilligant + new(17,01,1) { Species = 753, Ability = A4, Moves = new[]{ 210, 074, 075, 275 }, Index = 60 }, // Fomantis + new(17,01,1) { Species = 764, Ability = A4, Moves = new[]{ 345, 579, 035, 074 }, Index = 60 }, // Comfey + new(30,03,2) { Species = 045, Ability = A4, Moves = new[]{ 572, 236, 051, 496 }, Index = 60 }, // Vileplume + new(30,03,2) { Species = 182, Ability = A4, Moves = new[]{ 572, 483, 074, 605 }, Index = 60 }, // Bellossom + new(30,03,2) { Species = 421, Ability = A4, Moves = new[]{ 579, 345, 670, 033 }, Index = 60 }, // Cherrim + new(30,03,2) { Species = 549, Ability = A4, Moves = new[]{ 412, 298, 345, 263 }, Index = 60 }, // Lilligant + new(30,03,2) { Species = 753, Ability = A4, Moves = new[]{ 210, 163, 075, 230 }, Index = 60 }, // Fomantis + new(30,03,2) { Species = 764, Ability = A4, Moves = new[]{ 345, 579, 035, 583 }, Index = 60 }, // Comfey + new(40,05,3) { Species = 045, Ability = A4, Moves = new[]{ 572, 585, 051, 496 }, Index = 60 }, // Vileplume + new(40,05,3) { Species = 182, Ability = A4, Moves = new[]{ 572, 483, 077, 605 }, Index = 60 }, // Bellossom + new(40,05,3) { Species = 421, Ability = A4, Moves = new[]{ 579, 345, 583, 036 }, Index = 60 }, // Cherrim + new(40,05,3) { Species = 549, Ability = A4, Moves = new[]{ 572, 298, 345, 483 }, Index = 60 }, // Lilligant + new(40,05,3) { Species = 754, Ability = A4, Moves = new[]{ 210, 572, 400, 530 }, Index = 60 }, // Lurantis + new(40,05,3) { Species = 764, Ability = A4, Moves = new[]{ 572, 579, 035, 583 }, Index = 60 }, // Comfey + new(50,08,4) { Species = 045, Ability = A4, Moves = new[]{ 572, 585, 092, 496 }, Index = 60 }, // Vileplume + new(50,08,4) { Species = 182, Ability = A4, Moves = new[]{ 080, 483, 051, 605 }, Index = 60 }, // Bellossom + new(50,08,4) { Species = 421, Ability = A4, Moves = new[]{ 579, 572, 583, 676 }, Index = 60 }, // Cherrim + new(50,08,4) { Species = 549, Ability = A4, Moves = new[]{ 572, 298, 241, 676 }, Index = 60 }, // Lilligant + new(50,08,4) { Species = 754, Ability = A4, Moves = new[]{ 404, 572, 400, 530 }, Index = 60 }, // Lurantis + new(50,08,4) { Species = 764, Ability = A4, Moves = new[]{ 572, 579, 035, 583 }, Index = 60 }, // Comfey + new(60,10,5) { Species = 045, Ability = A4, Moves = new[]{ 572, 585, 092, 034 }, Index = 60 }, // Vileplume + new(60,10,5) { Species = 549, Ability = A4, Moves = new[]{ 080, 298, 241, 676 }, Index = 60, Shiny = Shiny.Always }, // Lilligant + new(60,10,5) { Species = 421, Ability = A4, Moves = new[]{ 579, 572, 605, 676 }, Index = 60 }, // Cherrim + new(60,10,5) { Species = 549, Ability = A4, Moves = new[]{ 080, 298, 241, 676 }, Index = 60 }, // Lilligant + new(60,10,5) { Species = 754, Ability = A4, Moves = new[]{ 404, 572, 398, 530 }, Index = 60 }, // Lurantis + new(60,10,5) { Species = 764, Ability = A4, Moves = new[]{ 572, 579, 447, 583 }, Index = 60 }, // Comfey - new(17,01,1) { Species = 856, Ability = A4, Moves = new[]{ 312, 093, 574, 595 }, Index = 59 }, // Hatenna - new(17,01,1) { Species = 280, Ability = A4, Moves = new[]{ 574, 093, 104, 045 }, Index = 59 }, // Ralts - new(17,01,1) { Species = 109, Ability = A4, Moves = new[]{ 499, 053, 124, 372 }, Index = 59 }, // Koffing - new(17,01,1) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 043, 681 }, Index = 59 }, // Rookidee - new(17,01,1) { Species = 627, Ability = A4, Moves = new[]{ 043, 276, 017, 064 }, Index = 59 }, // Rufflet - new(17,01,1) { Species = 845, Ability = A4, Moves = new[]{ 055, 254, 064, 562 }, Index = 59 }, // Cramorant - new(30,03,2) { Species = 856, Ability = A4, Moves = new[]{ 605, 060, 574, 595 }, Index = 59 }, // Hatenna - new(30,03,2) { Species = 281, Ability = A4, Moves = new[]{ 574, 060, 104, 085 }, Index = 59 }, // Kirlia - new(30,03,2) { Species = 109, Ability = A4, Moves = new[]{ 499, 053, 482, 372 }, Index = 59 }, // Koffing - new(30,03,2) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 279, 681 }, Index = 59 }, // Corvisquire - new(30,03,2) { Species = 627, Ability = A4, Moves = new[]{ 184, 276, 017, 157 }, Index = 59 }, // Rufflet - new(30,03,2) { Species = 845, Ability = A4, Moves = new[]{ 055, 058, 064, 562 }, Index = 59 }, // Cramorant - new(40,05,3) { Species = 857, Ability = A4, Moves = new[]{ 605, 060, 595, 574 }, Index = 59, CanGigantamax = true }, // Hattrem - new(40,05,3) { Species = 282, Ability = A4, Moves = new[]{ 585, 060, 595, 085 }, Index = 59 }, // Gardevoir - new(40,05,3) { Species = 110, Ability = A4, Moves = new[]{ 790, 053, 482, 372 }, Index = 59, Form = 1 }, // Weezing-1 - new(40,05,3) { Species = 823, Ability = A4, Moves = new[]{ 403, 442, 034, 681 }, Index = 59, CanGigantamax = true }, // Corviknight - new(40,05,3) { Species = 628, Ability = A4, Moves = new[]{ 403, 276, 163, 157 }, Index = 59 }, // Braviary - new(40,05,3) { Species = 845, Ability = A4, Moves = new[]{ 503, 058, 403, 065 }, Index = 59 }, // Cramorant - new(50,08,4) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 59, CanGigantamax = true }, // Hatterene - new(50,08,4) { Species = 282, Ability = A4, Moves = new[]{ 585, 094, 595, 085 }, Index = 59 }, // Gardevoir - new(50,08,4) { Species = 110, Ability = A4, Moves = new[]{ 790, 126, 482, 372 }, Index = 59, Form = 1 }, // Weezing-1 - new(50,08,4) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 034, 681 }, Index = 59, CanGigantamax = true }, // Corviknight - new(50,08,4) { Species = 628, Ability = A4, Moves = new[]{ 403, 276, 442, 157 }, Index = 59 }, // Braviary - new(50,08,4) { Species = 845, Ability = A4, Moves = new[]{ 056, 058, 403, 065 }, Index = 59 }, // Cramorant - new(60,10,5) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 438, 247 }, Index = 59, CanGigantamax = true }, // Hatterene - new(60,10,5) { Species = 282, Ability = A4, Moves = new[]{ 585, 094, 261, 085 }, Index = 59 }, // Gardevoir - new(60,10,5) { Species = 110, Ability = A4, Moves = new[]{ 790, 126, 482, 399 }, Index = 59, Form = 1 }, // Weezing-1 - new(60,10,5) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 776, 372 }, Index = 59, CanGigantamax = true }, // Corviknight - new(60,10,5) { Species = 628, Ability = A4, Moves = new[]{ 413, 370, 442, 157 }, Index = 59 }, // Braviary - new(60,10,5) { Species = 845, Ability = A4, Moves = new[]{ 056, 058, 403, 057 }, Index = 59 }, // Cramorant + new(17,01,1) { Species = 856, Ability = A4, Moves = new[]{ 312, 093, 574, 595 }, Index = 59 }, // Hatenna + new(17,01,1) { Species = 280, Ability = A4, Moves = new[]{ 574, 093, 104, 045 }, Index = 59 }, // Ralts + new(17,01,1) { Species = 109, Ability = A4, Moves = new[]{ 499, 053, 124, 372 }, Index = 59 }, // Koffing + new(17,01,1) { Species = 821, Ability = A4, Moves = new[]{ 403, 031, 043, 681 }, Index = 59 }, // Rookidee + new(17,01,1) { Species = 627, Ability = A4, Moves = new[]{ 043, 276, 017, 064 }, Index = 59 }, // Rufflet + new(17,01,1) { Species = 845, Ability = A4, Moves = new[]{ 055, 254, 064, 562 }, Index = 59 }, // Cramorant + new(30,03,2) { Species = 856, Ability = A4, Moves = new[]{ 605, 060, 574, 595 }, Index = 59 }, // Hatenna + new(30,03,2) { Species = 281, Ability = A4, Moves = new[]{ 574, 060, 104, 085 }, Index = 59 }, // Kirlia + new(30,03,2) { Species = 109, Ability = A4, Moves = new[]{ 499, 053, 482, 372 }, Index = 59 }, // Koffing + new(30,03,2) { Species = 822, Ability = A4, Moves = new[]{ 403, 263, 279, 681 }, Index = 59 }, // Corvisquire + new(30,03,2) { Species = 627, Ability = A4, Moves = new[]{ 184, 276, 017, 157 }, Index = 59 }, // Rufflet + new(30,03,2) { Species = 845, Ability = A4, Moves = new[]{ 055, 058, 064, 562 }, Index = 59 }, // Cramorant + new(40,05,3) { Species = 857, Ability = A4, Moves = new[]{ 605, 060, 595, 574 }, Index = 59, CanGigantamax = true }, // Hattrem + new(40,05,3) { Species = 282, Ability = A4, Moves = new[]{ 585, 060, 595, 085 }, Index = 59 }, // Gardevoir + new(40,05,3) { Species = 110, Ability = A4, Moves = new[]{ 790, 053, 482, 372 }, Index = 59, Form = 1 }, // Weezing-1 + new(40,05,3) { Species = 823, Ability = A4, Moves = new[]{ 403, 442, 034, 681 }, Index = 59, CanGigantamax = true }, // Corviknight + new(40,05,3) { Species = 628, Ability = A4, Moves = new[]{ 403, 276, 163, 157 }, Index = 59 }, // Braviary + new(40,05,3) { Species = 845, Ability = A4, Moves = new[]{ 503, 058, 403, 065 }, Index = 59 }, // Cramorant + new(50,08,4) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 595, 247 }, Index = 59, CanGigantamax = true }, // Hatterene + new(50,08,4) { Species = 282, Ability = A4, Moves = new[]{ 585, 094, 595, 085 }, Index = 59 }, // Gardevoir + new(50,08,4) { Species = 110, Ability = A4, Moves = new[]{ 790, 126, 482, 372 }, Index = 59, Form = 1 }, // Weezing-1 + new(50,08,4) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 034, 681 }, Index = 59, CanGigantamax = true }, // Corviknight + new(50,08,4) { Species = 628, Ability = A4, Moves = new[]{ 403, 276, 442, 157 }, Index = 59 }, // Braviary + new(50,08,4) { Species = 845, Ability = A4, Moves = new[]{ 056, 058, 403, 065 }, Index = 59 }, // Cramorant + new(60,10,5) { Species = 858, Ability = A4, Moves = new[]{ 605, 094, 438, 247 }, Index = 59, CanGigantamax = true }, // Hatterene + new(60,10,5) { Species = 282, Ability = A4, Moves = new[]{ 585, 094, 261, 085 }, Index = 59 }, // Gardevoir + new(60,10,5) { Species = 110, Ability = A4, Moves = new[]{ 790, 126, 482, 399 }, Index = 59, Form = 1 }, // Weezing-1 + new(60,10,5) { Species = 823, Ability = A4, Moves = new[]{ 413, 442, 776, 372 }, Index = 59, CanGigantamax = true }, // Corviknight + new(60,10,5) { Species = 628, Ability = A4, Moves = new[]{ 413, 370, 442, 157 }, Index = 59 }, // Braviary + new(60,10,5) { Species = 845, Ability = A4, Moves = new[]{ 056, 058, 403, 057 }, Index = 59 }, // Cramorant - new(17,01,1) { Species = 067, Ability = A4, Moves = new[]{ 279, 009, 490, 067 }, Index = 58 }, // Machoke - new(17,01,1) { Species = 447, Ability = A4, Moves = new[]{ 280, 098, 317, 523 }, Index = 58 }, // Riolu - new(17,01,1) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 249, 157 }, Index = 58 }, // Falinks - new(17,01,1) { Species = 825, Ability = A4, Moves = new[]{ 522, 263, 371, 247 }, Index = 58 }, // Dottler - new(17,01,1) { Species = 577, Ability = A4, Moves = new[]{ 060, 086, 360, 283 }, Index = 58 }, // Solosis - new(17,01,1) { Species = 574, Ability = A4, Moves = new[]{ 060, 086, 412, 157 }, Index = 58 }, // Gothita - new(30,03,2) { Species = 068, Ability = A4, Moves = new[]{ 279, 009, 233, 372 }, Index = 58, CanGigantamax = true }, // Machamp - new(30,03,2) { Species = 447, Ability = A4, Moves = new[]{ 280, 232, 317, 523 }, Index = 58 }, // Riolu - new(30,03,2) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 179, 157 }, Index = 58 }, // Falinks - new(30,03,2) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 371, 109 }, Index = 58, CanGigantamax = true }, // Orbeetle - new(30,03,2) { Species = 577, Ability = A4, Moves = new[]{ 473, 086, 360, 283 }, Index = 58 }, // Solosis - new(30,03,2) { Species = 574, Ability = A4, Moves = new[]{ 473, 086, 412, 157 }, Index = 58 }, // Gothita - new(40,05,3) { Species = 068, Ability = A4, Moves = new[]{ 530, 009, 233, 372 }, Index = 58, CanGigantamax = true }, // Machamp - new(40,05,3) { Species = 448, Ability = A4, Moves = new[]{ 612, 232, 444, 089 }, Index = 58 }, // Lucario - new(40,05,3) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 179, 317 }, Index = 58 }, // Falinks - new(40,05,3) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 371, 247 }, Index = 58, CanGigantamax = true }, // Orbeetle - new(40,05,3) { Species = 578, Ability = A4, Moves = new[]{ 094, 086, 360, 247 }, Index = 58 }, // Duosion - new(40,05,3) { Species = 575, Ability = A4, Moves = new[]{ 094, 085, 412, 157 }, Index = 58 }, // Gothorita - new(50,08,4) { Species = 068, Ability = A4, Moves = new[]{ 223, 009, 370, 372 }, Index = 58, CanGigantamax = true }, // Machamp - new(50,08,4) { Species = 448, Ability = A4, Moves = new[]{ 612, 309, 444, 089 }, Index = 58 }, // Lucario - new(50,08,4) { Species = 870, Ability = A4, Moves = new[]{ 442, 224, 179, 317 }, Index = 58 }, // Falinks - new(50,08,4) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 776, 247 }, Index = 58, CanGigantamax = true }, // Orbeetle - new(50,08,4) { Species = 579, Ability = A4, Moves = new[]{ 094, 009, 411, 247 }, Index = 58 }, // Reuniclus - new(50,08,4) { Species = 576, Ability = A4, Moves = new[]{ 094, 085, 412, 322 }, Index = 58 }, // Gothitelle - new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 804, 435, 057, 574 }, Index = 58, CanGigantamax = true, Shiny = Shiny.Always }, // Pikachu - new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 344, 280, 583, 231 }, Index = 58, CanGigantamax = true, Shiny = Shiny.Always }, // Pikachu - new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 804, 435, 057, 574 }, Index = 58, CanGigantamax = true }, // Pikachu - new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 344, 280, 583, 231 }, Index = 58, CanGigantamax = true }, // Pikachu + new(17,01,1) { Species = 067, Ability = A4, Moves = new[]{ 279, 009, 490, 067 }, Index = 58 }, // Machoke + new(17,01,1) { Species = 447, Ability = A4, Moves = new[]{ 280, 098, 317, 523 }, Index = 58 }, // Riolu + new(17,01,1) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 249, 157 }, Index = 58 }, // Falinks + new(17,01,1) { Species = 825, Ability = A4, Moves = new[]{ 522, 263, 371, 247 }, Index = 58 }, // Dottler + new(17,01,1) { Species = 577, Ability = A4, Moves = new[]{ 060, 086, 360, 283 }, Index = 58 }, // Solosis + new(17,01,1) { Species = 574, Ability = A4, Moves = new[]{ 060, 086, 412, 157 }, Index = 58 }, // Gothita + new(30,03,2) { Species = 068, Ability = A4, Moves = new[]{ 279, 009, 233, 372 }, Index = 58, CanGigantamax = true }, // Machamp + new(30,03,2) { Species = 447, Ability = A4, Moves = new[]{ 280, 232, 317, 523 }, Index = 58 }, // Riolu + new(30,03,2) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 179, 157 }, Index = 58 }, // Falinks + new(30,03,2) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 371, 109 }, Index = 58, CanGigantamax = true }, // Orbeetle + new(30,03,2) { Species = 577, Ability = A4, Moves = new[]{ 473, 086, 360, 283 }, Index = 58 }, // Solosis + new(30,03,2) { Species = 574, Ability = A4, Moves = new[]{ 473, 086, 412, 157 }, Index = 58 }, // Gothita + new(40,05,3) { Species = 068, Ability = A4, Moves = new[]{ 530, 009, 233, 372 }, Index = 58, CanGigantamax = true }, // Machamp + new(40,05,3) { Species = 448, Ability = A4, Moves = new[]{ 612, 232, 444, 089 }, Index = 58 }, // Lucario + new(40,05,3) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 179, 317 }, Index = 58 }, // Falinks + new(40,05,3) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 371, 247 }, Index = 58, CanGigantamax = true }, // Orbeetle + new(40,05,3) { Species = 578, Ability = A4, Moves = new[]{ 094, 086, 360, 247 }, Index = 58 }, // Duosion + new(40,05,3) { Species = 575, Ability = A4, Moves = new[]{ 094, 085, 412, 157 }, Index = 58 }, // Gothorita + new(50,08,4) { Species = 068, Ability = A4, Moves = new[]{ 223, 009, 370, 372 }, Index = 58, CanGigantamax = true }, // Machamp + new(50,08,4) { Species = 448, Ability = A4, Moves = new[]{ 612, 309, 444, 089 }, Index = 58 }, // Lucario + new(50,08,4) { Species = 870, Ability = A4, Moves = new[]{ 442, 224, 179, 317 }, Index = 58 }, // Falinks + new(50,08,4) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 776, 247 }, Index = 58, CanGigantamax = true }, // Orbeetle + new(50,08,4) { Species = 579, Ability = A4, Moves = new[]{ 094, 009, 411, 247 }, Index = 58 }, // Reuniclus + new(50,08,4) { Species = 576, Ability = A4, Moves = new[]{ 094, 085, 412, 322 }, Index = 58 }, // Gothitelle + new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 804, 435, 057, 574 }, Index = 58, CanGigantamax = true, Shiny = Shiny.Always }, // Pikachu + new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 344, 280, 583, 231 }, Index = 58, CanGigantamax = true, Shiny = Shiny.Always }, // Pikachu + new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 804, 435, 057, 574 }, Index = 58, CanGigantamax = true }, // Pikachu + new(60,10,5) { Species = 025, Ability = A4, Moves = new[]{ 344, 280, 583, 231 }, Index = 58, CanGigantamax = true }, // Pikachu - new(17,01,1) { Species = 868, Ability = A4, Moves = new[]{ 033, 186, 577, 496 }, Index = 56, CanGigantamax = true }, // Milcery - new(30,03,2) { Species = 868, Ability = A4, Moves = new[]{ 577, 186, 263, 500 }, Index = 56, CanGigantamax = true }, // Milcery - new(40,05,3) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 213 }, Index = 56, CanGigantamax = true }, // Milcery - new(50,08,4) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 56, CanGigantamax = true }, // Milcery - new(60,10,5) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 56, CanGigantamax = true, Shiny = Shiny.Always }, // Milcery - new(60,10,5) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 56, CanGigantamax = true }, // Milcery + new(17,01,1) { Species = 868, Ability = A4, Moves = new[]{ 033, 186, 577, 496 }, Index = 56, CanGigantamax = true }, // Milcery + new(30,03,2) { Species = 868, Ability = A4, Moves = new[]{ 577, 186, 263, 500 }, Index = 56, CanGigantamax = true }, // Milcery + new(40,05,3) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 213 }, Index = 56, CanGigantamax = true }, // Milcery + new(50,08,4) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 56, CanGigantamax = true }, // Milcery + new(60,10,5) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 56, CanGigantamax = true, Shiny = Shiny.Always }, // Milcery + new(60,10,5) { Species = 868, Ability = A4, Moves = new[]{ 577, 605, 496, 500 }, Index = 56, CanGigantamax = true }, // Milcery - new(17,01,1) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54 }, // Cramorant - new(17,01,1) { Species = 330, Ability = A4, Moves = new[]{ 225, 129, 693, 048 }, Index = 54 }, // Flygon - new(17,01,1) { Species = 623, Ability = A4, Moves = new[]{ 089, 157, 707, 009 }, Index = 54 }, // Golurk - new(17,01,1) { Species = 195, Ability = A4, Moves = new[]{ 055, 157, 281, 034 }, Index = 54 }, // Quagsire - new(17,01,1) { Species = 876, Ability = A4, Moves = new[]{ 574, 129, 304, 000 }, Index = 54, Form = 1 }, // Indeedee-1 - new(30,03,2) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54 }, // Cramorant - new(30,03,2) { Species = 330, Ability = A4, Moves = new[]{ 225, 129, 693, 048 }, Index = 54 }, // Flygon - new(30,03,2) { Species = 623, Ability = A4, Moves = new[]{ 089, 157, 707, 009 }, Index = 54 }, // Golurk - new(30,03,2) { Species = 195, Ability = A4, Moves = new[]{ 055, 157, 281, 034 }, Index = 54 }, // Quagsire - new(30,03,2) { Species = 876, Ability = A4, Moves = new[]{ 574, 129, 304, 000 }, Index = 54, Form = 1 }, // Indeedee-1 - new(40,05,3) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54 }, // Cramorant - new(40,05,3) { Species = 330, Ability = A4, Moves = new[]{ 225, 129, 693, 586 }, Index = 54 }, // Flygon - new(40,05,3) { Species = 623, Ability = A4, Moves = new[]{ 089, 157, 523, 009 }, Index = 54 }, // Golurk - new(40,05,3) { Species = 195, Ability = A4, Moves = new[]{ 330, 157, 281, 034 }, Index = 54 }, // Quagsire - new(40,05,3) { Species = 876, Ability = A4, Moves = new[]{ 574, 129, 304, 000 }, Index = 54, Form = 1 }, // Indeedee-1 - new(50,08,4) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54 }, // Cramorant - new(50,08,4) { Species = 330, Ability = A4, Moves = new[]{ 225, 129, 693, 586 }, Index = 54 }, // Flygon - new(50,08,4) { Species = 623, Ability = A4, Moves = new[]{ 089, 157, 523, 009 }, Index = 54 }, // Golurk - new(50,08,4) { Species = 195, Ability = A4, Moves = new[]{ 330, 157, 281, 034 }, Index = 54 }, // Quagsire - new(50,08,4) { Species = 876, Ability = A4, Moves = new[]{ 574, 129, 304, 000 }, Index = 54, Form = 1 }, // Indeedee-1 - new(60,10,5) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54 }, // Cramorant - new(60,10,5) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54, Shiny = Shiny.Always }, // Cramorant - new(60,10,5) { Species = 330, Ability = A4, Moves = new[]{ 225, 129, 693, 586 }, Index = 54 }, // Flygon - new(60,10,5) { Species = 623, Ability = A4, Moves = new[]{ 089, 157, 523, 009 }, Index = 54 }, // Golurk - new(60,10,5) { Species = 195, Ability = A4, Moves = new[]{ 330, 157, 281, 034 }, Index = 54 }, // Quagsire - new(60,10,5) { Species = 876, Ability = A4, Moves = new[]{ 574, 129, 304, 000 }, Index = 54, Form = 1 }, // Indeedee-1 + new(17,01,1) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54 }, // Cramorant + new(17,01,1) { Species = 330, Ability = A4, Moves = new[]{ 225, 129, 693, 048 }, Index = 54 }, // Flygon + new(17,01,1) { Species = 623, Ability = A4, Moves = new[]{ 089, 157, 707, 009 }, Index = 54 }, // Golurk + new(17,01,1) { Species = 195, Ability = A4, Moves = new[]{ 055, 157, 281, 034 }, Index = 54 }, // Quagsire + new(17,01,1) { Species = 876, Ability = A4, Moves = new[]{ 574, 129, 304, 000 }, Index = 54, Form = 1 }, // Indeedee-1 + new(30,03,2) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54 }, // Cramorant + new(30,03,2) { Species = 330, Ability = A4, Moves = new[]{ 225, 129, 693, 048 }, Index = 54 }, // Flygon + new(30,03,2) { Species = 623, Ability = A4, Moves = new[]{ 089, 157, 707, 009 }, Index = 54 }, // Golurk + new(30,03,2) { Species = 195, Ability = A4, Moves = new[]{ 055, 157, 281, 034 }, Index = 54 }, // Quagsire + new(30,03,2) { Species = 876, Ability = A4, Moves = new[]{ 574, 129, 304, 000 }, Index = 54, Form = 1 }, // Indeedee-1 + new(40,05,3) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54 }, // Cramorant + new(40,05,3) { Species = 330, Ability = A4, Moves = new[]{ 225, 129, 693, 586 }, Index = 54 }, // Flygon + new(40,05,3) { Species = 623, Ability = A4, Moves = new[]{ 089, 157, 523, 009 }, Index = 54 }, // Golurk + new(40,05,3) { Species = 195, Ability = A4, Moves = new[]{ 330, 157, 281, 034 }, Index = 54 }, // Quagsire + new(40,05,3) { Species = 876, Ability = A4, Moves = new[]{ 574, 129, 304, 000 }, Index = 54, Form = 1 }, // Indeedee-1 + new(50,08,4) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54 }, // Cramorant + new(50,08,4) { Species = 330, Ability = A4, Moves = new[]{ 225, 129, 693, 586 }, Index = 54 }, // Flygon + new(50,08,4) { Species = 623, Ability = A4, Moves = new[]{ 089, 157, 523, 009 }, Index = 54 }, // Golurk + new(50,08,4) { Species = 195, Ability = A4, Moves = new[]{ 330, 157, 281, 034 }, Index = 54 }, // Quagsire + new(50,08,4) { Species = 876, Ability = A4, Moves = new[]{ 574, 129, 304, 000 }, Index = 54, Form = 1 }, // Indeedee-1 + new(60,10,5) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54 }, // Cramorant + new(60,10,5) { Species = 845, Ability = A4, Moves = new[]{ 057, 056, 503, 000 }, Index = 54, Shiny = Shiny.Always }, // Cramorant + new(60,10,5) { Species = 330, Ability = A4, Moves = new[]{ 225, 129, 693, 586 }, Index = 54 }, // Flygon + new(60,10,5) { Species = 623, Ability = A4, Moves = new[]{ 089, 157, 523, 009 }, Index = 54 }, // Golurk + new(60,10,5) { Species = 195, Ability = A4, Moves = new[]{ 330, 157, 281, 034 }, Index = 54 }, // Quagsire + new(60,10,5) { Species = 876, Ability = A4, Moves = new[]{ 574, 129, 304, 000 }, Index = 54, Form = 1 }, // Indeedee-1 - new(17,01,1) { Species = 067, Ability = A4, Moves = new[]{ 279, 009, 490, 067 }, Index = 53 }, // Machoke - new(17,01,1) { Species = 447, Ability = A4, Moves = new[]{ 280, 098, 317, 523 }, Index = 53 }, // Riolu - new(17,01,1) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 249, 157 }, Index = 53 }, // Falinks - new(17,01,1) { Species = 825, Ability = A4, Moves = new[]{ 522, 263, 371, 247 }, Index = 53 }, // Dottler - new(17,01,1) { Species = 577, Ability = A4, Moves = new[]{ 060, 086, 360, 283 }, Index = 53 }, // Solosis - new(17,01,1) { Species = 574, Ability = A4, Moves = new[]{ 060, 086, 412, 157 }, Index = 53 }, // Gothita - new(30,03,2) { Species = 068, Ability = A4, Moves = new[]{ 279, 009, 233, 372 }, Index = 53, CanGigantamax = true }, // Machamp - new(30,03,2) { Species = 447, Ability = A4, Moves = new[]{ 280, 232, 317, 523 }, Index = 53 }, // Riolu - new(30,03,2) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 179, 157 }, Index = 53 }, // Falinks - new(30,03,2) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 371, 109 }, Index = 53, CanGigantamax = true }, // Orbeetle - new(30,03,2) { Species = 577, Ability = A4, Moves = new[]{ 473, 086, 360, 283 }, Index = 53 }, // Solosis - new(30,03,2) { Species = 574, Ability = A4, Moves = new[]{ 473, 086, 412, 157 }, Index = 53 }, // Gothita - new(40,05,3) { Species = 068, Ability = A4, Moves = new[]{ 530, 009, 233, 372 }, Index = 53, CanGigantamax = true }, // Machamp - new(40,05,3) { Species = 448, Ability = A4, Moves = new[]{ 612, 232, 444, 089 }, Index = 53 }, // Lucario - new(40,05,3) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 179, 317 }, Index = 53 }, // Falinks - new(40,05,3) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 371, 247 }, Index = 53, CanGigantamax = true }, // Orbeetle - new(40,05,3) { Species = 578, Ability = A4, Moves = new[]{ 094, 086, 360, 247 }, Index = 53 }, // Duosion - new(40,05,3) { Species = 575, Ability = A4, Moves = new[]{ 094, 085, 412, 157 }, Index = 53 }, // Gothorita - new(50,08,4) { Species = 068, Ability = A4, Moves = new[]{ 223, 009, 370, 372 }, Index = 53, CanGigantamax = true }, // Machamp - new(50,08,4) { Species = 448, Ability = A4, Moves = new[]{ 612, 309, 444, 089 }, Index = 53 }, // Lucario - new(50,08,4) { Species = 870, Ability = A4, Moves = new[]{ 442, 224, 179, 317 }, Index = 53 }, // Falinks - new(50,08,4) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 776, 247 }, Index = 53, CanGigantamax = true }, // Orbeetle - new(50,08,4) { Species = 579, Ability = A4, Moves = new[]{ 094, 009, 411, 247 }, Index = 53 }, // Reuniclus - new(50,08,4) { Species = 576, Ability = A4, Moves = new[]{ 094, 085, 412, 322 }, Index = 53 }, // Gothitelle - new(60,10,5) { Species = 068, Ability = A4, Moves = new[]{ 223, 009, 523, 372 }, Index = 53, CanGigantamax = true }, // Machamp - new(60,10,5) { Species = 448, Ability = A4, Moves = new[]{ 370, 309, 444, 089 }, Index = 53 }, // Lucario - new(60,10,5) { Species = 870, Ability = A4, Moves = new[]{ 442, 224, 370, 317 }, Index = 53 }, // Falinks - new(60,10,5) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 776, 412 }, Index = 53, CanGigantamax = true }, // Orbeetle - new(60,10,5) { Species = 579, Ability = A4, Moves = new[]{ 094, 087, 411, 247 }, Index = 53 }, // Reuniclus - new(60,10,5) { Species = 576, Ability = A4, Moves = new[]{ 094, 085, 412, 322 }, Index = 53 }, // Gothitelle + new(17,01,1) { Species = 067, Ability = A4, Moves = new[]{ 279, 009, 490, 067 }, Index = 53 }, // Machoke + new(17,01,1) { Species = 447, Ability = A4, Moves = new[]{ 280, 098, 317, 523 }, Index = 53 }, // Riolu + new(17,01,1) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 249, 157 }, Index = 53 }, // Falinks + new(17,01,1) { Species = 825, Ability = A4, Moves = new[]{ 522, 263, 371, 247 }, Index = 53 }, // Dottler + new(17,01,1) { Species = 577, Ability = A4, Moves = new[]{ 060, 086, 360, 283 }, Index = 53 }, // Solosis + new(17,01,1) { Species = 574, Ability = A4, Moves = new[]{ 060, 086, 412, 157 }, Index = 53 }, // Gothita + new(30,03,2) { Species = 068, Ability = A4, Moves = new[]{ 279, 009, 233, 372 }, Index = 53, CanGigantamax = true }, // Machamp + new(30,03,2) { Species = 447, Ability = A4, Moves = new[]{ 280, 232, 317, 523 }, Index = 53 }, // Riolu + new(30,03,2) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 179, 157 }, Index = 53 }, // Falinks + new(30,03,2) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 371, 109 }, Index = 53, CanGigantamax = true }, // Orbeetle + new(30,03,2) { Species = 577, Ability = A4, Moves = new[]{ 473, 086, 360, 283 }, Index = 53 }, // Solosis + new(30,03,2) { Species = 574, Ability = A4, Moves = new[]{ 473, 086, 412, 157 }, Index = 53 }, // Gothita + new(40,05,3) { Species = 068, Ability = A4, Moves = new[]{ 530, 009, 233, 372 }, Index = 53, CanGigantamax = true }, // Machamp + new(40,05,3) { Species = 448, Ability = A4, Moves = new[]{ 612, 232, 444, 089 }, Index = 53 }, // Lucario + new(40,05,3) { Species = 870, Ability = A4, Moves = new[]{ 442, 029, 179, 317 }, Index = 53 }, // Falinks + new(40,05,3) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 371, 247 }, Index = 53, CanGigantamax = true }, // Orbeetle + new(40,05,3) { Species = 578, Ability = A4, Moves = new[]{ 094, 086, 360, 247 }, Index = 53 }, // Duosion + new(40,05,3) { Species = 575, Ability = A4, Moves = new[]{ 094, 085, 412, 157 }, Index = 53 }, // Gothorita + new(50,08,4) { Species = 068, Ability = A4, Moves = new[]{ 223, 009, 370, 372 }, Index = 53, CanGigantamax = true }, // Machamp + new(50,08,4) { Species = 448, Ability = A4, Moves = new[]{ 612, 309, 444, 089 }, Index = 53 }, // Lucario + new(50,08,4) { Species = 870, Ability = A4, Moves = new[]{ 442, 224, 179, 317 }, Index = 53 }, // Falinks + new(50,08,4) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 776, 247 }, Index = 53, CanGigantamax = true }, // Orbeetle + new(50,08,4) { Species = 579, Ability = A4, Moves = new[]{ 094, 009, 411, 247 }, Index = 53 }, // Reuniclus + new(50,08,4) { Species = 576, Ability = A4, Moves = new[]{ 094, 085, 412, 322 }, Index = 53 }, // Gothitelle + new(60,10,5) { Species = 068, Ability = A4, Moves = new[]{ 223, 009, 523, 372 }, Index = 53, CanGigantamax = true }, // Machamp + new(60,10,5) { Species = 448, Ability = A4, Moves = new[]{ 370, 309, 444, 089 }, Index = 53 }, // Lucario + new(60,10,5) { Species = 870, Ability = A4, Moves = new[]{ 442, 224, 370, 317 }, Index = 53 }, // Falinks + new(60,10,5) { Species = 826, Ability = A4, Moves = new[]{ 405, 277, 776, 412 }, Index = 53, CanGigantamax = true }, // Orbeetle + new(60,10,5) { Species = 579, Ability = A4, Moves = new[]{ 094, 087, 411, 247 }, Index = 53 }, // Reuniclus + new(60,10,5) { Species = 576, Ability = A4, Moves = new[]{ 094, 085, 412, 322 }, Index = 53 }, // Gothitelle - new(17,01,1) { Species = 128, Ability = A4, Moves = new[]{ 033, 157, 030, 371 }, Index = 51 }, // Tauros - new(17,01,1) { Species = 626, Ability = A4, Moves = new[]{ 033, 030, 031, 523 }, Index = 51 }, // Bouffalant - new(17,01,1) { Species = 241, Ability = A4, Moves = new[]{ 707, 033, 023, 205 }, Index = 51 }, // Miltank - new(30,03,2) { Species = 128, Ability = A4, Moves = new[]{ 033, 157, 030, 370 }, Index = 51 }, // Tauros - new(30,03,2) { Species = 626, Ability = A4, Moves = new[]{ 279, 030, 675, 523 }, Index = 51 }, // Bouffalant - new(30,03,2) { Species = 241, Ability = A4, Moves = new[]{ 707, 428, 023, 205 }, Index = 51 }, // Miltank - new(40,05,3) { Species = 128, Ability = A4, Moves = new[]{ 036, 157, 030, 370 }, Index = 51 }, // Tauros - new(40,05,3) { Species = 626, Ability = A4, Moves = new[]{ 279, 543, 675, 523 }, Index = 51 }, // Bouffalant - new(40,05,3) { Species = 241, Ability = A4, Moves = new[]{ 707, 428, 034, 205 }, Index = 51 }, // Miltank - new(50,08,4) { Species = 128, Ability = A4, Moves = new[]{ 034, 157, 030, 370 }, Index = 51 }, // Tauros - new(50,08,4) { Species = 626, Ability = A4, Moves = new[]{ 224, 543, 675, 523 }, Index = 51 }, // Bouffalant - new(50,08,4) { Species = 241, Ability = A4, Moves = new[]{ 707, 428, 034, 583 }, Index = 51 }, // Miltank - new(60,10,5) { Species = 128, Ability = A4, Moves = new[]{ 034, 157, 372, 370 }, Index = 51 }, // Tauros - new(60,10,5) { Species = 128, Ability = A4, Moves = new[]{ 034, 157, 372, 370 }, Index = 51, Shiny = Shiny.Always }, // Tauros - new(60,10,5) { Species = 626, Ability = A4, Moves = new[]{ 224, 543, 675, 089 }, Index = 51 }, // Bouffalant - new(60,10,5) { Species = 241, Ability = A4, Moves = new[]{ 667, 428, 034, 583 }, Index = 51 }, // Miltank + new(17,01,1) { Species = 128, Ability = A4, Moves = new[]{ 033, 157, 030, 371 }, Index = 51 }, // Tauros + new(17,01,1) { Species = 626, Ability = A4, Moves = new[]{ 033, 030, 031, 523 }, Index = 51 }, // Bouffalant + new(17,01,1) { Species = 241, Ability = A4, Moves = new[]{ 707, 033, 023, 205 }, Index = 51 }, // Miltank + new(30,03,2) { Species = 128, Ability = A4, Moves = new[]{ 033, 157, 030, 370 }, Index = 51 }, // Tauros + new(30,03,2) { Species = 626, Ability = A4, Moves = new[]{ 279, 030, 675, 523 }, Index = 51 }, // Bouffalant + new(30,03,2) { Species = 241, Ability = A4, Moves = new[]{ 707, 428, 023, 205 }, Index = 51 }, // Miltank + new(40,05,3) { Species = 128, Ability = A4, Moves = new[]{ 036, 157, 030, 370 }, Index = 51 }, // Tauros + new(40,05,3) { Species = 626, Ability = A4, Moves = new[]{ 279, 543, 675, 523 }, Index = 51 }, // Bouffalant + new(40,05,3) { Species = 241, Ability = A4, Moves = new[]{ 707, 428, 034, 205 }, Index = 51 }, // Miltank + new(50,08,4) { Species = 128, Ability = A4, Moves = new[]{ 034, 157, 030, 370 }, Index = 51 }, // Tauros + new(50,08,4) { Species = 626, Ability = A4, Moves = new[]{ 224, 543, 675, 523 }, Index = 51 }, // Bouffalant + new(50,08,4) { Species = 241, Ability = A4, Moves = new[]{ 707, 428, 034, 583 }, Index = 51 }, // Miltank + new(60,10,5) { Species = 128, Ability = A4, Moves = new[]{ 034, 157, 372, 370 }, Index = 51 }, // Tauros + new(60,10,5) { Species = 128, Ability = A4, Moves = new[]{ 034, 157, 372, 370 }, Index = 51, Shiny = Shiny.Always }, // Tauros + new(60,10,5) { Species = 626, Ability = A4, Moves = new[]{ 224, 543, 675, 089 }, Index = 51 }, // Bouffalant + new(60,10,5) { Species = 241, Ability = A4, Moves = new[]{ 667, 428, 034, 583 }, Index = 51 }, // Miltank - new(17,01,1) { Species = 420, Ability = A4, Moves = new[]{ 033, 572, 074, 670 }, Index = 49 }, // Cherubi - new(17,01,1) { Species = 590, Ability = A4, Moves = new[]{ 078, 492, 310, 412 }, Index = 49 }, // Foongus - new(17,01,1) { Species = 755, Ability = A4, Moves = new[]{ 402, 109, 605, 310 }, Index = 49 }, // Morelull - new(17,01,1) { Species = 819, Ability = A4, Moves = new[]{ 747, 231, 371, 033 }, Index = 49 }, // Skwovet - new(30,03,2) { Species = 420, Ability = A4, Moves = new[]{ 033, 572, 074, 402 }, Index = 49 }, // Cherubi - new(30,03,2) { Species = 590, Ability = A4, Moves = new[]{ 078, 492, 310, 412 }, Index = 49 }, // Foongus - new(30,03,2) { Species = 756, Ability = A4, Moves = new[]{ 402, 109, 605, 310 }, Index = 49 }, // Shiinotic - new(30,03,2) { Species = 819, Ability = A4, Moves = new[]{ 747, 231, 371, 033 }, Index = 49 }, // Skwovet - new(30,03,2) { Species = 820, Ability = A4, Moves = new[]{ 747, 231, 371, 034 }, Index = 49 }, // Greedent - new(40,05,3) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 074, 402 }, Index = 49 }, // Cherubi - new(40,05,3) { Species = 591, Ability = A4, Moves = new[]{ 078, 492, 092, 412 }, Index = 49 }, // Amoonguss - new(40,05,3) { Species = 756, Ability = A4, Moves = new[]{ 402, 585, 605, 310 }, Index = 49 }, // Shiinotic - new(40,05,3) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 044 }, Index = 49 }, // Skwovet - new(40,05,3) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 424 }, Index = 49 }, // Greedent - new(50,08,4) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 605, 402 }, Index = 49 }, // Cherubi - new(50,08,4) { Species = 591, Ability = A4, Moves = new[]{ 188, 492, 092, 412 }, Index = 49 }, // Amoonguss - new(50,08,4) { Species = 756, Ability = A4, Moves = new[]{ 412, 585, 605, 188 }, Index = 49 }, // Shiinotic - new(50,08,4) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 331 }, Index = 49 }, // Skwovet - new(50,08,4) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 424 }, Index = 49 }, // Greedent - new(60,10,5) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 605, 412 }, Index = 49 }, // Cherubi - new(60,10,5) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 089 }, Index = 49, Shiny = Shiny.Always }, // Greedent - new(60,10,5) { Species = 756, Ability = A4, Moves = new[]{ 147, 585, 605, 188 }, Index = 49 }, // Shiinotic - new(60,10,5) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 034 }, Index = 49 }, // Skwovet - new(60,10,5) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 034 }, Index = 49, Shiny = Shiny.Always }, // Skwovet - new(60,10,5) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 089 }, Index = 49 }, // Greedent + new(17,01,1) { Species = 420, Ability = A4, Moves = new[]{ 033, 572, 074, 670 }, Index = 49 }, // Cherubi + new(17,01,1) { Species = 590, Ability = A4, Moves = new[]{ 078, 492, 310, 412 }, Index = 49 }, // Foongus + new(17,01,1) { Species = 755, Ability = A4, Moves = new[]{ 402, 109, 605, 310 }, Index = 49 }, // Morelull + new(17,01,1) { Species = 819, Ability = A4, Moves = new[]{ 747, 231, 371, 033 }, Index = 49 }, // Skwovet + new(30,03,2) { Species = 420, Ability = A4, Moves = new[]{ 033, 572, 074, 402 }, Index = 49 }, // Cherubi + new(30,03,2) { Species = 590, Ability = A4, Moves = new[]{ 078, 492, 310, 412 }, Index = 49 }, // Foongus + new(30,03,2) { Species = 756, Ability = A4, Moves = new[]{ 402, 109, 605, 310 }, Index = 49 }, // Shiinotic + new(30,03,2) { Species = 819, Ability = A4, Moves = new[]{ 747, 231, 371, 033 }, Index = 49 }, // Skwovet + new(30,03,2) { Species = 820, Ability = A4, Moves = new[]{ 747, 231, 371, 034 }, Index = 49 }, // Greedent + new(40,05,3) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 074, 402 }, Index = 49 }, // Cherubi + new(40,05,3) { Species = 591, Ability = A4, Moves = new[]{ 078, 492, 092, 412 }, Index = 49 }, // Amoonguss + new(40,05,3) { Species = 756, Ability = A4, Moves = new[]{ 402, 585, 605, 310 }, Index = 49 }, // Shiinotic + new(40,05,3) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 044 }, Index = 49 }, // Skwovet + new(40,05,3) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 424 }, Index = 49 }, // Greedent + new(50,08,4) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 605, 402 }, Index = 49 }, // Cherubi + new(50,08,4) { Species = 591, Ability = A4, Moves = new[]{ 188, 492, 092, 412 }, Index = 49 }, // Amoonguss + new(50,08,4) { Species = 756, Ability = A4, Moves = new[]{ 412, 585, 605, 188 }, Index = 49 }, // Shiinotic + new(50,08,4) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 331 }, Index = 49 }, // Skwovet + new(50,08,4) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 424 }, Index = 49 }, // Greedent + new(60,10,5) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 605, 412 }, Index = 49 }, // Cherubi + new(60,10,5) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 089 }, Index = 49, Shiny = Shiny.Always }, // Greedent + new(60,10,5) { Species = 756, Ability = A4, Moves = new[]{ 147, 585, 605, 188 }, Index = 49 }, // Shiinotic + new(60,10,5) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 034 }, Index = 49 }, // Skwovet + new(60,10,5) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 034 }, Index = 49, Shiny = Shiny.Always }, // Skwovet + new(60,10,5) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 089 }, Index = 49 }, // Greedent - new(17,01,1) { Species = 884, Ability = A4, Moves = new[]{ 232, 043, 468, 249 }, Index = 48, CanGigantamax = true }, // Duraludon - new(17,01,1) { Species = 610, Ability = A4, Moves = new[]{ 044, 163, 372, 010 }, Index = 48 }, // Axew - new(17,01,1) { Species = 704, Ability = A4, Moves = new[]{ 225, 352, 033, 175 }, Index = 48 }, // Goomy - new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 48 }, // Munchlax - new(17,01,1) { Species = 759, Ability = A4, Moves = new[]{ 693, 371, 608, 033 }, Index = 48 }, // Stufful - new(17,01,1) { Species = 572, Ability = A4, Moves = new[]{ 497, 204, 402, 001 }, Index = 48 }, // Minccino - new(30,03,2) { Species = 884, Ability = A4, Moves = new[]{ 232, 784, 468, 249 }, Index = 48, CanGigantamax = true }, // Duraludon - new(30,03,2) { Species = 610, Ability = A4, Moves = new[]{ 337, 163, 242, 530 }, Index = 48 }, // Axew - new(30,03,2) { Species = 704, Ability = A4, Moves = new[]{ 225, 352, 033, 341 }, Index = 48 }, // Goomy - new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 48, CanGigantamax = true }, // Snorlax - new(30,03,2) { Species = 759, Ability = A4, Moves = new[]{ 693, 371, 359, 036 }, Index = 48 }, // Stufful - new(30,03,2) { Species = 572, Ability = A4, Moves = new[]{ 497, 231, 402, 129 }, Index = 48 }, // Minccino - new(40,05,3) { Species = 884, Ability = A4, Moves = new[]{ 232, 525, 085, 249 }, Index = 48, CanGigantamax = true }, // Duraludon - new(40,05,3) { Species = 611, Ability = A4, Moves = new[]{ 406, 231, 242, 530 }, Index = 48 }, // Fraxure - new(40,05,3) { Species = 705, Ability = A4, Moves = new[]{ 406, 352, 491, 341 }, Index = 48 }, // Sliggoo - new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 48, CanGigantamax = true }, // Snorlax - new(40,05,3) { Species = 760, Ability = A4, Moves = new[]{ 693, 034, 359, 036 }, Index = 48 }, // Bewear - new(40,05,3) { Species = 573, Ability = A4, Moves = new[]{ 331, 231, 350, 129 }, Index = 48 }, // Cinccino - new(50,08,4) { Species = 884, Ability = A4, Moves = new[]{ 232, 406, 085, 776 }, Index = 48, CanGigantamax = true }, // Duraludon - new(50,08,4) { Species = 612, Ability = A4, Moves = new[]{ 406, 231, 370, 530 }, Index = 48 }, // Haxorus - new(50,08,4) { Species = 706, Ability = A4, Moves = new[]{ 406, 034, 491, 126 }, Index = 48 }, // Goodra - new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 48, CanGigantamax = true }, // Snorlax - new(50,08,4) { Species = 760, Ability = A4, Moves = new[]{ 663, 034, 359, 009 }, Index = 48 }, // Bewear - new(50,08,4) { Species = 573, Ability = A4, Moves = new[]{ 331, 231, 350, 304 }, Index = 48 }, // Cinccino - new(60,10,5) { Species = 884, Ability = A4, Moves = new[]{ 430, 406, 085, 776 }, Index = 48, CanGigantamax = true }, // Duraludon - new(60,10,5) { Species = 612, Ability = A4, Moves = new[]{ 200, 231, 370, 089 }, Index = 48 }, // Haxorus - new(60,10,5) { Species = 706, Ability = A4, Moves = new[]{ 406, 438, 482, 126 }, Index = 48 }, // Goodra - new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 48, CanGigantamax = true }, // Snorlax - new(60,10,5) { Species = 760, Ability = A4, Moves = new[]{ 663, 038, 276, 009 }, Index = 48 }, // Bewear - new(60,10,5) { Species = 573, Ability = A4, Moves = new[]{ 402, 231, 350, 304 }, Index = 48 }, // Cinccino + new(17,01,1) { Species = 884, Ability = A4, Moves = new[]{ 232, 043, 468, 249 }, Index = 48, CanGigantamax = true }, // Duraludon + new(17,01,1) { Species = 610, Ability = A4, Moves = new[]{ 044, 163, 372, 010 }, Index = 48 }, // Axew + new(17,01,1) { Species = 704, Ability = A4, Moves = new[]{ 225, 352, 033, 175 }, Index = 48 }, // Goomy + new(17,01,1) { Species = 446, Ability = A4, Moves = new[]{ 033, 044, 122, 111 }, Index = 48 }, // Munchlax + new(17,01,1) { Species = 759, Ability = A4, Moves = new[]{ 693, 371, 608, 033 }, Index = 48 }, // Stufful + new(17,01,1) { Species = 572, Ability = A4, Moves = new[]{ 497, 204, 402, 001 }, Index = 48 }, // Minccino + new(30,03,2) { Species = 884, Ability = A4, Moves = new[]{ 232, 784, 468, 249 }, Index = 48, CanGigantamax = true }, // Duraludon + new(30,03,2) { Species = 610, Ability = A4, Moves = new[]{ 337, 163, 242, 530 }, Index = 48 }, // Axew + new(30,03,2) { Species = 704, Ability = A4, Moves = new[]{ 225, 352, 033, 341 }, Index = 48 }, // Goomy + new(30,03,2) { Species = 143, Ability = A4, Moves = new[]{ 034, 242, 118, 111 }, Index = 48, CanGigantamax = true }, // Snorlax + new(30,03,2) { Species = 759, Ability = A4, Moves = new[]{ 693, 371, 359, 036 }, Index = 48 }, // Stufful + new(30,03,2) { Species = 572, Ability = A4, Moves = new[]{ 497, 231, 402, 129 }, Index = 48 }, // Minccino + new(40,05,3) { Species = 884, Ability = A4, Moves = new[]{ 232, 525, 085, 249 }, Index = 48, CanGigantamax = true }, // Duraludon + new(40,05,3) { Species = 611, Ability = A4, Moves = new[]{ 406, 231, 242, 530 }, Index = 48 }, // Fraxure + new(40,05,3) { Species = 705, Ability = A4, Moves = new[]{ 406, 352, 491, 341 }, Index = 48 }, // Sliggoo + new(40,05,3) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 242, 281 }, Index = 48, CanGigantamax = true }, // Snorlax + new(40,05,3) { Species = 760, Ability = A4, Moves = new[]{ 693, 034, 359, 036 }, Index = 48 }, // Bewear + new(40,05,3) { Species = 573, Ability = A4, Moves = new[]{ 331, 231, 350, 129 }, Index = 48 }, // Cinccino + new(50,08,4) { Species = 884, Ability = A4, Moves = new[]{ 232, 406, 085, 776 }, Index = 48, CanGigantamax = true }, // Duraludon + new(50,08,4) { Species = 612, Ability = A4, Moves = new[]{ 406, 231, 370, 530 }, Index = 48 }, // Haxorus + new(50,08,4) { Species = 706, Ability = A4, Moves = new[]{ 406, 034, 491, 126 }, Index = 48 }, // Goodra + new(50,08,4) { Species = 143, Ability = A4, Moves = new[]{ 034, 667, 280, 523 }, Index = 48, CanGigantamax = true }, // Snorlax + new(50,08,4) { Species = 760, Ability = A4, Moves = new[]{ 663, 034, 359, 009 }, Index = 48 }, // Bewear + new(50,08,4) { Species = 573, Ability = A4, Moves = new[]{ 331, 231, 350, 304 }, Index = 48 }, // Cinccino + new(60,10,5) { Species = 884, Ability = A4, Moves = new[]{ 430, 406, 085, 776 }, Index = 48, CanGigantamax = true }, // Duraludon + new(60,10,5) { Species = 612, Ability = A4, Moves = new[]{ 200, 231, 370, 089 }, Index = 48 }, // Haxorus + new(60,10,5) { Species = 706, Ability = A4, Moves = new[]{ 406, 438, 482, 126 }, Index = 48 }, // Goodra + new(60,10,5) { Species = 143, Ability = A4, Moves = new[]{ 034, 442, 242, 428 }, Index = 48, CanGigantamax = true }, // Snorlax + new(60,10,5) { Species = 760, Ability = A4, Moves = new[]{ 663, 038, 276, 009 }, Index = 48 }, // Bewear + new(60,10,5) { Species = 573, Ability = A4, Moves = new[]{ 402, 231, 350, 304 }, Index = 48 }, // Cinccino - new(17,01,1) { Species = 037, Ability = A4, Moves = new[]{ 420, 196, 039, 577 }, Index = 46, Form = 1 }, // Vulpix-1 - new(17,01,1) { Species = 124, Ability = A4, Moves = new[]{ 181, 001, 093, 122 }, Index = 46 }, // Jynx - new(17,01,1) { Species = 225, Ability = A4, Moves = new[]{ 217, 229, 098, 420 }, Index = 46 }, // Delibird - new(17,01,1) { Species = 607, Ability = A4, Moves = new[]{ 310, 052, 506, 123 }, Index = 46 }, // Litwick - new(17,01,1) { Species = 873, Ability = A4, Moves = new[]{ 522, 078, 181, 432 }, Index = 46 }, // Frosmoth - new(30,03,2) { Species = 037, Ability = A4, Moves = new[]{ 420, 058, 326, 577 }, Index = 46, Form = 1 }, // Vulpix-1 - new(30,03,2) { Species = 124, Ability = A4, Moves = new[]{ 181, 001, 093, 313 }, Index = 46 }, // Jynx - new(30,03,2) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 034, 693 }, Index = 46 }, // Delibird - new(30,03,2) { Species = 608, Ability = A4, Moves = new[]{ 310, 261, 083, 123 }, Index = 46 }, // Lampent - new(30,03,2) { Species = 873, Ability = A4, Moves = new[]{ 522, 078, 062, 432 }, Index = 46 }, // Frosmoth - new(40,05,3) { Species = 037, Ability = A4, Moves = new[]{ 062, 058, 326, 577 }, Index = 46, Form = 1 }, // Vulpix-1 - new(40,05,3) { Species = 124, Ability = A4, Moves = new[]{ 058, 142, 094, 247 }, Index = 46 }, // Jynx - new(40,05,3) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 280, 196 }, Index = 46 }, // Delibird - new(40,05,3) { Species = 609, Ability = A4, Moves = new[]{ 247, 261, 257, 094 }, Index = 46 }, // Chandelure - new(40,05,3) { Species = 873, Ability = A4, Moves = new[]{ 405, 403, 062, 432 }, Index = 46 }, // Frosmoth - new(50,08,4) { Species = 037, Ability = A4, Moves = new[]{ 694, 058, 326, 577 }, Index = 46, Form = 1 }, // Vulpix-1 - new(50,08,4) { Species = 124, Ability = A4, Moves = new[]{ 058, 142, 094, 247 }, Index = 46 }, // Jynx - new(50,08,4) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 034, 280 }, Index = 46 }, // Delibird - new(50,08,4) { Species = 609, Ability = A4, Moves = new[]{ 247, 261, 315, 094 }, Index = 46 }, // Chandelure - new(50,08,4) { Species = 873, Ability = A4, Moves = new[]{ 405, 403, 058, 297 }, Index = 46 }, // Frosmoth - new(60,10,5) { Species = 037, Ability = A4, Moves = new[]{ 694, 059, 326, 577 }, Index = 46, Form = 1 }, // Vulpix-1 - new(60,10,5) { Species = 037, Ability = A4, Moves = new[]{ 694, 059, 326, 577 }, Index = 46, Form = 1, Shiny = Shiny.Always }, // Vulpix-1 - new(60,10,5) { Species = 124, Ability = A4, Moves = new[]{ 058, 142, 094, 247 }, Index = 46 }, // Jynx - new(60,10,5) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 065, 280 }, Index = 46 }, // Delibird - new(60,10,5) { Species = 609, Ability = A4, Moves = new[]{ 247, 412, 315, 094 }, Index = 46 }, // Chandelure - new(60,10,5) { Species = 873, Ability = A4, Moves = new[]{ 405, 403, 058, 542 }, Index = 46 }, // Frosmoth + new(17,01,1) { Species = 037, Ability = A4, Moves = new[]{ 420, 196, 039, 577 }, Index = 46, Form = 1 }, // Vulpix-1 + new(17,01,1) { Species = 124, Ability = A4, Moves = new[]{ 181, 001, 093, 122 }, Index = 46 }, // Jynx + new(17,01,1) { Species = 225, Ability = A4, Moves = new[]{ 217, 229, 098, 420 }, Index = 46 }, // Delibird + new(17,01,1) { Species = 607, Ability = A4, Moves = new[]{ 310, 052, 506, 123 }, Index = 46 }, // Litwick + new(17,01,1) { Species = 873, Ability = A4, Moves = new[]{ 522, 078, 181, 432 }, Index = 46 }, // Frosmoth + new(30,03,2) { Species = 037, Ability = A4, Moves = new[]{ 420, 058, 326, 577 }, Index = 46, Form = 1 }, // Vulpix-1 + new(30,03,2) { Species = 124, Ability = A4, Moves = new[]{ 181, 001, 093, 313 }, Index = 46 }, // Jynx + new(30,03,2) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 034, 693 }, Index = 46 }, // Delibird + new(30,03,2) { Species = 608, Ability = A4, Moves = new[]{ 310, 261, 083, 123 }, Index = 46 }, // Lampent + new(30,03,2) { Species = 873, Ability = A4, Moves = new[]{ 522, 078, 062, 432 }, Index = 46 }, // Frosmoth + new(40,05,3) { Species = 037, Ability = A4, Moves = new[]{ 062, 058, 326, 577 }, Index = 46, Form = 1 }, // Vulpix-1 + new(40,05,3) { Species = 124, Ability = A4, Moves = new[]{ 058, 142, 094, 247 }, Index = 46 }, // Jynx + new(40,05,3) { Species = 225, Ability = A4, Moves = new[]{ 217, 065, 280, 196 }, Index = 46 }, // Delibird + new(40,05,3) { Species = 609, Ability = A4, Moves = new[]{ 247, 261, 257, 094 }, Index = 46 }, // Chandelure + new(40,05,3) { Species = 873, Ability = A4, Moves = new[]{ 405, 403, 062, 432 }, Index = 46 }, // Frosmoth + new(50,08,4) { Species = 037, Ability = A4, Moves = new[]{ 694, 058, 326, 577 }, Index = 46, Form = 1 }, // Vulpix-1 + new(50,08,4) { Species = 124, Ability = A4, Moves = new[]{ 058, 142, 094, 247 }, Index = 46 }, // Jynx + new(50,08,4) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 034, 280 }, Index = 46 }, // Delibird + new(50,08,4) { Species = 609, Ability = A4, Moves = new[]{ 247, 261, 315, 094 }, Index = 46 }, // Chandelure + new(50,08,4) { Species = 873, Ability = A4, Moves = new[]{ 405, 403, 058, 297 }, Index = 46 }, // Frosmoth + new(60,10,5) { Species = 037, Ability = A4, Moves = new[]{ 694, 059, 326, 577 }, Index = 46, Form = 1 }, // Vulpix-1 + new(60,10,5) { Species = 037, Ability = A4, Moves = new[]{ 694, 059, 326, 577 }, Index = 46, Form = 1, Shiny = Shiny.Always }, // Vulpix-1 + new(60,10,5) { Species = 124, Ability = A4, Moves = new[]{ 058, 142, 094, 247 }, Index = 46 }, // Jynx + new(60,10,5) { Species = 225, Ability = A4, Moves = new[]{ 217, 059, 065, 280 }, Index = 46 }, // Delibird + new(60,10,5) { Species = 609, Ability = A4, Moves = new[]{ 247, 412, 315, 094 }, Index = 46 }, // Chandelure + new(60,10,5) { Species = 873, Ability = A4, Moves = new[]{ 405, 403, 058, 542 }, Index = 46 }, // Frosmoth - new(17,01,1) { Species = 131, Ability = A4, Moves = new[]{ 055, 420, 045, 047 }, Index = 45, CanGigantamax = true }, // Lapras - new(17,01,1) { Species = 712, Ability = A4, Moves = new[]{ 181, 196, 033, 106 }, Index = 45 }, // Bergmite - new(17,01,1) { Species = 461, Ability = A4, Moves = new[]{ 420, 372, 232, 279 }, Index = 45 }, // Weavile - new(17,01,1) { Species = 850, Ability = A4, Moves = new[]{ 172, 044, 035, 052 }, Index = 45 }, // Sizzlipede - new(17,01,1) { Species = 776, Ability = A4, Moves = new[]{ 175, 123, 033, 052 }, Index = 45 }, // Turtonator - new(17,01,1) { Species = 077, Ability = A4, Moves = new[]{ 488, 045, 039, 052 }, Index = 45 }, // Ponyta - new(30,03,2) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 047 }, Index = 45, CanGigantamax = true }, // Lapras - new(30,03,2) { Species = 712, Ability = A4, Moves = new[]{ 157, 423, 033, 044 }, Index = 45 }, // Bergmite - new(30,03,2) { Species = 461, Ability = A4, Moves = new[]{ 420, 372, 232, 279 }, Index = 45 }, // Weavile - new(30,03,2) { Species = 851, Ability = A4, Moves = new[]{ 172, 404, 422, 044 }, Index = 45, CanGigantamax = true }, // Centiskorch - new(30,03,2) { Species = 776, Ability = A4, Moves = new[]{ 406, 123, 033, 052 }, Index = 45 }, // Turtonator - new(30,03,2) { Species = 077, Ability = A4, Moves = new[]{ 488, 023, 583, 097 }, Index = 45 }, // Ponyta - new(40,05,3) { Species = 131, Ability = A4, Moves = new[]{ 352, 196, 109, 047 }, Index = 45, CanGigantamax = true }, // Lapras - new(40,05,3) { Species = 713, Ability = A4, Moves = new[]{ 157, 423, 036, 044 }, Index = 45 }, // Avalugg - new(40,05,3) { Species = 461, Ability = A4, Moves = new[]{ 420, 468, 232, 279 }, Index = 45 }, // Weavile - new(40,05,3) { Species = 851, Ability = A4, Moves = new[]{ 424, 404, 422, 044 }, Index = 45, CanGigantamax = true }, // Centiskorch - new(40,05,3) { Species = 776, Ability = A4, Moves = new[]{ 406, 776, 034, 053 }, Index = 45 }, // Turtonator - new(40,05,3) { Species = 078, Ability = A4, Moves = new[]{ 172, 023, 583, 224 }, Index = 45 }, // Rapidash - new(50,08,4) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 058, 047 }, Index = 45, CanGigantamax = true }, // Lapras - new(50,08,4) { Species = 713, Ability = A4, Moves = new[]{ 776, 059, 036, 044 }, Index = 45 }, // Avalugg - new(50,08,4) { Species = 461, Ability = A4, Moves = new[]{ 420, 468, 232, 279 }, Index = 45 }, // Weavile - new(50,08,4) { Species = 851, Ability = A4, Moves = new[]{ 680, 404, 422, 044 }, Index = 45, CanGigantamax = true }, // Centiskorch - new(50,08,4) { Species = 776, Ability = A4, Moves = new[]{ 406, 776, 504, 053 }, Index = 45 }, // Turtonator - new(50,08,4) { Species = 078, Ability = A4, Moves = new[]{ 517, 528, 583, 224 }, Index = 45 }, // Rapidash - new(60,10,5) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 058, 329 }, Index = 45, CanGigantamax = true }, // Lapras - new(60,10,5) { Species = 713, Ability = A4, Moves = new[]{ 776, 059, 038, 044 }, Index = 45 }, // Avalugg - new(60,10,5) { Species = 461, Ability = A4, Moves = new[]{ 420, 400, 232, 279 }, Index = 45 }, // Weavile - new(60,10,5) { Species = 851, Ability = A4, Moves = new[]{ 680, 679, 422, 044 }, Index = 45, CanGigantamax = true }, // Centiskorch - new(60,10,5) { Species = 776, Ability = A4, Moves = new[]{ 434, 776, 504, 053 }, Index = 45 }, // Turtonator - new(60,10,5) { Species = 078, Ability = A4, Moves = new[]{ 394, 528, 583, 224 }, Index = 45 }, // Rapidash + new(17,01,1) { Species = 131, Ability = A4, Moves = new[]{ 055, 420, 045, 047 }, Index = 45, CanGigantamax = true }, // Lapras + new(17,01,1) { Species = 712, Ability = A4, Moves = new[]{ 181, 196, 033, 106 }, Index = 45 }, // Bergmite + new(17,01,1) { Species = 461, Ability = A4, Moves = new[]{ 420, 372, 232, 279 }, Index = 45 }, // Weavile + new(17,01,1) { Species = 850, Ability = A4, Moves = new[]{ 172, 044, 035, 052 }, Index = 45 }, // Sizzlipede + new(17,01,1) { Species = 776, Ability = A4, Moves = new[]{ 175, 123, 033, 052 }, Index = 45 }, // Turtonator + new(17,01,1) { Species = 077, Ability = A4, Moves = new[]{ 488, 045, 039, 052 }, Index = 45 }, // Ponyta + new(30,03,2) { Species = 131, Ability = A4, Moves = new[]{ 352, 420, 109, 047 }, Index = 45, CanGigantamax = true }, // Lapras + new(30,03,2) { Species = 712, Ability = A4, Moves = new[]{ 157, 423, 033, 044 }, Index = 45 }, // Bergmite + new(30,03,2) { Species = 461, Ability = A4, Moves = new[]{ 420, 372, 232, 279 }, Index = 45 }, // Weavile + new(30,03,2) { Species = 851, Ability = A4, Moves = new[]{ 172, 404, 422, 044 }, Index = 45, CanGigantamax = true }, // Centiskorch + new(30,03,2) { Species = 776, Ability = A4, Moves = new[]{ 406, 123, 033, 052 }, Index = 45 }, // Turtonator + new(30,03,2) { Species = 077, Ability = A4, Moves = new[]{ 488, 023, 583, 097 }, Index = 45 }, // Ponyta + new(40,05,3) { Species = 131, Ability = A4, Moves = new[]{ 352, 196, 109, 047 }, Index = 45, CanGigantamax = true }, // Lapras + new(40,05,3) { Species = 713, Ability = A4, Moves = new[]{ 157, 423, 036, 044 }, Index = 45 }, // Avalugg + new(40,05,3) { Species = 461, Ability = A4, Moves = new[]{ 420, 468, 232, 279 }, Index = 45 }, // Weavile + new(40,05,3) { Species = 851, Ability = A4, Moves = new[]{ 424, 404, 422, 044 }, Index = 45, CanGigantamax = true }, // Centiskorch + new(40,05,3) { Species = 776, Ability = A4, Moves = new[]{ 406, 776, 034, 053 }, Index = 45 }, // Turtonator + new(40,05,3) { Species = 078, Ability = A4, Moves = new[]{ 172, 023, 583, 224 }, Index = 45 }, // Rapidash + new(50,08,4) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 058, 047 }, Index = 45, CanGigantamax = true }, // Lapras + new(50,08,4) { Species = 713, Ability = A4, Moves = new[]{ 776, 059, 036, 044 }, Index = 45 }, // Avalugg + new(50,08,4) { Species = 461, Ability = A4, Moves = new[]{ 420, 468, 232, 279 }, Index = 45 }, // Weavile + new(50,08,4) { Species = 851, Ability = A4, Moves = new[]{ 680, 404, 422, 044 }, Index = 45, CanGigantamax = true }, // Centiskorch + new(50,08,4) { Species = 776, Ability = A4, Moves = new[]{ 406, 776, 504, 053 }, Index = 45 }, // Turtonator + new(50,08,4) { Species = 078, Ability = A4, Moves = new[]{ 517, 528, 583, 224 }, Index = 45 }, // Rapidash + new(60,10,5) { Species = 131, Ability = A4, Moves = new[]{ 057, 196, 058, 329 }, Index = 45, CanGigantamax = true }, // Lapras + new(60,10,5) { Species = 713, Ability = A4, Moves = new[]{ 776, 059, 038, 044 }, Index = 45 }, // Avalugg + new(60,10,5) { Species = 461, Ability = A4, Moves = new[]{ 420, 400, 232, 279 }, Index = 45 }, // Weavile + new(60,10,5) { Species = 851, Ability = A4, Moves = new[]{ 680, 679, 422, 044 }, Index = 45, CanGigantamax = true }, // Centiskorch + new(60,10,5) { Species = 776, Ability = A4, Moves = new[]{ 434, 776, 504, 053 }, Index = 45 }, // Turtonator + new(60,10,5) { Species = 078, Ability = A4, Moves = new[]{ 394, 528, 583, 224 }, Index = 45 }, // Rapidash - new(17,01,1) { Species = 420, Ability = A4, Moves = new[]{ 033, 572, 074, 670 }, Index = 43 }, // Cherubi - new(17,01,1) { Species = 590, Ability = A4, Moves = new[]{ 078, 492, 310, 412 }, Index = 43 }, // Foongus - new(17,01,1) { Species = 755, Ability = A4, Moves = new[]{ 402, 109, 605, 310 }, Index = 43 }, // Morelull - new(17,01,1) { Species = 819, Ability = A4, Moves = new[]{ 747, 231, 371, 033 }, Index = 43 }, // Skwovet - new(30,03,2) { Species = 420, Ability = A4, Moves = new[]{ 033, 572, 074, 402 }, Index = 43 }, // Cherubi - new(30,03,2) { Species = 590, Ability = A4, Moves = new[]{ 078, 492, 310, 412 }, Index = 43 }, // Foongus - new(30,03,2) { Species = 756, Ability = A4, Moves = new[]{ 402, 109, 605, 310 }, Index = 43 }, // Shiinotic - new(30,03,2) { Species = 819, Ability = A4, Moves = new[]{ 747, 231, 371, 033 }, Index = 43 }, // Skwovet - new(30,03,2) { Species = 820, Ability = A4, Moves = new[]{ 747, 231, 371, 034 }, Index = 43 }, // Greedent - new(40,05,3) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 074, 402 }, Index = 43 }, // Cherubi - new(40,05,3) { Species = 591, Ability = A4, Moves = new[]{ 078, 492, 092, 412 }, Index = 43 }, // Amoonguss - new(40,05,3) { Species = 756, Ability = A4, Moves = new[]{ 402, 585, 605, 310 }, Index = 43 }, // Shiinotic - new(40,05,3) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 044 }, Index = 43 }, // Skwovet - new(40,05,3) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 424 }, Index = 43 }, // Greedent - new(50,08,4) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 605, 402 }, Index = 43 }, // Cherubi - new(50,08,4) { Species = 591, Ability = A4, Moves = new[]{ 188, 492, 092, 412 }, Index = 43 }, // Amoonguss - new(50,08,4) { Species = 756, Ability = A4, Moves = new[]{ 412, 585, 605, 188 }, Index = 43 }, // Shiinotic - new(50,08,4) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 331 }, Index = 43 }, // Skwovet - new(50,08,4) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 424 }, Index = 43 }, // Greedent - new(60,10,5) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 605, 412 }, Index = 43 }, // Cherubi - new(60,10,5) { Species = 591, Ability = A4, Moves = new[]{ 147, 492, 092, 412 }, Index = 43 }, // Amoonguss - new(60,10,5) { Species = 756, Ability = A4, Moves = new[]{ 147, 585, 605, 188 }, Index = 43 }, // Shiinotic - new(60,10,5) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 034 }, Index = 43 }, // Skwovet - new(60,10,5) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 034 }, Index = 43, Shiny = Shiny.Always }, // Skwovet - new(60,10,5) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 089 }, Index = 43 }, // Greedent + new(17,01,1) { Species = 420, Ability = A4, Moves = new[]{ 033, 572, 074, 670 }, Index = 43 }, // Cherubi + new(17,01,1) { Species = 590, Ability = A4, Moves = new[]{ 078, 492, 310, 412 }, Index = 43 }, // Foongus + new(17,01,1) { Species = 755, Ability = A4, Moves = new[]{ 402, 109, 605, 310 }, Index = 43 }, // Morelull + new(17,01,1) { Species = 819, Ability = A4, Moves = new[]{ 747, 231, 371, 033 }, Index = 43 }, // Skwovet + new(30,03,2) { Species = 420, Ability = A4, Moves = new[]{ 033, 572, 074, 402 }, Index = 43 }, // Cherubi + new(30,03,2) { Species = 590, Ability = A4, Moves = new[]{ 078, 492, 310, 412 }, Index = 43 }, // Foongus + new(30,03,2) { Species = 756, Ability = A4, Moves = new[]{ 402, 109, 605, 310 }, Index = 43 }, // Shiinotic + new(30,03,2) { Species = 819, Ability = A4, Moves = new[]{ 747, 231, 371, 033 }, Index = 43 }, // Skwovet + new(30,03,2) { Species = 820, Ability = A4, Moves = new[]{ 747, 231, 371, 034 }, Index = 43 }, // Greedent + new(40,05,3) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 074, 402 }, Index = 43 }, // Cherubi + new(40,05,3) { Species = 591, Ability = A4, Moves = new[]{ 078, 492, 092, 412 }, Index = 43 }, // Amoonguss + new(40,05,3) { Species = 756, Ability = A4, Moves = new[]{ 402, 585, 605, 310 }, Index = 43 }, // Shiinotic + new(40,05,3) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 044 }, Index = 43 }, // Skwovet + new(40,05,3) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 424 }, Index = 43 }, // Greedent + new(50,08,4) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 605, 402 }, Index = 43 }, // Cherubi + new(50,08,4) { Species = 591, Ability = A4, Moves = new[]{ 188, 492, 092, 412 }, Index = 43 }, // Amoonguss + new(50,08,4) { Species = 756, Ability = A4, Moves = new[]{ 412, 585, 605, 188 }, Index = 43 }, // Shiinotic + new(50,08,4) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 331 }, Index = 43 }, // Skwovet + new(50,08,4) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 424 }, Index = 43 }, // Greedent + new(60,10,5) { Species = 420, Ability = A4, Moves = new[]{ 311, 572, 605, 412 }, Index = 43 }, // Cherubi + new(60,10,5) { Species = 591, Ability = A4, Moves = new[]{ 147, 492, 092, 412 }, Index = 43 }, // Amoonguss + new(60,10,5) { Species = 756, Ability = A4, Moves = new[]{ 147, 585, 605, 188 }, Index = 43 }, // Shiinotic + new(60,10,5) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 034 }, Index = 43 }, // Skwovet + new(60,10,5) { Species = 819, Ability = A4, Moves = new[]{ 747, 360, 371, 034 }, Index = 43, Shiny = Shiny.Always }, // Skwovet + new(60,10,5) { Species = 820, Ability = A4, Moves = new[]{ 747, 360, 371, 089 }, Index = 43 }, // Greedent - new(17,01,1) { Species = 012, Ability = A4, Moves = new[]{ 081, 060, 016, 079 }, Index = 42, CanGigantamax = true }, // Butterfree - new(17,01,1) { Species = 213, Ability = A4, Moves = new[]{ 088, 474, 414, 522 }, Index = 42 }, // Shuckle - new(17,01,1) { Species = 290, Ability = A4, Moves = new[]{ 189, 206, 010, 106 }, Index = 42 }, // Nincada - new(17,01,1) { Species = 568, Ability = A4, Moves = new[]{ 390, 133, 491, 001 }, Index = 42 }, // Trubbish - new(17,01,1) { Species = 043, Ability = A4, Moves = new[]{ 078, 077, 051, 230 }, Index = 42 }, // Oddish - new(17,01,1) { Species = 453, Ability = A4, Moves = new[]{ 040, 269, 279, 189 }, Index = 42 }, // Croagunk - new(30,03,2) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 42, CanGigantamax = true }, // Butterfree - new(30,03,2) { Species = 213, Ability = A4, Moves = new[]{ 088, 474, 414, 522 }, Index = 42 }, // Shuckle - new(30,03,2) { Species = 291, Ability = A4, Moves = new[]{ 210, 206, 332, 232 }, Index = 42 }, // Ninjask - new(30,03,2) { Species = 568, Ability = A4, Moves = new[]{ 092, 133, 491, 036 }, Index = 42 }, // Trubbish - new(30,03,2) { Species = 045, Ability = A4, Moves = new[]{ 080, 585, 051, 230 }, Index = 42 }, // Vileplume - new(30,03,2) { Species = 453, Ability = A4, Moves = new[]{ 474, 389, 279, 189 }, Index = 42 }, // Croagunk - new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 42, CanGigantamax = true }, // Butterfree - new(40,05,3) { Species = 213, Ability = A4, Moves = new[]{ 157, 188, 089, 522 }, Index = 42 }, // Shuckle - new(40,05,3) { Species = 291, Ability = A4, Moves = new[]{ 210, 206, 332, 232 }, Index = 42 }, // Ninjask - new(40,05,3) { Species = 569, Ability = A4, Moves = new[]{ 188, 133, 034, 707 }, Index = 42, CanGigantamax = true }, // Garbodor - new(40,05,3) { Species = 045, Ability = A4, Moves = new[]{ 080, 585, 051, 230 }, Index = 42 }, // Vileplume - new(40,05,3) { Species = 454, Ability = A4, Moves = new[]{ 188, 389, 279, 189 }, Index = 42 }, // Toxicroak - new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 093, 078 }, Index = 42, CanGigantamax = true }, // Butterfree - new(50,08,4) { Species = 213, Ability = A4, Moves = new[]{ 157, 188, 089, 564 }, Index = 42 }, // Shuckle - new(50,08,4) { Species = 291, Ability = A4, Moves = new[]{ 210, 163, 332, 232 }, Index = 42 }, // Ninjask - new(50,08,4) { Species = 569, Ability = A4, Moves = new[]{ 188, 499, 034, 707 }, Index = 42, CanGigantamax = true }, // Garbodor - new(50,08,4) { Species = 045, Ability = A4, Moves = new[]{ 080, 585, 051, 034 }, Index = 42 }, // Vileplume - new(50,08,4) { Species = 454, Ability = A4, Moves = new[]{ 188, 389, 280, 189 }, Index = 42 }, // Toxicroak - new(60,10,5) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 42, CanGigantamax = true }, // Butterfree - new(60,10,5) { Species = 213, Ability = A4, Moves = new[]{ 444, 188, 089, 564 }, Index = 42 }, // Shuckle - new(60,10,5) { Species = 291, Ability = A4, Moves = new[]{ 404, 163, 332, 232 }, Index = 42 }, // Ninjask - new(60,10,5) { Species = 569, Ability = A4, Moves = new[]{ 441, 499, 034, 707 }, Index = 42, CanGigantamax = true }, // Garbodor - new(60,10,5) { Species = 045, Ability = A4, Moves = new[]{ 080, 585, 051, 034 }, Index = 42 }, // Vileplume - new(60,10,5) { Species = 454, Ability = A4, Moves = new[]{ 188, 389, 280, 523 }, Index = 42 }, // Toxicroak + new(17,01,1) { Species = 012, Ability = A4, Moves = new[]{ 081, 060, 016, 079 }, Index = 42, CanGigantamax = true }, // Butterfree + new(17,01,1) { Species = 213, Ability = A4, Moves = new[]{ 088, 474, 414, 522 }, Index = 42 }, // Shuckle + new(17,01,1) { Species = 290, Ability = A4, Moves = new[]{ 189, 206, 010, 106 }, Index = 42 }, // Nincada + new(17,01,1) { Species = 568, Ability = A4, Moves = new[]{ 390, 133, 491, 001 }, Index = 42 }, // Trubbish + new(17,01,1) { Species = 043, Ability = A4, Moves = new[]{ 078, 077, 051, 230 }, Index = 42 }, // Oddish + new(17,01,1) { Species = 453, Ability = A4, Moves = new[]{ 040, 269, 279, 189 }, Index = 42 }, // Croagunk + new(30,03,2) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 079 }, Index = 42, CanGigantamax = true }, // Butterfree + new(30,03,2) { Species = 213, Ability = A4, Moves = new[]{ 088, 474, 414, 522 }, Index = 42 }, // Shuckle + new(30,03,2) { Species = 291, Ability = A4, Moves = new[]{ 210, 206, 332, 232 }, Index = 42 }, // Ninjask + new(30,03,2) { Species = 568, Ability = A4, Moves = new[]{ 092, 133, 491, 036 }, Index = 42 }, // Trubbish + new(30,03,2) { Species = 045, Ability = A4, Moves = new[]{ 080, 585, 051, 230 }, Index = 42 }, // Vileplume + new(30,03,2) { Species = 453, Ability = A4, Moves = new[]{ 474, 389, 279, 189 }, Index = 42 }, // Croagunk + new(40,05,3) { Species = 012, Ability = A4, Moves = new[]{ 405, 060, 016, 078 }, Index = 42, CanGigantamax = true }, // Butterfree + new(40,05,3) { Species = 213, Ability = A4, Moves = new[]{ 157, 188, 089, 522 }, Index = 42 }, // Shuckle + new(40,05,3) { Species = 291, Ability = A4, Moves = new[]{ 210, 206, 332, 232 }, Index = 42 }, // Ninjask + new(40,05,3) { Species = 569, Ability = A4, Moves = new[]{ 188, 133, 034, 707 }, Index = 42, CanGigantamax = true }, // Garbodor + new(40,05,3) { Species = 045, Ability = A4, Moves = new[]{ 080, 585, 051, 230 }, Index = 42 }, // Vileplume + new(40,05,3) { Species = 454, Ability = A4, Moves = new[]{ 188, 389, 279, 189 }, Index = 42 }, // Toxicroak + new(50,08,4) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 093, 078 }, Index = 42, CanGigantamax = true }, // Butterfree + new(50,08,4) { Species = 213, Ability = A4, Moves = new[]{ 157, 188, 089, 564 }, Index = 42 }, // Shuckle + new(50,08,4) { Species = 291, Ability = A4, Moves = new[]{ 210, 163, 332, 232 }, Index = 42 }, // Ninjask + new(50,08,4) { Species = 569, Ability = A4, Moves = new[]{ 188, 499, 034, 707 }, Index = 42, CanGigantamax = true }, // Garbodor + new(50,08,4) { Species = 045, Ability = A4, Moves = new[]{ 080, 585, 051, 034 }, Index = 42 }, // Vileplume + new(50,08,4) { Species = 454, Ability = A4, Moves = new[]{ 188, 389, 280, 189 }, Index = 42 }, // Toxicroak + new(60,10,5) { Species = 012, Ability = A4, Moves = new[]{ 405, 403, 527, 078 }, Index = 42, CanGigantamax = true }, // Butterfree + new(60,10,5) { Species = 213, Ability = A4, Moves = new[]{ 444, 188, 089, 564 }, Index = 42 }, // Shuckle + new(60,10,5) { Species = 291, Ability = A4, Moves = new[]{ 404, 163, 332, 232 }, Index = 42 }, // Ninjask + new(60,10,5) { Species = 569, Ability = A4, Moves = new[]{ 441, 499, 034, 707 }, Index = 42, CanGigantamax = true }, // Garbodor + new(60,10,5) { Species = 045, Ability = A4, Moves = new[]{ 080, 585, 051, 034 }, Index = 42 }, // Vileplume + new(60,10,5) { Species = 454, Ability = A4, Moves = new[]{ 188, 389, 280, 523 }, Index = 42 }, // Toxicroak - new(17,01,1) { Species = 562, Ability = A4, Moves = new[]{ 261, 114, 310, 101 }, Index = 41 }, // Yamask - new(17,01,1) { Species = 778, Ability = A4, Moves = new[]{ 086, 452, 425, 010 }, Index = 41 }, // Mimikyu - new(17,01,1) { Species = 709, Ability = A4, Moves = new[]{ 785, 421, 261, 310 }, Index = 41 }, // Trevenant - new(17,01,1) { Species = 855, Ability = A4, Moves = new[]{ 597, 110, 668, 310 }, Index = 41 }, // Polteageist - new(30,03,2) { Species = 710, Ability = A4, Moves = new[]{ 567, 425, 310, 331 }, Index = 41 }, // Pumpkaboo - new(30,03,2) { Species = 563, Ability = A4, Moves = new[]{ 578, 421, 310, 261 }, Index = 41 }, // Cofagrigus - new(30,03,2) { Species = 778, Ability = A4, Moves = new[]{ 086, 452, 425, 608 }, Index = 41 }, // Mimikyu - new(30,03,2) { Species = 709, Ability = A4, Moves = new[]{ 785, 506, 261, 310 }, Index = 41 }, // Trevenant - new(30,03,2) { Species = 855, Ability = A4, Moves = new[]{ 597, 110, 389, 310 }, Index = 41 }, // Polteageist - new(40,05,3) { Species = 710, Ability = A4, Moves = new[]{ 567, 247, 310, 402 }, Index = 41 }, // Pumpkaboo - new(40,05,3) { Species = 563, Ability = A4, Moves = new[]{ 578, 421, 310, 261 }, Index = 41 }, // Cofagrigus - new(40,05,3) { Species = 778, Ability = A4, Moves = new[]{ 085, 452, 421, 608 }, Index = 41 }, // Mimikyu - new(40,05,3) { Species = 709, Ability = A4, Moves = new[]{ 785, 506, 261, 310 }, Index = 41 }, // Trevenant - new(40,05,3) { Species = 855, Ability = A4, Moves = new[]{ 597, 110, 389, 310 }, Index = 41 }, // Polteageist - new(50,08,4) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 585, 402 }, Index = 41 }, // Gourgeist - new(50,08,4) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 585, 402 }, Index = 41, Form = 1 }, // Gourgeist-1 - new(50,08,4) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 585, 402 }, Index = 41, Form = 2 }, // Gourgeist-2 - new(50,08,4) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 585, 402 }, Index = 41, Form = 3 }, // Gourgeist-3 - new(50,08,4) { Species = 563, Ability = A4, Moves = new[]{ 578, 247, 399, 261 }, Index = 41 }, // Cofagrigus - new(50,08,4) { Species = 778, Ability = A4, Moves = new[]{ 085, 452, 261, 204 }, Index = 41 }, // Mimikyu - new(50,08,4) { Species = 709, Ability = A4, Moves = new[]{ 452, 506, 261, 310 }, Index = 41 }, // Trevenant - new(50,08,4) { Species = 855, Ability = A4, Moves = new[]{ 247, 417, 389, 310 }, Index = 41 }, // Polteageist - new(60,10,5) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 433, 402 }, Index = 41 }, // Gourgeist - new(60,10,5) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 433, 402 }, Index = 41, Form = 1, Shiny = Shiny.Always }, // Gourgeist-1 - new(60,10,5) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 433, 402 }, Index = 41, Form = 2 }, // Gourgeist-2 - new(60,10,5) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 433, 402 }, Index = 41, Form = 3, Shiny = Shiny.Always }, // Gourgeist-3 - new(60,10,5) { Species = 563, Ability = A4, Moves = new[]{ 578, 247, 399, 261 }, Index = 41 }, // Cofagrigus - new(60,10,5) { Species = 778, Ability = A4, Moves = new[]{ 087, 452, 261, 583 }, Index = 41 }, // Mimikyu - new(60,10,5) { Species = 709, Ability = A4, Moves = new[]{ 452, 506, 261, 310 }, Index = 41 }, // Trevenant - new(60,10,5) { Species = 855, Ability = A4, Moves = new[]{ 247, 417, 389, 310 }, Index = 41 }, // Polteageist + new(17,01,1) { Species = 562, Ability = A4, Moves = new[]{ 261, 114, 310, 101 }, Index = 41 }, // Yamask + new(17,01,1) { Species = 778, Ability = A4, Moves = new[]{ 086, 452, 425, 010 }, Index = 41 }, // Mimikyu + new(17,01,1) { Species = 709, Ability = A4, Moves = new[]{ 785, 421, 261, 310 }, Index = 41 }, // Trevenant + new(17,01,1) { Species = 855, Ability = A4, Moves = new[]{ 597, 110, 668, 310 }, Index = 41 }, // Polteageist + new(30,03,2) { Species = 710, Ability = A4, Moves = new[]{ 567, 425, 310, 331 }, Index = 41 }, // Pumpkaboo + new(30,03,2) { Species = 563, Ability = A4, Moves = new[]{ 578, 421, 310, 261 }, Index = 41 }, // Cofagrigus + new(30,03,2) { Species = 778, Ability = A4, Moves = new[]{ 086, 452, 425, 608 }, Index = 41 }, // Mimikyu + new(30,03,2) { Species = 709, Ability = A4, Moves = new[]{ 785, 506, 261, 310 }, Index = 41 }, // Trevenant + new(30,03,2) { Species = 855, Ability = A4, Moves = new[]{ 597, 110, 389, 310 }, Index = 41 }, // Polteageist + new(40,05,3) { Species = 710, Ability = A4, Moves = new[]{ 567, 247, 310, 402 }, Index = 41 }, // Pumpkaboo + new(40,05,3) { Species = 563, Ability = A4, Moves = new[]{ 578, 421, 310, 261 }, Index = 41 }, // Cofagrigus + new(40,05,3) { Species = 778, Ability = A4, Moves = new[]{ 085, 452, 421, 608 }, Index = 41 }, // Mimikyu + new(40,05,3) { Species = 709, Ability = A4, Moves = new[]{ 785, 506, 261, 310 }, Index = 41 }, // Trevenant + new(40,05,3) { Species = 855, Ability = A4, Moves = new[]{ 597, 110, 389, 310 }, Index = 41 }, // Polteageist + new(50,08,4) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 585, 402 }, Index = 41 }, // Gourgeist + new(50,08,4) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 585, 402 }, Index = 41, Form = 1 }, // Gourgeist-1 + new(50,08,4) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 585, 402 }, Index = 41, Form = 2 }, // Gourgeist-2 + new(50,08,4) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 585, 402 }, Index = 41, Form = 3 }, // Gourgeist-3 + new(50,08,4) { Species = 563, Ability = A4, Moves = new[]{ 578, 247, 399, 261 }, Index = 41 }, // Cofagrigus + new(50,08,4) { Species = 778, Ability = A4, Moves = new[]{ 085, 452, 261, 204 }, Index = 41 }, // Mimikyu + new(50,08,4) { Species = 709, Ability = A4, Moves = new[]{ 452, 506, 261, 310 }, Index = 41 }, // Trevenant + new(50,08,4) { Species = 855, Ability = A4, Moves = new[]{ 247, 417, 389, 310 }, Index = 41 }, // Polteageist + new(60,10,5) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 433, 402 }, Index = 41 }, // Gourgeist + new(60,10,5) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 433, 402 }, Index = 41, Form = 1, Shiny = Shiny.Always }, // Gourgeist-1 + new(60,10,5) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 433, 402 }, Index = 41, Form = 2 }, // Gourgeist-2 + new(60,10,5) { Species = 711, Ability = A4, Moves = new[]{ 567, 247, 433, 402 }, Index = 41, Form = 3, Shiny = Shiny.Always }, // Gourgeist-3 + new(60,10,5) { Species = 563, Ability = A4, Moves = new[]{ 578, 247, 399, 261 }, Index = 41 }, // Cofagrigus + new(60,10,5) { Species = 778, Ability = A4, Moves = new[]{ 087, 452, 261, 583 }, Index = 41 }, // Mimikyu + new(60,10,5) { Species = 709, Ability = A4, Moves = new[]{ 452, 506, 261, 310 }, Index = 41 }, // Trevenant + new(60,10,5) { Species = 855, Ability = A4, Moves = new[]{ 247, 417, 389, 310 }, Index = 41 }, // Polteageist - new(17,01,1) { Species = 093, Ability = A4, Moves = new[]{ 371, 122, 095, 325 }, Index = 40 }, // Haunter - new(17,01,1) { Species = 425, Ability = A4, Moves = new[]{ 016, 506, 310, 371 }, Index = 40 }, // Drifloon - new(17,01,1) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 043, 506 }, Index = 40 }, // Duskull - new(17,01,1) { Species = 859, Ability = A4, Moves = new[]{ 372, 313, 260, 044 }, Index = 40 }, // Impidimp - new(17,01,1) { Species = 633, Ability = A4, Moves = new[]{ 225, 033, 399, 044 }, Index = 40 }, // Deino - new(17,01,1) { Species = 877, Ability = A4, Moves = new[]{ 084, 098, 681, 043 }, Index = 40 }, // Morpeko - new(30,03,2) { Species = 094, Ability = A4, Moves = new[]{ 371, 389, 095, 325 }, Index = 40, CanGigantamax = true }, // Gengar - new(30,03,2) { Species = 426, Ability = A4, Moves = new[]{ 016, 247, 310, 371 }, Index = 40 }, // Drifblim - new(30,03,2) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 371, 506 }, Index = 40 }, // Duskull - new(30,03,2) { Species = 859, Ability = A4, Moves = new[]{ 259, 389, 207, 044 }, Index = 40 }, // Impidimp - new(30,03,2) { Species = 633, Ability = A4, Moves = new[]{ 225, 021, 399, 029 }, Index = 40 }, // Deino - new(30,03,2) { Species = 877, Ability = A4, Moves = new[]{ 209, 098, 044, 043 }, Index = 40 }, // Morpeko - new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 506, 389, 095, 325 }, Index = 40, CanGigantamax = true }, // Gengar - new(40,05,3) { Species = 426, Ability = A4, Moves = new[]{ 016, 247, 360, 371 }, Index = 40 }, // Drifblim - new(40,05,3) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 371, 157 }, Index = 40 }, // Dusknoir - new(40,05,3) { Species = 860, Ability = A4, Moves = new[]{ 417, 793, 421, 399 }, Index = 40 }, // Morgrem - new(40,05,3) { Species = 633, Ability = A4, Moves = new[]{ 406, 021, 399, 423 }, Index = 40 }, // Deino - new(40,05,3) { Species = 877, Ability = A4, Moves = new[]{ 209, 098, 044, 402 }, Index = 40 }, // Morpeko - new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 399, 094, 085 }, Index = 40, CanGigantamax = true }, // Gengar - new(50,08,4) { Species = 426, Ability = A4, Moves = new[]{ 366, 247, 360, 371 }, Index = 40 }, // Drifblim - new(50,08,4) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 157 }, Index = 40 }, // Dusknoir - new(50,08,4) { Species = 861, Ability = A4, Moves = new[]{ 789, 492, 421, 399 }, Index = 40, CanGigantamax = true }, // Grimmsnarl - new(50,08,4) { Species = 634, Ability = A4, Moves = new[]{ 406, 304, 399, 423 }, Index = 40 }, // Zweilous - new(50,08,4) { Species = 877, Ability = A4, Moves = new[]{ 783, 098, 242, 402 }, Index = 40 }, // Morpeko - new(60,10,5) { Species = 094, Ability = A4, Moves = new[]{ 247, 399, 605, 085 }, Index = 40, CanGigantamax = true }, // Gengar - new(60,10,5) { Species = 426, Ability = A4, Moves = new[]{ 366, 247, 360, 693 }, Index = 40 }, // Drifblim - new(60,10,5) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 089 }, Index = 40 }, // Dusknoir - new(60,10,5) { Species = 861, Ability = A4, Moves = new[]{ 789, 492, 421, 417 }, Index = 40, CanGigantamax = true }, // Grimmsnarl - new(60,10,5) { Species = 635, Ability = A4, Moves = new[]{ 406, 304, 399, 056 }, Index = 40 }, // Hydreigon - new(60,10,5) { Species = 877, Ability = A4, Moves = new[]{ 783, 037, 242, 402 }, Index = 40 }, // Morpeko - }; - } + new(17,01,1) { Species = 093, Ability = A4, Moves = new[]{ 371, 122, 095, 325 }, Index = 40 }, // Haunter + new(17,01,1) { Species = 425, Ability = A4, Moves = new[]{ 016, 506, 310, 371 }, Index = 40 }, // Drifloon + new(17,01,1) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 043, 506 }, Index = 40 }, // Duskull + new(17,01,1) { Species = 859, Ability = A4, Moves = new[]{ 372, 313, 260, 044 }, Index = 40 }, // Impidimp + new(17,01,1) { Species = 633, Ability = A4, Moves = new[]{ 225, 033, 399, 044 }, Index = 40 }, // Deino + new(17,01,1) { Species = 877, Ability = A4, Moves = new[]{ 084, 098, 681, 043 }, Index = 40 }, // Morpeko + new(30,03,2) { Species = 094, Ability = A4, Moves = new[]{ 371, 389, 095, 325 }, Index = 40, CanGigantamax = true }, // Gengar + new(30,03,2) { Species = 426, Ability = A4, Moves = new[]{ 016, 247, 310, 371 }, Index = 40 }, // Drifblim + new(30,03,2) { Species = 355, Ability = A4, Moves = new[]{ 310, 425, 371, 506 }, Index = 40 }, // Duskull + new(30,03,2) { Species = 859, Ability = A4, Moves = new[]{ 259, 389, 207, 044 }, Index = 40 }, // Impidimp + new(30,03,2) { Species = 633, Ability = A4, Moves = new[]{ 225, 021, 399, 029 }, Index = 40 }, // Deino + new(30,03,2) { Species = 877, Ability = A4, Moves = new[]{ 209, 098, 044, 043 }, Index = 40 }, // Morpeko + new(40,05,3) { Species = 094, Ability = A4, Moves = new[]{ 506, 389, 095, 325 }, Index = 40, CanGigantamax = true }, // Gengar + new(40,05,3) { Species = 426, Ability = A4, Moves = new[]{ 016, 247, 360, 371 }, Index = 40 }, // Drifblim + new(40,05,3) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 371, 157 }, Index = 40 }, // Dusknoir + new(40,05,3) { Species = 860, Ability = A4, Moves = new[]{ 417, 793, 421, 399 }, Index = 40 }, // Morgrem + new(40,05,3) { Species = 633, Ability = A4, Moves = new[]{ 406, 021, 399, 423 }, Index = 40 }, // Deino + new(40,05,3) { Species = 877, Ability = A4, Moves = new[]{ 209, 098, 044, 402 }, Index = 40 }, // Morpeko + new(50,08,4) { Species = 094, Ability = A4, Moves = new[]{ 247, 399, 094, 085 }, Index = 40, CanGigantamax = true }, // Gengar + new(50,08,4) { Species = 426, Ability = A4, Moves = new[]{ 366, 247, 360, 371 }, Index = 40 }, // Drifblim + new(50,08,4) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 157 }, Index = 40 }, // Dusknoir + new(50,08,4) { Species = 861, Ability = A4, Moves = new[]{ 789, 492, 421, 399 }, Index = 40, CanGigantamax = true }, // Grimmsnarl + new(50,08,4) { Species = 634, Ability = A4, Moves = new[]{ 406, 304, 399, 423 }, Index = 40 }, // Zweilous + new(50,08,4) { Species = 877, Ability = A4, Moves = new[]{ 783, 098, 242, 402 }, Index = 40 }, // Morpeko + new(60,10,5) { Species = 094, Ability = A4, Moves = new[]{ 247, 399, 605, 085 }, Index = 40, CanGigantamax = true }, // Gengar + new(60,10,5) { Species = 426, Ability = A4, Moves = new[]{ 366, 247, 360, 693 }, Index = 40 }, // Drifblim + new(60,10,5) { Species = 477, Ability = A4, Moves = new[]{ 247, 009, 280, 089 }, Index = 40 }, // Dusknoir + new(60,10,5) { Species = 861, Ability = A4, Moves = new[]{ 789, 492, 421, 417 }, Index = 40, CanGigantamax = true }, // Grimmsnarl + new(60,10,5) { Species = 635, Ability = A4, Moves = new[]{ 406, 304, 399, 056 }, Index = 40 }, // Hydreigon + new(60,10,5) { Species = 877, Ability = A4, Moves = new[]{ 783, 037, 242, 402 }, Index = 40 }, // Morpeko + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters8NestLair.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters8NestLair.cs index 9755e2cf4..0ad5934fa 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters8NestLair.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters8NestLair.cs @@ -1,289 +1,288 @@ -namespace PKHeX.Core -{ - // Dynamax Adventures - internal static partial class Encounters8Nest - { - // These are encountered as never-shiny, but forced shiny (Star) if the 1:300 (1:100 w/charm) post-adventure roll activates. - // The game does try to gate specific entries to Sword / Shield, but this restriction is ignored for online battles. - // All captures share the same met location, so there is no way to distinguish an online-play result from a local-play result. +namespace PKHeX.Core; - #region Dynamax Adventures Encounters (ROM) - internal static readonly EncounterStatic8U[] DynAdv_SWSH = - { - new(002,0,65) { Ability = A2, Moves = new[] {520,235,076,188} }, // Ivysaur - new(005,0,65) { Ability = A2, Moves = new[] {519,406,203,517} }, // Charmeleon - new(008,0,65) { Ability = A2, Moves = new[] {518,058,396,056} }, // Wartortle - new(012,0,65) { Ability = A2, Moves = new[] {676,474,476,202}, CanGigantamax = true }, // Butterfree - new(026,0,65) { Ability = A2, Moves = new[] {804,683,113,411} }, // Raichu - new(026,1,65) { Ability = A0, Moves = new[] {085,604,094,496} }, // Raichu-1 - new(028,0,65) { Ability = A0, Moves = new[] {306,707,444,141} }, // Sandslash - new(028,1,65) { Ability = A0, Moves = new[] {419,157,280,014} }, // Sandslash-1 - new(031,0,65) { Ability = A0, Moves = new[] {815,474,204,247} }, // Nidoqueen - new(034,0,65) { Ability = A1, Moves = new[] {667,007,008,009} }, // Nidoking - new(035,0,65) { Ability = A2, Moves = new[] {791,595,345,115} }, // Clefairy - new(737,0,65) { Ability = A0, Moves = new[] {081,598,209,091} }, // Charjabug - new(743,0,65) { Ability = A2, Moves = new[] {676,577,312,313} }, // Ribombee - new(040,0,65) { Ability = A1, Moves = new[] {605,496,797,186} }, // Wigglytuff - new(553,0,65) { Ability = A2, Moves = new[] {414,207,663,201} }, // Krookodile - new(045,0,65) { Ability = A0, Moves = new[] {202,580,092,676} }, // Vileplume - new(051,0,65) { Ability = A2, Moves = new[] {667,164,189,157} }, // Dugtrio - new(051,1,65) { Ability = A0, Moves = new[] {442,667,389,103} }, // Dugtrio-1 - new(053,0,65) { Ability = A1, Moves = new[] {263,583,364,496} }, // Persian - new(053,1,65) { Ability = A0, Moves = new[] {372,555,364,511} }, // Persian-1 - new(055,0,65) { Ability = A2, Moves = new[] {453,103,025,362} }, // Golduck - new(062,0,65) { Ability = A0, Moves = new[] {409,034,811,710} }, // Poliwrath - new(064,0,65) { Ability = A2, Moves = new[] {473,496,203,605} }, // Kadabra - new(067,0,65) { Ability = A1, Moves = new[] {223,317,371,811} }, // Machoke - new(745,0,65) { Ability = A1, Moves = new[] {709,444,496,336} }, // Lycanroc - new(745,1,65) { Ability = A2, Moves = new[] {444,280,269,242} }, // Lycanroc-1 - new(082,0,65) { Ability = A2, Moves = new[] {486,430,393,113} }, // Magneton - new(752,0,65) { Ability = A2, Moves = new[] {710,494,679,398} }, // Araquanid - new(754,0,65) { Ability = A2, Moves = new[] {437,311,404,496} }, // Lurantis - new(093,0,65) { Ability = A2, Moves = new[] {506,095,138,412} }, // Haunter - new(869,0,65) { Ability = A2, Moves = new[] {777,605,595,345}, CanGigantamax = true }, // Alcremie - new(099,0,65) { Ability = A0, Moves = new[] {152,469,091,276}, CanGigantamax = true }, // Kingler - new(105,0,65) { Ability = A2, Moves = new[] {155,675,442,103} }, // Marowak - new(105,1,65) { Ability = A2, Moves = new[] {394,708,261,442} }, // Marowak-1 - new(106,0,65) { Ability = A0, Moves = new[] {370,469,299,490} }, // Hitmonlee - new(107,0,65) { Ability = A1, Moves = new[] {612,007,009,008} }, // Hitmonchan - new(108,0,65) { Ability = A1, Moves = new[] {496,059,087,330} }, // Lickitung - new(110,0,65) { Ability = A1, Moves = new[] {499,257,188,399} }, // Weezing - new(110,1,65) { Ability = A2, Moves = new[] {790,499,053,269} }, // Weezing-1 - new(112,0,65) { Ability = A1, Moves = new[] {529,479,684,184} }, // Rhydon - new(113,0,65) { Ability = A2, Moves = new[] {496,505,270,113} }, // Chansey - new(114,0,65) { Ability = A1, Moves = new[] {438,078,803,034} }, // Tangela - new(115,0,65) { Ability = A0, Moves = new[] {034,389,091,200} }, // Kangaskhan - new(117,0,65) { Ability = A1, Moves = new[] {503,406,164,496} }, // Seadra - new(119,0,65) { Ability = A1, Moves = new[] {127,340,398,529} }, // Seaking - new(122,0,65) { Ability = A1, Moves = new[] {113,115,270,094} }, // Mr. Mime - new(122,1,65) { Ability = A2, Moves = new[] {113,115,196,094} }, // Mr. Mime-1 - new(123,0,65) { Ability = A1, Moves = new[] {210,098,372,017} }, // Scyther - new(124,0,65) { Ability = A2, Moves = new[] {577,142,058,496} }, // Jynx - new(125,0,65) { Ability = A2, Moves = new[] {804,527,270,496} }, // Electabuzz - new(126,0,65) { Ability = A2, Moves = new[] {126,807,499,496} }, // Magmar - new(756,0,65) { Ability = A2, Moves = new[] {668,585,240,311} }, // Shiinotic - new(128,0,65) { Ability = A1, Moves = new[] {263,667,370,372} }, // Tauros - new(148,0,65) { Ability = A0, Moves = new[] {059,784,799,087} }, // Dragonair - new(164,0,65) { Ability = A2, Moves = new[] {497,115,143,095} }, // Noctowl - new(171,0,65) { Ability = A0, Moves = new[] {352,056,085,109} }, // Lanturn - new(176,0,65) { Ability = A1, Moves = new[] {791,266,583,595} }, // Togetic - new(178,0,65) { Ability = A2, Moves = new[] {094,493,403,109} }, // Xatu - new(182,0,65) { Ability = A2, Moves = new[] {580,202,270,605} }, // Bellossom - new(184,0,65) { Ability = A2, Moves = new[] {453,583,401,340} }, // Azumarill - new(185,0,65) { Ability = A0, Moves = new[] {707,444,334,776} }, // Sudowoodo - new(186,0,65) { Ability = A2, Moves = new[] {710,496,414,270} }, // Politoed - new(195,0,65) { Ability = A2, Moves = new[] {411,503,092,133} }, // Quagsire - new(206,0,65) { Ability = A0, Moves = new[] {806,814,247,058} }, // Dunsparce - new(211,0,65) { Ability = A2, Moves = new[] {014,398,710,798} }, // Qwilfish - new(758,0,65) { Ability = A0, Moves = new[] {092,053,440,599} }, // Salazzle - new(215,0,65) { Ability = A1, Moves = new[] {813,808,675,555} }, // Sneasel - new(221,0,65) { Ability = A2, Moves = new[] {059,317,420,276} }, // Piloswine - new(760,0,65) { Ability = A1, Moves = new[] {038,608,371,416} }, // Bewear - new(763,0,65) { Ability = A1, Moves = new[] {312,688,512,207} }, // Tsareena - new(224,0,65) { Ability = A1, Moves = new[] {806,430,503,491} }, // Octillery - new(226,0,65) { Ability = A1, Moves = new[] {403,291,469,352} }, // Mantine - new(227,0,65) { Ability = A2, Moves = new[] {372,211,404,019} }, // Skarmory - new(237,0,65) { Ability = A0, Moves = new[] {529,813,280,811} }, // Hitmontop - new(241,0,65) { Ability = A1, Moves = new[] {025,208,086,583} }, // Miltank - new(764,0,65) { Ability = A1, Moves = new[] {666,577,495,412} }, // Comfey - new(264,0,65) { Ability = A0, Moves = new[] {163,042,608,421} }, // Linoone - new(264,1,65) { Ability = A0, Moves = new[] {675,555,269,164} }, // Linoone-1 - new(103,0,65) { Ability = A2, Moves = new[] {427,076,707,805} }, // Exeggutor - new(405,0,65) { Ability = A2, Moves = new[] {263,113,804,604} }, // Luxray - new(279,0,65) { Ability = A1, Moves = new[] {814,311,469,098} }, // Pelipper - new(291,0,65) { Ability = A0, Moves = new[] {210,164,189,806} }, // Ninjask - new(295,0,65) { Ability = A2, Moves = new[] {805,063,411,059} }, // Exploud - new(770,0,65) { Ability = A2, Moves = new[] {805,815,659,247} }, // Palossand - new(771,0,65) { Ability = A0, Moves = new[] {092,269,599,068} }, // Pyukumuku - new(305,0,65) { Ability = A0, Moves = new[] {798,231,157,319} }, // Lairon - new(310,0,65) { Ability = A1, Moves = new[] {804,129,315,706} }, // Manectric - new(315,0,65) { Ability = A1, Moves = new[] {437,326,311,791} }, // Roselia - new(319,0,65) { Ability = A2, Moves = new[] {453,372,207,799} }, // Sharpedo - new(320,0,65) { Ability = A0, Moves = new[] {362,798,340,203} }, // Wailmer - new(324,0,65) { Ability = A1, Moves = new[] {807,517,229,108} }, // Torkoal - new(862,0,65) { Ability = A0, Moves = new[] {808,085,263,103} }, // Obstagoon - new(334,0,65) { Ability = A2, Moves = new[] {605,257,538,406} }, // Altaria - new(844,0,65) { Ability = A0, Moves = new[] {815,799,806,137}, CanGigantamax = true }, // Sandaconda - new(858,0,65) { Ability = A1, Moves = new[] {797,583,791,219}, CanGigantamax = true }, // Hatterene - new(340,0,65) { Ability = A2, Moves = new[] {340,562,330,428} }, // Whiscash - new(342,0,65) { Ability = A2, Moves = new[] {808,263,330,014} }, // Crawdaunt - new(344,0,65) { Ability = A0, Moves = new[] {433,094,246,063} }, // Claydol - new(356,0,65) { Ability = A0, Moves = new[] {425,506,356,806} }, // Dusclops - new(359,0,65) { Ability = A0, Moves = new[] {059,400,163,126} }, // Absol - new(362,0,65) { Ability = A1, Moves = new[] {798,242,423,313} }, // Glalie - new(364,0,65) { Ability = A0, Moves = new[] {058,362,291,207} }, // Sealeo - new(369,0,65) { Ability = A1, Moves = new[] {710,457,175,799} }, // Relicanth - new(132,0,65) { Ability = A2, Moves = new[] {144,000,000,000} }, // Ditto - new(375,0,65) { Ability = A0, Moves = new[] {309,009,427,115} }, // Metang - new(416,0,65) { Ability = A0, Moves = new[] {454,207,814,279} }, // Vespiquen - new(421,0,65) { Ability = A0, Moves = new[] {076,388,241,311} }, // Cherrim - new(423,1,65) { Ability = A2, Moves = new[] {034,806,317,127} }, // Gastrodon-1 - new(426,0,65) { Ability = A0, Moves = new[] {261,094,366,085} }, // Drifblim - new(428,0,65) { Ability = A0, Moves = new[] {409,025,204,340} }, // Lopunny - new(435,0,65) { Ability = A1, Moves = new[] {808,807,491,389} }, // Skuntank - new(537,0,65) { Ability = A0, Moves = new[] {497,048,188,103} }, // Seismitoad - new(452,0,65) { Ability = A0, Moves = new[] {808,404,367,231} }, // Drapion - new(777,0,65) { Ability = A2, Moves = new[] {609,398,527,442} }, // Togedemaru - new(460,0,65) { Ability = A2, Moves = new[] {419,694,496,803} }, // Abomasnow - new(478,0,65) { Ability = A0, Moves = new[] {813,524,694,247} }, // Froslass - new(479,0,65) { Ability = A0, Moves = new[] {486,261,417,506} }, // Rotom - new(508,0,65) { Ability = A2, Moves = new[] {416,263,496,608} }, // Stoutland - new(510,0,65) { Ability = A0, Moves = new[] {372,583,259,103} }, // Liepard - new(518,0,65) { Ability = A0, Moves = new[] {797,473,281,412} }, // Musharna - new(521,0,65) { Ability = A0, Moves = new[] {814,269,297,366} }, // Unfezant - new(528,0,65) { Ability = A2, Moves = new[] {493,683,094,403} }, // Swoobat - new(531,0,65) { Ability = A0, Moves = new[] {791,577,304,053} }, // Audino - new(533,0,65) { Ability = A0, Moves = new[] {264,811,280,667} }, // Gurdurr - new(536,0,65) { Ability = A0, Moves = new[] {497,503,414,340} }, // Palpitoad - new(778,0,65) { Ability = A0, Moves = new[] {421,163,608,174} }, // Mimikyu - new(884,0,65) { Ability = A0, Moves = new[] {784,086,442,085}, CanGigantamax = true }, // Duraludon - new(545,0,65) { Ability = A1, Moves = new[] {798,092,675,224} }, // Scolipede - new(547,0,65) { Ability = A0, Moves = new[] {542,269,412,583} }, // Whimsicott - new(549,0,65) { Ability = A1, Moves = new[] {080,483,113,676} }, // Lilligant - new(550,0,65) { Ability = A1, Moves = new[] {710,291,706,423} }, // Basculin - new(550,1,65) { Ability = A1, Moves = new[] {503,291,242,164} }, // Basculin-1 - new(828,0,65) { Ability = A1, Moves = new[] {492,555,269,807} }, // Thievul - new(834,0,65) { Ability = A0, Moves = new[] {534,806,684,157} }, // Drednaw - new(556,0,65) { Ability = A2, Moves = new[] {437,412,389,367} }, // Maractus - new(558,0,65) { Ability = A1, Moves = new[] {504,404,317,776} }, // Crustle - new(830,0,65) { Ability = A2, Moves = new[] {113,311,538,437} }, // Eldegoss - new(561,0,65) { Ability = A0, Moves = new[] {094,240,403,430} }, // Sigilyph - new(446,0,65) { Ability = A1, Moves = new[] {009,007,034,441} }, // Munchlax - new(855,0,65) { Ability = A0, Moves = new[] {312,389,473,202} }, // Polteageist - new(569,0,65) { Ability = A2, Moves = new[] {441,188,409,599}, CanGigantamax = true }, // Garbodor - new(573,0,65) { Ability = A1, Moves = new[] {497,541,113,813} }, // Cinccino - new(836,0,65) { Ability = A0, Moves = new[] {804,242,204,270} }, // Boltund - new(820,0,65) { Ability = A0, Moves = new[] {360,706,014,034} }, // Greedent - new(583,0,65) { Ability = A0, Moves = new[] {054,058,059,304} }, // Vanillish - new(587,0,65) { Ability = A0, Moves = new[] {512,804,203,527} }, // Emolga - new(589,0,65) { Ability = A1, Moves = new[] {529,534,210,269} }, // Escavalier - new(591,0,65) { Ability = A0, Moves = new[] {499,476,202,474} }, // Amoonguss - new(593,0,65) { Ability = A0, Moves = new[] {605,291,433,196} }, // Jellicent - new(596,0,65) { Ability = A0, Moves = new[] {087,405,486,527} }, // Galvantula - new(601,0,65) { Ability = A0, Moves = new[] {544,508,416,319} }, // Klinklang - new(606,0,65) { Ability = A1, Moves = new[] {797,800,399,496} }, // Beheeyem - new(608,0,65) { Ability = A0, Moves = new[] {807,806,517,433} }, // Lampent - new(611,0,65) { Ability = A0, Moves = new[] {416,200,784,404} }, // Fraxure - new(614,0,65) { Ability = A1, Moves = new[] {776,059,524,362} }, // Beartic - new(615,0,65) { Ability = A0, Moves = new[] {059,058,115,076} }, // Cryogonal - new(617,0,65) { Ability = A0, Moves = new[] {522,491,240,405} }, // Accelgor - new(618,0,65) { Ability = A0, Moves = new[] {604,085,414,330} }, // Stunfisk - new(618,1,65) { Ability = A0, Moves = new[] {319,805,492,414} }, // Stunfisk-1 - new(621,0,65) { Ability = A1, Moves = new[] {808,814,442,091} }, // Druddigon - new(623,0,65) { Ability = A0, Moves = new[] {264,325,815,219} }, // Golurk - new(625,0,65) { Ability = A1, Moves = new[] {400,398,427,319} }, // Bisharp - new(626,0,65) { Ability = A1, Moves = new[] {034,808,684,276} }, // Bouffalant - new(631,0,65) { Ability = A1, Moves = new[] {680,315,241,076} }, // Heatmor - new(632,0,65) { Ability = A0, Moves = new[] {422,404,319,232} }, // Durant - new(832,0,65) { Ability = A0, Moves = new[] {803,025,776,164} }, // Dubwool - new(660,0,65) { Ability = A2, Moves = new[] {444,707,091,098} }, // Diggersby - new(663,0,65) { Ability = A2, Moves = new[] {366,542,211,053} }, // Talonflame - new(675,0,65) { Ability = A0, Moves = new[] {418,359,663,811} }, // Pangoro - new(039,0,65) { Ability = A2, Moves = new[] {164,113,313,577} }, // Jigglypuff - new(525,0,65) { Ability = A0, Moves = new[] {444,334,776,707} }, // Boldore - new(680,0,65) { Ability = A0, Moves = new[] {442,014,533,332} }, // Doublade - new(687,0,65) { Ability = A0, Moves = new[] {576,797,400,085} }, // Malamar - new(689,0,65) { Ability = A0, Moves = new[] {534,059,130,398} }, // Barbaracle - new(695,0,65) { Ability = A0, Moves = new[] {486,097,496,189} }, // Heliolisk - new(702,0,65) { Ability = A2, Moves = new[] {494,087,605,164} }, // Dedenne - new(851,0,65) { Ability = A1, Moves = new[] {053,815,474,021}, CanGigantamax = true }, // Centiskorch - new(707,0,65) { Ability = A0, Moves = new[] {113,578,430,583} }, // Klefki - new(709,0,65) { Ability = A2, Moves = new[] {532,115,409,433} }, // Trevenant - new(711,0,65) { Ability = A0, Moves = new[] {595,425,388,184} }, // Gourgeist - new(847,0,65) { Ability = A0, Moves = new[] {453,799,372,203} }, // Barraskewda - new(845,0,65) { Ability = A0, Moves = new[] {291,203,133,675} }, // Cramorant - new(620,0,65) { Ability = A0, Moves = new[] {396,469,317,025} }, // Mienshao - new(870,0,65) { Ability = A0, Moves = new[] {660,014,684,280} }, // Falinks - new(701,0,65) { Ability = A0, Moves = new[] {269,398,675,490} }, // Hawlucha - new(879,0,65) { Ability = A0, Moves = new[] {334,776,430,798} }, // Copperajah - new(826,0,65) { Ability = A0, Moves = new[] {495,094,060,522}, CanGigantamax = true }, // Orbeetle - new(838,0,65) { Ability = A2, Moves = new[] {315,083,115,157} }, // Carkol - new(877,0,65) { Ability = A0, Moves = new[] {783,399,085,423} }, // Morpeko - new(563,0,65) { Ability = A0, Moves = new[] {247,114,094,472} }, // Cofagrigus - new(750,0,65) { Ability = A0, Moves = new[] {808,276,328,249} }, // Mudsdale - new(863,0,65) { Ability = A2, Moves = new[] {232,133,808,087} }, // Perrserker - new(871,0,65) { Ability = A2, Moves = new[] {056,087,367,599} }, // Pincurchin - new(873,0,65) { Ability = A2, Moves = new[] {311,366,522,542} }, // Frosmoth - new(839,0,65) { Ability = A0, Moves = new[] {108,800,053,503}, CanGigantamax = true }, // Coalossal - new(853,0,65) { Ability = A0, Moves = new[] {576,409,330,411} }, // Grapploct - new(861,0,65) { Ability = A0, Moves = new[] {612,399,384,590}, CanGigantamax = true }, // Grimmsnarl - new(886,0,65) { Ability = A0, Moves = new[] {407,372,261,247} }, // Drakloak - new(036,0,65) { Ability = A1, Moves = new[] {800,605,266,322} }, // Clefable - new(044,0,65) { Ability = A0, Moves = new[] {474,092,585,078} }, // Gloom - new(137,0,65) { Ability = A1, Moves = new[] {492,058,085,063} }, // Porygon - new(600,0,65) { Ability = A1, Moves = new[] {451,804,430,408} }, // Klang - new(738,0,65) { Ability = A0, Moves = new[] {209,189,398,405} }, // Vikavolt - new(254,0,65) { Ability = A2, Moves = new[] {520,784,437,404} }, // Sceptile - new(257,0,65) { Ability = A2, Moves = new[] {519,299,370,811} }, // Blaziken - new(260,0,65) { Ability = A2, Moves = new[] {518,059,414,133} }, // Swampert - new(073,0,65) { Ability = A0, Moves = new[] {352,056,398,014} }, // Tentacruel - new(080,0,65) { Ability = A1, Moves = new[] {797,244,053,473} }, // Slowbro - new(121,0,65) { Ability = A2, Moves = new[] {408,605,427,196} }, // Starmie - new(849,0,65) { Ability = A1, Moves = new[] {804,086,304,715}, CanGigantamax = true }, // Toxtricity - new(134,0,65) { Ability = A0, Moves = new[] {352,204,311,114} }, // Vaporeon - new(135,0,65) { Ability = A0, Moves = new[] {085,129,247,270} }, // Jolteon - new(136,0,65) { Ability = A0, Moves = new[] {807,247,608,387} }, // Flareon - new(199,0,65) { Ability = A1, Moves = new[] {248,417,534,008} }, // Slowking - new(330,0,65) { Ability = A0, Moves = new[] {211,337,405,189} }, // Flygon - new(346,0,65) { Ability = A0, Moves = new[] {412,246,380,188} }, // Cradily - new(348,0,65) { Ability = A0, Moves = new[] {404,479,707,201} }, // Armaldo - new(437,0,65) { Ability = A0, Moves = new[] {428,319,798,285} }, // Bronzong - new(697,0,65) { Ability = A0, Moves = new[] {799,350,276,034} }, // Tyrantrum - new(253,0,65) { Ability = A0, Moves = new[] {520,103,280,203} }, // Grovyle - new(256,0,65) { Ability = A0, Moves = new[] {519,411,297,490} }, // Combusken - new(259,0,65) { Ability = A0, Moves = new[] {518,127,091,008} }, // Marshtomp - new(699,0,65) { Ability = A0, Moves = new[] {034,087,246,086} }, // Aurorus - new(765,0,65) { Ability = A2, Moves = new[] {689,113,094,473} }, // Oranguru - new(766,0,65) { Ability = A0, Moves = new[] {280,317,164,512} }, // Passimian - new(876,0,65) { Ability = A1, Moves = new[] {595,797,347,247} }, // Indeedee - new(145,0,70) { Ability = A0, Moves = new[] {087,065,413,097} }, // Zapdos - new(146,0,70) { Ability = A0, Moves = new[] {257,017,043,083} }, // Moltres - new(144,0,70) { Ability = A0, Moves = new[] {058,573,542,054} }, // Articuno - new(150,0,70) { Ability = A0, Moves = new[] {094,050,105,059} }, // Mewtwo - new(245,0,70) { Ability = A0, Moves = new[] {710,326,245,347} }, // Suicune - new(244,0,70) { Ability = A0, Moves = new[] {053,184,245,242} }, // Entei - new(243,0,70) { Ability = A0, Moves = new[] {085,336,245,311} }, // Raikou - new(249,0,70) { Ability = A0, Moves = new[] {406,326,250,246} }, // (SH) Lugia - new(250,0,70) { Ability = A0, Moves = new[] {394,326,241,246} }, // (SW) Ho-Oh - new(380,0,70) { Ability = A0, Moves = new[] {513,225,428,057} }, // (SH) Latias - new(381,0,70) { Ability = A0, Moves = new[] {349,406,428,396} }, // (SW) Latios - new(383,0,70) { Ability = A0, Moves = new[] {089,184,436,359} }, // (SW) Groudon - new(382,0,70) { Ability = A0, Moves = new[] {057,034,392,087} }, // (SH) Kyogre - new(384,0,70) { Ability = A0, Moves = new[] {620,693,245,239} }, // Rayquaza - new(480,0,70) { Ability = A0, Moves = new[] {094,248,478,247} }, // Uxie - new(482,0,70) { Ability = A0, Moves = new[] {094,605,417,263} }, // Azelf - new(481,0,70) { Ability = A0, Moves = new[] {094,204,577,161} }, // Mesprit - new(483,0,70) { Ability = A0, Moves = new[] {163,246,430,337} }, // (SW) Dialga - new(484,0,70) { Ability = A0, Moves = new[] {163,057,246,337} }, // (SH) Palkia - new(487,0,70) { Ability = A0, Moves = new[] {337,184,247,246} }, // Giratina - new(485,0,70) { Ability = A0, Moves = new[] {319,436,242,442} }, // Heatran - new(488,0,70) { Ability = A0, Moves = new[] {196,585,427,473} }, // Cresselia - new(641,0,70) { Ability = A0, Moves = new[] {542,097,196,257} }, // (SW) Tornadus - new(642,0,70) { Ability = A0, Moves = new[] {087,240,311,482} }, // (SH) Thundurus - new(645,0,70) { Ability = A0, Moves = new[] {328,157,523,411} }, // Landorus - new(643,0,70) { Ability = A0, Moves = new[] {568,326,558,406} }, // (SW) Reshiram - new(644,0,70) { Ability = A0, Moves = new[] {568,163,559,337} }, // (SH) Zekrom - new(646,0,70) { Ability = A0, Moves = new[] {058,304,247,184} }, // Kyurem - new(716,0,70) { Ability = A0, Moves = new[] {275,605,585,532} }, // (SW) Xerneas - new(717,0,70) { Ability = A0, Moves = new[] {269,613,407,389} }, // (SH) Yveltal - new(718,3,70) { Ability = A0, Moves = new[] {614,616,406,020} }, // Zygarde-3 - new(785,0,70) { Ability = A0, Moves = new[] {085,098,413,269} }, // Tapu Koko - new(786,0,70) { Ability = A0, Moves = new[] {094,583,478,204} }, // Tapu Lele - new(787,0,70) { Ability = A0, Moves = new[] {276,224,452,184} }, // Tapu Bulu - new(788,0,70) { Ability = A0, Moves = new[] {250,352,362,585} }, // Tapu Fini - new(791,0,70) { Ability = A0, Moves = new[] {428,083,231,568} }, // (SW) Solgaleo - new(792,0,70) { Ability = A0, Moves = new[] {247,585,277,129} }, // (SH) Lunala - new(800,0,70) { Ability = A0, Moves = new[] {427,451,408,475} }, // Necrozma - new(793,0,70) { Ability = A0, Moves = new[] {472,482,693,491} }, // Nihilego - new(794,0,70) { Ability = A0, Moves = new[] {612,269,141,223} }, // Buzzwole - new(795,0,70) { Ability = A0, Moves = new[] {136,129,675,679} }, // Pheromosa - new(796,0,70) { Ability = A0, Moves = new[] {438,435,598,693} }, // Xurkitree - new(798,0,70) { Ability = A0, Moves = new[] {410,314,348,014} }, // Kartana - new(797,0,70) { Ability = A0, Moves = new[] {073,479,360,089} }, // Celesteela - new(799,0,70) { Ability = A0, Moves = new[] {407,707,693,005} }, // Guzzlord - new(806,0,70) { Ability = A0, Moves = new[] {421,269,126,428} }, // Blacephalon - new(805,0,70) { Ability = A0, Moves = new[] {157,038,693,475} }, // Stakataka - }; - #endregion - } +// Dynamax Adventures +internal static partial class Encounters8Nest +{ + // These are encountered as never-shiny, but forced shiny (Star) if the 1:300 (1:100 w/charm) post-adventure roll activates. + // The game does try to gate specific entries to Sword / Shield, but this restriction is ignored for online battles. + // All captures share the same met location, so there is no way to distinguish an online-play result from a local-play result. + + #region Dynamax Adventures Encounters (ROM) + internal static readonly EncounterStatic8U[] DynAdv_SWSH = + { + new(002,0,65) { Ability = A2, Moves = new[] {520,235,076,188} }, // Ivysaur + new(005,0,65) { Ability = A2, Moves = new[] {519,406,203,517} }, // Charmeleon + new(008,0,65) { Ability = A2, Moves = new[] {518,058,396,056} }, // Wartortle + new(012,0,65) { Ability = A2, Moves = new[] {676,474,476,202}, CanGigantamax = true }, // Butterfree + new(026,0,65) { Ability = A2, Moves = new[] {804,683,113,411} }, // Raichu + new(026,1,65) { Ability = A0, Moves = new[] {085,604,094,496} }, // Raichu-1 + new(028,0,65) { Ability = A0, Moves = new[] {306,707,444,141} }, // Sandslash + new(028,1,65) { Ability = A0, Moves = new[] {419,157,280,014} }, // Sandslash-1 + new(031,0,65) { Ability = A0, Moves = new[] {815,474,204,247} }, // Nidoqueen + new(034,0,65) { Ability = A1, Moves = new[] {667,007,008,009} }, // Nidoking + new(035,0,65) { Ability = A2, Moves = new[] {791,595,345,115} }, // Clefairy + new(737,0,65) { Ability = A0, Moves = new[] {081,598,209,091} }, // Charjabug + new(743,0,65) { Ability = A2, Moves = new[] {676,577,312,313} }, // Ribombee + new(040,0,65) { Ability = A1, Moves = new[] {605,496,797,186} }, // Wigglytuff + new(553,0,65) { Ability = A2, Moves = new[] {414,207,663,201} }, // Krookodile + new(045,0,65) { Ability = A0, Moves = new[] {202,580,092,676} }, // Vileplume + new(051,0,65) { Ability = A2, Moves = new[] {667,164,189,157} }, // Dugtrio + new(051,1,65) { Ability = A0, Moves = new[] {442,667,389,103} }, // Dugtrio-1 + new(053,0,65) { Ability = A1, Moves = new[] {263,583,364,496} }, // Persian + new(053,1,65) { Ability = A0, Moves = new[] {372,555,364,511} }, // Persian-1 + new(055,0,65) { Ability = A2, Moves = new[] {453,103,025,362} }, // Golduck + new(062,0,65) { Ability = A0, Moves = new[] {409,034,811,710} }, // Poliwrath + new(064,0,65) { Ability = A2, Moves = new[] {473,496,203,605} }, // Kadabra + new(067,0,65) { Ability = A1, Moves = new[] {223,317,371,811} }, // Machoke + new(745,0,65) { Ability = A1, Moves = new[] {709,444,496,336} }, // Lycanroc + new(745,1,65) { Ability = A2, Moves = new[] {444,280,269,242} }, // Lycanroc-1 + new(082,0,65) { Ability = A2, Moves = new[] {486,430,393,113} }, // Magneton + new(752,0,65) { Ability = A2, Moves = new[] {710,494,679,398} }, // Araquanid + new(754,0,65) { Ability = A2, Moves = new[] {437,311,404,496} }, // Lurantis + new(093,0,65) { Ability = A2, Moves = new[] {506,095,138,412} }, // Haunter + new(869,0,65) { Ability = A2, Moves = new[] {777,605,595,345}, CanGigantamax = true }, // Alcremie + new(099,0,65) { Ability = A0, Moves = new[] {152,469,091,276}, CanGigantamax = true }, // Kingler + new(105,0,65) { Ability = A2, Moves = new[] {155,675,442,103} }, // Marowak + new(105,1,65) { Ability = A2, Moves = new[] {394,708,261,442} }, // Marowak-1 + new(106,0,65) { Ability = A0, Moves = new[] {370,469,299,490} }, // Hitmonlee + new(107,0,65) { Ability = A1, Moves = new[] {612,007,009,008} }, // Hitmonchan + new(108,0,65) { Ability = A1, Moves = new[] {496,059,087,330} }, // Lickitung + new(110,0,65) { Ability = A1, Moves = new[] {499,257,188,399} }, // Weezing + new(110,1,65) { Ability = A2, Moves = new[] {790,499,053,269} }, // Weezing-1 + new(112,0,65) { Ability = A1, Moves = new[] {529,479,684,184} }, // Rhydon + new(113,0,65) { Ability = A2, Moves = new[] {496,505,270,113} }, // Chansey + new(114,0,65) { Ability = A1, Moves = new[] {438,078,803,034} }, // Tangela + new(115,0,65) { Ability = A0, Moves = new[] {034,389,091,200} }, // Kangaskhan + new(117,0,65) { Ability = A1, Moves = new[] {503,406,164,496} }, // Seadra + new(119,0,65) { Ability = A1, Moves = new[] {127,340,398,529} }, // Seaking + new(122,0,65) { Ability = A1, Moves = new[] {113,115,270,094} }, // Mr. Mime + new(122,1,65) { Ability = A2, Moves = new[] {113,115,196,094} }, // Mr. Mime-1 + new(123,0,65) { Ability = A1, Moves = new[] {210,098,372,017} }, // Scyther + new(124,0,65) { Ability = A2, Moves = new[] {577,142,058,496} }, // Jynx + new(125,0,65) { Ability = A2, Moves = new[] {804,527,270,496} }, // Electabuzz + new(126,0,65) { Ability = A2, Moves = new[] {126,807,499,496} }, // Magmar + new(756,0,65) { Ability = A2, Moves = new[] {668,585,240,311} }, // Shiinotic + new(128,0,65) { Ability = A1, Moves = new[] {263,667,370,372} }, // Tauros + new(148,0,65) { Ability = A0, Moves = new[] {059,784,799,087} }, // Dragonair + new(164,0,65) { Ability = A2, Moves = new[] {497,115,143,095} }, // Noctowl + new(171,0,65) { Ability = A0, Moves = new[] {352,056,085,109} }, // Lanturn + new(176,0,65) { Ability = A1, Moves = new[] {791,266,583,595} }, // Togetic + new(178,0,65) { Ability = A2, Moves = new[] {094,493,403,109} }, // Xatu + new(182,0,65) { Ability = A2, Moves = new[] {580,202,270,605} }, // Bellossom + new(184,0,65) { Ability = A2, Moves = new[] {453,583,401,340} }, // Azumarill + new(185,0,65) { Ability = A0, Moves = new[] {707,444,334,776} }, // Sudowoodo + new(186,0,65) { Ability = A2, Moves = new[] {710,496,414,270} }, // Politoed + new(195,0,65) { Ability = A2, Moves = new[] {411,503,092,133} }, // Quagsire + new(206,0,65) { Ability = A0, Moves = new[] {806,814,247,058} }, // Dunsparce + new(211,0,65) { Ability = A2, Moves = new[] {014,398,710,798} }, // Qwilfish + new(758,0,65) { Ability = A0, Moves = new[] {092,053,440,599} }, // Salazzle + new(215,0,65) { Ability = A1, Moves = new[] {813,808,675,555} }, // Sneasel + new(221,0,65) { Ability = A2, Moves = new[] {059,317,420,276} }, // Piloswine + new(760,0,65) { Ability = A1, Moves = new[] {038,608,371,416} }, // Bewear + new(763,0,65) { Ability = A1, Moves = new[] {312,688,512,207} }, // Tsareena + new(224,0,65) { Ability = A1, Moves = new[] {806,430,503,491} }, // Octillery + new(226,0,65) { Ability = A1, Moves = new[] {403,291,469,352} }, // Mantine + new(227,0,65) { Ability = A2, Moves = new[] {372,211,404,019} }, // Skarmory + new(237,0,65) { Ability = A0, Moves = new[] {529,813,280,811} }, // Hitmontop + new(241,0,65) { Ability = A1, Moves = new[] {025,208,086,583} }, // Miltank + new(764,0,65) { Ability = A1, Moves = new[] {666,577,495,412} }, // Comfey + new(264,0,65) { Ability = A0, Moves = new[] {163,042,608,421} }, // Linoone + new(264,1,65) { Ability = A0, Moves = new[] {675,555,269,164} }, // Linoone-1 + new(103,0,65) { Ability = A2, Moves = new[] {427,076,707,805} }, // Exeggutor + new(405,0,65) { Ability = A2, Moves = new[] {263,113,804,604} }, // Luxray + new(279,0,65) { Ability = A1, Moves = new[] {814,311,469,098} }, // Pelipper + new(291,0,65) { Ability = A0, Moves = new[] {210,164,189,806} }, // Ninjask + new(295,0,65) { Ability = A2, Moves = new[] {805,063,411,059} }, // Exploud + new(770,0,65) { Ability = A2, Moves = new[] {805,815,659,247} }, // Palossand + new(771,0,65) { Ability = A0, Moves = new[] {092,269,599,068} }, // Pyukumuku + new(305,0,65) { Ability = A0, Moves = new[] {798,231,157,319} }, // Lairon + new(310,0,65) { Ability = A1, Moves = new[] {804,129,315,706} }, // Manectric + new(315,0,65) { Ability = A1, Moves = new[] {437,326,311,791} }, // Roselia + new(319,0,65) { Ability = A2, Moves = new[] {453,372,207,799} }, // Sharpedo + new(320,0,65) { Ability = A0, Moves = new[] {362,798,340,203} }, // Wailmer + new(324,0,65) { Ability = A1, Moves = new[] {807,517,229,108} }, // Torkoal + new(862,0,65) { Ability = A0, Moves = new[] {808,085,263,103} }, // Obstagoon + new(334,0,65) { Ability = A2, Moves = new[] {605,257,538,406} }, // Altaria + new(844,0,65) { Ability = A0, Moves = new[] {815,799,806,137}, CanGigantamax = true }, // Sandaconda + new(858,0,65) { Ability = A1, Moves = new[] {797,583,791,219}, CanGigantamax = true }, // Hatterene + new(340,0,65) { Ability = A2, Moves = new[] {340,562,330,428} }, // Whiscash + new(342,0,65) { Ability = A2, Moves = new[] {808,263,330,014} }, // Crawdaunt + new(344,0,65) { Ability = A0, Moves = new[] {433,094,246,063} }, // Claydol + new(356,0,65) { Ability = A0, Moves = new[] {425,506,356,806} }, // Dusclops + new(359,0,65) { Ability = A0, Moves = new[] {059,400,163,126} }, // Absol + new(362,0,65) { Ability = A1, Moves = new[] {798,242,423,313} }, // Glalie + new(364,0,65) { Ability = A0, Moves = new[] {058,362,291,207} }, // Sealeo + new(369,0,65) { Ability = A1, Moves = new[] {710,457,175,799} }, // Relicanth + new(132,0,65) { Ability = A2, Moves = new[] {144,000,000,000} }, // Ditto + new(375,0,65) { Ability = A0, Moves = new[] {309,009,427,115} }, // Metang + new(416,0,65) { Ability = A0, Moves = new[] {454,207,814,279} }, // Vespiquen + new(421,0,65) { Ability = A0, Moves = new[] {076,388,241,311} }, // Cherrim + new(423,1,65) { Ability = A2, Moves = new[] {034,806,317,127} }, // Gastrodon-1 + new(426,0,65) { Ability = A0, Moves = new[] {261,094,366,085} }, // Drifblim + new(428,0,65) { Ability = A0, Moves = new[] {409,025,204,340} }, // Lopunny + new(435,0,65) { Ability = A1, Moves = new[] {808,807,491,389} }, // Skuntank + new(537,0,65) { Ability = A0, Moves = new[] {497,048,188,103} }, // Seismitoad + new(452,0,65) { Ability = A0, Moves = new[] {808,404,367,231} }, // Drapion + new(777,0,65) { Ability = A2, Moves = new[] {609,398,527,442} }, // Togedemaru + new(460,0,65) { Ability = A2, Moves = new[] {419,694,496,803} }, // Abomasnow + new(478,0,65) { Ability = A0, Moves = new[] {813,524,694,247} }, // Froslass + new(479,0,65) { Ability = A0, Moves = new[] {486,261,417,506} }, // Rotom + new(508,0,65) { Ability = A2, Moves = new[] {416,263,496,608} }, // Stoutland + new(510,0,65) { Ability = A0, Moves = new[] {372,583,259,103} }, // Liepard + new(518,0,65) { Ability = A0, Moves = new[] {797,473,281,412} }, // Musharna + new(521,0,65) { Ability = A0, Moves = new[] {814,269,297,366} }, // Unfezant + new(528,0,65) { Ability = A2, Moves = new[] {493,683,094,403} }, // Swoobat + new(531,0,65) { Ability = A0, Moves = new[] {791,577,304,053} }, // Audino + new(533,0,65) { Ability = A0, Moves = new[] {264,811,280,667} }, // Gurdurr + new(536,0,65) { Ability = A0, Moves = new[] {497,503,414,340} }, // Palpitoad + new(778,0,65) { Ability = A0, Moves = new[] {421,163,608,174} }, // Mimikyu + new(884,0,65) { Ability = A0, Moves = new[] {784,086,442,085}, CanGigantamax = true }, // Duraludon + new(545,0,65) { Ability = A1, Moves = new[] {798,092,675,224} }, // Scolipede + new(547,0,65) { Ability = A0, Moves = new[] {542,269,412,583} }, // Whimsicott + new(549,0,65) { Ability = A1, Moves = new[] {080,483,113,676} }, // Lilligant + new(550,0,65) { Ability = A1, Moves = new[] {710,291,706,423} }, // Basculin + new(550,1,65) { Ability = A1, Moves = new[] {503,291,242,164} }, // Basculin-1 + new(828,0,65) { Ability = A1, Moves = new[] {492,555,269,807} }, // Thievul + new(834,0,65) { Ability = A0, Moves = new[] {534,806,684,157} }, // Drednaw + new(556,0,65) { Ability = A2, Moves = new[] {437,412,389,367} }, // Maractus + new(558,0,65) { Ability = A1, Moves = new[] {504,404,317,776} }, // Crustle + new(830,0,65) { Ability = A2, Moves = new[] {113,311,538,437} }, // Eldegoss + new(561,0,65) { Ability = A0, Moves = new[] {094,240,403,430} }, // Sigilyph + new(446,0,65) { Ability = A1, Moves = new[] {009,007,034,441} }, // Munchlax + new(855,0,65) { Ability = A0, Moves = new[] {312,389,473,202} }, // Polteageist + new(569,0,65) { Ability = A2, Moves = new[] {441,188,409,599}, CanGigantamax = true }, // Garbodor + new(573,0,65) { Ability = A1, Moves = new[] {497,541,113,813} }, // Cinccino + new(836,0,65) { Ability = A0, Moves = new[] {804,242,204,270} }, // Boltund + new(820,0,65) { Ability = A0, Moves = new[] {360,706,014,034} }, // Greedent + new(583,0,65) { Ability = A0, Moves = new[] {054,058,059,304} }, // Vanillish + new(587,0,65) { Ability = A0, Moves = new[] {512,804,203,527} }, // Emolga + new(589,0,65) { Ability = A1, Moves = new[] {529,534,210,269} }, // Escavalier + new(591,0,65) { Ability = A0, Moves = new[] {499,476,202,474} }, // Amoonguss + new(593,0,65) { Ability = A0, Moves = new[] {605,291,433,196} }, // Jellicent + new(596,0,65) { Ability = A0, Moves = new[] {087,405,486,527} }, // Galvantula + new(601,0,65) { Ability = A0, Moves = new[] {544,508,416,319} }, // Klinklang + new(606,0,65) { Ability = A1, Moves = new[] {797,800,399,496} }, // Beheeyem + new(608,0,65) { Ability = A0, Moves = new[] {807,806,517,433} }, // Lampent + new(611,0,65) { Ability = A0, Moves = new[] {416,200,784,404} }, // Fraxure + new(614,0,65) { Ability = A1, Moves = new[] {776,059,524,362} }, // Beartic + new(615,0,65) { Ability = A0, Moves = new[] {059,058,115,076} }, // Cryogonal + new(617,0,65) { Ability = A0, Moves = new[] {522,491,240,405} }, // Accelgor + new(618,0,65) { Ability = A0, Moves = new[] {604,085,414,330} }, // Stunfisk + new(618,1,65) { Ability = A0, Moves = new[] {319,805,492,414} }, // Stunfisk-1 + new(621,0,65) { Ability = A1, Moves = new[] {808,814,442,091} }, // Druddigon + new(623,0,65) { Ability = A0, Moves = new[] {264,325,815,219} }, // Golurk + new(625,0,65) { Ability = A1, Moves = new[] {400,398,427,319} }, // Bisharp + new(626,0,65) { Ability = A1, Moves = new[] {034,808,684,276} }, // Bouffalant + new(631,0,65) { Ability = A1, Moves = new[] {680,315,241,076} }, // Heatmor + new(632,0,65) { Ability = A0, Moves = new[] {422,404,319,232} }, // Durant + new(832,0,65) { Ability = A0, Moves = new[] {803,025,776,164} }, // Dubwool + new(660,0,65) { Ability = A2, Moves = new[] {444,707,091,098} }, // Diggersby + new(663,0,65) { Ability = A2, Moves = new[] {366,542,211,053} }, // Talonflame + new(675,0,65) { Ability = A0, Moves = new[] {418,359,663,811} }, // Pangoro + new(039,0,65) { Ability = A2, Moves = new[] {164,113,313,577} }, // Jigglypuff + new(525,0,65) { Ability = A0, Moves = new[] {444,334,776,707} }, // Boldore + new(680,0,65) { Ability = A0, Moves = new[] {442,014,533,332} }, // Doublade + new(687,0,65) { Ability = A0, Moves = new[] {576,797,400,085} }, // Malamar + new(689,0,65) { Ability = A0, Moves = new[] {534,059,130,398} }, // Barbaracle + new(695,0,65) { Ability = A0, Moves = new[] {486,097,496,189} }, // Heliolisk + new(702,0,65) { Ability = A2, Moves = new[] {494,087,605,164} }, // Dedenne + new(851,0,65) { Ability = A1, Moves = new[] {053,815,474,021}, CanGigantamax = true }, // Centiskorch + new(707,0,65) { Ability = A0, Moves = new[] {113,578,430,583} }, // Klefki + new(709,0,65) { Ability = A2, Moves = new[] {532,115,409,433} }, // Trevenant + new(711,0,65) { Ability = A0, Moves = new[] {595,425,388,184} }, // Gourgeist + new(847,0,65) { Ability = A0, Moves = new[] {453,799,372,203} }, // Barraskewda + new(845,0,65) { Ability = A0, Moves = new[] {291,203,133,675} }, // Cramorant + new(620,0,65) { Ability = A0, Moves = new[] {396,469,317,025} }, // Mienshao + new(870,0,65) { Ability = A0, Moves = new[] {660,014,684,280} }, // Falinks + new(701,0,65) { Ability = A0, Moves = new[] {269,398,675,490} }, // Hawlucha + new(879,0,65) { Ability = A0, Moves = new[] {334,776,430,798} }, // Copperajah + new(826,0,65) { Ability = A0, Moves = new[] {495,094,060,522}, CanGigantamax = true }, // Orbeetle + new(838,0,65) { Ability = A2, Moves = new[] {315,083,115,157} }, // Carkol + new(877,0,65) { Ability = A0, Moves = new[] {783,399,085,423} }, // Morpeko + new(563,0,65) { Ability = A0, Moves = new[] {247,114,094,472} }, // Cofagrigus + new(750,0,65) { Ability = A0, Moves = new[] {808,276,328,249} }, // Mudsdale + new(863,0,65) { Ability = A2, Moves = new[] {232,133,808,087} }, // Perrserker + new(871,0,65) { Ability = A2, Moves = new[] {056,087,367,599} }, // Pincurchin + new(873,0,65) { Ability = A2, Moves = new[] {311,366,522,542} }, // Frosmoth + new(839,0,65) { Ability = A0, Moves = new[] {108,800,053,503}, CanGigantamax = true }, // Coalossal + new(853,0,65) { Ability = A0, Moves = new[] {576,409,330,411} }, // Grapploct + new(861,0,65) { Ability = A0, Moves = new[] {612,399,384,590}, CanGigantamax = true }, // Grimmsnarl + new(886,0,65) { Ability = A0, Moves = new[] {407,372,261,247} }, // Drakloak + new(036,0,65) { Ability = A1, Moves = new[] {800,605,266,322} }, // Clefable + new(044,0,65) { Ability = A0, Moves = new[] {474,092,585,078} }, // Gloom + new(137,0,65) { Ability = A1, Moves = new[] {492,058,085,063} }, // Porygon + new(600,0,65) { Ability = A1, Moves = new[] {451,804,430,408} }, // Klang + new(738,0,65) { Ability = A0, Moves = new[] {209,189,398,405} }, // Vikavolt + new(254,0,65) { Ability = A2, Moves = new[] {520,784,437,404} }, // Sceptile + new(257,0,65) { Ability = A2, Moves = new[] {519,299,370,811} }, // Blaziken + new(260,0,65) { Ability = A2, Moves = new[] {518,059,414,133} }, // Swampert + new(073,0,65) { Ability = A0, Moves = new[] {352,056,398,014} }, // Tentacruel + new(080,0,65) { Ability = A1, Moves = new[] {797,244,053,473} }, // Slowbro + new(121,0,65) { Ability = A2, Moves = new[] {408,605,427,196} }, // Starmie + new(849,0,65) { Ability = A1, Moves = new[] {804,086,304,715}, CanGigantamax = true }, // Toxtricity + new(134,0,65) { Ability = A0, Moves = new[] {352,204,311,114} }, // Vaporeon + new(135,0,65) { Ability = A0, Moves = new[] {085,129,247,270} }, // Jolteon + new(136,0,65) { Ability = A0, Moves = new[] {807,247,608,387} }, // Flareon + new(199,0,65) { Ability = A1, Moves = new[] {248,417,534,008} }, // Slowking + new(330,0,65) { Ability = A0, Moves = new[] {211,337,405,189} }, // Flygon + new(346,0,65) { Ability = A0, Moves = new[] {412,246,380,188} }, // Cradily + new(348,0,65) { Ability = A0, Moves = new[] {404,479,707,201} }, // Armaldo + new(437,0,65) { Ability = A0, Moves = new[] {428,319,798,285} }, // Bronzong + new(697,0,65) { Ability = A0, Moves = new[] {799,350,276,034} }, // Tyrantrum + new(253,0,65) { Ability = A0, Moves = new[] {520,103,280,203} }, // Grovyle + new(256,0,65) { Ability = A0, Moves = new[] {519,411,297,490} }, // Combusken + new(259,0,65) { Ability = A0, Moves = new[] {518,127,091,008} }, // Marshtomp + new(699,0,65) { Ability = A0, Moves = new[] {034,087,246,086} }, // Aurorus + new(765,0,65) { Ability = A2, Moves = new[] {689,113,094,473} }, // Oranguru + new(766,0,65) { Ability = A0, Moves = new[] {280,317,164,512} }, // Passimian + new(876,0,65) { Ability = A1, Moves = new[] {595,797,347,247} }, // Indeedee + new(145,0,70) { Ability = A0, Moves = new[] {087,065,413,097} }, // Zapdos + new(146,0,70) { Ability = A0, Moves = new[] {257,017,043,083} }, // Moltres + new(144,0,70) { Ability = A0, Moves = new[] {058,573,542,054} }, // Articuno + new(150,0,70) { Ability = A0, Moves = new[] {094,050,105,059} }, // Mewtwo + new(245,0,70) { Ability = A0, Moves = new[] {710,326,245,347} }, // Suicune + new(244,0,70) { Ability = A0, Moves = new[] {053,184,245,242} }, // Entei + new(243,0,70) { Ability = A0, Moves = new[] {085,336,245,311} }, // Raikou + new(249,0,70) { Ability = A0, Moves = new[] {406,326,250,246} }, // (SH) Lugia + new(250,0,70) { Ability = A0, Moves = new[] {394,326,241,246} }, // (SW) Ho-Oh + new(380,0,70) { Ability = A0, Moves = new[] {513,225,428,057} }, // (SH) Latias + new(381,0,70) { Ability = A0, Moves = new[] {349,406,428,396} }, // (SW) Latios + new(383,0,70) { Ability = A0, Moves = new[] {089,184,436,359} }, // (SW) Groudon + new(382,0,70) { Ability = A0, Moves = new[] {057,034,392,087} }, // (SH) Kyogre + new(384,0,70) { Ability = A0, Moves = new[] {620,693,245,239} }, // Rayquaza + new(480,0,70) { Ability = A0, Moves = new[] {094,248,478,247} }, // Uxie + new(482,0,70) { Ability = A0, Moves = new[] {094,605,417,263} }, // Azelf + new(481,0,70) { Ability = A0, Moves = new[] {094,204,577,161} }, // Mesprit + new(483,0,70) { Ability = A0, Moves = new[] {163,246,430,337} }, // (SW) Dialga + new(484,0,70) { Ability = A0, Moves = new[] {163,057,246,337} }, // (SH) Palkia + new(487,0,70) { Ability = A0, Moves = new[] {337,184,247,246} }, // Giratina + new(485,0,70) { Ability = A0, Moves = new[] {319,436,242,442} }, // Heatran + new(488,0,70) { Ability = A0, Moves = new[] {196,585,427,473} }, // Cresselia + new(641,0,70) { Ability = A0, Moves = new[] {542,097,196,257} }, // (SW) Tornadus + new(642,0,70) { Ability = A0, Moves = new[] {087,240,311,482} }, // (SH) Thundurus + new(645,0,70) { Ability = A0, Moves = new[] {328,157,523,411} }, // Landorus + new(643,0,70) { Ability = A0, Moves = new[] {568,326,558,406} }, // (SW) Reshiram + new(644,0,70) { Ability = A0, Moves = new[] {568,163,559,337} }, // (SH) Zekrom + new(646,0,70) { Ability = A0, Moves = new[] {058,304,247,184} }, // Kyurem + new(716,0,70) { Ability = A0, Moves = new[] {275,605,585,532} }, // (SW) Xerneas + new(717,0,70) { Ability = A0, Moves = new[] {269,613,407,389} }, // (SH) Yveltal + new(718,3,70) { Ability = A0, Moves = new[] {614,616,406,020} }, // Zygarde-3 + new(785,0,70) { Ability = A0, Moves = new[] {085,098,413,269} }, // Tapu Koko + new(786,0,70) { Ability = A0, Moves = new[] {094,583,478,204} }, // Tapu Lele + new(787,0,70) { Ability = A0, Moves = new[] {276,224,452,184} }, // Tapu Bulu + new(788,0,70) { Ability = A0, Moves = new[] {250,352,362,585} }, // Tapu Fini + new(791,0,70) { Ability = A0, Moves = new[] {428,083,231,568} }, // (SW) Solgaleo + new(792,0,70) { Ability = A0, Moves = new[] {247,585,277,129} }, // (SH) Lunala + new(800,0,70) { Ability = A0, Moves = new[] {427,451,408,475} }, // Necrozma + new(793,0,70) { Ability = A0, Moves = new[] {472,482,693,491} }, // Nihilego + new(794,0,70) { Ability = A0, Moves = new[] {612,269,141,223} }, // Buzzwole + new(795,0,70) { Ability = A0, Moves = new[] {136,129,675,679} }, // Pheromosa + new(796,0,70) { Ability = A0, Moves = new[] {438,435,598,693} }, // Xurkitree + new(798,0,70) { Ability = A0, Moves = new[] {410,314,348,014} }, // Kartana + new(797,0,70) { Ability = A0, Moves = new[] {073,479,360,089} }, // Celesteela + new(799,0,70) { Ability = A0, Moves = new[] {407,707,693,005} }, // Guzzlord + new(806,0,70) { Ability = A0, Moves = new[] {421,269,126,428} }, // Blacephalon + new(805,0,70) { Ability = A0, Moves = new[] {157,038,693,475} }, // Stakataka + }; + #endregion } diff --git a/PKHeX.Core/Legality/Encounters/Data/Encounters8b.cs b/PKHeX.Core/Legality/Encounters/Data/Encounters8b.cs index 083ed691a..4b3e904e7 100644 --- a/PKHeX.Core/Legality/Encounters/Data/Encounters8b.cs +++ b/PKHeX.Core/Legality/Encounters/Data/Encounters8b.cs @@ -3,101 +3,100 @@ using static PKHeX.Core.GameVersion; using static PKHeX.Core.AbilityPermission; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class Encounters8b { - internal static class Encounters8b + private static readonly EncounterArea8b[] SlotsBD_OW = EncounterArea8b.GetAreas(Get("bd", "bs"), BD); + private static readonly EncounterArea8b[] SlotsSP_OW = EncounterArea8b.GetAreas(Get("sp", "bs"), SP); + private static readonly EncounterArea8b[] SlotsBD_UG = EncounterArea8b.GetAreas(Get("bd_underground", "bs"), BD); + private static readonly EncounterArea8b[] SlotsSP_UG = EncounterArea8b.GetAreas(Get("sp_underground", "bs"), SP); + + internal static readonly EncounterArea8b[] SlotsBD = ArrayUtil.ConcatAll(SlotsBD_OW, SlotsBD_UG); + internal static readonly EncounterArea8b[] SlotsSP = ArrayUtil.ConcatAll(SlotsSP_OW, SlotsSP_UG); + + static Encounters8b() => MarkEncounterTradeStrings(TradeGift_BDSP, TradeBDSP); + + private static readonly EncounterStatic8b[] Encounter_BDSP = { - private static readonly EncounterArea8b[] SlotsBD_OW = EncounterArea8b.GetAreas(Get("bd", "bs"), BD); - private static readonly EncounterArea8b[] SlotsSP_OW = EncounterArea8b.GetAreas(Get("sp", "bs"), SP); - private static readonly EncounterArea8b[] SlotsBD_UG = EncounterArea8b.GetAreas(Get("bd_underground", "bs"), BD); - private static readonly EncounterArea8b[] SlotsSP_UG = EncounterArea8b.GetAreas(Get("sp_underground", "bs"), SP); + // Gifts + new(BDSP) { Gift = true, Species = 387, Level = 05, Location = 323 }, // Turtwig + new(BDSP) { Gift = true, Species = 390, Level = 05, Location = 323 }, // Chimchar + new(BDSP) { Gift = true, Species = 393, Level = 05, Location = 323 }, // Piplup + new(BDSP) { Gift = true, Species = 133, Level = 05, Location = 104 }, // Eevee + new(BDSP) { Gift = true, Species = 440, Level = 01, EggLocation = 60007, EggCycles = 40 }, // Happiny Egg from Traveling Man + new(BDSP) { Gift = true, Species = 447, Level = 01, EggLocation = 60005, EggCycles = 25 }, // Riolu Egg from Riley - internal static readonly EncounterArea8b[] SlotsBD = ArrayUtil.ConcatAll(SlotsBD_OW, SlotsBD_UG); - internal static readonly EncounterArea8b[] SlotsSP = ArrayUtil.ConcatAll(SlotsSP_OW, SlotsSP_UG); + // Fossils + new(BDSP) { Gift = true, Species = 138, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Omanyte + new(BDSP) { Gift = true, Species = 140, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Kabuto + new(BDSP) { Gift = true, Species = 142, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Aerodactyl + new(BDSP) { Gift = true, Species = 345, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Lileep + new(BDSP) { Gift = true, Species = 347, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Anorith + new(BDSP) { Gift = true, Species = 408, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Cranidos + new(BDSP) { Gift = true, Species = 410, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Shieldon - static Encounters8b() => MarkEncounterTradeStrings(TradeGift_BDSP, TradeBDSP); + // Game-specific gifts + new(BDSP) { Gift = true, Species = 151, Level = 01, Ability = OnlySecond, Location = 438, Shiny = Never, FlawlessIVCount = 3, Fateful = true }, // Mew + new(BDSP) { Gift = true, Species = 385, Level = 05, Ability = OnlySecond, Location = 438, Shiny = Never, FlawlessIVCount = 3, Fateful = true }, // Jirachi - private static readonly EncounterStatic8b[] Encounter_BDSP = - { - // Gifts - new(BDSP) { Gift = true, Species = 387, Level = 05, Location = 323 }, // Turtwig - new(BDSP) { Gift = true, Species = 390, Level = 05, Location = 323 }, // Chimchar - new(BDSP) { Gift = true, Species = 393, Level = 05, Location = 323 }, // Piplup - new(BDSP) { Gift = true, Species = 133, Level = 05, Location = 104 }, // Eevee - new(BDSP) { Gift = true, Species = 440, Level = 01, EggLocation = 60007, EggCycles = 40 }, // Happiny Egg from Traveling Man - new(BDSP) { Gift = true, Species = 447, Level = 01, EggLocation = 60005, EggCycles = 25 }, // Riolu Egg from Riley + // Stationary + new(BDSP) { Species = 425, Level = 22, Location = 197 }, // Drifloon + new(BDSP) { Species = 442, Level = 25, Location = 367 }, // Spiritomb + new(BDSP) { Species = 479, Level = 15, Location = 311 }, // Rotom - // Fossils - new(BDSP) { Gift = true, Species = 138, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Omanyte - new(BDSP) { Gift = true, Species = 140, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Kabuto - new(BDSP) { Gift = true, Species = 142, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Aerodactyl - new(BDSP) { Gift = true, Species = 345, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Lileep - new(BDSP) { Gift = true, Species = 347, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Anorith - new(BDSP) { Gift = true, Species = 408, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Cranidos - new(BDSP) { Gift = true, Species = 410, Level = 01, Location = 049, FlawlessIVCount = 3 }, // Shieldon + // Roamers + new(BDSP) { Species = 481, Level = 50, FlawlessIVCount = 3, Roaming = true }, // Mesprit + new(BDSP) { Species = 488, Level = 50, FlawlessIVCount = 3, Roaming = true }, // Cresselia - // Game-specific gifts - new(BDSP) { Gift = true, Species = 151, Level = 01, Ability = OnlySecond, Location = 438, Shiny = Never, FlawlessIVCount = 3, Fateful = true }, // Mew - new(BDSP) { Gift = true, Species = 385, Level = 05, Ability = OnlySecond, Location = 438, Shiny = Never, FlawlessIVCount = 3, Fateful = true }, // Jirachi + // Legendary + new(BDSP) { Species = 480, Level = 50, Location = 331, FlawlessIVCount = 3 }, // Uxie + new(BDSP) { Species = 482, Level = 50, Location = 328, FlawlessIVCount = 3 }, // Azelf + new(BD ) { Species = 483, Level = 47, Location = 216, FlawlessIVCount = 3 }, // Dialga + new( SP) { Species = 484, Level = 47, Location = 217, FlawlessIVCount = 3 }, // Palkia + new(BDSP) { Species = 485, Level = 70, Location = 262, FlawlessIVCount = 3 }, // Heatran + new(BDSP) { Species = 486, Level = 70, Location = 291, FlawlessIVCount = 3 }, // Regigigas + new(BDSP) { Species = 487, Level = 70, Location = 266, FlawlessIVCount = 3 }, // Giratina - // Stationary - new(BDSP) { Species = 425, Level = 22, Location = 197 }, // Drifloon - new(BDSP) { Species = 442, Level = 25, Location = 367 }, // Spiritomb - new(BDSP) { Species = 479, Level = 15, Location = 311 }, // Rotom + // Mythical + new(BDSP) { Species = 491, Level = 50, Location = 333, FlawlessIVCount = 3, Fateful = true }, // Darkrai + new(BDSP) { Species = 492, Level = 30, Location = 285, FlawlessIVCount = 3, Fateful = true }, // Shaymin + new(BD ) { Species = 493, Level = 80, Location = 218, FlawlessIVCount = 3, Fateful = true }, // Arceus (Brilliant Diamond) + new( SP) { Species = 493, Level = 80, Location = 618, FlawlessIVCount = 3, Fateful = true }, // Arceus (Shining Pearl) - // Roamers - new(BDSP) { Species = 481, Level = 50, FlawlessIVCount = 3, Roaming = true }, // Mesprit - new(BDSP) { Species = 488, Level = 50, FlawlessIVCount = 3, Roaming = true }, // Cresselia + // Ramanas Park (Pure Space) + new( SP) { Species = 144, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Articuno + new( SP) { Species = 145, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Zapdos + new( SP) { Species = 146, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Moltres + new(BD ) { Species = 243, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Raikou + new(BD ) { Species = 244, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Entei + new(BD ) { Species = 245, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Suicune + new(BDSP) { Species = 377, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Regirock + new(BDSP) { Species = 378, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Regice + new(BDSP) { Species = 379, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Registeel + new(BDSP) { Species = 380, Level = 70, Location = 506, FlawlessIVCount = 3 }, // Latias + new(BDSP) { Species = 381, Level = 70, Location = 506, FlawlessIVCount = 3 }, // Latios - // Legendary - new(BDSP) { Species = 480, Level = 50, Location = 331, FlawlessIVCount = 3 }, // Uxie - new(BDSP) { Species = 482, Level = 50, Location = 328, FlawlessIVCount = 3 }, // Azelf - new(BD ) { Species = 483, Level = 47, Location = 216, FlawlessIVCount = 3 }, // Dialga - new( SP) { Species = 484, Level = 47, Location = 217, FlawlessIVCount = 3 }, // Palkia - new(BDSP) { Species = 485, Level = 70, Location = 262, FlawlessIVCount = 3 }, // Heatran - new(BDSP) { Species = 486, Level = 70, Location = 291, FlawlessIVCount = 3 }, // Regigigas - new(BDSP) { Species = 487, Level = 70, Location = 266, FlawlessIVCount = 3 }, // Giratina + // Ramanas Park (Strange Space) + new(BDSP) { Species = 150, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Mewtwo + new( SP) { Species = 249, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Lugia + new(BD ) { Species = 250, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Ho-Oh + new(BDSP) { Species = 382, Level = 70, Location = 507, FlawlessIVCount = 3 }, // Kyogre + new(BDSP) { Species = 383, Level = 70, Location = 507, FlawlessIVCount = 3 }, // Groudon + new(BDSP) { Species = 384, Level = 70, Location = 507, FlawlessIVCount = 3 }, // Rayquaza + }; - // Mythical - new(BDSP) { Species = 491, Level = 50, Location = 333, FlawlessIVCount = 3, Fateful = true }, // Darkrai - new(BDSP) { Species = 492, Level = 30, Location = 285, FlawlessIVCount = 3, Fateful = true }, // Shaymin - new(BD ) { Species = 493, Level = 80, Location = 218, FlawlessIVCount = 3, Fateful = true }, // Arceus (Brilliant Diamond) - new( SP) { Species = 493, Level = 80, Location = 618, FlawlessIVCount = 3, Fateful = true }, // Arceus (Shining Pearl) + internal static readonly EncounterStatic8b[] StaticBD = GetEncounters(Encounter_BDSP, BD); + internal static readonly EncounterStatic8b[] StaticSP = GetEncounters(Encounter_BDSP, SP); - // Ramanas Park (Pure Space) - new( SP) { Species = 144, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Articuno - new( SP) { Species = 145, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Zapdos - new( SP) { Species = 146, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Moltres - new(BD ) { Species = 243, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Raikou - new(BD ) { Species = 244, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Entei - new(BD ) { Species = 245, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Suicune - new(BDSP) { Species = 377, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Regirock - new(BDSP) { Species = 378, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Regice - new(BDSP) { Species = 379, Level = 70, Location = 506, FlawlessIVCount = 3, Ability = OnlyHidden }, // Registeel - new(BDSP) { Species = 380, Level = 70, Location = 506, FlawlessIVCount = 3 }, // Latias - new(BDSP) { Species = 381, Level = 70, Location = 506, FlawlessIVCount = 3 }, // Latios + private const string tradeBDSP = "tradebdsp"; + private static readonly string[][] TradeBDSP = Util.GetLanguageStrings10(tradeBDSP, "zh2"); - // Ramanas Park (Strange Space) - new(BDSP) { Species = 150, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Mewtwo - new( SP) { Species = 249, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Lugia - new(BD ) { Species = 250, Level = 70, Location = 507, FlawlessIVCount = 3, Ability = OnlyHidden }, // Ho-Oh - new(BDSP) { Species = 382, Level = 70, Location = 507, FlawlessIVCount = 3 }, // Kyogre - new(BDSP) { Species = 383, Level = 70, Location = 507, FlawlessIVCount = 3 }, // Groudon - new(BDSP) { Species = 384, Level = 70, Location = 507, FlawlessIVCount = 3 }, // Rayquaza - }; - - internal static readonly EncounterStatic8b[] StaticBD = GetEncounters(Encounter_BDSP, BD); - internal static readonly EncounterStatic8b[] StaticSP = GetEncounters(Encounter_BDSP, SP); - - private const string tradeBDSP = "tradebdsp"; - private static readonly string[][] TradeBDSP = Util.GetLanguageStrings10(tradeBDSP, "zh2"); - - internal static readonly EncounterTrade8b[] TradeGift_BDSP = - { - new(BDSP) { Species = 063, EncryptionConstant = 0x0000008E, PID = 0xFF50A8F5, Level = 09, Ability = OnlyFirst, Gender = 0, OTGender = 0, TID = 25643, IVs = new[] {28,10,09,31,11,03}, Moves = new[] {100,000,000,000}, HeightScalar = 029, WeightScalar = 202, Nature = Nature.Quiet }, // Abra - new(BDSP) { Species = 441, EncryptionConstant = 0x00000867, PID = 0x17DAAB19, Level = 15, Ability = OnlySecond, Gender = 1, OTGender = 0, TID = 44142, IVs = new[] {17,08,29,25,17,23}, Moves = new[] {448,047,064,045}, HeightScalar = 088, WeightScalar = 091, Nature = Nature.Lonely }, // Chatot - new(BDSP) { Species = 093, EncryptionConstant = 0x00000088, PID = 0xF60AB5BB, Level = 33, Ability = OnlyFirst, Gender = 0, OTGender = 0, TID = 19248, IVs = new[] {18,24,28,02,22,30}, Moves = new[] {247,371,389,109}, HeightScalar = 096, WeightScalar = 208, Nature = Nature.Hasty }, // Haunter - new(BDSP) { Species = 129, EncryptionConstant = 0x0000045C, PID = 0xFCE82F88, Level = 45, Ability = OnlyFirst, Gender = 1, OTGender = 0, TID = 53277, IVs = new[] {03,03,31,02,11,03}, Moves = new[] {150,000,000,000}, HeightScalar = 169, WeightScalar = 068, Nature = Nature.Mild }, // Magikarp - }; - } + internal static readonly EncounterTrade8b[] TradeGift_BDSP = + { + new(BDSP) { Species = 063, EncryptionConstant = 0x0000008E, PID = 0xFF50A8F5, Level = 09, Ability = OnlyFirst, Gender = 0, OTGender = 0, TID = 25643, IVs = new[] {28,10,09,31,11,03}, Moves = new[] {100,000,000,000}, HeightScalar = 029, WeightScalar = 202, Nature = Nature.Quiet }, // Abra + new(BDSP) { Species = 441, EncryptionConstant = 0x00000867, PID = 0x17DAAB19, Level = 15, Ability = OnlySecond, Gender = 1, OTGender = 0, TID = 44142, IVs = new[] {17,08,29,25,17,23}, Moves = new[] {448,047,064,045}, HeightScalar = 088, WeightScalar = 091, Nature = Nature.Lonely }, // Chatot + new(BDSP) { Species = 093, EncryptionConstant = 0x00000088, PID = 0xF60AB5BB, Level = 33, Ability = OnlyFirst, Gender = 0, OTGender = 0, TID = 19248, IVs = new[] {18,24,28,02,22,30}, Moves = new[] {247,371,389,109}, HeightScalar = 096, WeightScalar = 208, Nature = Nature.Hasty }, // Haunter + new(BDSP) { Species = 129, EncryptionConstant = 0x0000045C, PID = 0xFCE82F88, Level = 45, Ability = OnlyFirst, Gender = 1, OTGender = 0, TID = 53277, IVs = new[] {03,03,31,02,11,03}, Moves = new[] {150,000,000,000}, HeightScalar = 169, WeightScalar = 068, Nature = Nature.Mild }, // Magikarp + }; } diff --git a/PKHeX.Core/Legality/Encounters/Data/EncountersGO.cs b/PKHeX.Core/Legality/Encounters/Data/EncountersGO.cs index daca23741..58ba5c3d5 100644 --- a/PKHeX.Core/Legality/Encounters/Data/EncountersGO.cs +++ b/PKHeX.Core/Legality/Encounters/Data/EncountersGO.cs @@ -1,8 +1,8 @@ -namespace PKHeX.Core -{ - /// - /// Encounter data from , which has multiple generations of origin. - /// +namespace PKHeX.Core; + +/// +/// Encounter data from , which has multiple generations of origin. +/// #if !DEBUG internal static class EncountersGO { @@ -12,25 +12,24 @@ internal static class EncountersGO internal static readonly EncounterArea8g[] SlotsGO = EncounterArea8g.GetArea(EncounterUtil.Get("go_home", "go")); } #else - public static class EncountersGO +public static class EncountersGO +{ + internal const int MAX_LEVEL = 50; + + internal static EncounterArea7g[] SlotsGO_GG = EncounterArea7g.GetArea(Get("go_lgpe", "go")); + internal static EncounterArea8g[] SlotsGO = EncounterArea8g.GetArea(Get("go_home", "go")); + + public static void Reload() { - internal const int MAX_LEVEL = 50; - - internal static EncounterArea7g[] SlotsGO_GG = EncounterArea7g.GetArea(Get("go_lgpe", "go")); - internal static EncounterArea8g[] SlotsGO = EncounterArea8g.GetArea(Get("go_home", "go")); - - public static void Reload() - { - SlotsGO_GG = EncounterArea7g.GetArea(Get("go_lgpe", "go")); - SlotsGO = EncounterArea8g.GetArea(Get("go_home", "go")); - } - - private static BinLinkerAccessor Get(string resource, string ident) - { - var name = $"encounter_{resource}.pkl"; - var data = System.IO.File.Exists(name) ? System.IO.File.ReadAllBytes(name) : Util.GetBinaryResource(name); - return BinLinkerAccessor.Get(data, ident); - } + SlotsGO_GG = EncounterArea7g.GetArea(Get("go_lgpe", "go")); + SlotsGO = EncounterArea8g.GetArea(Get("go_home", "go")); + } + + private static BinLinkerAccessor Get(string resource, string ident) + { + var name = $"encounter_{resource}.pkl"; + var data = System.IO.File.Exists(name) ? System.IO.File.ReadAllBytes(name) : Util.GetBinaryResource(name); + return BinLinkerAccessor.Get(data, ident); } -#endif } +#endif \ No newline at end of file diff --git a/PKHeX.Core/Legality/Encounters/Data/EncountersWC3.cs b/PKHeX.Core/Legality/Encounters/Data/EncountersWC3.cs index 493ea264f..d2acf5ed3 100644 --- a/PKHeX.Core/Legality/Encounters/Data/EncountersWC3.cs +++ b/PKHeX.Core/Legality/Encounters/Data/EncountersWC3.cs @@ -1,269 +1,268 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 Gifts +/// +/// +/// Generation 3 has a wide range of PIDIV types and other restrictions, and was never consistently stored in raw bytes. +/// Normally we'd just load the data from a binary, but without raw data... hard-code everything by hand. +/// +internal static class EncountersWC3 { - /// - /// Generation 3 Gifts - /// - /// - /// Generation 3 has a wide range of PIDIV types and other restrictions, and was never consistently stored in raw bytes. - /// Normally we'd just load the data from a binary, but without raw data... hard-code everything by hand. - /// - internal static class EncountersWC3 + internal static readonly WC3[] Encounter_Event3_Special = { - internal static readonly WC3[] Encounter_Event3_Special = - { - new() { Species = 385, Level = 05, TID = 20043, OT_Gender = 0, Version = GameVersion.R, Method = PIDType.BACD_R, OT_Name = "WISHMKR", CardTitle = "Wishmaker Jirachi", Language = (int)LanguageID.English }, - }; + new() { Species = 385, Level = 05, TID = 20043, OT_Gender = 0, Version = GameVersion.R, Method = PIDType.BACD_R, OT_Name = "WISHMKR", CardTitle = "Wishmaker Jirachi", Language = (int)LanguageID.English }, + }; - private static IEnumerable GetIngameCXDData() - { - var langs = new[]{LanguageID.Japanese, LanguageID.English, LanguageID.French, LanguageID.Italian, LanguageID.German, LanguageID.Spanish}; - string[] p = {string.Empty, "コロシアム", "COLOS", "COLOSSEUM", "ARENA", "COLOSSEUM", string.Empty, "CLAUDIO" }; - string[] c = {string.Empty, "アゲト", "AGATE", "SAMARAGD", "SOFO", "EMERITAE", string.Empty, "ÁGATA" }; - string[] h = {string.Empty, "ダニー", "HORDEL", "VOLKER", "ODINO", "HORAZ", string.Empty, "HORDEL"}; - string[] d = {string.Empty, "ギンザル", "DUKING", "DOKING", "RODRIGO", "GRAND", string.Empty, "GERMÁN"}; - string[] m = {string.Empty, "バトルやま", "MATTLE", "MT BATAILL", "MONTE LOTT", "DUELLBERG", string.Empty, "ERNESTO"}; // truncated on ck3->pk3 transfer - string[] z = {string.Empty, "コンセント", "ZAPRONG", "ZAPRONG", "ZAPRONG", "ZAPRONG", string.Empty, "ZAPRONG"}; + private static IEnumerable GetIngameCXDData() + { + var langs = new[]{LanguageID.Japanese, LanguageID.English, LanguageID.French, LanguageID.Italian, LanguageID.German, LanguageID.Spanish}; + string[] p = {string.Empty, "コロシアム", "COLOS", "COLOSSEUM", "ARENA", "COLOSSEUM", string.Empty, "CLAUDIO" }; + string[] c = {string.Empty, "アゲト", "AGATE", "SAMARAGD", "SOFO", "EMERITAE", string.Empty, "ÁGATA" }; + string[] h = {string.Empty, "ダニー", "HORDEL", "VOLKER", "ODINO", "HORAZ", string.Empty, "HORDEL"}; + string[] d = {string.Empty, "ギンザル", "DUKING", "DOKING", "RODRIGO", "GRAND", string.Empty, "GERMÁN"}; + string[] m = {string.Empty, "バトルやま", "MATTLE", "MT BATAILL", "MONTE LOTT", "DUELLBERG", string.Empty, "ERNESTO"}; // truncated on ck3->pk3 transfer + string[] z = {string.Empty, "コンセント", "ZAPRONG", "ZAPRONG", "ZAPRONG", "ZAPRONG", string.Empty, "ZAPRONG"}; - return langs.SelectMany(l => GetIngame((int)l)); - IEnumerable GetIngame(int l) + return langs.SelectMany(l => GetIngame((int)l)); + IEnumerable GetIngame(int l) + { + var id = (LanguageID) l; + var nd = id != LanguageID.Japanese; + return new WC3[] { - var id = (LanguageID) l; - var nd = id != LanguageID.Japanese; - return new WC3[] - { - // Colosseum - new() { Species = 025, Level = 10, Language = l, Location = 255, TID = 31121, SID = 0, OT_Gender = 0, OT_Name = p[l], Version = GameVersion.R, CardTitle = $"Colosseum Pikachu ({id})",Method = PIDType.CXD, Shiny = Shiny.Never, NotDistributed = nd }, // Colosseum Pikachu bonus gift - new() { Species = 251, Level = 10, Language = l, Location = 255, TID = 31121, SID = 0, OT_Gender = 1, OT_Name = c[l], Version = GameVersion.R, CardTitle = $"Agate Celebi ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, NotDistributed = nd }, // Ageto Celebi bonus gift - new() { Species = 311, Level = 13, Language = l, Location = 254, TID = 37149, SID = 0, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.COLO, CardTitle = $"Special Gift ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, Moves = new[] { 045, 086, 098, 270 } }, // Plusle @ Ingame Trade - new() { Species = 250, Level = 70, Language = l, Location = 255, TID = 10048, SID = 0, OT_Gender = 0, OT_Name = m[l], Version = GameVersion.S, CardTitle = $"Mt. Battle Ho-Oh ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, Moves = new[] { 105, 126, 241, 129 } }, // Ho-oh @ Mt. Battle - // XD - new() { Species = 239, Level = 20, Language = l, Location = 164, TID = 41400, SID = -1, OT_Gender = 0, OT_Name = h[l], Version = GameVersion.XD, CardTitle = $"Trade Togepi ({id})", Method = PIDType.CXD, Moves = new[] { 008, 007, 009, 238 }, Fateful = true, Nickname = z[l] }, // Elekid @ Snagem Hideout - new() { Species = 307, Level = 20, Language = l, Location = 116, TID = 37149, SID = -1, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Trapinch ({id})", Method = PIDType.CXD, Moves = new[] { 223, 093, 247, 197 }, Fateful = true }, // Meditite @ Pyrite Town - new() { Species = 213, Level = 20, Language = l, Location = 116, TID = 37149, SID = -1, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Surskit ({id})", Method = PIDType.CXD, Moves = new[] { 092, 164, 188, 227 }, Fateful = true }, // Shuckle @ Pyrite Town - new() { Species = 246, Level = 20, Language = l, Location = 116, TID = 37149, SID = -1, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Wooper ({id})", Method = PIDType.CXD, Moves = new[] { 201, 349, 044, 200 }, Fateful = true }, // Larvitar @ Pyrite Town - }; - } + // Colosseum + new() { Species = 025, Level = 10, Language = l, Location = 255, TID = 31121, SID = 0, OT_Gender = 0, OT_Name = p[l], Version = GameVersion.R, CardTitle = $"Colosseum Pikachu ({id})",Method = PIDType.CXD, Shiny = Shiny.Never, NotDistributed = nd }, // Colosseum Pikachu bonus gift + new() { Species = 251, Level = 10, Language = l, Location = 255, TID = 31121, SID = 0, OT_Gender = 1, OT_Name = c[l], Version = GameVersion.R, CardTitle = $"Agate Celebi ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, NotDistributed = nd }, // Ageto Celebi bonus gift + new() { Species = 311, Level = 13, Language = l, Location = 254, TID = 37149, SID = 0, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.COLO, CardTitle = $"Special Gift ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, Moves = new[] { 045, 086, 098, 270 } }, // Plusle @ Ingame Trade + new() { Species = 250, Level = 70, Language = l, Location = 255, TID = 10048, SID = 0, OT_Gender = 0, OT_Name = m[l], Version = GameVersion.S, CardTitle = $"Mt. Battle Ho-Oh ({id})", Method = PIDType.CXD, Shiny = Shiny.Never, Moves = new[] { 105, 126, 241, 129 } }, // Ho-oh @ Mt. Battle + // XD + new() { Species = 239, Level = 20, Language = l, Location = 164, TID = 41400, SID = -1, OT_Gender = 0, OT_Name = h[l], Version = GameVersion.XD, CardTitle = $"Trade Togepi ({id})", Method = PIDType.CXD, Moves = new[] { 008, 007, 009, 238 }, Fateful = true, Nickname = z[l] }, // Elekid @ Snagem Hideout + new() { Species = 307, Level = 20, Language = l, Location = 116, TID = 37149, SID = -1, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Trapinch ({id})", Method = PIDType.CXD, Moves = new[] { 223, 093, 247, 197 }, Fateful = true }, // Meditite @ Pyrite Town + new() { Species = 213, Level = 20, Language = l, Location = 116, TID = 37149, SID = -1, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Surskit ({id})", Method = PIDType.CXD, Moves = new[] { 092, 164, 188, 227 }, Fateful = true }, // Shuckle @ Pyrite Town + new() { Species = 246, Level = 20, Language = l, Location = 116, TID = 37149, SID = -1, OT_Gender = 0, OT_Name = d[l], Version = GameVersion.XD, CardTitle = $"Trade Wooper ({id})", Method = PIDType.CXD, Moves = new[] { 201, 349, 044, 200 }, Fateful = true }, // Larvitar @ Pyrite Town + }; } - - internal static readonly WC3[] Encounter_Event3 = Encounter_Event3_Special.Concat(GetIngameCXDData()).ToArray(); - - internal static readonly WC3[] Encounter_Event3_FRLG = - { - // PCJP - Egg Pokémon Present Eggs (March 21 to April 4, 2004) - new() { Species = 043, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{073} }, // Oddish with Leech Seed - new() { Species = 052, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{080} }, // Meowth with Petal Dance - new() { Species = 060, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{186} }, // Poliwag with Sweet Kiss - new() { Species = 069, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{298} }, // Bellsprout with Teeter Dance - - // PCNY - Wish Eggs (December 16, 2004, to January 2, 2005) - new() { Species = 083, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 281} }, // Farfetch'd with Wish & Yawn - new() { Species = 096, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 187} }, // Drowzee with Wish & Belly Drum - new() { Species = 102, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 230} }, // Exeggcute with Wish & Sweet Scent - new() { Species = 108, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 215} }, // Lickitung with Wish & Heal Bell - new() { Species = 113, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 230} }, // Chansey with Wish & Sweet Scent - new() { Species = 115, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 281} }, // Kangaskhan with Wish & Yawn - - // PokePark Eggs - Wondercard - new() { Species = 054, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{300}, Method = PIDType.Method_2 }, // Psyduck with Mud Sport - new() { Species = 172, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{266}, Method = PIDType.Method_2 }, // Pichu with Follow me - new() { Species = 174, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{321}, Method = PIDType.Method_2 }, // Igglybuff with Tickle - new() { Species = 222, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{300}, Method = PIDType.Method_2 }, // Corsola with Mud Sport - new() { Species = 276, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{297}, Method = PIDType.Method_2 }, // Taillow with Feather Dance - new() { Species = 283, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{300}, Method = PIDType.Method_2 }, // Surskit with Mud Sport - new() { Species = 293, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{298}, Method = PIDType.Method_2 }, // Whismur with Teeter Dance - new() { Species = 300, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{205}, Method = PIDType.Method_2 }, // Skitty with Rollout - new() { Species = 311, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{346}, Method = PIDType.Method_2 }, // Plusle with Water Sport - new() { Species = 312, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{300}, Method = PIDType.Method_2 }, // Minun with Mud Sport - new() { Species = 325, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{253}, Method = PIDType.Method_2 }, // Spoink with Uproar - new() { Species = 327, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{047}, Method = PIDType.Method_2 }, // Spinda with Sing - new() { Species = 331, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{227}, Method = PIDType.Method_2 }, // Cacnea with Encore - new() { Species = 341, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{346}, Method = PIDType.Method_2 }, // Corphish with Water Sport - new() { Species = 360, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{321}, Method = PIDType.Method_2 }, // Wynaut with Tickle - }; - - internal static readonly WC3[] Encounter_Event3_RS = - { - // PCJP - Pokémon Center 5th Anniversary Eggs (April 25 to May 18, 2003) - new() { Species = 172, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{298} }, // Pichu with Teeter Dance - new() { Species = 172, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{273} }, // Pichu with Wish - new() { Species = 172, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R_S, Moves = new[]{298} }, // Pichu with Teeter Dance - new() { Species = 172, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R_S, Moves = new[]{273} }, // Pichu with Wish - new() { Species = 280, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{204 } }, // Ralts with Charm - new() { Species = 280, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{273} }, // Ralts with Wish - new() { Species = 359, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{180} }, // Absol with Spite - new() { Species = 359, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{273} }, // Absol with Wish - new() { Species = 371, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{334} }, // Bagon with Iron Defense - new() { Species = 371, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{273} }, // Bagon with Wish - - // Negai Boshi Jirachi - new() { Species = 385, Level = 05, TID = 30719, OT_Gender = 0, OT_Name = "ネガイボシ", Version = GameVersion.R, Method = PIDType.BACD_R, Language = (int)LanguageID.Japanese, Shiny = Shiny.Never }, - new() { Species = 385, Level = 05, TID = 30719, OT_Name = "ネガイボシ", Version = GameVersion.RS, Method = PIDType.BACD_U_AX, Language = (int)LanguageID.Japanese, Shiny = Shiny.Never }, - - // Berry Glitch Fix - // PCJP - (December 29, 2003 to March 31, 2004) - new() { Species = 263, Level = 5, Version = GameVersion.S, Language = (int)LanguageID.Japanese, Method = PIDType.BACD_R_S, TID = 21121, OT_Name = "ルビー", OT_Gender = 1, Shiny = Shiny.Always }, - new() { Species = 263, Level = 5, Version = GameVersion.S, Language = (int)LanguageID.Japanese, Method = PIDType.BACD_R_S, TID = 21121, OT_Name = "サファイア", OT_Gender = 0, Shiny = Shiny.Always }, - - // EBGames/GameStop (March 1, 2004 to April 22, 2007), also via multi-game discs - new() { Species = 263, Level = 5, Version = GameVersion.S, Language = (int)LanguageID.English, Method = PIDType.BACD_R_S, TID = 30317, OT_Name = "RUBY", OT_Gender = 1 }, - new() { Species = 263, Level = 5, Version = GameVersion.S, Language = (int)LanguageID.English, Method = PIDType.BACD_R_S, TID = 30317, OT_Name = "SAPHIRE", OT_Gender = 0 }, - - // Channel Jirachi - new() { Species = 385, Level = 5, Version = GameVersion.RS, Method = PIDType.Channel, TID = 40122, OT_Gender = 3,SID = -1, OT_Name = "CHANNEL", CardTitle = "Channel Jirachi", Met_Level = 0 }, - - // Aura Mew - new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 20078, OT_Name = "Aura", Fateful = true, Shiny = Shiny.Never }, // Mew - new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 20078, OT_Name = "Aura", Fateful = true, Shiny = Shiny.Never }, // Mew - new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 20078, OT_Name = "Aura", Fateful = true, Shiny = Shiny.Never }, // Mew - new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 20078, OT_Name = "Aura", Fateful = true, Shiny = Shiny.Never }, // Mew - new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 20078, OT_Name = "Aura", Fateful = true, Shiny = Shiny.Never }, // Mew - - // English Events - new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Charizard - new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Pikachu - new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Articuno - new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Raikou - new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Entei - new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Suicune - new() { Species = 249, Level = 70, Version = GameVersion.R, Moves = new[] {105,056,240,129}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Lugia - new() { Species = 250, Level = 70, Version = GameVersion.R, Moves = new[] {105,126,241,129}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Ho-Oh - new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Latias - new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Latios - - // French - new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Charizard - new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Pikachu - new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Articuno - new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Raikou - new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Entei - new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Suicune - new() { Species = 249, Level = 70, Version = GameVersion.R, Moves = new[] {105,056,240,129}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Lugia - new() { Species = 250, Level = 70, Version = GameVersion.R, Moves = new[] {105,126,241,129}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Ho-Oh - new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Latias - new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Latios - - // Italian - new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Charizard - new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Pikachu - new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Articuno - new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Raikou - new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Entei - new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Suicune - new() { Species = 249, Level = 70, Version = GameVersion.R, Moves = new[] {105,056,240,129}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Lugia - new() { Species = 250, Level = 70, Version = GameVersion.R, Moves = new[] {105,126,241,129}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Ho-Oh - new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Latias - new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Latios - - // German - new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Charizard - new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Pikachu - new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Articuno - new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Raikou - new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Entei - new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Suicune - new() { Species = 249, Level = 70, Version = GameVersion.R, Moves = new[] {105,056,240,129}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Lugia - new() { Species = 250, Level = 70, Version = GameVersion.R, Moves = new[] {105,126,241,129}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Ho-Oh - new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Latias - new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Latios - - // Spanish - new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Charizard - new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Pikachu - new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Articuno - new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Raikou - new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Entei - new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Suicune - new() { Species = 249, Level = 70, Version = GameVersion.R, Moves = new[] {105,056,240,129}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Lugia - new() { Species = 250, Level = 70, Version = GameVersion.R, Moves = new[] {105,126,241,129}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Ho-Oh - new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Latias - new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Latios - - new() { Species = 375, Level = 30, Version = GameVersion.R, Moves = new[] {036,093,232,287}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 02005, OT_Name = "ROCKS", OT_Gender = 0, RibbonNational = true, Shiny = Shiny.Never }, // Metang - new() { Species = 386, Level = 70, Version = GameVersion.R, Moves = new[] {322,105,354,063}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 28606, OT_Name = "DOEL", Fateful = true, Shiny = Shiny.Never }, // Deoxys - new() { Species = 386, Level = 70, Version = GameVersion.R, Moves = new[] {322,105,354,063}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "SPACE C", Fateful = true, Shiny = Shiny.Never }, // Deoxys - new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.English, Method = PIDType.BACD_U, TID = 06930, OT_Name = "MYSTRY", Fateful = true, Shiny = Shiny.Never }, // Mew - new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06930, OT_Name = "MYSTRY", Fateful = true, Shiny = Shiny.Never }, // Mew - - // Party of the Decade - new() { Species = 001, Level = 70, Version = GameVersion.R, Moves = new[] {230,074,076,235}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Bulbasaur - new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Charizard - new() { Species = 009, Level = 70, Version = GameVersion.R, Moves = new[] {182,240,130,056}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Blastoise - new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,087,113,019}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", HeldItem = 202, Shiny = Shiny.Never }, // Pikachu (Fly) - new() { Species = 065, Level = 70, Version = GameVersion.R, Moves = new[] {248,347,094,271}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Alakazam - new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Articuno - new() { Species = 145, Level = 70, Version = GameVersion.R, Moves = new[] {097,197,065,268}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Zapdos - new() { Species = 146, Level = 70, Version = GameVersion.R, Moves = new[] {097,203,053,219}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Moltres - new() { Species = 149, Level = 70, Version = GameVersion.R, Moves = new[] {097,219,017,200}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Dragonite - new() { Species = 157, Level = 70, Version = GameVersion.R, Moves = new[] {098,172,129,053}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Typhlosion - new() { Species = 196, Level = 70, Version = GameVersion.R, Moves = new[] {060,244,094,234}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Espeon - new() { Species = 197, Level = 70, Version = GameVersion.R, Moves = new[] {185,212,103,236}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Umbreon - new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Raikou - new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Entei - new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Suicune - new() { Species = 248, Level = 70, Version = GameVersion.R, Moves = new[] {037,184,242,089}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Tyranitar - new() { Species = 257, Level = 70, Version = GameVersion.R, Moves = new[] {299,163,119,327}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Blaziken - new() { Species = 359, Level = 70, Version = GameVersion.R, Moves = new[] {104,163,248,195}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Absol - new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", HeldItem = 191, Shiny = Shiny.Never }, // Latias - new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", HeldItem = 191, Shiny = Shiny.Never }, // Latios - - // Journey Across America - new() { Species = 001, Level = 70, Version = GameVersion.R, Moves = new[] {230,074,076,235}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Bulbasaur - new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Charizard - new() { Species = 009, Level = 70, Version = GameVersion.R, Moves = new[] {182,240,130,056}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Blastoise - new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", HeldItem = 202, Shiny = Shiny.Never }, // Pikachu (No Fly) - new() { Species = 065, Level = 70, Version = GameVersion.R, Moves = new[] {248,347,094,271}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Alakazam - new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Articuno - new() { Species = 145, Level = 70, Version = GameVersion.R, Moves = new[] {097,197,065,268}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Zapdos - new() { Species = 146, Level = 70, Version = GameVersion.R, Moves = new[] {097,203,053,219}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Moltres - new() { Species = 149, Level = 70, Version = GameVersion.R, Moves = new[] {097,219,017,200}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Dragonite - new() { Species = 157, Level = 70, Version = GameVersion.R, Moves = new[] {098,172,129,053}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Typhlosion - new() { Species = 196, Level = 70, Version = GameVersion.R, Moves = new[] {060,244,094,234}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Espeon - new() { Species = 197, Level = 70, Version = GameVersion.R, Moves = new[] {185,212,103,236}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Umbreon - new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Raikou - new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Entei - new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Suicune - new() { Species = 248, Level = 70, Version = GameVersion.R, Moves = new[] {037,184,242,089}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Tyranitar - new() { Species = 251, Level = 70, Version = GameVersion.R, Moves = new[] {246,248,226,195}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Celebi - new() { Species = 257, Level = 70, Version = GameVersion.R, Moves = new[] {299,163,119,327}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Blaziken - new() { Species = 359, Level = 70, Version = GameVersion.R, Moves = new[] {104,163,248,195}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Absol - new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", HeldItem = 191, Shiny = Shiny.Never }, // Latias - new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", HeldItem = 191, Shiny = Shiny.Never }, // Latios - }; - - internal static readonly WC3[] Encounter_Event3_Common = - { - // Pokémon Box -- RSE Recipient - new() { Species = 333, IsEgg = true, Level = 05, Moves = new[]{206}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.RSE }, // Swablu Egg with False Swipe - new() { Species = 263, IsEgg = true, Level = 05, Moves = new[]{245}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.RSE }, // Zigzagoon Egg with Extreme Speed - new() { Species = 300, IsEgg = true, Level = 05, Moves = new[]{006}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.RSE }, // Skitty Egg with Pay Day - new() { Species = 172, IsEgg = true, Level = 05, Moves = new[]{057}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.RSE }, // Pichu Egg with Surf - // Pokémon Box -- FRLG Recipient - new() { Species = 333, IsEgg = true, Level = 05, Moves = new[]{206}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.FRLG }, // Swablu Egg with False Swipe - new() { Species = 263, IsEgg = true, Level = 05, Moves = new[]{245}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.FRLG }, // Zigzagoon Egg with Extreme Speed - new() { Species = 300, IsEgg = true, Level = 05, Moves = new[]{006}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.FRLG }, // Skitty Egg with Pay Day - new() { Species = 172, IsEgg = true, Level = 05, Moves = new[]{057}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.FRLG }, // Pichu Egg with Surf - - // PokePark Eggs - DS Download Play - new() { Species = 054, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{300}, Method = PIDType.BACD_R }, // Psyduck with Mud Sport - new() { Species = 172, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{266}, Method = PIDType.BACD_R }, // Pichu with Follow me - new() { Species = 174, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{321}, Method = PIDType.BACD_R }, // Igglybuff with Tickle - new() { Species = 222, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{300}, Method = PIDType.BACD_R }, // Corsola with Mud Sport - new() { Species = 276, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{297}, Method = PIDType.BACD_R }, // Taillow with Feather Dance - new() { Species = 283, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{300}, Method = PIDType.BACD_R }, // Surskit with Mud Sport - new() { Species = 293, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{298}, Method = PIDType.BACD_R }, // Whismur with Teeter Dance - new() { Species = 300, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{205}, Method = PIDType.BACD_R }, // Skitty with Rollout - new() { Species = 311, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{346}, Method = PIDType.BACD_R }, // Plusle with Water Sport - new() { Species = 312, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{300}, Method = PIDType.BACD_R }, // Minun with Mud Sport - new() { Species = 325, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{253}, Method = PIDType.BACD_R }, // Spoink with Uproar - new() { Species = 327, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{047}, Method = PIDType.BACD_R }, // Spinda with Sing - new() { Species = 331, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{227}, Method = PIDType.BACD_R }, // Cacnea with Encore - new() { Species = 341, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{346}, Method = PIDType.BACD_R }, // Corphish with Water Sport - new() { Species = 360, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{321}, Method = PIDType.BACD_R }, // Wynaut with Tickle - }; - - internal static readonly WC3[] Encounter_WC3 = ArrayUtil.ConcatAll(Encounter_Event3, Encounter_Event3_RS, Encounter_Event3_FRLG, Encounter_Event3_Common); } + + internal static readonly WC3[] Encounter_Event3 = Encounter_Event3_Special.Concat(GetIngameCXDData()).ToArray(); + + internal static readonly WC3[] Encounter_Event3_FRLG = + { + // PCJP - Egg Pokémon Present Eggs (March 21 to April 4, 2004) + new() { Species = 043, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{073} }, // Oddish with Leech Seed + new() { Species = 052, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{080} }, // Meowth with Petal Dance + new() { Species = 060, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{186} }, // Poliwag with Sweet Kiss + new() { Species = 069, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{298} }, // Bellsprout with Teeter Dance + + // PCNY - Wish Eggs (December 16, 2004, to January 2, 2005) + new() { Species = 083, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 281} }, // Farfetch'd with Wish & Yawn + new() { Species = 096, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 187} }, // Drowzee with Wish & Belly Drum + new() { Species = 102, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 230} }, // Exeggcute with Wish & Sweet Scent + new() { Species = 108, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 215} }, // Lickitung with Wish & Heal Bell + new() { Species = 113, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 230} }, // Chansey with Wish & Sweet Scent + new() { Species = 115, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Method = PIDType.Method_2, Moves = new[]{273, 281} }, // Kangaskhan with Wish & Yawn + + // PokePark Eggs - Wondercard + new() { Species = 054, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{300}, Method = PIDType.Method_2 }, // Psyduck with Mud Sport + new() { Species = 172, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{266}, Method = PIDType.Method_2 }, // Pichu with Follow me + new() { Species = 174, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{321}, Method = PIDType.Method_2 }, // Igglybuff with Tickle + new() { Species = 222, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{300}, Method = PIDType.Method_2 }, // Corsola with Mud Sport + new() { Species = 276, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{297}, Method = PIDType.Method_2 }, // Taillow with Feather Dance + new() { Species = 283, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{300}, Method = PIDType.Method_2 }, // Surskit with Mud Sport + new() { Species = 293, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{298}, Method = PIDType.Method_2 }, // Whismur with Teeter Dance + new() { Species = 300, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{205}, Method = PIDType.Method_2 }, // Skitty with Rollout + new() { Species = 311, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{346}, Method = PIDType.Method_2 }, // Plusle with Water Sport + new() { Species = 312, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{300}, Method = PIDType.Method_2 }, // Minun with Mud Sport + new() { Species = 325, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{253}, Method = PIDType.Method_2 }, // Spoink with Uproar + new() { Species = 327, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{047}, Method = PIDType.Method_2 }, // Spinda with Sing + new() { Species = 331, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{227}, Method = PIDType.Method_2 }, // Cacnea with Encore + new() { Species = 341, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{346}, Method = PIDType.Method_2 }, // Corphish with Water Sport + new() { Species = 360, IsEgg = true, Fateful = true, Level = 05, TID = -1, SID = -1, Version = GameVersion.FRLG, Moves = new[]{321}, Method = PIDType.Method_2 }, // Wynaut with Tickle + }; + + internal static readonly WC3[] Encounter_Event3_RS = + { + // PCJP - Pokémon Center 5th Anniversary Eggs (April 25 to May 18, 2003) + new() { Species = 172, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{298} }, // Pichu with Teeter Dance + new() { Species = 172, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{273} }, // Pichu with Wish + new() { Species = 172, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R_S, Moves = new[]{298} }, // Pichu with Teeter Dance + new() { Species = 172, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R_S, Moves = new[]{273} }, // Pichu with Wish + new() { Species = 280, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{204 } }, // Ralts with Charm + new() { Species = 280, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{273} }, // Ralts with Wish + new() { Species = 359, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{180} }, // Absol with Spite + new() { Species = 359, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{273} }, // Absol with Wish + new() { Species = 371, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{334} }, // Bagon with Iron Defense + new() { Species = 371, IsEgg = true, Level = 05, OT_Name = "オヤNAME", TID = -1, SID = -1, Version = GameVersion.R, Method = PIDType.BACD_R, Moves = new[]{273} }, // Bagon with Wish + + // Negai Boshi Jirachi + new() { Species = 385, Level = 05, TID = 30719, OT_Gender = 0, OT_Name = "ネガイボシ", Version = GameVersion.R, Method = PIDType.BACD_R, Language = (int)LanguageID.Japanese, Shiny = Shiny.Never }, + new() { Species = 385, Level = 05, TID = 30719, OT_Name = "ネガイボシ", Version = GameVersion.RS, Method = PIDType.BACD_U_AX, Language = (int)LanguageID.Japanese, Shiny = Shiny.Never }, + + // Berry Glitch Fix + // PCJP - (December 29, 2003 to March 31, 2004) + new() { Species = 263, Level = 5, Version = GameVersion.S, Language = (int)LanguageID.Japanese, Method = PIDType.BACD_R_S, TID = 21121, OT_Name = "ルビー", OT_Gender = 1, Shiny = Shiny.Always }, + new() { Species = 263, Level = 5, Version = GameVersion.S, Language = (int)LanguageID.Japanese, Method = PIDType.BACD_R_S, TID = 21121, OT_Name = "サファイア", OT_Gender = 0, Shiny = Shiny.Always }, + + // EBGames/GameStop (March 1, 2004 to April 22, 2007), also via multi-game discs + new() { Species = 263, Level = 5, Version = GameVersion.S, Language = (int)LanguageID.English, Method = PIDType.BACD_R_S, TID = 30317, OT_Name = "RUBY", OT_Gender = 1 }, + new() { Species = 263, Level = 5, Version = GameVersion.S, Language = (int)LanguageID.English, Method = PIDType.BACD_R_S, TID = 30317, OT_Name = "SAPHIRE", OT_Gender = 0 }, + + // Channel Jirachi + new() { Species = 385, Level = 5, Version = GameVersion.RS, Method = PIDType.Channel, TID = 40122, OT_Gender = 3,SID = -1, OT_Name = "CHANNEL", CardTitle = "Channel Jirachi", Met_Level = 0 }, + + // Aura Mew + new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 20078, OT_Name = "Aura", Fateful = true, Shiny = Shiny.Never }, // Mew + new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 20078, OT_Name = "Aura", Fateful = true, Shiny = Shiny.Never }, // Mew + new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 20078, OT_Name = "Aura", Fateful = true, Shiny = Shiny.Never }, // Mew + new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 20078, OT_Name = "Aura", Fateful = true, Shiny = Shiny.Never }, // Mew + new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 20078, OT_Name = "Aura", Fateful = true, Shiny = Shiny.Never }, // Mew + + // English Events + new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Charizard + new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Pikachu + new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Articuno + new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Raikou + new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Entei + new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Suicune + new() { Species = 249, Level = 70, Version = GameVersion.R, Moves = new[] {105,056,240,129}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Lugia + new() { Species = 250, Level = 70, Version = GameVersion.R, Moves = new[] {105,126,241,129}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Ho-Oh + new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Latias + new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Latios + + // French + new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Charizard + new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Pikachu + new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Articuno + new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Raikou + new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Entei + new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Suicune + new() { Species = 249, Level = 70, Version = GameVersion.R, Moves = new[] {105,056,240,129}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Lugia + new() { Species = 250, Level = 70, Version = GameVersion.R, Moves = new[] {105,126,241,129}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Ho-Oh + new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Latias + new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.French, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNIV", Shiny = Shiny.Never }, // Latios + + // Italian + new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Charizard + new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Pikachu + new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Articuno + new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Raikou + new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Entei + new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Suicune + new() { Species = 249, Level = 70, Version = GameVersion.R, Moves = new[] {105,056,240,129}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Lugia + new() { Species = 250, Level = 70, Version = GameVersion.R, Moves = new[] {105,126,241,129}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Ho-Oh + new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Latias + new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.Italian, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANNI", Shiny = Shiny.Never }, // Latios + + // German + new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Charizard + new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Pikachu + new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Articuno + new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Raikou + new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Entei + new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Suicune + new() { Species = 249, Level = 70, Version = GameVersion.R, Moves = new[] {105,056,240,129}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Lugia + new() { Species = 250, Level = 70, Version = GameVersion.R, Moves = new[] {105,126,241,129}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Ho-Oh + new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Latias + new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.German, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10JAHRE", Shiny = Shiny.Never }, // Latios + + // Spanish + new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Charizard + new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Pikachu + new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Articuno + new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Raikou + new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Entei + new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Suicune + new() { Species = 249, Level = 70, Version = GameVersion.R, Moves = new[] {105,056,240,129}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Lugia + new() { Species = 250, Level = 70, Version = GameVersion.R, Moves = new[] {105,126,241,129}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Ho-Oh + new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Latias + new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.Spanish, Method = PIDType.BACD_R, TID = 06227, OT_Name = "10ANIV", Shiny = Shiny.Never }, // Latios + + new() { Species = 375, Level = 30, Version = GameVersion.R, Moves = new[] {036,093,232,287}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 02005, OT_Name = "ROCKS", OT_Gender = 0, RibbonNational = true, Shiny = Shiny.Never }, // Metang + new() { Species = 386, Level = 70, Version = GameVersion.R, Moves = new[] {322,105,354,063}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 28606, OT_Name = "DOEL", Fateful = true, Shiny = Shiny.Never }, // Deoxys + new() { Species = 386, Level = 70, Version = GameVersion.R, Moves = new[] {322,105,354,063}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "SPACE C", Fateful = true, Shiny = Shiny.Never }, // Deoxys + new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.English, Method = PIDType.BACD_U, TID = 06930, OT_Name = "MYSTRY", Fateful = true, Shiny = Shiny.Never }, // Mew + new() { Species = 151, Level = 10, Version = GameVersion.R, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06930, OT_Name = "MYSTRY", Fateful = true, Shiny = Shiny.Never }, // Mew + + // Party of the Decade + new() { Species = 001, Level = 70, Version = GameVersion.R, Moves = new[] {230,074,076,235}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Bulbasaur + new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Charizard + new() { Species = 009, Level = 70, Version = GameVersion.R, Moves = new[] {182,240,130,056}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Blastoise + new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,087,113,019}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", HeldItem = 202, Shiny = Shiny.Never }, // Pikachu (Fly) + new() { Species = 065, Level = 70, Version = GameVersion.R, Moves = new[] {248,347,094,271}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Alakazam + new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Articuno + new() { Species = 145, Level = 70, Version = GameVersion.R, Moves = new[] {097,197,065,268}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Zapdos + new() { Species = 146, Level = 70, Version = GameVersion.R, Moves = new[] {097,203,053,219}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Moltres + new() { Species = 149, Level = 70, Version = GameVersion.R, Moves = new[] {097,219,017,200}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Dragonite + new() { Species = 157, Level = 70, Version = GameVersion.R, Moves = new[] {098,172,129,053}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Typhlosion + new() { Species = 196, Level = 70, Version = GameVersion.R, Moves = new[] {060,244,094,234}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Espeon + new() { Species = 197, Level = 70, Version = GameVersion.R, Moves = new[] {185,212,103,236}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Umbreon + new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Raikou + new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Entei + new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Suicune + new() { Species = 248, Level = 70, Version = GameVersion.R, Moves = new[] {037,184,242,089}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Tyranitar + new() { Species = 257, Level = 70, Version = GameVersion.R, Moves = new[] {299,163,119,327}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Blaziken + new() { Species = 359, Level = 70, Version = GameVersion.R, Moves = new[] {104,163,248,195}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Absol + new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", HeldItem = 191, Shiny = Shiny.Never }, // Latias + new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 06808, OT_Name = "10 ANIV", HeldItem = 191, Shiny = Shiny.Never }, // Latios + + // Journey Across America + new() { Species = 001, Level = 70, Version = GameVersion.R, Moves = new[] {230,074,076,235}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Bulbasaur + new() { Species = 006, Level = 70, Version = GameVersion.R, Moves = new[] {017,163,082,083}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Charizard + new() { Species = 009, Level = 70, Version = GameVersion.R, Moves = new[] {182,240,130,056}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Blastoise + new() { Species = 025, Level = 70, Version = GameVersion.R, Moves = new[] {085,097,087,113}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", HeldItem = 202, Shiny = Shiny.Never }, // Pikachu (No Fly) + new() { Species = 065, Level = 70, Version = GameVersion.R, Moves = new[] {248,347,094,271}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Alakazam + new() { Species = 144, Level = 70, Version = GameVersion.R, Moves = new[] {097,170,058,115}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Articuno + new() { Species = 145, Level = 70, Version = GameVersion.R, Moves = new[] {097,197,065,268}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Zapdos + new() { Species = 146, Level = 70, Version = GameVersion.R, Moves = new[] {097,203,053,219}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Moltres + new() { Species = 149, Level = 70, Version = GameVersion.R, Moves = new[] {097,219,017,200}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Dragonite + new() { Species = 157, Level = 70, Version = GameVersion.R, Moves = new[] {098,172,129,053}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Typhlosion + new() { Species = 196, Level = 70, Version = GameVersion.R, Moves = new[] {060,244,094,234}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Espeon + new() { Species = 197, Level = 70, Version = GameVersion.R, Moves = new[] {185,212,103,236}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Umbreon + new() { Species = 243, Level = 70, Version = GameVersion.R, Moves = new[] {098,209,115,242}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Raikou + new() { Species = 244, Level = 70, Version = GameVersion.R, Moves = new[] {083,023,053,207}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Entei + new() { Species = 245, Level = 70, Version = GameVersion.R, Moves = new[] {016,062,054,243}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Suicune + new() { Species = 248, Level = 70, Version = GameVersion.R, Moves = new[] {037,184,242,089}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Tyranitar + new() { Species = 251, Level = 70, Version = GameVersion.R, Moves = new[] {246,248,226,195}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Celebi + new() { Species = 257, Level = 70, Version = GameVersion.R, Moves = new[] {299,163,119,327}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Blaziken + new() { Species = 359, Level = 70, Version = GameVersion.R, Moves = new[] {104,163,248,195}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", Shiny = Shiny.Never }, // Absol + new() { Species = 380, Level = 70, Version = GameVersion.R, Moves = new[] {296,094,105,204}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", HeldItem = 191, Shiny = Shiny.Never }, // Latias + new() { Species = 381, Level = 70, Version = GameVersion.R, Moves = new[] {295,094,105,349}, Language = (int)LanguageID.English, Method = PIDType.BACD_R, TID = 00010, OT_Name = "10 ANIV", HeldItem = 191, Shiny = Shiny.Never }, // Latios + }; + + internal static readonly WC3[] Encounter_Event3_Common = + { + // Pokémon Box -- RSE Recipient + new() { Species = 333, IsEgg = true, Level = 05, Moves = new[]{206}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.RSE }, // Swablu Egg with False Swipe + new() { Species = 263, IsEgg = true, Level = 05, Moves = new[]{245}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.RSE }, // Zigzagoon Egg with Extreme Speed + new() { Species = 300, IsEgg = true, Level = 05, Moves = new[]{006}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.RSE }, // Skitty Egg with Pay Day + new() { Species = 172, IsEgg = true, Level = 05, Moves = new[]{057}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.RSE }, // Pichu Egg with Surf + // Pokémon Box -- FRLG Recipient + new() { Species = 333, IsEgg = true, Level = 05, Moves = new[]{206}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.FRLG }, // Swablu Egg with False Swipe + new() { Species = 263, IsEgg = true, Level = 05, Moves = new[]{245}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.FRLG }, // Zigzagoon Egg with Extreme Speed + new() { Species = 300, IsEgg = true, Level = 05, Moves = new[]{006}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.FRLG }, // Skitty Egg with Pay Day + new() { Species = 172, IsEgg = true, Level = 05, Moves = new[]{057}, Method = PIDType.BACD_U, OT_Gender = 1, OT_Name = "AZUSA", Version = GameVersion.FRLG }, // Pichu Egg with Surf + + // PokePark Eggs - DS Download Play + new() { Species = 054, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{300}, Method = PIDType.BACD_R }, // Psyduck with Mud Sport + new() { Species = 172, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{266}, Method = PIDType.BACD_R }, // Pichu with Follow me + new() { Species = 174, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{321}, Method = PIDType.BACD_R }, // Igglybuff with Tickle + new() { Species = 222, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{300}, Method = PIDType.BACD_R }, // Corsola with Mud Sport + new() { Species = 276, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{297}, Method = PIDType.BACD_R }, // Taillow with Feather Dance + new() { Species = 283, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{300}, Method = PIDType.BACD_R }, // Surskit with Mud Sport + new() { Species = 293, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{298}, Method = PIDType.BACD_R }, // Whismur with Teeter Dance + new() { Species = 300, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{205}, Method = PIDType.BACD_R }, // Skitty with Rollout + new() { Species = 311, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{346}, Method = PIDType.BACD_R }, // Plusle with Water Sport + new() { Species = 312, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{300}, Method = PIDType.BACD_R }, // Minun with Mud Sport + new() { Species = 325, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{253}, Method = PIDType.BACD_R }, // Spoink with Uproar + new() { Species = 327, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{047}, Method = PIDType.BACD_R }, // Spinda with Sing + new() { Species = 331, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{227}, Method = PIDType.BACD_R }, // Cacnea with Encore + new() { Species = 341, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{346}, Method = PIDType.BACD_R }, // Corphish with Water Sport + new() { Species = 360, IsEgg = true, Level = 05, Met_Level = 05, TID = 50318, OT_Gender = 0, OT_Name = "ポケパーク", Version = GameVersion.R, Moves = new[]{321}, Method = PIDType.BACD_R }, // Wynaut with Tickle + }; + + internal static readonly WC3[] Encounter_WC3 = ArrayUtil.ConcatAll(Encounter_Event3, Encounter_Event3_RS, Encounter_Event3_FRLG, Encounter_Event3_Common); } diff --git a/PKHeX.Core/Legality/Encounters/EncounterMatchRating.cs b/PKHeX.Core/Legality/Encounters/EncounterMatchRating.cs index 367d7019b..128391717 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterMatchRating.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterMatchRating.cs @@ -1,20 +1,19 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum EncounterMatchRating { - public enum EncounterMatchRating - { - /// Matches all data, no other matches will be better. - Match, + /// Matches all data, no other matches will be better. + Match, - /// Matches most data, might have a better match later. - Deferred, + /// Matches most data, might have a better match later. + Deferred, - /// Matches most data, might have a better match later. Less preferred than due to small errors in secondary data. - DeferredErrors, + /// Matches most data, might have a better match later. Less preferred than due to small errors in secondary data. + DeferredErrors, - /// Matches some data, but will likely have a better match later. - PartialMatch, + /// Matches some data, but will likely have a better match later. + PartialMatch, - /// Unused - None, - } + /// Unused + None, } diff --git a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs b/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs index 409324c86..8fc22f4a0 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs @@ -1,149 +1,148 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Egg Encounter Data +/// +public sealed record EncounterEgg(int Species, int Form, byte Level, int Generation, GameVersion Version) : IEncounterable { - /// - /// Egg Encounter Data - /// - public sealed record EncounterEgg(int Species, int Form, byte Level, int Generation, GameVersion Version) : IEncounterable + public string Name => "Egg"; + public string LongName => "Egg"; + + public bool EggEncounter => true; + public byte LevelMin => Level; + public byte LevelMax => Level; + public bool IsShiny => false; + public int Location => 0; + public int EggLocation => Locations.GetDaycareLocation(Generation, Version); + public Ball FixedBall => BallBreedLegality.GetDefaultBall(Version, Species); + public Shiny Shiny => Shiny.Random; + public AbilityPermission Ability => AbilityPermission.Any12H; + + public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu && (Generation > 3 || Version is GameVersion.E); + + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) { - public string Name => "Egg"; - public string LongName => "Egg"; + int gen = Generation; + var version = Version; + var pk = EntityBlank.GetBlank(gen, version); - public bool EggEncounter => true; - public byte LevelMin => Level; - public byte LevelMax => Level; - public bool IsShiny => false; - public int Location => 0; - public int EggLocation => Locations.GetDaycareLocation(Generation, Version); - public Ball FixedBall => BallBreedLegality.GetDefaultBall(Version, Species); - public Shiny Shiny => Shiny.Random; - public AbilityPermission Ability => AbilityPermission.Any12H; + tr.ApplyTo(pk); - public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu && (Generation > 3 || Version is GameVersion.E); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + pk.Species = Species; + pk.Form = Form; + pk.Language = lang; + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, gen); + pk.CurrentLevel = Level; + pk.Version = (int)version; - public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted); + var ball = FixedBall; + pk.Ball = ball is Ball.None ? (int)Ball.Poke : (int)ball; + pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; - public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) + SetEncounterMoves(pk, version); + pk.HealPP(); + SetPINGA(pk, criteria); + + if (gen <= 2) { - int gen = Generation; - var version = Version; - var pk = EntityBlank.GetBlank(gen, version); - - sav.ApplyTo(pk); - - int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)sav.Language, version); - pk.Species = Species; - pk.Form = Form; - pk.Language = lang; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, gen); - pk.CurrentLevel = Level; - pk.Version = (int)version; - - var ball = FixedBall; - pk.Ball = ball is Ball.None ? (int)Ball.Poke : (int)ball; - pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; - - SetEncounterMoves(pk, version); - pk.HealPP(); - SetPINGA(pk, criteria); - - if (gen <= 2) + if (version != GameVersion.C) { - if (version != GameVersion.C) - { - pk.OT_Gender = 0; - } - else - { - pk.Met_Location = Locations.HatchLocationC; - pk.Met_Level = 1; - } - return pk; - } - - SetMetData(pk); - - if (gen >= 4) - pk.SetEggMetData(version, (GameVersion)sav.Game); - - if (gen < 6) - return pk; - if (pk is PK6 pk6) - pk6.SetHatchMemory6(); - - SetForm(pk, sav); - - pk.SetRandomEC(); - pk.RelearnMove1 = pk.Move1; - pk.RelearnMove2 = pk.Move2; - pk.RelearnMove3 = pk.Move3; - pk.RelearnMove4 = pk.Move4; - if (pk is IScaledSize s) - { - s.HeightScalar = PokeSizeUtil.GetRandomScalar(); - s.WeightScalar = PokeSizeUtil.GetRandomScalar(); - } - - return pk; - } - - private void SetForm(PKM pk, ITrainerInfo sav) - { - switch (Species) - { - case (int)Core.Species.Minior: - pk.Form = Util.Rand.Next(7, 14); - break; - case (int)Core.Species.Scatterbug or (int)Core.Species.Spewpa or (int)Core.Species.Vivillon: - if (sav is IRegionOrigin o) - pk.Form = Vivillon3DS.GetPattern(o.Country, o.Region); - // else 0 - break; - } - } - - private static void SetPINGA(PKM pk, EncounterCriteria criteria) - { - pk.SetRandomIVs(flawless: 3); - if (pk.Format <= 2) - return; - - int gender = criteria.GetGender(-1, pk.PersonalInfo); - int nature = (int)criteria.GetNature(Nature.Random); - - if (pk.Format <= 5) - { - pk.SetPIDGender(gender); - pk.Gender = gender; - pk.SetPIDNature(nature); - pk.Nature = nature; - pk.RefreshAbility(pk.PIDAbility); + pk.OT_Gender = 0; } else { - pk.PID = Util.Rand32(); - pk.Nature = nature; - pk.Gender = gender; - pk.RefreshAbility(Util.Rand.Next(2)); + pk.Met_Location = Locations.HatchLocationC; + pk.Met_Level = 1; } - pk.StatNature = nature; + return pk; } - private void SetMetData(PKM pk) + SetMetData(pk); + + if (gen >= 4) + pk.SetEggMetData(version, (GameVersion)tr.Game); + + if (gen < 6) + return pk; + if (pk is PK6 pk6) + pk6.SetHatchMemory6(); + + SetForm(pk, tr); + + pk.SetRandomEC(); + pk.RelearnMove1 = pk.Move1; + pk.RelearnMove2 = pk.Move2; + pk.RelearnMove3 = pk.Move3; + pk.RelearnMove4 = pk.Move4; + if (pk is IScaledSize s) { - pk.Met_Level = EggStateLegality.GetEggLevelMet(Version, Generation); - pk.Met_Location = Math.Max(0, EggStateLegality.GetEggHatchLocation(Version, Generation)); + s.HeightScalar = PokeSizeUtil.GetRandomScalar(); + s.WeightScalar = PokeSizeUtil.GetRandomScalar(); } - private void SetEncounterMoves(PKM pk, GameVersion version) + return pk; + } + + private void SetForm(PKM pk, ITrainerInfo sav) + { + switch (Species) { - var learnset = GameData.GetLearnset(version, Species, Form); - var baseMoves = learnset.GetBaseEggMoves(Level); - if (baseMoves.Length == 0) return; pk.Move1 = baseMoves[0]; - if (baseMoves.Length == 1) return; pk.Move2 = baseMoves[1]; - if (baseMoves.Length == 2) return; pk.Move3 = baseMoves[2]; - if (baseMoves.Length == 3) return; pk.Move4 = baseMoves[3]; + case (int)Core.Species.Minior: + pk.Form = Util.Rand.Next(7, 14); + break; + case (int)Core.Species.Scatterbug or (int)Core.Species.Spewpa or (int)Core.Species.Vivillon: + if (sav is IRegionOrigin o) + pk.Form = Vivillon3DS.GetPattern(o.Country, o.Region); + // else 0 + break; } } + + private static void SetPINGA(PKM pk, EncounterCriteria criteria) + { + pk.SetRandomIVs(flawless: 3); + if (pk.Format <= 2) + return; + + int gender = criteria.GetGender(-1, pk.PersonalInfo); + int nature = (int)criteria.GetNature(Nature.Random); + + if (pk.Format <= 5) + { + pk.SetPIDGender(gender); + pk.Gender = gender; + pk.SetPIDNature(nature); + pk.Nature = nature; + pk.RefreshAbility(pk.PIDAbility); + } + else + { + pk.PID = Util.Rand32(); + pk.Nature = nature; + pk.Gender = gender; + pk.RefreshAbility(Util.Rand.Next(2)); + } + pk.StatNature = nature; + } + + private void SetMetData(PKM pk) + { + pk.Met_Level = EggStateLegality.GetEggLevelMet(Version, Generation); + pk.Met_Location = Math.Max(0, EggStateLegality.GetEggHatchLocation(Version, Generation)); + } + + private void SetEncounterMoves(PKM pk, GameVersion version) + { + var learnset = GameData.GetLearnset(version, Species, Form); + var baseMoves = learnset.GetBaseEggMoves(Level); + if (baseMoves.Length == 0) return; pk.Move1 = baseMoves[0]; + if (baseMoves.Length == 1) return; pk.Move2 = baseMoves[1]; + if (baseMoves.Length == 2) return; pk.Move3 = baseMoves[2]; + if (baseMoves.Length == 3) return; pk.Move4 = baseMoves[3]; + } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterInvalid.cs b/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterInvalid.cs index 3509f3916..4749a62c7 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterInvalid.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterInvalid.cs @@ -1,45 +1,44 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Invalid Encounter Data +/// +public sealed record EncounterInvalid : IEncounterable { - /// - /// Invalid Encounter Data - /// - public sealed record EncounterInvalid : IEncounterable + public static readonly EncounterInvalid Default = new(); + + public int Species { get; } + public int Form { get; } + public byte LevelMin { get; } + public byte LevelMax { get; } + public bool EggEncounter { get; } + public int Generation { get; } + public GameVersion Version { get; } + public bool IsShiny => false; + public Shiny Shiny => Shiny.Never; + + public string Name => "Invalid"; + public string LongName => "Invalid"; + public int Location => 0; + public int EggLocation => 0; + public AbilityPermission Ability => AbilityPermission.Any12H; + public Ball FixedBall => Ball.None; + + private EncounterInvalid() { } + + public EncounterInvalid(PKM pk) { - public static readonly EncounterInvalid Default = new(); - - public int Species { get; } - public int Form { get; } - public byte LevelMin { get; } - public byte LevelMax { get; } - public bool EggEncounter { get; } - public int Generation { get; } - public GameVersion Version { get; } - public bool IsShiny => false; - public Shiny Shiny => Shiny.Never; - - public string Name => "Invalid"; - public string LongName => "Invalid"; - public int Location => 0; - public int EggLocation => 0; - public AbilityPermission Ability => AbilityPermission.Any12H; - public Ball FixedBall => Ball.None; - - private EncounterInvalid() { } - - public EncounterInvalid(PKM pkm) - { - Species = pkm.Species; - Form = pkm.Form; - LevelMin = (byte)pkm.Met_Level; - LevelMax = (byte)pkm.CurrentLevel; - EggEncounter = pkm.WasEgg; - Generation = pkm.Generation; - Version = (GameVersion)pkm.Version; - } - - public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted); - public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) => throw new ArgumentException($"Cannot convert an {nameof(EncounterInvalid)} to PKM."); + Species = pk.Species; + Form = pk.Form; + LevelMin = (byte)pk.Met_Level; + LevelMax = (byte)pk.CurrentLevel; + EggEncounter = pk.WasEgg; + Generation = pk.Generation; + Version = (GameVersion)pk.Version; } + + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => throw new ArgumentException($"Cannot convert an {nameof(EncounterInvalid)} to PKM."); } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs index a95560bb8..ce491a512 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs @@ -1,245 +1,244 @@ using System; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Wild Encounter Slot data +/// +/// Wild encounter slots are found as random encounters in-game. +public abstract record EncounterSlot(EncounterArea Area, int Species, int Form, byte LevelMin, byte LevelMax) : IEncounterable, IEncounterMatch { + public abstract int Generation { get; } + public bool EggEncounter => false; + public virtual bool IsShiny => false; + + protected readonly EncounterArea Area = Area; + public GameVersion Version => Area.Version; + public int Location => Area.Location; + public int EggLocation => 0; + public virtual Ball FixedBall => Ball.None; + public virtual Shiny Shiny => Shiny.Random; + + public bool IsFixedLevel => LevelMin == LevelMax; + public bool IsRandomLevel => LevelMin != LevelMax; + + private protected const string wild = "Wild Encounter"; + public string Name => wild; + /// - /// Wild Encounter Slot data + /// Gets if the specified level inputs are within range of the and /// - /// Wild encounter slots are found as random encounters in-game. - public abstract record EncounterSlot(EncounterArea Area, int Species, int Form, byte LevelMin, byte LevelMax) : IEncounterable, IEncounterMatch + /// Single level + /// True if within slot's range, false if impossible. + public bool IsLevelWithinRange(int lvl) => LevelMin <= lvl && lvl <= LevelMax; + + /// + /// Gets if the specified level inputs are within range of the and + /// + /// Highest value the low end of levels can be + /// Lowest value the high end of levels can be + /// True if within slot's range, false if impossible. + public bool IsLevelWithinRange(byte min, byte max) => LevelMin <= max && min <= LevelMax; + + /// + /// Gets if the specified level inputs are within range of the and + /// + /// Single level + /// Highest value the low end of levels can be + /// Lowest value the high end of levels can be + /// True if within slot's range, false if impossible. + public bool IsLevelWithinRange(int lvl, int minDecrease, int maxIncrease) => LevelMin - minDecrease <= lvl && lvl <= LevelMax + maxIncrease; + + /// + /// Gets if the specified level inputs are within range of the and + /// + /// Lowest level allowed + /// Highest level allowed + /// Highest value the low end of levels can be + /// Lowest value the high end of levels can be + /// True if within slot's range, false if impossible. + public bool IsLevelWithinRange(byte min, byte max, int minDecrease, int maxIncrease) => LevelMin - minDecrease <= max && min <= LevelMax + maxIncrease; + + public virtual string LongName { - public abstract int Generation { get; } - public bool EggEncounter => false; - public virtual bool IsShiny => false; - - protected readonly EncounterArea Area = Area; - public GameVersion Version => Area.Version; - public int Location => Area.Location; - public int EggLocation => 0; - public virtual Ball FixedBall => Ball.None; - public virtual Shiny Shiny => Shiny.Random; - - public bool IsFixedLevel => LevelMin == LevelMax; - public bool IsRandomLevel => LevelMin != LevelMax; - - private protected const string wild = "Wild Encounter"; - public string Name => wild; - - /// - /// Gets if the specified level inputs are within range of the and - /// - /// Single level - /// True if within slot's range, false if impossible. - public bool IsLevelWithinRange(int lvl) => LevelMin <= lvl && lvl <= LevelMax; - - /// - /// Gets if the specified level inputs are within range of the and - /// - /// Highest value the low end of levels can be - /// Lowest value the high end of levels can be - /// True if within slot's range, false if impossible. - public bool IsLevelWithinRange(byte min, byte max) => LevelMin <= max && min <= LevelMax; - - /// - /// Gets if the specified level inputs are within range of the and - /// - /// Single level - /// Highest value the low end of levels can be - /// Lowest value the high end of levels can be - /// True if within slot's range, false if impossible. - public bool IsLevelWithinRange(int lvl, int minDecrease, int maxIncrease) => LevelMin - minDecrease <= lvl && lvl <= LevelMax + maxIncrease; - - /// - /// Gets if the specified level inputs are within range of the and - /// - /// Lowest level allowed - /// Highest level allowed - /// Highest value the low end of levels can be - /// Lowest value the high end of levels can be - /// True if within slot's range, false if impossible. - public bool IsLevelWithinRange(byte min, byte max, int minDecrease, int maxIncrease) => LevelMin - minDecrease <= max && min <= LevelMax + maxIncrease; - - public virtual string LongName + get { - get - { - if (Area.Type == SlotType.Any) - return wild; - return $"{wild} {Area.Type.ToString().Replace('_', ' ')}"; - } - } - - public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted); - - public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - var pk = GetBlank(); - sav.ApplyTo(pk); - ApplyDetails(sav, criteria, pk); - return pk; - } - - protected virtual PKM GetBlank() => EntityBlank.GetBlank(Generation, Version); - - protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - var version = this.GetCompatibleVersion((GameVersion) sav.Game); - int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID) sav.Language, version); - int level = LevelMin; - pk.Species = Species; - pk.Form = GetWildForm(pk, Form, sav); - pk.Language = lang; - pk.CurrentLevel = level; - pk.Version = (int)version; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation); - - ApplyDetailsBall(pk); - pk.Language = lang; - pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; - - SetMetData(pk, level, Location); - SetPINGA(pk, criteria); - SetEncounterMoves(pk, version, level); - - SetFormatSpecificData(pk); - - if (pk.Format < 6) - return; - - sav.ApplyHandlingTrainerInfo(pk); - if (pk is IScaledSize { HeightScalar: 0, WeightScalar: 0 } s) - { - s.HeightScalar = PokeSizeUtil.GetRandomScalar(); - s.WeightScalar = PokeSizeUtil.GetRandomScalar(); - } - } - - protected virtual void ApplyDetailsBall(PKM pk) - { - var ball = FixedBall; - pk.Ball = (int)(ball == Ball.None ? Ball.Poke : ball); - } - - protected virtual void SetEncounterMoves(PKM pk, GameVersion version, int level) - { - var moves = MoveLevelUp.GetEncounterMoves(pk, level, version); - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); - } - - protected virtual void SetFormatSpecificData(PKM pk) { } - - protected virtual void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(-1, pi); - int nature = (int)criteria.GetNature(Nature.Random); - var ability = criteria.GetAbilityFromNumber(Ability); - - if (Generation == 3 && Species == (int)Unown) - { - do - { - PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender); - ability ^= 1; // some nature-forms cannot have a certain PID-ability set, so just flip it as Unown doesn't have dual abilities. - } while (pk.Form != Form); - } - else - { - PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender); - } - - pk.Gender = gender; - pk.StatNature = nature; - } - - private void SetMetData(PKM pk, int level, int location) - { - if (pk.Format <= 2 && Version != GameVersion.C) - return; - - pk.Met_Location = location; - pk.Met_Level = level; - - if (pk.Format >= 4) - pk.MetDate = DateTime.Today; - } - - public bool IsRandomUnspecificForm => Form >= FormDynamic; - private const int FormDynamic = FormVivillon; - protected internal const int FormVivillon = 30; - protected internal const int FormRandom = 31; - - private static int GetWildForm(PKM pk, int form, ITrainerInfo sav) - { - if (form < FormDynamic) // specified form - return form; - - if (form == FormRandom) // flagged as totally random - { - if (pk.Species == (int)Minior) - return 7 + Util.Rand.Next(7); - return Util.Rand.Next(pk.PersonalInfo.FormCount); - } - - int species = pk.Species; - if (species is >= (int)Scatterbug and <= (int)Vivillon) - { - if (sav is IRegionOrigin o) - return Vivillon3DS.GetPattern(o.Country, o.Region); - } - return 0; - } - - public virtual string GetConditionString(out bool valid) - { - valid = true; - return LegalityCheckStrings.LEncCondition; - } - - public bool IsMatchExact(PKM pkm, EvoCriteria evo) => true; // Matched by Area - - public virtual EncounterMatchRating GetMatchRating(PKM pkm) - { - if (IsDeferredWurmple(pkm)) - return EncounterMatchRating.PartialMatch; - - if (pkm.Format >= 5) - { - bool isHidden = pkm.AbilityNumber == 4; - if (isHidden && this.IsPartialMatchHidden(pkm.Species, Species)) - return EncounterMatchRating.PartialMatch; - if (IsDeferredHiddenAbility(isHidden)) - return EncounterMatchRating.Deferred; - } - - return EncounterMatchRating.Match; - } - - protected virtual HiddenAbilityPermission IsHiddenAbilitySlot() => HiddenAbilityPermission.Never; - - public AbilityPermission Ability => IsHiddenAbilitySlot() switch - { - HiddenAbilityPermission.Never => AbilityPermission.Any12, - HiddenAbilityPermission.Always => AbilityPermission.OnlyHidden, - _ => AbilityPermission.Any12H, - }; - - private bool IsDeferredWurmple(PKM pkm) => Species == (int)Wurmple && pkm.Species != (int)Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pkm); - - private bool IsDeferredHiddenAbility(bool IsHidden) => IsHiddenAbilitySlot() switch - { - HiddenAbilityPermission.Never => IsHidden, - HiddenAbilityPermission.Always => !IsHidden, - _ => false, - }; - - protected enum HiddenAbilityPermission - { - Always, - Never, - Possible, + if (Area.Type == SlotType.Any) + return wild; + return $"{wild} {Area.Type.ToString().Replace('_', ' ')}"; } } + + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var pk = GetBlank(); + tr.ApplyTo(pk); + ApplyDetails(tr, criteria, pk); + return pk; + } + + protected virtual PKM GetBlank() => EntityBlank.GetBlank(Generation, Version); + + protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + var version = this.GetCompatibleVersion((GameVersion) sav.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID) sav.Language, version); + int level = LevelMin; + pk.Species = Species; + pk.Form = GetWildForm(pk, Form, sav); + pk.Language = lang; + pk.CurrentLevel = level; + pk.Version = (int)version; + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation); + + ApplyDetailsBall(pk); + pk.Language = lang; + pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; + + SetMetData(pk, level, Location); + SetPINGA(pk, criteria); + SetEncounterMoves(pk, version, level); + + SetFormatSpecificData(pk); + + if (pk.Format < 6) + return; + + sav.ApplyHandlingTrainerInfo(pk); + if (pk is IScaledSize { HeightScalar: 0, WeightScalar: 0 } s) + { + s.HeightScalar = PokeSizeUtil.GetRandomScalar(); + s.WeightScalar = PokeSizeUtil.GetRandomScalar(); + } + } + + protected virtual void ApplyDetailsBall(PKM pk) + { + var ball = FixedBall; + pk.Ball = (int)(ball == Ball.None ? Ball.Poke : ball); + } + + protected virtual void SetEncounterMoves(PKM pk, GameVersion version, int level) + { + var moves = MoveLevelUp.GetEncounterMoves(pk, level, version); + pk.SetMoves(moves); + pk.SetMaximumPPCurrent(moves); + } + + protected virtual void SetFormatSpecificData(PKM pk) { } + + protected virtual void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + + if (Generation == 3 && Species == (int)Unown) + { + do + { + PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender); + ability ^= 1; // some nature-forms cannot have a certain PID-ability set, so just flip it as Unown doesn't have dual abilities. + } while (pk.Form != Form); + } + else + { + PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender); + } + + pk.Gender = gender; + pk.StatNature = nature; + } + + private void SetMetData(PKM pk, int level, int location) + { + if (pk.Format <= 2 && Version != GameVersion.C) + return; + + pk.Met_Location = location; + pk.Met_Level = level; + + if (pk.Format >= 4) + pk.MetDate = DateTime.Today; + } + + public bool IsRandomUnspecificForm => Form >= FormDynamic; + private const int FormDynamic = FormVivillon; + protected internal const int FormVivillon = 30; + protected internal const int FormRandom = 31; + + private static int GetWildForm(PKM pk, int form, ITrainerInfo sav) + { + if (form < FormDynamic) // specified form + return form; + + if (form == FormRandom) // flagged as totally random + { + if (pk.Species == (int)Minior) + return 7 + Util.Rand.Next(7); + return Util.Rand.Next(pk.PersonalInfo.FormCount); + } + + int species = pk.Species; + if (species is >= (int)Scatterbug and <= (int)Vivillon) + { + if (sav is IRegionOrigin o) + return Vivillon3DS.GetPattern(o.Country, o.Region); + } + return 0; + } + + public virtual string GetConditionString(out bool valid) + { + valid = true; + return LegalityCheckStrings.LEncCondition; + } + + public bool IsMatchExact(PKM pk, EvoCriteria evo) => true; // Matched by Area + + public virtual EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsDeferredWurmple(pk)) + return EncounterMatchRating.PartialMatch; + + if (pk.Format >= 5) + { + bool isHidden = pk.AbilityNumber == 4; + if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + if (IsDeferredHiddenAbility(isHidden)) + return EncounterMatchRating.Deferred; + } + + return EncounterMatchRating.Match; + } + + protected virtual HiddenAbilityPermission IsHiddenAbilitySlot() => HiddenAbilityPermission.Never; + + public AbilityPermission Ability => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => AbilityPermission.Any12, + HiddenAbilityPermission.Always => AbilityPermission.OnlyHidden, + _ => AbilityPermission.Any12H, + }; + + private bool IsDeferredWurmple(PKM pk) => Species == (int)Wurmple && pk.Species != (int)Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pk); + + private bool IsDeferredHiddenAbility(bool IsHidden) => IsHiddenAbilitySlot() switch + { + HiddenAbilityPermission.Never => IsHidden, + HiddenAbilityPermission.Always => !IsHidden, + _ => false, + }; + + protected enum HiddenAbilityPermission + { + Always, + Never, + Possible, + } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs index 210a1095b..7e4d0a779 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs @@ -1,39 +1,38 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public sealed record EncounterSlot1 : EncounterSlot, INumberedSlot { - /// - /// Encounter Slot found in . - /// - /// - public sealed record EncounterSlot1 : EncounterSlot, INumberedSlot + public override int Generation => 1; + public byte SlotNumber { get; } + public override Ball FixedBall => Ball.Poke; + + public EncounterSlot1(EncounterArea1 area, byte species, byte min, byte max, byte slot) : base(area, species, 0, min, max) { - public override int Generation => 1; - public byte SlotNumber { get; } - public override Ball FixedBall => Ball.Poke; + SlotNumber = slot; + } - public EncounterSlot1(EncounterArea1 area, byte species, byte min, byte max, byte slot) : base(area, species, 0, min, max) + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + + var pk1 = (PK1)pk; + if (Version == GameVersion.YW) { - SlotNumber = slot; + // Since we don't keep track of Yellow's Personal Data, just handle any differences here. + pk1.Catch_Rate = Species switch + { + (int) Core.Species.Kadabra => 96, + (int) Core.Species.Dragonair => 27, + _ => PersonalTable.RB[Species].CatchRate, + }; } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + else { - base.ApplyDetails(sav, criteria, pk); - - var pk1 = (PK1)pk; - if (Version == GameVersion.YW) - { - // Since we don't keep track of Yellow's Personal Data, just handle any differences here. - pk1.Catch_Rate = Species switch - { - (int) Core.Species.Kadabra => 96, - (int) Core.Species.Dragonair => 27, - _ => PersonalTable.RB[Species].CatchRate, - }; - } - else - { - pk1.Catch_Rate = PersonalTable.RB[Species].CatchRate; // RB - } + pk1.Catch_Rate = PersonalTable.RB[Species].CatchRate; // RB } } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs index b8b76cad9..914e1767a 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs @@ -1,79 +1,78 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +/// Referenced Area object contains Time data which is used for origin data. +/// +/// +public sealed record EncounterSlot2 : EncounterSlot, INumberedSlot { - /// - /// Encounter Slot found in . - /// - /// - /// Referenced Area object contains Time data which is used for origin data. - /// - /// - public sealed record EncounterSlot2 : EncounterSlot, INumberedSlot + public override int Generation => 2; + public byte SlotNumber { get; } + public override Ball FixedBall => Ball.Poke; + + public EncounterSlot2(EncounterArea2 area, byte species, byte min, byte max, byte slot) : base(area, species, species == 201 ? FormRandom : 0, min, max) { - public override int Generation => 2; - public byte SlotNumber { get; } - public override Ball FixedBall => Ball.Poke; - - public EncounterSlot2(EncounterArea2 area, byte species, byte min, byte max, byte slot) : base(area, species, species == 201 ? FormRandom : 0, min, max) - { - SlotNumber = slot; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - - var pk2 = (PK2)pk; - - if (GetSlotType() == SlotType.Headbutt) - { - while (!IsTreeAvailable(pk2.TID)) - pk2.TID = Util.Rand.Next(ushort.MaxValue + 1); - } - - if (Version == GameVersion.C) - pk2.Met_TimeOfDay = ((EncounterArea2)Area).Time.RandomValidTime(); - } - - private static readonly Dictionary Trees = new() - { - { 02, 0x3FF_3FF }, // Route 29 - { 04, 0x0FF_3FF }, // Route 30 - { 05, 0x3FE_3FF }, // Route 31 - { 08, 0x3EE_3FF }, // Route 32 - { 11, 0x240_3FF }, // Route 33 - { 12, 0x37F_3FF }, // Azalea Town - { 14, 0x3FF_3FF }, // Ilex Forest - { 15, 0x001_3FE }, // Route 34 - { 18, 0x261_3FF }, // Route 35 - { 20, 0x3FF_3FF }, // Route 36 - { 21, 0x2B9_3FF }, // Route 37 - { 25, 0x3FF_3FF }, // Route 38 - { 26, 0x184_3FF }, // Route 39 - { 34, 0x3FF_3FF }, // Route 42 - { 37, 0x3FF_3FF }, // Route 43 - { 38, 0x3FF_3FF }, // Lake of Rage - { 39, 0x2FF_3FF }, // Route 44 - { 91, 0x200_1FF }, // Route 26 - { 92, 0x2BB_3FF }, // Route 27 - }; - - internal bool IsTreeAvailable(int trainerID) - { - if (!Trees.TryGetValue(Location, out var permissions)) - return false; - - var pivot = trainerID % 10; - var type = Area.Type; - return type switch - { - SlotType.Headbutt => (permissions & (1 << pivot)) != 0, - /*special*/ _ => (permissions & (1 << (pivot + 12))) != 0, - }; - } - - // we have "Special" bitflag. Strip it out. - public SlotType GetSlotType() => Area.Type & (SlotType)0xF; + SlotNumber = slot; } + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + + var pk2 = (PK2)pk; + + if (SlotType == SlotType.Headbutt) + { + while (!IsTreeAvailable(pk2.TID)) + pk2.TID = Util.Rand.Next(ushort.MaxValue + 1); + } + + if (Version == GameVersion.C) + pk2.Met_TimeOfDay = ((EncounterArea2)Area).Time.RandomValidTime(); + } + + private static readonly Dictionary Trees = new() + { + { 02, 0x3FF_3FF }, // Route 29 + { 04, 0x0FF_3FF }, // Route 30 + { 05, 0x3FE_3FF }, // Route 31 + { 08, 0x3EE_3FF }, // Route 32 + { 11, 0x240_3FF }, // Route 33 + { 12, 0x37F_3FF }, // Azalea Town + { 14, 0x3FF_3FF }, // Ilex Forest + { 15, 0x001_3FE }, // Route 34 + { 18, 0x261_3FF }, // Route 35 + { 20, 0x3FF_3FF }, // Route 36 + { 21, 0x2B9_3FF }, // Route 37 + { 25, 0x3FF_3FF }, // Route 38 + { 26, 0x184_3FF }, // Route 39 + { 34, 0x3FF_3FF }, // Route 42 + { 37, 0x3FF_3FF }, // Route 43 + { 38, 0x3FF_3FF }, // Lake of Rage + { 39, 0x2FF_3FF }, // Route 44 + { 91, 0x200_1FF }, // Route 26 + { 92, 0x2BB_3FF }, // Route 27 + }; + + internal bool IsTreeAvailable(int trainerID) + { + if (!Trees.TryGetValue(Location, out var permissions)) + return false; + + var pivot = trainerID % 10; + var type = Area.Type; + return type switch + { + SlotType.Headbutt => (permissions & (1 << pivot)) != 0, + /*special*/ _ => (permissions & (1 << (pivot + 12))) != 0, + }; + } + + // we have "Special" bitflag. Strip it out. + public SlotType SlotType => Area.Type & (SlotType)0xF; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs index dfd887202..5aaeb2879 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs @@ -1,40 +1,39 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public record EncounterSlot3 : EncounterSlot, IMagnetStatic, INumberedSlot, ISlotRNGType { - /// - /// Encounter Slot found in . - /// - /// - public record EncounterSlot3 : EncounterSlot, IMagnetStatic, INumberedSlot, ISlotRNGType + public sealed override int Generation => 3; + + public byte StaticIndex { get; } + public byte MagnetPullIndex { get; } + public byte StaticCount { get; } + public byte MagnetPullCount { get; } + public SlotType Type => Area.Type; + + public byte SlotNumber { get; } + public override Ball FixedBall => Locations.IsSafariZoneLocation3(Location) ? Ball.Safari : Ball.None; + + public EncounterSlot3(EncounterArea3 area, ushort species, byte form, byte min, byte max, byte slot, byte mpi, byte mpc, byte sti, byte stc) : base(area, species, form, min, max) { - public sealed override int Generation => 3; + SlotNumber = slot; - public byte StaticIndex { get; } - public byte MagnetPullIndex { get; } - public byte StaticCount { get; } - public byte MagnetPullCount { get; } - public SlotType Type => Area.Type; + MagnetPullIndex = mpi; + MagnetPullCount = mpc; - public byte SlotNumber { get; } - public override Ball FixedBall => Locations.IsSafariZoneLocation3(Location) ? Ball.Safari : Ball.None; - - public EncounterSlot3(EncounterArea3 area, ushort species, byte form, byte min, byte max, byte slot, byte mpi, byte mpc, byte sti, byte stc) : base(area, species, form, min, max) - { - SlotNumber = slot; - - MagnetPullIndex = mpi; - MagnetPullCount = mpc; - - StaticIndex = sti; - StaticCount = stc; - } - - public override EncounterMatchRating GetMatchRating(PKM pkm) - { - if (IsDeferredSafari3(pkm.Ball == (int)Ball.Safari)) - return EncounterMatchRating.PartialMatch; - return base.GetMatchRating(pkm); - } - - private bool IsDeferredSafari3(bool IsSafariBall) => IsSafariBall != Locations.IsSafariZoneLocation3(Location); + StaticIndex = sti; + StaticCount = stc; } + + public override EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsDeferredSafari3(pk.Ball == (int)Ball.Safari)) + return EncounterMatchRating.PartialMatch; + return base.GetMatchRating(pk); + } + + private bool IsDeferredSafari3(bool IsSafariBall) => IsSafariBall != Locations.IsSafariZoneLocation3(Location); } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3PokeSpot.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3PokeSpot.cs index 4805551f7..a1ff1c155 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3PokeSpot.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3PokeSpot.cs @@ -1,32 +1,31 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public sealed record EncounterSlot3PokeSpot : EncounterSlot, INumberedSlot { - /// - /// Encounter Slot found in . - /// - /// - public sealed record EncounterSlot3PokeSpot : EncounterSlot, INumberedSlot + public override int Generation => 3; + + public byte SlotNumber { get; } + + public EncounterSlot3PokeSpot(EncounterArea3XD area, int species, byte min, byte max, byte slot) : base(area, species, 0, min, max) { - public override int Generation => 3; + SlotNumber = slot; + } - public byte SlotNumber { get; } + // PokeSpot encounters always have Fateful Encounter set. + protected override void SetFormatSpecificData(PKM pk) => pk.FatefulEncounter = true; - public EncounterSlot3PokeSpot(EncounterArea3XD area, int species, byte min, byte max, byte slot) : base(area, species, 0, min, max) - { - SlotNumber = slot; - } - - // PokeSpot encounters always have Fateful Encounter set. - protected override void SetFormatSpecificData(PKM pk) => pk.FatefulEncounter = true; - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(-1, pi); - int nature = (int)criteria.GetNature(Nature.Random); - int ability = criteria.GetAbilityFromNumber(0); - PIDGenerator.SetRandomPokeSpotPID(pk, nature, gender, ability, SlotNumber); - pk.Gender = gender; - pk.StatNature = nature; - } + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + int ability = criteria.GetAbilityFromNumber(0); + PIDGenerator.SetRandomPokeSpotPID(pk, nature, gender, ability, SlotNumber); + pk.Gender = gender; + pk.StatNature = nature; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs index 19d8bd941..066e35433 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3Swarm.cs @@ -1,26 +1,25 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +/// Handled differently as these slots have fixed moves that are different from their normal level-up moves. +/// +/// +internal sealed record EncounterSlot3Swarm : EncounterSlot3, IMoveset { - /// - /// Encounter Slot found in . - /// - /// - /// Handled differently as these slots have fixed moves that are different from their normal level-up moves. - /// - /// - internal sealed record EncounterSlot3Swarm : EncounterSlot3, IMoveset + public IReadOnlyList Moves { get; } + + public EncounterSlot3Swarm(EncounterArea3 area, ushort species, byte min, byte max, byte slot, + IReadOnlyList moves) : base(area, species, 0, min, max, slot, 0, 0, 0, 0) => Moves = moves; + + protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) { - public IReadOnlyList Moves { get; } - - public EncounterSlot3Swarm(EncounterArea3 area, ushort species, byte min, byte max, byte slot, - IReadOnlyList moves) : base(area, species, 0, min, max, slot, 0, 0, 0, 0) => Moves = moves; - - protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) - { - var moves = (int[])Moves; - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); - } + var moves = (int[])Moves; + pk.SetMoves(moves); + pk.SetMaximumPPCurrent(moves); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs index a85b36779..34e5d98c1 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs @@ -1,55 +1,54 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public sealed record EncounterSlot4 : EncounterSlot, IMagnetStatic, INumberedSlot, IGroundTypeTile, ISlotRNGType { - /// - /// Encounter Slot found in . - /// - /// - public sealed record EncounterSlot4 : EncounterSlot, IMagnetStatic, INumberedSlot, IGroundTypeTile, ISlotRNGType + public override int Generation => 4; + public GroundTileAllowed GroundTile => ((EncounterArea4)Area).GroundTile; + + public byte StaticIndex { get; } + public byte MagnetPullIndex { get; } + public byte StaticCount { get; } + public byte MagnetPullCount { get; } + public SlotType Type => Area.Type; + + public byte SlotNumber { get; } + public override Ball FixedBall => GetRequiredBallValue(); + public bool CanUseRadar => !GameVersion.HGSS.Contains(Version) && GroundTile.HasFlag(GroundTileAllowed.Grass); + + public EncounterSlot4(EncounterArea4 area, ushort species, byte form, byte min, byte max, byte slot, byte mpi, byte mpc, byte sti, byte stc) : base(area, species, form, min, max) { - public override int Generation => 4; - public GroundTilePermission GroundTile => ((EncounterArea4)Area).GroundTile; + SlotNumber = slot; - public byte StaticIndex { get; } - public byte MagnetPullIndex { get; } - public byte StaticCount { get; } - public byte MagnetPullCount { get; } - public SlotType Type => Area.Type; + MagnetPullIndex = mpi; + MagnetPullCount = mpc; - public byte SlotNumber { get; } - public override Ball FixedBall => GetRequiredBallValue(); - public bool CanUseRadar => !GameVersion.HGSS.Contains(Version) && GroundTile.HasFlag(GroundTilePermission.Grass); + StaticIndex = sti; + StaticCount = stc; + } - public EncounterSlot4(EncounterArea4 area, ushort species, byte form, byte min, byte max, byte slot, byte mpi, byte mpc, byte sti, byte stc) : base(area, species, form, min, max) + protected override void SetFormatSpecificData(PKM pk) => ((PK4)pk).GroundTile = GroundTile.GetIndex(); + + public override EncounterMatchRating GetMatchRating(PKM pk) + { + if ((pk.Ball == (int)Ball.Safari) != Locations.IsSafariZoneLocation4(Location)) + return EncounterMatchRating.PartialMatch; + if ((pk.Ball == (int)Ball.Sport) != (Type == SlotType.BugContest)) { - SlotNumber = slot; - - MagnetPullIndex = mpi; - MagnetPullCount = mpc; - - StaticIndex = sti; - StaticCount = stc; - } - - protected override void SetFormatSpecificData(PKM pk) => ((PK4)pk).GroundTile = GroundTile.GetIndex(); - - public override EncounterMatchRating GetMatchRating(PKM pkm) - { - if ((pkm.Ball == (int)Ball.Safari) != Locations.IsSafariZoneLocation4(Location)) + // Nincada => Shedinja can wipe the ball back to Poke + if (pk.Species != (int)Core.Species.Shedinja || pk.Ball != (int)Ball.Poke) return EncounterMatchRating.PartialMatch; - if ((pkm.Ball == (int)Ball.Sport) != (Type == SlotType.BugContest)) - { - // Nincada => Shedinja can wipe the ball back to Poke - if (pkm.Species != (int)Core.Species.Shedinja || pkm.Ball != (int)Ball.Poke) - return EncounterMatchRating.PartialMatch; - } - return base.GetMatchRating(pkm); } + return base.GetMatchRating(pk); + } - private Ball GetRequiredBallValue() - { - if (Type is SlotType.BugContest) - return Ball.Sport; - return Locations.IsSafariZoneLocation4(Location) ? Ball.Safari : Ball.None; - } + private Ball GetRequiredBallValue() + { + if (Type is SlotType.BugContest) + return Ball.Sport; + return Locations.IsSafariZoneLocation4(Location) ? Ball.Safari : Ball.None; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs index 5b3eacca3..832b0965a 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot5.cs @@ -1,19 +1,18 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public sealed record EncounterSlot5 : EncounterSlot { - /// - /// Encounter Slot found in . - /// - /// - public sealed record EncounterSlot5 : EncounterSlot + public override int Generation => 5; + + public EncounterSlot5(EncounterArea5 area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) { - public override int Generation => 5; - - public EncounterSlot5(EncounterArea5 area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) - { - } - - public bool IsHiddenGrotto => Area.Type == SlotType.HiddenGrotto; - - protected override HiddenAbilityPermission IsHiddenAbilitySlot() => Area.Type == SlotType.HiddenGrotto ? HiddenAbilityPermission.Always : HiddenAbilityPermission.Never; } + + public bool IsHiddenGrotto => Area.Type == SlotType.HiddenGrotto; + + protected override HiddenAbilityPermission IsHiddenAbilitySlot() => Area.Type == SlotType.HiddenGrotto ? HiddenAbilityPermission.Always : HiddenAbilityPermission.Never; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs index 60459232a..82042a8b2 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6AO.cs @@ -1,63 +1,62 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public sealed record EncounterSlot6AO : EncounterSlot { - /// - /// Encounter Slot found in . - /// - /// - public sealed record EncounterSlot6AO : EncounterSlot + public override int Generation => 6; + public bool CanDexNav => Area.Type != SlotType.Rock_Smash; + public bool IsHorde => Area.Type == SlotType.Horde; + + public bool Pressure { get; init; } + public bool DexNav { get; init; } + public bool WhiteFlute { get; init; } + public bool BlackFlute { get; init; } + + public EncounterSlot6AO(EncounterArea6AO area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) { - public override int Generation => 6; - public bool CanDexNav => Area.Type != SlotType.Rock_Smash; - public bool IsHorde => Area.Type == SlotType.Horde; + } - public bool Pressure { get; init; } - public bool DexNav { get; init; } - public bool WhiteFlute { get; init; } - public bool BlackFlute { get; init; } - - public EncounterSlot6AO(EncounterArea6AO area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) + protected override void SetFormatSpecificData(PKM pk) + { + var pk6 = (PK6)pk; + if (CanDexNav) { + var eggMoves = GetDexNavMoves(); + if (eggMoves.Length > 0) + pk6.RelearnMove1 = eggMoves[Util.Rand.Next(eggMoves.Length)]; } + pk6.SetRandomMemory6(); + pk6.SetRandomEC(); + } - protected override void SetFormatSpecificData(PKM pk) - { - var pk6 = (PK6)pk; - if (CanDexNav) - { - var eggMoves = GetDexNavMoves(); - if (eggMoves.Length > 0) - pk6.RelearnMove1 = eggMoves[Util.Rand.Next(eggMoves.Length)]; - } - pk6.SetRandomMemory6(); - pk6.SetRandomEC(); - } + public override string GetConditionString(out bool valid) + { + valid = true; + if (WhiteFlute) // Decreased Level Encounters + return Pressure ? LegalityCheckStrings.LEncConditionWhiteLead : LegalityCheckStrings.LEncConditionWhite; + if (BlackFlute) // Increased Level Encounters + return Pressure ? LegalityCheckStrings.LEncConditionBlackLead : LegalityCheckStrings.LEncConditionBlack; + if (DexNav) + return LegalityCheckStrings.LEncConditionDexNav; - public override string GetConditionString(out bool valid) - { - valid = true; - if (WhiteFlute) // Decreased Level Encounters - return Pressure ? LegalityCheckStrings.LEncConditionWhiteLead : LegalityCheckStrings.LEncConditionWhite; - if (BlackFlute) // Increased Level Encounters - return Pressure ? LegalityCheckStrings.LEncConditionBlackLead : LegalityCheckStrings.LEncConditionBlack; - if (DexNav) - return LegalityCheckStrings.LEncConditionDexNav; + return Pressure ? LegalityCheckStrings.LEncConditionLead : LegalityCheckStrings.LEncCondition; + } - return Pressure ? LegalityCheckStrings.LEncConditionLead : LegalityCheckStrings.LEncCondition; - } + protected override HiddenAbilityPermission IsHiddenAbilitySlot() => CanDexNav || IsHorde ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; - protected override HiddenAbilityPermission IsHiddenAbilitySlot() => CanDexNav || IsHorde ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; + private int[] GetDexNavMoves() + { + var et = EvolutionTree.Evolves6; + var sf = et.GetBaseSpeciesForm(Species, Form); + return MoveEgg.GetEggMoves(6, sf & 0x7FF, sf >> 11, Version); + } - private int[] GetDexNavMoves() - { - var et = EvolutionTree.Evolves6; - var sf = et.GetBaseSpeciesForm(Species, Form); - return MoveEgg.GetEggMoves(6, sf & 0x7FF, sf >> 11, Version); - } - - public bool CanBeDexNavMove(int move) - { - var baseEgg = GetDexNavMoves(); - return System.Array.IndexOf(baseEgg, move) >= 0; - } + public bool CanBeDexNavMove(int move) + { + var baseEgg = GetDexNavMoves(); + return System.Array.IndexOf(baseEgg, move) >= 0; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6XY.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6XY.cs index b837ad4fc..300443557 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6XY.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot6XY.cs @@ -1,35 +1,34 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public sealed record EncounterSlot6XY : EncounterSlot { - /// - /// Encounter Slot found in . - /// - /// - public sealed record EncounterSlot6XY : EncounterSlot + public override int Generation => 6; + public bool Pressure { get; init; } + public bool IsFriendSafari => Area.Type == SlotType.FriendSafari; + public bool IsHorde => Area.Type == SlotType.Horde; + + public EncounterSlot6XY(EncounterArea6XY area, int species, int form, byte min, byte max) : base(area, species, form, min, max) { - public override int Generation => 6; - public bool Pressure { get; init; } - public bool IsFriendSafari => Area.Type == SlotType.FriendSafari; - public bool IsHorde => Area.Type == SlotType.Horde; - - public EncounterSlot6XY(EncounterArea6XY area, int species, int form, byte min, byte max) : base(area, species, form, min, max) - { - } - - protected override void SetFormatSpecificData(PKM pk) - { - var pk6 = (PK6)pk; - pk6.SetRandomMemory6(); - pk6.SetRandomEC(); - } - - public override string GetConditionString(out bool valid) - { - valid = true; - return Pressure ? LegalityCheckStrings.LEncConditionLead : LegalityCheckStrings.LEncCondition; - } - - public EncounterSlot6XY CreatePressureFormCopy(int form) => this with {Form = form, Pressure = true}; - - protected override HiddenAbilityPermission IsHiddenAbilitySlot() => IsHorde || IsFriendSafari ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; } + + protected override void SetFormatSpecificData(PKM pk) + { + var pk6 = (PK6)pk; + pk6.SetRandomMemory6(); + pk6.SetRandomEC(); + } + + public override string GetConditionString(out bool valid) + { + valid = true; + return Pressure ? LegalityCheckStrings.LEncConditionLead : LegalityCheckStrings.LEncCondition; + } + + public EncounterSlot6XY CreatePressureFormCopy(int form) => this with {Form = form, Pressure = true}; + + protected override HiddenAbilityPermission IsHiddenAbilitySlot() => IsHorde || IsFriendSafari ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs index 2a4b66ee9..00a29c455 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs @@ -1,36 +1,35 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public sealed record EncounterSlot7 : EncounterSlot { - /// - /// Encounter Slot found in . - /// - /// - public sealed record EncounterSlot7 : EncounterSlot + public override int Generation => 7; + public bool IsSOS => Area.Type == SlotType.SOS; + + public EncounterSlot7(EncounterArea7 area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) { - public override int Generation => 7; - public bool IsSOS => Area.Type == SlotType.SOS; - - public EncounterSlot7(EncounterArea7 area, ushort species, byte form, byte min, byte max) : base(area, species, form, min, max) - { - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - pk.PID = Util.Rand32(); - pk.Nature = (int)criteria.GetNature(Nature.Random); - pk.Gender = criteria.GetGender(-1, pi); - criteria.SetRandomIVs(pk); - - var num = Ability; - if (IsSOS && pk.FlawlessIVCount < 2) - num = 0; // let's fake it as an insufficient chain, no HA possible. - var ability = criteria.GetAbilityFromNumber(num); - pk.RefreshAbility(ability); - pk.SetRandomEC(); - } - - protected override HiddenAbilityPermission IsHiddenAbilitySlot() => IsSOS ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; - - public override Ball FixedBall => Location == Locations.Pelago7 ? Ball.Poke : Ball.None; } + + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + pk.PID = Util.Rand32(); + pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.Gender = criteria.GetGender(-1, pi); + criteria.SetRandomIVs(pk); + + var num = Ability; + if (IsSOS && pk.FlawlessIVCount < 2) + num = 0; // let's fake it as an insufficient chain, no HA possible. + var ability = criteria.GetAbilityFromNumber(num); + pk.RefreshAbility(ability); + pk.SetRandomEC(); + } + + protected override HiddenAbilityPermission IsHiddenAbilitySlot() => IsSOS ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; + + public override Ball FixedBall => Location == Locations.Pelago7 ? Ball.Poke : Ball.None; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7b.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7b.cs index ee5eb14f1..3aa85f679 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7b.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7b.cs @@ -1,25 +1,24 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public sealed record EncounterSlot7b : EncounterSlot { - /// - /// Encounter Slot found in . - /// - /// - public sealed record EncounterSlot7b : EncounterSlot + public override int Generation => 7; + + public EncounterSlot7b(EncounterArea7b area, int species, byte min, byte max) : base(area, species, 0, min, max) { - public override int Generation => 7; + } - public EncounterSlot7b(EncounterArea7b area, int species, byte min, byte max) : base(area, species, 0, min, max) - { - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - pk.SetRandomEC(); - var pb = (PB7)pk; - pb.ResetHeight(); - pb.ResetWeight(); - pb.ResetCP(); - } + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + pk.SetRandomEC(); + var pb = (PB7)pk; + pb.ResetHeight(); + pb.ResetWeight(); + pb.ResetCP(); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8.cs index 3332c737c..c6abc204d 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8.cs @@ -1,138 +1,137 @@ using static PKHeX.Core.OverworldCorrelation8Requirement; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public sealed record EncounterSlot8 : EncounterSlot, IOverworldCorrelation8 { - /// - /// Encounter Slot found in . - /// - /// - public sealed record EncounterSlot8 : EncounterSlot, IOverworldCorrelation8 + public readonly AreaWeather8 Weather; + public readonly AreaSlotType8 SlotType; + public override string LongName => $"{wild} [{SlotType}] - {Weather.ToString().Replace("_", string.Empty)}"; + public override int Generation => 8; + + // Fishing are only from the hidden table (not symbol). + public bool CanEncounterViaFishing => SlotType.CanEncounterViaFishing(Weather); + public bool CanEncounterViaCurry { - public readonly AreaWeather8 Weather; - public readonly AreaSlotType8 SlotType; - public override string LongName => $"{wild} [{SlotType}] - {Weather.ToString().Replace("_", string.Empty)}"; - public override int Generation => 8; - - // Fishing are only from the hidden table (not symbol). - public bool CanEncounterViaFishing => SlotType.CanEncounterViaFishing(Weather); - public bool CanEncounterViaCurry + get { - get - { - if (!SlotType.CanEncounterViaCurry()) - return false; + if (!SlotType.CanEncounterViaCurry()) + return false; - if ((Weather & AreaWeather8.All) == 0) - return false; + if ((Weather & AreaWeather8.All) == 0) + return false; - if (EncounterArea8.IsWildArea(Location)) - return false; + if (EncounterArea8.IsWildArea(Location)) + return false; - return true; - } - } - - public EncounterSlot8(EncounterArea8 area, ushort species, byte form, byte min, byte max, AreaWeather8 weather, AreaSlotType8 slotType) : base(area, species, form, min, max) - { - Weather = weather; - SlotType = slotType; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - bool symbol = ((EncounterArea8)Area).PermitCrossover; - var c = symbol ? EncounterCriteria.Unrestricted : criteria; - if (!symbol && Location is 30 or 54 && (Weather & AreaWeather8.Fishing) == 0) - ((PK8)pk).RibbonMarkCurry = true; - - base.ApplyDetails(sav, c, pk); - if (Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) - pk.CurrentLevel = pk.Met_Level = EncounterArea8.BoostLevel; - - var req = GetRequirement(pk); - if (req != MustHave) - { - pk.SetRandomEC(); - return; - } - // Don't bother honoring shiny state. - Overworld8RNG.ApplyDetails(pk, c, Shiny.Random); - } - - public OverworldCorrelation8Requirement GetRequirement(PKM pk) - { - if (((EncounterArea8)Area).PermitCrossover) - return MustHave; // symbol walking overworld - - bool curry = pk is IRibbonSetMark8 {RibbonMarkCurry: true} || (pk.Species == (int)Core.Species.Shedinja && pk is IRibbonSetAffixed { AffixedRibbon:(int)RibbonIndex.MarkCurry}); - if (curry) - return MustNotHave; - - // Tree encounters are generated via the global seed, not the u32 - if ((Weather & AreaWeather8.Shaking_Trees) != 0) - { - // Some tree encounters are present in the regular encounters. - return Weather == AreaWeather8.Shaking_Trees - ? MustNotHave - : CanBeEither; - } - - return MustHave; - } - - public bool IsOverworldCorrelationCorrect(PKM pk) - { - var flawless = GetFlawlessIVCount(pk.Met_Level); - return Overworld8RNG.ValidateOverworldEncounter(pk, flawless: flawless); - } - - private int GetFlawlessIVCount(int met) - { - const int none = 0; - const int any023 = -1; - - // Brilliant encounters are boosted to max level for the slot. - if (met < LevelMax) - return none; - - var area = (EncounterArea8) Area; - if (area.PermitCrossover) - return any023; // Symbol - if ((Weather & AreaWeather8.Fishing) != 0) - return any023; // Fishing - return none; // Hidden - } - - public override EncounterMatchRating GetMatchRating(PKM pkm) - { - bool isHidden = pkm.AbilityNumber == 4; - if (isHidden && this.IsPartialMatchHidden(pkm.Species, Species)) - return EncounterMatchRating.PartialMatch; - - if (pkm is IRibbonSetMark8 m) - { - if (m.RibbonMarkCurry && (Weather & AreaWeather8.All) == 0) - return EncounterMatchRating.DeferredErrors; - if (m.RibbonMarkFishing && (Weather & AreaWeather8.Fishing) == 0) - return EncounterMatchRating.DeferredErrors; - - // Check if it has a mark and the weather does not permit the mark. - // Tree/Fishing slots should be deferred here and are checked later. - if (!Weather.IsMarkCompatible(m)) - return EncounterMatchRating.DeferredErrors; - - // Galar Mine hidden encounters can only be found via Curry or Fishing. - if (Location is (30 or 54) && SlotType is AreaSlotType8.HiddenMain && !m.RibbonMarkCurry && !SlotType.CanEncounterViaFishing(Weather)) - return EncounterMatchRating.DeferredErrors; - } - - var req = GetRequirement(pkm); - return req switch - { - MustHave when !IsOverworldCorrelationCorrect(pkm) => EncounterMatchRating.DeferredErrors, - MustNotHave when IsOverworldCorrelationCorrect(pkm) => EncounterMatchRating.DeferredErrors, - _ => EncounterMatchRating.Match, - }; + return true; } } + + public EncounterSlot8(EncounterArea8 area, ushort species, byte form, byte min, byte max, AreaWeather8 weather, AreaSlotType8 slotType) : base(area, species, form, min, max) + { + Weather = weather; + SlotType = slotType; + } + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + bool symbol = ((EncounterArea8)Area).PermitCrossover; + var c = symbol ? EncounterCriteria.Unrestricted : criteria; + if (!symbol && Location is 30 or 54 && (Weather & AreaWeather8.Fishing) == 0) + ((PK8)pk).RibbonMarkCurry = true; + + base.ApplyDetails(sav, c, pk); + if (Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) + pk.CurrentLevel = pk.Met_Level = EncounterArea8.BoostLevel; + + var req = GetRequirement(pk); + if (req != MustHave) + { + pk.SetRandomEC(); + return; + } + // Don't bother honoring shiny state. + Overworld8RNG.ApplyDetails(pk, c, Shiny.Random); + } + + public OverworldCorrelation8Requirement GetRequirement(PKM pk) + { + if (((EncounterArea8)Area).PermitCrossover) + return MustHave; // symbol walking overworld + + bool curry = pk is IRibbonSetMark8 {RibbonMarkCurry: true} || (pk.Species == (int)Core.Species.Shedinja && pk is IRibbonSetAffixed { AffixedRibbon:(int)RibbonIndex.MarkCurry}); + if (curry) + return MustNotHave; + + // Tree encounters are generated via the global seed, not the u32 + if ((Weather & AreaWeather8.Shaking_Trees) != 0) + { + // Some tree encounters are present in the regular encounters. + return Weather == AreaWeather8.Shaking_Trees + ? MustNotHave + : CanBeEither; + } + + return MustHave; + } + + public bool IsOverworldCorrelationCorrect(PKM pk) + { + var flawless = GetFlawlessIVCount(pk.Met_Level); + return Overworld8RNG.ValidateOverworldEncounter(pk, flawless: flawless); + } + + private int GetFlawlessIVCount(int met) + { + const int none = 0; + const int any023 = -1; + + // Brilliant encounters are boosted to max level for the slot. + if (met < LevelMax) + return none; + + var area = (EncounterArea8) Area; + if (area.PermitCrossover) + return any023; // Symbol + if ((Weather & AreaWeather8.Fishing) != 0) + return any023; // Fishing + return none; // Hidden + } + + public override EncounterMatchRating GetMatchRating(PKM pk) + { + bool isHidden = pk.AbilityNumber == 4; + if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + + if (pk is IRibbonSetMark8 m) + { + if (m.RibbonMarkCurry && (Weather & AreaWeather8.All) == 0) + return EncounterMatchRating.DeferredErrors; + if (m.RibbonMarkFishing && (Weather & AreaWeather8.Fishing) == 0) + return EncounterMatchRating.DeferredErrors; + + // Check if it has a mark and the weather does not permit the mark. + // Tree/Fishing slots should be deferred here and are checked later. + if (!Weather.IsMarkCompatible(m)) + return EncounterMatchRating.DeferredErrors; + + // Galar Mine hidden encounters can only be found via Curry or Fishing. + if (Location is (30 or 54) && SlotType is AreaSlotType8.HiddenMain && !m.RibbonMarkCurry && !SlotType.CanEncounterViaFishing(Weather)) + return EncounterMatchRating.DeferredErrors; + } + + var req = GetRequirement(pk); + return req switch + { + MustHave when !IsOverworldCorrelationCorrect(pk) => EncounterMatchRating.DeferredErrors, + MustNotHave when IsOverworldCorrelationCorrect(pk) => EncounterMatchRating.DeferredErrors, + _ => EncounterMatchRating.Match, + }; + } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs index c28902e09..5f35ba85b 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8a.cs @@ -90,42 +90,42 @@ protected override void SetFormatSpecificData(PKM pk) protected override void ApplyDetailsBall(PKM pk) => pk.Ball = (int)Ball.LAPoke; - public override EncounterMatchRating GetMatchRating(PKM pkm) + public override EncounterMatchRating GetMatchRating(PKM pk) { - if (Gender is not Gender.Random && pkm.Gender != (int)Gender) + if (Gender is not Gender.Random && pk.Gender != (int)Gender) return EncounterMatchRating.PartialMatch; - var result = GetMatchRatingInternal(pkm); - var orig = base.GetMatchRating(pkm); + var result = GetMatchRatingInternal(pk); + var orig = base.GetMatchRating(pk); return result > orig ? result : orig; } - private EncounterMatchRating GetMatchRatingInternal(PKM pkm) + private EncounterMatchRating GetMatchRatingInternal(PKM pk) { - if (pkm is IAlpha a && a.IsAlpha != IsAlpha) + if (pk is IAlpha a && a.IsAlpha != IsAlpha) return EncounterMatchRating.DeferredErrors; - if (FlawlessIVCount is not 0 && pkm.FlawlessIVCount < FlawlessIVCount) + if (FlawlessIVCount is not 0 && pk.FlawlessIVCount < FlawlessIVCount) return EncounterMatchRating.DeferredErrors; - if (IsFormArgMismatch(pkm)) + if (IsFormArgMismatch(pk)) return EncounterMatchRating.DeferredErrors; - if (!IsForcedMasteryCorrect(pkm)) + if (!IsForcedMasteryCorrect(pk)) return EncounterMatchRating.DeferredErrors; - return GetMoveCompatibility(pkm); + return GetMoveCompatibility(pk); } - private bool IsFormArgMismatch(PKM pkm) => pkm.Species switch + private bool IsFormArgMismatch(PKM pk) => pk.Species switch { - (int)Core.Species.Wyrdeer when Species is not (int)Core.Species.Wyrdeer && pkm is IFormArgument { FormArgument: 0 } => true, - (int)Core.Species.Overqwil when Species is not (int)Core.Species.Overqwil && pkm is IFormArgument { FormArgument: 0 } => true, - (int)Core.Species.Basculegion when Species is not (int)Core.Species.Basculegion && pkm is IFormArgument { FormArgument: 0 } => true, + (int)Core.Species.Wyrdeer when Species is not (int)Core.Species.Wyrdeer && pk is IFormArgument { FormArgument: 0 } => true, + (int)Core.Species.Overqwil when Species is not (int)Core.Species.Overqwil && pk is IFormArgument { FormArgument: 0 } => true, + (int)Core.Species.Basculegion when Species is not (int)Core.Species.Basculegion && pk is IFormArgument { FormArgument: 0 } => true, _ => false, }; - private EncounterMatchRating GetMoveCompatibility(PKM pkm) + private EncounterMatchRating GetMoveCompatibility(PKM pk) { // Check for Alpha move compatibility. - if (pkm is not PA8 pa) + if (pk is not PA8 pa) return EncounterMatchRating.Match; var alphaMove = pa.AlphaMove; @@ -154,22 +154,22 @@ private EncounterMatchRating GetMoveCompatibility(PKM pkm) return EncounterMatchRating.Match; } - public bool IsForcedMasteryCorrect(PKM pkm) + public bool IsForcedMasteryCorrect(PKM pk) { - if (pkm is not IMoveShop8Mastery p) + if (pk is not IMoveShop8Mastery p) return true; // Can't check. bool allowAlphaPurchaseBug = Area.Type is not SlotType.OverworldMMO; // Everything else Alpha is pre-1.1 - var level = pkm.Met_Level; + var level = pk.Met_Level; var index = PersonalTable.LA.GetFormIndex(Species, Form); var learn = Legal.LevelUpLA[index]; - ushort alpha = pkm is PA8 pa ? pa.AlphaMove : (ushort)0; + ushort alpha = pk is PA8 pa ? pa.AlphaMove : (ushort)0; if (!p.IsValidPurchasedEncounter(learn, level, alpha, allowAlphaPurchaseBug)) return false; Span moves = stackalloc int[4]; var mastery = Legal.MasteryLA[index]; - if (pkm is PA8 { AlphaMove: not 0 } pa8) + if (pk is PA8 { AlphaMove: not 0 } pa8) { moves[0] = pa8.AlphaMove; learn.SetEncounterMovesBackwards(level, moves, 1); diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs index c74ca4793..20fe45779 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs @@ -1,224 +1,223 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in . +/// +/// +public sealed record EncounterSlot8b : EncounterSlot { - /// - /// Encounter Slot found in . - /// - /// - public sealed record EncounterSlot8b : EncounterSlot + public override int Generation => 8; + public bool IsUnderground => Area.Location is (>= 508 and <= 617); + public bool IsMarsh => Area.Location is (>= 219 and <= 224); + public readonly bool IsBCAT; + public override Ball FixedBall => IsMarsh ? Ball.Safari : Ball.None; + + public EncounterSlot8b(EncounterArea area, ushort species, byte form, byte min, byte max, bool isBCAT = false) : base(area, species, form, min, max) { - public override int Generation => 8; - public bool IsUnderground => Area.Location is (>= 508 and <= 617); - public bool IsMarsh => Area.Location is (>= 219 and <= 224); - public readonly bool IsBCAT; - public override Ball FixedBall => IsMarsh ? Ball.Safari : Ball.None; - - public EncounterSlot8b(EncounterArea area, ushort species, byte form, byte min, byte max, bool isBCAT = false) : base(area, species, form, min, max) - { - IsBCAT = isBCAT; - } - - public override EncounterMatchRating GetMatchRating(PKM pkm) - { - bool isHidden = pkm.AbilityNumber == 4; - if (isHidden && this.IsPartialMatchHidden(pkm.Species, Species)) - return EncounterMatchRating.PartialMatch; - return base.GetMatchRating(pkm); - } - - protected override void SetFormatSpecificData(PKM pk) - { - if (IsUnderground) - { - if (GetBaseEggMove(out int move1)) - pk.RelearnMove1 = move1; - } - pk.SetRandomEC(); - } - - public bool CanBeUndergroundMove(int move) - { - var et = PersonalTable.BDSP; - var sf = (PersonalInfoBDSP)et.GetFormEntry(Species, Form); - var species = sf.HatchSpecies; - if (IsBCAT && IgnoreEggMoves.TryGetValue(species, out var exclude) && Array.IndexOf(exclude, move) != -1) - return false; - - var baseEgg = Legal.EggMovesBDSP[species].Moves; - if (baseEgg.Length == 0) - return move == 0; - return Array.IndexOf(baseEgg, move) >= 0; - } - - public bool GetBaseEggMove(out int move) - { - var et = PersonalTable.BDSP; - var sf = (PersonalInfoBDSP)et.GetFormEntry(Species, Form); - var species = sf.HatchSpecies; - - int[] Exclude = IgnoreEggMoves.TryGetValue(species, out var exclude) ? exclude : Array.Empty(); - var baseEgg = Legal.EggMovesBDSP[species].Moves; - if (baseEgg.Length == 0) - { - move = 0; - return false; - } - - // Official method creates a new List() with all the egg moves, removes all ignored, then picks a random index. - // We'll just loop instead to not allocate, and because it's a >50% chance the move won't be ignored, thus faster. - var rnd = Util.Rand; - while (true) - { - var index = rnd.Next(baseEgg.Length); - move = baseEgg[index]; - if (!IsBCAT || Array.IndexOf(Exclude, move) == -1) - return true; - } - } - - public bool CanUseRadar => Area.Type is SlotType.Grass && !IsUnderground && !IsMarsh && Location switch - { - 195 or 196 => false, // Oreburgh Mine - 203 or 204 or 205 or 208 or 209 or 210 or 211 or 212 or 213 or 214 or 215 => false, // Mount Coronet, 206/207 exterior - >= 225 and <= 243 => false, // Solaceon Ruins - 244 or 245 or 246 or 247 or 248 or 249 => false, // Victory Road - 252 => false, // Ravaged Path - 255 or 256 => false, // Oreburgh Gate - 260 or 261 or 262 => false, // Stark Mountain, 259 exterior - >= 264 and <= 284 => false, // Turnback Cave - 286 or 287 or 288 or 289 or 290 or 291 => false, // Snowpoint Temple - 292 or 293 => false, // Wayward Cave - 294 or 295 => false, // Ruin Maniac Cave - 296 => false, // Maniac Tunnel - 299 or 300 or 301 or 302 or 303 or 304 or 305 => false, // Iron Island, 298 exterior - 306 or 307 or 308 or 309 or 310 or 311 or 312 or 313 or 314 => false, // Old Chateau - 368 or 369 or 370 or 371 or 372 => false, // Route 209 (Lost Tower) - _ => true, - }; - - protected override HiddenAbilityPermission IsHiddenAbilitySlot() => CanUseRadar ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; - - /// - /// Unreferenced in v1.0, so all egg moves are possible for ROM encounters. - /// Since the Underground supports BCAT distributions, we will keep this around on the off chance they do utilize that method of distribution. - /// - private static readonly Dictionary IgnoreEggMoves = new() - { - {004, new[] {394}}, // Charmander - {016, new[] {403}}, // Pidgey - {019, new[] {044}}, // Rattata - {027, new[] {229}}, // Sandshrew - {037, new[] {180,050,326}}, // Vulpix - {050, new[] {310}}, // Diglett - {056, new[] {370}}, // Mankey - {058, new[] {242,336,394}}, // Growlithe - {060, new[] {061,341}}, // Poliwag - {066, new[] {282}}, // Machop - {077, new[] {172}}, // Ponyta - {079, new[] {428}}, // Slowpoke - {083, new[] {348}}, // Farfetchd - {084, new[] {098,283}}, // Doduo - {086, new[] {227}}, // Seel - {098, new[] {175,021}}, // Krabby - {102, new[] {235}}, // Exeggcute - {108, new[] {187}}, // Lickitung - {109, new[] {194}}, // Koffing - {113, new[] {270}}, // Chansey - {114, new[] {072}}, // Tangela - {115, new[] {023,116}}, // Kangaskhan - {116, new[] {225}}, // Horsea - {122, new[] {102,298}}, // Mr. Mime - {127, new[] {450,276}}, // Pinsir - {133, new[] {204,343}}, // Eevee - {140, new[] {341}}, // Kabuto - {143, new[] {122,562}}, // Snorlax - {147, new[] {349,407}}, // Dratini - {152, new[] {267,312,034}}, // Chikorita - {155, new[] {098,038}}, // Cyndaquil - {158, new[] {242,037,056}}, // Totodile - {161, new[] {179}}, // Sentret - {170, new[] {175}}, // Chinchou - {173, new[] {150}}, // Cleffa - {179, new[] {036,268}}, // Mareep - {183, new[] {276}}, // Marill - {187, new[] {388}}, // Hoppip - {190, new[] {103,097}}, // Aipom - {191, new[] {073,275}}, // Sunkern - {198, new[] {017,372}}, // Murkrow - {200, new[] {180}}, // Misdreavus - {204, new[] {038}}, // Pineco - {206, new[] {246}}, // Dunsparce - {209, new[] {242,423,424,422}}, // Snubbull - {214, new[] {224}}, // Heracross - {216, new[] {313}}, // Teddiursa - {218, new[] {414}}, // Slugma - {220, new[] {036}}, // Swinub - {222, new[] {392}}, // Corsola - {223, new[] {062}}, // Remoraid - {226, new[] {056,469}}, // Mantine - {227, new[] {065,413}}, // Skarmory - {228, new[] {251,424}}, // Houndour - {234, new[] {428}}, // Stantler - {236, new[] {270}}, // Tyrogue - {238, new[] {008}}, // Smoochum - {252, new[] {283,437}}, // Treecko - {255, new[] {179,297}}, // Torchic - {261, new[] {281,389,583}}, // Poochyena - {270, new[] {175,055}}, // Lotad - {276, new[] {413}}, // Taillow - {278, new[] {054,097}}, // Wingull - {283, new[] {453}}, // Surskit - {285, new[] {388,402}}, // Shroomish - {296, new[] {197}}, // Makuhita - {298, new[] {021}}, // Azurill - {299, new[] {335}}, // Nosepass - {300, new[] {252}}, // Skitty - {302, new[] {212}}, // Sableye - {303, new[] {389}}, // Mawile - {304, new[] {442}}, // Aron - {309, new[] {435,422}}, // Electrike - {311, new[] {435,204}}, // Plusle - {312, new[] {435,313}}, // Minun - {313, new[] {227}}, // Volbeat - {314, new[] {227}}, // Illumise - {315, new[] {235}}, // Roselia - {316, new[] {220,441}}, // Gulpin - {320, new[] {034}}, // Wailmer - {322, new[] {281}}, // Numel - {324, new[] {284,499}}, // Torkoal - {325, new[] {428}}, // Spoink - {328, new[] {414}}, // Trapinch - {336, new[] {400}}, // Seviper - {339, new[] {330}}, // Barboach - {341, new[] {283,282}}, // Corphish - {345, new[] {072}}, // Lileep - {352, new[] {050,492}}, // Kecleon - {353, new[] {425,566}}, // Shuppet - {357, new[] {437,235,349,692}}, // Tropius - {359, new[] {389,195}}, // Absol - {363, new[] {205}}, // Spheal - {369, new[] {401}}, // Relicanth - {370, new[] {392}}, // Luvdisc - {387, new[] {074}}, // Turtwig - {390, new[] {612}}, // Chimchar - {393, new[] {056}}, // Piplup - {399, new[] {111,205}}, // Bidoof - {408, new[] {043}}, // Cranidos - {417, new[] {608}}, // Pachirisu - {418, new[] {401}}, // Buizel - {422, new[] {262}}, // Shellos - {425, new[] {194,366}}, // Drifloon - {439, new[] {102,298}}, // Mime Jr. - {442, new[] {425}}, // Spiritomb - {443, new[] {225,328}}, // Gible - {446, new[] {122}}, // Munchlax - {449, new[] {303,328}}, // Hippopotas - {451, new[] {400}}, // Skorupi - {456, new[] {175}}, // Finneon - {458, new[] {056,469}}, // Mantyke - {459, new[] {054}}, // Snover - }; + IsBCAT = isBCAT; } + + public override EncounterMatchRating GetMatchRating(PKM pk) + { + bool isHidden = pk.AbilityNumber == 4; + if (isHidden && this.IsPartialMatchHidden(pk.Species, Species)) + return EncounterMatchRating.PartialMatch; + return base.GetMatchRating(pk); + } + + protected override void SetFormatSpecificData(PKM pk) + { + if (IsUnderground) + { + if (GetBaseEggMove(out int move1)) + pk.RelearnMove1 = move1; + } + pk.SetRandomEC(); + } + + public bool CanBeUndergroundMove(int move) + { + var et = PersonalTable.BDSP; + var sf = (PersonalInfoBDSP)et.GetFormEntry(Species, Form); + var species = sf.HatchSpecies; + if (IsBCAT && IgnoreEggMoves.TryGetValue(species, out var exclude) && Array.IndexOf(exclude, move) != -1) + return false; + + var baseEgg = Legal.EggMovesBDSP[species].Moves; + if (baseEgg.Length == 0) + return move == 0; + return Array.IndexOf(baseEgg, move) >= 0; + } + + public bool GetBaseEggMove(out int move) + { + var et = PersonalTable.BDSP; + var sf = (PersonalInfoBDSP)et.GetFormEntry(Species, Form); + var species = sf.HatchSpecies; + + int[] Exclude = IgnoreEggMoves.TryGetValue(species, out var exclude) ? exclude : Array.Empty(); + var baseEgg = Legal.EggMovesBDSP[species].Moves; + if (baseEgg.Length == 0) + { + move = 0; + return false; + } + + // Official method creates a new List() with all the egg moves, removes all ignored, then picks a random index. + // We'll just loop instead to not allocate, and because it's a >50% chance the move won't be ignored, thus faster. + var rnd = Util.Rand; + while (true) + { + var index = rnd.Next(baseEgg.Length); + move = baseEgg[index]; + if (!IsBCAT || Array.IndexOf(Exclude, move) == -1) + return true; + } + } + + public bool CanUseRadar => Area.Type is SlotType.Grass && !IsUnderground && !IsMarsh && Location switch + { + 195 or 196 => false, // Oreburgh Mine + 203 or 204 or 205 or 208 or 209 or 210 or 211 or 212 or 213 or 214 or 215 => false, // Mount Coronet, 206/207 exterior + >= 225 and <= 243 => false, // Solaceon Ruins + 244 or 245 or 246 or 247 or 248 or 249 => false, // Victory Road + 252 => false, // Ravaged Path + 255 or 256 => false, // Oreburgh Gate + 260 or 261 or 262 => false, // Stark Mountain, 259 exterior + >= 264 and <= 284 => false, // Turnback Cave + 286 or 287 or 288 or 289 or 290 or 291 => false, // Snowpoint Temple + 292 or 293 => false, // Wayward Cave + 294 or 295 => false, // Ruin Maniac Cave + 296 => false, // Maniac Tunnel + 299 or 300 or 301 or 302 or 303 or 304 or 305 => false, // Iron Island, 298 exterior + 306 or 307 or 308 or 309 or 310 or 311 or 312 or 313 or 314 => false, // Old Chateau + 368 or 369 or 370 or 371 or 372 => false, // Route 209 (Lost Tower) + _ => true, + }; + + protected override HiddenAbilityPermission IsHiddenAbilitySlot() => CanUseRadar ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never; + + /// + /// Unreferenced in v1.0, so all egg moves are possible for ROM encounters. + /// Since the Underground supports BCAT distributions, we will keep this around on the off chance they do utilize that method of distribution. + /// + private static readonly Dictionary IgnoreEggMoves = new() + { + {004, new[] {394}}, // Charmander + {016, new[] {403}}, // Pidgey + {019, new[] {044}}, // Rattata + {027, new[] {229}}, // Sandshrew + {037, new[] {180,050,326}}, // Vulpix + {050, new[] {310}}, // Diglett + {056, new[] {370}}, // Mankey + {058, new[] {242,336,394}}, // Growlithe + {060, new[] {061,341}}, // Poliwag + {066, new[] {282}}, // Machop + {077, new[] {172}}, // Ponyta + {079, new[] {428}}, // Slowpoke + {083, new[] {348}}, // Farfetchd + {084, new[] {098,283}}, // Doduo + {086, new[] {227}}, // Seel + {098, new[] {175,021}}, // Krabby + {102, new[] {235}}, // Exeggcute + {108, new[] {187}}, // Lickitung + {109, new[] {194}}, // Koffing + {113, new[] {270}}, // Chansey + {114, new[] {072}}, // Tangela + {115, new[] {023,116}}, // Kangaskhan + {116, new[] {225}}, // Horsea + {122, new[] {102,298}}, // Mr. Mime + {127, new[] {450,276}}, // Pinsir + {133, new[] {204,343}}, // Eevee + {140, new[] {341}}, // Kabuto + {143, new[] {122,562}}, // Snorlax + {147, new[] {349,407}}, // Dratini + {152, new[] {267,312,034}}, // Chikorita + {155, new[] {098,038}}, // Cyndaquil + {158, new[] {242,037,056}}, // Totodile + {161, new[] {179}}, // Sentret + {170, new[] {175}}, // Chinchou + {173, new[] {150}}, // Cleffa + {179, new[] {036,268}}, // Mareep + {183, new[] {276}}, // Marill + {187, new[] {388}}, // Hoppip + {190, new[] {103,097}}, // Aipom + {191, new[] {073,275}}, // Sunkern + {198, new[] {017,372}}, // Murkrow + {200, new[] {180}}, // Misdreavus + {204, new[] {038}}, // Pineco + {206, new[] {246}}, // Dunsparce + {209, new[] {242,423,424,422}}, // Snubbull + {214, new[] {224}}, // Heracross + {216, new[] {313}}, // Teddiursa + {218, new[] {414}}, // Slugma + {220, new[] {036}}, // Swinub + {222, new[] {392}}, // Corsola + {223, new[] {062}}, // Remoraid + {226, new[] {056,469}}, // Mantine + {227, new[] {065,413}}, // Skarmory + {228, new[] {251,424}}, // Houndour + {234, new[] {428}}, // Stantler + {236, new[] {270}}, // Tyrogue + {238, new[] {008}}, // Smoochum + {252, new[] {283,437}}, // Treecko + {255, new[] {179,297}}, // Torchic + {261, new[] {281,389,583}}, // Poochyena + {270, new[] {175,055}}, // Lotad + {276, new[] {413}}, // Taillow + {278, new[] {054,097}}, // Wingull + {283, new[] {453}}, // Surskit + {285, new[] {388,402}}, // Shroomish + {296, new[] {197}}, // Makuhita + {298, new[] {021}}, // Azurill + {299, new[] {335}}, // Nosepass + {300, new[] {252}}, // Skitty + {302, new[] {212}}, // Sableye + {303, new[] {389}}, // Mawile + {304, new[] {442}}, // Aron + {309, new[] {435,422}}, // Electrike + {311, new[] {435,204}}, // Plusle + {312, new[] {435,313}}, // Minun + {313, new[] {227}}, // Volbeat + {314, new[] {227}}, // Illumise + {315, new[] {235}}, // Roselia + {316, new[] {220,441}}, // Gulpin + {320, new[] {034}}, // Wailmer + {322, new[] {281}}, // Numel + {324, new[] {284,499}}, // Torkoal + {325, new[] {428}}, // Spoink + {328, new[] {414}}, // Trapinch + {336, new[] {400}}, // Seviper + {339, new[] {330}}, // Barboach + {341, new[] {283,282}}, // Corphish + {345, new[] {072}}, // Lileep + {352, new[] {050,492}}, // Kecleon + {353, new[] {425,566}}, // Shuppet + {357, new[] {437,235,349,692}}, // Tropius + {359, new[] {389,195}}, // Absol + {363, new[] {205}}, // Spheal + {369, new[] {401}}, // Relicanth + {370, new[] {392}}, // Luvdisc + {387, new[] {074}}, // Turtwig + {390, new[] {612}}, // Chimchar + {393, new[] {056}}, // Piplup + {399, new[] {111,205}}, // Bidoof + {408, new[] {043}}, // Cranidos + {417, new[] {608}}, // Pachirisu + {418, new[] {401}}, // Buizel + {422, new[] {262}}, // Shellos + {425, new[] {194,366}}, // Drifloon + {439, new[] {102,298}}, // Mime Jr. + {442, new[] {425}}, // Spiritomb + {443, new[] {225,328}}, // Gible + {446, new[] {122}}, // Munchlax + {449, new[] {303,328}}, // Hippopotas + {451, new[] {400}}, // Skorupi + {456, new[] {175}}, // Finneon + {458, new[] {056,469}}, // Mantyke + {459, new[] {054}}, // Snover + }; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs index f7a739a67..a44601253 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot7GO.cs @@ -1,54 +1,53 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot found in (GO Park, ). +/// +/// +public sealed record EncounterSlot7GO : EncounterSlotGO { - /// - /// Encounter Slot found in (GO Park, ). - /// - /// - public sealed record EncounterSlot7GO : EncounterSlotGO + public override int Generation => 7; + public override Ball FixedBall => Ball.None; // GO Park can override the ball; obey capture rules for LGP/E + + public EncounterSlot7GO(EncounterArea7g area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type) + : base(area, species, form, start, end, shiny, gender, type) { - public override int Generation => 7; - public override Ball FixedBall => Ball.None; // GO Park can override the ball; obey capture rules for LGP/E + } - public EncounterSlot7GO(EncounterArea7g area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type) - : base(area, species, form, start, end, shiny, gender, type) - { - } + protected override PKM GetBlank() => new PB7(); - protected override PKM GetBlank() => new PB7(); + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + var pb = (PB7) pk; + pb.AwakeningSetAllTo(2); + pk.SetRandomEC(); + pb.HeightScalar = PokeSizeUtil.GetRandomScalar(); + pb.WeightScalar = PokeSizeUtil.GetRandomScalar(); + pb.ResetHeight(); + pb.ResetWeight(); + pb.ResetCP(); + } - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pb = (PB7) pk; - pb.AwakeningSetAllTo(2); - pk.SetRandomEC(); - pb.HeightScalar = PokeSizeUtil.GetRandomScalar(); - pb.WeightScalar = PokeSizeUtil.GetRandomScalar(); - pb.ResetHeight(); - pb.ResetWeight(); - pb.ResetCP(); - } + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(-1, pi); - int nature = (int)criteria.GetNature(Nature.Random); - var ability = criteria.GetAbilityFromNumber(Ability); + pk.PID = Util.Rand32(); + pk.Nature = pk.StatNature = nature; + pk.Gender = gender; + pk.RefreshAbility(ability); + pk.SetRandomIVsGO(); + base.SetPINGA(pk, criteria); + } - pk.PID = Util.Rand32(); - pk.Nature = pk.StatNature = nature; - pk.Gender = gender; - pk.RefreshAbility(ability); - pk.SetRandomIVsGO(); - base.SetPINGA(pk, criteria); - } - - protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) - { - var moves = MoveLevelUp.GetEncounterMoves(pk, level, GameVersion.GG); - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); - } + protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) + { + var moves = MoveLevelUp.GetEncounterMoves(pk, level, GameVersion.GG); + pk.SetMoves(moves); + pk.SetMaximumPPCurrent(moves); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs index 856d88cfd..44f05e648 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlot8GO.cs @@ -1,189 +1,188 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Slot representing data transferred to (HOME). +/// +/// +public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship { + public override int Generation => 8; + /// - /// Encounter Slot representing data transferred to (HOME). - /// + /// Encounters need a Parent Game to determine the original moves when transferred to HOME. /// - public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship + /// + /// Future game releases might change this value. + /// With respect to date legality, new dates might be incompatible with initial values. + /// + public PogoImportFormat OriginFormat { get; } + + public EncounterSlot8GO(EncounterArea8g area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type, PogoImportFormat originFormat) + : base(area, species, form, start, end, shiny, gender, type) { - public override int Generation => 8; - - /// - /// Encounters need a Parent Game to determine the original moves when transferred to HOME. - /// - /// - /// Future game releases might change this value. - /// With respect to date legality, new dates might be incompatible with initial values. - /// - public PogoImportFormat OriginFormat { get; } - - public EncounterSlot8GO(EncounterArea8g area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type, PogoImportFormat originFormat) - : base(area, species, form, start, end, shiny, gender, type) - { - OriginFormat = originFormat; - } - - /// - /// Checks if the is compatible with the . - /// - public bool IsBallValid(Ball ball, int currentSpecies) - { - // GO does not natively produce Shedinja when evolving Nincada, and thus must be evolved in future games. - if (currentSpecies == (int)Core.Species.Shedinja && currentSpecies != Species) - return ball == Ball.Poke; - return Type.IsBallValid(ball); - } - - protected override PKM GetBlank() => OriginFormat switch - { - PogoImportFormat.PK7 => new PK8(), - PogoImportFormat.PB7 => new PB7(), - PogoImportFormat.PK8 => new PK8(), - PogoImportFormat.PA8 => new PA8(), - _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)), - }; - - private PersonalInfo GetPersonal() => OriginFormat switch - { - PogoImportFormat.PK7 => PersonalTable.USUM.GetFormEntry(Species, Form), - PogoImportFormat.PB7 => PersonalTable.GG.GetFormEntry(Species, Form), - PogoImportFormat.PK8 => PersonalTable.SWSH.GetFormEntry(Species, Form), - PogoImportFormat.PA8 => PersonalTable.LA.GetFormEntry(Species, Form), - _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)), - }; - - internal GameVersion OriginGroup => OriginFormat switch - { - PogoImportFormat.PK7 => GameVersion.USUM, - PogoImportFormat.PB7 => GameVersion.GG, - PogoImportFormat.PK8 => GameVersion.SWSH, - PogoImportFormat.PA8 => GameVersion.PLA, - _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)), - }; - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - pk.HT_Name = "PKHeX"; - pk.CurrentHandler = 1; - if (pk is IHandlerLanguage l) - l.HT_Language = 2; - - base.ApplyDetails(sav, criteria, pk); - var ball = Type.GetValidBall(); - if (ball != Ball.None) - pk.Ball = (int)ball; - - if (pk is IScaledSize s) - { - s.HeightScalar = PokeSizeUtil.GetRandomScalar(); - s.WeightScalar = PokeSizeUtil.GetRandomScalar(); - } - - if (OriginFormat is PogoImportFormat.PA8) - { - var pa8 = (PA8)pk; - pa8.ResetHeight(); - pa8.ResetWeight(); - pa8.HeightScalarCopy = pa8.HeightScalar; - } - - pk.OT_Friendship = OT_Friendship; - - pk.SetRandomEC(); - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = GetPersonal(); - if (OriginFormat is PogoImportFormat.PK7) - pk.EXP = Experience.GetEXP(LevelMin, pi.EXPGrowth); - int gender = criteria.GetGender(-1, pi); - int nature = (int)criteria.GetNature(Nature.Random); - var ability = criteria.GetAbilityFromNumber(Ability); - - pk.PID = Util.Rand32(); - pk.Nature = pk.StatNature = nature; - pk.Gender = gender; - - pk.AbilityNumber = 1 << ability; - var abilities = pi.Abilities; - if ((uint)ability < abilities.Count) - pk.Ability = abilities[ability]; - - pk.SetRandomIVsGO(); - base.SetPINGA(pk, criteria); - } - - protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) - { - var moves = MoveLevelUp.GetEncounterMoves(pk, level, OriginGroup); - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); - } - - public override EncounterMatchRating GetMatchRating(PKM pkm) - { - if (IsMatchPartial(pkm)) - return EncounterMatchRating.PartialMatch; - return base.GetMatchRating(pkm) == EncounterMatchRating.PartialMatch ? EncounterMatchRating.PartialMatch : EncounterMatchRating.Match; - } - - public byte OT_Friendship => Species switch - { - (int)Core.Species.Timburr when Form == 0 => 70, - (int)Core.Species.Stunfisk when Form == 0 => 70, - (int)Core.Species.Hoopa when Form == 1 => 50, - _ => GetHOMEFriendship(), - }; - - private byte GetHOMEFriendship() - { - var fs = (byte)GetPersonal().BaseFriendship; - if (fs == 70) - return 50; - return fs; - } - - private bool IsMatchPartial(PKM pk) - { - var stamp = GetTimeStamp(pk.Met_Year + 2000, pk.Met_Month, pk.Met_Day); - if (!IsWithinStartEnd(stamp)) - return true; - if (!GetIVsAboveMinimum(pk)) - return true; - if (!GetIVsBelowMaximum(pk)) - return true; - - // Eevee & Glaceon have different base friendships. Make sure if it is invalid that we yield the other encounter before. - if (pk.OT_Friendship != OT_Friendship) - return true; - - return Species switch - { - (int)Core.Species.Yamask when pk.Species != Species && Form == 1 => pk is IFormArgument { FormArgument: 0 }, - (int)Core.Species.Milcery when pk.Species != Species => pk is IFormArgument { FormArgument: 0 }, - (int)Core.Species.Qwilfish when pk.Species != Species && Form == 1 => pk is IFormArgument { FormArgument: 0 }, - (int)Core.Species.Basculin when pk.Species != Species && Form == 2 => pk is IFormArgument { FormArgument: 0 }, - (int)Core.Species.Stantler when pk.Species != Species => pk is IFormArgument { FormArgument: 0 }, - - (int)Core.Species.Runerigus => pk is IFormArgument { FormArgument: not 0 }, - (int)Core.Species.Alcremie => pk is IFormArgument { FormArgument: not 0 }, - (int)Core.Species.Wyrdeer => pk is IFormArgument { FormArgument: not 0 }, - (int)Core.Species.Basculegion => pk is IFormArgument { FormArgument: not 0 }, - (int)Core.Species.Overqwil => pk is IFormArgument { FormArgument: not 0 }, - - _ => false, - }; - } + OriginFormat = originFormat; } - public enum PogoImportFormat : byte + /// + /// Checks if the is compatible with the . + /// + public bool IsBallValid(Ball ball, int currentSpecies) { - PK7 = 0, - PB7 = 1, - PK8 = 2, - PA8 = 3, + // GO does not natively produce Shedinja when evolving Nincada, and thus must be evolved in future games. + if (currentSpecies == (int)Core.Species.Shedinja && currentSpecies != Species) + return ball == Ball.Poke; + return Type.IsBallValid(ball); + } + + protected override PKM GetBlank() => OriginFormat switch + { + PogoImportFormat.PK7 => new PK8(), + PogoImportFormat.PB7 => new PB7(), + PogoImportFormat.PK8 => new PK8(), + PogoImportFormat.PA8 => new PA8(), + _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)), + }; + + private PersonalInfo GetPersonal() => OriginFormat switch + { + PogoImportFormat.PK7 => PersonalTable.USUM.GetFormEntry(Species, Form), + PogoImportFormat.PB7 => PersonalTable.GG.GetFormEntry(Species, Form), + PogoImportFormat.PK8 => PersonalTable.SWSH.GetFormEntry(Species, Form), + PogoImportFormat.PA8 => PersonalTable.LA.GetFormEntry(Species, Form), + _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)), + }; + + internal GameVersion OriginGroup => OriginFormat switch + { + PogoImportFormat.PK7 => GameVersion.USUM, + PogoImportFormat.PB7 => GameVersion.GG, + PogoImportFormat.PK8 => GameVersion.SWSH, + PogoImportFormat.PA8 => GameVersion.PLA, + _ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)), + }; + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + pk.HT_Name = "PKHeX"; + pk.CurrentHandler = 1; + if (pk is IHandlerLanguage l) + l.HT_Language = 2; + + base.ApplyDetails(sav, criteria, pk); + var ball = Type.GetValidBall(); + if (ball != Ball.None) + pk.Ball = (int)ball; + + if (pk is IScaledSize s) + { + s.HeightScalar = PokeSizeUtil.GetRandomScalar(); + s.WeightScalar = PokeSizeUtil.GetRandomScalar(); + } + + if (OriginFormat is PogoImportFormat.PA8) + { + var pa8 = (PA8)pk; + pa8.ResetHeight(); + pa8.ResetWeight(); + pa8.HeightScalarCopy = pa8.HeightScalar; + } + + pk.OT_Friendship = OT_Friendship; + + pk.SetRandomEC(); + } + + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = GetPersonal(); + if (OriginFormat is PogoImportFormat.PK7) + pk.EXP = Experience.GetEXP(LevelMin, pi.EXPGrowth); + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + var ability = criteria.GetAbilityFromNumber(Ability); + + pk.PID = Util.Rand32(); + pk.Nature = pk.StatNature = nature; + pk.Gender = gender; + + pk.AbilityNumber = 1 << ability; + var abilities = pi.Abilities; + if ((uint)ability < abilities.Count) + pk.Ability = abilities[ability]; + + pk.SetRandomIVsGO(); + base.SetPINGA(pk, criteria); + } + + protected override void SetEncounterMoves(PKM pk, GameVersion version, int level) + { + var moves = MoveLevelUp.GetEncounterMoves(pk, level, OriginGroup); + pk.SetMoves(moves); + pk.SetMaximumPPCurrent(moves); + } + + public override EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return base.GetMatchRating(pk) == EncounterMatchRating.PartialMatch ? EncounterMatchRating.PartialMatch : EncounterMatchRating.Match; + } + + public byte OT_Friendship => Species switch + { + (int)Core.Species.Timburr when Form == 0 => 70, + (int)Core.Species.Stunfisk when Form == 0 => 70, + (int)Core.Species.Hoopa when Form == 1 => 50, + _ => GetHOMEFriendship(), + }; + + private byte GetHOMEFriendship() + { + var fs = (byte)GetPersonal().BaseFriendship; + if (fs == 70) + return 50; + return fs; + } + + private bool IsMatchPartial(PKM pk) + { + var stamp = GetTimeStamp(pk.Met_Year + 2000, pk.Met_Month, pk.Met_Day); + if (!IsWithinStartEnd(stamp)) + return true; + if (!GetIVsAboveMinimum(pk)) + return true; + if (!GetIVsBelowMaximum(pk)) + return true; + + // Eevee & Glaceon have different base friendships. Make sure if it is invalid that we yield the other encounter before. + if (pk.OT_Friendship != OT_Friendship) + return true; + + return Species switch + { + (int)Core.Species.Yamask when pk.Species != Species && Form == 1 => pk is IFormArgument { FormArgument: 0 }, + (int)Core.Species.Milcery when pk.Species != Species => pk is IFormArgument { FormArgument: 0 }, + (int)Core.Species.Qwilfish when pk.Species != Species && Form == 1 => pk is IFormArgument { FormArgument: 0 }, + (int)Core.Species.Basculin when pk.Species != Species && Form == 2 => pk is IFormArgument { FormArgument: 0 }, + (int)Core.Species.Stantler when pk.Species != Species => pk is IFormArgument { FormArgument: 0 }, + + (int)Core.Species.Runerigus => pk is IFormArgument { FormArgument: not 0 }, + (int)Core.Species.Alcremie => pk is IFormArgument { FormArgument: not 0 }, + (int)Core.Species.Wyrdeer => pk is IFormArgument { FormArgument: not 0 }, + (int)Core.Species.Basculegion => pk is IFormArgument { FormArgument: not 0 }, + (int)Core.Species.Overqwil => pk is IFormArgument { FormArgument: not 0 }, + + _ => false, + }; } } + +public enum PogoImportFormat : byte +{ + PK7 = 0, + PB7 = 1, + PK8 = 2, + PA8 = 3, +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs index cd1274d70..d5d22a3e9 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs @@ -1,170 +1,169 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains details about an encounter that can be found in . +/// +public abstract record EncounterSlotGO : EncounterSlot, IPogoSlot { - /// - /// Contains details about an encounter that can be found in . - /// - public abstract record EncounterSlotGO : EncounterSlot, IPogoSlot + /// + public int StartDate { get; } + + /// + public int EndDate { get; } + + public override Shiny Shiny { get; } + + /// + public PogoType Type { get; } + + /// + public Gender Gender { get; } + + public override bool IsShiny => Shiny.IsShiny(); + + public override Ball FixedBall => Type.GetValidBall(); + + protected EncounterSlotGO(EncounterArea area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type) : base(area, species, form, type.GetMinLevel(), EncountersGO.MAX_LEVEL) { - /// - public int Start { get; } + StartDate = start; + EndDate = end; - /// - public int End { get; } + Shiny = shiny; + Gender = gender; + Type = type; + } - public override Shiny Shiny { get; } - - /// - public PogoType Type { get; } - - /// - public Gender Gender { get; } - - public override bool IsShiny => Shiny.IsShiny(); - - public override Ball FixedBall => Type.GetValidBall(); - - protected EncounterSlotGO(EncounterArea area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type) : base(area, species, form, type.GetMinLevel(), EncountersGO.MAX_LEVEL) + public sealed override string LongName + { + get { - Start = start; - End = end; - - Shiny = shiny; - Gender = gender; - Type = type; + var init = $"{Name} ({Type})"; + if (StartDate == 0 && EndDate == 0) + return init; + return $"{init}: {GetDateString(StartDate)}-{GetDateString(EndDate)}"; } + } - public sealed override string LongName + private static string GetDateString(int time) => time == 0 ? "X" : $"{GetDate(time):yyyy.MM.dd}"; + + private static DateTime GetDate(int time) + { + var d = time & 0xFF; + var m = (time >> 8) & 0xFF; + var y = time >> 16; + return new DateTime(y, m, d); + } + + public bool IsWithinStartEnd(int stamp) + { + if (EndDate == 0) + return StartDate <= stamp && GetDate(stamp) <= GetMaxDateTime(); + if (StartDate == 0) + return stamp <= EndDate; + return StartDate <= stamp && stamp <= EndDate; + } + + /// + /// Converts a split timestamp into a single integer. + /// + public static int GetTimeStamp(int year, int month, int day) => (year << 16) | (month << 8) | day; + + private static DateTime GetMaxDateTime() => DateTime.UtcNow.AddHours(12); // UTC+12 for Kiribati, no daylight savings + + /// + /// Gets a random date within the availability range. + /// + public DateTime GetRandomValidDate() + { + if (StartDate == 0) + return EndDate == 0 ? GetMaxDateTime() : GetDate(EndDate); + + var start = GetDate(StartDate); + if (EndDate == 0) + return start; + var end = GetDate(EndDate); + return DateUtil.GetRandomDateWithin(start, end); + } + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + if (StartDate != 0 || EndDate != 0) + pk.MetDate = GetRandomValidDate(); + if (Gender != Gender.Random) + pk.Gender = (int)Gender; + pk.SetRandomIVsGO(Type.GetMinIV(), Type.GetMaxIV()); + } + + public bool GetIVsAboveMinimum(PKM pk) + { + int min = Type.GetMinIV(); + if (min == 0) + return true; + return GetIVsAboveMinimum(pk, min); + } + + public bool GetIVsBelowMaximum(PKM pk) + { + int max = Type.GetMaxIV(); + if (max == 15) + return true; + return GetIVsBelowMaximum(pk, max); + } + + private static bool GetIVsAboveMinimum(PKM pk, int min) + { + if (pk.IV_ATK >> 1 < min) // ATK + return false; + if (pk.IV_DEF >> 1 < min) // DEF + return false; + return pk.IV_HP >> 1 >= min; // HP + } + + private static bool GetIVsBelowMaximum(PKM pk, int max) + { + if (pk.IV_ATK >> 1 > max) // ATK + return false; + if (pk.IV_DEF >> 1 > max) // DEF + return false; + return pk.IV_HP >> 1 <= max; // HP + } + + public bool GetIVsValid(PKM pk) + { + if (!GetIVsAboveMinimum(pk)) + return false; + if (!GetIVsBelowMaximum(pk)) + return false; + + // HP * 2 | 1 -> HP + // ATK * 2 | 1 -> ATK&SPA + // DEF * 2 | 1 -> DEF&SPD + // Speed is random. + + // All IVs must be odd (except speed) and equal to their counterpart. + if ((pk.GetIV(1) & 1) != 1 || pk.GetIV(1) != pk.GetIV(4)) // ATK=SPA + return false; + if ((pk.GetIV(2) & 1) != 1 || pk.GetIV(2) != pk.GetIV(5)) // DEF=SPD + return false; + return (pk.GetIV(0) & 1) == 1; // HP + } + + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + switch (Shiny) { - get - { - var init = $"{Name} ({Type})"; - if (Start == 0 && End == 0) - return init; - return $"{init}: {GetDateString(Start)}-{GetDateString(End)}"; - } - } + case Shiny.Random when !pk.IsShiny && criteria.Shiny.IsShiny(): + case Shiny.Always when !pk.IsShiny: // Force Square + pk.PID = (uint)(((pk.TID ^ pk.SID ^ (pk.PID & 0xFFFF) ^ 0) << 16) | (pk.PID & 0xFFFF)); + break; - private static string GetDateString(int time) => time == 0 ? "X" : GetDate(time).ToString("yyyy.MM.dd"); - - private static DateTime GetDate(int time) - { - var d = time & 0xFF; - var m = (time >> 8) & 0xFF; - var y = time >> 16; - return new DateTime(y, m, d); - } - - public bool IsWithinStartEnd(int stamp) - { - if (End == 0) - return Start <= stamp && GetDate(stamp) <= GetMaxDateTime(); - if (Start == 0) - return stamp <= End; - return Start <= stamp && stamp <= End; - } - - /// - /// Converts a split timestamp into a single integer. - /// - public static int GetTimeStamp(int year, int month, int day) => (year << 16) | (month << 8) | day; - - private static DateTime GetMaxDateTime() => DateTime.UtcNow.AddHours(12); // UTC+12 for Kiribati, no daylight savings - - /// - /// Gets a random date within the availability range. - /// - public DateTime GetRandomValidDate() - { - if (Start == 0) - return End == 0 ? GetMaxDateTime() : GetDate(End); - - var start = GetDate(Start); - if (End == 0) - return start; - var end = GetDate(End); - return DateUtil.GetRandomDateWithin(start, end); - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - if (Start != 0 || End != 0) - pk.MetDate = GetRandomValidDate(); - if (Gender != Gender.Random) - pk.Gender = (int)Gender; - pk.SetRandomIVsGO(Type.GetMinIV(), Type.GetMaxIV()); - } - - public bool GetIVsAboveMinimum(PKM pkm) - { - int min = Type.GetMinIV(); - if (min == 0) - return true; - return GetIVsAboveMinimum(pkm, min); - } - - public bool GetIVsBelowMaximum(PKM pkm) - { - int max = Type.GetMaxIV(); - if (max == 15) - return true; - return GetIVsBelowMaximum(pkm, max); - } - - private static bool GetIVsAboveMinimum(PKM pkm, int min) - { - if (pkm.IV_ATK >> 1 < min) // ATK - return false; - if (pkm.IV_DEF >> 1 < min) // DEF - return false; - return pkm.IV_HP >> 1 >= min; // HP - } - - private static bool GetIVsBelowMaximum(PKM pkm, int max) - { - if (pkm.IV_ATK >> 1 > max) // ATK - return false; - if (pkm.IV_DEF >> 1 > max) // DEF - return false; - return pkm.IV_HP >> 1 <= max; // HP - } - - public bool GetIVsValid(PKM pkm) - { - if (!GetIVsAboveMinimum(pkm)) - return false; - if (!GetIVsBelowMaximum(pkm)) - return false; - - // HP * 2 | 1 -> HP - // ATK * 2 | 1 -> ATK&SPA - // DEF * 2 | 1 -> DEF&SPD - // Speed is random. - - // All IVs must be odd (except speed) and equal to their counterpart. - if ((pkm.GetIV(1) & 1) != 1 || pkm.GetIV(1) != pkm.GetIV(4)) // ATK=SPA - return false; - if ((pkm.GetIV(2) & 1) != 1 || pkm.GetIV(2) != pkm.GetIV(5)) // DEF=SPD - return false; - return (pkm.GetIV(0) & 1) == 1; // HP - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - switch (Shiny) - { - case Shiny.Random when !pk.IsShiny && criteria.Shiny.IsShiny(): - case Shiny.Always when !pk.IsShiny: // Force Square - pk.PID = (uint)(((pk.TID ^ pk.SID ^ (pk.PID & 0xFFFF) ^ 0) << 16) | (pk.PID & 0xFFFF)); - break; - - case Shiny.Random when pk.IsShiny && !criteria.Shiny.IsShiny(): - case Shiny.Never when pk.IsShiny: // Force Not Shiny - pk.PID ^= 0x1000_0000; - break; - } + case Shiny.Random when pk.IsShiny && !criteria.Shiny.IsShiny(): + case Shiny.Never when pk.IsShiny: // Force Not Shiny + pk.PID ^= 0x1000_0000; + break; } } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/IPogoSlot.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/IPogoSlot.cs index ce7af5344..a26708161 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/IPogoSlot.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/IPogoSlot.cs @@ -1,24 +1,23 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Stores details about encounters relevant for legality. +/// +public interface IPogoSlot { - /// - /// Stores details about encounters relevant for legality. - /// - public interface IPogoSlot - { - /// Start date the encounter became available. If zero, no date specified (unbounded start). - int Start { get; } + /// Start date the encounter became available. If zero, no date specified (unbounded start). + int StartDate { get; } - /// Last day the encounter was available. If zero, no date specified (unbounded finish). - /// If there is no end date (yet), we'll try to clamp to a date in the near-future to prevent it from being open-ended. - int End { get; } + /// Last day the encounter was available. If zero, no date specified (unbounded finish). + /// If there is no end date (yet), we'll try to clamp to a date in the near-future to prevent it from being open-ended. + int EndDate { get; } - /// Possibility of shiny for the encounter. - Shiny Shiny { get; } + /// Possibility of shiny for the encounter. + Shiny Shiny { get; } - /// Method the Pokmon may be encountered with. - PogoType Type { get; } + /// Method the Pokmon may be encountered with. + PogoType Type { get; } - /// Gender the Pokmon may be encountered with. - Gender Gender { get; } - } + /// Gender the Pokmon may be encountered with. + Gender Gender { get; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/PogoType.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/PogoType.cs index f8c82fced..cddd49459 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/PogoType.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/PogoType.cs @@ -1,125 +1,124 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Type for various encounters. +/// +public enum PogoType : byte +{ + None, // Don't use this. + + /// Wild encounter, no special requirements + Wild, + + /// Pokmon Egg, requires Lv. 1 and IV = 1 + Egg, + /// Strange Egg, requires Lv. 8 and IV = 1 + EggS, + + /// Raid Boss, requires Lv. 20 and IV = 1 + Raid = 10, + /// Raid Boss (Mythical), requires Lv. 20 and IV = 10 + RaidM, + + /// Field Research Reward, requires Lv. 15 and IV = 1 + Research = 20, + /// Field Research Reward (Mythical), requires Lv. 15 and IV = 10 + ResearchM, + /// Field Research Reward, requires Lv. 15 and IV = 10 (Pok Ball only) + ResearchP, + + /// GO Battle League Reward, requires Lv. 20 and IV = 1 + GBL, + /// GO Battle League Reward (Mythical), requires Lv. 20 and IV = 10 + GBLM, + /// GO Battle League Reward, requires Lv. 20 and IV = 0 + /// On GO Battle Day (September 18, 2021), IV floor and ceiling were both temporarily set to 0 for non-Legendary encounters. This was fixed at 14:43 UTC (September 17, 2021). + GBLZero, + /// GO Battle League Reward, requires Lv. 20 and IV = 0 + /// On GO Battle Day (September 18, 2021), IV floor was set to 0 after a mishap that also set the IV ceiling to 0. + GBLDay, + + /// Purified, requires Lv. 8 and IV = 1 (Premier Ball only) + Shadow = 30, +} + +public static class PogoTypeExtensions { /// - /// Encounter Type for various encounters. + /// Gets the minimum level (relative to GO's 1-) the must have. /// - public enum PogoType : byte + /// Descriptor indicating how the Pokmon was encountered in GO. + public static byte GetMinLevel(this PogoType encounterType) => encounterType switch { - None, // Don't use this. + PogoType.EggS => 8, + PogoType.Raid => 20, + PogoType.RaidM => 20, + PogoType.Research => 15, + PogoType.ResearchM => 15, + PogoType.ResearchP => 15, + PogoType.GBL => 20, + PogoType.GBLM => 20, + PogoType.GBLZero => 20, + PogoType.GBLDay => 20, + PogoType.Shadow => 8, + _ => 1, + }; - /// Wild encounter, no special requirements - Wild, + /// + /// Gets the minimum IVs (relative to GO's 0-15) the must have. + /// + /// Descriptor indicating how the Pokmon was encountered in GO. + /// Required minimum IV (0-15) + public static int GetMinIV(this PogoType encounterType) => encounterType switch + { + PogoType.Wild => 0, + PogoType.RaidM => 10, + PogoType.ResearchM => 10, + PogoType.ResearchP => 10, + PogoType.GBLM => 10, + PogoType.GBLZero => 0, + PogoType.GBLDay => 0, + _ => 1, + }; - /// Pokmon Egg, requires Lv. 1 and IV = 1 - Egg, - /// Strange Egg, requires Lv. 8 and IV = 1 - EggS, + /// + /// Gets the minimum IVs (relative to GO's 0-15) the must have. + /// + /// Descriptor indicating how the Pokmon was encountered in GO. + /// Required minimum IV (0-15) + public static int GetMaxIV(this PogoType encounterType) => encounterType switch + { + PogoType.GBLZero => 0, + _ => 15, + }; - /// Raid Boss, requires Lv. 20 and IV = 1 - Raid = 10, - /// Raid Boss (Mythical), requires Lv. 20 and IV = 10 - RaidM, - - /// Field Research Reward, requires Lv. 15 and IV = 1 - Research = 20, - /// Field Research Reward (Mythical), requires Lv. 15 and IV = 10 - ResearchM, - /// Field Research Reward, requires Lv. 15 and IV = 10 (Pok Ball only) - ResearchP, - - /// GO Battle League Reward, requires Lv. 20 and IV = 1 - GBL, - /// GO Battle League Reward (Mythical), requires Lv. 20 and IV = 10 - GBLM, - /// GO Battle League Reward, requires Lv. 20 and IV = 0 - /// On GO Battle Day (September 18, 2021), IV floor and ceiling were both temporarily set to 0 for non-Legendary encounters. This was fixed at 14:43 UTC (September 17, 2021). - GBLZero, - /// GO Battle League Reward, requires Lv. 20 and IV = 0 - /// On GO Battle Day (September 18, 2021), IV floor was set to 0 after a mishap that also set the IV ceiling to 0. - GBLDay, - - /// Purified, requires Lv. 8 and IV = 1 (Premier Ball only) - Shadow = 30, + /// + /// Checks if the is valid for the . + /// + /// Descriptor indicating how the Pokmon was encountered in GO. + /// Current the Pokmon is in. + /// True if valid, false if invalid. + public static bool IsBallValid(this PogoType encounterType, Ball ball) + { + var req = encounterType.GetValidBall(); + if (req == Ball.None) + return (uint)(ball - 2) <= 2; // Poke, Great, Ultra + return ball == req; } - public static class PogoTypeExtensions + /// + /// Gets a valid ball that the can have based on the type of capture in Pokmon GO. + /// + /// Descriptor indicating how the Pokmon was encountered in GO. + /// if no specific ball is required, otherwise returns the required ball. + public static Ball GetValidBall(this PogoType encounterType) => encounterType switch { - /// - /// Gets the minimum level (relative to GO's 1-) the must have. - /// - /// Descriptor indicating how the Pokmon was encountered in GO. - public static byte GetMinLevel(this PogoType encounterType) => encounterType switch - { - PogoType.EggS => 8, - PogoType.Raid => 20, - PogoType.RaidM => 20, - PogoType.Research => 15, - PogoType.ResearchM => 15, - PogoType.ResearchP => 15, - PogoType.GBL => 20, - PogoType.GBLM => 20, - PogoType.GBLZero => 20, - PogoType.GBLDay => 20, - PogoType.Shadow => 8, - _ => 1, - }; - - /// - /// Gets the minimum IVs (relative to GO's 0-15) the must have. - /// - /// Descriptor indicating how the Pokmon was encountered in GO. - /// Required minimum IV (0-15) - public static int GetMinIV(this PogoType encounterType) => encounterType switch - { - PogoType.Wild => 0, - PogoType.RaidM => 10, - PogoType.ResearchM => 10, - PogoType.ResearchP => 10, - PogoType.GBLM => 10, - PogoType.GBLZero => 0, - PogoType.GBLDay => 0, - _ => 1, - }; - - /// - /// Gets the minimum IVs (relative to GO's 0-15) the must have. - /// - /// Descriptor indicating how the Pokmon was encountered in GO. - /// Required minimum IV (0-15) - public static int GetMaxIV(this PogoType encounterType) => encounterType switch - { - PogoType.GBLZero => 0, - _ => 15, - }; - - /// - /// Checks if the is valid for the . - /// - /// Descriptor indicating how the Pokmon was encountered in GO. - /// Current the Pokmon is in. - /// True if valid, false if invalid. - public static bool IsBallValid(this PogoType encounterType, Ball ball) - { - var req = encounterType.GetValidBall(); - if (req == Ball.None) - return (uint)(ball - 2) <= 2; // Poke, Great, Ultra - return ball == req; - } - - /// - /// Gets a valid ball that the can have based on the type of capture in Pokmon GO. - /// - /// Descriptor indicating how the Pokmon was encountered in GO. - /// if no specific ball is required, otherwise returns the required ball. - public static Ball GetValidBall(this PogoType encounterType) => encounterType switch - { - PogoType.Egg => Ball.Poke, - PogoType.EggS => Ball.Poke, - PogoType.Raid => Ball.Premier, - PogoType.RaidM => Ball.Premier, - PogoType.ResearchP => Ball.Poke, - PogoType.Shadow => Ball.Premier, - _ => Ball.None, // Poke, Great, Ultra - }; - } + PogoType.Egg => Ball.Poke, + PogoType.EggS => Ball.Poke, + PogoType.Raid => Ball.Premier, + PogoType.RaidM => Ball.Premier, + PogoType.ResearchP => Ball.Poke, + PogoType.Shadow => Ball.Premier, + _ => Ball.None, // Poke, Great, Ultra + }; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/IGroundTypeTile.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/IGroundTypeTile.cs index 3a0157ebd..b90f7f5a6 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/IGroundTypeTile.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/IGroundTypeTile.cs @@ -1,26 +1,25 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains information pertaining the floor tile the was obtained on in . +/// +/// +/// +/// +/// +public interface IGroundTypeTile { /// - /// Contains information pertaining the floor tile the was obtained on in . + /// Tile Type the was obtained on. /// - /// - /// - /// - /// - public interface IGroundTypeTile - { - /// - /// Tile Type the was obtained on. - /// - GroundTilePermission GroundTile { get; } - } - - public static class GroundTypeTileExtensions - { - /// - /// Gets if the resulting will still have a value depending on the current . - /// - /// Generation 7 no longer stores this value. - public static bool HasGroundTile(this IGroundTypeTile _, int format) => format is (4 or 5 or 6); - } + GroundTileAllowed GroundTile { get; } +} + +public static class GroundTypeTileExtensions +{ + /// + /// Gets if the resulting will still have a value depending on the current . + /// + /// Generation 7 no longer stores this value. + public static bool HasGroundTile(this IGroundTypeTile _, int format) => format is (4 or 5 or 6); } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/DreamWorldEntry.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/DreamWorldEntry.cs index 7c2f21378..2e69753d8 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/DreamWorldEntry.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/DreamWorldEntry.cs @@ -1,64 +1,63 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Intermediary Representation of Dream World Data +/// +internal record DreamWorldEntry(ushort Species, byte Level, ushort Move1 = 0, ushort Move2 = 0, ushort Move3 = 0, byte Form = 0, sbyte Gender = -1) { - /// - /// Intermediary Representation of Dream World Data - /// - internal record DreamWorldEntry(ushort Species, byte Level, ushort Move1 = 0, ushort Move2 = 0, ushort Move3 = 0, byte Form = 0, sbyte Gender = -1) + private int EntryCount => Move1 == 0 ? 1 : Move2 == 0 ? 1 : Move3 == 0 ? 2 : 3; + + private void AddTo(GameVersion game, EncounterStatic5[] result, ref int ctr) { - private int EntryCount => Move1 == 0 ? 1 : Move2 == 0 ? 1 : Move3 == 0 ? 2 : 3; - - private void AddTo(GameVersion game, EncounterStatic5[] result, ref int ctr) + var p = (PersonalInfoBW)PersonalTable.B2W2[Species]; + var a = p.HasHiddenAbility ? AbilityPermission.OnlyHidden : AbilityPermission.OnlyFirst; + if (Move1 == 0) { - var p = (PersonalInfoBW)PersonalTable.B2W2[Species]; - var a = p.HasHiddenAbility ? AbilityPermission.OnlyHidden : AbilityPermission.OnlyFirst; - if (Move1 == 0) + result[ctr++] = new EncounterStatic5(game) { - result[ctr++] = new EncounterStatic5(game) - { - Species = Species, - Form = Form, - Gender = Gender, - Level = Level, - Ability = a, - Location = 075, - Shiny = Shiny.Never, - }; - return; - } - result[ctr++] = Create(game, a, Move1); - if (Move2 == 0) - return; - result[ctr++] = Create(game, a, Move2); - if (Move3 == 0) - return; - result[ctr++] = Create(game, a, Move3); + Species = Species, + Form = Form, + Gender = Gender, + Level = Level, + Ability = a, + Location = 075, + Shiny = Shiny.Never, + }; + return; } + result[ctr++] = Create(game, a, Move1); + if (Move2 == 0) + return; + result[ctr++] = Create(game, a, Move2); + if (Move3 == 0) + return; + result[ctr++] = Create(game, a, Move3); + } - private EncounterStatic5 Create(GameVersion game, AbilityPermission ability, int move) => new(game) - { - Species = Species, - Form = Form, - Gender = Gender, - Level = Level, - Ability = ability, - Location = 075, - Shiny = Shiny.Never, - Moves = new[] { move }, - }; + private EncounterStatic5 Create(GameVersion game, AbilityPermission ability, int move) => new(game) + { + Species = Species, + Form = Form, + Gender = Gender, + Level = Level, + Ability = ability, + Location = 075, + Shiny = Shiny.Never, + Moves = new[] { move }, + }; - public static EncounterStatic5[] GetArray(GameVersion game, IReadOnlyList t) - { - // Split encounters with multiple permitted special moves -- a pkm can only be obtained with 1 of the special moves! - var count = t.Sum(z => z.EntryCount); - var result = new EncounterStatic5[count]; + public static EncounterStatic5[] GetArray(GameVersion game, IReadOnlyList t) + { + // Split encounters with multiple permitted special moves -- a pk can only be obtained with 1 of the special moves! + var count = t.Sum(z => z.EntryCount); + var result = new EncounterStatic5[count]; - int ctr = 0; - foreach (var s in t) - s.AddTo(game, result, ref ctr); - return result; - } + int ctr = 0; + foreach (var s in t) + s.AddTo(game, result, ref ctr); + return result; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs index 30f46ecf3..61cec5a11 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs @@ -1,322 +1,321 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Static Encounter Data +/// +/// +/// Static Encounters are fixed position encounters with properties that are not subject to Wild Encounter conditions. +/// +public abstract record EncounterStatic(GameVersion Version) : IEncounterable, IMoveset, IEncounterMatch { - /// - /// Static Encounter Data - /// - /// - /// Static Encounters are fixed position encounters with properties that are not subject to Wild Encounter conditions. - /// - public abstract record EncounterStatic(GameVersion Version) : IEncounterable, IMoveset, IEncounterMatch + public int Species { get; init; } + public int Form { get; init; } + public virtual byte Level { get; init; } + public virtual byte LevelMin => Level; + public virtual byte LevelMax => Level; + public abstract int Generation { get; } + + public virtual int Location { get; init; } + public AbilityPermission Ability { get; init; } + public Shiny Shiny { get; init; } + public Nature Nature { get; init; } = Nature.Random; + public sbyte Gender { get; init; } = -1; + + public ushort HeldItem { get; init; } + public bool Gift { get; init; } + public bool Fateful { get; init; } + + public byte EggCycles { get; init; } + public byte FlawlessIVCount { get; init; } + public byte Ball { get; init; } = 4; // Only checked when is Gift + + public int EggLocation { get; init; } + + public Ball FixedBall => Gift ? (Ball)Ball : Core.Ball.None; + + public IReadOnlyList Moves { get; init; } = Array.Empty(); + public IReadOnlyList IVs { get; init; } = Array.Empty(); + + public virtual bool EggEncounter => EggLocation != 0; + + private const string _name = "Static Encounter"; + public string Name => _name; + public string LongName => $"{_name} ({Version})"; + public bool IsShiny => Shiny.IsShiny(); + + public bool IsRandomUnspecificForm => Form >= FormDynamic; + private const int FormDynamic = FormVivillon; + internal const int FormVivillon = 30; + //protected const int FormRandom = 31; + + protected virtual PKM GetBlank(ITrainerInfo tr) => EntityBlank.GetBlank(Generation, Version); + + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) { - public int Species { get; init; } - public int Form { get; init; } - public virtual byte Level { get; init; } - public virtual byte LevelMin => Level; - public virtual byte LevelMax => Level; - public abstract int Generation { get; } + var pk = GetBlank(tr); + tr.ApplyTo(pk); - public virtual int Location { get; init; } - public AbilityPermission Ability { get; init; } - public Shiny Shiny { get; init; } - public Nature Nature { get; init; } = Nature.Random; - public sbyte Gender { get; init; } = -1; + ApplyDetails(tr, criteria, pk); + return pk; + } - public ushort HeldItem { get; init; } - public bool Gift { get; init; } - public bool Fateful { get; init; } + protected virtual void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + pk.EncryptionConstant = Util.Rand32(); + pk.Species = Species; + pk.Form = Form; - public byte EggCycles { get; init; } - public byte FlawlessIVCount { get; init; } - public byte Ball { get; init; } = 4; // Only checked when is Gift + var version = this.GetCompatibleVersion((GameVersion)tr.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + int level = GetMinimalLevel(); - public int EggLocation { get; init; } + pk.Version = (int)version; + pk.Language = lang = GetEdgeCaseLanguage(pk, lang); + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation); - public Ball FixedBall => Gift ? (Ball)Ball : Core.Ball.None; + pk.CurrentLevel = level; + ApplyDetailsBall(pk); + pk.HeldItem = HeldItem; + pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; - public IReadOnlyList Moves { get; init; } = Array.Empty(); - public IReadOnlyList IVs { get; init; } = Array.Empty(); + var today = DateTime.Today; + SetMetData(pk, level, today); + if (EggEncounter) + SetEggMetData(pk, tr, today); - public virtual bool EggEncounter => EggLocation != 0; + SetPINGA(pk, criteria); + SetEncounterMoves(pk, version, level); - private const string _name = "Static Encounter"; - public string Name => _name; - public string LongName => $"{_name} ({Version})"; - public bool IsShiny => Shiny.IsShiny(); + if (Fateful) + pk.FatefulEncounter = true; - public bool IsRandomUnspecificForm => Form >= FormDynamic; - private const int FormDynamic = FormVivillon; - internal const int FormVivillon = 30; - //protected const int FormRandom = 31; + if (pk.Format < 6) + return; - protected virtual PKM GetBlank(ITrainerInfo tr) => EntityBlank.GetBlank(Generation, Version); + if (this is IRelearn relearn) + pk.SetRelearnMoves(relearn.Relearn); - public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted); + tr.ApplyHandlingTrainerInfo(pk); - public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) + if (pk is IScaledSize { HeightScalar: 0, WeightScalar: 0 } s) { - var pk = GetBlank(sav); - sav.ApplyTo(pk); - - ApplyDetails(sav, criteria, pk); - return pk; + s.HeightScalar = PokeSizeUtil.GetRandomScalar(); + s.WeightScalar = PokeSizeUtil.GetRandomScalar(); } + if (this is IGigantamax g && pk is PK8 pg) + pg.CanGigantamax = g.CanGigantamax; + if (this is IDynamaxLevel d && pk is PK8 pd) + pd.DynamaxLevel = d.DynamaxLevel; + } - protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + protected virtual void ApplyDetailsBall(PKM pk) => pk.Ball = Ball; + + protected virtual int GetMinimalLevel() => LevelMin; + + protected virtual void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(Gender, pi); + int nature = (int)criteria.GetNature(Nature); + int ability = criteria.GetAbilityFromNumber(Ability); + + var pidtype = GetPIDType(); + PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender, pidtype); + SetIVs(pk); + pk.StatNature = pk.Nature; + } + + private void SetEggMetData(PKM pk, ITrainerInfo tr, DateTime today) + { + pk.Met_Location = Math.Max(0, EncounterSuggestion.GetSuggestedEggMetLocation(pk)); + pk.Met_Level = EncounterSuggestion.GetSuggestedEncounterEggMetLevel(pk); + + if (Generation >= 4) { - pk.EncryptionConstant = Util.Rand32(); - pk.Species = Species; - pk.Form = Form; - - var version = this.GetCompatibleVersion((GameVersion)sav.Game); - int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)sav.Language, version); - int level = GetMinimalLevel(); - - pk.Version = (int)version; - pk.Language = lang = GetEdgeCaseLanguage(pk, lang); - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation); - - pk.CurrentLevel = level; - ApplyDetailsBall(pk); - pk.HeldItem = HeldItem; - pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; - - var today = DateTime.Today; - SetMetData(pk, level, today); - if (EggEncounter) - SetEggMetData(pk, sav, today); - - SetPINGA(pk, criteria); - SetEncounterMoves(pk, version, level); - - if (Fateful) - pk.FatefulEncounter = true; - - if (pk.Format < 6) - return; - - if (this is IRelearn relearn) - pk.SetRelearnMoves(relearn.Relearn); - - sav.ApplyHandlingTrainerInfo(pk); - - if (pk is IScaledSize { HeightScalar: 0, WeightScalar: 0 } s) - { - s.HeightScalar = PokeSizeUtil.GetRandomScalar(); - s.WeightScalar = PokeSizeUtil.GetRandomScalar(); - } - if (this is IGigantamax g && pk is PK8 pg) - pg.CanGigantamax = g.CanGigantamax; - if (this is IDynamaxLevel d && pk is PK8 pd) - pd.DynamaxLevel = d.DynamaxLevel; - } - - protected virtual void ApplyDetailsBall(PKM pk) => pk.Ball = Ball; - - protected virtual int GetMinimalLevel() => LevelMin; - - protected virtual void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(Gender, pi); - int nature = (int)criteria.GetNature(Nature); - int ability = criteria.GetAbilityFromNumber(Ability); - - var pidtype = GetPIDType(); - PIDGenerator.SetRandomWildPID(pk, pk.Format, nature, ability, gender, pidtype); - SetIVs(pk); - pk.StatNature = pk.Nature; - } - - private void SetEggMetData(PKM pk, ITrainerInfo tr, DateTime today) - { - pk.Met_Location = Math.Max(0, EncounterSuggestion.GetSuggestedEggMetLocation(pk)); - pk.Met_Level = EncounterSuggestion.GetSuggestedEncounterEggMetLevel(pk); - - if (Generation >= 4) - { - bool traded = (int)Version == tr.Game; - pk.Egg_Location = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(Generation, Version, traded); - pk.EggMetDate = today; - } - pk.Egg_Location = EggLocation; + bool traded = (int)Version == tr.Game; + pk.Egg_Location = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(Generation, Version, traded); pk.EggMetDate = today; } + pk.Egg_Location = EggLocation; + pk.EggMetDate = today; + } - protected virtual void SetMetData(PKM pk, int level, DateTime today) + protected virtual void SetMetData(PKM pk, int level, DateTime today) + { + if (pk.Format <= 2) + return; + + pk.Met_Location = Location; + pk.Met_Level = level; + if (pk.Format >= 4) + pk.MetDate = today; + } + + protected virtual void SetEncounterMoves(PKM pk, GameVersion version, int level) + { + var moves = Moves.Count > 0 ? (int[])Moves : MoveLevelUp.GetEncounterMoves(pk, level, version); + pk.SetMoves(moves); + pk.SetMaximumPPCurrent(moves); + } + + protected void SetIVs(PKM pk) + { + if (IVs.Count != 0) + pk.SetRandomIVs((int[])IVs, FlawlessIVCount); + else if (FlawlessIVCount > 0) + pk.SetRandomIVs(flawless: FlawlessIVCount); + } + + private int GetEdgeCaseLanguage(PKM pk, int lang) + { + switch (this) { - if (pk.Format <= 2) - return; + // Cannot trade between languages + case IFixedGBLanguage e: + return e.Language == EncounterGBLanguage.Japanese ? 1 : 2; - pk.Met_Location = Location; - pk.Met_Level = level; - if (pk.Format >= 4) - pk.MetDate = today; - } + // E-Reader was only available to Japanese games. + case EncounterStaticShadow {EReader: true}: + // Old Sea Map was only distributed to Japanese games. + case EncounterStatic3 when Species == (int)Core.Species.Mew: + pk.OT_Name = "ゲーフリ"; + return (int)LanguageID.Japanese; - protected virtual void SetEncounterMoves(PKM pk, GameVersion version, int level) - { - var moves = Moves.Count > 0 ? (int[])Moves : MoveLevelUp.GetEncounterMoves(pk, level, version); - pk.SetMoves(moves); - pk.SetMaximumPPCurrent(moves); - } + // Deoxys for Emerald was not available for Japanese games. + case EncounterStatic3 when Species == (int)Core.Species.Deoxys && Version == GameVersion.E && lang == 1: + pk.OT_Name = "GF"; + return (int)LanguageID.English; - protected void SetIVs(PKM pk) - { - if (IVs.Count != 0) - pk.SetRandomIVs((int[])IVs, FlawlessIVCount); - else if (FlawlessIVCount > 0) - pk.SetRandomIVs(flawless: FlawlessIVCount); - } - - private int GetEdgeCaseLanguage(PKM pk, int lang) - { - switch (this) - { - // Cannot trade between languages - case IFixedGBLanguage e: - return e.Language == EncounterGBLanguage.Japanese ? 1 : 2; - - // E-Reader was only available to Japanese games. - case EncounterStaticShadow {EReader: true}: - // Old Sea Map was only distributed to Japanese games. - case EncounterStatic3 when Species == (int)Core.Species.Mew: - pk.OT_Name = "ゲーフリ"; - return (int)LanguageID.Japanese; - - // Deoxys for Emerald was not available for Japanese games. - case EncounterStatic3 when Species == (int)Core.Species.Deoxys && Version == GameVersion.E && lang == 1: - pk.OT_Name = "GF"; - return (int)LanguageID.English; - - default: - return lang; - } - } - - private PIDType GetPIDType() - { - switch (Generation) - { - case 3 when this is EncounterStatic3 {Roaming: true, Version: not GameVersion.E}: // Roamer IV glitch was fixed in Emerald - return PIDType.Method_1_Roamer; - case 4 when Shiny == Shiny.Always: // Lake of Rage Gyarados - return PIDType.ChainShiny; - case 4 when Species == (int)Core.Species.Pichu: // Spiky Eared Pichu - case 4 when Location == Locations.PokeWalker4: // Pokéwalker - return PIDType.Pokewalker; - case 5 when Shiny == Shiny.Always: - return PIDType.G5MGShiny; - - default: return PIDType.None; - } - } - - public virtual bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (Nature != Nature.Random && pkm.Nature != (int) Nature) - return false; - - if (!IsMatchEggLocation(pkm)) - return false; - if (!IsMatchLocation(pkm)) - return false; - if (!IsMatchLevel(pkm, evo)) - return false; - if (!IsMatchGender(pkm)) - return false; - if (!IsMatchForm(pkm, evo)) - return false; - if (!IsMatchIVs(pkm)) - return false; - - if (this is IContestStats es && pkm is IContestStats s && s.IsContestBelow(es)) - return false; - - // Defer to EC/PID check - // if (e.Shiny != null && e.Shiny != pkm.IsShiny) - // continue; - - // Defer ball check to later - // if (e.Gift && pkm.Ball != 4) // PokéBall - // continue; - - return true; - } - - private bool IsMatchIVs(PKM pkm) - { - if (IVs.Count == 0) - return true; // nothing to check, IVs are random - if (Generation <= 2 && pkm.Format > 2) - return true; // IVs are regenerated on VC transfer upward - - return Legal.GetIsFixedIVSequenceValidSkipRand((int[])IVs, pkm); - } - - protected virtual bool IsMatchForm(PKM pkm, EvoCriteria evo) - { - if (IsRandomUnspecificForm) - return true; - return Form == evo.Form || FormInfo.IsFormChangeable(Species, Form, pkm.Form, pkm.Format); - } - - // override me if the encounter type has any eggs - protected virtual bool IsMatchEggLocation(PKM pkm) - { - var expect = pkm is PB8 ? Locations.Default8bNone : 0; - return pkm.Egg_Location == expect; - } - - private bool IsMatchGender(PKM pkm) - { - if (Gender == -1 || Gender == pkm.Gender) - return true; - - if (Species == (int) Core.Species.Azurill && Generation == 4 && Location == 233 && pkm.Gender == 0) - return EntityGender.GetFromPIDAndRatio(pkm.PID, 0xBF) == 1; - - return false; - } - - protected virtual bool IsMatchLocation(PKM pkm) - { - if (EggEncounter) - return true; - if (Location == 0) - return true; - if (!pkm.HasOriginalMetLocation) - return true; - return Location == pkm.Met_Location; - } - - protected virtual bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - return pkm.Met_Level == Level; - } - - public virtual EncounterMatchRating GetMatchRating(PKM pkm) - { - if (IsMatchPartial(pkm)) - return EncounterMatchRating.PartialMatch; - return IsMatchDeferred(pkm); - } - - /// - /// Checks if the provided might not be the best match, or even a bad match due to minor reasons. - /// - protected virtual EncounterMatchRating IsMatchDeferred(PKM pkm) => EncounterMatchRating.Match; - - /// - /// Checks if the provided is not an exact match due to minor reasons. - /// - protected virtual bool IsMatchPartial(PKM pkm) - { - if (pkm.Format >= 5 && pkm.AbilityNumber == 4 && this.IsPartialMatchHidden(pkm.Species, Species)) - return true; - return pkm.FatefulEncounter != Fateful; + default: + return lang; } } + + private PIDType GetPIDType() + { + switch (Generation) + { + case 3 when this is EncounterStatic3 {Roaming: true, Version: not GameVersion.E}: // Roamer IV glitch was fixed in Emerald + return PIDType.Method_1_Roamer; + case 4 when Shiny == Shiny.Always: // Lake of Rage Gyarados + return PIDType.ChainShiny; + case 4 when Species == (int)Core.Species.Pichu: // Spiky Eared Pichu + case 4 when Location == Locations.PokeWalker4: // Pokéwalker + return PIDType.Pokewalker; + case 5 when Shiny == Shiny.Always: + return PIDType.G5MGShiny; + + default: return PIDType.None; + } + } + + public virtual bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (Nature != Nature.Random && pk.Nature != (int) Nature) + return false; + + if (!IsMatchEggLocation(pk)) + return false; + if (!IsMatchLocation(pk)) + return false; + if (!IsMatchLevel(pk, evo)) + return false; + if (!IsMatchGender(pk)) + return false; + if (!IsMatchForm(pk, evo)) + return false; + if (!IsMatchIVs(pk)) + return false; + + if (this is IContestStats es && pk is IContestStats s && s.IsContestBelow(es)) + return false; + + // Defer to EC/PID check + // if (e.Shiny != null && e.Shiny != pk.IsShiny) + // continue; + + // Defer ball check to later + // if (e.Gift && pk.Ball != 4) // PokéBall + // continue; + + return true; + } + + private bool IsMatchIVs(PKM pk) + { + if (IVs.Count == 0) + return true; // nothing to check, IVs are random + if (Generation <= 2 && pk.Format > 2) + return true; // IVs are regenerated on VC transfer upward + + return Legal.GetIsFixedIVSequenceValidSkipRand((int[])IVs, pk); + } + + protected virtual bool IsMatchForm(PKM pk, EvoCriteria evo) + { + if (IsRandomUnspecificForm) + return true; + return Form == evo.Form || FormInfo.IsFormChangeable(Species, Form, pk.Form, pk.Format); + } + + // override me if the encounter type has any eggs + protected virtual bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + private bool IsMatchGender(PKM pk) + { + if (Gender == -1 || Gender == pk.Gender) + return true; + + if (Species == (int) Core.Species.Azurill && Generation == 4 && Location == 233 && pk.Gender == 0) + return EntityGender.GetFromPIDAndRatio(pk.PID, 0xBF) == 1; + + return false; + } + + protected virtual bool IsMatchLocation(PKM pk) + { + if (EggEncounter) + return true; + if (Location == 0) + return true; + if (!pk.HasOriginalMetLocation) + return true; + return Location == pk.Met_Location; + } + + protected virtual bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + return pk.Met_Level == Level; + } + + public virtual EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + return IsMatchDeferred(pk); + } + + /// + /// Checks if the provided might not be the best match, or even a bad match due to minor reasons. + /// + protected virtual EncounterMatchRating IsMatchDeferred(PKM pk) => EncounterMatchRating.Match; + + /// + /// Checks if the provided is not an exact match due to minor reasons. + /// + protected virtual bool IsMatchPartial(PKM pk) + { + if (pk.Format >= 5 && pk.AbilityNumber == 4 && this.IsPartialMatchHidden(pk.Species, Species)) + return true; + return pk.FatefulEncounter != Fateful; + } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1.cs index 8cb7b8a74..21a4a3887 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1.cs @@ -1,93 +1,92 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 1 Static Encounter +/// +/// +public record EncounterStatic1 : EncounterStatic { - /// - /// Generation 1 Static Encounter - /// - /// - public record EncounterStatic1 : EncounterStatic + public override int Generation => 1; + public sealed override byte Level { get; init; } + + private const int LightBallPikachuCatchRate = 0xA3; // 163 + + public EncounterStatic1(byte species, byte level, GameVersion game) : base(game) { - public override int Generation => 1; - public sealed override byte Level { get; init; } + Species = species; + Level = level; + } - private const int LightBallPikachuCatchRate = 0xA3; // 163 + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); - public EncounterStatic1(byte species, byte level, GameVersion game) : base(game) + var pk1 = (PK1) pk; + if (Species == (int) Core.Species.Pikachu && Version == GameVersion.YW && Level == 5 && Moves.Count == 0) { - Species = species; - Level = level; + pk1.Catch_Rate = LightBallPikachuCatchRate; // Light Ball + return; } - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + // Encounters can have different Catch Rates (RBG vs Y) + var table = Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; + pk1.Catch_Rate = table[Species].CatchRate; + } + + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + // Met Level is not stored in the PK1 format. + // Check if it is at or above the encounter level. + return Level <= evo.LevelMax; + } + + protected override bool IsMatchLocation(PKM pk) + { + // Met Location is not stored in the PK1 format. + return true; + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!base.IsMatchExact(pk, evo)) + return false; + + // Encounters with this version have to originate from the Japanese Blue game. + if (!pk.Japanese && Version == GameVersion.BU) + return false; + + return true; + } + + protected override bool IsMatchPartial(PKM pk) + { + if (pk is not PK1 pk1) + return false; + if (ParseSettings.AllowGen1Tradeback && PK1.IsCatchRateHeldItem(pk1.Catch_Rate)) + return false; + if (IsCatchRateValid(pk1)) + return false; + return true; + } + + private bool IsCatchRateValid(PK1 pk1) + { + var catch_rate = pk1.Catch_Rate; + + // Light Ball (Yellow) starter + if (Version == GameVersion.YW && Species == (int)Core.Species.Pikachu && Level == 5) { - base.ApplyDetails(sav, criteria, pk); - - var pk1 = (PK1) pk; - if (Species == (int) Core.Species.Pikachu && Version == GameVersion.YW && Level == 5 && Moves.Count == 0) - { - pk1.Catch_Rate = LightBallPikachuCatchRate; // Light Ball - return; - } - - // Encounters can have different Catch Rates (RBG vs Y) - var table = Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; - pk1.Catch_Rate = table[Species].CatchRate; + return catch_rate == LightBallPikachuCatchRate; + } + if (Version == GameVersion.Stadium) + { + // Amnesia Psyduck has different catch rates depending on language + if (Species == (int)Core.Species.Psyduck) + return catch_rate == (pk1.Japanese ? 167 : 168); + return catch_rate is 167 or 168; } - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - // Met Level is not stored in the PK1 format. - // Check if it is at or above the encounter level. - return Level <= evo.LevelMax; - } - - protected override bool IsMatchLocation(PKM pkm) - { - // Met Location is not stored in the PK1 format. - return true; - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!base.IsMatchExact(pkm, evo)) - return false; - - // Encounters with this version have to originate from the Japanese Blue game. - if (!pkm.Japanese && Version == GameVersion.BU) - return false; - - return true; - } - - protected override bool IsMatchPartial(PKM pkm) - { - if (pkm is not PK1 pk1) - return false; - if (ParseSettings.AllowGen1Tradeback && PK1.IsCatchRateHeldItem(pk1.Catch_Rate)) - return false; - if (IsCatchRateValid(pk1)) - return false; - return true; - } - - private bool IsCatchRateValid(PK1 pk1) - { - var catch_rate = pk1.Catch_Rate; - - // Light Ball (Yellow) starter - if (Version == GameVersion.YW && Species == (int)Core.Species.Pikachu && Level == 5) - { - return catch_rate == LightBallPikachuCatchRate; - } - if (Version == GameVersion.Stadium) - { - // Amnesia Psyduck has different catch rates depending on language - if (Species == (int)Core.Species.Psyduck) - return catch_rate == (pk1.Japanese ? 167 : 168); - return catch_rate is 167 or 168; - } - - // Encounters can have different Catch Rates (RBG vs Y) - return GBRestrictions.RateMatchesEncounter(Species, Version, catch_rate); - } + // Encounters can have different Catch Rates (RBG vs Y) + return GBRestrictions.RateMatchesEncounter(Species, Version, catch_rate); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1E.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1E.cs index 82c6f5d65..5140416df 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1E.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic1E.cs @@ -2,107 +2,106 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Event data for Generation 1 +/// +/// +public sealed record EncounterStatic1E : EncounterStatic1, IFixedGBLanguage { - /// - /// Event data for Generation 1 - /// - /// - public sealed record EncounterStatic1E : EncounterStatic1, IFixedGBLanguage + public EncounterGBLanguage Language { get; init; } = EncounterGBLanguage.Japanese; + + /// Trainer name for the event. + public string OT_Name { get; init; } = string.Empty; + + public IReadOnlyList OT_Names { get; init; } = Array.Empty(); + + /// Trainer ID for the event. + public int TID { get; init; } = -1; + + public EncounterStatic1E(byte species, byte level, GameVersion game) : base(species, level, game) { - public EncounterGBLanguage Language { get; init; } = EncounterGBLanguage.Japanese; - - /// Trainer name for the event. - public string OT_Name { get; init; } = string.Empty; - - public IReadOnlyList OT_Names { get; init; } = Array.Empty(); - - /// Trainer ID for the event. - public int TID { get; init; } = -1; - - public EncounterStatic1E(byte species, byte level, GameVersion game) : base(species, level, game) - { - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!base.IsMatchExact(pkm, evo)) - return false; - - if (Language != EncounterGBLanguage.Any && pkm.Japanese != (Language == EncounterGBLanguage.Japanese)) - return false; - - // EC/PID check doesn't exist for these, so check Shiny state here. - if (!IsShinyValid(pkm)) - return false; - - if (TID != -1 && pkm.TID != TID) - return false; - - if (OT_Name.Length != 0) - { - if (pkm.OT_Name != OT_Name) - return false; - } - else if (OT_Names.Count != 0) - { - if (!OT_Names.Contains(pkm.OT_Name)) - return false; - } - - return true; - } - - private bool IsShinyValid(PKM pkm) => Shiny switch - { - Shiny.Never => !pkm.IsShiny, - Shiny.Always => pkm.IsShiny, - _ => true, - }; - - protected override PKM GetBlank(ITrainerInfo tr) => Language switch - { - EncounterGBLanguage.Japanese => new PK1(true), - EncounterGBLanguage.International => new PK1(), - _ => new PK1(tr.Language == 1), - }; - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - - if (Version == GameVersion.Stadium) - { - var pk1 = (PK1)pk; - // Amnesia Psyduck has different catch rates depending on language - if (Species == (int)Core.Species.Psyduck) - pk1.Catch_Rate = pk1.Japanese ? 167 : 168; - else - pk1.Catch_Rate = 167 + Util.Rand.Next(2); // 167 or 168 - } - - if (TID != -1) - pk.TID = TID; - - if (OT_Name.Length != 0) - pk.OT_Name = OT_Name; - else if (OT_Names.Count != 0) - pk.OT_Name = OT_Names[Util.Rand.Next(OT_Names.Count)]; - } } - public interface IFixedGBLanguage + public override bool IsMatchExact(PKM pk, EvoCriteria evo) { - EncounterGBLanguage Language { get; } + if (!base.IsMatchExact(pk, evo)) + return false; + + if (Language != EncounterGBLanguage.Any && pk.Japanese != (Language == EncounterGBLanguage.Japanese)) + return false; + + // EC/PID check doesn't exist for these, so check Shiny state here. + if (!IsShinyValid(pk)) + return false; + + if (TID != -1 && pk.TID != TID) + return false; + + if (OT_Name.Length != 0) + { + if (pk.OT_Name != OT_Name) + return false; + } + else if (OT_Names.Count != 0) + { + if (!OT_Names.Contains(pk.OT_Name)) + return false; + } + + return true; } - /// - /// Generations 1 & 2 cannot communicate between Japanese & International versions. - /// - public enum EncounterGBLanguage + private bool IsShinyValid(PKM pk) => Shiny switch { - Japanese, - International, - Any, + Shiny.Never => !pk.IsShiny, + Shiny.Always => pk.IsShiny, + _ => true, + }; + + protected override PKM GetBlank(ITrainerInfo tr) => Language switch + { + EncounterGBLanguage.Japanese => new PK1(true), + EncounterGBLanguage.International => new PK1(), + _ => new PK1(tr.Language == 1), + }; + + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + + if (Version == GameVersion.Stadium) + { + var pk1 = (PK1)pk; + // Amnesia Psyduck has different catch rates depending on language + if (Species == (int)Core.Species.Psyduck) + pk1.Catch_Rate = pk1.Japanese ? 167 : 168; + else + pk1.Catch_Rate = 167 + Util.Rand.Next(2); // 167 or 168 + } + + if (TID != -1) + pk.TID = TID; + + if (OT_Name.Length != 0) + pk.OT_Name = OT_Name; + else if (OT_Names.Count != 0) + pk.OT_Name = OT_Names[Util.Rand.Next(OT_Names.Count)]; } } + +public interface IFixedGBLanguage +{ + EncounterGBLanguage Language { get; } +} + +/// +/// Generations 1 & 2 cannot communicate between Japanese & International versions. +/// +public enum EncounterGBLanguage +{ + Japanese, + International, + Any, +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2.cs index 0b44d9572..72c3f3261 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2.cs @@ -1,139 +1,138 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 2 Static Encounter +/// +/// +public record EncounterStatic2 : EncounterStatic { - /// - /// Generation 2 Static Encounter - /// - /// - public record EncounterStatic2 : EncounterStatic + public sealed override int Generation => 2; + public sealed override byte Level { get; init; } + + public EncounterStatic2(byte species, byte level, GameVersion game) : base(game) { - public sealed override int Generation => 2; - public sealed override byte Level { get; init; } + Species = species; + Level = level; + } - public EncounterStatic2(byte species, byte level, GameVersion game) : base(game) - { - Species = species; - Level = level; - } + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (Shiny == Shiny.Always && !pk.IsShiny) + return false; + return base.IsMatchExact(pk, evo); + } - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) + protected override bool IsMatchEggLocation(PKM pk) + { + if (pk.Format > 2) + return true; + + if (pk.IsEgg) { - if (Shiny == Shiny.Always && !pkm.IsShiny) + if (!EggEncounter) + return false; + if (pk.Met_Location != 0 && pk.Met_Level != 0) + return false; + if (pk.OT_Friendship > EggCycles) // Dizzy Punch eggs start with below-normal hatch counters. return false; - return base.IsMatchExact(pkm, evo); } - - protected override bool IsMatchEggLocation(PKM pkm) + else { - if (pkm.Format > 2) - return true; - - if (pkm.IsEgg) + switch (pk.Met_Level) { - if (!EggEncounter) + case 0 when pk.Met_Location != 0: return false; - if (pkm.Met_Location != 0 && pkm.Met_Level != 0) - return false; - if (pkm.OT_Friendship > EggCycles) // Dizzy Punch eggs start with below-normal hatch counters. - return false; - } - else - { - switch (pkm.Met_Level) - { - case 0 when pkm.Met_Location != 0: + case 1: // 0 = second floor of every Pokémon Center, valid + return true; + default: + if (pk.Met_Location == 0 && pk.Met_Level != 0) return false; - case 1: // 0 = second floor of every Pokémon Center, valid - return true; - default: - if (pkm.Met_Location == 0 && pkm.Met_Level != 0) - return false; - break; - } + break; } - - return true; } - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - if (pkm is ICaughtData2 {CaughtData: not 0}) - return pkm.Met_Level == (EggEncounter ? 1 : Level); - - return Level <= evo.LevelMax; - } - - protected override bool IsMatchLocation(PKM pkm) - { - if (EggEncounter) - return true; - if (Location == 0) - return true; - if (pkm is ICaughtData2 {CaughtData: not 0}) - return Location == pkm.Met_Location; - return true; - } - - protected override bool IsMatchPartial(PKM pkm) => false; - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pk2 = (PK2)pk; - if (Shiny == Shiny.Always) - pk2.SetShiny(); - } - - protected override void SetMetData(PKM pk, int level, DateTime today) - { - if (Version != GameVersion.C && pk.OT_Gender != 1) - return; - var pk2 = (PK2)pk; - pk2.Met_Location = Location; - pk2.Met_Level = level; - pk2.Met_TimeOfDay = EncounterTime.Any.RandomValidTime(); - } + return true; } - public sealed record EncounterStatic2Odd : EncounterStatic2 + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) { - public EncounterStatic2Odd(byte species) : base(species, 5, GameVersion.C) - { - EggLocation = 256; - EggCycles = 20; - } + if (pk is ICaughtData2 {CaughtData: not 0}) + return pk.Met_Level == (EggEncounter ? 1 : Level); - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - // Let it get picked up as regular EncounterEgg under other conditions. - if (pkm.Format > 2) - return false; - if (!pkm.HasMove((int)Move.DizzyPunch)) - return false; - if (pkm.IsEgg && pkm.EXP != 125) - return false; - return base.IsMatchExact(pkm, evo); - } + return Level <= evo.LevelMax; } - public sealed record EncounterStatic2Roam : EncounterStatic2 + protected override bool IsMatchLocation(PKM pk) { - // Routes 29-46, except 40 & 41; total 16. - // 02, 04, 05, 08, 11, 15, 18, 20, - // 21, 25, 26, 34, 37, 39, 43, 45, - private const ulong RoamLocations = 0b10_1000_1010_0100_0000_0110_0011_0100_1000_1001_0011_0100; - public override int Location => 2; + if (EggEncounter) + return true; + if (Location == 0) + return true; + if (pk is ICaughtData2 {CaughtData: not 0}) + return Location == pk.Met_Location; + return true; + } - public EncounterStatic2Roam(byte species, byte level, GameVersion ver) : base(species, level, ver) { } + protected override bool IsMatchPartial(PKM pk) => false; - protected override bool IsMatchLocation(PKM pkm) - { - if (!pkm.HasOriginalMetLocation) - return true; - // Gen2 met location is always u8 - var loc = pkm.Met_Location; - return loc <= 45 && ((RoamLocations & (1UL << loc)) != 0); - } + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + var pk2 = (PK2)pk; + if (Shiny == Shiny.Always) + pk2.SetShiny(); + } + + protected override void SetMetData(PKM pk, int level, DateTime today) + { + if (Version != GameVersion.C && pk.OT_Gender != 1) + return; + var pk2 = (PK2)pk; + pk2.Met_Location = Location; + pk2.Met_Level = level; + pk2.Met_TimeOfDay = EncounterTime.Any.RandomValidTime(); + } +} + +public sealed record EncounterStatic2Odd : EncounterStatic2 +{ + public EncounterStatic2Odd(byte species) : base(species, 5, GameVersion.C) + { + EggLocation = 256; + EggCycles = 20; + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + // Let it get picked up as regular EncounterEgg under other conditions. + if (pk.Format > 2) + return false; + if (!pk.HasMove((int)Move.DizzyPunch)) + return false; + if (pk.IsEgg && pk.EXP != 125) + return false; + return base.IsMatchExact(pk, evo); + } +} + +public sealed record EncounterStatic2Roam : EncounterStatic2 +{ + // Routes 29-46, except 40 & 41; total 16. + // 02, 04, 05, 08, 11, 15, 18, 20, + // 21, 25, 26, 34, 37, 39, 43, 45, + private const ulong RoamLocations = 0b10_1000_1010_0100_0000_0110_0011_0100_1000_1001_0011_0100; + public override int Location => 2; + + public EncounterStatic2Roam(byte species, byte level, GameVersion ver) : base(species, level, ver) { } + + protected override bool IsMatchLocation(PKM pk) + { + if (!pk.HasOriginalMetLocation) + return true; + // Gen2 met location is always u8 + var loc = pk.Met_Location; + return loc <= 45 && ((RoamLocations & (1UL << loc)) != 0); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2E.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2E.cs index dd2fd2a69..b50a4e240 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2E.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic2E.cs @@ -2,99 +2,98 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Event data for Generation 2 +/// +/// +public sealed record EncounterStatic2E : EncounterStatic2, IFixedGBLanguage { - /// - /// Event data for Generation 2 - /// - /// - public sealed record EncounterStatic2E : EncounterStatic2, IFixedGBLanguage + public EncounterGBLanguage Language { get; init; } = EncounterGBLanguage.Japanese; + + /// Trainer name for the event. + public string OT_Name { get; init; } = string.Empty; + + public IReadOnlyList OT_Names { get; init; } = Array.Empty(); + + /// Trainer ID for the event. + public int TID { get; init; } = -1; + + public bool IsGift => TID != -1; + + public int CurrentLevel { get; init; } = -1; + + public EncounterStatic2E(byte species, byte level, GameVersion ver) : base(species, level, ver) { - public EncounterGBLanguage Language { get; init; } = EncounterGBLanguage.Japanese; + } - /// Trainer name for the event. - public string OT_Name { get; init; } = string.Empty; + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!base.IsMatchExact(pk, evo)) + return false; - public IReadOnlyList OT_Names { get; init; } = Array.Empty(); + if (Language != EncounterGBLanguage.Any && pk.Japanese != (Language == EncounterGBLanguage.Japanese)) + return false; - /// Trainer ID for the event. - public int TID { get; init; } = -1; + if (CurrentLevel != -1 && CurrentLevel > pk.CurrentLevel) + return false; - public bool IsGift => TID != -1; - - public int CurrentLevel { get; init; } = -1; - - public EncounterStatic2E(byte species, byte level, GameVersion ver) : base(species, level, ver) - { - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!base.IsMatchExact(pkm, evo)) - return false; - - if (Language != EncounterGBLanguage.Any && pkm.Japanese != (Language == EncounterGBLanguage.Japanese)) - return false; - - if (CurrentLevel != -1 && CurrentLevel > pkm.CurrentLevel) - return false; - - // EC/PID check doesn't exist for these, so check Shiny state here. - if (!IsShinyValid(pkm)) - return false; - - if (EggEncounter && !pkm.IsEgg) - return true; - - // Check OT Details - if (TID != -1 && pkm.TID != TID) - return false; - - if (OT_Name.Length != 0) - { - if (pkm.OT_Name != OT_Name) - return false; - } - else if (OT_Names.Count != 0) - { - if (!OT_Names.Contains(pkm.OT_Name)) - return false; - } + // EC/PID check doesn't exist for these, so check Shiny state here. + if (!IsShinyValid(pk)) + return false; + if (EggEncounter && !pk.IsEgg) return true; + + // Check OT Details + if (TID != -1 && pk.TID != TID) + return false; + + if (OT_Name.Length != 0) + { + if (pk.OT_Name != OT_Name) + return false; + } + else if (OT_Names.Count != 0) + { + if (!OT_Names.Contains(pk.OT_Name)) + return false; } - private bool IsShinyValid(PKM pkm) => Shiny switch - { - Shiny.Never => !pkm.IsShiny, - Shiny.Always => pkm.IsShiny, - _ => true, - }; + return true; + } - protected override int GetMinimalLevel() => CurrentLevel == -1 ? base.GetMinimalLevel() : CurrentLevel; + private bool IsShinyValid(PKM pk) => Shiny switch + { + Shiny.Never => !pk.IsShiny, + Shiny.Always => pk.IsShiny, + _ => true, + }; - protected override PKM GetBlank(ITrainerInfo tr) => Language switch - { - EncounterGBLanguage.Japanese => new PK2(true), - EncounterGBLanguage.International => new PK2(), - _ => new PK2(tr.Language == 1), - }; + protected override int GetMinimalLevel() => CurrentLevel == -1 ? base.GetMinimalLevel() : CurrentLevel; - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - if (CurrentLevel != -1) // Restore met level - pk.Met_Level = LevelMin; + protected override PKM GetBlank(ITrainerInfo tr) => Language switch + { + EncounterGBLanguage.Japanese => new PK2(true), + EncounterGBLanguage.International => new PK2(), + _ => new PK2(tr.Language == 1), + }; - if (TID != -1) - pk.TID = TID; - if (IsGift) - pk.OT_Gender = 0; + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + if (CurrentLevel != -1) // Restore met level + pk.Met_Level = LevelMin; - if (OT_Name.Length != 0) - pk.OT_Name = OT_Name; - else if (OT_Names.Count != 0) - pk.OT_Name = OT_Names[Util.Rand.Next(OT_Names.Count)]; - } + if (TID != -1) + pk.TID = TID; + if (IsGift) + pk.OT_Gender = 0; + + if (OT_Name.Length != 0) + pk.OT_Name = OT_Name; + else if (OT_Names.Count != 0) + pk.OT_Name = OT_Names[Util.Rand.Next(OT_Names.Count)]; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs index 0b4a01948..8090721eb 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic3.cs @@ -1,85 +1,84 @@ using System; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 Static Encounter +/// +/// +public sealed record EncounterStatic3 : EncounterStatic { - /// - /// Generation 3 Static Encounter - /// - /// - public sealed record EncounterStatic3 : EncounterStatic + public override int Generation => 3; + public bool Roaming { get; init; } + + public EncounterStatic3(ushort species, byte level, GameVersion game) : base(game) { - public override int Generation => 3; - public bool Roaming { get; init; } - - public EncounterStatic3(ushort species, byte level, GameVersion game) : base(game) - { - Species = species; - Level = level; - } - - protected override bool IsMatchEggLocation(PKM pkm) - { - if (pkm.Format == 3) - return !pkm.IsEgg || EggLocation == 0 || EggLocation == pkm.Met_Location; - return base.IsMatchEggLocation(pkm); - } - - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - if (pkm.Format != 3) // Met Level lost on PK3=>PK4 - return Level <= evo.LevelMax; - - if (EggEncounter) - return pkm.Met_Level == 0 && pkm.CurrentLevel >= 5; // met level 0, origin level 5 - - return pkm.Met_Level == Level; - } - - protected override bool IsMatchLocation(PKM pkm) - { - if (EggEncounter) - return true; - if (pkm.Format != 3) - return true; // transfer location verified later - - var met = pkm.Met_Location; - if (!Roaming) - return Location == met; - - var table = Version <= GameVersion.E ? Roaming_MetLocation_RSE : Roaming_MetLocation_FRLG; - return table.Contains(met); - } - - protected override bool IsMatchPartial(PKM pkm) - { - if (Gift && pkm.Ball != Ball) - return true; - return base.IsMatchPartial(pkm); - } - - protected override void SetMetData(PKM pk, int level, DateTime today) - { - pk.Met_Level = level; - pk.Met_Location = !Roaming ? Location : (Version <= GameVersion.E ? Roaming_MetLocation_RSE : Roaming_MetLocation_FRLG)[0]; - } - - private static readonly int[] Roaming_MetLocation_FRLG = - { - // Route 1-25 encounter is possible either in grass or on water - 101,102,103,104,105,106,107,108,109,110, - 111,112,113,114,115,116,117,118,119,120, - 121,122,123,124,125, - }; - - private static readonly int[] Roaming_MetLocation_RSE = - { - // Roaming encounter is possible in tall grass and on water - // Route 101-138 - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, - 46, 47, 48, 49, - }; + Species = species; + Level = level; } + + protected override bool IsMatchEggLocation(PKM pk) + { + if (pk.Format == 3) + return !pk.IsEgg || EggLocation == 0 || EggLocation == pk.Met_Location; + return base.IsMatchEggLocation(pk); + } + + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 3) // Met Level lost on PK3=>PK4 + return Level <= evo.LevelMax; + + if (EggEncounter) + return pk.Met_Level == 0 && pk.CurrentLevel >= 5; // met level 0, origin level 5 + + return pk.Met_Level == Level; + } + + protected override bool IsMatchLocation(PKM pk) + { + if (EggEncounter) + return true; + if (pk.Format != 3) + return true; // transfer location verified later + + var met = pk.Met_Location; + if (!Roaming) + return Location == met; + + var table = Version <= GameVersion.E ? Roaming_MetLocation_RSE : Roaming_MetLocation_FRLG; + return table.Contains(met); + } + + protected override bool IsMatchPartial(PKM pk) + { + if (Gift && pk.Ball != Ball) + return true; + return base.IsMatchPartial(pk); + } + + protected override void SetMetData(PKM pk, int level, DateTime today) + { + pk.Met_Level = level; + pk.Met_Location = !Roaming ? Location : (Version <= GameVersion.E ? Roaming_MetLocation_RSE : Roaming_MetLocation_FRLG)[0]; + } + + private static readonly int[] Roaming_MetLocation_FRLG = + { + // Route 1-25 encounter is possible either in grass or on water + 101,102,103,104,105,106,107,108,109,110, + 111,112,113,114,115,116,117,118,119,120, + 121,122,123,124,125, + }; + + private static readonly int[] Roaming_MetLocation_RSE = + { + // Roaming encounter is possible in tall grass and on water + // Route 101-138 + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, + }; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs index e1895c8d4..1598276de 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4.cs @@ -1,163 +1,162 @@ using System; using System.Linq; -using static PKHeX.Core.GroundTilePermission; +using static PKHeX.Core.GroundTileAllowed; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 Static Encounter +/// +/// +public sealed record EncounterStatic4(GameVersion Version) : EncounterStatic(Version), IGroundTypeTile { - /// - /// Generation 4 Static Encounter - /// - /// - public sealed record EncounterStatic4(GameVersion Version) : EncounterStatic(Version), IGroundTypeTile + public override int Generation => 4; + + /// Indicates if the encounter is a Roamer (variable met location) + public bool Roaming { get; init; } + + /// values permitted for the encounter. + public GroundTileAllowed GroundTile { get; init; } = None; + + protected override bool IsMatchLocation(PKM pk) { - public override int Generation => 4; + if (!Roaming) + return base.IsMatchLocation(pk); - /// Indicates if the encounter is a Roamer (variable met location) - public bool Roaming { get; init; } - - /// values permitted for the encounter. - public GroundTilePermission GroundTile { get; init; } = None; - - protected override bool IsMatchLocation(PKM pkm) - { - if (!Roaming) - return base.IsMatchLocation(pkm); - - // Met location is lost on transfer - if (pkm is not G4PKM pk4) - return true; - - var locs = GetRoamLocations(Species, pk4.GroundTile); - return locs.Contains(pk4.Met_Location); - } - - protected override bool IsMatchEggLocation(PKM pkm) - { - if (!EggEncounter) - return base.IsMatchEggLocation(pkm); - - var eggloc = pkm.Egg_Location; - // Transferring 4->5 clears Pt/HG/SS location value and keeps Faraway Place - if (pkm is not G4PKM pk4) - { - if (eggloc == Locations.LinkTrade4) - return true; - var cmp = Locations.IsPtHGSSLocationEgg(EggLocation) ? Locations.Faraway4 : EggLocation; - return eggloc == cmp; - } - - if (!pk4.IsEgg) // hatched - return eggloc == EggLocation || eggloc == Locations.LinkTrade4; - - // Unhatched: - if (eggloc != EggLocation) - return false; - if (pk4.Met_Location is not (0 or Locations.LinkTrade4)) - return false; + // Met location is lost on transfer + if (pk is not G4PKM pk4) return true; - } - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - SanityCheckVersion(pk); - } - - private void SanityCheckVersion(PKM pk) - { - // Unavailable encounters in DP, morph them to Pt so they're legal. - switch (Species) - { - case (int)Core.Species.Darkrai when Location == 079: // DP Darkrai - case (int)Core.Species.Shaymin when Location == 063: // DP Shaymin - pk.Version = (int)GameVersion.Pt; - return; - } - } - - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - if (pkm.Format != 4) // Met Level lost on PK4=>PK5 - return Level <= evo.LevelMax; - - return pkm.Met_Level == (EggEncounter ? 0 : Level); - } - - protected override bool IsMatchPartial(PKM pkm) - { - if (Gift && pkm.Ball != Ball) - return true; - return base.IsMatchPartial(pkm); - } - - protected override void SetMetData(PKM pk, int level, DateTime today) - { - var pk4 = (PK4)pk; - var type = pk4.GroundTile = GroundTile.GetIndex(); - pk.Met_Location = Roaming ? GetRoamLocations(Species, type)[0] : Location; - pk.Met_Level = level; - pk.MetDate = today; - } - - private static int[] GetRoamLocations(int species, GroundTileType type) => species switch - { - 481 or 488 or 144 or 145 or 146 => type == GroundTileType.Grass ? Roaming_MetLocation_DPPt_Grass : Roaming_MetLocation_DPPt_Surf, - 243 or 244 => type == GroundTileType.Grass ? Roaming_MetLocation_HGSS_Johto_Grass : Roaming_MetLocation_HGSS_Johto_Surf, - 380 or 381 => type == GroundTileType.Grass ? Roaming_MetLocation_HGSS_Kanto_Grass : Roaming_MetLocation_HGSS_Kanto_Surf, - _ => throw new IndexOutOfRangeException(nameof(species)), - }; - - private static readonly int[] Roaming_MetLocation_DPPt_Grass = - { - // Routes 201-218, 221-222 can be encountered in grass - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - 26, 27, 28, 29, 30, 31, 32, 33, 36, 37, - 47, // Valley Windworks - 49, // Fuego Ironworks - }; - - private static readonly int[] Roaming_MetLocation_DPPt_Surf = - { - // Routes 203-205, 208-210, 212-214, 218-222 can be encountered in water - 18, 19, 20, 23, 24, 25, 27, 28, 29, 33, - 34, 35, 36, 37, - 47, // Valley Windworks - 49, // Fuego Ironworks - }; - - // Grass 29-39, 42-46, 47, 48 - // Surf 30-32 34-35, 40-45, 47 - // Route 45 inaccessible surf - private static readonly int[] Roaming_MetLocation_HGSS_Johto_Grass = - { - // Routes 29-48 can be encountered in grass - // Won't go to routes 40,41,47,48 - 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, - 187, 190, 191, 192, 193, 194, - }; - - private static readonly int[] Roaming_MetLocation_HGSS_Johto_Surf = - { - // Routes 30-32,34-35,40-45 and 47 can be encountered in water - // Won't go to routes 40,41,47,48 - 178, 179, 180, 182, 183, 190, 191, 192, 193, - }; - - private static readonly int[] Roaming_MetLocation_HGSS_Kanto_Grass = - { - // Route 01-18,21,22,24,26 and 28 can be encountered in grass - // Won't go to route 23 25 27 - 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, - 159, 160, 161, 162, 163, 164, 165, 166, - 169, 170, 172, 174, 176, - }; - - private static readonly int[] Roaming_MetLocation_HGSS_Kanto_Surf = - { - // Route 4,6,9,10,12,13,19-22,24,26 and 28 can be encountered in water - // Won't go to route 23 25 27 - 152, 154, 157, 158, 160, 161, 167, 168, 169, 170, - 172, 174, 176, - }; + var locs = GetRoamLocations(Species, pk4.GroundTile); + return locs.Contains(pk4.Met_Location); } -} \ No newline at end of file + + protected override bool IsMatchEggLocation(PKM pk) + { + if (!EggEncounter) + return base.IsMatchEggLocation(pk); + + var eggloc = pk.Egg_Location; + // Transferring 4->5 clears Pt/HG/SS location value and keeps Faraway Place + if (pk is not G4PKM pk4) + { + if (eggloc == Locations.LinkTrade4) + return true; + var cmp = Locations.IsPtHGSSLocationEgg(EggLocation) ? Locations.Faraway4 : EggLocation; + return eggloc == cmp; + } + + if (!pk4.IsEgg) // hatched + return eggloc == EggLocation || eggloc == Locations.LinkTrade4; + + // Unhatched: + if (eggloc != EggLocation) + return false; + if (pk4.Met_Location is not (0 or Locations.LinkTrade4)) + return false; + return true; + } + + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + SanityCheckVersion(pk); + } + + private void SanityCheckVersion(PKM pk) + { + // Unavailable encounters in DP, morph them to Pt so they're legal. + switch (Species) + { + case (int)Core.Species.Darkrai when Location == 079: // DP Darkrai + case (int)Core.Species.Shaymin when Location == 063: // DP Shaymin + pk.Version = (int)GameVersion.Pt; + return; + } + } + + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 4) // Met Level lost on PK4=>PK5 + return Level <= evo.LevelMax; + + return pk.Met_Level == (EggEncounter ? 0 : Level); + } + + protected override bool IsMatchPartial(PKM pk) + { + if (Gift && pk.Ball != Ball) + return true; + return base.IsMatchPartial(pk); + } + + protected override void SetMetData(PKM pk, int level, DateTime today) + { + var pk4 = (PK4)pk; + var type = pk4.GroundTile = GroundTile.GetIndex(); + pk.Met_Location = Roaming ? GetRoamLocations(Species, type)[0] : Location; + pk.Met_Level = level; + pk.MetDate = today; + } + + private static int[] GetRoamLocations(int species, GroundTileType type) => species switch + { + 481 or 488 or 144 or 145 or 146 => type == GroundTileType.Grass ? Roaming_MetLocation_DPPt_Grass : Roaming_MetLocation_DPPt_Surf, + 243 or 244 => type == GroundTileType.Grass ? Roaming_MetLocation_HGSS_Johto_Grass : Roaming_MetLocation_HGSS_Johto_Surf, + 380 or 381 => type == GroundTileType.Grass ? Roaming_MetLocation_HGSS_Kanto_Grass : Roaming_MetLocation_HGSS_Kanto_Surf, + _ => throw new ArgumentOutOfRangeException(nameof(species)), + }; + + private static readonly int[] Roaming_MetLocation_DPPt_Grass = + { + // Routes 201-218, 221-222 can be encountered in grass + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 36, 37, + 47, // Valley Windworks + 49, // Fuego Ironworks + }; + + private static readonly int[] Roaming_MetLocation_DPPt_Surf = + { + // Routes 203-205, 208-210, 212-214, 218-222 can be encountered in water + 18, 19, 20, 23, 24, 25, 27, 28, 29, 33, + 34, 35, 36, 37, + 47, // Valley Windworks + 49, // Fuego Ironworks + }; + + // Grass 29-39, 42-46, 47, 48 + // Surf 30-32 34-35, 40-45, 47 + // Route 45 inaccessible surf + private static readonly int[] Roaming_MetLocation_HGSS_Johto_Grass = + { + // Routes 29-48 can be encountered in grass + // Won't go to routes 40,41,47,48 + 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, + 187, 190, 191, 192, 193, 194, + }; + + private static readonly int[] Roaming_MetLocation_HGSS_Johto_Surf = + { + // Routes 30-32,34-35,40-45 and 47 can be encountered in water + // Won't go to routes 40,41,47,48 + 178, 179, 180, 182, 183, 190, 191, 192, 193, + }; + + private static readonly int[] Roaming_MetLocation_HGSS_Kanto_Grass = + { + // Route 01-18,21,22,24,26 and 28 can be encountered in grass + // Won't go to route 23 25 27 + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, + 159, 160, 161, 162, 163, 164, 165, 166, + 169, 170, 172, 174, 176, + }; + + private static readonly int[] Roaming_MetLocation_HGSS_Kanto_Surf = + { + // Route 4,6,9,10,12,13,19-22,24,26 and 28 can be encountered in water + // Won't go to route 23 25 27 + 152, 154, 157, 158, 160, 161, 167, 168, 169, 170, + 172, 174, 176, + }; +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4Pokewalker.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4Pokewalker.cs index f7ee88d91..91e3696ef 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4Pokewalker.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic4Pokewalker.cs @@ -1,55 +1,54 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 Pokéwalker Encounter +/// +/// +public sealed record EncounterStatic4Pokewalker : EncounterStatic { - /// - /// Generation 4 Pokéwalker Encounter - /// - /// - public sealed record EncounterStatic4Pokewalker : EncounterStatic + public override int Generation => 4; + + public EncounterStatic4Pokewalker(ushort species, sbyte gender, byte level) : base(GameVersion.HGSS) { - public override int Generation => 4; + Species = species; + Gender = gender; + Level = level; + Gift = true; + Location = Locations.PokeWalker4; + } - public EncounterStatic4Pokewalker(ushort species, sbyte gender, byte level) : base(GameVersion.HGSS) - { - Species = species; - Gender = gender; - Level = level; - Gift = true; - Location = Locations.PokeWalker4; - } + protected override bool IsMatchLocation(PKM pk) + { + if (pk.Format == 4) + return Location == pk.Met_Location; + return true; // transfer location verified later + } - protected override bool IsMatchLocation(PKM pkm) - { - if (pkm.Format == 4) - return Location == pkm.Met_Location; - return true; // transfer location verified later - } + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 4) // Met Level lost on PK4=>PK5 + return Level <= evo.LevelMax; - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - if (pkm.Format != 4) // Met Level lost on PK4=>PK5 - return Level <= evo.LevelMax; + return pk.Met_Level == Level; + } - return pkm.Met_Level == Level; - } + protected override bool IsMatchPartial(PKM pk) + { + if (Gift && pk.Ball != Ball) + return true; + return base.IsMatchPartial(pk); + } - protected override bool IsMatchPartial(PKM pkm) - { - if (Gift && pkm.Ball != Ball) - return true; - return base.IsMatchPartial(pkm); - } + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(Gender, pi); + int nature = (int)criteria.GetNature(Nature.Random); - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(Gender, pi); - int nature = (int)criteria.GetNature(Nature.Random); + // Cannot force an ability; nature-gender-trainerID only yield fixed PIDs. + // int ability = criteria.GetAbilityFromNumber(Ability, pi); - // Cannot force an ability; nature-gender-trainerID only yield fixed PIDs. - // int ability = criteria.GetAbilityFromNumber(Ability, pi); - - PIDGenerator.SetRandomPIDPokewalker(pk, nature, gender); - criteria.SetRandomIVs(pk); - } + PIDGenerator.SetRandomPIDPokewalker(pk, nature, gender); + criteria.SetRandomIVs(pk); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs index 5358789d8..071928e00 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5.cs @@ -1,56 +1,55 @@ using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 Static Encounter +/// +/// +public record EncounterStatic5(GameVersion Version) : EncounterStatic(Version) { - /// - /// Generation 5 Static Encounter - /// - /// - public record EncounterStatic5(GameVersion Version) : EncounterStatic(Version) + public sealed override int Generation => 5; + public bool Roaming { get; init; } + public bool IsWildCorrelationPID => !Roaming && Shiny == Shiny.Random && Species != (int)Core.Species.Crustle; + + protected sealed override bool IsMatchPartial(PKM pk) { - public sealed override int Generation => 5; - public bool Roaming { get; init; } - public bool IsWildCorrelationPID => !Roaming && Shiny == Shiny.Random && Species != (int)Core.Species.Crustle; - - protected sealed override bool IsMatchPartial(PKM pkm) - { - // BW/2 Jellicent collision with wild surf slot, resolved by duplicating the encounter with any abil - if (Ability == AbilityPermission.OnlyHidden && pkm.AbilityNumber != 4 && pkm.Format <= 7) - return true; - return base.IsMatchPartial(pkm); - } - - protected sealed override bool IsMatchLocation(PKM pk) - { - if (!Roaming) - return base.IsMatchLocation(pk); - return Roaming_MetLocation_BW.Contains(pk.Met_Location); - } - - protected override bool IsMatchEggLocation(PKM pkm) - { - if (!EggEncounter) - return base.IsMatchEggLocation(pkm); - - var eggloc = pkm.Egg_Location; - if (!pkm.IsEgg) // hatched - return eggloc == EggLocation || eggloc == Locations.LinkTrade5; - - // Unhatched: - if (eggloc != EggLocation) - return false; - if (pkm.Met_Location is not (0 or Locations.LinkTrade5)) - return false; + // BW/2 Jellicent collision with wild surf slot, resolved by duplicating the encounter with any abil + if (Ability == AbilityPermission.OnlyHidden && pk.AbilityNumber != 4 && pk.Format <= 7) return true; - } - - private static readonly int[] Roaming_MetLocation_BW = - { - 25,26,27,28, // Route 12, 13, 14, 15 Night latter half - 15,16,31, // Route 2, 3, 18 Morning - 17,18,29, // Route 4, 5, 16 Daytime - 19,20,21, // Route 6, 7, 8 Evening - 22,23,24, // Route 9, 10, 11 Night former half - }; + return base.IsMatchPartial(pk); } + + protected sealed override bool IsMatchLocation(PKM pk) + { + if (!Roaming) + return base.IsMatchLocation(pk); + return Roaming_MetLocation_BW.Contains(pk.Met_Location); + } + + protected override bool IsMatchEggLocation(PKM pk) + { + if (!EggEncounter) + return base.IsMatchEggLocation(pk); + + var eggloc = pk.Egg_Location; + if (!pk.IsEgg) // hatched + return eggloc == EggLocation || eggloc == Locations.LinkTrade5; + + // Unhatched: + if (eggloc != EggLocation) + return false; + if (pk.Met_Location is not (0 or Locations.LinkTrade5)) + return false; + return true; + } + + private static readonly int[] Roaming_MetLocation_BW = + { + 25,26,27,28, // Route 12, 13, 14, 15 Night latter half + 15,16,31, // Route 2, 3, 18 Morning + 17,18,29, // Route 4, 5, 16 Daytime + 19,20,21, // Route 6, 7, 8 Evening + 22,23,24, // Route 9, 10, 11 Night former half + }; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5DR.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5DR.cs index 99415476f..3e7b4fd70 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5DR.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5DR.cs @@ -1,30 +1,29 @@ -namespace PKHeX.Core -{ - /// - /// Generation 5 Dream Radar gift encounters - /// - /// - public sealed record EncounterStatic5DR : EncounterStatic5 - { - public EncounterStatic5DR(int species, int form, AbilityPermission ability = AbilityPermission.OnlyHidden) : base(GameVersion.B2W2) - { - Species = species; - Form = form; - Ability = ability; - Location = 30015; - Gift = true; - Ball = 25; - Level = 5; // to 40 - Shiny = Shiny.Never; - } +namespace PKHeX.Core; - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - // Level from 5->40 depends on the number of badges - var met = pkm.Met_Level; - if (met % 5 != 0) - return false; - return (uint) (met - 5) <= 35; // 5 <= x <= 40 - } +/// +/// Generation 5 Dream Radar gift encounters +/// +/// +public sealed record EncounterStatic5DR : EncounterStatic5 +{ + public EncounterStatic5DR(int species, int form, AbilityPermission ability = AbilityPermission.OnlyHidden) : base(GameVersion.B2W2) + { + Species = species; + Form = form; + Ability = ability; + Location = 30015; + Gift = true; + Ball = 25; + Level = 5; // to 40 + Shiny = Shiny.Never; + } + + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + // Level from 5->40 depends on the number of badges + var met = pk.Met_Level; + if (met % 5 != 0) + return false; + return (uint) (met - 5) <= 35; // 5 <= x <= 40 } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs index 7f67929e1..cd5d12bdf 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic5N.cs @@ -1,56 +1,55 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 Static Encounter from N +/// +/// +internal sealed record EncounterStatic5N : EncounterStatic5 { - /// - /// Generation 5 Static Encounter from N - /// - /// - internal sealed record EncounterStatic5N : EncounterStatic5 + public readonly uint PID; + public const bool NSparkle = true; + + internal EncounterStatic5N(uint pid) : base(GameVersion.B2W2) { - public readonly uint PID; - public const bool NSparkle = true; - - internal EncounterStatic5N(uint pid) : base(GameVersion.B2W2) - { - Shiny = Shiny.FixedValue; - PID = pid; - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pk.PersonalInfo); - int nature = (int)Nature; - int ability = Ability.IsSingleValue(out var index) ? index : 0; - - pk.PID = PID; - pk.Gender = gender; - SetIVs(pk); - - pk.Nature = nature; - pk.RefreshAbility(ability); - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (PID != pkm.PID) - return false; - return base.IsMatchExact(pkm, evo); - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - SetNPokemonData((PK5)pk, pk.Language); - } - - private static void SetNPokemonData(PK5 pk5, int lang) - { - pk5.IV_HP = pk5.IV_ATK = pk5.IV_DEF = pk5.IV_SPA = pk5.IV_SPD = pk5.IV_SPE = 30; - pk5.NSparkle = NSparkle; - pk5.OT_Name = GetOT(lang); - pk5.TID = 00002; - pk5.SID = 00000; - } - - public static string GetOT(int lang) => lang == (int)LanguageID.Japanese ? "N" : "N"; + Shiny = Shiny.FixedValue; + PID = pid; } + + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pk.PersonalInfo); + int nature = (int)Nature; + int ability = Ability.IsSingleValue(out var index) ? index : 0; + + pk.PID = PID; + pk.Gender = gender; + SetIVs(pk); + + pk.Nature = nature; + pk.RefreshAbility(ability); + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (PID != pk.PID) + return false; + return base.IsMatchExact(pk, evo); + } + + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + SetNPokemonData((PK5)pk, pk.Language); + } + + private static void SetNPokemonData(PK5 pk5, int lang) + { + pk5.IV_HP = pk5.IV_ATK = pk5.IV_DEF = pk5.IV_SPA = pk5.IV_SPD = pk5.IV_SPE = 30; + pk5.NSparkle = NSparkle; + pk5.OT_Name = GetOT(lang); + pk5.TID = 00002; + pk5.SID = 00000; + } + + public static string GetOT(int lang) => lang == (int)LanguageID.Japanese ? "N" : "N"; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs index 8419de0b7..2ffc224fb 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic6.cs @@ -1,57 +1,56 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 6 Static Encounter +/// +/// +public sealed record EncounterStatic6(GameVersion Version) : EncounterStatic(Version), IContestStats { - /// - /// Generation 6 Static Encounter - /// - /// - public sealed record EncounterStatic6(GameVersion Version) : EncounterStatic(Version), IContestStats + public override int Generation => 6; + + public byte CNT_Cool { get; init; } + public byte CNT_Beauty { get; init; } + public byte CNT_Cute { get; init; } + public byte CNT_Smart { get; init; } + public byte CNT_Tough { get; init; } + public byte CNT_Sheen { get; init; } + + protected override bool IsMatchLocation(PKM pk) { - public override int Generation => 6; - - public byte CNT_Cool { get; init; } - public byte CNT_Beauty { get; init; } - public byte CNT_Cute { get; init; } - public byte CNT_Smart { get; init; } - public byte CNT_Tough { get; init; } - public byte CNT_Sheen { get; init; } - - protected override bool IsMatchLocation(PKM pkm) - { - if (base.IsMatchLocation(pkm)) - return true; - - if (Species != (int) Core.Species.Pikachu) - return false; - - // Cosplay Pikachu is given from multiple locations - var loc = pkm.Met_Location; - return loc is 180 or 186 or 194; - } - - protected override bool IsMatchEggLocation(PKM pkm) - { - if (!EggEncounter) - return base.IsMatchEggLocation(pkm); - - var eggloc = pkm.Egg_Location; - if (!pkm.IsEgg) // hatched - return eggloc == EggLocation || eggloc == Locations.LinkTrade6; - - // Unhatched: - if (eggloc != EggLocation) - return false; - if (pkm.Met_Location is not (0 or Locations.LinkTrade6)) - return false; + if (base.IsMatchLocation(pk)) return true; - } - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pk6 = (PK6)pk; - this.CopyContestStatsTo(pk6); - pk6.SetRandomMemory6(); - pk6.SetRandomEC(); - } + if (Species != (int) Core.Species.Pikachu) + return false; + + // Cosplay Pikachu is given from multiple locations + var loc = pk.Met_Location; + return loc is 180 or 186 or 194; + } + + protected override bool IsMatchEggLocation(PKM pk) + { + if (!EggEncounter) + return base.IsMatchEggLocation(pk); + + var eggloc = pk.Egg_Location; + if (!pk.IsEgg) // hatched + return eggloc == EggLocation || eggloc == Locations.LinkTrade6; + + // Unhatched: + if (eggloc != EggLocation) + return false; + if (pk.Met_Location is not (0 or Locations.LinkTrade6)) + return false; + return true; + } + + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + var pk6 = (PK6)pk; + this.CopyContestStatsTo(pk6); + pk6.SetRandomMemory6(); + pk6.SetRandomEC(); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs index 60fddc69b..3fc24cf39 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7.cs @@ -1,96 +1,95 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 Static Encounter +/// +/// +public sealed record EncounterStatic7(GameVersion Version) : EncounterStatic(Version), IRelearn { - /// - /// Generation 7 Static Encounter - /// - /// - public sealed record EncounterStatic7(GameVersion Version) : EncounterStatic(Version), IRelearn + public override int Generation => 7; + public IReadOnlyList Relearn { get; init; } = Array.Empty(); + + public bool IsTotem => FormInfo.IsTotemForm(Species, Form); + public bool IsTotemNoTransfer => Legal.Totem_NoTransfer.Contains(Species); + public int GetTotemBaseForm() => FormInfo.GetTotemBaseForm(Species, Form); + + protected override bool IsMatchLocation(PKM pk) { - public override int Generation => 7; - public IReadOnlyList Relearn { get; init; } = Array.Empty(); + if (EggLocation == Locations.Daycare5 && Relearn.Count == 0 && pk.RelearnMove1 != 0) // Gift Eevee edge case + return false; + return base.IsMatchLocation(pk); + } - public bool IsTotem => FormInfo.IsTotemForm(Species, Form); - public bool IsTotemNoTransfer => Legal.Totem_NoTransfer.Contains(Species); - public int GetTotemBaseForm() => FormInfo.GetTotemBaseForm(Species, Form); + protected override bool IsMatchEggLocation(PKM pk) + { + if (!EggEncounter) + return base.IsMatchEggLocation(pk); - protected override bool IsMatchLocation(PKM pkm) + var eggloc = pk.Egg_Location; + if (!pk.IsEgg) // hatched + return eggloc == EggLocation || eggloc == Locations.LinkTrade6; + + // Unhatched: + if (eggloc != EggLocation) + return false; + if (pk.Met_Location is not (0 or Locations.LinkTrade6)) + return false; + return true; + } + + protected override bool IsMatchForm(PKM pk, EvoCriteria evo) + { + if (IsTotem) { - if (EggLocation == Locations.Daycare5 && Relearn.Count == 0 && pkm.RelearnMove1 != 0) // Gift Eevee edge case - return false; - return base.IsMatchLocation(pkm); + var expectForm = pk.Format == 7 ? Form : FormInfo.GetTotemBaseForm(Species, Form); + return expectForm == evo.Form; } + return base.IsMatchForm(pk, evo); + } - protected override bool IsMatchEggLocation(PKM pkm) + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + if (Species == (int)Core.Species.Magearna && pk is IRibbonSetEvent4 e4) + e4.RibbonWishing = true; + if (Form == FormVivillon && pk is PK7 pk7) + pk.Form = Vivillon3DS.GetPattern(pk7.Country, pk7.Region); + pk.SetRandomEC(); + } + + internal static EncounterStatic7 GetVC1(int species, byte metLevel) + { + bool mew = species == (int)Core.Species.Mew; + return new EncounterStatic7(GameVersion.RBY) { - if (!EggEncounter) - return base.IsMatchEggLocation(pkm); + Species = species, + Gift = true, // Forces Poké Ball + Ability = Legal.TransferSpeciesDefaultAbilityGen1(species) ? AbilityPermission.OnlyFirst : AbilityPermission.OnlyHidden, // Hidden by default, else first + Shiny = mew ? Shiny.Never : Shiny.Random, + Fateful = mew, + Location = Locations.Transfer1, + Level = metLevel, + FlawlessIVCount = mew ? (byte)5 : (byte)3, + }; + } - var eggloc = pkm.Egg_Location; - if (!pkm.IsEgg) // hatched - return eggloc == EggLocation || eggloc == Locations.LinkTrade6; - - // Unhatched: - if (eggloc != EggLocation) - return false; - if (pkm.Met_Location is not (0 or Locations.LinkTrade6)) - return false; - return true; - } - - protected override bool IsMatchForm(PKM pkm, EvoCriteria evo) + internal static EncounterStatic7 GetVC2(int species, byte metLevel) + { + bool mew = species == (int)Core.Species.Mew; + bool fateful = mew || species == (int)Core.Species.Celebi; + return new EncounterStatic7(GameVersion.GSC) { - if (IsTotem) - { - var expectForm = pkm.Format == 7 ? Form : FormInfo.GetTotemBaseForm(Species, Form); - return expectForm == evo.Form; - } - return base.IsMatchForm(pkm, evo); - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - if (Species == (int)Core.Species.Magearna && pk is IRibbonSetEvent4 e4) - e4.RibbonWishing = true; - if (Form == FormVivillon && pk is PK7 pk7) - pk.Form = Vivillon3DS.GetPattern(pk7.Country, pk7.Region); - pk.SetRandomEC(); - } - - internal static EncounterStatic7 GetVC1(int species, byte metLevel) - { - bool mew = species == (int)Core.Species.Mew; - return new EncounterStatic7(GameVersion.RBY) - { - Species = species, - Gift = true, // Forces Poké Ball - Ability = Legal.TransferSpeciesDefaultAbilityGen1(species) ? AbilityPermission.OnlyFirst : AbilityPermission.OnlyHidden, // Hidden by default, else first - Shiny = mew ? Shiny.Never : Shiny.Random, - Fateful = mew, - Location = Locations.Transfer1, - Level = metLevel, - FlawlessIVCount = mew ? (byte)5 : (byte)3, - }; - } - - internal static EncounterStatic7 GetVC2(int species, byte metLevel) - { - bool mew = species == (int)Core.Species.Mew; - bool fateful = mew || species == (int)Core.Species.Celebi; - return new EncounterStatic7(GameVersion.GSC) - { - Species = species, - Gift = true, // Forces Poké Ball - Ability = Legal.TransferSpeciesDefaultAbilityGen2(species) ? AbilityPermission.OnlyFirst : AbilityPermission.OnlyHidden, // Hidden by default, else first - Shiny = mew ? Shiny.Never : Shiny.Random, - Fateful = fateful, - Location = Locations.Transfer2, - Level = metLevel, - FlawlessIVCount = fateful ? (byte)5 : (byte)3, - }; - } + Species = species, + Gift = true, // Forces Poké Ball + Ability = Legal.TransferSpeciesDefaultAbilityGen2(species) ? AbilityPermission.OnlyFirst : AbilityPermission.OnlyHidden, // Hidden by default, else first + Shiny = mew ? Shiny.Never : Shiny.Random, + Fateful = fateful, + Location = Locations.Transfer2, + Level = metLevel, + FlawlessIVCount = fateful ? (byte)5 : (byte)3, + }; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7b.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7b.cs index 024493bb4..cb213220a 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7b.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic7b.cs @@ -1,21 +1,20 @@ -namespace PKHeX.Core -{ - /// - /// Generation 7 Static Encounter ( - /// - /// - public sealed record EncounterStatic7b(GameVersion Version) : EncounterStatic(Version) - { - public override int Generation => 7; +namespace PKHeX.Core; - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - pk.SetRandomEC(); - var pb = (PB7)pk; - pb.ResetHeight(); - pb.ResetWeight(); - pb.ResetCP(); - } +/// +/// Generation 7 Static Encounter ( +/// +/// +public sealed record EncounterStatic7b(GameVersion Version) : EncounterStatic(Version) +{ + public override int Generation => 7; + + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + pk.SetRandomEC(); + var pb = (PB7)pk; + pb.ResetHeight(); + pb.ResetWeight(); + pb.ResetCP(); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs index 03a855723..b01dee12c 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8.cs @@ -2,119 +2,118 @@ using System.Collections.Generic; using static PKHeX.Core.OverworldCorrelation8Requirement; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 Static Encounter +/// +/// +public record EncounterStatic8(GameVersion Version) : EncounterStatic(Version), IDynamaxLevel, IGigantamax, IRelearn, IOverworldCorrelation8 { - /// - /// Generation 8 Static Encounter - /// - /// - public record EncounterStatic8(GameVersion Version) : EncounterStatic(Version), IDynamaxLevel, IGigantamax, IRelearn, IOverworldCorrelation8 + public sealed override int Generation => 8; + public bool ScriptedNoMarks { get; init; } + public bool CanGigantamax { get; set; } + public byte DynamaxLevel { get; set; } + public IReadOnlyList Relearn { get; init; } = Array.Empty(); + + public AreaWeather8 Weather {get; init; } = AreaWeather8.Normal; + + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) { - public sealed override int Generation => 8; - public bool ScriptedNoMarks { get; init; } - public bool CanGigantamax { get; set; } - public byte DynamaxLevel { get; set; } - public IReadOnlyList Relearn { get; init; } = Array.Empty(); + var met = pk.Met_Level; + var lvl = Level; + if (met == lvl) + return true; + if (lvl < EncounterArea8.BoostLevel && EncounterArea8.IsBoostedArea60(Location)) + return met == EncounterArea8.BoostLevel; + return false; + } - public AreaWeather8 Weather {get; init; } = AreaWeather8.Normal; - - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - var met = pkm.Met_Level; - var lvl = Level; - if (met == lvl) - return true; - if (lvl < EncounterArea8.BoostLevel && EncounterArea8.IsBoostedArea60(Location)) - return met == EncounterArea8.BoostLevel; + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk is PK8 d && d.DynamaxLevel < DynamaxLevel) return false; - } + if (pk.Met_Level < EncounterArea8.BoostLevel && Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) + return false; + return base.IsMatchExact(pk, evo); + } - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + if (Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) + pk.CurrentLevel = pk.Met_Level = EncounterArea8.BoostLevel; + + var req = GetRequirement(pk); + if (req != MustHave) { - if (pkm is PK8 d && d.DynamaxLevel < DynamaxLevel) - return false; - if (pkm.Met_Level < EncounterArea8.BoostLevel && Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) - return false; - return base.IsMatchExact(pkm, evo); + pk.SetRandomEC(); + return; } + var shiny = Shiny == Shiny.Random ? Shiny.FixedValue : Shiny; + Overworld8RNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount); + } - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + public bool IsOverworldCorrelation + { + get { - base.ApplyDetails(sav, criteria, pk); - if (Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location)) - pk.CurrentLevel = pk.Met_Level = EncounterArea8.BoostLevel; - - var req = GetRequirement(pk); - if (req != MustHave) - { - pk.SetRandomEC(); - return; - } - var shiny = Shiny == Shiny.Random ? Shiny.FixedValue : Shiny; - Overworld8RNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount); - } - - public bool IsOverworldCorrelation - { - get - { - if (Gift) - return false; // gifts can have any 128bit seed from overworld - if (ScriptedNoMarks) - return false; // scripted encounters don't act as saved spawned overworld encounters - return true; - } - } - - public OverworldCorrelation8Requirement GetRequirement(PKM pk) => IsOverworldCorrelation - ? MustHave - : MustNotHave; - - public bool IsOverworldCorrelationCorrect(PKM pk) - { - return Overworld8RNG.ValidateOverworldEncounter(pk, Shiny == Shiny.Random ? Shiny.FixedValue : Shiny, FlawlessIVCount); - } - - public override EncounterMatchRating GetMatchRating(PKM pkm) - { - var rating = base.GetMatchRating(pkm); - if (rating != EncounterMatchRating.Match) - return rating; - - var req = GetRequirement(pkm); - bool correlation = IsOverworldCorrelationCorrect(pkm); - if ((req == MustHave) != correlation) - return EncounterMatchRating.DeferredErrors; - - // Only encounter slots can have these marks; defer for collisions. - if (pkm.Species == (int) Core.Species.Shedinja) - { - // Loses Mark on evolution to Shedinja, but not affixed ribbon value. - return pkm switch - { - IRibbonSetMark8 {RibbonMarkCurry: true} => EncounterMatchRating.DeferredErrors, - PK8 {AffixedRibbon: (int) RibbonIndex.MarkCurry} => EncounterMatchRating.Deferred, - _ => EncounterMatchRating.Match, - }; - } - - if (pkm is IRibbonSetMark8 m && (m.RibbonMarkCurry || m.RibbonMarkFishing || m.HasWeatherMark())) - return EncounterMatchRating.DeferredErrors; - - return EncounterMatchRating.Match; + if (Gift) + return false; // gifts can have any 128bit seed from overworld + if (ScriptedNoMarks) + return false; // scripted encounters don't act as saved spawned overworld encounters + return true; } } - public interface IOverworldCorrelation8 + public OverworldCorrelation8Requirement GetRequirement(PKM pk) => IsOverworldCorrelation + ? MustHave + : MustNotHave; + + public bool IsOverworldCorrelationCorrect(PKM pk) { - OverworldCorrelation8Requirement GetRequirement(PKM pk); - bool IsOverworldCorrelationCorrect(PKM pk); + return Overworld8RNG.ValidateOverworldEncounter(pk, Shiny == Shiny.Random ? Shiny.FixedValue : Shiny, FlawlessIVCount); } - public enum OverworldCorrelation8Requirement + public override EncounterMatchRating GetMatchRating(PKM pk) { - CanBeEither, - MustHave, - MustNotHave, + var rating = base.GetMatchRating(pk); + if (rating != EncounterMatchRating.Match) + return rating; + + var req = GetRequirement(pk); + bool correlation = IsOverworldCorrelationCorrect(pk); + if ((req == MustHave) != correlation) + return EncounterMatchRating.DeferredErrors; + + // Only encounter slots can have these marks; defer for collisions. + if (pk.Species == (int) Core.Species.Shedinja) + { + // Loses Mark on evolution to Shedinja, but not affixed ribbon value. + return pk switch + { + IRibbonSetMark8 {RibbonMarkCurry: true} => EncounterMatchRating.DeferredErrors, + PK8 {AffixedRibbon: (int) RibbonIndex.MarkCurry} => EncounterMatchRating.Deferred, + _ => EncounterMatchRating.Match, + }; + } + + if (pk is IRibbonSetMark8 m && (m.RibbonMarkCurry || m.RibbonMarkFishing || m.HasWeatherMark())) + return EncounterMatchRating.DeferredErrors; + + return EncounterMatchRating.Match; } } + +public interface IOverworldCorrelation8 +{ + OverworldCorrelation8Requirement GetRequirement(PKM pk); + bool IsOverworldCorrelationCorrect(PKM pk); +} + +public enum OverworldCorrelation8Requirement +{ + CanBeEither, + MustHave, + MustNotHave, +} diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8N.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8N.cs index 1a0831016..2757efd9e 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8N.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8N.cs @@ -2,97 +2,96 @@ using System.Linq; using static PKHeX.Core.Encounters8Nest; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 Nest Encounter (Regular Raid Dens) +/// +/// +public sealed record EncounterStatic8N : EncounterStatic8Nest { - /// - /// Generation 8 Nest Encounter (Regular Raid Dens) - /// - /// - public sealed record EncounterStatic8N : EncounterStatic8Nest + private readonly uint MinRank; + private readonly uint MaxRank; + private readonly byte NestID; + + private IReadOnlyList NestLocations => Encounters8Nest.NestLocations[NestID]; + + public override byte Level { get => LevelMin; init { } } + public override byte LevelMin => LevelCaps[MinRank * 2]; + public override byte LevelMax => LevelCaps[(MaxRank * 2) + 1]; + + public EncounterStatic8N(byte nestID, uint minRank, uint maxRank, byte val, GameVersion game) : base(game) { - private readonly uint MinRank; - private readonly uint MaxRank; - private readonly byte NestID; + NestID = nestID; + MinRank = minRank; + MaxRank = maxRank; + DynamaxLevel = (byte)(MinRank + 1u); + FlawlessIVCount = val; + } - private IReadOnlyList NestLocations => Encounters8Nest.NestLocations[NestID]; + private static readonly byte[] LevelCaps = + { + 15, 20, // 0 + 25, 30, // 1 + 35, 40, // 2 + 45, 50, // 3 + 55, 60, // 4 + }; - public override byte Level { get => LevelMin; init { } } - public override byte LevelMin => LevelCaps[MinRank * 2]; - public override byte LevelMax => LevelCaps[(MaxRank * 2) + 1]; + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + var met = pk.Met_Level; + var metLevel = met - 15; + var rank = ((uint)metLevel) / 10; + if (rank > 4) + return false; + if (rank > MaxRank) + return false; - public EncounterStatic8N(byte nestID, uint minRank, uint maxRank, byte val, GameVersion game) : base(game) + if (rank <= 1) { - NestID = nestID; - MinRank = minRank; - MaxRank = maxRank; - DynamaxLevel = (byte)(MinRank + 1u); - FlawlessIVCount = val; - } - - private static readonly byte[] LevelCaps = - { - 15, 20, // 0 - 25, 30, // 1 - 35, 40, // 2 - 45, 50, // 3 - 55, 60, // 4 - }; - - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - var met = pkm.Met_Level; - var metLevel = met - 15; - var rank = ((uint)metLevel) / 10; - if (rank > 4) + if (InaccessibleRank12Nests.TryGetValue(pk.Met_Location, out var nests) && nests.Contains(NestID)) return false; - if (rank > MaxRank) - return false; - - if (rank <= 1) - { - if (InaccessibleRank12Nests.TryGetValue(pkm.Met_Location, out var nests) && nests.Contains(NestID)) - return false; - } - - if (rank < MinRank) // down-leveled - return IsDownLeveled(pkm, metLevel, met); - - return metLevel % 10 <= 5; } - public bool IsDownLeveled(PKM pkm) - { - var met = pkm.Met_Level; - var metLevel = met - 15; - return met != LevelMax && IsDownLeveled(pkm, metLevel, met); - } + if (rank < MinRank) // down-leveled + return IsDownLeveled(pk, metLevel, met); - private bool IsDownLeveled(PKM pkm, int metLevel, int met) - { - if (metLevel % 5 != 0) - return false; + return metLevel % 10 <= 5; + } - // shared nests can be down-leveled to any - if (pkm.Met_Location == SharedNest) - return met >= 20; + public bool IsDownLeveled(PKM pk) + { + var met = pk.Met_Level; + var metLevel = met - 15; + return met != LevelMax && IsDownLeveled(pk, metLevel, met); + } - // native down-levels: only allow 1 rank down (1 badge 2star -> 25), (3badge 3star -> 35) - return ((MinRank <= 1 && 1 <= MaxRank && met == 25) - || (MinRank <= 2 && 2 <= MaxRank && met == 35)) && !pkm.IsShiny; - } + private bool IsDownLeveled(PKM pk, int metLevel, int met) + { + if (metLevel % 5 != 0) + return false; - protected override bool IsMatchLocation(PKM pkm) - { - var loc = pkm.Met_Location; - return loc == SharedNest || (loc <= 255 && NestLocations.Contains((byte)loc)); - } + // shared nests can be down-leveled to any + if (pk.Met_Location == SharedNest) + return met >= 20; - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (pkm.FlawlessIVCount < FlawlessIVCount) - return false; + // native down-levels: only allow 1 rank down (1 badge 2star -> 25), (3badge 3star -> 35) + return ((MinRank <= 1 && 1 <= MaxRank && met == 25) + || (MinRank <= 2 && 2 <= MaxRank && met == 35)) && !pk.IsShiny; + } - return base.IsMatchExact(pkm, evo); - } + protected override bool IsMatchLocation(PKM pk) + { + var loc = pk.Met_Location; + return loc == SharedNest || (loc <= 255 && NestLocations.Contains((byte)loc)); + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.FlawlessIVCount < FlawlessIVCount) + return false; + + return base.IsMatchExact(pk, evo); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8NC.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8NC.cs index 76de545f1..dad4a1cee 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8NC.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8NC.cs @@ -1,33 +1,32 @@ using static PKHeX.Core.Encounters8Nest; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 Nest Encounter (Distributed Crystal Data) +/// +/// +public sealed record EncounterStatic8NC(GameVersion Version) : EncounterStatic8Nest(Version) { - /// - /// Generation 8 Nest Encounter (Distributed Crystal Data) - /// - /// - public sealed record EncounterStatic8NC(GameVersion Version) : EncounterStatic8Nest(Version) + protected override bool IsMatchLocation(PKM pk) { - protected override bool IsMatchLocation(PKM pkm) - { - var loc = pkm.Met_Location; - return loc is SharedNest or Watchtower; - } + var loc = pk.Met_Location; + return loc is SharedNest or Watchtower; + } - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - var lvl = pkm.Met_Level; - if (lvl == Level) - return true; + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + var lvl = pk.Met_Level; + if (lvl == Level) + return true; - // Check downleveled (20-55) - if (lvl > Level) - return false; - if (lvl is < 20 or > 55) - return false; - if (pkm.Met_Location == Watchtower && pkm.IsShiny) - return false; // host cannot downlevel and be shiny - return lvl % 5 == 0; - } + // Check downleveled (20-55) + if (lvl > Level) + return false; + if (lvl is < 20 or > 55) + return false; + if (pk.Met_Location == Watchtower && pk.IsShiny) + return false; // host cannot downlevel and be shiny + return lvl % 5 == 0; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8ND.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8ND.cs index c9b8894a7..07f0d5625 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8ND.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8ND.cs @@ -1,73 +1,72 @@ using static PKHeX.Core.Encounters8Nest; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 Nest Encounter (Distributed Data) +/// +/// +public sealed record EncounterStatic8ND : EncounterStatic8Nest { /// - /// Generation 8 Nest Encounter (Distributed Data) + /// Distribution raid index for /// - /// - public sealed record EncounterStatic8ND : EncounterStatic8Nest + public byte Index { get; init; } + + public EncounterStatic8ND(byte lvl, byte dyna, byte flawless, GameVersion game = GameVersion.SWSH) : base(game) { - /// - /// Distribution raid index for - /// - public byte Index { get; init; } + Level = lvl; + DynamaxLevel = dyna; + FlawlessIVCount = flawless; + } - public EncounterStatic8ND(byte lvl, byte dyna, byte flawless, GameVersion game = GameVersion.SWSH) : base(game) + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + var lvl = pk.Met_Level; + + if (lvl <= 25) // 1 or 2 stars { - Level = lvl; - DynamaxLevel = dyna; - FlawlessIVCount = flawless; + if (InaccessibleRank12DistributionLocations.Contains(pk.Met_Location)) + return false; } - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) + if (lvl == Level) + return true; + + // Check downleveled (20-55) + if (lvl > Level) + return false; + if (lvl is < 20 or > 55) + return false; + + if (lvl % 5 != 0) + return false; + + // shared nests can be down-leveled to any + if (pk.Met_Location == SharedNest) + return true; + + // native down-levels: only allow 1 rank down (1 badge 2star -> 25), (3badge 3star -> 35) + var badges = (lvl - 20) / 5; + return badges is 1 or 3 && !pk.IsShiny; + } + + protected override bool IsMatchLocation(PKM pk) + { + var loc = pk.Met_Location; + return loc is SharedNest || Index switch { - var lvl = pkm.Met_Level; + >= 40 => EncounterArea8.IsWildArea(loc), + >= 25 => EncounterArea8.IsWildArea8(loc) || EncounterArea8.IsWildArea8Armor(loc), + _ => EncounterArea8.IsWildArea8(loc), + }; + } - if (lvl <= 25) // 1 or 2 stars - { - if (InaccessibleRank12DistributionLocations.Contains(pkm.Met_Location)) - return false; - } + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.FlawlessIVCount < FlawlessIVCount) + return false; - if (lvl == Level) - return true; - - // Check downleveled (20-55) - if (lvl > Level) - return false; - if (lvl is < 20 or > 55) - return false; - - if (lvl % 5 != 0) - return false; - - // shared nests can be down-leveled to any - if (pkm.Met_Location == SharedNest) - return true; - - // native down-levels: only allow 1 rank down (1 badge 2star -> 25), (3badge 3star -> 35) - var badges = (lvl - 20) / 5; - return badges is 1 or 3 && !pkm.IsShiny; - } - - protected override bool IsMatchLocation(PKM pkm) - { - var loc = pkm.Met_Location; - return loc is SharedNest || Index switch - { - >= 40 => EncounterArea8.IsWildArea(loc), - >= 25 => EncounterArea8.IsWildArea8(loc) || EncounterArea8.IsWildArea8Armor(loc), - _ => EncounterArea8.IsWildArea8(loc), - }; - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (pkm.FlawlessIVCount < FlawlessIVCount) - return false; - - return base.IsMatchExact(pkm, evo); - } + return base.IsMatchExact(pk, evo); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs index 7666ae880..b21887752 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8Nest.cs @@ -2,119 +2,118 @@ using static PKHeX.Core.Encounters8Nest; using static PKHeX.Core.AbilityPermission; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 Nest Encounter (Raid) +/// +/// +public abstract record EncounterStatic8Nest(GameVersion Version) : EncounterStatic(Version), IGigantamax, IDynamaxLevel where T : EncounterStatic8Nest { - /// - /// Generation 8 Nest Encounter (Raid) - /// - /// - public abstract record EncounterStatic8Nest(GameVersion Version) : EncounterStatic(Version), IGigantamax, IDynamaxLevel where T : EncounterStatic8Nest + public sealed override int Generation => 8; + public static Func? VerifyCorrelation { private get; set; } + public static Action? GenerateData { private get; set; } + + public bool CanGigantamax { get; set; } + public byte DynamaxLevel { get; set; } + public override int Location { get => SharedNest; init { } } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) { - public sealed override int Generation => 8; - public static Func? VerifyCorrelation { private get; set; } - public static Action? GenerateData { private get; set; } + if (pk is PK8 d && d.DynamaxLevel < DynamaxLevel) + return false; - public bool CanGigantamax { get; set; } - public byte DynamaxLevel { get; set; } - public override int Location { get => SharedNest; init { } } + // Required Ability + if (Ability == OnlyHidden && pk.AbilityNumber != 4) + return false; // H - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) + if (Version != GameVersion.SWSH && pk.Version != (int)Version && pk.Met_Location != SharedNest) + return false; + + if (VerifyCorrelation != null && !VerifyCorrelation(pk, (T)this)) + return false; + + if (pk is IRibbonSetMark8 m8 && m8.HasMark()) + return false; + if (pk.Species == (int)Core.Species.Shedinja && pk is IRibbonSetAffixed { AffixedRibbon: >= (int)RibbonIndex.MarkLunchtime }) + return false; + + return base.IsMatchExact(pk, evo); + } + + protected sealed override EncounterMatchRating IsMatchDeferred(PKM pk) + { + if (Ability != Any12H) { - if (pkm is PK8 d && d.DynamaxLevel < DynamaxLevel) - return false; - - // Required Ability - if (Ability == OnlyHidden && pkm.AbilityNumber != 4) - return false; // H - - if (Version != GameVersion.SWSH && pkm.Version != (int)Version && pkm.Met_Location != SharedNest) - return false; - - if (VerifyCorrelation != null && !VerifyCorrelation(pkm, (T)this)) - return false; - - if (pkm is IRibbonSetMark8 m8 && m8.HasMark()) - return false; - if (pkm.Species == (int)Core.Species.Shedinja && pkm is IRibbonSetAffixed { AffixedRibbon: >= (int)RibbonIndex.MarkLunchtime }) - return false; - - return base.IsMatchExact(pkm, evo); - } - - protected sealed override EncounterMatchRating IsMatchDeferred(PKM pkm) - { - if (Ability != Any12H) + // HA-Only is a strict match. Ability Capsule and Patch can potentially change these. + var num = pk.AbilityNumber; + if (num == 4) { - // HA-Only is a strict match. Ability Capsule and Patch can potentially change these. - var num = pkm.AbilityNumber; - if (num == 4) - { - if (Ability is not OnlyHidden && !AbilityVerifier.CanAbilityPatch(8, PersonalTable.SWSH.GetFormEntry(Species, Form).Abilities, pkm.Species)) - return EncounterMatchRating.DeferredErrors; - } - else if (Ability.IsSingleValue(out int index) && 1 << index != num) // Fixed regular ability - { - if (Ability is OnlyFirst or OnlySecond && !AbilityVerifier.CanAbilityCapsule(8, PersonalTable.SWSH.GetFormEntry(Species, Form).Abilities)) - return EncounterMatchRating.DeferredErrors; - } + if (Ability is not OnlyHidden && !AbilityVerifier.CanAbilityPatch(8, PersonalTable.SWSH.GetFormEntry(Species, Form).Abilities, pk.Species)) + return EncounterMatchRating.DeferredErrors; + } + else if (Ability.IsSingleValue(out int index) && 1 << index != num) // Fixed regular ability + { + if (Ability is OnlyFirst or OnlySecond && !AbilityVerifier.CanAbilityCapsule(8, PersonalTable.SWSH.GetFormEntry(Species, Form).Abilities)) + return EncounterMatchRating.DeferredErrors; } - - return base.IsMatchDeferred(pkm); } - protected override bool IsMatchPartial(PKM pkm) + return base.IsMatchDeferred(pk); + } + + protected override bool IsMatchPartial(PKM pk) + { + if (pk is PK8 and IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pk.Species, pk.Form, Species, Form)) + return true; + if (Species == (int)Core.Species.Alcremie && pk is IFormArgument { FormArgument: not 0 }) + return true; + if (Species == (int)Core.Species.Runerigus && pk is IFormArgument { FormArgument: not 0 }) + return true; + + switch (Shiny) { - if (pkm is PK8 and IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form)) + case Shiny.Never when pk.IsShiny: + case Shiny.Always when !pk.IsShiny: return true; - if (Species == (int)Core.Species.Alcremie && pkm is IFormArgument { FormArgument: not 0 }) - return true; - if (Species == (int)Core.Species.Runerigus && pkm is IFormArgument { FormArgument: not 0 }) - return true; - - switch (Shiny) - { - case Shiny.Never when pkm.IsShiny: - case Shiny.Always when !pkm.IsShiny: - return true; - } - - return base.IsMatchPartial(pkm); } - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + return base.IsMatchPartial(pk); + } + + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + if (GenerateData == null) + pk.SetRandomEC(); + } + + protected sealed override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + if (GenerateData != null) { - base.ApplyDetails(sav, criteria, pk); - if (GenerateData == null) - pk.SetRandomEC(); + GenerateData(pk, (T)this, criteria); + return; } - protected sealed override void SetPINGA(PKM pk, EncounterCriteria criteria) + base.SetPINGA(pk, criteria); + if (Species == (int) Core.Species.Toxtricity) { - if (GenerateData != null) + while (true) { - GenerateData(pk, (T)this, criteria); - return; + var result = EvolutionMethod.GetAmpLowKeyResult(pk.Nature); + if (result == pk.Form) + break; + pk.Nature = Util.Rand.Next(25); } - base.SetPINGA(pk, criteria); - if (Species == (int) Core.Species.Toxtricity) - { - while (true) - { - var result = EvolutionMethod.GetAmpLowKeyResult(pk.Nature); - if (result == pk.Form) - break; - pk.Nature = Util.Rand.Next(25); - } - - // Might be originally generated with a Neutral nature, then above logic changes to another. - // Realign the stat nature to Serious mint. - if (pk.Nature != pk.StatNature && ((Nature)pk.StatNature).IsNeutral()) - pk.StatNature = (int)Nature.Serious; - } - var pid = pk.PID; - RaidRNG.ForceShinyState(pk, Shiny == Shiny.Always, ref pid); - pk.PID = pid; + // Might be originally generated with a Neutral nature, then above logic changes to another. + // Realign the stat nature to Serious mint. + if (pk.Nature != pk.StatNature && ((Nature)pk.StatNature).IsNeutral()) + pk.StatNature = (int)Nature.Serious; } + var pid = pk.PID; + RaidRNG.ForceShinyState(pk, Shiny == Shiny.Always, ref pid); + pk.PID = pid; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8S.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8S.cs index c0595ee68..9dc0fdd90 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8S.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8S.cs @@ -2,16 +2,15 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// with multiple references (used for multiple met locations) +/// +/// +public sealed record EncounterStatic8S(GameVersion Version) : EncounterStatic8(Version) { - /// - /// with multiple references (used for multiple met locations) - /// - /// - public sealed record EncounterStatic8S(GameVersion Version) : EncounterStatic8(Version) - { - public override int Location { get => Locations[0]; init { } } - public IReadOnlyList Locations { get; init; } = Array.Empty(); - protected override bool IsMatchLocation(PKM pkm) => Locations.Contains(pkm.Met_Location); - } + public override int Location { get => Locations[0]; init { } } + public IReadOnlyList Locations { get; init; } = Array.Empty(); + protected override bool IsMatchLocation(PKM pk) => Locations.Contains(pk.Met_Location); } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8U.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8U.cs index 95a22bc60..f66ea637f 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8U.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8U.cs @@ -1,33 +1,32 @@ using static PKHeX.Core.Encounters8Nest; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 Nest Encounter (Max Raid) Underground +/// +/// +public sealed record EncounterStatic8U : EncounterStatic8Nest { - /// - /// Generation 8 Nest Encounter (Max Raid) Underground - /// - /// - public sealed record EncounterStatic8U : EncounterStatic8Nest + public override int Location { get => MaxLair; init { } } + + public EncounterStatic8U(ushort species, byte form, byte level) : base(GameVersion.SWSH) // no difference in met location for hosted raids { - public override int Location { get => MaxLair; init { } } - - public EncounterStatic8U(ushort species, byte form, byte level) : base(GameVersion.SWSH) // no difference in met location for hosted raids - { - Species = species; - Form = form; - Level = level; - DynamaxLevel = 8; - FlawlessIVCount = 4; - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (pkm.FlawlessIVCount < FlawlessIVCount) - return false; - - return base.IsMatchExact(pkm, evo); - } - - // no downleveling, unlike all other raids - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) => pkm.Met_Level == Level; + Species = species; + Form = form; + Level = level; + DynamaxLevel = 8; + FlawlessIVCount = 4; } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk.FlawlessIVCount < FlawlessIVCount) + return false; + + return base.IsMatchExact(pk, evo); + } + + // no downleveling, unlike all other raids + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) => pk.Met_Level == Level; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs index 2aa1f2c92..369d01d6d 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8a.cs @@ -29,9 +29,9 @@ public EncounterStatic8a(ushort species, ushort form, byte level, byte h = NoSca Shiny = Shiny.Never; } - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) { - base.ApplyDetails(sav, criteria, pk); + base.ApplyDetails(tr, criteria, pk); var pa = (PA8)pk; @@ -64,12 +64,12 @@ protected override void SetPINGA(PKM pk, EncounterCriteria criteria) protected override void ApplyDetailsBall(PKM pk) => pk.Ball = Gift ? Ball : (int)Core.Ball.LAPoke; - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) + public override bool IsMatchExact(PKM pk, EvoCriteria evo) { - if (!base.IsMatchExact(pkm, evo)) + if (!base.IsMatchExact(pk, evo)) return false; - if (pkm is IScaledSize s) + if (pk is IScaledSize s) { if (HasFixedHeight && s.HeightScalar != HeightScalar) return false; @@ -77,63 +77,63 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo) return false; } - if (pkm is IAlpha a && a.IsAlpha != IsAlpha) + if (pk is IAlpha a && a.IsAlpha != IsAlpha) return false; return true; } - protected override bool IsMatchLocation(PKM pkm) + protected override bool IsMatchLocation(PKM pk) { - if (pkm is PK8) - return pkm.Met_Location == Locations.HOME_SWLA; - if (pkm is PB8 { Version: (int)GameVersion.PLA, Met_Location: Locations.HOME_SWLA }) + if (pk is PK8) + return pk.Met_Location == Locations.HOME_SWLA; + if (pk is PB8 { Version: (int)GameVersion.PLA, Met_Location: Locations.HOME_SWLA }) return true; - return base.IsMatchLocation(pkm); + return base.IsMatchLocation(pk); } - public override EncounterMatchRating GetMatchRating(PKM pkm) + public override EncounterMatchRating GetMatchRating(PKM pk) { - var result = GetMatchRatingInternal(pkm); - var orig = base.GetMatchRating(pkm); + var result = GetMatchRatingInternal(pk); + var orig = base.GetMatchRating(pk); return result > orig ? result : orig; } - private EncounterMatchRating GetMatchRatingInternal(PKM pkm) + private EncounterMatchRating GetMatchRatingInternal(PKM pk) { - if (Shiny != Shiny.Random && !Shiny.IsValid(pkm)) + if (Shiny != Shiny.Random && !Shiny.IsValid(pk)) return EncounterMatchRating.DeferredErrors; - if (Gift && pkm.Ball != Ball) + if (Gift && pk.Ball != Ball) return EncounterMatchRating.DeferredErrors; - var orig = base.GetMatchRating(pkm); + var orig = base.GetMatchRating(pk); if (orig is not EncounterMatchRating.Match) return orig; - if (!IsForcedMasteryCorrect(pkm)) + if (!IsForcedMasteryCorrect(pk)) return EncounterMatchRating.DeferredErrors; - if (IsAlpha && pkm is PA8 { AlphaMove: 0 }) + if (IsAlpha && pk is PA8 { AlphaMove: 0 }) return EncounterMatchRating.Deferred; return EncounterMatchRating.Match; } - public bool IsForcedMasteryCorrect(PKM pkm) + public bool IsForcedMasteryCorrect(PKM pk) { ushort alpha = 0; if (IsAlpha && Moves.Count != 0) { - if (pkm is PA8 pa && (alpha = pa.AlphaMove) != Moves[0]) + if (pk is PA8 pa && (alpha = pa.AlphaMove) != Moves[0]) return false; } - if (pkm is not IMoveShop8Mastery p) + if (pk is not IMoveShop8Mastery p) return true; const bool allowAlphaPurchaseBug = true; // Everything else Alpha is pre-1.1 - var level = pkm.Met_Level; + var level = pk.Met_Level; var index = PersonalTable.LA.GetFormIndex(Species, Form); var learn = Legal.LevelUpLA[index]; if (!p.IsValidPurchasedEncounter(learn, level, alpha, allowAlphaPurchaseBug)) diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs index ba637d31b..5d11a82c2 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic8b.cs @@ -1,127 +1,126 @@ using System; using static PKHeX.Core.StaticCorrelation8bRequirement; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 Static Encounter +/// +/// +public sealed record EncounterStatic8b : EncounterStatic, IStaticCorrelation8b { - /// - /// Generation 7 Static Encounter - /// - /// - public sealed record EncounterStatic8b : EncounterStatic, IStaticCorrelation8b + public override int Generation => 8; + + public bool Roaming { get; init; } + public override bool EggEncounter => EggLocation != Locations.Default8bNone; + + public EncounterStatic8b(GameVersion game) : base(game) => EggLocation = Locations.Default8bNone; + + protected override bool IsMatchLocation(PKM pk) { - public override int Generation => 8; + if (pk is PK8) + return Locations.IsValidMetBDSP(pk.Met_Location, pk.Version); + if (!Roaming) + return base.IsMatchLocation(pk); + return IsRoamingLocation(pk); + } - public bool Roaming { get; init; } - public override bool EggEncounter => EggLocation != Locations.Default8bNone; - - public EncounterStatic8b(GameVersion game) : base(game) => EggLocation = Locations.Default8bNone; - - protected override bool IsMatchLocation(PKM pkm) + private static bool IsRoamingLocation(PKM pk) + { + var location = pk.Met_Location; + foreach (var value in Roaming_MetLocation_BDSP) { - if (pkm is PK8) - return Locations.IsValidMetBDSP(pkm.Met_Location, pkm.Version); - if (!Roaming) - return base.IsMatchLocation(pkm); - return IsRoamingLocation(pkm); + if (value == location) + return true; } + return false; + } - private static bool IsRoamingLocation(PKM pkm) + public StaticCorrelation8bRequirement GetRequirement(PKM pk) => Roaming + ? MustHave + : MustNotHave; + + public bool IsStaticCorrelationCorrect(PKM pk) + { + return Roaming8bRNG.ValidateRoamingEncounter(pk, Shiny == Shiny.Random ? Shiny.FixedValue : Shiny, FlawlessIVCount); + } + + protected override bool IsMatchEggLocation(PKM pk) + { + if (pk is not PB8) { - var location = pkm.Met_Location; - foreach (var value in Roaming_MetLocation_BDSP) - { - if (value == location) - return true; - } - return false; - } - - public StaticCorrelation8bRequirement GetRequirement(PKM pk) => Roaming - ? MustHave - : MustNotHave; - - public bool IsStaticCorrelationCorrect(PKM pk) - { - return Roaming8bRNG.ValidateRoamingEncounter(pk, Shiny == Shiny.Random ? Shiny.FixedValue : Shiny, FlawlessIVCount); - } - - protected override bool IsMatchEggLocation(PKM pkm) - { - if (pkm is not PB8) - { - if (!EggEncounter) - return pkm.Egg_Location == 0; - - if (pkm is PK8) - { - if (EggLocation > 60000 && pkm.Egg_Location == Locations.HOME_SWSHBDSPEgg) - return true; - // >60000 can be reset to Link Trade (30001), then altered differently. - return Locations.IsValidMetBDSP(pkm.Egg_Location, pkm.Version); - } - - // Hatched - return pkm.Egg_Location == EggLocation || pkm.Egg_Location == Locations.LinkTrade6NPC; - } - - var eggloc = pkm.Egg_Location; if (!EggEncounter) - return eggloc == EggLocation; + return pk.Egg_Location == 0; - if (!pkm.IsEgg) // hatched - return eggloc == EggLocation || eggloc == Locations.LinkTrade6NPC; - - // Unhatched: - if (eggloc != EggLocation) - return false; - if (pkm.Met_Location is not (Locations.Default8bNone or Locations.LinkTrade6NPC)) - return false; - return true; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - pk.Met_Location = pk.Egg_Location = Locations.Default8bNone; - base.ApplyDetails(sav, criteria, pk); - var req = GetRequirement(pk); - if (req == MustHave) // Roamers + if (pk is PK8) { - var shiny = Shiny == Shiny.Random ? Shiny.FixedValue : Shiny; - Roaming8bRNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount); - } - else - { - var shiny = Shiny == Shiny.Never ? Shiny.Never : Shiny.Random; - Wild8bRNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount, Ability); + if (EggLocation > 60000 && pk.Egg_Location == Locations.HOME_SWSHBDSPEgg) + return true; + // >60000 can be reset to Link Trade (30001), then altered differently. + return Locations.IsValidMetBDSP(pk.Egg_Location, pk.Version); } + + // Hatched + return pk.Egg_Location == EggLocation || pk.Egg_Location == Locations.LinkTrade6NPC; } - protected override void SetMetData(PKM pk, int level, DateTime today) + var eggloc = pk.Egg_Location; + if (!EggEncounter) + return eggloc == EggLocation; + + if (!pk.IsEgg) // hatched + return eggloc == EggLocation || eggloc == Locations.LinkTrade6NPC; + + // Unhatched: + if (eggloc != EggLocation) + return false; + if (pk.Met_Location is not (Locations.Default8bNone or Locations.LinkTrade6NPC)) + return false; + return true; + } + + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + pk.Met_Location = pk.Egg_Location = Locations.Default8bNone; + base.ApplyDetails(tr, criteria, pk); + var req = GetRequirement(pk); + if (req == MustHave) // Roamers { - pk.Met_Level = level; - pk.Met_Location = !Roaming ? Location : Roaming_MetLocation_BDSP[0]; - pk.MetDate = today; + var shiny = Shiny == Shiny.Random ? Shiny.FixedValue : Shiny; + Roaming8bRNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount); } - - // defined by mvpoke in encounter data - private static readonly ushort[] Roaming_MetLocation_BDSP = + else { - 197, 201, 354, 355, 356, 357, 358, 359, 361, 362, 364, 365, 367, 373, 375, 377, - 378, 379, 383, 385, 392, 394, 395, 397, 400, 403, 404, 407, - 485, - }; + var shiny = Shiny == Shiny.Never ? Shiny.Never : Shiny.Random; + Wild8bRNG.ApplyDetails(pk, criteria, shiny, FlawlessIVCount, Ability); + } } - public interface IStaticCorrelation8b + protected override void SetMetData(PKM pk, int level, DateTime today) { - StaticCorrelation8bRequirement GetRequirement(PKM pk); - bool IsStaticCorrelationCorrect(PKM pk); + pk.Met_Level = level; + pk.Met_Location = !Roaming ? Location : Roaming_MetLocation_BDSP[0]; + pk.MetDate = today; } - public enum StaticCorrelation8bRequirement + // defined by mvpoke in encounter data + private static readonly ushort[] Roaming_MetLocation_BDSP = { - CanBeEither, - MustHave, - MustNotHave, - } + 197, 201, 354, 355, 356, 357, 358, 359, 361, 362, 364, 365, 367, 373, 375, 377, + 378, 379, 383, 385, 392, 394, 395, 397, 400, 403, 404, 407, + 485, + }; +} + +public interface IStaticCorrelation8b +{ + StaticCorrelation8bRequirement GetRequirement(PKM pk); + bool IsStaticCorrelationCorrect(PKM pk); +} + +public enum StaticCorrelation8bRequirement +{ + CanBeEither, + MustHave, + MustNotHave, } diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStaticShadow.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStaticShadow.cs index 105f5b263..a70baabe2 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStaticShadow.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStaticShadow.cs @@ -1,121 +1,120 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Shadow Pokémon Encounter found in +/// +/// +/// Initial Shadow Gauge value. +/// Initial Shadow Gauge value. +/// Team Specification with required , and Gender. +// ReSharper disable NotAccessedPositionalProperty.Global +public sealed record EncounterStaticShadow(GameVersion Version, byte ID, short Gauge, TeamLock[] Locks) : EncounterStatic(Version) { + // ReSharper restore NotAccessedPositionalProperty.Global + public override int Generation => 3; + /// - /// Shadow Pokémon Encounter found in + /// Originates from the EReader scans (Japanese Only) /// - /// - /// Initial Shadow Gauge value. - /// Initial Shadow Gauge value. - /// Team Specification with required , and Gender. - // ReSharper disable NotAccessedPositionalProperty.Global - public sealed record EncounterStaticShadow(GameVersion Version, byte ID, short Gauge, TeamLock[] Locks) : EncounterStatic(Version) + public bool EReader => ReferenceEquals(IVs, EReaderEmpty); + + public static readonly IReadOnlyList EReaderEmpty = new[] {0,0,0,0,0,0}; + + protected override bool IsMatchLocation(PKM pk) { - // ReSharper restore NotAccessedPositionalProperty.Global - public override int Generation => 3; + if (pk.Format != 3) + return true; // transfer location verified later - /// - /// Originates from the EReader scans (Japanese Only) - /// - public bool EReader => ReferenceEquals(IVs, EReaderEmpty); + var met = pk.Met_Location; + if (met == Location) + return true; - public static readonly IReadOnlyList EReaderEmpty = new[] {0,0,0,0,0,0}; + // XD can re-battle with Miror B + // Realgam Tower, Rock, Oasis, Cave, Pyrite Town + return Version == GameVersion.XD && met is (59 or 90 or 91 or 92 or 113); + } - protected override bool IsMatchLocation(PKM pkm) + protected override bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (pk.Format != 3) // Met Level lost on PK3=>PK4 + return Level <= evo.LevelMax; + + return pk.Met_Level == Level; + } + + protected override void ApplyDetails(ITrainerInfo tr, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(tr, criteria, pk); + ((IRibbonSetEvent3)pk).RibbonNational = true; + } + + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + if (!EReader) + SetPINGA_Regular(pk, criteria); + else + SetPINGA_EReader(pk); + } + + private void SetPINGA_Regular(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(-1, pi); + int nature = (int)criteria.GetNature(Nature.Random); + int ability = criteria.GetAbilityFromNumber(0); + + // Ensure that any generated specimen has valid Shadow Locks + // This can be kinda slow, depending on how many locks / how strict they are. + // Cancel this operation if too many attempts are made to prevent infinite loops. + int ctr = 0; + const int max = 100_000; + do { - if (pkm.Format != 3) - return true; // transfer location verified later - - var met = pkm.Met_Location; - if (met == Location) - return true; - - // XD can re-battle with Miror B - // Realgam Tower, Rock, Oasis, Cave, Pyrite Town - return Version == GameVersion.XD && met is (59 or 90 or 91 or 92 or 113); + PIDGenerator.SetRandomWildPID(pk, 3, nature, ability, gender, PIDType.CXD); + var pidiv = MethodFinder.Analyze(pk); + var result = LockFinder.IsAllShadowLockValid(this, pidiv, pk); + if (result) + break; } - - protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - if (pkm.Format != 3) // Met Level lost on PK3=>PK4 - return Level <= evo.LevelMax; - - return pkm.Met_Level == Level; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - ((IRibbonSetEvent3)pk).RibbonNational = true; - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - if (!EReader) - SetPINGA_Regular(pk, criteria); - else - SetPINGA_EReader(pk); - } - - private void SetPINGA_Regular(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(-1, pi); - int nature = (int)criteria.GetNature(Nature.Random); - int ability = criteria.GetAbilityFromNumber(0); - - // Ensure that any generated specimen has valid Shadow Locks - // This can be kinda slow, depending on how many locks / how strict they are. - // Cancel this operation if too many attempts are made to prevent infinite loops. - int ctr = 0; - const int max = 100_000; - do - { - PIDGenerator.SetRandomWildPID(pk, 3, nature, ability, gender, PIDType.CXD); - var pidiv = MethodFinder.Analyze(pk); - var result = LockFinder.IsAllShadowLockValid(this, pidiv, pk); - if (result) - break; - } - while (++ctr <= max); + while (++ctr <= max); #if DEBUG - System.Diagnostics.Debug.Assert(ctr < 100_000); + System.Diagnostics.Debug.Assert(ctr < 100_000); #endif - } + } - private void SetPINGA_EReader(PKM pk) + private void SetPINGA_EReader(PKM pk) + { + // E-Reader have all IVs == 0 + for (int i = 0; i < IVs.Count; i++) + pk.SetIV(i, 0); + + // All E-Reader shadows are actually nature/gender locked. + var locked = Locks[0].Locks[^1]; + var (nature, gender) = locked.GetLock; + + // Ensure that any generated specimen has valid Shadow Locks + // This can be kinda slow, depending on how many locks / how strict they are. + // Cancel this operation if too many attempts are made to prevent infinite loops. + int ctr = 0; + const int max = 100_000; + do { - // E-Reader have all IVs == 0 - for (int i = 0; i < IVs.Count; i++) - pk.SetIV(i, 0); - - // All E-Reader shadows are actually nature/gender locked. - var locked = Locks[0].Locks[^1]; - var (nature, gender) = locked.GetLock; - - // Ensure that any generated specimen has valid Shadow Locks - // This can be kinda slow, depending on how many locks / how strict they are. - // Cancel this operation if too many attempts are made to prevent infinite loops. - int ctr = 0; - const int max = 100_000; - do - { - var seed = Util.Rand32(); - PIDGenerator.SetValuesFromSeedXDRNG_EReader(pk, seed); - if (pk.Nature != nature || pk.Gender != gender) - continue; - var pidiv = new PIDIV(PIDType.CXD, seed); - var result = LockFinder.IsAllShadowLockValid(this, pidiv, pk); - if (result) - break; - } - while (++ctr <= max); + var seed = Util.Rand32(); + PIDGenerator.SetValuesFromSeedXDRNG_EReader(pk, seed); + if (pk.Nature != nature || pk.Gender != gender) + continue; + var pidiv = new PIDIV(PIDType.CXD, seed); + var result = LockFinder.IsAllShadowLockValid(this, pidiv, pk); + if (result) + break; + } + while (++ctr <= max); #if DEBUG - System.Diagnostics.Debug.Assert(ctr < 100_000); + System.Diagnostics.Debug.Assert(ctr < 100_000); #endif - } } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs index 8f4227846..c853685a2 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs @@ -2,262 +2,261 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Trade Encounter data +/// +/// +/// Trade data is fixed level in all cases except for the first few generations of games. +/// +public abstract record EncounterTrade(GameVersion Version) : IEncounterable, IMoveset, IEncounterMatch { - /// - /// Trade Encounter data - /// - /// - /// Trade data is fixed level in all cases except for the first few generations of games. - /// - public abstract record EncounterTrade(GameVersion Version) : IEncounterable, IMoveset, IEncounterMatch + public int Species { get; init; } + public int Form { get; init; } + public byte Level { get; init; } + public virtual byte LevelMin => Level; + public byte LevelMax => 100; + public abstract int Generation { get; } + + public int CurrentLevel { get; init; } = -1; + public abstract int Location { get; } + + public AbilityPermission Ability { get; init; } + public Nature Nature { get; init; } = Nature.Random; + public virtual Shiny Shiny => Shiny.Never; + public sbyte Gender { get; init; } = -1; + + public sbyte OTGender { get; init; } = -1; + public bool IsNicknamed { get; init; } = true; + public bool EvolveOnTrade { get; init; } + public byte Ball { get; init; } = 4; + + public int EggLocation { get; init; } + + public ushort TID { get; init; } + public ushort SID { get; init; } + + public IReadOnlyList Moves { get; init; } = Array.Empty(); + public IReadOnlyList IVs { get; init; } = Array.Empty(); + + public Ball FixedBall => (Ball)Ball; + public bool EggEncounter => false; + + public int TID7 { - public int Species { get; init; } - public int Form { get; init; } - public byte Level { get; init; } - public virtual byte LevelMin => Level; - public byte LevelMax => 100; - public abstract int Generation { get; } - - public int CurrentLevel { get; init; } = -1; - public abstract int Location { get; } - - public AbilityPermission Ability { get; init; } - public Nature Nature { get; init; } = Nature.Random; - public virtual Shiny Shiny => Shiny.Never; - public sbyte Gender { get; init; } = -1; - - public sbyte OTGender { get; init; } = -1; - public bool IsNicknamed { get; init; } = true; - public bool EvolveOnTrade { get; init; } - public byte Ball { get; init; } = 4; - - public int EggLocation { get; init; } - - public ushort TID { get; init; } - public ushort SID { get; init; } - - public IReadOnlyList Moves { get; init; } = Array.Empty(); - public IReadOnlyList IVs { get; init; } = Array.Empty(); - - public Ball FixedBall => (Ball)Ball; - public bool EggEncounter => false; - - public int TID7 + init { - init - { - TID = (ushort) value; - SID = (ushort)(value >> 16); - } + TID = (ushort) value; + SID = (ushort)(value >> 16); } - - private const string _name = "In-game Trade"; - public string Name => _name; - public string LongName => _name; - public bool IsShiny => Shiny.IsShiny(); - - public IReadOnlyList Nicknames { get; internal set; } = Array.Empty(); - public IReadOnlyList TrainerNames { get; internal set; } = Array.Empty(); - public string GetNickname(int language) => (uint)language < Nicknames.Count ? Nicknames[language] : string.Empty; - public string GetOT(int language) => (uint)language < TrainerNames.Count ? TrainerNames[language] : string.Empty; - public bool HasNickname => Nicknames.Count != 0 && IsNicknamed; - public bool HasTrainerName => TrainerNames.Count != 0; - - public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted); - - public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - var pk = EntityBlank.GetBlank(Generation, Version); - sav.ApplyTo(pk); - - ApplyDetails(sav, criteria, pk); - return pk; - } - - protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - var version = this.GetCompatibleVersion((GameVersion)sav.Game); - int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)sav.Language, version); - int level = CurrentLevel > 0 ? CurrentLevel : LevelMin; - if (level == 0) - level = Math.Max((byte)1, LevelMin); - - int species = Species; - if (EvolveOnTrade) - species++; - - pk.EncryptionConstant = Util.Rand32(); - pk.Species = species; - pk.Form = Form; - pk.Language = lang; - pk.OT_Name = pk.Format == 1 ? StringConverter12.G1TradeOTStr : HasTrainerName ? GetOT(lang) : sav.OT; - pk.OT_Gender = HasTrainerName ? Math.Max(0, (int)OTGender) : sav.Gender; - pk.SetNickname(HasNickname ? GetNickname(lang) : string.Empty); - - pk.CurrentLevel = level; - pk.Version = (int) version; - pk.TID = TID; - pk.SID = SID; - pk.Ball = Ball; - pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; - - SetPINGA(pk, criteria); - SetMoves(pk, version, level); - - var time = DateTime.Now; - if (pk.Format != 2 || version == GameVersion.C) - { - SetMetData(pk, level, Location, time); - } - else - { - pk.OT_Gender = 0; - } - - if (EggLocation != 0) - SetEggMetData(pk, time); - - if (pk.Format < 6) - return; - - sav.ApplyHandlingTrainerInfo(pk, force: true); - pk.SetRandomEC(); - - if (pk is IScaledSize s) - { - s.HeightScalar = PokeSizeUtil.GetRandomScalar(); - s.WeightScalar = PokeSizeUtil.GetRandomScalar(); - } - if (pk is PK6 pk6) - pk6.SetRandomMemory6(); - } - - protected virtual void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(Gender, pi); - int nature = (int)criteria.GetNature(Nature); - int ability = criteria.GetAbilityFromNumber(Ability); - - PIDGenerator.SetRandomWildPID(pk, Generation, nature, ability, gender); - pk.Nature = pk.StatNature = nature; - pk.Gender = gender; - pk.RefreshAbility(ability); - - SetIVs(pk); - } - - protected void SetIVs(PKM pk) - { - if (IVs.Count != 0) - pk.SetRandomIVs((int[])IVs, 0); - else - pk.SetRandomIVs(flawless: 3); - } - - private void SetMoves(PKM pk, GameVersion version, int level) - { - var moves = Moves.Count != 0 ? Moves : MoveLevelUp.GetEncounterMoves(pk, level, version); - if (pk.Format == 1 && moves.All(z => z == 0)) - moves = ((PersonalInfoG1)PersonalTable.RB[Species]).Moves; - pk.SetMoves((int[])moves); - pk.SetMaximumPPCurrent((int[])moves); - } - - private void SetEggMetData(PKM pk, DateTime time) - { - pk.Egg_Location = EggLocation; - pk.EggMetDate = time; - } - - private static void SetMetData(PKM pk, int level, int location, DateTime time) - { - pk.Met_Level = level; - pk.Met_Location = location; - pk.MetDate = time; - } - - public virtual bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (IVs.Count != 0) - { - if (!Legal.GetIsFixedIVSequenceValidSkipRand((int[])IVs, pkm)) - return false; - } - - if (!IsMatchNatureGenderShiny(pkm)) - return false; - if (TID != pkm.TID) - return false; - if (SID != pkm.SID) - return false; - - if (!IsMatchLevel(pkm, evo)) - return false; - - if (CurrentLevel != -1 && CurrentLevel > pkm.CurrentLevel) - return false; - - if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pkm.Form, pkm.Format)) - return false; - if (OTGender != -1 && OTGender != pkm.OT_Gender) - return false; - if (!IsMatchEggLocation(pkm)) - return false; - // if (z.Ability == 4 ^ pkm.AbilityNumber == 4) // defer to Ability - // continue; - if (!Version.Contains((GameVersion)pkm.Version)) - return false; - - return true; - } - - protected virtual bool IsMatchEggLocation(PKM pkm) - { - var expect = EggLocation; - if (pkm is PB8 && expect is 0) - expect = Locations.Default8bNone; - return pkm.Egg_Location == expect; - } - - private bool IsMatchLevel(PKM pkm, EvoCriteria evo) - { - if (!pkm.HasOriginalMetLocation) - return evo.LevelMax >= Level; - - if (Location != pkm.Met_Location) - return false; - - if (pkm.Format < 5) - return evo.LevelMax >= Level; - - return pkm.Met_Level == Level; - } - - protected virtual bool IsMatchNatureGenderShiny(PKM pkm) - { - if (!Shiny.IsValid(pkm)) - return false; - if (Gender != -1 && Gender != pkm.Gender) - return false; - - if (Nature != Nature.Random && pkm.Nature != (int)Nature) - return false; - - return true; - } - - public EncounterMatchRating GetMatchRating(PKM pkm) - { - if (IsMatchPartial(pkm)) - return EncounterMatchRating.PartialMatch; - if (IsMatchDeferred(pkm)) - return EncounterMatchRating.Deferred; - return EncounterMatchRating.Match; - } - - protected virtual bool IsMatchDeferred(PKM pkm) => false; - protected virtual bool IsMatchPartial(PKM pkm) => false; } + + private const string _name = "In-game Trade"; + public string Name => _name; + public string LongName => _name; + public bool IsShiny => Shiny.IsShiny(); + + public IReadOnlyList Nicknames { get; internal set; } = Array.Empty(); + public IReadOnlyList TrainerNames { get; internal set; } = Array.Empty(); + public string GetNickname(int language) => (uint)language < Nicknames.Count ? Nicknames[language] : string.Empty; + public string GetOT(int language) => (uint)language < TrainerNames.Count ? TrainerNames[language] : string.Empty; + public bool HasNickname => Nicknames.Count != 0 && IsNicknamed; + public bool HasTrainerName => TrainerNames.Count != 0; + + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + + public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var pk = EntityBlank.GetBlank(Generation, Version); + tr.ApplyTo(pk); + + ApplyDetails(tr, criteria, pk); + return pk; + } + + protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + var version = this.GetCompatibleVersion((GameVersion)sav.Game); + int lang = (int)Language.GetSafeLanguage(Generation, (LanguageID)sav.Language, version); + int level = CurrentLevel > 0 ? CurrentLevel : LevelMin; + if (level == 0) + level = Math.Max((byte)1, LevelMin); + + int species = Species; + if (EvolveOnTrade) + species++; + + pk.EncryptionConstant = Util.Rand32(); + pk.Species = species; + pk.Form = Form; + pk.Language = lang; + pk.OT_Name = pk.Format == 1 ? StringConverter12.G1TradeOTStr : HasTrainerName ? GetOT(lang) : sav.OT; + pk.OT_Gender = HasTrainerName ? Math.Max(0, (int)OTGender) : sav.Gender; + pk.SetNickname(HasNickname ? GetNickname(lang) : string.Empty); + + pk.CurrentLevel = level; + pk.Version = (int) version; + pk.TID = TID; + pk.SID = SID; + pk.Ball = Ball; + pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; + + SetPINGA(pk, criteria); + SetMoves(pk, version, level); + + var time = DateTime.Now; + if (pk.Format != 2 || version == GameVersion.C) + { + SetMetData(pk, level, Location, time); + } + else + { + pk.OT_Gender = 0; + } + + if (EggLocation != 0) + SetEggMetData(pk, time); + + if (pk.Format < 6) + return; + + sav.ApplyHandlingTrainerInfo(pk, force: true); + pk.SetRandomEC(); + + if (pk is IScaledSize s) + { + s.HeightScalar = PokeSizeUtil.GetRandomScalar(); + s.WeightScalar = PokeSizeUtil.GetRandomScalar(); + } + if (pk is PK6 pk6) + pk6.SetRandomMemory6(); + } + + protected virtual void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(Gender, pi); + int nature = (int)criteria.GetNature(Nature); + int ability = criteria.GetAbilityFromNumber(Ability); + + PIDGenerator.SetRandomWildPID(pk, Generation, nature, ability, gender); + pk.Nature = pk.StatNature = nature; + pk.Gender = gender; + pk.RefreshAbility(ability); + + SetIVs(pk); + } + + protected void SetIVs(PKM pk) + { + if (IVs.Count != 0) + pk.SetRandomIVs((int[])IVs, 0); + else + pk.SetRandomIVs(flawless: 3); + } + + private void SetMoves(PKM pk, GameVersion version, int level) + { + var moves = Moves.Count != 0 ? Moves : MoveLevelUp.GetEncounterMoves(pk, level, version); + if (pk.Format == 1 && moves.All(z => z == 0)) + moves = ((PersonalInfoG1)PersonalTable.RB[Species]).Moves; + pk.SetMoves((int[])moves); + pk.SetMaximumPPCurrent((int[])moves); + } + + private void SetEggMetData(PKM pk, DateTime time) + { + pk.Egg_Location = EggLocation; + pk.EggMetDate = time; + } + + private static void SetMetData(PKM pk, int level, int location, DateTime time) + { + pk.Met_Level = level; + pk.Met_Location = location; + pk.MetDate = time; + } + + public virtual bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (IVs.Count != 0) + { + if (!Legal.GetIsFixedIVSequenceValidSkipRand((int[])IVs, pk)) + return false; + } + + if (!IsMatchNatureGenderShiny(pk)) + return false; + if (TID != pk.TID) + return false; + if (SID != pk.SID) + return false; + + if (!IsMatchLevel(pk, evo)) + return false; + + if (CurrentLevel != -1 && CurrentLevel > pk.CurrentLevel) + return false; + + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, pk.Format)) + return false; + if (OTGender != -1 && OTGender != pk.OT_Gender) + return false; + if (!IsMatchEggLocation(pk)) + return false; + // if (z.Ability == 4 ^ pk.AbilityNumber == 4) // defer to Ability + // continue; + if (!Version.Contains((GameVersion)pk.Version)) + return false; + + return true; + } + + protected virtual bool IsMatchEggLocation(PKM pk) + { + var expect = EggLocation; + if (pk is PB8 && expect is 0) + expect = Locations.Default8bNone; + return pk.Egg_Location == expect; + } + + private bool IsMatchLevel(PKM pk, EvoCriteria evo) + { + if (!pk.HasOriginalMetLocation) + return evo.LevelMax >= Level; + + if (Location != pk.Met_Location) + return false; + + if (pk.Format < 5) + return evo.LevelMax >= Level; + + return pk.Met_Level == Level; + } + + protected virtual bool IsMatchNatureGenderShiny(PKM pk) + { + if (!Shiny.IsValid(pk)) + return false; + if (Gender != -1 && Gender != pk.Gender) + return false; + + if (Nature != Nature.Random && pk.Nature != (int)Nature) + return false; + + return true; + } + + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + if (IsMatchDeferred(pk)) + return EncounterMatchRating.Deferred; + return EncounterMatchRating.Match; + } + + protected virtual bool IsMatchDeferred(PKM pk) => false; + protected virtual bool IsMatchPartial(PKM pk) => false; } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade1.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade1.cs index 11d148483..80690e8de 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade1.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade1.cs @@ -1,138 +1,137 @@ using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Trade Encounter data with a fixed Catch Rate +/// +/// +/// Generation 1 specific value used in detecting unmodified/un-traded Generation 1 Trade Encounter data. +/// Species & Minimum level (legal) possible to acquire at. +/// +public sealed record EncounterTrade1 : EncounterTradeGB { - /// - /// Trade Encounter data with a fixed Catch Rate - /// - /// - /// Generation 1 specific value used in detecting unmodified/un-traded Generation 1 Trade Encounter data. - /// Species & Minimum level (legal) possible to acquire at. - /// - public sealed record EncounterTrade1 : EncounterTradeGB + public override int Generation => 1; + public override byte LevelMin => CanObtainMinGSC() ? LevelMinGSC : LevelMinRBY; + + private readonly byte LevelMinRBY; + private readonly byte LevelMinGSC; + public override int Location => 0; + public override Shiny Shiny => Shiny.Random; + + public EncounterTrade1(int species, GameVersion game, byte rby, byte gsc) : base(species, gsc, game) { - public override int Generation => 1; - public override byte LevelMin => CanObtainMinGSC() ? LevelMinGSC : LevelMinRBY; + TrainerNames = StringConverter12.G1TradeOTName; - private readonly byte LevelMinRBY; - private readonly byte LevelMinGSC; - public override int Location => 0; - public override Shiny Shiny => Shiny.Random; + LevelMinRBY = rby; + LevelMinGSC = gsc; + } - public EncounterTrade1(int species, GameVersion game, byte rby, byte gsc) : base(species, gsc, game) + public EncounterTrade1(int species, GameVersion game, byte rby) : this(species, game, rby, rby) { } + + public byte GetInitialCatchRate() + { + var pt = Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; + return (byte)pt[Species].CatchRate; + } + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + var pk1 = (PK1)pk; + pk1.Catch_Rate = GetInitialCatchRate(); + } + + internal bool IsNicknameValid(PKM pk) + { + var nick = pk.Nickname; + if (pk.Format <= 2) + return Nicknames.Contains(nick); + + // Converted string 1/2->7 to language specific value + // Nicknames can be from any of the languages it can trade between. + int lang = pk.Language; + if (lang == 1) { - TrainerNames = StringConverter12.G1TradeOTName; - - LevelMinRBY = rby; - LevelMinGSC = gsc; + // Special consideration for Hiragana strings that are transferred + if (Version == GameVersion.YW && Species == (int)Core.Species.Dugtrio) + return nick == "ぐりお"; + return nick == Nicknames[1]; } - public EncounterTrade1(int species, GameVersion game, byte rby) : this(species, game, rby, rby) { } + return GetNicknameIndex(nick) >= 2; + } - public byte GetInitialCatchRate() + internal bool IsTrainerNameValid(PKM pk) + { + string ot = pk.OT_Name; + if (pk.Format <= 2) + return ot == StringConverter12.G1TradeOTStr; + + // Converted string 1/2->7 to language specific value + int lang = pk.Language; + var tr = GetOT(lang); + return ot == tr; + } + + private int GetNicknameIndex(string nickname) + { + var nn = Nicknames; + for (int i = 0; i < nn.Count; i++) { - var pt = Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; - return (byte)pt[Species].CatchRate; + if (nn[i] == nickname) + return i; } + return -1; + } - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + private bool CanObtainMinGSC() + { + if (!ParseSettings.AllowGen1Tradeback) + return false; + if (Version == GameVersion.BU && EvolveOnTrade) + return ParseSettings.AllowGBCartEra; + return true; + } + + private bool IsMatchLevel(PKM pk, int lvl) + { + if (pk is not PK1) + return lvl >= LevelMinGSC; + return lvl >= LevelMin; + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsMatchLevel(pk, pk.CurrentLevel)) // minimum required level + return false; + + if (Version == GameVersion.BU) { - base.ApplyDetails(sav, criteria, pk); - var pk1 = (PK1)pk; - pk1.Catch_Rate = GetInitialCatchRate(); - } - - internal bool IsNicknameValid(PKM pkm) - { - var nick = pkm.Nickname; - if (pkm.Format <= 2) - return Nicknames.Contains(nick); - - // Converted string 1/2->7 to language specific value - // Nicknames can be from any of the languages it can trade between. - int lang = pkm.Language; - if (lang == 1) - { - // Special consideration for Hiragana strings that are transferred - if (Version == GameVersion.YW && Species == (int)Core.Species.Dugtrio) - return nick == "ぐりお"; - return nick == Nicknames[1]; - } - - return GetNicknameIndex(nick) >= 2; - } - - internal bool IsTrainerNameValid(PKM pkm) - { - string ot = pkm.OT_Name; - if (pkm.Format <= 2) - return ot == StringConverter12.G1TradeOTStr; - - // Converted string 1/2->7 to language specific value - int lang = pkm.Language; - var tr = GetOT(lang); - return ot == tr; - } - - private int GetNicknameIndex(string nickname) - { - var nn = Nicknames; - for (int i = 0; i < nn.Count; i++) - { - if (nn[i] == nickname) - return i; - } - return -1; - } - - private bool CanObtainMinGSC() - { - if (!ParseSettings.AllowGen1Tradeback) + // Encounters with this version have to originate from the Japanese Blue game. + if (!pk.Japanese) return false; - if (Version == GameVersion.BU && EvolveOnTrade) - return ParseSettings.AllowGBCartEra; + // Stadium 2 can transfer from GSC->RBY without a "Trade", thus allowing un-evolved outsiders + if (EvolveOnTrade && !ParseSettings.AllowGBCartEra && pk.CurrentLevel < LevelMinRBY) + return false; + } + + return true; + } + + protected override bool IsMatchPartial(PKM pk) + { + if (!IsTrainerNameValid(pk)) return true; - } - - private bool IsMatchLevel(PKM pkm, int lvl) - { - if (pkm is not PK1) - return lvl >= LevelMinGSC; - return lvl >= LevelMin; - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!IsMatchLevel(pkm, pkm.CurrentLevel)) // minimum required level - return false; - - if (Version == GameVersion.BU) - { - // Encounters with this version have to originate from the Japanese Blue game. - if (!pkm.Japanese) - return false; - // Stadium 2 can transfer from GSC->RBY without a "Trade", thus allowing un-evolved outsiders - if (EvolveOnTrade && !ParseSettings.AllowGBCartEra && pkm.CurrentLevel < LevelMinRBY) - return false; - } - + if (!IsNicknameValid(pk)) return true; - } - protected override bool IsMatchPartial(PKM pkm) - { - if (!IsTrainerNameValid(pkm)) - return true; - if (!IsNicknameValid(pkm)) - return true; + if (ParseSettings.AllowGen1Tradeback) + return false; + if (pk is not PK1 pk1) + return false; - if (ParseSettings.AllowGen1Tradeback) - return false; - if (pkm is not PK1 pk1) - return false; - - var req = GetInitialCatchRate(); - return req != pk1.Catch_Rate; - } + var req = GetInitialCatchRate(); + return req != pk1.Catch_Rate; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade2.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade2.cs index 2d3be43f2..1dd4d54b3 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade2.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade2.cs @@ -1,99 +1,98 @@ using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 2 Trade Encounter +/// +/// +public sealed record EncounterTrade2 : EncounterTradeGB { - /// - /// Generation 2 Trade Encounter - /// - /// - public sealed record EncounterTrade2 : EncounterTradeGB + public override int Generation => 2; + public override int Location => Locations.LinkTrade2NPC; + + public EncounterTrade2(ushort species, byte level, ushort tid) : base(species, level, GameVersion.GSC) { - public override int Generation => 2; - public override int Location => Locations.LinkTrade2NPC; + TID = tid; + } - public EncounterTrade2(ushort species, byte level, ushort tid) : base(species, level, GameVersion.GSC) - { - TID = tid; - } + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (Level > pk.CurrentLevel) // minimum required level + return false; + if (TID != pk.TID) + return false; - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) + if (pk.Format <= 2) { - if (Level > pkm.CurrentLevel) // minimum required level + if (Gender >= 0 && Gender != pk.Gender) return false; - if (TID != pkm.TID) + if (IVs.Count != 0 && !Legal.GetIsFixedIVSequenceValidNoRand((int[])IVs, pk)) return false; - - if (pkm.Format <= 2) - { - if (Gender >= 0 && Gender != pkm.Gender) - return false; - if (IVs.Count != 0 && !Legal.GetIsFixedIVSequenceValidNoRand((int[])IVs, pkm)) - return false; - if (pkm.Format == 2 && pkm.Met_Location is not (0 or 126)) - return false; - } - - if (!IsValidTradeOTGender(pkm)) + if (pk.Format == 2 && pk.Met_Location is not (0 or 126)) return false; - return IsValidTradeOTName(pkm); } - private bool IsValidTradeOTGender(PKM pkm) + if (!IsValidTradeOTGender(pk)) + return false; + return IsValidTradeOTName(pk); + } + + private bool IsValidTradeOTGender(PKM pk) + { + if (OTGender == 1) { - if (OTGender == 1) - { - // Female, can be cleared if traded to RBY (clears met location) - if (pkm.Format <= 2) - return pkm.OT_Gender == (pkm.Met_Location != 0 ? 1 : 0); - return pkm.OT_Gender == 0 || !pkm.VC1; // require male except if transferred from GSC - } - return pkm.OT_Gender == 0; + // Female, can be cleared if traded to RBY (clears met location) + if (pk.Format <= 2) + return pk.OT_Gender == (pk.Met_Location != 0 ? 1 : 0); + return pk.OT_Gender == 0 || !pk.VC1; // require male except if transferred from GSC } + return pk.OT_Gender == 0; + } - private bool IsValidTradeOTName(PKM pkm) + private bool IsValidTradeOTName(PKM pk) + { + var OT = pk.OT_Name; + if (pk.Japanese) + return GetOT((int)LanguageID.Japanese) == OT; + if (pk.Korean) + return GetOT((int)LanguageID.Korean) == OT; + + var lang = GetInternationalLanguageID(OT); + if (pk.Format < 7) + return lang != -1; + + switch (Species) { - var OT = pkm.OT_Name; - if (pkm.Japanese) - return GetOT((int)LanguageID.Japanese) == OT; - if (pkm.Korean) - return GetOT((int)LanguageID.Korean) == OT; + case (int)Voltorb when pk.Language == (int)LanguageID.French: + if (lang == (int)LanguageID.Spanish) + return false; + if (lang != -1) + return true; + return OT == "FALCçN"; // FALCÁN - var lang = GetInternationalLanguageID(OT); - if (pkm.Format < 7) - return lang != -1; + case (int)Shuckle when pk.Language == (int)LanguageID.French: + if (lang == (int)LanguageID.Spanish) + return false; + if (lang != -1) + return true; + return OT == "MANôA"; // MANÍA - switch (Species) - { - case (int)Voltorb when pkm.Language == (int)LanguageID.French: - if (lang == (int)LanguageID.Spanish) - return false; - if (lang != -1) - return true; - return OT == "FALCçN"; // FALCÁN - - case (int)Shuckle when pkm.Language == (int)LanguageID.French: - if (lang == (int)LanguageID.Spanish) - return false; - if (lang != -1) - return true; - return OT == "MANôA"; // MANÍA - - default: return lang != -1; - } - } - - private int GetInternationalLanguageID(string OT) - { - const int start = (int)LanguageID.English; - const int end = (int)LanguageID.Spanish; - - var tr = TrainerNames; - for (int i = start; i <= end; i++) - { - if (tr[i] == OT) - return i; - } - return -1; + default: return lang != -1; } } + + private int GetInternationalLanguageID(string OT) + { + const int start = (int)LanguageID.English; + const int end = (int)LanguageID.Spanish; + + var tr = TrainerNames; + for (int i = start; i <= end; i++) + { + if (tr[i] == OT) + return i; + } + return -1; + } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade3.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade3.cs index 50f4ee8a6..9f97a3b99 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade3.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade3.cs @@ -1,94 +1,93 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 Trade Encounter +/// +/// +public sealed record EncounterTrade3 : EncounterTrade, IContestStats { + public override int Generation => 3; + public override int Location => Locations.LinkTrade3NPC; + /// - /// Generation 3 Trade Encounter + /// Fixed value the encounter must have. /// - /// - public sealed record EncounterTrade3 : EncounterTrade, IContestStats + public readonly uint PID; + + public override Shiny Shiny => Shiny.FixedValue; + + public byte CNT_Cool { get; private init; } + public byte CNT_Beauty { get; private init; } + public byte CNT_Cute { get; private init; } + public byte CNT_Smart { get; private init; } + public byte CNT_Tough { get; private init; } + public byte CNT_Sheen { get; private init; } + + public IReadOnlyList Contest { - public override int Generation => 3; - public override int Location => Locations.LinkTrade3NPC; - - /// - /// Fixed value the encounter must have. - /// - public readonly uint PID; - - public override Shiny Shiny => Shiny.FixedValue; - - public byte CNT_Cool { get; private init; } - public byte CNT_Beauty { get; private init; } - public byte CNT_Cute { get; private init; } - public byte CNT_Smart { get; private init; } - public byte CNT_Tough { get; private init; } - public byte CNT_Sheen { get; private init; } - - public IReadOnlyList Contest + init { - init - { - CNT_Cool = value[0]; - CNT_Beauty = value[1]; - CNT_Cute = value[2]; - CNT_Smart = value[3]; - CNT_Tough = value[4]; - CNT_Sheen = value[5]; - } - } - - public EncounterTrade3(GameVersion game, uint pid, ushort species, byte level) : base(game) - { - PID = pid; - Species = species; - Level = level; - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!base.IsMatchExact(pkm, evo)) - return false; - - if (pkm is IContestStats s && s.IsContestBelow(this)) - return false; - - return true; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pk3 = (PK3) pk; - - // Italian LG Jynx untranslated from English name - if (Species == (int)Core.Species.Jynx && pk3.Version == (int)GameVersion.LG && pk3.Language == (int)LanguageID.Italian) - { - pk3.OT_Name = GetOT((int)LanguageID.English); - pk3.SetNickname(GetNickname((int)LanguageID.English)); - } - - this.CopyContestStatsTo((PK3)pk); - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pi); - int nature = (int)criteria.GetNature(Nature); - int ability = criteria.GetAbilityFromNumber(Ability); - - pk.PID = PID; - pk.Nature = nature; - pk.Gender = gender; - pk.RefreshAbility(ability); - - SetIVs(pk); - } - - protected override bool IsMatchNatureGenderShiny(PKM pkm) - { - return PID == pkm.EncryptionConstant; + CNT_Cool = value[0]; + CNT_Beauty = value[1]; + CNT_Cute = value[2]; + CNT_Smart = value[3]; + CNT_Tough = value[4]; + CNT_Sheen = value[5]; } } + + public EncounterTrade3(GameVersion game, uint pid, ushort species, byte level) : base(game) + { + PID = pid; + Species = species; + Level = level; + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!base.IsMatchExact(pk, evo)) + return false; + + if (pk is IContestStats s && s.IsContestBelow(this)) + return false; + + return true; + } + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + var pk3 = (PK3) pk; + + // Italian LG Jynx untranslated from English name + if (Species == (int)Core.Species.Jynx && pk3.Version == (int)GameVersion.LG && pk3.Language == (int)LanguageID.Italian) + { + pk3.OT_Name = GetOT((int)LanguageID.English); + pk3.SetNickname(GetNickname((int)LanguageID.English)); + } + + this.CopyContestStatsTo((PK3)pk); + } + + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pi); + int nature = (int)criteria.GetNature(Nature); + int ability = criteria.GetAbilityFromNumber(Ability); + + pk.PID = PID; + pk.Nature = nature; + pk.Gender = gender; + pk.RefreshAbility(ability); + + SetIVs(pk); + } + + protected override bool IsMatchNatureGenderShiny(PKM pk) + { + return PID == pk.EncryptionConstant; + } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade4.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade4.cs index 3a1c12765..625ecefe4 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade4.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade4.cs @@ -1,168 +1,167 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 Trade Encounter +/// +/// +public abstract record EncounterTrade4(GameVersion Version) : EncounterTrade(Version) +{ + public sealed override int Generation => 4; + + protected static readonly string[] RanchOTNames = { string.Empty, "ユカリ", "Hayley", "EULALIE", "GIULIA", "EUKALIA", string.Empty, "Eulalia" }; +} + +/// +/// Generation 4 Trade Encounter with a fixed PID value. +/// +/// +public sealed record EncounterTrade4PID : EncounterTrade4, IContestStats { /// - /// Generation 4 Trade Encounter + /// Fixed value the encounter must have. /// - /// - public abstract record EncounterTrade4(GameVersion Version) : EncounterTrade(Version) - { - public sealed override int Generation => 4; + public readonly uint PID; - protected static readonly string[] RanchOTNames = { string.Empty, "ユカリ", "Hayley", "EULALIE", "GIULIA", "EUKALIA", string.Empty, "Eulalia" }; + public override Shiny Shiny => Shiny.FixedValue; + + public EncounterTrade4PID(GameVersion game, uint pid, ushort species, byte level) : base(game) + { + PID = pid; + Species = species; + Level = level; } - /// - /// Generation 4 Trade Encounter with a fixed PID value. - /// - /// - public sealed record EncounterTrade4PID : EncounterTrade4, IContestStats + public byte CNT_Cool { get; init; } + public byte CNT_Beauty { get; init; } + public byte CNT_Cute { get; init; } + public byte CNT_Smart { get; init; } + public byte CNT_Tough { get; init; } + public byte CNT_Sheen { get; init; } + + public byte Contest { - /// - /// Fixed value the encounter must have. - /// - public readonly uint PID; - - public override Shiny Shiny => Shiny.FixedValue; - - public EncounterTrade4PID(GameVersion game, uint pid, ushort species, byte level) : base(game) + init { - PID = pid; - Species = species; - Level = level; - } - - public byte CNT_Cool { get; init; } - public byte CNT_Beauty { get; init; } - public byte CNT_Cute { get; init; } - public byte CNT_Smart { get; init; } - public byte CNT_Tough { get; init; } - public byte CNT_Sheen { get; init; } - - public byte Contest - { - init - { - CNT_Cool = value; - CNT_Beauty = value; - CNT_Cute = value; - CNT_Smart = value; - CNT_Tough = value; - //CNT_Sheen = value; - } - } - - public int MetLocation { get; init; } - public override int Location => MetLocation == default ? Locations.LinkTrade4NPC : MetLocation; - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!base.IsMatchExact(pkm, evo)) - return false; - - if (pkm is IContestStats s && s.IsContestBelow(this)) - return false; - - return true; - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pkm = (PK4) pk; - - if (Version == GameVersion.DPPt) - { - // Has German Language ID for all except German origin, which is English - if (Species == (int)Core.Species.Magikarp) - pkm.Language = (int)(pkm.Language == (int)LanguageID.German ? LanguageID.English : LanguageID.German); - // All other trades received (DP only): English games have a Japanese language ID instead of English. - else if (pkm.Version is not (int)GameVersion.Pt && pkm.Language == (int)LanguageID.English) - pkm.Language = (int)LanguageID.Japanese; - } - else // HGSS - { - // Has English Language ID for all except English origin, which is French - if (Species == (int)Core.Species.Pikachu) - pkm.Language = (int)(pkm.Language == (int)LanguageID.English ? LanguageID.French : LanguageID.English); - } - - this.CopyContestStatsTo((PK4)pk); - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - pk.PID = PID; - pk.Nature = (int)(PID % 25); - pk.Gender = Gender; - pk.RefreshAbility(Ability.GetSingleValue()); - SetIVs(pk); - } - - protected override bool IsMatchNatureGenderShiny(PKM pkm) - { - return PID == pkm.EncryptionConstant; + CNT_Cool = value; + CNT_Beauty = value; + CNT_Cute = value; + CNT_Smart = value; + CNT_Tough = value; + //CNT_Sheen = value; } } - /// - /// Generation 4 Trade Encounter with a fixed PID value, met location, and version. - /// - /// - public sealed record EncounterTrade4RanchGift : EncounterTrade4 + public int MetLocation { get; init; } + public override int Location => MetLocation == default ? Locations.LinkTrade4NPC : MetLocation; + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) { - /// - /// Fixed value the encounter must have. - /// - public readonly uint PID; + if (!base.IsMatchExact(pk, evo)) + return false; - public int MetLocation { private get; init; } - public override int Location => MetLocation; - public override Shiny Shiny => Shiny.FixedValue; + if (pk is IContestStats s && s.IsContestBelow(this)) + return false; - public EncounterTrade4RanchGift(uint pid, ushort species, byte level) : base(GameVersion.D) - { - PID = pid; - Species = species; - Level = level; - TrainerNames = RanchOTNames; - } - - protected override bool IsMatchNatureGenderShiny(PKM pkm) - { - return PID == pkm.EncryptionConstant; - } - - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - pk.PID = PID; - pk.Nature = (int)(PID % 25); - pk.Gender = Gender; - pk.RefreshAbility(Ability.GetSingleValue()); - SetIVs(pk); - } + return true; } - /// - /// Generation 4 Trade Encounter with a fixed location and version, as well as special details. - /// - /// - public sealed record EncounterTrade4RanchSpecial : EncounterTrade4 + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) { - public override int Location => 3000; + base.ApplyDetails(sav, criteria, pk); + var pk4 = (PK4) pk; - public EncounterTrade4RanchSpecial(ushort species, byte level) : base(GameVersion.D) + if (Version == GameVersion.DPPt) { - Species = species; - Level = level; - Ball = 0x10; - OTGender = 1; - TrainerNames = RanchOTNames; + // Has German Language ID for all except German origin, which is English + if (Species == (int)Core.Species.Magikarp) + pk4.Language = (int)(pk4.Language == (int)LanguageID.German ? LanguageID.English : LanguageID.German); + // All other trades received (DP only): English games have a Japanese language ID instead of English. + else if (pk4.Version is not (int)GameVersion.Pt && pk4.Language == (int)LanguageID.English) + pk4.Language = (int)LanguageID.Japanese; + } + else // HGSS + { + // Has English Language ID for all except English origin, which is French + if (Species == (int)Core.Species.Pikachu) + pk4.Language = (int)(pk4.Language == (int)LanguageID.English ? LanguageID.French : LanguageID.English); } - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - pk.FatefulEncounter = true; - } + this.CopyContestStatsTo(pk4); + } + + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + pk.PID = PID; + pk.Nature = (int)(PID % 25); + pk.Gender = Gender; + pk.RefreshAbility(Ability.GetSingleValue()); + SetIVs(pk); + } + + protected override bool IsMatchNatureGenderShiny(PKM pk) + { + return PID == pk.EncryptionConstant; + } +} + +/// +/// Generation 4 Trade Encounter with a fixed PID value, met location, and version. +/// +/// +public sealed record EncounterTrade4RanchGift : EncounterTrade4 +{ + /// + /// Fixed value the encounter must have. + /// + public readonly uint PID; + + public int MetLocation { private get; init; } + public override int Location => MetLocation; + public override Shiny Shiny => Shiny.FixedValue; + + public EncounterTrade4RanchGift(uint pid, ushort species, byte level) : base(GameVersion.D) + { + PID = pid; + Species = species; + Level = level; + TrainerNames = RanchOTNames; + } + + protected override bool IsMatchNatureGenderShiny(PKM pk) + { + return PID == pk.EncryptionConstant; + } + + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) + { + pk.PID = PID; + pk.Nature = (int)(PID % 25); + pk.Gender = Gender; + pk.RefreshAbility(Ability.GetSingleValue()); + SetIVs(pk); + } +} + +/// +/// Generation 4 Trade Encounter with a fixed location and version, as well as special details. +/// +/// +public sealed record EncounterTrade4RanchSpecial : EncounterTrade4 +{ + public override int Location => 3000; + + public EncounterTrade4RanchSpecial(ushort species, byte level) : base(GameVersion.D) + { + Species = species; + Level = level; + Ball = 0x10; + OTGender = 1; + TrainerNames = RanchOTNames; + } + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + pk.FatefulEncounter = true; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade5.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade5.cs index f2196cfaf..e83636f16 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade5.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade5.cs @@ -1,60 +1,59 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 Trade Encounter +/// +/// +public sealed record EncounterTrade5(GameVersion Version) : EncounterTrade(Version) { - /// - /// Generation 5 Trade Encounter - /// - /// - public sealed record EncounterTrade5(GameVersion Version) : EncounterTrade(Version) + public override int Generation => 5; + public override int Location => Locations.LinkTrade5NPC; +} + +/// Generation 5 Trade with Fixed PID +/// Fixed value the encounter must have. +public sealed record EncounterTrade5PID(GameVersion Version, uint PID) : EncounterTrade(Version) +{ + public override int Generation => 5; + public override int Location => Locations.LinkTrade5NPC; + + public override Shiny Shiny => Shiny.FixedValue; + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) { - public override int Generation => 5; - public override int Location => Locations.LinkTrade5NPC; + base.ApplyDetails(sav, criteria, pk); + + // Trades for JPN games have language ID of 0, not 1. + if (pk.Language == (int) LanguageID.Japanese) + pk.Language = 0; } - /// Generation 5 Trade with Fixed PID - /// Fixed value the encounter must have. - public sealed record EncounterTrade5PID(GameVersion Version, uint PID) : EncounterTrade(Version) + protected override void SetPINGA(PKM pk, EncounterCriteria criteria) { - public override int Generation => 5; - public override int Location => Locations.LinkTrade5NPC; + var pi = pk.PersonalInfo; + int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pi); + int nature = (int)criteria.GetNature(Nature); + int ability = criteria.GetAbilityFromNumber(Ability); - public override Shiny Shiny => Shiny.FixedValue; + pk.PID = PID; + pk.Nature = nature; + pk.Gender = gender; + pk.RefreshAbility(ability); - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); + SetIVs(pk); + } - // Trades for JPN games have language ID of 0, not 1. - if (pk.Language == (int) LanguageID.Japanese) - pk.Language = 0; - } + protected override bool IsMatchNatureGenderShiny(PKM pk) + { + if (PID != pk.EncryptionConstant) + return false; + if (Nature != Nature.Random && (int)Nature != pk.Nature) // gen5 BW only + return false; + return true; + } - protected override void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = pk.PersonalInfo; - int gender = criteria.GetGender(EntityGender.GetFromPID(Species, PID), pi); - int nature = (int)criteria.GetNature(Nature); - int ability = criteria.GetAbilityFromNumber(Ability); - - pk.PID = PID; - pk.Nature = nature; - pk.Gender = gender; - pk.RefreshAbility(ability); - - SetIVs(pk); - } - - protected override bool IsMatchNatureGenderShiny(PKM pkm) - { - if (PID != pkm.EncryptionConstant) - return false; - if (Nature != Nature.Random && (int)Nature != pkm.Nature) // gen5 BW only - return false; - return true; - } - - public static bool IsValidMissingLanguage(PKM pkm) - { - return pkm.Format == 5 && pkm.BW; - } + public static bool IsValidMissingLanguage(PKM pk) + { + return pk.Format == 5 && pk.BW; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade6.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade6.cs index 03c1d162a..1df3d84c2 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade6.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade6.cs @@ -1,28 +1,27 @@ -namespace PKHeX.Core -{ - /// - /// Generation 6 Trade Encounter - /// - /// - public sealed record EncounterTrade6(GameVersion Version, byte OT_Memory, byte OT_Intensity, byte OT_Feeling, ushort OT_TextVar) : EncounterTrade(Version), IMemoryOT - { - public override int Generation => 6; - public override int Location => Locations.LinkTrade6NPC; - public byte OT_Memory { get; set; } = OT_Memory; - public byte OT_Intensity { get; set; } = OT_Intensity; - public byte OT_Feeling { get; set; } = OT_Feeling; - public ushort OT_TextVar { get; set; } = OT_TextVar; +namespace PKHeX.Core; - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) +/// +/// Generation 6 Trade Encounter +/// +/// +public sealed record EncounterTrade6(GameVersion Version, byte OT_Memory, byte OT_Intensity, byte OT_Feeling, ushort OT_TextVar) : EncounterTrade(Version), IMemoryOT +{ + public override int Generation => 6; + public override int Location => Locations.LinkTrade6NPC; + public byte OT_Memory { get; set; } = OT_Memory; + public byte OT_Intensity { get; set; } = OT_Intensity; + public byte OT_Feeling { get; set; } = OT_Feeling; + public ushort OT_TextVar { get; set; } = OT_TextVar; + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + if (pk is IMemoryOT o) { - base.ApplyDetails(sav, criteria, pk); - if (pk is IMemoryOT o) - { - o.OT_Memory = OT_Memory; - o.OT_Intensity = OT_Intensity; - o.OT_Feeling = OT_Feeling; - o.OT_TextVar = OT_TextVar; - } + o.OT_Memory = OT_Memory; + o.OT_Intensity = OT_Intensity; + o.OT_Feeling = OT_Feeling; + o.OT_TextVar = OT_TextVar; } } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7.cs index f7413ea23..f49850cd0 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7.cs @@ -1,29 +1,28 @@ using System; -namespace PKHeX.Core -{ - /// - /// Generation 7 Trade Encounter - /// - /// - public sealed record EncounterTrade7(GameVersion Version) : EncounterTrade(Version), IMemoryOT - { - public override int Generation => 7; - public override int Location => Locations.LinkTrade6NPC; - // immutable setters - public byte OT_Memory { get => 1; set => throw new InvalidOperationException(); } - public byte OT_Intensity { get => 3; set => throw new InvalidOperationException(); } - public byte OT_Feeling { get => 5; set => throw new InvalidOperationException(); } - public ushort OT_TextVar { get => 40; set => throw new InvalidOperationException(); } +namespace PKHeX.Core; - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pk7 = (PK7)pk; - pk7.OT_Memory = OT_Memory; - pk7.OT_Intensity = OT_Intensity; - pk7.OT_Feeling = OT_Feeling; - pk7.OT_TextVar = OT_TextVar; - } +/// +/// Generation 7 Trade Encounter +/// +/// +public sealed record EncounterTrade7(GameVersion Version) : EncounterTrade(Version), IMemoryOT +{ + public override int Generation => 7; + public override int Location => Locations.LinkTrade6NPC; + // immutable setters + public byte OT_Memory { get => 1; set => throw new InvalidOperationException(); } + public byte OT_Intensity { get => 3; set => throw new InvalidOperationException(); } + public byte OT_Feeling { get => 5; set => throw new InvalidOperationException(); } + public ushort OT_TextVar { get => 40; set => throw new InvalidOperationException(); } + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + var pk7 = (PK7)pk; + pk7.OT_Memory = OT_Memory; + pk7.OT_Intensity = OT_Intensity; + pk7.OT_Feeling = OT_Feeling; + pk7.OT_TextVar = OT_TextVar; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7b.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7b.cs index 4288b2b6c..8958b4dc2 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7b.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade7b.cs @@ -1,25 +1,24 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 LGP/E Trade Encounter +/// +/// +public sealed record EncounterTrade7b : EncounterTrade { - /// - /// Generation 7 LGP/E Trade Encounter - /// - /// - public sealed record EncounterTrade7b : EncounterTrade + public override int Generation => 7; + public override int Location => Locations.LinkTrade6NPC; + public override Shiny Shiny => Shiny.Random; + + public EncounterTrade7b(GameVersion game) : base(game) => IsNicknamed = false; + + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) { - public override int Generation => 7; - public override int Location => Locations.LinkTrade6NPC; - public override Shiny Shiny => Shiny.Random; - - public EncounterTrade7b(GameVersion game) : base(game) => IsNicknamed = false; - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - pk.SetRandomEC(); - var pb = (PB7)pk; - pb.ResetHeight(); - pb.ResetWeight(); - pb.ResetCP(); - } + base.ApplyDetails(sav, criteria, pk); + pk.SetRandomEC(); + var pb = (PB7)pk; + pb.ResetHeight(); + pb.ResetWeight(); + pb.ResetCP(); } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs index 759352700..d609ec900 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8.cs @@ -1,59 +1,58 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 Trade Encounter +/// +/// +public sealed record EncounterTrade8 : EncounterTrade, IDynamaxLevel, IRelearn, IMemoryOT { - /// - /// Generation 8 Trade Encounter - /// - /// - public sealed record EncounterTrade8 : EncounterTrade, IDynamaxLevel, IRelearn, IMemoryOT + public override int Generation => 8; + public override int Location => Locations.LinkTrade6NPC; + public IReadOnlyList Relearn { get; init; } = Array.Empty(); + + public ushort OT_TextVar { get; set; } + public byte OT_Memory { get; set; } + public byte OT_Feeling { get; set; } + public byte OT_Intensity { get; set; } + public byte DynamaxLevel { get; set; } + public byte FlawlessIVCount { get; init; } + public override Shiny Shiny { get; } + + public EncounterTrade8(GameVersion game, int species, byte level, byte memory, ushort arg, byte feel, byte intensity, Shiny shiny = Shiny.Never) : base(game) { - public override int Generation => 8; - public override int Location => Locations.LinkTrade6NPC; - public IReadOnlyList Relearn { get; init; } = Array.Empty(); + Species = species; + Level = level; + Shiny = shiny; - public ushort OT_TextVar { get; set; } - public byte OT_Memory { get; set; } - public byte OT_Feeling { get; set; } - public byte OT_Intensity { get; set; } - public byte DynamaxLevel { get; set; } - public byte FlawlessIVCount { get; init; } - public override Shiny Shiny { get; } + OT_Memory = memory; + OT_TextVar = arg; + OT_Feeling = feel; + OT_Intensity = intensity; + } - public EncounterTrade8(GameVersion game, int species, byte level, byte memory, ushort arg, byte feel, byte intensity, Shiny shiny = Shiny.Never) : base(game) - { - Species = species; - Level = level; - Shiny = shiny; + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (pk is PK8 d && d.DynamaxLevel < DynamaxLevel) + return false; + if (pk.FlawlessIVCount < FlawlessIVCount) + return false; + return base.IsMatchExact(pk, evo); + } - OT_Memory = memory; - OT_TextVar = arg; - OT_Feeling = feel; - OT_Intensity = intensity; - } + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + pk.SetRelearnMoves(Relearn); - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (pkm is PK8 d && d.DynamaxLevel < DynamaxLevel) - return false; - if (pkm.FlawlessIVCount < FlawlessIVCount) - return false; - return base.IsMatchExact(pkm, evo); - } - - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - pk.SetRelearnMoves(Relearn); - - var pk8 = (PK8)pk; - pk8.DynamaxLevel = DynamaxLevel; - pk8.HT_Language = (byte)sav.Language; - pk8.OT_Memory = OT_Memory; - pk8.OT_TextVar = OT_TextVar; - pk8.OT_Feeling = OT_Feeling; - pk8.OT_Intensity = OT_Intensity; - } + var pk8 = (PK8)pk; + pk8.DynamaxLevel = DynamaxLevel; + pk8.HT_Language = (byte)sav.Language; + pk8.OT_Memory = OT_Memory; + pk8.OT_TextVar = OT_TextVar; + pk8.OT_Feeling = OT_Feeling; + pk8.OT_Intensity = OT_Intensity; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs index a8e9a1a0e..cca0e42a2 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade8b.cs @@ -1,66 +1,65 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 Trade Encounter +/// +/// +public sealed record EncounterTrade8b : EncounterTrade, IContestStats, IScaledSize, IFixedOTFriendship { - /// - /// Generation 7 Trade Encounter - /// - /// - public sealed record EncounterTrade8b : EncounterTrade, IContestStats, IScaledSize, IFixedOTFriendship + public override int Generation => 8; + public override int Location => Locations.LinkTrade6NPC; + + public EncounterTrade8b(GameVersion game) : base(game) => EggLocation = Locations.Default8bNone; + public byte CNT_Cool => BaseContest; + public byte CNT_Beauty => BaseContest; + public byte CNT_Cute => BaseContest; + public byte CNT_Smart => BaseContest; + public byte CNT_Tough => BaseContest; + public byte CNT_Sheen => 0; + public byte HeightScalar { get; set; } + public byte WeightScalar { get; set; } + public byte OT_Friendship => Species == (int)Core.Species.Chatot ? (byte)35 : (byte)50; + private byte BaseContest => Species == (int)Core.Species.Chatot ? (byte)20 : (byte)0; + public uint PID { get; init; } + public uint EncryptionConstant { get; init; } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) { - public override int Generation => 8; - public override int Location => Locations.LinkTrade6NPC; + if (pk.EncryptionConstant != EncryptionConstant) + return false; + if (pk.PID != PID) + return false; + if (pk is IContestStats s && s.IsContestBelow(this)) + return false; + if (pk is IScaledSize h && h.HeightScalar != HeightScalar) + return false; + if (pk is IScaledSize w && w.WeightScalar != WeightScalar) + return false; + return base.IsMatchExact(pk, evo); + } - public EncounterTrade8b(GameVersion game) : base(game) => EggLocation = Locations.Default8bNone; - public byte CNT_Cool => BaseContest; - public byte CNT_Beauty => BaseContest; - public byte CNT_Cute => BaseContest; - public byte CNT_Smart => BaseContest; - public byte CNT_Tough => BaseContest; - public byte CNT_Sheen => 0; - public byte HeightScalar { get; set; } - public byte WeightScalar { get; set; } - public byte OT_Friendship => Species == (int)Core.Species.Chatot ? (byte)35 : (byte)50; - private byte BaseContest => Species == (int)Core.Species.Chatot ? (byte)20 : (byte)0; - public uint PID { get; init; } - public uint EncryptionConstant { get; init; } + protected override bool IsMatchEggLocation(PKM pk) + { + var expect = EggLocation; + if (pk is not PB8 && expect == Locations.Default8bNone) + expect = 0; + return pk.Egg_Location == expect; + } - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (pkm.EncryptionConstant != EncryptionConstant) - return false; - if (pkm.PID != PID) - return false; - if (pkm is IContestStats s && s.IsContestBelow(this)) - return false; - if (pkm is IScaledSize h && h.HeightScalar != HeightScalar) - return false; - if (pkm is IScaledSize w && w.WeightScalar != WeightScalar) - return false; - return base.IsMatchExact(pkm, evo); - } + protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) + { + base.ApplyDetails(sav, criteria, pk); + var pb8 = (PB8)pk; + pb8.EncryptionConstant = EncryptionConstant; + pb8.PID = PID; - protected override bool IsMatchEggLocation(PKM pkm) - { - var expect = EggLocation; - if (pkm is not PB8 && expect == Locations.Default8bNone) - expect = 0; - return pkm.Egg_Location == expect; - } + // Has German Language ID for all except German origin, which is Japanese + if (Species == (int)Core.Species.Magikarp) + pb8.Language = (int)(pb8.Language == (int)LanguageID.German ? LanguageID.Japanese : LanguageID.German); - protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk) - { - base.ApplyDetails(sav, criteria, pk); - var pb8 = (PB8)pk; - pb8.EncryptionConstant = EncryptionConstant; - pb8.PID = PID; - - // Has German Language ID for all except German origin, which is Japanese - if (Species == (int)Core.Species.Magikarp) - pb8.Language = (int)(pb8.Language == (int)LanguageID.German ? LanguageID.Japanese : LanguageID.German); - - this.CopyContestStatsTo(pb8); - pb8.HT_Language = (byte)sav.Language; - pb8.HeightScalar = HeightScalar; - pb8.WeightScalar = WeightScalar; - } + this.CopyContestStatsTo(pb8); + pb8.HT_Language = (byte)sav.Language; + pb8.HeightScalar = HeightScalar; + pb8.WeightScalar = WeightScalar; } } diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTradeGB.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTradeGB.cs index f7962b65d..c649877cf 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTradeGB.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTradeGB.cs @@ -1,14 +1,13 @@ -namespace PKHeX.Core -{ - /// - public abstract record EncounterTradeGB : EncounterTrade - { - protected EncounterTradeGB(int species, byte level, GameVersion game) : base(game) - { - Species = species; - Level = level; - } +namespace PKHeX.Core; - public abstract override bool IsMatchExact(PKM pkm, EvoCriteria evo); +/// +public abstract record EncounterTradeGB : EncounterTrade +{ + protected EncounterTradeGB(int species, byte level, GameVersion game) : base(game) + { + Species = species; + Level = level; } + + public abstract override bool IsMatchExact(PKM pk, EvoCriteria evo); } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator12.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator12.cs index df52cda47..06a43649f 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator12.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator12.cs @@ -6,170 +6,169 @@ using static PKHeX.Core.EncounterEggGenerator2; using static PKHeX.Core.EncounterMatchRating; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// This class is essentially a sub-class of specialized for Gen1 & Gen2 encounters. +/// +internal static class EncounterGenerator12 { - /// - /// This class is essentially a sub-class of specialized for Gen1 & Gen2 encounters. - /// - internal static class EncounterGenerator12 + internal static IEnumerable GetEncounters12(PKM pk, LegalInfo info) { - internal static IEnumerable GetEncounters12(PKM pkm, LegalInfo info) + foreach (var z in GenerateFilteredEncounters12(pk)) { - foreach (var z in GenerateFilteredEncounters12(pkm)) - { - info.StoreMetadata(z.Generation); - yield return z; - } - } - - private static IEnumerable GenerateRawEncounters12(PKM pkm, GameVersion game) - { - // Since encounter matching is super weak due to limited stored data in the structure - // Calculate all 3 at the same time and pick the best result (by species). - // Favor special event move gifts as Static Encounters when applicable - var chain = EncounterOrigin.GetOriginChain12(pkm, game); - - IEncounterable? deferred = null; - foreach (var t in GetValidEncounterTradesVC(pkm, chain, game)) - { - // Gen2 trades are strictly matched (OT/Nick), while Gen1 trades allow for deferral (shrug). - if (t is EncounterTrade1 t1 && t1.GetMatchRating(pkm) != Match) - { - deferred ??= t; - continue; - } - yield return t; - } - foreach (var s in GetValidStaticEncounter(pkm, chain, game)) - { - yield return s; - } - foreach (var e in GetValidWildEncounters12(pkm, chain, game)) - { - yield return e; - } - if (game != GameVersion.RBY) - { - foreach (var e in GenerateEggs(pkm, chain)) - yield return e; - } - - foreach (var s in GenerateGBEvents(pkm, chain, game)) - { - yield return s; - } - - if (deferred != null) - yield return deferred; - } - - private static IEnumerable GenerateGBEvents(PKM pkm, EvoCriteria[] chain, GameVersion game) - { - if (pkm.Korean) // only GS; no events - yield break; - - foreach (var e in GetValidGBGifts(pkm, chain, game)) - { - foreach (var evo in chain) - { - if (e.IsMatchExact(pkm, evo)) - yield return e; - } - } - } - - private static IEnumerable GenerateFilteredEncounters12(PKM pkm) - { - // If the current data indicates that it must have originated from Crystal, only yield encounter data from Crystal. - bool crystal = (pkm is ICaughtData2 {CaughtData: not 0}) || (pkm.Format >= 7 && pkm.OT_Gender == 1); - if (crystal) - return GenerateRawEncounters12(pkm, GameVersion.C); - - var visited = GBRestrictions.GetTradebackStatusInitial(pkm); - switch (visited) - { - case PotentialGBOrigin.Gen1Only: - return GenerateRawEncounters12(pkm, GameVersion.RBY); - case PotentialGBOrigin.Gen2Only: - return GenerateRawEncounters12(pkm, GameVersion.GSC); - default: - if (pkm.Korean) - return GenerateFilteredEncounters12BothKorean(pkm); - return GenerateFilteredEncounters12Both(pkm); - } - } - - private static IEnumerable GenerateFilteredEncounters12BothKorean(PKM pkm) - { - // Korean origin PK1/PK2 can only originate from GS, but since we're nice we'll defer & yield matches from other games. - // Yield GS first, then Crystal, then RBY. Anything other than GS will be flagged by later checks. - - var deferred = new List(); - var get2 = GenerateRawEncounters12(pkm, GameVersion.GSC); - foreach (var enc in get2) - { - if (enc.Version == GameVersion.C) - deferred.Add(enc); - else - yield return enc; - } - - foreach (var enc in deferred) - yield return enc; - - var get1 = GenerateRawEncounters12(pkm, GameVersion.RBY); - foreach (var enc in get1) - yield return enc; - } - - private static IEnumerable GenerateFilteredEncounters12Both(PKM pkm) - { - // Iterate over both games, consuming from one list at a time until the other list has higher priority encounters - // Buffer the encounters so that we can consume each iterator separately - var get1 = GenerateRawEncounters12(pkm, GameVersion.RBY); - var get2 = GenerateRawEncounters12(pkm, GameVersion.GSC); - using var g1i = new PeekEnumerator(get1); - using var g2i = new PeekEnumerator(get2); - while (g2i.PeekIsNext() || g1i.PeekIsNext()) - { - var iter = PickPreferredIterator(pkm, g1i, g2i); - yield return iter.Current; - iter.MoveNext(); - } - } - - private static PeekEnumerator PickPreferredIterator(PKM pkm, PeekEnumerator g1i, PeekEnumerator g2i) - { - if (!g1i.PeekIsNext()) - return g2i; - if (!g2i.PeekIsNext()) - return g1i; - var p1 = GetGBEncounterPriority(pkm, g1i.Current); - var p2 = GetGBEncounterPriority(pkm, g2i.Current); - return p1 > p2 ? g1i : g2i; - } - - private static GBEncounterPriority GetGBEncounterPriority(PKM pkm, IEncounterTemplate enc) => enc switch - { - EncounterTrade1 t1 when t1.GetMatchRating(pkm) != Match => GBEncounterPriority.Least, - EncounterTrade1 => GBEncounterPriority.TradeEncounterG1, - EncounterTrade2 => GBEncounterPriority.TradeEncounterG2, - EncounterStatic => GBEncounterPriority.StaticEncounter, - EncounterSlot => GBEncounterPriority.WildEncounter, - _ => GBEncounterPriority.EggEncounter, - }; - - /// - /// Generation 1/2 Encounter Data type, which serves as a 'best match' priority rating when returning from a list. - /// - private enum GBEncounterPriority - { - Least, - EggEncounter, - WildEncounter, - StaticEncounter, - TradeEncounterG1, - TradeEncounterG2, + info.StoreMetadata(z.Generation); + yield return z; } } + + private static IEnumerable GenerateRawEncounters12(PKM pk, GameVersion game) + { + // Since encounter matching is super weak due to limited stored data in the structure + // Calculate all 3 at the same time and pick the best result (by species). + // Favor special event move gifts as Static Encounters when applicable + var chain = EncounterOrigin.GetOriginChain12(pk, game); + + IEncounterable? deferred = null; + foreach (var t in GetValidEncounterTradesVC(pk, chain, game)) + { + // Gen2 trades are strictly matched (OT/Nick), while Gen1 trades allow for deferral (shrug). + if (t is EncounterTrade1 t1 && t1.GetMatchRating(pk) != Match) + { + deferred ??= t; + continue; + } + yield return t; + } + foreach (var s in GetValidStaticEncounter(pk, chain, game)) + { + yield return s; + } + foreach (var e in GetValidWildEncounters12(pk, chain, game)) + { + yield return e; + } + if (game != GameVersion.RBY) + { + foreach (var e in GenerateEggs(pk, chain)) + yield return e; + } + + foreach (var s in GenerateGBEvents(pk, chain, game)) + { + yield return s; + } + + if (deferred != null) + yield return deferred; + } + + private static IEnumerable GenerateGBEvents(PKM pk, EvoCriteria[] chain, GameVersion game) + { + if (pk.Korean) // only GS; no events + yield break; + + foreach (var e in GetValidGBGifts(pk, chain, game)) + { + foreach (var evo in chain) + { + if (e.IsMatchExact(pk, evo)) + yield return e; + } + } + } + + private static IEnumerable GenerateFilteredEncounters12(PKM pk) + { + // If the current data indicates that it must have originated from Crystal, only yield encounter data from Crystal. + bool crystal = (pk is ICaughtData2 {CaughtData: not 0}) || (pk.Format >= 7 && pk.OT_Gender == 1); + if (crystal) + return GenerateRawEncounters12(pk, GameVersion.C); + + var visited = GBRestrictions.GetTradebackStatusInitial(pk); + switch (visited) + { + case PotentialGBOrigin.Gen1Only: + return GenerateRawEncounters12(pk, GameVersion.RBY); + case PotentialGBOrigin.Gen2Only: + return GenerateRawEncounters12(pk, GameVersion.GSC); + default: + if (pk.Korean) + return GenerateFilteredEncounters12BothKorean(pk); + return GenerateFilteredEncounters12Both(pk); + } + } + + private static IEnumerable GenerateFilteredEncounters12BothKorean(PKM pk) + { + // Korean origin PK1/PK2 can only originate from GS, but since we're nice we'll defer & yield matches from other games. + // Yield GS first, then Crystal, then RBY. Anything other than GS will be flagged by later checks. + + var deferred = new List(); + var get2 = GenerateRawEncounters12(pk, GameVersion.GSC); + foreach (var enc in get2) + { + if (enc.Version == GameVersion.C) + deferred.Add(enc); + else + yield return enc; + } + + foreach (var enc in deferred) + yield return enc; + + var get1 = GenerateRawEncounters12(pk, GameVersion.RBY); + foreach (var enc in get1) + yield return enc; + } + + private static IEnumerable GenerateFilteredEncounters12Both(PKM pk) + { + // Iterate over both games, consuming from one list at a time until the other list has higher priority encounters + // Buffer the encounters so that we can consume each iterator separately + var get1 = GenerateRawEncounters12(pk, GameVersion.RBY); + var get2 = GenerateRawEncounters12(pk, GameVersion.GSC); + using var g1i = new PeekEnumerator(get1); + using var g2i = new PeekEnumerator(get2); + while (g2i.PeekIsNext() || g1i.PeekIsNext()) + { + var iter = PickPreferredIterator(pk, g1i, g2i); + yield return iter.Current; + iter.MoveNext(); + } + } + + private static PeekEnumerator PickPreferredIterator(PKM pk, PeekEnumerator g1i, PeekEnumerator g2i) + { + if (!g1i.PeekIsNext()) + return g2i; + if (!g2i.PeekIsNext()) + return g1i; + var p1 = GetGBEncounterPriority(pk, g1i.Current); + var p2 = GetGBEncounterPriority(pk, g2i.Current); + return p1 > p2 ? g1i : g2i; + } + + private static GBEncounterPriority GetGBEncounterPriority(PKM pk, IEncounterTemplate enc) => enc switch + { + EncounterTrade1 t1 when t1.GetMatchRating(pk) != Match => GBEncounterPriority.Least, + EncounterTrade1 => GBEncounterPriority.TradeEncounterG1, + EncounterTrade2 => GBEncounterPriority.TradeEncounterG2, + EncounterStatic => GBEncounterPriority.StaticEncounter, + EncounterSlot => GBEncounterPriority.WildEncounter, + _ => GBEncounterPriority.EggEncounter, + }; + + /// + /// Generation 1/2 Encounter Data type, which serves as a 'best match' priority rating when returning from a list. + /// + private enum GBEncounterPriority + { + Least, + EggEncounter, + WildEncounter, + StaticEncounter, + TradeEncounterG1, + TradeEncounterG2, + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs index ea490a82c..a1b62ae34 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs @@ -8,237 +8,236 @@ using static PKHeX.Core.EncounterEggGenerator; using static PKHeX.Core.EncounterMatchRating; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class EncounterGenerator3 { - public static class EncounterGenerator3 + public static IEnumerable GetEncounters(PKM pk, LegalInfo info) { - public static IEnumerable GetEncounters(PKM pkm, LegalInfo info) + if (pk.Version == (int) GameVersion.CXD) + return GetEncounters3CXD(pk, info); + return GetEncounters3(pk, info); + } + + private static IEnumerable GetEncounters3(PKM pk, LegalInfo info) + { + info.PIDIV = MethodFinder.Analyze(pk); + IEncounterable? Partial = null; + + foreach (var z in GenerateRawEncounters3(pk, info)) { - if (pkm.Version == (int) GameVersion.CXD) - return GetEncounters3CXD(pkm, info); - return GetEncounters3(pkm, info); - } - - private static IEnumerable GetEncounters3(PKM pkm, LegalInfo info) - { - info.PIDIV = MethodFinder.Analyze(pkm); - IEncounterable? Partial = null; - - foreach (var z in GenerateRawEncounters3(pkm, info)) - { - if (info.PIDIV.Type.IsCompatible3(z, pkm)) - yield return z; - else - Partial ??= z; - } - if (Partial == null) - yield break; - - info.PIDIVMatches = false; - yield return Partial; - } - - private static IEnumerable GetEncounters3CXD(PKM pkm, LegalInfo info) - { - info.PIDIV = MethodFinder.Analyze(pkm); - IEncounterable? Partial = null; - foreach (var z in GenerateRawEncounters3CXD(pkm)) - { - if (z is EncounterSlot3PokeSpot w) - { - var seeds = MethodFinder.GetPokeSpotSeeds(pkm, w.SlotNumber); - foreach (var s in seeds) - { - info.PIDIV = s; - break; - } - } - else if (z is EncounterStaticShadow s) - { - bool valid = GetIsShadowLockValid(pkm, info, s); - if (!valid) - { - Partial ??= s; - continue; - } - } - - if (info.PIDIV.Type.IsCompatible3(z, pkm)) - yield return z; - else - Partial ??= z; - } - if (Partial == null) - yield break; - - info.PIDIVMatches = false; - yield return Partial; - } - - private static IEnumerable GenerateRawEncounters3CXD(PKM pkm) - { - var chain = EncounterOrigin.GetOriginChain(pkm); - - var game = (GameVersion)pkm.Version; - // Mystery Gifts - foreach (var z in GetValidGifts(pkm, chain, game)) - { - // Don't bother deferring matches. - var match = z.GetMatchRating(pkm); - if (match != PartialMatch) - yield return z; - } - - // Trades - foreach (var z in GetValidEncounterTrades(pkm, chain, game)) - { - // Don't bother deferring matches. - var match = z.GetMatchRating(pkm); - if (match != PartialMatch) - yield return z; - } - - IEncounterable? partial = null; - - // Static Encounter - foreach (var z in GetValidStaticEncounter(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - if (match == PartialMatch) - partial ??= z; - else - yield return z; - } - - // Encounter Slots - foreach (var z in GetValidWildEncounters(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - if (match == PartialMatch) - { - partial ??= z; - continue; - } + if (info.PIDIV.Type.IsCompatible3(z, pk)) yield return z; + else + Partial ??= z; + } + if (Partial == null) + yield break; + + info.PIDIVMatches = false; + yield return Partial; + } + + private static IEnumerable GetEncounters3CXD(PKM pk, LegalInfo info) + { + info.PIDIV = MethodFinder.Analyze(pk); + IEncounterable? Partial = null; + foreach (var z in GenerateRawEncounters3CXD(pk)) + { + if (z is EncounterSlot3PokeSpot w) + { + var seeds = MethodFinder.GetPokeSpotSeeds(pk, w.SlotNumber); + foreach (var s in seeds) + { + info.PIDIV = s; + break; + } + } + else if (z is EncounterStaticShadow s) + { + bool valid = GetIsShadowLockValid(pk, info, s); + if (!valid) + { + Partial ??= s; + continue; + } } - if (partial is not null) - yield return partial; + if (info.PIDIV.Type.IsCompatible3(z, pk)) + yield return z; + else + Partial ??= z; + } + if (Partial == null) + yield break; + + info.PIDIVMatches = false; + yield return Partial; + } + + private static IEnumerable GenerateRawEncounters3CXD(PKM pk) + { + var chain = EncounterOrigin.GetOriginChain(pk); + + var game = (GameVersion)pk.Version; + // Mystery Gifts + foreach (var z in GetValidGifts(pk, chain, game)) + { + // Don't bother deferring matches. + var match = z.GetMatchRating(pk); + if (match != PartialMatch) + yield return z; } - private static IEnumerable GenerateRawEncounters3(PKM pkm, LegalInfo info) + // Trades + foreach (var z in GetValidEncounterTrades(pk, chain, game)) { - var chain = EncounterOrigin.GetOriginChain(pkm); - var game = (GameVersion)pkm.Version; - - // Mystery Gifts - foreach (var z in GetValidGifts(pkm, chain, game)) - { - // Don't bother deferring matches. - var match = z.GetMatchRating(pkm); - if (match != PartialMatch) - yield return z; - } - - // Trades - foreach (var z in GetValidEncounterTrades(pkm, chain, game)) - { - // Don't bother deferring matches. - var match = z.GetMatchRating(pkm); - if (match != PartialMatch) - yield return z; - } - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - // Static Encounter - // Defer everything if Safari Ball - bool safari = pkm.Ball == 0x05; // never static encounters - if (!safari) - { - foreach (var z in GetValidStaticEncounter(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - if (match == PartialMatch) - partial ??= z; - else - yield return z; - } - } - - // Encounter Slots - var slots = FrameFinder.GetFrames(info.PIDIV, pkm).ToList(); - foreach (var z in GetValidWildEncounters(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - if (match == PartialMatch) - { - partial ??= z; - continue; - } - - var frame = slots.Find(s => s.IsSlotCompatibile((EncounterSlot3)z, pkm)); - if (frame == null) - { - deferred ??= z; - continue; - } + // Don't bother deferring matches. + var match = z.GetMatchRating(pk); + if (match != PartialMatch) yield return z; - } + } - info.FrameMatches = false; - if (deferred is EncounterSlot3 x) - yield return x; + IEncounterable? partial = null; - if (pkm.Version != (int)GameVersion.CXD) // no eggs in C/XD + // Static Encounter + foreach (var z in GetValidStaticEncounter(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + if (match == PartialMatch) + partial ??= z; + else + yield return z; + } + + // Encounter Slots + foreach (var z in GetValidWildEncounters(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + if (match == PartialMatch) { - foreach (var z in GenerateEggs(pkm, 3)) - yield return z; + partial ??= z; + continue; } + yield return z; + } - if (partial is EncounterSlot3 y) + if (partial is not null) + yield return partial; + } + + private static IEnumerable GenerateRawEncounters3(PKM pk, LegalInfo info) + { + var chain = EncounterOrigin.GetOriginChain(pk); + var game = (GameVersion)pk.Version; + + // Mystery Gifts + foreach (var z in GetValidGifts(pk, chain, game)) + { + // Don't bother deferring matches. + var match = z.GetMatchRating(pk); + if (match != PartialMatch) + yield return z; + } + + // Trades + foreach (var z in GetValidEncounterTrades(pk, chain, game)) + { + // Don't bother deferring matches. + var match = z.GetMatchRating(pk); + if (match != PartialMatch) + yield return z; + } + + IEncounterable? deferred = null; + IEncounterable? partial = null; + + // Static Encounter + // Defer everything if Safari Ball + bool safari = pk.Ball == 0x05; // never static encounters + if (!safari) + { + foreach (var z in GetValidStaticEncounter(pk, chain, game)) { - var frame = slots.Find(s => s.IsSlotCompatibile(y, pkm)); - info.FrameMatches = frame != null; - yield return y; - } - - // do static encounters if they were deferred to end, spit out any possible encounters for invalid pkm - if (!safari) - yield break; - - partial = null; - - foreach (var z in GetValidStaticEncounter(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); + var match = z.GetMatchRating(pk); if (match == PartialMatch) partial ??= z; else yield return z; } - - if (partial is not null) - yield return partial; } - private static bool GetIsShadowLockValid(PKM pkm, LegalInfo info, EncounterStaticShadow s) + // Encounter Slots + var slots = FrameFinder.GetFrames(info.PIDIV, pk).ToList(); + foreach (var z in GetValidWildEncounters(pk, chain, game)) { - if (s.IVs.Count == 0) // not E-Reader - return LockFinder.IsAllShadowLockValid(s, info.PIDIV, pkm); - - // E-Reader have fixed IVs, and aren't recognized as CXD (no PID-IV correlation). - var possible = MethodFinder.GetColoEReaderMatches(pkm.EncryptionConstant); - foreach (var poss in possible) + var match = z.GetMatchRating(pk); + if (match == PartialMatch) { - if (!LockFinder.IsAllShadowLockValid(s, poss, pkm)) - continue; - info.PIDIV = poss; - return true; + partial ??= z; + continue; } - return false; + var frame = slots.Find(s => s.IsSlotCompatibile((EncounterSlot3)z, pk)); + if (frame == null) + { + deferred ??= z; + continue; + } + yield return z; } + + info.FrameMatches = false; + if (deferred is EncounterSlot3 x) + yield return x; + + if (pk.Version != (int)GameVersion.CXD) // no eggs in C/XD + { + foreach (var z in GenerateEggs(pk, 3)) + yield return z; + } + + if (partial is EncounterSlot3 y) + { + var frame = slots.Find(s => s.IsSlotCompatibile(y, pk)); + info.FrameMatches = frame != null; + yield return y; + } + + // do static encounters if they were deferred to end, spit out any possible encounters for invalid pk + if (!safari) + yield break; + + partial = null; + + foreach (var z in GetValidStaticEncounter(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + if (match == PartialMatch) + partial ??= z; + else + yield return z; + } + + if (partial is not null) + yield return partial; + } + + private static bool GetIsShadowLockValid(PKM pk, LegalInfo info, EncounterStaticShadow s) + { + if (s.IVs.Count == 0) // not E-Reader + return LockFinder.IsAllShadowLockValid(s, info.PIDIV, pk); + + // E-Reader have fixed IVs, and aren't recognized as CXD (no PID-IV correlation). + var possible = MethodFinder.GetColoEReaderMatches(pk.EncryptionConstant); + foreach (var poss in possible) + { + if (!LockFinder.IsAllShadowLockValid(s, poss, pk)) + continue; + info.PIDIV = poss; + return true; + } + + return false; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs index 294670aa3..501fe6564 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator4.cs @@ -8,125 +8,124 @@ using static PKHeX.Core.EncounterEggGenerator; using static PKHeX.Core.EncounterMatchRating; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class EncounterGenerator4 { - internal static class EncounterGenerator4 + public static IEnumerable GetEncounters(PKM pk, LegalInfo info) { - public static IEnumerable GetEncounters(PKM pkm, LegalInfo info) + info.PIDIV = MethodFinder.Analyze(pk); + var deferredPIDIV = new List(); + var deferredEType = new List(); + + foreach (var z in GenerateRawEncounters4(pk, info)) { - info.PIDIV = MethodFinder.Analyze(pkm); - var deferredPIDIV = new List(); - var deferredEType = new List(); - - foreach (var z in GenerateRawEncounters4(pkm, info)) - { - if (!info.PIDIV.Type.IsCompatible4(z, pkm)) - deferredPIDIV.Add(z); - else if (pkm is IGroundTile e && !(z is IGroundTypeTile t ? t.GroundTile.Contains(e.GroundTile) : e.GroundTile == 0)) - deferredEType.Add(z); - else - yield return z; - } - - foreach (var z in deferredEType) - yield return z; - - if (deferredPIDIV.Count == 0) - yield break; - - info.PIDIVMatches = false; - foreach (var z in deferredPIDIV) + if (!info.PIDIV.Type.IsCompatible4(z, pk)) + deferredPIDIV.Add(z); + else if (pk is IGroundTile e && !(z is IGroundTypeTile t ? t.GroundTile.Contains(e.GroundTile) : e.GroundTile == 0)) + deferredEType.Add(z); + else yield return z; } - private static IEnumerable GenerateRawEncounters4(PKM pkm, LegalInfo info) + foreach (var z in deferredEType) + yield return z; + + if (deferredPIDIV.Count == 0) + yield break; + + info.PIDIVMatches = false; + foreach (var z in deferredPIDIV) + yield return z; + } + + private static IEnumerable GenerateRawEncounters4(PKM pk, LegalInfo info) + { + var chain = EncounterOrigin.GetOriginChain(pk); + var game = (GameVersion)pk.Version; + if (pk.FatefulEncounter) { - var chain = EncounterOrigin.GetOriginChain(pkm); - var game = (GameVersion)pkm.Version; - if (pkm.FatefulEncounter) - { - int ctr = 0; - foreach (var z in GetValidGifts(pkm, chain, game)) - { yield return z; ++ctr; } - if (ctr != 0) yield break; - } - if (Locations.IsEggLocationBred4(pkm.Egg_Location, game)) - { - foreach (var z in GenerateEggs(pkm, 4)) - yield return z; - } - foreach (var z in GetValidEncounterTrades(pkm, chain, game)) + int ctr = 0; + foreach (var z in GetValidGifts(pk, chain, game)) + { yield return z; ++ctr; } + if (ctr != 0) yield break; + } + if (Locations.IsEggLocationBred4(pk.Egg_Location, game)) + { + foreach (var z in GenerateEggs(pk, 4)) yield return z; + } + foreach (var z in GetValidEncounterTrades(pk, chain, game)) + yield return z; - IEncounterable? deferred = null; - IEncounterable? partial = null; + IEncounterable? deferred = null; + IEncounterable? partial = null; - bool safariSport = pkm.Ball is (int)Ball.Sport or (int)Ball.Safari; // never static encounters - if (!safariSport) + bool safariSport = pk.Ball is (int)Ball.Sport or (int)Ball.Safari; // never static encounters + if (!safariSport) + { + foreach (var z in GetValidStaticEncounter(pk, chain, game)) { - foreach (var z in GetValidStaticEncounter(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - if (match == PartialMatch) - partial ??= z; - else - yield return z; - } - } - - var slots = FrameFinder.GetFrames(info.PIDIV, pkm).ToList(); - foreach (var slot in GetValidWildEncounters(pkm, chain, game)) - { - var z = (EncounterSlot4)slot; - var match = z.GetMatchRating(pkm); - if (match == PartialMatch) - { - partial ??= z; - continue; - } - - // Can use Radar to force the encounter slot to stay consistent across encounters. - if (z.CanUseRadar) - { - yield return slot; - continue; - } - - var frame = slots.Find(s => s.IsSlotCompatibile(z, pkm)); - if (frame == null) - { - deferred ??= z; - continue; - } - yield return z; - } - - info.FrameMatches = false; - if (deferred is EncounterSlot4 x) - yield return x; - - if (partial is EncounterSlot4 y) - { - var frame = slots.Find(s => s.IsSlotCompatibile(y, pkm)); - info.FrameMatches = frame != null; - yield return y; - } - - // do static encounters if they were deferred to end, spit out any possible encounters for invalid pkm - if (!safariSport) - yield break; - - foreach (var z in GetValidStaticEncounter(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); + var match = z.GetMatchRating(pk); if (match == PartialMatch) partial ??= z; else yield return z; } - - if (partial is not null) - yield return partial; } + + var slots = FrameFinder.GetFrames(info.PIDIV, pk).ToList(); + foreach (var slot in GetValidWildEncounters(pk, chain, game)) + { + var z = (EncounterSlot4)slot; + var match = z.GetMatchRating(pk); + if (match == PartialMatch) + { + partial ??= z; + continue; + } + + // Can use Radar to force the encounter slot to stay consistent across encounters. + if (z.CanUseRadar) + { + yield return slot; + continue; + } + + var frame = slots.Find(s => s.IsSlotCompatibile(z, pk)); + if (frame == null) + { + deferred ??= z; + continue; + } + yield return z; + } + + info.FrameMatches = false; + if (deferred is EncounterSlot4 x) + yield return x; + + if (partial is EncounterSlot4 y) + { + var frame = slots.Find(s => s.IsSlotCompatibile(y, pk)); + info.FrameMatches = frame != null; + yield return y; + } + + // do static encounters if they were deferred to end, spit out any possible encounters for invalid pk + if (!safariSport) + yield break; + + foreach (var z in GetValidStaticEncounter(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + if (match == PartialMatch) + partial ??= z; + else + yield return z; + } + + if (partial is not null) + yield return partial; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs index 6abdeafbb..514f0d7a4 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator5.cs @@ -7,74 +7,73 @@ using static PKHeX.Core.EncounterEggGenerator; using static PKHeX.Core.EncounterMatchRating; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class EncounterGenerator5 { - internal static class EncounterGenerator5 + public static IEnumerable GetEncounters(PKM pk) { - public static IEnumerable GetEncounters(PKM pkm) + int ctr = 0; + + var chain = EncounterOrigin.GetOriginChain(pk); + var game = (GameVersion)pk.Version; + + if (pk.FatefulEncounter) { - int ctr = 0; - - var chain = EncounterOrigin.GetOriginChain(pkm); - var game = (GameVersion)pkm.Version; - - if (pkm.FatefulEncounter) - { - foreach (var z in GetValidGifts(pkm, chain, game)) - { yield return z; ++ctr; } - if (ctr != 0) yield break; - } - - if (Locations.IsEggLocationBred5(pkm.Egg_Location)) - { - foreach (var z in GenerateEggs(pkm, 5)) - { yield return z; ++ctr; } - if (ctr == 0) yield break; - } - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - foreach (var z in GetValidStaticEncounter(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; ++ctr; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } + foreach (var z in GetValidGifts(pk, chain, game)) + { yield return z; ++ctr; } if (ctr != 0) yield break; - - foreach (var z in GetValidWildEncounters(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; ++ctr; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } - if (ctr != 0) yield break; - - foreach (var z in GetValidEncounterTrades(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; /*++ctr*/ break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } - - if (deferred != null) - yield return deferred; - - if (partial != null) - yield return partial; } + + if (Locations.IsEggLocationBred5(pk.Egg_Location)) + { + foreach (var z in GenerateEggs(pk, 5)) + { yield return z; ++ctr; } + if (ctr == 0) yield break; + } + + IEncounterable? deferred = null; + IEncounterable? partial = null; + + foreach (var z in GetValidStaticEncounter(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; ++ctr; break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + } + if (ctr != 0) yield break; + + foreach (var z in GetValidWildEncounters(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; ++ctr; break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + } + if (ctr != 0) yield break; + + foreach (var z in GetValidEncounterTrades(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; /*++ctr*/ break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + } + + if (deferred != null) + yield return deferred; + + if (partial != null) + yield return partial; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs index 36badf7df..5e1aab7f4 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator6.cs @@ -7,93 +7,92 @@ using static PKHeX.Core.EncounterEggGenerator; using static PKHeX.Core.EncounterMatchRating; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class EncounterGenerator6 { - internal static class EncounterGenerator6 + public static IEnumerable GetEncounters(PKM pk) { - public static IEnumerable GetEncounters(PKM pkm) + int ctr = 0; + + var chain = EncounterOrigin.GetOriginChain(pk); + var game = (GameVersion)pk.Version; + + IEncounterable? deferred = null; + IEncounterable? partial = null; + + if (pk.FatefulEncounter || pk.Met_Location == Locations.LinkGift6) { - int ctr = 0; - - var chain = EncounterOrigin.GetOriginChain(pkm); - var game = (GameVersion)pkm.Version; - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - if (pkm.FatefulEncounter || pkm.Met_Location == Locations.LinkGift6) + foreach (var z in GetValidGifts(pk, chain, game)) { - foreach (var z in GetValidGifts(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - ++ctr; - } - - if (ctr != 0) - { - if (deferred != null) - yield return deferred; - - if (partial != null) - yield return partial; - - yield break; - } - } - - if (Locations.IsEggLocationBred6(pkm.Egg_Location)) - { - foreach (var z in GenerateEggs(pkm, 6)) - { yield return z; ++ctr; } - if (ctr == 0) yield break; - } - - foreach (var z in GetValidStaticEncounter(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); + var match = z.GetMatchRating(pk); switch (match) { - case Match: yield return z; ++ctr; break; + case Match: yield return z; break; case Deferred: deferred ??= z; break; case PartialMatch: partial ??= z; break; } + ++ctr; } - if (ctr != 0) yield break; - foreach (var z in GetValidWildEncounters(pkm, chain, game)) + if (ctr != 0) { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; ++ctr; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } + if (deferred != null) + yield return deferred; + + if (partial != null) + yield return partial; + + yield break; } - if (ctr != 0) yield break; - - foreach (var z in GetValidEncounterTrades(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; /*++ctr*/ break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } - - if (deferred != null) - yield return deferred; - - if (partial != null) - yield return partial; } + + if (Locations.IsEggLocationBred6(pk.Egg_Location)) + { + foreach (var z in GenerateEggs(pk, 6)) + { yield return z; ++ctr; } + if (ctr == 0) yield break; + } + + foreach (var z in GetValidStaticEncounter(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; ++ctr; break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + } + if (ctr != 0) yield break; + + foreach (var z in GetValidWildEncounters(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; ++ctr; break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + } + if (ctr != 0) yield break; + + foreach (var z in GetValidEncounterTrades(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; /*++ctr*/ break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + } + + if (deferred != null) + yield return deferred; + + if (partial != null) + yield return partial; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs index de82c7da8..5be864a5a 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator7.cs @@ -7,166 +7,165 @@ using static PKHeX.Core.EncounterEggGenerator; using static PKHeX.Core.EncounterMatchRating; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class EncounterGenerator7 { - internal static class EncounterGenerator7 + public static IEnumerable GetEncounters(PKM pk) { - public static IEnumerable GetEncounters(PKM pkm) + var chain = EncounterOrigin.GetOriginChain(pk); + return pk.Version switch { - var chain = EncounterOrigin.GetOriginChain(pkm); - return pkm.Version switch + (int)GameVersion.GO => GetEncountersGO(pk, chain), + > (int)GameVersion.GO => GetEncountersGG(pk, chain), + _ => GetEncountersMainline(pk, chain), + }; + } + + internal static IEnumerable GetEncountersGO(PKM pk, EvoCriteria[] chain) + { + IEncounterable? deferred = null; + IEncounterable? partial = null; + + int ctr = 0; + foreach (var z in GetValidWildEncounters(pk, chain, GameVersion.GO)) + { + var match = z.GetMatchRating(pk); + switch (match) { - (int)GameVersion.GO => GetEncountersGO(pkm, chain), - > (int)GameVersion.GO => GetEncountersGG(pkm, chain), - _ => GetEncountersMainline(pkm, chain), - }; + case Match: yield return z; ++ctr; break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + } + if (ctr != 0) yield break; + + if (deferred != null) + yield return deferred; + + if (partial != null) + yield return partial; + } + + private static IEnumerable GetEncountersGG(PKM pk, EvoCriteria[] chain) + { + int ctr = 0; + var game = (GameVersion)pk.Version; + + if (pk.FatefulEncounter) + { + foreach (var z in GetValidGifts(pk, chain, game)) + { yield return z; ++ctr; } + if (ctr != 0) yield break; } - internal static IEnumerable GetEncountersGO(PKM pkm, EvoCriteria[] chain) + IEncounterable? deferred = null; + IEncounterable? partial = null; + + foreach (var z in GetValidStaticEncounter(pk, chain, game)) { - IEncounterable? deferred = null; - IEncounterable? partial = null; - - int ctr = 0; - foreach (var z in GetValidWildEncounters(pkm, chain, GameVersion.GO)) + var match = z.GetMatchRating(pk); + switch (match) { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; ++ctr; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } + case Match: yield return z; ++ctr; break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; } - if (ctr != 0) yield break; + } + if (ctr != 0) yield break; - if (deferred != null) - yield return deferred; + foreach (var z in GetValidWildEncounters(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; ++ctr; break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + } + if (ctr != 0) yield break; - if (partial != null) - yield return partial; + foreach (var z in GetValidEncounterTrades(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; /*++ctr*/ break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } } - private static IEnumerable GetEncountersGG(PKM pkm, EvoCriteria[] chain) + if (deferred != null) + yield return deferred; + + if (partial != null) + yield return partial; + } + + private static IEnumerable GetEncountersMainline(PKM pk, EvoCriteria[] chain) + { + int ctr = 0; + var game = (GameVersion)pk.Version; + + if (pk.FatefulEncounter) { - int ctr = 0; - var game = (GameVersion)pkm.Version; - - if (pkm.FatefulEncounter) - { - foreach (var z in GetValidGifts(pkm, chain, game)) - { yield return z; ++ctr; } - if (ctr != 0) yield break; - } - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - foreach (var z in GetValidStaticEncounter(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; ++ctr; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } + foreach (var z in GetValidGifts(pk, chain, game)) + { yield return z; ++ctr; } if (ctr != 0) yield break; - - foreach (var z in GetValidWildEncounters(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; ++ctr; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } - if (ctr != 0) yield break; - - foreach (var z in GetValidEncounterTrades(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; /*++ctr*/ break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } - - if (deferred != null) - yield return deferred; - - if (partial != null) - yield return partial; } - private static IEnumerable GetEncountersMainline(PKM pkm, EvoCriteria[] chain) + if (Locations.IsEggLocationBred6(pk.Egg_Location)) { - int ctr = 0; - var game = (GameVersion)pkm.Version; - - if (pkm.FatefulEncounter) - { - foreach (var z in GetValidGifts(pkm, chain, game)) - { yield return z; ++ctr; } - if (ctr != 0) yield break; - } - - if (Locations.IsEggLocationBred6(pkm.Egg_Location)) - { - foreach (var z in GenerateEggs(pkm, 7)) - { yield return z; ++ctr; } - if (ctr == 0) yield break; - } - - IEncounterable? deferred = null; - IEncounterable? partial = null; - - foreach (var z in GetValidStaticEncounter(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; ++ctr; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } - if (ctr != 0) yield break; - - foreach (var z in GetValidWildEncounters(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; ++ctr; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - } - if (ctr != 0) yield break; - - foreach (var z in GetValidEncounterTrades(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - switch (match) - { - case Match: yield return z; break; - case Deferred: deferred ??= z; break; - case PartialMatch: partial ??= z; break; - } - //++ctr; - } - - if (deferred != null) - yield return deferred; - - if (partial != null) - yield return partial; + foreach (var z in GenerateEggs(pk, 7)) + { yield return z; ++ctr; } + if (ctr == 0) yield break; } + + IEncounterable? deferred = null; + IEncounterable? partial = null; + + foreach (var z in GetValidStaticEncounter(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; ++ctr; break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + } + if (ctr != 0) yield break; + + foreach (var z in GetValidWildEncounters(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; ++ctr; break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + } + if (ctr != 0) yield break; + + foreach (var z in GetValidEncounterTrades(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + switch (match) + { + case Match: yield return z; break; + case Deferred: deferred ??= z; break; + case PartialMatch: partial ??= z; break; + } + //++ctr; + } + + if (deferred != null) + yield return deferred; + + if (partial != null) + yield return partial; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs index 50a8b69de..dbc664986 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8.cs @@ -7,88 +7,53 @@ using static PKHeX.Core.EncounterEggGenerator; using static PKHeX.Core.EncounterMatchRating; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class EncounterGenerator8 { - internal static class EncounterGenerator8 + public static IEnumerable GetEncounters(PKM pk) { - public static IEnumerable GetEncounters(PKM pkm) + var chain = EncounterOrigin.GetOriginChain(pk); + return pk.Version switch { - var chain = EncounterOrigin.GetOriginChain(pkm); - return pkm.Version switch - { - (int)GameVersion.GO => EncounterGenerator7.GetEncountersGO(pkm, chain), - (int)GameVersion.PLA => EncounterGenerator8a.GetEncounters(pkm, chain), - (int)GameVersion.BD or (int)GameVersion.SP => EncounterGenerator8b.GetEncounters(pkm, chain), - (int)GameVersion.SW when pkm.Met_Location == Locations.HOME_SWLA => EncounterGenerator8a.GetEncounters(pkm, chain), - (int)GameVersion.SW when pkm.Met_Location == Locations.HOME_SWBD => EncounterGenerator8b.GetEncountersFuzzy(pkm, chain, GameVersion.BD), - (int)GameVersion.SH when pkm.Met_Location == Locations.HOME_SHSP => EncounterGenerator8b.GetEncountersFuzzy(pkm, chain, GameVersion.SP), - _ => GetEncountersMainline(pkm, chain), - }; + (int)GameVersion.GO => EncounterGenerator7.GetEncountersGO(pk, chain), + (int)GameVersion.PLA => EncounterGenerator8a.GetEncounters(pk, chain), + (int)GameVersion.BD or (int)GameVersion.SP => EncounterGenerator8b.GetEncounters(pk, chain), + (int)GameVersion.SW when pk.Met_Location == Locations.HOME_SWLA => EncounterGenerator8a.GetEncounters(pk, chain), + (int)GameVersion.SW when pk.Met_Location == Locations.HOME_SWBD => EncounterGenerator8b.GetEncountersFuzzy(pk, chain, GameVersion.BD), + (int)GameVersion.SH when pk.Met_Location == Locations.HOME_SHSP => EncounterGenerator8b.GetEncountersFuzzy(pk, chain, GameVersion.SP), + _ => GetEncountersMainline(pk, chain), + }; + } + + private static IEnumerable GetEncountersMainline(PKM pk, EvoCriteria[] chain) + { + int ctr = 0; + var game = (GameVersion)pk.Version; + + if (pk.FatefulEncounter) + { + foreach (var z in GetValidGifts(pk, chain, game)) + { yield return z; ++ctr; } + if (ctr != 0) yield break; } - private static IEnumerable GetEncountersMainline(PKM pkm, EvoCriteria[] chain) + if (Locations.IsEggLocationBred6(pk.Egg_Location)) { - int ctr = 0; - var game = (GameVersion)pkm.Version; + foreach (var z in GenerateEggs(pk, 8)) + { yield return z; ++ctr; } + if (ctr == 0) yield break; + } - if (pkm.FatefulEncounter) + IEncounterable? cache = null; + EncounterMatchRating rating = None; + + // Trades + if (pk.Met_Location == Locations.LinkTrade6NPC) + { + foreach (var z in GetValidEncounterTrades(pk, chain, game)) { - foreach (var z in GetValidGifts(pkm, chain, game)) - { yield return z; ++ctr; } - if (ctr != 0) yield break; - } - - if (Locations.IsEggLocationBred6(pkm.Egg_Location)) - { - foreach (var z in GenerateEggs(pkm, 8)) - { yield return z; ++ctr; } - if (ctr == 0) yield break; - } - - IEncounterable? cache = null; - EncounterMatchRating rating = None; - - // Trades - if (pkm.Met_Location == Locations.LinkTrade6NPC) - { - foreach (var z in GetValidEncounterTrades(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - } - - if (cache != null) - yield return cache; - yield break; - } - - // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. - var encs = GetValidStaticEncounter(pkm, chain, game); - foreach (var z in encs) - { - var match = z.GetMatchRating(pkm); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - } - - foreach (var z in GetValidWildEncounters(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); + var match = z.GetMatchRating(pk); if (match == Match) { yield return z; @@ -102,6 +67,40 @@ private static IEnumerable GetEncountersMainline(PKM pkm, EvoCri if (cache != null) yield return cache; + yield break; } + + // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. + var encs = GetValidStaticEncounter(pk, chain, game); + foreach (var z in encs) + { + var match = z.GetMatchRating(pk); + if (match == Match) + { + yield return z; + } + else if (match < rating) + { + cache = z; + rating = match; + } + } + + foreach (var z in GetValidWildEncounters(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + if (match == Match) + { + yield return z; + } + else if (match < rating) + { + cache = z; + rating = match; + } + } + + if (cache != null) + yield return cache; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs index e61ef7514..92528d67a 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8a.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using static PKHeX.Core.MysteryGiftGenerator; using static PKHeX.Core.EncounterSlotGenerator; @@ -9,17 +9,17 @@ namespace PKHeX.Core; internal static class EncounterGenerator8a { - public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] chain) + public static IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain) { - if (pkm is PK8 { SWSH: false }) + if (pk is PK8 { SWSH: false }) yield break; - if (pkm.IsEgg) + if (pk.IsEgg) yield break; int ctr = 0; - if (pkm.FatefulEncounter) + if (pk.FatefulEncounter) { - foreach (var z in GetValidGifts(pkm, chain, GameVersion.PLA)) + foreach (var z in GetValidGifts(pk, chain, GameVersion.PLA)) { yield return z; ++ctr; } if (ctr != 0) yield break; } @@ -28,10 +28,10 @@ public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] c EncounterMatchRating rating = None; // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. - var encs = GetValidStaticEncounter(pkm, chain, GameVersion.PLA); + var encs = GetValidStaticEncounter(pk, chain, GameVersion.PLA); foreach (var z in encs) { - var match = z.GetMatchRating(pkm); + var match = z.GetMatchRating(pk); if (match == Match) { yield return z; @@ -43,9 +43,9 @@ public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] c } } - foreach (var z in GetValidWildEncounters(pkm, chain, GameVersion.PLA)) + foreach (var z in GetValidWildEncounters(pk, chain, GameVersion.PLA)) { - var match = z.GetMatchRating(pkm); + var match = z.GetMatchRating(pk); if (match == Match) { yield return z; diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs index 8c0d8eca4..5b2eaa2e8 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator8b.cs @@ -7,61 +7,40 @@ using static PKHeX.Core.EncounterEggGenerator; using static PKHeX.Core.EncounterMatchRating; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class EncounterGenerator8b { - internal static class EncounterGenerator8b + public static IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain) { - public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] chain) + if (pk is PK8) + yield break; + int ctr = 0; + var game = (GameVersion)pk.Version; + + if (pk.FatefulEncounter) { - if (pkm is PK8) - yield break; - int ctr = 0; - var game = (GameVersion)pkm.Version; + foreach (var z in GetValidGifts(pk, chain, game)) + { yield return z; ++ctr; } + if (ctr != 0) yield break; + } - if (pkm.FatefulEncounter) + if (Locations.IsEggLocationBred8b(pk.Egg_Location)) + { + foreach (var z in GenerateEggs(pk, 8)) + { yield return z; ++ctr; } + if (ctr == 0) yield break; + } + + IEncounterable? cache = null; + EncounterMatchRating rating = None; + + // Trades + if (!pk.IsEgg && pk.Met_Location == Locations.LinkTrade6NPC) + { + foreach (var z in GetValidEncounterTrades(pk, chain, game)) { - foreach (var z in GetValidGifts(pkm, chain, game)) - { yield return z; ++ctr; } - if (ctr != 0) yield break; - } - - if (Locations.IsEggLocationBred8b(pkm.Egg_Location)) - { - foreach (var z in GenerateEggs(pkm, 8)) - { yield return z; ++ctr; } - if (ctr == 0) yield break; - } - - IEncounterable? cache = null; - EncounterMatchRating rating = None; - - // Trades - if (!pkm.IsEgg && pkm.Met_Location == Locations.LinkTrade6NPC) - { - foreach (var z in GetValidEncounterTrades(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - } - - if (cache != null) - yield return cache; - yield break; - } - - // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. - var encs = GetValidStaticEncounter(pkm, chain, game); - foreach (var z in encs) - { - var match = z.GetMatchRating(pkm); + var match = z.GetMatchRating(pk); if (match == Match) { yield return z; @@ -73,9 +52,72 @@ public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] c } } - foreach (var z in GetValidWildEncounters(pkm, chain, game)) + if (cache != null) + yield return cache; + yield break; + } + + // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. + var encs = GetValidStaticEncounter(pk, chain, game); + foreach (var z in encs) + { + var match = z.GetMatchRating(pk); + if (match == Match) { - var match = z.GetMatchRating(pkm); + yield return z; + } + else if (match < rating) + { + cache = z; + rating = match; + } + } + + foreach (var z in GetValidWildEncounters(pk, chain, game)) + { + var match = z.GetMatchRating(pk); + if (match == Match) + { + yield return z; + } + else if (match < rating) + { + cache = z; + rating = match; + } + } + + if (cache != null) + yield return cache; + } + + public static IEnumerable GetEncountersFuzzy(PKM pk, EvoCriteria[] chain, GameVersion game) + { + int ctr = 0; + + if (pk.FatefulEncounter) + { + foreach (var z in GetValidGifts(pk, chain, game)) + { yield return z; ++ctr; } + if (ctr != 0) yield break; + } + + if (pk.Egg_Location == Locations.HOME_SWSHBDSPEgg && pk.Met_Level == 1) + { + foreach (var z in GenerateEggs(pk, 8)) + { yield return z; ++ctr; } + if (ctr == 0) yield break; + } + + IEncounterable? cache = null; + EncounterMatchRating rating = None; + + // Trades + if (!pk.IsEgg) + { + foreach (var z in GetValidEncounterTrades(pk, chain, game)) + { + var match = z.GetMatchRating(pk); if (match == Match) { yield return z; @@ -91,85 +133,42 @@ public static IEnumerable GetEncounters(PKM pkm, EvoCriteria[] c yield return cache; } - public static IEnumerable GetEncountersFuzzy(PKM pkm, EvoCriteria[] chain, GameVersion game) + // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. + var encs = GetValidStaticEncounter(pk, chain, game); + foreach (var z in encs) { - int ctr = 0; - - if (pkm.FatefulEncounter) + var match = z.GetMatchRating(pk); + if (match == Match) { - foreach (var z in GetValidGifts(pkm, chain, game)) - { yield return z; ++ctr; } - if (ctr != 0) yield break; + yield return z; } - - if (pkm.Egg_Location == Locations.HOME_SWSHBDSPEgg && pkm.Met_Level == 1) + else if (match < rating) { - foreach (var z in GenerateEggs(pkm, 8)) - { yield return z; ++ctr; } - if (ctr == 0) yield break; + cache = z; + rating = match; } - - IEncounterable? cache = null; - EncounterMatchRating rating = None; - - // Trades - if (!pkm.IsEgg) - { - foreach (var z in GetValidEncounterTrades(pkm, chain, game)) - { - var match = z.GetMatchRating(pkm); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - } - - if (cache != null) - yield return cache; - } - - // Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded. - var encs = GetValidStaticEncounter(pkm, chain, game); - foreach (var z in encs) - { - var match = z.GetMatchRating(pkm); - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - } - - // Only yield if Safari and Marsh encounters match. - bool safari = pkm is PK8 { Ball: (int)Ball.Safari }; - foreach (var z in GetValidWildEncounters(pkm, chain, game)) - { - var marsh = Locations.IsSafariZoneLocation8b(z.Location); - var match = z.GetMatchRating(pkm); - if (safari != marsh) - match = DeferredErrors; - if (match == Match) - { - yield return z; - } - else if (match < rating) - { - cache = z; - rating = match; - } - } - - if (cache != null) - yield return cache; } + + // Only yield if Safari and Marsh encounters match. + bool safari = pk is PK8 { Ball: (int)Ball.Safari }; + foreach (var z in GetValidWildEncounters(pk, chain, game)) + { + var marsh = Locations.IsSafariZoneLocation8b(z.Location); + var match = z.GetMatchRating(pk); + if (safari != marsh) + match = DeferredErrors; + if (match == Match) + { + yield return z; + } + else if (match < rating) + { + cache = z; + rating = match; + } + } + + if (cache != null) + yield return cache; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs index 6f22516d4..d75ccf76f 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterCriteria.cs @@ -2,181 +2,180 @@ using System.Collections.Generic; using static PKHeX.Core.AbilityPermission; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Object that can be fed to a converter to ensure that the resulting meets rough specifications. +/// +public sealed record EncounterCriteria { /// - /// Object that can be fed to a converter to ensure that the resulting meets rough specifications. + /// Default criteria with no restrictions (random) for all fields. /// - public sealed record EncounterCriteria + public static readonly EncounterCriteria Unrestricted = new(); + + /// End result's gender. + /// Leave as -1 to not restrict gender. + public int Gender { get; init; } = -1; + + /// End result's ability numbers permitted. + /// Leave as to not restrict ability. + public AbilityPermission AbilityNumber { get; init; } = Any12H; + + /// End result's nature. + /// Leave as to not restrict nature. + public Nature Nature { get; init; } = Nature.Random; + + /// End result's nature. + /// Leave as to not restrict shininess. + public Shiny Shiny { get; init; } + + public int IV_HP { get; init; } = RandomIV; + public int IV_ATK { get; init; } = RandomIV; + public int IV_DEF { get; init; } = RandomIV; + public int IV_SPA { get; init; } = RandomIV; + public int IV_SPD { get; init; } = RandomIV; + public int IV_SPE { get; init; } = RandomIV; + + // unused + public int HPType { get; init; } = -1; + + private const int RandomIV = -1; + + /// + /// Checks if the IVs are compatible with the encounter's defined IV restrictions. + /// + /// Encounter template's IV restrictions. Speed is not last. + /// Destination generation + /// True if compatible, false if incompatible. + public bool IsIVsCompatible(Span encounterIVs, int generation) { - /// - /// Default criteria with no restrictions (random) for all fields. - /// - public static readonly EncounterCriteria Unrestricted = new(); + var IVs = encounterIVs; + if (!ivCanMatch(IV_HP , IVs[0])) return false; + if (!ivCanMatch(IV_ATK, IVs[1])) return false; + if (!ivCanMatch(IV_DEF, IVs[2])) return false; + if (!ivCanMatch(IV_SPE, IVs[3])) return false; + if (!ivCanMatch(IV_SPA, IVs[4])) return false; + if (!ivCanMatch(IV_SPD, IVs[5])) return false; - /// End result's gender. - /// Leave as -1 to not restrict gender. - public int Gender { get; init; } = -1; - - /// End result's ability numbers permitted. - /// Leave as to not restrict ability. - public AbilityPermission AbilityNumber { get; init; } = Any12H; - - /// End result's nature. - /// Leave as to not restrict nature. - public Nature Nature { get; init; } = Nature.Random; - - /// End result's nature. - /// Leave as to not restrict shininess. - public Shiny Shiny { get; init; } - - public int IV_HP { get; init; } = RandomIV; - public int IV_ATK { get; init; } = RandomIV; - public int IV_DEF { get; init; } = RandomIV; - public int IV_SPA { get; init; } = RandomIV; - public int IV_SPD { get; init; } = RandomIV; - public int IV_SPE { get; init; } = RandomIV; - - // unused - public int HPType { get; init; } = -1; - - private const int RandomIV = -1; - - /// - /// Checks if the IVs are compatible with the encounter's defined IV restrictions. - /// - /// Encounter template's IV restrictions. Speed is not last. - /// Destination generation - /// True if compatible, false if incompatible. - public bool IsIVsCompatible(Span encounterIVs, int generation) + bool ivCanMatch(int requestedIV, int encounterIV) { - var IVs = encounterIVs; - if (!ivCanMatch(IV_HP , IVs[0])) return false; - if (!ivCanMatch(IV_ATK, IVs[1])) return false; - if (!ivCanMatch(IV_DEF, IVs[2])) return false; - if (!ivCanMatch(IV_SPE, IVs[3])) return false; - if (!ivCanMatch(IV_SPA, IVs[4])) return false; - if (!ivCanMatch(IV_SPD, IVs[5])) return false; - - bool ivCanMatch(int requestedIV, int encounterIV) - { - if (requestedIV >= 30 && generation >= 6) // hyper training possible - return true; - return encounterIV == RandomIV || requestedIV == RandomIV || requestedIV == encounterIV; - } - - return true; + if (requestedIV >= 30 && generation >= 6) // hyper training possible + return true; + return encounterIV == RandomIV || requestedIV == RandomIV || requestedIV == encounterIV; } - /// - /// Template data (end result). - /// Personal table the end result will exist with. - public static EncounterCriteria GetCriteria(IBattleTemplate s, PersonalTable t) - { - var pi = t.GetFormEntry(s.Species, s.Form); - return GetCriteria(s, pi); - } + return true; + } - /// - /// Creates a new by loading parameters from the provided . - /// - /// Template data (end result). - /// Personal info the end result will exist with. - /// Initialized criteria data to be passed to generators. - public static EncounterCriteria GetCriteria(IBattleTemplate s, PersonalInfo pi) => new() - { - Gender = s.Gender, - IV_HP = s.IVs[0], - IV_ATK = s.IVs[1], - IV_DEF = s.IVs[2], - IV_SPE = s.IVs[3], - IV_SPA = s.IVs[4], - IV_SPD = s.IVs[5], - HPType = s.HiddenPowerType, + /// + /// Template data (end result). + /// Personal table the end result will exist with. + public static EncounterCriteria GetCriteria(IBattleTemplate s, PersonalTable t) + { + var pi = t.GetFormEntry(s.Species, s.Form); + return GetCriteria(s, pi); + } - AbilityNumber = GetAbilityNumber(s.Ability, pi), - Nature = NatureUtil.GetNature(s.Nature), - Shiny = s.Shiny ? Shiny.Always : Shiny.Never, - }; + /// + /// Creates a new by loading parameters from the provided . + /// + /// Template data (end result). + /// Personal info the end result will exist with. + /// Initialized criteria data to be passed to generators. + public static EncounterCriteria GetCriteria(IBattleTemplate s, PersonalInfo pi) => new() + { + Gender = s.Gender, + IV_HP = s.IVs[0], + IV_ATK = s.IVs[1], + IV_DEF = s.IVs[2], + IV_SPE = s.IVs[3], + IV_SPA = s.IVs[4], + IV_SPD = s.IVs[5], + HPType = s.HiddenPowerType, - private static AbilityPermission GetAbilityNumber(int ability, PersonalInfo pi) - { - var abilities = pi.Abilities; - if (abilities.Count < 2) - return 0; - var dual = GetAbilityValueDual(ability, abilities); - if (abilities.Count == 2) // prior to gen5 - return dual; - if (abilities[2] == ability) - return dual == 0 ? Any12H : OnlyHidden; + AbilityNumber = GetAbilityNumber(s.Ability, pi), + Nature = NatureUtil.GetNature(s.Nature), + Shiny = s.Shiny ? Shiny.Always : Shiny.Never, + }; + + private static AbilityPermission GetAbilityNumber(int ability, PersonalInfo pi) + { + var abilities = pi.Abilities; + if (abilities.Count < 2) + return 0; + var dual = GetAbilityValueDual(ability, abilities); + if (abilities.Count == 2) // prior to gen5 return dual; - } + if (abilities[2] == ability) + return dual == 0 ? Any12H : OnlyHidden; + return dual; + } - private static AbilityPermission GetAbilityValueDual(int ability, IReadOnlyList abilities) - { - if (ability == abilities[0]) - return ability != abilities[1] ? OnlyFirst : Any12; - return ability == abilities[1] ? OnlySecond : Any12; - } + private static AbilityPermission GetAbilityValueDual(int ability, IReadOnlyList abilities) + { + if (ability == abilities[0]) + return ability != abilities[1] ? OnlyFirst : Any12; + return ability == abilities[1] ? OnlySecond : Any12; + } - /// - /// Gets a random nature to generate, based off an encounter's . - /// - public Nature GetNature(Nature encValue) - { - if ((uint)encValue < 25) - return encValue; - if (Nature != Nature.Random) - return Nature; - return (Nature)Util.Rand.Next(25); - } + /// + /// Gets a random nature to generate, based off an encounter's . + /// + public Nature GetNature(Nature encValue) + { + if ((uint)encValue < 25) + return encValue; + if (Nature != Nature.Random) + return Nature; + return (Nature)Util.Rand.Next(25); + } - /// - /// Gets a random gender to generate, based off an encounter's . - /// - public int GetGender(int gender, PersonalInfo pkPersonalInfo) - { - if ((uint)gender < 3) - return gender; - if (!pkPersonalInfo.IsDualGender) - return pkPersonalInfo.FixedGender; - if (Gender >= 0) - return Gender; - return pkPersonalInfo.RandomGender(); - } + /// + /// Gets a random gender to generate, based off an encounter's . + /// + public int GetGender(int gender, PersonalInfo pkPersonalInfo) + { + if ((uint)gender < 3) + return gender; + if (!pkPersonalInfo.IsDualGender) + return pkPersonalInfo.FixedGender; + if (Gender >= 0) + return Gender; + return pkPersonalInfo.RandomGender(); + } - /// - /// Gets a random ability index (0/1/2) to generate, based off an encounter's . - /// - public int GetAbilityFromNumber(AbilityPermission num) - { - if (num.IsSingleValue(out int index)) // fixed number - return index; + /// + /// Gets a random ability index (0/1/2) to generate, based off an encounter's . + /// + public int GetAbilityFromNumber(AbilityPermission num) + { + if (num.IsSingleValue(out int index)) // fixed number + return index; - bool canBeHidden = num.CanBeHidden(); - return GetAbilityIndexPreference(canBeHidden); - } + bool canBeHidden = num.CanBeHidden(); + return GetAbilityIndexPreference(canBeHidden); + } - private int GetAbilityIndexPreference(bool canBeHidden = false) => AbilityNumber switch - { - OnlyFirst => 0, - OnlySecond => 1, - OnlyHidden or Any12H when canBeHidden => 2, // hidden allowed - _ => Util.Rand.Next(2), - }; + private int GetAbilityIndexPreference(bool canBeHidden = false) => AbilityNumber switch + { + OnlyFirst => 0, + OnlySecond => 1, + OnlyHidden or Any12H when canBeHidden => 2, // hidden allowed + _ => Util.Rand.Next(2), + }; - /// - /// Applies random IVs without any correlation. - /// - /// Entity to mutate. - public void SetRandomIVs(PKM pk) - { - pk.IV_HP = IV_HP != RandomIV ? IV_HP : Util.Rand.Next(32); - pk.IV_ATK = IV_ATK != RandomIV ? IV_ATK : Util.Rand.Next(32); - pk.IV_DEF = IV_DEF != RandomIV ? IV_DEF : Util.Rand.Next(32); - pk.IV_SPA = IV_SPA != RandomIV ? IV_SPA : Util.Rand.Next(32); - pk.IV_SPD = IV_SPD != RandomIV ? IV_SPD : Util.Rand.Next(32); - pk.IV_SPE = IV_SPE != RandomIV ? IV_SPE : Util.Rand.Next(32); - } + /// + /// Applies random IVs without any correlation. + /// + /// Entity to mutate. + public void SetRandomIVs(PKM pk) + { + pk.IV_HP = IV_HP != RandomIV ? IV_HP : Util.Rand.Next(32); + pk.IV_ATK = IV_ATK != RandomIV ? IV_ATK : Util.Rand.Next(32); + pk.IV_DEF = IV_DEF != RandomIV ? IV_DEF : Util.Rand.Next(32); + pk.IV_SPA = IV_SPA != RandomIV ? IV_SPA : Util.Rand.Next(32); + pk.IV_SPD = IV_SPD != RandomIV ? IV_SPD : Util.Rand.Next(32); + pk.IV_SPE = IV_SPE != RandomIV ? IV_SPE : Util.Rand.Next(32); } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs index 5a37cb33f..5f51dfc81 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterFinder.cs @@ -1,198 +1,197 @@ using System; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Finds matching data and relevant for a . +/// +public static class EncounterFinder { /// - /// Finds matching data and relevant for a . + /// Iterates through all possible encounters until a sufficient match is found /// - public static class EncounterFinder + /// + /// The iterator lazily finds matching encounters, then verifies secondary checks to weed out any non-exact matches. + /// + /// Source data to find a match for + /// Object to store matched encounter info + /// + /// Information containing the matched encounter and any parsed checks. + /// If no clean match is found, the last checked match is returned. + /// If no match is found, an invalid encounter object is returned. + /// + public static void FindVerifiedEncounter(PKM pk, LegalInfo info) { - /// - /// Iterates through all possible encounters until a sufficient match is found - /// - /// - /// The iterator lazily finds matching encounters, then verifies secondary checks to weed out any non-exact matches. - /// - /// Source data to find a match for - /// Object to store matched encounter info - /// - /// Information containing the matched encounter and any parsed checks. - /// If no clean match is found, the last checked match is returned. - /// If no match is found, an invalid encounter object is returned. - /// - public static void FindVerifiedEncounter(PKM pkm, LegalInfo info) + var encounters = EncounterGenerator.GetEncounters(pk, info); + + using var encounter = new PeekEnumerator(encounters); + if (!encounter.PeekIsNext()) { - var encounters = EncounterGenerator.GetEncounters(pkm, info); - - using var encounter = new PeekEnumerator(encounters); - if (!encounter.PeekIsNext()) - { - VerifyWithoutEncounter(pkm, info); - return; - } - - var first = encounter.Current; - var EncounterValidator = EncounterVerifier.GetEncounterVerifierMethod(first.Generation); - while (encounter.MoveNext()) - { - var enc = encounter.Current; - - // Check for basic compatibility. - var e = EncounterValidator(pkm, enc); - if (!e.Valid && encounter.PeekIsNext()) - continue; - - // Looks like we might have a good enough match. Check if this is really a good match. - info.EncounterMatch = enc; - info.Parse.Add(e); - if (!VerifySecondaryChecks(pkm, info, encounter)) - continue; - - // Sanity Check -- Some secondary checks might not be as thorough as the partial-match leak-through checks done by the encounter. - if (enc is not IEncounterMatch mx) - break; - - var match = mx.GetMatchRating(pkm); - if (match != EncounterMatchRating.PartialMatch) - break; - - // Reaching here implies the encounter wasn't valid. Try stepping to the next encounter. - if (encounter.PeekIsNext()) - continue; - - // We ran out of possible encounters without finding a suitable match; add a message indicating that the encounter is not a complete match. - info.Parse.Add(new CheckResult(Severity.Invalid, LEncInvalid, CheckIdentifier.Encounter)); - break; - } - - if (!info.FrameMatches && info.EncounterMatch is EncounterSlot {Version: not GameVersion.CXD}) // if false, all valid RNG frame matches have already been consumed - info.Parse.Add(new CheckResult(ParseSettings.RNGFrameNotFound, LEncConditionBadRNGFrame, CheckIdentifier.PID)); // todo for further confirmation - if (!info.PIDIVMatches) // if false, all valid PIDIV matches have already been consumed - info.Parse.Add(new CheckResult(Severity.Invalid, LPIDTypeMismatch, CheckIdentifier.PID)); + VerifyWithoutEncounter(pk, info); + return; } - /// - /// Checks supplementary info to see if the encounter is still valid. - /// - /// - /// When an encounter is initially validated, only encounter-related checks are performed. - /// By checking Moves, Evolution, and data, a best match encounter can be found. - /// If the encounter is not valid, the method will not reject it unless another encounter is available to check. - /// - /// Source data to check the match for - /// Information containing the matched encounter - /// Peekable iterator - /// Indication whether or not the encounter passes secondary checks - private static bool VerifySecondaryChecks(PKM pkm, LegalInfo info, PeekEnumerator iterator) + var first = encounter.Current; + var EncounterValidator = EncounterVerifier.GetEncounterVerifierMethod(first.Generation); + while (encounter.MoveNext()) { - var relearn = info.Relearn; - if (pkm.Format >= 6) + var enc = encounter.Current; + + // Check for basic compatibility. + var e = EncounterValidator(pk, enc); + if (!e.Valid && encounter.PeekIsNext()) + continue; + + // Looks like we might have a good enough match. Check if this is really a good match. + info.EncounterMatch = enc; + info.Parse.Add(e); + if (!VerifySecondaryChecks(pk, info, encounter)) + continue; + + // Sanity Check -- Some secondary checks might not be as thorough as the partial-match leak-through checks done by the encounter. + if (enc is not IEncounterMatch mx) + break; + + var match = mx.GetMatchRating(pk); + if (match != EncounterMatchRating.PartialMatch) + break; + + // Reaching here implies the encounter wasn't valid. Try stepping to the next encounter. + if (encounter.PeekIsNext()) + continue; + + // We ran out of possible encounters without finding a suitable match; add a message indicating that the encounter is not a complete match. + info.Parse.Add(new CheckResult(Severity.Invalid, LEncInvalid, CheckIdentifier.Encounter)); + break; + } + + if (!info.FrameMatches && info.EncounterMatch is EncounterSlot {Version: not GameVersion.CXD}) // if false, all valid RNG frame matches have already been consumed + info.Parse.Add(new CheckResult(ParseSettings.RNGFrameNotFound, LEncConditionBadRNGFrame, CheckIdentifier.PID)); // todo for further confirmation + if (!info.PIDIVMatches) // if false, all valid PIDIV matches have already been consumed + info.Parse.Add(new CheckResult(Severity.Invalid, LPIDTypeMismatch, CheckIdentifier.PID)); + } + + /// + /// Checks supplementary info to see if the encounter is still valid. + /// + /// + /// When an encounter is initially validated, only encounter-related checks are performed. + /// By checking Moves, Evolution, and data, a best match encounter can be found. + /// If the encounter is not valid, the method will not reject it unless another encounter is available to check. + /// + /// Source data to check the match for + /// Information containing the matched encounter + /// Peekable iterator + /// Indication whether or not the encounter passes secondary checks + private static bool VerifySecondaryChecks(PKM pk, LegalInfo info, PeekEnumerator iterator) + { + var relearn = info.Relearn; + if (pk.Format >= 6) + { + VerifyRelearnMoves.VerifyRelearn(pk, info.EncounterOriginal, relearn); + if (!Array.TrueForAll(relearn, z => z.Valid) && iterator.PeekIsNext()) + return false; + } + else + { + foreach (var p in relearn) + VerifyRelearnMoves.DummyValid(p); + } + + VerifyCurrentMoves.VerifyMoves(pk, info); + if (!Array.TrueForAll(info.Moves, z => z.Valid) && iterator.PeekIsNext()) + return false; + + if (!info.Parse.TrueForAll(z => z.Valid) && iterator.PeekIsNext()) + return false; + + var evo = EvolutionVerifier.VerifyEvolution(pk, info); + if (!evo.Valid && iterator.PeekIsNext()) + return false; + + // Memories of Knowing a move which is later forgotten can be problematic with encounters that have special moves. + if (pk is ITrainerMemories m) + { + if (m is IMemoryOT o && MemoryPermissions.IsMemoryOfKnownMove(o.OT_Memory)) { - VerifyRelearnMoves.VerifyRelearn(pkm, info.EncounterOriginal, relearn); - if (!Array.TrueForAll(relearn, z => z.Valid) && iterator.PeekIsNext()) + var mem = MemoryVariableSet.Read(m, 0); + if (!MemoryPermissions.CanKnowMove(pk, mem, info.EncounterMatch.Generation, info)) return false; } - else + if (m is IMemoryHT h && MemoryPermissions.IsMemoryOfKnownMove(h.HT_Memory) && !pk.HasMove(h.HT_TextVar)) { - foreach (var p in relearn) - VerifyRelearnMoves.DummyValid(p); + var mem = MemoryVariableSet.Read(m, 1); + if (!MemoryPermissions.CanKnowMove(pk, mem, pk.Format, info)) + return false; } - - VerifyCurrentMoves.VerifyMoves(pkm, info); - if (!Array.TrueForAll(info.Moves, z => z.Valid) && iterator.PeekIsNext()) - return false; - - if (!info.Parse.TrueForAll(z => z.Valid) && iterator.PeekIsNext()) - return false; - - var evo = EvolutionVerifier.VerifyEvolution(pkm, info); - if (!evo.Valid && iterator.PeekIsNext()) - return false; - - // Memories of Knowing a move which is later forgotten can be problematic with encounters that have special moves. - if (pkm is ITrainerMemories m) + } + else if (pk is PK1 pk1) + { + var hasGen2 = Array.Exists(info.Moves, z => z.Generation is not 1); + if (hasGen2) { - if (m is IMemoryOT o && MemoryPermissions.IsMemoryOfKnownMove(o.OT_Memory)) - { - var mem = MemoryVariableSet.Read(m, 0); - if (!MemoryPermissions.CanKnowMove(pkm, mem, info.EncounterMatch.Generation, info)) - return false; - } - if (m is IMemoryHT h && MemoryPermissions.IsMemoryOfKnownMove(h.HT_Memory) && !pkm.HasMove(h.HT_TextVar)) - { - var mem = MemoryVariableSet.Read(m, 1); - if (!MemoryPermissions.CanKnowMove(pkm, mem, pkm.Format, info)) - return false; - } + if (!ParseSettings.AllowGen1Tradeback) + return false; + if (!PK1.IsCatchRateHeldItem(pk1.Catch_Rate)) + return false; } - else if (pkm is PK1 pk1) - { - var hasGen2 = Array.Exists(info.Moves, z => z.Generation is not 1); - if (hasGen2) - { - if (!ParseSettings.AllowGen1Tradeback) - return false; - if (!PK1.IsCatchRateHeldItem(pk1.Catch_Rate)) - return false; - } - } - - info.Parse.Add(evo); - return true; } - /// - /// Returns legality info for an unmatched encounter scenario, including a hint as to what the actual match could be. - /// - /// Source data to check the match for - /// Information containing the unmatched encounter - /// Updated information pertaining to the unmatched encounter - private static void VerifyWithoutEncounter(PKM pkm, LegalInfo info) - { - info.EncounterMatch = new EncounterInvalid(pkm); - string hint = GetHintWhyNotFound(pkm, info.EncounterMatch.Generation); - - info.Parse.Add(new CheckResult(Severity.Invalid, hint, CheckIdentifier.Encounter)); - VerifyRelearnMoves.VerifyRelearn(pkm, info.EncounterOriginal, info.Relearn); - VerifyCurrentMoves.VerifyMoves(pkm, info); - } - - private static string GetHintWhyNotFound(PKM pkm, int gen) - { - if (WasGiftEgg(pkm, gen, pkm.Egg_Location)) - return LEncGift; - if (WasEventEgg(pkm, gen)) - return LEncGiftEggEvent; - if (WasEvent(pkm, gen)) - return LEncGiftNotFound; - return LEncInvalid; - } - - private static bool WasGiftEgg(PKM pkm, int gen, int loc) => !pkm.FatefulEncounter && gen switch - { - 3 => pkm.IsEgg && pkm.Met_Location == 253, // Gift Egg, indistinguible from normal eggs after hatch - 4 => Legal.GiftEggLocation4.Contains(loc) || (pkm.Format != 4 && (loc == Locations.Faraway4 && pkm.HGSS)), - 5 => loc is Locations.Breeder5, - _ => loc is Locations.Breeder6, - }; - - private static bool WasEventEgg(PKM pkm, int gen) => gen switch - { - // Event Egg, indistinguishable from normal eggs after hatch - // can't tell after transfer - 3 => pkm.Format == 3 && pkm.IsEgg && Locations.IsEventLocation3(pkm.Met_Location), - - // Manaphy was the only generation 4 released event egg - _ => pkm.FatefulEncounter && pkm.Egg_Day != 0, - }; - - private static bool WasEvent(PKM pkm, int gen) => pkm.FatefulEncounter || gen switch - { - 3 => Locations.IsEventLocation3(pkm.Met_Location) && pkm.Format == 3, - 4 => Locations.IsEventLocation4(pkm.Met_Location) && pkm.Format == 4, - >=5 => Locations.IsEventLocation5(pkm.Met_Location), - _ => false, - }; + info.Parse.Add(evo); + return true; } + + /// + /// Returns legality info for an unmatched encounter scenario, including a hint as to what the actual match could be. + /// + /// Source data to check the match for + /// Information containing the unmatched encounter + /// Updated information pertaining to the unmatched encounter + private static void VerifyWithoutEncounter(PKM pk, LegalInfo info) + { + info.EncounterMatch = new EncounterInvalid(pk); + string hint = GetHintWhyNotFound(pk, info.EncounterMatch.Generation); + + info.Parse.Add(new CheckResult(Severity.Invalid, hint, CheckIdentifier.Encounter)); + VerifyRelearnMoves.VerifyRelearn(pk, info.EncounterOriginal, info.Relearn); + VerifyCurrentMoves.VerifyMoves(pk, info); + } + + private static string GetHintWhyNotFound(PKM pk, int gen) + { + if (WasGiftEgg(pk, gen, pk.Egg_Location)) + return LEncGift; + if (WasEventEgg(pk, gen)) + return LEncGiftEggEvent; + if (WasEvent(pk, gen)) + return LEncGiftNotFound; + return LEncInvalid; + } + + private static bool WasGiftEgg(PKM pk, int gen, int loc) => !pk.FatefulEncounter && gen switch + { + 3 => pk.IsEgg && pk.Met_Location == 253, // Gift Egg, indistinguible from normal eggs after hatch + 4 => Legal.GiftEggLocation4.Contains(loc) || (pk.Format != 4 && (loc == Locations.Faraway4 && pk.HGSS)), + 5 => loc is Locations.Breeder5, + _ => loc is Locations.Breeder6, + }; + + private static bool WasEventEgg(PKM pk, int gen) => gen switch + { + // Event Egg, indistinguishable from normal eggs after hatch + // can't tell after transfer + 3 => pk.Format == 3 && pk.IsEgg && Locations.IsEventLocation3(pk.Met_Location), + + // Manaphy was the only generation 4 released event egg + _ => pk.FatefulEncounter && pk.Egg_Day != 0, + }; + + private static bool WasEvent(PKM pk, int gen) => pk.FatefulEncounter || gen switch + { + 3 => Locations.IsEventLocation3(pk.Met_Location) && pk.Format == 3, + 4 => Locations.IsEventLocation4(pk.Met_Location) && pk.Format == 4, + >=5 => Locations.IsEventLocation5(pk.Met_Location), + _ => false, + }; } diff --git a/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator.cs index 77d57c3c2..25af9574e 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/EncounterGenerator.cs @@ -1,34 +1,33 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generates matching data and relevant for a . +/// Logic for generating possible in-game encounter data. +/// +public static class EncounterGenerator { /// - /// Generates matching data and relevant for a . - /// Logic for generating possible in-game encounter data. + /// Generates possible data according to the input PKM data and legality info. /// - public static class EncounterGenerator + /// PKM data + /// Legality information + /// Possible encounters + /// + /// The iterator lazily finds possible encounters. If no encounters are possible, the enumerable will be empty. + /// + public static IEnumerable GetEncounters(PKM pk, LegalInfo info) => info.Generation switch { - /// - /// Generates possible data according to the input PKM data and legality info. - /// - /// PKM data - /// Legality information - /// Possible encounters - /// - /// The iterator lazily finds possible encounters. If no encounters are possible, the enumerable will be empty. - /// - public static IEnumerable GetEncounters(PKM pkm, LegalInfo info) => info.Generation switch - { - 1 => EncounterGenerator12.GetEncounters12(pkm, info), - 2 => EncounterGenerator12.GetEncounters12(pkm, info), - 3 => EncounterGenerator3.GetEncounters(pkm, info), - 4 => EncounterGenerator4.GetEncounters(pkm, info), - 5 => EncounterGenerator5.GetEncounters(pkm), - 6 => EncounterGenerator6.GetEncounters(pkm), - 7 => EncounterGenerator7.GetEncounters(pkm), - 8 => EncounterGenerator8.GetEncounters(pkm), - _ => Array.Empty(), - }; - } + 1 => EncounterGenerator12.GetEncounters12(pk, info), + 2 => EncounterGenerator12.GetEncounters12(pk, info), + 3 => EncounterGenerator3.GetEncounters(pk, info), + 4 => EncounterGenerator4.GetEncounters(pk, info), + 5 => EncounterGenerator5.GetEncounters(pk), + 6 => EncounterGenerator6.GetEncounters(pk), + 7 => EncounterGenerator7.GetEncounters(pk), + 8 => EncounterGenerator8.GetEncounters(pk), + _ => Array.Empty(), + }; } diff --git a/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs index 91d1babec..ec2293831 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterMovesetGenerator.cs @@ -4,434 +4,433 @@ using System.Linq; using System.Runtime.CompilerServices; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generates weakly matched objects for an input (and/or criteria). +/// +public static class EncounterMovesetGenerator { /// - /// Generates weakly matched objects for an input (and/or criteria). + /// Order in which objects are yielded from the generator. /// - public static class EncounterMovesetGenerator + // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global + public static IReadOnlyCollection PriorityList { get; set; } = PriorityList = (EncounterOrder[])Enum.GetValues(typeof(EncounterOrder)); + + /// + /// Resets the to the default values. + /// + public static void ResetFilters() => PriorityList = (EncounterOrder[])Enum.GetValues(typeof(EncounterOrder)); + + /// + /// Gets possible objects that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Trainer information of the receiver. + /// Moves that the resulting must be able to learn. + /// Any specific version(s) to iterate for. If left blank, all will be checked. + /// A consumable list of possible results. + /// When updating, update the sister method. + public static IEnumerable GeneratePKMs(PKM pk, ITrainerInfo info, int[]? moves = null, params GameVersion[] versions) { - /// - /// Order in which objects are yielded from the generator. - /// - // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global - public static IReadOnlyCollection PriorityList { get; set; } = PriorityList = (EncounterOrder[])Enum.GetValues(typeof(EncounterOrder)); - - /// - /// Resets the to the default values. - /// - public static void ResetFilters() => PriorityList = (EncounterOrder[])Enum.GetValues(typeof(EncounterOrder)); - - /// - /// Gets possible objects that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Trainer information of the receiver. - /// Moves that the resulting must be able to learn. - /// Any specific version(s) to iterate for. If left blank, all will be checked. - /// A consumable list of possible results. - /// When updating, update the sister method. - public static IEnumerable GeneratePKMs(PKM pk, ITrainerInfo info, int[]? moves = null, params GameVersion[] versions) + pk.TID = info.TID; + var m = moves ?? pk.Moves; + var vers = versions.Length >= 1 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Format); + foreach (var ver in vers) { - pk.TID = info.TID; - var m = moves ?? pk.Moves; - var vers = versions.Length >= 1 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Format); - foreach (var ver in vers) + var encounters = GenerateVersionEncounters(pk, m, ver); + foreach (var enc in encounters) { - var encounters = GenerateVersionEncounters(pk, m, ver); - foreach (var enc in encounters) - { - var result = enc.ConvertToPKM(info); + var result = enc.ConvertToPKM(info); #if VERIFY_GEN var la = new LegalityAnalysis(result); if (!la.Valid) throw new Exception("Legality analysis of generated Pokémon is invalid"); #endif - yield return result; - } + yield return result; } } - - /// - /// Gets possible objects that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Trainer information of the receiver. - /// Moves that the resulting must be able to learn. - /// Any specific version(s) to iterate for. If left blank, all will be checked. - /// A consumable list of possible results. - /// When updating, update the sister method. - public static IEnumerable GenerateEncounters(PKM pk, ITrainerInfo info, int[]? moves = null, params GameVersion[] versions) - { - pk.TID = info.TID; - var m = moves ?? pk.Moves; - var vers = versions.Length >= 1 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Format); - foreach (var ver in vers) - { - var encounters = GenerateVersionEncounters(pk, m, ver); - foreach (var enc in encounters) - yield return enc; - } - } - - /// - /// Gets possible objects that allow all moves requested to be learned within a specific generation. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Trainer information of the receiver. - /// Specific generation to iterate versions for. - /// Moves that the resulting must be able to learn. - public static IEnumerable GeneratePKMs(PKM pk, ITrainerInfo info, int generation, int[]? moves = null) - { - var vers = GameUtil.GetVersionsInGeneration(generation, pk.Version); - return GeneratePKMs(pk, info, moves, vers); - } - - /// - /// Gets possible encounters that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Specific generation to iterate versions for. - /// Moves that the resulting must be able to learn. - /// A consumable list of possible encounters. - public static IEnumerable GenerateEncounter(PKM pk, int generation, int[]? moves = null) - { - var vers = GameUtil.GetVersionsInGeneration(generation, pk.Version); - return GenerateEncounters(pk, moves, vers); - } - - /// - /// Gets possible encounters that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Moves that the resulting must be able to learn. - /// Any specific version(s) to iterate for. If left blank, all will be checked. - /// A consumable list of possible encounters. - public static IEnumerable GenerateEncounters(PKM pk, int[]? moves = null, params GameVersion[] versions) - { - moves ??= pk.Moves; - if (versions.Length > 0) - return GenerateEncounters(pk, moves, (IReadOnlyList)versions); - - var vers = GameUtil.GetVersionsWithinRange(pk, pk.Format); - return vers.SelectMany(ver => GenerateVersionEncounters(pk, moves, ver)); - } - - /// - /// Gets possible encounters that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Moves that the resulting must be able to learn. - /// Any specific version(s) to iterate for. If left blank, all will be checked. - /// A consumable list of possible encounters. - public static IEnumerable GenerateEncounters(PKM pk, int[]? moves, IReadOnlyList vers) - { - moves ??= pk.Moves; - return vers.SelectMany(ver => GenerateVersionEncounters(pk, moves, ver)); - } - - /// - /// Gets possible encounters that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Moves that the resulting must be able to learn. - /// Specific version to iterate for. - /// A consumable list of possible encounters. - public static IEnumerable GenerateVersionEncounters(PKM pk, IEnumerable moves, GameVersion version) - { - if (pk.Species == 0) // can enter this method after failing to set a species ID that cannot exist in the format - return Array.Empty(); - pk.Version = (int)version; - var context = pk.Context; - if (context is EntityContext.Gen2 && version is GameVersion.RD or GameVersion.GN or GameVersion.BU or GameVersion.YW) - context = EntityContext.Gen1; // try excluding baby pokemon from our evolution chain, for move learning purposes. - var et = EvolutionTree.GetEvolutionTree(context); - var chain = et.GetValidPreEvolutions(pk, maxLevel: 100, skipChecks: true); - int[] needs = GetNeededMoves(pk, moves, chain); - - return PriorityList.SelectMany(type => GetPossibleOfType(pk, needs, version, type, chain)); - } - - private static int[] GetNeededMoves(PKM pk, IEnumerable moves, EvoCriteria[] chain) - { - if (pk.Species == (int)Species.Smeargle) - return moves.Where(z => !Legal.IsValidSketch(z, pk.Format)).ToArray(); // Can learn anything - - // Roughly determine the generation the PKM is originating from - var ver = pk.Version; - int origin = pk.Generation; - if (origin < 0) - origin = ((GameVersion)ver).GetGeneration(); - - // Temporarily replace the Version for VC1 transfers, so that they can have VC2 moves if needed. - bool vcBump = origin == 1 && pk.Format >= 7; - if (vcBump) - pk.Version = (int)GameVersion.C; - - var gens = GenerationTraversal.GetVisitedGenerationOrder(pk, origin); - var canlearn = gens.SelectMany(z => GetMovesForGeneration(pk, chain, z)); - if (origin is (1 or 2)) // gb initial moves - { - var max = origin == 1 ? Legal.MaxSpeciesID_1 : Legal.MaxSpeciesID_2; - foreach (var evo in chain) - { - var species = evo.Species; - if (species > max) - continue; - var enc = MoveLevelUp.GetEncounterMoves(species, 0, 1, (GameVersion)ver); - canlearn = canlearn.Concat(enc); - } - } - var result = moves.Where(z => z != 0).Except(canlearn).ToArray(); - - if (vcBump) - pk.Version = ver; - - return result; - } - - private static IEnumerable GetMovesForGeneration(PKM pk, EvoCriteria[] chain, int generation) - { - IEnumerable moves = MoveList.GetValidMoves(pk, chain, generation); - if (generation <= 2) - moves = moves.Concat(MoveList.GetValidMoves(pk, chain, generation, MoveSourceType.LevelUp)); - if (pk.Format >= 8) - { - // Shared Egg Moves via daycare - // Any egg move can be obtained - moves = moves.Concat(MoveEgg.GetSharedEggMoves(pk, generation)); - - // TR moves -- default logic checks the TR flags, so we need to add all possible ones here. - if (!(pk.BDSP || pk.LA)) - moves = moves.Concat(MoveTechnicalMachine.GetAllPossibleRecords(pk.Species, pk.Form)); - } - if (pk.Species == (int)Species.Shedinja) - { - // Leveling up Nincada in Gen3/4 levels up, evolves to Ninjask, applies moves for Ninjask, then spawns Shedinja with the current moveset. - // Future games spawn the Shedinja before doing Ninjask moves, so this is a special case. - // Can't get more than the evolved-at level move; >=2 special moves will get caught by the legality checker later. - return generation switch - { - 3 => moves.Concat(Legal.LevelUpE [(int)Species.Ninjask].GetMoves(100, 20)), - 4 => moves.Concat(Legal.LevelUpPt[(int)Species.Ninjask].GetMoves(100, 20)), - _ => moves, - }; - } - return moves; - } - - private static IEnumerable GetPossibleOfType(PKM pk, IReadOnlyList needs, GameVersion version, EncounterOrder type, EvoCriteria[] chain) - { - return type switch - { - EncounterOrder.Egg => GetEggs(pk, needs, chain, version), - EncounterOrder.Mystery => GetGifts(pk, needs, chain, version), - EncounterOrder.Static => GetStatic(pk, needs, chain, version), - EncounterOrder.Trade => GetTrades(pk, needs, chain, version), - EncounterOrder.Slot => GetSlots(pk, needs, chain, version), - _ => throw new ArgumentOutOfRangeException(nameof(type), type, null), - }; - } - - /// - /// Gets possible encounters that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Moves which cannot be taught by the player. - /// Origin possible evolution chain - /// Specific version to iterate for. Necessary for retrieving possible Egg Moves. - /// A consumable list of possible encounters. - private static IEnumerable GetEggs(PKM pk, IReadOnlyCollection needs, EvoCriteria[] chain, GameVersion version) - { - if (!Breeding.CanGameGenerateEggs(version)) - yield break; // no eggs from these games - int gen = version.GetGeneration(); - var eggs = gen == 2 - ? EncounterEggGenerator2.GenerateEggs(pk, chain, all: true) - : EncounterEggGenerator.GenerateEggs(pk, chain, gen, all: true); - foreach (var egg in eggs) - { - if (needs.Count == 0) - { - yield return egg; - continue; - } - - IEnumerable em = MoveEgg.GetEggMoves(pk.PersonalInfo, egg.Species, egg.Form, egg.Version, egg.Generation); - if (egg.Generation <= 2) - em = em.Concat(MoveLevelUp.GetEncounterMoves(egg.Species, 0, egg.Level, egg.Version)); - else if (egg.Species is (int)Species.Pichu && needs.Contains((int)Move.VoltTackle) && egg.CanHaveVoltTackle) - em = em.Concat(new[] { (int)Move.VoltTackle }); - - if (!needs.Except(em).Any()) - yield return egg; - } - } - - /// - /// Gets possible encounters that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Moves which cannot be taught by the player. - /// Origin possible evolution chain - /// Specific version to iterate for. - /// A consumable list of possible encounters. - private static IEnumerable GetGifts(PKM pk, IReadOnlyCollection needs, EvoCriteria[] chain, GameVersion version) - { - var format = pk.Format; - var gifts = MysteryGiftGenerator.GetPossible(pk, chain, version); - foreach (var gift in gifts) - { - if (gift is WC3 {NotDistributed: true}) - continue; - if (!IsSane(chain, gift, format)) - continue; - if (needs.Count == 0) - { - yield return gift; - continue; - } - var em = gift.Moves.Concat(gift.Relearn); - if (!needs.Except(em).Any()) - yield return gift; - } - } - - /// - /// Gets possible encounters that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Moves which cannot be taught by the player. - /// Origin possible evolution chain - /// Specific version to iterate for. - /// A consumable list of possible encounters. - private static IEnumerable GetStatic(PKM pk, IReadOnlyCollection needs, EvoCriteria[] chain, GameVersion version) - { - var format = pk.Format; - var encounters = EncounterStaticGenerator.GetPossible(pk, chain, version); - foreach (var enc in encounters) - { - if (!IsSane(chain, enc, format)) - continue; - if (needs.Count == 0) - { - yield return enc; - continue; - } - - // Some rare encounters have special moves hidden in the Relearn section (Gen7 Wormhole Ho-Oh). Include relearn moves - IEnumerable em = enc.Moves; - if (enc is IRelearn { Relearn.Count: not 0 } r) - em = em.Concat(r.Relearn); - if (enc.Generation <= 2) - em = em.Concat(MoveLevelUp.GetEncounterMoves(enc.Species, 0, enc.Level, enc.Version)); - - if (!needs.Except(em).Any()) - yield return enc; - } - - int gen = version.GetGeneration(); - if ((uint)gen >= 3) - yield break; - - var gifts = EncounterStaticGenerator.GetPossibleGBGifts(chain, version); - foreach (var enc in gifts) - { - if (needs.Count == 0) - { - yield return enc; - continue; - } - - var em = enc.Moves; - if (!needs.Except(em).Any()) - yield return enc; - } - } - - /// - /// Gets possible encounters that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Moves which cannot be taught by the player. - /// Origin possible evolution chain - /// Specific version to iterate for. - /// A consumable list of possible encounters. - private static IEnumerable GetTrades(PKM pk, IReadOnlyCollection needs, EvoCriteria[] chain, GameVersion version) - { - var format = pk.Format; - var trades = EncounterTradeGenerator.GetPossible(pk, chain, version); - foreach (var trade in trades) - { - if (!IsSane(chain, trade, format)) - continue; - if (needs.Count == 0) - { - yield return trade; - continue; - } - IEnumerable em = trade.Moves; - if (trade.Generation <= 2) - em = em.Concat(MoveLevelUp.GetEncounterMoves(trade.Species, 0, trade.Level, trade.Version)); - else if (trade is IRelearn { Relearn.Count: not 0 } r) - em = em.Concat(r.Relearn); - if (!needs.Except(em).Any()) - yield return trade; - } - } - - /// - /// Gets possible encounters that allow all moves requested to be learned. - /// - /// Rough Pokémon data which contains the requested species, gender, and form. - /// Moves which cannot be taught by the player. - /// Origin possible evolution chain - /// Origin version - /// A consumable list of possible encounters. - private static IEnumerable GetSlots(PKM pk, IReadOnlyList needs, EvoCriteria[] chain, GameVersion version) - { - var format = pk.Format; - var slots = EncounterSlotGenerator.GetPossible(pk, chain, version); - foreach (var slot in slots) - { - if (!IsSane(chain, slot, format)) - continue; - - if (needs.Count == 0) - { - yield return slot; - continue; - } - - if (slot is IMoveset m && !needs.Except(m.Moves).Any()) - yield return slot; - else if (needs.Count == 1 && slot is EncounterSlot6AO {CanDexNav: true} dn && dn.CanBeDexNavMove(needs[0])) - yield return slot; - else if (needs.Count == 1 && slot is EncounterSlot8b {IsUnderground: true} ug && ug.CanBeUndergroundMove(needs[0])) - yield return slot; - else if (slot.Generation <= 2 && !needs.Except(MoveLevelUp.GetEncounterMoves(slot.Species, 0, slot.LevelMin, slot.Version)).Any()) - yield return slot; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSane(EvoCriteria[] chain, IEncounterTemplate enc, int format) - { - foreach (var evo in chain) - { - if (evo.Species != enc.Species) - continue; - if (evo.Form == enc.Form) - return true; - if (FormInfo.IsFormChangeable(enc.Species, enc.Form, evo.Form, enc.Generation)) - return true; - if (enc is EncounterSlot {IsRandomUnspecificForm: true} or EncounterStatic {IsRandomUnspecificForm: true}) - return true; - if (enc is EncounterStatic7 {IsTotem: true} && evo.Form == 0 && format > 7) // totems get form wiped - return true; - break; - } - return false; - } + } + + /// + /// Gets possible objects that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Trainer information of the receiver. + /// Moves that the resulting must be able to learn. + /// Any specific version(s) to iterate for. If left blank, all will be checked. + /// A consumable list of possible results. + /// When updating, update the sister method. + public static IEnumerable GenerateEncounters(PKM pk, ITrainerInfo info, int[]? moves = null, params GameVersion[] versions) + { + pk.TID = info.TID; + var m = moves ?? pk.Moves; + var vers = versions.Length >= 1 ? versions : GameUtil.GetVersionsWithinRange(pk, pk.Format); + foreach (var ver in vers) + { + var encounters = GenerateVersionEncounters(pk, m, ver); + foreach (var enc in encounters) + yield return enc; + } + } + + /// + /// Gets possible objects that allow all moves requested to be learned within a specific generation. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Trainer information of the receiver. + /// Specific generation to iterate versions for. + /// Moves that the resulting must be able to learn. + public static IEnumerable GeneratePKMs(PKM pk, ITrainerInfo info, int generation, int[]? moves = null) + { + var vers = GameUtil.GetVersionsInGeneration(generation, pk.Version); + return GeneratePKMs(pk, info, moves, vers); + } + + /// + /// Gets possible encounters that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Specific generation to iterate versions for. + /// Moves that the resulting must be able to learn. + /// A consumable list of possible encounters. + public static IEnumerable GenerateEncounter(PKM pk, int generation, int[]? moves = null) + { + var vers = GameUtil.GetVersionsInGeneration(generation, pk.Version); + return GenerateEncounters(pk, moves, vers); + } + + /// + /// Gets possible encounters that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Moves that the resulting must be able to learn. + /// Any specific version(s) to iterate for. If left blank, all will be checked. + /// A consumable list of possible encounters. + public static IEnumerable GenerateEncounters(PKM pk, int[]? moves = null, params GameVersion[] versions) + { + moves ??= pk.Moves; + if (versions.Length > 0) + return GenerateEncounters(pk, moves, (IReadOnlyList)versions); + + var vers = GameUtil.GetVersionsWithinRange(pk, pk.Format); + return vers.SelectMany(ver => GenerateVersionEncounters(pk, moves, ver)); + } + + /// + /// Gets possible encounters that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Moves that the resulting must be able to learn. + /// Any specific version(s) to iterate for. If left blank, all will be checked. + /// A consumable list of possible encounters. + public static IEnumerable GenerateEncounters(PKM pk, int[]? moves, IReadOnlyList vers) + { + moves ??= pk.Moves; + return vers.SelectMany(ver => GenerateVersionEncounters(pk, moves, ver)); + } + + /// + /// Gets possible encounters that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Moves that the resulting must be able to learn. + /// Specific version to iterate for. + /// A consumable list of possible encounters. + public static IEnumerable GenerateVersionEncounters(PKM pk, IEnumerable moves, GameVersion version) + { + if (pk.Species == 0) // can enter this method after failing to set a species ID that cannot exist in the format + return Array.Empty(); + pk.Version = (int)version; + var context = pk.Context; + if (context is EntityContext.Gen2 && version is GameVersion.RD or GameVersion.GN or GameVersion.BU or GameVersion.YW) + context = EntityContext.Gen1; // try excluding baby pokemon from our evolution chain, for move learning purposes. + var et = EvolutionTree.GetEvolutionTree(context); + var chain = et.GetValidPreEvolutions(pk, maxLevel: 100, skipChecks: true); + int[] needs = GetNeededMoves(pk, moves, chain); + + return PriorityList.SelectMany(type => GetPossibleOfType(pk, needs, version, type, chain)); + } + + private static int[] GetNeededMoves(PKM pk, IEnumerable moves, EvoCriteria[] chain) + { + if (pk.Species == (int)Species.Smeargle) + return moves.Where(z => !Legal.IsValidSketch(z, pk.Format)).ToArray(); // Can learn anything + + // Roughly determine the generation the PKM is originating from + var ver = pk.Version; + int origin = pk.Generation; + if (origin < 0) + origin = ((GameVersion)ver).GetGeneration(); + + // Temporarily replace the Version for VC1 transfers, so that they can have VC2 moves if needed. + bool vcBump = origin == 1 && pk.Format >= 7; + if (vcBump) + pk.Version = (int)GameVersion.C; + + var gens = GenerationTraversal.GetVisitedGenerationOrder(pk, origin); + var canlearn = gens.SelectMany(z => GetMovesForGeneration(pk, chain, z)); + if (origin is (1 or 2)) // gb initial moves + { + var max = origin == 1 ? Legal.MaxSpeciesID_1 : Legal.MaxSpeciesID_2; + foreach (var evo in chain) + { + var species = evo.Species; + if (species > max) + continue; + var enc = MoveLevelUp.GetEncounterMoves(species, 0, 1, (GameVersion)ver); + canlearn = canlearn.Concat(enc); + } + } + var result = moves.Where(z => z != 0).Except(canlearn).ToArray(); + + if (vcBump) + pk.Version = ver; + + return result; + } + + private static IEnumerable GetMovesForGeneration(PKM pk, EvoCriteria[] chain, int generation) + { + IEnumerable moves = MoveList.GetValidMoves(pk, chain, generation); + if (generation <= 2) + moves = moves.Concat(MoveList.GetValidMoves(pk, chain, generation, MoveSourceType.LevelUp)); + if (pk.Format >= 8) + { + // Shared Egg Moves via daycare + // Any egg move can be obtained + moves = moves.Concat(MoveEgg.GetSharedEggMoves(pk, generation)); + + // TR moves -- default logic checks the TR flags, so we need to add all possible ones here. + if (!(pk.BDSP || pk.LA)) + moves = moves.Concat(MoveTechnicalMachine.GetAllPossibleRecords(pk.Species, pk.Form)); + } + if (pk.Species == (int)Species.Shedinja) + { + // Leveling up Nincada in Gen3/4 levels up, evolves to Ninjask, applies moves for Ninjask, then spawns Shedinja with the current moveset. + // Future games spawn the Shedinja before doing Ninjask moves, so this is a special case. + // Can't get more than the evolved-at level move; >=2 special moves will get caught by the legality checker later. + return generation switch + { + 3 => moves.Concat(Legal.LevelUpE [(int)Species.Ninjask].GetMoves(100, 20)), + 4 => moves.Concat(Legal.LevelUpPt[(int)Species.Ninjask].GetMoves(100, 20)), + _ => moves, + }; + } + return moves; + } + + private static IEnumerable GetPossibleOfType(PKM pk, IReadOnlyList needs, GameVersion version, EncounterOrder type, EvoCriteria[] chain) + { + return type switch + { + EncounterOrder.Egg => GetEggs(pk, needs, chain, version), + EncounterOrder.Mystery => GetGifts(pk, needs, chain, version), + EncounterOrder.Static => GetStatic(pk, needs, chain, version), + EncounterOrder.Trade => GetTrades(pk, needs, chain, version), + EncounterOrder.Slot => GetSlots(pk, needs, chain, version), + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null), + }; + } + + /// + /// Gets possible encounters that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Moves which cannot be taught by the player. + /// Origin possible evolution chain + /// Specific version to iterate for. Necessary for retrieving possible Egg Moves. + /// A consumable list of possible encounters. + private static IEnumerable GetEggs(PKM pk, IReadOnlyCollection needs, EvoCriteria[] chain, GameVersion version) + { + if (!Breeding.CanGameGenerateEggs(version)) + yield break; // no eggs from these games + int gen = version.GetGeneration(); + var eggs = gen == 2 + ? EncounterEggGenerator2.GenerateEggs(pk, chain, all: true) + : EncounterEggGenerator.GenerateEggs(pk, chain, gen, all: true); + foreach (var egg in eggs) + { + if (needs.Count == 0) + { + yield return egg; + continue; + } + + IEnumerable em = MoveEgg.GetEggMoves(pk.PersonalInfo, egg.Species, egg.Form, egg.Version, egg.Generation); + if (egg.Generation <= 2) + em = em.Concat(MoveLevelUp.GetEncounterMoves(egg.Species, 0, egg.Level, egg.Version)); + else if (egg.Species is (int)Species.Pichu && needs.Contains((int)Move.VoltTackle) && egg.CanHaveVoltTackle) + em = em.Concat(new[] { (int)Move.VoltTackle }); + + if (!needs.Except(em).Any()) + yield return egg; + } + } + + /// + /// Gets possible encounters that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Moves which cannot be taught by the player. + /// Origin possible evolution chain + /// Specific version to iterate for. + /// A consumable list of possible encounters. + private static IEnumerable GetGifts(PKM pk, IReadOnlyCollection needs, EvoCriteria[] chain, GameVersion version) + { + var format = pk.Format; + var gifts = MysteryGiftGenerator.GetPossible(pk, chain, version); + foreach (var gift in gifts) + { + if (gift is WC3 {NotDistributed: true}) + continue; + if (!IsSane(chain, gift, format)) + continue; + if (needs.Count == 0) + { + yield return gift; + continue; + } + var em = gift.Moves.Concat(gift.Relearn); + if (!needs.Except(em).Any()) + yield return gift; + } + } + + /// + /// Gets possible encounters that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Moves which cannot be taught by the player. + /// Origin possible evolution chain + /// Specific version to iterate for. + /// A consumable list of possible encounters. + private static IEnumerable GetStatic(PKM pk, IReadOnlyCollection needs, EvoCriteria[] chain, GameVersion version) + { + var format = pk.Format; + var encounters = EncounterStaticGenerator.GetPossible(pk, chain, version); + foreach (var enc in encounters) + { + if (!IsSane(chain, enc, format)) + continue; + if (needs.Count == 0) + { + yield return enc; + continue; + } + + // Some rare encounters have special moves hidden in the Relearn section (Gen7 Wormhole Ho-Oh). Include relearn moves + IEnumerable em = enc.Moves; + if (enc is IRelearn { Relearn.Count: not 0 } r) + em = em.Concat(r.Relearn); + if (enc.Generation <= 2) + em = em.Concat(MoveLevelUp.GetEncounterMoves(enc.Species, 0, enc.Level, enc.Version)); + + if (!needs.Except(em).Any()) + yield return enc; + } + + int gen = version.GetGeneration(); + if ((uint)gen >= 3) + yield break; + + var gifts = EncounterStaticGenerator.GetPossibleGBGifts(chain, version); + foreach (var enc in gifts) + { + if (needs.Count == 0) + { + yield return enc; + continue; + } + + var em = enc.Moves; + if (!needs.Except(em).Any()) + yield return enc; + } + } + + /// + /// Gets possible encounters that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Moves which cannot be taught by the player. + /// Origin possible evolution chain + /// Specific version to iterate for. + /// A consumable list of possible encounters. + private static IEnumerable GetTrades(PKM pk, IReadOnlyCollection needs, EvoCriteria[] chain, GameVersion version) + { + var format = pk.Format; + var trades = EncounterTradeGenerator.GetPossible(pk, chain, version); + foreach (var trade in trades) + { + if (!IsSane(chain, trade, format)) + continue; + if (needs.Count == 0) + { + yield return trade; + continue; + } + IEnumerable em = trade.Moves; + if (trade.Generation <= 2) + em = em.Concat(MoveLevelUp.GetEncounterMoves(trade.Species, 0, trade.Level, trade.Version)); + else if (trade is IRelearn { Relearn.Count: not 0 } r) + em = em.Concat(r.Relearn); + if (!needs.Except(em).Any()) + yield return trade; + } + } + + /// + /// Gets possible encounters that allow all moves requested to be learned. + /// + /// Rough Pokémon data which contains the requested species, gender, and form. + /// Moves which cannot be taught by the player. + /// Origin possible evolution chain + /// Origin version + /// A consumable list of possible encounters. + private static IEnumerable GetSlots(PKM pk, IReadOnlyList needs, EvoCriteria[] chain, GameVersion version) + { + var format = pk.Format; + var slots = EncounterSlotGenerator.GetPossible(pk, chain, version); + foreach (var slot in slots) + { + if (!IsSane(chain, slot, format)) + continue; + + if (needs.Count == 0) + { + yield return slot; + continue; + } + + if (slot is IMoveset m && !needs.Except(m.Moves).Any()) + yield return slot; + else if (needs.Count == 1 && slot is EncounterSlot6AO {CanDexNav: true} dn && dn.CanBeDexNavMove(needs[0])) + yield return slot; + else if (needs.Count == 1 && slot is EncounterSlot8b {IsUnderground: true} ug && ug.CanBeUndergroundMove(needs[0])) + yield return slot; + else if (slot.Generation <= 2 && !needs.Except(MoveLevelUp.GetEncounterMoves(slot.Species, 0, slot.LevelMin, slot.Version)).Any()) + yield return slot; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSane(EvoCriteria[] chain, IEncounterTemplate enc, int format) + { + foreach (var evo in chain) + { + if (evo.Species != enc.Species) + continue; + if (evo.Form == enc.Form) + return true; + if (FormInfo.IsFormChangeable(enc.Species, enc.Form, evo.Form, enc.Generation)) + return true; + if (enc is EncounterSlot {IsRandomUnspecificForm: true} or EncounterStatic {IsRandomUnspecificForm: true}) + return true; + if (enc is EncounterStatic7 {IsTotem: true} && evo.Form == 0 && format > 7) // totems get form wiped + return true; + break; + } + return false; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterOrder.cs b/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterOrder.cs index 39f6530cf..690971b1c 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterOrder.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Moveset/EncounterOrder.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum EncounterOrder { - public enum EncounterOrder - { - Egg, - Mystery, - Static, - Trade, - Slot, - } + Egg, + Mystery, + Static, + Trade, + Slot, } diff --git a/PKHeX.Core/Legality/Encounters/Generator/PeekEnumerator.cs b/PKHeX.Core/Legality/Encounters/Generator/PeekEnumerator.cs index 1d8db1f7f..fb7545043 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/PeekEnumerator.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/PeekEnumerator.cs @@ -2,82 +2,81 @@ using System.Collections; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Iterates a generic collection with the ability to peek into the collection to see if the next element exists. +/// +/// Generic Collection Element Type +public sealed class PeekEnumerator : IEnumerator where T : class { + private readonly IEnumerator Enumerator; + private T? peek; + private bool didPeek; + + #region IEnumerator Implementation + /// - /// Iterates a generic collection with the ability to peek into the collection to see if the next element exists. + /// Advances the enumerator to the next element in the collection. /// - /// Generic Collection Element Type - public sealed class PeekEnumerator : IEnumerator where T : class + /// Indication if there are more elements in the collection. + public bool MoveNext() { - private readonly IEnumerator Enumerator; - private T? peek; - private bool didPeek; - - #region IEnumerator Implementation - - /// - /// Advances the enumerator to the next element in the collection. - /// - /// Indication if there are more elements in the collection. - public bool MoveNext() - { - if (!didPeek) - return Enumerator.MoveNext(); - didPeek = false; - return true; - } - - /// - /// Sets the enumerator to its initial position, which is before the first element in the collection. - /// - public void Reset() - { - Enumerator.Reset(); - peek = default; - didPeek = false; - } - - object IEnumerator.Current => Current; - public void Dispose() => Enumerator.Dispose(); - public T Current => didPeek ? peek! : Enumerator.Current ?? throw new NullReferenceException(); - - #endregion - - public PeekEnumerator(IEnumerator enumerator) => Enumerator = enumerator ?? throw new ArgumentNullException(nameof(enumerator)); - public PeekEnumerator(IEnumerable enumerable) => Enumerator = enumerable.GetEnumerator(); - - /// - /// Fetch the next element, if not already performed. - /// - /// True/False that a Next element exists - /// Advances the enumerator if Next has not been peeked already - private bool TryFetchPeek() - { - if (!didPeek && (didPeek = Enumerator.MoveNext())) - peek = Enumerator.Current; - return didPeek; - } - - /// - /// Peeks to the next element - /// - /// Next element - /// Throws an exception if no element exists - public T Peek() - { - if (!TryFetchPeek()) - throw new InvalidOperationException("Enumeration already finished."); - - return peek!; - } - - public T? PeekOrDefault() => !TryFetchPeek() ? default : peek; - - /// - /// Checks if a Next element exists - /// - /// True/False that a Next element exists - public bool PeekIsNext() => TryFetchPeek(); + if (!didPeek) + return Enumerator.MoveNext(); + didPeek = false; + return true; } + + /// + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// + public void Reset() + { + Enumerator.Reset(); + peek = default; + didPeek = false; + } + + object IEnumerator.Current => Current; + public void Dispose() => Enumerator.Dispose(); + public T Current => didPeek ? peek! : Enumerator.Current ?? throw new ArgumentNullException(nameof(Enumerator.Current)); + + #endregion + + public PeekEnumerator(IEnumerator enumerator) => Enumerator = enumerator ?? throw new ArgumentNullException(nameof(enumerator)); + public PeekEnumerator(IEnumerable enumerable) => Enumerator = enumerable.GetEnumerator(); + + /// + /// Fetch the next element, if not already performed. + /// + /// True/False that a Next element exists + /// Advances the enumerator if Next has not been peeked already + private bool TryFetchPeek() + { + if (!didPeek && (didPeek = Enumerator.MoveNext())) + peek = Enumerator.Current; + return didPeek; + } + + /// + /// Peeks to the next element + /// + /// Next element + /// Throws an exception if no element exists + public T Peek() + { + if (!TryFetchPeek()) + throw new InvalidOperationException("Enumeration already finished."); + + return peek!; + } + + public T? PeekOrDefault() => !TryFetchPeek() ? default : peek; + + /// + /// Checks if a Next element exists + /// + /// True/False that a Next element exists + public bool PeekIsNext() => TryFetchPeek(); } diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator.cs index 92a640aec..da6ee6d5c 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator.cs @@ -2,98 +2,97 @@ using static PKHeX.Core.Legal; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class EncounterEggGenerator { - public static class EncounterEggGenerator + public static IEnumerable GenerateEggs(PKM pk, int generation, bool all = false) { - public static IEnumerable GenerateEggs(PKM pkm, int generation, bool all = false) + var table = EvolutionTree.GetEvolutionTree(pk.Context); + int maxSpeciesOrigin = GetMaxSpeciesOrigin(generation); + var evos = table.GetValidPreEvolutions(pk, maxLevel: 100, maxSpeciesOrigin: maxSpeciesOrigin, skipChecks: true); + return GenerateEggs(pk, evos, generation, all); + } + + public static IEnumerable GenerateEggs(PKM pk, EvoCriteria[] chain, int generation, bool all = false) + { + System.Diagnostics.Debug.Assert(generation >= 3); // if generating Gen2 eggs, use the other generator. + int currentSpecies = pk.Species; + if (!Breeding.CanHatchAsEgg(currentSpecies)) + yield break; + + var currentForm = pk.Form; + if (!Breeding.CanHatchAsEgg(currentSpecies, currentForm, generation)) + yield break; // can't originate from eggs + + // version is a true indicator for all generation 3-5 origins + var ver = (GameVersion)pk.Version; + if (!Breeding.CanGameGenerateEggs(ver)) + yield break; + + var lvl = EggStateLegality.GetEggLevel(generation); + int max = GetMaxSpeciesOrigin(generation); + + var (species, form) = GetBaseSpecies(chain, 0); + if ((uint)species <= max) { - var table = EvolutionTree.GetEvolutionTree(pkm.Context); - int maxSpeciesOrigin = GetMaxSpeciesOrigin(generation); - var evos = table.GetValidPreEvolutions(pkm, maxLevel: 100, maxSpeciesOrigin: maxSpeciesOrigin, skipChecks: true); - return GenerateEggs(pkm, evos, generation, all); - } - - public static IEnumerable GenerateEggs(PKM pkm, EvoCriteria[] chain, int generation, bool all = false) - { - System.Diagnostics.Debug.Assert(generation >= 3); // if generating Gen2 eggs, use the other generator. - int currentSpecies = pkm.Species; - if (!Breeding.CanHatchAsEgg(currentSpecies)) - yield break; - - var currentForm = pkm.Form; - if (!Breeding.CanHatchAsEgg(currentSpecies, currentForm, generation)) - yield break; // can't originate from eggs - - // version is a true indicator for all generation 3-5 origins - var ver = (GameVersion)pkm.Version; - if (!Breeding.CanGameGenerateEggs(ver)) - yield break; - - var lvl = EggStateLegality.GetEggLevel(generation); - int max = GetMaxSpeciesOrigin(generation); - - var (species, form) = GetBaseSpecies(chain, 0); - if ((uint)species <= max) + // NOTE: THE SPLIT-BREED SECTION OF CODE SHOULD BE EXACTLY THE SAME AS THE BELOW SECTION + if (FormInfo.IsBattleOnlyForm(species, form, generation)) + form = FormInfo.GetOutOfBattleForm(species, form, generation); + if (Breeding.CanHatchAsEgg(species, form, ver)) { - // NOTE: THE SPLIT-BREED SECTION OF CODE SHOULD BE EXACTLY THE SAME AS THE BELOW SECTION - if (FormInfo.IsBattleOnlyForm(species, form, generation)) - form = FormInfo.GetOutOfBattleForm(species, form, generation); - if (Breeding.CanHatchAsEgg(species, form, ver)) - { - yield return new EncounterEgg(species, form, lvl, generation, ver); - if (generation > 5 && (pkm.WasTradedEgg || all) && HasOtherGamePair(ver)) - yield return new EncounterEgg(species, form, lvl, generation, GetOtherTradePair(ver)); - } - } - - if (!Breeding.GetSplitBreedGeneration(generation).Contains(currentSpecies)) - yield break; // no other possible species - - var otherSplit = species; - (species, form) = GetBaseSpecies(chain, 1); - if ((uint)species == otherSplit) - yield break; - - if (species <= max) - { - // NOTE: THIS SECTION OF CODE SHOULD BE EXACTLY THE SAME AS THE ABOVE SECTION - if (FormInfo.IsBattleOnlyForm(species, form, generation)) - form = FormInfo.GetOutOfBattleForm(species, form, generation); - if (Breeding.CanHatchAsEgg(species, form, ver)) - { - yield return new EncounterEgg(species, form, lvl, generation, ver); - if (generation > 5 && (pkm.WasTradedEgg || all) && HasOtherGamePair(ver)) - yield return new EncounterEgg(species, form, lvl, generation, GetOtherTradePair(ver)); - } + yield return new EncounterEgg(species, form, lvl, generation, ver); + if (generation > 5 && (pk.WasTradedEgg || all) && HasOtherGamePair(ver)) + yield return new EncounterEgg(species, form, lvl, generation, GetOtherTradePair(ver)); } } - // Gen6+ update the origin game when hatched. Quick manip for X.Y<->A.O | S.M<->US.UM, ie X->A - private static GameVersion GetOtherTradePair(GameVersion ver) => ver switch - { - <= GameVersion.OR => (GameVersion) ((int) ver ^ 2), // gen6 - <= GameVersion.MN => ver + 2, // gen7 - _ => ver - 2, - }; + if (!Breeding.GetSplitBreedGeneration(generation).Contains(currentSpecies)) + yield break; // no other possible species - private static bool HasOtherGamePair(GameVersion ver) - { - return ver < GameVersion.GP; // lgpe and sw/sh don't have a sister pair - } + var otherSplit = species; + (species, form) = GetBaseSpecies(chain, 1); + if ((uint)species == otherSplit) + yield break; - private static (int Species, int Form) GetBaseSpecies(EvoCriteria[] evolutions, int skipOption) + if (species <= max) { - int species = evolutions[0].Species; - if (species == (int)Species.Shedinja) // Shedinja - return ((int)Species.Nincada, 0); // Nincada - - // skip n from end, return empty if invalid index - int index = evolutions.Length - 1 - skipOption; - if ((uint)index >= evolutions.Length) - return (-1, 0); - var evo = evolutions[index]; - return (evo.Species, evo.Form); + // NOTE: THIS SECTION OF CODE SHOULD BE EXACTLY THE SAME AS THE ABOVE SECTION + if (FormInfo.IsBattleOnlyForm(species, form, generation)) + form = FormInfo.GetOutOfBattleForm(species, form, generation); + if (Breeding.CanHatchAsEgg(species, form, ver)) + { + yield return new EncounterEgg(species, form, lvl, generation, ver); + if (generation > 5 && (pk.WasTradedEgg || all) && HasOtherGamePair(ver)) + yield return new EncounterEgg(species, form, lvl, generation, GetOtherTradePair(ver)); + } } } + + // Gen6+ update the origin game when hatched. Quick manip for X.Y<->A.O | S.M<->US.UM, ie X->A + private static GameVersion GetOtherTradePair(GameVersion ver) => ver switch + { + <= GameVersion.OR => (GameVersion) ((int) ver ^ 2), // gen6 + <= GameVersion.MN => ver + 2, // gen7 + _ => ver - 2, + }; + + private static bool HasOtherGamePair(GameVersion ver) + { + return ver < GameVersion.GP; // lgpe and sw/sh don't have a sister pair + } + + private static (int Species, int Form) GetBaseSpecies(EvoCriteria[] evolutions, int skipOption) + { + int species = evolutions[0].Species; + if (species == (int)Species.Shedinja) // Shedinja + return ((int)Species.Nincada, 0); // Nincada + + // skip n from end, return empty if invalid index + int index = evolutions.Length - 1 - skipOption; + if ((uint)index >= evolutions.Length) + return (-1, 0); + var evo = evolutions[index]; + return (evo.Species, evo.Form); + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator2.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator2.cs index 91d78f059..8a81a38cb 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator2.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterEggGenerator2.cs @@ -1,106 +1,105 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Specialized Egg Generator for Gen2 +/// +internal static class EncounterEggGenerator2 { - /// - /// Specialized Egg Generator for Gen2 - /// - internal static class EncounterEggGenerator2 + public static IEnumerable GenerateEggs(PKM pk, bool all = false) { - public static IEnumerable GenerateEggs(PKM pkm, bool all = false) - { - var table = EvolutionTree.Evolves2; - int maxSpeciesOrigin = Legal.GetMaxSpeciesOrigin(2); - var evos = table.GetValidPreEvolutions(pkm, maxLevel: 100, maxSpeciesOrigin: maxSpeciesOrigin, skipChecks: true); - return GenerateEggs(pkm, evos, all); - } + var table = EvolutionTree.Evolves2; + int maxSpeciesOrigin = Legal.GetMaxSpeciesOrigin(2); + var evos = table.GetValidPreEvolutions(pk, maxLevel: 100, maxSpeciesOrigin: maxSpeciesOrigin, skipChecks: true); + return GenerateEggs(pk, evos, all); + } - public static IEnumerable GenerateEggs(PKM pkm, EvoCriteria[] chain, bool all = false) - { - int species = pkm.Species; - if (!Breeding.CanHatchAsEgg(species)) - yield break; + public static IEnumerable GenerateEggs(PKM pk, EvoCriteria[] chain, bool all = false) + { + int species = pk.Species; + if (!Breeding.CanHatchAsEgg(species)) + yield break; - var canBeEgg = all || GetCanBeEgg(pkm); - if (!canBeEgg) - yield break; + var canBeEgg = all || GetCanBeEgg(pk); + if (!canBeEgg) + yield break; - // Gen2 was before split-breed species existed; try to ensure that the egg we try and match to can actually originate in the game. - // Species must be < 251 - // Form must be 0 (Unown cannot breed). - var baseID = chain[^1]; - if ((baseID.Species >= Legal.MaxSpeciesID_2 || baseID.Form != 0) && chain.Length != 1) - baseID = chain[^2]; - if (baseID.Form != 0) - yield break; // Forms don't exist in Gen2, besides Unown (which can't breed). Nothing can form-change. + // Gen2 was before split-breed species existed; try to ensure that the egg we try and match to can actually originate in the game. + // Species must be < 251 + // Form must be 0 (Unown cannot breed). + var baseID = chain[^1]; + if ((baseID.Species >= Legal.MaxSpeciesID_2 || baseID.Form != 0) && chain.Length != 1) + baseID = chain[^2]; + if (baseID.Form != 0) + yield break; // Forms don't exist in Gen2, besides Unown (which can't breed). Nothing can form-change. - species = baseID.Species; - if (species > Legal.MaxSpeciesID_2) - yield break; - if (GetCanBeCrystalEgg(pkm, species, all)) - yield return new EncounterEgg(species, 0, 5, 2, GameVersion.C); // gen2 egg - yield return new EncounterEgg(species, 0, 5, 2, GameVersion.GS); // gen2 egg - } - - private static bool GetCanBeCrystalEgg(PKM pkm, int species, bool all) - { - if (!ParseSettings.AllowGen2Crystal(pkm)) - return false; - - if (all) - return true; - - // Check if the met data is present or could have been erased. - if (pkm.Format > 2) - return true; // doesn't have original met location - if (pkm.IsEgg) - return true; // doesn't have location yet - if (pkm.Met_Level == 1) // Met location of 0 is valid -- second floor of every Pokémon Center - return true; // has original met location - if (species < Legal.MaxSpeciesID_1) - return true; // can trade RBY to wipe location - if (pkm.Species < Legal.MaxSpeciesID_1) - return true; // can trade RBY to wipe location + species = baseID.Species; + if (species > Legal.MaxSpeciesID_2) + yield break; + if (GetCanBeCrystalEgg(pk, species, all)) + yield return new EncounterEgg(species, 0, 5, 2, GameVersion.C); // gen2 egg + yield return new EncounterEgg(species, 0, 5, 2, GameVersion.GS); // gen2 egg + } + private static bool GetCanBeCrystalEgg(PKM pk, int species, bool all) + { + if (!ParseSettings.AllowGen2Crystal(pk)) return false; - } - - private static bool GetCanBeEgg(PKM pkm) - { - bool canBeEgg = !(pkm.Format == 1 && !ParseSettings.AllowGen1Tradeback) && GetCanBeEgg2(pkm); - if (!canBeEgg) - return false; - - if (!IsEvolutionValid(pkm)) - return false; + if (all) return true; - } - private static bool GetCanBeEgg2(PKM pkm) + // Check if the met data is present or could have been erased. + if (pk.Format > 2) + return true; // doesn't have original met location + if (pk.IsEgg) + return true; // doesn't have location yet + if (pk.Met_Level == 1) // Met location of 0 is valid -- second floor of every Pokémon Center + return true; // has original met location + if (species < Legal.MaxSpeciesID_1) + return true; // can trade RBY to wipe location + if (pk.Species < Legal.MaxSpeciesID_1) + return true; // can trade RBY to wipe location + + return false; + } + + private static bool GetCanBeEgg(PKM pk) + { + bool canBeEgg = !(pk.Format == 1 && !ParseSettings.AllowGen1Tradeback) && GetCanBeEgg2(pk); + if (!canBeEgg) + return false; + + if (!IsEvolutionValid(pk)) + return false; + + return true; + } + + private static bool GetCanBeEgg2(PKM pk) + { + if (pk.IsEgg) + return pk.Format == 2; + + if (pk.Format > 2) { - if (pkm.IsEgg) - return pkm.Format == 2; - - if (pkm.Format > 2) - { - if (pkm.Met_Level < 5) - return false; - } - else - { - if (pkm.Met_Location != 0 && pkm.Met_Level != 1) // 2->1->2 clears met info - return false; - } - - return pkm.CurrentLevel >= 5; + if (pk.Met_Level < 5) + return false; } - - private static bool IsEvolutionValid(PKM pkm) + else { - var curr = EvolutionChain.GetValidPreEvolutions(pkm, minLevel: 5); - var poss = EvolutionChain.GetValidPreEvolutions(pkm, maxLevel: 100, minLevel: 5, skipChecks: true); - return curr.Length >= poss.Length; + if (pk.Met_Location != 0 && pk.Met_Level != 1) // 2->1->2 clears met info + return false; } + + return pk.CurrentLevel >= 5; + } + + private static bool IsEvolutionValid(PKM pk) + { + var curr = EvolutionChain.GetValidPreEvolutions(pk, minLevel: 5); + var poss = EvolutionChain.GetValidPreEvolutions(pk, maxLevel: 100, minLevel: 5, skipChecks: true); + return curr.Length >= poss.Length; } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterSlotGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterSlotGenerator.cs index aa89343f8..87b3e4cb4 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterSlotGenerator.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterSlotGenerator.cs @@ -19,162 +19,161 @@ using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class EncounterSlotGenerator { - public static class EncounterSlotGenerator + public static IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion gameSource) { - public static IEnumerable GetPossible(PKM pkm, EvoCriteria[] chain, GameVersion gameSource) + var possibleAreas = GetAreasByGame(pk, gameSource); + return possibleAreas.SelectMany(z => z.GetSpecies(chain)); + } + + private static IEnumerable GetAreasByGame(PKM pk, GameVersion gameSource) => gameSource switch + { + RD => SlotsRD, + GN => SlotsGN, + BU => SlotsBU, + YW => SlotsYW, + + GD => SlotsGD, + SI => SlotsSV, + C => SlotsC, + + _ => GetEncounterTable(pk, gameSource), + }; + + private static IEnumerable GetRawEncounterSlots(PKM pk, EvoCriteria[] chain, GameVersion gameSource) + { + if (pk.IsEgg) + yield break; + if (IsMetAsEgg(pk)) + yield break; + + var possibleAreas = GetEncounterAreas(pk, gameSource); + foreach (var area in possibleAreas) { - var possibleAreas = GetAreasByGame(pkm, gameSource); - return possibleAreas.SelectMany(z => z.GetSpecies(chain)); - } - - private static IEnumerable GetAreasByGame(PKM pkm, GameVersion gameSource) => gameSource switch - { - RD => SlotsRD, - GN => SlotsGN, - BU => SlotsBU, - YW => SlotsYW, - - GD => SlotsGD, - SI => SlotsSV, - C => SlotsC, - - _ => GetEncounterTable(pkm, gameSource), - }; - - private static IEnumerable GetRawEncounterSlots(PKM pkm, EvoCriteria[] chain, GameVersion gameSource) - { - if (pkm.IsEgg) - yield break; - if (IsMetAsEgg(pkm)) - yield break; - - var possibleAreas = GetEncounterAreas(pkm, gameSource); - foreach (var area in possibleAreas) - { - var slots = area.GetMatchingSlots(pkm, chain); - foreach (var s in slots) - yield return s; - } - } - - public static IEnumerable GetValidWildEncounters12(PKM pkm, EvoCriteria[] chain, GameVersion gameSource) - { - return GetRawEncounterSlots(pkm, chain, gameSource); - } - - public static IEnumerable GetValidWildEncounters(PKM pkm, EvoCriteria[] chain, GameVersion gameSource) - { - return GetRawEncounterSlots(pkm, chain, gameSource); - } - - public static IEnumerable GetValidWildEncounters(PKM pkm, EvoCriteria[] chain) - { - var gameSource = (GameVersion)pkm.Version; - return GetRawEncounterSlots(pkm, chain, gameSource); - } - - private static IEnumerable GetEncounterAreas(PKM pkm, GameVersion gameSource) - { - var slots = GetEncounterTable(pkm, gameSource); - bool noMet = !pkm.HasOriginalMetLocation || (pkm.Format == 2 && gameSource != C); - if (noMet) - return slots; - var metLocation = pkm.Met_Location; - return slots.Where(z => z.IsMatchLocation(metLocation)); - } - - internal static EncounterSlot? GetCaptureLocation(PKM pkm, EvoCriteria[] chain) - { - return GetPossible(pkm, chain, (GameVersion)pkm.Version) - .OrderBy(z => !chain.Any(s => s.Species == z.Species && s.Form == z.Form)) - .ThenBy(z => z.LevelMin) - .FirstOrDefault(); - } - - private static IEnumerable GetEncounterTable(PKM pkm, GameVersion game) => game switch - { - RBY or RD or BU or GN or YW => pkm.Japanese ? SlotsRGBY : SlotsRBY, - - GSC or GD or SI or C => GetEncounterTableGSC(pkm), - - R => SlotsR, - S => SlotsS, - E => SlotsE, - FR => SlotsFR, - LG => SlotsLG, - CXD => SlotsXD, - - D => SlotsD, - P => SlotsP, - Pt => SlotsPt, - HG => SlotsHG, - SS => SlotsSS, - - B => SlotsB, - W => SlotsW, - B2 => SlotsB2, - W2 => SlotsW2, - - X => SlotsX, - Y => SlotsY, - AS => SlotsA, - OR => SlotsO, - - SN => SlotsSN, - MN => SlotsMN, - US => SlotsUS, - UM => SlotsUM, - GP => SlotsGP, - GE => SlotsGE, - - GO => GetEncounterTableGO(pkm), - SW => SlotsSW, - SH => SlotsSH, - BD => SlotsBD, - SP => SlotsSP, - PLA => SlotsLA, - _ => Array.Empty(), - }; - - private static EncounterArea[] GetEncounterTableGSC(PKM pkm) - { - if (!ParseSettings.AllowGen2Crystal(pkm)) - return SlotsGS; - - // Gen 2 met location is lost outside gen 2 games - if (pkm.Format != 2) - return SlotsGSC; - - // Format 2 with met location, encounter should be from Crystal - if (pkm.HasOriginalMetLocation) - return SlotsC; - - // Format 2 without met location but pokemon could not be tradeback to gen 1, - // encounter should be from gold or silver - if (pkm.Species > MaxSpeciesID_1 && !EvolutionLegality.FutureEvolutionsGen1.Contains(pkm.Species)) - return SlotsGS; - - // Encounter could be any gen 2 game, it can have empty met location for have a g/s origin - // or it can be a Crystal pokemon that lost met location after being tradeback to gen 1 games - return SlotsGSC; - } - - private static IEnumerable GetEncounterTableGO(PKM pkm) - { - if (pkm.Format < 8) - return SlotsGO_GG; - - // If we know the met location, return the specific area list. - // If we're just getting all encounters (lack of met location is kinda bad...), just return everything. - var met = pkm.Met_Location; - return met switch - { - Locations.GO8 => SlotsGO, - Locations.GO7 => SlotsGO_GG, - _ => SlotsGO_GG.Concat(SlotsGO), - }; + var slots = area.GetMatchingSlots(pk, chain); + foreach (var s in slots) + yield return s; } } + + public static IEnumerable GetValidWildEncounters12(PKM pk, EvoCriteria[] chain, GameVersion gameSource) + { + return GetRawEncounterSlots(pk, chain, gameSource); + } + + public static IEnumerable GetValidWildEncounters(PKM pk, EvoCriteria[] chain, GameVersion gameSource) + { + return GetRawEncounterSlots(pk, chain, gameSource); + } + + public static IEnumerable GetValidWildEncounters(PKM pk, EvoCriteria[] chain) + { + var gameSource = (GameVersion)pk.Version; + return GetRawEncounterSlots(pk, chain, gameSource); + } + + private static IEnumerable GetEncounterAreas(PKM pk, GameVersion gameSource) + { + var slots = GetEncounterTable(pk, gameSource); + bool noMet = !pk.HasOriginalMetLocation || (pk.Format == 2 && gameSource != C); + if (noMet) + return slots; + var metLocation = pk.Met_Location; + return slots.Where(z => z.IsMatchLocation(metLocation)); + } + + internal static EncounterSlot? GetCaptureLocation(PKM pk, EvoCriteria[] chain) + { + return GetPossible(pk, chain, (GameVersion)pk.Version) + .OrderBy(z => !chain.Any(s => s.Species == z.Species && s.Form == z.Form)) + .ThenBy(z => z.LevelMin) + .FirstOrDefault(); + } + + private static IEnumerable GetEncounterTable(PKM pk, GameVersion game) => game switch + { + RBY or RD or BU or GN or YW => pk.Japanese ? SlotsRGBY : SlotsRBY, + + GSC or GD or SI or C => GetEncounterTableGSC(pk), + + R => SlotsR, + S => SlotsS, + E => SlotsE, + FR => SlotsFR, + LG => SlotsLG, + CXD => SlotsXD, + + D => SlotsD, + P => SlotsP, + Pt => SlotsPt, + HG => SlotsHG, + SS => SlotsSS, + + B => SlotsB, + W => SlotsW, + B2 => SlotsB2, + W2 => SlotsW2, + + X => SlotsX, + Y => SlotsY, + AS => SlotsA, + OR => SlotsO, + + SN => SlotsSN, + MN => SlotsMN, + US => SlotsUS, + UM => SlotsUM, + GP => SlotsGP, + GE => SlotsGE, + + GO => GetEncounterTableGO(pk), + SW => SlotsSW, + SH => SlotsSH, + BD => SlotsBD, + SP => SlotsSP, + PLA => SlotsLA, + _ => Array.Empty(), + }; + + private static EncounterArea[] GetEncounterTableGSC(PKM pk) + { + if (!ParseSettings.AllowGen2Crystal(pk)) + return SlotsGS; + + // Gen 2 met location is lost outside gen 2 games + if (pk.Format != 2) + return SlotsGSC; + + // Format 2 with met location, encounter should be from Crystal + if (pk.HasOriginalMetLocation) + return SlotsC; + + // Format 2 without met location but pokemon could not be tradeback to gen 1, + // encounter should be from gold or silver + if (pk.Species > MaxSpeciesID_1 && !EvolutionLegality.FutureEvolutionsGen1.Contains(pk.Species)) + return SlotsGS; + + // Encounter could be any gen 2 game, it can have empty met location for have a g/s origin + // or it can be a Crystal pokemon that lost met location after being tradeback to gen 1 games + return SlotsGSC; + } + + private static IEnumerable GetEncounterTableGO(PKM pk) + { + if (pk.Format < 8) + return SlotsGO_GG; + + // If we know the met location, return the specific area list. + // If we're just getting all encounters (lack of met location is kinda bad...), just return everything. + var met = pk.Met_Location; + return met switch + { + Locations.GO8 => SlotsGO, + Locations.GO7 => SlotsGO_GG, + _ => SlotsGO_GG.Concat(SlotsGO), + }; + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterStaticGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterStaticGenerator.cs index 9b8c5d35f..d39e97018 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterStaticGenerator.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterStaticGenerator.cs @@ -18,180 +18,179 @@ using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class EncounterStaticGenerator { - public static class EncounterStaticGenerator + public static IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion gameSource) { - public static IEnumerable GetPossible(PKM pkm, EvoCriteria[] chain, GameVersion gameSource) + var table = gameSource switch { - var table = gameSource switch - { - RD or GN or BU or YW => StaticRBY.Where(z => z.Version.Contains(gameSource)), - GD or SI => StaticGS.Where(z => z.Version.Contains(gameSource)), - C => StaticC, - _ => GetEncounterStaticTable(pkm, gameSource), - }; - return table.Where(e => chain.Any(d => d.Species == e.Species)); - } - - public static IEnumerable GetPossibleGBGifts(EvoCriteria[] chain, GameVersion gameSource) - { - static IEnumerable GetEvents(GameVersion g) - { - if (g.GetGeneration() == 1) - return !ParseSettings.AllowGBCartEra ? Encounters1.StaticEventsVC : Encounters1.StaticEventsGB; - - return !ParseSettings.AllowGBCartEra ? Encounters2.StaticEventsVC : Encounters2.StaticEventsGB; - } - - var table = GetEvents(gameSource); - return table.Where(e => chain.Any(d => d.Species == e.Species)); - } - - public static IEnumerable GetValidStaticEncounter(PKM pkm, EvoCriteria[] chain, GameVersion gameSource) - { - var table = GetEncounterStaticTable(pkm, gameSource); - return GetMatchingStaticEncounters(pkm, table, chain); - } - - public static IEnumerable GetValidGBGifts(PKM pkm, EvoCriteria[] chain, GameVersion gameSource) - { - var poss = GetPossibleGBGifts(chain, gameSource: gameSource); - foreach (EncounterStatic e in poss) - { - foreach (var evo in chain) - { - if (evo.Species != e.Species) - continue; - if (!e.IsMatchExact(pkm, evo)) - continue; - - yield return e; - } - } - } - - private static IEnumerable GetMatchingStaticEncounters(PKM pkm, IEnumerable poss, EvoCriteria[] chain) - { - // check for petty rejection scenarios that will be flagged by other legality checks - foreach (var e in poss) - { - foreach (var evo in chain) - { - if (evo.Species != e.Species) - continue; - if (!e.IsMatchExact(pkm, evo)) - continue; - - yield return e; - } - } - } - - internal static EncounterStatic7 GetVCStaticTransferEncounter(PKM pkm, IEncounterTemplate enc, ReadOnlySpan chain) - { - // Obtain the lowest evolution species with matching OT friendship. Not all species chains have the same base friendship. - var met = (byte)pkm.Met_Level; - if (pkm.VC1) - { - // Only yield a VC1 template if it could originate in VC1. - // Catch anything that can only exist in VC2 (Entei) even if it was "transferred" from VC1. - var species = GetVCSpecies(chain, pkm, MaxSpeciesID_1); - var vc1Species = species > MaxSpeciesID_1 ? enc.Species : species; - if (vc1Species <= MaxSpeciesID_1) - return EncounterStatic7.GetVC1(vc1Species, met); - } - // fall through else - { - var species = GetVCSpecies(chain, pkm, MaxSpeciesID_2); - return EncounterStatic7.GetVC2(species > MaxSpeciesID_2 ? enc.Species : species, met); - } - } - - private static int GetVCSpecies(ReadOnlySpan chain, PKM pkm, int max) - { - int species = pkm.Species; - foreach (var z in chain) - { - if (z.Species > max) - continue; - if (z.Form != 0) - continue; - if (PersonalTable.SM.GetFormEntry(z.Species, z.Form).BaseFriendship != pkm.OT_Friendship) - continue; - species = z.Species; - } - return species; - } - - internal static EncounterStatic? GetStaticLocation(PKM pkm, EvoCriteria[] chain) => pkm.Generation switch - { - 1 => EncounterStatic7.GetVC1(MaxSpeciesID_1, (byte)pkm.Met_Level), - 2 => EncounterStatic7.GetVC2(MaxSpeciesID_2, (byte)pkm.Met_Level), - _ => GetStaticMinByLevel(pkm, chain), + RD or GN or BU or YW => StaticRBY.Where(z => z.Version.Contains(gameSource)), + GD or SI => StaticGS.Where(z => z.Version.Contains(gameSource)), + C => StaticC, + _ => GetEncounterStaticTable(pk, gameSource), }; + return table.Where(e => chain.Any(d => d.Species == e.Species)); + } - private static EncounterStatic? GetStaticMinByLevel(PKM pkm, EvoCriteria[] chain) + public static IEnumerable GetPossibleGBGifts(EvoCriteria[] chain, GameVersion gameSource) + { + static IEnumerable GetEvents(GameVersion g) { - var possible = GetPossible(pkm, chain, (GameVersion)pkm.Version); - return EncounterUtil.GetMinByLevel(chain, possible); + if (g.GetGeneration() == 1) + return !ParseSettings.AllowGBCartEra ? Encounters1.StaticEventsVC : Encounters1.StaticEventsGB; + + return !ParseSettings.AllowGBCartEra ? Encounters2.StaticEventsVC : Encounters2.StaticEventsGB; } - // Generation Specific Fetching - private static IEnumerable GetEncounterStaticTable(PKM pkm, GameVersion game) => game switch + var table = GetEvents(gameSource); + return table.Where(e => chain.Any(d => d.Species == e.Species)); + } + + public static IEnumerable GetValidStaticEncounter(PKM pk, EvoCriteria[] chain, GameVersion gameSource) + { + var table = GetEncounterStaticTable(pk, gameSource); + return GetMatchingStaticEncounters(pk, table, chain); + } + + public static IEnumerable GetValidGBGifts(PKM pk, EvoCriteria[] chain, GameVersion gameSource) + { + var poss = GetPossibleGBGifts(chain, gameSource: gameSource); + foreach (EncounterStatic e in poss) { - RBY or RD or BU or GN or YW => StaticRBY, + foreach (var evo in chain) + { + if (evo.Species != e.Species) + continue; + if (!e.IsMatchExact(pk, evo)) + continue; - GSC or GD or SI or C => GetEncounterStaticTableGSC(pkm), - - R => StaticR, - S => StaticS, - E => StaticE, - FR => StaticFR, - LG => StaticLG, - CXD => Encounter_CXD, - - D => StaticD, - P => StaticP, - Pt => StaticPt, - HG => StaticHG, - SS => StaticSS, - - B => StaticB, - W => StaticW, - B2 => StaticB2, - W2 => StaticW2, - - X => StaticX, - Y => StaticY, - AS => StaticA, - OR => StaticO, - - SN => StaticSN, - MN => StaticMN, - US => StaticUS, - UM => StaticUM, - GP => StaticGP, - GE => StaticGE, - - SW => StaticSW, - SH => StaticSH, - BD => StaticBD, - SP => StaticSP, - PLA => StaticLA, - _ => Array.Empty(), - }; - - private static IEnumerable GetEncounterStaticTableGSC(PKM pkm) - { - if (!ParseSettings.AllowGen2Crystal(pkm)) - return StaticGS; - if (pkm.Format != 2) - return StaticGSC; - - if (pkm.HasOriginalMetLocation) - return StaticC; - return StaticGSC; + yield return e; + } } } + + private static IEnumerable GetMatchingStaticEncounters(PKM pk, IEnumerable poss, EvoCriteria[] chain) + { + // check for petty rejection scenarios that will be flagged by other legality checks + foreach (var e in poss) + { + foreach (var evo in chain) + { + if (evo.Species != e.Species) + continue; + if (!e.IsMatchExact(pk, evo)) + continue; + + yield return e; + } + } + } + + internal static EncounterStatic7 GetVCStaticTransferEncounter(PKM pk, IEncounterTemplate enc, ReadOnlySpan chain) + { + // Obtain the lowest evolution species with matching OT friendship. Not all species chains have the same base friendship. + var met = (byte)pk.Met_Level; + if (pk.VC1) + { + // Only yield a VC1 template if it could originate in VC1. + // Catch anything that can only exist in VC2 (Entei) even if it was "transferred" from VC1. + var species = GetVCSpecies(chain, pk, MaxSpeciesID_1); + var vc1Species = species > MaxSpeciesID_1 ? enc.Species : species; + if (vc1Species <= MaxSpeciesID_1) + return EncounterStatic7.GetVC1(vc1Species, met); + } + // fall through else + { + var species = GetVCSpecies(chain, pk, MaxSpeciesID_2); + return EncounterStatic7.GetVC2(species > MaxSpeciesID_2 ? enc.Species : species, met); + } + } + + private static int GetVCSpecies(ReadOnlySpan chain, PKM pk, int max) + { + int species = pk.Species; + foreach (var z in chain) + { + if (z.Species > max) + continue; + if (z.Form != 0) + continue; + if (PersonalTable.SM.GetFormEntry(z.Species, z.Form).BaseFriendship != pk.OT_Friendship) + continue; + species = z.Species; + } + return species; + } + + internal static EncounterStatic? GetStaticLocation(PKM pk, EvoCriteria[] chain) => pk.Generation switch + { + 1 => EncounterStatic7.GetVC1(MaxSpeciesID_1, (byte)pk.Met_Level), + 2 => EncounterStatic7.GetVC2(MaxSpeciesID_2, (byte)pk.Met_Level), + _ => GetStaticMinByLevel(pk, chain), + }; + + private static EncounterStatic? GetStaticMinByLevel(PKM pk, EvoCriteria[] chain) + { + var possible = GetPossible(pk, chain, (GameVersion)pk.Version); + return EncounterUtil.GetMinByLevel(chain, possible); + } + + // Generation Specific Fetching + private static IEnumerable GetEncounterStaticTable(PKM pk, GameVersion game) => game switch + { + RBY or RD or BU or GN or YW => StaticRBY, + + GSC or GD or SI or C => GetEncounterStaticTableGSC(pk), + + R => StaticR, + S => StaticS, + E => StaticE, + FR => StaticFR, + LG => StaticLG, + CXD => Encounter_CXD, + + D => StaticD, + P => StaticP, + Pt => StaticPt, + HG => StaticHG, + SS => StaticSS, + + B => StaticB, + W => StaticW, + B2 => StaticB2, + W2 => StaticW2, + + X => StaticX, + Y => StaticY, + AS => StaticA, + OR => StaticO, + + SN => StaticSN, + MN => StaticMN, + US => StaticUS, + UM => StaticUM, + GP => StaticGP, + GE => StaticGE, + + SW => StaticSW, + SH => StaticSH, + BD => StaticBD, + SP => StaticSP, + PLA => StaticLA, + _ => Array.Empty(), + }; + + private static IEnumerable GetEncounterStaticTableGSC(PKM pk) + { + if (!ParseSettings.AllowGen2Crystal(pk)) + return StaticGS; + if (pk.Format != 2) + return StaticGSC; + + if (pk.HasOriginalMetLocation) + return StaticC; + return StaticGSC; + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterTradeGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterTradeGenerator.cs index d0f0f802e..03fbb98c0 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterTradeGenerator.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/EncounterTradeGenerator.cs @@ -3,98 +3,97 @@ using System.Linq; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class EncounterTradeGenerator { - public static class EncounterTradeGenerator + public static IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion gameSource) { - public static IEnumerable GetPossible(PKM pkm, EvoCriteria[] chain, GameVersion gameSource) - { - if (pkm.Format <= 2 || pkm.VC) - return GetPossibleVC(chain, gameSource); - return GetPossible(chain, gameSource); - } - - private static IEnumerable GetPossibleVC(EvoCriteria[] chain, GameVersion game) - { - var table = GetTableVC(game); - return table.Where(e => chain.Any(c => c.Species == e.Species && c.Form == 0)); - } - - private static IEnumerable GetPossible(EvoCriteria[] chain, GameVersion game) - { - var table = GetTable(game); - return table.Where(e => chain.Any(c => c.Species == e.Species)); - } - - public static IEnumerable GetValidEncounterTradesVC(PKM pkm, EvoCriteria[] chain, GameVersion game) - { - var table = GetTableVC(game); - foreach (var p in table) - { - foreach (var evo in chain) - { - if (evo.Species != p.Species || evo.Form != 0) - continue; - if (p.IsMatchExact(pkm, evo)) - yield return p; - break; - } - } - } - - public static IEnumerable GetValidEncounterTrades(PKM pkm, EvoCriteria[] chain, GameVersion game) - { - // Pre-filter for some language scenarios - int lang = pkm.Language; - if (lang == (int)LanguageID.UNUSED_6) // invalid language - return Array.Empty(); - if (lang == (int)LanguageID.Hacked && !EncounterTrade5PID.IsValidMissingLanguage(pkm)) // Japanese trades in BW have no language ID - return Array.Empty(); - - var table = GetTable(game); - return GetValidEncounterTrades(pkm, chain, table); - } - - private static IEnumerable GetValidEncounterTrades(PKM pkm, EvoCriteria[] chain, EncounterTrade[] poss) - { - foreach (var p in poss) - { - foreach (var evo in chain) - { - if (evo.Species != p.Species) - continue; - if (p.IsMatchExact(pkm, evo)) - yield return p; - break; - } - } - } - - private static IEnumerable GetTableVC(GameVersion game) - { - if (RBY.Contains(game)) - return Encounters1.TradeGift_RBY; - if (GSC.Contains(game)) - return Encounters2.TradeGift_GSC; - return Array.Empty(); - } - - private static EncounterTrade[] GetTable(GameVersion game) => game switch - { - R or S or E => Encounters3.TradeGift_RSE, - FR or LG => Encounters3.TradeGift_FRLG, - D or P or Pt => Encounters4.TradeGift_DPPt, - HG or SS => Encounters4.TradeGift_HGSS, - B or W => Encounters5.TradeGift_BW, - B2 or W2 => Encounters5.TradeGift_B2W2, - X or Y => Encounters6.TradeGift_XY, - AS or OR => Encounters6.TradeGift_AO, - SN or MN => Encounters7.TradeGift_SM, - US or UM => Encounters7.TradeGift_USUM, - GP or GE => Encounters7b.TradeGift_GG, - SW or SH => Encounters8.TradeGift_SWSH, - BD or SP => Encounters8b.TradeGift_BDSP, - _ => Array.Empty(), - }; + if (pk.Format <= 2 || pk.VC) + return GetPossibleVC(chain, gameSource); + return GetPossible(chain, gameSource); } + + private static IEnumerable GetPossibleVC(EvoCriteria[] chain, GameVersion game) + { + var table = GetTableVC(game); + return table.Where(e => chain.Any(c => c.Species == e.Species && c.Form == 0)); + } + + private static IEnumerable GetPossible(EvoCriteria[] chain, GameVersion game) + { + var table = GetTable(game); + return table.Where(e => chain.Any(c => c.Species == e.Species)); + } + + public static IEnumerable GetValidEncounterTradesVC(PKM pk, EvoCriteria[] chain, GameVersion game) + { + var table = GetTableVC(game); + foreach (var p in table) + { + foreach (var evo in chain) + { + if (evo.Species != p.Species || evo.Form != 0) + continue; + if (p.IsMatchExact(pk, evo)) + yield return p; + break; + } + } + } + + public static IEnumerable GetValidEncounterTrades(PKM pk, EvoCriteria[] chain, GameVersion game) + { + // Pre-filter for some language scenarios + int lang = pk.Language; + if (lang == (int)LanguageID.UNUSED_6) // invalid language + return Array.Empty(); + if (lang == (int)LanguageID.Hacked && !EncounterTrade5PID.IsValidMissingLanguage(pk)) // Japanese trades in BW have no language ID + return Array.Empty(); + + var table = GetTable(game); + return GetValidEncounterTrades(pk, chain, table); + } + + private static IEnumerable GetValidEncounterTrades(PKM pk, EvoCriteria[] chain, EncounterTrade[] poss) + { + foreach (var p in poss) + { + foreach (var evo in chain) + { + if (evo.Species != p.Species) + continue; + if (p.IsMatchExact(pk, evo)) + yield return p; + break; + } + } + } + + private static IEnumerable GetTableVC(GameVersion game) + { + if (RBY.Contains(game)) + return Encounters1.TradeGift_RBY; + if (GSC.Contains(game)) + return Encounters2.TradeGift_GSC; + return Array.Empty(); + } + + private static EncounterTrade[] GetTable(GameVersion game) => game switch + { + R or S or E => Encounters3.TradeGift_RSE, + FR or LG => Encounters3.TradeGift_FRLG, + D or P or Pt => Encounters4.TradeGift_DPPt, + HG or SS => Encounters4.TradeGift_HGSS, + B or W => Encounters5.TradeGift_BW, + B2 or W2 => Encounters5.TradeGift_B2W2, + X or Y => Encounters6.TradeGift_XY, + AS or OR => Encounters6.TradeGift_AO, + SN or MN => Encounters7.TradeGift_SM, + US or UM => Encounters7.TradeGift_USUM, + GP or GE => Encounters7b.TradeGift_GG, + SW or SH => Encounters8.TradeGift_SWSH, + BD or SP => Encounters8b.TradeGift_BDSP, + _ => Array.Empty(), + }; } diff --git a/PKHeX.Core/Legality/Encounters/Generator/Specific/MysteryGiftGenerator.cs b/PKHeX.Core/Legality/Encounters/Generator/Specific/MysteryGiftGenerator.cs index d69902d71..a374c43e2 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Specific/MysteryGiftGenerator.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Specific/MysteryGiftGenerator.cs @@ -3,78 +3,77 @@ using System.Linq; using static PKHeX.Core.EncounterEvent; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class MysteryGiftGenerator { - public static class MysteryGiftGenerator + public static IEnumerable GetPossible(PKM pk, EvoCriteria[] chain, GameVersion game) { - public static IEnumerable GetPossible(PKM pkm, EvoCriteria[] chain, GameVersion game) - { - // Ranger Manaphy is a PGT and is not in the PCD[] for gen4. Check manually. - int gen = pkm.Generation; - if (gen == 4 && pkm.Species == (int) Species.Manaphy) - yield return RangerManaphy; + // Ranger Manaphy is a PGT and is not in the PCD[] for gen4. Check manually. + int gen = pk.Generation; + if (gen == 4 && pk.Species == (int) Species.Manaphy) + yield return RangerManaphy; - var table = GetTable(gen, game); - var possible = table.Where(wc => chain.Any(evo => evo.Species == wc.Species)); - foreach (var enc in possible) - yield return enc; - } - - public static IEnumerable GetValidGifts(PKM pkm, EvoCriteria[] chain, GameVersion game) - { - int gen = pkm.Generation; - if (pkm.IsEgg && pkm.Format != gen) // transferred - return Array.Empty(); - - if (gen == 4) // check for Manaphy gift - return GetMatchingPCD(pkm, MGDB_G4, chain); - var table = GetTable(gen, game); - return GetMatchingGifts(pkm, table, chain); - } - - private static IReadOnlyCollection GetTable(int generation, GameVersion game) => generation switch - { - 3 => MGDB_G3, - 4 => MGDB_G4, - 5 => MGDB_G5, - 6 => MGDB_G6, - 7 => game is GameVersion.GP or GameVersion.GE ? MGDB_G7GG : MGDB_G7, - 8 => game switch - { - GameVersion.BD or GameVersion.SP => MGDB_G8B, - GameVersion.PLA => MGDB_G8A, - _ => MGDB_G8, - }, - _ => Array.Empty(), - }; - - private static IEnumerable GetMatchingPCD(PKM pkm, IReadOnlyCollection DB, EvoCriteria[] chain) - { - if (PGT.IsRangerManaphy(pkm)) - { - yield return RangerManaphy; - yield break; - } - - foreach (var g in GetMatchingGifts(pkm, DB, chain)) - yield return g; - } - - private static IEnumerable GetMatchingGifts(PKM pkm, IReadOnlyCollection DB, EvoCriteria[] chain) - { - foreach (var mg in DB) - { - foreach (var evo in chain) - { - if (evo.Species != mg.Species) - continue; - if (mg.IsMatchExact(pkm, evo)) - yield return mg; - } - } - } - - // Utility - private static readonly PGT RangerManaphy = new() {Data = {[0] = 7, [8] = 1}}; + var table = GetTable(gen, game); + var possible = table.Where(wc => chain.Any(evo => evo.Species == wc.Species)); + foreach (var enc in possible) + yield return enc; } + + public static IEnumerable GetValidGifts(PKM pk, EvoCriteria[] chain, GameVersion game) + { + int gen = pk.Generation; + if (pk.IsEgg && pk.Format != gen) // transferred + return Array.Empty(); + + if (gen == 4) // check for Manaphy gift + return GetMatchingPCD(pk, MGDB_G4, chain); + var table = GetTable(gen, game); + return GetMatchingGifts(pk, table, chain); + } + + private static IReadOnlyCollection GetTable(int generation, GameVersion game) => generation switch + { + 3 => MGDB_G3, + 4 => MGDB_G4, + 5 => MGDB_G5, + 6 => MGDB_G6, + 7 => game is GameVersion.GP or GameVersion.GE ? MGDB_G7GG : MGDB_G7, + 8 => game switch + { + GameVersion.BD or GameVersion.SP => MGDB_G8B, + GameVersion.PLA => MGDB_G8A, + _ => MGDB_G8, + }, + _ => Array.Empty(), + }; + + private static IEnumerable GetMatchingPCD(PKM pk, IReadOnlyCollection table, EvoCriteria[] chain) + { + if (PGT.IsRangerManaphy(pk)) + { + yield return RangerManaphy; + yield break; + } + + foreach (var g in GetMatchingGifts(pk, table, chain)) + yield return g; + } + + private static IEnumerable GetMatchingGifts(PKM pk, IReadOnlyCollection table, EvoCriteria[] chain) + { + foreach (var mg in table) + { + foreach (var evo in chain) + { + if (evo.Species != mg.Species) + continue; + if (mg.IsMatchExact(pk, evo)) + yield return mg; + } + } + } + + // Utility + private static readonly PGT RangerManaphy = new() {Data = {[0] = 7, [8] = 1}}; } diff --git a/PKHeX.Core/Legality/Encounters/IEncounterConvertible.cs b/PKHeX.Core/Legality/Encounters/IEncounterConvertible.cs index 599ce249a..314c43fe9 100644 --- a/PKHeX.Core/Legality/Encounters/IEncounterConvertible.cs +++ b/PKHeX.Core/Legality/Encounters/IEncounterConvertible.cs @@ -1,20 +1,19 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Exposes conversion methods to create a from the object's data. +/// +public interface IEncounterConvertible { /// - /// Exposes conversion methods to create a from the object's data. + /// Creates a from the template, using the input as the trainer data. /// - public interface IEncounterConvertible - { - /// - /// Creates a from the template, using the input as the trainer data. - /// - /// This method calls with a fixed criteria containing no restrictions on the generated data. - PKM ConvertToPKM(ITrainerInfo sav); + /// This method calls with a fixed criteria containing no restrictions on the generated data. + PKM ConvertToPKM(ITrainerInfo tr); - /// - /// Creates a from the template, using the input as the trainer data. - ///
The generation routine will try to yield a result that matches the specifications in the .
- ///
- PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria); - } + /// + /// Creates a from the template, using the input as the trainer data. + ///
The generation routine will try to yield a result that matches the specifications in the .
+ ///
+ PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria); } diff --git a/PKHeX.Core/Legality/Encounters/IEncounterInfo.cs b/PKHeX.Core/Legality/Encounters/IEncounterInfo.cs index 85eb30a0f..0d552d4c5 100644 --- a/PKHeX.Core/Legality/Encounters/IEncounterInfo.cs +++ b/PKHeX.Core/Legality/Encounters/IEncounterInfo.cs @@ -1,10 +1,9 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Exposes simple encounter info and can be converted to a . +/// +/// Combined interface used to require multiple interfaces being present for a calling method. +public interface IEncounterInfo : IEncounterTemplate, IEncounterConvertible { - /// - /// Exposes simple encounter info and can be converted to a . - /// - /// Combined interface used to require multiple interfaces being present for a calling method. - public interface IEncounterInfo : IEncounterTemplate, IEncounterConvertible - { - } } diff --git a/PKHeX.Core/Legality/Encounters/IEncounterMatch.cs b/PKHeX.Core/Legality/Encounters/IEncounterMatch.cs index 02e8b1eca..982e593a0 100644 --- a/PKHeX.Core/Legality/Encounters/IEncounterMatch.cs +++ b/PKHeX.Core/Legality/Encounters/IEncounterMatch.cs @@ -1,53 +1,52 @@ using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Exposes details about the quality of a potential match compared to an input . +/// +public interface IEncounterMatch { /// - /// Exposes details about the quality of a potential match compared to an input . + /// Checks if the implementing object's details might have been the originator of the current data. /// - public interface IEncounterMatch - { - /// - /// Checks if the implementing object's details might have been the originator of the current data. - /// - bool IsMatchExact(PKM pkm, EvoCriteria evo); + bool IsMatchExact(PKM pk, EvoCriteria evo); - /// - /// Checks if the potential match may not be a perfect match (might be a better match later during iteration). - /// - EncounterMatchRating GetMatchRating(PKM pkm); + /// + /// Checks if the potential match may not be a perfect match (might be a better match later during iteration). + /// + EncounterMatchRating GetMatchRating(PKM pk); +} + +internal static class EncounterMatchExtensions +{ + /// + /// Some species do not have a Hidden Ability, but can be altered to have the HA slot via pre-evolution. + /// + /// Match object + /// Species ID + /// True if it should not originate as this species. + private static bool IsPartialMatchHidden(this IEncounterMatch _, int species) + { + return species is (int)Metapod or (int)Kakuna + or (int)Pupitar + or (int)Silcoon or (int)Cascoon + or (int)Vibrava or (int)Flygon; } - internal static class EncounterMatchExtensions + /// + /// Some species do not have a Hidden Ability, but can be altered to have the HA slot via pre-evolution. + /// + /// Match object + /// Current Species ID + /// Original Species ID + /// True if it should not originate as this species. + public static bool IsPartialMatchHidden(this IEncounterMatch _, int current, int original) { - /// - /// Some species do not have a Hidden Ability, but can be altered to have the HA slot via pre-evolution. - /// - /// Match object - /// Species ID - /// True if it should not originate as this species. - private static bool IsPartialMatchHidden(this IEncounterMatch _, int species) - { - return species is (int)Metapod or (int)Kakuna - or (int)Pupitar - or (int)Silcoon or (int)Cascoon - or (int)Vibrava or (int)Flygon; - } - - /// - /// Some species do not have a Hidden Ability, but can be altered to have the HA slot via pre-evolution. - /// - /// Match object - /// Current Species ID - /// Original Species ID - /// True if it should not originate as this species. - public static bool IsPartialMatchHidden(this IEncounterMatch _, int current, int original) - { - if (current == original) - return false; - if (!_.IsPartialMatchHidden(original)) - return false; - return _.IsPartialMatchHidden(current); - } + if (current == original) + return false; + if (!_.IsPartialMatchHidden(original)) + return false; + return _.IsPartialMatchHidden(current); } } diff --git a/PKHeX.Core/Legality/Encounters/IEncounterTemplate.cs b/PKHeX.Core/Legality/Encounters/IEncounterTemplate.cs index 02da0d657..78c0e3f05 100644 --- a/PKHeX.Core/Legality/Encounters/IEncounterTemplate.cs +++ b/PKHeX.Core/Legality/Encounters/IEncounterTemplate.cs @@ -1,35 +1,34 @@ -namespace PKHeX.Core -{ - public interface IEncounterTemplate : ISpeciesForm, IVersion, IGeneration, IShiny - { - /// - /// Indicates if the encounter originated as an egg. - /// - bool EggEncounter { get; } +namespace PKHeX.Core; - /// - /// Minimum level for the encounter. - /// - byte LevelMin { get; } - byte LevelMax { get; } +public interface IEncounterTemplate : ISpeciesForm, IVersion, IGeneration, IShiny +{ + /// + /// Indicates if the encounter originated as an egg. + /// + bool EggEncounter { get; } + + /// + /// Minimum level for the encounter. + /// + byte LevelMin { get; } + byte LevelMax { get; } +} + +public static partial class Extensions +{ + private static bool IsWithinEncounterRange(this IEncounterTemplate encounter, int lvl) + { + return encounter.LevelMin <= lvl && lvl <= encounter.LevelMax; } - public static partial class Extensions + public static bool IsWithinEncounterRange(this IEncounterTemplate encounter, PKM pk) { - private static bool IsWithinEncounterRange(this IEncounterTemplate encounter, int lvl) - { - return encounter.LevelMin <= lvl && lvl <= encounter.LevelMax; - } - - public static bool IsWithinEncounterRange(this IEncounterTemplate encounter, PKM pkm) - { - if (!pkm.HasOriginalMetLocation) - return encounter.IsWithinEncounterRange(pkm.CurrentLevel); - if (encounter.EggEncounter) - return pkm.CurrentLevel == encounter.LevelMin; - if (encounter is MysteryGift g) - return pkm.CurrentLevel == g.Level; - return pkm.CurrentLevel == pkm.Met_Level; - } + if (!pk.HasOriginalMetLocation) + return encounter.IsWithinEncounterRange(pk.CurrentLevel); + if (encounter.EggEncounter) + return pk.CurrentLevel == encounter.LevelMin; + if (encounter is MysteryGift g) + return pk.CurrentLevel == g.Level; + return pk.CurrentLevel == pk.Met_Level; } } diff --git a/PKHeX.Core/Legality/Encounters/IEncounterable.cs b/PKHeX.Core/Legality/Encounters/IEncounterable.cs index ee86542a2..253f1955e 100644 --- a/PKHeX.Core/Legality/Encounters/IEncounterable.cs +++ b/PKHeX.Core/Legality/Encounters/IEncounterable.cs @@ -1,19 +1,18 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Common Encounter Properties base interface. +/// +/// +public interface IEncounterable : IEncounterInfo, ILocation, IFixedAbilityNumber, IFixedBall, IShinyPotential { /// - /// Common Encounter Properties base interface. - /// + /// Short name to describe the encounter data, usually just indicating which of the main component encounter types the data is. /// - public interface IEncounterable : IEncounterInfo, ILocation, IFixedAbilityNumber, IFixedBall, IShinyPotential - { - /// - /// Short name to describe the encounter data, usually just indicating which of the main component encounter types the data is. - /// - string Name { get; } + string Name { get; } - /// - /// Long name to describe the encounter data, containing more detailed (type-specific) information. - /// - string LongName { get; } - } + /// + /// Long name to describe the encounter data, containing more detailed (type-specific) information. + /// + string LongName { get; } } diff --git a/PKHeX.Core/Legality/Encounters/IMasteryInitialMoveShop8.cs b/PKHeX.Core/Legality/Encounters/IMasteryInitialMoveShop8.cs index ecbc9ab55..26180817d 100644 --- a/PKHeX.Core/Legality/Encounters/IMasteryInitialMoveShop8.cs +++ b/PKHeX.Core/Legality/Encounters/IMasteryInitialMoveShop8.cs @@ -6,7 +6,7 @@ public interface IMasteryInitialMoveShop8 { (Learnset Learn, Learnset Mastery) GetLevelUpInfo(); void LoadInitialMoveset(PA8 pa8, Span moves, Learnset learn, int level); - bool IsForcedMasteryCorrect(PKM pkm); + bool IsForcedMasteryCorrect(PKM pk); } public static class MasteryInitialMoveShop8Extensions diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs index 44bce88e0..63d6315f9 100644 --- a/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs @@ -2,88 +2,87 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class EncounterLearn { - public static class EncounterLearn + static EncounterLearn() { - static EncounterLearn() - { - if (!EncounterEvent.Initialized) - EncounterEvent.RefreshMGDB(); - } + if (!EncounterEvent.Initialized) + EncounterEvent.RefreshMGDB(); + } - /// - /// Default response if there are no matches. - /// - public const string NoMatches = "None"; + /// + /// Default response if there are no matches. + /// + public const string NoMatches = "None"; - /// - /// Checks if a can learn all input . - /// - public static bool CanLearn(string species, IEnumerable moves, int form = 0, string lang = GameLanguage.DefaultLanguage) - { - var encounters = GetLearn(species, moves, form, lang); - return encounters.Any(); - } + /// + /// Checks if a can learn all input . + /// + public static bool CanLearn(string species, IEnumerable moves, int form = 0, string lang = GameLanguage.DefaultLanguage) + { + var encounters = GetLearn(species, moves, form, lang); + return encounters.Any(); + } - /// - /// Gets a summary of all encounters where a can learn all input . - /// - public static IEnumerable GetLearnSummary(string species, IEnumerable moves, int form = 0, string lang = GameLanguage.DefaultLanguage) - { - var encounters = GetLearn(species, moves, form, lang); - var msg = Summarize(encounters).ToList(); - if (msg.Count == 0) - msg.Add(NoMatches); - return msg; - } + /// + /// Gets a summary of all encounters where a can learn all input . + /// + public static IEnumerable GetLearnSummary(string species, IEnumerable moves, int form = 0, string lang = GameLanguage.DefaultLanguage) + { + var encounters = GetLearn(species, moves, form, lang); + var msg = Summarize(encounters).ToList(); + if (msg.Count == 0) + msg.Add(NoMatches); + return msg; + } - /// - /// Gets all encounters where a can learn all input . - /// - public static IEnumerable GetLearn(string species, IEnumerable moves, int form = 0, string lang = GameLanguage.DefaultLanguage) - { - var str = GameInfo.GetStrings(lang); + /// + /// Gets all encounters where a can learn all input . + /// + public static IEnumerable GetLearn(string species, IEnumerable moves, int form = 0, string lang = GameLanguage.DefaultLanguage) + { + var str = GameInfo.GetStrings(lang); - var speciesID = StringUtil.FindIndexIgnoreCase(str.specieslist, species); - var moveIDs = StringUtil.GetIndexes(str.movelist, moves.ToList()); + var speciesID = StringUtil.FindIndexIgnoreCase(str.specieslist, species); + var moveIDs = StringUtil.GetIndexes(str.movelist, moves.ToList()); - return GetLearn(speciesID, moveIDs, form); - } + return GetLearn(speciesID, moveIDs, form); + } - /// - /// Gets all encounters where a can learn all input . - /// - public static IEnumerable GetLearn(int species, int[] moves, int form = 0) - { - if (species <= 0) - return Array.Empty(); - if (moves.Any(z => z < 0)) - return Array.Empty(); + /// + /// Gets all encounters where a can learn all input . + /// + public static IEnumerable GetLearn(int species, int[] moves, int form = 0) + { + if (species <= 0) + return Array.Empty(); + if (moves.Any(z => z < 0)) + return Array.Empty(); - var blank = EntityBlank.GetBlank(PKX.Generation); - blank.Species = species; - blank.Form = form; + var blank = EntityBlank.GetBlank(PKX.Generation); + blank.Species = species; + blank.Form = form; - var vers = GameUtil.GameVersions; - return EncounterMovesetGenerator.GenerateEncounters(blank, moves, vers); - } + var vers = GameUtil.GameVersions; + return EncounterMovesetGenerator.GenerateEncounters(blank, moves, vers); + } - /// - /// Summarizes all encounters by grouping by . - /// - public static IEnumerable Summarize(IEnumerable encounters, bool advanced = false) - { - var types = encounters.GroupBy(z => z.Name); - return Summarize(types, advanced); - } + /// + /// Summarizes all encounters by grouping by . + /// + public static IEnumerable Summarize(IEnumerable encounters, bool advanced = false) + { + var types = encounters.GroupBy(z => z.Name); + return Summarize(types, advanced); + } - /// - /// Summarizes groups of encounters. - /// - public static IEnumerable Summarize(IEnumerable> types, bool advanced = false) - { - return types.SelectMany(g => EncounterSummary.SummarizeGroup(g, g.Key, advanced)); - } + /// + /// Summarizes groups of encounters. + /// + public static IEnumerable Summarize(IEnumerable> types, bool advanced = false) + { + return types.SelectMany(g => EncounterSummary.SummarizeGroup(g, g.Key, advanced)); } } diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs index 33e59cade..3adb86933 100644 --- a/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestion.cs @@ -1,212 +1,171 @@ using System; -using System.Collections.Generic; using System.Linq; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for providing suggested property values with respect to the input data. +/// +public static class EncounterSuggestion { /// - /// Logic for providing suggested property values with respect to the input data. + /// Gets an object containing met data properties that might be legal. /// - public static class EncounterSuggestion + public static EncounterSuggestionData? GetSuggestedMetInfo(PKM pk) { - /// - /// Gets an object containing met data properties that might be legal. - /// - public static EncounterSuggestionData? GetSuggestedMetInfo(PKM pkm) - { - int loc = GetSuggestedTransferLocation(pkm); + int loc = GetSuggestedTransferLocation(pk); - if (pkm.WasEgg) - return GetSuggestedEncounterEgg(pkm, loc); + if (pk.WasEgg) + return GetSuggestedEncounterEgg(pk, loc); - var chain = EvolutionChain.GetValidPreEvolutions(pkm, maxLevel: 100, skipChecks: true); - var w = EncounterSlotGenerator.GetCaptureLocation(pkm, chain); - var s = EncounterStaticGenerator.GetStaticLocation(pkm, chain); - if (w is null) - return s is null ? null : GetSuggestedEncounter(pkm, s, loc); - if (s is null) - return GetSuggestedEncounter(pkm, w, loc); + var chain = EvolutionChain.GetValidPreEvolutions(pk, maxLevel: 100, skipChecks: true); + var w = EncounterSlotGenerator.GetCaptureLocation(pk, chain); + var s = EncounterStaticGenerator.GetStaticLocation(pk, chain); + if (w is null) + return s is null ? null : GetSuggestedEncounter(pk, s, loc); + if (s is null) + return GetSuggestedEncounter(pk, w, loc); - bool isDefinitelySlot = chain.Any(z => z.Species == w.Species && z.Form == w.Form); - bool isDefinitelyStatic = chain.Any(z => z.Species == s.Species && z.Form == s.Form); - IEncounterable obj = (isDefinitelySlot || !isDefinitelyStatic) ? w : s; - return GetSuggestedEncounter(pkm, obj, loc); - } - - private static EncounterSuggestionData GetSuggestedEncounterEgg(PKM pkm, int loc = -1) - { - int lvl = GetSuggestedEncounterEggMetLevel(pkm); - var met = loc != -1 ? loc : GetSuggestedEggMetLocation(pkm); - return new EncounterSuggestionData(pkm, met, (byte)lvl); - } - - public static int GetSuggestedEncounterEggMetLevel(PKM pkm) - { - if (!pkm.IsNative && pkm.Generation < 5) - return pkm.CurrentLevel; // be generous with transfer conditions - if (pkm.Format < 5) // and native - return pkm.Format == 2 && pkm.Met_Location != 0 ? 1 : 0; - return 1; // gen5+ - } - - public static int GetSuggestedEncounterEggLocationEgg(PKM pkm, bool traded = false) - { - return GetSuggestedEncounterEggLocationEgg(pkm.Generation, (GameVersion)pkm.Version, traded); - } - - public static int GetSuggestedEncounterEggLocationEgg(int generation, GameVersion version, bool traded = false) => generation switch - { - 1 or 2 or 3 => 0, - 4 => traded ? Locations.LinkTrade4 : Locations.Daycare4, - 5 => traded ? Locations.LinkTrade5 : Locations.Daycare5, - 8 when BDSP.Contains(version)=> traded ? Locations.LinkTrade6NPC : Locations.Daycare8b, - _ => traded ? Locations.LinkTrade6 : Locations.Daycare5, - }; - - private static EncounterSuggestionData GetSuggestedEncounter(PKM pkm, IEncounterable enc, int loc = -1) - { - var met = loc != -1 ? loc : enc.Location; - return new EncounterSuggestionData(pkm, enc, met); - } - - /// - public static int GetSuggestedEggMetLocation(PKM pkm) => EggStateLegality.GetEggHatchLocation((GameVersion)pkm.Version, pkm.Format); - - /// - /// Gets the correct Transfer Met location for the origin game. - /// - /// Pokémon data to suggest for - /// - /// Returns -1 if the met location is not overriden with a transfer location - /// - public static int GetSuggestedTransferLocation(PKM pkm) - { - if (pkm.Version == (int)GO) - return Locations.GO8; - if (pkm.HasOriginalMetLocation) - return -1; - if (pkm.VC1) - return Locations.Transfer1; - if (pkm.VC2) - return Locations.Transfer2; - if (pkm.Format == 4) // Pal Park - return Locations.Transfer3; - if (pkm.Format >= 5) // Transporter - return Legal.GetTransfer45MetLocation(pkm); - return -1; - } - - public static int GetLowestLevel(PKM pkm, byte startLevel) - { - if (startLevel >= 100) - startLevel = 100; - - var table = EvolutionTree.GetEvolutionTree(pkm.Context); - int count = 1; - for (byte i = 100; i >= startLevel; i--) - { - var evos = table.GetValidPreEvolutions(pkm, maxLevel: i, minLevel: startLevel, skipChecks: true); - if (evos.Length < count) // lost an evolution, prior level was minimum current level - return evos.Max(evo => evo.LevelMax) + 1; - count = evos.Length; - } - return startLevel; - } - - public static bool IterateMinimumCurrentLevel(PKM pk, bool isLegal, int max = 100) - { - var original = pk.CurrentLevel; - var originalEXP = pk.EXP; - if (isLegal) - { - if (original == 1) - return false; - max = original - 1; - } - - for (int i = max; i != 0; i--) - { - pk.CurrentLevel = i; - var la = new LegalityAnalysis(pk); - var valid = la.Valid; - if (valid) - continue; - - var revert = Math.Min(100, i + 1); - if (revert == original) - { - pk.EXP = originalEXP; - return false; - } - - pk.CurrentLevel = revert; - return true; - } - return true; // 1 - } - - /// - /// Gets the suggested based on a baseline and the 's current moves. - /// - /// Entity to calculate for - /// Encounter minimum level to calculate for - /// Minimum level the can have for its - /// Brute-forces the value by cloning the and adjusting the and returning the lowest valid value. - public static int GetSuggestedMetLevel(PKM pkm, int minLevel) - { - var clone = pkm.Clone(); - int minMove = -1; - for (int i = clone.CurrentLevel; i >= minLevel; i--) - { - clone.Met_Level = i; - var la = new LegalityAnalysis(clone); - if (la.Valid) - return i; - if (la.Info.Moves.All(z => z.Valid)) - minMove = i; - } - return Math.Max(minMove, minLevel); - } + bool isDefinitelySlot = chain.Any(z => z.Species == w.Species && z.Form == w.Form); + bool isDefinitelyStatic = chain.Any(z => z.Species == s.Species && z.Form == s.Form); + IEncounterable obj = (isDefinitelySlot || !isDefinitelyStatic) ? w : s; + return GetSuggestedEncounter(pk, obj, loc); } - public sealed class EncounterSuggestionData : ISpeciesForm, IRelearn + private static EncounterSuggestionData GetSuggestedEncounterEgg(PKM pk, int loc = -1) { - private readonly IEncounterable? Encounter; + int lvl = GetSuggestedEncounterEggMetLevel(pk); + var met = loc != -1 ? loc : GetSuggestedEggMetLocation(pk); + return new EncounterSuggestionData(pk, met, (byte)lvl); + } - public IReadOnlyList Relearn => Encounter is IRelearn r ? r.Relearn : Array.Empty(); + public static int GetSuggestedEncounterEggMetLevel(PKM pk) + { + if (!pk.IsNative && pk.Generation < 5) + return pk.CurrentLevel; // be generous with transfer conditions + if (pk.Format < 5) // and native + return pk.Format == 2 && pk.Met_Location != 0 ? 1 : 0; + return 1; // gen5+ + } - public EncounterSuggestionData(PKM pkm, IEncounterable enc, int met) + public static int GetSuggestedEncounterEggLocationEgg(PKM pk, bool traded = false) + { + return GetSuggestedEncounterEggLocationEgg(pk.Generation, (GameVersion)pk.Version, traded); + } + + public static int GetSuggestedEncounterEggLocationEgg(int generation, GameVersion version, bool traded = false) => generation switch + { + 1 or 2 or 3 => 0, + 4 => traded ? Locations.LinkTrade4 : Locations.Daycare4, + 5 => traded ? Locations.LinkTrade5 : Locations.Daycare5, + 8 when BDSP.Contains(version)=> traded ? Locations.LinkTrade6NPC : Locations.Daycare8b, + _ => traded ? Locations.LinkTrade6 : Locations.Daycare5, + }; + + private static EncounterSuggestionData GetSuggestedEncounter(PKM pk, IEncounterable enc, int loc = -1) + { + var met = loc != -1 ? loc : enc.Location; + return new EncounterSuggestionData(pk, enc, met); + } + + /// + public static int GetSuggestedEggMetLocation(PKM pk) => EggStateLegality.GetEggHatchLocation((GameVersion)pk.Version, pk.Format); + + /// + /// Gets the correct Transfer Met location for the origin game. + /// + /// Pokémon data to suggest for + /// + /// Returns -1 if the met location is not overriden with a transfer location + /// + public static int GetSuggestedTransferLocation(PKM pk) + { + if (pk.Version == (int)GO) + return Locations.GO8; + if (pk.HasOriginalMetLocation) + return -1; + if (pk.VC1) + return Locations.Transfer1; + if (pk.VC2) + return Locations.Transfer2; + if (pk.Format == 4) // Pal Park + return Locations.Transfer3; + if (pk.Format >= 5) // Transporter + return Legal.GetTransfer45MetLocation(pk); + return -1; + } + + public static int GetLowestLevel(PKM pk, byte startLevel) + { + if (startLevel >= 100) + startLevel = 100; + + var table = EvolutionTree.GetEvolutionTree(pk.Context); + int count = 1; + for (byte i = 100; i >= startLevel; i--) { - Encounter = enc; - Species = pkm.Species; - Form = pkm.Form; - Location = met; + var evos = table.GetValidPreEvolutions(pk, maxLevel: i, minLevel: startLevel, skipChecks: true); + if (evos.Length < count) // lost an evolution, prior level was minimum current level + return evos.Max(evo => evo.LevelMax) + 1; + count = evos.Length; + } + return startLevel; + } - LevelMin = enc.LevelMin; - LevelMax = enc.LevelMax; + public static bool IterateMinimumCurrentLevel(PKM pk, bool isLegal, int max = 100) + { + var original = pk.CurrentLevel; + var originalEXP = pk.EXP; + if (isLegal) + { + if (original == 1) + return false; + max = original - 1; } - public EncounterSuggestionData(PKM pkm, int met, byte lvl) + for (int i = max; i != 0; i--) { - Species = pkm.Species; - Form = pkm.Form; - Location = met; + pk.CurrentLevel = i; + var la = new LegalityAnalysis(pk); + var valid = la.Valid; + if (valid) + continue; - LevelMin = lvl; - LevelMax = lvl; + var revert = Math.Min(100, i + 1); + if (revert == original) + { + pk.EXP = originalEXP; + return false; + } + + pk.CurrentLevel = revert; + return true; } + return true; // 1 + } - public int Species { get; } - public int Form { get; } - public int Location { get; } - - public byte LevelMin { get; } - public byte LevelMax { get; } - - public int GetSuggestedMetLevel(PKM pkm) => EncounterSuggestion.GetSuggestedMetLevel(pkm, LevelMin); - public GroundTileType GetSuggestedGroundTile() => Encounter is IGroundTypeTile t ? t.GroundTile.GetIndex() : 0; - public bool HasGroundTile(int format) => Encounter is IGroundTypeTile t && t.HasGroundTile(format); + /// + /// Gets the suggested based on a baseline and the 's current moves. + /// + /// Entity to calculate for + /// Encounter minimum level to calculate for + /// Minimum level the can have for its + /// Brute-forces the value by cloning the and adjusting the and returning the lowest valid value. + public static int GetSuggestedMetLevel(PKM pk, int minLevel) + { + var clone = pk.Clone(); + int minMove = -1; + for (int i = clone.CurrentLevel; i >= minLevel; i--) + { + clone.Met_Level = i; + var la = new LegalityAnalysis(clone); + if (la.Valid) + return i; + if (la.Info.Moves.All(z => z.Valid)) + minMove = i; + } + return Math.Max(minMove, minLevel); } } diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestionData.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestionData.cs new file mode 100644 index 000000000..61845e0eb --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterSuggestionData.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; + +namespace PKHeX.Core; + +public sealed class EncounterSuggestionData : ISpeciesForm, IRelearn +{ + private readonly IEncounterable? Encounter; + + public IReadOnlyList Relearn => Encounter is IRelearn r ? r.Relearn : Array.Empty(); + + public EncounterSuggestionData(PKM pk, IEncounterable enc, int met) + { + Encounter = enc; + Species = pk.Species; + Form = pk.Form; + Location = met; + + LevelMin = enc.LevelMin; + LevelMax = enc.LevelMax; + } + + public EncounterSuggestionData(PKM pk, int met, byte lvl) + { + Species = pk.Species; + Form = pk.Form; + Location = met; + + LevelMin = lvl; + LevelMax = lvl; + } + + public int Species { get; } + public int Form { get; } + public int Location { get; } + + public byte LevelMin { get; } + public byte LevelMax { get; } + + public int GetSuggestedMetLevel(PKM pk) => EncounterSuggestion.GetSuggestedMetLevel(pk, LevelMin); + public GroundTileType GetSuggestedGroundTile() => Encounter is IGroundTypeTile t ? t.GroundTile.GetIndex() : 0; + public bool HasGroundTile(int format) => Encounter is IGroundTypeTile t && t.HasGroundTile(format); +} diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterSummary.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterSummary.cs index 864fd96f9..2f6bd2b63 100644 --- a/PKHeX.Core/Legality/Encounters/Information/EncounterSummary.cs +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterSummary.cs @@ -1,53 +1,52 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides a summary for an object. +/// +public record EncounterSummary { - /// - /// Provides a summary for an object. - /// - public record EncounterSummary + private readonly GameVersion Version; + private readonly string LocationName; + + private EncounterSummary(IEncounterTemplate z) { - private readonly GameVersion Version; - private readonly string LocationName; - - private EncounterSummary(IEncounterTemplate z) - { - Version = z.Version; - LocationName = GetLocationName(z); - } - - private static string GetLocationName(IEncounterTemplate z) - { - var gen = z.Generation; - var version = z.Version; - if (gen < 0 && version > 0) - gen = version.GetGeneration(); - - if (z is not ILocation l) - return $"[Gen{gen}]\t"; - var loc = l.GetEncounterLocation(gen, (int)version); - - if (string.IsNullOrWhiteSpace(loc)) - return $"[Gen{gen}]\t"; - return $"[Gen{gen}]\t{loc}: "; - } - - public static IEnumerable SummarizeGroup(IEnumerable items, string header = "", bool advanced = false) - { - if (!string.IsNullOrWhiteSpace(header)) - yield return $"=={header}=="; - var summaries = advanced ? GetSummaries(items) : items.Select(z => new EncounterSummary(z)); - var objs = summaries.GroupBy(z => z.LocationName); - foreach (var g in objs) - yield return $"\t{g.Key}{string.Join(", ", g.Select(z => z.Version).Distinct())}"; - } - - public static IEnumerable GetSummaries(IEnumerable items) => items.Select(GetSummary); - - private static EncounterSummary GetSummary(IEncounterTemplate item) => item switch - { - _ => new EncounterSummary(item), - }; + Version = z.Version; + LocationName = GetLocationName(z); } + + private static string GetLocationName(IEncounterTemplate z) + { + var gen = z.Generation; + var version = z.Version; + if (gen < 0 && version > 0) + gen = version.GetGeneration(); + + if (z is not ILocation l) + return $"[Gen{gen}]\t"; + var loc = l.GetEncounterLocation(gen, (int)version); + + if (string.IsNullOrWhiteSpace(loc)) + return $"[Gen{gen}]\t"; + return $"[Gen{gen}]\t{loc}: "; + } + + public static IEnumerable SummarizeGroup(IEnumerable items, string header = "", bool advanced = false) + { + if (!string.IsNullOrWhiteSpace(header)) + yield return $"=={header}=="; + var summaries = advanced ? GetSummaries(items) : items.Select(z => new EncounterSummary(z)); + var objs = summaries.GroupBy(z => z.LocationName); + foreach (var g in objs) + yield return $"\t{g.Key}{string.Join(", ", g.Select(z => z.Version).Distinct())}"; + } + + public static IEnumerable GetSummaries(IEnumerable items) => items.Select(GetSummary); + + private static EncounterSummary GetSummary(IEncounterTemplate item) => item switch + { + _ => new EncounterSummary(item), + }; } diff --git a/PKHeX.Core/Legality/Encounters/Information/ValidEncounterMoves.cs b/PKHeX.Core/Legality/Encounters/Information/ValidEncounterMoves.cs index f8a68180d..1157c2a89 100644 --- a/PKHeX.Core/Legality/Encounters/Information/ValidEncounterMoves.cs +++ b/PKHeX.Core/Legality/Encounters/Information/ValidEncounterMoves.cs @@ -2,55 +2,54 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Object which stores information useful for analyzing a moveset relative to the encounter data. +/// +public sealed class ValidEncounterMoves { - /// - /// Object which stores information useful for analyzing a moveset relative to the encounter data. - /// - public sealed class ValidEncounterMoves + public readonly IReadOnlyList[] LevelUpMoves; + public readonly IReadOnlyList[] TMHMMoves = Empty; + public readonly IReadOnlyList[] TutorMoves = Empty; + public int[] Relearn = Array.Empty(); + + private const int EmptyCount = PKX.Generation + 1; // one for each generation index (and 0th) + private static readonly IReadOnlyList[] Empty = Enumerable.Repeat((IReadOnlyList)new List(), EmptyCount).ToArray(); + + public ValidEncounterMoves(PKM pk, IEncounterTemplate encounter, EvolutionHistory chains) { - public readonly IReadOnlyList[] LevelUpMoves; - public readonly IReadOnlyList[] TMHMMoves = Empty; - public readonly IReadOnlyList[] TutorMoves = Empty; - public int[] Relearn = Array.Empty(); + var level = MoveList.GetValidMovesAllGens(pk, chains, types: MoveSourceType.Encounter, RemoveTransferHM: false); - private const int EmptyCount = PKX.Generation + 1; // one for each generation index (and 0th) - private static readonly IReadOnlyList[] Empty = Enumerable.Repeat((IReadOnlyList)new List(), EmptyCount).ToArray(); + int gen = encounter.Generation; + if ((uint)gen < level.Length && level[gen] is List x) + AddEdgeCaseMoves(x, encounter, pk); - public ValidEncounterMoves(PKM pkm, IEncounterTemplate encounter, EvolutionHistory chains) - { - var level = MoveList.GetValidMovesAllGens(pkm, chains, types: MoveSourceType.Encounter, RemoveTransferHM: false); - - int gen = encounter.Generation; - if ((uint)gen < level.Length && level[gen] is List x) - AddEdgeCaseMoves(x, encounter, pkm); - - LevelUpMoves = level; - TMHMMoves = MoveList.GetValidMovesAllGens(pkm, chains, types: MoveSourceType.AllMachines); - TutorMoves = MoveList.GetValidMovesAllGens(pkm, chains, types: MoveSourceType.AllTutors); - } - - private static void AddEdgeCaseMoves(List moves, IEncounterTemplate encounter, PKM pkm) - { - if (pkm is IBattleVersion {BattleVersion: not 0}) - return; - - switch (encounter) - { - case EncounterStatic8N r when r.IsDownLeveled(pkm): // Downleveled Raid can happen for shared raids and self-hosted raids. - moves.AddRange(MoveLevelUp.GetMovesLevelUp(pkm, r.Species, r.Form, r.LevelMax, 0, 0, GameVersion.SW, false, 8)); - break; - case EncounterSlot8GO g: - moves.AddRange(MoveLevelUp.GetEncounterMoves(g.Species, g.Form, pkm.Met_Level, g.OriginGroup)); - break; - } - } - - public ValidEncounterMoves(IReadOnlyList[] levelup) - { - LevelUpMoves = levelup; - } - - public ValidEncounterMoves() : this(Empty) { } + LevelUpMoves = level; + TMHMMoves = MoveList.GetValidMovesAllGens(pk, chains, types: MoveSourceType.AllMachines); + TutorMoves = MoveList.GetValidMovesAllGens(pk, chains, types: MoveSourceType.AllTutors); } + + private static void AddEdgeCaseMoves(List moves, IEncounterTemplate encounter, PKM pk) + { + if (pk is IBattleVersion {BattleVersion: not 0}) + return; + + switch (encounter) + { + case EncounterStatic8N r when r.IsDownLeveled(pk): // Downleveled Raid can happen for shared raids and self-hosted raids. + moves.AddRange(MoveLevelUp.GetMovesLevelUp(pk, r.Species, r.Form, r.LevelMax, 0, 0, GameVersion.SW, false, 8)); + break; + case EncounterSlot8GO g: + moves.AddRange(MoveLevelUp.GetEncounterMoves(g.Species, g.Form, pk.Met_Level, g.OriginGroup)); + break; + } + } + + public ValidEncounterMoves(IReadOnlyList[] levelup) + { + LevelUpMoves = levelup; + } + + public ValidEncounterMoves() : this(Empty) { } } diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs b/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs index 63e64be8d..4452af042 100644 --- a/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs +++ b/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs @@ -2,326 +2,325 @@ using System.Collections.Generic; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core -{ - public static class EncounterVerifier - { - /// - /// Gets the method to verify the data. - /// - /// Source generation to verify - /// Returns the verification method appropriate for the input PKM - public static Func GetEncounterVerifierMethod(int generation) => generation switch - { - 1 or 2 => VerifyEncounterG12, - _ => VerifyEncounter, - }; +namespace PKHeX.Core; - private static CheckResult VerifyEncounter(PKM pkm, IEncounterTemplate enc) => enc switch +public static class EncounterVerifier +{ + /// + /// Gets the method to verify the data. + /// + /// Source generation to verify + /// Returns the verification method appropriate for the input PKM + public static Func GetEncounterVerifierMethod(int generation) => generation switch + { + 1 or 2 => VerifyEncounterG12, + _ => VerifyEncounter, + }; + + private static CheckResult VerifyEncounter(PKM pk, IEncounterTemplate enc) => enc switch + { + EncounterEgg e => VerifyEncounterEgg(pk, e.Generation), + EncounterTrade t => VerifyEncounterTrade(pk, t), + EncounterSlot w => VerifyEncounterWild(w), + EncounterStatic s => VerifyEncounterStatic(pk, s), + MysteryGift g => VerifyEncounterEvent(pk, g), + _ => new CheckResult(Severity.Invalid, LEncInvalid, CheckIdentifier.Encounter), + }; + + private static CheckResult VerifyEncounterG12(PKM pk, IEncounterTemplate enc) + { + if (enc.EggEncounter) + return VerifyEncounterEgg(pk, enc.Generation); + + return enc switch { - EncounterEgg e => VerifyEncounterEgg(pkm, e.Generation), - EncounterTrade t => VerifyEncounterTrade(pkm, t), - EncounterSlot w => VerifyEncounterWild(w), - EncounterStatic s => VerifyEncounterStatic(pkm, s), - MysteryGift g => VerifyEncounterEvent(pkm, g), + EncounterSlot1 => new CheckResult(Severity.Valid, LEncCondition, CheckIdentifier.Encounter), + EncounterSlot2 s2 => VerifyWildEncounterGen2(pk, s2), + EncounterStatic s => VerifyEncounterStatic(pk, s), + EncounterTrade t => VerifyEncounterTrade(pk, t), _ => new CheckResult(Severity.Invalid, LEncInvalid, CheckIdentifier.Encounter), }; + } - private static CheckResult VerifyEncounterG12(PKM pkm, IEncounterTemplate enc) + // Gen2 Wild Encounters + private static CheckResult VerifyWildEncounterGen2(PKM pk, EncounterSlot2 encounter) + { + switch (encounter.SlotType) { - if (enc.EggEncounter) - return VerifyEncounterEgg(pkm, enc.Generation); + case SlotType.Headbutt: + return VerifyWildEncounterCrystalHeadbutt(pk, encounter); - return enc switch - { - EncounterSlot1 => new CheckResult(Severity.Valid, LEncCondition, CheckIdentifier.Encounter), - EncounterSlot2 s2 => VerifyWildEncounterGen2(pkm, s2), - EncounterStatic s => VerifyEncounterStatic(pkm, s), - EncounterTrade t => VerifyEncounterTrade(pkm, t), - _ => new CheckResult(Severity.Invalid, LEncInvalid, CheckIdentifier.Encounter), - }; + case SlotType.Old_Rod or SlotType.Good_Rod or SlotType.Super_Rod: + switch (encounter.Location) + { + case 19: // National Park + return new CheckResult(Severity.Invalid, LG2InvalidTilePark, CheckIdentifier.Encounter); + case 76: // Route 14 + return new CheckResult(Severity.Invalid, LG2InvalidTileR14, CheckIdentifier.Encounter); + } + break; } - // Gen2 Wild Encounters - private static CheckResult VerifyWildEncounterGen2(PKM pkm, EncounterSlot2 encounter) - { - switch (encounter.GetSlotType()) - { - case SlotType.Headbutt: - return VerifyWildEncounterCrystalHeadbutt(pkm, encounter); + return new CheckResult(Severity.Valid, LEncCondition, CheckIdentifier.Encounter); + } - case SlotType.Old_Rod or SlotType.Good_Rod or SlotType.Super_Rod: - switch (encounter.Location) - { - case 19: // National Park - return new CheckResult(Severity.Invalid, LG2InvalidTilePark, CheckIdentifier.Encounter); - case 76: // Route 14 - return new CheckResult(Severity.Invalid, LG2InvalidTileR14, CheckIdentifier.Encounter); - } - break; - } + private static CheckResult VerifyWildEncounterCrystalHeadbutt(ITrainerID tr, EncounterSlot2 s2) + { + return s2.IsTreeAvailable(tr.TID) + ? new CheckResult(Severity.Valid, LG2TreeID, CheckIdentifier.Encounter) + : new CheckResult(Severity.Invalid, LG2InvalidTileTreeNotFound, CheckIdentifier.Encounter); + } - return new CheckResult(Severity.Valid, LEncCondition, CheckIdentifier.Encounter); - } + // Eggs + private static CheckResult VerifyEncounterEgg(PKM pk, int gen) => gen switch + { + 2 => new CheckResult(CheckIdentifier.Encounter), // valid -- no met location info + 3 => pk.IsEgg ? VerifyUnhatchedEgg3(pk) : VerifyEncounterEgg3(pk), + 4 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade4) : VerifyEncounterEgg4(pk), + 5 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade5) : VerifyEncounterEgg5(pk), + 6 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade6) : VerifyEncounterEgg6(pk), + 7 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade6) : VerifyEncounterEgg7(pk), + 8 when GameVersion.BDSP.Contains((GameVersion)pk.Version) => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade6NPC, Locations.Default8bNone) : VerifyEncounterEgg8BDSP(pk), + 8 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade6) : VerifyEncounterEgg8(pk), + _ => new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter), + }; - private static CheckResult VerifyWildEncounterCrystalHeadbutt(ITrainerID tr, EncounterSlot2 s2) - { - return s2.IsTreeAvailable(tr.TID) - ? new CheckResult(Severity.Valid, LG2TreeID, CheckIdentifier.Encounter) - : new CheckResult(Severity.Invalid, LG2InvalidTileTreeNotFound, CheckIdentifier.Encounter); - } + private static CheckResult VerifyUnhatchedEgg3(PKM pk) + { + if (pk.Met_Level != 0) + return new CheckResult(Severity.Invalid, string.Format(LEggFMetLevel_0, 0), CheckIdentifier.Encounter); - // Eggs - private static CheckResult VerifyEncounterEgg(PKM pkm, int gen) => gen switch - { - 2 => new CheckResult(CheckIdentifier.Encounter), // valid -- no met location info - 3 => pkm.IsEgg ? VerifyUnhatchedEgg3(pkm) : VerifyEncounterEgg3(pkm), - 4 => pkm.IsEgg ? VerifyUnhatchedEgg(pkm, Locations.LinkTrade4) : VerifyEncounterEgg4(pkm), - 5 => pkm.IsEgg ? VerifyUnhatchedEgg(pkm, Locations.LinkTrade5) : VerifyEncounterEgg5(pkm), - 6 => pkm.IsEgg ? VerifyUnhatchedEgg(pkm, Locations.LinkTrade6) : VerifyEncounterEgg6(pkm), - 7 => pkm.IsEgg ? VerifyUnhatchedEgg(pkm, Locations.LinkTrade6) : VerifyEncounterEgg7(pkm), - 8 when GameVersion.BDSP.Contains((GameVersion)pkm.Version) => pkm.IsEgg ? VerifyUnhatchedEgg(pkm, Locations.LinkTrade6NPC, Locations.Default8bNone) : VerifyEncounterEgg8BDSP(pkm), - 8 => pkm.IsEgg ? VerifyUnhatchedEgg(pkm, Locations.LinkTrade6) : VerifyEncounterEgg8(pkm), - _ => new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter), - }; + // Only EncounterEgg should reach here. + var loc = pk.FRLG ? Locations.HatchLocationFRLG : Locations.HatchLocationRSE; + if (pk.Met_Location != loc) + return new CheckResult(Severity.Invalid, LEggMetLocationFail, CheckIdentifier.Encounter); - private static CheckResult VerifyUnhatchedEgg3(PKM pkm) - { - if (pkm.Met_Level != 0) - return new CheckResult(Severity.Invalid, string.Format(LEggFMetLevel_0, 0), CheckIdentifier.Encounter); + return new CheckResult(Severity.Valid, LEggLocation, CheckIdentifier.Encounter); + } - // Only EncounterEgg should reach here. - var loc = pkm.FRLG ? Locations.HatchLocationFRLG : Locations.HatchLocationRSE; - if (pkm.Met_Location != loc) - return new CheckResult(Severity.Invalid, LEggMetLocationFail, CheckIdentifier.Encounter); + private static CheckResult VerifyEncounterEgg3(PKM pk) + { + if (pk.Format != 3) + return VerifyEncounterEgg3Transfer(pk); + if (pk.Met_Level != 0) + return new CheckResult(Severity.Invalid, string.Format(LEggFMetLevel_0, 0), CheckIdentifier.Encounter); + + // Check the origin game list. + var met = pk.Met_Location; + var locs = pk.FRLG ? Legal.ValidMet_FRLG : pk.E ? Legal.ValidMet_E : Legal.ValidMet_RS; + if (locs.Contains(met)) return new CheckResult(Severity.Valid, LEggLocation, CheckIdentifier.Encounter); - } - private static CheckResult VerifyEncounterEgg3(PKM pkm) + // Version isn't updated when hatching on a different game. Check any game. + if (Legal.ValidMet_FRLG.Contains(met) || Legal.ValidMet_E.Contains(met) || Legal.ValidMet_RS.Contains(met)) + return new CheckResult(Severity.Valid, LEggLocationTrade, CheckIdentifier.Encounter); + return new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter); + } + + private static CheckResult VerifyEncounterEgg3Transfer(PKM pk) + { + if (pk.IsEgg) + return new CheckResult(Severity.Invalid, LTransferEgg, CheckIdentifier.Encounter); + if (pk.Met_Level < 5) + return new CheckResult(Severity.Invalid, LTransferEggMetLevel, CheckIdentifier.Encounter); + + var expectEgg = pk is PB8 ? Locations.Default8bNone : 0; + if (pk.Egg_Location != expectEgg) + return new CheckResult(Severity.Invalid, LEggLocationNone, CheckIdentifier.Encounter); + + if (pk.Format != 4) { - if (pkm.Format != 3) - return VerifyEncounterEgg3Transfer(pkm); - - if (pkm.Met_Level != 0) - return new CheckResult(Severity.Invalid, string.Format(LEggFMetLevel_0, 0), CheckIdentifier.Encounter); - - // Check the origin game list. - var met = pkm.Met_Location; - var locs = pkm.FRLG ? Legal.ValidMet_FRLG : pkm.E ? Legal.ValidMet_E : Legal.ValidMet_RS; - if (locs.Contains(met)) - return new CheckResult(Severity.Valid, LEggLocation, CheckIdentifier.Encounter); - - // Version isn't updated when hatching on a different game. Check any game. - if (Legal.ValidMet_FRLG.Contains(met) || Legal.ValidMet_E.Contains(met) || Legal.ValidMet_RS.Contains(met)) - return new CheckResult(Severity.Valid, LEggLocationTrade, CheckIdentifier.Encounter); - return new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter); - } - - private static CheckResult VerifyEncounterEgg3Transfer(PKM pkm) - { - if (pkm.IsEgg) - return new CheckResult(Severity.Invalid, LTransferEgg, CheckIdentifier.Encounter); - if (pkm.Met_Level < 5) - return new CheckResult(Severity.Invalid, LTransferEggMetLevel, CheckIdentifier.Encounter); - - var expectEgg = pkm is PB8 ? Locations.Default8bNone : 0; - if (pkm.Egg_Location != expectEgg) - return new CheckResult(Severity.Invalid, LEggLocationNone, CheckIdentifier.Encounter); - - if (pkm.Format != 4) - { - if (pkm.Met_Location != Locations.Transfer4) - return new CheckResult(Severity.Invalid, LTransferEggLocationTransporter, CheckIdentifier.Encounter); - } - else - { - if (pkm.Met_Location != Locations.Transfer3) - return new CheckResult(Severity.Invalid, LEggLocationPalPark, CheckIdentifier.Encounter); - } - - return new CheckResult(Severity.Valid, LEggLocation, CheckIdentifier.Encounter); - } - - private static CheckResult VerifyEncounterEgg4(PKM pkm) - { - if (pkm.Format == 4) - { - // Traded eggs don't update Version, like in future games. - var locations = pkm.WasTradedEgg ? Legal.ValidMet_4 : - pkm.HGSS ? Legal.ValidMet_HGSS : - pkm.Pt ? Legal.ValidMet_Pt : - Legal.ValidMet_DP; - return VerifyEncounterEggLevelLoc(pkm, 0, locations); - } - if (pkm.IsEgg) - return new CheckResult(Severity.Invalid, LTransferEgg, CheckIdentifier.Encounter); - - // transferred - if (pkm.Met_Level < 1) - return new CheckResult(Severity.Invalid, LTransferEggMetLevel, CheckIdentifier.Encounter); - if (pkm.Met_Location != Locations.Transfer4) + if (pk.Met_Location != Locations.Transfer4) return new CheckResult(Severity.Invalid, LTransferEggLocationTransporter, CheckIdentifier.Encounter); - return new CheckResult(Severity.Valid, LEggLocation, CheckIdentifier.Encounter); } - - private static CheckResult VerifyEncounterEgg5(PKM pkm) + else { - return VerifyEncounterEggLevelLoc(pkm, 1, pkm.B2W2 ? Legal.ValidMet_B2W2 : Legal.ValidMet_BW); + if (pk.Met_Location != Locations.Transfer3) + return new CheckResult(Severity.Invalid, LEggLocationPalPark, CheckIdentifier.Encounter); } - private static CheckResult VerifyEncounterEgg6(PKM pkm) + return new CheckResult(Severity.Valid, LEggLocation, CheckIdentifier.Encounter); + } + + private static CheckResult VerifyEncounterEgg4(PKM pk) + { + if (pk.Format == 4) { - if (pkm.AO) - return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_AO); - - if (pkm.Egg_Location == Locations.HatchLocation6AO) // Battle Resort Daycare is only OR/AS. - return new CheckResult(Severity.Invalid, LEggMetLocationFail, CheckIdentifier.Encounter); - - return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_XY); + // Traded eggs don't update Version, like in future games. + var locations = pk.WasTradedEgg ? Legal.ValidMet_4 : + pk.HGSS ? Legal.ValidMet_HGSS : + pk.Pt ? Legal.ValidMet_Pt : + Legal.ValidMet_DP; + return VerifyEncounterEggLevelLoc(pk, 0, locations); } + if (pk.IsEgg) + return new CheckResult(Severity.Invalid, LTransferEgg, CheckIdentifier.Encounter); - private static CheckResult VerifyEncounterEgg7(PKM pkm) + // transferred + if (pk.Met_Level < 1) + return new CheckResult(Severity.Invalid, LTransferEggMetLevel, CheckIdentifier.Encounter); + if (pk.Met_Location != Locations.Transfer4) + return new CheckResult(Severity.Invalid, LTransferEggLocationTransporter, CheckIdentifier.Encounter); + return new CheckResult(Severity.Valid, LEggLocation, CheckIdentifier.Encounter); + } + + private static CheckResult VerifyEncounterEgg5(PKM pk) + { + return VerifyEncounterEggLevelLoc(pk, 1, pk.B2W2 ? Legal.ValidMet_B2W2 : Legal.ValidMet_BW); + } + + private static CheckResult VerifyEncounterEgg6(PKM pk) + { + if (pk.AO) + return VerifyEncounterEggLevelLoc(pk, 1, Legal.ValidMet_AO); + + if (pk.Egg_Location == Locations.HatchLocation6AO) // Battle Resort Daycare is only OR/AS. + return new CheckResult(Severity.Invalid, LEggMetLocationFail, CheckIdentifier.Encounter); + + return VerifyEncounterEggLevelLoc(pk, 1, Legal.ValidMet_XY); + } + + private static CheckResult VerifyEncounterEgg7(PKM pk) + { + if (pk.SM) + return VerifyEncounterEggLevelLoc(pk, 1, Legal.ValidMet_SM); + if (pk.USUM) + return VerifyEncounterEggLevelLoc(pk, 1, Legal.ValidMet_USUM); + + // no other games + return new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter); + } + + private static CheckResult VerifyEncounterEgg8(PKM pk) + { + if (pk.SWSH) { - if (pkm.SM) - return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_SM); - if (pkm.USUM) - return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_USUM); - - // no other games - return new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter); + if (pk.BDSP) + return VerifyEncounterEggLevelLoc(pk, 1, (location, game) => location == (game == GameVersion.SW ? Locations.HOME_SWBD : Locations.HOME_SHSP)); + return VerifyEncounterEggLevelLoc(pk, 1, Legal.ValidMet_SWSH); } - private static CheckResult VerifyEncounterEgg8(PKM pkm) + // no other games + return new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter); + } + + private static CheckResult VerifyEncounterEgg8BDSP(PKM pk) + { + if (pk.BDSP) + return VerifyEncounterEggLevelLoc(pk, 1, Legal.IsValidEggHatchLocation8b); + + // no other games + return new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter); + } + + private static CheckResult VerifyEncounterEggLevelLoc(PKM pk, int eggLevel, ICollection MetLocations) + { + return VerifyEncounterEggLevelLoc(pk, eggLevel, (location, _) => MetLocations.Contains(location)); + } + + // (hatch location, hatch version, bool result) + private static CheckResult VerifyEncounterEggLevelLoc(PKM pk, int eggLevel, Func isValid) + { + if (pk.Met_Level != eggLevel) + return new CheckResult(Severity.Invalid, string.Format(LEggFMetLevel_0, eggLevel), CheckIdentifier.Encounter); + return isValid(pk.Met_Location, (GameVersion)pk.Version) + ? new CheckResult(Severity.Valid, LEggLocation, CheckIdentifier.Encounter) + : new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter); + } + + private static CheckResult VerifyUnhatchedEgg(PKM pk, int tradeLoc, int noneLoc = 0) + { + var eggLevel = pk.Format < 5 ? 0 : 1; + if (pk.Met_Level != eggLevel) + return new CheckResult(Severity.Invalid, string.Format(LEggFMetLevel_0, eggLevel), CheckIdentifier.Encounter); + if (pk.Egg_Location == tradeLoc) + return new CheckResult(Severity.Invalid, LEggLocationTradeFail, CheckIdentifier.Encounter); + + var met = pk.Met_Location; + if (met == tradeLoc) + return new CheckResult(Severity.Valid, LEggLocationTrade, CheckIdentifier.Encounter); + return met == noneLoc + ? new CheckResult(Severity.Valid, LEggUnhatched, CheckIdentifier.Encounter) + : new CheckResult(Severity.Invalid, LEggLocationNone, CheckIdentifier.Encounter); + } + + // Other + private static CheckResult VerifyEncounterWild(EncounterSlot slot) + { + var summary = slot.GetConditionString(out bool valid); + return new CheckResult(valid ? Severity.Valid : Severity.Invalid, summary, CheckIdentifier.Encounter); + } + + private static CheckResult VerifyEncounterStatic(PKM pk, EncounterStatic s) + { + // Check for Unreleased Encounters / Collisions + switch (s.Generation) { - if (pkm.SWSH) - { - if (pkm.BDSP) - return VerifyEncounterEggLevelLoc(pkm, 1, (location, game) => location == (game == GameVersion.SW ? Locations.HOME_SWBD : Locations.HOME_SHSP)); - return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_SWSH); - } + case 3: + if (s is EncounterStaticShadow {EReader: true} && pk.Language != (int)LanguageID.Japanese) // Non-JP E-reader Pokemon + return new CheckResult(Severity.Invalid, LG3EReader, CheckIdentifier.Encounter); - // no other games - return new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter); + switch (s.Species) + { + case (int)Species.Mew when s.Location == 201 && pk.Language != (int)LanguageID.Japanese: // Non-JP Mew (Old Sea Map) + return new CheckResult(Severity.Invalid, LEncUnreleasedEMewJP, CheckIdentifier.Encounter); + case (int)Species.Deoxys when s.Location == 200 && pk.Language == (int)LanguageID.Japanese: // JP Deoxys (Birth Island) + return new CheckResult(Severity.Invalid, LEncUnreleased, CheckIdentifier.Encounter); + } + + break; + case 4: + if (s is EncounterStatic4 {Roaming: true} && pk.Met_Location == 193 && pk is IGroundTile {GroundTile:GroundTileType.Water}) // Roaming pokemon surfing in Johto Route 45 + return new CheckResult(Severity.Invalid, LG4InvalidTileR45Surf, CheckIdentifier.Encounter); + break; + case 7: + if (s.EggLocation == Locations.Daycare5 && pk.RelearnMove1 != 0) // Eevee gift egg + return new CheckResult(Severity.Invalid, LEncStaticRelearn, CheckIdentifier.RelearnMove); // not gift egg + break; } - - private static CheckResult VerifyEncounterEgg8BDSP(PKM pkm) + if (s.EggEncounter && !pk.IsEgg) // hatched { - if (pkm.BDSP) - return VerifyEncounterEggLevelLoc(pkm, 1, Legal.IsValidEggHatchLocation8b); - - // no other games - return new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter); + var hatchCheck = VerifyEncounterEgg(pk, s.Generation); + if (!hatchCheck.Valid) + return hatchCheck; } - private static CheckResult VerifyEncounterEggLevelLoc(PKM pkm, int eggLevel, ICollection MetLocations) + return new CheckResult(Severity.Valid, LEncStaticMatch, CheckIdentifier.Encounter); + } + + private static CheckResult VerifyEncounterTrade(ISpeciesForm pk, EncounterTrade trade) + { + var species = pk.Species; + if (trade.EvolveOnTrade && trade.Species == species) { - return VerifyEncounterEggLevelLoc(pkm, eggLevel, (location, _) => MetLocations.Contains(location)); + // Pokemon that evolve on trade can not be in the phase evolution after the trade + // If the trade holds an everstone EvolveOnTrade will be false for the encounter + var names = ParseSettings.SpeciesStrings; + var evolved = names[species + 1]; + var unevolved = names[species]; + return new CheckResult(Severity.Invalid, string.Format(LEvoTradeReq, unevolved, evolved), CheckIdentifier.Encounter); } + return new CheckResult(Severity.Valid, LEncTradeMatch, CheckIdentifier.Encounter); + } - // (hatch location, hatch version, bool result) - private static CheckResult VerifyEncounterEggLevelLoc(PKM pkm, int eggLevel, Func isValid) + private static CheckResult VerifyEncounterEvent(PKM pk, MysteryGift gift) + { + switch (gift) { - if (pkm.Met_Level != eggLevel) - return new CheckResult(Severity.Invalid, string.Format(LEggFMetLevel_0, eggLevel), CheckIdentifier.Encounter); - return isValid(pkm.Met_Location, (GameVersion)pkm.Version) - ? new CheckResult(Severity.Valid, LEggLocation, CheckIdentifier.Encounter) - : new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter); + case PCD pcd: + if (!pcd.CanBeReceivedBy(pk.Version) && pcd.Gift.PK.Version == 0) + return new CheckResult(Severity.Invalid, string.Format(L_XMatches0_1, gift.CardHeader, $"-- {LEncGiftVersionNotDistributed}"), CheckIdentifier.Encounter); + break; } - - private static CheckResult VerifyUnhatchedEgg(PKM pkm, int tradeLoc, int noneLoc = 0) + if (!pk.IsEgg && gift.IsEgg) // hatched { - var eggLevel = pkm.Format < 5 ? 0 : 1; - if (pkm.Met_Level != eggLevel) - return new CheckResult(Severity.Invalid, string.Format(LEggFMetLevel_0, eggLevel), CheckIdentifier.Encounter); - if (pkm.Egg_Location == tradeLoc) - return new CheckResult(Severity.Invalid, LEggLocationTradeFail, CheckIdentifier.Encounter); - - var met = pkm.Met_Location; - if (met == tradeLoc) - return new CheckResult(Severity.Valid, LEggLocationTrade, CheckIdentifier.Encounter); - return met == noneLoc - ? new CheckResult(Severity.Valid, LEggUnhatched, CheckIdentifier.Encounter) - : new CheckResult(Severity.Invalid, LEggLocationNone, CheckIdentifier.Encounter); + var hatchCheck = VerifyEncounterEgg(pk, gift.Generation); + if (!hatchCheck.Valid) + return hatchCheck; } - // Other - private static CheckResult VerifyEncounterWild(EncounterSlot slot) - { - var summary = slot.GetConditionString(out bool valid); - return new CheckResult(valid ? Severity.Valid : Severity.Invalid, summary, CheckIdentifier.Encounter); - } - - private static CheckResult VerifyEncounterStatic(PKM pkm, EncounterStatic s) - { - // Check for Unreleased Encounters / Collisions - switch (s.Generation) - { - case 3: - if (s is EncounterStaticShadow {EReader: true} && pkm.Language != (int)LanguageID.Japanese) // Non-JP E-reader Pokemon - return new CheckResult(Severity.Invalid, LG3EReader, CheckIdentifier.Encounter); - - switch (s.Species) - { - case (int)Species.Mew when s.Location == 201 && pkm.Language != (int)LanguageID.Japanese: // Non-JP Mew (Old Sea Map) - return new CheckResult(Severity.Invalid, LEncUnreleasedEMewJP, CheckIdentifier.Encounter); - case (int)Species.Deoxys when s.Location == 200 && pkm.Language == (int)LanguageID.Japanese: // JP Deoxys (Birth Island) - return new CheckResult(Severity.Invalid, LEncUnreleased, CheckIdentifier.Encounter); - } - - break; - case 4: - if (s is EncounterStatic4 {Roaming: true} && pkm.Met_Location == 193 && pkm is IGroundTile {GroundTile:GroundTileType.Water}) // Roaming pokemon surfing in Johto Route 45 - return new CheckResult(Severity.Invalid, LG4InvalidTileR45Surf, CheckIdentifier.Encounter); - break; - case 7: - if (s.EggLocation == Locations.Daycare5 && pkm.RelearnMove1 != 0) // Eevee gift egg - return new CheckResult(Severity.Invalid, LEncStaticRelearn, CheckIdentifier.RelearnMove); // not gift egg - break; - } - if (s.EggEncounter && !pkm.IsEgg) // hatched - { - var hatchCheck = VerifyEncounterEgg(pkm, s.Generation); - if (!hatchCheck.Valid) - return hatchCheck; - } - - return new CheckResult(Severity.Valid, LEncStaticMatch, CheckIdentifier.Encounter); - } - - private static CheckResult VerifyEncounterTrade(ISpeciesForm pkm, EncounterTrade trade) - { - var species = pkm.Species; - if (trade.EvolveOnTrade && trade.Species == species) - { - // Pokemon that evolve on trade can not be in the phase evolution after the trade - // If the trade holds an everstone EvolveOnTrade will be false for the encounter - var names = ParseSettings.SpeciesStrings; - var evolved = names[species + 1]; - var unevolved = names[species]; - return new CheckResult(Severity.Invalid, string.Format(LEvoTradeReq, unevolved, evolved), CheckIdentifier.Encounter); - } - return new CheckResult(Severity.Valid, LEncTradeMatch, CheckIdentifier.Encounter); - } - - private static CheckResult VerifyEncounterEvent(PKM pkm, MysteryGift gift) - { - switch (gift) - { - case PCD pcd: - if (!pcd.CanBeReceivedBy(pkm.Version) && pcd.Gift.PK.Version == 0) - return new CheckResult(Severity.Invalid, string.Format(L_XMatches0_1, gift.CardHeader, $"-- {LEncGiftVersionNotDistributed}"), CheckIdentifier.Encounter); - break; - } - if (!pkm.IsEgg && gift.IsEgg) // hatched - { - var hatchCheck = VerifyEncounterEgg(pkm, gift.Generation); - if (!hatchCheck.Valid) - return hatchCheck; - } - - // Strict matching already performed by EncounterGenerator. May be worth moving some checks here to better flag invalid gifts. - return new CheckResult(Severity.Valid, string.Format(L_XMatches0_1, gift.CardHeader, string.Empty), CheckIdentifier.Encounter); - } + // Strict matching already performed by EncounterGenerator. May be worth moving some checks here to better flag invalid gifts. + return new CheckResult(Severity.Valid, string.Format(L_XMatches0_1, gift.CardHeader, string.Empty), CheckIdentifier.Encounter); } } diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/EvolutionVerifier.cs b/PKHeX.Core/Legality/Encounters/Verifiers/EvolutionVerifier.cs index 0b5a72c9e..0d2839700 100644 --- a/PKHeX.Core/Legality/Encounters/Verifiers/EvolutionVerifier.cs +++ b/PKHeX.Core/Legality/Encounters/Verifiers/EvolutionVerifier.cs @@ -2,58 +2,57 @@ using static PKHeX.Core.EvolutionRestrictions; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verify Evolution Information for a matched +/// +public static class EvolutionVerifier { /// - /// Verify Evolution Information for a matched + /// Verifies Evolution scenarios of an for an input and relevant . /// - public static class EvolutionVerifier + /// Source data to verify + /// Source supporting information to verify with + public static CheckResult VerifyEvolution(PKM pk, LegalInfo info) { - /// - /// Verifies Evolution scenarios of an for an input and relevant . - /// - /// Source data to verify - /// Source supporting information to verify with - public static CheckResult VerifyEvolution(PKM pkm, LegalInfo info) - { - // Check if basic evolution methods are satisfiable with this encounter. - if (!IsValidEvolution(pkm, info)) - return new CheckResult(Severity.Invalid, LEvoInvalid, CheckIdentifier.Evolution); + // Check if basic evolution methods are satisfiable with this encounter. + if (!IsValidEvolution(pk, info)) + return new CheckResult(Severity.Invalid, LEvoInvalid, CheckIdentifier.Evolution); - // Check if complex evolution methods are satisfiable with this encounter. - if (!IsValidEvolutionWithMove(pkm, info)) - return new CheckResult(Severity.Invalid, string.Format(LMoveEvoFCombination_0, ParseSettings.SpeciesStrings[pkm.Species]), CheckIdentifier.Evolution); + // Check if complex evolution methods are satisfiable with this encounter. + if (!IsValidEvolutionWithMove(pk, info)) + return new CheckResult(Severity.Invalid, string.Format(LMoveEvoFCombination_0, ParseSettings.SpeciesStrings[pk.Species]), CheckIdentifier.Evolution); - return VALID; - } + return VALID; + } - private static readonly CheckResult VALID = new(CheckIdentifier.Evolution); + private static readonly CheckResult VALID = new(CheckIdentifier.Evolution); - /// - /// Checks if the Evolution from the source is valid. - /// - /// Source data to verify - /// Source supporting information to verify with - /// Evolution is valid or not - private static bool IsValidEvolution(PKM pkm, LegalInfo info) - { - var chains = info.EvoChainsAllGens; - if (chains[pkm.Format].Length == 0) - return false; // Can't exist as current species - - // OK if un-evolved from original encounter - int species = pkm.Species; - if (info.EncounterMatch.Species == species) - return true; - - // Bigender->Fixed (non-Genderless) destination species, accounting for PID-Gender relationship - if (species == (int)Species.Vespiquen && info.Generation < 6 && (pkm.PID & 0xFF) >= 0x1F) // Combee->Vespiquen Invalid Evolution - return false; - - if (chains[info.Generation].All(z => z.Species != info.EncounterMatch.Species)) - return false; // Can't exist as origin species + /// + /// Checks if the Evolution from the source is valid. + /// + /// Source data to verify + /// Source supporting information to verify with + /// Evolution is valid or not + private static bool IsValidEvolution(PKM pk, LegalInfo info) + { + var chains = info.EvoChainsAllGens; + if (chains[pk.Format].Length == 0) + return false; // Can't exist as current species + // OK if un-evolved from original encounter + int species = pk.Species; + if (info.EncounterMatch.Species == species) return true; - } + + // Bigender->Fixed (non-Genderless) destination species, accounting for PID-Gender relationship + if (species == (int)Species.Vespiquen && info.Generation < 6 && (pk.PID & 0xFF) >= 0x1F) // Combee->Vespiquen Invalid Evolution + return false; + + if (chains[info.Generation].All(z => z.Species != info.EncounterMatch.Species)) + return false; // Can't exist as origin species + + return true; } } diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/GenerationTraversal.cs b/PKHeX.Core/Legality/Encounters/Verifiers/GenerationTraversal.cs index 42d974724..f73c59a36 100644 --- a/PKHeX.Core/Legality/Encounters/Verifiers/GenerationTraversal.cs +++ b/PKHeX.Core/Legality/Encounters/Verifiers/GenerationTraversal.cs @@ -1,53 +1,52 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class GenerationTraversal { - public static class GenerationTraversal + /// + /// Gets the generation numbers in descending order for iterating over. + /// + public static int[] GetVisitedGenerationOrder(PKM pk, int origin) { - /// - /// Gets the generation numbers in descending order for iterating over. - /// - public static int[] GetVisitedGenerationOrder(PKM pkm, int origin) - { - if (pkm.Format < 3) - return GetVisitedGenerationOrderGB(pkm, pkm.Format); - if (pkm.VC) - return GetVisitedGenerationOrderVC(pkm, origin); - return GetVisitedGenerationOrder(pkm.Format, origin); - } + if (pk.Format < 3) + return GetVisitedGenerationOrderGB(pk, pk.Format); + if (pk.VC) + return GetVisitedGenerationOrderVC(pk, origin); + return GetVisitedGenerationOrder(pk.Format, origin); + } - private static int[] GetVisitedGenerationOrderVC(PKM pkm, int origin) - { - // VC case: check transfer games in reverse order (8, 7..) then past games. - int[] xfer = GetVisitedGenerationOrder(pkm.Format, 7); - int[] past = GetVisitedGenerationOrderGB(pkm, origin); - int end = xfer.Length; - Array.Resize(ref xfer, xfer.Length + past.Length); - past.CopyTo(xfer, end); - return xfer; - } + private static int[] GetVisitedGenerationOrderVC(PKM pk, int origin) + { + // VC case: check transfer games in reverse order (8, 7..) then past games. + int[] xfer = GetVisitedGenerationOrder(pk.Format, 7); + int[] past = GetVisitedGenerationOrderGB(pk, origin); + int end = xfer.Length; + Array.Resize(ref xfer, xfer.Length + past.Length); + past.CopyTo(xfer, end); + return xfer; + } - private static readonly int[] G2 = { 2 }; - private static readonly int[] G12 = { 1, 2 }; - private static readonly int[] G21 = { 2, 1 }; + private static readonly int[] G2 = { 2 }; + private static readonly int[] G12 = { 1, 2 }; + private static readonly int[] G21 = { 2, 1 }; - private static int[] GetVisitedGenerationOrderGB(PKM pkm, int originalGeneration) - { - if (originalGeneration == 2) - return pkm.Korean ? G2 : G21; - return G12; // RBY - } + private static int[] GetVisitedGenerationOrderGB(PKM pk, int originalGeneration) + { + if (originalGeneration == 2) + return pk.Korean ? G2 : G21; + return G12; // RBY + } - private static int[] GetVisitedGenerationOrder(int start, int end) - { - if (end < 0) - return Array.Empty(); - if (start <= end) - return new[] { start }; - var order = new int[start - end + 1]; - for (int i = 0; i < order.Length; i++) - order[i] = start - i; - return order; - } + private static int[] GetVisitedGenerationOrder(int start, int end) + { + if (end < 0) + return Array.Empty(); + if (start <= end) + return new[] { start }; + var order = new int[start - end + 1]; + for (int i = 0; i < order.Length; i++) + order[i] = start - i; + return order; } } diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/MysteryGiftRestriction.cs b/PKHeX.Core/Legality/Encounters/Verifiers/MysteryGiftRestriction.cs index 8cf39f583..d4e8673b8 100644 --- a/PKHeX.Core/Legality/Encounters/Verifiers/MysteryGiftRestriction.cs +++ b/PKHeX.Core/Legality/Encounters/Verifiers/MysteryGiftRestriction.cs @@ -1,67 +1,66 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Restriction Flags for receiving 3DS/NDS era events. +/// +[Flags] +public enum MysteryGiftRestriction { + None = 0, + LangJapanese = 1 << LanguageID.Japanese, + LangEnglish = 1 << LanguageID.English, + LangFrench = 1 << LanguageID.French, + LangItalian = 1 << LanguageID.Italian, + LangGerman = 1 << LanguageID.German, + LangSpanish = 1 << LanguageID.Spanish, + LangKorean = 1 << LanguageID.Korean, + + RegionBase = LangKorean, + LangRestrict = LangJapanese | LangEnglish | LangFrench | LangItalian | LangGerman | LangSpanish | LangKorean, + + RegJP = RegionBase << Region3DSIndex.Japan, + RegNA = RegionBase << Region3DSIndex.NorthAmerica, + RegEU = RegionBase << Region3DSIndex.Europe, + RegZH = RegionBase << Region3DSIndex.China, + RegKO = RegionBase << Region3DSIndex.Korea, + RegTW = RegionBase << Region3DSIndex.Taiwan, + + RegionRestrict = RegJP | RegNA | RegEU | RegZH | RegKO | RegTW, + + OTReplacedOnTrade = RegTW << 1, +} + +public static class MysteryGiftRestrictionExtensions +{ + public static bool HasFlagFast(this MysteryGiftRestriction value, MysteryGiftRestriction flag) + { + return (value & flag) != 0; + } + /// - /// Restriction Flags for receiving 3DS/NDS era events. + /// Checks the flags to pick out a language that can receive the gift. /// - [Flags] - public enum MysteryGiftRestriction + /// Flag value + /// Language ID; -1 if none + public static int GetSuggestedLanguage(this MysteryGiftRestriction value) { - None = 0, - LangJapanese = 1 << LanguageID.Japanese, - LangEnglish = 1 << LanguageID.English, - LangFrench = 1 << LanguageID.French, - LangItalian = 1 << LanguageID.Italian, - LangGerman = 1 << LanguageID.German, - LangSpanish = 1 << LanguageID.Spanish, - LangKorean = 1 << LanguageID.Korean, - - RegionBase = LangKorean, - LangRestrict = LangJapanese | LangEnglish | LangFrench | LangItalian | LangGerman | LangSpanish | LangKorean, - - RegJP = RegionBase << Region3DSIndex.Japan, - RegNA = RegionBase << Region3DSIndex.NorthAmerica, - RegEU = RegionBase << Region3DSIndex.Europe, - RegZH = RegionBase << Region3DSIndex.China, - RegKO = RegionBase << Region3DSIndex.Korea, - RegTW = RegionBase << Region3DSIndex.Taiwan, - - RegionRestrict = RegJP | RegNA | RegEU | RegZH | RegKO | RegTW, - - OTReplacedOnTrade = RegTW << 1, + for (int i = (int)LanguageID.Japanese; i <= (int)LanguageID.Korean; i++) + { + if (value.HasFlagFast((MysteryGiftRestriction)(1 << i))) + return i; + } + return -1; } - public static class MysteryGiftRestrictionExtensions + public static int GetSuggestedRegion(this MysteryGiftRestriction value) { - public static bool HasFlagFast(this MysteryGiftRestriction value, MysteryGiftRestriction flag) + for (int i = (int)Region3DSIndex.Japan; i <= (int)Region3DSIndex.Taiwan; i++) { - return (value & flag) != 0; - } - - /// - /// Checks the flags to pick out a language that can receive the gift. - /// - /// Flag value - /// Language ID; -1 if none - public static int GetSuggestedLanguage(this MysteryGiftRestriction value) - { - for (int i = (int)LanguageID.Japanese; i <= (int)LanguageID.Korean; i++) - { - if (value.HasFlagFast((MysteryGiftRestriction)(1 << i))) - return i; - } - return -1; - } - - public static int GetSuggestedRegion(this MysteryGiftRestriction value) - { - for (int i = (int)Region3DSIndex.Japan; i <= (int)Region3DSIndex.Taiwan; i++) - { - if (value.HasFlagFast((MysteryGiftRestriction)((int)MysteryGiftRestriction.RegionBase << i))) - return i; - } - return -1; + if (value.HasFlagFast((MysteryGiftRestriction)((int)MysteryGiftRestriction.RegionBase << i))) + return i; } + return -1; } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/MysteryGiftVerifier.cs b/PKHeX.Core/Legality/Encounters/Verifiers/MysteryGiftVerifier.cs index cb5fe5c5f..8e60c9a0e 100644 --- a/PKHeX.Core/Legality/Encounters/Verifiers/MysteryGiftVerifier.cs +++ b/PKHeX.Core/Legality/Encounters/Verifiers/MysteryGiftVerifier.cs @@ -3,107 +3,106 @@ using static PKHeX.Core.LegalityCheckStrings; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class MysteryGiftVerifier { - public static class MysteryGiftVerifier + private static readonly Dictionary?[] RestrictionSet = Get(); + + private static Dictionary?[] Get() { - private static readonly Dictionary?[] RestrictionSet = Get(); + var s = new Dictionary?[PKX.Generation + 1]; + for (int i = 3; i < s.Length; i++) + s[i] = GetRestriction(i); + return s; + } - private static Dictionary?[] Get() + private static string RestrictionSetName(int generation) => $"mgrestrict{generation}.pkl"; + + private static Dictionary GetRestriction(int generation) + { + var resource = RestrictionSetName(generation); + var data = Util.GetBinaryResource(resource).AsSpan(); + var dict = new Dictionary(); + for (int i = 0; i < data.Length; i += 8) { - var s = new Dictionary?[PKX.Generation + 1]; - for (int i = 3; i < s.Length; i++) - s[i] = GetRestriction(i); - return s; + var entry = data[i..]; + int hash = ReadInt32LittleEndian(entry); + var restrict = ReadInt32LittleEndian(entry[4..]); + dict.Add(hash, (MysteryGiftRestriction)restrict); } + return dict; + } - private static string RestrictionSetName(int generation) => $"mgrestrict{generation}.pkl"; - - private static Dictionary GetRestriction(int generation) - { - var resource = RestrictionSetName(generation); - var data = Util.GetBinaryResource(resource).AsSpan(); - var dict = new Dictionary(); - for (int i = 0; i < data.Length; i += 8) - { - var entry = data[i..]; - int hash = ReadInt32LittleEndian(entry); - var restrict = ReadInt32LittleEndian(entry[4..]); - dict.Add(hash, (MysteryGiftRestriction)restrict); - } - return dict; - } - - public static CheckResult VerifyGift(PKM pk, MysteryGift g) - { - bool restricted = TryGetRestriction(g, out var value); - if (!restricted) - return new CheckResult(CheckIdentifier.GameOrigin); - - var ver = (int)value >> 16; - if (ver != 0 && !CanVersionReceiveGift(g.Generation, ver, pk.Version)) - return new CheckResult(Severity.Invalid, LEncGiftVersionNotDistributed, CheckIdentifier.GameOrigin); - - var lang = value & MysteryGiftRestriction.LangRestrict; - if (lang != 0 && !lang.HasFlagFast((MysteryGiftRestriction) (1 << pk.Language))) - return new CheckResult(Severity.Invalid, string.Format(LOTLanguage, lang.GetSuggestedLanguage(), pk.Language), CheckIdentifier.GameOrigin); - - if (pk is IRegionOrigin tr) - { - var region = value & MysteryGiftRestriction.RegionRestrict; - if (region != 0 && !region.HasFlagFast((MysteryGiftRestriction)((int)MysteryGiftRestriction.RegionBase << tr.ConsoleRegion))) - return new CheckResult(Severity.Invalid, LGeoHardwareRange, CheckIdentifier.GameOrigin); - } - + public static CheckResult VerifyGift(PKM pk, MysteryGift g) + { + bool restricted = TryGetRestriction(g, out var value); + if (!restricted) return new CheckResult(CheckIdentifier.GameOrigin); + + var ver = (int)value >> 16; + if (ver != 0 && !CanVersionReceiveGift(g.Generation, ver, pk.Version)) + return new CheckResult(Severity.Invalid, LEncGiftVersionNotDistributed, CheckIdentifier.GameOrigin); + + var lang = value & MysteryGiftRestriction.LangRestrict; + if (lang != 0 && !lang.HasFlagFast((MysteryGiftRestriction) (1 << pk.Language))) + return new CheckResult(Severity.Invalid, string.Format(LOTLanguage, lang.GetSuggestedLanguage(), pk.Language), CheckIdentifier.GameOrigin); + + if (pk is IRegionOrigin tr) + { + var region = value & MysteryGiftRestriction.RegionRestrict; + if (region != 0 && !region.HasFlagFast((MysteryGiftRestriction)((int)MysteryGiftRestriction.RegionBase << tr.ConsoleRegion))) + return new CheckResult(Severity.Invalid, LGeoHardwareRange, CheckIdentifier.GameOrigin); } - private static bool TryGetRestriction(MysteryGift g, out MysteryGiftRestriction val) - { - var restrict = RestrictionSet[g.Generation]; - if (restrict != null) - return restrict.TryGetValue(g.GetHashCode(), out val); - val = MysteryGiftRestriction.None; + return new CheckResult(CheckIdentifier.GameOrigin); + } + + private static bool TryGetRestriction(MysteryGift g, out MysteryGiftRestriction val) + { + var restrict = RestrictionSet[g.Generation]; + if (restrict != null) + return restrict.TryGetValue(g.GetHashCode(), out val); + val = MysteryGiftRestriction.None; + return false; + } + + public static bool IsValidChangedOTName(PKM pk, MysteryGift g) + { + bool restricted = TryGetRestriction(g, out var val); + if (!restricted) + return false; // no data + if (!val.HasFlagFast(MysteryGiftRestriction.OTReplacedOnTrade)) return false; - } + return CurrentOTMatchesReplaced(g.Generation, pk.OT_Name); + } - public static bool IsValidChangedOTName(PKM pk, MysteryGift g) + private static bool CanVersionReceiveGift(int generation, int version4bit, int version) + { + return generation switch { - bool restricted = TryGetRestriction(g, out var val); - if (!restricted) - return false; // no data - if (!val.HasFlagFast(MysteryGiftRestriction.OTReplacedOnTrade)) - return false; - return CurrentOTMatchesReplaced(g.Generation, pk.OT_Name); - } + _ => false, + }; + } - private static bool CanVersionReceiveGift(int generation, int version4bit, int version) - { - return generation switch - { - _ => false, - }; - } + private static bool CurrentOTMatchesReplaced(int format, string pkOtName) + { + if (format <= 4 && IsMatchName(pkOtName, 4)) + return true; + if (format <= 5 && IsMatchName(pkOtName, 5)) + return true; + if (format <= 6 && IsMatchName(pkOtName, 6)) + return true; + if (format <= 7 && IsMatchName(pkOtName, 7)) + return true; + return false; + } - private static bool CurrentOTMatchesReplaced(int format, string pkOtName) + private static bool IsMatchName(string pkOtName, int generation) + { + return generation switch { - if (format <= 4 && IsMatchName(pkOtName, 4)) - return true; - if (format <= 5 && IsMatchName(pkOtName, 5)) - return true; - if (format <= 6 && IsMatchName(pkOtName, 6)) - return true; - if (format <= 7 && IsMatchName(pkOtName, 7)) - return true; - return false; - } - - private static bool IsMatchName(string pkOtName, int generation) - { - return generation switch - { - _ => false, - }; - } + _ => false, + }; } } diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/VerifyCurrentMoves.cs b/PKHeX.Core/Legality/Encounters/Verifiers/VerifyCurrentMoves.cs index d1dfde8d3..084ae738a 100644 --- a/PKHeX.Core/Legality/Encounters/Verifiers/VerifyCurrentMoves.cs +++ b/PKHeX.Core/Legality/Encounters/Verifiers/VerifyCurrentMoves.cs @@ -8,743 +8,742 @@ using static PKHeX.Core.Severity; using static PKHeX.Core.CheckIdentifier; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic to verify the current . +/// +public static class VerifyCurrentMoves { /// - /// Logic to verify the current . + /// Verifies the current moves of the data based on the provided . /// - public static class VerifyCurrentMoves + /// Data to check + /// Encounter conditions and legality info + /// Validity of the + public static void VerifyMoves(PKM pk, LegalInfo info) { - /// - /// Verifies the current moves of the data based on the provided . - /// - /// Data to check - /// Encounter conditions and legality info - /// Validity of the - public static void VerifyMoves(PKM pkm, LegalInfo info) - { - var parse = info.Moves; - Array.ForEach(parse, p => p.Reset()); - var currentMoves = pkm.Moves; - ParseMovesForEncounters(pkm, parse, info, currentMoves); + var parse = info.Moves; + Array.ForEach(parse, p => p.Reset()); + var currentMoves = pk.Moves; + ParseMovesForEncounters(pk, parse, info, currentMoves); - // Duplicate Moves Check - VerifyNoEmptyDuplicates(currentMoves, parse); - if (currentMoves[0] == 0) // Can't have an empty move slot for the first move. - parse[0].FlagIllegal(LMoveSourceEmpty, CurrentMove); + // Duplicate Moves Check + VerifyNoEmptyDuplicates(currentMoves, parse); + if (currentMoves[0] == 0) // Can't have an empty move slot for the first move. + parse[0].FlagIllegal(LMoveSourceEmpty, CurrentMove); + } + + private static void ParseMovesForEncounters(PKM pk, CheckMoveResult[] parse, LegalInfo info, int[] currentMoves) + { + if (pk.Species == (int)Species.Smeargle) // special handling for Smeargle + { + ParseMovesForSmeargle(pk, parse, currentMoves, info); // Smeargle can have any moves except a few + return; } - private static void ParseMovesForEncounters(PKM pkm, CheckMoveResult[] parse, LegalInfo info, int[] currentMoves) + // gather valid moves for encounter species + info.EncounterMoves = new ValidEncounterMoves(pk, info.EncounterMatch, info.EvoChainsAllGens); + + if (info.Generation >= 6) + ParseMoves3DS(pk, parse, currentMoves, info); + else + ParseMovesPre3DS(pk, parse, currentMoves, info); + } + + private static void ParseMovesForSmeargle(PKM pk, CheckMoveResult[] parse, int[] currentMoves, LegalInfo info) + { + if (!pk.IsEgg) { - if (pkm.Species == (int)Species.Smeargle) // special handling for Smeargle - { - ParseMovesForSmeargle(pkm, parse, currentMoves, info); // Smeargle can have any moves except a few - return; - } + ParseMovesSketch(pk, parse, currentMoves); + return; + } - // gather valid moves for encounter species - info.EncounterMoves = new ValidEncounterMoves(pkm, info.EncounterMatch, info.EvoChainsAllGens); + // can only know sketch as egg + var levelup = new int[info.EvoChainsAllGens.Length][]; + levelup[pk.Format] = new[] { (int)Move.Sketch }; + info.EncounterMoves = new ValidEncounterMoves(levelup); + var source = new MoveParseSource { CurrentMoves = currentMoves }; + ParseMoves(pk, source, info, parse); + } - if (info.Generation >= 6) - ParseMoves3DS(pkm, parse, currentMoves, info); + private static void ParseMovesWasEggPreRelearn(PKM pk, CheckMoveResult[] parse, IReadOnlyList currentMoves, LegalInfo info, EncounterEgg e) + { + // Level up moves could not be inherited if Ditto is parent, + // that means genderless species and male only species (except Nidoran-M and Volbeat; they breed with Nidoran-F and Illumise) could not have level up moves as an egg + var pi = pk.PersonalInfo; + var AllowLevelUp = !pi.Genderless && !(pi.OnlyMale && Breeding.MixedGenderBreeding.Contains(e.Species)); + int BaseLevel = AllowLevelUp ? 100 : e.LevelMin; + var LevelUp = MoveList.GetBaseEggMoves(pk, e.Species, e.Form, e.Version, BaseLevel); + + var TradebackPreevo = pk.Format == 2 && e.Species > 151; + var NonTradebackLvlMoves = TradebackPreevo + ? MoveList.GetExclusivePreEvolutionMoves(pk, e.Species, info.EvoChainsAllGens.Gen2, 2, e.Version).Where(m => m > Legal.MaxMoveID_1).ToArray() + : Array.Empty(); + + var Egg = MoveEgg.GetEggMoves(pk.PersonalInfo, e.Species, e.Form, e.Version, e.Generation); + if (info.Generation < 3 && pk.Format >= 7 && pk.VC1) + Egg = Array.FindAll(Egg, m => m <= Legal.MaxMoveID_1); + + var specialMoves = e.CanHaveVoltTackle ? new[] { (int)Move.VoltTackle } : Array.Empty(); // Volt Tackle for bred Pichu line + + var source = new MoveParseSource + { + CurrentMoves = currentMoves, + SpecialSource = specialMoves, + NonTradeBackLevelUpMoves = NonTradebackLvlMoves, + + EggLevelUpSource = LevelUp, + EggMoveSource = Egg, + }; + ParseMoves(pk, source, info, parse); + } + + private static void ParseMovesSketch(PKM pk, CheckMoveResult[] parse, ReadOnlySpan currentMoves) + { + for (int i = parse.Length - 1; i >= 0; i--) + { + var r = parse[i]; + var move = currentMoves[i]; + if (move == 0) + r.Set(None, pk.Format, Valid, LMoveSourceEmpty, CurrentMove); + else if (Legal.IsValidSketch(move, pk.Format)) + r.Set(Sketch, pk.Format, Valid, L_AValid, CurrentMove); else - ParseMovesPre3DS(pkm, parse, currentMoves, info); + r.Set(Unknown, pk.Format, Invalid, LMoveSourceInvalidSketch, CurrentMove); + } + } + + private static void ParseMoves3DS(PKM pk, CheckMoveResult[] parse, IReadOnlyList currentMoves, LegalInfo info) + { + info.EncounterMoves.Relearn = info.Generation >= 6 ? pk.RelearnMoves : Array.Empty(); + if (info.EncounterMatch is IMoveset) + ParseMovesSpecialMoveset(pk, currentMoves, info, parse); + else + ParseMovesRelearn(pk, currentMoves, info, parse); + } + + private static void ParseMovesPre3DS(PKM pk, CheckMoveResult[] parse, int[] currentMoves, LegalInfo info) + { + if (info.EncounterMatch is EncounterEgg e) + { + if (pk.IsEgg) + VerifyRelearnMoves.VerifyEggMoveset(e, parse, currentMoves, CurrentMove); + else + ParseMovesWasEggPreRelearn(pk, parse, currentMoves, info, e); + return; } - private static void ParseMovesForSmeargle(PKM pkm, CheckMoveResult[] parse, int[] currentMoves, LegalInfo info) + // Not all games have a re-learner. Initial moves may not fill out all 4 slots. + int gen = info.EncounterMatch.Generation; + if (gen == 1 || (gen == 2 && !AllowGen2MoveReminder(pk))) + ParseMovesGenGB(pk, currentMoves, info, parse); + + ParseMovesSpecialMoveset(pk, currentMoves, info, parse); + } + + private static void ParseMovesGenGB(PKM pk, IReadOnlyList currentMoves, LegalInfo info, CheckMoveResult[] parse) + { + var enc = info.EncounterMatch; + var evos = info.EvoChainsAllGens[enc.Generation]; + var level = evos.Length > 0 ? evos[^1].LevelMin : enc.LevelMin; + var InitialMoves = Array.Empty(); + var games = enc.Generation == 1 ? GBRestrictions.GetGen1Versions(enc) : GBRestrictions.GetGen2Versions(enc, pk.Korean); + foreach (var ver in games) { - if (!pkm.IsEgg) - { - ParseMovesSketch(pkm, parse, currentMoves); - return; - } - - // can only know sketch as egg - var levelup = new int[info.EvoChainsAllGens.Length][]; - levelup[pkm.Format] = new[] { (int)Move.Sketch }; - info.EncounterMoves = new ValidEncounterMoves(levelup); - var source = new MoveParseSource { CurrentMoves = currentMoves }; - ParseMoves(pkm, source, info, parse); - } - - private static void ParseMovesWasEggPreRelearn(PKM pkm, CheckMoveResult[] parse, IReadOnlyList currentMoves, LegalInfo info, EncounterEgg e) - { - // Level up moves could not be inherited if Ditto is parent, - // that means genderless species and male only species (except Nidoran-M and Volbeat; they breed with Nidoran-F and Illumise) could not have level up moves as an egg - var pi = pkm.PersonalInfo; - var AllowLevelUp = !pi.Genderless && !(pi.OnlyMale && Breeding.MixedGenderBreeding.Contains(e.Species)); - int BaseLevel = AllowLevelUp ? 100 : e.LevelMin; - var LevelUp = MoveList.GetBaseEggMoves(pkm, e.Species, e.Form, e.Version, BaseLevel); - - var TradebackPreevo = pkm.Format == 2 && e.Species > 151; - var NonTradebackLvlMoves = TradebackPreevo - ? MoveList.GetExclusivePreEvolutionMoves(pkm, e.Species, info.EvoChainsAllGens.Gen2, 2, e.Version).Where(m => m > Legal.MaxMoveID_1).ToArray() - : Array.Empty(); - - var Egg = MoveEgg.GetEggMoves(pkm.PersonalInfo, e.Species, e.Form, e.Version, e.Generation); - if (info.Generation < 3 && pkm.Format >= 7 && pkm.VC1) - Egg = Array.FindAll(Egg, m => m <= Legal.MaxMoveID_1); - - var specialMoves = e.CanHaveVoltTackle ? new[] { (int)Move.VoltTackle } : Array.Empty(); // Volt Tackle for bred Pichu line + var VerInitialMoves = enc is IMoveset {Moves.Count: not 0 } x ? (int[])x.Moves : MoveLevelUp.GetEncounterMoves(enc.Species, 0, level, ver); + if (VerInitialMoves.SequenceEqual(InitialMoves)) + return; // Already checked this combination, and it wasn't valid. Don't bother repeating. var source = new MoveParseSource { CurrentMoves = currentMoves, - SpecialSource = specialMoves, - NonTradeBackLevelUpMoves = NonTradebackLvlMoves, - - EggLevelUpSource = LevelUp, - EggMoveSource = Egg, + Base = VerInitialMoves, }; - ParseMoves(pkm, source, info, parse); - } + ParseMoves(pk, source, info, parse); - private static void ParseMovesSketch(PKM pkm, CheckMoveResult[] parse, ReadOnlySpan currentMoves) - { - for (int i = parse.Length - 1; i >= 0; i--) + // Must have a minimum count of moves, depending on the tradeback state. + if (pk is PK1 pk1) { - var r = parse[i]; - var move = currentMoves[i]; - if (move == 0) - r.Set(None, pkm.Format, Valid, LMoveSourceEmpty, CurrentMove); - else if (Legal.IsValidSketch(move, pkm.Format)) - r.Set(Sketch, pkm.Format, Valid, L_AValid, CurrentMove); - else - r.Set(Unknown, pkm.Format, Invalid, LMoveSourceInvalidSketch, CurrentMove); - } - } - - private static void ParseMoves3DS(PKM pkm, CheckMoveResult[] parse, IReadOnlyList currentMoves, LegalInfo info) - { - info.EncounterMoves.Relearn = info.Generation >= 6 ? pkm.RelearnMoves : Array.Empty(); - if (info.EncounterMatch is IMoveset) - ParseMovesSpecialMoveset(pkm, currentMoves, info, parse); - else - ParseMovesRelearn(pkm, currentMoves, info, parse); - } - - private static void ParseMovesPre3DS(PKM pkm, CheckMoveResult[] parse, int[] currentMoves, LegalInfo info) - { - if (info.EncounterMatch is EncounterEgg e) - { - if (pkm.IsEgg) - VerifyRelearnMoves.VerifyEggMoveset(e, parse, currentMoves, CurrentMove); - else - ParseMovesWasEggPreRelearn(pkm, parse, currentMoves, info, e); - return; - } - - // Not all games have a re-learner. Initial moves may not fill out all 4 slots. - int gen = info.EncounterMatch.Generation; - if (gen == 1 || (gen == 2 && !AllowGen2MoveReminder(pkm))) - ParseMovesGenGB(pkm, currentMoves, info, parse); - - ParseMovesSpecialMoveset(pkm, currentMoves, info, parse); - } - - private static void ParseMovesGenGB(PKM pkm, IReadOnlyList currentMoves, LegalInfo info, CheckMoveResult[] parse) - { - var enc = info.EncounterMatch; - var evos = info.EvoChainsAllGens[enc.Generation]; - var level = evos.Length > 0 ? evos[^1].LevelMin : enc.LevelMin; - var InitialMoves = Array.Empty(); - var games = enc.Generation == 1 ? GBRestrictions.GetGen1Versions(enc) : GBRestrictions.GetGen2Versions(enc, pkm.Korean); - foreach (var ver in games) - { - var VerInitialMoves = enc is IMoveset {Moves.Count: not 0 } x ? (int[])x.Moves : MoveLevelUp.GetEncounterMoves(enc.Species, 0, level, ver); - if (VerInitialMoves.SequenceEqual(InitialMoves)) - return; // Already checked this combination, and it wasn't valid. Don't bother repeating. - - var source = new MoveParseSource - { - CurrentMoves = currentMoves, - Base = VerInitialMoves, - }; - ParseMoves(pkm, source, info, parse); - - // Must have a minimum count of moves, depending on the tradeback state. - if (pkm is PK1 pk1) - { - int count = GBRestrictions.GetRequiredMoveCount(pk1, source.CurrentMoves, info, source.Base); - if (count == 1) - return; - - // Reverse for loop and break instead of 0..count continue -- early-breaks for the vast majority of cases. - // We already flag for empty interstitial moveslots. - for (int m = count - 1; m >= 0; m--) - { - var move = source.CurrentMoves[m]; - if (move != 0) - break; - - // There are ways to skip level up moves by leveling up more than once. - // https://bulbapedia.bulbagarden.net/wiki/List_of_glitches_(Generation_I)#Level-up_learnset_skipping - // Evolution canceling also leads to incorrect assumptions in the above used method, so just indicate them as fishy in that case. - // Not leveled up? Not possible to be missing the move slot. - var severity = enc.LevelMin == pkm.CurrentLevel ? Invalid : Fishy; - parse[m].Set(None, pkm.Format, severity, LMoveSourceEmpty, CurrentMove); - } - } - if (Array.TrueForAll(parse, z => z.Valid)) + int count = GBRestrictions.GetRequiredMoveCount(pk1, source.CurrentMoves, info, source.Base); + if (count == 1) return; - InitialMoves = VerInitialMoves; + + // Reverse for loop and break instead of 0..count continue -- early-breaks for the vast majority of cases. + // We already flag for empty interstitial moveslots. + for (int m = count - 1; m >= 0; m--) + { + var move = source.CurrentMoves[m]; + if (move != 0) + break; + + // There are ways to skip level up moves by leveling up more than once. + // https://bulbapedia.bulbagarden.net/wiki/List_of_glitches_(Generation_I)#Level-up_learnset_skipping + // Evolution canceling also leads to incorrect assumptions in the above used method, so just indicate them as fishy in that case. + // Not leveled up? Not possible to be missing the move slot. + var severity = enc.LevelMin == pk.CurrentLevel ? Invalid : Fishy; + parse[m].Set(None, pk.Format, severity, LMoveSourceEmpty, CurrentMove); + } } - } - - private static void ParseMovesSpecialMoveset(PKM pkm, IReadOnlyList currentMoves, LegalInfo info, CheckMoveResult[] parse) - { - var source = new MoveParseSource - { - CurrentMoves = currentMoves, - SpecialSource = GetSpecialMoves(info.EncounterMatch), - }; - ParseMoves(pkm, source, info, parse); - } - - private static IReadOnlyList GetSpecialMoves(IEncounterTemplate enc) - { - if (enc is IMoveset mg) - return mg.Moves; - return Array.Empty(); - } - - private static void ParseMovesRelearn(PKM pkm, IReadOnlyList currentMoves, LegalInfo info, CheckMoveResult[] parse) - { - var source = new MoveParseSource - { - CurrentMoves = currentMoves, - SpecialSource = GetSpecialMoves(info.EncounterMatch), - }; - - if (info.EncounterMatch is EncounterEgg e) - source.EggMoveSource = MoveEgg.GetEggMoves(pkm.PersonalInfo, e.Species, e.Form, e.Version, e.Generation); - - ParseMoves(pkm, source, info, parse); - var relearn = pkm.RelearnMoves; - for (int i = parse.Length - 1; i >= 0; i--) - { - var r = parse[i]; - if (!r.IsRelearn && !pkm.IsEgg) - continue; - if (relearn.Contains(currentMoves[i])) - continue; - - r.FlagIllegal(string.Format(LMoveRelearnFMiss_0, r.Comment)); - } - } - - private static void ParseMoves(PKM pkm, MoveParseSource source, LegalInfo info, CheckMoveResult[] parse) - { - // Special considerations! - const int NoMinGeneration = 0; - int minGeneration = NoMinGeneration; - if (pkm.IsOriginalMovesetDeleted()) - { - var (_, resetGame) = pkm.IsMovesetRestricted(); - minGeneration = resetGame.GetGeneration(); - source.ResetSources(); - } - - // Check empty moves and relearn moves before generation specific moves - for (int m = parse.Length - 1; m >= 0; m--) - { - var move = source.CurrentMoves[m]; - var r = parse[m]; - if (move == 0) - r.Set(None, pkm.Format, Valid, LMoveSourceEmpty, CurrentMove); - else if (minGeneration == NoMinGeneration && info.EncounterMoves.Relearn.Contains(move)) - r.Set(Relearn, info.Generation, Valid, LMoveSourceRelearn, CurrentMove); - } - - if (Array.TrueForAll(parse, z => z.IsParsed)) + if (Array.TrueForAll(parse, z => z.Valid)) return; + InitialMoves = VerInitialMoves; + } + } - // Encapsulate arguments to simplify method calls - var moveInfo = new LearnInfo(pkm, source); - // Check moves going backwards, marking the move valid in the most current generation when it can be learned - int[] generations = GenerationTraversal.GetVisitedGenerationOrder(pkm, info.EncounterOriginal.Generation); - if (pkm.Format <= 2) - generations = Array.FindAll(generations, z => z < info.EncounterMoves.LevelUpMoves.Length); - if (minGeneration != NoMinGeneration) - generations = Array.FindAll(generations, z => z >= minGeneration); + private static void ParseMovesSpecialMoveset(PKM pk, IReadOnlyList currentMoves, LegalInfo info, CheckMoveResult[] parse) + { + var source = new MoveParseSource + { + CurrentMoves = currentMoves, + SpecialSource = GetSpecialMoves(info.EncounterMatch), + }; + ParseMoves(pk, source, info, parse); + } - if (generations.Length != 0) + private static IReadOnlyList GetSpecialMoves(IEncounterTemplate enc) + { + if (enc is IMoveset mg) + return mg.Moves; + return Array.Empty(); + } + + private static void ParseMovesRelearn(PKM pk, IReadOnlyList currentMoves, LegalInfo info, CheckMoveResult[] parse) + { + var source = new MoveParseSource + { + CurrentMoves = currentMoves, + SpecialSource = GetSpecialMoves(info.EncounterMatch), + }; + + if (info.EncounterMatch is EncounterEgg e) + source.EggMoveSource = MoveEgg.GetEggMoves(pk.PersonalInfo, e.Species, e.Form, e.Version, e.Generation); + + ParseMoves(pk, source, info, parse); + var relearn = pk.RelearnMoves; + for (int i = parse.Length - 1; i >= 0; i--) + { + var r = parse[i]; + if (!r.IsRelearn && !pk.IsEgg) + continue; + if (relearn.Contains(currentMoves[i])) + continue; + + r.FlagIllegal(string.Format(LMoveRelearnFMiss_0, r.Comment)); + } + } + + private static void ParseMoves(PKM pk, MoveParseSource source, LegalInfo info, CheckMoveResult[] parse) + { + // Special considerations! + const int NoMinGeneration = 0; + int minGeneration = NoMinGeneration; + if (pk.IsOriginalMovesetDeleted()) + { + var (_, resetGame) = pk.IsMovesetRestricted(); + minGeneration = resetGame.GetGeneration(); + source.ResetSources(); + } + + // Check empty moves and relearn moves before generation specific moves + for (int m = parse.Length - 1; m >= 0; m--) + { + var move = source.CurrentMoves[m]; + var r = parse[m]; + if (move == 0) + r.Set(None, pk.Format, Valid, LMoveSourceEmpty, CurrentMove); + else if (minGeneration == NoMinGeneration && info.EncounterMoves.Relearn.Contains(move)) + r.Set(Relearn, info.Generation, Valid, LMoveSourceRelearn, CurrentMove); + } + + if (Array.TrueForAll(parse, z => z.IsParsed)) + return; + + // Encapsulate arguments to simplify method calls + var moveInfo = new LearnInfo(pk, source); + // Check moves going backwards, marking the move valid in the most current generation when it can be learned + int[] generations = GenerationTraversal.GetVisitedGenerationOrder(pk, info.EncounterOriginal.Generation); + if (pk.Format <= 2) + generations = Array.FindAll(generations, z => z < info.EncounterMoves.LevelUpMoves.Length); + if (minGeneration != NoMinGeneration) + generations = Array.FindAll(generations, z => z >= minGeneration); + + if (generations.Length != 0) + { + int lastgen = generations[^1]; + foreach (var gen in generations) { - int lastgen = generations[^1]; - foreach (var gen in generations) - { - ParseMovesByGeneration(pkm, parse, gen, info, moveInfo, lastgen); - if (Array.TrueForAll(parse, z => z.IsParsed)) - return; - } - } - - if (pkm.Species == (int)Species.Shedinja && info.Generation <= 4) - ParseShedinjaEvolveMoves(pkm, parse, source.CurrentMoves, info.EvoChainsAllGens); - - foreach (var r in parse) - { - if (!r.IsParsed) - r.Set(Unknown, info.Generation, Invalid, LMoveSourceInvalid, CurrentMove); + ParseMovesByGeneration(pk, parse, gen, info, moveInfo, lastgen); + if (Array.TrueForAll(parse, z => z.IsParsed)) + return; } } - private static void ParseMovesByGeneration(PKM pkm, CheckMoveResult[] parse, int gen, LegalInfo info, LearnInfo learnInfo, int last) + if (pk.Species == (int)Species.Shedinja && info.Generation <= 4) + ParseShedinjaEvolveMoves(pk, parse, source.CurrentMoves, info.EvoChainsAllGens); + + foreach (var r in parse) { - Span HMLearned = stackalloc bool[parse.Length]; - bool KnowDefogWhirlpool = GetHMCompatibility(pkm, parse, gen, learnInfo.Source.CurrentMoves, HMLearned); - ParseMovesByGeneration(pkm, parse, gen, info, learnInfo); + if (!r.IsParsed) + r.Set(Unknown, info.Generation, Invalid, LMoveSourceInvalid, CurrentMove); + } + } - if (gen == last) - ParseMovesByGenerationLast(parse, learnInfo, info.EncounterMatch); + private static void ParseMovesByGeneration(PKM pk, CheckMoveResult[] parse, int gen, LegalInfo info, LearnInfo learnInfo, int last) + { + Span HMLearned = stackalloc bool[parse.Length]; + bool KnowDefogWhirlpool = GetHMCompatibility(pk, parse, gen, learnInfo.Source.CurrentMoves, HMLearned); + ParseMovesByGeneration(pk, parse, gen, info, learnInfo); - switch (gen) + if (gen == last) + ParseMovesByGenerationLast(parse, learnInfo, info.EncounterMatch); + + switch (gen) + { + case 1 or 2: + ParseMovesByGeneration12(pk, parse, learnInfo.Source.CurrentMoves, gen, info, learnInfo); + break; + + case 3 or 4: + if (pk.Format > gen) + FlagIncompatibleTransferHMs45(parse, learnInfo.Source.CurrentMoves, gen, HMLearned, KnowDefogWhirlpool); + break; + } + } + + private static void ParseMovesByGeneration(PKM pk, CheckMoveResult[] parse, int gen, LegalInfo info, LearnInfo learnInfo) + { + var moves = learnInfo.Source.CurrentMoves; + bool native = gen == pk.Format; + for (int m = parse.Length - 1; m >= 0; m--) + { + var r = parse[m]; + if (IsCheckValid(r)) // already validated with another generation + continue; + int move = moves[m]; + if (move == 0) + continue; + + if (gen <= 2) { - case 1 or 2: - ParseMovesByGeneration12(pkm, parse, learnInfo.Source.CurrentMoves, gen, info, learnInfo); - break; + if (gen == 2 && !native && move > Legal.MaxMoveID_1 && pk.VC1) + { + r.Set(Unknown, gen, Invalid, LMoveSourceInvalid, CurrentMove); + continue; + } + if (gen == 2 && learnInfo.Source.EggMoveSource.Contains(move)) + r.Set(EggMove, gen, Valid, LMoveSourceEgg, CurrentMove); + else if (learnInfo.Source.Base.Contains(move)) + r.Set(Initial, gen, Valid, native ? LMoveSourceDefault : string.Format(LMoveFDefault_0, gen), CurrentMove); + } + if (info.EncounterMoves.LevelUpMoves[gen].Contains(move)) + r.Set(LevelUp, gen, Valid, native ? LMoveSourceLevelUp : string.Format(LMoveFLevelUp_0, gen), CurrentMove); + else if (info.EncounterMoves.TMHMMoves[gen].Contains(move)) + r.Set(TMHM, gen, Valid, native ? LMoveSourceTMHM : string.Format(LMoveFTMHM_0, gen), CurrentMove); + else if (info.EncounterMoves.TutorMoves[gen].Contains(move)) + r.Set(Tutor, gen, Valid, native ? LMoveSourceTutor : string.Format(LMoveFTutor_0, gen), CurrentMove); + else if (gen == info.Generation && learnInfo.Source.SpecialSource.Contains(move)) + r.Set(Special, gen, Valid, LMoveSourceSpecial, CurrentMove); + else if (gen >= 8 && MoveEgg.GetIsSharedEggMove(pk, gen, move)) + r.Set(Shared, gen, Valid, native ? LMoveSourceShared : string.Format(LMoveSourceSharedF, gen), CurrentMove); - case 3 or 4: - if (pkm.Format > gen) - FlagIncompatibleTransferHMs45(parse, learnInfo.Source.CurrentMoves, gen, HMLearned, KnowDefogWhirlpool); - break; + if (gen >= 3 || !IsCheckValid(r)) + continue; + + // Gen1/Gen2 only below + if (gen == 2 && learnInfo.Source.NonTradeBackLevelUpMoves.Contains(m)) + { + learnInfo.Gen2PreevoMoves.Add(m); + } + else if (gen == 1) + { + learnInfo.Gen1Moves.Add(m); + if (learnInfo.Gen2PreevoMoves.Count != 0) + learnInfo.MixedGen12NonTradeback = true; } } + } - private static void ParseMovesByGeneration(PKM pkm, CheckMoveResult[] parse, int gen, LegalInfo info, LearnInfo learnInfo) + private static void ParseMovesByGeneration12(PKM pk, CheckMoveResult[] parse, IReadOnlyList currentMoves, int gen, LegalInfo info, LearnInfo learnInfo) + { + // Mark the gen 1 exclusive moves as illegal because the pokemon also have Non tradeback egg moves. + if (learnInfo.MixedGen12NonTradeback) { - var moves = learnInfo.Source.CurrentMoves; - bool native = gen == pkm.Format; - for (int m = parse.Length - 1; m >= 0; m--) - { - var r = parse[m]; - if (IsCheckValid(r)) // already validated with another generation - continue; - int move = moves[m]; - if (move == 0) - continue; + foreach (int m in learnInfo.Gen1Moves) + parse[m].FlagIllegal(LG1MoveExclusive, CurrentMove); - if (gen <= 2) - { - if (gen == 2 && !native && move > Legal.MaxMoveID_1 && pkm.VC1) - { - r.Set(Unknown, gen, Invalid, LMoveSourceInvalid, CurrentMove); - continue; - } - if (gen == 2 && learnInfo.Source.EggMoveSource.Contains(move)) - r.Set(EggMove, gen, Valid, LMoveSourceEgg, CurrentMove); - else if (learnInfo.Source.Base.Contains(move)) - r.Set(Initial, gen, Valid, native ? LMoveSourceDefault : string.Format(LMoveFDefault_0, gen), CurrentMove); - } - if (info.EncounterMoves.LevelUpMoves[gen].Contains(move)) - r.Set(LevelUp, gen, Valid, native ? LMoveSourceLevelUp : string.Format(LMoveFLevelUp_0, gen), CurrentMove); - else if (info.EncounterMoves.TMHMMoves[gen].Contains(move)) - r.Set(TMHM, gen, Valid, native ? LMoveSourceTMHM : string.Format(LMoveFTMHM_0, gen), CurrentMove); - else if (info.EncounterMoves.TutorMoves[gen].Contains(move)) - r.Set(Tutor, gen, Valid, native ? LMoveSourceTutor : string.Format(LMoveFTutor_0, gen), CurrentMove); - else if (gen == info.Generation && learnInfo.Source.SpecialSource.Contains(move)) - r.Set(Special, gen, Valid, LMoveSourceSpecial, CurrentMove); - else if (gen >= 8 && MoveEgg.GetIsSharedEggMove(pkm, gen, move)) - r.Set(Shared, gen, Valid, native ? LMoveSourceShared : string.Format(LMoveSourceSharedF, gen), CurrentMove); - - if (gen >= 3 || !IsCheckValid(r)) - continue; - - // Gen1/Gen2 only below - if (gen == 2 && learnInfo.Source.NonTradeBackLevelUpMoves.Contains(m)) - { - learnInfo.Gen2PreevoMoves.Add(m); - } - else if (gen == 1) - { - learnInfo.Gen1Moves.Add(m); - if (learnInfo.Gen2PreevoMoves.Count != 0) - learnInfo.MixedGen12NonTradeback = true; - } - } + foreach (int m in learnInfo.Gen2PreevoMoves) + parse[m].FlagIllegal(LG1TradebackPreEvoMove, CurrentMove); } - private static void ParseMovesByGeneration12(PKM pkm, CheckMoveResult[] parse, IReadOnlyList currentMoves, int gen, LegalInfo info, LearnInfo learnInfo) + if (gen == 1 && pk.Format == 1 && !AllowGen1Tradeback) { - // Mark the gen 1 exclusive moves as illegal because the pokemon also have Non tradeback egg moves. - if (learnInfo.MixedGen12NonTradeback) - { - foreach (int m in learnInfo.Gen1Moves) - parse[m].FlagIllegal(LG1MoveExclusive, CurrentMove); + ParseRedYellowIncompatibleMoves(pk, parse, currentMoves); + ParseEvolutionsIncompatibleMoves(pk, parse, currentMoves, info.EncounterMoves.TMHMMoves[1]); + } + } - foreach (int m in learnInfo.Gen2PreevoMoves) - parse[m].FlagIllegal(LG1TradebackPreEvoMove, CurrentMove); + private static void ParseMovesByGenerationLast(CheckMoveResult[] parse, LearnInfo learnInfo, IEncounterTemplate enc) + { + int gen = enc.Generation; + ParseEggMovesInherited(parse, gen, learnInfo); + ParseEggMoves(parse, gen, learnInfo); + ParseEggMovesRemaining(parse, learnInfo, enc); + } + + private static void ParseEggMovesInherited(CheckMoveResult[] parse, int gen, LearnInfo learnInfo) + { + var moves = learnInfo.Source.CurrentMoves; + // Check higher-level moves after all the moves but just before egg moves to differentiate it from normal level up moves + // Also check if the base egg moves is a non tradeback move + for (int m = parse.Length - 1; m >= 0; m--) + { + var r = parse[m]; + if (IsCheckValid(r)) // already validated + { + if (gen == 2 && r.Generation != 1) + continue; } - if (gen == 1 && pkm.Format == 1 && !AllowGen1Tradeback) + int move = moves[m]; + if (move == 0) + continue; + if (!learnInfo.Source.EggLevelUpSource.Contains(move)) // Check if contains level-up egg moves from parents + continue; + + if (learnInfo.IsGen2Pkm && learnInfo.Gen1Moves.Count != 0 && move > Legal.MaxMoveID_1) { - ParseRedYellowIncompatibleMoves(pkm, parse, currentMoves); - ParseEvolutionsIncompatibleMoves(pkm, parse, currentMoves, info.EncounterMoves.TMHMMoves[1]); + r.Set(InheritLevelUp, gen, Invalid, LG1MoveTradeback, CurrentMove); + learnInfo.MixedGen12NonTradeback = true; } - } - - private static void ParseMovesByGenerationLast(CheckMoveResult[] parse, LearnInfo learnInfo, IEncounterTemplate enc) - { - int gen = enc.Generation; - ParseEggMovesInherited(parse, gen, learnInfo); - ParseEggMoves(parse, gen, learnInfo); - ParseEggMovesRemaining(parse, learnInfo, enc); - } - - private static void ParseEggMovesInherited(CheckMoveResult[] parse, int gen, LearnInfo learnInfo) - { - var moves = learnInfo.Source.CurrentMoves; - // Check higher-level moves after all the moves but just before egg moves to differentiate it from normal level up moves - // Also check if the base egg moves is a non tradeback move - for (int m = parse.Length - 1; m >= 0; m--) + else { - var r = parse[m]; - if (IsCheckValid(r)) // already validated - { - if (gen == 2 && r.Generation != 1) - continue; - } + r.Set(InheritLevelUp, gen, Valid, LMoveEggLevelUp, CurrentMove); + } + learnInfo.LevelUpEggMoves.Add(m); + if (gen == 2 && learnInfo.Gen1Moves.Contains(m)) + learnInfo.Gen1Moves.Remove(m); + } + } - int move = moves[m]; - if (move == 0) - continue; - if (!learnInfo.Source.EggLevelUpSource.Contains(move)) // Check if contains level-up egg moves from parents - continue; + private static void ParseEggMoves(CheckMoveResult[] parse, int gen, LearnInfo learnInfo) + { + var moves = learnInfo.Source.CurrentMoves; + // Check egg moves after all the generations and all the moves, every move that can't be learned in another source should have preference + // the moves that can only be learned from egg moves should in the future check if the move combinations can be breed in gens 2 to 5 + for (int m = parse.Length - 1; m >= 0; m--) + { + var r = parse[m]; + if (IsCheckValid(r)) + continue; + int move = moves[m]; + if (move == 0) + continue; + bool wasEggMove = learnInfo.Source.EggMoveSource.Contains(move); + if (wasEggMove) + { + // To learn exclusive generation 1 moves the pokemon was tradeback, but it can't be trade to generation 1 + // without removing moves above MaxMoveID_1, egg moves above MaxMoveID_1 and gen 1 moves are incompatible if (learnInfo.IsGen2Pkm && learnInfo.Gen1Moves.Count != 0 && move > Legal.MaxMoveID_1) { - r.Set(InheritLevelUp, gen, Invalid, LG1MoveTradeback, CurrentMove); + r.Set(EggMove, gen, Invalid, LG1MoveTradeback, CurrentMove); learnInfo.MixedGen12NonTradeback = true; } else { - r.Set(InheritLevelUp, gen, Valid, LMoveEggLevelUp, CurrentMove); + r.Set(EggMove, gen, Valid, LMoveSourceEgg, CurrentMove); } - learnInfo.LevelUpEggMoves.Add(m); - if (gen == 2 && learnInfo.Gen1Moves.Contains(m)) - learnInfo.Gen1Moves.Remove(m); + + learnInfo.EggMovesLearned.Add(m); } + if (!learnInfo.Source.EggEventSource.Contains(move)) + continue; + + if (!wasEggMove) + { + if (learnInfo.IsGen2Pkm && learnInfo.Gen1Moves.Count != 0 && move > Legal.MaxMoveID_1) + { + r.Set(SpecialEgg, gen, Invalid, LG1MoveTradeback, CurrentMove); + learnInfo.MixedGen12NonTradeback = true; + } + else + { + r.Set(SpecialEgg, gen, Valid, LMoveSourceEggEvent, CurrentMove); + } + } + learnInfo.EventEggMoves.Add(m); } + } - private static void ParseEggMoves(CheckMoveResult[] parse, int gen, LearnInfo learnInfo) + private static void ParseEggMovesRemaining(CheckMoveResult[] parse, LearnInfo learnInfo, IEncounterTemplate enc) + { + // A pokemon could have normal egg moves and regular egg moves + // Only if all regular egg moves are event egg moves or all event egg moves are regular egg moves + var RegularEggMovesLearned = learnInfo.EggMovesLearned.FindAll(learnInfo.LevelUpEggMoves.Contains); + if (RegularEggMovesLearned.Count != 0 && learnInfo.EventEggMoves.Count != 0) { - var moves = learnInfo.Source.CurrentMoves; - // Check egg moves after all the generations and all the moves, every move that can't be learned in another source should have preference - // the moves that can only be learned from egg moves should in the future check if the move combinations can be breed in gens 2 to 5 - for (int m = parse.Length - 1; m >= 0; m--) + // Moves that are egg moves or event egg moves but not both + var IncompatibleEggMoves = RegularEggMovesLearned.Except(learnInfo.EventEggMoves).Union(learnInfo.EventEggMoves.Except(RegularEggMovesLearned)); + foreach (int m in IncompatibleEggMoves) { - var r = parse[m]; - if (IsCheckValid(r)) - continue; - int move = moves[m]; - if (move == 0) - continue; - - bool wasEggMove = learnInfo.Source.EggMoveSource.Contains(move); - if (wasEggMove) + bool isEvent = learnInfo.EventEggMoves.Contains(m); + if (isEvent) { - // To learn exclusive generation 1 moves the pokemon was tradeback, but it can't be trade to generation 1 - // without removing moves above MaxMoveID_1, egg moves above MaxMoveID_1 and gen 1 moves are incompatible - if (learnInfo.IsGen2Pkm && learnInfo.Gen1Moves.Count != 0 && move > Legal.MaxMoveID_1) - { - r.Set(EggMove, gen, Invalid, LG1MoveTradeback, CurrentMove); - learnInfo.MixedGen12NonTradeback = true; - } - else - { - r.Set(EggMove, gen, Valid, LMoveSourceEgg, CurrentMove); - } - - learnInfo.EggMovesLearned.Add(m); + if (!learnInfo.EggMovesLearned.Contains(m)) + parse[m].FlagIllegal(LMoveEggIncompatibleEvent, CurrentMove); } - if (!learnInfo.Source.EggEventSource.Contains(move)) - continue; - - if (!wasEggMove) - { - if (learnInfo.IsGen2Pkm && learnInfo.Gen1Moves.Count != 0 && move > Legal.MaxMoveID_1) - { - r.Set(SpecialEgg, gen, Invalid, LG1MoveTradeback, CurrentMove); - learnInfo.MixedGen12NonTradeback = true; - } - else - { - r.Set(SpecialEgg, gen, Valid, LMoveSourceEggEvent, CurrentMove); - } - } - learnInfo.EventEggMoves.Add(m); - } - } - - private static void ParseEggMovesRemaining(CheckMoveResult[] parse, LearnInfo learnInfo, IEncounterTemplate enc) - { - // A pokemon could have normal egg moves and regular egg moves - // Only if all regular egg moves are event egg moves or all event egg moves are regular egg moves - var RegularEggMovesLearned = learnInfo.EggMovesLearned.FindAll(learnInfo.LevelUpEggMoves.Contains); - if (RegularEggMovesLearned.Count != 0 && learnInfo.EventEggMoves.Count != 0) - { - // Moves that are egg moves or event egg moves but not both - var IncompatibleEggMoves = RegularEggMovesLearned.Except(learnInfo.EventEggMoves).Union(learnInfo.EventEggMoves.Except(RegularEggMovesLearned)); - foreach (int m in IncompatibleEggMoves) - { - bool isEvent = learnInfo.EventEggMoves.Contains(m); - if (isEvent) - { - if (!learnInfo.EggMovesLearned.Contains(m)) - parse[m].FlagIllegal(LMoveEggIncompatibleEvent, CurrentMove); - } - else - { - if (learnInfo.EggMovesLearned.Contains(m)) - parse[m].FlagIllegal(LMoveEggIncompatible, CurrentMove); - else if (learnInfo.LevelUpEggMoves.Contains(m)) - parse[m].FlagIllegal(LMoveEventEggLevelUp, CurrentMove); - } - } - } - else if (enc is not EncounterEgg) - { - // Event eggs cannot inherit moves from parents; they are not bred. - var gift = enc is EncounterStatic {Gift: true}; // otherwise, EncounterInvalid - foreach (int m in RegularEggMovesLearned) + else { if (learnInfo.EggMovesLearned.Contains(m)) - parse[m].FlagIllegal(gift ? LMoveEggMoveGift : LMoveEggInvalidEvent, CurrentMove); + parse[m].FlagIllegal(LMoveEggIncompatible, CurrentMove); else if (learnInfo.LevelUpEggMoves.Contains(m)) - parse[m].FlagIllegal(gift ? LMoveEggInvalidEventLevelUpGift : LMoveEggInvalidEventLevelUp, CurrentMove); + parse[m].FlagIllegal(LMoveEventEggLevelUp, CurrentMove); } } } - - private static void ParseRedYellowIncompatibleMoves(PKM pkm, CheckMoveResult[] parse, IReadOnlyList currentMoves) + else if (enc is not EncounterEgg) { - var incompatible = GetIncompatibleRBYMoves(pkm, currentMoves); - if (incompatible.Count == 0) - return; - for (int m = parse.Length - 1; m >= 0; m--) + // Event eggs cannot inherit moves from parents; they are not bred. + var gift = enc is EncounterStatic {Gift: true}; // otherwise, EncounterInvalid + foreach (int m in RegularEggMovesLearned) { - if (incompatible.Contains(currentMoves[m])) - parse[m].FlagIllegal(LG1MoveLearnSameLevel, CurrentMove); - } - } - - private static IList GetIncompatibleRBYMoves(PKM pkm, IReadOnlyList currentMoves) - { - // Check moves that are learned at the same level in Red/Blue and Yellow, these are illegal because there is no Move Reminder in Gen1. - // There are only two incompatibilities for Gen1; there are no illegal combination in Gen2. - - switch (pkm.Species) - { - // Vaporeon in Yellow learns Mist and Haze at level 42, Mist can only be learned if it leveled up in the daycare - // Vaporeon in Red/Blue learns Acid Armor at level 42 and level 47 in Yellow - case (int)Species.Vaporeon when pkm.CurrentLevel < 47 && currentMoves.Contains((int)Move.AcidArmor): - { - var incompatible = new List(3); - if (currentMoves.Contains((int)Move.Mist)) incompatible.Add((int)Move.Mist); - if (currentMoves.Contains((int)Move.Haze)) incompatible.Add((int)Move.Haze); - if (incompatible.Count != 0) - incompatible.Add((int)Move.AcidArmor); - else - return Array.Empty(); - return incompatible; - } - - // Flareon in Yellow learns Smog at level 42 - // Flareon in Red Blue learns Leer at level 42 and level 47 in Yellow - case (int)Species.Flareon when pkm.CurrentLevel < 47 && currentMoves.Contains((int)Move.Leer) && currentMoves.Contains((int)Move.Smog): - return new[] { (int)Move.Leer, (int)Move.Smog }; - - default: return Array.Empty(); - } - } - - private static void ParseEvolutionsIncompatibleMoves(PKM pkm, CheckMoveResult[] parse, IReadOnlyList moves, IReadOnlyList tmhm) - { - GBRestrictions.GetIncompatibleEvolutionMoves(pkm, moves, tmhm, - out var prevSpeciesID, - out var incompatPrev, - out var incompatCurr); - - if (prevSpeciesID == 0) - return; - - var prev = SpeciesStrings[prevSpeciesID]; - var curr = SpeciesStrings[pkm.Species]; - for (int m = parse.Length - 1; m >= 0; m--) - { - if (incompatCurr.Contains(moves[m])) - parse[m].FlagIllegal(string.Format(LMoveEvoFLower, curr, prev), CurrentMove); - if (incompatPrev.Contains(moves[m])) - parse[m].FlagIllegal(string.Format(LMoveEvoFHigher, curr, prev), CurrentMove); - } - } - - private static void ParseShedinjaEvolveMoves(PKM pkm, CheckMoveResult[] parse, IReadOnlyList currentMoves, EvolutionHistory evos) - { - int shedinjaEvoMoveIndex = 0; - var format = pkm.Format; - for (int gen = Math.Min(format, 4); gen >= 3; gen--) - { - if (evos[gen].Length != 2) - continue; // Was not evolved in this generation - if (gen == 4 && pkm.Ball != 4 && !(pkm.Ball == (int)Ball.Sport && pkm.HGSS)) - continue; // Was definitively evolved in Gen3 - - var maxLevel = pkm.CurrentLevel; - var ninjaskMoves = MoveList.GetShedinjaEvolveMoves(pkm, gen, maxLevel); - bool native = gen == format; - for (int m = parse.Length - 1; m >= 0; m--) - { - var r = parse[m]; - if (IsCheckValid(r)) // already validated - continue; - - if (!ninjaskMoves.Contains(currentMoves[m])) - continue; - - // Can't have more than one Ninjask exclusive move on Shedinja - if (shedinjaEvoMoveIndex != 0) - { - r.FlagIllegal(LMoveNincada, CurrentMove); - continue; - } - - var msg = native ? LMoveNincadaEvo : string.Format(LMoveNincadaEvoF_0, gen); - r.Set(ShedinjaEvo, gen, Valid, msg, CurrentMove); - shedinjaEvoMoveIndex = m; - } - } - - if (shedinjaEvoMoveIndex == 0) - return; - - // Double check that the Ninjask move level isn't less than any Nincada move level - { - var r = parse[shedinjaEvoMoveIndex]; - if (r.Source != LevelUp) - return; - - var move = currentMoves[shedinjaEvoMoveIndex]; - int levelS = MoveList.GetShedinjaMoveLevel((int)Species.Shedinja, move, r.Generation); - if (levelS > 0) - return; - - int levelN = MoveList.GetShedinjaMoveLevel((int)Species.Nincada, move, r.Generation); - int levelJ = MoveList.GetShedinjaMoveLevel((int)Species.Ninjask, move, r.Generation); - if (levelN > levelJ) - r.FlagIllegal(string.Format(LMoveEvoFHigher, SpeciesStrings[(int)Species.Nincada], SpeciesStrings[(int)Species.Ninjask]), CurrentMove); - } - } - - private static bool GetHMCompatibility(PKM pkm, IReadOnlyList parse, int gen, IReadOnlyList moves, Span HMLearned) - { - // Check if pokemon knows HM moves from generation 3 and 4 but are not valid yet, that means it cant learn the HMs in future generations - if (gen == 4 && pkm.Format > 4) - { - FlagIsHMSource(HMLearned, Legal.HM_4_RemovePokeTransfer); - return moves.Where((m, i) => IsDefogWhirl(m) && IsCheckInvalid(parse[i])).Count() == 2; - } - if (gen == 3 && pkm.Format > 3) - FlagIsHMSource(HMLearned, Legal.HM_3); - return false; - - void FlagIsHMSource(Span flags, ICollection source) - { - for (int i = parse.Count - 1; i >= 0; i--) - flags[i] = IsCheckInvalid(parse[i]) && source.Contains(moves[i]); - } - } - - private static bool IsDefogWhirl(int move) => move is (int)Move.Defog or (int)Move.Whirlpool; - private static int GetDefogWhirlCount(IReadOnlyList parse, IReadOnlyList moves) - { - int ctr = 0; - for (int i = moves.Count - 1; i >= 0; i--) - { - if (!IsDefogWhirl(moves[i])) - continue; - var r = parse[i]; - if (!r.Valid || r.Generation >= 5) - continue; - ctr++; - } - return ctr; - } - - private static bool IsCheckInvalid(CheckMoveResult chk) => chk.IsParsed && !chk.Valid; - private static bool IsCheckValid(CheckMoveResult chk) => chk.IsParsed && chk.Valid; - - private static void FlagIncompatibleTransferHMs45(IReadOnlyList parse, IReadOnlyList currentMoves, int gen, ReadOnlySpan HMLearned, bool KnowDefogWhirlpool) - { - // After all the moves from the generations 3 and 4, - // including egg moves if is the origin generation because some hidden moves are also special egg moves in gen 3 - // Check if the marked hidden moves that were invalid at the start are now marked as valid, that means - // the hidden move was learned in gen 3 or 4 but was not removed when transfer to 4 or 5 - if (KnowDefogWhirlpool) - { - int invalidCount = GetDefogWhirlCount(parse, currentMoves); - if (invalidCount == 2) // can't know both at the same time - { - for (int i = parse.Count - 1; i >= 0; i--) // flag both moves - { - if (IsDefogWhirl(currentMoves[i])) - parse[i].FlagIllegal(LTransferMoveG4HM, CurrentMove); - } - } - } - - // Flag moves that are only legal when learned from a past-gen HM source - for (int i = HMLearned.Length - 1; i >= 0; i--) - { - if (HMLearned[i] && IsCheckValid(parse[i])) - parse[i].FlagIllegal(string.Format(LTransferMoveHM, gen, gen + 1), CurrentMove); - } - } - - private static void VerifyNoEmptyDuplicates(ReadOnlySpan moves, ReadOnlySpan parse) - { - bool emptySlot = false; - for (int i = 0; i < parse.Length; i++) - { - var move = moves[i]; - if (move == 0) - { - emptySlot = true; - continue; - } - - // If an empty slot was noted for a prior move, flag the empty slots. - if (emptySlot) - { - FlagEmptySlotsBeforeIndex(moves, parse, i); - emptySlot = false; - continue; - } - - // Check for same move in next move slots - FlagDuplicateMovesAfterIndex(moves, parse, i, move); - } - } - - private static void FlagDuplicateMovesAfterIndex(ReadOnlySpan moves, ReadOnlySpan parse, int index, int move) - { - for (int i = parse.Length - 1; i > index; i--) - { - if (moves[i] != move) - continue; - parse[index].FlagIllegal(LMoveSourceEmpty); - return; - } - } - - private static void FlagEmptySlotsBeforeIndex(ReadOnlySpan moves, ReadOnlySpan parse, int index) - { - for (int i = index - 1; i >= 0; i--) - { - if (moves[i] != 0) - return; - parse[i].FlagIllegal(LMoveSourceEmpty); + if (learnInfo.EggMovesLearned.Contains(m)) + parse[m].FlagIllegal(gift ? LMoveEggMoveGift : LMoveEggInvalidEvent, CurrentMove); + else if (learnInfo.LevelUpEggMoves.Contains(m)) + parse[m].FlagIllegal(gift ? LMoveEggInvalidEventLevelUpGift : LMoveEggInvalidEventLevelUp, CurrentMove); } } } + + private static void ParseRedYellowIncompatibleMoves(PKM pk, CheckMoveResult[] parse, IReadOnlyList currentMoves) + { + var incompatible = GetIncompatibleRBYMoves(pk, currentMoves); + if (incompatible.Count == 0) + return; + for (int m = parse.Length - 1; m >= 0; m--) + { + if (incompatible.Contains(currentMoves[m])) + parse[m].FlagIllegal(LG1MoveLearnSameLevel, CurrentMove); + } + } + + private static IList GetIncompatibleRBYMoves(PKM pk, IReadOnlyList currentMoves) + { + // Check moves that are learned at the same level in Red/Blue and Yellow, these are illegal because there is no Move Reminder in Gen1. + // There are only two incompatibilities for Gen1; there are no illegal combination in Gen2. + + switch (pk.Species) + { + // Vaporeon in Yellow learns Mist and Haze at level 42, Mist can only be learned if it leveled up in the daycare + // Vaporeon in Red/Blue learns Acid Armor at level 42 and level 47 in Yellow + case (int)Species.Vaporeon when pk.CurrentLevel < 47 && currentMoves.Contains((int)Move.AcidArmor): + { + var incompatible = new List(3); + if (currentMoves.Contains((int)Move.Mist)) incompatible.Add((int)Move.Mist); + if (currentMoves.Contains((int)Move.Haze)) incompatible.Add((int)Move.Haze); + if (incompatible.Count != 0) + incompatible.Add((int)Move.AcidArmor); + else + return Array.Empty(); + return incompatible; + } + + // Flareon in Yellow learns Smog at level 42 + // Flareon in Red Blue learns Leer at level 42 and level 47 in Yellow + case (int)Species.Flareon when pk.CurrentLevel < 47 && currentMoves.Contains((int)Move.Leer) && currentMoves.Contains((int)Move.Smog): + return new[] { (int)Move.Leer, (int)Move.Smog }; + + default: return Array.Empty(); + } + } + + private static void ParseEvolutionsIncompatibleMoves(PKM pk, CheckMoveResult[] parse, IReadOnlyList moves, IReadOnlyList tmhm) + { + GBRestrictions.GetIncompatibleEvolutionMoves(pk, moves, tmhm, + out var prevSpeciesID, + out var incompatPrev, + out var incompatCurr); + + if (prevSpeciesID == 0) + return; + + var prev = SpeciesStrings[prevSpeciesID]; + var curr = SpeciesStrings[pk.Species]; + for (int m = parse.Length - 1; m >= 0; m--) + { + if (incompatCurr.Contains(moves[m])) + parse[m].FlagIllegal(string.Format(LMoveEvoFLower, curr, prev), CurrentMove); + if (incompatPrev.Contains(moves[m])) + parse[m].FlagIllegal(string.Format(LMoveEvoFHigher, curr, prev), CurrentMove); + } + } + + private static void ParseShedinjaEvolveMoves(PKM pk, CheckMoveResult[] parse, IReadOnlyList currentMoves, EvolutionHistory evos) + { + int shedinjaEvoMoveIndex = 0; + var format = pk.Format; + for (int gen = Math.Min(format, 4); gen >= 3; gen--) + { + if (evos[gen].Length != 2) + continue; // Was not evolved in this generation + if (gen == 4 && pk.Ball != 4 && !(pk.Ball == (int)Ball.Sport && pk.HGSS)) + continue; // Was definitively evolved in Gen3 + + var maxLevel = pk.CurrentLevel; + var ninjaskMoves = MoveList.GetShedinjaEvolveMoves(pk, gen, maxLevel); + bool native = gen == format; + for (int m = parse.Length - 1; m >= 0; m--) + { + var r = parse[m]; + if (IsCheckValid(r)) // already validated + continue; + + if (!ninjaskMoves.Contains(currentMoves[m])) + continue; + + // Can't have more than one Ninjask exclusive move on Shedinja + if (shedinjaEvoMoveIndex != 0) + { + r.FlagIllegal(LMoveNincada, CurrentMove); + continue; + } + + var msg = native ? LMoveNincadaEvo : string.Format(LMoveNincadaEvoF_0, gen); + r.Set(ShedinjaEvo, gen, Valid, msg, CurrentMove); + shedinjaEvoMoveIndex = m; + } + } + + if (shedinjaEvoMoveIndex == 0) + return; + + // Double check that the Ninjask move level isn't less than any Nincada move level + { + var r = parse[shedinjaEvoMoveIndex]; + if (r.Source != LevelUp) + return; + + var move = currentMoves[shedinjaEvoMoveIndex]; + int levelS = MoveList.GetShedinjaMoveLevel((int)Species.Shedinja, move, r.Generation); + if (levelS > 0) + return; + + int levelN = MoveList.GetShedinjaMoveLevel((int)Species.Nincada, move, r.Generation); + int levelJ = MoveList.GetShedinjaMoveLevel((int)Species.Ninjask, move, r.Generation); + if (levelN > levelJ) + r.FlagIllegal(string.Format(LMoveEvoFHigher, SpeciesStrings[(int)Species.Nincada], SpeciesStrings[(int)Species.Ninjask]), CurrentMove); + } + } + + private static bool GetHMCompatibility(PKM pk, IReadOnlyList parse, int gen, IReadOnlyList moves, Span HMLearned) + { + // Check if pokemon knows HM moves from generation 3 and 4 but are not valid yet, that means it cant learn the HMs in future generations + if (gen == 4 && pk.Format > 4) + { + FlagIsHMSource(HMLearned, Legal.HM_4_RemovePokeTransfer); + return moves.Where((m, i) => IsDefogWhirl(m) && IsCheckInvalid(parse[i])).Count() == 2; + } + if (gen == 3 && pk.Format > 3) + FlagIsHMSource(HMLearned, Legal.HM_3); + return false; + + void FlagIsHMSource(Span flags, ICollection source) + { + for (int i = parse.Count - 1; i >= 0; i--) + flags[i] = IsCheckInvalid(parse[i]) && source.Contains(moves[i]); + } + } + + private static bool IsDefogWhirl(int move) => move is (int)Move.Defog or (int)Move.Whirlpool; + private static int GetDefogWhirlCount(IReadOnlyList parse, IReadOnlyList moves) + { + int ctr = 0; + for (int i = moves.Count - 1; i >= 0; i--) + { + if (!IsDefogWhirl(moves[i])) + continue; + var r = parse[i]; + if (!r.Valid || r.Generation >= 5) + continue; + ctr++; + } + return ctr; + } + + private static bool IsCheckInvalid(CheckMoveResult chk) => chk.IsParsed && !chk.Valid; + private static bool IsCheckValid(CheckMoveResult chk) => chk.IsParsed && chk.Valid; + + private static void FlagIncompatibleTransferHMs45(IReadOnlyList parse, IReadOnlyList currentMoves, int gen, ReadOnlySpan HMLearned, bool KnowDefogWhirlpool) + { + // After all the moves from the generations 3 and 4, + // including egg moves if is the origin generation because some hidden moves are also special egg moves in gen 3 + // Check if the marked hidden moves that were invalid at the start are now marked as valid, that means + // the hidden move was learned in gen 3 or 4 but was not removed when transfer to 4 or 5 + if (KnowDefogWhirlpool) + { + int invalidCount = GetDefogWhirlCount(parse, currentMoves); + if (invalidCount == 2) // can't know both at the same time + { + for (int i = parse.Count - 1; i >= 0; i--) // flag both moves + { + if (IsDefogWhirl(currentMoves[i])) + parse[i].FlagIllegal(LTransferMoveG4HM, CurrentMove); + } + } + } + + // Flag moves that are only legal when learned from a past-gen HM source + for (int i = HMLearned.Length - 1; i >= 0; i--) + { + if (HMLearned[i] && IsCheckValid(parse[i])) + parse[i].FlagIllegal(string.Format(LTransferMoveHM, gen, gen + 1), CurrentMove); + } + } + + private static void VerifyNoEmptyDuplicates(ReadOnlySpan moves, ReadOnlySpan parse) + { + bool emptySlot = false; + for (int i = 0; i < parse.Length; i++) + { + var move = moves[i]; + if (move == 0) + { + emptySlot = true; + continue; + } + + // If an empty slot was noted for a prior move, flag the empty slots. + if (emptySlot) + { + FlagEmptySlotsBeforeIndex(moves, parse, i); + emptySlot = false; + continue; + } + + // Check for same move in next move slots + FlagDuplicateMovesAfterIndex(moves, parse, i, move); + } + } + + private static void FlagDuplicateMovesAfterIndex(ReadOnlySpan moves, ReadOnlySpan parse, int index, int move) + { + for (int i = parse.Length - 1; i > index; i--) + { + if (moves[i] != move) + continue; + parse[index].FlagIllegal(LMoveSourceEmpty); + return; + } + } + + private static void FlagEmptySlotsBeforeIndex(ReadOnlySpan moves, ReadOnlySpan parse, int index) + { + for (int i = index - 1; i >= 0; i--) + { + if (moves[i] != 0) + return; + parse[i].FlagIllegal(LMoveSourceEmpty); + } + } } diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/VerifyRelearnMoves.cs b/PKHeX.Core/Legality/Encounters/Verifiers/VerifyRelearnMoves.cs index fc90ec0c9..a471c8608 100644 --- a/PKHeX.Core/Legality/Encounters/Verifiers/VerifyRelearnMoves.cs +++ b/PKHeX.Core/Legality/Encounters/Verifiers/VerifyRelearnMoves.cs @@ -3,159 +3,158 @@ using static PKHeX.Core.LegalityCheckStrings; using static PKHeX.Core.ParseSettings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic to verify the current . +/// +public static class VerifyRelearnMoves { - /// - /// Logic to verify the current . - /// - public static class VerifyRelearnMoves + internal static void DummyValid(CheckMoveResult p) => p.Set(MoveSource.Relearn, 0, Severity.Valid, L_AValid, CheckIdentifier.RelearnMove); + + public static CheckMoveResult[] VerifyRelearn(PKM pk, IEncounterTemplate enc, CheckMoveResult[] result) { - internal static void DummyValid(CheckMoveResult p) => p.Set(MoveSource.Relearn, 0, Severity.Valid, L_AValid, CheckIdentifier.RelearnMove); + if (ShouldNotHaveRelearnMoves(enc, pk)) + return VerifyRelearnNone(pk, result); - public static CheckMoveResult[] VerifyRelearn(PKM pkm, IEncounterTemplate enc, CheckMoveResult[] result) + return enc switch { - if (ShouldNotHaveRelearnMoves(enc, pkm)) - return VerifyRelearnNone(pkm, result); + IRelearn s when s.Relearn.Count != 0 => VerifyRelearnSpecifiedMoveset(pk, s.Relearn, result), + EncounterEgg e => VerifyEggMoveset(e, result, pk.RelearnMoves), + EncounterSlot6AO {CanDexNav:true} z when pk.RelearnMove1 != 0 => VerifyRelearnDexNav(pk, result, z), + EncounterSlot8b {IsUnderground:true} u => VerifyRelearnUnderground(pk, result, u), + _ => VerifyRelearnNone(pk, result), + }; + } - return enc switch + public static bool ShouldNotHaveRelearnMoves(IGeneration enc, PKM pk) => enc.Generation < 6 || pk.IsOriginalMovesetDeleted(); + + private static CheckMoveResult[] VerifyRelearnSpecifiedMoveset(PKM pk, IReadOnlyList required, CheckMoveResult[] result) + { + CheckResult(pk.RelearnMove4, required[3], result[3]); + CheckResult(pk.RelearnMove3, required[2], result[2]); + CheckResult(pk.RelearnMove2, required[1], result[1]); + CheckResult(pk.RelearnMove1, required[0], result[0]); + return result; + + static void CheckResult(int move, int require, CheckMoveResult p) + { + if (move == require) { - IRelearn s when s.Relearn.Count != 0 => VerifyRelearnSpecifiedMoveset(pkm, s.Relearn, result), - EncounterEgg e => VerifyEggMoveset(e, result, pkm.RelearnMoves), - EncounterSlot6AO {CanDexNav:true} z when pkm.RelearnMove1 != 0 => VerifyRelearnDexNav(pkm, result, z), - EncounterSlot8b {IsUnderground:true} u => VerifyRelearnUnderground(pkm, result, u), - _ => VerifyRelearnNone(pkm, result), - }; - } - - public static bool ShouldNotHaveRelearnMoves(IGeneration enc, PKM pkm) => enc.Generation < 6 || pkm.IsOriginalMovesetDeleted(); - - private static CheckMoveResult[] VerifyRelearnSpecifiedMoveset(PKM pkm, IReadOnlyList required, CheckMoveResult[] result) - { - CheckResult(pkm.RelearnMove4, required[3], result[3]); - CheckResult(pkm.RelearnMove3, required[2], result[2]); - CheckResult(pkm.RelearnMove2, required[1], result[1]); - CheckResult(pkm.RelearnMove1, required[0], result[0]); - return result; - - static void CheckResult(int move, int require, CheckMoveResult p) - { - if (move == require) - { - DummyValid(p); - return; - } - var c = string.Format(LMoveFExpect_0, MoveStrings[require]); - p.Set(MoveSource.Relearn, 0, Severity.Invalid, c, CheckIdentifier.RelearnMove); - } - } - - private static void ParseExpectEmpty(CheckMoveResult p, int move) - { - if (move == 0) DummyValid(p); - else - p.Set(MoveSource.Relearn, 0, Severity.Invalid, LMoveRelearnNone, CheckIdentifier.RelearnMove); - } - - private static CheckMoveResult[] VerifyRelearnDexNav(PKM pkm, CheckMoveResult[] result, EncounterSlot6AO slot) - { - // All other relearn moves must be empty. - ParseExpectEmpty(result[3], pkm.RelearnMove4); - ParseExpectEmpty(result[2], pkm.RelearnMove3); - ParseExpectEmpty(result[1], pkm.RelearnMove2); - - // DexNav Pokémon can have 1 random egg move as a relearn move. - var p = result[0]; - if (!slot.CanBeDexNavMove(pkm.RelearnMove1)) // not found - p.Set(MoveSource.Relearn, 6, Severity.Invalid, LMoveRelearnDexNav, CheckIdentifier.RelearnMove); - else - DummyValid(p); - - return result; - } - - private static CheckMoveResult[] VerifyRelearnUnderground(PKM pkm, CheckMoveResult[] result, EncounterSlot8b slot) - { - // All other relearn moves must be empty. - ParseExpectEmpty(result[3], pkm.RelearnMove4); - ParseExpectEmpty(result[2], pkm.RelearnMove3); - ParseExpectEmpty(result[1], pkm.RelearnMove2); - - // Underground Pokémon can have 1 random egg move as a relearn move. - var p = result[0]; - if (!slot.CanBeUndergroundMove(pkm.RelearnMove1)) // not found - p.Set(MoveSource.Relearn, 0, Severity.Invalid, LMoveRelearnUnderground, CheckIdentifier.RelearnMove); - else - DummyValid(p); - - return result; - } - - private static CheckMoveResult[] VerifyRelearnNone(PKM pkm, CheckMoveResult[] result) - { - // No relearn moves should be present. - ParseExpectEmpty(result[3], pkm.RelearnMove4); - ParseExpectEmpty(result[2], pkm.RelearnMove3); - ParseExpectEmpty(result[1], pkm.RelearnMove2); - ParseExpectEmpty(result[0], pkm.RelearnMove1); - return result; - } - - internal static CheckMoveResult[] VerifyEggMoveset(EncounterEgg e, CheckMoveResult[] result, int[] moves, CheckIdentifier type = CheckIdentifier.RelearnMove) - { - int gen = e.Generation; - var origins = MoveBreed.Process(gen, e.Species, e.Form, e.Version, moves, out var valid); - if (valid) - { - for (int i = 0; i < result.Length; i++) - { - var msg = EggSourceUtil.GetSource(origins, gen, i); - result[i].Set(MoveSource.EggMove, gen, Severity.Valid, msg, type); - } + return; } - else - { - var expected = MoveBreed.GetExpectedMoves(moves, e); - origins = MoveBreed.Process(gen, e.Species, e.Form, e.Version, expected, out _); - for (int i = 0; i < moves.Length; i++) - { - var msg = EggSourceUtil.GetSource(origins, gen, i); - var expect = expected[i]; - var p = result[i]; - if (moves[i] == expect) - { - p.Set(MoveSource.EggMove, gen, Severity.Valid, msg, type); - } - else - { - msg = string.Format(LMoveRelearnFExpect_0, MoveStrings[expect], msg); - p.Set(MoveSource.EggMove, gen, Severity.Invalid, msg, type); - } - } - } - - var dupe = IsAnyMoveDuplicate(moves); - if (dupe != NO_DUPE) - result[dupe].Set(MoveSource.EggMove, gen, Severity.Invalid, LMoveSourceDuplicate, type); - return result; - } - - private const int NO_DUPE = -1; - - private static int IsAnyMoveDuplicate(ReadOnlySpan move) - { - int m1 = move[0]; - int m2 = move[1]; - - if (m1 != 0 && m1 == m2) - return 1; - int m3 = move[2]; - if (m3 != 0 && (m1 == m3 || m2 == m3)) - return 2; - int m4 = move[3]; - if (m4 != 0 && (m1 == m4 || m2 == m4 || m3 == m4)) - return 3; - return NO_DUPE; + var c = string.Format(LMoveFExpect_0, MoveStrings[require]); + p.Set(MoveSource.Relearn, 0, Severity.Invalid, c, CheckIdentifier.RelearnMove); } } + + private static void ParseExpectEmpty(CheckMoveResult p, int move) + { + if (move == 0) + DummyValid(p); + else + p.Set(MoveSource.Relearn, 0, Severity.Invalid, LMoveRelearnNone, CheckIdentifier.RelearnMove); + } + + private static CheckMoveResult[] VerifyRelearnDexNav(PKM pk, CheckMoveResult[] result, EncounterSlot6AO slot) + { + // All other relearn moves must be empty. + ParseExpectEmpty(result[3], pk.RelearnMove4); + ParseExpectEmpty(result[2], pk.RelearnMove3); + ParseExpectEmpty(result[1], pk.RelearnMove2); + + // DexNav Pokémon can have 1 random egg move as a relearn move. + var p = result[0]; + if (!slot.CanBeDexNavMove(pk.RelearnMove1)) // not found + p.Set(MoveSource.Relearn, 6, Severity.Invalid, LMoveRelearnDexNav, CheckIdentifier.RelearnMove); + else + DummyValid(p); + + return result; + } + + private static CheckMoveResult[] VerifyRelearnUnderground(PKM pk, CheckMoveResult[] result, EncounterSlot8b slot) + { + // All other relearn moves must be empty. + ParseExpectEmpty(result[3], pk.RelearnMove4); + ParseExpectEmpty(result[2], pk.RelearnMove3); + ParseExpectEmpty(result[1], pk.RelearnMove2); + + // Underground Pokémon can have 1 random egg move as a relearn move. + var p = result[0]; + if (!slot.CanBeUndergroundMove(pk.RelearnMove1)) // not found + p.Set(MoveSource.Relearn, 0, Severity.Invalid, LMoveRelearnUnderground, CheckIdentifier.RelearnMove); + else + DummyValid(p); + + return result; + } + + private static CheckMoveResult[] VerifyRelearnNone(PKM pk, CheckMoveResult[] result) + { + // No relearn moves should be present. + ParseExpectEmpty(result[3], pk.RelearnMove4); + ParseExpectEmpty(result[2], pk.RelearnMove3); + ParseExpectEmpty(result[1], pk.RelearnMove2); + ParseExpectEmpty(result[0], pk.RelearnMove1); + return result; + } + + internal static CheckMoveResult[] VerifyEggMoveset(EncounterEgg e, CheckMoveResult[] result, int[] moves, CheckIdentifier type = CheckIdentifier.RelearnMove) + { + int gen = e.Generation; + var origins = MoveBreed.Process(gen, e.Species, e.Form, e.Version, moves, out var valid); + if (valid) + { + for (int i = 0; i < result.Length; i++) + { + var msg = EggSourceUtil.GetSource(origins, gen, i); + result[i].Set(MoveSource.EggMove, gen, Severity.Valid, msg, type); + } + } + else + { + var expected = MoveBreed.GetExpectedMoves(moves, e); + origins = MoveBreed.Process(gen, e.Species, e.Form, e.Version, expected, out _); + for (int i = 0; i < moves.Length; i++) + { + var msg = EggSourceUtil.GetSource(origins, gen, i); + var expect = expected[i]; + var p = result[i]; + if (moves[i] == expect) + { + p.Set(MoveSource.EggMove, gen, Severity.Valid, msg, type); + } + else + { + msg = string.Format(LMoveRelearnFExpect_0, MoveStrings[expect], msg); + p.Set(MoveSource.EggMove, gen, Severity.Invalid, msg, type); + } + } + } + + var dupe = IsAnyMoveDuplicate(moves); + if (dupe != NO_DUPE) + result[dupe].Set(MoveSource.EggMove, gen, Severity.Invalid, LMoveSourceDuplicate, type); + return result; + } + + private const int NO_DUPE = -1; + + private static int IsAnyMoveDuplicate(ReadOnlySpan move) + { + int m1 = move[0]; + int m2 = move[1]; + + if (m1 != 0 && m1 == m2) + return 1; + int m3 = move[2]; + if (m3 != 0 && (m1 == m3 || m2 == m3)) + return 2; + int m4 = move[3]; + if (m4 != 0 && (m1 == m4 || m2 == m4 || m3 == m4)) + return 3; + return NO_DUPE; + } } diff --git a/PKHeX.Core/Legality/Enums/CheckIdentifier.cs b/PKHeX.Core/Legality/Enums/CheckIdentifier.cs index 6df0c98be..a395e06ae 100644 --- a/PKHeX.Core/Legality/Enums/CheckIdentifier.cs +++ b/PKHeX.Core/Legality/Enums/CheckIdentifier.cs @@ -1,157 +1,156 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Identification flair for what properties a pertains to +public enum CheckIdentifier : byte { - /// Identification flair for what properties a pertains to - public enum CheckIdentifier : byte - { - /// - /// The pertains to the . - /// - CurrentMove, + /// + /// The pertains to the . + /// + CurrentMove, - /// - /// The pertains to the . - /// - RelearnMove, + /// + /// The pertains to the . + /// + RelearnMove, - /// - /// The pertains to the 's matched encounter information. - /// - Encounter, + /// + /// The pertains to the 's matched encounter information. + /// + Encounter, - /// - /// The pertains to the status. - /// - Shiny, + /// + /// The pertains to the status. + /// + Shiny, - /// - /// The pertains to the . - /// - EC, + /// + /// The pertains to the . + /// + EC, - /// - /// The pertains to the . - /// - PID, + /// + /// The pertains to the . + /// + PID, - /// - /// The pertains to the . - /// - Gender, + /// + /// The pertains to the . + /// + Gender, - /// - /// The pertains to the . - /// - EVs, + /// + /// The pertains to the . + /// + EVs, - /// - /// The pertains to the . - /// - Language, + /// + /// The pertains to the . + /// + Language, - /// - /// The pertains to the . - /// - Nickname, + /// + /// The pertains to the . + /// + Nickname, - /// - /// The pertains to the , , or . - /// - Trainer, + /// + /// The pertains to the , , or . + /// + Trainer, - /// - /// The pertains to the . - /// - IVs, + /// + /// The pertains to the . + /// + IVs, - /// - /// The pertains to the or . - /// - Level, + /// + /// The pertains to the or . + /// + Level, - /// - /// The pertains to the . - /// - Ball, + /// + /// The pertains to the . + /// + Ball, - /// - /// The pertains to the memory data. - /// - Memory, + /// + /// The pertains to the memory data. + /// + Memory, - /// - /// The pertains to the geography data. - /// - Geography, + /// + /// The pertains to the geography data. + /// + Geography, - /// - /// The pertains to the . - /// - Form, + /// + /// The pertains to the . + /// + Form, - /// - /// The pertains to the status. - /// - Egg, + /// + /// The pertains to the status. + /// + Egg, - /// - /// The pertains to the miscellaneous properties. - /// - Misc, + /// + /// The pertains to the miscellaneous properties. + /// + Misc, - /// - /// The pertains to the . - /// - Fateful, + /// + /// The pertains to the . + /// + Fateful, - /// - /// The pertains to the ribbon data. - /// - Ribbon, + /// + /// The pertains to the ribbon data. + /// + Ribbon, - /// - /// The pertains to the super training data. - /// - Training, + /// + /// The pertains to the super training data. + /// + Training, - /// - /// The pertains to the . - /// - Ability, + /// + /// The pertains to the . + /// + Ability, - /// - /// The pertains to the evolution chain relative to the matched encounter. - /// - Evolution, + /// + /// The pertains to the evolution chain relative to the matched encounter. + /// + Evolution, - /// - /// The pertains to the . - /// - Nature, + /// + /// The pertains to the . + /// + Nature, - /// - /// The pertains to the 's compatibility. - /// This is used for parsing checks to ensure the didn't debut on a future - /// - GameOrigin, + /// + /// The pertains to the 's compatibility. + /// This is used for parsing checks to ensure the didn't debut on a future + /// + GameOrigin, - /// - /// The CheckResult pertains to the . - /// - HeldItem, + /// + /// The CheckResult pertains to the . + /// + HeldItem, - /// - /// The pertains to the . - /// - RibbonMark, + /// + /// The pertains to the . + /// + RibbonMark, - /// - /// The pertains to the values. - /// - GVs, + /// + /// The pertains to the values. + /// + GVs, - /// - /// The pertains to values. - /// - Marking, - } + /// + /// The pertains to values. + /// + Marking, } diff --git a/PKHeX.Core/Legality/Enums/EncounterTime.cs b/PKHeX.Core/Legality/Enums/EncounterTime.cs index ffa01f390..476f8c8c5 100644 --- a/PKHeX.Core/Legality/Enums/EncounterTime.cs +++ b/PKHeX.Core/Legality/Enums/EncounterTime.cs @@ -1,32 +1,31 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 2 Time of Encounter enum +/// +[Flags] +internal enum EncounterTime : byte { - /// - /// Generation 2 Time of Encounter enum - /// - [Flags] - internal enum EncounterTime : byte - { - Any = 0, - Morning = 1 << 1, - Day = 1 << 2, - Night = 1 << 3, - } + Any = 0, + Morning = 1 << 1, + Day = 1 << 2, + Night = 1 << 3, +} - internal static class EncounterTimeExtension - { - internal static bool Contains(this EncounterTime t1, int t2) => t1 == EncounterTime.Any || (t1 & (EncounterTime)(1 << t2)) != 0; +internal static class EncounterTimeExtension +{ + internal static bool Contains(this EncounterTime t1, int t2) => t1 == EncounterTime.Any || (t1 & (EncounterTime)(1 << t2)) != 0; - internal static int RandomValidTime(this EncounterTime t1) - { - var rnd = Util.Rand; - int val = rnd.Next(1, 4); - if (t1 == EncounterTime.Any) - return val; - while (!t1.Contains(val)) - val = rnd.Next(1, 4); + internal static int RandomValidTime(this EncounterTime t1) + { + var rnd = Util.Rand; + int val = rnd.Next(1, 4); + if (t1 == EncounterTime.Any) return val; - } + while (!t1.Contains(val)) + val = rnd.Next(1, 4); + return val; } } diff --git a/PKHeX.Core/Legality/Enums/GroundTileAllowed.cs b/PKHeX.Core/Legality/Enums/GroundTileAllowed.cs new file mode 100644 index 000000000..877acefaa --- /dev/null +++ b/PKHeX.Core/Legality/Enums/GroundTileAllowed.cs @@ -0,0 +1,67 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Permitted values an encounter can be acquired with. +/// +/// +/// Some locations have multiple tile types that can proc an encounter, requiring multiple values possible. +/// +[Flags] +public enum GroundTileAllowed +{ + Undefined = 0, + None = 1 << 00, // No animation for the tile + Sand = 1 << 01, // Obtainable only via HG/SS + Grass = 1 << 02, + Puddle = 1 << 03, // No encounters from this tile type + Rock = 1 << 04, + Cave = 1 << 05, + Snow = 1 << 06, // No encounters from this tile type + Water = 1 << 07, + Ice = 1 << 08, // No encounters from this tile type + Building = 1 << 09, + Marsh = 1 << 10, + Bridge = 1 << 11, // No encounters from this tile type + Max_DP = 1 << 12, // Unspecific, catch-all for DP undefined tiles. + + // added tile types in Pt + // no encounters from these tile types + Elite4_1 = 1 << 12, // Elite Four Room #1 + Elite4_2 = 1 << 13, // Elite Four Room #2 + Elite4_3 = 1 << 14, // Elite Four Room #3 + Elite4_4 = 1 << 15, // Elite Four Room #4 + Elite4_M = 1 << 16, // Elite Four Champion Room + DistortionSideways = 1 << 17, // Distortion World (Not Giratina) + BattleTower = 1 << 18, + BattleFactory = 1 << 19, + BattleArcade = 1 << 20, + BattleCastle = 1 << 21, + BattleHall = 1 << 22, + + Distortion = 1 << 23, + Max_Pt = 1 << 24, // Unspecific, catch-all for Pt undefined tiles. +} + +public static class GroundTileAllowedExtensions +{ + public static bool Contains(this GroundTileAllowed g1, GroundTileType g2) => (g1 & (GroundTileAllowed)(1 << (int)g2)) != 0; + + /// + /// Grabs the highest set bit from the tile value. + /// + /// Tile bit-permission value + /// Bit index + public static GroundTileType GetIndex(this GroundTileAllowed g) + { + int val = (int) g; + for (byte i = 0; i < 8 * sizeof(GroundTileAllowed); i++) + { + val >>= 1; + if (val == 0) + return (GroundTileType)i; + } + return 0; + } +} diff --git a/PKHeX.Core/Legality/Enums/GroundTilePermission.cs b/PKHeX.Core/Legality/Enums/GroundTilePermission.cs deleted file mode 100644 index 65692d039..000000000 --- a/PKHeX.Core/Legality/Enums/GroundTilePermission.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; - -namespace PKHeX.Core -{ - /// - /// Permitted values an encounter can be acquired with. - /// - /// - /// Some locations have multiple tile types that can proc an encounter, requiring multiple values possible. - /// - [Flags] - public enum GroundTilePermission - { - Undefined = 0, - None = 1 << 00, // No animation for the tile - Sand = 1 << 01, // Obtainable only via HG/SS - Grass = 1 << 02, - Puddle = 1 << 03, // No encounters from this tile type - Rock = 1 << 04, - Cave = 1 << 05, - Snow = 1 << 06, // No encounters from this tile type - Water = 1 << 07, - Ice = 1 << 08, // No encounters from this tile type - Building = 1 << 09, - Marsh = 1 << 10, - Bridge = 1 << 11, // No encounters from this tile type - Max_DP = 1 << 12, // Unspecific, catch-all for DP undefined tiles. - - // added tile types in Pt - // no encounters from these tile types - Elite4_1 = 1 << 12, // Elite Four Room #1 - Elite4_2 = 1 << 13, // Elite Four Room #2 - Elite4_3 = 1 << 14, // Elite Four Room #3 - Elite4_4 = 1 << 15, // Elite Four Room #4 - Elite4_M = 1 << 16, // Elite Four Champion Room - DistortionSideways = 1 << 17, // Distortion World (Not Giratina) - BattleTower = 1 << 18, - BattleFactory = 1 << 19, - BattleArcade = 1 << 20, - BattleCastle = 1 << 21, - BattleHall = 1 << 22, - - Distortion = 1 << 23, - Max_Pt = 1 << 24, // Unspecific, catch-all for Pt undefined tiles. - } - - public static class GroundTilePermissionExtensions - { - public static bool Contains(this GroundTilePermission g1, GroundTileType g2) => (g1 & (GroundTilePermission)(1 << (int)g2)) != 0; - - /// - /// Grabs the highest set bit from the tile value. - /// - /// Tile bit-permission value - /// Bit index - public static GroundTileType GetIndex(this GroundTilePermission g) - { - int val = (int) g; - for (byte i = 0; i < 8 * sizeof(GroundTilePermission); i++) - { - val >>= 1; - if (val == 0) - return (GroundTileType)i; - } - return 0; - } - } -} diff --git a/PKHeX.Core/Legality/Enums/Severity.cs b/PKHeX.Core/Legality/Enums/Severity.cs index 6be15445f..2fd2cb172 100644 --- a/PKHeX.Core/Legality/Enums/Severity.cs +++ b/PKHeX.Core/Legality/Enums/Severity.cs @@ -1,50 +1,49 @@ using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Severity indication of the associated +/// +/// Severity >= is green +/// Severity == is yellow +/// Severity <= is red +/// +public enum Severity : sbyte { - /// Severity indication of the associated - /// - /// Severity >= is green - /// Severity == is yellow - /// Severity <= is red - /// - public enum Severity : sbyte - { - /// - /// Cannot determine validity; not valid. - /// - Indeterminate = -2, + /// + /// Cannot determine validity; not valid. + /// + Indeterminate = -2, - /// - /// Definitively not valid. - /// - Invalid = -1, + /// + /// Definitively not valid. + /// + Invalid = -1, - /// - /// Suspicious values, but still valid. - /// - Fishy = 0, + /// + /// Suspicious values, but still valid. + /// + Fishy = 0, - /// - /// Values are valid. - /// - Valid = 1, - } - - public static partial class Extensions - { - /// - /// Converts a Check result Severity determination (Valid/Invalid/etc) to the localized string. - /// - /// value to convert to string. - /// Localized . - public static string Description(this Severity s) => s switch - { - Severity.Indeterminate => L_SIndeterminate, - Severity.Invalid => L_SInvalid, - Severity.Fishy => L_SFishy, - Severity.Valid => L_SValid, - _ => L_SNotImplemented, - }; - } + /// + /// Values are valid. + /// + Valid = 1, +} + +public static partial class Extensions +{ + /// + /// Converts a Check result Severity determination (Valid/Invalid/etc) to the localized string. + /// + /// value to convert to string. + /// Localized . + public static string Description(this Severity s) => s switch + { + Severity.Indeterminate => L_SIndeterminate, + Severity.Invalid => L_SInvalid, + Severity.Fishy => L_SFishy, + Severity.Valid => L_SValid, + _ => L_SNotImplemented, + }; } diff --git a/PKHeX.Core/Legality/Enums/SlotType.cs b/PKHeX.Core/Legality/Enums/SlotType.cs index 4c28b05ab..6218ba41b 100644 --- a/PKHeX.Core/Legality/Enums/SlotType.cs +++ b/PKHeX.Core/Legality/Enums/SlotType.cs @@ -1,114 +1,113 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Wild Encounter data Type +/// +/// +/// Different from , this corresponds to the method that the may be encountered. +[Flags] +#pragma warning disable RCS1191 // Declare enum value as combination of names. +public enum SlotType : byte { /// - /// Wild Encounter data Type + /// Default (un-assigned) encounter slot type. /// - /// - /// Different from , this corresponds to the method that the may be encountered. - [Flags] -#pragma warning disable RCS1191 // Declare enum value as combination of names. - public enum SlotType : byte - { - /// - /// Default (un-assigned) encounter slot type. - /// - Any = 0, + Any = 0, - /// - /// Slot is encountered via Grass. - /// - Grass = 1, + /// + /// Slot is encountered via Grass. + /// + Grass = 1, - /// - /// Slot is encountered via Surfing. - /// - Surf = 2, + /// + /// Slot is encountered via Surfing. + /// + Surf = 2, - /// - /// Slot is encountered via Old Rod (Fishing). - /// - Old_Rod = 3, + /// + /// Slot is encountered via Old Rod (Fishing). + /// + Old_Rod = 3, - /// - /// Slot is encountered via Good Rod (Fishing). - /// - Good_Rod = 4, + /// + /// Slot is encountered via Good Rod (Fishing). + /// + Good_Rod = 4, - /// - /// Slot is encountered via Super Rod (Fishing). - /// - Super_Rod = 5, + /// + /// Slot is encountered via Super Rod (Fishing). + /// + Super_Rod = 5, - /// - /// Slot is encountered via Rock Smash. - /// - Rock_Smash = 6, + /// + /// Slot is encountered via Rock Smash. + /// + Rock_Smash = 6, - /// - /// Slot is encountered via Headbutt. - /// - Headbutt = 7, + /// + /// Slot is encountered via Headbutt. + /// + Headbutt = 7, - /// - /// Slot is encountered via a Honey Tree. - /// - HoneyTree = 8, + /// + /// Slot is encountered via a Honey Tree. + /// + HoneyTree = 8, - /// - /// Slot is encountered via the Bug Catching Contest. - /// - BugContest = 9, + /// + /// Slot is encountered via the Bug Catching Contest. + /// + BugContest = 9, - /// - /// Slot is encountered via Generation 5 Hidden Grotto. - /// - HiddenGrotto = 10, + /// + /// Slot is encountered via Generation 5 Hidden Grotto. + /// + HiddenGrotto = 10, - // GoPark = 11, UNUSED, now EncounterSlot7g + // GoPark = 11, UNUSED, now EncounterSlot7g - /// - /// Slot is encountered via Generation 6 Friend Safari. - /// - FriendSafari = 12, + /// + /// Slot is encountered via Generation 6 Friend Safari. + /// + FriendSafari = 12, - /// - /// Slot is encountered via Generation 6 Horde Battle. - /// - Horde = 13, + /// + /// Slot is encountered via Generation 6 Horde Battle. + /// + Horde = 13, - // Pokeradar = 14, // UNUSED, don't need to differentiate Gen4 Radar Slots + // Pokeradar = 14, // UNUSED, don't need to differentiate Gen4 Radar Slots - /// - /// Slot is encountered via Generation 7 SOS triggers only. - /// - SOS = 15, + /// + /// Slot is encountered via Generation 7 SOS triggers only. + /// + SOS = 15, - // Legends: Arceus - Overworld = 16, - Distortion = 17, - Landmark = 18, - OverworldMass = 19, - OverworldMMO = 20, + // Legends: Arceus + Overworld = 16, + Distortion = 17, + Landmark = 18, + OverworldMass = 19, + OverworldMMO = 20, - // Modifiers + // Modifiers - /// - /// Used to differentiate the two types of headbutt tree encounters. - /// - /// - Special = 1 << 6, + /// + /// Used to differentiate the two types of headbutt tree encounters. + /// + /// + Special = 1 << 6, - /// - /// Used to identify encounters that are triggered via alternate ESV proc calculations. - /// - Swarm = 1 << 7, - } - - public static partial class Extensions - { - internal static bool IsFishingRodType(this SlotType t) => (t & (SlotType)0xF) is SlotType.Old_Rod or SlotType.Good_Rod or SlotType.Super_Rod; - internal static bool IsSweetScentType(this SlotType t) => (t & (SlotType)0xF) is SlotType.Grass or SlotType.Surf or SlotType.BugContest; - } + /// + /// Used to identify encounters that are triggered via alternate ESV proc calculations. + /// + Swarm = 1 << 7, +} + +public static partial class Extensions +{ + internal static bool IsFishingRodType(this SlotType t) => (t & (SlotType)0xF) is SlotType.Old_Rod or SlotType.Good_Rod or SlotType.Super_Rod; + internal static bool IsSweetScentType(this SlotType t) => (t & (SlotType)0xF) is SlotType.Grass or SlotType.Surf or SlotType.BugContest; } diff --git a/PKHeX.Core/Legality/Evolutions/EncounterOrigin.cs b/PKHeX.Core/Legality/Evolutions/EncounterOrigin.cs index 90cbb74dd..f654f4d60 100644 --- a/PKHeX.Core/Legality/Evolutions/EncounterOrigin.cs +++ b/PKHeX.Core/Legality/Evolutions/EncounterOrigin.cs @@ -2,180 +2,179 @@ using System.Collections.Generic; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains logic that calculates the evolution chain of a , only considering the generation it originated in. +/// +public static class EncounterOrigin { /// - /// Contains logic that calculates the evolution chain of a , only considering the generation it originated in. + /// Gets possible evolution details for the input /// - public static class EncounterOrigin + /// Current state of the Pokémon + /// Possible origin species-form-levels to match against encounter data. + /// Use if the originated from Generation 1 or 2. + public static EvoCriteria[] GetOriginChain(PKM pk) { - /// - /// Gets possible evolution details for the input - /// - /// Current state of the Pokémon - /// Possible origin species-form-levels to match against encounter data. - /// Use if the originated from Generation 1 or 2. - public static EvoCriteria[] GetOriginChain(PKM pkm) - { - bool hasOriginMet = pkm.HasOriginalMetLocation; - var maxLevel = GetLevelOriginMax(pkm, hasOriginMet); - var minLevel = GetLevelOriginMin(pkm, hasOriginMet); - return GetOriginChain(pkm, -1, (byte)maxLevel, (byte)minLevel, hasOriginMet); - } - - /// - /// Gets possible evolution details for the input originating from Generation 1 or 2. - /// - /// Current state of the Pokémon - /// Game/group the originated from. If , it assumes Gen 1, otherwise Gen 2. - /// Possible origin species-form-levels to match against encounter data. - public static EvoCriteria[] GetOriginChain12(PKM pkm, GameVersion gameSource) - { - bool rby = gameSource == GameVersion.RBY; - var maxSpecies = rby ? Legal.MaxSpeciesID_1 : Legal.MaxSpeciesID_2; - - bool hasOriginMet; - int maxLevel, minLevel; - if (pkm is ICaughtData2 pk2) - { - hasOriginMet = pk2.CaughtData != 0; - maxLevel = rby && Future_LevelUp2.Contains(pkm.Species) ? pkm.CurrentLevel - 1 : pkm.CurrentLevel; - minLevel = !hasOriginMet ? 2 : pkm.IsEgg ? 5 : pk2.Met_Level; - } - else if (pkm is PK1 pk1) - { - hasOriginMet = false; - maxLevel = pk1.CurrentLevel; - minLevel = 2; - } - else if (rby) - { - hasOriginMet = false; - maxLevel = Future_LevelUp2.Contains(pkm.Species) ? pkm.CurrentLevel - 1 : GetLevelOriginMaxTransfer(pkm, pkm.Met_Level, 1); - minLevel = 2; - } - else // GSC - { - hasOriginMet = false; - maxLevel = GetLevelOriginMaxTransfer(pkm, pkm.Met_Level, 2); - minLevel = 2; - } - - return GetOriginChain(pkm, maxSpecies, (byte)maxLevel, (byte)minLevel, hasOriginMet); - } - - private static EvoCriteria[] GetOriginChain(PKM pkm, int maxSpecies, byte maxLevel, byte minLevel, bool hasOriginMet) - { - if (maxLevel < minLevel) - return Array.Empty(); - - if (hasOriginMet) - return EvolutionChain.GetValidPreEvolutions(pkm, maxSpecies, maxLevel, minLevel); - - // Permit the maximum to be all the way up to Current Level; we'll trim these impossible evolutions out later. - var tempMax = pkm.CurrentLevel; - var chain = EvolutionChain.GetValidPreEvolutions(pkm, maxSpecies, tempMax, minLevel); - - for (var i = 0; i < chain.Length; i++) - chain[i] = chain[i] with { LevelMax = maxLevel, LevelMin = minLevel }; - - return chain; - } - - private static int GetLevelOriginMin(PKM pkm, bool hasMet) - { - if (pkm.Format == 3) - { - if (pkm.IsEgg) - return 5; - return Math.Max(2, pkm.Met_Level); - } - if (!hasMet) - return 1; - return Math.Max(1, pkm.Met_Level); - } - - private static int GetLevelOriginMax(PKM pkm, bool hasMet) - { - var met = pkm.Met_Level; - if (hasMet) - return pkm.CurrentLevel; - - int generation = pkm.Generation; - if (generation >= 4) - return met; - - var downLevel = GetLevelOriginMaxTransfer(pkm, pkm.CurrentLevel, generation); - return Math.Min(met, downLevel); - } - - private static int GetLevelOriginMaxTransfer(PKM pkm, int met, int generation) - { - var species = pkm.Species; - - if (Future_LevelUp.TryGetValue(species | (pkm.Form << 11), out var delta)) - return met - delta; - - if (generation < 4 && Future_LevelUp4.Contains(species) && (pkm.Format <= 7 || !Future_LevelUp4_Not8.Contains(species))) - return met - 1; - - return met; - } - - /// - /// Species introduced in Generation 2 that require a level up to evolve into from a specimen that originated in a previous generation. - /// - private static readonly HashSet Future_LevelUp2 = new() - { - (int)Crobat, - (int)Espeon, - (int)Umbreon, - (int)Blissey, - }; - - /// - /// Species introduced in Generation 4 that require a level up to evolve into from a specimen that originated in a previous generation. - /// - private static readonly HashSet Future_LevelUp4 = new() - { - (int)Ambipom, - (int)Weavile, - (int)Magnezone, - (int)Lickilicky, - (int)Tangrowth, - (int)Yanmega, - (int)Leafeon, - (int)Glaceon, - (int)Mamoswine, - (int)Gliscor, - (int)Probopass, - }; - - /// - /// Species introduced in Generation 4 that used to require a level up to evolve prior to Generation 8. - /// - private static readonly HashSet Future_LevelUp4_Not8 = new() - { - (int)Magnezone, // Thunder Stone - (int)Leafeon, // Leaf Stone - (int)Glaceon, // Ice Stone - }; - - /// - /// Species introduced in Generation 6+ that require a level up to evolve into from a specimen that originated in a previous generation. - /// - private static readonly Dictionary Future_LevelUp = new() - { - // Gen6 - {(int)Sylveon, 1}, - - // Gen7 - {(int)Marowak | (1 << 11), 1}, - - // Gen8 - {(int)Weezing | (1 << 11), 1}, - {(int)MrMime | (1 << 11), 1}, - {(int)MrRime, 2}, - }; + bool hasOriginMet = pk.HasOriginalMetLocation; + var maxLevel = GetLevelOriginMax(pk, hasOriginMet); + var minLevel = GetLevelOriginMin(pk, hasOriginMet); + return GetOriginChain(pk, -1, (byte)maxLevel, (byte)minLevel, hasOriginMet); } + + /// + /// Gets possible evolution details for the input originating from Generation 1 or 2. + /// + /// Current state of the Pokémon + /// Game/group the originated from. If , it assumes Gen 1, otherwise Gen 2. + /// Possible origin species-form-levels to match against encounter data. + public static EvoCriteria[] GetOriginChain12(PKM pk, GameVersion gameSource) + { + bool rby = gameSource == GameVersion.RBY; + var maxSpecies = rby ? Legal.MaxSpeciesID_1 : Legal.MaxSpeciesID_2; + + bool hasOriginMet; + int maxLevel, minLevel; + if (pk is ICaughtData2 pk2) + { + hasOriginMet = pk2.CaughtData != 0; + maxLevel = rby && Future_LevelUp2.Contains(pk.Species) ? pk.CurrentLevel - 1 : pk.CurrentLevel; + minLevel = !hasOriginMet ? 2 : pk.IsEgg ? 5 : pk2.Met_Level; + } + else if (pk is PK1 pk1) + { + hasOriginMet = false; + maxLevel = pk1.CurrentLevel; + minLevel = 2; + } + else if (rby) + { + hasOriginMet = false; + maxLevel = Future_LevelUp2.Contains(pk.Species) ? pk.CurrentLevel - 1 : GetLevelOriginMaxTransfer(pk, pk.Met_Level, 1); + minLevel = 2; + } + else // GSC + { + hasOriginMet = false; + maxLevel = GetLevelOriginMaxTransfer(pk, pk.Met_Level, 2); + minLevel = 2; + } + + return GetOriginChain(pk, maxSpecies, (byte)maxLevel, (byte)minLevel, hasOriginMet); + } + + private static EvoCriteria[] GetOriginChain(PKM pk, int maxSpecies, byte maxLevel, byte minLevel, bool hasOriginMet) + { + if (maxLevel < minLevel) + return Array.Empty(); + + if (hasOriginMet) + return EvolutionChain.GetValidPreEvolutions(pk, maxSpecies, maxLevel, minLevel); + + // Permit the maximum to be all the way up to Current Level; we'll trim these impossible evolutions out later. + var tempMax = pk.CurrentLevel; + var chain = EvolutionChain.GetValidPreEvolutions(pk, maxSpecies, tempMax, minLevel); + + for (var i = 0; i < chain.Length; i++) + chain[i] = chain[i] with { LevelMax = maxLevel, LevelMin = minLevel }; + + return chain; + } + + private static int GetLevelOriginMin(PKM pk, bool hasMet) + { + if (pk.Format == 3) + { + if (pk.IsEgg) + return 5; + return Math.Max(2, pk.Met_Level); + } + if (!hasMet) + return 1; + return Math.Max(1, pk.Met_Level); + } + + private static int GetLevelOriginMax(PKM pk, bool hasMet) + { + var met = pk.Met_Level; + if (hasMet) + return pk.CurrentLevel; + + int generation = pk.Generation; + if (generation >= 4) + return met; + + var downLevel = GetLevelOriginMaxTransfer(pk, pk.CurrentLevel, generation); + return Math.Min(met, downLevel); + } + + private static int GetLevelOriginMaxTransfer(PKM pk, int met, int generation) + { + var species = pk.Species; + + if (Future_LevelUp.TryGetValue(species | (pk.Form << 11), out var delta)) + return met - delta; + + if (generation < 4 && Future_LevelUp4.Contains(species) && (pk.Format <= 7 || !Future_LevelUp4_Not8.Contains(species))) + return met - 1; + + return met; + } + + /// + /// Species introduced in Generation 2 that require a level up to evolve into from a specimen that originated in a previous generation. + /// + private static readonly HashSet Future_LevelUp2 = new() + { + (int)Crobat, + (int)Espeon, + (int)Umbreon, + (int)Blissey, + }; + + /// + /// Species introduced in Generation 4 that require a level up to evolve into from a specimen that originated in a previous generation. + /// + private static readonly HashSet Future_LevelUp4 = new() + { + (int)Ambipom, + (int)Weavile, + (int)Magnezone, + (int)Lickilicky, + (int)Tangrowth, + (int)Yanmega, + (int)Leafeon, + (int)Glaceon, + (int)Mamoswine, + (int)Gliscor, + (int)Probopass, + }; + + /// + /// Species introduced in Generation 4 that used to require a level up to evolve prior to Generation 8. + /// + private static readonly HashSet Future_LevelUp4_Not8 = new() + { + (int)Magnezone, // Thunder Stone + (int)Leafeon, // Leaf Stone + (int)Glaceon, // Ice Stone + }; + + /// + /// Species introduced in Generation 6+ that require a level up to evolve into from a specimen that originated in a previous generation. + /// + private static readonly Dictionary Future_LevelUp = new() + { + // Gen6 + {(int)Sylveon, 1}, + + // Gen7 + {(int)Marowak | (1 << 11), 1}, + + // Gen8 + {(int)Weezing | (1 << 11), 1}, + {(int)MrMime | (1 << 11), 1}, + {(int)MrRime, 2}, + }; } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionChain.cs b/PKHeX.Core/Legality/Evolutions/EvolutionChain.cs index 07908f360..5e49fe53e 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionChain.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionChain.cs @@ -2,321 +2,320 @@ using static PKHeX.Core.Legal; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class EvolutionChain { - public static class EvolutionChain + internal static EvolutionHistory GetEvolutionChainsAllGens(PKM pk, IEncounterTemplate enc) { - internal static EvolutionHistory GetEvolutionChainsAllGens(PKM pkm, IEncounterTemplate enc) + var chain = GetEvolutionChain(pk, enc, pk.Species, (byte)pk.CurrentLevel); + if (chain.Length == 0 || pk.IsEgg || enc is EncounterInvalid) + return GetChainSingle(pk, chain); + + return GetChainAll(pk, enc, chain); + } + + private static EvolutionHistory GetChainSingle(PKM pk, EvoCriteria[] fullChain) + { + var count = Math.Max(2, pk.Format) + 1; + return new EvolutionHistory(fullChain, count) { - var chain = GetEvolutionChain(pkm, enc, pkm.Species, (byte)pkm.CurrentLevel); - if (chain.Length == 0 || pkm.IsEgg || enc is EncounterInvalid) - return GetChainSingle(pkm, chain); - - return GetChainAll(pkm, enc, chain); - } - - private static EvolutionHistory GetChainSingle(PKM pkm, EvoCriteria[] fullChain) - { - var count = Math.Max(2, pkm.Format) + 1; - return new EvolutionHistory(fullChain, count) - { - [pkm.Format] = fullChain, - }; - } - - private static EvolutionHistory GetChainAll(PKM pkm, IEncounterTemplate enc, EvoCriteria[] fullChain) - { - int maxgen = ParseSettings.AllowGen1Tradeback && pkm.Context == EntityContext.Gen1 ? 2 : pkm.Format; - var GensEvoChains = new EvolutionHistory(fullChain, maxgen + 1); - - var head = 0; // inlined FIFO queue indexing - var mostEvolved = fullChain[head++]; - - var lvl = (byte)pkm.CurrentLevel; - var maxLevel = lvl; - - // Iterate generations backwards - // Maximum level of an earlier generation (GenX) will never be greater than a later generation (GenX+Y). - int mingen = enc.Generation; - if (mingen is 1 or 2) - mingen = GBRestrictions.GetTradebackStatusInitial(pkm) == PotentialGBOrigin.Gen2Only ? 2 : 1; - - bool noxfrDecremented = true; - for (int g = GensEvoChains.Length - 1; g >= mingen; g--) - { - if (g == 6 && enc.Generation < 3) - g = 2; // skip over 6543 as it never existed in these. - - if (g <= 4 && pkm.Format > 2 && pkm.Format > g && !pkm.HasOriginalMetLocation) - { - // Met location was lost at this point but it also means the pokemon existed in generations 1 to 4 with maximum level equals to met level - var met = pkm.Met_Level; - if (lvl > pkm.Met_Level) - lvl = (byte)met; - } - - int maxspeciesgen = g == 2 && pkm.VC1 ? MaxSpeciesID_1 : GetMaxSpeciesOrigin(g); - - // Remove future gen evolutions after a few special considerations: - // If the pokemon origin is illegal (e.g. Gen3 Infernape) the list will be emptied -- species lineage did not exist at any evolution stage. - while (mostEvolved.Species > maxspeciesgen) - { - if (head >= fullChain.Length) - { - if (g <= 2 && pkm.VC1) - GensEvoChains.Invalidate(); // invalidate here since we haven't reached the regular invalidation - return GensEvoChains; - } - if (mostEvolved.RequiresLvlUp) - ReviseMaxLevel(ref lvl, pkm, g, maxLevel); - - mostEvolved = fullChain[head++]; - } - - // Alolan form evolutions, remove from gens 1-6 chains - if (g < 7 && HasAlolanForm(mostEvolved.Species) && pkm.Format >= 7 && mostEvolved.Form > 0) - { - if (head >= fullChain.Length) - return GensEvoChains; - mostEvolved = fullChain[head++]; - } - - var tmp = GetEvolutionChain(pkm, enc, mostEvolved.Species, lvl); - if (tmp.Length == 0) - continue; - - GensEvoChains[g] = tmp; - if (g == 1) - { - CleanGen1(pkm, enc, GensEvoChains); - continue; - } - - if (g >= 3 && !pkm.HasOriginalMetLocation && g >= enc.Generation && noxfrDecremented) - { - bool isTransferred = HasMetLocationUpdatedTransfer(enc.Generation, g); - if (!isTransferred) - continue; - - noxfrDecremented = g > (enc.Generation != 3 ? 4 : 5); - - // Remove previous evolutions below transfer level - // For example a gen3 Charizard in format 7 with current level 36 and met level 36, thus could never be Charmander / Charmeleon in Gen5+. - // chain level for Charmander is 35, is below met level. - int minlvl = GetMinLevelGeneration(pkm, g); - - ref var genChain = ref GensEvoChains[g]; - int minIndex = Array.FindIndex(genChain, e => e.LevelMax >= minlvl); - if (minIndex != -1) - genChain = genChain.AsSpan(minIndex).ToArray(); - } - } - return GensEvoChains; - } - - private static void ReviseMaxLevel(ref byte lvl, PKM pkm, int g, byte maxLevel) - { - // This is a Gen3 pokemon in a Gen4 phase evolution that requires level up and then transferred to Gen5+ - // We can deduce that it existed in Gen4 until met level, - // but if current level is met level we can also deduce it existed in Gen3 until maximum met level -1 - if (g == 3 && pkm.Format > 4 && lvl == maxLevel) - lvl--; - - // The same condition for Gen2 evolution of Gen1 pokemon, level of the pokemon in Gen1 games would be CurrentLevel -1 one level below Gen2 level - else if (g == 1 && pkm.Format == 2 && lvl == maxLevel) - lvl--; - } - - private static void CleanGen1(PKM pkm, IEncounterTemplate enc, EvolutionHistory chains) - { - // Remove Gen7 pre-evolutions and chain break scenarios - if (pkm.VC1) - { - var index = Array.FindLastIndex(chains.Gen7, z => z.Species <= MaxSpeciesID_1); - if (index == -1) - { - chains.Invalidate(); // needed a Gen1 species present; invalidate the chain. - return; - } - } - - TrimSpeciesAbove(enc, MaxSpeciesID_1, ref chains.Gen1); - } - - private static void TrimSpeciesAbove(IEncounterTemplate enc, int species, ref EvoCriteria[] chain) - { - var span = chain.AsSpan(); - - // Remove post-evolutions - if (span[0].Species > species) - { - if (span.Length == 1) - { - chain = Array.Empty(); - return; - } - - span = span[1..]; - } - - // Remove pre-evolutions - if (span[^1].Species > species) - { - if (span.Length == 1) - { - chain = Array.Empty(); - return; - } - - span = span[..^1]; - } - - if (span.Length != chain.Length) - chain = span.ToArray(); - - // Update min level for the encounter to prevent certain level up moves. - if (span.Length != 0) - { - ref var last = ref span[^1]; - last = last with { LevelMin = enc.LevelMin }; - } - } - - private static bool HasMetLocationUpdatedTransfer(int originalGeneration, int currentGeneration) => originalGeneration switch - { - < 3 => currentGeneration >= 3, - <= 4 => currentGeneration != originalGeneration, - _ => false, + [pk.Format] = fullChain, }; + } - private static EvoCriteria[] GetEvolutionChain(PKM pkm, IEncounterTemplate enc, int mostEvolvedSpecies, byte maxlevel) + private static EvolutionHistory GetChainAll(PKM pk, IEncounterTemplate enc, EvoCriteria[] fullChain) + { + int maxgen = ParseSettings.AllowGen1Tradeback && pk.Context == EntityContext.Gen1 ? 2 : pk.Format; + var GensEvoChains = new EvolutionHistory(fullChain, maxgen + 1); + + var head = 0; // inlined FIFO queue indexing + var mostEvolved = fullChain[head++]; + + var lvl = (byte)pk.CurrentLevel; + var maxLevel = lvl; + + // Iterate generations backwards + // Maximum level of an earlier generation (GenX) will never be greater than a later generation (GenX+Y). + int mingen = enc.Generation; + if (mingen is 1 or 2) + mingen = GBRestrictions.GetTradebackStatusInitial(pk) == PotentialGBOrigin.Gen2Only ? 2 : 1; + + bool noxfrDecremented = true; + for (int g = GensEvoChains.Length - 1; g >= mingen; g--) { - int min = enc.LevelMin; - if (pkm.HasOriginalMetLocation && pkm.Met_Level != 0) - min = pkm.Met_Level; + if (g == 6 && enc.Generation < 3) + g = 2; // skip over 6543 as it never existed in these. - var chain = GetValidPreEvolutions(pkm, minLevel: min); - return TrimChain(chain, enc, mostEvolvedSpecies, maxlevel); - } - - private static EvoCriteria[] TrimChain(EvoCriteria[] chain, IEncounterTemplate enc, int mostEvolvedSpecies, byte maxlevel) - { - if (enc.Species == mostEvolvedSpecies) - return TrimChainSingle(chain, enc); - - // Evolution chain is in reverse order (devolution) - // Find the index of the minimum species to determine the end of the chain - int minIndex = Array.FindLastIndex(chain, z => z.Species == enc.Species); - bool last = minIndex < 0 || minIndex == chain.Length - 1; - - // If we remove a pre-evolution, update the chain if appropriate. - if (!last) + if (g <= 4 && pk.Format > 2 && pk.Format > g && !pk.HasOriginalMetLocation) { - // Remove chain species after the encounter - if (minIndex + 1 == chain.Length) - return Array.Empty(); // no species left in chain - - chain = chain.AsSpan(0, minIndex + 1).ToArray(); - CheckLastEncounterRemoval(enc, chain); + // Met location was lost at this point but it also means the pokemon existed in generations 1 to 4 with maximum level equals to met level + var met = pk.Met_Level; + if (lvl > pk.Met_Level) + lvl = (byte)met; } - return TrimChainMore(chain, mostEvolvedSpecies, maxlevel); - } + int maxspeciesgen = g == 2 && pk.VC1 ? MaxSpeciesID_1 : GetMaxSpeciesOrigin(g); - private static EvoCriteria[] TrimChainMore(EvoCriteria[] chain, int mostEvolvedSpecies, byte maxlevel) - { - // maxspec is used to remove future geneneration evolutions, to gather evolution chain of a pokemon in previous generations - var maxSpeciesIndex = Array.FindIndex(chain, z => z.Species == mostEvolvedSpecies); - if (maxSpeciesIndex > 0) - chain = chain.AsSpan(maxSpeciesIndex).ToArray(); - - // Gen3->4 and Gen4->5 transfer sets the Met Level property to the Pokémon's current level. - // Removes evolutions impossible before the transfer level. - // For example a FireRed Charizard with a current level (in XY) is 50 but Met Level is 20; it couldn't be a Charizard in Gen3 and Gen4 games - var clampIndex = Array.FindIndex(chain, z => z.LevelMin > maxlevel); - if (clampIndex != -1) - chain = Array.FindAll(chain, z => z.LevelMin <= maxlevel); - - // Reduce the evolution chain levels to max level to limit any later analysis/results. - SanitizeMaxLevel(chain, maxlevel); - - return chain; - } - - private static void SanitizeMaxLevel(EvoCriteria[] chain, byte maxlevel) - { - for (var i = 0; i < chain.Length; i++) + // Remove future gen evolutions after a few special considerations: + // If the pokemon origin is illegal (e.g. Gen3 Infernape) the list will be emptied -- species lineage did not exist at any evolution stage. + while (mostEvolved.Species > maxspeciesgen) { - ref var c = ref chain[i]; - c = c with { LevelMax = Math.Min(c.LevelMax, maxlevel) }; + if (head >= fullChain.Length) + { + if (g <= 2 && pk.VC1) + GensEvoChains.Invalidate(); // invalidate here since we haven't reached the regular invalidation + return GensEvoChains; + } + if (mostEvolved.RequiresLvlUp) + ReviseMaxLevel(ref lvl, pk, g, maxLevel); + + mostEvolved = fullChain[head++]; + } + + // Alolan form evolutions, remove from gens 1-6 chains + if (g < 7 && HasAlolanForm(mostEvolved.Species) && pk.Format >= 7 && mostEvolved.Form > 0) + { + if (head >= fullChain.Length) + return GensEvoChains; + mostEvolved = fullChain[head++]; + } + + var tmp = GetEvolutionChain(pk, enc, mostEvolved.Species, lvl); + if (tmp.Length == 0) + continue; + + GensEvoChains[g] = tmp; + if (g == 1) + { + CleanGen1(pk, enc, GensEvoChains); + continue; + } + + if (g >= 3 && !pk.HasOriginalMetLocation && g >= enc.Generation && noxfrDecremented) + { + bool isTransferred = HasMetLocationUpdatedTransfer(enc.Generation, g); + if (!isTransferred) + continue; + + noxfrDecremented = g > (enc.Generation != 3 ? 4 : 5); + + // Remove previous evolutions below transfer level + // For example a gen3 Charizard in format 7 with current level 36 and met level 36, thus could never be Charmander / Charmeleon in Gen5+. + // chain level for Charmander is 35, is below met level. + int minlvl = GetMinLevelGeneration(pk, g); + + ref var genChain = ref GensEvoChains[g]; + int minIndex = Array.FindIndex(genChain, e => e.LevelMax >= minlvl); + if (minIndex != -1) + genChain = genChain.AsSpan(minIndex).ToArray(); } } + return GensEvoChains; + } - private static EvoCriteria[] TrimChainSingle(EvoCriteria[] chain, IEncounterTemplate enc) + private static void ReviseMaxLevel(ref byte lvl, PKM pk, int g, byte maxLevel) + { + // This is a Gen3 pokemon in a Gen4 phase evolution that requires level up and then transferred to Gen5+ + // We can deduce that it existed in Gen4 until met level, + // but if current level is met level we can also deduce it existed in Gen3 until maximum met level -1 + if (g == 3 && pk.Format > 4 && lvl == maxLevel) + lvl--; + + // The same condition for Gen2 evolution of Gen1 pokemon, level of the pokemon in Gen1 games would be CurrentLevel -1 one level below Gen2 level + else if (g == 1 && pk.Format == 2 && lvl == maxLevel) + lvl--; + } + + private static void CleanGen1(PKM pk, IEncounterTemplate enc, EvolutionHistory chains) + { + // Remove Gen7 pre-evolutions and chain break scenarios + if (pk.VC1) { - if (chain.Length == 1) - return chain; - var index = Array.FindLastIndex(chain, z => z.Species == enc.Species); + var index = Array.FindLastIndex(chains.Gen7, z => z.Species <= MaxSpeciesID_1); if (index == -1) - return Array.Empty(); - return new[] { chain[index] }; - } - - private static void CheckLastEncounterRemoval(IEncounterTemplate enc, EvoCriteria[] chain) - { - // Last entry from chain is removed, turn next entry into the encountered Pokémon - ref var last = ref chain[^1]; - last = last with { LevelMin = enc.LevelMin, LevelUpRequired = 1 }; - - ref var first = ref chain[0]; - if (first.RequiresLvlUp) + { + chains.Invalidate(); // needed a Gen1 species present; invalidate the chain. return; - - if (first.LevelMin == 2) - { - // Example: Raichu in Gen2 or later - // Because Pichu requires a level up, the minimum level of the resulting Raichu must be be >2 - // But after removing Pichu (because the origin species is Pikachu), the Raichu minimum level should be 1. - first = first with { LevelMin = 1, LevelUpRequired = 0 }; - } - else // in-game trade or evolution stone can evolve immediately - { - first = first with { LevelMin = enc.LevelMin }; } } - internal static EvoCriteria[] GetValidPreEvolutions(PKM pkm, int maxspeciesorigin = -1, int maxLevel = -1, int minLevel = 1, bool skipChecks = false) + TrimSpeciesAbove(enc, MaxSpeciesID_1, ref chains.Gen1); + } + + private static void TrimSpeciesAbove(IEncounterTemplate enc, int species, ref EvoCriteria[] chain) + { + var span = chain.AsSpan(); + + // Remove post-evolutions + if (span[0].Species > species) { - if (maxLevel < 0) - maxLevel = pkm.CurrentLevel; + if (span.Length == 1) + { + chain = Array.Empty(); + return; + } - if (maxspeciesorigin == -1 && pkm.InhabitedGeneration(2) && pkm.Format <= 2 && pkm.Generation == 1) - maxspeciesorigin = MaxSpeciesID_2; - - var context = pkm.Context; - if (context < EntityContext.Gen2) - context = EntityContext.Gen2; - var et = EvolutionTree.GetEvolutionTree(context); - return et.GetValidPreEvolutions(pkm, maxLevel: (byte)maxLevel, maxSpeciesOrigin: maxspeciesorigin, skipChecks: skipChecks, minLevel: (byte)minLevel); + span = span[1..]; } - private static int GetMinLevelGeneration(PKM pkm, int generation) + // Remove pre-evolutions + if (span[^1].Species > species) { - if (!pkm.InhabitedGeneration(generation)) - return 0; + if (span.Length == 1) + { + chain = Array.Empty(); + return; + } - if (pkm.Format <= 2) - return 2; + span = span[..^1]; + } - var origin = pkm.Generation; - if (!pkm.HasOriginalMetLocation && generation != origin) - return pkm.Met_Level; + if (span.Length != chain.Length) + chain = span.ToArray(); - // gen 3 and prior can't obtain anything at level 1 - if (origin <= 3) - return 2; - - return 1; + // Update min level for the encounter to prevent certain level up moves. + if (span.Length != 0) + { + ref var last = ref span[^1]; + last = last with { LevelMin = enc.LevelMin }; } } + + private static bool HasMetLocationUpdatedTransfer(int originalGeneration, int currentGeneration) => originalGeneration switch + { + < 3 => currentGeneration >= 3, + <= 4 => currentGeneration != originalGeneration, + _ => false, + }; + + private static EvoCriteria[] GetEvolutionChain(PKM pk, IEncounterTemplate enc, int mostEvolvedSpecies, byte maxlevel) + { + int min = enc.LevelMin; + if (pk.HasOriginalMetLocation && pk.Met_Level != 0) + min = pk.Met_Level; + + var chain = GetValidPreEvolutions(pk, minLevel: min); + return TrimChain(chain, enc, mostEvolvedSpecies, maxlevel); + } + + private static EvoCriteria[] TrimChain(EvoCriteria[] chain, IEncounterTemplate enc, int mostEvolvedSpecies, byte maxlevel) + { + if (enc.Species == mostEvolvedSpecies) + return TrimChainSingle(chain, enc); + + // Evolution chain is in reverse order (devolution) + // Find the index of the minimum species to determine the end of the chain + int minIndex = Array.FindLastIndex(chain, z => z.Species == enc.Species); + bool last = minIndex < 0 || minIndex == chain.Length - 1; + + // If we remove a pre-evolution, update the chain if appropriate. + if (!last) + { + // Remove chain species after the encounter + if (minIndex + 1 == chain.Length) + return Array.Empty(); // no species left in chain + + chain = chain.AsSpan(0, minIndex + 1).ToArray(); + CheckLastEncounterRemoval(enc, chain); + } + + return TrimChainMore(chain, mostEvolvedSpecies, maxlevel); + } + + private static EvoCriteria[] TrimChainMore(EvoCriteria[] chain, int mostEvolvedSpecies, byte maxlevel) + { + // maxspec is used to remove future geneneration evolutions, to gather evolution chain of a pokemon in previous generations + var maxSpeciesIndex = Array.FindIndex(chain, z => z.Species == mostEvolvedSpecies); + if (maxSpeciesIndex > 0) + chain = chain.AsSpan(maxSpeciesIndex).ToArray(); + + // Gen3->4 and Gen4->5 transfer sets the Met Level property to the Pokémon's current level. + // Removes evolutions impossible before the transfer level. + // For example a FireRed Charizard with a current level (in XY) is 50 but Met Level is 20; it couldn't be a Charizard in Gen3 and Gen4 games + var clampIndex = Array.FindIndex(chain, z => z.LevelMin > maxlevel); + if (clampIndex != -1) + chain = Array.FindAll(chain, z => z.LevelMin <= maxlevel); + + // Reduce the evolution chain levels to max level to limit any later analysis/results. + SanitizeMaxLevel(chain, maxlevel); + + return chain; + } + + private static void SanitizeMaxLevel(EvoCriteria[] chain, byte maxlevel) + { + for (var i = 0; i < chain.Length; i++) + { + ref var c = ref chain[i]; + c = c with { LevelMax = Math.Min(c.LevelMax, maxlevel) }; + } + } + + private static EvoCriteria[] TrimChainSingle(EvoCriteria[] chain, IEncounterTemplate enc) + { + if (chain.Length == 1) + return chain; + var index = Array.FindLastIndex(chain, z => z.Species == enc.Species); + if (index == -1) + return Array.Empty(); + return new[] { chain[index] }; + } + + private static void CheckLastEncounterRemoval(IEncounterTemplate enc, EvoCriteria[] chain) + { + // Last entry from chain is removed, turn next entry into the encountered Pokémon + ref var last = ref chain[^1]; + last = last with { LevelMin = enc.LevelMin, LevelUpRequired = 1 }; + + ref var first = ref chain[0]; + if (first.RequiresLvlUp) + return; + + if (first.LevelMin == 2) + { + // Example: Raichu in Gen2 or later + // Because Pichu requires a level up, the minimum level of the resulting Raichu must be be >2 + // But after removing Pichu (because the origin species is Pikachu), the Raichu minimum level should be 1. + first = first with { LevelMin = 1, LevelUpRequired = 0 }; + } + else // in-game trade or evolution stone can evolve immediately + { + first = first with { LevelMin = enc.LevelMin }; + } + } + + internal static EvoCriteria[] GetValidPreEvolutions(PKM pk, int maxspeciesorigin = -1, int maxLevel = -1, int minLevel = 1, bool skipChecks = false) + { + if (maxLevel < 0) + maxLevel = pk.CurrentLevel; + + if (maxspeciesorigin == -1 && pk.InhabitedGeneration(2) && pk.Format <= 2 && pk.Generation == 1) + maxspeciesorigin = MaxSpeciesID_2; + + var context = pk.Context; + if (context < EntityContext.Gen2) + context = EntityContext.Gen2; + var et = EvolutionTree.GetEvolutionTree(context); + return et.GetValidPreEvolutions(pk, maxLevel: (byte)maxLevel, maxSpeciesOrigin: maxspeciesorigin, skipChecks: skipChecks, minLevel: (byte)minLevel); + } + + private static int GetMinLevelGeneration(PKM pk, int generation) + { + if (!pk.InhabitedGeneration(generation)) + return 0; + + if (pk.Format <= 2) + return 2; + + var origin = pk.Generation; + if (!pk.HasOriginalMetLocation && generation != origin) + return pk.Met_Level; + + // gen 3 and prior can't obtain anything at level 1 + if (origin <= 3) + return 2; + + return 1; + } } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs b/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs index 52c94651a..7e919c37f 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionHistory.cs @@ -44,7 +44,7 @@ public EvolutionHistory(EvoCriteria[] fullChain, int count) if (index == 6) return ref Gen6; if (index == 7) return ref Gen7; if (index == 8) return ref Gen8; - throw new IndexOutOfRangeException(nameof(index)); + throw new ArgumentOutOfRangeException(nameof(index)); } } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionLegality.cs b/PKHeX.Core/Legality/Evolutions/EvolutionLegality.cs index 207043e26..fa1f80f93 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionLegality.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionLegality.cs @@ -1,108 +1,107 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class EvolutionLegality { - internal static class EvolutionLegality + internal static readonly HashSet FutureEvolutionsGen1 = new() { - internal static readonly HashSet FutureEvolutionsGen1 = new() - { - (int)Species.Crobat, - (int)Species.Bellossom, - (int)Species.Politoed, - (int)Species.Espeon, - (int)Species.Umbreon, - (int)Species.Slowking, - (int)Species.Steelix, - (int)Species.Scizor, - (int)Species.Kingdra, - (int)Species.Porygon2, - (int)Species.Blissey, + (int)Species.Crobat, + (int)Species.Bellossom, + (int)Species.Politoed, + (int)Species.Espeon, + (int)Species.Umbreon, + (int)Species.Slowking, + (int)Species.Steelix, + (int)Species.Scizor, + (int)Species.Kingdra, + (int)Species.Porygon2, + (int)Species.Blissey, - (int)Species.Magnezone, - (int)Species.Lickilicky, - (int)Species.Rhyperior, - (int)Species.Tangrowth, - (int)Species.Electivire, - (int)Species.Magmortar, - (int)Species.Leafeon, - (int)Species.Glaceon, - (int)Species.PorygonZ, + (int)Species.Magnezone, + (int)Species.Lickilicky, + (int)Species.Rhyperior, + (int)Species.Tangrowth, + (int)Species.Electivire, + (int)Species.Magmortar, + (int)Species.Leafeon, + (int)Species.Glaceon, + (int)Species.PorygonZ, - (int)Species.Sylveon, + (int)Species.Sylveon, - (int)Species.Kleavor, - }; + (int)Species.Kleavor, + }; - private static readonly HashSet FutureEvolutionsGen2 = new() - { - (int)Species.Ambipom, - (int)Species.Mismagius, - (int)Species.Honchkrow, - (int)Species.Weavile, - (int)Species.Magnezone, - (int)Species.Lickilicky, - (int)Species.Rhyperior, - (int)Species.Tangrowth, - (int)Species.Electivire, - (int)Species.Magmortar, - (int)Species.Togekiss, - (int)Species.Yanmega, - (int)Species.Leafeon, - (int)Species.Glaceon, - (int)Species.Gliscor, - (int)Species.Mamoswine, - (int)Species.PorygonZ, + private static readonly HashSet FutureEvolutionsGen2 = new() + { + (int)Species.Ambipom, + (int)Species.Mismagius, + (int)Species.Honchkrow, + (int)Species.Weavile, + (int)Species.Magnezone, + (int)Species.Lickilicky, + (int)Species.Rhyperior, + (int)Species.Tangrowth, + (int)Species.Electivire, + (int)Species.Magmortar, + (int)Species.Togekiss, + (int)Species.Yanmega, + (int)Species.Leafeon, + (int)Species.Glaceon, + (int)Species.Gliscor, + (int)Species.Mamoswine, + (int)Species.PorygonZ, - (int)Species.Sylveon, + (int)Species.Sylveon, - (int)Species.Wyrdeer, - (int)Species.Ursaluna, - }; + (int)Species.Wyrdeer, + (int)Species.Ursaluna, + }; - private static readonly HashSet FutureEvolutionsGen3 = new() - { - (int)Species.Roserade, - (int)Species.Ambipom, - (int)Species.Mismagius, - (int)Species.Honchkrow, - (int)Species.Weavile, - (int)Species.Magnezone, - (int)Species.Lickilicky, - (int)Species.Rhyperior, - (int)Species.Tangrowth, - (int)Species.Electivire, - (int)Species.Magmortar, - (int)Species.Togekiss, - (int)Species.Yanmega, - (int)Species.Leafeon, - (int)Species.Glaceon, - (int)Species.Gliscor, - (int)Species.Mamoswine, - (int)Species.PorygonZ, - (int)Species.Gallade, - (int)Species.Probopass, - (int)Species.Dusknoir, - (int)Species.Froslass, + private static readonly HashSet FutureEvolutionsGen3 = new() + { + (int)Species.Roserade, + (int)Species.Ambipom, + (int)Species.Mismagius, + (int)Species.Honchkrow, + (int)Species.Weavile, + (int)Species.Magnezone, + (int)Species.Lickilicky, + (int)Species.Rhyperior, + (int)Species.Tangrowth, + (int)Species.Electivire, + (int)Species.Magmortar, + (int)Species.Togekiss, + (int)Species.Yanmega, + (int)Species.Leafeon, + (int)Species.Glaceon, + (int)Species.Gliscor, + (int)Species.Mamoswine, + (int)Species.PorygonZ, + (int)Species.Gallade, + (int)Species.Probopass, + (int)Species.Dusknoir, + (int)Species.Froslass, - (int)Species.Sylveon, - }; + (int)Species.Sylveon, + }; - private static readonly int[] FutureEvolutionsGen4 = - { - (int)Species.Sylveon, - }; + private static readonly int[] FutureEvolutionsGen4 = + { + (int)Species.Sylveon, + }; - private static readonly int[] FutureEvolutionsGen5 = FutureEvolutionsGen4; + private static readonly int[] FutureEvolutionsGen5 = FutureEvolutionsGen4; - internal static ICollection GetFutureGenEvolutions(int generation) => generation switch - { - 1 => FutureEvolutionsGen1, - 2 => FutureEvolutionsGen2, - 3 => FutureEvolutionsGen3, - 4 => FutureEvolutionsGen4, - 5 => FutureEvolutionsGen5, - _ => Array.Empty(), - }; - } + internal static ICollection GetFutureGenEvolutions(int generation) => generation switch + { + 1 => FutureEvolutionsGen1, + 2 => FutureEvolutionsGen2, + 3 => FutureEvolutionsGen3, + 4 => FutureEvolutionsGen4, + 5 => FutureEvolutionsGen5, + _ => Array.Empty(), + }; } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionMethod.cs b/PKHeX.Core/Legality/Evolutions/EvolutionMethod.cs index 4212009f5..898e0549d 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionMethod.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionMethod.cs @@ -1,167 +1,166 @@ using static PKHeX.Core.EvolutionType; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Criteria for evolving to this branch in the +/// +public sealed class EvolutionMethod { /// - /// Criteria for evolving to this branch in the + /// Evolution Method /// - public sealed class EvolutionMethod + public readonly int Method; + + /// + /// Evolve to Species + /// + public readonly int Species; + + /// + /// Conditional Argument (different from ) + /// + public readonly int Argument; + + /// + /// Conditional Argument (different from ) + /// + public readonly byte Level; + + /// + /// Destination Form + /// + /// Is if the evolved form isn't modified. Special consideration for , which forces 1. + public readonly int Form; + + private const int AnyForm = -1; + + // Not stored in binary data + public bool RequiresLevelUp; // tracks if this method requires a Level Up, lazily set + + public EvolutionMethod(int method, int species, int argument = 0, byte level = 0, int form = AnyForm) { - /// - /// Evolution Method - /// - public readonly int Method; + Method = method; + Species = species; + Argument = argument; + Form = form; + Level = level; + } - /// - /// Evolve to Species - /// - public readonly int Species; + public override string ToString() => $"{(Species) Species}-{Form} [{Argument}] @ {Level}{(RequiresLevelUp ? "X" : "")}"; - /// - /// Conditional Argument (different from ) - /// - public readonly int Argument; + /// + /// Returns the form that the Pokmon will have after evolution. + /// + /// Un-evolved Form ID + public int GetDestinationForm(int form) + { + if (Method == (int)LevelUpFormFemale1) + return 1; + if (Form == AnyForm) + return form; + return Form; + } - /// - /// Conditional Argument (different from ) - /// - public readonly byte Level; - - /// - /// Destination Form - /// - /// Is if the evolved form isn't modified. Special consideration for , which forces 1. - public readonly int Form; - - private const int AnyForm = -1; - - // Not stored in binary data - public bool RequiresLevelUp; // tracks if this method requires a Level Up, lazily set - - public EvolutionMethod(int method, int species, int argument = 0, byte level = 0, int form = AnyForm) + /// + /// Checks the for validity by comparing against the data. + /// + /// Entity to check + /// Current level + /// Option to skip some comparisons to return a 'possible' evolution. + /// True if a evolution criteria is valid. + public bool Valid(PKM pk, int lvl, bool skipChecks) + { + RequiresLevelUp = false; + switch ((EvolutionType)Method) { - Method = method; - Species = species; - Argument = argument; - Form = form; - Level = level; - } + case UseItem or UseItemWormhole or UseItemFullMoon: + case CriticalHitsInBattle or HitPointsLostInBattle or Spin: + case UseAgileStyleMoves or UseStrongStyleMoves: + case TowerOfDarkness or TowerOfWaters: + return true; + case UseItemMale or RecoilDamageMale: + return pk.Gender == 0; + case UseItemFemale or RecoilDamageFemale: + return pk.Gender == 1; - public override string ToString() => $"{(Species) Species}-{Form} [{Argument}] @ {Level}{(RequiresLevelUp ? "X" : "")}"; + case Trade or TradeHeldItem or TradeShelmetKarrablast: + return !pk.IsUntraded || skipChecks; - /// - /// Returns the form that the Pokmon will have after evolution. - /// - /// Un-evolved Form ID - public int GetDestinationForm(int form) - { - if (Method == (int)LevelUpFormFemale1) - return 1; - if (Form == AnyForm) - return form; - return Form; - } + // Special Level Up Cases -- return false if invalid + case LevelUpNatureAmped or LevelUpNatureLowKey when GetAmpLowKeyResult(pk.Nature) != pk.Form && !skipChecks: + return false; - /// - /// Checks the for validity by comparing against the data. - /// - /// Entity to check - /// Current level - /// Option to skip some comparisons to return a 'possible' evolution. - /// True if a evolution criteria is valid. - public bool Valid(PKM pkm, int lvl, bool skipChecks) - { - RequiresLevelUp = false; - switch ((EvolutionType)Method) - { - case UseItem or UseItemWormhole or UseItemFullMoon: - case CriticalHitsInBattle or HitPointsLostInBattle or Spin: - case UseAgileStyleMoves or UseStrongStyleMoves: - case TowerOfDarkness or TowerOfWaters: - return true; - case UseItemMale or RecoilDamageMale: - return pkm.Gender == 0; - case UseItemFemale or RecoilDamageFemale: - return pkm.Gender == 1; + case LevelUpBeauty when pk is not IContestStats s || s.CNT_Beauty < Argument: + return skipChecks; + case LevelUpMale when pk.Gender != 0: + return false; + case LevelUpFemale when pk.Gender != 1: + return false; + case LevelUpFormFemale1 when pk.Gender != 1 || pk.Form != 1: + return false; - case Trade or TradeHeldItem or TradeShelmetKarrablast: - return !pkm.IsUntraded || skipChecks; + case LevelUpVersion or LevelUpVersionDay or LevelUpVersionNight when ((pk.Version & 1) != (Argument & 1) && pk.IsUntraded) || skipChecks: + return skipChecks; // Version checks come in pairs, check for any pair match - // Special Level Up Cases -- return false if invalid - case LevelUpNatureAmped or LevelUpNatureLowKey when GetAmpLowKeyResult(pkm.Nature) != pkm.Form && !skipChecks: + // Level Up (any); the above Level Up (with condition) cases will reach here if they were valid + default: + if (IsThresholdCheckMode(pk)) + return lvl >= Level; + + if (Level == 0 && lvl < 2) + return false; + if (lvl < Level) return false; - case LevelUpBeauty when pkm is not IContestStats s || s.CNT_Beauty < Argument: - return skipChecks; - case LevelUpMale when pkm.Gender != 0: - return false; - case LevelUpFemale when pkm.Gender != 1: - return false; - case LevelUpFormFemale1 when pkm.Gender != 1 || pkm.Form != 1: - return false; + RequiresLevelUp = true; + if (skipChecks) + return lvl >= Level; - case LevelUpVersion or LevelUpVersionDay or LevelUpVersionNight when ((pkm.Version & 1) != (Argument & 1) && pkm.IsUntraded) || skipChecks: - return skipChecks; // Version checks come in pairs, check for any pair match - - // Level Up (any); the above Level Up (with condition) cases will reach here if they were valid - default: - if (IsThresholdCheckMode(pkm)) - return lvl >= Level; - - if (Level == 0 && lvl < 2) - return false; - if (lvl < Level) - return false; - - RequiresLevelUp = true; - if (skipChecks) - return lvl >= Level; - - // Check Met Level for extra validity - return HasMetLevelIncreased(pkm, lvl); - } - } - - private static bool IsThresholdCheckMode(PKM pkm) - { - // Starting in Legends: Arceus, level-up evolutions can be triggered if the current level is >= criteria. - // This allows for evolving over-leveled captures immediately without leveling up from capture level. - return pkm is PA8; - } - - private bool HasMetLevelIncreased(PKM pkm, int lvl) - { - int origin = pkm.Generation; - return origin switch - { - // No met data in RBY; No met data in GS, Crystal met data can be reset - 1 or 2 => true, - - // Pal Park / PokeTransfer updates Met Level - 3 or 4 => pkm.Format > origin || pkm.Met_Level < lvl, - - // 5=>6 and later transfers keep current level - >=5 => lvl >= Level && (!pkm.IsNative || pkm.Met_Level < lvl), - - _ => false, - }; - } - - public EvoCriteria GetEvoCriteria(ushort species, byte form, byte lvl) => new() - { - Species = species, - Form = form, - LevelMax = lvl, - LevelMin = 0, - Method = (EvolutionType)Method, - }; - - public static int GetAmpLowKeyResult(int n) - { - var index = n - 1; - if ((uint)index > 22) - return 0; - return (0b_0101_1011_1100_1010_0101_0001 >> index) & 1; + // Check Met Level for extra validity + return HasMetLevelIncreased(pk, lvl); } } + + private static bool IsThresholdCheckMode(PKM pk) + { + // Starting in Legends: Arceus, level-up evolutions can be triggered if the current level is >= criteria. + // This allows for evolving over-leveled captures immediately without leveling up from capture level. + return pk is PA8; + } + + private bool HasMetLevelIncreased(PKM pk, int lvl) + { + int origin = pk.Generation; + return origin switch + { + // No met data in RBY; No met data in GS, Crystal met data can be reset + 1 or 2 => true, + + // Pal Park / PokeTransfer updates Met Level + 3 or 4 => pk.Format > origin || pk.Met_Level < lvl, + + // 5=>6 and later transfers keep current level + >=5 => lvl >= Level && (!pk.IsNative || pk.Met_Level < lvl), + + _ => false, + }; + } + + public EvoCriteria GetEvoCriteria(ushort species, byte form, byte lvl) => new() + { + Species = species, + Form = form, + LevelMax = lvl, + LevelMin = 0, + Method = (EvolutionType)Method, + }; + + public static int GetAmpLowKeyResult(int n) + { + var index = n - 1; + if ((uint)index > 22) + return 0; + return (0b_0101_1011_1100_1010_0101_0001 >> index) & 1; + } } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet1.cs b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet1.cs index a40455975..89297ea5b 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet1.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet1.cs @@ -1,47 +1,46 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 1 Evolution Branch Entries +/// +public static class EvolutionSet1 { - /// - /// Generation 1 Evolution Branch Entries - /// - public static class EvolutionSet1 + private static EvolutionMethod GetMethod(ReadOnlySpan data) { - private static EvolutionMethod GetMethod(ReadOnlySpan data) - { - int method = data[0]; - int species = data[1]; - var arg = data[2]; + int method = data[0]; + int species = data[1]; + var arg = data[2]; - return (method == 1) - ? new EvolutionMethod(method, species, level: arg) - : new EvolutionMethod(method, species, argument: arg); - } + return (method == 1) + ? new EvolutionMethod(method, species, level: arg) + : new EvolutionMethod(method, species, argument: arg); + } - public static IReadOnlyList GetArray(ReadOnlySpan data, int maxSpecies) + public static IReadOnlyList GetArray(ReadOnlySpan data, int maxSpecies) + { + var evos = new EvolutionMethod[maxSpecies + 1][]; + int ofs = 0; + const int bpe = 3; + for (int i = 0; i < evos.Length; i++) { - var evos = new EvolutionMethod[maxSpecies + 1][]; - int ofs = 0; - const int bpe = 3; - for (int i = 0; i < evos.Length; i++) + int count = data[ofs]; + ofs++; + if (count == 0) { - int count = data[ofs]; - ofs++; - if (count == 0) - { - evos[i] = Array.Empty(); - continue; - } - var m = new EvolutionMethod[count]; - for (int j = 0; j < m.Length; j++) - { - m[j] = GetMethod(data.Slice(ofs, bpe)); - ofs += bpe; - } - evos[i] = m; + evos[i] = Array.Empty(); + continue; } - return evos; + var m = new EvolutionMethod[count]; + for (int j = 0; j < m.Length; j++) + { + m[j] = GetMethod(data.Slice(ofs, bpe)); + ofs += bpe; + } + evos[i] = m; } + return evos; } } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet3.cs b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet3.cs index 119fac53a..564a5e31f 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet3.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet3.cs @@ -2,79 +2,78 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 Evolution Branch Entries +/// +public static class EvolutionSet3 { - /// - /// Generation 3 Evolution Branch Entries - /// - public static class EvolutionSet3 + private static EvolutionMethod GetMethod(ReadOnlySpan data) { - private static EvolutionMethod GetMethod(ReadOnlySpan data) + int method = ReadUInt16LittleEndian(data); + int arg = ReadUInt16LittleEndian(data[2..]); + int species = SpeciesConverter.GetG4Species(ReadUInt16LittleEndian(data[4..])); + //2 bytes padding + + switch (method) { - int method = ReadUInt16LittleEndian(data); - int arg = ReadUInt16LittleEndian(data[2..]); - int species = SpeciesConverter.GetG4Species(ReadUInt16LittleEndian(data[4..])); - //2 bytes padding + case 1: /* Friendship*/ + case 2: /* Friendship day*/ + case 3: /* Friendship night*/ + case 5: /* Trade */ + case 6: /* Trade while holding */ + return new EvolutionMethod(method, species, argument: arg); + case 4: /* Level Up */ + return new EvolutionMethod(4, species, argument: arg, level: (byte)arg); + case 7: /* Use item */ + case 15: /* Beauty evolution*/ + return new EvolutionMethod(method + 1, species, argument: arg); + case 8: /* Tyrogue -> Hitmonchan */ + case 9: /* Tyrogue -> Hitmonlee */ + case 10: /* Tyrogue -> Hitmontop*/ + case 11: /* Wurmple -> Silcoon evolution */ + case 12: /* Wurmple -> Cascoon evolution */ + case 13: /* Nincada -> Ninjask evolution */ + case 14: /* Shedinja spawn in Nincada -> Ninjask evolution */ + return new EvolutionMethod(method + 1, species, argument: arg, level: (byte)arg); - switch (method) - { - case 1: /* Friendship*/ - case 2: /* Friendship day*/ - case 3: /* Friendship night*/ - case 5: /* Trade */ - case 6: /* Trade while holding */ - return new EvolutionMethod(method, species, argument: arg); - case 4: /* Level Up */ - return new EvolutionMethod(4, species, argument: arg, level: (byte)arg); - case 7: /* Use item */ - case 15: /* Beauty evolution*/ - return new EvolutionMethod(method + 1, species, argument: arg); - case 8: /* Tyrogue -> Hitmonchan */ - case 9: /* Tyrogue -> Hitmonlee */ - case 10: /* Tyrogue -> Hitmontop*/ - case 11: /* Wurmple -> Silcoon evolution */ - case 12: /* Wurmple -> Cascoon evolution */ - case 13: /* Nincada -> Ninjask evolution */ - case 14: /* Shedinja spawn in Nincada -> Ninjask evolution */ - return new EvolutionMethod(method + 1, species, argument: arg, level: (byte)arg); - - default: - throw new ArgumentOutOfRangeException(nameof(method)); - } - } - - public static IReadOnlyList GetArray(ReadOnlySpan data) - { - var evos = new EvolutionMethod[Legal.MaxSpeciesID_3 + 1][]; - evos[0] = Array.Empty(); - for (int i = 1; i <= Legal.MaxSpeciesIndex_3; i++) - { - int g4species = SpeciesConverter.GetG4Species(i); - if (g4species == 0) - continue; - - const int maxCount = 5; - const int size = 8; - - int offset = i * (maxCount * size); - int count = 0; - for (; count < maxCount; count++) - { - if (data[offset + (count * size)] == 0) - break; - } - if (count == 0) - { - evos[g4species] = Array.Empty(); - continue; - } - - var set = new EvolutionMethod[count]; - for (int j = 0; j < set.Length; j++) - set[j] = GetMethod(data.Slice(offset + (j * size), size)); - evos[g4species] = set; - } - return evos; + default: + throw new ArgumentOutOfRangeException(nameof(method)); } } + + public static IReadOnlyList GetArray(ReadOnlySpan data) + { + var evos = new EvolutionMethod[Legal.MaxSpeciesID_3 + 1][]; + evos[0] = Array.Empty(); + for (int i = 1; i <= Legal.MaxSpeciesIndex_3; i++) + { + int g4species = SpeciesConverter.GetG4Species(i); + if (g4species == 0) + continue; + + const int maxCount = 5; + const int size = 8; + + int offset = i * (maxCount * size); + int count = 0; + for (; count < maxCount; count++) + { + if (data[offset + (count * size)] == 0) + break; + } + if (count == 0) + { + evos[g4species] = Array.Empty(); + continue; + } + + var set = new EvolutionMethod[count]; + for (int j = 0; j < set.Length; j++) + set[j] = GetMethod(data.Slice(offset + (j * size), size)); + evos[g4species] = set; + } + return evos; + } } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet4.cs b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet4.cs index d9ec5fa8f..8dc9b8d3e 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet4.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet4.cs @@ -2,61 +2,60 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 Evolution Branch Entries +/// +public static class EvolutionSet4 { - /// - /// Generation 4 Evolution Branch Entries - /// - public static class EvolutionSet4 + private static EvolutionMethod GetMethod(ReadOnlySpan data) { - private static EvolutionMethod GetMethod(ReadOnlySpan data) + var method = data[0]; // other byte unnecessary + int arg = ReadUInt16LittleEndian(data[2..]); + int species = ReadUInt16LittleEndian(data[4..]); + + if (method == 0) + throw new ArgumentOutOfRangeException(nameof(method)); + + // To have the same structure as gen 6 + // Gen 4 Method 6 is Gen 6 Method 7, G4 7 = G6 8, and so on + if (method > 6) + method++; + + var lvl = EvolutionSet6.EvosWithArg.Contains(method) ? 0 : arg; + return new EvolutionMethod(method, species, argument: arg, level: (byte)lvl); + } + + public static IReadOnlyList GetArray(ReadOnlySpan data) + { + const int bpe = 6; // bytes per evolution entry + const int entries = 7; // amount of entries per species + const int size = (entries * bpe) + 2; // bytes per species entry, + 2 alignment bytes + + var evos = new EvolutionMethod[data.Length / size][]; + for (int i = 0; i < evos.Length; i++) { - var method = data[0]; // other byte unnecessary - int arg = ReadUInt16LittleEndian(data[2..]); - int species = ReadUInt16LittleEndian(data[4..]); - - if (method == 0) - throw new ArgumentOutOfRangeException(nameof(method)); - - // To have the same structure as gen 6 - // Gen 4 Method 6 is Gen 6 Method 7, G4 7 = G6 8, and so on - if (method > 6) - method++; - - var lvl = EvolutionSet6.EvosWithArg.Contains(method) ? 0 : arg; - return new EvolutionMethod(method, species, argument: arg, level: (byte)lvl); - } - - public static IReadOnlyList GetArray(ReadOnlySpan data) - { - const int bpe = 6; // bytes per evolution entry - const int entries = 7; // amount of entries per species - const int size = (entries * bpe) + 2; // bytes per species entry, + 2 alignment bytes - - var evos = new EvolutionMethod[data.Length / size][]; - for (int i = 0; i < evos.Length; i++) + int offset = i * size; + int count = 0; + for (; count < entries; count++) { - int offset = i * size; - int count = 0; - for (; count < entries; count++) - { - var methodOffset = offset + (count * bpe); - var method = data[methodOffset]; - if (method == 0) - break; - } - if (count == 0) - { - evos[i] = Array.Empty(); - continue; - } - - var set = new EvolutionMethod[count]; - for (int j = 0; j < set.Length; j++) - set[j] = GetMethod(data.Slice(offset + (j * bpe), bpe)); - evos[i] = set; + var methodOffset = offset + (count * bpe); + var method = data[methodOffset]; + if (method == 0) + break; } - return evos; + if (count == 0) + { + evos[i] = Array.Empty(); + continue; + } + + var set = new EvolutionMethod[count]; + for (int j = 0; j < set.Length; j++) + set[j] = GetMethod(data.Slice(offset + (j * bpe), bpe)); + evos[i] = set; } + return evos; } } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet5.cs b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet5.cs index 9af294202..fa4e44160 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet5.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet5.cs @@ -2,62 +2,61 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 Evolution Branch Entries +/// +public static class EvolutionSet5 { - /// - /// Generation 5 Evolution Branch Entries - /// - public static class EvolutionSet5 + private static EvolutionMethod GetMethod(ReadOnlySpan data) { - private static EvolutionMethod GetMethod(ReadOnlySpan data) - { - var method = data[0]; // other byte unnecessary - int arg = ReadUInt16LittleEndian(data[2..]); - int species = ReadUInt16LittleEndian(data[4..]); + var method = data[0]; // other byte unnecessary + int arg = ReadUInt16LittleEndian(data[2..]); + int species = ReadUInt16LittleEndian(data[4..]); + if (method == 0) + throw new ArgumentOutOfRangeException(nameof(method)); + + var lvl = EvolutionSet6.EvosWithArg.Contains(method) ? 0 : arg; + return new EvolutionMethod(method, species, argument: arg, level: (byte)lvl); + } + + private const int bpe = 6; // bytes per evolution entry + private const int entries = 7; // amount of entries per species + private const int size = entries * bpe; // bytes per species entry + + public static IReadOnlyList GetArray(ReadOnlySpan data) + { + var evos = new EvolutionMethod[data.Length / size][]; + for (int i = 0; i < evos.Length; i++) + { + int offset = i * size; + var rawEntries = data.Slice(offset, size); + var count = ScanCountEvolutions(rawEntries); + if (count == 0) + { + evos[i] = Array.Empty(); + continue; + } + + var set = new EvolutionMethod[count]; + for (int j = 0; j < set.Length; j++) + set[j] = GetMethod(rawEntries.Slice(j * bpe, bpe)); + evos[i] = set; + } + return evos; + } + + private static int ScanCountEvolutions(ReadOnlySpan data) + { + for (int count = 0; count < entries; count++) + { + var methodOffset = count * bpe; + var method = data[methodOffset]; if (method == 0) - throw new ArgumentOutOfRangeException(nameof(method)); - - var lvl = EvolutionSet6.EvosWithArg.Contains(method) ? 0 : arg; - return new EvolutionMethod(method, species, argument: arg, level: (byte)lvl); - } - - private const int bpe = 6; // bytes per evolution entry - private const int entries = 7; // amount of entries per species - private const int size = entries * bpe; // bytes per species entry - - public static IReadOnlyList GetArray(ReadOnlySpan data) - { - var evos = new EvolutionMethod[data.Length / size][]; - for (int i = 0; i < evos.Length; i++) - { - int offset = i * size; - var rawEntries = data.Slice(offset, size); - var count = ScanCountEvolutions(rawEntries); - if (count == 0) - { - evos[i] = Array.Empty(); - continue; - } - - var set = new EvolutionMethod[count]; - for (int j = 0; j < set.Length; j++) - set[j] = GetMethod(rawEntries.Slice(j * bpe, bpe)); - evos[i] = set; - } - return evos; - } - - private static int ScanCountEvolutions(ReadOnlySpan data) - { - for (int count = 0; count < entries; count++) - { - var methodOffset = count * bpe; - var method = data[methodOffset]; - if (method == 0) - return count; - } - return entries; + return count; } + return entries; } } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet6.cs b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet6.cs index af4ae0382..44761f2cf 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet6.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet6.cs @@ -2,45 +2,44 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 6 Evolution Branch Entries +/// +public static class EvolutionSet6 { - /// - /// Generation 6 Evolution Branch Entries - /// - public static class EvolutionSet6 + internal static readonly HashSet EvosWithArg = new() {6, 8, 16, 17, 18, 19, 20, 21, 22, 29}; + private const int SIZE = 6; + + private static EvolutionMethod[] GetMethods(ReadOnlySpan data) { - internal static readonly HashSet EvosWithArg = new() {6, 8, 16, 17, 18, 19, 20, 21, 22, 29}; - private const int SIZE = 6; - - private static EvolutionMethod[] GetMethods(ReadOnlySpan data) + var evos = new EvolutionMethod[data.Length / SIZE]; + for (int i = 0; i < data.Length; i += SIZE) { - var evos = new EvolutionMethod[data.Length / SIZE]; - for (int i = 0; i < data.Length; i += SIZE) - { - var entry = data.Slice(i, SIZE); - evos[i/SIZE] = GetMethod(entry); - } - return evos; + var entry = data.Slice(i, SIZE); + evos[i/SIZE] = GetMethod(entry); } + return evos; + } - private static EvolutionMethod GetMethod(ReadOnlySpan entry) - { - var method = ReadUInt16LittleEndian(entry); - var arg = ReadUInt16LittleEndian(entry[2..]); - var species = ReadUInt16LittleEndian(entry[4..]); + private static EvolutionMethod GetMethod(ReadOnlySpan entry) + { + var method = ReadUInt16LittleEndian(entry); + var arg = ReadUInt16LittleEndian(entry[2..]); + var species = ReadUInt16LittleEndian(entry[4..]); - // Argument is used by both Level argument and Item/Move/etc. Clear if appropriate. - var lvl = EvosWithArg.Contains(method) ? 0 : arg; + // Argument is used by both Level argument and Item/Move/etc. Clear if appropriate. + var lvl = EvosWithArg.Contains(method) ? 0 : arg; - return new EvolutionMethod(method, species, argument: arg, level: (byte)lvl); - } + return new EvolutionMethod(method, species, argument: arg, level: (byte)lvl); + } - public static IReadOnlyList GetArray(BinLinkerAccessor data) - { - var evos = new EvolutionMethod[data.Length][]; - for (int i = 0; i < evos.Length; i++) - evos[i] = GetMethods(data[i]); - return evos; - } + public static IReadOnlyList GetArray(BinLinkerAccessor data) + { + var evos = new EvolutionMethod[data.Length][]; + for (int i = 0; i < evos.Length; i++) + evos[i] = GetMethods(data[i]); + return evos; } } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet7.cs b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet7.cs index b3aa03da1..abc986b5f 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet7.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionSets/EvolutionSet7.cs @@ -2,42 +2,41 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 Evolution Branch Entries +/// +public static class EvolutionSet7 { - /// - /// Generation 7 Evolution Branch Entries - /// - public static class EvolutionSet7 + private const int SIZE = 8; + + private static EvolutionMethod[] GetMethods(ReadOnlySpan data) { - private const int SIZE = 8; - - private static EvolutionMethod[] GetMethods(ReadOnlySpan data) + var evos = new EvolutionMethod[data.Length / SIZE]; + for (int i = 0; i < data.Length; i += SIZE) { - var evos = new EvolutionMethod[data.Length / SIZE]; - for (int i = 0; i < data.Length; i += SIZE) - { - var entry = data.Slice(i, SIZE); - evos[i / SIZE] = ReadEvolution(entry); - } - return evos; + var entry = data.Slice(i, SIZE); + evos[i / SIZE] = ReadEvolution(entry); } + return evos; + } - private static EvolutionMethod ReadEvolution(ReadOnlySpan entry) - { - var method = ReadUInt16LittleEndian(entry); - var arg = ReadUInt16LittleEndian(entry[2..]); - var species = ReadUInt16LittleEndian(entry[4..]); - var form = (sbyte)entry[6]; - var level = entry[7]; - return new EvolutionMethod(method, species, argument: arg, level: level, form: form); - } + private static EvolutionMethod ReadEvolution(ReadOnlySpan entry) + { + var method = ReadUInt16LittleEndian(entry); + var arg = ReadUInt16LittleEndian(entry[2..]); + var species = ReadUInt16LittleEndian(entry[4..]); + var form = (sbyte)entry[6]; + var level = entry[7]; + return new EvolutionMethod(method, species, argument: arg, level: level, form: form); + } - public static IReadOnlyList GetArray(BinLinkerAccessor data) - { - var evos = new EvolutionMethod[data.Length][]; - for (int i = 0; i < evos.Length; i++) - evos[i] = GetMethods(data[i]); - return evos; - } + public static IReadOnlyList GetArray(BinLinkerAccessor data) + { + var evos = new EvolutionMethod[data.Length][]; + for (int i = 0; i < evos.Length; i++) + evos[i] = GetMethods(data[i]); + return evos; } } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs b/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs index eaa6addd2..502fb8fe5 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionTree.cs @@ -4,508 +4,502 @@ using static PKHeX.Core.GameVersion; using static PKHeX.Core.Legal; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation specific Evolution Tree data. +/// +/// +/// Used to determine if a can evolve from prior steps in its evolution branch. +/// +public sealed class EvolutionTree { - /// - /// Generation specific Evolution Tree data. - /// - /// - /// Used to determine if a can evolve from prior steps in its evolution branch. - /// - public sealed class EvolutionTree + public static readonly EvolutionTree Evolves1 = new(GetResource("rby"), Gen1, PersonalTable.Y, MaxSpeciesID_1); + public static readonly EvolutionTree Evolves2 = new(GetResource("gsc"), Gen2, PersonalTable.C, MaxSpeciesID_2); + public static readonly EvolutionTree Evolves3 = new(GetResource("g3"), Gen3, PersonalTable.RS, MaxSpeciesID_3); + public static readonly EvolutionTree Evolves4 = new(GetResource("g4"), Gen4, PersonalTable.DP, MaxSpeciesID_4); + public static readonly EvolutionTree Evolves5 = new(GetResource("g5"), Gen5, PersonalTable.BW, MaxSpeciesID_5); + public static readonly EvolutionTree Evolves6 = new(GetReader("ao"), Gen6, PersonalTable.AO, MaxSpeciesID_6); + public static readonly EvolutionTree Evolves7 = new(GetReader("uu"), Gen7, PersonalTable.USUM, MaxSpeciesID_7_USUM); + public static readonly EvolutionTree Evolves7b = new(GetReader("gg"), Gen7, PersonalTable.GG, MaxSpeciesID_7b); + public static readonly EvolutionTree Evolves8 = new(GetReader("ss"), Gen8, PersonalTable.SWSH, MaxSpeciesID_8); + public static readonly EvolutionTree Evolves8a = new(GetReader("la"), Gen8, PersonalTable.LA, MaxSpeciesID_8a); + public static readonly EvolutionTree Evolves8b = new(GetReader("bs"), Gen8, PersonalTable.BDSP, MaxSpeciesID_8b); + + private static ReadOnlySpan GetResource(string resource) => Util.GetBinaryResource($"evos_{resource}.pkl"); + private static BinLinkerAccessor GetReader(string resource) => BinLinkerAccessor.Get(GetResource(resource), resource); + + static EvolutionTree() { - public static readonly EvolutionTree Evolves1 = new(GetResource("rby"), Gen1, PersonalTable.Y, MaxSpeciesID_1); - public static readonly EvolutionTree Evolves2 = new(GetResource("gsc"), Gen2, PersonalTable.C, MaxSpeciesID_2); - public static readonly EvolutionTree Evolves3 = new(GetResource("g3"), Gen3, PersonalTable.RS, MaxSpeciesID_3); - public static readonly EvolutionTree Evolves4 = new(GetResource("g4"), Gen4, PersonalTable.DP, MaxSpeciesID_4); - public static readonly EvolutionTree Evolves5 = new(GetResource("g5"), Gen5, PersonalTable.BW, MaxSpeciesID_5); - public static readonly EvolutionTree Evolves6 = new(GetReader("ao"), Gen6, PersonalTable.AO, MaxSpeciesID_6); - public static readonly EvolutionTree Evolves7 = new(GetReader("uu"), Gen7, PersonalTable.USUM, MaxSpeciesID_7_USUM); - public static readonly EvolutionTree Evolves7b = new(GetReader("gg"), Gen7, PersonalTable.GG, MaxSpeciesID_7b); - public static readonly EvolutionTree Evolves8 = new(GetReader("ss"), Gen8, PersonalTable.SWSH, MaxSpeciesID_8); - public static readonly EvolutionTree Evolves8a = new(GetReader("la"), Gen8, PersonalTable.LA, MaxSpeciesID_8a); - public static readonly EvolutionTree Evolves8b = new(GetReader("bs"), Gen8, PersonalTable.BDSP, MaxSpeciesID_8b); + // Add in banned evolution data! + Evolves7.FixEvoTreeSM(); + Evolves8.FixEvoTreeSS(); + Evolves8b.FixEvoTreeBS(); + } - private static ReadOnlySpan GetResource(string resource) => Util.GetBinaryResource($"evos_{resource}.pkl"); - private static BinLinkerAccessor GetReader(string resource) => BinLinkerAccessor.Get(GetResource(resource), resource); + public static EvolutionTree GetEvolutionTree(EntityContext context) => context switch + { + EntityContext.Gen1 => Evolves1, + EntityContext.Gen2 => Evolves2, + EntityContext.Gen3 => Evolves3, + EntityContext.Gen4 => Evolves4, + EntityContext.Gen5 => Evolves5, + EntityContext.Gen6 => Evolves6, + EntityContext.Gen7 => Evolves7, + EntityContext.Gen8 => Evolves8, + EntityContext.Gen7b => Evolves7b, + EntityContext.Gen8a => Evolves8a, + EntityContext.Gen8b => Evolves8b, + _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) + }; - static EvolutionTree() + private readonly IReadOnlyList Entries; + private readonly GameVersion Game; + private readonly PersonalTable Personal; + private readonly int MaxSpeciesTree; + private readonly ILookup Lineage; + private static int GetLookupKey(int species, int form) => species | (form << 11); + + #region Constructor + + private EvolutionTree(ReadOnlySpan data, GameVersion game, PersonalTable personal, int maxSpeciesTree) + { + Game = game; + Personal = personal; + MaxSpeciesTree = maxSpeciesTree; + Entries = GetEntries(data, game); + + // Starting in Generation 7, forms have separate evolution data. + int format = Game - Gen1 + 1; + var oldStyle = format < 7; + var connections = oldStyle ? CreateTreeOld() : CreateTree(); + + Lineage = connections.ToLookup(obj => obj.Key, obj => obj.Value); + } + + private EvolutionTree(BinLinkerAccessor data, GameVersion game, PersonalTable personal, int maxSpeciesTree) + { + Game = game; + Personal = personal; + MaxSpeciesTree = maxSpeciesTree; + Entries = GetEntries(data, game); + + // Starting in Generation 7, forms have separate evolution data. + int format = Game - Gen1 + 1; + var oldStyle = format < 7; + var connections = oldStyle ? CreateTreeOld() : CreateTree(); + + Lineage = connections.ToLookup(obj => obj.Key, obj => obj.Value); + } + + private IEnumerable> CreateTreeOld() + { + for (int sSpecies = 1; sSpecies <= MaxSpeciesTree; sSpecies++) { - // Add in banned evolution data! - Evolves7.FixEvoTreeSM(); - Evolves8.FixEvoTreeSS(); - Evolves8a.FixEvoTreeLA(); - Evolves8b.FixEvoTreeBS(); - } - - public static EvolutionTree GetEvolutionTree(EntityContext context) => context switch - { - EntityContext.Gen1 => Evolves1, - EntityContext.Gen2 => Evolves2, - EntityContext.Gen3 => Evolves3, - EntityContext.Gen4 => Evolves4, - EntityContext.Gen5 => Evolves5, - EntityContext.Gen6 => Evolves6, - EntityContext.Gen7 => Evolves7, - EntityContext.Gen8 => Evolves8, - EntityContext.Gen7b => Evolves7b, - EntityContext.Gen8a => Evolves8a, - EntityContext.Gen8b => Evolves8b, - _ => throw new ArgumentOutOfRangeException(nameof(context), context, null) - }; - - private readonly IReadOnlyList Entries; - private readonly GameVersion Game; - private readonly PersonalTable Personal; - private readonly int MaxSpeciesTree; - private readonly ILookup Lineage; - private static int GetLookupKey(int species, int form) => species | (form << 11); - - #region Constructor - - private EvolutionTree(ReadOnlySpan data, GameVersion game, PersonalTable personal, int maxSpeciesTree) - { - Game = game; - Personal = personal; - MaxSpeciesTree = maxSpeciesTree; - Entries = GetEntries(data, game); - - // Starting in Generation 7, forms have separate evolution data. - int format = Game - Gen1 + 1; - var oldStyle = format < 7; - var connections = oldStyle ? CreateTreeOld() : CreateTree(); - - Lineage = connections.ToLookup(obj => obj.Key, obj => obj.Value); - } - - private EvolutionTree(BinLinkerAccessor data, GameVersion game, PersonalTable personal, int maxSpeciesTree) - { - Game = game; - Personal = personal; - MaxSpeciesTree = maxSpeciesTree; - Entries = GetEntries(data, game); - - // Starting in Generation 7, forms have separate evolution data. - int format = Game - Gen1 + 1; - var oldStyle = format < 7; - var connections = oldStyle ? CreateTreeOld() : CreateTree(); - - Lineage = connections.ToLookup(obj => obj.Key, obj => obj.Value); - } - - private IEnumerable> CreateTreeOld() - { - for (int sSpecies = 1; sSpecies <= MaxSpeciesTree; sSpecies++) + var fc = Personal[sSpecies].FormCount; + for (int sForm = 0; sForm < fc; sForm++) { - var fc = Personal[sSpecies].FormCount; - for (int sForm = 0; sForm < fc; sForm++) + var index = sSpecies; + var evos = Entries[index]; + foreach (var evo in evos) { - var index = sSpecies; - var evos = Entries[index]; - foreach (var evo in evos) - { - var dSpecies = evo.Species; - if (dSpecies == 0) - continue; - - var dForm = sSpecies == (int)Species.Espurr && evo.Method == (int)EvolutionType.LevelUpFormFemale1 ? 1 : sForm; - var key = GetLookupKey(dSpecies, dForm); - - var link = new EvolutionLink(sSpecies, sForm, evo); - yield return new KeyValuePair(key, link); - } - } - } - } - - private IEnumerable> CreateTree() - { - for (int sSpecies = 1; sSpecies <= MaxSpeciesTree; sSpecies++) - { - var fc = Personal[sSpecies].FormCount; - for (int sForm = 0; sForm < fc; sForm++) - { - var index = Personal.GetFormIndex(sSpecies, sForm); - var evos = Entries[index]; - foreach (var evo in evos) - { - var dSpecies = evo.Species; - if (dSpecies == 0) - break; - - var dForm = evo.GetDestinationForm(sForm); - var key = GetLookupKey(dSpecies, dForm); - - var link = new EvolutionLink(sSpecies, sForm, evo); - yield return new KeyValuePair(key, link); - } - } - } - } - - private IReadOnlyList GetEntries(ReadOnlySpan data, GameVersion game) => game switch - { - Gen1 => EvolutionSet1.GetArray(data, MaxSpeciesTree), - Gen2 => EvolutionSet1.GetArray(data, MaxSpeciesTree), - Gen3 => EvolutionSet3.GetArray(data), - Gen4 => EvolutionSet4.GetArray(data), - Gen5 => EvolutionSet5.GetArray(data), - _ => throw new ArgumentOutOfRangeException(), - }; - - private IReadOnlyList GetEntries(BinLinkerAccessor data, GameVersion game) => game switch - { - Gen6 => EvolutionSet6.GetArray(data), - Gen7 => EvolutionSet7.GetArray(data), - Gen8 => EvolutionSet7.GetArray(data), - _ => throw new ArgumentOutOfRangeException(), - }; - - private void FixEvoTreeSM() - { - // Sun/Moon lack Ultra's Kantonian evolution methods. - BanEvo((int)Species.Raichu, 0, pkm => pkm.IsUntraded && pkm.SM); - BanEvo((int)Species.Marowak, 0, pkm => pkm.IsUntraded && pkm.SM); - BanEvo((int)Species.Exeggutor, 0, pkm => pkm.IsUntraded && pkm.SM); - } - - private void FixEvoTreeSS() - { - // Gigantamax Pikachu, Meowth-0, and Eevee are prevented from evolving. - // Raichu cannot be evolved to the Alolan variant at this time. - BanEvo((int)Species.Raichu, 0, pkm => pkm is IGigantamax {CanGigantamax: true}); - BanEvo((int)Species.Raichu, 1, pkm => (pkm is IGigantamax {CanGigantamax: true}) || pkm.Version is (int)GO or >= (int)GP); - BanEvo((int)Species.Persian, 0, pkm => pkm is IGigantamax {CanGigantamax: true}); - BanEvo((int)Species.Persian, 1, pkm => pkm is IGigantamax {CanGigantamax: true}); - BanEvo((int)Species.Perrserker, 0, pkm => pkm is IGigantamax {CanGigantamax: true}); - - BanEvo((int)Species.Exeggutor, 1, pkm => pkm.Version is (int)GO or >= (int)GP); - BanEvo((int)Species.Marowak, 1, pkm => pkm.Version is (int)GO or >= (int)GP); - BanEvo((int)Species.Weezing, 0, pkm => pkm.Version >= (int)SW); - BanEvo((int)Species.MrMime, 0, pkm => pkm.Version >= (int)SW); - - foreach (var s in GetEvolutions((int)Species.Eevee, 0)) // Eeveelutions - BanEvo(s, 0, pkm => pkm is IGigantamax {CanGigantamax: true}); - } - - private void FixEvoTreeLA() - { - } - - private void FixEvoTreeBS() - { - BanEvo((int)Species.Glaceon, 0, pkm => pkm.CurrentLevel == pkm.Met_Level); // Ice Stone is unreleased, requires Route 217 Ice Rock Level Up instead - BanEvo((int)Species.Milotic, 0, pkm => pkm is IContestStats { CNT_Beauty: < 170 } || pkm.CurrentLevel == pkm.Met_Level); // Prism Scale is unreleased, requires 170 Beauty Level Up instead - } - - private void BanEvo(int species, int form, Func func) - { - var key = GetLookupKey(species, form); - var node = Lineage[key]; - foreach (var link in node) - link.IsBanned = func; - } - - #endregion - - /// - /// Gets a list of evolutions for the input by checking each evolution in the chain. - /// - /// Pokémon data to check with. - /// Maximum level to permit before the chain breaks. - /// Maximum species ID to permit within the chain. - /// Ignores an evolution's criteria, causing the returned list to have all possible evolutions. - /// Minimum level to permit before the chain breaks. - public EvoCriteria[] GetValidPreEvolutions(PKM pkm, byte maxLevel, int maxSpeciesOrigin = -1, bool skipChecks = false, byte minLevel = 1) - { - if (maxSpeciesOrigin <= 0) - maxSpeciesOrigin = GetMaxSpeciesOrigin(pkm); - - ushort species = (ushort)pkm.Species; - byte form = (byte)pkm.Form; - - return GetExplicitLineage(species, form, pkm, minLevel, maxLevel, maxSpeciesOrigin, skipChecks); - } - - public bool IsSpeciesDerivedFrom(int species, int form, int otherSpecies, int otherForm, bool ignoreForm = true) - { - var evos = GetEvolutionsAndPreEvolutions(species, form); - foreach (var evo in evos) - { - var s = evo & 0x3FF; - if (s != otherSpecies) - continue; - if (ignoreForm) - return true; - var f = evo >> 11; - return f == otherForm; - } - return false; - } - - /// - /// Gets all species the - can evolve to & from, yielded in order of increasing evolution stage. - /// - /// Species ID - /// Form ID - /// Enumerable of species IDs (with the Form IDs included, left shifted by 11). - public IEnumerable GetEvolutionsAndPreEvolutions(int species, int form) - { - foreach (var s in GetPreEvolutions(species, form)) - yield return s; - yield return species; - foreach (var s in GetEvolutions(species, form)) - yield return s; - } - - public int GetBaseSpeciesForm(int species, int form, int skip = 0) - { - var chain = GetEvolutionsAndPreEvolutions(species, form); - foreach (var c in chain) - { - if (skip == 0) - return c; - skip--; - } - return species | (form << 11); - } - - /// - /// Gets all species the - can evolve from, yielded in order of increasing evolution stage. - /// - /// Species ID - /// Form ID - /// Enumerable of species IDs (with the Form IDs included, left shifted by 11). - public IEnumerable GetPreEvolutions(int species, int form) - { - int index = GetLookupKey(species, form); - var node = Lineage[index]; - foreach (var method in node) - { - var s = method.Species; - if (s == 0) - continue; - var f = method.Form; - var preEvolutions = GetPreEvolutions(s, f); - foreach (var preEvo in preEvolutions) - yield return preEvo; - yield return s | (f << 11); - } - } - - /// - /// Gets all species the - can evolve to, yielded in order of increasing evolution stage. - /// - /// Species ID - /// Form ID - /// Enumerable of species IDs (with the Form IDs included, left shifted by 11). - public IEnumerable GetEvolutions(int species, int form) - { - int format = Game - Gen1 + 1; - int index = format < 7 ? species : Personal.GetFormIndex(species, form); - var evos = Entries[index]; - foreach (var method in evos) - { - var s = method.Species; - if (s == 0) - continue; - var f = method.GetDestinationForm(form); - yield return s | (f << 11); - var nextEvolutions = GetEvolutions(s, f); - foreach (var nextEvo in nextEvolutions) - yield return nextEvo; - } - } - - /// - /// Generates the reverse evolution path for the input . - /// - /// Entity Species to begin the chain - /// Entity Form to begin the chain - /// Entity data - /// Minimum level - /// Maximum level - /// Clamp for maximum species ID - /// Skip the secondary checks that validate the evolution - private EvoCriteria[] GetExplicitLineage(ushort species, byte form, PKM pkm, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks) - { - if (pkm.IsEgg && !skipChecks) - { - return new[] - { - new EvoCriteria{ Species = species, Form = form, LevelMax = levelMax, LevelMin = levelMax }, - }; - } - - // Shedinja's evolution case can be a little tricky; hard-code handling. - if (species == (int)Species.Shedinja && levelMax >= 20 && (!pkm.HasOriginalMetLocation || levelMin < levelMax)) - { - var min = Math.Max(levelMin, (byte)20); - return new[] - { - new EvoCriteria { Species = (ushort)Species.Shedinja, LevelMax = levelMax, LevelMin = min, Method = EvolutionType.LevelUp }, - new EvoCriteria { Species = (ushort)Species.Nincada, LevelMax = levelMax, LevelMin = levelMin }, - }; - } - return GetLineage(species, form, pkm, levelMin, levelMax, maxSpeciesID, skipChecks); - } - - private EvoCriteria[] GetLineage(int species, int form, PKM pkm, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks) - { - var lvl = levelMax; - var first = new EvoCriteria { Species = (ushort)species, Form = (byte)form, LevelMax = lvl }; - - const int maxEvolutions = 3; - Span evos = stackalloc EvoCriteria[maxEvolutions]; - evos[0] = first; - - switch (species) - { - case (int)Species.Silvally: - form = 0; - break; - } - - // There aren't any circular evolution paths, and all lineages have at most 3 evolutions total. - // There aren't any convergent evolution paths, so only yield the first connection. - int ctr = 1; - while (true) - { - var key = GetLookupKey(species, form); - bool oneValid = false; - var node = Lineage[key]; - - foreach (var link in node) - { - if (link.IsEvolutionBanned(pkm) && !skipChecks) + var dSpecies = evo.Species; + if (dSpecies == 0) continue; - var evo = link.Method; - if (!evo.Valid(pkm, lvl, skipChecks)) - continue; + var dForm = sSpecies == (int)Species.Espurr && evo.Method == (int)EvolutionType.LevelUpFormFemale1 ? 1 : sForm; + var key = GetLookupKey(dSpecies, dForm); - if (evo.RequiresLevelUp && levelMin >= lvl) - break; // impossible evolution - - UpdateMinValues(evos[..ctr], evo, levelMin); - - species = link.Species; - form = link.Form; - evos[ctr++] = evo.GetEvoCriteria((ushort)species, (byte)form, lvl); - if (evo.RequiresLevelUp) - lvl--; - - oneValid = true; - break; - } - if (!oneValid) - break; - } - - // Remove future gen pre-evolutions; no Munchlax from a Gen3 Snorlax, no Pichu from a Gen1-only Raichu, etc - ref var last = ref evos[ctr - 1]; - if (last.Species > maxSpeciesID) - { - for (int i = 0; i < ctr; i++) - { - if (evos[i].Species > maxSpeciesID) - continue; - ctr--; - break; + var link = new EvolutionLink(sSpecies, sForm, evo); + yield return new KeyValuePair(key, link); } } - - // Last species is the wild/hatched species, the minimum level is because it has not evolved from previous species - var result = evos[..ctr]; - last = ref result[^1]; - last = last with { LevelMin = levelMin, LevelUpRequired = 0 }; - - // Rectify minimum levels - RectifyMinimumLevels(result); - - return result.ToArray(); - } - - private static void RectifyMinimumLevels(Span result) - { - for (int i = result.Length - 2; i >= 0; i--) - { - ref var evo = ref result[i]; - var prev = result[i + 1]; - var min = (byte)Math.Max(prev.LevelMin + evo.LevelUpRequired, evo.LevelMin); - evo = evo with { LevelMin = min }; - } - } - - private static void UpdateMinValues(Span evos, EvolutionMethod evo, byte minLevel) - { - ref var last = ref evos[^1]; - if (!evo.RequiresLevelUp) - { - // Evolutions like elemental stones, trade, etc - last = last with { LevelMin = minLevel }; - return; - } - if (evo.Level == 0) - { - // Friendship based Level Up Evolutions, Pichu -> Pikachu, Eevee -> Umbreon, etc - last = last with { LevelMin = (byte)(minLevel + 1) }; - - // Raichu from Pikachu would have a minimum level of 1; accounting for Pichu (level up required) results in a minimum level of 2 - if (evos.Length > 1) - { - ref var first = ref evos[0]; - if (!first.RequiresLvlUp) - first = first with { LevelMin = (byte)(minLevel + 1) }; - } - } - else // level up evolutions - { - last = last with { LevelMin = evo.Level }; - - if (evos.Length > 1) - { - ref var first = ref evos[0]; - if (first.RequiresLvlUp) - { - // Pokemon like Crobat, its minimum level is Golbat minimum level + 1 - if (first.LevelMin <= evo.Level) - first = first with {LevelMin = (byte)(evo.Level + 1) }; - } - else - { - // Pokemon like Nidoqueen who evolve with an evolution stone, minimum level is prior evolution minimum level - if (first.LevelMin < evo.Level) - first = first with { LevelMin = evo.Level }; - } - } - } - last = last with { LevelUpRequired = evo.RequiresLevelUp ? (byte)1 : (byte)0 }; - } - - /// - /// Links a to the source and that the method can be triggered from. - /// - private sealed class EvolutionLink - { - public readonly int Species; - public readonly int Form; - public readonly EvolutionMethod Method; - public Func? IsBanned { private get; set; } - - public EvolutionLink(int species, int form, EvolutionMethod method) - { - Species = species; - Form = form; - Method = method; - } - - /// - /// Checks if the is allowed. - /// - /// Entity to check - /// True if banned, false if allowed. - public bool IsEvolutionBanned(PKM pkm) => IsBanned != null && IsBanned(pkm); } } + + private IEnumerable> CreateTree() + { + for (int sSpecies = 1; sSpecies <= MaxSpeciesTree; sSpecies++) + { + var fc = Personal[sSpecies].FormCount; + for (int sForm = 0; sForm < fc; sForm++) + { + var index = Personal.GetFormIndex(sSpecies, sForm); + var evos = Entries[index]; + foreach (var evo in evos) + { + var dSpecies = evo.Species; + if (dSpecies == 0) + break; + + var dForm = evo.GetDestinationForm(sForm); + var key = GetLookupKey(dSpecies, dForm); + + var link = new EvolutionLink(sSpecies, sForm, evo); + yield return new KeyValuePair(key, link); + } + } + } + } + + private static IReadOnlyList GetEntries(ReadOnlySpan data, GameVersion game) => game switch + { + Gen1 => EvolutionSet1.GetArray(data, 151), + Gen2 => EvolutionSet1.GetArray(data, 251), + Gen3 => EvolutionSet3.GetArray(data), + Gen4 => EvolutionSet4.GetArray(data), + Gen5 => EvolutionSet5.GetArray(data), + _ => throw new ArgumentOutOfRangeException(nameof(game)), + }; + + private static IReadOnlyList GetEntries(BinLinkerAccessor data, GameVersion game) => game switch + { + Gen6 => EvolutionSet6.GetArray(data), + Gen7 => EvolutionSet7.GetArray(data), + Gen8 => EvolutionSet7.GetArray(data), + _ => throw new ArgumentOutOfRangeException(nameof(game)), + }; + + private void FixEvoTreeSM() + { + // Sun/Moon lack Ultra's Kantonian evolution methods. + BanEvo((int)Species.Raichu, 0, pk => pk.IsUntraded && pk.SM); + BanEvo((int)Species.Marowak, 0, pk => pk.IsUntraded && pk.SM); + BanEvo((int)Species.Exeggutor, 0, pk => pk.IsUntraded && pk.SM); + } + + private void FixEvoTreeSS() + { + // Gigantamax Pikachu, Meowth-0, and Eevee are prevented from evolving. + // Raichu cannot be evolved to the Alolan variant at this time. + BanEvo((int)Species.Raichu, 0, pk => pk is IGigantamax {CanGigantamax: true}); + BanEvo((int)Species.Raichu, 1, pk => (pk is IGigantamax {CanGigantamax: true}) || pk.Version is (int)GO or >= (int)GP); + BanEvo((int)Species.Persian, 0, pk => pk is IGigantamax {CanGigantamax: true}); + BanEvo((int)Species.Persian, 1, pk => pk is IGigantamax {CanGigantamax: true}); + BanEvo((int)Species.Perrserker, 0, pk => pk is IGigantamax {CanGigantamax: true}); + + BanEvo((int)Species.Exeggutor, 1, pk => pk.Version is (int)GO or >= (int)GP); + BanEvo((int)Species.Marowak, 1, pk => pk.Version is (int)GO or >= (int)GP); + BanEvo((int)Species.Weezing, 0, pk => pk.Version >= (int)SW); + BanEvo((int)Species.MrMime, 0, pk => pk.Version >= (int)SW); + + foreach (var s in GetEvolutions((int)Species.Eevee, 0)) // Eeveelutions + BanEvo(s, 0, pk => pk is IGigantamax {CanGigantamax: true}); + } + + private void FixEvoTreeBS() + { + BanEvo((int)Species.Glaceon, 0, pk => pk.CurrentLevel == pk.Met_Level); // Ice Stone is unreleased, requires Route 217 Ice Rock Level Up instead + BanEvo((int)Species.Milotic, 0, pk => pk is IContestStats { CNT_Beauty: < 170 } || pk.CurrentLevel == pk.Met_Level); // Prism Scale is unreleased, requires 170 Beauty Level Up instead + } + + private void BanEvo(int species, int form, Func func) + { + var key = GetLookupKey(species, form); + var node = Lineage[key]; + foreach (var link in node) + link.IsBanned = func; + } + + #endregion + + /// + /// Gets a list of evolutions for the input by checking each evolution in the chain. + /// + /// Pokémon data to check with. + /// Maximum level to permit before the chain breaks. + /// Maximum species ID to permit within the chain. + /// Ignores an evolution's criteria, causing the returned list to have all possible evolutions. + /// Minimum level to permit before the chain breaks. + public EvoCriteria[] GetValidPreEvolutions(PKM pk, byte maxLevel, int maxSpeciesOrigin = -1, bool skipChecks = false, byte minLevel = 1) + { + if (maxSpeciesOrigin <= 0) + maxSpeciesOrigin = GetMaxSpeciesOrigin(pk); + + ushort species = (ushort)pk.Species; + byte form = (byte)pk.Form; + + return GetExplicitLineage(species, form, pk, minLevel, maxLevel, maxSpeciesOrigin, skipChecks); + } + + public bool IsSpeciesDerivedFrom(int species, int form, int otherSpecies, int otherForm, bool ignoreForm = true) + { + var evos = GetEvolutionsAndPreEvolutions(species, form); + foreach (var evo in evos) + { + var s = evo & 0x3FF; + if (s != otherSpecies) + continue; + if (ignoreForm) + return true; + var f = evo >> 11; + return f == otherForm; + } + return false; + } + + /// + /// Gets all species the - can evolve to & from, yielded in order of increasing evolution stage. + /// + /// Species ID + /// Form ID + /// Enumerable of species IDs (with the Form IDs included, left shifted by 11). + public IEnumerable GetEvolutionsAndPreEvolutions(int species, int form) + { + foreach (var s in GetPreEvolutions(species, form)) + yield return s; + yield return species; + foreach (var s in GetEvolutions(species, form)) + yield return s; + } + + public int GetBaseSpeciesForm(int species, int form, int skip = 0) + { + var chain = GetEvolutionsAndPreEvolutions(species, form); + foreach (var c in chain) + { + if (skip == 0) + return c; + skip--; + } + return species | (form << 11); + } + + /// + /// Gets all species the - can evolve from, yielded in order of increasing evolution stage. + /// + /// Species ID + /// Form ID + /// Enumerable of species IDs (with the Form IDs included, left shifted by 11). + public IEnumerable GetPreEvolutions(int species, int form) + { + int index = GetLookupKey(species, form); + var node = Lineage[index]; + foreach (var method in node) + { + var s = method.Species; + if (s == 0) + continue; + var f = method.Form; + var preEvolutions = GetPreEvolutions(s, f); + foreach (var preEvo in preEvolutions) + yield return preEvo; + yield return s | (f << 11); + } + } + + /// + /// Gets all species the - can evolve to, yielded in order of increasing evolution stage. + /// + /// Species ID + /// Form ID + /// Enumerable of species IDs (with the Form IDs included, left shifted by 11). + public IEnumerable GetEvolutions(int species, int form) + { + int format = Game - Gen1 + 1; + int index = format < 7 ? species : Personal.GetFormIndex(species, form); + var evos = Entries[index]; + foreach (var method in evos) + { + var s = method.Species; + if (s == 0) + continue; + var f = method.GetDestinationForm(form); + yield return s | (f << 11); + var nextEvolutions = GetEvolutions(s, f); + foreach (var nextEvo in nextEvolutions) + yield return nextEvo; + } + } + + /// + /// Generates the reverse evolution path for the input . + /// + /// Entity Species to begin the chain + /// Entity Form to begin the chain + /// Entity data + /// Minimum level + /// Maximum level + /// Clamp for maximum species ID + /// Skip the secondary checks that validate the evolution + private EvoCriteria[] GetExplicitLineage(ushort species, byte form, PKM pk, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks) + { + if (pk.IsEgg && !skipChecks) + { + return new[] + { + new EvoCriteria{ Species = species, Form = form, LevelMax = levelMax, LevelMin = levelMax }, + }; + } + + // Shedinja's evolution case can be a little tricky; hard-code handling. + if (species == (int)Species.Shedinja && levelMax >= 20 && (!pk.HasOriginalMetLocation || levelMin < levelMax)) + { + var min = Math.Max(levelMin, (byte)20); + return new[] + { + new EvoCriteria { Species = (ushort)Species.Shedinja, LevelMax = levelMax, LevelMin = min, Method = EvolutionType.LevelUp }, + new EvoCriteria { Species = (ushort)Species.Nincada, LevelMax = levelMax, LevelMin = levelMin }, + }; + } + return GetLineage(species, form, pk, levelMin, levelMax, maxSpeciesID, skipChecks); + } + + private EvoCriteria[] GetLineage(int species, int form, PKM pk, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks) + { + var lvl = levelMax; + var first = new EvoCriteria { Species = (ushort)species, Form = (byte)form, LevelMax = lvl }; + + const int maxEvolutions = 3; + Span evos = stackalloc EvoCriteria[maxEvolutions]; + evos[0] = first; + + switch (species) + { + case (int)Species.Silvally: + form = 0; + break; + } + + // There aren't any circular evolution paths, and all lineages have at most 3 evolutions total. + // There aren't any convergent evolution paths, so only yield the first connection. + int ctr = 1; + while (true) + { + var key = GetLookupKey(species, form); + bool oneValid = false; + var node = Lineage[key]; + + foreach (var link in node) + { + if (link.IsEvolutionBanned(pk) && !skipChecks) + continue; + + var evo = link.Method; + if (!evo.Valid(pk, lvl, skipChecks)) + continue; + + if (evo.RequiresLevelUp && levelMin >= lvl) + break; // impossible evolution + + UpdateMinValues(evos[..ctr], evo, levelMin); + + species = link.Species; + form = link.Form; + evos[ctr++] = evo.GetEvoCriteria((ushort)species, (byte)form, lvl); + if (evo.RequiresLevelUp) + lvl--; + + oneValid = true; + break; + } + if (!oneValid) + break; + } + + // Remove future gen pre-evolutions; no Munchlax from a Gen3 Snorlax, no Pichu from a Gen1-only Raichu, etc + ref var last = ref evos[ctr - 1]; + if (last.Species > maxSpeciesID) + { + for (int i = 0; i < ctr; i++) + { + if (evos[i].Species > maxSpeciesID) + continue; + ctr--; + break; + } + } + + // Last species is the wild/hatched species, the minimum level is because it has not evolved from previous species + var result = evos[..ctr]; + last = ref result[^1]; + last = last with { LevelMin = levelMin, LevelUpRequired = 0 }; + + // Rectify minimum levels + RectifyMinimumLevels(result); + + return result.ToArray(); + } + + private static void RectifyMinimumLevels(Span result) + { + for (int i = result.Length - 2; i >= 0; i--) + { + ref var evo = ref result[i]; + var prev = result[i + 1]; + var min = (byte)Math.Max(prev.LevelMin + evo.LevelUpRequired, evo.LevelMin); + evo = evo with { LevelMin = min }; + } + } + + private static void UpdateMinValues(Span evos, EvolutionMethod evo, byte minLevel) + { + ref var last = ref evos[^1]; + if (!evo.RequiresLevelUp) + { + // Evolutions like elemental stones, trade, etc + last = last with { LevelMin = minLevel }; + return; + } + if (evo.Level == 0) + { + // Friendship based Level Up Evolutions, Pichu -> Pikachu, Eevee -> Umbreon, etc + last = last with { LevelMin = (byte)(minLevel + 1) }; + + // Raichu from Pikachu would have a minimum level of 1; accounting for Pichu (level up required) results in a minimum level of 2 + if (evos.Length > 1) + { + ref var first = ref evos[0]; + if (!first.RequiresLvlUp) + first = first with { LevelMin = (byte)(minLevel + 1) }; + } + } + else // level up evolutions + { + last = last with { LevelMin = evo.Level }; + + if (evos.Length > 1) + { + ref var first = ref evos[0]; + if (first.RequiresLvlUp) + { + // Pokemon like Crobat, its minimum level is Golbat minimum level + 1 + if (first.LevelMin <= evo.Level) + first = first with {LevelMin = (byte)(evo.Level + 1) }; + } + else + { + // Pokemon like Nidoqueen who evolve with an evolution stone, minimum level is prior evolution minimum level + if (first.LevelMin < evo.Level) + first = first with { LevelMin = evo.Level }; + } + } + } + last = last with { LevelUpRequired = evo.RequiresLevelUp ? (byte)1 : (byte)0 }; + } + + /// + /// Links a to the source and that the method can be triggered from. + /// + private sealed class EvolutionLink + { + public readonly int Species; + public readonly int Form; + public readonly EvolutionMethod Method; + public Func? IsBanned { private get; set; } + + public EvolutionLink(int species, int form, EvolutionMethod method) + { + Species = species; + Form = form; + Method = method; + } + + /// + /// Checks if the is allowed. + /// + /// Entity to check + /// True if banned, false if allowed. + public bool IsEvolutionBanned(PKM pk) => IsBanned != null && IsBanned(pk); + } } diff --git a/PKHeX.Core/Legality/Evolutions/EvolutionType.cs b/PKHeX.Core/Legality/Evolutions/EvolutionType.cs index ff9d20b5e..1b55c7fe2 100644 --- a/PKHeX.Core/Legality/Evolutions/EvolutionType.cs +++ b/PKHeX.Core/Legality/Evolutions/EvolutionType.cs @@ -1,69 +1,69 @@ +using System; using static PKHeX.Core.EvolutionType; -namespace PKHeX.Core -{ - public enum EvolutionType : byte - { - None = 0, - LevelUpFriendship = 1, - LevelUpFriendshipMorning = 2, - LevelUpFriendshipNight = 3, - LevelUp = 4, - Trade = 5, - TradeHeldItem = 6, - TradeShelmetKarrablast = 7, - UseItem = 8, - LevelUpATK = 9, - LevelUpAeqD = 10, - LevelUpDEF = 11, - LevelUpECl5 = 12, - LevelUpECgeq5 = 13, - LevelUpNinjask = 14, - LevelUpShedinja = 15, - LevelUpBeauty = 16, - UseItemMale = 17, - UseItemFemale = 18, - LevelUpHeldItemDay = 19, - LevelUpHeldItemNight = 20, - LevelUpKnowMove = 21, - LevelUpWithTeammate = 22, - LevelUpMale = 23, - LevelUpFemale = 24, - LevelUpElectric = 25, - LevelUpForest = 26, - LevelUpCold = 27, - LevelUpInverted = 28, - LevelUpAffection50MoveType = 29, - LevelUpMoveType = 30, - LevelUpWeather = 31, - LevelUpMorning = 32, - LevelUpNight = 33, - LevelUpFormFemale1 = 34, - UNUSED = 35, - LevelUpVersion = 36, - LevelUpVersionDay = 37, - LevelUpVersionNight = 38, - LevelUpSummit = 39, - LevelUpDusk = 40, - LevelUpWormhole = 41, - UseItemWormhole = 42, - CriticalHitsInBattle = 43, // Sirfetch'd - HitPointsLostInBattle = 44, // Runerigus - Spin = 45, // Alcremie - LevelUpNatureAmped = 46, // Toxtricity - LevelUpNatureLowKey = 47, // Toxtricity - TowerOfDarkness = 48, // Urshifu - TowerOfWaters = 49, // Urshifu - UseItemFullMoon = 50, // Ursaluna - UseAgileStyleMoves = 51, // Wyrdeer - UseStrongStyleMoves = 52, // Overqwil - RecoilDamageMale = 53, // Basculegion-0 - RecoilDamageFemale = 54, // Basculegion-1 - } +namespace PKHeX.Core; - public static class EvolutionTypeExtensions - { - public static bool IsTrade(this EvolutionType t) => t is Trade or TradeHeldItem or TradeShelmetKarrablast; - public static bool IsLevelUpRequired(this EvolutionType t) => t.ToString().StartsWith("LevelUp"); // don't use this - } -} \ No newline at end of file +public enum EvolutionType : byte +{ + None = 0, + LevelUpFriendship = 1, + LevelUpFriendshipMorning = 2, + LevelUpFriendshipNight = 3, + LevelUp = 4, + Trade = 5, + TradeHeldItem = 6, + TradeShelmetKarrablast = 7, + UseItem = 8, + LevelUpATK = 9, + LevelUpAeqD = 10, + LevelUpDEF = 11, + LevelUpECl5 = 12, + LevelUpECgeq5 = 13, + LevelUpNinjask = 14, + LevelUpShedinja = 15, + LevelUpBeauty = 16, + UseItemMale = 17, + UseItemFemale = 18, + LevelUpHeldItemDay = 19, + LevelUpHeldItemNight = 20, + LevelUpKnowMove = 21, + LevelUpWithTeammate = 22, + LevelUpMale = 23, + LevelUpFemale = 24, + LevelUpElectric = 25, + LevelUpForest = 26, + LevelUpCold = 27, + LevelUpInverted = 28, + LevelUpAffection50MoveType = 29, + LevelUpMoveType = 30, + LevelUpWeather = 31, + LevelUpMorning = 32, + LevelUpNight = 33, + LevelUpFormFemale1 = 34, + UNUSED = 35, + LevelUpVersion = 36, + LevelUpVersionDay = 37, + LevelUpVersionNight = 38, + LevelUpSummit = 39, + LevelUpDusk = 40, + LevelUpWormhole = 41, + UseItemWormhole = 42, + CriticalHitsInBattle = 43, // Sirfetch'd + HitPointsLostInBattle = 44, // Runerigus + Spin = 45, // Alcremie + LevelUpNatureAmped = 46, // Toxtricity + LevelUpNatureLowKey = 47, // Toxtricity + TowerOfDarkness = 48, // Urshifu + TowerOfWaters = 49, // Urshifu + UseItemFullMoon = 50, // Ursaluna + UseAgileStyleMoves = 51, // Wyrdeer + UseStrongStyleMoves = 52, // Overqwil + RecoilDamageMale = 53, // Basculegion-0 + RecoilDamageFemale = 54, // Basculegion-1 +} + +public static class EvolutionTypeExtensions +{ + public static bool IsTrade(this EvolutionType t) => t is Trade or TradeHeldItem or TradeShelmetKarrablast; + public static bool IsLevelUpRequired(this EvolutionType t) => t.ToString().StartsWith("LevelUp", StringComparison.Ordinal); // don't use this +} diff --git a/PKHeX.Core/Legality/Formatting/BaseLegalityFormatter.cs b/PKHeX.Core/Legality/Formatting/BaseLegalityFormatter.cs index b4658832b..21547dd6e 100644 --- a/PKHeX.Core/Legality/Formatting/BaseLegalityFormatter.cs +++ b/PKHeX.Core/Legality/Formatting/BaseLegalityFormatter.cs @@ -2,72 +2,71 @@ using System.Collections.Generic; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Default formatter for Legality Result displays. +/// +public sealed class BaseLegalityFormatter : ILegalityFormatter { - /// - /// Default formatter for Legality Result displays. - /// - public sealed class BaseLegalityFormatter : ILegalityFormatter + public string GetReport(LegalityAnalysis l) { - public string GetReport(LegalityAnalysis l) - { - if (l.Valid) - return L_ALegal; - if (!l.Parsed) - return L_AnalysisUnavailable; + if (l.Valid) + return L_ALegal; + if (!l.Parsed) + return L_AnalysisUnavailable; - var lines = GetLegalityReportLines(l); - return string.Join(Environment.NewLine, lines); - } + var lines = GetLegalityReportLines(l); + return string.Join(Environment.NewLine, lines); + } - public string GetReportVerbose(LegalityAnalysis l) - { - if (!l.Parsed) - return L_AnalysisUnavailable; + public string GetReportVerbose(LegalityAnalysis l) + { + if (!l.Parsed) + return L_AnalysisUnavailable; - var lines = GetVerboseLegalityReportLines(l); - return string.Join(Environment.NewLine, lines); - } + var lines = GetVerboseLegalityReportLines(l); + return string.Join(Environment.NewLine, lines); + } - private static List GetLegalityReportLines(LegalityAnalysis l) - { - var lines = new List(); - var info = l.Info; - var pkm = l.pkm; + private static List GetLegalityReportLines(LegalityAnalysis l) + { + var lines = new List(); + var info = l.Info; + var pk = l.Entity; - LegalityFormatting.AddMoves(info.Moves, lines, pkm.Format, false); - if (pkm.Format >= 6) - LegalityFormatting.AddRelearn(info.Relearn, lines, false); - LegalityFormatting.AddSecondaryChecksInvalid(l.Results, lines); - return lines; - } + LegalityFormatting.AddMoves(info.Moves, lines, pk.Format, false); + if (pk.Format >= 6) + LegalityFormatting.AddRelearn(info.Relearn, lines, false); + LegalityFormatting.AddSecondaryChecksInvalid(l.Results, lines); + return lines; + } - private static IReadOnlyList GetVerboseLegalityReportLines(LegalityAnalysis l) - { - var lines = l.Valid ? new List {L_ALegal} : GetLegalityReportLines(l); - var info = l.Info; - var pkm = l.pkm; - const string separator = "==="; - lines.Add(separator); + private static IReadOnlyList GetVerboseLegalityReportLines(LegalityAnalysis l) + { + var lines = l.Valid ? new List {L_ALegal} : GetLegalityReportLines(l); + var info = l.Info; + var pk = l.Entity; + const string separator = "==="; + lines.Add(separator); + lines.Add(string.Empty); + int initialCount = lines.Count; + + var format = pk.Format; + LegalityFormatting.AddMoves(info.Moves, lines, format, true); + + if (format >= 6) + LegalityFormatting.AddRelearn(info.Relearn, lines, true); + + if (lines.Count != initialCount) // move info added, break for next section lines.Add(string.Empty); - int initialCount = lines.Count; - var format = pkm.Format; - LegalityFormatting.AddMoves(info.Moves, lines, format, true); + LegalityFormatting.AddSecondaryChecksValid(l.Results, lines); - if (format >= 6) - LegalityFormatting.AddRelearn(info.Relearn, lines, true); + lines.Add(separator); + lines.Add(string.Empty); + LegalityFormatting.AddEncounterInfo(l, lines); - if (lines.Count != initialCount) // move info added, break for next section - lines.Add(string.Empty); - - LegalityFormatting.AddSecondaryChecksValid(l.Results, lines); - - lines.Add(separator); - lines.Add(string.Empty); - LegalityFormatting.AddEncounterInfo(l, lines); - - return lines; - } + return lines; } } diff --git a/PKHeX.Core/Legality/Formatting/ILegalityFormatter.cs b/PKHeX.Core/Legality/Formatting/ILegalityFormatter.cs index 4c9e4b6ae..60a50abc7 100644 --- a/PKHeX.Core/Legality/Formatting/ILegalityFormatter.cs +++ b/PKHeX.Core/Legality/Formatting/ILegalityFormatter.cs @@ -1,18 +1,17 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Formats legality results into a for display. +/// +public interface ILegalityFormatter { /// - /// Formats legality results into a for display. + /// Gets a small summary of the legality analysis. /// - public interface ILegalityFormatter - { - /// - /// Gets a small summary of the legality analysis. - /// - string GetReport(LegalityAnalysis l); + string GetReport(LegalityAnalysis l); - /// - /// Gets a verbose summary of the legality analysis. - /// - string GetReportVerbose(LegalityAnalysis l); - } + /// + /// Gets a verbose summary of the legality analysis. + /// + string GetReportVerbose(LegalityAnalysis l); } diff --git a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs index b496e21c8..eb12f389c 100644 --- a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs +++ b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs @@ -1,493 +1,492 @@ // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Legality Check Message Strings to indicate why certain values are flagged. +/// +public static class LegalityCheckStrings { - /// - /// Legality Check Message Strings to indicate why certain values are flagged. - /// - public static class LegalityCheckStrings - { - // Message String Name format: L/F[Category][Summary] - #region General Strings + // Message String Name format: L/F[Category][Summary] + #region General Strings - /// Default text for indicating validity. - public static string L_AValid { get; set; } = "Valid."; + /// Default text for indicating validity. + public static string L_AValid { get; set; } = "Valid."; - /// Default text for indicating legality. - public static string L_ALegal { get; set; } = "Legal!"; + /// Default text for indicating legality. + public static string L_ALegal { get; set; } = "Legal!"; - /// Default text for indicating an error has occurred. - public static string L_AError { get; set; } = "Internal error."; + /// Default text for indicating an error has occurred. + public static string L_AError { get; set; } = "Internal error."; - /// Analysis not available for the - public static string L_AnalysisUnavailable { get; set; } = "Analysis not available for this Pokémon."; + /// Analysis not available for the + public static string L_AnalysisUnavailable { get; set; } = "Analysis not available for this Pokémon."; - /// Format text for exporting a legality check result. - public static string L_F0_1 { get; set; } = "{0}: {1}"; + /// Format text for exporting a legality check result. + public static string L_F0_1 { get; set; } = "{0}: {1}"; - /// Format text for exporting a legality check result for a Move. - public static string L_F0_M_1_2 { get; set; } = "{0} Move {1}: {2}"; + /// Format text for exporting a legality check result for a Move. + public static string L_F0_M_1_2 { get; set; } = "{0} Move {1}: {2}"; - /// Format text for exporting a legality check result for a Relearn Move. - public static string L_F0_RM_1_2 { get; set; } = "{0} Relearn Move {1}: {2}"; + /// Format text for exporting a legality check result for a Relearn Move. + public static string L_F0_RM_1_2 { get; set; } = "{0} Relearn Move {1}: {2}"; - /// Format text for exporting the type of Encounter that was matched for the - public static string L_FEncounterType_0 { get; set; } = "Encounter Type: {0}"; + /// Format text for exporting the type of Encounter that was matched for the + public static string L_FEncounterType_0 { get; set; } = "Encounter Type: {0}"; - /// Format text for exporting the that was matched for the - public static string L_FOriginSeed_0 { get; set; } = "Origin Seed: {0}"; + /// Format text for exporting the that was matched for the + public static string L_FOriginSeed_0 { get; set; } = "Origin Seed: {0}"; - /// Format text for exporting the that was matched for the - public static string L_FPIDType_0 { get; set; } = "PID Type: {0}"; + /// Format text for exporting the that was matched for the + public static string L_FPIDType_0 { get; set; } = "PID Type: {0}"; - /// Severity string for - public static string L_SIndeterminate { get; set; } = "Indeterminate"; + /// Severity string for + public static string L_SIndeterminate { get; set; } = "Indeterminate"; - /// Severity string for - public static string L_SInvalid { get; set; } = "Invalid"; + /// Severity string for + public static string L_SInvalid { get; set; } = "Invalid"; - /// Severity string for - public static string L_SFishy { get; set; } = "Fishy"; + /// Severity string for + public static string L_SFishy { get; set; } = "Fishy"; - /// Severity string for - public static string L_SValid { get; set; } = "Valid"; + /// Severity string for + public static string L_SValid { get; set; } = "Valid"; - /// Severity string for anything not implemented. - public static string L_SNotImplemented { get; set; } = "Not Implemented"; + /// Severity string for anything not implemented. + public static string L_SNotImplemented { get; set; } = "Not Implemented"; - public static string L_XOT { get; set; } = "OT"; - public static string L_XHT { get; set; } = "HT"; - public static string L_XKorean { get; set; } = "Korean"; - public static string L_XKoreanNon { get; set; } = "Non-Korean"; - public static string L_XEnigmaBerry_0 { get; set; } = "{0} Berry"; - public static string L_XMatches0_1 { get; set; } = "Matches: {0} {1}"; - public static string L_XWurmpleEvo_0 { get; set; } = "Wurmple Evolution: {0}"; + public static string L_XOT { get; set; } = "OT"; + public static string L_XHT { get; set; } = "HT"; + public static string L_XKorean { get; set; } = "Korean"; + public static string L_XKoreanNon { get; set; } = "Non-Korean"; + public static string L_XEnigmaBerry_0 { get; set; } = "{0} Berry"; + public static string L_XMatches0_1 { get; set; } = "Matches: {0} {1}"; + public static string L_XWurmpleEvo_0 { get; set; } = "Wurmple Evolution: {0}"; - public static string LAbilityCapsuleUsed { get; set; } = "Ability available with Ability Capsule."; - public static string LAbilityPatchUsed { get; set; } = "Ability available with Ability Patch."; - public static string LAbilityFlag { get; set; } = "Ability matches ability number."; - public static string LAbilityHiddenFail { get; set; } = "Hidden Ability mismatch for encounter type."; - public static string LAbilityHiddenUnavailable { get; set; } = "Hidden Ability not available."; - public static string LAbilityMismatch { get; set; } = "Ability mismatch for encounter."; - public static string LAbilityMismatch3 { get; set; } = "Ability does not match Generation 3 species ability."; - public static string LAbilityMismatchFlag { get; set; } = "Ability does not match ability number."; - public static string LAbilityMismatchGift { get; set; } = "Ability does not match Mystery Gift."; - public static string LAbilityMismatchGrotto { get; set; } = "Hidden Grotto captures should have Hidden Ability."; - public static string LAbilityMismatchHordeSafari { get; set; } = "Hidden Ability on non-horde/friend safari wild encounter."; - public static string LAbilityMismatchPID { get; set; } = "Ability does not match PID."; - public static string LAbilityMismatchSOS { get; set; } = "Hidden Ability on non-SOS wild encounter."; - public static string LAbilityUnexpected { get; set; } = "Ability is not valid for species/form."; + public static string LAbilityCapsuleUsed { get; set; } = "Ability available with Ability Capsule."; + public static string LAbilityPatchUsed { get; set; } = "Ability available with Ability Patch."; + public static string LAbilityFlag { get; set; } = "Ability matches ability number."; + public static string LAbilityHiddenFail { get; set; } = "Hidden Ability mismatch for encounter type."; + public static string LAbilityHiddenUnavailable { get; set; } = "Hidden Ability not available."; + public static string LAbilityMismatch { get; set; } = "Ability mismatch for encounter."; + public static string LAbilityMismatch3 { get; set; } = "Ability does not match Generation 3 species ability."; + public static string LAbilityMismatchFlag { get; set; } = "Ability does not match ability number."; + public static string LAbilityMismatchGift { get; set; } = "Ability does not match Mystery Gift."; + public static string LAbilityMismatchGrotto { get; set; } = "Hidden Grotto captures should have Hidden Ability."; + public static string LAbilityMismatchHordeSafari { get; set; } = "Hidden Ability on non-horde/friend safari wild encounter."; + public static string LAbilityMismatchPID { get; set; } = "Ability does not match PID."; + public static string LAbilityMismatchSOS { get; set; } = "Hidden Ability on non-SOS wild encounter."; + public static string LAbilityUnexpected { get; set; } = "Ability is not valid for species/form."; - public static string LAwakenedCap { get; set; } = "Individual AV cannot be greater than {0}."; - public static string LAwakenedShouldBeValue { get; set; } = "Individual AV should be greater than {0}."; - public static string LAwakenedEXPIncreased { get; set; } = "All AVs are zero, but leveled above Met Level."; + public static string LAwakenedCap { get; set; } = "Individual AV cannot be greater than {0}."; + public static string LAwakenedShouldBeValue { get; set; } = "Individual AV should be greater than {0}."; + public static string LAwakenedEXPIncreased { get; set; } = "All AVs are zero, but leveled above Met Level."; - public static string LBallAbility { get; set; } = "Can't obtain Hidden Ability with Ball."; - public static string LBallEggCherish { get; set; } = "Can't have Cherish Ball for regular Egg."; - public static string LBallEggMaster { get; set; } = "Can't have Master Ball for regular Egg."; - public static string LBallEnc { get; set; } = "Correct ball for encounter type."; - public static string LBallEncMismatch { get; set; } = "Can't have ball for encounter type."; - public static string LBallHeavy { get; set; } = "Can't have Heavy Ball for light, low-catch rate species (Gen VII)."; - public static string LBallNone { get; set; } = "No check satisfied, assuming illegal."; - public static string LBallSpecies { get; set; } = "Can't obtain species in Ball."; - public static string LBallSpeciesPass { get; set; } = "Ball possible for species."; - public static string LBallUnavailable { get; set; } = "Ball unobtainable in origin Generation."; + public static string LBallAbility { get; set; } = "Can't obtain Hidden Ability with Ball."; + public static string LBallEggCherish { get; set; } = "Can't have Cherish Ball for regular Egg."; + public static string LBallEggMaster { get; set; } = "Can't have Master Ball for regular Egg."; + public static string LBallEnc { get; set; } = "Correct ball for encounter type."; + public static string LBallEncMismatch { get; set; } = "Can't have ball for encounter type."; + public static string LBallHeavy { get; set; } = "Can't have Heavy Ball for light, low-catch rate species (Gen VII)."; + public static string LBallNone { get; set; } = "No check satisfied, assuming illegal."; + public static string LBallSpecies { get; set; } = "Can't obtain species in Ball."; + public static string LBallSpeciesPass { get; set; } = "Ball possible for species."; + public static string LBallUnavailable { get; set; } = "Ball unobtainable in origin Generation."; - public static string LContestZero { get; set; } = "Contest Stats should be 0."; - public static string LContestZeroSheen { get; set; } = "Contest Stat Sheen should be 0."; - public static string LContestSheenTooLow_0 { get; set; } = "Contest Stat Sheen should be >= {0}."; - public static string LContestSheenTooHigh_0 { get; set; } = "Contest Stat Sheen should be <= {0}."; + public static string LContestZero { get; set; } = "Contest Stats should be 0."; + public static string LContestZeroSheen { get; set; } = "Contest Stat Sheen should be 0."; + public static string LContestSheenTooLow_0 { get; set; } = "Contest Stat Sheen should be >= {0}."; + public static string LContestSheenTooHigh_0 { get; set; } = "Contest Stat Sheen should be <= {0}."; - public static string LDateOutsideDistributionWindow { get; set; } = "Met Date is outside of distribution window."; + public static string LDateOutsideDistributionWindow { get; set; } = "Met Date is outside of distribution window."; - public static string LEggContest { get; set; } = "Cannot increase Contest Stats of an Egg."; - public static string LEggEXP { get; set; } = "Eggs cannot receive experience."; - public static string LEggFMetLevel_0 { get; set; } = "Invalid Met Level, expected {0}."; - public static string LEggHatchCycles { get; set; } = "Invalid Egg hatch cycles."; - public static string LEggLocation { get; set; } = "Able to hatch an Egg at Met Location."; - public static string LEggLocationInvalid { get; set; } = "Can't hatch an Egg at Met Location."; - public static string LEggLocationNone { get; set; } = "Invalid Egg Location, expected none."; - public static string LEggLocationPalPark { get; set; } = "Invalid Met Location, expected Pal Park."; - public static string LEggLocationTrade { get; set; } = "Able to hatch a traded Egg at Met Location."; - public static string LEggLocationTradeFail { get; set; } = "Invalid Egg Location, shouldn't be 'traded' while an Egg."; - public static string LEggMetLocationFail { get; set; } = "Can't obtain Egg from Egg Location."; - public static string LEggNature { get; set; } = "Eggs cannot have their Stat Nature changed."; - public static string LEggPokeathlon { get; set; } = "Eggs cannot have Pokéathlon stats."; - public static string LEggPokerus { get; set; } = "Eggs cannot be infected with Pokérus."; - public static string LEggPP { get; set; } = "Eggs cannot have modified move PP counts."; - public static string LEggPPUp { get; set; } = "Cannot apply PP Ups to an Egg."; - public static string LEggRelearnFlags { get; set; } = "Expected no Relearn Move Flags."; - public static string LEggShinyLeaf { get; set; } = "Eggs cannot have Shiny Leaf/Crown."; - public static string LEggShinyPokeStar { get; set; } = "Eggs cannot be a Pokéstar Studios star."; - public static string LEggSpecies { get; set; } = "Can't obtain Egg for this species."; - public static string LEggUnhatched { get; set; } = "Valid un-hatched Egg."; + public static string LEggContest { get; set; } = "Cannot increase Contest Stats of an Egg."; + public static string LEggEXP { get; set; } = "Eggs cannot receive experience."; + public static string LEggFMetLevel_0 { get; set; } = "Invalid Met Level, expected {0}."; + public static string LEggHatchCycles { get; set; } = "Invalid Egg hatch cycles."; + public static string LEggLocation { get; set; } = "Able to hatch an Egg at Met Location."; + public static string LEggLocationInvalid { get; set; } = "Can't hatch an Egg at Met Location."; + public static string LEggLocationNone { get; set; } = "Invalid Egg Location, expected none."; + public static string LEggLocationPalPark { get; set; } = "Invalid Met Location, expected Pal Park."; + public static string LEggLocationTrade { get; set; } = "Able to hatch a traded Egg at Met Location."; + public static string LEggLocationTradeFail { get; set; } = "Invalid Egg Location, shouldn't be 'traded' while an Egg."; + public static string LEggMetLocationFail { get; set; } = "Can't obtain Egg from Egg Location."; + public static string LEggNature { get; set; } = "Eggs cannot have their Stat Nature changed."; + public static string LEggPokeathlon { get; set; } = "Eggs cannot have Pokéathlon stats."; + public static string LEggPokerus { get; set; } = "Eggs cannot be infected with Pokérus."; + public static string LEggPP { get; set; } = "Eggs cannot have modified move PP counts."; + public static string LEggPPUp { get; set; } = "Cannot apply PP Ups to an Egg."; + public static string LEggRelearnFlags { get; set; } = "Expected no Relearn Move Flags."; + public static string LEggShinyLeaf { get; set; } = "Eggs cannot have Shiny Leaf/Crown."; + public static string LEggShinyPokeStar { get; set; } = "Eggs cannot be a Pokéstar Studios star."; + public static string LEggSpecies { get; set; } = "Can't obtain Egg for this species."; + public static string LEggUnhatched { get; set; } = "Valid un-hatched Egg."; - public static string LEncCondition { get; set; } = "Valid Wild Encounter at location."; - public static string LEncConditionBadRNGFrame { get; set; } = "Unable to match encounter conditions to a possible RNG frame."; - public static string LEncConditionBadSpecies { get; set; } = "Species does not exist in origin game."; - public static string LEncConditionBlack { get; set; } = "Valid Wild Encounter at location (Black Flute)."; - public static string LEncConditionBlackLead { get; set; } = "Valid Wild Encounter at location (Black Flute & Pressure/Hustle/Vital Spirit)."; - public static string LEncConditionDexNav { get; set; } = "Valid Wild Encounter at location (DexNav)."; - public static string LEncConditionLead { get; set; } = "Valid Wild Encounter at location (Pressure/Hustle/Vital Spirit)."; - public static string LEncConditionWhite { get; set; } = "Valid Wild Encounter at location (White Flute)."; - public static string LEncConditionWhiteLead { get; set; } = "Valid Wild Encounter at location (White Flute & Pressure/Hustle/Vital Spirit)."; + public static string LEncCondition { get; set; } = "Valid Wild Encounter at location."; + public static string LEncConditionBadRNGFrame { get; set; } = "Unable to match encounter conditions to a possible RNG frame."; + public static string LEncConditionBadSpecies { get; set; } = "Species does not exist in origin game."; + public static string LEncConditionBlack { get; set; } = "Valid Wild Encounter at location (Black Flute)."; + public static string LEncConditionBlackLead { get; set; } = "Valid Wild Encounter at location (Black Flute & Pressure/Hustle/Vital Spirit)."; + public static string LEncConditionDexNav { get; set; } = "Valid Wild Encounter at location (DexNav)."; + public static string LEncConditionLead { get; set; } = "Valid Wild Encounter at location (Pressure/Hustle/Vital Spirit)."; + public static string LEncConditionWhite { get; set; } = "Valid Wild Encounter at location (White Flute)."; + public static string LEncConditionWhiteLead { get; set; } = "Valid Wild Encounter at location (White Flute & Pressure/Hustle/Vital Spirit)."; - public static string LEncGift { get; set; } = "Unable to match a gift Egg encounter from origin game."; - public static string LEncGiftEggEvent { get; set; } = "Unable to match an event Egg encounter from origin game."; - public static string LEncGiftIVMismatch { get; set; } = "IVs do not match Mystery Gift Data."; - public static string LEncGiftNicknamed { get; set; } = "Event gift has been nicknamed."; - public static string LEncGiftNotFound { get; set; } = "Unable to match to a Mystery Gift in the database."; - public static string LEncGiftPIDMismatch { get; set; } = "Mystery Gift fixed PID mismatch."; - public static string LEncGiftShinyMismatch { get; set; } = "Mystery Gift shiny mismatch."; - public static string LEncGiftVersionNotDistributed { get; set; } = "Mystery Gift cannot be received by this version."; + public static string LEncGift { get; set; } = "Unable to match a gift Egg encounter from origin game."; + public static string LEncGiftEggEvent { get; set; } = "Unable to match an event Egg encounter from origin game."; + public static string LEncGiftIVMismatch { get; set; } = "IVs do not match Mystery Gift Data."; + public static string LEncGiftNicknamed { get; set; } = "Event gift has been nicknamed."; + public static string LEncGiftNotFound { get; set; } = "Unable to match to a Mystery Gift in the database."; + public static string LEncGiftPIDMismatch { get; set; } = "Mystery Gift fixed PID mismatch."; + public static string LEncGiftShinyMismatch { get; set; } = "Mystery Gift shiny mismatch."; + public static string LEncGiftVersionNotDistributed { get; set; } = "Mystery Gift cannot be received by this version."; - public static string LEncInvalid { get; set; } = "Unable to match an encounter from origin game."; - public static string LEncMasteryInitial { get; set; } = "Initial move mastery flags do not match the encounter's expected state."; + public static string LEncInvalid { get; set; } = "Unable to match an encounter from origin game."; + public static string LEncMasteryInitial { get; set; } = "Initial move mastery flags do not match the encounter's expected state."; - public static string LEncTradeChangedNickname { get; set; } = "In-game Trade Nickname has been altered."; - public static string LEncTradeChangedOT { get; set; } = "In-game Trade OT has been altered."; - public static string LEncTradeIndexBad { get; set; } = "In-game Trade invalid index?"; - public static string LEncTradeMatch { get; set; } = "Valid In-game trade."; - public static string LEncTradeUnchanged { get; set; } = "In-game Trade OT and Nickname have not been altered."; + public static string LEncTradeChangedNickname { get; set; } = "In-game Trade Nickname has been altered."; + public static string LEncTradeChangedOT { get; set; } = "In-game Trade OT has been altered."; + public static string LEncTradeIndexBad { get; set; } = "In-game Trade invalid index?"; + public static string LEncTradeMatch { get; set; } = "Valid In-game trade."; + public static string LEncTradeUnchanged { get; set; } = "In-game Trade OT and Nickname have not been altered."; - public static string LEncStaticMatch { get; set; } = "Valid gift/static encounter."; - public static string LEncStaticPIDShiny { get; set; } = "Static Encounter shiny mismatch."; - public static string LEncStaticRelearn { get; set; } = "Static encounter relearn move mismatch."; + public static string LEncStaticMatch { get; set; } = "Valid gift/static encounter."; + public static string LEncStaticPIDShiny { get; set; } = "Static Encounter shiny mismatch."; + public static string LEncStaticRelearn { get; set; } = "Static encounter relearn move mismatch."; - public static string LEncTypeMatch { get; set; } = "Encounter Type matches encounter."; - public static string LEncTypeMismatch { get; set; } = "Encounter Type does not match encounter."; - public static string LEncUnreleased { get; set; } = "Unreleased event."; - public static string LEncUnreleasedEMewJP { get; set; } = "Non japanese Mew from Faraway Island. Unreleased event."; - public static string LEncUnreleasedHoOArceus { get; set; } = "Arceus from Hall of Origin. Unreleased event."; - public static string LEncUnreleasedPtDarkrai { get; set; } = "Non Platinum Darkrai from Newmoon Island. Unreleased event."; - public static string LEncUnreleasedPtShaymin { get; set; } = "Non Platinum Shaymin from Flower Paradise. Unreleased event."; + public static string LEncTypeMatch { get; set; } = "Encounter Type matches encounter."; + public static string LEncTypeMismatch { get; set; } = "Encounter Type does not match encounter."; + public static string LEncUnreleased { get; set; } = "Unreleased event."; + public static string LEncUnreleasedEMewJP { get; set; } = "Non japanese Mew from Faraway Island. Unreleased event."; + public static string LEncUnreleasedHoOArceus { get; set; } = "Arceus from Hall of Origin. Unreleased event."; + public static string LEncUnreleasedPtDarkrai { get; set; } = "Non Platinum Darkrai from Newmoon Island. Unreleased event."; + public static string LEncUnreleasedPtShaymin { get; set; } = "Non Platinum Shaymin from Flower Paradise. Unreleased event."; - public static string LEReaderAmerica { get; set; } = "American E-Reader Berry in Japanese save file."; - public static string LEReaderInvalid { get; set; } = "Invalid E-Reader Berry."; - public static string LEReaderJapan { get; set; } = "Japanese E-Reader Berry in international save file."; + public static string LEReaderAmerica { get; set; } = "American E-Reader Berry in Japanese save file."; + public static string LEReaderInvalid { get; set; } = "Invalid E-Reader Berry."; + public static string LEReaderJapan { get; set; } = "Japanese E-Reader Berry in international save file."; - public static string LEffort2Remaining { get; set; } = "2 EVs remaining."; - public static string LEffortAbove252 { get; set; } = "EVs cannot go above 252."; - public static string LEffortAbove510 { get; set; } = "EV total cannot be above 510."; - public static string LEffortAllEqual { get; set; } = "EVs are all equal."; - public static string LEffortCap100 { get; set; } = "Individual EV for a level 100 encounter in Generation 4 cannot be greater than 100."; - public static string LEffortEgg { get; set; } = "Eggs cannot receive EVs."; - public static string LEffortShouldBeZero { get; set; } = "Cannot receive EVs."; - public static string LEffortEXPIncreased { get; set; } = "All EVs are zero, but leveled above Met Level."; - public static string LEffortUntrainedCap { get; set; } = "Individual EV without changing EXP cannot be greater than {0}."; + public static string LEffort2Remaining { get; set; } = "2 EVs remaining."; + public static string LEffortAbove252 { get; set; } = "EVs cannot go above 252."; + public static string LEffortAbove510 { get; set; } = "EV total cannot be above 510."; + public static string LEffortAllEqual { get; set; } = "EVs are all equal."; + public static string LEffortCap100 { get; set; } = "Individual EV for a level 100 encounter in Generation 4 cannot be greater than 100."; + public static string LEffortEgg { get; set; } = "Eggs cannot receive EVs."; + public static string LEffortShouldBeZero { get; set; } = "Cannot receive EVs."; + public static string LEffortEXPIncreased { get; set; } = "All EVs are zero, but leveled above Met Level."; + public static string LEffortUntrainedCap { get; set; } = "Individual EV without changing EXP cannot be greater than {0}."; - public static string LEvoInvalid { get; set; } = "Evolution not valid (or level/trade evolution unsatisfied)."; - public static string LEvoTradeReq { get; set; } = "In-game trade {0} should have evolved into {1}."; - public static string LEvoTradeReqOutsider { get; set; } = "Outsider {0} should have evolved into {1}."; - public static string LEvoTradeRequired { get; set; } = "Version Specific evolution requires a trade to opposite version. A Handling Trainer is required."; + public static string LEvoInvalid { get; set; } = "Evolution not valid (or level/trade evolution unsatisfied)."; + public static string LEvoTradeReq { get; set; } = "In-game trade {0} should have evolved into {1}."; + public static string LEvoTradeReqOutsider { get; set; } = "Outsider {0} should have evolved into {1}."; + public static string LEvoTradeRequired { get; set; } = "Version Specific evolution requires a trade to opposite version. A Handling Trainer is required."; - public static string LFateful { get; set; } = "Special In-game Fateful Encounter."; - public static string LFatefulGiftMissing { get; set; } = "Fateful Encounter with no matching Encounter. Has the Mystery Gift data been contributed?"; - public static string LFatefulInvalid { get; set; } = "Fateful Encounter should not be checked."; - public static string LFatefulMissing { get; set; } = "Special In-game Fateful Encounter flag missing."; - public static string LFatefulMystery { get; set; } = "Mystery Gift Fateful Encounter."; - public static string LFatefulMysteryMissing { get; set; } = "Mystery Gift Fateful Encounter flag missing."; + public static string LFateful { get; set; } = "Special In-game Fateful Encounter."; + public static string LFatefulGiftMissing { get; set; } = "Fateful Encounter with no matching Encounter. Has the Mystery Gift data been contributed?"; + public static string LFatefulInvalid { get; set; } = "Fateful Encounter should not be checked."; + public static string LFatefulMissing { get; set; } = "Special In-game Fateful Encounter flag missing."; + public static string LFatefulMystery { get; set; } = "Mystery Gift Fateful Encounter."; + public static string LFatefulMysteryMissing { get; set; } = "Mystery Gift Fateful Encounter flag missing."; - public static string LFavoriteMarkingUnavailable { get; set; } = "Favorite Marking is not available."; + public static string LFavoriteMarkingUnavailable { get; set; } = "Favorite Marking is not available."; - public static string LFormArgumentHigh { get; set; } = "Form argument is too high for current form."; - public static string LFormArgumentLow { get; set; } = "Form argument is too low for current form."; - public static string LFormArgumentNotAllowed { get; set; } = "Form argument is not allowed for this encounter."; - public static string LFormArgumentValid { get; set; } = "Form argument is valid."; - public static string LFormArgumentInvalid { get; set; } = "Form argument is not valid."; - public static string LFormBattle { get; set; } = "Form cannot exist outside of a battle."; - public static string LFormEternal { get; set; } = "Valid Eternal Flower encounter."; - public static string LFormEternalInvalid { get; set; } = "Invalid Eternal Flower encounter."; - public static string LFormInvalidGame { get; set; } = "Form cannot be obtained in origin game."; - public static string LFormInvalidNature { get; set; } = "Form cannot have this nature."; - public static string LFormInvalidRange { get; set; } = "Form Count is out of range. Expected <= {0}, got {1}."; - public static string LFormItem { get; set; } = "Held item matches Form."; - public static string LFormItemInvalid { get; set; } = "Held item does not match Form."; - public static string LFormParty { get; set; } = "Form cannot exist outside of Party."; - public static string LFormPikachuCosplay { get; set; } = "Only Cosplay Pikachu can have this form."; - public static string LFormPikachuCosplayInvalid { get; set; } = "Cosplay Pikachu cannot have the default form."; - public static string LFormPikachuEventInvalid { get; set; } = "Event Pikachu cannot have the default form."; - public static string LFormValid { get; set; } = "Form is Valid."; - public static string LFormVivillon { get; set; } = "Valid Vivillon pattern."; - public static string LFormVivillonEventPre { get; set; } = "Event Vivillon pattern on pre-evolution."; - public static string LFormVivillonInvalid { get; set; } = "Invalid Vivillon pattern."; - public static string LFormVivillonNonNative { get; set; } = "Non-native Vivillon pattern."; + public static string LFormArgumentHigh { get; set; } = "Form argument is too high for current form."; + public static string LFormArgumentLow { get; set; } = "Form argument is too low for current form."; + public static string LFormArgumentNotAllowed { get; set; } = "Form argument is not allowed for this encounter."; + public static string LFormArgumentValid { get; set; } = "Form argument is valid."; + public static string LFormArgumentInvalid { get; set; } = "Form argument is not valid."; + public static string LFormBattle { get; set; } = "Form cannot exist outside of a battle."; + public static string LFormEternal { get; set; } = "Valid Eternal Flower encounter."; + public static string LFormEternalInvalid { get; set; } = "Invalid Eternal Flower encounter."; + public static string LFormInvalidGame { get; set; } = "Form cannot be obtained in origin game."; + public static string LFormInvalidNature { get; set; } = "Form cannot have this nature."; + public static string LFormInvalidRange { get; set; } = "Form Count is out of range. Expected <= {0}, got {1}."; + public static string LFormItem { get; set; } = "Held item matches Form."; + public static string LFormItemInvalid { get; set; } = "Held item does not match Form."; + public static string LFormParty { get; set; } = "Form cannot exist outside of Party."; + public static string LFormPikachuCosplay { get; set; } = "Only Cosplay Pikachu can have this form."; + public static string LFormPikachuCosplayInvalid { get; set; } = "Cosplay Pikachu cannot have the default form."; + public static string LFormPikachuEventInvalid { get; set; } = "Event Pikachu cannot have the default form."; + public static string LFormValid { get; set; } = "Form is Valid."; + public static string LFormVivillon { get; set; } = "Valid Vivillon pattern."; + public static string LFormVivillonEventPre { get; set; } = "Event Vivillon pattern on pre-evolution."; + public static string LFormVivillonInvalid { get; set; } = "Invalid Vivillon pattern."; + public static string LFormVivillonNonNative { get; set; } = "Non-native Vivillon pattern."; - public static string LG1CatchRateChain { get; set; } = "Catch rate does not match any species from Pokémon evolution chain."; - public static string LG1CatchRateEvo { get; set; } = "Catch rate match species without encounters. Expected a preevolution catch rate."; - public static string LG1CatchRateItem { get; set; } = "Catch rate does not match a valid held item from Generation 2."; - public static string LG1CatchRateMatchPrevious { get; set; } = "Catch Rate matches a species from Pokémon evolution chain."; - public static string LG1CatchRateMatchTradeback { get; set; } = "Catch rate matches a valid held item from Generation 2."; - public static string LG1CatchRateNone { get; set; } = "Catch rate does not match any species from Pokémon evolution chain or any Generation 2 held items."; - public static string LG1CharNick { get; set; } = "Nickname from Generation 1/2 uses unavailable characters."; - public static string LG1CharOT { get; set; } = "OT from Generation 1/2 uses unavailable characters."; - public static string LG1GBEncounter { get; set; } = "Can't obtain Special encounter in Virtual Console games."; - public static string LG1MoveExclusive { get; set; } = "Generation 1 exclusive move. Incompatible with Non-tradeback moves."; - public static string LG1MoveLearnSameLevel { get; set; } = "Incompatible moves. Learned at the same level in Red/Blue and Yellow."; - public static string LG1MoveTradeback { get; set; } = "Non-tradeback Egg move. Incompatible with Generation 1 exclusive moves."; - public static string LG1OTEvent { get; set; } = "Incorrect RBY event OT Name."; - public static string LG1OTGender { get; set; } = "Female OT from Generation 1/2 is invalid."; - public static string LG1Stadium { get; set; } = "Incorrect Stadium OT."; - public static string LG1StadiumInternational { get; set; } = "Valid International Stadium OT."; - public static string LG1StadiumJapanese { get; set; } = "Valid Japanese Stadium OT."; - public static string LG1TradebackPreEvoMove { get; set; } = "Non-tradeback pre evolution move. Incompatible with Generation 1 exclusive moves."; - public static string LG1Type1Fail { get; set; } = "Invalid Type A, does not match species type."; - public static string LG1Type2Fail { get; set; } = "Invalid Type B, does not match species type."; - public static string LG1TypeMatch1 { get; set; } = "Valid Type A, matches species type."; - public static string LG1TypeMatch2 { get; set; } = "Valid Type B, matches species type."; - public static string LG1TypeMatchPorygon { get; set; } = "Porygon with valid Type A and B values."; - public static string LG1TypePorygonFail { get; set; } = "Porygon with invalid Type A and B values. Does not a match a valid type combination."; - public static string LG1TypePorygonFail1 { get; set; } = "Porygon with invalid Type A value."; - public static string LG1TypePorygonFail2 { get; set; } = "Porygon with invalid Type B value."; - public static string LG2InvalidTilePark { get; set; } = "National Park fishing encounter. Unreachable Water tiles."; - public static string LG2InvalidTileR14 { get; set; } = "Kanto Route 14 fishing encounter. Unreachable Water tiles."; - public static string LG2InvalidTileSafari { get; set; } = "Generation 2 Safari Zone fishing encounter. Unreachable zone."; - public static string LG2InvalidTileTreeID { get; set; } = "Found an unreachable tree for Crystal headbutt encounter that matches OTID."; - public static string LG2InvalidTileTreeNotFound { get; set; } = "Could not find a tree for Crystal headbutt encounter that matches OTID."; - public static string LG2TreeID { get; set; } = "Found a tree for Crystal headbutt encounter that matches OTID."; - public static string LG2OTGender { get; set; } = "OT from Virtual Console games other than Crystal cannot be female."; + public static string LG1CatchRateChain { get; set; } = "Catch rate does not match any species from Pokémon evolution chain."; + public static string LG1CatchRateEvo { get; set; } = "Catch rate match species without encounters. Expected a preevolution catch rate."; + public static string LG1CatchRateItem { get; set; } = "Catch rate does not match a valid held item from Generation 2."; + public static string LG1CatchRateMatchPrevious { get; set; } = "Catch Rate matches a species from Pokémon evolution chain."; + public static string LG1CatchRateMatchTradeback { get; set; } = "Catch rate matches a valid held item from Generation 2."; + public static string LG1CatchRateNone { get; set; } = "Catch rate does not match any species from Pokémon evolution chain or any Generation 2 held items."; + public static string LG1CharNick { get; set; } = "Nickname from Generation 1/2 uses unavailable characters."; + public static string LG1CharOT { get; set; } = "OT from Generation 1/2 uses unavailable characters."; + public static string LG1GBEncounter { get; set; } = "Can't obtain Special encounter in Virtual Console games."; + public static string LG1MoveExclusive { get; set; } = "Generation 1 exclusive move. Incompatible with Non-tradeback moves."; + public static string LG1MoveLearnSameLevel { get; set; } = "Incompatible moves. Learned at the same level in Red/Blue and Yellow."; + public static string LG1MoveTradeback { get; set; } = "Non-tradeback Egg move. Incompatible with Generation 1 exclusive moves."; + public static string LG1OTEvent { get; set; } = "Incorrect RBY event OT Name."; + public static string LG1OTGender { get; set; } = "Female OT from Generation 1/2 is invalid."; + public static string LG1Stadium { get; set; } = "Incorrect Stadium OT."; + public static string LG1StadiumInternational { get; set; } = "Valid International Stadium OT."; + public static string LG1StadiumJapanese { get; set; } = "Valid Japanese Stadium OT."; + public static string LG1TradebackPreEvoMove { get; set; } = "Non-tradeback pre evolution move. Incompatible with Generation 1 exclusive moves."; + public static string LG1Type1Fail { get; set; } = "Invalid Type A, does not match species type."; + public static string LG1Type2Fail { get; set; } = "Invalid Type B, does not match species type."; + public static string LG1TypeMatch1 { get; set; } = "Valid Type A, matches species type."; + public static string LG1TypeMatch2 { get; set; } = "Valid Type B, matches species type."; + public static string LG1TypeMatchPorygon { get; set; } = "Porygon with valid Type A and B values."; + public static string LG1TypePorygonFail { get; set; } = "Porygon with invalid Type A and B values. Does not a match a valid type combination."; + public static string LG1TypePorygonFail1 { get; set; } = "Porygon with invalid Type A value."; + public static string LG1TypePorygonFail2 { get; set; } = "Porygon with invalid Type B value."; + public static string LG2InvalidTilePark { get; set; } = "National Park fishing encounter. Unreachable Water tiles."; + public static string LG2InvalidTileR14 { get; set; } = "Kanto Route 14 fishing encounter. Unreachable Water tiles."; + public static string LG2InvalidTileSafari { get; set; } = "Generation 2 Safari Zone fishing encounter. Unreachable zone."; + public static string LG2InvalidTileTreeID { get; set; } = "Found an unreachable tree for Crystal headbutt encounter that matches OTID."; + public static string LG2InvalidTileTreeNotFound { get; set; } = "Could not find a tree for Crystal headbutt encounter that matches OTID."; + public static string LG2TreeID { get; set; } = "Found a tree for Crystal headbutt encounter that matches OTID."; + public static string LG2OTGender { get; set; } = "OT from Virtual Console games other than Crystal cannot be female."; - public static string LG3EReader { get; set; } = "Non Japanese Shadow E-reader Pokémon. Unreleased encounter."; - public static string LG3OTGender { get; set; } = "OT from Colosseum/XD cannot be female."; - public static string LG4InvalidTileR45Surf { get; set; } = "Johto Route 45 surfing encounter. Unreachable Water tiles."; - public static string LG5ID_N { get; set; } = "The Name/TID/SID of N is incorrect."; - public static string LG5IVAll30 { get; set; } = "All IVs of N's Pokémon should be 30."; - public static string LG5OTGenderN { get; set; } = "N's Pokémon must have a male OT gender."; - public static string LG5PIDShinyGrotto { get; set; } = "Hidden Grotto captures cannot be shiny."; - public static string LG5PIDShinyN { get; set; } = "N's Pokémon cannot be shiny."; - public static string LG5SparkleInvalid { get; set; } = "Special In-game N's Sparkle flag should not be checked."; - public static string LG5SparkleRequired { get; set; } = "Special In-game N's Sparkle flag missing."; + public static string LG3EReader { get; set; } = "Non Japanese Shadow E-reader Pokémon. Unreleased encounter."; + public static string LG3OTGender { get; set; } = "OT from Colosseum/XD cannot be female."; + public static string LG4InvalidTileR45Surf { get; set; } = "Johto Route 45 surfing encounter. Unreachable Water tiles."; + public static string LG5ID_N { get; set; } = "The Name/TID/SID of N is incorrect."; + public static string LG5IVAll30 { get; set; } = "All IVs of N's Pokémon should be 30."; + public static string LG5OTGenderN { get; set; } = "N's Pokémon must have a male OT gender."; + public static string LG5PIDShinyGrotto { get; set; } = "Hidden Grotto captures cannot be shiny."; + public static string LG5PIDShinyN { get; set; } = "N's Pokémon cannot be shiny."; + public static string LG5SparkleInvalid { get; set; } = "Special In-game N's Sparkle flag should not be checked."; + public static string LG5SparkleRequired { get; set; } = "Special In-game N's Sparkle flag missing."; - public static string LGanbaruStatTooHigh { get; set; } = "One or more Ganbaru Value is above the natural limit of (10 - IV bonus)."; + public static string LGanbaruStatTooHigh { get; set; } = "One or more Ganbaru Value is above the natural limit of (10 - IV bonus)."; - public static string LGenderInvalidNone { get; set; } = "Genderless Pokémon should not have a gender."; - public static string LGeoBadOrder { get; set; } = "GeoLocation Memory: Gap/Blank present."; - public static string LGeoHardwareInvalid { get; set; } = "Geolocation: Country is not in 3DS region."; - public static string LGeoHardwareRange { get; set; } = "Invalid Console Region."; - public static string LGeoHardwareValid { get; set; } = "Geolocation: Country is in 3DS region."; - public static string LGeoMemoryMissing { get; set; } = "GeoLocation Memory: Memories should be present."; - public static string LGeoNoCountryHT { get; set; } = "GeoLocation Memory: HT Name present but has no previous Country."; - public static string LGeoNoRegion { get; set; } = "GeoLocation Memory: Region without Country."; + public static string LGenderInvalidNone { get; set; } = "Genderless Pokémon should not have a gender."; + public static string LGeoBadOrder { get; set; } = "GeoLocation Memory: Gap/Blank present."; + public static string LGeoHardwareInvalid { get; set; } = "Geolocation: Country is not in 3DS region."; + public static string LGeoHardwareRange { get; set; } = "Invalid Console Region."; + public static string LGeoHardwareValid { get; set; } = "Geolocation: Country is in 3DS region."; + public static string LGeoMemoryMissing { get; set; } = "GeoLocation Memory: Memories should be present."; + public static string LGeoNoCountryHT { get; set; } = "GeoLocation Memory: HT Name present but has no previous Country."; + public static string LGeoNoRegion { get; set; } = "GeoLocation Memory: Region without Country."; - public static string LHyperBelow100 { get; set; } = "Can't Hyper Train a Pokémon that isn't level 100."; - public static string LHyperPerfectAll { get; set; } = "Can't Hyper Train a Pokémon with perfect IVs."; - public static string LHyperPerfectOne { get; set; } = "Can't Hyper Train a perfect IV."; - public static string LHyperPerfectUnavailable { get; set; } = "Can't Hyper Train any IV(s)."; + public static string LHyperBelow100 { get; set; } = "Can't Hyper Train a Pokémon that isn't level 100."; + public static string LHyperPerfectAll { get; set; } = "Can't Hyper Train a Pokémon with perfect IVs."; + public static string LHyperPerfectOne { get; set; } = "Can't Hyper Train a perfect IV."; + public static string LHyperPerfectUnavailable { get; set; } = "Can't Hyper Train any IV(s)."; - public static string LItemEgg { get; set; } = "Eggs cannot hold items."; - public static string LItemUnreleased { get; set; } = "Held item is unreleased."; + public static string LItemEgg { get; set; } = "Eggs cannot hold items."; + public static string LItemUnreleased { get; set; } = "Held item is unreleased."; - public static string LIVAllEqual_0 { get; set; } = "All IVs are {0}."; - public static string LIVNotCorrect { get; set; } = "IVs do not match encounter requirements."; - public static string LIVF_COUNT0_31 { get; set; } = "Should have at least {0} IVs = 31."; + public static string LIVAllEqual_0 { get; set; } = "All IVs are {0}."; + public static string LIVNotCorrect { get; set; } = "IVs do not match encounter requirements."; + public static string LIVF_COUNT0_31 { get; set; } = "Should have at least {0} IVs = 31."; - public static string LLevelEXPThreshold { get; set; } = "Current experience matches level threshold."; - public static string LLevelEXPTooHigh { get; set; } = "Current experience exceeds maximum amount for level 100."; - public static string LLevelMetBelow { get; set; } = "Current level is below met level."; - public static string LLevelMetGift { get; set; } = "Met Level does not match Mystery Gift level."; - public static string LLevelMetGiftFail { get; set; } = "Current Level below Mystery Gift level."; - public static string LLevelMetSane { get; set; } = "Current level is not below met level."; + public static string LLevelEXPThreshold { get; set; } = "Current experience matches level threshold."; + public static string LLevelEXPTooHigh { get; set; } = "Current experience exceeds maximum amount for level 100."; + public static string LLevelMetBelow { get; set; } = "Current level is below met level."; + public static string LLevelMetGift { get; set; } = "Met Level does not match Mystery Gift level."; + public static string LLevelMetGiftFail { get; set; } = "Current Level below Mystery Gift level."; + public static string LLevelMetSane { get; set; } = "Current level is not below met level."; - public static string LMarkValueOutOfRange_0 { get; set; } = "Individual marking at index {0} is not within the allowed value range."; - public static string LMarkValueShouldBeZero { get; set; } = "Marking flags cannot be set."; - public static string LMarkValueUnusedBitsPresent { get; set; } = "Marking flags uses bits beyond the accessible range."; + public static string LMarkValueOutOfRange_0 { get; set; } = "Individual marking at index {0} is not within the allowed value range."; + public static string LMarkValueShouldBeZero { get; set; } = "Marking flags cannot be set."; + public static string LMarkValueUnusedBitsPresent { get; set; } = "Marking flags uses bits beyond the accessible range."; - public static string LMemoryArgBadCatch { get; set; } = "{0} Memory: {0} did not catch this."; - public static string LMemoryArgBadHatch { get; set; } = "{0} Memory: {0} did not hatch this."; - public static string LMemoryArgBadHT { get; set; } = "Memory: Can't have Handling Trainer Memory as Egg."; - public static string LMemoryArgBadID { get; set; } = "{0} Memory: Can't obtain Memory on {0} Version."; - public static string LMemoryArgBadItem { get; set; } = "{0} Memory: Species can't hold this item."; - public static string LMemoryArgBadLocation { get; set; } = "{0} Memory: Can't obtain Location on {0} Version."; - public static string LMemoryArgBadMove { get; set; } = "{0} Memory: Species can't learn this move."; - public static string LMemoryArgBadOTEgg { get; set; } = "{0} Memory: Link Trade is not a valid first memory."; - public static string LMemoryArgBadSpecies { get; set; } = "{0} Memory: Can't capture species in game."; - public static string LMemoryArgSpecies { get; set; } = "{0} Memory: Species can be captured in game."; - public static string LMemoryCleared { get; set; } = "Memory: Not cleared properly."; - public static string LMemoryF_0_Valid { get; set; } = "{0} Memory is valid."; - public static string LMemoryFeelInvalid { get; set; } = "{0} Memory: Invalid Feeling."; - public static string LMemoryHTFlagInvalid { get; set; } = "Untraded: Current handler should not be the Handling Trainer."; - public static string LMemoryHTGender { get; set; } = "HT Gender invalid: {0}"; - public static string LMemoryHTLanguage { get; set; } = "HT Language is missing."; + public static string LMemoryArgBadCatch { get; set; } = "{0} Memory: {0} did not catch this."; + public static string LMemoryArgBadHatch { get; set; } = "{0} Memory: {0} did not hatch this."; + public static string LMemoryArgBadHT { get; set; } = "Memory: Can't have Handling Trainer Memory as Egg."; + public static string LMemoryArgBadID { get; set; } = "{0} Memory: Can't obtain Memory on {0} Version."; + public static string LMemoryArgBadItem { get; set; } = "{0} Memory: Species can't hold this item."; + public static string LMemoryArgBadLocation { get; set; } = "{0} Memory: Can't obtain Location on {0} Version."; + public static string LMemoryArgBadMove { get; set; } = "{0} Memory: Species can't learn this move."; + public static string LMemoryArgBadOTEgg { get; set; } = "{0} Memory: Link Trade is not a valid first memory."; + public static string LMemoryArgBadSpecies { get; set; } = "{0} Memory: Can't capture species in game."; + public static string LMemoryArgSpecies { get; set; } = "{0} Memory: Species can be captured in game."; + public static string LMemoryCleared { get; set; } = "Memory: Not cleared properly."; + public static string LMemoryF_0_Valid { get; set; } = "{0} Memory is valid."; + public static string LMemoryFeelInvalid { get; set; } = "{0} Memory: Invalid Feeling."; + public static string LMemoryHTFlagInvalid { get; set; } = "Untraded: Current handler should not be the Handling Trainer."; + public static string LMemoryHTGender { get; set; } = "HT Gender invalid: {0}"; + public static string LMemoryHTLanguage { get; set; } = "HT Language is missing."; - public static string LMemoryIndexArgHT { get; set; } = "Should have a HT Memory TextVar value (somewhere)."; - public static string LMemoryIndexFeel { get; set; } = "{0} Memory: Feeling should be index {1}."; - public static string LMemoryIndexFeelHT09 { get; set; } = "Should have a HT Memory Feeling value 0-9."; - public static string LMemoryIndexID { get; set; } = "{0} Memory: Should be index {1}."; - public static string LMemoryIndexIntensity { get; set; } = "{0} Memory: Intensity should be index {1}."; - public static string LMemoryIndexIntensityHT1 { get; set; } = "Should have a HT Memory Intensity value (1st)."; - public static string LMemoryIndexIntensityMin { get; set; } = "{0} Memory: Intensity should be at least {1}."; - public static string LMemoryIndexLinkHT { get; set; } = "Should have a Link Trade HT Memory."; - public static string LMemoryIndexVar { get; set; } = "{0} Memory: TextVar should be index {1}."; - public static string LMemoryMissingHT { get; set; } = "Memory: Handling Trainer Memory missing."; - public static string LMemoryMissingOT { get; set; } = "Memory: Original Trainer Memory missing."; + public static string LMemoryIndexArgHT { get; set; } = "Should have a HT Memory TextVar value (somewhere)."; + public static string LMemoryIndexFeel { get; set; } = "{0} Memory: Feeling should be index {1}."; + public static string LMemoryIndexFeelHT09 { get; set; } = "Should have a HT Memory Feeling value 0-9."; + public static string LMemoryIndexID { get; set; } = "{0} Memory: Should be index {1}."; + public static string LMemoryIndexIntensity { get; set; } = "{0} Memory: Intensity should be index {1}."; + public static string LMemoryIndexIntensityHT1 { get; set; } = "Should have a HT Memory Intensity value (1st)."; + public static string LMemoryIndexIntensityMin { get; set; } = "{0} Memory: Intensity should be at least {1}."; + public static string LMemoryIndexLinkHT { get; set; } = "Should have a Link Trade HT Memory."; + public static string LMemoryIndexVar { get; set; } = "{0} Memory: TextVar should be index {1}."; + public static string LMemoryMissingHT { get; set; } = "Memory: Handling Trainer Memory missing."; + public static string LMemoryMissingOT { get; set; } = "Memory: Original Trainer Memory missing."; - public static string LMemorySocialZero { get; set; } = "Social Stat should be zero."; - public static string LMemorySocialTooHigh_0 { get; set; } = "Social Stat should be <= {0}"; + public static string LMemorySocialZero { get; set; } = "Social Stat should be zero."; + public static string LMemorySocialTooHigh_0 { get; set; } = "Social Stat should be <= {0}"; - public static string LMemoryStatAffectionHT0 { get; set; } = "Untraded: Handling Trainer Affection should be 0."; - public static string LMemoryStatAffectionOT0 { get; set; } = "OT Affection should be 0."; - public static string LMemoryStatFriendshipHT0 { get; set; } = "Untraded: Handling Trainer Friendship should be 0."; - public static string LMemoryStatFriendshipOTBaseEvent { get; set; } = "Event OT Friendship does not match base friendship."; + public static string LMemoryStatAffectionHT0 { get; set; } = "Untraded: Handling Trainer Affection should be 0."; + public static string LMemoryStatAffectionOT0 { get; set; } = "OT Affection should be 0."; + public static string LMemoryStatFriendshipHT0 { get; set; } = "Untraded: Handling Trainer Friendship should be 0."; + public static string LMemoryStatFriendshipOTBaseEvent { get; set; } = "Event OT Friendship does not match base friendship."; - public static string LMemoryStatFullness { get; set; } = "Fullness should be {0}."; - public static string LMemoryStatEnjoyment { get; set; } = "Enjoyment should be {0}."; + public static string LMemoryStatFullness { get; set; } = "Fullness should be {0}."; + public static string LMemoryStatEnjoyment { get; set; } = "Enjoyment should be {0}."; - public static string LMoveEggFIncompatible0_1 { get; set; } = "{0} Inherited Move. Incompatible with {1} inherited moves."; - public static string LMoveEggIncompatible { get; set; } = "Egg Move. Incompatible with event Egg moves."; - public static string LMoveEggIncompatibleEvent { get; set; } = "Event Egg Move. Incompatible with normal Egg moves."; - public static string LMoveEggInherited { get; set; } = "Inherited Egg move."; - public static string LMoveEggInheritedTutor { get; set; } = "Inherited tutor move."; - public static string LMoveEggInvalid { get; set; } = "Not an expected Egg move."; - public static string LMoveEggInvalidEvent { get; set; } = "Egg Move. Not expected in an event Egg."; - public static string LMoveEggInvalidEventLevelUp { get; set; } = "Inherited move learned by Level-up. Not expected in an event Egg."; - public static string LMoveEggInvalidEventLevelUpGift { get; set; } = "Inherited move learned by Level-up. Not expected in a gift Egg."; - public static string LMoveEggInvalidEventTMHM { get; set; } = "Inherited TM/HM move. Not expected in an event Egg."; - public static string LMoveEggInvalidEventTutor { get; set; } = "Inherited tutor move. Not expected in an event Egg."; - public static string LMoveEggLevelUp { get; set; } = "Inherited move learned by Level-up."; - public static string LMoveEggMissing { get; set; } = "Event Egg move missing."; - public static string LMoveEggMoveGift { get; set; } = "Egg Move. Not expected in a gift Egg."; - public static string LMoveEggTMHM { get; set; } = "Inherited TM/HM move."; + public static string LMoveEggFIncompatible0_1 { get; set; } = "{0} Inherited Move. Incompatible with {1} inherited moves."; + public static string LMoveEggIncompatible { get; set; } = "Egg Move. Incompatible with event Egg moves."; + public static string LMoveEggIncompatibleEvent { get; set; } = "Event Egg Move. Incompatible with normal Egg moves."; + public static string LMoveEggInherited { get; set; } = "Inherited Egg move."; + public static string LMoveEggInheritedTutor { get; set; } = "Inherited tutor move."; + public static string LMoveEggInvalid { get; set; } = "Not an expected Egg move."; + public static string LMoveEggInvalidEvent { get; set; } = "Egg Move. Not expected in an event Egg."; + public static string LMoveEggInvalidEventLevelUp { get; set; } = "Inherited move learned by Level-up. Not expected in an event Egg."; + public static string LMoveEggInvalidEventLevelUpGift { get; set; } = "Inherited move learned by Level-up. Not expected in a gift Egg."; + public static string LMoveEggInvalidEventTMHM { get; set; } = "Inherited TM/HM move. Not expected in an event Egg."; + public static string LMoveEggInvalidEventTutor { get; set; } = "Inherited tutor move. Not expected in an event Egg."; + public static string LMoveEggLevelUp { get; set; } = "Inherited move learned by Level-up."; + public static string LMoveEggMissing { get; set; } = "Event Egg move missing."; + public static string LMoveEggMoveGift { get; set; } = "Egg Move. Not expected in a gift Egg."; + public static string LMoveEggTMHM { get; set; } = "Inherited TM/HM move."; - public static string LMoveEventEggLevelUp { get; set; } = "Inherited move learned by Level-up. Incompatible with event Egg moves."; - public static string LMoveEvoFCombination_0 { get; set; } = "Moves combinations is not compatible with {0} evolution."; - public static string LMoveEvoFHigher { get; set; } = "Incompatible evolution moves. {1} Move learned at a higher level than other {0} moves."; - public static string LMoveEvoFLower { get; set; } = "Incompatible evolution moves. {0} Move learned at a lower level than other {1} moves."; - public static string LMoveFDefault_0 { get; set; } = "Default move in Generation {0}."; - public static string LMoveFExpect_0 { get; set; } = "Expected the following Moves: {0}"; - public static string LMoveFLevelUp_0 { get; set; } = "Learned by Level-up in Generation {0}."; - public static string LMoveFTMHM_0 { get; set; } = "Learned by TM/HM in Generation {0}."; - public static string LMoveFTutor_0 { get; set; } = "Learned by Move Tutor in Generation {0}."; - public static string LMoveKeldeoMismatch { get; set; } = "Keldeo Move/Form mismatch."; - public static string LMoveNincada { get; set; } = "Only one Ninjask move allowed."; - public static string LMoveNincadaEvo { get; set; } = "Learned by evolving Nincada into Ninjask."; - public static string LMoveNincadaEvoF_0 { get; set; } = "Learned by evolving Nincada into Ninjask in Generation {0}."; - public static string LMovePPTooHigh_0 { get; set; } = "Move {0} PP is above the amount allowed."; - public static string LMovePPUpsTooHigh_0 { get; set; } = "Move {0} PP Ups is above the amount allowed."; - public static string LMoveSourceShared { get; set; } = "Shared Non-Relearn Move."; - public static string LMoveSourceSharedF { get; set; } = "Shared Non-Relearn Move in Generation {0}."; + public static string LMoveEventEggLevelUp { get; set; } = "Inherited move learned by Level-up. Incompatible with event Egg moves."; + public static string LMoveEvoFCombination_0 { get; set; } = "Moves combinations is not compatible with {0} evolution."; + public static string LMoveEvoFHigher { get; set; } = "Incompatible evolution moves. {1} Move learned at a higher level than other {0} moves."; + public static string LMoveEvoFLower { get; set; } = "Incompatible evolution moves. {0} Move learned at a lower level than other {1} moves."; + public static string LMoveFDefault_0 { get; set; } = "Default move in Generation {0}."; + public static string LMoveFExpect_0 { get; set; } = "Expected the following Moves: {0}"; + public static string LMoveFLevelUp_0 { get; set; } = "Learned by Level-up in Generation {0}."; + public static string LMoveFTMHM_0 { get; set; } = "Learned by TM/HM in Generation {0}."; + public static string LMoveFTutor_0 { get; set; } = "Learned by Move Tutor in Generation {0}."; + public static string LMoveKeldeoMismatch { get; set; } = "Keldeo Move/Form mismatch."; + public static string LMoveNincada { get; set; } = "Only one Ninjask move allowed."; + public static string LMoveNincadaEvo { get; set; } = "Learned by evolving Nincada into Ninjask."; + public static string LMoveNincadaEvoF_0 { get; set; } = "Learned by evolving Nincada into Ninjask in Generation {0}."; + public static string LMovePPTooHigh_0 { get; set; } = "Move {0} PP is above the amount allowed."; + public static string LMovePPUpsTooHigh_0 { get; set; } = "Move {0} PP Ups is above the amount allowed."; + public static string LMoveSourceShared { get; set; } = "Shared Non-Relearn Move."; + public static string LMoveSourceSharedF { get; set; } = "Shared Non-Relearn Move in Generation {0}."; - public static string LMoveRelearnDexNav { get; set; } = "Not an expected DexNav move."; - public static string LMoveRelearnUnderground { get; set; } = "Not an expected Underground egg move."; - public static string LMoveRelearnEgg { get; set; } = "Base Egg move."; - public static string LMoveRelearnEggMissing { get; set; } = "Base Egg move missing."; - public static string LMoveRelearnFExpect_0 { get; set; } = "Expected the following Relearn Moves: {0} ({1})"; - public static string LMoveRelearnFMiss_0 { get; set; } = "Relearn Moves missing: {0}"; - public static string LMoveRelearnInvalid { get; set; } = "Not an expected Relearnable move."; - public static string LMoveRelearnNone { get; set; } = "Expected no Relearn Move in slot."; + public static string LMoveRelearnDexNav { get; set; } = "Not an expected DexNav move."; + public static string LMoveRelearnUnderground { get; set; } = "Not an expected Underground egg move."; + public static string LMoveRelearnEgg { get; set; } = "Base Egg move."; + public static string LMoveRelearnEggMissing { get; set; } = "Base Egg move missing."; + public static string LMoveRelearnFExpect_0 { get; set; } = "Expected the following Relearn Moves: {0} ({1})"; + public static string LMoveRelearnFMiss_0 { get; set; } = "Relearn Moves missing: {0}"; + public static string LMoveRelearnInvalid { get; set; } = "Not an expected Relearnable move."; + public static string LMoveRelearnNone { get; set; } = "Expected no Relearn Move in slot."; - public static string LMoveShopAlphaMoveShouldBeMastered { get; set; } = "Alpha Move should be marked as mastered."; - public static string LMoveShopAlphaMoveShouldBeOther { get; set; } = "Alpha encounter cannot be found with this Alpha Move."; - public static string LMoveShopAlphaMoveShouldBeZero { get; set; } = "Only Alphas may have an Alpha Move set."; - public static string LMoveShopMasterInvalid_0 { get; set; } = "Cannot manually master {0}: not permitted to master."; - public static string LMoveShopMasterNotLearned_0 { get; set; } = "Cannot manually master {0}: not in possible learned level up moves."; - public static string LMoveShopPurchaseInvalid_0 { get; set; } = "Cannot purchase {0} from the move shop."; + public static string LMoveShopAlphaMoveShouldBeMastered { get; set; } = "Alpha Move should be marked as mastered."; + public static string LMoveShopAlphaMoveShouldBeOther { get; set; } = "Alpha encounter cannot be found with this Alpha Move."; + public static string LMoveShopAlphaMoveShouldBeZero { get; set; } = "Only Alphas may have an Alpha Move set."; + public static string LMoveShopMasterInvalid_0 { get; set; } = "Cannot manually master {0}: not permitted to master."; + public static string LMoveShopMasterNotLearned_0 { get; set; } = "Cannot manually master {0}: not in possible learned level up moves."; + public static string LMoveShopPurchaseInvalid_0 { get; set; } = "Cannot purchase {0} from the move shop."; - public static string LMoveSourceDefault { get; set; } = "Default move."; - public static string LMoveSourceDuplicate { get; set; } = "Duplicate Move."; - public static string LMoveSourceEgg { get; set; } = "Egg Move."; - public static string LMoveSourceEggEvent { get; set; } = "Event Egg Move."; - public static string LMoveSourceEmpty { get; set; } = "Empty Move."; - public static string LMoveSourceInvalid { get; set; } = "Invalid Move."; - public static string LMoveSourceInvalidSketch { get; set; } = "Invalid Move (Sketch)."; - public static string LMoveSourceLevelUp { get; set; } = "Learned by Level-up."; - public static string LMoveSourceRelearn { get; set; } = "Relearnable Move."; - public static string LMoveSourceSpecial { get; set; } = "Special Non-Relearn Move."; - public static string LMoveSourceTMHM { get; set; } = "Learned by TM/HM."; - public static string LMoveSourceTutor { get; set; } = "Learned by Move Tutor."; - public static string LMoveSourceTR { get; set; } = "Unexpected Technical Record Learned flag: {0}"; + public static string LMoveSourceDefault { get; set; } = "Default move."; + public static string LMoveSourceDuplicate { get; set; } = "Duplicate Move."; + public static string LMoveSourceEgg { get; set; } = "Egg Move."; + public static string LMoveSourceEggEvent { get; set; } = "Event Egg Move."; + public static string LMoveSourceEmpty { get; set; } = "Empty Move."; + public static string LMoveSourceInvalid { get; set; } = "Invalid Move."; + public static string LMoveSourceInvalidSketch { get; set; } = "Invalid Move (Sketch)."; + public static string LMoveSourceLevelUp { get; set; } = "Learned by Level-up."; + public static string LMoveSourceRelearn { get; set; } = "Relearnable Move."; + public static string LMoveSourceSpecial { get; set; } = "Special Non-Relearn Move."; + public static string LMoveSourceTMHM { get; set; } = "Learned by TM/HM."; + public static string LMoveSourceTutor { get; set; } = "Learned by Move Tutor."; + public static string LMoveSourceTR { get; set; } = "Unexpected Technical Record Learned flag: {0}"; - public static string LNickFlagEggNo { get; set; } = "Egg must be not nicknamed."; - public static string LNickFlagEggYes { get; set; } = "Egg must be nicknamed."; - public static string LNickInvalidChar { get; set; } = "Cannot be given this Nickname."; - public static string LNickLengthLong { get; set; } = "Nickname too long."; - public static string LNickLengthShort { get; set; } = "Nickname is empty."; - public static string LNickMatchLanguage { get; set; } = "Nickname matches species name."; - public static string LNickMatchLanguageEgg { get; set; } = "Egg matches language Egg name."; - public static string LNickMatchLanguageEggFail { get; set; } = "Egg name does not match language Egg name."; - public static string LNickMatchLanguageFail { get; set; } = "Nickname does not match species name."; - public static string LNickMatchLanguageFlag { get; set; } = "Nickname flagged, matches species name."; - public static string LNickMatchNoOthers { get; set; } = "Nickname does not match another species name."; - public static string LNickMatchNoOthersFail { get; set; } = "Nickname matches another species name (+language)."; + public static string LNickFlagEggNo { get; set; } = "Egg must be not nicknamed."; + public static string LNickFlagEggYes { get; set; } = "Egg must be nicknamed."; + public static string LNickInvalidChar { get; set; } = "Cannot be given this Nickname."; + public static string LNickLengthLong { get; set; } = "Nickname too long."; + public static string LNickLengthShort { get; set; } = "Nickname is empty."; + public static string LNickMatchLanguage { get; set; } = "Nickname matches species name."; + public static string LNickMatchLanguageEgg { get; set; } = "Egg matches language Egg name."; + public static string LNickMatchLanguageEggFail { get; set; } = "Egg name does not match language Egg name."; + public static string LNickMatchLanguageFail { get; set; } = "Nickname does not match species name."; + public static string LNickMatchLanguageFlag { get; set; } = "Nickname flagged, matches species name."; + public static string LNickMatchNoOthers { get; set; } = "Nickname does not match another species name."; + public static string LNickMatchNoOthersFail { get; set; } = "Nickname matches another species name (+language)."; - public static string LOTLanguage { get; set; } = "Language ID should be {0}, not {1}."; - public static string LOTLong { get; set; } = "OT Name too long."; - public static string LOTShort { get; set; } = "OT Name too short."; - public static string LOTSuspicious { get; set; } = "Suspicious Original Trainer details."; + public static string LOTLanguage { get; set; } = "Language ID should be {0}, not {1}."; + public static string LOTLong { get; set; } = "OT Name too long."; + public static string LOTShort { get; set; } = "OT Name too short."; + public static string LOTSuspicious { get; set; } = "Suspicious Original Trainer details."; - public static string LOT_IDEqual { get; set; } = "TID and SID are equal."; - public static string LOT_IDs0 { get; set; } = "TID and SID are 0."; - public static string LOT_SID0 { get; set; } = "SID is zero."; - public static string LOT_SID0Invalid { get; set; } = "SID should be 0."; - public static string LOT_TID0 { get; set; } = "TID is zero."; - public static string LOT_IDInvalid { get; set; } = "TID and SID combination is not possible."; + public static string LOT_IDEqual { get; set; } = "TID and SID are equal."; + public static string LOT_IDs0 { get; set; } = "TID and SID are 0."; + public static string LOT_SID0 { get; set; } = "SID is zero."; + public static string LOT_SID0Invalid { get; set; } = "SID should be 0."; + public static string LOT_TID0 { get; set; } = "TID is zero."; + public static string LOT_IDInvalid { get; set; } = "TID and SID combination is not possible."; - public static string LPIDEncryptWurmple { get; set; } = "Wurmple evolution Encryption Constant mismatch."; - public static string LPIDEncryptZero { get; set; } = "Encryption Constant is not set."; - public static string LPIDEqualsEC { get; set; } = "Encryption Constant matches PID."; - public static string LPIDGenderMatch { get; set; } = "Gender matches PID."; - public static string LPIDGenderMismatch { get; set; } = "PID-Gender mismatch."; - public static string LPIDNatureMatch { get; set; } = "Nature matches PID."; - public static string LPIDNatureMismatch { get; set; } = "PID-Nature mismatch."; - public static string LPIDTypeMismatch { get; set; } = "Encounter Type PID mismatch."; - public static string LPIDZero { get; set; } = "PID is not set."; + public static string LPIDEncryptWurmple { get; set; } = "Wurmple evolution Encryption Constant mismatch."; + public static string LPIDEncryptZero { get; set; } = "Encryption Constant is not set."; + public static string LPIDEqualsEC { get; set; } = "Encryption Constant matches PID."; + public static string LPIDGenderMatch { get; set; } = "Gender matches PID."; + public static string LPIDGenderMismatch { get; set; } = "PID-Gender mismatch."; + public static string LPIDNatureMatch { get; set; } = "Nature matches PID."; + public static string LPIDNatureMismatch { get; set; } = "PID-Nature mismatch."; + public static string LPIDTypeMismatch { get; set; } = "Encounter Type PID mismatch."; + public static string LPIDZero { get; set; } = "PID is not set."; - public static string LPokerusDaysTooHigh_0 { get; set; } = "Pokérus Days Remaining value is too high; expected <= {0}."; - public static string LPokerusStrainUnobtainable_0 { get; set; } = "Pokérus Strain {0} cannot be obtained."; + public static string LPokerusDaysTooHigh_0 { get; set; } = "Pokérus Days Remaining value is too high; expected <= {0}."; + public static string LPokerusStrainUnobtainable_0 { get; set; } = "Pokérus Strain {0} cannot be obtained."; - public static string LRibbonAllValid { get; set; } = "All ribbons accounted for."; - public static string LRibbonEgg { get; set; } = "Can't receive Ribbon(s) as an Egg."; - public static string LRibbonFInvalid_0 { get; set; } = "Invalid Ribbons: {0}"; - public static string LRibbonFMissing_0 { get; set; } = "Missing Ribbons: {0}"; - public static string LRibbonMarkingFInvalid_0 { get; set; } = "Invalid Marking: {0}"; - public static string LRibbonMarkingAffixedF_0 { get; set; } = "Invalid Affixed Ribbon/Marking: {0}"; + public static string LRibbonAllValid { get; set; } = "All ribbons accounted for."; + public static string LRibbonEgg { get; set; } = "Can't receive Ribbon(s) as an Egg."; + public static string LRibbonFInvalid_0 { get; set; } = "Invalid Ribbons: {0}"; + public static string LRibbonFMissing_0 { get; set; } = "Missing Ribbons: {0}"; + public static string LRibbonMarkingFInvalid_0 { get; set; } = "Invalid Marking: {0}"; + public static string LRibbonMarkingAffixedF_0 { get; set; } = "Invalid Affixed Ribbon/Marking: {0}"; - public static string LStatDynamaxInvalid { get; set; } = "Dynamax Level is not within the expected range."; - public static string LStatIncorrectHeight { get; set; } = "Calculated Height does not match stored value."; - public static string LStatIncorrectHeightCopy { get; set; } = "Copy Height does not match the original value."; - public static string LStatIncorrectHeightValue { get; set; } = "Height does not match the expected value."; - public static string LStatIncorrectWeight { get; set; } = "Calculated Weight does not match stored value."; - public static string LStatIncorrectWeightValue { get; set; } = "Weight does not match the expected value."; - public static string LStatInvalidHeightWeight { get; set; } = "Height / Weight values are statistically improbable."; - public static string LStatIncorrectCP { get; set; } = "Calculated CP does not match stored value."; - public static string LStatGigantamaxInvalid { get; set; } = "Gigantamax Flag mismatch."; - public static string LStatGigantamaxValid { get; set; } = "Gigantamax Flag was changed via Max Soup."; - public static string LStatNatureInvalid { get; set; } = "Stat Nature is not within the expected range."; - public static string LStatBattleVersionInvalid { get; set; } = "Battle Version is not within the expected range."; - public static string LStatNobleInvalid { get; set; } = "Noble Flag mismatch."; - public static string LStatAlphaInvalid { get; set; } = "Alpha Flag mismatch."; + public static string LStatDynamaxInvalid { get; set; } = "Dynamax Level is not within the expected range."; + public static string LStatIncorrectHeight { get; set; } = "Calculated Height does not match stored value."; + public static string LStatIncorrectHeightCopy { get; set; } = "Copy Height does not match the original value."; + public static string LStatIncorrectHeightValue { get; set; } = "Height does not match the expected value."; + public static string LStatIncorrectWeight { get; set; } = "Calculated Weight does not match stored value."; + public static string LStatIncorrectWeightValue { get; set; } = "Weight does not match the expected value."; + public static string LStatInvalidHeightWeight { get; set; } = "Height / Weight values are statistically improbable."; + public static string LStatIncorrectCP { get; set; } = "Calculated CP does not match stored value."; + public static string LStatGigantamaxInvalid { get; set; } = "Gigantamax Flag mismatch."; + public static string LStatGigantamaxValid { get; set; } = "Gigantamax Flag was changed via Max Soup."; + public static string LStatNatureInvalid { get; set; } = "Stat Nature is not within the expected range."; + public static string LStatBattleVersionInvalid { get; set; } = "Battle Version is not within the expected range."; + public static string LStatNobleInvalid { get; set; } = "Noble Flag mismatch."; + public static string LStatAlphaInvalid { get; set; } = "Alpha Flag mismatch."; - public static string LSuperComplete { get; set; } = "Super Training complete flag mismatch."; - public static string LSuperDistro { get; set; } = "Distribution Super Training missions are not released."; - public static string LSuperEgg { get; set; } = "Can't Super Train an Egg."; - public static string LSuperNoComplete { get; set; } = "Can't have active Super Training complete flag for origins."; - public static string LSuperNoUnlocked { get; set; } = "Can't have active Super Training unlocked flag for origins."; - public static string LSuperUnavailable { get; set; } = "Super Training missions are not available in games visited."; - public static string LSuperUnused { get; set; } = "Unused Super Training Flag is flagged."; + public static string LSuperComplete { get; set; } = "Super Training complete flag mismatch."; + public static string LSuperDistro { get; set; } = "Distribution Super Training missions are not released."; + public static string LSuperEgg { get; set; } = "Can't Super Train an Egg."; + public static string LSuperNoComplete { get; set; } = "Can't have active Super Training complete flag for origins."; + public static string LSuperNoUnlocked { get; set; } = "Can't have active Super Training unlocked flag for origins."; + public static string LSuperUnavailable { get; set; } = "Super Training missions are not available in games visited."; + public static string LSuperUnused { get; set; } = "Unused Super Training Flag is flagged."; - public static string LTransferBad { get; set; } = "Incorrectly transferred from previous generation."; + public static string LTransferBad { get; set; } = "Incorrectly transferred from previous generation."; - public static string LTransferCurrentHandlerInvalid { get; set; } = "Invalid Current handler value, trainer details for save file expected another value."; - public static string LTransferEgg { get; set; } = "Can't transfer Eggs between Generations."; - public static string LTransferEggLocationTransporter { get; set; } = "Invalid Met Location, expected Poké Transfer."; - public static string LTransferEggMetLevel { get; set; } = "Invalid Met Level for transfer."; - public static string LTransferFlagIllegal { get; set; } = "Flagged as illegal by the game (glitch abuse)."; - public static string LTransferHTFlagRequired { get; set; } = "Current handler cannot be past gen OT for transferred specimen."; - public static string LTransferHTMismatchName { get; set; } = "Handling trainer does not match the expected trainer name."; - public static string LTransferHTMismatchLanguage { get; set; } = "Handling trainer does not match the expected trainer language."; - public static string LTransferMet { get; set; } = "Invalid Met Location, expected Poké Transfer or Crown."; - public static string LTransferNotPossible { get; set; } = "Unable to transfer into current format from origin format."; - public static string LTransferMetLocation { get; set; } = "Invalid Transfer Met Location."; - public static string LTransferMove { get; set; } = "Incompatible transfer move."; - public static string LTransferMoveG4HM { get; set; } = "Defog and Whirlpool. One of the two moves should have been removed before transferred to Generation 5."; - public static string LTransferMoveHM { get; set; } = "Generation {0} HM. Should have been removed before transferred to Generation {1}."; - public static string LTransferNature { get; set; } = "Invalid Nature for transfer Experience."; - public static string LTransferOriginFInvalid0_1 { get; set; } = "{0} origin cannot exist in the currently loaded ({1}) save file."; - public static string LTransferPIDECBitFlip { get; set; } = "PID should be equal to EC [with top bit flipped]!"; - public static string LTransferPIDECEquals { get; set; } = "PID should be equal to EC!"; - public static string LTransferPIDECXor { get; set; } = "Encryption Constant matches shinyxored PID."; - public static string LTransferTrackerMissing { get; set; } = "Pokémon HOME Transfer Tracker is missing."; - public static string LTransferTrackerShouldBeZero { get; set; } = "Pokémon HOME Transfer Tracker should be 0."; - #endregion + public static string LTransferCurrentHandlerInvalid { get; set; } = "Invalid Current handler value, trainer details for save file expected another value."; + public static string LTransferEgg { get; set; } = "Can't transfer Eggs between Generations."; + public static string LTransferEggLocationTransporter { get; set; } = "Invalid Met Location, expected Poké Transfer."; + public static string LTransferEggMetLevel { get; set; } = "Invalid Met Level for transfer."; + public static string LTransferFlagIllegal { get; set; } = "Flagged as illegal by the game (glitch abuse)."; + public static string LTransferHTFlagRequired { get; set; } = "Current handler cannot be past gen OT for transferred specimen."; + public static string LTransferHTMismatchName { get; set; } = "Handling trainer does not match the expected trainer name."; + public static string LTransferHTMismatchLanguage { get; set; } = "Handling trainer does not match the expected trainer language."; + public static string LTransferMet { get; set; } = "Invalid Met Location, expected Poké Transfer or Crown."; + public static string LTransferNotPossible { get; set; } = "Unable to transfer into current format from origin format."; + public static string LTransferMetLocation { get; set; } = "Invalid Transfer Met Location."; + public static string LTransferMove { get; set; } = "Incompatible transfer move."; + public static string LTransferMoveG4HM { get; set; } = "Defog and Whirlpool. One of the two moves should have been removed before transferred to Generation 5."; + public static string LTransferMoveHM { get; set; } = "Generation {0} HM. Should have been removed before transferred to Generation {1}."; + public static string LTransferNature { get; set; } = "Invalid Nature for transfer Experience."; + public static string LTransferOriginFInvalid0_1 { get; set; } = "{0} origin cannot exist in the currently loaded ({1}) save file."; + public static string LTransferPIDECBitFlip { get; set; } = "PID should be equal to EC [with top bit flipped]!"; + public static string LTransferPIDECEquals { get; set; } = "PID should be equal to EC!"; + public static string LTransferPIDECXor { get; set; } = "Encryption Constant matches shinyxored PID."; + public static string LTransferTrackerMissing { get; set; } = "Pokémon HOME Transfer Tracker is missing."; + public static string LTransferTrackerShouldBeZero { get; set; } = "Pokémon HOME Transfer Tracker should be 0."; + #endregion - } } diff --git a/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs b/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs index ab680af43..7ebfd1c69 100644 --- a/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs +++ b/PKHeX.Core/Legality/Formatting/LegalityFormatting.cs @@ -2,122 +2,121 @@ using System.Linq; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Formatting logic for to create a human readable . +/// +public static class LegalityFormatting { /// - /// Formatting logic for to create a human readable . + /// Creates a report message with optional verbosity for in-depth analysis. /// - public static class LegalityFormatting + /// Legality result to format + /// Include all details in the parse, including valid check messages. + /// Single line string + public static string Report(this LegalityAnalysis la, bool verbose = false) => verbose ? GetVerboseLegalityReport(la) : GetLegalityReport(la); + + public static ILegalityFormatter Formatter { private get; set; } = new BaseLegalityFormatter(); + + public static string GetLegalityReport(LegalityAnalysis la) => Formatter.GetReport(la); + public static string GetVerboseLegalityReport(LegalityAnalysis la) => Formatter.GetReportVerbose(la); + + public static void AddSecondaryChecksValid(IEnumerable results, List lines) { - /// - /// Creates a report message with optional verbosity for in-depth analysis. - /// - /// Legality result to format - /// Include all details in the parse, including valid check messages. - /// Single line string - public static string Report(this LegalityAnalysis la, bool verbose = false) => verbose ? GetVerboseLegalityReport(la) : GetLegalityReport(la); + var outputLines = results + .Where(chk => chk.Valid && chk.Comment != L_AValid) + .OrderBy(chk => chk.Judgement) // Fishy sorted to top + .Select(chk => chk.Format(L_F0_1)); + lines.AddRange(outputLines); + } - public static ILegalityFormatter Formatter { private get; set; } = new BaseLegalityFormatter(); - - public static string GetLegalityReport(LegalityAnalysis la) => Formatter.GetReport(la); - public static string GetVerboseLegalityReport(LegalityAnalysis la) => Formatter.GetReportVerbose(la); - - public static void AddSecondaryChecksValid(IEnumerable results, List lines) + public static void AddSecondaryChecksInvalid(IReadOnlyList results, List lines) + { + foreach (var chk in results) { - var outputLines = results - .Where(chk => chk.Valid && chk.Comment != L_AValid) - .OrderBy(chk => chk.Judgement) // Fishy sorted to top - .Select(chk => chk.Format(L_F0_1)); - lines.AddRange(outputLines); - } - - public static void AddSecondaryChecksInvalid(IReadOnlyList results, List lines) - { - foreach (var chk in results) - { - if (chk.Valid) - continue; - lines.Add(chk.Format(L_F0_1)); - } - } - - public static void AddRelearn(CheckMoveResult[] relearn, List lines, bool state) - { - for (int i = 0; i < relearn.Length; i++) - { - var move = relearn[i]; - if (move.Valid == state) - lines.Add(move.Format(L_F0_RM_1_2, i + 1)); - } - } - - public static void AddMoves(CheckMoveResult[] moves, List lines, in int currentFormat, bool state) - { - for (int i = 0; i < moves.Length; i++) - { - var move = moves[i]; - if (move.Valid != state) - continue; - var msg = move.Format(L_F0_M_1_2, i + 1); - var gen = move.Generation; - if (currentFormat != gen) - msg += $" [Gen{gen}]"; - lines.Add(msg); - } - } - - /// - /// Adds information about the to the . - /// - public static void AddEncounterInfo(LegalityAnalysis la, List lines) - { - var enc = la.EncounterOriginal; - - // Name - lines.Add(string.Format(L_FEncounterType_0, enc.GetEncounterName())); - if (enc is MysteryGift g) - lines.Add(g.CardHeader); - - // Location - var loc = enc.GetEncounterLocation(); - if (!string.IsNullOrEmpty(loc)) - lines.Add(string.Format(L_F0_1, "Location", loc)); - - // Version - if (enc.Generation <= 2) - lines.Add(string.Format(L_F0_1, nameof(GameVersion), enc.Version)); - - // PIDIV - AddEncounterInfoPIDIV(la, lines); - } - - public static void AddEncounterInfoPIDIV(LegalityAnalysis la, List lines) - { - var info = la.Info; - if (!info.PIDParsed) - info.PIDIV = MethodFinder.Analyze(la.pkm); - AddEncounterInfoPIDIV(lines, info.PIDIV); - } - - private static void AddEncounterInfoPIDIV(List lines, PIDIV pidiv) - { - lines.Add(string.Format(L_FPIDType_0, pidiv.Type)); - if (!pidiv.NoSeed) - lines.Add(string.Format(L_FOriginSeed_0, pidiv.OriginSeed.ToString("X8"))); - } - - public static string GetEncounterName(this IEncounterable enc) - { - var str = ParseSettings.SpeciesStrings; - var name = (uint) enc.Species < str.Count ? str[enc.Species] : enc.Species.ToString(); - return $"{enc.LongName} ({name})"; - } - - public static string? GetEncounterLocation(this IEncounterTemplate enc) - { - if (enc is not ILocation loc) - return null; - return loc.GetEncounterLocation(enc.Generation, (int)enc.Version); + if (chk.Valid) + continue; + lines.Add(chk.Format(L_F0_1)); } } + + public static void AddRelearn(CheckMoveResult[] relearn, List lines, bool state) + { + for (int i = 0; i < relearn.Length; i++) + { + var move = relearn[i]; + if (move.Valid == state) + lines.Add(move.Format(L_F0_RM_1_2, i + 1)); + } + } + + public static void AddMoves(CheckMoveResult[] moves, List lines, in int currentFormat, bool state) + { + for (int i = 0; i < moves.Length; i++) + { + var move = moves[i]; + if (move.Valid != state) + continue; + var msg = move.Format(L_F0_M_1_2, i + 1); + var gen = move.Generation; + if (currentFormat != gen) + msg += $" [Gen{gen}]"; + lines.Add(msg); + } + } + + /// + /// Adds information about the to the . + /// + public static void AddEncounterInfo(LegalityAnalysis la, List lines) + { + var enc = la.EncounterOriginal; + + // Name + lines.Add(string.Format(L_FEncounterType_0, enc.GetEncounterName())); + if (enc is MysteryGift g) + lines.Add(g.CardHeader); + + // Location + var loc = enc.GetEncounterLocation(); + if (!string.IsNullOrEmpty(loc)) + lines.Add(string.Format(L_F0_1, "Location", loc)); + + // Version + if (enc.Generation <= 2) + lines.Add(string.Format(L_F0_1, nameof(GameVersion), enc.Version)); + + // PIDIV + AddEncounterInfoPIDIV(la, lines); + } + + public static void AddEncounterInfoPIDIV(LegalityAnalysis la, List lines) + { + var info = la.Info; + if (!info.PIDParsed) + info.PIDIV = MethodFinder.Analyze(la.Entity); + AddEncounterInfoPIDIV(lines, info.PIDIV); + } + + private static void AddEncounterInfoPIDIV(List lines, PIDIV pidiv) + { + lines.Add(string.Format(L_FPIDType_0, pidiv.Type)); + if (!pidiv.NoSeed) + lines.Add(string.Format(L_FOriginSeed_0, pidiv.OriginSeed.ToString("X8"))); + } + + public static string GetEncounterName(this IEncounterable enc) + { + var str = ParseSettings.SpeciesStrings; + var name = (uint) enc.Species < str.Count ? str[enc.Species] : enc.Species.ToString(); + return $"{enc.LongName} ({name})"; + } + + public static string? GetEncounterLocation(this IEncounterTemplate enc) + { + if (enc is not ILocation loc) + return null; + return loc.GetEncounterLocation(enc.Generation, (int)enc.Version); + } } diff --git a/PKHeX.Core/Legality/Learnset/Learnset.cs b/PKHeX.Core/Legality/Learnset/Learnset.cs index f729be0e7..099426333 100644 --- a/PKHeX.Core/Legality/Learnset/Learnset.cs +++ b/PKHeX.Core/Legality/Learnset/Learnset.cs @@ -1,336 +1,335 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Level Up Learn Movepool Information +/// +public sealed class Learnset { /// - /// Level Up Learn Movepool Information + /// Moves that can be learned. /// - public sealed class Learnset + private readonly int[] Moves; + + /// + /// Levels at which a move at a given index can be learned. + /// + private readonly int[] Levels; + + public Learnset(int[] moves, int[] levels) { - /// - /// Moves that can be learned. - /// - private readonly int[] Moves; + Moves = moves; + Levels = levels; + } - /// - /// Levels at which a move at a given index can be learned. - /// - private readonly int[] Levels; + /// + /// Returns the moves a Pokémon can learn between the specified level range. + /// + /// Maximum level + /// Minimum level + /// Array of Move IDs + public int[] GetMoves(int maxLevel, int minLevel = 0) + { + if (minLevel <= 1 && maxLevel >= 100) + return Moves; + if (minLevel > maxLevel) + return Array.Empty(); + int start = Array.FindIndex(Levels, z => z >= minLevel); + if (start < 0) + return Array.Empty(); + int end = Array.FindLastIndex(Levels, z => z <= maxLevel); + if (end < 0) + return Array.Empty(); - public Learnset(int[] moves, int[] levels) + var length = end - start + 1; + if (length == Moves.Length) + return Moves; + return Moves.AsSpan(start, length).ToArray(); + } + + /// + /// Adds the moves a Pokémon can learn between the specified level range. + /// + /// Movepool + /// Maximum level + /// Minimum level + /// Array of Move IDs + public List AddMoves(List moves, int maxLevel, int minLevel = 0) + { + if (minLevel <= 1 && maxLevel >= 100) { - Moves = moves; - Levels = levels; - } - - /// - /// Returns the moves a Pokémon can learn between the specified level range. - /// - /// Maximum level - /// Minimum level - /// Array of Move IDs - public int[] GetMoves(int maxLevel, int minLevel = 0) - { - if (minLevel <= 1 && maxLevel >= 100) - return Moves; - if (minLevel > maxLevel) - return Array.Empty(); - int start = Array.FindIndex(Levels, z => z >= minLevel); - if (start < 0) - return Array.Empty(); - int end = Array.FindLastIndex(Levels, z => z <= maxLevel); - if (end < 0) - return Array.Empty(); - - var length = end - start + 1; - if (length == Moves.Length) - return Moves; - return Moves.AsSpan(start, length).ToArray(); - } - - /// - /// Adds the moves a Pokémon can learn between the specified level range. - /// - /// Movepool - /// Maximum level - /// Minimum level - /// Array of Move IDs - public List AddMoves(List moves, int maxLevel, int minLevel = 0) - { - if (minLevel <= 1 && maxLevel >= 100) - { - moves.AddRange(Moves); - return moves; - } - if (minLevel > maxLevel) - return moves; - int start = Array.FindIndex(Levels, z => z >= minLevel); - if (start < 0) - return moves; - int end = Array.FindLastIndex(Levels, z => z <= maxLevel); - if (end < 0) - return moves; - for (int i = start; i < end + 1; i++) - moves.Add(Moves[i]); + moves.AddRange(Moves); return moves; } - - /// - /// Gets the moves a Pokémon can learn between the specified level range as a list. - /// - /// Maximum level - /// Minimum level - /// Array of Move IDs - public List GetMoveList(int maxLevel, int minLevel = 0) - { - var list = new List(); - return AddMoves(list, maxLevel, minLevel); - } - - /// Returns the moves a Pokémon would have if it were encountered at the specified level. - /// In Generation 1, it is not possible to learn any moves lower than these encounter moves. - /// The level the Pokémon was encountered at. - /// Array of Move IDs - public int[] GetEncounterMoves(int level) - { - const int count = 4; - var moves = new int[count]; - SetEncounterMoves(level, moves); + if (minLevel > maxLevel) return moves; - } + int start = Array.FindIndex(Levels, z => z >= minLevel); + if (start < 0) + return moves; + int end = Array.FindLastIndex(Levels, z => z <= maxLevel); + if (end < 0) + return moves; + for (int i = start; i < end + 1; i++) + moves.Add(Moves[i]); + return moves; + } - /// Returns the moves a Pokémon would have if it were encountered at the specified level. - /// In Generation 1, it is not possible to learn any moves lower than these encounter moves. - /// The level the Pokémon was encountered at. - /// Move array to write to - /// Starting index to begin overwriting at - /// Array of Move IDs - public void SetEncounterMoves(int level, Span moves, int ctr = 0) + /// + /// Gets the moves a Pokémon can learn between the specified level range as a list. + /// + /// Maximum level + /// Minimum level + /// Array of Move IDs + public List GetMoveList(int maxLevel, int minLevel = 0) + { + var list = new List(); + return AddMoves(list, maxLevel, minLevel); + } + + /// Returns the moves a Pokémon would have if it were encountered at the specified level. + /// In Generation 1, it is not possible to learn any moves lower than these encounter moves. + /// The level the Pokémon was encountered at. + /// Array of Move IDs + public int[] GetEncounterMoves(int level) + { + const int count = 4; + var moves = new int[count]; + SetEncounterMoves(level, moves); + return moves; + } + + /// Returns the moves a Pokémon would have if it were encountered at the specified level. + /// In Generation 1, it is not possible to learn any moves lower than these encounter moves. + /// The level the Pokémon was encountered at. + /// Move array to write to + /// Starting index to begin overwriting at + /// Array of Move IDs + public void SetEncounterMoves(int level, Span moves, int ctr = 0) + { + for (int i = 0; i < Moves.Length; i++) { - for (int i = 0; i < Moves.Length; i++) - { - if (Levels[i] > level) - break; + if (Levels[i] > level) + break; - int move = Moves[i]; - bool alreadyHasMove = moves.IndexOf(move) >= 0; - if (alreadyHasMove) - continue; + int move = Moves[i]; + bool alreadyHasMove = moves.IndexOf(move) >= 0; + if (alreadyHasMove) + continue; - moves[ctr++] = move; - ctr &= 3; - } - } - - public void SetEncounterMovesBackwards(int level, Span moves, int ctr = 0) - { - int index = Array.FindLastIndex(Levels, z => z <= level); - - while (true) - { - if (index == -1) - return; // no moves to add? - - // In the event we have multiple moves at the same level, insert them in regular descending order. - int start = index; - while (start != 0 && Levels[start] == Levels[start - 1]) - start--; - - for (int i = start; i <= index; i++) - { - var move = Moves[i]; - if (moves.IndexOf(move) == -1) - moves[ctr++] = move; - - if (ctr == 4) - return; - } - - index = start - 1; - } - } - - /// Adds the learned moves by level up to the specified level. - public void SetLevelUpMoves(int startLevel, int endLevel, Span moves, int ctr = 0) - { - int startIndex = Array.FindIndex(Levels, z => z >= startLevel); - int endIndex = Array.FindIndex(Levels, z => z > endLevel); - for (int i = startIndex; i < endIndex; i++) - { - int move = Moves[i]; - bool alreadyHasMove = moves.IndexOf(move) >= 0; - if (alreadyHasMove) - continue; - - moves[ctr++] = move; - ctr &= 3; - } - } - - /// Adds the moves that are gained upon evolving. - /// Move array to write to - /// Starting index to begin overwriting at - public void SetEvolutionMoves(Span moves, int ctr = 0) - { - for (int i = 0; i < Moves.Length; i++) - { - if (Levels[i] != 0) - break; - - int move = Moves[i]; - bool alreadyHasMove = moves.IndexOf(move) >= 0; - if (alreadyHasMove) - continue; - - moves[ctr++] = move; - ctr &= 3; - } - } - - /// Adds the learned moves by level up to the specified level. - public void SetLevelUpMoves(int startLevel, int endLevel, Span moves, ReadOnlySpan ignore, int ctr = 0) - { - int startIndex = Array.FindIndex(Levels, z => z >= startLevel); - if (startIndex == -1) - return; // No more remain - int endIndex = Array.FindIndex(Levels, z => z > endLevel); - if (endIndex == -1) - endIndex = Levels.Length; - for (int i = startIndex; i < endIndex; i++) - { - int move = Moves[i]; - if (ignore.IndexOf(move) >= 0) - continue; - - bool alreadyHasMove = moves.IndexOf(move) >= 0; - if (alreadyHasMove) - continue; - - moves[ctr++] = move; - ctr &= 3; - } - } - - /// Adds the moves that are gained upon evolving. - /// Move array to write to - /// Ignored moves - /// Starting index to begin overwriting at - public void SetEvolutionMoves(Span moves, ReadOnlySpan ignore, int ctr = 0) - { - for (int i = 0; i < Moves.Length; i++) - { - if (Levels[i] != 0) - break; - - int move = Moves[i]; - if (ignore.IndexOf(move) >= 0) - continue; - - bool alreadyHasMove = moves.IndexOf(move) >= 0; - if (alreadyHasMove) - continue; - - moves[ctr++] = move; - ctr &= 3; - } - } - - public IList GetUniqueMovesLearned(IEnumerable seed, int maxLevel, int minLevel = 0) - { - int start = Array.FindIndex(Levels, z => z >= minLevel); - int end = Array.FindLastIndex(Levels, z => z <= maxLevel); - var list = new List(seed); - for (int i = start; i <= end; i++) - { - if (!list.Contains(Moves[i])) - list.Add(Moves[i]); - } - return list; - } - - /// Returns the index of the lowest level move if the Pokémon were encountered at the specified level. - /// Helps determine the minimum level an encounter can be at. - /// The level the Pokémon was encountered at. - /// Array of Move IDs - public int GetMinMoveLevel(int level) - { - if (Levels.Length == 0) - return 1; - - int end = Array.FindLastIndex(Levels, z => z <= level); - return Math.Max(end - 4, 1); - } - - public int GetMoveLevel(int move) - { - var index = Array.LastIndexOf(Moves, move); - if (index == -1) - return -1; - return Levels[index]; - } - - private Dictionary? Learn; - - private Dictionary GetDictionary() - { - var dict = new Dictionary(); - for (int i = 0; i < Moves.Length; i++) - { - if (!dict.ContainsKey(Moves[i])) - dict.Add(Moves[i], Levels[i]); - } - return dict; - } - - /// Returns the level that a Pokémon can learn the specified move. - /// Move ID - /// Level the move is learned at. If the result is below 0, the move cannot be learned by leveling up. - public int GetLevelLearnMove(int move) - { - return (Learn ??= GetDictionary()).TryGetValue(move, out var level) ? level : -1; - } - - /// Returns the level that a Pokémon can learn the specified move. - /// Move ID - /// Minimum level to start looking at. - /// Level the move is learned at. If the result is below 0, the move cannot be learned by leveling up. - public int GetLevelLearnMove(int move, int min) - { - for (int i = 0; i < Moves.Length; i++) - { - if (move != Moves[i]) - continue; - - var lv = Levels[i]; - if (lv >= min) - return lv; - } - return -1; - } - - public ReadOnlySpan GetBaseEggMoves(int level) - { - // Count moves <= level - var count = 0; - foreach (var x in Levels) - { - if (x > level) - break; - count++; - } - - // Return a slice containing the moves <= level. - if (count == 0) - return ReadOnlySpan.Empty; - - int start = 0; - if (count > 4) - { - start = count - 4; - count = 4; - } - return Moves.AsSpan(start, count); + moves[ctr++] = move; + ctr &= 3; } } + + public void SetEncounterMovesBackwards(int level, Span moves, int ctr = 0) + { + int index = Array.FindLastIndex(Levels, z => z <= level); + + while (true) + { + if (index == -1) + return; // no moves to add? + + // In the event we have multiple moves at the same level, insert them in regular descending order. + int start = index; + while (start != 0 && Levels[start] == Levels[start - 1]) + start--; + + for (int i = start; i <= index; i++) + { + var move = Moves[i]; + if (moves.IndexOf(move) == -1) + moves[ctr++] = move; + + if (ctr == 4) + return; + } + + index = start - 1; + } + } + + /// Adds the learned moves by level up to the specified level. + public void SetLevelUpMoves(int startLevel, int endLevel, Span moves, int ctr = 0) + { + int startIndex = Array.FindIndex(Levels, z => z >= startLevel); + int endIndex = Array.FindIndex(Levels, z => z > endLevel); + for (int i = startIndex; i < endIndex; i++) + { + int move = Moves[i]; + bool alreadyHasMove = moves.IndexOf(move) >= 0; + if (alreadyHasMove) + continue; + + moves[ctr++] = move; + ctr &= 3; + } + } + + /// Adds the moves that are gained upon evolving. + /// Move array to write to + /// Starting index to begin overwriting at + public void SetEvolutionMoves(Span moves, int ctr = 0) + { + for (int i = 0; i < Moves.Length; i++) + { + if (Levels[i] != 0) + break; + + int move = Moves[i]; + bool alreadyHasMove = moves.IndexOf(move) >= 0; + if (alreadyHasMove) + continue; + + moves[ctr++] = move; + ctr &= 3; + } + } + + /// Adds the learned moves by level up to the specified level. + public void SetLevelUpMoves(int startLevel, int endLevel, Span moves, ReadOnlySpan ignore, int ctr = 0) + { + int startIndex = Array.FindIndex(Levels, z => z >= startLevel); + if (startIndex == -1) + return; // No more remain + int endIndex = Array.FindIndex(Levels, z => z > endLevel); + if (endIndex == -1) + endIndex = Levels.Length; + for (int i = startIndex; i < endIndex; i++) + { + int move = Moves[i]; + if (ignore.IndexOf(move) >= 0) + continue; + + bool alreadyHasMove = moves.IndexOf(move) >= 0; + if (alreadyHasMove) + continue; + + moves[ctr++] = move; + ctr &= 3; + } + } + + /// Adds the moves that are gained upon evolving. + /// Move array to write to + /// Ignored moves + /// Starting index to begin overwriting at + public void SetEvolutionMoves(Span moves, ReadOnlySpan ignore, int ctr = 0) + { + for (int i = 0; i < Moves.Length; i++) + { + if (Levels[i] != 0) + break; + + int move = Moves[i]; + if (ignore.IndexOf(move) >= 0) + continue; + + bool alreadyHasMove = moves.IndexOf(move) >= 0; + if (alreadyHasMove) + continue; + + moves[ctr++] = move; + ctr &= 3; + } + } + + public IList GetUniqueMovesLearned(IEnumerable seed, int maxLevel, int minLevel = 0) + { + int start = Array.FindIndex(Levels, z => z >= minLevel); + int end = Array.FindLastIndex(Levels, z => z <= maxLevel); + var list = new List(seed); + for (int i = start; i <= end; i++) + { + if (!list.Contains(Moves[i])) + list.Add(Moves[i]); + } + return list; + } + + /// Returns the index of the lowest level move if the Pokémon were encountered at the specified level. + /// Helps determine the minimum level an encounter can be at. + /// The level the Pokémon was encountered at. + /// Array of Move IDs + public int GetMinMoveLevel(int level) + { + if (Levels.Length == 0) + return 1; + + int end = Array.FindLastIndex(Levels, z => z <= level); + return Math.Max(end - 4, 1); + } + + public int GetMoveLevel(int move) + { + var index = Array.LastIndexOf(Moves, move); + if (index == -1) + return -1; + return Levels[index]; + } + + private Dictionary? Learn; + + private Dictionary GetDictionary() + { + var dict = new Dictionary(); + for (int i = 0; i < Moves.Length; i++) + { + if (!dict.ContainsKey(Moves[i])) + dict.Add(Moves[i], Levels[i]); + } + return dict; + } + + /// Returns the level that a Pokémon can learn the specified move. + /// Move ID + /// Level the move is learned at. If the result is below 0, the move cannot be learned by leveling up. + public int GetLevelLearnMove(int move) + { + return (Learn ??= GetDictionary()).TryGetValue(move, out var level) ? level : -1; + } + + /// Returns the level that a Pokémon can learn the specified move. + /// Move ID + /// Minimum level to start looking at. + /// Level the move is learned at. If the result is below 0, the move cannot be learned by leveling up. + public int GetLevelLearnMove(int move, int min) + { + for (int i = 0; i < Moves.Length; i++) + { + if (move != Moves[i]) + continue; + + var lv = Levels[i]; + if (lv >= min) + return lv; + } + return -1; + } + + public ReadOnlySpan GetBaseEggMoves(int level) + { + // Count moves <= level + var count = 0; + foreach (var x in Levels) + { + if (x > level) + break; + count++; + } + + // Return a slice containing the moves <= level. + if (count == 0) + return ReadOnlySpan.Empty; + + int start = 0; + if (count > 4) + { + start = count - 4; + count = 4; + } + return Moves.AsSpan(start, count); + } } diff --git a/PKHeX.Core/Legality/Learnset/LearnsetReader.cs b/PKHeX.Core/Legality/Learnset/LearnsetReader.cs index fd0d19e64..e40e99b0d 100644 --- a/PKHeX.Core/Legality/Learnset/LearnsetReader.cs +++ b/PKHeX.Core/Legality/Learnset/LearnsetReader.cs @@ -1,85 +1,84 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Unpacks data from legality binary inputs. +/// +public static class LearnsetReader { + private static readonly Learnset EMPTY = new(Array.Empty(), Array.Empty()); + /// - /// Unpacks data from legality binary inputs. + /// Loads a learnset using the 8-bit-per-move storage structure used by Generation 1 & 2 games. /// - public static class LearnsetReader + /// Raw ROM data containing the contiguous moves + /// Highest species ID for the input game. + public static Learnset[] GetArray(ReadOnlySpan input, int maxSpecies) { - private static readonly Learnset EMPTY = new(Array.Empty(), Array.Empty()); + int offset = 0; + var result = new Learnset[maxSpecies + 1]; + for (int i = 0; i < result.Length; i++) + result[i] = ReadLearnset8(input, ref offset); + return result; + } - /// - /// Loads a learnset using the 8-bit-per-move storage structure used by Generation 1 & 2 games. - /// - /// Raw ROM data containing the contiguous moves - /// Highest species ID for the input game. - public static Learnset[] GetArray(ReadOnlySpan input, int maxSpecies) + /// + /// Loads a learnset by reading 16-bit move,level pairs. + /// + /// Entry data + public static Learnset[] GetArray(BinLinkerAccessor entries) + { + var result = new Learnset[entries.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = ReadLearnset16(entries[i]); + return result; + } + + /// + /// Reads a Level up move pool definition from a contiguous chunk of GB era ROM data. + /// + /// Moves and Levels are 8-bit + private static Learnset ReadLearnset8(ReadOnlySpan data, ref int offset) + { + int end = offset; // scan for count + if (data[end] == 0) { - int offset = 0; - var result = new Learnset[maxSpecies + 1]; - for (int i = 0; i < result.Length; i++) - result[i] = ReadLearnset8(input, ref offset); - return result; - } - - /// - /// Loads a learnset by reading 16-bit move,level pairs. - /// - /// Entry data - public static Learnset[] GetArray(BinLinkerAccessor entries) - { - var result = new Learnset[entries.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = ReadLearnset16(entries[i]); - return result; - } - - /// - /// Reads a Level up move pool definition from a contiguous chunk of GB era ROM data. - /// - /// Moves and Levels are 8-bit - private static Learnset ReadLearnset8(ReadOnlySpan data, ref int offset) - { - int end = offset; // scan for count - if (data[end] == 0) - { - ++offset; - return EMPTY; - } - do { end += 2; } while (data[end] != 0); - - var count = (end - offset) / 2; - var moves = new int[count]; - var levels = new int[count]; - for (int i = 0; i < moves.Length; i++) - { - levels[i] = data[offset++]; - moves[i] = data[offset++]; - } ++offset; - return new Learnset(moves, levels); + return EMPTY; } + do { end += 2; } while (data[end] != 0); - /// - /// Reads a Level up move pool definition from a single move pool definition. - /// - /// Count of moves, followed by Moves and Levels which are 16-bit - private static Learnset ReadLearnset16(ReadOnlySpan data) + var count = (end - offset) / 2; + var moves = new int[count]; + var levels = new int[count]; + for (int i = 0; i < moves.Length; i++) { - if (data.Length == 0) - return EMPTY; - var count = (data.Length / 4) - 1; - var moves = new int[count]; - var levels = new int[count]; - for (int i = 0; i < count; i++) - { - var move = data.Slice(i * 4, 4); - levels[i] = ReadInt16LittleEndian(move[2..]); - moves[i] = ReadInt16LittleEndian(move); - } - return new Learnset(moves, levels); + levels[i] = data[offset++]; + moves[i] = data[offset++]; } + ++offset; + return new Learnset(moves, levels); + } + + /// + /// Reads a Level up move pool definition from a single move pool definition. + /// + /// Count of moves, followed by Moves and Levels which are 16-bit + private static Learnset ReadLearnset16(ReadOnlySpan data) + { + if (data.Length == 0) + return EMPTY; + var count = (data.Length / 4) - 1; + var moves = new int[count]; + var levels = new int[count]; + for (int i = 0; i < count; i++) + { + var move = data.Slice(i * 4, 4); + levels[i] = ReadInt16LittleEndian(move[2..]); + moves[i] = ReadInt16LittleEndian(move); + } + return new Learnset(moves, levels); } } diff --git a/PKHeX.Core/Legality/LegalityAnalysis.cs b/PKHeX.Core/Legality/LegalityAnalysis.cs index 64a2c5b2f..ab507a594 100644 --- a/PKHeX.Core/Legality/LegalityAnalysis.cs +++ b/PKHeX.Core/Legality/LegalityAnalysis.cs @@ -5,313 +5,312 @@ using static PKHeX.Core.LegalityAnalyzers; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Legality Check object containing the data and overview values from the parse. +/// +public sealed class LegalityAnalysis { + /// The entity we are checking. + internal readonly PKM Entity; + + /// The entity's , which may have been sourced from the Save File it resides on. + /// We store this rather than re-fetching, as some games that use the same format have different values. + internal readonly PersonalInfo PersonalInfo; + + private readonly List Parse = new(8); + /// - /// Legality Check object containing the data and overview values from the parse. + /// Parse result list allowing view of the legality parse. /// - public sealed class LegalityAnalysis + public IReadOnlyList Results => Parse; + + /// + /// Only use this when trying to mutate the legality. Not for use when checking legality. + /// + public void ResetParse() => Parse.Clear(); + + /// + /// Matched encounter data for the . + /// + public IEncounterable EncounterMatch => Info.EncounterMatch; + + /// + /// Original encounter data for the . + /// + /// + /// Generation 1/2 that are transferred forward to Generation 7 are restricted to new encounter details. + /// By retaining their original match, more information can be provided by the parse. + /// + public IEncounterable EncounterOriginal => Info.EncounterOriginal; + + public readonly SlotOrigin SlotOrigin; + + /// + /// Indicates if all checks ran to completion. + /// + /// This value is false if any checks encountered an error. + public readonly bool Parsed; + + /// + /// Indicates if all checks returned a result. + /// + public readonly bool Valid; + + /// + /// Contains various data reused for multiple checks. + /// + public readonly LegalInfo Info; + + /// + /// Checks the input data for legality. This is the best method for checking with context, as some games do not have all Alternate Form data available. + /// + /// Input data to check + /// specific personal data + /// Details about where the originated from. + public LegalityAnalysis(PKM pk, PersonalTable table, SlotOrigin source = SlotOrigin.Party) : this(pk, table.GetFormEntry(pk.Species, pk.Form), source) { } + + /// + /// Checks the input data for legality. + /// + /// Input data to check + /// Details about where the originated from. + public LegalityAnalysis(PKM pk, SlotOrigin source = SlotOrigin.Party) : this(pk, pk.PersonalInfo, source) { } + + /// + /// Checks the input data for legality. + /// + /// Input data to check + /// Personal info to parse with + /// Details about where the originated from. + public LegalityAnalysis(PKM pk, PersonalInfo pi, SlotOrigin source = SlotOrigin.Party) { - /// The entity we are checking. - internal readonly PKM pkm; + Entity = pk; + PersonalInfo = pi; + SlotOrigin = source; - /// The entity's , which may have been sourced from the Save File it resides on. - /// We store this rather than re-fetching, as some games that use the same format have different values. - internal readonly PersonalInfo PersonalInfo; - - private readonly List Parse = new(8); - - /// - /// Parse result list allowing view of the legality parse. - /// - public IReadOnlyList Results => Parse; - - /// - /// Only use this when trying to mutate the legality. Not for use when checking legality. - /// - public void ResetParse() => Parse.Clear(); - - /// - /// Matched encounter data for the . - /// - public IEncounterable EncounterMatch => Info.EncounterMatch; - - /// - /// Original encounter data for the . - /// - /// - /// Generation 1/2 that are transferred forward to Generation 7 are restricted to new encounter details. - /// By retaining their original match, more information can be provided by the parse. - /// - public IEncounterable EncounterOriginal => Info.EncounterOriginal; - - public readonly SlotOrigin SlotOrigin; - - /// - /// Indicates if all checks ran to completion. - /// - /// This value is false if any checks encountered an error. - public readonly bool Parsed; - - /// - /// Indicates if all checks returned a result. - /// - public readonly bool Valid; - - /// - /// Contains various data reused for multiple checks. - /// - public readonly LegalInfo Info; - - /// - /// Checks the input data for legality. This is the best method for checking with context, as some games do not have all Alternate Form data available. - /// - /// Input data to check - /// specific personal data - /// Details about where the originated from. - public LegalityAnalysis(PKM pk, PersonalTable table, SlotOrigin source = SlotOrigin.Party) : this(pk, table.GetFormEntry(pk.Species, pk.Form), source) { } - - /// - /// Checks the input data for legality. - /// - /// Input data to check - /// Details about where the originated from. - public LegalityAnalysis(PKM pk, SlotOrigin source = SlotOrigin.Party) : this(pk, pk.PersonalInfo, source) { } - - /// - /// Checks the input data for legality. - /// - /// Input data to check - /// Personal info to parse with - /// Details about where the originated from. - public LegalityAnalysis(PKM pk, PersonalInfo pi, SlotOrigin source = SlotOrigin.Party) - { - pkm = pk; - PersonalInfo = pi; - SlotOrigin = source; - - Info = new LegalInfo(pkm, Parse); + Info = new LegalInfo(pk, Parse); #if SUPPRESS - try + try #endif - { - EncounterFinder.FindVerifiedEncounter(pkm, Info); - if (!pkm.IsOriginValid) - AddLine(Severity.Invalid, LEncConditionBadSpecies, CheckIdentifier.GameOrigin); - GetParseMethod()(); + { + EncounterFinder.FindVerifiedEncounter(pk, Info); + if (!pk.IsOriginValid) + AddLine(Severity.Invalid, LEncConditionBadSpecies, CheckIdentifier.GameOrigin); + GetParseMethod()(); - Valid = Parse.TrueForAll(chk => chk.Valid) + Valid = Parse.TrueForAll(chk => chk.Valid) && Array.TrueForAll(Info.Moves, m => m.Valid) && Array.TrueForAll(Info.Relearn, m => m.Valid); - if (!Valid && IsPotentiallyMysteryGift(Info, pkm)) - AddLine(Severity.Indeterminate, LFatefulGiftMissing, CheckIdentifier.Fateful); - Parsed = true; - } + if (!Valid && IsPotentiallyMysteryGift(Info, pk)) + AddLine(Severity.Indeterminate, LFatefulGiftMissing, CheckIdentifier.Fateful); + Parsed = true; + } #if SUPPRESS - // We want to swallow any error from malformed input data from the user. The Valid state is all that we really need. - catch (Exception e) - { - System.Diagnostics.Debug.WriteLine(e.Message); - Valid = false; + // We want to swallow any error from malformed input data from the user. The Valid state is all that we really need. + catch (Exception e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + Valid = false; - // Moves and Relearn arrays can potentially be empty on error. - foreach (var p in Info.Moves) - { - if (!p.IsParsed) - p.Set(MoveSource.Unknown, pkm.Format, Severity.Indeterminate, L_AError, CheckIdentifier.CurrentMove); - } - foreach (var p in Info.Relearn) - { - if (!p.IsParsed) - p.Set(MoveSource.Unknown, 0, Severity.Indeterminate, L_AError, CheckIdentifier.RelearnMove); - } - AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc); + // Moves and Relearn arrays can potentially be empty on error. + foreach (var p in Info.Moves) + { + if (!p.IsParsed) + p.Set(MoveSource.Unknown, pk.Format, Severity.Indeterminate, L_AError, CheckIdentifier.CurrentMove); } -#endif - } - - private static bool IsPotentiallyMysteryGift(LegalInfo info, PKM pk) - { - if (info.EncounterOriginal is not EncounterInvalid enc) - return false; - if (enc.Generation <= 3) - return true; - if (!pk.FatefulEncounter) - return false; - if (enc.Generation < 6) - return true; - if (Array.TrueForAll(info.Relearn, chk => !chk.Valid)) - return true; - return false; - } - - private Action GetParseMethod() - { - if (pkm.Format <= 2) // prior to storing GameVersion - return ParsePK1; - - int gen = pkm.Generation; - if (gen <= 0) - gen = pkm.Format; - return gen switch + foreach (var p in Info.Relearn) { - 3 => ParsePK3, - 4 => ParsePK4, - 5 => ParsePK5, - 6 => ParsePK6, - - 1 => ParsePK7, - 2 => ParsePK7, - 7 => ParsePK7, - - 8 => ParsePK8, - - _ => throw new Exception(), - }; + if (!p.IsParsed) + p.Set(MoveSource.Unknown, 0, Severity.Indeterminate, L_AError, CheckIdentifier.RelearnMove); + } + AddLine(Severity.Invalid, L_AError, CheckIdentifier.Misc); } +#endif + } - private void ParsePK1() + private static bool IsPotentiallyMysteryGift(LegalInfo info, PKM pk) + { + if (info.EncounterOriginal is not EncounterInvalid enc) + return false; + if (enc.Generation <= 3) + return true; + if (!pk.FatefulEncounter) + return false; + if (enc.Generation < 6) + return true; + if (Array.TrueForAll(info.Relearn, chk => !chk.Valid)) + return true; + return false; + } + + private Action GetParseMethod() + { + if (Entity.Format <= 2) // prior to storing GameVersion + return ParsePK1; + + int gen = Entity.Generation; + if (gen <= 0) + gen = Entity.Format; + return gen switch { - Nickname.Verify(this); - Level.Verify(this); - Level.VerifyG1(this); - Trainer.VerifyOTG1(this); - MiscValues.VerifyMiscG1(this); - if (pkm.Format == 2) - Item.Verify(this); - } + 3 => ParsePK3, + 4 => ParsePK4, + 5 => ParsePK5, + 6 => ParsePK6, - private void ParsePK3() - { - UpdateChecks(); - if (pkm.Format > 3) - Transfer.VerifyTransferLegalityG3(this); + 1 => ParsePK7, + 2 => ParsePK7, + 7 => ParsePK7, - if (pkm.Version == (int)GameVersion.CXD) - CXD.Verify(this); + 8 => ParsePK8, - if (Info.EncounterMatch is WC3 {NotDistributed: true}) - AddLine(Severity.Invalid, LEncUnreleased, CheckIdentifier.Encounter); + _ => throw new ArgumentOutOfRangeException(nameof(gen)), + }; + } - if (pkm.Format >= 8) - Transfer.VerifyTransferLegalityG8(this); - } - - private void ParsePK4() - { - UpdateChecks(); - if (pkm.Format > 4) - Transfer.VerifyTransferLegalityG4(this); - if (pkm.Format >= 8) - Transfer.VerifyTransferLegalityG8(this); - } - - private void ParsePK5() - { - UpdateChecks(); - NHarmonia.Verify(this); - if (pkm.Format >= 8) - Transfer.VerifyTransferLegalityG8(this); - } - - private void ParsePK6() - { - UpdateChecks(); - if (pkm.Format >= 8) - Transfer.VerifyTransferLegalityG8(this); - } - - private void ParsePK7() - { - if (pkm.VC) - UpdateVCTransferInfo(); - UpdateChecks(); - if (pkm.Format >= 8) - Transfer.VerifyTransferLegalityG8(this); - } - - private void ParsePK8() - { - UpdateChecks(); - Transfer.VerifyTransferLegalityG8(this); - } - - /// - /// Adds a new Check parse value. - /// - /// Check severity - /// Check comment - /// Check type - internal void AddLine(Severity s, string c, CheckIdentifier i) => AddLine(new CheckResult(s, c, i)); - - /// - /// Adds a new Check parse value. - /// - /// Check result to add. - internal void AddLine(CheckResult chk) => Parse.Add(chk); - - private void UpdateVCTransferInfo() - { - var enc = (Info.EncounterOriginalGB = EncounterMatch); - if (enc is EncounterInvalid) - return; - var vc = EncounterStaticGenerator.GetVCStaticTransferEncounter(pkm, enc, Info.EvoChainsAllGens.Gen7); - Info.EncounterMatch = vc; - - foreach (var z in Transfer.VerifyVCEncounter(pkm, enc, vc, Info.Moves)) - AddLine(z); - - Transfer.VerifyTransferLegalityG12(this); - } - - private void UpdateChecks() - { - PIDEC.Verify(this); - Nickname.Verify(this); - LanguageIndex.Verify(this); - Trainer.Verify(this); - TrainerID.Verify(this); - IVs.Verify(this); - EVs.Verify(this); - Level.Verify(this); - Ribbon.Verify(this); - AbilityValues.Verify(this); - BallIndex.Verify(this); - FormValues.Verify(this); - MiscValues.Verify(this); - GenderValues.Verify(this); + private void ParsePK1() + { + Nickname.Verify(this); + Level.Verify(this); + Level.VerifyG1(this); + Trainer.VerifyOTG1(this); + MiscValues.VerifyMiscG1(this); + if (Entity.Format == 2) Item.Verify(this); - Contest.Verify(this); - Marking.Verify(this); + } - var format = pkm.Format; - if (format is 4 or 5 or 6) // Gen 6->7 transfer removes this property. - Gen4GroundTile.Verify(this); + private void ParsePK3() + { + UpdateChecks(); + if (Entity.Format > 3) + Transfer.VerifyTransferLegalityG3(this); - if (format < 6) - return; + if (Entity.Version == (int)GameVersion.CXD) + CXD.Verify(this); - History.Verify(this); - if (format < 8) // Gen 7->8 transfer removes these properties. - ConsoleRegion.Verify(this); + if (Info.EncounterMatch is WC3 {NotDistributed: true}) + AddLine(Severity.Invalid, LEncUnreleased, CheckIdentifier.Encounter); - if (pkm is ITrainerMemories) - Memory.Verify(this); - if (pkm is ISuperTrain) - Medal.Verify(this); + if (Entity.Format >= 8) + Transfer.VerifyTransferLegalityG8(this); + } - if (format < 7) - return; + private void ParsePK4() + { + UpdateChecks(); + if (Entity.Format > 4) + Transfer.VerifyTransferLegalityG4(this); + if (Entity.Format >= 8) + Transfer.VerifyTransferLegalityG8(this); + } - HyperTraining.Verify(this); - MiscValues.VerifyVersionEvolution(this); + private void ParsePK5() + { + UpdateChecks(); + NHarmonia.Verify(this); + if (Entity.Format >= 8) + Transfer.VerifyTransferLegalityG8(this); + } - if (format < 8) - return; + private void ParsePK6() + { + UpdateChecks(); + if (Entity.Format >= 8) + Transfer.VerifyTransferLegalityG8(this); + } - Mark.Verify(this); - Arceus.Verify(this); - } + private void ParsePK7() + { + if (Entity.VC) + UpdateVCTransferInfo(); + UpdateChecks(); + if (Entity.Format >= 8) + Transfer.VerifyTransferLegalityG8(this); + } + + private void ParsePK8() + { + UpdateChecks(); + Transfer.VerifyTransferLegalityG8(this); + } + + /// + /// Adds a new Check parse value. + /// + /// Check severity + /// Check comment + /// Check type + internal void AddLine(Severity s, string c, CheckIdentifier i) => AddLine(new CheckResult(s, c, i)); + + /// + /// Adds a new Check parse value. + /// + /// Check result to add. + internal void AddLine(CheckResult chk) => Parse.Add(chk); + + private void UpdateVCTransferInfo() + { + var enc = (Info.EncounterOriginalGB = EncounterMatch); + if (enc is EncounterInvalid) + return; + var vc = EncounterStaticGenerator.GetVCStaticTransferEncounter(Entity, enc, Info.EvoChainsAllGens.Gen7); + Info.EncounterMatch = vc; + + foreach (var z in Transfer.VerifyVCEncounter(Entity, enc, vc, Info.Moves)) + AddLine(z); + + Transfer.VerifyTransferLegalityG12(this); + } + + private void UpdateChecks() + { + PIDEC.Verify(this); + Nickname.Verify(this); + LanguageIndex.Verify(this); + Trainer.Verify(this); + TrainerID.Verify(this); + IVs.Verify(this); + EVs.Verify(this); + Level.Verify(this); + Ribbon.Verify(this); + AbilityValues.Verify(this); + BallIndex.Verify(this); + FormValues.Verify(this); + MiscValues.Verify(this); + GenderValues.Verify(this); + Item.Verify(this); + Contest.Verify(this); + Marking.Verify(this); + + var format = Entity.Format; + if (format is 4 or 5 or 6) // Gen 6->7 transfer removes this property. + Gen4GroundTile.Verify(this); + + if (format < 6) + return; + + History.Verify(this); + if (format < 8) // Gen 7->8 transfer removes these properties. + ConsoleRegion.Verify(this); + + if (Entity is ITrainerMemories) + Memory.Verify(this); + if (Entity is ISuperTrain) + Medal.Verify(this); + + if (format < 7) + return; + + HyperTraining.Verify(this); + MiscValues.VerifyVersionEvolution(this); + + if (format < 8) + return; + + Mark.Verify(this); + Arceus.Verify(this); } } diff --git a/PKHeX.Core/Legality/LegalityAnalyzers.cs b/PKHeX.Core/Legality/LegalityAnalyzers.cs index 7ea8a9153..e579ed751 100644 --- a/PKHeX.Core/Legality/LegalityAnalyzers.cs +++ b/PKHeX.Core/Legality/LegalityAnalyzers.cs @@ -1,38 +1,37 @@ -namespace PKHeX.Core -{ - /// - /// Collection of analyzers that are used for parsing secondary details. - /// - internal static class LegalityAnalyzers - { - public static readonly LanguageVerifier LanguageIndex = new(); - public static readonly NicknameVerifier Nickname = new(); - public static readonly EffortValueVerifier EVs = new(); - public static readonly IndividualValueVerifier IVs = new(); - public static readonly BallVerifier BallIndex = new(); - public static readonly FormVerifier FormValues = new(); - public static readonly ConsoleRegionVerifier ConsoleRegion = new(); - public static readonly AbilityVerifier AbilityValues = new(); - public static readonly MedalVerifier Medal = new(); - public static readonly RibbonVerifier Ribbon = new(); - public static readonly ItemVerifier Item = new(); - public static readonly GroundTileVerifier Gen4GroundTile = new(); - public static readonly HyperTrainingVerifier HyperTraining = new(); - public static readonly GenderVerifier GenderValues = new(); - public static readonly PIDVerifier PIDEC = new(); - public static readonly NHarmoniaVerifier NHarmonia = new(); - public static readonly CXDVerifier CXD = new(); - public static readonly MemoryVerifier Memory = new(); - public static readonly HistoryVerifier History = new(); - public static readonly ContestStatVerifier Contest = new(); - public static readonly MarkingVerifier Marking = new(); +namespace PKHeX.Core; - public static readonly TrainerNameVerifier Trainer = new(); - public static readonly TrainerIDVerifier TrainerID = new(); - public static readonly LevelVerifier Level = new(); - public static readonly MiscVerifier MiscValues = new(); - public static readonly TransferVerifier Transfer = new(); - public static readonly MarkVerifier Mark = new(); - public static readonly LegendsArceusVerifier Arceus = new(); - } +/// +/// Collection of analyzers that are used for parsing secondary details. +/// +internal static class LegalityAnalyzers +{ + public static readonly LanguageVerifier LanguageIndex = new(); + public static readonly NicknameVerifier Nickname = new(); + public static readonly EffortValueVerifier EVs = new(); + public static readonly IndividualValueVerifier IVs = new(); + public static readonly BallVerifier BallIndex = new(); + public static readonly FormVerifier FormValues = new(); + public static readonly ConsoleRegionVerifier ConsoleRegion = new(); + public static readonly AbilityVerifier AbilityValues = new(); + public static readonly MedalVerifier Medal = new(); + public static readonly RibbonVerifier Ribbon = new(); + public static readonly ItemVerifier Item = new(); + public static readonly GroundTileVerifier Gen4GroundTile = new(); + public static readonly HyperTrainingVerifier HyperTraining = new(); + public static readonly GenderVerifier GenderValues = new(); + public static readonly PIDVerifier PIDEC = new(); + public static readonly NHarmoniaVerifier NHarmonia = new(); + public static readonly CXDVerifier CXD = new(); + public static readonly MemoryVerifier Memory = new(); + public static readonly HistoryVerifier History = new(); + public static readonly ContestStatVerifier Contest = new(); + public static readonly MarkingVerifier Marking = new(); + + public static readonly TrainerNameVerifier Trainer = new(); + public static readonly TrainerIDVerifier TrainerID = new(); + public static readonly LevelVerifier Level = new(); + public static readonly MiscVerifier MiscValues = new(); + public static readonly TransferVerifier Transfer = new(); + public static readonly MarkVerifier Mark = new(); + public static readonly LegendsArceusVerifier Arceus = new(); } diff --git a/PKHeX.Core/Legality/MoveList.cs b/PKHeX.Core/Legality/MoveList.cs index 013ea32fd..c33a5f913 100644 --- a/PKHeX.Core/Legality/MoveList.cs +++ b/PKHeX.Core/Legality/MoveList.cs @@ -4,326 +4,325 @@ using static PKHeX.Core.Legal; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for obtaining a list of moves. +/// +internal static class MoveList { - /// - /// Logic for obtaining a list of moves. - /// - internal static class MoveList + internal static IEnumerable GetValidRelearn(PKM pk, int species, int form, bool inheritlvlmoves, GameVersion version = Any) { - internal static IEnumerable GetValidRelearn(PKM pkm, int species, int form, bool inheritlvlmoves, GameVersion version = Any) - { - int generation = pkm.Generation; - if (generation < 6) - return Array.Empty(); - - var r = new List(); - r.AddRange(MoveEgg.GetRelearnLVLMoves(pkm, species, form, 1, version)); - - if (pkm.Format == 6 && pkm.Species != (int)Species.Meowstic) - form = 0; - - r.AddRange(MoveEgg.GetEggMoves(pkm.PersonalInfo, species, form, version, Math.Max(2, generation))); - if (inheritlvlmoves) - r.AddRange(MoveEgg.GetRelearnLVLMoves(pkm, species, form, 100, version)); - return r.Distinct(); - } - - internal static int[] GetShedinjaEvolveMoves(PKM pkm, int generation, int lvl) - { - if (pkm.Species != (int)Species.Shedinja || lvl < 20) - return Array.Empty(); - - // If Nincada evolves into Ninjask and learns a move after evolution from Ninjask's LevelUp data, Shedinja would appear with that move. - // Only one move above level 20 is allowed; check the count of Ninjask moves elsewhere. - return generation switch - { - 3 when pkm.InhabitedGeneration(3) => LevelUpE[(int)Species.Ninjask].GetMoves(lvl, 20), // Same LevelUp data in all Gen3 games - 4 when pkm.InhabitedGeneration(4) => LevelUpPt[(int)Species.Ninjask].GetMoves(lvl, 20), // Same LevelUp data in all Gen4 games - _ => Array.Empty(), - }; - } - - internal static int GetShedinjaMoveLevel(int species, int move, int generation) - { - var src = generation == 4 ? LevelUpPt : LevelUpE; - var moves = src[species]; - return moves.GetLevelLearnMove(move); - } - - internal static int[] GetBaseEggMoves(PKM pkm, int species, int form, GameVersion gameSource, int lvl) - { - if (gameSource == Any) - gameSource = (GameVersion)pkm.Version; - - switch (gameSource) - { - case GSC or GS: - // If checking back-transfer specimen (GSC->RBY), remove moves that must be deleted prior to transfer - static int[] getRBYCompatibleMoves(int format, int[] moves) => format == 1 ? Array.FindAll(moves, m => m <= MaxMoveID_1) : moves; - if (pkm.InhabitedGeneration(2)) - return getRBYCompatibleMoves(pkm.Format, LevelUpGS[species].GetMoves(lvl)); - break; - case C: - if (pkm.InhabitedGeneration(2)) - return getRBYCompatibleMoves(pkm.Format, LevelUpC[species].GetMoves(lvl)); - break; - - case R or S or RS: - if (pkm.InhabitedGeneration(3)) - return LevelUpRS[species].GetMoves(lvl); - break; - case E: - if (pkm.InhabitedGeneration(3)) - return LevelUpE[species].GetMoves(lvl); - break; - case FR or LG or FRLG: - // The only difference in FR/LG is Deoxys, which doesn't breed. - if (pkm.InhabitedGeneration(3)) - return LevelUpFR[species].GetMoves(lvl); - break; - - case D or P or DP: - if (pkm.InhabitedGeneration(4)) - return LevelUpDP[species].GetMoves(lvl); - break; - case Pt: - if (pkm.InhabitedGeneration(4)) - return LevelUpPt[species].GetMoves(lvl); - break; - case HG or SS or HGSS: - if (pkm.InhabitedGeneration(4)) - return LevelUpHGSS[species].GetMoves(lvl); - break; - - case B or W or BW: - if (pkm.InhabitedGeneration(5)) - return LevelUpBW[species].GetMoves(lvl); - break; - - case B2 or W2 or B2W2: - if (pkm.InhabitedGeneration(5)) - return LevelUpB2W2[species].GetMoves(lvl); - break; - - case X or Y or XY: - if (pkm.InhabitedGeneration(6)) - return LevelUpXY[species].GetMoves(lvl); - break; - - case AS or OR or ORAS: - if (pkm.InhabitedGeneration(6)) - return LevelUpAO[species].GetMoves(lvl); - break; - - case SN or MN or SM: - if (species > MaxSpeciesID_7) - break; - if (pkm.InhabitedGeneration(7)) - { - int index = PersonalTable.SM.GetFormIndex(species, form); - return LevelUpSM[index].GetMoves(lvl); - } - break; - - case US or UM or USUM: - if (pkm.InhabitedGeneration(7)) - { - int index = PersonalTable.USUM.GetFormIndex(species, form); - return LevelUpUSUM[index].GetMoves(lvl); - } - break; - - case SW or SH or SWSH: - if (pkm.InhabitedGeneration(8)) - { - int index = PersonalTable.SWSH.GetFormIndex(species, form); - return LevelUpSWSH[index].GetMoves(lvl); - } - break; - - case PLA: - if (pkm.InhabitedGeneration(8)) - { - int index = PersonalTable.LA.GetFormIndex(species, form); - return LevelUpLA[index].GetMoves(lvl); - } - break; - - case BD or SP or BDSP: - if (pkm.InhabitedGeneration(8)) - { - int index = PersonalTable.BDSP.GetFormIndex(species, form); - return LevelUpBDSP[index].GetMoves(lvl); - } - break; - } + int generation = pk.Generation; + if (generation < 6) return Array.Empty(); - } - internal static IReadOnlyList[] GetValidMovesAllGens(PKM pkm, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true) + var r = new List(); + r.AddRange(MoveEgg.GetRelearnLVLMoves(pk, species, form, 1, version)); + + if (pk.Format == 6 && pk.Species != (int)Species.Meowstic) + form = 0; + + r.AddRange(MoveEgg.GetEggMoves(pk.PersonalInfo, species, form, version, Math.Max(2, generation))); + if (inheritlvlmoves) + r.AddRange(MoveEgg.GetRelearnLVLMoves(pk, species, form, 100, version)); + return r.Distinct(); + } + + internal static int[] GetShedinjaEvolveMoves(PKM pk, int generation, int lvl) + { + if (pk.Species != (int)Species.Shedinja || lvl < 20) + return Array.Empty(); + + // If Nincada evolves into Ninjask and learns a move after evolution from Ninjask's LevelUp data, Shedinja would appear with that move. + // Only one move above level 20 is allowed; check the count of Ninjask moves elsewhere. + return generation switch { - var result = new IReadOnlyList[evoChains.Length]; - for (int i = 0; i < result.Length; i++) - result[i] = Array.Empty(); + 3 when pk.InhabitedGeneration(3) => LevelUpE[(int)Species.Ninjask].GetMoves(lvl, 20), // Same LevelUp data in all Gen3 games + 4 when pk.InhabitedGeneration(4) => LevelUpPt[(int)Species.Ninjask].GetMoves(lvl, 20), // Same LevelUp data in all Gen4 games + _ => Array.Empty(), + }; + } - var min = pkm is IBattleVersion b ? Math.Max(0, b.GetMinGeneration()) : 1; - for (int i = min; i < evoChains.Length; i++) - { - var evos = evoChains[i]; - if (evos.Length == 0) - continue; + internal static int GetShedinjaMoveLevel(int species, int move, int generation) + { + var src = generation == 4 ? LevelUpPt : LevelUpE; + var moves = src[species]; + return moves.GetLevelLearnMove(move); + } - result[i] = GetValidMoves(pkm, evos, i, types, RemoveTransferHM).ToList(); - } - return result; - } + internal static int[] GetBaseEggMoves(PKM pk, int species, int form, GameVersion gameSource, int lvl) + { + if (gameSource == Any) + gameSource = (GameVersion)pk.Version; - internal static IEnumerable GetValidMoves(PKM pkm, EvoCriteria[] evoChain, int generation, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true) + switch (gameSource) { - var (_, version) = pkm.IsMovesetRestricted(); - return GetValidMoves(pkm, version, evoChain, generation, types: types, RemoveTransferHM: RemoveTransferHM); - } + case GSC or GS: + // If checking back-transfer specimen (GSC->RBY), remove moves that must be deleted prior to transfer + static int[] getRBYCompatibleMoves(int format, int[] moves) => format == 1 ? Array.FindAll(moves, m => m <= MaxMoveID_1) : moves; + if (pk.InhabitedGeneration(2)) + return getRBYCompatibleMoves(pk.Format, LevelUpGS[species].GetMoves(lvl)); + break; + case C: + if (pk.InhabitedGeneration(2)) + return getRBYCompatibleMoves(pk.Format, LevelUpC[species].GetMoves(lvl)); + break; - internal static IEnumerable GetValidRelearn(PKM pkm, int species, int form, GameVersion version = Any) - { - return GetValidRelearn(pkm, species, form, Breeding.GetCanInheritMoves(species), version); - } + case R or S or RS: + if (pk.InhabitedGeneration(3)) + return LevelUpRS[species].GetMoves(lvl); + break; + case E: + if (pk.InhabitedGeneration(3)) + return LevelUpE[species].GetMoves(lvl); + break; + case FR or LG or FRLG: + // The only difference in FR/LG is Deoxys, which doesn't breed. + if (pk.InhabitedGeneration(3)) + return LevelUpFR[species].GetMoves(lvl); + break; - /// - /// ONLY CALL FOR GEN2 EGGS - /// - internal static IEnumerable GetExclusivePreEvolutionMoves(PKM pkm, int Species, EvoCriteria[] evoChain, int generation, GameVersion Version) - { - var preevomoves = new List(); - var evomoves = new List(); - var index = Array.FindIndex(evoChain, z => z.Species == Species); - for (int i = 0; i < evoChain.Length; i++) - { - int minLvLG2; - var evo = evoChain[i]; - if (ParseSettings.AllowGen2MoveReminder(pkm)) - minLvLG2 = 0; - else if (i == evoChain.Length - 1) // minimum level, otherwise next learnable level - minLvLG2 = 5; - else if (evo.RequiresLvlUp) - minLvLG2 = evo.LevelMax + 1; - else - minLvLG2 = evo.LevelMax; + case D or P or DP: + if (pk.InhabitedGeneration(4)) + return LevelUpDP[species].GetMoves(lvl); + break; + case Pt: + if (pk.InhabitedGeneration(4)) + return LevelUpPt[species].GetMoves(lvl); + break; + case HG or SS or HGSS: + if (pk.InhabitedGeneration(4)) + return LevelUpHGSS[species].GetMoves(lvl); + break; - var moves = GetMoves(pkm, evo.Species, evo.Form, evo.LevelMax, 0, minLvLG2, Version: Version, types: MoveSourceType.ExternalSources, RemoveTransferHM: false, generation: generation); - var list = i >= index ? preevomoves : evomoves; - list.AddRange(moves); - } - return preevomoves.Except(evomoves).Distinct(); - } + case B or W or BW: + if (pk.InhabitedGeneration(5)) + return LevelUpBW[species].GetMoves(lvl); + break; - internal static IEnumerable GetValidMoves(PKM pkm, GameVersion version, EvoCriteria[] chain, int generation, MoveSourceType types = MoveSourceType.Reminder, bool RemoveTransferHM = true) - { - var r = new List { 0 }; - int species = pkm.Species; + case B2 or W2 or B2W2: + if (pk.InhabitedGeneration(5)) + return LevelUpB2W2[species].GetMoves(lvl); + break; - if (FormChangeMovesRetain.Contains(species)) // Deoxys & Shaymin & Giratina (others don't have extra but whatever) - return GetValidMovesAllForms(pkm, chain, version, generation, types, RemoveTransferHM, species, r); + case X or Y or XY: + if (pk.InhabitedGeneration(6)) + return LevelUpXY[species].GetMoves(lvl); + break; - // Generation 1 & 2 do not always have move relearning capability, so the bottom bound for learnable indexes needs to be determined. - var minLvLG1 = 0; - var minLvLG2 = 0; - for (var i = 0; i < chain.Length; i++) - { - var evo = chain[i]; - bool encounteredEvo = i == chain.Length - 1; + case AS or OR or ORAS: + if (pk.InhabitedGeneration(6)) + return LevelUpAO[species].GetMoves(lvl); + break; - if (generation <= 2) + case SN or MN or SM: + if (species > MaxSpeciesID_7) + break; + if (pk.InhabitedGeneration(7)) { - if (encounteredEvo) // minimum level, otherwise next learnable level - minLvLG1 = evo.LevelMin + 1; - else // learns level up moves immediately after evolving - minLvLG1 = evo.LevelMin; - - if (!ParseSettings.AllowGen2MoveReminder(pkm)) - minLvLG2 = minLvLG1; + int index = PersonalTable.SM.GetFormIndex(species, form); + return LevelUpSM[index].GetMoves(lvl); } + break; - var maxLevel = evo.LevelMax; - if (!encounteredEvo) // evolution - ++maxLevel; // allow lvlmoves from the level it evolved to the next species - var moves = GetMoves(pkm, evo.Species, evo.Form, maxLevel, minLvLG1, minLvLG2, version, types, RemoveTransferHM, generation); - r.AddRange(moves); + case US or UM or USUM: + if (pk.InhabitedGeneration(7)) + { + int index = PersonalTable.USUM.GetFormIndex(species, form); + return LevelUpUSUM[index].GetMoves(lvl); + } + break; + + case SW or SH or SWSH: + if (pk.InhabitedGeneration(8)) + { + int index = PersonalTable.SWSH.GetFormIndex(species, form); + return LevelUpSWSH[index].GetMoves(lvl); + } + break; + + case PLA: + if (pk.InhabitedGeneration(8)) + { + int index = PersonalTable.LA.GetFormIndex(species, form); + return LevelUpLA[index].GetMoves(lvl); + } + break; + + case BD or SP or BDSP: + if (pk.InhabitedGeneration(8)) + { + int index = PersonalTable.BDSP.GetFormIndex(species, form); + return LevelUpBDSP[index].GetMoves(lvl); + } + break; + } + return Array.Empty(); + } + + internal static IReadOnlyList[] GetValidMovesAllGens(PKM pk, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true) + { + var result = new IReadOnlyList[evoChains.Length]; + for (int i = 0; i < result.Length; i++) + result[i] = Array.Empty(); + + var min = pk is IBattleVersion b ? Math.Max(0, b.GetMinGeneration()) : 1; + for (int i = min; i < evoChains.Length; i++) + { + var evos = evoChains[i]; + if (evos.Length == 0) + continue; + + result[i] = GetValidMoves(pk, evos, i, types, RemoveTransferHM).ToList(); + } + return result; + } + + internal static IEnumerable GetValidMoves(PKM pk, EvoCriteria[] evoChain, int generation, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true) + { + var (_, version) = pk.IsMovesetRestricted(); + return GetValidMoves(pk, version, evoChain, generation, types: types, RemoveTransferHM: RemoveTransferHM); + } + + internal static IEnumerable GetValidRelearn(PKM pk, int species, int form, GameVersion version = Any) + { + return GetValidRelearn(pk, species, form, Breeding.GetCanInheritMoves(species), version); + } + + /// + /// ONLY CALL FOR GEN2 EGGS + /// + internal static IEnumerable GetExclusivePreEvolutionMoves(PKM pk, int Species, EvoCriteria[] evoChain, int generation, GameVersion Version) + { + var preevomoves = new List(); + var evomoves = new List(); + var index = Array.FindIndex(evoChain, z => z.Species == Species); + for (int i = 0; i < evoChain.Length; i++) + { + int minLvLG2; + var evo = evoChain[i]; + if (ParseSettings.AllowGen2MoveReminder(pk)) + minLvLG2 = 0; + else if (i == evoChain.Length - 1) // minimum level, otherwise next learnable level + minLvLG2 = 5; + else if (evo.RequiresLvlUp) + minLvLG2 = evo.LevelMax + 1; + else + minLvLG2 = evo.LevelMax; + + var moves = GetMoves(pk, evo.Species, evo.Form, evo.LevelMax, 0, minLvLG2, Version: Version, types: MoveSourceType.ExternalSources, RemoveTransferHM: false, generation: generation); + var list = i >= index ? preevomoves : evomoves; + list.AddRange(moves); + } + return preevomoves.Except(evomoves).Distinct(); + } + + internal static IEnumerable GetValidMoves(PKM pk, GameVersion version, EvoCriteria[] chain, int generation, MoveSourceType types = MoveSourceType.Reminder, bool RemoveTransferHM = true) + { + var r = new List { 0 }; + int species = pk.Species; + + if (FormChangeMovesRetain.Contains(species)) // Deoxys & Shaymin & Giratina (others don't have extra but whatever) + return GetValidMovesAllForms(pk, chain, version, generation, types, RemoveTransferHM, species, r); + + // Generation 1 & 2 do not always have move relearning capability, so the bottom bound for learnable indexes needs to be determined. + var minLvLG1 = 0; + var minLvLG2 = 0; + for (var i = 0; i < chain.Length; i++) + { + var evo = chain[i]; + bool encounteredEvo = i == chain.Length - 1; + + if (generation <= 2) + { + if (encounteredEvo) // minimum level, otherwise next learnable level + minLvLG1 = evo.LevelMin + 1; + else // learns level up moves immediately after evolving + minLvLG1 = evo.LevelMin; + + if (!ParseSettings.AllowGen2MoveReminder(pk)) + minLvLG2 = minLvLG1; } - if (pkm.Format <= 3) - return r.Distinct(); - - if (types.HasFlagFast(MoveSourceType.LevelUp)) - MoveTutor.AddSpecialFormChangeMoves(r, pkm, generation, species); - if (types.HasFlagFast(MoveSourceType.SpecialTutor)) - MoveTutor.AddSpecialTutorMoves(r, pkm, generation, species); - if (types.HasFlagFast(MoveSourceType.RelearnMoves) && generation >= 6) - r.AddRange(pkm.RelearnMoves); - return r.Distinct(); + var maxLevel = evo.LevelMax; + if (!encounteredEvo) // evolution + ++maxLevel; // allow lvlmoves from the level it evolved to the next species + var moves = GetMoves(pk, evo.Species, evo.Form, maxLevel, minLvLG1, minLvLG2, version, types, RemoveTransferHM, generation); + r.AddRange(moves); } - internal static IEnumerable GetValidMovesAllForms(PKM pkm, EvoCriteria[] chain, GameVersion version, int generation, MoveSourceType types, bool RemoveTransferHM, int species, List r) - { - // These don't evolve, so don't bother iterating for all entries in the evolution chain (should always be count==1). - int formCount; - - // In gen 3 deoxys has different forms depending on the current game, in the PersonalInfo there is no alternate form info - if (pkm.Format == 3 && species == (int) Species.Deoxys) - formCount = 4; - else - formCount = pkm.PersonalInfo.FormCount; - - for (int form = 0; form < formCount; form++) - r.AddRange(GetMoves(pkm, species, form, chain[0].LevelMax, 0, 0, version, types, RemoveTransferHM, generation)); - if (types.HasFlagFast(MoveSourceType.RelearnMoves)) - r.AddRange(pkm.RelearnMoves); + if (pk.Format <= 3) return r.Distinct(); - } - private static IEnumerable GetMoves(PKM pkm, int species, int form, int maxLevel, int minlvlG1, int minlvlG2, GameVersion Version, MoveSourceType types, bool RemoveTransferHM, int generation) - { - var r = new List(); - if (types.HasFlagFast(MoveSourceType.LevelUp)) - r.AddRange(MoveLevelUp.GetMovesLevelUp(pkm, species, form, maxLevel, minlvlG1, minlvlG2, Version, types.HasFlagFast(MoveSourceType.Reminder), generation)); - if (types.HasFlagFast(MoveSourceType.Machine)) - r.AddRange(MoveTechnicalMachine.GetTMHM(pkm, species, form, generation, Version, RemoveTransferHM)); - if (types.HasFlagFast(MoveSourceType.TechnicalRecord)) - r.AddRange(MoveTechnicalMachine.GetRecords(pkm, species, form, generation)); - if (types.HasFlagFast(MoveSourceType.AllTutors)) - r.AddRange(MoveTutor.GetTutorMoves(pkm, species, form, types.HasFlagFast(MoveSourceType.SpecialTutor), generation)); - return r.Distinct(); - } + if (types.HasFlagFast(MoveSourceType.LevelUp)) + MoveTutor.AddSpecialFormChangeMoves(r, pk, generation, species); + if (types.HasFlagFast(MoveSourceType.SpecialTutor)) + MoveTutor.AddSpecialTutorMoves(r, pk, generation, species); + if (types.HasFlagFast(MoveSourceType.RelearnMoves) && generation >= 6) + r.AddRange(pk.RelearnMoves); + return r.Distinct(); } - [Flags] -#pragma warning disable RCS1154 // Sort enum members. - public enum MoveSourceType -#pragma warning restore RCS1154 // Sort enum members. + internal static IEnumerable GetValidMovesAllForms(PKM pk, EvoCriteria[] chain, GameVersion version, int generation, MoveSourceType types, bool RemoveTransferHM, int species, List r) { - None, - LevelUp = 1 << 0, - RelearnMoves = 1 << 1, - Machine = 1 << 2, - TypeTutor = 1 << 3, - SpecialTutor = 1 << 4, - EnhancedTutor = 1 << 5, - SharedEggMove = 1 << 6, - TechnicalRecord = 1 << 7, + // These don't evolve, so don't bother iterating for all entries in the evolution chain (should always be count==1). + int formCount; - AllTutors = TypeTutor | SpecialTutor | EnhancedTutor, - AllMachines = Machine | TechnicalRecord, + // In gen 3 deoxys has different forms depending on the current game, in the PersonalInfo there is no alternate form info + if (pk.Format == 3 && species == (int) Species.Deoxys) + formCount = 4; + else + formCount = pk.PersonalInfo.FormCount; - Reminder = LevelUp | RelearnMoves | TechnicalRecord, - Encounter = LevelUp | RelearnMoves, - ExternalSources = Reminder | AllMachines | AllTutors, - All = ExternalSources | SharedEggMove | RelearnMoves, + for (int form = 0; form < formCount; form++) + r.AddRange(GetMoves(pk, species, form, chain[0].LevelMax, 0, 0, version, types, RemoveTransferHM, generation)); + if (types.HasFlagFast(MoveSourceType.RelearnMoves)) + r.AddRange(pk.RelearnMoves); + return r.Distinct(); } - public static class MoveSourceTypeExtensions + private static IEnumerable GetMoves(PKM pk, int species, int form, int maxLevel, int minlvlG1, int minlvlG2, GameVersion Version, MoveSourceType types, bool RemoveTransferHM, int generation) { - public static bool HasFlagFast(this MoveSourceType value, MoveSourceType flag) => (value & flag) != 0; - public static MoveSourceType ClearNonEggSources(this MoveSourceType value) => value & MoveSourceType.Encounter; + var r = new List(); + if (types.HasFlagFast(MoveSourceType.LevelUp)) + r.AddRange(MoveLevelUp.GetMovesLevelUp(pk, species, form, maxLevel, minlvlG1, minlvlG2, Version, types.HasFlagFast(MoveSourceType.Reminder), generation)); + if (types.HasFlagFast(MoveSourceType.Machine)) + r.AddRange(MoveTechnicalMachine.GetTMHM(pk, species, form, generation, Version, RemoveTransferHM)); + if (types.HasFlagFast(MoveSourceType.TechnicalRecord)) + r.AddRange(MoveTechnicalMachine.GetRecords(pk, species, form, generation)); + if (types.HasFlagFast(MoveSourceType.AllTutors)) + r.AddRange(MoveTutor.GetTutorMoves(pk, species, form, types.HasFlagFast(MoveSourceType.SpecialTutor), generation)); + return r.Distinct(); } } + +[Flags] +#pragma warning disable RCS1154 // Sort enum members. +public enum MoveSourceType +#pragma warning restore RCS1154 // Sort enum members. +{ + None, + LevelUp = 1 << 0, + RelearnMoves = 1 << 1, + Machine = 1 << 2, + TypeTutor = 1 << 3, + SpecialTutor = 1 << 4, + EnhancedTutor = 1 << 5, + SharedEggMove = 1 << 6, + TechnicalRecord = 1 << 7, + + AllTutors = TypeTutor | SpecialTutor | EnhancedTutor, + AllMachines = Machine | TechnicalRecord, + + Reminder = LevelUp | RelearnMoves | TechnicalRecord, + Encounter = LevelUp | RelearnMoves, + ExternalSources = Reminder | AllMachines | AllTutors, + All = ExternalSources | SharedEggMove | RelearnMoves, +} + +public static class MoveSourceTypeExtensions +{ + public static bool HasFlagFast(this MoveSourceType value, MoveSourceType flag) => (value & flag) != 0; + public static MoveSourceType ClearNonEggSources(this MoveSourceType value) => value & MoveSourceType.Encounter; +} diff --git a/PKHeX.Core/Legality/MoveListSuggest.cs b/PKHeX.Core/Legality/MoveListSuggest.cs index e26361502..c8ca3c5eb 100644 --- a/PKHeX.Core/Legality/MoveListSuggest.cs +++ b/PKHeX.Core/Legality/MoveListSuggest.cs @@ -2,194 +2,193 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class MoveListSuggest { - public static class MoveListSuggest + private static int[] GetSuggestedMoves(PKM pk, EvolutionHistory evoChains, MoveSourceType types, IEncounterTemplate enc) { - private static int[] GetSuggestedMoves(PKM pkm, EvolutionHistory evoChains, MoveSourceType types, IEncounterTemplate enc) + if (pk.IsEgg && pk.Format <= 5) // pre relearn + return MoveList.GetBaseEggMoves(pk, pk.Species, 0, (GameVersion)pk.Version, pk.CurrentLevel); + + if (types != MoveSourceType.None) + return GetValidMoves(pk, evoChains, types).Skip(1).ToArray(); // skip move 0 + + // try to give current moves + if (enc.Generation <= 2) { - if (pkm.IsEgg && pkm.Format <= 5) // pre relearn - return MoveList.GetBaseEggMoves(pkm, pkm.Species, 0, (GameVersion)pkm.Version, pkm.CurrentLevel); - - if (types != MoveSourceType.None) - return GetValidMoves(pkm, evoChains, types).Skip(1).ToArray(); // skip move 0 - - // try to give current moves - if (enc.Generation <= 2) - { - var lvl = pkm.Format >= 7 ? pkm.Met_Level : pkm.CurrentLevel; - var ver = enc.Version; - return MoveLevelUp.GetEncounterMoves(enc.Species, 0, lvl, ver); - } - - if (pkm.Species == enc.Species) - { - return MoveLevelUp.GetEncounterMoves(pkm.Species, pkm.Form, pkm.CurrentLevel, (GameVersion)pkm.Version); - } - - return GetValidMoves(pkm, evoChains, types).Skip(1).ToArray(); // skip move 0 + var lvl = pk.Format >= 7 ? pk.Met_Level : pk.CurrentLevel; + var ver = enc.Version; + return MoveLevelUp.GetEncounterMoves(enc.Species, 0, lvl, ver); } - private static IEnumerable GetValidMoves(PKM pkm, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true) + if (pk.Species == enc.Species) { - var (_, version) = pkm.IsMovesetRestricted(); - return GetValidMoves(pkm, version, evoChains, types: types, RemoveTransferHM: RemoveTransferHM); + return MoveLevelUp.GetEncounterMoves(pk.Species, pk.Form, pk.CurrentLevel, (GameVersion)pk.Version); } - private static IEnumerable GetValidMoves(PKM pkm, GameVersion version, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.Reminder, bool RemoveTransferHM = true) + return GetValidMoves(pk, evoChains, types).Skip(1).ToArray(); // skip move 0 + } + + private static IEnumerable GetValidMoves(PKM pk, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true) + { + var (_, version) = pk.IsMovesetRestricted(); + return GetValidMoves(pk, version, evoChains, types: types, RemoveTransferHM: RemoveTransferHM); + } + + private static IEnumerable GetValidMoves(PKM pk, GameVersion version, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.Reminder, bool RemoveTransferHM = true) + { + var r = new List { 0 }; + if (types.HasFlagFast(MoveSourceType.RelearnMoves) && pk.Format >= 6) + r.AddRange(pk.RelearnMoves); + + int start = pk.Generation; + if (start < 0) + start = pk.Format; // be generous instead of returning nothing + if (pk is IBattleVersion b) + start = Math.Max(0, b.GetMinGeneration()); + + for (int generation = start; generation <= pk.Format; generation++) { - var r = new List { 0 }; - if (types.HasFlagFast(MoveSourceType.RelearnMoves) && pkm.Format >= 6) - r.AddRange(pkm.RelearnMoves); - - int start = pkm.Generation; - if (start < 0) - start = pkm.Format; // be generous instead of returning nothing - if (pkm is IBattleVersion b) - start = Math.Max(0, b.GetMinGeneration()); - - for (int generation = start; generation <= pkm.Format; generation++) - { - var chain = evoChains[generation]; - if (chain.Length == 0) - continue; - r.AddRange(MoveList.GetValidMoves(pkm, version, chain, generation, types: types, RemoveTransferHM: RemoveTransferHM)); - } - - return r.Distinct(); + var chain = evoChains[generation]; + if (chain.Length == 0) + continue; + r.AddRange(MoveList.GetValidMoves(pk, version, chain, generation, types: types, RemoveTransferHM: RemoveTransferHM)); } - private static IEnumerable AllSuggestedMoves(this LegalityAnalysis analysis) + return r.Distinct(); + } + + private static IEnumerable AllSuggestedMoves(this LegalityAnalysis analysis) + { + if (!analysis.Parsed) + return new int[4]; + return analysis.GetSuggestedCurrentMoves(); + } + + private static IEnumerable AllSuggestedRelearnMoves(this LegalityAnalysis analysis) + { + if (!analysis.Parsed) + return new int[4]; + var pk = analysis.Entity; + var enc = analysis.EncounterMatch; + return MoveList.GetValidRelearn(pk, enc.Species, enc.Form, (GameVersion)pk.Version).ToArray(); + } + + public static int[] GetSuggestedMovesAndRelearn(this LegalityAnalysis analysis) + { + if (!analysis.Parsed) + return new int[4]; + return analysis.AllSuggestedMoves().Concat(analysis.AllSuggestedRelearnMoves()).ToArray(); + } + + /// + /// Gets four moves which can be learned depending on the input arguments. + /// + /// Parse information to generate a moveset for. + /// Allowed move sources for populating the result array + public static int[] GetSuggestedCurrentMoves(this LegalityAnalysis analysis, MoveSourceType types = MoveSourceType.All) + { + if (!analysis.Parsed) + return new int[4]; + var pk = analysis.Entity; + if (pk.IsEgg && pk.Format >= 6) + return pk.RelearnMoves; + + if (pk.IsEgg) + types = types.ClearNonEggSources(); + + var info = analysis.Info; + return GetSuggestedMoves(pk, info.EvoChainsAllGens, types, info.EncounterOriginal); + } + + /// + /// Gets the current array of four moves that might be legal. + /// + /// Use instead of calling directly; this method just puts default values in without considering the final moveset. + public static IReadOnlyList GetSuggestedRelearn(this IEncounterTemplate enc, PKM pk) + { + if (VerifyRelearnMoves.ShouldNotHaveRelearnMoves(enc, pk)) + return Empty; + + return GetSuggestedRelearnInternal(enc, pk); + } + + // Invalid encounters won't be recognized as an EncounterEgg; check if it *should* be a bred egg. + private static IReadOnlyList GetSuggestedRelearnInternal(this IEncounterTemplate enc, PKM pk) => enc switch + { + IRelearn s when s.Relearn.Count > 0 => s.Relearn, + EncounterEgg or EncounterInvalid {EggEncounter: true} => MoveBreed.GetExpectedMoves(pk.RelearnMoves, enc), + _ => Empty, + }; + + private static readonly IReadOnlyList Empty = new int[4]; + + /// + /// Gets the current array of four moves that might be legal. + /// + public static IReadOnlyList GetSuggestedRelearnMovesFromEncounter(this LegalityAnalysis analysis, IEncounterTemplate? enc = null) + { + var info = analysis.Info; + enc ??= info.EncounterOriginal; + var pk = analysis.Entity; + + if (VerifyRelearnMoves.ShouldNotHaveRelearnMoves(enc, pk)) + return Empty; + + if (enc is EncounterEgg or EncounterInvalid {EggEncounter: true}) + return enc.GetSuggestedRelearnEgg(info.Moves, pk); + return enc.GetSuggestedRelearnInternal(pk); + } + + private static IReadOnlyList GetSuggestedRelearnEgg(this IEncounterTemplate enc, IReadOnlyList parse, PKM pk) + { + var result = enc.GetEggRelearnMoves(parse, pk); + int generation = enc.Generation; + if (generation <= 5) // gen2 does not have splitbreed, <=5 do not have relearn moves and shouldn't even be here. + return result; + + // Split-breed species like Budew & Roselia may be legal for one, and not the other. + // If we're not a split-breed or are already legal, return. + var split = Breeding.GetSplitBreedGeneration(generation); + if (!split.Contains(enc.Species)) + return result; + + var tmp = pk.Clone(); + tmp.SetRelearnMoves(result); + var la = new LegalityAnalysis(tmp); + if (la.Info.Moves.All(z => z.Valid)) + return result; + + // Try again with the other split-breed species if possible. + var incense = EncounterEggGenerator.GenerateEggs(tmp, generation).FirstOrDefault(); + if (incense is null || incense.Species == enc.Species) + return result; + + return incense.GetEggRelearnMoves(parse, pk); + } + + private static IReadOnlyList GetEggRelearnMoves(this IEncounterTemplate enc, IReadOnlyList parse, PKM pk) + { + // Extract a list of the moves that should end up in the relearn move list. + int ctr = 0; + var moves = new int[4]; + for (var i = 0; i < parse.Count; i++) { - if (!analysis.Parsed) - return new int[4]; - return analysis.GetSuggestedCurrentMoves(); + var m = parse[i]; + if (!m.ShouldBeInRelearnMoves()) + continue; + moves[ctr++] = pk.GetMove(i); } - private static IEnumerable AllSuggestedRelearnMoves(this LegalityAnalysis analysis) + // Swap Volt Tackle to the end of the list. + int volt = Array.IndexOf(moves, (int) Move.VoltTackle, 0, ctr); + if (volt != -1) { - if (!analysis.Parsed) - return new int[4]; - var pkm = analysis.pkm; - var enc = analysis.EncounterMatch; - return MoveList.GetValidRelearn(pkm, enc.Species, enc.Form, (GameVersion)pkm.Version).ToArray(); - } - - public static int[] GetSuggestedMovesAndRelearn(this LegalityAnalysis analysis) - { - if (!analysis.Parsed) - return new int[4]; - return analysis.AllSuggestedMoves().Concat(analysis.AllSuggestedRelearnMoves()).ToArray(); - } - - /// - /// Gets four moves which can be learned depending on the input arguments. - /// - /// Parse information to generate a moveset for. - /// Allowed move sources for populating the result array - public static int[] GetSuggestedCurrentMoves(this LegalityAnalysis analysis, MoveSourceType types = MoveSourceType.All) - { - if (!analysis.Parsed) - return new int[4]; - var pkm = analysis.pkm; - if (pkm.IsEgg && pkm.Format >= 6) - return pkm.RelearnMoves; - - if (pkm.IsEgg) - types = types.ClearNonEggSources(); - - var info = analysis.Info; - return GetSuggestedMoves(pkm, info.EvoChainsAllGens, types, info.EncounterOriginal); - } - - /// - /// Gets the current array of four moves that might be legal. - /// - /// Use instead of calling directly; this method just puts default values in without considering the final moveset. - public static IReadOnlyList GetSuggestedRelearn(this IEncounterTemplate enc, PKM pkm) - { - if (VerifyRelearnMoves.ShouldNotHaveRelearnMoves(enc, pkm)) - return Empty; - - return GetSuggestedRelearnInternal(enc, pkm); - } - - // Invalid encounters won't be recognized as an EncounterEgg; check if it *should* be a bred egg. - private static IReadOnlyList GetSuggestedRelearnInternal(this IEncounterTemplate enc, PKM pkm) => enc switch - { - IRelearn s when s.Relearn.Count > 0 => s.Relearn, - EncounterEgg or EncounterInvalid {EggEncounter: true} => MoveBreed.GetExpectedMoves(pkm.RelearnMoves, enc), - _ => Empty, - }; - - private static readonly IReadOnlyList Empty = new int[4]; - - /// - /// Gets the current array of four moves that might be legal. - /// - public static IReadOnlyList GetSuggestedRelearnMovesFromEncounter(this LegalityAnalysis analysis, IEncounterTemplate? enc = null) - { - var info = analysis.Info; - enc ??= info.EncounterOriginal; - var pkm = analysis.pkm; - - if (VerifyRelearnMoves.ShouldNotHaveRelearnMoves(enc, pkm)) - return Empty; - - if (enc is EncounterEgg or EncounterInvalid {EggEncounter: true}) - return enc.GetSuggestedRelearnEgg(info.Moves, pkm); - return enc.GetSuggestedRelearnInternal(pkm); - } - - private static IReadOnlyList GetSuggestedRelearnEgg(this IEncounterTemplate enc, IReadOnlyList parse, PKM pkm) - { - var result = enc.GetEggRelearnMoves(parse, pkm); - int generation = enc.Generation; - if (generation <= 5) // gen2 does not have splitbreed, <=5 do not have relearn moves and shouldn't even be here. - return result; - - // Split-breed species like Budew & Roselia may be legal for one, and not the other. - // If we're not a split-breed or are already legal, return. - var split = Breeding.GetSplitBreedGeneration(generation); - if (!split.Contains(enc.Species)) - return result; - - var tmp = pkm.Clone(); - tmp.SetRelearnMoves(result); - var la = new LegalityAnalysis(tmp); - if (la.Info.Moves.All(z => z.Valid)) - return result; - - // Try again with the other split-breed species if possible. - var incense = EncounterEggGenerator.GenerateEggs(tmp, generation).FirstOrDefault(); - if (incense is null || incense.Species == enc.Species) - return result; - - return incense.GetEggRelearnMoves(parse, pkm); - } - - private static IReadOnlyList GetEggRelearnMoves(this IEncounterTemplate enc, IReadOnlyList parse, PKM pkm) - { - // Extract a list of the moves that should end up in the relearn move list. - int ctr = 0; - var moves = new int[4]; - for (var i = 0; i < parse.Count; i++) - { - var m = parse[i]; - if (!m.ShouldBeInRelearnMoves()) - continue; - moves[ctr++] = pkm.GetMove(i); - } - - // Swap Volt Tackle to the end of the list. - int volt = Array.IndexOf(moves, (int) Move.VoltTackle, 0, ctr); - if (volt != -1) - { - var dest = ctr - 1; - moves[volt] = moves[dest]; - moves[dest] = (int) Move.VoltTackle; - } - return MoveBreed.GetExpectedMoves(moves, enc); + var dest = ctr - 1; + moves[volt] = moves[dest]; + moves[dest] = (int) Move.VoltTackle; } + return MoveBreed.GetExpectedMoves(moves, enc); } } diff --git a/PKHeX.Core/Legality/Moves/Breeding/BreedInfo.cs b/PKHeX.Core/Legality/Moves/Breeding/BreedInfo.cs index 198f34710..a9bb09bfc 100644 --- a/PKHeX.Core/Legality/Moves/Breeding/BreedInfo.cs +++ b/PKHeX.Core/Legality/Moves/Breeding/BreedInfo.cs @@ -1,35 +1,34 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Value passing object to simplify some initialization. +/// +/// Egg Move source type enumeration. +internal readonly ref struct BreedInfo where T : unmanaged { - /// - /// Value passing object to simplify some initialization. - /// - /// Egg Move source type enumeration. - internal readonly ref struct BreedInfo where T : unmanaged + /// Indicates the analyzed source of each move. + public readonly T[] Actual; + + /// Indicates all possible sources of each move. + public readonly Span Possible; + + /// Level Up entry for the egg. + public readonly Learnset Learnset; + + /// Moves the egg knows after it is finalized. + public readonly ReadOnlySpan Moves; + + /// Level the egg originated at. + public readonly int Level; + + public BreedInfo(T[] actual, Span possible, Learnset learnset, ReadOnlySpan moves, int level) { - /// Indicates the analyzed source of each move. - public readonly T[] Actual; - - /// Indicates all possible sources of each move. - public readonly Span Possible; - - /// Level Up entry for the egg. - public readonly Learnset Learnset; - - /// Moves the egg knows after it is finalized. - public readonly ReadOnlySpan Moves; - - /// Level the egg originated at. - public readonly int Level; - - public BreedInfo(T[] actual, Span possible, Learnset learnset, ReadOnlySpan moves, int level) - { - Actual = actual; - Possible = possible; - Learnset = learnset; - Moves = moves; - Level = level; - } + Actual = actual; + Possible = possible; + Learnset = learnset; + Moves = moves; + Level = level; } } diff --git a/PKHeX.Core/Legality/Moves/Breeding/EggSource.cs b/PKHeX.Core/Legality/Moves/Breeding/EggSource.cs index fe5218361..aeec1d77f 100644 --- a/PKHeX.Core/Legality/Moves/Breeding/EggSource.cs +++ b/PKHeX.Core/Legality/Moves/Breeding/EggSource.cs @@ -1,158 +1,157 @@ using System; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Egg Moveset Building Order for Generation 2 +/// +public enum EggSource2 : byte +{ + None, + /// Initial moveset for the egg's level. + Base, + /// Egg move inherited from the Father. + FatherEgg, + /// Technical Machine move inherited from the Father. + FatherTM, + /// Level Up move inherited from a parent. + ParentLevelUp, + /// Tutor move (Elemental Beam) inherited from a parent. + Tutor, + + Max, +} + +/// +/// Egg Moveset Building Order for Generation 3 & 4 +/// +public enum EggSource34 : byte +{ + None, + /// Initial moveset for the egg's level. + Base, + /// Egg move inherited from the Father. + FatherEgg, + /// Technical Machine move inherited from the Father. + FatherTM, + /// Level Up move inherited from a parent. + ParentLevelUp, + + Max, + + /// Special Move applied at the end if certain conditions are satisfied. + VoltTackle, +} + +/// +/// Egg Moveset Building Order for Generation 5 +/// +public enum EggSource5 : byte +{ + None, + /// Initial moveset for the egg's level. + Base, + /// Egg move inherited from the Father. + FatherEgg, + /// Technical Machine move inherited from the Father. + ParentLevelUp, + /// Technical Machine move inherited from the Father. + /// After level up, unlike Gen3/4! + FatherTM, + + Max, + + /// Special Move applied at the end if certain conditions are satisfied. + VoltTackle, +} + +/// +/// Egg Moveset Building Order for Generation 6+ +/// +public enum EggSource6 : byte +{ + None, + /// Initial moveset for the egg's level. + Base, + /// Level Up move inherited from a parent. + ParentLevelUp, + /// Egg move inherited from a parent. + ParentEgg, + + Max, + + /// Special Move applied at the end if certain conditions are satisfied. + VoltTackle, +} + +/// +/// Utility logic for converting a move result into a user friendly string. +/// +public static class EggSourceUtil { /// - /// Egg Moveset Building Order for Generation 2 + /// Unboxes the parse result and returns a user friendly string for the move result. /// - public enum EggSource2 : byte + public static string GetSource(object parse, int generation, int index) { - None, - /// Initial moveset for the egg's level. - Base, - /// Egg move inherited from the Father. - FatherEgg, - /// Technical Machine move inherited from the Father. - FatherTM, - /// Level Up move inherited from a parent. - ParentLevelUp, - /// Tutor move (Elemental Beam) inherited from a parent. - Tutor, - - Max, - } - - /// - /// Egg Moveset Building Order for Generation 3 & 4 - /// - public enum EggSource34 : byte - { - None, - /// Initial moveset for the egg's level. - Base, - /// Egg move inherited from the Father. - FatherEgg, - /// Technical Machine move inherited from the Father. - FatherTM, - /// Level Up move inherited from a parent. - ParentLevelUp, - - Max, - - /// Special Move applied at the end if certain conditions are satisfied. - VoltTackle, - } - - /// - /// Egg Moveset Building Order for Generation 5 - /// - public enum EggSource5 : byte - { - None, - /// Initial moveset for the egg's level. - Base, - /// Egg move inherited from the Father. - FatherEgg, - /// Technical Machine move inherited from the Father. - ParentLevelUp, - /// Technical Machine move inherited from the Father. - /// After level up, unlike Gen3/4! - FatherTM, - - Max, - - /// Special Move applied at the end if certain conditions are satisfied. - VoltTackle, - } - - /// - /// Egg Moveset Building Order for Generation 6+ - /// - public enum EggSource6 : byte - { - None, - /// Initial moveset for the egg's level. - Base, - /// Level Up move inherited from a parent. - ParentLevelUp, - /// Egg move inherited from a parent. - ParentEgg, - - Max, - - /// Special Move applied at the end if certain conditions are satisfied. - VoltTackle, - } - - /// - /// Utility logic for converting a move result into a user friendly string. - /// - public static class EggSourceUtil - { - /// - /// Unboxes the parse result and returns a user friendly string for the move result. - /// - public static string GetSource(object parse, int generation, int index) + static string GetLine(T[] arr, Func act, int index) { - static string GetLine(T[] arr, Func act, int index) - { - if ((uint)index >= arr.Length) - return LMoveSourceEmpty; - return act(arr[index]); - } - - return generation switch - { - 2 => GetLine((EggSource2[]) parse, GetSource, index), - 3 or 4 => GetLine((EggSource34[])parse, GetSource, index), - 5 => GetLine((EggSource5[]) parse, GetSource, index), - >= 6 => GetLine((EggSource6[]) parse, GetSource, index), - _ => LMoveSourceEmpty, - }; + if ((uint)index >= arr.Length) + return LMoveSourceEmpty; + return act(arr[index]); } - public static string GetSource(this EggSource2 source) => source switch + return generation switch { - EggSource2.Base => LMoveRelearnEgg, - EggSource2.FatherEgg => LMoveEggInherited, - EggSource2.FatherTM => LMoveEggTMHM, - EggSource2.ParentLevelUp => LMoveEggLevelUp, - EggSource2.Tutor => LMoveEggInheritedTutor, - EggSource2.Max => "Any", - _ => LMoveEggInvalid, - }; - - public static string GetSource(this EggSource34 source) => source switch - { - EggSource34.Base => LMoveRelearnEgg, - EggSource34.FatherEgg => LMoveEggInherited, - EggSource34.FatherTM => LMoveEggTMHM, - EggSource34.ParentLevelUp => LMoveEggLevelUp, - EggSource34.Max => "Any", - EggSource34.VoltTackle => LMoveSourceSpecial, - _ => LMoveEggInvalid, - }; - - public static string GetSource(this EggSource5 source) => source switch - { - EggSource5.Base => LMoveRelearnEgg, - EggSource5.FatherEgg => LMoveEggInherited, - EggSource5.ParentLevelUp => LMoveEggLevelUp, - EggSource5.FatherTM => LMoveEggTMHM, - EggSource5.Max => "Any", - EggSource5.VoltTackle => LMoveSourceSpecial, - _ => LMoveEggInvalid, - }; - - public static string GetSource(this EggSource6 source) => source switch - { - EggSource6.Base => LMoveRelearnEgg, - EggSource6.ParentLevelUp => LMoveEggLevelUp, - EggSource6.ParentEgg => LMoveEggInherited, - EggSource6.Max => "Any", - EggSource6.VoltTackle => LMoveSourceSpecial, - _ => LMoveEggInvalid, + 2 => GetLine((EggSource2[]) parse, GetSource, index), + 3 or 4 => GetLine((EggSource34[])parse, GetSource, index), + 5 => GetLine((EggSource5[]) parse, GetSource, index), + >= 6 => GetLine((EggSource6[]) parse, GetSource, index), + _ => LMoveSourceEmpty, }; } + + public static string GetSource(this EggSource2 source) => source switch + { + EggSource2.Base => LMoveRelearnEgg, + EggSource2.FatherEgg => LMoveEggInherited, + EggSource2.FatherTM => LMoveEggTMHM, + EggSource2.ParentLevelUp => LMoveEggLevelUp, + EggSource2.Tutor => LMoveEggInheritedTutor, + EggSource2.Max => "Any", + _ => LMoveEggInvalid, + }; + + public static string GetSource(this EggSource34 source) => source switch + { + EggSource34.Base => LMoveRelearnEgg, + EggSource34.FatherEgg => LMoveEggInherited, + EggSource34.FatherTM => LMoveEggTMHM, + EggSource34.ParentLevelUp => LMoveEggLevelUp, + EggSource34.Max => "Any", + EggSource34.VoltTackle => LMoveSourceSpecial, + _ => LMoveEggInvalid, + }; + + public static string GetSource(this EggSource5 source) => source switch + { + EggSource5.Base => LMoveRelearnEgg, + EggSource5.FatherEgg => LMoveEggInherited, + EggSource5.ParentLevelUp => LMoveEggLevelUp, + EggSource5.FatherTM => LMoveEggTMHM, + EggSource5.Max => "Any", + EggSource5.VoltTackle => LMoveSourceSpecial, + _ => LMoveEggInvalid, + }; + + public static string GetSource(this EggSource6 source) => source switch + { + EggSource6.Base => LMoveRelearnEgg, + EggSource6.ParentLevelUp => LMoveEggLevelUp, + EggSource6.ParentEgg => LMoveEggInherited, + EggSource6.Max => "Any", + EggSource6.VoltTackle => LMoveSourceSpecial, + _ => LMoveEggInvalid, + }; } diff --git a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed.cs b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed.cs index ed59f1db6..7f87cb913 100644 --- a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed.cs +++ b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed.cs @@ -2,106 +2,105 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class MoveBreed { - public static class MoveBreed + public static bool Process(int generation, int species, int form, GameVersion version, int[] moves) { - public static bool Process(int generation, int species, int form, GameVersion version, int[] moves) - { - _ = Process(generation, species, form, version, moves, out var valid); - return valid; - } - - public static object Process(int generation, int species, int form, GameVersion version, ReadOnlySpan moves, out bool valid) => generation switch - { - 2 => MoveBreed2.Validate(species, version, moves, out valid), - 3 => MoveBreed3.Validate(species, version, moves, out valid), - 4 => MoveBreed4.Validate(species, version, moves, out valid), - 5 => MoveBreed5.Validate(species, version, moves, out valid), - _ => MoveBreed6.Validate(generation, species, form, version, moves, out valid), - }; - - public static int[] GetExpectedMoves(int[] moves, IEncounterTemplate enc) - { - var parse = Process(enc.Generation, enc.Species, enc.Form, enc.Version, moves, out var valid); - if (valid) - return moves; - return GetExpectedMoves(enc.Generation, enc.Species, enc.Form, enc.Version, moves, parse); - } - - public static int[] GetExpectedMoves(int generation, int species, int form, GameVersion version, ReadOnlySpan moves, object parse) - { - // Try rearranging the order of the moves. - // Build an info table - var x = (byte[])parse; - var details = new MoveOrder[moves.Length]; - for (byte i = 0; i < x.Length; i++) - details[i] = new MoveOrder((ushort) moves[i], x[i]); - - // Kick empty slots to the end, then order by source priority. - IOrderedEnumerable expect = generation != 2 - ? details.OrderBy(z => z.Move == 0).ThenBy(z => z.Source) - : details.OrderBy(z => z.Move == 0).ThenBy(z => z.Source != (byte) EggSource2.Base); - - // Reorder the moves. - var reorder1 = new int[moves.Length]; - var exp = expect.ToList(); - for (int i = 0; i < moves.Length; i++) - reorder1[i] = exp[i].Move; - - // Check if that worked... - _ = Process(generation, species, form, version, reorder1, out var valid); - if (valid) - return reorder1; - - // Well, that didn't work; probably because the moves aren't valid. Let's remove all the base moves, and get a fresh set. - var reorder2 = reorder1; // reuse instead of reallocate - var learn = GameData.GetLearnsets(version); - var table = GameData.GetPersonal(version); - var index = table.GetFormIndex(species, form); - var learnset = learn[index]; - var eggLevel = EggStateLegality.GetEggLevel(generation); - var baseMoves = learnset.GetBaseEggMoves(eggLevel); - - RebuildMoves(baseMoves, exp, reorder2); - - // Check if that worked... - _ = Process(generation, species, form, version, reorder2, out valid); - if (valid) - return reorder2; - - // Total failure; just return the base moves. - baseMoves.CopyTo(reorder2); - for (int i = baseMoves.Length; i < reorder2.Length; i++) - reorder2[i] = 0; - return reorder2; - } - - private static void RebuildMoves(ReadOnlySpan baseMoves, List exp, int[] result) - { - var notBase = new List(); - foreach (var m in exp) - { - if (m.Source == 0) - continue; // invalid - int move = m.Move; - if (baseMoves.IndexOf(move) == -1) - notBase.Add(move); - } - - int baseCount = 4 - notBase.Count; - if (baseCount > baseMoves.Length) - baseCount = baseMoves.Length; - int ctr = 0; - for (; ctr < baseCount; ctr++) - result[ctr] = baseMoves[baseMoves.Length - baseCount + ctr]; - foreach (var m in notBase) - result[ctr++] = m; - - for (int i = ctr; i < result.Length; i++) - result[i] = 0; - } - - private readonly record struct MoveOrder(ushort Move, byte Source); + _ = Process(generation, species, form, version, moves, out var valid); + return valid; } + + public static object Process(int generation, int species, int form, GameVersion version, ReadOnlySpan moves, out bool valid) => generation switch + { + 2 => MoveBreed2.Validate(species, version, moves, out valid), + 3 => MoveBreed3.Validate(species, version, moves, out valid), + 4 => MoveBreed4.Validate(species, version, moves, out valid), + 5 => MoveBreed5.Validate(species, version, moves, out valid), + _ => MoveBreed6.Validate(generation, species, form, version, moves, out valid), + }; + + public static int[] GetExpectedMoves(int[] moves, IEncounterTemplate enc) + { + var parse = Process(enc.Generation, enc.Species, enc.Form, enc.Version, moves, out var valid); + if (valid) + return moves; + return GetExpectedMoves(enc.Generation, enc.Species, enc.Form, enc.Version, moves, parse); + } + + public static int[] GetExpectedMoves(int generation, int species, int form, GameVersion version, ReadOnlySpan moves, object parse) + { + // Try rearranging the order of the moves. + // Build an info table + var x = (byte[])parse; + var details = new MoveOrder[moves.Length]; + for (byte i = 0; i < x.Length; i++) + details[i] = new MoveOrder((ushort) moves[i], x[i]); + + // Kick empty slots to the end, then order by source priority. + IOrderedEnumerable expect = generation != 2 + ? details.OrderBy(z => z.Move == 0).ThenBy(z => z.Source) + : details.OrderBy(z => z.Move == 0).ThenBy(z => z.Source != (byte) EggSource2.Base); + + // Reorder the moves. + var reorder1 = new int[moves.Length]; + var exp = expect.ToList(); + for (int i = 0; i < moves.Length; i++) + reorder1[i] = exp[i].Move; + + // Check if that worked... + _ = Process(generation, species, form, version, reorder1, out var valid); + if (valid) + return reorder1; + + // Well, that didn't work; probably because the moves aren't valid. Let's remove all the base moves, and get a fresh set. + var reorder2 = reorder1; // reuse instead of reallocate + var learn = GameData.GetLearnsets(version); + var table = GameData.GetPersonal(version); + var index = table.GetFormIndex(species, form); + var learnset = learn[index]; + var eggLevel = EggStateLegality.GetEggLevel(generation); + var baseMoves = learnset.GetBaseEggMoves(eggLevel); + + RebuildMoves(baseMoves, exp, reorder2); + + // Check if that worked... + _ = Process(generation, species, form, version, reorder2, out valid); + if (valid) + return reorder2; + + // Total failure; just return the base moves. + baseMoves.CopyTo(reorder2); + for (int i = baseMoves.Length; i < reorder2.Length; i++) + reorder2[i] = 0; + return reorder2; + } + + private static void RebuildMoves(ReadOnlySpan baseMoves, List exp, int[] result) + { + var notBase = new List(); + foreach (var m in exp) + { + if (m.Source == 0) + continue; // invalid + int move = m.Move; + if (baseMoves.IndexOf(move) == -1) + notBase.Add(move); + } + + int baseCount = 4 - notBase.Count; + if (baseCount > baseMoves.Length) + baseCount = baseMoves.Length; + int ctr = 0; + for (; ctr < baseCount; ctr++) + result[ctr] = baseMoves[baseMoves.Length - baseCount + ctr]; + foreach (var m in notBase) + result[ctr++] = m; + + for (int i = ctr; i < result.Length; i++) + result[i] = 0; + } + + private readonly record struct MoveOrder(ushort Move, byte Source); } diff --git a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed2.cs b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed2.cs index 2e22c5342..6ab403c2a 100644 --- a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed2.cs +++ b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed2.cs @@ -3,182 +3,181 @@ using System.Runtime.CompilerServices; using static PKHeX.Core.EggSource2; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class MoveBreed2 { - public static class MoveBreed2 + private const int level = 5; + + public static EggSource2[] Validate(int species, GameVersion version, ReadOnlySpan moves, out bool valid) { - private const int level = 5; - - public static EggSource2[] Validate(int species, GameVersion version, ReadOnlySpan moves, out bool valid) + var count = moves.IndexOf(0); + if (count == 0) { - var count = moves.IndexOf(0); - if (count == 0) - { - valid = false; // empty moveset - return Array.Empty(); - } - if (count == -1) - count = moves.Length; + valid = false; // empty moveset + return Array.Empty(); + } + if (count == -1) + count = moves.Length; - var learn = GameData.GetLearnsets(version); - var table = GameData.GetPersonal(version); - var learnset = learn[species]; - var pi = table[species]; - var egg = (version == GameVersion.C ? Legal.EggMovesC : Legal.EggMovesGS)[species].Moves; + var learn = GameData.GetLearnsets(version); + var table = GameData.GetPersonal(version); + var learnset = learn[species]; + var pi = table[species]; + var egg = (version == GameVersion.C ? Legal.EggMovesC : Legal.EggMovesGS)[species].Moves; - var actual = new EggSource2[count]; - Span possible = stackalloc byte[count]; - var value = new BreedInfo(actual, possible, learnset, moves, level); - { - bool inherit = Breeding.GetCanInheritMoves(species); - MarkMovesForOrigin(value, egg, count, inherit, pi, version); - valid = RecurseMovesForOrigin(value, count - 1); - } - - if (!valid) - CleanResult(actual, possible); - return actual; + var actual = new EggSource2[count]; + Span possible = stackalloc byte[count]; + var value = new BreedInfo(actual, possible, learnset, moves, level); + { + bool inherit = Breeding.GetCanInheritMoves(species); + MarkMovesForOrigin(value, egg, count, inherit, pi, version); + valid = RecurseMovesForOrigin(value, count - 1); } - private static void CleanResult(EggSource2[] valueActual, Span valuePossible) + if (!valid) + CleanResult(actual, possible); + return actual; + } + + private static void CleanResult(EggSource2[] valueActual, Span valuePossible) + { + for (int i = 0; i < valueActual.Length; i++) { - for (int i = 0; i < valueActual.Length; i++) + if (valueActual[i] != 0) + continue; + var poss = valuePossible[i]; + if (poss == 0) + continue; + + for (int j = 0; j < (int) Max; j++) { - if (valueActual[i] != 0) + if ((poss & (1 << j)) == 0) continue; - var poss = valuePossible[i]; - if (poss == 0) - continue; - - for (int j = 0; j < (int) Max; j++) - { - if ((poss & (1 << j)) == 0) - continue; - valueActual[i] = (EggSource2)j; - break; - } + valueActual[i] = (EggSource2)j; + break; } } + } - private static bool RecurseMovesForOrigin(in BreedInfo info, int start, EggSource2 type = Max) + private static bool RecurseMovesForOrigin(in BreedInfo info, int start, EggSource2 type = Max) + { + int i = start; + do { - int i = start; - do + if (type != Base) { - if (type != Base) - { - if (RecurseMovesForOrigin(info, i, Base)) - return true; - } - - var flag = 1 << (int)Base; - if (type != Base) - flag = ~flag; - - var permit = info.Possible[i]; - if ((permit & flag) == 0) - return false; - - info.Actual[i] = type == Base ? Base : GetFirstType(permit); - } while (--i >= 0); - - return VerifyBaseMoves(info); - } - - private static EggSource2 GetFirstType(byte permit) - { - for (var type = FatherEgg; type < Max; type++) - { - if ((permit & (1 << (int)type)) != 0) - return type; - } - throw new ArgumentOutOfRangeException(nameof(permit), permit, null); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool VerifyBaseMoves(in BreedInfo info) - { - var count = 0; - foreach (var x in info.Actual) - { - if (x == Base) - count++; - else - break; + if (RecurseMovesForOrigin(info, i, Base)) + return true; } - var moves = info.Moves; - if (count == -1) - return moves[^1] != 0; + var flag = 1 << (int)Base; + if (type != Base) + flag = ~flag; - var baseMoves = info.Learnset.GetBaseEggMoves(info.Level); - if (baseMoves.Length < count) - return false; - if (moves[^1] == 0 && count != baseMoves.Length) + var permit = info.Possible[i]; + if ((permit & flag) == 0) return false; - for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--) - { - var move = moves[i]; - var expect = baseMoves[b]; - if (expect != move) - return false; - } + info.Actual[i] = type == Base ? Base : GetFirstType(permit); + } while (--i >= 0); - // A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it. - if (baseMoves.Length == count) - return true; + return VerifyBaseMoves(info); + } - for (int i = count; i < info.Actual.Length; i++) - { - var isBase = (info.Possible[i] & (1 << (int)Base)) != 0; - if (!isBase) - continue; + private static EggSource2 GetFirstType(byte permit) + { + for (var type = FatherEgg; type < Max; type++) + { + if ((permit & (1 << (int)type)) != 0) + return type; + } + throw new ArgumentOutOfRangeException(nameof(permit), permit, null); + } - var baseIndex = baseMoves.IndexOf(moves[i]); - var min = moves.Length - baseMoves.Length + baseIndex; - if (i < min + count) - return false; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool VerifyBaseMoves(in BreedInfo info) + { + var count = 0; + foreach (var x in info.Actual) + { + if (x == Base) + count++; + else + break; + } + var moves = info.Moves; + if (count == -1) + return moves[^1] != 0; + + var baseMoves = info.Learnset.GetBaseEggMoves(info.Level); + if (baseMoves.Length < count) + return false; + if (moves[^1] == 0 && count != baseMoves.Length) + return false; + + for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--) + { + var move = moves[i]; + var expect = baseMoves[b]; + if (expect != move) + return false; + } + + // A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it. + if (baseMoves.Length == count) return true; + + for (int i = count; i < info.Actual.Length; i++) + { + var isBase = (info.Possible[i] & (1 << (int)Base)) != 0; + if (!isBase) + continue; + + var baseIndex = baseMoves.IndexOf(moves[i]); + var min = moves.Length - baseMoves.Length + baseIndex; + if (i < min + count) + return false; } - private static void MarkMovesForOrigin(in BreedInfo value, ICollection eggMoves, int count, bool inheritLevelUp, PersonalInfo info, GameVersion version) + return true; + } + + private static void MarkMovesForOrigin(in BreedInfo value, ICollection eggMoves, int count, bool inheritLevelUp, PersonalInfo info, GameVersion version) + { + var possible = value.Possible; + var learn = value.Learnset; + var baseEgg = value.Learnset.GetBaseEggMoves(value.Level); + var tm = info.TMHM; + + var moves = value.Moves; + for (int i = 0; i < count; i++) { - var possible = value.Possible; - var learn = value.Learnset; - var baseEgg = value.Learnset.GetBaseEggMoves(value.Level); - var tm = info.TMHM; + var move = moves[i]; - var moves = value.Moves; - for (int i = 0; i < count; i++) + if (baseEgg.IndexOf(move) != -1) + possible[i] |= 1 << (int)Base; + + if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1) + possible[i] |= 1 << (int)ParentLevelUp; + + if (eggMoves.Contains(move)) + possible[i] |= 1 << (int)FatherEgg; + + var tmIndex = Array.IndexOf(Legal.TMHM_GSC, move, 0, 50); + if (tmIndex != -1 && tm[tmIndex]) + possible[i] |= 1 << (int)FatherTM; + + var hmIndex = Array.IndexOf(Legal.TMHM_GSC, move, 50); + if (hmIndex != -1 && tm[hmIndex + 50]) + possible[i] |= 1 << (int)FatherTM; + + if (version is GameVersion.C) { - var move = moves[i]; - - if (baseEgg.IndexOf(move) != -1) - possible[i] |= 1 << (int)Base; - - if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1) - possible[i] |= 1 << (int)ParentLevelUp; - - if (eggMoves.Contains(move)) - possible[i] |= 1 << (int)FatherEgg; - - var tmIndex = Array.IndexOf(Legal.TMHM_GSC, move, 0, 50); - if (tmIndex != -1 && tm[tmIndex]) - possible[i] |= 1 << (int)FatherTM; - - var hmIndex = Array.IndexOf(Legal.TMHM_GSC, move, 50); - if (hmIndex != -1 && tm[hmIndex + 50]) - possible[i] |= 1 << (int)FatherTM; - - if (version is GameVersion.C) - { - var tutorIndex = Array.IndexOf(Legal.Tutors_GSC, move); - if (tutorIndex != -1 && tm[57 + tutorIndex]) - possible[i] |= 1 << (int)Tutor; - } + var tutorIndex = Array.IndexOf(Legal.Tutors_GSC, move); + if (tutorIndex != -1 && tm[57 + tutorIndex]) + possible[i] |= 1 << (int)Tutor; } } } diff --git a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed3.cs b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed3.cs index 6dcb3c888..7758c7a24 100644 --- a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed3.cs +++ b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed3.cs @@ -3,173 +3,172 @@ using System.Runtime.CompilerServices; using static PKHeX.Core.EggSource34; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Inheritance logic for Generation 3. +/// +/// Refer to for inheritance ordering. +public static class MoveBreed3 { - /// - /// Inheritance logic for Generation 3. - /// - /// Refer to for inheritance ordering. - public static class MoveBreed3 + private const int level = 5; + + public static EggSource34[] Validate(int species, GameVersion version, ReadOnlySpan moves, out bool valid) { - private const int level = 5; - - public static EggSource34[] Validate(int species, GameVersion version, ReadOnlySpan moves, out bool valid) + var count = moves.IndexOf(0); + if (count == 0) { - var count = moves.IndexOf(0); - if (count == 0) - { - valid = false; // empty moveset - return Array.Empty(); - } - if (count == -1) - count = moves.Length; + valid = false; // empty moveset + return Array.Empty(); + } + if (count == -1) + count = moves.Length; - var learn = GameData.GetLearnsets(version); - var table = GameData.GetPersonal(version); - var learnset = learn[species]; - var pi = table[species]; - var egg = Legal.EggMovesRS[species].Moves; + var learn = GameData.GetLearnsets(version); + var table = GameData.GetPersonal(version); + var learnset = learn[species]; + var pi = table[species]; + var egg = Legal.EggMovesRS[species].Moves; - var actual = new EggSource34[count]; - Span possible = stackalloc byte[count]; - var value = new BreedInfo(actual, possible, learnset, moves, level); - if (species is (int)Species.Pichu && moves[count - 1] is (int)Move.VoltTackle && version == GameVersion.E) - actual[--count] = VoltTackle; + var actual = new EggSource34[count]; + Span possible = stackalloc byte[count]; + var value = new BreedInfo(actual, possible, learnset, moves, level); + if (species is (int)Species.Pichu && moves[count - 1] is (int)Move.VoltTackle && version == GameVersion.E) + actual[--count] = VoltTackle; - if (count == 0) - { - valid = VerifyBaseMoves(value); - } - else - { - bool inherit = Breeding.GetCanInheritMoves(species); - MarkMovesForOrigin(value, egg, count, inherit, pi); - valid = RecurseMovesForOrigin(value, count - 1); - } - - if (!valid) - CleanResult(actual, possible); - return value.Actual; + if (count == 0) + { + valid = VerifyBaseMoves(value); + } + else + { + bool inherit = Breeding.GetCanInheritMoves(species); + MarkMovesForOrigin(value, egg, count, inherit, pi); + valid = RecurseMovesForOrigin(value, count - 1); } - private static void CleanResult(EggSource34[] valueActual, Span valuePossible) + if (!valid) + CleanResult(actual, possible); + return value.Actual; + } + + private static void CleanResult(EggSource34[] valueActual, Span valuePossible) + { + for (int i = 0; i < valueActual.Length; i++) { - for (int i = 0; i < valueActual.Length; i++) + if (valueActual[i] != 0) + continue; + var poss = valuePossible[i]; + if (poss == 0) + continue; + + for (int j = 0; j < (int)Max; j++) { - if (valueActual[i] != 0) + if ((poss & (1 << j)) == 0) continue; - var poss = valuePossible[i]; - if (poss == 0) - continue; - - for (int j = 0; j < (int)Max; j++) - { - if ((poss & (1 << j)) == 0) - continue; - valueActual[i] = (EggSource34)j; - break; - } - } - } - - private static bool RecurseMovesForOrigin(in BreedInfo info, int start, EggSource34 type = Max - 1) - { - int i = start; - do - { - var unpeel = type - 1; - if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel)) - return true; - - var permit = info.Possible[i]; - if ((permit & (1 << (int)type)) == 0) - return false; - - info.Actual[i] = type; - } while (--i >= 0); - - return VerifyBaseMoves(info); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool VerifyBaseMoves(in BreedInfo info) - { - var count = 0; - foreach (var x in info.Actual) - { - if (x == Base) - count++; - else - break; - } - - var moves = info.Moves; - if (count == -1) - return moves[^1] != 0; - - var baseMoves = info.Learnset.GetBaseEggMoves(info.Level); - if (baseMoves.Length < count) - return false; - if (moves[^1] == 0 && count != baseMoves.Length) - return false; - - for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--) - { - var move = moves[i]; - var expect = baseMoves[b]; - if (expect != move) - return false; - } - - // A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it. - if (baseMoves.Length == count) - return true; - - for (int i = count; i < info.Actual.Length; i++) - { - var isBase = (info.Possible[i] & (1 << (int)Base)) != 0; - if (!isBase) - continue; - - var baseIndex = baseMoves.IndexOf(moves[i]); - var min = moves.Length - baseMoves.Length + baseIndex; - if (i < min + count) - return false; - } - - return true; - } - - private static void MarkMovesForOrigin(in BreedInfo value, ICollection eggMoves, int count, bool inheritLevelUp, PersonalInfo info) - { - var possible = value.Possible; - var learn = value.Learnset; - var baseEgg = value.Learnset.GetBaseEggMoves(value.Level); - var tm = info.TMHM; - var tmlist = Legal.TM_3.AsSpan(0, 50); - var hmlist = Legal.HM_3.AsSpan(); - var moves = value.Moves; - for (int i = 0; i < count; i++) - { - var move = moves[i]; - - if (baseEgg.IndexOf(move) != -1) - possible[i] |= 1 << (int)Base; - - if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1) - possible[i] |= 1 << (int)ParentLevelUp; - - if (eggMoves.Contains(move)) - possible[i] |= 1 << (int)FatherEgg; - - var tmIndex = tmlist.IndexOf(move); - if (tmIndex != -1 && tm[tmIndex]) - possible[i] |= 1 << (int)FatherTM; - - var hmIndex = hmlist.IndexOf(move); - if (hmIndex != -1 && tm[hmIndex + 50]) - possible[i] |= 1 << (int)FatherTM; + valueActual[i] = (EggSource34)j; + break; } } } + + private static bool RecurseMovesForOrigin(in BreedInfo info, int start, EggSource34 type = Max - 1) + { + int i = start; + do + { + var unpeel = type - 1; + if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel)) + return true; + + var permit = info.Possible[i]; + if ((permit & (1 << (int)type)) == 0) + return false; + + info.Actual[i] = type; + } while (--i >= 0); + + return VerifyBaseMoves(info); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool VerifyBaseMoves(in BreedInfo info) + { + var count = 0; + foreach (var x in info.Actual) + { + if (x == Base) + count++; + else + break; + } + + var moves = info.Moves; + if (count == -1) + return moves[^1] != 0; + + var baseMoves = info.Learnset.GetBaseEggMoves(info.Level); + if (baseMoves.Length < count) + return false; + if (moves[^1] == 0 && count != baseMoves.Length) + return false; + + for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--) + { + var move = moves[i]; + var expect = baseMoves[b]; + if (expect != move) + return false; + } + + // A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it. + if (baseMoves.Length == count) + return true; + + for (int i = count; i < info.Actual.Length; i++) + { + var isBase = (info.Possible[i] & (1 << (int)Base)) != 0; + if (!isBase) + continue; + + var baseIndex = baseMoves.IndexOf(moves[i]); + var min = moves.Length - baseMoves.Length + baseIndex; + if (i < min + count) + return false; + } + + return true; + } + + private static void MarkMovesForOrigin(in BreedInfo value, ICollection eggMoves, int count, bool inheritLevelUp, PersonalInfo info) + { + var possible = value.Possible; + var learn = value.Learnset; + var baseEgg = value.Learnset.GetBaseEggMoves(value.Level); + var tm = info.TMHM; + var tmlist = Legal.TM_3.AsSpan(0, 50); + var hmlist = Legal.HM_3.AsSpan(); + var moves = value.Moves; + for (int i = 0; i < count; i++) + { + var move = moves[i]; + + if (baseEgg.IndexOf(move) != -1) + possible[i] |= 1 << (int)Base; + + if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1) + possible[i] |= 1 << (int)ParentLevelUp; + + if (eggMoves.Contains(move)) + possible[i] |= 1 << (int)FatherEgg; + + var tmIndex = tmlist.IndexOf(move); + if (tmIndex != -1 && tm[tmIndex]) + possible[i] |= 1 << (int)FatherTM; + + var hmIndex = hmlist.IndexOf(move); + if (hmIndex != -1 && tm[hmIndex + 50]) + possible[i] |= 1 << (int)FatherTM; + } + } } diff --git a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed4.cs b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed4.cs index 33f95f4c8..e8d2044ff 100644 --- a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed4.cs +++ b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed4.cs @@ -4,174 +4,173 @@ using static PKHeX.Core.EggSource34; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Inheritance logic for Generation 4. +/// +/// Refer to for inheritance ordering. +public static class MoveBreed4 { - /// - /// Inheritance logic for Generation 4. - /// - /// Refer to for inheritance ordering. - public static class MoveBreed4 + private const int level = 1; + + public static EggSource34[] Validate(int species, GameVersion version, ReadOnlySpan moves, out bool valid) { - private const int level = 1; - - public static EggSource34[] Validate(int species, GameVersion version, ReadOnlySpan moves, out bool valid) + var count = moves.IndexOf(0); + if (count == 0) { - var count = moves.IndexOf(0); - if (count == 0) - { - valid = false; // empty moveset - return Array.Empty(); - } - if (count == -1) - count = moves.Length; + valid = false; // empty moveset + return Array.Empty(); + } + if (count == -1) + count = moves.Length; - var learn = GameData.GetLearnsets(version); - var table = GameData.GetPersonal(version); - var learnset = learn[species]; - var pi = table[species]; - var egg = (version is HG or SS ? Legal.EggMovesHGSS : Legal.EggMovesDPPt)[species].Moves; + var learn = GameData.GetLearnsets(version); + var table = GameData.GetPersonal(version); + var learnset = learn[species]; + var pi = table[species]; + var egg = (version is HG or SS ? Legal.EggMovesHGSS : Legal.EggMovesDPPt)[species].Moves; - var actual = new EggSource34[count]; - Span possible = stackalloc byte[count]; - var value = new BreedInfo(actual, possible, learnset, moves, level); - if (species is (int)Species.Pichu && moves[count - 1] is (int)Move.VoltTackle) - actual[--count] = VoltTackle; + var actual = new EggSource34[count]; + Span possible = stackalloc byte[count]; + var value = new BreedInfo(actual, possible, learnset, moves, level); + if (species is (int)Species.Pichu && moves[count - 1] is (int)Move.VoltTackle) + actual[--count] = VoltTackle; - if (count == 0) - { - valid = VerifyBaseMoves(value); - } - else - { - bool inherit = Breeding.GetCanInheritMoves(species); - MarkMovesForOrigin(value, egg, count, inherit, pi, version); - valid = RecurseMovesForOrigin(value, count - 1); - } - - if (!valid) - CleanResult(actual, possible); - return value.Actual; + if (count == 0) + { + valid = VerifyBaseMoves(value); + } + else + { + bool inherit = Breeding.GetCanInheritMoves(species); + MarkMovesForOrigin(value, egg, count, inherit, pi, version); + valid = RecurseMovesForOrigin(value, count - 1); } - private static void CleanResult(EggSource34[] valueActual, Span valuePossible) + if (!valid) + CleanResult(actual, possible); + return value.Actual; + } + + private static void CleanResult(EggSource34[] valueActual, Span valuePossible) + { + for (int i = 0; i < valueActual.Length; i++) { - for (int i = 0; i < valueActual.Length; i++) + if (valueActual[i] != 0) + continue; + var poss = valuePossible[i]; + if (poss == 0) + continue; + + for (int j = 0; j < (int)Max; j++) { - if (valueActual[i] != 0) + if ((poss & (1 << j)) == 0) continue; - var poss = valuePossible[i]; - if (poss == 0) - continue; - - for (int j = 0; j < (int)Max; j++) - { - if ((poss & (1 << j)) == 0) - continue; - valueActual[i] = (EggSource34)j; - break; - } - } - } - - private static bool RecurseMovesForOrigin(in BreedInfo info, int start, EggSource34 type = Max - 1) - { - int i = start; - do - { - var unpeel = type - 1; - if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel)) - return true; - - var permit = info.Possible[i]; - if ((permit & (1 << (int)type)) == 0) - return false; - - info.Actual[i] = type; - } while (--i >= 0); - - return VerifyBaseMoves(info); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool VerifyBaseMoves(in BreedInfo info) - { - var count = 0; - foreach (var x in info.Actual) - { - if (x == Base) - count++; - else - break; - } - - var moves = info.Moves; - if (count == -1) - return moves[^1] != 0; - - var baseMoves = info.Learnset.GetBaseEggMoves(info.Level); - if (baseMoves.Length < count) - return false; - if (moves[^1] == 0 && count != baseMoves.Length) - return false; - - for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--) - { - var move = moves[i]; - var expect = baseMoves[b]; - if (expect != move) - return false; - } - - // A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it. - if (baseMoves.Length == count) - return true; - - for (int i = count; i < info.Actual.Length; i++) - { - var isBase = (info.Possible[i] & (1 << (int)Base)) != 0; - if (!isBase) - continue; - - var baseIndex = baseMoves.IndexOf(moves[i]); - var min = moves.Length - baseMoves.Length + baseIndex; - if (i < min + count) - return false; - } - - return true; - } - - private static void MarkMovesForOrigin(in BreedInfo value, ICollection eggMoves, int count, bool inheritLevelUp, PersonalInfo info, GameVersion gameVersion) - { - var possible = value.Possible; - var learn = value.Learnset; - var baseEgg = value.Learnset.GetBaseEggMoves(value.Level); - var tm = info.TMHM; - var tmlist = Legal.TM_4.AsSpan(0, 92); - var hmlist = (gameVersion is HG or SS ? Legal.HM_HGSS : Legal.HM_DPPt).AsSpan(); - - var moves = value.Moves; - for (int i = 0; i < count; i++) - { - var move = moves[i]; - - if (baseEgg.IndexOf(move) != -1) - possible[i] |= 1 << (int)Base; - - if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1) - possible[i] |= 1 << (int)ParentLevelUp; - - if (eggMoves.Contains(move)) - possible[i] |= 1 << (int)FatherEgg; - - var tmIndex = tmlist.IndexOf(move); - if (tmIndex != -1 && tm[tmIndex]) - possible[i] |= 1 << (int)FatherTM; - - var hmIndex = hmlist.IndexOf(move); - if (hmIndex != -1 && tm[hmIndex + 92]) - possible[i] |= 1 << (int)FatherTM; + valueActual[i] = (EggSource34)j; + break; } } } + + private static bool RecurseMovesForOrigin(in BreedInfo info, int start, EggSource34 type = Max - 1) + { + int i = start; + do + { + var unpeel = type - 1; + if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel)) + return true; + + var permit = info.Possible[i]; + if ((permit & (1 << (int)type)) == 0) + return false; + + info.Actual[i] = type; + } while (--i >= 0); + + return VerifyBaseMoves(info); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool VerifyBaseMoves(in BreedInfo info) + { + var count = 0; + foreach (var x in info.Actual) + { + if (x == Base) + count++; + else + break; + } + + var moves = info.Moves; + if (count == -1) + return moves[^1] != 0; + + var baseMoves = info.Learnset.GetBaseEggMoves(info.Level); + if (baseMoves.Length < count) + return false; + if (moves[^1] == 0 && count != baseMoves.Length) + return false; + + for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--) + { + var move = moves[i]; + var expect = baseMoves[b]; + if (expect != move) + return false; + } + + // A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it. + if (baseMoves.Length == count) + return true; + + for (int i = count; i < info.Actual.Length; i++) + { + var isBase = (info.Possible[i] & (1 << (int)Base)) != 0; + if (!isBase) + continue; + + var baseIndex = baseMoves.IndexOf(moves[i]); + var min = moves.Length - baseMoves.Length + baseIndex; + if (i < min + count) + return false; + } + + return true; + } + + private static void MarkMovesForOrigin(in BreedInfo value, ICollection eggMoves, int count, bool inheritLevelUp, PersonalInfo info, GameVersion gameVersion) + { + var possible = value.Possible; + var learn = value.Learnset; + var baseEgg = value.Learnset.GetBaseEggMoves(value.Level); + var tm = info.TMHM; + var tmlist = Legal.TM_4.AsSpan(0, 92); + var hmlist = (gameVersion is HG or SS ? Legal.HM_HGSS : Legal.HM_DPPt).AsSpan(); + + var moves = value.Moves; + for (int i = 0; i < count; i++) + { + var move = moves[i]; + + if (baseEgg.IndexOf(move) != -1) + possible[i] |= 1 << (int)Base; + + if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1) + possible[i] |= 1 << (int)ParentLevelUp; + + if (eggMoves.Contains(move)) + possible[i] |= 1 << (int)FatherEgg; + + var tmIndex = tmlist.IndexOf(move); + if (tmIndex != -1 && tm[tmIndex]) + possible[i] |= 1 << (int)FatherTM; + + var hmIndex = hmlist.IndexOf(move); + if (hmIndex != -1 && tm[hmIndex + 92]) + possible[i] |= 1 << (int)FatherTM; + } + } } diff --git a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed5.cs b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed5.cs index 96eb16a87..8b2f95768 100644 --- a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed5.cs +++ b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed5.cs @@ -3,169 +3,168 @@ using System.Runtime.CompilerServices; using static PKHeX.Core.EggSource5; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Inheritance logic for Generation 5. +/// +/// Refer to for inheritance ordering. +public static class MoveBreed5 { - /// - /// Inheritance logic for Generation 5. - /// - /// Refer to for inheritance ordering. - public static class MoveBreed5 + private const int level = 1; + + public static EggSource5[] Validate(int species, GameVersion version, ReadOnlySpan moves, out bool valid) { - private const int level = 1; - - public static EggSource5[] Validate(int species, GameVersion version, ReadOnlySpan moves, out bool valid) + var count = moves.IndexOf(0); + if (count == 0) { - var count = moves.IndexOf(0); - if (count == 0) - { - valid = false; // empty moveset - return Array.Empty(); - } - if (count == -1) - count = moves.Length; + valid = false; // empty moveset + return Array.Empty(); + } + if (count == -1) + count = moves.Length; - var learn = GameData.GetLearnsets(version); - var table = GameData.GetPersonal(version); - var learnset = learn[species]; - var pi = table[species]; - var egg = Legal.EggMovesBW[species].Moves; + var learn = GameData.GetLearnsets(version); + var table = GameData.GetPersonal(version); + var learnset = learn[species]; + var pi = table[species]; + var egg = Legal.EggMovesBW[species].Moves; - var actual = new EggSource5[count]; - Span possible = stackalloc byte[count]; - var value = new BreedInfo(actual, possible, learnset, moves, level); - if (species is (int)Species.Pichu && moves[count - 1] is (int)Move.VoltTackle) - actual[--count] = VoltTackle; + var actual = new EggSource5[count]; + Span possible = stackalloc byte[count]; + var value = new BreedInfo(actual, possible, learnset, moves, level); + if (species is (int)Species.Pichu && moves[count - 1] is (int)Move.VoltTackle) + actual[--count] = VoltTackle; - if (count == 0) - { - valid = VerifyBaseMoves(value); - } - else - { - bool inherit = Breeding.GetCanInheritMoves(species); - MarkMovesForOrigin(value, egg, count, inherit, pi); - valid = RecurseMovesForOrigin(value, count - 1); - } - - if (!valid) - CleanResult(actual, possible); - return value.Actual; + if (count == 0) + { + valid = VerifyBaseMoves(value); + } + else + { + bool inherit = Breeding.GetCanInheritMoves(species); + MarkMovesForOrigin(value, egg, count, inherit, pi); + valid = RecurseMovesForOrigin(value, count - 1); } - private static void CleanResult(EggSource5[] valueActual, Span valuePossible) + if (!valid) + CleanResult(actual, possible); + return value.Actual; + } + + private static void CleanResult(EggSource5[] valueActual, Span valuePossible) + { + for (int i = 0; i < valueActual.Length; i++) { - for (int i = 0; i < valueActual.Length; i++) + if (valueActual[i] != 0) + continue; + var poss = valuePossible[i]; + if (poss == 0) + continue; + + for (int j = 0; j < (int)Max; j++) { - if (valueActual[i] != 0) + if ((poss & (1 << j)) == 0) continue; - var poss = valuePossible[i]; - if (poss == 0) - continue; - - for (int j = 0; j < (int)Max; j++) - { - if ((poss & (1 << j)) == 0) - continue; - valueActual[i] = (EggSource5)j; - break; - } - } - } - - private static bool RecurseMovesForOrigin(in BreedInfo info, int start, EggSource5 type = Max - 1) - { - int i = start; - do - { - var unpeel = type - 1; - if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel)) - return true; - - var permit = info.Possible[i]; - if ((permit & (1 << (int)type)) == 0) - return false; - - info.Actual[i] = type; - } while (--i >= 0); - - return VerifyBaseMoves(info); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool VerifyBaseMoves(in BreedInfo info) - { - var count = 0; - foreach (var x in info.Actual) - { - if (x == Base) - count++; - else - break; - } - - var moves = info.Moves; - if (count == -1) - return moves[^1] != 0; - - var baseMoves = info.Learnset.GetBaseEggMoves(info.Level); - if (baseMoves.Length < count) - return false; - if (moves[^1] == 0 && count != baseMoves.Length) - return false; - - for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--) - { - var move = moves[i]; - var expect = baseMoves[b]; - if (expect != move) - return false; - } - - // A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it. - if (baseMoves.Length == count) - return true; - - for (int i = count; i < info.Actual.Length; i++) - { - var isBase = (info.Possible[i] & (1 << (int)Base)) != 0; - if (!isBase) - continue; - - var baseIndex = baseMoves.IndexOf(moves[i]); - var min = moves.Length - baseMoves.Length + baseIndex; - if (i < min + count) - return false; - } - - return true; - } - - private static void MarkMovesForOrigin(in BreedInfo value, ICollection eggMoves, int count, bool inheritLevelUp, PersonalInfo info) - { - var possible = value.Possible; - var learn = value.Learnset; - var baseEgg = value.Learnset.GetBaseEggMoves(value.Level); - var tm = info.TMHM; - var tmlist = Legal.TMHM_BW.AsSpan(0, 95); // actually 96, but TM96 is unavailable (Snarl - Lock Capsule) - - var moves = value.Moves; - for (int i = 0; i < count; i++) - { - var move = moves[i]; - - if (baseEgg.IndexOf(move) != -1) - possible[i] |= 1 << (int)Base; - - if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1) - possible[i] |= 1 << (int)ParentLevelUp; - - if (eggMoves.Contains(move)) - possible[i] |= 1 << (int)FatherEgg; - - var tmIndex = tmlist.IndexOf(move); - if (tmIndex != -1 && tm[tmIndex]) - possible[i] |= 1 << (int)FatherTM; + valueActual[i] = (EggSource5)j; + break; } } } + + private static bool RecurseMovesForOrigin(in BreedInfo info, int start, EggSource5 type = Max - 1) + { + int i = start; + do + { + var unpeel = type - 1; + if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel)) + return true; + + var permit = info.Possible[i]; + if ((permit & (1 << (int)type)) == 0) + return false; + + info.Actual[i] = type; + } while (--i >= 0); + + return VerifyBaseMoves(info); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool VerifyBaseMoves(in BreedInfo info) + { + var count = 0; + foreach (var x in info.Actual) + { + if (x == Base) + count++; + else + break; + } + + var moves = info.Moves; + if (count == -1) + return moves[^1] != 0; + + var baseMoves = info.Learnset.GetBaseEggMoves(info.Level); + if (baseMoves.Length < count) + return false; + if (moves[^1] == 0 && count != baseMoves.Length) + return false; + + for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--) + { + var move = moves[i]; + var expect = baseMoves[b]; + if (expect != move) + return false; + } + + // A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it. + if (baseMoves.Length == count) + return true; + + for (int i = count; i < info.Actual.Length; i++) + { + var isBase = (info.Possible[i] & (1 << (int)Base)) != 0; + if (!isBase) + continue; + + var baseIndex = baseMoves.IndexOf(moves[i]); + var min = moves.Length - baseMoves.Length + baseIndex; + if (i < min + count) + return false; + } + + return true; + } + + private static void MarkMovesForOrigin(in BreedInfo value, ICollection eggMoves, int count, bool inheritLevelUp, PersonalInfo info) + { + var possible = value.Possible; + var learn = value.Learnset; + var baseEgg = value.Learnset.GetBaseEggMoves(value.Level); + var tm = info.TMHM; + var tmlist = Legal.TMHM_BW.AsSpan(0, 95); // actually 96, but TM96 is unavailable (Snarl - Lock Capsule) + + var moves = value.Moves; + for (int i = 0; i < count; i++) + { + var move = moves[i]; + + if (baseEgg.IndexOf(move) != -1) + possible[i] |= 1 << (int)Base; + + if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1) + possible[i] |= 1 << (int)ParentLevelUp; + + if (eggMoves.Contains(move)) + possible[i] |= 1 << (int)FatherEgg; + + var tmIndex = tmlist.IndexOf(move); + if (tmIndex != -1 && tm[tmIndex]) + possible[i] |= 1 << (int)FatherTM; + } + } } diff --git a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed6.cs b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed6.cs index 69582cc99..8871cfdad 100644 --- a/PKHeX.Core/Legality/Moves/Breeding/MoveBreed6.cs +++ b/PKHeX.Core/Legality/Moves/Breeding/MoveBreed6.cs @@ -3,163 +3,162 @@ using System.Runtime.CompilerServices; using static PKHeX.Core.EggSource6; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Inheritance logic for Generations 6+. +/// +/// Refer to for inheritance ordering. +public static class MoveBreed6 { - /// - /// Inheritance logic for Generations 6+. - /// - /// Refer to for inheritance ordering. - public static class MoveBreed6 + private const int level = 1; + + public static EggSource6[] Validate(int generation, int species, int form, GameVersion version, ReadOnlySpan moves, out bool valid) { - private const int level = 1; - - public static EggSource6[] Validate(int generation, int species, int form, GameVersion version, ReadOnlySpan moves, out bool valid) + var count = moves.IndexOf(0); + if (count == 0) { - var count = moves.IndexOf(0); - if (count == 0) - { - valid = false; // empty moveset - return Array.Empty(); - } - if (count == -1) - count = moves.Length; + valid = false; // empty moveset + return Array.Empty(); + } + if (count == -1) + count = moves.Length; - var learn = GameData.GetLearnsets(version); - var table = GameData.GetPersonal(version); - var index = table.GetFormIndex(species, form); - var learnset = learn[index]; - var egg = MoveEgg.GetEggMoves(generation, species, form, version); + var learn = GameData.GetLearnsets(version); + var table = GameData.GetPersonal(version); + var index = table.GetFormIndex(species, form); + var learnset = learn[index]; + var egg = MoveEgg.GetEggMoves(generation, species, form, version); - var actual = new EggSource6[count]; - Span possible = stackalloc byte[count]; - var value = new BreedInfo(actual, possible, learnset, moves, level); - if (species is (int)Species.Pichu && moves[count - 1] is (int)Move.VoltTackle) - actual[--count] = VoltTackle; + var actual = new EggSource6[count]; + Span possible = stackalloc byte[count]; + var value = new BreedInfo(actual, possible, learnset, moves, level); + if (species is (int)Species.Pichu && moves[count - 1] is (int)Move.VoltTackle) + actual[--count] = VoltTackle; - if (count == 0) - { - valid = VerifyBaseMoves(value); - } - else - { - bool inherit = Breeding.GetCanInheritMoves(species); - MarkMovesForOrigin(value, egg, count, inherit); - valid = RecurseMovesForOrigin(value, count - 1); - } - - if (!valid) - CleanResult(actual, possible); - return value.Actual; + if (count == 0) + { + valid = VerifyBaseMoves(value); + } + else + { + bool inherit = Breeding.GetCanInheritMoves(species); + MarkMovesForOrigin(value, egg, count, inherit); + valid = RecurseMovesForOrigin(value, count - 1); } - private static void CleanResult(EggSource6[] valueActual, Span valuePossible) + if (!valid) + CleanResult(actual, possible); + return value.Actual; + } + + private static void CleanResult(EggSource6[] valueActual, Span valuePossible) + { + for (int i = 0; i < valueActual.Length; i++) { - for (int i = 0; i < valueActual.Length; i++) + if (valueActual[i] != 0) + continue; + var poss = valuePossible[i]; + if (poss == 0) + continue; + + for (int j = 0; j < (int)Max; j++) { - if (valueActual[i] != 0) + if ((poss & (1 << j)) == 0) continue; - var poss = valuePossible[i]; - if (poss == 0) - continue; - - for (int j = 0; j < (int)Max; j++) - { - if ((poss & (1 << j)) == 0) - continue; - valueActual[i] = (EggSource6)j; - break; - } - } - } - - private static bool RecurseMovesForOrigin(in BreedInfo info, int start, EggSource6 type = Max - 1) - { - int i = start; - do - { - var unpeel = type - 1; - if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel)) - return true; - - var permit = info.Possible[i]; - if ((permit & (1 << (int)type)) == 0) - return false; - - info.Actual[i] = type; - } while (--i >= 0); - - return VerifyBaseMoves(info); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool VerifyBaseMoves(in BreedInfo info) - { - var count = 0; - foreach (var x in info.Actual) - { - if (x == Base) - count++; - else - break; - } - - var moves = info.Moves; - if (count == -1) - return moves[^1] != 0; - - var baseMoves = info.Learnset.GetBaseEggMoves(info.Level); - if (baseMoves.Length < count) - return false; - if (moves[^1] == 0 && count != baseMoves.Length) - return false; - - for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--) - { - var move = moves[i]; - var expect = baseMoves[b]; - if (expect != move) - return false; - } - - // A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it. - if (baseMoves.Length == count) - return true; - - for (int i = count; i < info.Actual.Length; i++) - { - var isBase = (info.Possible[i] & (1 << (int)Base)) != 0; - if (!isBase) - continue; - - var baseIndex = baseMoves.IndexOf(info.Moves[i]); - var min = info.Moves.Length - baseMoves.Length + baseIndex; - if (i < min + count) - return false; - } - - return true; - } - - private static void MarkMovesForOrigin(in BreedInfo value, ICollection eggMoves, int count, bool inheritLevelUp) - { - var possible = value.Possible; - var learn = value.Learnset; - var baseEgg = value.Learnset.GetBaseEggMoves(value.Level); - - var moves = value.Moves; - for (int i = 0; i < count; i++) - { - var move = moves[i]; - - if (baseEgg.IndexOf(move) != -1) - possible[i] |= 1 << (int)Base; - - if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1) - possible[i] |= 1 << (int)ParentLevelUp; - - if (eggMoves.Contains(move)) - possible[i] |= 1 << (int)ParentEgg; + valueActual[i] = (EggSource6)j; + break; } } } + + private static bool RecurseMovesForOrigin(in BreedInfo info, int start, EggSource6 type = Max - 1) + { + int i = start; + do + { + var unpeel = type - 1; + if (unpeel != 0 && RecurseMovesForOrigin(info, i, unpeel)) + return true; + + var permit = info.Possible[i]; + if ((permit & (1 << (int)type)) == 0) + return false; + + info.Actual[i] = type; + } while (--i >= 0); + + return VerifyBaseMoves(info); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool VerifyBaseMoves(in BreedInfo info) + { + var count = 0; + foreach (var x in info.Actual) + { + if (x == Base) + count++; + else + break; + } + + var moves = info.Moves; + if (count == -1) + return moves[^1] != 0; + + var baseMoves = info.Learnset.GetBaseEggMoves(info.Level); + if (baseMoves.Length < count) + return false; + if (moves[^1] == 0 && count != baseMoves.Length) + return false; + + for (int i = count - 1, b = baseMoves.Length - 1; i >= 0; i--, b--) + { + var move = moves[i]; + var expect = baseMoves[b]; + if (expect != move) + return false; + } + + // A low-index base egg move may be nudged out, but can only reappear if sufficient non-base moves are before it. + if (baseMoves.Length == count) + return true; + + for (int i = count; i < info.Actual.Length; i++) + { + var isBase = (info.Possible[i] & (1 << (int)Base)) != 0; + if (!isBase) + continue; + + var baseIndex = baseMoves.IndexOf(info.Moves[i]); + var min = info.Moves.Length - baseMoves.Length + baseIndex; + if (i < min + count) + return false; + } + + return true; + } + + private static void MarkMovesForOrigin(in BreedInfo value, ICollection eggMoves, int count, bool inheritLevelUp) + { + var possible = value.Possible; + var learn = value.Learnset; + var baseEgg = value.Learnset.GetBaseEggMoves(value.Level); + + var moves = value.Moves; + for (int i = 0; i < count; i++) + { + var move = moves[i]; + + if (baseEgg.IndexOf(move) != -1) + possible[i] |= 1 << (int)Base; + + if (inheritLevelUp && learn.GetLevelLearnMove(move) != -1) + possible[i] |= 1 << (int)ParentLevelUp; + + if (eggMoves.Contains(move)) + possible[i] |= 1 << (int)ParentEgg; + } + } } diff --git a/PKHeX.Core/Legality/Moves/GameData.cs b/PKHeX.Core/Legality/Moves/GameData.cs index d2ec22386..d8f5eab95 100644 --- a/PKHeX.Core/Legality/Moves/GameData.cs +++ b/PKHeX.Core/Legality/Moves/GameData.cs @@ -1,111 +1,110 @@ using System; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class GameData { - public static class GameData + public static Learnset[] GetLearnsets(GameVersion game) => Learnsets(game); + public static PersonalTable GetPersonal(GameVersion game) => Personal(game); + + public static Learnset GetLearnset(GameVersion game, int species, int form) { - public static Learnset[] GetLearnsets(GameVersion game) => Learnsets(game); - public static PersonalTable GetPersonal(GameVersion game) => Personal(game); - - public static Learnset GetLearnset(GameVersion game, int species, int form) - { - var pt = Personal(game); - var index = pt.GetFormIndex(species, form); - var sets = Learnsets(game); - return sets[index]; - } - - private static Learnset[] Learnsets(GameVersion game) => game switch - { - RD or GN or BU or RB => Legal.LevelUpRB, - YW or RBY => Legal.LevelUpY, - GD or SI or GS => Legal.LevelUpGS, - C or GSC => Legal.LevelUpC, - - R or S or RS or RSE => Legal.LevelUpRS, - E or COLO or XD or FRLG or CXD => Legal.LevelUpE, - FR => Legal.LevelUpFR, - LG => Legal.LevelUpLG, - - D or P or DP => Legal.LevelUpDP, - Pt or DPPt => Legal.LevelUpPt, - HG or SS or HGSS => Legal.LevelUpHGSS, - - B or W or BW => Legal.LevelUpBW, - B2 or W2 or B2W2 => Legal.LevelUpB2W2, - - X or Y or XY => Legal.LevelUpXY, - AS or OR or ORAS => Legal.LevelUpAO, - - SN or MN or SM => Legal.LevelUpSM, - US or UM or USUM => Legal.LevelUpUSUM, - GO or GP or GE or GG => Legal.LevelUpGG, - - SW or SH or SWSH => Legal.LevelUpSWSH, - BD or SP or BDSP => Legal.LevelUpBDSP, - PLA => Legal.LevelUpLA, - - Gen1 => Legal.LevelUpY, - Gen2 => Legal.LevelUpC, - Gen3 => Legal.LevelUpE, - Gen4 => Legal.LevelUpHGSS, - Gen5 => Legal.LevelUpB2W2, - Gen6 => Legal.LevelUpAO, - Gen7 => Legal.LevelUpSM, - Gen7b => Legal.LevelUpGG, - Gen8 => Legal.LevelUpSWSH, - - Stadium => Legal.LevelUpY, - Stadium2 => Legal.LevelUpGS, - - _ => throw new ArgumentOutOfRangeException(nameof(game), $"{game} is not a valid entry in the expression."), - }; - - private static PersonalTable Personal(GameVersion game) => game switch - { - RD or GN or BU or RB => PersonalTable.RB, - YW or RBY => PersonalTable.Y, - GD or SI or GS => PersonalTable.GS, - C or GSC => PersonalTable.C, - - R or S or RS or RSE => PersonalTable.RS, - E or COLO or XD or FRLG or CXD => PersonalTable.E, - FR => PersonalTable.FR, - LG => PersonalTable.LG, - - D or P or DP => PersonalTable.DP, - Pt or DPPt => PersonalTable.Pt, - HG or SS or HGSS => PersonalTable.HGSS, - - B or W or BW => PersonalTable.BW, - B2 or W2 or B2W2 => PersonalTable.B2W2, - - X or Y or XY => PersonalTable.XY, - AS or OR or ORAS => PersonalTable.AO, - - SN or MN or SM => PersonalTable.SM, - US or UM or USUM => PersonalTable.USUM, - GO or GP or GE or GG => PersonalTable.GG, - - SW or SH or SWSH => PersonalTable.SWSH, - BD or SP or BDSP => PersonalTable.BDSP, - PLA => PersonalTable.LA, - - Gen1 => PersonalTable.Y, - Gen2 => PersonalTable.C, - Gen3 => PersonalTable.E, - Gen4 => PersonalTable.HGSS, - Gen5 => PersonalTable.B2W2, - Gen6 => PersonalTable.AO, - Gen7 => PersonalTable.USUM, - Gen7b => PersonalTable.GG, - Gen8 => PersonalTable.SWSH, - - Stadium => PersonalTable.Y, - Stadium2 => PersonalTable.GS, - - _ => throw new ArgumentOutOfRangeException(nameof(game), $"{game} is not a valid entry in the expression."), - }; + var pt = Personal(game); + var index = pt.GetFormIndex(species, form); + var sets = Learnsets(game); + return sets[index]; } + + private static Learnset[] Learnsets(GameVersion game) => game switch + { + RD or GN or BU or RB => Legal.LevelUpRB, + YW or RBY => Legal.LevelUpY, + GD or SI or GS => Legal.LevelUpGS, + C or GSC => Legal.LevelUpC, + + R or S or RS or RSE => Legal.LevelUpRS, + E or COLO or XD or FRLG or CXD => Legal.LevelUpE, + FR => Legal.LevelUpFR, + LG => Legal.LevelUpLG, + + D or P or DP => Legal.LevelUpDP, + Pt or DPPt => Legal.LevelUpPt, + HG or SS or HGSS => Legal.LevelUpHGSS, + + B or W or BW => Legal.LevelUpBW, + B2 or W2 or B2W2 => Legal.LevelUpB2W2, + + X or Y or XY => Legal.LevelUpXY, + AS or OR or ORAS => Legal.LevelUpAO, + + SN or MN or SM => Legal.LevelUpSM, + US or UM or USUM => Legal.LevelUpUSUM, + GO or GP or GE or GG => Legal.LevelUpGG, + + SW or SH or SWSH => Legal.LevelUpSWSH, + BD or SP or BDSP => Legal.LevelUpBDSP, + PLA => Legal.LevelUpLA, + + Gen1 => Legal.LevelUpY, + Gen2 => Legal.LevelUpC, + Gen3 => Legal.LevelUpE, + Gen4 => Legal.LevelUpHGSS, + Gen5 => Legal.LevelUpB2W2, + Gen6 => Legal.LevelUpAO, + Gen7 => Legal.LevelUpSM, + Gen7b => Legal.LevelUpGG, + Gen8 => Legal.LevelUpSWSH, + + Stadium => Legal.LevelUpY, + Stadium2 => Legal.LevelUpGS, + + _ => throw new ArgumentOutOfRangeException(nameof(game), $"{game} is not a valid entry in the expression."), + }; + + private static PersonalTable Personal(GameVersion game) => game switch + { + RD or GN or BU or RB => PersonalTable.RB, + YW or RBY => PersonalTable.Y, + GD or SI or GS => PersonalTable.GS, + C or GSC => PersonalTable.C, + + R or S or RS or RSE => PersonalTable.RS, + E or COLO or XD or FRLG or CXD => PersonalTable.E, + FR => PersonalTable.FR, + LG => PersonalTable.LG, + + D or P or DP => PersonalTable.DP, + Pt or DPPt => PersonalTable.Pt, + HG or SS or HGSS => PersonalTable.HGSS, + + B or W or BW => PersonalTable.BW, + B2 or W2 or B2W2 => PersonalTable.B2W2, + + X or Y or XY => PersonalTable.XY, + AS or OR or ORAS => PersonalTable.AO, + + SN or MN or SM => PersonalTable.SM, + US or UM or USUM => PersonalTable.USUM, + GO or GP or GE or GG => PersonalTable.GG, + + SW or SH or SWSH => PersonalTable.SWSH, + BD or SP or BDSP => PersonalTable.BDSP, + PLA => PersonalTable.LA, + + Gen1 => PersonalTable.Y, + Gen2 => PersonalTable.C, + Gen3 => PersonalTable.E, + Gen4 => PersonalTable.HGSS, + Gen5 => PersonalTable.B2W2, + Gen6 => PersonalTable.AO, + Gen7 => PersonalTable.USUM, + Gen7b => PersonalTable.GG, + Gen8 => PersonalTable.SWSH, + + Stadium => PersonalTable.Y, + Stadium2 => PersonalTable.GS, + + _ => throw new ArgumentOutOfRangeException(nameof(game), $"{game} is not a valid entry in the expression."), + }; } diff --git a/PKHeX.Core/Legality/Moves/LearnInfo.cs b/PKHeX.Core/Legality/Moves/LearnInfo.cs index 9d999fd35..f3f9f4c3c 100644 --- a/PKHeX.Core/Legality/Moves/LearnInfo.cs +++ b/PKHeX.Core/Legality/Moves/LearnInfo.cs @@ -1,23 +1,22 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal sealed class LearnInfo { - internal sealed class LearnInfo + public bool MixedGen12NonTradeback { get; set; } + public List Gen1Moves { get; } = new(); + public List Gen2PreevoMoves { get; } = new(); + public List EggMovesLearned { get; } = new(); + public List LevelUpEggMoves { get; } = new(); + public List EventEggMoves { get; } = new(); + + public readonly MoveParseSource Source; + public readonly bool IsGen2Pkm; + + public LearnInfo(PKM pk, MoveParseSource source) { - public bool MixedGen12NonTradeback { get; set; } - public List Gen1Moves { get; } = new(); - public List Gen2PreevoMoves { get; } = new(); - public List EggMovesLearned { get; } = new(); - public List LevelUpEggMoves { get; } = new(); - public List EventEggMoves { get; } = new(); - - public readonly MoveParseSource Source; - public readonly bool IsGen2Pkm; - - public LearnInfo(PKM pkm, MoveParseSource source) - { - IsGen2Pkm = pkm.Format == 2 || pkm.VC2; - Source = source; - } + IsGen2Pkm = pk.Format == 2 || pk.VC2; + Source = source; } } diff --git a/PKHeX.Core/Legality/Moves/LearnLookup.cs b/PKHeX.Core/Legality/Moves/LearnLookup.cs index 82a59b4ac..f27f032ad 100644 --- a/PKHeX.Core/Legality/Moves/LearnLookup.cs +++ b/PKHeX.Core/Legality/Moves/LearnLookup.cs @@ -3,114 +3,113 @@ using System.Linq; using static PKHeX.Core.Legal; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Level-Up Lookup object +/// +public sealed class LearnLookup { - /// - /// Level-Up Lookup object - /// - public sealed class LearnLookup + private readonly GameVersion Version; + private readonly PersonalTable Table; + private readonly Learnset[] Learn; + + public LearnLookup(PersonalTable table, Learnset[] learn, GameVersion version) { - private readonly GameVersion Version; - private readonly PersonalTable Table; - private readonly Learnset[] Learn; + Version = version; + Table = table; + Learn = learn; + } - public LearnLookup(PersonalTable table, Learnset[] learn, GameVersion version) - { - Version = version; - Table = table; - Learn = learn; - } + public List AddMovesIndex(List moves, int index, int max, int min) + { + if (index <= 0) + return moves; + return Learn[index].AddMoves(moves, max, min); + } - public List AddMovesIndex(List moves, int index, int max, int min) - { - if (index <= 0) - return moves; - return Learn[index].AddMoves(moves, max, min); - } + public List AddMoves(List moves, int species, int form, int max, int min = 0) + { + int index = Table.GetFormIndex(species, form); + return AddMovesIndex(moves, index, max, min); + } - public List AddMoves(List moves, int species, int form, int max, int min = 0) - { - int index = Table.GetFormIndex(species, form); - return AddMovesIndex(moves, index, max, min); - } + public List AddMoves1(List moves, int species, int form, int max, int min) + { + int index = Table.GetFormIndex(species, form); + return AddMovesIndex1(moves, index, max, min); + } - public List AddMoves1(List moves, int species, int form, int max, int min) - { - int index = Table.GetFormIndex(species, form); - return AddMovesIndex1(moves, index, max, min); - } + public List AddMovesIndex1(List moves, int index, int max, int min) + { + if (min == 1) + moves.AddRange(((PersonalInfoG1)Table[index]).Moves); + return AddMovesIndex(moves, index, max, min); + } - public List AddMovesIndex1(List moves, int index, int max, int min) - { - if (min == 1) - moves.AddRange(((PersonalInfoG1)Table[index]).Moves); - return AddMovesIndex(moves, index, max, min); - } + public List GetMoves(int species, int form, int min, int max) + { + int index = Table.GetFormIndex(species, form); + return Learn[index].GetMoveList(max, min); + } - public List GetMoves(int species, int form, int min, int max) - { - int index = Table.GetFormIndex(species, form); - return Learn[index].GetMoveList(max, min); - } - - public LearnVersion GetIsLevelUp(int species, int form, int move) - { - int index = Table.GetFormIndex(species, form); - if (index <= 0) - return LearnNONE; - var lv = Learn[index].GetLevelLearnMove(move); - if (lv >= 0) - return new LearnVersion(lv, Version); + public LearnVersion GetIsLevelUp(int species, int form, int move) + { + int index = Table.GetFormIndex(species, form); + if (index <= 0) return LearnNONE; - } + var lv = Learn[index].GetLevelLearnMove(move); + if (lv >= 0) + return new LearnVersion(lv, Version); + return LearnNONE; + } - public LearnVersion GetIsLevelUp(int species, int form, int move, int max) - { - int index = Table.GetFormIndex(species, form); - if (index <= 0) - return LearnNONE; - var lv = Learn[index].GetLevelLearnMove(move); - if (lv >= 0 && lv <= max) - return new LearnVersion(lv, Version); + public LearnVersion GetIsLevelUp(int species, int form, int move, int max) + { + int index = Table.GetFormIndex(species, form); + if (index <= 0) return LearnNONE; - } + var lv = Learn[index].GetLevelLearnMove(move); + if (lv >= 0 && lv <= max) + return new LearnVersion(lv, Version); + return LearnNONE; + } - public LearnVersion GetIsLevelUpMin(int species, int move, int max, int min, int form) - { - int index = Table.GetFormIndex(species, form); - if (index <= 0) - return LearnNONE; - var lv = Learn[index].GetLevelLearnMove(move, min); - if (lv >= min && lv <= max) - return new LearnVersion(lv, Version); + public LearnVersion GetIsLevelUpMin(int species, int move, int max, int min, int form) + { + int index = Table.GetFormIndex(species, form); + if (index <= 0) return LearnNONE; - } + var lv = Learn[index].GetLevelLearnMove(move, min); + if (lv >= min && lv <= max) + return new LearnVersion(lv, Version); + return LearnNONE; + } - public LearnVersion GetIsLevelUpG1(int species, int form, int move, int max, int min = 0) - { - int index = PersonalTable.RB.GetFormIndex(species, form); - if (index == 0) - return LearnNONE; - - // No Move re-learner -- have to be learned on level-up - var lv = Learn[index].GetLevelLearnMove(move, min); - if (lv >= 0 && lv <= max) - return new LearnVersion(lv, Version); - - if (min >= 1) - return LearnNONE; - - var pi = (PersonalInfoG1)Table[index]; - var i = Array.IndexOf(pi.Moves, move); - - // Check if move was not overwritten by higher level moves before it was encountered - if (i >= 0) - { - var unique = Learn[index].GetUniqueMovesLearned(pi.Moves.Where(z => z != 0), max); - if (unique.Count - i <= 4) - return new LearnVersion(0, Version); - } + public LearnVersion GetIsLevelUpG1(int species, int form, int move, int max, int min = 0) + { + int index = PersonalTable.RB.GetFormIndex(species, form); + if (index == 0) return LearnNONE; + + // No Move re-learner -- have to be learned on level-up + var lv = Learn[index].GetLevelLearnMove(move, min); + if (lv >= 0 && lv <= max) + return new LearnVersion(lv, Version); + + if (min >= 1) + return LearnNONE; + + var pi = (PersonalInfoG1)Table[index]; + var i = Array.IndexOf(pi.Moves, move); + + // Check if move was not overwritten by higher level moves before it was encountered + if (i >= 0) + { + var unique = Learn[index].GetUniqueMovesLearned(pi.Moves.Where(z => z != 0), max); + if (unique.Count - i <= 4) + return new LearnVersion(0, Version); } + return LearnNONE; } } diff --git a/PKHeX.Core/Legality/Moves/MoveEgg.cs b/PKHeX.Core/Legality/Moves/MoveEgg.cs index 91dc3a352..bd2068370 100644 --- a/PKHeX.Core/Legality/Moves/MoveEgg.cs +++ b/PKHeX.Core/Legality/Moves/MoveEgg.cs @@ -3,120 +3,119 @@ using static PKHeX.Core.Legal; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class MoveEgg { - public static class MoveEgg + public static int[] GetEggMoves(PersonalInfo pi, int species, int form, GameVersion version, int generation) { - public static int[] GetEggMoves(PersonalInfo pi, int species, int form, GameVersion version, int generation) + if (species > GetMaxSpeciesOrigin(generation, version)) + return Array.Empty(); + + if (pi.Genderless && !FixedGenderFromBiGender.Contains(species)) + return Array.Empty(); + + if (!Breeding.CanGameGenerateEggs(version)) + return Array.Empty(); + + return GetEggMoves(generation, species, form, version); + } + + public static int[] GetEggMoves(int generation, int species, int form, GameVersion version) => generation switch + { + 1 or 2 => GetMovesSafe(version == C ? EggMovesC : EggMovesGS, species), + 3 => GetMovesSafe(EggMovesRS, species), + 4 when version is D or P or Pt => GetMovesSafe(EggMovesDPPt, species), + 4 when version is HG or SS => GetMovesSafe(EggMovesHGSS, species), + 5 => GetMovesSafe(EggMovesBW, species), + + 6 when version is X or Y => GetMovesSafe(EggMovesXY, species), + 6 when version is OR or AS => GetMovesSafe(EggMovesAO, species), + + 7 when version is SN or MN => GetFormEggMoves(species, form, EggMovesSM), + 7 when version is US or UM => GetFormEggMoves(species, form, EggMovesUSUM), + 8 when version is SW or SH => GetFormEggMoves(species, form, EggMovesSWSH), + 8 when version is BD or SP => GetMovesSafe(EggMovesBDSP, species), + _ => Array.Empty(), + }; + + private static int[] GetMovesSafe(IReadOnlyList moves, int species) where T : EggMoves + { + if ((uint)species >= moves.Count) + return Array.Empty(); + return moves[species].Moves; + } + + private static int[] GetFormEggMoves(int species, int form, IReadOnlyList table) + { + if ((uint)species >= table.Count) + return Array.Empty(); + + var entry = table[species]; + if (form <= 0 || entry.FormTableIndex <= species) + return entry.Moves; + + // Sanity check form in the event it is out of range. + var baseIndex = entry.FormTableIndex; + var index = baseIndex + form - 1; + if ((uint)index >= table.Count) + return Array.Empty(); + entry = table[index]; + if (entry.FormTableIndex != baseIndex) + return Array.Empty(); + + return entry.Moves; + } + + internal static int[] GetRelearnLVLMoves(PKM pk, int species, int form, int lvl, GameVersion version = Any) + { + if (version == Any) + version = (GameVersion)pk.Version; + // A pk can only have levelup relearn moves from the game it originated on + // eg Plusle/Minun have Charm/Fake Tears (respectively) only in OR/AS, not X/Y + return version switch { - if (species > GetMaxSpeciesOrigin(generation, version)) - return Array.Empty(); - - if (pi.Genderless && !FixedGenderFromBiGender.Contains(species)) - return Array.Empty(); - - if (!Breeding.CanGameGenerateEggs(version)) - return Array.Empty(); - - return GetEggMoves(generation, species, form, version); - } - - public static int[] GetEggMoves(int generation, int species, int form, GameVersion version) => generation switch - { - 1 or 2 => GetMovesSafe(version == C ? EggMovesC : EggMovesGS, species), - 3 => GetMovesSafe(EggMovesRS, species), - 4 when version is D or P or Pt => GetMovesSafe(EggMovesDPPt, species), - 4 when version is HG or SS => GetMovesSafe(EggMovesHGSS, species), - 5 => GetMovesSafe(EggMovesBW, species), - - 6 when version is X or Y => GetMovesSafe(EggMovesXY, species), - 6 when version is OR or AS => GetMovesSafe(EggMovesAO, species), - - 7 when version is SN or MN => GetFormEggMoves(species, form, EggMovesSM), - 7 when version is US or UM => GetFormEggMoves(species, form, EggMovesUSUM), - 8 when version is SW or SH => GetFormEggMoves(species, form, EggMovesSWSH), - 8 when version is BD or SP => GetMovesSafe(EggMovesBDSP, species), + X or Y => getMoves(LevelUpXY, PersonalTable.XY), + OR or AS => getMoves(LevelUpAO, PersonalTable.AO), + SN or MN when species <= MaxSpeciesID_7 => getMoves(LevelUpSM, PersonalTable.SM), + US or UM => getMoves(LevelUpUSUM, PersonalTable.USUM), + SW or SH => getMoves(LevelUpSWSH, PersonalTable.SWSH), + BD or SP => getMoves(LevelUpBDSP, PersonalTable.BDSP), + PLA => getMoves(LevelUpLA, PersonalTable.LA), _ => Array.Empty(), }; - private static int[] GetMovesSafe(IReadOnlyList moves, int species) where T : EggMoves - { - if ((uint)species >= moves.Count) - return Array.Empty(); - return moves[species].Moves; - } + int[] getMoves(IReadOnlyList moves, PersonalTable table) => moves[table.GetFormIndex(species, form)].GetMoves(lvl); + } - private static int[] GetFormEggMoves(int species, int form, IReadOnlyList table) - { - if ((uint)species >= table.Count) - return Array.Empty(); - - var entry = table[species]; - if (form <= 0 || entry.FormTableIndex <= species) - return entry.Moves; - - // Sanity check form in the event it is out of range. - var baseIndex = entry.FormTableIndex; - var index = baseIndex + form - 1; - if ((uint)index >= table.Count) - return Array.Empty(); - entry = table[index]; - if (entry.FormTableIndex != baseIndex) - return Array.Empty(); - - return entry.Moves; - } - - internal static int[] GetRelearnLVLMoves(PKM pkm, int species, int form, int lvl, GameVersion version = Any) - { - if (version == Any) - version = (GameVersion)pkm.Version; - // A pkm can only have levelup relearn moves from the game it originated on - // eg Plusle/Minun have Charm/Fake Tears (respectively) only in OR/AS, not X/Y - return version switch - { - X or Y => getMoves(LevelUpXY, PersonalTable.XY), - OR or AS => getMoves(LevelUpAO, PersonalTable.AO), - SN or MN when species <= MaxSpeciesID_7 => getMoves(LevelUpSM, PersonalTable.SM), - US or UM => getMoves(LevelUpUSUM, PersonalTable.USUM), - SW or SH => getMoves(LevelUpSWSH, PersonalTable.SWSH), - BD or SP => getMoves(LevelUpBDSP, PersonalTable.BDSP), - PLA => getMoves(LevelUpLA, PersonalTable.LA), - _ => Array.Empty(), - }; - - int[] getMoves(IReadOnlyList moves, PersonalTable table) => moves[table.GetFormIndex(species, form)].GetMoves(lvl); - } - - public static bool GetIsSharedEggMove(PKM pkm, int gen, int move) - { - if (gen < 8 || pkm.IsEgg) - return false; - var egg = GetSharedEggMoves(pkm, gen); - return Array.IndexOf(egg, move) >= 0; - } - - public static int[] GetSharedEggMoves(PKM pkm, int gen) - { - if (gen < 8 || pkm.IsEgg) - return Array.Empty(); - - if (pkm is PB8 pb) - { - var entry = (PersonalInfoBDSP)pb.PersonalInfo; - var baseSpecies = entry.HatchSpecies; - var baseForm = entry.HatchFormIndex; - return GetEggMoves(8, baseSpecies, baseForm, BD); - } - if (pkm is PK8 pk) - { - var entry = (PersonalInfoSWSH)pk.PersonalInfo; - var baseSpecies = entry.HatchSpecies; - var baseForm = entry.HatchFormIndexEverstone; - return GetEggMoves(8, baseSpecies, baseForm, SW); - } + public static bool GetIsSharedEggMove(PKM pk, int gen, int move) + { + if (gen < 8 || pk.IsEgg) + return false; + var egg = GetSharedEggMoves(pk, gen); + return Array.IndexOf(egg, move) >= 0; + } + public static int[] GetSharedEggMoves(PKM pk, int gen) + { + if (gen < 8 || pk.IsEgg) return Array.Empty(); + + if (pk is PB8 pb) + { + var entry = (PersonalInfoBDSP)pb.PersonalInfo; + var baseSpecies = entry.HatchSpecies; + var baseForm = entry.HatchFormIndex; + return GetEggMoves(8, baseSpecies, baseForm, BD); } + if (pk is PK8 pk8) + { + var entry = (PersonalInfoSWSH)pk8.PersonalInfo; + var baseSpecies = entry.HatchSpecies; + var baseForm = entry.HatchFormIndexEverstone; + return GetEggMoves(8, baseSpecies, baseForm, SW); + } + + return Array.Empty(); } } diff --git a/PKHeX.Core/Legality/Moves/MoveLevelUp.cs b/PKHeX.Core/Legality/Moves/MoveLevelUp.cs index aee0bf084..d1f167463 100644 --- a/PKHeX.Core/Legality/Moves/MoveLevelUp.cs +++ b/PKHeX.Core/Legality/Moves/MoveLevelUp.cs @@ -4,543 +4,542 @@ using static PKHeX.Core.Legal; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class MoveLevelUp { - public static class MoveLevelUp + private static readonly LearnLookup + LearnLA = new(PersonalTable.LA, LevelUpLA, PLA), + LearnBDSP = new(PersonalTable.BDSP, LevelUpBDSP, BDSP), + LearnSWSH = new(PersonalTable.SWSH, LevelUpSWSH, SWSH), + LearnSM = new(PersonalTable.SM, LevelUpSM, SM), + LearnUSUM = new(PersonalTable.USUM, LevelUpUSUM, USUM), + LearnGG = new(PersonalTable.GG, LevelUpGG, Gen7b), + LearnXY = new(PersonalTable.XY, LevelUpXY, XY), + LearnAO = new(PersonalTable.AO, LevelUpAO, ORAS), + LearnBW = new(PersonalTable.BW, LevelUpBW, BW), + LearnB2W2 = new(PersonalTable.B2W2, LevelUpB2W2, B2W2), + LearnDP = new(PersonalTable.DP, LevelUpDP, DP), + LearnPt = new(PersonalTable.Pt, LevelUpPt, Pt), + LearnHGSS = new(PersonalTable.HGSS, LevelUpHGSS, HGSS), + LearnRSE = new(PersonalTable.RS, LevelUpRS, RSE), + LearnFRLG = new(PersonalTable.LG, LevelUpLG, FRLG), + LearnGS = new(PersonalTable.GS, LevelUpGS, GS), + LearnC = new(PersonalTable.C, LevelUpC, C), + LearnRB = new(PersonalTable.RB, LevelUpRB, RB), + LearnY = new(PersonalTable.Y, LevelUpY, YW); + + public static LearnVersion GetIsLevelUpMove(PKM pk, int species, int form, int maxLevel, int generation, int move, int minlvlG1, int minlvlG2, GameVersion version = Any) { - private static readonly LearnLookup - LearnLA = new(PersonalTable.LA, LevelUpLA, PLA), - LearnBDSP = new(PersonalTable.BDSP, LevelUpBDSP, BDSP), - LearnSWSH = new(PersonalTable.SWSH, LevelUpSWSH, SWSH), - LearnSM = new(PersonalTable.SM, LevelUpSM, SM), - LearnUSUM = new(PersonalTable.USUM, LevelUpUSUM, USUM), - LearnGG = new(PersonalTable.GG, LevelUpGG, Gen7b), - LearnXY = new(PersonalTable.XY, LevelUpXY, XY), - LearnAO = new(PersonalTable.AO, LevelUpAO, ORAS), - LearnBW = new(PersonalTable.BW, LevelUpBW, BW), - LearnB2W2 = new(PersonalTable.B2W2, LevelUpB2W2, B2W2), - LearnDP = new(PersonalTable.DP, LevelUpDP, DP), - LearnPt = new(PersonalTable.Pt, LevelUpPt, Pt), - LearnHGSS = new(PersonalTable.HGSS, LevelUpHGSS, HGSS), - LearnRSE = new(PersonalTable.RS, LevelUpRS, RSE), - LearnFRLG = new(PersonalTable.LG, LevelUpLG, FRLG), - LearnGS = new(PersonalTable.GS, LevelUpGS, GS), - LearnC = new(PersonalTable.C, LevelUpC, C), - LearnRB = new(PersonalTable.RB, LevelUpRB, RB), - LearnY = new(PersonalTable.Y, LevelUpY, YW); + var restrict = pk.IsMovesetRestricted(); + if (restrict.IsRestricted) + version = restrict.Game; - public static LearnVersion GetIsLevelUpMove(PKM pkm, int species, int form, int maxLevel, int generation, int move, int minlvlG1, int minlvlG2, GameVersion version = Any) + return generation switch { - var restrict = pkm.IsMovesetRestricted(); - if (restrict.IsRestricted) - version = restrict.Game; - - return generation switch - { - 1 => GetIsLevelUp1(species, form, move, maxLevel, minlvlG1, version), - 2 when move > MaxMoveID_1 && pkm.LearnMovesNew2Disallowed() => LearnNONE, - 2 => GetIsLevelUp2(species, form, move, maxLevel, minlvlG2, version, pkm.Korean), - 3 => GetIsLevelUp3(species, form, move, maxLevel, version), - 4 => GetIsLevelUp4(species, form, move, maxLevel, version), - 5 => GetIsLevelUp5(species, form, move, maxLevel, version), - 6 => GetIsLevelUp6(species, form, move, maxLevel, version), - 7 => GetIsLevelUp7(species, form, move, (pkm.LGPE || pkm.GO) ? (GameVersion)pkm.Version : version), // move reminder can give any move 1-100 - 8 => GetIsLevelUp8(species, form, move, maxLevel, version), - _ => LearnNONE, - }; - } - - internal static LearnVersion GetIsLevelUp1(int species, int form, int move, int maxLevel, int minLevel, GameVersion ver = Any) - { - if (move > MaxMoveID_1) - return LearnNONE; - - switch (ver) - { - case Any: case RBY: - var first = LearnRB.GetIsLevelUpG1(species, form, move, maxLevel, minLevel); - var second = LearnY.GetIsLevelUpG1(species, form, move, maxLevel, minLevel); - if (!first.IsLevelUp) - return second; - if (!second.IsLevelUp) - return first; - return first.Level > second.Level ? second : first; - - case RD or BU or GN or RB: - return LearnRB.GetIsLevelUpG1(species, form, move, maxLevel, minLevel); - case YW: - return LearnY.GetIsLevelUpG1(species, form, move, maxLevel, minLevel); - } - - return LearnNONE; - } - - private static LearnVersion GetIsLevelUp2(int species, int form, int move, int maxLevel, int minLevel, GameVersion ver = Any, bool korean = false) - { - // No Korean Crystal - switch (ver) - { - case Any: case GSC: - var first = LearnGS.GetIsLevelUpMin(species, move, maxLevel, minLevel, form); - if (first.IsLevelUp || korean) - return first; - return LearnC.GetIsLevelUpMin(species, move, maxLevel, minLevel, form); - - case GD or SI or GS: - return LearnGS.GetIsLevelUpMin(species, move, maxLevel, minLevel, form); - case C when !korean: - return LearnC.GetIsLevelUpMin(species, move, maxLevel, minLevel, form); - } - return LearnNONE; - } - - private static LearnVersion GetIsLevelUp3(int species, int form, int move, int lvlLevel, GameVersion ver = Any) - { - if (species == (int)Species.Deoxys) - return GetIsLevelUp3Deoxys(form, move, lvlLevel, ver); - - // Emerald level up tables are equal to R/S level up tables - switch (ver) - { - case Any: - var first = LearnRSE.GetIsLevelUp(species, form, move, lvlLevel); - if (first.IsLevelUp) - return first; - return LearnFRLG.GetIsLevelUp(species, form, move, lvlLevel); - - case R or S or E or RS or RSE: - return LearnRSE.GetIsLevelUp(species, form, move, lvlLevel); - case FR or LG or FRLG: - return LearnFRLG.GetIsLevelUp(species, form, move, lvlLevel); - } - return LearnNONE; - } - - private static LearnVersion GetIsLevelUp4(int species, int form, int move, int maxLevel, GameVersion ver = Any) - { - switch (ver) - { - case Any: case DPPt: - var first = LearnDP.GetIsLevelUp(species, form, move, maxLevel); - if (first.IsLevelUp) - return first; - var second = LearnPt.GetIsLevelUp(species, form, move, maxLevel); - if (second.IsLevelUp) - return second; - if (ver == DPPt) // stop here - return LearnNONE; - return LearnHGSS.GetIsLevelUp(species, form, move, maxLevel); - - case D or P or DP: - return LearnDP.GetIsLevelUp(species, form, move, maxLevel); - case Pt: - return LearnPt.GetIsLevelUp(species, form, move, maxLevel); - case HG or SS or HGSS: - return LearnHGSS.GetIsLevelUp(species, form, move, maxLevel); - } - return LearnNONE; - } - - private static LearnVersion GetIsLevelUp5(int species, int form, int move, int maxLevel, GameVersion ver = Any) - { - switch (ver) - { - case Any: - var first = LearnBW.GetIsLevelUp(species, form, move, maxLevel); - if (first.IsLevelUp && species != 646) // Kyurem moves are same for both versions, but form movepool not present. - return first; - return LearnB2W2.GetIsLevelUp(species, form, move, maxLevel); - case B or W or BW: - return LearnBW.GetIsLevelUp(species, form, move, maxLevel); - case B2 or W2 or B2W2: - return LearnB2W2.GetIsLevelUp(species, form, move, maxLevel); - } - return LearnNONE; - } - - private static LearnVersion GetIsLevelUp6(int species, int form, int move, int maxLevel, GameVersion ver = Any) - { - switch (ver) - { - case Any: - var first = LearnXY.GetIsLevelUp(species, form, move, maxLevel); - if (first.IsLevelUp) - return first; - return LearnAO.GetIsLevelUp(species, form, move, maxLevel); - - case X or Y or XY: - return LearnXY.GetIsLevelUp(species, form, move, maxLevel); - case OR or AS or ORAS: - return LearnAO.GetIsLevelUp(species, form, move, maxLevel); - } - return LearnNONE; - } - - private static LearnVersion GetIsLevelUp7(int species, int form, int move, GameVersion ver = Any) - { - switch (ver) - { - case GP or GE or GG or GO: - return LearnGG.GetIsLevelUp(species, form, move); - - case Any: - if (species > MaxSpeciesID_7_USUM) - return LearnNONE; - var first = LearnUSUM.GetIsLevelUp(species, form, move); - if (first.IsLevelUp) - return first; - if (species > MaxSpeciesID_7) - return LearnNONE; - return LearnSM.GetIsLevelUp(species, form, move); - - case SN or MN or SM: - if (species > MaxSpeciesID_7) - return LearnNONE; - return LearnSM.GetIsLevelUp(species, form, move); - - case US or UM or USUM: - if (species > MaxSpeciesID_7_USUM) - return LearnNONE; - return LearnUSUM.GetIsLevelUp(species, form, move); - } - return LearnNONE; - } - - private static LearnVersion GetIsLevelUp8(int species, int form, int move, int maxLevel, GameVersion ver = Any) - { - switch (ver) - { - case Any: - case GO: - case SW or SH or SWSH: - if (species > MaxSpeciesID_8) - return LearnNONE; - return LearnSWSH.GetIsLevelUp(species, form, move, maxLevel); - - case PLA: - if (species > MaxSpeciesID_8a) - return LearnNONE; - return LearnLA.GetIsLevelUp(species, form, move, maxLevel); - - case BD or SP or BDSP: - if (species > MaxSpeciesID_8b) - return LearnNONE; - return LearnBDSP.GetIsLevelUp(species, form, move, maxLevel); - } - return LearnNONE; - } - - private static LearnVersion GetIsLevelUp3Deoxys(int form, int move, int lvl, GameVersion ver = Any) - { - var moveset = GetDeoxysLearn3(form, ver); - if (moveset == null) - return LearnNONE; - var lv = moveset.GetLevelLearnMove(move); - if (lv >= 0 && lv <= lvl) - return new LearnVersion(lv, GetDeoxysGameVersion3(form)); - return LearnNONE; - } - - private static GameVersion GetDeoxysGameVersion3(int form) => form switch - { - 0 => RS, - 1 => FR, - 2 => LG, - 3 => E, - _ => Invalid, + 1 => GetIsLevelUp1(species, form, move, maxLevel, minlvlG1, version), + 2 when move > MaxMoveID_1 && pk.LearnMovesNew2Disallowed() => LearnNONE, + 2 => GetIsLevelUp2(species, form, move, maxLevel, minlvlG2, version, pk.Korean), + 3 => GetIsLevelUp3(species, form, move, maxLevel, version), + 4 => GetIsLevelUp4(species, form, move, maxLevel, version), + 5 => GetIsLevelUp5(species, form, move, maxLevel, version), + 6 => GetIsLevelUp6(species, form, move, maxLevel, version), + 7 => GetIsLevelUp7(species, form, move, (pk.LGPE || pk.GO) ? (GameVersion)pk.Version : version), // move reminder can give any move 1-100 + 8 => GetIsLevelUp8(species, form, move, maxLevel, version), + _ => LearnNONE, }; + } - private static Learnset? GetDeoxysLearn3(int form, GameVersion ver = Any) + internal static LearnVersion GetIsLevelUp1(int species, int form, int move, int maxLevel, int minLevel, GameVersion ver = Any) + { + if (move > MaxMoveID_1) + return LearnNONE; + + switch (ver) { - const int index = (int)Species.Deoxys; - if (ver != Any && Gen3.Contains(ver)) - return GameData.GetLearnsets(ver)[index]; + case Any: case RBY: + var first = LearnRB.GetIsLevelUpG1(species, form, move, maxLevel, minLevel); + var second = LearnY.GetIsLevelUpG1(species, form, move, maxLevel, minLevel); + if (!first.IsLevelUp) + return second; + if (!second.IsLevelUp) + return first; + return first.Level > second.Level ? second : first; - return form switch - { - 0 => LevelUpRS[index], // Normal - 1 => LevelUpFR[index], // Attack - 2 => LevelUpLG[index], // Defense - 3 => LevelUpE[index], // Speed - _ => null, - }; + case RD or BU or GN or RB: + return LearnRB.GetIsLevelUpG1(species, form, move, maxLevel, minLevel); + case YW: + return LearnY.GetIsLevelUpG1(species, form, move, maxLevel, minLevel); } - public static IEnumerable GetMovesLevelUp(PKM pkm, int species, int form, int maxLevel, int minlvlG1, int minlvlG2, GameVersion version, bool MoveReminder, int generation) - { - var restrict = pkm.IsMovesetRestricted(); - if (restrict.IsRestricted) - version = restrict.Game; + return LearnNONE; + } - return generation switch - { - 1 => GetMovesLevelUp1(species, form, maxLevel, minlvlG1, version), - 2 => GetMovesLevelUp2(species, form, maxLevel, minlvlG2, pkm.Korean, pkm.LearnMovesNew2Disallowed(), version), - 3 => GetMovesLevelUp3(species, form, maxLevel, version), - 4 => GetMovesLevelUp4(species, form, maxLevel, version), - 5 => GetMovesLevelUp5(species, form, maxLevel, version), - 6 => GetMovesLevelUp6(species, form, maxLevel, version), - 7 => GetMovesLevelUp7(species, form, maxLevel, MoveReminder, pkm.LGPE || pkm.GO ? (GameVersion)pkm.Version : version), - 8 => GetMovesLevelUp8(species, form, maxLevel, version), - _ => Array.Empty(), - }; + private static LearnVersion GetIsLevelUp2(int species, int form, int move, int maxLevel, int minLevel, GameVersion ver = Any, bool korean = false) + { + // No Korean Crystal + switch (ver) + { + case Any: case GSC: + var first = LearnGS.GetIsLevelUpMin(species, move, maxLevel, minLevel, form); + if (first.IsLevelUp || korean) + return first; + return LearnC.GetIsLevelUpMin(species, move, maxLevel, minLevel, form); + + case GD or SI or GS: + return LearnGS.GetIsLevelUpMin(species, move, maxLevel, minLevel, form); + case C when !korean: + return LearnC.GetIsLevelUpMin(species, move, maxLevel, minLevel, form); } + return LearnNONE; + } - private static bool LearnMovesNew2Disallowed(this PKM pkm) => pkm.Format == 1 || (pkm.Format >= 7 && pkm.VC1); + private static LearnVersion GetIsLevelUp3(int species, int form, int move, int lvlLevel, GameVersion ver = Any) + { + if (species == (int)Species.Deoxys) + return GetIsLevelUp3Deoxys(form, move, lvlLevel, ver); - internal static List GetMovesLevelUp1(int species, int form, int maxLevel, int minLevel, GameVersion ver = Any) + // Emerald level up tables are equal to R/S level up tables + switch (ver) { - return AddMovesLevelUp1(new List(), ver, species, form, maxLevel, minLevel); + case Any: + var first = LearnRSE.GetIsLevelUp(species, form, move, lvlLevel); + if (first.IsLevelUp) + return first; + return LearnFRLG.GetIsLevelUp(species, form, move, lvlLevel); + + case R or S or E or RS or RSE: + return LearnRSE.GetIsLevelUp(species, form, move, lvlLevel); + case FR or LG or FRLG: + return LearnFRLG.GetIsLevelUp(species, form, move, lvlLevel); } + return LearnNONE; + } - private static List GetMovesLevelUp2(int species, int form, int maxLevel, int minLevel, bool korean, bool removeNewGSCMoves, GameVersion ver = Any) + private static LearnVersion GetIsLevelUp4(int species, int form, int move, int maxLevel, GameVersion ver = Any) + { + switch (ver) { - var moves = AddMovesLevelUp2(new List(), ver, species, form, maxLevel, minLevel, korean); - if (removeNewGSCMoves) - moves.RemoveAll(m => m > MaxMoveID_1); + case Any: case DPPt: + var first = LearnDP.GetIsLevelUp(species, form, move, maxLevel); + if (first.IsLevelUp) + return first; + var second = LearnPt.GetIsLevelUp(species, form, move, maxLevel); + if (second.IsLevelUp) + return second; + if (ver == DPPt) // stop here + return LearnNONE; + return LearnHGSS.GetIsLevelUp(species, form, move, maxLevel); + + case D or P or DP: + return LearnDP.GetIsLevelUp(species, form, move, maxLevel); + case Pt: + return LearnPt.GetIsLevelUp(species, form, move, maxLevel); + case HG or SS or HGSS: + return LearnHGSS.GetIsLevelUp(species, form, move, maxLevel); + } + return LearnNONE; + } + + private static LearnVersion GetIsLevelUp5(int species, int form, int move, int maxLevel, GameVersion ver = Any) + { + switch (ver) + { + case Any: + var first = LearnBW.GetIsLevelUp(species, form, move, maxLevel); + if (first.IsLevelUp && species != 646) // Kyurem moves are same for both versions, but form movepool not present. + return first; + return LearnB2W2.GetIsLevelUp(species, form, move, maxLevel); + case B or W or BW: + return LearnBW.GetIsLevelUp(species, form, move, maxLevel); + case B2 or W2 or B2W2: + return LearnB2W2.GetIsLevelUp(species, form, move, maxLevel); + } + return LearnNONE; + } + + private static LearnVersion GetIsLevelUp6(int species, int form, int move, int maxLevel, GameVersion ver = Any) + { + switch (ver) + { + case Any: + var first = LearnXY.GetIsLevelUp(species, form, move, maxLevel); + if (first.IsLevelUp) + return first; + return LearnAO.GetIsLevelUp(species, form, move, maxLevel); + + case X or Y or XY: + return LearnXY.GetIsLevelUp(species, form, move, maxLevel); + case OR or AS or ORAS: + return LearnAO.GetIsLevelUp(species, form, move, maxLevel); + } + return LearnNONE; + } + + private static LearnVersion GetIsLevelUp7(int species, int form, int move, GameVersion ver = Any) + { + switch (ver) + { + case GP or GE or GG or GO: + return LearnGG.GetIsLevelUp(species, form, move); + + case Any: + if (species > MaxSpeciesID_7_USUM) + return LearnNONE; + var first = LearnUSUM.GetIsLevelUp(species, form, move); + if (first.IsLevelUp) + return first; + if (species > MaxSpeciesID_7) + return LearnNONE; + return LearnSM.GetIsLevelUp(species, form, move); + + case SN or MN or SM: + if (species > MaxSpeciesID_7) + return LearnNONE; + return LearnSM.GetIsLevelUp(species, form, move); + + case US or UM or USUM: + if (species > MaxSpeciesID_7_USUM) + return LearnNONE; + return LearnUSUM.GetIsLevelUp(species, form, move); + } + return LearnNONE; + } + + private static LearnVersion GetIsLevelUp8(int species, int form, int move, int maxLevel, GameVersion ver = Any) + { + switch (ver) + { + case Any: + case GO: + case SW or SH or SWSH: + if (species > MaxSpeciesID_8) + return LearnNONE; + return LearnSWSH.GetIsLevelUp(species, form, move, maxLevel); + + case PLA: + if (species > MaxSpeciesID_8a) + return LearnNONE; + return LearnLA.GetIsLevelUp(species, form, move, maxLevel); + + case BD or SP or BDSP: + if (species > MaxSpeciesID_8b) + return LearnNONE; + return LearnBDSP.GetIsLevelUp(species, form, move, maxLevel); + } + return LearnNONE; + } + + private static LearnVersion GetIsLevelUp3Deoxys(int form, int move, int lvl, GameVersion ver = Any) + { + var moveset = GetDeoxysLearn3(form, ver); + if (moveset == null) + return LearnNONE; + var lv = moveset.GetLevelLearnMove(move); + if (lv >= 0 && lv <= lvl) + return new LearnVersion(lv, GetDeoxysGameVersion3(form)); + return LearnNONE; + } + + private static GameVersion GetDeoxysGameVersion3(int form) => form switch + { + 0 => RS, + 1 => FR, + 2 => LG, + 3 => E, + _ => Invalid, + }; + + private static Learnset? GetDeoxysLearn3(int form, GameVersion ver = Any) + { + const int index = (int)Species.Deoxys; + if (ver != Any && Gen3.Contains(ver)) + return GameData.GetLearnsets(ver)[index]; + + return form switch + { + 0 => LevelUpRS[index], // Normal + 1 => LevelUpFR[index], // Attack + 2 => LevelUpLG[index], // Defense + 3 => LevelUpE[index], // Speed + _ => null, + }; + } + + public static IEnumerable GetMovesLevelUp(PKM pk, int species, int form, int maxLevel, int minlvlG1, int minlvlG2, GameVersion version, bool MoveReminder, int generation) + { + var restrict = pk.IsMovesetRestricted(); + if (restrict.IsRestricted) + version = restrict.Game; + + return generation switch + { + 1 => GetMovesLevelUp1(species, form, maxLevel, minlvlG1, version), + 2 => GetMovesLevelUp2(species, form, maxLevel, minlvlG2, pk.Korean, pk.LearnMovesNew2Disallowed(), version), + 3 => GetMovesLevelUp3(species, form, maxLevel, version), + 4 => GetMovesLevelUp4(species, form, maxLevel, version), + 5 => GetMovesLevelUp5(species, form, maxLevel, version), + 6 => GetMovesLevelUp6(species, form, maxLevel, version), + 7 => GetMovesLevelUp7(species, form, maxLevel, MoveReminder, pk.LGPE || pk.GO ? (GameVersion)pk.Version : version), + 8 => GetMovesLevelUp8(species, form, maxLevel, version), + _ => Array.Empty(), + }; + } + + private static bool LearnMovesNew2Disallowed(this PKM pk) => pk.Format == 1 || (pk.Format >= 7 && pk.VC1); + + internal static List GetMovesLevelUp1(int species, int form, int maxLevel, int minLevel, GameVersion ver = Any) + { + return AddMovesLevelUp1(new List(), ver, species, form, maxLevel, minLevel); + } + + private static List GetMovesLevelUp2(int species, int form, int maxLevel, int minLevel, bool korean, bool removeNewGSCMoves, GameVersion ver = Any) + { + var moves = AddMovesLevelUp2(new List(), ver, species, form, maxLevel, minLevel, korean); + if (removeNewGSCMoves) + moves.RemoveAll(m => m > MaxMoveID_1); + return moves; + } + + private static List GetMovesLevelUp3(int species, int form, int maxLevel, GameVersion ver = Any) + { + return AddMovesLevelUp3(new List(), ver, species, form, maxLevel); + } + + private static List GetMovesLevelUp4(int species, int form, int maxLevel, GameVersion ver = Any) + { + return AddMovesLevelUp4(new List(), ver, species, form, maxLevel); + } + + private static List GetMovesLevelUp5(int species, int form, int maxLevel, GameVersion ver = Any) + { + return AddMovesLevelUp5(new List(), ver, species, form, maxLevel); + } + + private static List GetMovesLevelUp6(int species, int form, int maxLevel, GameVersion ver = Any) + { + return AddMovesLevelUp6(new List(), ver, species, form, maxLevel); + } + + private static List GetMovesLevelUp7(int species, int form, int maxLevel, bool MoveReminder, GameVersion ver = Any) + { + return AddMovesLevelUp7(new List(), ver, species, form, maxLevel, MoveReminder); + } + + private static List GetMovesLevelUp8(int species, int form, int maxLevel, GameVersion ver = Any) + { + return AddMovesLevelUp8(new List(), ver, species, form, maxLevel); + } + + private static List AddMovesLevelUp1(List moves, GameVersion ver, int species, int form, int max, int min) + { + switch (ver) + { + case Any: case RBY: + LearnRB.AddMoves1(moves, species, form, max, min); + return LearnY.AddMoves1(moves, species, form, max, min); + + case RD or BU or GN or RB: + return LearnRB.AddMoves1(moves, species, form, max, min); + case YW: + return LearnY.AddMoves1(moves, species, form, max, min); + } + return moves; + } + + private static List AddMovesLevelUp2(List moves, GameVersion ver, int species, int form, int max, int min, bool korean) + { + switch (ver) + { + case Any: case GSC: + LearnGS.AddMoves(moves, species, form, max, min); + if (korean) + return moves; + return LearnC.AddMoves(moves, species, form, max, min); + + case GD or SI or GS: + return LearnGS.AddMoves(moves, species, form, max, min); + case C when !korean: + return LearnC.AddMoves(moves, species, form, max, min); + } + return moves; + } + + private static List AddMovesLevelUp3(List moves, GameVersion ver, int species, int form, int maxLevel) + { + if (species == (int)Species.Deoxys) + { + var learn = GetDeoxysLearn3(form, ver); + if (learn != null) + moves.AddRange(learn.GetMoves(maxLevel)); return moves; } - private static List GetMovesLevelUp3(int species, int form, int maxLevel, GameVersion ver = Any) + // Emerald level up tables are equal to R/S level up tables + switch (ver) { - return AddMovesLevelUp3(new List(), ver, species, form, maxLevel); - } + case Any: + LearnRSE.AddMoves(moves, species, form, maxLevel); + return LearnFRLG.AddMoves(moves, species, form, maxLevel); - private static List GetMovesLevelUp4(int species, int form, int maxLevel, GameVersion ver = Any) + case R or S or E or RS or RSE: + return LearnRSE.AddMoves(moves, species, form, maxLevel); + case FR or LG or FRLG: + return LearnFRLG.AddMoves(moves, species, form, maxLevel); + } + return moves; + } + + private static List AddMovesLevelUp4(List moves, GameVersion ver, int species, int form, int maxLevel) + { + switch (ver) { - return AddMovesLevelUp4(new List(), ver, species, form, maxLevel); - } + case Any: case DPPt: + LearnDP.AddMoves(moves, species, form, maxLevel); + LearnPt.AddMoves(moves, species, form, maxLevel); + if (ver == DPPt) // stop here + return moves; + return LearnHGSS.AddMoves(moves, species, form, maxLevel); - private static List GetMovesLevelUp5(int species, int form, int maxLevel, GameVersion ver = Any) + case D or P or DP: + return LearnDP.AddMoves(moves, species, form, maxLevel); + case Pt: + return LearnPt.AddMoves(moves, species, form, maxLevel); + case HG or SS or HGSS: + return LearnHGSS.AddMoves(moves, species, form, maxLevel); + } + return moves; + } + + private static List AddMovesLevelUp5(List moves, GameVersion ver, int species, int form, int maxLevel) + { + switch (ver) { - return AddMovesLevelUp5(new List(), ver, species, form, maxLevel); - } + case Any: + if (species != 646) // Kyurem moves are same for both versions, but form movepool not present. + LearnBW.AddMoves(moves, species, form, maxLevel); + return LearnB2W2.AddMoves(moves, species, form, maxLevel); - private static List GetMovesLevelUp6(int species, int form, int maxLevel, GameVersion ver = Any) + case B or W or BW: + return LearnBW.AddMoves(moves, species, form, maxLevel); + case B2 or W2 or B2W2: + return LearnB2W2.AddMoves(moves, species, form, maxLevel); + } + return moves; + } + + private static List AddMovesLevelUp6(List moves, GameVersion ver, int species, int form, int maxLevel) + { + switch (ver) { - return AddMovesLevelUp6(new List(), ver, species, form, maxLevel); - } + case Any: + LearnXY.AddMoves(moves, species, form, maxLevel); + return LearnAO.AddMoves(moves, species, form, maxLevel); - private static List GetMovesLevelUp7(int species, int form, int maxLevel, bool MoveReminder, GameVersion ver = Any) + case X or Y or XY: + return LearnXY.AddMoves(moves, species, form, maxLevel); + case AS or OR or ORAS: + return LearnAO.AddMoves(moves, species, form, maxLevel); + } + return moves; + } + + private static List AddMovesLevelUp7(List moves, GameVersion ver, int species, int form, int maxLevel, bool reminder) + { + if (reminder) + maxLevel = 100; // Move reminder can teach any level in movepool now! + switch (ver) { - return AddMovesLevelUp7(new List(), ver, species, form, maxLevel, MoveReminder); - } + case GP or GE or GG or GO: + return LearnGG.AddMoves(moves, species, form, maxLevel); - private static List GetMovesLevelUp8(int species, int form, int maxLevel, GameVersion ver = Any) + case Any: + if (species > MaxSpeciesID_7_USUM) + return moves; + LearnUSUM.AddMoves(moves, species, form, maxLevel); + if (species > MaxSpeciesID_7) + return moves; + return LearnSM.AddMoves(moves, species, form, maxLevel); + + case SN or MN or SM: + if (species > MaxSpeciesID_7) + return moves; + return LearnSM.AddMoves(moves, species, form, maxLevel); + + case US or UM or USUM: + if (species > MaxSpeciesID_7_USUM) + return moves; + LearnUSUM.AddMoves(moves, species, form, maxLevel); + break; + } + return moves; + } + + private static List AddMovesLevelUp8(List moves, GameVersion ver, int species, int form, int maxLevel) + { + // Move reminder can NOT teach any level like Gen7 + switch (ver) { - return AddMovesLevelUp8(new List(), ver, species, form, maxLevel); + case Any: + case GO: + case SW or SH or SWSH: + if (species > MaxSpeciesID_8) + return moves; + return LearnSWSH.AddMoves(moves, species, form, maxLevel); + + case PLA: + if (species > MaxSpeciesID_8a) + return moves; + return LearnLA.AddMoves(moves, species, form, maxLevel); + + case BD or SP or BDSP: + if (species > MaxSpeciesID_8b) + return moves; + return LearnBDSP.AddMoves(moves, species, form, maxLevel); } + return moves; + } - private static List AddMovesLevelUp1(List moves, GameVersion ver, int species, int form, int max, int min) - { - switch (ver) - { - case Any: case RBY: - LearnRB.AddMoves1(moves, species, form, max, min); - return LearnY.AddMoves1(moves, species, form, max, min); + public static int[] GetEncounterMoves(PKM pk, int level, GameVersion version) + { + if (version <= 0) + version = (GameVersion)pk.Version; + return GetEncounterMoves(pk.Species, pk.Form, level, version); + } - case RD or BU or GN or RB: - return LearnRB.AddMoves1(moves, species, form, max, min); - case YW: - return LearnY.AddMoves1(moves, species, form, max, min); - } - return moves; - } + private static int[] GetEncounterMoves1(int species, int level, GameVersion version) + { + var learn = GameData.GetLearnsets(version); + var table = GameData.GetPersonal(version); + var index = table.GetFormIndex(species, 0); + var lvl0 = (int[])((PersonalInfoG1) table[index]).Moves.Clone(); + int start = Math.Max(0, Array.IndexOf(lvl0, 0)); - private static List AddMovesLevelUp2(List moves, GameVersion ver, int species, int form, int max, int min, bool korean) - { - switch (ver) - { - case Any: case GSC: - LearnGS.AddMoves(moves, species, form, max, min); - if (korean) - return moves; - return LearnC.AddMoves(moves, species, form, max, min); + learn[index].SetEncounterMoves(level, lvl0, start); + return lvl0; + } - case GD or SI or GS: - return LearnGS.AddMoves(moves, species, form, max, min); - case C when !korean: - return LearnC.AddMoves(moves, species, form, max, min); - } - return moves; - } + private static int[] GetEncounterMoves2(int species, int level, GameVersion version) + { + var learn = GameData.GetLearnsets(version); + var table = GameData.GetPersonal(version); + var index = table.GetFormIndex(species, 0); + var lvl0 = learn[species].GetEncounterMoves(1); + int start = Math.Max(0, Array.IndexOf(lvl0, 0)); - private static List AddMovesLevelUp3(List moves, GameVersion ver, int species, int form, int maxLevel) - { - if (species == (int)Species.Deoxys) - { - var learn = GetDeoxysLearn3(form, ver); - if (learn != null) - moves.AddRange(learn.GetMoves(maxLevel)); - return moves; - } + learn[index].SetEncounterMoves(level, lvl0, start); + return lvl0; + } - // Emerald level up tables are equal to R/S level up tables - switch (ver) - { - case Any: - LearnRSE.AddMoves(moves, species, form, maxLevel); - return LearnFRLG.AddMoves(moves, species, form, maxLevel); - - case R or S or E or RS or RSE: - return LearnRSE.AddMoves(moves, species, form, maxLevel); - case FR or LG or FRLG: - return LearnFRLG.AddMoves(moves, species, form, maxLevel); - } - return moves; - } - - private static List AddMovesLevelUp4(List moves, GameVersion ver, int species, int form, int maxLevel) - { - switch (ver) - { - case Any: case DPPt: - LearnDP.AddMoves(moves, species, form, maxLevel); - LearnPt.AddMoves(moves, species, form, maxLevel); - if (ver == DPPt) // stop here - return moves; - return LearnHGSS.AddMoves(moves, species, form, maxLevel); - - case D or P or DP: - return LearnDP.AddMoves(moves, species, form, maxLevel); - case Pt: - return LearnPt.AddMoves(moves, species, form, maxLevel); - case HG or SS or HGSS: - return LearnHGSS.AddMoves(moves, species, form, maxLevel); - } - return moves; - } - - private static List AddMovesLevelUp5(List moves, GameVersion ver, int species, int form, int maxLevel) - { - switch (ver) - { - case Any: - if (species != 646) // Kyurem moves are same for both versions, but form movepool not present. - LearnBW.AddMoves(moves, species, form, maxLevel); - return LearnB2W2.AddMoves(moves, species, form, maxLevel); - - case B or W or BW: - return LearnBW.AddMoves(moves, species, form, maxLevel); - case B2 or W2 or B2W2: - return LearnB2W2.AddMoves(moves, species, form, maxLevel); - } - return moves; - } - - private static List AddMovesLevelUp6(List moves, GameVersion ver, int species, int form, int maxLevel) - { - switch (ver) - { - case Any: - LearnXY.AddMoves(moves, species, form, maxLevel); - return LearnAO.AddMoves(moves, species, form, maxLevel); - - case X or Y or XY: - return LearnXY.AddMoves(moves, species, form, maxLevel); - case AS or OR or ORAS: - return LearnAO.AddMoves(moves, species, form, maxLevel); - } - return moves; - } - - private static List AddMovesLevelUp7(List moves, GameVersion ver, int species, int form, int maxLevel, bool reminder) - { - if (reminder) - maxLevel = 100; // Move reminder can teach any level in movepool now! - switch (ver) - { - case GP or GE or GG or GO: - return LearnGG.AddMoves(moves, species, form, maxLevel); - - case Any: - if (species > MaxSpeciesID_7_USUM) - return moves; - LearnUSUM.AddMoves(moves, species, form, maxLevel); - if (species > MaxSpeciesID_7) - return moves; - return LearnSM.AddMoves(moves, species, form, maxLevel); - - case SN or MN or SM: - if (species > MaxSpeciesID_7) - return moves; - return LearnSM.AddMoves(moves, species, form, maxLevel); - - case US or UM or USUM: - if (species > MaxSpeciesID_7_USUM) - return moves; - LearnUSUM.AddMoves(moves, species, form, maxLevel); - break; - } - return moves; - } - - private static List AddMovesLevelUp8(List moves, GameVersion ver, int species, int form, int maxLevel) - { - // Move reminder can NOT teach any level like Gen7 - switch (ver) - { - case Any: - case GO: - case SW or SH or SWSH: - if (species > MaxSpeciesID_8) - return moves; - return LearnSWSH.AddMoves(moves, species, form, maxLevel); - - case PLA: - if (species > MaxSpeciesID_8a) - return moves; - return LearnLA.AddMoves(moves, species, form, maxLevel); - - case BD or SP or BDSP: - if (species > MaxSpeciesID_8b) - return moves; - return LearnBDSP.AddMoves(moves, species, form, maxLevel); - } - return moves; - } - - public static int[] GetEncounterMoves(PKM pk, int level, GameVersion version) - { - if (version <= 0) - version = (GameVersion)pk.Version; - return GetEncounterMoves(pk.Species, pk.Form, level, version); - } - - private static int[] GetEncounterMoves1(int species, int level, GameVersion version) - { - var learn = GameData.GetLearnsets(version); - var table = GameData.GetPersonal(version); - var index = table.GetFormIndex(species, 0); - var lvl0 = (int[])((PersonalInfoG1) table[index]).Moves.Clone(); - int start = Math.Max(0, Array.IndexOf(lvl0, 0)); - - learn[index].SetEncounterMoves(level, lvl0, start); - return lvl0; - } - - private static int[] GetEncounterMoves2(int species, int level, GameVersion version) - { - var learn = GameData.GetLearnsets(version); - var table = GameData.GetPersonal(version); - var index = table.GetFormIndex(species, 0); - var lvl0 = learn[species].GetEncounterMoves(1); - int start = Math.Max(0, Array.IndexOf(lvl0, 0)); - - learn[index].SetEncounterMoves(level, lvl0, start); - return lvl0; - } - - public static int[] GetEncounterMoves(int species, int form, int level, GameVersion version) - { - if (RBY.Contains(version)) - return GetEncounterMoves1(species, level, version); - if (GSC.Contains(version)) - return GetEncounterMoves2(species, level, version); - var learn = GameData.GetLearnsets(version); - var table = GameData.GetPersonal(version); - var index = table.GetFormIndex(species, form); - return learn[index].GetEncounterMoves(level); - } + public static int[] GetEncounterMoves(int species, int form, int level, GameVersion version) + { + if (RBY.Contains(version)) + return GetEncounterMoves1(species, level, version); + if (GSC.Contains(version)) + return GetEncounterMoves2(species, level, version); + var learn = GameData.GetLearnsets(version); + var table = GameData.GetPersonal(version); + var index = table.GetFormIndex(species, form); + return learn[index].GetEncounterMoves(level); } } diff --git a/PKHeX.Core/Legality/Moves/MoveParseSource.cs b/PKHeX.Core/Legality/Moves/MoveParseSource.cs index ac4340074..99a9d0db1 100644 --- a/PKHeX.Core/Legality/Moves/MoveParseSource.cs +++ b/PKHeX.Core/Legality/Moves/MoveParseSource.cs @@ -1,35 +1,34 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal sealed class MoveParseSource { - internal sealed class MoveParseSource + private static readonly int[] Empty = Array.Empty(); + public IReadOnlyList CurrentMoves { get; init; } = Empty; + public IReadOnlyList SpecialSource { get; set; } = Empty; + public int[] NonTradeBackLevelUpMoves { get; set; } = Empty; + + /// + /// Base moves from a standard encounter + /// + public int[] Base { get; set; } = Empty; + + public int[] EggLevelUpSource { get; set; } = Empty; + public int[] EggMoveSource { get; set; } = Empty; + public IReadOnlyList EggEventSource { get; set; } = Empty; + + /// + /// Clears all sources except for the . + /// + public void ResetSources() { - private static readonly int[] Empty = Array.Empty(); - public IReadOnlyList CurrentMoves { get; init; } = Empty; - public IReadOnlyList SpecialSource { get; set; } = Empty; - public int[] NonTradeBackLevelUpMoves { get; set; } = Empty; - - /// - /// Base moves from a standard encounter - /// - public int[] Base { get; set; } = Empty; - - public int[] EggLevelUpSource { get; set; } = Empty; - public int[] EggMoveSource { get; set; } = Empty; - public IReadOnlyList EggEventSource { get; set; } = Empty; - - /// - /// Clears all sources except for the . - /// - public void ResetSources() - { - EggEventSource = Empty; - Base = Empty; - EggLevelUpSource = Empty; - EggMoveSource = Empty; - NonTradeBackLevelUpMoves = Empty; - SpecialSource = Empty; - } + EggEventSource = Empty; + Base = Empty; + EggLevelUpSource = Empty; + EggMoveSource = Empty; + NonTradeBackLevelUpMoves = Empty; + SpecialSource = Empty; } } diff --git a/PKHeX.Core/Legality/Moves/MoveTechnicalMachine.cs b/PKHeX.Core/Legality/Moves/MoveTechnicalMachine.cs index 71a15950c..3ee975f10 100644 --- a/PKHeX.Core/Legality/Moves/MoveTechnicalMachine.cs +++ b/PKHeX.Core/Legality/Moves/MoveTechnicalMachine.cs @@ -1,472 +1,471 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core -{ - public static class MoveTechnicalMachine - { - public static GameVersion GetIsMachineMove(PKM pkm, int species, int form, int generation, int move, GameVersion ver = GameVersion.Any, bool RemoveTransfer = false) - { - var (isRestricted, game) = pkm.IsMovesetRestricted(); - if (isRestricted) - ver = game; +namespace PKHeX.Core; - switch (generation) - { - case 1: return GetIsMachine1(species, move); - case 2: - if (pkm.VC1 && move > Legal.MaxMoveID_1) - return Legal.NONE; - return GetIsMachine2(species, move); - case 3: return GetIsMachine3(species, move, pkm.Format, RemoveTransfer); - case 4: return GetIsMachine4(species, move, pkm.Format, RemoveTransfer, form); - case 5: return GetIsMachine5(species, move, form); - case 6: return GetIsMachine6(species, move, form, ver); - case 7: return GetIsMachine7(species, move, form, pkm.LGPE || pkm.GO ? (GameVersion)pkm.Version : ver); - case 8: return GetIsMachine8(species, move, form, ver); - default: +public static class MoveTechnicalMachine +{ + public static GameVersion GetIsMachineMove(PKM pk, int species, int form, int generation, int move, GameVersion ver = GameVersion.Any, bool RemoveTransfer = false) + { + var (isRestricted, game) = pk.IsMovesetRestricted(); + if (isRestricted) + ver = game; + + switch (generation) + { + case 1: return GetIsMachine1(species, move); + case 2: + if (pk.VC1 && move > Legal.MaxMoveID_1) return Legal.NONE; + return GetIsMachine2(species, move); + case 3: return GetIsMachine3(species, move, pk.Format, RemoveTransfer); + case 4: return GetIsMachine4(species, move, pk.Format, RemoveTransfer, form); + case 5: return GetIsMachine5(species, move, form); + case 6: return GetIsMachine6(species, move, form, ver); + case 7: return GetIsMachine7(species, move, form, pk.LGPE || pk.GO ? (GameVersion)pk.Version : ver); + case 8: return GetIsMachine8(species, move, form, ver); + default: + return Legal.NONE; + } + } + + public static GameVersion GetIsRecordMove(PKM pk, int species, int form, int generation, int move, GameVersion ver = GameVersion.Any, bool allowBit = false) + { + var (isRestricted, game) = pk.IsMovesetRestricted(); + if (isRestricted) + ver = game; + + return generation switch + { + 8 => GetIsRecord8(pk, species, move, form, ver, allowBit), + _ => Legal.NONE, + }; + } + + private static GameVersion GetIsMachine1(int species, int move) + { + var index = Array.IndexOf(Legal.TMHM_RBY, move); + if (index == -1) + return Legal.NONE; + if (PersonalTable.RB.GetFormEntry(species, 0).TMHM[index]) + return GameVersion.RB; + if (PersonalTable.Y.GetFormEntry(species, 0).TMHM[index]) + return GameVersion.YW; + + return Legal.NONE; + } + + private static GameVersion GetIsMachine2(int species, int move) + { + var index = Array.IndexOf(Legal.TMHM_GSC, move); + if (index != -1 && PersonalTable.C.GetFormEntry(species, 0).TMHM[index]) + return GameVersion.GS; + + return Legal.NONE; + } + + private static GameVersion GetIsMachine3(int species, int move, int format, bool RemoveTransfer) + { + var index = Array.IndexOf(Legal.TM_3, move); + if (index != -1 && PersonalTable.E.GetFormEntry(species, 0).TMHM[index]) + return GameVersion.Gen3; + + if (!RemoveTransfer && format <= 3) + return GetIsMachine3HM(species, move); + + return Legal.NONE; + } + + private static GameVersion GetIsMachine3HM(int species, int move) + { + var index = Array.IndexOf(Legal.HM_3, move); + if (index == -1) + return Legal.NONE; + if (PersonalTable.E.GetFormEntry(species, 0).TMHM[index + 50]) + return GameVersion.Gen3; + return Legal.NONE; + } + + private static GameVersion GetIsMachine4(int species, int move, int format, bool RemoveTransfer, int form) + { + // TM + var index = Array.IndexOf(Legal.TM_4, move); + if (index != -1 && PersonalTable.HGSS.GetFormEntry(species, form).TMHM[index]) + return GameVersion.Gen4; + + // HM + if (RemoveTransfer && format > 4) + return GetIsMachine4HMTransfer(species, move, form); + + return GetIsMachine4HM(species, move, form); + } + + private static GameVersion GetIsMachine4HMTransfer(int species, int move, int form) + { + // The combination of both these moves is illegal, it should be checked that the pokemon only learn one + // except if it can learn any of these moves in gen 5 or later + switch (move) + { + case 250: // Whirlpool + if (PersonalTable.HGSS.GetFormEntry(species, form).TMHM[96]) + return GameVersion.HGSS; + break; + case 432: // Defog + if (PersonalTable.Pt.GetFormEntry(species, form).TMHM[96]) + return GameVersion.DPPt; + break; + } + return Legal.NONE; + } + + private static GameVersion GetIsMachine4HM(int species, int move, int form) + { + var dpi = Array.IndexOf(Legal.HM_DPPt, move); + if (dpi != 0 && PersonalTable.Pt.GetFormEntry(species, form).TMHM[92 + dpi]) + return GameVersion.DPPt; + + var gsi = Array.IndexOf(Legal.HM_HGSS, move); + if (gsi != 0 && PersonalTable.HGSS.GetFormEntry(species, form).TMHM[92 + gsi]) + return GameVersion.DPPt; + + return Legal.NONE; + } + + private static GameVersion GetIsMachine5(int species, int move, int form) + { + var index = Array.IndexOf(Legal.TMHM_BW, move); + if (index != -1 && PersonalTable.B2W2.GetFormEntry(species, form).TMHM[index]) + return GameVersion.Gen5; + + return Legal.NONE; + } + + private static GameVersion GetIsMachine6(int species, int move, int form, GameVersion ver) + { + if (GameVersion.XY.Contains(ver)) + { + var index = Array.IndexOf(Legal.TMHM_XY, move); + if (index != -1 && PersonalTable.XY.GetFormEntry(species, form).TMHM[index]) + return GameVersion.XY; + } + + if (GameVersion.ORAS.Contains(ver)) + { + var index = Array.IndexOf(Legal.TMHM_AO, move); + if (index != -1 && PersonalTable.AO.GetFormEntry(species, form).TMHM[index]) + return GameVersion.ORAS; + } + + return Legal.NONE; + } + + private static GameVersion GetIsMachine7(int species, int move, int form, GameVersion ver) + { + if (GameVersion.Gen7b.Contains(ver)) + { + var index = Array.IndexOf(Legal.TMHM_GG, move); + if (index != -1 && PersonalTable.GG.GetFormEntry(species, form).TMHM[index]) + return GameVersion.GG; + } + + if (GameVersion.SM.Contains(ver) && species <= Legal.MaxSpeciesID_7) + { + var index = Array.IndexOf(Legal.TMHM_SM, move); + if (index != -1 && PersonalTable.SM.GetFormEntry(species, form).TMHM[index]) + return GameVersion.SM; + } + + if (GameVersion.USUM.Contains(ver) && species <= Legal.MaxSpeciesID_7_USUM) + { + var index = Array.IndexOf(Legal.TMHM_SM, move); + if (index != -1 && PersonalTable.USUM.GetFormEntry(species, form).TMHM[index]) + return GameVersion.USUM; + } + + return Legal.NONE; + } + + private static GameVersion GetIsMachine8(int species, int move, int form, GameVersion ver) + { + if (GameVersion.SWSH.Contains(ver)) + { + var index = Legal.TMHM_SWSH.AsSpan(0, PersonalInfoSWSH.CountTM).IndexOf(move); + if (index != -1 && PersonalTable.SWSH.GetFormEntry(species, form).TMHM[index]) + return GameVersion.SWSH; + } + + if (GameVersion.BDSP.Contains(ver)) + { + var index = Legal.TMHM_BDSP.AsSpan(0, PersonalInfoBDSP.CountTM).IndexOf(move); + if (index != -1 && PersonalTable.BDSP.GetFormEntry(species, form).TMHM[index]) + return GameVersion.BDSP; + } + + return Legal.NONE; + } + + private static GameVersion GetIsRecord8(PKM pk, int species, int move, int form, GameVersion ver, bool allowBit) + { + if (GameVersion.SWSH.Contains(ver)) + { + var index = Legal.TMHM_SWSH.AsSpan(PersonalInfoSWSH.CountTM, PersonalInfoSWSH.CountTR).IndexOf(move); + if (index != -1) + { + if (allowBit) + return GameVersion.SWSH; + if (((ITechRecord8)pk).GetMoveRecordFlag(index)) + return GameVersion.SWSH; + if (index == 12 && species == (int)Species.Calyrex && form == 0) // TR12 + return GameVersion.SWSH; // Agility Calyrex without TR glitch. } } - public static GameVersion GetIsRecordMove(PKM pkm, int species, int form, int generation, int move, GameVersion ver = GameVersion.Any, bool allowBit = false) + return Legal.NONE; + } + + public static IEnumerable GetTMHM(PKM pk, int species, int form, int generation, GameVersion ver = GameVersion.Any, bool RemoveTransfer = true) + { + var r = new List(); + var (isRestricted, game) = pk.IsMovesetRestricted(); + if (isRestricted) + ver = game; + + switch (generation) { - var (isRestricted, game) = pkm.IsMovesetRestricted(); - if (isRestricted) - ver = game; - - return generation switch - { - 8 => GetIsRecord8(pkm, species, move, form, ver, allowBit), - _ => Legal.NONE, - }; + case 1: AddMachine1(r, species); break; + case 2: AddMachine2(r, species); + if (pk.Format >= 7 && pk.VC1) + r.RemoveAll(z => z > Legal.MaxMoveID_1); + break; + case 3: AddMachine3(r, species, pk.Format, RemoveTransfer); break; + case 4: AddMachine4(r, species, pk.Format, RemoveTransfer, form); break; + case 5: AddMachine5(r, species, form); break; + case 6: AddMachine6(r, species, form, ver); break; + case 7: AddMachine7(r, species, form, (pk.LGPE || pk.GO) ? (GameVersion)pk.Version : ver); break; + case 8: AddMachine8(r, species, form, ver); break; } + return r; + } - private static GameVersion GetIsMachine1(int species, int move) + public static IEnumerable GetRecords(PKM pk, int species, int form, int generation) + { + var r = new List(); + switch (generation) { - var index = Array.IndexOf(Legal.TMHM_RBY, move); - if (index == -1) - return Legal.NONE; - if (PersonalTable.RB.GetFormEntry(species, 0).TMHM[index]) - return GameVersion.RB; - if (PersonalTable.Y.GetFormEntry(species, 0).TMHM[index]) - return GameVersion.YW; - - return Legal.NONE; + case 8: AddRecordSWSH(r, species, form, pk); break; } + return r; + } - private static GameVersion GetIsMachine2(int species, int move) + private static void AddPermittedIndexes(List moves, ReadOnlySpan moveIDs, ReadOnlySpan permit) + { + for (int i = 0; i < moveIDs.Length; i++) { - var index = Array.IndexOf(Legal.TMHM_GSC, move); - if (index != -1 && PersonalTable.C.GetFormEntry(species, 0).TMHM[index]) - return GameVersion.GS; - - return Legal.NONE; + if (permit[i]) + moves.Add(moveIDs[i]); } + } - private static GameVersion GetIsMachine3(int species, int move, int format, bool RemoveTransfer) - { - var index = Array.IndexOf(Legal.TM_3, move); - if (index != -1 && PersonalTable.E.GetFormEntry(species, 0).TMHM[index]) - return GameVersion.Gen3; + private static void AddMachine1(List r, int species) + { + int index = PersonalTable.RB.GetFormIndex(species, 0); + if (index == 0) + return; + var pi_rb = (PersonalInfoG1)PersonalTable.RB[index]; + var pi_y = (PersonalInfoG1)PersonalTable.Y[index]; + AddPermittedIndexes(r, Legal.TMHM_RBY, pi_rb.TMHM); + AddPermittedIndexes(r, Legal.TMHM_RBY, pi_y.TMHM); + } - if (!RemoveTransfer && format <= 3) - return GetIsMachine3HM(species, move); + private static void AddMachine2(List r, int species) + { + int index = PersonalTable.C.GetFormIndex(species, 0); + if (index == 0) + return; + var pi_c = PersonalTable.C[index]; + AddPermittedIndexes(r, Legal.TMHM_GSC, pi_c.TMHM); + } - return Legal.NONE; - } + private static void AddMachine3(List r, int species, int format, bool RemoveTransfer) + { + int index = PersonalTable.E.GetFormIndex(species, 0); + if (index == 0) + return; + var pi_c = PersonalTable.E[index]; + AddPermittedIndexes(r, Legal.TM_3, pi_c.TMHM); - private static GameVersion GetIsMachine3HM(int species, int move) - { - var index = Array.IndexOf(Legal.HM_3, move); - if (index == -1) - return Legal.NONE; - if (PersonalTable.E.GetFormEntry(species, 0).TMHM[index + 50]) - return GameVersion.Gen3; - return Legal.NONE; - } + if (!RemoveTransfer || format == 3) // HM moves must be removed for 3->4, only give if current format. + AddPermittedIndexes(r, Legal.HM_3, pi_c.TMHM.AsSpan(50)); + } - private static GameVersion GetIsMachine4(int species, int move, int format, bool RemoveTransfer, int form) - { - // TM - var index = Array.IndexOf(Legal.TM_4, move); - if (index != -1 && PersonalTable.HGSS.GetFormEntry(species, form).TMHM[index]) - return GameVersion.Gen4; + private static void AddMachine4(List r, int species, int format, bool RemoveTransfer, int form) + { + var pi_hgss = PersonalTable.HGSS.GetFormEntry(species, form); + var pi_dppt = PersonalTable.Pt.GetFormEntry(species, form); + AddPermittedIndexes(r, Legal.TM_4, pi_hgss.TMHM); - // HM - if (RemoveTransfer && format > 4) - return GetIsMachine4HMTransfer(species, move, form); - - return GetIsMachine4HM(species, move, form); - } - - private static GameVersion GetIsMachine4HMTransfer(int species, int move, int form) + if (RemoveTransfer && format > 4) { // The combination of both these moves is illegal, it should be checked that the pokemon only learn one // except if it can learn any of these moves in gen 5 or later - switch (move) - { - case 250: // Whirlpool - if (PersonalTable.HGSS.GetFormEntry(species, form).TMHM[96]) - return GameVersion.HGSS; - break; - case 432: // Defog - if (PersonalTable.Pt.GetFormEntry(species, form).TMHM[96]) - return GameVersion.DPPt; - break; - } - return Legal.NONE; + if (pi_hgss.TMHM[96]) + r.Add(250); // Whirlpool + if (pi_dppt.TMHM[96]) + r.Add(432); // Defog } - - private static GameVersion GetIsMachine4HM(int species, int move, int form) + else { - var dpi = Array.IndexOf(Legal.HM_DPPt, move); - if (dpi != 0 && PersonalTable.Pt.GetFormEntry(species, form).TMHM[92 + dpi]) - return GameVersion.DPPt; - - var gsi = Array.IndexOf(Legal.HM_HGSS, move); - if (gsi != 0 && PersonalTable.HGSS.GetFormEntry(species, form).TMHM[92 + gsi]) - return GameVersion.DPPt; - - return Legal.NONE; + AddPermittedIndexes(r, Legal.HM_DPPt, pi_dppt.TMHM.AsSpan(92)); + AddPermittedIndexes(r, Legal.HM_HGSS, pi_hgss.TMHM.AsSpan(92)); } + } - private static GameVersion GetIsMachine5(int species, int move, int form) + private static void AddMachine5(List r, int species, int form) + { + var pi = PersonalTable.B2W2.GetFormEntry(species, form); + AddPermittedIndexes(r, Legal.TMHM_BW, pi.TMHM); + } + + private static void AddMachine6(List r, int species, int form, GameVersion ver = GameVersion.Any) + { + switch (ver) { - var index = Array.IndexOf(Legal.TMHM_BW, move); - if (index != -1 && PersonalTable.B2W2.GetFormEntry(species, form).TMHM[index]) - return GameVersion.Gen5; - - return Legal.NONE; - } - - private static GameVersion GetIsMachine6(int species, int move, int form, GameVersion ver) - { - if (GameVersion.XY.Contains(ver)) - { - var index = Array.IndexOf(Legal.TMHM_XY, move); - if (index != -1 && PersonalTable.XY.GetFormEntry(species, form).TMHM[index]) - return GameVersion.XY; - } - - if (GameVersion.ORAS.Contains(ver)) - { - var index = Array.IndexOf(Legal.TMHM_AO, move); - if (index != -1 && PersonalTable.AO.GetFormEntry(species, form).TMHM[index]) - return GameVersion.ORAS; - } - - return Legal.NONE; - } - - private static GameVersion GetIsMachine7(int species, int move, int form, GameVersion ver) - { - if (GameVersion.Gen7b.Contains(ver)) - { - var index = Array.IndexOf(Legal.TMHM_GG, move); - if (index != -1 && PersonalTable.GG.GetFormEntry(species, form).TMHM[index]) - return GameVersion.GG; - } - - if (GameVersion.SM.Contains(ver) && species <= Legal.MaxSpeciesID_7) - { - var index = Array.IndexOf(Legal.TMHM_SM, move); - if (index != -1 && PersonalTable.SM.GetFormEntry(species, form).TMHM[index]) - return GameVersion.SM; - } - - if (GameVersion.USUM.Contains(ver) && species <= Legal.MaxSpeciesID_7_USUM) - { - var index = Array.IndexOf(Legal.TMHM_SM, move); - if (index != -1 && PersonalTable.USUM.GetFormEntry(species, form).TMHM[index]) - return GameVersion.USUM; - } - - return Legal.NONE; - } - - private static GameVersion GetIsMachine8(int species, int move, int form, GameVersion ver) - { - if (GameVersion.SWSH.Contains(ver)) - { - var index = Legal.TMHM_SWSH.AsSpan(0, PersonalInfoSWSH.CountTM).IndexOf(move); - if (index != -1 && PersonalTable.SWSH.GetFormEntry(species, form).TMHM[index]) - return GameVersion.SWSH; - } - - if (GameVersion.BDSP.Contains(ver)) - { - var index = Legal.TMHM_BDSP.AsSpan(0, PersonalInfoBDSP.CountTM).IndexOf(move); - if (index != -1 && PersonalTable.BDSP.GetFormEntry(species, form).TMHM[index]) - return GameVersion.BDSP; - } - - return Legal.NONE; - } - - private static GameVersion GetIsRecord8(PKM pkm, int species, int move, int form, GameVersion ver, bool allowBit) - { - if (GameVersion.SWSH.Contains(ver)) - { - var index = Legal.TMHM_SWSH.AsSpan(PersonalInfoSWSH.CountTM, PersonalInfoSWSH.CountTR).IndexOf(move); - if (index != -1) - { - if (allowBit) - return GameVersion.SWSH; - if (((ITechRecord8)pkm).GetMoveRecordFlag(index)) - return GameVersion.SWSH; - if (index == 12 && species == (int)Species.Calyrex && form == 0) // TR12 - return GameVersion.SWSH; // Agility Calyrex without TR glitch. - } - } - - return Legal.NONE; - } - - public static IEnumerable GetTMHM(PKM pkm, int species, int form, int generation, GameVersion ver = GameVersion.Any, bool RemoveTransfer = true) - { - var r = new List(); - var (isRestricted, game) = pkm.IsMovesetRestricted(); - if (isRestricted) - ver = game; - - switch (generation) - { - case 1: AddMachine1(r, species); break; - case 2: AddMachine2(r, species); - if (pkm.Format >= 7 && pkm.VC1) - r.RemoveAll(z => z > Legal.MaxMoveID_1); - break; - case 3: AddMachine3(r, species, pkm.Format, RemoveTransfer); break; - case 4: AddMachine4(r, species, pkm.Format, RemoveTransfer, form); break; - case 5: AddMachine5(r, species, form); break; - case 6: AddMachine6(r, species, form, ver); break; - case 7: AddMachine7(r, species, form, (pkm.LGPE || pkm.GO) ? (GameVersion)pkm.Version : ver); break; - case 8: AddMachine8(r, species, form, ver); break; - } - return r; - } - - public static IEnumerable GetRecords(PKM pkm, int species, int form, int generation) - { - var r = new List(); - switch (generation) - { - case 8: AddRecordSWSH(r, species, form, pkm); break; - } - return r; - } - - private static void AddPermittedIndexes(List moves, ReadOnlySpan moveIDs, ReadOnlySpan permit) - { - for (int i = 0; i < moveIDs.Length; i++) - { - if (permit[i]) - moves.Add(moveIDs[i]); - } - } - - private static void AddMachine1(List r, int species) - { - int index = PersonalTable.RB.GetFormIndex(species, 0); - if (index == 0) - return; - var pi_rb = (PersonalInfoG1)PersonalTable.RB[index]; - var pi_y = (PersonalInfoG1)PersonalTable.Y[index]; - AddPermittedIndexes(r, Legal.TMHM_RBY, pi_rb.TMHM); - AddPermittedIndexes(r, Legal.TMHM_RBY, pi_y.TMHM); - } - - private static void AddMachine2(List r, int species) - { - int index = PersonalTable.C.GetFormIndex(species, 0); - if (index == 0) - return; - var pi_c = PersonalTable.C[index]; - AddPermittedIndexes(r, Legal.TMHM_GSC, pi_c.TMHM); - } - - private static void AddMachine3(List r, int species, int format, bool RemoveTransfer) - { - int index = PersonalTable.E.GetFormIndex(species, 0); - if (index == 0) - return; - var pi_c = PersonalTable.E[index]; - AddPermittedIndexes(r, Legal.TM_3, pi_c.TMHM); - - if (!RemoveTransfer || format == 3) // HM moves must be removed for 3->4, only give if current format. - AddPermittedIndexes(r, Legal.HM_3, pi_c.TMHM.AsSpan(50)); - } - - private static void AddMachine4(List r, int species, int format, bool RemoveTransfer, int form) - { - var pi_hgss = PersonalTable.HGSS.GetFormEntry(species, form); - var pi_dppt = PersonalTable.Pt.GetFormEntry(species, form); - AddPermittedIndexes(r, Legal.TM_4, pi_hgss.TMHM); - - if (RemoveTransfer && format > 4) - { - // The combination of both these moves is illegal, it should be checked that the pokemon only learn one - // except if it can learn any of these moves in gen 5 or later - if (pi_hgss.TMHM[96]) - r.Add(250); // Whirlpool - if (pi_dppt.TMHM[96]) - r.Add(432); // Defog - } - else - { - AddPermittedIndexes(r, Legal.HM_DPPt, pi_dppt.TMHM.AsSpan(92)); - AddPermittedIndexes(r, Legal.HM_HGSS, pi_hgss.TMHM.AsSpan(92)); - } - } - - private static void AddMachine5(List r, int species, int form) - { - var pi = PersonalTable.B2W2.GetFormEntry(species, form); - AddPermittedIndexes(r, Legal.TMHM_BW, pi.TMHM); - } - - private static void AddMachine6(List r, int species, int form, GameVersion ver = GameVersion.Any) - { - switch (ver) - { - case GameVersion.Any: // Start at the top, hit every table - case GameVersion.X or GameVersion.Y or GameVersion.XY: - AddMachine6XY(r, species, form); - if (ver == GameVersion.Any) // Fall Through - AddMachine6AO(r, species, form); - break; - case GameVersion.AS or GameVersion.OR or GameVersion.ORAS: + case GameVersion.Any: // Start at the top, hit every table + case GameVersion.X or GameVersion.Y or GameVersion.XY: + AddMachine6XY(r, species, form); + if (ver == GameVersion.Any) // Fall Through AddMachine6AO(r, species, form); - break; - } + break; + case GameVersion.AS or GameVersion.OR or GameVersion.ORAS: + AddMachine6AO(r, species, form); + break; } + } - private static void AddMachine7(List r, int species, int form, GameVersion ver = GameVersion.Any) + private static void AddMachine7(List r, int species, int form, GameVersion ver = GameVersion.Any) + { + switch (ver) { - switch (ver) + case GameVersion.GP or GameVersion.GE or GameVersion.GG or GameVersion.GO: + AddMachineGG(r, species, form); + return; + case GameVersion.SN or GameVersion.MN or GameVersion.SM: + if (species <= Legal.MaxSpeciesID_7) + AddMachineSM(r, species, form); + return; + case GameVersion.Any: + case GameVersion.US or GameVersion.UM or GameVersion.USUM: + AddMachineUSUM(r, species, form); + if (ver == GameVersion.Any) // Fall Through + AddMachineSM(r, species, form); + return; + } + } + + private static void AddMachine8(List r, int species, int form, GameVersion ver = GameVersion.Any) + { + switch (ver) + { + case GameVersion.Any: + case GameVersion.SW or GameVersion.SH or GameVersion.SWSH: + AddMachineSWSH(r, species, form); + return; + case GameVersion.BD or GameVersion.SP or GameVersion.BDSP: + AddMachineBDSP(r, species, form); + return; + } + } + + private static void AddMachine6XY(List r, int species, int form) + { + var pi = PersonalTable.XY.GetFormEntry(species, form); + AddPermittedIndexes(r, Legal.TMHM_XY, pi.TMHM); + } + + private static void AddMachine6AO(List r, int species, int form) + { + var pi = PersonalTable.AO.GetFormEntry(species, form); + AddPermittedIndexes(r, Legal.TMHM_AO, pi.TMHM); + } + + private static void AddMachineSM(List r, int species, int form) + { + if (species > Legal.MaxSpeciesID_7) + return; + var pi = PersonalTable.SM.GetFormEntry(species, form); + AddPermittedIndexes(r, Legal.TMHM_SM, pi.TMHM); + } + + private static void AddMachineUSUM(List r, int species, int form) + { + var pi = PersonalTable.USUM.GetFormEntry(species, form); + AddPermittedIndexes(r, Legal.TMHM_SM, pi.TMHM); + } + + private static void AddMachineGG(List r, int species, int form) + { + if (species > Legal.MaxSpeciesID_7b) + return; + var pi = PersonalTable.GG.GetFormEntry(species, form); + AddPermittedIndexes(r, Legal.TMHM_GG, pi.TMHM); + } + + private static void AddMachineSWSH(List r, int species, int form) + { + if (species > Legal.MaxSpeciesID_8) + return; + var pi = PersonalTable.SWSH.GetFormEntry(species, form); + var tmhm = pi.TMHM; + var arr = Legal.TMHM_SWSH.AsSpan(0, PersonalInfoSWSH.CountTM); + AddPermittedIndexes(r, arr, tmhm); + } + + private static void AddMachineBDSP(List r, int species, int form) + { + if (species > Legal.MaxSpeciesID_8b) + return; + var pi = PersonalTable.BDSP.GetFormEntry(species, form); + AddPermittedIndexes(r, Legal.TMHM_BDSP, pi.TMHM); + } + + public static void AddRecordSWSH(List r, int species, int form, PKM pk) + { + if (pk is not PK8 pk8) + return; + var pi = PersonalTable.SWSH.GetFormEntry(species, form); + var tmhm = pi.TMHM; + for (int i = 0; i < PersonalInfoSWSH.CountTR; i++) + { + var index = i + PersonalInfoSWSH.CountTM; + if (!tmhm[index]) + continue; + if (!pk8.GetMoveRecordFlag(i)) { - case GameVersion.GP or GameVersion.GE or GameVersion.GG or GameVersion.GO: - AddMachineGG(r, species, form); - return; - case GameVersion.SN or GameVersion.MN or GameVersion.SM: - if (species <= Legal.MaxSpeciesID_7) - AddMachineSM(r, species, form); - return; - case GameVersion.Any: - case GameVersion.US or GameVersion.UM or GameVersion.USUM: - AddMachineUSUM(r, species, form); - if (ver == GameVersion.Any) // Fall Through - AddMachineSM(r, species, form); - return; - } - } - - private static void AddMachine8(List r, int species, int form, GameVersion ver = GameVersion.Any) - { - switch (ver) - { - case GameVersion.Any: - case GameVersion.SW or GameVersion.SH or GameVersion.SWSH: - AddMachineSWSH(r, species, form); - return; - case GameVersion.BD or GameVersion.SP or GameVersion.BDSP: - AddMachineBDSP(r, species, form); - return; - } - } - - private static void AddMachine6XY(List r, int species, int form) - { - var pi = PersonalTable.XY.GetFormEntry(species, form); - AddPermittedIndexes(r, Legal.TMHM_XY, pi.TMHM); - } - - private static void AddMachine6AO(List r, int species, int form) - { - var pi = PersonalTable.AO.GetFormEntry(species, form); - AddPermittedIndexes(r, Legal.TMHM_AO, pi.TMHM); - } - - private static void AddMachineSM(List r, int species, int form) - { - if (species > Legal.MaxSpeciesID_7) - return; - var pi = PersonalTable.SM.GetFormEntry(species, form); - AddPermittedIndexes(r, Legal.TMHM_SM, pi.TMHM); - } - - private static void AddMachineUSUM(List r, int species, int form) - { - var pi = PersonalTable.USUM.GetFormEntry(species, form); - AddPermittedIndexes(r, Legal.TMHM_SM, pi.TMHM); - } - - private static void AddMachineGG(List r, int species, int form) - { - if (species > Legal.MaxSpeciesID_7b) - return; - var pi = PersonalTable.GG.GetFormEntry(species, form); - AddPermittedIndexes(r, Legal.TMHM_GG, pi.TMHM); - } - - private static void AddMachineSWSH(List r, int species, int form) - { - if (species > Legal.MaxSpeciesID_8) - return; - var pi = PersonalTable.SWSH.GetFormEntry(species, form); - var tmhm = pi.TMHM; - var arr = Legal.TMHM_SWSH.AsSpan(0, PersonalInfoSWSH.CountTM); - AddPermittedIndexes(r, arr, tmhm); - } - - private static void AddMachineBDSP(List r, int species, int form) - { - if (species > Legal.MaxSpeciesID_8b) - return; - var pi = PersonalTable.BDSP.GetFormEntry(species, form); - AddPermittedIndexes(r, Legal.TMHM_BDSP, pi.TMHM); - } - - public static void AddRecordSWSH(List r, int species, int form, PKM pkm) - { - if (pkm is not PK8 pk8) - return; - var pi = PersonalTable.SWSH.GetFormEntry(species, form); - var tmhm = pi.TMHM; - for (int i = 0; i < PersonalInfoSWSH.CountTR; i++) - { - var index = i + PersonalInfoSWSH.CountTM; - if (!tmhm[index]) - continue; - if (!pk8.GetMoveRecordFlag(i)) + if (i == 12 && species == (int) Species.Calyrex && form == 0) // TR12 { - if (i == 12 && species == (int) Species.Calyrex && form == 0) // TR12 - { - // Unfuse logic does not check if the currently known TR move has the flag or not. - // Agility can be learned via Level Up while fused, but only via TR when Unfused. - // We'll let Agility be a legal TR move without the flag, only for Calyrex! - } - else - { - continue; - } + // Unfuse logic does not check if the currently known TR move has the flag or not. + // Agility can be learned via Level Up while fused, but only via TR when Unfused. + // We'll let Agility be a legal TR move without the flag, only for Calyrex! } - r.Add(Legal.TMHM_SWSH[index]); - } - } - - public static IEnumerable GetAllPossibleRecords(int species, int form) - { - var pi = PersonalTable.SWSH.GetFormEntry(species, form); - var tmhm = pi.TMHM; - for (int i = 0; i < PersonalInfoSWSH.CountTM; i++) - { - var index = i + PersonalInfoSWSH.CountTM; - if (!tmhm[index]) + else + { continue; - yield return Legal.TMHM_SWSH[index]; + } } + r.Add(Legal.TMHM_SWSH[index]); + } + } + + public static IEnumerable GetAllPossibleRecords(int species, int form) + { + var pi = PersonalTable.SWSH.GetFormEntry(species, form); + var tmhm = pi.TMHM; + for (int i = 0; i < PersonalInfoSWSH.CountTM; i++) + { + var index = i + PersonalInfoSWSH.CountTM; + if (!tmhm[index]) + continue; + yield return Legal.TMHM_SWSH[index]; } } } diff --git a/PKHeX.Core/Legality/Moves/MoveTutor.cs b/PKHeX.Core/Legality/Moves/MoveTutor.cs index d701af290..81d529255 100644 --- a/PKHeX.Core/Legality/Moves/MoveTutor.cs +++ b/PKHeX.Core/Legality/Moves/MoveTutor.cs @@ -3,401 +3,400 @@ using static PKHeX.Core.Legal; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class MoveTutor { - public static class MoveTutor + public static GameVersion GetIsTutorMove(PKM pk, int species, int form, int generation, int move, bool specialTutors = true) { - public static GameVersion GetIsTutorMove(PKM pkm, int species, int form, int generation, int move, bool specialTutors = true) + return generation switch { - return generation switch - { - 1 => GetIsTutor1(pkm, species, move), - 2 => GetIsTutor2(pkm, species, move), - 3 => GetIsTutor3(species, move), - 4 => GetIsTutor4(species, form, move), - 5 => GetIsTutor5(pkm, species, form, specialTutors, move), - 6 => GetIsTutor6(pkm, species, form, specialTutors, move), - 7 => GetIsTutor7(pkm, species, form, specialTutors, move), - 8 => GetIsTutor8(pkm, species, form, specialTutors, move), - _ => NONE, - }; - } + 1 => GetIsTutor1(pk, species, move), + 2 => GetIsTutor2(pk, species, move), + 3 => GetIsTutor3(species, move), + 4 => GetIsTutor4(species, form, move), + 5 => GetIsTutor5(pk, species, form, specialTutors, move), + 6 => GetIsTutor6(pk, species, form, specialTutors, move), + 7 => GetIsTutor7(pk, species, form, specialTutors, move), + 8 => GetIsTutor8(pk, species, form, specialTutors, move), + _ => NONE, + }; + } - private static GameVersion GetIsTutor1(PKM pkm, int species, int move) - { - // Surf Pikachu via Stadium - if (move != (int)Move.Surf || ParseSettings.AllowGBCartEra) - return NONE; - if (pkm.Format < 3 && species is (int)Species.Pikachu or (int)Species.Raichu) - return GameVersion.Stadium; + private static GameVersion GetIsTutor1(PKM pk, int species, int move) + { + // Surf Pikachu via Stadium + if (move != (int)Move.Surf || ParseSettings.AllowGBCartEra) return NONE; - } + if (pk.Format < 3 && species is (int)Species.Pikachu or (int)Species.Raichu) + return GameVersion.Stadium; + return NONE; + } - private static GameVersion GetIsTutor2(PKM pkm, int species, int move) - { - if (!ParseSettings.AllowGen2Crystal(pkm)) - return NONE; - var info = PersonalTable.C[species]; - var tutor = Array.IndexOf(Tutors_GSC, move); - if (tutor != -1 && info.TMHM[57 + tutor]) - return GameVersion.C; + private static GameVersion GetIsTutor2(PKM pk, int species, int move) + { + if (!ParseSettings.AllowGen2Crystal(pk)) return NONE; - } + var info = PersonalTable.C[species]; + var tutor = Array.IndexOf(Tutors_GSC, move); + if (tutor != -1 && info.TMHM[57 + tutor]) + return GameVersion.C; + return NONE; + } - private static GameVersion GetIsTutor3(int species, int move) - { - // E Tutors (Free) - // E Tutors (BP) - var info = PersonalTable.E[species]; - var e = Array.IndexOf(Tutor_E, move); - if (e != -1 && info.TypeTutors[e]) - return GameVersion.E; - - // FRLG Tutors - // Only special tutor moves, normal tutor moves are already included in Emerald data - var frlg = Array.IndexOf(SpecialTutors_FRLG, move); - if (frlg != -1 && SpecialTutors_Compatibility_FRLG[frlg] == species) - return GameVersion.FRLG; - - // XD - var xd = Array.IndexOf(SpecialTutors_XD_Exclusive, move); - if (xd != -1 && SpecialTutors_Compatibility_XD_Exclusive[xd].AsSpan().IndexOf(species) != -1) - return GameVersion.XD; - - // XD (Mew) - if (species == (int)Species.Mew && Tutor_3Mew.AsSpan().IndexOf(move) != -1) - return GameVersion.XD; - - return NONE; - } - - private static GameVersion GetIsTutor4(int species, int form, int move) - { - var pi = PersonalTable.HGSS[species, form]; - var type = Array.IndexOf(Tutors_4, move); - if (type != -1 && pi.TypeTutors[type]) - return GameVersion.Gen4; - - var special = Array.IndexOf(SpecialTutors_4, move); - if (special != -1 && SpecialTutors_Compatibility_4[special].AsSpan().IndexOf(species) != -1) - return GameVersion.HGSS; - - return NONE; - } - - private static GameVersion GetIsTutor5(PKM pkm, int species, int form, bool specialTutors, int move) - { - var pi = PersonalTable.B2W2[species, form]; - var type = Array.IndexOf(TypeTutor6, move); - if (type != -1 && pi.TypeTutors[type]) - return GameVersion.Gen5; - - if (specialTutors && pkm.HasVisitedB2W2(species)) - { - var tutors = Tutors_B2W2; - for (int i = 0; i < tutors.Length; i++) - { - var tutor = Array.IndexOf(tutors[i], move); - if (tutor == -1) - continue; - if (pi.SpecialTutors[i][tutor]) - return GameVersion.B2W2; - break; - } - } - - return NONE; - } - - private static GameVersion GetIsTutor6(PKM pkm, int species, int form, bool specialTutors, int move) - { - var pi = PersonalTable.AO[species, form]; - var type = Array.IndexOf(TypeTutor6, move); - if (type != -1 && pi.TypeTutors[type]) - return GameVersion.Gen6; - - if (specialTutors && pkm.HasVisitedORAS(species)) - { - var tutors = Tutors_AO; - for (int i = 0; i < tutors.Length; i++) - { - var tutor = Array.IndexOf(tutors[i], move); - if (tutor == -1) - continue; - if (pi.SpecialTutors[i][tutor]) - return GameVersion.ORAS; - break; - } - } - - return NONE; - } - - private static GameVersion GetIsTutor7(PKM pkm, int species, int form, bool specialTutors, int move) - { - var pi = PersonalTable.USUM[species, form]; - var type = Array.IndexOf(TypeTutor6, move); - if (type != -1 && pi.TypeTutors[type]) - return GameVersion.Gen7; - - if (specialTutors && pkm.HasVisitedUSUM(species)) - { - var tutor = Array.IndexOf(Tutors_USUM, move); - if (tutor != -1 && pi.SpecialTutors[0][tutor]) - return GameVersion.USUM; - } - - return NONE; - } - - private static GameVersion GetIsTutor8(PKM pkm, int species, int form, bool specialTutors, int move) - { - if (pkm is PA8) - { - var pi = (PersonalInfoLA)PersonalTable.LA[species, form]; - if (!pi.IsPresentInGame) - return NONE; - var index = Array.IndexOf(MoveShop8_LA, (ushort)move); - if (index != -1 && pi.SpecialTutors[0][index]) - return GameVersion.PLA; - - return NONE; - } - if (pkm is PB8) - { - var pi = (PersonalInfoBDSP)PersonalTable.BDSP[species, form]; - if (!pi.IsPresentInGame) - return NONE; - var type = Array.IndexOf(TypeTutor8b, move); - if (type != -1 && pi.TypeTutors[type]) - return GameVersion.BDSP; - - return NONE; - } - else - { - var pi = (PersonalInfoSWSH)PersonalTable.SWSH[species, form]; - if (!pi.IsPresentInGame) - return NONE; - var type = Array.IndexOf(TypeTutor8, move); - if (type != -1 && pi.TypeTutors[type]) - return GameVersion.SWSH; - - if (!specialTutors) - return NONE; - - var tutor = Array.IndexOf(Tutors_SWSH_1, move); - if (tutor != -1 && pi.SpecialTutors[0][tutor]) - return GameVersion.SWSH; - - return NONE; - } - } - - public static IEnumerable GetTutorMoves(PKM pkm, int species, int form, bool specialTutors, int generation) - { - List moves = new(); - switch (generation) - { - case 1: AddMovesTutor1(moves, species, pkm.Format); break; - case 2: AddMovesTutor2(moves, species, pkm.Format, pkm.Korean); break; - case 3: AddMovesTutor3(moves, species); break; - case 4: AddMovesTutor4(moves, species, form); break; - case 5: AddMovesTutor5(moves, species, form, pkm, specialTutors); break; - case 6: AddMovesTutor6(moves, species, form, pkm, specialTutors); break; - case 7: AddMovesTutor7(moves, species, form, pkm, specialTutors); break; - case 8: AddMovesTutor8(moves, species, form, pkm, specialTutors); break; - } - return moves; - } - - private static void AddMovesTutor1(List moves, int species, int format) - { - if (ParseSettings.AllowGBCartEra && format < 3 && species is (int)Species.Pikachu or (int)Species.Raichu) // Surf Pikachu via Stadium - moves.Add(57); - } - - private static void AddMovesTutor2(List moves, int species, int format, bool korean = false) - { - if (korean) - return; - var pi = PersonalTable.C[species]; - var hmBits = pi.TMHM.AsSpan(57, 3); - for (int i = 0; i < Tutors_GSC.Length; i++) - { - if (hmBits[i]) - moves.Add(Tutors_GSC[i]); - } - AddMovesTutor1(moves, species, format); - } - - private static void AddMovesTutor3(List moves, int species) - { - // E Tutors (Free) - // E Tutors (BP) - var pi = PersonalTable.E[species]; - AddPermittedIndexes(moves, Tutor_E, pi.TypeTutors); - // FRLG Tutors - // Only special tutor moves, normal tutor moves are already included in Emerald data - AddIfPermitted(moves, SpecialTutors_FRLG, SpecialTutors_Compatibility_FRLG, species); - // XD - for (int i = 0; i < SpecialTutors_XD_Exclusive.Length; i++) - { - var allowed = SpecialTutors_Compatibility_XD_Exclusive[i].AsSpan(); - var index = allowed.IndexOf(species); - if (index != -1) - moves.Add(SpecialTutors_XD_Exclusive[i]); - } - // XD (Mew) - if (species == (int)Species.Mew) - moves.AddRange(Tutor_3Mew); - } - - private static void AddMovesTutor4(List moves, int species, int form) - { - var pi = PersonalTable.HGSS[species, form]; - AddPermittedIndexes(moves, Tutors_4, pi.TypeTutors); - for (int i = 0; i < SpecialTutors_4.Length; i++) - { - var allowed = SpecialTutors_Compatibility_4[i].AsSpan(); - var index = allowed.IndexOf(species); - if (index != -1) - moves.Add(SpecialTutors_4[i]); - } - } - - private static void AddMovesTutor5(List moves, int species, int form, PKM pkm, bool specialTutors) - { - var pi = PersonalTable.B2W2[species, form]; - AddPermittedIndexes(moves, TypeTutor6, pi.TypeTutors); - if (pkm.InhabitedGeneration(5) && specialTutors) - AddPermittedIndexes(moves, Tutors_B2W2, pi.SpecialTutors); - } - - private static void AddMovesTutor6(List moves, int species, int form, PKM pkm, bool specialTutors) - { - var pi = PersonalTable.AO[species, form]; - AddPermittedIndexes(moves, TypeTutor6, pi.TypeTutors); - if (specialTutors && pkm.HasVisitedORAS(species)) - AddPermittedIndexes(moves, Tutors_AO, pi.SpecialTutors); - } - - private static void AddMovesTutor7(List moves, int species, int form, PKM pkm, bool specialTutors) - { - if (pkm.Version is (int)GameVersion.GO or (int)GameVersion.GP or (int)GameVersion.GE) - return; - var pi = PersonalTable.USUM[species, form]; - AddPermittedIndexes(moves, TypeTutor6, pi.TypeTutors); - if (specialTutors && pkm.HasVisitedUSUM(species)) - AddPermittedIndexes(moves, Tutors_USUM, pi.SpecialTutors[0]); - } - - private static void AddMovesTutor8(List moves, int species, int form, PKM pkm, bool specialTutors) - { - if (pkm is PA8) - { - var pi = (PersonalInfoLA)PersonalTable.LA[species, form]; - if (!pi.IsPresentInGame) - return; - var shop = MoveShop8_LA; - var tutors = pi.SpecialTutors[0]; - for (int i = 0; i < shop.Length; i++) - { - if (tutors[i]) - moves.Add(shop[i]); - } - return; - } - if (pkm is PB8) - { - var pi = (PersonalInfoBDSP)PersonalTable.BDSP[species, form]; - if (pi.IsPresentInGame) - AddPermittedIndexes(moves, TypeTutor8b, pi.TypeTutors); - } - else // SWSH - { - var pi = (PersonalInfoSWSH)PersonalTable.SWSH[species, form]; - if (!pi.IsPresentInGame) - return; - AddPermittedIndexes(moves, TypeTutor8, pi.TypeTutors); - if (specialTutors) - AddPermittedIndexes(moves, Tutors_SWSH_1, pi.SpecialTutors[0]); - } - } - - private static void AddIfPermitted(List moves, ReadOnlySpan arrMoves, ReadOnlySpan arrSpecies, int species) - { - var index = arrSpecies.IndexOf(species); - if (index != -1) - moves.Add(arrMoves[index]); - } - - private static void AddPermittedIndexes(List moves, int[][] tutors, bool[][] permit) + private static GameVersion GetIsTutor3(int species, int move) + { + // E Tutors (Free) + // E Tutors (BP) + var info = PersonalTable.E[species]; + var e = Array.IndexOf(Tutor_E, move); + if (e != -1 && info.TypeTutors[e]) + return GameVersion.E; + + // FRLG Tutors + // Only special tutor moves, normal tutor moves are already included in Emerald data + var frlg = Array.IndexOf(SpecialTutors_FRLG, move); + if (frlg != -1 && SpecialTutors_Compatibility_FRLG[frlg] == species) + return GameVersion.FRLG; + + // XD + var xd = Array.IndexOf(SpecialTutors_XD_Exclusive, move); + if (xd != -1 && SpecialTutors_Compatibility_XD_Exclusive[xd].AsSpan().IndexOf(species) != -1) + return GameVersion.XD; + + // XD (Mew) + if (species == (int)Species.Mew && Tutor_3Mew.AsSpan().IndexOf(move) != -1) + return GameVersion.XD; + + return NONE; + } + + private static GameVersion GetIsTutor4(int species, int form, int move) + { + var pi = PersonalTable.HGSS[species, form]; + var type = Array.IndexOf(Tutors_4, move); + if (type != -1 && pi.TypeTutors[type]) + return GameVersion.Gen4; + + var special = Array.IndexOf(SpecialTutors_4, move); + if (special != -1 && SpecialTutors_Compatibility_4[special].AsSpan().IndexOf(species) != -1) + return GameVersion.HGSS; + + return NONE; + } + + private static GameVersion GetIsTutor5(PKM pk, int species, int form, bool specialTutors, int move) + { + var pi = PersonalTable.B2W2[species, form]; + var type = Array.IndexOf(TypeTutor6, move); + if (type != -1 && pi.TypeTutors[type]) + return GameVersion.Gen5; + + if (specialTutors && pk.HasVisitedB2W2(species)) { + var tutors = Tutors_B2W2; for (int i = 0; i < tutors.Length; i++) { - var arr = tutors[i]; - var per = permit[i]; - AddPermittedIndexes(moves, arr, per); + var tutor = Array.IndexOf(tutors[i], move); + if (tutor == -1) + continue; + if (pi.SpecialTutors[i][tutor]) + return GameVersion.B2W2; + break; } } - private static void AddPermittedIndexes(List moves, int[] moveIDs, bool[] permit) + return NONE; + } + + private static GameVersion GetIsTutor6(PKM pk, int species, int form, bool specialTutors, int move) + { + var pi = PersonalTable.AO[species, form]; + var type = Array.IndexOf(TypeTutor6, move); + if (type != -1 && pi.TypeTutors[type]) + return GameVersion.Gen6; + + if (specialTutors && pk.HasVisitedORAS(species)) { - for (int i = 0; i < moveIDs.Length; i++) + var tutors = Tutors_AO; + for (int i = 0; i < tutors.Length; i++) { - if (permit[i]) - moves.Add(moveIDs[i]); + var tutor = Array.IndexOf(tutors[i], move); + if (tutor == -1) + continue; + if (pi.SpecialTutors[i][tutor]) + return GameVersion.ORAS; + break; } } - internal static void AddSpecialTutorMoves(List r, PKM pkm, int Generation, int species) + return NONE; + } + + private static GameVersion GetIsTutor7(PKM pk, int species, int form, bool specialTutors, int move) + { + var pi = PersonalTable.USUM[species, form]; + var type = Array.IndexOf(TypeTutor6, move); + if (type != -1 && pi.TypeTutors[type]) + return GameVersion.Gen7; + + if (specialTutors && pk.HasVisitedUSUM(species)) { - switch (species) - { - case (int)Species.Keldeo: - r.Add((int)Move.SecretSword); - break; - case (int)Species.Meloetta: - r.Add((int)Move.RelicSong); - break; - - case (int)Species.Pikachu when Generation == 7 && pkm.Form == 8: - r.AddRange(Tutor_StarterPikachu); - break; - case (int)Species.Eevee when Generation == 7 && pkm.Form == 1: - r.AddRange(Tutor_StarterEevee); - break; - - case (int)Species.Pikachu or (int)Species.Raichu when Generation == 7 && !(pkm.LGPE || pkm.GO): - r.Add((int)Move.VoltTackle); - break; - } + var tutor = Array.IndexOf(Tutors_USUM, move); + if (tutor != -1 && pi.SpecialTutors[0][tutor]) + return GameVersion.USUM; } - /// Rotom Moves that correspond to a specific form (form-0 ignored). - private static readonly int[] RotomMoves = { (int)Move.Overheat, (int)Move.HydroPump, (int)Move.Blizzard, (int)Move.AirSlash, (int)Move.LeafStorm }; + return NONE; + } - internal static void AddSpecialFormChangeMoves(List r, PKM pkm, int generation, int species) + private static GameVersion GetIsTutor8(PKM pk, int species, int form, bool specialTutors, int move) + { + if (pk is PA8) { - switch (species) + var pi = (PersonalInfoLA)PersonalTable.LA[species, form]; + if (!pi.IsPresentInGame) + return NONE; + var index = Array.IndexOf(MoveShop8_LA, (ushort)move); + if (index != -1 && pi.SpecialTutors[0][index]) + return GameVersion.PLA; + + return NONE; + } + if (pk is PB8) + { + var pi = (PersonalInfoBDSP)PersonalTable.BDSP[species, form]; + if (!pi.IsPresentInGame) + return NONE; + var type = Array.IndexOf(TypeTutor8b, move); + if (type != -1 && pi.TypeTutors[type]) + return GameVersion.BDSP; + + return NONE; + } + else + { + var pi = (PersonalInfoSWSH)PersonalTable.SWSH[species, form]; + if (!pi.IsPresentInGame) + return NONE; + var type = Array.IndexOf(TypeTutor8, move); + if (type != -1 && pi.TypeTutors[type]) + return GameVersion.SWSH; + + if (!specialTutors) + return NONE; + + var tutor = Array.IndexOf(Tutors_SWSH_1, move); + if (tutor != -1 && pi.SpecialTutors[0][tutor]) + return GameVersion.SWSH; + + return NONE; + } + } + + public static IEnumerable GetTutorMoves(PKM pk, int species, int form, bool specialTutors, int generation) + { + List moves = new(); + switch (generation) + { + case 1: AddMovesTutor1(moves, species, pk.Format); break; + case 2: AddMovesTutor2(moves, species, pk.Format, pk.Korean); break; + case 3: AddMovesTutor3(moves, species); break; + case 4: AddMovesTutor4(moves, species, form); break; + case 5: AddMovesTutor5(moves, species, form, pk, specialTutors); break; + case 6: AddMovesTutor6(moves, species, form, pk, specialTutors); break; + case 7: AddMovesTutor7(moves, species, form, pk, specialTutors); break; + case 8: AddMovesTutor8(moves, species, form, pk, specialTutors); break; + } + return moves; + } + + private static void AddMovesTutor1(List moves, int species, int format) + { + if (ParseSettings.AllowGBCartEra && format < 3 && species is (int)Species.Pikachu or (int)Species.Raichu) // Surf Pikachu via Stadium + moves.Add(57); + } + + private static void AddMovesTutor2(List moves, int species, int format, bool korean = false) + { + if (korean) + return; + var pi = PersonalTable.C[species]; + var hmBits = pi.TMHM.AsSpan(57, 3); + for (int i = 0; i < Tutors_GSC.Length; i++) + { + if (hmBits[i]) + moves.Add(Tutors_GSC[i]); + } + AddMovesTutor1(moves, species, format); + } + + private static void AddMovesTutor3(List moves, int species) + { + // E Tutors (Free) + // E Tutors (BP) + var pi = PersonalTable.E[species]; + AddPermittedIndexes(moves, Tutor_E, pi.TypeTutors); + // FRLG Tutors + // Only special tutor moves, normal tutor moves are already included in Emerald data + AddIfPermitted(moves, SpecialTutors_FRLG, SpecialTutors_Compatibility_FRLG, species); + // XD + for (int i = 0; i < SpecialTutors_XD_Exclusive.Length; i++) + { + var allowed = SpecialTutors_Compatibility_XD_Exclusive[i].AsSpan(); + var index = allowed.IndexOf(species); + if (index != -1) + moves.Add(SpecialTutors_XD_Exclusive[i]); + } + // XD (Mew) + if (species == (int)Species.Mew) + moves.AddRange(Tutor_3Mew); + } + + private static void AddMovesTutor4(List moves, int species, int form) + { + var pi = PersonalTable.HGSS[species, form]; + AddPermittedIndexes(moves, Tutors_4, pi.TypeTutors); + for (int i = 0; i < SpecialTutors_4.Length; i++) + { + var allowed = SpecialTutors_Compatibility_4[i].AsSpan(); + var index = allowed.IndexOf(species); + if (index != -1) + moves.Add(SpecialTutors_4[i]); + } + } + + private static void AddMovesTutor5(List moves, int species, int form, PKM pk, bool specialTutors) + { + var pi = PersonalTable.B2W2[species, form]; + AddPermittedIndexes(moves, TypeTutor6, pi.TypeTutors); + if (pk.InhabitedGeneration(5) && specialTutors) + AddPermittedIndexes(moves, Tutors_B2W2, pi.SpecialTutors); + } + + private static void AddMovesTutor6(List moves, int species, int form, PKM pk, bool specialTutors) + { + var pi = PersonalTable.AO[species, form]; + AddPermittedIndexes(moves, TypeTutor6, pi.TypeTutors); + if (specialTutors && pk.HasVisitedORAS(species)) + AddPermittedIndexes(moves, Tutors_AO, pi.SpecialTutors); + } + + private static void AddMovesTutor7(List moves, int species, int form, PKM pk, bool specialTutors) + { + if (pk.Version is (int)GameVersion.GO or (int)GameVersion.GP or (int)GameVersion.GE) + return; + var pi = PersonalTable.USUM[species, form]; + AddPermittedIndexes(moves, TypeTutor6, pi.TypeTutors); + if (specialTutors && pk.HasVisitedUSUM(species)) + AddPermittedIndexes(moves, Tutors_USUM, pi.SpecialTutors[0]); + } + + private static void AddMovesTutor8(List moves, int species, int form, PKM pk, bool specialTutors) + { + if (pk is PA8) + { + var pi = (PersonalInfoLA)PersonalTable.LA[species, form]; + if (!pi.IsPresentInGame) + return; + var shop = MoveShop8_LA; + var tutors = pi.SpecialTutors[0]; + for (int i = 0; i < shop.Length; i++) { - case (int)Species.Rotom when generation >= 4: - var formMoves = RotomMoves; - var form = pkm.Form - 1; - if ((uint)form < formMoves.Length) - r.Add(RotomMoves[form]); - break; - case (int)Species.Zygarde when generation == 7: - r.AddRange(ZygardeMoves); - break; - case (int)Species.Necrozma when pkm.Form == 1: // Sun - r.Add((int)Move.SunsteelStrike); - break; - case (int)Species.Necrozma when pkm.Form == 2: // Moon - r.Add((int)Move.MoongeistBeam); - break; + if (tutors[i]) + moves.Add(shop[i]); } + return; + } + if (pk is PB8) + { + var pi = (PersonalInfoBDSP)PersonalTable.BDSP[species, form]; + if (pi.IsPresentInGame) + AddPermittedIndexes(moves, TypeTutor8b, pi.TypeTutors); + } + else // SWSH + { + var pi = (PersonalInfoSWSH)PersonalTable.SWSH[species, form]; + if (!pi.IsPresentInGame) + return; + AddPermittedIndexes(moves, TypeTutor8, pi.TypeTutors); + if (specialTutors) + AddPermittedIndexes(moves, Tutors_SWSH_1, pi.SpecialTutors[0]); + } + } + + private static void AddIfPermitted(List moves, ReadOnlySpan arrMoves, ReadOnlySpan arrSpecies, int species) + { + var index = arrSpecies.IndexOf(species); + if (index != -1) + moves.Add(arrMoves[index]); + } + + private static void AddPermittedIndexes(List moves, int[][] tutors, bool[][] permit) + { + for (int i = 0; i < tutors.Length; i++) + { + var arr = tutors[i]; + var per = permit[i]; + AddPermittedIndexes(moves, arr, per); + } + } + + private static void AddPermittedIndexes(List moves, int[] moveIDs, bool[] permit) + { + for (int i = 0; i < moveIDs.Length; i++) + { + if (permit[i]) + moves.Add(moveIDs[i]); + } + } + + internal static void AddSpecialTutorMoves(List r, PKM pk, int Generation, int species) + { + switch (species) + { + case (int)Species.Keldeo: + r.Add((int)Move.SecretSword); + break; + case (int)Species.Meloetta: + r.Add((int)Move.RelicSong); + break; + + case (int)Species.Pikachu when Generation == 7 && pk.Form == 8: + r.AddRange(Tutor_StarterPikachu); + break; + case (int)Species.Eevee when Generation == 7 && pk.Form == 1: + r.AddRange(Tutor_StarterEevee); + break; + + case (int)Species.Pikachu or (int)Species.Raichu when Generation == 7 && !(pk.LGPE || pk.GO): + r.Add((int)Move.VoltTackle); + break; + } + } + + /// Rotom Moves that correspond to a specific form (form-0 ignored). + private static readonly int[] RotomMoves = { (int)Move.Overheat, (int)Move.HydroPump, (int)Move.Blizzard, (int)Move.AirSlash, (int)Move.LeafStorm }; + + internal static void AddSpecialFormChangeMoves(List r, PKM pk, int generation, int species) + { + switch (species) + { + case (int)Species.Rotom when generation >= 4: + var formMoves = RotomMoves; + var form = pk.Form - 1; + if ((uint)form < formMoves.Length) + r.Add(RotomMoves[form]); + break; + case (int)Species.Zygarde when generation == 7: + r.AddRange(ZygardeMoves); + break; + case (int)Species.Necrozma when pk.Form == 1: // Sun + r.Add((int)Move.SunsteelStrike); + break; + case (int)Species.Necrozma when pk.Form == 2: // Moon + r.Add((int)Move.MoongeistBeam); + break; } } } diff --git a/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs index 582278c0b..739fe22f7 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs @@ -1,78 +1,77 @@ using System.Runtime.CompilerServices; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// 32 Bit Linear Congruential Random Number Generator +/// +/// Frame advancement for forward and reverse. +///
+/// https://en.wikipedia.org/wiki/Linear_congruential_generator +///
+///
+/// seed_n+1 = seed_n * + +///
+///
+public class LCRNG { - /// - /// 32 Bit Linear Congruential Random Number Generator - /// - /// Frame advancement for forward and reverse. - ///
- /// https://en.wikipedia.org/wiki/Linear_congruential_generator - ///
- ///
- /// seed_n+1 = seed_n * + - ///
- ///
- public class LCRNG + // Forward + protected readonly uint Mult; + private readonly uint Add; + + // Reverse + private readonly uint rMult; + private readonly uint rAdd; + + public LCRNG(uint f_mult, uint f_add, uint r_mult, uint r_add) { - // Forward - protected readonly uint Mult; - private readonly uint Add; + Mult = f_mult; + Add = f_add; + rMult = r_mult; + rAdd = r_add; + } - // Reverse - private readonly uint rMult; - private readonly uint rAdd; + /// + /// Advances the RNG seed to the next state value. + /// + /// Current seed + /// Seed advanced a single time. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Next(uint seed) => (seed * Mult) + Add; - public LCRNG(uint f_mult, uint f_add, uint r_mult, uint r_add) - { - Mult = f_mult; - Add = f_add; - rMult = r_mult; - rAdd = r_add; - } + /// + /// Reverses the RNG seed to the previous state value. + /// + /// Current seed + /// Seed reversed a single time. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Prev(uint seed) => (seed * rMult) + rAdd; - /// - /// Advances the RNG seed to the next state value. - /// - /// Current seed - /// Seed advanced a single time. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Next(uint seed) => (seed * Mult) + Add; + /// + /// Advances the RNG seed to the next state value a specified amount of times. + /// + /// Current seed + /// Amount of times to advance. + /// Seed advanced the specified amount of times. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Advance(uint seed, int frames) + { + for (int i = 0; i < frames; i++) + seed = Next(seed); + return seed; + } - /// - /// Reverses the RNG seed to the previous state value. - /// - /// Current seed - /// Seed reversed a single time. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Prev(uint seed) => (seed * rMult) + rAdd; - - /// - /// Advances the RNG seed to the next state value a specified amount of times. - /// - /// Current seed - /// Amount of times to advance. - /// Seed advanced the specified amount of times. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Advance(uint seed, int frames) - { - for (int i = 0; i < frames; i++) - seed = Next(seed); - return seed; - } - - /// - /// Reverses the RNG seed to the previous state value a specified amount of times. - /// - /// Current seed - /// Amount of times to reverse. - /// Seed reversed the specified amount of times. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Reverse(uint seed, int frames) - { - for (int i = 0; i < frames; i++) - seed = Prev(seed); - return seed; - } + /// + /// Reverses the RNG seed to the previous state value a specified amount of times. + /// + /// Current seed + /// Amount of times to reverse. + /// Seed reversed the specified amount of times. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Reverse(uint seed, int frames) + { + for (int i = 0; i < frames; i++) + seed = Prev(seed); + return seed; } } diff --git a/PKHeX.Core/Legality/RNG/Algorithms/RNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/RNG.cs index 269747889..5383de315 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/RNG.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/RNG.cs @@ -1,188 +1,187 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// +/// +/// +/// +///
+/// Provides common RNG algorithms used by Generation 3 & 4. +/// This class has extra logic (tuned for performance) that can be used to find the original state(s) based on a limited amount of observed results. +/// Refer to the documentation for those methods. +///
+///
+public sealed class RNG : LCRNG { - /// - /// - /// - /// - /// - ///
- /// Provides common RNG algorithms used by Generation 3 & 4. - /// This class has extra logic (tuned for performance) that can be used to find the original state(s) based on a limited amount of observed results. - /// Refer to the documentation for those methods. - ///
- ///
- public sealed class RNG : LCRNG + /// LCRNG used for Encryption and mainline game RNG calls. + public static readonly RNG LCRNG = new(0x41C64E6D, 0x00006073, 0xEEB9EB65, 0x0A3561A1); + + /// LCRNG used by Colosseum & XD for game RNG calls. + public static readonly RNG XDRNG = new(0x000343FD, 0x00269EC3, 0xB9B33155, 0xA170F641); + + /// Alternate LCRNG used by mainline game RNG calls to disassociate the seed from the , for anti-shiny and other purposes. + public static readonly LCRNG ARNG = new(0x6C078965, 0x00000001, 0x9638806D, 0x69C77F93); + + #region Seed Reversal Logic + + // Bruteforce cache for searching seeds + private const int cacheSize = 1 << 16; + // 1,2 (no gap) + private readonly uint k2; // Mult<<8 + private readonly byte[] low8 = new byte[cacheSize]; + private readonly bool[] flags = new bool[cacheSize]; + // 1,3 (single gap) + private readonly uint k0g; // Mult*Mult + private readonly uint k2s; // Mult*Mult<<8 + private readonly byte[] g_low8 = new byte[cacheSize]; + private readonly bool[] g_flags = new bool[cacheSize]; + + // Euclidean division approach + private readonly long t0; // Add - 0xFFFF + private readonly long t1; // 0xFFFF * ((long)Mult + 1) + + #endregion + + private RNG(uint f_mult, uint f_add, uint r_mult, uint r_add) : base(f_mult, f_add, r_mult, r_add) { - /// LCRNG used for Encryption and mainline game RNG calls. - public static readonly RNG LCRNG = new(0x41C64E6D, 0x00006073, 0xEEB9EB65, 0x0A3561A1); + // Set up bruteforce utility + k2 = f_mult << 8; + k0g = f_mult * f_mult; + k2s = k0g << 8; - /// LCRNG used by Colosseum & XD for game RNG calls. - public static readonly RNG XDRNG = new(0x000343FD, 0x00269EC3, 0xB9B33155, 0xA170F641); - - /// Alternate LCRNG used by mainline game RNG calls to disassociate the seed from the , for anti-shiny and other purposes. - public static readonly LCRNG ARNG = new(0x6C078965, 0x00000001, 0x9638806D, 0x69C77F93); - - #region Seed Reversal Logic - - // Bruteforce cache for searching seeds - private const int cacheSize = 1 << 16; - // 1,2 (no gap) - private readonly uint k2; // Mult<<8 - private readonly byte[] low8 = new byte[cacheSize]; - private readonly bool[] flags = new bool[cacheSize]; - // 1,3 (single gap) - private readonly uint k0g; // Mult*Mult - private readonly uint k2s; // Mult*Mult<<8 - private readonly byte[] g_low8 = new byte[cacheSize]; - private readonly bool[] g_flags = new bool[cacheSize]; - - // Euclidean division approach - private readonly long t0; // Add - 0xFFFF - private readonly long t1; // 0xFFFF * ((long)Mult + 1) - - #endregion - - private RNG(uint f_mult, uint f_add, uint r_mult, uint r_add) : base(f_mult, f_add, r_mult, r_add) + // Populate Meet Middle Arrays + uint k4g = f_add * (f_mult + 1); // 1,3's multiplier + for (uint i = 0; i <= byte.MaxValue; i++) { - // Set up bruteforce utility - k2 = f_mult << 8; - k0g = f_mult * f_mult; - k2s = k0g << 8; - - // Populate Meet Middle Arrays - uint k4g = f_add * (f_mult + 1); // 1,3's multiplier - for (uint i = 0; i <= byte.MaxValue; i++) - { - SetFlagData(i, f_mult, f_add, flags, low8); // 1,2 - SetFlagData(i, k0g, k4g, g_flags, g_low8); // 1,3 - } - - t0 = f_add - 0xFFFFU; - t1 = 0xFFFFL * ((long) f_mult + 1); + SetFlagData(i, f_mult, f_add, flags, low8); // 1,2 + SetFlagData(i, k0g, k4g, g_flags, g_low8); // 1,3 } - #region Initialization + t0 = f_add - 0xFFFFU; + t1 = 0xFFFFL * ((long) f_mult + 1); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetFlagData(uint i, uint mult, uint add, bool[] f, byte[] v) + #region Initialization + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetFlagData(uint i, uint mult, uint add, bool[] f, byte[] v) + { + // the second rand() also has 16 bits that aren't known. It is a 16 bit value added to either side. + // to consider these bits and their impact, they can at most increment/decrement the result by 1. + // with the current calc setup, the search loop's calculated value may be -1 (loop does subtraction) + // since LCGs are linear (hence the name), there's no values in adjacent cells. (no collisions) + // if we mark the prior adjacent cell, we eliminate the need to check flags twice on each loop. + uint right = (mult * i) + add; + ushort val = (ushort) (right >> 16); + + f[val] = true; v[val] = (byte)i; + --val; + f[val] = true; v[val] = (byte)i; + // now the search only has to access the flags array once per loop. + } + + #endregion + + /// + /// Gets the origin seeds for two successive 16 bit rand() calls using a meet-in-the-middle approach. + /// + /// First rand() call, 16 bits, already shifted left 16 bits. + /// Second rand() call, 16 bits, already shifted left 16 bits. + /// + /// Use a meet-in-the-middle attack to reduce the search space to 2^8 instead of 2^16 + /// flag/2^8 tables are precomputed and constant (unrelated to rand pairs) + /// https://crypto.stackexchange.com/a/10609 + /// + /// Possible origin seeds that generate the 2 random numbers + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal IEnumerable RecoverLower16Bits(uint first, uint second) + { + uint k1 = second - (first * Mult); + for (uint i = 0, k3 = k1; i <= 255; ++i, k3 -= k2) { - // the second rand() also has 16 bits that aren't known. It is a 16 bit value added to either side. - // to consider these bits and their impact, they can at most increment/decrement the result by 1. - // with the current calc setup, the search loop's calculated value may be -1 (loop does subtraction) - // since LCGs are linear (hence the name), there's no values in adjacent cells. (no collisions) - // if we mark the prior adjacent cell, we eliminate the need to check flags twice on each loop. - uint right = (mult * i) + add; - ushort val = (ushort) (right >> 16); - - f[val] = true; v[val] = (byte)i; - --val; - f[val] = true; v[val] = (byte)i; - // now the search only has to access the flags array once per loop. + ushort val = (ushort)(k3 >> 16); + if (flags[val]) + yield return Prev(first | (i << 8) | low8[val]); } + } - #endregion - - /// - /// Gets the origin seeds for two successive 16 bit rand() calls using a meet-in-the-middle approach. - /// - /// First rand() call, 16 bits, already shifted left 16 bits. - /// Second rand() call, 16 bits, already shifted left 16 bits. - /// - /// Use a meet-in-the-middle attack to reduce the search space to 2^8 instead of 2^16 - /// flag/2^8 tables are precomputed and constant (unrelated to rand pairs) - /// https://crypto.stackexchange.com/a/10609 - /// - /// Possible origin seeds that generate the 2 random numbers - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal IEnumerable RecoverLower16Bits(uint first, uint second) + /// + /// Gets the origin seeds for two 16 bit rand() calls (ignoring a rand() in between) using a meet-in-the-middle approach. + /// + /// First rand() call, 16 bits, already shifted left 16 bits. + /// Third rand() call, 16 bits, already shifted left 16 bits. + /// + /// Use a meet-in-the-middle attack to reduce the search space to 2^8 instead of 2^16 + /// flag/2^8 tables are precomputed and constant (unrelated to rand pairs) + /// https://crypto.stackexchange.com/a/10609 + /// + /// Possible origin seeds that generate the 2 random numbers + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal IEnumerable RecoverLower16BitsGap(uint first, uint third) + { + uint k1 = third - (first * k0g); + for (uint i = 0, k3 = k1; i <= 255; ++i, k3 -= k2s) { - uint k1 = second - (first * Mult); - for (uint i = 0, k3 = k1; i <= 255; ++i, k3 -= k2) - { - ushort val = (ushort)(k3 >> 16); - if (flags[val]) - yield return Prev(first | i << 8 | low8[val]); - } + ushort val = (ushort)(k3 >> 16); + if (g_flags[val]) + yield return Prev(first | (i << 8) | g_low8[val]); } + } - /// - /// Gets the origin seeds for two 16 bit rand() calls (ignoring a rand() in between) using a meet-in-the-middle approach. - /// - /// First rand() call, 16 bits, already shifted left 16 bits. - /// Third rand() call, 16 bits, already shifted left 16 bits. - /// - /// Use a meet-in-the-middle attack to reduce the search space to 2^8 instead of 2^16 - /// flag/2^8 tables are precomputed and constant (unrelated to rand pairs) - /// https://crypto.stackexchange.com/a/10609 - /// - /// Possible origin seeds that generate the 2 random numbers - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal IEnumerable RecoverLower16BitsGap(uint first, uint third) - { - uint k1 = third - (first * k0g); - for (uint i = 0, k3 = k1; i <= 255; ++i, k3 -= k2s) - { - ushort val = (ushort)(k3 >> 16); - if (g_flags[val]) - yield return Prev(first | i << 8 | g_low8[val]); - } - } + /// + /// Gets the origin seeds for two successive 16 bit rand() calls using a Euclidean division approach. + /// + /// First rand() call, 16 bits, already shifted left 16 bits. + /// Second rand() call, 16 bits, already shifted left 16 bits. + /// + /// For favorable multiplier values, this k_max gives a search space less than 2^8 (meet-in-the-middle) + /// For the programmed methods in this program, it is only advantageous to use this with . + /// https://crypto.stackexchange.com/a/10629 + /// + /// Possible origin seeds that generate the 2 random numbers + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal IEnumerable RecoverLower16BitsEuclid16(uint first, uint second) + { + const int bitshift = 32; + const long inc = 1L << bitshift; + return GetPossibleSeedsEuclid(first, second, bitshift, inc); + } - /// - /// Gets the origin seeds for two successive 16 bit rand() calls using a Euclidean division approach. - /// - /// First rand() call, 16 bits, already shifted left 16 bits. - /// Second rand() call, 16 bits, already shifted left 16 bits. - /// - /// For favorable multiplier values, this k_max gives a search space less than 2^8 (meet-in-the-middle) - /// For the programmed methods in this program, it is only advantageous to use this with . - /// https://crypto.stackexchange.com/a/10629 - /// - /// Possible origin seeds that generate the 2 random numbers - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal IEnumerable RecoverLower16BitsEuclid16(uint first, uint second) - { - const int bitshift = 32; - const long inc = 1L << bitshift; - return GetPossibleSeedsEuclid(first, second, bitshift, inc); - } + /// + /// Gets the origin seeds for two successive 15 bit rand() calls using a Euclidean division approach. + /// + /// First rand() call, 15 bits, already shifted left 16 bits. + /// Second rand() call, 15 bits, already shifted left 16 bits. + /// + /// Calculate the quotient of the Euclidean division (k_max) attack to reduce the search space. + /// For favorable multiplier values, this k_max gives a search space less than 2^8 (meet-in-the-middle) + /// For the programmed methods in this program, it is only advantageous to use this with . + /// https://crypto.stackexchange.com/a/10629 + /// + /// Possible origin seeds that generate the 2 random numbers + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal IEnumerable RecoverLower16BitsEuclid15(uint first, uint second) + { + const int bitshift = 31; + const long inc = 1L << bitshift; + return GetPossibleSeedsEuclid(first, second, bitshift, inc); + } - /// - /// Gets the origin seeds for two successive 15 bit rand() calls using a Euclidean division approach. - /// - /// First rand() call, 15 bits, already shifted left 16 bits. - /// Second rand() call, 15 bits, already shifted left 16 bits. - /// - /// Calculate the quotient of the Euclidean division (k_max) attack to reduce the search space. - /// For favorable multiplier values, this k_max gives a search space less than 2^8 (meet-in-the-middle) - /// For the programmed methods in this program, it is only advantageous to use this with . - /// https://crypto.stackexchange.com/a/10629 - /// - /// Possible origin seeds that generate the 2 random numbers - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal IEnumerable RecoverLower16BitsEuclid15(uint first, uint second) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private IEnumerable GetPossibleSeedsEuclid(uint first, uint second, int bitshift, long inc) + { + long t = second - (Mult * first) - t0; + long kmax = (((t1 - t) >> bitshift) << bitshift) + t; + for (long k = t; k <= kmax; k += inc) { - const int bitshift = 31; - const long inc = 1L << bitshift; - return GetPossibleSeedsEuclid(first, second, bitshift, inc); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private IEnumerable GetPossibleSeedsEuclid(uint first, uint second, int bitshift, long inc) - { - long t = second - (Mult * first) - t0; - long kmax = (((t1 - t) >> bitshift) << bitshift) + t; - for (long k = t; k <= kmax; k += inc) - { - // compute modulo in steps for reuse in yielded value (x % Mult) - long fix = k / Mult; - long remainder = k - (Mult * fix); - if (remainder >> 16 == 0) - yield return Prev(first | (uint) fix); - } + // compute modulo in steps for reuse in yielded value (x % Mult) + long fix = k / Mult; + long remainder = k - (Mult * fix); + if (remainder >> 16 == 0) + yield return Prev(first | (uint) fix); } } } diff --git a/PKHeX.Core/Legality/RNG/Algorithms/RNGType.cs b/PKHeX.Core/Legality/RNG/Algorithms/RNGType.cs index e6f0bbe07..45ebf7c16 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/RNGType.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/RNGType.cs @@ -1,30 +1,29 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum RNGType { - public enum RNGType - { - /// No RNG type specified - None, + /// No RNG type specified + None, - /// - LCRNG, + /// + LCRNG, - /// - XDRNG, + /// + XDRNG, - /// - ARNG, - } - - public static class RNGTypeUtil - { - public static LCRNG GetRNG(this RNGType type) => type switch - { - RNGType.LCRNG => RNG.LCRNG, - RNGType.XDRNG => RNG.XDRNG, - RNGType.ARNG => RNG.ARNG, - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; - } + /// + ARNG, +} + +public static class RNGTypeUtil +{ + public static LCRNG GetRNG(this RNGType type) => type switch + { + RNGType.LCRNG => RNG.LCRNG, + RNGType.XDRNG => RNG.XDRNG, + RNGType.ARNG => RNG.ARNG, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; } diff --git a/PKHeX.Core/Legality/RNG/Algorithms/RNGUtil.cs b/PKHeX.Core/Legality/RNG/Algorithms/RNGUtil.cs index 04a97d783..5ceadbf9e 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/RNGUtil.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/RNGUtil.cs @@ -1,45 +1,44 @@ using System; using System.Runtime.CompilerServices; -namespace PKHeX.Core -{ - public static class RNGUtil - { - /// - /// Generates an IV for each RNG call using the top 5 bits of frame seeds. - /// - /// RNG to use - /// RNG seed - /// Expected IVs - /// True if all match. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool GetSequentialIVsUInt32(this LCRNG rng, uint seed, ReadOnlySpan IVs) - { - foreach (var iv in IVs) - { - seed = rng.Next(seed); - var IV = seed >> 27; - if (IV != iv) - return false; - } - return true; - } +namespace PKHeX.Core; - /// - /// Generates an IV for each RNG call using the top 5 bits of frame seeds. - /// - /// RNG to use - /// RNG seed - /// Buffer to store generated values - /// Array of 6 IVs as . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void GetSequentialIVsInt32(this LCRNG rng, uint seed, Span ivs) +public static class RNGUtil +{ + /// + /// Generates an IV for each RNG call using the top 5 bits of frame seeds. + /// + /// RNG to use + /// RNG seed + /// Expected IVs + /// True if all match. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetSequentialIVsUInt32(this LCRNG rng, uint seed, ReadOnlySpan IVs) + { + foreach (var iv in IVs) { - for (int i = 0; i < 6; i++) - { - seed = rng.Next(seed); - ivs[i] = (int)(seed >> 27); - } + seed = rng.Next(seed); + var IV = seed >> 27; + if (IV != iv) + return false; + } + return true; + } + + /// + /// Generates an IV for each RNG call using the top 5 bits of frame seeds. + /// + /// RNG to use + /// RNG seed + /// Buffer to store generated values + /// Array of 6 IVs as . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void GetSequentialIVsInt32(this LCRNG rng, uint seed, Span ivs) + { + for (int i = 0; i < 6; i++) + { + seed = rng.Next(seed); + ivs[i] = (int)(seed >> 27); } } } diff --git a/PKHeX.Core/Legality/RNG/Frame/Frame.cs b/PKHeX.Core/Legality/RNG/Frame/Frame.cs index f70d27047..db708503d 100644 --- a/PKHeX.Core/Legality/RNG/Frame/Frame.cs +++ b/PKHeX.Core/Legality/RNG/Frame/Frame.cs @@ -1,100 +1,98 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Frame { - public sealed class Frame + /// + /// Ending seed value for the frame (prior to nature call). + /// + public readonly uint Seed; + public readonly LeadRequired Lead; + + private readonly FrameType FrameType; + + /// + /// Starting seed for the frame (to generate the frame). + /// + public uint OriginSeed { get; set; } + + /// + /// RNG Call Value for the Level Calc + /// + public uint RandLevel { get; set; } + + /// + /// RNG Call Value for the Encounter Slot Calc + /// + public uint RandESV { get; set; } + + public bool LevelSlotModified => Lead.IsLevelOrSlotModified() || (Lead & LeadRequired.UsesLevelCall) != 0; + + public Frame(uint seed, FrameType type, LeadRequired lead) { - /// - /// Ending seed value for the frame (prior to nature call). - /// - public readonly uint Seed; - public readonly LeadRequired Lead; + Seed = seed; + Lead = lead; + FrameType = type; + } - private readonly FrameType FrameType; - - /// - /// Starting seed for the frame (to generate the frame). - /// - public uint OriginSeed { get; set; } - - /// - /// RNG Call Value for the Level Calc - /// - public uint RandLevel { get; set; } - - /// - /// RNG Call Value for the Encounter Slot Calc - /// - public uint RandESV { get; set; } - - public bool LevelSlotModified => Lead.IsLevelOrSlotModified() || (Lead & LeadRequired.UsesLevelCall) != 0; - - public Frame(uint seed, FrameType type, LeadRequired lead) + /// + /// Checks the Encounter Slot for RNG calls before the Nature loop. + /// + /// Slot Data + /// Ancillary pk data for determining how to check level. + /// Slot number for this frame & lead value. + public bool IsSlotCompatibile(T slot, PKM pk) where T : EncounterSlot, IMagnetStatic, INumberedSlot, ISlotRNGType + { + if (FrameType != FrameType.MethodH) // gen3 always does level rand { - Seed = seed; - Lead = lead; - FrameType = type; - } - - /// - /// Checks the Encounter Slot for RNG calls before the Nature loop. - /// - /// Slot Data - /// Ancillary pkm data for determining how to check level. - /// Slot number for this frame & lead value. - public bool IsSlotCompatibile(T slot, PKM pkm) where T : EncounterSlot, IMagnetStatic, INumberedSlot, ISlotRNGType - { - if (FrameType != FrameType.MethodH) // gen3 always does level rand - { - bool hasLevelCall = slot.IsRandomLevel; - if (Lead.NeedsLevelCall() != hasLevelCall) - return false; - } - - // Level is before Nature, but usually isn't varied. Check ESV calc first. - int s = GetSlot(slot); - if (s != slot.SlotNumber) + bool hasLevelCall = slot.IsRandomLevel; + if (Lead.NeedsLevelCall() != hasLevelCall) return false; - - // Check Level Now - int lvl = SlotRange.GetLevel(slot, Lead, RandLevel); - if (pkm.HasOriginalMetLocation) - { - if (lvl != pkm.Met_Level) - return false; - } - else - { - if (lvl > pkm.Met_Level) - return false; - } - - // Check if the slot is actually encounterable (considering Sweet Scent) - bool encounterable = SlotRange.GetIsEncounterable(slot, FrameType, (int)(OriginSeed >> 16), Lead); - return encounterable; } - /// - /// Gets the slot value for the input slot. - /// - /// Slot Data - /// Slot number for this frame & lead value. - private int GetSlot(T slot) where T : EncounterSlot, IMagnetStatic, INumberedSlot, ISlotRNGType + // Level is before Nature, but usually isn't varied. Check ESV calc first. + int s = GetSlot(slot); + if (s != slot.SlotNumber) + return false; + + // Check Level Now + int lvl = SlotRange.GetLevel(slot, Lead, RandLevel); + if (pk.HasOriginalMetLocation) { - // Static and Magnet Pull do a slot search rather than slot mapping 0-99. - return Lead != LeadRequired.StaticMagnet - ? SlotRange.GetSlot(slot.Type, RandESV, FrameType) - : SlotRange.GetSlotStaticMagnet(slot, RandESV); + if (lvl != pk.Met_Level) + return false; + } + else + { + if (lvl > pk.Met_Level) + return false; } - /// - /// Only use this for test methods. - /// - /// - /// - public int GetSlot(SlotType t) => SlotRange.GetSlot(t, RandESV, FrameType); + // Check if the slot is actually encounterable (considering Sweet Scent) + bool encounterable = SlotRange.GetIsEncounterable(slot, FrameType, (int)(OriginSeed >> 16), Lead); + return encounterable; } - public interface ISlotRNGType + /// + /// Gets the slot value for the input slot. + /// + /// Slot Data + /// Slot number for this frame & lead value. + private int GetSlot(T slot) where T : EncounterSlot, IMagnetStatic, INumberedSlot, ISlotRNGType { - SlotType Type { get; } + // Static and Magnet Pull do a slot search rather than slot mapping 0-99. + return Lead != LeadRequired.StaticMagnet + ? SlotRange.GetSlot(slot.Type, RandESV, FrameType) + : SlotRange.GetSlotStaticMagnet(slot, RandESV); } + + /// + /// Only use this for test methods. + /// + /// + public int GetSlot(SlotType t) => SlotRange.GetSlot(t, RandESV, FrameType); +} + +public interface ISlotRNGType +{ + SlotType Type { get; } } diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameCache.cs b/PKHeX.Core/Legality/RNG/Frame/FrameCache.cs index f2297b315..e5a1376f7 100644 --- a/PKHeX.Core/Legality/RNG/Frame/FrameCache.cs +++ b/PKHeX.Core/Legality/RNG/Frame/FrameCache.cs @@ -1,62 +1,60 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Frame List used to cache results. +/// +public sealed class FrameCache { + private const int DefaultSize = 32; + private readonly List Seeds = new(DefaultSize); + private readonly List Values = new(DefaultSize); + private readonly Func Advance; + /// - /// Frame List used to cache results. + /// Creates a new instance of a . /// - public sealed class FrameCache + /// Seed at frame 0. + /// method used to get the next seed. Can use or . + public FrameCache(uint origin, Func advance) { - private const int DefaultSize = 32; - private readonly List Seeds = new(DefaultSize); - private readonly List Values = new(DefaultSize); - private readonly Func Advance; + Advance = advance; + Add(origin); + } - /// - /// Creates a new instance of a . - /// - /// Seed at frame 0. - /// method used to get the next seed. Can use or . - public FrameCache(uint origin, Func advance) - { - Advance = advance; - Add(origin); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Add(uint seed) + { + Seeds.Add(seed); + Values.Add(seed >> 16); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Add(uint seed) - { - Seeds.Add(seed); - Values.Add(seed >> 16); - } - - /// - /// Gets the 16 bit value from at a given . - /// - /// Index to grab the value from - /// - public uint this[int index] - { - get - { - while (index >= Seeds.Count) - Add(Advance(Seeds[^1])); - return Values[index]; - } - } - - /// - /// Gets the Seed at a specified frame index. - /// - /// Frame number - /// Seed at index - public uint GetSeed(int index) + /// + /// Gets the 16 bit value from at a given . + /// + /// Index to grab the value from + public uint this[int index] + { + get { while (index >= Seeds.Count) Add(Advance(Seeds[^1])); - return Seeds[index]; + return Values[index]; } } + + /// + /// Gets the Seed at a specified frame index. + /// + /// Frame number + /// Seed at index + public uint GetSeed(int index) + { + while (index >= Seeds.Count) + Add(Advance(Seeds[^1])); + return Seeds[index]; + } } diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs b/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs index dd5ab922e..536262791 100644 --- a/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs +++ b/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs @@ -1,399 +1,398 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class FrameFinder { - public static class FrameFinder + /// + /// Checks a to see if any encounter frames can generate the spread. Requires further filtering against matched Encounter Slots and generation patterns. + /// + /// Matched containing info and . + /// object containing various accessible information required for the encounter. + /// to yield possible encounter details for further filtering + public static IEnumerable GetFrames(PIDIV pidiv, PKM pk) { - /// - /// Checks a to see if any encounter frames can generate the spread. Requires further filtering against matched Encounter Slots and generation patterns. - /// - /// Matched containing info and . - /// object containing various accessible information required for the encounter. - /// to yield possible encounter details for further filtering - public static IEnumerable GetFrames(PIDIV pidiv, PKM pk) + if (pk.Version == (int)GameVersion.CXD) + return Array.Empty(); + + var info = new FrameGenerator(pk) {Nature = pk.EncryptionConstant % 25}; + var seeds = GetSeeds(pidiv, info, pk); + var frames = pidiv.Type == PIDType.CuteCharm + ? FilterCuteCharm(seeds, info) + : FilterNatureSync(seeds, info); + + return GetRefinedSeeds(frames, info, pidiv); + } + + // gather possible nature determination seeds until a same-nature PID breaks the unrolling + private static IEnumerable GetSeeds(PIDIV pidiv, FrameGenerator info, PKM pk) + { + if (pk.Species == (int)Species.Unown && pk.FRLG) // Gen3 FRLG Unown: reversed await case + return SeedInfo.GetSeedsUntilUnownForm(pidiv, info, pk.Form); + if (pidiv.Type == PIDType.CuteCharm && info.FrameType != FrameType.MethodH) // Gen4: ambiguous seed due to gender-buffered PID + return SeedInfo.GetSeedsUntilNature4Cute(pk); + return SeedInfo.GetSeedsUntilNature(pidiv, info); + } + + private static IEnumerable GetRefinedSeeds(IEnumerable frames, FrameGenerator info, PIDIV pidiv) + { + var refined = RefineFrames(frames, info); + if (pidiv.Type == PIDType.CuteCharm && info.FrameType != FrameType.MethodH) // only permit cute charm successful frames + return refined.Where(z => (z.Lead & ~LeadRequired.UsesLevelCall) == LeadRequired.CuteCharm); + return refined; + } + + private static IEnumerable RefineFrames(IEnumerable frames, FrameGenerator info) + { + return info.FrameType == FrameType.MethodH + ? RefineFrames3(frames, info) + : RefineFrames4(frames, info); + } + + private static IEnumerable RefineFrames3(IEnumerable frames, FrameGenerator info) + { + // ESV + // Level + // Nature + // Current Seed of the frame is the Level Calc (frame before nature) + var list = new List(); + foreach (var f in frames) { - if (pk.Version == (int)GameVersion.CXD) - return Array.Empty(); + bool noLead = !info.AllowLeads && f.Lead != LeadRequired.None; + if (noLead) + continue; - var info = new FrameGenerator(pk) {Nature = pk.EncryptionConstant % 25}; - var seeds = GetSeeds(pidiv, info, pk); - var frames = pidiv.Type == PIDType.CuteCharm - ? FilterCuteCharm(seeds, info) - : FilterNatureSync(seeds, info); - - return GetRefinedSeeds(frames, info, pidiv); - } - - // gather possible nature determination seeds until a same-nature PID breaks the unrolling - private static IEnumerable GetSeeds(PIDIV pidiv, FrameGenerator info, PKM pk) - { - if (pk.Species == (int)Species.Unown && pk.FRLG) // Gen3 FRLG Unown: reversed await case - return SeedInfo.GetSeedsUntilUnownForm(pidiv, info, pk.Form); - if (pidiv.Type == PIDType.CuteCharm && info.FrameType != FrameType.MethodH) // Gen4: ambiguous seed due to gender-buffered PID - return SeedInfo.GetSeedsUntilNature4Cute(pk); - return SeedInfo.GetSeedsUntilNature(pidiv, info); - } - - private static IEnumerable GetRefinedSeeds(IEnumerable frames, FrameGenerator info, PIDIV pidiv) - { - var refined = RefineFrames(frames, info); - if (pidiv.Type == PIDType.CuteCharm && info.FrameType != FrameType.MethodH) // only permit cute charm successful frames - return refined.Where(z => (z.Lead & ~LeadRequired.UsesLevelCall) == LeadRequired.CuteCharm); - return refined; - } - - private static IEnumerable RefineFrames(IEnumerable frames, FrameGenerator info) - { - return info.FrameType == FrameType.MethodH - ? RefineFrames3(frames, info) - : RefineFrames4(frames, info); - } - - private static IEnumerable RefineFrames3(IEnumerable frames, FrameGenerator info) - { - // ESV - // Level - // Nature - // Current Seed of the frame is the Level Calc (frame before nature) - var list = new List(); - foreach (var f in frames) - { - bool noLead = !info.AllowLeads && f.Lead != LeadRequired.None; - if (noLead) - continue; - - var prev = info.RNG.Prev(f.Seed); // ESV - var rand = prev >> 16; - f.RandESV = rand; - f.RandLevel = f.Seed >> 16; - f.OriginSeed = info.RNG.Prev(prev); - if (f.Lead != LeadRequired.CuteCharm) // needs proc checking - yield return f; - - // Generate frames for other slots after the regular slots - if (info.AllowLeads && (f.Lead is LeadRequired.CuteCharm or LeadRequired.None)) - list.Add(f); - } - foreach (var f in list) - { - var leadframes = GenerateLeadSpecificFrames3(f, info); - foreach (var frame in leadframes) - yield return frame; - } - } - - private static IEnumerable GenerateLeadSpecificFrames3(Frame f, FrameGenerator info) - { - // Check leads -- none in list if leads are not allowed - // Certain leads inject a RNG call - // 3 different rand places - LeadRequired lead; - var prev0 = f.Seed; // 0 - var prev1 = info.RNG.Prev(f.Seed); // -1 - var prev2 = info.RNG.Prev(prev1); // -2 - var prev3 = info.RNG.Prev(prev2); // -3 - - // Rand call raw values - var p0 = prev0 >> 16; - var p1 = prev1 >> 16; - var p2 = prev2 >> 16; - - // Cute Charm - // -2 ESV - // -1 Level - // 0 CC Proc (Random() % 3 != 0) - // 1 Nature - if (info.Gendered) - { - bool cc = p0 % 3 != 0; - if (f.Lead == LeadRequired.CuteCharm) // 100% required for frame base - { - if (cc) - yield return info.GetFrame(prev2, LeadRequired.CuteCharm, p2, p1, prev3); - yield break; - } - lead = cc ? LeadRequired.CuteCharm : LeadRequired.CuteCharmFail; - yield return info.GetFrame(prev2, lead, p2, p1, prev3); - } - if (f.Lead == LeadRequired.CuteCharm) - yield break; - - // Pressure, Hustle, Vital Spirit = Force Maximum Level from slot - // -2 ESV - // -1 Level - // 0 LevelMax proc (Random() & 1) - // 1 Nature - bool max = p0 % 2 == 1; - lead = max ? LeadRequired.PressureHustleSpirit : LeadRequired.PressureHustleSpiritFail; - yield return info.GetFrame(prev2, lead, p2, p1, prev3); - - // Keen Eye, Intimidate (Not compatible with Sweet Scent) - // -2 ESV - // -1 Level - // 0 Level Adequate Check !(Random() % 2 == 1) rejects -- rand%2==1 is adequate - // 1 Nature - // Note: if this check fails, the encounter generation routine is aborted. - if (max) // same result as above, no need to recalculate - { - lead = LeadRequired.IntimidateKeenEye; - yield return info.GetFrame(prev2, lead, p2, p1, prev3); - } - - // Static or Magnet Pull - // -2 SlotProc (Random % 2 == 0) - // -1 ESV (select slot) - // 0 Level - // 1 Nature - bool force = p2 % 2 == 0; - if (force) - { - // Since a failed proc is indistinguishable from the default frame calls, only generate if it succeeds. - lead = LeadRequired.StaticMagnet; - yield return info.GetFrame(prev2, lead, p1, p0, prev3); - } - } - - private static IEnumerable RefineFrames4(IEnumerable frames, FrameGenerator info) - { - var list = new List(); - foreach (var f in frames) - { - // Current Seed of the frame is the ESV. - var rand = f.Seed >> 16; - f.RandESV = rand; - f.RandLevel = rand; // unused - f.OriginSeed = info.RNG.Prev(f.Seed); + var prev = info.RNG.Prev(f.Seed); // ESV + var rand = prev >> 16; + f.RandESV = rand; + f.RandLevel = f.Seed >> 16; + f.OriginSeed = info.RNG.Prev(prev); + if (f.Lead != LeadRequired.CuteCharm) // needs proc checking yield return f; - // Create a copy for level; shift ESV and origin back - var esv = f.OriginSeed >> 16; - var origin = info.RNG.Prev(f.OriginSeed); - var withLevel = info.GetFrame(f.Seed, f.Lead | LeadRequired.UsesLevelCall, esv, f.RandLevel, origin); - yield return withLevel; - - if (f.Lead != LeadRequired.None) - continue; - - // Generate frames for other slots after the regular slots + // Generate frames for other slots after the regular slots + if (info.AllowLeads && (f.Lead is LeadRequired.CuteCharm or LeadRequired.None)) list.Add(f); - } - foreach (var f in list) + } + foreach (var f in list) + { + var leadframes = GenerateLeadSpecificFrames3(f, info); + foreach (var frame in leadframes) + yield return frame; + } + } + + private static IEnumerable GenerateLeadSpecificFrames3(Frame f, FrameGenerator info) + { + // Check leads -- none in list if leads are not allowed + // Certain leads inject a RNG call + // 3 different rand places + LeadRequired lead; + var prev0 = f.Seed; // 0 + var prev1 = info.RNG.Prev(f.Seed); // -1 + var prev2 = info.RNG.Prev(prev1); // -2 + var prev3 = info.RNG.Prev(prev2); // -3 + + // Rand call raw values + var p0 = prev0 >> 16; + var p1 = prev1 >> 16; + var p2 = prev2 >> 16; + + // Cute Charm + // -2 ESV + // -1 Level + // 0 CC Proc (Random() % 3 != 0) + // 1 Nature + if (info.Gendered) + { + bool cc = p0 % 3 != 0; + if (f.Lead == LeadRequired.CuteCharm) // 100% required for frame base { - var leadframes = GenerateLeadSpecificFrames4(f, info); - foreach (var frame in leadframes) - yield return frame; + if (cc) + yield return info.GetFrame(prev2, LeadRequired.CuteCharm, p2, p1, prev3); + yield break; } + lead = cc ? LeadRequired.CuteCharm : LeadRequired.CuteCharmFail; + yield return info.GetFrame(prev2, lead, p2, p1, prev3); + } + if (f.Lead == LeadRequired.CuteCharm) + yield break; + + // Pressure, Hustle, Vital Spirit = Force Maximum Level from slot + // -2 ESV + // -1 Level + // 0 LevelMax proc (Random() & 1) + // 1 Nature + bool max = p0 % 2 == 1; + lead = max ? LeadRequired.PressureHustleSpirit : LeadRequired.PressureHustleSpiritFail; + yield return info.GetFrame(prev2, lead, p2, p1, prev3); + + // Keen Eye, Intimidate (Not compatible with Sweet Scent) + // -2 ESV + // -1 Level + // 0 Level Adequate Check !(Random() % 2 == 1) rejects -- rand%2==1 is adequate + // 1 Nature + // Note: if this check fails, the encounter generation routine is aborted. + if (max) // same result as above, no need to recalculate + { + lead = LeadRequired.IntimidateKeenEye; + yield return info.GetFrame(prev2, lead, p2, p1, prev3); } - private static IEnumerable GenerateLeadSpecificFrames4(Frame f, FrameGenerator info) + // Static or Magnet Pull + // -2 SlotProc (Random % 2 == 0) + // -1 ESV (select slot) + // 0 Level + // 1 Nature + bool force = p2 % 2 == 0; + if (force) { - LeadRequired lead; - var prev0 = f.Seed; // 0 - var prev1 = info.RNG.Prev(f.Seed); // -1 - var prev2 = info.RNG.Prev(prev1); // -2 - var prev3 = info.RNG.Prev(prev2); // -3 - - // Rand call raw values - var p0 = prev0 >> 16; - var p1 = prev1 >> 16; - var p2 = prev2 >> 16; - - // Cute Charm - // -2 ESV - // -1 Level (Optional) - // 0 CC Proc (Random() % 3 != 0) - // 1 Nature - if (info.Gendered) - { - bool cc = (info.DPPt ? p0 / 0x5556 : p0 % 3) != 0; - if (f.Lead == LeadRequired.CuteCharm) // 100% required for frame base - { - if (!cc) yield break; - yield return info.GetFrame(prev2, LeadRequired.CuteCharm, p1, p1, prev2); - yield return info.GetFrame(prev2, LeadRequired.CuteCharm | LeadRequired.UsesLevelCall, p2, p1, prev3); - yield break; - } - lead = cc ? LeadRequired.CuteCharm : LeadRequired.CuteCharmFail; - yield return info.GetFrame(prev2, lead, p1, p1, prev2); - yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, p2, p1, prev3); - } - if (f.Lead == LeadRequired.CuteCharm) - yield break; - - // Pressure, Hustle, Vital Spirit = Force Maximum Level from slot - // -2 ESV - // -1 Level (Optional) - // 0 LevelMax proc (Random() & 1) - // 1 Nature - bool max = (info.DPPt ? p0 >> 15 : p0 & 1) == 1; - lead = max ? LeadRequired.PressureHustleSpirit : LeadRequired.PressureHustleSpiritFail; - yield return info.GetFrame(prev2, lead, p1, p1, prev2); - yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, p2, p1, prev3); - - // Keen Eye, Intimidate (Not compatible with Sweet Scent) - // -2 ESV - // -1 Level (Optional) - // 0 Level Adequate Check !(Random() % 2 == 1) rejects -- rand%2==1 is adequate - // 1 Nature - // Note: if this check fails, the encounter generation routine is aborted. - if (max) // same result as above, no need to recalculate - { - lead = LeadRequired.IntimidateKeenEye; - yield return info.GetFrame(prev2, lead, p1, p1, prev2); - yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, p2, p1, prev3); - } - - // Static or Magnet Pull - // -2 SlotProc (Random % 2 == 0) - // -1 ESV (select slot) - // 0 Level (Optional) - // 1 Nature - var force1 = (info.DPPt ? p1 >> 15 : p1 & 1) == 1; - lead = force1 ? LeadRequired.StaticMagnet : LeadRequired.StaticMagnetFail; - yield return info.GetFrame(prev2, lead, p0, p0, prev3); - - var force2 = (info.DPPt ? p2 >> 15 : p2 & 1) == 1; - lead = (force2 ? LeadRequired.StaticMagnet : LeadRequired.StaticMagnetFail) | LeadRequired.UsesLevelCall; + // Since a failed proc is indistinguishable from the default frame calls, only generate if it succeeds. + lead = LeadRequired.StaticMagnet; yield return info.GetFrame(prev2, lead, p1, p0, prev3); } + } - /// - /// Filters the input according to a Nature Lock frame generation pattern. - /// - /// Seed Information for the frame - /// Search Info for the frame - /// Possible matches to the Nature Lock frame generation pattern - private static IEnumerable FilterNatureSync(IEnumerable seeds, FrameGenerator info) + private static IEnumerable RefineFrames4(IEnumerable frames, FrameGenerator info) + { + var list = new List(); + foreach (var f in frames) { - foreach (var seed in seeds) + // Current Seed of the frame is the ESV. + var rand = f.Seed >> 16; + f.RandESV = rand; + f.RandLevel = rand; // unused + f.OriginSeed = info.RNG.Prev(f.Seed); + yield return f; + + // Create a copy for level; shift ESV and origin back + var esv = f.OriginSeed >> 16; + var origin = info.RNG.Prev(f.OriginSeed); + var withLevel = info.GetFrame(f.Seed, f.Lead | LeadRequired.UsesLevelCall, esv, f.RandLevel, origin); + yield return withLevel; + + if (f.Lead != LeadRequired.None) + continue; + + // Generate frames for other slots after the regular slots + list.Add(f); + } + foreach (var f in list) + { + var leadframes = GenerateLeadSpecificFrames4(f, info); + foreach (var frame in leadframes) + yield return frame; + } + } + + private static IEnumerable GenerateLeadSpecificFrames4(Frame f, FrameGenerator info) + { + LeadRequired lead; + var prev0 = f.Seed; // 0 + var prev1 = info.RNG.Prev(f.Seed); // -1 + var prev2 = info.RNG.Prev(prev1); // -2 + var prev3 = info.RNG.Prev(prev2); // -3 + + // Rand call raw values + var p0 = prev0 >> 16; + var p1 = prev1 >> 16; + var p2 = prev2 >> 16; + + // Cute Charm + // -2 ESV + // -1 Level (Optional) + // 0 CC Proc (Random() % 3 != 0) + // 1 Nature + if (info.Gendered) + { + bool cc = (info.DPPt ? p0 / 0x5556 : p0 % 3) != 0; + if (f.Lead == LeadRequired.CuteCharm) // 100% required for frame base { - var s = seed.Seed; - - if (info.Safari3) - { - // successful pokeblock activation - bool result = IsValidPokeBlockNature(s, info.Nature, out uint blockSeed); - if (result) - yield return info.GetFrame(blockSeed, seed.Charm3 ? LeadRequired.CuteCharm : LeadRequired.None); - - // if no pokeblocks present (or failed call), fall out of the safari specific code and generate via the other scenarios - } - - var rand = s >> 16; - bool sync = info.AllowLeads && !seed.Charm3 && (info.DPPt ? rand >> 15 : rand & 1) == 0; - bool reg = (info.DPPt ? rand / 0xA3E : rand % 25) == info.Nature; - if (!sync && !reg) // doesn't generate nature frame - continue; - - uint prev = RNG.LCRNG.Prev(s); - if (info.AllowLeads && reg) // check for failed sync - { - var failsync = (info.DPPt ? prev >> 31 : (prev >> 16) & 1) != 1; - if (failsync) - yield return info.GetFrame(RNG.LCRNG.Prev(prev), LeadRequired.SynchronizeFail); - } - if (sync) - yield return info.GetFrame(prev, LeadRequired.Synchronize); - if (reg) - { - if (seed.Charm3) - { - yield return info.GetFrame(prev, LeadRequired.CuteCharm); - } - else - { - if (info.Safari3) - prev = RNG.LCRNG.Prev(prev); // wasted RNG call - yield return info.GetFrame(prev, LeadRequired.None); - } - } + if (!cc) yield break; + yield return info.GetFrame(prev2, LeadRequired.CuteCharm, p1, p1, prev2); + yield return info.GetFrame(prev2, LeadRequired.CuteCharm | LeadRequired.UsesLevelCall, p2, p1, prev3); + yield break; } + lead = cc ? LeadRequired.CuteCharm : LeadRequired.CuteCharmFail; + yield return info.GetFrame(prev2, lead, p1, p1, prev2); + yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, p2, p1, prev3); + } + if (f.Lead == LeadRequired.CuteCharm) + yield break; + + // Pressure, Hustle, Vital Spirit = Force Maximum Level from slot + // -2 ESV + // -1 Level (Optional) + // 0 LevelMax proc (Random() & 1) + // 1 Nature + bool max = (info.DPPt ? p0 >> 15 : p0 & 1) == 1; + lead = max ? LeadRequired.PressureHustleSpirit : LeadRequired.PressureHustleSpiritFail; + yield return info.GetFrame(prev2, lead, p1, p1, prev2); + yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, p2, p1, prev3); + + // Keen Eye, Intimidate (Not compatible with Sweet Scent) + // -2 ESV + // -1 Level (Optional) + // 0 Level Adequate Check !(Random() % 2 == 1) rejects -- rand%2==1 is adequate + // 1 Nature + // Note: if this check fails, the encounter generation routine is aborted. + if (max) // same result as above, no need to recalculate + { + lead = LeadRequired.IntimidateKeenEye; + yield return info.GetFrame(prev2, lead, p1, p1, prev2); + yield return info.GetFrame(prev2, lead | LeadRequired.UsesLevelCall, p2, p1, prev3); } - private static bool IsValidPokeBlockNature(uint seed, uint nature, out uint natureOrigin) + // Static or Magnet Pull + // -2 SlotProc (Random % 2 == 0) + // -1 ESV (select slot) + // 0 Level (Optional) + // 1 Nature + var force1 = (info.DPPt ? p1 >> 15 : p1 & 1) == 1; + lead = force1 ? LeadRequired.StaticMagnet : LeadRequired.StaticMagnetFail; + yield return info.GetFrame(prev2, lead, p0, p0, prev3); + + var force2 = (info.DPPt ? p2 >> 15 : p2 & 1) == 1; + lead = (force2 ? LeadRequired.StaticMagnet : LeadRequired.StaticMagnetFail) | LeadRequired.UsesLevelCall; + yield return info.GetFrame(prev2, lead, p1, p0, prev3); + } + + /// + /// Filters the input according to a Nature Lock frame generation pattern. + /// + /// Seed Information for the frame + /// Search Info for the frame + /// Possible matches to the Nature Lock frame generation pattern + private static IEnumerable FilterNatureSync(IEnumerable seeds, FrameGenerator info) + { + foreach (var seed in seeds) { - if (nature % 6 == 0) // neutral + var s = seed.Seed; + + if (info.Safari3) { - natureOrigin = 0; - return false; + // successful pokeblock activation + bool result = IsValidPokeBlockNature(s, info.Nature, out uint blockSeed); + if (result) + yield return info.GetFrame(blockSeed, seed.Charm3 ? LeadRequired.CuteCharm : LeadRequired.None); + + // if no pokeblocks present (or failed call), fall out of the safari specific code and generate via the other scenarios } - // unroll the RNG to a stack of seeds - var stack = new Stack(); - for (uint i = 0; i < 25; i++) + var rand = s >> 16; + bool sync = info.AllowLeads && !seed.Charm3 && (info.DPPt ? rand >> 15 : rand & 1) == 0; + bool reg = (info.DPPt ? rand / 0xA3E : rand % 25) == info.Nature; + if (!sync && !reg) // doesn't generate nature frame + continue; + + uint prev = RNG.LCRNG.Prev(s); + if (info.AllowLeads && reg) // check for failed sync { - for (uint j = 1 + i; j < 25; j++) - stack.Push(seed = RNG.LCRNG.Prev(seed)); + var failsync = (info.DPPt ? prev >> 31 : (prev >> 16) & 1) != 1; + if (failsync) + yield return info.GetFrame(RNG.LCRNG.Prev(prev), LeadRequired.SynchronizeFail); } - - natureOrigin = RNG.LCRNG.Prev(stack.Peek()); - if (natureOrigin >> 16 % 100 >= 80) // failed proc - return false; - - // init natures - uint[] natures = new uint[25]; - for (uint i = 0; i < 25; i++) - natures[i] = i; - - // shuffle nature list - for (uint i = 0; i < 25; i++) + if (sync) + yield return info.GetFrame(prev, LeadRequired.Synchronize); + if (reg) { - for (uint j = 1 + i; j < 25; j++) + if (seed.Charm3) { - var s = stack.Pop(); - if ((s >> 16 & 1) == 0) - continue; // only swap if 1 - - (natures[i], natures[j]) = (natures[j], natures[i]); + yield return info.GetFrame(prev, LeadRequired.CuteCharm); + } + else + { + if (info.Safari3) + prev = RNG.LCRNG.Prev(prev); // wasted RNG call + yield return info.GetFrame(prev, LeadRequired.None); } - } - - var likes = Pokeblock.GetLikedBlockFlavor(nature); - // best case scenario is a perfect flavored pokeblock for the nature. - // has liked flavor, and all other non-disliked flavors are present. - // is it possible to skip this step? - for (int i = 0; i < 25; i++) - { - var n = natures[i]; - if (n == nature) - break; - - var nl = Pokeblock.GetLikedBlockFlavor(natures[i]); - if (nl == likes) // next random nature likes the same block as the desired nature - return false; // current nature is chosen instead, fail! - } - // unroll once more to hit the level calc (origin with respect for beginning the nature calcs) - natureOrigin = RNG.LCRNG.Prev(natureOrigin); - return true; - } - - /// - /// Filters the input according to a Cute Charm frame generation pattern. - /// - /// Seed Information for the frame - /// Search Info for the frame - /// Possible matches to the Cute Charm frame generation pattern - private static IEnumerable FilterCuteCharm(IEnumerable seeds, FrameGenerator info) - { - foreach (var seed in seeds) - { - var s = seed.Seed; - - var rand = s >> 16; - var nature = info.DPPt ? rand / 0xA3E : rand % 25; - if (nature != info.Nature) - continue; - - var prev = RNG.LCRNG.Prev(s); - var proc = prev >> 16; - bool charmProc = (info.DPPt ? proc / 0x5556 : proc % 3) != 0; // 2/3 odds - if (!charmProc) - continue; - - yield return info.GetFrame(RNG.LCRNG.Prev(prev), LeadRequired.CuteCharm); } } } + + private static bool IsValidPokeBlockNature(uint seed, uint nature, out uint natureOrigin) + { + if (nature % 6 == 0) // neutral + { + natureOrigin = 0; + return false; + } + + // unroll the RNG to a stack of seeds + var stack = new Stack(); + for (uint i = 0; i < 25; i++) + { + for (uint j = 1 + i; j < 25; j++) + stack.Push(seed = RNG.LCRNG.Prev(seed)); + } + + natureOrigin = RNG.LCRNG.Prev(stack.Peek()); + if (natureOrigin >> (16 % 100) >= 80) // failed proc + return false; + + // init natures + uint[] natures = new uint[25]; + for (uint i = 0; i < 25; i++) + natures[i] = i; + + // shuffle nature list + for (uint i = 0; i < 25; i++) + { + for (uint j = 1 + i; j < 25; j++) + { + var s = stack.Pop(); + if (((s >> 16) & 1) == 0) + continue; // only swap if 1 + + (natures[i], natures[j]) = (natures[j], natures[i]); + } + } + + var likes = Pokeblock.GetLikedBlockFlavor(nature); + // best case scenario is a perfect flavored pokeblock for the nature. + // has liked flavor, and all other non-disliked flavors are present. + // is it possible to skip this step? + for (int i = 0; i < 25; i++) + { + var n = natures[i]; + if (n == nature) + break; + + var nl = Pokeblock.GetLikedBlockFlavor(natures[i]); + if (nl == likes) // next random nature likes the same block as the desired nature + return false; // current nature is chosen instead, fail! + } + // unroll once more to hit the level calc (origin with respect for beginning the nature calcs) + natureOrigin = RNG.LCRNG.Prev(natureOrigin); + return true; + } + + /// + /// Filters the input according to a Cute Charm frame generation pattern. + /// + /// Seed Information for the frame + /// Search Info for the frame + /// Possible matches to the Cute Charm frame generation pattern + private static IEnumerable FilterCuteCharm(IEnumerable seeds, FrameGenerator info) + { + foreach (var seed in seeds) + { + var s = seed.Seed; + + var rand = s >> 16; + var nature = info.DPPt ? rand / 0xA3E : rand % 25; + if (nature != info.Nature) + continue; + + var prev = RNG.LCRNG.Prev(s); + var proc = prev >> 16; + bool charmProc = (info.DPPt ? proc / 0x5556 : proc % 3) != 0; // 2/3 odds + if (!charmProc) + continue; + + yield return info.GetFrame(RNG.LCRNG.Prev(prev), LeadRequired.CuteCharm); + } + } } diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs b/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs index 67455fa6e..6f4bab3b3 100644 --- a/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs +++ b/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs @@ -1,102 +1,101 @@ using System; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generator class for Gen3/4 Frame patterns +/// +public sealed class FrameGenerator { - /// - /// Generator class for Gen3/4 Frame patterns - /// - public sealed class FrameGenerator + public uint Nature { get; init; } + public readonly bool Gendered; + public readonly int GenderHigh; + public readonly int GenderLow; + public readonly bool DPPt; + public readonly bool AllowLeads; + public readonly FrameType FrameType; + public readonly RNG RNG = RNG.LCRNG; + public readonly bool Safari3; + + public Frame GetFrame(uint seed, LeadRequired lead) => new(seed, FrameType, lead); + public Frame GetFrame(uint seed, LeadRequired lead, uint esv, uint origin) => GetFrame(seed, lead, esv, esv, origin); + + public Frame GetFrame(uint seed, LeadRequired lead, uint esv, uint lvl, uint origin) => new(seed, FrameType, lead) { - public uint Nature { get; init; } - public readonly bool Gendered; - public readonly int GenderHigh; - public readonly int GenderLow; - public readonly bool DPPt; - public readonly bool AllowLeads; - public readonly FrameType FrameType; - public readonly RNG RNG = RNG.LCRNG; - public readonly bool Safari3; + RandESV = esv, + RandLevel = lvl, + OriginSeed = origin, + }; - public Frame GetFrame(uint seed, LeadRequired lead) => new(seed, FrameType, lead); - public Frame GetFrame(uint seed, LeadRequired lead, uint esv, uint origin) => GetFrame(seed, lead, esv, esv, origin); - - public Frame GetFrame(uint seed, LeadRequired lead, uint esv, uint lvl, uint origin) => new(seed, FrameType, lead) + /// + /// Gets the Search Criteria parameters necessary for generating and objects for Gen3/4 mainline games. + /// + /// object containing various accessible information required for the encounter. + /// Object containing search criteria to be passed by reference to search/filter methods. + public FrameGenerator(PKM pk) + { + var version = (GameVersion)pk.Version; + switch (version) { - RandESV = esv, - RandLevel = lvl, - OriginSeed = origin, - }; + // Method H + case R or S or E or FR or LG: + DPPt = false; + FrameType = FrameType.MethodH; + Safari3 = pk.Ball == 5 && version is not (FR or LG); - /// - /// Gets the Search Criteria parameters necessary for generating and objects for Gen3/4 mainline games. - /// - /// object containing various accessible information required for the encounter. - /// Object containing search criteria to be passed by reference to search/filter methods. - public FrameGenerator(PKM pk) - { - var version = (GameVersion)pk.Version; - switch (version) - { - // Method H - case R or S or E or FR or LG: - DPPt = false; - FrameType = FrameType.MethodH; - Safari3 = pk.Ball == 5 && version is not (FR or LG); - - if (version != E) - return; - - AllowLeads = true; - - // Cute Charm waits for gender too! - var gender = pk.Gender; - bool gendered = gender != 2; - if (!gendered) - return; - - var gr = pk.PersonalInfo.Gender; - Gendered = true; - GenderLow = GetGenderMinMax(gender, gr, false); - GenderHigh = GetGenderMinMax(gender, gr, true); + if (version != E) return; - // Method J - case D or P or Pt: - DPPt = true; - AllowLeads = true; - FrameType = FrameType.MethodJ; + AllowLeads = true; + + // Cute Charm waits for gender too! + var gender = pk.Gender; + bool gendered = gender != 2; + if (!gendered) return; - // Method K - case HG or SS: - DPPt = false; - AllowLeads = true; - FrameType = FrameType.MethodK; - return; - default: - throw new ArgumentOutOfRangeException(nameof(version)); - } + var gr = pk.PersonalInfo.Gender; + Gendered = true; + GenderLow = GetGenderMinMax(gender, gr, false); + GenderHigh = GetGenderMinMax(gender, gr, true); + return; + + // Method J + case D or P or Pt: + DPPt = true; + AllowLeads = true; + FrameType = FrameType.MethodJ; + return; + + // Method K + case HG or SS: + DPPt = false; + AllowLeads = true; + FrameType = FrameType.MethodK; + return; + default: + throw new ArgumentOutOfRangeException(nameof(version), version, "Unknown version."); } - - /// - /// Gets the span of values for a given Gender - /// - /// Gender - /// Gender Ratio - /// Return Max (or Min) - /// Returns the maximum or minimum gender value that corresponds to the input gender ratio. - private static int GetGenderMinMax(int gender, int ratio, bool max) => ratio switch - { - PersonalInfo.RatioMagicMale => max ? 255 : 0, - PersonalInfo.RatioMagicFemale => max ? 255 : 0, - PersonalInfo.RatioMagicGenderless => max ? 255 : 0, - _ => gender switch - { - 0 => max ? 255 : ratio, // male - 1 => max ? ratio - 1 : 0, // female - _ => max ? 255 : 0, - }, - }; } + + /// + /// Gets the span of values for a given Gender + /// + /// Gender + /// Gender Ratio + /// Return Max (or Min) + /// Returns the maximum or minimum gender value that corresponds to the input gender ratio. + private static int GetGenderMinMax(int gender, int ratio, bool max) => ratio switch + { + PersonalInfo.RatioMagicMale => max ? 255 : 0, + PersonalInfo.RatioMagicFemale => max ? 255 : 0, + PersonalInfo.RatioMagicGenderless => max ? 255 : 0, + _ => gender switch + { + 0 => max ? 255 : ratio, // male + 1 => max ? ratio - 1 : 0, // female + _ => max ? 255 : 0, + }, + }; } diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameType.cs b/PKHeX.Core/Legality/RNG/Frame/FrameType.cs index 3dd7a26cc..af865b755 100644 --- a/PKHeX.Core/Legality/RNG/Frame/FrameType.cs +++ b/PKHeX.Core/Legality/RNG/Frame/FrameType.cs @@ -1,19 +1,18 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Type of PID-Nature roll algorithm the frame is obtained from. +/// +public enum FrameType { - /// - /// Type of PID-Nature roll algorithm the frame is obtained from. - /// - public enum FrameType - { - None, + None, - /// Generation 3 PID-Nature roll algorithm - MethodH, + /// Generation 3 PID-Nature roll algorithm + MethodH, - /// Generation 4 PID-Nature roll algorithm present for D/P/Pt - MethodJ, + /// Generation 4 PID-Nature roll algorithm present for D/P/Pt + MethodJ, - /// Generation 4 PID-Nature roll algorithm present for HG/SS - MethodK, - } + /// Generation 4 PID-Nature roll algorithm present for HG/SS + MethodK, } diff --git a/PKHeX.Core/Legality/RNG/Frame/LeadRequired.cs b/PKHeX.Core/Legality/RNG/Frame/LeadRequired.cs index 7704b93b8..005ec3b49 100644 --- a/PKHeX.Core/Legality/RNG/Frame/LeadRequired.cs +++ b/PKHeX.Core/Legality/RNG/Frame/LeadRequired.cs @@ -1,53 +1,52 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Indicates the requirement of the player's lead Pokémon, first sent out when starting a battle. +/// +[Flags] +public enum LeadRequired { - /// - /// Indicates the requirement of the player's lead Pokémon, first sent out when starting a battle. - /// - [Flags] - public enum LeadRequired - { - /// No Lead ability effect is present, or is not checked for this type of frame. - None = 0, + /// No Lead ability effect is present, or is not checked for this type of frame. + None = 0, - /// - CuteCharm = 1 << 0, - /// - Synchronize = 1 << 1, + /// + CuteCharm = 1 << 0, + /// + Synchronize = 1 << 1, - // Slot Modifiers - /// or - StaticMagnet = 1 << 2, + // Slot Modifiers + /// or + StaticMagnet = 1 << 2, - // Level Modifiers - /// or - IntimidateKeenEye = 1 << 3, - /// or or - PressureHustleSpirit = 1 << 4, - /// - SuctionCups = 1 << 5, + // Level Modifiers + /// or + IntimidateKeenEye = 1 << 3, + /// or or + PressureHustleSpirit = 1 << 4, + /// + SuctionCups = 1 << 5, - // Compatibility Flags - UsesLevelCall = 1 << 6, - Fail = 1 << 7, + // Compatibility Flags + UsesLevelCall = 1 << 6, + Fail = 1 << 7, - /// - CuteCharmFail = CuteCharm | Fail, - /// - SynchronizeFail = Synchronize | Fail, - /// - StaticMagnetFail = StaticMagnet | Fail, - /// - PressureHustleSpiritFail = PressureHustleSpirit | Fail, + /// + CuteCharmFail = CuteCharm | Fail, + /// + SynchronizeFail = Synchronize | Fail, + /// + StaticMagnetFail = StaticMagnet | Fail, + /// + PressureHustleSpiritFail = PressureHustleSpirit | Fail, - AllFlags = UsesLevelCall | Fail, - } - - public static partial class Extensions - { - internal static bool IsLevelOrSlotModified(this LeadRequired lead) => lead.RemoveFlags() > LeadRequired.Synchronize; - internal static LeadRequired RemoveFlags(this LeadRequired lead) => lead & ~LeadRequired.AllFlags; - internal static bool NeedsLevelCall(this LeadRequired lead) => (lead & LeadRequired.UsesLevelCall) != 0; - } + AllFlags = UsesLevelCall | Fail, +} + +public static partial class Extensions +{ + internal static bool IsLevelOrSlotModified(this LeadRequired lead) => lead.RemoveFlags() > LeadRequired.Synchronize; + internal static LeadRequired RemoveFlags(this LeadRequired lead) => lead & ~LeadRequired.AllFlags; + internal static bool NeedsLevelCall(this LeadRequired lead) => (lead & LeadRequired.UsesLevelCall) != 0; } diff --git a/PKHeX.Core/Legality/RNG/Frame/LockInfo.cs b/PKHeX.Core/Legality/RNG/Frame/LockInfo.cs index 96be661ee..f1b20b8c8 100644 --- a/PKHeX.Core/Legality/RNG/Frame/LockInfo.cs +++ b/PKHeX.Core/Legality/RNG/Frame/LockInfo.cs @@ -1,23 +1,22 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Status of passing or failing frame match results. +/// +public enum LockInfo { /// - /// Status of passing or failing frame match results. + /// PID matches the required parameters. /// - public enum LockInfo - { - /// - /// PID matches the required parameters. - /// - Pass, + Pass, - /// - /// PID did not match the required Nature. - /// - Nature, + /// + /// PID did not match the required Nature. + /// + Nature, - /// - /// PID did not match the required Gender. - /// - Gender, - } + /// + /// PID did not match the required Gender. + /// + Gender, } diff --git a/PKHeX.Core/Legality/RNG/Frame/Pokeblock.cs b/PKHeX.Core/Legality/RNG/Frame/Pokeblock.cs index 670f16168..d14c7fd69 100644 --- a/PKHeX.Core/Legality/RNG/Frame/Pokeblock.cs +++ b/PKHeX.Core/Legality/RNG/Frame/Pokeblock.cs @@ -1,17 +1,16 @@ -namespace PKHeX.Core -{ - public static class Pokeblock - { - public static Flavor GetLikedBlockFlavor(uint nature) => (Flavor)(nature/5); - public static Flavor GetDislikedBlockFlavor(uint nature) => (Flavor)(nature%5); +namespace PKHeX.Core; - public enum Flavor - { - Spicy, - Sour, - Sweet, - Dry, - Bitter, - } +public static class Pokeblock +{ + public static Flavor GetLikedBlockFlavor(uint nature) => (Flavor)(nature/5); + public static Flavor GetDislikedBlockFlavor(uint nature) => (Flavor)(nature%5); + + public enum Flavor + { + Spicy, + Sour, + Sweet, + Dry, + Bitter, } } diff --git a/PKHeX.Core/Legality/RNG/Frame/SeedInfo.cs b/PKHeX.Core/Legality/RNG/Frame/SeedInfo.cs index b83c0f1fc..41b9b8875 100644 --- a/PKHeX.Core/Legality/RNG/Frame/SeedInfo.cs +++ b/PKHeX.Core/Legality/RNG/Frame/SeedInfo.cs @@ -1,118 +1,117 @@ -using System.Collections.Generic; +using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Seed result value storage for passing frame seeds & conditions. +/// +public readonly record struct SeedInfo(uint Seed, bool Charm3 = false) { /// - /// Seed result value storage for passing frame seeds & conditions. + /// Yields an enumerable list of seeds until another valid PID breaks the chain. /// - public readonly record struct SeedInfo(uint Seed, bool Charm3 = false) + /// Seed and RNG data + /// Verification information + /// Seed information data, which needs to be unrolled once for the nature call. + public static IEnumerable GetSeedsUntilNature(PIDIV pidiv, FrameGenerator info) { - /// - /// Yields an enumerable list of seeds until another valid PID breaks the chain. - /// - /// Seed and RNG data - /// Verification information - /// Seed information data, which needs to be unrolled once for the nature call. - public static IEnumerable GetSeedsUntilNature(PIDIV pidiv, FrameGenerator info) - { - var seed = pidiv.OriginSeed; - yield return new SeedInfo(seed); + var seed = pidiv.OriginSeed; + yield return new SeedInfo(seed); - var s1 = seed; - var s2 = RNG.LCRNG.Prev(s1); - bool charm3 = false; - while (true) + var s1 = seed; + var s2 = RNG.LCRNG.Prev(s1); + bool charm3 = false; + while (true) + { + var a = s2 >> 16; + var b = s1 >> 16; + + var pid = b << 16 | a; + + // Process Conditions + switch (VerifyPIDCriteria(pid, info)) { - var a = s2 >> 16; - var b = s1 >> 16; - - var pid = b << 16 | a; - - // Process Conditions - switch (VerifyPIDCriteria(pid, info)) - { - case LockInfo.Pass: - yield break; - case LockInfo.Gender: - charm3 = true; - break; - } - - s1 = RNG.LCRNG.Prev(s2); - s2 = RNG.LCRNG.Prev(s1); - - yield return new SeedInfo(s1, charm3); + case LockInfo.Pass: + yield break; + case LockInfo.Gender: + charm3 = true; + break; } - } - /// - /// Yields an enumerable list of seeds until another valid PID breaks the chain. - /// - /// Entity data created from the ambiguous cute charm seeds. - /// Seed information data, which needs to be unrolled once for the nature call. - public static IEnumerable GetSeedsUntilNature4Cute(PKM pk) - { - // We cannot rely on a PID-IV origin seed. Since IVs are 2^30, they are not strong enough to assume a single seed was the source. - // We must reverse the IVs to find all seeds that could generate this. - // ESV,Proc,Nature,IV1,IV2; these do not do the nature loop for Method J/K so each seed originates a single seed frame. - var seeds = MethodFinder.GetCuteCharmSeeds(pk); - foreach (var seed in seeds) - yield return new SeedInfo(seed); - } + s1 = RNG.LCRNG.Prev(s2); + s2 = RNG.LCRNG.Prev(s1); - /// - /// Yields an enumerable list of seeds until another valid PID breaks the chain. - /// - /// Seed and RNG data - /// Verification information - /// Unown Form lock value - /// Seed information data, which needs to be unrolled once for the nature call. - public static IEnumerable GetSeedsUntilUnownForm(PIDIV pidiv, FrameGenerator info, int form) - { - var seed = pidiv.OriginSeed; - yield return new SeedInfo(seed); - - var s1 = seed; - var s2 = RNG.LCRNG.Prev(s1); - while (true) - { - var a = s2 >> 16; - var b = s1 >> 16; - // PID is in reverse for FRLG Unown - var pid = a << 16 | b; - - // Process Conditions - if (EntityPID.GetUnownForm3(pid) == form) // matches form, does it match nature? - { - switch (VerifyPIDCriteria(pid, info)) - { - case LockInfo.Pass: // yes - yield break; - } - } - - s1 = RNG.LCRNG.Prev(s2); - s2 = RNG.LCRNG.Prev(s1); - - yield return new SeedInfo(s1); - } - } - - private static LockInfo VerifyPIDCriteria(uint pid, FrameGenerator info) - { - // Nature locks are always a given - var nval = pid % 25; - if (nval != info.Nature) - return LockInfo.Nature; - - if (!info.Gendered) - return LockInfo.Pass; - - var gender = pid & 0xFF; - if (info.GenderLow > gender || gender > info.GenderHigh) - return LockInfo.Gender; - - return LockInfo.Pass; + yield return new SeedInfo(s1, charm3); } } + + /// + /// Yields an enumerable list of seeds until another valid PID breaks the chain. + /// + /// Entity data created from the ambiguous cute charm seeds. + /// Seed information data, which needs to be unrolled once for the nature call. + public static IEnumerable GetSeedsUntilNature4Cute(PKM pk) + { + // We cannot rely on a PID-IV origin seed. Since IVs are 2^30, they are not strong enough to assume a single seed was the source. + // We must reverse the IVs to find all seeds that could generate this. + // ESV,Proc,Nature,IV1,IV2; these do not do the nature loop for Method J/K so each seed originates a single seed frame. + var seeds = MethodFinder.GetCuteCharmSeeds(pk); + foreach (var seed in seeds) + yield return new SeedInfo(seed); + } + + /// + /// Yields an enumerable list of seeds until another valid PID breaks the chain. + /// + /// Seed and RNG data + /// Verification information + /// Unown Form lock value + /// Seed information data, which needs to be unrolled once for the nature call. + public static IEnumerable GetSeedsUntilUnownForm(PIDIV pidiv, FrameGenerator info, int form) + { + var seed = pidiv.OriginSeed; + yield return new SeedInfo(seed); + + var s1 = seed; + var s2 = RNG.LCRNG.Prev(s1); + while (true) + { + var a = s2 >> 16; + var b = s1 >> 16; + // PID is in reverse for FRLG Unown + var pid = (a << 16) | b; + + // Process Conditions + if (EntityPID.GetUnownForm3(pid) == form) // matches form, does it match nature? + { + switch (VerifyPIDCriteria(pid, info)) + { + case LockInfo.Pass: // yes + yield break; + } + } + + s1 = RNG.LCRNG.Prev(s2); + s2 = RNG.LCRNG.Prev(s1); + + yield return new SeedInfo(s1); + } + } + + private static LockInfo VerifyPIDCriteria(uint pid, FrameGenerator info) + { + // Nature locks are always a given + var nval = pid % 25; + if (nval != info.Nature) + return LockInfo.Nature; + + if (!info.Gendered) + return LockInfo.Pass; + + var gender = pid & 0xFF; + if (info.GenderLow > gender || gender > info.GenderHigh) + return LockInfo.Gender; + + return LockInfo.Pass; + } } diff --git a/PKHeX.Core/Legality/RNG/Frame/SlotRange.cs b/PKHeX.Core/Legality/RNG/Frame/SlotRange.cs index 46aa23d1a..31a31b647 100644 --- a/PKHeX.Core/Legality/RNG/Frame/SlotRange.cs +++ b/PKHeX.Core/Legality/RNG/Frame/SlotRange.cs @@ -1,186 +1,185 @@ using System.Linq; using static PKHeX.Core.SlotType; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// RNG Encounter Slot Ranges to convert the [0,100) value into a slot index. +/// +public static class SlotRange { + private static readonly Range[] H_OldRod = GetRanges(70, 30); + private static readonly Range[] H_GoodRod = GetRanges(60, 20, 20); + private static readonly Range[] H_SuperRod = GetRanges(40, 40, 15, 4, 1); + private static readonly Range[] H_Surf = GetRanges(60, 30, 5, 4, 1); + private static readonly Range[] H_Regular = GetRanges(20, 20, 10, 10, 10, 10, 5, 5, 4, 4, 1, 1); + + private static readonly Range[] J_SuperRod = GetRanges(40, 40, 15, 4, 1); + private static readonly Range[] K_SuperRod = GetRanges(40, 30, 15, 10, 5); + private static readonly Range[] K_BCC = GetRanges(5,5,5,5, 10,10,10,10, 20,20).Reverse().ToArray(); + private static readonly Range[] K_Headbutt = GetRanges(50, 15, 15, 10, 5, 5); + + private const int Invalid = -1; // all slots are [0,X], unsigned. This will always result in a non-match. + /// - /// RNG Encounter Slot Ranges to convert the [0,100) value into a slot index. + /// Gets the from the raw 16bit seed half. /// - public static class SlotRange + public static int GetSlot(SlotType type, uint rand, FrameType t) => t switch { - private static readonly Range[] H_OldRod = GetRanges(70, 30); - private static readonly Range[] H_GoodRod = GetRanges(60, 20, 20); - private static readonly Range[] H_SuperRod = GetRanges(40, 40, 15, 4, 1); - private static readonly Range[] H_Surf = GetRanges(60, 30, 5, 4, 1); - private static readonly Range[] H_Regular = GetRanges(20, 20, 10, 10, 10, 10, 5, 5, 4, 4, 1, 1); + FrameType.MethodH => HSlot(type, rand), + FrameType.MethodJ => JSlot(type, rand), + FrameType.MethodK => KSlot(type, rand), + _ => Invalid, + }; - private static readonly Range[] J_SuperRod = GetRanges(40, 40, 15, 4, 1); - private static readonly Range[] K_SuperRod = GetRanges(40, 30, 15, 10, 5); - private static readonly Range[] K_BCC = GetRanges(5,5,5,5, 10,10,10,10, 20,20).Reverse().ToArray(); - private static readonly Range[] K_Headbutt = GetRanges(50, 15, 15, 10, 5, 5); + /// + /// Gets the from the raw 16bit seed half. + /// + private static int HSlot(SlotType type, uint rand) + { + var ESV = rand % 100; + if ((type & Swarm) != 0) + return ESV < 50 ? 0 : Invalid; - private const int Invalid = -1; // all slots are [0,X], unsigned. This will always result in a non-match. - - /// - /// Gets the from the raw 16bit seed half. - /// - public static int GetSlot(SlotType type, uint rand, FrameType t) => t switch + return type switch { - FrameType.MethodH => HSlot(type, rand), - FrameType.MethodJ => JSlot(type, rand), - FrameType.MethodK => KSlot(type, rand), - _ => Invalid, + Old_Rod => CalcSlot(ESV, H_OldRod), + Good_Rod => CalcSlot(ESV, H_GoodRod), + Super_Rod => CalcSlot(ESV, H_SuperRod), + Rock_Smash => CalcSlot(ESV, H_Surf), + Surf => CalcSlot(ESV, H_Surf), + _ => CalcSlot(ESV, H_Regular), }; + } - /// - /// Gets the from the raw 16bit seed half. - /// - private static int HSlot(SlotType type, uint rand) + /// + /// Gets the from the raw 16bit seed half. + /// + private static int KSlot(SlotType type, uint rand) + { + var ESV = rand % 100; + return type switch { - var ESV = rand % 100; - if ((type & Swarm) != 0) - return ESV < 50 ? 0 : Invalid; + Rock_Smash or Surf => CalcSlot(ESV, H_Surf), + Old_Rod or Good_Rod or Super_Rod => CalcSlot(ESV, K_SuperRod), + BugContest => CalcSlot(ESV, K_BCC), + Headbutt or (Headbutt | Special) => CalcSlot(ESV, K_Headbutt), + _ => CalcSlot(ESV, H_Regular), + }; + } - return type switch - { - Old_Rod => CalcSlot(ESV, H_OldRod), - Good_Rod => CalcSlot(ESV, H_GoodRod), - Super_Rod => CalcSlot(ESV, H_SuperRod), - Rock_Smash => CalcSlot(ESV, H_Surf), - Surf => CalcSlot(ESV, H_Surf), - _ => CalcSlot(ESV, H_Regular), - }; - } - - /// - /// Gets the from the raw 16bit seed half. - /// - private static int KSlot(SlotType type, uint rand) + /// + /// Gets the from the raw 16bit seed half. + /// + private static int JSlot(SlotType type, uint rand) + { + uint ESV = rand / 656; + return type switch { - var ESV = rand % 100; - return type switch - { - Rock_Smash or Surf => CalcSlot(ESV, H_Surf), - Old_Rod or Good_Rod or Super_Rod => CalcSlot(ESV, K_SuperRod), - BugContest => CalcSlot(ESV, K_BCC), - Headbutt or (Headbutt | Special) => CalcSlot(ESV, K_Headbutt), - _ => CalcSlot(ESV, H_Regular), - }; - } + Old_Rod or Rock_Smash or Surf => CalcSlot(ESV, H_Surf), + Good_Rod or Super_Rod => CalcSlot(ESV, J_SuperRod), + HoneyTree => 0, + _ => CalcSlot(ESV, H_Regular), + }; + } - /// - /// Gets the from the raw 16bit seed half. - /// - private static int JSlot(SlotType type, uint rand) + private readonly record struct Range(uint Min, uint Max); + + private static Range[] GetRanges(params byte[] rates) + { + var len = rates.Length; + var arr = new Range[len]; + uint sum = 0; + for (int i = 0; i < len; ++i) + arr[i] = new Range(sum, (sum += rates[i]) - 1); + return arr; + } + + private static int CalcSlot(uint esv, Range[] ranges) + { + for (int i = 0; i < ranges.Length; ++i) { - uint ESV = rand / 656; - return type switch - { - Old_Rod or Rock_Smash or Surf => CalcSlot(ESV, H_Surf), - Good_Rod or Super_Rod => CalcSlot(ESV, J_SuperRod), - HoneyTree => 0, - _ => CalcSlot(ESV, H_Regular), - }; + var (min, max) = ranges[i]; + if (esv >= min && esv <= max) + return i; } + return Invalid; + } - private readonly record struct Range(uint Min, uint Max); + public static int GetLevel(EncounterSlot slot, LeadRequired lead, uint lvlrand) + { + if (lead == LeadRequired.PressureHustleSpirit) + return slot.LevelMax; + if (slot.IsFixedLevel) + return slot.LevelMin; + int delta = slot.LevelMax - slot.LevelMin + 1; + var adjust = (int)(lvlrand % delta); - private static Range[] GetRanges(params byte[] rates) - { - var len = rates.Length; - var arr = new Range[len]; - uint sum = 0; - for (int i = 0; i < len; ++i) - arr[i] = new Range(sum, (sum += rates[i]) - 1); - return arr; - } - - private static int CalcSlot(uint esv, Range[] ranges) - { - for (int i = 0; i < ranges.Length; ++i) - { - var (min, max) = ranges[i]; - if (esv >= min && esv <= max) - return i; - } - return Invalid; - } - - public static int GetLevel(EncounterSlot slot, LeadRequired lead, uint lvlrand) - { - if (lead == LeadRequired.PressureHustleSpirit) - return slot.LevelMax; - if (slot.IsFixedLevel) - return slot.LevelMin; - int delta = slot.LevelMax - slot.LevelMin + 1; - var adjust = (int)(lvlrand % delta); - - return slot.LevelMin + adjust; - } + return slot.LevelMin + adjust; + } #pragma warning disable IDE0060, RCS1163 // Unused parameter. - public static bool GetIsEncounterable(T slot, FrameType frameType, int rand, LeadRequired lead) where T : ISlotRNGType + public static bool GetIsEncounterable(T slot, FrameType frameType, int rand, LeadRequired lead) where T : ISlotRNGType #pragma warning restore IDE0060, RCS1163 // Unused parameter. - { - if (slot.Type.IsSweetScentType()) - return true; - return true; // todo - //return GetCanEncounter(slot, frameType, rand, lead); - } + { + if (slot.Type.IsSweetScentType()) + return true; + return true; // todo + //return GetCanEncounter(slot, frameType, rand, lead); + } - // ReSharper disable once UnusedMember.Global - public static bool GetCanEncounter(T slot, FrameType frameType, int rand, LeadRequired lead) where T : ISlotRNGType - { - int proc = frameType == FrameType.MethodJ ? rand / 656 : rand % 100; - var stype = slot.Type; - if (stype == Rock_Smash) - return proc < 60; - if (frameType == FrameType.MethodH) - return true; // fishing encounters are disjointed by the hooked message. - return GetCanEncounterFish(lead, stype, proc); - } + // ReSharper disable once UnusedMember.Global + public static bool GetCanEncounter(T slot, FrameType frameType, int rand, LeadRequired lead) where T : ISlotRNGType + { + int proc = frameType == FrameType.MethodJ ? rand / 656 : rand % 100; + var stype = slot.Type; + if (stype == Rock_Smash) + return proc < 60; + if (frameType == FrameType.MethodH) + return true; // fishing encounters are disjointed by the hooked message. + return GetCanEncounterFish(lead, stype, proc); + } - private static bool GetCanEncounterFish(LeadRequired lead, SlotType stype, int proc) => stype switch + private static bool GetCanEncounterFish(LeadRequired lead, SlotType stype, int proc) => stype switch + { + // Lead:None => can be suction cups + Old_Rod => proc switch { - // Lead:None => can be suction cups - Old_Rod => proc switch - { - < 25 => true, - < 50 => lead == LeadRequired.None, - _ => false, - }, - Good_Rod => proc switch - { - < 50 => true, - < 75 => lead == LeadRequired.None, - _ => false, - }, - Super_Rod => proc < 75 || lead == LeadRequired.None, - + < 25 => true, + < 50 => lead == LeadRequired.None, _ => false, - }; - - /// - /// Checks both Static and Magnet Pull ability type selection encounters to see if the encounter can be selected. - /// - /// Slot Data - /// Rand16 value for the call - /// Slot number from the slot data if the slot is selected on this frame, else an invalid slot value. - internal static int GetSlotStaticMagnet(T slot, uint esv) where T : EncounterSlot, IMagnetStatic, INumberedSlot + }, + Good_Rod => proc switch { - if (slot.IsStaticSlot()) - { - var index = esv % slot.StaticCount; - if (index == slot.StaticIndex) - return slot.SlotNumber; - } - if (slot.IsMagnetSlot()) - { - var index = esv % slot.MagnetPullCount; - if (index == slot.MagnetPullIndex) - return slot.SlotNumber; - } - return Invalid; + < 50 => true, + < 75 => lead == LeadRequired.None, + _ => false, + }, + Super_Rod => proc < 75 || lead == LeadRequired.None, + + _ => false, + }; + + /// + /// Checks both Static and Magnet Pull ability type selection encounters to see if the encounter can be selected. + /// + /// Slot Data + /// Rand16 value for the call + /// Slot number from the slot data if the slot is selected on this frame, else an invalid slot value. + internal static int GetSlotStaticMagnet(T slot, uint esv) where T : EncounterSlot, IMagnetStatic, INumberedSlot + { + if (slot.IsStaticSlot()) + { + var index = esv % slot.StaticCount; + if (index == slot.StaticIndex) + return slot.SlotNumber; } + if (slot.IsMagnetSlot()) + { + var index = esv % slot.MagnetPullCount; + if (index == slot.MagnetPullIndex) + return slot.SlotNumber; + } + return Invalid; } } diff --git a/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs b/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs index ad5ce05bf..051075dae 100644 --- a/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs +++ b/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs @@ -1,111 +1,110 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class LockFinder { - public static class LockFinder + public static bool IsAllShadowLockValid(EncounterStaticShadow s, PIDIV pv, PKM pk) { - public static bool IsAllShadowLockValid(EncounterStaticShadow s, PIDIV pv, PKM pkm) - { - if (s.Version == GameVersion.XD && pkm.IsShiny) - return false; // no xd shiny shadow mons - var teams = s.Locks; - if (teams.Length == 0) - return true; - - var tsv = s.Version == GameVersion.XD ? (pkm.TID ^ pkm.SID) >> 3 : -1; // no xd shiny shadow mons - return IsAllShadowLockValid(pv, teams, tsv); - } - - public static bool IsAllShadowLockValid(PIDIV pv, IEnumerable teams, int tsv = -1) - { - foreach (var t in teams) - { - var result = new TeamLockResult(t, pv.OriginSeed, tsv); - if (result.Valid) - return true; - } - return false; - } - - // Colosseum/XD Starters - public static bool IsXDStarterValid(uint seed, int TID, int SID) - { - // pidiv reversed 2x yields SID, 3x yields TID. shift by 7 if another PKM is generated prior - var SIDf = RNG.XDRNG.Reverse(seed, 2); - var TIDf = RNG.XDRNG.Prev(SIDf); - return SIDf >> 16 == SID && TIDf >> 16 == TID; - } - - public static bool IsColoStarterValid(int species, ref uint seed, int TID, int SID, uint pkPID, uint IV1, uint IV2) - { - // reverse the seed the bare minimum - int rev = 2; - if (species == (int)Species.Espeon) - rev += 7; - - var rng = RNG.XDRNG; - var SIDf = rng.Reverse(seed, rev); - int ctr = 0; - uint temp; - while ((temp = rng.Prev(SIDf)) >> 16 != TID || SIDf >> 16 != SID) - { - SIDf = temp; - if (ctr > 32) // arbitrary - return false; - ctr++; - } - - var next = rng.Next(SIDf); - - // generate Umbreon - var PIDIV = GenerateValidColoStarterPID(ref next, TID, SID); - if (species == (int)Species.Espeon) // need Espeon, which is immediately next - PIDIV = GenerateValidColoStarterPID(ref next, TID, SID); - - if (!PIDIV.Equals(pkPID, IV1, IV2)) - return false; - seed = rng.Reverse(SIDf, 2); + if (s.Version == GameVersion.XD && pk.IsShiny) + return false; // no xd shiny shadow mons + var teams = s.Locks; + if (teams.Length == 0) return true; - } - private readonly record struct PIDIVGroup(uint PID, uint IV1, uint IV2) + var tsv = s.Version == GameVersion.XD ? (pk.TID ^ pk.SID) >> 3 : -1; // no xd shiny shadow mons + return IsAllShadowLockValid(pv, teams, tsv); + } + + public static bool IsAllShadowLockValid(PIDIV pv, IEnumerable teams, int tsv = -1) + { + foreach (var t in teams) { - public bool Equals(uint pid, uint iv1, uint iv2) => PID == pid && IV1 == iv1 && IV2 == iv2; + var result = new TeamLockResult(t, pv.OriginSeed, tsv); + if (result.Valid) + return true; } + return false; + } - private static PIDIVGroup GenerateValidColoStarterPID(ref uint uSeed, int TID, int SID) + // Colosseum/XD Starters + public static bool IsXDStarterValid(uint seed, int TID, int SID) + { + // pidiv reversed 2x yields SID, 3x yields TID. shift by 7 if another PKM is generated prior + var SIDf = RNG.XDRNG.Reverse(seed, 2); + var TIDf = RNG.XDRNG.Prev(SIDf); + return SIDf >> 16 == SID && TIDf >> 16 == TID; + } + + public static bool IsColoStarterValid(int species, ref uint seed, int TID, int SID, uint pkPID, uint IV1, uint IV2) + { + // reverse the seed the bare minimum + int rev = 2; + if (species == (int)Species.Espeon) + rev += 7; + + var rng = RNG.XDRNG; + var SIDf = rng.Reverse(seed, rev); + int ctr = 0; + uint temp; + while ((temp = rng.Prev(SIDf)) >> 16 != TID || SIDf >> 16 != SID) { - var rng = RNG.XDRNG; - - uSeed = rng.Advance(uSeed, 2); // skip fakePID - var IV1 = (uSeed >> 16) & 0x7FFF; - uSeed = rng.Next(uSeed); - var IV2 = (uSeed >> 16) & 0x7FFF; - uSeed = rng.Next(uSeed); - uSeed = rng.Advance(uSeed, 1); // skip ability call - var PID = GenerateStarterPID(ref uSeed, TID, SID); - - uSeed = rng.Advance(uSeed, 2); // PID calls consumed - - return new PIDIVGroup(PID, IV1, IV2); + SIDf = temp; + if (ctr > 32) // arbitrary + return false; + ctr++; } - private static bool IsShiny(int TID, int SID, uint PID) => (TID ^ SID ^ (PID >> 16) ^ (PID & 0xFFFF)) < 8; + var next = rng.Next(SIDf); - private static uint GenerateStarterPID(ref uint uSeed, int TID, int SID) + // generate Umbreon + var PIDIV = GenerateValidColoStarterPID(ref next, TID, SID); + if (species == (int)Species.Espeon) // need Espeon, which is immediately next + PIDIV = GenerateValidColoStarterPID(ref next, TID, SID); + + if (!PIDIV.Equals(pkPID, IV1, IV2)) + return false; + seed = rng.Reverse(SIDf, 2); + return true; + } + + private readonly record struct PIDIVGroup(uint PID, uint IV1, uint IV2) + { + public bool Equals(uint pid, uint iv1, uint iv2) => PID == pid && IV1 == iv1 && IV2 == iv2; + } + + private static PIDIVGroup GenerateValidColoStarterPID(ref uint uSeed, int TID, int SID) + { + var rng = RNG.XDRNG; + + uSeed = rng.Advance(uSeed, 2); // skip fakePID + var IV1 = (uSeed >> 16) & 0x7FFF; + uSeed = rng.Next(uSeed); + var IV2 = (uSeed >> 16) & 0x7FFF; + uSeed = rng.Next(uSeed); + uSeed = rng.Advance(uSeed, 1); // skip ability call + var PID = GenerateStarterPID(ref uSeed, TID, SID); + + uSeed = rng.Advance(uSeed, 2); // PID calls consumed + + return new PIDIVGroup(PID, IV1, IV2); + } + + private static bool IsShiny(int TID, int SID, uint PID) => (TID ^ SID ^ (PID >> 16) ^ (PID & 0xFFFF)) < 8; + + private static uint GenerateStarterPID(ref uint uSeed, int TID, int SID) + { + uint PID; + const byte ratio = 0x1F; // 12.5% F (can't be female) + while (true) { - uint PID; - const byte ratio = 0x1F; // 12.5% F (can't be female) - while (true) - { - var next = RNG.XDRNG.Next(uSeed); - PID = (uSeed & 0xFFFF0000) | (next >> 16); - if ((PID & 0xFF) >= ratio && !IsShiny(TID, SID, PID)) - break; - uSeed = RNG.XDRNG.Next(next); - } - - return PID; + var next = RNG.XDRNG.Next(uSeed); + PID = (uSeed & 0xFFFF0000) | (next >> 16); + if ((PID & 0xFF) >= ratio && !IsShiny(TID, SID, PID)) + break; + uSeed = RNG.XDRNG.Next(next); } + + return PID; } } diff --git a/PKHeX.Core/Legality/RNG/Locks/NPCLock.cs b/PKHeX.Core/Legality/RNG/Locks/NPCLock.cs index a3ccc743b..1a6f4a56c 100644 --- a/PKHeX.Core/Legality/RNG/Locks/NPCLock.cs +++ b/PKHeX.Core/Legality/RNG/Locks/NPCLock.cs @@ -1,67 +1,66 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Locks associated to a given NPC PKM that appears before a . +/// +public readonly record struct NPCLock { - /// - /// Locks associated to a given NPC PKM that appears before a . - /// - public readonly record struct NPCLock + private readonly int Species; + private readonly byte Nature; + private readonly byte Gender; + private readonly byte Ratio; + private readonly byte State; + + public int FramesConsumed => Seen ? 5 : 7; + public bool Seen => State > 1; + public bool Shadow => State != 0; + public (byte Nature, byte Gender) GetLock => (Nature, Gender); + + // Not-Shadow + public NPCLock(short s, byte n, byte g, byte r) { - private readonly int Species; - private readonly byte Nature; - private readonly byte Gender; - private readonly byte Ratio; - private readonly byte State; + Species = s; + Nature = n; + Gender = g; + Ratio = r; + State = 0; + } - public int FramesConsumed => Seen ? 5 : 7; - public bool Seen => State > 1; - public bool Shadow => State != 0; - public (byte Nature, byte Gender) GetLock => (Nature, Gender); + // Shadow + public NPCLock(short s, bool seen = false) + { + Species = s; + Nature = 0; + Gender = 0; + Ratio = 0; + State = seen ? (byte)2 : (byte)1; + } - // Not-Shadow - public NPCLock(short s, byte n, byte g, byte r) - { - Species = s; - Nature = n; - Gender = g; - Ratio = r; - State = 0; - } - - // Shadow - public NPCLock(short s, bool seen = false) - { - Species = s; - Nature = 0; - Gender = 0; - Ratio = 0; - State = seen ? (byte)2 : (byte)1; - } - - public bool MatchesLock(uint PID) - { - if (Shadow && Nature == 0) // Non-locked shadow - return true; - if (Gender != 2 && Gender != ((PID & 0xFF) < Ratio ? 1 : 0)) - return false; - if (Nature != PID % 25) - return false; + public bool MatchesLock(uint PID) + { + if (Shadow && Nature == 0) // Non-locked shadow return true; - } + if (Gender != 2 && Gender != ((PID & 0xFF) < Ratio ? 1 : 0)) + return false; + if (Nature != PID % 25) + return false; + return true; + } #if DEBUG - public override string ToString() - { - var sb = new System.Text.StringBuilder(64); - sb.Append((Species)Species); - if (State != 0) - sb.Append(" (Shadow)"); - if (Seen) - sb.Append(" [Seen]"); - sb.Append(" - "); - sb.Append("Nature: ").Append((Nature)Nature); - if (Gender != 2) - sb.Append(", ").Append("Gender: ").Append(Gender); - return sb.ToString(); - } -#endif + public override string ToString() + { + var sb = new System.Text.StringBuilder(64); + sb.Append((Species)Species); + if (State != 0) + sb.Append(" (Shadow)"); + if (Seen) + sb.Append(" [Seen]"); + sb.Append(" - "); + sb.Append("Nature: ").Append((Nature)Nature); + if (Gender != 2) + sb.Append(", ").Append("Gender: ").Append(Gender); + return sb.ToString(); } +#endif } diff --git a/PKHeX.Core/Legality/RNG/Locks/TeamLock.cs b/PKHeX.Core/Legality/RNG/Locks/TeamLock.cs index eac975a55..476eaebed 100644 --- a/PKHeX.Core/Legality/RNG/Locks/TeamLock.cs +++ b/PKHeX.Core/Legality/RNG/Locks/TeamLock.cs @@ -1,25 +1,24 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class TeamLock { - public sealed class TeamLock + public readonly NPCLock[] Locks; + public readonly string Comment; + public readonly int Species; + + public TeamLock(int species, NPCLock[] locks) : this(species, string.Empty, locks) { } + + public TeamLock(int species, string comment, NPCLock[] locks) { - public readonly NPCLock[] Locks; - public readonly string Comment; - public readonly int Species; + Species = species; + Locks = locks; + Comment = comment; + } - public TeamLock(int species, NPCLock[] locks) : this(species, string.Empty, locks) { } - - public TeamLock(int species, string comment, NPCLock[] locks) - { - Species = species; - Locks = locks; - Comment = comment; - } - - public override string ToString() - { - if (Comment.Length == 0) - return $"{(Species)Species} [{Locks.Length}]"; - return $"{(Species)Species} [{Locks.Length}] - {Comment}"; - } + public override string ToString() + { + if (Comment.Length == 0) + return $"{(Species)Species} [{Locks.Length}]"; + return $"{(Species)Species} [{Locks.Length}] - {Comment}"; } } diff --git a/PKHeX.Core/Legality/RNG/Locks/TeamLockResult.cs b/PKHeX.Core/Legality/RNG/Locks/TeamLockResult.cs index d0ec47dba..0e00efab5 100644 --- a/PKHeX.Core/Legality/RNG/Locks/TeamLockResult.cs +++ b/PKHeX.Core/Legality/RNG/Locks/TeamLockResult.cs @@ -1,265 +1,263 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Analyzes a to determine if the provided parameters are a valid end product. +/// +/// +/// When generating a Trainer Team for , the game must generate PID values that match the programmed team specifications. +/// The game is 'locked' into a PID generating loop until a valid PID is created, after which the routine is unlocked and proceeds to generate the next member. +/// These locks cause the of the current to be rerolled until the requisite lock is satisfied. +/// This locking is the same as Method H/J/K and Gen3 Cute Charm by extension, with the additional restriction of not allowing shinies. +/// locks require a certain , which is derived from the . +/// locks require a certain gender value, which is derived from the and ratio. +/// locks require the member to not be shiny. This is enforced for non-shadow members for both games, and for shadow members in . +/// +public sealed class TeamLockResult { /// - /// Analyzes a to determine if the provided parameters are a valid end product. + /// Seed prior to generating the team per . + /// + public readonly uint OriginSeed; + + /// + /// Indicates if the input parameters can be generated by the provided. + /// + public readonly bool Valid; + + /// + /// NPC Team data containing a list of members. + /// + internal readonly TeamLock Specifications; + + /// + /// Frame the is from, based on frames reversed from the input seed provided. + /// + private int OriginFrame; + + /// + /// Required CPU Trainer Shiny Value /// /// - /// When generating a Trainer Team for , the game must generate PID values that match the programmed team specifications. - /// The game is 'locked' into a PID generating loop until a valid PID is created, after which the routine is unlocked and proceeds to generate the next member. - /// These locks cause the of the current to be rerolled until the requisite lock is satisfied. - /// This locking is the same as Method H/J/K and Gen3 Cute Charm by extension, with the additional restriction of not allowing shinies. - /// locks require a certain , which is derived from the . - /// locks require a certain gender value, which is derived from the and ratio. - /// locks require the member to not be shiny. This is enforced for non-shadow members for both games, and for shadow members in . + /// If this value is >= 0, the CPU Trainer Shiny Value must be equal to this value as a skipped over a matching interrupt frame. + /// If this value is , the CPU Trainer Shiny Value can be anything (except matching any of the result members. /// - public sealed class TeamLockResult + private int RCSV = NOT_FORCED; + + /// + /// Player Trainer Shiny Value + /// + /// Only used by encounters, which disallow shiny shadow members for both player & CPU TSVs. + private readonly int TSV; + + private readonly Stack Locks; + private readonly FrameCache Cache; + // only save a list of valid nodes we've visited to save memory while we recursively search for a full team. + private readonly Stack Team; + + private const int NOT_FORCED = -1; + + internal TeamLockResult(TeamLock teamSpec, uint originSeed, int tsv) { - /// - /// Seed prior to generating the team per . - /// - public readonly uint OriginSeed; + Locks = new Stack((Specifications = teamSpec).Locks); + Team = new Stack(Locks.Count); + Cache = new FrameCache(RNG.XDRNG.Reverse(originSeed, 2), RNG.XDRNG.Prev); + TSV = tsv; - /// - /// Indicates if the input parameters can be generated by the provided. - /// - public readonly bool Valid; + Valid = FindLockSeed(); + if (Valid) + OriginSeed = Cache.GetSeed(OriginFrame); + } - /// - /// NPC Team data containing a list of members. - /// - internal readonly TeamLock Specifications; + /// + /// Depth-first search traversal which finds a possible origin for the . + /// + /// Frame at which the search starts/continues at. + /// Prior data. If this is the last lock in the CPU Team, this is null. + /// True if the are valid. + private bool FindLockSeed(int frame = 0, NPCLock? prior = null) + { + if (Locks.Count == 0) // full team reverse-generated + return VerifyNPC(frame); - /// - /// Frame the is from, based on frames reversed from the input seed provided. - /// - private int OriginFrame; - - /// - /// Required CPU Trainer Shiny Value - /// - /// - /// If this value is >= 0, the CPU Trainer Shiny Value must be equal to this value as a skipped over a matching interrupt frame. - /// If this value is , the CPU Trainer Shiny Value can be anything (except matching any of the result members. - /// - private int RCSV = NOT_FORCED; - - /// - /// Player Trainer Shiny Value - /// - /// Only used by encounters, which disallow shiny shadow members for both player & CPU TSVs. - private readonly int TSV; - - private readonly Stack Locks; - private readonly FrameCache Cache; - // only save a list of valid nodes we've visited to save memory while we recursively search for a full team. - private readonly Stack Team; - - private const int NOT_FORCED = -1; - - internal TeamLockResult(TeamLock teamSpec, uint originSeed, int tsv) + var current = Locks.Pop(); + var locks = GetPossibleLocks(frame, current, prior); + foreach (var l in locks) { - Locks = new Stack((Specifications = teamSpec).Locks); - Team = new Stack(Locks.Count); - Cache = new FrameCache(RNG.XDRNG.Reverse(originSeed, 2), RNG.XDRNG.Prev); - TSV = tsv; - - Valid = FindLockSeed(); - if (Valid) - OriginSeed = Cache.GetSeed(OriginFrame); + Team.Push(l); // possible match + if (FindLockSeed(l.FrameID, current)) + return true; // all locks are satisfied + Team.Pop(); // no match, remove } - /// - /// Depth-first search traversal which finds a possible origin for the . - /// - /// Frame at which the search starts/continues at. - /// Prior data. If this is the last lock in the CPU Team, this is null. - /// True if the are valid. - private bool FindLockSeed(int frame = 0, NPCLock? prior = null) - { - if (Locks.Count == 0) // full team reverse-generated - return VerifyNPC(frame); + Locks.Push(current); // return the lock, lock is impossible + return false; + } - var current = Locks.Pop(); - var locks = GetPossibleLocks(frame, current, prior); - foreach (var l in locks) + /// + /// Generates a list of frames the lock data can be generated at. + /// + /// Starting frame for the traversal. + /// Current lock criteria to satisfy. Used to find valid results to yield. + /// Prior lock criteria. Used for determining when the traversal stops. + /// List of possible locks for the provided input. + private IEnumerable GetPossibleLocks(int ctr, NPCLock current, NPCLock? prior) + { + if (prior?.Shadow != false) + return GetSingleLock(ctr, current); + + return GetAllLocks(ctr, current, (NPCLock)prior); + } + + /// + /// Returns a single as the lock must match precisely. + /// + /// Starting frame for the traversal. + /// Current lock criteria to satisfy. Used to find valid results to yield. + private IEnumerable GetSingleLock(int ctr, NPCLock current) + { + uint pid = (Cache[ctr + 1] << 16) | Cache[ctr]; + if (current.MatchesLock(pid)) + yield return new SeedFrame(pid, ctr + current.FramesConsumed); + else + yield break; + + // Reaching here means the single lock didn't cut it. Maybe the frame before it was an anti-shiny reroll? + + // Track if we ever require the CPU Trainer Shiny Value to be a value for a shiny skip. + // We need to un-set this flag if future frames don't pan out. + bool forcedOT = false; + + int start = 2; + while (true) + { + var upper = Cache[start + 1]; + var lower = Cache[start]; + // uint cid = upper << 16 | lower; + var sv = (upper ^ lower) >> 3; + if (sv == TSV) // XD shiny checks all opponent PKM, even non-shadow. { - Team.Push(l); // possible match - if (FindLockSeed(l.FrameID, current)) - return true; // all locks are satisfied - Team.Pop(); // no match, remove + // Anti-shiny rerolled! This is a possible frame. } - - Locks.Push(current); // return the lock, lock is impossible - return false; - } - - /// - /// Generates a list of frames the lock data can be generated at. - /// - /// Starting frame for the traversal. - /// Current lock criteria to satisfy. Used to find valid results to yield. - /// Prior lock criteria. Used for determining when the traversal stops. - /// List of possible locks for the provided input. - private IEnumerable GetPossibleLocks(int ctr, NPCLock current, NPCLock? prior) - { - if (prior?.Shadow != false) - return GetSingleLock(ctr, current); - - return GetAllLocks(ctr, current, (NPCLock)prior); - } - - /// - /// Returns a single as the lock must match precisely. - /// - /// Starting frame for the traversal. - /// Current lock criteria to satisfy. Used to find valid results to yield. - /// - private IEnumerable GetSingleLock(int ctr, NPCLock current) - { - uint pid = Cache[ctr + 1] << 16 | Cache[ctr]; - if (current.MatchesLock(pid)) - yield return new SeedFrame(pid, ctr + current.FramesConsumed); - else - yield break; - - // Reaching here means the single lock didn't cut it. Maybe the frame before it was an anti-shiny reroll? - - // Track if we ever require the CPU Trainer Shiny Value to be a value for a shiny skip. - // We need to un-set this flag if future frames don't pan out. - bool forcedOT = false; - - int start = 2; - while (true) + else if (RCSV != NOT_FORCED) // CPU shiny value is required for a previous lock { - var upper = Cache[start + 1]; - var lower = Cache[start]; - // uint cid = upper << 16 | lower; + if (sv == RCSV) + { + // No CPU shiny value forced yet. Lets try to skip this lock by requiring the eventual OT to get this shiny. + RCSV = (int) sv; + forcedOT = true; + continue; // don't break + } + + if (forcedOT) // current call to this method had forced the OT; clear the forced OT before breaking. + RCSV = NOT_FORCED; + yield break; // Since we can't skip this interrupt, we're done. + } + // Yield the final rerolled pid instead of the bad anti-shiny (metadata/validation). + yield return new SeedFrame(pid, start + current.FramesConsumed); + start += 2; + } + } + + /// + /// Generates nodes until the traversal is ended by an interrupt frame that matches the . + /// + /// Starting frame for the traversal. + /// Current lock criteria to satisfy. Used to find valid results to yield. + /// Prior lock criteria. Used for determining when the traversal stops. + /// List of possible locks for the provided input. + /// + /// An "interrupt" signals the end of the traversal. + /// Any afterwards (when generated forward from the CPU Trainer) will use the interrupt rather than the previous that was found for the lock. + /// + private IEnumerable GetAllLocks(int ctr, NPCLock current, NPCLock prior) + { + // Since the prior(next) lock is generated 7+2*n frames after, the worst case break is 7 frames after the PID. + // Continue reversing until a sequential generation case is found. + int start = ctr; + + // Track if we ever require the CPU Trainer Shiny Value to be a value for a shiny skip. + // We need to un-set this flag if future frames don't pan out. + bool forcedOT = false; + + while (true) + { + int p7 = ctr - 7; + if (p7 > start) + { + // check for interrupting cpu team cases + var upper = Cache[p7 + 1]; + var lower = Cache[p7]; + uint cid = (upper << 16) | lower; var sv = (upper ^ lower) >> 3; if (sv == TSV) // XD shiny checks all opponent PKM, even non-shadow. { - // Anti-shiny rerolled! This is a possible frame. + // This interrupt is ignored! The result is shiny. } - else if (RCSV != NOT_FORCED) // CPU shiny value is required for a previous lock + else if (prior.MatchesLock(cid)) // lock matched cpu mon { - if (sv == RCSV) + if (RCSV != NOT_FORCED) // CPU shiny value is required for a previous lock { - // No CPU shiny value forced yet. Lets try to skip this lock by requiring the eventual OT to get this shiny. - RCSV = (int) sv; + if (sv != RCSV) + { + if (forcedOT) // current call to this method had forced the OT; clear the forced OT before breaking. + RCSV = NOT_FORCED; + yield break; // Since we can't skip this interrupt, we're done. + } + } + else // No CPU shiny value forced yet. Lets try to skip this lock by requiring the eventual OT to get this shiny. + { + RCSV = (int)sv; forcedOT = true; - continue; // don't break - } - - if (forcedOT) // current call to this method had forced the OT; clear the forced OT before breaking. - RCSV = NOT_FORCED; - yield break; // Since we can't skip this interrupt, we're done. - } - // Yield the final rerolled pid instead of the bad anti-shiny (metadata/validation). - yield return new SeedFrame(pid, start + current.FramesConsumed); - start += 2; - } - } - - /// - /// Generates nodes until the traversal is ended by an interrupt frame that matches the . - /// - /// Starting frame for the traversal. - /// Current lock criteria to satisfy. Used to find valid results to yield. - /// Prior lock criteria. Used for determining when the traversal stops. - /// List of possible locks for the provided input. - /// - /// An "interrupt" signals the end of the traversal. - /// Any afterwards (when generated forward from the CPU Trainer) will use the interrupt rather than the previous that was found for the lock. - /// - private IEnumerable GetAllLocks(int ctr, NPCLock current, NPCLock prior) - { - // Since the prior(next) lock is generated 7+2*n frames after, the worst case break is 7 frames after the PID. - // Continue reversing until a sequential generation case is found. - int start = ctr; - - // Track if we ever require the CPU Trainer Shiny Value to be a value for a shiny skip. - // We need to un-set this flag if future frames don't pan out. - bool forcedOT = false; - - while (true) - { - int p7 = ctr - 7; - if (p7 > start) - { - // check for interrupting cpu team cases - var upper = Cache[p7 + 1]; - var lower = Cache[p7]; - uint cid = upper << 16 | lower; - var sv = (upper ^ lower) >> 3; - if (sv == TSV) // XD shiny checks all opponent PKM, even non-shadow. - { - // This interrupt is ignored! The result is shiny. - } - else if (prior.MatchesLock(cid)) // lock matched cpu mon - { - if (RCSV != NOT_FORCED) // CPU shiny value is required for a previous lock - { - if (sv != RCSV) - { - if (forcedOT) // current call to this method had forced the OT; clear the forced OT before breaking. - RCSV = NOT_FORCED; - yield break; // Since we can't skip this interrupt, we're done. - } - } - else // No CPU shiny value forced yet. Lets try to skip this lock by requiring the eventual OT to get this shiny. - { - RCSV = (int)sv; - forcedOT = true; - // don't break - } + // don't break } } - uint pid = Cache[ctr + 1] << 16 | Cache[ctr]; - if (current.MatchesLock(pid)) - yield return new SeedFrame(pid, ctr + current.FramesConsumed); - - ctr += 2; } - } + uint pid = (Cache[ctr + 1] << 16) | Cache[ctr]; + if (current.MatchesLock(pid)) + yield return new SeedFrame(pid, ctr + current.FramesConsumed); - /// - /// Checks to see if the generated is compatible with the CPU Trainer Data. - /// - /// Ending frame of the traversal. - /// True if the are valid. - private bool VerifyNPC(int ctr) - { - var TID = Cache[ctr + 1]; - var SID = Cache[ctr]; - var CPUSV = (TID ^ SID) >> 3; - if (RCSV != NOT_FORCED && RCSV != CPUSV) - return false; // required CPU Trainer's shiny value did not match the required value. - - int pos = Team.Count - 1; // stack can't do a for loop :( - foreach (var member in Team) - { - var pid = member.PID; - var psv = ((pid & 0xFFFF) ^ (pid >> 16)) >> 3; - - // check for shiny for Trainer -- XD only - // if (psv == TSV) // XD shiny checks all opponent PKM, even non-shadow. - // return false; // no shiny shadow mons - // we already checked this when re-generating the team - - // check for shiny for CPU - if (psv == CPUSV) - { - if (!Specifications.Locks[pos].Shadow) - return false; // no shiny CPU mons - if (TSV != NOT_FORCED) // XD - return false; // no shiny shadow mons - } - pos--; - } - - OriginFrame = ctr + 2; - return true; + ctr += 2; } } + + /// + /// Checks to see if the generated is compatible with the CPU Trainer Data. + /// + /// Ending frame of the traversal. + /// True if the are valid. + private bool VerifyNPC(int ctr) + { + var TID = Cache[ctr + 1]; + var SID = Cache[ctr]; + var CPUSV = (TID ^ SID) >> 3; + if (RCSV != NOT_FORCED && RCSV != CPUSV) + return false; // required CPU Trainer's shiny value did not match the required value. + + int pos = Team.Count - 1; // stack can't do a for loop :( + foreach (var member in Team) + { + var pid = member.PID; + var psv = ((pid & 0xFFFF) ^ (pid >> 16)) >> 3; + + // check for shiny for Trainer -- XD only + // if (psv == TSV) // XD shiny checks all opponent PKM, even non-shadow. + // return false; // no shiny shadow mons + // we already checked this when re-generating the team + + // check for shiny for CPU + if (psv == CPUSV) + { + if (!Specifications.Locks[pos].Shadow) + return false; // no shiny CPU mons + if (TSV != NOT_FORCED) // XD + return false; // no shiny shadow mons + } + pos--; + } + + OriginFrame = ctr + 2; + return true; + } } diff --git a/PKHeX.Core/Legality/RNG/MethodFinder.cs b/PKHeX.Core/Legality/RNG/MethodFinder.cs index a66c8c776..5d0efcc77 100644 --- a/PKHeX.Core/Legality/RNG/MethodFinder.cs +++ b/PKHeX.Core/Legality/RNG/MethodFinder.cs @@ -5,940 +5,939 @@ using System.Runtime.CompilerServices; using static PKHeX.Core.PIDType; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Class containing logic to obtain a PKM's PIDIV method. +/// +public static class MethodFinder { /// - /// Class containing logic to obtain a PKM's PIDIV method. + /// Analyzes a to find a matching PIDIV method. /// - public static class MethodFinder + /// Input . + /// object containing seed and method info. + public static PIDIV Analyze(PKM pk) { - /// - /// Analyzes a to find a matching PIDIV method. - /// - /// Input . - /// object containing seed and method info. - public static PIDIV Analyze(PKM pk) + if (pk.Format < 3) + return AnalyzeGB(pk); + var pid = pk.EncryptionConstant; + + var top = pid >> 16; + var bot = pid & 0xFFFF; + + Span temp = stackalloc uint[6]; + for (int i = 0; i < 6; i++) + temp[i] = (uint)pk.GetIV(i); + ReadOnlySpan IVs = temp; + + if (GetLCRNGMatch(top, bot, IVs, out PIDIV pidiv)) + return pidiv; + if (pk.Species == (int)Species.Unown && GetLCRNGUnownMatch(top, bot, IVs, out pidiv)) // frlg only + return pidiv; + if (GetColoStarterMatch(pk, top, bot, IVs, out pidiv)) + return pidiv; + if (GetXDRNGMatch(pk, top, bot, IVs, out pidiv)) + return pidiv; + + // Special cases + if (GetLCRNGRoamerMatch(top, bot, IVs, out pidiv)) + return pidiv; + if (GetChannelMatch(top, bot, IVs, out pidiv, pk)) + return pidiv; + if (GetMG4Match(pid, IVs, out pidiv)) + return pidiv; + + if (GetBACDMatch(pk, pid, IVs, out pidiv)) + return pidiv; + if (GetModifiedPIDMatch(pk, pid, IVs, out pidiv)) + return pidiv; + + return PIDIV.None; // no match + } + + private static bool GetModifiedPIDMatch(PKM pk, uint pid, ReadOnlySpan IVs, out PIDIV pidiv) + { + if (pk.IsShiny) { - if (pk.Format < 3) - return AnalyzeGB(pk); - var pid = pk.EncryptionConstant; - - var top = pid >> 16; - var bot = pid & 0xFFFF; - - Span temp = stackalloc uint[6]; - for (int i = 0; i < 6; i++) - temp[i] = (uint)pk.GetIV(i); - ReadOnlySpan IVs = temp; - - if (GetLCRNGMatch(top, bot, IVs, out PIDIV pidiv)) - return pidiv; - if (pk.Species == (int)Species.Unown && GetLCRNGUnownMatch(top, bot, IVs, out pidiv)) // frlg only - return pidiv; - if (GetColoStarterMatch(pk, top, bot, IVs, out pidiv)) - return pidiv; - if (GetXDRNGMatch(pk, top, bot, IVs, out pidiv)) - return pidiv; - - // Special cases - if (GetLCRNGRoamerMatch(top, bot, IVs, out pidiv)) - return pidiv; - if (GetChannelMatch(top, bot, IVs, out pidiv, pk)) - return pidiv; - if (GetMG4Match(pid, IVs, out pidiv)) - return pidiv; - - if (GetBACDMatch(pk, pid, IVs, out pidiv)) - return pidiv; - if (GetModifiedPIDMatch(pk, pid, IVs, out pidiv)) - return pidiv; - - return PIDIV.None; // no match + if (GetChainShinyMatch(pk, pid, IVs, out pidiv)) + return true; + if (GetModified8BitMatch(pk, pid, out pidiv)) + return true; + } + else + { + if (pid <= 0xFF && GetCuteCharmMatch(pk, pid, out pidiv)) + return true; } - private static bool GetModifiedPIDMatch(PKM pk, uint pid, ReadOnlySpan IVs, out PIDIV pidiv) + return GetPokewalkerMatch(pk, pid, out pidiv); + } + + private static bool GetModified8BitMatch(PKM pk, uint pid, out PIDIV pidiv) + { + return pk.Gen4 + ? (pid <= 0xFF && GetCuteCharmMatch(pk, pid, out pidiv)) || GetG5MGShinyMatch(pk, pid, out pidiv) + : GetG5MGShinyMatch(pk, pid, out pidiv) || (pid <= 0xFF && GetCuteCharmMatch(pk, pid, out pidiv)); + } + + private static bool GetLCRNGMatch(uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv) + { + var reg = GetSeedsFromPID(RNG.LCRNG, top, bot); + var iv1 = GetIVChunk(IVs, 0); + var iv2 = GetIVChunk(IVs, 3); + foreach (var seed in reg) { - if (pk.IsShiny) + // A and B are already used by PID + var B = RNG.LCRNG.Advance(seed, 2); + + // Method 1/2/4 can use 3 different RNG frames + var C = RNG.LCRNG.Next(B); + var ivC = C >> 16 & 0x7FFF; + if (iv1 == ivC) { - if (GetChainShinyMatch(pk, pid, IVs, out pidiv)) + var D = RNG.LCRNG.Next(C); + var ivD = D >> 16 & 0x7FFF; + if (iv2 == ivD) // ABCD + { + pidiv = new PIDIV(Method_1, seed); return true; - if (GetModified8BitMatch(pk, pid, out pidiv)) + } + + var E = RNG.LCRNG.Next(D); + var ivE = E >> 16 & 0x7FFF; + if (iv2 == ivE) // ABCE + { + pidiv = new PIDIV(Method_4, seed); return true; + } } else { - if (pid <= 0xFF && GetCuteCharmMatch(pk, pid, out pidiv)) + var D = RNG.LCRNG.Next(C); + var ivD = D >> 16 & 0x7FFF; + if (iv1 != ivD) + continue; + + var E = RNG.LCRNG.Next(D); + var ivE = E >> 16 & 0x7FFF; + if (iv2 == ivE) // ABDE + { + pidiv = new PIDIV(Method_2, seed); return true; - } - - return GetPokewalkerMatch(pk, pid, out pidiv); - } - - private static bool GetModified8BitMatch(PKM pk, uint pid, out PIDIV pidiv) - { - return pk.Gen4 - ? (pid <= 0xFF && GetCuteCharmMatch(pk, pid, out pidiv)) || GetG5MGShinyMatch(pk, pid, out pidiv) - : GetG5MGShinyMatch(pk, pid, out pidiv) || (pid <= 0xFF && GetCuteCharmMatch(pk, pid, out pidiv)); - } - - private static bool GetLCRNGMatch(uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv) - { - var reg = GetSeedsFromPID(RNG.LCRNG, top, bot); - var iv1 = GetIVChunk(IVs, 0); - var iv2 = GetIVChunk(IVs, 3); - foreach (var seed in reg) - { - // A and B are already used by PID - var B = RNG.LCRNG.Advance(seed, 2); - - // Method 1/2/4 can use 3 different RNG frames - var C = RNG.LCRNG.Next(B); - var ivC = C >> 16 & 0x7FFF; - if (iv1 == ivC) - { - var D = RNG.LCRNG.Next(C); - var ivD = D >> 16 & 0x7FFF; - if (iv2 == ivD) // ABCD - { - pidiv = new PIDIV(Method_1, seed); - return true; - } - - var E = RNG.LCRNG.Next(D); - var ivE = E >> 16 & 0x7FFF; - if (iv2 == ivE) // ABCE - { - pidiv = new PIDIV(Method_4, seed); - return true; - } - } - else - { - var D = RNG.LCRNG.Next(C); - var ivD = D >> 16 & 0x7FFF; - if (iv1 != ivD) - continue; - - var E = RNG.LCRNG.Next(D); - var ivE = E >> 16 & 0x7FFF; - if (iv2 == ivE) // ABDE - { - pidiv = new PIDIV(Method_2, seed); - return true; - } } } - reg = GetSeedsFromPIDSkip(RNG.LCRNG, top, bot); - foreach (var seed in reg) - { - // A and B are already used by PID - var C = RNG.LCRNG.Advance(seed, 3); + } + reg = GetSeedsFromPIDSkip(RNG.LCRNG, top, bot); + foreach (var seed in reg) + { + // A and B are already used by PID + var C = RNG.LCRNG.Advance(seed, 3); - // Method 3 + // Method 3 + var D = RNG.LCRNG.Next(C); + var ivD = D >> 16 & 0x7FFF; + if (iv1 != ivD) + continue; + var E = RNG.LCRNG.Next(D); + var ivE = E >> 16 & 0x7FFF; + if (iv2 != ivE) + continue; + pidiv = new PIDIV(Method_3, seed); + return true; + } + return GetNonMatch(out pidiv); + } + + private static bool GetLCRNGUnownMatch(uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv) + { + // this is an exact copy of LCRNG 1,2,4 matching, except the PID has its halves switched (BACD, BADE, BACE) + var reg = GetSeedsFromPID(RNG.LCRNG, bot, top); // reversed! + var iv1 = GetIVChunk(IVs, 0); + var iv2 = GetIVChunk(IVs, 3); + foreach (var seed in reg) + { + // A and B are already used by PID + var B = RNG.LCRNG.Advance(seed, 2); + + // Method 1/2/4 can use 3 different RNG frames + var C = RNG.LCRNG.Next(B); + var ivC = C >> 16 & 0x7FFF; + if (iv1 == ivC) + { + var D = RNG.LCRNG.Next(C); + var ivD = D >> 16 & 0x7FFF; + if (iv2 == ivD) // BACD + { + pidiv = new PIDIV(Method_1_Unown, seed); + return true; + } + + var E = RNG.LCRNG.Next(D); + var ivE = E >> 16 & 0x7FFF; + if (iv2 == ivE) // BACE + { + pidiv = new PIDIV(Method_4_Unown, seed); + return true; + } + } + else + { var D = RNG.LCRNG.Next(C); var ivD = D >> 16 & 0x7FFF; if (iv1 != ivD) continue; + var E = RNG.LCRNG.Next(D); var ivE = E >> 16 & 0x7FFF; - if (iv2 != ivE) - continue; - pidiv = new PIDIV(Method_3, seed); - return true; - } - return GetNonMatch(out pidiv); - } - - private static bool GetLCRNGUnownMatch(uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv) - { - // this is an exact copy of LCRNG 1,2,4 matching, except the PID has its halves switched (BACD, BADE, BACE) - var reg = GetSeedsFromPID(RNG.LCRNG, bot, top); // reversed! - var iv1 = GetIVChunk(IVs, 0); - var iv2 = GetIVChunk(IVs, 3); - foreach (var seed in reg) - { - // A and B are already used by PID - var B = RNG.LCRNG.Advance(seed, 2); - - // Method 1/2/4 can use 3 different RNG frames - var C = RNG.LCRNG.Next(B); - var ivC = C >> 16 & 0x7FFF; - if (iv1 == ivC) + if (iv2 == ivE) // BADE { - var D = RNG.LCRNG.Next(C); - var ivD = D >> 16 & 0x7FFF; - if (iv2 == ivD) // BACD - { - pidiv = new PIDIV(Method_1_Unown, seed); - return true; - } - - var E = RNG.LCRNG.Next(D); - var ivE = E >> 16 & 0x7FFF; - if (iv2 == ivE) // BACE - { - pidiv = new PIDIV(Method_4_Unown, seed); - return true; - } - } - else - { - var D = RNG.LCRNG.Next(C); - var ivD = D >> 16 & 0x7FFF; - if (iv1 != ivD) - continue; - - var E = RNG.LCRNG.Next(D); - var ivE = E >> 16 & 0x7FFF; - if (iv2 == ivE) // BADE - { - pidiv = new PIDIV(Method_2_Unown, seed); - return true; - } + pidiv = new PIDIV(Method_2_Unown, seed); + return true; } } - reg = GetSeedsFromPIDSkip(RNG.LCRNG, bot, top); // reversed! - foreach (var seed in reg) - { - // A and B are already used by PID - var C = RNG.LCRNG.Advance(seed, 3); + } + reg = GetSeedsFromPIDSkip(RNG.LCRNG, bot, top); // reversed! + foreach (var seed in reg) + { + // A and B are already used by PID + var C = RNG.LCRNG.Advance(seed, 3); - // Method 3 - var D = RNG.LCRNG.Next(C); - var ivD = D >> 16 & 0x7FFF; - if (iv1 != ivD) - continue; - var E = RNG.LCRNG.Next(D); - var ivE = E >> 16 & 0x7FFF; - if (iv2 != ivE) - continue; - pidiv = new PIDIV(Method_3_Unown, seed); + // Method 3 + var D = RNG.LCRNG.Next(C); + var ivD = D >> 16 & 0x7FFF; + if (iv1 != ivD) + continue; + var E = RNG.LCRNG.Next(D); + var ivE = E >> 16 & 0x7FFF; + if (iv2 != ivE) + continue; + pidiv = new PIDIV(Method_3_Unown, seed); + return true; + } + return GetNonMatch(out pidiv); + } + + private static bool GetLCRNGRoamerMatch(uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv) + { + if (IVs[2] != 0 || IVs[3] != 0 || IVs[4] != 0 || IVs[5] != 0 || IVs[1] > 7) + return GetNonMatch(out pidiv); + var iv1 = GetIVChunk(IVs, 0); + var reg = GetSeedsFromPID(RNG.LCRNG, top, bot); + foreach (var seed in reg) + { + // Only the first 8 bits are kept + var ivC = RNG.LCRNG.Advance(seed, 3) >> 16 & 0x00FF; + if (iv1 != ivC) + continue; + + pidiv = new PIDIV(Method_1_Roamer, seed); + return true; + } + return GetNonMatch(out pidiv); + } + + private static bool GetXDRNGMatch(PKM pk, uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv) + { + var xdc = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); + foreach (var seed in xdc) + { + var B = RNG.XDRNG.Prev(seed); + var A = RNG.XDRNG.Prev(B); + + var hi = A >> 16; + var lo = B >> 16; + if (IVsMatch(hi, lo, IVs)) + { + pidiv = new PIDIV(CXD, RNG.XDRNG.Prev(A)); return true; } - return GetNonMatch(out pidiv); - } - private static bool GetLCRNGRoamerMatch(uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv) - { - if (IVs[2] != 0 || IVs[3] != 0 || IVs[4] != 0 || IVs[5] != 0 || IVs[1] > 7) - return GetNonMatch(out pidiv); - var iv1 = GetIVChunk(IVs, 0); - var reg = GetSeedsFromPID(RNG.LCRNG, top, bot); - foreach (var seed in reg) + // check for anti-shiny against player TSV + var tsv = (uint)(pk.TID ^ pk.SID) >> 3; + var psv = (top ^ bot) >> 3; + if (psv == tsv) // already shiny, wouldn't be anti-shiny + continue; + + var p2 = seed; + var p1 = B; + psv = ((p2 ^ p1) >> 19); + if (psv != tsv) // prior PID must be shiny + continue; + + do { - // Only the first 8 bits are kept - var ivC = RNG.LCRNG.Advance(seed, 3) >> 16 & 0x00FF; - if (iv1 != ivC) - continue; - - pidiv = new PIDIV(Method_1_Roamer, seed); - return true; - } - return GetNonMatch(out pidiv); - } - - private static bool GetXDRNGMatch(PKM pk, uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv) - { - var xdc = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); - foreach (var seed in xdc) - { - var B = RNG.XDRNG.Prev(seed); - var A = RNG.XDRNG.Prev(B); - - var hi = A >> 16; - var lo = B >> 16; + B = RNG.XDRNG.Prev(A); + A = RNG.XDRNG.Prev(B); + hi = A >> 16; + lo = B >> 16; if (IVsMatch(hi, lo, IVs)) { - pidiv = new PIDIV(CXD, RNG.XDRNG.Prev(A)); + pidiv = new PIDIV(CXDAnti, RNG.XDRNG.Prev(A)); return true; } - // check for anti-shiny against player TSV - var tsv = (uint)(pk.TID ^ pk.SID) >> 3; - var psv = (top ^ bot) >> 3; - if (psv == tsv) // already shiny, wouldn't be anti-shiny - continue; - - var p2 = seed; - var p1 = B; - psv = ((p2 ^ p1) >> 19); - if (psv != tsv) // prior PID must be shiny - continue; - - do - { - B = RNG.XDRNG.Prev(A); - A = RNG.XDRNG.Prev(B); - hi = A >> 16; - lo = B >> 16; - if (IVsMatch(hi, lo, IVs)) - { - pidiv = new PIDIV(CXDAnti, RNG.XDRNG.Prev(A)); - return true; - } - - p2 = RNG.XDRNG.Prev(p1); - p1 = RNG.XDRNG.Prev(p2); - psv = (p2 ^ p1) >> 19; - } - while (psv == tsv); + p2 = RNG.XDRNG.Prev(p1); + p1 = RNG.XDRNG.Prev(p2); + psv = (p2 ^ p1) >> 19; } - return GetNonMatch(out pidiv); + while (psv == tsv); } + return GetNonMatch(out pidiv); + } - private static bool GetChannelMatch(uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv, PKM pk) + private static bool GetChannelMatch(uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv, PKM pk) + { + var ver = pk.Version; + if (ver is not ((int)GameVersion.R or (int)GameVersion.S)) + return GetNonMatch(out pidiv); + + var undo = top ^ 0x8000; + if ((undo > 7 ? 0 : 1) != (bot ^ pk.SID ^ 40122)) + top = undo; + var channel = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); + foreach (var seed in channel) { - var ver = pk.Version; - if (ver is not ((int)GameVersion.R or (int)GameVersion.S)) - return GetNonMatch(out pidiv); + var C = RNG.XDRNG.Advance(seed, 3); // held item + // no checks, held item can be swapped - var undo = top ^ 0x8000; - if ((undo > 7 ? 0 : 1) != (bot ^ pk.SID ^ 40122)) - top = undo; - var channel = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); - foreach (var seed in channel) + var D = RNG.XDRNG.Next(C); // Version + if ((D >> 31) + 1 != ver) // (0-Sapphire, 1-Ruby) + continue; + + var E = RNG.XDRNG.Next(D); // OT Gender + if (E >> 31 != pk.OT_Gender) + continue; + + if (!RNG.XDRNG.GetSequentialIVsUInt32(E, IVs)) + continue; + + if (seed >> 16 != pk.SID) + continue; + + pidiv = new PIDIV(Channel, RNG.XDRNG.Prev(seed)); + return true; + } + return GetNonMatch(out pidiv); + } + + private static bool GetMG4Match(uint pid, ReadOnlySpan IVs, out PIDIV pidiv) + { + uint mg4Rev = RNG.ARNG.Prev(pid); + var mg4 = GetSeedsFromPID(RNG.LCRNG, mg4Rev >> 16, mg4Rev & 0xFFFF); + foreach (var seed in mg4) + { + var B = RNG.LCRNG.Advance(seed, 2); + var C = RNG.LCRNG.Next(B); + var D = RNG.LCRNG.Next(C); + if (!IVsMatch(C >> 16, D >> 16, IVs)) + continue; + + pidiv = new PIDIV(G4MGAntiShiny, seed); + return true; + } + return GetNonMatch(out pidiv); + } + + private static bool GetG5MGShinyMatch(PKM pk, uint pid, out PIDIV pidiv) + { + var low = pid & 0xFFFF; + // generation 5 shiny PIDs + if (low <= 0xFF) + { + var av = (pid >> 16) & 1; + var genPID = PIDGenerator.GetMG5ShinyPID(low, av, pk.TID, pk.SID); + if (genPID == pid) { - var C = RNG.XDRNG.Advance(seed, 3); // held item - // no checks, held item can be swapped - - var D = RNG.XDRNG.Next(C); // Version - if ((D >> 31) + 1 != ver) // (0-Sapphire, 1-Ruby) - continue; - - var E = RNG.XDRNG.Next(D); // OT Gender - if (E >> 31 != pk.OT_Gender) - continue; - - if (!RNG.XDRNG.GetSequentialIVsUInt32(E, IVs)) - continue; - - if (seed >> 16 != pk.SID) - continue; - - pidiv = new PIDIV(Channel, RNG.XDRNG.Prev(seed)); + pidiv = PIDIV.G5MGShiny; return true; } - return GetNonMatch(out pidiv); } + return GetNonMatch(out pidiv); + } - private static bool GetMG4Match(uint pid, ReadOnlySpan IVs, out PIDIV pidiv) + private static bool GetCuteCharmMatch(PKM pk, uint pid, out PIDIV pidiv) + { + if (pid > 0xFF) + return GetNonMatch(out pidiv); + + (int species, int genderValue) = GetCuteCharmGenderSpecies(pk, pid, pk.Species); + int getRatio() => PersonalTable.HGSS[species].Gender; + switch (genderValue) { - uint mg4Rev = RNG.ARNG.Prev(pid); - var mg4 = GetSeedsFromPID(RNG.LCRNG, mg4Rev >> 16, mg4Rev & 0xFFFF); - foreach (var seed in mg4) - { - var B = RNG.LCRNG.Advance(seed, 2); - var C = RNG.LCRNG.Next(B); - var D = RNG.LCRNG.Next(C); - if (!IVsMatch(C >> 16, D >> 16, IVs)) - continue; + case 2: break; // can't cute charm a genderless pk + case 0: // male + var gr = getRatio(); + if (gr >= PersonalInfo.RatioMagicFemale) // no modification for PID + break; + var rate = 25*((gr / 25) + 1); // buffered + var nature = pid % 25; + if (nature + rate != pid) + break; - pidiv = new PIDIV(G4MGAntiShiny, seed); + pidiv = PIDIV.CuteCharm; return true; - } - return GetNonMatch(out pidiv); - } + case 1: // female + if (pid >= 25) + break; // nope, this isn't a valid nature + if (getRatio() >= PersonalInfo.RatioMagicFemale) // no modification for PID + break; - private static bool GetG5MGShinyMatch(PKM pk, uint pid, out PIDIV pidiv) - { - var low = pid & 0xFFFF; - // generation 5 shiny PIDs - if (low <= 0xFF) - { - var av = (pid >> 16) & 1; - var genPID = PIDGenerator.GetMG5ShinyPID(low, av, pk.TID, pk.SID); - if (genPID == pid) - { - pidiv = PIDIV.G5MGShiny; - return true; - } - } - return GetNonMatch(out pidiv); - } - - private static bool GetCuteCharmMatch(PKM pk, uint pid, out PIDIV pidiv) - { - if (pid > 0xFF) - return GetNonMatch(out pidiv); - - (int species, int genderValue) = GetCuteCharmGenderSpecies(pk, pid, pk.Species); - int getRatio() => PersonalTable.HGSS[species].Gender; - switch (genderValue) - { - case 2: break; // can't cute charm a genderless pkm - case 0: // male - var gr = getRatio(); - if (gr >= PersonalInfo.RatioMagicFemale) // no modification for PID - break; - var rate = 25*((gr / 25) + 1); // buffered - var nature = pid % 25; - if (nature + rate != pid) - break; - - pidiv = PIDIV.CuteCharm; - return true; - case 1: // female - if (pid >= 25) - break; // nope, this isn't a valid nature - if (getRatio() >= PersonalInfo.RatioMagicFemale) // no modification for PID - break; - - pidiv = PIDIV.CuteCharm; - return true; - } - return GetNonMatch(out pidiv); - } - - private static bool GetChainShinyMatch(PKM pk, uint pid, ReadOnlySpan IVs, out PIDIV pidiv) - { - // 13 shiny bits - // PIDH & 7 - // PIDL & 7 - // IVs - var bot = GetIVChunk(IVs, 0); - var top = GetIVChunk(IVs, 3); - var reg = GetSeedsFromIVs(RNG.LCRNG, top, bot); - foreach (var seed in reg) - { - // check the individual bits - var s = seed; - int i = 15; - do - { - var bit = s >> 16 & 1; - if (bit != (pid >> i & 1)) - break; - s = RNG.LCRNG.Prev(s); - } - while (--i != 2); - if (i != 2) // bit failed - continue; - // Shiny Bits of PID validated - var upper = s; - if ((upper >> 16 & 7) != (pid >> 16 & 7)) - continue; - var lower = RNG.LCRNG.Prev(upper); - if ((lower >> 16 & 7) != (pid & 7)) - continue; - - var upid = (((pid & 0xFFFF) ^ pk.TID ^ pk.SID) & 0xFFF8) | ((upper >> 16) & 0x7); - if (upid != pid >> 16) - continue; - - s = RNG.LCRNG.Reverse(lower, 2); // unroll one final time to get the origin seed - pidiv = new PIDIV(ChainShiny, s); + pidiv = PIDIV.CuteCharm; return true; - } - return GetNonMatch(out pidiv); } + return GetNonMatch(out pidiv); + } - public static IEnumerable GetCuteCharmSeeds(PKM pk) + private static bool GetChainShinyMatch(PKM pk, uint pid, ReadOnlySpan IVs, out PIDIV pidiv) + { + // 13 shiny bits + // PIDH & 7 + // PIDL & 7 + // IVs + var bot = GetIVChunk(IVs, 0); + var top = GetIVChunk(IVs, 3); + var reg = GetSeedsFromIVs(RNG.LCRNG, top, bot); + foreach (var seed in reg) { - Span IVs = stackalloc uint[6]; - for (int i = 0; i < 6; i++) - IVs[i] = (uint)pk.GetIV(i); - var bot = GetIVChunk(IVs, 0); - var top = GetIVChunk(IVs, 3); - - return GetSeedsFromIVs(RNG.LCRNG, top, bot); - } - - private static bool GetBACDMatch(PKM pk, uint pid, ReadOnlySpan IVs, out PIDIV pidiv) - { - var bot = GetIVChunk(IVs, 0); - var top = GetIVChunk(IVs, 3); - var reg = GetSeedsFromIVs(RNG.LCRNG, top, bot); - PIDType type = BACD_U; - foreach (var seed in reg) + // check the individual bits + var s = seed; + int i = 15; + do { - var B = seed; - var A = RNG.LCRNG.Prev(B); - var low = B >> 16; + var bit = s >> 16 & 1; + if (bit != (pid >> i & 1)) + break; + s = RNG.LCRNG.Prev(s); + } + while (--i != 2); + if (i != 2) // bit failed + continue; + // Shiny Bits of PID validated + var upper = s; + if ((upper >> 16 & 7) != (pid >> 16 & 7)) + continue; + var lower = RNG.LCRNG.Prev(upper); + if ((lower >> 16 & 7) != (pid & 7)) + continue; - var PID = (A & 0xFFFF0000) | low; - if (PID != pid) + var upid = (((pid & 0xFFFF) ^ pk.TID ^ pk.SID) & 0xFFF8) | ((upper >> 16) & 0x7); + if (upid != pid >> 16) + continue; + + s = RNG.LCRNG.Reverse(lower, 2); // unroll one final time to get the origin seed + pidiv = new PIDIV(ChainShiny, s); + return true; + } + return GetNonMatch(out pidiv); + } + + public static IEnumerable GetCuteCharmSeeds(PKM pk) + { + Span IVs = stackalloc uint[6]; + for (int i = 0; i < 6; i++) + IVs[i] = (uint)pk.GetIV(i); + var bot = GetIVChunk(IVs, 0); + var top = GetIVChunk(IVs, 3); + + return GetSeedsFromIVs(RNG.LCRNG, top, bot); + } + + private static bool GetBACDMatch(PKM pk, uint pid, ReadOnlySpan IVs, out PIDIV pidiv) + { + var bot = GetIVChunk(IVs, 0); + var top = GetIVChunk(IVs, 3); + var reg = GetSeedsFromIVs(RNG.LCRNG, top, bot); + PIDType type = BACD_U; + foreach (var seed in reg) + { + var B = seed; + var A = RNG.LCRNG.Prev(B); + var low = B >> 16; + + var PID = (A & 0xFFFF0000) | low; + if (PID != pid) + { + uint idxor = (uint)(pk.TID ^ pk.SID); + bool isShiny = (idxor ^ PID >> 16 ^ (PID & 0xFFFF)) < 8; + if (!isShiny) { - uint idxor = (uint)(pk.TID ^ pk.SID); - bool isShiny = (idxor ^ PID >> 16 ^ (PID & 0xFFFF)) < 8; - if (!isShiny) + if (!pk.IsShiny) // check for nyx antishiny { - if (!pk.IsShiny) // check for nyx antishiny - { - if (!IsBACD_U_AX(idxor, pid, low, A, ref type)) - continue; - } - else // check for force shiny pkm - { - if (!IsBACD_U_S(idxor, pid, low, ref A, ref type)) - continue; - } - } - else if (!IsBACD_U_AX(idxor, pid, low, A, ref type)) - { - if ((PID + 8 & 0xFFFFFFF8) != pid) + if (!IsBACD_U_AX(idxor, pid, low, A, ref type)) + continue; + } + else // check for force shiny pk + { + if (!IsBACD_U_S(idxor, pid, low, ref A, ref type)) continue; - type = BACD_U_A; } } - var s = RNG.LCRNG.Prev(A); - - // Check for prior Restricted seed - var sn = s; - for (int i = 0; i < 3; i++, sn = RNG.LCRNG.Prev(sn)) + else if (!IsBACD_U_AX(idxor, pid, low, A, ref type)) { - if ((sn & 0xFFFF0000) != 0) + if ((PID + 8 & 0xFFFFFFF8) != pid) continue; - // shift from unrestricted enum val to restricted enum val - pidiv = new PIDIV(--type, sn); - return true; + type = BACD_U_A; } - // no restricted seed found, thus unrestricted - pidiv = new PIDIV(type, s); + } + var s = RNG.LCRNG.Prev(A); + + // Check for prior Restricted seed + var sn = s; + for (int i = 0; i < 3; i++, sn = RNG.LCRNG.Prev(sn)) + { + if ((sn & 0xFFFF0000) != 0) + continue; + // shift from unrestricted enum val to restricted enum val + pidiv = new PIDIV(--type, sn); return true; } - return GetNonMatch(out pidiv); - } - - private static bool GetPokewalkerMatch(PKM pk, uint oldpid, out PIDIV pidiv) - { - // check surface compatibility - var mid = oldpid & 0x00FFFF00; - if (mid != 0 && mid != 0x00FFFF00) // not expected bits - return GetNonMatch(out pidiv); - var nature = oldpid % 25; - if (nature == 24) // impossible nature - return GetNonMatch(out pidiv); - - var gender = pk.Gender; - uint pid = PIDGenerator.GetPokeWalkerPID(pk.TID, pk.SID, nature, gender, pk.PersonalInfo.Gender); - - if (pid != oldpid) - { - if (!(gender == 0 && IsAzurillEdgeCaseM(pk, nature, oldpid))) - return GetNonMatch(out pidiv); - } - pidiv = PIDIV.Pokewalker; + // no restricted seed found, thus unrestricted + pidiv = new PIDIV(type, s); return true; } + return GetNonMatch(out pidiv); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsAzurillEdgeCaseM(PKM pk, uint nature, uint oldpid) - { - // check for Azurill evolution edge case... 75% F-M is now 50% F-M; was this a F->M bend? - int species = pk.Species; - if (species is not ((int)Species.Marill or (int)Species.Azumarill)) - return false; - - const int AzurillGenderRatio = 0xBF; - var gender = EntityGender.GetFromPIDAndRatio(pk.PID, AzurillGenderRatio); - if (gender != 1) - return false; - - var pid = PIDGenerator.GetPokeWalkerPID(pk.TID, pk.SID, nature, 1, AzurillGenderRatio); - return pid == oldpid; - } - - private static bool GetColoStarterMatch(PKM pk, uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv) - { - if (pk.Version != (int)GameVersion.CXD || pk.Species is not ((int)Species.Espeon or (int)Species.Umbreon)) - return GetNonMatch(out pidiv); - - var iv1 = GetIVChunk(IVs, 0); - var iv2 = GetIVChunk(IVs, 3); - var xdc = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); - foreach (var seed in xdc) - { - uint origin = seed; - if (!LockFinder.IsColoStarterValid(pk.Species, ref origin, pk.TID, pk.SID, pk.PID, iv1, iv2)) - continue; - - pidiv = new PIDIV(CXD_ColoStarter, origin); - return true; - } + private static bool GetPokewalkerMatch(PKM pk, uint oldpid, out PIDIV pidiv) + { + // check surface compatibility + var mid = oldpid & 0x00FFFF00; + if (mid != 0 && mid != 0x00FFFF00) // not expected bits + return GetNonMatch(out pidiv); + var nature = oldpid % 25; + if (nature == 24) // impossible nature return GetNonMatch(out pidiv); - } - /// - /// Returns false and no . - /// - /// Null - /// False - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetNonMatch(out PIDIV pidiv) + var gender = pk.Gender; + uint pid = PIDGenerator.GetPokeWalkerPID(pk.TID, pk.SID, nature, gender, pk.PersonalInfo.Gender); + + if (pid != oldpid) { - pidiv = PIDIV.None; + if (!(gender == 0 && IsAzurillEdgeCaseM(pk, nature, oldpid))) + return GetNonMatch(out pidiv); + } + pidiv = PIDIV.Pokewalker; + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsAzurillEdgeCaseM(PKM pk, uint nature, uint oldpid) + { + // check for Azurill evolution edge case... 75% F-M is now 50% F-M; was this a F->M bend? + int species = pk.Species; + if (species is not ((int)Species.Marill or (int)Species.Azumarill)) return false; - } - /// - /// Checks if the PID is a match. - /// - /// ^ - /// Full actual PID - /// Low portion of PID (B) - /// First RNG call - /// PID Type is updated if successful - /// True/False if the PID matches - /// First RNG call is unrolled once if the PID is valid with this correlation - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsBACD_U_S(uint idxor, uint pid, uint low, ref uint A, ref PIDType type) + const int AzurillGenderRatio = 0xBF; + var gender = EntityGender.GetFromPIDAndRatio(pk.PID, AzurillGenderRatio); + if (gender != 1) + return false; + + var pid = PIDGenerator.GetPokeWalkerPID(pk.TID, pk.SID, nature, 1, AzurillGenderRatio); + return pid == oldpid; + } + + private static bool GetColoStarterMatch(PKM pk, uint top, uint bot, ReadOnlySpan IVs, out PIDIV pidiv) + { + if (pk.Version != (int)GameVersion.CXD || pk.Species is not ((int)Species.Espeon or (int)Species.Umbreon)) + return GetNonMatch(out pidiv); + + var iv1 = GetIVChunk(IVs, 0); + var iv2 = GetIVChunk(IVs, 3); + var xdc = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); + foreach (var seed in xdc) { - // 0-Origin - // 1-PIDH - // 2-PIDL (ends up unused) - // 3-FORCEBITS - // PID = PIDH << 16 | (SID ^ TID ^ PIDH) + uint origin = seed; + if (!LockFinder.IsColoStarterValid(pk.Species, ref origin, pk.TID, pk.SID, pk.PID, iv1, iv2)) + continue; - var X = RNG.LCRNG.Prev(A); // unroll once as there's 3 calls instead of 2 - uint PID = (X & 0xFFFF0000) | (idxor ^ X >> 16); - PID &= 0xFFFFFFF8; - PID |= low & 0x7; // lowest 3 bits - - if (PID != pid) - return false; - A = X; // keep the unrolled seed - type = BACD_U_S; + pidiv = new PIDIV(CXD_ColoStarter, origin); return true; } + return GetNonMatch(out pidiv); + } - /// - /// Checks if the PID is a match. - /// - /// ^ - /// Full actual PID - /// Low portion of PID (B) - /// First RNG call - /// PID Type is updated if successful - /// True/False if the PID matches - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsBACD_U_AX(uint idxor, uint pid, uint low, uint A, ref PIDType type) + /// + /// Returns false and no . + /// + /// Null + /// False + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetNonMatch(out PIDIV pidiv) + { + pidiv = PIDIV.None; + return false; + } + + /// + /// Checks if the PID is a match. + /// + /// ^ + /// Full actual PID + /// Low portion of PID (B) + /// First RNG call + /// PID Type is updated if successful + /// True/False if the PID matches + /// First RNG call is unrolled once if the PID is valid with this correlation + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsBACD_U_S(uint idxor, uint pid, uint low, ref uint A, ref PIDType type) + { + // 0-Origin + // 1-PIDH + // 2-PIDL (ends up unused) + // 3-FORCEBITS + // PID = PIDH << 16 | (SID ^ TID ^ PIDH) + + var X = RNG.LCRNG.Prev(A); // unroll once as there's 3 calls instead of 2 + uint PID = (X & 0xFFFF0000) | (idxor ^ X >> 16); + PID &= 0xFFFFFFF8; + PID |= low & 0x7; // lowest 3 bits + + if (PID != pid) + return false; + A = X; // keep the unrolled seed + type = BACD_U_S; + return true; + } + + /// + /// Checks if the PID is a match. + /// + /// ^ + /// Full actual PID + /// Low portion of PID (B) + /// First RNG call + /// PID Type is updated if successful + /// True/False if the PID matches + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsBACD_U_AX(uint idxor, uint pid, uint low, uint A, ref PIDType type) + { + if ((pid & 0xFFFF) != low) + return false; + + // 0-Origin + // 1-ushort rnd, do until >8 + // 2-PIDL + + uint rnd = A >> 16; + if (rnd < 8) + return false; + uint PID = ((rnd ^ idxor ^ low) << 16) | low; + if (PID != pid) + return false; + type = BACD_U_AX; + return true; + } + + private static PIDIV AnalyzeGB(PKM _) + { + // not implemented; correlation between IVs and RNG hasn't been converted to code. + return PIDIV.None; + } + + private static IEnumerable GetSeedsFromPID(RNG method, uint a, uint b) + { + Debug.Assert(a >> 16 == 0); + Debug.Assert(b >> 16 == 0); + uint second = a << 16; + uint first = b << 16; + return method.RecoverLower16Bits(first, second); + } + + private static IEnumerable GetSeedsFromPIDSkip(RNG method, uint a, uint b) + { + Debug.Assert(a >> 16 == 0); + Debug.Assert(b >> 16 == 0); + uint third = a << 16; + uint first = b << 16; + return method.RecoverLower16BitsGap(first, third); + } + + private static IEnumerable GetSeedsFromIVs(RNG method, uint a, uint b) + { + Debug.Assert(a >> 15 == 0); + Debug.Assert(b >> 15 == 0); + uint second = a << 16; + uint first = b << 16; + var pairs = method.RecoverLower16Bits(first, second) + .Concat(method.RecoverLower16Bits(first, second ^ 0x80000000)); + foreach (var z in pairs) { - if ((pid & 0xFFFF) != low) - return false; - - // 0-Origin - // 1-ushort rnd, do until >8 - // 2-PIDL - - uint rnd = A >> 16; - if (rnd < 8) - return false; - uint PID = ((rnd ^ idxor ^ low) << 16) | low; - if (PID != pid) - return false; - type = BACD_U_AX; - return true; + yield return z; + yield return z ^ 0x80000000; // sister bitflip } + } - private static PIDIV AnalyzeGB(PKM _) + public static IEnumerable GetSeedsFromIVsSkip(RNG method, uint rand1, uint rand3) + { + Debug.Assert(rand1 >> 15 == 0); + Debug.Assert(rand3 >> 15 == 0); + rand1 <<= 16; + rand3 <<= 16; + var seeds = method.RecoverLower16BitsGap(rand1, rand3) + .Concat(method.RecoverLower16BitsGap(rand1, rand3 ^ 0x80000000)); + foreach (var z in seeds) { - // not implemented; correlation between IVs and RNG hasn't been converted to code. - return PIDIV.None; + yield return z; + yield return z ^ 0x80000000; // sister bitflip } + } - private static IEnumerable GetSeedsFromPID(RNG method, uint a, uint b) + public static IEnumerable GetSeedsFromPIDEuclid(RNG method, uint rand1, uint rand2) + { + return method.RecoverLower16BitsEuclid16(rand1 << 16, rand2 << 16); + } + + public static IEnumerable GetSeedsFromIVsEuclid(RNG method, uint rand1, uint rand2) + { + return method.RecoverLower16BitsEuclid15(rand1 << 16, rand2 << 16); + } + + /// + /// Generates IVs from 2 RNG calls using 15 bits of each to generate 6 IVs (5bits each). + /// + /// First rand frame + /// Second rand frame + /// IVs that should be the result + /// IVs match random number IVs + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IVsMatch(uint r1, uint r2, ReadOnlySpan IVs) + { + if (IVs[0] != (r1 & 31)) + return false; + if (IVs[1] != (r1 >> 5 & 31)) + return false; + if (IVs[2] != (r1 >> 10 & 31)) + return false; + if (IVs[3] != (r2 & 31)) + return false; + if (IVs[4] != (r2 >> 5 & 31)) + return false; + if (IVs[5] != (r2 >> 10 & 31)) + return false; + return true; + } + + /// + /// Generates IVs from 2 RNG calls using 15 bits of each to generate 6 IVs (5bits each). + /// + /// Result storage + /// First rand frame + /// Second rand frame + /// Array of 6 IVs + internal static void GetIVsInt32(Span result, uint r1, uint r2) + { + result[5] = (int)r2 >> 10 & 31; + result[4] = (int)r2 >> 5 & 31; + result[3] = (int)r2 & 31; + result[2] = (int)r1 >> 10 & 31; + result[1] = (int)r1 >> 5 & 31; + result[0] = (int)r1 & 31; + } + + private static uint GetIVChunk(ReadOnlySpan IVs, int start) + { + uint val = 0; + for (int i = 0; i < 3; i++) + val |= IVs[i+start] << (5*i); + return val; + } + + public static IEnumerable GetColoEReaderMatches(uint PID) + { + var top = PID >> 16; + var bot = (ushort)PID; + var xdc = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); + foreach (var seed in xdc) { - Debug.Assert(a >> 16 == 0); - Debug.Assert(b >> 16 == 0); - uint second = a << 16; - uint first = b << 16; - return method.RecoverLower16Bits(first, second); + var B = RNG.XDRNG.Prev(seed); + var A = RNG.XDRNG.Prev(B); + + var C = RNG.XDRNG.Advance(A, 7); + + yield return new PIDIV(CXD, RNG.XDRNG.Prev(C)); } + } - private static IEnumerable GetSeedsFromPIDSkip(RNG method, uint a, uint b) + public static IEnumerable GetPokeSpotSeeds(PKM pk, int slot) + { + // Activate (rand % 3) + // Munchlax / Bonsly (10%/30%) + // Encounter Slot Value (ESV) = 50%/35%/15% rarity (0-49, 50-84, 85-99) + var pid = pk.PID; + var top = pid >> 16; + var bot = pid & 0xFFFF; + var seeds = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); + foreach (var seed in seeds) { - Debug.Assert(a >> 16 == 0); - Debug.Assert(b >> 16 == 0); - uint third = a << 16; - uint first = b << 16; - return method.RecoverLower16BitsGap(first, third); + // check for valid encounter slot info + if (!IsPokeSpotActivation(slot, seed, out uint s)) + continue; + yield return new PIDIV(PokeSpot, s); } + } - private static IEnumerable GetSeedsFromIVs(RNG method, uint a, uint b) + public static bool IsPokeSpotActivation(int slot, uint seed, out uint s) + { + s = seed; + var esv = (seed >> 16) % 100; + if (!IsPokeSpotSlotValid(slot, esv)) { - Debug.Assert(a >> 15 == 0); - Debug.Assert(b >> 15 == 0); - uint second = a << 16; - uint first = b << 16; - var pairs = method.RecoverLower16Bits(first, second) - .Concat(method.RecoverLower16Bits(first, second ^ 0x80000000)); - foreach (var z in pairs) - { - yield return z; - yield return z ^ 0x80000000; // sister bitflip - } + // todo } - - public static IEnumerable GetSeedsFromIVsSkip(RNG method, uint rand1, uint rand3) + // check for valid activation + s = RNG.XDRNG.Prev(seed); + if ((s >> 16) % 3 != 0) { - Debug.Assert(rand1 >> 15 == 0); - Debug.Assert(rand3 >> 15 == 0); - rand1 <<= 16; - rand3 <<= 16; - var seeds = method.RecoverLower16BitsGap(rand1, rand3) - .Concat(method.RecoverLower16BitsGap(rand1, rand3 ^ 0x80000000)); - foreach (var z in seeds) - { - yield return z; - yield return z ^ 0x80000000; // sister bitflip - } - } - - public static IEnumerable GetSeedsFromPIDEuclid(RNG method, uint rand1, uint rand2) - { - return method.RecoverLower16BitsEuclid16(rand1 << 16, rand2 << 16); - } - - public static IEnumerable GetSeedsFromIVsEuclid(RNG method, uint rand1, uint rand2) - { - return method.RecoverLower16BitsEuclid15(rand1 << 16, rand2 << 16); - } - - /// - /// Generates IVs from 2 RNG calls using 15 bits of each to generate 6 IVs (5bits each). - /// - /// First rand frame - /// Second rand frame - /// IVs that should be the result - /// IVs match random number IVs - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IVsMatch(uint r1, uint r2, ReadOnlySpan IVs) - { - if (IVs[0] != (r1 & 31)) - return false; - if (IVs[1] != (r1 >> 5 & 31)) - return false; - if (IVs[2] != (r1 >> 10 & 31)) - return false; - if (IVs[3] != (r2 & 31)) - return false; - if (IVs[4] != (r2 >> 5 & 31)) - return false; - if (IVs[5] != (r2 >> 10 & 31)) - return false; - return true; - } - - /// - /// Generates IVs from 2 RNG calls using 15 bits of each to generate 6 IVs (5bits each). - /// - /// Result storage - /// First rand frame - /// Second rand frame - /// Array of 6 IVs - internal static void GetIVsInt32(Span result, uint r1, uint r2) - { - result[5] = (int)r2 >> 10 & 31; - result[4] = (int)r2 >> 5 & 31; - result[3] = (int)r2 & 31; - result[2] = (int)r1 >> 10 & 31; - result[1] = (int)r1 >> 5 & 31; - result[0] = (int)r1 & 31; - } - - private static uint GetIVChunk(ReadOnlySpan IVs, int start) - { - uint val = 0; - for (int i = 0; i < 3; i++) - val |= IVs[i+start] << (5*i); - return val; - } - - public static IEnumerable GetColoEReaderMatches(uint PID) - { - var top = PID >> 16; - var bot = (ushort)PID; - var xdc = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); - foreach (var seed in xdc) - { - var B = RNG.XDRNG.Prev(seed); - var A = RNG.XDRNG.Prev(B); - - var C = RNG.XDRNG.Advance(A, 7); - - yield return new PIDIV(CXD, RNG.XDRNG.Prev(C)); - } - } - - public static IEnumerable GetPokeSpotSeeds(PKM pkm, int slot) - { - // Activate (rand % 3) - // Munchlax / Bonsly (10%/30%) - // Encounter Slot Value (ESV) = 50%/35%/15% rarity (0-49, 50-84, 85-99) - var pid = pkm.PID; - var top = pid >> 16; - var bot = pid & 0xFFFF; - var seeds = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); - foreach (var seed in seeds) - { - // check for valid encounter slot info - if (!IsPokeSpotActivation(slot, seed, out uint s)) - continue; - yield return new PIDIV(PokeSpot, s); - } - } - - public static bool IsPokeSpotActivation(int slot, uint seed, out uint s) - { - s = seed; - var esv = (seed >> 16) % 100; - if (!IsPokeSpotSlotValid(slot, esv)) + if ((s >> 16) % 100 < 10) // can't fail a munchlax/bonsly encounter check { // todo } - // check for valid activation - s = RNG.XDRNG.Prev(seed); - if ((s >> 16) % 3 != 0) + s = RNG.XDRNG.Prev(s); + if ((s >> 16) % 3 != 0) // can't activate even if generous { - if ((s >> 16) % 100 < 10) // can't fail a munchlax/bonsly encounter check - { - // todo - } - s = RNG.XDRNG.Prev(s); - if ((s >> 16) % 3 != 0) // can't activate even if generous - { - // todo - } - } - return true; - } - - private static bool IsPokeSpotSlotValid(int slot, uint esv) => slot switch - { - 0 => esv < 50 , // [0,50) - 1 => esv - 50 < 35, // [50,85) - 2 => esv >= 85, // [85,100) - _ => false, - }; - - public static bool IsCompatible3(this PIDType val, IEncounterTemplate encounter, PKM pkm) => encounter switch - { - WC3 g => IsCompatible3Mystery(val, pkm, g), - EncounterStatic3 s => IsCompatible3Static(val, pkm, s), - EncounterSlot3 w => (w.Species == (int)Species.Unown ? MethodH_Unown : MethodH).Contains(val), - EncounterStaticShadow => val is CXD or CXDAnti, - EncounterSlot3PokeSpot => val == PokeSpot, - _ => val == None, - }; - - private static bool IsCompatible3Static(PIDType val, PKM pkm, EncounterStatic3 s) => pkm.Version switch - { - (int)GameVersion.CXD => val is CXD or CXD_ColoStarter or CXDAnti, - (int)GameVersion.E => val == Method_1, // no roamer glitch - (int)GameVersion.FR or (int) GameVersion.LG => s.Roaming ? val.IsRoamerPIDIV(pkm) : val == Method_1, // roamer glitch - _ => s.Roaming ? val.IsRoamerPIDIV(pkm) : MethodH14.Contains(val), // RS, roamer glitch && RSBox s/w emulation => method 4 available - }; - - private static bool IsCompatible3Mystery(PIDType val, PKM pkm, WC3 g) => val == g.Method || val switch - { - // forced shiny eggs, when hatched, can lose their detectable correlation. - None => (g.Method is BACD_R_S or BACD_U_S) && g.IsEgg && !pkm.IsEgg, - CXDAnti => g.Method == CXD && g.Shiny == Shiny.Never, - _ => false, - }; - - private static bool IsRoamerPIDIV(this PIDType val, PKM pkm) - { - // Roamer PIDIV is always Method 1. - // M1 is checked before M1R. A M1R PIDIV can also be a M1 PIDIV, so check that collision. - if (Method_1_Roamer == val) - return true; - if (Method_1 != val) - return false; - - // only 8 bits are stored instead of 32 -- 5 bits HP, 3 bits for ATK. - // return pkm.IV32 <= 0xFF; - return pkm.IV_DEF == 0 && pkm.IV_SPE == 0 && pkm.IV_SPA == 0 && pkm.IV_SPD == 0 && pkm.IV_ATK <= 7; - } - - public static bool IsCompatible4(this PIDType val, IEncounterTemplate encounter, PKM pkm) => encounter switch - { - // Pokewalker can sometimes be confused with CuteCharm due to the PID creation routine. Double check if it is okay. - EncounterStatic4Pokewalker when val is CuteCharm => GetCuteCharmMatch(pkm, pkm.EncryptionConstant, out _) && IsCuteCharm4Valid(encounter, pkm), - EncounterStatic4Pokewalker => val == Pokewalker, - - EncounterStatic4 {Species: (int)Species.Pichu} => val == Pokewalker, - EncounterStatic4 {Shiny: Shiny.Always} => val == ChainShiny, - EncounterStatic4 when val is CuteCharm => IsCuteCharm4Valid(encounter, pkm), - EncounterStatic4 => val == Method_1, - - EncounterSlot4 w => val switch - { - // Chain shiny with Poké Radar is only possible in DPPt, in grass. Safari Zone does not allow using the Poké Radar - ChainShiny => pkm.IsShiny && !pkm.HGSS && (w.GroundTile & GroundTilePermission.Grass) != 0 && !Locations.IsSafariZoneLocation4(w.Location), - CuteCharm => IsCuteCharm4Valid(encounter, pkm), - _ => val == Method_1, - }, - - PGT => IsG4ManaphyPIDValid(val, pkm), // Manaphy is the only PGT in the database - PCD d when d.Gift.PK.PID != 1 => true, // Already matches PCD's fixed PID requirement - _ => val == None, - }; - - private static bool IsG4ManaphyPIDValid(PIDType val, PKM pkm) - { - if (pkm.IsEgg) - { - if (pkm.IsShiny) - return false; - if (val == Method_1) - return true; - return val == G4MGAntiShiny && IsAntiShinyARNG(); - } - - if (val == Method_1) - return pkm.WasTradedEgg || !pkm.IsShiny; // can't be shiny on received game - return val == G4MGAntiShiny && (pkm.WasTradedEgg || IsAntiShinyARNG()); - - bool IsAntiShinyARNG() - { - var shinyPID = RNG.ARNG.Prev(pkm.PID); - return (pkm.TID ^ pkm.SID ^ (shinyPID & 0xFFFF) ^ (shinyPID >> 16)) < 8; // shiny proc + // todo } } - - private static bool IsCuteCharm4Valid(ISpeciesForm encounter, PKM pkm) - { - if (pkm.Species is (int)Species.Marill or (int)Species.Azumarill) - { - return !IsCuteCharmAzurillMale(pkm.PID) // recognized as not Azurill - || encounter.Species == (int)Species.Azurill; // encounter must be male Azurill - } - - return true; - } - - private static bool IsCuteCharmAzurillMale(uint pid) => pid is >= 0xC8 and <= 0xE0; - - /// - /// There are some edge cases when the gender ratio changes across evolutions. - /// - private static (int Species, int Gender) GetCuteCharmGenderSpecies(PKM pk, uint pid, int currentSpecies) => currentSpecies switch - { - // Nincada evo chain travels from M/F -> Genderless Shedinja - (int)Species.Shedinja => ((int)Species.Nincada, EntityGender.GetFromPID((int)Species.Nincada, pid)), - - // These evolved species cannot be encountered with cute charm. - // 100% fixed gender does not modify PID; override this with the encounter species for correct calculation. - // We can assume the re-mapped species's [gender ratio] is what was encountered. - (int)Species.Wormadam => ((int)Species.Burmy, 1), - (int)Species.Mothim => ((int)Species.Burmy, 0), - (int)Species.Vespiquen => ((int)Species.Combee, 1), - (int)Species.Gallade => ((int)Species.Kirlia, 0), - (int)Species.Froslass => ((int)Species.Snorunt, 1), - // Azurill & Marill/Azumarill collision - // Changed gender ratio (25% M -> 50% M) needs special treatment. - // Double check the encounter species with IsCuteCharm4Valid afterwards. - (int)Species.Marill or (int)Species.Azumarill when IsCuteCharmAzurillMale(pid) => ((int)Species.Azurill, 0), - - // Future evolutions - (int)Species.Sylveon => ((int)Species.Eevee, pk.Gender), - - _ => (currentSpecies, pk.Gender), - }; - - private static readonly PIDType[] MethodH = { Method_1, Method_2, Method_3, Method_4 }; - private static readonly PIDType[] MethodH14 = { Method_1, Method_4 }; - private static readonly PIDType[] MethodH_Unown = { Method_1_Unown, Method_2_Unown, Method_3_Unown, Method_4_Unown }; + return true; } + + private static bool IsPokeSpotSlotValid(int slot, uint esv) => slot switch + { + 0 => esv < 50 , // [0,50) + 1 => esv - 50 < 35, // [50,85) + 2 => esv >= 85, // [85,100) + _ => false, + }; + + public static bool IsCompatible3(this PIDType val, IEncounterTemplate encounter, PKM pk) => encounter switch + { + WC3 g => IsCompatible3Mystery(val, pk, g), + EncounterStatic3 s => IsCompatible3Static(val, pk, s), + EncounterSlot3 w => (w.Species == (int)Species.Unown ? MethodH_Unown : MethodH).Contains(val), + EncounterStaticShadow => val is CXD or CXDAnti, + EncounterSlot3PokeSpot => val == PokeSpot, + _ => val == None, + }; + + private static bool IsCompatible3Static(PIDType val, PKM pk, EncounterStatic3 s) => pk.Version switch + { + (int)GameVersion.CXD => val is CXD or CXD_ColoStarter or CXDAnti, + (int)GameVersion.E => val == Method_1, // no roamer glitch + (int)GameVersion.FR or (int) GameVersion.LG => s.Roaming ? val.IsRoamerPIDIV(pk) : val == Method_1, // roamer glitch + _ => s.Roaming ? val.IsRoamerPIDIV(pk) : MethodH14.Contains(val), // RS, roamer glitch && RSBox s/w emulation => method 4 available + }; + + private static bool IsCompatible3Mystery(PIDType val, PKM pk, WC3 g) => val == g.Method || val switch + { + // forced shiny eggs, when hatched, can lose their detectable correlation. + None => (g.Method is BACD_R_S or BACD_U_S) && g.IsEgg && !pk.IsEgg, + CXDAnti => g.Method == CXD && g.Shiny == Shiny.Never, + _ => false, + }; + + private static bool IsRoamerPIDIV(this PIDType val, PKM pk) + { + // Roamer PIDIV is always Method 1. + // M1 is checked before M1R. A M1R PIDIV can also be a M1 PIDIV, so check that collision. + if (Method_1_Roamer == val) + return true; + if (Method_1 != val) + return false; + + // only 8 bits are stored instead of 32 -- 5 bits HP, 3 bits for ATK. + // return pk.IV32 <= 0xFF; + return pk.IV_DEF == 0 && pk.IV_SPE == 0 && pk.IV_SPA == 0 && pk.IV_SPD == 0 && pk.IV_ATK <= 7; + } + + public static bool IsCompatible4(this PIDType val, IEncounterTemplate encounter, PKM pk) => encounter switch + { + // Pokewalker can sometimes be confused with CuteCharm due to the PID creation routine. Double check if it is okay. + EncounterStatic4Pokewalker when val is CuteCharm => GetCuteCharmMatch(pk, pk.EncryptionConstant, out _) && IsCuteCharm4Valid(encounter, pk), + EncounterStatic4Pokewalker => val == Pokewalker, + + EncounterStatic4 {Species: (int)Species.Pichu} => val == Pokewalker, + EncounterStatic4 {Shiny: Shiny.Always} => val == ChainShiny, + EncounterStatic4 when val is CuteCharm => IsCuteCharm4Valid(encounter, pk), + EncounterStatic4 => val == Method_1, + + EncounterSlot4 w => val switch + { + // Chain shiny with Poké Radar is only possible in DPPt, in grass. Safari Zone does not allow using the Poké Radar + ChainShiny => pk.IsShiny && !pk.HGSS && (w.GroundTile & GroundTileAllowed.Grass) != 0 && !Locations.IsSafariZoneLocation4(w.Location), + CuteCharm => IsCuteCharm4Valid(encounter, pk), + _ => val == Method_1, + }, + + PGT => IsG4ManaphyPIDValid(val, pk), // Manaphy is the only PGT in the database + PCD d when d.Gift.PK.PID != 1 => true, // Already matches PCD's fixed PID requirement + _ => val == None, + }; + + private static bool IsG4ManaphyPIDValid(PIDType val, PKM pk) + { + if (pk.IsEgg) + { + if (pk.IsShiny) + return false; + if (val == Method_1) + return true; + return val == G4MGAntiShiny && IsAntiShinyARNG(); + } + + if (val == Method_1) + return pk.WasTradedEgg || !pk.IsShiny; // can't be shiny on received game + return val == G4MGAntiShiny && (pk.WasTradedEgg || IsAntiShinyARNG()); + + bool IsAntiShinyARNG() + { + var shinyPID = RNG.ARNG.Prev(pk.PID); + return (pk.TID ^ pk.SID ^ (shinyPID & 0xFFFF) ^ (shinyPID >> 16)) < 8; // shiny proc + } + } + + private static bool IsCuteCharm4Valid(ISpeciesForm encounter, PKM pk) + { + if (pk.Species is (int)Species.Marill or (int)Species.Azumarill) + { + return !IsCuteCharmAzurillMale(pk.PID) // recognized as not Azurill + || encounter.Species == (int)Species.Azurill; // encounter must be male Azurill + } + + return true; + } + + private static bool IsCuteCharmAzurillMale(uint pid) => pid is >= 0xC8 and <= 0xE0; + + /// + /// There are some edge cases when the gender ratio changes across evolutions. + /// + private static (int Species, int Gender) GetCuteCharmGenderSpecies(PKM pk, uint pid, int currentSpecies) => currentSpecies switch + { + // Nincada evo chain travels from M/F -> Genderless Shedinja + (int)Species.Shedinja => ((int)Species.Nincada, EntityGender.GetFromPID((int)Species.Nincada, pid)), + + // These evolved species cannot be encountered with cute charm. + // 100% fixed gender does not modify PID; override this with the encounter species for correct calculation. + // We can assume the re-mapped species's [gender ratio] is what was encountered. + (int)Species.Wormadam => ((int)Species.Burmy, 1), + (int)Species.Mothim => ((int)Species.Burmy, 0), + (int)Species.Vespiquen => ((int)Species.Combee, 1), + (int)Species.Gallade => ((int)Species.Kirlia, 0), + (int)Species.Froslass => ((int)Species.Snorunt, 1), + // Azurill & Marill/Azumarill collision + // Changed gender ratio (25% M -> 50% M) needs special treatment. + // Double check the encounter species with IsCuteCharm4Valid afterwards. + (int)Species.Marill or (int)Species.Azumarill when IsCuteCharmAzurillMale(pid) => ((int)Species.Azurill, 0), + + // Future evolutions + (int)Species.Sylveon => ((int)Species.Eevee, pk.Gender), + + _ => (currentSpecies, pk.Gender), + }; + + private static readonly PIDType[] MethodH = { Method_1, Method_2, Method_3, Method_4 }; + private static readonly PIDType[] MethodH14 = { Method_1, Method_4 }; + private static readonly PIDType[] MethodH_Unown = { Method_1_Unown, Method_2_Unown, Method_3_Unown, Method_4_Unown }; } diff --git a/PKHeX.Core/Legality/RNG/Overworld8RNG.cs b/PKHeX.Core/Legality/RNG/Overworld8RNG.cs index b3145a2b7..7c7a330d6 100644 --- a/PKHeX.Core/Legality/RNG/Overworld8RNG.cs +++ b/PKHeX.Core/Legality/RNG/Overworld8RNG.cs @@ -1,230 +1,229 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains logic for the Generation 8 (SW/SH) overworld spawns that walk around the overworld. +/// +/// +/// Entities spawned into the overworld that can be encountered are assigned a 32bit seed, which can be immediately derived from the . +/// +public static class Overworld8RNG { - /// - /// Contains logic for the Generation 8 (SW/SH) overworld spawns that walk around the overworld. - /// - /// - /// Entities spawned into the overworld that can be encountered are assigned a 32bit seed, which can be immediately derived from the . - /// - public static class Overworld8RNG + public static void ApplyDetails(PKM pk, EncounterCriteria criteria, Shiny shiny = Shiny.FixedValue, int flawless = -1) { - public static void ApplyDetails(PKM pk, EncounterCriteria criteria, Shiny shiny = Shiny.FixedValue, int flawless = -1) - { - if (shiny == Shiny.FixedValue) - shiny = criteria.Shiny is Shiny.Random or Shiny.Never ? Shiny.Never : Shiny.Always; - if (flawless == -1) - flawless = 0; + if (shiny == Shiny.FixedValue) + shiny = criteria.Shiny is Shiny.Random or Shiny.Never ? Shiny.Never : Shiny.Always; + if (flawless == -1) + flawless = 0; - int ctr = 0; - const int maxAttempts = 50_000; - var rnd = Util.Rand; - do - { - var seed = rnd.Rand32(); - if (TryApplyFromSeed(pk, criteria, shiny, flawless, seed)) - return; - } while (++ctr != maxAttempts); - TryApplyFromSeed(pk, EncounterCriteria.Unrestricted, shiny, flawless, rnd.Rand32()); + int ctr = 0; + const int maxAttempts = 50_000; + var rnd = Util.Rand; + do + { + var seed = rnd.Rand32(); + if (TryApplyFromSeed(pk, criteria, shiny, flawless, seed)) + return; + } while (++ctr != maxAttempts); + TryApplyFromSeed(pk, EncounterCriteria.Unrestricted, shiny, flawless, rnd.Rand32()); + } + + private static bool TryApplyFromSeed(PKM pk, EncounterCriteria criteria, Shiny shiny, int flawless, uint seed) + { + var xoro = new Xoroshiro128Plus(seed); + + // Encryption Constant + pk.EncryptionConstant = (uint) xoro.NextInt(uint.MaxValue); + + // PID + var pid = (uint) xoro.NextInt(uint.MaxValue); + if (shiny == Shiny.Never) + { + if (GetIsShiny(pk.TID, pk.SID, pid)) + pid ^= 0x1000_0000; } - - private static bool TryApplyFromSeed(PKM pk, EncounterCriteria criteria, Shiny shiny, int flawless, uint seed) + else if (shiny != Shiny.Random) { - var xoro = new Xoroshiro128Plus(seed); - - // Encryption Constant - pk.EncryptionConstant = (uint) xoro.NextInt(uint.MaxValue); - - // PID - var pid = (uint) xoro.NextInt(uint.MaxValue); - if (shiny == Shiny.Never) - { - if (GetIsShiny(pk.TID, pk.SID, pid)) - pid ^= 0x1000_0000; - } - else if (shiny != Shiny.Random) - { - if (!GetIsShiny(pk.TID, pk.SID, pid)) - pid = GetShinyPID(pk.TID, pk.SID, pid, 0); - - if (shiny == Shiny.AlwaysSquare && pk.ShinyXor != 0) - return false; - if (shiny == Shiny.AlwaysStar && pk.ShinyXor == 0) - return false; - } - - pk.PID = pid; - - // IVs - Span ivs = stackalloc[] {UNSET, UNSET, UNSET, UNSET, UNSET, UNSET}; - const int MAX = 31; - for (int i = 0; i < flawless; i++) - { - int index; - do { index = (int) xoro.NextInt(6); } - while (ivs[index] != UNSET); - - ivs[index] = MAX; - } - - for (int i = 0; i < ivs.Length; i++) - { - if (ivs[i] == UNSET) - ivs[i] = (int) xoro.NextInt(32); - } - - if (!criteria.IsIVsCompatible(ivs, 8)) - return false; - - pk.IV_HP = ivs[0]; - pk.IV_ATK = ivs[1]; - pk.IV_DEF = ivs[2]; - pk.IV_SPA = ivs[3]; - pk.IV_SPD = ivs[4]; - pk.IV_SPE = ivs[5]; - - // Remainder - var scale = (IScaledSize) pk; - scale.HeightScalar = (byte)((int) xoro.NextInt(0x81) + (int) xoro.NextInt(0x80)); - scale.WeightScalar = (byte)((int) xoro.NextInt(0x81) + (int) xoro.NextInt(0x80)); - - return true; - } - - public static bool ValidateOverworldEncounter(PKM pk, Shiny shiny = Shiny.FixedValue, int flawless = -1) - { - var seed = GetOriginalSeed(pk); - return ValidateOverworldEncounter(pk, seed, shiny, flawless); - } - - public static bool ValidateOverworldEncounter(PKM pk, uint seed, Shiny shiny = Shiny.FixedValue, int flawless = -1) - { - // is the seed Xoroshiro determined, or just truncated state? - if (seed == uint.MaxValue) - return false; - - var xoro = new Xoroshiro128Plus(seed); - var ec = (uint) xoro.NextInt(uint.MaxValue); - if (ec != pk.EncryptionConstant) - return false; - - var pid = (uint) xoro.NextInt(uint.MaxValue); - if (!IsPIDValid(pk, pid, shiny)) - return false; - - var actualCount = flawless == -1 ? GetIsMatchEnd(pk, xoro) : GetIsMatchEnd(pk, xoro, flawless, flawless); - return actualCount != NoMatchIVs; - } - - private static bool IsPIDValid(PKM pk, uint pid, Shiny shiny) - { - if (shiny == Shiny.Random) - return pid == pk.PID; - - if (pid == pk.PID) - return true; - - // Check forced shiny - if (pk.IsShiny) - { - if (GetIsShiny(pk.TID, pk.SID, pid)) - return false; - - pid = GetShinyPID(pk.TID, pk.SID, pid, 0); - return pid == pk.PID; - } - - // Check forced non-shiny if (!GetIsShiny(pk.TID, pk.SID, pid)) + pid = GetShinyPID(pk.TID, pk.SID, pid, 0); + + if (shiny == Shiny.AlwaysSquare && pk.ShinyXor != 0) + return false; + if (shiny == Shiny.AlwaysStar && pk.ShinyXor == 0) + return false; + } + + pk.PID = pid; + + // IVs + Span ivs = stackalloc[] {UNSET, UNSET, UNSET, UNSET, UNSET, UNSET}; + const int MAX = 31; + for (int i = 0; i < flawless; i++) + { + int index; + do { index = (int) xoro.NextInt(6); } + while (ivs[index] != UNSET); + + ivs[index] = MAX; + } + + for (int i = 0; i < ivs.Length; i++) + { + if (ivs[i] == UNSET) + ivs[i] = (int) xoro.NextInt(32); + } + + if (!criteria.IsIVsCompatible(ivs, 8)) + return false; + + pk.IV_HP = ivs[0]; + pk.IV_ATK = ivs[1]; + pk.IV_DEF = ivs[2]; + pk.IV_SPA = ivs[3]; + pk.IV_SPD = ivs[4]; + pk.IV_SPE = ivs[5]; + + // Remainder + var scale = (IScaledSize) pk; + scale.HeightScalar = (byte)((int) xoro.NextInt(0x81) + (int) xoro.NextInt(0x80)); + scale.WeightScalar = (byte)((int) xoro.NextInt(0x81) + (int) xoro.NextInt(0x80)); + + return true; + } + + public static bool ValidateOverworldEncounter(PKM pk, Shiny shiny = Shiny.FixedValue, int flawless = -1) + { + var seed = GetOriginalSeed(pk); + return ValidateOverworldEncounter(pk, seed, shiny, flawless); + } + + public static bool ValidateOverworldEncounter(PKM pk, uint seed, Shiny shiny = Shiny.FixedValue, int flawless = -1) + { + // is the seed Xoroshiro determined, or just truncated state? + if (seed == uint.MaxValue) + return false; + + var xoro = new Xoroshiro128Plus(seed); + var ec = (uint) xoro.NextInt(uint.MaxValue); + if (ec != pk.EncryptionConstant) + return false; + + var pid = (uint) xoro.NextInt(uint.MaxValue); + if (!IsPIDValid(pk, pid, shiny)) + return false; + + var actualCount = flawless == -1 ? GetIsMatchEnd(pk, xoro) : GetIsMatchEnd(pk, xoro, flawless, flawless); + return actualCount != NoMatchIVs; + } + + private static bool IsPIDValid(PKM pk, uint pid, Shiny shiny) + { + if (shiny == Shiny.Random) + return pid == pk.PID; + + if (pid == pk.PID) + return true; + + // Check forced shiny + if (pk.IsShiny) + { + if (GetIsShiny(pk.TID, pk.SID, pid)) return false; - pid ^= 0x1000_0000; + pid = GetShinyPID(pk.TID, pk.SID, pid, 0); return pid == pk.PID; } - private const int NoMatchIVs = -1; - private const int UNSET = -1; + // Check forced non-shiny + if (!GetIsShiny(pk.TID, pk.SID, pid)) + return false; - private static int GetIsMatchEnd(PKM pk, Xoroshiro128Plus xoro, int start = 0, int end = 3) + pid ^= 0x1000_0000; + return pid == pk.PID; + } + + private const int NoMatchIVs = -1; + private const int UNSET = -1; + + private static int GetIsMatchEnd(PKM pk, Xoroshiro128Plus xoro, int start = 0, int end = 3) + { + bool skip1 = start == 0 && end == 3; + for (int iv_count = start; iv_count <= end; iv_count++) { - bool skip1 = start == 0 && end == 3; - for (int iv_count = start; iv_count <= end; iv_count++) + if (skip1 && iv_count == 1) + continue; + + var copy = xoro; + Span ivs = stackalloc [] { UNSET, UNSET, UNSET, UNSET, UNSET, UNSET }; + const int MAX = 31; + for (int i = 0; i < iv_count; i++) { - if (skip1 && iv_count == 1) - continue; - - var copy = xoro; - Span ivs = stackalloc [] { UNSET, UNSET, UNSET, UNSET, UNSET, UNSET }; - const int MAX = 31; - for (int i = 0; i < iv_count; i++) - { - int index; - do { index = (int)copy.NextInt(6); } while (ivs[index] != UNSET); - ivs[index] = MAX; - } - - for (var i = 0; i < ivs.Length; i++) - { - if (ivs[i] == UNSET) - ivs[i] = (int)copy.NextInt(31 + 1); - } - - if (ivs[0] != pk.IV_HP) continue; - if (ivs[1] != pk.IV_ATK) continue; - if (ivs[2] != pk.IV_DEF) continue; - if (ivs[3] != pk.IV_SPA) continue; - if (ivs[4] != pk.IV_SPD) continue; - if (ivs[5] != pk.IV_SPE) continue; - - if (pk is not IScaledSize s) - continue; - var height = (int) copy.NextInt(0x81) + (int) copy.NextInt(0x80); - if (s.HeightScalar != height) - continue; - var weight = (int) copy.NextInt(0x81) + (int) copy.NextInt(0x80); - if (s.WeightScalar != weight) - continue; - - return iv_count; - } - return NoMatchIVs; - } - - private static uint GetShinyPID(int tid, int sid, uint pid, int type) - { - return (uint)(((tid ^ sid ^ (pid & 0xFFFF) ^ type) << 16) | (pid & 0xFFFF)); - } - - private static bool GetIsShiny(int tid, int sid, uint pid) - { - return GetShinyXor(pid, (uint)((sid << 16) | tid)) < 16; - } - - private static uint GetShinyXor(uint pid, uint oid) - { - var xor = pid ^ oid; - return (xor ^ (xor >> 16)) & 0xFFFF; - } - - /// - /// Obtains the original seed for the Generation 8 overworld wild encounter. - /// - /// Entity to check for - /// Seed - public static uint GetOriginalSeed(PKM pk) - { - var seed = pk.EncryptionConstant - unchecked((uint)Xoroshiro128Plus.XOROSHIRO_CONST); - if (seed == 0xD5B9C463) // Collision seed with the 0xFFFFFFFF re-roll. - { - var xoro = new Xoroshiro128Plus(seed); - /* ec */ xoro.NextInt(uint.MaxValue); - var pid = xoro.NextInt(uint.MaxValue); - if (pid != pk.PID) - return 0xDD6295A4; + int index; + do { index = (int)copy.NextInt(6); } while (ivs[index] != UNSET); + ivs[index] = MAX; } - return seed; + for (var i = 0; i < ivs.Length; i++) + { + if (ivs[i] == UNSET) + ivs[i] = (int)copy.NextInt(31 + 1); + } + + if (ivs[0] != pk.IV_HP) continue; + if (ivs[1] != pk.IV_ATK) continue; + if (ivs[2] != pk.IV_DEF) continue; + if (ivs[3] != pk.IV_SPA) continue; + if (ivs[4] != pk.IV_SPD) continue; + if (ivs[5] != pk.IV_SPE) continue; + + if (pk is not IScaledSize s) + continue; + var height = (int) copy.NextInt(0x81) + (int) copy.NextInt(0x80); + if (s.HeightScalar != height) + continue; + var weight = (int) copy.NextInt(0x81) + (int) copy.NextInt(0x80); + if (s.WeightScalar != weight) + continue; + + return iv_count; } + return NoMatchIVs; + } + + private static uint GetShinyPID(int tid, int sid, uint pid, int type) + { + return (uint)(((tid ^ sid ^ (pid & 0xFFFF) ^ type) << 16) | (pid & 0xFFFF)); + } + + private static bool GetIsShiny(int tid, int sid, uint pid) + { + return GetShinyXor(pid, (uint)((sid << 16) | tid)) < 16; + } + + private static uint GetShinyXor(uint pid, uint oid) + { + var xor = pid ^ oid; + return (xor ^ (xor >> 16)) & 0xFFFF; + } + + /// + /// Obtains the original seed for the Generation 8 overworld wild encounter. + /// + /// Entity to check for + /// Seed + public static uint GetOriginalSeed(PKM pk) + { + var seed = pk.EncryptionConstant - unchecked((uint)Xoroshiro128Plus.XOROSHIRO_CONST); + if (seed == 0xD5B9C463) // Collision seed with the 0xFFFFFFFF re-roll. + { + var xoro = new Xoroshiro128Plus(seed); + /* ec */ xoro.NextInt(uint.MaxValue); + var pid = xoro.NextInt(uint.MaxValue); + if (pid != pk.PID) + return 0xDD6295A4; + } + + return seed; } } diff --git a/PKHeX.Core/Legality/RNG/PIDGenerator.cs b/PKHeX.Core/Legality/RNG/PIDGenerator.cs index 983bedcfb..64c7b3f2e 100644 --- a/PKHeX.Core/Legality/RNG/PIDGenerator.cs +++ b/PKHeX.Core/Legality/RNG/PIDGenerator.cs @@ -1,440 +1,439 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains a collection of methods that mutate the input Pokémon object, usually to obtain a correlation. +/// +public static class PIDGenerator { - /// - /// Contains a collection of methods that mutate the input Pokémon object, usually to obtain a correlation. - /// - public static class PIDGenerator + private static void SetValuesFromSeedLCRNG(PKM pk, PIDType type, uint seed) { - private static void SetValuesFromSeedLCRNG(PKM pk, PIDType type, uint seed) + var rng = RNG.LCRNG; + var A = rng.Next(seed); + var B = rng.Next(A); + var skipBetweenPID = type is PIDType.Method_3 or PIDType.Method_3_Unown; + if (skipBetweenPID) // VBlank skip between PID rand() [RARE] + B = rng.Next(B); + + var swappedPIDHalves = type is >= PIDType.Method_1_Unown and <= PIDType.Method_4_Unown; + if (swappedPIDHalves) // switched order of PID halves, "BA.." + pk.PID = (A & 0xFFFF0000) | (B >> 16); + else + pk.PID = (B & 0xFFFF0000) | (A >> 16); + + var C = rng.Next(B); + var skipIV1Frame = type is PIDType.Method_2 or PIDType.Method_2_Unown; + if (skipIV1Frame) // VBlank skip after PID + C = rng.Next(C); + + var D = rng.Next(C); + var skipIV2Frame = type is PIDType.Method_4 or PIDType.Method_4_Unown; + if (skipIV2Frame) // VBlank skip between IVs + D = rng.Next(D); + + Span IVs = stackalloc int[6]; + MethodFinder.GetIVsInt32(IVs, C >> 16, D >> 16); + if (type == PIDType.Method_1_Roamer) { - var rng = RNG.LCRNG; - var A = rng.Next(seed); - var B = rng.Next(A); - var skipBetweenPID = type is PIDType.Method_3 or PIDType.Method_3_Unown; - if (skipBetweenPID) // VBlank skip between PID rand() [RARE] - B = rng.Next(B); + // Only store lowest 8 bits of IV data; zero out the other bits. + IVs[1] &= 7; + for (int i = 2; i < 6; i++) + IVs[i] = 0; + } + pk.SetIVs(IVs); + } - var swappedPIDHalves = type is >= PIDType.Method_1_Unown and <= PIDType.Method_4_Unown; - if (swappedPIDHalves) // switched order of PID halves, "BA.." - pk.PID = (A & 0xFFFF0000) | B >> 16; - else - pk.PID = (B & 0xFFFF0000) | A >> 16; + private static void SetValuesFromSeedBACD(PKM pk, PIDType type, uint seed) + { + var rng = RNG.LCRNG; + bool shiny = type is PIDType.BACD_R_S or PIDType.BACD_U_S; + uint X = shiny ? rng.Next(seed) : seed; + var A = rng.Next(X); + var B = rng.Next(A); + var C = rng.Next(B); + var D = rng.Next(C); - var C = rng.Next(B); - var skipIV1Frame = type is PIDType.Method_2 or PIDType.Method_2_Unown; - if (skipIV1Frame) // VBlank skip after PID - C = rng.Next(C); + if (shiny) + { + uint PID = (X & 0xFFFF0000) | ((uint)pk.SID ^ (uint)pk.TID ^ (X >> 16)); + PID &= 0xFFFFFFF8; + PID |= (B >> 16) & 0x7; // lowest 3 bits - var D = rng.Next(C); - var skipIV2Frame = type is PIDType.Method_4 or PIDType.Method_4_Unown; - if (skipIV2Frame) // VBlank skip between IVs - D = rng.Next(D); - - Span IVs = stackalloc int[6]; - MethodFinder.GetIVsInt32(IVs, C >> 16, D >> 16); - if (type == PIDType.Method_1_Roamer) - { - // Only store lowest 8 bits of IV data; zero out the other bits. - IVs[1] &= 7; - for (int i = 2; i < 6; i++) - IVs[i] = 0; - } - pk.SetIVs(IVs); + pk.PID = PID; + } + else if (type is PIDType.BACD_R_AX or PIDType.BACD_U_AX) + { + uint low = B >> 16; + pk.PID = ((A & 0xFFFF0000) ^ (((uint)pk.TID ^ (uint)pk.SID ^ low) << 16)) | low; + } + else + { + pk.PID = (A & 0xFFFF0000) | (B >> 16); } - private static void SetValuesFromSeedBACD(PKM pk, PIDType type, uint seed) + Span IVs = stackalloc int[6]; + MethodFinder.GetIVsInt32(IVs, C >> 16, D >> 16); + pk.SetIVs(IVs); + + bool antishiny = type is PIDType.BACD_R_A or PIDType.BACD_U_A; + while (antishiny && pk.IsShiny) + pk.PID = unchecked(pk.PID + 1); + } + + private static void SetValuesFromSeedXDRNG(PKM pk, uint seed) + { + var rng = RNG.XDRNG; + switch (pk.Species) { - var rng = RNG.LCRNG; - bool shiny = type is PIDType.BACD_R_S or PIDType.BACD_U_S; - uint X = shiny ? rng.Next(seed) : seed; - var A = rng.Next(X); - var B = rng.Next(A); - var C = rng.Next(B); - var D = rng.Next(C); - - if (shiny) - { - uint PID = (X & 0xFFFF0000) | ((uint)pk.SID ^ (uint)pk.TID ^ X >> 16); - PID &= 0xFFFFFFF8; - PID |= B >> 16 & 0x7; // lowest 3 bits - - pk.PID = PID; - } - else if (type is PIDType.BACD_R_AX or PIDType.BACD_U_AX) - { - uint low = B >> 16; - pk.PID = ((A & 0xFFFF0000) ^ (((uint)pk.TID ^ (uint)pk.SID ^ low) << 16)) | low; - } - else - { - pk.PID = (A & 0xFFFF0000) | B >> 16; - } - - Span IVs = stackalloc int[6]; - MethodFinder.GetIVsInt32(IVs, C >> 16, D >> 16); - pk.SetIVs(IVs); - - bool antishiny = type is PIDType.BACD_R_A or PIDType.BACD_U_A; - while (antishiny && pk.IsShiny) - pk.PID = unchecked(pk.PID + 1); + case (int)Species.Umbreon or (int)Species.Eevee: // Colo Umbreon, XD Eevee + pk.TID = (int)((seed = rng.Next(seed)) >> 16); + pk.SID = (int)((seed = rng.Next(seed)) >> 16); + seed = rng.Advance(seed, 2); // PID calls consumed + break; + case (int)Species.Espeon: // Colo Espeon + pk.TID = (int)((seed = rng.Next(seed)) >> 16); + pk.SID = (int)((seed = rng.Next(seed)) >> 16); + seed = rng.Advance(seed, 9); // PID calls consumed, skip over Umbreon + break; } + var A = rng.Next(seed); // IV1 + var B = rng.Next(A); // IV2 + var C = rng.Next(B); // Ability? + var D = rng.Next(C); // PID + var E = rng.Next(D); // PID - private static void SetValuesFromSeedXDRNG(PKM pk, uint seed) + pk.PID = (D & 0xFFFF0000) | (E >> 16); + Span IVs = stackalloc int[6]; + MethodFinder.GetIVsInt32(IVs, A >> 16, B >> 16); + pk.SetIVs(IVs); + } + + public static void SetValuesFromSeedXDRNG_EReader(PKM pk, uint seed) + { + var rng = RNG.XDRNG; + var A = rng.Reverse(seed, 4); + var D = rng.Next(A); // PID + var E = rng.Next(D); // PID + + pk.PID = (D & 0xFFFF0000) | (E >> 16); + } + + private static void SetValuesFromSeedChannel(PKM pk, uint seed) + { + var rng = RNG.XDRNG; + var O = rng.Next(seed); // SID + var A = rng.Next(O); // PID + var B = rng.Next(A); // PID + var C = rng.Next(B); // Held Item + var D = rng.Next(C); // Version + var E = rng.Next(D); // OT Gender + + const int TID = 40122; + var SID = (int)(O >> 16); + var pid1 = A >> 16; + var pid2 = B >> 16; + pk.TID = TID; + pk.SID = SID; + var pid = (pid1 << 16) | pid2; + if ((pid2 > 7 ? 0 : 1) != (pid1 ^ SID ^ TID)) + pid ^= 0x80000000; + pk.PID = pid; + pk.HeldItem = (int)(C >> 31) + 169; // 0-Ganlon, 1-Salac + pk.Version = (int)(D >> 31) + 1; // 0-Sapphire, 1-Ruby + pk.OT_Gender = (int)(E >> 31); + Span ivs = stackalloc int[6]; + rng.GetSequentialIVsInt32(E, ivs); + pk.SetIVs(ivs); + } + + public static void SetValuesFromSeed(PKM pk, PIDType type, uint seed) + { + var method = GetGeneratorMethod(type); + method(pk, seed); + } + + private static Action GetGeneratorMethod(PIDType t) + { + switch (t) { + case PIDType.Channel: + return SetValuesFromSeedChannel; + case PIDType.CXD: + return SetValuesFromSeedXDRNG; + + case PIDType.Method_1 or PIDType.Method_2 or PIDType.Method_3 or PIDType.Method_4: + case PIDType.Method_1_Unown or PIDType.Method_2_Unown or PIDType.Method_3_Unown or PIDType.Method_4_Unown: + case PIDType.Method_1_Roamer: + return (pk, seed) => SetValuesFromSeedLCRNG(pk, t, seed); + + case PIDType.BACD_R: + case PIDType.BACD_R_A: + case PIDType.BACD_R_S: + case PIDType.BACD_R_AX: + return (pk, seed) => SetValuesFromSeedBACD(pk, t, seed & 0xFFFF); + case PIDType.BACD_U: + case PIDType.BACD_U_A: + case PIDType.BACD_U_S: + case PIDType.BACD_U_AX: + return (pk, seed) => SetValuesFromSeedBACD(pk, t, seed); + + case PIDType.PokeSpot: + return SetRandomPIDIV; + + case PIDType.G5MGShiny: + return SetValuesFromSeedMG5Shiny; + + case PIDType.Pokewalker: + return (pk, seed) => pk.PID = GetPokeWalkerPID(pk.TID, pk.SID, seed%24, pk.Gender, pk.PersonalInfo.Gender); + + // others: unimplemented + case PIDType.CuteCharm: + break; + case PIDType.ChainShiny: + return SetRandomChainShinyPID; + case PIDType.G4MGAntiShiny: + break; + } + return (_, _) => { }; + } + + public static void SetRandomChainShinyPID(PKM pk, uint seed) + { + // 13 rand bits + // 1 3-bit for upper + // 1 3-bit for lower + + uint Next() => (seed = RNG.LCRNG.Next(seed)) >> 16; + uint lower = Next() & 7; + uint upper = Next() & 7; + for (int i = 0; i < 13; i++) + lower |= (Next() & 1) << (3 + i); + + upper = ((uint)(lower ^ pk.TID ^ pk.SID) & 0xFFF8) | (upper & 0x7); + pk.PID = (upper << 16) | lower; + Span IVs = stackalloc int[6]; + MethodFinder.GetIVsInt32(IVs, Next(), Next()); + pk.SetIVs(IVs); + } + + public static void SetRandomPokeSpotPID(PKM pk, int nature, int gender, int ability, int slot) + { + var rnd = Util.Rand; + while (true) + { + var seed = rnd.Rand32(); + if (!MethodFinder.IsPokeSpotActivation(slot, seed, out _)) + continue; + var rng = RNG.XDRNG; - switch (pk.Species) - { - case (int)Species.Umbreon or (int)Species.Eevee: // Colo Umbreon, XD Eevee - pk.TID = (int)((seed = rng.Next(seed)) >> 16); - pk.SID = (int)((seed = rng.Next(seed)) >> 16); - seed = rng.Advance(seed, 2); // PID calls consumed - break; - case (int)Species.Espeon: // Colo Espeon - pk.TID = (int)((seed = rng.Next(seed)) >> 16); - pk.SID = (int)((seed = rng.Next(seed)) >> 16); - seed = rng.Advance(seed, 9); // PID calls consumed, skip over Umbreon - break; - } - var A = rng.Next(seed); // IV1 - var B = rng.Next(A); // IV2 - var C = rng.Next(B); // Ability? - var D = rng.Next(C); // PID + var D = rng.Next(seed); // PID var E = rng.Next(D); // PID - pk.PID = (D & 0xFFFF0000) | E >> 16; - Span IVs = stackalloc int[6]; - MethodFinder.GetIVsInt32(IVs, A >> 16, B >> 16); - pk.SetIVs(IVs); - } + pk.PID = (D & 0xFFFF0000) | (E >> 16); + if (!IsValidCriteria4(pk, nature, ability, gender)) + continue; - public static void SetValuesFromSeedXDRNG_EReader(PKM pk, uint seed) - { - var rng = RNG.XDRNG; - var A = rng.Reverse(seed, 4); - var D = rng.Next(A); // PID - var E = rng.Next(D); // PID - - pk.PID = (D & 0xFFFF0000) | E >> 16; - } - - private static void SetValuesFromSeedChannel(PKM pk, uint seed) - { - var rng = RNG.XDRNG; - var O = rng.Next(seed); // SID - var A = rng.Next(O); // PID - var B = rng.Next(A); // PID - var C = rng.Next(B); // Held Item - var D = rng.Next(C); // Version - var E = rng.Next(D); // OT Gender - - const int TID = 40122; - var SID = (int)(O >> 16); - var pid1 = A >> 16; - var pid2 = B >> 16; - pk.TID = TID; - pk.SID = SID; - var pid = pid1 << 16 | pid2; - if ((pid2 > 7 ? 0 : 1) != (pid1 ^ SID ^ TID)) - pid ^= 0x80000000; - pk.PID = pid; - pk.HeldItem = (int)(C >> 31) + 169; // 0-Ganlon, 1-Salac - pk.Version = (int)(D >> 31) + 1; // 0-Sapphire, 1-Ruby - pk.OT_Gender = (int)(E >> 31); - Span ivs = stackalloc int[6]; - rng.GetSequentialIVsInt32(E, ivs); - pk.SetIVs(ivs); - } - - public static void SetValuesFromSeed(PKM pk, PIDType type, uint seed) - { - var method = GetGeneratorMethod(type); - method(pk, seed); - } - - private static Action GetGeneratorMethod(PIDType t) - { - switch (t) - { - case PIDType.Channel: - return SetValuesFromSeedChannel; - case PIDType.CXD: - return SetValuesFromSeedXDRNG; - - case PIDType.Method_1 or PIDType.Method_2 or PIDType.Method_3 or PIDType.Method_4: - case PIDType.Method_1_Unown or PIDType.Method_2_Unown or PIDType.Method_3_Unown or PIDType.Method_4_Unown: - case PIDType.Method_1_Roamer: - return (pk, seed) => SetValuesFromSeedLCRNG(pk, t, seed); - - case PIDType.BACD_R: - case PIDType.BACD_R_A: - case PIDType.BACD_R_S: - case PIDType.BACD_R_AX: - return (pk, seed) => SetValuesFromSeedBACD(pk, t, seed & 0xFFFF); - case PIDType.BACD_U: - case PIDType.BACD_U_A: - case PIDType.BACD_U_S: - case PIDType.BACD_U_AX: - return (pk, seed) => SetValuesFromSeedBACD(pk, t, seed); - - case PIDType.PokeSpot: - return SetRandomPIDIV; - - case PIDType.G5MGShiny: - return SetValuesFromSeedMG5Shiny; - - case PIDType.Pokewalker: - return (pk, seed) => pk.PID = GetPokeWalkerPID(pk.TID, pk.SID, seed%24, pk.Gender, pk.PersonalInfo.Gender); - - // others: unimplemented - case PIDType.CuteCharm: - break; - case PIDType.ChainShiny: - return SetRandomChainShinyPID; - case PIDType.G4MGAntiShiny: - break; - } - return (_, _) => { }; - } - - public static void SetRandomChainShinyPID(PKM pk, uint seed) - { - // 13 rand bits - // 1 3-bit for upper - // 1 3-bit for lower - - uint Next() => (seed = RNG.LCRNG.Next(seed)) >> 16; - uint lower = Next() & 7; - uint upper = Next() & 7; - for (int i = 0; i < 13; i++) - lower |= (Next() & 1) << (3 + i); - - upper = ((uint)(lower ^ pk.TID ^ pk.SID) & 0xFFF8) | (upper & 0x7); - pk.PID = upper << 16 | lower; - Span IVs = stackalloc int[6]; - MethodFinder.GetIVsInt32(IVs, Next(), Next()); - pk.SetIVs(IVs); - } - - public static void SetRandomPokeSpotPID(PKM pk, int nature, int gender, int ability, int slot) - { - var rnd = Util.Rand; - while (true) - { - var seed = rnd.Rand32(); - if (!MethodFinder.IsPokeSpotActivation(slot, seed, out _)) - continue; - - var rng = RNG.XDRNG; - var D = rng.Next(seed); // PID - var E = rng.Next(D); // PID - - pk.PID = (D & 0xFFFF0000) | E >> 16; - if (!IsValidCriteria4(pk, nature, ability, gender)) - continue; - - pk.SetRandomIVs(); - return; - } - } - - public static uint GetMG5ShinyPID(uint gval, uint av, int TID, int SID) - { - uint PID = (uint)((TID ^ SID ^ gval) << 16 | gval); - if ((PID & 0x10000) != av << 16) - PID ^= 0x10000; - return PID; - } - - public static uint GetPokeWalkerPID(int TID, int SID, uint nature, int gender, int gr) - { - if (nature >= 24) - nature = 0; - uint pid = (uint)((TID ^ SID) >> 8 ^ 0xFF) << 24; // the most significant byte of the PID is chosen so the Pokémon can never be shiny. - // Ensure nature is set to required nature without affecting shininess - pid += nature - (pid % 25); - - if (gr is 0 or >= 0xFE) // non-dual gender - return pid; - - // Ensure Gender is set to required gender without affecting other properties - // If Gender is modified, modify the ability if appropriate - - // either m/f - var pidGender = (pid & 0xFF) < gr ? 1 : 0; - if (gender == pidGender) - return pid; - - if (gender == 0) // Male - { - pid += (uint)((((gr - (pid & 0xFF)) / 25) + 1) * 25); - if ((nature & 1) != (pid & 1)) - pid += 25; - } - else - { - pid -= (uint)(((((pid & 0xFF) - gr) / 25) + 1) * 25); - if ((nature & 1) != (pid & 1)) - pid -= 25; - } - return pid; - } - - public static void SetValuesFromSeedMG5Shiny(PKM pk, uint seed) - { - var gv = seed >> 24; - var av = seed & 1; // arbitrary choice - pk.PID = GetMG5ShinyPID(gv, av, pk.TID, pk.SID); - SetRandomIVs(pk); - } - - public static void SetRandomWildPID(PKM pk, int gen, int nature, int ability, int gender, PIDType specific = PIDType.None) - { - if (specific == PIDType.Pokewalker) - { - SetRandomPIDPokewalker(pk, nature, gender); - return; - } - switch (gen) - { - case 3: - case 4: - SetRandomWildPID4(pk, nature, ability, gender, specific); - break; - case 5: - SetRandomWildPID5(pk, nature, ability, gender, specific); - break; - default: - SetRandomWildPID(pk, nature, ability, gender); - break; - } - } - - public static void SetRandomPIDPokewalker(PKM pk, int nature, int gender) - { - // Pokewalker PIDs cannot yield multiple abilities from the input nature-gender-trainerID. Disregard any ability request. - pk.Gender = gender; - do - { - pk.PID = GetPokeWalkerPID(pk.TID, pk.SID, (uint) nature, gender, pk.PersonalInfo.Gender); - } while (!pk.IsGenderValid()); - - pk.RefreshAbility((int) (pk.PID & 1)); - SetRandomIVs(pk); - } - - /// - /// Generates a and that are unrelated. - /// - /// Pokémon to modify. - /// Seed which is used for the . - private static void SetRandomPIDIV(PKM pkm, uint seed) - { - pkm.PID = seed; - SetRandomIVs(pkm); - } - - private static void SetRandomWildPID4(PKM pk, int nature, int ability, int gender, PIDType specific = PIDType.None) - { - pk.RefreshAbility(ability); - pk.Gender = gender; - var type = GetPIDType(pk, specific); - var method = GetGeneratorMethod(type); - - var rnd = Util.Rand; - while (true) - { - method(pk, rnd.Rand32()); - if (!IsValidCriteria4(pk, nature, ability, gender)) - continue; - return; - } - } - - private static bool IsValidCriteria4(PKM pk, int nature, int ability, int gender) - { - if (pk.GetSaneGender() != gender) - return false; - - if (pk.Nature != nature) - return false; - - if ((pk.PID & 1) != ability) - return false; - - return true; - } - - private static PIDType GetPIDType(PKM pk, PIDType specific) - { - if (specific != PIDType.None) - return specific; - if (pk.Version == 15) - return PIDType.CXD; - if (pk.Gen3 && pk.Species == (int)Species.Unown) - { - return Util.Rand.Next(3) switch - { - 1 => PIDType.Method_2_Unown, - 2 => PIDType.Method_4_Unown, - _ => PIDType.Method_1_Unown, - }; - } - - return PIDType.Method_1; - } - - private static void SetRandomWildPID5(PKM pk, int nature, int ability, int gender, PIDType specific = PIDType.None) - { - var tidbit = (pk.TID ^ pk.SID) & 1; - pk.RefreshAbility(ability); - pk.Gender = gender; - pk.Nature = nature; - - if (ability == 2) - ability = 0; - - var rnd = Util.Rand; - while (true) - { - uint seed = rnd.Rand32(); - if (specific == PIDType.G5MGShiny) - { - SetValuesFromSeedMG5Shiny(pk, seed); - seed = pk.PID; - } - else - { - var bitxor = (seed >> 31) ^ (seed & 1); - if (bitxor != tidbit) - seed ^= 1; - } - - if (((seed >> 16) & 1) != ability) - continue; - - pk.PID = seed; - if (pk.GetSaneGender() != gender) - continue; - - SetRandomIVs(pk); - return; - } - } - - private static void SetRandomWildPID(PKM pk, int nature, int ability, int gender) - { - pk.PID = Util.Rand32(); - pk.Nature = nature; - pk.Gender = gender; - pk.RefreshAbility(ability); - SetRandomIVs(pk); - } - - private static void SetRandomIVs(PKM pk) - { pk.SetRandomIVs(); + return; } } + + public static uint GetMG5ShinyPID(uint gval, uint av, int TID, int SID) + { + uint PID = (uint)(((TID ^ SID ^ gval) << 16) | gval); + if ((PID & 0x10000) != av << 16) + PID ^= 0x10000; + return PID; + } + + public static uint GetPokeWalkerPID(int TID, int SID, uint nature, int gender, int gr) + { + if (nature >= 24) + nature = 0; + uint pid = (uint)(((TID ^ SID) >> 8) ^ 0xFF) << 24; // the most significant byte of the PID is chosen so the Pokémon can never be shiny. + // Ensure nature is set to required nature without affecting shininess + pid += nature - (pid % 25); + + if (gr is 0 or >= 0xFE) // non-dual gender + return pid; + + // Ensure Gender is set to required gender without affecting other properties + // If Gender is modified, modify the ability if appropriate + + // either m/f + var pidGender = (pid & 0xFF) < gr ? 1 : 0; + if (gender == pidGender) + return pid; + + if (gender == 0) // Male + { + pid += (uint)((((gr - (pid & 0xFF)) / 25) + 1) * 25); + if ((nature & 1) != (pid & 1)) + pid += 25; + } + else + { + pid -= (uint)(((((pid & 0xFF) - gr) / 25) + 1) * 25); + if ((nature & 1) != (pid & 1)) + pid -= 25; + } + return pid; + } + + public static void SetValuesFromSeedMG5Shiny(PKM pk, uint seed) + { + var gv = seed >> 24; + var av = seed & 1; // arbitrary choice + pk.PID = GetMG5ShinyPID(gv, av, pk.TID, pk.SID); + SetRandomIVs(pk); + } + + public static void SetRandomWildPID(PKM pk, int gen, int nature, int ability, int gender, PIDType specific = PIDType.None) + { + if (specific == PIDType.Pokewalker) + { + SetRandomPIDPokewalker(pk, nature, gender); + return; + } + switch (gen) + { + case 3: + case 4: + SetRandomWildPID4(pk, nature, ability, gender, specific); + break; + case 5: + SetRandomWildPID5(pk, nature, ability, gender, specific); + break; + default: + SetRandomWildPID(pk, nature, ability, gender); + break; + } + } + + public static void SetRandomPIDPokewalker(PKM pk, int nature, int gender) + { + // Pokewalker PIDs cannot yield multiple abilities from the input nature-gender-trainerID. Disregard any ability request. + pk.Gender = gender; + do + { + pk.PID = GetPokeWalkerPID(pk.TID, pk.SID, (uint) nature, gender, pk.PersonalInfo.Gender); + } while (!pk.IsGenderValid()); + + pk.RefreshAbility((int) (pk.PID & 1)); + SetRandomIVs(pk); + } + + /// + /// Generates a and that are unrelated. + /// + /// Pokémon to modify. + /// Seed which is used for the . + private static void SetRandomPIDIV(PKM pk, uint seed) + { + pk.PID = seed; + SetRandomIVs(pk); + } + + private static void SetRandomWildPID4(PKM pk, int nature, int ability, int gender, PIDType specific = PIDType.None) + { + pk.RefreshAbility(ability); + pk.Gender = gender; + var type = GetPIDType(pk, specific); + var method = GetGeneratorMethod(type); + + var rnd = Util.Rand; + while (true) + { + method(pk, rnd.Rand32()); + if (!IsValidCriteria4(pk, nature, ability, gender)) + continue; + return; + } + } + + private static bool IsValidCriteria4(PKM pk, int nature, int ability, int gender) + { + if (pk.GetSaneGender() != gender) + return false; + + if (pk.Nature != nature) + return false; + + if ((pk.PID & 1) != ability) + return false; + + return true; + } + + private static PIDType GetPIDType(PKM pk, PIDType specific) + { + if (specific != PIDType.None) + return specific; + if (pk.Version == 15) + return PIDType.CXD; + if (pk.Gen3 && pk.Species == (int)Species.Unown) + { + return Util.Rand.Next(3) switch + { + 1 => PIDType.Method_2_Unown, + 2 => PIDType.Method_4_Unown, + _ => PIDType.Method_1_Unown, + }; + } + + return PIDType.Method_1; + } + + private static void SetRandomWildPID5(PKM pk, int nature, int ability, int gender, PIDType specific = PIDType.None) + { + var tidbit = (pk.TID ^ pk.SID) & 1; + pk.RefreshAbility(ability); + pk.Gender = gender; + pk.Nature = nature; + + if (ability == 2) + ability = 0; + + var rnd = Util.Rand; + while (true) + { + uint seed = rnd.Rand32(); + if (specific == PIDType.G5MGShiny) + { + SetValuesFromSeedMG5Shiny(pk, seed); + seed = pk.PID; + } + else + { + var bitxor = (seed >> 31) ^ (seed & 1); + if (bitxor != tidbit) + seed ^= 1; + } + + if (((seed >> 16) & 1) != ability) + continue; + + pk.PID = seed; + if (pk.GetSaneGender() != gender) + continue; + + SetRandomIVs(pk); + return; + } + } + + private static void SetRandomWildPID(PKM pk, int nature, int ability, int gender) + { + pk.PID = Util.Rand32(); + pk.Nature = nature; + pk.Gender = gender; + pk.RefreshAbility(ability); + SetRandomIVs(pk); + } + + private static void SetRandomIVs(PKM pk) + { + pk.SetRandomIVs(); + } } diff --git a/PKHeX.Core/Legality/RNG/PIDType.cs b/PKHeX.Core/Legality/RNG/PIDType.cs index bac5992a0..bbdd83e9e 100644 --- a/PKHeX.Core/Legality/RNG/PIDType.cs +++ b/PKHeX.Core/Legality/RNG/PIDType.cs @@ -1,215 +1,214 @@ using static PKHeX.Core.PIDType; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// PID + IV correlation. +/// +/// This is just a catch-all enumeration to describe the different correlations. +public enum PIDType { + /// No relationship between the PID and IVs + None, + + #region LCRNG + + /// Method 1 Variants (H1/J/K) + /// + Method_1, + + /// Method H2 + /// + Method_2, + + /// Method H3 + /// + Method_3, + + /// Method H4 + /// + Method_4, + + /// Method H1_Unown (FRLG) + /// + Method_1_Unown, + + /// Method H2_Unown (FRLG) + /// + Method_2_Unown, + + /// Method H3_Unown (FRLG) + /// + Method_3_Unown, + + /// Method H4_Unown (FRLG) + /// + Method_4_Unown, + + /// Method 1 Roamer (Gen3) + /// + Method_1_Roamer, + /// - /// PID + IV correlation. + /// Event Reversed Order PID restricted to 16bit Origin Seed /// - /// This is just a catch-all enumeration to describe the different correlations. - public enum PIDType - { - /// No relationship between the PID and IVs - None, + /// seed is clamped to 16bits. + BACD_R, - #region LCRNG + /// + /// Event Reversed Order PID without Origin Seed restrictions + /// + /// + BACD_U, - /// Method 1 Variants (H1/J/K) - /// - Method_1, + /// + /// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny. + /// + /// seed is clamped to 16bits. + BACD_R_A, - /// Method H2 - /// - Method_2, + /// + /// Event Reversed Order PID without Origin Seed restrictions, antishiny. + /// + /// + BACD_U_A, - /// Method H3 - /// - Method_3, + /// + /// Event Reversed Order PID restricted to 8bit Origin Seed, shiny + /// + /// seed is clamped to 16bits. + BACD_R_S, - /// Method H4 - /// - Method_4, + /// + /// Event Reversed Order PID without Origin Seed restrictions, shiny + /// + /// + BACD_U_S, - /// Method H1_Unown (FRLG) - /// - Method_1_Unown, + /// + /// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny (nyx) + /// + /// seed is clamped to 16bits. + BACD_R_AX, - /// Method H2_Unown (FRLG) - /// - Method_2_Unown, + /// + /// Event Reversed Order PID without Origin Seed restrictions, antishiny (nyx) + /// + /// + BACD_U_AX, - /// Method H3_Unown (FRLG) - /// - Method_3_Unown, + /// + /// Generation 4 Cute Charm PID, which is forced to an 8 bit PID value based on the gender & gender ratio value. + /// + /// + CuteCharm, - /// Method H4_Unown (FRLG) - /// - Method_4_Unown, + /// + /// Generation 4 Chained Shiny + /// + /// + ChainShiny, - /// Method 1 Roamer (Gen3) - /// - Method_1_Roamer, + #endregion - /// - /// Event Reversed Order PID restricted to 16bit Origin Seed - /// - /// seed is clamped to 16bits. - BACD_R, + #region XDRNG - /// - /// Event Reversed Order PID without Origin Seed restrictions - /// - /// - BACD_U, + /// + /// Generation 3 PID+IV correlation. + /// + /// + CXD, - /// - /// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny. - /// - /// seed is clamped to 16bits. - BACD_R_A, + /// + /// Generation 3 PID+IV correlation that was rerolled because it was shiny. + /// + /// + CXDAnti, - /// - /// Event Reversed Order PID without Origin Seed restrictions, antishiny. - /// - /// - BACD_U_A, + /// + /// Generation 3 PID+IV which is created immediately after the TID and SID RNG calls. + /// + /// . The second starter is created after the first starter, with the same TID and SID. + CXD_ColoStarter, - /// - /// Event Reversed Order PID restricted to 8bit Origin Seed, shiny - /// - /// seed is clamped to 16bits. - BACD_R_S, + /// + /// Generation 3 Pokémon Channel Jirachi + /// + /// + Channel, - /// - /// Event Reversed Order PID without Origin Seed restrictions, shiny - /// - /// - BACD_U_S, + /// + /// Generation 3 PokeSpot PID + /// + /// + PokeSpot, - /// - /// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny (nyx) - /// - /// seed is clamped to 16bits. - BACD_R_AX, + #endregion - /// - /// Event Reversed Order PID without Origin Seed restrictions, antishiny (nyx) - /// - /// - BACD_U_AX, + #region ARNG - /// - /// Generation 4 Cute Charm PID, which is forced to an 8 bit PID value based on the gender & gender ratio value. - /// - /// - CuteCharm, + /// + /// Generation 4 Mystery Gift Anti-Shiny + /// + /// + G4MGAntiShiny, - /// - /// Generation 4 Chained Shiny - /// - /// - ChainShiny, + #endregion - #endregion + #region Formulaic - #region XDRNG + /// + /// Generation 5 Mystery Gift Shiny + /// + /// Formulaic based on TID, SID, and Gender bytes. + /// Unrelated to IVs + G5MGShiny, - /// - /// Generation 3 PID+IV correlation. - /// - /// - CXD, + /// + /// Generation 4 Pokewalker PID, never Shiny. + /// + /// Formulaic based on TID, SID, and Gender bytes. + /// Unrelated to IVs + Pokewalker, - /// - /// Generation 3 PID+IV correlation that was rerolled because it was shiny. - /// - /// - CXDAnti, + /// + /// Generation 8 Raid PID + /// + /// Formulaic based on PID & EC values from a 64bit-seed. + Raid8, - /// - /// Generation 3 PID+IV which is created immediately after the TID and SID RNG calls. - /// - /// . The second starter is created after the first starter, with the same TID and SID. - CXD_ColoStarter, + /// + /// Generation 8 Overworld Spawn PID + /// + /// Formulaic based on PID & EC values from a 32bit-seed. + Overworld8, - /// - /// Generation 3 Pokémon Channel Jirachi - /// - /// - Channel, + /// + /// Generation 8b Roaming Pokemon PID + /// + /// Formulaic based on EC value = 32bit-seed. + Roaming8b, - /// - /// Generation 3 PokeSpot PID - /// - /// - PokeSpot, - - #endregion - - #region ARNG - - /// - /// Generation 4 Mystery Gift Anti-Shiny - /// - /// - G4MGAntiShiny, - - #endregion - - #region Formulaic - - /// - /// Generation 5 Mystery Gift Shiny - /// - /// Formulaic based on TID, SID, and Gender bytes. - /// Unrelated to IVs - G5MGShiny, - - /// - /// Generation 4 Pokewalker PID, never Shiny. - /// - /// Formulaic based on TID, SID, and Gender bytes. - /// Unrelated to IVs - Pokewalker, - - /// - /// Generation 8 Raid PID - /// - /// Formulaic based on PID & EC values from a 64bit-seed. - Raid8, - - /// - /// Generation 8 Overworld Spawn PID - /// - /// Formulaic based on PID & EC values from a 32bit-seed. - Overworld8, - - /// - /// Generation 8b Roaming Pokemon PID - /// - /// Formulaic based on EC value = 32bit-seed. - Roaming8b, - - #endregion - } - - public static class PIDTypeExtensions - { - public static RNGType GetRNGType(this PIDType type) => type switch - { - 0 => RNGType.None, - <= ChainShiny => RNGType.LCRNG, - <= PokeSpot => RNGType.XDRNG, - G4MGAntiShiny => RNGType.ARNG, - _ => RNGType.None, - }; - - public static bool IsReversedPID(this PIDType type) => type switch - { - CXD or CXDAnti => true, - BACD_R or BACD_R_A or BACD_R_S => true, - BACD_U or BACD_U_A or BACD_U_S => true, - Method_1_Unown or Method_2_Unown or Method_3_Unown or Method_4_Unown => true, - _ => false, - }; - } + #endregion +} + +public static class PIDTypeExtensions +{ + public static RNGType GetRNGType(this PIDType type) => type switch + { + 0 => RNGType.None, + <= ChainShiny => RNGType.LCRNG, + <= PokeSpot => RNGType.XDRNG, + G4MGAntiShiny => RNGType.ARNG, + _ => RNGType.None, + }; + + public static bool IsReversedPID(this PIDType type) => type switch + { + CXD or CXDAnti => true, + BACD_R or BACD_R_A or BACD_R_S => true, + BACD_U or BACD_U_A or BACD_U_S => true, + Method_1_Unown or Method_2_Unown or Method_3_Unown or Method_4_Unown => true, + _ => false, + }; } diff --git a/PKHeX.Core/Legality/RNG/RaidRNG.cs b/PKHeX.Core/Legality/RNG/RaidRNG.cs index 149cfed37..f0306e7cd 100644 --- a/PKHeX.Core/Legality/RNG/RaidRNG.cs +++ b/PKHeX.Core/Legality/RNG/RaidRNG.cs @@ -1,339 +1,338 @@ using System; using System.Runtime.CompilerServices; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for Generating and Verifying Gen8 Raid Templates against PKM data. +/// +public static class RaidRNG { - /// - /// Logic for Generating and Verifying Gen8 Raid Templates against PKM data. - /// - public static class RaidRNG + public static bool Verify(this T raid, PK8 pk8, ulong seed) where T: EncounterStatic8Nest { - public static bool Verify(this T raid, PK8 pk8, ulong seed) where T: EncounterStatic8Nest - { - var pi = PersonalTable.SWSH.GetFormEntry(raid.Species, raid.Form); - var ratio = pi.Gender; - var abil = RemapAbilityToParam(raid.Ability); + var pi = PersonalTable.SWSH.GetFormEntry(raid.Species, raid.Form); + var ratio = pi.Gender; + var abil = RemapAbilityToParam(raid.Ability); - Span IVs = stackalloc int[6]; - LoadIVs(raid, IVs); - return Verify(pk8, seed, IVs, raid.FlawlessIVCount, abil, ratio); + Span IVs = stackalloc int[6]; + LoadIVs(raid, IVs); + return Verify(pk8, seed, IVs, raid.FlawlessIVCount, abil, ratio); + } + + public static void ApplyDetailsTo(this T raid, PK8 pk8, ulong seed) where T : EncounterStatic8Nest + { + // Ensure the species-form is set correctly (nature) + pk8.Species = raid.Species; + pk8.Form = raid.Form; + var pi = PersonalTable.SWSH.GetFormEntry(raid.Species, raid.Form); + var ratio = pi.Gender; + var abil = RemapAbilityToParam(raid.Ability); + + Span IVs = stackalloc int[6]; + LoadIVs(raid, IVs); + ApplyDetailsTo(pk8, seed, IVs, raid.FlawlessIVCount, abil, ratio); + } + + private static void LoadIVs(T raid, Span IVs) where T : EncounterStatic8Nest + { + if (raid.IVs.Count == 0) + { + IVs.Fill(-1); + } + else + { + // Template stores with speed in middle (standard), convert for generator purpose. + var value = raid.IVs; + IVs[5] = value[3]; // spe + IVs[4] = value[5]; // spd + IVs[3] = value[4]; // spa + IVs[2] = value[2]; // def + IVs[1] = value[1]; // atk + IVs[0] = value[0]; // hp + } + } + + private static int RemapAbilityToParam(AbilityPermission a) => a switch + { + AbilityPermission.Any12H => 254, + AbilityPermission.Any12 => 255, + _ => a.GetSingleValue(), + }; + + private static bool Verify(PKM pk, ulong seed, Span ivs, int iv_count, int ability_param, int gender_ratio, sbyte nature_param = -1, Shiny shiny = Shiny.Random) + { + var rng = new Xoroshiro128Plus(seed); + var ec = (uint)rng.NextInt(); + if (ec != pk.EncryptionConstant) + return false; + + uint pid; + bool isShiny; + if (shiny == Shiny.Random) // let's decide if it's shiny or not! + { + var trID = (uint)rng.NextInt(); + pid = (uint)rng.NextInt(); + isShiny = GetShinyXor(pid, trID) < 16; + } + else + { + // no need to calculate a fake trainer + pid = (uint)rng.NextInt(); + isShiny = shiny == Shiny.Always; } - public static void ApplyDetailsTo(this T raid, PK8 pk8, ulong seed) where T : EncounterStatic8Nest - { - // Ensure the species-form is set correctly (nature) - pk8.Species = raid.Species; - pk8.Form = raid.Form; - var pi = PersonalTable.SWSH.GetFormEntry(raid.Species, raid.Form); - var ratio = pi.Gender; - var abil = RemapAbilityToParam(raid.Ability); + ForceShinyState(pk, isShiny, ref pid); - Span IVs = stackalloc int[6]; - LoadIVs(raid, IVs); - ApplyDetailsTo(pk8, seed, IVs, raid.FlawlessIVCount, abil, ratio); + if (pk.PID != pid) + return false; + + const int UNSET = -1; + const int MAX = 31; + for (int i = ivs.Count(MAX); i < iv_count; i++) + { + int index = (int)rng.NextInt(6); + while (ivs[index] != UNSET) + index = (int)rng.NextInt(6); + ivs[index] = MAX; } - private static void LoadIVs(T raid, Span IVs) where T : EncounterStatic8Nest + for (int i = 0; i < 6; i++) { - if (raid.IVs.Count == 0) + if (ivs[i] == UNSET) + ivs[i] = (int)rng.NextInt(32); + } + + if (pk.IV_HP != ivs[0]) + return false; + if (pk.IV_ATK != ivs[1]) + return false; + if (pk.IV_DEF != ivs[2]) + return false; + if (pk.IV_SPA != ivs[3]) + return false; + if (pk.IV_SPD != ivs[4]) + return false; + if (pk.IV_SPE != ivs[5]) + return false; + + int abil = ability_param switch + { + 254 => (int)rng.NextInt(3), + 255 => (int)rng.NextInt(2), + _ => ability_param, + }; + abil <<= 1; // 1/2/4 + + var current = pk.AbilityNumber; + if (abil == 4) + { + if (current != 4) + return false; + } + // else, for things that were made Hidden Ability, defer to Ability Checks (Ability Patch) + + switch (gender_ratio) + { + case PersonalInfo.RatioMagicGenderless when pk.Gender != 2: + if (pk.Gender != 2) + return false; + break; + case PersonalInfo.RatioMagicFemale when pk.Gender != 1: + if (pk.Gender != 1) + return false; + break; + case PersonalInfo.RatioMagicMale: + if (pk.Gender != 0) + return false; + break; + default: + var gender = (int)rng.NextInt(252) + 1 < gender_ratio ? 1 : 0; + if (pk.Gender != gender) + return false; + break; + } + + if (nature_param == -1) + { + if (pk.Species == (int) Species.Toxtricity && pk.Form == 0) { - IVs.Fill(-1); + var table = Nature0; + var choice = table[rng.NextInt((uint)table.Length)]; + if (pk.Nature != choice) + return false; + } + else if (pk.Species == (int) Species.Toxtricity && pk.Form == 1) + { + var table = Nature1; + var choice = table[rng.NextInt((uint)table.Length)]; + if (pk.Nature != choice) + return false; } else { - // Template stores with speed in middle (standard), convert for generator purpose. - var value = raid.IVs; - IVs[5] = value[3]; // spe - IVs[4] = value[5]; // spd - IVs[3] = value[4]; // spa - IVs[2] = value[2]; // def - IVs[1] = value[1]; // atk - IVs[0] = value[0]; // hp + var nature = (int)rng.NextInt(25); + if (pk.Nature != nature) + return false; } } - - private static int RemapAbilityToParam(AbilityPermission a) => a switch + else { - AbilityPermission.Any12H => 254, - AbilityPermission.Any12 => 255, - _ => a.GetSingleValue(), + if (pk.Nature != nature_param) + return false; + } + + if (pk is IScaledSize s) + { + var height = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); + if (s.HeightScalar != height) + return false; + var weight = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); + if (s.WeightScalar != weight) + return false; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ForceShinyState(PKM pk, bool isShiny, ref uint pid) + { + if (isShiny) + { + if (!GetIsShiny(pk.TID, pk.SID, pid)) + pid = GetShinyPID(pk.TID, pk.SID, pid, 0); + } + else + { + if (GetIsShiny(pk.TID, pk.SID, pid)) + pid ^= 0x1000_0000; + } + } + + private static bool ApplyDetailsTo(PKM pk, ulong seed, Span ivs, int iv_count, int ability_param, int gender_ratio, sbyte nature_param = -1, Shiny shiny = Shiny.Random) + { + var rng = new Xoroshiro128Plus(seed); + pk.EncryptionConstant = (uint)rng.NextInt(); + + uint pid; + bool isShiny; + if (shiny == Shiny.Random) // let's decide if it's shiny or not! + { + var trID = (uint)rng.NextInt(); + pid = (uint)rng.NextInt(); + isShiny = GetShinyXor(pid, trID) < 16; + } + else + { + // no need to calculate a fake trainer + pid = (uint)rng.NextInt(); + isShiny = shiny == Shiny.Always; + } + + if (isShiny) + { + if (!GetIsShiny(pk.TID, pk.SID, pid)) + pid = GetShinyPID(pk.TID, pk.SID, pid, 0); + } + else + { + if (GetIsShiny(pk.TID, pk.SID, pid)) + pid ^= 0x1000_0000; + } + + pk.PID = pid; + + const int UNSET = -1; + const int MAX = 31; + for (int i = ivs.Count(MAX); i < iv_count; i++) + { + int index = (int)rng.NextInt(6); + while (ivs[index] != UNSET) + index = (int)rng.NextInt(6); + ivs[index] = MAX; + } + + for (int i = 0; i < 6; i++) + { + if (ivs[i] == UNSET) + ivs[i] = (int)rng.NextInt(32); + } + + pk.IV_HP = ivs[0]; + pk.IV_ATK = ivs[1]; + pk.IV_DEF = ivs[2]; + pk.IV_SPA = ivs[3]; + pk.IV_SPD = ivs[4]; + pk.IV_SPE = ivs[5]; + + int abil = ability_param switch + { + 254 => (int)rng.NextInt(3), + 255 => (int)rng.NextInt(2), + _ => ability_param, + }; + pk.RefreshAbility(abil); + + pk.Gender = gender_ratio switch + { + PersonalInfo.RatioMagicGenderless => 2, + PersonalInfo.RatioMagicFemale => 1, + PersonalInfo.RatioMagicMale => 0, + _ => (int) rng.NextInt(252) + 1 < gender_ratio ? 1 : 0, }; - private static bool Verify(PKM pk, ulong seed, Span ivs, int iv_count, int ability_param, int gender_ratio, sbyte nature_param = -1, Shiny shiny = Shiny.Random) + int nature; + if (nature_param == -1) { - var rng = new Xoroshiro128Plus(seed); - var ec = (uint)rng.NextInt(); - if (ec != pk.EncryptionConstant) - return false; - - uint pid; - bool isShiny; - if (shiny == Shiny.Random) // let's decide if it's shiny or not! + if (pk.Species == (int)Species.Toxtricity && pk.Form == 0) { - var trID = (uint)rng.NextInt(); - pid = (uint)rng.NextInt(); - isShiny = GetShinyXor(pid, trID) < 16; + var table = Nature0; + nature = table[rng.NextInt((uint)table.Length)]; + } + else if (pk.Species == (int)Species.Toxtricity && pk.Form == 1) + { + var table = Nature1; + nature = table[rng.NextInt((uint)table.Length)]; } else { - // no need to calculate a fake trainer - pid = (uint)rng.NextInt(); - isShiny = shiny == Shiny.Always; + nature = (int)rng.NextInt(25); } - - ForceShinyState(pk, isShiny, ref pid); - - if (pk.PID != pid) - return false; - - const int UNSET = -1; - const int MAX = 31; - for (int i = ivs.Count(MAX); i < iv_count; i++) - { - int index = (int)rng.NextInt(6); - while (ivs[index] != UNSET) - index = (int)rng.NextInt(6); - ivs[index] = MAX; - } - - for (int i = 0; i < 6; i++) - { - if (ivs[i] == UNSET) - ivs[i] = (int)rng.NextInt(32); - } - - if (pk.IV_HP != ivs[0]) - return false; - if (pk.IV_ATK != ivs[1]) - return false; - if (pk.IV_DEF != ivs[2]) - return false; - if (pk.IV_SPA != ivs[3]) - return false; - if (pk.IV_SPD != ivs[4]) - return false; - if (pk.IV_SPE != ivs[5]) - return false; - - int abil = ability_param switch - { - 254 => (int)rng.NextInt(3), - 255 => (int)rng.NextInt(2), - _ => ability_param, - }; - abil <<= 1; // 1/2/4 - - var current = pk.AbilityNumber; - if (abil == 4) - { - if (current != 4) - return false; - } - // else, for things that were made Hidden Ability, defer to Ability Checks (Ability Patch) - - switch (gender_ratio) - { - case PersonalInfo.RatioMagicGenderless when pk.Gender != 2: - if (pk.Gender != 2) - return false; - break; - case PersonalInfo.RatioMagicFemale when pk.Gender != 1: - if (pk.Gender != 1) - return false; - break; - case PersonalInfo.RatioMagicMale: - if (pk.Gender != 0) - return false; - break; - default: - var gender = (int)rng.NextInt(252) + 1 < gender_ratio ? 1 : 0; - if (pk.Gender != gender) - return false; - break; - } - - if (nature_param == -1) - { - if (pk.Species == (int) Species.Toxtricity && pk.Form == 0) - { - var table = Nature0; - var choice = table[rng.NextInt((uint)table.Length)]; - if (pk.Nature != choice) - return false; - } - else if (pk.Species == (int) Species.Toxtricity && pk.Form == 1) - { - var table = Nature1; - var choice = table[rng.NextInt((uint)table.Length)]; - if (pk.Nature != choice) - return false; - } - else - { - var nature = (int)rng.NextInt(25); - if (pk.Nature != nature) - return false; - } - } - else - { - if (pk.Nature != nature_param) - return false; - } - - if (pk is IScaledSize s) - { - var height = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); - if (s.HeightScalar != height) - return false; - var weight = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); - if (s.WeightScalar != weight) - return false; - } - - return true; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ForceShinyState(PKM pk, bool isShiny, ref uint pid) + else { - if (isShiny) - { - if (!GetIsShiny(pk.TID, pk.SID, pid)) - pid = GetShinyPID(pk.TID, pk.SID, pid, 0); - } - else - { - if (GetIsShiny(pk.TID, pk.SID, pid)) - pid ^= 0x1000_0000; - } + nature = nature_param; } - private static bool ApplyDetailsTo(PKM pk, ulong seed, Span ivs, int iv_count, int ability_param, int gender_ratio, sbyte nature_param = -1, Shiny shiny = Shiny.Random) + pk.StatNature = pk.Nature = nature; + + if (pk is IScaledSize s) { - var rng = new Xoroshiro128Plus(seed); - pk.EncryptionConstant = (uint)rng.NextInt(); - - uint pid; - bool isShiny; - if (shiny == Shiny.Random) // let's decide if it's shiny or not! - { - var trID = (uint)rng.NextInt(); - pid = (uint)rng.NextInt(); - isShiny = GetShinyXor(pid, trID) < 16; - } - else - { - // no need to calculate a fake trainer - pid = (uint)rng.NextInt(); - isShiny = shiny == Shiny.Always; - } - - if (isShiny) - { - if (!GetIsShiny(pk.TID, pk.SID, pid)) - pid = GetShinyPID(pk.TID, pk.SID, pid, 0); - } - else - { - if (GetIsShiny(pk.TID, pk.SID, pid)) - pid ^= 0x1000_0000; - } - - pk.PID = pid; - - const int UNSET = -1; - const int MAX = 31; - for (int i = ivs.Count(MAX); i < iv_count; i++) - { - int index = (int)rng.NextInt(6); - while (ivs[index] != UNSET) - index = (int)rng.NextInt(6); - ivs[index] = MAX; - } - - for (int i = 0; i < 6; i++) - { - if (ivs[i] == UNSET) - ivs[i] = (int)rng.NextInt(32); - } - - pk.IV_HP = ivs[0]; - pk.IV_ATK = ivs[1]; - pk.IV_DEF = ivs[2]; - pk.IV_SPA = ivs[3]; - pk.IV_SPD = ivs[4]; - pk.IV_SPE = ivs[5]; - - int abil = ability_param switch - { - 254 => (int)rng.NextInt(3), - 255 => (int)rng.NextInt(2), - _ => ability_param, - }; - pk.RefreshAbility(abil); - - pk.Gender = gender_ratio switch - { - PersonalInfo.RatioMagicGenderless => 2, - PersonalInfo.RatioMagicFemale => 1, - PersonalInfo.RatioMagicMale => 0, - _ => (int) rng.NextInt(252) + 1 < gender_ratio ? 1 : 0, - }; - - int nature; - if (nature_param == -1) - { - if (pk.Species == (int)Species.Toxtricity && pk.Form == 0) - { - var table = Nature0; - nature = table[rng.NextInt((uint)table.Length)]; - } - else if (pk.Species == (int)Species.Toxtricity && pk.Form == 1) - { - var table = Nature1; - nature = table[rng.NextInt((uint)table.Length)]; - } - else - { - nature = (int)rng.NextInt(25); - } - } - else - { - nature = nature_param; - } - - pk.StatNature = pk.Nature = nature; - - if (pk is IScaledSize s) - { - var height = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); - var weight = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); - s.HeightScalar = (byte)height; - s.WeightScalar = (byte)weight; - } - - return true; + var height = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); + var weight = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); + s.HeightScalar = (byte)height; + s.WeightScalar = (byte)weight; } - private static uint GetShinyPID(int tid, int sid, uint pid, int type) - { - return (uint) (((tid ^ sid ^ (pid & 0xFFFF) ^ type) << 16) | (pid & 0xFFFF)); - } - - private static bool GetIsShiny(int tid, int sid, uint pid) - { - return GetShinyXor(pid, (uint) ((sid << 16) | tid)) < 16; - } - - private static uint GetShinyXor(uint pid, uint oid) - { - var xor = pid ^ oid; - return (xor ^ (xor >> 16)) & 0xFFFF; - } - - private static readonly byte[] Nature0 = {3, 4, 2, 8, 9, 19, 22, 11, 13, 14, 0, 6, 24}; - private static readonly byte[] Nature1 = {1, 5, 7, 10, 12, 15, 16, 17, 18, 20, 21, 23}; + return true; } + + private static uint GetShinyPID(int tid, int sid, uint pid, int type) + { + return (uint) (((tid ^ sid ^ (pid & 0xFFFF) ^ type) << 16) | (pid & 0xFFFF)); + } + + private static bool GetIsShiny(int tid, int sid, uint pid) + { + return GetShinyXor(pid, (uint) ((sid << 16) | tid)) < 16; + } + + private static uint GetShinyXor(uint pid, uint oid) + { + var xor = pid ^ oid; + return (xor ^ (xor >> 16)) & 0xFFFF; + } + + private static readonly byte[] Nature0 = {3, 4, 2, 8, 9, 19, 22, 11, 13, 14, 0, 6, 24}; + private static readonly byte[] Nature1 = {1, 5, 7, 10, 12, 15, 16, 17, 18, 20, 21, 23}; } diff --git a/PKHeX.Core/Legality/RNG/Roaming8bRNG.cs b/PKHeX.Core/Legality/RNG/Roaming8bRNG.cs index b8ede8ef2..ca901cea0 100644 --- a/PKHeX.Core/Legality/RNG/Roaming8bRNG.cs +++ b/PKHeX.Core/Legality/RNG/Roaming8bRNG.cs @@ -1,273 +1,272 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains logic for the Generation 8b (BD/SP) roaming spawns. +/// +/// +/// Roaming encounters use the pokemon's 32-bit as RNG seed. +/// +public static class Roaming8bRNG { - /// - /// Contains logic for the Generation 8b (BD/SP) roaming spawns. - /// - /// - /// Roaming encounters use the pokemon's 32-bit as RNG seed. - /// - public static class Roaming8bRNG + private const int UNSET = -1; + + public static void ApplyDetails(PKM pk, EncounterCriteria criteria, Shiny shiny = Shiny.FixedValue, int flawless = -1) { - private const int UNSET = -1; + if (shiny == Shiny.FixedValue) + shiny = criteria.Shiny is Shiny.Random or Shiny.Never ? Shiny.Never : Shiny.Always; + if (flawless == -1) + flawless = 0; - public static void ApplyDetails(PKM pk, EncounterCriteria criteria, Shiny shiny = Shiny.FixedValue, int flawless = -1) + int ctr = 0; + const int maxAttempts = 50_000; + var rnd = Util.Rand; + do { - if (shiny == Shiny.FixedValue) - shiny = criteria.Shiny is Shiny.Random or Shiny.Never ? Shiny.Never : Shiny.Always; - if (flawless == -1) - flawless = 0; - - int ctr = 0; - const int maxAttempts = 50_000; - var rnd = Util.Rand; - do - { - var seed = rnd.Rand32(); - if (seed == int.MaxValue) - continue; // Unity's Rand is [int.MinValue, int.MaxValue) - if (TryApplyFromSeed(pk, criteria, shiny, flawless, seed)) - return; - } while (++ctr != maxAttempts); - TryApplyFromSeed(pk, EncounterCriteria.Unrestricted, shiny, flawless, rnd.Rand32()); - } - - private static bool TryApplyFromSeed(PKM pk, EncounterCriteria criteria, Shiny shiny, int flawless, uint seed) - { - var xoro = new Xoroshiro128Plus8b(seed); - - // Encryption Constant - pk.EncryptionConstant = seed; - - // PID - var fakeTID = xoro.NextUInt(); // fakeTID - var pid = xoro.NextUInt(); - pid = GetRevisedPID(fakeTID, pid, pk); - if (shiny == Shiny.Never) - { - if (GetIsShiny(pk.TID, pk.SID, pid)) - return false; - } - else if (shiny != Shiny.Random) - { - if (!GetIsShiny(pk.TID, pk.SID, pid)) - return false; - - if (shiny == Shiny.AlwaysSquare && pk.ShinyXor != 0) - return false; - if (shiny == Shiny.AlwaysStar && pk.ShinyXor == 0) - return false; - } - pk.PID = pid; - - // Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless. - Span ivs = stackalloc [] { UNSET, UNSET, UNSET, UNSET, UNSET, UNSET }; - const int MAX = 31; - var determined = 0; - while (determined < flawless) - { - var idx = (int)xoro.NextUInt(6); - if (ivs[idx] != UNSET) - continue; - ivs[idx] = 31; - determined++; - } - - for (var i = 0; i < ivs.Length; i++) - { - if (ivs[i] == UNSET) - ivs[i] = (int)xoro.NextUInt(MAX + 1); - } - - if (!criteria.IsIVsCompatible(ivs, 8)) - return false; - - pk.IV_HP = ivs[0]; - pk.IV_ATK = ivs[1]; - pk.IV_DEF = ivs[2]; - pk.IV_SPA = ivs[3]; - pk.IV_SPD = ivs[4]; - pk.IV_SPE = ivs[5]; - - // Ability - pk.SetAbilityIndex((int)xoro.NextUInt(2)); - - // Remainder - var scale = (IScaledSize)pk; - scale.HeightScalar = (byte)((int)xoro.NextUInt(0x81) + (int)xoro.NextUInt(0x80)); - scale.WeightScalar = (byte)((int)xoro.NextUInt(0x81) + (int)xoro.NextUInt(0x80)); - - return true; - } - - public static bool ValidateRoamingEncounter(PKM pk, Shiny shiny = Shiny.Random, int flawless = 0) - { - var seed = pk.EncryptionConstant; + var seed = rnd.Rand32(); if (seed == int.MaxValue) - return false; // Unity's Rand is [int.MinValue, int.MaxValue) - var xoro = new Xoroshiro128Plus8b(seed); + continue; // Unity's Rand is [int.MinValue, int.MaxValue) + if (TryApplyFromSeed(pk, criteria, shiny, flawless, seed)) + return; + } while (++ctr != maxAttempts); + TryApplyFromSeed(pk, EncounterCriteria.Unrestricted, shiny, flawless, rnd.Rand32()); + } - // Check PID - var fakeTID = xoro.NextUInt(); // fakeTID - var pid = xoro.NextUInt(); - pid = GetRevisedPID(fakeTID, pid, pk); - if (pk.PID != pid) + private static bool TryApplyFromSeed(PKM pk, EncounterCriteria criteria, Shiny shiny, int flawless, uint seed) + { + var xoro = new Xoroshiro128Plus8b(seed); + + // Encryption Constant + pk.EncryptionConstant = seed; + + // PID + var fakeTID = xoro.NextUInt(); // fakeTID + var pid = xoro.NextUInt(); + pid = GetRevisedPID(fakeTID, pid, pk); + if (shiny == Shiny.Never) + { + if (GetIsShiny(pk.TID, pk.SID, pid)) + return false; + } + else if (shiny != Shiny.Random) + { + if (!GetIsShiny(pk.TID, pk.SID, pid)) return false; - // Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless. - Span ivs = stackalloc [] { UNSET, UNSET, UNSET, UNSET, UNSET, UNSET }; - - var determined = 0; - while (determined < flawless) - { - var idx = (int)xoro.NextUInt(6); - if (ivs[idx] != UNSET) - continue; - ivs[idx] = 31; - determined++; - } - - for (var i = 0; i < ivs.Length; i++) - { - if (ivs[i] == UNSET) - ivs[i] = (int)xoro.NextUInt(31 + 1); - } - - if (ivs[0] != pk.IV_HP ) return false; - if (ivs[1] != pk.IV_ATK) return false; - if (ivs[2] != pk.IV_DEF) return false; - if (ivs[3] != pk.IV_SPA) return false; - if (ivs[4] != pk.IV_SPD) return false; - if (ivs[5] != pk.IV_SPE) return false; - - // Don't check Hidden ability, as roaming encounters are 1/2 only. - if (pk.AbilityNumber != (1 << (int)xoro.NextUInt(2))) + if (shiny == Shiny.AlwaysSquare && pk.ShinyXor != 0) return false; - - return GetIsMatchEnd(pk, xoro) || GetIsMatchEndWithCuteCharm(pk, xoro) || GetIsMatchEndWithSynchronize(pk, xoro); - } - - private static bool GetIsMatchEnd(PKM pk, Xoroshiro128Plus8b xoro) - { - // Check that gender matches - var genderRatio = PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form).Gender; - if (genderRatio == PersonalInfo.RatioMagicGenderless) - { - if (pk.Gender != (int)Gender.Genderless) - return false; - } - else if (genderRatio == PersonalInfo.RatioMagicMale) - { - if (pk.Gender != (int)Gender.Male) - return false; - } - else if (genderRatio == PersonalInfo.RatioMagicFemale) - { - if (pk.Gender != (int)Gender.Female) - return false; - } - else - { - if (pk.Gender != (((int)xoro.NextUInt(253) + 1 < genderRatio) ? 1 : 0)) - return false; - } - - // Check that the nature matches - if (pk.Nature != (int)xoro.NextUInt(25)) + if (shiny == Shiny.AlwaysStar && pk.ShinyXor == 0) return false; + } + pk.PID = pid; - return GetIsHeightWeightMatch(pk, xoro); + // Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless. + Span ivs = stackalloc [] { UNSET, UNSET, UNSET, UNSET, UNSET, UNSET }; + const int MAX = 31; + var determined = 0; + while (determined < flawless) + { + var idx = (int)xoro.NextUInt(6); + if (ivs[idx] != UNSET) + continue; + ivs[idx] = 31; + determined++; } - private static bool GetIsMatchEndWithCuteCharm(PKM pk, Xoroshiro128Plus8b xoro) + for (var i = 0; i < ivs.Length; i++) { - // Check that gender matches - // Assume that the gender is a match due to cute charm. + if (ivs[i] == UNSET) + ivs[i] = (int)xoro.NextUInt(MAX + 1); + } - // Check that the nature matches - if (pk.Nature != (int)xoro.NextUInt(25)) + if (!criteria.IsIVsCompatible(ivs, 8)) + return false; + + pk.IV_HP = ivs[0]; + pk.IV_ATK = ivs[1]; + pk.IV_DEF = ivs[2]; + pk.IV_SPA = ivs[3]; + pk.IV_SPD = ivs[4]; + pk.IV_SPE = ivs[5]; + + // Ability + pk.SetAbilityIndex((int)xoro.NextUInt(2)); + + // Remainder + var scale = (IScaledSize)pk; + scale.HeightScalar = (byte)((int)xoro.NextUInt(0x81) + (int)xoro.NextUInt(0x80)); + scale.WeightScalar = (byte)((int)xoro.NextUInt(0x81) + (int)xoro.NextUInt(0x80)); + + return true; + } + + public static bool ValidateRoamingEncounter(PKM pk, Shiny shiny = Shiny.Random, int flawless = 0) + { + var seed = pk.EncryptionConstant; + if (seed == int.MaxValue) + return false; // Unity's Rand is [int.MinValue, int.MaxValue) + var xoro = new Xoroshiro128Plus8b(seed); + + // Check PID + var fakeTID = xoro.NextUInt(); // fakeTID + var pid = xoro.NextUInt(); + pid = GetRevisedPID(fakeTID, pid, pk); + if (pk.PID != pid) + return false; + + // Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless. + Span ivs = stackalloc [] { UNSET, UNSET, UNSET, UNSET, UNSET, UNSET }; + + var determined = 0; + while (determined < flawless) + { + var idx = (int)xoro.NextUInt(6); + if (ivs[idx] != UNSET) + continue; + ivs[idx] = 31; + determined++; + } + + for (var i = 0; i < ivs.Length; i++) + { + if (ivs[i] == UNSET) + ivs[i] = (int)xoro.NextUInt(31 + 1); + } + + if (ivs[0] != pk.IV_HP ) return false; + if (ivs[1] != pk.IV_ATK) return false; + if (ivs[2] != pk.IV_DEF) return false; + if (ivs[3] != pk.IV_SPA) return false; + if (ivs[4] != pk.IV_SPD) return false; + if (ivs[5] != pk.IV_SPE) return false; + + // Don't check Hidden ability, as roaming encounters are 1/2 only. + if (pk.AbilityNumber != (1 << (int)xoro.NextUInt(2))) + return false; + + return GetIsMatchEnd(pk, xoro) || GetIsMatchEndWithCuteCharm(pk, xoro) || GetIsMatchEndWithSynchronize(pk, xoro); + } + + private static bool GetIsMatchEnd(PKM pk, Xoroshiro128Plus8b xoro) + { + // Check that gender matches + var genderRatio = PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form).Gender; + if (genderRatio == PersonalInfo.RatioMagicGenderless) + { + if (pk.Gender != (int)Gender.Genderless) return false; - - return GetIsHeightWeightMatch(pk, xoro); } - - private static bool GetIsMatchEndWithSynchronize(PKM pk, Xoroshiro128Plus8b xoro) + else if (genderRatio == PersonalInfo.RatioMagicMale) { - // Check that gender matches - var genderRatio = PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form).Gender; - if (genderRatio == PersonalInfo.RatioMagicGenderless) - { - if (pk.Gender != (int)Gender.Genderless) - return false; - } - else if (genderRatio == PersonalInfo.RatioMagicMale) - { - if (pk.Gender != (int)Gender.Male) - return false; - } - else if (genderRatio == PersonalInfo.RatioMagicFemale) - { - if (pk.Gender != (int)Gender.Female) - return false; - } - else - { - if (pk.Gender != (((int)xoro.NextUInt(253) + 1 < genderRatio) ? 1 : 0)) - return false; - } - - // Assume that the nature is a match due to synchronize. - - return GetIsHeightWeightMatch(pk, xoro); - } - - private static bool GetIsHeightWeightMatch(PKM pk, Xoroshiro128Plus8b xoro) - { - // Check height/weight - if (pk is not IScaledSize s) + if (pk.Gender != (int)Gender.Male) + return false; + } + else if (genderRatio == PersonalInfo.RatioMagicFemale) + { + if (pk.Gender != (int)Gender.Female) + return false; + } + else + { + if (pk.Gender != (((int)xoro.NextUInt(253) + 1 < genderRatio) ? 1 : 0)) return false; - - var height = xoro.NextUInt(0x81) + xoro.NextUInt(0x80); - var weight = xoro.NextUInt(0x81) + xoro.NextUInt(0x80); - return s.HeightScalar == height && s.WeightScalar == weight; } - private static uint GetRevisedPID(uint fakeTID, uint pid, ITrainerID tr) + // Check that the nature matches + if (pk.Nature != (int)xoro.NextUInt(25)) + return false; + + return GetIsHeightWeightMatch(pk, xoro); + } + + private static bool GetIsMatchEndWithCuteCharm(PKM pk, Xoroshiro128Plus8b xoro) + { + // Check that gender matches + // Assume that the gender is a match due to cute charm. + + // Check that the nature matches + if (pk.Nature != (int)xoro.NextUInt(25)) + return false; + + return GetIsHeightWeightMatch(pk, xoro); + } + + private static bool GetIsMatchEndWithSynchronize(PKM pk, Xoroshiro128Plus8b xoro) + { + // Check that gender matches + var genderRatio = PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form).Gender; + if (genderRatio == PersonalInfo.RatioMagicGenderless) { - var xor = GetShinyXor(pid, fakeTID); - var newXor = GetShinyXor(pid, (uint)(tr.TID | (tr.SID << 16))); - - var fakeRare = GetRareType(xor); - var newRare = GetRareType(newXor); - - if (fakeRare == newRare) - return pid; - - var isShiny = xor < 16; - if (isShiny) - return (((uint)(tr.TID ^ tr.SID) ^ (pid & 0xFFFF) ^ (xor == 0 ? 0u : 1u)) << 16) | (pid & 0xFFFF); // force same shiny star type - return pid ^ 0x1000_0000; + if (pk.Gender != (int)Gender.Genderless) + return false; + } + else if (genderRatio == PersonalInfo.RatioMagicMale) + { + if (pk.Gender != (int)Gender.Male) + return false; + } + else if (genderRatio == PersonalInfo.RatioMagicFemale) + { + if (pk.Gender != (int)Gender.Female) + return false; + } + else + { + if (pk.Gender != (((int)xoro.NextUInt(253) + 1 < genderRatio) ? 1 : 0)) + return false; } - private static Shiny GetRareType(uint xor) => xor switch - { - 0 => Shiny.AlwaysSquare, - < 16 => Shiny.AlwaysStar, - _ => Shiny.Never, - }; + // Assume that the nature is a match due to synchronize. - private static bool GetIsShiny(int tid, int sid, uint pid) - { - return GetIsShiny(pid, (uint)((sid << 16) | tid)); - } + return GetIsHeightWeightMatch(pk, xoro); + } - private static bool GetIsShiny(uint pid, uint oid) => GetShinyXor(pid, oid) < 16; + private static bool GetIsHeightWeightMatch(PKM pk, Xoroshiro128Plus8b xoro) + { + // Check height/weight + if (pk is not IScaledSize s) + return false; - private static uint GetShinyXor(uint pid, uint oid) - { - var xor = pid ^ oid; - return (xor ^ (xor >> 16)) & 0xFFFF; - } + var height = xoro.NextUInt(0x81) + xoro.NextUInt(0x80); + var weight = xoro.NextUInt(0x81) + xoro.NextUInt(0x80); + return s.HeightScalar == height && s.WeightScalar == weight; + } + + private static uint GetRevisedPID(uint fakeTID, uint pid, ITrainerID tr) + { + var xor = GetShinyXor(pid, fakeTID); + var newXor = GetShinyXor(pid, (uint)(tr.TID | (tr.SID << 16))); + + var fakeRare = GetRareType(xor); + var newRare = GetRareType(newXor); + + if (fakeRare == newRare) + return pid; + + var isShiny = xor < 16; + if (isShiny) + return (((uint)(tr.TID ^ tr.SID) ^ (pid & 0xFFFF) ^ (xor == 0 ? 0u : 1u)) << 16) | (pid & 0xFFFF); // force same shiny star type + return pid ^ 0x1000_0000; + } + + private static Shiny GetRareType(uint xor) => xor switch + { + 0 => Shiny.AlwaysSquare, + < 16 => Shiny.AlwaysStar, + _ => Shiny.Never, + }; + + private static bool GetIsShiny(int tid, int sid, uint pid) + { + return GetIsShiny(pid, (uint)((sid << 16) | tid)); + } + + private static bool GetIsShiny(uint pid, uint oid) => GetShinyXor(pid, oid) < 16; + + private static uint GetShinyXor(uint pid, uint oid) + { + var xor = pid ^ oid; + return (xor ^ (xor >> 16)) & 0xFFFF; } } diff --git a/PKHeX.Core/Legality/RNG/Wild8bRNG.cs b/PKHeX.Core/Legality/RNG/Wild8bRNG.cs index 5071b816a..f4aa0d8af 100644 --- a/PKHeX.Core/Legality/RNG/Wild8bRNG.cs +++ b/PKHeX.Core/Legality/RNG/Wild8bRNG.cs @@ -1,180 +1,179 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains logic for the Generation 8b (BD/SP) wild and stationary spawns. +/// +public static class Wild8bRNG { - /// - /// Contains logic for the Generation 8b (BD/SP) wild and stationary spawns. - /// - public static class Wild8bRNG + private const int UNSET = -1; + + public static void ApplyDetails(PKM pk, EncounterCriteria criteria, + Shiny shiny = Shiny.FixedValue, + int flawless = -1, + AbilityPermission ability = AbilityPermission.Any12) { - private const int UNSET = -1; + if (shiny == Shiny.FixedValue) + shiny = criteria.Shiny is Shiny.Random or Shiny.Never ? Shiny.Never : Shiny.Always; + if (flawless == -1) + flawless = 0; - public static void ApplyDetails(PKM pk, EncounterCriteria criteria, - Shiny shiny = Shiny.FixedValue, - int flawless = -1, - AbilityPermission ability = AbilityPermission.Any12) + int ctr = 0; + const int maxAttempts = 50_000; + var rnd = Util.Rand; + do { - if (shiny == Shiny.FixedValue) - shiny = criteria.Shiny is Shiny.Random or Shiny.Never ? Shiny.Never : Shiny.Always; - if (flawless == -1) - flawless = 0; + ulong s0 = rnd.Rand64(); + ulong s1 = rnd.Rand64(); + var xors = new XorShift128(s0, s1); + if (TryApplyFromSeed(pk, criteria, shiny, flawless, xors, ability)) + return; + } while (++ctr != maxAttempts); - int ctr = 0; - const int maxAttempts = 50_000; - var rnd = Util.Rand; - do - { - ulong s0 = rnd.Rand64(); - ulong s1 = rnd.Rand64(); - var xors = new XorShift128(s0, s1); - if (TryApplyFromSeed(pk, criteria, shiny, flawless, xors, ability)) - return; - } while (++ctr != maxAttempts); - - { - ulong s0 = rnd.Rand64(); - ulong s1 = rnd.Rand64(); - var xors = new XorShift128(s0, s1); - TryApplyFromSeed(pk, EncounterCriteria.Unrestricted, shiny, flawless, xors, ability); - } - } - - public static bool TryApplyFromSeed(PKM pk, EncounterCriteria criteria, Shiny shiny, int flawless, XorShift128 xors, AbilityPermission ability) { - // Encryption Constant - pk.EncryptionConstant = xors.NextUInt(); - - // PID - var fakeTID = xors.NextUInt(); // fakeTID - var pid = xors.NextUInt(); - pid = GetRevisedPID(fakeTID, pid, pk); - if (shiny == Shiny.Never) - { - if (GetIsShiny(pk.TID, pk.SID, pid)) - return false; - } - else if (shiny != Shiny.Random) - { - if (!GetIsShiny(pk.TID, pk.SID, pid)) - return false; - - if (shiny == Shiny.AlwaysSquare && pk.ShinyXor != 0) - return false; - if (shiny == Shiny.AlwaysStar && pk.ShinyXor == 0) - return false; - } - pk.PID = pid; - - // Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless. - Span ivs = stackalloc[] { UNSET, UNSET, UNSET, UNSET, UNSET, UNSET }; - const int MAX = 31; - var determined = 0; - while (determined < flawless) - { - var idx = (int)xors.NextUInt(6); - if (ivs[idx] != UNSET) - continue; - ivs[idx] = 31; - determined++; - } - - for (var i = 0; i < ivs.Length; i++) - { - if (ivs[i] == UNSET) - ivs[i] = xors.NextInt(0, MAX + 1); - } - - if (!criteria.IsIVsCompatible(ivs, 8)) - return false; - - pk.IV_HP = ivs[0]; - pk.IV_ATK = ivs[1]; - pk.IV_DEF = ivs[2]; - pk.IV_SPA = ivs[3]; - pk.IV_SPD = ivs[4]; - pk.IV_SPE = ivs[5]; - - // Ability - var n = ability switch - { - AbilityPermission.Any12 => (int)xors.NextUInt(2), - AbilityPermission.Any12H => (int)xors.NextUInt(3), - _ => (int)ability >> 1, - }; - pk.SetAbilityIndex(n); - - // Gender (skip this if gender is fixed) - var genderRatio = PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form).Gender; - if (genderRatio == PersonalInfo.RatioMagicGenderless) - { - pk.Gender = 2; - } - else if (genderRatio == PersonalInfo.RatioMagicMale) - { - pk.Gender = 0; - } - else if (genderRatio == PersonalInfo.RatioMagicFemale) - { - pk.Gender = 1; - } - else - { - var next = (((int)xors.NextUInt(253) + 1 < genderRatio) ? 1 : 0); - if (criteria.Gender is 0 or 1 && next != criteria.Gender) - return false; - pk.Gender = next; - } - - if (criteria.Nature is Nature.Random) - pk.Nature = (int)xors.NextUInt(25); - else // Skip nature, assuming Synchronize - pk.Nature = (int)criteria.Nature; - pk.StatNature = pk.Nature; - - // Remainder - var scale = (IScaledSize)pk; - scale.HeightScalar = (byte)((int)xors.NextUInt(0x81) + (int)xors.NextUInt(0x80)); - scale.WeightScalar = (byte)((int)xors.NextUInt(0x81) + (int)xors.NextUInt(0x80)); - - // Item, don't care - return true; - } - - private static uint GetRevisedPID(uint fakeTID, uint pid, ITrainerID tr) - { - var xor = GetShinyXor(pid, fakeTID); - var newXor = GetShinyXor(pid, (uint)(tr.TID | (tr.SID << 16))); - - var fakeRare = GetRareType(xor); - var newRare = GetRareType(newXor); - - if (fakeRare == newRare) - return pid; - - var isShiny = xor < 16; - if (isShiny) - return (((uint)(tr.TID ^ tr.SID) ^ (pid & 0xFFFF) ^ (xor == 0 ? 0u : 1u)) << 16) | (pid & 0xFFFF); // force same shiny star type - return pid ^ 0x1000_0000; - } - - private static Shiny GetRareType(uint xor) => xor switch - { - 0 => Shiny.AlwaysSquare, - < 16 => Shiny.AlwaysStar, - _ => Shiny.Never, - }; - - private static bool GetIsShiny(int tid, int sid, uint pid) - { - return GetIsShiny(pid, (uint)((sid << 16) | tid)); - } - - private static bool GetIsShiny(uint pid, uint oid) => GetShinyXor(pid, oid) < 16; - - private static uint GetShinyXor(uint pid, uint oid) - { - var xor = pid ^ oid; - return (xor ^ (xor >> 16)) & 0xFFFF; + ulong s0 = rnd.Rand64(); + ulong s1 = rnd.Rand64(); + var xors = new XorShift128(s0, s1); + TryApplyFromSeed(pk, EncounterCriteria.Unrestricted, shiny, flawless, xors, ability); } } + + public static bool TryApplyFromSeed(PKM pk, EncounterCriteria criteria, Shiny shiny, int flawless, XorShift128 xors, AbilityPermission ability) + { + // Encryption Constant + pk.EncryptionConstant = xors.NextUInt(); + + // PID + var fakeTID = xors.NextUInt(); // fakeTID + var pid = xors.NextUInt(); + pid = GetRevisedPID(fakeTID, pid, pk); + if (shiny == Shiny.Never) + { + if (GetIsShiny(pk.TID, pk.SID, pid)) + return false; + } + else if (shiny != Shiny.Random) + { + if (!GetIsShiny(pk.TID, pk.SID, pid)) + return false; + + if (shiny == Shiny.AlwaysSquare && pk.ShinyXor != 0) + return false; + if (shiny == Shiny.AlwaysStar && pk.ShinyXor == 0) + return false; + } + pk.PID = pid; + + // Check IVs: Create flawless IVs at random indexes, then the random IVs for not flawless. + Span ivs = stackalloc[] { UNSET, UNSET, UNSET, UNSET, UNSET, UNSET }; + const int MAX = 31; + var determined = 0; + while (determined < flawless) + { + var idx = (int)xors.NextUInt(6); + if (ivs[idx] != UNSET) + continue; + ivs[idx] = 31; + determined++; + } + + for (var i = 0; i < ivs.Length; i++) + { + if (ivs[i] == UNSET) + ivs[i] = xors.NextInt(0, MAX + 1); + } + + if (!criteria.IsIVsCompatible(ivs, 8)) + return false; + + pk.IV_HP = ivs[0]; + pk.IV_ATK = ivs[1]; + pk.IV_DEF = ivs[2]; + pk.IV_SPA = ivs[3]; + pk.IV_SPD = ivs[4]; + pk.IV_SPE = ivs[5]; + + // Ability + var n = ability switch + { + AbilityPermission.Any12 => (int)xors.NextUInt(2), + AbilityPermission.Any12H => (int)xors.NextUInt(3), + _ => (int)ability >> 1, + }; + pk.SetAbilityIndex(n); + + // Gender (skip this if gender is fixed) + var genderRatio = PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form).Gender; + if (genderRatio == PersonalInfo.RatioMagicGenderless) + { + pk.Gender = 2; + } + else if (genderRatio == PersonalInfo.RatioMagicMale) + { + pk.Gender = 0; + } + else if (genderRatio == PersonalInfo.RatioMagicFemale) + { + pk.Gender = 1; + } + else + { + var next = (((int)xors.NextUInt(253) + 1 < genderRatio) ? 1 : 0); + if (criteria.Gender is 0 or 1 && next != criteria.Gender) + return false; + pk.Gender = next; + } + + if (criteria.Nature is Nature.Random) + pk.Nature = (int)xors.NextUInt(25); + else // Skip nature, assuming Synchronize + pk.Nature = (int)criteria.Nature; + pk.StatNature = pk.Nature; + + // Remainder + var scale = (IScaledSize)pk; + scale.HeightScalar = (byte)((int)xors.NextUInt(0x81) + (int)xors.NextUInt(0x80)); + scale.WeightScalar = (byte)((int)xors.NextUInt(0x81) + (int)xors.NextUInt(0x80)); + + // Item, don't care + return true; + } + + private static uint GetRevisedPID(uint fakeTID, uint pid, ITrainerID tr) + { + var xor = GetShinyXor(pid, fakeTID); + var newXor = GetShinyXor(pid, (uint)(tr.TID | (tr.SID << 16))); + + var fakeRare = GetRareType(xor); + var newRare = GetRareType(newXor); + + if (fakeRare == newRare) + return pid; + + var isShiny = xor < 16; + if (isShiny) + return (((uint)(tr.TID ^ tr.SID) ^ (pid & 0xFFFF) ^ (xor == 0 ? 0u : 1u)) << 16) | (pid & 0xFFFF); // force same shiny star type + return pid ^ 0x1000_0000; + } + + private static Shiny GetRareType(uint xor) => xor switch + { + 0 => Shiny.AlwaysSquare, + < 16 => Shiny.AlwaysStar, + _ => Shiny.Never, + }; + + private static bool GetIsShiny(int tid, int sid, uint pid) + { + return GetIsShiny(pid, (uint)((sid << 16) | tid)); + } + + private static bool GetIsShiny(uint pid, uint oid) => GetShinyXor(pid, oid) < 16; + + private static uint GetShinyXor(uint pid, uint oid) + { + var xor = pid ^ oid; + return (xor ^ (xor >> 16)) & 0xFFFF; + } } diff --git a/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs b/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs index 5d82bd9b9..9b36f275e 100644 --- a/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs +++ b/PKHeX.Core/Legality/Restrictions/EvolutionRestrictions.cs @@ -1,249 +1,248 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using static PKHeX.Core.Move; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Restriction logic for evolutions that are a little more complex than can simply check. +/// +/// +/// Currently only checks "is able to know a move required to level up". +/// +internal static class EvolutionRestrictions { /// - /// Restriction logic for evolutions that are a little more complex than can simply check. + /// List of species that evolve from a previous species having a move while leveling up /// - /// - /// Currently only checks "is able to know a move required to level up". - /// - internal static class EvolutionRestrictions + private static readonly Dictionary SpeciesEvolutionWithMove = new() { - /// - /// List of species that evolve from a previous species having a move while leveling up - /// - private static readonly Dictionary SpeciesEvolutionWithMove = new() - { - {(int)Eevee, new(00, 0)}, // FairyMoves - {(int)MimeJr, new(01, (int)Mimic)}, - {(int)Bonsly, new(02, (int)Mimic)}, - {(int)Aipom, new(03, (int)Mimic)}, - {(int)Lickitung, new(04, (int)Rollout)}, - {(int)Tangela, new(05, (int)AncientPower)}, - {(int)Yanma, new(06, (int)AncientPower)}, - {(int)Piloswine, new(07, (int)AncientPower)}, - {(int)Steenee, new(08, (int)Stomp)}, - {(int)Clobbopus, new(09, (int)Taunt)}, - {(int)Stantler, new(10, (int)PsyshieldBash)}, - {(int)Qwilfish, new(11, (int)BarbBarrage)}, - }; + {(int)Eevee, new(00, 0)}, // FairyMoves + {(int)MimeJr, new(01, (int)Mimic)}, + {(int)Bonsly, new(02, (int)Mimic)}, + {(int)Aipom, new(03, (int)Mimic)}, + {(int)Lickitung, new(04, (int)Rollout)}, + {(int)Tangela, new(05, (int)AncientPower)}, + {(int)Yanma, new(06, (int)AncientPower)}, + {(int)Piloswine, new(07, (int)AncientPower)}, + {(int)Steenee, new(08, (int)Stomp)}, + {(int)Clobbopus, new(09, (int)Taunt)}, + {(int)Stantler, new(10, (int)PsyshieldBash)}, + {(int)Qwilfish, new(11, (int)BarbBarrage)}, + }; - private readonly record struct MoveEvolution(int ReferenceIndex, int Move); + private readonly record struct MoveEvolution(int ReferenceIndex, int Move); - private static readonly int[] FairyMoves = - { - (int)SweetKiss, - (int)Charm, - (int)Moonlight, - (int)DisarmingVoice, - (int)DrainingKiss, - (int)CraftyShield, - (int)FlowerShield, - (int)MistyTerrain, - (int)PlayRough, - (int)FairyWind, - (int)Moonblast, - (int)FairyLock, - (int)AromaticMist, - (int)Geomancy, - (int)DazzlingGleam, - (int)BabyDollEyes, - (int)LightofRuin, - (int)TwinkleTackleP, - (int)TwinkleTackleS, - (int)FloralHealing, - (int)GuardianofAlola, - (int)FleurCannon, - (int)NaturesMadness, - (int)LetsSnuggleForever, - (int)SparklySwirl, - (int)MaxStarfall, - (int)Decorate, - (int)SpiritBreak, - (int)StrangeSteam, - (int)MistyExplosion, - (int)SpringtideStorm, - }; + private static readonly int[] FairyMoves = + { + (int)SweetKiss, + (int)Charm, + (int)Moonlight, + (int)DisarmingVoice, + (int)DrainingKiss, + (int)CraftyShield, + (int)FlowerShield, + (int)MistyTerrain, + (int)PlayRough, + (int)FairyWind, + (int)Moonblast, + (int)FairyLock, + (int)AromaticMist, + (int)Geomancy, + (int)DazzlingGleam, + (int)BabyDollEyes, + (int)LightofRuin, + (int)TwinkleTackleP, + (int)TwinkleTackleS, + (int)FloralHealing, + (int)GuardianofAlola, + (int)FleurCannon, + (int)NaturesMadness, + (int)LetsSnuggleForever, + (int)SparklySwirl, + (int)MaxStarfall, + (int)Decorate, + (int)SpiritBreak, + (int)StrangeSteam, + (int)MistyExplosion, + (int)SpringtideStorm, + }; - /// - /// Minimum current level for a given species to have learned the evolve-move and be successfully evolved. - /// - /// Having a value of 0 means the move can't be learned. - private static readonly byte[][] MinLevelEvolutionWithMove = - { - new byte[] { 00, 00, 00, 00, 00, 29, 09, 02, 02 }, // Sylveon (Eevee with Fairy Move) - new byte[] { 00, 00, 00, 00, 18, 15, 15, 02, 32 }, // Mr. Mime (Mime Jr with Mimic) - new byte[] { 00, 00, 00, 00, 17, 17, 15, 02, 16 }, // Sudowoodo (Bonsly with Mimic) - new byte[] { 00, 00, 00, 00, 32, 32, 32, 02, 32 }, // Ambipom (Aipom with Double Hit) - new byte[] { 00, 00, 02, 00, 02, 33, 33, 02, 06 }, // Lickilicky (Lickitung with Rollout) - new byte[] { 00, 00, 00, 00, 02, 36, 38, 02, 24 }, // Tangrowth (Tangela with Ancient Power) - new byte[] { 00, 00, 00, 00, 02, 33, 33, 02, 33 }, // Yanmega (Yanma with Ancient Power) - new byte[] { 00, 00, 00, 00, 02, 02, 02, 02, 02 }, // Mamoswine (Piloswine with Ancient Power) - new byte[] { 00, 00, 00, 00, 00, 00, 00, 02, 28 }, // Tsareena (Steenee with Stomp) - new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 35 }, // Grapploct (Clobbopus with Taunt) - new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00 }, // Wyrdeer (Stantler with AGILE Psyshield Bash) - new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00 }, // Overqwil (Qwilfish with STRONG Barb Barrage) - }; + /// + /// Minimum current level for a given species to have learned the evolve-move and be successfully evolved. + /// + /// Having a value of 0 means the move can't be learned. + private static readonly byte[][] MinLevelEvolutionWithMove = + { + new byte[] { 00, 00, 00, 00, 00, 29, 09, 02, 02 }, // Sylveon (Eevee with Fairy Move) + new byte[] { 00, 00, 00, 00, 18, 15, 15, 02, 32 }, // Mr. Mime (Mime Jr with Mimic) + new byte[] { 00, 00, 00, 00, 17, 17, 15, 02, 16 }, // Sudowoodo (Bonsly with Mimic) + new byte[] { 00, 00, 00, 00, 32, 32, 32, 02, 32 }, // Ambipom (Aipom with Double Hit) + new byte[] { 00, 00, 02, 00, 02, 33, 33, 02, 06 }, // Lickilicky (Lickitung with Rollout) + new byte[] { 00, 00, 00, 00, 02, 36, 38, 02, 24 }, // Tangrowth (Tangela with Ancient Power) + new byte[] { 00, 00, 00, 00, 02, 33, 33, 02, 33 }, // Yanmega (Yanma with Ancient Power) + new byte[] { 00, 00, 00, 00, 02, 02, 02, 02, 02 }, // Mamoswine (Piloswine with Ancient Power) + new byte[] { 00, 00, 00, 00, 00, 00, 00, 02, 28 }, // Tsareena (Steenee with Stomp) + new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 35 }, // Grapploct (Clobbopus with Taunt) + new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00 }, // Wyrdeer (Stantler with AGILE Psyshield Bash) + new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00 }, // Overqwil (Qwilfish with STRONG Barb Barrage) + }; - private static readonly byte[] MinLevelEvolutionWithMove_8LA = - { - 00, // Sylveon (Eevee with Fairy Move) - 25, // Mr. Mime (Mime Jr with Mimic) - 29, // Sudowoodo (Bonsly with Mimic) - 25, // Ambipom (Aipom with Double Hit) - 34, // Lickilicky (Lickitung with Rollout) - 34, // Tangrowth (Tangela with Ancient Power) - 34, // Yanmega (Yanma with Ancient Power) - 34, // Mamoswine (Piloswine with Ancient Power) - 99, // Tsareena (Steenee with Stomp) - 99, // Grapploct (Clobbopus with Taunt) - 31, // Wyrdeer (Stantler with AGILE Psyshield Bash) - 25, // Overqwil (Qwilfish with STRONG Barb Barrage) - }; + private static readonly byte[] MinLevelEvolutionWithMove_8LA = + { + 00, // Sylveon (Eevee with Fairy Move) + 25, // Mr. Mime (Mime Jr with Mimic) + 29, // Sudowoodo (Bonsly with Mimic) + 25, // Ambipom (Aipom with Double Hit) + 34, // Lickilicky (Lickitung with Rollout) + 34, // Tangrowth (Tangela with Ancient Power) + 34, // Yanmega (Yanma with Ancient Power) + 34, // Mamoswine (Piloswine with Ancient Power) + 99, // Tsareena (Steenee with Stomp) + 99, // Grapploct (Clobbopus with Taunt) + 31, // Wyrdeer (Stantler with AGILE Psyshield Bash) + 25, // Overqwil (Qwilfish with STRONG Barb Barrage) + }; - private static readonly bool[][] CanEggHatchWithEvolveMove = - { - new [] { false, false, true, true, true, true, true, true, true }, // Sylveon (Eevee with Fairy Move) - new [] { false, false, false, false, true, true, true, true, true }, // Mr. Mime (Mime Jr with Mimic) - new [] { false, false, false, false, true, true, true, true, true }, // Sudowoodo (Bonsly with Mimic) - new [] { false, false, false, false, true, true, true, true, true }, // Ambipom (Aipom with Double Hit) - new [] { false, false, true, false, true, true, true, true, true }, // Lickilicky (Lickitung with Rollout) - new [] { false, false, false, false, true, true, true, true, true }, // Tangrowth (Tangela with Ancient Power) - new [] { false, false, false, false, true, true, true, true, true }, // Yanmega (Yanma with Ancient Power) - new [] { false, false, true, true, true, true, true, true, true }, // Mamoswine (Piloswine with Ancient Power) - new [] { false, false, false, false, false, false, false, false, false }, // Tsareena (Steenee with Stomp) - new [] { false, false, false, false, false, false, false, false, true }, // Grapploct (Clobbopus with Taunt) - new [] { false, false, false, false, false, false, false, false, false }, // Wyrdeer (Stantler with AGILE Psyshield Bash) - new [] { false, false, false, false, false, false, false, false, false }, // Overqwil (Qwilfish with STRONG Barb Barrage) - }; + private static readonly bool[][] CanEggHatchWithEvolveMove = + { + new [] { false, false, true, true, true, true, true, true, true }, // Sylveon (Eevee with Fairy Move) + new [] { false, false, false, false, true, true, true, true, true }, // Mr. Mime (Mime Jr with Mimic) + new [] { false, false, false, false, true, true, true, true, true }, // Sudowoodo (Bonsly with Mimic) + new [] { false, false, false, false, true, true, true, true, true }, // Ambipom (Aipom with Double Hit) + new [] { false, false, true, false, true, true, true, true, true }, // Lickilicky (Lickitung with Rollout) + new [] { false, false, false, false, true, true, true, true, true }, // Tangrowth (Tangela with Ancient Power) + new [] { false, false, false, false, true, true, true, true, true }, // Yanmega (Yanma with Ancient Power) + new [] { false, false, true, true, true, true, true, true, true }, // Mamoswine (Piloswine with Ancient Power) + new [] { false, false, false, false, false, false, false, false, false }, // Tsareena (Steenee with Stomp) + new [] { false, false, false, false, false, false, false, false, true }, // Grapploct (Clobbopus with Taunt) + new [] { false, false, false, false, false, false, false, false, false }, // Wyrdeer (Stantler with AGILE Psyshield Bash) + new [] { false, false, false, false, false, false, false, false, false }, // Overqwil (Qwilfish with STRONG Barb Barrage) + }; - /// - /// Checks if the is correctly evolved, assuming it had a known move requirement evolution in its evolution chain. - /// - /// True if unnecessary to check or the evolution was valid. - public static bool IsValidEvolutionWithMove(PKM pkm, LegalInfo info) + /// + /// Checks if the is correctly evolved, assuming it had a known move requirement evolution in its evolution chain. + /// + /// True if unnecessary to check or the evolution was valid. + public static bool IsValidEvolutionWithMove(PKM pk, LegalInfo info) + { + // Known-move evolutions were introduced in Gen4. + if (pk.Format < 4) // doesn't exist yet! + return true; + + // OK if un-evolved from original encounter + int species = pk.Species; + if (info.EncounterMatch.Species == species) + return true; + + // Exclude evolution paths that did not require a move w/level-up evolution + var enc = info.EncounterOriginal; + if (!SpeciesEvolutionWithMove.TryGetValue(enc.Species, out var entry)) + return true; + + var move = entry.Move; + if (move == 0) { - // Known-move evolutions were introduced in Gen4. - if (pkm.Format < 4) // doesn't exist yet! + // Other evolutions are fine. + if (pk.Species != (int)Sylveon) return true; + } - // OK if un-evolved from original encounter - int species = pkm.Species; - if (info.EncounterMatch.Species == species) - return true; - - // Exclude evolution paths that did not require a move w/level-up evolution - var enc = info.EncounterOriginal; - if (!SpeciesEvolutionWithMove.TryGetValue(enc.Species, out var entry)) - return true; - - var move = entry.Move; - if (move == 0) + // Check if the move was already known when it was originally encountered. + var gen = info.Generation; + var index = entry.ReferenceIndex; + if (enc is EncounterEgg) + { + if (CanEggHatchWithEvolveMove[index][gen]) { - // Other evolutions are fine. - if (pkm.Species != (int)Sylveon) - return true; - } - - // Check if the move was already known when it was originally encountered. - var gen = info.Generation; - var index = entry.ReferenceIndex; - if (enc is EncounterEgg) - { - if (CanEggHatchWithEvolveMove[index][gen]) - { - var result = move == 0 ? IsMoveInherited(pkm, info, FairyMoves) : IsMoveInherited(pkm, info, move); - if (result) - return true; - } - } - else if (enc is IMoveset s) - { - var result = move == 0 ? s.Moves.Any(FairyMoves.Contains) : s.Moves.Contains(move); + var result = move == 0 ? IsMoveInherited(pk, info, FairyMoves) : IsMoveInherited(pk, info, move); if (result) return true; } - - // Current level must be at least the minimum post-evolution level. - var lvl = GetMinLevelKnowRequiredMove(pkm, gen, index, info.EvoChainsAllGens); - return pkm.CurrentLevel >= lvl; } - - private static int GetMinLevelKnowRequiredMove(PKM pkm, int gen, int index, EvolutionHistory evos) + else if (enc is IMoveset s) { - var lvl = GetLevelLearnMove(pkm, gen, index); - if (pkm.HasVisitedLA(evos.Gen8a)) // No Level Up required, and different levels than mainline SW/SH. - { - var la = MinLevelEvolutionWithMove_8LA[index]; - if (la <= lvl) - return la; - } - - // If has original met location the minimum evolution level is one level after met level - // Gen 3 pokemon in gen 4 games: minimum level is one level after transfer to generation 4 - // VC pokemon: minimum level is one level after transfer to generation 7 - // Sylveon: always one level after met level, for gen 4 and 5 eevees in gen 6 games minimum for evolution is one level after transfer to generation 5 - if (pkm.HasOriginalMetLocation || (pkm.Format == 4 && gen == 3) || pkm.VC || pkm.Species == (int)Sylveon) - lvl = Math.Max(pkm.Met_Level + 1, lvl); - return lvl; - } - - private static int GetLevelLearnMove(PKM pkm, int gen, int index) - { - // Get the minimum level in any generation when the pokemon could learn the evolve move - var levels = MinLevelEvolutionWithMove[index]; - var lvl = 101; - var end = pkm.Format; - for (int g = gen; g <= end; g++) - { - var l = levels[g]; - if (l == 0) - continue; - if (l == 2) - return 2; // true minimum - if (l < lvl) - lvl = l; // best minimum - } - return lvl; - } - - private static bool IsMoveInherited(PKM pkm, LegalInfo info, int move) - { - // In 3DS games, the inherited move must be in the relearn moves. - if (info.Generation >= 6) - return pkm.RelearnMoves.Contains(move); - - // In Pre-3DS games, the move is inherited if it has the move and it can be hatched with the move. - if (pkm.HasMove(move)) + var result = move == 0 ? s.Moves.Any(FairyMoves.Contains) : s.Moves.Contains(move); + if (result) return true; - - return DidLearnAndForget(info); } - private static bool IsMoveInherited(PKM pkm, LegalInfo info, int[] moves) + // Current level must be at least the minimum post-evolution level. + var lvl = GetMinLevelKnowRequiredMove(pk, gen, index, info.EvoChainsAllGens); + return pk.CurrentLevel >= lvl; + } + + private static int GetMinLevelKnowRequiredMove(PKM pk, int gen, int index, EvolutionHistory evos) + { + var lvl = GetLevelLearnMove(pk, gen, index); + if (pk.HasVisitedLA(evos.Gen8a)) // No Level Up required, and different levels than mainline SW/SH. { - // In 3DS games, the inherited move must be in the relearn moves. - if (info.Generation >= 6) - return pkm.RelearnMoves.Any(moves.Contains); - - // In Pre-3DS games, the move is inherited if it has the move and it can be hatched with the move. - if (pkm.Moves.Any(moves.Contains)) - return true; - - return DidLearnAndForget(info); + var la = MinLevelEvolutionWithMove_8LA[index]; + if (la <= lvl) + return la; } - private static bool DidLearnAndForget(LegalInfo info) + // If has original met location the minimum evolution level is one level after met level + // Gen 3 pokemon in gen 4 games: minimum level is one level after transfer to generation 4 + // VC pokemon: minimum level is one level after transfer to generation 7 + // Sylveon: always one level after met level, for gen 4 and 5 eevees in gen 6 games minimum for evolution is one level after transfer to generation 5 + if (pk.HasOriginalMetLocation || (pk.Format == 4 && gen == 3) || pk.VC || pk.Species == (int)Sylveon) + lvl = Math.Max(pk.Met_Level + 1, lvl); + return lvl; + } + + private static int GetLevelLearnMove(PKM pk, int gen, int index) + { + // Get the minimum level in any generation when the pokemon could learn the evolve move + var levels = MinLevelEvolutionWithMove[index]; + var lvl = 101; + var end = pk.Format; + for (int g = gen; g <= end; g++) { - // If the pokemon does not currently have the move, it could have been an egg move that was forgotten. - // This requires the pokemon to not have 4 other moves identified as egg moves or inherited level up moves. - var fromEggCount = info.Moves.Count(m => m.IsEggSource); - return fromEggCount < 4; + var l = levels[g]; + if (l == 0) + continue; + if (l == 2) + return 2; // true minimum + if (l < lvl) + lvl = l; // best minimum } + return lvl; + } + + private static bool IsMoveInherited(PKM pk, LegalInfo info, int move) + { + // In 3DS games, the inherited move must be in the relearn moves. + if (info.Generation >= 6) + return pk.RelearnMoves.Contains(move); + + // In Pre-3DS games, the move is inherited if it has the move and it can be hatched with the move. + if (pk.HasMove(move)) + return true; + + return DidLearnAndForget(info); + } + + private static bool IsMoveInherited(PKM pk, LegalInfo info, int[] moves) + { + // In 3DS games, the inherited move must be in the relearn moves. + if (info.Generation >= 6) + return pk.RelearnMoves.Any(moves.Contains); + + // In Pre-3DS games, the move is inherited if it has the move and it can be hatched with the move. + if (pk.Moves.Any(moves.Contains)) + return true; + + return DidLearnAndForget(info); + } + + private static bool DidLearnAndForget(LegalInfo info) + { + // If the pokemon does not currently have the move, it could have been an egg move that was forgotten. + // This requires the pokemon to not have 4 other moves identified as egg moves or inherited level up moves. + var fromEggCount = info.Moves.Count(m => m.IsEggSource); + return fromEggCount < 4; } } diff --git a/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs b/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs index 4434d326b..0be146ea1 100644 --- a/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs +++ b/PKHeX.Core/Legality/Restrictions/GBRestrictions.cs @@ -6,562 +6,561 @@ using static PKHeX.Core.GameVersion; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Miscellaneous GB Era restriction logic for legality checking +/// +internal static class GBRestrictions { - /// - /// Miscellaneous GB Era restriction logic for legality checking - /// - internal static class GBRestrictions + private static readonly int[] G1Exeggcute_IncompatibleMoves = { 78, 77, 79 }; + + private static readonly int[] Stadium_CatchRate = { - private static readonly int[] G1Exeggcute_IncompatibleMoves = { 78, 77, 79 }; + 167, // Normal Box + 168, // Gorgeous Box + }; - private static readonly int[] Stadium_CatchRate = + private static readonly HashSet Stadium_GiftSpecies = new() + { + (int)Bulbasaur, + (int)Charmander, + (int)Squirtle, + (int)Psyduck, + (int)Hitmonlee, + (int)Hitmonchan, + (int)Eevee, + (int)Omanyte, + (int)Kabuto, + }; + + /// + /// Species that have a specific minimum amount of moves based on their evolution state. + /// + private static readonly HashSet SpecialMinMoveSlots = new() + { + (int)Pikachu, + (int)Raichu, + (int)NidoranF, + (int)Nidorina, + (int)Nidoqueen, + (int)NidoranM, + (int)Nidorino, + (int)Nidoking, + (int)Clefable, + (int)Ninetales, + (int)Wigglytuff, + (int)Arcanine, + (int)Cloyster, + (int)Exeggutor, + (int)Tangela, + (int)Starmie, + }; + + internal static bool TypeIDExists(int type) => Types_Gen1.Contains(type); + + /// + /// Valid type IDs extracted from the Personal Table used for R/G/B/Y games. + /// + private static readonly HashSet Types_Gen1 = new() + { + 0, 1, 2, 3, 4, 5, 7, 8, 20, 21, 22, 23, 24, 25, 26, + }; + + /// + /// Species that have a catch rate value that is different from their pre-evolutions, and cannot be obtained directly. + /// + internal static readonly HashSet Species_NotAvailable_CatchRate = new() + { + (int)Butterfree, + (int)Pidgeot, + (int)Nidoqueen, + (int)Nidoking, + (int)Ninetales, + (int)Vileplume, + (int)Persian, + (int)Arcanine, + (int)Poliwrath, + (int)Alakazam, + (int)Machamp, + (int)Victreebel, + (int)Rapidash, + (int)Cloyster, + (int)Exeggutor, + (int)Starmie, + (int)Dragonite, + }; + + internal static readonly HashSet Trade_Evolution1 = new() + { + (int)Kadabra, + (int)Machoke, + (int)Graveler, + (int)Haunter, + }; + + public static bool RateMatchesEncounter(int species, GameVersion version, int rate) + { + if (version.Contains(YW)) { - 167, // Normal Box - 168, // Gorgeous Box - }; - - private static readonly HashSet Stadium_GiftSpecies = new() - { - (int)Bulbasaur, - (int)Charmander, - (int)Squirtle, - (int)Psyduck, - (int)Hitmonlee, - (int)Hitmonchan, - (int)Eevee, - (int)Omanyte, - (int)Kabuto, - }; - - /// - /// Species that have a specific minimum amount of moves based on their evolution state. - /// - private static readonly HashSet SpecialMinMoveSlots = new() - { - (int)Pikachu, - (int)Raichu, - (int)NidoranF, - (int)Nidorina, - (int)Nidoqueen, - (int)NidoranM, - (int)Nidorino, - (int)Nidoking, - (int)Clefable, - (int)Ninetales, - (int)Wigglytuff, - (int)Arcanine, - (int)Cloyster, - (int)Exeggutor, - (int)Tangela, - (int)Starmie, - }; - - internal static bool TypeIDExists(int type) => Types_Gen1.Contains(type); - - /// - /// Valid type IDs extracted from the Personal Table used for R/G/B/Y games. - /// - private static readonly HashSet Types_Gen1 = new() - { - 0, 1, 2, 3, 4, 5, 7, 8, 20, 21, 22, 23, 24, 25, 26, - }; - - /// - /// Species that have a catch rate value that is different from their pre-evolutions, and cannot be obtained directly. - /// - internal static readonly HashSet Species_NotAvailable_CatchRate = new() - { - (int)Butterfree, - (int)Pidgeot, - (int)Nidoqueen, - (int)Nidoking, - (int)Ninetales, - (int)Vileplume, - (int)Persian, - (int)Arcanine, - (int)Poliwrath, - (int)Alakazam, - (int)Machamp, - (int)Victreebel, - (int)Rapidash, - (int)Cloyster, - (int)Exeggutor, - (int)Starmie, - (int)Dragonite, - }; - - internal static readonly HashSet Trade_Evolution1 = new() - { - (int)Kadabra, - (int)Machoke, - (int)Graveler, - (int)Haunter, - }; - - public static bool RateMatchesEncounter(int species, GameVersion version, int rate) - { - if (version.Contains(YW)) - { - if (rate == PersonalTable.Y[species].CatchRate) - return true; - if (version == YW) // no RB - return false; - } - return rate == PersonalTable.RB[species].CatchRate; + if (rate == PersonalTable.Y[species].CatchRate) + return true; + if (version == YW) // no RB + return false; } + return rate == PersonalTable.RB[species].CatchRate; + } - private static int[] GetMinLevelLearnMoveG1(int species, List moves) - { - var result = new int[moves.Count]; - for (int i = 0; i < result.Length; i++) - result[i] = MoveLevelUp.GetIsLevelUp1(species, 0, moves[i], 100, 0).Level; + private static int[] GetMinLevelLearnMoveG1(int species, List moves) + { + var result = new int[moves.Count]; + for (int i = 0; i < result.Length; i++) + result[i] = MoveLevelUp.GetIsLevelUp1(species, 0, moves[i], 100, 0).Level; + return result; + } + + private static int[] GetMaxLevelLearnMoveG1(int species, List moves) + { + var result = new int[moves.Count]; + + int index = PersonalTable.RB.GetFormIndex(species, 0); + if (index == 0) return result; + + var pi_rb = ((PersonalInfoG1)PersonalTable.RB[index]).Moves; + var pi_y = ((PersonalInfoG1)PersonalTable.Y[index]).Moves; + + for (int m = 0; m < moves.Count; m++) + { + bool start = pi_rb.Contains(moves[m]) && pi_y.Contains(moves[m]); + result[m] = start ? 1 : Math.Max(GetHighest(LevelUpRB), GetHighest(LevelUpY)); + int GetHighest(IReadOnlyList learn) => learn[index].GetLevelLearnMove(moves[m]); + } + return result; + } + + private static List[] GetExclusiveMovesG1(int species1, int species2, IEnumerable tmhm, IEnumerable moves) + { + // Return from two species the exclusive moves that only one could learn and also the current pokemon have it in its current moveset + var moves1 = MoveLevelUp.GetMovesLevelUp1(species1, 0, 1, 100); + var moves2 = MoveLevelUp.GetMovesLevelUp1(species2, 0, 1, 100); + + // Remove common moves and remove tmhm, remove not learned moves + var common = new HashSet(moves1.Intersect(moves2).Concat(tmhm)); + var hashMoves = new HashSet(moves); + moves1.RemoveAll(x => !hashMoves.Contains(x) || common.Contains(x)); + moves2.RemoveAll(x => !hashMoves.Contains(x) || common.Contains(x)); + return new[] { moves1, moves2 }; + } + + internal static void GetIncompatibleEvolutionMoves(PKM pk, IReadOnlyList moves, IReadOnlyList tmhm, out int previousspecies, out IList incompatible_previous, out IList incompatible_current) + { + switch (pk.Species) + { + case (int)Nidoking when moves.Contains(31) && moves.Contains(37): + // Nidoking learns Thrash at level 23 + // Nidorino learns Fury Attack at level 36, Nidoran♂ at level 30 + // Other moves are either learned by Nidoran♂ up to level 23 or by TM + incompatible_current = new[] { 31 }; + incompatible_previous = new[] { 37 }; + previousspecies = 33; + return; + + case (int)Exeggutor when moves.Contains(23) && moves.Any(m => G1Exeggcute_IncompatibleMoves.Contains(m)): + // Exeggutor learns Stomp at level 28 + // Exeggcute learns Stun Spore at 32, PoisonPowder at 37 and Sleep Powder at 48 + incompatible_current = new[] { 23 }; + incompatible_previous = G1Exeggcute_IncompatibleMoves; + previousspecies = 103; + return; + + case (int)Vaporeon or (int)Jolteon or (int)Flareon: + incompatible_previous = new List(); + incompatible_current = new List(); + previousspecies = 133; + var ExclusiveMoves = GetExclusiveMovesG1((int)Eevee, pk.Species, tmhm, moves); + var EeveeLevels = GetMinLevelLearnMoveG1((int)Eevee, ExclusiveMoves[0]); + var EvoLevels = GetMaxLevelLearnMoveG1(pk.Species, ExclusiveMoves[1]); + + for (int i = 0; i < ExclusiveMoves[0].Count; i++) + { + // There is a evolution move with a lower level that current Eevee move + var el = EeveeLevels[i]; + if (EvoLevels.Any(ev => ev < el)) + incompatible_previous.Add(ExclusiveMoves[0][i]); + } + for (int i = 0; i < ExclusiveMoves[1].Count; i++) + { + // There is an Eevee move with a greater level that current evolution move + var el = EvoLevels[i]; + if (EeveeLevels.Any(ev => ev > el)) + incompatible_current.Add(ExclusiveMoves[1][i]); + } + return; + } + incompatible_previous = Array.Empty(); + incompatible_current = Array.Empty(); + previousspecies = 0; + } + + internal static int GetRequiredMoveCount(PK1 pk, IReadOnlyList moves, LegalInfo info, IReadOnlyList initialmoves) + { + var enc = info.EncounterMatch; + var capsuleState = IsTimeCapsuleTransferred(pk, info.Moves, enc); + if (capsuleState != TimeCapsuleEvaluation.NotTransferred) // No Move Deleter in Gen 1 + return 1; // Move Deleter exits, slots from 2 onwards can always be empty + + if (enc is IMoveset {Moves.Count: 4 }) + return 4; + + int required = GetRequiredMoveCount(pk, moves, info.EncounterMoves.LevelUpMoves, initialmoves, enc.Species); + if (required >= 4) + return 4; + + // tm, hm and tutor moves replace a free slots if the pokemon have less than 4 moves + // Ignore tm, hm and tutor moves already in the learnset table + var learn = info.EncounterMoves.LevelUpMoves; + var tmhm = info.EncounterMoves.TMHMMoves; + var tutor = info.EncounterMoves.TutorMoves; + var union = initialmoves.Union(learn[1]); + required += moves.Count(m => m != 0 && union.All(t => t != m) && (tmhm[1].Any(t => t == m) || tutor[1].Any(t => t == m))); + + return Math.Min(4, required); + } + + private static int GetRequiredMoveCount(PK1 pk, IReadOnlyList moves, IReadOnlyList[] learn, IReadOnlyList initialmoves, int originalSpecies) + { + if (SpecialMinMoveSlots.Contains(pk.Species)) + return GetRequiredMoveCountSpecial(pk, moves, learn, originalSpecies); + + // A pokemon is captured with initial moves and can't forget any until have all 4 slots used + // If it has learn a move before having 4 it will be in one of the free slots + int required = GetRequiredMoveSlotsRegular(pk, moves, learn, initialmoves, originalSpecies); + return required != 0 ? required : GetRequiredMoveCountDecrement(pk, moves, learn, initialmoves); + } + + private static int GetRequiredMoveSlotsRegular(PK1 pk, IReadOnlyList moves, IReadOnlyList[] learn, IReadOnlyList initialmoves, int originalSpecies) + { + if (originalSpecies == pk.Species) + return 0; // Can skip over all learned moves that we can reasonably care about + + return originalSpecies switch + { + (int)Caterpie => initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && (lm != (int)Move.Harden || moves.Contains((int)Move.Harden))), + (int)Metapod => initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && (lm != (int)Move.Confusion || moves.Contains((int)Move.Confusion))), + (int)Mankey => initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && (lm != (int)Move.Rage || moves.Contains((int)Move.Rage))), + _ => IsMoveCountRequired3(pk.Species, pk.CurrentLevel, moves) ? 3 : 0, + }; + } + + private static bool IsMoveCountRequired3(int species, int level, IReadOnlyList moves) => species switch + { + // Species that evolve and learn the 4th move as evolved species at a greater level than base species + // The 4th move is included in the level up table set as a pre-evolution move, + // it should be removed from the used slots count if is not the learn move + (int)Pidgeotto => level < 21 && !moves.Contains(018), // Whirlwind + (int)Sandslash => level < 27 && !moves.Contains(040), // Poison Sting + (int)Parasect => level < 30 && !moves.Contains(147), // Spore + (int)Golduck => level < 39 && !moves.Contains(093), // Confusion + (int)Dewgong => level < 44 && !moves.Contains(156), // Rest + (int)Weezing => level < 39 && !moves.Contains(108), // Smoke Screen + (int)Haunter or (int)Gengar => level < 29 && !moves.Contains(095), // Hypnosis + _ => false, + }; + + private static int GetRequiredMoveCountDecrement(PKM pk, IReadOnlyList moves, IReadOnlyList[] learn, IReadOnlyList initialmoves) + { + int usedslots = initialmoves.Union(learn[1]).Where(m => m != 0).Distinct().Count(); + switch (pk.Species) + { + case (int)Venonat: // Venonat; ignore Venomoth (by the time Venonat evolves it will always have 4 moves) + if (pk.CurrentLevel >= 11 && !moves.Contains(48)) // Supersonic + usedslots--; + if (pk.CurrentLevel >= 19 && !moves.Contains(93)) // Confusion + usedslots--; + break; + case (int)Kadabra or (int)Alakazam: // Abra & Kadabra + int catch_rate = ((PK1)pk).Catch_Rate; + if (catch_rate != 100)// Initial Yellow Kadabra Kinesis (move 134) + usedslots--; + if (catch_rate == 200 && pk.CurrentLevel < 20) // Kadabra Disable, not learned until 20 if captured as Abra (move 50) + usedslots--; + break; + case (int)Cubone or (int)Marowak: // Cubone & Marowak + if (!moves.Contains((int)Move.TailWhip) && !moves.Contains((int)Move.Headbutt)) // Initial Red + { + usedslots -=2; + } + else + { + if (!moves.Contains(39)) // Initial Yellow Tail Whip + usedslots--; + if (!moves.Contains(125)) // Initial Yellow Bone Club + usedslots--; + } + if (pk.Species == 105 && pk.CurrentLevel < 33 && !moves.Contains(116)) // Marowak evolved without Focus Energy + usedslots--; + break; + case (int)Chansey: + if (!moves.Contains(39)) // Yellow Initial Tail Whip + usedslots--; + if (!moves.Contains(3)) // Yellow Lvl 12 and Initial Red/Blue Double Slap + usedslots--; + break; + case (int)Mankey when pk.CurrentLevel >= 9 && !moves.Contains(67): // Mankey (Low Kick) + case (int)Pinsir when pk.CurrentLevel >= 21 && !moves.Contains(20): // Pinsir (Bind) + case (int)Gyarados when pk.CurrentLevel < 32: // Gyarados + usedslots--; + break; + default: return usedslots; + } + return usedslots; + } + + private static int GetRequiredMoveCountSpecial(PKM pk, IReadOnlyList moves, IReadOnlyList[] learn, int originalSpecies) + { + // Species with few mandatory slots, species with stone evolutions that could evolve at lower level and do not learn any more moves + // and Pikachu and Nidoran family, those only have mandatory the initial moves and a few have one level up moves, + // every other move could be avoided switching game or evolving + var mandatory = GetRequiredMoveCountLevel(pk, originalSpecies); + switch (pk.Species) + { + case (int)Exeggutor when pk.CurrentLevel >= 28: // Exeggutor + // At level 28 learn different move if is a Exeggute or Exeggutor + if (moves.Contains(73)) + mandatory.Add(73); // Leech Seed level 28 Exeggute + if (moves.Contains(23)) + mandatory.Add(23); // Stomp level 28 Exeggutor + break; + case (int)Pikachu when pk.CurrentLevel >= 33: + mandatory.Add(97); // Pikachu always learns Agility + break; + case (int)Tangela: + mandatory.Add(132); // Tangela always has Constrict as Initial Move + break; } - private static int[] GetMaxLevelLearnMoveG1(int species, List moves) + // Add to used slots the non-mandatory moves from the learnset table that the pokemon have learned + return mandatory.Distinct().Count(z => z != 0) + moves.Where(m => m != 0).Count(m => !mandatory.Contains(m) && learn[1].Contains(m)); + } + + private static List GetRequiredMoveCountLevel(PKM pk, int basespecies) + { + int species = pk.Species; + int maxlevel = 1; + int minlevel = 1; + + if (species == (int)Tangela) // Tangela moves before level 32 are different in RB vs Y { - var result = new int[moves.Count]; + minlevel = 32; + maxlevel = pk.CurrentLevel; + } + else if (species is >= (int)NidoranF and <= (int)Nidoking && pk.CurrentLevel >= 8) + { + maxlevel = 8; // Always learns a third move at level 8 + } - int index = PersonalTable.RB.GetFormIndex(species, 0); - if (index == 0) - return result; + if (minlevel > pk.CurrentLevel) + return new List(); - var pi_rb = ((PersonalInfoG1)PersonalTable.RB[index]).Moves; - var pi_y = ((PersonalInfoG1)PersonalTable.Y[index]).Moves; + return MoveLevelUp.GetMovesLevelUp1(basespecies, 0, maxlevel, minlevel); + } - for (int m = 0; m < moves.Count; m++) + internal static IEnumerable GetGen2Versions(IEncounterTemplate enc, bool korean) + { + if (ParseSettings.AllowGen2Crystal(korean) && enc.Version is C or GSC) + yield return C; + yield return GS; + } + + internal static IEnumerable GetGen1Versions(IEncounterTemplate enc) + { + if (enc.Species == (int)Eevee && enc.Version == Stadium) + { + // Stadium Eevee; check for RB and yellow initial moves + yield return RB; + yield return YW; + yield break; + } + if (enc.Version == YW) + { + yield return YW; + yield break; + } + + // Any encounter marked with version RBY is for pokemon with the same moves and catch rate in RB and Y, + // it is sufficient to check just RB's case + yield return RB; + } + + private static bool GetCatchRateMatchesPreEvolution(PK1 pk, int catch_rate) + { + // For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions + var table = EvolutionTree.Evolves1; + var chain = table.GetValidPreEvolutions(pk, maxLevel: (byte)pk.CurrentLevel); + foreach (var entry in chain) + { + var s = entry.Species; + if (Species_NotAvailable_CatchRate.Contains(s)) + continue; + if (catch_rate == PersonalTable.RB[s].CatchRate || catch_rate == PersonalTable.Y[s].CatchRate) + return true; + } + + // Krabby encounter trade special catch rate + int species = pk.Species; + if (catch_rate == 204 && (species is (int)Krabby or (int)Kingler)) + return true; + + if (Stadium_GiftSpecies.Contains(species) && Stadium_CatchRate.Contains(catch_rate)) + return true; + + return false; + } + + /// + /// Checks if the can inhabit + /// + /// Data to check + /// true if can inhabit, false if not. + private static bool CanInhabitGen1(this PKM pk) + { + // Korean Gen2 games can't trade-back because there are no Gen1 Korean games released + if (pk.Korean || pk.IsEgg) + return false; + + // Gen2 format with met data can't receive Gen1 moves, unless Stadium 2 is used (Oak's PC). + // If you put a Pokemon in the N64 box, the met info is retained, even if you switch over to a Gen I game to teach it TMs + // You can use rare candies from within the lab, so level-up moves from RBY context can be learned this way as well + // Stadium 2 is GB Cart Era only (not 3DS Virtual Console). + if (pk is ICaughtData2 {CaughtData: not 0} && !ParseSettings.AllowGBCartEra) + return false; + + // Sanity check species, if it could have existed as a pre-evolution. + int species = pk.Species; + if (species <= MaxSpeciesID_1) + return true; + return EvolutionLegality.FutureEvolutionsGen1.Contains(species); + } + + /// + /// Gets the Tradeback status depending on various values. + /// + /// Pokémon to guess the tradeback status from. + internal static PotentialGBOrigin GetTradebackStatusInitial(PKM pk) + { + if (pk is PK1 pk1) + return GetTradebackStatusRBY(pk1); + + if (pk.Format == 2 || pk.VC2) // Check for impossible tradeback scenarios + return !pk.CanInhabitGen1() ? PotentialGBOrigin.Gen2Only : PotentialGBOrigin.Either; + + // VC2 is released, we can assume it will be TradebackType.Any. + // Is impossible to differentiate a VC1 pokemon traded to Gen7 after VC2 is available. + // Met Date cannot be used definitively as the player can change their system clock. + return PotentialGBOrigin.Either; + } + + /// + /// Gets the Tradeback status depending on the + /// + /// Pokémon to guess the tradeback status from. + private static PotentialGBOrigin GetTradebackStatusRBY(PK1 pk) + { + if (!ParseSettings.AllowGen1Tradeback) + return PotentialGBOrigin.Gen1Only; + + // Detect tradeback status by comparing the catch rate(Gen1)/held item(Gen2) to the species in the pk's evolution chain. + var catch_rate = pk.Catch_Rate; + if (catch_rate == 0) + return PotentialGBOrigin.Either; + + bool matchAny = GetCatchRateMatchesPreEvolution(pk, catch_rate); + + if (!matchAny) + return PotentialGBOrigin.Either; + + if (HeldItems_GSC.Contains((ushort)catch_rate)) + return PotentialGBOrigin.Either; + + return PotentialGBOrigin.Gen1Only; + } + + public static TimeCapsuleEvaluation IsTimeCapsuleTransferred(PKM pk, IList moves, IEncounterTemplate enc) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (moves.Any(z => z != null && z.Generation != enc.Generation && z.Generation <= 2)) + { + if (pk is PK1 {Catch_Rate: not 0} g1 && !IsTradebackCatchRate(g1.Catch_Rate)) + return TimeCapsuleEvaluation.BadCatchRate; + return enc.Generation == 2 ? TimeCapsuleEvaluation.Transferred21 : TimeCapsuleEvaluation.Transferred12; + } + + if (pk is not GBPKM gb) + { + return enc.Generation switch { - bool start = pi_rb.Contains(moves[m]) && pi_y.Contains(moves[m]); - result[m] = start ? 1 : Math.Max(GetHighest(LevelUpRB), GetHighest(LevelUpY)); - int GetHighest(IReadOnlyList learn) => learn[index].GetLevelLearnMove(moves[m]); - } - return result; - } - - private static List[] GetExclusiveMovesG1(int species1, int species2, IEnumerable tmhm, IEnumerable moves) - { - // Return from two species the exclusive moves that only one could learn and also the current pokemon have it in its current moveset - var moves1 = MoveLevelUp.GetMovesLevelUp1(species1, 0, 1, 100); - var moves2 = MoveLevelUp.GetMovesLevelUp1(species2, 0, 1, 100); - - // Remove common moves and remove tmhm, remove not learned moves - var common = new HashSet(moves1.Intersect(moves2).Concat(tmhm)); - var hashMoves = new HashSet(moves); - moves1.RemoveAll(x => !hashMoves.Contains(x) || common.Contains(x)); - moves2.RemoveAll(x => !hashMoves.Contains(x) || common.Contains(x)); - return new[] { moves1, moves2 }; - } - - internal static void GetIncompatibleEvolutionMoves(PKM pkm, IReadOnlyList moves, IReadOnlyList tmhm, out int previousspecies, out IList incompatible_previous, out IList incompatible_current) - { - switch (pkm.Species) - { - case (int)Nidoking when moves.Contains(31) && moves.Contains(37): - // Nidoking learns Thrash at level 23 - // Nidorino learns Fury Attack at level 36, Nidoran♂ at level 30 - // Other moves are either learned by Nidoran♂ up to level 23 or by TM - incompatible_current = new[] { 31 }; - incompatible_previous = new[] { 37 }; - previousspecies = 33; - return; - - case (int)Exeggutor when moves.Contains(23) && moves.Any(m => G1Exeggcute_IncompatibleMoves.Contains(m)): - // Exeggutor learns Stomp at level 28 - // Exeggcute learns Stun Spore at 32, PoisonPowder at 37 and Sleep Powder at 48 - incompatible_current = new[] { 23 }; - incompatible_previous = G1Exeggcute_IncompatibleMoves; - previousspecies = 103; - return; - - case (int)Vaporeon or (int)Jolteon or (int)Flareon: - incompatible_previous = new List(); - incompatible_current = new List(); - previousspecies = 133; - var ExclusiveMoves = GetExclusiveMovesG1((int)Eevee, pkm.Species, tmhm, moves); - var EeveeLevels = GetMinLevelLearnMoveG1((int)Eevee, ExclusiveMoves[0]); - var EvoLevels = GetMaxLevelLearnMoveG1(pkm.Species, ExclusiveMoves[1]); - - for (int i = 0; i < ExclusiveMoves[0].Count; i++) - { - // There is a evolution move with a lower level that current Eevee move - var el = EeveeLevels[i]; - if (EvoLevels.Any(ev => ev < el)) - incompatible_previous.Add(ExclusiveMoves[0][i]); - } - for (int i = 0; i < ExclusiveMoves[1].Count; i++) - { - // There is an Eevee move with a greater level that current evolution move - var el = EvoLevels[i]; - if (EeveeLevels.Any(ev => ev > el)) - incompatible_current.Add(ExclusiveMoves[1][i]); - } - return; - } - incompatible_previous = Array.Empty(); - incompatible_current = Array.Empty(); - previousspecies = 0; - } - - internal static int GetRequiredMoveCount(PK1 pk, IReadOnlyList moves, LegalInfo info, IReadOnlyList initialmoves) - { - var enc = info.EncounterMatch; - var capsuleState = IsTimeCapsuleTransferred(pk, info.Moves, enc); - if (capsuleState != TimeCapsuleEvaluation.NotTransferred) // No Move Deleter in Gen 1 - return 1; // Move Deleter exits, slots from 2 onwards can always be empty - - if (enc is IMoveset {Moves.Count: 4 }) - return 4; - - int required = GetRequiredMoveCount(pk, moves, info.EncounterMoves.LevelUpMoves, initialmoves, enc.Species); - if (required >= 4) - return 4; - - // tm, hm and tutor moves replace a free slots if the pokemon have less than 4 moves - // Ignore tm, hm and tutor moves already in the learnset table - var learn = info.EncounterMoves.LevelUpMoves; - var tmhm = info.EncounterMoves.TMHMMoves; - var tutor = info.EncounterMoves.TutorMoves; - var union = initialmoves.Union(learn[1]); - required += moves.Count(m => m != 0 && union.All(t => t != m) && (tmhm[1].Any(t => t == m) || tutor[1].Any(t => t == m))); - - return Math.Min(4, required); - } - - private static int GetRequiredMoveCount(PK1 pk, IReadOnlyList moves, IReadOnlyList[] learn, IReadOnlyList initialmoves, int originalSpecies) - { - if (SpecialMinMoveSlots.Contains(pk.Species)) - return GetRequiredMoveCountSpecial(pk, moves, learn, originalSpecies); - - // A pokemon is captured with initial moves and can't forget any until have all 4 slots used - // If it has learn a move before having 4 it will be in one of the free slots - int required = GetRequiredMoveSlotsRegular(pk, moves, learn, initialmoves, originalSpecies); - return required != 0 ? required : GetRequiredMoveCountDecrement(pk, moves, learn, initialmoves); - } - - private static int GetRequiredMoveSlotsRegular(PK1 pk, IReadOnlyList moves, IReadOnlyList[] learn, IReadOnlyList initialmoves, int originalSpecies) - { - if (originalSpecies == pk.Species) - return 0; // Can skip over all learned moves that we can reasonably care about - - return originalSpecies switch - { - (int)Caterpie => initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && (lm != (int)Move.Harden || moves.Contains((int)Move.Harden))), - (int)Metapod => initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && (lm != (int)Move.Confusion || moves.Contains((int)Move.Confusion))), - (int)Mankey => initialmoves.Union(learn[1]).Distinct().Count(lm => lm != 0 && (lm != (int)Move.Rage || moves.Contains((int)Move.Rage))), - _ => IsMoveCountRequired3(pk.Species, pk.CurrentLevel, moves) ? 3 : 0, + 2 when pk.VC2 => TimeCapsuleEvaluation.Transferred21, + 1 when pk.VC1 => TimeCapsuleEvaluation.Transferred12, + _ => TimeCapsuleEvaluation.NotTransferred, }; } - private static bool IsMoveCountRequired3(int species, int level, IReadOnlyList moves) => species switch + if (gb is ICaughtData2 pk2) { - // Species that evolve and learn the 4th move as evolved species at a greater level than base species - // The 4th move is included in the level up table set as a pre-evolution move, - // it should be removed from the used slots count if is not the learn move - (int)Pidgeotto => level < 21 && !moves.Contains(018), // Whirlwind - (int)Sandslash => level < 27 && !moves.Contains(040), // Poison Sting - (int)Parasect => level < 30 && !moves.Contains(147), // Spore - (int)Golduck => level < 39 && !moves.Contains(093), // Confusion - (int)Dewgong => level < 44 && !moves.Contains(156), // Rest - (int)Weezing => level < 39 && !moves.Contains(108), // Smoke Screen - (int)Haunter or (int)Gengar => level < 29 && !moves.Contains(095), // Hypnosis - _ => false, - }; - - private static int GetRequiredMoveCountDecrement(PKM pk, IReadOnlyList moves, IReadOnlyList[] learn, IReadOnlyList initialmoves) - { - int usedslots = initialmoves.Union(learn[1]).Where(m => m != 0).Distinct().Count(); - switch (pk.Species) - { - case (int)Venonat: // Venonat; ignore Venomoth (by the time Venonat evolves it will always have 4 moves) - if (pk.CurrentLevel >= 11 && !moves.Contains(48)) // Supersonic - usedslots--; - if (pk.CurrentLevel >= 19 && !moves.Contains(93)) // Confusion - usedslots--; - break; - case (int)Kadabra or (int)Alakazam: // Abra & Kadabra - int catch_rate = ((PK1)pk).Catch_Rate; - if (catch_rate != 100)// Initial Yellow Kadabra Kinesis (move 134) - usedslots--; - if (catch_rate == 200 && pk.CurrentLevel < 20) // Kadabra Disable, not learned until 20 if captured as Abra (move 50) - usedslots--; - break; - case (int)Cubone or (int)Marowak: // Cubone & Marowak - if (!moves.Contains((int)Move.TailWhip) && !moves.Contains((int)Move.Headbutt)) // Initial Red - { - usedslots -=2; - } - else - { - if (!moves.Contains(39)) // Initial Yellow Tail Whip - usedslots--; - if (!moves.Contains(125)) // Initial Yellow Bone Club - usedslots--; - } - if (pk.Species == 105 && pk.CurrentLevel < 33 && !moves.Contains(116)) // Marowak evolved without Focus Energy - usedslots--; - break; - case (int)Chansey: - if (!moves.Contains(39)) // Yellow Initial Tail Whip - usedslots--; - if (!moves.Contains(3)) // Yellow Lvl 12 and Initial Red/Blue Double Slap - usedslots--; - break; - case (int)Mankey when pk.CurrentLevel >= 9 && !moves.Contains(67): // Mankey (Low Kick) - case (int)Pinsir when pk.CurrentLevel >= 21 && !moves.Contains(20): // Pinsir (Bind) - case (int)Gyarados when pk.CurrentLevel < 32: // Gyarados - usedslots--; - break; - default: return usedslots; - } - return usedslots; - } - - private static int GetRequiredMoveCountSpecial(PKM pk, IReadOnlyList moves, IReadOnlyList[] learn, int originalSpecies) - { - // Species with few mandatory slots, species with stone evolutions that could evolve at lower level and do not learn any more moves - // and Pikachu and Nidoran family, those only have mandatory the initial moves and a few have one level up moves, - // every other move could be avoided switching game or evolving - var mandatory = GetRequiredMoveCountLevel(pk, originalSpecies); - switch (pk.Species) - { - case (int)Exeggutor when pk.CurrentLevel >= 28: // Exeggutor - // At level 28 learn different move if is a Exeggute or Exeggutor - if (moves.Contains(73)) - mandatory.Add(73); // Leech Seed level 28 Exeggute - if (moves.Contains(23)) - mandatory.Add(23); // Stomp level 28 Exeggutor - break; - case (int)Pikachu when pk.CurrentLevel >= 33: - mandatory.Add(97); // Pikachu always learns Agility - break; - case (int)Tangela: - mandatory.Add(132); // Tangela always has Constrict as Initial Move - break; - } - - // Add to used slots the non-mandatory moves from the learnset table that the pokemon have learned - return mandatory.Distinct().Count(z => z != 0) + moves.Where(m => m != 0).Count(m => !mandatory.Contains(m) && learn[1].Contains(m)); - } - - private static List GetRequiredMoveCountLevel(PKM pk, int basespecies) - { - int species = pk.Species; - int maxlevel = 1; - int minlevel = 1; - - if (species == (int)Tangela) // Tangela moves before level 32 are different in RB vs Y - { - minlevel = 32; - maxlevel = pk.CurrentLevel; - } - else if (species is >= (int)NidoranF and <= (int)Nidoking && pk.CurrentLevel >= 8) - { - maxlevel = 8; // Always learns a third move at level 8 - } - - if (minlevel > pk.CurrentLevel) - return new List(); - - return MoveLevelUp.GetMovesLevelUp1(basespecies, 0, maxlevel, minlevel); - } - - internal static IEnumerable GetGen2Versions(IEncounterTemplate enc, bool korean) - { - if (ParseSettings.AllowGen2Crystal(korean) && enc.Version is C or GSC) - yield return C; - yield return GS; - } - - internal static IEnumerable GetGen1Versions(IEncounterTemplate enc) - { - if (enc.Species == (int)Eevee && enc.Version == Stadium) - { - // Stadium Eevee; check for RB and yellow initial moves - yield return RB; - yield return YW; - yield break; - } - if (enc.Version == YW) - { - yield return YW; - yield break; - } - - // Any encounter marked with version RBY is for pokemon with the same moves and catch rate in RB and Y, - // it is sufficient to check just RB's case - yield return RB; - } - - private static bool GetCatchRateMatchesPreEvolution(PK1 pkm, int catch_rate) - { - // For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions - var table = EvolutionTree.Evolves1; - var chain = table.GetValidPreEvolutions(pkm, maxLevel: (byte)pkm.CurrentLevel); - foreach (var entry in chain) - { - var s = entry.Species; - if (Species_NotAvailable_CatchRate.Contains(s)) - continue; - if (catch_rate == PersonalTable.RB[s].CatchRate || catch_rate == PersonalTable.Y[s].CatchRate) - return true; - } - - // Krabby encounter trade special catch rate - int species = pkm.Species; - if (catch_rate == 204 && (species is (int)Krabby or (int)Kingler)) - return true; - - if (Stadium_GiftSpecies.Contains(species) && Stadium_CatchRate.Contains(catch_rate)) - return true; - - return false; - } - - /// - /// Checks if the can inhabit - /// - /// Data to check - /// true if can inhabit, false if not. - private static bool CanInhabitGen1(this PKM pkm) - { - // Korean Gen2 games can't trade-back because there are no Gen1 Korean games released - if (pkm.Korean || pkm.IsEgg) - return false; - - // Gen2 format with met data can't receive Gen1 moves, unless Stadium 2 is used (Oak's PC). - // If you put a Pokemon in the N64 box, the met info is retained, even if you switch over to a Gen I game to teach it TMs - // You can use rare candies from within the lab, so level-up moves from RBY context can be learned this way as well - // Stadium 2 is GB Cart Era only (not 3DS Virtual Console). - if (pkm is ICaughtData2 {CaughtData: not 0} && !ParseSettings.AllowGBCartEra) - return false; - - // Sanity check species, if it could have existed as a pre-evolution. - int species = pkm.Species; - if (species <= MaxSpeciesID_1) - return true; - return EvolutionLegality.FutureEvolutionsGen1.Contains(species); - } - - /// - /// Gets the Tradeback status depending on various values. - /// - /// Pokémon to guess the tradeback status from. - internal static PotentialGBOrigin GetTradebackStatusInitial(PKM pkm) - { - if (pkm is PK1 pk1) - return GetTradebackStatusRBY(pk1); - - if (pkm.Format == 2 || pkm.VC2) // Check for impossible tradeback scenarios - return !pkm.CanInhabitGen1() ? PotentialGBOrigin.Gen2Only : PotentialGBOrigin.Either; - - // VC2 is released, we can assume it will be TradebackType.Any. - // Is impossible to differentiate a VC1 pokemon traded to Gen7 after VC2 is available. - // Met Date cannot be used definitively as the player can change their system clock. - return PotentialGBOrigin.Either; - } - - /// - /// Gets the Tradeback status depending on the - /// - /// Pokémon to guess the tradeback status from. - private static PotentialGBOrigin GetTradebackStatusRBY(PK1 pkm) - { - if (!ParseSettings.AllowGen1Tradeback) - return PotentialGBOrigin.Gen1Only; - - // Detect tradeback status by comparing the catch rate(Gen1)/held item(Gen2) to the species in the pkm's evolution chain. - var catch_rate = pkm.Catch_Rate; - if (catch_rate == 0) - return PotentialGBOrigin.Either; - - bool matchAny = GetCatchRateMatchesPreEvolution(pkm, catch_rate); - - if (!matchAny) - return PotentialGBOrigin.Either; - - if (HeldItems_GSC.Contains((ushort)catch_rate)) - return PotentialGBOrigin.Either; - - return PotentialGBOrigin.Gen1Only; - } - - public static TimeCapsuleEvaluation IsTimeCapsuleTransferred(PKM pkm, IList moves, IEncounterTemplate enc) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (moves.Any(z => z != null && z.Generation != enc.Generation && z.Generation <= 2)) - { - if (pkm is PK1 {Catch_Rate: not 0} g1 && !IsTradebackCatchRate(g1.Catch_Rate)) - return TimeCapsuleEvaluation.BadCatchRate; - return enc.Generation == 2 ? TimeCapsuleEvaluation.Transferred21 : TimeCapsuleEvaluation.Transferred12; - } - - if (pkm is not GBPKM gb) - { - return enc.Generation switch - { - 2 when pkm.VC2 => TimeCapsuleEvaluation.Transferred21, - 1 when pkm.VC1 => TimeCapsuleEvaluation.Transferred12, - _ => TimeCapsuleEvaluation.NotTransferred, - }; - } - - if (gb is ICaughtData2 pk2) - { - if (enc.Generation == 1) - return TimeCapsuleEvaluation.Transferred12; - if (pk2.CaughtData != 0) - return TimeCapsuleEvaluation.NotTransferred; - if (enc.Version == C) - return TimeCapsuleEvaluation.Transferred21; - return TimeCapsuleEvaluation.Indeterminate; - } - - if (gb is PK1 pk1) - { - var rate = pk1.Catch_Rate; - if (rate == 0) - return TimeCapsuleEvaluation.Transferred12; - - bool isTradebackItem = IsTradebackCatchRate(rate); - if (IsCatchRateMatchEncounter(enc, pk1)) - return isTradebackItem ? TimeCapsuleEvaluation.Indeterminate : TimeCapsuleEvaluation.NotTransferred; - return isTradebackItem ? TimeCapsuleEvaluation.Transferred12 : TimeCapsuleEvaluation.BadCatchRate; - } + if (enc.Generation == 1) + return TimeCapsuleEvaluation.Transferred12; + if (pk2.CaughtData != 0) + return TimeCapsuleEvaluation.NotTransferred; + if (enc.Version == C) + return TimeCapsuleEvaluation.Transferred21; return TimeCapsuleEvaluation.Indeterminate; } - private static bool IsCatchRateMatchEncounter(IEncounterTemplate enc, PK1 pk1) => enc switch + if (gb is PK1 pk1) { - EncounterStatic1 s when s.GetMatchRating(pk1) != EncounterMatchRating.PartialMatch => true, - EncounterTrade1 => true, - _ => RateMatchesEncounter(enc.Species, enc.Version, pk1.Catch_Rate), - }; + var rate = pk1.Catch_Rate; + if (rate == 0) + return TimeCapsuleEvaluation.Transferred12; - public static bool IsTradebackCatchRate(int rate) - { - return HeldItems_GSC.Contains((ushort)rate); + bool isTradebackItem = IsTradebackCatchRate(rate); + if (IsCatchRateMatchEncounter(enc, pk1)) + return isTradebackItem ? TimeCapsuleEvaluation.Indeterminate : TimeCapsuleEvaluation.NotTransferred; + return isTradebackItem ? TimeCapsuleEvaluation.Transferred12 : TimeCapsuleEvaluation.BadCatchRate; } + return TimeCapsuleEvaluation.Indeterminate; } - public enum PotentialGBOrigin + private static bool IsCatchRateMatchEncounter(IEncounterTemplate enc, PK1 pk1) => enc switch { - Either, - Gen1Only, - Gen2Only, - } + EncounterStatic1 s when s.GetMatchRating(pk1) != EncounterMatchRating.PartialMatch => true, + EncounterTrade1 => true, + _ => RateMatchesEncounter(enc.Species, enc.Version, pk1.Catch_Rate), + }; - public enum TimeCapsuleEvaluation + public static bool IsTradebackCatchRate(int rate) { - Indeterminate, - Transferred21, - Transferred12, - NotTransferred, - BadCatchRate, - } - - public static class TimeCapsuleEvlautationExtensions - { - public static bool WasTimeCapsuleTransferred(this TimeCapsuleEvaluation eval) => eval is not (TimeCapsuleEvaluation.Indeterminate or TimeCapsuleEvaluation.NotTransferred or TimeCapsuleEvaluation.BadCatchRate); + return HeldItems_GSC.Contains((ushort)rate); } } + +public enum PotentialGBOrigin +{ + Either, + Gen1Only, + Gen2Only, +} + +public enum TimeCapsuleEvaluation +{ + Indeterminate, + Transferred21, + Transferred12, + NotTransferred, + BadCatchRate, +} + +public static class TimeCapsuleEvlautationExtensions +{ + public static bool WasTimeCapsuleTransferred(this TimeCapsuleEvaluation eval) => eval is not (TimeCapsuleEvaluation.Indeterminate or TimeCapsuleEvaluation.NotTransferred or TimeCapsuleEvaluation.BadCatchRate); +} diff --git a/PKHeX.Core/Legality/Restrictions/ItemRestrictions.cs b/PKHeX.Core/Legality/Restrictions/ItemRestrictions.cs index 06aa16a4c..1363e52ec 100644 --- a/PKHeX.Core/Legality/Restrictions/ItemRestrictions.cs +++ b/PKHeX.Core/Legality/Restrictions/ItemRestrictions.cs @@ -3,52 +3,51 @@ using static PKHeX.Core.Legal; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information about Held Item Restrictions +/// +public static class ItemRestrictions { /// - /// Information about Held Item Restrictions + /// Checks if a is available to be held in the current . /// - public static class ItemRestrictions + /// Entity data + /// True if able to be held, false if not + public static bool IsHeldItemAllowed(PKM pk) { - /// - /// Checks if a is available to be held in the current . - /// - /// Entity data - /// True if able to be held, false if not - public static bool IsHeldItemAllowed(PKM pk) - { - if (pk is PB7 pb7) // no held items in game - return pb7.HeldItem == 0; - return IsHeldItemAllowed(pk.HeldItem, pk.Format, pk); - } - - /// - /// Checks if an is available to be held in . - /// - /// Held Item ID - /// Generation Number - /// Entity context to check - /// True if able to be held, false if not - public static bool IsHeldItemAllowed(int item, int generation, PKM pk) - { - if (item == 0) - return true; - var items = GetReleasedHeldItems(generation, pk); - return (uint)item < items.Count && items[item]; - } - - private static IReadOnlyList GetReleasedHeldItems(int generation, PKM pk) => generation switch - { - 2 => ReleasedHeldItems_2, - 3 => ReleasedHeldItems_3, - 4 => ReleasedHeldItems_4, - 5 => ReleasedHeldItems_5, - 6 => ReleasedHeldItems_6, - 7 => ReleasedHeldItems_7, - 8 when pk is PA8 => Array.Empty(), - 8 when pk is PB8 => ReleasedHeldItems_8b, - 8 => ReleasedHeldItems_8, - _ => Array.Empty(), - }; + if (pk is PB7 pb7) // no held items in game + return pb7.HeldItem == 0; + return IsHeldItemAllowed(pk.HeldItem, pk.Format, pk); } + + /// + /// Checks if an is available to be held in . + /// + /// Held Item ID + /// Generation Number + /// Entity context to check + /// True if able to be held, false if not + public static bool IsHeldItemAllowed(int item, int generation, PKM pk) + { + if (item == 0) + return true; + var items = GetReleasedHeldItems(generation, pk); + return (uint)item < items.Count && items[item]; + } + + private static IReadOnlyList GetReleasedHeldItems(int generation, PKM pk) => generation switch + { + 2 => ReleasedHeldItems_2, + 3 => ReleasedHeldItems_3, + 4 => ReleasedHeldItems_4, + 5 => ReleasedHeldItems_5, + 6 => ReleasedHeldItems_6, + 7 => ReleasedHeldItems_7, + 8 when pk is PA8 => Array.Empty(), + 8 when pk is PB8 => ReleasedHeldItems_8b, + 8 => ReleasedHeldItems_8, + _ => Array.Empty(), + }; } diff --git a/PKHeX.Core/Legality/Restrictions/Locale3DS.cs b/PKHeX.Core/Legality/Restrictions/Locale3DS.cs index 5e2e06eed..e8837ce95 100644 --- a/PKHeX.Core/Legality/Restrictions/Locale3DS.cs +++ b/PKHeX.Core/Legality/Restrictions/Locale3DS.cs @@ -1,66 +1,65 @@ using static PKHeX.Core.LanguageID; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides information for and data. +/// +/// These values were specific to the 3DS games (Generations 6 and 7, excluding LGP/E) +public static class Locale3DS { /// - /// Provides information for and data. + /// Compares the and to determine if the country is available within that region. /// - /// These values were specific to the 3DS games (Generations 6 and 7, excluding LGP/E) - public static class Locale3DS + /// Console region. + /// Country of nationality + /// Country is within Console Region + public static bool IsConsoleRegionCountryValid(int consoleRegion, int country) => consoleRegion switch { - /// - /// Compares the and to determine if the country is available within that region. - /// - /// Console region. - /// Country of nationality - /// Country is within Console Region - public static bool IsConsoleRegionCountryValid(int consoleRegion, int country) => consoleRegion switch - { - 0 => country is 1, // Japan - 1 => country is (>= 8 and <= 52) or 153 or 156 or 168 or 174 or 186, // Americas - 2 => country is (>= 64 and <= 127) or 169 or 184 or 185, // Europe - 4 => country is 144 or 160, // China - 5 => country is 136, // Korea - 6 => country is 144 or 128, // Taiwan - _ => false, - }; + 0 => country is 1, // Japan + 1 => country is (>= 8 and <= 52) or 153 or 156 or 168 or 174 or 186, // Americas + 2 => country is (>= 64 and <= 127) or 169 or 184 or 185, // Europe + 4 => country is 144 or 160, // China + 5 => country is 136, // Korea + 6 => country is 144 or 128, // Taiwan + _ => false, + }; - /// - /// Compares the and language ID to determine if the language is available within that region. - /// - /// - /// Used to check for Virtual Console language availability. VC Games were region locked to the Console, meaning not all language games are available. - /// - /// Console region. - /// Language of region - /// Language is available within Console Region - public static bool IsRegionLockedLanguageValidVC(int consoleRegion, int language) => consoleRegion switch - { - 0 or 6 => language == 1, // Japan & Taiwan - 1 => language is (int)English or (int)Spanish or (int)French, // Americas - 2 => language is (int)English or (int)Spanish or (int)French or (int)German or (int)Italian, // Europe - 5 => language is (int)Korean, // Korea - _ => false, // China & Invalid - }; + /// + /// Compares the and language ID to determine if the language is available within that region. + /// + /// + /// Used to check for Virtual Console language availability. VC Games were region locked to the Console, meaning not all language games are available. + /// + /// Console region. + /// Language of region + /// Language is available within Console Region + public static bool IsRegionLockedLanguageValidVC(int consoleRegion, int language) => consoleRegion switch + { + 0 or 6 => language == 1, // Japan & Taiwan + 1 => language is (int)English or (int)Spanish or (int)French, // Americas + 2 => language is (int)English or (int)Spanish or (int)French or (int)German or (int)Italian, // Europe + 5 => language is (int)Korean, // Korea + _ => false, // China & Invalid + }; - /// - /// Compares the and to determine if the country is available within that region. - /// - /// - /// Used to check for gift availability. - /// - /// Console region. - /// Language of region - /// Language is available within Console Region - public static bool IsRegionLockedLanguageValid(int consoleRegion, int language) => consoleRegion switch - { - 0 => language is (int)Japanese, // Japan & Taiwan - 1 => language is (int)English or (int)Spanish or (int)French, // Americas - 2 => language is (int)English or (int)Spanish or (int)French or (int)German or (int)Italian, // Europe - 4 => language is (int)ChineseS or (int)ChineseT, // China - 5 => language is (int)Korean, // Korea - 6 => language is (int)ChineseS or (int)ChineseT, - _ => false, // China & Invalid - }; - } + /// + /// Compares the and to determine if the country is available within that region. + /// + /// + /// Used to check for gift availability. + /// + /// Console region. + /// Language of region + /// Language is available within Console Region + public static bool IsRegionLockedLanguageValid(int consoleRegion, int language) => consoleRegion switch + { + 0 => language is (int)Japanese, // Japan & Taiwan + 1 => language is (int)English or (int)Spanish or (int)French, // Americas + 2 => language is (int)English or (int)Spanish or (int)French or (int)German or (int)Italian, // Europe + 4 => language is (int)ChineseS or (int)ChineseT, // China + 5 => language is (int)Korean, // Korea + 6 => language is (int)ChineseS or (int)ChineseT, + _ => false, // China & Invalid + }; } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/Memories.cs b/PKHeX.Core/Legality/Restrictions/Memories/Memories.cs index 245801b62..107dc0ad4 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/Memories.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/Memories.cs @@ -1,44 +1,43 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 6 Memory parameters & validation +/// +public static class Memories { - /// - /// Generation 6 Memory parameters & validation - /// - public static class Memories + public static readonly MemoryContext6 Memory6 = new(); + public static readonly MemoryContext8 Memory8 = new(); + + internal static readonly HashSet MemoryGeneral = new() { 1, 2, 3, 4, 19, 24, 31, 32, 33, 35, 36, 37, 38, 39, 42, 52, 59, 70, 86 }; + private static readonly HashSet MemorySpecific = new() { 6 }; + private static readonly HashSet MemoryMove = new() { 12, 16, 48, 49, 80, 81, 89 }; + private static readonly HashSet MemoryItem = new() { 5, 15, 26, 34, 40, 51, 84, 88 }; + private static readonly HashSet MemorySpecies = new() { 7, 9, 13, 14, 17, 21, 18, 25, 29, 44, 45, 50, 60, 70, 71, 72, 75, 82, 83, 87 }; + + public static MemoryArgType GetMemoryArgType(int memory, int memoryGen) { - public static readonly MemoryContext6 Memory6 = new(); - public static readonly MemoryContext8 Memory8 = new(); - - internal static readonly HashSet MemoryGeneral = new() { 1, 2, 3, 4, 19, 24, 31, 32, 33, 35, 36, 37, 38, 39, 42, 52, 59, 70, 86 }; - private static readonly HashSet MemorySpecific = new() { 6 }; - private static readonly HashSet MemoryMove = new() { 12, 16, 48, 49, 80, 81, 89 }; - private static readonly HashSet MemoryItem = new() { 5, 15, 26, 34, 40, 51, 84, 88 }; - private static readonly HashSet MemorySpecies = new() { 7, 9, 13, 14, 17, 21, 18, 25, 29, 44, 45, 50, 60, 70, 71, 72, 75, 82, 83, 87 }; - - public static MemoryArgType GetMemoryArgType(int memory, int memoryGen) + if (MemoryGeneral.Contains(memory)) return MemoryArgType.GeneralLocation; + if (MemorySpecific.Contains(memory)) { - if (MemoryGeneral.Contains(memory)) return MemoryArgType.GeneralLocation; - if (MemorySpecific.Contains(memory)) - { - if (memoryGen <= 7) - return MemoryArgType.SpecificLocation; - return MemoryArgType.GeneralLocation; - } - - if (MemoryItem.Contains(memory)) return MemoryArgType.Item; - if (MemoryMove.Contains(memory)) return MemoryArgType.Move; - if (MemorySpecies.Contains(memory)) return MemoryArgType.Species; - - return MemoryArgType.None; + if (memoryGen <= 7) + return MemoryArgType.SpecificLocation; + return MemoryArgType.GeneralLocation; } - public static MemoryContext GetContext(int memoryGen) => memoryGen switch - { - <=7 => Memory6, - _ => Memory8, - }; + if (MemoryItem.Contains(memory)) return MemoryArgType.Item; + if (MemoryMove.Contains(memory)) return MemoryArgType.Move; + if (MemorySpecies.Contains(memory)) return MemoryArgType.Species; - public static IEnumerable GetMemoryItemParams(int memoryGen) => GetContext(memoryGen).GetMemoryItemParams(); + return MemoryArgType.None; } + + public static MemoryContext GetContext(int memoryGen) => memoryGen switch + { + <=7 => Memory6, + _ => Memory8, + }; + + public static IEnumerable GetMemoryItemParams(int memoryGen) => GetContext(memoryGen).GetMemoryItemParams(); } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext.cs index 819c78f1e..eebd8ecb4 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext.cs @@ -1,30 +1,29 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class MemoryContext { - public abstract class MemoryContext - { - public abstract IEnumerable GetMemoryItemParams(); - public abstract IEnumerable GetKeyItemParams(); + public abstract IEnumerable GetMemoryItemParams(); + public abstract IEnumerable GetKeyItemParams(); - public abstract bool CanUseItemGeneric(int item); - public abstract bool IsUsedKeyItemUnspecific(int item); - public abstract bool IsUsedKeyItemSpecific(int item, int species); - public abstract bool CanBuyItem(int item, GameVersion version); - public abstract bool CanWinLotoID(int item); - public virtual bool CanPlantBerry(int item) => false; - public abstract bool CanHoldItem(int item); + public abstract bool CanUseItemGeneric(int item); + public abstract bool IsUsedKeyItemUnspecific(int item); + public abstract bool IsUsedKeyItemSpecific(int item, int species); + public abstract bool CanBuyItem(int item, GameVersion version); + public abstract bool CanWinLotoID(int item); + public virtual bool CanPlantBerry(int item) => false; + public abstract bool CanHoldItem(int item); - public abstract bool CanObtainMemory(byte memory); - public abstract bool CanObtainMemoryOT(GameVersion pkmVersion, byte memory); - public abstract bool CanObtainMemoryHT(GameVersion pkmVersion, byte memory); + public abstract bool CanObtainMemory(byte memory); + public abstract bool CanObtainMemoryOT(GameVersion pkmVersion, byte memory); + public abstract bool CanObtainMemoryHT(GameVersion pkmVersion, byte memory); - public abstract bool HasPokeCenter(GameVersion version, int location); - public abstract bool IsInvalidGeneralLocationMemoryValue(byte memory, ushort variable, IEncounterTemplate enc, PKM pk); - public abstract bool IsInvalidMiscMemory(byte memory, ushort variable); + public abstract bool HasPokeCenter(GameVersion version, int location); + public abstract bool IsInvalidGeneralLocationMemoryValue(byte memory, ushort variable, IEncounterTemplate enc, PKM pk); + public abstract bool IsInvalidMiscMemory(byte memory, ushort variable); - public abstract bool CanHaveIntensity(byte memory, byte intensity); - public abstract bool CanHaveFeeling(byte memory, byte feeling, ushort argument); - public abstract int GetMinimumIntensity(byte memory); - } + public abstract bool CanHaveIntensity(byte memory, byte intensity); + public abstract bool CanHaveFeeling(byte memory, byte feeling, ushort argument); + public abstract int GetMinimumIntensity(byte memory); } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6.cs index 4444223b0..d77227ef5 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6.cs @@ -1,125 +1,124 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed partial class MemoryContext6 : MemoryContext { - public sealed partial class MemoryContext6 : MemoryContext + private const int MAX_MEMORY_ID_XY = 64; + private const int MAX_MEMORY_ID_AO = 69; + + private static ICollection GetPokeCenterLocations(GameVersion game) { - private const int MAX_MEMORY_ID_XY = 64; - private const int MAX_MEMORY_ID_AO = 69; - - private static ICollection GetPokeCenterLocations(GameVersion game) - { - return GameVersion.XY.Contains(game) ? LocationsWithPokeCenter_XY : LocationsWithPokeCenter_AO; - } - - public static bool GetHasPokeCenterLocation(GameVersion game, int loc) - { - if (game == GameVersion.Any) - return GetHasPokeCenterLocation(GameVersion.X, loc) || GetHasPokeCenterLocation(GameVersion.AS, loc); - return GetPokeCenterLocations(game).Contains(loc); - } - - public static int GetMemoryRarity(int memory) => (uint)memory >= MemoryRandChance.Length ? -1 : MemoryRandChance[memory]; - - public override IEnumerable GetKeyItemParams() => KeyItemUsableObserve6.Concat(KeyItemMemoryArgsGen6.Values.SelectMany(z => z)).Distinct(); - - public override bool CanUseItemGeneric(int item) - { - // Key Item usage while in party on another species. - if (KeyItemUsableObserve6.Contains((ushort)item)) - return true; - if (KeyItemMemoryArgsGen6.Values.Any(z => z.Contains((ushort)item))) - return true; - - return true; // todo - } - - public override IEnumerable GetMemoryItemParams() => Legal.HeldItem_AO.Distinct() - .Concat(GetKeyItemParams()) - .Concat(Legal.TMHM_AO.Take(100).Select(z => (ushort)z)) - .Where(z => z <= Legal.MaxItemID_6_AO); - - public override bool IsUsedKeyItemUnspecific(int item) => KeyItemUsableObserve6.Contains((ushort)item); - public override bool IsUsedKeyItemSpecific(int item, int species) => KeyItemMemoryArgsGen6.TryGetValue(species, out var value) && value.Contains((ushort)item); - - public override bool CanPlantBerry(int item) => Legal.Pouch_Berry_XY.Contains((ushort)item); - public override bool CanHoldItem(int item) => Legal.HeldItem_AO.Contains((ushort)item); - - public override bool CanObtainMemoryOT(GameVersion pkmVersion, byte memory) => pkmVersion switch - { - GameVersion.X or GameVersion.Y => CanObtainMemoryXY(memory), - GameVersion.OR or GameVersion.AS => CanObtainMemoryAO(memory), - _ => false, - }; - - public override bool CanObtainMemory(byte memory) => memory <= MAX_MEMORY_ID_AO; - public override bool HasPokeCenter(GameVersion version, int location) => GetHasPokeCenterLocation(version, location); - - public override bool IsInvalidGeneralLocationMemoryValue(byte memory, ushort variable, IEncounterTemplate enc, PKM pk) - { - return false; // todo - } - - public override bool IsInvalidMiscMemory(byte memory, ushort variable) - { - return false; // todo - } - - private static bool CanObtainMemoryAO(int memory) => memory <= MAX_MEMORY_ID_AO && !Memory_NotAO.Contains(memory); - private static bool CanObtainMemoryXY(int memory) => memory <= MAX_MEMORY_ID_XY && !Memory_NotXY.Contains(memory); - public override bool CanObtainMemoryHT(GameVersion pkmVersion, byte memory) => CanObtainMemory(memory); - - public override bool CanWinLotoID(int item) => LotoPrizeXYAO.Contains((ushort)item); - - public override bool CanBuyItem(int item, GameVersion version) - { - if (version is GameVersion.Any) - return PurchaseableItemAO.Contains((ushort)item) || PurchaseableItemXY.Contains((ushort)item); - if (version is GameVersion.X or GameVersion.Y) - return PurchaseableItemXY.Contains((ushort)item); - if (version is GameVersion.AS or GameVersion.OR) - return PurchaseableItemAO.Contains((ushort)item); - return false; - } - - public static bool CanHaveFeeling6(int memory, int feeling, int argument) - { - if (memory >= MemoryFeelings.Length) - return false; - if (memory == 4 && argument == 0) // Bank "came through Link Trade" @ "somewhere" is rand(0,10); doesn't use the bit-table to pick a feeling. - return (uint)feeling < 10; - return (MemoryFeelings[memory] & (1 << feeling)) != 0; - } - - public static bool CanHaveIntensity6(int memory, int intensity) - { - if (memory >= MemoryFeelings.Length) - return false; - return MemoryMinIntensity[memory] <= intensity; - } - - public static byte GetRandomFeeling6(int memory, int max = 24) - { - var bits = MemoryFeelings[memory]; - var rnd = Util.Rand; - while (true) - { - int feel = rnd.Next(max); - if ((bits & (1 << feel)) != 0) - return (byte)feel; - } - } - - public static int GetMinimumIntensity6(int memory) - { - if (memory >= MemoryMinIntensity.Length) - return -1; - return MemoryMinIntensity[memory]; - } - - public override bool CanHaveIntensity(byte memory, byte intensity) => CanHaveIntensity6(memory, intensity); - public override bool CanHaveFeeling(byte memory, byte feeling, ushort argument) => CanHaveFeeling6(memory, feeling, argument); - public override int GetMinimumIntensity(byte memory) => GetMinimumIntensity6(memory); + return GameVersion.XY.Contains(game) ? LocationsWithPokeCenter_XY : LocationsWithPokeCenter_AO; } + + public static bool GetHasPokeCenterLocation(GameVersion game, int loc) + { + if (game == GameVersion.Any) + return GetHasPokeCenterLocation(GameVersion.X, loc) || GetHasPokeCenterLocation(GameVersion.AS, loc); + return GetPokeCenterLocations(game).Contains(loc); + } + + public static int GetMemoryRarity(int memory) => (uint)memory >= MemoryRandChance.Length ? -1 : MemoryRandChance[memory]; + + public override IEnumerable GetKeyItemParams() => KeyItemUsableObserve6.Concat(KeyItemMemoryArgsGen6.Values.SelectMany(z => z)).Distinct(); + + public override bool CanUseItemGeneric(int item) + { + // Key Item usage while in party on another species. + if (KeyItemUsableObserve6.Contains((ushort)item)) + return true; + if (KeyItemMemoryArgsGen6.Values.Any(z => z.Contains((ushort)item))) + return true; + + return true; // todo + } + + public override IEnumerable GetMemoryItemParams() => Legal.HeldItem_AO.Distinct() + .Concat(GetKeyItemParams()) + .Concat(Legal.TMHM_AO.Take(100).Select(z => (ushort)z)) + .Where(z => z <= Legal.MaxItemID_6_AO); + + public override bool IsUsedKeyItemUnspecific(int item) => KeyItemUsableObserve6.Contains((ushort)item); + public override bool IsUsedKeyItemSpecific(int item, int species) => KeyItemMemoryArgsGen6.TryGetValue(species, out var value) && value.Contains((ushort)item); + + public override bool CanPlantBerry(int item) => Legal.Pouch_Berry_XY.Contains((ushort)item); + public override bool CanHoldItem(int item) => Legal.HeldItem_AO.Contains((ushort)item); + + public override bool CanObtainMemoryOT(GameVersion pkmVersion, byte memory) => pkmVersion switch + { + GameVersion.X or GameVersion.Y => CanObtainMemoryXY(memory), + GameVersion.OR or GameVersion.AS => CanObtainMemoryAO(memory), + _ => false, + }; + + public override bool CanObtainMemory(byte memory) => memory <= MAX_MEMORY_ID_AO; + public override bool HasPokeCenter(GameVersion version, int location) => GetHasPokeCenterLocation(version, location); + + public override bool IsInvalidGeneralLocationMemoryValue(byte memory, ushort variable, IEncounterTemplate enc, PKM pk) + { + return false; // todo + } + + public override bool IsInvalidMiscMemory(byte memory, ushort variable) + { + return false; // todo + } + + private static bool CanObtainMemoryAO(int memory) => memory <= MAX_MEMORY_ID_AO && !Memory_NotAO.Contains(memory); + private static bool CanObtainMemoryXY(int memory) => memory <= MAX_MEMORY_ID_XY && !Memory_NotXY.Contains(memory); + public override bool CanObtainMemoryHT(GameVersion pkmVersion, byte memory) => CanObtainMemory(memory); + + public override bool CanWinLotoID(int item) => LotoPrizeXYAO.Contains((ushort)item); + + public override bool CanBuyItem(int item, GameVersion version) + { + if (version is GameVersion.Any) + return PurchaseableItemAO.Contains((ushort)item) || PurchaseableItemXY.Contains((ushort)item); + if (version is GameVersion.X or GameVersion.Y) + return PurchaseableItemXY.Contains((ushort)item); + if (version is GameVersion.AS or GameVersion.OR) + return PurchaseableItemAO.Contains((ushort)item); + return false; + } + + public static bool CanHaveFeeling6(int memory, int feeling, int argument) + { + if (memory >= MemoryFeelings.Length) + return false; + if (memory == 4 && argument == 0) // Bank "came through Link Trade" @ "somewhere" is rand(0,10); doesn't use the bit-table to pick a feeling. + return (uint)feeling < 10; + return (MemoryFeelings[memory] & (1 << feeling)) != 0; + } + + public static bool CanHaveIntensity6(int memory, int intensity) + { + if (memory >= MemoryFeelings.Length) + return false; + return MemoryMinIntensity[memory] <= intensity; + } + + public static byte GetRandomFeeling6(int memory, int max = 24) + { + var bits = MemoryFeelings[memory]; + var rnd = Util.Rand; + while (true) + { + int feel = rnd.Next(max); + if ((bits & (1 << feel)) != 0) + return (byte)feel; + } + } + + public static int GetMinimumIntensity6(int memory) + { + if (memory >= MemoryMinIntensity.Length) + return -1; + return MemoryMinIntensity[memory]; + } + + public override bool CanHaveIntensity(byte memory, byte intensity) => CanHaveIntensity6(memory, intensity); + public override bool CanHaveFeeling(byte memory, byte feeling, ushort argument) => CanHaveFeeling6(memory, feeling, argument); + public override int GetMinimumIntensity(byte memory) => GetMinimumIntensity6(memory); } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6Data.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6Data.cs index 73db49e84..ecfaf07f9 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6Data.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext6Data.cs @@ -1,165 +1,164 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +// data tables stored separately! +public partial class MemoryContext6 { - // data tables stored separately! - public partial class MemoryContext6 + private static readonly int[] Memory_NotXY = { - private static readonly int[] Memory_NotXY = - { - 65, // {0} was with {1} when (he/she) built a Secret Base. {4} that {3}. - 66, // {0} participated in a contest with {1} and impressed many people. {4} that {3}. - 67, // {0} participated in a contest with {1} and won the title. {4} that {3}. - 68, // {0} soared through the sky with {1} and went to many different places. {4} that {3}. - 69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}. - }; + 65, // {0} was with {1} when (he/she) built a Secret Base. {4} that {3}. + 66, // {0} participated in a contest with {1} and impressed many people. {4} that {3}. + 67, // {0} participated in a contest with {1} and won the title. {4} that {3}. + 68, // {0} soared through the sky with {1} and went to many different places. {4} that {3}. + 69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}. + }; - private static readonly int[] Memory_NotAO = - { - 11, // {0} went clothes shopping with {1}. {4} that {3}. - 43, // {0} was impressed by the speed of the train it took with {1}. {4} that {3}. - 44, // {0} encountered {2} with {1} using the Poké Radar. {4} that {3}. - 56, // {0} was with {1} when (he/she) went to a boutique and tried on clothes, but (he/she) left the boutique without buying anything. {4} that {3}. - 57, // {0} went to a nice restaurant with {1} and ate until it got totally full. {4} that {3}. - 62, // {0} saw itself in a mirror in a mirror cave that it went to with {1}. {4} that {3}. - }; + private static readonly int[] Memory_NotAO = + { + 11, // {0} went clothes shopping with {1}. {4} that {3}. + 43, // {0} was impressed by the speed of the train it took with {1}. {4} that {3}. + 44, // {0} encountered {2} with {1} using the Poké Radar. {4} that {3}. + 56, // {0} was with {1} when (he/she) went to a boutique and tried on clothes, but (he/she) left the boutique without buying anything. {4} that {3}. + 57, // {0} went to a nice restaurant with {1} and ate until it got totally full. {4} that {3}. + 62, // {0} saw itself in a mirror in a mirror cave that it went to with {1}. {4} that {3}. + }; - internal static readonly byte[] MoveSpecificMemoryHM = // Ordered by HM index for bitflag checks. - { - 36, // {0} proudly used Cut at {1}’s instruction in... {2}. {4} that {3}. - 24, // {0} flew, carrying {1} on its back, to {2}. {4} that {3}. - 20, // {0} surfed across the water, carrying {1} on its back. {4} that {3}. - 35, // {0} proudly used Strength at {1}’s instruction in... {2}. {4} that {3}. - 38, // {0} used Waterfall while carrying {1} on its back in... {2}. {4} that {3}. - 37, // {0} shattered rocks to its heart’s content at {1}’s instruction in... {2}. {4} that {3}. - 69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}. - }; + internal static readonly byte[] MoveSpecificMemoryHM = // Ordered by HM index for bitflag checks. + { + 36, // {0} proudly used Cut at {1}’s instruction in... {2}. {4} that {3}. + 24, // {0} flew, carrying {1} on its back, to {2}. {4} that {3}. + 20, // {0} surfed across the water, carrying {1} on its back. {4} that {3}. + 35, // {0} proudly used Strength at {1}’s instruction in... {2}. {4} that {3}. + 38, // {0} used Waterfall while carrying {1} on its back in... {2}. {4} that {3}. + 37, // {0} shattered rocks to its heart’s content at {1}’s instruction in... {2}. {4} that {3}. + 69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}. + }; - /// - /// Kalos locations with a Pokémon Center - /// - private static readonly int[] LocationsWithPokeCenter_XY = - { - // Kalos locations with a PKMN CENTER - 018, // Santalune City - 022, // Lumiose City - 030, // Camphrier Town - 040, // Cyllage City - 044, // Ambrette Town - 052, // Geosenge Towny - 058, // Shalour City - 064, // Coumarine City - 070, // Laverre City - 076, // Dendemille Town - 086, // Anistar City - 090, // Couriway Town - 094, // Snowbelle City - 106, // Pokémon League (X/Y) - }; + /// + /// Kalos locations with a Pokémon Center + /// + private static readonly int[] LocationsWithPokeCenter_XY = + { + // Kalos locations with a PKMN CENTER + 018, // Santalune City + 022, // Lumiose City + 030, // Camphrier Town + 040, // Cyllage City + 044, // Ambrette Town + 052, // Geosenge Towny + 058, // Shalour City + 064, // Coumarine City + 070, // Laverre City + 076, // Dendemille Town + 086, // Anistar City + 090, // Couriway Town + 094, // Snowbelle City + 106, // Pokémon League (X/Y) + }; - /// - /// Hoenn locations with a Pokémon Center - /// - private static readonly int[] LocationsWithPokeCenter_AO = - { - // Hoenn locations with a PKMN CENTER - 172, // Oldale Town - 174, // Dewford Town - 176, // Lavaridge Town - 178, // Fallarbor Town - 180, // Verdanturf Town - 182, // Pacifidlog Town - 184, // Petalburg City - 186, // Slateport City - 188, // Mauville City - 190, // Rustboro City - 192, // Fortree City - 194, // Lilycove City - 196, // Mossdeep City - 198, // Sootopolis City - 200, // Ever Grande City - 202, // Pokémon League (OR/AS) - }; + /// + /// Hoenn locations with a Pokémon Center + /// + private static readonly int[] LocationsWithPokeCenter_AO = + { + // Hoenn locations with a PKMN CENTER + 172, // Oldale Town + 174, // Dewford Town + 176, // Lavaridge Town + 178, // Fallarbor Town + 180, // Verdanturf Town + 182, // Pacifidlog Town + 184, // Petalburg City + 186, // Slateport City + 188, // Mauville City + 190, // Rustboro City + 192, // Fortree City + 194, // Lilycove City + 196, // Mossdeep City + 198, // Sootopolis City + 200, // Ever Grande City + 202, // Pokémon League (OR/AS) + }; - private static readonly byte[] MemoryMinIntensity = - { - 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, - 3, 3, 3, 3, 3, 3, 3, 4, 5, 5, - 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 1, 3, 2, 2, 4, 3, 4, 4, - 4, 4, 2, 4, 2, 4, 3, 3, 4, 2, - 3, 3, 3, 3, 3, 2, 3, 4, 4, 2, - }; + private static readonly byte[] MemoryMinIntensity = + { + 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 4, 5, 5, + 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 1, 3, 2, 2, 4, 3, 4, 4, + 4, 4, 2, 4, 2, 4, 3, 3, 4, 2, + 3, 3, 3, 3, 3, 2, 3, 4, 4, 2, + }; - private static readonly byte[] MemoryRandChance = - { - 000, 100, 100, 100, 100, 005, 005, 005, 005, 005, - 005, 005, 005, 005, 010, 020, 010, 001, 050, 030, - 005, 005, 020, 005, 005, 005, 001, 050, 100, 050, - 050, 002, 002, 005, 005, 005, 005, 005, 005, 002, - 020, 020, 005, 010, 001, 001, 050, 030, 020, 020, - 010, 010, 001, 010, 001, 050, 030, 030, 030, 002, - 050, 020, 020, 020, 020, 010, 010, 050, 020, 005, - }; + private static readonly byte[] MemoryRandChance = + { + 000, 100, 100, 100, 100, 005, 005, 005, 005, 005, + 005, 005, 005, 005, 010, 020, 010, 001, 050, 030, + 005, 005, 020, 005, 005, 005, 001, 050, 100, 050, + 050, 002, 002, 005, 005, 005, 005, 005, 005, 002, + 020, 020, 005, 010, 001, 001, 050, 030, 020, 020, + 010, 010, 001, 010, 001, 050, 030, 030, 030, 002, + 050, 020, 020, 020, 020, 010, 010, 050, 020, 005, + }; - /// - /// 24bits of flags allowing certain feelings for a given memory index. - /// - private static readonly uint[] MemoryFeelings = - { - 0x000000, 0x04CBFD, 0x004BFD, 0x04CBFD, 0x04CBFD, 0xFFFBFB, 0x84FFF9, 0x47FFFF, 0xBF7FFA, 0x7660B0, - 0x80BDF9, 0x88FB7A, 0x083F79, 0x0001FE, 0xCFEFFF, 0x84EBAF, 0xB368B0, 0x091F7E, 0x0320A0, 0x080DDD, - 0x081A7B, 0x404030, 0x0FFFFF, 0x9A08BC, 0x089A7B, 0x0032AA, 0x80FF7A, 0x0FFFFF, 0x0805FD, 0x098278, - 0x0B3FFF, 0x8BBFFA, 0x8BBFFE, 0x81A97C, 0x8BB97C, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0xAC3ABE, - 0xBFFFFF, 0x8B837C, 0x848AFA, 0x88FFFE, 0x8B0B7C, 0xB76AB2, 0x8B1FFF, 0xBE7AB8, 0xB77EB8, 0x8C9FFD, - 0xBF9BFF, 0xF408B0, 0xBCFE7A, 0x8F3F72, 0x90DB7A, 0xBCEBFF, 0xBC5838, 0x9C3FFE, 0x9CFFFF, 0x96D83A, - 0xB770B0, 0x881F7A, 0x839F7A, 0x839F7A, 0x839F7A, 0x53897F, 0x41BB6F, 0x0C35FF, 0x8BBF7F, 0x8BBF7F, - }; + /// + /// 24bits of flags allowing certain feelings for a given memory index. + /// + private static readonly uint[] MemoryFeelings = + { + 0x000000, 0x04CBFD, 0x004BFD, 0x04CBFD, 0x04CBFD, 0xFFFBFB, 0x84FFF9, 0x47FFFF, 0xBF7FFA, 0x7660B0, + 0x80BDF9, 0x88FB7A, 0x083F79, 0x0001FE, 0xCFEFFF, 0x84EBAF, 0xB368B0, 0x091F7E, 0x0320A0, 0x080DDD, + 0x081A7B, 0x404030, 0x0FFFFF, 0x9A08BC, 0x089A7B, 0x0032AA, 0x80FF7A, 0x0FFFFF, 0x0805FD, 0x098278, + 0x0B3FFF, 0x8BBFFA, 0x8BBFFE, 0x81A97C, 0x8BB97C, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0xAC3ABE, + 0xBFFFFF, 0x8B837C, 0x848AFA, 0x88FFFE, 0x8B0B7C, 0xB76AB2, 0x8B1FFF, 0xBE7AB8, 0xB77EB8, 0x8C9FFD, + 0xBF9BFF, 0xF408B0, 0xBCFE7A, 0x8F3F72, 0x90DB7A, 0xBCEBFF, 0xBC5838, 0x9C3FFE, 0x9CFFFF, 0x96D83A, + 0xB770B0, 0x881F7A, 0x839F7A, 0x839F7A, 0x839F7A, 0x53897F, 0x41BB6F, 0x0C35FF, 0x8BBF7F, 0x8BBF7F, + }; - private static readonly Dictionary KeyItemMemoryArgsGen6 = new() - { - {(int) Species.Shaymin, new ushort[] {466}}, // Gracidea - {(int) Species.Tornadus, new ushort[] {638}}, // Reveal Glass - {(int) Species.Thundurus, new ushort[] {638}}, // Reveal Glass - {(int) Species.Landorus, new ushort[] {638}}, // Reveal Glass - {(int) Species.Kyurem, new ushort[] {628, 629}}, // DNA Splicers - {(int) Species.Hoopa, new ushort[] {765}}, // Prison Bottle - }; + private static readonly Dictionary KeyItemMemoryArgsGen6 = new() + { + {(int) Species.Shaymin, new ushort[] {466}}, // Gracidea + {(int) Species.Tornadus, new ushort[] {638}}, // Reveal Glass + {(int) Species.Thundurus, new ushort[] {638}}, // Reveal Glass + {(int) Species.Landorus, new ushort[] {638}}, // Reveal Glass + {(int) Species.Kyurem, new ushort[] {628, 629}}, // DNA Splicers + {(int) Species.Hoopa, new ushort[] {765}}, // Prison Bottle + }; - private static readonly ushort[] KeyItemUsableObserve6 = - { - 775, // Eon Flute - }; + private static readonly ushort[] KeyItemUsableObserve6 = + { + 775, // Eon Flute + }; - private static readonly HashSet PurchaseableItemXY = new() - { - 002, 003, 004, 006, 007, 008, 009, 010, 011, 012, - 013, 014, 015, 017, 018, 019, 020, 021, 022, 023, - 024, 025, 026, 027, 028, 034, 035, 036, 037, 045, - 046, 047, 048, 049, 052, 055, 056, 057, 058, 059, - 060, 061, 062, 076, 077, 078, 079, 082, 084, 085, - 254, 255, 314, 315, 316, 317, 318, 319, 320, 334, - 338, 341, 342, 343, 345, 347, 352, 355, 360, 364, - 365, 377, 379, 395, 402, 403, 405, 411, 618, - }; + private static readonly HashSet PurchaseableItemXY = new() + { + 002, 003, 004, 006, 007, 008, 009, 010, 011, 012, + 013, 014, 015, 017, 018, 019, 020, 021, 022, 023, + 024, 025, 026, 027, 028, 034, 035, 036, 037, 045, + 046, 047, 048, 049, 052, 055, 056, 057, 058, 059, + 060, 061, 062, 076, 077, 078, 079, 082, 084, 085, + 254, 255, 314, 315, 316, 317, 318, 319, 320, 334, + 338, 341, 342, 343, 345, 347, 352, 355, 360, 364, + 365, 377, 379, 395, 402, 403, 405, 411, 618, + }; - private static readonly HashSet PurchaseableItemAO = new() - { - 002, 003, 004, 006, 007, 008, 009, 010, 011, 013, - 014, 015, 017, 018, 019, 020, 021, 022, 023, 024, - 025, 026, 027, 028, 034, 035, 036, 037, 045, 046, - 047, 048, 049, 052, 055, 056, 057, 058, 059, 060, - 061, 062, 063, 064, 076, 077, 078, 079, 254, 255, - 314, 315, 316, 317, 318, 319, 320, 328, 336, 341, - 342, 343, 344, 347, 352, 360, 365, 367, 369, 374, - 379, 384, 395, 398, 400, 403, 405, 409, 577, 692, - 694, - }; + private static readonly HashSet PurchaseableItemAO = new() + { + 002, 003, 004, 006, 007, 008, 009, 010, 011, 013, + 014, 015, 017, 018, 019, 020, 021, 022, 023, 024, + 025, 026, 027, 028, 034, 035, 036, 037, 045, 046, + 047, 048, 049, 052, 055, 056, 057, 058, 059, 060, + 061, 062, 063, 064, 076, 077, 078, 079, 254, 255, + 314, 315, 316, 317, 318, 319, 320, 328, 336, 341, + 342, 343, 344, 347, 352, 360, 365, 367, 369, 374, + 379, 384, 395, 398, 400, 403, 405, 409, 577, 692, + 694, + }; - private static readonly ushort[] LotoPrizeXYAO = - { - 0001, 0033, 0050, 0051, 0053, - }; - } + private static readonly ushort[] LotoPrizeXYAO = + { + 0001, 0033, 0050, 0051, 0053, + }; } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs index cd1de5510..cd84dede1 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8.cs @@ -1,184 +1,183 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed partial class MemoryContext8 : MemoryContext { - public sealed partial class MemoryContext8 : MemoryContext + private const int MAX_MEMORY_ID_SWSH = 89; + + public override IEnumerable GetKeyItemParams() => (KeyItemMemoryArgsGen8.Values).SelectMany(z => z).Distinct(); + + public override IEnumerable GetMemoryItemParams() => Legal.HeldItem_AO.Concat(Legal.HeldItems_SWSH).Distinct() + .Concat(GetKeyItemParams()) + .Concat(Legal.TMHM_AO.Take(100).Select(z => (ushort)z)) + .Where(z => z <= Legal.MaxItemID_8_R2); + + public override bool CanUseItemGeneric(int item) => true; // todo + + public override bool IsUsedKeyItemUnspecific(int item) => false; + public override bool IsUsedKeyItemSpecific(int item, int species) => KeyItemMemoryArgsGen8.TryGetValue(species, out var value) && value.Contains((ushort)item); + public override bool CanHoldItem(int item) => Legal.HeldItems_SWSH.Contains((ushort)item); + + public override bool CanObtainMemoryOT(GameVersion pkmVersion, byte memory) => pkmVersion switch { - private const int MAX_MEMORY_ID_SWSH = 89; + GameVersion.SW or GameVersion.SH => CanObtainMemorySWSH(memory), + _ => memory is 0, + }; - public override IEnumerable GetKeyItemParams() => (KeyItemMemoryArgsGen8.Values).SelectMany(z => z).Distinct(); + public override bool CanObtainMemoryHT(GameVersion pkmVersion, byte memory) => CanObtainMemorySWSH(memory); - public override IEnumerable GetMemoryItemParams() => Legal.HeldItem_AO.Concat(Legal.HeldItems_SWSH).Distinct() - .Concat(GetKeyItemParams()) - .Concat(Legal.TMHM_AO.Take(100).Select(z => (ushort)z)) - .Where(z => z <= Legal.MaxItemID_8_R2); + public override bool CanObtainMemory(byte memory) => CanObtainMemorySWSH(memory); + public override bool HasPokeCenter(GameVersion version, int location) => location == 9; // in a Pokémon Center - public override bool CanUseItemGeneric(int item) => true; // todo - - public override bool IsUsedKeyItemUnspecific(int item) => false; - public override bool IsUsedKeyItemSpecific(int item, int species) => KeyItemMemoryArgsGen8.TryGetValue(species, out var value) && value.Contains((ushort)item); - public override bool CanHoldItem(int item) => Legal.HeldItems_SWSH.Contains((ushort)item); - - public override bool CanObtainMemoryOT(GameVersion pkmVersion, byte memory) => pkmVersion switch - { - GameVersion.SW or GameVersion.SH => CanObtainMemorySWSH(memory), - _ => memory is 0, - }; - - public override bool CanObtainMemoryHT(GameVersion pkmVersion, byte memory) => CanObtainMemorySWSH(memory); - - public override bool CanObtainMemory(byte memory) => CanObtainMemorySWSH(memory); - public override bool HasPokeCenter(GameVersion version, int location) => location == 9; // in a Pokémon Center - - public override bool IsInvalidGeneralLocationMemoryValue(byte memory, ushort variable, IEncounterTemplate enc, PKM pk) - { - if (!Memories.MemoryGeneral.Contains(memory)) - return false; - - if (memory is 1 or 2 or 3) // Encounter only - return IsInvalidGenLoc8(memory, pk.Met_Location, pk.Egg_Location, variable, pk, enc); - return IsInvalidGenLoc8Other(memory, variable); - } - - public override bool IsInvalidMiscMemory(byte memory, ushort variable) - { - return memory switch - { - // {0} encountered {2} when it was with {1}. {4} that {3}. - 29 when variable is not (888 or 889 or 890 or 898) => true, // Zacian, Zamazenta, Eternatus, Calyrex - _ => false, - }; - } - - private static bool CanObtainMemorySWSH(int memory) => memory <= MAX_MEMORY_ID_SWSH && !Memory_NotSWSH.Contains(memory); - - public override bool CanWinLotoID(int item) => LotoPrizeSWSH.Contains((ushort)item); - - public override bool CanBuyItem(int item, GameVersion version) => item switch - { - 1085 => version is GameVersion.SW or GameVersion.Any, // Bob's Food Tin - 1086 => version is GameVersion.SH or GameVersion.Any, // Bach's Food Tin - _ => PurchaseableItemSWSH.Contains((ushort)item), - }; - - private static bool IsInvalidGenLoc8(byte memory, int loc, int egg, ushort variable, PKM pk, IEncounterTemplate enc) - { - if (variable > 255) - return true; - - switch (memory) - { - case 1 when !IsWildEncounter(pk, enc): - case 2 when !enc.EggEncounter: - case 3 when !IsWildEncounterMeet(pk, enc): - return true; - } - - var arg = (byte)variable; - if (loc > 255) // gift - return memory != 3 || !PossibleGeneralLocations8.Contains(arg); - if (memory == 2 && egg == 0) - return true; - if (loc is Encounters8Nest.SharedNest) - return !PossibleGeneralLocations8.Contains(arg) || arg is 79; // dangerous place - all locations are Y-Comm locked - if (SingleGenLocAreas.TryGetValue((byte)loc, out var value)) - return arg != value; - if (MultiGenLocAreas.TryGetValue((byte)loc, out var arr)) - return !arr.Contains(arg); + public override bool IsInvalidGeneralLocationMemoryValue(byte memory, ushort variable, IEncounterTemplate enc, PKM pk) + { + if (!Memories.MemoryGeneral.Contains(memory)) return false; - } - private static bool IsWildEncounterMeet(PKM pk, IEncounterTemplate enc) - { - if (enc is EncounterTrade or EncounterStatic { Gift: true } or WC8 { IsHOMEGift: false }) - return true; - if (IsCurryEncounter(pk, enc)) - return true; - return false; - } - - private static bool IsWildEncounter(PKM pk, IEncounterTemplate enc) - { - if (enc is not (EncounterSlot or EncounterStatic { Gift: false })) - return false; - if (pk is IRibbonSetMark8 { RibbonMarkCurry: true }) - return false; - if (pk.Species == (int)Species.Shedinja && pk is IRibbonSetAffixed { AffixedRibbon: (int)RibbonIndex.MarkCurry }) - return false; - return true; - } - - private static bool IsCurryEncounter(PKM pk, IEncounterTemplate enc) - { - if (enc is not EncounterSlot8) - return false; - return pk is IRibbonSetMark8 { RibbonMarkCurry: true } || pk.Species == (int)Species.Shedinja; - } - - private static bool IsInvalidGenLoc8Other(byte memory, ushort variable) - { - if (variable > byte.MaxValue) - return true; - var arg = (byte)variable; - return memory switch - { - // {0} became {1}’s friend when it came through Link Trade {2}. {4} that {3}. - 4 when !PossibleGeneralLocations8.Contains(arg) || arg is 79 => true, // dangerous place - all locations are Y-Comm locked - - // {0} rode a bike with {1} {2}. {4} that {3}. - 32 when arg is not (1 or 8 or 12 or 22 or 33 or 35 or 37 or 40 or 41 or 44 or 47 or 48 or 49 or 50 or 51 or 53 or 65 or 71 or 72 or 75 or 76 or 77 or 78) => true, - - // {0} saw {1} secretly picking up something {2}. {4} that {3}. - 39 when arg is not (8 or 12 or 22 or 33 or 35 or 37 or 40 or 41 or 44 or 47 or 48 or 49 or 50 or 51 or 53 or 65 or 71 or 72 or 75 or 76 or 77) => true, - - // {0} checked the sign with {1} {2}. {4} that {3}. - 42 when arg is not (1 or 12 or 22 or 33 or 35 or 37 or 44 or 47 or 53 or 71 or 72 or 76 or 77) => true, - - // {0} sat with {1} on a bench {2}. {4} that {3}. - 70 when arg is not (12 or 22 or 28 or 33 or 35 or 37 or 38 or 44 or 53 or 77) => true, - - _ => !PossibleGeneralLocations8.Contains(arg), - }; - } - - public static bool CanHaveFeeling8(byte memory, byte feeling, ushort argument) - { - if (memory >= MemoryFeelings.Length) - return false; - if (feeling <= 0) - return false; // Different from Gen6; this +1 is to match them treating 0 as empty - return (MemoryFeelings[memory] & (1 << --feeling)) != 0; - } - - public static bool CanHaveIntensity8(byte memory, byte intensity) - { - if (memory >= MemoryFeelings.Length) - return false; - return MemoryMinIntensity[memory] <= intensity; - } - - public static byte GetRandomFeeling8(int memory, int max = 24) - { - var bits = MemoryFeelings[memory]; - var rnd = Util.Rand; - while (true) - { - int feel = rnd.Next(max); - if ((bits & (1 << feel)) != 0) - return (byte)(feel + 1); // Different from Gen6; this +1 is to match them treating 0 as empty - } - } - - public static int GetMinimumIntensity8(int memory) - { - if (memory >= MemoryMinIntensity.Length) - return -1; - return MemoryMinIntensity[memory]; - } - - public override bool CanHaveIntensity(byte memory, byte intensity) => CanHaveIntensity8(memory, intensity); - public override bool CanHaveFeeling(byte memory, byte feeling, ushort argument) => CanHaveFeeling8(memory, feeling, argument); - public override int GetMinimumIntensity(byte memory) => GetMinimumIntensity8(memory); + if (memory is 1 or 2 or 3) // Encounter only + return IsInvalidGenLoc8(memory, pk.Met_Location, pk.Egg_Location, variable, pk, enc); + return IsInvalidGenLoc8Other(memory, variable); } + + public override bool IsInvalidMiscMemory(byte memory, ushort variable) + { + return memory switch + { + // {0} encountered {2} when it was with {1}. {4} that {3}. + 29 when variable is not (888 or 889 or 890 or 898) => true, // Zacian, Zamazenta, Eternatus, Calyrex + _ => false, + }; + } + + private static bool CanObtainMemorySWSH(int memory) => memory <= MAX_MEMORY_ID_SWSH && !Memory_NotSWSH.Contains(memory); + + public override bool CanWinLotoID(int item) => LotoPrizeSWSH.Contains((ushort)item); + + public override bool CanBuyItem(int item, GameVersion version) => item switch + { + 1085 => version is GameVersion.SW or GameVersion.Any, // Bob's Food Tin + 1086 => version is GameVersion.SH or GameVersion.Any, // Bach's Food Tin + _ => PurchaseableItemSWSH.Contains((ushort)item), + }; + + private static bool IsInvalidGenLoc8(byte memory, int loc, int egg, ushort variable, PKM pk, IEncounterTemplate enc) + { + if (variable > 255) + return true; + + switch (memory) + { + case 1 when !IsWildEncounter(pk, enc): + case 2 when !enc.EggEncounter: + case 3 when !IsWildEncounterMeet(pk, enc): + return true; + } + + var arg = (byte)variable; + if (loc > 255) // gift + return memory != 3 || !PossibleGeneralLocations8.Contains(arg); + if (memory == 2 && egg == 0) + return true; + if (loc is Encounters8Nest.SharedNest) + return !PossibleGeneralLocations8.Contains(arg) || arg is 79; // dangerous place - all locations are Y-Comm locked + if (SingleGenLocAreas.TryGetValue((byte)loc, out var value)) + return arg != value; + if (MultiGenLocAreas.TryGetValue((byte)loc, out var arr)) + return !arr.Contains(arg); + return false; + } + + private static bool IsWildEncounterMeet(PKM pk, IEncounterTemplate enc) + { + if (enc is EncounterTrade or EncounterStatic { Gift: true } or WC8 { IsHOMEGift: false }) + return true; + if (IsCurryEncounter(pk, enc)) + return true; + return false; + } + + private static bool IsWildEncounter(PKM pk, IEncounterTemplate enc) + { + if (enc is not (EncounterSlot or EncounterStatic { Gift: false })) + return false; + if (pk is IRibbonSetMark8 { RibbonMarkCurry: true }) + return false; + if (pk.Species == (int)Species.Shedinja && pk is IRibbonSetAffixed { AffixedRibbon: (int)RibbonIndex.MarkCurry }) + return false; + return true; + } + + private static bool IsCurryEncounter(PKM pk, IEncounterTemplate enc) + { + if (enc is not EncounterSlot8) + return false; + return pk is IRibbonSetMark8 { RibbonMarkCurry: true } || pk.Species == (int)Species.Shedinja; + } + + private static bool IsInvalidGenLoc8Other(byte memory, ushort variable) + { + if (variable > byte.MaxValue) + return true; + var arg = (byte)variable; + return memory switch + { + // {0} became {1}’s friend when it came through Link Trade {2}. {4} that {3}. + 4 when !PossibleGeneralLocations8.Contains(arg) || arg is 79 => true, // dangerous place - all locations are Y-Comm locked + + // {0} rode a bike with {1} {2}. {4} that {3}. + 32 when arg is not (1 or 8 or 12 or 22 or 33 or 35 or 37 or 40 or 41 or 44 or 47 or 48 or 49 or 50 or 51 or 53 or 65 or 71 or 72 or 75 or 76 or 77 or 78) => true, + + // {0} saw {1} secretly picking up something {2}. {4} that {3}. + 39 when arg is not (8 or 12 or 22 or 33 or 35 or 37 or 40 or 41 or 44 or 47 or 48 or 49 or 50 or 51 or 53 or 65 or 71 or 72 or 75 or 76 or 77) => true, + + // {0} checked the sign with {1} {2}. {4} that {3}. + 42 when arg is not (1 or 12 or 22 or 33 or 35 or 37 or 44 or 47 or 53 or 71 or 72 or 76 or 77) => true, + + // {0} sat with {1} on a bench {2}. {4} that {3}. + 70 when arg is not (12 or 22 or 28 or 33 or 35 or 37 or 38 or 44 or 53 or 77) => true, + + _ => !PossibleGeneralLocations8.Contains(arg), + }; + } + + public static bool CanHaveFeeling8(byte memory, byte feeling, ushort argument) + { + if (memory >= MemoryFeelings.Length) + return false; + if (feeling <= 0) + return false; // Different from Gen6; this +1 is to match them treating 0 as empty + return (MemoryFeelings[memory] & (1 << --feeling)) != 0; + } + + public static bool CanHaveIntensity8(byte memory, byte intensity) + { + if (memory >= MemoryFeelings.Length) + return false; + return MemoryMinIntensity[memory] <= intensity; + } + + public static byte GetRandomFeeling8(int memory, int max = 24) + { + var bits = MemoryFeelings[memory]; + var rnd = Util.Rand; + while (true) + { + int feel = rnd.Next(max); + if ((bits & (1 << feel)) != 0) + return (byte)(feel + 1); // Different from Gen6; this +1 is to match them treating 0 as empty + } + } + + public static int GetMinimumIntensity8(int memory) + { + if (memory >= MemoryMinIntensity.Length) + return -1; + return MemoryMinIntensity[memory]; + } + + public override bool CanHaveIntensity(byte memory, byte intensity) => CanHaveIntensity8(memory, intensity); + public override bool CanHaveFeeling(byte memory, byte feeling, ushort argument) => CanHaveFeeling8(memory, feeling, argument); + public override int GetMinimumIntensity(byte memory) => GetMinimumIntensity8(memory); } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8Data.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8Data.cs index b7b228237..5ff332705 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8Data.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryContext8Data.cs @@ -1,270 +1,270 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public partial class MemoryContext8 { - public partial class MemoryContext8 + private static readonly int[] Memory_NotSWSH = { - private static readonly int[] Memory_NotSWSH = - { - 10, // {0} got treats from {1}. {4} that {3}. - 17, // {0} battled at {1}’s side and beat {2}. {4} that {3}. - 20, // {0} surfed across the water, carrying {1} on its back. {4} that {3}. - 21, // {0} saw {2} carrying {1} on its back. {4} that {3}. - 23, // When {1} challenged the Battle Maison, {0} got really nervous. {4} that {3}. - 24, // {0} flew, carrying {1} on its back, to {2}. {4} that {3}. - 26, // {0} saw {1} using {2}. {4} that {3}. - 31, // {0} searched for hidden items with {1} using the Dowsing Machine {2}. {4} that {3}. - 34, // {0} planted {2} with {1} and imagined a big harvest. {4} that {3}. - 35, // {0} proudly used Strength at {1}’s instruction in... {2}. {4} that {3}. - 36, // {0} proudly used Cut at {1}’s instruction in... {2}. {4} that {3}. - 37, // {0} shattered rocks to its heart’s content at {1}’s instruction in... {2}. {4} that {3}. - 38, // {0} used Waterfall while carrying {1} on its back in... {2}. {4} that {3}. - 41, // {0} headed for Victory Road with {1}. {4} that {3}. - 44, // {0} encountered {2} with {1} using the Poké Radar. {4} that {3}. - 45, // When {2} jumped out, {0} was surprised and ran away with {1}. {4} that {3}. - 46, // {0} got a high score at the Battle Institute where it challenged others with {1}. {4} that {3}. - 47, // {0} was stared at by the Judge when it met him with {1}. {4} that {3}. - 50, // {0} was taken to the Pokémon Day Care by {1} and left with {2}. {4} that {3}. - 52, // {0} was there when {1} used a repellent {2}. {4} that {3}. - 54, // {0} took an elevator with {1}. {4} that {3}. - 58, // {0} was taken to a nice lady by {1} and pampered. {4} that {3}. - 59, // {0} checked a bin with {1} {2}. {4} that {3}. - 61, // {0} went to a tall tower with {1} and looked down on the world. {4} that {3}. - 62, // {0} saw itself in a mirror inside a mirror cave that it explored with {1}. {4} that {3}. - 64, // {0} went to a factory with {1} and saw a lot of machines that looked very complicated. {4} that {3}. - 65, // {0} was there when {1} created a Secret Base. {4} that {3}. - 66, // {0} participated in a contest with {1} and impressed many people. {4} that {3}. - 67, // {0} participated in a contest with {1} and won the title. {4} that {3}. - 68, // {0} soared through the sky with {1} and went to many different places. {4} that {3}. - 69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}. - 87, // {0} got in a fight with the {2} that it was in a Box with about {1}. {4} that {3}. - 88, // When {0} was in a Box, it thought about the reason why {1} had it hold the {2}. {4} that {3}. - 89, // When {0} was in a Box, it had a weird dream in which {1} was using the move {2}. {4} that {3}. - }; + 10, // {0} got treats from {1}. {4} that {3}. + 17, // {0} battled at {1}’s side and beat {2}. {4} that {3}. + 20, // {0} surfed across the water, carrying {1} on its back. {4} that {3}. + 21, // {0} saw {2} carrying {1} on its back. {4} that {3}. + 23, // When {1} challenged the Battle Maison, {0} got really nervous. {4} that {3}. + 24, // {0} flew, carrying {1} on its back, to {2}. {4} that {3}. + 26, // {0} saw {1} using {2}. {4} that {3}. + 31, // {0} searched for hidden items with {1} using the Dowsing Machine {2}. {4} that {3}. + 34, // {0} planted {2} with {1} and imagined a big harvest. {4} that {3}. + 35, // {0} proudly used Strength at {1}’s instruction in... {2}. {4} that {3}. + 36, // {0} proudly used Cut at {1}’s instruction in... {2}. {4} that {3}. + 37, // {0} shattered rocks to its heart’s content at {1}’s instruction in... {2}. {4} that {3}. + 38, // {0} used Waterfall while carrying {1} on its back in... {2}. {4} that {3}. + 41, // {0} headed for Victory Road with {1}. {4} that {3}. + 44, // {0} encountered {2} with {1} using the Poké Radar. {4} that {3}. + 45, // When {2} jumped out, {0} was surprised and ran away with {1}. {4} that {3}. + 46, // {0} got a high score at the Battle Institute where it challenged others with {1}. {4} that {3}. + 47, // {0} was stared at by the Judge when it met him with {1}. {4} that {3}. + 50, // {0} was taken to the Pokémon Day Care by {1} and left with {2}. {4} that {3}. + 52, // {0} was there when {1} used a repellent {2}. {4} that {3}. + 54, // {0} took an elevator with {1}. {4} that {3}. + 58, // {0} was taken to a nice lady by {1} and pampered. {4} that {3}. + 59, // {0} checked a bin with {1} {2}. {4} that {3}. + 61, // {0} went to a tall tower with {1} and looked down on the world. {4} that {3}. + 62, // {0} saw itself in a mirror inside a mirror cave that it explored with {1}. {4} that {3}. + 64, // {0} went to a factory with {1} and saw a lot of machines that looked very complicated. {4} that {3}. + 65, // {0} was there when {1} created a Secret Base. {4} that {3}. + 66, // {0} participated in a contest with {1} and impressed many people. {4} that {3}. + 67, // {0} participated in a contest with {1} and won the title. {4} that {3}. + 68, // {0} soared through the sky with {1} and went to many different places. {4} that {3}. + 69, // {1} asked {0} to dive. Down it went, deep into the ocean, to explore the bottom of the sea. {4} that {3}. + 87, // {0} got in a fight with the {2} that it was in a Box with about {1}. {4} that {3}. + 88, // When {0} was in a Box, it thought about the reason why {1} had it hold the {2}. {4} that {3}. + 89, // When {0} was in a Box, it had a weird dream in which {1} was using the move {2}. {4} that {3}. + }; - private static readonly Dictionary KeyItemMemoryArgsGen8 = new() - { - {(int) Species.Rotom, new ushort[] {1278}}, // Rotom Catalog - {(int) Species.Kyurem, new ushort[] {628, 629}}, // DNA Splicers - {(int) Species.Necrozma, new ushort[] {943, 944, 945, 946}}, // N-Lunarizer / N-Solarizer - {(int) Species.Calyrex, new ushort[] {1590, 1591}}, // Reigns of Unity - }; + private static readonly Dictionary KeyItemMemoryArgsGen8 = new() + { + {(int) Species.Rotom, new ushort[] {1278}}, // Rotom Catalog + {(int) Species.Kyurem, new ushort[] {628, 629}}, // DNA Splicers + {(int) Species.Necrozma, new ushort[] {943, 944, 945, 946}}, // N-Lunarizer / N-Solarizer + {(int) Species.Calyrex, new ushort[] {1590, 1591}}, // Reigns of Unity + }; - private static readonly HashSet PurchaseableItemSWSH = new(Legal.TR_SWSH) - { - 0002, 0003, 0004, 0006, 0007, 0008, 0009, 0010, 0011, 0013, - 0014, 0015, 0017, 0018, 0019, 0020, 0021, 0022, 0023, 0024, - 0025, 0026, 0027, 0028, 0033, 0034, 0035, 0036, 0037, 0042, - 0045, 0046, 0047, 0048, 0049, 0050, 0051, 0052, 0055, 0056, - 0057, 0058, 0059, 0060, 0061, 0062, 0063, 0076, 0077, 0079, - 0089, 0149, 0150, 0151, 0155, 0214, 0215, 0219, 0220, 0254, - 0255, 0269, 0270, 0271, 0275, 0280, 0287, 0289, 0290, 0291, - 0292, 0293, 0294, 0297, 0314, 0315, 0316, 0317, 0318, 0319, - 0320, 0321, 0325, 0326, 0541, 0542, 0545, 0546, 0547, 0639, - 0640, 0645, 0646, 0647, 0648, 0649, 0795, 0846, 0879, 1084, - 1087, 1088, 1089, 1090, 1091, 1094, 1095, 1097, 1098, 1099, - 1118, 1119, 1121, 1122, 1231, 1232, 1233, 1234, 1235, 1236, - 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, - 1247, 1248, 1249, 1250, 1251, 1252, 1256, 1257, 1258, 1259, - 1260, 1261, 1262, 1263, - }; + private static readonly HashSet PurchaseableItemSWSH = new(Legal.TR_SWSH) + { + 0002, 0003, 0004, 0006, 0007, 0008, 0009, 0010, 0011, 0013, + 0014, 0015, 0017, 0018, 0019, 0020, 0021, 0022, 0023, 0024, + 0025, 0026, 0027, 0028, 0033, 0034, 0035, 0036, 0037, 0042, + 0045, 0046, 0047, 0048, 0049, 0050, 0051, 0052, 0055, 0056, + 0057, 0058, 0059, 0060, 0061, 0062, 0063, 0076, 0077, 0079, + 0089, 0149, 0150, 0151, 0155, 0214, 0215, 0219, 0220, 0254, + 0255, 0269, 0270, 0271, 0275, 0280, 0287, 0289, 0290, 0291, + 0292, 0293, 0294, 0297, 0314, 0315, 0316, 0317, 0318, 0319, + 0320, 0321, 0325, 0326, 0541, 0542, 0545, 0546, 0547, 0639, + 0640, 0645, 0646, 0647, 0648, 0649, 0795, 0846, 0879, 1084, + 1087, 1088, 1089, 1090, 1091, 1094, 1095, 1097, 1098, 1099, + 1118, 1119, 1121, 1122, 1231, 1232, 1233, 1234, 1235, 1236, + 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, + 1247, 1248, 1249, 1250, 1251, 1252, 1256, 1257, 1258, 1259, + 1260, 1261, 1262, 1263, + }; - private static readonly ushort[] LotoPrizeSWSH = - { - 0001, 0033, 0050, 0051, 0053, - }; + private static readonly ushort[] LotoPrizeSWSH = + { + 0001, 0033, 0050, 0051, 0053, + }; - // {met, values allowed} - private static readonly Dictionary MultiGenLocAreas = new() - { - // town of Postwick - // first town, at home, friend's house - {6, new byte[] {1, 2, 3}}, + // {met, values allowed} + private static readonly Dictionary MultiGenLocAreas = new() + { + // town of Postwick + // first town, at home, friend's house + {6, new byte[] {1, 2, 3}}, - // town of Wedgehurst - // someone's house, boutique, simple town, Pokémon Center - {14, new byte[] {4, 6, 8, 9}}, + // town of Wedgehurst + // someone's house, boutique, simple town, Pokémon Center + {14, new byte[] {4, 6, 8, 9}}, - // Route 2 - // lab, tranquil road, lakeside road - {18, new byte[] {16, 44, 71}}, + // Route 2 + // lab, tranquil road, lakeside road + {18, new byte[] {16, 44, 71}}, - // city of Motostoke - // boutique, Pokémon Center, hotel, Pokémon Gym, stylish café, hair salon, town with a mysterious air - {20, new byte[] {6, 9, 11, 20, 24, 30, 35}}, + // city of Motostoke + // boutique, Pokémon Center, hotel, Pokémon Gym, stylish café, hair salon, town with a mysterious air + {20, new byte[] {6, 9, 11, 20, 24, 30, 35}}, - // Motostoke Stadium - // Pokémon Gym, stadium - {24, new byte[] {20, 73}}, + // Motostoke Stadium + // Pokémon Gym, stadium + {24, new byte[] {20, 73}}, - // town of Turffield - // Pokémon Center, town with a mysterious air - {34, new byte[] {9, 12}}, + // town of Turffield + // Pokémon Center, town with a mysterious air + {34, new byte[] {9, 12}}, - // Route 5 - // tranquil road, Pokémon Nursery - {40, new byte[] {44, 74}}, + // Route 5 + // tranquil road, Pokémon Nursery + {40, new byte[] {44, 74}}, - // town of Hulbury - // someone’s house, Pokémon Center, restaurant, seaside town - {44, new byte[] {4, 9, 31, 33}}, + // town of Hulbury + // someone’s house, Pokémon Center, restaurant, seaside town + {44, new byte[] {4, 9, 31, 33}}, - // city of Hammerlocke - // someone’s house, boutique, Pokémon Center, Pokémon Gym, stylish café, hair salon, town with a mysterious air - {56, new byte[] {4, 6, 9, 20, 24, 30, 35}}, + // city of Hammerlocke + // someone’s house, boutique, Pokémon Center, Pokémon Gym, stylish café, hair salon, town with a mysterious air + {56, new byte[] {4, 6, 9, 20, 24, 30, 35}}, - // town of Stow-on-Side - // someone’s house, Pokémon Center, town in the mountains - {70, new byte[] {4, 9, 76}}, + // town of Stow-on-Side + // someone’s house, Pokémon Center, town in the mountains + {70, new byte[] {4, 9, 76}}, - // town of Ballonlea - // someone’s house, Pokémon Center, town with a mysterious air - {78, new byte[] {4, 9, 12}}, + // town of Ballonlea + // someone’s house, Pokémon Center, town with a mysterious air + {78, new byte[] {4, 9, 12}}, - // town of Circhester - // someone’s house, boutique, Pokémon Center, hotel, hair salon, restaurant, snowcapped town - {96, new byte[] {4, 6, 9, 11, 30, 31, 37}}, + // town of Circhester + // someone’s house, boutique, Pokémon Center, hotel, hair salon, restaurant, snowcapped town + {96, new byte[] {4, 6, 9, 11, 30, 31, 37}}, - // town of Spikemuth - // Pokémon Center, run-down town - {102, new byte[] {9, 77}}, + // town of Spikemuth + // Pokémon Center, run-down town + {102, new byte[] {9, 77}}, - // city of Wyndon - // someone’s house, boutique, Pokémon Center, hotel, large town, stylish café, hair salon - {110, new byte[] {4, 6, 9, 11, 22, 24, 30}}, + // city of Wyndon + // someone’s house, boutique, Pokémon Center, hotel, large town, stylish café, hair salon + {110, new byte[] {4, 6, 9, 11, 22, 24, 30}}, - // town of Freezington - // someone’s house, snowcapped town - {206, new byte[] {04, 37}}, + // town of Freezington + // someone’s house, snowcapped town + {206, new byte[] {04, 37}}, - // at the Crown Shrine - // on a snow-swept road, in a mystical place - {220, new byte[] {53, 65}}, - }; + // at the Crown Shrine + // on a snow-swept road, in a mystical place + {220, new byte[] {53, 65}}, + }; - // {met, value allowed} - private static readonly Dictionary SingleGenLocAreas = new() - { - {008, 41}, // Slumbering Weald, forest - {012, 44}, // Route 1, tranquil road - {016, 28}, // Wedgehurst Station, train station - {022, 28}, // Motostoke Station, train station - {028, 44}, // Route 3, tranquil road - {030, 75}, // Galar Mine, mine - {032, 44}, // Route 4, tranquil road - {036, 73}, // Turffield Stadium, stadium - {046, 28}, // Hulbury Station, train station - {048, 73}, // Hulbury Stadium, stadium - {052, 35}, // Motostoke Outskirts, town with a mysterious air - {054, 75}, // Galar Mine No. 2, mine - {058, 28}, // Hammerlocke Station, train station - {060, 73}, // Hammerlocke Stadium, stadium - {064, 79}, // Energy Plant, dangerous place - {066, 79}, // the tower summit, dangerous place - {068, 47}, // Route 6, rugged mountain pass - {072, 73}, // Stow-on-Side Stadium, stadium - {076, 41}, // Glimwood Tangle, forest - {080, 73}, // Ballonlea Stadium, stadium - {084, 44}, // Route 7, tranquil road - {086, 47}, // Route 8, rugged mountain pass - {088, 53}, // Route 8 (on Steamdrift Way), snow-swept road - {090, 53}, // Route 9, snow-swept road - {092, 53}, // Route 9 (in Circhester Bay), snow-swept road - {094, 53}, // Route 9 (in Outer Spikemuth), snow-swept road - {098, 73}, // Circhester Stadium, stadium - {104, 78}, // Route 9 Tunnel, tunnel - {106, 53}, // Route 10, snow-swept road - {108, 28}, // White Hill Station, train station - {112, 28}, // Wyndon Station, train station - {114, 38}, // Wyndon Stadium (at the Pokémon League HQ), Pokémon League - {116, 38}, // Wyndon Stadium (in a locker room), Pokémon League - {120, 44}, // Meetup Spot, tranquil road - {122, 72}, // Rolling Fields, vast field - {124, 72}, // Dappled Grove, vast field - {126, 72}, // Watchtower Ruins, vast field - {128, 72}, // East Lake Axewell, vast field - {130, 72}, // West Lake Axewell, vast field - {132, 72}, // Axew’s Eye, vast field - {134, 72}, // South Lake Miloch, vast field - {136, 72}, // near the Giant’s Seat, vast field - {138, 72}, // North Lake Miloch, vast field - {140, 72}, // Motostoke Riverbank, vast field - {142, 72}, // Bridge Field, vast field - {144, 72}, // Stony Wilderness, vast field - {146, 72}, // Dusty Bowl, vast field - {148, 72}, // around the Giant’s Mirror, vast field - {150, 72}, // the Hammerlocke Hills, vast field - {152, 72}, // near the Giant’s Cap, vast field - {154, 72}, // Lake of Outrage, vast field - {156, 28}, // Wild Area Station, train station - {158, 34}, // Battle Tower, tall building - {160, 34}, // Rose Tower, tall building - {164, 72}, // Fields of Honor, vast field - {166, 50}, // Soothing Wetlands, muddy road - {168, 41}, // Forest of Focus, forest - {170, 49}, // Challenge Beach, seaside road - {172, 40}, // Brawlers’ Cave, cave - {174, 76}, // Challenge Road, town in the mountains - {176, 40}, // Courageous Cavern, cave - {178, 49}, // Loop Lagoon, seaside road - {180, 72}, // Training Lowlands, vast field - {182, 40}, // Warm-Up Tunnel, cave - {184, 51}, // Potbottom Desert, sand-swept road - {186, 49}, // Workout Sea, seaside road - {188, 49}, // Stepping-Stone Sea, seaside road - {190, 49}, // Insular Sea, seaside road - {192, 49}, // Honeycalm Sea, seaside road - {194, 49}, // Honeycalm Island, seaside road - {196, 29}, // Master Dojo, battling spot - {198, 34}, // Tower of Darkness, tall building - {200, 34}, // Tower of Waters, tall building - {202, 28}, // Armor Station, train station - {204, 53}, // Slippery Slope, snow-swept road - {208, 53}, // Frostpoint Field, snow-swept road - {210, 72}, // Giant’s Bed, vast field - {212, 48}, // Old Cemetery, stone-lined area - {214, 53}, // Snowslide Slope, snow-swept road - {216, 40}, // Tunnel to the Top, cave - {218, 53}, // the Path to the Peak, snow-swept road - {222, 44}, // Giant’s Foot, tranquil road - {224, 40}, // Roaring-Sea Caves, cave - {226, 49}, // Frigid Sea, seaside road - {228, 72}, // Three-Point Pass, vast field - {230, 72}, // Ballimere Lake, vast field - {232, 40}, // Lakeside Cave, cave - {234, 72}, // Dyna Tree Hill, vast field - {236, 65}, // Rock Peak Ruins, mystical place - {238, 65}, // Iceberg Ruins, mystical place - {240, 65}, // Iron Ruins, mystical place - {242, 65}, // Split-Decision Ruins, mystical place - {244, 40}, // Max Lair, cave - {246, 28}, // Crown Tundra Station, train station - }; + // {met, value allowed} + private static readonly Dictionary SingleGenLocAreas = new() + { + {008, 41}, // Slumbering Weald, forest + {012, 44}, // Route 1, tranquil road + {016, 28}, // Wedgehurst Station, train station + {022, 28}, // Motostoke Station, train station + {028, 44}, // Route 3, tranquil road + {030, 75}, // Galar Mine, mine + {032, 44}, // Route 4, tranquil road + {036, 73}, // Turffield Stadium, stadium + {046, 28}, // Hulbury Station, train station + {048, 73}, // Hulbury Stadium, stadium + {052, 35}, // Motostoke Outskirts, town with a mysterious air + {054, 75}, // Galar Mine No. 2, mine + {058, 28}, // Hammerlocke Station, train station + {060, 73}, // Hammerlocke Stadium, stadium + {064, 79}, // Energy Plant, dangerous place + {066, 79}, // the tower summit, dangerous place + {068, 47}, // Route 6, rugged mountain pass + {072, 73}, // Stow-on-Side Stadium, stadium + {076, 41}, // Glimwood Tangle, forest + {080, 73}, // Ballonlea Stadium, stadium + {084, 44}, // Route 7, tranquil road + {086, 47}, // Route 8, rugged mountain pass + {088, 53}, // Route 8 (on Steamdrift Way), snow-swept road + {090, 53}, // Route 9, snow-swept road + {092, 53}, // Route 9 (in Circhester Bay), snow-swept road + {094, 53}, // Route 9 (in Outer Spikemuth), snow-swept road + {098, 73}, // Circhester Stadium, stadium + {104, 78}, // Route 9 Tunnel, tunnel + {106, 53}, // Route 10, snow-swept road + {108, 28}, // White Hill Station, train station + {112, 28}, // Wyndon Station, train station + {114, 38}, // Wyndon Stadium (at the Pokémon League HQ), Pokémon League + {116, 38}, // Wyndon Stadium (in a locker room), Pokémon League + {120, 44}, // Meetup Spot, tranquil road + {122, 72}, // Rolling Fields, vast field + {124, 72}, // Dappled Grove, vast field + {126, 72}, // Watchtower Ruins, vast field + {128, 72}, // East Lake Axewell, vast field + {130, 72}, // West Lake Axewell, vast field + {132, 72}, // Axew’s Eye, vast field + {134, 72}, // South Lake Miloch, vast field + {136, 72}, // near the Giant’s Seat, vast field + {138, 72}, // North Lake Miloch, vast field + {140, 72}, // Motostoke Riverbank, vast field + {142, 72}, // Bridge Field, vast field + {144, 72}, // Stony Wilderness, vast field + {146, 72}, // Dusty Bowl, vast field + {148, 72}, // around the Giant’s Mirror, vast field + {150, 72}, // the Hammerlocke Hills, vast field + {152, 72}, // near the Giant’s Cap, vast field + {154, 72}, // Lake of Outrage, vast field + {156, 28}, // Wild Area Station, train station + {158, 34}, // Battle Tower, tall building + {160, 34}, // Rose Tower, tall building + {164, 72}, // Fields of Honor, vast field + {166, 50}, // Soothing Wetlands, muddy road + {168, 41}, // Forest of Focus, forest + {170, 49}, // Challenge Beach, seaside road + {172, 40}, // Brawlers’ Cave, cave + {174, 76}, // Challenge Road, town in the mountains + {176, 40}, // Courageous Cavern, cave + {178, 49}, // Loop Lagoon, seaside road + {180, 72}, // Training Lowlands, vast field + {182, 40}, // Warm-Up Tunnel, cave + {184, 51}, // Potbottom Desert, sand-swept road + {186, 49}, // Workout Sea, seaside road + {188, 49}, // Stepping-Stone Sea, seaside road + {190, 49}, // Insular Sea, seaside road + {192, 49}, // Honeycalm Sea, seaside road + {194, 49}, // Honeycalm Island, seaside road + {196, 29}, // Master Dojo, battling spot + {198, 34}, // Tower of Darkness, tall building + {200, 34}, // Tower of Waters, tall building + {202, 28}, // Armor Station, train station + {204, 53}, // Slippery Slope, snow-swept road + {208, 53}, // Frostpoint Field, snow-swept road + {210, 72}, // Giant’s Bed, vast field + {212, 48}, // Old Cemetery, stone-lined area + {214, 53}, // Snowslide Slope, snow-swept road + {216, 40}, // Tunnel to the Top, cave + {218, 53}, // the Path to the Peak, snow-swept road + {222, 44}, // Giant’s Foot, tranquil road + {224, 40}, // Roaring-Sea Caves, cave + {226, 49}, // Frigid Sea, seaside road + {228, 72}, // Three-Point Pass, vast field + {230, 72}, // Ballimere Lake, vast field + {232, 40}, // Lakeside Cave, cave + {234, 72}, // Dyna Tree Hill, vast field + {236, 65}, // Rock Peak Ruins, mystical place + {238, 65}, // Iceberg Ruins, mystical place + {240, 65}, // Iron Ruins, mystical place + {242, 65}, // Split-Decision Ruins, mystical place + {244, 40}, // Max Lair, cave + {246, 28}, // Crown Tundra Station, train station + }; - private static readonly HashSet PossibleGeneralLocations8 = new() - { - 01, 02, 03, 04, 06, 08, 09, - 11, 12, 16, - 20, 22, 24, 28, 29, - 30, 31, 33, 34, 35, 37, 38, - 40, 41, 44, 47, 48, 49, - 50, 51, 53, - 65, - 71, 72, 73, 74, 75, 76, 77, 78, 79, - }; + private static readonly HashSet PossibleGeneralLocations8 = new() + { + 01, 02, 03, 04, 06, 08, 09, + 11, 12, 16, + 20, 22, 24, 28, 29, + 30, 31, 33, 34, 35, 37, 38, + 40, 41, 44, 47, 48, 49, + 50, 51, 53, + 65, + 71, 72, 73, 74, 75, 76, 77, 78, 79, + }; - private static readonly byte[] MemoryMinIntensity = - { - 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, - 3, 3, 3, 3, 3, 3, 3, 4, 5, 5, - 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 1, 3, 2, 2, 4, 3, 4, 4, - 4, 4, 2, 4, 2, 4, 3, 3, 4, 2, - 3, 3, 3, 3, 3, 2, 3, 4, 4, 2, // same as Gen6 - 2, 3, 3, 3, 3, 4, 5, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - }; + private static readonly byte[] MemoryMinIntensity = + { + 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 4, 5, 5, + 5, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 1, 3, 2, 2, 4, 3, 4, 4, + 4, 4, 2, 4, 2, 4, 3, 3, 4, 2, + 3, 3, 3, 3, 3, 2, 3, 4, 4, 2, // same as Gen6 + 2, 3, 3, 3, 3, 4, 5, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + }; #if FALSE // [0,99]+1 >= CHANCE -> abort @@ -286,21 +286,20 @@ public partial class MemoryContext8 }; #endif - /// - /// 24bits of flags allowing certain feelings for a given memory index. - /// - /// Beware, there was an off-by-one error in the game that made Feeling 0 unobtainable, and thus the Happy feeling bit (rightmost) is omitted. - private static readonly uint[] MemoryFeelings = - { - 0x000000, 0x04CBFD, 0x004BFD, 0x04CBFD, 0x04CBFD, 0xFFFBFB, 0x84FFF9, 0x47FFFF, 0xBF7FFA, 0x7660B0, - 0x80BDF9, 0x88FB7A, 0x083F79, 0x0001FE, 0xCFEFFF, 0x84EBAF, 0xB368B0, 0x091F7E, 0x0320A0, 0x080DDD, - 0x081A7B, 0x404030, 0x0FFFFF, 0x9A08BC, 0x089A7B, 0x0032AA, 0x80FF7A, 0x0FFFFF, 0x0805FD, 0x098278, - 0x0B3FFF, 0x8BBFFA, 0x8BBFFE, 0x81A97C, 0x8BB97C, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0xAC3ABE, - 0xBFFFFF, 0x8B837C, 0x848AFA, 0x88FFFE, 0x8B0B7C, 0xB76AB2, 0x8B1FFF, 0xBE7AB8, 0xB77EB8, 0x8C9FFD, - 0xBF9BFF, 0xF408B0, 0xBCFE7A, 0x8F3F72, 0x90DB7A, 0xBCEBFF, 0xBC5838, 0x9C3FFE, 0x9CFFFF, 0x96D83A, - 0xB770B0, 0x881F7A, 0x839F7A, 0x839F7A, 0x839F7A, 0x53897F, 0x41BB6F, 0x0C35FF, 0x8BBF7F, 0x8BBF7F, // same as Gen6 - 0x90CC7E, 0x2FBF7F, 0x2FBF7F, 0xB797FF, 0x3FB7FF, 0xBFFFFF, 0xCC8BFF, 0xF69F7F, 0x37FDFF, 0x2B277F, - 0x8FFBFA, 0x8CDFFA, 0xFCE9EF, 0x8F6F7B, 0x826AB0, 0x866AB0, 0x8C69FE, 0x776AB0, 0x8CFB7A, 0x0CFEBA, - }; - } + /// + /// 24bits of flags allowing certain feelings for a given memory index. + /// + /// Beware, there was an off-by-one error in the game that made Feeling 0 unobtainable, and thus the Happy feeling bit (rightmost) is omitted. + private static readonly uint[] MemoryFeelings = + { + 0x000000, 0x04CBFD, 0x004BFD, 0x04CBFD, 0x04CBFD, 0xFFFBFB, 0x84FFF9, 0x47FFFF, 0xBF7FFA, 0x7660B0, + 0x80BDF9, 0x88FB7A, 0x083F79, 0x0001FE, 0xCFEFFF, 0x84EBAF, 0xB368B0, 0x091F7E, 0x0320A0, 0x080DDD, + 0x081A7B, 0x404030, 0x0FFFFF, 0x9A08BC, 0x089A7B, 0x0032AA, 0x80FF7A, 0x0FFFFF, 0x0805FD, 0x098278, + 0x0B3FFF, 0x8BBFFA, 0x8BBFFE, 0x81A97C, 0x8BB97C, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0x8BBF7F, 0xAC3ABE, + 0xBFFFFF, 0x8B837C, 0x848AFA, 0x88FFFE, 0x8B0B7C, 0xB76AB2, 0x8B1FFF, 0xBE7AB8, 0xB77EB8, 0x8C9FFD, + 0xBF9BFF, 0xF408B0, 0xBCFE7A, 0x8F3F72, 0x90DB7A, 0xBCEBFF, 0xBC5838, 0x9C3FFE, 0x9CFFFF, 0x96D83A, + 0xB770B0, 0x881F7A, 0x839F7A, 0x839F7A, 0x839F7A, 0x53897F, 0x41BB6F, 0x0C35FF, 0x8BBF7F, 0x8BBF7F, // same as Gen6 + 0x90CC7E, 0x2FBF7F, 0x2FBF7F, 0xB797FF, 0x3FB7FF, 0xBFFFFF, 0xCC8BFF, 0xF69F7F, 0x37FDFF, 0x2B277F, + 0x8FFBFA, 0x8CDFFA, 0xFCE9EF, 0x8F6F7B, 0x826AB0, 0x866AB0, 0x8C69FE, 0x776AB0, 0x8CFB7A, 0x0CFEBA, + }; } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs index f3f4c68a6..f0143a37b 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryPermissions.cs @@ -6,350 +6,349 @@ using static PKHeX.Core.Move; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Validation logic for specific memory conditions +/// +public static class MemoryPermissions { - /// - /// Validation logic for specific memory conditions - /// - public static class MemoryPermissions + public static bool IsMemoryOfKnownMove(int memory) => memory is 48 or 80 or 81; + + public static bool CanWinLotoID(int generation, int item) { - public static bool IsMemoryOfKnownMove(int memory) => memory is 48 or 80 or 81; + var context = Memories.GetContext(generation); + return context.CanWinLotoID(item); + } - public static bool CanWinLotoID(int generation, int item) - { - var context = Memories.GetContext(generation); - return context.CanWinLotoID(item); - } + public static bool CanHoldItem(int generation, int item) + { + var context = Memories.GetContext(generation); + return context.CanHoldItem(item); + } - public static bool CanHoldItem(int generation, int item) - { - var context = Memories.GetContext(generation); - return context.CanHoldItem(item); - } + public static bool CanPlantBerry(int generation, int item) + { + var context = Memories.GetContext(generation); + return context.CanPlantBerry(item); + } - public static bool CanPlantBerry(int generation, int item) - { - var context = Memories.GetContext(generation); - return context.CanPlantBerry(item); - } + public static bool CanUseItemGeneric(int generation, int item) + { + var context = Memories.GetContext(generation); + return context.CanUseItemGeneric(item); + } - public static bool CanUseItemGeneric(int generation, int item) - { - var context = Memories.GetContext(generation); - return context.CanUseItemGeneric(item); - } + public static bool CanUseItem(int generation, int item, int species) + { + if (IsUsedKeyItemUnspecific(generation, item)) + return true; + if (IsUsedKeyItemSpecific(generation, item, species)) + return true; + return true; // todo + } - public static bool CanUseItem(int generation, int item, int species) + private static bool IsUsedKeyItemUnspecific(int generation, int item) + { + var context = Memories.GetContext(generation); + return context.IsUsedKeyItemUnspecific(item); + } + + private static bool IsUsedKeyItemSpecific(int generation, int item, int species) + { + var context = Memories.GetContext(generation); + return context.IsUsedKeyItemSpecific(item, species); + } + + public static bool CanBuyItem(int generation, int item, GameVersion version = GameVersion.Any) + { + var context = Memories.GetContext(generation); + return context.CanBuyItem(item, version); + } + + public static bool GetCanLearnMachineMove(PKM pk, EvoCriteria[] evos, int move, int generation, GameVersion version = GameVersion.Any) + { + if (IsOtherFormMove(pk, evos, move, generation, version, types: MoveSourceType.AllMachines)) + return true; + return MoveList.GetValidMoves(pk, version, evos, generation, types: MoveSourceType.AllMachines).Contains(move); + } + + private static bool IsOtherFormMove(PKM pk, EvoCriteria[] evos, int move, int generation, GameVersion version, MoveSourceType types) + { + if (!Legal.FormChangeMoves.TryGetValue(pk.Species, out var criteria)) + return false; + if (!criteria(generation, pk.Form)) + return false; + var list = new List(8); + MoveList.GetValidMovesAllForms(pk, evos, version, generation, types, false, pk.Species, list); + return list.Contains(move); + } + + public static bool CanKnowMove(PKM pk, MemoryVariableSet memory, int gen, LegalInfo info, bool battleOnly = false) + { + var move = memory.Variable; + if (move == 0) + return false; + + if (pk.HasMove(move)) + return true; + + if (pk.IsEgg) + return false; + + if (GetCanKnowMove(pk, move, gen, info.EvoChainsAllGens)) + return true; + + var enc = info.EncounterMatch; + if (enc is IMoveset ms && ms.Moves.Contains(move)) + return true; + if (enc is IRelearn r && r.Relearn.Contains(move)) + return true; + + // Relearns can be wiped via Battle Version. Check for eggs too. + if (IsSpecialEncounterMoveEggDeleted(pk, enc)) { - if (IsUsedKeyItemUnspecific(generation, item)) + var em = MoveEgg.GetEggMoves(enc.Generation, enc.Species, enc.Form, enc.Version); + if (em.Contains(move)) return true; - if (IsUsedKeyItemSpecific(generation, item, species)) - return true; - return true; // todo } - private static bool IsUsedKeyItemUnspecific(int generation, int item) + if (battleOnly) { - var context = Memories.GetContext(generation); - return context.IsUsedKeyItemUnspecific(item); - } - - private static bool IsUsedKeyItemSpecific(int generation, int item, int species) - { - var context = Memories.GetContext(generation); - return context.IsUsedKeyItemSpecific(item, species); - } - - public static bool CanBuyItem(int generation, int item, GameVersion version = GameVersion.Any) - { - var context = Memories.GetContext(generation); - return context.CanBuyItem(item, version); - } - - public static bool GetCanLearnMachineMove(PKM pkm, EvoCriteria[] evos, int move, int generation, GameVersion version = GameVersion.Any) - { - if (IsOtherFormMove(pkm, evos, move, generation, version, types: MoveSourceType.AllMachines)) - return true; - return MoveList.GetValidMoves(pkm, version, evos, generation, types: MoveSourceType.AllMachines).Contains(move); - } - - private static bool IsOtherFormMove(PKM pkm, EvoCriteria[] evos, int move, int generation, GameVersion version, MoveSourceType types) - { - if (!Legal.FormChangeMoves.TryGetValue(pkm.Species, out var criteria)) - return false; - if (!criteria(generation, pkm.Form)) - return false; - var list = new List(8); - MoveList.GetValidMovesAllForms(pkm, evos, version, generation, types, false, pkm.Species, list); - return list.Contains(move); - } - - public static bool CanKnowMove(PKM pkm, MemoryVariableSet memory, int gen, LegalInfo info, bool battleOnly = false) - { - var move = memory.Variable; - if (move == 0) - return false; - - if (pkm.HasMove(move)) - return true; - - if (pkm.IsEgg) - return false; - - if (GetCanKnowMove(pkm, move, gen, info.EvoChainsAllGens)) - return true; - - var enc = info.EncounterMatch; - if (enc is IMoveset ms && ms.Moves.Contains(move)) - return true; - if (enc is IRelearn r && r.Relearn.Contains(move)) - return true; - - // Relearns can be wiped via Battle Version. Check for eggs too. - if (IsSpecialEncounterMoveEggDeleted(pkm, enc)) + // Some moves can only be known in battle; outside of battle they aren't obtainable as a memory parameter. + switch (move) { - var em = MoveEgg.GetEggMoves(enc.Generation, enc.Species, enc.Form, enc.Version); - if (em.Contains(move)) + case (int)BehemothBlade when pk.Species == (int)Zacian: + case (int)BehemothBash when pk.Species == (int)Zamazenta: return true; } - - if (battleOnly) + if (gen == 8 && MoveInfo.IsDynamaxMove(move)) + return true; + if (pk.Species == (int)Ditto) { - // Some moves can only be known in battle; outside of battle they aren't obtainable as a memory parameter. - switch (move) + if (move == (int)Struggle) + return false; + return gen switch { - case (int)BehemothBlade when pkm.Species == (int)Zacian: - case (int)BehemothBash when pkm.Species == (int)Zamazenta: - return true; - } - if (gen == 8 && MoveInfo.IsDynamaxMove(move)) - return true; - if (pkm.Species == (int)Ditto) - { - if (move == (int)Struggle) - return false; - return gen switch - { - 8 => move <= Legal.MaxMoveID_8_R2 && !MoveInfo.IsDummiedMove(pkm, move), - _ => move <= Legal.MaxMoveID_6_AO, - }; - } + 8 => move <= Legal.MaxMoveID_8_R2 && !MoveInfo.IsDummiedMove(pk, move), + _ => move <= Legal.MaxMoveID_6_AO, + }; } + } + return false; + } + + private static bool IsSpecialEncounterMoveEggDeleted(PKM pk, IEncounterTemplate enc) + { + if (pk.IsOriginalMovesetDeleted()) + return true; + return enc is EncounterEgg { Generation: < 6 }; // egg moves that are no longer in the movepool + } + + public static bool GetCanRelearnMove(PKM pk, int move, int generation, EvoCriteria[] evos, GameVersion version = GameVersion.Any) + { + if (IsOtherFormMove(pk, evos, move, generation, version, types: MoveSourceType.Reminder)) + return true; + return MoveList.GetValidMoves(pk, version, evos, generation, types: MoveSourceType.Reminder).Contains(move); + } + + private static bool GetCanKnowMove(PKM pk, int move, int generation, EvolutionHistory evos, GameVersion version = GameVersion.Any) + { + if (pk.Species == (int)Smeargle) + return Legal.IsValidSketch(move, generation); + + if (generation >= 8 && MoveEgg.GetIsSharedEggMove(pk, generation, move)) + return true; + + if (evos.Length <= generation) return false; - } - - private static bool IsSpecialEncounterMoveEggDeleted(PKM pkm, IEncounterTemplate enc) + for (int i = 1; i <= generation; i++) { - if (pkm.IsOriginalMovesetDeleted()) - return true; - return enc is EncounterEgg { Generation: < 6 }; // egg moves that are no longer in the movepool - } + var chain = evos[i]; + if (chain.Length == 0) + continue; - public static bool GetCanRelearnMove(PKM pkm, int move, int generation, EvoCriteria[] evos, GameVersion version = GameVersion.Any) - { - if (IsOtherFormMove(pkm, evos, move, generation, version, types: MoveSourceType.Reminder)) - return true; - return MoveList.GetValidMoves(pkm, version, evos, generation, types: MoveSourceType.Reminder).Contains(move); - } - - private static bool GetCanKnowMove(PKM pkm, int move, int generation, EvolutionHistory evos, GameVersion version = GameVersion.Any) - { - if (pkm.Species == (int)Smeargle) - return Legal.IsValidSketch(move, generation); - - if (generation >= 8 && MoveEgg.GetIsSharedEggMove(pkm, generation, move)) + var moves = MoveList.GetValidMoves(pk, version, chain, i, types: MoveSourceType.All); + if (moves.Contains(move)) return true; - if (evos.Length <= generation) - return false; - for (int i = 1; i <= generation; i++) - { - var chain = evos[i]; - if (chain.Length == 0) - continue; - - var moves = MoveList.GetValidMoves(pkm, version, chain, i, types: MoveSourceType.All); - if (moves.Contains(move)) - return true; - - if (IsOtherFormMove(pkm, chain, move, i, GameVersion.Any, types: MoveSourceType.All)) - return true; - } - return false; + if (IsOtherFormMove(pk, chain, move, i, GameVersion.Any, types: MoveSourceType.All)) + return true; } + return false; + } - public static bool GetCanBeCaptured(int species, int gen, GameVersion version) => gen switch + public static bool GetCanBeCaptured(int species, int gen, GameVersion version) => gen switch + { + 6 => version switch { - 6 => version switch - { - GameVersion.Any => GetCanBeCaptured(species, SlotsX, StaticX) || GetCanBeCaptured(species, SlotsY, StaticY) - || GetCanBeCaptured(species, SlotsA, StaticA) || GetCanBeCaptured(species, SlotsO, StaticO), + GameVersion.Any => GetCanBeCaptured(species, SlotsX, StaticX) || GetCanBeCaptured(species, SlotsY, StaticY) + || GetCanBeCaptured(species, SlotsA, StaticA) || GetCanBeCaptured(species, SlotsO, StaticO), - GameVersion.X => GetCanBeCaptured(species, SlotsX, StaticX), - GameVersion.Y => GetCanBeCaptured(species, SlotsY, StaticY), + GameVersion.X => GetCanBeCaptured(species, SlotsX, StaticX), + GameVersion.Y => GetCanBeCaptured(species, SlotsY, StaticY), - GameVersion.AS => GetCanBeCaptured(species, SlotsA, StaticA), - GameVersion.OR => GetCanBeCaptured(species, SlotsO, StaticO), - _ => false, - }, - 8 => version switch - { - GameVersion.Any => GetCanBeCaptured(species, SlotsSW.Concat(SlotsSH), StaticSW.Concat(StaticSH)), - GameVersion.SW => GetCanBeCaptured(species, SlotsSW, StaticSW), - GameVersion.SH => GetCanBeCaptured(species, SlotsSH, StaticSH), - _ => false, - }, + GameVersion.AS => GetCanBeCaptured(species, SlotsA, StaticA), + GameVersion.OR => GetCanBeCaptured(species, SlotsO, StaticO), _ => false, - }; - - private static bool GetCanBeCaptured(int species, IEnumerable area, IEnumerable statics) + }, + 8 => version switch { - if (area.Any(loc => loc.HasSpecies(species))) - return true; - if (statics.Any(enc => enc.Species == species && !enc.Gift)) - return true; - return false; - } - - public static bool GetCanDynamaxTrainer(int species, int gen, GameVersion version) - { - if (gen != 8) - return false; - - return version switch - { - GameVersion.SW => DynamaxTrainer_SWSH.Contains(species) || IsDynamaxSW(species), - GameVersion.SH => DynamaxTrainer_SWSH.Contains(species) || IsDynamaxSH(species), - _ => DynamaxTrainer_SWSH.Contains(species) || IsDynamaxSW(species) || IsDynamaxSH(species), - }; - } - - // exclusive to version - private static bool IsDynamaxSW(int species) => species is (int)Machamp or (int)Gigalith or (int)Conkeldurr or (int)Coalossal or (int)Flapple; - private static bool IsDynamaxSH(int species) => species is (int)Gengar or (int)Lapras or (int)Dusknoir or (int)Froslass or (int)Appletun; - - // common to SW & SH - private static readonly HashSet DynamaxTrainer_SWSH = new() - { - (int)Venusaur, - (int)Blastoise, - (int)Charizard, - (int)Slowbro, - (int)Gyarados, - (int)Snorlax, - (int)Slowking, - (int)Torkoal, - (int)Vespiquen, - (int)Regigigas, - (int)Garbodor, - (int)Haxorus, - (int)Tsareena, - (int)Rillaboom, - (int)Inteleon, - (int)Cinderace, - (int)Greedent, - (int)Corviknight, - (int)Eldegoss, - (int)Drednaw, - (int)Centiskorch, - (int)Hatterene, - (int)Grimmsnarl, - (int)Alcremie, - (int)Copperajah, - (int)Duraludon, - (int)Urshifu, - }; - - public static bool GetCanFishSpecies(int species, int gen, GameVersion version) => gen switch - { - 6 => version switch - { - GameVersion.Any => FishingSpecies_XY.Contains(species) || FishingSpecies_AO.Contains(species) - || IsFishingSpeciesX(species) || IsFishingSpeciesY(species), - - GameVersion.X => FishingSpecies_XY.Contains(species) || IsFishingSpeciesX(species), - GameVersion.Y => FishingSpecies_XY.Contains(species) || IsFishingSpeciesY(species), - - GameVersion.OR or GameVersion.AS => FishingSpecies_AO.Contains(species), - _ => false, - }, - 8 => version switch - { - GameVersion.Any or GameVersion.SW or GameVersion.SH => FishingSpecies_SWSH.Contains(species), - _ => false, - }, + GameVersion.Any => GetCanBeCaptured(species, SlotsSW.Concat(SlotsSH), StaticSW.Concat(StaticSH)), + GameVersion.SW => GetCanBeCaptured(species, SlotsSW, StaticSW), + GameVersion.SH => GetCanBeCaptured(species, SlotsSH, StaticSH), _ => false, - }; + }, + _ => false, + }; - private static readonly HashSet FishingSpecies_SWSH = new() + private static bool GetCanBeCaptured(int species, IEnumerable area, IEnumerable statics) + { + if (area.Any(loc => loc.HasSpecies(species))) + return true; + if (statics.Any(enc => enc.Species == species && !enc.Gift)) + return true; + return false; + } + + public static bool GetCanDynamaxTrainer(int species, int gen, GameVersion version) + { + if (gen != 8) + return false; + + return version switch { - (int)Shellder, (int)Cloyster, - (int)Krabby, - (int)Goldeen, - (int)Magikarp, (int)Gyarados, - (int)Lapras, - (int)Dratini, - (int)Chinchou, (int)Lanturn, - (int)Qwilfish, - (int)Remoraid, (int)Octillery, - (int)Carvanha, (int)Sharpedo, - (int)Wailmer, (int)Wailord, - (int)Barboach, (int)Whiscash, - (int)Corphish, - (int)Lileep, - (int)Feebas, - (int)Mantyke, (int)Mantine, - (int)Basculin, - (int)Wishiwashi, - (int)Mareanie, - (int)Pyukumuku, - (int)Dhelmise, - (int)Chewtle, (int)Drednaw, - (int)Arrokuda, (int)Barraskewda, - }; - - private static readonly HashSet FishingSpecies_AO = new() - { - (int)Tentacool, - (int)Horsea, (int)Seadra, - (int)Goldeen, (int)Seaking, - (int)Staryu, - (int)Magikarp, (int)Gyarados, - (int)Corsola, - (int)Remoraid, (int)Octillery, - (int)Carvanha, (int)Sharpedo, - (int)Wailmer, - (int)Barboach, (int)Whiscash, - (int)Corphish, (int)Crawdaunt, - (int)Feebas, - (int)Luvdisc, - }; - - // exclusive to version - private static bool IsFishingSpeciesX(int species) => species is (int)Staryu or (int)Starmie or (int)Huntail or (int)Clauncher or (int)Clawitzer; - private static bool IsFishingSpeciesY(int species) => species is (int)Shellder or (int)Cloyster or (int)Gorebyss or (int)Skrelp or (int)Dragalge; - - // common to X & Y - private static readonly HashSet FishingSpecies_XY = new() - { - (int)Poliwag, (int)Poliwhirl, (int)Poliwrath, (int)Politoed, - (int)Horsea, (int)Seadra, - (int)Goldeen, (int)Seaking, - (int)Magikarp, (int)Gyarados, - (int)Dratini, (int)Dragonair, - (int)Chinchou, (int)Lanturn, - (int)Qwilfish, - (int)Corsola, - (int)Remoraid, (int)Octillery, - (int)Carvanha, (int)Sharpedo, - (int)Barboach, (int)Whiscash, - (int)Corphish, (int)Crawdaunt, - (int)Clamperl, - (int)Relicanth, - (int)Luvdisc, - (int)Basculin, - (int)Alomomola, + GameVersion.SW => DynamaxTrainer_SWSH.Contains(species) || IsDynamaxSW(species), + GameVersion.SH => DynamaxTrainer_SWSH.Contains(species) || IsDynamaxSH(species), + _ => DynamaxTrainer_SWSH.Contains(species) || IsDynamaxSW(species) || IsDynamaxSH(species), }; } + + // exclusive to version + private static bool IsDynamaxSW(int species) => species is (int)Machamp or (int)Gigalith or (int)Conkeldurr or (int)Coalossal or (int)Flapple; + private static bool IsDynamaxSH(int species) => species is (int)Gengar or (int)Lapras or (int)Dusknoir or (int)Froslass or (int)Appletun; + + // common to SW & SH + private static readonly HashSet DynamaxTrainer_SWSH = new() + { + (int)Venusaur, + (int)Blastoise, + (int)Charizard, + (int)Slowbro, + (int)Gyarados, + (int)Snorlax, + (int)Slowking, + (int)Torkoal, + (int)Vespiquen, + (int)Regigigas, + (int)Garbodor, + (int)Haxorus, + (int)Tsareena, + (int)Rillaboom, + (int)Inteleon, + (int)Cinderace, + (int)Greedent, + (int)Corviknight, + (int)Eldegoss, + (int)Drednaw, + (int)Centiskorch, + (int)Hatterene, + (int)Grimmsnarl, + (int)Alcremie, + (int)Copperajah, + (int)Duraludon, + (int)Urshifu, + }; + + public static bool GetCanFishSpecies(int species, int gen, GameVersion version) => gen switch + { + 6 => version switch + { + GameVersion.Any => FishingSpecies_XY.Contains(species) || FishingSpecies_AO.Contains(species) + || IsFishingSpeciesX(species) || IsFishingSpeciesY(species), + + GameVersion.X => FishingSpecies_XY.Contains(species) || IsFishingSpeciesX(species), + GameVersion.Y => FishingSpecies_XY.Contains(species) || IsFishingSpeciesY(species), + + GameVersion.OR or GameVersion.AS => FishingSpecies_AO.Contains(species), + _ => false, + }, + 8 => version switch + { + GameVersion.Any or GameVersion.SW or GameVersion.SH => FishingSpecies_SWSH.Contains(species), + _ => false, + }, + _ => false, + }; + + private static readonly HashSet FishingSpecies_SWSH = new() + { + (int)Shellder, (int)Cloyster, + (int)Krabby, + (int)Goldeen, + (int)Magikarp, (int)Gyarados, + (int)Lapras, + (int)Dratini, + (int)Chinchou, (int)Lanturn, + (int)Qwilfish, + (int)Remoraid, (int)Octillery, + (int)Carvanha, (int)Sharpedo, + (int)Wailmer, (int)Wailord, + (int)Barboach, (int)Whiscash, + (int)Corphish, + (int)Lileep, + (int)Feebas, + (int)Mantyke, (int)Mantine, + (int)Basculin, + (int)Wishiwashi, + (int)Mareanie, + (int)Pyukumuku, + (int)Dhelmise, + (int)Chewtle, (int)Drednaw, + (int)Arrokuda, (int)Barraskewda, + }; + + private static readonly HashSet FishingSpecies_AO = new() + { + (int)Tentacool, + (int)Horsea, (int)Seadra, + (int)Goldeen, (int)Seaking, + (int)Staryu, + (int)Magikarp, (int)Gyarados, + (int)Corsola, + (int)Remoraid, (int)Octillery, + (int)Carvanha, (int)Sharpedo, + (int)Wailmer, + (int)Barboach, (int)Whiscash, + (int)Corphish, (int)Crawdaunt, + (int)Feebas, + (int)Luvdisc, + }; + + // exclusive to version + private static bool IsFishingSpeciesX(int species) => species is (int)Staryu or (int)Starmie or (int)Huntail or (int)Clauncher or (int)Clawitzer; + private static bool IsFishingSpeciesY(int species) => species is (int)Shellder or (int)Cloyster or (int)Gorebyss or (int)Skrelp or (int)Dragalge; + + // common to X & Y + private static readonly HashSet FishingSpecies_XY = new() + { + (int)Poliwag, (int)Poliwhirl, (int)Poliwrath, (int)Politoed, + (int)Horsea, (int)Seadra, + (int)Goldeen, (int)Seaking, + (int)Magikarp, (int)Gyarados, + (int)Dratini, (int)Dragonair, + (int)Chinchou, (int)Lanturn, + (int)Qwilfish, + (int)Corsola, + (int)Remoraid, (int)Octillery, + (int)Carvanha, (int)Sharpedo, + (int)Barboach, (int)Whiscash, + (int)Corphish, (int)Crawdaunt, + (int)Clamperl, + (int)Relicanth, + (int)Luvdisc, + (int)Basculin, + (int)Alomomola, + }; } diff --git a/PKHeX.Core/Legality/Restrictions/Memories/MemoryVariableSet.cs b/PKHeX.Core/Legality/Restrictions/Memories/MemoryVariableSet.cs index 8e85ad840..fc4175def 100644 --- a/PKHeX.Core/Legality/Restrictions/Memories/MemoryVariableSet.cs +++ b/PKHeX.Core/Legality/Restrictions/Memories/MemoryVariableSet.cs @@ -2,10 +2,10 @@ public readonly record struct MemoryVariableSet(string Handler, byte MemoryID, ushort Variable, byte Intensity, byte Feeling) { - public static MemoryVariableSet Read(ITrainerMemories pkm, int handler) => handler switch + public static MemoryVariableSet Read(ITrainerMemories pk, int handler) => handler switch { - 0 => new MemoryVariableSet(LegalityCheckStrings.L_XOT, pkm.OT_Memory, pkm.OT_TextVar, pkm.OT_Intensity, pkm.OT_Feeling), // OT - 1 => new MemoryVariableSet(LegalityCheckStrings.L_XHT, pkm.HT_Memory, pkm.HT_TextVar, pkm.HT_Intensity, pkm.HT_Feeling), // HT + 0 => new MemoryVariableSet(LegalityCheckStrings.L_XOT, pk.OT_Memory, pk.OT_TextVar, pk.OT_Intensity, pk.OT_Feeling), // OT + 1 => new MemoryVariableSet(LegalityCheckStrings.L_XHT, pk.HT_Memory, pk.HT_TextVar, pk.HT_Intensity, pk.HT_Feeling), // HT _ => new MemoryVariableSet(LegalityCheckStrings.L_XOT, 0, 0, 0, 0), }; } diff --git a/PKHeX.Core/Legality/Restrictions/Vivillon3DS.cs b/PKHeX.Core/Legality/Restrictions/Vivillon3DS.cs index 694a1af29..3d2035e4f 100644 --- a/PKHeX.Core/Legality/Restrictions/Vivillon3DS.cs +++ b/PKHeX.Core/Legality/Restrictions/Vivillon3DS.cs @@ -1,285 +1,284 @@ using System; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides information for Vivillon origins with respect to the 3DS game data. +/// +public static class Vivillon3DS { - /// - /// Provides information for Vivillon origins with respect to the 3DS game data. - /// - public static class Vivillon3DS + public const int MaxWildFormID = 17; // 0-17 valid form indexes + + private sealed class CountryTable { - public const int MaxWildFormID = 17; // 0-17 valid form indexes + public readonly byte BaseForm; + public readonly byte CountryID; + public readonly FormSubregionTable[] SubRegionForms; - private sealed class CountryTable + public CountryTable(byte country, byte form, params FormSubregionTable[] subs) { - public readonly byte BaseForm; - public readonly byte CountryID; - public readonly FormSubregionTable[] SubRegionForms; - - public CountryTable(byte country, byte form, params FormSubregionTable[] subs) - { - BaseForm = form; - CountryID = country; - SubRegionForms = subs; - } - } - - private sealed class FormSubregionTable - { - public readonly byte Form; - public readonly byte[] Regions; - - internal FormSubregionTable(byte form, byte[] regions) - { - Form = form; - Regions = regions; - } - } - - /// - /// List of valid regions as bitflags indexed by Vivillon form. - /// - private static readonly Region3DSFlags[] VivillonRegionTable = - { - /* 0 Icy Snow */ Region3DSFlags.Americas | Region3DSFlags.Europe, - /* 1 Polar */ Region3DSFlags.Americas | Region3DSFlags.Europe | Region3DSFlags.China, - /* 2 Tundra */ Region3DSFlags.Japan | Region3DSFlags.Europe, - /* 3 Continental */ Region3DSFlags.Americas | Region3DSFlags.Europe | Region3DSFlags.China | Region3DSFlags.Korea | Region3DSFlags.Taiwan, - /* 4 Garden */ Region3DSFlags.Europe, - /* 5 Elegant */ Region3DSFlags.Japan, - /* 6 Meadow */ Region3DSFlags.Europe, - /* 7 Modern */ Region3DSFlags.Americas, - /* 8 Marine */ Region3DSFlags.Americas | Region3DSFlags.Europe, - /* 9 Archipelago */ Region3DSFlags.Americas | Region3DSFlags.Europe, - /*10 High Plains */ Region3DSFlags.Americas | Region3DSFlags.Europe | Region3DSFlags.China, - /*11 Sandstorm */ Region3DSFlags.Americas | Region3DSFlags.Europe, - /*12 River */ Region3DSFlags.Europe, - /*13 Monsoon */ Region3DSFlags.Japan | Region3DSFlags.Europe | Region3DSFlags.China | Region3DSFlags.Taiwan, - /*14 Savanna */ Region3DSFlags.Americas, - /*15 Sun */ Region3DSFlags.Americas | Region3DSFlags.Europe, - /*16 Ocean */ Region3DSFlags.Americas | Region3DSFlags.Europe, - /*17 Jungle */ Region3DSFlags.Americas | Region3DSFlags.Europe, - }; - - private static Region3DSFlags GetConsoleRegionFlag(int consoleRegion) => (Region3DSFlags)(1 << consoleRegion); - - /// - /// List of valid countries for each Vivillon form. - /// - private static readonly byte[][] VivillonCountryTable = - { - /* 0 Icy Snow */ new byte[] {018,076,096,100,107}, - /* 1 Polar */ new byte[] {010,018,020,049,076,096,100,107,160}, - /* 2 Tundra */ new byte[] {001,074,081,096}, - /* 3 Continental */ new byte[] {010,067,073,074,075,077,078,084,087,094,096,097,100,107,128,136,144,160,169}, - /* 4 Garden */ new byte[] {065,082,095,110,125}, - /* 5 Elegant */ new byte[] {001}, - /* 6 Meadow */ new byte[] {066,077,078,083,086,088,105,108,122,127}, - /* 7 Modern */ new byte[] {018,049,186}, - /* 8 Marine */ new byte[] {020,064,066,068,070,071,073,077,078,079,080,083,089,090,091,098,099,100,101,102,103,105,109,123,124,126,184,185}, - /* 9 Archipelago */ new byte[] {008,009,011,012,013,017,021,023,024,028,029,032,034,035,036,037,038,043,044,045,047,048,049,051,052,077,085,104}, - /*10 High Plains */ new byte[] {018,036,049,100,109,113,160}, - /*11 Sandstorm */ new byte[] {072,109,118,119,120,121,168,174}, - /*12 River */ new byte[] {065,069,085,093,104,105,114,115,116,117}, - /*13 Monsoon */ new byte[] {001,128,160,169}, - /*14 Savanna */ new byte[] {010,015,016,041,042,050}, - /*15 Sun */ new byte[] {014,019,026,030,033,036,039,065,085,092,104,106,111,112}, - /*16 Ocean */ new byte[] {049,077}, - /*17 Jungle */ new byte[] {016,021,022,025,027,031,040,042,046,052,077,153,156,169}, - }; - - /// - /// List of valid subregions for countries that can have multiple Vivillon forms. - /// - /// BaseForm is the form for no selected subregion. - private static readonly CountryTable[] RegionFormTable = - { - new(001, 05, // Japan: Elegant - new FormSubregionTable(02, new byte[] {03,04}), - new FormSubregionTable(13, new byte[] {48})), - - new(010, 14, // Argentina: Savanna - new FormSubregionTable(01, new byte[] {21,24}), - new FormSubregionTable(03, new byte[] {06,12,14,16,17,19,20})), - - new(016, 14, // Brazil: Savanna - new FormSubregionTable(17, new byte[] {03,05,06,21,22})), - - new(018, 01, // Canada: Polar - new FormSubregionTable(00, new byte[] {12,13,14}), - new FormSubregionTable(07, new byte[] {05}), - new FormSubregionTable(10, new byte[] {04})), - - new(020, 08, // Chile: Marine - new FormSubregionTable(01, new byte[] {12})), - - new(021, 17, // Colombia: Jungle - new FormSubregionTable(09, new byte[] {07,19,20})), - - new(036, 15, // Mexico: Sun - new FormSubregionTable(10, new byte[] {03,04,05,08,09,11,12,15,19,20,23,25,26,27,29,33})), - - new(042, 14, // Peru: Savanna - new FormSubregionTable(17, new byte[] {03,08,12,15,16,17,21,23,25,26})), - - new(049, 07, // USA: Modern - new FormSubregionTable(01, new byte[] {03,09,21,23,24,32,33,36,40,41,48,50}), - new FormSubregionTable(09, new byte[] {53}), - new FormSubregionTable(10, new byte[] {06,07,08,15,28,34,35,39,46,49}), - new FormSubregionTable(16, new byte[] {13})), - - new(052, 09, // Venezuela: Archipelago - new FormSubregionTable(17, new byte[] {03,04,05,07,08,09,10,11,13,15,17,19,21})), - - new(065, 12, // Australia: River - new FormSubregionTable(04, new byte[] {07}), - new FormSubregionTable(15, new byte[] {04})), - - new(066, 08, // Austria: Marine - new FormSubregionTable(06, new byte[] {10})), - - new(073, 03, // Czech Republic: Continental - new FormSubregionTable(08, new byte[] {04,05,13,14,15})), - - new(074, 03, // Denmark: Continental - new FormSubregionTable(02, new byte[] {18,24})), - - new(076, 00, // Finland: Icy Snow - new FormSubregionTable(01, new byte[] {27})), - - new(077, 06, // France: Meadow - new FormSubregionTable(03, new byte[] {18}), - new FormSubregionTable(08, new byte[] {04,06,08,19}), - new FormSubregionTable(09, new byte[] {24,25}), - new FormSubregionTable(16, new byte[] {27}), - new FormSubregionTable(17, new byte[] {26})), - - new(078, 03, // Germany: Continental - new FormSubregionTable(06, new byte[] {04,12,13}), - new FormSubregionTable(08, new byte[] {05})), - - new(083, 08, // Italy: Marine - new FormSubregionTable(06, new byte[] {03,04,05,06})), - - new(085, 12, // Lesotho: River - new FormSubregionTable(09, new byte[] {10}), - new FormSubregionTable(15, new byte[] {06,07,08,09})), - - new(096, 03, // Norway: Continental - new FormSubregionTable(00, new byte[] {11,26}), - new FormSubregionTable(01, new byte[] {12,15,16,17,20,22}), - new FormSubregionTable(02, new byte[] {13,14,19})), - - new(100, 03, // Russia: Continental - new FormSubregionTable(00, new byte[] {14,22,34,38,40,52,53,66,88}), - new FormSubregionTable(01, new byte[] {11,12,13,16,19,21,23,26,27,32,35,36,37,39,41,43,44,48,49,50,54,55,56,57,58,60,61,62,67,68,70,74,75,76,77,80,81,82,83,84,90,91}), - new FormSubregionTable(08, new byte[] {42,64}), - new FormSubregionTable(10, new byte[] {10,15,20,24,25,28,30,33,71,73,85})), - - new(104, 12, // South Africa: River - new FormSubregionTable(15, new byte[] {06,09}), - new FormSubregionTable(09, new byte[] {03,05})), - - new(105, 08, // Spain: Marine - new FormSubregionTable(06, new byte[] {11}), - new FormSubregionTable(12, new byte[] {07})), - - new(107, 03, // Sweden: Continental - new FormSubregionTable(00, new byte[] {10,11}), - new FormSubregionTable(01, new byte[] {09,13,17})), - - new(109, 11, // Turkey: Sandstorm - new FormSubregionTable(08, new byte[] {03,04,05,17,20,23,24,26,27,28,30,33,34,36,41,42,44,47,48,50,52,55,57,60,62,63,65,66,69,71,73,75,80,83}), - new FormSubregionTable(10, new byte[] {53,54,70,79})), - - new(128, 13, // Taiwan: Monsoon - new FormSubregionTable(03, new byte[] {24,25,26})), - - new(160, 03, // China: Continental - new FormSubregionTable(01, new byte[] {13,19,20}), - new FormSubregionTable(10, new byte[] {30,32}), - new FormSubregionTable(13, new byte[] {10,26,29,33})), - - new(169, 13, // India: Monsoon - new FormSubregionTable(03, new byte[] {06,09,10,21,36}), - new FormSubregionTable(17, new byte[] {12})), - }; - - /// - /// Compares the Vivillon pattern against its console region to determine if the pattern is legal. - /// - public static bool IsPatternValid(int form, int consoleRegion) - { - if ((uint)form > MaxWildFormID) - return false; - var permit = GetConsoleRegionFlag(consoleRegion); - return VivillonRegionTable[form].HasFlag(permit); - } - - /// - /// Compares the Vivillon pattern against its country and subregion to determine if the pattern could have been natively obtained. - /// - /// Alternate Form Pattern - /// Country ID - /// Subregion ID - /// True if valid - public static bool IsPatternNative(int form, byte country, byte region) - { - if ((uint)form > MaxWildFormID) - return false; - if (!VivillonCountryTable[form].Contains(country)) - return false; // Country mismatch - - var ct = Array.Find(RegionFormTable, t => t.CountryID == country); - if (ct == null) // empty = one form for country - return true; // No subregion table, already checked if Country can have this form - - if (ct.BaseForm == form) - return !ct.SubRegionForms.Any(e => e.Regions.Contains(region)); //true if Mainform not in other specific region - - return ct.SubRegionForms.Any(e => e.Form == form && e.Regions.Contains(region)); - } - - /// - /// Gets a compatible Vivillon pattern based on its country and subregion. - /// - /// Country ID - /// Subregion ID - public static int GetPattern(byte country, byte region) - { - var ct = Array.Find(RegionFormTable, t => t.CountryID == country); - if (ct == null) // empty = no forms referenced - return GetPattern(country); - - foreach (var sub in ct.SubRegionForms) - { - if (sub.Regions.Contains(region)) - return sub.Form; - } - - return ct.BaseForm; - } - - private static int GetPattern(byte country) - { - var form = Array.FindIndex(VivillonCountryTable, z => z.Contains(country)); - return Math.Max(0, form); - } - - /// - /// Console Region Flags for the 3DS. - /// - /// Not to be confused with . - [Flags] - private enum Region3DSFlags : ushort - { - None, - Japan = 1, - Americas = 1 << 1, - Europe = 1 << 2, - China = 1 << 4, - Korea = 1 << 5, - Taiwan = 1 << 6, + BaseForm = form; + CountryID = country; + SubRegionForms = subs; } } + + private sealed class FormSubregionTable + { + public readonly byte Form; + public readonly byte[] Regions; + + internal FormSubregionTable(byte form, byte[] regions) + { + Form = form; + Regions = regions; + } + } + + /// + /// List of valid regions as bitflags indexed by Vivillon form. + /// + private static readonly Region3DSFlags[] VivillonRegionTable = + { + /* 0 Icy Snow */ Region3DSFlags.Americas | Region3DSFlags.Europe, + /* 1 Polar */ Region3DSFlags.Americas | Region3DSFlags.Europe | Region3DSFlags.China, + /* 2 Tundra */ Region3DSFlags.Japan | Region3DSFlags.Europe, + /* 3 Continental */ Region3DSFlags.Americas | Region3DSFlags.Europe | Region3DSFlags.China | Region3DSFlags.Korea | Region3DSFlags.Taiwan, + /* 4 Garden */ Region3DSFlags.Europe, + /* 5 Elegant */ Region3DSFlags.Japan, + /* 6 Meadow */ Region3DSFlags.Europe, + /* 7 Modern */ Region3DSFlags.Americas, + /* 8 Marine */ Region3DSFlags.Americas | Region3DSFlags.Europe, + /* 9 Archipelago */ Region3DSFlags.Americas | Region3DSFlags.Europe, + /*10 High Plains */ Region3DSFlags.Americas | Region3DSFlags.Europe | Region3DSFlags.China, + /*11 Sandstorm */ Region3DSFlags.Americas | Region3DSFlags.Europe, + /*12 River */ Region3DSFlags.Europe, + /*13 Monsoon */ Region3DSFlags.Japan | Region3DSFlags.Europe | Region3DSFlags.China | Region3DSFlags.Taiwan, + /*14 Savanna */ Region3DSFlags.Americas, + /*15 Sun */ Region3DSFlags.Americas | Region3DSFlags.Europe, + /*16 Ocean */ Region3DSFlags.Americas | Region3DSFlags.Europe, + /*17 Jungle */ Region3DSFlags.Americas | Region3DSFlags.Europe, + }; + + private static Region3DSFlags GetConsoleRegionFlag(int consoleRegion) => (Region3DSFlags)(1 << consoleRegion); + + /// + /// List of valid countries for each Vivillon form. + /// + private static readonly byte[][] VivillonCountryTable = + { + /* 0 Icy Snow */ new byte[] {018,076,096,100,107}, + /* 1 Polar */ new byte[] {010,018,020,049,076,096,100,107,160}, + /* 2 Tundra */ new byte[] {001,074,081,096}, + /* 3 Continental */ new byte[] {010,067,073,074,075,077,078,084,087,094,096,097,100,107,128,136,144,160,169}, + /* 4 Garden */ new byte[] {065,082,095,110,125}, + /* 5 Elegant */ new byte[] {001}, + /* 6 Meadow */ new byte[] {066,077,078,083,086,088,105,108,122,127}, + /* 7 Modern */ new byte[] {018,049,186}, + /* 8 Marine */ new byte[] {020,064,066,068,070,071,073,077,078,079,080,083,089,090,091,098,099,100,101,102,103,105,109,123,124,126,184,185}, + /* 9 Archipelago */ new byte[] {008,009,011,012,013,017,021,023,024,028,029,032,034,035,036,037,038,043,044,045,047,048,049,051,052,077,085,104}, + /*10 High Plains */ new byte[] {018,036,049,100,109,113,160}, + /*11 Sandstorm */ new byte[] {072,109,118,119,120,121,168,174}, + /*12 River */ new byte[] {065,069,085,093,104,105,114,115,116,117}, + /*13 Monsoon */ new byte[] {001,128,160,169}, + /*14 Savanna */ new byte[] {010,015,016,041,042,050}, + /*15 Sun */ new byte[] {014,019,026,030,033,036,039,065,085,092,104,106,111,112}, + /*16 Ocean */ new byte[] {049,077}, + /*17 Jungle */ new byte[] {016,021,022,025,027,031,040,042,046,052,077,153,156,169}, + }; + + /// + /// List of valid subregions for countries that can have multiple Vivillon forms. + /// + /// BaseForm is the form for no selected subregion. + private static readonly CountryTable[] RegionFormTable = + { + new(001, 05, // Japan: Elegant + new FormSubregionTable(02, new byte[] {03,04}), + new FormSubregionTable(13, new byte[] {48})), + + new(010, 14, // Argentina: Savanna + new FormSubregionTable(01, new byte[] {21,24}), + new FormSubregionTable(03, new byte[] {06,12,14,16,17,19,20})), + + new(016, 14, // Brazil: Savanna + new FormSubregionTable(17, new byte[] {03,05,06,21,22})), + + new(018, 01, // Canada: Polar + new FormSubregionTable(00, new byte[] {12,13,14}), + new FormSubregionTable(07, new byte[] {05}), + new FormSubregionTable(10, new byte[] {04})), + + new(020, 08, // Chile: Marine + new FormSubregionTable(01, new byte[] {12})), + + new(021, 17, // Colombia: Jungle + new FormSubregionTable(09, new byte[] {07,19,20})), + + new(036, 15, // Mexico: Sun + new FormSubregionTable(10, new byte[] {03,04,05,08,09,11,12,15,19,20,23,25,26,27,29,33})), + + new(042, 14, // Peru: Savanna + new FormSubregionTable(17, new byte[] {03,08,12,15,16,17,21,23,25,26})), + + new(049, 07, // USA: Modern + new FormSubregionTable(01, new byte[] {03,09,21,23,24,32,33,36,40,41,48,50}), + new FormSubregionTable(09, new byte[] {53}), + new FormSubregionTable(10, new byte[] {06,07,08,15,28,34,35,39,46,49}), + new FormSubregionTable(16, new byte[] {13})), + + new(052, 09, // Venezuela: Archipelago + new FormSubregionTable(17, new byte[] {03,04,05,07,08,09,10,11,13,15,17,19,21})), + + new(065, 12, // Australia: River + new FormSubregionTable(04, new byte[] {07}), + new FormSubregionTable(15, new byte[] {04})), + + new(066, 08, // Austria: Marine + new FormSubregionTable(06, new byte[] {10})), + + new(073, 03, // Czech Republic: Continental + new FormSubregionTable(08, new byte[] {04,05,13,14,15})), + + new(074, 03, // Denmark: Continental + new FormSubregionTable(02, new byte[] {18,24})), + + new(076, 00, // Finland: Icy Snow + new FormSubregionTable(01, new byte[] {27})), + + new(077, 06, // France: Meadow + new FormSubregionTable(03, new byte[] {18}), + new FormSubregionTable(08, new byte[] {04,06,08,19}), + new FormSubregionTable(09, new byte[] {24,25}), + new FormSubregionTable(16, new byte[] {27}), + new FormSubregionTable(17, new byte[] {26})), + + new(078, 03, // Germany: Continental + new FormSubregionTable(06, new byte[] {04,12,13}), + new FormSubregionTable(08, new byte[] {05})), + + new(083, 08, // Italy: Marine + new FormSubregionTable(06, new byte[] {03,04,05,06})), + + new(085, 12, // Lesotho: River + new FormSubregionTable(09, new byte[] {10}), + new FormSubregionTable(15, new byte[] {06,07,08,09})), + + new(096, 03, // Norway: Continental + new FormSubregionTable(00, new byte[] {11,26}), + new FormSubregionTable(01, new byte[] {12,15,16,17,20,22}), + new FormSubregionTable(02, new byte[] {13,14,19})), + + new(100, 03, // Russia: Continental + new FormSubregionTable(00, new byte[] {14,22,34,38,40,52,53,66,88}), + new FormSubregionTable(01, new byte[] {11,12,13,16,19,21,23,26,27,32,35,36,37,39,41,43,44,48,49,50,54,55,56,57,58,60,61,62,67,68,70,74,75,76,77,80,81,82,83,84,90,91}), + new FormSubregionTable(08, new byte[] {42,64}), + new FormSubregionTable(10, new byte[] {10,15,20,24,25,28,30,33,71,73,85})), + + new(104, 12, // South Africa: River + new FormSubregionTable(15, new byte[] {06,09}), + new FormSubregionTable(09, new byte[] {03,05})), + + new(105, 08, // Spain: Marine + new FormSubregionTable(06, new byte[] {11}), + new FormSubregionTable(12, new byte[] {07})), + + new(107, 03, // Sweden: Continental + new FormSubregionTable(00, new byte[] {10,11}), + new FormSubregionTable(01, new byte[] {09,13,17})), + + new(109, 11, // Turkey: Sandstorm + new FormSubregionTable(08, new byte[] {03,04,05,17,20,23,24,26,27,28,30,33,34,36,41,42,44,47,48,50,52,55,57,60,62,63,65,66,69,71,73,75,80,83}), + new FormSubregionTable(10, new byte[] {53,54,70,79})), + + new(128, 13, // Taiwan: Monsoon + new FormSubregionTable(03, new byte[] {24,25,26})), + + new(160, 03, // China: Continental + new FormSubregionTable(01, new byte[] {13,19,20}), + new FormSubregionTable(10, new byte[] {30,32}), + new FormSubregionTable(13, new byte[] {10,26,29,33})), + + new(169, 13, // India: Monsoon + new FormSubregionTable(03, new byte[] {06,09,10,21,36}), + new FormSubregionTable(17, new byte[] {12})), + }; + + /// + /// Compares the Vivillon pattern against its console region to determine if the pattern is legal. + /// + public static bool IsPatternValid(int form, int consoleRegion) + { + if ((uint)form > MaxWildFormID) + return false; + var permit = GetConsoleRegionFlag(consoleRegion); + return VivillonRegionTable[form].HasFlag(permit); + } + + /// + /// Compares the Vivillon pattern against its country and subregion to determine if the pattern could have been natively obtained. + /// + /// Alternate Form Pattern + /// Country ID + /// Subregion ID + /// True if valid + public static bool IsPatternNative(int form, byte country, byte region) + { + if ((uint)form > MaxWildFormID) + return false; + if (!VivillonCountryTable[form].Contains(country)) + return false; // Country mismatch + + var ct = Array.Find(RegionFormTable, t => t.CountryID == country); + if (ct == null) // empty = one form for country + return true; // No subregion table, already checked if Country can have this form + + if (ct.BaseForm == form) + return !ct.SubRegionForms.Any(e => e.Regions.Contains(region)); //true if Mainform not in other specific region + + return ct.SubRegionForms.Any(e => e.Form == form && e.Regions.Contains(region)); + } + + /// + /// Gets a compatible Vivillon pattern based on its country and subregion. + /// + /// Country ID + /// Subregion ID + public static int GetPattern(byte country, byte region) + { + var ct = Array.Find(RegionFormTable, t => t.CountryID == country); + if (ct == null) // empty = no forms referenced + return GetPattern(country); + + foreach (var sub in ct.SubRegionForms) + { + if (sub.Regions.Contains(region)) + return sub.Form; + } + + return ct.BaseForm; + } + + private static int GetPattern(byte country) + { + var form = Array.FindIndex(VivillonCountryTable, z => z.Contains(country)); + return Math.Max(0, form); + } + + /// + /// Console Region Flags for the 3DS. + /// + /// Not to be confused with . + [Flags] + private enum Region3DSFlags : ushort + { + None, + Japan = 1, + Americas = 1 << 1, + Europe = 1 << 2, + China = 1 << 4, + Korea = 1 << 5, + Taiwan = 1 << 6, + } } diff --git a/PKHeX.Core/Legality/Restrictions/WordFilter.cs b/PKHeX.Core/Legality/Restrictions/WordFilter.cs index 1c6088ccd..4b60471cc 100644 --- a/PKHeX.Core/Legality/Restrictions/WordFilter.cs +++ b/PKHeX.Core/Legality/Restrictions/WordFilter.cs @@ -1,84 +1,83 @@ using System.Collections.Generic; using System.Text.RegularExpressions; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Bad-word Filter class containing logic to check against unsavory regular expressions. +/// +public static class WordFilter { /// - /// Bad-word Filter class containing logic to check against unsavory regular expressions. + /// Regex patterns to check against /// - public static class WordFilter + /// No need to keep the original pattern strings around; the object retrieves this via + private static readonly Regex[] Regexes = LoadPatterns(Util.GetStringList("badwords")); + + // if you're running this as a server and don't mind a few extra seconds of startup, add RegexOptions.Compiled for slightly better checking. + private const RegexOptions Options = RegexOptions.CultureInvariant; + + private static Regex[] LoadPatterns(IReadOnlyList patterns) { - /// - /// Regex patterns to check against - /// - /// No need to keep the original pattern strings around; the object retrieves this via - private static readonly Regex[] Regexes = LoadPatterns(Util.GetStringList("badwords")); + var result = new Regex[patterns.Count]; + for (int i = 0; i < patterns.Count; i++) + result[i] = new Regex(patterns[i], Options); + return result; + } - // if you're running this as a server and don't mind a few extra seconds of startup, add RegexOptions.Compiled for slightly better checking. - private const RegexOptions Options = RegexOptions.CultureInvariant; + /// + /// Due to some messages repeating (Trainer names), keep a list of repeated values for faster lookup. + /// + private static readonly Dictionary Lookup = new(INIT_COUNT); - private static Regex[] LoadPatterns(IReadOnlyList patterns) + private const string NoMatch = ""; + + /// + /// Checks to see if a phrase contains filtered content. + /// + /// Phrase to check for + /// Matching regex that filters the phrase. + /// Boolean result if the message is filtered or not. + public static bool IsFiltered(string message, out string regMatch) + { + if (string.IsNullOrWhiteSpace(message) || message.Length <= 1) { - var result = new Regex[patterns.Count]; - for (int i = 0; i < patterns.Count; i++) - result[i] = new Regex(patterns[i], Options); - return result; - } - - /// - /// Due to some messages repeating (Trainer names), keep a list of repeated values for faster lookup. - /// - private static readonly Dictionary Lookup = new(INIT_COUNT); - - private const string NoMatch = ""; - - /// - /// Checks to see if a phrase contains filtered content. - /// - /// Phrase to check for - /// Matching regex that filters the phrase. - /// Boolean result if the message is filtered or not. - public static bool IsFiltered(string message, out string regMatch) - { - if (string.IsNullOrWhiteSpace(message) || message.Length <= 1) - { - regMatch = NoMatch; - return false; - } - - var msg = message.ToLowerInvariant(); - // Check dictionary - lock (dictLock) - { - if (Lookup.TryGetValue(msg, out regMatch)) - return !ReferenceEquals(regMatch, NoMatch); - } - - // not in dictionary, check patterns - foreach (var regex in Regexes) - { - if (!regex.IsMatch(msg)) - continue; - - // match found, cache result - regMatch = regex.ToString(); // fetches from regex field - lock (dictLock) - Lookup[msg] = regMatch; - return true; - } - - // didn't match any pattern, cache result - lock (dictLock) - { - if ((Lookup.Count & ~MAX_COUNT) != 0) - Lookup.Clear(); // reset - Lookup[msg] = regMatch = NoMatch; - } + regMatch = NoMatch; return false; } - private static readonly object dictLock = new(); - private const int MAX_COUNT = (1 << 17) - 1; // arbitrary cap for max dictionary size - private const int INIT_COUNT = 1 << 10; // arbitrary init size to limit future doublings + var msg = message.ToLowerInvariant(); + // Check dictionary + lock (dictLock) + { + if (Lookup.TryGetValue(msg, out regMatch)) + return !ReferenceEquals(regMatch, NoMatch); + } + + // not in dictionary, check patterns + foreach (var regex in Regexes) + { + if (!regex.IsMatch(msg)) + continue; + + // match found, cache result + regMatch = regex.ToString(); // fetches from regex field + lock (dictLock) + Lookup[msg] = regMatch; + return true; + } + + // didn't match any pattern, cache result + lock (dictLock) + { + if ((Lookup.Count & ~MAX_COUNT) != 0) + Lookup.Clear(); // reset + Lookup[msg] = regMatch = NoMatch; + } + return false; } + + private static readonly object dictLock = new(); + private const int MAX_COUNT = (1 << 17) - 1; // arbitrary cap for max dictionary size + private const int INIT_COUNT = 1 << 10; // arbitrary init size to limit future doublings } diff --git a/PKHeX.Core/Legality/Structures/EggMoves.cs b/PKHeX.Core/Legality/Structures/EggMoves.cs index 9d7a17715..54faa7844 100644 --- a/PKHeX.Core/Legality/Structures/EggMoves.cs +++ b/PKHeX.Core/Legality/Structures/EggMoves.cs @@ -2,107 +2,106 @@ using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class EggMoves { - public abstract class EggMoves - { - public readonly int[] Moves; - protected EggMoves(int[] moves) => Moves = moves; - public bool GetHasEggMove(int move) => Moves.Contains(move); - } + public readonly int[] Moves; + protected EggMoves(int[] moves) => Moves = moves; + public bool GetHasEggMove(int move) => Moves.Contains(move); +} - public sealed class EggMoves2 : EggMoves - { - private EggMoves2(int[] moves) : base(moves) { } +public sealed class EggMoves2 : EggMoves +{ + private EggMoves2(int[] moves) : base(moves) { } - public static EggMoves2[] GetArray(ReadOnlySpan data, int count) + public static EggMoves2[] GetArray(ReadOnlySpan data, int count) + { + var entries = new EggMoves2[count + 1]; + var empty = entries[0] = new EggMoves2(Array.Empty()); + + int baseOffset = ReadInt16LittleEndian(data) - (count * 2); + for (int i = 1; i < entries.Length; i++) { - var entries = new EggMoves2[count + 1]; - var empty = entries[0] = new EggMoves2(Array.Empty()); - - int baseOffset = ReadInt16LittleEndian(data) - (count * 2); - for (int i = 1; i < entries.Length; i++) + int start = ReadInt16LittleEndian(data[((i - 1) * 2)..]) - baseOffset; + var slice = data[start..]; + int moveCount = slice.IndexOf((byte)0xFF); + if (moveCount == 0) { - int start = ReadInt16LittleEndian(data[((i - 1) * 2)..]) - baseOffset; - var slice = data[start..]; - int moveCount = slice.IndexOf((byte)0xFF); - if (moveCount == 0) - { - entries[i] = empty; - continue; - } - - int[] moves = new int[moveCount]; - for (int m = 0; m < moves.Length; m++) - moves[m] = slice[m]; - - entries[i] = new EggMoves2(moves); + entries[i] = empty; + continue; } - return entries; + int[] moves = new int[moveCount]; + for (int m = 0; m < moves.Length; m++) + moves[m] = slice[m]; + + entries[i] = new EggMoves2(moves); } - } - public sealed class EggMoves6 : EggMoves - { - private EggMoves6(int[] moves) : base(moves) { } - - public static EggMoves6[] GetArray(BinLinkerAccessor entries) - { - var result = new EggMoves6[entries.Length]; - var empty = result[0] = new EggMoves6(Array.Empty()); - for (int i = 1; i < result.Length; i++) - { - var data = entries[i]; - int count = ReadInt16LittleEndian(data); - if (count == 0) - { - result[i] = empty; - continue; - } - - var moves = new int[count]; - var span = data[2..]; - for (int j = 0; j < moves.Length; j++) - moves[j] = ReadInt16LittleEndian(span[(j * 2)..]); - result[i] = new EggMoves6(moves); - } - return result; - } - } - - public sealed class EggMoves7 : EggMoves - { - public readonly int FormTableIndex; - - private EggMoves7(int[] moves, int formIndex = 0) : base(moves) => FormTableIndex = formIndex; - - public static EggMoves7[] GetArray(BinLinkerAccessor entries) - { - var result = new EggMoves7[entries.Length]; - var empty = result[0] = new EggMoves7(Array.Empty()); - for (int i = 1; i < result.Length; i++) - { - var data = entries[i]; - int count = ReadInt16LittleEndian(data[2..]); - int formIndex = ReadInt16LittleEndian(data); - if (count == 0) - { - // Might need to keep track of the Form Index for unavailable forms pointing to valid forms. - if (formIndex != 0) - result[i] = new EggMoves7(Array.Empty(), formIndex); - else - result[i] = empty; - continue; - } - - var moves = new int[count]; - var span = data[4..]; - for (int j = 0; j < moves.Length; j++) - moves[j] = ReadInt16LittleEndian(span[(j * 2)..]); - result[i] = new EggMoves7(moves, formIndex); - } - return result; - } + return entries; + } +} + +public sealed class EggMoves6 : EggMoves +{ + private EggMoves6(int[] moves) : base(moves) { } + + public static EggMoves6[] GetArray(BinLinkerAccessor entries) + { + var result = new EggMoves6[entries.Length]; + var empty = result[0] = new EggMoves6(Array.Empty()); + for (int i = 1; i < result.Length; i++) + { + var data = entries[i]; + int count = ReadInt16LittleEndian(data); + if (count == 0) + { + result[i] = empty; + continue; + } + + var moves = new int[count]; + var span = data[2..]; + for (int j = 0; j < moves.Length; j++) + moves[j] = ReadInt16LittleEndian(span[(j * 2)..]); + result[i] = new EggMoves6(moves); + } + return result; + } +} + +public sealed class EggMoves7 : EggMoves +{ + public readonly int FormTableIndex; + + private EggMoves7(int[] moves, int formIndex = 0) : base(moves) => FormTableIndex = formIndex; + + public static EggMoves7[] GetArray(BinLinkerAccessor entries) + { + var result = new EggMoves7[entries.Length]; + var empty = result[0] = new EggMoves7(Array.Empty()); + for (int i = 1; i < result.Length; i++) + { + var data = entries[i]; + int count = ReadInt16LittleEndian(data[2..]); + int formIndex = ReadInt16LittleEndian(data); + if (count == 0) + { + // Might need to keep track of the Form Index for unavailable forms pointing to valid forms. + if (formIndex != 0) + result[i] = new EggMoves7(Array.Empty(), formIndex); + else + result[i] = empty; + continue; + } + + var moves = new int[count]; + var span = data[4..]; + for (int j = 0; j < moves.Length; j++) + moves[j] = ReadInt16LittleEndian(span[(j * 2)..]); + result[i] = new EggMoves7(moves, formIndex); + } + return result; } } diff --git a/PKHeX.Core/Legality/Structures/IFixedAbilityNumber.cs b/PKHeX.Core/Legality/Structures/IFixedAbilityNumber.cs index bb6359f48..0b500b090 100644 --- a/PKHeX.Core/Legality/Structures/IFixedAbilityNumber.cs +++ b/PKHeX.Core/Legality/Structures/IFixedAbilityNumber.cs @@ -1,7 +1,6 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IFixedAbilityNumber { - public interface IFixedAbilityNumber - { - AbilityPermission Ability { get; } - } + AbilityPermission Ability { get; } } diff --git a/PKHeX.Core/Legality/Structures/IFixedBall.cs b/PKHeX.Core/Legality/Structures/IFixedBall.cs index ac6c0fea4..8acd4dfad 100644 --- a/PKHeX.Core/Legality/Structures/IFixedBall.cs +++ b/PKHeX.Core/Legality/Structures/IFixedBall.cs @@ -1,7 +1,6 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IFixedBall { - public interface IFixedBall - { - Ball FixedBall { get; } - } + Ball FixedBall { get; } } diff --git a/PKHeX.Core/Legality/Structures/ILocation.cs b/PKHeX.Core/Legality/Structures/ILocation.cs index 610e9ea96..58dfa4610 100644 --- a/PKHeX.Core/Legality/Structures/ILocation.cs +++ b/PKHeX.Core/Legality/Structures/ILocation.cs @@ -1,28 +1,27 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface ILocation { - public interface ILocation + int Location { get; } + int EggLocation { get; } +} + +public static partial class Extensions +{ + public static int GetLocation(this ILocation encounter) { - int Location { get; } - int EggLocation { get; } + return encounter.Location != 0 + ? encounter.Location + : encounter.EggLocation; } - public static partial class Extensions + public static string? GetEncounterLocation(this ILocation Encounter, int gen, int version = -1) { - public static int GetLocation(this ILocation encounter) - { - return encounter.Location != 0 - ? encounter.Location - : encounter.EggLocation; - } + int loc = Encounter.GetLocation(); + if (loc < 0) + return null; - public static string? GetEncounterLocation(this ILocation Encounter, int gen, int version = -1) - { - int loc = Encounter.GetLocation(); - if (loc < 0) - return null; - - bool egg = loc != Encounter.Location; - return GameInfo.GetLocationName(egg, loc, gen, gen, (GameVersion)version); - } + bool egg = loc != Encounter.Location; + return GameInfo.GetLocationName(egg, loc, gen, gen, (GameVersion)version); } } diff --git a/PKHeX.Core/Legality/Structures/IMoveset.cs b/PKHeX.Core/Legality/Structures/IMoveset.cs index 521126a97..cb753301c 100644 --- a/PKHeX.Core/Legality/Structures/IMoveset.cs +++ b/PKHeX.Core/Legality/Structures/IMoveset.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface that exposes a Moveset for the object. +/// +public interface IMoveset { - /// - /// Interface that exposes a Moveset for the object. - /// - public interface IMoveset - { - IReadOnlyList Moves { get; } - } + IReadOnlyList Moves { get; } } diff --git a/PKHeX.Core/Legality/Structures/IRelearn.cs b/PKHeX.Core/Legality/Structures/IRelearn.cs index b55dc81be..dee1b0354 100644 --- a/PKHeX.Core/Legality/Structures/IRelearn.cs +++ b/PKHeX.Core/Legality/Structures/IRelearn.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IRelearn { - public interface IRelearn - { - IReadOnlyList Relearn { get; } - } -} \ No newline at end of file + IReadOnlyList Relearn { get; } +} diff --git a/PKHeX.Core/Legality/Structures/ITrainerInfo.cs b/PKHeX.Core/Legality/Structures/ITrainerInfo.cs index 184c7da42..d8ed51b98 100644 --- a/PKHeX.Core/Legality/Structures/ITrainerInfo.cs +++ b/PKHeX.Core/Legality/Structures/ITrainerInfo.cs @@ -1,92 +1,91 @@ -namespace PKHeX.Core -{ - /// - /// Minimal Trainer Information necessary for generating a . - /// - public interface ITrainerInfo : ITrainerID - { - string OT { get; } - int Gender { get; } - int Game { get; } - int Language { get; } +namespace PKHeX.Core; - int Generation { get; } - EntityContext Context { get; } +/// +/// Minimal Trainer Information necessary for generating a . +/// +public interface ITrainerInfo : ITrainerID +{ + string OT { get; } + int Gender { get; } + int Game { get; } + int Language { get; } + + int Generation { get; } + EntityContext Context { get; } +} + +public static class TrainerInfoExtensions +{ + public static void ApplyTo(this ITrainerInfo info, PKM pk) + { + pk.OT_Name = info.OT; + pk.TID = info.TID; + pk.SID = pk.Format < 3 || pk.VC ? 0 : info.SID; + pk.OT_Gender = info.Gender; + pk.Language = info.Language; + pk.Version = info.Game; + + if (pk is not IRegionOrigin tr) + return; + + if (info is not IRegionOrigin o) + return; + tr.Country = o.Country; + tr.Region = o.Region; + tr.ConsoleRegion = o.ConsoleRegion; } - public static class TrainerInfoExtensions + public static void ApplyHandlingTrainerInfo(this ITrainerInfo sav, PKM pk, bool force = false) { - public static void ApplyTo(this ITrainerInfo info, PKM pk) + if (pk.Format == sav.Generation && !force) + return; + + pk.HT_Name = sav.OT; + pk.HT_Gender = sav.Gender; + pk.HT_Friendship = pk.OT_Friendship; + pk.CurrentHandler = 1; + + if (pk is PK6 pk6 && sav is IRegionOrigin o) { - pk.OT_Name = info.OT; - pk.TID = info.TID; - pk.SID = pk.Format < 3 || pk.VC ? 0 : info.SID; - pk.OT_Gender = info.Gender; - pk.Language = info.Language; - pk.Version = info.Game; - - if (pk is not IRegionOrigin tr) - return; - - if (info is not IRegionOrigin o) - return; - tr.Country = o.Country; - tr.Region = o.Region; - tr.ConsoleRegion = o.ConsoleRegion; + pk6.Geo1_Country = o.Country; + pk6.Geo1_Region = o.Region; + pk6.SetTradeMemoryHT6(true); } - - public static void ApplyHandlingTrainerInfo(this ITrainerInfo sav, PKM pk, bool force = false) + else if (pk is PK8 pk8) { - if (pk.Format == sav.Generation && !force) - return; - - pk.HT_Name = sav.OT; - pk.HT_Gender = sav.Gender; - pk.HT_Friendship = pk.OT_Friendship; - pk.CurrentHandler = 1; - - if (pk is PK6 pk6 && sav is IRegionOrigin o) - { - pk6.Geo1_Country = o.Country; - pk6.Geo1_Region = o.Region; - pk6.SetTradeMemoryHT6(true); - } - else if (pk is PK8 pk8) - { - pk8.SetTradeMemoryHT8(); - } + pk8.SetTradeMemoryHT8(); } + } - public static bool IsFromTrainer(this ITrainerInfo tr, PKM pk) - { - if (tr.Game == (int)GameVersion.Any) - return true; + public static bool IsFromTrainer(this ITrainerInfo tr, PKM pk) + { + if (tr.Game == (int)GameVersion.Any) + return true; - if (tr.TID != pk.TID) - return false; - if (tr.OT != pk.OT_Name) - return false; - if (pk.Format <= 2) - return false; - - if (tr.SID != pk.SID) - return false; - if (pk.Format == 3) - return false; - - if (tr.Gender != pk.OT_Gender) - return false; - - return IsMatchVersion(tr, pk); - } - - private static bool IsMatchVersion(ITrainerInfo tr, PKM pk) - { - if (tr.Game == pk.Version) - return true; - if (pk.GO_LGPE) - return tr.Game is (int)GameVersion.GP or (int)GameVersion.GE; + if (tr.TID != pk.TID) return false; - } + if (tr.OT != pk.OT_Name) + return false; + if (pk.Format <= 2) + return false; + + if (tr.SID != pk.SID) + return false; + if (pk.Format == 3) + return false; + + if (tr.Gender != pk.OT_Gender) + return false; + + return IsMatchVersion(tr, pk); + } + + private static bool IsMatchVersion(ITrainerInfo tr, PKM pk) + { + if (tr.Game == pk.Version) + return true; + if (pk.GO_LGPE) + return tr.Game is (int)GameVersion.GP or (int)GameVersion.GE; + return false; } } diff --git a/PKHeX.Core/Legality/Structures/IVersion.cs b/PKHeX.Core/Legality/Structures/IVersion.cs index 03021cb25..1913b0b0c 100644 --- a/PKHeX.Core/Legality/Structures/IVersion.cs +++ b/PKHeX.Core/Legality/Structures/IVersion.cs @@ -1,41 +1,40 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface that exposes a to see which version the data originated in. +/// +public interface IVersion { /// - /// Interface that exposes a to see which version the data originated in. + /// The version the data originated in. /// - public interface IVersion + GameVersion Version { get; } +} + +public static partial class Extensions +{ + private static bool CanBeReceivedBy(this IVersion ver, GameVersion game) => ver.Version.Contains(game); + + public static GameVersion GetCompatibleVersion(this IVersion ver, GameVersion prefer) { - /// - /// The version the data originated in. - /// - GameVersion Version { get; } + if (ver.CanBeReceivedBy(prefer) || ver.Version <= GameVersion.Unknown) + return prefer; + return ver.GetSingleVersion(); } - public static partial class Extensions + private static GameVersion GetSingleVersion(this IVersion ver) { - private static bool CanBeReceivedBy(this IVersion ver, GameVersion game) => ver.Version.Contains(game); - - public static GameVersion GetCompatibleVersion(this IVersion ver, GameVersion prefer) + const int max = (int)GameUtil.HighestGameID; + if ((int)ver.Version <= max) + return ver.Version; + var rnd = Util.Rand; + while (true) // this isn't optimal, but is low maintenance { - if (ver.CanBeReceivedBy(prefer) || ver.Version <= GameVersion.Unknown) - return prefer; - return ver.GetSingleVersion(); - } - - private static GameVersion GetSingleVersion(this IVersion ver) - { - const int max = (int)GameUtil.HighestGameID; - if ((int)ver.Version <= max) - return ver.Version; - var rnd = Util.Rand; - while (true) // this isn't optimal, but is low maintenance - { - var game = (GameVersion)rnd.Next(1, max); - if (game == GameVersion.BU) - continue; // Ignore this one; only can be Japanese language. - if (ver.CanBeReceivedBy(game)) - return game; - } + var game = (GameVersion)rnd.Next(1, max); + if (game == GameVersion.BU) + continue; // Ignore this one; only can be Japanese language. + if (ver.CanBeReceivedBy(game)) + return game; } } } diff --git a/PKHeX.Core/Legality/Structures/LegalInfo.cs b/PKHeX.Core/Legality/Structures/LegalInfo.cs index 981a236e5..1d4212515 100644 --- a/PKHeX.Core/Legality/Structures/LegalInfo.cs +++ b/PKHeX.Core/Legality/Structures/LegalInfo.cs @@ -1,97 +1,96 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Calculated Information storage with properties useful for parsing the legality of the input . +/// +public sealed class LegalInfo : IGeneration { - /// - /// Calculated Information storage with properties useful for parsing the legality of the input . - /// - public sealed class LegalInfo : IGeneration + /// The object used for comparisons. + private readonly PKM Entity; + + /// The generation of games the originated from. + public int Generation { get; private set; } + + /// The matched Encounter details for the . + public IEncounterable EncounterMatch { - /// The object used for comparisons. - private readonly PKM pkm; - - /// The generation of games the originated from. - public int Generation { get; private set; } - - /// The matched Encounter details for the . - public IEncounterable EncounterMatch + get => _match; + set { - get => _match; - set - { - if (!ReferenceEquals(_match, EncounterInvalid.Default) && (value.LevelMin != _match.LevelMin || value.Species != _match.Species)) - _evochains = null; // clear if evo chain has the potential to be different - _match = value; - Parse.Clear(); - } - } - - /// - /// Original encounter data for the . - /// - /// - /// Generation 1/2 that are transferred forward to Generation 7 are restricted to new encounter details. - /// By retaining their original match, more information can be provided by the parse. - /// - public IEncounterable EncounterOriginal => EncounterOriginalGB ?? EncounterMatch; - - internal IEncounterable? EncounterOriginalGB; - private IEncounterable _match = EncounterInvalid.Default; - - /// Top level Legality Check result list for the . - internal readonly List Parse; - - private const int MoveCount = 4; - public readonly CheckMoveResult[] Relearn = GetArray(); - public readonly CheckMoveResult[] Moves = GetArray(); - - private static CheckMoveResult[] GetArray() - { - var result = new CheckMoveResult[MoveCount]; - for (int i = 0; i < result.Length; i++) - result[i] = new CheckMoveResult(); - return result; - } - - private static readonly ValidEncounterMoves NONE = new(); - public ValidEncounterMoves EncounterMoves { get; internal set; } = NONE; - public EvolutionHistory EvoChainsAllGens => _evochains ??= EvolutionChain.GetEvolutionChainsAllGens(pkm, EncounterMatch); - private EvolutionHistory? _evochains; - - /// related information that generated the / value(s). - public PIDIV PIDIV - { - get => _pidiv; - internal set - { - _pidiv = value; - PIDParsed = true; - } - } - - public bool PIDParsed { get; private set; } - private PIDIV _pidiv = PIDIV.None; - - /// Indicates whether or not the can originate from the . - /// This boolean is true until all valid encounters are tested, after which it is false. - public bool PIDIVMatches { get; internal set; } = true; - - /// Indicates whether or not the can originate from the with explicit matching. - /// This boolean is true until all valid entries are tested for all possible matches, after which it is false. - public bool FrameMatches { get; internal set; } = true; - - public LegalInfo(PKM pk, List parse) - { - pkm = pk; - Parse = parse; - StoreMetadata(pkm.Generation); - } - - internal void StoreMetadata(int gen) - { - // We can call this method at the start for any Gen3+ encounter iteration. - // We need to call this for each Gen1/2 encounter as Version is not stored for those origins. - Generation = gen; + if (!ReferenceEquals(_match, EncounterInvalid.Default) && (value.LevelMin != _match.LevelMin || value.Species != _match.Species)) + _evochains = null; // clear if evo chain has the potential to be different + _match = value; + Parse.Clear(); } } + + /// + /// Original encounter data for the . + /// + /// + /// Generation 1/2 that are transferred forward to Generation 7 are restricted to new encounter details. + /// By retaining their original match, more information can be provided by the parse. + /// + public IEncounterable EncounterOriginal => EncounterOriginalGB ?? EncounterMatch; + + internal IEncounterable? EncounterOriginalGB; + private IEncounterable _match = EncounterInvalid.Default; + + /// Top level Legality Check result list for the . + internal readonly List Parse; + + private const int MoveCount = 4; + public readonly CheckMoveResult[] Relearn = GetArray(); + public readonly CheckMoveResult[] Moves = GetArray(); + + private static CheckMoveResult[] GetArray() + { + var result = new CheckMoveResult[MoveCount]; + for (int i = 0; i < result.Length; i++) + result[i] = new CheckMoveResult(); + return result; + } + + private static readonly ValidEncounterMoves NONE = new(); + public ValidEncounterMoves EncounterMoves { get; internal set; } = NONE; + public EvolutionHistory EvoChainsAllGens => _evochains ??= EvolutionChain.GetEvolutionChainsAllGens(Entity, EncounterMatch); + private EvolutionHistory? _evochains; + + /// related information that generated the / value(s). + public PIDIV PIDIV + { + get => _pidiv; + internal set + { + _pidiv = value; + PIDParsed = true; + } + } + + public bool PIDParsed { get; private set; } + private PIDIV _pidiv = PIDIV.None; + + /// Indicates whether or not the can originate from the . + /// This boolean is true until all valid encounters are tested, after which it is false. + public bool PIDIVMatches { get; internal set; } = true; + + /// Indicates whether or not the can originate from the with explicit matching. + /// This boolean is true until all valid entries are tested for all possible matches, after which it is false. + public bool FrameMatches { get; internal set; } = true; + + public LegalInfo(PKM pk, List parse) + { + Entity = pk; + Parse = parse; + StoreMetadata(pk.Generation); + } + + internal void StoreMetadata(int gen) + { + // We can call this method at the start for any Gen3+ encounter iteration. + // We need to call this for each Gen1/2 encounter as Version is not stored for those origins. + Generation = gen; + } } diff --git a/PKHeX.Core/Legality/Structures/MoveSource.cs b/PKHeX.Core/Legality/Structures/MoveSource.cs index 4fd6e76c9..6c90fcdc8 100644 --- a/PKHeX.Core/Legality/Structures/MoveSource.cs +++ b/PKHeX.Core/Legality/Structures/MoveSource.cs @@ -1,24 +1,23 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Indicates the method of learning a move +/// +public enum MoveSource : byte { - /// - /// Indicates the method of learning a move - /// - public enum MoveSource : byte - { - NotParsed, - Unknown, - None, - Relearn, - Initial, - LevelUp, - TMHM, - Tutor, - EggMove, - InheritLevelUp, - Special, - SpecialEgg, - ShedinjaEvo, - Sketch, - Shared, - } + NotParsed, + Unknown, + None, + Relearn, + Initial, + LevelUp, + TMHM, + Tutor, + EggMove, + InheritLevelUp, + Special, + SpecialEgg, + ShedinjaEvo, + Sketch, + Shared, } diff --git a/PKHeX.Core/Legality/Structures/Shiny.cs b/PKHeX.Core/Legality/Structures/Shiny.cs index 3d2cde212..4668cb41a 100644 --- a/PKHeX.Core/Legality/Structures/Shiny.cs +++ b/PKHeX.Core/Legality/Structures/Shiny.cs @@ -26,12 +26,12 @@ public enum Shiny : byte public static class ShinyExtensions { - public static bool IsValid(this Shiny s, PKM pkm) => s switch + public static bool IsValid(this Shiny s, PKM pk) => s switch { - Shiny.Always => pkm.IsShiny, - Shiny.Never => !pkm.IsShiny, - Shiny.AlwaysSquare => pkm.ShinyXor == 0, - Shiny.AlwaysStar => pkm.ShinyXor == 1, + Shiny.Always => pk.IsShiny, + Shiny.Never => !pk.IsShiny, + Shiny.AlwaysSquare => pk.ShinyXor == 0, + Shiny.AlwaysStar => pk.ShinyXor == 1, _ => true, }; diff --git a/PKHeX.Core/Legality/Structures/SimpleTrainerInfo.cs b/PKHeX.Core/Legality/Structures/SimpleTrainerInfo.cs index e569db5f4..cb5fbd6d2 100644 --- a/PKHeX.Core/Legality/Structures/SimpleTrainerInfo.cs +++ b/PKHeX.Core/Legality/Structures/SimpleTrainerInfo.cs @@ -1,47 +1,46 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed record SimpleTrainerInfo : ITrainerInfo, IRegionOrigin { - public sealed record SimpleTrainerInfo : ITrainerInfo, IRegionOrigin + public string OT { get; set; } = "PKHeX"; + public int TID { get; set; } = 12345; + public int SID { get; set; } = 54321; + public int Gender { get; set; } + public int Language { get; set; } = (int)LanguageID.English; + + // IRegionOrigin for generation 6/7 + public byte ConsoleRegion { get; set; } = 1; // North America + public byte Region { get; set; } = 7; // California + public byte Country { get; set; } = 49; // USA + + public int Game { get; } + public int Generation { get; set; } = PKX.Generation; + public EntityContext Context { get; set; } = PKX.Context; + + public SimpleTrainerInfo(GameVersion game = GameVersion.SW) { - public string OT { get; set; } = "PKHeX"; - public int TID { get; set; } = 12345; - public int SID { get; set; } = 54321; - public int Gender { get; set; } - public int Language { get; set; } = (int)LanguageID.English; + Game = (int) game; + SanityCheckRegionOrigin(game); + } - // IRegionOrigin for generation 6/7 - public byte ConsoleRegion { get; set; } = 1; // North America - public byte Region { get; set; } = 7; // California - public byte Country { get; set; } = 49; // USA + private void SanityCheckRegionOrigin(GameVersion game) + { + if (GameVersion.Gen7b.Contains(game) || game.GetGeneration() >= 8) + this.ClearRegionOrigin(); + } - public int Game { get; } - public int Generation { get; set; } = PKX.Generation; - public EntityContext Context { get; set; } = PKX.Context; + public SimpleTrainerInfo(ITrainerInfo other) : this((GameVersion)other.Game) + { + OT = other.OT; + TID = other.TID; + SID = other.SID; + Gender = other.Gender; + Language = other.Language; + Generation = other.Generation; - public SimpleTrainerInfo(GameVersion game = GameVersion.SW) - { - Game = (int) game; - SanityCheckRegionOrigin(game); - } + if (other is IRegionOrigin r) + r.CopyRegionOrigin(this); - private void SanityCheckRegionOrigin(GameVersion game) - { - if (GameVersion.Gen7b.Contains(game) || game.GetGeneration() >= 8) - this.ClearRegionOrigin(); - } - - public SimpleTrainerInfo(ITrainerInfo other) : this((GameVersion)other.Game) - { - OT = other.OT; - TID = other.TID; - SID = other.SID; - Gender = other.Gender; - Language = other.Language; - Generation = other.Generation; - - if (other is IRegionOrigin r) - r.CopyRegionOrigin(this); - - SanityCheckRegionOrigin((GameVersion)Game); - } + SanityCheckRegionOrigin((GameVersion)Game); } } diff --git a/PKHeX.Core/Legality/Tables/FormInfo.cs b/PKHeX.Core/Legality/Tables/FormInfo.cs index af65435d9..b0f895e54 100644 --- a/PKHeX.Core/Legality/Tables/FormInfo.cs +++ b/PKHeX.Core/Legality/Tables/FormInfo.cs @@ -1,304 +1,303 @@ using System.Collections.Generic; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains logic for Alternate Form information. +/// +public static class FormInfo { /// - /// Contains logic for Alternate Form information. + /// Checks if the form cannot exist outside of a Battle. /// - public static class FormInfo + /// Entity species + /// Entity form + /// Current generation format + /// True if it can only exist in a battle, false if it can exist outside of battle. + public static bool IsBattleOnlyForm(int species, int form, int format) { - /// - /// Checks if the form cannot exist outside of a Battle. - /// - /// Entity species - /// Entity form - /// Current generation format - /// True if it can only exist in a battle, false if it can exist outside of battle. - public static bool IsBattleOnlyForm(int species, int form, int format) - { - if (!BattleOnly.Contains(species)) - return false; - - // Some species have battle only forms as well as out-of-battle forms (other than base form). - switch (species) - { - case (int)Slowbro when form == 2 && format >= 8: // this one is OK, Galarian Slowbro (not a Mega) - case (int)Darmanitan when form == 2 && format >= 8: // this one is OK, Galarian non-Zen - case (int)Zygarde when form < 4: // Zygarde Complete - case (int)Mimikyu when form == 2: // Totem disguise Mimikyu - case (int)Necrozma when form < 3: // Only mark Ultra Necrozma as Battle Only - return false; - case (int)Minior: return form < 7; // Minior Shields-Down - - default: - return form != 0; - } - } - - /// - /// Reverts the Battle Form to the form it would have outside of Battle. - /// - /// Only call this if you've already checked that returns true. - /// Entity species - /// Entity form - /// Current generation format - /// Suggested alt form value. - public static int GetOutOfBattleForm(int species, int form, int format) => species switch - { - (int)Darmanitan => form & 2, - (int)Zygarde when format > 6 => 3, - (int)Minior => form + 7, - _ => 0, - }; - - /// - /// Checks if the is a fused form, which indicates it cannot be traded away. - /// - /// Entity species - /// Entity form - /// Current generation format - /// True if it is a fused species-form, false if it is not fused. - public static bool IsFusedForm(int species, int form, int format) => species switch - { - (int)Kyurem when form != 0 && format >= 5 => true, - (int)Necrozma when form != 0 && format >= 7 => true, - (int)Calyrex when form != 0 && format >= 8 => true, - _ => false, - }; - - /// Checks if the form may be different than the original encounter detail. - /// Original species - /// Original form - /// Current form - /// Current format - public static bool IsFormChangeable(int species, int oldForm, int newForm, int format) - { - if (FormChange.Contains(species)) - return true; - - // Zygarde Form Changing - // Gen6: Introduced; no form changing. - // Gen7: Form changing introduced; can only change to Form 2/3 (Power Construct), never to 0/1 (Aura Break). A form-1 can be boosted to form-0. - // Gen8: Form changing improved; can pick any Form & Ability combination. - if (species == (int)Zygarde) - { - return format switch - { - 6 => false, - 7 => newForm >= 2 || (oldForm == 1 && newForm == 0), - _ => true, - }; - } + if (!BattleOnly.Contains(species)) return false; - } - /// - /// Species that can change between their forms, regardless of origin. - /// - /// Excludes Zygarde as it has special conditions. Check separately. - private static readonly HashSet FormChange = new() + // Some species have battle only forms as well as out-of-battle forms (other than base form). + switch (species) { - // Sometimes considered for wild encounters - (int)Burmy, - (int)Rotom, - (int)Furfrou, - (int)Oricorio, - - (int)Deoxys, - (int)Dialga, - (int)Palkia, - (int)Giratina, - (int)Shaymin, - (int)Arceus, - (int)Tornadus, - (int)Thundurus, - (int)Landorus, - (int)Kyurem, - (int)Keldeo, - (int)Genesect, - (int)Hoopa, - (int)Silvally, - (int)Necrozma, - (int)Calyrex, - (int)Enamorus, - }; - - /// - /// Species that have an alternate form that cannot exist outside of battle. - /// - private static readonly HashSet BattleForms = new() - { - (int)Castform, - (int)Cherrim, - (int)Darmanitan, - (int)Meloetta, - (int)Aegislash, - (int)Xerneas, - (int)Zygarde, - - (int)Wishiwashi, - (int)Minior, - (int)Mimikyu, - - (int)Cramorant, - (int)Morpeko, - (int)Eiscue, - - (int)Zacian, - (int)Zamazenta, - (int)Eternatus, - }; - - /// - /// Species that have a mega form that cannot exist outside of battle. - /// - /// Using a held item to change form during battle, via an in-battle transformation feature. - private static readonly HashSet BattleMegas = new() - { - // XY - (int)Venusaur, (int)Charizard, (int)Blastoise, - (int)Alakazam, (int)Gengar, (int)Kangaskhan, (int)Pinsir, - (int)Gyarados, (int)Aerodactyl, (int)Mewtwo, - - (int)Ampharos, (int)Scizor, (int)Heracross, (int)Houndoom, (int)Tyranitar, - - (int)Blaziken, (int)Gardevoir, (int)Mawile, (int)Aggron, (int)Medicham, - (int)Manectric, (int)Banette, (int)Absol, (int)Latios, (int)Latias, - - (int)Garchomp, (int)Lucario, (int)Abomasnow, - - // AO - (int)Beedrill, (int)Pidgeot, (int)Slowbro, - - (int)Steelix, - - (int)Sceptile, (int)Swampert, (int)Sableye, (int)Sharpedo, (int)Camerupt, - (int)Altaria, (int)Glalie, (int)Salamence, (int)Metagross, (int)Rayquaza, - - (int)Lopunny, (int)Gallade, - (int)Audino, (int)Diancie, - - // USUM - (int)Necrozma, // Ultra Necrozma - }; - - /// - /// Species that have a primal form that cannot exist outside of battle. - /// - private static readonly HashSet BattlePrimals = new() { (int)Kyogre, (int)Groudon }; - - private static readonly HashSet BattleOnly = GetBattleFormSet(); - - private static HashSet GetBattleFormSet() - { - var hs = new HashSet(BattleForms); - hs.UnionWith(BattleMegas); - hs.UnionWith(BattlePrimals); - return hs; - } - - /// - /// Checks if the for the is a Totem form. - /// - /// Entity species - /// Entity form - /// Current generation format - public static bool IsTotemForm(int species, int form, int format) => format == 7 && IsTotemForm(species, form); - - /// - /// Checks if the for the is a Totem form. - /// - /// Use if you aren't 100% sure the format is 7. - /// Entity species - /// Entity form - public static bool IsTotemForm(int species, int form) - { - if (form == 0) + case (int)Slowbro when form == 2 && format >= 8: // this one is OK, Galarian Slowbro (not a Mega) + case (int)Darmanitan when form == 2 && format >= 8: // this one is OK, Galarian non-Zen + case (int)Zygarde when form < 4: // Zygarde Complete + case (int)Mimikyu when form == 2: // Totem disguise Mimikyu + case (int)Necrozma when form < 3: // Only mark Ultra Necrozma as Battle Only return false; - if (!Legal.Totem_USUM.Contains(species)) - return false; - if (species == (int)Mimikyu) - return form is 2 or 3; - if (Legal.Totem_Alolan.Contains(species)) - return form == 2; - return form == 1; + case (int)Minior: return form < 7; // Minior Shields-Down + + default: + return form != 0; } - - /// - /// Gets the base for the when the Totem form is reverted (on transfer). - /// - /// Entity species - /// Entity form - public static int GetTotemBaseForm(int species, int form) - { - if (species == (int)Mimikyu) - return form - 2; - return form - 1; - } - - public static bool IsLordForm(int species, int form, int generation) - { - if (generation != 8) - return false; - return IsLordForm(species, form); - } - - private static bool IsLordForm(int species, int form) => form != 0 && species switch - { - (int)Arcanine when form == 2 => true, - (int)Electrode when form == 2 => true, - (int)Lilligant when form == 2 => true, - (int)Avalugg when form == 2 => true, - (int)Kleavor when form == 1 => true, - _ => false, - }; - - /// - /// Checks if the exists for the without having an associated index. - /// - /// Entity species - /// Entity form - /// Current generation format - /// - public static bool IsValidOutOfBoundsForm(int species, int form, int format) => (Species) species switch - { - Unown => form < (format == 2 ? 26 : 28), // A-Z : A-Z?! - Mothim => form < 3, // Burmy base form is kept - - Scatterbug => form <= Vivillon3DS.MaxWildFormID, // Vivillon Pre-evolutions - Spewpa => form <= Vivillon3DS.MaxWildFormID, // Vivillon Pre-evolutions - - _ => false, - }; - - /// - /// Checks if the data should have a drop-down selection visible for the value. - /// - /// Game specific personal info - /// ID - /// ID - /// True if has forms that can be provided by , otherwise false for none. - public static bool HasFormSelection(PersonalInfo pi, int species, int format) - { - if (format <= 3 && species != (int)Unown) - return false; - - if (HasFormValuesNotIndicatedByPersonal.Contains(species)) - return true; - - int count = pi.FormCount; - return count > 1; - } - - /// - /// - /// - private static readonly HashSet HasFormValuesNotIndicatedByPersonal = new() - { - (int)Unown, - (int)Mothim, // (Burmy form is not cleared on evolution) - (int)Scatterbug, (int)Spewpa, // Vivillon pre-evos - }; } + + /// + /// Reverts the Battle Form to the form it would have outside of Battle. + /// + /// Only call this if you've already checked that returns true. + /// Entity species + /// Entity form + /// Current generation format + /// Suggested alt form value. + public static int GetOutOfBattleForm(int species, int form, int format) => species switch + { + (int)Darmanitan => form & 2, + (int)Zygarde when format > 6 => 3, + (int)Minior => form + 7, + _ => 0, + }; + + /// + /// Checks if the is a fused form, which indicates it cannot be traded away. + /// + /// Entity species + /// Entity form + /// Current generation format + /// True if it is a fused species-form, false if it is not fused. + public static bool IsFusedForm(int species, int form, int format) => species switch + { + (int)Kyurem when form != 0 && format >= 5 => true, + (int)Necrozma when form != 0 && format >= 7 => true, + (int)Calyrex when form != 0 && format >= 8 => true, + _ => false, + }; + + /// Checks if the form may be different than the original encounter detail. + /// Original species + /// Original form + /// Current form + /// Current format + public static bool IsFormChangeable(int species, int oldForm, int newForm, int format) + { + if (FormChange.Contains(species)) + return true; + + // Zygarde Form Changing + // Gen6: Introduced; no form changing. + // Gen7: Form changing introduced; can only change to Form 2/3 (Power Construct), never to 0/1 (Aura Break). A form-1 can be boosted to form-0. + // Gen8: Form changing improved; can pick any Form & Ability combination. + if (species == (int)Zygarde) + { + return format switch + { + 6 => false, + 7 => newForm >= 2 || (oldForm == 1 && newForm == 0), + _ => true, + }; + } + return false; + } + + /// + /// Species that can change between their forms, regardless of origin. + /// + /// Excludes Zygarde as it has special conditions. Check separately. + private static readonly HashSet FormChange = new() + { + // Sometimes considered for wild encounters + (int)Burmy, + (int)Rotom, + (int)Furfrou, + (int)Oricorio, + + (int)Deoxys, + (int)Dialga, + (int)Palkia, + (int)Giratina, + (int)Shaymin, + (int)Arceus, + (int)Tornadus, + (int)Thundurus, + (int)Landorus, + (int)Kyurem, + (int)Keldeo, + (int)Genesect, + (int)Hoopa, + (int)Silvally, + (int)Necrozma, + (int)Calyrex, + (int)Enamorus, + }; + + /// + /// Species that have an alternate form that cannot exist outside of battle. + /// + private static readonly HashSet BattleForms = new() + { + (int)Castform, + (int)Cherrim, + (int)Darmanitan, + (int)Meloetta, + (int)Aegislash, + (int)Xerneas, + (int)Zygarde, + + (int)Wishiwashi, + (int)Minior, + (int)Mimikyu, + + (int)Cramorant, + (int)Morpeko, + (int)Eiscue, + + (int)Zacian, + (int)Zamazenta, + (int)Eternatus, + }; + + /// + /// Species that have a mega form that cannot exist outside of battle. + /// + /// Using a held item to change form during battle, via an in-battle transformation feature. + private static readonly HashSet BattleMegas = new() + { + // XY + (int)Venusaur, (int)Charizard, (int)Blastoise, + (int)Alakazam, (int)Gengar, (int)Kangaskhan, (int)Pinsir, + (int)Gyarados, (int)Aerodactyl, (int)Mewtwo, + + (int)Ampharos, (int)Scizor, (int)Heracross, (int)Houndoom, (int)Tyranitar, + + (int)Blaziken, (int)Gardevoir, (int)Mawile, (int)Aggron, (int)Medicham, + (int)Manectric, (int)Banette, (int)Absol, (int)Latios, (int)Latias, + + (int)Garchomp, (int)Lucario, (int)Abomasnow, + + // AO + (int)Beedrill, (int)Pidgeot, (int)Slowbro, + + (int)Steelix, + + (int)Sceptile, (int)Swampert, (int)Sableye, (int)Sharpedo, (int)Camerupt, + (int)Altaria, (int)Glalie, (int)Salamence, (int)Metagross, (int)Rayquaza, + + (int)Lopunny, (int)Gallade, + (int)Audino, (int)Diancie, + + // USUM + (int)Necrozma, // Ultra Necrozma + }; + + /// + /// Species that have a primal form that cannot exist outside of battle. + /// + private static readonly HashSet BattlePrimals = new() { (int)Kyogre, (int)Groudon }; + + private static readonly HashSet BattleOnly = GetBattleFormSet(); + + private static HashSet GetBattleFormSet() + { + var hs = new HashSet(BattleForms); + hs.UnionWith(BattleMegas); + hs.UnionWith(BattlePrimals); + return hs; + } + + /// + /// Checks if the for the is a Totem form. + /// + /// Entity species + /// Entity form + /// Current generation format + public static bool IsTotemForm(int species, int form, int format) => format == 7 && IsTotemForm(species, form); + + /// + /// Checks if the for the is a Totem form. + /// + /// Use if you aren't 100% sure the format is 7. + /// Entity species + /// Entity form + public static bool IsTotemForm(int species, int form) + { + if (form == 0) + return false; + if (!Legal.Totem_USUM.Contains(species)) + return false; + if (species == (int)Mimikyu) + return form is 2 or 3; + if (Legal.Totem_Alolan.Contains(species)) + return form == 2; + return form == 1; + } + + /// + /// Gets the base for the when the Totem form is reverted (on transfer). + /// + /// Entity species + /// Entity form + public static int GetTotemBaseForm(int species, int form) + { + if (species == (int)Mimikyu) + return form - 2; + return form - 1; + } + + public static bool IsLordForm(int species, int form, int generation) + { + if (generation != 8) + return false; + return IsLordForm(species, form); + } + + private static bool IsLordForm(int species, int form) => form != 0 && species switch + { + (int)Arcanine when form == 2 => true, + (int)Electrode when form == 2 => true, + (int)Lilligant when form == 2 => true, + (int)Avalugg when form == 2 => true, + (int)Kleavor when form == 1 => true, + _ => false, + }; + + /// + /// Checks if the exists for the without having an associated index. + /// + /// Entity species + /// Entity form + /// Current generation format + /// + public static bool IsValidOutOfBoundsForm(int species, int form, int format) => (Species) species switch + { + Unown => form < (format == 2 ? 26 : 28), // A-Z : A-Z?! + Mothim => form < 3, // Burmy base form is kept + + Scatterbug => form <= Vivillon3DS.MaxWildFormID, // Vivillon Pre-evolutions + Spewpa => form <= Vivillon3DS.MaxWildFormID, // Vivillon Pre-evolutions + + _ => false, + }; + + /// + /// Checks if the data should have a drop-down selection visible for the value. + /// + /// Game specific personal info + /// ID + /// ID + /// True if has forms that can be provided by , otherwise false for none. + public static bool HasFormSelection(PersonalInfo pi, int species, int format) + { + if (format <= 3 && species != (int)Unown) + return false; + + if (HasFormValuesNotIndicatedByPersonal.Contains(species)) + return true; + + int count = pi.FormCount; + return count > 1; + } + + /// + /// + /// + private static readonly HashSet HasFormValuesNotIndicatedByPersonal = new() + { + (int)Unown, + (int)Mothim, // (Burmy form is not cleared on evolution) + (int)Scatterbug, (int)Spewpa, // Vivillon pre-evos + }; } diff --git a/PKHeX.Core/Legality/Tables/MystryMew.cs b/PKHeX.Core/Legality/Tables/MystryMew.cs index 2cf1a2d41..2d41cfb4b 100644 --- a/PKHeX.Core/Legality/Tables/MystryMew.cs +++ b/PKHeX.Core/Legality/Tables/MystryMew.cs @@ -1,72 +1,71 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for MYSTRY Mew origins. +/// +public static class MystryMew { - /// - /// Logic for MYSTRY Mew origins. - /// - public static class MystryMew + private static readonly ushort[] Seeds = { - private static readonly ushort[] Seeds = - { - 0x0652, 0x0932, 0x0C13, 0x0D43, 0x0EEE, - 0x1263, 0x13C9, 0x1614, 0x1C09, 0x1EA5, - 0x20BF, 0x2389, 0x2939, 0x302D, 0x306E, - 0x34F3, 0x45F3, 0x46CE, 0x4A0D, 0x4B63, + 0x0652, 0x0932, 0x0C13, 0x0D43, 0x0EEE, + 0x1263, 0x13C9, 0x1614, 0x1C09, 0x1EA5, + 0x20BF, 0x2389, 0x2939, 0x302D, 0x306E, + 0x34F3, 0x45F3, 0x46CE, 0x4A0D, 0x4B63, - 0x4C79, 0x508E, 0x50AB, 0x5240, 0x5327, - 0x56BA, 0x56CC, 0x5841, 0x5A60, 0x5BC1, - 0x5E2B, 0x5EF3, 0x6065, 0x643F, 0x6457, - 0x67A3, 0x6944, 0x6E06, 0x6E62, 0x7667, + 0x4C79, 0x508E, 0x50AB, 0x5240, 0x5327, + 0x56BA, 0x56CC, 0x5841, 0x5A60, 0x5BC1, + 0x5E2B, 0x5EF3, 0x6065, 0x643F, 0x6457, + 0x67A3, 0x6944, 0x6E06, 0x6E62, 0x7667, - 0x77EF, 0x78D2, 0x8655, 0x8A92, 0x8B48, - 0x93D0, 0x941D, 0x95A0, 0x967D, 0x9690, - 0x9C37, 0x9C40, 0x9D9C, 0x9DE4, 0x9E86, - 0xA153, 0xA443, 0xA8AC, 0xAC08, 0xAFFB, + 0x77EF, 0x78D2, 0x8655, 0x8A92, 0x8B48, + 0x93D0, 0x941D, 0x95A0, 0x967D, 0x9690, + 0x9C37, 0x9C40, 0x9D9C, 0x9DE4, 0x9E86, + 0xA153, 0xA443, 0xA8AC, 0xAC08, 0xAFFB, - 0xB1F2, 0xB831, 0xBE96, 0xC2D4, 0xC385, - 0xC6CE, 0xC92C, 0xC953, 0xC962, 0xCC43, - 0xCD47, 0xCD96, 0xD1E4, 0xDFED, 0xE62C, - 0xE6CC, 0xE90A, 0xE95D, 0xE991, 0xEBB2, + 0xB1F2, 0xB831, 0xBE96, 0xC2D4, 0xC385, + 0xC6CE, 0xC92C, 0xC953, 0xC962, 0xCC43, + 0xCD47, 0xCD96, 0xD1E4, 0xDFED, 0xE62C, + 0xE6CC, 0xE90A, 0xE95D, 0xE991, 0xEBB2, - 0xEE7F, 0xEE9F, 0xEFC8, 0xF0E4, 0xFE4E, - 0xFE9D, - }; + 0xEE7F, 0xEE9F, 0xEFC8, 0xF0E4, 0xFE4E, + 0xFE9D, + }; - private const int FramesPerMew = 5; - private const int MewPerRestrictedSeed = 5; - - /// - /// Gets a random valid seed based on the input value. - /// - public static uint GetSeed(uint random, PIDType type = PIDType.BACD_U) - { - uint restricted = random % (uint)Seeds.Length; - var seed = (uint)Seeds[restricted]; - if (type == PIDType.BACD_R) - return seed; - - uint position = (random % (MewPerRestrictedSeed - 1)) + 1; - for (int i = 0; i < position; i++) - seed = RNG.LCRNG.Advance(seed, FramesPerMew); + private const int FramesPerMew = 5; + private const int MewPerRestrictedSeed = 5; + /// + /// Gets a random valid seed based on the input value. + /// + public static uint GetSeed(uint random, PIDType type = PIDType.BACD_U) + { + uint restricted = random % (uint)Seeds.Length; + var seed = (uint)Seeds[restricted]; + if (type == PIDType.BACD_R) return seed; - } - /// - /// Checks if the seed is a known seed. - /// - /// Origin seed (for the PID/IV) - public static int GetSeedIndex(uint seed) + uint position = (random % (MewPerRestrictedSeed - 1)) + 1; + for (int i = 0; i < position; i++) + seed = RNG.LCRNG.Advance(seed, FramesPerMew); + + return seed; + } + + /// + /// Checks if the seed is a known seed. + /// + /// Origin seed (for the PID/IV) + public static int GetSeedIndex(uint seed) + { + for (int i = 0; i < 5; i++) { - for (int i = 0; i < 5; i++) - { - if (seed <= ushort.MaxValue) - return Array.BinarySearch(Seeds, (ushort)seed); - seed = RNG.LCRNG.Reverse(seed, FramesPerMew); - } - - return -1; + if (seed <= ushort.MaxValue) + return Array.BinarySearch(Seeds, (ushort)seed); + seed = RNG.LCRNG.Reverse(seed, FramesPerMew); } + + return -1; } } diff --git a/PKHeX.Core/Legality/Tables/Tables.cs b/PKHeX.Core/Legality/Tables/Tables.cs index fd5b51d82..6f22d516f 100644 --- a/PKHeX.Core/Legality/Tables/Tables.cs +++ b/PKHeX.Core/Legality/Tables/Tables.cs @@ -3,168 +3,167 @@ using static PKHeX.Core.Species; using static PKHeX.Core.Move; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + /// + /// Species that can change between their forms and get access to form-specific moves. + /// + public static readonly HashSet FormChangeMovesRetain = new() { - /// - /// Species that can change between their forms and get access to form-specific moves. - /// - public static readonly HashSet FormChangeMovesRetain = new() + (int)Deoxys, + (int)Giratina, + (int)Shaymin, + (int)Hoopa, + }; + + /// + /// Species that can change between their forms and get access to form-specific moves. + /// + public static readonly Dictionary> FormChangeMoves = new() + { + {(int)Deoxys, (g, _) => g >= 6}, + {(int)Giratina, (g, _) => g >= 6}, + {(int)Shaymin, (g, _) => g >= 6}, + {(int)Rotom, (g, _) => g >= 6}, + {(int)Hoopa, (g, _) => g >= 6}, + {(int)Tornadus, (g, _) => g >= 6}, + {(int)Thundurus,(g, _) => g >= 6}, + {(int)Landorus, (g, _) => g >= 6}, + {(int)Urshifu, (g, _) => g >= 8}, + {(int)Enamorus, (g, _) => g >= 8}, + + // Fused + {(int)Kyurem, (g, _) => g >= 6}, + {(int)Necrozma, (g, _) => g >= 8}, + {(int)Calyrex, (g, _) => g >= 8}, + + {(int)Pikachu, (g, f) => g == 6 && f != 0}, + }; + + /// + /// Generation 3 & 4 Battle Frontier Species banlist. When referencing this in context to generation 4, be sure to disallow with Form 1 (Spiky). + /// + public static readonly HashSet BattleFrontierBanlist = new() + { + (int)Mewtwo, (int)Mew, + (int)Lugia, (int)HoOh, (int)Celebi, + (int)Kyogre, (int)Groudon, (int)Rayquaza, (int)Jirachi, (int)Deoxys, + (int)Dialga, (int)Palkia, (int)Giratina, (int)Phione, (int)Manaphy, (int)Darkrai, (int)Shaymin, (int)Arceus, + (int)Victini, (int)Reshiram, (int)Zekrom, (int)Kyurem, (int)Keldeo, (int)Meloetta, (int)Genesect, + (int)Xerneas, (int)Yveltal, (int)Zygarde, (int)Diancie, (int)Hoopa, (int)Volcanion, + (int)Cosmog, (int)Cosmoem, (int)Solgaleo, (int)Lunala, (int)Necrozma, (int)Magearna, (int)Marshadow, (int)Zeraora, + (int)Meltan, (int)Melmetal, + }; + + /// + /// Checks if Sketch can obtain the in the requested + /// + /// Doesn't bounds check the for max move ID. + /// Move ID + /// Generation to check + /// True if can be sketched, false if not available. + public static bool IsValidSketch(int move, int generation) + { + if (MoveInfo.InvalidSketch.Contains(move)) + return false; + if (generation is 6 && move is ((int)ThousandArrows or (int)ThousandWaves)) + return false; + if (generation is 8) // can't Sketch unusable moves in BDSP, no Sketch in PLA { - (int)Deoxys, - (int)Giratina, - (int)Shaymin, - (int)Hoopa, - }; - - /// - /// Species that can change between their forms and get access to form-specific moves. - /// - public static readonly Dictionary> FormChangeMoves = new() - { - {(int)Deoxys, (g, _) => g >= 6}, - {(int)Giratina, (g, _) => g >= 6}, - {(int)Shaymin, (g, _) => g >= 6}, - {(int)Rotom, (g, _) => g >= 6}, - {(int)Hoopa, (g, _) => g >= 6}, - {(int)Tornadus, (g, _) => g >= 6}, - {(int)Thundurus,(g, _) => g >= 6}, - {(int)Landorus, (g, _) => g >= 6}, - {(int)Urshifu, (g, _) => g >= 8}, - {(int)Enamorus, (g, _) => g >= 8}, - - // Fused - {(int)Kyurem, (g, _) => g >= 6}, - {(int)Necrozma, (g, _) => g >= 8}, - {(int)Calyrex, (g, _) => g >= 8}, - - {(int)Pikachu, (g, f) => g == 6 && f != 0}, - }; - - /// - /// Generation 3 & 4 Battle Frontier Species banlist. When referencing this in context to generation 4, be sure to disallow with Form 1 (Spiky). - /// - public static readonly HashSet BattleFrontierBanlist = new() - { - (int)Mewtwo, (int)Mew, - (int)Lugia, (int)HoOh, (int)Celebi, - (int)Kyogre, (int)Groudon, (int)Rayquaza, (int)Jirachi, (int)Deoxys, - (int)Dialga, (int)Palkia, (int)Giratina, (int)Phione, (int)Manaphy, (int)Darkrai, (int)Shaymin, (int)Arceus, - (int)Victini, (int)Reshiram, (int)Zekrom, (int)Kyurem, (int)Keldeo, (int)Meloetta, (int)Genesect, - (int)Xerneas, (int)Yveltal, (int)Zygarde, (int)Diancie, (int)Hoopa, (int)Volcanion, - (int)Cosmog, (int)Cosmoem, (int)Solgaleo, (int)Lunala, (int)Necrozma, (int)Magearna, (int)Marshadow, (int)Zeraora, - (int)Meltan, (int)Melmetal, - }; - - /// - /// Checks if Sketch can obtain the in the requested - /// - /// Doesn't bounds check the for max move ID. - /// Move ID - /// Generation to check - /// True if can be sketched, false if not available. - public static bool IsValidSketch(int move, int generation) - { - if (MoveInfo.InvalidSketch.Contains(move)) + if (DummiedMoves_BDSP.Contains(move)) return false; - if (generation is 6 && move is ((int)ThousandArrows or (int)ThousandWaves)) + if (move > MaxMoveID_8) return false; - if (generation is 8) // can't Sketch unusable moves in BDSP, no Sketch in PLA - { - if (DummiedMoves_BDSP.Contains(move)) - return false; - if (move > MaxMoveID_8) - return false; - } - - return move <= GetMaxMoveID(generation); } - /// - /// Species that are from Mythical Distributions (disallowed species for competitive rulesets) - /// - public static readonly HashSet Mythicals = new() - { - (int)Mew, - (int)Celebi, - (int)Jirachi, (int)Deoxys, - (int)Phione, (int)Manaphy, (int)Darkrai, (int)Shaymin, (int)Arceus, - (int)Victini, (int)Keldeo, (int)Meloetta, (int)Genesect, - (int)Diancie, (int)Hoopa, (int)Volcanion, - (int)Magearna, (int)Marshadow, - (int)Zeraora, (int)Meltan, (int)Melmetal, - (int)Zarude, - }; + return move <= GetMaxMoveID(generation); + } - /// - /// Species classified as "Legend" by the game code. - /// - public static readonly HashSet Legends = new() - { - (int)Mewtwo, (int)Mew, - (int)Lugia, (int)HoOh, (int)Celebi, - (int)Kyogre, (int)Groudon, (int)Rayquaza, (int)Jirachi, (int)Deoxys, - (int)Dialga, (int)Palkia, (int)Giratina, (int)Phione, (int)Manaphy, (int)Darkrai, (int)Shaymin, (int)Arceus, - (int)Victini, (int)Reshiram, (int)Zekrom, (int)Kyurem, (int)Keldeo, (int)Meloetta, (int)Genesect, - (int)Xerneas, (int)Yveltal, (int)Zygarde, (int)Diancie, (int)Hoopa, (int)Volcanion, - (int)Cosmog, (int)Cosmoem, (int)Solgaleo, (int)Lunala, (int)Necrozma, (int)Magearna, (int)Marshadow, (int)Zeraora, - (int)Meltan, (int)Melmetal, - (int)Zacian, (int)Zamazenta, (int)Eternatus, (int)Zarude, (int)Calyrex, - }; + /// + /// Species that are from Mythical Distributions (disallowed species for competitive rulesets) + /// + public static readonly HashSet Mythicals = new() + { + (int)Mew, + (int)Celebi, + (int)Jirachi, (int)Deoxys, + (int)Phione, (int)Manaphy, (int)Darkrai, (int)Shaymin, (int)Arceus, + (int)Victini, (int)Keldeo, (int)Meloetta, (int)Genesect, + (int)Diancie, (int)Hoopa, (int)Volcanion, + (int)Magearna, (int)Marshadow, + (int)Zeraora, (int)Meltan, (int)Melmetal, + (int)Zarude, + }; - /// - /// Species classified as "SubLegend" by the game code. - /// - public static readonly HashSet SubLegends = new() - { - (int)Articuno, (int)Zapdos, (int)Moltres, - (int)Raikou, (int)Entei, (int)Suicune, - (int)Regirock, (int)Regice, (int)Registeel, (int)Latias, (int)Latios, - (int)Uxie, (int)Mesprit, (int)Azelf, (int)Heatran, (int)Regigigas, (int)Cresselia, - (int)Cobalion, (int)Terrakion, (int)Virizion, (int)Tornadus, (int)Thundurus, (int)Landorus, - (int)TypeNull, (int)Silvally, (int)TapuKoko, (int)TapuLele, (int)TapuBulu, (int)TapuFini, - (int)Nihilego, (int)Buzzwole, (int)Pheromosa, (int)Xurkitree, (int)Celesteela, (int)Kartana, (int)Guzzlord, - (int)Poipole, (int)Naganadel, (int)Stakataka, (int)Blacephalon, - (int)Kubfu, (int)Urshifu, (int)Regieleki, (int)Regidrago, (int)Glastrier, (int)Spectrier, (int)Enamorus, - }; + /// + /// Species classified as "Legend" by the game code. + /// + public static readonly HashSet Legends = new() + { + (int)Mewtwo, (int)Mew, + (int)Lugia, (int)HoOh, (int)Celebi, + (int)Kyogre, (int)Groudon, (int)Rayquaza, (int)Jirachi, (int)Deoxys, + (int)Dialga, (int)Palkia, (int)Giratina, (int)Phione, (int)Manaphy, (int)Darkrai, (int)Shaymin, (int)Arceus, + (int)Victini, (int)Reshiram, (int)Zekrom, (int)Kyurem, (int)Keldeo, (int)Meloetta, (int)Genesect, + (int)Xerneas, (int)Yveltal, (int)Zygarde, (int)Diancie, (int)Hoopa, (int)Volcanion, + (int)Cosmog, (int)Cosmoem, (int)Solgaleo, (int)Lunala, (int)Necrozma, (int)Magearna, (int)Marshadow, (int)Zeraora, + (int)Meltan, (int)Melmetal, + (int)Zacian, (int)Zamazenta, (int)Eternatus, (int)Zarude, (int)Calyrex, + }; - /// - /// Species that evolve from a Bi-Gendered species into a Single-Gender. - /// - public static readonly HashSet FixedGenderFromBiGender = new() - { - (int)Nincada, - (int)Shedinja, // (G) + /// + /// Species classified as "SubLegend" by the game code. + /// + public static readonly HashSet SubLegends = new() + { + (int)Articuno, (int)Zapdos, (int)Moltres, + (int)Raikou, (int)Entei, (int)Suicune, + (int)Regirock, (int)Regice, (int)Registeel, (int)Latias, (int)Latios, + (int)Uxie, (int)Mesprit, (int)Azelf, (int)Heatran, (int)Regigigas, (int)Cresselia, + (int)Cobalion, (int)Terrakion, (int)Virizion, (int)Tornadus, (int)Thundurus, (int)Landorus, + (int)TypeNull, (int)Silvally, (int)TapuKoko, (int)TapuLele, (int)TapuBulu, (int)TapuFini, + (int)Nihilego, (int)Buzzwole, (int)Pheromosa, (int)Xurkitree, (int)Celesteela, (int)Kartana, (int)Guzzlord, + (int)Poipole, (int)Naganadel, (int)Stakataka, (int)Blacephalon, + (int)Kubfu, (int)Urshifu, (int)Regieleki, (int)Regidrago, (int)Glastrier, (int)Spectrier, (int)Enamorus, + }; - (int)Burmy, - (int)Wormadam, //(F) - (int)Mothim, // (M) + /// + /// Species that evolve from a Bi-Gendered species into a Single-Gender. + /// + public static readonly HashSet FixedGenderFromBiGender = new() + { + (int)Nincada, + (int)Shedinja, // (G) - (int)Ralts, - (int)Gallade, // (M) + (int)Burmy, + (int)Wormadam, //(F) + (int)Mothim, // (M) - (int)Snorunt, - (int)Froslass, // (F) + (int)Ralts, + (int)Gallade, // (M) - (int)Espurr, - (int)Meowstic, // (M/F) form specific - }; + (int)Snorunt, + (int)Froslass, // (F) - private static bool[] GetPermitList(int max, IEnumerable held) - { - var result = new bool[max + 1]; - foreach (var item in held) - result[item] = true; - return result; - } + (int)Espurr, + (int)Meowstic, // (M/F) form specific + }; - private static bool[] GetPermitList(int max, IEnumerable held, IEnumerable unreleased) - { - var result = GetPermitList(max, held); - foreach (var u in unreleased) - result[u] = false; - return result; - } + private static bool[] GetPermitList(int max, IEnumerable held) + { + var result = new bool[max + 1]; + foreach (var item in held) + result[item] = true; + return result; + } + + private static bool[] GetPermitList(int max, IEnumerable held, IEnumerable unreleased) + { + var result = GetPermitList(max, held); + foreach (var u in unreleased) + result[u] = false; + return result; } } diff --git a/PKHeX.Core/Legality/Tables/Tables1.cs b/PKHeX.Core/Legality/Tables/Tables1.cs index 2276418fe..257479377 100644 --- a/PKHeX.Core/Legality/Tables/Tables1.cs +++ b/PKHeX.Core/Legality/Tables/Tables1.cs @@ -1,48 +1,47 @@ using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + internal const int MaxSpeciesID_1 = 151; + internal const int MaxMoveID_1 = 165; + internal const int MaxItemID_1 = 255; + internal const int MaxAbilityID_1 = 0; + + internal static readonly ushort[] Pouch_Items_RBY = { - internal const int MaxSpeciesID_1 = 151; - internal const int MaxMoveID_1 = 165; - internal const int MaxItemID_1 = 255; - internal const int MaxAbilityID_1 = 0; + 000,001,002,003,004,005,006, 010,011,012,013,014,015, + 016,017,018,019,020, 029,030,031, + 032,033,034,035,036,037,038,039,040,041,042,043, 045,046,047, + 048,049, 051,052,053,054,055,056,057,058, 060,061,062,063, + 064,065,066,067,068,069,070,071,072,073,074,075,076,077,078,079, + 080,081,082,083, - internal static readonly ushort[] Pouch_Items_RBY = - { - 000,001,002,003,004,005,006, 010,011,012,013,014,015, - 016,017,018,019,020, 029,030,031, - 032,033,034,035,036,037,038,039,040,041,042,043, 045,046,047, - 048,049, 051,052,053,054,055,056,057,058, 060,061,062,063, - 064,065,066,067,068,069,070,071,072,073,074,075,076,077,078,079, - 080,081,082,083, + // ... - // ... + 196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250, + }; - 196,197,198,199,200,201,202,203,204,205,206,207, - 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, - 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, - 240,241,242,243,244,245,246,247,248,249,250, - }; - - internal static bool TransferSpeciesDefaultAbilityGen1(int species) - { - System.Diagnostics.Debug.Assert((uint)species <= MaxSpeciesID_1); - return species is (int)Gastly or (int)Haunter or (int)Gengar - or (int)Koffing or (int)Weezing - or (int)Mew; - } - - internal static readonly int[] TMHM_RBY = - { - 005, 013, 014, 018, 025, 092, 032, 034, 036, 038, - 061, 055, 058, 059, 063, 006, 066, 068, 069, 099, - 072, 076, 082, 085, 087, 089, 090, 091, 094, 100, - 102, 104, 115, 117, 118, 120, 121, 126, 129, 130, - 135, 138, 143, 156, 086, 149, 153, 157, 161, 164, - - 015, 019, 057, 070, 148, - }; + internal static bool TransferSpeciesDefaultAbilityGen1(int species) + { + System.Diagnostics.Debug.Assert((uint)species <= MaxSpeciesID_1); + return species is (int)Gastly or (int)Haunter or (int)Gengar + or (int)Koffing or (int)Weezing + or (int)Mew; } + + internal static readonly int[] TMHM_RBY = + { + 005, 013, 014, 018, 025, 092, 032, 034, 036, 038, + 061, 055, 058, 059, 063, 006, 066, 068, 069, 099, + 072, 076, 082, 085, 087, 089, 090, 091, 094, 100, + 102, 104, 115, 117, 118, 120, 121, 126, 129, 130, + 135, 138, 143, 156, 086, 149, 153, 157, 161, 164, + + 015, 019, 057, 070, 148, + }; } diff --git a/PKHeX.Core/Legality/Tables/Tables2.cs b/PKHeX.Core/Legality/Tables/Tables2.cs index 74a928da1..b3b05c93e 100644 --- a/PKHeX.Core/Legality/Tables/Tables2.cs +++ b/PKHeX.Core/Legality/Tables/Tables2.cs @@ -1,56 +1,55 @@ using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + internal const int MaxSpeciesID_2 = 251; + internal const int MaxMoveID_2 = 251; + internal const int MaxItemID_2 = 255; + internal const int MaxAbilityID_2 = 0; + + internal static readonly ushort[] Pouch_Items_GSC = { + 3, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 51, 52, 53, 57, 60, 62, 63, 64, 65, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 91, 92, 93, 94, 95, 96, 97, 98, 99, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 117, 118, 119, 121, 122, 123, 124, 125, 126, 131, 132, 138, 139, 140, 143, 144, 146, 150, 151, 152, 156, 158, 163, 167, 168, 169, 170, 172, 173, 174, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, + }; + + internal static readonly ushort[] Pouch_Ball_GSC = { + 1, 2, 4, 5, 157, 159, 160, 161, 164, 165, 166, + }; + + internal static readonly ushort[] Pouch_Key_GS = { + 7, 54, 55, 58, 59, 61, 66, 67, 68, 69, 71, 127, 128, 130, 133, 134, 175, 178, + }; + + internal static readonly ushort[] Pouch_Key_C = ArrayUtil.ConcatAll(Pouch_Key_GS, new ushort[]{70, 115, 116, 129}); + + internal static readonly ushort[] Pouch_TMHM_GSC = { + 191, 192, 193, 194, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, + }; + + internal static readonly ushort[] HeldItems_GSC = ArrayUtil.ConcatAll(Pouch_Items_GSC, Pouch_Ball_GSC, Pouch_TMHM_GSC); + + internal static readonly int[] TMHM_GSC = { - internal const int MaxSpeciesID_2 = 251; - internal const int MaxMoveID_2 = 251; - internal const int MaxItemID_2 = 255; - internal const int MaxAbilityID_2 = 0; + 223, 029, 174, 205, 046, 092, 192, 249, 244, 237, + 241, 230, 173, 059, 063, 196, 182, 240, 202, 203, + 218, 076, 231, 225, 087, 089, 216, 091, 094, 247, + 189, 104, 008, 207, 214, 188, 201, 126, 129, 111, + 009, 138, 197, 156, 213, 168, 211, 007, 210, 171, - internal static readonly ushort[] Pouch_Items_GSC = { - 3, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 51, 52, 53, 57, 60, 62, 63, 64, 65, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 91, 92, 93, 94, 95, 96, 97, 98, 99, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 117, 118, 119, 121, 122, 123, 124, 125, 126, 131, 132, 138, 139, 140, 143, 144, 146, 150, 151, 152, 156, 158, 163, 167, 168, 169, 170, 172, 173, 174, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, - }; + 015, 019, 057, 070, 148, 250, 127, + }; - internal static readonly ushort[] Pouch_Ball_GSC = { - 1, 2, 4, 5, 157, 159, 160, 161, 164, 165, 166, - }; + internal static readonly int[] Tutors_GSC = { (int)Move.Flamethrower, (int)Move.Thunderbolt, (int)Move.IceBeam }; - internal static readonly ushort[] Pouch_Key_GS = { - 7, 54, 55, 58, 59, 61, 66, 67, 68, 69, 71, 127, 128, 130, 133, 134, 175, 178, - }; + internal static readonly bool[] ReleasedHeldItems_2 = GetPermitList(MaxItemID_2, HeldItems_GSC); - internal static readonly ushort[] Pouch_Key_C = ArrayUtil.ConcatAll(Pouch_Key_GS, new ushort[]{70, 115, 116, 129}); - - internal static readonly ushort[] Pouch_TMHM_GSC = { - 191, 192, 193, 194, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, - }; - - internal static readonly ushort[] HeldItems_GSC = ArrayUtil.ConcatAll(Pouch_Items_GSC, Pouch_Ball_GSC, Pouch_TMHM_GSC); - - internal static readonly int[] TMHM_GSC = - { - 223, 029, 174, 205, 046, 092, 192, 249, 244, 237, - 241, 230, 173, 059, 063, 196, 182, 240, 202, 203, - 218, 076, 231, 225, 087, 089, 216, 091, 094, 247, - 189, 104, 008, 207, 214, 188, 201, 126, 129, 111, - 009, 138, 197, 156, 213, 168, 211, 007, 210, 171, - - 015, 019, 057, 070, 148, 250, 127, - }; - - internal static readonly int[] Tutors_GSC = { (int)Move.Flamethrower, (int)Move.Thunderbolt, (int)Move.IceBeam }; - - internal static readonly bool[] ReleasedHeldItems_2 = GetPermitList(MaxItemID_2, HeldItems_GSC); - - internal static bool TransferSpeciesDefaultAbilityGen2(int species) - { - System.Diagnostics.Debug.Assert((uint)species <= MaxSpeciesID_2); - return species is (int)Gastly or (int)Haunter or (int)Gengar - or (int)Koffing or (int)Weezing - or (int)Misdreavus or (int)Unown - or (int)Mew or (int)Celebi; - } + internal static bool TransferSpeciesDefaultAbilityGen2(int species) + { + System.Diagnostics.Debug.Assert((uint)species <= MaxSpeciesID_2); + return species is (int)Gastly or (int)Haunter or (int)Gengar + or (int)Koffing or (int)Weezing + or (int)Misdreavus or (int)Unown + or (int)Mew or (int)Celebi; } } diff --git a/PKHeX.Core/Legality/Tables/Tables3.cs b/PKHeX.Core/Legality/Tables/Tables3.cs index 8ebf85012..860ca7b53 100644 --- a/PKHeX.Core/Legality/Tables/Tables3.cs +++ b/PKHeX.Core/Legality/Tables/Tables3.cs @@ -1,258 +1,257 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + internal const int MaxSpeciesIndex_3 = 412; + internal const int MaxSpeciesID_3 = 386; + internal const int MaxMoveID_3 = 354; + internal const int MaxItemID_3 = 374; + internal const int MaxItemID_3_COLO = 547; + internal const int MaxItemID_3_XD = 593; + internal const int MaxAbilityID_3 = 77; + internal const int MaxBallID_3 = 0xC; + internal const int MaxGameID_3 = 15; // CXD + + #region RS + internal static readonly ushort[] Pouch_Items_RS = { + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 93, 94, 95, 96, 97, 98, 103, 104, 106, 107, 108, 109, 110, 111, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 254, 255, 256, 257, 258, + }; + + internal static readonly ushort[] Pouch_Key_RS = { + 259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, + }; + + internal static readonly ushort[] Pouch_TM_RS = { + 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, + }; + + public static readonly ushort[] Pouch_HM_RS = { + 339, 340, 341, 342, 343, 344, 345, 346, + }; + + internal static readonly ushort[] Pouch_Berries_RS = { + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + }; + + internal static readonly ushort[] Pouch_Ball_RS = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + }; + + internal static readonly ushort[] Pouch_Key_FRLG = ArrayUtil.ConcatAll(Pouch_Key_RS, new ushort[] { 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374 }); + internal static readonly ushort[] Pouch_Key_E = ArrayUtil.ConcatAll(Pouch_Key_FRLG, new ushort[] { 375, 376 }); + + internal static readonly ushort[] Pouch_TMHM_RS = ArrayUtil.ConcatAll(Pouch_TM_RS, Pouch_HM_RS); + internal static readonly ushort[] HeldItems_RS = ArrayUtil.ConcatAll(Pouch_Items_RS, Pouch_Ball_RS, Pouch_Berries_RS, Pouch_TM_RS); + #endregion + + internal static readonly ushort[] Pouch_Cologne_COLO = {543, 544, 545}; + internal static readonly ushort[] Pouch_Items_COLO = ArrayUtil.ConcatAll(Pouch_Items_RS, new ushort[] {537}); // Time Flute + internal static readonly ushort[] HeldItems_COLO = ArrayUtil.ConcatAll(Pouch_Items_COLO, Pouch_Ball_RS, Pouch_Berries_RS, Pouch_TM_RS); + + internal static readonly ushort[] Pouch_Key_COLO = { - internal const int MaxSpeciesIndex_3 = 412; - internal const int MaxSpeciesID_3 = 386; - internal const int MaxMoveID_3 = 354; - internal const int MaxItemID_3 = 374; - internal const int MaxItemID_3_COLO = 547; - internal const int MaxItemID_3_XD = 593; - internal const int MaxAbilityID_3 = 77; - internal const int MaxBallID_3 = 0xC; - internal const int MaxGameID_3 = 15; // CXD + 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, + 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, + 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, + 530, 531, 532, 533, 534, 535, 536, 538, 539, + 540, 541, 542, 546, 547, + }; - #region RS - internal static readonly ushort[] Pouch_Items_RS = { - 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 63, 64, 65, 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 93, 94, 95, 96, 97, 98, 103, 104, 106, 107, 108, 109, 110, 111, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 254, 255, 256, 257, 258, - }; + internal static readonly ushort[] Pouch_Cologne_XD = {513, 514, 515}; + internal static readonly ushort[] Pouch_Items_XD = ArrayUtil.ConcatAll(Pouch_Items_RS, new ushort[] {511}); // Poké Snack + internal static readonly ushort[] HeldItems_XD = ArrayUtil.ConcatAll(Pouch_Items_XD, Pouch_Ball_RS, Pouch_Berries_RS, Pouch_TM_RS); - internal static readonly ushort[] Pouch_Key_RS = { - 259, 260, 261, 262, 263, 264, 265, 266, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, - }; + internal static readonly ushort[] Pouch_Key_XD = + { + 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, + 510, 512, 516, 517, 518, 519, + 523, 524, 525, 526, 527, 528, 529, + 530, 531, 532, 533, + }; - internal static readonly ushort[] Pouch_TM_RS = { - 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, - }; + internal static readonly ushort[] Pouch_Disc_XD = + { + 534, 535, 536, 537, 538, 539, + 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, + 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, + 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, + 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, + 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, + 590, 591, 592, 593, + }; - public static readonly ushort[] Pouch_HM_RS = { - 339, 340, 341, 342, 343, 344, 345, 346, - }; + internal static readonly bool[] ReleasedHeldItems_3 = GetPermitList(MaxItemID_3, HeldItems_RS, new ushort[] {005}); // Safari Ball - internal static readonly ushort[] Pouch_Berries_RS = { - 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - }; + internal static readonly int[] TM_3 = + { + 264, 337, 352, 347, 046, 092, 258, 339, 331, 237, + 241, 269, 058, 059, 063, 113, 182, 240, 202, 219, + 218, 076, 231, 085, 087, 089, 216, 091, 094, 247, + 280, 104, 115, 351, 053, 188, 201, 126, 317, 332, + 259, 263, 290, 156, 213, 168, 211, 285, 289, 315, + }; - internal static readonly ushort[] Pouch_Ball_RS = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, - }; + internal static readonly int[] HM_3 = {15, 19, 57, 70, 148, 249, 127, 291}; - internal static readonly ushort[] Pouch_Key_FRLG = ArrayUtil.ConcatAll(Pouch_Key_RS, new ushort[] { 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374 }); - internal static readonly ushort[] Pouch_Key_E = ArrayUtil.ConcatAll(Pouch_Key_FRLG, new ushort[] { 375, 376 }); + internal static readonly int[] Tutor_3Mew = + { + (int)Move.FeintAttack, + (int)Move.FakeOut, + (int)Move.Hypnosis, + (int)Move.NightShade, + (int)Move.RolePlay, + (int)Move.ZapCannon, + }; - internal static readonly ushort[] Pouch_TMHM_RS = ArrayUtil.ConcatAll(Pouch_TM_RS, Pouch_HM_RS); - internal static readonly ushort[] HeldItems_RS = ArrayUtil.ConcatAll(Pouch_Items_RS, Pouch_Ball_RS, Pouch_Berries_RS, Pouch_TM_RS); - #endregion + internal static readonly int[] Tutor_E = + { + 005, 014, 025, 034, 038, 068, 069, 102, 118, 135, + 138, 086, 153, 157, 164, 223, 205, 244, 173, 196, + 203, 189, 008, 207, 214, 129, 111, 009, 007, 210, + }; - internal static readonly ushort[] Pouch_Cologne_COLO = {543, 544, 545}; - internal static readonly ushort[] Pouch_Items_COLO = ArrayUtil.ConcatAll(Pouch_Items_RS, new ushort[] {537}); // Time Flute - internal static readonly ushort[] HeldItems_COLO = ArrayUtil.ConcatAll(Pouch_Items_COLO, Pouch_Ball_RS, Pouch_Berries_RS, Pouch_TM_RS); + // subset of Emerald Tutors + //internal static readonly int[] Tutor_FRLG = + //{ + // 005, 014, 025, 034, 038, 068, 069, 102, 118, 135, + // 138, 086, 153, 157, 164 + //}; - internal static readonly ushort[] Pouch_Key_COLO = - { - 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, - 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, - 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, - 530, 531, 532, 533, 534, 535, 536, 538, 539, - 540, 541, 542, 546, 547, - }; + internal static readonly int[] SpecialTutors_FRLG = + { + (int)Move.BlastBurn, + (int)Move.HydroCannon, + (int)Move.FrenzyPlant, + }; - internal static readonly ushort[] Pouch_Cologne_XD = {513, 514, 515}; - internal static readonly ushort[] Pouch_Items_XD = ArrayUtil.ConcatAll(Pouch_Items_RS, new ushort[] {511}); // Poké Snack - internal static readonly ushort[] HeldItems_XD = ArrayUtil.ConcatAll(Pouch_Items_XD, Pouch_Ball_RS, Pouch_Berries_RS, Pouch_TM_RS); + internal static readonly int[] SpecialTutors_Compatibility_FRLG = { 6, 9, 3 }; - internal static readonly ushort[] Pouch_Key_XD = - { - 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, - 510, 512, 516, 517, 518, 519, - 523, 524, 525, 526, 527, 528, 529, - 530, 531, 532, 533, - }; + internal static readonly int[] SpecialTutors_XD_Exclusive = + { + 120, 143, 171, + }; - internal static readonly ushort[] Pouch_Disc_XD = - { - 534, 535, 536, 537, 538, 539, - 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, - 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, - 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, - 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, - 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, - 590, 591, 592, 593, - }; + internal static readonly int[][] SpecialTutors_Compatibility_XD_Exclusive = + { + new[] { 074, 075, 076, 088, 089, 090, 091, 092, 093, 094, 095, + 100, 101, 102, 103, 109, 110, 143, 150, 151, 185, 204, + 205, 208, 211, 218, 219, 222, 273, 274, 275, 299, 316, + 317, 320, 321, 323, 324, 337, 338, 343, 344, 362, 375, + 376, 377, 378, 379 }, - internal static readonly bool[] ReleasedHeldItems_3 = GetPermitList(MaxItemID_3, HeldItems_RS, new ushort[] {005}); // Safari Ball + new[] { 016, 017, 018, 021, 022, 084, 085, 142, 144, 145, 146, + 151, 163, 164, 176, 177, 178, 198, 225, 227, 250, 276, + 277, 278, 279, 333, 334 }, - internal static readonly int[] TM_3 = - { - 264, 337, 352, 347, 046, 092, 258, 339, 331, 237, - 241, 269, 058, 059, 063, 113, 182, 240, 202, 219, - 218, 076, 231, 085, 087, 089, 216, 091, 094, 247, - 280, 104, 115, 351, 053, 188, 201, 126, 317, 332, - 259, 263, 290, 156, 213, 168, 211, 285, 289, 315, - }; + new[] { 012, 035, 036, 039, 040, 052, 053, 063, 064, 065, 079, + 080, 092, 093, 094, 096, 097, 102, 103, 108, 121, 122, + 124, 131, 137, 150, 151, 163, 164, 173, 174, 177, 178, + 190, 196, 197, 198, 199, 200, 203, 206, 215, 228, 229, + 233, 234, 238, 248, 249, 250, 251, 280, 281, 282, 284, + 292, 302, 315, 316, 317, 327, 353, 354, 355, 356, 358, + 359, 385, 386 }, + }; - internal static readonly int[] HM_3 = {15, 19, 57, 70, 148, 249, 127, 291}; + // 064 is an unused location for Meteor Falls + // 084 is Inside of a truck, no possible pokemon can be hatched there + // 071 is Mirage island, cannot be obtained as the player is technically still on Route 130's map. + internal static readonly HashSet ValidMet_RS = new() + { + 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, + 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, + 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, + 060, 061, 062, 063, 065, 066, 067, 068, 069, 070 , 072, 073, 074, 075, 076, 077, 078, 079, 080, + 081, 082, 083, 085, 086, 087, + }; + // 155 - 158 Sevii Isle 6-9 Unused + // 171 - 173 Sevii Isle 22-24 Unused + internal static readonly HashSet ValidMet_FRLG = new() + { + 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 159, 160, 161, 162, 163, + 164, 165, 166, 167, 168, 169, 170, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, + 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, + }; - internal static readonly int[] Tutor_3Mew = - { - (int)Move.FeintAttack, - (int)Move.FakeOut, - (int)Move.Hypnosis, - (int)Move.NightShade, - (int)Move.RolePlay, - (int)Move.ZapCannon, - }; + internal static readonly HashSet ValidMet_E = new(ValidMet_RS) + { + 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, + }; - internal static readonly int[] Tutor_E = - { - 005, 014, 025, 034, 038, 068, 069, 102, 118, 135, - 138, 086, 153, 157, 164, 223, 205, 244, 173, 196, - 203, 189, 008, 207, 214, 129, 111, 009, 007, 210, - }; - - // subset of Emerald Tutors - //internal static readonly int[] Tutor_FRLG = - //{ - // 005, 014, 025, 034, 038, 068, 069, 102, 118, 135, - // 138, 086, 153, 157, 164 - //}; - - internal static readonly int[] SpecialTutors_FRLG = - { - (int)Move.BlastBurn, - (int)Move.HydroCannon, - (int)Move.FrenzyPlant, - }; - - internal static readonly int[] SpecialTutors_Compatibility_FRLG = { 6, 9, 3 }; - - internal static readonly int[] SpecialTutors_XD_Exclusive = - { - 120, 143, 171, - }; - - internal static readonly int[][] SpecialTutors_Compatibility_XD_Exclusive = - { - new[] { 074, 075, 076, 088, 089, 090, 091, 092, 093, 094, 095, - 100, 101, 102, 103, 109, 110, 143, 150, 151, 185, 204, - 205, 208, 211, 218, 219, 222, 273, 274, 275, 299, 316, - 317, 320, 321, 323, 324, 337, 338, 343, 344, 362, 375, - 376, 377, 378, 379 }, - - new[] { 016, 017, 018, 021, 022, 084, 085, 142, 144, 145, 146, - 151, 163, 164, 176, 177, 178, 198, 225, 227, 250, 276, - 277, 278, 279, 333, 334 }, - - new[] { 012, 035, 036, 039, 040, 052, 053, 063, 064, 065, 079, - 080, 092, 093, 094, 096, 097, 102, 103, 108, 121, 122, - 124, 131, 137, 150, 151, 163, 164, 173, 174, 177, 178, - 190, 196, 197, 198, 199, 200, 203, 206, 215, 228, 229, - 233, 234, 238, 248, 249, 250, 251, 280, 281, 282, 284, - 292, 302, 315, 316, 317, 327, 353, 354, 355, 356, 358, - 359, 385, 386 }, - }; - - // 064 is an unused location for Meteor Falls - // 084 is Inside of a truck, no possible pokemon can be hatched there - // 071 is Mirage island, cannot be obtained as the player is technically still on Route 130's map. - internal static readonly HashSet ValidMet_RS = new() - { - 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, - 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, - 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, - 060, 061, 062, 063, 065, 066, 067, 068, 069, 070 , 072, 073, 074, 075, 076, 077, 078, 079, 080, - 081, 082, 083, 085, 086, 087, - }; - // 155 - 158 Sevii Isle 6-9 Unused - // 171 - 173 Sevii Isle 22-24 Unused - internal static readonly HashSet ValidMet_FRLG = new() - { - 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 159, 160, 161, 162, 163, - 164, 165, 166, 167, 168, 169, 170, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, - 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, - }; - - internal static readonly HashSet ValidMet_E = new(ValidMet_RS) - { - 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, - }; - - /// - /// Species ID that can be originated from Colosseum (using only Generation 3 max Species ID values). - /// - internal static readonly HashSet ValidSpecies_Colo = new() - { - 025, // Pikachu - 153, // Bayleef - 154, // Meganium - 156, // Quilava - 157, // Typhlosion - 159, // Croconaw - 160, // Feraligatr - 162, // Furret - 164, // Noctowl - 166, // Ledian - 168, // Ariados - 175, // Togepi - 176, // Togetic - 179, // Mareep - 180, // Flaaffy - 185, // Sudowoodo - 188, // Skiploom - 189, // Jumpluff - 190, // Aipom - 192, // Sunflora - 193, // Yanma - 195, // Quagsire - 196, // Espeon - 197, // Umbreon - 198, // Murkrow - 200, // Misdreavus - 205, // Forretress - 206, // Dunsparce - 207, // Gligar - 210, // Granbull - 211, // Qwilfish - 212, // Scizor - 213, // Shuckle - 214, // Heracross - 215, // Sneasel - 217, // Ursaring - 218, // Slugma - 219, // Magcargo - 221, // Piloswine - 223, // Remoraid - 225, // Delibird - 226, // Mantine - 227, // Skarmory - 229, // Houndoom - 234, // Stantler - 235, // Smeargle - 237, // Hitmontop - 241, // Miltank - 243, // Raikou - 244, // Entei - 245, // Suicune - 248, // Tyranitar - 250, // Ho-Oh - 251, // Celebi - 296, // Makuhita - 297, // Hariyama - 307, // Meditite - 308, // Medicham - 311, // Plusle - 329, // Vibrava - 330, // Flygon - 333, // Swablu - 334, // Altaria - 357, // Tropius - 359, // Absol - 376, // Metagross - }; - } + /// + /// Species ID that can be originated from Colosseum (using only Generation 3 max Species ID values). + /// + internal static readonly HashSet ValidSpecies_Colo = new() + { + 025, // Pikachu + 153, // Bayleef + 154, // Meganium + 156, // Quilava + 157, // Typhlosion + 159, // Croconaw + 160, // Feraligatr + 162, // Furret + 164, // Noctowl + 166, // Ledian + 168, // Ariados + 175, // Togepi + 176, // Togetic + 179, // Mareep + 180, // Flaaffy + 185, // Sudowoodo + 188, // Skiploom + 189, // Jumpluff + 190, // Aipom + 192, // Sunflora + 193, // Yanma + 195, // Quagsire + 196, // Espeon + 197, // Umbreon + 198, // Murkrow + 200, // Misdreavus + 205, // Forretress + 206, // Dunsparce + 207, // Gligar + 210, // Granbull + 211, // Qwilfish + 212, // Scizor + 213, // Shuckle + 214, // Heracross + 215, // Sneasel + 217, // Ursaring + 218, // Slugma + 219, // Magcargo + 221, // Piloswine + 223, // Remoraid + 225, // Delibird + 226, // Mantine + 227, // Skarmory + 229, // Houndoom + 234, // Stantler + 235, // Smeargle + 237, // Hitmontop + 241, // Miltank + 243, // Raikou + 244, // Entei + 245, // Suicune + 248, // Tyranitar + 250, // Ho-Oh + 251, // Celebi + 296, // Makuhita + 297, // Hariyama + 307, // Meditite + 308, // Medicham + 311, // Plusle + 329, // Vibrava + 330, // Flygon + 333, // Swablu + 334, // Altaria + 357, // Tropius + 359, // Absol + 376, // Metagross + }; } diff --git a/PKHeX.Core/Legality/Tables/Tables4.cs b/PKHeX.Core/Legality/Tables/Tables4.cs index c75dd26a2..28a3451e6 100644 --- a/PKHeX.Core/Legality/Tables/Tables4.cs +++ b/PKHeX.Core/Legality/Tables/Tables4.cs @@ -1,231 +1,230 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + internal const int MaxSpeciesID_4 = 493; + internal const int MaxMoveID_4 = 467; + internal const int MaxItemID_4_DP = 464; + internal const int MaxItemID_4_Pt = 467; + internal const int MaxItemID_4_HGSS = 536; + internal const int MaxAbilityID_4 = 123; + internal const int MaxBallID_4 = 0x18; + internal const int MaxGameID_4 = 15; // CXD + + #region DP + internal static readonly ushort[] Pouch_Items_DP = { + 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 135, 136, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, + }; + + internal static readonly ushort[] Pouch_Key_DP = { + 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, + }; + + internal static readonly ushort[] Pouch_TMHM_DP = { + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, + }; + + internal static readonly ushort[] Pouch_Mail_DP = { + 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, + }; + + internal static readonly ushort[] Pouch_Medicine_DP = { + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + }; + + internal static readonly ushort[] Pouch_Berries_DP = { + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, + }; + + internal static readonly ushort[] Pouch_Ball_DP = { + 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + }; + + internal static readonly ushort[] Pouch_Battle_DP = { + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + }; + + internal static readonly ushort[] HeldItems_DP = ArrayUtil.ConcatAll(Pouch_Items_DP, Pouch_Mail_DP, Pouch_Medicine_DP, Pouch_Berries_DP, Pouch_Ball_DP, Pouch_TMHM_DP.Slice(0, Pouch_TMHM_DP.Length - 8)); + #endregion + + #region Pt + internal static readonly ushort[] Pouch_Items_Pt = { + 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 135, 136, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, + }; + + internal static readonly ushort[] Pouch_Key_Pt = { + 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, + }; + + internal static readonly ushort[] Pouch_TMHM_Pt = Pouch_TMHM_DP; + internal static readonly ushort[] Pouch_Mail_Pt = Pouch_Mail_DP; + internal static readonly ushort[] Pouch_Medicine_Pt = Pouch_Medicine_DP; + internal static readonly ushort[] Pouch_Berries_Pt = Pouch_Berries_DP; + internal static readonly ushort[] Pouch_Ball_Pt = Pouch_Ball_DP; + internal static readonly ushort[] Pouch_Battle_Pt = Pouch_Battle_DP; + + internal static readonly ushort[] HeldItems_Pt = ArrayUtil.ConcatAll(Pouch_Items_Pt, Pouch_Mail_Pt, Pouch_Medicine_Pt, Pouch_Berries_Pt, Pouch_Ball_Pt, Pouch_TMHM_Pt.Slice(0, Pouch_TMHM_Pt.Length - 8)); + #endregion + + #region HGSS + internal static readonly ushort[] Pouch_Items_HGSS = Pouch_Items_Pt; + + internal static readonly ushort[] Pouch_Key_HGSS = { + 434, 435, 437, 444, 445, 446, 447, 450, 456, 464, 465, 466, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 501, 502, 503, 504, 532, 533, 534, 535, 536, + }; + + internal static readonly ushort[] Pouch_TMHM_HGSS = Pouch_TMHM_DP; + internal static readonly ushort[] Pouch_Mail_HGSS = Pouch_Mail_DP; + internal static readonly ushort[] Pouch_Medicine_HGSS = Pouch_Medicine_DP; + internal static readonly ushort[] Pouch_Berries_HGSS = Pouch_Berries_DP; + + internal static readonly ushort[] Pouch_Ball_HGSS = { + 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 492, 493, 494, 495, 496, 497, 498, 499, 500, + }; + + internal static readonly ushort[] Pouch_Battle_HGSS = Pouch_Battle_DP; + + internal static readonly ushort[] HeldItems_HGSS = ArrayUtil.ConcatAll(Pouch_Items_HGSS, Pouch_Mail_HGSS, Pouch_Medicine_HGSS, Pouch_Berries_HGSS, Pouch_Ball_Pt, Pouch_TMHM_HGSS.Slice(0, Pouch_TMHM_HGSS.Length - 8)); + #endregion + + internal static readonly int[] TM_4 = { - internal const int MaxSpeciesID_4 = 493; - internal const int MaxMoveID_4 = 467; - internal const int MaxItemID_4_DP = 464; - internal const int MaxItemID_4_Pt = 467; - internal const int MaxItemID_4_HGSS = 536; - internal const int MaxAbilityID_4 = 123; - internal const int MaxBallID_4 = 0x18; - internal const int MaxGameID_4 = 15; // CXD + 264, 337, 352, 347, 046, 092, 258, 339, 331, 237, + 241, 269, 058, 059, 063, 113, 182, 240, 202, 219, + 218, 076, 231, 085, 087, 089, 216, 091, 094, 247, + 280, 104, 115, 351, 053, 188, 201, 126, 317, 332, + 259, 263, 290, 156, 213, 168, 211, 285, 289, 315, + 355, 411, 412, 206, 362, 374, 451, 203, 406, 409, + 261, 318, 373, 153, 421, 371, 278, 416, 397, 148, + 444, 419, 086, 360, 014, 446, 244, 445, 399, 157, + 404, 214, 363, 398, 138, 447, 207, 365, 369, 164, + 430, 433, + }; - #region DP - internal static readonly ushort[] Pouch_Items_DP = { - 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 135, 136, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, - }; + internal static readonly HashSet HM_4_RemovePokeTransfer = new() + { + (int)Move.Cut, + (int)Move.Fly, + (int)Move.Surf, + (int)Move.Strength, + (int)Move.RockSmash, + (int)Move.Waterfall, + (int)Move.RockClimb, - internal static readonly ushort[] Pouch_Key_DP = { - 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, - }; + // Exclude Defog and Whirlpool; check separately. + // Defog (DPPt) excluded since it's actually useful -- prefer to fake transfer from HGSS instead of DPPt. + }; - internal static readonly ushort[] Pouch_TMHM_DP = { - 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, - }; + internal static readonly int[] HM_DPPt = + { + (int)Move.Cut, + (int)Move.Fly, + (int)Move.Surf, + (int)Move.Strength, + (int)Move.Defog, + (int)Move.RockSmash, + (int)Move.Waterfall, + (int)Move.RockClimb, + }; - internal static readonly ushort[] Pouch_Mail_DP = { - 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, - }; + internal static readonly int[] HM_HGSS = + { + (int)Move.Cut, + (int)Move.Fly, + (int)Move.Surf, + (int)Move.Strength, + (int)Move.Whirlpool, + (int)Move.RockSmash, + (int)Move.Waterfall, + (int)Move.RockClimb, + }; - internal static readonly ushort[] Pouch_Medicine_DP = { - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, - }; + internal static readonly bool[] ReleasedHeldItems_4 = GetPermitList(MaxItemID_4_HGSS, HeldItems_HGSS, new ushort[] + { + 005, // Safari Ball + 016, // Cherish Ball + 147, // Mosaic Mail + 499, // Sport Ball + 500, // Park Ball + }); - internal static readonly ushort[] Pouch_Berries_DP = { - 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, - }; + internal static readonly int[] Tutors_4 = + { + 291, 189, 210, 196, 205, 009, 007, 276, + 008, 442, 401, 466, 380, 173, 180, 314, + 270, 283, 200, 246, 235, 324, 428, 410, + 414, 441, 239, 402, 334, 393, 387, 340, + 271, 257, 282, 389, 129, 253, 162, 220, + 081, 366, 356, 388, 277, 272, 215, 067, + 143, 335, 450, 029, + }; - internal static readonly ushort[] Pouch_Ball_DP = { - 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - }; + internal static readonly int[] SpecialTutors_4 = + { + (int)Move.BlastBurn, + (int)Move.HydroCannon, + (int)Move.FrenzyPlant, + (int)Move.DracoMeteor, + }; - internal static readonly ushort[] Pouch_Battle_DP = { - 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, - }; + internal static readonly int[][] SpecialTutors_Compatibility_4 = + { + new[] { 006, 157, 257, 392 }, + new[] { 009, 160, 260, 395 }, + new[] { 003, 154, 254, 389 }, + new[] { 147, 148, 149, 230, 329, 330, 334, 371, 372, 373, 380, 381, 384, 443, 444, 445, 483, 484, 487 }, + }; - internal static readonly ushort[] HeldItems_DP = ArrayUtil.ConcatAll(Pouch_Items_DP, Pouch_Mail_DP, Pouch_Medicine_DP, Pouch_Berries_DP, Pouch_Ball_DP, Pouch_TMHM_DP.Slice(0, Pouch_TMHM_DP.Length - 8)); - #endregion + internal static readonly HashSet ValidMet_DP = new() + { + // 063: Flower Paradise unreleased DP event + // 079: Newmoon Island unreleased DP event + // 085: Seabreak Path unreleased DP event + // 086: Hall of Origin unreleased event + 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, 020, + 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, 040, + 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, 060, + 061, 062, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 080, + 081, 082, 083, 084, 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, 100, + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + }; - #region Pt - internal static readonly ushort[] Pouch_Items_Pt = { - 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 135, 136, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, - }; + internal static readonly HashSet ValidMet_Pt = new(ValidMet_DP) + { + 63, 79, 85, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, + }; - internal static readonly ushort[] Pouch_Key_Pt = { - 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, - }; + internal static readonly HashSet ValidMet_HGSS = new() + { + 080, 112, 113, 114, 115, 116, + 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, + 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 172, 173, 174, 175, 176, 177, 178, 179, 180, //171: Route 23 no longer exists + 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, + 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, + 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 234, //233: Pokéwalker + }; - internal static readonly ushort[] Pouch_TMHM_Pt = Pouch_TMHM_DP; - internal static readonly ushort[] Pouch_Mail_Pt = Pouch_Mail_DP; - internal static readonly ushort[] Pouch_Medicine_Pt = Pouch_Medicine_DP; - internal static readonly ushort[] Pouch_Berries_Pt = Pouch_Berries_DP; - internal static readonly ushort[] Pouch_Ball_Pt = Pouch_Ball_DP; - internal static readonly ushort[] Pouch_Battle_Pt = Pouch_Battle_DP; + internal static readonly HashSet ValidMet_4 = new(ValidMet_Pt.Concat(ValidMet_HGSS)); - internal static readonly ushort[] HeldItems_Pt = ArrayUtil.ConcatAll(Pouch_Items_Pt, Pouch_Mail_Pt, Pouch_Medicine_Pt, Pouch_Berries_Pt, Pouch_Ball_Pt, Pouch_TMHM_Pt.Slice(0, Pouch_TMHM_Pt.Length - 8)); - #endregion + internal static readonly HashSet GiftEggLocation4 = new() + { + 2009, 2010, 2011, 2013, 2014, + }; - #region HGSS - internal static readonly ushort[] Pouch_Items_HGSS = Pouch_Items_Pt; + internal static int GetTransfer45MetLocation(PKM pk) + { + if (!pk.Gen4 || !pk.FatefulEncounter) + return Locations.Transfer4; // Pokétransfer - internal static readonly ushort[] Pouch_Key_HGSS = { - 434, 435, 437, 444, 445, 446, 447, 450, 456, 464, 465, 466, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 501, 502, 503, 504, 532, 533, 534, 535, 536, - }; - - internal static readonly ushort[] Pouch_TMHM_HGSS = Pouch_TMHM_DP; - internal static readonly ushort[] Pouch_Mail_HGSS = Pouch_Mail_DP; - internal static readonly ushort[] Pouch_Medicine_HGSS = Pouch_Medicine_DP; - internal static readonly ushort[] Pouch_Berries_HGSS = Pouch_Berries_DP; - - internal static readonly ushort[] Pouch_Ball_HGSS = { - 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 492, 493, 494, 495, 496, 497, 498, 499, 500, - }; - - internal static readonly ushort[] Pouch_Battle_HGSS = Pouch_Battle_DP; - - internal static readonly ushort[] HeldItems_HGSS = ArrayUtil.ConcatAll(Pouch_Items_HGSS, Pouch_Mail_HGSS, Pouch_Medicine_HGSS, Pouch_Berries_HGSS, Pouch_Ball_Pt, Pouch_TMHM_HGSS.Slice(0, Pouch_TMHM_HGSS.Length - 8)); - #endregion - - internal static readonly int[] TM_4 = + return pk.Species switch { - 264, 337, 352, 347, 046, 092, 258, 339, 331, 237, - 241, 269, 058, 059, 063, 113, 182, 240, 202, 219, - 218, 076, 231, 085, 087, 089, 216, 091, 094, 247, - 280, 104, 115, 351, 053, 188, 201, 126, 317, 332, - 259, 263, 290, 156, 213, 168, 211, 285, 289, 315, - 355, 411, 412, 206, 362, 374, 451, 203, 406, 409, - 261, 318, 373, 153, 421, 371, 278, 416, 397, 148, - 444, 419, 086, 360, 014, 446, 244, 445, 399, 157, - 404, 214, 363, 398, 138, 447, 207, 365, 369, 164, - 430, 433, + 243 or 244 or 245 => Locations.Transfer4_CrownUnused, // Beast + 251 => Locations.Transfer4_CelebiUnused, // Celebi + _ => Locations.Transfer4, }; - - internal static readonly HashSet HM_4_RemovePokeTransfer = new() - { - (int)Move.Cut, - (int)Move.Fly, - (int)Move.Surf, - (int)Move.Strength, - (int)Move.RockSmash, - (int)Move.Waterfall, - (int)Move.RockClimb, - - // Exclude Defog and Whirlpool; check separately. - // Defog (DPPt) excluded since it's actually useful -- prefer to fake transfer from HGSS instead of DPPt. - }; - - internal static readonly int[] HM_DPPt = - { - (int)Move.Cut, - (int)Move.Fly, - (int)Move.Surf, - (int)Move.Strength, - (int)Move.Defog, - (int)Move.RockSmash, - (int)Move.Waterfall, - (int)Move.RockClimb, - }; - - internal static readonly int[] HM_HGSS = - { - (int)Move.Cut, - (int)Move.Fly, - (int)Move.Surf, - (int)Move.Strength, - (int)Move.Whirlpool, - (int)Move.RockSmash, - (int)Move.Waterfall, - (int)Move.RockClimb, - }; - - internal static readonly bool[] ReleasedHeldItems_4 = GetPermitList(MaxItemID_4_HGSS, HeldItems_HGSS, new ushort[] - { - 005, // Safari Ball - 016, // Cherish Ball - 147, // Mosaic Mail - 499, // Sport Ball - 500, // Park Ball - }); - - internal static readonly int[] Tutors_4 = - { - 291, 189, 210, 196, 205, 009, 007, 276, - 008, 442, 401, 466, 380, 173, 180, 314, - 270, 283, 200, 246, 235, 324, 428, 410, - 414, 441, 239, 402, 334, 393, 387, 340, - 271, 257, 282, 389, 129, 253, 162, 220, - 081, 366, 356, 388, 277, 272, 215, 067, - 143, 335, 450, 029, - }; - - internal static readonly int[] SpecialTutors_4 = - { - (int)Move.BlastBurn, - (int)Move.HydroCannon, - (int)Move.FrenzyPlant, - (int)Move.DracoMeteor, - }; - - internal static readonly int[][] SpecialTutors_Compatibility_4 = - { - new[] { 006, 157, 257, 392 }, - new[] { 009, 160, 260, 395 }, - new[] { 003, 154, 254, 389 }, - new[] { 147, 148, 149, 230, 329, 330, 334, 371, 372, 373, 380, 381, 384, 443, 444, 445, 483, 484, 487 }, - }; - - internal static readonly HashSet ValidMet_DP = new() - { - // 063: Flower Paradise unreleased DP event - // 079: Newmoon Island unreleased DP event - // 085: Seabreak Path unreleased DP event - // 086: Hall of Origin unreleased event - 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, 020, - 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, 040, - 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, 060, - 061, 062, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 080, - 081, 082, 083, 084, 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, 100, - 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - }; - - internal static readonly HashSet ValidMet_Pt = new(ValidMet_DP) - { - 63, 79, 85, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, - }; - - internal static readonly HashSet ValidMet_HGSS = new() - { - 080, 112, 113, 114, 115, 116, - 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, - 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, - 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 172, 173, 174, 175, 176, 177, 178, 179, 180, //171: Route 23 no longer exists - 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, - 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, - 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 234, //233: Pokéwalker - }; - - internal static readonly HashSet ValidMet_4 = new(ValidMet_Pt.Concat(ValidMet_HGSS)); - - internal static readonly HashSet GiftEggLocation4 = new() - { - 2009, 2010, 2011, 2013, 2014, - }; - - internal static int GetTransfer45MetLocation(PKM pkm) - { - if (!pkm.Gen4 || !pkm.FatefulEncounter) - return Locations.Transfer4; // Pokétransfer - - return pkm.Species switch - { - 243 or 244 or 245 => Locations.Transfer4_CrownUnused, // Beast - 251 => Locations.Transfer4_CelebiUnused, // Celebi - _ => Locations.Transfer4, - }; - } } } diff --git a/PKHeX.Core/Legality/Tables/Tables5.cs b/PKHeX.Core/Legality/Tables/Tables5.cs index 770d12727..8fd84b866 100644 --- a/PKHeX.Core/Legality/Tables/Tables5.cs +++ b/PKHeX.Core/Legality/Tables/Tables5.cs @@ -1,108 +1,107 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + internal const int MaxSpeciesID_5 = 649; + internal const int MaxMoveID_5 = 559; + internal const int MaxItemID_5_BW = 632; + internal const int MaxItemID_5_B2W2 = 638; + internal const int MaxAbilityID_5 = 164; + internal const int MaxBallID_5 = 0x19; + internal const int MaxGameID_5 = 23; // B2 + + internal static readonly ushort[] Pouch_Items_BW = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 116, 117, 118, 119, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 492, 493, 494, 495, 496, 497, 498, 499, 500, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, 572, 573, 575, 576, 577, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, + }; + + internal static readonly ushort[] Pouch_Key_BW = { + 437, 442, 447, 450, 465, 466, 471, 504, 533, 574, 578, 579, 616, 617, 621, 623, 624, 625, 626, + }; + + internal static readonly ushort[] Pouch_TMHM_BW = { + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 618, 619, 620, 420, 421, 422, 423, 424, 425, + }; + + internal static readonly ushort[] Pouch_Medicine_BW = { + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 134, 504, 565, 566, 567, 568, 569, 570, 591, + }; + + internal static readonly ushort[] Pouch_Berries_BW = { + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, + }; + + internal static readonly ushort[] HeldItems_BW = ArrayUtil.ConcatAll(Pouch_Items_BW, Pouch_Medicine_BW, Pouch_Berries_BW); + + internal static readonly ushort[] Pouch_Key_B2W2 = { + 437, 442, 447, 450, 453, 458, 465, 466, 471, 504, 578, 616, 617, 621, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, + }; + + internal static readonly int[] TMHM_BW = { - internal const int MaxSpeciesID_5 = 649; - internal const int MaxMoveID_5 = 559; - internal const int MaxItemID_5_BW = 632; - internal const int MaxItemID_5_B2W2 = 638; - internal const int MaxAbilityID_5 = 164; - internal const int MaxBallID_5 = 0x19; - internal const int MaxGameID_5 = 23; // B2 + 468, 337, 473, 347, 046, 092, 258, 339, 474, 237, + 241, 269, 058, 059, 063, 113, 182, 240, 477, 219, + 218, 076, 479, 085, 087, 089, 216, 091, 094, 247, + 280, 104, 115, 482, 053, 188, 201, 126, 317, 332, + 259, 263, 488, 156, 213, 168, 490, 496, 497, 315, + 502, 411, 412, 206, 503, 374, 451, 507, 510, 511, + 261, 512, 373, 153, 421, 371, 514, 416, 397, 148, + 444, 521, 086, 360, 014, 522, 244, 523, 524, 157, + 404, 525, 526, 398, 138, 447, 207, 365, 369, 164, + 430, 433, 528, 249, 555, - internal static readonly ushort[] Pouch_Items_BW = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 116, 117, 118, 119, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 492, 493, 494, 495, 496, 497, 498, 499, 500, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, 572, 573, 575, 576, 577, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, - }; + 015, 019, 057, 070, 127, 291, + }; - internal static readonly ushort[] Pouch_Key_BW = { - 437, 442, 447, 450, 465, 466, 471, 504, 533, 574, 578, 579, 616, 617, 621, 623, 624, 625, 626, - }; + internal static readonly bool[] ReleasedHeldItems_5 = GetPermitList(MaxItemID_5_B2W2, HeldItems_BW, new ushort[] + { + 005, // Safari Ball + 016, // Cherish Ball + 260, // Red Scarf + 261, // Blue Scarf + 262, // Pink Scarf + 263, // Green Scarf + 264, // Yellow Scarf + 492, // Fast Ball + 493, // Level Ball + 494, // Lure Ball + 495, // Heavy Ball + 496, // Love Ball + 497, // Friend Ball + 498, // Moon Ball + 499, // Sport Ball + 500, // Park Ball + 576, // Dream Ball + }); - internal static readonly ushort[] Pouch_TMHM_BW = { - 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 618, 619, 620, 420, 421, 422, 423, 424, 425, - }; + internal static readonly int[][] Tutors_B2W2 = + { + new[] { 450, 343, 162, 530, 324, 442, 402, 529, 340, 067, 441, 253, 009, 007, 008 }, // Driftveil City + new[] { 277, 335, 414, 492, 356, 393, 334, 387, 276, 527, 196, 401, 399, 428, 406, 304, 231 }, // Lentimas Town + new[] { 020, 173, 282, 235, 257, 272, 215, 366, 143, 220, 202, 409, 355 }, // Humilau City + new[] { 380, 388, 180, 495, 270, 271, 478, 472, 283, 200, 278, 289, 446, 214, 285 }, // Nacrene City + }; - internal static readonly ushort[] Pouch_Medicine_BW = { - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 134, 504, 565, 566, 567, 568, 569, 570, 591, - }; + internal static readonly HashSet ValidMet_BW = new() + { + 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, 020, + 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, 040, + 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, 060, + 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, 080, + 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, 100, + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + }; - internal static readonly ushort[] Pouch_Berries_BW = { - 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, - }; - - internal static readonly ushort[] HeldItems_BW = ArrayUtil.ConcatAll(Pouch_Items_BW, Pouch_Medicine_BW, Pouch_Berries_BW); - - internal static readonly ushort[] Pouch_Key_B2W2 = { - 437, 442, 447, 450, 453, 458, 465, 466, 471, 504, 578, 616, 617, 621, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, - }; - - internal static readonly int[] TMHM_BW = - { - 468, 337, 473, 347, 046, 092, 258, 339, 474, 237, - 241, 269, 058, 059, 063, 113, 182, 240, 477, 219, - 218, 076, 479, 085, 087, 089, 216, 091, 094, 247, - 280, 104, 115, 482, 053, 188, 201, 126, 317, 332, - 259, 263, 488, 156, 213, 168, 490, 496, 497, 315, - 502, 411, 412, 206, 503, 374, 451, 507, 510, 511, - 261, 512, 373, 153, 421, 371, 514, 416, 397, 148, - 444, 521, 086, 360, 014, 522, 244, 523, 524, 157, - 404, 525, 526, 398, 138, 447, 207, 365, 369, 164, - 430, 433, 528, 249, 555, - - 015, 019, 057, 070, 127, 291, - }; - - internal static readonly bool[] ReleasedHeldItems_5 = GetPermitList(MaxItemID_5_B2W2, HeldItems_BW, new ushort[] - { - 005, // Safari Ball - 016, // Cherish Ball - 260, // Red Scarf - 261, // Blue Scarf - 262, // Pink Scarf - 263, // Green Scarf - 264, // Yellow Scarf - 492, // Fast Ball - 493, // Level Ball - 494, // Lure Ball - 495, // Heavy Ball - 496, // Love Ball - 497, // Friend Ball - 498, // Moon Ball - 499, // Sport Ball - 500, // Park Ball - 576, // Dream Ball - }); - - internal static readonly int[][] Tutors_B2W2 = - { - new[] { 450, 343, 162, 530, 324, 442, 402, 529, 340, 067, 441, 253, 009, 007, 008 }, // Driftveil City - new[] { 277, 335, 414, 492, 356, 393, 334, 387, 276, 527, 196, 401, 399, 428, 406, 304, 231 }, // Lentimas Town - new[] { 020, 173, 282, 235, 257, 272, 215, 366, 143, 220, 202, 409, 355 }, // Humilau City - new[] { 380, 388, 180, 495, 270, 271, 478, 472, 283, 200, 278, 289, 446, 214, 285 }, // Nacrene City - }; - - internal static readonly HashSet ValidMet_BW = new() - { - 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, 020, - 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, 040, - 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, 060, - 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, 080, - 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, 100, - 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, - }; - - internal static readonly HashSet ValidMet_B2W2 = new() - { - 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, 020, - 021, 022, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, //023 Route 10, 040->134 Victory Road - 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 060, //059 Challenger's cave - 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, 080, - 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, 100, - 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, - 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 139, 140, //138 --- - 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, - }; - } + internal static readonly HashSet ValidMet_B2W2 = new() + { + 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, 020, + 021, 022, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, //023 Route 10, 040->134 Victory Road + 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 060, //059 Challenger's cave + 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, 080, + 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, 100, + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 139, 140, //138 --- + 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, + }; } diff --git a/PKHeX.Core/Legality/Tables/Tables6.cs b/PKHeX.Core/Legality/Tables/Tables6.cs index d00af22c5..d90499563 100644 --- a/PKHeX.Core/Legality/Tables/Tables6.cs +++ b/PKHeX.Core/Legality/Tables/Tables6.cs @@ -1,262 +1,261 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + internal const int MaxSpeciesID_6 = 721; + internal const int MaxMoveID_6_XY = 617; + internal const int MaxMoveID_6_AO = 621; + internal const int MaxItemID_6_XY = 717; + internal const int MaxItemID_6_AO = 775; + internal const int MaxAbilityID_6_XY = 188; + internal const int MaxAbilityID_6_AO = 191; + internal const int MaxBallID_6 = 0x19; + internal const int MaxGameID_6 = 27; // OR + + #region Inventory Pouch + + internal static readonly ushort[] Pouch_Items_XY = { - internal const int MaxSpeciesID_6 = 721; - internal const int MaxMoveID_6_XY = 617; - internal const int MaxMoveID_6_AO = 621; - internal const int MaxItemID_6_XY = 717; - internal const int MaxItemID_6_AO = 775; - internal const int MaxAbilityID_6_XY = 188; - internal const int MaxAbilityID_6_AO = 191; - internal const int MaxBallID_6 = 0x19; - internal const int MaxGameID_6 = 27; // OR + 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 055, 056, + 057, 058, 059, 060, 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, + 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, + 099, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 112, 116, 117, 118, 119, 135, 136, + 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, + 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, + 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, + 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, + 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, + 492, 493, 494, 495, 496, 497, 498, 499, 500, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, + 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, + 572, 573, 576, 577, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 639, 640, 644, 646, + 647, 648, 649, 650, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, + 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, + 699, 704, 710, 711, 715, + }; - #region Inventory Pouch + internal static readonly ushort[] Pouch_Items_AO = + { + 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 055, 056, + 057, 058, 059, 060, 061, 062, 063, 064, 068, 069, 070, 071, 072, 073, 074, 075, + 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, + 099, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 112, 116, 117, 118, 119, 135, 136, + 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, + 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, + 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, + 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, + 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, + 492, 493, 494, 495, 496, 497, 498, 499, 500, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, + 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, + 572, 573, 576, 577, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 639, 640, 644, 646, + 647, 648, 649, 650, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, + 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, + 699, 704, 710, 711, 715, - internal static readonly ushort[] Pouch_Items_XY = - { - 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 055, 056, - 057, 058, 059, 060, 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, - 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, - 099, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 112, 116, 117, 118, 119, 135, 136, - 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, - 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, - 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, - 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, - 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, - 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, - 492, 493, 494, 495, 496, 497, 498, 499, 500, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, - 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, - 572, 573, 576, 577, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 639, 640, 644, 646, - 647, 648, 649, 650, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, - 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, - 699, 704, 710, 711, 715, - }; + // ORAS + 534, 535, + 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 767, 768, 769, 770, + }; - internal static readonly ushort[] Pouch_Items_AO = - { - 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 055, 056, - 057, 058, 059, 060, 061, 062, 063, 064, 068, 069, 070, 071, 072, 073, 074, 075, - 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, - 099, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 112, 116, 117, 118, 119, 135, 136, - 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, - 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, - 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, - 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, - 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, - 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, - 492, 493, 494, 495, 496, 497, 498, 499, 500, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, - 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, - 572, 573, 576, 577, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 639, 640, 644, 646, - 647, 648, 649, 650, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, - 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, - 699, 704, 710, 711, 715, + internal static readonly ushort[] Pouch_Key_XY = + { + 216, 431, 442, 445, 446, 447, 450, 465, 466, 471, 628, + 629, 631, 632, 638, 641, 642, 643, 689, 695, 696, 697, 698, + 700, 701, 702, 703, 705, 712, 713, 714, - // ORAS - 534, 535, - 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 767, 768, 769, 770, - }; + // Illegal + // 716, 717, 706, 707, + }; - internal static readonly ushort[] Pouch_Key_XY = - { - 216, 431, 442, 445, 446, 447, 450, 465, 466, 471, 628, - 629, 631, 632, 638, 641, 642, 643, 689, 695, 696, 697, 698, - 700, 701, 702, 703, 705, 712, 713, 714, + internal static readonly ushort[] Pouch_Key_AO = + { + 216, 445, 446, 447, 465, 466, 471, 628, + 629, 631, 632, 638, 697, - // Illegal - // 716, 717, 706, 707, - }; + // Illegal + // 716, 717, 723, 745, 746, 747, 748, 749, 750, 766, - internal static readonly ushort[] Pouch_Key_AO = - { - 216, 445, 446, 447, 465, 466, 471, 628, - 629, 631, 632, 638, 697, + // ORAS + 457, 474, 503, - // Illegal - // 716, 717, 723, 745, 746, 747, 748, 749, 750, 766, + 718, 719, + 720, 721, 722, 724, 725, 726, 727, 728, 729, + 730, 731, 732, 733, 734, 735, 736, 738, 739, + 740, 741, 742, 743, 744, + 751, + 765, 771, 772, 774, 775, + }; - // ORAS - 457, 474, 503, + internal static readonly ushort[] Pouch_TMHM_XY = + { + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, + 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, + 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 618, 619, 620, 690, 691, 692, 693, 694, - 718, 719, - 720, 721, 722, 724, 725, 726, 727, 728, 729, - 730, 731, 732, 733, 734, 735, 736, 738, 739, - 740, 741, 742, 743, 744, - 751, - 765, 771, 772, 774, 775, - }; + 420, 421, 422, 423, 424, + }; - internal static readonly ushort[] Pouch_TMHM_XY = - { - 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, - 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, - 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, - 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, - 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, - 418, 419, 618, 619, 620, 690, 691, 692, 693, 694, + internal static readonly ushort[] Pouch_TMHM_AO = + { + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, + 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, + 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 618, 619, 620, 690, 691, 692, 693, 694, - 420, 421, 422, 423, 424, - }; + 420, 421, 422, 423, 424, - internal static readonly ushort[] Pouch_TMHM_AO = - { - 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, - 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, - 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, - 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, - 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, - 418, 419, 618, 619, 620, 690, 691, 692, 693, 694, + // ORAS + 425, 737, + }; - 420, 421, 422, 423, 424, + internal static readonly ushort[] Pouch_Medicine_XY = + { + 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, + 034, 035, 036, 037, 038, 039, 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, + 052, 053, 054, 134, 504, 565, 566, 567, 568, 569, 570, 571, 591, 645, 708, 709, + }; - // ORAS - 425, 737, - }; + internal static readonly ushort[] Pouch_Medicine_AO = + { + 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, + 034, 035, 036, 037, 038, 039, 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, + 052, 053, 054, 134, 504, 565, 566, 567, 568, 569, 570, 571, 591, 645, 708, 709, - internal static readonly ushort[] Pouch_Medicine_XY = - { - 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, - 034, 035, 036, 037, 038, 039, 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, - 052, 053, 054, 134, 504, 565, 566, 567, 568, 569, 570, 571, 591, 645, 708, 709, - }; + //ORAS + 065, 066, 067, + }; - internal static readonly ushort[] Pouch_Medicine_AO = - { - 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, - 034, 035, 036, 037, 038, 039, 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, - 052, 053, 054, 134, 504, 565, 566, 567, 568, 569, 570, 571, 591, 645, 708, 709, + public static readonly ushort[] Pouch_Berry_XY = + { + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, + 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, + 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 686, 687, 688, + }; - //ORAS - 065, 066, 067, - }; + internal static readonly ushort[] HeldItem_XY = ArrayUtil.ConcatAll(Pouch_Items_XY, Pouch_Medicine_XY, Pouch_Berry_XY); + internal static readonly ushort[] HeldItem_AO = ArrayUtil.ConcatAll(Pouch_Items_AO, Pouch_Medicine_AO, Pouch_Berry_XY); + #endregion - public static readonly ushort[] Pouch_Berry_XY = - { - 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, - 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, - 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, - 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, - 208, 209, 210, 211, 212, 686, 687, 688, - }; + #region TMHM - internal static readonly ushort[] HeldItem_XY = ArrayUtil.ConcatAll(Pouch_Items_XY, Pouch_Medicine_XY, Pouch_Berry_XY); - internal static readonly ushort[] HeldItem_AO = ArrayUtil.ConcatAll(Pouch_Items_AO, Pouch_Medicine_AO, Pouch_Berry_XY); - #endregion + internal static readonly int[] TMHM_AO = + { + 468, 337, 473, 347, 046, 092, 258, 339, 474, 237, + 241, 269, 058, 059, 063, 113, 182, 240, 355, 219, + 218, 076, 479, 085, 087, 089, 216, 091, 094, 247, + 280, 104, 115, 482, 053, 188, 201, 126, 317, 332, + 259, 263, 488, 156, 213, 168, 490, 496, 497, 315, + 211, 411, 412, 206, 503, 374, 451, 507, 510, 511, + 261, 512, 373, 153, 421, 371, 514, 416, 397, 148, + 444, 521, 086, 360, 014, 522, 244, 523, 524, 157, + 404, 525, 611, 398, 138, 447, 207, 214, 369, 164, + 430, 433, 528, 290, 555, 267, 399, 612, 605, 590, - #region TMHM + 15, 19, 57, 70, 127, 249, 291, + }; - internal static readonly int[] TMHM_AO = - { - 468, 337, 473, 347, 046, 092, 258, 339, 474, 237, - 241, 269, 058, 059, 063, 113, 182, 240, 355, 219, - 218, 076, 479, 085, 087, 089, 216, 091, 094, 247, - 280, 104, 115, 482, 053, 188, 201, 126, 317, 332, - 259, 263, 488, 156, 213, 168, 490, 496, 497, 315, - 211, 411, 412, 206, 503, 374, 451, 507, 510, 511, - 261, 512, 373, 153, 421, 371, 514, 416, 397, 148, - 444, 521, 086, 360, 014, 522, 244, 523, 524, 157, - 404, 525, 611, 398, 138, 447, 207, 214, 369, 164, - 430, 433, 528, 290, 555, 267, 399, 612, 605, 590, + internal static readonly int[] TMHM_XY = + { + 468, 337, 473, 347, 046, 092, 258, 339, 474, 237, + 241, 269, 058, 059, 063, 113, 182, 240, 355, 219, + 218, 076, 479, 085, 087, 089, 216, 091, 094, 247, + 280, 104, 115, 482, 053, 188, 201, 126, 317, 332, + 259, 263, 488, 156, 213, 168, 490, 496, 497, 315, + 211, 411, 412, 206, 503, 374, 451, 507, 510, 511, + 261, 512, 373, 153, 421, 371, 514, 416, 397, 148, + 444, 521, 086, 360, 014, 522, 244, 523, 524, 157, + 404, 525, 611, 398, 138, 447, 207, 214, 369, 164, + 430, 433, 528, 249, 555, 267, 399, 612, 605, 590, - 15, 19, 57, 70, 127, 249, 291, - }; + 15, 19, 57, 70, 127, + }; - internal static readonly int[] TMHM_XY = - { - 468, 337, 473, 347, 046, 092, 258, 339, 474, 237, - 241, 269, 058, 059, 063, 113, 182, 240, 355, 219, - 218, 076, 479, 085, 087, 089, 216, 091, 094, 247, - 280, 104, 115, 482, 053, 188, 201, 126, 317, 332, - 259, 263, 488, 156, 213, 168, 490, 496, 497, 315, - 211, 411, 412, 206, 503, 374, 451, 507, 510, 511, - 261, 512, 373, 153, 421, 371, 514, 416, 397, 148, - 444, 521, 086, 360, 014, 522, 244, 523, 524, 157, - 404, 525, 611, 398, 138, 447, 207, 214, 369, 164, - 430, 433, 528, 249, 555, 267, 399, 612, 605, 590, + internal static readonly int[] TypeTutor6 = + { + (int)Move.GrassPledge, + (int)Move.FirePledge, + (int)Move.WaterPledge, + (int)Move.FrenzyPlant, + (int)Move.BlastBurn, + (int)Move.HydroCannon, + (int)Move.DracoMeteor, + (int)Move.DragonAscent, + }; - 15, 19, 57, 70, 127, - }; + internal static readonly int[][] Tutors_AO = + { + new[] {450, 343, 162, 530, 324, 442, 402, 529, 340, 067, 441, 253, 009, 007, 008}, + new[] {277, 335, 414, 492, 356, 393, 334, 387, 276, 527, 196, 401, 399, 428, 406, 304, 231}, + new[] {020, 173, 282, 235, 257, 272, 215, 366, 143, 220, 202, 409, 355, 264, 351, 352}, + new[] {380, 388, 180, 495, 270, 271, 478, 472, 283, 200, 278, 289, 446, 214, 285}, + }; - internal static readonly int[] TypeTutor6 = - { - (int)Move.GrassPledge, - (int)Move.FirePledge, - (int)Move.WaterPledge, - (int)Move.FrenzyPlant, - (int)Move.BlastBurn, - (int)Move.HydroCannon, - (int)Move.DracoMeteor, - (int)Move.DragonAscent, - }; + #endregion - internal static readonly int[][] Tutors_AO = - { - new[] {450, 343, 162, 530, 324, 442, 402, 529, 340, 067, 441, 253, 009, 007, 008}, - new[] {277, 335, 414, 492, 356, 393, 334, 387, 276, 527, 196, 401, 399, 428, 406, 304, 231}, - new[] {020, 173, 282, 235, 257, 272, 215, 366, 143, 220, 202, 409, 355, 264, 351, 352}, - new[] {380, 388, 180, 495, 270, 271, 478, 472, 283, 200, 278, 289, 446, 214, 285}, - }; + internal static readonly HashSet ValidMet_XY = new() + { + 006, 008, 009, 010, 012, 013, 014, 016, 017, 018, 020, 021, 022, 024, 026, 028, 029, 030, 032, 034, 035, 036, + 038, 039, 040, 042, 043, 044, 046, 047, 048, 050, 051, 052, 054, 055, 056, 058, 060, 062, 063, 064, 066, 067, + 068, 069, 070, 072, 074, 075, 076, 078, 079, 082, 084, 085, 086, 088, 089, 090, 092, 093, 094, 096, 097, 098, + 100, 101, 102, 103, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 135, 136, + 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, + }; - #endregion + internal static readonly HashSet ValidMet_AO = new() + { + 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, + 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, + 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, + 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, + 332, 334, 336, 338, 340, 342, 344, 346, 350, 352, 354, + }; - internal static readonly HashSet ValidMet_XY = new() - { - 006, 008, 009, 010, 012, 013, 014, 016, 017, 018, 020, 021, 022, 024, 026, 028, 029, 030, 032, 034, 035, 036, - 038, 039, 040, 042, 043, 044, 046, 047, 048, 050, 051, 052, 054, 055, 056, 058, 060, 062, 063, 064, 066, 067, - 068, 069, 070, 072, 074, 075, 076, 078, 079, 082, 084, 085, 086, 088, 089, 090, 092, 093, 094, 096, 097, 098, - 100, 101, 102, 103, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 135, 136, - 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, - }; - - internal static readonly HashSet ValidMet_AO = new() - { - 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, - 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, - 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, - 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, - 332, 334, 336, 338, 340, 342, 344, 346, 350, 352, 354, - }; - - internal static readonly bool[] ReleasedHeldItems_6 = GetPermitList(MaxItemID_6_AO, HeldItem_AO, new ushort[] - { - 005, // Safari Ball - 016, // Cherish Ball - 492, // Fast Ball - 493, // Level Ball - 494, // Lure Ball - 495, // Heavy Ball - 496, // Love Ball - 497, // Friend Ball - 498, // Moon Ball - 499, // Sport Ball - 500, // Park Ball - 548, // Fire Gem - 549, // Water Gem - 550, // Electric Gem - 551, // Grass Gem - 552, // Ice Gem - 553, // Fighting Gem - 554, // Poison Gem - 555, // Ground Gem - 556, // Flying Gem - 557, // Psychic Gem - 558, // Bug Gem - 559, // Rock Gem - 560, // Ghost Gem - 561, // Dragon Gem - 562, // Dark Gem - 563, // Steel Gem - 576, // Dream Ball - 584, // Relic Copper - 585, // Relic Silver - 587, // Relic Vase - 588, // Relic Band - 589, // Relic Statue - 590, // Relic Crown - 715, // Fairy Gem - }); - } + internal static readonly bool[] ReleasedHeldItems_6 = GetPermitList(MaxItemID_6_AO, HeldItem_AO, new ushort[] + { + 005, // Safari Ball + 016, // Cherish Ball + 492, // Fast Ball + 493, // Level Ball + 494, // Lure Ball + 495, // Heavy Ball + 496, // Love Ball + 497, // Friend Ball + 498, // Moon Ball + 499, // Sport Ball + 500, // Park Ball + 548, // Fire Gem + 549, // Water Gem + 550, // Electric Gem + 551, // Grass Gem + 552, // Ice Gem + 553, // Fighting Gem + 554, // Poison Gem + 555, // Ground Gem + 556, // Flying Gem + 557, // Psychic Gem + 558, // Bug Gem + 559, // Rock Gem + 560, // Ghost Gem + 561, // Dragon Gem + 562, // Dark Gem + 563, // Steel Gem + 576, // Dream Ball + 584, // Relic Copper + 585, // Relic Silver + 587, // Relic Vase + 588, // Relic Band + 589, // Relic Statue + 590, // Relic Crown + 715, // Fairy Gem + }); } diff --git a/PKHeX.Core/Legality/Tables/Tables7.cs b/PKHeX.Core/Legality/Tables/Tables7.cs index 571143fdd..97b0392b5 100644 --- a/PKHeX.Core/Legality/Tables/Tables7.cs +++ b/PKHeX.Core/Legality/Tables/Tables7.cs @@ -1,340 +1,339 @@ using System.Collections.Generic; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + internal const int MaxSpeciesID_7 = 802; + internal const int MaxMoveID_7 = 719; + internal const int MaxItemID_7 = 920; + internal const int MaxAbilityID_7 = 232; + internal const int MaxBallID_7 = 0x1A; // 26 + internal const int MaxGameID_7 = 41; // Crystal (VC?) + + internal const int MaxSpeciesID_7_USUM = 807; + internal const int MaxMoveID_7_USUM = 728; + internal const int MaxItemID_7_USUM = 959; + internal const int MaxAbilityID_7_USUM = 233; + + internal static readonly int[] Tutors_USUM = { - internal const int MaxSpeciesID_7 = 802; - internal const int MaxMoveID_7 = 719; - internal const int MaxItemID_7 = 920; - internal const int MaxAbilityID_7 = 232; - internal const int MaxBallID_7 = 0x1A; // 26 - internal const int MaxGameID_7 = 41; // Crystal (VC?) + 450, 343, 162, 530, 324, 442, 402, 529, 340, 067, 441, 253, 009, 007, 008, + 277, 335, 414, 492, 356, 393, 334, 387, 276, 527, 196, 401, 428, 406, 304, 231, + 020, 173, 282, 235, 257, 272, 215, 366, 143, 220, 202, 409, 264, 351, 352, + 380, 388, 180, 495, 270, 271, 478, 472, 283, 200, 278, 289, 446, 285, - internal const int MaxSpeciesID_7_USUM = 807; - internal const int MaxMoveID_7_USUM = 728; - internal const int MaxItemID_7_USUM = 959; - internal const int MaxAbilityID_7_USUM = 233; + 477, 502, 432, 710, 707, 675, 673, + }; - internal static readonly int[] Tutors_USUM = - { - 450, 343, 162, 530, 324, 442, 402, 529, 340, 067, 441, 253, 009, 007, 008, - 277, 335, 414, 492, 356, 393, 334, 387, 276, 527, 196, 401, 428, 406, 304, 231, - 020, 173, 282, 235, 257, 272, 215, 366, 143, 220, 202, 409, 264, 351, 352, - 380, 388, 180, 495, 270, 271, 478, 472, 283, 200, 278, 289, 446, 285, + internal static readonly ushort[] Pouch_Regular_SM = // 00 + { + 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, + 088, 089, 090, 091, 092, 093, 094, 099, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 116, 117, 118, 119, 135, 136, 137, 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, + 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, + 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, + 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, + 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, + 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, + 326, 327, 499, 534, 535, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, + 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, 572, 573, 580, 581, 582, 583, + 584, 585, 586, 587, 588, 589, 590, 639, 640, 644, 646, 647, 648, 649, 650, 656, 657, 658, 659, + 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, + 680, 681, 682, 683, 684, 685, 699, 704, 710, 711, 715, 752, 753, 754, 755, 756, 757, 758, 759, 760, + 761, 762, 763, 764, 767, 768, 769, 770, 795, 796, 844, 849, 853, 854, 855, 856, 879, 880, 881, 882, + 883, 884, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, + }; - 477, 502, 432, 710, 707, 675, 673, - }; + internal static readonly ushort[] Pouch_Ball_SM = { // 08 + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 492, 493, 494, 495, 496, 497, 498, 576, + 851, + }; - internal static readonly ushort[] Pouch_Regular_SM = // 00 - { - 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, 080, 081, 082, 083, 084, 085, 086, 087, - 088, 089, 090, 091, 092, 093, 094, 099, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 116, 117, 118, 119, 135, 136, 137, 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, - 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, - 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, - 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, - 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, - 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, - 326, 327, 499, 534, 535, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, - 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 571, 572, 573, 580, 581, 582, 583, - 584, 585, 586, 587, 588, 589, 590, 639, 640, 644, 646, 647, 648, 649, 650, 656, 657, 658, 659, - 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, - 680, 681, 682, 683, 684, 685, 699, 704, 710, 711, 715, 752, 753, 754, 755, 756, 757, 758, 759, 760, - 761, 762, 763, 764, 767, 768, 769, 770, 795, 796, 844, 849, 853, 854, 855, 856, 879, 880, 881, 882, - 883, 884, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, - }; + internal static readonly ushort[] Pouch_Battle_SM = { // 16 + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 577, + 846, + }; - internal static readonly ushort[] Pouch_Ball_SM = { // 08 - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 492, 493, 494, 495, 496, 497, 498, 576, - 851, - }; + internal static readonly ushort[] Pouch_Items_SM = ArrayUtil.ConcatAll(Pouch_Regular_SM, Pouch_Ball_SM, Pouch_Battle_SM); - internal static readonly ushort[] Pouch_Battle_SM = { // 16 - 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 577, - 846, - }; + internal static readonly ushort[] Pouch_Key_SM = { + 216, 465, 466, 628, 629, 631, 632, 638, + 705, 706, 765, 773, 797, + 841, 842, 843, 845, 847, 850, 857, 858, 860, + }; - internal static readonly ushort[] Pouch_Items_SM = ArrayUtil.ConcatAll(Pouch_Regular_SM, Pouch_Ball_SM, Pouch_Battle_SM); + internal static readonly ushort[] Pouch_Key_USUM = ArrayUtil.ConcatAll(Pouch_Key_SM, new ushort[] { + 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, + 440, + }); - internal static readonly ushort[] Pouch_Key_SM = { - 216, 465, 466, 628, 629, 631, 632, 638, - 705, 706, 765, 773, 797, - 841, 842, 843, 845, 847, 850, 857, 858, 860, - }; + public static readonly ushort[] Pouch_Roto_USUM = { + 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, + }; - internal static readonly ushort[] Pouch_Key_USUM = ArrayUtil.ConcatAll(Pouch_Key_SM, new ushort[] { - 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, - 440, - }); + internal static readonly ushort[] Pouch_TMHM_SM = { // 02 + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, + 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, + 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, + 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 618, 619, 620, 690, 691, 692, 693, 694, + }; - public static readonly ushort[] Pouch_Roto_USUM = { - 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, - }; + internal static readonly ushort[] Pouch_Medicine_SM = { // 32 + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 65, 66, 67, 134, + 504, 565, 566, 567, 568, 569, 570, 591, 645, 708, 709, + 852, + }; - internal static readonly ushort[] Pouch_TMHM_SM = { // 02 - 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, - 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, - 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, - 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, - 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, - 418, 419, 618, 619, 620, 690, 691, 692, 693, 694, - }; + internal static readonly ushort[] Pouch_Berries_SM = { + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, + 686, 687, 688, + }; - internal static readonly ushort[] Pouch_Medicine_SM = { // 32 - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 65, 66, 67, 134, - 504, 565, 566, 567, 568, 569, 570, 591, 645, 708, 709, - 852, - }; + internal static readonly ushort[] Pouch_ZCrystal_SM = { // Bead + 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, + }; - internal static readonly ushort[] Pouch_Berries_SM = { - 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, - 686, 687, 688, - }; + internal static readonly ushort[] Pouch_ZCrystalHeld_SM = { // Piece + 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 798, 799, 800, 801, 802, 803, 804, 805, 806, 836, + }; - internal static readonly ushort[] Pouch_ZCrystal_SM = { // Bead - 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, - }; + internal static readonly ushort[] Pouch_ZCrystal_USUM = ArrayUtil.ConcatAll(Pouch_ZCrystal_SM, new ushort[] { // Bead + 927, 928, 929, 930, 931, 932, + }); - internal static readonly ushort[] Pouch_ZCrystalHeld_SM = { // Piece - 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 798, 799, 800, 801, 802, 803, 804, 805, 806, 836, - }; + internal static readonly ushort[] Pouch_ZCrystalHeld_USUM = ArrayUtil.ConcatAll(Pouch_ZCrystalHeld_SM, new ushort[] { // Piece + 921, 922, 923, 924, 925, 926, + }); - internal static readonly ushort[] Pouch_ZCrystal_USUM = ArrayUtil.ConcatAll(Pouch_ZCrystal_SM, new ushort[] { // Bead - 927, 928, 929, 930, 931, 932, - }); + public static readonly Dictionary ZCrystalDictionary = GetDictionary(Pouch_ZCrystal_USUM, Pouch_ZCrystalHeld_USUM); - internal static readonly ushort[] Pouch_ZCrystalHeld_USUM = ArrayUtil.ConcatAll(Pouch_ZCrystalHeld_SM, new ushort[] { // Piece - 921, 922, 923, 924, 925, 926, - }); - - public static readonly Dictionary ZCrystalDictionary = GetDictionary(Pouch_ZCrystal_USUM, Pouch_ZCrystalHeld_USUM); - - private static Dictionary GetDictionary(IReadOnlyList key, IReadOnlyList held) - { - var result = new Dictionary(held.Count); - for (int i = 0; i < key.Count; i++) - result.Add(key[i], held[i]); - return result; - } - - internal static readonly ushort[] HeldItems_SM = ArrayUtil.ConcatAll(Pouch_Items_SM, Pouch_Berries_SM, Pouch_Medicine_SM, Pouch_ZCrystalHeld_SM); - internal static readonly ushort[] HeldItems_USUM = ArrayUtil.ConcatAll(Pouch_Items_SM, Pouch_Berries_SM, Pouch_Medicine_SM, Pouch_ZCrystalHeld_USUM, Pouch_Roto_USUM); - - internal static readonly HashSet AlolanOriginForms = new() - { - (int)Rattata, - (int)Raticate, - (int)Sandshrew, - (int)Sandslash, - (int)Vulpix, - (int)Ninetales, - (int)Diglett, - (int)Dugtrio, - (int)Meowth, - (int)Persian, - (int)Geodude, - (int)Graveler, - (int)Golem, - (int)Grimer, - (int)Muk, - }; - - internal static readonly HashSet AlolanVariantEvolutions12 = new() - { - (int)Raichu, - (int)Exeggutor, - (int)Marowak, - }; - - public static bool HasAlolanForm(int species) => AlolanOriginForms.Contains(species) || AlolanVariantEvolutions12.Contains(species); - - public static readonly HashSet PastGenAlolanNatives = new() - { - 010, 011, 012, 019, 020, 021, 022, 025, 026, 027, 028, 035, 036, 037, 038, 039, 040, 041, 042, 046, 047, 050, - 051, 052, 053, 054, 055, 056, 057, 058, 059, 060, 061, 062, 063, 064, 065, 066, 067, 068, 072, 073, 074, 075, - 076, 079, 080, 081, 082, 088, 089, 090, 091, 092, 093, 094, 096, 097, 102, 103, 104, 105, 113, 115, 118, 119, - 120, 121, 123, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 142, 143, 147, 148, 149, 165, - 166, 167, 168, 169, 170, 171, 172, 173, 174, 185, 186, 196, 197, 198, 199, 200, 209, 210, 212, 215, 222, 225, - 227, 233, 235, 239, 240, 241, 242, 278, 279, 283, 284, 296, 297, 299, 302, 318, 319, 320, 321, 324, 327, 328, - 329, 330, 339, 340, 349, 350, 351, 359, 361, 362, 369, 370, 371, 372, 373, 374, 375, 376, 408, 409, 410, 411, - 422, 423, 425, 426, 429, 430, 438, 440, 443, 444, 445, 446, 447, 448, 456, 457, 461, 462, 466, 467, 470, 471, - 474, 476, 478, 506, 507, 508, 524, 525, 526, 546, 547, 548, 549, 551, 552, 553, 564, 565, 566, 567, 568, 569, - 582, 583, 584, 587, 594, 627, 628, 629, 630, 661, 662, 663, 674, 675, 700, 703, 704, 705, 706, 707, 708, 709, - 718, - - // Regular - 023, 086, 108, 122, 138, 140, 163, 177, 179, 190, 204, - 206, 214, 223, 228, 238, 246, 303, 309, 341, 343, - 345, 347, 352, 353, 357, 366, 427, 439, 458, 550, - 559, 570, 572, 592, 605, 619, 621, 622, 624, 636, - 667, 669, 676, 686, 690, 692, 696, 698, 701, 702, - 714, - - // Wormhole - 333, 334, // Altaria - 193, 469, // Yanmega - 561, // Sigilyph - 580, 581, // Swanna - 276, 277, // Swellow - 451, 452, // Drapion - 531, // Audino - 694, 695, // Heliolisk - 273, 274, 275, // Nuzleaf - 325, 326, // Gumpig - 459, 460, // Abomasnow - 307, 308, // Medicham - 449, 450, // Hippowdon - 557, 558, // Crustle - 218, 219, // Magcargo - 688, 689, // Barbaracle - 270, 271, 272, // Lombre - 618, // Stunfisk - 418, 419, // Floatzel - 194, 195, // Quagsire - - 100, 101, // Voltorb & Electrode - }; - - internal static readonly int[] ZygardeMoves = - { - 245, // Extreme Speed - 349, // Dragon Dance - 614, // Thousand Arrows - 615, // Thousand Waves - 687, // Core Enforcer - }; - - internal static readonly HashSet Totem_Alolan = new() - { - (int)Raticate, // (Normal, Alolan, Totem) - (int)Marowak, // (Normal, Alolan, Totem) - (int)Mimikyu, // (Normal, Busted, Totem, Totem_Busted) - }; - - internal static readonly HashSet Totem_NoTransfer = new() - { - (int)Marowak, - (int)Araquanid, - (int)Togedemaru, - (int)Ribombee, - }; - - internal static readonly HashSet Totem_USUM = new() - { - (int)Raticate, - (int)Gumshoos, - //(int)Wishiwashi, - (int)Salazzle, - (int)Lurantis, - (int)Vikavolt, - (int)Mimikyu, - (int)Kommoo, - (int)Marowak, - (int)Araquanid, - (int)Togedemaru, - (int)Ribombee, - }; - - internal static readonly HashSet ValidMet_SM = new() - { - 006, 008, 010, 012, 014, 016, 018, 020, 022, 024, 026, 028, 030, 032, 034, 036, 038, 040, 042, 044, 046, 048, - 050, 052, 054, 056, 058, 060, 062, 064, 068, 070, 072, 074, 076, 078, 082, 084, 086, 088, 090, 092, 094, - 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, - 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, - - Locations.Pelago7, // 30016 - }; - - internal static readonly HashSet ValidMet_USUM = new(ValidMet_SM) - { - // 194, 195, 196, 197, // Unobtainable new Locations - 198, - 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, - }; - - internal static readonly int[] TMHM_SM = - { - 526, 337, 473, 347, 046, 092, 258, 339, 474, 237, - 241, 269, 058, 059, 063, 113, 182, 240, 355, 219, - 218, 076, 479, 085, 087, 089, 216, 141, 094, 247, - 280, 104, 115, 482, 053, 188, 201, 126, 317, 332, - 259, 263, 488, 156, 213, 168, 490, 496, 497, 315, - 211, 411, 412, 206, 503, 374, 451, 507, 693, 511, - 261, 512, 373, 153, 421, 371, 684, 416, 397, 694, - 444, 521, 086, 360, 014, 019, 244, 523, 524, 157, - 404, 525, 611, 398, 138, 447, 207, 214, 369, 164, - 430, 433, 528, 057, 555, 267, 399, 127, 605, 590, - - // No HMs - }; - - #region Unreleased Items - internal static readonly bool[] ReleasedHeldItems_7 = GetPermitList(MaxItemID_7_USUM, HeldItems_USUM, new ushort[] - { - 005, // Safari Ball - 016, // Cherish Ball - 064, // Fluffy Tail - 065, // Blue Flute - 066, // Yellow Flute - 067, // Red Flute - 068, // Black Flute - 069, // White Flute - 070, // Shoal Salt - 071, // Shoal Shell - 103, // Old Amber - 111, // Odd Keystone - 164, // Razz Berry - 166, // Nanab Berry - 167, // Wepear Berry - 175, // Cornn Berry - 176, // Magost Berry - 177, // Rabuta Berry - 178, // Nomel Berry - 179, // Spelon Berry - 180, // Pamtre Berry - 181, // Watmel Berry - 182, // Durin Berry - 183, // Belue Berry - //208, // Enigma Berry - //209, // Micle Berry - //210, // Custap Berry - //211, // Jaboca Berry - //212, // Rowap Berry - 215, // Macho Brace - 260, // Red Scarf - 261, // Blue Scarf - 262, // Pink Scarf - 263, // Green Scarf - 264, // Yellow Scarf - 499, // Sport Ball - 548, // Fire Gem - 549, // Water Gem - 550, // Electric Gem - 551, // Grass Gem - 552, // Ice Gem - 553, // Fighting Gem - 554, // Poison Gem - 555, // Ground Gem - 556, // Flying Gem - 557, // Psychic Gem - 558, // Bug Gem - 559, // Rock Gem - 560, // Ghost Gem - 561, // Dragon Gem - 562, // Dark Gem - 563, // Steel Gem - 576, // Dream Ball - 584, // Relic Copper - 585, // Relic Silver - 587, // Relic Vase - 588, // Relic Band - 589, // Relic Statue - 590, // Relic Crown - 699, // Discount Coupon - 715, // Fairy Gem - }); - #endregion + private static Dictionary GetDictionary(IReadOnlyList key, IReadOnlyList held) + { + var result = new Dictionary(held.Count); + for (int i = 0; i < key.Count; i++) + result.Add(key[i], held[i]); + return result; } + + internal static readonly ushort[] HeldItems_SM = ArrayUtil.ConcatAll(Pouch_Items_SM, Pouch_Berries_SM, Pouch_Medicine_SM, Pouch_ZCrystalHeld_SM); + internal static readonly ushort[] HeldItems_USUM = ArrayUtil.ConcatAll(Pouch_Items_SM, Pouch_Berries_SM, Pouch_Medicine_SM, Pouch_ZCrystalHeld_USUM, Pouch_Roto_USUM); + + internal static readonly HashSet AlolanOriginForms = new() + { + (int)Rattata, + (int)Raticate, + (int)Sandshrew, + (int)Sandslash, + (int)Vulpix, + (int)Ninetales, + (int)Diglett, + (int)Dugtrio, + (int)Meowth, + (int)Persian, + (int)Geodude, + (int)Graveler, + (int)Golem, + (int)Grimer, + (int)Muk, + }; + + internal static readonly HashSet AlolanVariantEvolutions12 = new() + { + (int)Raichu, + (int)Exeggutor, + (int)Marowak, + }; + + public static bool HasAlolanForm(int species) => AlolanOriginForms.Contains(species) || AlolanVariantEvolutions12.Contains(species); + + public static readonly HashSet PastGenAlolanNatives = new() + { + 010, 011, 012, 019, 020, 021, 022, 025, 026, 027, 028, 035, 036, 037, 038, 039, 040, 041, 042, 046, 047, 050, + 051, 052, 053, 054, 055, 056, 057, 058, 059, 060, 061, 062, 063, 064, 065, 066, 067, 068, 072, 073, 074, 075, + 076, 079, 080, 081, 082, 088, 089, 090, 091, 092, 093, 094, 096, 097, 102, 103, 104, 105, 113, 115, 118, 119, + 120, 121, 123, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 142, 143, 147, 148, 149, 165, + 166, 167, 168, 169, 170, 171, 172, 173, 174, 185, 186, 196, 197, 198, 199, 200, 209, 210, 212, 215, 222, 225, + 227, 233, 235, 239, 240, 241, 242, 278, 279, 283, 284, 296, 297, 299, 302, 318, 319, 320, 321, 324, 327, 328, + 329, 330, 339, 340, 349, 350, 351, 359, 361, 362, 369, 370, 371, 372, 373, 374, 375, 376, 408, 409, 410, 411, + 422, 423, 425, 426, 429, 430, 438, 440, 443, 444, 445, 446, 447, 448, 456, 457, 461, 462, 466, 467, 470, 471, + 474, 476, 478, 506, 507, 508, 524, 525, 526, 546, 547, 548, 549, 551, 552, 553, 564, 565, 566, 567, 568, 569, + 582, 583, 584, 587, 594, 627, 628, 629, 630, 661, 662, 663, 674, 675, 700, 703, 704, 705, 706, 707, 708, 709, + 718, + + // Regular + 023, 086, 108, 122, 138, 140, 163, 177, 179, 190, 204, + 206, 214, 223, 228, 238, 246, 303, 309, 341, 343, + 345, 347, 352, 353, 357, 366, 427, 439, 458, 550, + 559, 570, 572, 592, 605, 619, 621, 622, 624, 636, + 667, 669, 676, 686, 690, 692, 696, 698, 701, 702, + 714, + + // Wormhole + 333, 334, // Altaria + 193, 469, // Yanmega + 561, // Sigilyph + 580, 581, // Swanna + 276, 277, // Swellow + 451, 452, // Drapion + 531, // Audino + 694, 695, // Heliolisk + 273, 274, 275, // Nuzleaf + 325, 326, // Gumpig + 459, 460, // Abomasnow + 307, 308, // Medicham + 449, 450, // Hippowdon + 557, 558, // Crustle + 218, 219, // Magcargo + 688, 689, // Barbaracle + 270, 271, 272, // Lombre + 618, // Stunfisk + 418, 419, // Floatzel + 194, 195, // Quagsire + + 100, 101, // Voltorb & Electrode + }; + + internal static readonly int[] ZygardeMoves = + { + 245, // Extreme Speed + 349, // Dragon Dance + 614, // Thousand Arrows + 615, // Thousand Waves + 687, // Core Enforcer + }; + + internal static readonly HashSet Totem_Alolan = new() + { + (int)Raticate, // (Normal, Alolan, Totem) + (int)Marowak, // (Normal, Alolan, Totem) + (int)Mimikyu, // (Normal, Busted, Totem, Totem_Busted) + }; + + internal static readonly HashSet Totem_NoTransfer = new() + { + (int)Marowak, + (int)Araquanid, + (int)Togedemaru, + (int)Ribombee, + }; + + internal static readonly HashSet Totem_USUM = new() + { + (int)Raticate, + (int)Gumshoos, + //(int)Wishiwashi, + (int)Salazzle, + (int)Lurantis, + (int)Vikavolt, + (int)Mimikyu, + (int)Kommoo, + (int)Marowak, + (int)Araquanid, + (int)Togedemaru, + (int)Ribombee, + }; + + internal static readonly HashSet ValidMet_SM = new() + { + 006, 008, 010, 012, 014, 016, 018, 020, 022, 024, 026, 028, 030, 032, 034, 036, 038, 040, 042, 044, 046, 048, + 050, 052, 054, 056, 058, 060, 062, 064, 068, 070, 072, 074, 076, 078, 082, 084, 086, 088, 090, 092, 094, + 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, + 150, 152, 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, + + Locations.Pelago7, // 30016 + }; + + internal static readonly HashSet ValidMet_USUM = new(ValidMet_SM) + { + // 194, 195, 196, 197, // Unobtainable new Locations + 198, + 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, + }; + + internal static readonly int[] TMHM_SM = + { + 526, 337, 473, 347, 046, 092, 258, 339, 474, 237, + 241, 269, 058, 059, 063, 113, 182, 240, 355, 219, + 218, 076, 479, 085, 087, 089, 216, 141, 094, 247, + 280, 104, 115, 482, 053, 188, 201, 126, 317, 332, + 259, 263, 488, 156, 213, 168, 490, 496, 497, 315, + 211, 411, 412, 206, 503, 374, 451, 507, 693, 511, + 261, 512, 373, 153, 421, 371, 684, 416, 397, 694, + 444, 521, 086, 360, 014, 019, 244, 523, 524, 157, + 404, 525, 611, 398, 138, 447, 207, 214, 369, 164, + 430, 433, 528, 057, 555, 267, 399, 127, 605, 590, + + // No HMs + }; + + #region Unreleased Items + internal static readonly bool[] ReleasedHeldItems_7 = GetPermitList(MaxItemID_7_USUM, HeldItems_USUM, new ushort[] + { + 005, // Safari Ball + 016, // Cherish Ball + 064, // Fluffy Tail + 065, // Blue Flute + 066, // Yellow Flute + 067, // Red Flute + 068, // Black Flute + 069, // White Flute + 070, // Shoal Salt + 071, // Shoal Shell + 103, // Old Amber + 111, // Odd Keystone + 164, // Razz Berry + 166, // Nanab Berry + 167, // Wepear Berry + 175, // Cornn Berry + 176, // Magost Berry + 177, // Rabuta Berry + 178, // Nomel Berry + 179, // Spelon Berry + 180, // Pamtre Berry + 181, // Watmel Berry + 182, // Durin Berry + 183, // Belue Berry + //208, // Enigma Berry + //209, // Micle Berry + //210, // Custap Berry + //211, // Jaboca Berry + //212, // Rowap Berry + 215, // Macho Brace + 260, // Red Scarf + 261, // Blue Scarf + 262, // Pink Scarf + 263, // Green Scarf + 264, // Yellow Scarf + 499, // Sport Ball + 548, // Fire Gem + 549, // Water Gem + 550, // Electric Gem + 551, // Grass Gem + 552, // Ice Gem + 553, // Fighting Gem + 554, // Poison Gem + 555, // Ground Gem + 556, // Flying Gem + 557, // Psychic Gem + 558, // Bug Gem + 559, // Rock Gem + 560, // Ghost Gem + 561, // Dragon Gem + 562, // Dark Gem + 563, // Steel Gem + 576, // Dream Ball + 584, // Relic Copper + 585, // Relic Silver + 587, // Relic Vase + 588, // Relic Band + 589, // Relic Statue + 590, // Relic Crown + 699, // Discount Coupon + 715, // Fairy Gem + }); + #endregion } diff --git a/PKHeX.Core/Legality/Tables/Tables7b.cs b/PKHeX.Core/Legality/Tables/Tables7b.cs index 76de6cb69..a86b26760 100644 --- a/PKHeX.Core/Legality/Tables/Tables7b.cs +++ b/PKHeX.Core/Legality/Tables/Tables7b.cs @@ -1,219 +1,218 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + internal const int MaxSpeciesID_7b = 809; // Melmetal + internal const int MaxMoveID_7b = 742; // Double Iron Bash + internal const int MaxItemID_7b = 1057; // Magmar Candy + internal const int MaxBallID_7b = (int)Ball.Beast; + internal const int MaxGameID_7b = (int)GameVersion.GE; + internal const int MaxAbilityID_7b = MaxAbilityID_7_USUM; + internal static readonly ushort[] HeldItems_GG = Array.Empty(); + public const byte AwakeningMax = 200; + + #region Items + + internal static readonly ushort[] Pouch_Candy_GG_Regular = { - internal const int MaxSpeciesID_7b = 809; // Melmetal - internal const int MaxMoveID_7b = 742; // Double Iron Bash - internal const int MaxItemID_7b = 1057; // Magmar Candy - internal const int MaxBallID_7b = (int)Ball.Beast; - internal const int MaxGameID_7b = (int)GameVersion.GE; - internal const int MaxAbilityID_7b = MaxAbilityID_7_USUM; - internal static readonly ushort[] HeldItems_GG = Array.Empty(); - public const byte AwakeningMax = 200; + 050, // Rare Candy + 960, 961, 962, 963, 964, 965, // S + 966, 967, 968, 969, 970, 971, // L + 972, 973, 974, 975, 976, 977, // XL + }; - #region Items + internal static readonly ushort[] Pouch_Candy_GG_Species = + { + 978, 979, + 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, + 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, + 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, + 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, + 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, + 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, + 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, + 1050, 1051, 1052, 1053, 1054, 1055, 1056, + 1057, + }; - internal static readonly ushort[] Pouch_Candy_GG_Regular = - { - 050, // Rare Candy - 960, 961, 962, 963, 964, 965, // S - 966, 967, 968, 969, 970, 971, // L - 972, 973, 974, 975, 976, 977, // XL - }; + internal static readonly ushort[] Pouch_Candy_GG = ArrayUtil.ConcatAll(Pouch_Candy_GG_Regular, Pouch_Candy_GG_Species); - internal static readonly ushort[] Pouch_Candy_GG_Species = - { - 978, 979, - 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, - 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, - 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, - 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, - 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, - 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, - 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, - 1050, 1051, 1052, 1053, 1054, 1055, 1056, - 1057, - }; + internal static readonly ushort[] Pouch_Medicine_GG = + { + 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 038, 039, 040, 041, 709, 903, + }; - internal static readonly ushort[] Pouch_Candy_GG = ArrayUtil.ConcatAll(Pouch_Candy_GG_Regular, Pouch_Candy_GG_Species); + internal static readonly ushort[] Pouch_TM_GG = + { + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, + 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, + 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, + 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, + 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, + 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, + }; - internal static readonly ushort[] Pouch_Medicine_GG = - { - 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 038, 039, 040, 041, 709, 903, - }; + internal static readonly ushort[] Pouch_PowerUp_GG = + { + 051, 053, 081, 082, 083, 084, 085, + 849, + }; - internal static readonly ushort[] Pouch_TM_GG = - { - 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, - 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, - 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, - 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, - 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, - 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, - }; + internal static readonly ushort[] Pouch_Catching_GG = + { + 001, 002, 003, 004, 012, 164, 166, 168, + 861, 862, 863, 864, 865, 866, + }; - internal static readonly ushort[] Pouch_PowerUp_GG = - { - 051, 053, 081, 082, 083, 084, 085, - 849, - }; + internal static readonly ushort[] Pouch_Battle_GG = + { + 055, 056, 057, 058, 059, 060, 061, 062, + 656, 659, 660, 661, 662, 663, 671, 672, 675, 676, 678, 679, + 760, 762, 770, 773, + }; - internal static readonly ushort[] Pouch_Catching_GG = - { - 001, 002, 003, 004, 012, 164, 166, 168, - 861, 862, 863, 864, 865, 866, - }; + internal static readonly ushort[] Pouch_Regular_GG = + { + 076, 077, 078, 079, 086, 087, 088, 089, + 090, 091, 092, 093, 101, 102, 103, 113, 115, + 121, 122, 123, 124, 125, 126, 127, 128, + 442, + 571, + 632, 651, + 795, 796, + 872, 873, 874, 875, 876, 877, 878, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 900, 901, 902, + }; - internal static readonly ushort[] Pouch_Battle_GG = - { - 055, 056, 057, 058, 059, 060, 061, 062, - 656, 659, 660, 661, 662, 663, 671, 672, 675, 676, 678, 679, - 760, 762, 770, 773, - }; + internal static readonly ushort[] Pouch_Regular_GG_Item = + { + 076, // Super Repel + 077, // Max Repel + 078, // Escape Rope + 079, // Repel + 086, // Tiny Mushroom + 087, // Big Mushroom + 088, // Pearl + 089, // Big Pearl + 090, // Stardust + 091, // Star Piece + 092, // Nugget + 093, // Heart Scale - internal static readonly ushort[] Pouch_Regular_GG = - { - 076, 077, 078, 079, 086, 087, 088, 089, - 090, 091, 092, 093, 101, 102, 103, 113, 115, - 121, 122, 123, 124, 125, 126, 127, 128, - 442, - 571, - 632, 651, - 795, 796, - 872, 873, 874, 875, 876, 877, 878, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 900, 901, 902, - }; + 571, // Pretty Wing + 795, // Bottle Cap + 796, // Gold Bottle Cap - internal static readonly ushort[] Pouch_Regular_GG_Item = - { - 076, // Super Repel - 077, // Max Repel - 078, // Escape Rope - 079, // Repel - 086, // Tiny Mushroom - 087, // Big Mushroom - 088, // Pearl - 089, // Big Pearl - 090, // Stardust - 091, // Star Piece - 092, // Nugget - 093, // Heart Scale + 900, // Lure + 901, // Super Lure + 902, // Max Lure + }; - 571, // Pretty Wing - 795, // Bottle Cap - 796, // Gold Bottle Cap + internal static readonly ushort[] Pouch_Regular_GG_Key = + { + 113, // Tea + 115, // Autograph + 121, // Pokémon Box + 122, // Medicine Pocket + 123, // TM Case + 124, // Candy Jar + 125, // Power-Up Pocket + 126, // Clothing Trunk + 127, // Catching Pocket + 128, // Battle Pocket + 442, // Town Map + 632, // Shiny Charm + 651, // Poké Flute - 900, // Lure - 901, // Super Lure - 902, // Max Lure - }; + 872, // Secret Key + 873, // S.S. Ticket + 874, // Silph Scope + 875, // Parcel + 876, // Card Key + 877, // Gold Teeth + 878, // Lift Key + 885, // Stretchy Spring + 886, // Chalky Stone + 887, // Marble + 888, // Lone Earring + 889, // Beach Glass + 890, // Gold Leaf + 891, // Silver Leaf + 892, // Polished Mud Ball + 893, // Tropical Shell + 894, // Leaf Letter (P) + 895, // Leaf Letter (E) + 896, // Small Bouquet + }; - internal static readonly ushort[] Pouch_Regular_GG_Key = - { - 113, // Tea - 115, // Autograph - 121, // Pokémon Box - 122, // Medicine Pocket - 123, // TM Case - 124, // Candy Jar - 125, // Power-Up Pocket - 126, // Clothing Trunk - 127, // Catching Pocket - 128, // Battle Pocket - 442, // Town Map - 632, // Shiny Charm - 651, // Poké Flute + #endregion - 872, // Secret Key - 873, // S.S. Ticket - 874, // Silph Scope - 875, // Parcel - 876, // Card Key - 877, // Gold Teeth - 878, // Lift Key - 885, // Stretchy Spring - 886, // Chalky Stone - 887, // Marble - 888, // Lone Earring - 889, // Beach Glass - 890, // Gold Leaf - 891, // Silver Leaf - 892, // Polished Mud Ball - 893, // Tropical Shell - 894, // Leaf Letter (P) - 895, // Leaf Letter (E) - 896, // Small Bouquet - }; + #region Moves - #endregion + internal static readonly int[] TMHM_GG = + { + 029, 269, 270, 100, 156, 113, 182, 164, 115, 091, + 261, 263, 280, 019, 069, 086, 525, 369, 231, 399, + 492, 157, 009, 404, 127, 398, 092, 161, 503, 339, + 007, 605, 347, 406, 008, 085, 053, 087, 200, 094, + 089, 120, 247, 583, 076, 126, 057, 063, 276, 355, + 059, 188, 072, 430, 058, 446, 006, 529, 138, 224, + // rest are same as SM, unused - #region Moves + // No HMs + }; - internal static readonly int[] TMHM_GG = - { - 029, 269, 270, 100, 156, 113, 182, 164, 115, 091, - 261, 263, 280, 019, 069, 086, 525, 369, 231, 399, - 492, 157, 009, 404, 127, 398, 092, 161, 503, 339, - 007, 605, 347, 406, 008, 085, 053, 087, 200, 094, - 089, 120, 247, 583, 076, 126, 057, 063, 276, 355, - 059, 188, 072, 430, 058, 446, 006, 529, 138, 224, - // rest are same as SM, unused + internal static readonly int[] Tutor_StarterPikachu = + { + (int)Move.ZippyZap, + (int)Move.SplishySplash, + (int)Move.FloatyFall, + //(int)Move.PikaPapow, // Joycon Shake + }; - // No HMs - }; + internal static readonly int[] Tutor_StarterEevee = + { + (int)Move.BouncyBubble, + (int)Move.BuzzyBuzz, + (int)Move.SizzlySlide, + (int)Move.GlitzyGlow, + (int)Move.BaddyBad, + (int)Move.SappySeed, + (int)Move.FreezyFrost, + (int)Move.SparklySwirl, + //(int)Move.VeeveeVolley, // Joycon Shake + }; - internal static readonly int[] Tutor_StarterPikachu = - { - (int)Move.ZippyZap, - (int)Move.SplishySplash, - (int)Move.FloatyFall, - //(int)Move.PikaPapow, // Joycon Shake - }; + internal static readonly HashSet AllowedMovesGG = new() + { + 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, + 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, + 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, + 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, + 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, + 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, + 060, 061, 062, 063, 064, 065, 066, 067, 068, 069, + 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, + 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, + 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, + 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, - internal static readonly int[] Tutor_StarterEevee = - { - (int)Move.BouncyBubble, - (int)Move.BuzzyBuzz, - (int)Move.SizzlySlide, - (int)Move.GlitzyGlow, - (int)Move.BaddyBad, - (int)Move.SappySeed, - (int)Move.FreezyFrost, - (int)Move.SparklySwirl, - //(int)Move.VeeveeVolley, // Joycon Shake - }; + 182, 188, 200, 224, 227, 231, 242, 243, 247, 252, + 257, 261, 263, 269, 270, 276, 280, 281, 339, 347, + 355, 364, 369, 389, 394, 398, 399, 403, 404, 405, + 406, 417, 420, 430, 438, 446, 453, 483, 492, 499, + 503, 504, 525, 529, 583, 585, 603, 605, 606, 607, + 729, 730, 731, 733, 734, 735, 736, 737, 738, 739, + 740, 742, + }; - internal static readonly HashSet AllowedMovesGG = new() - { - 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, - 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, - 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, - 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, - 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, - 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, - 060, 061, 062, 063, 064, 065, 066, 067, 068, 069, - 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, - 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, - 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, - 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, - 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, - 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, - - 182, 188, 200, 224, 227, 231, 242, 243, 247, 252, - 257, 261, 263, 269, 270, 276, 280, 281, 339, 347, - 355, 364, 369, 389, 394, 398, 399, 403, 404, 405, - 406, 417, 420, 430, 438, 446, 453, 483, 492, 499, - 503, 504, 525, 529, 583, 585, 603, 605, 606, 607, - 729, 730, 731, 733, 734, 735, 736, 737, 738, 739, - 740, 742, - }; - - #endregion - } + #endregion } diff --git a/PKHeX.Core/Legality/Tables/Tables8.cs b/PKHeX.Core/Legality/Tables/Tables8.cs index e8a38b804..ee60ab993 100644 --- a/PKHeX.Core/Legality/Tables/Tables8.cs +++ b/PKHeX.Core/Legality/Tables/Tables8.cs @@ -1,38 +1,38 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + // Current Binaries + internal const int MaxSpeciesID_8 = MaxSpeciesID_8_R2; + internal const int MaxMoveID_8 = MaxMoveID_8_R2; + internal const int MaxItemID_8 = MaxItemID_8_R2; + internal const int MaxAbilityID_8 = MaxAbilityID_8_R2; + + // Orion (No DLC) + internal const int MaxSpeciesID_8_O0 = 890; // Eternatus + internal const int MaxMoveID_8_O0 = 796; // Steel Beam + internal const int MaxItemID_8_O0 = 1278; // Rotom Catalog, ignore all catalog parts + internal const int MaxAbilityID_8_O0 = 258; // Hunger Switch + + // Rigel 1 (DLC 1: Isle of Armor) + internal const int MaxSpeciesID_8_R1 = 893; // Zarude + internal const int MaxMoveID_8_R1 = 818; // Surging Strikes + internal const int MaxItemID_8_R1 = 1589; // Mark Charm + internal const int MaxAbilityID_8_R1 = 260; // Unseen Fist + + // Rigel 2 (DLC 2: Crown Tundra) + internal const int MaxSpeciesID_8_R2 = 898; // Calyrex + internal const int MaxMoveID_8_R2 = 826; // Eerie Spell + internal const int MaxItemID_8_R2 = 1607; // Reins of Unity + internal const int MaxAbilityID_8_R2 = 267; // As One (Glastrier) + + internal const int MaxBallID_8 = 0x1A; // 26 Beast + internal const int MaxGameID_8 = 45; // Shield + + internal static readonly ushort[] Pouch_Regular_SWSH = { - // Current Binaries - internal const int MaxSpeciesID_8 = MaxSpeciesID_8_R2; - internal const int MaxMoveID_8 = MaxMoveID_8_R2; - internal const int MaxItemID_8 = MaxItemID_8_R2; - internal const int MaxAbilityID_8 = MaxAbilityID_8_R2; - - // Orion (No DLC) - internal const int MaxSpeciesID_8_O0 = 890; // Eternatus - internal const int MaxMoveID_8_O0 = 796; // Steel Beam - internal const int MaxItemID_8_O0 = 1278; // Rotom Catalog, ignore all catalog parts - internal const int MaxAbilityID_8_O0 = 258; // Hunger Switch - - // Rigel 1 (DLC 1: Isle of Armor) - internal const int MaxSpeciesID_8_R1 = 893; // Zarude - internal const int MaxMoveID_8_R1 = 818; // Surging Strikes - internal const int MaxItemID_8_R1 = 1589; // Mark Charm - internal const int MaxAbilityID_8_R1 = 260; // Unseen Fist - - // Rigel 2 (DLC 2: Crown Tundra) - internal const int MaxSpeciesID_8_R2 = 898; // Calyrex - internal const int MaxMoveID_8_R2 = 826; // Eerie Spell - internal const int MaxItemID_8_R2 = 1607; // Reins of Unity - internal const int MaxAbilityID_8_R2 = 267; // As One (Glastrier) - - internal const int MaxBallID_8 = 0x1A; // 26 Beast - internal const int MaxGameID_8 = 45; // Shield - - internal static readonly ushort[] Pouch_Regular_SWSH = - { 045, 046, 047, 048, 049, 050, 051, 052, 053, 076, 077, 079, 080, 081, 082, 083, 084, 085, 107, 108, 109, 110, 112, 116, 117, 118, 119, 135, 136, 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, @@ -69,549 +69,548 @@ public static partial class Legal 1592, 1604, 1606, }; - internal static readonly ushort[] Pouch_Ball_SWSH = - { - 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, - 492, 493, 494, 495, 496, 497, 498, 499, 500, - 576, - 851, - }; + internal static readonly ushort[] Pouch_Ball_SWSH = + { + 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, + 492, 493, 494, 495, 496, 497, 498, 499, 500, + 576, + 851, + }; - internal static readonly ushort[] Pouch_Battle_SWSH = - { - 055, 056, 057, 058, 059, 060, 061, 062, 063, 1580, - }; + internal static readonly ushort[] Pouch_Battle_SWSH = + { + 055, 056, 057, 058, 059, 060, 061, 062, 063, 1580, + }; - internal static readonly ushort[] Pouch_Items_SWSH = ArrayUtil.ConcatAll(Pouch_Regular_SWSH, Pouch_Ball_SWSH, Pouch_Battle_SWSH); + internal static readonly ushort[] Pouch_Items_SWSH = ArrayUtil.ConcatAll(Pouch_Regular_SWSH, Pouch_Ball_SWSH, Pouch_Battle_SWSH); - internal static readonly ushort[] Pouch_Key_SWSH = - { - 078, - 628, 629, 631, 632, 638, - 703, - 847, - 943, 944, 945, 946, - 1074, 1075, 1076, 1077, 1080, 1081, 1100, 1255, 1266, 1267, - 1269, 1270, 1271, 1278, 1583, 1584, 1585, 1586, 1587, 1589, + internal static readonly ushort[] Pouch_Key_SWSH = + { + 078, + 628, 629, 631, 632, 638, + 703, + 847, + 943, 944, 945, 946, + 1074, 1075, 1076, 1077, 1080, 1081, 1100, 1255, 1266, 1267, + 1269, 1270, 1271, 1278, 1583, 1584, 1585, 1586, 1587, 1589, - // DLC 2 - 1590, 1591, 1593, 1594, 1595, 1596, 1597, 1598, 1599, 1600, 1601, 1602, 1603, 1605, 1607, - }; + // DLC 2 + 1590, 1591, 1593, 1594, 1595, 1596, 1597, 1598, 1599, 1600, 1601, 1602, 1603, 1605, 1607, + }; - internal static readonly ushort[] TM_SWSH = - { - 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, - 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, - 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, - 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, - 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, - 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, - 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, - 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, - 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, - 418, 419, 618, 619, 620, 690, 691, 692, 693, // TM99 - 1230, // TM00 - }; + internal static readonly ushort[] TM_SWSH = + { + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, + 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, + 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, + 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, + 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, + 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, + 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, + 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, + 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 618, 619, 620, 690, 691, 692, 693, // TM99 + 1230, // TM00 + }; - internal static readonly ushort[] TR_SWSH = - { - 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, - 1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, - 1150, 1151, 1152, 1153, 1154, 1155, 1156, 1157, 1158, 1159, - 1160, 1161, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, - 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177, 1178, 1179, - 1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, - 1190, 1191, 1192, 1193, 1194, 1195, 1196, 1197, 1198, 1199, - 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, - 1210, 1211, 1212, 1213, 1214, 1215, 1216, 1217, 1218, 1219, - 1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 1228, 1229, - }; + internal static readonly ushort[] TR_SWSH = + { + 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, + 1140, 1141, 1142, 1143, 1144, 1145, 1146, 1147, 1148, 1149, + 1150, 1151, 1152, 1153, 1154, 1155, 1156, 1157, 1158, 1159, + 1160, 1161, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, + 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177, 1178, 1179, + 1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, + 1190, 1191, 1192, 1193, 1194, 1195, 1196, 1197, 1198, 1199, + 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, + 1210, 1211, 1212, 1213, 1214, 1215, 1216, 1217, 1218, 1219, + 1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 1228, 1229, + }; - internal static readonly ushort[] Pouch_TMHM_SWSH = ArrayUtil.ConcatAll(TM_SWSH, TR_SWSH); + internal static readonly ushort[] Pouch_TMHM_SWSH = ArrayUtil.ConcatAll(TM_SWSH, TR_SWSH); - internal static readonly ushort[] Pouch_Medicine_SWSH = - { - 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, - 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, - 037, 038, 039, 040, 041, 042, 043, 054, - 134, - 504, 591, - 708, 709, - 852, 903, - 1579, - }; + internal static readonly ushort[] Pouch_Medicine_SWSH = + { + 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, + 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, + 037, 038, 039, 040, 041, 042, 043, 054, + 134, + 504, 591, + 708, 709, + 852, 903, + 1579, + }; - internal static readonly ushort[] Pouch_Berries_SWSH = - { - 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, - 159, 160, 161, 162, 163, 169, 170, 171, 172, 173, - 174, 184, 185, 186, 187, 188, 189, 190, 191, 192, - 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, - 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, - 686, 687, 688, - }; + internal static readonly ushort[] Pouch_Berries_SWSH = + { + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, + 159, 160, 161, 162, 163, 169, 170, 171, 172, 173, + 174, 184, 185, 186, 187, 188, 189, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, + 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, + 686, 687, 688, + }; - internal static readonly ushort[] Pouch_Ingredients_SWSH = - { - 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, - 1094, 1095, 1096, 1097, 1098, 1099, 1256, 1257, 1258, 1259, - 1260, 1261, 1262, 1263, 1264, - }; + internal static readonly ushort[] Pouch_Ingredients_SWSH = + { + 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, + 1094, 1095, 1096, 1097, 1098, 1099, 1256, 1257, 1258, 1259, + 1260, 1261, 1262, 1263, 1264, + }; - internal static readonly ushort[] Pouch_Treasure_SWSH = - { - 086, 087, 088, 089, 090, 091, 092, 094, 106, - 571, 580, 581, 582, 583, - 795, 796, - 1105, 1106, 1107, 1108, - }; + internal static readonly ushort[] Pouch_Treasure_SWSH = + { + 086, 087, 088, 089, 090, 091, 092, 094, 106, + 571, 580, 581, 582, 583, + 795, 796, + 1105, 1106, 1107, 1108, + }; - internal static readonly ushort[] HeldItems_SWSH = ArrayUtil.ConcatAll(Pouch_Items_SWSH, Pouch_Berries_SWSH, Pouch_Medicine_SWSH, TR_SWSH, Pouch_Treasure_SWSH, Pouch_Ingredients_SWSH); + internal static readonly ushort[] HeldItems_SWSH = ArrayUtil.ConcatAll(Pouch_Items_SWSH, Pouch_Berries_SWSH, Pouch_Medicine_SWSH, TR_SWSH, Pouch_Treasure_SWSH, Pouch_Ingredients_SWSH); - internal static readonly int[] Tutors_SWSH_1 = - { - 805, 807, 812, 804, - 803, 813, 811, 810, - 815, 814, 797, 806, - 800, 809, 799, 808, - 798, 802, - }; + internal static readonly int[] Tutors_SWSH_1 = + { + 805, 807, 812, 804, + 803, 813, 811, 810, + 815, 814, 797, 806, + 800, 809, 799, 808, + 798, 802, + }; - internal static readonly HashSet GalarOriginForms = new() - { - (int)Species.Meowth, - (int)Species.Ponyta, - (int)Species.Rapidash, - (int)Species.Slowpoke, - (int)Species.Farfetchd, - (int)Species.MrMime, - (int)Species.Corsola, - (int)Species.Zigzagoon, - (int)Species.Linoone, - (int)Species.Yamask, - (int)Species.Darumaka, - (int)Species.Darmanitan, - (int)Species.Stunfisk, - }; + internal static readonly HashSet GalarOriginForms = new() + { + (int)Species.Meowth, + (int)Species.Ponyta, + (int)Species.Rapidash, + (int)Species.Slowpoke, + (int)Species.Farfetchd, + (int)Species.MrMime, + (int)Species.Corsola, + (int)Species.Zigzagoon, + (int)Species.Linoone, + (int)Species.Yamask, + (int)Species.Darumaka, + (int)Species.Darmanitan, + (int)Species.Stunfisk, + }; - internal static readonly HashSet GalarVariantFormEvolutions = new() - { - (int)Species.MrMime, - (int)Species.Weezing, - }; + internal static readonly HashSet GalarVariantFormEvolutions = new() + { + (int)Species.MrMime, + (int)Species.Weezing, + }; - internal static readonly IReadOnlyDictionary GalarForm0Evolutions = new Dictionary - { - {(int)Species.Perrserker, 2}, - {(int)Species.Obstagoon, 1}, - {(int)Species.MrRime, 1}, - {(int)Species.Sirfetchd, 2}, - {(int)Species.Runerigus, 1}, - {(int)Species.Cursola, 1}, - }; + internal static readonly IReadOnlyDictionary GalarForm0Evolutions = new Dictionary + { + {(int)Species.Perrserker, 2}, + {(int)Species.Obstagoon, 1}, + {(int)Species.MrRime, 1}, + {(int)Species.Sirfetchd, 2}, + {(int)Species.Runerigus, 1}, + {(int)Species.Cursola, 1}, + }; - internal static readonly HashSet ValidMet_SWSH = new() - { - 006, 008, - 012, 014, 016, 018, - 020, 022, 024, 028, - 030, 032, 034, 036, - 040, 044, 046, 048, - 052, 054, 056, 058, - 060, 064, 066, 068, - 070, 072, 076, 078, - 080, 084, 086, 088, - 090, 092, 094, 096, 098, - 102, 104, 106, 108, - 110, 112, 114, 116, 118, - 120, 122, 124, 126, 128, - 130, 132, 134, 136, 138, - 140, 142, 144, 146, 148, - 150, 152, 154, 156, 158, - 160, 164, 166, 168, - 170, 172, 174, 176, 178, - 180, 182, 184, 186, 188, - 190, 192, 194, 196, 198, - 200, + internal static readonly HashSet ValidMet_SWSH = new() + { + 006, 008, + 012, 014, 016, 018, + 020, 022, 024, 028, + 030, 032, 034, 036, + 040, 044, 046, 048, + 052, 054, 056, 058, + 060, 064, 066, 068, + 070, 072, 076, 078, + 080, 084, 086, 088, + 090, 092, 094, 096, 098, + 102, 104, 106, 108, + 110, 112, 114, 116, 118, + 120, 122, 124, 126, 128, + 130, 132, 134, 136, 138, + 140, 142, 144, 146, 148, + 150, 152, 154, 156, 158, + 160, 164, 166, 168, + 170, 172, 174, 176, 178, + 180, 182, 184, 186, 188, + 190, 192, 194, 196, 198, + 200, - 202, 204, 206, 208, 210, - 212, 214, 216, 218, 220, - 222, 224, 226, 228, 230, - 232, 234, 236, 238, 240, - 242, 244, 246, - }; + 202, 204, 206, 208, 210, + 212, 214, 216, 218, 220, + 222, 224, 226, 228, 230, + 232, 234, 236, 238, 240, + 242, 244, 246, + }; - public static readonly int[] TMHM_SWSH = - { - // TM - 005, 025, 006, 007, 008, 009, 019, 042, 063, 416, - 345, 076, 669, 083, 086, 091, 103, 113, 115, 219, - 120, 156, 157, 168, 173, 182, 184, 196, 202, 204, - 211, 213, 201, 240, 241, 258, 250, 251, 261, 263, - 129, 270, 279, 280, 286, 291, 311, 313, 317, 328, - 331, 333, 340, 341, 350, 362, 369, 371, 372, 374, - 384, 385, 683, 409, 419, 421, 422, 423, 424, 427, - 433, 472, 478, 440, 474, 490, 496, 506, 512, 514, - 521, 523, 527, 534, 541, 555, 566, 577, 580, 581, - 604, 678, 595, 598, 206, 403, 684, 693, 707, 784, + public static readonly int[] TMHM_SWSH = + { + // TM + 005, 025, 006, 007, 008, 009, 019, 042, 063, 416, + 345, 076, 669, 083, 086, 091, 103, 113, 115, 219, + 120, 156, 157, 168, 173, 182, 184, 196, 202, 204, + 211, 213, 201, 240, 241, 258, 250, 251, 261, 263, + 129, 270, 279, 280, 286, 291, 311, 313, 317, 328, + 331, 333, 340, 341, 350, 362, 369, 371, 372, 374, + 384, 385, 683, 409, 419, 421, 422, 423, 424, 427, + 433, 472, 478, 440, 474, 490, 496, 506, 512, 514, + 521, 523, 527, 534, 541, 555, 566, 577, 580, 581, + 604, 678, 595, 598, 206, 403, 684, 693, 707, 784, - // TR - 014, 034, 053, 056, 057, 058, 059, 067, 085, 087, - 089, 094, 097, 116, 118, 126, 127, 133, 141, 161, - 164, 179, 188, 191, 200, 473, 203, 214, 224, 226, - 227, 231, 242, 247, 248, 253, 257, 269, 271, 276, - 285, 299, 304, 315, 322, 330, 334, 337, 339, 347, - 348, 349, 360, 370, 390, 394, 396, 398, 399, 402, - 404, 405, 406, 408, 411, 412, 413, 414, 417, 428, - 430, 437, 438, 441, 442, 444, 446, 447, 482, 484, - 486, 492, 500, 502, 503, 526, 528, 529, 535, 542, - 583, 599, 605, 663, 667, 675, 676, 706, 710, 776, - }; + // TR + 014, 034, 053, 056, 057, 058, 059, 067, 085, 087, + 089, 094, 097, 116, 118, 126, 127, 133, 141, 161, + 164, 179, 188, 191, 200, 473, 203, 214, 224, 226, + 227, 231, 242, 247, 248, 253, 257, 269, 271, 276, + 285, 299, 304, 315, 322, 330, 334, 337, 339, 347, + 348, 349, 360, 370, 390, 394, 396, 398, 399, 402, + 404, 405, 406, 408, 411, 412, 413, 414, 417, 428, + 430, 437, 438, 441, 442, 444, 446, 447, 482, 484, + 486, 492, 500, 502, 503, 526, 528, 529, 535, 542, + 583, 599, 605, 663, 667, 675, 676, 706, 710, 776, + }; - #region Unreleased Items + #region Unreleased Items - private const int DMAX_START = 1279; - private const int DMAX_END = 1578; - private const int DMAX_LEGAL_END = 1290; // ★Sgr7194 (Eevee) - public static bool IsDynamaxCrystal(ushort item) => item is >= DMAX_START and <= DMAX_END; - public static bool IsDynamaxCrystalAvailable(ushort item) => item is >= DMAX_START and <= DMAX_LEGAL_END; + private const int DMAX_START = 1279; + private const int DMAX_END = 1578; + private const int DMAX_LEGAL_END = 1290; // ★Sgr7194 (Eevee) + public static bool IsDynamaxCrystal(ushort item) => item is >= DMAX_START and <= DMAX_END; + public static bool IsDynamaxCrystalAvailable(ushort item) => item is >= DMAX_START and <= DMAX_LEGAL_END; - internal static readonly bool[] ReleasedHeldItems_8 = GetPermitList(MaxItemID_8, HeldItems_SWSH, new ushort[] - { - 298, // Flame Plate - 299, // Splash Plate - 300, // Zap Plate - 301, // Meadow Plate - 302, // Icicle Plate - 303, // Fist Plate - 304, // Toxic Plate - 305, // Earth Plate - 306, // Sky Plate - 307, // Mind Plate - 308, // Insect Plate - 309, // Stone Plate - 310, // Spooky Plate - 311, // Draco Plate - 312, // Dread Plate - 313, // Iron Plate - // 644, // Pixie Plate + internal static readonly bool[] ReleasedHeldItems_8 = GetPermitList(MaxItemID_8, HeldItems_SWSH, new ushort[] + { + 298, // Flame Plate + 299, // Splash Plate + 300, // Zap Plate + 301, // Meadow Plate + 302, // Icicle Plate + 303, // Fist Plate + 304, // Toxic Plate + 305, // Earth Plate + 306, // Sky Plate + 307, // Mind Plate + 308, // Insect Plate + 309, // Stone Plate + 310, // Spooky Plate + 311, // Draco Plate + 312, // Dread Plate + 313, // Iron Plate + // 644, // Pixie Plate - 1279, // ★And458 (Jangmo-o) - 1280, // ★And15 (Larvitar) - 1281, // ★And337 (Corviknight) - 1282, // ★And603 (Eiscue) - 1283, // ★And390 (Stonjourner) - 1284, // ★Sgr6879 (Copperajah) - 1285, // ★Sgr6859 (Centiskorch) - 1286, // ★Sgr6913 (Flapple/Appletun) - 1287, // ★Sgr7348 (Sandaconda) - 1288, // ★Sgr7121 (Duraludon) - 1289, // ★Sgr6746 (Pikachu) - 1290, // ★Sgr7194 (Eevee) - 1291, // ★Sgr7337 - 1292, // ★Sgr7343 - 1293, // ★Sgr6812 - 1294, // ★Sgr7116 - 1295, // ★Sgr7264 - 1296, // ★Sgr7597 - 1297, // ★Del7882 - 1298, // ★Del7906 - 1299, // ★Del7852 - 1300, // ★Psc596 - 1301, // ★Psc361 - 1302, // ★Psc510 - 1303, // ★Psc437 - 1304, // ★Psc8773 - 1305, // ★Lep1865 - 1306, // ★Lep1829 - 1307, // ★Boo5340 - 1308, // ★Boo5506 - 1309, // ★Boo5435 - 1310, // ★Boo5602 - 1311, // ★Boo5733 - 1312, // ★Boo5235 - 1313, // ★Boo5351 - 1314, // ★Hya3748 - 1315, // ★Hya3903 - 1316, // ★Hya3418 - 1317, // ★Hya3482 - 1318, // ★Hya3845 - 1319, // ★Eri1084 - 1320, // ★Eri472 - 1321, // ★Eri1666 - 1322, // ★Eri897 - 1323, // ★Eri1231 - 1324, // ★Eri874 - 1325, // ★Eri1298 - 1326, // ★Eri1325 - 1327, // ★Eri984 - 1328, // ★Eri1464 - 1329, // ★Eri1393 - 1330, // ★Eri850 - 1331, // ★Tau1409 - 1332, // ★Tau1457 - 1333, // ★Tau1165 - 1334, // ★Tau1791 - 1335, // ★Tau1910 - 1336, // ★Tau1346 - 1337, // ★Tau1373 - 1338, // ★Tau1412 - 1339, // ★CMa2491 - 1340, // ★CMa2693 - 1341, // ★CMa2294 - 1342, // ★CMa2827 - 1343, // ★CMa2282 - 1344, // ★CMa2618 - 1345, // ★CMa2657 - 1346, // ★CMa2646 - 1347, // ★UMa4905 - 1348, // ★UMa4301 - 1349, // ★UMa5191 - 1350, // ★UMa5054 - 1351, // ★UMa4295 - 1352, // ★UMa4660 - 1353, // ★UMa4554 - 1354, // ★UMa4069 - 1355, // ★UMa3569 - 1356, // ★UMa3323 - 1357, // ★UMa4033 - 1358, // ★UMa4377 - 1359, // ★UMa4375 - 1360, // ★UMa4518 - 1361, // ★UMa3594 - 1362, // ★Vir5056 - 1363, // ★Vir4825 - 1364, // ★Vir4932 - 1365, // ★Vir4540 - 1366, // ★Vir4689 - 1367, // ★Vir5338 - 1368, // ★Vir4910 - 1369, // ★Vir5315 - 1370, // ★Vir5359 - 1371, // ★Vir5409 - 1372, // ★Vir5107 - 1373, // ★Ari617 - 1374, // ★Ari553 - 1375, // ★Ari546 - 1376, // ★Ari951 - 1377, // ★Ori1713 - 1378, // ★Ori2061 - 1379, // ★Ori1790 - 1380, // ★Ori1903 - 1381, // ★Ori1948 - 1382, // ★Ori2004 - 1383, // ★Ori1852 - 1384, // ★Ori1879 - 1385, // ★Ori1899 - 1386, // ★Ori1543 - 1387, // ★Cas21 - 1388, // ★Cas168 - 1389, // ★Cas403 - 1390, // ★Cas153 - 1391, // ★Cas542 - 1392, // ★Cas219 - 1393, // ★Cas265 - 1394, // ★Cnc3572 - 1395, // ★Cnc3208 - 1396, // ★Cnc3461 - 1397, // ★Cnc3449 - 1398, // ★Cnc3429 - 1399, // ★Cnc3627 - 1400, // ★Cnc3268 - 1401, // ★Cnc3249 - 1402, // ★Com4968 - 1403, // ★Crv4757 - 1404, // ★Crv4623 - 1405, // ★Crv4662 - 1406, // ★Crv4786 - 1407, // ★Aur1708 - 1408, // ★Aur2088 - 1409, // ★Aur1605 - 1410, // ★Aur2095 - 1411, // ★Aur1577 - 1412, // ★Aur1641 - 1413, // ★Aur1612 - 1414, // ★Pav7790 - 1415, // ★Cet911 - 1416, // ★Cet681 - 1417, // ★Cet188 - 1418, // ★Cet539 - 1419, // ★Cet804 - 1420, // ★Cep8974 - 1421, // ★Cep8162 - 1422, // ★Cep8238 - 1423, // ★Cep8417 - 1424, // ★Cen5267 - 1425, // ★Cen5288 - 1426, // ★Cen551 - 1427, // ★Cen5459 - 1428, // ★Cen5460 - 1429, // ★CMi2943 - 1430, // ★CMi2845 - 1431, // ★Equ8131 - 1432, // ★Vul7405 - 1433, // ★UMi424 - 1434, // ★UMi5563 - 1435, // ★UMi5735 - 1436, // ★UMi6789 - 1437, // ★Crt4287 - 1438, // ★Lyr7001 - 1439, // ★Lyr7178 - 1440, // ★Lyr7106 - 1441, // ★Lyr7298 - 1442, // ★Ara6585 - 1443, // ★Sco6134 - 1444, // ★Sco6527 - 1445, // ★Sco6553 - 1446, // ★Sco5953 - 1447, // ★Sco5984 - 1448, // ★Sco6508 - 1449, // ★Sco6084 - 1450, // ★Sco5944 - 1451, // ★Sco6630 - 1452, // ★Sco6027 - 1453, // ★Sco6247 - 1454, // ★Sco6252 - 1455, // ★Sco5928 - 1456, // ★Sco6241 - 1457, // ★Sco6165 - 1458, // ★Tri544 - 1459, // ★Leo3982 - 1460, // ★Leo4534 - 1461, // ★Leo4357 - 1462, // ★Leo4057 - 1463, // ★Leo4359 - 1464, // ★Leo4031 - 1465, // ★Leo3852 - 1466, // ★Leo3905 - 1467, // ★Leo3773 - 1468, // ★Gru8425 - 1469, // ★Gru8636 - 1470, // ★Gru8353 - 1471, // ★Lib5685 - 1472, // ★Lib5531 - 1473, // ★Lib5787 - 1474, // ★Lib5603 - 1475, // ★Pup3165 - 1476, // ★Pup3185 - 1477, // ★Pup3045 - 1478, // ★Cyg7924 - 1479, // ★Cyg7417 - 1480, // ★Cyg7796 - 1481, // ★Cyg8301 - 1482, // ★Cyg7949 - 1483, // ★Cyg7528 - 1484, // ★Oct7228 - 1485, // ★Col1956 - 1486, // ★Col2040 - 1487, // ★Col2177 - 1488, // ★Gem2990 - 1489, // ★Gem2891 - 1490, // ★Gem2421 - 1491, // ★Gem2473 - 1492, // ★Gem2216 - 1493, // ★Gem2777 - 1494, // ★Gem2650 - 1495, // ★Gem2286 - 1496, // ★Gem2484 - 1497, // ★Gem2930 - 1498, // ★Peg8775 - 1499, // ★Peg8781 - 1500, // ★Peg39 - 1501, // ★Peg8308 - 1502, // ★Peg8650 - 1503, // ★Peg8634 - 1504, // ★Peg8684 - 1505, // ★Peg8450 - 1506, // ★Peg8880 - 1507, // ★Peg8905 - 1508, // ★Oph6556 - 1509, // ★Oph6378 - 1510, // ★Oph6603 - 1511, // ★Oph6149 - 1512, // ★Oph6056 - 1513, // ★Oph6075 - 1514, // ★Ser5854 - 1515, // ★Ser7141 - 1516, // ★Ser5879 - 1517, // ★Her6406 - 1518, // ★Her6148 - 1519, // ★Her6410 - 1520, // ★Her6526 - 1521, // ★Her6117 - 1522, // ★Her6008 - 1523, // ★Per936 - 1524, // ★Per1017 - 1525, // ★Per1131 - 1526, // ★Per1228 - 1527, // ★Per834 - 1528, // ★Per941 - 1529, // ★Phe99 - 1530, // ★Phe338 - 1531, // ★Vel3634 - 1532, // ★Vel3485 - 1533, // ★Vel3734 - 1534, // ★Aqr8232 - 1535, // ★Aqr8414 - 1536, // ★Aqr8709 - 1537, // ★Aqr8518 - 1538, // ★Aqr7950 - 1539, // ★Aqr8499 - 1540, // ★Aqr8610 - 1541, // ★Aqr8264 - 1542, // ★Cru4853 - 1543, // ★Cru4730 - 1544, // ★Cru4763 - 1545, // ★Cru4700 - 1546, // ★Cru4656 - 1547, // ★PsA8728 - 1548, // ★TrA6217 - 1549, // ★Cap7776 - 1550, // ★Cap7754 - 1551, // ★Cap8278 - 1552, // ★Cap8322 - 1553, // ★Cap7773 - 1554, // ★Sge7479 - 1555, // ★Car2326 - 1556, // ★Car3685 - 1557, // ★Car3307 - 1558, // ★Car3699 - 1559, // ★Dra5744 - 1560, // ★Dra5291 - 1561, // ★Dra6705 - 1562, // ★Dra6536 - 1563, // ★Dra7310 - 1564, // ★Dra6688 - 1565, // ★Dra4434 - 1566, // ★Dra6370 - 1567, // ★Dra7462 - 1568, // ★Dra6396 - 1569, // ★Dra6132 - 1570, // ★Dra6636 - 1571, // ★CVn4915 - 1572, // ★CVn4785 - 1573, // ★CVn4846 - 1574, // ★Aql7595 - 1575, // ★Aql7557 - 1576, // ★Aql7525 - 1577, // ★Aql7602 - 1578, // ★Aql7235 + 1279, // ★And458 (Jangmo-o) + 1280, // ★And15 (Larvitar) + 1281, // ★And337 (Corviknight) + 1282, // ★And603 (Eiscue) + 1283, // ★And390 (Stonjourner) + 1284, // ★Sgr6879 (Copperajah) + 1285, // ★Sgr6859 (Centiskorch) + 1286, // ★Sgr6913 (Flapple/Appletun) + 1287, // ★Sgr7348 (Sandaconda) + 1288, // ★Sgr7121 (Duraludon) + 1289, // ★Sgr6746 (Pikachu) + 1290, // ★Sgr7194 (Eevee) + 1291, // ★Sgr7337 + 1292, // ★Sgr7343 + 1293, // ★Sgr6812 + 1294, // ★Sgr7116 + 1295, // ★Sgr7264 + 1296, // ★Sgr7597 + 1297, // ★Del7882 + 1298, // ★Del7906 + 1299, // ★Del7852 + 1300, // ★Psc596 + 1301, // ★Psc361 + 1302, // ★Psc510 + 1303, // ★Psc437 + 1304, // ★Psc8773 + 1305, // ★Lep1865 + 1306, // ★Lep1829 + 1307, // ★Boo5340 + 1308, // ★Boo5506 + 1309, // ★Boo5435 + 1310, // ★Boo5602 + 1311, // ★Boo5733 + 1312, // ★Boo5235 + 1313, // ★Boo5351 + 1314, // ★Hya3748 + 1315, // ★Hya3903 + 1316, // ★Hya3418 + 1317, // ★Hya3482 + 1318, // ★Hya3845 + 1319, // ★Eri1084 + 1320, // ★Eri472 + 1321, // ★Eri1666 + 1322, // ★Eri897 + 1323, // ★Eri1231 + 1324, // ★Eri874 + 1325, // ★Eri1298 + 1326, // ★Eri1325 + 1327, // ★Eri984 + 1328, // ★Eri1464 + 1329, // ★Eri1393 + 1330, // ★Eri850 + 1331, // ★Tau1409 + 1332, // ★Tau1457 + 1333, // ★Tau1165 + 1334, // ★Tau1791 + 1335, // ★Tau1910 + 1336, // ★Tau1346 + 1337, // ★Tau1373 + 1338, // ★Tau1412 + 1339, // ★CMa2491 + 1340, // ★CMa2693 + 1341, // ★CMa2294 + 1342, // ★CMa2827 + 1343, // ★CMa2282 + 1344, // ★CMa2618 + 1345, // ★CMa2657 + 1346, // ★CMa2646 + 1347, // ★UMa4905 + 1348, // ★UMa4301 + 1349, // ★UMa5191 + 1350, // ★UMa5054 + 1351, // ★UMa4295 + 1352, // ★UMa4660 + 1353, // ★UMa4554 + 1354, // ★UMa4069 + 1355, // ★UMa3569 + 1356, // ★UMa3323 + 1357, // ★UMa4033 + 1358, // ★UMa4377 + 1359, // ★UMa4375 + 1360, // ★UMa4518 + 1361, // ★UMa3594 + 1362, // ★Vir5056 + 1363, // ★Vir4825 + 1364, // ★Vir4932 + 1365, // ★Vir4540 + 1366, // ★Vir4689 + 1367, // ★Vir5338 + 1368, // ★Vir4910 + 1369, // ★Vir5315 + 1370, // ★Vir5359 + 1371, // ★Vir5409 + 1372, // ★Vir5107 + 1373, // ★Ari617 + 1374, // ★Ari553 + 1375, // ★Ari546 + 1376, // ★Ari951 + 1377, // ★Ori1713 + 1378, // ★Ori2061 + 1379, // ★Ori1790 + 1380, // ★Ori1903 + 1381, // ★Ori1948 + 1382, // ★Ori2004 + 1383, // ★Ori1852 + 1384, // ★Ori1879 + 1385, // ★Ori1899 + 1386, // ★Ori1543 + 1387, // ★Cas21 + 1388, // ★Cas168 + 1389, // ★Cas403 + 1390, // ★Cas153 + 1391, // ★Cas542 + 1392, // ★Cas219 + 1393, // ★Cas265 + 1394, // ★Cnc3572 + 1395, // ★Cnc3208 + 1396, // ★Cnc3461 + 1397, // ★Cnc3449 + 1398, // ★Cnc3429 + 1399, // ★Cnc3627 + 1400, // ★Cnc3268 + 1401, // ★Cnc3249 + 1402, // ★Com4968 + 1403, // ★Crv4757 + 1404, // ★Crv4623 + 1405, // ★Crv4662 + 1406, // ★Crv4786 + 1407, // ★Aur1708 + 1408, // ★Aur2088 + 1409, // ★Aur1605 + 1410, // ★Aur2095 + 1411, // ★Aur1577 + 1412, // ★Aur1641 + 1413, // ★Aur1612 + 1414, // ★Pav7790 + 1415, // ★Cet911 + 1416, // ★Cet681 + 1417, // ★Cet188 + 1418, // ★Cet539 + 1419, // ★Cet804 + 1420, // ★Cep8974 + 1421, // ★Cep8162 + 1422, // ★Cep8238 + 1423, // ★Cep8417 + 1424, // ★Cen5267 + 1425, // ★Cen5288 + 1426, // ★Cen551 + 1427, // ★Cen5459 + 1428, // ★Cen5460 + 1429, // ★CMi2943 + 1430, // ★CMi2845 + 1431, // ★Equ8131 + 1432, // ★Vul7405 + 1433, // ★UMi424 + 1434, // ★UMi5563 + 1435, // ★UMi5735 + 1436, // ★UMi6789 + 1437, // ★Crt4287 + 1438, // ★Lyr7001 + 1439, // ★Lyr7178 + 1440, // ★Lyr7106 + 1441, // ★Lyr7298 + 1442, // ★Ara6585 + 1443, // ★Sco6134 + 1444, // ★Sco6527 + 1445, // ★Sco6553 + 1446, // ★Sco5953 + 1447, // ★Sco5984 + 1448, // ★Sco6508 + 1449, // ★Sco6084 + 1450, // ★Sco5944 + 1451, // ★Sco6630 + 1452, // ★Sco6027 + 1453, // ★Sco6247 + 1454, // ★Sco6252 + 1455, // ★Sco5928 + 1456, // ★Sco6241 + 1457, // ★Sco6165 + 1458, // ★Tri544 + 1459, // ★Leo3982 + 1460, // ★Leo4534 + 1461, // ★Leo4357 + 1462, // ★Leo4057 + 1463, // ★Leo4359 + 1464, // ★Leo4031 + 1465, // ★Leo3852 + 1466, // ★Leo3905 + 1467, // ★Leo3773 + 1468, // ★Gru8425 + 1469, // ★Gru8636 + 1470, // ★Gru8353 + 1471, // ★Lib5685 + 1472, // ★Lib5531 + 1473, // ★Lib5787 + 1474, // ★Lib5603 + 1475, // ★Pup3165 + 1476, // ★Pup3185 + 1477, // ★Pup3045 + 1478, // ★Cyg7924 + 1479, // ★Cyg7417 + 1480, // ★Cyg7796 + 1481, // ★Cyg8301 + 1482, // ★Cyg7949 + 1483, // ★Cyg7528 + 1484, // ★Oct7228 + 1485, // ★Col1956 + 1486, // ★Col2040 + 1487, // ★Col2177 + 1488, // ★Gem2990 + 1489, // ★Gem2891 + 1490, // ★Gem2421 + 1491, // ★Gem2473 + 1492, // ★Gem2216 + 1493, // ★Gem2777 + 1494, // ★Gem2650 + 1495, // ★Gem2286 + 1496, // ★Gem2484 + 1497, // ★Gem2930 + 1498, // ★Peg8775 + 1499, // ★Peg8781 + 1500, // ★Peg39 + 1501, // ★Peg8308 + 1502, // ★Peg8650 + 1503, // ★Peg8634 + 1504, // ★Peg8684 + 1505, // ★Peg8450 + 1506, // ★Peg8880 + 1507, // ★Peg8905 + 1508, // ★Oph6556 + 1509, // ★Oph6378 + 1510, // ★Oph6603 + 1511, // ★Oph6149 + 1512, // ★Oph6056 + 1513, // ★Oph6075 + 1514, // ★Ser5854 + 1515, // ★Ser7141 + 1516, // ★Ser5879 + 1517, // ★Her6406 + 1518, // ★Her6148 + 1519, // ★Her6410 + 1520, // ★Her6526 + 1521, // ★Her6117 + 1522, // ★Her6008 + 1523, // ★Per936 + 1524, // ★Per1017 + 1525, // ★Per1131 + 1526, // ★Per1228 + 1527, // ★Per834 + 1528, // ★Per941 + 1529, // ★Phe99 + 1530, // ★Phe338 + 1531, // ★Vel3634 + 1532, // ★Vel3485 + 1533, // ★Vel3734 + 1534, // ★Aqr8232 + 1535, // ★Aqr8414 + 1536, // ★Aqr8709 + 1537, // ★Aqr8518 + 1538, // ★Aqr7950 + 1539, // ★Aqr8499 + 1540, // ★Aqr8610 + 1541, // ★Aqr8264 + 1542, // ★Cru4853 + 1543, // ★Cru4730 + 1544, // ★Cru4763 + 1545, // ★Cru4700 + 1546, // ★Cru4656 + 1547, // ★PsA8728 + 1548, // ★TrA6217 + 1549, // ★Cap7776 + 1550, // ★Cap7754 + 1551, // ★Cap8278 + 1552, // ★Cap8322 + 1553, // ★Cap7773 + 1554, // ★Sge7479 + 1555, // ★Car2326 + 1556, // ★Car3685 + 1557, // ★Car3307 + 1558, // ★Car3699 + 1559, // ★Dra5744 + 1560, // ★Dra5291 + 1561, // ★Dra6705 + 1562, // ★Dra6536 + 1563, // ★Dra7310 + 1564, // ★Dra6688 + 1565, // ★Dra4434 + 1566, // ★Dra6370 + 1567, // ★Dra7462 + 1568, // ★Dra6396 + 1569, // ★Dra6132 + 1570, // ★Dra6636 + 1571, // ★CVn4915 + 1572, // ★CVn4785 + 1573, // ★CVn4846 + 1574, // ★Aql7595 + 1575, // ★Aql7557 + 1576, // ★Aql7525 + 1577, // ★Aql7602 + 1578, // ★Aql7235 - 016, // Cherish Ball - 500, // Park Ball - }); - #endregion + 016, // Cherish Ball + 500, // Park Ball + }); + #endregion - internal static readonly int[] TypeTutor8 = - { - (int)Move.GrassPledge, - (int)Move.FirePledge, - (int)Move.WaterPledge, - (int)Move.FrenzyPlant, - (int)Move.BlastBurn, - (int)Move.HydroCannon, - (int)Move.DracoMeteor, - (int)Move.SteelBeam, - }; - } + internal static readonly int[] TypeTutor8 = + { + (int)Move.GrassPledge, + (int)Move.FirePledge, + (int)Move.WaterPledge, + (int)Move.FrenzyPlant, + (int)Move.BlastBurn, + (int)Move.HydroCannon, + (int)Move.DracoMeteor, + (int)Move.SteelBeam, + }; } diff --git a/PKHeX.Core/Legality/Tables/Tables8a.cs b/PKHeX.Core/Legality/Tables/Tables8a.cs index 08a85cecb..0a1a7262e 100644 --- a/PKHeX.Core/Legality/Tables/Tables8a.cs +++ b/PKHeX.Core/Legality/Tables/Tables8a.cs @@ -1,168 +1,167 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + internal const int MaxSpeciesID_8a = (int)Species.Enamorus; + internal const int MaxMoveID_8a = (int)Move.TakeHeart; + internal const int MaxItemID_8a = 1828; // Legend Plate + internal const int MaxBallID_8a = (int)Ball.LAOrigin; + internal const int MaxGameID_8a = (int)GameVersion.SP; + internal const int MaxAbilityID_8a = MaxAbilityID_8_R2; + + internal static readonly ushort[] Pouch_Items_LA = { - internal const int MaxSpeciesID_8a = (int)Species.Enamorus; - internal const int MaxMoveID_8a = (int)Move.TakeHeart; - internal const int MaxItemID_8a = 1828; // Legend Plate - internal const int MaxBallID_8a = (int)Ball.LAOrigin; - internal const int MaxGameID_8a = (int)GameVersion.SP; - internal const int MaxAbilityID_8a = MaxAbilityID_8_R2; + 017, 023, 024, 025, 026, 027, 028, 029, 039, 041, + 050, 054, 072, 073, 075, 080, 081, 082, 083, 084, + 085, 090, 091, 092, 107, 108, 109, 110, 149, 150, + 151, 152, 153, 154, 155, 157, 158, 159, 160, 161, + 162, 163, 164, 166, 168, 233, 252, 321, 322, 323, + 324, 325, 326, 327, 583, 849, - internal static readonly ushort[] Pouch_Items_LA = - { - 017, 023, 024, 025, 026, 027, 028, 029, 039, 041, - 050, 054, 072, 073, 075, 080, 081, 082, 083, 084, - 085, 090, 091, 092, 107, 108, 109, 110, 149, 150, - 151, 152, 153, 154, 155, 157, 158, 159, 160, 161, - 162, 163, 164, 166, 168, 233, 252, 321, 322, 323, - 324, 325, 326, 327, 583, 849, + 1125, 1126, 1127, 1128, 1231, 1232, 1233, 1234, 1235, 1236, + 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, + 1247, 1248, 1249, 1250, 1251, - 1125, 1126, 1127, 1128, 1231, 1232, 1233, 1234, 1235, 1236, - 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, - 1247, 1248, 1249, 1250, 1251, + 1611, 1613, 1614, 1615, 1616, 1617, 1618, 1619, 1620, 1621, + 1628, 1630, 1631, 1632, 1633, 1634, 1635, 1636, 1637, 1638, + 1651, 1679, 1681, 1682, 1684, 1686, 1687, 1688, 1689, 1690, + 1691, 1692, 1693, 1694, 1695, 1696, 1699, 1700, 1701, 1702, + 1703, 1704, 1705, 1706, 1707, 1708, 1709, 1710, 1711, 1712, + 1713, 1716, 1717, 1720, 1724, 1725, 1726, 1727, 1728, 1732, + 1733, 1734, 1735, 1736, 1738, 1739, 1740, 1741, 1742, 1746, + 1747, 1748, 1749, 1750, 1754, 1755, 1756, 1757, 1758, 1759, + 1760, 1761, 1762, 1764, 1785, + }; - 1611, 1613, 1614, 1615, 1616, 1617, 1618, 1619, 1620, 1621, - 1628, 1630, 1631, 1632, 1633, 1634, 1635, 1636, 1637, 1638, - 1651, 1679, 1681, 1682, 1684, 1686, 1687, 1688, 1689, 1690, - 1691, 1692, 1693, 1694, 1695, 1696, 1699, 1700, 1701, 1702, - 1703, 1704, 1705, 1706, 1707, 1708, 1709, 1710, 1711, 1712, - 1713, 1716, 1717, 1720, 1724, 1725, 1726, 1727, 1728, 1732, - 1733, 1734, 1735, 1736, 1738, 1739, 1740, 1741, 1742, 1746, - 1747, 1748, 1749, 1750, 1754, 1755, 1756, 1757, 1758, 1759, - 1760, 1761, 1762, 1764, 1785, - }; + internal static readonly ushort[] Pouch_Recipe_LA = + { + 1640, 1641, 1642, 1643, 1644, 1646, 1647, 1648, 1649, + 1650, 1652, 1653, 1654, 1655, 1656, 1657, 1658, 1659, + 1660, 1661, 1662, 1663, 1664, 1665, 1666, 1667, 1668, 1669, + 1670, 1671, 1673, 1674, 1675, 1676, 1677, - internal static readonly ushort[] Pouch_Recipe_LA = - { - 1640, 1641, 1642, 1643, 1644, 1646, 1647, 1648, 1649, - 1650, 1652, 1653, 1654, 1655, 1656, 1657, 1658, 1659, - 1660, 1661, 1662, 1663, 1664, 1665, 1666, 1667, 1668, 1669, - 1670, 1671, 1673, 1674, 1675, 1676, 1677, + 1729, + 1730, 1731, - 1729, - 1730, 1731, + 1751, 1752, 1753, - 1751, 1752, 1753, + 1783, 1784, + }; - 1783, 1784, - }; + internal static readonly ushort[] Pouch_Key_LA = + { + 111, + 298, 299, + 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, + 310, 311, 312, 313, + 441, 455, 466, + 632, 638, 644, + 1608, 1609, 1610, 1612, 1622, 1624, 1625, 1626, 1627, 1629, + 1639, 1678, 1721, 1722, 1723, 1737, 1743, 1744, 1745, 1763, + 1765, 1766, 1767, 1768, 1769, 1771, 1776, 1777, 1778, 1779, + 1780, 1782, 1786, 1787, 1788, 1789, 1790, 1792, 1793, 1794, + 1795, 1796, 1797, 1798, 1799, 1800, 1801, 1802, 1803, 1804, + 1805, 1806, 1807, + 1828, + }; - internal static readonly ushort[] Pouch_Key_LA = - { - 111, - 298, 299, - 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, - 310, 311, 312, 313, - 441, 455, 466, - 632, 638, 644, - 1608, 1609, 1610, 1612, 1622, 1624, 1625, 1626, 1627, 1629, - 1639, 1678, 1721, 1722, 1723, 1737, 1743, 1744, 1745, 1763, - 1765, 1766, 1767, 1768, 1769, 1771, 1776, 1777, 1778, 1779, - 1780, 1782, 1786, 1787, 1788, 1789, 1790, 1792, 1793, 1794, - 1795, 1796, 1797, 1798, 1799, 1800, 1801, 1802, 1803, 1804, - 1805, 1806, 1807, - 1828, - }; + internal static readonly ushort[] HeldItems_LA = { 0 }; - internal static readonly ushort[] HeldItems_LA = { 0 }; + internal static readonly HashSet HisuiOriginForms = new() + { + (int)Species.Sneasel, + (int)Species.Growlithe, + (int)Species.Arcanine, + (int)Species.Voltorb, + (int)Species.Electrode, + (int)Species.Qwilfish, + (int)Species.Sliggoo, + (int)Species.Goodra, + }; - internal static readonly HashSet HisuiOriginForms = new() - { - (int)Species.Sneasel, - (int)Species.Growlithe, - (int)Species.Arcanine, - (int)Species.Voltorb, - (int)Species.Electrode, - (int)Species.Qwilfish, - (int)Species.Sliggoo, - (int)Species.Goodra, - }; + internal static readonly IReadOnlyDictionary HisuiForm0Evolutions = new Dictionary + { + {(int)Species.Sneasler, 1}, + }; - internal static readonly IReadOnlyDictionary HisuiForm0Evolutions = new Dictionary - { - {(int)Species.Sneasler, 1}, - }; + internal static readonly HashSet HisuiVariantFormEvolutions = new() + { + (int)Species.Decidueye, + (int)Species.Typhlosion, + (int)Species.Samurott, + (int)Species.Lilligant, + (int)Species.Braviary, + (int)Species.Avalugg, + }; - internal static readonly HashSet HisuiVariantFormEvolutions = new() - { - (int)Species.Decidueye, - (int)Species.Typhlosion, - (int)Species.Samurott, - (int)Species.Lilligant, - (int)Species.Braviary, - (int)Species.Avalugg, - }; + #region Moves - #region Moves + internal static readonly ushort[] MoveShop8_LA = + { + (int)Move.FalseSwipe, + (int)Move.FireFang, + (int)Move.ThunderFang, + (int)Move.IceFang, + (int)Move.IceBall, + (int)Move.RockSmash, + (int)Move.Spikes, + (int)Move.Bulldoze, + (int)Move.AerialAce, + (int)Move.StealthRock, + (int)Move.Swift, + (int)Move.TriAttack, + (int)Move.MagicalLeaf, + (int)Move.OminousWind, + (int)Move.PowerShift, + (int)Move.FocusEnergy, + (int)Move.BulkUp, + (int)Move.CalmMind, + (int)Move.Rest, + (int)Move.BabyDollEyes, + (int)Move.FirePunch, + (int)Move.ThunderPunch, + (int)Move.IcePunch, + (int)Move.DrainPunch, + (int)Move.PoisonJab, + (int)Move.PsychoCut, + (int)Move.ZenHeadbutt, + (int)Move.LeechLife, + (int)Move.XScissor, + (int)Move.RockSlide, + (int)Move.ShadowClaw, + (int)Move.IronHead, + (int)Move.IronTail, + (int)Move.MysticalFire, + (int)Move.WaterPulse, + (int)Move.ChargeBeam, + (int)Move.EnergyBall, + (int)Move.IcyWind, + (int)Move.SludgeBomb, + (int)Move.EarthPower, + (int)Move.ShadowBall, + (int)Move.Snarl, + (int)Move.FlashCannon, + (int)Move.DazzlingGleam, + (int)Move.GigaImpact, + (int)Move.AquaTail, + (int)Move.WildCharge, + (int)Move.HighHorsepower, + (int)Move.Megahorn, + (int)Move.StoneEdge, + (int)Move.Outrage, + (int)Move.PlayRough, + (int)Move.HyperBeam, + (int)Move.Flamethrower, + (int)Move.Thunderbolt, + (int)Move.IceBeam, + (int)Move.Psychic, + (int)Move.DarkPulse, + (int)Move.DracoMeteor, + (int)Move.SteelBeam, + (int)Move.VoltTackle, + }; - internal static readonly ushort[] MoveShop8_LA = - { - (int)Move.FalseSwipe, - (int)Move.FireFang, - (int)Move.ThunderFang, - (int)Move.IceFang, - (int)Move.IceBall, - (int)Move.RockSmash, - (int)Move.Spikes, - (int)Move.Bulldoze, - (int)Move.AerialAce, - (int)Move.StealthRock, - (int)Move.Swift, - (int)Move.TriAttack, - (int)Move.MagicalLeaf, - (int)Move.OminousWind, - (int)Move.PowerShift, - (int)Move.FocusEnergy, - (int)Move.BulkUp, - (int)Move.CalmMind, - (int)Move.Rest, - (int)Move.BabyDollEyes, - (int)Move.FirePunch, - (int)Move.ThunderPunch, - (int)Move.IcePunch, - (int)Move.DrainPunch, - (int)Move.PoisonJab, - (int)Move.PsychoCut, - (int)Move.ZenHeadbutt, - (int)Move.LeechLife, - (int)Move.XScissor, - (int)Move.RockSlide, - (int)Move.ShadowClaw, - (int)Move.IronHead, - (int)Move.IronTail, - (int)Move.MysticalFire, - (int)Move.WaterPulse, - (int)Move.ChargeBeam, - (int)Move.EnergyBall, - (int)Move.IcyWind, - (int)Move.SludgeBomb, - (int)Move.EarthPower, - (int)Move.ShadowBall, - (int)Move.Snarl, - (int)Move.FlashCannon, - (int)Move.DazzlingGleam, - (int)Move.GigaImpact, - (int)Move.AquaTail, - (int)Move.WildCharge, - (int)Move.HighHorsepower, - (int)Move.Megahorn, - (int)Move.StoneEdge, - (int)Move.Outrage, - (int)Move.PlayRough, - (int)Move.HyperBeam, - (int)Move.Flamethrower, - (int)Move.Thunderbolt, - (int)Move.IceBeam, - (int)Move.Psychic, - (int)Move.DarkPulse, - (int)Move.DracoMeteor, - (int)Move.SteelBeam, - (int)Move.VoltTackle, - }; - - #endregion - } + #endregion } diff --git a/PKHeX.Core/Legality/Tables/Tables8bs.cs b/PKHeX.Core/Legality/Tables/Tables8bs.cs index b69d5d1ab..3d1426442 100644 --- a/PKHeX.Core/Legality/Tables/Tables8bs.cs +++ b/PKHeX.Core/Legality/Tables/Tables8bs.cs @@ -1,257 +1,256 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Legal { - public static partial class Legal + internal const int MaxSpeciesID_8b = MaxSpeciesID_4; // Arceus-493 + internal const int MaxMoveID_8b = MaxMoveID_8_R2; + internal const int MaxItemID_8b = 1822; // DS Sounds + internal const int MaxBallID_8b = (int)Ball.LAOrigin; + internal const int MaxGameID_8b = (int)GameVersion.SP; + internal const int MaxAbilityID_8b = MaxAbilityID_8_R2; + + internal static readonly ushort[] Pouch_Regular_BS = { - internal const int MaxSpeciesID_8b = MaxSpeciesID_4; // Arceus-493 - internal const int MaxMoveID_8b = MaxMoveID_8_R2; - internal const int MaxItemID_8b = 1822; // DS Sounds - internal const int MaxBallID_8b = (int)Ball.LAOrigin; - internal const int MaxGameID_8b = (int)GameVersion.SP; - internal const int MaxAbilityID_8b = MaxAbilityID_8_R2; + 045, 046, 047, 048, 049, 050, 051, 052, 053, 072, 073, 074, 075, 076, 077, 078, + 079, 080, 081, 082, 083, 084, 085, 093, 094, 107, 108, 109, 110, 111, 112, 135, + 136, 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, + 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, + 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, + 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, + 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, + 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, + 325, 326, 327, 537, 565, 566, 567, 568, 569, 570, 644, 645, 849, - internal static readonly ushort[] Pouch_Regular_BS = - { - 045, 046, 047, 048, 049, 050, 051, 052, 053, 072, 073, 074, 075, 076, 077, 078, - 079, 080, 081, 082, 083, 084, 085, 093, 094, 107, 108, 109, 110, 111, 112, 135, - 136, 213, 214, 215, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, - 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, - 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, - 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, - 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, - 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, - 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, - 325, 326, 327, 537, 565, 566, 567, 568, 569, 570, 644, 645, 849, + 1231, 1232, 1233, 1234, 1235, 1236, 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, + 1245, 1246, 1247, 1248, 1249, 1250, 1251, 1606, + }; - 1231, 1232, 1233, 1234, 1235, 1236, 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, - 1245, 1246, 1247, 1248, 1249, 1250, 1251, 1606, - }; + internal static readonly ushort[] Pouch_Ball_BS = + { + 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, + 492, 493, 494, 495, 496, 497, 498, 499, 500, + 576, + 851, + }; - internal static readonly ushort[] Pouch_Ball_BS = - { - 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, - 492, 493, 494, 495, 496, 497, 498, 499, 500, - 576, - 851, - }; + internal static readonly ushort[] Pouch_Battle_BS = + { + 055, 056, 057, 058, 059, 060, 061, 062, 063, + }; - internal static readonly ushort[] Pouch_Battle_BS = - { - 055, 056, 057, 058, 059, 060, 061, 062, 063, - }; + internal static readonly ushort[] Pouch_Items_BS = ArrayUtil.ConcatAll(Pouch_Regular_BS, Pouch_Ball_BS, Pouch_Battle_BS); - internal static readonly ushort[] Pouch_Items_BS = ArrayUtil.ConcatAll(Pouch_Regular_BS, Pouch_Ball_BS, Pouch_Battle_BS); + internal static readonly ushort[] Pouch_Key_BS = + { + 428, 431, 432, 433, 438, 439, 440, 443, 445, 446, 447, 448, 449, 450, 451, 452, + 453, 454, 455, 459, 460, 461, 462, 463, 464, 466, 467, 631, 632, - internal static readonly ushort[] Pouch_Key_BS = - { - 428, 431, 432, 433, 438, 439, 440, 443, 445, 446, 447, 448, 449, 450, 451, 452, - 453, 454, 455, 459, 460, 461, 462, 463, 464, 466, 467, 631, 632, + 1267, 1278, 1822, + }; - 1267, 1278, 1822, - }; + internal static readonly ushort[] Pouch_Medicine_BS = + { + 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, + 038, 039, 040, 041, 042, 043, 044, 054, - internal static readonly ushort[] Pouch_Medicine_BS = - { - 017, 018, 019, 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, - 038, 039, 040, 041, 042, 043, 044, 054, + // 134 Sweet Heart (future event item?) + }; - // 134 Sweet Heart (future event item?) - }; + internal static readonly ushort[] Pouch_Berries_BS = + { + 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, + 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, + 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, + 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, + 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, + 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, + 209, 210, 211, 212, 686, + }; - internal static readonly ushort[] Pouch_Berries_BS = - { - 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, - 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, - 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, - 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, - 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, - 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, - 209, 210, 211, 212, 686, - }; + internal static readonly ushort[] Pouch_Treasure_BS = + { + 086, 087, 088, 089, 090, 091, 092, 099, 100, 101, 102, 103, 104, 105, 106, 795, 796, - internal static readonly ushort[] Pouch_Treasure_BS = - { - 086, 087, 088, 089, 090, 091, 092, 099, 100, 101, 102, 103, 104, 105, 106, 795, 796, + 1808, 1809, 1810, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819, 1820, 1821, + }; - 1808, 1809, 1810, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819, 1820, 1821, - }; + internal static readonly ushort[] Pouch_TMHM_BS = // TM01-TM100 + { + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, + 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, + 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, + 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, + 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, + 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, + 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, + 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, + 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, + 420, 421, 422, 423, 424, 425, 426, 427, // Previously called HM0X, in BDSP they're now called TM93-TM100 + }; - internal static readonly ushort[] Pouch_TMHM_BS = // TM01-TM100 - { - 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, - 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, - 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, - 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, - 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, - 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, - 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, - 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, - 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, - 418, 419, - 420, 421, 422, 423, 424, 425, 426, 427, // Previously called HM0X, in BDSP they're now called TM93-TM100 - }; + internal static readonly ushort[] HeldItems_BS = ArrayUtil.ConcatAll(Pouch_Items_BS, Pouch_Berries_BS, Pouch_TMHM_BS, Pouch_Medicine_BS, Pouch_Treasure_BS); - internal static readonly ushort[] HeldItems_BS = ArrayUtil.ConcatAll(Pouch_Items_BS, Pouch_Berries_BS, Pouch_TMHM_BS, Pouch_Medicine_BS, Pouch_Treasure_BS); + #region Moves - #region Moves + public static readonly int[] TMHM_BDSP = + { + 264, 337, 352, 347, 046, 092, 258, 339, 331, 526, + 241, 269, 058, 059, 063, 113, 182, 240, 202, 219, + 605, 076, 231, 085, 087, 089, 490, 091, 094, 247, + 280, 104, 115, 351, 053, 188, 201, 126, 317, 332, + 259, 263, 521, 156, 213, 168, 211, 285, 503, 315, + 355, 411, 412, 206, 362, 374, 451, 203, 406, 409, + 261, 405, 417, 153, 421, 371, 278, 416, 397, 148, + 444, 419, 086, 360, 014, 446, 244, 555, 399, 157, + 404, 214, 523, 398, 138, 447, 207, 365, 369, 164, + 430, 433, + 015, 019, 057, 070, 432, 249, 127, 431, + }; - public static readonly int[] TMHM_BDSP = - { - 264, 337, 352, 347, 046, 092, 258, 339, 331, 526, - 241, 269, 058, 059, 063, 113, 182, 240, 202, 219, - 605, 076, 231, 085, 087, 089, 490, 091, 094, 247, - 280, 104, 115, 351, 053, 188, 201, 126, 317, 332, - 259, 263, 521, 156, 213, 168, 211, 285, 503, 315, - 355, 411, 412, 206, 362, 374, 451, 203, 406, 409, - 261, 405, 417, 153, 421, 371, 278, 416, 397, 148, - 444, 419, 086, 360, 014, 446, 244, 555, 399, 157, - 404, 214, 523, 398, 138, 447, 207, 365, 369, 164, - 430, 433, - 015, 019, 057, 070, 432, 249, 127, 431, - }; + internal static readonly int[] TypeTutor8b = + { + (int)Move.FrenzyPlant, + (int)Move.BlastBurn, + (int)Move.HydroCannon, + (int)Move.DracoMeteor, + }; - internal static readonly int[] TypeTutor8b = - { - (int)Move.FrenzyPlant, - (int)Move.BlastBurn, - (int)Move.HydroCannon, - (int)Move.DracoMeteor, - }; + /// + /// Moves that are kill + /// + public static readonly HashSet DummiedMoves_BDSP = new() + { + 002, 003, 004, 013, 026, 027, 041, 049, 082, 096, + 099, 112, 117, 119, 121, 125, 128, 131, 132, 140, + 145, 146, 149, 158, 159, 169, 171, 185, 193, 216, + 218, 222, 228, 265, 274, 287, 289, 290, 293, 300, + 301, 302, 316, 318, 320, 324, 327, 346, 357, 358, + 363, 373, 376, 377, 378, 381, 382, 386, 426, 429, + 443, 445, 456, 466, 477, 481, 485, 498, 507, 508, + 516, 518, 519, 520, 527, 531, 532, 533, 535, 537, + 539, 541, 543, 544, 545, 546, 547, 548, 549, 550, + 551, 552, 553, 554, 557, 558, 559, 560, 561, 563, + 567, 569, 570, 571, 576, 578, 582, 587, 588, 591, + 592, 593, 594, 600, 601, 603, 606, 607, 610, 613, + 614, 615, 616, 617, 621, 622, 623, 624, 625, 626, + 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, + 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, + 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, + 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, + 669, 671, 674, 676, 677, 678, 680, 681, 683, 685, + 686, 687, 688, 689, 690, 691, 693, 695, 696, 697, + 698, 699, 700, 701, 702, 703, 704, 705, 706, 708, + 709, 711, 712, 713, 714, 716, 717, 718, 719, 720, + 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, + 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, + 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, + 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, + 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, + 771, 772, 773, 774, 775, 777, 778, 779, 780, 781, + 782, 783, 784, 785, 786, 787, 788, 789, 790, 792, + 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, + 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, + 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, + 823, 824, 825, 826, + }; - /// - /// Moves that are kill - /// - public static readonly HashSet DummiedMoves_BDSP = new() - { - 002, 003, 004, 013, 026, 027, 041, 049, 082, 096, - 099, 112, 117, 119, 121, 125, 128, 131, 132, 140, - 145, 146, 149, 158, 159, 169, 171, 185, 193, 216, - 218, 222, 228, 265, 274, 287, 289, 290, 293, 300, - 301, 302, 316, 318, 320, 324, 327, 346, 357, 358, - 363, 373, 376, 377, 378, 381, 382, 386, 426, 429, - 443, 445, 456, 466, 477, 481, 485, 498, 507, 508, - 516, 518, 519, 520, 527, 531, 532, 533, 535, 537, - 539, 541, 543, 544, 545, 546, 547, 548, 549, 550, - 551, 552, 553, 554, 557, 558, 559, 560, 561, 563, - 567, 569, 570, 571, 576, 578, 582, 587, 588, 591, - 592, 593, 594, 600, 601, 603, 606, 607, 610, 613, - 614, 615, 616, 617, 621, 622, 623, 624, 625, 626, - 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, - 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, - 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, - 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, - 669, 671, 674, 676, 677, 678, 680, 681, 683, 685, - 686, 687, 688, 689, 690, 691, 693, 695, 696, 697, - 698, 699, 700, 701, 702, 703, 704, 705, 706, 708, - 709, 711, 712, 713, 714, 716, 717, 718, 719, 720, - 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, - 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, - 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, - 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, - 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, - 771, 772, 773, 774, 775, 777, 778, 779, 780, 781, - 782, 783, 784, 785, 786, 787, 788, 789, 790, 792, - 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, - 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, - 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, - 823, 824, 825, 826, - }; + #endregion - #endregion + #region Unreleased Items + internal static readonly bool[] ReleasedHeldItems_8b = GetPermitList(MaxItemID_8b, HeldItems_BS, new ushort[] + { + 044, // Sacred Ash + 537, // Prism Scale + 565, // Health Feather + 566, // Muscle Feather + 567, // Resist Feather + 568, // Genius Feather + 569, // Clever Feather + 570, // Swift Feather + 849, // Ice Stone - #region Unreleased Items - internal static readonly bool[] ReleasedHeldItems_8b = GetPermitList(MaxItemID_8b, HeldItems_BS, new ushort[] - { - 044, // Sacred Ash - 537, // Prism Scale - 565, // Health Feather - 566, // Muscle Feather - 567, // Resist Feather - 568, // Genius Feather - 569, // Clever Feather - 570, // Swift Feather - 849, // Ice Stone + 005, // Safari Ball + 016, // Cherish Ball + 499, // Sport Ball + 500, // Park Ball + 576, // Dream Ball + 851, // Beast Ball - 005, // Safari Ball - 016, // Cherish Ball - 499, // Sport Ball - 500, // Park Ball - 576, // Dream Ball - 851, // Beast Ball + // new BDSP items, but they can't be held + 1808, // Mysterious Shard S + 1809, // Mysterious Shard L + 1810, // Digger Drill + 1811, // Kanto Slate + 1812, // Johto Slate + 1813, // Soul Slate + 1814, // Rainbow Slate + 1815, // Squall Slate + 1816, // Oceanic Slate + 1817, // Tectonic Slate + 1818, // Stratospheric Slate + 1819, // Genome Slate + 1820, // Discovery Slate + 1821, // Distortion Slate + }); + #endregion - // new BDSP items, but they can't be held - 1808, // Mysterious Shard S - 1809, // Mysterious Shard L - 1810, // Digger Drill - 1811, // Kanto Slate - 1812, // Johto Slate - 1813, // Soul Slate - 1814, // Rainbow Slate - 1815, // Squall Slate - 1816, // Oceanic Slate - 1817, // Tectonic Slate - 1818, // Stratospheric Slate - 1819, // Genome Slate - 1820, // Discovery Slate - 1821, // Distortion Slate - }); - #endregion + private const int MaxValidHatchLocation8b = 657; - private const int MaxValidHatchLocation8b = 657; + public static bool IsValidEggHatchLocation8b(int location, GameVersion version) + { + if ((uint)location > MaxValidHatchLocation8b) + return false; + var loc16 = (ushort)location; + if (LocationsNoHatchBDSP.Contains(loc16)) + return false; - public static bool IsValidEggHatchLocation8b(int location, GameVersion version) - { - if ((uint)location > MaxValidHatchLocation8b) - return false; - var loc16 = (ushort)location; - if (LocationsNoHatchBDSP.Contains(loc16)) - return false; - - // Check if the location isn't an exclusive location that is only accessible in the other game. - var table = version == GameVersion.BD ? LocationsExclusiveSP : LocationsExclusiveBD; - return !table.Contains(loc16); - } - - private static readonly HashSet LocationsExclusiveBD = new() - { - 216, // Spear Pillar - 218, // Hall of Origin - 498, // Ramanas Park (Johto Room) - 503, // Ramanas Park (Rainbow Room) - 650, // Ramanas Park (Johto Room) - 655, // Ramanas Park (Rainbow Room) - }; - - private static readonly HashSet LocationsExclusiveSP = new() - { - 217, // Spear Pillar - 497, // Ramanas Park (Kanto Room) - 504, // Ramanas Park (Squall Room) - 618, // Hall of Origin - 649, // Ramanas Park (Kanto Room) - 656, // Ramanas Park (Squall Room) - }; - - private static readonly HashSet LocationsNoHatchBDSP = new() - { - 094, 103, 107, // Hearthome City - 154, 155, 158, // Sunyshore City - 181, 182, 183, // Pokémon League - 329, // Lake Acuity - 337, 338, // Battle Park - 339, 340, 341, 342, 343, 344, // Battle Tower - 345, 353, 421, // Mystery Zone - 474, // Resort Area - 483, 484, // Mystery Zone - 491, 492, 493, // Mystery Zone - 495, // Ramanas Park - 620, 621, 622, 623, // Grand Underground (Secret Base) - 625, // Sea (sailing animation) - 627, 628, 629, 630, 631, 632, // Grand Underground (Secret Base) - 633, 634, 635, 636, 637, 638, // Grand Underground (Secret Base) - 639, 640, 641, 642, 643, 644, // Grand Underground (Secret Base) - 645, 646, 647, // Grand Underground (Secret Base) - }; + // Check if the location isn't an exclusive location that is only accessible in the other game. + var table = version == GameVersion.BD ? LocationsExclusiveSP : LocationsExclusiveBD; + return !table.Contains(loc16); } + + private static readonly HashSet LocationsExclusiveBD = new() + { + 216, // Spear Pillar + 218, // Hall of Origin + 498, // Ramanas Park (Johto Room) + 503, // Ramanas Park (Rainbow Room) + 650, // Ramanas Park (Johto Room) + 655, // Ramanas Park (Rainbow Room) + }; + + private static readonly HashSet LocationsExclusiveSP = new() + { + 217, // Spear Pillar + 497, // Ramanas Park (Kanto Room) + 504, // Ramanas Park (Squall Room) + 618, // Hall of Origin + 649, // Ramanas Park (Kanto Room) + 656, // Ramanas Park (Squall Room) + }; + + private static readonly HashSet LocationsNoHatchBDSP = new() + { + 094, 103, 107, // Hearthome City + 154, 155, 158, // Sunyshore City + 181, 182, 183, // Pokémon League + 329, // Lake Acuity + 337, 338, // Battle Park + 339, 340, 341, 342, 343, 344, // Battle Tower + 345, 353, 421, // Mystery Zone + 474, // Resort Area + 483, 484, // Mystery Zone + 491, 492, 493, // Mystery Zone + 495, // Ramanas Park + 620, 621, 622, 623, // Grand Underground (Secret Base) + 625, // Sea (sailing animation) + 627, 628, 629, 630, 631, 632, // Grand Underground (Secret Base) + 633, 634, 635, 636, 637, 638, // Grand Underground (Secret Base) + 639, 640, 641, 642, 643, 644, // Grand Underground (Secret Base) + 645, 646, 647, // Grand Underground (Secret Base) + }; } diff --git a/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs b/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs index aaf09259e..f6b159b6f 100644 --- a/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs +++ b/PKHeX.Core/Legality/Verifiers/Ability/AbilityBreedLegality.cs @@ -1,172 +1,171 @@ -using System.Collections.Generic; +using System.Collections.Generic; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Tables used for +/// +internal static class AbilityBreedLegality { /// - /// Tables used for + /// Species that cannot be bred with a Hidden Ability originating in /// - internal static class AbilityBreedLegality + internal static readonly HashSet BanHidden5 = new() { - /// - /// Species that cannot be bred with a Hidden Ability originating in - /// - internal static readonly HashSet BanHidden5 = new() - { - // Only males distributed; unable to pass to offspring - (int)Bulbasaur, (int)Charmander, (int)Squirtle, - (int)Tauros, - (int)Chikorita, (int)Cyndaquil, (int)Totodile, - (int)Tyrogue, - (int)Treecko, (int)Torchic, (int)Mudkip, - (int)Turtwig, (int)Chimchar, (int)Piplup, - (int)Pansage, (int)Pansear, (int)Panpour, - (int)Gothita, + // Only males distributed; unable to pass to offspring + (int)Bulbasaur, (int)Charmander, (int)Squirtle, + (int)Tauros, + (int)Chikorita, (int)Cyndaquil, (int)Totodile, + (int)Tyrogue, + (int)Treecko, (int)Torchic, (int)Mudkip, + (int)Turtwig, (int)Chimchar, (int)Piplup, + (int)Pansage, (int)Pansear, (int)Panpour, + (int)Gothita, - // Genderless; unable to pass to offspring - (int)Magnemite, - (int)Voltorb, - (int)Staryu, - (int)Ditto, - (int)Porygon, - (int)Beldum, - (int)Bronzor, - (int)Golett, + // Genderless; unable to pass to offspring + (int)Magnemite, + (int)Voltorb, + (int)Staryu, + (int)Ditto, + (int)Porygon, + (int)Beldum, + (int)Bronzor, + (int)Golett, - // Not available at all - (int)Gastly, - (int)Koffing, - (int)Misdreavus, - (int)Unown, - (int)Slakoth, - (int)Plusle, - (int)Plusle, - (int)Lunatone, - (int)Solrock, - (int)Baltoy, - (int)Castform, - (int)Kecleon, - (int)Duskull, - (int)Chimecho, - (int)Cherubi, - (int)Chingling, - (int)Rotom, - (int)Phione, - (int)Snivy, (int)Tepig, (int)Oshawott, - (int)Throh, (int)Sawk, - (int)Yamask, - (int)Archen, - (int)Zorua, - (int)Ferroseed, - (int)Klink, - (int)Tynamo, - (int)Litwick, - (int)Cryogonal, - (int)Rufflet, - (int)Deino, - (int)Larvesta, - }; + // Not available at all + (int)Gastly, + (int)Koffing, + (int)Misdreavus, + (int)Unown, + (int)Slakoth, + (int)Plusle, + (int)Plusle, + (int)Lunatone, + (int)Solrock, + (int)Baltoy, + (int)Castform, + (int)Kecleon, + (int)Duskull, + (int)Chimecho, + (int)Cherubi, + (int)Chingling, + (int)Rotom, + (int)Phione, + (int)Snivy, (int)Tepig, (int)Oshawott, + (int)Throh, (int)Sawk, + (int)Yamask, + (int)Archen, + (int)Zorua, + (int)Ferroseed, + (int)Klink, + (int)Tynamo, + (int)Litwick, + (int)Cryogonal, + (int)Rufflet, + (int)Deino, + (int)Larvesta, + }; - /// - /// Species that cannot be bred with a Hidden Ability originating in - /// - internal static readonly HashSet BanHidden6 = new() - { - // Not available at Friend Safari or Horde Encounter - (int)Flabébé + (2 << 11), // Orange - (int)Flabébé + (4 << 11), // White + /// + /// Species that cannot be bred with a Hidden Ability originating in + /// + internal static readonly HashSet BanHidden6 = new() + { + // Not available at Friend Safari or Horde Encounter + (int)Flabébé + (2 << 11), // Orange + (int)Flabébé + (4 << 11), // White - // Super Size can be obtained as a Pumpkaboo from event distributions - (int)Pumpkaboo + (1 << 11), // Small - (int)Pumpkaboo + (2 << 11), // Large + // Super Size can be obtained as a Pumpkaboo from event distributions + (int)Pumpkaboo + (1 << 11), // Small + (int)Pumpkaboo + (2 << 11), // Large - // Same abilities (1/2/H), not available as H - (int)Honedge, - (int)Carnivine, - (int)Cryogonal, - (int)Archen, - (int)Rotom, - (int)Rotom + (1 << 11), - (int)Rotom + (2 << 11), - (int)Rotom + (3 << 11), - (int)Rotom + (4 << 11), - (int)Rotom + (5 << 11), + // Same abilities (1/2/H), not available as H + (int)Honedge, + (int)Carnivine, + (int)Cryogonal, + (int)Archen, + (int)Rotom, + (int)Rotom + (1 << 11), + (int)Rotom + (2 << 11), + (int)Rotom + (3 << 11), + (int)Rotom + (4 << 11), + (int)Rotom + (5 << 11), - (int)Castform, - (int)Furfrou, - (int)Furfrou + (1 << 11), - (int)Furfrou + (2 << 11), - (int)Furfrou + (3 << 11), - (int)Furfrou + (4 << 11), - (int)Furfrou + (5 << 11), - (int)Furfrou + (6 << 11), - (int)Furfrou + (7 << 11), - (int)Furfrou + (8 << 11), - (int)Furfrou + (9 << 11), - }; + (int)Castform, + (int)Furfrou, + (int)Furfrou + (1 << 11), + (int)Furfrou + (2 << 11), + (int)Furfrou + (3 << 11), + (int)Furfrou + (4 << 11), + (int)Furfrou + (5 << 11), + (int)Furfrou + (6 << 11), + (int)Furfrou + (7 << 11), + (int)Furfrou + (8 << 11), + (int)Furfrou + (9 << 11), + }; - /// - /// Species that cannot be bred with a Hidden Ability originating in - /// - internal static readonly HashSet BanHidden7 = new() - { - // SOS slots have 0 call rate - (int)Wimpod, - (int)Golisopod, - (int)Komala, + /// + /// Species that cannot be bred with a Hidden Ability originating in + /// + internal static readonly HashSet BanHidden7 = new() + { + // SOS slots have 0 call rate + (int)Wimpod, + (int)Golisopod, + (int)Komala, - // No Encounter - (int)Minior + (07 << 11), - (int)Minior + (08 << 11), - (int)Minior + (09 << 11), - (int)Minior + (10 << 11), - (int)Minior + (11 << 11), - (int)Minior + (12 << 11), - (int)Minior + (13 << 11), + // No Encounter + (int)Minior + (07 << 11), + (int)Minior + (08 << 11), + (int)Minior + (09 << 11), + (int)Minior + (10 << 11), + (int)Minior + (11 << 11), + (int)Minior + (12 << 11), + (int)Minior + (13 << 11), - // Previous-Gen - (int)Pumpkaboo + (1 << 11), // Small - (int)Pumpkaboo + (2 << 11), // Large + // Previous-Gen + (int)Pumpkaboo + (1 << 11), // Small + (int)Pumpkaboo + (2 << 11), // Large - // Same abilities (1/2/H), not available as H - (int)Honedge, - (int)Doublade, - (int)Aegislash, - (int)Carnivine, - (int)Cryogonal, - (int)Archen, - (int)Archeops, - (int)Rotom, - (int)Rotom + (1 << 11), - (int)Rotom + (2 << 11), - (int)Rotom + (3 << 11), - (int)Rotom + (4 << 11), - (int)Rotom + (5 << 11), - }; + // Same abilities (1/2/H), not available as H + (int)Honedge, + (int)Doublade, + (int)Aegislash, + (int)Carnivine, + (int)Cryogonal, + (int)Archen, + (int)Archeops, + (int)Rotom, + (int)Rotom + (1 << 11), + (int)Rotom + (2 << 11), + (int)Rotom + (3 << 11), + (int)Rotom + (4 << 11), + (int)Rotom + (5 << 11), + }; - // - // Species that cannot be bred with a Hidden Ability originating in - // - // internal static readonly HashSet BanHidden8 = new(); // none as of DLC 1! + // + // Species that cannot be bred with a Hidden Ability originating in + // + // internal static readonly HashSet BanHidden8 = new(); // none as of DLC 1! - /// - /// Species that cannot be bred with a Hidden Ability originating in - /// - internal static readonly HashSet BanHidden8b = new() - { - (int)Rotom, - (int)Rotom + (1 << 11), - (int)Rotom + (2 << 11), - (int)Rotom + (3 << 11), - (int)Rotom + (4 << 11), - (int)Rotom + (5 << 11), + /// + /// Species that cannot be bred with a Hidden Ability originating in + /// + internal static readonly HashSet BanHidden8b = new() + { + (int)Rotom, + (int)Rotom + (1 << 11), + (int)Rotom + (2 << 11), + (int)Rotom + (3 << 11), + (int)Rotom + (4 << 11), + (int)Rotom + (5 << 11), - (int)Baltoy, - (int)Claydol, - (int)Solrock, - (int)Lunatone, + (int)Baltoy, + (int)Claydol, + (int)Solrock, + (int)Lunatone, - (int)Phione, - }; - } + (int)Phione, + }; } diff --git a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs index adc3b6667..b701589cd 100644 --- a/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Ability/AbilityVerifier.cs @@ -1,504 +1,503 @@ -using System.Collections.Generic; +using System.Collections.Generic; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the values. +/// +public sealed class AbilityVerifier : Verifier { - /// - /// Verifies the values. - /// - public sealed class AbilityVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Ability; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Ability; + var result = VerifyAbility(data); + data.AddLine(result); + } - public override void Verify(LegalityAnalysis data) + private CheckResult VALID => GetValid(LAbilityFlag); + private CheckResult INVALID => GetInvalid(LAbilityMismatch); + + private enum AbilityState : byte + { + CanMismatch, + MustMatch, + } + + private CheckResult VerifyAbility(LegalityAnalysis data) + { + var pk = data.Entity; + var pi = data.PersonalInfo; + + // Check ability is possible (within bounds) + int ability = pk.Ability; + int abilIndex = pi.GetAbilityIndex(ability); + if (abilIndex < 0) + return GetInvalid(LAbilityUnexpected); + + var abilities = pi.Abilities; + int format = pk.Format; + if (format >= 6) { - var result = VerifyAbility(data); - data.AddLine(result); - } + var num = pk.AbilityNumber; + if (!IsValidAbilityBits(num)) + return INVALID; - private CheckResult VALID => GetValid(LAbilityFlag); - private CheckResult INVALID => GetInvalid(LAbilityMismatch); + // Check AbilityNumber points to ability + int an = num >> 1; + if (an >= abilities.Count || abilities[an] != ability) + return INVALID; - private enum AbilityState : byte - { - CanMismatch, - MustMatch, - } - - private CheckResult VerifyAbility(LegalityAnalysis data) - { - var pkm = data.pkm; - var pi = data.PersonalInfo; - - // Check ability is possible (within bounds) - int ability = pkm.Ability; - int abilIndex = pi.GetAbilityIndex(ability); - if (abilIndex < 0) - return GetInvalid(LAbilityUnexpected); - - var abilities = pi.Abilities; - int format = pkm.Format; - if (format >= 6) + // Check AbilityNumber for transfers without unique abilities + int gen = data.Info.Generation; + if (gen is 3 or 4 or 5 && num != 4) { - var num = pkm.AbilityNumber; - if (!IsValidAbilityBits(num)) - return INVALID; - - // Check AbilityNumber points to ability - int an = num >> 1; - if (an >= abilities.Count || abilities[an] != ability) - return INVALID; - - // Check AbilityNumber for transfers without unique abilities - int gen = data.Info.Generation; - if (gen is 3 or 4 or 5 && num != 4) + // To determine AbilityNumber [PK5->PK6], check if the first ability in Personal matches the ability. + // It is not possible to flip it to the other index as capsule requires unique abilities. + if (abilities[0] == abilities[1] && num != 1) { - // To determine AbilityNumber [PK5->PK6], check if the first ability in Personal matches the ability. - // It is not possible to flip it to the other index as capsule requires unique abilities. - if (abilities[0] == abilities[1] && num != 1) - { - // Check if any pre-evolution could have it flipped. - var evos = data.Info.EvoChainsAllGens.Gen6; - var pt = GameData.GetPersonal(pkm.Context.GetSingleGameVersion()); - if (!GetWasDual(evos, pt, pkm)) - return INVALID; - } + // Check if any pre-evolution could have it flipped. + var evos = data.Info.EvoChainsAllGens.Gen6; + var pt = GameData.GetPersonal(pk.Context.GetSingleGameVersion()); + if (!GetWasDual(evos, pt, pk)) + return INVALID; } } - - if (format >= 8) // Ability Patch - { - var evos = data.Info.EvoChainsAllGens; - if (pkm.AbilityNumber == 4 && IsAccessibleAbilityPatch(pkm, evos)) - { - if (CanAbilityPatch(format, abilities, pkm.Species)) - return GetValid(LAbilityPatchUsed); - - var e = data.EncounterOriginal; - if (e.Species != pkm.Species && CanAbilityPatch(format, PKX.Personal.GetFormEntry(e.Species, e.Form).Abilities, e.Species)) - return GetValid(LAbilityPatchUsed); - - // Verify later, it may be encountered with its hidden ability without using an ability patch. - } - } - - var enc = data.EncounterMatch; - if (enc is MysteryGift {Generation: >= 4} g) - return VerifyAbilityMG(data, g, abilities); - - if (format < 6) - return VerifyAbility345(data, enc, abilities, abilIndex); - - return VerifyAbility(data, abilities, abilIndex); } - public static bool IsValidAbilityBits(int num) => num is 1 or 2 or 4; - - private static bool GetWasDual(EvoCriteria[] evos, PersonalTable pt, ISpeciesForm pk) + if (format >= 8) // Ability Patch { - foreach (var evo in evos) + var evos = data.Info.EvoChainsAllGens; + if (pk.AbilityNumber == 4 && IsAccessibleAbilityPatch(pk, evos)) { - if (evo.Species == pk.Species) - continue; + if (CanAbilityPatch(format, abilities, pk.Species)) + return GetValid(LAbilityPatchUsed); - var pe = pt.GetFormEntry(evo.Species, evo.Form); - var abils = pe.Abilities; - if (CanAbilityCapsule(6, abils)) - return true; + var e = data.EncounterOriginal; + if (e.Species != pk.Species && CanAbilityPatch(format, PKX.Personal.GetFormEntry(e.Species, e.Form).Abilities, e.Species)) + return GetValid(LAbilityPatchUsed); + + // Verify later, it may be encountered with its hidden ability without using an ability patch. } - - return false; } - private CheckResult VerifyAbility(LegalityAnalysis data, IReadOnlyList abilities, int abilIndex) - { - var enc = data.EncounterMatch; - var eabil = enc.Ability; - if (eabil >= 0) - { - if ((data.pkm.AbilityNumber == 4) != (eabil == AbilityPermission.OnlyHidden)) - return GetInvalid(LAbilityHiddenFail); - if (eabil > 0) - return VerifyFixedAbility(data, abilities, AbilityState.CanMismatch, eabil, abilIndex); - } + var enc = data.EncounterMatch; + if (enc is MysteryGift {Generation: >= 4} g) + return VerifyAbilityMG(data, g, abilities); - var gen = enc.Generation; - return gen switch - { - 5 => VerifyAbility5(data, enc, abilities), - 6 => VerifyAbility6(data, enc), - 7 => VerifyAbility7(data, enc), - 8 when data.pkm.BDSP => VerifyAbility8BDSP(data, enc), - >=8 => VALID, - _ => CheckMatch(data.pkm, abilities, gen, AbilityState.CanMismatch, enc), - }; + if (format < 6) + return VerifyAbility345(data, enc, abilities, abilIndex); + + return VerifyAbility(data, abilities, abilIndex); + } + + public static bool IsValidAbilityBits(int num) => num is 1 or 2 or 4; + + private static bool GetWasDual(EvoCriteria[] evos, PersonalTable pt, ISpeciesForm pk) + { + foreach (var evo in evos) + { + if (evo.Species == pk.Species) + continue; + + var pe = pt.GetFormEntry(evo.Species, evo.Form); + var abils = pe.Abilities; + if (CanAbilityCapsule(6, abils)) + return true; } - private CheckResult VerifyAbility345(LegalityAnalysis data, IEncounterable enc, IReadOnlyList abilities, int abilIndex) + return false; + } + + private CheckResult VerifyAbility(LegalityAnalysis data, IReadOnlyList abilities, int abilIndex) + { + var enc = data.EncounterMatch; + var eabil = enc.Ability; + if (eabil >= 0) { - var pkm = data.pkm; - int format = pkm.Format; - var state = AbilityState.MustMatch; - if (format is (3 or 4 or 5) && abilities[0] != abilities[1]) // 3-4/5 and have 2 distinct abilities now - state = VerifyAbilityPreCapsule(data, abilities); - - var encounterAbility = enc.Ability; - if (encounterAbility >= 0) - { - if ((pkm.AbilityNumber == 4) != (encounterAbility == AbilityPermission.OnlyHidden)) - return GetInvalid(LAbilityHiddenFail); - if (encounterAbility > 0) - return VerifyFixedAbility(data, abilities, state, encounterAbility, abilIndex); - } - - int gen = enc.Generation; - if (gen == 5) - return VerifyAbility5(data, enc, abilities); - - return CheckMatch(pkm, abilities, gen, state, enc); - } - - private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList abilities, AbilityState state, AbilityPermission encounterAbility, int abilIndex) - { - var pkm = data.pkm; - var enc = data.Info.EncounterMatch; - if (enc.Generation >= 6) - { - if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility, data.Info.EvoChainsAllGens)) - return GetValid(LAbilityCapsuleUsed); - if (pkm.AbilityNumber != 1 << encounterAbility.GetSingleValue()) - return INVALID; - return VALID; - } - - if ((pkm.AbilityNumber == 4) != (encounterAbility == AbilityPermission.OnlyHidden)) + if ((data.Entity.AbilityNumber == 4) != (eabil == AbilityPermission.OnlyHidden)) return GetInvalid(LAbilityHiddenFail); + if (eabil > 0) + return VerifyFixedAbility(data, abilities, AbilityState.CanMismatch, eabil, abilIndex); + } - bool hasEvolved = enc.Species != pkm.Species; - if (hasEvolved && state != AbilityState.CanMismatch) - { - // Evolving in Gen3 does not mutate the ability bit, so any mismatched abilities will stay mismatched. - if (enc.Generation == 3) - { - if (encounterAbility.GetSingleValue() == abilIndex) - return VALID; + var gen = enc.Generation; + return gen switch + { + 5 => VerifyAbility5(data, enc, abilities), + 6 => VerifyAbility6(data, enc), + 7 => VerifyAbility7(data, enc), + 8 when data.Entity.BDSP => VerifyAbility8BDSP(data, enc), + >=8 => VALID, + _ => CheckMatch(data.Entity, abilities, gen, AbilityState.CanMismatch, enc), + }; + } - // If it is in a future game and does not match the fixed ability, then it must match the PID. - if (pkm.Format != 3) - return GetPIDAbilityMatch(pkm, abilities); + private CheckResult VerifyAbility345(LegalityAnalysis data, IEncounterable enc, IReadOnlyList abilities, int abilIndex) + { + var pk = data.Entity; + int format = pk.Format; + var state = AbilityState.MustMatch; + if (format is (3 or 4 or 5) && abilities[0] != abilities[1]) // 3-4/5 and have 2 distinct abilities now + state = VerifyAbilityPreCapsule(data, abilities); - // No way to un-mismatch it while existing solely on Gen3 games. - return INVALID; - } + var encounterAbility = enc.Ability; + if (encounterAbility >= 0) + { + if ((pk.AbilityNumber == 4) != (encounterAbility == AbilityPermission.OnlyHidden)) + return GetInvalid(LAbilityHiddenFail); + if (encounterAbility > 0) + return VerifyFixedAbility(data, abilities, state, encounterAbility, abilIndex); + } - return CheckMatch(pkm, abilities, enc.Generation, AbilityState.MustMatch, enc); - } + int gen = enc.Generation; + if (gen == 5) + return VerifyAbility5(data, enc, abilities); - if (encounterAbility.GetSingleValue() == abilIndex) - return VALID; + return CheckMatch(pk, abilities, gen, state, enc); + } - if (pkm.AbilityNumber == 1 << encounterAbility.GetSingleValue()) - return VALID; - - if (state == AbilityState.CanMismatch || encounterAbility == 0) - return CheckMatch(pkm, abilities, enc.Generation, AbilityState.MustMatch, enc); - - if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility, data.Info.EvoChainsAllGens)) + private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList abilities, AbilityState state, AbilityPermission encounterAbility, int abilIndex) + { + var pk = data.Entity; + var enc = data.Info.EncounterMatch; + if (enc.Generation >= 6) + { + if (IsAbilityCapsuleModified(pk, abilities, encounterAbility, data.Info.EvoChainsAllGens)) return GetValid(LAbilityCapsuleUsed); - - return INVALID; - } - - private AbilityState VerifyAbilityPreCapsule(LegalityAnalysis data, IReadOnlyList abilities) - { - var info = data.Info; - // Gen4/5 origin - if (info.Generation != 3) - return AbilityState.MustMatch; - - // Gen3 origin... a lot of edge cases to check. - var pkm = data.pkm; - var format = pkm.Format; - // CXD pokemon can have any ability without matching PID - if (format == 3) - { - if (pkm.Version == (int)GameVersion.CXD) - return AbilityState.CanMismatch; - return AbilityState.MustMatch; - } - - // Evovled in Gen4/5 - if (pkm.Species > Legal.MaxSpeciesID_3) - return AbilityState.MustMatch; - - // If the species could not exist in Gen3, must match. - var g3 = info.EvoChainsAllGens.Gen3; - if (g3.Length == 0) - return AbilityState.MustMatch; - - // Fall through when gen3 pkm transferred to gen4/5 - var maxGen3Species = g3[0].Species; - return VerifyAbilityGen3Transfer(data, abilities, maxGen3Species); - } - - private AbilityState VerifyAbilityGen3Transfer(LegalityAnalysis data, IReadOnlyList abilities, int maxGen3Species) - { - var pkm = data.pkm; - var pers = (PersonalInfoG3)PersonalTable.E[maxGen3Species]; - if (pers.Ability1 != pers.Ability2) // Excluding Colosseum/XD, a Gen3 pkm must match PID if it has 2 unique abilities - return pkm.Version == (int) GameVersion.CXD ? AbilityState.CanMismatch : AbilityState.MustMatch; - - if (pkm.Species != maxGen3Species) // it has evolved in either gen 4 or gen 5; the ability must match PID - return AbilityState.MustMatch; - - var chain = data.Info.EvoChainsAllGens; - bool evolved45 = chain.Gen4.Length > 1 || (pkm.Format == 5 && chain.Gen5.Length > 1); - if (evolved45) - { - if (pkm.Ability == pers.Ability1) // Could evolve in Gen4/5 and have a Gen3 only ability - return AbilityState.CanMismatch; // Not evolved in Gen4/5, doesn't need to match PIDAbility - - if (pkm.Ability == abilities[1]) // It could evolve in Gen4/5 and have Gen4 second ability - return AbilityState.MustMatch; // Evolved in Gen4/5, must match PIDAbility - } - - // If we reach here, it has not evolved in Gen4/5 games or has an invalid ability. - // The ability does not need to match the PIDAbility, but only Gen3 ability is allowed. - if (pkm.Ability != pers.Ability1) // Not evolved in Gen4/5, but doesn't have Gen3 only ability - data.AddLine(GetInvalid(LAbilityMismatch3)); // probably bad to do this here - - return AbilityState.CanMismatch; - } - - private CheckResult VerifyAbilityMG(LegalityAnalysis data, MysteryGift g, IReadOnlyList abilities) - { - if (g is PCD d) - return VerifyAbilityPCD(data, abilities, d); - - var pkm = data.pkm; - if (g is PGT) // Ranger Manaphy - return (pkm.Format >= 6 ? (pkm.AbilityNumber == 1) : (pkm.AbilityNumber < 4)) ? VALID : GetInvalid(LAbilityMismatchGift); - - var cardType = g.AbilityType; - if (cardType == 4) // 1/2/H - return VALID; - int abilNumber = pkm.AbilityNumber; - if (cardType == 3) // 1/2 - return abilNumber == 4 ? GetInvalid(LAbilityMismatchGift) : VALID; - - // Only remaining matches are fixed index abilities - int cardAbilIndex = 1 << cardType; - if (abilNumber == cardAbilIndex) - return VALID; - - // Can still match if the ability was changed via ability capsule... - // However, it can't change to/from Hidden Abilities. - if (abilNumber == 4 || cardType == 2) - return GetInvalid(LAbilityHiddenFail); - - // Ability can be flipped 0/1 if Ability Capsule is available, is not Hidden Ability, and Abilities are different. - if (pkm.Format >= 6) - { - if (CanAbilityCapsule(6, abilities)) - return GetValid(LAbilityCapsuleUsed); - - // Maybe was evolved after using ability capsule. - var evos = data.Info.EvoChainsAllGens[pkm.Format]; - if (GetWasDual(evos, PKX.Personal, pkm)) - return GetValid(LAbilityCapsuleUsed); - } - - return pkm.Format < 6 ? GetInvalid(LAbilityMismatchPID) : INVALID; - } - - private CheckResult VerifyAbilityPCD(LegalityAnalysis data, IReadOnlyList abilities, PCD pcd) - { - var pkm = data.pkm; - var format = pkm.Format; - if (format >= 6) - { - if (!CanAbilityCapsule(format, abilities)) - { - // Gen3-5 transfer with same ability -> 1st ability that matches - if (pkm.AbilityNumber == 1) - return VALID; - return CheckMatch(pkm, abilities, 4, AbilityState.MustMatch, pcd); // evolved, must match - } - if (pkm.AbilityNumber < 4) // Ability Capsule can change between 1/2 - return GetValid(LAbilityCapsuleUsed); - } - - if (pcd.Species != pkm.Species) - return CheckMatch(pkm, abilities, 4, AbilityState.MustMatch, pcd); // evolved, must match - - // Edge case (PID ability gift mismatch) -- must match gift ability. - return pkm.Ability == pcd.Gift.PK.Ability ? VALID : INVALID; - } - - private CheckResult VerifyAbility5(LegalityAnalysis data, IEncounterTemplate enc, IReadOnlyList abilities) - { - var pkm = data.pkm; - - // Eggs and Encounter Slots are not yet checked for Hidden Ability potential. - return enc switch - { - EncounterEgg e when pkm.AbilityNumber == 4 && AbilityBreedLegality.BanHidden5.Contains(e.Species) => GetInvalid(LAbilityHiddenUnavailable), - _ => CheckMatch(data.pkm, abilities, 5, pkm.Format == 5 ? AbilityState.MustMatch : AbilityState.CanMismatch, enc), - }; - } - - private CheckResult VerifyAbility6(LegalityAnalysis data, IEncounterTemplate enc) - { - var pkm = data.pkm; - if (pkm.AbilityNumber != 4) - return VALID; - - // Eggs and Encounter Slots are not yet checked for Hidden Ability potential. - return enc switch - { - EncounterEgg egg when AbilityBreedLegality.BanHidden6.Contains(egg.Species | (egg.Form << 11)) => GetInvalid(LAbilityHiddenUnavailable), - _ => VALID, - }; - } - - private CheckResult VerifyAbility7(LegalityAnalysis data, IEncounterTemplate enc) - { - var pkm = data.pkm; - if (pkm.AbilityNumber != 4) - return VALID; - - return enc switch - { - EncounterEgg egg when AbilityBreedLegality.BanHidden7.Contains(egg.Species | (egg.Form << 11)) => GetInvalid(LAbilityHiddenUnavailable), - _ => VALID, - }; - } - - private CheckResult VerifyAbility8BDSP(LegalityAnalysis data, IEncounterable enc) - { - var pkm = data.pkm; - if (pkm.AbilityNumber != 4) - return VALID; - - return enc switch - { - EncounterEgg egg when AbilityBreedLegality.BanHidden8b.Contains(egg.Species | (egg.Form << 11)) => GetInvalid(LAbilityHiddenUnavailable), - _ => VALID, - }; - } - - /// - /// Final checks assuming nothing else has flagged the ability. - /// - /// Pokémon - /// Current abilities - /// Generation - /// Permissive to allow ability to deviate under special circumstances - /// Encounter template the was matched to. - private CheckResult CheckMatch(PKM pkm, IReadOnlyList abilities, int gen, AbilityState state, IEncounterTemplate enc) - { - if (gen is (3 or 4) && pkm.AbilityNumber == 4) - return GetInvalid(LAbilityHiddenUnavailable); - - // other cases of hidden ability already flagged, all that is left is 1/2 mismatching - if (state != AbilityState.MustMatch) - return VALID; - - // Check that the ability bit is correct. - if (pkm is G3PKM g3) - { - var abit = g3.AbilityBit; - // We've sanitized our personal data to replace "None" abilities with the first ability. - // Granbull, Vibrava, and Flygon have dual abilities being the same. - if (abilities[0] == abilities[1] && g3.Species is not ((int)Species.Granbull or (int)Species.Vibrava or (int)Species.Flygon)) // Not a dual ability - { - // Must not have the Ability bit flag set. - // Shadow encounters set a random ability index; don't bother checking if it's a re-battle for ability bit flipping. - if (abit && enc is not EncounterStaticShadow) - return GetInvalid(LAbilityMismatchFlag, CheckIdentifier.PID); - } - else - { - // Gen3 mainline origin sets the Ability index based on the PID, but only if it has two abilities. - // Version value check isn't factually correct, but there are no C/XD gifts with (Version!=15) that have two abilities. - // Pikachu, Celebi, Ho-Oh - if (pkm.Version != (int)GameVersion.CXD && abit != ((pkm.PID & 1) == 1)) - return GetInvalid(LAbilityMismatchPID, CheckIdentifier.PID); - } - } - else if (pkm.Format >= 6) - { - // 6+ already checked at the top of the verifier call stack - return VALID; - } - - // 3-5 - return GetPIDAbilityMatch(pkm, abilities); - } - - private CheckResult GetPIDAbilityMatch(PKM pkm, IReadOnlyList abilities) - { - // Ability Number bits are already verified as clean. - var abil = abilities[pkm.AbilityNumber >> 1]; - if (abil != pkm.Ability) - return GetInvalid(LAbilityMismatchPID); - + if (pk.AbilityNumber != 1 << encounterAbility.GetSingleValue()) + return INVALID; return VALID; } - private static bool IsAccessibleAbilityPatch(PKM pkm, EvolutionHistory evosAll) + if ((pk.AbilityNumber == 4) != (encounterAbility == AbilityPermission.OnlyHidden)) + return GetInvalid(LAbilityHiddenFail); + + bool hasEvolved = enc.Species != pk.Species; + if (hasEvolved && state != AbilityState.CanMismatch) { - return pkm.HasVisitedSWSH(evosAll.Gen8) || pkm.HasVisitedBDSP(evosAll.Gen8b); - } - - private static bool IsAccessibleAbilityCapsule(PKM pkm, EvolutionHistory evosAll) - { - if (evosAll.Gen6.Length > 0 || evosAll.Gen7.Length > 0) - return true; - return pkm.HasVisitedSWSH(evosAll.Gen8) || pkm.HasVisitedBDSP(evosAll.Gen8b); - } - - // Ability Capsule can change between 1/2 - private static bool IsAbilityCapsuleModified(PKM pkm, IReadOnlyList abilities, AbilityPermission encounterAbility, EvolutionHistory evos) - { - if (!IsAccessibleAbilityCapsule(pkm, evos)) - return false; // Not available. - if (!CanAbilityCapsule(pkm.Format, abilities)) - return false; - if (pkm.AbilityNumber == 4) - return false; // Cannot alter to hidden ability. - if (encounterAbility == AbilityPermission.OnlyHidden) - return false; // Cannot alter from hidden ability. - return true; - } - - public static bool CanAbilityCapsule(int format, IReadOnlyList abilities) - { - if (format < 6) // Ability Capsule does not exist - return false; - return abilities[0] != abilities[1]; // Cannot alter ability index if it is the same as the other ability. - } - - public static bool CanAbilityPatch(int format, IReadOnlyList abilities, int species) - { - if (format < 8) // Ability Patch does not exist - return false; - - // Can alter ability index if it is different from the other abilities. - var h = abilities[2]; - if (h != abilities[0] || h != abilities[1]) - return true; - - // Some species have a distinct hidden ability only on another form, and can change between that form and its current form. - return species switch + // Evolving in Gen3 does not mutate the ability bit, so any mismatched abilities will stay mismatched. + if (enc.Generation == 3) { - (int)Species.Giratina => true, // Form-0 is a/a/h - (int)Species.Tornadus => true, // Form-0 is a/a/h - (int)Species.Thundurus => true, // Form-0 is a/a/h - (int)Species.Landorus => true, // Form-0 is a/a/h - (int)Species.Enamorus => true, // Form-0 is a/a/h - _ => false, - }; + if (encounterAbility.GetSingleValue() == abilIndex) + return VALID; + + // If it is in a future game and does not match the fixed ability, then it must match the PID. + if (pk.Format != 3) + return GetPIDAbilityMatch(pk, abilities); + + // No way to un-mismatch it while existing solely on Gen3 games. + return INVALID; + } + + return CheckMatch(pk, abilities, enc.Generation, AbilityState.MustMatch, enc); } + + if (encounterAbility.GetSingleValue() == abilIndex) + return VALID; + + if (pk.AbilityNumber == 1 << encounterAbility.GetSingleValue()) + return VALID; + + if (state == AbilityState.CanMismatch || encounterAbility == 0) + return CheckMatch(pk, abilities, enc.Generation, AbilityState.MustMatch, enc); + + if (IsAbilityCapsuleModified(pk, abilities, encounterAbility, data.Info.EvoChainsAllGens)) + return GetValid(LAbilityCapsuleUsed); + + return INVALID; + } + + private AbilityState VerifyAbilityPreCapsule(LegalityAnalysis data, IReadOnlyList abilities) + { + var info = data.Info; + // Gen4/5 origin + if (info.Generation != 3) + return AbilityState.MustMatch; + + // Gen3 origin... a lot of edge cases to check. + var pk = data.Entity; + var format = pk.Format; + // CXD pokemon can have any ability without matching PID + if (format == 3) + { + if (pk.Version == (int)GameVersion.CXD) + return AbilityState.CanMismatch; + return AbilityState.MustMatch; + } + + // Evovled in Gen4/5 + if (pk.Species > Legal.MaxSpeciesID_3) + return AbilityState.MustMatch; + + // If the species could not exist in Gen3, must match. + var g3 = info.EvoChainsAllGens.Gen3; + if (g3.Length == 0) + return AbilityState.MustMatch; + + // Fall through when gen3 pk transferred to gen4/5 + var maxGen3Species = g3[0].Species; + return VerifyAbilityGen3Transfer(data, abilities, maxGen3Species); + } + + private AbilityState VerifyAbilityGen3Transfer(LegalityAnalysis data, IReadOnlyList abilities, int maxGen3Species) + { + var pk = data.Entity; + var pers = (PersonalInfoG3)PersonalTable.E[maxGen3Species]; + if (pers.Ability1 != pers.Ability2) // Excluding Colosseum/XD, a Gen3 pk must match PID if it has 2 unique abilities + return pk.Version == (int) GameVersion.CXD ? AbilityState.CanMismatch : AbilityState.MustMatch; + + if (pk.Species != maxGen3Species) // it has evolved in either gen 4 or gen 5; the ability must match PID + return AbilityState.MustMatch; + + var chain = data.Info.EvoChainsAllGens; + bool evolved45 = chain.Gen4.Length > 1 || (pk.Format == 5 && chain.Gen5.Length > 1); + if (evolved45) + { + if (pk.Ability == pers.Ability1) // Could evolve in Gen4/5 and have a Gen3 only ability + return AbilityState.CanMismatch; // Not evolved in Gen4/5, doesn't need to match PIDAbility + + if (pk.Ability == abilities[1]) // It could evolve in Gen4/5 and have Gen4 second ability + return AbilityState.MustMatch; // Evolved in Gen4/5, must match PIDAbility + } + + // If we reach here, it has not evolved in Gen4/5 games or has an invalid ability. + // The ability does not need to match the PIDAbility, but only Gen3 ability is allowed. + if (pk.Ability != pers.Ability1) // Not evolved in Gen4/5, but doesn't have Gen3 only ability + data.AddLine(GetInvalid(LAbilityMismatch3)); // probably bad to do this here + + return AbilityState.CanMismatch; + } + + private CheckResult VerifyAbilityMG(LegalityAnalysis data, MysteryGift g, IReadOnlyList abilities) + { + if (g is PCD d) + return VerifyAbilityPCD(data, abilities, d); + + var pk = data.Entity; + if (g is PGT) // Ranger Manaphy + return (pk.Format >= 6 ? (pk.AbilityNumber == 1) : (pk.AbilityNumber < 4)) ? VALID : GetInvalid(LAbilityMismatchGift); + + var cardType = g.AbilityType; + if (cardType == 4) // 1/2/H + return VALID; + int abilNumber = pk.AbilityNumber; + if (cardType == 3) // 1/2 + return abilNumber == 4 ? GetInvalid(LAbilityMismatchGift) : VALID; + + // Only remaining matches are fixed index abilities + int cardAbilIndex = 1 << cardType; + if (abilNumber == cardAbilIndex) + return VALID; + + // Can still match if the ability was changed via ability capsule... + // However, it can't change to/from Hidden Abilities. + if (abilNumber == 4 || cardType == 2) + return GetInvalid(LAbilityHiddenFail); + + // Ability can be flipped 0/1 if Ability Capsule is available, is not Hidden Ability, and Abilities are different. + if (pk.Format >= 6) + { + if (CanAbilityCapsule(6, abilities)) + return GetValid(LAbilityCapsuleUsed); + + // Maybe was evolved after using ability capsule. + var evos = data.Info.EvoChainsAllGens[pk.Format]; + if (GetWasDual(evos, PKX.Personal, pk)) + return GetValid(LAbilityCapsuleUsed); + } + + return pk.Format < 6 ? GetInvalid(LAbilityMismatchPID) : INVALID; + } + + private CheckResult VerifyAbilityPCD(LegalityAnalysis data, IReadOnlyList abilities, PCD pcd) + { + var pk = data.Entity; + var format = pk.Format; + if (format >= 6) + { + if (!CanAbilityCapsule(format, abilities)) + { + // Gen3-5 transfer with same ability -> 1st ability that matches + if (pk.AbilityNumber == 1) + return VALID; + return CheckMatch(pk, abilities, 4, AbilityState.MustMatch, pcd); // evolved, must match + } + if (pk.AbilityNumber < 4) // Ability Capsule can change between 1/2 + return GetValid(LAbilityCapsuleUsed); + } + + if (pcd.Species != pk.Species) + return CheckMatch(pk, abilities, 4, AbilityState.MustMatch, pcd); // evolved, must match + + // Edge case (PID ability gift mismatch) -- must match gift ability. + return pk.Ability == pcd.Gift.PK.Ability ? VALID : INVALID; + } + + private CheckResult VerifyAbility5(LegalityAnalysis data, IEncounterTemplate enc, IReadOnlyList abilities) + { + var pk = data.Entity; + + // Eggs and Encounter Slots are not yet checked for Hidden Ability potential. + return enc switch + { + EncounterEgg e when pk.AbilityNumber == 4 && AbilityBreedLegality.BanHidden5.Contains(e.Species) => GetInvalid(LAbilityHiddenUnavailable), + _ => CheckMatch(data.Entity, abilities, 5, pk.Format == 5 ? AbilityState.MustMatch : AbilityState.CanMismatch, enc), + }; + } + + private CheckResult VerifyAbility6(LegalityAnalysis data, IEncounterTemplate enc) + { + var pk = data.Entity; + if (pk.AbilityNumber != 4) + return VALID; + + // Eggs and Encounter Slots are not yet checked for Hidden Ability potential. + return enc switch + { + EncounterEgg egg when AbilityBreedLegality.BanHidden6.Contains(egg.Species | (egg.Form << 11)) => GetInvalid(LAbilityHiddenUnavailable), + _ => VALID, + }; + } + + private CheckResult VerifyAbility7(LegalityAnalysis data, IEncounterTemplate enc) + { + var pk = data.Entity; + if (pk.AbilityNumber != 4) + return VALID; + + return enc switch + { + EncounterEgg egg when AbilityBreedLegality.BanHidden7.Contains(egg.Species | (egg.Form << 11)) => GetInvalid(LAbilityHiddenUnavailable), + _ => VALID, + }; + } + + private CheckResult VerifyAbility8BDSP(LegalityAnalysis data, IEncounterable enc) + { + var pk = data.Entity; + if (pk.AbilityNumber != 4) + return VALID; + + return enc switch + { + EncounterEgg egg when AbilityBreedLegality.BanHidden8b.Contains(egg.Species | (egg.Form << 11)) => GetInvalid(LAbilityHiddenUnavailable), + _ => VALID, + }; + } + + /// + /// Final checks assuming nothing else has flagged the ability. + /// + /// Pokémon + /// Current abilities + /// Generation + /// Permissive to allow ability to deviate under special circumstances + /// Encounter template the was matched to. + private CheckResult CheckMatch(PKM pk, IReadOnlyList abilities, int gen, AbilityState state, IEncounterTemplate enc) + { + if (gen is (3 or 4) && pk.AbilityNumber == 4) + return GetInvalid(LAbilityHiddenUnavailable); + + // other cases of hidden ability already flagged, all that is left is 1/2 mismatching + if (state != AbilityState.MustMatch) + return VALID; + + // Check that the ability bit is correct. + if (pk is G3PKM g3) + { + var abit = g3.AbilityBit; + // We've sanitized our personal data to replace "None" abilities with the first ability. + // Granbull, Vibrava, and Flygon have dual abilities being the same. + if (abilities[0] == abilities[1] && g3.Species is not ((int)Species.Granbull or (int)Species.Vibrava or (int)Species.Flygon)) // Not a dual ability + { + // Must not have the Ability bit flag set. + // Shadow encounters set a random ability index; don't bother checking if it's a re-battle for ability bit flipping. + if (abit && enc is not EncounterStaticShadow) + return GetInvalid(LAbilityMismatchFlag, CheckIdentifier.PID); + } + else + { + // Gen3 mainline origin sets the Ability index based on the PID, but only if it has two abilities. + // Version value check isn't factually correct, but there are no C/XD gifts with (Version!=15) that have two abilities. + // Pikachu, Celebi, Ho-Oh + if (pk.Version != (int)GameVersion.CXD && abit != ((pk.PID & 1) == 1)) + return GetInvalid(LAbilityMismatchPID, CheckIdentifier.PID); + } + } + else if (pk.Format >= 6) + { + // 6+ already checked at the top of the verifier call stack + return VALID; + } + + // 3-5 + return GetPIDAbilityMatch(pk, abilities); + } + + private CheckResult GetPIDAbilityMatch(PKM pk, IReadOnlyList abilities) + { + // Ability Number bits are already verified as clean. + var abil = abilities[pk.AbilityNumber >> 1]; + if (abil != pk.Ability) + return GetInvalid(LAbilityMismatchPID); + + return VALID; + } + + private static bool IsAccessibleAbilityPatch(PKM pk, EvolutionHistory evosAll) + { + return pk.HasVisitedSWSH(evosAll.Gen8) || pk.HasVisitedBDSP(evosAll.Gen8b); + } + + private static bool IsAccessibleAbilityCapsule(PKM pk, EvolutionHistory evosAll) + { + if (evosAll.Gen6.Length > 0 || evosAll.Gen7.Length > 0) + return true; + return pk.HasVisitedSWSH(evosAll.Gen8) || pk.HasVisitedBDSP(evosAll.Gen8b); + } + + // Ability Capsule can change between 1/2 + private static bool IsAbilityCapsuleModified(PKM pk, IReadOnlyList abilities, AbilityPermission encounterAbility, EvolutionHistory evos) + { + if (!IsAccessibleAbilityCapsule(pk, evos)) + return false; // Not available. + if (!CanAbilityCapsule(pk.Format, abilities)) + return false; + if (pk.AbilityNumber == 4) + return false; // Cannot alter to hidden ability. + if (encounterAbility == AbilityPermission.OnlyHidden) + return false; // Cannot alter from hidden ability. + return true; + } + + public static bool CanAbilityCapsule(int format, IReadOnlyList abilities) + { + if (format < 6) // Ability Capsule does not exist + return false; + return abilities[0] != abilities[1]; // Cannot alter ability index if it is the same as the other ability. + } + + public static bool CanAbilityPatch(int format, IReadOnlyList abilities, int species) + { + if (format < 8) // Ability Patch does not exist + return false; + + // Can alter ability index if it is different from the other abilities. + var h = abilities[2]; + if (h != abilities[0] || h != abilities[1]) + return true; + + // Some species have a distinct hidden ability only on another form, and can change between that form and its current form. + return species switch + { + (int)Species.Giratina => true, // Form-0 is a/a/h + (int)Species.Tornadus => true, // Form-0 is a/a/h + (int)Species.Thundurus => true, // Form-0 is a/a/h + (int)Species.Landorus => true, // Form-0 is a/a/h + (int)Species.Enamorus => true, // Form-0 is a/a/h + _ => false, + }; } } diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs index 4447a2b9a..8a16bd060 100644 --- a/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs +++ b/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs @@ -2,999 +2,998 @@ using System.Linq; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Tables used for +/// +internal static class BallBreedLegality { /// - /// Tables used for + /// Species that are male only in Generation 6; for ball inheritance, these behave the same as Genderless species (no ball inherited). /// - internal static class BallBreedLegality + internal static readonly HashSet BreedMaleOnly6 = new() { - /// - /// Species that are male only in Generation 6; for ball inheritance, these behave the same as Genderless species (no ball inherited). - /// - internal static readonly HashSet BreedMaleOnly6 = new() - { - (int)Tauros, - (int)Rufflet, - (int)Braviary, - (int)Tyrogue, - (int)Sawk, - (int)Throh, - }; + (int)Tauros, + (int)Rufflet, + (int)Braviary, + (int)Tyrogue, + (int)Sawk, + (int)Throh, + }; - internal static readonly HashSet PastGenAlolanNativesUncapturable = new() - { - // Porygon++ - (int)Porygon, - // Gen1 Fossils - (int)Aerodactyl, (int)Omanyte, (int)Kabuto, - // Gen3 Fossils - (int)Lileep, (int)Anorith, - // Gen4 Fossils - (int)Cranidos, (int)Shieldon, - // Gen5 Fossils - (int)Tirtouga, (int)Archen, - // Gen6 Fossils - (int)Tyrunt, (int)Amaura, - }; + internal static readonly HashSet PastGenAlolanNativesUncapturable = new() + { + // Porygon++ + (int)Porygon, + // Gen1 Fossils + (int)Aerodactyl, (int)Omanyte, (int)Kabuto, + // Gen3 Fossils + (int)Lileep, (int)Anorith, + // Gen4 Fossils + (int)Cranidos, (int)Shieldon, + // Gen5 Fossils + (int)Tirtouga, (int)Archen, + // Gen6 Fossils + (int)Tyrunt, (int)Amaura, + }; - internal static readonly HashSet PastGenAlolanScans = new() - { - (int)Bellsprout, - (int)Rhyhorn, - (int)Horsea, - (int)Chikorita, - (int)Cyndaquil, - (int)Totodile, - (int)Swinub, - (int)Spheal, - (int)Venipede, - (int)Gothita, - (int)Klink, - (int)Litwick, - (int)Axew, - (int)Deino, - (int)Honedge, - (int)Marill, (int)Azurill, - (int)Roselia, (int)Budew, - (int)Togepi, - (int)Slakoth, - (int)Starly, - (int)Shinx, - (int)Snivy, - (int)Solosis, - (int)Tepig, - (int)Oshawott, - (int)Timburr, - (int)Sewaddle, - (int)Tynamo, - (int)Charmander, - (int)Squirtle, - (int)Onix, - (int)Talonflame, - (int)Scatterbug, - (int)Bulbasaur, - (int)Ralts, - (int)Weedle, - (int)Torchic, - (int)Treecko, - (int)Mudkip, - (int)Piplup, - (int)Turtwig, - (int)Pidgey, - (int)Chimchar, - (int)Aron, - (int)Rotom, - (int)Chespin, - (int)Fennekin, - (int)Froakie, - }; + internal static readonly HashSet PastGenAlolanScans = new() + { + (int)Bellsprout, + (int)Rhyhorn, + (int)Horsea, + (int)Chikorita, + (int)Cyndaquil, + (int)Totodile, + (int)Swinub, + (int)Spheal, + (int)Venipede, + (int)Gothita, + (int)Klink, + (int)Litwick, + (int)Axew, + (int)Deino, + (int)Honedge, + (int)Marill, (int)Azurill, + (int)Roselia, (int)Budew, + (int)Togepi, + (int)Slakoth, + (int)Starly, + (int)Shinx, + (int)Snivy, + (int)Solosis, + (int)Tepig, + (int)Oshawott, + (int)Timburr, + (int)Sewaddle, + (int)Tynamo, + (int)Charmander, + (int)Squirtle, + (int)Onix, + (int)Talonflame, + (int)Scatterbug, + (int)Bulbasaur, + (int)Ralts, + (int)Weedle, + (int)Torchic, + (int)Treecko, + (int)Mudkip, + (int)Piplup, + (int)Turtwig, + (int)Pidgey, + (int)Chimchar, + (int)Aron, + (int)Rotom, + (int)Chespin, + (int)Fennekin, + (int)Froakie, + }; - internal static readonly HashSet Inherit_Sport = new() - { - (int)Caterpie, - (int)Weedle, - (int)Paras, - (int)Venonat, - (int)Scyther, - (int)Pinsir, - (int)Wurmple, - (int)Nincada, - (int)Illumise, - (int)Kricketot, - (int)Combee, - (int)Volbeat, - }; + internal static readonly HashSet Inherit_Sport = new() + { + (int)Caterpie, + (int)Weedle, + (int)Paras, + (int)Venonat, + (int)Scyther, + (int)Pinsir, + (int)Wurmple, + (int)Nincada, + (int)Illumise, + (int)Kricketot, + (int)Combee, + (int)Volbeat, + }; - internal static readonly HashSet Inherit_Safari = new() - { - (int)Pidgey, - (int)Rattata, - (int)Spearow, - (int)Ekans, - (int)Sandshrew, - (int)NidoranF, - (int)Zubat, - (int)Oddish, - (int)Paras, - (int)Venonat, - (int)Diglett, - (int)Psyduck, - (int)Poliwag, - (int)Abra, - (int)Machop, - (int)Bellsprout, - (int)Geodude, - (int)Ponyta, - (int)Slowpoke, - (int)Farfetchd, - (int)Doduo, - (int)Grimer, - (int)Gastly, - (int)Onix, - (int)Drowzee, - (int)Krabby, - (int)Exeggcute, - (int)Cubone, - (int)Lickitung, - (int)Koffing, - (int)Rhyhorn, - (int)Chansey, - (int)Tangela, - (int)Kangaskhan, - (int)Goldeen, - (int)MrMime, - (int)Scyther, - (int)Pinsir, - (int)Magikarp, - (int)Lapras, - (int)Dratini, - (int)Sentret, - (int)Hoothoot, - (int)Ledyba, - (int)Spinarak, - (int)Natu, - (int)Mareep, - (int)Marill, - (int)Hoppip, - (int)Jumpluff, - (int)Aipom, - (int)Sunkern, - (int)Yanma, - (int)Wooper, - (int)Murkrow, - (int)Misdreavus, - (int)Wobbuffet, - (int)Girafarig, - (int)Pineco, - (int)Gligar, - (int)Snubbull, - (int)Shuckle, - (int)Heracross, - (int)Teddiursa, - (int)Remoraid, - (int)Houndour, - (int)Phanpy, - (int)Stantler, - (int)Smeargle, - (int)Miltank, - (int)Larvitar, - (int)Zigzagoon, - (int)Linoone, - (int)Lotad, - (int)Seedot, - (int)Surskit, - (int)Shroomish, - (int)Slakoth, - (int)Nosepass, - (int)Aron, - (int)Lairon, - (int)Meditite, - (int)Electrike, - (int)Illumise, - (int)Roselia, - (int)Gulpin, - (int)Carvanha, - (int)Torkoal, - (int)Spinda, - (int)Trapinch, - (int)Cacnea, - (int)Zangoose, - (int)Seviper, - (int)Barboach, - (int)Corphish, - (int)Kecleon, - (int)Shuppet, - (int)Duskull, - (int)Tropius, - (int)Chimecho, - (int)Spheal, - (int)Bagon, - (int)Starly, - (int)Bidoof, - (int)Shinx, - (int)Budew, - (int)Pachirisu, - (int)Buizel, - (int)Chingling, - (int)Gible, - (int)Riolu, - (int)Hippopotas, - (int)Skorupi, - (int)Croagunk, - (int)Carnivine, + internal static readonly HashSet Inherit_Safari = new() + { + (int)Pidgey, + (int)Rattata, + (int)Spearow, + (int)Ekans, + (int)Sandshrew, + (int)NidoranF, + (int)Zubat, + (int)Oddish, + (int)Paras, + (int)Venonat, + (int)Diglett, + (int)Psyduck, + (int)Poliwag, + (int)Abra, + (int)Machop, + (int)Bellsprout, + (int)Geodude, + (int)Ponyta, + (int)Slowpoke, + (int)Farfetchd, + (int)Doduo, + (int)Grimer, + (int)Gastly, + (int)Onix, + (int)Drowzee, + (int)Krabby, + (int)Exeggcute, + (int)Cubone, + (int)Lickitung, + (int)Koffing, + (int)Rhyhorn, + (int)Chansey, + (int)Tangela, + (int)Kangaskhan, + (int)Goldeen, + (int)MrMime, + (int)Scyther, + (int)Pinsir, + (int)Magikarp, + (int)Lapras, + (int)Dratini, + (int)Sentret, + (int)Hoothoot, + (int)Ledyba, + (int)Spinarak, + (int)Natu, + (int)Mareep, + (int)Marill, + (int)Hoppip, + (int)Jumpluff, + (int)Aipom, + (int)Sunkern, + (int)Yanma, + (int)Wooper, + (int)Murkrow, + (int)Misdreavus, + (int)Wobbuffet, + (int)Girafarig, + (int)Pineco, + (int)Gligar, + (int)Snubbull, + (int)Shuckle, + (int)Heracross, + (int)Teddiursa, + (int)Remoraid, + (int)Houndour, + (int)Phanpy, + (int)Stantler, + (int)Smeargle, + (int)Miltank, + (int)Larvitar, + (int)Zigzagoon, + (int)Linoone, + (int)Lotad, + (int)Seedot, + (int)Surskit, + (int)Shroomish, + (int)Slakoth, + (int)Nosepass, + (int)Aron, + (int)Lairon, + (int)Meditite, + (int)Electrike, + (int)Illumise, + (int)Roselia, + (int)Gulpin, + (int)Carvanha, + (int)Torkoal, + (int)Spinda, + (int)Trapinch, + (int)Cacnea, + (int)Zangoose, + (int)Seviper, + (int)Barboach, + (int)Corphish, + (int)Kecleon, + (int)Shuppet, + (int)Duskull, + (int)Tropius, + (int)Chimecho, + (int)Spheal, + (int)Bagon, + (int)Starly, + (int)Bidoof, + (int)Shinx, + (int)Budew, + (int)Pachirisu, + (int)Buizel, + (int)Chingling, + (int)Gible, + (int)Riolu, + (int)Hippopotas, + (int)Skorupi, + (int)Croagunk, + (int)Carnivine, - // Splitbreed/Baby - (int)NidoranM, // Via Nidoran-F - (int)Volbeat, // Via Illumise - (int)Pichu, - (int)Cleffa, - (int)Igglybuff, - (int)Elekid, - (int)Magby, - (int)Azurill, - (int)Wynaut, - (int)Budew, - (int)Chingling, - (int)MimeJr, - (int)Happiny, - }; + // Splitbreed/Baby + (int)NidoranM, // Via Nidoran-F + (int)Volbeat, // Via Illumise + (int)Pichu, + (int)Cleffa, + (int)Igglybuff, + (int)Elekid, + (int)Magby, + (int)Azurill, + (int)Wynaut, + (int)Budew, + (int)Chingling, + (int)MimeJr, + (int)Happiny, + }; - internal static readonly HashSet Inherit_Dream = new() - { - (int)Caterpie, - (int)Weedle, - (int)Pidgey, - (int)Rattata, - (int)Spearow, - (int)Ekans, - (int)Sandshrew, - (int)NidoranF, - (int)Vulpix, - (int)Zubat, - (int)Oddish, - (int)Paras, - (int)Venonat, - (int)Diglett, - (int)Meowth, - (int)Psyduck, - (int)Mankey, - (int)Growlithe, - (int)Poliwag, - (int)Abra, - (int)Machop, - (int)Bellsprout, - (int)Tentacool, - (int)Geodude, - (int)Ponyta, - (int)Slowpoke, - (int)Farfetchd, - (int)Doduo, - (int)Seel, - (int)Grimer, - (int)Shellder, - (int)Gastly, - (int)Onix, - (int)Drowzee, - (int)Krabby, - (int)Exeggcute, - (int)Cubone, - (int)Lickitung, - (int)Koffing, - (int)Rhyhorn, - (int)Chansey, - (int)Tangela, - (int)Kangaskhan, - (int)Horsea, - (int)Goldeen, - (int)MrMime, - (int)Scyther, - (int)Pinsir, - (int)Magikarp, - (int)Lapras, - (int)Eevee, - (int)Omanyte, - (int)Kabuto, - (int)Aerodactyl, - (int)Snorlax, - (int)Dratini, - (int)Sentret, - (int)Hoothoot, - (int)Ledyba, - (int)Spinarak, - (int)Chinchou, - (int)Cleffa, - (int)Igglybuff, - (int)Togepi, - (int)Natu, - (int)Mareep, - (int)Marill, - (int)Sudowoodo, - (int)Hoppip, - (int)Aipom, - (int)Sunkern, - (int)Yanma, - (int)Wooper, - (int)Murkrow, - (int)Misdreavus, - (int)Wobbuffet, - (int)Girafarig, - (int)Pineco, - (int)Dunsparce, - (int)Gligar, - (int)Snubbull, - (int)Qwilfish, - (int)Shuckle, - (int)Heracross, - (int)Sneasel, - (int)Teddiursa, - (int)Slugma, - (int)Swinub, - (int)Corsola, - (int)Remoraid, - (int)Delibird, - (int)Mantine, - (int)Skarmory, - (int)Houndour, - (int)Phanpy, - (int)Stantler, - (int)Smeargle, - (int)Smoochum, - (int)Elekid, - (int)Magby, - (int)Miltank, - (int)Larvitar, - (int)Poochyena, - (int)Zigzagoon, - (int)Wurmple, - (int)Lotad, - (int)Seedot, - (int)Taillow, - (int)Wingull, - (int)Ralts, - (int)Surskit, - (int)Shroomish, - (int)Slakoth, - (int)Nincada, - (int)Whismur, - (int)Makuhita, - (int)Nosepass, - (int)Skitty, - (int)Sableye, - (int)Mawile, - (int)Aron, - (int)Meditite, - (int)Electrike, - (int)Plusle, - (int)Minun, - (int)Illumise, - (int)Roselia, - (int)Gulpin, - (int)Carvanha, - (int)Wailmer, - (int)Numel, - (int)Torkoal, - (int)Spoink, - (int)Spinda, - (int)Trapinch, - (int)Cacnea, - (int)Swablu, - (int)Zangoose, - (int)Seviper, - (int)Barboach, - (int)Corphish, - (int)Lileep, - (int)Anorith, - (int)Feebas, - (int)Castform, - (int)Kecleon, - (int)Shuppet, - (int)Duskull, - (int)Tropius, - (int)Chimecho, - (int)Absol, - (int)Snorunt, - (int)Spheal, - (int)Clamperl, - (int)Relicanth, - (int)Luvdisc, - (int)Bagon, - (int)Starly, - (int)Bidoof, - (int)Kricketot, - (int)Shinx, - (int)Cranidos, - (int)Shieldon, - (int)Burmy, - (int)Combee, - (int)Pachirisu, - (int)Buizel, - (int)Cherubi, - (int)Shellos, - (int)Drifloon, - (int)Buneary, - (int)Glameow, - (int)Stunky, - (int)Chatot, - (int)Spiritomb, - (int)Gible, - (int)Riolu, - (int)Hippopotas, - (int)Skorupi, - (int)Croagunk, - (int)Carnivine, - (int)Finneon, - (int)Snover, - (int)Munna, - (int)Pidove, - (int)Boldore, - (int)Drilbur, - (int)Audino, - (int)Gurdurr, - (int)Tympole, - (int)Scolipede, - (int)Cottonee, - (int)Petilil, - (int)Basculin, - (int)Krookodile, - (int)Maractus, - (int)Crustle, - (int)Scraggy, - (int)Sigilyph, - (int)Tirtouga, - (int)Duosion, - (int)Ducklett, - (int)Vanillish, - (int)Emolga, - (int)Karrablast, - (int)Alomomola, - (int)Galvantula, - (int)Elgyem, - (int)Axew, - (int)Shelmet, - (int)Stunfisk, - (int)Druddigon, - (int)Pawniard, - (int)Heatmor, - (int)Durant, - (int)NidoranM, // Via Nidoran-F - (int)Volbeat, // Via Illumise + internal static readonly HashSet Inherit_Dream = new() + { + (int)Caterpie, + (int)Weedle, + (int)Pidgey, + (int)Rattata, + (int)Spearow, + (int)Ekans, + (int)Sandshrew, + (int)NidoranF, + (int)Vulpix, + (int)Zubat, + (int)Oddish, + (int)Paras, + (int)Venonat, + (int)Diglett, + (int)Meowth, + (int)Psyduck, + (int)Mankey, + (int)Growlithe, + (int)Poliwag, + (int)Abra, + (int)Machop, + (int)Bellsprout, + (int)Tentacool, + (int)Geodude, + (int)Ponyta, + (int)Slowpoke, + (int)Farfetchd, + (int)Doduo, + (int)Seel, + (int)Grimer, + (int)Shellder, + (int)Gastly, + (int)Onix, + (int)Drowzee, + (int)Krabby, + (int)Exeggcute, + (int)Cubone, + (int)Lickitung, + (int)Koffing, + (int)Rhyhorn, + (int)Chansey, + (int)Tangela, + (int)Kangaskhan, + (int)Horsea, + (int)Goldeen, + (int)MrMime, + (int)Scyther, + (int)Pinsir, + (int)Magikarp, + (int)Lapras, + (int)Eevee, + (int)Omanyte, + (int)Kabuto, + (int)Aerodactyl, + (int)Snorlax, + (int)Dratini, + (int)Sentret, + (int)Hoothoot, + (int)Ledyba, + (int)Spinarak, + (int)Chinchou, + (int)Cleffa, + (int)Igglybuff, + (int)Togepi, + (int)Natu, + (int)Mareep, + (int)Marill, + (int)Sudowoodo, + (int)Hoppip, + (int)Aipom, + (int)Sunkern, + (int)Yanma, + (int)Wooper, + (int)Murkrow, + (int)Misdreavus, + (int)Wobbuffet, + (int)Girafarig, + (int)Pineco, + (int)Dunsparce, + (int)Gligar, + (int)Snubbull, + (int)Qwilfish, + (int)Shuckle, + (int)Heracross, + (int)Sneasel, + (int)Teddiursa, + (int)Slugma, + (int)Swinub, + (int)Corsola, + (int)Remoraid, + (int)Delibird, + (int)Mantine, + (int)Skarmory, + (int)Houndour, + (int)Phanpy, + (int)Stantler, + (int)Smeargle, + (int)Smoochum, + (int)Elekid, + (int)Magby, + (int)Miltank, + (int)Larvitar, + (int)Poochyena, + (int)Zigzagoon, + (int)Wurmple, + (int)Lotad, + (int)Seedot, + (int)Taillow, + (int)Wingull, + (int)Ralts, + (int)Surskit, + (int)Shroomish, + (int)Slakoth, + (int)Nincada, + (int)Whismur, + (int)Makuhita, + (int)Nosepass, + (int)Skitty, + (int)Sableye, + (int)Mawile, + (int)Aron, + (int)Meditite, + (int)Electrike, + (int)Plusle, + (int)Minun, + (int)Illumise, + (int)Roselia, + (int)Gulpin, + (int)Carvanha, + (int)Wailmer, + (int)Numel, + (int)Torkoal, + (int)Spoink, + (int)Spinda, + (int)Trapinch, + (int)Cacnea, + (int)Swablu, + (int)Zangoose, + (int)Seviper, + (int)Barboach, + (int)Corphish, + (int)Lileep, + (int)Anorith, + (int)Feebas, + (int)Castform, + (int)Kecleon, + (int)Shuppet, + (int)Duskull, + (int)Tropius, + (int)Chimecho, + (int)Absol, + (int)Snorunt, + (int)Spheal, + (int)Clamperl, + (int)Relicanth, + (int)Luvdisc, + (int)Bagon, + (int)Starly, + (int)Bidoof, + (int)Kricketot, + (int)Shinx, + (int)Cranidos, + (int)Shieldon, + (int)Burmy, + (int)Combee, + (int)Pachirisu, + (int)Buizel, + (int)Cherubi, + (int)Shellos, + (int)Drifloon, + (int)Buneary, + (int)Glameow, + (int)Stunky, + (int)Chatot, + (int)Spiritomb, + (int)Gible, + (int)Riolu, + (int)Hippopotas, + (int)Skorupi, + (int)Croagunk, + (int)Carnivine, + (int)Finneon, + (int)Snover, + (int)Munna, + (int)Pidove, + (int)Boldore, + (int)Drilbur, + (int)Audino, + (int)Gurdurr, + (int)Tympole, + (int)Scolipede, + (int)Cottonee, + (int)Petilil, + (int)Basculin, + (int)Krookodile, + (int)Maractus, + (int)Crustle, + (int)Scraggy, + (int)Sigilyph, + (int)Tirtouga, + (int)Duosion, + (int)Ducklett, + (int)Vanillish, + (int)Emolga, + (int)Karrablast, + (int)Alomomola, + (int)Galvantula, + (int)Elgyem, + (int)Axew, + (int)Shelmet, + (int)Stunfisk, + (int)Druddigon, + (int)Pawniard, + (int)Heatmor, + (int)Durant, + (int)NidoranM, // Via Nidoran-F + (int)Volbeat, // Via Illumise - // Via Evolution - (int)Roggenrola, - (int)Timburr, - (int)Venipede, - (int)Sandile, - (int)Dwebble, - (int)Solosis, - (int)Vanillite, - (int)Joltik, + // Via Evolution + (int)Roggenrola, + (int)Timburr, + (int)Venipede, + (int)Sandile, + (int)Dwebble, + (int)Solosis, + (int)Vanillite, + (int)Joltik, - // Via Incense Breeding - (int)Azurill, - (int)Wynaut, - (int)Budew, - (int)Chingling, - (int)Bonsly, - (int)MimeJr, - (int)Happiny, - (int)Munchlax, - (int)Mantyke, - }; + // Via Incense Breeding + (int)Azurill, + (int)Wynaut, + (int)Budew, + (int)Chingling, + (int)Bonsly, + (int)MimeJr, + (int)Happiny, + (int)Munchlax, + (int)Mantyke, + }; - internal static readonly HashSet Ban_DreamHidden = new() - { - (int)Plusle, - (int)Minun, - (int)Kecleon, - (int)Duskull, - }; + internal static readonly HashSet Ban_DreamHidden = new() + { + (int)Plusle, + (int)Minun, + (int)Kecleon, + (int)Duskull, + }; - internal static readonly HashSet Ban_Gen3Ball = new() - { - (int)Treecko, (int)Torchic, (int)Mudkip, - (int)Turtwig, (int)Chimchar, (int)Piplup, - (int)Snivy, (int)Tepig, (int)Oshawott, + internal static readonly HashSet Ban_Gen3Ball = new() + { + (int)Treecko, (int)Torchic, (int)Mudkip, + (int)Turtwig, (int)Chimchar, (int)Piplup, + (int)Snivy, (int)Tepig, (int)Oshawott, - // Fossil Only obtain - (int)Archen, (int)Tyrunt, (int)Amaura, - }; + // Fossil Only obtain + (int)Archen, (int)Tyrunt, (int)Amaura, + }; - internal static readonly HashSet Ban_Gen3BallHidden = new() - { - // can have HA and can be in gen 3 ball as eggs but can not at same time. - (int)Chikorita, (int)Cyndaquil, (int)Totodile, - (int)Deerling + (1 << 11), //Deerling-Summer - (int)Deerling + (2 << 11), //Deerling-Autumn - (int)Deerling + (3 << 11), //Deerling-Winter - (int)Pumpkaboo + (3 << 11), //Pumpkaboo-Super - }; + internal static readonly HashSet Ban_Gen3BallHidden = new() + { + // can have HA and can be in gen 3 ball as eggs but can not at same time. + (int)Chikorita, (int)Cyndaquil, (int)Totodile, + (int)Deerling + (1 << 11), //Deerling-Summer + (int)Deerling + (2 << 11), //Deerling-Autumn + (int)Deerling + (3 << 11), //Deerling-Winter + (int)Pumpkaboo + (3 << 11), //Pumpkaboo-Super + }; - internal static readonly HashSet Ban_Gen4Ball_6 = new() - { - (int)Chikorita, (int)Cyndaquil, (int)Totodile, - (int)Treecko, (int)Torchic, (int)Mudkip, - (int)Turtwig, (int)Chimchar, (int)Piplup, - (int)Snivy, (int)Tepig, (int)Oshawott, + internal static readonly HashSet Ban_Gen4Ball_6 = new() + { + (int)Chikorita, (int)Cyndaquil, (int)Totodile, + (int)Treecko, (int)Torchic, (int)Mudkip, + (int)Turtwig, (int)Chimchar, (int)Piplup, + (int)Snivy, (int)Tepig, (int)Oshawott, - // Fossil Only obtain - (int)Archen, (int)Tyrunt, (int)Amaura, - }; + // Fossil Only obtain + (int)Archen, (int)Tyrunt, (int)Amaura, + }; - internal static readonly HashSet Inherit_Apricorn6 = new() - { - (int)Caterpie, - (int)Weedle, - (int)Pidgey, - (int)Rattata, - (int)Spearow, - (int)Ekans, - (int)Sandshrew, - (int)NidoranF, - (int)Vulpix, - (int)Zubat, - (int)Oddish, - (int)Paras, - (int)Venonat, - (int)Diglett, - (int)Meowth, - (int)Psyduck, - (int)Mankey, - (int)Growlithe, - (int)Poliwag, - (int)Abra, - (int)Machop, - (int)Bellsprout, - (int)Tentacool, - (int)Geodude, - (int)Ponyta, - (int)Slowpoke, - (int)Farfetchd, - (int)Doduo, - (int)Seel, - (int)Grimer, - (int)Shellder, - (int)Gastly, - (int)Onix, - (int)Drowzee, - (int)Krabby, - (int)Exeggcute, - (int)Cubone, - (int)Lickitung, - (int)Koffing, - (int)Rhyhorn, - (int)Chansey, - (int)Tangela, - (int)Kangaskhan, - (int)Horsea, - (int)Goldeen, - (int)MrMime, - (int)Magikarp, - (int)Lapras, - (int)Snorlax, - (int)Dratini, - (int)Sentret, - (int)Hoothoot, - (int)Ledyba, - (int)Spinarak, - (int)Chinchou, - (int)Natu, - (int)Mareep, - (int)Marill, - (int)Sudowoodo, - (int)Hoppip, - (int)Aipom, - (int)Sunkern, - (int)Yanma, - (int)Wooper, - (int)Murkrow, - (int)Misdreavus, - (int)Wobbuffet, - (int)Girafarig, - (int)Pineco, - (int)Dunsparce, - (int)Gligar, - (int)Snubbull, - (int)Qwilfish, - (int)Shuckle, - (int)Heracross, - (int)Sneasel, - (int)Teddiursa, - (int)Slugma, - (int)Swinub, - (int)Corsola, - (int)Remoraid, - (int)Delibird, - (int)Mantine, - (int)Skarmory, - (int)Houndour, - (int)Phanpy, - (int)Stantler, - (int)Smeargle, - (int)Miltank, - (int)Larvitar, - (int)Poochyena, - (int)Zigzagoon, - (int)Wurmple, - (int)Seedot, - (int)Taillow, - (int)Wingull, - (int)Ralts, - (int)Shroomish, - (int)Slakoth, - (int)Whismur, - (int)Makuhita, - (int)Sableye, - (int)Mawile, - (int)Meditite, - (int)Plusle, - (int)Minun, - (int)Gulpin, - (int)Numel, - (int)Spoink, - (int)Spinda, - (int)Swablu, - (int)Barboach, - (int)Absol, - (int)Clamperl, - (int)Relicanth, - (int)Luvdisc, - (int)Starly, - (int)Bidoof, - (int)Kricketot, - (int)Shinx, - (int)Budew, - (int)Burmy, - (int)Combee, - (int)Buizel, - (int)Cherubi, - (int)Buneary, - (int)Chingling, - (int)Chatot, - (int)Carnivine, - (int)NidoranM, // Via Nidoran-F - (int)Happiny, // Via Chansey - (int)Smoochum, // Via Jynx - (int)Elekid, // Via Electabuzz - (int)Magby, // Via Magmar - (int)Azurill, // Via Marill - (int)Wynaut, // Via Wobbuffet - (int)Bonsly, // Via Sudowoodo - (int)MimeJr, // Via Mr. Mime - (int)Munchlax, // Via Snorlax - (int)Mantyke, // Via Mantine - (int)Chimecho, // Via Chingling - (int)Pichu, // Via Pikachu - (int)Cleffa, // Via Clefairy - (int)Igglybuff, // Via Jigglypuff - }; + internal static readonly HashSet Inherit_Apricorn6 = new() + { + (int)Caterpie, + (int)Weedle, + (int)Pidgey, + (int)Rattata, + (int)Spearow, + (int)Ekans, + (int)Sandshrew, + (int)NidoranF, + (int)Vulpix, + (int)Zubat, + (int)Oddish, + (int)Paras, + (int)Venonat, + (int)Diglett, + (int)Meowth, + (int)Psyduck, + (int)Mankey, + (int)Growlithe, + (int)Poliwag, + (int)Abra, + (int)Machop, + (int)Bellsprout, + (int)Tentacool, + (int)Geodude, + (int)Ponyta, + (int)Slowpoke, + (int)Farfetchd, + (int)Doduo, + (int)Seel, + (int)Grimer, + (int)Shellder, + (int)Gastly, + (int)Onix, + (int)Drowzee, + (int)Krabby, + (int)Exeggcute, + (int)Cubone, + (int)Lickitung, + (int)Koffing, + (int)Rhyhorn, + (int)Chansey, + (int)Tangela, + (int)Kangaskhan, + (int)Horsea, + (int)Goldeen, + (int)MrMime, + (int)Magikarp, + (int)Lapras, + (int)Snorlax, + (int)Dratini, + (int)Sentret, + (int)Hoothoot, + (int)Ledyba, + (int)Spinarak, + (int)Chinchou, + (int)Natu, + (int)Mareep, + (int)Marill, + (int)Sudowoodo, + (int)Hoppip, + (int)Aipom, + (int)Sunkern, + (int)Yanma, + (int)Wooper, + (int)Murkrow, + (int)Misdreavus, + (int)Wobbuffet, + (int)Girafarig, + (int)Pineco, + (int)Dunsparce, + (int)Gligar, + (int)Snubbull, + (int)Qwilfish, + (int)Shuckle, + (int)Heracross, + (int)Sneasel, + (int)Teddiursa, + (int)Slugma, + (int)Swinub, + (int)Corsola, + (int)Remoraid, + (int)Delibird, + (int)Mantine, + (int)Skarmory, + (int)Houndour, + (int)Phanpy, + (int)Stantler, + (int)Smeargle, + (int)Miltank, + (int)Larvitar, + (int)Poochyena, + (int)Zigzagoon, + (int)Wurmple, + (int)Seedot, + (int)Taillow, + (int)Wingull, + (int)Ralts, + (int)Shroomish, + (int)Slakoth, + (int)Whismur, + (int)Makuhita, + (int)Sableye, + (int)Mawile, + (int)Meditite, + (int)Plusle, + (int)Minun, + (int)Gulpin, + (int)Numel, + (int)Spoink, + (int)Spinda, + (int)Swablu, + (int)Barboach, + (int)Absol, + (int)Clamperl, + (int)Relicanth, + (int)Luvdisc, + (int)Starly, + (int)Bidoof, + (int)Kricketot, + (int)Shinx, + (int)Budew, + (int)Burmy, + (int)Combee, + (int)Buizel, + (int)Cherubi, + (int)Buneary, + (int)Chingling, + (int)Chatot, + (int)Carnivine, + (int)NidoranM, // Via Nidoran-F + (int)Happiny, // Via Chansey + (int)Smoochum, // Via Jynx + (int)Elekid, // Via Electabuzz + (int)Magby, // Via Magmar + (int)Azurill, // Via Marill + (int)Wynaut, // Via Wobbuffet + (int)Bonsly, // Via Sudowoodo + (int)MimeJr, // Via Mr. Mime + (int)Munchlax, // Via Snorlax + (int)Mantyke, // Via Mantine + (int)Chimecho, // Via Chingling + (int)Pichu, // Via Pikachu + (int)Cleffa, // Via Clefairy + (int)Igglybuff, // Via Jigglypuff + }; - internal static readonly HashSet AlolanCaptureOffspring = new() - { - (int)Caterpie, - (int)Rattata, - (int)Spearow, - (int)Sandshrew, - (int)Vulpix, - (int)Jigglypuff, - (int)Zubat, - (int)Paras, - (int)Diglett, - (int)Meowth, - (int)Psyduck, - (int)Mankey, - (int)Growlithe, - (int)Poliwag, - (int)Abra, - (int)Machop, - (int)Tentacool, - (int)Geodude, - (int)Slowpoke, - (int)Magnemite, - (int)Grimer, - (int)Shellder, - (int)Gastly, - (int)Drowzee, - (int)Exeggcute, - (int)Cubone, - (int)Chansey, - (int)Kangaskhan, - (int)Goldeen, - (int)Staryu, - (int)Scyther, - (int)Pinsir, - (int)Tauros, - (int)Magikarp, - (int)Lapras, - (int)Ditto, - (int)Eevee, - (int)Snorlax, - (int)Dratini, - (int)Ledyba, - (int)Spinarak, - (int)Chinchou, - (int)Pichu, - (int)Cleffa, - (int)Igglybuff, - (int)Sudowoodo, - (int)Murkrow, - (int)Misdreavus, - (int)Snubbull, - (int)Scizor, - (int)Sneasel, - (int)Corsola, - (int)Delibird, - (int)Skarmory, - (int)Smeargle, - (int)Elekid, - (int)Magby, - (int)Miltank, - (int)Wingull, - (int)Surskit, - (int)Makuhita, - (int)Nosepass, - (int)Sableye, - (int)Carvanha, - (int)Wailmer, - (int)Torkoal, - (int)Spinda, - (int)Trapinch, - (int)Barboach, - (int)Feebas, - (int)Castform, - (int)Absol, - (int)Snorunt, - (int)Relicanth, - (int)Luvdisc, - (int)Bagon, - (int)Beldum, - (int)Shellos, - (int)Drifloon, - (int)Bonsly, - (int)Happiny, - (int)Gible, - (int)Munchlax, - (int)Riolu, - (int)Finneon, - (int)Lillipup, - (int)Roggenrola, - (int)Cottonee, - (int)Petilil, - (int)Sandile, - (int)Trubbish, - (int)Vanillite, - (int)Emolga, - (int)Alomomola, - (int)Rufflet, - (int)Vullaby, - (int)Fletchling, - (int)Pancham, - (int)Carbink, - (int)Goomy, - (int)Klefki, - (int)Phantump, - (int)Pikipek, - (int)Yungoos, - (int)Grubbin, - (int)Crabrawler, - (int)Oricorio, - (int)Cutiefly, - (int)Rockruff, - (int)Wishiwashi, - (int)Mareanie, - (int)Mudbray, - (int)Dewpider, - (int)Fomantis, - (int)Morelull, - (int)Salandit, - (int)Stufful, - (int)Bounsweet, - (int)Comfey, - (int)Oranguru, - (int)Passimian, - (int)Wimpod, - (int)Sandygast, - (int)Pyukumuku, - (int)Minior, - (int)Komala, - (int)Turtonator, - (int)Togedemaru, - (int)Mimikyu, - (int)Bruxish, - (int)Drampa, - (int)Dhelmise, - (int)Jangmoo, + internal static readonly HashSet AlolanCaptureOffspring = new() + { + (int)Caterpie, + (int)Rattata, + (int)Spearow, + (int)Sandshrew, + (int)Vulpix, + (int)Jigglypuff, + (int)Zubat, + (int)Paras, + (int)Diglett, + (int)Meowth, + (int)Psyduck, + (int)Mankey, + (int)Growlithe, + (int)Poliwag, + (int)Abra, + (int)Machop, + (int)Tentacool, + (int)Geodude, + (int)Slowpoke, + (int)Magnemite, + (int)Grimer, + (int)Shellder, + (int)Gastly, + (int)Drowzee, + (int)Exeggcute, + (int)Cubone, + (int)Chansey, + (int)Kangaskhan, + (int)Goldeen, + (int)Staryu, + (int)Scyther, + (int)Pinsir, + (int)Tauros, + (int)Magikarp, + (int)Lapras, + (int)Ditto, + (int)Eevee, + (int)Snorlax, + (int)Dratini, + (int)Ledyba, + (int)Spinarak, + (int)Chinchou, + (int)Pichu, + (int)Cleffa, + (int)Igglybuff, + (int)Sudowoodo, + (int)Murkrow, + (int)Misdreavus, + (int)Snubbull, + (int)Scizor, + (int)Sneasel, + (int)Corsola, + (int)Delibird, + (int)Skarmory, + (int)Smeargle, + (int)Elekid, + (int)Magby, + (int)Miltank, + (int)Wingull, + (int)Surskit, + (int)Makuhita, + (int)Nosepass, + (int)Sableye, + (int)Carvanha, + (int)Wailmer, + (int)Torkoal, + (int)Spinda, + (int)Trapinch, + (int)Barboach, + (int)Feebas, + (int)Castform, + (int)Absol, + (int)Snorunt, + (int)Relicanth, + (int)Luvdisc, + (int)Bagon, + (int)Beldum, + (int)Shellos, + (int)Drifloon, + (int)Bonsly, + (int)Happiny, + (int)Gible, + (int)Munchlax, + (int)Riolu, + (int)Finneon, + (int)Lillipup, + (int)Roggenrola, + (int)Cottonee, + (int)Petilil, + (int)Sandile, + (int)Trubbish, + (int)Vanillite, + (int)Emolga, + (int)Alomomola, + (int)Rufflet, + (int)Vullaby, + (int)Fletchling, + (int)Pancham, + (int)Carbink, + (int)Goomy, + (int)Klefki, + (int)Phantump, + (int)Pikipek, + (int)Yungoos, + (int)Grubbin, + (int)Crabrawler, + (int)Oricorio, + (int)Cutiefly, + (int)Rockruff, + (int)Wishiwashi, + (int)Mareanie, + (int)Mudbray, + (int)Dewpider, + (int)Fomantis, + (int)Morelull, + (int)Salandit, + (int)Stufful, + (int)Bounsweet, + (int)Comfey, + (int)Oranguru, + (int)Passimian, + (int)Wimpod, + (int)Sandygast, + (int)Pyukumuku, + (int)Minior, + (int)Komala, + (int)Turtonator, + (int)Togedemaru, + (int)Mimikyu, + (int)Bruxish, + (int)Drampa, + (int)Dhelmise, + (int)Jangmoo, - // USUM Additions - (int)Ekans, - (int)Seel, - (int)Lickitung, - (int)MrMime, - (int)Hoothoot, - (int)Natu, - (int)Mareep, - (int)Aipom, - (int)Pineco, - (int)Dunsparce, - (int)Heracross, - (int)Remoraid, - (int)Mantine, - (int)Houndour, - (int)Smoochum, - (int)Larvitar, - (int)Mawile, - (int)Electrike, - (int)Corphish, - (int)Baltoy, - (int)Kecleon, - (int)Shuppet, - (int)Tropius, - (int)Clamperl, - (int)Buneary, - (int)MimeJr, - (int)Mantyke, - (int)Basculin, - (int)Scraggy, - (int)Zorua, - (int)Minccino, - (int)Frillish, - (int)Elgyem, - (int)Mienfoo, - (int)Druddigon, - (int)Golett, - (int)Pawniard, - (int)Larvesta, - (int)Litleo, - (int)Flabébé, - (int)Furfrou, - (int)Inkay, - (int)Skrelp, - (int)Clauncher, - (int)Hawlucha, - (int)Dedenne, - (int)Noibat, + // USUM Additions + (int)Ekans, + (int)Seel, + (int)Lickitung, + (int)MrMime, + (int)Hoothoot, + (int)Natu, + (int)Mareep, + (int)Aipom, + (int)Pineco, + (int)Dunsparce, + (int)Heracross, + (int)Remoraid, + (int)Mantine, + (int)Houndour, + (int)Smoochum, + (int)Larvitar, + (int)Mawile, + (int)Electrike, + (int)Corphish, + (int)Baltoy, + (int)Kecleon, + (int)Shuppet, + (int)Tropius, + (int)Clamperl, + (int)Buneary, + (int)MimeJr, + (int)Mantyke, + (int)Basculin, + (int)Scraggy, + (int)Zorua, + (int)Minccino, + (int)Frillish, + (int)Elgyem, + (int)Mienfoo, + (int)Druddigon, + (int)Golett, + (int)Pawniard, + (int)Larvesta, + (int)Litleo, + (int)Flabébé, + (int)Furfrou, + (int)Inkay, + (int)Skrelp, + (int)Clauncher, + (int)Hawlucha, + (int)Dedenne, + (int)Noibat, - // Wormhole - (int)Swablu, - (int)Yanma, - (int)Sigilyph, - (int)Ducklett, - (int)Taillow, - (int)Skorupi, - (int)Audino, - (int)Helioptile, - (int)Seedot, - (int)Spoink, - (int)Snover, - (int)Meditite, - (int)Hippopotas, - (int)Dwebble, - (int)Slugma, - (int)Binacle, - (int)Lotad, - (int)Stunfisk, - (int)Buizel, - (int)Wooper, + // Wormhole + (int)Swablu, + (int)Yanma, + (int)Sigilyph, + (int)Ducklett, + (int)Taillow, + (int)Skorupi, + (int)Audino, + (int)Helioptile, + (int)Seedot, + (int)Spoink, + (int)Snover, + (int)Meditite, + (int)Hippopotas, + (int)Dwebble, + (int)Slugma, + (int)Binacle, + (int)Lotad, + (int)Stunfisk, + (int)Buizel, + (int)Wooper, - // Static Encounters - (int)Voltorb, - }; + // Static Encounters + (int)Voltorb, + }; - internal static readonly HashSet Ban_NoHidden7Apricorn = new() - { - (int)NidoranF, - (int)NidoranM, - (int)Voltorb, - (int)Bronzor, - (int)Flabébé + (3 << 11), // Flabébé-Blue - }; + internal static readonly HashSet Ban_NoHidden7Apricorn = new() + { + (int)NidoranF, + (int)NidoranM, + (int)Voltorb, + (int)Bronzor, + (int)Flabébé + (3 << 11), // Flabébé-Blue + }; - internal static readonly HashSet AlolanCaptureNoHeavyBall = new() { (int)Beldum, (int)TapuKoko, (int)TapuLele, (int)TapuBulu, (int)TapuFini }; + internal static readonly HashSet AlolanCaptureNoHeavyBall = new() { (int)Beldum, (int)TapuKoko, (int)TapuLele, (int)TapuBulu, (int)TapuFini }; - private static readonly HashSet Inherit_ApricornMale7 = new() - { - (int)Voltorb, - (int)Baltoy, - (int)Bronzor, + private static readonly HashSet Inherit_ApricornMale7 = new() + { + (int)Voltorb, + (int)Baltoy, + (int)Bronzor, - // Others are capturable in the Alola region - // Magnemite, Staryu, Tauros - }; + // Others are capturable in the Alola region + // Magnemite, Staryu, Tauros + }; - internal static readonly HashSet Inherit_Apricorn7 = new(Inherit_Apricorn6.Concat(Inherit_ApricornMale7).Concat(PastGenAlolanScans).Concat(AlolanCaptureOffspring).Distinct()); + internal static readonly HashSet Inherit_Apricorn7 = new(Inherit_Apricorn6.Concat(Inherit_ApricornMale7).Concat(PastGenAlolanScans).Concat(AlolanCaptureOffspring).Distinct()); - internal static readonly HashSet Inherit_SafariMale = new() - { - (int)Tauros, + internal static readonly HashSet Inherit_SafariMale = new() + { + (int)Tauros, - (int)Magnemite, - (int)Voltorb, - (int)Lunatone, - (int)Solrock, - (int)Beldum, - (int)Bronzor, - }; + (int)Magnemite, + (int)Voltorb, + (int)Lunatone, + (int)Solrock, + (int)Beldum, + (int)Bronzor, + }; - internal static readonly HashSet Inherit_DreamMale = new() - { - // Starting with Gen7, Males pass Ball via breeding with Ditto. - (int)Bulbasaur, (int)Charmander, (int)Squirtle, - (int)Tauros, - (int)Pichu, - (int)Tyrogue, - (int)Treecko, - (int)Torchic, (int)Mudkip, (int)Turtwig, - (int)Chimchar, (int)Piplup, (int)Pansage, - (int)Pansear, (int)Panpour, - (int)Throh, (int)Sawk, - (int)Gothita, + internal static readonly HashSet Inherit_DreamMale = new() + { + // Starting with Gen7, Males pass Ball via breeding with Ditto. + (int)Bulbasaur, (int)Charmander, (int)Squirtle, + (int)Tauros, + (int)Pichu, + (int)Tyrogue, + (int)Treecko, + (int)Torchic, (int)Mudkip, (int)Turtwig, + (int)Chimchar, (int)Piplup, (int)Pansage, + (int)Pansear, (int)Panpour, + (int)Throh, (int)Sawk, + (int)Gothita, - (int)Magnemite, - (int)Voltorb, - (int)Staryu, - (int)Porygon, - (int)Lunatone, - (int)Solrock, - (int)Baltoy, - (int)Beldum, - (int)Bronzor, - (int)Rotom, - (int)Klink, - (int)Golett, - }; + (int)Magnemite, + (int)Voltorb, + (int)Staryu, + (int)Porygon, + (int)Lunatone, + (int)Solrock, + (int)Baltoy, + (int)Beldum, + (int)Bronzor, + (int)Rotom, + (int)Klink, + (int)Golett, + }; - internal static readonly HashSet Ban_Gen3Ball_7 = new() - { - (int)Phione, - (int)Archen, - (int)Tyrunt, - (int)Amaura, - }; + internal static readonly HashSet Ban_Gen3Ball_7 = new() + { + (int)Phione, + (int)Archen, + (int)Tyrunt, + (int)Amaura, + }; - // Same as Gen3 Balls - internal static readonly HashSet Ban_Gen4Ball_7 = Ban_Gen3Ball_7; + // Same as Gen3 Balls + internal static readonly HashSet Ban_Gen4Ball_7 = Ban_Gen3Ball_7; - internal static readonly HashSet Ban_SafariBallHidden_7 = new() - { - (int)NidoranF, - (int)NidoranM, - (int)Volbeat, (int)Illumise, - (int)Magnemite, - (int)Voltorb, - (int)Kangaskhan, - (int)Tauros, - (int)Ditto, - (int)Miltank, - (int)Beldum, - (int)Bronzor, - (int)Happiny, - (int)Tyrogue, - (int)Staryu, - (int)Lunatone, - (int)Solrock, - (int)Rotom, - (int)Klink, - (int)Golett, - }; + internal static readonly HashSet Ban_SafariBallHidden_7 = new() + { + (int)NidoranF, + (int)NidoranM, + (int)Volbeat, (int)Illumise, + (int)Magnemite, + (int)Voltorb, + (int)Kangaskhan, + (int)Tauros, + (int)Ditto, + (int)Miltank, + (int)Beldum, + (int)Bronzor, + (int)Happiny, + (int)Tyrogue, + (int)Staryu, + (int)Lunatone, + (int)Solrock, + (int)Rotom, + (int)Klink, + (int)Golett, + }; - internal static readonly HashSet Ban_NoHidden8Apricorn = new() - { - // Nidoran, Bronzor -- Used to not be encounterable in Gen7 with HA; Gen8 now can via Raids - (int)Voltorb, // Voltorb - (int)Flabébé + (3 << 11), // Flabébé-Blue - }; + internal static readonly HashSet Ban_NoHidden8Apricorn = new() + { + // Nidoran, Bronzor -- Used to not be encounterable in Gen7 with HA; Gen8 now can via Raids + (int)Voltorb, // Voltorb + (int)Flabébé + (3 << 11), // Flabébé-Blue + }; - /// - /// Gets a legal value for a bred egg encounter. - /// - /// Version the egg was created on. - /// Species the egg contained. - /// Valid ball to hatch with. - /// Not all things can hatch with a Poké Ball! + /// + /// Gets a legal value for a bred egg encounter. + /// + /// Version the egg was created on. + /// Species the egg contained. + /// Valid ball to hatch with. + /// Not all things can hatch with a Poké Ball! #pragma warning disable RCS1163, IDE0060 // Unused parameter. - public static Ball GetDefaultBall(GameVersion version, int species) - { - return Ball.None; - } + public static Ball GetDefaultBall(GameVersion version, int species) + { + return Ball.None; } } diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallUseLegality.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallUseLegality.cs index ddc7dffb9..c41a4dec5 100644 --- a/PKHeX.Core/Legality/Verifiers/Ball/BallUseLegality.cs +++ b/PKHeX.Core/Legality/Verifiers/Ball/BallUseLegality.cs @@ -2,85 +2,84 @@ using System.Collections.Generic; using static PKHeX.Core.Ball; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal static class BallUseLegality { - internal static class BallUseLegality + internal static ICollection GetWildBalls(int generation, GameVersion game) => generation switch { - internal static ICollection GetWildBalls(int generation, GameVersion game) => generation switch - { - 1 => WildPokeBalls1, - 2 => WildPokeBalls2, - 3 => WildPokeBalls3, - 4 => GameVersion.HGSS.Contains(game) ? WildPokeBalls4_HGSS : WildPokeBalls4_DPPt, - 5 => WildPokeBalls5, - 6 => WildPokeballs6, - 7 => GameVersion.Gen7b.Contains(game) ? WildPokeballs7b : WildPokeballs7, - 8 when GameVersion.BDSP.Contains(game) => WildPokeBalls4_HGSS, - 8 when GameVersion.PLA == game => WildPokeBalls8a, - 8 => GameVersion.GO == game ? WildPokeballs8g : WildPokeballs8, - _ => Array.Empty(), - }; + 1 => WildPokeBalls1, + 2 => WildPokeBalls2, + 3 => WildPokeBalls3, + 4 => GameVersion.HGSS.Contains(game) ? WildPokeBalls4_HGSS : WildPokeBalls4_DPPt, + 5 => WildPokeBalls5, + 6 => WildPokeballs6, + 7 => GameVersion.Gen7b.Contains(game) ? WildPokeballs7b : WildPokeballs7, + 8 when GameVersion.BDSP.Contains(game) => WildPokeBalls4_HGSS, + 8 when GameVersion.PLA == game => WildPokeBalls8a, + 8 => GameVersion.GO == game ? WildPokeballs8g : WildPokeballs8, + _ => Array.Empty(), + }; - private static readonly int[] WildPokeBalls1 = { 4 }; - private static readonly int[] WildPokeBalls2 = WildPokeBalls1; + private static readonly int[] WildPokeBalls1 = { 4 }; + private static readonly int[] WildPokeBalls2 = WildPokeBalls1; - private static readonly HashSet WildPokeBalls3 = new() - { - (int)Master, (int)Ultra, (int)Great, (int)Poke, (int)Net, (int)Dive, - (int)Nest, (int)Repeat, (int)Timer, (int)Luxury, (int)Premier, - }; + private static readonly HashSet WildPokeBalls3 = new() + { + (int)Master, (int)Ultra, (int)Great, (int)Poke, (int)Net, (int)Dive, + (int)Nest, (int)Repeat, (int)Timer, (int)Luxury, (int)Premier, + }; - private static readonly HashSet WildPokeBalls4_DPPt = new(WildPokeBalls3) - { - (int)Dusk, (int)Heal, (int)Quick, - }; + private static readonly HashSet WildPokeBalls4_DPPt = new(WildPokeBalls3) + { + (int)Dusk, (int)Heal, (int)Quick, + }; - private static readonly HashSet WildPokeBalls4_HGSS = new(WildPokeBalls4_DPPt) - { - (int)Fast, (int)Level, (int)Lure, (int)Heavy, (int)Love, (int)Friend, (int)Moon, - }; + private static readonly HashSet WildPokeBalls4_HGSS = new(WildPokeBalls4_DPPt) + { + (int)Fast, (int)Level, (int)Lure, (int)Heavy, (int)Love, (int)Friend, (int)Moon, + }; - private static readonly HashSet WildPokeBalls5 = WildPokeBalls4_DPPt; + private static readonly HashSet WildPokeBalls5 = WildPokeBalls4_DPPt; - internal static readonly HashSet DreamWorldBalls = new(WildPokeBalls5) {(int)Dream}; + internal static readonly HashSet DreamWorldBalls = new(WildPokeBalls5) {(int)Dream}; - internal static readonly HashSet WildPokeballs6 = WildPokeBalls5; // Same as Gen5 + internal static readonly HashSet WildPokeballs6 = WildPokeBalls5; // Same as Gen5 - internal static readonly HashSet WildPokeballs7 = new(WildPokeBalls4_HGSS) {(int)Beast}; // Same as HGSS + Beast + internal static readonly HashSet WildPokeballs7 = new(WildPokeBalls4_HGSS) {(int)Beast}; // Same as HGSS + Beast - private static readonly HashSet WildPokeballs7b = new() - { - (int)Master, (int)Ultra, (int)Great, (int)Poke, - (int)Premier, - }; + private static readonly HashSet WildPokeballs7b = new() + { + (int)Master, (int)Ultra, (int)Great, (int)Poke, + (int)Premier, + }; - private static readonly HashSet WildPokeballs8g = new() - { - (int)Ultra, (int)Great, (int)Poke, - (int)Premier, - }; + private static readonly HashSet WildPokeballs8g = new() + { + (int)Ultra, (int)Great, (int)Poke, + (int)Premier, + }; - internal static readonly HashSet WildPokeballs8 = new(WildPokeballs7) // All Except Cherish - { - (int)Dream, - (int)Safari, - (int)Sport, - // no cherish ball - }; + internal static readonly HashSet WildPokeballs8 = new(WildPokeballs7) // All Except Cherish + { + (int)Dream, + (int)Safari, + (int)Sport, + // no cherish ball + }; - private static readonly HashSet WildPokeBalls8a = new() - { - (int)LAPoke, - (int)LAGreat, - (int)LAUltra, + private static readonly HashSet WildPokeBalls8a = new() + { + (int)LAPoke, + (int)LAGreat, + (int)LAUltra, - (int)LAFeather, - (int)LAWing, - (int)LAJet, + (int)LAFeather, + (int)LAWing, + (int)LAJet, - (int)LAHeavy, - (int)LALeaden, - (int)LAGigaton, - }; - } + (int)LAHeavy, + (int)LALeaden, + (int)LAGigaton, + }; } diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs index d2cd69bf2..66ed27090 100644 --- a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs @@ -2,471 +2,470 @@ using static PKHeX.Core.LegalityCheckStrings; using static PKHeX.Core.Ball; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the value. +/// +public sealed class BallVerifier : Verifier { - /// - /// Verifies the value. - /// - public sealed class BallVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Ball; + private CheckResult NONE => GetInvalid(LBallNone); + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Ball; - private CheckResult NONE => GetInvalid(LBallNone); - - public override void Verify(LegalityAnalysis data) - { - if (data.pkm.Format <= 2) - return; // no ball info saved - var result = VerifyBall(data); - data.AddLine(result); - } - - private static int IsReplacedBall(IVersion enc, PKM pk) => pk switch - { - PK8 when enc.Version == GameVersion.PLA => (int)Poke, - _ => (int)None, - }; - - private CheckResult VerifyBall(LegalityAnalysis data) - { - var Info = data.Info; - var enc = Info.EncounterMatch; - - var ball = IsReplacedBall(enc, data.pkm); - if (ball != 0) - return VerifyBallEquals(data, ball); - - // Fixed ball cases -- can be only one ball ever - switch (enc) - { - case MysteryGift g: - return VerifyBallMysteryGift(data, g); - case EncounterTrade t: - return VerifyBallEquals(data, t.Ball); - case EncounterStatic {Gift: true} s: - return VerifyBallEquals(data, s.Ball); - case EncounterSlot8GO: // Already a strict match - return GetResult(true); - case EncounterSlot8b {IsMarsh: true}: - return VerifyBallEquals(data, (int)Safari); - } - - // Capture / Inherit cases -- can be one of many balls - var pkm = data.pkm; - if (pkm.Species == (int)Species.Shedinja && enc.Species != (int)Species.Shedinja) // Shedinja. For gen3, copy the ball from Nincada - { - // Only Gen3 origin Shedinja can copy the wild ball. - // Evolution chains will indicate if it could have existed as Shedinja in Gen3. - // The special move verifier has a similar check! - if (pkm.HGSS && pkm.Ball == (int)Sport) // Can evolve in DP to retain the HG/SS ball -- not able to be captured in any other ball - return VerifyBallEquals(data, (int)Sport); - if (Info.Generation != 3 || Info.EvoChainsAllGens.Gen3.Length != 2) - return VerifyBallEquals(data, (int)Poke); // Pokeball Only - } - - if (pkm.Ball == (int)Heavy && BallBreedLegality.AlolanCaptureNoHeavyBall.Contains(enc.Species) && !enc.EggEncounter && pkm.SM) - return GetInvalid(LBallHeavy); // Heavy Ball, can inherit if from egg (USUM fixed catch rate calc) - - return enc switch - { - EncounterStatic e => VerifyBallStatic(data, e), - EncounterSlot w => VerifyBallWild(data, w), - EncounterEgg => VerifyBallEgg(data), - EncounterInvalid => VerifyBallEquals(data, pkm.Ball), // ignore ball, pass whatever - _ => VerifyBallEquals(data, (int)Poke), - }; - } - - private CheckResult VerifyBallMysteryGift(LegalityAnalysis data, MysteryGift g) - { - if (g.Generation == 4 && g.Species == (int)Species.Manaphy && g.Ball == 0) // there is no ball data in Manaphy Mystery Gift from Gen4 - return VerifyBallEquals(data, (int)Poke); // Pokeball - return VerifyBallEquals(data, g.Ball); - } - - private CheckResult VerifyBallStatic(LegalityAnalysis data, EncounterStatic s) - { - if (s.Location == 75 && s.Generation == 5) // Entree Forest (Dream World) - return VerifyBallEquals(data, BallUseLegality.DreamWorldBalls); - return VerifyBallEquals(data, BallUseLegality.GetWildBalls(data.Info.Generation, s.Version)); - } - - private CheckResult VerifyBallWild(LegalityAnalysis data, EncounterSlot w) - { - var req = w.FixedBall; - if (req != None) - return VerifyBallEquals(data, (int) req); - - return VerifyBallEquals(data, BallUseLegality.GetWildBalls(data.Info.Generation, w.Version)); - } - - private CheckResult VerifyBallEgg(LegalityAnalysis data) - { - var pkm = data.pkm; - if (data.Info.Generation < 6) // No inheriting Balls - return VerifyBallEquals(data, (int)Poke); // Must be Pokéball -- no ball inheritance. - - return pkm.Ball switch - { - (int)Master => GetInvalid(LBallEggMaster), // Master Ball - (int)Cherish => GetInvalid(LBallEggCherish), // Cherish Ball - _ => VerifyBallInherited(data), - }; - } - - private CheckResult VerifyBallInherited(LegalityAnalysis data) => data.Info.Generation switch - { - 6 => VerifyBallEggGen6(data), // Gen6 Inheritance Rules - 7 => VerifyBallEggGen7(data), // Gen7 Inheritance Rules - 8 => data.pkm.BDSP ? VerifyBallEggGen8BDSP(data) : VerifyBallEggGen8(data), - _ => NONE, - }; - - private CheckResult VerifyBallEggGen6(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.Ball == (int)Poke) - return GetValid(LBallEnc); // Poké Ball - - var enc = data.EncounterMatch; - int species = enc.Species; - if (pkm.Gender == 2 || BallBreedLegality.BreedMaleOnly6.Contains(species)) // Genderless - return VerifyBallEquals(data, (int)Poke); // Must be Pokéball as ball can only pass via mother (not Ditto!) - - Ball ball = (Ball)pkm.Ball; - - if (ball == Safari) // Safari Ball - { - if (!BallBreedLegality.Inherit_Safari.Contains(species)) - return GetInvalid(LBallSpecies); - if (IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball.IsApricornBall()) // Apricorn Ball - { - if (!BallBreedLegality.Inherit_Apricorn6.Contains(species)) - return GetInvalid(LBallSpecies); - if (IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball == Sport) // Sport Ball - { - if (!BallBreedLegality.Inherit_Sport.Contains(species)) - return GetInvalid(LBallSpecies); - if (IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball == Dream) // Dream Ball - { - if (BallBreedLegality.Ban_DreamHidden.Contains(species) && IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); - if (BallBreedLegality.Inherit_Dream.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - if (ball is >= Dusk and <= Quick) // Dusk Heal Quick - { - if (!BallBreedLegality.Ban_Gen4Ball_6.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked. - { - if (BallBreedLegality.Ban_Gen3Ball.Contains(species)) - return GetInvalid(LBallSpecies); - if (BallBreedLegality.Ban_Gen3BallHidden.Contains(species | (enc.Form << 11)) && IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - - if (species > 650 && species != 700) // Sylveon - { - if (BallUseLegality.WildPokeballs6.Contains(pkm.Ball)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - - if (ball >= Dream) - return GetInvalid(LBallUnavailable); - - return NONE; - } - - private CheckResult VerifyBallEggGen7(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.Ball == (int)Poke) - return GetValid(LBallEnc); // Poké Ball - - int species = data.EncounterMatch.Species; - if (species is >= 722 and <= 730) // G7 Starters - return VerifyBallEquals(data, (int)Poke); - - Ball ball = (Ball)pkm.Ball; - - if (ball == Safari) - { - if (!(BallBreedLegality.Inherit_Safari.Contains(species) || BallBreedLegality.Inherit_SafariMale.Contains(species))) - return GetInvalid(LBallSpecies); - if (BallBreedLegality.Ban_SafariBallHidden_7.Contains(species) && IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball.IsApricornBall()) // Apricorn Ball - { - if (!BallBreedLegality.Inherit_Apricorn7.Contains(species)) - return GetInvalid(LBallSpecies); - if (BallBreedLegality.Ban_NoHidden7Apricorn.Contains(species | pkm.Form << 11) && IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball == Sport) // Sport Ball - { - if (!BallBreedLegality.Inherit_Sport.Contains(species)) - return GetInvalid(LBallSpecies); - if ((species is (int)Species.Volbeat or (int)Species.Illumise) && IsHiddenAndNotPossible(pkm)) // Volbeat/Illumise - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball == Dream) // Dream Ball - { - if (BallBreedLegality.Inherit_Dream.Contains(species) || BallBreedLegality.Inherit_DreamMale.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - if (ball is >= Dusk and <= Quick) // Dusk Heal Quick - { - if (!BallBreedLegality.Ban_Gen4Ball_7.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked. - { - if (!BallBreedLegality.Ban_Gen3Ball_7.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - - if (ball == Beast) - { - if (species == (int)Species.Flabébé && pkm.Form == 3 && IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); // Can't obtain Flabébé-Blue with Hidden Ability in wild - if (species == (int)Species.Voltorb && IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); // Can't obtain with Hidden Ability in wild (can only breed with Ditto) - if ((species is >= (int)Species.Pikipek and <= (int)Species.Kommoo) || (BallBreedLegality.AlolanCaptureOffspring.Contains(species) && !BallBreedLegality.PastGenAlolanNativesUncapturable.Contains(species))) - return GetValid(LBallSpeciesPass); - if (BallBreedLegality.PastGenAlolanScans.Contains(species)) - return GetValid(LBallSpeciesPass); - // next statement catches all new alolans - } - - if (species > (int)Species.Volcanion) - return VerifyBallEquals(data, BallUseLegality.WildPokeballs7); - - if (ball > Beast) - return GetInvalid(LBallUnavailable); - - return NONE; - } - - private CheckResult VerifyBallEggGen8BDSP(LegalityAnalysis data) - { - int species = data.EncounterMatch.Species; - if (species == (int)Species.Phione) - return VerifyBallEquals(data, (int)Poke); - - if (species is (int)Species.Cranidos or (int)Species.Shieldon) - return VerifyBallEquals(data, BallUseLegality.DreamWorldBalls); - - var pkm = data.pkm; - Ball ball = (Ball)pkm.Ball; - var balls = BallUseLegality.GetWildBalls(8, GameVersion.BDSP); - if (balls.Contains((int)ball)) - return GetValid(LBallSpeciesPass); - - if (species is (int)Species.Spinda) - return GetInvalid(LBallSpecies); // Can't enter or exit, needs to adhere to wild balls. - - // Cross-game inheritance - if (IsGalarCatchAndBreed(species)) - { - if (BallUseLegality.WildPokeballs8.Contains(pkm.Ball)) - return GetValid(LBallSpeciesPass); - } - - if (ball == Safari) - { - if (!(BallBreedLegality.Inherit_Safari.Contains(species) || BallBreedLegality.Inherit_SafariMale.Contains(species))) - return GetInvalid(LBallSpecies); - if (BallBreedLegality.Ban_SafariBallHidden_7.Contains(species) && IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball.IsApricornBall()) // Apricorn Ball - { - if (!BallBreedLegality.Inherit_Apricorn7.Contains(species)) - return GetInvalid(LBallSpecies); - if (BallBreedLegality.Ban_NoHidden8Apricorn.Contains(species | pkm.Form << 11) && IsHiddenAndNotPossible(pkm)) // lineage is 3->2->origin - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball == Sport) // Sport Ball - { - if (!BallBreedLegality.Inherit_Sport.Contains(species)) - return GetInvalid(LBallSpecies); - if ((species is (int)Species.Volbeat or (int)Species.Illumise) && IsHiddenAndNotPossible(pkm)) // Volbeat/Illumise - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball == Dream) // Dream Ball - { - if (BallBreedLegality.Inherit_Dream.Contains(species) || BallBreedLegality.Inherit_DreamMale.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - if (ball is >= Dusk and <= Quick) // Dusk Heal Quick - { - if (!BallBreedLegality.Ban_Gen4Ball_7.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked. - { - if (!BallBreedLegality.Ban_Gen3Ball_7.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - - if (ball == Beast) - { - if (BallBreedLegality.PastGenAlolanScans.Contains(species)) - return GetValid(LBallSpeciesPass); - } - - if (ball > Beast) - return GetInvalid(LBallUnavailable); - - return GetInvalid(LBallEncMismatch); - } - - private CheckResult VerifyBallEggGen8(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.Ball == (int)Poke) - return GetValid(LBallEnc); // Poké Ball - - int species = data.EncounterMatch.Species; - if (species is >= (int)Species.Grookey and <= (int)Species.Inteleon) // G8 Starters - return VerifyBallEquals(data, (int)Poke); - - if (IsGalarCatchAndBreed(species)) - { - if (BallUseLegality.WildPokeballs8.Contains(pkm.Ball)) - return GetValid(LBallSpeciesPass); - if (species >= (int)Species.Grookey) - return GetInvalid(LBallSpecies); - } - - Ball ball = (Ball)pkm.Ball; - - if (ball == Safari) - { - if (!(BallBreedLegality.Inherit_Safari.Contains(species) || BallBreedLegality.Inherit_SafariMale.Contains(species))) - return GetInvalid(LBallSpecies); - if (BallBreedLegality.Ban_SafariBallHidden_7.Contains(species) && IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball.IsApricornBall()) // Apricorn Ball - { - if (!BallBreedLegality.Inherit_Apricorn7.Contains(species)) - return GetInvalid(LBallSpecies); - if (BallBreedLegality.Ban_NoHidden8Apricorn.Contains(species | pkm.Form << 11) && IsHiddenAndNotPossible(pkm)) // lineage is 3->2->origin - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball == Sport) // Sport Ball - { - if (!BallBreedLegality.Inherit_Sport.Contains(species)) - return GetInvalid(LBallSpecies); - if ((species is (int)Species.Volbeat or (int)Species.Illumise) && IsHiddenAndNotPossible(pkm)) // Volbeat/Illumise - return GetInvalid(LBallAbility); - return GetValid(LBallSpeciesPass); - } - if (ball == Dream) // Dream Ball - { - if (BallBreedLegality.Inherit_Dream.Contains(species) || BallBreedLegality.Inherit_DreamMale.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - if (ball is >= Dusk and <= Quick) // Dusk Heal Quick - { - if (!BallBreedLegality.Ban_Gen4Ball_7.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked. - { - if (!BallBreedLegality.Ban_Gen3Ball_7.Contains(species)) - return GetValid(LBallSpeciesPass); - return GetInvalid(LBallSpecies); - } - - if (ball == Beast) - { - if (species == (int)Species.Flabébé && pkm.Form == 3 && IsHiddenAndNotPossible(pkm)) - return GetInvalid(LBallAbility); // Can't obtain Flabébé-Blue with Hidden Ability in wild - if ((species is >= (int)Species.Pikipek and <= (int)Species.Kommoo) || (BallBreedLegality.AlolanCaptureOffspring.Contains(species) && !BallBreedLegality.PastGenAlolanNativesUncapturable.Contains(species))) - return GetValid(LBallSpeciesPass); - if (BallBreedLegality.PastGenAlolanScans.Contains(species)) - return GetValid(LBallSpeciesPass); - // next statement catches all new alolans - } - - if (species > Legal.MaxSpeciesID_7_USUM) - return VerifyBallEquals(data, BallUseLegality.WildPokeballs8); - - if (species > (int)Species.Volcanion) - return VerifyBallEquals(data, BallUseLegality.WildPokeballs7); - - if (ball > Beast) - return GetInvalid(LBallUnavailable); - - return NONE; - } - - private static bool IsHiddenAndNotPossible(PKM pkm) - { - if (pkm.AbilityNumber != 4) - return false; - return !AbilityVerifier.CanAbilityPatch(pkm.Format, pkm.PersonalInfo.Abilities, pkm.Species); - } - - private static bool IsGalarCatchAndBreed(int species) - { - if (species is >= (int)Species.Grookey and <= (int)Species.Inteleon) // starter - return false; - - // Everything breed-able that is in the Galar Dex can be captured in-game. - var pt = PersonalTable.SWSH; - var pi = (PersonalInfoSWSH) pt.GetFormEntry(species, 0); - if (pi.IsInDex) - return true; - - // Foreign Captures - if (species is >= (int)Species.Treecko and <= (int)Species.Swampert) // Dynamax Adventures - return true; - if (species is >= (int)Species.Rowlet and <= (int)Species.Primarina) // Distribution Raids - return true; - - return false; - } - - private CheckResult VerifyBallEquals(LegalityAnalysis data, int ball) => GetResult(ball == data.pkm.Ball); - private CheckResult VerifyBallEquals(LegalityAnalysis data, HashSet balls) => GetResult(balls.Contains(data.pkm.Ball)); - private CheckResult VerifyBallEquals(LegalityAnalysis data, ICollection balls) => GetResult(balls.Contains(data.pkm.Ball)); - private CheckResult GetResult(bool valid) => valid ? GetValid(LBallEnc) : GetInvalid(LBallEncMismatch); + if (data.Entity.Format <= 2) + return; // no ball info saved + var result = VerifyBall(data); + data.AddLine(result); } + + private static int IsReplacedBall(IVersion enc, PKM pk) => pk switch + { + PK8 when enc.Version == GameVersion.PLA => (int)Poke, + _ => (int)None, + }; + + private CheckResult VerifyBall(LegalityAnalysis data) + { + var Info = data.Info; + var enc = Info.EncounterMatch; + + var ball = IsReplacedBall(enc, data.Entity); + if (ball != 0) + return VerifyBallEquals(data, ball); + + // Fixed ball cases -- can be only one ball ever + switch (enc) + { + case MysteryGift g: + return VerifyBallMysteryGift(data, g); + case EncounterTrade t: + return VerifyBallEquals(data, t.Ball); + case EncounterStatic {Gift: true} s: + return VerifyBallEquals(data, s.Ball); + case EncounterSlot8GO: // Already a strict match + return GetResult(true); + case EncounterSlot8b {IsMarsh: true}: + return VerifyBallEquals(data, (int)Safari); + } + + // Capture / Inherit cases -- can be one of many balls + var pk = data.Entity; + if (pk.Species == (int)Species.Shedinja && enc.Species != (int)Species.Shedinja) // Shedinja. For gen3, copy the ball from Nincada + { + // Only Gen3 origin Shedinja can copy the wild ball. + // Evolution chains will indicate if it could have existed as Shedinja in Gen3. + // The special move verifier has a similar check! + if (pk.HGSS && pk.Ball == (int)Sport) // Can evolve in DP to retain the HG/SS ball -- not able to be captured in any other ball + return VerifyBallEquals(data, (int)Sport); + if (Info.Generation != 3 || Info.EvoChainsAllGens.Gen3.Length != 2) + return VerifyBallEquals(data, (int)Poke); // Pokeball Only + } + + if (pk.Ball == (int)Heavy && BallBreedLegality.AlolanCaptureNoHeavyBall.Contains(enc.Species) && !enc.EggEncounter && pk.SM) + return GetInvalid(LBallHeavy); // Heavy Ball, can inherit if from egg (USUM fixed catch rate calc) + + return enc switch + { + EncounterStatic e => VerifyBallStatic(data, e), + EncounterSlot w => VerifyBallWild(data, w), + EncounterEgg => VerifyBallEgg(data), + EncounterInvalid => VerifyBallEquals(data, pk.Ball), // ignore ball, pass whatever + _ => VerifyBallEquals(data, (int)Poke), + }; + } + + private CheckResult VerifyBallMysteryGift(LegalityAnalysis data, MysteryGift g) + { + if (g.Generation == 4 && g.Species == (int)Species.Manaphy && g.Ball == 0) // there is no ball data in Manaphy Mystery Gift from Gen4 + return VerifyBallEquals(data, (int)Poke); // Pokeball + return VerifyBallEquals(data, g.Ball); + } + + private CheckResult VerifyBallStatic(LegalityAnalysis data, EncounterStatic s) + { + if (s.Location == 75 && s.Generation == 5) // Entree Forest (Dream World) + return VerifyBallEquals(data, BallUseLegality.DreamWorldBalls); + return VerifyBallEquals(data, BallUseLegality.GetWildBalls(data.Info.Generation, s.Version)); + } + + private CheckResult VerifyBallWild(LegalityAnalysis data, EncounterSlot w) + { + var req = w.FixedBall; + if (req != None) + return VerifyBallEquals(data, (int) req); + + return VerifyBallEquals(data, BallUseLegality.GetWildBalls(data.Info.Generation, w.Version)); + } + + private CheckResult VerifyBallEgg(LegalityAnalysis data) + { + var pk = data.Entity; + if (data.Info.Generation < 6) // No inheriting Balls + return VerifyBallEquals(data, (int)Poke); // Must be Pokéball -- no ball inheritance. + + return pk.Ball switch + { + (int)Master => GetInvalid(LBallEggMaster), // Master Ball + (int)Cherish => GetInvalid(LBallEggCherish), // Cherish Ball + _ => VerifyBallInherited(data), + }; + } + + private CheckResult VerifyBallInherited(LegalityAnalysis data) => data.Info.Generation switch + { + 6 => VerifyBallEggGen6(data), // Gen6 Inheritance Rules + 7 => VerifyBallEggGen7(data), // Gen7 Inheritance Rules + 8 => data.Entity.BDSP ? VerifyBallEggGen8BDSP(data) : VerifyBallEggGen8(data), + _ => NONE, + }; + + private CheckResult VerifyBallEggGen6(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.Ball == (int)Poke) + return GetValid(LBallEnc); // Poké Ball + + var enc = data.EncounterMatch; + int species = enc.Species; + if (pk.Gender == 2 || BallBreedLegality.BreedMaleOnly6.Contains(species)) // Genderless + return VerifyBallEquals(data, (int)Poke); // Must be Pokéball as ball can only pass via mother (not Ditto!) + + Ball ball = (Ball)pk.Ball; + + if (ball == Safari) // Safari Ball + { + if (!BallBreedLegality.Inherit_Safari.Contains(species)) + return GetInvalid(LBallSpecies); + if (IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball.IsApricornBall()) // Apricorn Ball + { + if (!BallBreedLegality.Inherit_Apricorn6.Contains(species)) + return GetInvalid(LBallSpecies); + if (IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball == Sport) // Sport Ball + { + if (!BallBreedLegality.Inherit_Sport.Contains(species)) + return GetInvalid(LBallSpecies); + if (IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball == Dream) // Dream Ball + { + if (BallBreedLegality.Ban_DreamHidden.Contains(species) && IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); + if (BallBreedLegality.Inherit_Dream.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + if (ball is >= Dusk and <= Quick) // Dusk Heal Quick + { + if (!BallBreedLegality.Ban_Gen4Ball_6.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked. + { + if (BallBreedLegality.Ban_Gen3Ball.Contains(species)) + return GetInvalid(LBallSpecies); + if (BallBreedLegality.Ban_Gen3BallHidden.Contains(species | (enc.Form << 11)) && IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + + if (species > 650 && species != 700) // Sylveon + { + if (BallUseLegality.WildPokeballs6.Contains(pk.Ball)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + + if (ball >= Dream) + return GetInvalid(LBallUnavailable); + + return NONE; + } + + private CheckResult VerifyBallEggGen7(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.Ball == (int)Poke) + return GetValid(LBallEnc); // Poké Ball + + int species = data.EncounterMatch.Species; + if (species is >= 722 and <= 730) // G7 Starters + return VerifyBallEquals(data, (int)Poke); + + Ball ball = (Ball)pk.Ball; + + if (ball == Safari) + { + if (!(BallBreedLegality.Inherit_Safari.Contains(species) || BallBreedLegality.Inherit_SafariMale.Contains(species))) + return GetInvalid(LBallSpecies); + if (BallBreedLegality.Ban_SafariBallHidden_7.Contains(species) && IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball.IsApricornBall()) // Apricorn Ball + { + if (!BallBreedLegality.Inherit_Apricorn7.Contains(species)) + return GetInvalid(LBallSpecies); + if (BallBreedLegality.Ban_NoHidden7Apricorn.Contains(species | (pk.Form << 11)) && IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball == Sport) // Sport Ball + { + if (!BallBreedLegality.Inherit_Sport.Contains(species)) + return GetInvalid(LBallSpecies); + if ((species is (int)Species.Volbeat or (int)Species.Illumise) && IsHiddenAndNotPossible(pk)) // Volbeat/Illumise + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball == Dream) // Dream Ball + { + if (BallBreedLegality.Inherit_Dream.Contains(species) || BallBreedLegality.Inherit_DreamMale.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + if (ball is >= Dusk and <= Quick) // Dusk Heal Quick + { + if (!BallBreedLegality.Ban_Gen4Ball_7.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked. + { + if (!BallBreedLegality.Ban_Gen3Ball_7.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + + if (ball == Beast) + { + if (species == (int)Species.Flabébé && pk.Form == 3 && IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); // Can't obtain Flabébé-Blue with Hidden Ability in wild + if (species == (int)Species.Voltorb && IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); // Can't obtain with Hidden Ability in wild (can only breed with Ditto) + if ((species is >= (int)Species.Pikipek and <= (int)Species.Kommoo) || (BallBreedLegality.AlolanCaptureOffspring.Contains(species) && !BallBreedLegality.PastGenAlolanNativesUncapturable.Contains(species))) + return GetValid(LBallSpeciesPass); + if (BallBreedLegality.PastGenAlolanScans.Contains(species)) + return GetValid(LBallSpeciesPass); + // next statement catches all new alolans + } + + if (species > (int)Species.Volcanion) + return VerifyBallEquals(data, BallUseLegality.WildPokeballs7); + + if (ball > Beast) + return GetInvalid(LBallUnavailable); + + return NONE; + } + + private CheckResult VerifyBallEggGen8BDSP(LegalityAnalysis data) + { + int species = data.EncounterMatch.Species; + if (species == (int)Species.Phione) + return VerifyBallEquals(data, (int)Poke); + + if (species is (int)Species.Cranidos or (int)Species.Shieldon) + return VerifyBallEquals(data, BallUseLegality.DreamWorldBalls); + + var pk = data.Entity; + Ball ball = (Ball)pk.Ball; + var balls = BallUseLegality.GetWildBalls(8, GameVersion.BDSP); + if (balls.Contains((int)ball)) + return GetValid(LBallSpeciesPass); + + if (species is (int)Species.Spinda) + return GetInvalid(LBallSpecies); // Can't enter or exit, needs to adhere to wild balls. + + // Cross-game inheritance + if (IsGalarCatchAndBreed(species)) + { + if (BallUseLegality.WildPokeballs8.Contains(pk.Ball)) + return GetValid(LBallSpeciesPass); + } + + if (ball == Safari) + { + if (!(BallBreedLegality.Inherit_Safari.Contains(species) || BallBreedLegality.Inherit_SafariMale.Contains(species))) + return GetInvalid(LBallSpecies); + if (BallBreedLegality.Ban_SafariBallHidden_7.Contains(species) && IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball.IsApricornBall()) // Apricorn Ball + { + if (!BallBreedLegality.Inherit_Apricorn7.Contains(species)) + return GetInvalid(LBallSpecies); + if (BallBreedLegality.Ban_NoHidden8Apricorn.Contains(species | (pk.Form << 11)) && IsHiddenAndNotPossible(pk)) // lineage is 3->2->origin + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball == Sport) // Sport Ball + { + if (!BallBreedLegality.Inherit_Sport.Contains(species)) + return GetInvalid(LBallSpecies); + if ((species is (int)Species.Volbeat or (int)Species.Illumise) && IsHiddenAndNotPossible(pk)) // Volbeat/Illumise + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball == Dream) // Dream Ball + { + if (BallBreedLegality.Inherit_Dream.Contains(species) || BallBreedLegality.Inherit_DreamMale.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + if (ball is >= Dusk and <= Quick) // Dusk Heal Quick + { + if (!BallBreedLegality.Ban_Gen4Ball_7.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked. + { + if (!BallBreedLegality.Ban_Gen3Ball_7.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + + if (ball == Beast) + { + if (BallBreedLegality.PastGenAlolanScans.Contains(species)) + return GetValid(LBallSpeciesPass); + } + + if (ball > Beast) + return GetInvalid(LBallUnavailable); + + return GetInvalid(LBallEncMismatch); + } + + private CheckResult VerifyBallEggGen8(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.Ball == (int)Poke) + return GetValid(LBallEnc); // Poké Ball + + int species = data.EncounterMatch.Species; + if (species is >= (int)Species.Grookey and <= (int)Species.Inteleon) // G8 Starters + return VerifyBallEquals(data, (int)Poke); + + if (IsGalarCatchAndBreed(species)) + { + if (BallUseLegality.WildPokeballs8.Contains(pk.Ball)) + return GetValid(LBallSpeciesPass); + if (species >= (int)Species.Grookey) + return GetInvalid(LBallSpecies); + } + + Ball ball = (Ball)pk.Ball; + + if (ball == Safari) + { + if (!(BallBreedLegality.Inherit_Safari.Contains(species) || BallBreedLegality.Inherit_SafariMale.Contains(species))) + return GetInvalid(LBallSpecies); + if (BallBreedLegality.Ban_SafariBallHidden_7.Contains(species) && IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball.IsApricornBall()) // Apricorn Ball + { + if (!BallBreedLegality.Inherit_Apricorn7.Contains(species)) + return GetInvalid(LBallSpecies); + if (BallBreedLegality.Ban_NoHidden8Apricorn.Contains(species | (pk.Form << 11)) && IsHiddenAndNotPossible(pk)) // lineage is 3->2->origin + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball == Sport) // Sport Ball + { + if (!BallBreedLegality.Inherit_Sport.Contains(species)) + return GetInvalid(LBallSpecies); + if ((species is (int)Species.Volbeat or (int)Species.Illumise) && IsHiddenAndNotPossible(pk)) // Volbeat/Illumise + return GetInvalid(LBallAbility); + return GetValid(LBallSpeciesPass); + } + if (ball == Dream) // Dream Ball + { + if (BallBreedLegality.Inherit_Dream.Contains(species) || BallBreedLegality.Inherit_DreamMale.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + if (ball is >= Dusk and <= Quick) // Dusk Heal Quick + { + if (!BallBreedLegality.Ban_Gen4Ball_7.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked. + { + if (!BallBreedLegality.Ban_Gen3Ball_7.Contains(species)) + return GetValid(LBallSpeciesPass); + return GetInvalid(LBallSpecies); + } + + if (ball == Beast) + { + if (species == (int)Species.Flabébé && pk.Form == 3 && IsHiddenAndNotPossible(pk)) + return GetInvalid(LBallAbility); // Can't obtain Flabébé-Blue with Hidden Ability in wild + if ((species is >= (int)Species.Pikipek and <= (int)Species.Kommoo) || (BallBreedLegality.AlolanCaptureOffspring.Contains(species) && !BallBreedLegality.PastGenAlolanNativesUncapturable.Contains(species))) + return GetValid(LBallSpeciesPass); + if (BallBreedLegality.PastGenAlolanScans.Contains(species)) + return GetValid(LBallSpeciesPass); + // next statement catches all new alolans + } + + if (species > Legal.MaxSpeciesID_7_USUM) + return VerifyBallEquals(data, BallUseLegality.WildPokeballs8); + + if (species > (int)Species.Volcanion) + return VerifyBallEquals(data, BallUseLegality.WildPokeballs7); + + if (ball > Beast) + return GetInvalid(LBallUnavailable); + + return NONE; + } + + private static bool IsHiddenAndNotPossible(PKM pk) + { + if (pk.AbilityNumber != 4) + return false; + return !AbilityVerifier.CanAbilityPatch(pk.Format, pk.PersonalInfo.Abilities, pk.Species); + } + + private static bool IsGalarCatchAndBreed(int species) + { + if (species is >= (int)Species.Grookey and <= (int)Species.Inteleon) // starter + return false; + + // Everything breed-able that is in the Galar Dex can be captured in-game. + var pt = PersonalTable.SWSH; + var pi = (PersonalInfoSWSH) pt.GetFormEntry(species, 0); + if (pi.IsInDex) + return true; + + // Foreign Captures + if (species is >= (int)Species.Treecko and <= (int)Species.Swampert) // Dynamax Adventures + return true; + if (species is >= (int)Species.Rowlet and <= (int)Species.Primarina) // Distribution Raids + return true; + + return false; + } + + private CheckResult VerifyBallEquals(LegalityAnalysis data, int ball) => GetResult(ball == data.Entity.Ball); + private CheckResult VerifyBallEquals(LegalityAnalysis data, HashSet balls) => GetResult(balls.Contains(data.Entity.Ball)); + private CheckResult VerifyBallEquals(LegalityAnalysis data, ICollection balls) => GetResult(balls.Contains(data.Entity.Ball)); + private CheckResult GetResult(bool valid) => valid ? GetValid(LBallEnc) : GetInvalid(LBallEncMismatch); } diff --git a/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs b/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs index 98ee65d28..4a5003720 100644 --- a/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/CXDVerifier.cs @@ -1,41 +1,40 @@ using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the specific origin data of encounters. +/// +public sealed class CXDVerifier : Verifier { - /// - /// Verifies the specific origin data of encounters. - /// - public sealed class CXDVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Misc; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Misc; + var pk = data.Entity; + if (data.EncounterMatch is EncounterStatic3 s3) + VerifyCXDStarterCorrelation(data, s3); + else if (pk.Egg_Location != 0 && pk is not PB8 {Egg_Location: Locations.Default8bNone}) // can't obtain eggs in CXD + data.AddLine(GetInvalid(LEncInvalid, CheckIdentifier.Encounter)); // invalid encounter - public override void Verify(LegalityAnalysis data) + if (pk.OT_Gender == 1) + data.AddLine(GetInvalid(LG3OTGender, CheckIdentifier.Trainer)); + } + + private static void VerifyCXDStarterCorrelation(LegalityAnalysis data, EncounterStatic3 enc) + { + var (type, seed) = data.Info.PIDIV; + if (type is not (PIDType.CXD or PIDType.CXDAnti or PIDType.CXD_ColoStarter)) + return; // already flagged as invalid + + var pk = data.Entity; + bool valid = enc.Species switch { - var pkm = data.pkm; - if (data.EncounterMatch is EncounterStatic3 s3) - VerifyCXDStarterCorrelation(data, s3); - else if (pkm.Egg_Location != 0 && pkm is not PB8 {Egg_Location: Locations.Default8bNone}) // can't obtain eggs in CXD - data.AddLine(GetInvalid(LEncInvalid, CheckIdentifier.Encounter)); // invalid encounter - - if (pkm.OT_Gender == 1) - data.AddLine(GetInvalid(LG3OTGender, CheckIdentifier.Trainer)); - } - - private static void VerifyCXDStarterCorrelation(LegalityAnalysis data, EncounterStatic3 enc) - { - var (type, seed) = data.Info.PIDIV; - if (type is not (PIDType.CXD or PIDType.CXDAnti or PIDType.CXD_ColoStarter)) - return; // already flagged as invalid - - var pkm = data.pkm; - bool valid = enc.Species switch - { - (int)Species.Eevee => LockFinder.IsXDStarterValid(seed, pkm.TID, pkm.SID), - (int)Species.Espeon or (int)Species.Umbreon => type == PIDType.CXD_ColoStarter, - _ => true, - }; - if (!valid) - data.AddLine(GetInvalid(LEncConditionBadRNGFrame, CheckIdentifier.PID)); - } + (int)Species.Eevee => LockFinder.IsXDStarterValid(seed, pk.TID, pk.SID), + (int)Species.Espeon or (int)Species.Umbreon => type == PIDType.CXD_ColoStarter, + _ => true, + }; + if (!valid) + data.AddLine(GetInvalid(LEncConditionBadRNGFrame, CheckIdentifier.PID)); } } diff --git a/PKHeX.Core/Legality/Verifiers/ConsoleRegionVerifier.cs b/PKHeX.Core/Legality/Verifiers/ConsoleRegionVerifier.cs index fe1e234ce..b73a83789 100644 --- a/PKHeX.Core/Legality/Verifiers/ConsoleRegionVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/ConsoleRegionVerifier.cs @@ -1,36 +1,35 @@ using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the and of origin values. +/// +public sealed class ConsoleRegionVerifier : Verifier { - /// - /// Verifies the and of origin values. - /// - public sealed class ConsoleRegionVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Geography; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Geography; + if (data.Entity is not IRegionOrigin tr) + return; + var result = VerifyConsoleRegion(tr); + data.AddLine(result); + } - public override void Verify(LegalityAnalysis data) - { - if (data.pkm is not IRegionOrigin tr) - return; - var result = VerifyConsoleRegion(tr); - data.AddLine(result); - } + private CheckResult VerifyConsoleRegion(IRegionOrigin pk) + { + int consoleRegion = pk.ConsoleRegion; + if (consoleRegion >= 7) + return GetInvalid(LGeoHardwareRange); - private CheckResult VerifyConsoleRegion(IRegionOrigin pkm) - { - int consoleRegion = pkm.ConsoleRegion; - if (consoleRegion >= 7) - return GetInvalid(LGeoHardwareRange); + return Verify3DSDataPresent(pk, consoleRegion); + } - return Verify3DSDataPresent(pkm, consoleRegion); - } - - private CheckResult Verify3DSDataPresent(IRegionOrigin pkm, int consoleRegion) - { - if (!Locale3DS.IsConsoleRegionCountryValid(consoleRegion, pkm.Country)) - return GetInvalid(LGeoHardwareInvalid); - return GetValid(LGeoHardwareValid); - } + private CheckResult Verify3DSDataPresent(IRegionOrigin pk, int consoleRegion) + { + if (!Locale3DS.IsConsoleRegionCountryValid(consoleRegion, pk.Country)) + return GetInvalid(LGeoHardwareInvalid); + return GetValid(LGeoHardwareValid); } } diff --git a/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs b/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs index c5dd977cd..fba05aca9 100644 --- a/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/ContestStatVerifier.cs @@ -12,8 +12,8 @@ public sealed class ContestStatVerifier : Verifier protected override CheckIdentifier Identifier => CheckIdentifier.Memory; public override void Verify(LegalityAnalysis data) { - var pkm = data.pkm; - if (pkm is not IContestStats s) + var pk = data.Entity; + if (pk is not IContestStats s) return; // If no stats have been increased from the initial amount, then we're done here. @@ -25,7 +25,7 @@ public override void Verify(LegalityAnalysis data) // In generations 3,4 and BDSP, blocks/poffins have a feel(sheen) equal to sheen=sum(stats)/5, with +/- 10% for a favored stat. // In generation 6 (ORAS), they don't award any sheen, so any value is legal. - var correlation = GetContestStatRestriction(pkm, data.Info.Generation, data.Info.EvoChainsAllGens); + var correlation = GetContestStatRestriction(pk, data.Info.Generation, data.Info.EvoChainsAllGens); if (correlation == None) { // We're only here because we have contest stat values. We aren't permitted to have any, so flag it. @@ -41,19 +41,19 @@ public override void Verify(LegalityAnalysis data) else if (correlation == CorrelateSheen) { bool gen3 = data.Info.Generation == 3; - bool bdsp = pkm.HasVisitedBDSP(data.Info.EvoChainsAllGens.Gen8b); + bool bdsp = pk.HasVisitedBDSP(data.Info.EvoChainsAllGens.Gen8b); var method = gen3 ? ContestStatGrantingSheen.Gen3 : bdsp ? ContestStatGrantingSheen.Gen8b : ContestStatGrantingSheen.Gen4; // Check for stat values that exceed a valid sheen value. var initial = GetReferenceTemplate(data.Info.EncounterMatch); - var minSheen = CalculateMinimumSheen(s, initial, pkm, method); + var minSheen = CalculateMinimumSheen(s, initial, pk, method); if (s.CNT_Sheen < minSheen) data.AddLine(GetInvalid(string.Format(LContestSheenTooLow_0, minSheen))); // Check for sheen values that are too high. - var maxSheen = CalculateMaximumSheen(s, pkm.Nature, initial, gen3); + var maxSheen = CalculateMaximumSheen(s, pk.Nature, initial, gen3); if (s.CNT_Sheen > maxSheen) data.AddLine(GetInvalid(string.Format(LContestSheenTooHigh_0, maxSheen))); } @@ -63,7 +63,7 @@ public override void Verify(LegalityAnalysis data) // Check for sheen values that are too high. var initial = GetReferenceTemplate(data.Info.EncounterMatch); - var maxSheen = CalculateMaximumSheen(s, pkm.Nature, initial, gen3); + var maxSheen = CalculateMaximumSheen(s, pk.Nature, initial, gen3); if (s.CNT_Sheen > maxSheen) data.AddLine(GetInvalid(string.Format(LContestSheenTooHigh_0, maxSheen))); } diff --git a/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs b/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs index e00257881..bff65f6eb 100644 --- a/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/EffortValueVerifier.cs @@ -1,101 +1,100 @@ using System; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the . +/// +public sealed class EffortValueVerifier : Verifier { - /// - /// Verifies the . - /// - public sealed class EffortValueVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.EVs; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.EVs; - - public override void Verify(LegalityAnalysis data) + var pk = data.Entity; + if (pk is IAwakened a) { - var pkm = data.pkm; - if (pkm is IAwakened a) - { - VerifyAwakenedValues(data, a); - return; - } - var enc = data.EncounterMatch; - if (pkm.IsEgg) - { - if (pkm.EVTotal is not 0) - data.AddLine(GetInvalid(LEffortEgg)); - return; - } - - // In Generations I and II, when a Pokémon is taken out of the Day Care, its experience will lower to the minimum value for its current level. - int format = pkm.Format; - if (format < 3) // can abuse daycare for EV training without EXP gain - return; - - int sum = pkm.EVTotal; - if (sum > 510) // format >= 3 - data.AddLine(GetInvalid(LEffortAbove510)); - Span evs = stackalloc int[6]; - pkm.GetEVs(evs); - if (format >= 6 && evs.Find(ev => ev > 252) != default) - data.AddLine(GetInvalid(LEffortAbove252)); - - const int vitaMax = 100; // Vitamin Max - if (format < 5) // 3/4 - { - if (enc.LevelMin == 100) // only true for Gen4 and Format=4 - { - // Cannot EV train at level 100 -- Certain events are distributed at level 100. - if (evs.Find(ev => ev > vitaMax) != default) // EVs can only be increased by vitamins to a max of 100. - data.AddLine(GetInvalid(LEffortCap100)); - } - else // check for gained EVs without gaining EXP -- don't check gen5+ which have wings to boost above 100. - { - var growth = PersonalTable.HGSS[enc.Species].EXPGrowth; - var baseEXP = Experience.GetEXP(enc.LevelMin, growth); - if (baseEXP == pkm.EXP && evs.Find(ev => ev > vitaMax) != default) - data.AddLine(GetInvalid(string.Format(LEffortUntrainedCap, vitaMax))); - } - } - - // Only one of the following can be true: 0, 508, and x%6!=0 - if (sum == 0 && !enc.IsWithinEncounterRange(pkm)) - data.AddLine(Get(LEffortEXPIncreased, Severity.Fishy)); - else if (sum == 508) - data.AddLine(Get(LEffort2Remaining, Severity.Fishy)); - else if (evs[0] != 0 && evs.Count(evs[0]) == evs.Length) - data.AddLine(Get(LEffortAllEqual, Severity.Fishy)); + VerifyAwakenedValues(data, a); + return; + } + var enc = data.EncounterMatch; + if (pk.IsEgg) + { + if (pk.EVTotal is not 0) + data.AddLine(GetInvalid(LEffortEgg)); + return; } - private void VerifyAwakenedValues(LegalityAnalysis data, IAwakened awakened) + // In Generations I and II, when a Pokémon is taken out of the Day Care, its experience will lower to the minimum value for its current level. + int format = pk.Format; + if (format < 3) // can abuse daycare for EV training without EXP gain + return; + + int sum = pk.EVTotal; + if (sum > 510) // format >= 3 + data.AddLine(GetInvalid(LEffortAbove510)); + Span evs = stackalloc int[6]; + pk.GetEVs(evs); + if (format >= 6 && evs.Find(ev => ev > 252) != default) + data.AddLine(GetInvalid(LEffortAbove252)); + + const int vitaMax = 100; // Vitamin Max + if (format < 5) // 3/4 { - var pkm = data.pkm; - int sum = pkm.EVTotal; - if (sum != 0) - data.AddLine(GetInvalid(LEffortShouldBeZero)); - - if (!awakened.AwakeningAllValid()) - data.AddLine(GetInvalid(LAwakenedCap)); - - var enc = data.EncounterMatch; - - // go park transfers have 2 AVs for all stats. - if (enc is EncounterSlot7GO) + if (enc.LevelMin == 100) // only true for Gen4 and Format=4 { - Span avs = stackalloc byte[6]; - awakened.GetAVs(avs); - foreach (var av in avs) - { - if (av >= 2) - continue; - - data.AddLine(GetInvalid(string.Format(LAwakenedShouldBeValue, 2))); - break; - } - return; + // Cannot EV train at level 100 -- Certain events are distributed at level 100. + if (evs.Find(ev => ev > vitaMax) != default) // EVs can only be increased by vitamins to a max of 100. + data.AddLine(GetInvalid(LEffortCap100)); + } + else // check for gained EVs without gaining EXP -- don't check gen5+ which have wings to boost above 100. + { + var growth = PersonalTable.HGSS[enc.Species].EXPGrowth; + var baseEXP = Experience.GetEXP(enc.LevelMin, growth); + if (baseEXP == pk.EXP && evs.Find(ev => ev > vitaMax) != default) + data.AddLine(GetInvalid(string.Format(LEffortUntrainedCap, vitaMax))); } - - if (awakened.AwakeningSum() == 0 && !enc.IsWithinEncounterRange(pkm)) - data.AddLine(Get(LAwakenedEXPIncreased, Severity.Fishy)); } + + // Only one of the following can be true: 0, 508, and x%6!=0 + if (sum == 0 && !enc.IsWithinEncounterRange(pk)) + data.AddLine(Get(LEffortEXPIncreased, Severity.Fishy)); + else if (sum == 508) + data.AddLine(Get(LEffort2Remaining, Severity.Fishy)); + else if (evs[0] != 0 && evs.Count(evs[0]) == evs.Length) + data.AddLine(Get(LEffortAllEqual, Severity.Fishy)); + } + + private void VerifyAwakenedValues(LegalityAnalysis data, IAwakened awakened) + { + var pk = data.Entity; + int sum = pk.EVTotal; + if (sum != 0) + data.AddLine(GetInvalid(LEffortShouldBeZero)); + + if (!awakened.AwakeningAllValid()) + data.AddLine(GetInvalid(LAwakenedCap)); + + var enc = data.EncounterMatch; + + // go park transfers have 2 AVs for all stats. + if (enc is EncounterSlot7GO) + { + Span avs = stackalloc byte[6]; + awakened.GetAVs(avs); + foreach (var av in avs) + { + if (av >= 2) + continue; + + data.AddLine(GetInvalid(string.Format(LAwakenedShouldBeValue, 2))); + break; + } + return; + } + + if (awakened.AwakeningSum() == 0 && !enc.IsWithinEncounterRange(pk)) + data.AddLine(Get(LAwakenedEXPIncreased, Severity.Fishy)); } } diff --git a/PKHeX.Core/Legality/Verifiers/Egg/EggStateLegality.cs b/PKHeX.Core/Legality/Verifiers/Egg/EggStateLegality.cs index 85b79aeca..e99d21b5c 100644 --- a/PKHeX.Core/Legality/Verifiers/Egg/EggStateLegality.cs +++ b/PKHeX.Core/Legality/Verifiers/Egg/EggStateLegality.cs @@ -1,138 +1,137 @@ using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides information for what values an Egg can have, while it still is an egg. +/// +public static class EggStateLegality { /// - /// Provides information for what values an Egg can have, while it still is an egg. + /// Checks if the Egg Entity's hatch counter value is within the confines of game legality. /// - public static class EggStateLegality + /// Egg Entity + /// Encounter the egg was generated from + /// True if valid, false if invalid. + public static bool GetIsEggHatchCyclesValid(PKM pk, IEncounterTemplate enc) { - /// - /// Checks if the Egg Entity's hatch counter value is within the confines of game legality. - /// - /// Egg Entity - /// Encounter the egg was generated from - /// True if valid, false if invalid. - public static bool GetIsEggHatchCyclesValid(PKM pk, IEncounterTemplate enc) - { - var hatchCounter = pk.OT_Friendship; - var max = GetMaximumEggHatchCycles(pk, enc); - if (hatchCounter > max) - return false; - var min = GetMinimumEggHatchCycles(pk); - if (hatchCounter < min) - return false; + var hatchCounter = pk.OT_Friendship; + var max = GetMaximumEggHatchCycles(pk, enc); + if (hatchCounter > max) + return false; + var min = GetMinimumEggHatchCycles(pk); + if (hatchCounter < min) + return false; - return true; - } - - /// - /// Gets the minimum hatch counter value allowed for an Egg Entity. - /// - /// Egg Entity - /// Usually 0... - public static int GetMinimumEggHatchCycles(PKM pk) => pk switch - { - PK2 or PB8 => 1, // no grace period between 1 step remaining and hatch - _ => 0, // having several Eggs in your party and then hatching one will give the rest 0... they can then be boxed! - }; - - /// - /// Will create a new to find the encounter. - public static int GetMaximumEggHatchCycles(PKM pk) - { - var la = new LegalityAnalysis(pk); - var enc = la.EncounterMatch; - return GetMaximumEggHatchCycles(pk, enc); - } - - /// - /// Gets the original Hatch Cycle value for an Egg Entity. - /// - /// Egg Entity - /// Encounter the egg was generated from - /// Maximum value the Hatch Counter can be. - public static int GetMaximumEggHatchCycles(PKM pk, IEncounterTemplate enc) - { - if (enc is EncounterStatic { EggCycles: not 0 } s) - return s.EggCycles; - return pk.PersonalInfo.HatchCycles; - } - - /// - /// Level which eggs are given to the player. - /// - /// Generation the egg is given in - public static byte GetEggLevel(int generation) => generation >= 4 ? (byte)1 : (byte)5; - - /// - /// Met Level which eggs are given to the player. May change if transferred to future games. - /// - /// Game the egg is obtained in - /// Generation the egg is given in - public static int GetEggLevelMet(GameVersion version, int generation) => generation switch - { - 2 => version is C ? 1 : 0, // GS do not store met data - 3 or 4 => 0, - _ => 1, - }; - - /// - /// Checks if the and associated details can be set for the provided egg . - /// - /// Egg Entity - /// True if valid, false if invalid. - public static bool IsValidHTEgg(PKM pk) => pk switch - { - PB8 { Met_Location: Locations.LinkTrade6NPC } pb8 when pb8.HT_Friendship == PersonalTable.BDSP[pb8.Species].BaseFriendship => true, - _ => false, - }; - - /// - /// Indicates if the flag should be set for an Egg entity. - /// - /// Encounter the egg was generated with - /// Egg Entity - /// True if the flag should be set, otherwise false. - public static bool IsNicknameFlagSet(IEncounterTemplate enc, PKM pk) => enc switch - { - EncounterStatic7 => false, - WB8 or EncounterStatic8b when pk.IsUntraded => false, - { Generation: 4 } => false, - _ => true, - }; - - /// - public static bool IsNicknameFlagSet(PKM pk) => IsNicknameFlagSet(new LegalityAnalysis(pk).EncounterMatch, pk); - - /// - /// Gets a valid for an egg hatched in the origin game, accounting for future format transfers altering the data. - /// - public static int GetEggHatchLocation(GameVersion game, int format) => game switch - { - R or S or E or FR or LG => format switch - { - 3 => game is FR or LG ? Locations.HatchLocationFRLG : Locations.HatchLocationRSE, - 4 => Locations.Transfer3, // Pal Park - _ => Locations.Transfer4, - }, - - D or P or Pt => format > 4 ? Locations.Transfer4 : Locations.HatchLocationDPPt, - HG or SS => format > 4 ? Locations.Transfer4 : Locations.HatchLocationHGSS, - - B or W or B2 or W2 => Locations.HatchLocation5, - - X or Y => Locations.HatchLocation6XY, - AS or OR => Locations.HatchLocation6AO, - SN or MN or US or UM => Locations.HatchLocation7, - - GSC or C when format <= 2 => Locations.HatchLocationC, - RD or BU or GN or YW => Locations.Transfer1, - GD or SI or C => Locations.Transfer2, - - SW or SH => Locations.HatchLocation8, - BD or SP => Locations.HatchLocation8b, - _ => -1, - }; + return true; } + + /// + /// Gets the minimum hatch counter value allowed for an Egg Entity. + /// + /// Egg Entity + /// Usually 0... + public static int GetMinimumEggHatchCycles(PKM pk) => pk switch + { + PK2 or PB8 => 1, // no grace period between 1 step remaining and hatch + _ => 0, // having several Eggs in your party and then hatching one will give the rest 0... they can then be boxed! + }; + + /// + /// Will create a new to find the encounter. + public static int GetMaximumEggHatchCycles(PKM pk) + { + var la = new LegalityAnalysis(pk); + var enc = la.EncounterMatch; + return GetMaximumEggHatchCycles(pk, enc); + } + + /// + /// Gets the original Hatch Cycle value for an Egg Entity. + /// + /// Egg Entity + /// Encounter the egg was generated from + /// Maximum value the Hatch Counter can be. + public static int GetMaximumEggHatchCycles(PKM pk, IEncounterTemplate enc) + { + if (enc is EncounterStatic { EggCycles: not 0 } s) + return s.EggCycles; + return pk.PersonalInfo.HatchCycles; + } + + /// + /// Level which eggs are given to the player. + /// + /// Generation the egg is given in + public static byte GetEggLevel(int generation) => generation >= 4 ? (byte)1 : (byte)5; + + /// + /// Met Level which eggs are given to the player. May change if transferred to future games. + /// + /// Game the egg is obtained in + /// Generation the egg is given in + public static int GetEggLevelMet(GameVersion version, int generation) => generation switch + { + 2 => version is C ? 1 : 0, // GS do not store met data + 3 or 4 => 0, + _ => 1, + }; + + /// + /// Checks if the and associated details can be set for the provided egg . + /// + /// Egg Entity + /// True if valid, false if invalid. + public static bool IsValidHTEgg(PKM pk) => pk switch + { + PB8 { Met_Location: Locations.LinkTrade6NPC } pb8 when pb8.HT_Friendship == PersonalTable.BDSP[pb8.Species].BaseFriendship => true, + _ => false, + }; + + /// + /// Indicates if the flag should be set for an Egg entity. + /// + /// Encounter the egg was generated with + /// Egg Entity + /// True if the flag should be set, otherwise false. + public static bool IsNicknameFlagSet(IEncounterTemplate enc, PKM pk) => enc switch + { + EncounterStatic7 => false, + WB8 or EncounterStatic8b when pk.IsUntraded => false, + { Generation: 4 } => false, + _ => true, + }; + + /// + public static bool IsNicknameFlagSet(PKM pk) => IsNicknameFlagSet(new LegalityAnalysis(pk).EncounterMatch, pk); + + /// + /// Gets a valid for an egg hatched in the origin game, accounting for future format transfers altering the data. + /// + public static int GetEggHatchLocation(GameVersion game, int format) => game switch + { + R or S or E or FR or LG => format switch + { + 3 => game is FR or LG ? Locations.HatchLocationFRLG : Locations.HatchLocationRSE, + 4 => Locations.Transfer3, // Pal Park + _ => Locations.Transfer4, + }, + + D or P or Pt => format > 4 ? Locations.Transfer4 : Locations.HatchLocationDPPt, + HG or SS => format > 4 ? Locations.Transfer4 : Locations.HatchLocationHGSS, + + B or W or B2 or W2 => Locations.HatchLocation5, + + X or Y => Locations.HatchLocation6XY, + AS or OR => Locations.HatchLocation6AO, + SN or MN or US or UM => Locations.HatchLocation7, + + GSC or C when format <= 2 => Locations.HatchLocationC, + RD or BU or GN or YW => Locations.Transfer1, + GD or SI or C => Locations.Transfer2, + + SW or SH => Locations.HatchLocation8, + BD or SP => Locations.HatchLocation8b, + _ => -1, + }; } diff --git a/PKHeX.Core/Legality/Verifiers/FormVerifier.cs b/PKHeX.Core/Legality/Verifiers/FormVerifier.cs index 06c5bdeb9..e948147e6 100644 --- a/PKHeX.Core/Legality/Verifiers/FormVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/FormVerifier.cs @@ -1,399 +1,398 @@ -using System; +using System; using static PKHeX.Core.LegalityCheckStrings; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the value. +/// +public sealed class FormVerifier : Verifier { - /// - /// Verifies the value. - /// - public sealed class FormVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Form; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Form; + var pk = data.Entity; + if (pk.Format < 4) + return; // no forms exist + var result = VerifyForm(data); + data.AddLine(result); - public override void Verify(LegalityAnalysis data) + if (pk is IFormArgument f) + data.AddLine(VerifyFormArgument(data, f)); + } + + private CheckResult VALID => GetValid(LFormValid); + + private CheckResult VerifyForm(LegalityAnalysis data) + { + var pk = data.Entity; + var PersonalInfo = data.PersonalInfo; + + int count = PersonalInfo.FormCount; + var form = pk.Form; + if (count <= 1 && form == 0) + return VALID; // no forms to check + + var species = pk.Species; + var enc = data.EncounterMatch; + var Info = data.Info; + + if (!PersonalInfo.IsFormWithinRange(form) && !FormInfo.IsValidOutOfBoundsForm(species, form, Info.Generation)) + return GetInvalid(string.Format(LFormInvalidRange, count - 1, form)); + + switch ((Species)species) { - var pkm = data.pkm; - if (pkm.Format < 4) - return; // no forms exist - var result = VerifyForm(data); - data.AddLine(result); + case Pikachu when Info.Generation == 6: // Cosplay + bool isStatic = enc is EncounterStatic6; + bool validCosplay = form == (isStatic ? enc.Form : 0); + if (!validCosplay) + return GetInvalid(isStatic ? LFormPikachuCosplayInvalid : LFormPikachuCosplay); + break; - if (pkm is IFormArgument f) - data.AddLine(VerifyFormArgument(data, f)); - } - - private CheckResult VALID => GetValid(LFormValid); - - private CheckResult VerifyForm(LegalityAnalysis data) - { - var pkm = data.pkm; - var PersonalInfo = data.PersonalInfo; - - int count = PersonalInfo.FormCount; - var form = pkm.Form; - if (count <= 1 && form == 0) - return VALID; // no forms to check - - var species = pkm.Species; - var enc = data.EncounterMatch; - var Info = data.Info; - - if (!PersonalInfo.IsFormWithinRange(form) && !FormInfo.IsValidOutOfBoundsForm(species, form, Info.Generation)) - return GetInvalid(string.Format(LFormInvalidRange, count - 1, form)); - - switch ((Species)species) - { - case Pikachu when Info.Generation == 6: // Cosplay - bool isStatic = enc is EncounterStatic6; - bool validCosplay = form == (isStatic ? enc.Form : 0); - if (!validCosplay) - return GetInvalid(isStatic ? LFormPikachuCosplayInvalid : LFormPikachuCosplay); - break; - - case Pikachu when form is not 0 && ParseSettings.ActiveTrainer is SAV7b {Version:GameVersion.GE}: - case Eevee when form is not 0 && ParseSettings.ActiveTrainer is SAV7b {Version:GameVersion.GP}: - return GetInvalid(LFormBattle); - - case Pikachu when Info.Generation >= 7: // Cap - bool validCap = form == (enc is EncounterInvalid or EncounterEgg ? 0 : enc.Form); - if (!validCap) - { - bool gift = enc is MysteryGift g && g.Form != form; - var msg = gift ? LFormPikachuEventInvalid : LFormInvalidGame; - return GetInvalid(msg); - } - break; - case Unown when Info.Generation == 2 && form >= 26: - return GetInvalid(string.Format(LFormInvalidRange, "Z", form == 26 ? "!" : "?")); - case Dialga or Palkia or Giratina or Arceus when form > 0 && pkm is PA8: // can change forms with key items - break; - case Giratina when form == 1 ^ pkm.HeldItem == 112: // Giratina, Origin form only with Griseous Orb - return GetInvalid(LFormItemInvalid); - - case Arceus: - { - int arceus = GetArceusFormFromHeldItem(pkm.HeldItem, pkm.Format); - return arceus != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem); - } - case Keldeo when enc.Generation != 5 || pkm.Format >= 8: - // can mismatch in gen5 via BW tutor and transfer up - // can mismatch in gen8+ as the form activates in battle when knowing the move; outside of battle can be either state. - // Generation 8 patched out the mismatch; always forced to match moves. - bool hasSword = pkm.HasMove((int) Move.SecretSword); - bool isSword = pkm.Form == 1; - if (isSword != hasSword) - return GetInvalid(LMoveKeldeoMismatch); - break; - case Genesect: - { - int genesect = GetGenesectFormFromHeldItem(pkm.HeldItem); - return genesect != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem); - } - case Greninja: - if (form > 1) // Ash Battle Bond active - return GetInvalid(LFormBattle); - if (form != 0 && enc is not MysteryGift) // Formes are not breedable, MysteryGift already checked - return GetInvalid(string.Format(LFormInvalidRange, 0, form)); - break; - - case Scatterbug or Spewpa: - if (form > Vivillon3DS.MaxWildFormID) // Fancy & Pokéball - return GetInvalid(LFormVivillonEventPre); - if (pkm is not IRegionOrigin tr) - break; - if (!Vivillon3DS.IsPatternValid(form, tr.ConsoleRegion)) - return GetInvalid(LFormVivillonInvalid); - if (!Vivillon3DS.IsPatternNative(form, tr.Country, tr.Region)) - data.AddLine(Get(LFormVivillonNonNative, Severity.Fishy)); - break; - case Vivillon: - if (form > Vivillon3DS.MaxWildFormID) // Fancy & Pokéball - { - if (enc is not MysteryGift) - return GetInvalid(LFormVivillonInvalid); - return GetValid(LFormVivillon); - } - if (pkm is not IRegionOrigin trv) - break; - if (!Vivillon3DS.IsPatternValid(form, trv.ConsoleRegion)) - return GetInvalid(LFormVivillonInvalid); - if (!Vivillon3DS.IsPatternNative(form, trv.Country, trv.Region)) - data.AddLine(Get(LFormVivillonNonNative, Severity.Fishy)); - break; - - case Floette when form == 5: // Floette Eternal Flower -- Never Released - if (enc is not MysteryGift) - return GetInvalid(LFormEternalInvalid); - return GetValid(LFormEternal); - case Meowstic when form != pkm.Gender: - return GetInvalid(LGenderInvalidNone); - - case Silvally: - { - int silvally = GetSilvallyFormFromHeldItem(pkm.HeldItem); - return silvally != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem); - } - - // Form doesn't exist in SM; cannot originate from that game. - case Rockruff when enc.Generation == 7 && form == 1 && pkm.SM: - case Lycanroc when enc.Generation == 7 && form == 2 && pkm.SM: - return GetInvalid(LFormInvalidGame); - - // Toxel encounters have already been checked for the nature-specific evolution criteria. - case Toxtricity when enc.Species == (int)Toxtricity: - { - // The game enforces the Nature for Toxtricity encounters too! - if (pkm.Form != EvolutionMethod.GetAmpLowKeyResult(pkm.Nature)) - return GetInvalid(LFormInvalidNature); - break; - } - - // Impossible Egg forms - case Rotom when pkm.IsEgg && form != 0: - case Furfrou when pkm.IsEgg && form != 0: - return GetInvalid(LEggSpecies); - - // Party Only Forms - case Shaymin: - case Furfrou: - case Hoopa: - if (form != 0 && data.SlotOrigin is not SlotOrigin.Party && pkm.Format <= 6) // has form but stored in box - return GetInvalid(LFormParty); - break; - } - - var format = pkm.Format; - if (FormInfo.IsBattleOnlyForm(species, form, format)) + case Pikachu when form is not 0 && ParseSettings.ActiveTrainer is SAV7b {Version:GameVersion.GE}: + case Eevee when form is not 0 && ParseSettings.ActiveTrainer is SAV7b {Version:GameVersion.GP}: return GetInvalid(LFormBattle); - if (form == 0) - return VALID; - - // everything below here is not Form 0, so it has a form. - if (format >= 7 && Info.Generation < 7) - { - if (species == 25 || Legal.AlolanOriginForms.Contains(species) || Legal.AlolanVariantEvolutions12.Contains(enc.Species)) - return GetInvalid(LFormInvalidGame); - } - if (format >= 8 && Info.Generation < 8) - { - var orig = enc.Species; - if (Legal.GalarOriginForms.Contains(species) || Legal.GalarVariantFormEvolutions.Contains(orig)) + case Pikachu when Info.Generation >= 7: // Cap + bool validCap = form == (enc is EncounterInvalid or EncounterEgg ? 0 : enc.Form); + if (!validCap) { - if (species == (int)Meowth && enc.Form != 2) - { - // We're okay here. There's also Alolan Meowth... - } - else if (((Species)orig is MrMime or MimeJr) && pkm.CurrentLevel > enc.LevelMin && Info.Generation >= 4) - { - // We're okay with a Mime Jr. that has evolved via level up. - } - else if (enc.Version != GameVersion.GO) - { - return GetInvalid(LFormInvalidGame); - } + bool gift = enc is MysteryGift g && g.Form != form; + var msg = gift ? LFormPikachuEventInvalid : LFormInvalidGame; + return GetInvalid(msg); } + break; + case Unown when Info.Generation == 2 && form >= 26: + return GetInvalid(string.Format(LFormInvalidRange, "Z", form == 26 ? "!" : "?")); + case Dialga or Palkia or Giratina or Arceus when form > 0 && pk is PA8: // can change forms with key items + break; + case Giratina when form == 1 ^ pk.HeldItem == 112: // Giratina, Origin form only with Griseous Orb + return GetInvalid(LFormItemInvalid); + + case Arceus: + { + int arceus = GetArceusFormFromHeldItem(pk.HeldItem, pk.Format); + return arceus != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem); + } + case Keldeo when enc.Generation != 5 || pk.Format >= 8: + // can mismatch in gen5 via BW tutor and transfer up + // can mismatch in gen8+ as the form activates in battle when knowing the move; outside of battle can be either state. + // Generation 8 patched out the mismatch; always forced to match moves. + bool hasSword = pk.HasMove((int) Move.SecretSword); + bool isSword = pk.Form == 1; + if (isSword != hasSword) + return GetInvalid(LMoveKeldeoMismatch); + break; + case Genesect: + { + int genesect = GetGenesectFormFromHeldItem(pk.HeldItem); + return genesect != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem); + } + case Greninja: + if (form > 1) // Ash Battle Bond active + return GetInvalid(LFormBattle); + if (form != 0 && enc is not MysteryGift) // Formes are not breedable, MysteryGift already checked + return GetInvalid(string.Format(LFormInvalidRange, 0, form)); + break; + + case Scatterbug or Spewpa: + if (form > Vivillon3DS.MaxWildFormID) // Fancy & Pokéball + return GetInvalid(LFormVivillonEventPre); + if (pk is not IRegionOrigin tr) + break; + if (!Vivillon3DS.IsPatternValid(form, tr.ConsoleRegion)) + return GetInvalid(LFormVivillonInvalid); + if (!Vivillon3DS.IsPatternNative(form, tr.Country, tr.Region)) + data.AddLine(Get(LFormVivillonNonNative, Severity.Fishy)); + break; + case Vivillon: + if (form > Vivillon3DS.MaxWildFormID) // Fancy & Pokéball + { + if (enc is not MysteryGift) + return GetInvalid(LFormVivillonInvalid); + return GetValid(LFormVivillon); + } + if (pk is not IRegionOrigin trv) + break; + if (!Vivillon3DS.IsPatternValid(form, trv.ConsoleRegion)) + return GetInvalid(LFormVivillonInvalid); + if (!Vivillon3DS.IsPatternNative(form, trv.Country, trv.Region)) + data.AddLine(Get(LFormVivillonNonNative, Severity.Fishy)); + break; + + case Floette when form == 5: // Floette Eternal Flower -- Never Released + if (enc is not MysteryGift) + return GetInvalid(LFormEternalInvalid); + return GetValid(LFormEternal); + case Meowstic when form != pk.Gender: + return GetInvalid(LGenderInvalidNone); + + case Silvally: + { + int silvally = GetSilvallyFormFromHeldItem(pk.HeldItem); + return silvally != form ? GetInvalid(LFormItemInvalid) : GetValid(LFormItem); } + // Form doesn't exist in SM; cannot originate from that game. + case Rockruff when enc.Generation == 7 && form == 1 && pk.SM: + case Lycanroc when enc.Generation == 7 && form == 2 && pk.SM: + return GetInvalid(LFormInvalidGame); + + // Toxel encounters have already been checked for the nature-specific evolution criteria. + case Toxtricity when enc.Species == (int)Toxtricity: + { + // The game enforces the Nature for Toxtricity encounters too! + if (pk.Form != EvolutionMethod.GetAmpLowKeyResult(pk.Nature)) + return GetInvalid(LFormInvalidNature); + break; + } + + // Impossible Egg forms + case Rotom when pk.IsEgg && form != 0: + case Furfrou when pk.IsEgg && form != 0: + return GetInvalid(LEggSpecies); + + // Party Only Forms + case Shaymin: + case Furfrou: + case Hoopa: + if (form != 0 && data.SlotOrigin is not SlotOrigin.Party && pk.Format <= 6) // has form but stored in box + return GetInvalid(LFormParty); + break; + } + + var format = pk.Format; + if (FormInfo.IsBattleOnlyForm(species, form, format)) + return GetInvalid(LFormBattle); + + if (form == 0) return VALID; + + // everything below here is not Form 0, so it has a form. + if (format >= 7 && Info.Generation < 7) + { + if (species == 25 || Legal.AlolanOriginForms.Contains(species) || Legal.AlolanVariantEvolutions12.Contains(enc.Species)) + return GetInvalid(LFormInvalidGame); } - - private static readonly ushort[] Arceus_PlateIDs = { 303, 306, 304, 305, 309, 308, 310, 313, 298, 299, 301, 300, 307, 302, 311, 312, 644 }; - private static readonly ushort[] Arceus_ZCrystal = { 782, 785, 783, 784, 788, 787, 789, 792, 777, 778, 780, 779, 786, 781, 790, 791, 793 }; - - public static int GetArceusFormFromHeldItem(int item, int format) => item switch + if (format >= 8 && Info.Generation < 8) { - >= 777 and <= 793 => GetArceusFormFromZCrystal(item), - >= 298 and <= 313 or 644 => GetArceusFormFromPlate(item, format), - _ => 0, - }; - - private static int GetArceusFormFromZCrystal(int item) - { - return Array.IndexOf(Arceus_ZCrystal, (ushort)item) + 1; - } - - private static int GetArceusFormFromPlate(int item, int format) - { - int form = Array.IndexOf(Arceus_PlateIDs, (ushort)item) + 1; - if (format != 4) // No need to consider Curse type - return form; - if (form < 9) - return form; - return form + 1; // ??? type Form shifts everything by 1 - } - - public static int GetSilvallyFormFromHeldItem(int item) - { - if (item is >= 904 and <= 920) - return item - 903; - return 0; - } - - public static int GetGenesectFormFromHeldItem(int item) - { - if (item is >= 116 and <= 119) - return item - 115; - return 0; - } - - private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f) - { - var pkm = data.pkm; - var enc = data.EncounterMatch; - var arg = f.FormArgument; - - var unusedMask = pkm.Format == 6 ? 0xFFFF_FF00 : 0xFF00_0000; - if ((arg & unusedMask) != 0) - return GetInvalid(LFormArgumentHigh); - - return (Species)pkm.Species switch + var orig = enc.Species; + if (Legal.GalarOriginForms.Contains(species) || Legal.GalarVariantFormEvolutions.Contains(orig)) { - // Transfer Edge Cases -- Bank wipes the form but keeps old FormArgument value. - Furfrou when pkm.Format == 7 && pkm.Form == 0 && - ((enc.Generation == 6 && f.FormArgument <= byte.MaxValue) || IsFormArgumentDayCounterValid(f, 5, true)) - => GetValid(LFormArgumentValid), - - Furfrou when pkm.Form != 0 => !IsFormArgumentDayCounterValid(f, 5, true) ? GetInvalid(LFormArgumentInvalid) :GetValid(LFormArgumentValid), - Hoopa when pkm.Form == 1 => !IsFormArgumentDayCounterValid(f, 3) ? GetInvalid(LFormArgumentInvalid) : GetValid(LFormArgumentValid), - Yamask when pkm.Form == 1 => arg switch + if (species == (int)Meowth && enc.Form != 2) { - not 0 when pkm.IsEgg => GetInvalid(LFormArgumentNotAllowed), - > 9_999 => GetInvalid(LFormArgumentHigh), - _ => GetValid(LFormArgumentValid), - }, - Runerigus when enc.Species == (int)Runerigus => arg switch - { - not 0 => GetInvalid(LFormArgumentNotAllowed), - _ => GetValid(LFormArgumentValid), - }, - Runerigus => arg switch // From Yamask-1 - { - < 49 => GetInvalid(LFormArgumentLow), - > 9_999 => GetInvalid(LFormArgumentHigh), - _ => GetValid(LFormArgumentValid), - }, - Alcremie when enc.Species == (int)Alcremie => arg switch - { - not 0 => GetInvalid(LFormArgumentNotAllowed), - _ => GetValid(LFormArgumentValid), - }, - Alcremie => arg switch // From Milcery - { - > (uint) AlcremieDecoration.Ribbon => GetInvalid(LFormArgumentHigh), - _ => GetValid(LFormArgumentValid), - }, - Overqwil when enc.Species == (int)Overqwil => arg switch - { - not 0 => GetInvalid(LFormArgumentNotAllowed), - _ => GetValid(LFormArgumentValid), - }, - Wyrdeer when enc.Species == (int)Wyrdeer => arg switch - { - not 0 => GetInvalid(LFormArgumentNotAllowed), - _ => GetValid(LFormArgumentValid), - }, - Basculegion when enc.Species == (int)Basculegion => arg switch - { - not 0 => GetInvalid(LFormArgumentNotAllowed), - _ => GetValid(LFormArgumentValid), - }, - Basculin when pkm.Form is 2 => arg switch - { - not 0 when pkm.IsEgg => GetInvalid(LFormArgumentNotAllowed), - > 9_999 => GetInvalid(LFormArgumentHigh), - _ => GetValid(LFormArgumentValid), - }, - Qwilfish when pkm.Form is 1 => arg switch - { - not 0 when pkm.IsEgg => GetInvalid(LFormArgumentNotAllowed), - not 0 when pkm.CurrentLevel < 25 => GetInvalid(LFormArgumentHigh), - > 9_999 => GetInvalid(LFormArgumentHigh), - _ => GetValid(LFormArgumentValid), - }, - Stantler when pkm is PA8 || pkm.HasVisitedLA(data.Info.EvoChainsAllGens.Gen8a) => arg switch - { - not 0 when pkm.IsEgg => GetInvalid(LFormArgumentNotAllowed), - not 0 when pkm.CurrentLevel < 31 => GetInvalid(LFormArgumentHigh), - > 9_999 => GetInvalid(LFormArgumentHigh), - _ => GetValid(LFormArgumentValid), - }, - Wyrdeer => arg switch // From Stantler - { - < 20 => GetInvalid(LFormArgumentLow), - > 9_999 => GetInvalid(LFormArgumentHigh), - _ => GetValid(LFormArgumentValid), - }, - Overqwil => arg switch // From Qwilfish-1 - { - < 20 => GetInvalid(LFormArgumentLow), - > 9_999 => GetInvalid(LFormArgumentHigh), - _ => GetValid(LFormArgumentValid), - }, - Basculegion => arg switch // From Basculin-2 - { - < 294 => GetInvalid(LFormArgumentLow), - > 9_999 => GetInvalid(LFormArgumentHigh), - _ => GetValid(LFormArgumentValid), - }, - _ => VerifyFormArgumentNone(pkm, f), - }; - } - - private CheckResult VerifyFormArgumentNone(PKM pkm, IFormArgument f) - { - if (pkm is not PK6 pk6) - { - if (f.FormArgument != 0) - { - if (pkm.Species == (int)Furfrou && pkm.Form == 0 && (f.FormArgument & ~0xFF_00_00u) == 0) - return GetValid(LFormArgumentValid); - return GetInvalid(LFormArgumentNotAllowed); + // We're okay here. There's also Alolan Meowth... + } + else if (((Species)orig is MrMime or MimeJr) && pk.CurrentLevel > enc.LevelMin && Info.Generation >= 4) + { + // We're okay with a Mime Jr. that has evolved via level up. + } + else if (enc.Version != GameVersion.GO) + { + return GetInvalid(LFormInvalidGame); } - return GetValid(LFormArgumentValid); } + } + return VALID; + } + + private static readonly ushort[] Arceus_PlateIDs = { 303, 306, 304, 305, 309, 308, 310, 313, 298, 299, 301, 300, 307, 302, 311, 312, 644 }; + private static readonly ushort[] Arceus_ZCrystal = { 782, 785, 783, 784, 788, 787, 789, 792, 777, 778, 780, 779, 786, 781, 790, 791, 793 }; + + public static int GetArceusFormFromHeldItem(int item, int format) => item switch + { + (>= 777 and <= 793) => GetArceusFormFromZCrystal(item), + (>= 298 and <= 313) or 644 => GetArceusFormFromPlate(item, format), + _ => 0, + }; + + private static int GetArceusFormFromZCrystal(int item) + { + return Array.IndexOf(Arceus_ZCrystal, (ushort)item) + 1; + } + + private static int GetArceusFormFromPlate(int item, int format) + { + int form = Array.IndexOf(Arceus_PlateIDs, (ushort)item) + 1; + if (format != 4) // No need to consider Curse type + return form; + if (form < 9) + return form; + return form + 1; // ??? type Form shifts everything by 1 + } + + public static int GetSilvallyFormFromHeldItem(int item) + { + if (item is >= 904 and <= 920) + return item - 903; + return 0; + } + + public static int GetGenesectFormFromHeldItem(int item) + { + if (item is >= 116 and <= 119) + return item - 115; + return 0; + } + + private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f) + { + var pk = data.Entity; + var enc = data.EncounterMatch; + var arg = f.FormArgument; + + var unusedMask = pk.Format == 6 ? 0xFFFF_FF00 : 0xFF00_0000; + if ((arg & unusedMask) != 0) + return GetInvalid(LFormArgumentHigh); + + return (Species)pk.Species switch + { + // Transfer Edge Cases -- Bank wipes the form but keeps old FormArgument value. + Furfrou when pk.Format == 7 && pk.Form == 0 && + ((enc.Generation == 6 && f.FormArgument <= byte.MaxValue) || IsFormArgumentDayCounterValid(f, 5, true)) + => GetValid(LFormArgumentValid), + + Furfrou when pk.Form != 0 => !IsFormArgumentDayCounterValid(f, 5, true) ? GetInvalid(LFormArgumentInvalid) :GetValid(LFormArgumentValid), + Hoopa when pk.Form == 1 => !IsFormArgumentDayCounterValid(f, 3) ? GetInvalid(LFormArgumentInvalid) : GetValid(LFormArgumentValid), + Yamask when pk.Form == 1 => arg switch + { + not 0 when pk.IsEgg => GetInvalid(LFormArgumentNotAllowed), + > 9_999 => GetInvalid(LFormArgumentHigh), + _ => GetValid(LFormArgumentValid), + }, + Runerigus when enc.Species == (int)Runerigus => arg switch + { + not 0 => GetInvalid(LFormArgumentNotAllowed), + _ => GetValid(LFormArgumentValid), + }, + Runerigus => arg switch // From Yamask-1 + { + < 49 => GetInvalid(LFormArgumentLow), + > 9_999 => GetInvalid(LFormArgumentHigh), + _ => GetValid(LFormArgumentValid), + }, + Alcremie when enc.Species == (int)Alcremie => arg switch + { + not 0 => GetInvalid(LFormArgumentNotAllowed), + _ => GetValid(LFormArgumentValid), + }, + Alcremie => arg switch // From Milcery + { + > (uint) AlcremieDecoration.Ribbon => GetInvalid(LFormArgumentHigh), + _ => GetValid(LFormArgumentValid), + }, + Overqwil when enc.Species == (int)Overqwil => arg switch + { + not 0 => GetInvalid(LFormArgumentNotAllowed), + _ => GetValid(LFormArgumentValid), + }, + Wyrdeer when enc.Species == (int)Wyrdeer => arg switch + { + not 0 => GetInvalid(LFormArgumentNotAllowed), + _ => GetValid(LFormArgumentValid), + }, + Basculegion when enc.Species == (int)Basculegion => arg switch + { + not 0 => GetInvalid(LFormArgumentNotAllowed), + _ => GetValid(LFormArgumentValid), + }, + Basculin when pk.Form is 2 => arg switch + { + not 0 when pk.IsEgg => GetInvalid(LFormArgumentNotAllowed), + > 9_999 => GetInvalid(LFormArgumentHigh), + _ => GetValid(LFormArgumentValid), + }, + Qwilfish when pk.Form is 1 => arg switch + { + not 0 when pk.IsEgg => GetInvalid(LFormArgumentNotAllowed), + not 0 when pk.CurrentLevel < 25 => GetInvalid(LFormArgumentHigh), + > 9_999 => GetInvalid(LFormArgumentHigh), + _ => GetValid(LFormArgumentValid), + }, + Stantler when pk is PA8 || pk.HasVisitedLA(data.Info.EvoChainsAllGens.Gen8a) => arg switch + { + not 0 when pk.IsEgg => GetInvalid(LFormArgumentNotAllowed), + not 0 when pk.CurrentLevel < 31 => GetInvalid(LFormArgumentHigh), + > 9_999 => GetInvalid(LFormArgumentHigh), + _ => GetValid(LFormArgumentValid), + }, + Wyrdeer => arg switch // From Stantler + { + < 20 => GetInvalid(LFormArgumentLow), + > 9_999 => GetInvalid(LFormArgumentHigh), + _ => GetValid(LFormArgumentValid), + }, + Overqwil => arg switch // From Qwilfish-1 + { + < 20 => GetInvalid(LFormArgumentLow), + > 9_999 => GetInvalid(LFormArgumentHigh), + _ => GetValid(LFormArgumentValid), + }, + Basculegion => arg switch // From Basculin-2 + { + < 294 => GetInvalid(LFormArgumentLow), + > 9_999 => GetInvalid(LFormArgumentHigh), + _ => GetValid(LFormArgumentValid), + }, + _ => VerifyFormArgumentNone(pk, f), + }; + } + + private CheckResult VerifyFormArgumentNone(PKM pk, IFormArgument f) + { + if (pk is not PK6 pk6) + { if (f.FormArgument != 0) { - if (pkm.Species == (int)Furfrou && pkm.Form == 0 && (f.FormArgument & ~0xFFu) == 0) + if (pk.Species == (int)Furfrou && pk.Form == 0 && (f.FormArgument & ~0xFF_00_00u) == 0) return GetValid(LFormArgumentValid); return GetInvalid(LFormArgumentNotAllowed); } - - // Stored separately from main form argument value - if (pk6.FormArgumentRemain != 0) - return GetInvalid(LFormArgumentNotAllowed); - if (pk6.FormArgumentElapsed != 0) - return GetInvalid(LFormArgumentNotAllowed); - return GetValid(LFormArgumentValid); } - private static bool IsFormArgumentDayCounterValid(IFormArgument f, uint maxSeed, bool canRefresh = false) + if (f.FormArgument != 0) { - var remain = f.FormArgumentRemain; - var elapsed = f.FormArgumentElapsed; - var maxElapsed = f.FormArgumentMaximum; - if (canRefresh) - { - if (maxElapsed < elapsed) - return false; - - if (remain + elapsed < maxSeed) - return false; - } - else - { - if (maxElapsed != 0) - return false; - - if (remain + elapsed != maxSeed) - return false; - } - if (remain > maxSeed) - return false; - return remain != 0; + if (pk.Species == (int)Furfrou && pk.Form == 0 && (f.FormArgument & ~0xFFu) == 0) + return GetValid(LFormArgumentValid); + return GetInvalid(LFormArgumentNotAllowed); } + + // Stored separately from main form argument value + if (pk6.FormArgumentRemain != 0) + return GetInvalid(LFormArgumentNotAllowed); + if (pk6.FormArgumentElapsed != 0) + return GetInvalid(LFormArgumentNotAllowed); + + return GetValid(LFormArgumentValid); + } + + private static bool IsFormArgumentDayCounterValid(IFormArgument f, uint maxSeed, bool canRefresh = false) + { + var remain = f.FormArgumentRemain; + var elapsed = f.FormArgumentElapsed; + var maxElapsed = f.FormArgumentMaximum; + if (canRefresh) + { + if (maxElapsed < elapsed) + return false; + + if (remain + elapsed < maxSeed) + return false; + } + else + { + if (maxElapsed != 0) + return false; + + if (remain + elapsed != maxSeed) + return false; + } + if (remain > maxSeed) + return false; + return remain != 0; } } diff --git a/PKHeX.Core/Legality/Verifiers/GenderVerifier.cs b/PKHeX.Core/Legality/Verifiers/GenderVerifier.cs index 54fc0431d..72f71489d 100644 --- a/PKHeX.Core/Legality/Verifiers/GenderVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/GenderVerifier.cs @@ -1,87 +1,86 @@ using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the . +/// +public sealed class GenderVerifier : Verifier { - /// - /// Verifies the . - /// - public sealed class GenderVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Gender; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Gender; - - public override void Verify(LegalityAnalysis data) + var pk = data.Entity; + var pi = pk.PersonalInfo; + if (pi.Genderless != (pk.Gender == 2)) { - var pkm = data.pkm; - var pi = pkm.PersonalInfo; - if (pi.Genderless != (pkm.Gender == 2)) - { - // DP/HGSS shedinja glitch -- only generation 4 spawns - bool ignore = pkm.Format == 4 && pkm.Species == (int)Species.Shedinja && pkm.Met_Level != pkm.CurrentLevel; - if (!ignore) - data.AddLine(GetInvalid(LGenderInvalidNone)); - return; - } - - // Check for PID relationship to Gender & Nature if applicable - int gen = data.Info.Generation; - if (gen is 3 or 4 or 5) - { - // Gender-PID & Nature-PID relationship check - var result = IsValidGenderPID(data) ? GetValid(LPIDGenderMatch) : GetInvalid(LPIDGenderMismatch); - data.AddLine(result); - - if (gen != 5) - VerifyNaturePID(data); - return; - } - - // Check fixed gender cases - if ((pi.OnlyFemale && pkm.Gender != 1) || (pi.OnlyMale && pkm.Gender != 0)) + // DP/HGSS shedinja glitch -- only generation 4 spawns + bool ignore = pk.Format == 4 && pk.Species == (int)Species.Shedinja && pk.Met_Level != pk.CurrentLevel; + if (!ignore) data.AddLine(GetInvalid(LGenderInvalidNone)); + return; } - private static void VerifyNaturePID(LegalityAnalysis data) + // Check for PID relationship to Gender & Nature if applicable + int gen = data.Info.Generation; + if (gen is 3 or 4 or 5) { - var pkm = data.pkm; - var result = pkm.EncryptionConstant % 25 == pkm.Nature - ? GetValid(LPIDNatureMatch, CheckIdentifier.Nature) - : GetInvalid(LPIDNatureMismatch, CheckIdentifier.Nature); + // Gender-PID & Nature-PID relationship check + var result = IsValidGenderPID(data) ? GetValid(LPIDGenderMatch) : GetInvalid(LPIDGenderMismatch); data.AddLine(result); + + if (gen != 5) + VerifyNaturePID(data); + return; } - private static bool IsValidGenderPID(LegalityAnalysis data) - { - var pkm = data.pkm; - bool genderValid = pkm.IsGenderValid(); - if (!genderValid) - return IsValidGenderMismatch(pkm); - - // check for mixed->fixed gender incompatibility by checking the gender of the original species - int original = data.EncounterMatch.Species; - if (Legal.FixedGenderFromBiGender.Contains(original)) - return IsValidFixedGenderFromBiGender(pkm, original); - - return true; - } - - private static bool IsValidFixedGenderFromBiGender(PKM pkm, int original) - { - var current = pkm.Gender; - if (current == 2) // shedinja, genderless - return true; - var gender = EntityGender.GetFromPID(original, pkm.EncryptionConstant); - return gender == current; - } - - private static bool IsValidGenderMismatch(PKM pkm) => pkm.Species switch - { - // Shedinja evolution gender glitch, should match original Gender - (int) Species.Shedinja when pkm.Format == 4 => pkm.Gender == EntityGender.GetFromPIDAndRatio(pkm.EncryptionConstant, 0x7F), // 50M-50F - - // Evolved from Azurill after transferring to keep gender - (int) Species.Marill or (int) Species.Azumarill when pkm.Format >= 6 => pkm.Gender == 1 && (pkm.EncryptionConstant & 0xFF) > 0x3F, - - _ => false, - }; + // Check fixed gender cases + if ((pi.OnlyFemale && pk.Gender != 1) || (pi.OnlyMale && pk.Gender != 0)) + data.AddLine(GetInvalid(LGenderInvalidNone)); } + + private static void VerifyNaturePID(LegalityAnalysis data) + { + var pk = data.Entity; + var result = pk.EncryptionConstant % 25 == pk.Nature + ? GetValid(LPIDNatureMatch, CheckIdentifier.Nature) + : GetInvalid(LPIDNatureMismatch, CheckIdentifier.Nature); + data.AddLine(result); + } + + private static bool IsValidGenderPID(LegalityAnalysis data) + { + var pk = data.Entity; + bool genderValid = pk.IsGenderValid(); + if (!genderValid) + return IsValidGenderMismatch(pk); + + // check for mixed->fixed gender incompatibility by checking the gender of the original species + int original = data.EncounterMatch.Species; + if (Legal.FixedGenderFromBiGender.Contains(original)) + return IsValidFixedGenderFromBiGender(pk, original); + + return true; + } + + private static bool IsValidFixedGenderFromBiGender(PKM pk, int original) + { + var current = pk.Gender; + if (current == 2) // shedinja, genderless + return true; + var gender = EntityGender.GetFromPID(original, pk.EncryptionConstant); + return gender == current; + } + + private static bool IsValidGenderMismatch(PKM pk) => pk.Species switch + { + // Shedinja evolution gender glitch, should match original Gender + (int) Species.Shedinja when pk.Format == 4 => pk.Gender == EntityGender.GetFromPIDAndRatio(pk.EncryptionConstant, 0x7F), // 50M-50F + + // Evolved from Azurill after transferring to keep gender + (int) Species.Marill or (int) Species.Azumarill when pk.Format >= 6 => pk.Gender == 1 && (pk.EncryptionConstant & 0xFF) > 0x3F, + + _ => false, + }; } diff --git a/PKHeX.Core/Legality/Verifiers/GroundTileVerifier.cs b/PKHeX.Core/Legality/Verifiers/GroundTileVerifier.cs index fa241ce84..ce1373c4f 100644 --- a/PKHeX.Core/Legality/Verifiers/GroundTileVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/GroundTileVerifier.cs @@ -1,21 +1,20 @@ using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core -{ - /// - /// Verifies the . - /// - public sealed class GroundTileVerifier : Verifier - { - protected override CheckIdentifier Identifier => CheckIdentifier.Encounter; +namespace PKHeX.Core; - public override void Verify(LegalityAnalysis data) - { - if (data.pkm is not IGroundTile e) - return; - var type = data.EncounterMatch is IGroundTypeTile t ? t.GroundTile : GroundTilePermission.None; - var result = !type.Contains(e.GroundTile) ? GetInvalid(LEncTypeMismatch) : GetValid(LEncTypeMatch); - data.AddLine(result); - } +/// +/// Verifies the . +/// +public sealed class GroundTileVerifier : Verifier +{ + protected override CheckIdentifier Identifier => CheckIdentifier.Encounter; + + public override void Verify(LegalityAnalysis data) + { + if (data.Entity is not IGroundTile e) + return; + var type = data.EncounterMatch is IGroundTypeTile t ? t.GroundTile : GroundTileAllowed.None; + var result = !type.Contains(e.GroundTile) ? GetInvalid(LEncTypeMismatch) : GetValid(LEncTypeMatch); + data.AddLine(result); } } diff --git a/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs b/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs index 405b669b7..c3f5f7491 100644 --- a/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/HistoryVerifier.cs @@ -1,260 +1,266 @@ -using System; +using System; +using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the Friendship, Affection, and other miscellaneous stats that can be present for OT/HT data. +/// +public sealed class HistoryVerifier : Verifier { - /// - /// Verifies the Friendship, Affection, and other miscellaneous stats that can be present for OT/HT data. - /// - public sealed class HistoryVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Memory; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Memory; + bool neverOT = !GetCanOTHandle(data.Info.EncounterMatch, data.Entity, data.Info.Generation); + VerifyHandlerState(data, neverOT); + VerifyTradeState(data); + VerifyOTMisc(data, neverOT); + VerifyHTMisc(data); + } - public override void Verify(LegalityAnalysis data) + private void VerifyTradeState(LegalityAnalysis data) + { + var pk = data.Entity; + + if (data.Entity is IGeoTrack t) + VerifyGeoLocationData(data, t, data.Entity); + + if (pk.VC && pk is PK7 {Geo1_Country: 0}) // VC transfers set Geo1 Country + data.AddLine(GetInvalid(LGeoMemoryMissing)); + + if (!pk.IsUntraded) { - bool neverOT = !GetCanOTHandle(data.Info.EncounterMatch, data.pkm, data.Info.Generation); - VerifyHandlerState(data, neverOT); - VerifyTradeState(data); - VerifyOTMisc(data, neverOT); - VerifyHTMisc(data); + // Can't have HT details even as a Link Trade egg, except in some games. + if (pk.IsEgg && !EggStateLegality.IsValidHTEgg(pk)) + data.AddLine(GetInvalid(LMemoryArgBadHT)); + return; } - private void VerifyTradeState(LegalityAnalysis data) + if (pk.CurrentHandler != 0) // Badly edited; PKHeX doesn't trip this. + data.AddLine(GetInvalid(LMemoryHTFlagInvalid)); + else if (pk.HT_Friendship != 0) + data.AddLine(GetInvalid(LMemoryStatFriendshipHT0)); + else if (pk is IAffection {HT_Affection: not 0}) + data.AddLine(GetInvalid(LMemoryStatAffectionHT0)); + + // Don't check trade evolutions if Untraded. The Evolution Chain already checks for trade evolutions. + } + + /// + /// Checks if the state is set correctly. + /// + private void VerifyHandlerState(LegalityAnalysis data, bool neverOT) + { + var pk = data.Entity; + var Info = data.Info; + + // HT Flag + if (ParseSettings.CheckActiveHandler) { - var pkm = data.pkm; - - if (data.pkm is IGeoTrack t) - VerifyGeoLocationData(data, t, data.pkm); - - if (pkm.VC && pkm is PK7 {Geo1_Country: 0}) // VC transfers set Geo1 Country - data.AddLine(GetInvalid(LegalityCheckStrings.LGeoMemoryMissing)); - - if (!pkm.IsUntraded) + var tr = ParseSettings.ActiveTrainer; + var withOT = tr.IsFromTrainer(pk); + var flag = pk.CurrentHandler; + var expect = withOT ? 0 : 1; + if (flag != expect) { - // Can't have HT details even as a Link Trade egg, except in some games. - if (pkm.IsEgg && !EggStateLegality.IsValidHTEgg(pkm)) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryArgBadHT)); + data.AddLine(GetInvalid(LTransferCurrentHandlerInvalid)); return; } - if (pkm.CurrentHandler != 0) // Badly edited; PKHeX doesn't trip this. - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryHTFlagInvalid)); - else if (pkm.HT_Friendship != 0) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatFriendshipHT0)); - else if (pkm is IAffection {HT_Affection: not 0}) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatAffectionHT0)); - - // Don't check trade evolutions if Untraded. The Evolution Chain already checks for trade evolutions. - } - - /// - /// Checks if the state is set correctly. - /// - private void VerifyHandlerState(LegalityAnalysis data, bool neverOT) - { - var pkm = data.pkm; - var Info = data.Info; - - // HT Flag - if (ParseSettings.CheckActiveHandler) + if (flag == 1) { - var tr = ParseSettings.ActiveTrainer; - var withOT = tr.IsFromTrainer(pkm); - var flag = pkm.CurrentHandler; - var expect = withOT ? 0 : 1; - if (flag != expect) - { - data.AddLine(GetInvalid(LegalityCheckStrings.LTransferCurrentHandlerInvalid)); - return; - } - - if (flag == 1) - { - if (pkm.HT_Name != tr.OT) - data.AddLine(GetInvalid(LegalityCheckStrings.LTransferHTMismatchName)); - if (pkm is IHandlerLanguage h && h.HT_Language != tr.Language) - data.AddLine(GetInvalid(LegalityCheckStrings.LTransferHTMismatchLanguage)); - if (Info.EncounterMatch is EncounterStatic7b { Location: 28 }) // Starter, untradeable - data.AddLine(GetInvalid(LegalityCheckStrings.LTransferCurrentHandlerInvalid)); - } - } - - if ((Info.Generation != pkm.Format || neverOT) && pkm.CurrentHandler != 1) - data.AddLine(GetInvalid(LegalityCheckStrings.LTransferHTFlagRequired)); - } - - /// - /// Checks the non-Memory data for the details. - /// - private void VerifyOTMisc(LegalityAnalysis data, bool neverOT) - { - var pkm = data.pkm; - var Info = data.Info; - - VerifyOTAffection(data, neverOT, Info.Generation, pkm); - VerifyOTFriendship(data, neverOT, Info.Generation, pkm); - } - - private void VerifyOTFriendship(LegalityAnalysis data, bool neverOT, int origin, PKM pkm) - { - if (origin < 0) - return; - - if (origin <= 2) - { - VerifyOTFriendshipVC12(data, pkm); - return; - } - - if (neverOT) - { - // Verify the original friendship value since it cannot change from the value it was assigned in the original generation. - // If none match, then it is not a valid OT friendship. - var fs = pkm.OT_Friendship; - var enc = data.Info.EncounterMatch; - if (GetBaseFriendship(enc, origin) != fs) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatFriendshipOTBaseEvent)); + if (pk.HT_Name != tr.OT) + data.AddLine(GetInvalid(LTransferHTMismatchName)); + if (pk is IHandlerLanguage h && h.HT_Language != tr.Language) + data.AddLine(GetInvalid(LTransferHTMismatchLanguage)); } } - private void VerifyOTFriendshipVC12(LegalityAnalysis data, PKM pkm) + if (!pk.IsUntraded && IsUntradeableEncounter(Info.EncounterMatch)) // Starter, untradeable + data.AddLine(GetInvalid(LTransferCurrentHandlerInvalid)); + if ((Info.Generation != pk.Format || neverOT) && pk.CurrentHandler != 1) + data.AddLine(GetInvalid(LTransferHTFlagRequired)); + } + + private static bool IsUntradeableEncounter(IEncounterTemplate enc) => enc switch + { + EncounterStatic7b { Location: 28 } => true, // LGP/E Starter + _ => false, + }; + + /// + /// Checks the non-Memory data for the details. + /// + private void VerifyOTMisc(LegalityAnalysis data, bool neverOT) + { + var pk = data.Entity; + var Info = data.Info; + + VerifyOTAffection(data, neverOT, Info.Generation, pk); + VerifyOTFriendship(data, neverOT, Info.Generation, pk); + } + + private void VerifyOTFriendship(LegalityAnalysis data, bool neverOT, int origin, PKM pk) + { + if (origin < 0) + return; + + if (origin <= 2) + { + VerifyOTFriendshipVC12(data, pk); + return; + } + + if (neverOT) { // Verify the original friendship value since it cannot change from the value it was assigned in the original generation. - // Since some evolutions have different base friendship values, check all possible evolutions for a match. // If none match, then it is not a valid OT friendship. - // VC transfers use SM personal info - var any = IsMatchFriendship(data.Info.EvoChainsAllGens.Gen7, PersonalTable.USUM, pkm.OT_Friendship); - if (!any) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatFriendshipOTBaseEvent)); + var fs = pk.OT_Friendship; + var enc = data.Info.EncounterMatch; + if (GetBaseFriendship(enc, origin) != fs) + data.AddLine(GetInvalid(LMemoryStatFriendshipOTBaseEvent)); } + } - private static bool IsMatchFriendship(EvoCriteria[] evos, PersonalTable pt, int fs) + private void VerifyOTFriendshipVC12(LegalityAnalysis data, PKM pk) + { + // Verify the original friendship value since it cannot change from the value it was assigned in the original generation. + // Since some evolutions have different base friendship values, check all possible evolutions for a match. + // If none match, then it is not a valid OT friendship. + // VC transfers use SM personal info + var any = IsMatchFriendship(data.Info.EvoChainsAllGens.Gen7, PersonalTable.USUM, pk.OT_Friendship); + if (!any) + data.AddLine(GetInvalid(LMemoryStatFriendshipOTBaseEvent)); + } + + private static bool IsMatchFriendship(EvoCriteria[] evos, PersonalTable pt, int fs) + { + foreach (var z in evos) { - foreach (var z in evos) - { - if (!pt.IsPresentInGame(z.Species, z.Form)) - continue; - var entry = pt.GetFormEntry(z.Species, z.Form); - if (entry.BaseFriendship == fs) - return true; - } - return false; + if (!pt.IsPresentInGame(z.Species, z.Form)) + continue; + var entry = pt.GetFormEntry(z.Species, z.Form); + if (entry.BaseFriendship == fs) + return true; } + return false; + } - private void VerifyOTAffection(LegalityAnalysis data, bool neverOT, int origin, PKM pkm) + private void VerifyOTAffection(LegalityAnalysis data, bool neverOT, int origin, PKM pk) + { + if (pk is not IAffection a) + return; + + if (origin < 6) { - if (pkm is not IAffection a) - return; - - if (origin < 6) + // Can gain affection in Gen6 via the Contest glitch applying affection to OT rather than HT. + // VC encounters cannot obtain OT affection since they can't visit Gen6. + if ((origin <= 2 && a.OT_Affection != 0) || IsInvalidContestAffection(a)) + data.AddLine(GetInvalid(LMemoryStatAffectionOT0)); + } + else if (neverOT) + { + if (origin == 6) { - // Can gain affection in Gen6 via the Contest glitch applying affection to OT rather than HT. - // VC encounters cannot obtain OT affection since they can't visit Gen6. - if ((origin <= 2 && a.OT_Affection != 0) || IsInvalidContestAffection(a)) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatAffectionOT0)); - } - else if (neverOT) - { - if (origin == 6) - { - if (pkm.IsUntraded && pkm.XY) - { - if (a.OT_Affection != 0) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatAffectionOT0)); - } - else if (IsInvalidContestAffection(a)) - { - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatAffectionOT0)); - } - } - else + if (pk.IsUntraded && pk.XY) { if (a.OT_Affection != 0) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatAffectionOT0)); + data.AddLine(GetInvalid(LMemoryStatAffectionOT0)); + } + else if (IsInvalidContestAffection(a)) + { + data.AddLine(GetInvalid(LMemoryStatAffectionOT0)); } } - } - - /// - /// Checks the non-Memory data for the details. - /// - private void VerifyHTMisc(LegalityAnalysis data) - { - var pkm = data.pkm; - var htGender = pkm.HT_Gender; - if (htGender > 1 || (pkm.IsUntraded && htGender != 0)) - data.AddLine(GetInvalid(string.Format(LegalityCheckStrings.LMemoryHTGender, htGender))); - - if (pkm is IHandlerLanguage h) - VerifyHTLanguage(data, h, pkm); - } - - private void VerifyHTLanguage(LegalityAnalysis data, IHandlerLanguage h, PKM pkm) - { - if (h.HT_Language == 0) + else { - if (!string.IsNullOrWhiteSpace(pkm.HT_Name)) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryHTLanguage)); - return; + if (a.OT_Affection != 0) + data.AddLine(GetInvalid(LMemoryStatAffectionOT0)); } + } + } - if (string.IsNullOrWhiteSpace(pkm.HT_Name)) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryHTLanguage)); - else if (h.HT_Language > (int)LanguageID.ChineseT) - data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryHTLanguage)); + /// + /// Checks the non-Memory data for the details. + /// + private void VerifyHTMisc(LegalityAnalysis data) + { + var pk = data.Entity; + var htGender = pk.HT_Gender; + if (htGender > 1 || (pk.IsUntraded && htGender != 0)) + data.AddLine(GetInvalid(string.Format(LMemoryHTGender, htGender))); + + if (pk is IHandlerLanguage h) + VerifyHTLanguage(data, h, pk); + } + + private void VerifyHTLanguage(LegalityAnalysis data, IHandlerLanguage h, PKM pk) + { + if (h.HT_Language == 0) + { + if (!string.IsNullOrWhiteSpace(pk.HT_Name)) + data.AddLine(GetInvalid(LMemoryHTLanguage)); + return; } - private void VerifyGeoLocationData(LegalityAnalysis data, IGeoTrack t, PKM pkm) + if (string.IsNullOrWhiteSpace(pk.HT_Name)) + data.AddLine(GetInvalid(LMemoryHTLanguage)); + else if (h.HT_Language > (int)LanguageID.ChineseT) + data.AddLine(GetInvalid(LMemoryHTLanguage)); + } + + private void VerifyGeoLocationData(LegalityAnalysis data, IGeoTrack t, PKM pk) + { + var valid = t.GetValidity(); + if (valid == GeoValid.CountryAfterPreviousEmpty) + data.AddLine(GetInvalid(LGeoBadOrder)); + else if (valid == GeoValid.RegionWithoutCountry) + data.AddLine(GetInvalid(LGeoNoRegion)); + if (t.Geo1_Country != 0 && pk.IsUntraded) // traded + data.AddLine(GetInvalid(LGeoNoCountryHT)); + } + + // ORAS contests mistakenly apply 20 affection to the OT instead of the current handler's value + private static bool IsInvalidContestAffection(IAffection pk) => pk.OT_Affection != 255 && pk.OT_Affection % 20 != 0; + + public static bool GetCanOTHandle(IEncounterTemplate enc, PKM pk, int generation) + { + // Handlers introduced in Generation 6. OT Handling was always the case for Generation 3-5 data. + if (generation < 6) + return generation >= 3; + + return enc switch { - var valid = t.GetValidity(); - if (valid == GeoValid.CountryAfterPreviousEmpty) - data.AddLine(GetInvalid(LegalityCheckStrings.LGeoBadOrder)); - else if (valid == GeoValid.RegionWithoutCountry) - data.AddLine(GetInvalid(LegalityCheckStrings.LGeoNoRegion)); - if (t.Geo1_Country != 0 && pkm.IsUntraded) // traded - data.AddLine(GetInvalid(LegalityCheckStrings.LGeoNoCountryHT)); - } - - // ORAS contests mistakenly apply 20 affection to the OT instead of the current handler's value - private static bool IsInvalidContestAffection(IAffection pkm) => pkm.OT_Affection != 255 && pkm.OT_Affection % 20 != 0; - - public static bool GetCanOTHandle(IEncounterTemplate enc, PKM pkm, int generation) - { - // Handlers introduced in Generation 6. OT Handling was always the case for Generation 3-5 data. - if (generation < 6) - return generation >= 3; - - return enc switch - { - EncounterTrade => false, - EncounterSlot8GO => false, - WC6 wc6 when wc6.OT_Name.Length > 0 => false, - WC7 wc7 when wc7.OT_Name.Length > 0 && wc7.TID != 18075 => false, // Ash Pikachu QR Gift doesn't set Current Handler - WC8 wc8 when wc8.GetHasOT(pkm.Language) => false, - WB8 wb8 when wb8.GetHasOT(pkm.Language) => false, - WA8 wa8 when wa8.GetHasOT(pkm.Language) => false, - WC8 {IsHOMEGift: true} => false, - _ => true, - }; - } - - private static int GetBaseFriendship(IEncounterTemplate enc, int generation) => enc switch - { - IFixedOTFriendship f => f.OT_Friendship, - - { Version: GameVersion.BDSP or GameVersion.BD or GameVersion.SP } - => PersonalTable.BDSP.GetFormEntry(enc.Species, enc.Form).BaseFriendship, - { Version: GameVersion.PLA } - => PersonalTable.LA .GetFormEntry(enc.Species, enc.Form).BaseFriendship, - - _ => GetBaseFriendship(generation, enc.Species, enc.Form), - }; - - private static int GetBaseFriendship(int generation, int species, int form) => generation switch - { - 6 => PersonalTable.AO[species].BaseFriendship, - 7 => PersonalTable.USUM[species].BaseFriendship, - 8 => PersonalTable.SWSH.GetFormEntry(species, form).BaseFriendship, - _ => throw new ArgumentOutOfRangeException(nameof(generation)), + EncounterTrade => false, + EncounterSlot8GO => false, + WC6 wc6 when wc6.OT_Name.Length > 0 => false, + WC7 wc7 when wc7.OT_Name.Length > 0 && wc7.TID != 18075 => false, // Ash Pikachu QR Gift doesn't set Current Handler + WC8 wc8 when wc8.GetHasOT(pk.Language) => false, + WB8 wb8 when wb8.GetHasOT(pk.Language) => false, + WA8 wa8 when wa8.GetHasOT(pk.Language) => false, + WC8 {IsHOMEGift: true} => false, + _ => true, }; } + + private static int GetBaseFriendship(IEncounterTemplate enc, int generation) => enc switch + { + IFixedOTFriendship f => f.OT_Friendship, + + { Version: GameVersion.BDSP or GameVersion.BD or GameVersion.SP } + => PersonalTable.BDSP.GetFormEntry(enc.Species, enc.Form).BaseFriendship, + { Version: GameVersion.PLA } + => PersonalTable.LA .GetFormEntry(enc.Species, enc.Form).BaseFriendship, + + _ => GetBaseFriendship(generation, enc.Species, enc.Form), + }; + + private static int GetBaseFriendship(int generation, int species, int form) => generation switch + { + 6 => PersonalTable.AO[species].BaseFriendship, + 7 => PersonalTable.USUM[species].BaseFriendship, + 8 => PersonalTable.SWSH.GetFormEntry(species, form).BaseFriendship, + _ => throw new ArgumentOutOfRangeException(nameof(generation)), + }; } diff --git a/PKHeX.Core/Legality/Verifiers/HyperTrainingVerifier.cs b/PKHeX.Core/Legality/Verifiers/HyperTrainingVerifier.cs index f88a3c3f8..2b322c34d 100644 --- a/PKHeX.Core/Legality/Verifiers/HyperTrainingVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/HyperTrainingVerifier.cs @@ -1,53 +1,52 @@ using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the values. +/// +public sealed class HyperTrainingVerifier : Verifier { - /// - /// Verifies the values. - /// - public sealed class HyperTrainingVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Training; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Training; + var pk = data.Entity; + if (pk is not IHyperTrain t) + return; // No Hyper Training before Gen7 - public override void Verify(LegalityAnalysis data) + if (!t.IsHyperTrained()) + return; + + if (!t.IsHyperTrainingAvailable(data.Info.EvoChainsAllGens)) { - var pkm = data.pkm; - if (pkm is not IHyperTrain t) - return; // No Hyper Training before Gen7 + data.AddLine(GetInvalid(LHyperPerfectUnavailable)); + return; + } - if (!t.IsHyperTrained()) - return; + if (pk.CurrentLevel != 100) + { + data.AddLine(GetInvalid(LHyperBelow100)); + return; + } - if (!t.IsHyperTrainingAvailable(data.Info.EvoChainsAllGens)) - { - data.AddLine(GetInvalid(LHyperPerfectUnavailable)); - return; - } + int max = pk.MaxIV; + if (pk.IVTotal == max * 6) + { + data.AddLine(GetInvalid(LHyperPerfectAll)); + return; + } - if (pkm.CurrentLevel != 100) - { - data.AddLine(GetInvalid(LHyperBelow100)); - return; - } + // LGPE gold bottle cap applies to all IVs regardless + if (pk.GG && t.IsHyperTrainedAll()) // already checked for 6IV, therefore we're flawed on at least one IV + return; - int max = pkm.MaxIV; - if (pkm.IVTotal == max * 6) - { - data.AddLine(GetInvalid(LHyperPerfectAll)); - return; - } - - // LGPE gold bottle cap applies to all IVs regardless - if (pkm.GG && t.IsHyperTrainedAll()) // already checked for 6IV, therefore we're flawed on at least one IV - return; - - for (int i = 0; i < 6; i++) // Check individual IVs - { - if (pkm.GetIV(i) != max || !t.IsHyperTrained(i)) - continue; - data.AddLine(GetInvalid(LHyperPerfectOne)); - break; - } + for (int i = 0; i < 6; i++) // Check individual IVs + { + if (pk.GetIV(i) != max || !t.IsHyperTrained(i)) + continue; + data.AddLine(GetInvalid(LHyperPerfectOne)); + break; } } } diff --git a/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs b/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs index e980ac75d..fe4276b10 100644 --- a/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/IndividualValueVerifier.cs @@ -1,122 +1,121 @@ using System; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the . +/// +public sealed class IndividualValueVerifier : Verifier { - /// - /// Verifies the . - /// - public sealed class IndividualValueVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.IVs; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.IVs; - - public override void Verify(LegalityAnalysis data) + switch (data.EncounterMatch) { - switch (data.EncounterMatch) - { - case EncounterStatic s: - VerifyIVsStatic(data, s); - break; - case EncounterSlot w: - VerifyIVsSlot(data, w); - break; - case MysteryGift g: - VerifyIVsMystery(data, g); - break; - } - - var pkm = data.pkm; - { - var hpiv = pkm.IV_HP; - if (hpiv < 30 && AllIVsEqual(pkm, hpiv)) - data.AddLine(Get(string.Format(LIVAllEqual_0, hpiv), Severity.Fishy)); - } + case EncounterStatic s: + VerifyIVsStatic(data, s); + break; + case EncounterSlot w: + VerifyIVsSlot(data, w); + break; + case MysteryGift g: + VerifyIVsMystery(data, g); + break; } - public static bool AllIVsEqual(PKM pkm) => AllIVsEqual(pkm, pkm.IV_HP); - - private static bool AllIVsEqual(PKM pkm, int hpiv) + var pk = data.Entity; { - return (pkm.IV_ATK == hpiv) && (pkm.IV_DEF == hpiv) && (pkm.IV_SPA == hpiv) && (pkm.IV_SPD == hpiv) && (pkm.IV_SPE == hpiv); - } - - private void VerifyIVsMystery(LegalityAnalysis data, MysteryGift g) - { - if (!g.HasFixedIVs) - return; // PID/IV style instead of fixed IVs. - - Span IVs = stackalloc int[6]; - g.GetIVs(IVs); - var ivflag = IVs.Find(iv => (byte)(iv - 0xFC) < 3); - if (ivflag == 0) // Random IVs - { - bool valid = Legal.GetIsFixedIVSequenceValidSkipRand(IVs, data.pkm); - if (!valid) - data.AddLine(GetInvalid(LEncGiftIVMismatch)); - } - else - { - int IVCount = ivflag - 0xFB; // IV2/IV3 - VerifyIVsFlawless(data, IVCount); - } - } - - private void VerifyIVsSlot(LegalityAnalysis data, EncounterSlot w) - { - switch (w.Generation) - { - case 6: VerifyIVsGen6(data, w); break; - case 7: VerifyIVsGen7(data); break; - case 8: VerifyIVsGen8(data); break; - } - } - - private void VerifyIVsGen7(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.GO) - VerifyIVsGoTransfer(data); - else if (pkm.AbilityNumber == 4 && !AbilityVerifier.CanAbilityPatch(pkm.Format, pkm.PersonalInfo.Abilities, pkm.Species)) - VerifyIVsFlawless(data, 2); // Chain of 10 yields 5% HA and 2 flawless IVs - } - - private void VerifyIVsGen8(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.GO) - VerifyIVsGoTransfer(data); - - if (data.EncounterMatch is EncounterSlot8a s) - VerifyIVsFlawless(data, s.FlawlessIVCount); - } - - private void VerifyIVsGen6(LegalityAnalysis data, EncounterSlot w) - { - if (w is EncounterSlot6XY xy) - { - if (PersonalTable.XY[data.EncounterMatch.Species].IsEggGroup(15)) // Undiscovered - VerifyIVsFlawless(data, 3); - if (xy.IsFriendSafari) - VerifyIVsFlawless(data, 2); - } - } - - private void VerifyIVsFlawless(LegalityAnalysis data, int count) - { - if (data.pkm.FlawlessIVCount < count) - data.AddLine(GetInvalid(string.Format(LIVF_COUNT0_31, count))); - } - - private void VerifyIVsStatic(LegalityAnalysis data, EncounterStatic s) - { - if (s.FlawlessIVCount != 0) - VerifyIVsFlawless(data, s.FlawlessIVCount); - } - - private void VerifyIVsGoTransfer(LegalityAnalysis data) - { - if (data.EncounterMatch is EncounterSlotGO g && !g.GetIVsValid(data.pkm)) - data.AddLine(GetInvalid(LIVNotCorrect)); + var hpiv = pk.IV_HP; + if (hpiv < 30 && AllIVsEqual(pk, hpiv)) + data.AddLine(Get(string.Format(LIVAllEqual_0, hpiv), Severity.Fishy)); } } + + public static bool AllIVsEqual(PKM pk) => AllIVsEqual(pk, pk.IV_HP); + + private static bool AllIVsEqual(PKM pk, int hpiv) + { + return (pk.IV_ATK == hpiv) && (pk.IV_DEF == hpiv) && (pk.IV_SPA == hpiv) && (pk.IV_SPD == hpiv) && (pk.IV_SPE == hpiv); + } + + private void VerifyIVsMystery(LegalityAnalysis data, MysteryGift g) + { + if (!g.HasFixedIVs) + return; // PID/IV style instead of fixed IVs. + + Span IVs = stackalloc int[6]; + g.GetIVs(IVs); + var ivflag = IVs.Find(iv => (byte)(iv - 0xFC) < 3); + if (ivflag == 0) // Random IVs + { + bool valid = Legal.GetIsFixedIVSequenceValidSkipRand(IVs, data.Entity); + if (!valid) + data.AddLine(GetInvalid(LEncGiftIVMismatch)); + } + else + { + int IVCount = ivflag - 0xFB; // IV2/IV3 + VerifyIVsFlawless(data, IVCount); + } + } + + private void VerifyIVsSlot(LegalityAnalysis data, EncounterSlot w) + { + switch (w.Generation) + { + case 6: VerifyIVsGen6(data, w); break; + case 7: VerifyIVsGen7(data); break; + case 8: VerifyIVsGen8(data); break; + } + } + + private void VerifyIVsGen7(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.GO) + VerifyIVsGoTransfer(data); + else if (pk.AbilityNumber == 4 && !AbilityVerifier.CanAbilityPatch(pk.Format, pk.PersonalInfo.Abilities, pk.Species)) + VerifyIVsFlawless(data, 2); // Chain of 10 yields 5% HA and 2 flawless IVs + } + + private void VerifyIVsGen8(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.GO) + VerifyIVsGoTransfer(data); + + if (data.EncounterMatch is EncounterSlot8a s) + VerifyIVsFlawless(data, s.FlawlessIVCount); + } + + private void VerifyIVsGen6(LegalityAnalysis data, EncounterSlot w) + { + if (w is EncounterSlot6XY xy) + { + if (PersonalTable.XY[data.EncounterMatch.Species].IsEggGroup(15)) // Undiscovered + VerifyIVsFlawless(data, 3); + if (xy.IsFriendSafari) + VerifyIVsFlawless(data, 2); + } + } + + private void VerifyIVsFlawless(LegalityAnalysis data, int count) + { + if (data.Entity.FlawlessIVCount < count) + data.AddLine(GetInvalid(string.Format(LIVF_COUNT0_31, count))); + } + + private void VerifyIVsStatic(LegalityAnalysis data, EncounterStatic s) + { + if (s.FlawlessIVCount != 0) + VerifyIVsFlawless(data, s.FlawlessIVCount); + } + + private void VerifyIVsGoTransfer(LegalityAnalysis data) + { + if (data.EncounterMatch is EncounterSlotGO g && !g.GetIVsValid(data.Entity)) + data.AddLine(GetInvalid(LIVNotCorrect)); + } } diff --git a/PKHeX.Core/Legality/Verifiers/ItemVerifier.cs b/PKHeX.Core/Legality/Verifiers/ItemVerifier.cs index e08dfd7ad..d1554af63 100644 --- a/PKHeX.Core/Legality/Verifiers/ItemVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/ItemVerifier.cs @@ -1,42 +1,41 @@ using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the . +/// +public sealed class ItemVerifier : Verifier { - /// - /// Verifies the . - /// - public sealed class ItemVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.HeldItem; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.HeldItem; + var pk = data.Entity; + if (!ItemRestrictions.IsHeldItemAllowed(pk)) + data.AddLine(GetInvalid(LItemUnreleased)); - public override void Verify(LegalityAnalysis data) - { - var pkm = data.pkm; - if (!ItemRestrictions.IsHeldItemAllowed(pkm)) - data.AddLine(GetInvalid(LItemUnreleased)); + if (pk.Format == 3 && pk.HeldItem == 175) // Enigma Berry + VerifyEReaderBerry(data); - if (pkm.Format == 3 && pkm.HeldItem == 175) // Enigma Berry - VerifyEReaderBerry(data); - - if (pkm.IsEgg && pkm.HeldItem != 0) - data.AddLine(GetInvalid(LItemEgg)); - } - - private void VerifyEReaderBerry(LegalityAnalysis data) - { - var status = EReaderBerrySettings.GetStatus(); - var chk = GetEReaderCheckResult(status); - if (chk != null) - data.AddLine(chk); - } - - private CheckResult? GetEReaderCheckResult(EReaderBerryMatch status) => status switch - { - EReaderBerryMatch.NoMatch => GetInvalid(LEReaderInvalid), - EReaderBerryMatch.NoData => GetInvalid(LItemUnreleased), - EReaderBerryMatch.InvalidUSA => GetInvalid(LEReaderAmerica), - EReaderBerryMatch.InvalidJPN => GetInvalid(LEReaderJapan), - _ => null, - }; + if (pk.IsEgg && pk.HeldItem != 0) + data.AddLine(GetInvalid(LItemEgg)); } + + private void VerifyEReaderBerry(LegalityAnalysis data) + { + var status = EReaderBerrySettings.GetStatus(); + var chk = GetEReaderCheckResult(status); + if (chk != null) + data.AddLine(chk); + } + + private CheckResult? GetEReaderCheckResult(EReaderBerryMatch status) => status switch + { + EReaderBerryMatch.NoMatch => GetInvalid(LEReaderInvalid), + EReaderBerryMatch.NoData => GetInvalid(LItemUnreleased), + EReaderBerryMatch.InvalidUSA => GetInvalid(LEReaderAmerica), + EReaderBerryMatch.InvalidJPN => GetInvalid(LEReaderJapan), + _ => null, + }; } diff --git a/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs b/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs index 2ca119128..61e7fd486 100644 --- a/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/LanguageVerifier.cs @@ -1,78 +1,76 @@ -using static PKHeX.Core.LegalityCheckStrings; +using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the . +/// +public sealed class LanguageVerifier : Verifier { - /// - /// Verifies the . - /// - public sealed class LanguageVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Language; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Language; - - public override void Verify(LegalityAnalysis data) + var pk = data.Entity; + int originalGeneration = data.Info.Generation; + int currentLanguage = pk.Language; + int maxLanguageID = Legal.GetMaxLanguageID(originalGeneration); + var enc = data.EncounterMatch; + if (!IsValidLanguageID(currentLanguage, maxLanguageID, pk, enc)) { - var pkm = data.pkm; - int originalGeneration = data.Info.Generation; - int currentLanguage = pkm.Language; - int maxLanguageID = Legal.GetMaxLanguageID(originalGeneration); - var enc = data.EncounterMatch; - if (!IsValidLanguageID(currentLanguage, maxLanguageID, pkm, enc)) - { - data.AddLine(GetInvalid(string.Format(LOTLanguage, $"<={(LanguageID)maxLanguageID}", (LanguageID)currentLanguage))); - return; - } - - // Korean Gen4 games can not trade with other Gen4 languages, but can use Pal Park with any Gen3 game/language. - if (pkm.Format == 4 && enc.Generation == 4 && !IsValidG4Korean(currentLanguage) - && enc is not EncounterTrade4PID {Species: (int)Species.Pikachu or (int)Species.Magikarp} // ger magikarp / eng pikachu - ) - { - bool kor = currentLanguage == (int)LanguageID.Korean; - var msgpkm = kor ? L_XKorean : L_XKoreanNon; - var msgsav = kor ? L_XKoreanNon : L_XKorean; - data.AddLine(GetInvalid(string.Format(LTransferOriginFInvalid0_1, msgpkm, msgsav))); - return; - } - - if (originalGeneration <= 2) - { - // Korean Crystal does not exist, neither do Korean VC1 - if (pkm.Korean && !GameVersion.GS.Contains((GameVersion)pkm.Version)) - data.AddLine(GetInvalid(string.Format(LOTLanguage, $"!={(LanguageID)currentLanguage}", (LanguageID)currentLanguage))); - - // Japanese VC is language locked; cannot obtain Japanese-Blue version as other languages. - if (pkm.Version == (int)GameVersion.BU && !pkm.Japanese) - data.AddLine(GetInvalid(string.Format(LOTLanguage, nameof(LanguageID.Japanese), (LanguageID)currentLanguage))); - } + data.AddLine(GetInvalid(string.Format(LOTLanguage, $"<={(LanguageID)maxLanguageID}", (LanguageID)currentLanguage))); + return; } - public static bool IsValidLanguageID(int currentLanguage, int maxLanguageID, PKM pkm, IEncounterTemplate enc) + // Korean Gen4 games can not trade with other Gen4 languages, but can use Pal Park with any Gen3 game/language. + if (pk.Format == 4 && enc.Generation == 4 && !IsValidG4Korean(currentLanguage) + && enc is not EncounterTrade4PID {Species: (int)Species.Pikachu or (int)Species.Magikarp} // ger magikarp / eng pikachu + ) { - if (currentLanguage == (int)LanguageID.UNUSED_6) - return false; // Language ID 6 is unused. - - if (currentLanguage > maxLanguageID) - return false; // Language not available (yet) - - if (currentLanguage <= (int)LanguageID.Hacked && !(enc is EncounterTrade5PID && EncounterTrade5PID.IsValidMissingLanguage(pkm))) - return false; // Missing Language value is not obtainable - - return true; // Language is possible + bool kor = currentLanguage == (int)LanguageID.Korean; + var msgpkm = kor ? L_XKorean : L_XKoreanNon; + var msgsav = kor ? L_XKoreanNon : L_XKorean; + data.AddLine(GetInvalid(string.Format(LTransferOriginFInvalid0_1, msgpkm, msgsav))); + return; } - /// - /// Check if the can exist in the Generation 4 savefile. - /// - /// - /// - public static bool IsValidG4Korean(int currentLanguage) + if (originalGeneration <= 2) { - bool savKOR = ParseSettings.ActiveTrainer.Language == (int) LanguageID.Korean; - bool pkmKOR = currentLanguage == (int) LanguageID.Korean; - if (savKOR == pkmKOR) - return true; + // Korean Crystal does not exist, neither do Korean VC1 + if (pk.Korean && !GameVersion.GS.Contains((GameVersion)pk.Version)) + data.AddLine(GetInvalid(string.Format(LOTLanguage, $"!={(LanguageID)currentLanguage}", (LanguageID)currentLanguage))); - return ParseSettings.ActiveTrainer.Language < 0; // check not overriden by Legality settings + // Japanese VC is language locked; cannot obtain Japanese-Blue version as other languages. + if (pk.Version == (int)GameVersion.BU && !pk.Japanese) + data.AddLine(GetInvalid(string.Format(LOTLanguage, nameof(LanguageID.Japanese), (LanguageID)currentLanguage))); } } + + public static bool IsValidLanguageID(int currentLanguage, int maxLanguageID, PKM pk, IEncounterTemplate enc) + { + if (currentLanguage == (int)LanguageID.UNUSED_6) + return false; // Language ID 6 is unused. + + if (currentLanguage > maxLanguageID) + return false; // Language not available (yet) + + if (currentLanguage <= (int)LanguageID.Hacked && !(enc is EncounterTrade5PID && EncounterTrade5PID.IsValidMissingLanguage(pk))) + return false; // Missing Language value is not obtainable + + return true; // Language is possible + } + + /// + /// Check if the can exist in the Generation 4 savefile. + /// + /// + public static bool IsValidG4Korean(int currentLanguage) + { + bool savKOR = ParseSettings.ActiveTrainer.Language == (int) LanguageID.Korean; + bool pkmKOR = currentLanguage == (int) LanguageID.Korean; + if (savKOR == pkmKOR) + return true; + + return ParseSettings.ActiveTrainer.Language < 0; // check not overriden by Legality settings + } } diff --git a/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs b/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs index e71b24521..dffb4cd17 100644 --- a/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/LegendsArceusVerifier.cs @@ -13,7 +13,7 @@ public sealed class LegendsArceusVerifier : Verifier public override void Verify(LegalityAnalysis data) { - if (data.pkm is not PA8 pa) + if (data.Entity is not PA8 pa) return; if (pa.IsNoble) diff --git a/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs b/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs index dc79d1895..cb817a940 100644 --- a/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/LevelVerifier.cs @@ -1,134 +1,133 @@ using System.Linq; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the . +/// +public sealed class LevelVerifier : Verifier { - /// - /// Verifies the . - /// - public sealed class LevelVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Level; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Level; - - public override void Verify(LegalityAnalysis data) + var pk = data.Entity; + var enc = data.EncounterOriginal; + if (enc is MysteryGift gift) { - var pkm = data.pkm; - var enc = data.EncounterOriginal; - if (enc is MysteryGift gift) + if (gift.Level != pk.Met_Level && pk.HasOriginalMetLocation) { - if (gift.Level != pkm.Met_Level && pkm.HasOriginalMetLocation) + switch (gift) { - switch (gift) - { - case WC3 wc3 when wc3.Met_Level == pkm.Met_Level || wc3.IsEgg: - break; - case WC7 wc7 when wc7.MetLevel == pkm.Met_Level: - break; - case PGT {IsManaphyEgg: true} when pkm.Met_Level == 0: - break; - default: - data.AddLine(GetInvalid(LLevelMetGift)); - return; - } - } - if (gift.Level > pkm.CurrentLevel) - { - data.AddLine(GetInvalid(LLevelMetGiftFail)); - return; + case WC3 wc3 when wc3.Met_Level == pk.Met_Level || wc3.IsEgg: + break; + case WC7 wc7 when wc7.MetLevel == pk.Met_Level: + break; + case PGT {IsManaphyEgg: true} when pk.Met_Level == 0: + break; + default: + data.AddLine(GetInvalid(LLevelMetGift)); + return; } } - - if (pkm.IsEgg) + if (gift.Level > pk.CurrentLevel) { - int elvl = enc.LevelMin; - if (elvl != pkm.CurrentLevel) - { - data.AddLine(GetInvalid(string.Format(LEggFMetLevel_0, elvl))); - return; - } + data.AddLine(GetInvalid(LLevelMetGiftFail)); + return; + } + } - var reqEXP = enc is EncounterStatic2Odd - ? 125 // Gen2 Dizzy Punch gifts always have 125 EXP, even if it's more than the Lv5 exp required. - : Experience.GetEXP(elvl, pkm.PersonalInfo.EXPGrowth); - if (reqEXP != pkm.EXP) - data.AddLine(GetInvalid(LEggEXP)); + if (pk.IsEgg) + { + int elvl = enc.LevelMin; + if (elvl != pk.CurrentLevel) + { + data.AddLine(GetInvalid(string.Format(LEggFMetLevel_0, elvl))); return; } - int lvl = pkm.CurrentLevel; - if (lvl >= 100) - { - var expect = Experience.GetEXP(100, pkm.PersonalInfo.EXPGrowth); - if (pkm.EXP != expect) - data.AddLine(GetInvalid(LLevelEXPTooHigh)); - } + var reqEXP = enc is EncounterStatic2Odd + ? 125 // Gen2 Dizzy Punch gifts always have 125 EXP, even if it's more than the Lv5 exp required. + : Experience.GetEXP(elvl, pk.PersonalInfo.EXPGrowth); + if (reqEXP != pk.EXP) + data.AddLine(GetInvalid(LEggEXP)); + return; + } - if (lvl < pkm.Met_Level) + int lvl = pk.CurrentLevel; + if (lvl >= 100) + { + var expect = Experience.GetEXP(100, pk.PersonalInfo.EXPGrowth); + if (pk.EXP != expect) + data.AddLine(GetInvalid(LLevelEXPTooHigh)); + } + + if (lvl < pk.Met_Level) + data.AddLine(GetInvalid(LLevelMetBelow)); + else if (!enc.IsWithinEncounterRange(pk) && lvl != 100 && pk.EXP == Experience.GetEXP(lvl, pk.PersonalInfo.EXPGrowth)) + data.AddLine(Get(LLevelEXPThreshold, Severity.Fishy)); + else + data.AddLine(GetValid(LLevelMetSane)); + } + + public void VerifyG1(LegalityAnalysis data) + { + var pk = data.Entity; + var enc = data.EncounterMatch; + if (pk.IsEgg) + { + const int elvl = 5; + if (elvl != pk.CurrentLevel) + data.AddLine(GetInvalid(string.Format(LEggFMetLevel_0, elvl))); + return; + } + if (pk.Met_Location != 0) // crystal + { + int lvl = pk.CurrentLevel; + if (lvl < pk.Met_Level) data.AddLine(GetInvalid(LLevelMetBelow)); - else if (!enc.IsWithinEncounterRange(pkm) && lvl != 100 && pkm.EXP == Experience.GetEXP(lvl, pkm.PersonalInfo.EXPGrowth)) - data.AddLine(Get(LLevelEXPThreshold, Severity.Fishy)); - else - data.AddLine(GetValid(LLevelMetSane)); } - public void VerifyG1(LegalityAnalysis data) + if (IsTradeEvolutionRequired(data, enc)) { - var pkm = data.pkm; - var enc = data.EncounterMatch; - if (pkm.IsEgg) - { - const int elvl = 5; - if (elvl != pkm.CurrentLevel) - data.AddLine(GetInvalid(string.Format(LEggFMetLevel_0, elvl))); - return; - } - if (pkm.Met_Location != 0) // crystal - { - int lvl = pkm.CurrentLevel; - if (lvl < pkm.Met_Level) - data.AddLine(GetInvalid(LLevelMetBelow)); - } - - if (IsTradeEvolutionRequired(data, enc)) - { - // Pokemon has been traded illegally between games without evolving. - // Trade evolution species IDs for Gen1 are sequential dex numbers. - var species = enc.Species; - var evolved = ParseSettings.SpeciesStrings[species + 1]; - var unevolved = ParseSettings.SpeciesStrings[species]; - data.AddLine(GetInvalid(string.Format(LEvoTradeReqOutsider, unevolved, evolved))); - } - } - - /// - /// Checks if a Gen1 trade evolution must have occurred. - /// - private static bool IsTradeEvolutionRequired(LegalityAnalysis data, IEncounterTemplate enc) - { - // There is no way to prevent a Gen1 trade evolution, as held items (Everstone) did not exist. - // Machoke, Graveler, Haunter and Kadabra captured in the second phase evolution, excluding in-game trades, are already checked - var pkm = data.pkm; - var species = pkm.Species; - - // This check is only applicable if it's a trade evolution that has not been evolved. - if (!GBRestrictions.Trade_Evolution1.Contains(enc.Species) || enc.Species != species) - return false; - - // Context check is only applicable to gen1/2; transferring to Gen2 is a trade. - // Stadium 2 can transfer across game/generation boundaries without initiating a trade. - // Ignore this check if the environment's loaded trainer is not from Gen1/2 or is from GB Era. - if (ParseSettings.ActiveTrainer.Generation >= 3 || ParseSettings.AllowGBCartEra) - return false; - - // Gen2 stuff can be traded between Gen2 games holding an Everstone, assuming it hasn't been transferred to Gen1 for special moves. - if (enc.Generation == 2) - return data.Info.Moves.Any(z => z.Generation != 2); - // Gen1 stuff can only be un-evolved if it was never traded from the OT. - if (data.Info.Moves.Any(z => z.Generation != 1)) - return true; // traded to Gen2 for special moves - if (pkm.Format != 1) - return true; // traded to Gen2 (current state) - return !ParseSettings.IsFromActiveTrainer(pkm); // not with OT + // Pokemon has been traded illegally between games without evolving. + // Trade evolution species IDs for Gen1 are sequential dex numbers. + var species = enc.Species; + var evolved = ParseSettings.SpeciesStrings[species + 1]; + var unevolved = ParseSettings.SpeciesStrings[species]; + data.AddLine(GetInvalid(string.Format(LEvoTradeReqOutsider, unevolved, evolved))); } } + + /// + /// Checks if a Gen1 trade evolution must have occurred. + /// + private static bool IsTradeEvolutionRequired(LegalityAnalysis data, IEncounterTemplate enc) + { + // There is no way to prevent a Gen1 trade evolution, as held items (Everstone) did not exist. + // Machoke, Graveler, Haunter and Kadabra captured in the second phase evolution, excluding in-game trades, are already checked + var pk = data.Entity; + var species = pk.Species; + + // This check is only applicable if it's a trade evolution that has not been evolved. + if (!GBRestrictions.Trade_Evolution1.Contains(enc.Species) || enc.Species != species) + return false; + + // Context check is only applicable to gen1/2; transferring to Gen2 is a trade. + // Stadium 2 can transfer across game/generation boundaries without initiating a trade. + // Ignore this check if the environment's loaded trainer is not from Gen1/2 or is from GB Era. + if (ParseSettings.ActiveTrainer.Generation >= 3 || ParseSettings.AllowGBCartEra) + return false; + + // Gen2 stuff can be traded between Gen2 games holding an Everstone, assuming it hasn't been transferred to Gen1 for special moves. + if (enc.Generation == 2) + return data.Info.Moves.Any(z => z.Generation != 2); + // Gen1 stuff can only be un-evolved if it was never traded from the OT. + if (data.Info.Moves.Any(z => z.Generation != 1)) + return true; // traded to Gen2 for special moves + if (pk.Format != 1) + return true; // traded to Gen2 (current state) + return !ParseSettings.IsFromActiveTrainer(pk); // not with OT + } } diff --git a/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs b/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs index 244addec9..fea140e95 100644 --- a/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MarkVerifier.cs @@ -1,214 +1,213 @@ using System.Linq; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the values for markings. +/// +public sealed class MarkVerifier : Verifier { - /// - /// Verifies the values for markings. - /// - public sealed class MarkVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.RibbonMark; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.RibbonMark; + var pk = data.Entity; + if (pk is not IRibbonIndex m) + return; - public override void Verify(LegalityAnalysis data) + if (data.Info.Generation != 8 || (pk.Species == (int)Species.Shedinja && data.EncounterOriginal.Species is not (int)Species.Shedinja)) // Shedinja doesn't copy Ribbons or Marks + VerifyNoMarksPresent(data, m); + else + VerifyMarksPresent(data, m); + + VerifyAffixedRibbonMark(data, m); + } + + private void VerifyNoMarksPresent(LegalityAnalysis data, IRibbonIndex m) + { + for (var x = RibbonIndex.MarkLunchtime; x <= RibbonIndex.MarkSlump; x++) { - var pkm = data.pkm; - if (pkm is not IRibbonIndex m) - return; - - if (data.Info.Generation != 8 || (pkm.Species == (int)Species.Shedinja && data.EncounterOriginal.Species is not (int)Species.Shedinja)) // Shedinja doesn't copy Ribbons or Marks - VerifyNoMarksPresent(data, m); - else - VerifyMarksPresent(data, m); - - VerifyAffixedRibbonMark(data, m); - } - - private void VerifyNoMarksPresent(LegalityAnalysis data, IRibbonIndex m) - { - for (var x = RibbonIndex.MarkLunchtime; x <= RibbonIndex.MarkSlump; x++) - { - if (m.GetRibbon((int)x)) - data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, x))); - } - } - - private void VerifyMarksPresent(LegalityAnalysis data, IRibbonIndex m) - { - bool hasOne = false; - for (var mark = RibbonIndex.MarkLunchtime; mark <= RibbonIndex.MarkSlump; mark++) - { - bool has = m.GetRibbon((int)mark); - if (!has) - continue; - - if (hasOne) - { - data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark)))); - return; - } - - bool result = IsMarkValid(mark, data.pkm, data.EncounterMatch); - if (!result) - { - data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark)))); - return; - } - - hasOne = true; - } - } - - private static string GetRibbonNameSafe(RibbonIndex index) - { - if (index >= RibbonIndex.MAX_COUNT) - return index.ToString(); - var expect = $"Ribbon{index}"; - return RibbonStrings.GetName(expect); - } - - public static bool IsMarkValid(RibbonIndex mark, PKM pk, IEncounterTemplate enc) - { - return IsMarkAllowedAny(enc) && IsMarkAllowedSpecific(mark, pk, enc); - } - - public static bool IsMarkAllowedSpecific(RibbonIndex mark, PKM pk, IEncounterTemplate x) => mark switch - { - RibbonIndex.MarkCurry when !IsMarkAllowedCurry(pk, x) => false, - RibbonIndex.MarkFishing when !IsMarkAllowedFishing(x) => false, - RibbonIndex.MarkMisty when pk.Met_Level < EncounterArea8.BoostLevel && EncounterArea8.IsBoostedArea60Fog(pk.Met_Location) => false, - RibbonIndex.MarkDestiny => false, - >= RibbonIndex.MarkCloudy and <= RibbonIndex.MarkMisty => IsWeatherPermitted(mark, x), - _ => true, - }; - - private static bool IsWeatherPermitted(RibbonIndex mark, IEncounterTemplate enc) - { - var permit = mark.GetWeather8(); - - // Encounter slots check location weather, while static encounters check weather per encounter. - return enc switch - { - EncounterSlot8 w => IsSlotWeatherPermitted(permit, w), - EncounterStatic8 s => s.Weather.HasFlag(permit), - _ => false, - }; - } - - private static bool IsSlotWeatherPermitted(AreaWeather8 permit, EncounterSlot8 s) - { - var location = s.Location; - // If it's not in the main table, it can only have Normal weather. - if (!EncounterArea8.WeatherbyArea.TryGetValue(location, out var weather)) - weather = AreaWeather8.Normal; - if (weather.HasFlag(permit)) - return true; - - // Valid tree/fishing weathers should have returned with main area weather. - if ((s.Weather & (AreaWeather8.Shaking_Trees | AreaWeather8.Fishing)) != 0) - return false; - - // Check bleed conditions otherwise. - return EncounterArea8.IsWeatherBleedPossible(s.SlotType, permit, location); - } - - public static bool IsMarkAllowedAny(IEncounterTemplate enc) => enc.Generation == 8 && enc switch - { - // Gen 8 - EncounterSlot8 or EncounterStatic8 {Gift: false, ScriptedNoMarks: false} => true, - _ => false, - }; - - public static bool IsMarkAllowedCurry(PKM pkm, IEncounterTemplate enc) - { - // Curry are only encounter slots, from the hidden table (not symbol). Slots taken from area's current weather(?). - if (enc is not EncounterSlot8 {CanEncounterViaCurry: true}) - return false; - - var ball = pkm.Ball; - return (uint)(ball - 2) <= 2; - } - - public static bool IsMarkAllowedFishing(IEncounterTemplate enc) - { - return enc is EncounterSlot8 {CanEncounterViaFishing: true}; - } - - private void VerifyAffixedRibbonMark(LegalityAnalysis data, IRibbonIndex m) - { - if (m is not IRibbonSetAffixed a) - return; - - var affix = a.AffixedRibbon; - if (affix == -1) // None - return; - - if ((byte)affix > (int)RibbonIndex.MarkSlump) // SW/SH cannot affix anything higher. - { - data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex)affix)))); - return; - } - - if (m is not PKM pk) - return; - - if (pk.Species == (int)Species.Shedinja && data.EncounterOriginal.Species is not (int)Species.Shedinja) - { - VerifyShedinjaAffixed(data, affix, pk, m); - return; - } - EnsureHasRibbon(data, m, affix); - } - - private void VerifyShedinjaAffixed(LegalityAnalysis data, sbyte affix, PKM pk, IRibbonIndex r) - { - // Does not copy ribbons or marks, but retains the Affixed Ribbon value. - // Try re-verifying to see if it could have had the Ribbon/Mark. - - var enc = data.EncounterOriginal; - if ((byte) affix >= (int) RibbonIndex.MarkLunchtime) - { - if (!IsMarkValid((RibbonIndex)affix, pk, enc)) - data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex)affix)))); - return; - } - - if (enc.Generation <= 4 && (pk.Ball != (int)Ball.Poke || IsMoveSetEvolvedShedinja(pk))) - { - // Evolved in a prior generation. - EnsureHasRibbon(data, r, affix); - return; - } - - var clone = pk.Clone(); - clone.Species = (int) Species.Nincada; - ((IRibbonIndex) clone).SetRibbon(affix); - var parse = RibbonVerifier.GetRibbonResults(clone, data.Info.EvoChainsAllGens, enc); - var name = GetRibbonNameSafe((RibbonIndex)affix); - bool invalid = parse.FirstOrDefault(z => z.Name == name)?.Invalid == true; - var severity = invalid ? Severity.Invalid : Severity.Fishy; - data.AddLine(Get(string.Format(LRibbonMarkingAffixedF_0, name), severity)); - } - - private static bool IsMoveSetEvolvedShedinja(PKM pk) - { - // Check for gen3/4 exclusive moves that are Ninjask glitch only. - if (pk.HasMove((int) Move.Screech)) - return true; - if (pk.HasMove((int) Move.SwordsDance)) - return true; - if (pk.HasMove((int) Move.Slash)) - return true; - if (pk.HasMove((int) Move.BatonPass)) - return true; - return pk.HasMove((int)Move.Agility) && pk is PK8 pk8 && !pk8.GetMoveRecordFlag(12); // TR12 (Agility) - } - - private void EnsureHasRibbon(LegalityAnalysis data, IRibbonIndex m, sbyte affix) - { - var hasRibbon = m.GetRibbonIndex((RibbonIndex) affix); - if (!hasRibbon) - data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex) affix)))); + if (m.GetRibbon((int)x)) + data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, x))); } } + + private void VerifyMarksPresent(LegalityAnalysis data, IRibbonIndex m) + { + bool hasOne = false; + for (var mark = RibbonIndex.MarkLunchtime; mark <= RibbonIndex.MarkSlump; mark++) + { + bool has = m.GetRibbon((int)mark); + if (!has) + continue; + + if (hasOne) + { + data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark)))); + return; + } + + bool result = IsMarkValid(mark, data.Entity, data.EncounterMatch); + if (!result) + { + data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark)))); + return; + } + + hasOne = true; + } + } + + private static string GetRibbonNameSafe(RibbonIndex index) + { + if (index >= RibbonIndex.MAX_COUNT) + return index.ToString(); + var expect = $"Ribbon{index}"; + return RibbonStrings.GetName(expect); + } + + public static bool IsMarkValid(RibbonIndex mark, PKM pk, IEncounterTemplate enc) + { + return IsMarkAllowedAny(enc) && IsMarkAllowedSpecific(mark, pk, enc); + } + + public static bool IsMarkAllowedSpecific(RibbonIndex mark, PKM pk, IEncounterTemplate x) => mark switch + { + RibbonIndex.MarkCurry when !IsMarkAllowedCurry(pk, x) => false, + RibbonIndex.MarkFishing when !IsMarkAllowedFishing(x) => false, + RibbonIndex.MarkMisty when pk.Met_Level < EncounterArea8.BoostLevel && EncounterArea8.IsBoostedArea60Fog(pk.Met_Location) => false, + RibbonIndex.MarkDestiny => false, + >= RibbonIndex.MarkCloudy and <= RibbonIndex.MarkMisty => IsWeatherPermitted(mark, x), + _ => true, + }; + + private static bool IsWeatherPermitted(RibbonIndex mark, IEncounterTemplate enc) + { + var permit = mark.GetWeather8(); + + // Encounter slots check location weather, while static encounters check weather per encounter. + return enc switch + { + EncounterSlot8 w => IsSlotWeatherPermitted(permit, w), + EncounterStatic8 s => s.Weather.HasFlag(permit), + _ => false, + }; + } + + private static bool IsSlotWeatherPermitted(AreaWeather8 permit, EncounterSlot8 s) + { + var location = s.Location; + // If it's not in the main table, it can only have Normal weather. + if (!EncounterArea8.WeatherbyArea.TryGetValue(location, out var weather)) + weather = AreaWeather8.Normal; + if (weather.HasFlag(permit)) + return true; + + // Valid tree/fishing weathers should have returned with main area weather. + if ((s.Weather & (AreaWeather8.Shaking_Trees | AreaWeather8.Fishing)) != 0) + return false; + + // Check bleed conditions otherwise. + return EncounterArea8.IsWeatherBleedPossible(s.SlotType, permit, location); + } + + public static bool IsMarkAllowedAny(IEncounterTemplate enc) => enc.Generation == 8 && enc switch + { + // Gen 8 + EncounterSlot8 or EncounterStatic8 {Gift: false, ScriptedNoMarks: false} => true, + _ => false, + }; + + public static bool IsMarkAllowedCurry(PKM pk, IEncounterTemplate enc) + { + // Curry are only encounter slots, from the hidden table (not symbol). Slots taken from area's current weather(?). + if (enc is not EncounterSlot8 {CanEncounterViaCurry: true}) + return false; + + var ball = pk.Ball; + return (uint)(ball - 2) <= 2; + } + + public static bool IsMarkAllowedFishing(IEncounterTemplate enc) + { + return enc is EncounterSlot8 {CanEncounterViaFishing: true}; + } + + private void VerifyAffixedRibbonMark(LegalityAnalysis data, IRibbonIndex m) + { + if (m is not IRibbonSetAffixed a) + return; + + var affix = a.AffixedRibbon; + if (affix == -1) // None + return; + + if ((byte)affix > (int)RibbonIndex.MarkSlump) // SW/SH cannot affix anything higher. + { + data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex)affix)))); + return; + } + + if (m is not PKM pk) + return; + + if (pk.Species == (int)Species.Shedinja && data.EncounterOriginal.Species is not (int)Species.Shedinja) + { + VerifyShedinjaAffixed(data, affix, pk, m); + return; + } + EnsureHasRibbon(data, m, affix); + } + + private void VerifyShedinjaAffixed(LegalityAnalysis data, sbyte affix, PKM pk, IRibbonIndex r) + { + // Does not copy ribbons or marks, but retains the Affixed Ribbon value. + // Try re-verifying to see if it could have had the Ribbon/Mark. + + var enc = data.EncounterOriginal; + if ((byte) affix >= (int) RibbonIndex.MarkLunchtime) + { + if (!IsMarkValid((RibbonIndex)affix, pk, enc)) + data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex)affix)))); + return; + } + + if (enc.Generation <= 4 && (pk.Ball != (int)Ball.Poke || IsMoveSetEvolvedShedinja(pk))) + { + // Evolved in a prior generation. + EnsureHasRibbon(data, r, affix); + return; + } + + var clone = pk.Clone(); + clone.Species = (int) Species.Nincada; + ((IRibbonIndex) clone).SetRibbon(affix); + var parse = RibbonVerifier.GetRibbonResults(clone, data.Info.EvoChainsAllGens, enc); + var name = GetRibbonNameSafe((RibbonIndex)affix); + bool invalid = parse.FirstOrDefault(z => z.Name == name)?.Invalid == true; + var severity = invalid ? Severity.Invalid : Severity.Fishy; + data.AddLine(Get(string.Format(LRibbonMarkingAffixedF_0, name), severity)); + } + + private static bool IsMoveSetEvolvedShedinja(PKM pk) + { + // Check for gen3/4 exclusive moves that are Ninjask glitch only. + if (pk.HasMove((int) Move.Screech)) + return true; + if (pk.HasMove((int) Move.SwordsDance)) + return true; + if (pk.HasMove((int) Move.Slash)) + return true; + if (pk.HasMove((int) Move.BatonPass)) + return true; + return pk.HasMove((int)Move.Agility) && pk is PK8 pk8 && !pk8.GetMoveRecordFlag(12); // TR12 (Agility) + } + + private void EnsureHasRibbon(LegalityAnalysis data, IRibbonIndex m, sbyte affix) + { + var hasRibbon = m.GetRibbonIndex((RibbonIndex) affix); + if (!hasRibbon) + data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex) affix)))); + } } diff --git a/PKHeX.Core/Legality/Verifiers/MarkingVerifier.cs b/PKHeX.Core/Legality/Verifiers/MarkingVerifier.cs index c42bcb456..9db70c177 100644 --- a/PKHeX.Core/Legality/Verifiers/MarkingVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MarkingVerifier.cs @@ -12,40 +12,40 @@ public sealed class MarkingVerifier : Verifier public override void Verify(LegalityAnalysis data) { - var pkm = data.pkm; - VerifyFavoriteMark(data, pkm); - VerifyMarkValue(data, pkm); + var pk = data.Entity; + VerifyFavoriteMark(data, pk); + VerifyMarkValue(data, pk); } - private void VerifyFavoriteMark(LegalityAnalysis data, PKM pkm) + private void VerifyFavoriteMark(LegalityAnalysis data, PKM pk) { // Can only be toggled on in LGP/E, and is retained via transfer to HOME and into other games. - if (pkm is IFavorite { Favorite: true } && !pkm.GG) + if (pk is IFavorite { Favorite: true } && !pk.GG) data.AddLine(GetInvalid(LFavoriteMarkingUnavailable)); } - private void VerifyMarkValue(LegalityAnalysis data, PKM pkm) + private void VerifyMarkValue(LegalityAnalysis data, PKM pk) { - var mv = pkm.MarkValue; + var mv = pk.MarkValue; if (mv == 0) return; // Eggs can have markings applied. - //if (pkm.IsEgg) + //if (pk.IsEgg) //{ // data.AddLine(GetInvalid(LMarkValueShouldBeZero)); // return; //} - switch (pkm.Format) + switch (pk.Format) { case <= 2: return; case <= 6: - VerifyMarkValueSingle(data, pkm, mv); + VerifyMarkValueSingle(data, pk, mv); return; default: - VerifyMarkValueDual(data, pkm, mv); + VerifyMarkValueDual(data, pk, mv); return; } } @@ -54,29 +54,29 @@ private void VerifyMarkValue(LegalityAnalysis data, PKM pkm) private const int Single6 = 0b_111111; private const int Dual6 = 0b_1111_1111_1111; - private void VerifyMarkValueDual(LegalityAnalysis data, PKM pkm, int mv) + private void VerifyMarkValueDual(LegalityAnalysis data, PKM pk, int mv) { if (mv > Dual6) data.AddLine(GetInvalid(LMarkValueUnusedBitsPresent)); - var count = pkm.MarkingCount; + var count = pk.MarkingCount; for (int i = 0; i < count; i++) { - var value = pkm.GetMarking(i); + var value = pk.GetMarking(i); if (value is not (0 or 1 or 2)) data.AddLine(GetInvalid(string.Format(LMarkValueOutOfRange_0, i))); } } - private void VerifyMarkValueSingle(LegalityAnalysis data, PKM pkm, int mv) + private void VerifyMarkValueSingle(LegalityAnalysis data, PKM pk, int mv) { - if (!IsMarkValueValid3456(pkm, mv)) + if (!IsMarkValueValid3456(pk, mv)) data.AddLine(GetInvalid(LMarkValueUnusedBitsPresent)); } - private static bool IsMarkValueValid3456(PKM pkm, int value) + private static bool IsMarkValueValid3456(PKM pk, int value) { - var max = pkm.Format is 3 ? Single4 : Single6; + var max = pk.Format is 3 ? Single4 : Single6; return value <= max; } } diff --git a/PKHeX.Core/Legality/Verifiers/MedalVerifier.cs b/PKHeX.Core/Legality/Verifiers/MedalVerifier.cs index b7fa6d78d..ef24786b1 100644 --- a/PKHeX.Core/Legality/Verifiers/MedalVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MedalVerifier.cs @@ -1,76 +1,75 @@ using System; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the and associated values. +/// +public sealed class MedalVerifier : Verifier { - /// - /// Verifies the and associated values. - /// - public sealed class MedalVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Training; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Training; + VerifyMedalsRegular(data); + VerifyMedalsEvent(data); + } - public override void Verify(LegalityAnalysis data) + private void VerifyMedalsRegular(LegalityAnalysis data) + { + var pk = data.Entity; + var train = (ISuperTrain)pk; + var Info = data.Info; + uint value = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(data.Entity.Data.AsSpan(0x2C)); + if ((value & 3) != 0) // 2 unused flags + data.AddLine(GetInvalid(LSuperUnused)); + int TrainCount = train.SuperTrainingMedalCount(); + + if (pk.IsEgg) { - VerifyMedalsRegular(data); - VerifyMedalsEvent(data); + // Can't have any super training data as an egg. + if (TrainCount > 0) + data.AddLine(GetInvalid(LSuperEgg)); + if (train.SecretSuperTrainingUnlocked) + data.AddLine(GetInvalid(LSuperNoUnlocked)); + if (train.SecretSuperTrainingComplete) + data.AddLine(GetInvalid(LSuperNoComplete)); + return; } - private void VerifyMedalsRegular(LegalityAnalysis data) + if (Info.Generation is >= 7 or <= 2) { - var pkm = data.pkm; - var train = (ISuperTrain)pkm; - var Info = data.Info; - uint value = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(data.pkm.Data.AsSpan(0x2C)); - if ((value & 3) != 0) // 2 unused flags - data.AddLine(GetInvalid(LSuperUnused)); - int TrainCount = train.SuperTrainingMedalCount(); - - if (pkm.IsEgg) - { - // Can't have any super training data as an egg. - if (TrainCount > 0) - data.AddLine(GetInvalid(LSuperEgg)); - if (train.SecretSuperTrainingUnlocked) - data.AddLine(GetInvalid(LSuperNoUnlocked)); - if (train.SecretSuperTrainingComplete) - data.AddLine(GetInvalid(LSuperNoComplete)); - return; - } - - if (Info.Generation is >= 7 or <= 2) - { - // Can't have any super training data if it never visited Gen6. - if (TrainCount > 0) - data.AddLine(GetInvalid(LSuperUnavailable)); - if (train.SecretSuperTrainingUnlocked) - data.AddLine(GetInvalid(LSuperNoUnlocked)); - if (train.SecretSuperTrainingComplete) - data.AddLine(GetInvalid(LSuperNoComplete)); - return; - } - - if (pkm.Format >= 7) - { - // Gen6->Gen7 transfer wipes the two Secret flags. - if (train.SecretSuperTrainingUnlocked) - data.AddLine(GetInvalid(LSuperNoUnlocked)); - if (train.SecretSuperTrainingComplete) - data.AddLine(GetInvalid(LSuperNoComplete)); - return; - } - - // Only reach here if Format==6. - if (TrainCount == 30 ^ train.SecretSuperTrainingComplete) - data.AddLine(GetInvalid(LSuperComplete)); + // Can't have any super training data if it never visited Gen6. + if (TrainCount > 0) + data.AddLine(GetInvalid(LSuperUnavailable)); + if (train.SecretSuperTrainingUnlocked) + data.AddLine(GetInvalid(LSuperNoUnlocked)); + if (train.SecretSuperTrainingComplete) + data.AddLine(GetInvalid(LSuperNoComplete)); + return; } - private void VerifyMedalsEvent(LegalityAnalysis data) + if (pk.Format >= 7) { - var pkm = data.pkm; - byte value = pkm.Data[0x3A]; - if (value != 0) - data.AddLine(GetInvalid(LSuperDistro)); + // Gen6->Gen7 transfer wipes the two Secret flags. + if (train.SecretSuperTrainingUnlocked) + data.AddLine(GetInvalid(LSuperNoUnlocked)); + if (train.SecretSuperTrainingComplete) + data.AddLine(GetInvalid(LSuperNoComplete)); + return; } + + // Only reach here if Format==6. + if (TrainCount == 30 ^ train.SecretSuperTrainingComplete) + data.AddLine(GetInvalid(LSuperComplete)); + } + + private void VerifyMedalsEvent(LegalityAnalysis data) + { + var pk = data.Entity; + byte value = pk.Data[0x3A]; + if (value != 0) + data.AddLine(GetInvalid(LSuperDistro)); } } diff --git a/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs b/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs index ee4e8c945..bad0f2ebf 100644 --- a/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MemoryVerifier.cs @@ -3,383 +3,382 @@ using static PKHeX.Core.LegalityCheckStrings; using static PKHeX.Core.MemoryPermissions; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the , , and associated values. +/// +public sealed class MemoryVerifier : Verifier { - /// - /// Verifies the , , and associated values. - /// - public sealed class MemoryVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Memory; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Memory; - - public override void Verify(LegalityAnalysis data) + var pk = data.Entity; + if (ShouldHaveNoMemory(data, pk)) { - var pkm = data.pkm; - if (ShouldHaveNoMemory(data, pkm)) - { - VerifyOTMemoryIs(data, 0, 0, 0, 0); - VerifyHTMemoryNone(data, (ITrainerMemories)pkm); - return; - } - VerifyOTMemory(data); - VerifyHTMemory(data); + VerifyOTMemoryIs(data, 0, 0, 0, 0); + VerifyHTMemoryNone(data, (ITrainerMemories)pk); + return; } + VerifyOTMemory(data); + VerifyHTMemory(data); + } - private static bool ShouldHaveNoMemory(LegalityAnalysis data, PKM pkm) + private static bool ShouldHaveNoMemory(LegalityAnalysis data, PKM pk) + { + if (pk.BDSP || pk.LA) + return !pk.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8); + return false; + } + + private CheckResult VerifyCommonMemory(PKM pk, int handler, int gen, LegalInfo info, MemoryContext context) + { + var memory = MemoryVariableSet.Read((ITrainerMemories)pk, handler); + + // Actionable HM moves + int hmIndex = Array.IndexOf(MemoryContext6.MoveSpecificMemoryHM, memory.MemoryID); + if (hmIndex != -1) { - if (pkm.BDSP || pkm.LA) - return !pkm.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8); - return false; - } + if (gen != 6) // Gen8 has no HMs, so this memory can never exist. + return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); - private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo info, MemoryContext context) - { - var memory = MemoryVariableSet.Read((ITrainerMemories)pkm, handler); - - // Actionable HM moves - int hmIndex = Array.IndexOf(MemoryContext6.MoveSpecificMemoryHM, memory.MemoryID); - if (hmIndex != -1) + if (pk.Species != (int)Species.Smeargle) { - if (gen != 6) // Gen8 has no HMs, so this memory can never exist. + // All AO hidden machine permissions are super-sets of Gen 3-5 games. + // Don't need to check the move history -- a learned HM in a prior game can still be learned in Gen6. + var evos = info.EvoChainsAllGens.Gen6; + var indexLearn = Array.FindIndex(evos, z => PersonalTable.AO.GetFormEntry(z.Species, 0).TMHM[100 + hmIndex]); + if (indexLearn == -1) return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); - - if (pkm.Species != (int)Species.Smeargle) - { - // All AO hidden machine permissions are super-sets of Gen 3-5 games. - // Don't need to check the move history -- a learned HM in a prior game can still be learned in Gen6. - var evos = info.EvoChainsAllGens.Gen6; - var indexLearn = Array.FindIndex(evos, z => PersonalTable.AO.GetFormEntry(z.Species, 0).TMHM[100 + hmIndex]); - if (indexLearn == -1) - return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); - } } + } - if (context.IsInvalidGeneralLocationMemoryValue(memory.MemoryID, memory.Variable, info.EncounterMatch, pkm)) + if (context.IsInvalidGeneralLocationMemoryValue(memory.MemoryID, memory.Variable, info.EncounterMatch, pk)) + return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler)); + + if (context.IsInvalidMiscMemory(memory.MemoryID, memory.Variable)) + return GetInvalid(string.Format(LMemoryArgBadID, memory.Handler)); + + switch (memory.MemoryID) + { + case 19 when pk.Species is (int)Species.Urshifu && memory.Variable is not 34: // tall building is the only location for evolving Urshifu + case 19 when pk.Species is (int)Species.Runerigus && memory.Variable is not 72: // vast field is the only location for evolving Runerigus return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler)); - if (context.IsInvalidMiscMemory(memory.MemoryID, memory.Variable)) + // {0} saw {2} carrying {1} on its back. {4} that {3}. + case 21 when gen != 6 || !PersonalTable.AO.GetFormEntry(memory.Variable, 0).TMHM[101]: // Fly + return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); + + // {0} used {2} at {1}’s instruction, but it had no effect. {4} that {3}. + // The Move Deleter that {0} met through {1} made it forget {2}. {4} that {3}. + case 16 or 48 when !CanKnowMove(pk, memory, gen, info, memory.MemoryID == 16): + return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); + + case 49 when memory.Variable == 0 || !GetCanRelearnMove(pk, memory.Variable, gen, info.EvoChainsAllGens[gen]): + return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); + + // Dynamaxing + // {0} battled at {1}’s side against {2} that Dynamaxed. {4} that {3}. + case 71 when !GetCanDynamaxTrainer(memory.Variable, 8, handler == 0 ? (GameVersion)pk.Version : GameVersion.Any): + // {0} battled {2} and Dynamaxed upon {1}’s instruction. {4} that {3}. + case 72 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable): + return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler)); + + // Move + // {0} studied about how to use {2} in a Box, thinking about {1}. {4} that {3}. + // {0} practiced its cool pose for the move {2} in a Box, wishing to be praised by {1}. {4} that {3}. + case 80 or 81 when !CanKnowMove(pk, memory, gen, info): + return Get(string.Format(LMemoryArgBadMove, memory.Handler), Severity.Invalid); + + // Species + // With {1}, {0} went fishing, and they caught {2}. {4} that {3}. + case 7 when !GetCanFishSpecies(memory.Variable, gen, handler == 0 ? (GameVersion)pk.Version : GameVersion.Any): + return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler)); + + // {0} saw {1} paying attention to {2}. {4} that {3}. + // {0} fought hard until it had to use Struggle when it battled at {1}’s side against {2}. {4} that {3}. + // {0} was taken to a Pokémon Nursery by {1} and left with {2}. {4} that {3}. + case 9 or 60 or 75 when gen == 8 && !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable): + return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler)); + + // {0} had a great chat about {1} with the {2} that it was in a Box with. {4} that {3}. + // {0} became good friends with the {2} in a Box, practiced moves with it, and talked about the day that {0} would be praised by {1}. {4} that {3}. + // {0} got in a fight with the {2} that it was in a Box with about {1}. {4} that {3}. + case 82 or 83 or 87 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable): + return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler)); + + // {0} had a very hard training session with {1}. {4} that {3}. + case 53 when gen == 8 && pk is IHyperTrain t && !t.IsHyperTrained(): return GetInvalid(string.Format(LMemoryArgBadID, memory.Handler)); - switch (memory.MemoryID) - { - case 19 when pkm.Species is (int)Species.Urshifu && memory.Variable is not 34: // tall building is the only location for evolving Urshifu - case 19 when pkm.Species is (int)Species.Runerigus && memory.Variable is not 72: // vast field is the only location for evolving Runerigus - return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler)); - - // {0} saw {2} carrying {1} on its back. {4} that {3}. - case 21 when gen != 6 || !PersonalTable.AO.GetFormEntry(memory.Variable, 0).TMHM[101]: // Fly - return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); - - // {0} used {2} at {1}’s instruction, but it had no effect. {4} that {3}. - // The Move Deleter that {0} met through {1} made it forget {2}. {4} that {3}. - case 16 or 48 when !CanKnowMove(pkm, memory, gen, info, memory.MemoryID == 16): - return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); - - case 49 when memory.Variable == 0 || !GetCanRelearnMove(pkm, memory.Variable, gen, info.EvoChainsAllGens[gen]): - return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler)); - - // Dynamaxing - // {0} battled at {1}’s side against {2} that Dynamaxed. {4} that {3}. - case 71 when !GetCanDynamaxTrainer(memory.Variable, 8, handler == 0 ? (GameVersion)pkm.Version : GameVersion.Any): - // {0} battled {2} and Dynamaxed upon {1}’s instruction. {4} that {3}. - case 72 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable): - return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler)); - - // Move - // {0} studied about how to use {2} in a Box, thinking about {1}. {4} that {3}. - // {0} practiced its cool pose for the move {2} in a Box, wishing to be praised by {1}. {4} that {3}. - case 80 or 81 when !CanKnowMove(pkm, memory, gen, info): - return Get(string.Format(LMemoryArgBadMove, memory.Handler), Severity.Invalid); - - // Species - // With {1}, {0} went fishing, and they caught {2}. {4} that {3}. - case 7 when !GetCanFishSpecies(memory.Variable, gen, handler == 0 ? (GameVersion)pkm.Version : GameVersion.Any): - return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler)); - - // {0} saw {1} paying attention to {2}. {4} that {3}. - // {0} fought hard until it had to use Struggle when it battled at {1}’s side against {2}. {4} that {3}. - // {0} was taken to a Pokémon Nursery by {1} and left with {2}. {4} that {3}. - case 9 or 60 or 75 when gen == 8 && !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable): - return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler)); - - // {0} had a great chat about {1} with the {2} that it was in a Box with. {4} that {3}. - // {0} became good friends with the {2} in a Box, practiced moves with it, and talked about the day that {0} would be praised by {1}. {4} that {3}. - // {0} got in a fight with the {2} that it was in a Box with about {1}. {4} that {3}. - case 82 or 83 or 87 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable): - return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler)); - - // {0} had a very hard training session with {1}. {4} that {3}. - case 53 when gen == 8 && pkm is IHyperTrain t && !t.IsHyperTrained(): - return GetInvalid(string.Format(LMemoryArgBadID, memory.Handler)); - - // Item - // {0} went to a Pokémon Center with {1} to buy {2}. {4} that {3}. - case 5 when !CanBuyItem(gen, memory.Variable, handler == 0 ? (GameVersion)pkm.Version : GameVersion.Any): - // {1} used {2} when {0} was in trouble. {4} that {3}. - case 15 when !CanUseItem(gen, memory.Variable, pkm.Species): - // {0} saw {1} using {2}. {4} that {3}. - case 26 when !CanUseItemGeneric(gen, memory.Variable): - // {0} planted {2} with {1} and imagined a big harvest. {4} that {3}. - case 34 when !CanPlantBerry(gen, memory.Variable): - // {1} had {0} hold items like {2} to help it along. {4} that {3}. - case 40 when !CanHoldItem(gen, memory.Variable): - // {0} was excited when {1} won prizes like {2} through Loto-ID. {4} that {3}. - case 51 when !CanWinLotoID(gen, memory.Variable): - // {0} was worried if {1} was looking for the {2} that it was holding in a Box. {4} that {3}. - // When {0} was in a Box, it thought about the reason why {1} had it hold the {2}. {4} that {3}. - case 84 or 88 when !Legal.HeldItems_SWSH.Contains(memory.Variable) || pkm.IsEgg: - return GetInvalid(string.Format(LMemoryArgBadItem, memory.Handler)); - } - - return VerifyCommonMemoryEtc(memory, context); + // Item + // {0} went to a Pokémon Center with {1} to buy {2}. {4} that {3}. + case 5 when !CanBuyItem(gen, memory.Variable, handler == 0 ? (GameVersion)pk.Version : GameVersion.Any): + // {1} used {2} when {0} was in trouble. {4} that {3}. + case 15 when !CanUseItem(gen, memory.Variable, pk.Species): + // {0} saw {1} using {2}. {4} that {3}. + case 26 when !CanUseItemGeneric(gen, memory.Variable): + // {0} planted {2} with {1} and imagined a big harvest. {4} that {3}. + case 34 when !CanPlantBerry(gen, memory.Variable): + // {1} had {0} hold items like {2} to help it along. {4} that {3}. + case 40 when !CanHoldItem(gen, memory.Variable): + // {0} was excited when {1} won prizes like {2} through Loto-ID. {4} that {3}. + case 51 when !CanWinLotoID(gen, memory.Variable): + // {0} was worried if {1} was looking for the {2} that it was holding in a Box. {4} that {3}. + // When {0} was in a Box, it thought about the reason why {1} had it hold the {2}. {4} that {3}. + case 84 or 88 when !Legal.HeldItems_SWSH.Contains(memory.Variable) || pk.IsEgg: + return GetInvalid(string.Format(LMemoryArgBadItem, memory.Handler)); } - private CheckResult VerifyCommonMemoryEtc(MemoryVariableSet memory, MemoryContext context) + return VerifyCommonMemoryEtc(memory, context); + } + + private CheckResult VerifyCommonMemoryEtc(MemoryVariableSet memory, MemoryContext context) + { + if (!context.CanHaveIntensity(memory.MemoryID, memory.Intensity)) { - if (!context.CanHaveIntensity(memory.MemoryID, memory.Intensity)) - { - var min = context.GetMinimumIntensity(memory.MemoryID); - return GetInvalid(string.Format(LMemoryIndexIntensityMin, memory.Handler, min)); - } - - if (!context.CanHaveFeeling(memory.MemoryID, memory.Feeling, memory.Variable)) - return GetInvalid(string.Format(LMemoryFeelInvalid, memory.Handler)); - - return GetValid(string.Format(LMemoryF_0_Valid, memory.Handler)); + var min = context.GetMinimumIntensity(memory.MemoryID); + return GetInvalid(string.Format(LMemoryIndexIntensityMin, memory.Handler, min)); } - /// - /// Used for enforcing a fixed memory detail. - /// - /// Output storage - /// Memory ID - /// Intensity - /// Text Variable - /// Feeling - private void VerifyOTMemoryIs(LegalityAnalysis data, byte m, byte i, ushort t, byte f) + if (!context.CanHaveFeeling(memory.MemoryID, memory.Feeling, memory.Variable)) + return GetInvalid(string.Format(LMemoryFeelInvalid, memory.Handler)); + + return GetValid(string.Format(LMemoryF_0_Valid, memory.Handler)); + } + + /// + /// Used for enforcing a fixed memory detail. + /// + /// Output storage + /// Memory ID + /// Intensity + /// Text Variable + /// Feeling + private void VerifyOTMemoryIs(LegalityAnalysis data, byte m, byte i, ushort t, byte f) + { + var pk = (ITrainerMemories)data.Entity; + if (pk.OT_Memory != m) + data.AddLine(GetInvalid(string.Format(LMemoryIndexID, L_XOT, m))); + if (pk.OT_Intensity != i) + data.AddLine(GetInvalid(string.Format(LMemoryIndexIntensity, L_XOT, i))); + if (pk.OT_TextVar != t) + data.AddLine(GetInvalid(string.Format(LMemoryIndexVar, L_XOT, t))); + if (pk.OT_Feeling != f) + data.AddLine(GetInvalid(string.Format(LMemoryIndexFeel, L_XOT, f))); + } + + private void VerifyHTMemoryNone(LegalityAnalysis data, ITrainerMemories pk) + { + if (pk.HT_Memory != 0 || pk.HT_TextVar != 0 || pk.HT_Intensity != 0 || pk.HT_Feeling != 0) + data.AddLine(GetInvalid(string.Format(LMemoryCleared, L_XHT))); + } + + private void VerifyOTMemory(LegalityAnalysis data) + { + var pk = data.Entity; + var mem = (ITrainerMemories)pk; + var Info = data.Info; + + // If the encounter has a memory from the OT that could never have it replaced, ensure it was not modified. + switch (data.EncounterMatch) { - var pkm = (ITrainerMemories)data.pkm; - if (pkm.OT_Memory != m) - data.AddLine(GetInvalid(string.Format(LMemoryIndexID, L_XOT, m))); - if (pkm.OT_Intensity != i) - data.AddLine(GetInvalid(string.Format(LMemoryIndexIntensity, L_XOT, i))); - if (pkm.OT_TextVar != t) - data.AddLine(GetInvalid(string.Format(LMemoryIndexVar, L_XOT, t))); - if (pkm.OT_Feeling != f) - data.AddLine(GetInvalid(string.Format(LMemoryIndexFeel, L_XOT, f))); + case WC6 {IsEgg: false} g when g.OTGender != 3: + VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling); + return; + case WC7 {IsEgg: false} g when g.OTGender != 3: + VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling); + return; + case WC8 {IsEgg: false} g when g.OTGender != 3: + VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling); + return; + + case IMemoryOT t and not MysteryGift: // Ignore Mystery Gift cases (covered above) + VerifyOTMemoryIs(data, t.OT_Memory, t.OT_Intensity, t.OT_TextVar, t.OT_Feeling); + return; } - private void VerifyHTMemoryNone(LegalityAnalysis data, ITrainerMemories pkm) + int memoryGen = Info.Generation; + var memory = mem.OT_Memory; + + if (pk.IsEgg) { - if (pkm.HT_Memory != 0 || pkm.HT_TextVar != 0 || pkm.HT_Intensity != 0 || pkm.HT_Feeling != 0) - data.AddLine(GetInvalid(string.Format(LMemoryCleared, L_XHT))); - } - - private void VerifyOTMemory(LegalityAnalysis data) - { - var pkm = data.pkm; - var mem = (ITrainerMemories)pkm; - var Info = data.Info; - - // If the encounter has a memory from the OT that could never have it replaced, ensure it was not modified. - switch (data.EncounterMatch) - { - case WC6 {IsEgg: false} g when g.OTGender != 3: - VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling); - return; - case WC7 {IsEgg: false} g when g.OTGender != 3: - VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling); - return; - case WC8 {IsEgg: false} g when g.OTGender != 3: - VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling); - return; - - case IMemoryOT t and not MysteryGift: // Ignore Mystery Gift cases (covered above) - VerifyOTMemoryIs(data, t.OT_Memory, t.OT_Intensity, t.OT_TextVar, t.OT_Feeling); - return; - } - - int memoryGen = Info.Generation; - var memory = mem.OT_Memory; - - if (pkm.IsEgg) - { - // Traded unhatched eggs in Gen8 have OT link trade memory applied erroneously. - // They can also have the box-inspect memory! - if (memoryGen != 8 || !((pkm.Met_Location == Locations.LinkTrade6 && memory == 4) || memory == 85)) - { - VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty - return; - } - } - else if (!CanHaveMemoryForOT(pkm, memoryGen, memory, Info.EvoChainsAllGens)) + // Traded unhatched eggs in Gen8 have OT link trade memory applied erroneously. + // They can also have the box-inspect memory! + if (memoryGen != 8 || !((pk.Met_Location == Locations.LinkTrade6 && memory == 4) || memory == 85)) { VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty return; } - - // Bounds checking - var context = Memories.GetContext(memoryGen); - if (!context.CanObtainMemoryOT((GameVersion)pkm.Version, memory)) - data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XOT))); - - // Verify memory if specific to OT - switch (memory) - { - // No Memory - case 0: // SWSH trades don't set HT memories immediately, which is hilarious. - data.AddLine(Get(LMemoryMissingOT, memoryGen == 8 ? Severity.Fishy : Severity.Invalid)); - VerifyOTMemoryIs(data, 0, 0, 0, 0); - return; - - // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}. - case 2 when !Info.EncounterMatch.EggEncounter: - data.AddLine(GetInvalid(string.Format(LMemoryArgBadHatch, L_XOT))); - break; - - // {0} became {1}’s friend when it arrived via Link Trade at... {2}. {4} that {3}. - case 4 when Info.Generation == 6: // gen8 applies this memory erroneously - data.AddLine(GetInvalid(string.Format(LMemoryArgBadOTEgg, L_XOT))); - return; - - // {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}. - case 6 when !context.HasPokeCenter((GameVersion)pkm.Version, mem.OT_TextVar): - data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XOT))); - return; - - // {0} was with {1} when {1} caught {2}. {4} that {3}. - case 14: - var result = GetCanBeCaptured(mem.OT_TextVar, Info.Generation, (GameVersion)pkm.Version) // Any Game in the Handling Trainer's generation - ? GetValid(string.Format(LMemoryArgSpecies, L_XOT)) - : GetInvalid(string.Format(LMemoryArgBadSpecies, L_XOT)); - data.AddLine(result); - return; - } - - data.AddLine(VerifyCommonMemory(pkm, 0, Info.Generation, Info, context)); + } + else if (!CanHaveMemoryForOT(pk, memoryGen, memory, Info.EvoChainsAllGens)) + { + VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty + return; } - private static bool CanHaveMemoryForOT(PKM pkm, int origin, int memory, EvolutionHistory evos) + // Bounds checking + var context = Memories.GetContext(memoryGen); + if (!context.CanObtainMemoryOT((GameVersion)pk.Version, memory)) + data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XOT))); + + // Verify memory if specific to OT + switch (memory) { - switch (origin) - { - // Bank Memories only: Gen7 does not set memories. - case 1 or 2 or 7 when memory != 4: // VC transfers - - // Memories don't exist - case 7 when pkm.GG: // LGPE does not set memories. - case 8 when pkm.GO_HOME: // HOME does not set memories. - case 8 when pkm.Met_Location == Locations.HOME8: // HOME does not set memories. - case 8 when pkm.BDSP && !pkm.HasVisitedSWSH(evos.Gen8): // BDSP does not set memories. - case 8 when pkm.LA && !pkm.HasVisitedSWSH(evos.Gen8): // LA does not set memories. - return false; - - // Eggs cannot have memories - // Cannot have memories if the OT was from a generation prior to Gen6. - default: - return origin >= 6 && !pkm.IsEgg; - } - } - - private void VerifyHTMemory(LegalityAnalysis data) - { - var pkm = data.pkm; - var mem = (ITrainerMemories)pkm; - var Info = data.Info; - - var memory = mem.HT_Memory; - - if (pkm.IsUntraded) - { - if (memory == 4 && WasTradedSWSHEgg(pkm)) - { - // Untraded link trade eggs in Gen8 have HT link trade memory applied erroneously. - // Verify the link trade memory later. - } - else - { - VerifyHTMemoryNone(data, mem); - return; - } - } - - if (pkm.Format == 7) - { - VerifyHTMemoryTransferTo7(data, pkm, Info); + // No Memory + case 0: // SWSH trades don't set HT memories immediately, which is hilarious. + data.AddLine(Get(LMemoryMissingOT, memoryGen == 8 ? Severity.Fishy : Severity.Invalid)); + VerifyOTMemoryIs(data, 0, 0, 0, 0); return; - } - var memoryGen = pkm.Format >= 8 ? 8 : 6; + // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}. + case 2 when !Info.EncounterMatch.EggEncounter: + data.AddLine(GetInvalid(string.Format(LMemoryArgBadHatch, L_XOT))); + break; - // Bounds checking - var context = Memories.GetContext(memoryGen); - if (!context.CanObtainMemoryHT((GameVersion)pkm.Version, memory)) - data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XHT))); + // {0} became {1}’s friend when it arrived via Link Trade at... {2}. {4} that {3}. + case 4 when Info.Generation == 6: // gen8 applies this memory erroneously + data.AddLine(GetInvalid(string.Format(LMemoryArgBadOTEgg, L_XOT))); + return; - // Verify memory if specific to HT - switch (memory) - { - // No Memory - case 0: // SWSH memory application has an off-by-one error: [0,99] + 1 <= chance --> don't apply - data.AddLine(Get(LMemoryMissingHT, memoryGen == 8 ? ParseSettings.Gen8MemoryMissingHT : Severity.Invalid)); - VerifyHTMemoryNone(data, mem); - return; + // {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}. + case 6 when !context.HasPokeCenter((GameVersion)pk.Version, mem.OT_TextVar): + data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XOT))); + return; - // {0} met {1} at... {2}. {1} threw a Poké Ball at it, and they started to travel together. {4} that {3}. - case 1: - data.AddLine(GetInvalid(string.Format(LMemoryArgBadCatch, L_XHT))); - return; - - // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}. - case 2: - data.AddLine(GetInvalid(string.Format(LMemoryArgBadHatch, L_XHT))); - return; - - // {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}. - case 6 when !context.HasPokeCenter(GameVersion.Any, mem.HT_TextVar): - data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XHT))); - return; - - // {0} was with {1} when {1} caught {2}. {4} that {3}. - case 14: - var result = GetCanBeCaptured(mem.HT_TextVar, memoryGen, GameVersion.Any) // Any Game in the Handling Trainer's generation - ? GetValid(string.Format(LMemoryArgSpecies, L_XHT)) - : GetInvalid(string.Format(LMemoryArgBadSpecies, L_XHT)); - data.AddLine(result); - return; - } - - var commonResult = VerifyCommonMemory(pkm, 1, memoryGen, Info, context); - data.AddLine(commonResult); + // {0} was with {1} when {1} caught {2}. {4} that {3}. + case 14: + var result = GetCanBeCaptured(mem.OT_TextVar, Info.Generation, (GameVersion)pk.Version) // Any Game in the Handling Trainer's generation + ? GetValid(string.Format(LMemoryArgSpecies, L_XOT)) + : GetInvalid(string.Format(LMemoryArgBadSpecies, L_XOT)); + data.AddLine(result); + return; } - private static bool WasTradedSWSHEgg(PKM pkm) => pkm.SWSH && (!pkm.IsEgg ? pkm.Egg_Location : pkm.Met_Location) is Locations.LinkTrade6; + data.AddLine(VerifyCommonMemory(pk, 0, Info.Generation, Info, context)); + } - private void VerifyHTMemoryTransferTo7(LegalityAnalysis data, PKM pkm, LegalInfo Info) + private static bool CanHaveMemoryForOT(PKM pk, int origin, int memory, EvolutionHistory evos) + { + switch (origin) { - var mem = (ITrainerMemories)pkm; - // Bank Transfer adds in the Link Trade Memory. - // Trading 7<->7 between games (not Bank) clears this data. - if (mem.HT_Memory == 0) + // Bank Memories only: Gen7 does not set memories. + case 1 or 2 or 7 when memory != 4: // VC transfers + + // Memories don't exist + case 7 when pk.GG: // LGPE does not set memories. + case 8 when pk.GO_HOME: // HOME does not set memories. + case 8 when pk.Met_Location == Locations.HOME8: // HOME does not set memories. + case 8 when pk.BDSP && !pk.HasVisitedSWSH(evos.Gen8): // BDSP does not set memories. + case 8 when pk.LA && !pk.HasVisitedSWSH(evos.Gen8): // LA does not set memories. + return false; + + // Eggs cannot have memories + // Cannot have memories if the OT was from a generation prior to Gen6. + default: + return origin >= 6 && !pk.IsEgg; + } + } + + private void VerifyHTMemory(LegalityAnalysis data) + { + var pk = data.Entity; + var mem = (ITrainerMemories)pk; + var Info = data.Info; + + var memory = mem.HT_Memory; + + if (pk.IsUntraded) + { + if (memory == 4 && WasTradedSWSHEgg(pk)) + { + // Untraded link trade eggs in Gen8 have HT link trade memory applied erroneously. + // Verify the link trade memory later. + } + else { VerifyHTMemoryNone(data, mem); return; } + } - // Transfer 6->7 & withdraw to same HT => keeps past gen memory - // Don't require link trade memory for these past gen cases - int gen = Info.Generation; - if (gen is >= 3 and < 7 && pkm.CurrentHandler == 1) + if (pk.Format == 7) + { + VerifyHTMemoryTransferTo7(data, pk, Info); + return; + } + + var memoryGen = pk.Format >= 8 ? 8 : 6; + + // Bounds checking + var context = Memories.GetContext(memoryGen); + if (!context.CanObtainMemoryHT((GameVersion)pk.Version, memory)) + data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XHT))); + + // Verify memory if specific to HT + switch (memory) + { + // No Memory + case 0: // SWSH memory application has an off-by-one error: [0,99] + 1 <= chance --> don't apply + data.AddLine(Get(LMemoryMissingHT, memoryGen == 8 ? ParseSettings.Gen8MemoryMissingHT : Severity.Invalid)); + VerifyHTMemoryNone(data, mem); return; - if (mem.HT_Memory != 4) - data.AddLine(Severity.Invalid, LMemoryIndexLinkHT, CheckIdentifier.Memory); - if (mem.HT_TextVar != 0) - data.AddLine(Severity.Invalid, LMemoryIndexArgHT, CheckIdentifier.Memory); - if (mem.HT_Intensity != 1) - data.AddLine(Severity.Invalid, LMemoryIndexIntensityHT1, CheckIdentifier.Memory); - if (mem.HT_Feeling > 10) - data.AddLine(Severity.Invalid, LMemoryIndexFeelHT09, CheckIdentifier.Memory); + // {0} met {1} at... {2}. {1} threw a Poké Ball at it, and they started to travel together. {4} that {3}. + case 1: + data.AddLine(GetInvalid(string.Format(LMemoryArgBadCatch, L_XHT))); + return; + + // {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}. + case 2: + data.AddLine(GetInvalid(string.Format(LMemoryArgBadHatch, L_XHT))); + return; + + // {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}. + case 6 when !context.HasPokeCenter(GameVersion.Any, mem.HT_TextVar): + data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XHT))); + return; + + // {0} was with {1} when {1} caught {2}. {4} that {3}. + case 14: + var result = GetCanBeCaptured(mem.HT_TextVar, memoryGen, GameVersion.Any) // Any Game in the Handling Trainer's generation + ? GetValid(string.Format(LMemoryArgSpecies, L_XHT)) + : GetInvalid(string.Format(LMemoryArgBadSpecies, L_XHT)); + data.AddLine(result); + return; } + + var commonResult = VerifyCommonMemory(pk, 1, memoryGen, Info, context); + data.AddLine(commonResult); + } + + private static bool WasTradedSWSHEgg(PKM pk) => pk.SWSH && (!pk.IsEgg ? pk.Egg_Location : pk.Met_Location) is Locations.LinkTrade6; + + private void VerifyHTMemoryTransferTo7(LegalityAnalysis data, PKM pk, LegalInfo Info) + { + var mem = (ITrainerMemories)pk; + // Bank Transfer adds in the Link Trade Memory. + // Trading 7<->7 between games (not Bank) clears this data. + if (mem.HT_Memory == 0) + { + VerifyHTMemoryNone(data, mem); + return; + } + + // Transfer 6->7 & withdraw to same HT => keeps past gen memory + // Don't require link trade memory for these past gen cases + int gen = Info.Generation; + if (gen is >= 3 and < 7 && pk.CurrentHandler == 1) + return; + + if (mem.HT_Memory != 4) + data.AddLine(Severity.Invalid, LMemoryIndexLinkHT, CheckIdentifier.Memory); + if (mem.HT_TextVar != 0) + data.AddLine(Severity.Invalid, LMemoryIndexArgHT, CheckIdentifier.Memory); + if (mem.HT_Intensity != 1) + data.AddLine(Severity.Invalid, LMemoryIndexIntensityHT1, CheckIdentifier.Memory); + if (mem.HT_Feeling > 10) + data.AddLine(Severity.Invalid, LMemoryIndexFeelHT09, CheckIdentifier.Memory); } } diff --git a/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs b/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs index 712dbd8f5..122e54e82 100644 --- a/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs +++ b/PKHeX.Core/Legality/Verifiers/Misc/ContestStatInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using static PKHeX.Core.ContestStatGranting; namespace PKHeX.Core; @@ -87,12 +87,12 @@ public static int CalculateMaximumSheen(IContestStats s, int nature, IContestSta return Math.Min(MaxContestStat, avg * HighestFeelPoffin8b); } - public static int CalculateMinimumSheen(IContestStats s, IContestStats initial, INature pkm, ContestStatGrantingSheen method) => method switch + public static int CalculateMinimumSheen(IContestStats s, IContestStats initial, INature pk, ContestStatGrantingSheen method) => method switch { - ContestStatGrantingSheen.Gen8b => CalculateMinimumSheen8b(s, pkm.Nature, initial), - ContestStatGrantingSheen.Gen3 => CalculateMinimumSheen3(s, pkm.Nature, initial), - ContestStatGrantingSheen.Gen4 => CalculateMinimumSheen4(s, pkm.Nature, initial), - _ => throw new IndexOutOfRangeException(nameof(method)), + ContestStatGrantingSheen.Gen8b => CalculateMinimumSheen8b(s, pk.Nature, initial), + ContestStatGrantingSheen.Gen3 => CalculateMinimumSheen3(s, pk.Nature, initial), + ContestStatGrantingSheen.Gen4 => CalculateMinimumSheen4(s, pk.Nature, initial), + _ => throw new ArgumentOutOfRangeException(nameof(method)), }; // Slightly better stat:sheen ratio than Gen4; prefer if has visited. @@ -211,7 +211,7 @@ static int Boost(int stat, int factor) private static readonly DummyContestNone DummyNone = new(); - public static IContestStats GetReferenceTemplate(IEncounterTemplate initial) => initial is IContestStats s ? s : DummyNone; + public static IContestStats GetReferenceTemplate(IEncounterTemplate initial) => initial as IContestStats ?? DummyNone; private sealed class DummyContestNone : IContestStats { diff --git a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs index 78809708f..8f272894e 100644 --- a/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/MiscVerifier.cs @@ -4,657 +4,656 @@ using static PKHeX.Core.LegalityCheckStrings; using static PKHeX.Core.CheckIdentifier; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies miscellaneous data including and minor values. +/// +public sealed class MiscVerifier : Verifier { - /// - /// Verifies miscellaneous data including and minor values. - /// - public sealed class MiscVerifier : Verifier + protected override CheckIdentifier Identifier => Misc; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => Misc; - - public override void Verify(LegalityAnalysis data) + var pk = data.Entity; + if (pk.IsEgg) { - var pkm = data.pkm; - if (pkm.IsEgg) + VerifyMiscEggCommon(data); + + if (pk is IContestStats s && s.HasContestStats()) + data.AddLine(GetInvalid(LEggContest, Egg)); + + switch (pk) { - VerifyMiscEggCommon(data); - - if (pkm is IContestStats s && s.HasContestStats()) - data.AddLine(GetInvalid(LEggContest, Egg)); - - switch (pkm) - { - case PK5 pk5 when pk5.PokeStarFame != 0 && pk5.IsEgg: - data.AddLine(GetInvalid(LEggShinyPokeStar, Egg)); - break; - case PK4 pk4 when pk4.ShinyLeaf != 0: - data.AddLine(GetInvalid(LEggShinyLeaf, Egg)); - break; - case PK4 pk4 when pk4.PokeathlonStat != 0: - data.AddLine(GetInvalid(LEggPokeathlon, Egg)); - break; - case PK3 when pkm.Language != 1: // All Eggs are Japanese and flagged specially for localized string - data.AddLine(GetInvalid(string.Format(LOTLanguage, LanguageID.Japanese, (LanguageID)pkm.Language), Egg)); - break; - } - - if (pkm is IHomeTrack {Tracker: not 0}) - data.AddLine(GetInvalid(LTransferTrackerShouldBeZero)); - } - else - { - VerifyMiscMovePP(data); - } - - switch (pkm) - { - case PK7 {ResortEventStatus: >= ResortEventState.MAX}: - data.AddLine(GetInvalid(LTransferBad)); + case PK5 pk5 when pk5.PokeStarFame != 0 && pk5.IsEgg: + data.AddLine(GetInvalid(LEggShinyPokeStar, Egg)); break; - case PB7 pb7: - VerifyBelugaStats(data, pb7); + case PK4 pk4 when pk4.ShinyLeaf != 0: + data.AddLine(GetInvalid(LEggShinyLeaf, Egg)); break; - case PK8 pk8: - VerifySWSHStats(data, pk8); + case PK4 pk4 when pk4.PokeathlonStat != 0: + data.AddLine(GetInvalid(LEggPokeathlon, Egg)); break; - case PB8 pb8: - VerifyBDSPStats(data, pb8); - break; - case PA8 pa8: - VerifyPLAStats(data, pa8); + case PK3 when pk.Language != 1: // All Eggs are Japanese and flagged specially for localized string + data.AddLine(GetInvalid(string.Format(LOTLanguage, LanguageID.Japanese, (LanguageID)pk.Language), Egg)); break; } - if (pkm.Format >= 6) - VerifyFullness(data, pkm); + if (pk is IHomeTrack {Tracker: not 0}) + data.AddLine(GetInvalid(LTransferTrackerShouldBeZero)); + } + else + { + VerifyMiscMovePP(data); + } - var enc = data.EncounterMatch; - if (enc is IEncounterServerDate { IsDateRestricted: true } serverGift) + switch (pk) + { + case PK7 {ResortEventStatus: >= ResortEventState.MAX}: + data.AddLine(GetInvalid(LTransferBad)); + break; + case PB7 pb7: + VerifyBelugaStats(data, pb7); + break; + case PK8 pk8: + VerifySWSHStats(data, pk8); + break; + case PB8 pb8: + VerifyBDSPStats(data, pb8); + break; + case PA8 pa8: + VerifyPLAStats(data, pa8); + break; + } + + if (pk.Format >= 6) + VerifyFullness(data, pk); + + var enc = data.EncounterMatch; + if (enc is IEncounterServerDate { IsDateRestricted: true } serverGift) + { + var date = new DateTime(pk.Met_Year + 2000, pk.Met_Month, pk.Met_Day); + + // HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC). + if (enc is WB8 { CardID: 9015 or 9016 or 9017 } or WA8 { CardID: 9018 or 9019 or 9020 }) { - var date = new DateTime(pkm.Met_Year + 2000, pkm.Met_Month, pkm.Met_Day); - - // HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC). - if (enc is WB8 { CardID: 9015 or 9016 or 9017 } or WA8 { CardID: 9018 or 9019 or 9020 }) - { - if (date < new DateTime(2022, 5, 20) && pkm.Language != (int)LanguageID.Japanese) - data.AddLine(GetInvalid(LDateOutsideDistributionWindow)); - } - - var result = serverGift.IsValidDate(date); - if (result == EncounterServerDateCheck.Invalid) + if (date < new DateTime(2022, 5, 20) && pk.Language != (int)LanguageID.Japanese) data.AddLine(GetInvalid(LDateOutsideDistributionWindow)); } - else if (enc is IOverworldCorrelation8 z) + + var result = serverGift.IsValidDate(date); + if (result == EncounterServerDateCheck.Invalid) + data.AddLine(GetInvalid(LDateOutsideDistributionWindow)); + } + else if (enc is IOverworldCorrelation8 z) + { + var match = z.IsOverworldCorrelationCorrect(pk); + var req = z.GetRequirement(pk); + if (match) { - var match = z.IsOverworldCorrelationCorrect(pkm); - var req = z.GetRequirement(pkm); - if (match) - { - var seed = Overworld8RNG.GetOriginalSeed(pkm); - data.Info.PIDIV = new PIDIV(PIDType.Overworld8, seed); - } - - bool valid = req switch - { - OverworldCorrelation8Requirement.MustHave => match, - OverworldCorrelation8Requirement.MustNotHave => !match, - _ => true, - }; - - if (!valid) - data.AddLine(GetInvalid(LPIDTypeMismatch)); - } - else if (enc is IStaticCorrelation8b s8b) - { - var match = s8b.IsStaticCorrelationCorrect(pkm); - var req = s8b.GetRequirement(pkm); - if (match) - data.Info.PIDIV = new PIDIV(PIDType.Roaming8b, pkm.EncryptionConstant); - - bool valid = req switch - { - StaticCorrelation8bRequirement.MustHave => match, - StaticCorrelation8bRequirement.MustNotHave => !match, - _ => true, - }; - - if (!valid) - data.AddLine(GetInvalid(LPIDTypeMismatch)); - } - else if (enc is IMasteryInitialMoveShop8 m) - { - if (!m.IsForcedMasteryCorrect(pkm)) - data.AddLine(GetInvalid(LEncMasteryInitial)); + var seed = Overworld8RNG.GetOriginalSeed(pk); + data.Info.PIDIV = new PIDIV(PIDType.Overworld8, seed); } - VerifyMiscFatefulEncounter(data); - VerifyMiscPokerus(data); - } - - private void VerifyMiscPokerus(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.Format == 1) - return; - - var strain = pkm.PKRS_Strain; - var days = pkm.PKRS_Days; - bool strainValid = Pokerus.IsStrainValid(pkm, strain, days); - if (!strainValid) - data.AddLine(GetInvalid(string.Format(LPokerusStrainUnobtainable_0, strain))); - - bool daysValid = Pokerus.IsDurationValid(strain, days, out var max); - if (!daysValid) - data.AddLine(GetInvalid(string.Format(LPokerusDaysTooHigh_0, max))); - } - - public void VerifyMiscG1(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.IsEgg) - VerifyMiscEggCommon(data); - - if (pkm is not PK1 pk1) - return; - - VerifyMiscG1Types(data, pk1); - VerifyMiscG1CatchRate(data, pk1); - } - - private void VerifyMiscG1Types(LegalityAnalysis data, PK1 pk1) - { - var Type_A = pk1.Type_A; - var Type_B = pk1.Type_B; - var species = pk1.Species; - if (species == (int)Species.Porygon) + bool valid = req switch { - // Can have any type combination of any species by using Conversion. - if (!GBRestrictions.TypeIDExists(Type_A)) - { - data.AddLine(GetInvalid(LG1TypePorygonFail1)); - } - if (!GBRestrictions.TypeIDExists(Type_B)) - { - data.AddLine(GetInvalid(LG1TypePorygonFail2)); - } - else // Both types exist, ensure a Gen1 species has this combination - { - var TypesAB_Match = PersonalTable.RB.IsValidTypeCombination(Type_A, Type_B); - var result = TypesAB_Match ? GetValid(LG1TypeMatchPorygon) : GetInvalid(LG1TypePorygonFail); - data.AddLine(result); - } + OverworldCorrelation8Requirement.MustHave => match, + OverworldCorrelation8Requirement.MustNotHave => !match, + _ => true, + }; + + if (!valid) + data.AddLine(GetInvalid(LPIDTypeMismatch)); + } + else if (enc is IStaticCorrelation8b s8b) + { + var match = s8b.IsStaticCorrelationCorrect(pk); + var req = s8b.GetRequirement(pk); + if (match) + data.Info.PIDIV = new PIDIV(PIDType.Roaming8b, pk.EncryptionConstant); + + bool valid = req switch + { + StaticCorrelation8bRequirement.MustHave => match, + StaticCorrelation8bRequirement.MustNotHave => !match, + _ => true, + }; + + if (!valid) + data.AddLine(GetInvalid(LPIDTypeMismatch)); + } + else if (enc is IMasteryInitialMoveShop8 m) + { + if (!m.IsForcedMasteryCorrect(pk)) + data.AddLine(GetInvalid(LEncMasteryInitial)); + } + + VerifyMiscFatefulEncounter(data); + VerifyMiscPokerus(data); + } + + private void VerifyMiscPokerus(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.Format == 1) + return; + + var strain = pk.PKRS_Strain; + var days = pk.PKRS_Days; + bool strainValid = Pokerus.IsStrainValid(pk, strain, days); + if (!strainValid) + data.AddLine(GetInvalid(string.Format(LPokerusStrainUnobtainable_0, strain))); + + bool daysValid = Pokerus.IsDurationValid(strain, days, out var max); + if (!daysValid) + data.AddLine(GetInvalid(string.Format(LPokerusDaysTooHigh_0, max))); + } + + public void VerifyMiscG1(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.IsEgg) + VerifyMiscEggCommon(data); + + if (pk is not PK1 pk1) + return; + + VerifyMiscG1Types(data, pk1); + VerifyMiscG1CatchRate(data, pk1); + } + + private void VerifyMiscG1Types(LegalityAnalysis data, PK1 pk1) + { + var Type_A = pk1.Type_A; + var Type_B = pk1.Type_B; + var species = pk1.Species; + if (species == (int)Species.Porygon) + { + // Can have any type combination of any species by using Conversion. + if (!GBRestrictions.TypeIDExists(Type_A)) + { + data.AddLine(GetInvalid(LG1TypePorygonFail1)); } - else // Types must match species types + if (!GBRestrictions.TypeIDExists(Type_B)) { - var pi = PersonalTable.RB[species]; - var Type_A_Match = Type_A == pi.Type1; - var Type_B_Match = Type_B == pi.Type2; - - var first = Type_A_Match ? GetValid(LG1TypeMatch1) : GetInvalid(LG1Type1Fail); - var second = Type_B_Match || (ParseSettings.AllowGBCartEra && ((species is (int)Species.Magnemite or (int)Species.Magneton) && Type_B == 9)) // Steel Magnemite via Stadium2 - ? GetValid(LG1TypeMatch2) : GetInvalid(LG1Type2Fail); - data.AddLine(first); - data.AddLine(second); + data.AddLine(GetInvalid(LG1TypePorygonFail2)); + } + else // Both types exist, ensure a Gen1 species has this combination + { + var TypesAB_Match = PersonalTable.RB.IsValidTypeCombination(Type_A, Type_B); + var result = TypesAB_Match ? GetValid(LG1TypeMatchPorygon) : GetInvalid(LG1TypePorygonFail); + data.AddLine(result); } } - - private void VerifyMiscG1CatchRate(LegalityAnalysis data, PK1 pk1) + else // Types must match species types { - var catch_rate = pk1.Catch_Rate; - var tradeback = GBRestrictions.IsTimeCapsuleTransferred(pk1, data.Info.Moves, data.EncounterMatch); - var result = tradeback is TimeCapsuleEvaluation.NotTransferred or TimeCapsuleEvaluation.BadCatchRate - ? GetWasNotTradeback(tradeback) - : GetWasTradeback(tradeback); - data.AddLine(result); + var pi = PersonalTable.RB[species]; + var Type_A_Match = Type_A == pi.Type1; + var Type_B_Match = Type_B == pi.Type2; - CheckResult GetWasTradeback(TimeCapsuleEvaluation timeCapsuleEvalution) - { - if (catch_rate == 0 || Legal.HeldItems_GSC.Contains((ushort)catch_rate)) - return GetValid(LG1CatchRateMatchTradeback); - if (timeCapsuleEvalution == TimeCapsuleEvaluation.BadCatchRate) - return GetInvalid(LG1CatchRateItem); - - return GetWasNotTradeback(timeCapsuleEvalution); - } - - CheckResult GetWasNotTradeback(TimeCapsuleEvaluation timeCapsuleEvalution) - { - if (data.Info.Moves.Any(z => z.Generation == 2)) - return GetInvalid(LG1CatchRateItem); - var e = data.EncounterMatch; - if (e is EncounterStatic1E {Version: GameVersion.Stadium} or EncounterTrade1) - return GetValid(LG1CatchRateMatchPrevious); // Encounters detected by the catch rate, cant be invalid if match this encounters - - int species = pk1.Species; - if (GBRestrictions.Species_NotAvailable_CatchRate.Contains(species) && catch_rate == PersonalTable.RB[species].CatchRate) - { - if (species != (int) Species.Dragonite || catch_rate != 45 || !e.Version.Contains(GameVersion.YW)) - return GetInvalid(LG1CatchRateEvo); - } - if (!GBRestrictions.RateMatchesEncounter(e.Species, e.Version, catch_rate)) - return GetInvalid(timeCapsuleEvalution == TimeCapsuleEvaluation.Transferred12 ? LG1CatchRateChain : LG1CatchRateNone); - return GetValid(LG1CatchRateMatchPrevious); - } - } - - private static void VerifyMiscFatefulEncounter(LegalityAnalysis data) - { - var pkm = data.pkm; - var enc = data.EncounterMatch; - switch (enc) - { - case WC3 {Fateful: true} w: - if (w.IsEgg) - { - // Eggs hatched in RS clear the obedience flag! - // Hatching in Gen3 doesn't change the origin version. - if (pkm.Format != 3) - return; // possible hatched in either game, don't bother checking - if (pkm.Met_Location <= 087) // hatched in RS or Emerald - return; // possible hatched in either game, don't bother checking - // else, ensure fateful is active (via below) - } - VerifyFatefulIngameActive(data); - VerifyWC3Shiny(data, w); - return; - case WC3 w: - if (w.Version == GameVersion.XD) - return; // Can have either state - VerifyWC3Shiny(data, w); - break; - case MysteryGift g: // WC3 handled above - VerifyReceivability(data, g); - VerifyFatefulMysteryGift(data, g); - return; - case EncounterStatic {Fateful: true}: // ingame fateful - case EncounterSlot3PokeSpot: // ingame pokespot - case EncounterTrade4RanchSpecial: // ranch varied PID - VerifyFatefulIngameActive(data); - return; - } - if (pkm.FatefulEncounter) - data.AddLine(GetInvalid(LFatefulInvalid, Fateful)); - } - - private static void VerifyMiscMovePP(LegalityAnalysis data) - { - var pkm = data.pkm; - - if (!Legal.IsPPUpAvailable(pkm)) // No PP Ups - { - if (pkm.Move1_PPUps is not 0) - data.AddLine(GetInvalid(string.Format(LMovePPUpsTooHigh_0, 1), CurrentMove)); - if (pkm.Move2_PPUps is not 0) - data.AddLine(GetInvalid(string.Format(LMovePPUpsTooHigh_0, 2), CurrentMove)); - if (pkm.Move3_PPUps is not 0) - data.AddLine(GetInvalid(string.Format(LMovePPUpsTooHigh_0, 3), CurrentMove)); - if (pkm.Move4_PPUps is not 0) - data.AddLine(GetInvalid(string.Format(LMovePPUpsTooHigh_0, 4), CurrentMove)); - } - - if (pkm.Move1_PP > pkm.GetMovePP(pkm.Move1, pkm.Move1_PPUps)) - data.AddLine(GetInvalid(string.Format(LMovePPTooHigh_0, 1), CurrentMove)); - if (pkm.Move2_PP > pkm.GetMovePP(pkm.Move2, pkm.Move2_PPUps)) - data.AddLine(GetInvalid(string.Format(LMovePPTooHigh_0, 2), CurrentMove)); - if (pkm.Move3_PP > pkm.GetMovePP(pkm.Move3, pkm.Move3_PPUps)) - data.AddLine(GetInvalid(string.Format(LMovePPTooHigh_0, 3), CurrentMove)); - if (pkm.Move4_PP > pkm.GetMovePP(pkm.Move4, pkm.Move4_PPUps)) - data.AddLine(GetInvalid(string.Format(LMovePPTooHigh_0, 4), CurrentMove)); - } - - private static void VerifyMiscEggCommon(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.Move1_PPUps > 0 || pkm.Move2_PPUps > 0 || pkm.Move3_PPUps > 0 || pkm.Move4_PPUps > 0) - data.AddLine(GetInvalid(LEggPPUp, Egg)); - if (pkm.Move1_PP != pkm.GetMovePP(pkm.Move1, 0) || pkm.Move2_PP != pkm.GetMovePP(pkm.Move2, 0) || pkm.Move3_PP != pkm.GetMovePP(pkm.Move3, 0) || pkm.Move4_PP != pkm.GetMovePP(pkm.Move4, 0)) - data.AddLine(GetInvalid(LEggPP, Egg)); - - var enc = data.EncounterMatch; - if (!EggStateLegality.GetIsEggHatchCyclesValid(pkm, enc)) - data.AddLine(GetInvalid(LEggHatchCycles, Egg)); - - if (pkm.Format >= 6 && enc is EncounterEgg && !MovesMatchRelearn(pkm)) - { - var moves = string.Join(", ", ParseSettings.GetMoveNames(pkm.Moves)); - var msg = string.Format(LMoveFExpect_0, moves); - data.AddLine(GetInvalid(msg, Egg)); - } - - if (pkm is ITechRecord8 pk8) - { - if (pk8.GetMoveRecordFlagAny()) - data.AddLine(GetInvalid(LEggRelearnFlags, Egg)); - if (pkm.StatNature != pkm.Nature) - data.AddLine(GetInvalid(LEggNature, Egg)); - } - } - - private static bool MovesMatchRelearn(PKM pkm) - { - if (pkm.Move1 != pkm.RelearnMove1) - return false; - if (pkm.Move2 != pkm.RelearnMove2) - return false; - if (pkm.Move3 != pkm.RelearnMove3) - return false; - if (pkm.Move4 != pkm.RelearnMove4) - return false; - return true; - } - - private static void VerifyFatefulMysteryGift(LegalityAnalysis data, MysteryGift g) - { - var pkm = data.pkm; - if (g is PGF {IsShiny: true}) - { - var Info = data.Info; - Info.PIDIV = MethodFinder.Analyze(pkm); - if (Info.PIDIV.Type != PIDType.G5MGShiny && pkm.Egg_Location != Locations.LinkTrade5) - data.AddLine(GetInvalid(LPIDTypeMismatch, PID)); - } - - bool shouldHave = GetFatefulState(g); - var result = pkm.FatefulEncounter == shouldHave - ? GetValid(LFatefulMystery, Fateful) - : GetInvalid(LFatefulMysteryMissing, Fateful); - data.AddLine(result); - } - - private static bool GetFatefulState(MysteryGift g) - { - if (g is WC6 {IsLinkGift: true}) - return false; // Pokémon Link fake-gifts do not have Fateful - return true; - } - - private static void VerifyReceivability(LegalityAnalysis data, MysteryGift g) - { - var pkm = data.pkm; - switch (g) - { - case WC6 wc6 when !wc6.CanBeReceivedByVersion(pkm.Version) && !pkm.WasTradedEgg: - case WC7 wc7 when !wc7.CanBeReceivedByVersion(pkm.Version) && !pkm.WasTradedEgg: - case WC8 wc8 when !wc8.CanBeReceivedByVersion(pkm.Version): - case WB8 wb8 when !wb8.CanBeReceivedByVersion(pkm.Version, pkm): - case WA8 wa8 when !wa8.CanBeReceivedByVersion(pkm.Version, pkm): - data.AddLine(GetInvalid(LEncGiftVersionNotDistributed, GameOrigin)); - return; - case WC6 wc6 when wc6.RestrictLanguage != 0 && pkm.Language != wc6.RestrictLanguage: - data.AddLine(GetInvalid(string.Format(LOTLanguage, wc6.RestrictLanguage, pkm.Language), CheckIdentifier.Language)); - return; - case WC7 wc7 when wc7.RestrictLanguage != 0 && pkm.Language != wc7.RestrictLanguage: - data.AddLine(GetInvalid(string.Format(LOTLanguage, wc7.RestrictLanguage, pkm.Language), CheckIdentifier.Language)); - return; - } - } - - private static void VerifyWC3Shiny(LegalityAnalysis data, WC3 g3) - { - // check for shiny locked gifts - if (!g3.Shiny.IsValid(data.pkm)) - data.AddLine(GetInvalid(LEncGiftShinyMismatch, Fateful)); - } - - private static void VerifyFatefulIngameActive(LegalityAnalysis data) - { - var pkm = data.pkm; - var result = pkm.FatefulEncounter - ? GetValid(LFateful, Fateful) - : GetInvalid(LFatefulMissing, Fateful); - data.AddLine(result); - } - - public void VerifyVersionEvolution(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.Format < 7 || data.EncounterMatch.Species == pkm.Species) - return; - - // No point using the evolution tree. Just handle certain species. - switch (pkm.Species) - { - case (int)Species.Lycanroc when pkm.Format == 7 && ((pkm.Form == 0 && Moon()) || (pkm.Form == 1 && Sun())): - case (int)Species.Solgaleo when Moon(): - case (int)Species.Lunala when Sun(): - bool Sun() => (pkm.Version & 1) == 0; - bool Moon() => (pkm.Version & 1) == 1; - if (pkm.IsUntraded) - data.AddLine(GetInvalid(LEvoTradeRequired, Evolution)); - break; - } - } - - private static void VerifyFullness(LegalityAnalysis data, PKM pkm) - { - if (pkm.IsEgg) - { - if (pkm.Fullness != 0) - data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter)); - if (pkm.Enjoyment != 0) - data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, "0"), Encounter)); - return; - } - - if (pkm.Format >= 8) - { - if (pkm.Fullness > 245) // Exiting camp is -10 - data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "<=245"), Encounter)); - else if (pkm.Fullness is not 0 && pkm is not PK8) - data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter)); - - if (pkm.Enjoyment != 0) - data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, "0"), Encounter)); - return; - } - - if (pkm.Format != 6 || !pkm.IsUntraded || pkm.XY) - return; - - // OR/AS PK6 - if (pkm.Fullness == 0) - return; - if (pkm.Species != data.EncounterMatch.Species) - return; // evolved - - if (Unfeedable.Contains(pkm.Species)) - data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter)); - } - - private static readonly HashSet Unfeedable = new() - { - (int)Species.Metapod, - (int)Species.Kakuna, - (int)Species.Pineco, - (int)Species.Silcoon, - (int)Species.Cascoon, - (int)Species.Shedinja, - (int)Species.Spewpa, - }; - - private static void VerifyBelugaStats(LegalityAnalysis data, PB7 pb7) - { - VerifyAbsoluteSizes(data, pb7); - if (pb7.Stat_CP != pb7.CalcCP && !IsStarterLGPE(pb7)) - data.AddLine(GetInvalid(LStatIncorrectCP, Encounter)); - } - - private static void VerifyAbsoluteSizes(LegalityAnalysis data, IScaledSizeValue obj) - { - // ReSharper disable once CompareOfFloatsByEqualityOperator -- THESE MUST MATCH EXACTLY - if (obj.HeightAbsolute != obj.CalcHeightAbsolute) - data.AddLine(GetInvalid(LStatIncorrectHeight, Encounter)); - // ReSharper disable once CompareOfFloatsByEqualityOperator -- THESE MUST MATCH EXACTLY - if (obj.WeightAbsolute != obj.CalcWeightAbsolute) - data.AddLine(GetInvalid(LStatIncorrectWeight, Encounter)); - } - - private static bool IsStarterLGPE(ISpeciesForm pk) => pk.Species switch - { - (int)Species.Pikachu when pk.Form == 8 => true, - (int)Species.Eevee when pk.Form == 1 => true, - _ => false, - }; - - private void VerifySWSHStats(LegalityAnalysis data, PK8 pk8) - { - var social = pk8.Sociability; - if (pk8.IsEgg) - { - if (social != 0) - data.AddLine(GetInvalid(LMemorySocialZero, Encounter)); - } - else if (social > byte.MaxValue) - { - data.AddLine(GetInvalid(string.Format(LMemorySocialTooHigh_0, byte.MaxValue), Encounter)); - } - - VerifyStatNature(data, pk8); - - if (!pk8.IsBattleVersionValid(data.Info.EvoChainsAllGens)) - data.AddLine(GetInvalid(LStatBattleVersionInvalid)); - - var enc = data.EncounterMatch; - bool originGMax = enc is IGigantamax {CanGigantamax: true}; - if (originGMax != pk8.CanGigantamax) - { - bool ok = !pk8.IsEgg && pk8.CanToggleGigantamax(pk8.Species, pk8.Form, enc.Species, enc.Form); - var chk = ok ? GetValid(LStatGigantamaxValid) : GetInvalid(LStatGigantamaxInvalid); - data.AddLine(chk); - } - - if (pk8.DynamaxLevel != 0) - { - if (!pk8.CanHaveDynamaxLevel(pk8) || pk8.DynamaxLevel > 10) - data.AddLine(GetInvalid(LStatDynamaxInvalid)); - } - - PersonalInfo? pi = null; - for (int i = 0; i < PersonalInfoSWSH.CountTR; i++) - { - if (!pk8.GetMoveRecordFlag(i)) - continue; - if ((pi ??= pk8.PersonalInfo).TMHM[i + 100]) - continue; - - // Calyrex-0 can have TR flags for Calyrex-1/2 after it has force unlearned them. - // Re-fusing can be reacquire the move via relearner, rather than needing another TR. - // Calyrex-0 cannot reacquire the move via relearner, even though the TR is checked off in the TR list. - if (pk8.Species == (int) Species.Calyrex) - { - var form = pk8.Form; - // Check if another alt form can learn the TR - if ((form != 1 && CanLearnTR((int) Species.Calyrex, 1, i)) || (form != 2 && CanLearnTR((int) Species.Calyrex, 2, i))) - continue; - } - - data.AddLine(GetInvalid(string.Format(LMoveSourceTR, ParseSettings.MoveStrings[Legal.TMHM_SWSH[i + PersonalInfoSWSH.CountTM]]))); - } - - if (CheckHeightWeightOdds(data.EncounterMatch) && pk8.HeightScalar == 0 && pk8.WeightScalar == 0 && ParseSettings.ZeroHeightWeight != Severity.Valid) - data.AddLine(Get(LStatInvalidHeightWeight, ParseSettings.ZeroHeightWeight, Encounter)); - } - - private void VerifyPLAStats(LegalityAnalysis data, PA8 pa8) - { - VerifyAbsoluteSizes(data, pa8); - if (!pa8.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8)) - { - var affix = pa8.AffixedRibbon; - if (affix != -1) // None - data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix))); - } - - var social = pa8.Sociability; - if (social != 0) - data.AddLine(GetInvalid(LMemorySocialZero, Encounter)); - - VerifyStatNature(data, pa8); - - if (!pa8.IsBattleVersionValid(data.Info.EvoChainsAllGens)) - data.AddLine(GetInvalid(LStatBattleVersionInvalid)); - - if (pa8.CanGigantamax) - data.AddLine(GetInvalid(LStatGigantamaxInvalid)); - - if (pa8.DynamaxLevel != 0) - data.AddLine(GetInvalid(LStatDynamaxInvalid)); - - if (pa8.GetMoveRecordFlagAny() && !pa8.IsEgg) // already checked for eggs - data.AddLine(GetInvalid(LEggRelearnFlags)); - - if (CheckHeightWeightOdds(data.EncounterMatch) && pa8.HeightScalar == 0 && pa8.WeightScalar == 0 && ParseSettings.ZeroHeightWeight != Severity.Valid) - data.AddLine(Get(LStatInvalidHeightWeight, ParseSettings.ZeroHeightWeight, Encounter)); - } - - private void VerifyBDSPStats(LegalityAnalysis data, PB8 pb8) - { - if (!pb8.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8)) - { - var affix = pb8.AffixedRibbon; - if (affix != -1) // None - data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix))); - } - - var social = pb8.Sociability; - if (social != 0) - data.AddLine(GetInvalid(LMemorySocialZero, Encounter)); - - if (pb8.IsDprIllegal) - data.AddLine(GetInvalid(LTransferFlagIllegal)); - if (pb8.Species is (int)Species.Spinda or (int)Species.Nincada && !pb8.BDSP) - data.AddLine(GetInvalid(LTransferNotPossible)); - if (pb8.Species is (int)Species.Spinda && pb8.Tracker != 0) - data.AddLine(GetInvalid(LTransferTrackerShouldBeZero)); - - VerifyStatNature(data, pb8); - - if (!pb8.IsBattleVersionValid(data.Info.EvoChainsAllGens)) - data.AddLine(GetInvalid(LStatBattleVersionInvalid)); - - if (pb8.CanGigantamax) - data.AddLine(GetInvalid(LStatGigantamaxInvalid)); - - if (pb8.DynamaxLevel != 0) - data.AddLine(GetInvalid(LStatDynamaxInvalid)); - - if (pb8.GetMoveRecordFlagAny() && !pb8.IsEgg) // already checked for eggs - data.AddLine(GetInvalid(LEggRelearnFlags)); - - if (CheckHeightWeightOdds(data.EncounterMatch) && pb8.HeightScalar == 0 && pb8.WeightScalar == 0 && ParseSettings.ZeroHeightWeight != Severity.Valid) - data.AddLine(Get(LStatInvalidHeightWeight, ParseSettings.ZeroHeightWeight, Encounter)); - } - - private static bool CheckHeightWeightOdds(IEncounterTemplate enc) - { - if (enc.Generation < 8) - return false; - - if (GameVersion.BDSP.Contains(enc.Version) || GameVersion.PLA == enc.Version) - return true; - - if (enc is WC8 { IsHOMEGift: true }) - return false; - if (GameVersion.SWSH.Contains(enc.Version)) - return true; - return false; - } - - private void VerifyStatNature(LegalityAnalysis data, PKM pk) - { - var sn = pk.StatNature; - if (sn == pk.Nature) - return; - // Only allow Serious nature (12); disallow all other neutral natures. - if (sn != 12 && (sn > 24 || sn % 6 == 0)) - data.AddLine(GetInvalid(LStatNatureInvalid)); - } - - private static bool CanLearnTR(int species, int form, int tr) - { - var pi = PersonalTable.SWSH.GetFormEntry(species, form); - return pi.TMHM[tr + PersonalInfoSWSH.CountTM]; + var first = Type_A_Match ? GetValid(LG1TypeMatch1) : GetInvalid(LG1Type1Fail); + var second = Type_B_Match || (ParseSettings.AllowGBCartEra && ((species is (int)Species.Magnemite or (int)Species.Magneton) && Type_B == 9)) // Steel Magnemite via Stadium2 + ? GetValid(LG1TypeMatch2) : GetInvalid(LG1Type2Fail); + data.AddLine(first); + data.AddLine(second); } } + + private void VerifyMiscG1CatchRate(LegalityAnalysis data, PK1 pk1) + { + var catch_rate = pk1.Catch_Rate; + var tradeback = GBRestrictions.IsTimeCapsuleTransferred(pk1, data.Info.Moves, data.EncounterMatch); + var result = tradeback is TimeCapsuleEvaluation.NotTransferred or TimeCapsuleEvaluation.BadCatchRate + ? GetWasNotTradeback(tradeback) + : GetWasTradeback(tradeback); + data.AddLine(result); + + CheckResult GetWasTradeback(TimeCapsuleEvaluation timeCapsuleEvalution) + { + if (catch_rate == 0 || Legal.HeldItems_GSC.Contains((ushort)catch_rate)) + return GetValid(LG1CatchRateMatchTradeback); + if (timeCapsuleEvalution == TimeCapsuleEvaluation.BadCatchRate) + return GetInvalid(LG1CatchRateItem); + + return GetWasNotTradeback(timeCapsuleEvalution); + } + + CheckResult GetWasNotTradeback(TimeCapsuleEvaluation timeCapsuleEvalution) + { + if (data.Info.Moves.Any(z => z.Generation == 2)) + return GetInvalid(LG1CatchRateItem); + var e = data.EncounterMatch; + if (e is EncounterStatic1E {Version: GameVersion.Stadium} or EncounterTrade1) + return GetValid(LG1CatchRateMatchPrevious); // Encounters detected by the catch rate, cant be invalid if match this encounters + + int species = pk1.Species; + if (GBRestrictions.Species_NotAvailable_CatchRate.Contains(species) && catch_rate == PersonalTable.RB[species].CatchRate) + { + if (species != (int) Species.Dragonite || catch_rate != 45 || !e.Version.Contains(GameVersion.YW)) + return GetInvalid(LG1CatchRateEvo); + } + if (!GBRestrictions.RateMatchesEncounter(e.Species, e.Version, catch_rate)) + return GetInvalid(timeCapsuleEvalution == TimeCapsuleEvaluation.Transferred12 ? LG1CatchRateChain : LG1CatchRateNone); + return GetValid(LG1CatchRateMatchPrevious); + } + } + + private static void VerifyMiscFatefulEncounter(LegalityAnalysis data) + { + var pk = data.Entity; + var enc = data.EncounterMatch; + switch (enc) + { + case WC3 {Fateful: true} w: + if (w.IsEgg) + { + // Eggs hatched in RS clear the obedience flag! + // Hatching in Gen3 doesn't change the origin version. + if (pk.Format != 3) + return; // possible hatched in either game, don't bother checking + if (pk.Met_Location <= 087) // hatched in RS or Emerald + return; // possible hatched in either game, don't bother checking + // else, ensure fateful is active (via below) + } + VerifyFatefulIngameActive(data); + VerifyWC3Shiny(data, w); + return; + case WC3 w: + if (w.Version == GameVersion.XD) + return; // Can have either state + VerifyWC3Shiny(data, w); + break; + case MysteryGift g: // WC3 handled above + VerifyReceivability(data, g); + VerifyFatefulMysteryGift(data, g); + return; + case EncounterStatic {Fateful: true}: // ingame fateful + case EncounterSlot3PokeSpot: // ingame pokespot + case EncounterTrade4RanchSpecial: // ranch varied PID + VerifyFatefulIngameActive(data); + return; + } + if (pk.FatefulEncounter) + data.AddLine(GetInvalid(LFatefulInvalid, Fateful)); + } + + private static void VerifyMiscMovePP(LegalityAnalysis data) + { + var pk = data.Entity; + + if (!Legal.IsPPUpAvailable(pk)) // No PP Ups + { + if (pk.Move1_PPUps is not 0) + data.AddLine(GetInvalid(string.Format(LMovePPUpsTooHigh_0, 1), CurrentMove)); + if (pk.Move2_PPUps is not 0) + data.AddLine(GetInvalid(string.Format(LMovePPUpsTooHigh_0, 2), CurrentMove)); + if (pk.Move3_PPUps is not 0) + data.AddLine(GetInvalid(string.Format(LMovePPUpsTooHigh_0, 3), CurrentMove)); + if (pk.Move4_PPUps is not 0) + data.AddLine(GetInvalid(string.Format(LMovePPUpsTooHigh_0, 4), CurrentMove)); + } + + if (pk.Move1_PP > pk.GetMovePP(pk.Move1, pk.Move1_PPUps)) + data.AddLine(GetInvalid(string.Format(LMovePPTooHigh_0, 1), CurrentMove)); + if (pk.Move2_PP > pk.GetMovePP(pk.Move2, pk.Move2_PPUps)) + data.AddLine(GetInvalid(string.Format(LMovePPTooHigh_0, 2), CurrentMove)); + if (pk.Move3_PP > pk.GetMovePP(pk.Move3, pk.Move3_PPUps)) + data.AddLine(GetInvalid(string.Format(LMovePPTooHigh_0, 3), CurrentMove)); + if (pk.Move4_PP > pk.GetMovePP(pk.Move4, pk.Move4_PPUps)) + data.AddLine(GetInvalid(string.Format(LMovePPTooHigh_0, 4), CurrentMove)); + } + + private static void VerifyMiscEggCommon(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.Move1_PPUps > 0 || pk.Move2_PPUps > 0 || pk.Move3_PPUps > 0 || pk.Move4_PPUps > 0) + data.AddLine(GetInvalid(LEggPPUp, Egg)); + if (pk.Move1_PP != pk.GetMovePP(pk.Move1, 0) || pk.Move2_PP != pk.GetMovePP(pk.Move2, 0) || pk.Move3_PP != pk.GetMovePP(pk.Move3, 0) || pk.Move4_PP != pk.GetMovePP(pk.Move4, 0)) + data.AddLine(GetInvalid(LEggPP, Egg)); + + var enc = data.EncounterMatch; + if (!EggStateLegality.GetIsEggHatchCyclesValid(pk, enc)) + data.AddLine(GetInvalid(LEggHatchCycles, Egg)); + + if (pk.Format >= 6 && enc is EncounterEgg && !MovesMatchRelearn(pk)) + { + var moves = string.Join(", ", ParseSettings.GetMoveNames(pk.Moves)); + var msg = string.Format(LMoveFExpect_0, moves); + data.AddLine(GetInvalid(msg, Egg)); + } + + if (pk is ITechRecord8 pk8) + { + if (pk8.GetMoveRecordFlagAny()) + data.AddLine(GetInvalid(LEggRelearnFlags, Egg)); + if (pk.StatNature != pk.Nature) + data.AddLine(GetInvalid(LEggNature, Egg)); + } + } + + private static bool MovesMatchRelearn(PKM pk) + { + if (pk.Move1 != pk.RelearnMove1) + return false; + if (pk.Move2 != pk.RelearnMove2) + return false; + if (pk.Move3 != pk.RelearnMove3) + return false; + if (pk.Move4 != pk.RelearnMove4) + return false; + return true; + } + + private static void VerifyFatefulMysteryGift(LegalityAnalysis data, MysteryGift g) + { + var pk = data.Entity; + if (g is PGF {IsShiny: true}) + { + var Info = data.Info; + Info.PIDIV = MethodFinder.Analyze(pk); + if (Info.PIDIV.Type != PIDType.G5MGShiny && pk.Egg_Location != Locations.LinkTrade5) + data.AddLine(GetInvalid(LPIDTypeMismatch, PID)); + } + + bool shouldHave = GetFatefulState(g); + var result = pk.FatefulEncounter == shouldHave + ? GetValid(LFatefulMystery, Fateful) + : GetInvalid(LFatefulMysteryMissing, Fateful); + data.AddLine(result); + } + + private static bool GetFatefulState(MysteryGift g) + { + if (g is WC6 {IsLinkGift: true}) + return false; // Pokémon Link fake-gifts do not have Fateful + return true; + } + + private static void VerifyReceivability(LegalityAnalysis data, MysteryGift g) + { + var pk = data.Entity; + switch (g) + { + case WC6 wc6 when !wc6.CanBeReceivedByVersion(pk.Version) && !pk.WasTradedEgg: + case WC7 wc7 when !wc7.CanBeReceivedByVersion(pk.Version) && !pk.WasTradedEgg: + case WC8 wc8 when !wc8.CanBeReceivedByVersion(pk.Version): + case WB8 wb8 when !wb8.CanBeReceivedByVersion(pk.Version, pk): + case WA8 wa8 when !wa8.CanBeReceivedByVersion(pk.Version, pk): + data.AddLine(GetInvalid(LEncGiftVersionNotDistributed, GameOrigin)); + return; + case WC6 wc6 when wc6.RestrictLanguage != 0 && pk.Language != wc6.RestrictLanguage: + data.AddLine(GetInvalid(string.Format(LOTLanguage, wc6.RestrictLanguage, pk.Language), CheckIdentifier.Language)); + return; + case WC7 wc7 when wc7.RestrictLanguage != 0 && pk.Language != wc7.RestrictLanguage: + data.AddLine(GetInvalid(string.Format(LOTLanguage, wc7.RestrictLanguage, pk.Language), CheckIdentifier.Language)); + return; + } + } + + private static void VerifyWC3Shiny(LegalityAnalysis data, WC3 g3) + { + // check for shiny locked gifts + if (!g3.Shiny.IsValid(data.Entity)) + data.AddLine(GetInvalid(LEncGiftShinyMismatch, Fateful)); + } + + private static void VerifyFatefulIngameActive(LegalityAnalysis data) + { + var pk = data.Entity; + var result = pk.FatefulEncounter + ? GetValid(LFateful, Fateful) + : GetInvalid(LFatefulMissing, Fateful); + data.AddLine(result); + } + + public void VerifyVersionEvolution(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.Format < 7 || data.EncounterMatch.Species == pk.Species) + return; + + // No point using the evolution tree. Just handle certain species. + switch (pk.Species) + { + case (int)Species.Lycanroc when pk.Format == 7 && ((pk.Form == 0 && Moon()) || (pk.Form == 1 && Sun())): + case (int)Species.Solgaleo when Moon(): + case (int)Species.Lunala when Sun(): + bool Sun() => (pk.Version & 1) == 0; + bool Moon() => (pk.Version & 1) == 1; + if (pk.IsUntraded) + data.AddLine(GetInvalid(LEvoTradeRequired, Evolution)); + break; + } + } + + private static void VerifyFullness(LegalityAnalysis data, PKM pk) + { + if (pk.IsEgg) + { + if (pk.Fullness != 0) + data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter)); + if (pk.Enjoyment != 0) + data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, "0"), Encounter)); + return; + } + + if (pk.Format >= 8) + { + if (pk.Fullness > 245) // Exiting camp is -10 + data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "<=245"), Encounter)); + else if (pk.Fullness is not 0 && pk is not PK8) + data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter)); + + if (pk.Enjoyment != 0) + data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, "0"), Encounter)); + return; + } + + if (pk.Format != 6 || !pk.IsUntraded || pk.XY) + return; + + // OR/AS PK6 + if (pk.Fullness == 0) + return; + if (pk.Species != data.EncounterMatch.Species) + return; // evolved + + if (Unfeedable.Contains(pk.Species)) + data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter)); + } + + private static readonly HashSet Unfeedable = new() + { + (int)Species.Metapod, + (int)Species.Kakuna, + (int)Species.Pineco, + (int)Species.Silcoon, + (int)Species.Cascoon, + (int)Species.Shedinja, + (int)Species.Spewpa, + }; + + private static void VerifyBelugaStats(LegalityAnalysis data, PB7 pb7) + { + VerifyAbsoluteSizes(data, pb7); + if (pb7.Stat_CP != pb7.CalcCP && !IsStarterLGPE(pb7)) + data.AddLine(GetInvalid(LStatIncorrectCP, Encounter)); + } + + private static void VerifyAbsoluteSizes(LegalityAnalysis data, IScaledSizeValue obj) + { + // ReSharper disable once CompareOfFloatsByEqualityOperator -- THESE MUST MATCH EXACTLY + if (obj.HeightAbsolute != obj.CalcHeightAbsolute) + data.AddLine(GetInvalid(LStatIncorrectHeight, Encounter)); + // ReSharper disable once CompareOfFloatsByEqualityOperator -- THESE MUST MATCH EXACTLY + if (obj.WeightAbsolute != obj.CalcWeightAbsolute) + data.AddLine(GetInvalid(LStatIncorrectWeight, Encounter)); + } + + private static bool IsStarterLGPE(ISpeciesForm pk) => pk.Species switch + { + (int)Species.Pikachu when pk.Form == 8 => true, + (int)Species.Eevee when pk.Form == 1 => true, + _ => false, + }; + + private void VerifySWSHStats(LegalityAnalysis data, PK8 pk8) + { + var social = pk8.Sociability; + if (pk8.IsEgg) + { + if (social != 0) + data.AddLine(GetInvalid(LMemorySocialZero, Encounter)); + } + else if (social > byte.MaxValue) + { + data.AddLine(GetInvalid(string.Format(LMemorySocialTooHigh_0, byte.MaxValue), Encounter)); + } + + VerifyStatNature(data, pk8); + + if (!pk8.IsBattleVersionValid(data.Info.EvoChainsAllGens)) + data.AddLine(GetInvalid(LStatBattleVersionInvalid)); + + var enc = data.EncounterMatch; + bool originGMax = enc is IGigantamax {CanGigantamax: true}; + if (originGMax != pk8.CanGigantamax) + { + bool ok = !pk8.IsEgg && pk8.CanToggleGigantamax(pk8.Species, pk8.Form, enc.Species, enc.Form); + var chk = ok ? GetValid(LStatGigantamaxValid) : GetInvalid(LStatGigantamaxInvalid); + data.AddLine(chk); + } + + if (pk8.DynamaxLevel != 0) + { + if (!pk8.CanHaveDynamaxLevel(pk8) || pk8.DynamaxLevel > 10) + data.AddLine(GetInvalid(LStatDynamaxInvalid)); + } + + PersonalInfo? pi = null; + for (int i = 0; i < PersonalInfoSWSH.CountTR; i++) + { + if (!pk8.GetMoveRecordFlag(i)) + continue; + if ((pi ??= pk8.PersonalInfo).TMHM[i + 100]) + continue; + + // Calyrex-0 can have TR flags for Calyrex-1/2 after it has force unlearned them. + // Re-fusing can be reacquire the move via relearner, rather than needing another TR. + // Calyrex-0 cannot reacquire the move via relearner, even though the TR is checked off in the TR list. + if (pk8.Species == (int) Species.Calyrex) + { + var form = pk8.Form; + // Check if another alt form can learn the TR + if ((form != 1 && CanLearnTR((int) Species.Calyrex, 1, i)) || (form != 2 && CanLearnTR((int) Species.Calyrex, 2, i))) + continue; + } + + data.AddLine(GetInvalid(string.Format(LMoveSourceTR, ParseSettings.MoveStrings[Legal.TMHM_SWSH[i + PersonalInfoSWSH.CountTM]]))); + } + + if (CheckHeightWeightOdds(data.EncounterMatch) && pk8.HeightScalar == 0 && pk8.WeightScalar == 0 && ParseSettings.ZeroHeightWeight != Severity.Valid) + data.AddLine(Get(LStatInvalidHeightWeight, ParseSettings.ZeroHeightWeight, Encounter)); + } + + private void VerifyPLAStats(LegalityAnalysis data, PA8 pa8) + { + VerifyAbsoluteSizes(data, pa8); + if (!pa8.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8)) + { + var affix = pa8.AffixedRibbon; + if (affix != -1) // None + data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix))); + } + + var social = pa8.Sociability; + if (social != 0) + data.AddLine(GetInvalid(LMemorySocialZero, Encounter)); + + VerifyStatNature(data, pa8); + + if (!pa8.IsBattleVersionValid(data.Info.EvoChainsAllGens)) + data.AddLine(GetInvalid(LStatBattleVersionInvalid)); + + if (pa8.CanGigantamax) + data.AddLine(GetInvalid(LStatGigantamaxInvalid)); + + if (pa8.DynamaxLevel != 0) + data.AddLine(GetInvalid(LStatDynamaxInvalid)); + + if (pa8.GetMoveRecordFlagAny() && !pa8.IsEgg) // already checked for eggs + data.AddLine(GetInvalid(LEggRelearnFlags)); + + if (CheckHeightWeightOdds(data.EncounterMatch) && pa8.HeightScalar == 0 && pa8.WeightScalar == 0 && ParseSettings.ZeroHeightWeight != Severity.Valid) + data.AddLine(Get(LStatInvalidHeightWeight, ParseSettings.ZeroHeightWeight, Encounter)); + } + + private void VerifyBDSPStats(LegalityAnalysis data, PB8 pb8) + { + if (!pb8.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8)) + { + var affix = pb8.AffixedRibbon; + if (affix != -1) // None + data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix))); + } + + var social = pb8.Sociability; + if (social != 0) + data.AddLine(GetInvalid(LMemorySocialZero, Encounter)); + + if (pb8.IsDprIllegal) + data.AddLine(GetInvalid(LTransferFlagIllegal)); + if (pb8.Species is (int)Species.Spinda or (int)Species.Nincada && !pb8.BDSP) + data.AddLine(GetInvalid(LTransferNotPossible)); + if (pb8.Species is (int)Species.Spinda && pb8.Tracker != 0) + data.AddLine(GetInvalid(LTransferTrackerShouldBeZero)); + + VerifyStatNature(data, pb8); + + if (!pb8.IsBattleVersionValid(data.Info.EvoChainsAllGens)) + data.AddLine(GetInvalid(LStatBattleVersionInvalid)); + + if (pb8.CanGigantamax) + data.AddLine(GetInvalid(LStatGigantamaxInvalid)); + + if (pb8.DynamaxLevel != 0) + data.AddLine(GetInvalid(LStatDynamaxInvalid)); + + if (pb8.GetMoveRecordFlagAny() && !pb8.IsEgg) // already checked for eggs + data.AddLine(GetInvalid(LEggRelearnFlags)); + + if (CheckHeightWeightOdds(data.EncounterMatch) && pb8.HeightScalar == 0 && pb8.WeightScalar == 0 && ParseSettings.ZeroHeightWeight != Severity.Valid) + data.AddLine(Get(LStatInvalidHeightWeight, ParseSettings.ZeroHeightWeight, Encounter)); + } + + private static bool CheckHeightWeightOdds(IEncounterTemplate enc) + { + if (enc.Generation < 8) + return false; + + if (GameVersion.BDSP.Contains(enc.Version) || GameVersion.PLA == enc.Version) + return true; + + if (enc is WC8 { IsHOMEGift: true }) + return false; + if (GameVersion.SWSH.Contains(enc.Version)) + return true; + return false; + } + + private void VerifyStatNature(LegalityAnalysis data, PKM pk) + { + var sn = pk.StatNature; + if (sn == pk.Nature) + return; + // Only allow Serious nature (12); disallow all other neutral natures. + if (sn != 12 && (sn > 24 || sn % 6 == 0)) + data.AddLine(GetInvalid(LStatNatureInvalid)); + } + + private static bool CanLearnTR(int species, int form, int tr) + { + var pi = PersonalTable.SWSH.GetFormEntry(species, form); + return pi.TMHM[tr + PersonalInfoSWSH.CountTM]; + } } diff --git a/PKHeX.Core/Legality/Verifiers/NHarmoniaVerifier.cs b/PKHeX.Core/Legality/Verifiers/NHarmoniaVerifier.cs index 8a1a8242c..2f0ef727a 100644 --- a/PKHeX.Core/Legality/Verifiers/NHarmoniaVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/NHarmoniaVerifier.cs @@ -1,49 +1,48 @@ using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the data. +/// +public sealed class NHarmoniaVerifier : Verifier { - /// - /// Verifies the data. - /// - public sealed class NHarmoniaVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Trainer; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Trainer; - - public override void Verify(LegalityAnalysis data) + var pk = data.Entity; + bool checksRequired = data.EncounterMatch is EncounterStatic5N; + if (pk is PK5 pk5) { - var pkm = data.pkm; - bool checksRequired = data.EncounterMatch is EncounterStatic5N; - if (pkm is PK5 pk5) - { - bool has = pk5.NSparkle; - if (checksRequired && !has) - data.AddLine(GetInvalid(LG5SparkleRequired, CheckIdentifier.Fateful)); - if (!checksRequired && has) - data.AddLine(GetInvalid(LG5SparkleInvalid, CheckIdentifier.Fateful)); - } - - if (!checksRequired) - return; - - if (pkm.OT_Gender != 0) - data.AddLine(GetInvalid(LG5OTGenderN, CheckIdentifier.Trainer)); - if (pkm.IVTotal != 30*6) - data.AddLine(GetInvalid(LG5IVAll30, CheckIdentifier.IVs)); - if (!VerifyNsPKMOTValid(pkm)) - data.AddLine(GetInvalid(LG5ID_N, CheckIdentifier.Trainer)); - if (pkm.IsShiny) - data.AddLine(GetInvalid(LG5PIDShinyN, CheckIdentifier.Shiny)); + bool has = pk5.NSparkle; + if (checksRequired && !has) + data.AddLine(GetInvalid(LG5SparkleRequired, CheckIdentifier.Fateful)); + if (!checksRequired && has) + data.AddLine(GetInvalid(LG5SparkleInvalid, CheckIdentifier.Fateful)); } - private static bool VerifyNsPKMOTValid(PKM pkm) - { - if (pkm.TID != 00002 || pkm.SID != 00000) - return false; - var ot = pkm.OT_Name; - if (ot.Length != 1) - return false; - var c = EncounterStatic5N.GetOT(pkm.Language); - return c == ot; - } + if (!checksRequired) + return; + + if (pk.OT_Gender != 0) + data.AddLine(GetInvalid(LG5OTGenderN, CheckIdentifier.Trainer)); + if (pk.IVTotal != 30*6) + data.AddLine(GetInvalid(LG5IVAll30, CheckIdentifier.IVs)); + if (!VerifyNsPKMOTValid(pk)) + data.AddLine(GetInvalid(LG5ID_N, CheckIdentifier.Trainer)); + if (pk.IsShiny) + data.AddLine(GetInvalid(LG5PIDShinyN, CheckIdentifier.Shiny)); + } + + private static bool VerifyNsPKMOTValid(PKM pk) + { + if (pk.TID != 00002 || pk.SID != 00000) + return false; + var ot = pk.OT_Name; + if (ot.Length != 1) + return false; + var c = EncounterStatic5N.GetOT(pk.Language); + return c == ot; } } diff --git a/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs b/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs index 1e93c65da..f2580c315 100644 --- a/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs @@ -4,548 +4,547 @@ using static PKHeX.Core.LegalityCheckStrings; using static PKHeX.Core.LanguageID; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the . +/// +public sealed class NicknameVerifier : Verifier { - /// - /// Verifies the . - /// - public sealed class NicknameVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Nickname; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Nickname; + var pk = data.Entity; - public override void Verify(LegalityAnalysis data) + // If the Pokémon is not nicknamed, it should match one of the language strings. + var nickname = pk.Nickname; + if (nickname.Length == 0) { - var pkm = data.pkm; + data.AddLine(GetInvalid(LNickLengthShort)); + return; + } + if (pk.Species > SpeciesName.SpeciesLang[0].Count) + { + data.AddLine(Get(LNickLengthShort, Severity.Indeterminate)); + return; + } - // If the Pokémon is not nicknamed, it should match one of the language strings. - var nickname = pkm.Nickname; - if (nickname.Length == 0) - { - data.AddLine(GetInvalid(LNickLengthShort)); - return; - } - if (pkm.Species > SpeciesName.SpeciesLang[0].Count) - { - data.AddLine(Get(LNickLengthShort, Severity.Indeterminate)); - return; - } - - var enc = data.EncounterOriginal; - if (enc is ILangNicknamedTemplate n) - { - VerifyFixedNicknameEncounter(data, n, enc, pkm, nickname); - if (pkm.IsEgg) - VerifyNicknameEgg(data); - return; - } - - if (pkm.Format <= 7 && pkm.IsNicknamed) // can nickname afterwards - { - if (pkm.VC) - VerifyG1NicknameWithinBounds(data, nickname.AsSpan()); - else if (enc is MysteryGift {IsEgg: false}) - data.AddLine(Get(LEncGiftNicknamed, ParseSettings.NicknamedMysteryGift)); - } - - if (enc is EncounterTrade t) - { - VerifyNicknameTrade(data, t); - if (t.HasNickname) - return; - } - - if (pkm.IsEgg) - { + var enc = data.EncounterOriginal; + if (enc is ILangNicknamedTemplate n) + { + VerifyFixedNicknameEncounter(data, n, enc, pk, nickname); + if (pk.IsEgg) VerifyNicknameEgg(data); + return; + } + + if (pk.Format <= 7 && pk.IsNicknamed) // can nickname afterwards + { + if (pk.VC) + VerifyG1NicknameWithinBounds(data, nickname.AsSpan()); + else if (enc is MysteryGift {IsEgg: false}) + data.AddLine(Get(LEncGiftNicknamed, ParseSettings.NicknamedMysteryGift)); + } + + if (enc is EncounterTrade t) + { + VerifyNicknameTrade(data, t); + if (t.HasNickname) return; - } - - if (VerifyUnNicknamedEncounter(data, pkm, nickname)) - return; - - // Non-nicknamed strings have already been checked. - if (ParseSettings.CheckWordFilter && pkm.IsNicknamed) - { - if (WordFilter.IsFiltered(nickname, out string bad)) - data.AddLine(GetInvalid($"Word Filter: {bad}")); - if (TrainerNameVerifier.ContainsTooManyNumbers(nickname, data.Info.Generation)) - data.AddLine(GetInvalid("Word Filter: Too many numbers.")); - } } - private void VerifyFixedNicknameEncounter(LegalityAnalysis data, ILangNicknamedTemplate n, IEncounterTemplate enc, PKM pkm, string nickname) + if (pk.IsEgg) { - var nick = n.GetNickname(pkm.Language); - - if (string.IsNullOrWhiteSpace(nick)) - { - if (n is WC8 {IsHOMEGift: true}) - { - VerifyHomeGiftNickname(data, enc, pkm, nickname); - return; - } - - if (n.CanHandleOT(pkm.Language)) - return; - - if (n is WC3 && pkm.Format != 3) - { - // Gen3 gifts transferred to Generation 4 from another language can set the nickname flag. - var evos = data.Info.EvoChainsAllGens.Gen3; - bool matchAny = evos.Any(evo => !SpeciesName.IsNicknamedAnyLanguage(evo.Species, nickname, 3)); - if (matchAny) - return; - } - - if (pkm.IsNicknamed) - data.AddLine(Get(LEncGiftNicknamed, Severity.Invalid)); - return; - } - - if (!pkm.IsNicknamed) - { - // Check if it had a nickname at all - var orig = SpeciesName.GetSpeciesNameGeneration(enc.Species, pkm.Language, enc.Generation); - if (orig == nick) - { - // Didn't have a nickname. Ensure that the language matches the current nickname string. - if (!SpeciesName.IsNicknamed(pkm.Species, nickname, pkm.Language, pkm.Format)) - return; - } - - // Should have a nickname present. - data.AddLine(GetInvalid(LNickMatchLanguageFail)); - return; - } - - // Encounter has a nickname, and PKM should have it. - var severity = nick != nickname || !pkm.IsNicknamed ? Severity.Invalid : Severity.Valid; - data.AddLine(Get(LEncGiftNicknamed, severity)); + VerifyNicknameEgg(data); + return; } - private void VerifyHomeGiftNickname(LegalityAnalysis data, IEncounterTemplate enc, ILangNick pkm, string nickname) + if (VerifyUnNicknamedEncounter(data, pk, nickname)) + return; + + // Non-nicknamed strings have already been checked. + if (ParseSettings.CheckWordFilter && pk.IsNicknamed) { - // can nickname on redemption - if (!pkm.IsNicknamed) - return; - - // Can't nickname everything. - if (enc.Species == (int) Species.Melmetal) - { - data.AddLine(GetInvalid(LEncGiftNicknamed)); - return; - } - - // Ensure the nickname does not match species name - var orig = SpeciesName.GetSpeciesNameGeneration(enc.Species, pkm.Language, enc.Generation); - if (nickname == orig) - data.AddLine(GetInvalid(LNickMatchLanguageFlag)); - } - - private bool VerifyUnNicknamedEncounter(LegalityAnalysis data, PKM pkm, string nickname) - { - if (pkm.IsNicknamed) - { - if (data.Info.Generation >= 8) - { - // Can only nickname if it matches your language. - // Setting the nickname to the same as the species name does not set the Nickname flag (equals unmodified, no flag) - if (!SpeciesName.IsNicknamed(pkm.Species, nickname, pkm.Language, pkm.Format)) - { - data.AddLine(Get(LNickMatchLanguageFlag, Severity.Invalid)); - return true; - } - } - for (int i = 0; i < SpeciesName.SpeciesDict.Count; i++) - { - if (!SpeciesName.SpeciesDict[i].TryGetValue(nickname, out int species)) - continue; - var msg = species == pkm.Species && i != pkm.Language ? LNickMatchNoOthersFail : LNickMatchLanguageFlag; - data.AddLine(Get(msg, ParseSettings.NicknamedAnotherSpecies)); - return true; - } - if (pkm.Format <= 7 && StringConverter.HasEastAsianScriptCharacters(nickname.AsSpan()) && pkm is not PB7) // East Asian Scripts - { - data.AddLine(GetInvalid(LNickInvalidChar)); - return true; - } - if (nickname.Length > Legal.GetMaxLengthNickname(data.Info.Generation, (LanguageID)pkm.Language)) - { - int length = GetForeignNicknameLength(pkm, data.Info.EncounterOriginal, data.Info.Generation); - var severe = (length != 0 && nickname.Length <= length) ? Severity.Fishy : Severity.Invalid; - data.AddLine(Get(LNickLengthLong, severe)); - return true; - } - data.AddLine(GetValid(LNickMatchNoOthers)); - } - else if (pkm.Format < 3) - { - // pk1/pk2 IsNicknamed getter checks for match, logic should only reach here if matches. - data.AddLine(GetValid(LNickMatchLanguage)); - } - else - { - var enc = data.EncounterOriginal; - bool valid = IsNicknameValid(pkm, enc, nickname); - var result = valid ? GetValid(LNickMatchLanguage) : GetInvalid(LNickMatchLanguageFail); - data.AddLine(result); - } - return false; - } - - private static int GetForeignNicknameLength(PKM pkm, IEncounterTemplate match, int origin) - { - // HOME gifts already verified prior to reaching here. - System.Diagnostics.Debug.Assert(match is not WC8 {IsHOMEGift:true}); - - int length = 0; - if (origin is (4 or 5 or 6 or 7) && match.EggEncounter && pkm.WasTradedEgg) - length = Legal.GetMaxLengthNickname(origin, English); - - if (pkm.FatefulEncounter) - return length; - - if (pkm.Format < 8 || pkm.BDSP) - return length; - - // Can only nickname if the language matches. - var future = Legal.GetMaxLengthNickname(pkm.Format, (LanguageID)pkm.Language); - return Math.Max(length, future); - } - - private static bool IsNicknameValid(PKM pkm, IEncounterTemplate enc, string nickname) - { - int species = pkm.Species; - int format = pkm.Format; - int language = pkm.Language; - if (SpeciesName.GetSpeciesNameGeneration(species, language, format) == nickname) - return true; - - // Can't have another language name if it hasn't evolved or wasn't a language-traded egg. - // Starting in Generation 8, hatched language-traded eggs will take the Language from the trainer that hatched it. - // Also in Generation 8, evolving in a foreign language game will retain the original language as the source for the newly evolved species name. - // Transferring from Gen7->Gen8 realigns the Nickname string to the Language, if not nicknamed. - bool canHaveAnyLanguage = format <= 7 && (enc.Species != species || pkm.WasTradedEgg) && !pkm.GG; - if (canHaveAnyLanguage && !SpeciesName.IsNicknamedAnyLanguage(species, nickname, format)) - return true; - - switch (enc) - { - case WC7 wc7 when wc7.IsAshGreninjaWC7(pkm): - return true; - case ILangNick loc: - if (loc.Language != 0 && !loc.IsNicknamed && !SpeciesName.IsNicknamedAnyLanguage(species, nickname, format)) - return true; // fixed language without nickname, nice job event maker! - break; - } - - if (format == 5 && !pkm.IsNative) // transfer - { - if (canHaveAnyLanguage) - return !SpeciesName.IsNicknamedAnyLanguage(species, nickname, 4); - return SpeciesName.GetSpeciesNameGeneration(species, language, 4) == nickname; - } - - return false; - } - - private static void VerifyNicknameEgg(LegalityAnalysis data) - { - var Info = data.Info; - var pkm = data.pkm; - - bool flagState = EggStateLegality.IsNicknameFlagSet(Info.EncounterMatch, pkm); - if (pkm.IsNicknamed != flagState) - data.AddLine(GetInvalid(flagState ? LNickFlagEggYes : LNickFlagEggNo, CheckIdentifier.Egg)); - - var nick = pkm.Nickname; - if (pkm.Format == 2 && !SpeciesName.IsNicknamedAnyLanguage(0, nick, 2)) - data.AddLine(GetValid(LNickMatchLanguageEgg, CheckIdentifier.Egg)); - else if (nick != SpeciesName.GetSpeciesNameGeneration(0, pkm.Language, Info.Generation)) - data.AddLine(GetInvalid(LNickMatchLanguageEggFail, CheckIdentifier.Egg)); - else - data.AddLine(GetValid(LNickMatchLanguageEgg, CheckIdentifier.Egg)); - } - - private static void VerifyNicknameTrade(LegalityAnalysis data, EncounterTrade t) - { - switch (data.Info.Generation) - { - case 8 when t is EncounterTrade8b b: VerifyTrade8b(data, b); return; - - case 1: VerifyTrade12(data, t); return; - case 2: return; // already checked all relevant properties when fetching with getValidEncounterTradeVC2 - case 3: VerifyTrade3(data, t); return; - case 4: VerifyTrade4(data, t); return; - case 5: VerifyTrade5(data, t); return; - case 6: - case 7: - case 8: - VerifyTrade(data, t, data.pkm.Language); return; - } - } - - private void VerifyG1NicknameWithinBounds(LegalityAnalysis data, ReadOnlySpan str) - { - var pkm = data.pkm; - if (StringConverter12.GetIsG1English(str)) - { - if (str.Length > 10) - data.AddLine(GetInvalid(LNickLengthLong)); - } - else if (StringConverter12.GetIsG1Japanese(str)) - { - if (str.Length > 5) - data.AddLine(GetInvalid(LNickLengthLong)); - } - else if (pkm.Korean && StringConverter2KOR.GetIsG2Korean(str)) - { - if (str.Length > 5) - data.AddLine(GetInvalid(LNickLengthLong)); - } - else - { - data.AddLine(GetInvalid(LG1CharNick)); - } - } - - private static void VerifyTrade12(LegalityAnalysis data, EncounterTrade t) - { - var t1 = (EncounterTrade1)t; - if (!t1.IsNicknameValid(data.pkm)) - data.AddLine(GetInvalid(LEncTradeChangedNickname, CheckIdentifier.Nickname)); - if (!t1.IsTrainerNameValid(data.pkm)) - data.AddLine(GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer)); - } - - private static void VerifyTrade3(LegalityAnalysis data, EncounterTrade t) - { - var pkm = data.pkm; - int lang = pkm.Language; - if (t.Species == (int)Species.Jynx) // FRLG Jynx - lang = DetectTradeLanguageG3DANTAEJynx(pkm, lang); - VerifyTrade(data, t, lang); - } - - private static void VerifyTrade4(LegalityAnalysis data, EncounterTrade t) - { - var pkm = data.pkm; - if (pkm.TID == 1000) - { - VerifyTradeOTOnly(data, t); - return; - } - int lang = pkm.Language; - switch (t.Species) - { - case (int)Species.Pikachu: // HGSS Pikachu - lang = DetectTradeLanguageG4SurgePikachu(pkm, t, lang); - // flag korean on gen4 saves since the pkm.Language is German - FlagKoreanIncompatibleSameGenTrade(data, pkm, lang); - break; - case (int)Species.Magikarp: // DPPt Magikarp - lang = DetectTradeLanguageG4MeisterMagikarp(pkm, t, lang); - // flag korean on gen4 saves since the pkm.Language is German - FlagKoreanIncompatibleSameGenTrade(data, pkm, lang); - break; - - default: - if (t is EncounterTrade4PID && pkm.Version is ((int)GameVersion.D or (int)GameVersion.P)) // mainline DP - { - // DP English origin are Japanese lang. Can't have LanguageID 2 - if (lang == 2) - { - data.AddLine(GetInvalid(string.Format(LOTLanguage, Japanese, English), CheckIdentifier.Language)); - break; - } - - // Since two locales (JPN/ENG) can have the same LanguageID, check which we should be validating with. - if (lang == 1 && pkm.OT_Name != t.GetOT(1)) // not Japanese - lang = 2; // verify strings with English locale instead. - } - break; - } - VerifyTrade(data, t, lang); - } - - private static void VerifyTrade8b(LegalityAnalysis data, EncounterTrade8b t) - { - var pkm = data.pkm; - int lang = pkm.Language; - if (t.Species == (int)Species.Magikarp) - { - // Japanese - if (pkm.Language == (int)Japanese && pkm.OT_Name is "Diamond." or "Pearl.") - { - // Traded between players, the original OT is replaced with the above OT (version dependent) as the original OT is >6 chars in length. - VerifyTradeNickname(data, t, t.Nicknames[(int)German], pkm); - return; - } - - lang = DetectTradeLanguageG8MeisterMagikarp(pkm, t, lang); - if (lang == 0) // err - data.AddLine(GetInvalid(string.Format(LOTLanguage, $"{Japanese}/{German}", $"{(LanguageID)pkm.Language}"), CheckIdentifier.Language)); - } - VerifyTrade(data, t, lang); - } - - private static int DetectTradeLanguageG8MeisterMagikarp(PKM pkm, EncounterTrade8b t, int currentLanguageID) - { - // Receiving the trade on a German game -> Japanese LanguageID. - // Receiving the trade on any other language -> German LanguageID. - if (currentLanguageID is not ((int)Japanese or (int)German)) - return 0; - - var nick = pkm.Nickname; - var ot = pkm.OT_Name; - for (int i = 1; i < (int)ChineseT; i++) - { - if (t.Nicknames[i] != nick) - continue; - if (t.TrainerNames[i] != ot) - continue; - - // Language gets flipped to another language ID; can't be equal. - var shouldNotBe = currentLanguageID == (int)German ? German : Japanese; - return i != (int)shouldNotBe ? i : 0; - } - return 0; - } - - private static void FlagKoreanIncompatibleSameGenTrade(LegalityAnalysis data, PKM pkm, int lang) - { - if (pkm.Format != 4 || lang != (int)Korean) - return; // transferred or not appropriate - if (ParseSettings.ActiveTrainer.Language != (int)Korean && ParseSettings.ActiveTrainer.Language >= 0) - data.AddLine(GetInvalid(string.Format(LTransferOriginFInvalid0_1, L_XKorean, L_XKoreanNon), CheckIdentifier.Language)); - } - - private static int DetectTradeLanguage(string OT, EncounterTrade t, int currentLanguageID) - { - var names = t.TrainerNames; - for (int lang = 1; lang < names.Count; lang++) - { - if (names[lang] != OT) - continue; - return lang; - } - return currentLanguageID; - } - - private static int DetectTradeLanguageG3DANTAEJynx(PKM pk, int currentLanguageID) - { - if (currentLanguageID != (int)Italian) - return currentLanguageID; - - if (pk.Version == (int)GameVersion.LG) - currentLanguageID = (int)English; // translation error; OT was not localized => same as English - return currentLanguageID; - } - - private static int DetectTradeLanguageG4MeisterMagikarp(PKM pkm, EncounterTrade t, int currentLanguageID) - { - if (currentLanguageID == (int)English) - return (int)German; - - // All have German, regardless of origin version. - var lang = DetectTradeLanguage(pkm.OT_Name, t, currentLanguageID); - if (lang == (int)English) // possible collision with FR/ES/DE. Check nickname - return pkm.Nickname == t.Nicknames[(int)French] ? (int)French : (int)Spanish; // Spanish is same as English - - return lang; - } - - private static int DetectTradeLanguageG4SurgePikachu(PKM pkm, EncounterTrade t, int currentLanguageID) - { - if (currentLanguageID == (int)French) - return (int)English; - - // All have English, regardless of origin version. - var lang = DetectTradeLanguage(pkm.OT_Name, t, currentLanguageID); - if (lang == 2) // possible collision with ES/IT. Check nickname - return pkm.Nickname == t.Nicknames[(int)Italian] ? (int)Italian : (int)Spanish; - - return lang; - } - - private static void VerifyTrade5(LegalityAnalysis data, EncounterTrade t) - { - var pkm = data.pkm; - int lang = pkm.Language; - // Trades for JPN games have language ID of 0, not 1. - if (pkm.BW) - { - if (pkm.Format == 5 && lang == (int)Japanese) - data.AddLine(GetInvalid(string.Format(LOTLanguage, 0, Japanese), CheckIdentifier.Language)); - - lang = Math.Max(lang, 1); - VerifyTrade(data, t, lang); - } - else // B2W2 - { - if (t.TID is Encounters5.YancyTID or Encounters5.CurtisTID) - VerifyTradeOTOnly(data, t); - else - VerifyTrade(data, t, lang); - } - } - - private static void VerifyTradeOTOnly(LegalityAnalysis data, EncounterTrade t) - { - var result = CheckTradeOTOnly(data, t.TrainerNames); - data.AddLine(result); - } - - private static CheckResult CheckTradeOTOnly(LegalityAnalysis data, IReadOnlyList validOT) - { - var pkm = data.pkm; - if (pkm.IsNicknamed && (pkm.Format < 8 || pkm.FatefulEncounter)) - return GetInvalid(LEncTradeChangedNickname, CheckIdentifier.Nickname); - int lang = pkm.Language; - if (validOT.Count <= lang) - return GetInvalid(LEncTradeIndexBad, CheckIdentifier.Trainer); - if (validOT[lang] != pkm.OT_Name) - return GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer); - return GetValid(LEncTradeUnchanged, CheckIdentifier.Nickname); - } - - private static void VerifyTrade(LegalityAnalysis data, EncounterTrade t, int language) - { - var ot = t.GetOT(language); - var nick = t.GetNickname(language); - if (string.IsNullOrEmpty(nick)) - VerifyTradeOTOnly(data, t); - else - VerifyTradeOTNick(data, t, nick, ot); - } - - private static void VerifyTradeOTNick(LegalityAnalysis data, EncounterTrade t, string nick, string OT) - { - var pkm = data.pkm; - // trades that are not nicknamed (but are present in a table with others being named) - VerifyTradeNickname(data, t, nick, pkm); - - if (OT != pkm.OT_Name) - data.AddLine(GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer)); - } - - private static void VerifyTradeNickname(LegalityAnalysis data, EncounterTrade t, string expectedNickname, PKM pkm) - { - var result = IsNicknameMatch(expectedNickname, pkm, t) - ? GetValid(LEncTradeUnchanged, CheckIdentifier.Nickname) - : Get(LEncTradeChangedNickname, ParseSettings.NicknamedTrade, CheckIdentifier.Nickname); - data.AddLine(result); - } - - private static bool IsNicknameMatch(string nick, ILangNick pkm, EncounterTrade enc) - { - if (nick == "Quacklin’" && pkm.Nickname == "Quacklin'") - return true; - if (enc.IsNicknamed != pkm.IsNicknamed) - return false; - if (nick != pkm.Nickname) // if not match, must not be a nicknamed trade && not currently named - return !enc.IsNicknamed && !pkm.IsNicknamed; - return true; + if (WordFilter.IsFiltered(nickname, out string bad)) + data.AddLine(GetInvalid($"Word Filter: {bad}")); + if (TrainerNameVerifier.ContainsTooManyNumbers(nickname, data.Info.Generation)) + data.AddLine(GetInvalid("Word Filter: Too many numbers.")); } } + + private void VerifyFixedNicknameEncounter(LegalityAnalysis data, ILangNicknamedTemplate n, IEncounterTemplate enc, PKM pk, string nickname) + { + var nick = n.GetNickname(pk.Language); + + if (string.IsNullOrWhiteSpace(nick)) + { + if (n is WC8 {IsHOMEGift: true}) + { + VerifyHomeGiftNickname(data, enc, pk, nickname); + return; + } + + if (n.CanHandleOT(pk.Language)) + return; + + if (n is WC3 && pk.Format != 3) + { + // Gen3 gifts transferred to Generation 4 from another language can set the nickname flag. + var evos = data.Info.EvoChainsAllGens.Gen3; + bool matchAny = evos.Any(evo => !SpeciesName.IsNicknamedAnyLanguage(evo.Species, nickname, 3)); + if (matchAny) + return; + } + + if (pk.IsNicknamed) + data.AddLine(Get(LEncGiftNicknamed, Severity.Invalid)); + return; + } + + if (!pk.IsNicknamed) + { + // Check if it had a nickname at all + var orig = SpeciesName.GetSpeciesNameGeneration(enc.Species, pk.Language, enc.Generation); + if (orig == nick) + { + // Didn't have a nickname. Ensure that the language matches the current nickname string. + if (!SpeciesName.IsNicknamed(pk.Species, nickname, pk.Language, pk.Format)) + return; + } + + // Should have a nickname present. + data.AddLine(GetInvalid(LNickMatchLanguageFail)); + return; + } + + // Encounter has a nickname, and PKM should have it. + var severity = nick != nickname || !pk.IsNicknamed ? Severity.Invalid : Severity.Valid; + data.AddLine(Get(LEncGiftNicknamed, severity)); + } + + private void VerifyHomeGiftNickname(LegalityAnalysis data, IEncounterTemplate enc, ILangNick pk, string nickname) + { + // can nickname on redemption + if (!pk.IsNicknamed) + return; + + // Can't nickname everything. + if (enc.Species == (int) Species.Melmetal) + { + data.AddLine(GetInvalid(LEncGiftNicknamed)); + return; + } + + // Ensure the nickname does not match species name + var orig = SpeciesName.GetSpeciesNameGeneration(enc.Species, pk.Language, enc.Generation); + if (nickname == orig) + data.AddLine(GetInvalid(LNickMatchLanguageFlag)); + } + + private bool VerifyUnNicknamedEncounter(LegalityAnalysis data, PKM pk, string nickname) + { + if (pk.IsNicknamed) + { + if (data.Info.Generation >= 8) + { + // Can only nickname if it matches your language. + // Setting the nickname to the same as the species name does not set the Nickname flag (equals unmodified, no flag) + if (!SpeciesName.IsNicknamed(pk.Species, nickname, pk.Language, pk.Format)) + { + data.AddLine(Get(LNickMatchLanguageFlag, Severity.Invalid)); + return true; + } + } + for (int i = 0; i < SpeciesName.SpeciesDict.Count; i++) + { + if (!SpeciesName.SpeciesDict[i].TryGetValue(nickname, out int species)) + continue; + var msg = species == pk.Species && i != pk.Language ? LNickMatchNoOthersFail : LNickMatchLanguageFlag; + data.AddLine(Get(msg, ParseSettings.NicknamedAnotherSpecies)); + return true; + } + if (pk.Format <= 7 && StringConverter.HasEastAsianScriptCharacters(nickname.AsSpan()) && pk is not PB7) // East Asian Scripts + { + data.AddLine(GetInvalid(LNickInvalidChar)); + return true; + } + if (nickname.Length > Legal.GetMaxLengthNickname(data.Info.Generation, (LanguageID)pk.Language)) + { + int length = GetForeignNicknameLength(pk, data.Info.EncounterOriginal, data.Info.Generation); + var severe = (length != 0 && nickname.Length <= length) ? Severity.Fishy : Severity.Invalid; + data.AddLine(Get(LNickLengthLong, severe)); + return true; + } + data.AddLine(GetValid(LNickMatchNoOthers)); + } + else if (pk.Format < 3) + { + // pk1/pk2 IsNicknamed getter checks for match, logic should only reach here if matches. + data.AddLine(GetValid(LNickMatchLanguage)); + } + else + { + var enc = data.EncounterOriginal; + bool valid = IsNicknameValid(pk, enc, nickname); + var result = valid ? GetValid(LNickMatchLanguage) : GetInvalid(LNickMatchLanguageFail); + data.AddLine(result); + } + return false; + } + + private static int GetForeignNicknameLength(PKM pk, IEncounterTemplate match, int origin) + { + // HOME gifts already verified prior to reaching here. + System.Diagnostics.Debug.Assert(match is not WC8 {IsHOMEGift:true}); + + int length = 0; + if (origin is (4 or 5 or 6 or 7) && match.EggEncounter && pk.WasTradedEgg) + length = Legal.GetMaxLengthNickname(origin, English); + + if (pk.FatefulEncounter) + return length; + + if (pk.Format < 8 || pk.BDSP) + return length; + + // Can only nickname if the language matches. + var future = Legal.GetMaxLengthNickname(pk.Format, (LanguageID)pk.Language); + return Math.Max(length, future); + } + + private static bool IsNicknameValid(PKM pk, IEncounterTemplate enc, string nickname) + { + int species = pk.Species; + int format = pk.Format; + int language = pk.Language; + if (SpeciesName.GetSpeciesNameGeneration(species, language, format) == nickname) + return true; + + // Can't have another language name if it hasn't evolved or wasn't a language-traded egg. + // Starting in Generation 8, hatched language-traded eggs will take the Language from the trainer that hatched it. + // Also in Generation 8, evolving in a foreign language game will retain the original language as the source for the newly evolved species name. + // Transferring from Gen7->Gen8 realigns the Nickname string to the Language, if not nicknamed. + bool canHaveAnyLanguage = format <= 7 && (enc.Species != species || pk.WasTradedEgg) && !pk.GG; + if (canHaveAnyLanguage && !SpeciesName.IsNicknamedAnyLanguage(species, nickname, format)) + return true; + + switch (enc) + { + case WC7 wc7 when wc7.IsAshGreninjaWC7(pk): + return true; + case ILangNick loc: + if (loc.Language != 0 && !loc.IsNicknamed && !SpeciesName.IsNicknamedAnyLanguage(species, nickname, format)) + return true; // fixed language without nickname, nice job event maker! + break; + } + + if (format == 5 && !pk.IsNative) // transfer + { + if (canHaveAnyLanguage) + return !SpeciesName.IsNicknamedAnyLanguage(species, nickname, 4); + return SpeciesName.GetSpeciesNameGeneration(species, language, 4) == nickname; + } + + return false; + } + + private static void VerifyNicknameEgg(LegalityAnalysis data) + { + var Info = data.Info; + var pk = data.Entity; + + bool flagState = EggStateLegality.IsNicknameFlagSet(Info.EncounterMatch, pk); + if (pk.IsNicknamed != flagState) + data.AddLine(GetInvalid(flagState ? LNickFlagEggYes : LNickFlagEggNo, CheckIdentifier.Egg)); + + var nick = pk.Nickname; + if (pk.Format == 2 && !SpeciesName.IsNicknamedAnyLanguage(0, nick, 2)) + data.AddLine(GetValid(LNickMatchLanguageEgg, CheckIdentifier.Egg)); + else if (nick != SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Info.Generation)) + data.AddLine(GetInvalid(LNickMatchLanguageEggFail, CheckIdentifier.Egg)); + else + data.AddLine(GetValid(LNickMatchLanguageEgg, CheckIdentifier.Egg)); + } + + private static void VerifyNicknameTrade(LegalityAnalysis data, EncounterTrade t) + { + switch (data.Info.Generation) + { + case 8 when t is EncounterTrade8b b: VerifyTrade8b(data, b); return; + + case 1: VerifyTrade12(data, t); return; + case 2: return; // already checked all relevant properties when fetching with getValidEncounterTradeVC2 + case 3: VerifyTrade3(data, t); return; + case 4: VerifyTrade4(data, t); return; + case 5: VerifyTrade5(data, t); return; + case 6: + case 7: + case 8: + VerifyTrade(data, t, data.Entity.Language); return; + } + } + + private void VerifyG1NicknameWithinBounds(LegalityAnalysis data, ReadOnlySpan str) + { + var pk = data.Entity; + if (StringConverter12.GetIsG1English(str)) + { + if (str.Length > 10) + data.AddLine(GetInvalid(LNickLengthLong)); + } + else if (StringConverter12.GetIsG1Japanese(str)) + { + if (str.Length > 5) + data.AddLine(GetInvalid(LNickLengthLong)); + } + else if (pk.Korean && StringConverter2KOR.GetIsG2Korean(str)) + { + if (str.Length > 5) + data.AddLine(GetInvalid(LNickLengthLong)); + } + else + { + data.AddLine(GetInvalid(LG1CharNick)); + } + } + + private static void VerifyTrade12(LegalityAnalysis data, EncounterTrade t) + { + var t1 = (EncounterTrade1)t; + if (!t1.IsNicknameValid(data.Entity)) + data.AddLine(GetInvalid(LEncTradeChangedNickname, CheckIdentifier.Nickname)); + if (!t1.IsTrainerNameValid(data.Entity)) + data.AddLine(GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer)); + } + + private static void VerifyTrade3(LegalityAnalysis data, EncounterTrade t) + { + var pk = data.Entity; + int lang = pk.Language; + if (t.Species == (int)Species.Jynx) // FRLG Jynx + lang = DetectTradeLanguageG3DANTAEJynx(pk, lang); + VerifyTrade(data, t, lang); + } + + private static void VerifyTrade4(LegalityAnalysis data, EncounterTrade t) + { + var pk = data.Entity; + if (pk.TID == 1000) + { + VerifyTradeOTOnly(data, t); + return; + } + int lang = pk.Language; + switch (t.Species) + { + case (int)Species.Pikachu: // HGSS Pikachu + lang = DetectTradeLanguageG4SurgePikachu(pk, t, lang); + // flag korean on gen4 saves since the pk.Language is German + FlagKoreanIncompatibleSameGenTrade(data, pk, lang); + break; + case (int)Species.Magikarp: // DPPt Magikarp + lang = DetectTradeLanguageG4MeisterMagikarp(pk, t, lang); + // flag korean on gen4 saves since the pk.Language is German + FlagKoreanIncompatibleSameGenTrade(data, pk, lang); + break; + + default: + if (t is EncounterTrade4PID && pk.Version is ((int)GameVersion.D or (int)GameVersion.P)) // mainline DP + { + // DP English origin are Japanese lang. Can't have LanguageID 2 + if (lang == 2) + { + data.AddLine(GetInvalid(string.Format(LOTLanguage, Japanese, English), CheckIdentifier.Language)); + break; + } + + // Since two locales (JPN/ENG) can have the same LanguageID, check which we should be validating with. + if (lang == 1 && pk.OT_Name != t.GetOT(1)) // not Japanese + lang = 2; // verify strings with English locale instead. + } + break; + } + VerifyTrade(data, t, lang); + } + + private static void VerifyTrade8b(LegalityAnalysis data, EncounterTrade8b t) + { + var pk = data.Entity; + int lang = pk.Language; + if (t.Species == (int)Species.Magikarp) + { + // Japanese + if (pk.Language == (int)Japanese && pk.OT_Name is "Diamond." or "Pearl.") + { + // Traded between players, the original OT is replaced with the above OT (version dependent) as the original OT is >6 chars in length. + VerifyTradeNickname(data, t, t.Nicknames[(int)German], pk); + return; + } + + lang = DetectTradeLanguageG8MeisterMagikarp(pk, t, lang); + if (lang == 0) // err + data.AddLine(GetInvalid(string.Format(LOTLanguage, $"{Japanese}/{German}", $"{(LanguageID)pk.Language}"), CheckIdentifier.Language)); + } + VerifyTrade(data, t, lang); + } + + private static int DetectTradeLanguageG8MeisterMagikarp(PKM pk, EncounterTrade8b t, int currentLanguageID) + { + // Receiving the trade on a German game -> Japanese LanguageID. + // Receiving the trade on any other language -> German LanguageID. + if (currentLanguageID is not ((int)Japanese or (int)German)) + return 0; + + var nick = pk.Nickname; + var ot = pk.OT_Name; + for (int i = 1; i < (int)ChineseT; i++) + { + if (t.Nicknames[i] != nick) + continue; + if (t.TrainerNames[i] != ot) + continue; + + // Language gets flipped to another language ID; can't be equal. + var shouldNotBe = currentLanguageID == (int)German ? German : Japanese; + return i != (int)shouldNotBe ? i : 0; + } + return 0; + } + + private static void FlagKoreanIncompatibleSameGenTrade(LegalityAnalysis data, PKM pk, int lang) + { + if (pk.Format != 4 || lang != (int)Korean) + return; // transferred or not appropriate + if (ParseSettings.ActiveTrainer.Language != (int)Korean && ParseSettings.ActiveTrainer.Language >= 0) + data.AddLine(GetInvalid(string.Format(LTransferOriginFInvalid0_1, L_XKorean, L_XKoreanNon), CheckIdentifier.Language)); + } + + private static int DetectTradeLanguage(string OT, EncounterTrade t, int currentLanguageID) + { + var names = t.TrainerNames; + for (int lang = 1; lang < names.Count; lang++) + { + if (names[lang] != OT) + continue; + return lang; + } + return currentLanguageID; + } + + private static int DetectTradeLanguageG3DANTAEJynx(PKM pk, int currentLanguageID) + { + if (currentLanguageID != (int)Italian) + return currentLanguageID; + + if (pk.Version == (int)GameVersion.LG) + currentLanguageID = (int)English; // translation error; OT was not localized => same as English + return currentLanguageID; + } + + private static int DetectTradeLanguageG4MeisterMagikarp(PKM pk, EncounterTrade t, int currentLanguageID) + { + if (currentLanguageID == (int)English) + return (int)German; + + // All have German, regardless of origin version. + var lang = DetectTradeLanguage(pk.OT_Name, t, currentLanguageID); + if (lang == (int)English) // possible collision with FR/ES/DE. Check nickname + return pk.Nickname == t.Nicknames[(int)French] ? (int)French : (int)Spanish; // Spanish is same as English + + return lang; + } + + private static int DetectTradeLanguageG4SurgePikachu(PKM pk, EncounterTrade t, int currentLanguageID) + { + if (currentLanguageID == (int)French) + return (int)English; + + // All have English, regardless of origin version. + var lang = DetectTradeLanguage(pk.OT_Name, t, currentLanguageID); + if (lang == 2) // possible collision with ES/IT. Check nickname + return pk.Nickname == t.Nicknames[(int)Italian] ? (int)Italian : (int)Spanish; + + return lang; + } + + private static void VerifyTrade5(LegalityAnalysis data, EncounterTrade t) + { + var pk = data.Entity; + int lang = pk.Language; + // Trades for JPN games have language ID of 0, not 1. + if (pk.BW) + { + if (pk.Format == 5 && lang == (int)Japanese) + data.AddLine(GetInvalid(string.Format(LOTLanguage, 0, Japanese), CheckIdentifier.Language)); + + lang = Math.Max(lang, 1); + VerifyTrade(data, t, lang); + } + else // B2W2 + { + if (t.TID is Encounters5.YancyTID or Encounters5.CurtisTID) + VerifyTradeOTOnly(data, t); + else + VerifyTrade(data, t, lang); + } + } + + private static void VerifyTradeOTOnly(LegalityAnalysis data, EncounterTrade t) + { + var result = CheckTradeOTOnly(data, t.TrainerNames); + data.AddLine(result); + } + + private static CheckResult CheckTradeOTOnly(LegalityAnalysis data, IReadOnlyList validOT) + { + var pk = data.Entity; + if (pk.IsNicknamed && (pk.Format < 8 || pk.FatefulEncounter)) + return GetInvalid(LEncTradeChangedNickname, CheckIdentifier.Nickname); + int lang = pk.Language; + if (validOT.Count <= lang) + return GetInvalid(LEncTradeIndexBad, CheckIdentifier.Trainer); + if (validOT[lang] != pk.OT_Name) + return GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer); + return GetValid(LEncTradeUnchanged, CheckIdentifier.Nickname); + } + + private static void VerifyTrade(LegalityAnalysis data, EncounterTrade t, int language) + { + var ot = t.GetOT(language); + var nick = t.GetNickname(language); + if (string.IsNullOrEmpty(nick)) + VerifyTradeOTOnly(data, t); + else + VerifyTradeOTNick(data, t, nick, ot); + } + + private static void VerifyTradeOTNick(LegalityAnalysis data, EncounterTrade t, string nick, string OT) + { + var pk = data.Entity; + // trades that are not nicknamed (but are present in a table with others being named) + VerifyTradeNickname(data, t, nick, pk); + + if (OT != pk.OT_Name) + data.AddLine(GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer)); + } + + private static void VerifyTradeNickname(LegalityAnalysis data, EncounterTrade t, string expectedNickname, PKM pk) + { + var result = IsNicknameMatch(expectedNickname, pk, t) + ? GetValid(LEncTradeUnchanged, CheckIdentifier.Nickname) + : Get(LEncTradeChangedNickname, ParseSettings.NicknamedTrade, CheckIdentifier.Nickname); + data.AddLine(result); + } + + private static bool IsNicknameMatch(string nick, ILangNick pk, EncounterTrade enc) + { + if (nick == "Quacklin’" && pk.Nickname == "Quacklin'") + return true; + if (enc.IsNicknamed != pk.IsNicknamed) + return false; + if (nick != pk.Nickname) // if not match, must not be a nicknamed trade && not currently named + return !enc.IsNicknamed && !pk.IsNicknamed; + return true; + } } diff --git a/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs b/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs index 59c5683eb..462328aeb 100644 --- a/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/PIDVerifier.cs @@ -1,231 +1,230 @@ -using static PKHeX.Core.LegalityCheckStrings; +using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the . +/// +public sealed class PIDVerifier : Verifier { - /// - /// Verifies the . - /// - public sealed class PIDVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.PID; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.PID; + var pk = data.Entity; + if (pk.Format >= 6) + VerifyEC(data); - public override void Verify(LegalityAnalysis data) + var enc = data.EncounterMatch; + if (enc.Species == (int)Species.Wurmple) + VerifyECPIDWurmple(data); + + if (pk.PID == 0) + data.AddLine(Get(LPIDZero, Severity.Fishy)); + if (pk.Nature >= 25) // out of range + data.AddLine(GetInvalid(LPIDNatureMismatch)); + + VerifyShiny(data); + } + + private void VerifyShiny(LegalityAnalysis data) + { + var pk = data.Entity; + + switch (data.EncounterMatch) { - var pkm = data.pkm; - if (pkm.Format >= 6) - VerifyEC(data); + case EncounterStatic s: + if (!s.Shiny.IsValid(pk)) + data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.Shiny)); - var enc = data.EncounterMatch; - if (enc.Species == (int)Species.Wurmple) - VerifyECPIDWurmple(data); - - if (pkm.PID == 0) - data.AddLine(Get(LPIDZero, Severity.Fishy)); - if (pkm.Nature >= 25) // out of range - data.AddLine(GetInvalid(LPIDNatureMismatch)); - - VerifyShiny(data); - } - - private void VerifyShiny(LegalityAnalysis data) - { - var pkm = data.pkm; - - switch (data.EncounterMatch) - { - case EncounterStatic s: - if (!s.Shiny.IsValid(pkm)) + // Underground Raids are originally anti-shiny on encounter. + // When selecting a prize at the end, the game rolls and force-shiny is applied to be XOR=1. + if (s is EncounterStatic8U {Shiny: Shiny.Random}) + { + if (pk.ShinyXor is <= 15 and not 1) data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.Shiny)); + break; + } - // Underground Raids are originally anti-shiny on encounter. - // When selecting a prize at the end, the game rolls and force-shiny is applied to be XOR=1. - if (s is EncounterStatic8U {Shiny: Shiny.Random}) - { - if (pkm.ShinyXor is <= 15 and not 1) - data.AddLine(GetInvalid(LEncStaticPIDShiny, CheckIdentifier.Shiny)); - break; - } - - if (s.Generation != 5) - break; - - // Generation 5 has a correlation for wild captures. - // Certain static encounter types are just generated straightforwardly. - if (s.Location == 75) // Entree Forest - break; - - // Not wild / forced ability - if (s.Gift || s.Ability == AbilityPermission.OnlyHidden) - break; - - // Forced PID or generated without an encounter - // Crustle has 0x80 for its StartWildBattle flag; dunno what it does, but sometimes it doesn't align with the expected PID xor. - if (s is EncounterStatic5 { IsWildCorrelationPID: true }) - VerifyG5PID_IDCorrelation(data); + if (s.Generation != 5) break; - case EncounterSlot5 {IsHiddenGrotto: true}: - if (pkm.IsShiny) - data.AddLine(GetInvalid(LG5PIDShinyGrotto, CheckIdentifier.Shiny)); + // Generation 5 has a correlation for wild captures. + // Certain static encounter types are just generated straightforwardly. + if (s.Location == 75) // Entree Forest break; - case EncounterSlot5: + + // Not wild / forced ability + if (s.Gift || s.Ability == AbilityPermission.OnlyHidden) + break; + + // Forced PID or generated without an encounter + // Crustle has 0x80 for its StartWildBattle flag; dunno what it does, but sometimes it doesn't align with the expected PID xor. + if (s is EncounterStatic5 { IsWildCorrelationPID: true }) VerifyG5PID_IDCorrelation(data); - break; + break; - case PCD d: // fixed PID - if (d.IsFixedPID() && pkm.EncryptionConstant != d.Gift.PK.PID) - data.AddLine(GetInvalid(LEncGiftPIDMismatch, CheckIdentifier.Shiny)); - break; + case EncounterSlot5 {IsHiddenGrotto: true}: + if (pk.IsShiny) + data.AddLine(GetInvalid(LG5PIDShinyGrotto, CheckIdentifier.Shiny)); + break; + case EncounterSlot5: + VerifyG5PID_IDCorrelation(data); + break; - case WC7 wc7 when wc7.IsAshGreninjaWC7(pkm) && pkm.IsShiny: - data.AddLine(GetInvalid(LEncGiftShinyMismatch, CheckIdentifier.Shiny)); - break; - } - } + case PCD d: // fixed PID + if (d.IsFixedPID() && pk.EncryptionConstant != d.Gift.PK.PID) + data.AddLine(GetInvalid(LEncGiftPIDMismatch, CheckIdentifier.Shiny)); + break; - private void VerifyG5PID_IDCorrelation(LegalityAnalysis data) - { - var pkm = data.pkm; - var pid = pkm.EncryptionConstant; - var result = (pid & 1) ^ (pid >> 31) ^ (pkm.TID & 1) ^ (pkm.SID & 1); - if (result != 0) - data.AddLine(GetInvalid(LPIDTypeMismatch)); - } - - private static void VerifyECPIDWurmple(LegalityAnalysis data) - { - var pkm = data.pkm; - - if (pkm.Species == (int)Species.Wurmple) - { - // Indicate what it will evolve into - uint evoVal = WurmpleUtil.GetWurmpleEvoVal(pkm.EncryptionConstant); - var evolvesTo = evoVal == 0 ? (int)Species.Beautifly : (int)Species.Dustox; - var species = ParseSettings.SpeciesStrings[evolvesTo]; - var msg = string.Format(L_XWurmpleEvo_0, species); - data.AddLine(GetValid(msg, CheckIdentifier.EC)); - } - else if (!WurmpleUtil.IsWurmpleEvoValid(pkm)) - { - data.AddLine(GetInvalid(LPIDEncryptWurmple, CheckIdentifier.EC)); - } - } - - private static void VerifyEC(LegalityAnalysis data) - { - var pkm = data.pkm; - var Info = data.Info; - - if (pkm.EncryptionConstant == 0) - { - if (Info.EncounterMatch is WC8 {IsHOMEGift: true}) - return; // HOME Gifts - data.AddLine(Get(LPIDEncryptZero, Severity.Fishy, CheckIdentifier.EC)); - } - - // Gen3-5 => Gen6 have PID==EC with an edge case exception. - if (Info.Generation is 3 or 4 or 5) - { - VerifyTransferEC(data); - return; - } - - // Gen1-2, Gen6+ should have PID != EC - if (pkm.PID == pkm.EncryptionConstant) - { - // Check for edge cases - var enc = Info.EncounterMatch; - if (enc is WA8 {IsEquivalentFixedECPID: true}) - return; - if (enc is WB8 {IsEquivalentFixedECPID: true}) - return; - - data.AddLine(GetInvalid(LPIDEqualsEC, CheckIdentifier.EC)); // better to flag than 1:2^32 odds since RNG is not feasible to yield match - return; - } - - // Check for Gen3-5 => Gen6 edge case being incorrectly applied here. - if ((pkm.PID ^ 0x80000000) == pkm.EncryptionConstant) - { - int xor = pkm.TSV ^ pkm.PSV; - if (xor >> 3 == 1) // 8 <= x <= 15 - data.AddLine(Get(LTransferPIDECXor, Severity.Fishy, CheckIdentifier.EC)); - } - } - - /// - /// Returns the expected for a Gen3-5 transfer to Gen6. - /// - /// Entity to check - /// PID result - /// True if the is appropriate to use. - public static bool GetTransferPID(PKM pk, out uint pid) - { - var ver = pk.Version; - if (ver is 0 or >= (int) GameVersion.X) // Gen6+ ignored - { - pid = 0; - return false; - } - - var _ = GetExpectedTransferPID(pk, out pid); - return true; - } - - /// - /// Returns the expected for a Gen3-5 transfer to Gen6. - /// - /// Entity to check - /// Encryption constant result - /// True if the is appropriate to use. - public static bool GetTransferEC(PKM pk, out uint ec) - { - var ver = pk.Version; - if (ver is 0 or >= (int)GameVersion.X) // Gen6+ ignored - { - ec = 0; - return false; - } - - uint pid = pk.PID; - uint LID = pid & 0xFFFF; - uint HID = pid >> 16; - uint XOR = (uint)(pk.TID ^ LID ^ pk.SID ^ HID); - - // Ensure we don't have a shiny. - if (XOR >> 3 == 1) // Illegal, fix. (not 16=8) - ec = pid ^ 0x80000000; // Keep as shiny, so we have to mod the EC - else if ((XOR ^ 0x8000) >> 3 == 1 && pid != pk.EncryptionConstant) - ec = pid ^ 0x80000000; // Already anti-shiny, ensure the anti-shiny relationship is present. - else - ec = pid; // Ensure the copy correlation is present. - - return true; - } - - private static void VerifyTransferEC(LegalityAnalysis data) - { - var pkm = data.pkm; - // When transferred to Generation 6, the Encryption Constant is copied from the PID. - // The PID is then checked to see if it becomes shiny with the new Shiny rules (>>4 instead of >>3) - // If the PID is nonshiny->shiny, the top bit is flipped. - - // Check to see if the PID and EC are properly configured. - var bitFlipProc = GetExpectedTransferPID(pkm, out var expect); - bool valid = pkm.PID == expect; - if (valid) - return; - - var msg = bitFlipProc ? LTransferPIDECBitFlip : LTransferPIDECEquals; - data.AddLine(GetInvalid(msg, CheckIdentifier.EC)); - } - - private static bool GetExpectedTransferPID(PKM pkm, out uint expect) - { - var ec = pkm.EncryptionConstant; // should be original PID - bool xorPID = ((pkm.TID ^ pkm.SID ^ (int) (ec & 0xFFFF) ^ (int) (ec >> 16)) & ~0x7) == 8; - expect = (xorPID ? (ec ^ 0x80000000) : ec); - return xorPID; + case WC7 wc7 when wc7.IsAshGreninjaWC7(pk) && pk.IsShiny: + data.AddLine(GetInvalid(LEncGiftShinyMismatch, CheckIdentifier.Shiny)); + break; } } + + private void VerifyG5PID_IDCorrelation(LegalityAnalysis data) + { + var pk = data.Entity; + var pid = pk.EncryptionConstant; + var result = (pid & 1) ^ (pid >> 31) ^ (pk.TID & 1) ^ (pk.SID & 1); + if (result != 0) + data.AddLine(GetInvalid(LPIDTypeMismatch)); + } + + private static void VerifyECPIDWurmple(LegalityAnalysis data) + { + var pk = data.Entity; + + if (pk.Species == (int)Species.Wurmple) + { + // Indicate what it will evolve into + uint evoVal = WurmpleUtil.GetWurmpleEvoVal(pk.EncryptionConstant); + var evolvesTo = evoVal == 0 ? (int)Species.Beautifly : (int)Species.Dustox; + var species = ParseSettings.SpeciesStrings[evolvesTo]; + var msg = string.Format(L_XWurmpleEvo_0, species); + data.AddLine(GetValid(msg, CheckIdentifier.EC)); + } + else if (!WurmpleUtil.IsWurmpleEvoValid(pk)) + { + data.AddLine(GetInvalid(LPIDEncryptWurmple, CheckIdentifier.EC)); + } + } + + private static void VerifyEC(LegalityAnalysis data) + { + var pk = data.Entity; + var Info = data.Info; + + if (pk.EncryptionConstant == 0) + { + if (Info.EncounterMatch is WC8 {IsHOMEGift: true}) + return; // HOME Gifts + data.AddLine(Get(LPIDEncryptZero, Severity.Fishy, CheckIdentifier.EC)); + } + + // Gen3-5 => Gen6 have PID==EC with an edge case exception. + if (Info.Generation is 3 or 4 or 5) + { + VerifyTransferEC(data); + return; + } + + // Gen1-2, Gen6+ should have PID != EC + if (pk.PID == pk.EncryptionConstant) + { + // Check for edge cases + var enc = Info.EncounterMatch; + if (enc is WA8 {IsEquivalentFixedECPID: true}) + return; + if (enc is WB8 {IsEquivalentFixedECPID: true}) + return; + + data.AddLine(GetInvalid(LPIDEqualsEC, CheckIdentifier.EC)); // better to flag than 1:2^32 odds since RNG is not feasible to yield match + return; + } + + // Check for Gen3-5 => Gen6 edge case being incorrectly applied here. + if ((pk.PID ^ 0x80000000) == pk.EncryptionConstant) + { + int xor = pk.TSV ^ pk.PSV; + if (xor >> 3 == 1) // 8 <= x <= 15 + data.AddLine(Get(LTransferPIDECXor, Severity.Fishy, CheckIdentifier.EC)); + } + } + + /// + /// Returns the expected for a Gen3-5 transfer to Gen6. + /// + /// Entity to check + /// PID result + /// True if the is appropriate to use. + public static bool GetTransferPID(PKM pk, out uint pid) + { + var ver = pk.Version; + if (ver is 0 or >= (int) GameVersion.X) // Gen6+ ignored + { + pid = 0; + return false; + } + + var _ = GetExpectedTransferPID(pk, out pid); + return true; + } + + /// + /// Returns the expected for a Gen3-5 transfer to Gen6. + /// + /// Entity to check + /// Encryption constant result + /// True if the is appropriate to use. + public static bool GetTransferEC(PKM pk, out uint ec) + { + var ver = pk.Version; + if (ver is 0 or >= (int)GameVersion.X) // Gen6+ ignored + { + ec = 0; + return false; + } + + uint pid = pk.PID; + uint LID = pid & 0xFFFF; + uint HID = pid >> 16; + uint XOR = (uint)(pk.TID ^ LID ^ pk.SID ^ HID); + + // Ensure we don't have a shiny. + if (XOR >> 3 == 1) // Illegal, fix. (not 16=8) + ec = pid ^ 0x80000000; // Keep as shiny, so we have to mod the EC + else if ((XOR ^ 0x8000) >> 3 == 1 && pid != pk.EncryptionConstant) + ec = pid ^ 0x80000000; // Already anti-shiny, ensure the anti-shiny relationship is present. + else + ec = pid; // Ensure the copy correlation is present. + + return true; + } + + private static void VerifyTransferEC(LegalityAnalysis data) + { + var pk = data.Entity; + // When transferred to Generation 6, the Encryption Constant is copied from the PID. + // The PID is then checked to see if it becomes shiny with the new Shiny rules (>>4 instead of >>3) + // If the PID is nonshiny->shiny, the top bit is flipped. + + // Check to see if the PID and EC are properly configured. + var bitFlipProc = GetExpectedTransferPID(pk, out var expect); + bool valid = pk.PID == expect; + if (valid) + return; + + var msg = bitFlipProc ? LTransferPIDECBitFlip : LTransferPIDECEquals; + data.AddLine(GetInvalid(msg, CheckIdentifier.EC)); + } + + private static bool GetExpectedTransferPID(PKM pk, out uint expect) + { + var ec = pk.EncryptionConstant; // should be original PID + bool xorPID = ((pk.TID ^ pk.SID ^ (int) (ec & 0xFFFF) ^ (int) (ec >> 16)) & ~0x7) == 8; + expect = (xorPID ? (ec ^ 0x80000000) : ec); + return xorPID; + } } diff --git a/PKHeX.Core/Legality/Verifiers/ParseSettings.cs b/PKHeX.Core/Legality/Verifiers/ParseSettings.cs index b74cd618f..2816a6b3d 100644 --- a/PKHeX.Core/Legality/Verifiers/ParseSettings.cs +++ b/PKHeX.Core/Legality/Verifiers/ParseSettings.cs @@ -1,128 +1,127 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class ParseSettings { - public static class ParseSettings + internal static ITrainerInfo ActiveTrainer { get; set; } = new SimpleTrainerInfo(GameVersion.Any) { OT = string.Empty, Language = -1 }; + + /// + /// Toggles whether or not the word filter should be used when checking the data. + /// + public static bool CheckWordFilter { get; set; } = true; + + /// + /// Setting to specify if an analysis should permit data sourced from the physical cartridge era of GameBoy games. + /// + /// If false, indicates to use Virtual Console rules (which are transferable to Gen7+) + public static bool AllowGBCartEra { get; set; } + + /// + /// Setting to specify if an analysis should permit trading a Generation 1 origin file to Generation 2, then back. Useful for checking RBY Metagame rules. + /// + public static bool AllowGen1Tradeback { get; set; } = true; + + public static Severity NicknamedTrade { get; private set; } = Severity.Invalid; + public static Severity NicknamedMysteryGift { get; private set; } = Severity.Fishy; + public static Severity RNGFrameNotFound { get; private set; } = Severity.Fishy; + public static Severity Gen7TransferStarPID { get; private set; } = Severity.Fishy; + public static Severity Gen8MemoryMissingHT { get; private set; } = Severity.Fishy; + public static Severity Gen8TransferTrackerNotPresent { get; private set; } = Severity.Fishy; + public static Severity NicknamedAnotherSpecies { get; private set; } = Severity.Fishy; + public static Severity ZeroHeightWeight { get; private set; } = Severity.Fishy; + public static Severity CurrentHandlerMismatch { get; private set; } = Severity.Invalid; + public static bool CheckActiveHandler { get; set; } + + public static IReadOnlyList MoveStrings { get; private set; } = Util.GetMovesList(GameLanguage.DefaultLanguage); + public static IReadOnlyList SpeciesStrings { get; private set; } = Util.GetSpeciesList(GameLanguage.DefaultLanguage); + public static string GetMoveName(int move) => (uint)move >= MoveStrings.Count ? LegalityCheckStrings.L_AError : MoveStrings[move]; + public static IEnumerable GetMoveNames(IEnumerable moves) => moves.Select(m => (uint)m >= MoveStrings.Count ? LegalityCheckStrings.L_AError : MoveStrings[m]); + + public static void ChangeLocalizationStrings(IReadOnlyList moves, IReadOnlyList species) { - internal static ITrainerInfo ActiveTrainer { get; set; } = new SimpleTrainerInfo(GameVersion.Any) { OT = string.Empty, Language = -1 }; - - /// - /// Toggles whether or not the word filter should be used when checking the data. - /// - public static bool CheckWordFilter { get; set; } = true; - - /// - /// Setting to specify if an analysis should permit data sourced from the physical cartridge era of GameBoy games. - /// - /// If false, indicates to use Virtual Console rules (which are transferable to Gen7+) - public static bool AllowGBCartEra { get; set; } - - /// - /// Setting to specify if an analysis should permit trading a Generation 1 origin file to Generation 2, then back. Useful for checking RBY Metagame rules. - /// - public static bool AllowGen1Tradeback { get; set; } = true; - - public static Severity NicknamedTrade { get; private set; } = Severity.Invalid; - public static Severity NicknamedMysteryGift { get; private set; } = Severity.Fishy; - public static Severity RNGFrameNotFound { get; private set; } = Severity.Fishy; - public static Severity Gen7TransferStarPID { get; private set; } = Severity.Fishy; - public static Severity Gen8MemoryMissingHT { get; private set; } = Severity.Fishy; - public static Severity Gen8TransferTrackerNotPresent { get; private set; } = Severity.Fishy; - public static Severity NicknamedAnotherSpecies { get; private set; } = Severity.Fishy; - public static Severity ZeroHeightWeight { get; private set; } = Severity.Fishy; - public static Severity CurrentHandlerMismatch { get; private set; } = Severity.Invalid; - public static bool CheckActiveHandler { get; set; } - - public static IReadOnlyList MoveStrings = Util.GetMovesList(GameLanguage.DefaultLanguage); - public static IReadOnlyList SpeciesStrings = Util.GetSpeciesList(GameLanguage.DefaultLanguage); - public static string GetMoveName(int move) => (uint)move >= MoveStrings.Count ? LegalityCheckStrings.L_AError : MoveStrings[move]; - public static IEnumerable GetMoveNames(IEnumerable moves) => moves.Select(m => (uint)m >= MoveStrings.Count ? LegalityCheckStrings.L_AError : MoveStrings[m]); - - public static void ChangeLocalizationStrings(IReadOnlyList moves, IReadOnlyList species) - { - SpeciesStrings = species; - MoveStrings = moves; - } - - /// - /// Checks to see if Crystal is available to visit/originate from. - /// - /// Pokemon Crystal was never released in Korea. - /// Korean data being checked - /// True if Crystal data is allowed - public static bool AllowGen2Crystal(bool Korean) => !Korean; - - /// - /// Checks to see if Crystal is available to visit/originate from. - /// - /// Data being checked - /// True if Crystal data is allowed - public static bool AllowGen2Crystal(PKM pkm) => !pkm.Korean; - - /// - /// Checks to see if the Move Reminder (Relearner) is available. - /// - /// Pokemon Stadium 2 was never released in Korea. - /// Data being checked - /// True if Crystal data is allowed - public static bool AllowGen2MoveReminder(PKM pkm) => !pkm.Korean && AllowGBCartEra; - - internal static bool IsFromActiveTrainer(PKM pkm) => ActiveTrainer.IsFromTrainer(pkm); - - /// - /// Initializes certain settings - /// - /// Newly loaded save file - /// Save file is Physical GB cartridge save file (not Virtual Console) - public static bool InitFromSaveFileData(SaveFile sav) - { - ActiveTrainer = sav; - return AllowGBCartEra = sav switch - { - SAV1 { IsVirtualConsole: true } => false, - SAV2 { IsVirtualConsole: true } => false, - { Generation: 1 or 2 } => true, - _ => false, - }; - } - - public static void InitFromSettings(IParseSettings settings) - { - CheckWordFilter = settings.CheckWordFilter; - AllowGen1Tradeback = settings.AllowGen1Tradeback; - NicknamedTrade = settings.NicknamedTrade; - NicknamedMysteryGift = settings.NicknamedMysteryGift; - RNGFrameNotFound = settings.RNGFrameNotFound; - Gen7TransferStarPID = settings.Gen7TransferStarPID; - Gen8TransferTrackerNotPresent = settings.Gen8TransferTrackerNotPresent; - Gen8MemoryMissingHT = settings.Gen8MemoryMissingHT; - NicknamedAnotherSpecies = settings.NicknamedAnotherSpecies; - ZeroHeightWeight = settings.ZeroHeightWeight; - CurrentHandlerMismatch = settings.CurrentHandlerMismatch; - CheckActiveHandler = settings.CheckActiveHandler; - } + SpeciesStrings = species; + MoveStrings = moves; } - public interface IParseSettings - { - bool CheckWordFilter { get; } - bool CheckActiveHandler { get; } - bool AllowGen1Tradeback { get; } + /// + /// Checks to see if Crystal is available to visit/originate from. + /// + /// Pokemon Crystal was never released in Korea. + /// Korean data being checked + /// True if Crystal data is allowed + public static bool AllowGen2Crystal(bool Korean) => !Korean; - Severity NicknamedTrade { get; } - Severity NicknamedMysteryGift { get; } - Severity RNGFrameNotFound { get; } - Severity Gen7TransferStarPID { get; } - Severity Gen8MemoryMissingHT { get; } - Severity Gen8TransferTrackerNotPresent { get; } - Severity NicknamedAnotherSpecies { get; } - Severity ZeroHeightWeight { get; } - Severity CurrentHandlerMismatch { get; } + /// + /// Checks to see if Crystal is available to visit/originate from. + /// + /// Data being checked + /// True if Crystal data is allowed + public static bool AllowGen2Crystal(PKM pk) => !pk.Korean; + + /// + /// Checks to see if the Move Reminder (Relearner) is available. + /// + /// Pokemon Stadium 2 was never released in Korea. + /// Data being checked + /// True if Crystal data is allowed + public static bool AllowGen2MoveReminder(PKM pk) => !pk.Korean && AllowGBCartEra; + + internal static bool IsFromActiveTrainer(PKM pk) => ActiveTrainer.IsFromTrainer(pk); + + /// + /// Initializes certain settings + /// + /// Newly loaded save file + /// Save file is Physical GB cartridge save file (not Virtual Console) + public static bool InitFromSaveFileData(SaveFile sav) + { + ActiveTrainer = sav; + return AllowGBCartEra = sav switch + { + SAV1 { IsVirtualConsole: true } => false, + SAV2 { IsVirtualConsole: true } => false, + { Generation: 1 or 2 } => true, + _ => false, + }; } - public interface IBulkAnalysisSettings + public static void InitFromSettings(IParseSettings settings) { - bool CheckActiveHandler { get; } + CheckWordFilter = settings.CheckWordFilter; + AllowGen1Tradeback = settings.AllowGen1Tradeback; + NicknamedTrade = settings.NicknamedTrade; + NicknamedMysteryGift = settings.NicknamedMysteryGift; + RNGFrameNotFound = settings.RNGFrameNotFound; + Gen7TransferStarPID = settings.Gen7TransferStarPID; + Gen8TransferTrackerNotPresent = settings.Gen8TransferTrackerNotPresent; + Gen8MemoryMissingHT = settings.Gen8MemoryMissingHT; + NicknamedAnotherSpecies = settings.NicknamedAnotherSpecies; + ZeroHeightWeight = settings.ZeroHeightWeight; + CurrentHandlerMismatch = settings.CurrentHandlerMismatch; + CheckActiveHandler = settings.CheckActiveHandler; } } + +public interface IParseSettings +{ + bool CheckWordFilter { get; } + bool CheckActiveHandler { get; } + bool AllowGen1Tradeback { get; } + + Severity NicknamedTrade { get; } + Severity NicknamedMysteryGift { get; } + Severity RNGFrameNotFound { get; } + Severity Gen7TransferStarPID { get; } + Severity Gen8MemoryMissingHT { get; } + Severity Gen8TransferTrackerNotPresent { get; } + Severity NicknamedAnotherSpecies { get; } + Severity ZeroHeightWeight { get; } + Severity CurrentHandlerMismatch { get; } +} + +public interface IBulkAnalysisSettings +{ + bool CheckActiveHandler { get; } +} diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResult.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResult.cs index 6da4eb183..6fb085927 100644 --- a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResult.cs +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonResult.cs @@ -1,29 +1,28 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Legality Check Parse object containing information about a single ribbon. +/// +internal sealed class RibbonResult { - /// - /// Legality Check Parse object containing information about a single ribbon. - /// - internal sealed class RibbonResult + /// Ribbon Display Name + public string Name { get; private set; } + + /// Ribbon should not be present. + /// If this is false, the Ribbon is missing. + public bool Invalid { get; } + + public RibbonResult(string prop, bool invalid = true) { - /// Ribbon Display Name - public string Name { get; private set; } + Name = RibbonStrings.GetName(prop); + Invalid = invalid; + } - /// Ribbon should not be present. - /// If this is false, the Ribbon is missing. - public bool Invalid { get; } - - public RibbonResult(string prop, bool invalid = true) - { - Name = RibbonStrings.GetName(prop); - Invalid = invalid; - } - - /// - /// Merges the result name with another provided result. - /// - public void Combine(RibbonResult other) - { - Name += $" / {other.Name}"; - } + /// + /// Merges the result name with another provided result. + /// + public void Combine(RibbonResult other) + { + Name += $" / {other.Name}"; } } diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonStrings.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonStrings.cs index 5b0f7bdfc..31cc14b86 100644 --- a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonStrings.cs +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonStrings.cs @@ -1,45 +1,44 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// String Translation Utility +/// +public static class RibbonStrings { + private static readonly Dictionary RibbonNames = new(); + /// - /// String Translation Utility + /// Resets the Ribbon Dictionary to use the supplied set of Ribbon (Property) Names. /// - public static class RibbonStrings + /// Array of strings that are tab separated with Property Name, \t, and Display Name. + public static void ResetDictionary(IEnumerable lines) { - private static readonly Dictionary RibbonNames = new(); - - /// - /// Resets the Ribbon Dictionary to use the supplied set of Ribbon (Property) Names. - /// - /// Array of strings that are tab separated with Property Name, \t, and Display Name. - public static void ResetDictionary(IEnumerable lines) + // Don't clear existing keys on reset; only update. + // A language will have the same keys (hopefully), only with differing values. + foreach (var line in lines) { - // Don't clear existing keys on reset; only update. - // A language will have the same keys (hopefully), only with differing values. - foreach (var line in lines) - { - var index = line.IndexOf('\t'); - if (index < 0) - continue; - var name = line[..index]; - var text = line[(index + 1)..]; - RibbonNames[name] = text; - } - } - - /// - /// Returns the Ribbon Display Name for the corresponding ribbon property name. - /// - /// Ribbon property name - /// Ribbon display name - public static string GetName(string propertyName) - { - // Throw an exception with the requested property name as the message, rather than an ambiguous "key not present" message. - // We should ALWAYS have the key present as the input arguments are not user-defined, rather, they are from PKM property names. - if (!RibbonNames.TryGetValue(propertyName, out string value)) - throw new KeyNotFoundException(propertyName); - return value; + var index = line.IndexOf('\t'); + if (index < 0) + continue; + var name = line[..index]; + var text = line[(index + 1)..]; + RibbonNames[name] = text; } } + + /// + /// Returns the Ribbon Display Name for the corresponding ribbon property name. + /// + /// Ribbon property name + /// Ribbon display name + public static string GetName(string propertyName) + { + // Throw an exception with the requested property name as the message, rather than an ambiguous "key not present" message. + // We should ALWAYS have the key present as the input arguments are not user-defined, rather, they are from PKM property names. + if (!RibbonNames.TryGetValue(propertyName, out string value)) + throw new KeyNotFoundException(propertyName); + return value; + } } diff --git a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs index d101da400..bed0aa3a8 100644 --- a/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Ribbons/RibbonVerifier.cs @@ -1,704 +1,703 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the Ribbon values. +/// +public sealed class RibbonVerifier : Verifier { - /// - /// Verifies the Ribbon values. - /// - public sealed class RibbonVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Ribbon; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Ribbon; + // Flag VC (Gen1/2) ribbons using Gen7 origin rules. + var enc = data.EncounterMatch; + var pk = data.Entity; - public override void Verify(LegalityAnalysis data) + // Check Unobtainable Ribbons + if (pk.IsEgg) { - // Flag VC (Gen1/2) ribbons using Gen7 origin rules. - var enc = data.EncounterMatch; - var pkm = data.pkm; + if (GetIncorrectRibbonsEgg(pk, enc)) + data.AddLine(GetInvalid(LRibbonEgg)); + return; + } - // Check Unobtainable Ribbons - if (pkm.IsEgg) - { - if (GetIncorrectRibbonsEgg(pkm, enc)) - data.AddLine(GetInvalid(LRibbonEgg)); - return; - } + var result = GetIncorrectRibbons(pk, data.Info.EvoChainsAllGens, enc); + if (result.Count != 0) + { + var msg = string.Join(Environment.NewLine, result); + data.AddLine(GetInvalid(msg)); + } + else + { + data.AddLine(GetValid(LRibbonAllValid)); + } + } - var result = GetIncorrectRibbons(pkm, data.Info.EvoChainsAllGens, enc); - if (result.Count != 0) + private static List GetIncorrectRibbons(PKM pk, EvolutionHistory evos, IEncounterTemplate enc) + { + List missingRibbons = new(); + List invalidRibbons = new(); + var ribs = GetRibbonResults(pk, evos, enc); + foreach (var bad in ribs) + (bad.Invalid ? invalidRibbons : missingRibbons).Add(bad.Name); + + var result = new List(); + if (missingRibbons.Count > 0) + result.Add(string.Format(LRibbonFMissing_0, string.Join(", ", missingRibbons).Replace(RibbonInfo.PropertyPrefix, string.Empty))); + if (invalidRibbons.Count > 0) + result.Add(string.Format(LRibbonFInvalid_0, string.Join(", ", invalidRibbons).Replace(RibbonInfo.PropertyPrefix, string.Empty))); + return result; + } + + private static bool GetIncorrectRibbonsEgg(PKM pk, IEncounterTemplate enc) + { + var names = ReflectUtil.GetPropertiesStartWithPrefix(pk.GetType(), RibbonInfo.PropertyPrefix); + if (enc is IRibbonSetEvent3 event3) + names = names.Except(event3.RibbonNames()); + if (enc is IRibbonSetEvent4 event4) + names = names.Except(event4.RibbonNames()); + + foreach (var value in names.Select(name => ReflectUtil.GetValue(pk, name))) + { + if (value is null) + continue; + if (HasFlag(value) || HasCount(value)) + return true; + + static bool HasFlag(object o) => o is true; + static bool HasCount(object o) => o is > 0; + } + return false; + } + + internal static IEnumerable GetRibbonResults(PKM pk, EvolutionHistory evos, IEncounterTemplate enc) + { + return GetInvalidRibbons(pk, evos, enc) + .Concat(GetInvalidRibbonsEvent1(pk, enc)) + .Concat(GetInvalidRibbonsEvent2(pk, enc)); + } + + private static IEnumerable GetInvalidRibbons(PKM pk, EvolutionHistory evos, IEncounterTemplate enc) + { + // is a part of Event4, but O3 doesn't have the others + if (pk is IRibbonSetOnly3 {RibbonWorld: true}) + yield return new RibbonResult(nameof(IRibbonSetOnly3.RibbonWorld)); + + if (pk is IRibbonSetUnique3 u3) + { + if (enc.Generation != 3) { - var msg = string.Join(Environment.NewLine, result); - data.AddLine(GetInvalid(msg)); + if (u3.RibbonWinning) + yield return new RibbonResult(nameof(u3.RibbonWinning)); + if (u3.RibbonVictory) + yield return new RibbonResult(nameof(u3.RibbonVictory)); } else { - data.AddLine(GetValid(LRibbonAllValid)); + if (u3.RibbonWinning && !CanHaveRibbonWinning(pk, enc, 3)) + yield return new RibbonResult(nameof(u3.RibbonWinning)); + if (u3.RibbonVictory && !CanHaveRibbonVictory(pk, 3)) + yield return new RibbonResult(nameof(u3.RibbonVictory)); } } - private static List GetIncorrectRibbons(PKM pkm, EvolutionHistory evos, IEncounterTemplate enc) + int gen = enc.Generation; + if (pk is IRibbonSetUnique4 u4) { - List missingRibbons = new(); - List invalidRibbons = new(); - var ribs = GetRibbonResults(pkm, evos, enc); - foreach (var bad in ribs) - (bad.Invalid ? invalidRibbons : missingRibbons).Add(bad.Name); - - var result = new List(); - if (missingRibbons.Count > 0) - result.Add(string.Format(LRibbonFMissing_0, string.Join(", ", missingRibbons).Replace(RibbonInfo.PropertyPrefix, string.Empty))); - if (invalidRibbons.Count > 0) - result.Add(string.Format(LRibbonFInvalid_0, string.Join(", ", invalidRibbons).Replace(RibbonInfo.PropertyPrefix, string.Empty))); - return result; - } - - private static bool GetIncorrectRibbonsEgg(PKM pkm, IEncounterTemplate enc) - { - var names = ReflectUtil.GetPropertiesStartWithPrefix(pkm.GetType(), RibbonInfo.PropertyPrefix); - if (enc is IRibbonSetEvent3 event3) - names = names.Except(event3.RibbonNames()); - if (enc is IRibbonSetEvent4 event4) - names = names.Except(event4.RibbonNames()); - - foreach (var value in names.Select(name => ReflectUtil.GetValue(pkm, name))) + if (!IsAllowedBattleFrontier(pk.Species, pk.Form, 4) || gen > 4) { - if (value is null) - continue; - if (HasFlag(value) || HasCount(value)) - return true; - - static bool HasFlag(object o) => o is true; - static bool HasCount(object o) => o is > 0; + foreach (var z in GetInvalidRibbonsNone(u4.RibbonBitsAbility(), u4.RibbonNamesAbility())) + yield return z; } - return false; + + var c3 = u4.RibbonBitsContest3(); + var c3n = u4.RibbonNamesContest3(); + var iter3 = gen == 3 ? GetMissingContestRibbons(c3, c3n) : GetInvalidRibbonsNone(c3, c3n); + foreach (var z in iter3) + yield return z; + + var c4 = u4.RibbonBitsContest4(); + var c4n = u4.RibbonNamesContest4(); + var iter4 = (gen is 3 or 4) && IsAllowedInContest4(pk.Species, pk.Form) ? GetMissingContestRibbons(c4, c4n) : GetInvalidRibbonsNone(c4, c4n); + foreach (var z in iter4) + yield return z; } - - internal static IEnumerable GetRibbonResults(PKM pkm, EvolutionHistory evos, IEncounterTemplate enc) + if (pk is IRibbonSetCommon4 s4) { - return GetInvalidRibbons(pkm, evos, enc) - .Concat(GetInvalidRibbonsEvent1(pkm, enc)) - .Concat(GetInvalidRibbonsEvent2(pkm, enc)); - } - - private static IEnumerable GetInvalidRibbons(PKM pkm, EvolutionHistory evos, IEncounterTemplate enc) - { - // is a part of Event4, but O3 doesn't have the others - if (pkm is IRibbonSetOnly3 {RibbonWorld: true}) - yield return new RibbonResult(nameof(IRibbonSetOnly3.RibbonWorld)); - - if (pkm is IRibbonSetUnique3 u3) + bool inhabited4 = gen is 3 or 4; + var iterate = GetInvalidRibbons4Any(pk, evos, s4, gen); + if (!inhabited4) { - if (enc.Generation != 3) - { - if (u3.RibbonWinning) - yield return new RibbonResult(nameof(u3.RibbonWinning)); - if (u3.RibbonVictory) - yield return new RibbonResult(nameof(u3.RibbonVictory)); - } + if (pk.HasVisitedBDSP(evos.Gen8b)) // Allow Sinnoh Champion. ILCA reused the Gen4 ribbon for the remake. + iterate = iterate.Concat(GetInvalidRibbonsNoneSkipIndex(s4.RibbonBitsOnly(), s4.RibbonNamesOnly(), 1)); else - { - if (u3.RibbonWinning && !CanHaveRibbonWinning(pkm, enc, 3)) - yield return new RibbonResult(nameof(u3.RibbonWinning)); - if (u3.RibbonVictory && !CanHaveRibbonVictory(pkm, 3)) - yield return new RibbonResult(nameof(u3.RibbonVictory)); - } + iterate = iterate.Concat(GetInvalidRibbonsNone(s4.RibbonBitsOnly(), s4.RibbonNamesOnly())); + } + foreach (var z in iterate) + yield return z; + } + if (pk is IRibbonSetCommon6 s6) + { + bool inhabited6 = gen is >= 3 and <= 6; + + var iterate = inhabited6 + ? GetInvalidRibbons6Any(pk, s6, gen, enc) + : pk.Format >= 8 + ? GetInvalidRibbons6AnyG8(pk, s6, evos) + : GetInvalidRibbonsNone(s6.RibbonBits(), s6.RibbonNamesBool()); + foreach (var z in iterate) + yield return z; + + if (!inhabited6) + { + if (s6.RibbonCountMemoryContest > 0) + yield return new RibbonResult(nameof(s6.RibbonCountMemoryContest)); + if (s6.RibbonCountMemoryBattle > 0) + yield return new RibbonResult(nameof(s6.RibbonCountMemoryBattle)); } - int gen = enc.Generation; - if (pkm is IRibbonSetUnique4 u4) - { - if (!IsAllowedBattleFrontier(pkm.Species, pkm.Form, 4) || gen > 4) - { - foreach (var z in GetInvalidRibbonsNone(u4.RibbonBitsAbility(), u4.RibbonNamesAbility())) - yield return z; - } + if (s6.RibbonBestFriends && !IsRibbonValidBestFriend(pk, evos, gen)) + yield return new RibbonResult(nameof(IRibbonSetCommon6.RibbonBestFriends)); + } + if (pk is IRibbonSetCommon7 s7) + { + bool inhabited7 = gen <= 7 && !pk.GG; + var iterate = inhabited7 ? GetInvalidRibbons7Any(pk, s7) : GetInvalidRibbonsNone(s7.RibbonBits(), s7.RibbonNames()); + foreach (var z in iterate) + yield return z; + } + if (pk is IRibbonSetCommon3 s3) + { + if (s3.RibbonChampionG3 && gen != 3) + yield return new RibbonResult(nameof(s3.RibbonChampionG3)); // RSE HoF + if (s3.RibbonArtist && gen != 3) + yield return new RibbonResult(nameof(s3.RibbonArtist)); // RSE Master Rank Portrait + if (s3.RibbonEffort && !IsRibbonValidEffort(pk, evos, gen)) // unobtainable in Gen 5 + yield return new RibbonResult(nameof(s3.RibbonEffort)); + } + if (pk is IRibbonSetCommon8 s8) + { + bool inhabited8 = gen <= 8; + var iterate = inhabited8 ? GetInvalidRibbons8Any(pk, s8, enc, evos) : GetInvalidRibbonsNone(s8.RibbonBits(), s8.RibbonNames()); + foreach (var z in iterate) + yield return z; + } + } - var c3 = u4.RibbonBitsContest3(); - var c3n = u4.RibbonNamesContest3(); - var iter3 = gen == 3 ? GetMissingContestRibbons(c3, c3n) : GetInvalidRibbonsNone(c3, c3n); - foreach (var z in iter3) - yield return z; + private static bool IsRibbonValidEffort(PKM pk, EvolutionHistory evos, int gen) => gen switch + { + 5 when pk.Format == 5 => false, + 8 when !pk.HasVisitedSWSH(evos.Gen8) && !pk.HasVisitedBDSP(evos.Gen8b) => false, + _ => true, + }; - var c4 = u4.RibbonBitsContest4(); - var c4n = u4.RibbonNamesContest4(); - var iter4 = (gen is 3 or 4) && IsAllowedInContest4(pkm.Species, pkm.Form) ? GetMissingContestRibbons(c4, c4n) : GetInvalidRibbonsNone(c4, c4n); - foreach (var z in iter4) - yield return z; - } - if (pkm is IRibbonSetCommon4 s4) - { - bool inhabited4 = gen is 3 or 4; - var iterate = GetInvalidRibbons4Any(pkm, evos, s4, gen); - if (!inhabited4) - { - if (pkm.HasVisitedBDSP(evos.Gen8b)) // Allow Sinnoh Champion. ILCA reused the Gen4 ribbon for the remake. - iterate = iterate.Concat(GetInvalidRibbonsNoneSkipIndex(s4.RibbonBitsOnly(), s4.RibbonNamesOnly(), 1)); - else - iterate = iterate.Concat(GetInvalidRibbonsNone(s4.RibbonBitsOnly(), s4.RibbonNamesOnly())); - } - foreach (var z in iterate) - yield return z; - } - if (pkm is IRibbonSetCommon6 s6) - { - bool inhabited6 = gen is >= 3 and <= 6; + private static bool IsRibbonValidBestFriend(PKM pk, EvolutionHistory evos, int gen) => gen switch + { + < 7 when pk is { IsUntraded: true } and IAffection { OT_Affection: < 255 } => false, // Gen6/7 uses affection. Can't lower it on OT! + 8 when !pk.HasVisitedSWSH(evos.Gen8) && !pk.HasVisitedBDSP(evos.Gen8b) => false, // Gen8+ replaced with Max Friendship. + _ => true, + }; - var iterate = inhabited6 - ? GetInvalidRibbons6Any(pkm, s6, gen, enc) - : pkm.Format >= 8 - ? GetInvalidRibbons6AnyG8(pkm, s6, evos) - : GetInvalidRibbonsNone(s6.RibbonBits(), s6.RibbonNamesBool()); - foreach (var z in iterate) - yield return z; - - if (!inhabited6) - { - if (s6.RibbonCountMemoryContest > 0) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryContest)); - if (s6.RibbonCountMemoryBattle > 0) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryBattle)); - } - - if (s6.RibbonBestFriends && !IsRibbonValidBestFriend(pkm, evos, gen)) - yield return new RibbonResult(nameof(IRibbonSetCommon6.RibbonBestFriends)); - } - if (pkm is IRibbonSetCommon7 s7) + private static IEnumerable GetMissingContestRibbons(IReadOnlyList bits, IReadOnlyList names) + { + for (int i = 0; i < bits.Count; i += 4) + { + bool required = false; + for (int j = i + 3; j >= i; j--) { - bool inhabited7 = gen <= 7 && !pkm.GG; - var iterate = inhabited7 ? GetInvalidRibbons7Any(pkm, s7) : GetInvalidRibbonsNone(s7.RibbonBits(), s7.RibbonNames()); - foreach (var z in iterate) - yield return z; - } - if (pkm is IRibbonSetCommon3 s3) - { - if (s3.RibbonChampionG3 && gen != 3) - yield return new RibbonResult(nameof(s3.RibbonChampionG3)); // RSE HoF - if (s3.RibbonArtist && gen != 3) - yield return new RibbonResult(nameof(s3.RibbonArtist)); // RSE Master Rank Portrait - if (s3.RibbonEffort && !IsRibbonValidEffort(pkm, evos, gen)) // unobtainable in Gen 5 - yield return new RibbonResult(nameof(s3.RibbonEffort)); - } - if (pkm is IRibbonSetCommon8 s8) - { - bool inhabited8 = gen <= 8; - var iterate = inhabited8 ? GetInvalidRibbons8Any(pkm, s8, enc, evos) : GetInvalidRibbonsNone(s8.RibbonBits(), s8.RibbonNames()); - foreach (var z in iterate) - yield return z; + if (bits[j]) + required = true; + else if (required) yield return new RibbonResult(names[j], false); } } + } - private static bool IsRibbonValidEffort(PKM pkm, EvolutionHistory evos, int gen) => gen switch - { - 5 when pkm.Format == 5 => false, - 8 when !pkm.HasVisitedSWSH(evos.Gen8) && !pkm.HasVisitedBDSP(evos.Gen8b) => false, - _ => true, - }; + private static IEnumerable GetInvalidRibbons4Any(PKM pk, EvolutionHistory evos, IRibbonSetCommon4 s4, int gen) + { + if (s4.RibbonRecord) + yield return new RibbonResult(nameof(s4.RibbonRecord)); // Unobtainable + if (s4.RibbonFootprint && !CanHaveFootprintRibbon(pk, evos, gen)) + yield return new RibbonResult(nameof(s4.RibbonFootprint)); - private static bool IsRibbonValidBestFriend(PKM pkm, EvolutionHistory evos, int gen) => gen switch - { - < 7 when pkm is { IsUntraded: true } and IAffection { OT_Affection: < 255 } => false, // Gen6/7 uses affection. Can't lower it on OT! - 8 when !pkm.HasVisitedSWSH(evos.Gen8) && !pkm.HasVisitedBDSP(evos.Gen8b) => false, // Gen8+ replaced with Max Friendship. - _ => true, - }; + bool visitBDSP = pk.HasVisitedBDSP(evos.Gen8b); + bool gen34 = gen is 3 or 4; + bool not6 = pk.Format < 6 || gen is > 6 or < 3; + bool noDaily = !gen34 && not6 && !visitBDSP; + bool noSinnoh = pk is G4PKM { Species: (int)Species.Pichu, Form: 1 }; // Spiky Pichu + bool noCosmetic = (!gen34 && (not6 || (pk.XY && pk.IsUntraded)) && !visitBDSP) || noSinnoh; - private static IEnumerable GetMissingContestRibbons(IReadOnlyList bits, IReadOnlyList names) + if (noSinnoh) { - for (int i = 0; i < bits.Count; i += 4) - { - bool required = false; - for (int j = i + 3; j >= i; j--) - { - if (bits[j]) - required = true; - else if (required) yield return new RibbonResult(names[j], false); - } - } + if (s4.RibbonChampionSinnoh) + yield return new RibbonResult(nameof(s4.RibbonChampionSinnoh)); } - private static IEnumerable GetInvalidRibbons4Any(PKM pkm, EvolutionHistory evos, IRibbonSetCommon4 s4, int gen) + if (noDaily) { - if (s4.RibbonRecord) - yield return new RibbonResult(nameof(s4.RibbonRecord)); // Unobtainable - if (s4.RibbonFootprint && !CanHaveFootprintRibbon(pkm, evos, gen)) - yield return new RibbonResult(nameof(s4.RibbonFootprint)); - - bool visitBDSP = pkm.HasVisitedBDSP(evos.Gen8b); - bool gen34 = gen is 3 or 4; - bool not6 = pkm.Format < 6 || gen is > 6 or < 3; - bool noDaily = !gen34 && not6 && !visitBDSP; - bool noSinnoh = pkm is G4PKM { Species: (int)Species.Pichu, Form: 1 }; // Spiky Pichu - bool noCosmetic = (!gen34 && (not6 || (pkm.XY && pkm.IsUntraded)) && !visitBDSP) || noSinnoh; - - if (noSinnoh) - { - if (s4.RibbonChampionSinnoh) - yield return new RibbonResult(nameof(s4.RibbonChampionSinnoh)); - } - - if (noDaily) - { - foreach (var z in GetInvalidRibbonsNone(s4.RibbonBitsDaily(), s4.RibbonNamesDaily())) - yield return z; - } - - if (noCosmetic) - { - foreach (var z in GetInvalidRibbonsNone(s4.RibbonBitsCosmetic(), s4.RibbonNamesCosmetic())) - yield return z; - } + foreach (var z in GetInvalidRibbonsNone(s4.RibbonBitsDaily(), s4.RibbonNamesDaily())) + yield return z; } - private static IEnumerable GetInvalidRibbons6Any(PKM pkm, IRibbonSetCommon6 s6, int gen, IEncounterTemplate enc) + if (noCosmetic) { - foreach (var p in GetInvalidRibbons6Memory(pkm, s6, gen, enc)) - yield return p; + foreach (var z in GetInvalidRibbonsNone(s4.RibbonBitsCosmetic(), s4.RibbonNamesCosmetic())) + yield return z; + } + } - bool untraded = pkm.IsUntraded || (enc is EncounterStatic6 {Species:(int)Species.Pikachu, Form: not 0}); // Disallow cosplay pikachu from XY ribbons - var iter = untraded ? GetInvalidRibbons6Untraded(pkm, s6) : GetInvalidRibbons6Traded(pkm, s6); - foreach (var p in iter) - yield return p; + private static IEnumerable GetInvalidRibbons6Any(PKM pk, IRibbonSetCommon6 s6, int gen, IEncounterTemplate enc) + { + foreach (var p in GetInvalidRibbons6Memory(pk, s6, gen, enc)) + yield return p; - var contest = s6.RibbonBitsContest(); - bool allContest = contest.All(z => z); - if ((allContest != s6.RibbonContestStar) && !(untraded && pkm.XY)) // if not already checked - yield return new RibbonResult(nameof(s6.RibbonContestStar), s6.RibbonContestStar); + bool untraded = pk.IsUntraded || (enc is EncounterStatic6 {Species:(int)Species.Pikachu, Form: not 0}); // Disallow cosplay pikachu from XY ribbons + var iter = untraded ? GetInvalidRibbons6Untraded(pk, s6) : GetInvalidRibbons6Traded(pk, s6); + foreach (var p in iter) + yield return p; - // Each contest victory requires a contest participation; each participation gives 20 OT affection (not current trainer). - // Affection is discarded on PK7->PK8 in favor of friendship, which can be lowered. - if (pkm is IAffection a) - { - var affect = a.OT_Affection; - var contMemory = s6.RibbonNamesContest(); - int contCount = 0; - var present = contMemory.Where((_, i) => contest[i] && affect < 20 * ++contCount); - foreach (var rib in present) - yield return new RibbonResult(rib); - } + var contest = s6.RibbonBitsContest(); + bool allContest = contest.All(z => z); + if ((allContest != s6.RibbonContestStar) && !(untraded && pk.XY)) // if not already checked + yield return new RibbonResult(nameof(s6.RibbonContestStar), s6.RibbonContestStar); - // Gen6 can get the memory on those who did not participate by being in the party with other participants. - // This includes those who cannot enter into the Maison; having memory and no ribbon. - const int memChatelaine = 30; - bool hasChampMemory = enc.Generation == 7 && pkm.Format == 7 && pkm is ITrainerMemories m && (m.HT_Memory == memChatelaine || m.OT_Memory == memChatelaine); - if (!IsAllowedBattleFrontier(pkm.Species)) - { - if (hasChampMemory || s6.RibbonBattlerSkillful) // having memory and not ribbon is too rare, just flag here. - yield return new RibbonResult(nameof(s6.RibbonBattlerSkillful)); - if (s6.RibbonBattlerExpert) - yield return new RibbonResult(nameof(s6.RibbonBattlerExpert)); - yield break; - } - if (!hasChampMemory || s6.RibbonBattlerSkillful || s6.RibbonBattlerExpert) - yield break; - - var result = new RibbonResult(nameof(s6.RibbonBattlerSkillful), false); - result.Combine(new RibbonResult(nameof(s6.RibbonBattlerExpert))); - yield return result; + // Each contest victory requires a contest participation; each participation gives 20 OT affection (not current trainer). + // Affection is discarded on PK7->PK8 in favor of friendship, which can be lowered. + if (pk is IAffection a) + { + var affect = a.OT_Affection; + var contMemory = s6.RibbonNamesContest(); + int contCount = 0; + var present = contMemory.Where((_, i) => contest[i] && affect < 20 * ++contCount); + foreach (var rib in present) + yield return new RibbonResult(rib); } - private static IEnumerable GetInvalidRibbons6AnyG8(PKM pkm, IRibbonSetCommon6 s6, EvolutionHistory evos) + // Gen6 can get the memory on those who did not participate by being in the party with other participants. + // This includes those who cannot enter into the Maison; having memory and no ribbon. + const int memChatelaine = 30; + bool hasChampMemory = enc.Generation == 7 && pk.Format == 7 && pk is ITrainerMemories m && (m.HT_Memory == memChatelaine || m.OT_Memory == memChatelaine); + if (!IsAllowedBattleFrontier(pk.Species)) { - if (!pkm.HasVisitedBDSP(evos.Gen8b)) - { - var none = GetInvalidRibbonsNone(s6.RibbonBits(), s6.RibbonNamesBool()); - foreach (var x in none) - yield return x; - yield break; - } - - if (s6.RibbonChampionKalos) - yield return new RibbonResult(nameof(s6.RibbonChampionKalos)); - if (s6.RibbonChampionG6Hoenn) - yield return new RibbonResult(nameof(s6.RibbonChampionG6Hoenn)); - //if (s6.RibbonBestFriends) - // yield return new RibbonResult(nameof(s6.RibbonBestFriends)); - if (s6.RibbonTraining) - yield return new RibbonResult(nameof(s6.RibbonTraining)); - if (s6.RibbonBattlerSkillful) + if (hasChampMemory || s6.RibbonBattlerSkillful) // having memory and not ribbon is too rare, just flag here. yield return new RibbonResult(nameof(s6.RibbonBattlerSkillful)); if (s6.RibbonBattlerExpert) yield return new RibbonResult(nameof(s6.RibbonBattlerExpert)); - - if (s6.RibbonCountMemoryContest != 0) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryContest)); - if (s6.RibbonCountMemoryBattle != 0) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryBattle)); - - // Can get contest ribbons via BD/SP contests. - //if (s6.RibbonContestStar) - // yield return new RibbonResult(nameof(s6.RibbonContestStar)); - //if (s6.RibbonMasterCoolness) - // yield return new RibbonResult(nameof(s6.RibbonMasterCoolness)); - //if (s6.RibbonMasterBeauty) - // yield return new RibbonResult(nameof(s6.RibbonMasterBeauty)); - //if (s6.RibbonMasterCuteness) - // yield return new RibbonResult(nameof(s6.RibbonMasterCuteness)); - //if (s6.RibbonMasterCleverness) - // yield return new RibbonResult(nameof(s6.RibbonMasterCleverness)); - //if (s6.RibbonMasterToughness) - // yield return new RibbonResult(nameof(s6.RibbonMasterToughness)); - - var contest = s6.RibbonBitsContest(); - bool allContest = contest.All(z => z); - if (allContest != s6.RibbonContestStar) // if not already checked - yield return new RibbonResult(nameof(s6.RibbonContestStar), s6.RibbonContestStar); + yield break; } + if (!hasChampMemory || s6.RibbonBattlerSkillful || s6.RibbonBattlerExpert) + yield break; - private static IEnumerable GetInvalidRibbons6Memory(PKM pkm, IRibbonSetCommon6 s6, int gen, IEncounterTemplate enc) - { - int contest = 0; - int battle = 0; - switch (gen) - { - case 3: - contest = IsAllowedInContest4(pkm.Species, pkm.Form) ? 40 : 20; - battle = IsAllowedBattleFrontier(pkm.Species) ? CanHaveRibbonWinning(pkm, enc, 3) ? 8 : 7 : 0; - break; - case 4: - contest = IsAllowedInContest4(pkm.Species, pkm.Form) ? 20 : 0; - battle = IsAllowedBattleFrontier(pkm.Species) ? 6 : 0; - break; - } - if (s6.RibbonCountMemoryContest > contest) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryContest)); - if (s6.RibbonCountMemoryBattle > battle) - yield return new RibbonResult(nameof(s6.RibbonCountMemoryBattle)); - } - - private static IEnumerable GetInvalidRibbons6Untraded(PKM pkm, IRibbonSetCommon6 s6) - { - if (pkm.XY) - { - if (s6.RibbonChampionG6Hoenn) - yield return new RibbonResult(nameof(s6.RibbonChampionG6Hoenn)); - - if (s6.RibbonContestStar) - yield return new RibbonResult(nameof(s6.RibbonContestStar)); - if (s6.RibbonMasterCoolness) - yield return new RibbonResult(nameof(s6.RibbonMasterCoolness)); - if (s6.RibbonMasterBeauty) - yield return new RibbonResult(nameof(s6.RibbonMasterBeauty)); - if (s6.RibbonMasterCuteness) - yield return new RibbonResult(nameof(s6.RibbonMasterCuteness)); - if (s6.RibbonMasterCleverness) - yield return new RibbonResult(nameof(s6.RibbonMasterCleverness)); - if (s6.RibbonMasterToughness) - yield return new RibbonResult(nameof(s6.RibbonMasterToughness)); - } - else if (pkm.AO) - { - if (s6.RibbonChampionKalos) - yield return new RibbonResult(nameof(s6.RibbonChampionKalos)); - } - } - - private static IEnumerable GetInvalidRibbons6Traded(PKM pkm, IRibbonSetCommon6 s6) - { - // Medal count is wiped on transfer to pk8 - if (s6.RibbonTraining && pkm.Format <= 7) - { - const int req = 12; // only first 12 - int count = ((ISuperTrain)pkm).SuperTrainingMedalCount(req); - if (count < req) - yield return new RibbonResult(nameof(s6.RibbonTraining)); - } - - const int memChampion = 27; - bool hasChampMemory = pkm is ITrainerMemories m && ((pkm.Format < 8 && m.HT_Memory == memChampion) || (pkm.Gen6 && m.OT_Memory == memChampion)); - if (!hasChampMemory || s6.RibbonChampionKalos || s6.RibbonChampionG6Hoenn) - yield break; - - var result = new RibbonResult(nameof(s6.RibbonChampionKalos), false); - result.Combine(new RibbonResult(nameof(s6.RibbonChampionG6Hoenn))); - yield return result; - } - - private static IEnumerable GetInvalidRibbons7Any(PKM pkm, IRibbonSetCommon7 s7) - { - if (!IsAllowedBattleFrontier(pkm.Species)) - { - if (s7.RibbonBattleRoyale) - yield return new RibbonResult(nameof(s7.RibbonBattleRoyale)); - if (s7.RibbonBattleTreeGreat && !pkm.USUM && pkm.IsUntraded) - yield return new RibbonResult(nameof(s7.RibbonBattleTreeGreat)); - if (s7.RibbonBattleTreeMaster) - yield return new RibbonResult(nameof(s7.RibbonBattleTreeMaster)); - } - } - - private static IEnumerable GetInvalidRibbons8Any(PKM pkm, IRibbonSetCommon8 s8, IEncounterTemplate enc, EvolutionHistory evos) - { - bool swsh = pkm.HasVisitedSWSH(evos.Gen8); - bool bdsp = pkm.HasVisitedBDSP(evos.Gen8b); - bool pla = pkm.HasVisitedLA(evos.Gen8a); - - if (!swsh && !bdsp) - { - if (s8.RibbonTowerMaster) - yield return new RibbonResult(nameof(s8.RibbonTowerMaster)); - } - if (!swsh) - { - if (s8.RibbonChampionGalar) - yield return new RibbonResult(nameof(s8.RibbonChampionGalar)); - if (s8.RibbonMasterRank) - yield return new RibbonResult(nameof(s8.RibbonMasterRank)); - } - else - { - const int memChampion = 27; - { - bool hasChampMemory = (pkm.Format == 8 && pkm is IMemoryHT {HT_Memory: memChampion}) || - (enc.Generation == 8 && pkm is IMemoryOT {OT_Memory: memChampion}); - if (hasChampMemory && !s8.RibbonChampionGalar) - yield return new RibbonResult(nameof(s8.RibbonChampionGalar)); - } - - // Legends cannot compete in Ranked, thus cannot reach Master Rank and obtain the ribbon. - // Past gen Pokemon can get the ribbon only if they've been reset. - if (s8.RibbonMasterRank && !CanParticipateInRankedSWSH(pkm, enc, evos)) - yield return new RibbonResult(nameof(s8.RibbonMasterRank)); - - if (!s8.RibbonTowerMaster) - { - // If the Tower Master ribbon is not present but a memory hint implies it should... - // This memory can also be applied in Gen6/7 via defeating the Chatelaines, where legends are disallowed. - const int strongest = 30; - if (pkm is IMemoryOT {OT_Memory: strongest} or IMemoryHT {HT_Memory: strongest}) - { - if (enc.Generation == 8 || !IsAllowedBattleFrontier(pkm.Species) || pkm is IRibbonSetCommon6 {RibbonBattlerSkillful: false}) - yield return new RibbonResult(nameof(s8.RibbonTowerMaster)); - } - } - } - - if (s8.RibbonTwinklingStar && (!bdsp || pkm is IRibbonSetCommon6 {RibbonContestStar:false})) - { - yield return new RibbonResult(nameof(s8.RibbonTwinklingStar)); - } - - // received when capturing photos with Pokémon in the Photography Studio - if (s8.RibbonPioneer && !pla) - { - yield return new RibbonResult(nameof(s8.RibbonPioneer)); - } - } - - private static bool CanParticipateInRankedSWSH(PKM pkm, IEncounterTemplate enc, EvolutionHistory evos) - { - bool exist = enc.Generation switch - { - < 8 => pkm is IBattleVersion { BattleVersion: (int)GameVersion.SW or (int)GameVersion.SH }, - _ => pkm.HasVisitedSWSH(evos.Gen8), - }; - if (!exist) - return false; - - // Clamp to permitted species - var species = pkm.Species; - if (species > Legal.MaxSpeciesID_8_R2) - return false; - if (Legal.Legends.Contains(species)) - { - // Box Legends were only allowed for a single rule-set until May 1st. - // This rule-set disallowed Mythicals, but everything else present in the game was usable. - if (Legal.Mythicals.Contains(species)) - return false; - - if (enc.Version == GameVersion.GO || enc is IEncounterServerDate { IsDateRestricted: true }) // Capture date is global time, and not console changeable. - { - if (pkm.MetDate > new DateTime(2022, 9, 1)) // Series 12 end date - return false; - } - } - - return PersonalTable.SWSH.IsPresentInGame(species, pkm.Form); - } - - private static IEnumerable GetInvalidRibbonsEvent1(PKM pkm, IEncounterTemplate enc) - { - if (pkm is not IRibbonSetEvent3 set1) - yield break; - var names = set1.RibbonNames(); - var sb = set1.RibbonBits(); - var eb = enc is IRibbonSetEvent3 e3 ? e3.RibbonBits() : new bool[sb.Length]; - - if (enc.Generation == 3) - { - eb[0] = sb[0]; // permit Earth Ribbon - if (pkm.Version == 15 && enc is EncounterStaticShadow s) - { - // only require national ribbon if no longer on origin game - bool untraded = s.Version == GameVersion.XD - ? pkm is XK3 {RibbonNational: false} - : pkm is CK3 {RibbonNational: false}; - eb[1] = !untraded; - } - } - - for (int i = 0; i < sb.Length; i++) - { - if (sb[i] != eb[i]) - yield return new RibbonResult(names[i], !eb[i]); // only flag if invalid - } - } - - private static IEnumerable GetInvalidRibbonsEvent2(PKM pkm, IEncounterTemplate enc) - { - if (pkm is not IRibbonSetEvent4 set2) - yield break; - var names = set2.RibbonNames(); - var sb = set2.RibbonBits(); - var eb = enc is IRibbonSetEvent4 e4 ? e4.RibbonBits() : new bool[sb.Length]; - - if (enc is EncounterStatic7 {Species: (int)Species.Magearna}) - eb[1] = true; // require Wishing Ribbon - - for (int i = 0; i < sb.Length; i++) - { - if (sb[i] != eb[i]) - yield return new RibbonResult(names[i], !eb[i]); // only flag if invalid - } - } - - private static IEnumerable GetInvalidRibbonsNone(IReadOnlyList bits, IReadOnlyList names) - { - for (int i = 0; i < bits.Count; i++) - { - if (bits[i]) - yield return new RibbonResult(names[i]); - } - } - - private static IEnumerable GetInvalidRibbonsNoneSkipIndex(IReadOnlyList bits, IReadOnlyList names, int skipIndex) - { - for (int i = 0; i < bits.Count; i++) - { - if (bits[i] && i != skipIndex) - yield return new RibbonResult(names[i]); - } - } - - private static bool IsAllowedInContest4(int species, int form) => species switch - { - // Disallow Unown and Ditto, and Spiky Pichu (cannot trade) - (int)Species.Ditto => false, - (int)Species.Unown => false, - (int)Species.Pichu when form == 1 => false, - _ => true, - }; - - private static bool IsAllowedBattleFrontier(int species) => !Legal.BattleFrontierBanlist.Contains(species); - - private static bool IsAllowedBattleFrontier(int species, int form, int gen) - { - if (gen == 4 && species == (int)Species.Pichu && form == 1) // spiky - return false; - return IsAllowedBattleFrontier(species); - } - - private static bool CanHaveFootprintRibbon(PKM pkm, EvolutionHistory evos, int gen) - { - if (gen <= 4) // Friendship Check unnecessary - can decrease after obtaining ribbon. - return true; - // Gen5: Can't obtain - if (pkm.Format < 6) - return false; - - // Gen6/7: Increase level by 30 from original level - if (gen != 8 && !pkm.GG && (pkm.CurrentLevel - pkm.Met_Level >= 30)) - return true; - - // Gen8-BDSP: Variable by species Footprint - var bdspEvos = evos.Gen8b; - if (pkm.HasVisitedBDSP(bdspEvos)) - { - if (bdspEvos.Any(z => PersonalTable.BDSP.IsPresentInGame(z.Species, z.Form) && !HasFootprintBDSP[z.Species])) - return true; // no footprint - if (pkm.CurrentLevel - pkm.Met_Level >= 30) - return true; // traveled well - } - - // Otherwise: Can't obtain - return false; - } - - private static bool CanHaveRibbonWinning(PKM pkm, IEncounterTemplate enc, int gen) - { - if (gen != 3) - return false; - if (!IsAllowedBattleFrontier(pkm.Species)) - return false; - if (pkm.Format == 3) - return pkm.Met_Level <= 50; - - // Most encounter types can be below level 50; only Shadow Dragonite & Tyranitar, and select Gen3 Event Gifts. - // These edge cases can't be obtained below level 50, unlike some wild Pokémon which can be encountered at different locations for lower levels. - if (enc.LevelMin <= 50) - return true; - - return enc is not (EncounterStaticShadow or WC3); - } - - private static bool CanHaveRibbonVictory(PKM pkm, int gen) - { - return gen == 3 && IsAllowedBattleFrontier(pkm.Species); - } - - // Footprint type is not Type 5, requiring 30 levels. - private static readonly bool[] HasFootprintBDSP = - { - true, true, true, true, true, true, true, true, true, true, - true, false, true, true, false, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, false, false, true, false, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, false, false, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - false, false, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, false, true, true, - false, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, false, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, false, true, true, false, false, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, false, true, true, true, true, true, true, - true, true, true, false, true, true, true, true, true, true, - true, true, true, true, true, true, true, false, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, false, true, false, true, - true, true, true, false, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - false, true, true, true, true, true, true, true, true, false, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, false, false, true, - true, true, true, false, false, false, false, false, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, false, true, false, false, true, false, false, false, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, false, false, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, - true, true, false, true, true, true, true, true, true, true, - true, true, true, true, false, true, false, true, true, true, - true, true, true, true, true, true, false, true, true, true, - true, true, true, true, - }; + var result = new RibbonResult(nameof(s6.RibbonBattlerSkillful), false); + result.Combine(new RibbonResult(nameof(s6.RibbonBattlerExpert))); + yield return result; } + + private static IEnumerable GetInvalidRibbons6AnyG8(PKM pk, IRibbonSetCommon6 s6, EvolutionHistory evos) + { + if (!pk.HasVisitedBDSP(evos.Gen8b)) + { + var none = GetInvalidRibbonsNone(s6.RibbonBits(), s6.RibbonNamesBool()); + foreach (var x in none) + yield return x; + yield break; + } + + if (s6.RibbonChampionKalos) + yield return new RibbonResult(nameof(s6.RibbonChampionKalos)); + if (s6.RibbonChampionG6Hoenn) + yield return new RibbonResult(nameof(s6.RibbonChampionG6Hoenn)); + //if (s6.RibbonBestFriends) + // yield return new RibbonResult(nameof(s6.RibbonBestFriends)); + if (s6.RibbonTraining) + yield return new RibbonResult(nameof(s6.RibbonTraining)); + if (s6.RibbonBattlerSkillful) + yield return new RibbonResult(nameof(s6.RibbonBattlerSkillful)); + if (s6.RibbonBattlerExpert) + yield return new RibbonResult(nameof(s6.RibbonBattlerExpert)); + + if (s6.RibbonCountMemoryContest != 0) + yield return new RibbonResult(nameof(s6.RibbonCountMemoryContest)); + if (s6.RibbonCountMemoryBattle != 0) + yield return new RibbonResult(nameof(s6.RibbonCountMemoryBattle)); + + // Can get contest ribbons via BD/SP contests. + //if (s6.RibbonContestStar) + // yield return new RibbonResult(nameof(s6.RibbonContestStar)); + //if (s6.RibbonMasterCoolness) + // yield return new RibbonResult(nameof(s6.RibbonMasterCoolness)); + //if (s6.RibbonMasterBeauty) + // yield return new RibbonResult(nameof(s6.RibbonMasterBeauty)); + //if (s6.RibbonMasterCuteness) + // yield return new RibbonResult(nameof(s6.RibbonMasterCuteness)); + //if (s6.RibbonMasterCleverness) + // yield return new RibbonResult(nameof(s6.RibbonMasterCleverness)); + //if (s6.RibbonMasterToughness) + // yield return new RibbonResult(nameof(s6.RibbonMasterToughness)); + + var contest = s6.RibbonBitsContest(); + bool allContest = contest.All(z => z); + if (allContest != s6.RibbonContestStar) // if not already checked + yield return new RibbonResult(nameof(s6.RibbonContestStar), s6.RibbonContestStar); + } + + private static IEnumerable GetInvalidRibbons6Memory(PKM pk, IRibbonSetCommon6 s6, int gen, IEncounterTemplate enc) + { + int contest = 0; + int battle = 0; + switch (gen) + { + case 3: + contest = IsAllowedInContest4(pk.Species, pk.Form) ? 40 : 20; + battle = IsAllowedBattleFrontier(pk.Species) ? CanHaveRibbonWinning(pk, enc, 3) ? 8 : 7 : 0; + break; + case 4: + contest = IsAllowedInContest4(pk.Species, pk.Form) ? 20 : 0; + battle = IsAllowedBattleFrontier(pk.Species) ? 6 : 0; + break; + } + if (s6.RibbonCountMemoryContest > contest) + yield return new RibbonResult(nameof(s6.RibbonCountMemoryContest)); + if (s6.RibbonCountMemoryBattle > battle) + yield return new RibbonResult(nameof(s6.RibbonCountMemoryBattle)); + } + + private static IEnumerable GetInvalidRibbons6Untraded(PKM pk, IRibbonSetCommon6 s6) + { + if (pk.XY) + { + if (s6.RibbonChampionG6Hoenn) + yield return new RibbonResult(nameof(s6.RibbonChampionG6Hoenn)); + + if (s6.RibbonContestStar) + yield return new RibbonResult(nameof(s6.RibbonContestStar)); + if (s6.RibbonMasterCoolness) + yield return new RibbonResult(nameof(s6.RibbonMasterCoolness)); + if (s6.RibbonMasterBeauty) + yield return new RibbonResult(nameof(s6.RibbonMasterBeauty)); + if (s6.RibbonMasterCuteness) + yield return new RibbonResult(nameof(s6.RibbonMasterCuteness)); + if (s6.RibbonMasterCleverness) + yield return new RibbonResult(nameof(s6.RibbonMasterCleverness)); + if (s6.RibbonMasterToughness) + yield return new RibbonResult(nameof(s6.RibbonMasterToughness)); + } + else if (pk.AO) + { + if (s6.RibbonChampionKalos) + yield return new RibbonResult(nameof(s6.RibbonChampionKalos)); + } + } + + private static IEnumerable GetInvalidRibbons6Traded(PKM pk, IRibbonSetCommon6 s6) + { + // Medal count is wiped on transfer to pk8 + if (s6.RibbonTraining && pk.Format <= 7) + { + const int req = 12; // only first 12 + int count = ((ISuperTrain)pk).SuperTrainingMedalCount(req); + if (count < req) + yield return new RibbonResult(nameof(s6.RibbonTraining)); + } + + const int memChampion = 27; + bool hasChampMemory = pk is ITrainerMemories m && ((pk.Format < 8 && m.HT_Memory == memChampion) || (pk.Gen6 && m.OT_Memory == memChampion)); + if (!hasChampMemory || s6.RibbonChampionKalos || s6.RibbonChampionG6Hoenn) + yield break; + + var result = new RibbonResult(nameof(s6.RibbonChampionKalos), false); + result.Combine(new RibbonResult(nameof(s6.RibbonChampionG6Hoenn))); + yield return result; + } + + private static IEnumerable GetInvalidRibbons7Any(PKM pk, IRibbonSetCommon7 s7) + { + if (!IsAllowedBattleFrontier(pk.Species)) + { + if (s7.RibbonBattleRoyale) + yield return new RibbonResult(nameof(s7.RibbonBattleRoyale)); + if (s7.RibbonBattleTreeGreat && !pk.USUM && pk.IsUntraded) + yield return new RibbonResult(nameof(s7.RibbonBattleTreeGreat)); + if (s7.RibbonBattleTreeMaster) + yield return new RibbonResult(nameof(s7.RibbonBattleTreeMaster)); + } + } + + private static IEnumerable GetInvalidRibbons8Any(PKM pk, IRibbonSetCommon8 s8, IEncounterTemplate enc, EvolutionHistory evos) + { + bool swsh = pk.HasVisitedSWSH(evos.Gen8); + bool bdsp = pk.HasVisitedBDSP(evos.Gen8b); + bool pla = pk.HasVisitedLA(evos.Gen8a); + + if (!swsh && !bdsp) + { + if (s8.RibbonTowerMaster) + yield return new RibbonResult(nameof(s8.RibbonTowerMaster)); + } + if (!swsh) + { + if (s8.RibbonChampionGalar) + yield return new RibbonResult(nameof(s8.RibbonChampionGalar)); + if (s8.RibbonMasterRank) + yield return new RibbonResult(nameof(s8.RibbonMasterRank)); + } + else + { + const int memChampion = 27; + { + bool hasChampMemory = (pk.Format == 8 && pk is IMemoryHT {HT_Memory: memChampion}) || + (enc.Generation == 8 && pk is IMemoryOT {OT_Memory: memChampion}); + if (hasChampMemory && !s8.RibbonChampionGalar) + yield return new RibbonResult(nameof(s8.RibbonChampionGalar)); + } + + // Legends cannot compete in Ranked, thus cannot reach Master Rank and obtain the ribbon. + // Past gen Pokemon can get the ribbon only if they've been reset. + if (s8.RibbonMasterRank && !CanParticipateInRankedSWSH(pk, enc, evos)) + yield return new RibbonResult(nameof(s8.RibbonMasterRank)); + + if (!s8.RibbonTowerMaster) + { + // If the Tower Master ribbon is not present but a memory hint implies it should... + // This memory can also be applied in Gen6/7 via defeating the Chatelaines, where legends are disallowed. + const int strongest = 30; + if (pk is IMemoryOT {OT_Memory: strongest} or IMemoryHT {HT_Memory: strongest}) + { + if (enc.Generation == 8 || !IsAllowedBattleFrontier(pk.Species) || pk is IRibbonSetCommon6 {RibbonBattlerSkillful: false}) + yield return new RibbonResult(nameof(s8.RibbonTowerMaster)); + } + } + } + + if (s8.RibbonTwinklingStar && (!bdsp || pk is IRibbonSetCommon6 {RibbonContestStar:false})) + { + yield return new RibbonResult(nameof(s8.RibbonTwinklingStar)); + } + + // received when capturing photos with Pokémon in the Photography Studio + if (s8.RibbonPioneer && !pla) + { + yield return new RibbonResult(nameof(s8.RibbonPioneer)); + } + } + + private static bool CanParticipateInRankedSWSH(PKM pk, IEncounterTemplate enc, EvolutionHistory evos) + { + bool exist = enc.Generation switch + { + < 8 => pk is IBattleVersion { BattleVersion: (int)GameVersion.SW or (int)GameVersion.SH }, + _ => pk.HasVisitedSWSH(evos.Gen8), + }; + if (!exist) + return false; + + // Clamp to permitted species + var species = pk.Species; + if (species > Legal.MaxSpeciesID_8_R2) + return false; + if (Legal.Legends.Contains(species)) + { + // Box Legends were only allowed for a single rule-set until May 1st. + // This rule-set disallowed Mythicals, but everything else present in the game was usable. + if (Legal.Mythicals.Contains(species)) + return false; + + if (enc.Version == GameVersion.GO || enc is IEncounterServerDate { IsDateRestricted: true }) // Capture date is global time, and not console changeable. + { + if (pk.MetDate > new DateTime(2022, 9, 1)) // Series 12 end date + return false; + } + } + + return PersonalTable.SWSH.IsPresentInGame(species, pk.Form); + } + + private static IEnumerable GetInvalidRibbonsEvent1(PKM pk, IEncounterTemplate enc) + { + if (pk is not IRibbonSetEvent3 set1) + yield break; + var names = set1.RibbonNames(); + var sb = set1.RibbonBits(); + var eb = enc is IRibbonSetEvent3 e3 ? e3.RibbonBits() : new bool[sb.Length]; + + if (enc.Generation == 3) + { + eb[0] = sb[0]; // permit Earth Ribbon + if (pk.Version == 15 && enc is EncounterStaticShadow s) + { + // only require national ribbon if no longer on origin game + bool untraded = s.Version == GameVersion.XD + ? pk is XK3 {RibbonNational: false} + : pk is CK3 {RibbonNational: false}; + eb[1] = !untraded; + } + } + + for (int i = 0; i < sb.Length; i++) + { + if (sb[i] != eb[i]) + yield return new RibbonResult(names[i], !eb[i]); // only flag if invalid + } + } + + private static IEnumerable GetInvalidRibbonsEvent2(PKM pk, IEncounterTemplate enc) + { + if (pk is not IRibbonSetEvent4 set2) + yield break; + var names = set2.RibbonNames(); + var sb = set2.RibbonBits(); + var eb = enc is IRibbonSetEvent4 e4 ? e4.RibbonBits() : new bool[sb.Length]; + + if (enc is EncounterStatic7 {Species: (int)Species.Magearna}) + eb[1] = true; // require Wishing Ribbon + + for (int i = 0; i < sb.Length; i++) + { + if (sb[i] != eb[i]) + yield return new RibbonResult(names[i], !eb[i]); // only flag if invalid + } + } + + private static IEnumerable GetInvalidRibbonsNone(IReadOnlyList bits, IReadOnlyList names) + { + for (int i = 0; i < bits.Count; i++) + { + if (bits[i]) + yield return new RibbonResult(names[i]); + } + } + + private static IEnumerable GetInvalidRibbonsNoneSkipIndex(IReadOnlyList bits, IReadOnlyList names, int skipIndex) + { + for (int i = 0; i < bits.Count; i++) + { + if (bits[i] && i != skipIndex) + yield return new RibbonResult(names[i]); + } + } + + private static bool IsAllowedInContest4(int species, int form) => species switch + { + // Disallow Unown and Ditto, and Spiky Pichu (cannot trade) + (int)Species.Ditto => false, + (int)Species.Unown => false, + (int)Species.Pichu when form == 1 => false, + _ => true, + }; + + private static bool IsAllowedBattleFrontier(int species) => !Legal.BattleFrontierBanlist.Contains(species); + + private static bool IsAllowedBattleFrontier(int species, int form, int gen) + { + if (gen == 4 && species == (int)Species.Pichu && form == 1) // spiky + return false; + return IsAllowedBattleFrontier(species); + } + + private static bool CanHaveFootprintRibbon(PKM pk, EvolutionHistory evos, int gen) + { + if (gen <= 4) // Friendship Check unnecessary - can decrease after obtaining ribbon. + return true; + // Gen5: Can't obtain + if (pk.Format < 6) + return false; + + // Gen6/7: Increase level by 30 from original level + if (gen != 8 && !pk.GG && (pk.CurrentLevel - pk.Met_Level >= 30)) + return true; + + // Gen8-BDSP: Variable by species Footprint + var bdspEvos = evos.Gen8b; + if (pk.HasVisitedBDSP(bdspEvos)) + { + if (bdspEvos.Any(z => PersonalTable.BDSP.IsPresentInGame(z.Species, z.Form) && !HasFootprintBDSP[z.Species])) + return true; // no footprint + if (pk.CurrentLevel - pk.Met_Level >= 30) + return true; // traveled well + } + + // Otherwise: Can't obtain + return false; + } + + private static bool CanHaveRibbonWinning(PKM pk, IEncounterTemplate enc, int gen) + { + if (gen != 3) + return false; + if (!IsAllowedBattleFrontier(pk.Species)) + return false; + if (pk.Format == 3) + return pk.Met_Level <= 50; + + // Most encounter types can be below level 50; only Shadow Dragonite & Tyranitar, and select Gen3 Event Gifts. + // These edge cases can't be obtained below level 50, unlike some wild Pokémon which can be encountered at different locations for lower levels. + if (enc.LevelMin <= 50) + return true; + + return enc is not (EncounterStaticShadow or WC3); + } + + private static bool CanHaveRibbonVictory(PKM pk, int gen) + { + return gen == 3 && IsAllowedBattleFrontier(pk.Species); + } + + // Footprint type is not Type 5, requiring 30 levels. + private static readonly bool[] HasFootprintBDSP = + { + true, true, true, true, true, true, true, true, true, true, + true, false, true, true, false, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, false, false, true, false, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, false, false, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + false, false, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, false, true, true, + false, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, false, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, false, true, true, false, false, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, false, true, true, true, true, true, true, + true, true, true, false, true, true, true, true, true, true, + true, true, true, true, true, true, true, false, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, false, true, false, true, + true, true, true, false, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + false, true, true, true, true, true, true, true, true, false, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, false, false, true, + true, true, true, false, false, false, false, false, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, false, true, false, false, true, false, false, false, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, false, false, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, false, true, true, true, true, true, true, true, + true, true, true, true, false, true, false, true, true, true, + true, true, true, true, true, true, false, true, true, true, + true, true, true, true, + }; } diff --git a/PKHeX.Core/Legality/Verifiers/TrainerIDVerifier.cs b/PKHeX.Core/Legality/Verifiers/TrainerIDVerifier.cs index 99ded88e2..935356cb1 100644 --- a/PKHeX.Core/Legality/Verifiers/TrainerIDVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/TrainerIDVerifier.cs @@ -1,69 +1,70 @@ -namespace PKHeX.Core +using static PKHeX.Core.LegalityCheckStrings; + +namespace PKHeX.Core; + +/// +/// Verifies the . +/// +public sealed class TrainerIDVerifier : Verifier { - /// - /// Verifies the . - /// - public sealed class TrainerIDVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Trainer; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Trainer; + var pk = data.Entity; + if (!TrainerNameVerifier.IsPlayerOriginalTrainer(data.EncounterMatch)) + return; // already verified - public override void Verify(LegalityAnalysis data) + if (pk.BDSP) { - var pkm = data.pkm; - if (!TrainerNameVerifier.IsPlayerOriginalTrainer(data.EncounterMatch)) - return; // already verified - - if (pkm.BDSP) + if (pk.TID == 0 && pk.SID == 0) // Game loops to ensure a nonzero full-ID { - if (pkm.TID == 0 && pkm.SID == 0) // Game loops to ensure a nonzero full-ID - { - data.AddLine(GetInvalid(LegalityCheckStrings.LOT_IDInvalid)); - return; - } - if (pkm.TID == 0xFFFF && pkm.SID == 0x7FFF) // int.MaxValue cannot be yielded by Unity's Random.Range[min, max) - { - data.AddLine(GetInvalid(LegalityCheckStrings.LOT_IDInvalid)); - return; - } - } - else if (pkm.VC && pkm.SID != 0) - { - data.AddLine(GetInvalid(LegalityCheckStrings.LOT_SID0Invalid)); + data.AddLine(GetInvalid(LOT_IDInvalid)); return; } - - if (pkm.TID == 0 && pkm.SID == 0) + if (pk.TID == 0xFFFF && pk.SID == 0x7FFF) // int.MaxValue cannot be yielded by Unity's Random.Range[min, max) { - data.AddLine(Get(LegalityCheckStrings.LOT_IDs0, Severity.Fishy)); - } - else if (pkm.TID == pkm.SID) - { - data.AddLine(Get(LegalityCheckStrings.LOT_IDEqual, Severity.Fishy)); - } - else if (pkm.TID == 0) - { - data.AddLine(Get(LegalityCheckStrings.LOT_TID0, Severity.Fishy)); - } - else if (pkm.SID == 0) - { - data.AddLine(Get(LegalityCheckStrings.LOT_SID0, Severity.Fishy)); - } - else if (IsOTIDSuspicious(pkm.TID, pkm.SID)) - { - data.AddLine(Get(LegalityCheckStrings.LOTSuspicious, Severity.Fishy)); + data.AddLine(GetInvalid(LOT_IDInvalid)); + return; } } - - public static bool IsOTIDSuspicious(int tid16, int sid16) + else if (pk.VC && pk.SID != 0) { - if (tid16 == 12345 && sid16 == 54321) - return true; + data.AddLine(GetInvalid(LOT_SID0Invalid)); + return; + } - // 1234_123456 (SID7_TID7) - if (tid16 == 15040 && sid16 == 18831) - return true; - - return false; + if (pk.TID == 0 && pk.SID == 0) + { + data.AddLine(Get(LOT_IDs0, Severity.Fishy)); + } + else if (pk.TID == pk.SID) + { + data.AddLine(Get(LOT_IDEqual, Severity.Fishy)); + } + else if (pk.TID == 0) + { + data.AddLine(Get(LOT_TID0, Severity.Fishy)); + } + else if (pk.SID == 0) + { + data.AddLine(Get(LOT_SID0, Severity.Fishy)); + } + else if (IsOTIDSuspicious(pk.TID, pk.SID)) + { + data.AddLine(Get(LOTSuspicious, Severity.Fishy)); } } + + public static bool IsOTIDSuspicious(int tid16, int sid16) + { + if (tid16 == 12345 && sid16 == 54321) + return true; + + // 1234_123456 (SID7_TID7) + if (tid16 == 15040 && sid16 == 18831) + return true; + + return false; + } } diff --git a/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs b/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs index e52dca409..e37732461 100644 --- a/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs @@ -1,178 +1,177 @@ using System; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the . +/// +public sealed class TrainerNameVerifier : Verifier { - /// - /// Verifies the . - /// - public sealed class TrainerNameVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Trainer; + + private static readonly string[] SuspiciousOTNames = { - protected override CheckIdentifier Identifier => CheckIdentifier.Trainer; + "PKHeX", + "PKHeX", + }; - private static readonly string[] SuspiciousOTNames = + public override void Verify(LegalityAnalysis data) + { + var pk = data.Entity; + var enc = data.EncounterMatch; + if (!IsPlayerOriginalTrainer(enc)) + return; // already verified + + var ot = pk.OT_Name; + if (ot.Length == 0) + data.AddLine(GetInvalid(LOTShort)); + + if (IsOTNameSuspicious(ot)) { - "PKHeX", - "PKHeX", - }; - - public override void Verify(LegalityAnalysis data) - { - var pkm = data.pkm; - var enc = data.EncounterMatch; - if (!IsPlayerOriginalTrainer(enc)) - return; // already verified - - var ot = pkm.OT_Name; - if (ot.Length == 0) - data.AddLine(GetInvalid(LOTShort)); - - if (IsOTNameSuspicious(ot)) - { - data.AddLine(Get(LOTSuspicious, Severity.Fishy)); - } - - if (pkm.VC) - { - VerifyOTG1(data); - } - else if (ot.Length > Legal.GetMaxLengthOT(data.Info.Generation, (LanguageID)pkm.Language)) - { - if (!IsEdgeCaseLength(pkm, data.EncounterOriginal, ot)) - data.AddLine(Get(LOTLong, Severity.Invalid)); - } - - if (ParseSettings.CheckWordFilter) - { - if (WordFilter.IsFiltered(ot, out string bad)) - data.AddLine(GetInvalid($"Wordfilter: {bad}")); - if (ContainsTooManyNumbers(ot, data.Info.Generation)) - data.AddLine(GetInvalid("Wordfilter: Too many numbers.")); - - if (WordFilter.IsFiltered(pkm.HT_Name, out bad)) - data.AddLine(GetInvalid($"Wordfilter: {bad}")); - } + data.AddLine(Get(LOTSuspicious, Severity.Fishy)); } - /// - /// Checks if any player (human) was the original OT. - /// - internal static bool IsPlayerOriginalTrainer(IEncounterable enc) => enc switch + if (pk.VC) { - EncounterTrade { HasTrainerName: true } => false, - MysteryGift { IsEgg: false } => false, - EncounterStatic5N => false, - _ => true, - }; - - public static bool IsEdgeCaseLength(PKM pkm, IEncounterTemplate e, string ot) + VerifyOTG1(data); + } + else if (ot.Length > Legal.GetMaxLengthOT(data.Info.Generation, (LanguageID)pk.Language)) { - if (e.EggEncounter) - { - if (e is WC3 wc3 && pkm.IsEgg && wc3.OT_Name == ot) - return true; // Fixed OT Mystery Gift Egg - bool eggEdge = pkm.IsEgg ? pkm.IsTradedEgg || pkm.Format == 3 : pkm.WasTradedEgg; - if (!eggEdge) - return false; - var len = Legal.GetMaxLengthOT(e.Generation, LanguageID.English); // max case - return ot.Length <= len; - } - - if (e is EncounterTrade { HasTrainerName: true }) - return true; // already verified - - if (e is MysteryGift mg && mg.OT_Name.Length == ot.Length) - return true; // Mattle Ho-Oh - return false; + if (!IsEdgeCaseLength(pk, data.EncounterOriginal, ot)) + data.AddLine(Get(LOTLong, Severity.Invalid)); } - public void VerifyOTG1(LegalityAnalysis data) + if (ParseSettings.CheckWordFilter) { - var pkm = data.pkm; - string tr = pkm.OT_Name; + if (WordFilter.IsFiltered(ot, out string bad)) + data.AddLine(GetInvalid($"Wordfilter: {bad}")); + if (ContainsTooManyNumbers(ot, data.Info.Generation)) + data.AddLine(GetInvalid("Wordfilter: Too many numbers.")); - if (tr.Length == 0) - { - if (pkm is SK2 {TID: 0, IsRental: true}) - { - data.AddLine(Get(LOTShort, Severity.Fishy)); - } - else - { - data.AddLine(GetInvalid(LOTShort)); - return; - } - } - - VerifyG1OTWithinBounds(data, tr.AsSpan()); - - if (pkm.OT_Gender == 1) - { - if (pkm is ICaughtData2 {CaughtData:0} || (pkm.Format > 2 && pkm.VC1) || data is {EncounterOriginal: {Generation:1} or EncounterStatic2E {IsGift:true}}) - data.AddLine(GetInvalid(LG1OTGender)); - } - } - - private void VerifyG1OTWithinBounds(LegalityAnalysis data, ReadOnlySpan str) - { - if (StringConverter12.GetIsG1English(str)) - { - if (str.Length > 7 && data.EncounterOriginal is not EncounterTradeGB) // OT already verified; GER shuckle has 8 chars - data.AddLine(GetInvalid(LOTLong)); - } - else if (StringConverter12.GetIsG1Japanese(str)) - { - if (str.Length > 5) - data.AddLine(GetInvalid(LOTLong)); - } - else if (data.pkm.Korean && StringConverter2KOR.GetIsG2Korean(str)) - { - if (str.Length > 5) - data.AddLine(GetInvalid(LOTLong)); - } - else if (data.EncounterOriginal is not EncounterTrade2) // OT already verified; SPA Shuckle/Voltorb transferred from French can yield 2 inaccessible chars - { - data.AddLine(GetInvalid(LG1CharOT)); - } - } - - private static bool IsOTNameSuspicious(string name) - { - foreach (var s in SuspiciousOTNames) - { - if (s.StartsWith(name, StringComparison.InvariantCultureIgnoreCase)) - return true; - } - return false; - } - - public static bool ContainsTooManyNumbers(string str, int originalGeneration) - { - if (originalGeneration <= 3) - return false; // no limit from these generations - int max = originalGeneration < 6 ? 4 : 5; - if (str.Length <= max) - return false; - int count = GetNumberCount(str); - return count > max; - } - - private static int GetNumberCount(string str) - { - static bool IsNumber(char c) - { - if ('0' <= c) - return c <= '9'; - return (uint)(c - '0') <= 9; - } - - int ctr = 0; - foreach (var c in str) - { - if (IsNumber(c)) - ++ctr; - } - return ctr; + if (WordFilter.IsFiltered(pk.HT_Name, out bad)) + data.AddLine(GetInvalid($"Wordfilter: {bad}")); } } + + /// + /// Checks if any player (human) was the original OT. + /// + internal static bool IsPlayerOriginalTrainer(IEncounterable enc) => enc switch + { + EncounterTrade { HasTrainerName: true } => false, + MysteryGift { IsEgg: false } => false, + EncounterStatic5N => false, + _ => true, + }; + + public static bool IsEdgeCaseLength(PKM pk, IEncounterTemplate e, string ot) + { + if (e.EggEncounter) + { + if (e is WC3 wc3 && pk.IsEgg && wc3.OT_Name == ot) + return true; // Fixed OT Mystery Gift Egg + bool eggEdge = pk.IsEgg ? pk.IsTradedEgg || pk.Format == 3 : pk.WasTradedEgg; + if (!eggEdge) + return false; + var len = Legal.GetMaxLengthOT(e.Generation, LanguageID.English); // max case + return ot.Length <= len; + } + + if (e is EncounterTrade { HasTrainerName: true }) + return true; // already verified + + if (e is MysteryGift mg && mg.OT_Name.Length == ot.Length) + return true; // Mattle Ho-Oh + return false; + } + + public void VerifyOTG1(LegalityAnalysis data) + { + var pk = data.Entity; + string tr = pk.OT_Name; + + if (tr.Length == 0) + { + if (pk is SK2 {TID: 0, IsRental: true}) + { + data.AddLine(Get(LOTShort, Severity.Fishy)); + } + else + { + data.AddLine(GetInvalid(LOTShort)); + return; + } + } + + VerifyG1OTWithinBounds(data, tr.AsSpan()); + + if (pk.OT_Gender == 1) + { + if (pk is ICaughtData2 {CaughtData:0} || (pk.Format > 2 && pk.VC1) || data is {EncounterOriginal: {Generation:1} or EncounterStatic2E {IsGift:true}}) + data.AddLine(GetInvalid(LG1OTGender)); + } + } + + private void VerifyG1OTWithinBounds(LegalityAnalysis data, ReadOnlySpan str) + { + if (StringConverter12.GetIsG1English(str)) + { + if (str.Length > 7 && data.EncounterOriginal is not EncounterTradeGB) // OT already verified; GER shuckle has 8 chars + data.AddLine(GetInvalid(LOTLong)); + } + else if (StringConverter12.GetIsG1Japanese(str)) + { + if (str.Length > 5) + data.AddLine(GetInvalid(LOTLong)); + } + else if (data.Entity.Korean && StringConverter2KOR.GetIsG2Korean(str)) + { + if (str.Length > 5) + data.AddLine(GetInvalid(LOTLong)); + } + else if (data.EncounterOriginal is not EncounterTrade2) // OT already verified; SPA Shuckle/Voltorb transferred from French can yield 2 inaccessible chars + { + data.AddLine(GetInvalid(LG1CharOT)); + } + } + + private static bool IsOTNameSuspicious(string name) + { + foreach (var s in SuspiciousOTNames) + { + if (s.StartsWith(name, StringComparison.InvariantCultureIgnoreCase)) + return true; + } + return false; + } + + public static bool ContainsTooManyNumbers(string str, int originalGeneration) + { + if (originalGeneration <= 3) + return false; // no limit from these generations + int max = originalGeneration < 6 ? 4 : 5; + if (str.Length <= max) + return false; + int count = GetNumberCount(str); + return count > max; + } + + private static int GetNumberCount(string str) + { + static bool IsNumber(char c) + { + if ('0' <= c) + return c <= '9'; + return (uint)(c - '0') <= 9; + } + + int ctr = 0; + foreach (var c in str) + { + if (IsNumber(c)) + ++ctr; + } + return ctr; + } } diff --git a/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs b/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs index c19fde8df..95fc78611 100644 --- a/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/TransferVerifier.cs @@ -2,232 +2,231 @@ using System.Collections.Generic; using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verifies the transfer data for a that has been irreversibly transferred forward. +/// +public sealed class TransferVerifier : Verifier { - /// - /// Verifies the transfer data for a that has been irreversibly transferred forward. - /// - public sealed class TransferVerifier : Verifier + protected override CheckIdentifier Identifier => CheckIdentifier.Encounter; + + public override void Verify(LegalityAnalysis data) { - protected override CheckIdentifier Identifier => CheckIdentifier.Encounter; + throw new Exception("Don't call via this."); + } - public override void Verify(LegalityAnalysis data) + public void VerifyTransferLegalityG12(LegalityAnalysis data) + { + VerifyVCOTGender(data); + VerifyVCNatureEXP(data); + VerifyVCShinyXorIfShiny(data); + VerifyVCGeolocation(data); + } + + private void VerifyVCOTGender(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.OT_Gender == 1 && pk.Version != (int)GameVersion.C) + data.AddLine(GetInvalid(LG2OTGender)); + } + + private void VerifyVCNatureEXP(LegalityAnalysis data) + { + var pk = data.Entity; + var met = pk.Met_Level; + + if (met == 100) // check for precise match, can't receive EXP after transfer. { - throw new Exception("Don't call via this."); + var nature = Experience.GetNatureVC(pk.EXP); + if (nature != pk.Nature) + data.AddLine(GetInvalid(LTransferNature)); + return; } - - public void VerifyTransferLegalityG12(LegalityAnalysis data) + if (met <= 2) // Not enough EXP to have every nature -- check for exclusions! { - VerifyVCOTGender(data); - VerifyVCNatureEXP(data); - VerifyVCShinyXorIfShiny(data); - VerifyVCGeolocation(data); - } - - private void VerifyVCOTGender(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.OT_Gender == 1 && pkm.Version != (int)GameVersion.C) - data.AddLine(GetInvalid(LG2OTGender)); - } - - private void VerifyVCNatureEXP(LegalityAnalysis data) - { - var pkm = data.pkm; - var met = pkm.Met_Level; - - if (met == 100) // check for precise match, can't receive EXP after transfer. - { - var nature = Experience.GetNatureVC(pkm.EXP); - if (nature != pkm.Nature) - data.AddLine(GetInvalid(LTransferNature)); - return; - } - if (met <= 2) // Not enough EXP to have every nature -- check for exclusions! - { - var pi = pkm.PersonalInfo; - var growth = pi.EXPGrowth; - var nature = pkm.Nature; - bool valid = VerifyVCNature(growth, nature); - if (!valid) - data.AddLine(GetInvalid(LTransferNature)); - } - } - - private static bool VerifyVCNature(int growth, int nature) => growth switch - { - // exp % 25 with a limited amount of EXP does not allow for every nature - 0 => (0x01FFFF03 & (1 << nature)) != 0, // MediumFast -- Can't be Brave, Adamant, Naughty, Bold, Docile, or Relaxed - 4 => (0x001FFFC0 & (1 << nature)) != 0, // Fast -- Can't be Gentle, Sassy, Careful, Quirky, Hardy, Lonely, Brave, Adamant, Naughty, or Bold - 5 => (0x01FFFCFF & (1 << nature)) != 0, // Slow -- Can't be Impish or Lax - _ => true, - }; - - private static void VerifyVCShinyXorIfShiny(LegalityAnalysis data) - { - // Star, not square. Requires transferring a shiny and having the initially random PID to already be a Star shiny. - // (15:65536, ~1:4096) odds on a given shiny transfer! - var xor = data.pkm.ShinyXor; - if (xor is <= 15 and not 0) - data.AddLine(Get(LEncStaticPIDShiny, ParseSettings.Gen7TransferStarPID, CheckIdentifier.PID)); - } - - private static void VerifyVCGeolocation(LegalityAnalysis data) - { - if (data.pkm is not PK7 pk7) - return; - - // VC Games were region locked to the Console, meaning not all language games are available. - var within = Locale3DS.IsRegionLockedLanguageValidVC(pk7.ConsoleRegion, pk7.Language); - if (!within) - data.AddLine(GetInvalid(string.Format(LOTLanguage, $"!={(LanguageID)pk7.Language}", ((LanguageID)pk7.Language).ToString()), CheckIdentifier.Language)); - } - - public void VerifyTransferLegalityG3(LegalityAnalysis data) - { - var pkm = data.pkm; - if (pkm.Format == 4) // Pal Park (3->4) - { - if (pkm.Met_Location != Locations.Transfer3) - data.AddLine(GetInvalid(LEggLocationPalPark)); - } - else // Transporter (4->5) - { - if (pkm.Met_Location != Locations.Transfer4) - data.AddLine(GetInvalid(LTransferEggLocationTransporter)); - } - } - - public void VerifyTransferLegalityG4(LegalityAnalysis data) - { - var pkm = data.pkm; - int loc = pkm.Met_Location; - if (loc == Locations.Transfer4) - return; - - // Crown met location must be present if transferred via lock capsule - switch (pkm.Species) - { - case (int)Species.Celebi: - if (loc is not (Locations.Transfer4_CelebiUnused or Locations.Transfer4_CelebiUsed)) - data.AddLine(GetInvalid(LTransferMet)); - break; - case (int)Species.Raikou or (int)Species.Entei or (int)Species.Suicune: - if (loc is not (Locations.Transfer4_CrownUnused or Locations.Transfer4_CrownUsed)) - data.AddLine(GetInvalid(LTransferMet)); - break; - default: - data.AddLine(GetInvalid(LTransferEggLocationTransporter)); - break; - } - } - - public void VerifyTransferLegalityG8(LegalityAnalysis data) - { - var pkm = data.pkm; - var enc = data.EncounterMatch; - bool native = enc.Generation == 8 && pkm.IsNative; - if (!native || IsHOMETrackerRequired(enc)) - VerifyHOMETracker(data, pkm); - - if (enc.Generation < 8) - { - VerifyHOMETransfer(data, pkm); - // Check for impossible 7->8 transfers - if (enc is EncounterStatic7 { IsTotem: true } s) - { - if (s.IsTotemNoTransfer) - data.AddLine(GetInvalid(LTransferBad)); - else if (pkm.Form != s.GetTotemBaseForm()) - data.AddLine(GetInvalid(LTransferBad)); - } - } - - // Starting in Generation 8, games have a selective amount of species/forms from prior games. - var pt = pkm switch - { - PA8 => PersonalTable.LA, - PB8 => PersonalTable.BDSP, - _ => PersonalTable.SWSH, - }; - if (!pt.IsPresentInGame(pkm.Species, pkm.Form)) - data.AddLine(GetInvalid(LTransferBad)); - } - - // Encounters that originate in HOME -> transfer to save data - private static bool IsHOMETrackerRequired(IEncounterTemplate enc) => enc switch - { - EncounterSlot8GO => true, - WC8 { IsHOMEGift: true } => true, - WB8 { IsHOMEGift: true } => true, - WA8 { IsHOMEGift: true } => true, - _ => enc.Generation < 8, - }; - - private void VerifyHOMETransfer(LegalityAnalysis data, PKM pkm) - { - if (pkm is not IScaledSize s) - return; - - if (pkm.LGPE || pkm.GO) - return; // can have any size value - if (s.HeightScalar != 0) - data.AddLine(GetInvalid(LTransferBad)); - if (s.WeightScalar != 0) - data.AddLine(GetInvalid(LTransferBad)); - } - - private void VerifyHOMETracker(LegalityAnalysis data, PKM pkm) - { - // Tracker value is set via Transfer across HOME. - // Can't validate the actual values (we aren't the server), so we can only check against zero. - if (pkm is IHomeTrack {Tracker: 0}) - { - data.AddLine(Get(LTransferTrackerMissing, ParseSettings.Gen8TransferTrackerNotPresent)); - // To the reader: It seems like the best course of action for setting a tracker is: - // - Transfer a 0-Tracker pkm to HOME to get assigned a valid Tracker - // - Don't make one up. - } - } - - public IEnumerable VerifyVCEncounter(PKM pkm, IEncounterTemplate encounter, ILocation transfer, IList Moves) - { - if (pkm.Met_Location != transfer.Location) - yield return GetInvalid(LTransferMetLocation); - - var expecteEgg = pkm is PB8 ? Locations.Default8bNone : transfer.EggLocation; - if (pkm.Egg_Location != expecteEgg) - yield return GetInvalid(LEggLocationNone); - - // Flag Moves that cannot be transferred - if (encounter is EncounterStatic2Odd) // Dizzy Punch Gifts - FlagIncompatibleTransferMove(pkm, Moves, 146, 2); // can't have Dizzy Punch at all - - bool checkShiny = pkm.VC2 || (pkm.VC1 && GBRestrictions.IsTimeCapsuleTransferred(pkm, Moves, encounter).WasTimeCapsuleTransferred()); - if (!checkShiny) - yield break; - - if (pkm.Gender == 1) // female - { - if (pkm.PersonalInfo.Gender == 31 && pkm.IsShiny) // impossible gender-shiny - yield return GetInvalid(LEncStaticPIDShiny, CheckIdentifier.PID); - } - else if (pkm.Species == (int)Species.Unown) - { - if (pkm.Form is not (8 or 21) && pkm.IsShiny) // impossibly form-shiny (not I or V) - yield return GetInvalid(LEncStaticPIDShiny, CheckIdentifier.PID); - } - } - - private static void FlagIncompatibleTransferMove(PKM pkm, IList Moves, int move, int gen) - { - int index = Array.IndexOf(pkm.Moves, move); - if (index < 0) - return; // doesn't have move - - var chk = Moves[index]; - if (chk.Generation == gen) // not obtained from a future gen - Moves[index].FlagIllegal(LTransferMove, CheckIdentifier.CurrentMove); + var pi = pk.PersonalInfo; + var growth = pi.EXPGrowth; + var nature = pk.Nature; + bool valid = VerifyVCNature(growth, nature); + if (!valid) + data.AddLine(GetInvalid(LTransferNature)); } } + + private static bool VerifyVCNature(int growth, int nature) => growth switch + { + // exp % 25 with a limited amount of EXP does not allow for every nature + 0 => (0x01FFFF03 & (1 << nature)) != 0, // MediumFast -- Can't be Brave, Adamant, Naughty, Bold, Docile, or Relaxed + 4 => (0x001FFFC0 & (1 << nature)) != 0, // Fast -- Can't be Gentle, Sassy, Careful, Quirky, Hardy, Lonely, Brave, Adamant, Naughty, or Bold + 5 => (0x01FFFCFF & (1 << nature)) != 0, // Slow -- Can't be Impish or Lax + _ => true, + }; + + private static void VerifyVCShinyXorIfShiny(LegalityAnalysis data) + { + // Star, not square. Requires transferring a shiny and having the initially random PID to already be a Star shiny. + // (15:65536, ~1:4096) odds on a given shiny transfer! + var xor = data.Entity.ShinyXor; + if (xor is <= 15 and not 0) + data.AddLine(Get(LEncStaticPIDShiny, ParseSettings.Gen7TransferStarPID, CheckIdentifier.PID)); + } + + private static void VerifyVCGeolocation(LegalityAnalysis data) + { + if (data.Entity is not PK7 pk7) + return; + + // VC Games were region locked to the Console, meaning not all language games are available. + var within = Locale3DS.IsRegionLockedLanguageValidVC(pk7.ConsoleRegion, pk7.Language); + if (!within) + data.AddLine(GetInvalid(string.Format(LOTLanguage, $"!={(LanguageID)pk7.Language}", ((LanguageID)pk7.Language).ToString()), CheckIdentifier.Language)); + } + + public void VerifyTransferLegalityG3(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.Format == 4) // Pal Park (3->4) + { + if (pk.Met_Location != Locations.Transfer3) + data.AddLine(GetInvalid(LEggLocationPalPark)); + } + else // Transporter (4->5) + { + if (pk.Met_Location != Locations.Transfer4) + data.AddLine(GetInvalid(LTransferEggLocationTransporter)); + } + } + + public void VerifyTransferLegalityG4(LegalityAnalysis data) + { + var pk = data.Entity; + int loc = pk.Met_Location; + if (loc == Locations.Transfer4) + return; + + // Crown met location must be present if transferred via lock capsule + switch (pk.Species) + { + case (int)Species.Celebi: + if (loc is not (Locations.Transfer4_CelebiUnused or Locations.Transfer4_CelebiUsed)) + data.AddLine(GetInvalid(LTransferMet)); + break; + case (int)Species.Raikou or (int)Species.Entei or (int)Species.Suicune: + if (loc is not (Locations.Transfer4_CrownUnused or Locations.Transfer4_CrownUsed)) + data.AddLine(GetInvalid(LTransferMet)); + break; + default: + data.AddLine(GetInvalid(LTransferEggLocationTransporter)); + break; + } + } + + public void VerifyTransferLegalityG8(LegalityAnalysis data) + { + var pk = data.Entity; + var enc = data.EncounterMatch; + bool native = enc.Generation == 8 && pk.IsNative; + if (!native || IsHOMETrackerRequired(enc)) + VerifyHOMETracker(data, pk); + + if (enc.Generation < 8) + { + VerifyHOMETransfer(data, pk); + // Check for impossible 7->8 transfers + if (enc is EncounterStatic7 { IsTotem: true } s) + { + if (s.IsTotemNoTransfer) + data.AddLine(GetInvalid(LTransferBad)); + else if (pk.Form != s.GetTotemBaseForm()) + data.AddLine(GetInvalid(LTransferBad)); + } + } + + // Starting in Generation 8, games have a selective amount of species/forms from prior games. + var pt = pk switch + { + PA8 => PersonalTable.LA, + PB8 => PersonalTable.BDSP, + _ => PersonalTable.SWSH, + }; + if (!pt.IsPresentInGame(pk.Species, pk.Form)) + data.AddLine(GetInvalid(LTransferBad)); + } + + // Encounters that originate in HOME -> transfer to save data + private static bool IsHOMETrackerRequired(IEncounterTemplate enc) => enc switch + { + EncounterSlot8GO => true, + WC8 { IsHOMEGift: true } => true, + WB8 { IsHOMEGift: true } => true, + WA8 { IsHOMEGift: true } => true, + _ => enc.Generation < 8, + }; + + private void VerifyHOMETransfer(LegalityAnalysis data, PKM pk) + { + if (pk is not IScaledSize s) + return; + + if (pk.LGPE || pk.GO) + return; // can have any size value + if (s.HeightScalar != 0) + data.AddLine(GetInvalid(LTransferBad)); + if (s.WeightScalar != 0) + data.AddLine(GetInvalid(LTransferBad)); + } + + private void VerifyHOMETracker(LegalityAnalysis data, PKM pk) + { + // Tracker value is set via Transfer across HOME. + // Can't validate the actual values (we aren't the server), so we can only check against zero. + if (pk is IHomeTrack {Tracker: 0}) + { + data.AddLine(Get(LTransferTrackerMissing, ParseSettings.Gen8TransferTrackerNotPresent)); + // To the reader: It seems like the best course of action for setting a tracker is: + // - Transfer a 0-Tracker pk to HOME to get assigned a valid Tracker + // - Don't make one up. + } + } + + public IEnumerable VerifyVCEncounter(PKM pk, IEncounterTemplate encounter, ILocation transfer, IList Moves) + { + if (pk.Met_Location != transfer.Location) + yield return GetInvalid(LTransferMetLocation); + + var expecteEgg = pk is PB8 ? Locations.Default8bNone : transfer.EggLocation; + if (pk.Egg_Location != expecteEgg) + yield return GetInvalid(LEggLocationNone); + + // Flag Moves that cannot be transferred + if (encounter is EncounterStatic2Odd) // Dizzy Punch Gifts + FlagIncompatibleTransferMove(pk, Moves, 146, 2); // can't have Dizzy Punch at all + + bool checkShiny = pk.VC2 || (pk.VC1 && GBRestrictions.IsTimeCapsuleTransferred(pk, Moves, encounter).WasTimeCapsuleTransferred()); + if (!checkShiny) + yield break; + + if (pk.Gender == 1) // female + { + if (pk.PersonalInfo.Gender == 31 && pk.IsShiny) // impossible gender-shiny + yield return GetInvalid(LEncStaticPIDShiny, CheckIdentifier.PID); + } + else if (pk.Species == (int)Species.Unown) + { + if (pk.Form is not (8 or 21) && pk.IsShiny) // impossibly form-shiny (not I or V) + yield return GetInvalid(LEncStaticPIDShiny, CheckIdentifier.PID); + } + } + + private static void FlagIncompatibleTransferMove(PKM pk, IList Moves, int move, int gen) + { + int index = Array.IndexOf(pk.Moves, move); + if (index < 0) + return; // doesn't have move + + var chk = Moves[index]; + if (chk.Generation == gen) // not obtained from a future gen + Moves[index].FlagIllegal(LTransferMove, CheckIdentifier.CurrentMove); + } } diff --git a/PKHeX.Core/Legality/Verifiers/Verifier.cs b/PKHeX.Core/Legality/Verifiers/Verifier.cs index 52813c63f..f7bc050c6 100644 --- a/PKHeX.Core/Legality/Verifiers/Verifier.cs +++ b/PKHeX.Core/Legality/Verifiers/Verifier.cs @@ -1,27 +1,26 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Verification that provides new values for a . +/// +public abstract class Verifier { /// - /// Verification that provides new values for a . + /// category. /// - public abstract class Verifier - { - /// - /// category. - /// - protected abstract CheckIdentifier Identifier { get; } + protected abstract CheckIdentifier Identifier { get; } - /// - /// Processes the and adds any relevant values to the . - /// - /// Analysis data to process - public abstract void Verify(LegalityAnalysis data); + /// + /// Processes the and adds any relevant values to the . + /// + /// Analysis data to process + public abstract void Verify(LegalityAnalysis data); - protected CheckResult GetInvalid(string msg) => Get(msg, Severity.Invalid); - protected CheckResult GetValid(string msg) => Get(msg, Severity.Valid); - protected CheckResult Get(string msg, Severity s) => new(s, msg, Identifier); + protected CheckResult GetInvalid(string msg) => Get(msg, Severity.Invalid); + protected CheckResult GetValid(string msg) => Get(msg, Severity.Valid); + protected CheckResult Get(string msg, Severity s) => new(s, msg, Identifier); - protected static CheckResult GetInvalid(string msg, CheckIdentifier c) => Get(msg, Severity.Invalid, c); - protected static CheckResult GetValid(string msg, CheckIdentifier c) => Get(msg, Severity.Valid, c); - protected static CheckResult Get(string msg, Severity s, CheckIdentifier c) => new(s, msg, c); - } + protected static CheckResult GetInvalid(string msg, CheckIdentifier c) => Get(msg, Severity.Invalid, c); + protected static CheckResult GetValid(string msg, CheckIdentifier c) => Get(msg, Severity.Valid, c); + protected static CheckResult Get(string msg, Severity s, CheckIdentifier c) => new(s, msg, c); } diff --git a/PKHeX.Core/Moves/MoveInfo.cs b/PKHeX.Core/Moves/MoveInfo.cs index 3e8b44ecd..96b7a9cf4 100644 --- a/PKHeX.Core/Moves/MoveInfo.cs +++ b/PKHeX.Core/Moves/MoveInfo.cs @@ -21,7 +21,7 @@ public static byte GetPP(EntityContext context, int move) return table[move]; } - public static ReadOnlySpan GetPPTable(PKM pkm) => GetPPTable(pkm.Context); + public static ReadOnlySpan GetPPTable(PKM pk) => GetPPTable(pk.Context); public static ReadOnlySpan GetPPTable(EntityContext context) => context switch { diff --git a/PKHeX.Core/MysteryGifts/DataMysteryGift.cs b/PKHeX.Core/MysteryGifts/DataMysteryGift.cs index 76c93682f..ba9832e5f 100644 --- a/PKHeX.Core/MysteryGifts/DataMysteryGift.cs +++ b/PKHeX.Core/MysteryGifts/DataMysteryGift.cs @@ -1,41 +1,40 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Mystery Gift backed by serialized fields from ROM/SAV data, rather than observed specifications. +/// +public abstract class DataMysteryGift : MysteryGift { + public readonly byte[] Data; + + protected DataMysteryGift(byte[] data) => Data = data; + /// - /// Mystery Gift backed by serialized fields from ROM/SAV data, rather than observed specifications. + /// Returns an array for exporting outside the program (to disk, etc). /// - public abstract class DataMysteryGift : MysteryGift + public virtual byte[] Write() => Data; + + public override int GetHashCode() { - public readonly byte[] Data; - - protected DataMysteryGift(byte[] data) => Data = data; - - /// - /// Returns an array for exporting outside the program (to disk, etc). - /// - public virtual byte[] Write() => Data; - - public override int GetHashCode() - { - int hash = 17; - foreach (var b in Data) - hash = (hash * 31) + b; - return hash; - } - - /// - /// Creates a deep copy of the object data. - /// - public override MysteryGift Clone() - { - byte[] data = (byte[])Data.Clone(); - var result = GetMysteryGift(data); - if (result == null) - throw new ArgumentException(nameof(MysteryGift)); - return result; - } - - public override bool Empty => new ReadOnlySpan(Data).IsRangeEmpty(); + int hash = 17; + foreach (var b in Data) + hash = (hash * 31) + b; + return hash; } + + /// + /// Creates a deep copy of the object data. + /// + public override MysteryGift Clone() + { + byte[] data = (byte[])Data.Clone(); + var result = GetMysteryGift(data); + if (result == null) + throw new ArgumentException(nameof(MysteryGift)); + return result; + } + + public override bool Empty => new ReadOnlySpan(Data).IsRangeEmpty(); } diff --git a/PKHeX.Core/MysteryGifts/MysteryGift.cs b/PKHeX.Core/MysteryGifts/MysteryGift.cs index 2f8845ee0..f22de622a 100644 --- a/PKHeX.Core/MysteryGifts/MysteryGift.cs +++ b/PKHeX.Core/MysteryGifts/MysteryGift.cs @@ -1,180 +1,178 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Mystery Gift Template File +/// +public abstract class MysteryGift : IEncounterable, IMoveset, IRelearn { /// - /// Mystery Gift Template File + /// Determines whether or not the given length of bytes is valid for a mystery gift. /// - public abstract class MysteryGift : IEncounterable, IMoveset, IRelearn + /// Length, in bytes, of the data of which to determine validity. + /// A boolean indicating whether or not the given length is valid for a mystery gift. + public static bool IsMysteryGift(long len) => Sizes.Contains((int)len); + + private static readonly HashSet Sizes = new() { WA8.Size, WB8.Size, WC8.Size, WC6Full.Size, WC6.Size, PGF.Size, PGT.Size, PCD.Size }; + + /// + /// Converts the given data to a . + /// + /// Raw data of the mystery gift. + /// Extension of the file from which the was retrieved. + /// An instance of representing the given data, or null if or is invalid. + /// This overload differs from by checking the / combo for validity. If either is invalid, a null reference is returned. + public static DataMysteryGift? GetMysteryGift(byte[] data, string ext) => data.Length switch { - /// - /// Determines whether or not the given length of bytes is valid for a mystery gift. - /// - /// Length, in bytes, of the data of which to determine validity. - /// A boolean indicating whether or not the given length is valid for a mystery gift. - public static bool IsMysteryGift(long len) => Sizes.Contains((int)len); + PGT.Size when ext == ".pgt" => new PGT(data), + PCD.Size when ext is ".pcd" or ".wc4" => new PCD(data), + PGF.Size when ext == ".pgf" => new PGF(data), + WC6.Size when ext == ".wc6" => new WC6(data), + WC7.Size when ext == ".wc7" => new WC7(data), + WB7.Size when ext == ".wb7" => new WB7(data), + WR7.Size when ext == ".wr7" => new WR7(data), + WC8.Size when ext is ".wc8" or ".wc8full" => new WC8(data), + WB8.Size when ext is ".wb8" => new WB8(data), + WA8.Size when ext is ".wa8" => new WA8(data), - private static readonly HashSet Sizes = new() { WA8.Size, WB8.Size, WC8.Size, WC6Full.Size, WC6.Size, PGF.Size, PGT.Size, PCD.Size }; + WB7.SizeFull when ext == ".wb7full" => new WB7(data), + WC6Full.Size when ext == ".wc6full" => new WC6Full(data).Gift, + WC7Full.Size when ext == ".wc7full" => new WC7Full(data).Gift, + _ => null, + }; - /// - /// Converts the given data to a . - /// - /// Raw data of the mystery gift. - /// Extension of the file from which the was retrieved. - /// An instance of representing the given data, or null if or is invalid. - /// This overload differs from by checking the / combo for validity. If either is invalid, a null reference is returned. - public static DataMysteryGift? GetMysteryGift(byte[] data, string ext) => data.Length switch - { - PGT.Size when ext == ".pgt" => new PGT(data), - PCD.Size when ext is ".pcd" or ".wc4" => new PCD(data), - PGF.Size when ext == ".pgf" => new PGF(data), - WC6.Size when ext == ".wc6" => new WC6(data), - WC7.Size when ext == ".wc7" => new WC7(data), - WB7.Size when ext == ".wb7" => new WB7(data), - WR7.Size when ext == ".wr7" => new WR7(data), - WC8.Size when ext is ".wc8" or ".wc8full" => new WC8(data), - WB8.Size when ext is ".wb8" => new WB8(data), - WA8.Size when ext is ".wa8" => new WA8(data), + /// + /// Converts the given data to a . + /// + /// Raw data of the mystery gift. + /// An instance of representing the given data, or null if is invalid. + public static DataMysteryGift? GetMysteryGift(byte[] data) => data.Length switch + { + PGT.Size => new PGT(data), + PCD.Size => new PCD(data), + PGF.Size => new PGF(data), + WR7.Size => new WR7(data), + WC8.Size => new WC8(data), + WB8.Size => new WB8(data), + WA8.Size => new WA8(data), - WB7.SizeFull when ext == ".wb7full" => new WB7(data), - WC6Full.Size when ext == ".wc6full" => new WC6Full(data).Gift, - WC7Full.Size when ext == ".wc7full" => new WC7Full(data).Gift, - _ => null, - }; + // WC6/WC7: Check year + WC6.Size => ReadUInt32LittleEndian(data.AsSpan(0x4C)) / 10000 < 2000 ? new WC7(data) : new WC6(data), + // WC6Full/WC7Full: 0x205 has 3 * 0x46 for gen6, now only 2. + WC6Full.Size => data[0x205] == 0 ? new WC7Full(data).Gift : new WC6Full(data).Gift, + _ => null, + }; - /// - /// Converts the given data to a . - /// - /// Raw data of the mystery gift. - /// An instance of representing the given data, or null if is invalid. - public static DataMysteryGift? GetMysteryGift(byte[] data) => data.Length switch - { - PGT.Size => new PGT(data), - PCD.Size => new PCD(data), - PGF.Size => new PGF(data), - WR7.Size => new WR7(data), - WC8.Size => new WC8(data), - WB8.Size => new WB8(data), - WA8.Size => new WA8(data), + public string Extension => GetType().Name.ToLowerInvariant(); + public string FileName => $"{CardHeader}.{Extension}"; + public abstract int Generation { get; } - // WC6/WC7: Check year - WC6.Size => ReadUInt32LittleEndian(data.AsSpan(0x4C)) / 10000 < 2000 ? new WC7(data) : new WC6(data), - // WC6Full/WC7Full: 0x205 has 3 * 0x46 for gen6, now only 2. - WC6Full.Size => data[0x205] == 0 ? new WC7Full(data).Gift : new WC6Full(data).Gift, - _ => null, - }; + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + public abstract PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria); - public string Extension => GetType().Name.ToLowerInvariant(); - public string FileName => $"{CardHeader}.{Extension}"; - public abstract int Generation { get; } + public abstract bool IsMatchExact(PKM pk, EvoCriteria evo); + protected abstract bool IsMatchDeferred(PKM pk); + protected abstract bool IsMatchPartial(PKM pk); - public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted); - public abstract PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria); - - public abstract bool IsMatchExact(PKM pkm, EvoCriteria evo); - protected abstract bool IsMatchDeferred(PKM pkm); - protected abstract bool IsMatchPartial(PKM pkm); - - public EncounterMatchRating GetMatchRating(PKM pkm) - { - if (IsMatchPartial(pkm)) - return EncounterMatchRating.PartialMatch; - if (IsMatchDeferred(pkm)) - return EncounterMatchRating.Deferred; - return EncounterMatchRating.Match; - } - - /// - /// Creates a deep copy of the object data. - /// - /// - public abstract MysteryGift Clone(); - - /// - /// Gets a friendly name for the underlying type. - /// - public string Type => GetType().Name; - - /// - /// Gets a friendly name for the underlying type for the interface. - /// - public string Name => "Event Gift"; - - /// - /// Gets a friendly name for the underlying type for the interface. - /// - public string LongName => $"{Name} ({Type})"; - - public virtual GameVersion Version - { - get => GameUtil.GetVersion(Generation); - set { } - } - - // Properties - public virtual int Species { get => -1; set { } } - public abstract AbilityPermission Ability { get; } - public abstract bool GiftUsed { get; set; } - public abstract string CardTitle { get; set; } - public abstract int CardID { get; set; } - - public abstract bool IsItem { get; set; } - public abstract int ItemID { get; set; } - - public abstract bool IsEntity { get; set; } - public virtual int Quantity { get => 1; set { } } - public virtual bool Empty => false; - - public virtual string CardHeader => (CardID > 0 ? $"Card #: {CardID:0000}" : "N/A") + $" - {CardTitle.Replace('\u3000',' ').Trim()}"; - - // Search Properties - public virtual IReadOnlyList Moves { get => Array.Empty(); set { } } - public virtual IReadOnlyList Relearn { get => Array.Empty(); set { } } - public virtual int[] IVs { get => Array.Empty(); set { } } - public virtual bool HasFixedIVs => true; - public virtual void GetIVs(Span value) { } - public virtual bool IsShiny => false; - - public virtual Shiny Shiny - { - get => Shiny.Never; - init => throw new InvalidOperationException(); - } - - public virtual bool IsEgg { get => false; set { } } - public virtual int HeldItem { get => -1; set { } } - public virtual int AbilityType { get => -1; set { } } - public abstract int Gender { get; set; } - public abstract int Form { get; set; } - public abstract int TID { get; set; } - public abstract int SID { get; set; } - public abstract string OT_Name { get; set; } - public abstract int Location { get; set; } - - public abstract byte Level { get; set; } - public byte LevelMin => Level; - public byte LevelMax => Level; - public abstract int Ball { get; set; } - public virtual bool EggEncounter => IsEgg; - public abstract int EggLocation { get; set; } - - protected virtual bool IsMatchEggLocation(PKM pk) - { - var expect = EggEncounter ? EggLocation : pk is PB8 ? Locations.Default8bNone : 0; - return pk.Egg_Location == expect; - } - - public Ball FixedBall => (Ball)Ball; - - public int TrainerID7 => (int)((uint)(TID | (SID << 16)) % 1000000); - public int TrainerSID7 => (int)((uint)(TID | (SID << 16)) / 1000000); - - /// - /// Checks if the has the in its current move list. - /// - public bool HasMove(int move) => Moves.Contains(move); + public EncounterMatchRating GetMatchRating(PKM pk) + { + if (IsMatchPartial(pk)) + return EncounterMatchRating.PartialMatch; + if (IsMatchDeferred(pk)) + return EncounterMatchRating.Deferred; + return EncounterMatchRating.Match; } + + /// + /// Creates a deep copy of the object data. + /// + public abstract MysteryGift Clone(); + + /// + /// Gets a friendly name for the underlying type. + /// + public string Type => GetType().Name; + + /// + /// Gets a friendly name for the underlying type for the interface. + /// + public string Name => "Event Gift"; + + /// + /// Gets a friendly name for the underlying type for the interface. + /// + public string LongName => $"{Name} ({Type})"; + + public virtual GameVersion Version + { + get => GameUtil.GetVersion(Generation); + set { } + } + + // Properties + public virtual int Species { get => -1; set { } } + public abstract AbilityPermission Ability { get; } + public abstract bool GiftUsed { get; set; } + public abstract string CardTitle { get; set; } + public abstract int CardID { get; set; } + + public abstract bool IsItem { get; set; } + public abstract int ItemID { get; set; } + + public abstract bool IsEntity { get; set; } + public virtual int Quantity { get => 1; set { } } + public virtual bool Empty => false; + + public virtual string CardHeader => (CardID > 0 ? $"Card #: {CardID:0000}" : "N/A") + $" - {CardTitle.Replace('\u3000',' ').Trim()}"; + + // Search Properties + public virtual IReadOnlyList Moves { get => Array.Empty(); set { } } + public virtual IReadOnlyList Relearn { get => Array.Empty(); set { } } + public virtual int[] IVs { get => Array.Empty(); set { } } + public virtual bool HasFixedIVs => true; + public virtual void GetIVs(Span value) { } + public virtual bool IsShiny => false; + + public virtual Shiny Shiny + { + get => Shiny.Never; + init => throw new InvalidOperationException(); + } + + public virtual bool IsEgg { get => false; set { } } + public virtual int HeldItem { get => -1; set { } } + public virtual int AbilityType { get => -1; set { } } + public abstract int Gender { get; set; } + public abstract int Form { get; set; } + public abstract int TID { get; set; } + public abstract int SID { get; set; } + public abstract string OT_Name { get; set; } + public abstract int Location { get; set; } + + public abstract byte Level { get; set; } + public byte LevelMin => Level; + public byte LevelMax => Level; + public abstract int Ball { get; set; } + public virtual bool EggEncounter => IsEgg; + public abstract int EggLocation { get; set; } + + protected virtual bool IsMatchEggLocation(PKM pk) + { + var expect = EggEncounter ? EggLocation : pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + public Ball FixedBall => (Ball)Ball; + + public int TrainerID7 => (int)((uint)(TID | (SID << 16)) % 1000000); + public int TrainerSID7 => (int)((uint)(TID | (SID << 16)) / 1000000); + + /// + /// Checks if the has the in its current move list. + /// + public bool HasMove(int move) => Moves.Contains(move); } diff --git a/PKHeX.Core/MysteryGifts/MysteryUtil.cs b/PKHeX.Core/MysteryGifts/MysteryUtil.cs index ccce8c2b3..46c267fb1 100644 --- a/PKHeX.Core/MysteryGifts/MysteryUtil.cs +++ b/PKHeX.Core/MysteryGifts/MysteryUtil.cs @@ -4,170 +4,169 @@ using static PKHeX.Core.MessageStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Utility logic for dealing with objects. +/// +public static class MysteryUtil { /// - /// Utility logic for dealing with objects. + /// Gets objects from a folder. /// - public static class MysteryUtil + /// Folder path + /// Consumable list of gifts. + public static IEnumerable GetGiftsFromFolder(string folder) { - /// - /// Gets objects from a folder. - /// - /// Folder path - /// Consumable list of gifts. - public static IEnumerable GetGiftsFromFolder(string folder) + foreach (var file in Directory.EnumerateFiles(folder, "*", SearchOption.AllDirectories)) { - foreach (var file in Directory.EnumerateFiles(folder, "*", SearchOption.AllDirectories)) - { - var fi = new FileInfo(file); - if (!MysteryGift.IsMysteryGift(fi.Length)) - continue; + var fi = new FileInfo(file); + if (!MysteryGift.IsMysteryGift(fi.Length)) + continue; - var gift = MysteryGift.GetMysteryGift(File.ReadAllBytes(file), fi.Extension); - if (gift != null) - yield return gift; - } + var gift = MysteryGift.GetMysteryGift(File.ReadAllBytes(file), fi.Extension); + if (gift != null) + yield return gift; } + } - /// - /// Gets a description of the using the current default string data. - /// - /// Gift data to parse - /// List of lines - public static IEnumerable GetDescription(this MysteryGift gift) => gift.GetDescription(GameInfo.Strings); + /// + /// Gets a description of the using the current default string data. + /// + /// Gift data to parse + /// List of lines + public static IEnumerable GetDescription(this MysteryGift gift) => gift.GetDescription(GameInfo.Strings); - /// - /// Gets a description of the using provided string data. - /// - /// Gift data to parse - /// String data to use - /// List of lines - public static IEnumerable GetDescription(this MysteryGift gift, IBasicStrings strings) + /// + /// Gets a description of the using provided string data. + /// + /// Gift data to parse + /// String data to use + /// List of lines + public static IEnumerable GetDescription(this MysteryGift gift, IBasicStrings strings) + { + if (gift.Empty) + return new[] { MsgMysteryGiftSlotEmpty }; + + var result = new List { gift.CardHeader }; + if (gift.IsItem) { - if (gift.Empty) - return new[] { MsgMysteryGiftSlotEmpty }; - - var result = new List { gift.CardHeader }; - if (gift.IsItem) + AddLinesItem(gift, strings, result); + } + else if (gift.IsEntity) + { + try { - AddLinesItem(gift, strings, result); + AddLinesPKM(gift, strings, result); } - else if (gift.IsEntity) - { - try - { - AddLinesPKM(gift, strings, result); - } - catch { result.Add(MsgMysteryGiftParseFail); } - } - else - { - switch (gift) - { - case WC7 { IsBP: true } w7bp: - result.Add($"BP: {w7bp.BP}"); - break; - case WC7 { IsBean: true } w7bean: - result.Add($"Bean ID: {w7bean.Bean}"); - result.Add($"Quantity: {w7bean.Quantity}"); - break; - default: - result.Add(MsgMysteryGiftParseTypeUnknown); - break; - } - } - + catch { result.Add(MsgMysteryGiftParseFail); } + } + else + { switch (gift) { - case WC7 w7: - result.Add($"Repeatable: {w7.GiftRepeatable}"); - result.Add($"Collected: {w7.GiftUsed}"); - result.Add($"Once Per Day: {w7.GiftOncePerDay}"); + case WC7 { IsBP: true } w7bp: + result.Add($"BP: {w7bp.BP}"); + break; + case WC7 { IsBean: true } w7bean: + result.Add($"Bean ID: {w7bean.Bean}"); + result.Add($"Quantity: {w7bean.Quantity}"); + break; + default: + result.Add(MsgMysteryGiftParseTypeUnknown); break; } - return result; } - private static void AddLinesItem(MysteryGift gift, IBasicStrings strings, ICollection result) + switch (gift) { - result.Add($"Item: {strings.Item[gift.ItemID]} (Quantity: {gift.Quantity})"); - if (gift is not WC7 wc7) - return; + case WC7 w7: + result.Add($"Repeatable: {w7.GiftRepeatable}"); + result.Add($"Collected: {w7.GiftUsed}"); + result.Add($"Once Per Day: {w7.GiftOncePerDay}"); + break; + } + return result; + } - for (var ind = 1; wc7.GetItem(ind) != 0; ind++) - { - result.Add($"Item: {strings.Item[wc7.GetItem(ind)]} (Quantity: {wc7.GetQuantity(ind)})"); - } + private static void AddLinesItem(MysteryGift gift, IBasicStrings strings, ICollection result) + { + result.Add($"Item: {strings.Item[gift.ItemID]} (Quantity: {gift.Quantity})"); + if (gift is not WC7 wc7) + return; + + for (var ind = 1; wc7.GetItem(ind) != 0; ind++) + { + result.Add($"Item: {strings.Item[wc7.GetItem(ind)]} (Quantity: {wc7.GetQuantity(ind)})"); + } + } + + private static void AddLinesPKM(MysteryGift gift, IBasicStrings strings, ICollection result) + { + var id = gift.Generation < 7 ? $"{gift.TID:D5}/{gift.SID:D5}" : $"[{gift.TrainerSID7:D4}]{gift.TrainerID7:D6}"; + + var first = + $"{strings.Species[gift.Species]} @ {strings.Item[gift.HeldItem >= 0 ? gift.HeldItem : 0]} --- " + + (gift.IsEgg ? strings.EggName : $"{gift.OT_Name} - {id}"); + result.Add(first); + result.Add(string.Join(" / ", gift.Moves.Select(z => strings.Move[z]))); + + if (gift is WC7 wc7) + { + var addItem = wc7.AdditionalItem; + if (addItem != 0) + result.Add($"+ {strings.Item[addItem]}"); + } + } + + /// + /// Checks if the data is compatible with the . Sets appropriate data to the save file in order to receive the gift. + /// + /// Gift data to potentially insert to the save file. + /// Save file receiving the gift data. + /// Error message if incompatible. + /// True if compatible, false if incompatible. + public static bool IsCardCompatible(this MysteryGift g, SaveFile sav, out string message) + { + if (g.Generation != sav.Generation) + { + message = MsgMysteryGiftSlotSpecialReject; + return false; } - private static void AddLinesPKM(MysteryGift gift, IBasicStrings strings, ICollection result) + if (!sav.CanReceiveGift(g)) { - var id = gift.Generation < 7 ? $"{gift.TID:D5}/{gift.SID:D5}" : $"[{gift.TrainerSID7:D4}]{gift.TrainerID7:D6}"; - - var first = - $"{strings.Species[gift.Species]} @ {strings.Item[gift.HeldItem >= 0 ? gift.HeldItem : 0]} --- " - + (gift.IsEgg ? strings.EggName : $"{gift.OT_Name} - {id}"); - result.Add(first); - result.Add(string.Join(" / ", gift.Moves.Select(z => strings.Move[z]))); - - if (gift is WC7 wc7) - { - var addItem = wc7.AdditionalItem; - if (addItem != 0) - result.Add($"+ {strings.Item[addItem]}"); - } + message = MsgMysteryGiftTypeDetails; + return false; } - /// - /// Checks if the data is compatible with the . Sets appropriate data to the save file in order to receive the gift. - /// - /// Gift data to potentially insert to the save file. - /// Save file receiving the gift data. - /// Error message if incompatible. - /// True if compatible, false if incompatible. - public static bool IsCardCompatible(this MysteryGift g, SaveFile sav, out string message) + if (g is WC6 && g.CardID == 2048 && g.ItemID == 726) // Eon Ticket (OR/AS) { - if (g.Generation != sav.Generation) + if (sav is not SAV6AO) { message = MsgMysteryGiftSlotSpecialReject; return false; } - - if (!sav.CanReceiveGift(g)) - { - message = MsgMysteryGiftTypeDetails; - return false; - } - - if (g is WC6 && g.CardID == 2048 && g.ItemID == 726) // Eon Ticket (OR/AS) - { - if (sav is not SAV6AO) - { - message = MsgMysteryGiftSlotSpecialReject; - return false; - } - } - - message = string.Empty; - return true; } - /// - /// Checks if the gift values are receivable by the game. - /// - /// Save file receiving the gift data. - /// Gift data to potentially insert to the save file. - /// True if compatible, false if incompatible. - public static bool CanReceiveGift(this SaveFile sav, MysteryGift gift) - { - if (gift.Species > sav.MaxSpeciesID) - return false; - if (gift.Moves.Any(move => move > sav.MaxMoveID)) - return false; - if (gift.HeldItem > sav.MaxItemID) - return false; - return true; - } + message = string.Empty; + return true; + } + + /// + /// Checks if the gift values are receivable by the game. + /// + /// Save file receiving the gift data. + /// Gift data to potentially insert to the save file. + /// True if compatible, false if incompatible. + public static bool CanReceiveGift(this SaveFile sav, MysteryGift gift) + { + if (gift.Species > sav.MaxSpeciesID) + return false; + if (gift.Moves.Any(move => move > sav.MaxMoveID)) + return false; + if (gift.HeldItem > sav.MaxItemID) + return false; + return true; } } diff --git a/PKHeX.Core/MysteryGifts/PCD.cs b/PKHeX.Core/MysteryGifts/PCD.cs index cc45affb0..4cd4f8110 100644 --- a/PKHeX.Core/MysteryGifts/PCD.cs +++ b/PKHeX.Core/MysteryGifts/PCD.cs @@ -1,215 +1,214 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 Mystery Gift Template File +/// +/// +/// Big thanks to Grovyle91's Pokémon Mystery Gift Editor, from which the structure was referenced. +/// https://projectpokemon.org/home/profile/859-grovyle91/ +/// https://projectpokemon.org/home/forums/topic/5870-pok%C3%A9mon-mystery-gift-editor-v143-now-with-bw-support/ +/// See also: http://tccphreak.shiny-clique.net/debugger/pcdfiles.htm +/// +public sealed class PCD : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 { - /// - /// Generation 4 Mystery Gift Template File - /// - /// - /// Big thanks to Grovyle91's Pokémon Mystery Gift Editor, from which the structure was referenced. - /// https://projectpokemon.org/home/profile/859-grovyle91/ - /// https://projectpokemon.org/home/forums/topic/5870-pok%C3%A9mon-mystery-gift-editor-v143-now-with-bw-support/ - /// See also: http://tccphreak.shiny-clique.net/debugger/pcdfiles.htm - /// - public sealed class PCD : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 + public const int Size = 0x358; // 856 + public override int Generation => 4; + + public override byte Level { - public const int Size = 0x358; // 856 - public override int Generation => 4; + get => Gift.Level; + set => Gift.Level = value; + } - public override byte Level + public override int Ball + { + get => Gift.Ball; + set => Gift.Ball = value; + } + + public PCD() : this(new byte[Size]) { } + public PCD(byte[] data) : base(data) { } + + public override byte[] Write() + { + // Ensure PGT content is encrypted + var clone = new PCD((byte[])Data.Clone()); + if (clone.Gift.VerifyPKEncryption()) + clone.Gift = clone.Gift; + return clone.Data; + } + + public PGT Gift + { + get => _gift ??= new PGT(Data.Slice(0, PGT.Size)); + set => (_gift = value).Data.CopyTo(Data, 0); + } + + private PGT? _gift; + + public Span GetMetadata() => Data.AsSpan(PGT.Size); + public void SetMetadata(ReadOnlySpan data) => data.CopyTo(Data.AsSpan(PGT.Size)); + + public override bool GiftUsed { get => Gift.GiftUsed; set => Gift.GiftUsed = value; } + public override bool IsEntity { get => Gift.IsEntity; set => Gift.IsEntity = value; } + public override bool IsItem { get => Gift.IsItem; set => Gift.IsItem = value; } + public override int ItemID { get => Gift.ItemID; set => Gift.ItemID = value; } + public bool IsLockCapsule => IsItem && ItemID == 533; // 0x215 + public bool CanConvertToPGT => !IsLockCapsule; + + public override int CardID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x150)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x150), (ushort)value); + } + + private const int TitleLength = 0x48; + + private Span CardTitleSpan => Data.AsSpan(0x104, TitleLength); + + public override string CardTitle + { + get => StringConverter4.GetString(CardTitleSpan); + set => StringConverter4.SetString(CardTitleSpan, value.AsSpan(), TitleLength / 2, StringConverterOption.ClearFF); + } + + public ushort CardCompatibility => ReadUInt16LittleEndian(Data.AsSpan(0x14C)); // rest of bytes we don't really care about + + public override int Species { get => Gift.IsManaphyEgg ? 490 : Gift.Species; set => Gift.Species = value; } + public override IReadOnlyList Moves { get => Gift.Moves; set => Gift.Moves = value; } + public override int HeldItem { get => Gift.HeldItem; set => Gift.HeldItem = value; } + public override bool IsShiny => Gift.IsShiny; + public override Shiny Shiny => Gift.Shiny; + public override bool IsEgg { get => Gift.IsEgg; set => Gift.IsEgg = value; } + public override int Gender { get => Gift.Gender; set => Gift.Gender = value; } + public override int Form { get => Gift.Form; set => Gift.Form = value; } + public override int TID { get => Gift.TID; set => Gift.TID = value; } + public override int SID { get => Gift.SID; set => Gift.SID = value; } + public override string OT_Name { get => Gift.OT_Name; set => Gift.OT_Name = value; } + public override AbilityPermission Ability => Gift.Ability; + public override bool HasFixedIVs => Gift.HasFixedIVs; + public override void GetIVs(Span value) => Gift.GetIVs(value); + + // ILocation overrides + public override int Location { get => IsEgg ? 0 : Gift.EggLocation + 3000; set { } } + public override int EggLocation { get => IsEgg ? Gift.EggLocation + 3000 : 0; set { } } + + public bool GiftEquals(PGT pgt) + { + // Skip over the PGT's "Corresponding PCD Slot" @ 0x02 + byte[] g = pgt.Data; + byte[] c = Gift.Data; + if (g.Length != c.Length || g.Length < 3) + return false; + for (int i = 0; i < 2; i++) { - get => Gift.Level; - set => Gift.Level = value; - } - - public override int Ball - { - get => Gift.Ball; - set => Gift.Ball = value; - } - - public PCD() : this(new byte[Size]) { } - public PCD(byte[] data) : base(data) { } - - public override byte[] Write() - { - // Ensure PGT content is encrypted - var clone = new PCD((byte[])Data.Clone()); - if (clone.Gift.VerifyPKEncryption()) - clone.Gift = clone.Gift; - return clone.Data; - } - - public PGT Gift - { - get => _gift ??= new PGT(Data.Slice(0, PGT.Size)); - set => (_gift = value).Data.CopyTo(Data, 0); - } - - private PGT? _gift; - - public Span GetMetadata() => Data.AsSpan(PGT.Size); - public void SetMetadata(ReadOnlySpan data) => data.CopyTo(Data.AsSpan(PGT.Size)); - - public override bool GiftUsed { get => Gift.GiftUsed; set => Gift.GiftUsed = value; } - public override bool IsEntity { get => Gift.IsEntity; set => Gift.IsEntity = value; } - public override bool IsItem { get => Gift.IsItem; set => Gift.IsItem = value; } - public override int ItemID { get => Gift.ItemID; set => Gift.ItemID = value; } - public bool IsLockCapsule => IsItem && ItemID == 533; // 0x215 - public bool CanConvertToPGT => !IsLockCapsule; - - public override int CardID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x150)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x150), (ushort)value); - } - - private const int TitleLength = 0x48; - - private Span CardTitleSpan => Data.AsSpan(0x104, TitleLength); - - public override string CardTitle - { - get => StringConverter4.GetString(CardTitleSpan); - set => StringConverter4.SetString(CardTitleSpan, value.AsSpan(), TitleLength / 2, StringConverterOption.ClearFF); - } - - public ushort CardCompatibility => ReadUInt16LittleEndian(Data.AsSpan(0x14C)); // rest of bytes we don't really care about - - public override int Species { get => Gift.IsManaphyEgg ? 490 : Gift.Species; set => Gift.Species = value; } - public override IReadOnlyList Moves { get => Gift.Moves; set => Gift.Moves = value; } - public override int HeldItem { get => Gift.HeldItem; set => Gift.HeldItem = value; } - public override bool IsShiny => Gift.IsShiny; - public override Shiny Shiny => Gift.Shiny; - public override bool IsEgg { get => Gift.IsEgg; set => Gift.IsEgg = value; } - public override int Gender { get => Gift.Gender; set => Gift.Gender = value; } - public override int Form { get => Gift.Form; set => Gift.Form = value; } - public override int TID { get => Gift.TID; set => Gift.TID = value; } - public override int SID { get => Gift.SID; set => Gift.SID = value; } - public override string OT_Name { get => Gift.OT_Name; set => Gift.OT_Name = value; } - public override AbilityPermission Ability => Gift.Ability; - public override bool HasFixedIVs => Gift.HasFixedIVs; - public override void GetIVs(Span value) => Gift.GetIVs(value); - - // ILocation overrides - public override int Location { get => IsEgg ? 0 : Gift.EggLocation + 3000; set { } } - public override int EggLocation { get => IsEgg ? Gift.EggLocation + 3000 : 0; set { } } - - public bool GiftEquals(PGT pgt) - { - // Skip over the PGT's "Corresponding PCD Slot" @ 0x02 - byte[] g = pgt.Data; - byte[] c = Gift.Data; - if (g.Length != c.Length || g.Length < 3) + if (g[i] != c[i]) return false; - for (int i = 0; i < 2; i++) - { - if (g[i] != c[i]) - return false; - } - - for (int i = 3; i < g.Length; i++) - { - if (g[i] != c[i]) - return false; - } - - return true; } - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) + for (int i = 3; i < g.Length; i++) { - return Gift.ConvertToPKM(sav, criteria); + if (g[i] != c[i]) + return false; } - public bool CanBeReceivedBy(int pkmVersion) => (CardCompatibility >> pkmVersion & 1) == 1; + return true; + } - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + return Gift.ConvertToPKM(tr, criteria); + } + + public bool CanBeReceivedBy(int pkmVersion) => ((CardCompatibility >> pkmVersion) & 1) == 1; + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + var wc = Gift.PK; + if (!wc.IsEgg) { - var wc = Gift.PK; - if (!wc.IsEgg) + if (wc.TID != pk.TID) return false; + if (wc.SID != pk.SID) return false; + if (wc.OT_Name != pk.OT_Name) return false; + if (wc.OT_Gender != pk.OT_Gender) return false; + if (wc.Language != 0 && wc.Language != pk.Language) return false; + + if (pk.Format != 4) // transferred { - if (wc.TID != pkm.TID) return false; - if (wc.SID != pkm.SID) return false; - if (wc.OT_Name != pkm.OT_Name) return false; - if (wc.OT_Gender != pkm.OT_Gender) return false; - if (wc.Language != 0 && wc.Language != pkm.Language) return false; - - if (pkm.Format != 4) // transferred - { - // met location: deferred to general transfer check - if (wc.CurrentLevel > pkm.Met_Level) return false; - if (!IsMatchEggLocation(pkm)) - return false; - } - else - { - if (wc.Egg_Location + 3000 != pkm.Met_Location) return false; - if (wc.CurrentLevel != pkm.Met_Level) return false; - } - } - else // Egg - { - if (wc.Egg_Location + 3000 != pkm.Egg_Location && pkm.Egg_Location != Locations.LinkTrade4) // traded + // met location: deferred to general transfer check + if (wc.CurrentLevel > pk.Met_Level) return false; + if (!IsMatchEggLocation(pk)) return false; - if (wc.CurrentLevel != pkm.Met_Level) - return false; - if (pkm.IsEgg && !pkm.IsNative) - return false; - } - - if (wc.Form != evo.Form && !FormInfo.IsFormChangeable(wc.Species, wc.Form, pkm.Form, pkm.Format)) - return false; - - if (wc.Ball != pkm.Ball) return false; - if (wc.OT_Gender < 3 && wc.OT_Gender != pkm.OT_Gender) return false; - - // Milotic is the only gift to come with Contest stats. - if (wc.Species == (int)Core.Species.Milotic && pkm is IContestStats s && s.IsContestBelow(wc)) - return false; - - if (IsRandomPID()) - { - // Random PID, never shiny - // PID=0 was never used (pure random). - if (pkm.IsShiny) - return false; - - // Don't check gender. All gifts that have PID=1 are genderless, with one exception. - // The KOR Arcanine can end up with either gender, even though the template may have a specified gender. } else { - // Fixed PID - if (wc.Gender != pkm.Gender) - return false; + if (wc.Egg_Location + 3000 != pk.Met_Location) return false; + if (wc.CurrentLevel != pk.Met_Level) return false; } - - return true; + } + else // Egg + { + if (wc.Egg_Location + 3000 != pk.Egg_Location && pk.Egg_Location != Locations.LinkTrade4) // traded + return false; + if (wc.CurrentLevel != pk.Met_Level) + return false; + if (pk.IsEgg && !pk.IsNative) + return false; } - protected override bool IsMatchPartial(PKM pkm) => CanBeReceivedBy(pkm.Version); - protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species; + if (wc.Form != evo.Form && !FormInfo.IsFormChangeable(wc.Species, wc.Form, pk.Form, pk.Format)) + return false; - public bool RibbonEarth { get => Gift.RibbonEarth; set => Gift.RibbonEarth = value; } - public bool RibbonNational { get => Gift.RibbonNational; set => Gift.RibbonNational = value; } - public bool RibbonCountry { get => Gift.RibbonCountry; set => Gift.RibbonCountry = value; } - public bool RibbonChampionBattle { get => Gift.RibbonChampionBattle; set => Gift.RibbonChampionBattle = value; } - public bool RibbonChampionRegional { get => Gift.RibbonChampionRegional; set => Gift.RibbonChampionRegional = value; } - public bool RibbonChampionNational { get => Gift.RibbonChampionNational; set => Gift.RibbonChampionNational = value; } - public bool RibbonClassic { get => Gift.RibbonClassic; set => Gift.RibbonClassic = value; } - public bool RibbonWishing { get => Gift.RibbonWishing; set => Gift.RibbonWishing = value; } - public bool RibbonPremier { get => Gift.RibbonPremier; set => Gift.RibbonPremier = value; } - public bool RibbonEvent { get => Gift.RibbonEvent; set => Gift.RibbonEvent = value; } - public bool RibbonBirthday { get => Gift.RibbonBirthday; set => Gift.RibbonBirthday = value; } - public bool RibbonSpecial { get => Gift.RibbonSpecial; set => Gift.RibbonSpecial = value; } - public bool RibbonWorld { get => Gift.RibbonWorld; set => Gift.RibbonWorld = value; } - public bool RibbonChampionWorld { get => Gift.RibbonChampionWorld; set => Gift.RibbonChampionWorld = value; } - public bool RibbonSouvenir { get => Gift.RibbonSouvenir; set => Gift.RibbonSouvenir = value; } + if (wc.Ball != pk.Ball) return false; + if (wc.OT_Gender < 3 && wc.OT_Gender != pk.OT_Gender) return false; - public bool IsFixedPID() => Gift.PK.PID != 1; - public bool IsRandomPID() => Gift.PK.PID == 1; // nothing used 0 (full random), always anti-shiny + // Milotic is the only gift to come with Contest stats. + if (wc.Species == (int)Core.Species.Milotic && pk is IContestStats s && s.IsContestBelow(wc)) + return false; + + if (IsRandomPID()) + { + // Random PID, never shiny + // PID=0 was never used (pure random). + if (pk.IsShiny) + return false; + + // Don't check gender. All gifts that have PID=1 are genderless, with one exception. + // The KOR Arcanine can end up with either gender, even though the template may have a specified gender. + } + else + { + // Fixed PID + if (wc.Gender != pk.Gender) + return false; + } + + return true; } + + protected override bool IsMatchPartial(PKM pk) => CanBeReceivedBy(pk.Version); + protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + + public bool RibbonEarth { get => Gift.RibbonEarth; set => Gift.RibbonEarth = value; } + public bool RibbonNational { get => Gift.RibbonNational; set => Gift.RibbonNational = value; } + public bool RibbonCountry { get => Gift.RibbonCountry; set => Gift.RibbonCountry = value; } + public bool RibbonChampionBattle { get => Gift.RibbonChampionBattle; set => Gift.RibbonChampionBattle = value; } + public bool RibbonChampionRegional { get => Gift.RibbonChampionRegional; set => Gift.RibbonChampionRegional = value; } + public bool RibbonChampionNational { get => Gift.RibbonChampionNational; set => Gift.RibbonChampionNational = value; } + public bool RibbonClassic { get => Gift.RibbonClassic; set => Gift.RibbonClassic = value; } + public bool RibbonWishing { get => Gift.RibbonWishing; set => Gift.RibbonWishing = value; } + public bool RibbonPremier { get => Gift.RibbonPremier; set => Gift.RibbonPremier = value; } + public bool RibbonEvent { get => Gift.RibbonEvent; set => Gift.RibbonEvent = value; } + public bool RibbonBirthday { get => Gift.RibbonBirthday; set => Gift.RibbonBirthday = value; } + public bool RibbonSpecial { get => Gift.RibbonSpecial; set => Gift.RibbonSpecial = value; } + public bool RibbonWorld { get => Gift.RibbonWorld; set => Gift.RibbonWorld = value; } + public bool RibbonChampionWorld { get => Gift.RibbonChampionWorld; set => Gift.RibbonChampionWorld = value; } + public bool RibbonSouvenir { get => Gift.RibbonSouvenir; set => Gift.RibbonSouvenir = value; } + + public bool IsFixedPID() => Gift.PK.PID != 1; + public bool IsRandomPID() => Gift.PK.PID == 1; // nothing used 0 (full random), always anti-shiny } diff --git a/PKHeX.Core/MysteryGifts/PGF.cs b/PKHeX.Core/MysteryGifts/PGF.cs index 560f6f335..e9fd761a8 100644 --- a/PKHeX.Core/MysteryGifts/PGF.cs +++ b/PKHeX.Core/MysteryGifts/PGF.cs @@ -2,412 +2,411 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 Mystery Gift Template File +/// +public sealed class PGF : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, IContestStatsMutable, INature { - /// - /// Generation 5 Mystery Gift Template File - /// - public sealed class PGF : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, IContestStatsMutable, INature + public const int Size = 0xCC; + public override int Generation => 5; + + public PGF() : this(new byte[Size]) { } + public PGF(byte[] data) : base(data) { } + + public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } + public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(0x02), (ushort)value); } + public int OriginGame { get => Data[0x04]; set => Data[0x04] = (byte)value; } + // Unused 0x05 0x06, 0x07 + public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x08)); set => WriteUInt32LittleEndian(Data.AsSpan(0x08), value); } + + private byte RIB0 { get => Data[0x0C]; set => Data[0x0C] = value; } + private byte RIB1 { get => Data[0x0D]; set => Data[0x0D] = value; } + public bool RibbonCountry { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonNational { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonEarth { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonWorld { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonClassic { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonPremier { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonEvent { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonBirthday { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonSpecial { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonSouvenir { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonWishing { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonChampionBattle { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonChampionRegional { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonChampionNational { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonChampionWorld { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RIB1_7 { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + + public override int Ball { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } + public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(0x10), (ushort)value); } + public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x12)); set => WriteUInt16LittleEndian(Data.AsSpan(0x12), (ushort)value); } + public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14), (ushort)value); } + public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); } + public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); } + public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), (ushort)value); } + public override int Form { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public int Language { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + + public string Nickname { - public const int Size = 0xCC; - public override int Generation => 5; + get => StringConverter5.GetString(Data.AsSpan(0x1E, 11 * 2)); + set => StringConverter5.SetString(Data.AsSpan(0x1E, 11 * 2), value.AsSpan(), 11, StringConverterOption.ClearFF); + } - public PGF() : this(new byte[Size]) { } - public PGF(byte[] data) : base(data) { } + public int Nature { get => (sbyte)Data[0x34]; set => Data[0x34] = (byte)value; } + public override int Gender { get => Data[0x35]; set => Data[0x35] = (byte)value; } + public override int AbilityType { get => Data[0x36]; set => Data[0x36] = (byte)value; } + public int PIDType { get => Data[0x37]; set => Data[0x37] = (byte)value; } + public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x38)); set => WriteUInt16LittleEndian(Data.AsSpan(0x38), (ushort)value); } + public ushort MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x3A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3A), value); } + public int MetLevel { get => Data[0x3C]; set => Data[0x3C] = (byte)value; } + public byte CNT_Cool { get => Data[0x3D]; set => Data[0x3D] = value; } + public byte CNT_Beauty { get => Data[0x3E]; set => Data[0x3E] = value; } + public byte CNT_Cute { get => Data[0x3F]; set => Data[0x3F] = value; } + public byte CNT_Smart { get => Data[0x40]; set => Data[0x40] = value; } + public byte CNT_Tough { get => Data[0x41]; set => Data[0x41] = value; } + public byte CNT_Sheen { get => Data[0x42]; set => Data[0x42] = value; } + public int IV_HP { get => Data[0x43]; set => Data[0x43] = (byte)value; } + public int IV_ATK { get => Data[0x44]; set => Data[0x44] = (byte)value; } + public int IV_DEF { get => Data[0x45]; set => Data[0x45] = (byte)value; } + public int IV_SPE { get => Data[0x46]; set => Data[0x46] = (byte)value; } + public int IV_SPA { get => Data[0x47]; set => Data[0x47] = (byte)value; } + public int IV_SPD { get => Data[0x48]; set => Data[0x48] = (byte)value; } + // Unused 0x49 + public override string OT_Name + { + get => StringConverter5.GetString(Data.AsSpan(0x4A, 8 * 2)); + set => StringConverter5.SetString(Data.AsSpan(0x4A, 8 * 2), value.AsSpan(), 8, StringConverterOption.ClearFF); + } - public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } - public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(0x02), (ushort)value); } - public int OriginGame { get => Data[0x04]; set => Data[0x04] = (byte)value; } - // Unused 0x05 0x06, 0x07 - public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x08)); set => WriteUInt32LittleEndian(Data.AsSpan(0x08), value); } + public int OTGender { get => Data[0x5A]; set => Data[0x5A] = (byte)value; } + public override byte Level { get => Data[0x5B]; set => Data[0x5C] = value; } + public override bool IsEgg { get => Data[0x5C] == 1; set => Data[0x5C] = value ? (byte)1 : (byte)0; } + // Unused 0x5D 0x5E 0x5F + public override string CardTitle + { + get => StringConverter5.GetString(Data.AsSpan(0x60, 37 * 2)); + set => StringConverter5.SetString(Data.AsSpan(0x60, 37 * 2), value.AsSpan(), 36, StringConverterOption.ClearZero); + } - private byte RIB0 { get => Data[0x0C]; set => Data[0x0C] = value; } - private byte RIB1 { get => Data[0x0D]; set => Data[0x0D] = value; } - public bool RibbonCountry { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonNational { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonEarth { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonWorld { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonClassic { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonPremier { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonEvent { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonBirthday { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonSpecial { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonSouvenir { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonWishing { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonChampionBattle { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonChampionRegional { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonChampionNational { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonChampionWorld { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RIB1_7 { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + // Card Attributes + public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } - public override int Ball { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } - public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(0x10), (ushort)value); } - public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x12)); set => WriteUInt16LittleEndian(Data.AsSpan(0x12), (ushort)value); } - public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14), (ushort)value); } - public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); } - public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); } - public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), (ushort)value); } - public override int Form { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } - public int Language { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + private ushort Year { get => ReadUInt16LittleEndian(Data.AsSpan(0xAE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xAE), value); } + private byte Month { get => Data[0xAD]; set => Data[0xAD] = value; } + private byte Day { get => Data[0xAC]; set => Data[0xAC] = value; } - public string Nickname + /// + /// Gets or sets the date of the card. + /// + public DateTime? Date + { + get { - get => StringConverter5.GetString(Data.AsSpan(0x1E, 11 * 2)); - set => StringConverter5.SetString(Data.AsSpan(0x1E, 11 * 2), value.AsSpan(), 11, StringConverterOption.ClearFF); + // Check to see if date is valid + if (!DateUtil.IsDateValid(Year, Month, Day)) + return null; + + return new DateTime(Year, Month, Day); } - - public int Nature { get => (sbyte)Data[0x34]; set => Data[0x34] = (byte)value; } - public override int Gender { get => Data[0x35]; set => Data[0x35] = (byte)value; } - public override int AbilityType { get => Data[0x36]; set => Data[0x36] = (byte)value; } - public int PIDType { get => Data[0x37]; set => Data[0x37] = (byte)value; } - public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x38)); set => WriteUInt16LittleEndian(Data.AsSpan(0x38), (ushort)value); } - public ushort MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x3A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3A), value); } - public int MetLevel { get => Data[0x3C]; set => Data[0x3C] = (byte)value; } - public byte CNT_Cool { get => Data[0x3D]; set => Data[0x3D] = value; } - public byte CNT_Beauty { get => Data[0x3E]; set => Data[0x3E] = value; } - public byte CNT_Cute { get => Data[0x3F]; set => Data[0x3F] = value; } - public byte CNT_Smart { get => Data[0x40]; set => Data[0x40] = value; } - public byte CNT_Tough { get => Data[0x41]; set => Data[0x41] = value; } - public byte CNT_Sheen { get => Data[0x42]; set => Data[0x42] = value; } - public int IV_HP { get => Data[0x43]; set => Data[0x43] = (byte)value; } - public int IV_ATK { get => Data[0x44]; set => Data[0x44] = (byte)value; } - public int IV_DEF { get => Data[0x45]; set => Data[0x45] = (byte)value; } - public int IV_SPE { get => Data[0x46]; set => Data[0x46] = (byte)value; } - public int IV_SPA { get => Data[0x47]; set => Data[0x47] = (byte)value; } - public int IV_SPD { get => Data[0x48]; set => Data[0x48] = (byte)value; } - // Unused 0x49 - public override string OT_Name + set { - get => StringConverter5.GetString(Data.AsSpan(0x4A, 8 * 2)); - set => StringConverter5.SetString(Data.AsSpan(0x4A, 8 * 2), value.AsSpan(), 8, StringConverterOption.ClearFF); - } - - public int OTGender { get => Data[0x5A]; set => Data[0x5A] = (byte)value; } - public override byte Level { get => Data[0x5B]; set => Data[0x5C] = value; } - public override bool IsEgg { get => Data[0x5C] == 1; set => Data[0x5C] = value ? (byte)1 : (byte)0; } - // Unused 0x5D 0x5E 0x5F - public override string CardTitle - { - get => StringConverter5.GetString(Data.AsSpan(0x60, 37 * 2)); - set => StringConverter5.SetString(Data.AsSpan(0x60, 37 * 2), value.AsSpan(), 36, StringConverterOption.ClearZero); - } - - // Card Attributes - public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } - - private ushort Year { get => ReadUInt16LittleEndian(Data.AsSpan(0xAE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xAE), value); } - private byte Month { get => Data[0xAD]; set => Data[0xAD] = value; } - private byte Day { get => Data[0xAC]; set => Data[0xAC] = value; } - - /// - /// Gets or sets the date of the card. - /// - public DateTime? Date - { - get + if (value.HasValue) { - // Check to see if date is valid - if (!DateUtil.IsDateValid(Year, Month, Day)) - return null; - - return new DateTime(Year, Month, Day); - } - set - { - if (value.HasValue) - { - // Only update the properties if a value is provided. - Year = (ushort)value.Value.Year; - Month = (byte)value.Value.Month; - Day = (byte)value.Value.Day; - } - else - { - // Clear the Met Date. - // If code tries to access MetDate again, null will be returned. - Year = 0; - Month = 0; - Day = 0; - } - } - } - - public override int CardID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0xB0)); - set => WriteUInt16LittleEndian(Data.AsSpan(0xB0), (ushort)value); - } - - public int CardLocation { get => Data[0xB2]; set => Data[0xB2] = (byte)value; } - public int CardType { get => Data[0xB3]; set => Data[0xB3] = (byte)value; } - public override bool GiftUsed { get => Data[0xB4] >> 1 > 0; set => Data[0xB4] = (byte)((Data[0xB4] & ~2) | (value ? 2 : 0)); } - public bool MultiObtain { get => Data[0xB4] == 1; set => Data[0xB4] = value ? (byte)1 : (byte)0; } - - // Meta Accessible Properties - public override int[] IVs - { - get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; - set - { - if (value.Length != 6) return; - IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; - IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; - } - } - - public override void GetIVs(Span value) - { - if (value.Length != 6) - return; - value[0] = IV_HP; - value[1] = IV_ATK; - value[2] = IV_DEF; - value[3] = IV_SPE; - value[4] = IV_SPA; - value[5] = IV_SPD; - } - - public bool IsNicknamed => Nickname.Length > 0; - public override bool IsShiny => PIDType == 2; - public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } - public override IReadOnlyList Moves => new[] { Move1, Move2, Move3, Move4 }; - public override bool IsEntity { get => CardType == 1; set { if (value) CardType = 1; } } - public override bool IsItem { get => CardType == 2; set { if (value) CardType = 2; } } - public bool IsPower { get => CardType == 3; set { if (value) CardType = 3; } } - - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - if (!IsEntity) - throw new ArgumentException(nameof(IsEntity)); - - var rnd = Util.Rand; - - var dt = DateTime.Now; - if (Day == 0) - { - Day = (byte)dt.Day; - Month = (byte)dt.Month; - Year = (byte)dt.Year; - } - - int currentLevel = Level > 0 ? Level : rnd.Next(1, 101); - var pi = PersonalTable.B2W2.GetFormEntry(Species, Form); - PK5 pk = new() - { - Species = Species, - HeldItem = HeldItem, - Met_Level = currentLevel, - Nature = Nature != -1 ? Nature : rnd.Next(25), - Form = Form, - Version = OriginGame == 0 ? sav.Game : OriginGame, - Language = Language == 0 ? sav.Language : Language, - Ball = Ball, - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - Met_Location = MetLocation, - MetDate = Date, - Egg_Location = EggLocation, - CNT_Cool = CNT_Cool, - CNT_Beauty = CNT_Beauty, - CNT_Cute = CNT_Cute, - CNT_Smart = CNT_Smart, - CNT_Tough = CNT_Tough, - CNT_Sheen = CNT_Sheen, - - EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), - - // Ribbons - RibbonCountry = RibbonCountry, - RibbonNational = RibbonNational, - RibbonEarth = RibbonEarth, - RibbonWorld = RibbonWorld, - RibbonClassic = RibbonClassic, - RibbonPremier = RibbonPremier, - RibbonEvent = RibbonEvent, - RibbonBirthday = RibbonBirthday, - - RibbonSpecial = RibbonSpecial, - RibbonSouvenir = RibbonSouvenir, - RibbonWishing = RibbonWishing, - RibbonChampionBattle = RibbonChampionBattle, - RibbonChampionRegional = RibbonChampionRegional, - RibbonChampionNational = RibbonChampionNational, - RibbonChampionWorld = RibbonChampionWorld, - - FatefulEncounter = true, - }; - if (sav.Generation > 5 && OriginGame == 0) // Gen6+, give random gen5 game - pk.Version = (int)GameVersion.W + rnd.Next(4); - - if (Move1 == 0) // No moves defined - pk.Moves = MoveLevelUp.GetEncounterMoves(Species, Form, Level, (GameVersion)pk.Version); - - pk.SetMaximumPPCurrent(); - - if (IsEgg) // User's - { - pk.TID = sav.TID; - pk.SID = sav.SID; - pk.OT_Name = sav.OT; - pk.OT_Gender = sav.Gender; - } - else // Hardcoded - { - pk.TID = TID; - pk.SID = SID; - pk.OT_Name = OT_Name; - pk.OT_Gender = (OTGender == 3 ? sav.Gender : OTGender) & 1; // some events have variable gender based on receiving SaveFile - } - - pk.IsNicknamed = IsNicknamed; - pk.Nickname = IsNicknamed ? Nickname : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); - - SetPINGA(pk, criteria); - - if (IsEgg) - SetEggMetDetails(pk); - - pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; - - pk.RefreshChecksum(); - return pk; - } - - private void SetEggMetDetails(PK5 pk) - { - pk.IsEgg = true; - pk.EggMetDate = Date; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); - pk.IsNicknamed = true; - } - - private void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = PersonalTable.B2W2.GetFormEntry(Species, Form); - pk.Nature = (int)criteria.GetNature((Nature)Nature); - pk.Gender = pi.Genderless ? 2 : Gender != 2 ? Gender : criteria.GetGender(-1, pi); - var av = GetAbilityIndex(criteria); - SetPID(pk, av); - pk.RefreshAbility(av); - SetIVs(pk); - } - - private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch - { - 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 - 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H - _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), - }; - - public override AbilityPermission Ability => AbilityType switch - { - 0 => AbilityPermission.OnlyFirst, - 1 => AbilityPermission.OnlySecond, - 2 => AbilityPermission.OnlyHidden, - 3 => AbilityPermission.Any12, - _ => AbilityPermission.Any12H, - }; - - private void SetPID(PKM pk, int av) - { - if (PID != 0) - { - pk.PID = PID; - return; - } - - var rnd = Util.Rand; - pk.PID = rnd.Rand32(); - // Force Gender - do { pk.PID = (pk.PID & 0xFFFFFF00) | (uint)rnd.Next(0x100); } - while (!pk.IsGenderValid()); - - if (PIDType == 2) // Always - { - uint gb = pk.PID & 0xFF; - pk.PID = PIDGenerator.GetMG5ShinyPID(gb, (uint)av, pk.TID, pk.SID); - } - else if (PIDType != 1) // Force Not Shiny - { - if (pk.IsShiny) - pk.PID ^= 0x10000000; - } - - if (av == 1) - pk.PID |= 0x10000; - else - pk.PID &= 0xFFFEFFFF; - } - - public override Shiny Shiny => PIDType switch - { - 0 when !IsEgg => Shiny.Never, - 2 when !IsEgg => Shiny.Always, - _ => Shiny.Random, // 1 - }; - - private void SetIVs(PKM pk) - { - Span finalIVs = stackalloc int[6]; - GetIVs(finalIVs); - var rnd = Util.Rand; - for (int i = 0; i < finalIVs.Length; i++) - finalIVs[i] = finalIVs[i] == 0xFF ? rnd.Next(32) : finalIVs[i]; - pk.SetIVs(finalIVs); - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!IsEgg) - { - if (SID != pkm.SID) return false; - if (TID != pkm.TID) return false; - if (OT_Name != pkm.OT_Name) return false; - if (OTGender < 3 && OTGender != pkm.OT_Gender) return false; - if (PID != 0 && pkm.PID != PID) return false; - if (PIDType == 0 && pkm.IsShiny) return false; - if (PIDType == 2 && !pkm.IsShiny) return false; - if (OriginGame != 0 && OriginGame != pkm.Version) return false; - if (Language != 0 && Language != pkm.Language) return false; - - if (!IsMatchEggLocation(pkm)) return false; - if (MetLocation != pkm.Met_Location) return false; + // Only update the properties if a value is provided. + Year = (ushort)value.Value.Year; + Month = (byte)value.Value.Month; + Day = (byte)value.Value.Day; } else { - if (EggLocation != pkm.Egg_Location) // traded - { - if (pkm.Egg_Location != Locations.LinkTrade5) - return false; - } - else if (PIDType == 0 && pkm.IsShiny) - { - return false; // can't be traded away for un-shiny - } + // Clear the Met Date. + // If code tries to access MetDate again, null will be returned. + Year = 0; + Month = 0; + Day = 0; + } + } + } - if (pkm.IsEgg && !pkm.IsNative) + public override int CardID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0xB0)); + set => WriteUInt16LittleEndian(Data.AsSpan(0xB0), (ushort)value); + } + + public int CardLocation { get => Data[0xB2]; set => Data[0xB2] = (byte)value; } + public int CardType { get => Data[0xB3]; set => Data[0xB3] = (byte)value; } + public override bool GiftUsed { get => Data[0xB4] >> 1 > 0; set => Data[0xB4] = (byte)((Data[0xB4] & ~2) | (value ? 2 : 0)); } + public bool MultiObtain { get => Data[0xB4] == 1; set => Data[0xB4] = value ? (byte)1 : (byte)0; } + + // Meta Accessible Properties + public override int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set + { + if (value.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; + } + } + + public override void GetIVs(Span value) + { + if (value.Length != 6) + return; + value[0] = IV_HP; + value[1] = IV_ATK; + value[2] = IV_DEF; + value[3] = IV_SPE; + value[4] = IV_SPA; + value[5] = IV_SPD; + } + + public bool IsNicknamed => Nickname.Length > 0; + public override bool IsShiny => PIDType == 2; + public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } + public override IReadOnlyList Moves => new[] { Move1, Move2, Move3, Move4 }; + public override bool IsEntity { get => CardType == 1; set { if (value) CardType = 1; } } + public override bool IsItem { get => CardType == 2; set { if (value) CardType = 2; } } + public bool IsPower { get => CardType == 3; set { if (value) CardType = 3; } } + + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + if (!IsEntity) + throw new ArgumentException(nameof(IsEntity)); + + var rnd = Util.Rand; + + var dt = DateTime.Now; + if (Day == 0) + { + Day = (byte)dt.Day; + Month = (byte)dt.Month; + Year = (byte)dt.Year; + } + + int currentLevel = Level > 0 ? Level : rnd.Next(1, 101); + var pi = PersonalTable.B2W2.GetFormEntry(Species, Form); + PK5 pk = new() + { + Species = Species, + HeldItem = HeldItem, + Met_Level = currentLevel, + Nature = Nature != -1 ? Nature : rnd.Next(25), + Form = Form, + Version = OriginGame == 0 ? tr.Game : OriginGame, + Language = Language == 0 ? tr.Language : Language, + Ball = Ball, + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + Met_Location = MetLocation, + MetDate = Date, + Egg_Location = EggLocation, + CNT_Cool = CNT_Cool, + CNT_Beauty = CNT_Beauty, + CNT_Cute = CNT_Cute, + CNT_Smart = CNT_Smart, + CNT_Tough = CNT_Tough, + CNT_Sheen = CNT_Sheen, + + EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), + + // Ribbons + RibbonCountry = RibbonCountry, + RibbonNational = RibbonNational, + RibbonEarth = RibbonEarth, + RibbonWorld = RibbonWorld, + RibbonClassic = RibbonClassic, + RibbonPremier = RibbonPremier, + RibbonEvent = RibbonEvent, + RibbonBirthday = RibbonBirthday, + + RibbonSpecial = RibbonSpecial, + RibbonSouvenir = RibbonSouvenir, + RibbonWishing = RibbonWishing, + RibbonChampionBattle = RibbonChampionBattle, + RibbonChampionRegional = RibbonChampionRegional, + RibbonChampionNational = RibbonChampionNational, + RibbonChampionWorld = RibbonChampionWorld, + + FatefulEncounter = true, + }; + if (tr.Generation > 5 && OriginGame == 0) // Gen6+, give random gen5 game + pk.Version = (int)GameVersion.W + rnd.Next(4); + + if (Move1 == 0) // No moves defined + pk.Moves = MoveLevelUp.GetEncounterMoves(Species, Form, Level, (GameVersion)pk.Version); + + pk.SetMaximumPPCurrent(); + + if (IsEgg) // User's + { + pk.TID = tr.TID; + pk.SID = tr.SID; + pk.OT_Name = tr.OT; + pk.OT_Gender = tr.Gender; + } + else // Hardcoded + { + pk.TID = TID; + pk.SID = SID; + pk.OT_Name = OT_Name; + pk.OT_Gender = (OTGender == 3 ? tr.Gender : OTGender) & 1; // some events have variable gender based on receiving SaveFile + } + + pk.IsNicknamed = IsNicknamed; + pk.Nickname = IsNicknamed ? Nickname : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); + + SetPINGA(pk, criteria); + + if (IsEgg) + SetEggMetDetails(pk); + + pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; + + pk.RefreshChecksum(); + return pk; + } + + private void SetEggMetDetails(PK5 pk) + { + pk.IsEgg = true; + pk.EggMetDate = Date; + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); + pk.IsNicknamed = true; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = PersonalTable.B2W2.GetFormEntry(Species, Form); + pk.Nature = (int)criteria.GetNature((Nature)Nature); + pk.Gender = pi.Genderless ? 2 : Gender != 2 ? Gender : criteria.GetGender(-1, pi); + var av = GetAbilityIndex(criteria); + SetPID(pk, av); + pk.RefreshAbility(av); + SetIVs(pk); + } + + private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch + { + 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 + 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H + _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), + }; + + public override AbilityPermission Ability => AbilityType switch + { + 0 => AbilityPermission.OnlyFirst, + 1 => AbilityPermission.OnlySecond, + 2 => AbilityPermission.OnlyHidden, + 3 => AbilityPermission.Any12, + _ => AbilityPermission.Any12H, + }; + + private void SetPID(PKM pk, int av) + { + if (PID != 0) + { + pk.PID = PID; + return; + } + + var rnd = Util.Rand; + pk.PID = rnd.Rand32(); + // Force Gender + do { pk.PID = (pk.PID & 0xFFFFFF00) | (uint)rnd.Next(0x100); } + while (!pk.IsGenderValid()); + + if (PIDType == 2) // Always + { + uint gb = pk.PID & 0xFF; + pk.PID = PIDGenerator.GetMG5ShinyPID(gb, (uint)av, pk.TID, pk.SID); + } + else if (PIDType != 1) // Force Not Shiny + { + if (pk.IsShiny) + pk.PID ^= 0x10000000; + } + + if (av == 1) + pk.PID |= 0x10000; + else + pk.PID &= 0xFFFEFFFF; + } + + public override Shiny Shiny => PIDType switch + { + 0 when !IsEgg => Shiny.Never, + 2 when !IsEgg => Shiny.Always, + _ => Shiny.Random, // 1 + }; + + private void SetIVs(PKM pk) + { + Span finalIVs = stackalloc int[6]; + GetIVs(finalIVs); + var rnd = Util.Rand; + for (int i = 0; i < finalIVs.Length; i++) + finalIVs[i] = finalIVs[i] == 0xFF ? rnd.Next(32) : finalIVs[i]; + pk.SetIVs(finalIVs); + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsEgg) + { + if (SID != pk.SID) return false; + if (TID != pk.TID) return false; + if (OT_Name != pk.OT_Name) return false; + if (OTGender < 3 && OTGender != pk.OT_Gender) return false; + if (PID != 0 && pk.PID != PID) return false; + if (PIDType == 0 && pk.IsShiny) return false; + if (PIDType == 2 && !pk.IsShiny) return false; + if (OriginGame != 0 && OriginGame != pk.Version) return false; + if (Language != 0 && Language != pk.Language) return false; + + if (!IsMatchEggLocation(pk)) return false; + if (MetLocation != pk.Met_Location) return false; + } + else + { + if (EggLocation != pk.Egg_Location) // traded + { + if (pk.Egg_Location != Locations.LinkTrade5) return false; } + else if (PIDType == 0 && pk.IsShiny) + { + return false; // can't be traded away for un-shiny + } - if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pkm.Form, pkm.Format)) + if (pk.IsEgg && !pk.IsNative) return false; - - if (Level != pkm.Met_Level) return false; - if (Ball != pkm.Ball) return false; - if (Nature != -1 && pkm.Nature != Nature) - return false; - if (Gender != 2 && Gender != pkm.Gender) return false; - - if (pkm is IContestStats s && s.IsContestBelow(this)) - return false; - - return true; } - protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species; - protected override bool IsMatchPartial(PKM pkm) => CanBeReceivedBy(pkm.Version); - private static bool CanBeReceivedBy(int _) => true; + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, pk.Format)) + return false; + + if (Level != pk.Met_Level) return false; + if (Ball != pk.Ball) return false; + if (Nature != -1 && pk.Nature != Nature) + return false; + if (Gender != 2 && Gender != pk.Gender) return false; + + if (pk is IContestStats s && s.IsContestBelow(this)) + return false; + + return true; } + + protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchPartial(PKM pk) => CanBeReceivedBy(pk.Version); + private static bool CanBeReceivedBy(int _) => true; } diff --git a/PKHeX.Core/MysteryGifts/PGT.cs b/PKHeX.Core/MysteryGifts/PGT.cs index d441c84f7..188a626c4 100644 --- a/PKHeX.Core/MysteryGifts/PGT.cs +++ b/PKHeX.Core/MysteryGifts/PGT.cs @@ -1,302 +1,301 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 Mystery Gift Template File (Inner Gift Data, no card data) +/// +public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 { - /// - /// Generation 4 Mystery Gift Template File (Inner Gift Data, no card data) - /// - public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4 + public const int Size = 0x104; // 260 + public override int Generation => 4; + + public override byte Level { - public const int Size = 0x104; // 260 - public override int Generation => 4; + get => IsManaphyEgg ? (byte)1 : IsEntity ? (byte)PK.Met_Level : (byte)0; + set { if (IsEntity) PK.Met_Level = value; } + } - public override byte Level + public override int Ball + { + get => IsEntity ? PK.Ball : 0; + set { if (IsEntity) PK.Ball = value; } + } + + public override AbilityPermission Ability => IsManaphyEgg ? AbilityPermission.Any12 : (int)(PK.PID & 1) == 1 ? AbilityPermission.OnlySecond : AbilityPermission.OnlyFirst; + + private enum GiftType + { + Pokémon = 1, + PokémonEgg = 2, + Item = 3, + Rule = 4, + Seal = 5, + Accessory = 6, + ManaphyEgg = 7, + MemberCard = 8, + OaksLetter = 9, + AzureFlute = 10, + PokétchApp = 11, + Ribbon = 12, + PokéWalkerArea = 14, + } + + public override string CardTitle { get => "Raw Gift (PGT)"; set { } } + public override int CardID { get => -1; set { } } + public override bool GiftUsed { get => false; set { } } + public override Shiny Shiny => IsEgg ? Shiny.Random : PK.PID == 1 ? Shiny.Never : IsShiny ? Shiny.Always : Shiny.Never; + + public PGT() : this(new byte[Size]) { } + public PGT(byte[] data) : base(data) { } + + public byte CardType { get => Data[0]; set => Data[0] = value; } + // Unused 0x01 + public byte Slot { get => Data[2]; set => Data[2] = value; } + public byte Detail { get => Data[3]; set => Data[3] = value; } + public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(0x4)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4), (ushort)value); } + + public PK4 PK + { + get => _pk ??= new PK4(Data.Slice(8, PokeCrypto.SIZE_4PARTY)); + set { - get => IsManaphyEgg ? (byte)1 : IsEntity ? (byte)PK.Met_Level : (byte)0; - set { if (IsEntity) PK.Met_Level = value; } + _pk = value; + var data = Array.FindIndex(value.Data, z => z != 0) == -1 // all zero + ? value.Data + : PokeCrypto.EncryptArray45(value.Data); + data.CopyTo(Data, 8); + } + } + + public override byte[] Write() + { + // Ensure PGT content is encrypted + var clone = new PGT((byte[])Data.Clone()); + clone.VerifyPKEncryption(); + return clone.Data; + } + + private PK4? _pk; + + /// + /// Double checks the encryption of the gift data for Pokemon data. + /// + /// True if data was encrypted, false if the data was not modified. + public bool VerifyPKEncryption() + { + if (PGTGiftType is not (GiftType.Pokémon or GiftType.PokémonEgg)) + return false; // not encrypted + if (ReadUInt32LittleEndian(Data.AsSpan(0x64 + 8)) != 0) + return false; // already encrypted (unused PK4 field, zero) + EncryptPK(); + return true; + } + + private void EncryptPK() + { + var span = Data.AsSpan(8, PokeCrypto.SIZE_4PARTY); + var ekdata = PokeCrypto.EncryptArray45(span); + ekdata.CopyTo(span); + } + + private GiftType PGTGiftType { get => (GiftType)Data[0]; set => Data[0] = (byte)value; } + public bool IsHatched => PGTGiftType == GiftType.Pokémon; + public override bool IsEgg { get => PGTGiftType == GiftType.PokémonEgg || IsManaphyEgg; set { if (value) { PGTGiftType = GiftType.PokémonEgg; PK.IsEgg = true; } } } + public bool IsManaphyEgg { get => PGTGiftType == GiftType.ManaphyEgg; set { if (value) PGTGiftType = GiftType.ManaphyEgg; } } + public override bool EggEncounter => IsEgg; + public override bool IsItem { get => PGTGiftType == GiftType.Item; set { if (value) PGTGiftType = GiftType.Item; } } + public override bool IsEntity { get => PGTGiftType is GiftType.Pokémon or GiftType.PokémonEgg or GiftType.ManaphyEgg; set { } } + + public override int Species { get => IsManaphyEgg ? 490 : PK.Species; set => PK.Species = value; } + public override IReadOnlyList Moves { get => PK.Moves; set => PK.SetMoves(value.ToArray()); } + public override int HeldItem { get => PK.HeldItem; set => PK.HeldItem = value; } + public override bool IsShiny => PK.IsShiny; + public override int Gender { get => PK.Gender; set => PK.Gender = value; } + public override int Form { get => PK.Form; set => PK.Form = value; } + public override int TID { get => (ushort)PK.TID; set => PK.TID = value; } + public override int SID { get => (ushort)PK.SID; set => PK.SID = value; } + public override string OT_Name { get => PK.OT_Name; set => PK.OT_Name = value; } + public override int Location { get => PK.Met_Location; set => PK.Met_Location = value; } + public override int EggLocation { get => PK.Egg_Location; set => PK.Egg_Location = value; } + public override bool HasFixedIVs => PK.IV32 != 0; + public override void GetIVs(Span value) + { + if (HasFixedIVs) + PK.GetIVs(value); + } + + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + if (!IsEntity) + throw new ArgumentException(nameof(IsEntity)); + + // template is already filled out, only minor mutations required + PK4 pk4 = new((byte[])PK.Data.Clone()) { Sanity = 0 }; + if (!IsHatched && Detail == 0) + { + pk4.OT_Name = tr.OT; + pk4.TID = tr.TID; + pk4.SID = tr.SID; + pk4.OT_Gender = tr.Gender; + pk4.Language = tr.Language; } - public override int Ball - { - get => IsEntity ? PK.Ball : 0; - set { if (IsEntity) PK.Ball = value; } - } - - public override AbilityPermission Ability => IsManaphyEgg ? AbilityPermission.Any12 : (int)(PK.PID & 1) == 1 ? AbilityPermission.OnlySecond : AbilityPermission.OnlyFirst; - - private enum GiftType - { - Pokémon = 1, - PokémonEgg = 2, - Item = 3, - Rule = 4, - Seal = 5, - Accessory = 6, - ManaphyEgg = 7, - MemberCard = 8, - OaksLetter = 9, - AzureFlute = 10, - PokétchApp = 11, - Ribbon = 12, - PokéWalkerArea = 14, - } - - public override string CardTitle { get => "Raw Gift (PGT)"; set { } } - public override int CardID { get => -1; set { } } - public override bool GiftUsed { get => false; set { } } - public override Shiny Shiny => IsEgg ? Shiny.Random : PK.PID == 1 ? Shiny.Never : IsShiny ? Shiny.Always : Shiny.Never; - - public PGT() : this(new byte[Size]) { } - public PGT(byte[] data) : base(data) { } - - public byte CardType { get => Data[0]; set => Data[0] = value; } - // Unused 0x01 - public byte Slot { get => Data[2]; set => Data[2] = value; } - public byte Detail { get => Data[3]; set => Data[3] = value; } - public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(0x4)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4), (ushort)value); } - - public PK4 PK - { - get => _pk ??= new PK4(Data.Slice(8, PokeCrypto.SIZE_4PARTY)); - set - { - _pk = value; - var data = Array.FindIndex(value.Data, z => z != 0) == -1 // all zero - ? value.Data - : PokeCrypto.EncryptArray45(value.Data); - data.CopyTo(Data, 8); - } - } - - public override byte[] Write() - { - // Ensure PGT content is encrypted - var clone = new PGT((byte[])Data.Clone()); - clone.VerifyPKEncryption(); - return clone.Data; - } - - private PK4? _pk; - - /// - /// Double checks the encryption of the gift data for Pokemon data. - /// - /// True if data was encrypted, false if the data was not modified. - public bool VerifyPKEncryption() - { - if (PGTGiftType is not (GiftType.Pokémon or GiftType.PokémonEgg)) - return false; // not encrypted - if (ReadUInt32LittleEndian(Data.AsSpan(0x64 + 8)) != 0) - return false; // already encrypted (unused PK4 field, zero) - EncryptPK(); - return true; - } - - private void EncryptPK() - { - var span = Data.AsSpan(8, PokeCrypto.SIZE_4PARTY); - var ekdata = PokeCrypto.EncryptArray45(span); - ekdata.CopyTo(span); - } - - private GiftType PGTGiftType { get => (GiftType)Data[0]; set => Data[0] = (byte)value; } - public bool IsHatched => PGTGiftType == GiftType.Pokémon; - public override bool IsEgg { get => PGTGiftType == GiftType.PokémonEgg || IsManaphyEgg; set { if (value) { PGTGiftType = GiftType.PokémonEgg; PK.IsEgg = true; } } } - public bool IsManaphyEgg { get => PGTGiftType == GiftType.ManaphyEgg; set { if (value) PGTGiftType = GiftType.ManaphyEgg; } } - public override bool EggEncounter => IsEgg; - public override bool IsItem { get => PGTGiftType == GiftType.Item; set { if (value) PGTGiftType = GiftType.Item; } } - public override bool IsEntity { get => PGTGiftType is GiftType.Pokémon or GiftType.PokémonEgg or GiftType.ManaphyEgg; set { } } - - public override int Species { get => IsManaphyEgg ? 490 : PK.Species; set => PK.Species = value; } - public override IReadOnlyList Moves { get => PK.Moves; set => PK.SetMoves(value.ToArray()); } - public override int HeldItem { get => PK.HeldItem; set => PK.HeldItem = value; } - public override bool IsShiny => PK.IsShiny; - public override int Gender { get => PK.Gender; set => PK.Gender = value; } - public override int Form { get => PK.Form; set => PK.Form = value; } - public override int TID { get => (ushort)PK.TID; set => PK.TID = value; } - public override int SID { get => (ushort)PK.SID; set => PK.SID = value; } - public override string OT_Name { get => PK.OT_Name; set => PK.OT_Name = value; } - public override int Location { get => PK.Met_Location; set => PK.Met_Location = value; } - public override int EggLocation { get => PK.Egg_Location; set => PK.Egg_Location = value; } - public override bool HasFixedIVs => PK.IV32 != 0; - public override void GetIVs(Span value) - { - if (HasFixedIVs) - PK.GetIVs(value); - } - - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - if (!IsEntity) - throw new ArgumentException(nameof(IsEntity)); - - // template is already filled out, only minor mutations required - PK4 pk4 = new((byte[])PK.Data.Clone()) { Sanity = 0 }; - if (!IsHatched && Detail == 0) - { - pk4.OT_Name = sav.OT; - pk4.TID = sav.TID; - pk4.SID = sav.SID; - pk4.OT_Gender = sav.Gender; - pk4.Language = sav.Language; - } - - if (IsManaphyEgg) - SetDefaultManaphyEggDetails(pk4, sav); - - SetPINGA(pk4, criteria); - SetMetData(pk4, sav); - - var pi = pk4.PersonalInfo; - pk4.CurrentFriendship = pk4.IsEgg ? pi.HatchCycles : pi.BaseFriendship; - - pk4.RefreshChecksum(); - return pk4; - } - - private void SetMetData(PK4 pk4, ITrainerInfo trainer) - { - if (!EggEncounter) - { - pk4.Met_Location = pk4.Egg_Location + 3000; - pk4.Egg_Location = 0; - pk4.MetDate = DateTime.Now; - pk4.IsEgg = false; - } - else - { - pk4.Egg_Location += 3000; - if (trainer.Generation == 4) - SetUnhatchedEggDetails(pk4); - else - SetHatchedEggDetails(pk4); - } - } - - private static void SetDefaultManaphyEggDetails(PK4 pk4, ITrainerInfo trainer) - { - // Since none of this data is populated, fill in default info. - pk4.Species = (int)Core.Species.Manaphy; - pk4.Gender = 2; - // Level 1 Moves - pk4.Move1 = 294; pk4.Move1_PP = 20; - pk4.Move2 = 145; pk4.Move2_PP = 30; - pk4.Move3 = 346; pk4.Move3_PP = 15; - pk4.Ability = (int)Core.Ability.Hydration; - pk4.FatefulEncounter = true; - pk4.Ball = (int)Core.Ball.Poke; - pk4.Version = GameVersion.Gen4.Contains(trainer.Game) ? trainer.Game : (int)GameVersion.D; - var lang = trainer.Language < (int)LanguageID.Korean ? trainer.Language : (int)LanguageID.English; - pk4.Language = lang; - pk4.Egg_Location = 1; // Ranger (will be +3000 later) - pk4.Nickname = SpeciesName.GetSpeciesNameGeneration((int)Core.Species.Manaphy, lang, 4); - } - - private void SetPINGA(PK4 pk4, EncounterCriteria criteria) - { - // Ability is forced already, can't force anything - - // Generate PID - var seed = SetPID(pk4, criteria); - - if (!IsManaphyEgg) - seed = Util.Rand32(); // reseed, do not have method 1 correlation - - // Generate IVs - if (pk4.IV32 == 0) // Ignore Nickname/Egg flag bits; none are set for varied-IV gifts. - { - uint iv1 = ((seed = RNG.LCRNG.Next(seed)) >> 16) & 0x7FFF; - uint iv2 = ((RNG.LCRNG.Next(seed)) >> 16) & 0x7FFF; - pk4.IV32 = iv1 | iv2 << 15; - } - } - - private uint SetPID(PK4 pk4, EncounterCriteria criteria) - { - uint seed = Util.Rand32(); - if (pk4.PID != 1 && !IsManaphyEgg) - return seed; // PID is already set. - - // The games don't decide the Nature/Gender up-front, but we can try to honor requests. - // Pre-determine the result values, and generate something. - var n = (int)criteria.GetNature(Nature.Random); - // Gender is already pre-determined in the template. - while (true) - { - seed = GeneratePID(seed, pk4); - if (pk4.Nature != n) - continue; - return seed; - } - } - - private static void SetHatchedEggDetails(PK4 pk4) + if (IsManaphyEgg) + SetDefaultManaphyEggDetails(pk4, tr); + + SetPINGA(pk4, criteria); + SetMetData(pk4, tr); + + var pi = pk4.PersonalInfo; + pk4.CurrentFriendship = pk4.IsEgg ? pi.HatchCycles : pi.BaseFriendship; + + pk4.RefreshChecksum(); + return pk4; + } + + private void SetMetData(PK4 pk4, ITrainerInfo trainer) + { + if (!EggEncounter) { + pk4.Met_Location = pk4.Egg_Location + 3000; + pk4.Egg_Location = 0; + pk4.MetDate = DateTime.Now; pk4.IsEgg = false; - // Met Location & Date is modified when transferred to pk5; don't worry about it. - pk4.EggMetDate = DateTime.Now; } - - private void SetUnhatchedEggDetails(PK4 pk4) + else { - pk4.IsEgg = true; - pk4.IsNicknamed = false; - pk4.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk4.Language, Generation); - pk4.EggMetDate = DateTime.Now; + pk4.Egg_Location += 3000; + if (trainer.Generation == 4) + SetUnhatchedEggDetails(pk4); + else + SetHatchedEggDetails(pk4); } + } - private static uint GeneratePID(uint seed, PK4 pk4) + private static void SetDefaultManaphyEggDetails(PK4 pk4, ITrainerInfo trainer) + { + // Since none of this data is populated, fill in default info. + pk4.Species = (int)Core.Species.Manaphy; + pk4.Gender = 2; + // Level 1 Moves + pk4.Move1 = 294; pk4.Move1_PP = 20; + pk4.Move2 = 145; pk4.Move2_PP = 30; + pk4.Move3 = 346; pk4.Move3_PP = 15; + pk4.Ability = (int)Core.Ability.Hydration; + pk4.FatefulEncounter = true; + pk4.Ball = (int)Core.Ball.Poke; + pk4.Version = GameVersion.Gen4.Contains(trainer.Game) ? trainer.Game : (int)GameVersion.D; + var lang = trainer.Language < (int)LanguageID.Korean ? trainer.Language : (int)LanguageID.English; + pk4.Language = lang; + pk4.Egg_Location = 1; // Ranger (will be +3000 later) + pk4.Nickname = SpeciesName.GetSpeciesNameGeneration((int)Core.Species.Manaphy, lang, 4); + } + + private void SetPINGA(PK4 pk4, EncounterCriteria criteria) + { + // Ability is forced already, can't force anything + + // Generate PID + var seed = SetPID(pk4, criteria); + + if (!IsManaphyEgg) + seed = Util.Rand32(); // reseed, do not have method 1 correlation + + // Generate IVs + if (pk4.IV32 == 0) // Ignore Nickname/Egg flag bits; none are set for varied-IV gifts. { - do - { - uint pid1 = (seed = RNG.LCRNG.Next(seed)) >> 16; // low - uint pid2 = (seed = RNG.LCRNG.Next(seed)) & 0xFFFF0000; // hi - pk4.PID = pid2 | pid1; - // sanity check gender for non-genderless PID cases - } while (!pk4.IsGenderValid()); + uint iv1 = ((seed = RNG.LCRNG.Next(seed)) >> 16) & 0x7FFF; + uint iv2 = ((RNG.LCRNG.Next(seed)) >> 16) & 0x7FFF; + pk4.IV32 = iv1 | (iv2 << 15); + } + } - while (pk4.IsShiny) // Call the ARNG to change the PID - pk4.PID = RNG.ARNG.Next(pk4.PID); + private uint SetPID(PK4 pk4, EncounterCriteria criteria) + { + uint seed = Util.Rand32(); + if (pk4.PID != 1 && !IsManaphyEgg) + return seed; // PID is already set. + + // The games don't decide the Nature/Gender up-front, but we can try to honor requests. + // Pre-determine the result values, and generate something. + var n = (int)criteria.GetNature(Nature.Random); + // Gender is already pre-determined in the template. + while (true) + { + seed = GeneratePID(seed, pk4); + if (pk4.Nature != n) + continue; return seed; } - - public static bool IsRangerManaphy(PKM pkm) - { - var egg = pkm.Egg_Location; - if (!pkm.IsEgg) // Link Trade Egg or Ranger - return egg is Locations.LinkTrade4 or Locations.Ranger4; - if (egg != Locations.Ranger4) - return false; - - if (pkm.Language == (int)LanguageID.Korean) // never korean - return false; - - var met = pkm.Met_Location; - return met is Locations.LinkTrade4 or 0; - } - - // Nothing is stored as a PGT besides Ranger Manaphy. Nothing should trigger these. - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) => false; - protected override bool IsMatchDeferred(PKM pkm) => false; - protected override bool IsMatchPartial(PKM pkm) => false; - - public bool RibbonEarth { get => PK.RibbonEarth; set => PK.RibbonEarth = value; } - public bool RibbonNational { get => PK.RibbonNational; set => PK.RibbonNational = value; } - public bool RibbonCountry { get => PK.RibbonCountry; set => PK.RibbonCountry = value; } - public bool RibbonChampionBattle { get => PK.RibbonChampionBattle; set => PK.RibbonChampionBattle = value; } - public bool RibbonChampionRegional { get => PK.RibbonChampionRegional; set => PK.RibbonChampionRegional = value; } - public bool RibbonChampionNational { get => PK.RibbonChampionNational; set => PK.RibbonChampionNational = value; } - public bool RibbonClassic { get => PK.RibbonClassic; set => PK.RibbonClassic = value; } - public bool RibbonWishing { get => PK.RibbonWishing; set => PK.RibbonWishing = value; } - public bool RibbonPremier { get => PK.RibbonPremier; set => PK.RibbonPremier = value; } - public bool RibbonEvent { get => PK.RibbonEvent; set => PK.RibbonEvent = value; } - public bool RibbonBirthday { get => PK.RibbonBirthday; set => PK.RibbonBirthday = value; } - public bool RibbonSpecial { get => PK.RibbonSpecial; set => PK.RibbonSpecial = value; } - public bool RibbonWorld { get => PK.RibbonWorld; set => PK.RibbonWorld = value; } - public bool RibbonChampionWorld { get => PK.RibbonChampionWorld; set => PK.RibbonChampionWorld = value; } - public bool RibbonSouvenir { get => PK.RibbonSouvenir; set => PK.RibbonSouvenir = value; } } + + private static void SetHatchedEggDetails(PK4 pk4) + { + pk4.IsEgg = false; + // Met Location & Date is modified when transferred to pk5; don't worry about it. + pk4.EggMetDate = DateTime.Now; + } + + private void SetUnhatchedEggDetails(PK4 pk4) + { + pk4.IsEgg = true; + pk4.IsNicknamed = false; + pk4.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk4.Language, Generation); + pk4.EggMetDate = DateTime.Now; + } + + private static uint GeneratePID(uint seed, PK4 pk4) + { + do + { + uint pid1 = (seed = RNG.LCRNG.Next(seed)) >> 16; // low + uint pid2 = (seed = RNG.LCRNG.Next(seed)) & 0xFFFF0000; // hi + pk4.PID = pid2 | pid1; + // sanity check gender for non-genderless PID cases + } while (!pk4.IsGenderValid()); + + while (pk4.IsShiny) // Call the ARNG to change the PID + pk4.PID = RNG.ARNG.Next(pk4.PID); + return seed; + } + + public static bool IsRangerManaphy(PKM pk) + { + var egg = pk.Egg_Location; + if (!pk.IsEgg) // Link Trade Egg or Ranger + return egg is Locations.LinkTrade4 or Locations.Ranger4; + if (egg != Locations.Ranger4) + return false; + + if (pk.Language == (int)LanguageID.Korean) // never korean + return false; + + var met = pk.Met_Location; + return met is Locations.LinkTrade4 or 0; + } + + // Nothing is stored as a PGT besides Ranger Manaphy. Nothing should trigger these. + public override bool IsMatchExact(PKM pk, EvoCriteria evo) => false; + protected override bool IsMatchDeferred(PKM pk) => false; + protected override bool IsMatchPartial(PKM pk) => false; + + public bool RibbonEarth { get => PK.RibbonEarth; set => PK.RibbonEarth = value; } + public bool RibbonNational { get => PK.RibbonNational; set => PK.RibbonNational = value; } + public bool RibbonCountry { get => PK.RibbonCountry; set => PK.RibbonCountry = value; } + public bool RibbonChampionBattle { get => PK.RibbonChampionBattle; set => PK.RibbonChampionBattle = value; } + public bool RibbonChampionRegional { get => PK.RibbonChampionRegional; set => PK.RibbonChampionRegional = value; } + public bool RibbonChampionNational { get => PK.RibbonChampionNational; set => PK.RibbonChampionNational = value; } + public bool RibbonClassic { get => PK.RibbonClassic; set => PK.RibbonClassic = value; } + public bool RibbonWishing { get => PK.RibbonWishing; set => PK.RibbonWishing = value; } + public bool RibbonPremier { get => PK.RibbonPremier; set => PK.RibbonPremier = value; } + public bool RibbonEvent { get => PK.RibbonEvent; set => PK.RibbonEvent = value; } + public bool RibbonBirthday { get => PK.RibbonBirthday; set => PK.RibbonBirthday = value; } + public bool RibbonSpecial { get => PK.RibbonSpecial; set => PK.RibbonSpecial = value; } + public bool RibbonWorld { get => PK.RibbonWorld; set => PK.RibbonWorld = value; } + public bool RibbonChampionWorld { get => PK.RibbonChampionWorld; set => PK.RibbonChampionWorld = value; } + public bool RibbonSouvenir { get => PK.RibbonSouvenir; set => PK.RibbonSouvenir = value; } } diff --git a/PKHeX.Core/MysteryGifts/PL6.cs b/PKHeX.Core/MysteryGifts/PL6.cs index 4625704c0..cc23f52e3 100644 --- a/PKHeX.Core/MysteryGifts/PL6.cs +++ b/PKHeX.Core/MysteryGifts/PL6.cs @@ -2,212 +2,211 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokemon Link Data Storage +/// +/// +/// This Template object is very similar to the structure in that it stores more data than just the gift. +/// This template object is only present in Generation 6 save files. +/// +public sealed class PL6 { + public const int Size = 0xA47; + public const string Filter = "Pokémon Link Data|*.pl6|All Files (*.*)|*.*"; + + public readonly byte[] Data; + + public PL6() => Data = new byte[Size]; + public PL6(byte[] data) => Data = data; + /// - /// Pokemon Link Data Storage + /// Pokémon Link Flag /// - /// - /// This Template object is very similar to the structure in that it stores more data than just the gift. - /// This template object is only present in Generation 6 save files. - /// - public sealed class PL6 + public byte Flags { - public const int Size = 0xA47; - public const string Filter = "Pokémon Link Data|*.pl6|All Files (*.*)|*.*"; - - public readonly byte[] Data; - - public PL6() => Data = new byte[Size]; - public PL6(byte[] data) => Data = data; - - /// - /// Pokémon Link Flag - /// - public byte Flags - { - get => Data[0x00]; - set => Data[0x00] = value; - } - - public bool Enabled { get => (Flags & 0x80) != 0; set => Flags = value ? (byte)(1 << 7) : (byte)0; } - - /// - /// Name of data source - /// - public string Origin { get => StringConverter6.GetString(Data.AsSpan(0x01, 110)); set => StringConverter6.SetString(Data.AsSpan(0x01, 110), value.AsSpan(), 54, StringConverterOption.ClearZero); } - - // Pokemon transfer flags? - public uint Flags_1 { get => ReadUInt32LittleEndian(Data.AsSpan(0x099)); set => WriteUInt32LittleEndian(Data.AsSpan(0x099), value); } - public uint Flags_2 { get => ReadUInt32LittleEndian(Data.AsSpan(0x141)); set => WriteUInt32LittleEndian(Data.AsSpan(0x141), value); } - public uint Flags_3 { get => ReadUInt32LittleEndian(Data.AsSpan(0x1E9)); set => WriteUInt32LittleEndian(Data.AsSpan(0x1E9), value); } - public uint Flags_4 { get => ReadUInt32LittleEndian(Data.AsSpan(0x291)); set => WriteUInt32LittleEndian(Data.AsSpan(0x291), value); } - public uint Flags_5 { get => ReadUInt32LittleEndian(Data.AsSpan(0x339)); set => WriteUInt32LittleEndian(Data.AsSpan(0x339), value); } - public uint Flags_6 { get => ReadUInt32LittleEndian(Data.AsSpan(0x3E1)); set => WriteUInt32LittleEndian(Data.AsSpan(0x3E1), value); } - - // Pokémon - public PL6_PKM Poke_1 { get => new(Data.Slice(0x09D, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x09D); } - public PL6_PKM Poke_2 { get => new(Data.Slice(0x145, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x145); } - public PL6_PKM Poke_3 { get => new(Data.Slice(0x1ED, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x1ED); } - public PL6_PKM Poke_4 { get => new(Data.Slice(0x295, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x295); } - public PL6_PKM Poke_5 { get => new(Data.Slice(0x33D, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x33D); } - public PL6_PKM Poke_6 { get => new(Data.Slice(0x3E5, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x3E5); } - - // Item Properties - public int Item_1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x489)); set => WriteUInt16LittleEndian(Data.AsSpan(0x489), (ushort)value); } - public int Quantity_1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x48B)); set => WriteUInt16LittleEndian(Data.AsSpan(0x48B), (ushort)value); } - public int Item_2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x48D)); set => WriteUInt16LittleEndian(Data.AsSpan(0x48D), (ushort)value); } - public int Quantity_2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x48F)); set => WriteUInt16LittleEndian(Data.AsSpan(0x48F), (ushort)value); } - public int Item_3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x491)); set => WriteUInt16LittleEndian(Data.AsSpan(0x491), (ushort)value); } - public int Quantity_3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x493)); set => WriteUInt16LittleEndian(Data.AsSpan(0x493), (ushort)value); } - public int Item_4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x495)); set => WriteUInt16LittleEndian(Data.AsSpan(0x495), (ushort)value); } - public int Quantity_4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x497)); set => WriteUInt16LittleEndian(Data.AsSpan(0x497), (ushort)value); } - public int Item_5 { get => ReadUInt16LittleEndian(Data.AsSpan(0x499)); set => WriteUInt16LittleEndian(Data.AsSpan(0x499), (ushort)value); } - public int Quantity_5 { get => ReadUInt16LittleEndian(Data.AsSpan(0x49B)); set => WriteUInt16LittleEndian(Data.AsSpan(0x49B), (ushort)value); } - public int Item_6 { get => ReadUInt16LittleEndian(Data.AsSpan(0x49D)); set => WriteUInt16LittleEndian(Data.AsSpan(0x49D), (ushort)value); } - public int Quantity_6 { get => ReadUInt16LittleEndian(Data.AsSpan(0x49F)); set => WriteUInt16LittleEndian(Data.AsSpan(0x49F), (ushort)value); } - - public int BattlePoints { get => ReadUInt16LittleEndian(Data.AsSpan(0x4A1)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4A1), (ushort)value); } - public int Pokemiles { get => ReadUInt16LittleEndian(Data.AsSpan(0x4A3)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4A3), (ushort)value); } + get => Data[0x00]; + set => Data[0x00] = value; } + public bool Enabled { get => (Flags & 0x80) != 0; set => Flags = value ? (byte)(1 << 7) : (byte)0; } + /// - /// Pokemon Link Gift Template + /// Name of data source /// - /// - /// This Template object is very similar to the structure and similar objects, in that the structure offsets are ordered the same. - /// This template object is only present in Generation 6 save files. - /// - public sealed class PL6_PKM : IRibbonSetEvent3, IRibbonSetEvent4, IEncounterInfo + public string Origin { get => StringConverter6.GetString(Data.AsSpan(0x01, 110)); set => StringConverter6.SetString(Data.AsSpan(0x01, 110), value.AsSpan(), 54, StringConverterOption.ClearZero); } + + // Pokemon transfer flags? + public uint Flags_1 { get => ReadUInt32LittleEndian(Data.AsSpan(0x099)); set => WriteUInt32LittleEndian(Data.AsSpan(0x099), value); } + public uint Flags_2 { get => ReadUInt32LittleEndian(Data.AsSpan(0x141)); set => WriteUInt32LittleEndian(Data.AsSpan(0x141), value); } + public uint Flags_3 { get => ReadUInt32LittleEndian(Data.AsSpan(0x1E9)); set => WriteUInt32LittleEndian(Data.AsSpan(0x1E9), value); } + public uint Flags_4 { get => ReadUInt32LittleEndian(Data.AsSpan(0x291)); set => WriteUInt32LittleEndian(Data.AsSpan(0x291), value); } + public uint Flags_5 { get => ReadUInt32LittleEndian(Data.AsSpan(0x339)); set => WriteUInt32LittleEndian(Data.AsSpan(0x339), value); } + public uint Flags_6 { get => ReadUInt32LittleEndian(Data.AsSpan(0x3E1)); set => WriteUInt32LittleEndian(Data.AsSpan(0x3E1), value); } + + // Pokémon + public PL6_PKM Poke_1 { get => new(Data.Slice(0x09D, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x09D); } + public PL6_PKM Poke_2 { get => new(Data.Slice(0x145, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x145); } + public PL6_PKM Poke_3 { get => new(Data.Slice(0x1ED, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x1ED); } + public PL6_PKM Poke_4 { get => new(Data.Slice(0x295, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x295); } + public PL6_PKM Poke_5 { get => new(Data.Slice(0x33D, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x33D); } + public PL6_PKM Poke_6 { get => new(Data.Slice(0x3E5, PL6_PKM.Size)); set => value.Data.CopyTo(Data, 0x3E5); } + + // Item Properties + public int Item_1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x489)); set => WriteUInt16LittleEndian(Data.AsSpan(0x489), (ushort)value); } + public int Quantity_1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x48B)); set => WriteUInt16LittleEndian(Data.AsSpan(0x48B), (ushort)value); } + public int Item_2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x48D)); set => WriteUInt16LittleEndian(Data.AsSpan(0x48D), (ushort)value); } + public int Quantity_2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x48F)); set => WriteUInt16LittleEndian(Data.AsSpan(0x48F), (ushort)value); } + public int Item_3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x491)); set => WriteUInt16LittleEndian(Data.AsSpan(0x491), (ushort)value); } + public int Quantity_3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x493)); set => WriteUInt16LittleEndian(Data.AsSpan(0x493), (ushort)value); } + public int Item_4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x495)); set => WriteUInt16LittleEndian(Data.AsSpan(0x495), (ushort)value); } + public int Quantity_4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x497)); set => WriteUInt16LittleEndian(Data.AsSpan(0x497), (ushort)value); } + public int Item_5 { get => ReadUInt16LittleEndian(Data.AsSpan(0x499)); set => WriteUInt16LittleEndian(Data.AsSpan(0x499), (ushort)value); } + public int Quantity_5 { get => ReadUInt16LittleEndian(Data.AsSpan(0x49B)); set => WriteUInt16LittleEndian(Data.AsSpan(0x49B), (ushort)value); } + public int Item_6 { get => ReadUInt16LittleEndian(Data.AsSpan(0x49D)); set => WriteUInt16LittleEndian(Data.AsSpan(0x49D), (ushort)value); } + public int Quantity_6 { get => ReadUInt16LittleEndian(Data.AsSpan(0x49F)); set => WriteUInt16LittleEndian(Data.AsSpan(0x49F), (ushort)value); } + + public int BattlePoints { get => ReadUInt16LittleEndian(Data.AsSpan(0x4A1)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4A1), (ushort)value); } + public int Pokemiles { get => ReadUInt16LittleEndian(Data.AsSpan(0x4A3)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4A3), (ushort)value); } +} + +/// +/// Pokemon Link Gift Template +/// +/// +/// This Template object is very similar to the structure and similar objects, in that the structure offsets are ordered the same. +/// This template object is only present in Generation 6 save files. +/// +public sealed class PL6_PKM : IRibbonSetEvent3, IRibbonSetEvent4, IEncounterInfo +{ + internal const int Size = 0xA0; + + public readonly byte[] Data; + + public PL6_PKM() : this(new byte[Size]) { } + public PL6_PKM(byte[] data) => Data = data; + + public int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } + public int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(0x02), (ushort)value); } + public int OriginGame { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(0x08)); set => WriteUInt32LittleEndian(Data.AsSpan(0x08), value); } + public int Ball { get => Data[0xE]; set => Data[0xE] = (byte)value; } + public int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(0x10), (ushort)value); } + public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x12)); set => WriteUInt16LittleEndian(Data.AsSpan(0x12), (ushort)value); } + public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14), (ushort)value); } + public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); } + public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); } + public int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), (ushort)value); } + public int Form { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public int Language { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + + public string Nickname { - internal const int Size = 0xA0; + get => StringConverter6.GetString(Data.AsSpan(0x1E, 0x1A)); + set => StringConverter6.SetString(Data.AsSpan(0x1E, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + } - public readonly byte[] Data; + public int Nature { get => Data[0x38]; set => Data[0x38] = (byte)value; } + public int Gender { get => Data[0x39]; set => Data[0x39] = (byte)value; } + public int AbilityType { get => Data[0x3A]; set => Data[0x3A] = (byte)value; } + public int PIDType { get => Data[0x3B]; set => Data[0x3B] = (byte)value; } + public int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3C), (ushort)value); } + public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x3E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3E), (ushort)value); } + public byte MetLevel { get => Data[0x40]; set => Data[0x40] = value; } - public PL6_PKM() : this(new byte[Size]) { } - public PL6_PKM(byte[] data) => Data = data; + public int CNT_Cool { get => Data[0x41]; set => Data[0x41] = (byte)value; } + public int CNT_Beauty { get => Data[0x42]; set => Data[0x42] = (byte)value; } + public int CNT_Cute { get => Data[0x43]; set => Data[0x43] = (byte)value; } + public int CNT_Smart { get => Data[0x44]; set => Data[0x44] = (byte)value; } + public int CNT_Tough { get => Data[0x45]; set => Data[0x45] = (byte)value; } + public int CNT_Sheen { get => Data[0x46]; set => Data[0x46] = (byte)value; } - public int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } - public int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(0x02), (ushort)value); } - public int OriginGame { get => Data[0x04]; set => Data[0x04] = (byte)value; } - public uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(0x08)); set => WriteUInt32LittleEndian(Data.AsSpan(0x08), value); } - public int Ball { get => Data[0xE]; set => Data[0xE] = (byte)value; } - public int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(0x10), (ushort)value); } - public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x12)); set => WriteUInt16LittleEndian(Data.AsSpan(0x12), (ushort)value); } - public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14), (ushort)value); } - public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); } - public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); } - public int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), (ushort)value); } - public int Form { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } - public int Language { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + public int IV_HP { get => Data[0x47]; set => Data[0x47] = (byte)value; } + public int IV_ATK { get => Data[0x48]; set => Data[0x48] = (byte)value; } + public int IV_DEF { get => Data[0x49]; set => Data[0x49] = (byte)value; } + public int IV_SPE { get => Data[0x4A]; set => Data[0x4A] = (byte)value; } + public int IV_SPA { get => Data[0x4B]; set => Data[0x4B] = (byte)value; } + public int IV_SPD { get => Data[0x4C]; set => Data[0x4C] = (byte)value; } - public string Nickname + public int OTGender { get => Data[0x4D]; set => Data[0x4D] = (byte)value; } + + public string OT + { + get => StringConverter6.GetString(Data.AsSpan(0x4E, 0x1A)); + set => StringConverter6.SetString(Data.AsSpan(0x4E, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + } + + public int Level { get => Data[0x68]; set => Data[0x68] = (byte)value; } + public bool IsEgg { get => Data[0x69] == 1; set => Data[0x69] = value ? (byte)1 : (byte)0; } + public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x6C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x6C), value); } + public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x70)); set => WriteUInt16LittleEndian(Data.AsSpan(0x70), (ushort)value); } + public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x72)); set => WriteUInt16LittleEndian(Data.AsSpan(0x72), (ushort)value); } + public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x74)); set => WriteUInt16LittleEndian(Data.AsSpan(0x74), (ushort)value); } + public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x76)); set => WriteUInt16LittleEndian(Data.AsSpan(0x76), (ushort)value); } + public int OT_Intensity { get => Data[0x78]; set => Data[0x78] = (byte)value; } + public int OT_Memory { get => Data[0x79]; set => Data[0x79] = (byte)value; } + public int OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0x7A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7A), (ushort)value); } + public int OT_Feeling { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } + + private byte RIB0 { get => Data[0x0C]; set => Data[0x0C] = value; } + private byte RIB1 { get => Data[0x0D]; set => Data[0x0D] = value; } + + public bool RibbonChampionBattle { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonChampionRegional { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonChampionNational { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonCountry { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonNational { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonEarth { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonEvent { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonChampionWorld { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonBirthday { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonSpecial { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonSouvenir { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonWishing { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonClassic { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonPremier { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RIB1_7 { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + + public byte LevelMin => MetLevel; + public byte LevelMax => MetLevel; + + public IReadOnlyList Moves + { + get => new[] { Move1, Move2, Move3, Move4 }; + set { - get => StringConverter6.GetString(Data.AsSpan(0x1E, 0x1A)); - set => StringConverter6.SetString(Data.AsSpan(0x1E, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + if (value.Count > 0) Move1 = value[0]; + if (value.Count > 1) Move2 = value[1]; + if (value.Count > 2) Move3 = value[2]; + if (value.Count > 3) Move4 = value[3]; } + } - public int Nature { get => Data[0x38]; set => Data[0x38] = (byte)value; } - public int Gender { get => Data[0x39]; set => Data[0x39] = (byte)value; } - public int AbilityType { get => Data[0x3A]; set => Data[0x3A] = (byte)value; } - public int PIDType { get => Data[0x3B]; set => Data[0x3B] = (byte)value; } - public int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3C), (ushort)value); } - public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x3E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3E), (ushort)value); } - public byte MetLevel { get => Data[0x40]; set => Data[0x40] = value; } - - public int CNT_Cool { get => Data[0x41]; set => Data[0x41] = (byte)value; } - public int CNT_Beauty { get => Data[0x42]; set => Data[0x42] = (byte)value; } - public int CNT_Cute { get => Data[0x43]; set => Data[0x43] = (byte)value; } - public int CNT_Smart { get => Data[0x44]; set => Data[0x44] = (byte)value; } - public int CNT_Tough { get => Data[0x45]; set => Data[0x45] = (byte)value; } - public int CNT_Sheen { get => Data[0x46]; set => Data[0x46] = (byte)value; } - - public int IV_HP { get => Data[0x47]; set => Data[0x47] = (byte)value; } - public int IV_ATK { get => Data[0x48]; set => Data[0x48] = (byte)value; } - public int IV_DEF { get => Data[0x49]; set => Data[0x49] = (byte)value; } - public int IV_SPE { get => Data[0x4A]; set => Data[0x4A] = (byte)value; } - public int IV_SPA { get => Data[0x4B]; set => Data[0x4B] = (byte)value; } - public int IV_SPD { get => Data[0x4C]; set => Data[0x4C] = (byte)value; } - - public int OTGender { get => Data[0x4D]; set => Data[0x4D] = (byte)value; } - - public string OT + public IReadOnlyList RelearnMoves + { + get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; + set { - get => StringConverter6.GetString(Data.AsSpan(0x4E, 0x1A)); - set => StringConverter6.SetString(Data.AsSpan(0x4E, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + if (value.Count > 0) RelearnMove1 = value[0]; + if (value.Count > 1) RelearnMove2 = value[1]; + if (value.Count > 2) RelearnMove3 = value[2]; + if (value.Count > 3) RelearnMove4 = value[3]; } + } - public int Level { get => Data[0x68]; set => Data[0x68] = (byte)value; } - public bool IsEgg { get => Data[0x69] == 1; set => Data[0x69] = value ? (byte)1 : (byte)0; } - public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x6C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x6C), value); } - public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x70)); set => WriteUInt16LittleEndian(Data.AsSpan(0x70), (ushort)value); } - public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x72)); set => WriteUInt16LittleEndian(Data.AsSpan(0x72), (ushort)value); } - public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x74)); set => WriteUInt16LittleEndian(Data.AsSpan(0x74), (ushort)value); } - public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x76)); set => WriteUInt16LittleEndian(Data.AsSpan(0x76), (ushort)value); } - public int OT_Intensity { get => Data[0x78]; set => Data[0x78] = (byte)value; } - public int OT_Memory { get => Data[0x79]; set => Data[0x79] = (byte)value; } - public int OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0x7A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7A), (ushort)value); } - public int OT_Feeling { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } + public int Generation => 6; + public bool IsShiny => false; + public bool EggEncounter => false; + public GameVersion Version => GameVersion.Gen6; - private byte RIB0 { get => Data[0x0C]; set => Data[0x0C] = value; } - private byte RIB1 { get => Data[0x0D]; set => Data[0x0D] = value; } + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); - public bool RibbonChampionBattle { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonChampionRegional { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonChampionNational { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonCountry { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonNational { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonEarth { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonEvent { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonChampionWorld { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonBirthday { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonSpecial { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonSouvenir { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonWishing { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonClassic { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonPremier { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RIB1_7 { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - - public byte LevelMin => MetLevel; - public byte LevelMax => MetLevel; - - public IReadOnlyList Moves - { - get => new[] { Move1, Move2, Move3, Move4 }; - set - { - if (value.Count > 0) Move1 = value[0]; - if (value.Count > 1) Move2 = value[1]; - if (value.Count > 2) Move3 = value[2]; - if (value.Count > 3) Move4 = value[3]; - } - } - - public IReadOnlyList RelearnMoves - { - get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; - set - { - if (value.Count > 0) RelearnMove1 = value[0]; - if (value.Count > 1) RelearnMove2 = value[1]; - if (value.Count > 2) RelearnMove3 = value[2]; - if (value.Count > 3) RelearnMove4 = value[3]; - } - } - - public int Generation => 6; - public bool IsShiny => false; - public bool EggEncounter => false; - public GameVersion Version => GameVersion.Gen6; - - public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted); - - public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - var wc6 = new WC6(); - Data.CopyTo(wc6.Data, 0x68); - return wc6.ConvertToPKM(sav, criteria); - } + public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + var wc6 = new WC6(); + Data.CopyTo(wc6.Data, 0x68); + return wc6.ConvertToPKM(tr, criteria); } } diff --git a/PKHeX.Core/MysteryGifts/WA8.cs b/PKHeX.Core/MysteryGifts/WA8.cs index 123a7dcc8..fd31a3b76 100644 --- a/PKHeX.Core/MysteryGifts/WA8.cs +++ b/PKHeX.Core/MysteryGifts/WA8.cs @@ -1,839 +1,838 @@ -using System; +using System; using System.Collections.Generic; using static PKHeX.Core.RibbonIndex; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 Mystery Gift Template File, same as with fields at the end. +/// +public sealed class WA8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDynamaxLevel, IRibbonIndex, IMemoryOT, IEncounterServerDate, + ILangNicknamedTemplate, IGanbaru, IAlpha, + IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8 { - /// - /// Generation 8 Mystery Gift Template File, same as with fields at the end. - /// - public sealed class WA8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDynamaxLevel, IRibbonIndex, IMemoryOT, IEncounterServerDate, - ILangNicknamedTemplate, IGanbaru, IAlpha, - IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8 + public const int Size = 0x2C8; + + public override int Generation => 8; + + public enum GiftType : byte { - public const int Size = 0x2C8; - - public override int Generation => 8; - - public enum GiftType : byte - { - None = 0, - Pokemon = 1, - Item = 2, - } - - public WA8() : this(new byte[Size]) { } - public WA8(byte[] data) : base(data) { } - - public bool CanBeReceivedByVersion(int v, PKM pk) => v is (int) GameVersion.PLA || (pk is PK8 && v is (int)GameVersion.SW); - public bool IsDateRestricted => true; - public bool IsEquivalentFixedECPID => EncryptionConstant != 0 && PIDType == ShinyType8.FixedValue && PID == EncryptionConstant; - - // General Card Properties - public override int CardID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x8)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x8), (ushort)value); - } - - public byte CardFlags { get => Data[0x10]; set => Data[0x10] = value; } - public GiftType CardType { get => (GiftType)Data[0x11]; set => Data[0x11] = (byte)value; } - public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); } - public override bool GiftUsed { get => false; set { } } - - public int CardTitleIndex - { - get => Data[0x13]; - set => Data[0x13] = (byte) value; - } - - public override string CardTitle - { - get => "Mystery Gift"; // TODO: Use text string from CardTitleIndex - set => throw new Exception(); - } - - // Item Properties - public override bool IsItem { get => CardType == GiftType.Item; set { if (value) CardType = GiftType.Item; } } - - public override int ItemID - { - get => GetItem(0); - set => SetItem(0, (ushort)value); - } - - public override int Quantity - { - get => GetQuantity(0); - set => SetQuantity(0, (ushort)value); - } - - public int GetItem(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x18 + (0x4 * index))); - public void SetItem(int index, ushort item) => WriteUInt16LittleEndian(Data.AsSpan(0x18 + (4 * index)), item); - public int GetQuantity(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x1A + (0x4 * index))); - public void SetQuantity(int index, ushort quantity) => WriteUInt16LittleEndian(Data.AsSpan(0x1A + (4 * index)), quantity); - - // Pokémon Properties - public override bool IsEntity { get => CardType == GiftType.Pokemon; set { if (value) CardType = GiftType.Pokemon; } } - - public override bool IsShiny => Shiny.IsShiny(); - - public override Shiny Shiny => PIDType switch - { - ShinyType8.FixedValue => GetShinyXor() switch - { - 0 => Shiny.AlwaysSquare, - <= 15 => Shiny.AlwaysStar, - _ => Shiny.Never, - }, - ShinyType8.Random => Shiny.Random, - ShinyType8.Never => Shiny.Never, - ShinyType8.AlwaysStar => Shiny.AlwaysStar, - ShinyType8.AlwaysSquare => Shiny.AlwaysSquare, - _ => throw new ArgumentOutOfRangeException(), - }; - - private int GetShinyXor() - { - // Player owned anti-shiny fixed PID - if (TID == 0 && SID == 0) - return int.MaxValue; - - var pid = PID; - var psv = (int)(pid >> 16 ^ (pid & 0xFFFF)); - var tsv = (TID ^ SID); - return psv ^ tsv; - } - - public override int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); - } - - public override int SID { - get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), (ushort)value); - } - - public int OriginGame - { - get => ReadInt32LittleEndian(Data.AsSpan(0x1C)); - set => WriteInt32LittleEndian(Data.AsSpan(0x1C), value); - } - - public uint EncryptionConstant - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x20)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x20), value); - } - - public uint PID - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x24)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x24), value); - } - - // Nicknames, OT Names 0x30 - 0x228 - public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x220)); set => WriteUInt16LittleEndian(Data.AsSpan(0x220), (ushort)value); } - public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x222)); set => WriteUInt16LittleEndian(Data.AsSpan(0x222), (ushort)value); } - - public override int Ball - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x224)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x224), (ushort)value); - } - - public override int HeldItem - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x226)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x226), (ushort)value); - } - - public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x228)); set => WriteUInt16LittleEndian(Data.AsSpan(0x228), (ushort)value); } - public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x22A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22A), (ushort)value); } - public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x22C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22C), (ushort)value); } - public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x22E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22E), (ushort)value); } - public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x230)); set => WriteUInt16LittleEndian(Data.AsSpan(0x230), (ushort)value); } - public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x232)); set => WriteUInt16LittleEndian(Data.AsSpan(0x232), (ushort)value); } - public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x234)); set => WriteUInt16LittleEndian(Data.AsSpan(0x234), (ushort)value); } - public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x236)); set => WriteUInt16LittleEndian(Data.AsSpan(0x236), (ushort)value); } - - public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x238)); set => WriteUInt16LittleEndian(Data.AsSpan(0x238), (ushort)value); } - public override int Form { get => Data[0x23A]; set => Data[0x23A] = (byte)value; } - public override int Gender { get => Data[0x23B]; set => Data[0x23B] = (byte)value; } - public override byte Level { get => Data[0x23C]; set => Data[0x23C] = value; } - public override bool IsEgg { get => Data[0x23D] == 1; set => Data[0x23D] = value ? (byte)1 : (byte)0; } - public int Nature { get => (sbyte)Data[0x23E]; set => Data[0x23E] = (byte)value; } - public override int AbilityType { get => Data[0x23F]; set => Data[0x23F] = (byte)value; } - - private byte PIDTypeValue => Data[0x240]; - - public ShinyType8 PIDType => PIDTypeValue switch - { - 0 => ShinyType8.Never, - 1 => ShinyType8.Random, - 2 => ShinyType8.AlwaysStar, - 3 => ShinyType8.AlwaysSquare, - 4 => ShinyType8.FixedValue, - _ => throw new ArgumentOutOfRangeException(nameof(PIDType)), - }; - - public int MetLevel { get => Data[0x241]; set => Data[0x241] = (byte)value; } - public byte DynamaxLevel { get => Data[0x242]; set => Data[0x242] = value; } - public bool CanGigantamax { get => Data[0x243] != 0; set => Data[0x243] = value ? (byte)1 : (byte)0; } - - // Ribbons 0x24C-0x26C - private const int RibbonBytesOffset = 0x244; - private const int RibbonBytesCount = 0x20; - private const int RibbonByteNone = 0xFF; // signed -1 - - public bool HasMark() - { - for (int i = 0; i < RibbonBytesCount; i++) - { - var value = Data[RibbonBytesOffset + i]; - if (value == RibbonByteNone) - return false; - if ((RibbonIndex)value is >= MarkLunchtime and <= MarkSlump) - return true; - } - return false; - } - - public byte GetRibbonAtIndex(int byteIndex) - { - if ((uint)byteIndex >= RibbonBytesCount) - throw new IndexOutOfRangeException(); - return Data[RibbonBytesOffset + byteIndex]; - } - - public void SetRibbonAtIndex(int byteIndex, byte ribbonIndex) - { - if ((uint)byteIndex >= RibbonBytesCount) - throw new IndexOutOfRangeException(); - Data[RibbonBytesOffset + byteIndex] = ribbonIndex; - } - - public int IV_HP { get => Data[0x264]; set => Data[0x264] = (byte)value; } - public int IV_ATK { get => Data[0x265]; set => Data[0x265] = (byte)value; } - public int IV_DEF { get => Data[0x266]; set => Data[0x266] = (byte)value; } - public int IV_SPE { get => Data[0x267]; set => Data[0x267] = (byte)value; } - public int IV_SPA { get => Data[0x268]; set => Data[0x268] = (byte)value; } - public int IV_SPD { get => Data[0x269]; set => Data[0x269] = (byte)value; } - - public int OTGender { get => Data[0x26A]; set => Data[0x26A] = (byte)value; } - - public int EV_HP { get => Data[0x26B]; set => Data[0x26B] = (byte)value; } - public int EV_ATK { get => Data[0x26C]; set => Data[0x26C] = (byte)value; } - public int EV_DEF { get => Data[0x26D]; set => Data[0x26D] = (byte)value; } - public int EV_SPE { get => Data[0x26E]; set => Data[0x26E] = (byte)value; } - public int EV_SPA { get => Data[0x26F]; set => Data[0x26F] = (byte)value; } - public int EV_SPD { get => Data[0x270]; set => Data[0x270] = (byte)value; } - - public byte OT_Intensity { get => Data[0x271]; set => Data[0x271] = value; } - public byte OT_Memory { get => Data[0x272]; set => Data[0x272] = value; } - public byte OT_Feeling { get => Data[0x273]; set => Data[0x273] = value; } - public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0x274)); set => WriteUInt16LittleEndian(Data.AsSpan(0x274), value); } - - // Only derivations to WC8 - public byte GV_HP { get => Data[0x27E]; set => Data[0x27E] = value; } - public byte GV_ATK { get => Data[0x27F]; set => Data[0x27F] = value; } - public byte GV_DEF { get => Data[0x280]; set => Data[0x280] = value; } - public byte GV_SPE { get => Data[0x281]; set => Data[0x281] = value; } - public byte GV_SPA { get => Data[0x282]; set => Data[0x282] = value; } - public byte GV_SPD { get => Data[0x283]; set => Data[0x283] = value; } - - // Meta Accessible Properties - public override int[] IVs - { - get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; - set - { - if (value.Length != 6) return; - IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; - IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; - } - } - - public override void GetIVs(Span value) - { - if (value.Length != 6) - return; - value[0] = IV_HP; - value[1] = IV_ATK; - value[2] = IV_DEF; - value[3] = IV_SPE; - value[4] = IV_SPA; - value[5] = IV_SPD; - } - - public int[] EVs - { - get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; - set - { - if (value.Length != 6) return; - EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; - EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; - } - } - - public bool GetIsNicknamed(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetNicknameOffset(language))) != 0; - - public bool CanBeAnyLanguage() - { - for (int i = 0; i < 9; i++) - { - var ofs = GetLanguageOffset(i); - var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); - if (lang != 0) - return false; - } - return true; - } - - public bool CanHaveLanguage(int language) - { - if (language is < (int)LanguageID.Japanese or > (int)LanguageID.ChineseT) - return false; - - if (CanBeAnyLanguage()) - return true; - - for (int i = 0; i < 9; i++) - { - var ofs = GetLanguageOffset(i); - var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); - if (lang == language) - return true; - } - return GetLanguage(language) == 0; - } - - public int GetLanguage(int redeemLanguage) => Data[GetLanguageOffset(GetLanguageIndex(redeemLanguage))]; - private static int GetLanguageOffset(int index) => 0x28 + (index * 0x1C) + 0x1A; - - public bool GetHasOT(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetOTOffset(language))) != 0; - - private static int GetLanguageIndex(int language) - { - var lang = (LanguageID) language; - if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.ChineseT) - return (int) LanguageID.English; // fallback - return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2; - } - - public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } - - public override IReadOnlyList Moves - { - get => new[] { Move1, Move2, Move3, Move4 }; - set - { - if (value.Count > 0) Move1 = value[0]; - if (value.Count > 1) Move2 = value[1]; - if (value.Count > 2) Move3 = value[2]; - if (value.Count > 3) Move4 = value[3]; - } - } - - public override IReadOnlyList Relearn - { - get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; - set - { - if (value.Count > 0) RelearnMove1 = value[0]; - if (value.Count > 1) RelearnMove2 = value[1]; - if (value.Count > 2) RelearnMove3 = value[2]; - if (value.Count > 3) RelearnMove4 = value[3]; - } - } - - public override string OT_Name { get; set; } = string.Empty; - public string Nickname => string.Empty; - public bool IsNicknamed => false; - public int Language => 2; - - public string GetNickname(int language) => StringConverter8.GetString(Data.AsSpan(GetNicknameOffset(language), 0x1A)); - public void SetNickname(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetNicknameOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - - public string GetOT(int language) => StringConverter8.GetString(Data.AsSpan(GetOTOffset(language), 0x1A)); - public void SetOT(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetOTOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - - private static int GetNicknameOffset(int language) - { - int index = GetLanguageIndex(language); - return 0x28 + (index * 0x1C); - } - - private static int GetOTOffset(int language) - { - int index = GetLanguageIndex(language); - return 0x124 + (index * 0x1C); - } - - public bool IsHOMEGift => CardID >= 9000; - - public bool CanHandleOT(int language) => !GetHasOT(language); - - public override GameVersion Version - { - get => OriginGame != 0 ? (GameVersion)OriginGame : GameVersion.PLA; - set { } - } - - public bool IsAlpha { get => false; set { } } - - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - if (!IsEntity) - throw new ArgumentException(nameof(IsEntity)); - - int currentLevel = Level > 0 ? Level : (1 + Util.Rand.Next(100)); - int metLevel = MetLevel > 0 ? MetLevel : currentLevel; - var pi = PersonalTable.LA.GetFormEntry(Species, Form); - var language = sav.Language; - var OT = GetOT(language); - bool hasOT = GetHasOT(language); - - var pk = new PA8 - { - EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(), - TID = TID, - SID = SID, - Species = Species, - Form = Form, - CurrentLevel = currentLevel, - Ball = Ball != 0 ? Ball : (int)Core.Ball.LAPoke, // Default is Pokeball - Met_Level = metLevel, - HeldItem = HeldItem, - - EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), - - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - RelearnMove1 = RelearnMove1, - RelearnMove2 = RelearnMove2, - RelearnMove3 = RelearnMove3, - RelearnMove4 = RelearnMove4, - - Version = OriginGame != 0 ? OriginGame : sav.Game, - - OT_Name = OT.Length > 0 ? OT : sav.OT, - OT_Gender = OTGender < 2 ? OTGender : sav.Gender, - HT_Name = hasOT ? sav.OT : string.Empty, - HT_Gender = hasOT ? sav.Gender : 0, - HT_Language = hasOT ? (byte)language : (byte)0, - CurrentHandler = hasOT ? 1 : 0, - OT_Friendship = pi.BaseFriendship, - - OT_Intensity = OT_Intensity, - OT_Memory = OT_Memory, - OT_TextVar = OT_TextVar, - OT_Feeling = OT_Feeling, - FatefulEncounter = true, - - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPE = EV_SPE, - EV_SPA = EV_SPA, - EV_SPD = EV_SPD, - - GV_HP = GV_HP, - GV_ATK = GV_ATK, - GV_DEF = GV_DEF, - GV_SPE = GV_SPE, - GV_SPA = GV_SPA, - GV_SPD = GV_SPD, - - //CanGigantamax = CanGigantamax, - //DynamaxLevel = DynamaxLevel, - - Met_Location = MetLocation, - Egg_Location = EggLocation, - }; - pk.SetMaximumPPCurrent(); - - if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version, pk)) - pk.Version = (int)GameVersion.PLA; - - if (OTGender >= 2) - { - pk.TID = sav.TID; - pk.SID = sav.SID; - } - - pk.MetDate = IsDateRestricted && EncounterServerDate.WA8Gifts.TryGetValue(CardID, out var dt) ? dt.Start : DateTime.Now; - - // HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC). - if (CardID is 9018 or 9019 or 9020) - pk.Met_Day = 20; - - var nickname_language = GetLanguage(language); - pk.Language = nickname_language != 0 ? nickname_language : sav.Language; - pk.IsNicknamed = GetIsNicknamed(language); - pk.Nickname = pk.IsNicknamed ? GetNickname(language) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); - - for (var i = 0; i < RibbonBytesCount; i++) - { - var ribbon = GetRibbonAtIndex(i); - if (ribbon != RibbonByteNone) - pk.SetRibbon(ribbon); - } - - SetPINGA(pk, criteria); - - if (IsEgg) - SetEggMetData(pk); - pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; - - pk.HeightScalar = PokeSizeUtil.GetRandomScalar(); - pk.WeightScalar = PokeSizeUtil.GetRandomScalar(); - pk.HeightScalarCopy = pk.HeightScalar; - pk.ResetHeight(); - pk.ResetWeight(); - - pk.ResetPartyStats(); - pk.RefreshChecksum(); - return pk; - } - - private void SetEggMetData(PKM pk) - { - pk.IsEgg = true; - pk.EggMetDate = DateTime.Now; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); - pk.IsNicknamed = true; - } - - private void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = PersonalTable.LA.GetFormEntry(Species, Form); - pk.Nature = (int)criteria.GetNature(Nature == -1 ? Core.Nature.Random : (Nature)Nature); - pk.StatNature = pk.Nature; - pk.Gender = criteria.GetGender(Gender, pi); - var av = GetAbilityIndex(criteria); - pk.RefreshAbility(av); - SetPID(pk); - SetIVs(pk); - } - - private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch - { - 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 - 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H - _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), - }; - - public override AbilityPermission Ability => AbilityType switch - { - 0 => AbilityPermission.OnlyFirst, - 1 => AbilityPermission.OnlySecond, - 2 => AbilityPermission.OnlyHidden, - 3 => AbilityPermission.Any12, - _ => AbilityPermission.Any12H, - }; - - private uint GetPID(ITrainerID tr, ShinyType8 type) => type switch - { - ShinyType8.Never => GetAntishiny(tr), // Random, Never Shiny - ShinyType8.Random => Util.Rand32(), // Random, Any - ShinyType8.AlwaysStar => (uint)(((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 1) << 16) | (PID & 0xFFFF)), // Fixed, Force Star - ShinyType8.AlwaysSquare => (uint)(((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 0) << 16) | (PID & 0xFFFF)), // Fixed, Force Square - ShinyType8.FixedValue => GetFixedPID(tr), - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; - - private uint GetFixedPID(ITrainerID tr) - { - var pid = PID; - if (pid != 0 && !(TID == 0 && SID == 0)) - return pid; - - if (!tr.IsShiny(pid, 8)) - return pid; - if (IsHOMEGift) - return GetAntishinyFixedHOME(tr); - return pid; - } - - private static uint GetAntishinyFixedHOME(ITrainerID tr) - { - var fid = ((uint)(tr.SID << 16) | (uint)tr.TID); - return fid ^ 0x10u; - } - - private static uint GetAntishiny(ITrainerID tr) - { - var pid = Util.Rand32(); - if (tr.IsShiny(pid, 8)) - return pid ^ 0x1000_0000; - return pid; - } - - private void SetPID(PKM pk) - { - pk.PID = GetPID(pk, PIDType); - } - - private void SetIVs(PKM pk) - { - Span finalIVs = stackalloc int[6]; - GetIVs(finalIVs); - var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); - var rng = Util.Rand; - if (ivflag == 0) // Random IVs - { - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] > 31) - finalIVs[i] = rng.Next(32); - } - } - else // 1/2/3 perfect IVs - { - int IVCount = ivflag - 0xFB; - do { finalIVs[rng.Next(6)] = 31; } - while (finalIVs.Count(31) < IVCount); - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] != 31) - finalIVs[i] = rng.Next(32); - } - } - pk.SetIVs(finalIVs); - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!IsEgg) - { - if (OTGender < 2) - { - if (SID != pkm.SID) return false; - if (TID != pkm.TID) return false; - if (OTGender != pkm.OT_Gender) return false; - } - - if (!CanBeAnyLanguage() && !CanHaveLanguage(pkm.Language)) - return false; - - var OT = GetOT(pkm.Language); // May not be guaranteed to work. - if (!string.IsNullOrEmpty(OT) && OT != pkm.OT_Name) return false; - if (OriginGame != 0 && OriginGame != pkm.Version) - { - if (OriginGame is (int)GameVersion.PLA && !(pkm.Version is (int)GameVersion.SW && pkm.Met_Location == Locations.HOME_SWLA)) - return false; - } - if (EncryptionConstant != 0) - { - if (EncryptionConstant != pkm.EncryptionConstant) - return false; - } - } - - if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pkm.Form, pkm.Format)) - return false; - - if (IsEgg) - { - if (EggLocation != pkm.Egg_Location) // traded - { - if (pkm.Egg_Location != Locations.LinkTrade6) - return false; - if (PIDType == ShinyType8.Random && pkm.IsShiny && pkm.ShinyXor > 1) - return false; // shiny traded egg will always have xor0/1. - } - if (!Shiny.IsValid(pkm)) - { - return false; // can't be traded away for unshiny - } - - if (pkm.IsEgg && !pkm.IsNative) - return false; - } - else - { - if (!Shiny.IsValid(pkm)) return false; - if (!IsMatchEggLocation(pkm)) return false; - if (pkm is PK8) - { - if (pkm.Met_Location != Locations.HOME_SWLA) - return false; - } - else - { - if (MetLocation != pkm.Met_Location) - return false; - } - } - - if (MetLevel != 0 && MetLevel != pkm.Met_Level) return false; - if (OTGender < 2 && OTGender != pkm.OT_Gender) return false; - if (Nature != -1 && pkm.Nature != Nature) return false; - if (Gender != 3 && Gender != pkm.Gender) return false; - - const int poke = (int)Core.Ball.LAPoke; - var expectedBall = (Ball == 0 ? poke : Ball); - if (expectedBall < poke) // Not even Cherish balls are safe! They get set to the proto-Poké ball. - expectedBall = poke; - if (pkm is PK8) - expectedBall = (int)Core.Ball.Poke; // Transferred to SWSH -> Regular Poké ball - if (expectedBall != pkm.Ball) - return false; - - if (pkm is IDynamaxLevel dl && dl.DynamaxLevel < DynamaxLevel) - return false; - - if (pkm is IGanbaru b && b.IsGanbaruValuesBelow(this)) - return false; - - // PID Types 0 and 1 do not use the fixed PID value. - // Values 2,3 are specific shiny states, and 4 is fixed value. - // 2,3,4 can change if it is a traded egg to ensure the same shiny state. - var type = PIDType; - if (type is ShinyType8.Never or ShinyType8.Random) - return true; - return pkm.PID == GetPID(pkm, type); - } - - protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species; - protected override bool IsMatchPartial(PKM pkm) => false; // no version compatibility checks yet. - - #region Lazy Ribbon Implementation - public bool RibbonEarth { get => this.GetRibbonIndex(Earth); set => this.SetRibbonIndex(Earth, value); } - public bool RibbonNational { get => this.GetRibbonIndex(National); set => this.SetRibbonIndex(National, value); } - public bool RibbonCountry { get => this.GetRibbonIndex(Country); set => this.SetRibbonIndex(Country, value); } - public bool RibbonChampionBattle { get => this.GetRibbonIndex(ChampionBattle); set => this.SetRibbonIndex(ChampionBattle, value); } - public bool RibbonChampionRegional { get => this.GetRibbonIndex(ChampionRegional); set => this.SetRibbonIndex(ChampionRegional, value); } - public bool RibbonChampionNational { get => this.GetRibbonIndex(ChampionNational); set => this.SetRibbonIndex(ChampionNational, value); } - public bool RibbonClassic { get => this.GetRibbonIndex(Classic); set => this.SetRibbonIndex(Classic, value); } - public bool RibbonWishing { get => this.GetRibbonIndex(Wishing); set => this.SetRibbonIndex(Wishing, value); } - public bool RibbonPremier { get => this.GetRibbonIndex(Premier); set => this.SetRibbonIndex(Premier, value); } - public bool RibbonEvent { get => this.GetRibbonIndex(Event); set => this.SetRibbonIndex(Event, value); } - public bool RibbonBirthday { get => this.GetRibbonIndex(Birthday); set => this.SetRibbonIndex(Birthday, value); } - public bool RibbonSpecial { get => this.GetRibbonIndex(Special); set => this.SetRibbonIndex(Special, value); } - public bool RibbonWorld { get => this.GetRibbonIndex(World); set => this.SetRibbonIndex(World, value); } - public bool RibbonChampionWorld { get => this.GetRibbonIndex(ChampionWorld); set => this.SetRibbonIndex(ChampionWorld, value); } - public bool RibbonSouvenir { get => this.GetRibbonIndex(Souvenir); set => this.SetRibbonIndex(Souvenir, value); } - public bool RibbonChampionG3 { get => this.GetRibbonIndex(ChampionG3); set => this.SetRibbonIndex(ChampionG3, value); } - public bool RibbonArtist { get => this.GetRibbonIndex(Artist); set => this.SetRibbonIndex(Artist, value); } - public bool RibbonEffort { get => this.GetRibbonIndex(Effort); set => this.SetRibbonIndex(Effort, value); } - public bool RibbonChampionSinnoh { get => this.GetRibbonIndex(ChampionSinnoh); set => this.SetRibbonIndex(ChampionSinnoh, value); } - public bool RibbonAlert { get => this.GetRibbonIndex(Alert); set => this.SetRibbonIndex(Alert, value); } - public bool RibbonShock { get => this.GetRibbonIndex(Shock); set => this.SetRibbonIndex(Shock, value); } - public bool RibbonDowncast { get => this.GetRibbonIndex(Downcast); set => this.SetRibbonIndex(Downcast, value); } - public bool RibbonCareless { get => this.GetRibbonIndex(Careless); set => this.SetRibbonIndex(Careless, value); } - public bool RibbonRelax { get => this.GetRibbonIndex(Relax); set => this.SetRibbonIndex(Relax, value); } - public bool RibbonSnooze { get => this.GetRibbonIndex(Snooze); set => this.SetRibbonIndex(Snooze, value); } - public bool RibbonSmile { get => this.GetRibbonIndex(Smile); set => this.SetRibbonIndex(Smile, value); } - public bool RibbonGorgeous { get => this.GetRibbonIndex(Gorgeous); set => this.SetRibbonIndex(Gorgeous, value); } - public bool RibbonRoyal { get => this.GetRibbonIndex(Royal); set => this.SetRibbonIndex(Royal, value); } - public bool RibbonGorgeousRoyal { get => this.GetRibbonIndex(GorgeousRoyal); set => this.SetRibbonIndex(GorgeousRoyal, value); } - public bool RibbonFootprint { get => this.GetRibbonIndex(Footprint); set => this.SetRibbonIndex(Footprint, value); } - public bool RibbonRecord { get => this.GetRibbonIndex(Record); set => this.SetRibbonIndex(Record, value); } - public bool RibbonLegend { get => this.GetRibbonIndex(Legend); set => this.SetRibbonIndex(Legend, value); } - public bool RibbonChampionKalos { get => this.GetRibbonIndex(ChampionKalos); set => this.SetRibbonIndex(ChampionKalos, value); } - public bool RibbonChampionG6Hoenn { get => this.GetRibbonIndex(ChampionG6Hoenn); set => this.SetRibbonIndex(ChampionG6Hoenn, value); } - public bool RibbonBestFriends { get => this.GetRibbonIndex(BestFriends); set => this.SetRibbonIndex(BestFriends, value); } - public bool RibbonTraining { get => this.GetRibbonIndex(Training); set => this.SetRibbonIndex(Training, value); } - public bool RibbonBattlerSkillful { get => this.GetRibbonIndex(BattlerSkillful); set => this.SetRibbonIndex(BattlerSkillful, value); } - public bool RibbonBattlerExpert { get => this.GetRibbonIndex(BattlerExpert); set => this.SetRibbonIndex(BattlerExpert, value); } - public bool RibbonContestStar { get => this.GetRibbonIndex(ContestStar); set => this.SetRibbonIndex(ContestStar, value); } - public bool RibbonMasterCoolness { get => this.GetRibbonIndex(MasterCoolness); set => this.SetRibbonIndex(MasterCoolness, value); } - public bool RibbonMasterBeauty { get => this.GetRibbonIndex(MasterBeauty); set => this.SetRibbonIndex(MasterBeauty, value); } - public bool RibbonMasterCuteness { get => this.GetRibbonIndex(MasterCuteness); set => this.SetRibbonIndex(MasterCuteness, value); } - public bool RibbonMasterCleverness { get => this.GetRibbonIndex(MasterCleverness); set => this.SetRibbonIndex(MasterCleverness, value); } - public bool RibbonMasterToughness { get => this.GetRibbonIndex(MasterToughness); set => this.SetRibbonIndex(MasterToughness, value); } - - public int RibbonCountMemoryContest { get => 0; set { } } - public int RibbonCountMemoryBattle { get => 0; set { } } - - public bool RibbonChampionAlola { get => this.GetRibbonIndex(ChampionAlola); set => this.SetRibbonIndex(ChampionAlola, value); } - public bool RibbonBattleRoyale { get => this.GetRibbonIndex(BattleRoyale); set => this.SetRibbonIndex(BattleRoyale, value); } - public bool RibbonBattleTreeGreat { get => this.GetRibbonIndex(BattleTreeGreat); set => this.SetRibbonIndex(BattleTreeGreat, value); } - public bool RibbonBattleTreeMaster { get => this.GetRibbonIndex(BattleTreeMaster); set => this.SetRibbonIndex(BattleTreeMaster, value); } - public bool RibbonChampionGalar { get => this.GetRibbonIndex(ChampionGalar); set => this.SetRibbonIndex(ChampionGalar, value); } - public bool RibbonTowerMaster { get => this.GetRibbonIndex(TowerMaster); set => this.SetRibbonIndex(TowerMaster, value); } - public bool RibbonMasterRank { get => this.GetRibbonIndex(MasterRank); set => this.SetRibbonIndex(MasterRank, value); } - public bool RibbonMarkLunchtime { get => this.GetRibbonIndex(MarkLunchtime); set => this.SetRibbonIndex(MarkLunchtime, value); } - public bool RibbonMarkSleepyTime { get => this.GetRibbonIndex(MarkSleepyTime); set => this.SetRibbonIndex(MarkSleepyTime, value); } - public bool RibbonMarkDusk { get => this.GetRibbonIndex(MarkDusk); set => this.SetRibbonIndex(MarkDusk, value); } - public bool RibbonMarkDawn { get => this.GetRibbonIndex(MarkDawn); set => this.SetRibbonIndex(MarkDawn, value); } - public bool RibbonMarkCloudy { get => this.GetRibbonIndex(MarkCloudy); set => this.SetRibbonIndex(MarkCloudy, value); } - public bool RibbonMarkRainy { get => this.GetRibbonIndex(MarkRainy); set => this.SetRibbonIndex(MarkRainy, value); } - public bool RibbonMarkStormy { get => this.GetRibbonIndex(MarkStormy); set => this.SetRibbonIndex(MarkStormy, value); } - public bool RibbonMarkSnowy { get => this.GetRibbonIndex(MarkSnowy); set => this.SetRibbonIndex(MarkSnowy, value); } - public bool RibbonMarkBlizzard { get => this.GetRibbonIndex(MarkBlizzard); set => this.SetRibbonIndex(MarkBlizzard, value); } - public bool RibbonMarkDry { get => this.GetRibbonIndex(MarkDry); set => this.SetRibbonIndex(MarkDry, value); } - public bool RibbonMarkSandstorm { get => this.GetRibbonIndex(MarkSandstorm); set => this.SetRibbonIndex(MarkSandstorm, value); } - public bool RibbonMarkMisty { get => this.GetRibbonIndex(MarkMisty); set => this.SetRibbonIndex(MarkMisty, value); } - public bool RibbonMarkDestiny { get => this.GetRibbonIndex(MarkDestiny); set => this.SetRibbonIndex(MarkDestiny, value); } - public bool RibbonMarkFishing { get => this.GetRibbonIndex(MarkFishing); set => this.SetRibbonIndex(MarkFishing, value); } - public bool RibbonMarkCurry { get => this.GetRibbonIndex(MarkCurry); set => this.SetRibbonIndex(MarkCurry, value); } - public bool RibbonMarkUncommon { get => this.GetRibbonIndex(MarkUncommon); set => this.SetRibbonIndex(MarkUncommon, value); } - public bool RibbonMarkRare { get => this.GetRibbonIndex(MarkRare); set => this.SetRibbonIndex(MarkRare, value); } - public bool RibbonMarkRowdy { get => this.GetRibbonIndex(MarkRowdy); set => this.SetRibbonIndex(MarkRowdy, value); } - public bool RibbonMarkAbsentMinded { get => this.GetRibbonIndex(MarkAbsentMinded); set => this.SetRibbonIndex(MarkAbsentMinded, value); } - public bool RibbonMarkJittery { get => this.GetRibbonIndex(MarkJittery); set => this.SetRibbonIndex(MarkJittery, value); } - public bool RibbonMarkExcited { get => this.GetRibbonIndex(MarkExcited); set => this.SetRibbonIndex(MarkExcited, value); } - public bool RibbonMarkCharismatic { get => this.GetRibbonIndex(MarkCharismatic); set => this.SetRibbonIndex(MarkCharismatic, value); } - public bool RibbonMarkCalmness { get => this.GetRibbonIndex(MarkCalmness); set => this.SetRibbonIndex(MarkCalmness, value); } - public bool RibbonMarkIntense { get => this.GetRibbonIndex(MarkIntense); set => this.SetRibbonIndex(MarkIntense, value); } - public bool RibbonMarkZonedOut { get => this.GetRibbonIndex(MarkZonedOut); set => this.SetRibbonIndex(MarkZonedOut, value); } - public bool RibbonMarkJoyful { get => this.GetRibbonIndex(MarkJoyful); set => this.SetRibbonIndex(MarkJoyful, value); } - public bool RibbonMarkAngry { get => this.GetRibbonIndex(MarkAngry); set => this.SetRibbonIndex(MarkAngry, value); } - public bool RibbonMarkSmiley { get => this.GetRibbonIndex(MarkSmiley); set => this.SetRibbonIndex(MarkSmiley, value); } - public bool RibbonMarkTeary { get => this.GetRibbonIndex(MarkTeary); set => this.SetRibbonIndex(MarkTeary, value); } - public bool RibbonMarkUpbeat { get => this.GetRibbonIndex(MarkUpbeat); set => this.SetRibbonIndex(MarkUpbeat, value); } - public bool RibbonMarkPeeved { get => this.GetRibbonIndex(MarkPeeved); set => this.SetRibbonIndex(MarkPeeved, value); } - public bool RibbonMarkIntellectual { get => this.GetRibbonIndex(MarkIntellectual); set => this.SetRibbonIndex(MarkIntellectual, value); } - public bool RibbonMarkFerocious { get => this.GetRibbonIndex(MarkFerocious); set => this.SetRibbonIndex(MarkFerocious, value); } - public bool RibbonMarkCrafty { get => this.GetRibbonIndex(MarkCrafty); set => this.SetRibbonIndex(MarkCrafty, value); } - public bool RibbonMarkScowling { get => this.GetRibbonIndex(MarkScowling); set => this.SetRibbonIndex(MarkScowling, value); } - public bool RibbonMarkKindly { get => this.GetRibbonIndex(MarkKindly); set => this.SetRibbonIndex(MarkKindly, value); } - public bool RibbonMarkFlustered { get => this.GetRibbonIndex(MarkFlustered); set => this.SetRibbonIndex(MarkFlustered, value); } - public bool RibbonMarkPumpedUp { get => this.GetRibbonIndex(MarkPumpedUp); set => this.SetRibbonIndex(MarkPumpedUp, value); } - public bool RibbonMarkZeroEnergy { get => this.GetRibbonIndex(MarkZeroEnergy); set => this.SetRibbonIndex(MarkZeroEnergy, value); } - public bool RibbonMarkPrideful { get => this.GetRibbonIndex(MarkPrideful); set => this.SetRibbonIndex(MarkPrideful, value); } - public bool RibbonMarkUnsure { get => this.GetRibbonIndex(MarkUnsure); set => this.SetRibbonIndex(MarkUnsure, value); } - public bool RibbonMarkHumble { get => this.GetRibbonIndex(MarkHumble); set => this.SetRibbonIndex(MarkHumble, value); } - public bool RibbonMarkThorny { get => this.GetRibbonIndex(MarkThorny); set => this.SetRibbonIndex(MarkThorny, value); } - public bool RibbonMarkVigor { get => this.GetRibbonIndex(MarkVigor); set => this.SetRibbonIndex(MarkVigor, value); } - public bool RibbonMarkSlump { get => this.GetRibbonIndex(MarkSlump); set => this.SetRibbonIndex(MarkSlump, value); } - public bool RibbonTwinklingStar { get => this.GetRibbonIndex(TwinklingStar); set => this.SetRibbonIndex(TwinklingStar, value); } - public bool RibbonPioneer { get => this.GetRibbonIndex(Pioneer); set => this.SetRibbonIndex(Pioneer, value); } - - public int GetRibbonByte(int index) => Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z == index); - public bool GetRibbon(int index) => GetRibbonByte(index) >= 0; - - public void SetRibbon(int index, bool value = true) - { - if ((uint)index > (uint)MarkSlump) - throw new ArgumentOutOfRangeException(nameof(index)); - - if (value) - { - if (GetRibbon(index)) - return; - var openIndex = Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z != RibbonByteNone); - if (openIndex < 0) - throw new ArgumentOutOfRangeException(nameof(index)); - SetRibbonAtIndex(openIndex, (byte)index); - } - else - { - var ofs = GetRibbonByte(index); - if (ofs < 0) - return; - SetRibbonAtIndex(ofs, RibbonByteNone); - } - } - #endregion + None = 0, + Pokemon = 1, + Item = 2, } + + public WA8() : this(new byte[Size]) { } + public WA8(byte[] data) : base(data) { } + + public bool CanBeReceivedByVersion(int v, PKM pk) => v is (int) GameVersion.PLA || (pk is PK8 && v is (int)GameVersion.SW); + public bool IsDateRestricted => true; + public bool IsEquivalentFixedECPID => EncryptionConstant != 0 && PIDType == ShinyType8.FixedValue && PID == EncryptionConstant; + + // General Card Properties + public override int CardID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x8)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x8), (ushort)value); + } + + public byte CardFlags { get => Data[0x10]; set => Data[0x10] = value; } + public GiftType CardType { get => (GiftType)Data[0x11]; set => Data[0x11] = (byte)value; } + public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); } + public override bool GiftUsed { get => false; set { } } + + public int CardTitleIndex + { + get => Data[0x13]; + set => Data[0x13] = (byte) value; + } + + public override string CardTitle + { + get => "Mystery Gift"; // TODO: Use text string from CardTitleIndex + set => throw new Exception(); + } + + // Item Properties + public override bool IsItem { get => CardType == GiftType.Item; set { if (value) CardType = GiftType.Item; } } + + public override int ItemID + { + get => GetItem(0); + set => SetItem(0, (ushort)value); + } + + public override int Quantity + { + get => GetQuantity(0); + set => SetQuantity(0, (ushort)value); + } + + public int GetItem(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x18 + (0x4 * index))); + public void SetItem(int index, ushort item) => WriteUInt16LittleEndian(Data.AsSpan(0x18 + (4 * index)), item); + public int GetQuantity(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x1A + (0x4 * index))); + public void SetQuantity(int index, ushort quantity) => WriteUInt16LittleEndian(Data.AsSpan(0x1A + (4 * index)), quantity); + + // Pokémon Properties + public override bool IsEntity { get => CardType == GiftType.Pokemon; set { if (value) CardType = GiftType.Pokemon; } } + + public override bool IsShiny => Shiny.IsShiny(); + + public override Shiny Shiny => PIDType switch + { + ShinyType8.FixedValue => GetShinyXor() switch + { + 0 => Shiny.AlwaysSquare, + <= 15 => Shiny.AlwaysStar, + _ => Shiny.Never, + }, + ShinyType8.Random => Shiny.Random, + ShinyType8.Never => Shiny.Never, + ShinyType8.AlwaysStar => Shiny.AlwaysStar, + ShinyType8.AlwaysSquare => Shiny.AlwaysSquare, + _ => throw new ArgumentOutOfRangeException(), + }; + + private int GetShinyXor() + { + // Player owned anti-shiny fixed PID + if (TID == 0 && SID == 0) + return int.MaxValue; + + var pid = PID; + var psv = (int)((pid >> 16) ^ (pid & 0xFFFF)); + var tsv = TID ^ SID; + return psv ^ tsv; + } + + public override int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); + } + + public override int SID { + get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), (ushort)value); + } + + public int OriginGame + { + get => ReadInt32LittleEndian(Data.AsSpan(0x1C)); + set => WriteInt32LittleEndian(Data.AsSpan(0x1C), value); + } + + public uint EncryptionConstant + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x20)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x20), value); + } + + public uint PID + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x24)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x24), value); + } + + // Nicknames, OT Names 0x30 - 0x228 + public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x220)); set => WriteUInt16LittleEndian(Data.AsSpan(0x220), (ushort)value); } + public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0x222)); set => WriteUInt16LittleEndian(Data.AsSpan(0x222), (ushort)value); } + + public override int Ball + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x224)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x224), (ushort)value); + } + + public override int HeldItem + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x226)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x226), (ushort)value); + } + + public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x228)); set => WriteUInt16LittleEndian(Data.AsSpan(0x228), (ushort)value); } + public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x22A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22A), (ushort)value); } + public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x22C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22C), (ushort)value); } + public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x22E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22E), (ushort)value); } + public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x230)); set => WriteUInt16LittleEndian(Data.AsSpan(0x230), (ushort)value); } + public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x232)); set => WriteUInt16LittleEndian(Data.AsSpan(0x232), (ushort)value); } + public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x234)); set => WriteUInt16LittleEndian(Data.AsSpan(0x234), (ushort)value); } + public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x236)); set => WriteUInt16LittleEndian(Data.AsSpan(0x236), (ushort)value); } + + public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x238)); set => WriteUInt16LittleEndian(Data.AsSpan(0x238), (ushort)value); } + public override int Form { get => Data[0x23A]; set => Data[0x23A] = (byte)value; } + public override int Gender { get => Data[0x23B]; set => Data[0x23B] = (byte)value; } + public override byte Level { get => Data[0x23C]; set => Data[0x23C] = value; } + public override bool IsEgg { get => Data[0x23D] == 1; set => Data[0x23D] = value ? (byte)1 : (byte)0; } + public int Nature { get => (sbyte)Data[0x23E]; set => Data[0x23E] = (byte)value; } + public override int AbilityType { get => Data[0x23F]; set => Data[0x23F] = (byte)value; } + + private byte PIDTypeValue => Data[0x240]; + + public ShinyType8 PIDType => PIDTypeValue switch + { + 0 => ShinyType8.Never, + 1 => ShinyType8.Random, + 2 => ShinyType8.AlwaysStar, + 3 => ShinyType8.AlwaysSquare, + 4 => ShinyType8.FixedValue, + _ => throw new ArgumentOutOfRangeException(nameof(PIDType)), + }; + + public int MetLevel { get => Data[0x241]; set => Data[0x241] = (byte)value; } + public byte DynamaxLevel { get => Data[0x242]; set => Data[0x242] = value; } + public bool CanGigantamax { get => Data[0x243] != 0; set => Data[0x243] = value ? (byte)1 : (byte)0; } + + // Ribbons 0x24C-0x26C + private const int RibbonBytesOffset = 0x244; + private const int RibbonBytesCount = 0x20; + private const int RibbonByteNone = 0xFF; // signed -1 + + public bool HasMark() + { + for (int i = 0; i < RibbonBytesCount; i++) + { + var value = Data[RibbonBytesOffset + i]; + if (value == RibbonByteNone) + return false; + if ((RibbonIndex)value is >= MarkLunchtime and <= MarkSlump) + return true; + } + return false; + } + + public byte GetRibbonAtIndex(int byteIndex) + { + if ((uint)byteIndex >= RibbonBytesCount) + throw new ArgumentOutOfRangeException(nameof(byteIndex)); + return Data[RibbonBytesOffset + byteIndex]; + } + + public void SetRibbonAtIndex(int byteIndex, byte ribbonIndex) + { + if ((uint)byteIndex >= RibbonBytesCount) + throw new ArgumentOutOfRangeException(nameof(byteIndex)); + Data[RibbonBytesOffset + byteIndex] = ribbonIndex; + } + + public int IV_HP { get => Data[0x264]; set => Data[0x264] = (byte)value; } + public int IV_ATK { get => Data[0x265]; set => Data[0x265] = (byte)value; } + public int IV_DEF { get => Data[0x266]; set => Data[0x266] = (byte)value; } + public int IV_SPE { get => Data[0x267]; set => Data[0x267] = (byte)value; } + public int IV_SPA { get => Data[0x268]; set => Data[0x268] = (byte)value; } + public int IV_SPD { get => Data[0x269]; set => Data[0x269] = (byte)value; } + + public int OTGender { get => Data[0x26A]; set => Data[0x26A] = (byte)value; } + + public int EV_HP { get => Data[0x26B]; set => Data[0x26B] = (byte)value; } + public int EV_ATK { get => Data[0x26C]; set => Data[0x26C] = (byte)value; } + public int EV_DEF { get => Data[0x26D]; set => Data[0x26D] = (byte)value; } + public int EV_SPE { get => Data[0x26E]; set => Data[0x26E] = (byte)value; } + public int EV_SPA { get => Data[0x26F]; set => Data[0x26F] = (byte)value; } + public int EV_SPD { get => Data[0x270]; set => Data[0x270] = (byte)value; } + + public byte OT_Intensity { get => Data[0x271]; set => Data[0x271] = value; } + public byte OT_Memory { get => Data[0x272]; set => Data[0x272] = value; } + public byte OT_Feeling { get => Data[0x273]; set => Data[0x273] = value; } + public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0x274)); set => WriteUInt16LittleEndian(Data.AsSpan(0x274), value); } + + // Only derivations to WC8 + public byte GV_HP { get => Data[0x27E]; set => Data[0x27E] = value; } + public byte GV_ATK { get => Data[0x27F]; set => Data[0x27F] = value; } + public byte GV_DEF { get => Data[0x280]; set => Data[0x280] = value; } + public byte GV_SPE { get => Data[0x281]; set => Data[0x281] = value; } + public byte GV_SPA { get => Data[0x282]; set => Data[0x282] = value; } + public byte GV_SPD { get => Data[0x283]; set => Data[0x283] = value; } + + // Meta Accessible Properties + public override int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set + { + if (value.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; + } + } + + public override void GetIVs(Span value) + { + if (value.Length != 6) + return; + value[0] = IV_HP; + value[1] = IV_ATK; + value[2] = IV_DEF; + value[3] = IV_SPE; + value[4] = IV_SPA; + value[5] = IV_SPD; + } + + public int[] EVs + { + get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; + set + { + if (value.Length != 6) return; + EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; + EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; + } + } + + public bool GetIsNicknamed(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetNicknameOffset(language))) != 0; + + public bool CanBeAnyLanguage() + { + for (int i = 0; i < 9; i++) + { + var ofs = GetLanguageOffset(i); + var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); + if (lang != 0) + return false; + } + return true; + } + + public bool CanHaveLanguage(int language) + { + if (language is < (int)LanguageID.Japanese or > (int)LanguageID.ChineseT) + return false; + + if (CanBeAnyLanguage()) + return true; + + for (int i = 0; i < 9; i++) + { + var ofs = GetLanguageOffset(i); + var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); + if (lang == language) + return true; + } + return GetLanguage(language) == 0; + } + + public int GetLanguage(int redeemLanguage) => Data[GetLanguageOffset(GetLanguageIndex(redeemLanguage))]; + private static int GetLanguageOffset(int index) => 0x28 + (index * 0x1C) + 0x1A; + + public bool GetHasOT(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetOTOffset(language))) != 0; + + private static int GetLanguageIndex(int language) + { + var lang = (LanguageID) language; + if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.ChineseT) + return (int) LanguageID.English; // fallback + return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2; + } + + public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } + + public override IReadOnlyList Moves + { + get => new[] { Move1, Move2, Move3, Move4 }; + set + { + if (value.Count > 0) Move1 = value[0]; + if (value.Count > 1) Move2 = value[1]; + if (value.Count > 2) Move3 = value[2]; + if (value.Count > 3) Move4 = value[3]; + } + } + + public override IReadOnlyList Relearn + { + get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; + set + { + if (value.Count > 0) RelearnMove1 = value[0]; + if (value.Count > 1) RelearnMove2 = value[1]; + if (value.Count > 2) RelearnMove3 = value[2]; + if (value.Count > 3) RelearnMove4 = value[3]; + } + } + + public override string OT_Name { get; set; } = string.Empty; + public string Nickname => string.Empty; + public bool IsNicknamed => false; + public int Language => 2; + + public string GetNickname(int language) => StringConverter8.GetString(Data.AsSpan(GetNicknameOffset(language), 0x1A)); + public void SetNickname(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetNicknameOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + + public string GetOT(int language) => StringConverter8.GetString(Data.AsSpan(GetOTOffset(language), 0x1A)); + public void SetOT(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetOTOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + + private static int GetNicknameOffset(int language) + { + int index = GetLanguageIndex(language); + return 0x28 + (index * 0x1C); + } + + private static int GetOTOffset(int language) + { + int index = GetLanguageIndex(language); + return 0x124 + (index * 0x1C); + } + + public bool IsHOMEGift => CardID >= 9000; + + public bool CanHandleOT(int language) => !GetHasOT(language); + + public override GameVersion Version + { + get => OriginGame != 0 ? (GameVersion)OriginGame : GameVersion.PLA; + set { } + } + + public bool IsAlpha { get => false; set { } } + + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + if (!IsEntity) + throw new ArgumentException(nameof(IsEntity)); + + int currentLevel = Level > 0 ? Level : 1 + Util.Rand.Next(100); + int metLevel = MetLevel > 0 ? MetLevel : currentLevel; + var pi = PersonalTable.LA.GetFormEntry(Species, Form); + var language = tr.Language; + var OT = GetOT(language); + bool hasOT = GetHasOT(language); + + var pk = new PA8 + { + EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(), + TID = TID, + SID = SID, + Species = Species, + Form = Form, + CurrentLevel = currentLevel, + Ball = Ball != 0 ? Ball : (int)Core.Ball.LAPoke, // Default is Pokeball + Met_Level = metLevel, + HeldItem = HeldItem, + + EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), + + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + RelearnMove1 = RelearnMove1, + RelearnMove2 = RelearnMove2, + RelearnMove3 = RelearnMove3, + RelearnMove4 = RelearnMove4, + + Version = OriginGame != 0 ? OriginGame : tr.Game, + + OT_Name = OT.Length > 0 ? OT : tr.OT, + OT_Gender = OTGender < 2 ? OTGender : tr.Gender, + HT_Name = hasOT ? tr.OT : string.Empty, + HT_Gender = hasOT ? tr.Gender : 0, + HT_Language = hasOT ? (byte)language : (byte)0, + CurrentHandler = hasOT ? 1 : 0, + OT_Friendship = pi.BaseFriendship, + + OT_Intensity = OT_Intensity, + OT_Memory = OT_Memory, + OT_TextVar = OT_TextVar, + OT_Feeling = OT_Feeling, + FatefulEncounter = true, + + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPE = EV_SPE, + EV_SPA = EV_SPA, + EV_SPD = EV_SPD, + + GV_HP = GV_HP, + GV_ATK = GV_ATK, + GV_DEF = GV_DEF, + GV_SPE = GV_SPE, + GV_SPA = GV_SPA, + GV_SPD = GV_SPD, + + //CanGigantamax = CanGigantamax, + //DynamaxLevel = DynamaxLevel, + + Met_Location = MetLocation, + Egg_Location = EggLocation, + }; + pk.SetMaximumPPCurrent(); + + if ((tr.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version, pk)) + pk.Version = (int)GameVersion.PLA; + + if (OTGender >= 2) + { + pk.TID = tr.TID; + pk.SID = tr.SID; + } + + pk.MetDate = IsDateRestricted && EncounterServerDate.WA8Gifts.TryGetValue(CardID, out var dt) ? dt.Start : DateTime.Now; + + // HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC). + if (CardID is 9018 or 9019 or 9020) + pk.Met_Day = 20; + + var nickname_language = GetLanguage(language); + pk.Language = nickname_language != 0 ? nickname_language : tr.Language; + pk.IsNicknamed = GetIsNicknamed(language); + pk.Nickname = pk.IsNicknamed ? GetNickname(language) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); + + for (var i = 0; i < RibbonBytesCount; i++) + { + var ribbon = GetRibbonAtIndex(i); + if (ribbon != RibbonByteNone) + pk.SetRibbon(ribbon); + } + + SetPINGA(pk, criteria); + + if (IsEgg) + SetEggMetData(pk); + pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; + + pk.HeightScalar = PokeSizeUtil.GetRandomScalar(); + pk.WeightScalar = PokeSizeUtil.GetRandomScalar(); + pk.HeightScalarCopy = pk.HeightScalar; + pk.ResetHeight(); + pk.ResetWeight(); + + pk.ResetPartyStats(); + pk.RefreshChecksum(); + return pk; + } + + private void SetEggMetData(PKM pk) + { + pk.IsEgg = true; + pk.EggMetDate = DateTime.Now; + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); + pk.IsNicknamed = true; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = PersonalTable.LA.GetFormEntry(Species, Form); + pk.Nature = (int)criteria.GetNature(Nature == -1 ? Core.Nature.Random : (Nature)Nature); + pk.StatNature = pk.Nature; + pk.Gender = criteria.GetGender(Gender, pi); + var av = GetAbilityIndex(criteria); + pk.RefreshAbility(av); + SetPID(pk); + SetIVs(pk); + } + + private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch + { + 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 + 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H + _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), + }; + + public override AbilityPermission Ability => AbilityType switch + { + 0 => AbilityPermission.OnlyFirst, + 1 => AbilityPermission.OnlySecond, + 2 => AbilityPermission.OnlyHidden, + 3 => AbilityPermission.Any12, + _ => AbilityPermission.Any12H, + }; + + private uint GetPID(ITrainerID tr, ShinyType8 type) => type switch + { + ShinyType8.Never => GetAntishiny(tr), // Random, Never Shiny + ShinyType8.Random => Util.Rand32(), // Random, Any + ShinyType8.AlwaysStar => (uint)(((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 1) << 16) | (PID & 0xFFFF)), // Fixed, Force Star + ShinyType8.AlwaysSquare => (uint)(((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 0) << 16) | (PID & 0xFFFF)), // Fixed, Force Square + ShinyType8.FixedValue => GetFixedPID(tr), + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + private uint GetFixedPID(ITrainerID tr) + { + var pid = PID; + if (pid != 0 && !(TID == 0 && SID == 0)) + return pid; + + if (!tr.IsShiny(pid, 8)) + return pid; + if (IsHOMEGift) + return GetAntishinyFixedHOME(tr); + return pid; + } + + private static uint GetAntishinyFixedHOME(ITrainerID tr) + { + var fid = (uint)(tr.SID << 16) | (uint)tr.TID; + return fid ^ 0x10u; + } + + private static uint GetAntishiny(ITrainerID tr) + { + var pid = Util.Rand32(); + if (tr.IsShiny(pid, 8)) + return pid ^ 0x1000_0000; + return pid; + } + + private void SetPID(PKM pk) + { + pk.PID = GetPID(pk, PIDType); + } + + private void SetIVs(PKM pk) + { + Span finalIVs = stackalloc int[6]; + GetIVs(finalIVs); + var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); + var rng = Util.Rand; + if (ivflag == 0) // Random IVs + { + for (int i = 0; i < finalIVs.Length; i++) + { + if (finalIVs[i] > 31) + finalIVs[i] = rng.Next(32); + } + } + else // 1/2/3 perfect IVs + { + int IVCount = ivflag - 0xFB; + do { finalIVs[rng.Next(6)] = 31; } + while (finalIVs.Count(31) < IVCount); + for (int i = 0; i < finalIVs.Length; i++) + { + if (finalIVs[i] != 31) + finalIVs[i] = rng.Next(32); + } + } + pk.SetIVs(finalIVs); + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsEgg) + { + if (OTGender < 2) + { + if (SID != pk.SID) return false; + if (TID != pk.TID) return false; + if (OTGender != pk.OT_Gender) return false; + } + + if (!CanBeAnyLanguage() && !CanHaveLanguage(pk.Language)) + return false; + + var OT = GetOT(pk.Language); // May not be guaranteed to work. + if (!string.IsNullOrEmpty(OT) && OT != pk.OT_Name) return false; + if (OriginGame != 0 && OriginGame != pk.Version) + { + if (OriginGame is (int)GameVersion.PLA && !(pk.Version is (int)GameVersion.SW && pk.Met_Location == Locations.HOME_SWLA)) + return false; + } + if (EncryptionConstant != 0) + { + if (EncryptionConstant != pk.EncryptionConstant) + return false; + } + } + + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, pk.Format)) + return false; + + if (IsEgg) + { + if (EggLocation != pk.Egg_Location) // traded + { + if (pk.Egg_Location != Locations.LinkTrade6) + return false; + if (PIDType == ShinyType8.Random && pk.IsShiny && pk.ShinyXor > 1) + return false; // shiny traded egg will always have xor0/1. + } + if (!Shiny.IsValid(pk)) + { + return false; // can't be traded away for unshiny + } + + if (pk.IsEgg && !pk.IsNative) + return false; + } + else + { + if (!Shiny.IsValid(pk)) return false; + if (!IsMatchEggLocation(pk)) return false; + if (pk is PK8) + { + if (pk.Met_Location != Locations.HOME_SWLA) + return false; + } + else + { + if (MetLocation != pk.Met_Location) + return false; + } + } + + if (MetLevel != 0 && MetLevel != pk.Met_Level) return false; + if (OTGender < 2 && OTGender != pk.OT_Gender) return false; + if (Nature != -1 && pk.Nature != Nature) return false; + if (Gender != 3 && Gender != pk.Gender) return false; + + const int poke = (int)Core.Ball.LAPoke; + var expectedBall = Ball == 0 ? poke : Ball; + if (expectedBall < poke) // Not even Cherish balls are safe! They get set to the proto-Poké ball. + expectedBall = poke; + if (pk is PK8) + expectedBall = (int)Core.Ball.Poke; // Transferred to SWSH -> Regular Poké ball + if (expectedBall != pk.Ball) + return false; + + if (pk is IDynamaxLevel dl && dl.DynamaxLevel < DynamaxLevel) + return false; + + if (pk is IGanbaru b && b.IsGanbaruValuesBelow(this)) + return false; + + // PID Types 0 and 1 do not use the fixed PID value. + // Values 2,3 are specific shiny states, and 4 is fixed value. + // 2,3,4 can change if it is a traded egg to ensure the same shiny state. + var type = PIDType; + if (type is ShinyType8.Never or ShinyType8.Random) + return true; + return pk.PID == GetPID(pk, type); + } + + protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchPartial(PKM pk) => false; // no version compatibility checks yet. + + #region Lazy Ribbon Implementation + public bool RibbonEarth { get => this.GetRibbonIndex(Earth); set => this.SetRibbonIndex(Earth, value); } + public bool RibbonNational { get => this.GetRibbonIndex(National); set => this.SetRibbonIndex(National, value); } + public bool RibbonCountry { get => this.GetRibbonIndex(Country); set => this.SetRibbonIndex(Country, value); } + public bool RibbonChampionBattle { get => this.GetRibbonIndex(ChampionBattle); set => this.SetRibbonIndex(ChampionBattle, value); } + public bool RibbonChampionRegional { get => this.GetRibbonIndex(ChampionRegional); set => this.SetRibbonIndex(ChampionRegional, value); } + public bool RibbonChampionNational { get => this.GetRibbonIndex(ChampionNational); set => this.SetRibbonIndex(ChampionNational, value); } + public bool RibbonClassic { get => this.GetRibbonIndex(Classic); set => this.SetRibbonIndex(Classic, value); } + public bool RibbonWishing { get => this.GetRibbonIndex(Wishing); set => this.SetRibbonIndex(Wishing, value); } + public bool RibbonPremier { get => this.GetRibbonIndex(Premier); set => this.SetRibbonIndex(Premier, value); } + public bool RibbonEvent { get => this.GetRibbonIndex(Event); set => this.SetRibbonIndex(Event, value); } + public bool RibbonBirthday { get => this.GetRibbonIndex(Birthday); set => this.SetRibbonIndex(Birthday, value); } + public bool RibbonSpecial { get => this.GetRibbonIndex(Special); set => this.SetRibbonIndex(Special, value); } + public bool RibbonWorld { get => this.GetRibbonIndex(World); set => this.SetRibbonIndex(World, value); } + public bool RibbonChampionWorld { get => this.GetRibbonIndex(ChampionWorld); set => this.SetRibbonIndex(ChampionWorld, value); } + public bool RibbonSouvenir { get => this.GetRibbonIndex(Souvenir); set => this.SetRibbonIndex(Souvenir, value); } + public bool RibbonChampionG3 { get => this.GetRibbonIndex(ChampionG3); set => this.SetRibbonIndex(ChampionG3, value); } + public bool RibbonArtist { get => this.GetRibbonIndex(Artist); set => this.SetRibbonIndex(Artist, value); } + public bool RibbonEffort { get => this.GetRibbonIndex(Effort); set => this.SetRibbonIndex(Effort, value); } + public bool RibbonChampionSinnoh { get => this.GetRibbonIndex(ChampionSinnoh); set => this.SetRibbonIndex(ChampionSinnoh, value); } + public bool RibbonAlert { get => this.GetRibbonIndex(Alert); set => this.SetRibbonIndex(Alert, value); } + public bool RibbonShock { get => this.GetRibbonIndex(Shock); set => this.SetRibbonIndex(Shock, value); } + public bool RibbonDowncast { get => this.GetRibbonIndex(Downcast); set => this.SetRibbonIndex(Downcast, value); } + public bool RibbonCareless { get => this.GetRibbonIndex(Careless); set => this.SetRibbonIndex(Careless, value); } + public bool RibbonRelax { get => this.GetRibbonIndex(Relax); set => this.SetRibbonIndex(Relax, value); } + public bool RibbonSnooze { get => this.GetRibbonIndex(Snooze); set => this.SetRibbonIndex(Snooze, value); } + public bool RibbonSmile { get => this.GetRibbonIndex(Smile); set => this.SetRibbonIndex(Smile, value); } + public bool RibbonGorgeous { get => this.GetRibbonIndex(Gorgeous); set => this.SetRibbonIndex(Gorgeous, value); } + public bool RibbonRoyal { get => this.GetRibbonIndex(Royal); set => this.SetRibbonIndex(Royal, value); } + public bool RibbonGorgeousRoyal { get => this.GetRibbonIndex(GorgeousRoyal); set => this.SetRibbonIndex(GorgeousRoyal, value); } + public bool RibbonFootprint { get => this.GetRibbonIndex(Footprint); set => this.SetRibbonIndex(Footprint, value); } + public bool RibbonRecord { get => this.GetRibbonIndex(Record); set => this.SetRibbonIndex(Record, value); } + public bool RibbonLegend { get => this.GetRibbonIndex(Legend); set => this.SetRibbonIndex(Legend, value); } + public bool RibbonChampionKalos { get => this.GetRibbonIndex(ChampionKalos); set => this.SetRibbonIndex(ChampionKalos, value); } + public bool RibbonChampionG6Hoenn { get => this.GetRibbonIndex(ChampionG6Hoenn); set => this.SetRibbonIndex(ChampionG6Hoenn, value); } + public bool RibbonBestFriends { get => this.GetRibbonIndex(BestFriends); set => this.SetRibbonIndex(BestFriends, value); } + public bool RibbonTraining { get => this.GetRibbonIndex(Training); set => this.SetRibbonIndex(Training, value); } + public bool RibbonBattlerSkillful { get => this.GetRibbonIndex(BattlerSkillful); set => this.SetRibbonIndex(BattlerSkillful, value); } + public bool RibbonBattlerExpert { get => this.GetRibbonIndex(BattlerExpert); set => this.SetRibbonIndex(BattlerExpert, value); } + public bool RibbonContestStar { get => this.GetRibbonIndex(ContestStar); set => this.SetRibbonIndex(ContestStar, value); } + public bool RibbonMasterCoolness { get => this.GetRibbonIndex(MasterCoolness); set => this.SetRibbonIndex(MasterCoolness, value); } + public bool RibbonMasterBeauty { get => this.GetRibbonIndex(MasterBeauty); set => this.SetRibbonIndex(MasterBeauty, value); } + public bool RibbonMasterCuteness { get => this.GetRibbonIndex(MasterCuteness); set => this.SetRibbonIndex(MasterCuteness, value); } + public bool RibbonMasterCleverness { get => this.GetRibbonIndex(MasterCleverness); set => this.SetRibbonIndex(MasterCleverness, value); } + public bool RibbonMasterToughness { get => this.GetRibbonIndex(MasterToughness); set => this.SetRibbonIndex(MasterToughness, value); } + + public int RibbonCountMemoryContest { get => 0; set { } } + public int RibbonCountMemoryBattle { get => 0; set { } } + + public bool RibbonChampionAlola { get => this.GetRibbonIndex(ChampionAlola); set => this.SetRibbonIndex(ChampionAlola, value); } + public bool RibbonBattleRoyale { get => this.GetRibbonIndex(BattleRoyale); set => this.SetRibbonIndex(BattleRoyale, value); } + public bool RibbonBattleTreeGreat { get => this.GetRibbonIndex(BattleTreeGreat); set => this.SetRibbonIndex(BattleTreeGreat, value); } + public bool RibbonBattleTreeMaster { get => this.GetRibbonIndex(BattleTreeMaster); set => this.SetRibbonIndex(BattleTreeMaster, value); } + public bool RibbonChampionGalar { get => this.GetRibbonIndex(ChampionGalar); set => this.SetRibbonIndex(ChampionGalar, value); } + public bool RibbonTowerMaster { get => this.GetRibbonIndex(TowerMaster); set => this.SetRibbonIndex(TowerMaster, value); } + public bool RibbonMasterRank { get => this.GetRibbonIndex(MasterRank); set => this.SetRibbonIndex(MasterRank, value); } + public bool RibbonMarkLunchtime { get => this.GetRibbonIndex(MarkLunchtime); set => this.SetRibbonIndex(MarkLunchtime, value); } + public bool RibbonMarkSleepyTime { get => this.GetRibbonIndex(MarkSleepyTime); set => this.SetRibbonIndex(MarkSleepyTime, value); } + public bool RibbonMarkDusk { get => this.GetRibbonIndex(MarkDusk); set => this.SetRibbonIndex(MarkDusk, value); } + public bool RibbonMarkDawn { get => this.GetRibbonIndex(MarkDawn); set => this.SetRibbonIndex(MarkDawn, value); } + public bool RibbonMarkCloudy { get => this.GetRibbonIndex(MarkCloudy); set => this.SetRibbonIndex(MarkCloudy, value); } + public bool RibbonMarkRainy { get => this.GetRibbonIndex(MarkRainy); set => this.SetRibbonIndex(MarkRainy, value); } + public bool RibbonMarkStormy { get => this.GetRibbonIndex(MarkStormy); set => this.SetRibbonIndex(MarkStormy, value); } + public bool RibbonMarkSnowy { get => this.GetRibbonIndex(MarkSnowy); set => this.SetRibbonIndex(MarkSnowy, value); } + public bool RibbonMarkBlizzard { get => this.GetRibbonIndex(MarkBlizzard); set => this.SetRibbonIndex(MarkBlizzard, value); } + public bool RibbonMarkDry { get => this.GetRibbonIndex(MarkDry); set => this.SetRibbonIndex(MarkDry, value); } + public bool RibbonMarkSandstorm { get => this.GetRibbonIndex(MarkSandstorm); set => this.SetRibbonIndex(MarkSandstorm, value); } + public bool RibbonMarkMisty { get => this.GetRibbonIndex(MarkMisty); set => this.SetRibbonIndex(MarkMisty, value); } + public bool RibbonMarkDestiny { get => this.GetRibbonIndex(MarkDestiny); set => this.SetRibbonIndex(MarkDestiny, value); } + public bool RibbonMarkFishing { get => this.GetRibbonIndex(MarkFishing); set => this.SetRibbonIndex(MarkFishing, value); } + public bool RibbonMarkCurry { get => this.GetRibbonIndex(MarkCurry); set => this.SetRibbonIndex(MarkCurry, value); } + public bool RibbonMarkUncommon { get => this.GetRibbonIndex(MarkUncommon); set => this.SetRibbonIndex(MarkUncommon, value); } + public bool RibbonMarkRare { get => this.GetRibbonIndex(MarkRare); set => this.SetRibbonIndex(MarkRare, value); } + public bool RibbonMarkRowdy { get => this.GetRibbonIndex(MarkRowdy); set => this.SetRibbonIndex(MarkRowdy, value); } + public bool RibbonMarkAbsentMinded { get => this.GetRibbonIndex(MarkAbsentMinded); set => this.SetRibbonIndex(MarkAbsentMinded, value); } + public bool RibbonMarkJittery { get => this.GetRibbonIndex(MarkJittery); set => this.SetRibbonIndex(MarkJittery, value); } + public bool RibbonMarkExcited { get => this.GetRibbonIndex(MarkExcited); set => this.SetRibbonIndex(MarkExcited, value); } + public bool RibbonMarkCharismatic { get => this.GetRibbonIndex(MarkCharismatic); set => this.SetRibbonIndex(MarkCharismatic, value); } + public bool RibbonMarkCalmness { get => this.GetRibbonIndex(MarkCalmness); set => this.SetRibbonIndex(MarkCalmness, value); } + public bool RibbonMarkIntense { get => this.GetRibbonIndex(MarkIntense); set => this.SetRibbonIndex(MarkIntense, value); } + public bool RibbonMarkZonedOut { get => this.GetRibbonIndex(MarkZonedOut); set => this.SetRibbonIndex(MarkZonedOut, value); } + public bool RibbonMarkJoyful { get => this.GetRibbonIndex(MarkJoyful); set => this.SetRibbonIndex(MarkJoyful, value); } + public bool RibbonMarkAngry { get => this.GetRibbonIndex(MarkAngry); set => this.SetRibbonIndex(MarkAngry, value); } + public bool RibbonMarkSmiley { get => this.GetRibbonIndex(MarkSmiley); set => this.SetRibbonIndex(MarkSmiley, value); } + public bool RibbonMarkTeary { get => this.GetRibbonIndex(MarkTeary); set => this.SetRibbonIndex(MarkTeary, value); } + public bool RibbonMarkUpbeat { get => this.GetRibbonIndex(MarkUpbeat); set => this.SetRibbonIndex(MarkUpbeat, value); } + public bool RibbonMarkPeeved { get => this.GetRibbonIndex(MarkPeeved); set => this.SetRibbonIndex(MarkPeeved, value); } + public bool RibbonMarkIntellectual { get => this.GetRibbonIndex(MarkIntellectual); set => this.SetRibbonIndex(MarkIntellectual, value); } + public bool RibbonMarkFerocious { get => this.GetRibbonIndex(MarkFerocious); set => this.SetRibbonIndex(MarkFerocious, value); } + public bool RibbonMarkCrafty { get => this.GetRibbonIndex(MarkCrafty); set => this.SetRibbonIndex(MarkCrafty, value); } + public bool RibbonMarkScowling { get => this.GetRibbonIndex(MarkScowling); set => this.SetRibbonIndex(MarkScowling, value); } + public bool RibbonMarkKindly { get => this.GetRibbonIndex(MarkKindly); set => this.SetRibbonIndex(MarkKindly, value); } + public bool RibbonMarkFlustered { get => this.GetRibbonIndex(MarkFlustered); set => this.SetRibbonIndex(MarkFlustered, value); } + public bool RibbonMarkPumpedUp { get => this.GetRibbonIndex(MarkPumpedUp); set => this.SetRibbonIndex(MarkPumpedUp, value); } + public bool RibbonMarkZeroEnergy { get => this.GetRibbonIndex(MarkZeroEnergy); set => this.SetRibbonIndex(MarkZeroEnergy, value); } + public bool RibbonMarkPrideful { get => this.GetRibbonIndex(MarkPrideful); set => this.SetRibbonIndex(MarkPrideful, value); } + public bool RibbonMarkUnsure { get => this.GetRibbonIndex(MarkUnsure); set => this.SetRibbonIndex(MarkUnsure, value); } + public bool RibbonMarkHumble { get => this.GetRibbonIndex(MarkHumble); set => this.SetRibbonIndex(MarkHumble, value); } + public bool RibbonMarkThorny { get => this.GetRibbonIndex(MarkThorny); set => this.SetRibbonIndex(MarkThorny, value); } + public bool RibbonMarkVigor { get => this.GetRibbonIndex(MarkVigor); set => this.SetRibbonIndex(MarkVigor, value); } + public bool RibbonMarkSlump { get => this.GetRibbonIndex(MarkSlump); set => this.SetRibbonIndex(MarkSlump, value); } + public bool RibbonTwinklingStar { get => this.GetRibbonIndex(TwinklingStar); set => this.SetRibbonIndex(TwinklingStar, value); } + public bool RibbonPioneer { get => this.GetRibbonIndex(Pioneer); set => this.SetRibbonIndex(Pioneer, value); } + + public int GetRibbonByte(int index) => Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z == index); + public bool GetRibbon(int index) => GetRibbonByte(index) >= 0; + + public void SetRibbon(int index, bool value = true) + { + if ((uint)index > (uint)MarkSlump) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (value) + { + if (GetRibbon(index)) + return; + var openIndex = Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z != RibbonByteNone); + if (openIndex < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + SetRibbonAtIndex(openIndex, (byte)index); + } + else + { + var ofs = GetRibbonByte(index); + if (ofs < 0) + return; + SetRibbonAtIndex(ofs, RibbonByteNone); + } + } + #endregion } diff --git a/PKHeX.Core/MysteryGifts/WB7.cs b/PKHeX.Core/MysteryGifts/WB7.cs index 124d6e7c5..48c7a209a 100644 --- a/PKHeX.Core/MysteryGifts/WB7.cs +++ b/PKHeX.Core/MysteryGifts/WB7.cs @@ -2,591 +2,590 @@ using System.Collections.Generic; using static System.Buffers.Binary. BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 Mystery Gift Template File +/// +public sealed class WB7 : DataMysteryGift, ILangNick, IAwakened, INature, ILangNicknamedTemplate { - /// - /// Generation 7 Mystery Gift Template File - /// - public sealed class WB7 : DataMysteryGift, ILangNick, IAwakened, INature, ILangNicknamedTemplate + public const int Size = 0x108; + public const int SizeFull = 0x310; + private const int CardStart = SizeFull - Size; + + public override int Generation => 7; + + public WB7() : this(new byte[SizeFull]) { } + public WB7(byte[] data) : base(data) { } + + public override GameVersion Version { get => GameVersion.GG; set { } } + + public byte RestrictVersion { get => Data[0]; set => Data[0] = value; } + + public bool CanBeReceivedByVersion(int v) { - public const int Size = 0x108; - public const int SizeFull = 0x310; - private const int CardStart = SizeFull - Size; + if (v is not ((int)GameVersion.GP or (int)GameVersion.GE)) + return false; + if (RestrictVersion == 0) + return true; // no data + var bitIndex = v - (int)GameVersion.GP; + var bit = 1 << bitIndex; + return (RestrictVersion & bit) != 0; + } - public override int Generation => 7; + // General Card Properties + public override int CardID + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0), (ushort)value); + } - public WB7() : this(new byte[SizeFull]) { } - public WB7(byte[] data) : base(data) { } + public override string CardTitle + { + // Max len 36 char, followed by null terminator + get => StringConverter8.GetString(Data.AsSpan(CardStart + 2, 0x4A)); + set => StringConverter8.SetString(Data.AsSpan(CardStart + 2, 0x4A), value.AsSpan(), 36, StringConverterOption.ClearZero); + } - public override GameVersion Version { get => GameVersion.GG; set { } } + private uint RawDate + { + get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x4C)); + set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x4C), value); + } - public byte RestrictVersion { get => Data[0]; set => Data[0] = value; } + private uint Year + { + get => (RawDate / 10000) + 2000; + set => RawDate = SetDate(value, Month, Day); + } - public bool CanBeReceivedByVersion(int v) + private uint Month + { + get => RawDate % 10000 / 100; + set => RawDate = SetDate(Year, value, Day); + } + + private uint Day + { + get => RawDate % 100; + set => RawDate = SetDate(Year, Month, value); + } + + private static uint SetDate(uint year, uint month, uint day) => (Math.Max(0, year - 2000) * 10000) + (month * 100) + day; + + /// + /// Gets or sets the date of the card. + /// + public DateTime? Date + { + get { - if (v is not ((int)GameVersion.GP or (int)GameVersion.GE)) - return false; - if (RestrictVersion == 0) - return true; // no data - var bitIndex = v - (int)GameVersion.GP; - var bit = 1 << bitIndex; - return (RestrictVersion & bit) != 0; + // Check to see if date is valid + if (!DateUtil.IsDateValid(Year, Month, Day)) + return null; + + return new DateTime((int)Year, (int)Month, (int)Day); } - - // General Card Properties - public override int CardID + set { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0), (ushort)value); - } - - public override string CardTitle - { - // Max len 36 char, followed by null terminator - get => StringConverter8.GetString(Data.AsSpan(CardStart + 2, 0x4A)); - set => StringConverter8.SetString(Data.AsSpan(CardStart + 2, 0x4A), value.AsSpan(), 36, StringConverterOption.ClearZero); - } - - private uint RawDate - { - get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x4C)); - set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x4C), value); - } - - private uint Year - { - get => (RawDate / 10000) + 2000; - set => RawDate = SetDate(value, Month, Day); - } - - private uint Month - { - get => RawDate % 10000 / 100; - set => RawDate = SetDate(Year, value, Day); - } - - private uint Day - { - get => RawDate % 100; - set => RawDate = SetDate(Year, Month, value); - } - - private static uint SetDate(uint year, uint month, uint day) => (Math.Max(0, year - 2000) * 10000) + (month * 100) + day; - - /// - /// Gets or sets the date of the card. - /// - public DateTime? Date - { - get + if (value.HasValue) { - // Check to see if date is valid - if (!DateUtil.IsDateValid(Year, Month, Day)) - return null; - - return new DateTime((int)Year, (int)Month, (int)Day); - } - set - { - if (value.HasValue) - { - // Only update the properties if a value is provided. - Year = (ushort)value.Value.Year; - Month = (byte)value.Value.Month; - Day = (byte)value.Value.Day; - } - else - { - // Clear the Met Date. - // If code tries to access MetDate again, null will be returned. - Year = 0; - Month = 0; - Day = 0; - } - } - } - - public int CardLocation { get => Data[CardStart + 0x50]; set => Data[CardStart + 0x50] = (byte)value; } - - public int CardType { get => Data[CardStart + 0x51]; set => Data[CardStart + 0x51] = (byte)value; } - public byte CardFlags { get => Data[CardStart + 0x52]; set => Data[CardStart + 0x52] = value; } - - public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); } - public override bool GiftUsed { get => (CardFlags & 2) == 2; set => CardFlags = (byte)((CardFlags & ~2) | (value ? 2 : 0)); } - public bool GiftOncePerDay { get => (CardFlags & 4) == 4; set => CardFlags = (byte)((CardFlags & ~4) | (value ? 4 : 0)); } - - public bool MultiObtain { get => Data[CardStart + 0x53] == 1; set => Data[CardStart + 0x53] = value ? (byte)1 : (byte)0; } - - // Item Properties - public override bool IsItem { get => CardType == 1; set { if (value) CardType = 1; } } - public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x68)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x68), (ushort)value); } - public int GetItem(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x68 + (0x4 * index))); - public void SetItem(int index, ushort item) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x68 + (4 * index)), item); - public int GetQuantity(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A + (0x4 * index))); - public void SetQuantity(int index, ushort quantity) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A + (4 * index)), quantity); - - public override int Quantity - { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A), (ushort)value); - } - - // Pokémon Properties - public override bool IsEntity { get => CardType == 0; set { if (value) CardType = 0; } } - public override bool IsShiny => PIDType == ShinyType6.Always; - - public override Shiny Shiny => PIDType switch - { - ShinyType6.FixedValue => Shiny.FixedValue, - ShinyType6.Random => Shiny.Random, - ShinyType6.Always => Shiny.Always, - ShinyType6.Never => Shiny.Never, - _ => throw new ArgumentOutOfRangeException(), - }; - - public override int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x68)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x68), (ushort)value); - } - - public override int SID { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A), (ushort)value); - } - - public int OriginGame - { - get => ReadInt32LittleEndian(Data.AsSpan(CardStart + 0x6C)); - set => WriteInt32LittleEndian(Data.AsSpan(CardStart + 0x6C), value); - } - - public uint EncryptionConstant { - get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x70)); - set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x70), value); - } - - public override int Ball - { - get => Data[CardStart + 0x76]; - set => Data[CardStart + 0x76] = (byte)value; } - - public override int HeldItem // no references - { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x78)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x78), (ushort)value); - } - - public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x7A)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x7A), (ushort)value); } - public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x7C)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x7C), (ushort)value); } - public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x7E)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x7E), (ushort)value); } - public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x80)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x80), (ushort)value); } - public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x82)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x82), (ushort)value); } - public override int Form { get => Data[CardStart + 0x84]; set => Data[CardStart + 0x84] = (byte)value; } - - // public int Language { get => Data[CardStart + 0x85]; set => Data[CardStart + 0x85] = (byte)value; } - - // public string Nickname - // { - // get => Util.TrimFromZero(Encoding.Unicode.GetString(Data, CardStart + 0x86, 0x1A)); - // set => Encoding.Unicode.GetBytes(value.PadRight(12 + 1, '\0')).CopyTo(Data, CardStart + 0x86); - // } - - public int Nature { get => (sbyte)Data[CardStart + 0xA0]; set => Data[CardStart + 0xA0] = (byte)value; } - public override int Gender { get => Data[CardStart + 0xA1]; set => Data[CardStart + 0xA1] = (byte)value; } - public override int AbilityType { get => 3; set => Data[CardStart + 0xA2] = (byte)value; } // no references, always ability 0/1 - public ShinyType6 PIDType { get => (ShinyType6)Data[CardStart + 0xA3]; set => Data[CardStart + 0xA3] = (byte)value; } - public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xA4)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xA4), (ushort)value); } - public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xA6)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xA6), (ushort)value); } - public int MetLevel { get => Data[CardStart + 0xA8]; set => Data[CardStart + 0xA8] = (byte)value; } - - public int IV_HP { get => Data[CardStart + 0xAF]; set => Data[CardStart + 0xAF] = (byte)value; } - public int IV_ATK { get => Data[CardStart + 0xB0]; set => Data[CardStart + 0xB0] = (byte)value; } - public int IV_DEF { get => Data[CardStart + 0xB1]; set => Data[CardStart + 0xB1] = (byte)value; } - public int IV_SPE { get => Data[CardStart + 0xB2]; set => Data[CardStart + 0xB2] = (byte)value; } - public int IV_SPA { get => Data[CardStart + 0xB3]; set => Data[CardStart + 0xB3] = (byte)value; } - public int IV_SPD { get => Data[CardStart + 0xB4]; set => Data[CardStart + 0xB4] = (byte)value; } - - public int OTGender { get => Data[CardStart + 0xB5]; set => Data[CardStart + 0xB5] = (byte)value; } - - // public override string OT_Name - // { - // get => Util.TrimFromZero(Encoding.Unicode.GetString(Data, CardStart + 0xB6, 0x1A)); - // set => Encoding.Unicode.GetBytes(value.PadRight(value.Length + 1, '\0')).CopyTo(Data, CardStart + 0xB6); - // } - - public override byte Level { get => Data[CardStart + 0xD0]; set => Data[CardStart + 0xD0] = value; } - public override bool IsEgg { get => Data[CardStart + 0xD1] == 1; set => Data[CardStart + 0xD1] = value ? (byte)1 : (byte)0; } - public ushort AdditionalItem { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xD2)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xD2), value); } - - public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0xD4)); set => WriteUInt32LittleEndian(Data.AsSpan(0xD4), value); } - public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xD8), (ushort)value); } - public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xDA), (ushort)value); } - public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xDC)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xDC), (ushort)value); } - public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xDE)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xDE), (ushort)value); } - - public byte AV_HP { get => Data[CardStart + 0xE5]; set => Data[CardStart + 0xE5] = value; } - public byte AV_ATK { get => Data[CardStart + 0xE6]; set => Data[CardStart + 0xE6] = value; } - public byte AV_DEF { get => Data[CardStart + 0xE7]; set => Data[CardStart + 0xE7] = value; } - public byte AV_SPE { get => Data[CardStart + 0xE8]; set => Data[CardStart + 0xE8] = value; } - public byte AV_SPA { get => Data[CardStart + 0xE9]; set => Data[CardStart + 0xE9] = value; } - public byte AV_SPD { get => Data[CardStart + 0xEA]; set => Data[CardStart + 0xEA] = value; } - - // Meta Accessible Properties - public override int[] IVs - { - get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; - set - { - if (value.Length != 6) return; - IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; - IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; - } - } - - public override void GetIVs(Span value) - { - if (value.Length != 6) - return; - value[0] = IV_HP; - value[1] = IV_ATK; - value[2] = IV_DEF; - value[3] = IV_SPE; - value[4] = IV_SPA; - value[5] = IV_SPD; - } - - public bool GetIsNicknamed(int language) => Data[GetNicknameOffset(language)] != 0; - - private static int GetLanguageIndex(int language) - { - var lang = (LanguageID) language; - if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.ChineseT) - return (int) LanguageID.English; // fallback - return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2; - } - - public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } - - public override IReadOnlyList Moves - { - get => new[] { Move1, Move2, Move3, Move4 }; - set - { - if (value.Count > 0) Move1 = value[0]; - if (value.Count > 1) Move2 = value[1]; - if (value.Count > 2) Move3 = value[2]; - if (value.Count > 3) Move4 = value[3]; - } - } - - public override IReadOnlyList Relearn - { - get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; - set - { - if (value.Count > 0) RelearnMove1 = value[0]; - if (value.Count > 1) RelearnMove2 = value[1]; - if (value.Count > 2) RelearnMove3 = value[2]; - if (value.Count > 3) RelearnMove4 = value[3]; - } - } - - public override string OT_Name { get; set; } = string.Empty; - public string Nickname => string.Empty; - public bool IsNicknamed => false; - public int Language => 2; - - public int GetLanguage(int redeemLanguage) - { - var languageOffset = GetLanguageIndex(redeemLanguage); - var value = Data[0x1D8 + languageOffset]; - if (value != 0) // Fixed receiving language - return value; - - // Use redeeming language (clamped to legal values for our sake) - if (redeemLanguage is < (int)LanguageID.Japanese or (int)LanguageID.UNUSED_6 or > (int)LanguageID.ChineseT) - return (int)LanguageID.English; // fallback - return redeemLanguage; - } - - public string GetNickname(int language) => StringConverter8.GetString(Data.AsSpan(GetNicknameOffset(language), 0x1A)); - public void SetNickname(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetNicknameOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - - public string GetOT(int language) => StringConverter8.GetString(Data.AsSpan(GetOTOffset(language), 0x1A)); - public void SetOT(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetOTOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - - private static int GetNicknameOffset(int language) - { - int index = GetLanguageIndex(language); - return 0x04 + (index * 0x1A); - } - - private static int GetOTOffset(int language) - { - int index = GetLanguageIndex(language); - return 0xEE + (index * 0x1A); - } - - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - if (!IsEntity) - throw new ArgumentException(nameof(IsEntity)); - - var rnd = Util.Rand; - - int currentLevel = Level > 0 ? Level : rnd.Next(1, 101); - int metLevel = MetLevel > 0 ? MetLevel : currentLevel; - var pi = PersonalTable.GG.GetFormEntry(Species, Form); - - var redeemLanguage = sav.Language; - var language = GetLanguage(redeemLanguage); - var OT = GetOT(redeemLanguage); - bool isRedeemHT = OT.Length != 0; - - var pk = new PB7 - { - Species = Species, - HeldItem = HeldItem, - TID = TID, - SID = SID, - Met_Level = metLevel, - Form = Form, - EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(), - Version = OriginGame != 0 ? OriginGame : sav.Game, - Language = language, - Ball = Ball, - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - RelearnMove1 = RelearnMove1, - RelearnMove2 = RelearnMove2, - RelearnMove3 = RelearnMove3, - RelearnMove4 = RelearnMove4, - Met_Location = MetLocation, - Egg_Location = EggLocation, - AV_HP = AV_HP, - AV_ATK = AV_ATK, - AV_DEF = AV_DEF, - AV_SPE = AV_SPE, - AV_SPA = AV_SPA, - AV_SPD = AV_SPD, - - OT_Name = isRedeemHT ? OT : sav.OT, - OT_Gender = OTGender != 3 ? OTGender % 2 : sav.Gender, - CurrentHandler = isRedeemHT ? 1 : 0, - - EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), - - OT_Friendship = pi.BaseFriendship, - FatefulEncounter = true, - }; - - if (isRedeemHT) - { - pk.HT_Name = sav.OT; - pk.HT_Gender = sav.Gender; - } - - pk.SetMaximumPPCurrent(); - - if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version)) - { - // give random valid game - do { pk.Version = (int)GameVersion.GP + rnd.Next(2); } - while (!CanBeReceivedByVersion(pk.Version)); - } - - if (OTGender == 3) - { - pk.TID = sav.TID; - pk.SID = sav.SID; - } - - pk.MetDate = Date ?? DateTime.Now; - pk.IsNicknamed = GetIsNicknamed(redeemLanguage); - pk.Nickname = pk.IsNicknamed ? GetNickname(redeemLanguage) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); - - SetPINGA(pk, criteria); - - if (IsEgg) - SetEggMetData(pk); - pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; - - pk.HeightScalar = (byte)rnd.Next(0x100); - pk.WeightScalar = (byte)rnd.Next(0x100); - pk.ResetCalculatedValues(); // cp & dimensions - - pk.RefreshChecksum(); - return pk; - } - - private void SetEggMetData(PKM pk) - { - pk.IsEgg = true; - pk.EggMetDate = Date; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); - pk.IsNicknamed = true; - } - - private void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = PersonalTable.GG.GetFormEntry(Species, Form); - pk.Nature = (int)criteria.GetNature((Nature)Nature); - pk.Gender = criteria.GetGender(Gender, pi); - var av = GetAbilityIndex(criteria); - pk.RefreshAbility(av); - SetPID(pk); - SetIVs(pk); - } - - private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch - { - 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 - 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H - _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), - }; - - public override AbilityPermission Ability => AbilityType switch - { - 0 => AbilityPermission.OnlyFirst, - 1 => AbilityPermission.OnlySecond, - 2 => AbilityPermission.OnlyHidden, - 3 => AbilityPermission.Any12, - _ => AbilityPermission.Any12H, - }; - - private void SetPID(PKM pk) - { - switch (PIDType) - { - case ShinyType6.FixedValue: // Specified - pk.PID = PID; - break; - case ShinyType6.Random: // Random - pk.PID = Util.Rand32(); - break; - case ShinyType6.Always: // Random Shiny - pk.PID = Util.Rand32(); - pk.PID = (uint)(((pk.TID ^ pk.SID ^ (pk.PID & 0xFFFF)) << 16) | (pk.PID & 0xFFFF)); - break; - case ShinyType6.Never: // Random Nonshiny - pk.PID = Util.Rand32(); - if (pk.IsShiny) pk.PID ^= 0x10000000; - break; - } - } - - private void SetIVs(PKM pk) - { - Span finalIVs = stackalloc int[6]; - GetIVs(finalIVs); - var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); - var rng = Util.Rand; - if (ivflag == 0) // Random IVs - { - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] > 31) - finalIVs[i] = rng.Next(32); - } - } - else // 1/2/3 perfect IVs - { - int IVCount = ivflag - 0xFB; - do { finalIVs[rng.Next(6)] = 31; } - while (finalIVs.Count(31) < IVCount); - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] != 31) - finalIVs[i] = rng.Next(32); - } - } - pk.SetIVs(finalIVs); - } - - public bool CanHaveLanguage(int language) - { - if (language is < (int) LanguageID.Japanese or > (int) LanguageID.ChineseT) - return false; - - if (CanBeAnyLanguage()) - return true; - - return Array.IndexOf(Data, (byte)language, 0x1D8, 9) >= 0; - } - - public bool CanBeAnyLanguage() - { - for (int i = 0; i < 9; i++) - { - if (Data[0x1D8 + i] != 0) - return false; - } - return true; - } - - public bool CanHandleOT(int language) => string.IsNullOrEmpty(GetOT(language)); - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!IsEgg) - { - if (OTGender != 3) - { - if (SID != pkm.SID) return false; - if (TID != pkm.TID) return false; - if (OTGender != pkm.OT_Gender) return false; - } - var OT = GetOT(pkm.Language); - if (!string.IsNullOrEmpty(OT) && OT != pkm.OT_Name) return false; - if (OriginGame != 0 && OriginGame != pkm.Version) return false; - if (EncryptionConstant != 0 && EncryptionConstant != pkm.EncryptionConstant) return false; - - if (!IsMatchEggLocation(pkm)) return false; - if (!CanBeAnyLanguage() && !CanHaveLanguage(pkm.Language)) - return false; - } - - if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pkm.Form, pkm.Format)) - return false; - - if (IsEgg) - { - if (EggLocation != pkm.Egg_Location) // traded - { - if (pkm.Egg_Location != Locations.LinkTrade6) - return false; - } - else if (PIDType == 0 && pkm.IsShiny) - { - return false; // can't be traded away for unshiny - } - - if (pkm.IsEgg && !pkm.IsNative) - return false; + // Only update the properties if a value is provided. + Year = (ushort)value.Value.Year; + Month = (byte)value.Value.Month; + Day = (byte)value.Value.Day; } else { - if (!Shiny.IsValid(pkm)) return false; - if (!IsMatchEggLocation(pkm)) return false; - if (MetLocation != pkm.Met_Location) return false; + // Clear the Met Date. + // If code tries to access MetDate again, null will be returned. + Year = 0; + Month = 0; + Day = 0; } + } + } - if (MetLevel != pkm.Met_Level) return false; - if (Ball != pkm.Ball) return false; - if (OTGender < 3 && OTGender != pkm.OT_Gender) return false; - if (Nature != -1 && pkm.Nature != Nature) return false; - if (Gender != 3 && Gender != pkm.Gender) return false; + public int CardLocation { get => Data[CardStart + 0x50]; set => Data[CardStart + 0x50] = (byte)value; } - if (pkm is IAwakened s && s.IsAwakeningBelow(this)) - return false; + public int CardType { get => Data[CardStart + 0x51]; set => Data[CardStart + 0x51] = (byte)value; } + public byte CardFlags { get => Data[CardStart + 0x52]; set => Data[CardStart + 0x52] = value; } - return true; + public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); } + public override bool GiftUsed { get => (CardFlags & 2) == 2; set => CardFlags = (byte)((CardFlags & ~2) | (value ? 2 : 0)); } + public bool GiftOncePerDay { get => (CardFlags & 4) == 4; set => CardFlags = (byte)((CardFlags & ~4) | (value ? 4 : 0)); } + + public bool MultiObtain { get => Data[CardStart + 0x53] == 1; set => Data[CardStart + 0x53] = value ? (byte)1 : (byte)0; } + + // Item Properties + public override bool IsItem { get => CardType == 1; set { if (value) CardType = 1; } } + public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x68)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x68), (ushort)value); } + public int GetItem(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x68 + (0x4 * index))); + public void SetItem(int index, ushort item) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x68 + (4 * index)), item); + public int GetQuantity(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A + (0x4 * index))); + public void SetQuantity(int index, ushort quantity) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A + (4 * index)), quantity); + + public override int Quantity + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A), (ushort)value); + } + + // Pokémon Properties + public override bool IsEntity { get => CardType == 0; set { if (value) CardType = 0; } } + public override bool IsShiny => PIDType == ShinyType6.Always; + + public override Shiny Shiny => PIDType switch + { + ShinyType6.FixedValue => Shiny.FixedValue, + ShinyType6.Random => Shiny.Random, + ShinyType6.Always => Shiny.Always, + ShinyType6.Never => Shiny.Never, + _ => throw new ArgumentOutOfRangeException(), + }; + + public override int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x68)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x68), (ushort)value); + } + + public override int SID { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x6A), (ushort)value); + } + + public int OriginGame + { + get => ReadInt32LittleEndian(Data.AsSpan(CardStart + 0x6C)); + set => WriteInt32LittleEndian(Data.AsSpan(CardStart + 0x6C), value); + } + + public uint EncryptionConstant { + get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x70)); + set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x70), value); + } + + public override int Ball + { + get => Data[CardStart + 0x76]; + set => Data[CardStart + 0x76] = (byte)value; } + + public override int HeldItem // no references + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x78)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x78), (ushort)value); + } + + public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x7A)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x7A), (ushort)value); } + public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x7C)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x7C), (ushort)value); } + public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x7E)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x7E), (ushort)value); } + public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x80)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x80), (ushort)value); } + public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x82)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x82), (ushort)value); } + public override int Form { get => Data[CardStart + 0x84]; set => Data[CardStart + 0x84] = (byte)value; } + + // public int Language { get => Data[CardStart + 0x85]; set => Data[CardStart + 0x85] = (byte)value; } + + // public string Nickname + // { + // get => Util.TrimFromZero(Encoding.Unicode.GetString(Data, CardStart + 0x86, 0x1A)); + // set => Encoding.Unicode.GetBytes(value.PadRight(12 + 1, '\0')).CopyTo(Data, CardStart + 0x86); + // } + + public int Nature { get => (sbyte)Data[CardStart + 0xA0]; set => Data[CardStart + 0xA0] = (byte)value; } + public override int Gender { get => Data[CardStart + 0xA1]; set => Data[CardStart + 0xA1] = (byte)value; } + public override int AbilityType { get => 3; set => Data[CardStart + 0xA2] = (byte)value; } // no references, always ability 0/1 + public ShinyType6 PIDType { get => (ShinyType6)Data[CardStart + 0xA3]; set => Data[CardStart + 0xA3] = (byte)value; } + public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xA4)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xA4), (ushort)value); } + public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xA6)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xA6), (ushort)value); } + public int MetLevel { get => Data[CardStart + 0xA8]; set => Data[CardStart + 0xA8] = (byte)value; } + + public int IV_HP { get => Data[CardStart + 0xAF]; set => Data[CardStart + 0xAF] = (byte)value; } + public int IV_ATK { get => Data[CardStart + 0xB0]; set => Data[CardStart + 0xB0] = (byte)value; } + public int IV_DEF { get => Data[CardStart + 0xB1]; set => Data[CardStart + 0xB1] = (byte)value; } + public int IV_SPE { get => Data[CardStart + 0xB2]; set => Data[CardStart + 0xB2] = (byte)value; } + public int IV_SPA { get => Data[CardStart + 0xB3]; set => Data[CardStart + 0xB3] = (byte)value; } + public int IV_SPD { get => Data[CardStart + 0xB4]; set => Data[CardStart + 0xB4] = (byte)value; } + + public int OTGender { get => Data[CardStart + 0xB5]; set => Data[CardStart + 0xB5] = (byte)value; } + + // public override string OT_Name + // { + // get => Util.TrimFromZero(Encoding.Unicode.GetString(Data, CardStart + 0xB6, 0x1A)); + // set => Encoding.Unicode.GetBytes(value.PadRight(value.Length + 1, '\0')).CopyTo(Data, CardStart + 0xB6); + // } + + public override byte Level { get => Data[CardStart + 0xD0]; set => Data[CardStart + 0xD0] = value; } + public override bool IsEgg { get => Data[CardStart + 0xD1] == 1; set => Data[CardStart + 0xD1] = value ? (byte)1 : (byte)0; } + public ushort AdditionalItem { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xD2)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xD2), value); } + + public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0xD4)); set => WriteUInt32LittleEndian(Data.AsSpan(0xD4), value); } + public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xD8), (ushort)value); } + public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xDA), (ushort)value); } + public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xDC)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xDC), (ushort)value); } + public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0xDE)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0xDE), (ushort)value); } + + public byte AV_HP { get => Data[CardStart + 0xE5]; set => Data[CardStart + 0xE5] = value; } + public byte AV_ATK { get => Data[CardStart + 0xE6]; set => Data[CardStart + 0xE6] = value; } + public byte AV_DEF { get => Data[CardStart + 0xE7]; set => Data[CardStart + 0xE7] = value; } + public byte AV_SPE { get => Data[CardStart + 0xE8]; set => Data[CardStart + 0xE8] = value; } + public byte AV_SPA { get => Data[CardStart + 0xE9]; set => Data[CardStart + 0xE9] = value; } + public byte AV_SPD { get => Data[CardStart + 0xEA]; set => Data[CardStart + 0xEA] = value; } + + // Meta Accessible Properties + public override int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set + { + if (value.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; + } + } + + public override void GetIVs(Span value) + { + if (value.Length != 6) + return; + value[0] = IV_HP; + value[1] = IV_ATK; + value[2] = IV_DEF; + value[3] = IV_SPE; + value[4] = IV_SPA; + value[5] = IV_SPD; + } + + public bool GetIsNicknamed(int language) => Data[GetNicknameOffset(language)] != 0; + + private static int GetLanguageIndex(int language) + { + var lang = (LanguageID) language; + if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.ChineseT) + return (int) LanguageID.English; // fallback + return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2; + } + + public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } + + public override IReadOnlyList Moves + { + get => new[] { Move1, Move2, Move3, Move4 }; + set + { + if (value.Count > 0) Move1 = value[0]; + if (value.Count > 1) Move2 = value[1]; + if (value.Count > 2) Move3 = value[2]; + if (value.Count > 3) Move4 = value[3]; + } + } + + public override IReadOnlyList Relearn + { + get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; + set + { + if (value.Count > 0) RelearnMove1 = value[0]; + if (value.Count > 1) RelearnMove2 = value[1]; + if (value.Count > 2) RelearnMove3 = value[2]; + if (value.Count > 3) RelearnMove4 = value[3]; + } + } + + public override string OT_Name { get; set; } = string.Empty; + public string Nickname => string.Empty; + public bool IsNicknamed => false; + public int Language => 2; + + public int GetLanguage(int redeemLanguage) + { + var languageOffset = GetLanguageIndex(redeemLanguage); + var value = Data[0x1D8 + languageOffset]; + if (value != 0) // Fixed receiving language + return value; + + // Use redeeming language (clamped to legal values for our sake) + if (redeemLanguage is < (int)LanguageID.Japanese or (int)LanguageID.UNUSED_6 or > (int)LanguageID.ChineseT) + return (int)LanguageID.English; // fallback + return redeemLanguage; + } + + public string GetNickname(int language) => StringConverter8.GetString(Data.AsSpan(GetNicknameOffset(language), 0x1A)); + public void SetNickname(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetNicknameOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + + public string GetOT(int language) => StringConverter8.GetString(Data.AsSpan(GetOTOffset(language), 0x1A)); + public void SetOT(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetOTOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + + private static int GetNicknameOffset(int language) + { + int index = GetLanguageIndex(language); + return 0x04 + (index * 0x1A); + } + + private static int GetOTOffset(int language) + { + int index = GetLanguageIndex(language); + return 0xEE + (index * 0x1A); + } + + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + if (!IsEntity) + throw new ArgumentException(nameof(IsEntity)); + + var rnd = Util.Rand; + + int currentLevel = Level > 0 ? Level : rnd.Next(1, 101); + int metLevel = MetLevel > 0 ? MetLevel : currentLevel; + var pi = PersonalTable.GG.GetFormEntry(Species, Form); + + var redeemLanguage = tr.Language; + var language = GetLanguage(redeemLanguage); + var OT = GetOT(redeemLanguage); + bool isRedeemHT = OT.Length != 0; + + var pk = new PB7 + { + Species = Species, + HeldItem = HeldItem, + TID = TID, + SID = SID, + Met_Level = metLevel, + Form = Form, + EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(), + Version = OriginGame != 0 ? OriginGame : tr.Game, + Language = language, + Ball = Ball, + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + RelearnMove1 = RelearnMove1, + RelearnMove2 = RelearnMove2, + RelearnMove3 = RelearnMove3, + RelearnMove4 = RelearnMove4, + Met_Location = MetLocation, + Egg_Location = EggLocation, + AV_HP = AV_HP, + AV_ATK = AV_ATK, + AV_DEF = AV_DEF, + AV_SPE = AV_SPE, + AV_SPA = AV_SPA, + AV_SPD = AV_SPD, + + OT_Name = isRedeemHT ? OT : tr.OT, + OT_Gender = OTGender != 3 ? OTGender % 2 : tr.Gender, + CurrentHandler = isRedeemHT ? 1 : 0, + + EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), + + OT_Friendship = pi.BaseFriendship, + FatefulEncounter = true, + }; + + if (isRedeemHT) + { + pk.HT_Name = tr.OT; + pk.HT_Gender = tr.Gender; } - protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species; - protected override bool IsMatchPartial(PKM pkm) => false; + pk.SetMaximumPPCurrent(); + + if ((tr.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version)) + { + // give random valid game + do { pk.Version = (int)GameVersion.GP + rnd.Next(2); } + while (!CanBeReceivedByVersion(pk.Version)); + } + + if (OTGender == 3) + { + pk.TID = tr.TID; + pk.SID = tr.SID; + } + + pk.MetDate = Date ?? DateTime.Now; + pk.IsNicknamed = GetIsNicknamed(redeemLanguage); + pk.Nickname = pk.IsNicknamed ? GetNickname(redeemLanguage) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); + + SetPINGA(pk, criteria); + + if (IsEgg) + SetEggMetData(pk); + pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; + + pk.HeightScalar = (byte)rnd.Next(0x100); + pk.WeightScalar = (byte)rnd.Next(0x100); + pk.ResetCalculatedValues(); // cp & dimensions + + pk.RefreshChecksum(); + return pk; } + + private void SetEggMetData(PKM pk) + { + pk.IsEgg = true; + pk.EggMetDate = Date; + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); + pk.IsNicknamed = true; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = PersonalTable.GG.GetFormEntry(Species, Form); + pk.Nature = (int)criteria.GetNature((Nature)Nature); + pk.Gender = criteria.GetGender(Gender, pi); + var av = GetAbilityIndex(criteria); + pk.RefreshAbility(av); + SetPID(pk); + SetIVs(pk); + } + + private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch + { + 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 + 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H + _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), + }; + + public override AbilityPermission Ability => AbilityType switch + { + 0 => AbilityPermission.OnlyFirst, + 1 => AbilityPermission.OnlySecond, + 2 => AbilityPermission.OnlyHidden, + 3 => AbilityPermission.Any12, + _ => AbilityPermission.Any12H, + }; + + private void SetPID(PKM pk) + { + switch (PIDType) + { + case ShinyType6.FixedValue: // Specified + pk.PID = PID; + break; + case ShinyType6.Random: // Random + pk.PID = Util.Rand32(); + break; + case ShinyType6.Always: // Random Shiny + pk.PID = Util.Rand32(); + pk.PID = (uint)(((pk.TID ^ pk.SID ^ (pk.PID & 0xFFFF)) << 16) | (pk.PID & 0xFFFF)); + break; + case ShinyType6.Never: // Random Nonshiny + pk.PID = Util.Rand32(); + if (pk.IsShiny) pk.PID ^= 0x10000000; + break; + } + } + + private void SetIVs(PKM pk) + { + Span finalIVs = stackalloc int[6]; + GetIVs(finalIVs); + var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); + var rng = Util.Rand; + if (ivflag == 0) // Random IVs + { + for (int i = 0; i < finalIVs.Length; i++) + { + if (finalIVs[i] > 31) + finalIVs[i] = rng.Next(32); + } + } + else // 1/2/3 perfect IVs + { + int IVCount = ivflag - 0xFB; + do { finalIVs[rng.Next(6)] = 31; } + while (finalIVs.Count(31) < IVCount); + for (int i = 0; i < finalIVs.Length; i++) + { + if (finalIVs[i] != 31) + finalIVs[i] = rng.Next(32); + } + } + pk.SetIVs(finalIVs); + } + + public bool CanHaveLanguage(int language) + { + if (language is < (int) LanguageID.Japanese or > (int) LanguageID.ChineseT) + return false; + + if (CanBeAnyLanguage()) + return true; + + return Array.IndexOf(Data, (byte)language, 0x1D8, 9) >= 0; + } + + public bool CanBeAnyLanguage() + { + for (int i = 0; i < 9; i++) + { + if (Data[0x1D8 + i] != 0) + return false; + } + return true; + } + + public bool CanHandleOT(int language) => string.IsNullOrEmpty(GetOT(language)); + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsEgg) + { + if (OTGender != 3) + { + if (SID != pk.SID) return false; + if (TID != pk.TID) return false; + if (OTGender != pk.OT_Gender) return false; + } + var OT = GetOT(pk.Language); + if (!string.IsNullOrEmpty(OT) && OT != pk.OT_Name) return false; + if (OriginGame != 0 && OriginGame != pk.Version) return false; + if (EncryptionConstant != 0 && EncryptionConstant != pk.EncryptionConstant) return false; + + if (!IsMatchEggLocation(pk)) return false; + if (!CanBeAnyLanguage() && !CanHaveLanguage(pk.Language)) + return false; + } + + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, pk.Format)) + return false; + + if (IsEgg) + { + if (EggLocation != pk.Egg_Location) // traded + { + if (pk.Egg_Location != Locations.LinkTrade6) + return false; + } + else if (PIDType == 0 && pk.IsShiny) + { + return false; // can't be traded away for unshiny + } + + if (pk.IsEgg && !pk.IsNative) + return false; + } + else + { + if (!Shiny.IsValid(pk)) return false; + if (!IsMatchEggLocation(pk)) return false; + if (MetLocation != pk.Met_Location) return false; + } + + if (MetLevel != pk.Met_Level) return false; + if (Ball != pk.Ball) return false; + if (OTGender < 3 && OTGender != pk.OT_Gender) return false; + if (Nature != -1 && pk.Nature != Nature) return false; + if (Gender != 3 && Gender != pk.Gender) return false; + + if (pk is IAwakened s && s.IsAwakeningBelow(this)) + return false; + + return true; + } + + protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchPartial(PKM pk) => false; } diff --git a/PKHeX.Core/MysteryGifts/WB8.cs b/PKHeX.Core/MysteryGifts/WB8.cs index 51d161112..3726f7de6 100644 --- a/PKHeX.Core/MysteryGifts/WB8.cs +++ b/PKHeX.Core/MysteryGifts/WB8.cs @@ -1,830 +1,829 @@ -using System; +using System; using System.Collections.Generic; using static PKHeX.Core.RibbonIndex; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8b Mystery Gift Template File +/// +public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, IContestStats, ILangNicknamedTemplate, IEncounterServerDate, + IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8 { - /// - /// Generation 8b Mystery Gift Template File - /// - public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, IContestStats, ILangNicknamedTemplate, IEncounterServerDate, - IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8 + public const int Size = 0x2DC; + public const int CardStart = 0x0; + + public override int Generation => 8; + + public bool IsDateRestricted => IsHOMEGift; + public bool IsEquivalentFixedECPID => EncryptionConstant != 0 && PIDType == ShinyType8.FixedValue && PID == EncryptionConstant; + + public enum GiftType : byte { - public const int Size = 0x2DC; - public const int CardStart = 0x0; + None = 0, + Pokemon = 1, + Item = 2, + BP = 3, + Clothing = 4, + Money = 5, + UnderGroundItem = 6, + } - public override int Generation => 8; + public WB8() : this(new byte[Size]) { } + public WB8(byte[] data) : base(data) { } - public bool IsDateRestricted => IsHOMEGift; - public bool IsEquivalentFixedECPID => EncryptionConstant != 0 && PIDType == ShinyType8.FixedValue && PID == EncryptionConstant; + // TODO: public byte RestrictVersion? - public enum GiftType : byte + public bool CanBeReceivedByVersion(int v, PKM pk) => v is (int) GameVersion.BD or (int) GameVersion.SP || (pk is PK8 && Locations.IsValidMetBDSP(pk.Met_Location, pk.Version)); + + // General Card Properties + + // +0x0: Timestamp + + public override int CardID + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x8)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x8), (ushort)value); + } + + public byte CardFlags { get => Data[CardStart + 0x10]; set => Data[CardStart + 0x10] = value; } + public GiftType CardType { get => (GiftType)Data[CardStart + 0x11]; set => Data[CardStart + 0x11] = (byte)value; } + public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); } + public bool GiftOncePerDay { get => (CardFlags & 4) == 4; set => CardFlags = (byte)((CardFlags & ~4) | (value ? 4 : 0)); } + public override bool GiftUsed { get => false; set { } } + + public int CardTitleIndex + { + get => Data[CardStart + 0x12]; + set => Data[CardStart + 0x12] = (byte) value; + } + + public override string CardTitle + { + get => "Mystery Gift"; // TODO: Use text string from CardTitleIndex + set => throw new Exception(); + } + + // Item Properties + public override bool IsItem { get => CardType == GiftType.Item; set { if (value) CardType = GiftType.Item; } } + + public override int ItemID + { + get => GetItem(0); + set => SetItem(0, (ushort)value); + } + + public override int Quantity + { + get => GetQuantity(0); + set => SetQuantity(0, (ushort)value); + } + + public int GetItem(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x20 + (0x10 * index))); + public void SetItem(int index, ushort item) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x20 + (0x10 * index)), item); + public int GetQuantity(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22 + (0x10 * index))); + public void SetQuantity(int index, ushort quantity) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22 + (0x10 * index)), quantity); + + // Pokémon Properties + public override bool IsEntity { get => CardType == GiftType.Pokemon; set { if (value) CardType = GiftType.Pokemon; } } + + public override bool IsShiny => Shiny.IsShiny(); + + public override Shiny Shiny => PIDType switch + { + ShinyType8.FixedValue => GetShinyXor() switch { - None = 0, - Pokemon = 1, - Item = 2, - BP = 3, - Clothing = 4, - Money = 5, - UnderGroundItem = 6, - } + 0 => Shiny.AlwaysSquare, + <= 15 => Shiny.AlwaysStar, + _ => Shiny.Never, + }, + ShinyType8.Random => Shiny.Random, + ShinyType8.Never => Shiny.Never, + ShinyType8.AlwaysStar => Shiny.AlwaysStar, + ShinyType8.AlwaysSquare => Shiny.AlwaysSquare, + _ => throw new ArgumentOutOfRangeException(), + }; - public WB8() : this(new byte[Size]) { } - public WB8(byte[] data) : base(data) { } + private int GetShinyXor() + { + // Player owned anti-shiny fixed PID + if (TID == 0 && SID == 0) + return int.MaxValue; - // TODO: public byte RestrictVersion? + var pid = PID; + var psv = (int)((pid >> 16) ^ (pid & 0xFFFF)); + var tsv = (TID ^ SID); + return psv ^ tsv; + } - public bool CanBeReceivedByVersion(int v, PKM pk) => v is (int) GameVersion.BD or (int) GameVersion.SP || (pk is PK8 && Locations.IsValidMetBDSP(pk.Met_Location, pk.Version)); + public override int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x20)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x20), (ushort)value); + } - // General Card Properties + public override int SID { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22), (ushort)value); + } - // +0x0: Timestamp + public int OriginGame + { + get => ReadInt32LittleEndian(Data.AsSpan(CardStart + 0x24)); + set => WriteInt32LittleEndian(Data.AsSpan(CardStart + 0x24), value); + } - public override int CardID + public uint EncryptionConstant + { + get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x28)); + set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x28), value); + } + + public uint PID + { + get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x2C)); + set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x2C), value); + } + + // Nicknames, OT Names 0x30 - 0x270 + public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x270)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x270), (ushort)value); } + public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x272)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x272), (ushort)value); } + + public override int Ball + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x274)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x274), (ushort)value); + } + + public override int HeldItem + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x276)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x276), (ushort)value); + } + + public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x278)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x278), (ushort)value); } + public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x27A)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x27A), (ushort)value); } + public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x27C)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x27C), (ushort)value); } + public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x27E)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x27E), (ushort)value); } + public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x280)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x280), (ushort)value); } + public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x282)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x282), (ushort)value); } + public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x284)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x284), (ushort)value); } + public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x286)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x286), (ushort)value); } + + public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x288)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x288), (ushort)value); } + public override int Form { get => Data[CardStart + 0x28A]; set => Data[CardStart + 0x28A] = (byte)value; } + public override int Gender { get => Data[CardStart + 0x28B]; set => Data[CardStart + 0x28B] = (byte)value; } + public override byte Level { get => Data[CardStart + 0x28C]; set => Data[CardStart + 0x28C] = value; } + public override bool IsEgg { get => Data[CardStart + 0x28D] == 1; set => Data[CardStart + 0x28D] = value ? (byte)1 : (byte)0; } + public int Nature { get => (sbyte)Data[CardStart + 0x28E]; set => Data[CardStart + 0x28E] = (byte)value; } + public override int AbilityType { get => Data[CardStart + 0x28F]; set => Data[CardStart + 0x28F] = (byte)value; } + + private byte PIDTypeValue => Data[CardStart + 0x290]; + + public ShinyType8 PIDType => (ShinyType8)PIDTypeValue; + + public int MetLevel { get => Data[CardStart + 0x291]; set => Data[CardStart + 0x291] = (byte)value; } + + // Ribbons 0x292-0x2B2 + private const int RibbonBytesOffset = 0x292; + private const int RibbonBytesCount = 0x20; + private const int RibbonByteNone = 0xFF; // signed -1 + + public bool HasMark() + { + for (int i = 0; i < RibbonBytesCount; i++) { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x8)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x8), (ushort)value); - } - - public byte CardFlags { get => Data[CardStart + 0x10]; set => Data[CardStart + 0x10] = value; } - public GiftType CardType { get => (GiftType)Data[CardStart + 0x11]; set => Data[CardStart + 0x11] = (byte)value; } - public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); } - public bool GiftOncePerDay { get => (CardFlags & 4) == 4; set => CardFlags = (byte)((CardFlags & ~4) | (value ? 4 : 0)); } - public override bool GiftUsed { get => false; set { } } - - public int CardTitleIndex - { - get => Data[CardStart + 0x12]; - set => Data[CardStart + 0x12] = (byte) value; - } - - public override string CardTitle - { - get => "Mystery Gift"; // TODO: Use text string from CardTitleIndex - set => throw new Exception(); - } - - // Item Properties - public override bool IsItem { get => CardType == GiftType.Item; set { if (value) CardType = GiftType.Item; } } - - public override int ItemID - { - get => GetItem(0); - set => SetItem(0, (ushort)value); - } - - public override int Quantity - { - get => GetQuantity(0); - set => SetQuantity(0, (ushort)value); - } - - public int GetItem(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x20 + (0x10 * index))); - public void SetItem(int index, ushort item) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x20 + (0x10 * index)), item); - public int GetQuantity(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22 + (0x10 * index))); - public void SetQuantity(int index, ushort quantity) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22 + (0x10 * index)), quantity); - - // Pokémon Properties - public override bool IsEntity { get => CardType == GiftType.Pokemon; set { if (value) CardType = GiftType.Pokemon; } } - - public override bool IsShiny => Shiny.IsShiny(); - - public override Shiny Shiny => PIDType switch - { - ShinyType8.FixedValue => GetShinyXor() switch - { - 0 => Shiny.AlwaysSquare, - <= 15 => Shiny.AlwaysStar, - _ => Shiny.Never, - }, - ShinyType8.Random => Shiny.Random, - ShinyType8.Never => Shiny.Never, - ShinyType8.AlwaysStar => Shiny.AlwaysStar, - ShinyType8.AlwaysSquare => Shiny.AlwaysSquare, - _ => throw new ArgumentOutOfRangeException(), - }; - - private int GetShinyXor() - { - // Player owned anti-shiny fixed PID - if (TID == 0 && SID == 0) - return int.MaxValue; - - var pid = PID; - var psv = (int)(pid >> 16 ^ (pid & 0xFFFF)); - var tsv = (TID ^ SID); - return psv ^ tsv; - } - - public override int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x20)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x20), (ushort)value); - } - - public override int SID { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22), (ushort)value); - } - - public int OriginGame - { - get => ReadInt32LittleEndian(Data.AsSpan(CardStart + 0x24)); - set => WriteInt32LittleEndian(Data.AsSpan(CardStart + 0x24), value); - } - - public uint EncryptionConstant - { - get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x28)); - set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x28), value); - } - - public uint PID - { - get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x2C)); - set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x2C), value); - } - - // Nicknames, OT Names 0x30 - 0x270 - public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x270)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x270), (ushort)value); } - public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x272)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x272), (ushort)value); } - - public override int Ball - { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x274)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x274), (ushort)value); - } - - public override int HeldItem - { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x276)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x276), (ushort)value); - } - - public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x278)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x278), (ushort)value); } - public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x27A)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x27A), (ushort)value); } - public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x27C)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x27C), (ushort)value); } - public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x27E)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x27E), (ushort)value); } - public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x280)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x280), (ushort)value); } - public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x282)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x282), (ushort)value); } - public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x284)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x284), (ushort)value); } - public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x286)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x286), (ushort)value); } - - public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x288)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x288), (ushort)value); } - public override int Form { get => Data[CardStart + 0x28A]; set => Data[CardStart + 0x28A] = (byte)value; } - public override int Gender { get => Data[CardStart + 0x28B]; set => Data[CardStart + 0x28B] = (byte)value; } - public override byte Level { get => Data[CardStart + 0x28C]; set => Data[CardStart + 0x28C] = value; } - public override bool IsEgg { get => Data[CardStart + 0x28D] == 1; set => Data[CardStart + 0x28D] = value ? (byte)1 : (byte)0; } - public int Nature { get => (sbyte)Data[CardStart + 0x28E]; set => Data[CardStart + 0x28E] = (byte)value; } - public override int AbilityType { get => Data[CardStart + 0x28F]; set => Data[CardStart + 0x28F] = (byte)value; } - - private byte PIDTypeValue => Data[CardStart + 0x290]; - - public ShinyType8 PIDType => (ShinyType8)PIDTypeValue; - - public int MetLevel { get => Data[CardStart + 0x291]; set => Data[CardStart + 0x291] = (byte)value; } - - // Ribbons 0x292-0x2B2 - private const int RibbonBytesOffset = 0x292; - private const int RibbonBytesCount = 0x20; - private const int RibbonByteNone = 0xFF; // signed -1 - - public bool HasMark() - { - for (int i = 0; i < RibbonBytesCount; i++) - { - var value = Data[RibbonBytesOffset + i]; - if (value == RibbonByteNone) - return false; - if ((RibbonIndex)value is >= MarkLunchtime and <= MarkSlump) - return true; - } - return false; - } - - public byte GetRibbonAtIndex(int byteIndex) - { - if ((uint)byteIndex >= RibbonBytesCount) - throw new IndexOutOfRangeException(); - return Data[RibbonBytesOffset + byteIndex]; - } - - public void SetRibbonAtIndex(int byteIndex, byte ribbonIndex) - { - if ((uint)byteIndex >= RibbonBytesCount) - throw new IndexOutOfRangeException(); - Data[RibbonBytesOffset + byteIndex] = ribbonIndex; - } - - public int IV_HP { get => Data[CardStart + 0x2B2]; set => Data[CardStart + 0x2B2] = (byte)value; } - public int IV_ATK { get => Data[CardStart + 0x2B3]; set => Data[CardStart + 0x2B3] = (byte)value; } - public int IV_DEF { get => Data[CardStart + 0x2B4]; set => Data[CardStart + 0x2B4] = (byte)value; } - public int IV_SPE { get => Data[CardStart + 0x2B5]; set => Data[CardStart + 0x2B5] = (byte)value; } - public int IV_SPA { get => Data[CardStart + 0x2B6]; set => Data[CardStart + 0x2B6] = (byte)value; } - public int IV_SPD { get => Data[CardStart + 0x2B7]; set => Data[CardStart + 0x2B7] = (byte)value; } - - public int OTGender { get => Data[CardStart + 0x2B8]; set => Data[CardStart + 0x2B8] = (byte)value; } - - public int EV_HP { get => Data[CardStart + 0x2B9]; set => Data[CardStart + 0x2B9] = (byte)value; } - public int EV_ATK { get => Data[CardStart + 0x2BA]; set => Data[CardStart + 0x2BA] = (byte)value; } - public int EV_DEF { get => Data[CardStart + 0x2BB]; set => Data[CardStart + 0x2BB] = (byte)value; } - public int EV_SPE { get => Data[CardStart + 0x2BC]; set => Data[CardStart + 0x2BC] = (byte)value; } - public int EV_SPA { get => Data[CardStart + 0x2BD]; set => Data[CardStart + 0x2BD] = (byte)value; } - public int EV_SPD { get => Data[CardStart + 0x2BE]; set => Data[CardStart + 0x2BE] = (byte)value; } - - public byte CNT_Cool { get => Data[0x2BF]; set => Data[0x2BF] = value; } - public byte CNT_Beauty { get => Data[0x2C0]; set => Data[0x2C0] = value; } - public byte CNT_Cute { get => Data[0x2C1]; set => Data[0x2C1] = value; } - public byte CNT_Smart { get => Data[0x2C2]; set => Data[0x2C2] = value; } - public byte CNT_Tough { get => Data[0x2C3]; set => Data[0x2C3] = value; } - public byte CNT_Sheen { get => Data[0x2C4]; set => Data[0x2C4] = value; } - - // Meta Accessible Properties - public override int[] IVs - { - get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; - set - { - if (value.Length != 6) return; - IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; - IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; - } - } - - public override void GetIVs(Span value) - { - if (value.Length != 6) - return; - value[0] = IV_HP; - value[1] = IV_ATK; - value[2] = IV_DEF; - value[3] = IV_SPE; - value[4] = IV_SPA; - value[5] = IV_SPD; - } - - public int[] EVs - { - get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; - set - { - if (value.Length != 6) return; - EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; - EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; - } - } - - public bool GetIsNicknamed(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetNicknameOffset(language))) != 0; - - public bool CanBeAnyLanguage() - { - for (int i = 0; i < 9; i++) - { - var ofs = GetLanguageOffset(i); - var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); - if (lang != 0) - return false; - } - return true; - } - - public bool CanHaveLanguage(int language) - { - if (language is < (int)LanguageID.Japanese or > (int)LanguageID.ChineseT) + var value = Data[RibbonBytesOffset + i]; + if (value == RibbonByteNone) return false; - - if (CanBeAnyLanguage()) + if ((RibbonIndex)value is >= MarkLunchtime and <= MarkSlump) return true; - - for (int i = 0; i < 9; i++) - { - var ofs = GetLanguageOffset(i); - var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); - if (lang == language) - return true; - } - return GetLanguage(language) == 0; } + return false; + } - public int GetLanguage(int redeemLanguage) => Data[GetLanguageOffset(GetLanguageIndex(redeemLanguage))]; - private static int GetLanguageOffset(int index) => 0x30 + (index * 0x20) + 0x1A; + public byte GetRibbonAtIndex(int byteIndex) + { + if ((uint)byteIndex >= RibbonBytesCount) + throw new ArgumentOutOfRangeException(nameof(byteIndex)); + return Data[RibbonBytesOffset + byteIndex]; + } - public bool GetHasOT(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetOTOffset(language))) != 0; + public void SetRibbonAtIndex(int byteIndex, byte ribbonIndex) + { + if ((uint)byteIndex >= RibbonBytesCount) + throw new ArgumentOutOfRangeException(nameof(byteIndex)); + Data[RibbonBytesOffset + byteIndex] = ribbonIndex; + } - private static int GetLanguageIndex(int language) + public int IV_HP { get => Data[CardStart + 0x2B2]; set => Data[CardStart + 0x2B2] = (byte)value; } + public int IV_ATK { get => Data[CardStart + 0x2B3]; set => Data[CardStart + 0x2B3] = (byte)value; } + public int IV_DEF { get => Data[CardStart + 0x2B4]; set => Data[CardStart + 0x2B4] = (byte)value; } + public int IV_SPE { get => Data[CardStart + 0x2B5]; set => Data[CardStart + 0x2B5] = (byte)value; } + public int IV_SPA { get => Data[CardStart + 0x2B6]; set => Data[CardStart + 0x2B6] = (byte)value; } + public int IV_SPD { get => Data[CardStart + 0x2B7]; set => Data[CardStart + 0x2B7] = (byte)value; } + + public int OTGender { get => Data[CardStart + 0x2B8]; set => Data[CardStart + 0x2B8] = (byte)value; } + + public int EV_HP { get => Data[CardStart + 0x2B9]; set => Data[CardStart + 0x2B9] = (byte)value; } + public int EV_ATK { get => Data[CardStart + 0x2BA]; set => Data[CardStart + 0x2BA] = (byte)value; } + public int EV_DEF { get => Data[CardStart + 0x2BB]; set => Data[CardStart + 0x2BB] = (byte)value; } + public int EV_SPE { get => Data[CardStart + 0x2BC]; set => Data[CardStart + 0x2BC] = (byte)value; } + public int EV_SPA { get => Data[CardStart + 0x2BD]; set => Data[CardStart + 0x2BD] = (byte)value; } + public int EV_SPD { get => Data[CardStart + 0x2BE]; set => Data[CardStart + 0x2BE] = (byte)value; } + + public byte CNT_Cool { get => Data[0x2BF]; set => Data[0x2BF] = value; } + public byte CNT_Beauty { get => Data[0x2C0]; set => Data[0x2C0] = value; } + public byte CNT_Cute { get => Data[0x2C1]; set => Data[0x2C1] = value; } + public byte CNT_Smart { get => Data[0x2C2]; set => Data[0x2C2] = value; } + public byte CNT_Tough { get => Data[0x2C3]; set => Data[0x2C3] = value; } + public byte CNT_Sheen { get => Data[0x2C4]; set => Data[0x2C4] = value; } + + // Meta Accessible Properties + public override int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set { - var lang = (LanguageID) language; - if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.ChineseT) - return (int) LanguageID.English; // fallback - return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2; + if (value.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; } + } - public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } + public override void GetIVs(Span value) + { + if (value.Length != 6) + return; + value[0] = IV_HP; + value[1] = IV_ATK; + value[2] = IV_DEF; + value[3] = IV_SPE; + value[4] = IV_SPA; + value[5] = IV_SPD; + } - public override IReadOnlyList Moves + public int[] EVs + { + get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; + set { - get => new[] { Move1, Move2, Move3, Move4 }; - set - { - if (value.Count > 0) Move1 = value[0]; - if (value.Count > 1) Move2 = value[1]; - if (value.Count > 2) Move3 = value[2]; - if (value.Count > 3) Move4 = value[3]; - } + if (value.Length != 6) return; + EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; + EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; } + } - public override IReadOnlyList Relearn + public bool GetIsNicknamed(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetNicknameOffset(language))) != 0; + + public bool CanBeAnyLanguage() + { + for (int i = 0; i < 9; i++) { - get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; - set - { - if (value.Count > 0) RelearnMove1 = value[0]; - if (value.Count > 1) RelearnMove2 = value[1]; - if (value.Count > 2) RelearnMove3 = value[2]; - if (value.Count > 3) RelearnMove4 = value[3]; - } + var ofs = GetLanguageOffset(i); + var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); + if (lang != 0) + return false; } + return true; + } - public override string OT_Name { get; set; } = string.Empty; - public string Nickname => string.Empty; - public bool IsNicknamed => false; - public int Language => 2; + public bool CanHaveLanguage(int language) + { + if (language is < (int)LanguageID.Japanese or > (int)LanguageID.ChineseT) + return false; - public string GetNickname(int language) => StringConverter8.GetString(Data.AsSpan(GetNicknameOffset(language), 0x1A)); - public void SetNickname(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetNicknameOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + if (CanBeAnyLanguage()) + return true; - public string GetOT(int language) => StringConverter8.GetString(Data.AsSpan(GetOTOffset(language), 0x1A)); - public void SetOT(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetOTOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - - private static int GetNicknameOffset(int language) + for (int i = 0; i < 9; i++) { - int index = GetLanguageIndex(language); - return 0x30 + (index * 0x20); + var ofs = GetLanguageOffset(i); + var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); + if (lang == language) + return true; } + return GetLanguage(language) == 0; + } - private static int GetOTOffset(int language) + public int GetLanguage(int redeemLanguage) => Data[GetLanguageOffset(GetLanguageIndex(redeemLanguage))]; + private static int GetLanguageOffset(int index) => 0x30 + (index * 0x20) + 0x1A; + + public bool GetHasOT(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetOTOffset(language))) != 0; + + private static int GetLanguageIndex(int language) + { + var lang = (LanguageID) language; + if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.ChineseT) + return (int) LanguageID.English; // fallback + return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2; + } + + public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } + + public override IReadOnlyList Moves + { + get => new[] { Move1, Move2, Move3, Move4 }; + set { - int index = GetLanguageIndex(language); - return 0x150 + (index * 0x20); + if (value.Count > 0) Move1 = value[0]; + if (value.Count > 1) Move2 = value[1]; + if (value.Count > 2) Move3 = value[2]; + if (value.Count > 3) Move4 = value[3]; } + } - public bool IsHOMEGift => CardID >= 9000; - - public bool CanHandleOT(int language) => !GetHasOT(language); - - public override GameVersion Version + public override IReadOnlyList Relearn + { + get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; + set { - get => OriginGame != 0 ? (GameVersion)OriginGame : GameVersion.BDSP; - set { } + if (value.Count > 0) RelearnMove1 = value[0]; + if (value.Count > 1) RelearnMove2 = value[1]; + if (value.Count > 2) RelearnMove3 = value[2]; + if (value.Count > 3) RelearnMove4 = value[3]; } + } - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) + public override string OT_Name { get; set; } = string.Empty; + public string Nickname => string.Empty; + public bool IsNicknamed => false; + public int Language => 2; + + public string GetNickname(int language) => StringConverter8.GetString(Data.AsSpan(GetNicknameOffset(language), 0x1A)); + public void SetNickname(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetNicknameOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + + public string GetOT(int language) => StringConverter8.GetString(Data.AsSpan(GetOTOffset(language), 0x1A)); + public void SetOT(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetOTOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + + private static int GetNicknameOffset(int language) + { + int index = GetLanguageIndex(language); + return 0x30 + (index * 0x20); + } + + private static int GetOTOffset(int language) + { + int index = GetLanguageIndex(language); + return 0x150 + (index * 0x20); + } + + public bool IsHOMEGift => CardID >= 9000; + + public bool CanHandleOT(int language) => !GetHasOT(language); + + public override GameVersion Version + { + get => OriginGame != 0 ? (GameVersion)OriginGame : GameVersion.BDSP; + set { } + } + + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + if (!IsEntity) + throw new ArgumentException(nameof(IsEntity)); + + int currentLevel = Level > 0 ? Level : 1 + Util.Rand.Next(100); + int metLevel = MetLevel > 0 ? MetLevel : currentLevel; + var pi = PersonalTable.BDSP.GetFormEntry(Species, Form); + var language = tr.Language; + var OT = GetOT(language); + bool hasOT = GetHasOT(language); + + var pk = new PB8 { - if (!IsEntity) - throw new ArgumentException(nameof(IsEntity)); + EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(), + TID = TID, + SID = SID, + Species = Species, + Form = Form, + CurrentLevel = currentLevel, + Ball = Ball != 0 ? Ball : 4, // Default is Pokeball + Met_Level = metLevel, + HeldItem = HeldItem, - int currentLevel = Level > 0 ? Level : (1 + Util.Rand.Next(100)); - int metLevel = MetLevel > 0 ? MetLevel : currentLevel; - var pi = PersonalTable.BDSP.GetFormEntry(Species, Form); - var language = sav.Language; - var OT = GetOT(language); - bool hasOT = GetHasOT(language); + EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), - var pk = new PB8 - { - EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(), - TID = TID, - SID = SID, - Species = Species, - Form = Form, - CurrentLevel = currentLevel, - Ball = Ball != 0 ? Ball : 4, // Default is Pokeball - Met_Level = metLevel, - HeldItem = HeldItem, + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + RelearnMove1 = RelearnMove1, + RelearnMove2 = RelearnMove2, + RelearnMove3 = RelearnMove3, + RelearnMove4 = RelearnMove4, - EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), + Version = OriginGame != 0 ? OriginGame : tr.Game, - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - RelearnMove1 = RelearnMove1, - RelearnMove2 = RelearnMove2, - RelearnMove3 = RelearnMove3, - RelearnMove4 = RelearnMove4, + OT_Name = OT.Length > 0 ? OT : tr.OT, + OT_Gender = OTGender < 2 ? OTGender : tr.Gender, + HT_Name = hasOT ? tr.OT : string.Empty, + HT_Gender = hasOT ? tr.Gender : 0, + HT_Language = (byte)(hasOT ? language : 0), + CurrentHandler = hasOT ? 1 : 0, + OT_Friendship = pi.BaseFriendship, - Version = OriginGame != 0 ? OriginGame : sav.Game, + FatefulEncounter = true, - OT_Name = OT.Length > 0 ? OT : sav.OT, - OT_Gender = OTGender < 2 ? OTGender : sav.Gender, - HT_Name = hasOT ? sav.OT : string.Empty, - HT_Gender = hasOT ? sav.Gender : 0, - HT_Language = (byte)(hasOT ? language : 0), - CurrentHandler = hasOT ? 1 : 0, - OT_Friendship = pi.BaseFriendship, + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPE = EV_SPE, + EV_SPA = EV_SPA, + EV_SPD = EV_SPD, - FatefulEncounter = true, + CNT_Cool = CNT_Cool, + CNT_Beauty = CNT_Beauty, + CNT_Cute = CNT_Cute, + CNT_Smart = CNT_Smart, + CNT_Tough = CNT_Tough, + CNT_Sheen = CNT_Sheen, - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPE = EV_SPE, - EV_SPA = EV_SPA, - EV_SPD = EV_SPD, + Met_Location = MetLocation, + Egg_Location = EggLocation, + }; + if (EggLocation == 0) + pk.Egg_Location = Locations.Default8bNone; - CNT_Cool = CNT_Cool, - CNT_Beauty = CNT_Beauty, - CNT_Cute = CNT_Cute, - CNT_Smart = CNT_Smart, - CNT_Tough = CNT_Tough, - CNT_Sheen = CNT_Sheen, - - Met_Location = MetLocation, - Egg_Location = EggLocation, - }; - if (EggLocation == 0) - pk.Egg_Location = Locations.Default8bNone; - - if (Species == (int)Core.Species.Manaphy && IsEgg) - { - pk.Egg_Location = MetLocation; - pk.Met_Location = Locations.Default8bNone; - pk.IsNicknamed = false; - } - pk.SetMaximumPPCurrent(); - - if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version, pk)) - { - // give random valid game - var rnd = Util.Rand; - do { pk.Version = (int)GameVersion.BD + rnd.Next(2); } - while (!CanBeReceivedByVersion(pk.Version, pk)); - } - - if (pk.TID == 0 && pk.SID == 0) - { - pk.TID = sav.TID; - pk.SID = sav.SID; - } - - pk.MetDate = IsDateRestricted && EncounterServerDate.WB8Gifts.TryGetValue(CardID, out var dt) ? dt.Start : DateTime.Now; - // HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC). - if (CardID is 9015 or 9016 or 9017) - pk.Met_Day = 20; - - var nickname_language = GetLanguage(language); - pk.Language = nickname_language != 0 ? nickname_language : sav.Language; - pk.IsNicknamed = !IsEgg && GetIsNicknamed(language); - pk.Nickname = pk.IsNicknamed ? GetNickname(language) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); - - for (var i = 0; i < RibbonBytesCount; i++) - { - var ribbon = GetRibbonAtIndex(i); - if (ribbon != RibbonByteNone) - pk.SetRibbon(ribbon); - } - - SetPINGA(pk, criteria); - - if (IsEgg) - SetEggMetData(pk); - pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; - - pk.HeightScalar = PokeSizeUtil.GetRandomScalar(); - pk.WeightScalar = PokeSizeUtil.GetRandomScalar(); - - pk.ResetPartyStats(); - pk.RefreshChecksum(); - return pk; - } - - private void SetEggMetData(PKM pk) + if (Species == (int)Core.Species.Manaphy && IsEgg) { - pk.IsEgg = true; - pk.EggMetDate = DateTime.Now; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); + pk.Egg_Location = MetLocation; + pk.Met_Location = Locations.Default8bNone; pk.IsNicknamed = false; } + pk.SetMaximumPPCurrent(); - private void SetPINGA(PKM pk, EncounterCriteria criteria) + if ((tr.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version, pk)) { - var pi = PersonalTable.BDSP.GetFormEntry(Species, Form); - pk.Nature = (int)criteria.GetNature(Nature == -1 ? Core.Nature.Random : (Nature)Nature); - pk.StatNature = pk.Nature; - pk.Gender = criteria.GetGender(Gender, pi); - var av = GetAbilityIndex(criteria); - pk.RefreshAbility(av); - SetPID(pk); - SetIVs(pk); + // give random valid game + var rnd = Util.Rand; + do { pk.Version = (int)GameVersion.BD + rnd.Next(2); } + while (!CanBeReceivedByVersion(pk.Version, pk)); } - private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch + if (pk.TID == 0 && pk.SID == 0) { - 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 - 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H - _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), - }; + pk.TID = tr.TID; + pk.SID = tr.SID; + } - public override AbilityPermission Ability => AbilityType switch + pk.MetDate = IsDateRestricted && EncounterServerDate.WB8Gifts.TryGetValue(CardID, out var dt) ? dt.Start : DateTime.Now; + // HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC). + if (CardID is 9015 or 9016 or 9017) + pk.Met_Day = 20; + + var nickname_language = GetLanguage(language); + pk.Language = nickname_language != 0 ? nickname_language : tr.Language; + pk.IsNicknamed = !IsEgg && GetIsNicknamed(language); + pk.Nickname = pk.IsNicknamed ? GetNickname(language) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); + + for (var i = 0; i < RibbonBytesCount; i++) { - 0 => AbilityPermission.OnlyFirst, - 1 => AbilityPermission.OnlySecond, - 2 => AbilityPermission.OnlyHidden, - 3 => AbilityPermission.Any12, - _ => AbilityPermission.Any12H, - }; + var ribbon = GetRibbonAtIndex(i); + if (ribbon != RibbonByteNone) + pk.SetRibbon(ribbon); + } - private uint GetPID(ITrainerID tr, ShinyType8 type) => type switch - { - ShinyType8.Never => GetAntishiny(tr), // Random, Never Shiny - ShinyType8.Random => Util.Rand32(), // Random, Any - ShinyType8.AlwaysStar => (uint)(((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 1) << 16) | (PID & 0xFFFF)), // Fixed, Force Star - ShinyType8.AlwaysSquare => (uint)(((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 0) << 16) | (PID & 0xFFFF)), // Fixed, Force Square - ShinyType8.FixedValue => GetFixedPID(tr), - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; + SetPINGA(pk, criteria); - private uint GetFixedPID(ITrainerID tr) - { - var pid = PID; - if (pid != 0 && !(TID == 0 && SID == 0)) - return pid; + if (IsEgg) + SetEggMetData(pk); + pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; - if (!tr.IsShiny(pid, 8)) - return pid; - if (IsHOMEGift) - return GetAntishinyFixedHOME(tr); + pk.HeightScalar = PokeSizeUtil.GetRandomScalar(); + pk.WeightScalar = PokeSizeUtil.GetRandomScalar(); + + pk.ResetPartyStats(); + pk.RefreshChecksum(); + return pk; + } + + private void SetEggMetData(PKM pk) + { + pk.IsEgg = true; + pk.EggMetDate = DateTime.Now; + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); + pk.IsNicknamed = false; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = PersonalTable.BDSP.GetFormEntry(Species, Form); + pk.Nature = (int)criteria.GetNature(Nature == -1 ? Core.Nature.Random : (Nature)Nature); + pk.StatNature = pk.Nature; + pk.Gender = criteria.GetGender(Gender, pi); + var av = GetAbilityIndex(criteria); + pk.RefreshAbility(av); + SetPID(pk); + SetIVs(pk); + } + + private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch + { + 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 + 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H + _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), + }; + + public override AbilityPermission Ability => AbilityType switch + { + 0 => AbilityPermission.OnlyFirst, + 1 => AbilityPermission.OnlySecond, + 2 => AbilityPermission.OnlyHidden, + 3 => AbilityPermission.Any12, + _ => AbilityPermission.Any12H, + }; + + private uint GetPID(ITrainerID tr, ShinyType8 type) => type switch + { + ShinyType8.Never => GetAntishiny(tr), // Random, Never Shiny + ShinyType8.Random => Util.Rand32(), // Random, Any + ShinyType8.AlwaysStar => (uint)(((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 1) << 16) | (PID & 0xFFFF)), // Fixed, Force Star + ShinyType8.AlwaysSquare => (uint)(((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 0) << 16) | (PID & 0xFFFF)), // Fixed, Force Square + ShinyType8.FixedValue => GetFixedPID(tr), + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + private uint GetFixedPID(ITrainerID tr) + { + var pid = PID; + if (pid != 0 && !(TID == 0 && SID == 0)) return pid; - } - private static uint GetAntishinyFixedHOME(ITrainerID tr) - { - var fid = ((uint)(tr.SID << 16) | (uint)tr.TID); - return fid ^ 0x10u; - } - - private static uint GetAntishiny(ITrainerID tr) - { - var pid = Util.Rand32(); - if (tr.IsShiny(pid, 8)) - return pid ^ 0x1000_0000; + if (!tr.IsShiny(pid, 8)) return pid; - } + if (IsHOMEGift) + return GetAntishinyFixedHOME(tr); + return pid; + } - private void SetPID(PKM pk) - { - pk.PID = GetPID(pk, PIDType); - } + private static uint GetAntishinyFixedHOME(ITrainerID tr) + { + var fid = ((uint)(tr.SID << 16) | (uint)tr.TID); + return fid ^ 0x10u; + } - private void SetIVs(PKM pk) + private static uint GetAntishiny(ITrainerID tr) + { + var pid = Util.Rand32(); + if (tr.IsShiny(pid, 8)) + return pid ^ 0x1000_0000; + return pid; + } + + private void SetPID(PKM pk) + { + pk.PID = GetPID(pk, PIDType); + } + + private void SetIVs(PKM pk) + { + Span finalIVs = stackalloc int[6]; + GetIVs(finalIVs); + var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); + var rng = Util.Rand; + if (ivflag == 0) // Random IVs { - Span finalIVs = stackalloc int[6]; - GetIVs(finalIVs); - var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); - var rng = Util.Rand; - if (ivflag == 0) // Random IVs + for (int i = 0; i < finalIVs.Length; i++) { - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] > 31) - finalIVs[i] = rng.Next(32); - } + if (finalIVs[i] > 31) + finalIVs[i] = rng.Next(32); } - else // 1/2/3 perfect IVs - { - int IVCount = ivflag - 0xFB; - do { finalIVs[rng.Next(6)] = 31; } - while (finalIVs.Count(31) < IVCount); - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] != 31) - finalIVs[i] = rng.Next(32); - } - } - pk.SetIVs(finalIVs); } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) + else // 1/2/3 perfect IVs { - if (!IsEgg) + int IVCount = ivflag - 0xFB; + do { finalIVs[rng.Next(6)] = 31; } + while (finalIVs.Count(31) < IVCount); + for (int i = 0; i < finalIVs.Length; i++) { - if (OTGender < 2) - { - if (SID != pkm.SID) return false; - if (TID != pkm.TID) return false; - if (OTGender != pkm.OT_Gender) return false; - } + if (finalIVs[i] != 31) + finalIVs[i] = rng.Next(32); + } + } + pk.SetIVs(finalIVs); + } - if (!CanBeAnyLanguage() && !CanHaveLanguage(pkm.Language)) - return false; - - var OT = GetOT(pkm.Language); // May not be guaranteed to work. - if (!string.IsNullOrEmpty(OT) && OT != pkm.OT_Name) return false; - if (OriginGame != 0 && OriginGame != pkm.Version) - { - if (OriginGame is (int)GameVersion.BD && !(pkm.Version is (int)GameVersion.SW && pkm.Met_Location == Locations.HOME_SWBD)) - return false; - if (OriginGame is (int)GameVersion.SP && !(pkm.Version is (int)GameVersion.SH && pkm.Met_Location == Locations.HOME_SHSP)) - return false; - } - if (EncryptionConstant != 0) - { - if (EncryptionConstant != pkm.EncryptionConstant) - return false; - } + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsEgg) + { + if (OTGender < 2) + { + if (SID != pk.SID) return false; + if (TID != pk.TID) return false; + if (OTGender != pk.OT_Gender) return false; } - if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pkm.Form, pkm.Format)) + if (!CanBeAnyLanguage() && !CanHaveLanguage(pk.Language)) return false; - if (IsEgg) + var OT = GetOT(pk.Language); // May not be guaranteed to work. + if (!string.IsNullOrEmpty(OT) && OT != pk.OT_Name) return false; + if (OriginGame != 0 && OriginGame != pk.Version) { - var eggloc = Species == (int)Core.Species.Manaphy ? MetLocation : EggLocation; - if (eggloc != pkm.Egg_Location) // traded - { - if (pkm.Egg_Location != Locations.LinkTrade6NPC) - return false; - if (PIDType == ShinyType8.Random && pkm.IsShiny && pkm.ShinyXor > 1) - return false; // shiny traded egg will always have xor0/1. - } - if (!Shiny.IsValid(pkm)) - { - return false; // can't be traded away for unshiny - } + if (OriginGame is (int)GameVersion.BD && !(pk.Version is (int)GameVersion.SW && pk.Met_Location == Locations.HOME_SWBD)) + return false; + if (OriginGame is (int)GameVersion.SP && !(pk.Version is (int)GameVersion.SH && pk.Met_Location == Locations.HOME_SHSP)) + return false; + } + if (EncryptionConstant != 0) + { + if (EncryptionConstant != pk.EncryptionConstant) + return false; + } + } - if (pkm.IsEgg && !pkm.IsNative) + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, pk.Format)) + return false; + + if (IsEgg) + { + var eggloc = Species == (int)Core.Species.Manaphy ? MetLocation : EggLocation; + if (eggloc != pk.Egg_Location) // traded + { + if (pk.Egg_Location != Locations.LinkTrade6NPC) + return false; + if (PIDType == ShinyType8.Random && pk.IsShiny && pk.ShinyXor > 1) + return false; // shiny traded egg will always have xor0/1. + } + if (!Shiny.IsValid(pk)) + { + return false; // can't be traded away for unshiny + } + + if (pk.IsEgg && !pk.IsNative) + return false; + } + else + { + if (!Shiny.IsValid(pk)) return false; + if (!IsMatchEggLocation(pk)) return false; + if (pk is PK8) + { + if (!Locations.IsValidMetBDSP(pk.Met_Location, pk.Version)) return false; } else { - if (!Shiny.IsValid(pkm)) return false; - if (!IsMatchEggLocation(pkm)) return false; - if (pkm is PK8) - { - if (!Locations.IsValidMetBDSP(pkm.Met_Location, pkm.Version)) - return false; - } - else - { - if (MetLocation != pkm.Met_Location) - return false; - } - } - - if (MetLevel != 0 && MetLevel != pkm.Met_Level) return false; - if ((Ball == 0 ? 4 : Ball) != pkm.Ball) return false; - if (OTGender < 2 && OTGender != pkm.OT_Gender) return false; - if (Nature != -1 && pkm.Nature != Nature) return false; - if (Gender != 3 && Gender != pkm.Gender) return false; - - // PID Types 0 and 1 do not use the fixed PID value. - // Values 2,3 are specific shiny states, and 4 is fixed value. - // 2,3,4 can change if it is a traded egg to ensure the same shiny state. - var type = PIDTypeValue; - if (type <= 1) - return true; - return pkm.PID == GetPID(pkm, (ShinyType8)type); - } - - protected override bool IsMatchEggLocation(PKM pk) - { - var expect = pk is PB8 ? Locations.Default8bNone : 0; - return pk.Egg_Location == expect; - } - - protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species; - protected override bool IsMatchPartial(PKM pkm) => false; // no version compatibility checks yet. - - #region Lazy Ribbon Implementation - public bool RibbonEarth { get => this.GetRibbonIndex(Earth); set => this.SetRibbonIndex(Earth, value); } - public bool RibbonNational { get => this.GetRibbonIndex(National); set => this.SetRibbonIndex(National, value); } - public bool RibbonCountry { get => this.GetRibbonIndex(Country); set => this.SetRibbonIndex(Country, value); } - public bool RibbonChampionBattle { get => this.GetRibbonIndex(ChampionBattle); set => this.SetRibbonIndex(ChampionBattle, value); } - public bool RibbonChampionRegional { get => this.GetRibbonIndex(ChampionRegional); set => this.SetRibbonIndex(ChampionRegional, value); } - public bool RibbonChampionNational { get => this.GetRibbonIndex(ChampionNational); set => this.SetRibbonIndex(ChampionNational, value); } - public bool RibbonClassic { get => this.GetRibbonIndex(Classic); set => this.SetRibbonIndex(Classic, value); } - public bool RibbonWishing { get => this.GetRibbonIndex(Wishing); set => this.SetRibbonIndex(Wishing, value); } - public bool RibbonPremier { get => this.GetRibbonIndex(Premier); set => this.SetRibbonIndex(Premier, value); } - public bool RibbonEvent { get => this.GetRibbonIndex(Event); set => this.SetRibbonIndex(Event, value); } - public bool RibbonBirthday { get => this.GetRibbonIndex(Birthday); set => this.SetRibbonIndex(Birthday, value); } - public bool RibbonSpecial { get => this.GetRibbonIndex(Special); set => this.SetRibbonIndex(Special, value); } - public bool RibbonWorld { get => this.GetRibbonIndex(World); set => this.SetRibbonIndex(World, value); } - public bool RibbonChampionWorld { get => this.GetRibbonIndex(ChampionWorld); set => this.SetRibbonIndex(ChampionWorld, value); } - public bool RibbonSouvenir { get => this.GetRibbonIndex(Souvenir); set => this.SetRibbonIndex(Souvenir, value); } - public bool RibbonChampionG3 { get => this.GetRibbonIndex(ChampionG3); set => this.SetRibbonIndex(ChampionG3, value); } - public bool RibbonArtist { get => this.GetRibbonIndex(Artist); set => this.SetRibbonIndex(Artist, value); } - public bool RibbonEffort { get => this.GetRibbonIndex(Effort); set => this.SetRibbonIndex(Effort, value); } - public bool RibbonChampionSinnoh { get => this.GetRibbonIndex(ChampionSinnoh); set => this.SetRibbonIndex(ChampionSinnoh, value); } - public bool RibbonAlert { get => this.GetRibbonIndex(Alert); set => this.SetRibbonIndex(Alert, value); } - public bool RibbonShock { get => this.GetRibbonIndex(Shock); set => this.SetRibbonIndex(Shock, value); } - public bool RibbonDowncast { get => this.GetRibbonIndex(Downcast); set => this.SetRibbonIndex(Downcast, value); } - public bool RibbonCareless { get => this.GetRibbonIndex(Careless); set => this.SetRibbonIndex(Careless, value); } - public bool RibbonRelax { get => this.GetRibbonIndex(Relax); set => this.SetRibbonIndex(Relax, value); } - public bool RibbonSnooze { get => this.GetRibbonIndex(Snooze); set => this.SetRibbonIndex(Snooze, value); } - public bool RibbonSmile { get => this.GetRibbonIndex(Smile); set => this.SetRibbonIndex(Smile, value); } - public bool RibbonGorgeous { get => this.GetRibbonIndex(Gorgeous); set => this.SetRibbonIndex(Gorgeous, value); } - public bool RibbonRoyal { get => this.GetRibbonIndex(Royal); set => this.SetRibbonIndex(Royal, value); } - public bool RibbonGorgeousRoyal { get => this.GetRibbonIndex(GorgeousRoyal); set => this.SetRibbonIndex(GorgeousRoyal, value); } - public bool RibbonFootprint { get => this.GetRibbonIndex(Footprint); set => this.SetRibbonIndex(Footprint, value); } - public bool RibbonRecord { get => this.GetRibbonIndex(Record); set => this.SetRibbonIndex(Record, value); } - public bool RibbonLegend { get => this.GetRibbonIndex(Legend); set => this.SetRibbonIndex(Legend, value); } - public bool RibbonChampionKalos { get => this.GetRibbonIndex(ChampionKalos); set => this.SetRibbonIndex(ChampionKalos, value); } - public bool RibbonChampionG6Hoenn { get => this.GetRibbonIndex(ChampionG6Hoenn); set => this.SetRibbonIndex(ChampionG6Hoenn, value); } - public bool RibbonBestFriends { get => this.GetRibbonIndex(BestFriends); set => this.SetRibbonIndex(BestFriends, value); } - public bool RibbonTraining { get => this.GetRibbonIndex(Training); set => this.SetRibbonIndex(Training, value); } - public bool RibbonBattlerSkillful { get => this.GetRibbonIndex(BattlerSkillful); set => this.SetRibbonIndex(BattlerSkillful, value); } - public bool RibbonBattlerExpert { get => this.GetRibbonIndex(BattlerExpert); set => this.SetRibbonIndex(BattlerExpert, value); } - public bool RibbonContestStar { get => this.GetRibbonIndex(ContestStar); set => this.SetRibbonIndex(ContestStar, value); } - public bool RibbonMasterCoolness { get => this.GetRibbonIndex(MasterCoolness); set => this.SetRibbonIndex(MasterCoolness, value); } - public bool RibbonMasterBeauty { get => this.GetRibbonIndex(MasterBeauty); set => this.SetRibbonIndex(MasterBeauty, value); } - public bool RibbonMasterCuteness { get => this.GetRibbonIndex(MasterCuteness); set => this.SetRibbonIndex(MasterCuteness, value); } - public bool RibbonMasterCleverness { get => this.GetRibbonIndex(MasterCleverness); set => this.SetRibbonIndex(MasterCleverness, value); } - public bool RibbonMasterToughness { get => this.GetRibbonIndex(MasterToughness); set => this.SetRibbonIndex(MasterToughness, value); } - - public int RibbonCountMemoryContest { get => 0; set { } } - public int RibbonCountMemoryBattle { get => 0; set { } } - - public bool RibbonChampionAlola { get => this.GetRibbonIndex(ChampionAlola); set => this.SetRibbonIndex(ChampionAlola, value); } - public bool RibbonBattleRoyale { get => this.GetRibbonIndex(BattleRoyale); set => this.SetRibbonIndex(BattleRoyale, value); } - public bool RibbonBattleTreeGreat { get => this.GetRibbonIndex(BattleTreeGreat); set => this.SetRibbonIndex(BattleTreeGreat, value); } - public bool RibbonBattleTreeMaster { get => this.GetRibbonIndex(BattleTreeMaster); set => this.SetRibbonIndex(BattleTreeMaster, value); } - public bool RibbonChampionGalar { get => this.GetRibbonIndex(ChampionGalar); set => this.SetRibbonIndex(ChampionGalar, value); } - public bool RibbonTowerMaster { get => this.GetRibbonIndex(TowerMaster); set => this.SetRibbonIndex(TowerMaster, value); } - public bool RibbonMasterRank { get => this.GetRibbonIndex(MasterRank); set => this.SetRibbonIndex(MasterRank, value); } - public bool RibbonMarkLunchtime { get => this.GetRibbonIndex(MarkLunchtime); set => this.SetRibbonIndex(MarkLunchtime, value); } - public bool RibbonMarkSleepyTime { get => this.GetRibbonIndex(MarkSleepyTime); set => this.SetRibbonIndex(MarkSleepyTime, value); } - public bool RibbonMarkDusk { get => this.GetRibbonIndex(MarkDusk); set => this.SetRibbonIndex(MarkDusk, value); } - public bool RibbonMarkDawn { get => this.GetRibbonIndex(MarkDawn); set => this.SetRibbonIndex(MarkDawn, value); } - public bool RibbonMarkCloudy { get => this.GetRibbonIndex(MarkCloudy); set => this.SetRibbonIndex(MarkCloudy, value); } - public bool RibbonMarkRainy { get => this.GetRibbonIndex(MarkRainy); set => this.SetRibbonIndex(MarkRainy, value); } - public bool RibbonMarkStormy { get => this.GetRibbonIndex(MarkStormy); set => this.SetRibbonIndex(MarkStormy, value); } - public bool RibbonMarkSnowy { get => this.GetRibbonIndex(MarkSnowy); set => this.SetRibbonIndex(MarkSnowy, value); } - public bool RibbonMarkBlizzard { get => this.GetRibbonIndex(MarkBlizzard); set => this.SetRibbonIndex(MarkBlizzard, value); } - public bool RibbonMarkDry { get => this.GetRibbonIndex(MarkDry); set => this.SetRibbonIndex(MarkDry, value); } - public bool RibbonMarkSandstorm { get => this.GetRibbonIndex(MarkSandstorm); set => this.SetRibbonIndex(MarkSandstorm, value); } - public bool RibbonMarkMisty { get => this.GetRibbonIndex(MarkMisty); set => this.SetRibbonIndex(MarkMisty, value); } - public bool RibbonMarkDestiny { get => this.GetRibbonIndex(MarkDestiny); set => this.SetRibbonIndex(MarkDestiny, value); } - public bool RibbonMarkFishing { get => this.GetRibbonIndex(MarkFishing); set => this.SetRibbonIndex(MarkFishing, value); } - public bool RibbonMarkCurry { get => this.GetRibbonIndex(MarkCurry); set => this.SetRibbonIndex(MarkCurry, value); } - public bool RibbonMarkUncommon { get => this.GetRibbonIndex(MarkUncommon); set => this.SetRibbonIndex(MarkUncommon, value); } - public bool RibbonMarkRare { get => this.GetRibbonIndex(MarkRare); set => this.SetRibbonIndex(MarkRare, value); } - public bool RibbonMarkRowdy { get => this.GetRibbonIndex(MarkRowdy); set => this.SetRibbonIndex(MarkRowdy, value); } - public bool RibbonMarkAbsentMinded { get => this.GetRibbonIndex(MarkAbsentMinded); set => this.SetRibbonIndex(MarkAbsentMinded, value); } - public bool RibbonMarkJittery { get => this.GetRibbonIndex(MarkJittery); set => this.SetRibbonIndex(MarkJittery, value); } - public bool RibbonMarkExcited { get => this.GetRibbonIndex(MarkExcited); set => this.SetRibbonIndex(MarkExcited, value); } - public bool RibbonMarkCharismatic { get => this.GetRibbonIndex(MarkCharismatic); set => this.SetRibbonIndex(MarkCharismatic, value); } - public bool RibbonMarkCalmness { get => this.GetRibbonIndex(MarkCalmness); set => this.SetRibbonIndex(MarkCalmness, value); } - public bool RibbonMarkIntense { get => this.GetRibbonIndex(MarkIntense); set => this.SetRibbonIndex(MarkIntense, value); } - public bool RibbonMarkZonedOut { get => this.GetRibbonIndex(MarkZonedOut); set => this.SetRibbonIndex(MarkZonedOut, value); } - public bool RibbonMarkJoyful { get => this.GetRibbonIndex(MarkJoyful); set => this.SetRibbonIndex(MarkJoyful, value); } - public bool RibbonMarkAngry { get => this.GetRibbonIndex(MarkAngry); set => this.SetRibbonIndex(MarkAngry, value); } - public bool RibbonMarkSmiley { get => this.GetRibbonIndex(MarkSmiley); set => this.SetRibbonIndex(MarkSmiley, value); } - public bool RibbonMarkTeary { get => this.GetRibbonIndex(MarkTeary); set => this.SetRibbonIndex(MarkTeary, value); } - public bool RibbonMarkUpbeat { get => this.GetRibbonIndex(MarkUpbeat); set => this.SetRibbonIndex(MarkUpbeat, value); } - public bool RibbonMarkPeeved { get => this.GetRibbonIndex(MarkPeeved); set => this.SetRibbonIndex(MarkPeeved, value); } - public bool RibbonMarkIntellectual { get => this.GetRibbonIndex(MarkIntellectual); set => this.SetRibbonIndex(MarkIntellectual, value); } - public bool RibbonMarkFerocious { get => this.GetRibbonIndex(MarkFerocious); set => this.SetRibbonIndex(MarkFerocious, value); } - public bool RibbonMarkCrafty { get => this.GetRibbonIndex(MarkCrafty); set => this.SetRibbonIndex(MarkCrafty, value); } - public bool RibbonMarkScowling { get => this.GetRibbonIndex(MarkScowling); set => this.SetRibbonIndex(MarkScowling, value); } - public bool RibbonMarkKindly { get => this.GetRibbonIndex(MarkKindly); set => this.SetRibbonIndex(MarkKindly, value); } - public bool RibbonMarkFlustered { get => this.GetRibbonIndex(MarkFlustered); set => this.SetRibbonIndex(MarkFlustered, value); } - public bool RibbonMarkPumpedUp { get => this.GetRibbonIndex(MarkPumpedUp); set => this.SetRibbonIndex(MarkPumpedUp, value); } - public bool RibbonMarkZeroEnergy { get => this.GetRibbonIndex(MarkZeroEnergy); set => this.SetRibbonIndex(MarkZeroEnergy, value); } - public bool RibbonMarkPrideful { get => this.GetRibbonIndex(MarkPrideful); set => this.SetRibbonIndex(MarkPrideful, value); } - public bool RibbonMarkUnsure { get => this.GetRibbonIndex(MarkUnsure); set => this.SetRibbonIndex(MarkUnsure, value); } - public bool RibbonMarkHumble { get => this.GetRibbonIndex(MarkHumble); set => this.SetRibbonIndex(MarkHumble, value); } - public bool RibbonMarkThorny { get => this.GetRibbonIndex(MarkThorny); set => this.SetRibbonIndex(MarkThorny, value); } - public bool RibbonMarkVigor { get => this.GetRibbonIndex(MarkVigor); set => this.SetRibbonIndex(MarkVigor, value); } - public bool RibbonMarkSlump { get => this.GetRibbonIndex(MarkSlump); set => this.SetRibbonIndex(MarkSlump, value); } - public bool RibbonTwinklingStar { get => this.GetRibbonIndex(TwinklingStar); set => this.SetRibbonIndex(TwinklingStar, value); } - public bool RibbonPioneer { get => this.GetRibbonIndex(Pioneer); set => this.SetRibbonIndex(Pioneer, value); } - - public int GetRibbonByte(int index) => Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z == index); - public bool GetRibbon(int index) => GetRibbonByte(index) >= 0; - - public void SetRibbon(int index, bool value = true) - { - if ((uint)index > (uint)MarkSlump) - throw new ArgumentOutOfRangeException(nameof(index)); - - if (value) - { - if (GetRibbon(index)) - return; - var openIndex = Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z != RibbonByteNone); - if (openIndex < 0) - throw new ArgumentOutOfRangeException(nameof(index)); - SetRibbonAtIndex(openIndex, (byte)index); - } - else - { - var ofs = GetRibbonByte(index); - if (ofs < 0) - return; - SetRibbonAtIndex(ofs, RibbonByteNone); + if (MetLocation != pk.Met_Location) + return false; } } - #endregion + + if (MetLevel != 0 && MetLevel != pk.Met_Level) return false; + if ((Ball == 0 ? 4 : Ball) != pk.Ball) return false; + if (OTGender < 2 && OTGender != pk.OT_Gender) return false; + if (Nature != -1 && pk.Nature != Nature) return false; + if (Gender != 3 && Gender != pk.Gender) return false; + + // PID Types 0 and 1 do not use the fixed PID value. + // Values 2,3 are specific shiny states, and 4 is fixed value. + // 2,3,4 can change if it is a traded egg to ensure the same shiny state. + var type = PIDTypeValue; + if (type <= 1) + return true; + return pk.PID == GetPID(pk, (ShinyType8)type); } + + protected override bool IsMatchEggLocation(PKM pk) + { + var expect = pk is PB8 ? Locations.Default8bNone : 0; + return pk.Egg_Location == expect; + } + + protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchPartial(PKM pk) => false; // no version compatibility checks yet. + + #region Lazy Ribbon Implementation + public bool RibbonEarth { get => this.GetRibbonIndex(Earth); set => this.SetRibbonIndex(Earth, value); } + public bool RibbonNational { get => this.GetRibbonIndex(National); set => this.SetRibbonIndex(National, value); } + public bool RibbonCountry { get => this.GetRibbonIndex(Country); set => this.SetRibbonIndex(Country, value); } + public bool RibbonChampionBattle { get => this.GetRibbonIndex(ChampionBattle); set => this.SetRibbonIndex(ChampionBattle, value); } + public bool RibbonChampionRegional { get => this.GetRibbonIndex(ChampionRegional); set => this.SetRibbonIndex(ChampionRegional, value); } + public bool RibbonChampionNational { get => this.GetRibbonIndex(ChampionNational); set => this.SetRibbonIndex(ChampionNational, value); } + public bool RibbonClassic { get => this.GetRibbonIndex(Classic); set => this.SetRibbonIndex(Classic, value); } + public bool RibbonWishing { get => this.GetRibbonIndex(Wishing); set => this.SetRibbonIndex(Wishing, value); } + public bool RibbonPremier { get => this.GetRibbonIndex(Premier); set => this.SetRibbonIndex(Premier, value); } + public bool RibbonEvent { get => this.GetRibbonIndex(Event); set => this.SetRibbonIndex(Event, value); } + public bool RibbonBirthday { get => this.GetRibbonIndex(Birthday); set => this.SetRibbonIndex(Birthday, value); } + public bool RibbonSpecial { get => this.GetRibbonIndex(Special); set => this.SetRibbonIndex(Special, value); } + public bool RibbonWorld { get => this.GetRibbonIndex(World); set => this.SetRibbonIndex(World, value); } + public bool RibbonChampionWorld { get => this.GetRibbonIndex(ChampionWorld); set => this.SetRibbonIndex(ChampionWorld, value); } + public bool RibbonSouvenir { get => this.GetRibbonIndex(Souvenir); set => this.SetRibbonIndex(Souvenir, value); } + public bool RibbonChampionG3 { get => this.GetRibbonIndex(ChampionG3); set => this.SetRibbonIndex(ChampionG3, value); } + public bool RibbonArtist { get => this.GetRibbonIndex(Artist); set => this.SetRibbonIndex(Artist, value); } + public bool RibbonEffort { get => this.GetRibbonIndex(Effort); set => this.SetRibbonIndex(Effort, value); } + public bool RibbonChampionSinnoh { get => this.GetRibbonIndex(ChampionSinnoh); set => this.SetRibbonIndex(ChampionSinnoh, value); } + public bool RibbonAlert { get => this.GetRibbonIndex(Alert); set => this.SetRibbonIndex(Alert, value); } + public bool RibbonShock { get => this.GetRibbonIndex(Shock); set => this.SetRibbonIndex(Shock, value); } + public bool RibbonDowncast { get => this.GetRibbonIndex(Downcast); set => this.SetRibbonIndex(Downcast, value); } + public bool RibbonCareless { get => this.GetRibbonIndex(Careless); set => this.SetRibbonIndex(Careless, value); } + public bool RibbonRelax { get => this.GetRibbonIndex(Relax); set => this.SetRibbonIndex(Relax, value); } + public bool RibbonSnooze { get => this.GetRibbonIndex(Snooze); set => this.SetRibbonIndex(Snooze, value); } + public bool RibbonSmile { get => this.GetRibbonIndex(Smile); set => this.SetRibbonIndex(Smile, value); } + public bool RibbonGorgeous { get => this.GetRibbonIndex(Gorgeous); set => this.SetRibbonIndex(Gorgeous, value); } + public bool RibbonRoyal { get => this.GetRibbonIndex(Royal); set => this.SetRibbonIndex(Royal, value); } + public bool RibbonGorgeousRoyal { get => this.GetRibbonIndex(GorgeousRoyal); set => this.SetRibbonIndex(GorgeousRoyal, value); } + public bool RibbonFootprint { get => this.GetRibbonIndex(Footprint); set => this.SetRibbonIndex(Footprint, value); } + public bool RibbonRecord { get => this.GetRibbonIndex(Record); set => this.SetRibbonIndex(Record, value); } + public bool RibbonLegend { get => this.GetRibbonIndex(Legend); set => this.SetRibbonIndex(Legend, value); } + public bool RibbonChampionKalos { get => this.GetRibbonIndex(ChampionKalos); set => this.SetRibbonIndex(ChampionKalos, value); } + public bool RibbonChampionG6Hoenn { get => this.GetRibbonIndex(ChampionG6Hoenn); set => this.SetRibbonIndex(ChampionG6Hoenn, value); } + public bool RibbonBestFriends { get => this.GetRibbonIndex(BestFriends); set => this.SetRibbonIndex(BestFriends, value); } + public bool RibbonTraining { get => this.GetRibbonIndex(Training); set => this.SetRibbonIndex(Training, value); } + public bool RibbonBattlerSkillful { get => this.GetRibbonIndex(BattlerSkillful); set => this.SetRibbonIndex(BattlerSkillful, value); } + public bool RibbonBattlerExpert { get => this.GetRibbonIndex(BattlerExpert); set => this.SetRibbonIndex(BattlerExpert, value); } + public bool RibbonContestStar { get => this.GetRibbonIndex(ContestStar); set => this.SetRibbonIndex(ContestStar, value); } + public bool RibbonMasterCoolness { get => this.GetRibbonIndex(MasterCoolness); set => this.SetRibbonIndex(MasterCoolness, value); } + public bool RibbonMasterBeauty { get => this.GetRibbonIndex(MasterBeauty); set => this.SetRibbonIndex(MasterBeauty, value); } + public bool RibbonMasterCuteness { get => this.GetRibbonIndex(MasterCuteness); set => this.SetRibbonIndex(MasterCuteness, value); } + public bool RibbonMasterCleverness { get => this.GetRibbonIndex(MasterCleverness); set => this.SetRibbonIndex(MasterCleverness, value); } + public bool RibbonMasterToughness { get => this.GetRibbonIndex(MasterToughness); set => this.SetRibbonIndex(MasterToughness, value); } + + public int RibbonCountMemoryContest { get => 0; set { } } + public int RibbonCountMemoryBattle { get => 0; set { } } + + public bool RibbonChampionAlola { get => this.GetRibbonIndex(ChampionAlola); set => this.SetRibbonIndex(ChampionAlola, value); } + public bool RibbonBattleRoyale { get => this.GetRibbonIndex(BattleRoyale); set => this.SetRibbonIndex(BattleRoyale, value); } + public bool RibbonBattleTreeGreat { get => this.GetRibbonIndex(BattleTreeGreat); set => this.SetRibbonIndex(BattleTreeGreat, value); } + public bool RibbonBattleTreeMaster { get => this.GetRibbonIndex(BattleTreeMaster); set => this.SetRibbonIndex(BattleTreeMaster, value); } + public bool RibbonChampionGalar { get => this.GetRibbonIndex(ChampionGalar); set => this.SetRibbonIndex(ChampionGalar, value); } + public bool RibbonTowerMaster { get => this.GetRibbonIndex(TowerMaster); set => this.SetRibbonIndex(TowerMaster, value); } + public bool RibbonMasterRank { get => this.GetRibbonIndex(MasterRank); set => this.SetRibbonIndex(MasterRank, value); } + public bool RibbonMarkLunchtime { get => this.GetRibbonIndex(MarkLunchtime); set => this.SetRibbonIndex(MarkLunchtime, value); } + public bool RibbonMarkSleepyTime { get => this.GetRibbonIndex(MarkSleepyTime); set => this.SetRibbonIndex(MarkSleepyTime, value); } + public bool RibbonMarkDusk { get => this.GetRibbonIndex(MarkDusk); set => this.SetRibbonIndex(MarkDusk, value); } + public bool RibbonMarkDawn { get => this.GetRibbonIndex(MarkDawn); set => this.SetRibbonIndex(MarkDawn, value); } + public bool RibbonMarkCloudy { get => this.GetRibbonIndex(MarkCloudy); set => this.SetRibbonIndex(MarkCloudy, value); } + public bool RibbonMarkRainy { get => this.GetRibbonIndex(MarkRainy); set => this.SetRibbonIndex(MarkRainy, value); } + public bool RibbonMarkStormy { get => this.GetRibbonIndex(MarkStormy); set => this.SetRibbonIndex(MarkStormy, value); } + public bool RibbonMarkSnowy { get => this.GetRibbonIndex(MarkSnowy); set => this.SetRibbonIndex(MarkSnowy, value); } + public bool RibbonMarkBlizzard { get => this.GetRibbonIndex(MarkBlizzard); set => this.SetRibbonIndex(MarkBlizzard, value); } + public bool RibbonMarkDry { get => this.GetRibbonIndex(MarkDry); set => this.SetRibbonIndex(MarkDry, value); } + public bool RibbonMarkSandstorm { get => this.GetRibbonIndex(MarkSandstorm); set => this.SetRibbonIndex(MarkSandstorm, value); } + public bool RibbonMarkMisty { get => this.GetRibbonIndex(MarkMisty); set => this.SetRibbonIndex(MarkMisty, value); } + public bool RibbonMarkDestiny { get => this.GetRibbonIndex(MarkDestiny); set => this.SetRibbonIndex(MarkDestiny, value); } + public bool RibbonMarkFishing { get => this.GetRibbonIndex(MarkFishing); set => this.SetRibbonIndex(MarkFishing, value); } + public bool RibbonMarkCurry { get => this.GetRibbonIndex(MarkCurry); set => this.SetRibbonIndex(MarkCurry, value); } + public bool RibbonMarkUncommon { get => this.GetRibbonIndex(MarkUncommon); set => this.SetRibbonIndex(MarkUncommon, value); } + public bool RibbonMarkRare { get => this.GetRibbonIndex(MarkRare); set => this.SetRibbonIndex(MarkRare, value); } + public bool RibbonMarkRowdy { get => this.GetRibbonIndex(MarkRowdy); set => this.SetRibbonIndex(MarkRowdy, value); } + public bool RibbonMarkAbsentMinded { get => this.GetRibbonIndex(MarkAbsentMinded); set => this.SetRibbonIndex(MarkAbsentMinded, value); } + public bool RibbonMarkJittery { get => this.GetRibbonIndex(MarkJittery); set => this.SetRibbonIndex(MarkJittery, value); } + public bool RibbonMarkExcited { get => this.GetRibbonIndex(MarkExcited); set => this.SetRibbonIndex(MarkExcited, value); } + public bool RibbonMarkCharismatic { get => this.GetRibbonIndex(MarkCharismatic); set => this.SetRibbonIndex(MarkCharismatic, value); } + public bool RibbonMarkCalmness { get => this.GetRibbonIndex(MarkCalmness); set => this.SetRibbonIndex(MarkCalmness, value); } + public bool RibbonMarkIntense { get => this.GetRibbonIndex(MarkIntense); set => this.SetRibbonIndex(MarkIntense, value); } + public bool RibbonMarkZonedOut { get => this.GetRibbonIndex(MarkZonedOut); set => this.SetRibbonIndex(MarkZonedOut, value); } + public bool RibbonMarkJoyful { get => this.GetRibbonIndex(MarkJoyful); set => this.SetRibbonIndex(MarkJoyful, value); } + public bool RibbonMarkAngry { get => this.GetRibbonIndex(MarkAngry); set => this.SetRibbonIndex(MarkAngry, value); } + public bool RibbonMarkSmiley { get => this.GetRibbonIndex(MarkSmiley); set => this.SetRibbonIndex(MarkSmiley, value); } + public bool RibbonMarkTeary { get => this.GetRibbonIndex(MarkTeary); set => this.SetRibbonIndex(MarkTeary, value); } + public bool RibbonMarkUpbeat { get => this.GetRibbonIndex(MarkUpbeat); set => this.SetRibbonIndex(MarkUpbeat, value); } + public bool RibbonMarkPeeved { get => this.GetRibbonIndex(MarkPeeved); set => this.SetRibbonIndex(MarkPeeved, value); } + public bool RibbonMarkIntellectual { get => this.GetRibbonIndex(MarkIntellectual); set => this.SetRibbonIndex(MarkIntellectual, value); } + public bool RibbonMarkFerocious { get => this.GetRibbonIndex(MarkFerocious); set => this.SetRibbonIndex(MarkFerocious, value); } + public bool RibbonMarkCrafty { get => this.GetRibbonIndex(MarkCrafty); set => this.SetRibbonIndex(MarkCrafty, value); } + public bool RibbonMarkScowling { get => this.GetRibbonIndex(MarkScowling); set => this.SetRibbonIndex(MarkScowling, value); } + public bool RibbonMarkKindly { get => this.GetRibbonIndex(MarkKindly); set => this.SetRibbonIndex(MarkKindly, value); } + public bool RibbonMarkFlustered { get => this.GetRibbonIndex(MarkFlustered); set => this.SetRibbonIndex(MarkFlustered, value); } + public bool RibbonMarkPumpedUp { get => this.GetRibbonIndex(MarkPumpedUp); set => this.SetRibbonIndex(MarkPumpedUp, value); } + public bool RibbonMarkZeroEnergy { get => this.GetRibbonIndex(MarkZeroEnergy); set => this.SetRibbonIndex(MarkZeroEnergy, value); } + public bool RibbonMarkPrideful { get => this.GetRibbonIndex(MarkPrideful); set => this.SetRibbonIndex(MarkPrideful, value); } + public bool RibbonMarkUnsure { get => this.GetRibbonIndex(MarkUnsure); set => this.SetRibbonIndex(MarkUnsure, value); } + public bool RibbonMarkHumble { get => this.GetRibbonIndex(MarkHumble); set => this.SetRibbonIndex(MarkHumble, value); } + public bool RibbonMarkThorny { get => this.GetRibbonIndex(MarkThorny); set => this.SetRibbonIndex(MarkThorny, value); } + public bool RibbonMarkVigor { get => this.GetRibbonIndex(MarkVigor); set => this.SetRibbonIndex(MarkVigor, value); } + public bool RibbonMarkSlump { get => this.GetRibbonIndex(MarkSlump); set => this.SetRibbonIndex(MarkSlump, value); } + public bool RibbonTwinklingStar { get => this.GetRibbonIndex(TwinklingStar); set => this.SetRibbonIndex(TwinklingStar, value); } + public bool RibbonPioneer { get => this.GetRibbonIndex(Pioneer); set => this.SetRibbonIndex(Pioneer, value); } + + public int GetRibbonByte(int index) => Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z == index); + public bool GetRibbon(int index) => GetRibbonByte(index) >= 0; + + public void SetRibbon(int index, bool value = true) + { + if ((uint)index > (uint)MarkSlump) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (value) + { + if (GetRibbon(index)) + return; + var openIndex = Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z != RibbonByteNone); + if (openIndex < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + SetRibbonAtIndex(openIndex, (byte)index); + } + else + { + var ofs = GetRibbonByte(index); + if (ofs < 0) + return; + SetRibbonAtIndex(ofs, RibbonByteNone); + } + } + #endregion } diff --git a/PKHeX.Core/MysteryGifts/WC3.cs b/PKHeX.Core/MysteryGifts/WC3.cs index 2338c7cad..9b8ff74bc 100644 --- a/PKHeX.Core/MysteryGifts/WC3.cs +++ b/PKHeX.Core/MysteryGifts/WC3.cs @@ -1,294 +1,293 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 Mystery Gift Template File +/// +/// +/// This is fabricated data built to emulate the future generation Mystery Gift objects. +/// Data here is not stored in any save file and cannot be naturally exported. +/// +public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate { + public override MysteryGift Clone() => (WC3)MemberwiseClone(); + /// - /// Generation 3 Mystery Gift Template File + /// Matched Type /// - /// - /// This is fabricated data built to emulate the future generation Mystery Gift objects. - /// Data here is not stored in any save file and cannot be naturally exported. - /// - public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate + public PIDType Method; + + public override string OT_Name { get; set; } = string.Empty; + public int OT_Gender { get; init; } = 3; + public override int TID { get; set; } + public override int SID { get; set; } + public override int Location { get; set; } = 255; + public override int EggLocation { get => 0; set {} } + public override GameVersion Version { get; set; } + public int Language { get; init; } = -1; + public override int Species { get; set; } + public override bool IsEgg { get; set; } + public override IReadOnlyList Moves { get; set; } = Array.Empty(); + public bool NotDistributed { get; init; } + public override Shiny Shiny { get; init; } + public bool Fateful { get; init; } // Obedience Flag + + // Mystery Gift Properties + public override int Generation => 3; + public override byte Level { get; set; } + public override int Ball { get; set; } = 4; + public override bool IsShiny => Shiny == Shiny.Always; + public override bool HasFixedIVs => false; + public bool RibbonEarth { get; set; } + public bool RibbonNational { get; set; } + public bool RibbonCountry { get; set; } + public bool RibbonChampionBattle { get; set; } + public bool RibbonChampionRegional { get; set; } + public bool RibbonChampionNational { get; set; } + + public string? Nickname { get; set; } + + // Description + public override string CardTitle { get; set; } = "Generation 3 Event"; + public override string CardHeader => CardTitle; + + // Unused + public override bool GiftUsed { get; set; } + public override int CardID { get; set; } + public override bool IsItem { get; set; } + public override int ItemID { get; set; } + public override bool IsEntity { get; set; } = true; + public override bool Empty => false; + public override int Gender { get; set; } + public override int Form { get; set; } + + // Synthetic + private readonly int? _metLevel; + + public int Met_Level { - public override MysteryGift Clone() => (WC3)MemberwiseClone(); + get => _metLevel ?? (IsEgg ? 0 : Level); + init => _metLevel = value; + } - /// - /// Matched Type - /// - public PIDType Method; + public override AbilityPermission Ability => AbilityPermission.Any12; - public override string OT_Name { get; set; } = string.Empty; - public int OT_Gender { get; init; } = 3; - public override int TID { get; set; } - public override int SID { get; set; } - public override int Location { get; set; } = 255; - public override int EggLocation { get => 0; set {} } - public override GameVersion Version { get; set; } - public int Language { get; init; } = -1; - public override int Species { get; set; } - public override bool IsEgg { get; set; } - public override IReadOnlyList Moves { get; set; } = Array.Empty(); - public bool NotDistributed { get; init; } - public override Shiny Shiny { get; init; } - public bool Fateful { get; init; } // Obedience Flag - - // Mystery Gift Properties - public override int Generation => 3; - public override byte Level { get; set; } - public override int Ball { get; set; } = 4; - public override bool IsShiny => Shiny == Shiny.Always; - public override bool HasFixedIVs => false; - public bool RibbonEarth { get; set; } - public bool RibbonNational { get; set; } - public bool RibbonCountry { get; set; } - public bool RibbonChampionBattle { get; set; } - public bool RibbonChampionRegional { get; set; } - public bool RibbonChampionNational { get; set; } - - public string? Nickname { get; set; } - - // Description - public override string CardTitle { get; set; } = "Generation 3 Event"; - public override string CardHeader => CardTitle; - - // Unused - public override bool GiftUsed { get; set; } - public override int CardID { get; set; } - public override bool IsItem { get; set; } - public override int ItemID { get; set; } - public override bool IsEntity { get; set; } = true; - public override bool Empty => false; - public override int Gender { get; set; } - public override int Form { get; set; } - - // Synthetic - private readonly int? _metLevel; - - public int Met_Level + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + PK3 pk = new() { - get => _metLevel ?? (IsEgg ? 0 : Level); - init => _metLevel = value; - } + Species = Species, + Met_Level = Met_Level, + Met_Location = Location, + Ball = 4, - public override AbilityPermission Ability => AbilityPermission.Any12; + // Ribbons + RibbonCountry = RibbonCountry, + RibbonNational = RibbonNational, + RibbonEarth = RibbonEarth, + RibbonChampionBattle = RibbonChampionBattle, + RibbonChampionRegional = RibbonChampionRegional, + RibbonChampionNational = RibbonChampionNational, - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - PK3 pk = new() - { - Species = Species, - Met_Level = Met_Level, - Met_Location = Location, - Ball = 4, - - // Ribbons - RibbonCountry = RibbonCountry, - RibbonNational = RibbonNational, - RibbonEarth = RibbonEarth, - RibbonChampionBattle = RibbonChampionBattle, - RibbonChampionRegional = RibbonChampionRegional, - RibbonChampionNational = RibbonChampionNational, - - FatefulEncounter = Fateful, - Version = GetVersion(sav), - }; - pk.EXP = Experience.GetEXP(Level, pk.PersonalInfo.EXPGrowth); - SetMoves(pk); - - bool hatchedEgg = IsEgg && sav.Generation != 3; - if (hatchedEgg) - { - SetForceHatchDetails(pk, sav); - } - else - { - pk.OT_Gender = OT_Gender != 3 ? OT_Gender & 1 : sav.Gender; - pk.TID = TID; - pk.SID = SID; - - pk.Language = (int)GetSafeLanguage((LanguageID)sav.Language); - pk.OT_Name = !string.IsNullOrWhiteSpace(OT_Name) ? OT_Name : sav.OT; - if (IsEgg) - pk.IsEgg = true; // lang should be set to japanese already - } - pk.Nickname = Nickname ?? SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, 3); // will be set to Egg nickname if appropriate by PK3 setter - - var pi = pk.PersonalInfo; - pk.OT_Friendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; - - // Generate PIDIV - SetPINGA(pk, criteria); - pk.HeldItem = 0; // clear, only random for Jirachi (?), no loss - - if (Version == GameVersion.XD) - pk.FatefulEncounter = true; // pk3 is already converted from xk3 - - pk.RefreshChecksum(); - return pk; - } - - private void SetForceHatchDetails(PK3 pk, ITrainerInfo sav) - { - pk.Language = (int)GetSafeLanguageNotEgg((LanguageID)sav.Language); - pk.OT_Name = sav.OT; - // ugly workaround for character table interactions - if (string.IsNullOrWhiteSpace(pk.OT_Name)) - { - pk.Language = (int)LanguageID.English; - pk.OT_Name = "PKHeX"; - } - - pk.OT_Gender = sav.Gender; - pk.TID = sav.TID; - pk.SID = sav.SID; - pk.Met_Location = pk.FRLG ? 146 /* Four Island */ : 32; // Route 117 - pk.FatefulEncounter &= pk.FRLG; // clear flag for RSE - pk.Met_Level = 0; // hatched - } - - private int GetVersion(ITrainerInfo sav) - { - if (Version != 0) - return (int) GetRandomVersion(Version); - bool gen3 = sav.Game <= 15 && GameVersion.Gen3.Contains((GameVersion)sav.Game); - return gen3 ? sav.Game : (int)GameVersion.R; - } - - private void SetMoves(PK3 pk) - { - if (Moves.Count == 0) // not completely defined - Moves = MoveList.GetBaseEggMoves(pk, Species, Form, (GameVersion)pk.Version, Level); - if (Moves.Count != 4) - { - int[] moves = Moves.ToArray(); - Array.Resize(ref moves, 4); - Moves = moves; - } - - var m = (int[])Moves; - pk.SetMoves(m); - pk.SetMaximumPPCurrent(m); - } - - private void SetPINGA(PK3 pk, EncounterCriteria _) - { - var seed = Util.Rand32(); - seed = TID == 06930 ? MystryMew.GetSeed(seed, Method) : GetSaneSeed(seed); - PIDGenerator.SetValuesFromSeed(pk, Method, seed); - } - - private uint GetSaneSeed(uint seed) => Method switch - { - PIDType.BACD_R => seed & 0x0000FFFF, // u16 - PIDType.BACD_R_S => seed & 0x000000FF, // u8 - _ => seed, + FatefulEncounter = Fateful, + Version = GetVersion(tr), }; + pk.EXP = Experience.GetEXP(Level, pk.PersonalInfo.EXPGrowth); + SetMoves(pk); - private LanguageID GetSafeLanguage(LanguageID hatchLang) + bool hatchedEgg = IsEgg && tr.Generation != 3; + if (hatchedEgg) { + SetForceHatchDetails(pk, tr); + } + else + { + pk.OT_Gender = OT_Gender != 3 ? OT_Gender & 1 : tr.Gender; + pk.TID = TID; + pk.SID = SID; + + pk.Language = (int)GetSafeLanguage((LanguageID)tr.Language); + pk.OT_Name = !string.IsNullOrWhiteSpace(OT_Name) ? OT_Name : tr.OT; if (IsEgg) - return LanguageID.Japanese; - return GetSafeLanguageNotEgg(hatchLang); + pk.IsEgg = true; // lang should be set to japanese already + } + pk.Nickname = Nickname ?? SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, 3); // will be set to Egg nickname if appropriate by PK3 setter + + var pi = pk.PersonalInfo; + pk.OT_Friendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; + + // Generate PIDIV + SetPINGA(pk, criteria); + pk.HeldItem = 0; // clear, only random for Jirachi (?), no loss + + if (Version == GameVersion.XD) + pk.FatefulEncounter = true; // pk3 is already converted from xk3 + + pk.RefreshChecksum(); + return pk; + } + + private void SetForceHatchDetails(PK3 pk, ITrainerInfo sav) + { + pk.Language = (int)GetSafeLanguageNotEgg((LanguageID)sav.Language); + pk.OT_Name = sav.OT; + // ugly workaround for character table interactions + if (string.IsNullOrWhiteSpace(pk.OT_Name)) + { + pk.Language = (int)LanguageID.English; + pk.OT_Name = "PKHeX"; } - private LanguageID GetSafeLanguageNotEgg(LanguageID hatchLang) + pk.OT_Gender = sav.Gender; + pk.TID = sav.TID; + pk.SID = sav.SID; + pk.Met_Location = pk.FRLG ? 146 /* Four Island */ : 32; // Route 117 + pk.FatefulEncounter &= pk.FRLG; // clear flag for RSE + pk.Met_Level = 0; // hatched + } + + private int GetVersion(ITrainerInfo sav) + { + if (Version != 0) + return (int) GetRandomVersion(Version); + bool gen3 = sav.Game <= 15 && GameVersion.Gen3.Contains((GameVersion)sav.Game); + return gen3 ? sav.Game : (int)GameVersion.R; + } + + private void SetMoves(PK3 pk) + { + if (Moves.Count == 0) // not completely defined + Moves = MoveList.GetBaseEggMoves(pk, Species, Form, (GameVersion)pk.Version, Level); + if (Moves.Count != 4) { - if (Language != -1) - return (LanguageID) Language; - if (hatchLang < LanguageID.Korean && hatchLang != LanguageID.Hacked) - return hatchLang; - return LanguageID.English; // fallback + int[] moves = Moves.ToArray(); + Array.Resize(ref moves, 4); + Moves = moves; } - private static GameVersion GetRandomVersion(GameVersion version) - { - if (version is <= GameVersion.CXD and > 0) // single game - return version; + var m = (int[])Moves; + pk.SetMoves(m); + pk.SetMaximumPPCurrent(m); + } - return version switch + private void SetPINGA(PK3 pk, EncounterCriteria _) + { + var seed = Util.Rand32(); + seed = TID == 06930 ? MystryMew.GetSeed(seed, Method) : GetSaneSeed(seed); + PIDGenerator.SetValuesFromSeed(pk, Method, seed); + } + + private uint GetSaneSeed(uint seed) => Method switch + { + PIDType.BACD_R => seed & 0x0000FFFF, // u16 + PIDType.BACD_R_S => seed & 0x000000FF, // u8 + _ => seed, + }; + + private LanguageID GetSafeLanguage(LanguageID hatchLang) + { + if (IsEgg) + return LanguageID.Japanese; + return GetSafeLanguageNotEgg(hatchLang); + } + + private LanguageID GetSafeLanguageNotEgg(LanguageID hatchLang) + { + if (Language != -1) + return (LanguageID) Language; + if (hatchLang < LanguageID.Korean && hatchLang != LanguageID.Hacked) + return hatchLang; + return LanguageID.English; // fallback + } + + private static GameVersion GetRandomVersion(GameVersion version) + { + if (version is <= GameVersion.CXD and > 0) // single game + return version; + + return version switch + { + GameVersion.FRLG => GameVersion.FR + Util.Rand.Next(2), // or LG + GameVersion.RS or GameVersion.RSE => GameVersion.S + Util.Rand.Next(2), // or R + GameVersion.COLO or GameVersion.XD => GameVersion.CXD, + _ => throw new Exception($"Unknown GameVersion: {version}"), + }; + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + // Gen3 Version MUST match. + if (Version != 0 && !Version.Contains((GameVersion)pk.Version)) + return false; + + bool hatchedEgg = IsEgg && !pk.IsEgg; + if (!hatchedEgg) + { + if (SID != -1 && SID != pk.SID) return false; + if (TID != -1 && TID != pk.TID) return false; + if (OT_Gender < 3 && OT_Gender != pk.OT_Gender) return false; + var wcOT = OT_Name; + if (!string.IsNullOrEmpty(wcOT)) { - GameVersion.FRLG => GameVersion.FR + Util.Rand.Next(2), // or LG - GameVersion.RS or GameVersion.RSE => GameVersion.S + Util.Rand.Next(2), // or R - GameVersion.COLO or GameVersion.XD => GameVersion.CXD, - _ => throw new Exception($"Unknown GameVersion: {version}"), - }; - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - // Gen3 Version MUST match. - if (Version != 0 && !Version.Contains((GameVersion)pkm.Version)) - return false; - - bool hatchedEgg = IsEgg && !pkm.IsEgg; - if (!hatchedEgg) - { - if (SID != -1 && SID != pkm.SID) return false; - if (TID != -1 && TID != pkm.TID) return false; - if (OT_Gender < 3 && OT_Gender != pkm.OT_Gender) return false; - var wcOT = OT_Name; - if (!string.IsNullOrEmpty(wcOT)) + if (wcOT.Length > 7) // Colosseum MATTLE Ho-Oh { - if (wcOT.Length > 7) // Colosseum MATTLE Ho-Oh - { - if (!GetIsValidOTMattleHoOh(wcOT, pkm.OT_Name, pkm is CK3)) - return false; - } - else if (wcOT != pkm.OT_Name) - { + if (!GetIsValidOTMattleHoOh(wcOT, pk.OT_Name, pk is CK3)) return false; - } + } + else if (wcOT != pk.OT_Name) + { + return false; } } - - if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pkm.Form, pkm.Format)) - return false; - - if (Language != -1 && Language != pkm.Language) return false; - if (Ball != pkm.Ball) return false; - if (Fateful != pkm.FatefulEncounter) - { - // XD Gifts only at level 20 get flagged after transfer - if (Version == GameVersion.XD != pkm is XK3) - return false; - } - - if (pkm.IsNative) - { - if (hatchedEgg) - return true; // defer egg specific checks to later. - if (Met_Level != pkm.Met_Level) - return false; - if (Location != pkm.Met_Location) - return false; - } - else - { - if (pkm.IsEgg) - return false; - if (Level > pkm.Met_Level) - return false; - } - return true; } - private static bool GetIsValidOTMattleHoOh(string wc, string ot, bool ck3) + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, pk.Format)) + return false; + + if (Language != -1 && Language != pk.Language) return false; + if (Ball != pk.Ball) return false; + if (Fateful != pk.FatefulEncounter) { - if (ck3) // match original if still ck3, otherwise must be truncated 7char - return wc == ot; - return ot.Length == 7 && wc.StartsWith(ot); + // XD Gifts only at level 20 get flagged after transfer + if (Version == GameVersion.XD != (pk is XK3)) + return false; } - protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species; - protected override bool IsMatchPartial(PKM pkm) => false; - - public string GetNickname(int language) => Nickname ?? string.Empty; - public bool GetIsNicknamed(int language) => Nickname != null; - public bool CanBeAnyLanguage() => false; - public bool CanHaveLanguage(int language) => Language == language; - public bool CanHandleOT(int language) => IsEgg; + if (pk.IsNative) + { + if (hatchedEgg) + return true; // defer egg specific checks to later. + if (Met_Level != pk.Met_Level) + return false; + if (Location != pk.Met_Location) + return false; + } + else + { + if (pk.IsEgg) + return false; + if (Level > pk.Met_Level) + return false; + } + return true; } + + private static bool GetIsValidOTMattleHoOh(string wc, string ot, bool ck3) + { + if (ck3) // match original if still ck3, otherwise must be truncated 7char + return wc == ot; + return ot.Length == 7 && wc.StartsWith(ot, StringComparison.Ordinal); + } + + protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchPartial(PKM pk) => false; + + public string GetNickname(int language) => Nickname ?? string.Empty; + public bool GetIsNicknamed(int language) => Nickname != null; + public bool CanBeAnyLanguage() => false; + public bool CanHaveLanguage(int language) => Language == language; + public bool CanHandleOT(int language) => IsEgg; } diff --git a/PKHeX.Core/MysteryGifts/WC6.cs b/PKHeX.Core/MysteryGifts/WC6.cs index 936dd89fe..0cb393ad3 100644 --- a/PKHeX.Core/MysteryGifts/WC6.cs +++ b/PKHeX.Core/MysteryGifts/WC6.cs @@ -1,598 +1,597 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 6 Mystery Gift Template File +/// +public sealed class WC6 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, IContestStatsMutable, INature, IMemoryOT { - /// - /// Generation 6 Mystery Gift Template File - /// - public sealed class WC6 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, IContestStatsMutable, INature, IMemoryOT + public const int Size = 0x108; + public const uint EonTicketConst = 0x225D73C2; + public override int Generation => 6; + + public WC6() : this(new byte[Size]) { } + public WC6(byte[] data) : base(data) { } + + public int RestrictLanguage { get; set; } // None + public byte RestrictVersion { get; set; } // Permit All + + public bool CanBeReceivedByVersion(int v) { - public const int Size = 0x108; - public const uint EonTicketConst = 0x225D73C2; - public override int Generation => 6; - - public WC6() : this(new byte[Size]) { } - public WC6(byte[] data) : base(data) { } - - public int RestrictLanguage { get; set; } // None - public byte RestrictVersion { get; set; } // Permit All - - public bool CanBeReceivedByVersion(int v) - { - if (v is < (int)GameVersion.X or > (int)GameVersion.OR) - return false; - if (RestrictVersion == 0) - return true; // no data - var bitIndex = v - (int) GameVersion.X; - var bit = 1 << bitIndex; - return (RestrictVersion & bit) != 0; - } - - // General Card Properties - public override int CardID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0)); - set => WriteUInt16LittleEndian(Data.AsSpan(0), (ushort)value); - } - - public override string CardTitle - { - // Max len 36 char, followed by null terminator - get => StringConverter6.GetString(Data.AsSpan(2, 0x4A)); - set => StringConverter6.SetString(Data.AsSpan(2, 0x4A), value.AsSpan(), 36, StringConverterOption.ClearZero); - } - - internal uint RawDate - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x4C)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x4C), value); - } - - private uint Year - { - get => RawDate / 10000; - set => RawDate = SetDate(value, Month, Day); - } - - private uint Month - { - get => RawDate % 10000 / 100; - set => RawDate = SetDate(Year, value, Day); - } - - private uint Day - { - get => RawDate % 100; - set => RawDate = SetDate(Year, Month, value); - } - - public static uint SetDate(uint year, uint month, uint day) => (year * 10000) + (month * 100) + day; - - /// - /// Gets or sets the date of the card. - /// - public DateTime? Date - { - get - { - // Check to see if date is valid - if (!DateUtil.IsDateValid(Year, Month, Day)) - return null; - - return new DateTime((int)Year, (int)Month, (int)Day); - } - set - { - if (value.HasValue) - { - // Only update the properties if a value is provided. - Year = (ushort)value.Value.Year; - Month = (byte)value.Value.Month; - Day = (byte)value.Value.Day; - } - else - { - // Clear the Met Date. - // If code tries to access MetDate again, null will be returned. - Year = 0; - Month = 0; - Day = 0; - } - } - } - - public int CardLocation { get => Data[0x50]; set => Data[0x50] = (byte)value; } - - public int CardType { get => Data[0x51]; set => Data[0x51] = (byte)value; } - public override bool GiftUsed { get => Data[0x52] >> 1 > 0; set => Data[0x52] = (byte)((Data[0x52] & ~2) | (value ? 2 : 0)); } - public bool MultiObtain { get => Data[0x53] == 1; set => Data[0x53] = value ? (byte)1 : (byte)0; } - - // Item Properties - public override bool IsItem { get => CardType == 1; set { if (value) CardType = 1; } } - - public override int ItemID { - get => ReadUInt16LittleEndian(Data.AsSpan(0x68)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x68), (ushort)value); } - - public override int Quantity { - get => ReadUInt16LittleEndian(Data.AsSpan(0x70)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x70), (ushort)value); } - - // Pokémon Properties - public override bool IsEntity { get => CardType == 0; set { if (value) CardType = 0; } } - public override bool IsShiny => Shiny.IsShiny(); - - public override Shiny Shiny => IsEgg ? Shiny.Random : PIDType switch - { - ShinyType6.FixedValue => GetShinyXor() switch - { - 0 => Shiny.AlwaysSquare, - <= 15 => Shiny.AlwaysStar, - _ => Shiny.Never, - }, - ShinyType6.Random => Shiny.Random, - ShinyType6.Never => Shiny.Never, - ShinyType6.Always => Shiny.Always, - _ => throw new ArgumentOutOfRangeException(), - }; - - private int GetShinyXor() - { - // Player owned anti-shiny fixed PID - if (TID == 0 && SID == 0) - return int.MaxValue; - - var pid = PID; - var psv = (int)(pid >> 16 ^ (pid & 0xFFFF)); - var tsv = (TID ^ SID); - return psv ^ tsv; - } - - public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x68)); set => WriteUInt16LittleEndian(Data.AsSpan(0x68), (ushort)value); } - public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); } - public int OriginGame { get => Data[0x6C]; set => Data[0x6C] = (byte)value; } - public uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(0x70)); set => WriteUInt32LittleEndian(Data.AsSpan(0x70), value); } - public override int Ball { get => Data[0x76]; set => Data[0x76] = (byte)value; } - public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x78)); set => WriteUInt16LittleEndian(Data.AsSpan(0x78), (ushort)value); } - - public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7A), (ushort)value); } - public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7C), (ushort)value); } - public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7E), (ushort)value); } - public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x80)); set => WriteUInt16LittleEndian(Data.AsSpan(0x80), (ushort)value); } - - public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x82)); set => WriteUInt16LittleEndian(Data.AsSpan(0x82), (ushort)value); } - public override int Form { get => Data[0x84]; set => Data[0x84] = (byte)value; } - public int Language { get => Data[0x85]; set => Data[0x85] = (byte)value; } - - public string Nickname - { - get => StringConverter6.GetString(Data.AsSpan(0x86, 0x1A)); - set => StringConverter6.SetString(Data.AsSpan(0x86, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - } - - public int Nature { get => (sbyte)Data[0xA0]; set => Data[0xA0] = (byte)value; } - public override int Gender { get => Data[0xA1]; set => Data[0xA1] = (byte)value; } - public override int AbilityType { get => Data[0xA2]; set => Data[0xA2] = (byte)value; } - public ShinyType6 PIDType { get => (ShinyType6)Data[0xA3]; set => Data[0xA3] = (byte)value; } - public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0xA4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA4), (ushort)value); } - public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0xA6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA6), (ushort)value); } - - public byte CNT_Cool { get => Data[0xA9]; set => Data[0xA9] = value; } - public byte CNT_Beauty { get => Data[0xAA]; set => Data[0xAA] = value; } - public byte CNT_Cute { get => Data[0xAB]; set => Data[0xAB] = value; } - public byte CNT_Smart { get => Data[0xAC]; set => Data[0xAC] = value; } - public byte CNT_Tough { get => Data[0xAD]; set => Data[0xAD] = value; } - public byte CNT_Sheen { get => Data[0xAE]; set => Data[0xAE] = value; } - - public int IV_HP { get => Data[0xAF]; set => Data[0xAF] = (byte)value; } - public int IV_ATK { get => Data[0xB0]; set => Data[0xB0] = (byte)value; } - public int IV_DEF { get => Data[0xB1]; set => Data[0xB1] = (byte)value; } - public int IV_SPE { get => Data[0xB2]; set => Data[0xB2] = (byte)value; } - public int IV_SPA { get => Data[0xB3]; set => Data[0xB3] = (byte)value; } - public int IV_SPD { get => Data[0xB4]; set => Data[0xB4] = (byte)value; } - - public int OTGender { get => Data[0xB5]; set => Data[0xB5] = (byte)value; } - - public override string OT_Name - { - get => StringConverter6.GetString(Data.AsSpan(0xB6, 0x1A)); - set => StringConverter6.SetString(Data.AsSpan(0xB6, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - } - - public override byte Level { get => Data[0xD0]; set => Data[0xD0] = value; } - public override bool IsEgg { get => Data[0xD1] == 1; set => Data[0xD1] = value ? (byte)1 : (byte)0; } - public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0xD4)); set => WriteUInt32LittleEndian(Data.AsSpan(0xD4), value); } - - public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), (ushort)value); } - public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), (ushort)value); } - public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDC), (ushort)value); } - public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDE), (ushort)value); } - - public byte OT_Intensity { get => Data[0xE0]; set => Data[0xE0] = value; } - public byte OT_Memory { get => Data[0xE1]; set => Data[0xE1] = value; } - public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xE2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xE2), value); } - public byte OT_Feeling { get => Data[0xE4]; set => Data[0xE4] = value; } - - public int EV_HP { get => Data[0xE5]; set => Data[0xE5] = (byte)value; } - public int EV_ATK { get => Data[0xE6]; set => Data[0xE6] = (byte)value; } - public int EV_DEF { get => Data[0xE7]; set => Data[0xE7] = (byte)value; } - public int EV_SPE { get => Data[0xE8]; set => Data[0xE8] = (byte)value; } - public int EV_SPA { get => Data[0xE9]; set => Data[0xE9] = (byte)value; } - public int EV_SPD { get => Data[0xEA]; set => Data[0xEA] = (byte)value; } - - private byte RIB0 { get => Data[0x74]; set => Data[0x74] = value; } - private byte RIB1 { get => Data[0x75]; set => Data[0x75] = value; } - public bool RibbonChampionBattle { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonChampionRegional { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonChampionNational { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonCountry { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonNational { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonEarth { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonEvent { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonChampionWorld { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonBirthday { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonSpecial { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonSouvenir { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonWishing { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonClassic { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonPremier { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RIB1_7 { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - - // Meta Accessible Properties - public override int[] IVs - { - get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; - set - { - if (value.Length != 6) return; - IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; - IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; - } - } - - public int[] EVs - { - get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; - set - { - if (value.Length != 6) return; - EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; - EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; - } - } - - public bool IsNicknamed => Nickname.Length > 0; - public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } - - public override IReadOnlyList Moves - { - get => new[] { Move1, Move2, Move3, Move4 }; - set - { - if (value.Count > 0) Move1 = value[0]; - if (value.Count > 1) Move2 = value[1]; - if (value.Count > 2) Move3 = value[2]; - if (value.Count > 3) Move4 = value[3]; - } - } - - public override IReadOnlyList Relearn - { - get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; - set - { - if (value.Count > 0) RelearnMove1 = value[0]; - if (value.Count > 1) RelearnMove2 = value[1]; - if (value.Count > 2) RelearnMove3 = value[2]; - if (value.Count > 3) RelearnMove4 = value[3]; - } - } - - public bool IsLinkGift => MetLocation == Locations.LinkGift6; - - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - if (!IsEntity) - throw new ArgumentException(nameof(IsEntity)); - - var rnd = Util.Rand; - - int currentLevel = Level > 0 ? Level : rnd.Next(1, 101); - var pi = PersonalTable.AO.GetFormEntry(Species, Form); - PK6 pk = new() - { - Species = Species, - HeldItem = HeldItem, - TID = TID, - SID = SID, - Met_Level = currentLevel, - Form = Form, - EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(), - Version = OriginGame != 0 ? OriginGame : sav.Game, - Language = Language != 0 ? Language : sav.Language, - Ball = Ball, - Move1 = Move1, Move2 = Move2, Move3 = Move3, Move4 = Move4, - RelearnMove1 = RelearnMove1, RelearnMove2 = RelearnMove2, - RelearnMove3 = RelearnMove3, RelearnMove4 = RelearnMove4, - Met_Location = MetLocation, - Egg_Location = EggLocation, - CNT_Cool = CNT_Cool, - CNT_Beauty = CNT_Beauty, - CNT_Cute = CNT_Cute, - CNT_Smart = CNT_Smart, - CNT_Tough = CNT_Tough, - CNT_Sheen = CNT_Sheen, - - OT_Name = OT_Name.Length > 0 ? OT_Name : sav.OT, - OT_Gender = OTGender != 3 ? OTGender % 2 : sav.Gender, - HT_Name = OT_Name.Length > 0 ? sav.OT : string.Empty, - HT_Gender = OT_Name.Length > 0 ? sav.Gender : 0, - CurrentHandler = OT_Name.Length > 0 ? 1 : 0, - - EXP = Experience.GetEXP(Level, pi.EXPGrowth), - - // Ribbons - RibbonCountry = RibbonCountry, - RibbonNational = RibbonNational, - - RibbonEarth = RibbonEarth, - RibbonWorld = RibbonWorld, - RibbonClassic = RibbonClassic, - RibbonPremier = RibbonPremier, - RibbonEvent = RibbonEvent, - RibbonBirthday = RibbonBirthday, - RibbonSpecial = RibbonSpecial, - RibbonSouvenir = RibbonSouvenir, - - RibbonWishing = RibbonWishing, - RibbonChampionBattle = RibbonChampionBattle, - RibbonChampionRegional = RibbonChampionRegional, - RibbonChampionNational = RibbonChampionNational, - RibbonChampionWorld = RibbonChampionWorld, - - OT_Friendship = pi.BaseFriendship, - OT_Intensity = OT_Intensity, - OT_Memory = OT_Memory, - OT_TextVar = OT_TextVar, - OT_Feeling = OT_Feeling, - FatefulEncounter = !IsLinkGift, // Link gifts do not set fateful encounter - - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPE = EV_SPE, - EV_SPA = EV_SPA, - EV_SPD = EV_SPD, - }; - - if (sav is IRegionOrigin o) - { - pk.Country = o.Country; - pk.Region = o.Region; - pk.ConsoleRegion = o.ConsoleRegion; - } - else - { - pk.SetDefaultRegionOrigins(); - } - - pk.SetMaximumPPCurrent(); - - pk.MetDate = Date ?? DateTime.Now; - - if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version)) - { - // give random valid game - do { pk.Version = (int)GameVersion.X + rnd.Next(4); } - while (!CanBeReceivedByVersion(pk.Version)); - } - - if (!IsEgg) - { - if (pk.CurrentHandler == 0) // OT - { - pk.OT_Memory = 3; - pk.OT_TextVar = 9; - pk.OT_Intensity = 1; - pk.OT_Feeling = MemoryContext6.GetRandomFeeling6(pk.OT_Memory, 10); // 0-9 - } - else - { - pk.HT_Memory = 3; - pk.HT_TextVar = 9; - pk.HT_Intensity = 1; - pk.HT_Feeling = MemoryContext6.GetRandomFeeling6(pk.HT_Memory, 10); // 0-9 - pk.HT_Friendship = pk.OT_Friendship; - } - } - - pk.IsNicknamed = IsNicknamed; - pk.Nickname = IsNicknamed ? Nickname : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); - - SetPINGA(pk, criteria); - - if (IsEgg) - SetEggMetData(pk); - pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; - - pk.RefreshChecksum(); - return pk; - } - - private void SetEggMetData(PKM pk) - { - pk.IsEgg = true; - pk.EggMetDate = Date; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); - pk.IsNicknamed = true; - } - - private void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = PersonalTable.AO.GetFormEntry(Species, Form); - pk.Nature = (int)criteria.GetNature((Nature)Nature); - pk.Gender = criteria.GetGender(Gender, pi); - var av = GetAbilityIndex(criteria); - pk.RefreshAbility(av); - SetPID(pk); - SetIVs(pk); - } - - private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch - { - 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 - 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H - _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), - }; - - public override AbilityPermission Ability => AbilityType switch - { - 0 => AbilityPermission.OnlyFirst, - 1 => AbilityPermission.OnlySecond, - 2 => AbilityPermission.OnlyHidden, - 3 => AbilityPermission.Any12, - _ => AbilityPermission.Any12H, - }; - - private void SetPID(PKM pk) - { - switch (PIDType) - { - case ShinyType6.FixedValue: // Specified - pk.PID = PID; - break; - case ShinyType6.Random: // Random - pk.PID = Util.Rand32(); - break; - case ShinyType6.Always: // Random Shiny - pk.PID = Util.Rand32(); - pk.PID = (uint)(((pk.TID ^ pk.SID ^ (pk.PID & 0xFFFF)) << 16) | (pk.PID & 0xFFFF)); - break; - case ShinyType6.Never: // Random Nonshiny - pk.PID = Util.Rand32(); - if (pk.IsShiny) pk.PID ^= 0x10000000; - break; - } - } - - public override void GetIVs(Span value) - { - if (value.Length != 6) - return; - value[0] = IV_HP; - value[1] = IV_ATK; - value[2] = IV_DEF; - value[3] = IV_SPE; - value[4] = IV_SPA; - value[5] = IV_SPD; - } - - private void SetIVs(PKM pk) - { - Span finalIVs = stackalloc int[6]; - GetIVs(finalIVs); - var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); - var rng = Util.Rand; - if (ivflag == 0) // Random IVs - { - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] > 31) - finalIVs[i] = rng.Next(32); - } - } - else // 1/2/3 perfect IVs - { - int IVCount = ivflag - 0xFB; - do { finalIVs[rng.Next(6)] = 31; } - while (finalIVs.Count(31) < IVCount); - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] != 31) - finalIVs[i] = rng.Next(32); - } - } - pk.SetIVs(finalIVs); - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!IsEgg) - { - if (OTGender != 3) - { - // Skip ID check if ORASDEMO Simulated wc6 - if (CardID != 0) - { - if (SID != pkm.SID) return false; - if (TID != pkm.TID) return false; - } - if (OTGender != pkm.OT_Gender) return false; - } - if (!string.IsNullOrEmpty(OT_Name) && OT_Name != pkm.OT_Name) return false; - if (PIDType == ShinyType6.FixedValue && pkm.PID != PID) return false; - if (!Shiny.IsValid(pkm)) return false; - if (OriginGame != 0 && OriginGame != pkm.Version) return false; - if (EncryptionConstant != 0 && EncryptionConstant != pkm.EncryptionConstant) return false; - if (Language != 0 && Language != pkm.Language) return false; - } - if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pkm.Form, pkm.Format)) - return false; - - if (IsEgg) - { - if (EggLocation != pkm.Egg_Location) // traded - { - if (pkm.Egg_Location != Locations.LinkTrade6) - return false; - } - else if (PIDType == 0 && pkm.IsShiny) - { - return false; // can't be traded away for unshiny - } - - if (pkm.IsEgg && !pkm.IsNative) - return false; - } - else - { - if (!IsMatchEggLocation(pkm)) return false; - if (MetLocation != pkm.Met_Location) return false; - } - - if (Level != pkm.Met_Level) return false; - if (Ball != pkm.Ball) return false; - if (OTGender < 3 && OTGender != pkm.OT_Gender) return false; - if (Nature != -1 && pkm.Nature != Nature) return false; - if (Gender != 3 && Gender != pkm.Gender) return false; - - if (pkm is IContestStats s && s.IsContestBelow(this)) - return false; - - return true; - } - - protected override bool IsMatchDeferred(PKM pkm) - { - switch (CardID) - { - case 0525 when IV_HP == 0xFE: // Diancie was distributed with no IV enforcement & 3IVs - case 0504 when RibbonClassic != ((IRibbonSetEvent4)pkm).RibbonClassic: // Magmar with/without classic - return true; - } - return Species != pkm.Species; - } - - protected override bool IsMatchPartial(PKM pkm) - { - if (RestrictLanguage != 0 && RestrictLanguage != pkm.Language) - return true; - if (!CanBeReceivedByVersion(pkm.Version)) - return true; + if (v is < (int)GameVersion.X or > (int)GameVersion.OR) return false; + if (RestrictVersion == 0) + return true; // no data + var bitIndex = v - (int) GameVersion.X; + var bit = 1 << bitIndex; + return (RestrictVersion & bit) != 0; + } + + // General Card Properties + public override int CardID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0)); + set => WriteUInt16LittleEndian(Data.AsSpan(0), (ushort)value); + } + + public override string CardTitle + { + // Max len 36 char, followed by null terminator + get => StringConverter6.GetString(Data.AsSpan(2, 0x4A)); + set => StringConverter6.SetString(Data.AsSpan(2, 0x4A), value.AsSpan(), 36, StringConverterOption.ClearZero); + } + + internal uint RawDate + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x4C)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x4C), value); + } + + private uint Year + { + get => RawDate / 10000; + set => RawDate = SetDate(value, Month, Day); + } + + private uint Month + { + get => RawDate % 10000 / 100; + set => RawDate = SetDate(Year, value, Day); + } + + private uint Day + { + get => RawDate % 100; + set => RawDate = SetDate(Year, Month, value); + } + + public static uint SetDate(uint year, uint month, uint day) => (year * 10000) + (month * 100) + day; + + /// + /// Gets or sets the date of the card. + /// + public DateTime? Date + { + get + { + // Check to see if date is valid + if (!DateUtil.IsDateValid(Year, Month, Day)) + return null; + + return new DateTime((int)Year, (int)Month, (int)Day); + } + set + { + if (value.HasValue) + { + // Only update the properties if a value is provided. + Year = (ushort)value.Value.Year; + Month = (byte)value.Value.Month; + Day = (byte)value.Value.Day; + } + else + { + // Clear the Met Date. + // If code tries to access MetDate again, null will be returned. + Year = 0; + Month = 0; + Day = 0; + } } } + + public int CardLocation { get => Data[0x50]; set => Data[0x50] = (byte)value; } + + public int CardType { get => Data[0x51]; set => Data[0x51] = (byte)value; } + public override bool GiftUsed { get => Data[0x52] >> 1 > 0; set => Data[0x52] = (byte)((Data[0x52] & ~2) | (value ? 2 : 0)); } + public bool MultiObtain { get => Data[0x53] == 1; set => Data[0x53] = value ? (byte)1 : (byte)0; } + + // Item Properties + public override bool IsItem { get => CardType == 1; set { if (value) CardType = 1; } } + + public override int ItemID { + get => ReadUInt16LittleEndian(Data.AsSpan(0x68)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x68), (ushort)value); } + + public override int Quantity { + get => ReadUInt16LittleEndian(Data.AsSpan(0x70)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x70), (ushort)value); } + + // Pokémon Properties + public override bool IsEntity { get => CardType == 0; set { if (value) CardType = 0; } } + public override bool IsShiny => Shiny.IsShiny(); + + public override Shiny Shiny => IsEgg ? Shiny.Random : PIDType switch + { + ShinyType6.FixedValue => GetShinyXor() switch + { + 0 => Shiny.AlwaysSquare, + <= 15 => Shiny.AlwaysStar, + _ => Shiny.Never, + }, + ShinyType6.Random => Shiny.Random, + ShinyType6.Never => Shiny.Never, + ShinyType6.Always => Shiny.Always, + _ => throw new ArgumentOutOfRangeException(), + }; + + private int GetShinyXor() + { + // Player owned anti-shiny fixed PID + if (TID == 0 && SID == 0) + return int.MaxValue; + + var pid = PID; + var psv = (int)((pid >> 16) ^ (pid & 0xFFFF)); + var tsv = TID ^ SID; + return psv ^ tsv; + } + + public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x68)); set => WriteUInt16LittleEndian(Data.AsSpan(0x68), (ushort)value); } + public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); } + public int OriginGame { get => Data[0x6C]; set => Data[0x6C] = (byte)value; } + public uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(0x70)); set => WriteUInt32LittleEndian(Data.AsSpan(0x70), value); } + public override int Ball { get => Data[0x76]; set => Data[0x76] = (byte)value; } + public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x78)); set => WriteUInt16LittleEndian(Data.AsSpan(0x78), (ushort)value); } + + public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7A), (ushort)value); } + public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7C), (ushort)value); } + public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7E), (ushort)value); } + public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x80)); set => WriteUInt16LittleEndian(Data.AsSpan(0x80), (ushort)value); } + + public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x82)); set => WriteUInt16LittleEndian(Data.AsSpan(0x82), (ushort)value); } + public override int Form { get => Data[0x84]; set => Data[0x84] = (byte)value; } + public int Language { get => Data[0x85]; set => Data[0x85] = (byte)value; } + + public string Nickname + { + get => StringConverter6.GetString(Data.AsSpan(0x86, 0x1A)); + set => StringConverter6.SetString(Data.AsSpan(0x86, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + } + + public int Nature { get => (sbyte)Data[0xA0]; set => Data[0xA0] = (byte)value; } + public override int Gender { get => Data[0xA1]; set => Data[0xA1] = (byte)value; } + public override int AbilityType { get => Data[0xA2]; set => Data[0xA2] = (byte)value; } + public ShinyType6 PIDType { get => (ShinyType6)Data[0xA3]; set => Data[0xA3] = (byte)value; } + public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0xA4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA4), (ushort)value); } + public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0xA6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA6), (ushort)value); } + + public byte CNT_Cool { get => Data[0xA9]; set => Data[0xA9] = value; } + public byte CNT_Beauty { get => Data[0xAA]; set => Data[0xAA] = value; } + public byte CNT_Cute { get => Data[0xAB]; set => Data[0xAB] = value; } + public byte CNT_Smart { get => Data[0xAC]; set => Data[0xAC] = value; } + public byte CNT_Tough { get => Data[0xAD]; set => Data[0xAD] = value; } + public byte CNT_Sheen { get => Data[0xAE]; set => Data[0xAE] = value; } + + public int IV_HP { get => Data[0xAF]; set => Data[0xAF] = (byte)value; } + public int IV_ATK { get => Data[0xB0]; set => Data[0xB0] = (byte)value; } + public int IV_DEF { get => Data[0xB1]; set => Data[0xB1] = (byte)value; } + public int IV_SPE { get => Data[0xB2]; set => Data[0xB2] = (byte)value; } + public int IV_SPA { get => Data[0xB3]; set => Data[0xB3] = (byte)value; } + public int IV_SPD { get => Data[0xB4]; set => Data[0xB4] = (byte)value; } + + public int OTGender { get => Data[0xB5]; set => Data[0xB5] = (byte)value; } + + public override string OT_Name + { + get => StringConverter6.GetString(Data.AsSpan(0xB6, 0x1A)); + set => StringConverter6.SetString(Data.AsSpan(0xB6, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + } + + public override byte Level { get => Data[0xD0]; set => Data[0xD0] = value; } + public override bool IsEgg { get => Data[0xD1] == 1; set => Data[0xD1] = value ? (byte)1 : (byte)0; } + public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0xD4)); set => WriteUInt32LittleEndian(Data.AsSpan(0xD4), value); } + + public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), (ushort)value); } + public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), (ushort)value); } + public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDC), (ushort)value); } + public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDE), (ushort)value); } + + public byte OT_Intensity { get => Data[0xE0]; set => Data[0xE0] = value; } + public byte OT_Memory { get => Data[0xE1]; set => Data[0xE1] = value; } + public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xE2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xE2), value); } + public byte OT_Feeling { get => Data[0xE4]; set => Data[0xE4] = value; } + + public int EV_HP { get => Data[0xE5]; set => Data[0xE5] = (byte)value; } + public int EV_ATK { get => Data[0xE6]; set => Data[0xE6] = (byte)value; } + public int EV_DEF { get => Data[0xE7]; set => Data[0xE7] = (byte)value; } + public int EV_SPE { get => Data[0xE8]; set => Data[0xE8] = (byte)value; } + public int EV_SPA { get => Data[0xE9]; set => Data[0xE9] = (byte)value; } + public int EV_SPD { get => Data[0xEA]; set => Data[0xEA] = (byte)value; } + + private byte RIB0 { get => Data[0x74]; set => Data[0x74] = value; } + private byte RIB1 { get => Data[0x75]; set => Data[0x75] = value; } + public bool RibbonChampionBattle { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonChampionRegional { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonChampionNational { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonCountry { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonNational { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonEarth { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonEvent { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonChampionWorld { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonBirthday { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonSpecial { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonSouvenir { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonWishing { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonClassic { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonPremier { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RIB1_7 { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + + // Meta Accessible Properties + public override int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set + { + if (value.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; + } + } + + public int[] EVs + { + get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; + set + { + if (value.Length != 6) return; + EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; + EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; + } + } + + public bool IsNicknamed => Nickname.Length > 0; + public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } + + public override IReadOnlyList Moves + { + get => new[] { Move1, Move2, Move3, Move4 }; + set + { + if (value.Count > 0) Move1 = value[0]; + if (value.Count > 1) Move2 = value[1]; + if (value.Count > 2) Move3 = value[2]; + if (value.Count > 3) Move4 = value[3]; + } + } + + public override IReadOnlyList Relearn + { + get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; + set + { + if (value.Count > 0) RelearnMove1 = value[0]; + if (value.Count > 1) RelearnMove2 = value[1]; + if (value.Count > 2) RelearnMove3 = value[2]; + if (value.Count > 3) RelearnMove4 = value[3]; + } + } + + public bool IsLinkGift => MetLocation == Locations.LinkGift6; + + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + if (!IsEntity) + throw new ArgumentException(nameof(IsEntity)); + + var rnd = Util.Rand; + + int currentLevel = Level > 0 ? Level : rnd.Next(1, 101); + var pi = PersonalTable.AO.GetFormEntry(Species, Form); + PK6 pk = new() + { + Species = Species, + HeldItem = HeldItem, + TID = TID, + SID = SID, + Met_Level = currentLevel, + Form = Form, + EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(), + Version = OriginGame != 0 ? OriginGame : tr.Game, + Language = Language != 0 ? Language : tr.Language, + Ball = Ball, + Move1 = Move1, Move2 = Move2, Move3 = Move3, Move4 = Move4, + RelearnMove1 = RelearnMove1, RelearnMove2 = RelearnMove2, + RelearnMove3 = RelearnMove3, RelearnMove4 = RelearnMove4, + Met_Location = MetLocation, + Egg_Location = EggLocation, + CNT_Cool = CNT_Cool, + CNT_Beauty = CNT_Beauty, + CNT_Cute = CNT_Cute, + CNT_Smart = CNT_Smart, + CNT_Tough = CNT_Tough, + CNT_Sheen = CNT_Sheen, + + OT_Name = OT_Name.Length > 0 ? OT_Name : tr.OT, + OT_Gender = OTGender != 3 ? OTGender % 2 : tr.Gender, + HT_Name = OT_Name.Length > 0 ? tr.OT : string.Empty, + HT_Gender = OT_Name.Length > 0 ? tr.Gender : 0, + CurrentHandler = OT_Name.Length > 0 ? 1 : 0, + + EXP = Experience.GetEXP(Level, pi.EXPGrowth), + + // Ribbons + RibbonCountry = RibbonCountry, + RibbonNational = RibbonNational, + + RibbonEarth = RibbonEarth, + RibbonWorld = RibbonWorld, + RibbonClassic = RibbonClassic, + RibbonPremier = RibbonPremier, + RibbonEvent = RibbonEvent, + RibbonBirthday = RibbonBirthday, + RibbonSpecial = RibbonSpecial, + RibbonSouvenir = RibbonSouvenir, + + RibbonWishing = RibbonWishing, + RibbonChampionBattle = RibbonChampionBattle, + RibbonChampionRegional = RibbonChampionRegional, + RibbonChampionNational = RibbonChampionNational, + RibbonChampionWorld = RibbonChampionWorld, + + OT_Friendship = pi.BaseFriendship, + OT_Intensity = OT_Intensity, + OT_Memory = OT_Memory, + OT_TextVar = OT_TextVar, + OT_Feeling = OT_Feeling, + FatefulEncounter = !IsLinkGift, // Link gifts do not set fateful encounter + + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPE = EV_SPE, + EV_SPA = EV_SPA, + EV_SPD = EV_SPD, + }; + + if (tr is IRegionOrigin o) + { + pk.Country = o.Country; + pk.Region = o.Region; + pk.ConsoleRegion = o.ConsoleRegion; + } + else + { + pk.SetDefaultRegionOrigins(); + } + + pk.SetMaximumPPCurrent(); + + pk.MetDate = Date ?? DateTime.Now; + + if ((tr.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version)) + { + // give random valid game + do { pk.Version = (int)GameVersion.X + rnd.Next(4); } + while (!CanBeReceivedByVersion(pk.Version)); + } + + if (!IsEgg) + { + if (pk.CurrentHandler == 0) // OT + { + pk.OT_Memory = 3; + pk.OT_TextVar = 9; + pk.OT_Intensity = 1; + pk.OT_Feeling = MemoryContext6.GetRandomFeeling6(pk.OT_Memory, 10); // 0-9 + } + else + { + pk.HT_Memory = 3; + pk.HT_TextVar = 9; + pk.HT_Intensity = 1; + pk.HT_Feeling = MemoryContext6.GetRandomFeeling6(pk.HT_Memory, 10); // 0-9 + pk.HT_Friendship = pk.OT_Friendship; + } + } + + pk.IsNicknamed = IsNicknamed; + pk.Nickname = IsNicknamed ? Nickname : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); + + SetPINGA(pk, criteria); + + if (IsEgg) + SetEggMetData(pk); + pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; + + pk.RefreshChecksum(); + return pk; + } + + private void SetEggMetData(PKM pk) + { + pk.IsEgg = true; + pk.EggMetDate = Date; + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); + pk.IsNicknamed = true; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = PersonalTable.AO.GetFormEntry(Species, Form); + pk.Nature = (int)criteria.GetNature((Nature)Nature); + pk.Gender = criteria.GetGender(Gender, pi); + var av = GetAbilityIndex(criteria); + pk.RefreshAbility(av); + SetPID(pk); + SetIVs(pk); + } + + private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch + { + 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 + 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H + _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), + }; + + public override AbilityPermission Ability => AbilityType switch + { + 0 => AbilityPermission.OnlyFirst, + 1 => AbilityPermission.OnlySecond, + 2 => AbilityPermission.OnlyHidden, + 3 => AbilityPermission.Any12, + _ => AbilityPermission.Any12H, + }; + + private void SetPID(PKM pk) + { + switch (PIDType) + { + case ShinyType6.FixedValue: // Specified + pk.PID = PID; + break; + case ShinyType6.Random: // Random + pk.PID = Util.Rand32(); + break; + case ShinyType6.Always: // Random Shiny + pk.PID = Util.Rand32(); + pk.PID = (uint)(((pk.TID ^ pk.SID ^ (pk.PID & 0xFFFF)) << 16) | (pk.PID & 0xFFFF)); + break; + case ShinyType6.Never: // Random Nonshiny + pk.PID = Util.Rand32(); + if (pk.IsShiny) pk.PID ^= 0x10000000; + break; + } + } + + public override void GetIVs(Span value) + { + if (value.Length != 6) + return; + value[0] = IV_HP; + value[1] = IV_ATK; + value[2] = IV_DEF; + value[3] = IV_SPE; + value[4] = IV_SPA; + value[5] = IV_SPD; + } + + private void SetIVs(PKM pk) + { + Span finalIVs = stackalloc int[6]; + GetIVs(finalIVs); + var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); + var rng = Util.Rand; + if (ivflag == 0) // Random IVs + { + for (int i = 0; i < finalIVs.Length; i++) + { + if (finalIVs[i] > 31) + finalIVs[i] = rng.Next(32); + } + } + else // 1/2/3 perfect IVs + { + int IVCount = ivflag - 0xFB; + do { finalIVs[rng.Next(6)] = 31; } + while (finalIVs.Count(31) < IVCount); + for (int i = 0; i < finalIVs.Length; i++) + { + if (finalIVs[i] != 31) + finalIVs[i] = rng.Next(32); + } + } + pk.SetIVs(finalIVs); + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsEgg) + { + if (OTGender != 3) + { + // Skip ID check if ORASDEMO Simulated wc6 + if (CardID != 0) + { + if (SID != pk.SID) return false; + if (TID != pk.TID) return false; + } + if (OTGender != pk.OT_Gender) return false; + } + if (!string.IsNullOrEmpty(OT_Name) && OT_Name != pk.OT_Name) return false; + if (PIDType == ShinyType6.FixedValue && pk.PID != PID) return false; + if (!Shiny.IsValid(pk)) return false; + if (OriginGame != 0 && OriginGame != pk.Version) return false; + if (EncryptionConstant != 0 && EncryptionConstant != pk.EncryptionConstant) return false; + if (Language != 0 && Language != pk.Language) return false; + } + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, pk.Format)) + return false; + + if (IsEgg) + { + if (EggLocation != pk.Egg_Location) // traded + { + if (pk.Egg_Location != Locations.LinkTrade6) + return false; + } + else if (PIDType == 0 && pk.IsShiny) + { + return false; // can't be traded away for unshiny + } + + if (pk.IsEgg && !pk.IsNative) + return false; + } + else + { + if (!IsMatchEggLocation(pk)) return false; + if (MetLocation != pk.Met_Location) return false; + } + + if (Level != pk.Met_Level) return false; + if (Ball != pk.Ball) return false; + if (OTGender < 3 && OTGender != pk.OT_Gender) return false; + if (Nature != -1 && pk.Nature != Nature) return false; + if (Gender != 3 && Gender != pk.Gender) return false; + + if (pk is IContestStats s && s.IsContestBelow(this)) + return false; + + return true; + } + + protected override bool IsMatchDeferred(PKM pk) + { + switch (CardID) + { + case 0525 when IV_HP == 0xFE: // Diancie was distributed with no IV enforcement & 3IVs + case 0504 when RibbonClassic != ((IRibbonSetEvent4)pk).RibbonClassic: // Magmar with/without classic + return true; + } + return Species != pk.Species; + } + + protected override bool IsMatchPartial(PKM pk) + { + if (RestrictLanguage != 0 && RestrictLanguage != pk.Language) + return true; + if (!CanBeReceivedByVersion(pk.Version)) + return true; + return false; + } } diff --git a/PKHeX.Core/MysteryGifts/WC6Full.cs b/PKHeX.Core/MysteryGifts/WC6Full.cs index 60bfe0050..6c5c74b9c 100644 --- a/PKHeX.Core/MysteryGifts/WC6Full.cs +++ b/PKHeX.Core/MysteryGifts/WC6Full.cs @@ -1,59 +1,58 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class WC6Full { - public sealed class WC6Full + public const int Size = 0x310; + private const int GiftStart = Size - WC6.Size; + public readonly byte[] Data; + public readonly WC6 Gift; + + public byte RestrictVersion { get => Data[0]; set => Data[0] = value; } + public byte RestrictLanguage { get => Data[0x1FF]; set => Data[0x1FF] = value; } + + public WC6Full(byte[] data) { - public const int Size = 0x310; - private const int GiftStart = Size - WC6.Size; - public readonly byte[] Data; - public readonly WC6 Gift; + Data = data; + var wc6 = data.SliceEnd(GiftStart); + Gift = new WC6(wc6); + var now = DateTime.Now; + Gift.RawDate = WC6.SetDate((uint)now.Year, (uint)now.Month, (uint)now.Day); - public byte RestrictVersion { get => Data[0]; set => Data[0] = value; } - public byte RestrictLanguage { get => Data[0x1FF]; set => Data[0x1FF] = value; } - - public WC6Full(byte[] data) - { - Data = data; - var wc6 = data.SliceEnd(GiftStart); - Gift = new WC6(wc6); - var now = DateTime.Now; - Gift.RawDate = WC6.SetDate((uint)now.Year, (uint)now.Month, (uint)now.Day); - - Gift.RestrictVersion = RestrictVersion; - Gift.RestrictLanguage = RestrictLanguage; - } - - public static WC6[] GetArray(ReadOnlySpan WC6Full, ReadOnlySpan data) - { - var countfull = WC6Full.Length / Size; - var countgift = data.Length / WC6.Size; - var result = new WC6[countfull + countgift]; - - var now = DateTime.Now; - for (int i = 0; i < countfull; i++) - result[i] = ReadWC6(WC6Full, i * Size, now); - for (int i = 0; i < countgift; i++) - result[i + countfull] = ReadWC6Only(data, i * WC6.Size); - - return result; - } - - private static WC6 ReadWC6(ReadOnlySpan data, int ofs, DateTime date) - { - var slice = data.Slice(ofs + GiftStart, WC6.Size).ToArray(); - return new WC6(slice) - { - RestrictVersion = data[ofs], - RestrictLanguage = data[ofs + 0x1FF], - RawDate = WC6.SetDate((uint)date.Year, (uint)date.Month, (uint)date.Day), - }; - } - - private static WC6 ReadWC6Only(ReadOnlySpan data, int ofs) - { - var slice = data.Slice(ofs, WC6.Size).ToArray(); - return new WC6(slice); - } + Gift.RestrictVersion = RestrictVersion; + Gift.RestrictLanguage = RestrictLanguage; } -} \ No newline at end of file + + public static WC6[] GetArray(ReadOnlySpan WC6Full, ReadOnlySpan data) + { + var countfull = WC6Full.Length / Size; + var countgift = data.Length / WC6.Size; + var result = new WC6[countfull + countgift]; + + var now = DateTime.Now; + for (int i = 0; i < countfull; i++) + result[i] = ReadWC6(WC6Full, i * Size, now); + for (int i = 0; i < countgift; i++) + result[i + countfull] = ReadWC6Only(data, i * WC6.Size); + + return result; + } + + private static WC6 ReadWC6(ReadOnlySpan data, int ofs, DateTime date) + { + var slice = data.Slice(ofs + GiftStart, WC6.Size).ToArray(); + return new WC6(slice) + { + RestrictVersion = data[ofs], + RestrictLanguage = data[ofs + 0x1FF], + RawDate = WC6.SetDate((uint)date.Year, (uint)date.Month, (uint)date.Day), + }; + } + + private static WC6 ReadWC6Only(ReadOnlySpan data, int ofs) + { + var slice = data.Slice(ofs, WC6.Size).ToArray(); + return new WC6(slice); + } +} diff --git a/PKHeX.Core/MysteryGifts/WC7.cs b/PKHeX.Core/MysteryGifts/WC7.cs index a1b27a55f..2fa35bd54 100644 --- a/PKHeX.Core/MysteryGifts/WC7.cs +++ b/PKHeX.Core/MysteryGifts/WC7.cs @@ -2,631 +2,630 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 Mystery Gift Template File +/// +public sealed class WC7 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, IContestStatsMutable, INature, IMemoryOT { - /// - /// Generation 7 Mystery Gift Template File - /// - public sealed class WC7 : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, ILangNick, IContestStats, IContestStatsMutable, INature, IMemoryOT + public const int Size = 0x108; + public override int Generation => 7; + + public WC7() : this(new byte[Size]) { } + public WC7(byte[] data) : base(data) { } + + public int RestrictLanguage { get; set; } // None + public byte RestrictVersion { get; set; } // Permit All + + public bool CanBeReceivedByVersion(int v) { - public const int Size = 0x108; - public override int Generation => 7; - - public WC7() : this(new byte[Size]) { } - public WC7(byte[] data) : base(data) { } - - public int RestrictLanguage { get; set; } // None - public byte RestrictVersion { get; set; } // Permit All - - public bool CanBeReceivedByVersion(int v) - { - if (v is < (int)GameVersion.SN or > (int)GameVersion.UM) - return false; - if (RestrictVersion == 0) - return true; // no data - var bitIndex = v - (int)GameVersion.SN; - var bit = 1 << bitIndex; - return (RestrictVersion & bit) != 0; - } - - // General Card Properties - public override int CardID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0)); - set => WriteUInt16LittleEndian(Data.AsSpan(0), (ushort)value); - } - - public override string CardTitle - { - // Max len 36 char, followed by null terminator - get => StringConverter7.GetString(Data.AsSpan(2, 0x4A)); - set => StringConverter7.SetString(Data.AsSpan(2, 0x4A), value.AsSpan(), 36, Language, StringConverterOption.ClearZero); - } - - internal uint RawDate - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x4C)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x4C), value); - } - - private uint Year - { - get => (RawDate / 10000) + 2000; - set => RawDate = SetDate(value, Month, Day); - } - - private uint Month - { - get => RawDate % 10000 / 100; - set => RawDate = SetDate(Year, value, Day); - } - - private uint Day - { - get => RawDate % 100; - set => RawDate = SetDate(Year, Month, value); - } - - public static uint SetDate(uint year, uint month, uint day) => (Math.Max(0, year - 2000) * 10000) + (month * 100) + day; - - /// - /// Gets or sets the date of the card. - /// - public DateTime? Date - { - get - { - // Check to see if date is valid - if (!DateUtil.IsDateValid(Year, Month, Day)) - return null; - - return new DateTime((int)Year, (int)Month, (int)Day); - } - set - { - if (value.HasValue) - { - // Only update the properties if a value is provided. - Year = (ushort)value.Value.Year; - Month = (byte)value.Value.Month; - Day = (byte)value.Value.Day; - } - else - { - // Clear the Met Date. - // If code tries to access MetDate again, null will be returned. - Year = 0; - Month = 0; - Day = 0; - } - } - } - - public int CardLocation { get => Data[0x50]; set => Data[0x50] = (byte)value; } - - public int CardType { get => Data[0x51]; set => Data[0x51] = (byte)value; } - public byte CardFlags { get => Data[0x52]; set => Data[0x52] = value; } - - public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); } - public override bool GiftUsed { get => (CardFlags & 2) == 2; set => CardFlags = (byte)((CardFlags & ~2) | (value ? 2 : 0)); } - public bool GiftOncePerDay { get => (CardFlags & 4) == 4; set => CardFlags = (byte)((CardFlags & ~4) | (value ? 4 : 0)); } - - public bool MultiObtain { get => Data[0x53] == 1; set => Data[0x53] = value ? (byte)1 : (byte)0; } - - // BP Properties - public bool IsBP { get => CardType == 3; set { if (value) CardType = 3; } } - public int BP { get => ItemID; set => ItemID = value; } - - // Bean (Mame) Properties - public bool IsBean { get => CardType == 2; set { if (value) CardType = 2; } } - public int Bean { get => ItemID; set => ItemID = value; } - - // Item Properties - public override bool IsItem { get => CardType == 1; set { if (value) CardType = 1; } } - public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(0x68)); set => WriteUInt16LittleEndian(Data.AsSpan(0x68), (ushort)value); } - public int GetItem(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x68 + (0x4 * index))); - public void SetItem(int index, ushort item) => WriteUInt16LittleEndian(Data.AsSpan(0x68 + (4 * index)), item); - public int GetQuantity(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x6A + (0x4 * index))); - public void SetQuantity(int index, ushort quantity) => WriteUInt16LittleEndian(Data.AsSpan(0x6A + (4 * index)), quantity); - - public override int Quantity - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); - } - - // Pokémon Properties - public override bool IsEntity { get => CardType == 0; set { if (value) CardType = 0; } } - public override bool IsShiny => Shiny.IsShiny(); - - public override Shiny Shiny => IsEgg ? Shiny.Random : PIDType switch - { - ShinyType6.FixedValue => GetShinyXor() switch - { - 0 => Shiny.AlwaysSquare, - <= 15 => Shiny.AlwaysStar, - _ => Shiny.Never, - }, - ShinyType6.Random => Shiny.Random, - ShinyType6.Never => Shiny.Never, - ShinyType6.Always => Shiny.Always, - _ => throw new ArgumentOutOfRangeException(), - }; - - private int GetShinyXor() - { - // Player owned anti-shiny fixed PID - if (TID == 0 && SID == 0) - return int.MaxValue; - - var pid = PID; - var psv = (int)(pid >> 16 ^ (pid & 0xFFFF)); - var tsv = (TID ^ SID); - return psv ^ tsv; - } - - public override int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x68)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x68), (ushort)value); - } - - public override int SID { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); - } - - public int OriginGame - { - get => Data[0x6C]; - set => Data[0x6C] = (byte)value; - } - - public uint EncryptionConstant { - get => ReadUInt32LittleEndian(Data.AsSpan(0x70)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x70), value); - } - - public override int Ball - { - get => Data[0x76]; - set => Data[0x76] = (byte)value; } - - public override int HeldItem - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x78)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x78), (ushort)value); - } - - public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7A), (ushort)value); } - public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7C), (ushort)value); } - public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7E), (ushort)value); } - public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x80)); set => WriteUInt16LittleEndian(Data.AsSpan(0x80), (ushort)value); } - public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x82)); set => WriteUInt16LittleEndian(Data.AsSpan(0x82), (ushort)value); } - public override int Form { get => Data[0x84]; set => Data[0x84] = (byte)value; } - public int Language { get => Data[0x85]; set => Data[0x85] = (byte)value; } - - public string Nickname - { - get => StringConverter7.GetString(Data.AsSpan(0x86, 0x1A)); - set => StringConverter7.SetString(Data.AsSpan(0x86, 0x1A), value.AsSpan(), 12, Language, StringConverterOption.ClearZero); - } - - public int Nature { get => (sbyte)Data[0xA0]; set => Data[0xA0] = (byte)value; } - public override int Gender { get => Data[0xA1]; set => Data[0xA1] = (byte)value; } - public override int AbilityType { get => Data[0xA2]; set => Data[0xA2] = (byte)value; } - public ShinyType6 PIDType { get => (ShinyType6)Data[0xA3]; set => Data[0xA3] = (byte)value; } - public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0xA4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA4), (ushort)value); } - public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0xA6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA6), (ushort)value); } - public int MetLevel { get => Data[0xA8]; set => Data[0xA8] = (byte)value; } - - public byte CNT_Cool { get => Data[0xA9]; set => Data[0xA9] = value; } - public byte CNT_Beauty { get => Data[0xAA]; set => Data[0xAA] = value; } - public byte CNT_Cute { get => Data[0xAB]; set => Data[0xAB] = value; } - public byte CNT_Smart { get => Data[0xAC]; set => Data[0xAC] = value; } - public byte CNT_Tough { get => Data[0xAD]; set => Data[0xAD] = value; } - public byte CNT_Sheen { get => Data[0xAE]; set => Data[0xAE] = value; } - - public int IV_HP { get => Data[0xAF]; set => Data[0xAF] = (byte)value; } - public int IV_ATK { get => Data[0xB0]; set => Data[0xB0] = (byte)value; } - public int IV_DEF { get => Data[0xB1]; set => Data[0xB1] = (byte)value; } - public int IV_SPE { get => Data[0xB2]; set => Data[0xB2] = (byte)value; } - public int IV_SPA { get => Data[0xB3]; set => Data[0xB3] = (byte)value; } - public int IV_SPD { get => Data[0xB4]; set => Data[0xB4] = (byte)value; } - - public int OTGender { get => Data[0xB5]; set => Data[0xB5] = (byte)value; } - - public override string OT_Name - { - get => StringConverter7.GetString(Data.AsSpan(0xB6, 0x1A)); - set => StringConverter7.SetString(Data.AsSpan(0xB6, 0x1A), value.AsSpan(), 12, Language, StringConverterOption.ClearZero); - } - - public override byte Level { get => Data[0xD0]; set => Data[0xD0] = value; } - public override bool IsEgg { get => Data[0xD1] == 1; set => Data[0xD1] = value ? (byte)1 : (byte)0; } - public ushort AdditionalItem { get => ReadUInt16LittleEndian(Data.AsSpan(0xD2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD2), value); } - - public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0xD4)); set => WriteUInt32LittleEndian(Data.AsSpan(0xD4), value); } - public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), (ushort)value); } - public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), (ushort)value); } - public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDC), (ushort)value); } - public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDE), (ushort)value); } - - public byte OT_Intensity { get => Data[0xE0]; set => Data[0xE0] = value; } - public byte OT_Memory { get => Data[0xE1]; set => Data[0xE1] = value; } - public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xE2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xE2), value); } - public byte OT_Feeling { get => Data[0xE4]; set => Data[0xE4] = value; } - - public int EV_HP { get => Data[0xE5]; set => Data[0xE5] = (byte)value; } - public int EV_ATK { get => Data[0xE6]; set => Data[0xE6] = (byte)value; } - public int EV_DEF { get => Data[0xE7]; set => Data[0xE7] = (byte)value; } - public int EV_SPE { get => Data[0xE8]; set => Data[0xE8] = (byte)value; } - public int EV_SPA { get => Data[0xE9]; set => Data[0xE9] = (byte)value; } - public int EV_SPD { get => Data[0xEA]; set => Data[0xEA] = (byte)value; } - - private byte RIB0 { get => Data[0x74]; set => Data[0x74] = value; } - private byte RIB1 { get => Data[0x75]; set => Data[0x75] = value; } - public bool RibbonChampionBattle { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonChampionRegional { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonChampionNational { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonCountry { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonNational { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonEarth { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonEvent { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonChampionWorld { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonBirthday { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonSpecial { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonSouvenir { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonWishing { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonClassic { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonPremier { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RIB1_7 { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - - // Meta Accessible Properties - public override int[] IVs - { - get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; - set - { - if (value.Length != 6) return; - IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; - IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; - } - } - - public override void GetIVs(Span value) - { - if (value.Length != 6) - return; - value[0] = IV_HP; - value[1] = IV_ATK; - value[2] = IV_DEF; - value[3] = IV_SPE; - value[4] = IV_SPA; - value[5] = IV_SPD; - } - - public int[] EVs - { - get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; - set - { - if (value.Length != 6) return; - EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; - EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; - } - } - - public bool IsNicknamed => Nickname.Length > 0 || IsEgg; - public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } - - public override IReadOnlyList Moves - { - get => new[] { Move1, Move2, Move3, Move4 }; - set - { - if (value.Count > 0) Move1 = value[0]; - if (value.Count > 1) Move2 = value[1]; - if (value.Count > 2) Move3 = value[2]; - if (value.Count > 3) Move4 = value[3]; - } - } - - public override IReadOnlyList Relearn - { - get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; - set - { - if (value.Count > 0) RelearnMove1 = value[0]; - if (value.Count > 1) RelearnMove2 = value[1]; - if (value.Count > 2) RelearnMove3 = value[2]; - if (value.Count > 3) RelearnMove4 = value[3]; - } - } - - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - if (!IsEntity) - throw new ArgumentException(nameof(IsEntity)); - - var rnd = Util.Rand; - - int currentLevel = Level > 0 ? Level : rnd.Next(1, 101); - int metLevel = MetLevel > 0 ? MetLevel : currentLevel; - var version = OriginGame != 0 ? OriginGame : (int)this.GetCompatibleVersion((GameVersion)sav.Game); - var language = Language != 0 ? Language : (int)Core.Language.GetSafeLanguage(Generation, (LanguageID)sav.Language, (GameVersion)version); - - var pi = PersonalTable.USUM.GetFormEntry(Species, Form); - PK7 pk = new() - { - Species = Species, - HeldItem = HeldItem, - TID = TID, - SID = SID, - Met_Level = metLevel, - Form = Form, - EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(), - Version = version, - Language = language, - Ball = Ball, - Move1 = Move1, Move2 = Move2, Move3 = Move3, Move4 = Move4, - RelearnMove1 = RelearnMove1, RelearnMove2 = RelearnMove2, - RelearnMove3 = RelearnMove3, RelearnMove4 = RelearnMove4, - Met_Location = MetLocation, - Egg_Location = EggLocation, - CNT_Cool = CNT_Cool, - CNT_Beauty = CNT_Beauty, - CNT_Cute = CNT_Cute, - CNT_Smart = CNT_Smart, - CNT_Tough = CNT_Tough, - CNT_Sheen = CNT_Sheen, - - OT_Name = OT_Name.Length > 0 ? OT_Name : sav.OT, - OT_Gender = OTGender != 3 ? OTGender % 2 : sav.Gender, - HT_Name = OT_Name.Length > 0 ? sav.OT : string.Empty, - HT_Gender = OT_Name.Length > 0 ? sav.Gender : 0, - CurrentHandler = OT_Name.Length > 0 ? 1 : 0, - - EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), - - // Ribbons - RibbonCountry = RibbonCountry, - RibbonNational = RibbonNational, - - RibbonEarth = RibbonEarth, - RibbonWorld = RibbonWorld, - RibbonClassic = RibbonClassic, - RibbonPremier = RibbonPremier, - RibbonEvent = RibbonEvent, - RibbonBirthday = RibbonBirthday, - RibbonSpecial = RibbonSpecial, - RibbonSouvenir = RibbonSouvenir, - - RibbonWishing = RibbonWishing, - RibbonChampionBattle = RibbonChampionBattle, - RibbonChampionRegional = RibbonChampionRegional, - RibbonChampionNational = RibbonChampionNational, - RibbonChampionWorld = RibbonChampionWorld, - - OT_Friendship = pi.BaseFriendship, - OT_Intensity = OT_Intensity, - OT_Memory = OT_Memory, - OT_TextVar = OT_TextVar, - OT_Feeling = OT_Feeling, - FatefulEncounter = true, - - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPE = EV_SPE, - EV_SPA = EV_SPA, - EV_SPD = EV_SPD, - }; - - if (sav is IRegionOrigin o) - { - pk.Country = o.Country; - pk.Region = o.Region; - pk.ConsoleRegion = o.ConsoleRegion; - } - else - { - pk.SetDefaultRegionOrigins(); - } - - pk.SetMaximumPPCurrent(); - - if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version)) - { - // give random valid game - do { pk.Version = (int)GameVersion.SN + rnd.Next(4); } - while (!CanBeReceivedByVersion(pk.Version)); - } - - if (OTGender == 3) - { - pk.TID = sav.TID; - pk.SID = sav.SID; - } - - pk.MetDate = Date ?? DateTime.Now; - - pk.IsNicknamed = IsNicknamed; - pk.Nickname = IsNicknamed ? Nickname : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); - - SetPINGA(pk, criteria); - - if (IsEgg) - SetEggMetData(pk); - pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; - - pk.RefreshChecksum(); - return pk; - } - - private void SetEggMetData(PKM pk) - { - pk.IsEgg = true; - pk.EggMetDate = Date; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); - pk.IsNicknamed = true; - } - - private void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = PersonalTable.USUM.GetFormEntry(Species, Form); - pk.Nature = (int)criteria.GetNature((Nature)Nature); - pk.Gender = criteria.GetGender(Gender, pi); - var av = GetAbilityIndex(criteria); - pk.RefreshAbility(av); - SetPID(pk); - SetIVs(pk); - } - - private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch - { - 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 - 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H - _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), - }; - - public override AbilityPermission Ability => AbilityType switch - { - 0 => AbilityPermission.OnlyFirst, - 1 => AbilityPermission.OnlySecond, - 2 => AbilityPermission.OnlyHidden, - 3 => AbilityPermission.Any12, - _ => AbilityPermission.Any12H, - }; - - private void SetPID(PKM pk) - { - switch (PIDType) - { - case ShinyType6.FixedValue: // Specified - pk.PID = PID; - break; - case ShinyType6.Random: // Random - pk.PID = Util.Rand32(); - break; - case ShinyType6.Always: // Random Shiny - pk.PID = Util.Rand32(); - pk.PID = (uint)(((pk.TID ^ pk.SID ^ (pk.PID & 0xFFFF)) << 16) | (pk.PID & 0xFFFF)); - break; - case ShinyType6.Never: // Random Nonshiny - pk.PID = Util.Rand32(); - if (pk.IsShiny) pk.PID ^= 0x10000000; - break; - } - } - - private void SetIVs(PKM pk) - { - Span finalIVs = stackalloc int[6]; - GetIVs(finalIVs); - var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); - var rng = Util.Rand; - if (ivflag == 0) // Random IVs - { - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] > 31) - finalIVs[i] = rng.Next(32); - } - } - else // 1/2/3 perfect IVs - { - int IVCount = ivflag - 0xFB; - do { finalIVs[rng.Next(6)] = 31; } - while (finalIVs.Count(31) < IVCount); - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] != 31) - finalIVs[i] = rng.Next(32); - } - } - pk.SetIVs(finalIVs); - } - - public bool IsAshGreninjaWC7(PKM pkm) - { - return CardID == 2046 && (pkm.SID << 16 | pkm.TID) == 0x79F57B49; - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!IsEgg) - { - if (OTGender != 3) - { - if (SID != pkm.SID) return false; - if (TID != pkm.TID) return false; - if (OTGender != pkm.OT_Gender) return false; - } - if (!string.IsNullOrEmpty(OT_Name) && OT_Name != pkm.OT_Name) return false; - if (OriginGame != 0 && OriginGame != pkm.Version) return false; - if (EncryptionConstant != 0 && EncryptionConstant != pkm.EncryptionConstant) return false; - if (Language != 0 && Language != pkm.Language) return false; - } - - if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pkm.Form, pkm.Format)) - return false; - - if (IsEgg) - { - if (EggLocation != pkm.Egg_Location) // traded - { - if (pkm.Egg_Location != Locations.LinkTrade6) - return false; - } - else if (PIDType == 0 && pkm.IsShiny) - { - return false; // can't be traded away for un-shiny - } - - if (pkm.IsEgg && !pkm.IsNative) - return false; - } - else - { - if (!Shiny.IsValid(pkm)) return false; - if (!IsMatchEggLocation(pkm)) return false; - if (MetLocation != pkm.Met_Location) return false; - } - - if (MetLevel != pkm.Met_Level) return false; - if (Ball != pkm.Ball) return false; - if (OTGender < 3 && OTGender != pkm.OT_Gender) return false; - if (Nature != -1 && pkm.Nature != Nature) return false; - if (Gender != 3 && Gender != pkm.Gender) return false; - - if (pkm is IContestStats s && s.IsContestBelow(this)) - return false; - - if (CardID is 1122 or 1133 && !CanBeReceivedByVersion(pkm.Version)) - return false; // Each version pair has a separate card -- since we aren't using deferral/partial match logic to reorder, just return false. - if (CardID == 2046) // Greninja WC has variant PID and can arrive @ 36 or 37 - return pkm.SM; // not USUM - - return PIDType != 0 || pkm.PID == PID; - } - - public override GameVersion Version - { - get => CardID == 2046 ? GameVersion.SM : GameVersion.Gen7; - set { } - } - - protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species; - - protected override bool IsMatchPartial(PKM pkm) - { - if (RestrictLanguage != 0 && RestrictLanguage != pkm.Language) - return true; - if (!CanBeReceivedByVersion(pkm.Version)) - return true; + if (v is < (int)GameVersion.SN or > (int)GameVersion.UM) return false; + if (RestrictVersion == 0) + return true; // no data + var bitIndex = v - (int)GameVersion.SN; + var bit = 1 << bitIndex; + return (RestrictVersion & bit) != 0; + } + + // General Card Properties + public override int CardID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0)); + set => WriteUInt16LittleEndian(Data.AsSpan(0), (ushort)value); + } + + public override string CardTitle + { + // Max len 36 char, followed by null terminator + get => StringConverter7.GetString(Data.AsSpan(2, 0x4A)); + set => StringConverter7.SetString(Data.AsSpan(2, 0x4A), value.AsSpan(), 36, Language, StringConverterOption.ClearZero); + } + + internal uint RawDate + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x4C)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x4C), value); + } + + private uint Year + { + get => (RawDate / 10000) + 2000; + set => RawDate = SetDate(value, Month, Day); + } + + private uint Month + { + get => RawDate % 10000 / 100; + set => RawDate = SetDate(Year, value, Day); + } + + private uint Day + { + get => RawDate % 100; + set => RawDate = SetDate(Year, Month, value); + } + + public static uint SetDate(uint year, uint month, uint day) => (Math.Max(0, year - 2000) * 10000) + (month * 100) + day; + + /// + /// Gets or sets the date of the card. + /// + public DateTime? Date + { + get + { + // Check to see if date is valid + if (!DateUtil.IsDateValid(Year, Month, Day)) + return null; + + return new DateTime((int)Year, (int)Month, (int)Day); + } + set + { + if (value.HasValue) + { + // Only update the properties if a value is provided. + Year = (ushort)value.Value.Year; + Month = (byte)value.Value.Month; + Day = (byte)value.Value.Day; + } + else + { + // Clear the Met Date. + // If code tries to access MetDate again, null will be returned. + Year = 0; + Month = 0; + Day = 0; + } } } + + public int CardLocation { get => Data[0x50]; set => Data[0x50] = (byte)value; } + + public int CardType { get => Data[0x51]; set => Data[0x51] = (byte)value; } + public byte CardFlags { get => Data[0x52]; set => Data[0x52] = value; } + + public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); } + public override bool GiftUsed { get => (CardFlags & 2) == 2; set => CardFlags = (byte)((CardFlags & ~2) | (value ? 2 : 0)); } + public bool GiftOncePerDay { get => (CardFlags & 4) == 4; set => CardFlags = (byte)((CardFlags & ~4) | (value ? 4 : 0)); } + + public bool MultiObtain { get => Data[0x53] == 1; set => Data[0x53] = value ? (byte)1 : (byte)0; } + + // BP Properties + public bool IsBP { get => CardType == 3; set { if (value) CardType = 3; } } + public int BP { get => ItemID; set => ItemID = value; } + + // Bean (Mame) Properties + public bool IsBean { get => CardType == 2; set { if (value) CardType = 2; } } + public int Bean { get => ItemID; set => ItemID = value; } + + // Item Properties + public override bool IsItem { get => CardType == 1; set { if (value) CardType = 1; } } + public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(0x68)); set => WriteUInt16LittleEndian(Data.AsSpan(0x68), (ushort)value); } + public int GetItem(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x68 + (0x4 * index))); + public void SetItem(int index, ushort item) => WriteUInt16LittleEndian(Data.AsSpan(0x68 + (4 * index)), item); + public int GetQuantity(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x6A + (0x4 * index))); + public void SetQuantity(int index, ushort quantity) => WriteUInt16LittleEndian(Data.AsSpan(0x6A + (4 * index)), quantity); + + public override int Quantity + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); + } + + // Pokémon Properties + public override bool IsEntity { get => CardType == 0; set { if (value) CardType = 0; } } + public override bool IsShiny => Shiny.IsShiny(); + + public override Shiny Shiny => IsEgg ? Shiny.Random : PIDType switch + { + ShinyType6.FixedValue => GetShinyXor() switch + { + 0 => Shiny.AlwaysSquare, + <= 15 => Shiny.AlwaysStar, + _ => Shiny.Never, + }, + ShinyType6.Random => Shiny.Random, + ShinyType6.Never => Shiny.Never, + ShinyType6.Always => Shiny.Always, + _ => throw new ArgumentOutOfRangeException(), + }; + + private int GetShinyXor() + { + // Player owned anti-shiny fixed PID + if (TID == 0 && SID == 0) + return int.MaxValue; + + var pid = PID; + var psv = (int)((pid >> 16) ^ (pid & 0xFFFF)); + var tsv = (TID ^ SID); + return psv ^ tsv; + } + + public override int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x68)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x68), (ushort)value); + } + + public override int SID { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); + } + + public int OriginGame + { + get => Data[0x6C]; + set => Data[0x6C] = (byte)value; + } + + public uint EncryptionConstant { + get => ReadUInt32LittleEndian(Data.AsSpan(0x70)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x70), value); + } + + public override int Ball + { + get => Data[0x76]; + set => Data[0x76] = (byte)value; } + + public override int HeldItem + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x78)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x78), (ushort)value); + } + + public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7A), (ushort)value); } + public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7C), (ushort)value); } + public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x7E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7E), (ushort)value); } + public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x80)); set => WriteUInt16LittleEndian(Data.AsSpan(0x80), (ushort)value); } + public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x82)); set => WriteUInt16LittleEndian(Data.AsSpan(0x82), (ushort)value); } + public override int Form { get => Data[0x84]; set => Data[0x84] = (byte)value; } + public int Language { get => Data[0x85]; set => Data[0x85] = (byte)value; } + + public string Nickname + { + get => StringConverter7.GetString(Data.AsSpan(0x86, 0x1A)); + set => StringConverter7.SetString(Data.AsSpan(0x86, 0x1A), value.AsSpan(), 12, Language, StringConverterOption.ClearZero); + } + + public int Nature { get => (sbyte)Data[0xA0]; set => Data[0xA0] = (byte)value; } + public override int Gender { get => Data[0xA1]; set => Data[0xA1] = (byte)value; } + public override int AbilityType { get => Data[0xA2]; set => Data[0xA2] = (byte)value; } + public ShinyType6 PIDType { get => (ShinyType6)Data[0xA3]; set => Data[0xA3] = (byte)value; } + public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0xA4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA4), (ushort)value); } + public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(0xA6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA6), (ushort)value); } + public int MetLevel { get => Data[0xA8]; set => Data[0xA8] = (byte)value; } + + public byte CNT_Cool { get => Data[0xA9]; set => Data[0xA9] = value; } + public byte CNT_Beauty { get => Data[0xAA]; set => Data[0xAA] = value; } + public byte CNT_Cute { get => Data[0xAB]; set => Data[0xAB] = value; } + public byte CNT_Smart { get => Data[0xAC]; set => Data[0xAC] = value; } + public byte CNT_Tough { get => Data[0xAD]; set => Data[0xAD] = value; } + public byte CNT_Sheen { get => Data[0xAE]; set => Data[0xAE] = value; } + + public int IV_HP { get => Data[0xAF]; set => Data[0xAF] = (byte)value; } + public int IV_ATK { get => Data[0xB0]; set => Data[0xB0] = (byte)value; } + public int IV_DEF { get => Data[0xB1]; set => Data[0xB1] = (byte)value; } + public int IV_SPE { get => Data[0xB2]; set => Data[0xB2] = (byte)value; } + public int IV_SPA { get => Data[0xB3]; set => Data[0xB3] = (byte)value; } + public int IV_SPD { get => Data[0xB4]; set => Data[0xB4] = (byte)value; } + + public int OTGender { get => Data[0xB5]; set => Data[0xB5] = (byte)value; } + + public override string OT_Name + { + get => StringConverter7.GetString(Data.AsSpan(0xB6, 0x1A)); + set => StringConverter7.SetString(Data.AsSpan(0xB6, 0x1A), value.AsSpan(), 12, Language, StringConverterOption.ClearZero); + } + + public override byte Level { get => Data[0xD0]; set => Data[0xD0] = value; } + public override bool IsEgg { get => Data[0xD1] == 1; set => Data[0xD1] = value ? (byte)1 : (byte)0; } + public ushort AdditionalItem { get => ReadUInt16LittleEndian(Data.AsSpan(0xD2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD2), value); } + + public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0xD4)); set => WriteUInt32LittleEndian(Data.AsSpan(0xD4), value); } + public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), (ushort)value); } + public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), (ushort)value); } + public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDC), (ushort)value); } + public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0xDE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDE), (ushort)value); } + + public byte OT_Intensity { get => Data[0xE0]; set => Data[0xE0] = value; } + public byte OT_Memory { get => Data[0xE1]; set => Data[0xE1] = value; } + public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xE2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xE2), value); } + public byte OT_Feeling { get => Data[0xE4]; set => Data[0xE4] = value; } + + public int EV_HP { get => Data[0xE5]; set => Data[0xE5] = (byte)value; } + public int EV_ATK { get => Data[0xE6]; set => Data[0xE6] = (byte)value; } + public int EV_DEF { get => Data[0xE7]; set => Data[0xE7] = (byte)value; } + public int EV_SPE { get => Data[0xE8]; set => Data[0xE8] = (byte)value; } + public int EV_SPA { get => Data[0xE9]; set => Data[0xE9] = (byte)value; } + public int EV_SPD { get => Data[0xEA]; set => Data[0xEA] = (byte)value; } + + private byte RIB0 { get => Data[0x74]; set => Data[0x74] = value; } + private byte RIB1 { get => Data[0x75]; set => Data[0x75] = value; } + public bool RibbonChampionBattle { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonChampionRegional { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonChampionNational { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonCountry { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonNational { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonEarth { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonEvent { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonChampionWorld { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonBirthday { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonSpecial { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonSouvenir { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonWishing { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonClassic { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonPremier { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RIB1_7 { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + + // Meta Accessible Properties + public override int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set + { + if (value.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; + } + } + + public override void GetIVs(Span value) + { + if (value.Length != 6) + return; + value[0] = IV_HP; + value[1] = IV_ATK; + value[2] = IV_DEF; + value[3] = IV_SPE; + value[4] = IV_SPA; + value[5] = IV_SPD; + } + + public int[] EVs + { + get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; + set + { + if (value.Length != 6) return; + EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; + EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; + } + } + + public bool IsNicknamed => Nickname.Length > 0 || IsEgg; + public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } + + public override IReadOnlyList Moves + { + get => new[] { Move1, Move2, Move3, Move4 }; + set + { + if (value.Count > 0) Move1 = value[0]; + if (value.Count > 1) Move2 = value[1]; + if (value.Count > 2) Move3 = value[2]; + if (value.Count > 3) Move4 = value[3]; + } + } + + public override IReadOnlyList Relearn + { + get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; + set + { + if (value.Count > 0) RelearnMove1 = value[0]; + if (value.Count > 1) RelearnMove2 = value[1]; + if (value.Count > 2) RelearnMove3 = value[2]; + if (value.Count > 3) RelearnMove4 = value[3]; + } + } + + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + if (!IsEntity) + throw new ArgumentException(nameof(IsEntity)); + + var rnd = Util.Rand; + + int currentLevel = Level > 0 ? Level : rnd.Next(1, 101); + int metLevel = MetLevel > 0 ? MetLevel : currentLevel; + var version = OriginGame != 0 ? OriginGame : (int)this.GetCompatibleVersion((GameVersion)tr.Game); + var language = Language != 0 ? Language : (int)Core.Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, (GameVersion)version); + + var pi = PersonalTable.USUM.GetFormEntry(Species, Form); + PK7 pk = new() + { + Species = Species, + HeldItem = HeldItem, + TID = TID, + SID = SID, + Met_Level = metLevel, + Form = Form, + EncryptionConstant = EncryptionConstant != 0 ? EncryptionConstant : Util.Rand32(), + Version = version, + Language = language, + Ball = Ball, + Move1 = Move1, Move2 = Move2, Move3 = Move3, Move4 = Move4, + RelearnMove1 = RelearnMove1, RelearnMove2 = RelearnMove2, + RelearnMove3 = RelearnMove3, RelearnMove4 = RelearnMove4, + Met_Location = MetLocation, + Egg_Location = EggLocation, + CNT_Cool = CNT_Cool, + CNT_Beauty = CNT_Beauty, + CNT_Cute = CNT_Cute, + CNT_Smart = CNT_Smart, + CNT_Tough = CNT_Tough, + CNT_Sheen = CNT_Sheen, + + OT_Name = OT_Name.Length > 0 ? OT_Name : tr.OT, + OT_Gender = OTGender != 3 ? OTGender % 2 : tr.Gender, + HT_Name = OT_Name.Length > 0 ? tr.OT : string.Empty, + HT_Gender = OT_Name.Length > 0 ? tr.Gender : 0, + CurrentHandler = OT_Name.Length > 0 ? 1 : 0, + + EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), + + // Ribbons + RibbonCountry = RibbonCountry, + RibbonNational = RibbonNational, + + RibbonEarth = RibbonEarth, + RibbonWorld = RibbonWorld, + RibbonClassic = RibbonClassic, + RibbonPremier = RibbonPremier, + RibbonEvent = RibbonEvent, + RibbonBirthday = RibbonBirthday, + RibbonSpecial = RibbonSpecial, + RibbonSouvenir = RibbonSouvenir, + + RibbonWishing = RibbonWishing, + RibbonChampionBattle = RibbonChampionBattle, + RibbonChampionRegional = RibbonChampionRegional, + RibbonChampionNational = RibbonChampionNational, + RibbonChampionWorld = RibbonChampionWorld, + + OT_Friendship = pi.BaseFriendship, + OT_Intensity = OT_Intensity, + OT_Memory = OT_Memory, + OT_TextVar = OT_TextVar, + OT_Feeling = OT_Feeling, + FatefulEncounter = true, + + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPE = EV_SPE, + EV_SPA = EV_SPA, + EV_SPD = EV_SPD, + }; + + if (tr is IRegionOrigin o) + { + pk.Country = o.Country; + pk.Region = o.Region; + pk.ConsoleRegion = o.ConsoleRegion; + } + else + { + pk.SetDefaultRegionOrigins(); + } + + pk.SetMaximumPPCurrent(); + + if ((tr.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version)) + { + // give random valid game + do { pk.Version = (int)GameVersion.SN + rnd.Next(4); } + while (!CanBeReceivedByVersion(pk.Version)); + } + + if (OTGender == 3) + { + pk.TID = tr.TID; + pk.SID = tr.SID; + } + + pk.MetDate = Date ?? DateTime.Now; + + pk.IsNicknamed = IsNicknamed; + pk.Nickname = IsNicknamed ? Nickname : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); + + SetPINGA(pk, criteria); + + if (IsEgg) + SetEggMetData(pk); + pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; + + pk.RefreshChecksum(); + return pk; + } + + private void SetEggMetData(PKM pk) + { + pk.IsEgg = true; + pk.EggMetDate = Date; + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); + pk.IsNicknamed = true; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = PersonalTable.USUM.GetFormEntry(Species, Form); + pk.Nature = (int)criteria.GetNature((Nature)Nature); + pk.Gender = criteria.GetGender(Gender, pi); + var av = GetAbilityIndex(criteria); + pk.RefreshAbility(av); + SetPID(pk); + SetIVs(pk); + } + + private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch + { + 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 + 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H + _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), + }; + + public override AbilityPermission Ability => AbilityType switch + { + 0 => AbilityPermission.OnlyFirst, + 1 => AbilityPermission.OnlySecond, + 2 => AbilityPermission.OnlyHidden, + 3 => AbilityPermission.Any12, + _ => AbilityPermission.Any12H, + }; + + private void SetPID(PKM pk) + { + switch (PIDType) + { + case ShinyType6.FixedValue: // Specified + pk.PID = PID; + break; + case ShinyType6.Random: // Random + pk.PID = Util.Rand32(); + break; + case ShinyType6.Always: // Random Shiny + pk.PID = Util.Rand32(); + pk.PID = (uint)(((pk.TID ^ pk.SID ^ (pk.PID & 0xFFFF)) << 16) | (pk.PID & 0xFFFF)); + break; + case ShinyType6.Never: // Random Nonshiny + pk.PID = Util.Rand32(); + if (pk.IsShiny) pk.PID ^= 0x10000000; + break; + } + } + + private void SetIVs(PKM pk) + { + Span finalIVs = stackalloc int[6]; + GetIVs(finalIVs); + var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); + var rng = Util.Rand; + if (ivflag == 0) // Random IVs + { + for (int i = 0; i < finalIVs.Length; i++) + { + if (finalIVs[i] > 31) + finalIVs[i] = rng.Next(32); + } + } + else // 1/2/3 perfect IVs + { + int IVCount = ivflag - 0xFB; + do { finalIVs[rng.Next(6)] = 31; } + while (finalIVs.Count(31) < IVCount); + for (int i = 0; i < finalIVs.Length; i++) + { + if (finalIVs[i] != 31) + finalIVs[i] = rng.Next(32); + } + } + pk.SetIVs(finalIVs); + } + + public bool IsAshGreninjaWC7(PKM pk) + { + return CardID == 2046 && ((pk.SID << 16) | pk.TID) == 0x79F57B49; + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsEgg) + { + if (OTGender != 3) + { + if (SID != pk.SID) return false; + if (TID != pk.TID) return false; + if (OTGender != pk.OT_Gender) return false; + } + if (!string.IsNullOrEmpty(OT_Name) && OT_Name != pk.OT_Name) return false; + if (OriginGame != 0 && OriginGame != pk.Version) return false; + if (EncryptionConstant != 0 && EncryptionConstant != pk.EncryptionConstant) return false; + if (Language != 0 && Language != pk.Language) return false; + } + + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, pk.Format)) + return false; + + if (IsEgg) + { + if (EggLocation != pk.Egg_Location) // traded + { + if (pk.Egg_Location != Locations.LinkTrade6) + return false; + } + else if (PIDType == 0 && pk.IsShiny) + { + return false; // can't be traded away for un-shiny + } + + if (pk.IsEgg && !pk.IsNative) + return false; + } + else + { + if (!Shiny.IsValid(pk)) return false; + if (!IsMatchEggLocation(pk)) return false; + if (MetLocation != pk.Met_Location) return false; + } + + if (MetLevel != pk.Met_Level) return false; + if (Ball != pk.Ball) return false; + if (OTGender < 3 && OTGender != pk.OT_Gender) return false; + if (Nature != -1 && pk.Nature != Nature) return false; + if (Gender != 3 && Gender != pk.Gender) return false; + + if (pk is IContestStats s && s.IsContestBelow(this)) + return false; + + if (CardID is 1122 or 1133 && !CanBeReceivedByVersion(pk.Version)) + return false; // Each version pair has a separate card -- since we aren't using deferral/partial match logic to reorder, just return false. + if (CardID == 2046) // Greninja WC has variant PID and can arrive @ 36 or 37 + return pk.SM; // not USUM + + return PIDType != 0 || pk.PID == PID; + } + + public override GameVersion Version + { + get => CardID == 2046 ? GameVersion.SM : GameVersion.Gen7; + set { } + } + + protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + + protected override bool IsMatchPartial(PKM pk) + { + if (RestrictLanguage != 0 && RestrictLanguage != pk.Language) + return true; + if (!CanBeReceivedByVersion(pk.Version)) + return true; + return false; + } } diff --git a/PKHeX.Core/MysteryGifts/WC7Full.cs b/PKHeX.Core/MysteryGifts/WC7Full.cs index 6af1225ae..2db905b0a 100644 --- a/PKHeX.Core/MysteryGifts/WC7Full.cs +++ b/PKHeX.Core/MysteryGifts/WC7Full.cs @@ -1,59 +1,58 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class WC7Full { - public sealed class WC7Full + public const int Size = 0x310; + private const int GiftStart = Size - WC7.Size; + public readonly byte[] Data; + public readonly WC7 Gift; + + public byte RestrictVersion { get => Data[0]; set => Data[0] = value; } + public byte RestrictLanguage { get => Data[0x1FF]; set => Data[0x1FF] = value; } + + public WC7Full(byte[] data) { - public const int Size = 0x310; - private const int GiftStart = Size - WC7.Size; - public readonly byte[] Data; - public readonly WC7 Gift; + Data = data; + var wc7 = data.SliceEnd(GiftStart); + Gift = new WC7(wc7); + var now = DateTime.Now; + Gift.RawDate = WC7.SetDate((uint)now.Year, (uint)now.Month, (uint)now.Day); - public byte RestrictVersion { get => Data[0]; set => Data[0] = value; } - public byte RestrictLanguage { get => Data[0x1FF]; set => Data[0x1FF] = value; } - - public WC7Full(byte[] data) - { - Data = data; - var wc7 = data.SliceEnd(GiftStart); - Gift = new WC7(wc7); - var now = DateTime.Now; - Gift.RawDate = WC7.SetDate((uint)now.Year, (uint)now.Month, (uint)now.Day); - - Gift.RestrictVersion = RestrictVersion; - Gift.RestrictLanguage = RestrictLanguage; - } - - public static WC7[] GetArray(ReadOnlySpan wc7Full, ReadOnlySpan data) - { - var countfull = wc7Full.Length / Size; - var countgift = data.Length / WC7.Size; - var result = new WC7[countfull + countgift]; - - var now = DateTime.Now; - for (int i = 0; i < countfull; i++) - result[i] = ReadWC7(wc7Full, i * Size, now); - for (int i = 0; i < countgift; i++) - result[i + countfull] = ReadWC7Only(data, i * WC7.Size); - - return result; - } - - private static WC7 ReadWC7(ReadOnlySpan data, int ofs, DateTime date) - { - var slice = data.Slice(ofs + GiftStart, WC7.Size).ToArray(); - return new WC7(slice) - { - RestrictVersion = data[ofs], - RestrictLanguage = data[ofs + 0x1FF], - RawDate = WC7.SetDate((uint) date.Year, (uint) date.Month, (uint) date.Day), - }; - } - - private static WC7 ReadWC7Only(ReadOnlySpan data, int ofs) - { - var slice = data.Slice(ofs, WC7.Size).ToArray(); - return new WC7(slice); - } + Gift.RestrictVersion = RestrictVersion; + Gift.RestrictLanguage = RestrictLanguage; } -} \ No newline at end of file + + public static WC7[] GetArray(ReadOnlySpan wc7Full, ReadOnlySpan data) + { + var countfull = wc7Full.Length / Size; + var countgift = data.Length / WC7.Size; + var result = new WC7[countfull + countgift]; + + var now = DateTime.Now; + for (int i = 0; i < countfull; i++) + result[i] = ReadWC7(wc7Full, i * Size, now); + for (int i = 0; i < countgift; i++) + result[i + countfull] = ReadWC7Only(data, i * WC7.Size); + + return result; + } + + private static WC7 ReadWC7(ReadOnlySpan data, int ofs, DateTime date) + { + var slice = data.Slice(ofs + GiftStart, WC7.Size).ToArray(); + return new WC7(slice) + { + RestrictVersion = data[ofs], + RestrictLanguage = data[ofs + 0x1FF], + RawDate = WC7.SetDate((uint) date.Year, (uint) date.Month, (uint) date.Day), + }; + } + + private static WC7 ReadWC7Only(ReadOnlySpan data, int ofs) + { + var slice = data.Slice(ofs, WC7.Size).ToArray(); + return new WC7(slice); + } +} diff --git a/PKHeX.Core/MysteryGifts/WC8.cs b/PKHeX.Core/MysteryGifts/WC8.cs index 00c2faf0a..35a5c4a4f 100644 --- a/PKHeX.Core/MysteryGifts/WC8.cs +++ b/PKHeX.Core/MysteryGifts/WC8.cs @@ -1,852 +1,851 @@ -using System; +using System; using System.Collections.Generic; using static PKHeX.Core.RibbonIndex; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 Mystery Gift Template File +/// +public sealed class WC8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDynamaxLevel, IRibbonIndex, IMemoryOT, ILangNicknamedTemplate, IEncounterServerDate, + IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8 { - /// - /// Generation 8 Mystery Gift Template File - /// - public sealed class WC8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDynamaxLevel, IRibbonIndex, IMemoryOT, ILangNicknamedTemplate, IEncounterServerDate, - IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8 + public const int Size = 0x2D0; + public const int CardStart = 0x0; + + public override int Generation => 8; + + public enum GiftType : byte { - public const int Size = 0x2D0; - public const int CardStart = 0x0; - - public override int Generation => 8; - - public enum GiftType : byte - { - None = 0, - Pokemon = 1, - Item = 2, - BP = 3, - Clothing = 4, - } - - public WC8() : this(new byte[Size]) { } - public WC8(byte[] data) : base(data) { } - - // TODO: public byte RestrictVersion? - - public bool CanBeReceivedByVersion(int v) => v is (int) GameVersion.SW or (int) GameVersion.SH; - - // General Card Properties - public override int CardID - { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x8)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x8), (ushort)value); - } - - public byte CardFlags { get => Data[CardStart + 0x10]; set => Data[CardStart + 0x10] = value; } - public GiftType CardType { get => (GiftType)Data[CardStart + 0x11]; set => Data[CardStart + 0x11] = (byte)value; } - public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); } - public override bool GiftUsed { get => false; set { } } - - public int CardTitleIndex - { - get => Data[CardStart + 0x15]; - set => Data[CardStart + 0x15] = (byte) value; - } - - public override string CardTitle - { - get => "Mystery Gift"; // TODO: Use text string from CardTitleIndex - set => throw new Exception(); - } - - // Item Properties - public override bool IsItem { get => CardType == GiftType.Item; set { if (value) CardType = GiftType.Item; } } - - public override int ItemID - { - get => GetItem(0); - set => SetItem(0, (ushort)value); - } - - public override int Quantity - { - get => GetQuantity(0); - set => SetQuantity(0, (ushort)value); - } - - public int GetItem(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x20 + (0x4 * index))); - public void SetItem(int index, ushort item) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x20 + (4 * index)), item); - public int GetQuantity(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22 + (0x4 * index))); - public void SetQuantity(int index, ushort quantity) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22 + (4 * index)), quantity); - - // Pokémon Properties - public override bool IsEntity { get => CardType == GiftType.Pokemon; set { if (value) CardType = GiftType.Pokemon; } } - - public override bool IsShiny => Shiny.IsShiny(); - - public override Shiny Shiny => PIDType switch - { - ShinyType8.FixedValue => FixedShinyType(DateTime.UtcNow), - ShinyType8.Random => Shiny.Random, - ShinyType8.Never => Shiny.Never, - ShinyType8.AlwaysStar => Shiny.AlwaysStar, - ShinyType8.AlwaysSquare => Shiny.AlwaysSquare, - _ => throw new ArgumentOutOfRangeException(), - }; - - private Shiny FixedShinyType(DateTime date) => IsHOMEGift && IsHOMEShinyPossible(date) ? Shiny.Random : GetShinyXor() switch - { - 0 => Shiny.AlwaysSquare, - <= 15 => Shiny.AlwaysStar, - _ => Shiny.Never, - }; - - private int GetShinyXor() - { - // Player owned anti-shiny fixed PID - if (TID == 0 && SID == 0) - return int.MaxValue; - - var pid = PID; - var psv = (int)(pid >> 16 ^ (pid & 0xFFFF)); - var tsv = (TID ^ SID); - return psv ^ tsv; - } - - public override int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x20)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x20), (ushort)value); - } - - public override int SID { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22), (ushort)value); - } - - public int OriginGame - { - get => ReadInt32LittleEndian(Data.AsSpan(CardStart + 0x24)); - set => WriteInt32LittleEndian(Data.AsSpan(CardStart + 0x24), value); - } - - public uint EncryptionConstant - { - get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x28)); - set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x28), value); - } - - public uint PID - { - get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x2C)); - set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x2C), value); - } - - // Nicknames, OT Names 0x30 - 0x228 - public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x228)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x228), (ushort)value); } - public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22A)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22A), (ushort)value); } - - public override int Ball - { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22C)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22C), (ushort)value); - } - - public override int HeldItem - { - get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22E)); - set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22E), (ushort)value); - } - - public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x230)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x230), (ushort)value); } - public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x232)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x232), (ushort)value); } - public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x234)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x234), (ushort)value); } - public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x236)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x236), (ushort)value); } - public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x238)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x238), (ushort)value); } - public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x23A)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x23A), (ushort)value); } - public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x23C)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x23C), (ushort)value); } - public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x23E)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x23E), (ushort)value); } - - public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x240)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x240), (ushort)value); } - public override int Form { get => Data[CardStart + 0x242]; set => Data[CardStart + 0x242] = (byte)value; } - public override int Gender { get => Data[CardStart + 0x243]; set => Data[CardStart + 0x243] = (byte)value; } - public override byte Level { get => Data[CardStart + 0x244]; set => Data[CardStart + 0x244] = value; } - public override bool IsEgg { get => Data[CardStart + 0x245] == 1; set => Data[CardStart + 0x245] = value ? (byte)1 : (byte)0; } - public int Nature { get => (sbyte)Data[CardStart + 0x246]; set => Data[CardStart + 0x246] = (byte)value; } - public override int AbilityType { get => Data[CardStart + 0x247]; set => Data[CardStart + 0x247] = (byte)value; } - - public ShinyType8 PIDType { get => (ShinyType8)Data[CardStart + 0x248]; set => Data[CardStart + 0x248] = (byte)value; } - - public int MetLevel { get => Data[CardStart + 0x249]; set => Data[CardStart + 0x249] = (byte)value; } - public byte DynamaxLevel { get => Data[CardStart + 0x24A]; set => Data[CardStart + 0x24A] = value; } - public bool CanGigantamax { get => Data[CardStart + 0x24B] != 0; set => Data[CardStart + 0x24B] = value ? (byte)1 : (byte)0; } - - // Ribbons 0x24C-0x26C - private const int RibbonBytesOffset = 0x24C; - private const int RibbonBytesCount = 0x20; - private const int RibbonByteNone = 0xFF; // signed -1 - - public bool HasMark() - { - for (int i = 0; i < RibbonBytesCount; i++) - { - var value = Data[RibbonBytesOffset + i]; - if (value == RibbonByteNone) - return false; - if ((RibbonIndex)value is >= MarkLunchtime and <= MarkSlump) - return true; - } - return false; - } - - public byte GetRibbonAtIndex(int byteIndex) - { - if ((uint)byteIndex >= RibbonBytesCount) - throw new IndexOutOfRangeException(); - return Data[RibbonBytesOffset + byteIndex]; - } - - public void SetRibbonAtIndex(int byteIndex, byte ribbonIndex) - { - if ((uint)byteIndex >= RibbonBytesCount) - throw new IndexOutOfRangeException(); - Data[RibbonBytesOffset + byteIndex] = ribbonIndex; - } - - public int IV_HP { get => Data[CardStart + 0x26C]; set => Data[CardStart + 0x26C] = (byte)value; } - public int IV_ATK { get => Data[CardStart + 0x26D]; set => Data[CardStart + 0x26D] = (byte)value; } - public int IV_DEF { get => Data[CardStart + 0x26E]; set => Data[CardStart + 0x26E] = (byte)value; } - public int IV_SPE { get => Data[CardStart + 0x26F]; set => Data[CardStart + 0x26F] = (byte)value; } - public int IV_SPA { get => Data[CardStart + 0x270]; set => Data[CardStart + 0x270] = (byte)value; } - public int IV_SPD { get => Data[CardStart + 0x271]; set => Data[CardStart + 0x271] = (byte)value; } - - public int OTGender { get => Data[CardStart + 0x272]; set => Data[CardStart + 0x272] = (byte)value; } - - public int EV_HP { get => Data[CardStart + 0x273]; set => Data[CardStart + 0x273] = (byte)value; } - public int EV_ATK { get => Data[CardStart + 0x274]; set => Data[CardStart + 0x274] = (byte)value; } - public int EV_DEF { get => Data[CardStart + 0x275]; set => Data[CardStart + 0x275] = (byte)value; } - public int EV_SPE { get => Data[CardStart + 0x276]; set => Data[CardStart + 0x276] = (byte)value; } - public int EV_SPA { get => Data[CardStart + 0x277]; set => Data[CardStart + 0x277] = (byte)value; } - public int EV_SPD { get => Data[CardStart + 0x278]; set => Data[CardStart + 0x278] = (byte)value; } - - public byte OT_Intensity { get => Data[CardStart + 0x279]; set => Data[CardStart + 0x279] = value; } - public byte OT_Memory { get => Data[CardStart + 0x27A]; set => Data[CardStart + 0x27A] = value; } - public byte OT_Feeling { get => Data[CardStart + 0x27B]; set => Data[CardStart + 0x27B] = value; } - public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x27C)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x27C), value); } - - // Meta Accessible Properties - public override int[] IVs - { - get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; - set - { - if (value.Length != 6) return; - IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; - IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; - } - } - - public override void GetIVs(Span value) - { - if (value.Length != 6) - return; - value[0] = IV_HP; - value[1] = IV_ATK; - value[2] = IV_DEF; - value[3] = IV_SPE; - value[4] = IV_SPA; - value[5] = IV_SPD; - } - - public int[] EVs - { - get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; - set - { - if (value.Length != 6) return; - EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; - EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; - } - } - - public bool GetIsNicknamed(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetNicknameOffset(language))) != 0; - - public bool CanBeAnyLanguage() - { - for (int i = 0; i < 9; i++) - { - var ofs = GetLanguageOffset(i); - var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); - if (lang != 0) - return false; - } - return true; - } - - public bool CanHaveLanguage(int language) - { - if (language is < (int)LanguageID.Japanese or > (int)LanguageID.ChineseT) - return false; - - if (CanBeAnyLanguage()) - return true; - - for (int i = 0; i < 9; i++) - { - var ofs = GetLanguageOffset(i); - var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); - if (lang == language) - return true; - } - return GetLanguage(language) == 0; - } - - public int GetLanguage(int redeemLanguage) => Data[GetLanguageOffset(GetLanguageIndex(redeemLanguage))]; - private static int GetLanguageOffset(int index) => 0x30 + (index * 0x1C) + 0x1A; - - public bool GetHasOT(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetOTOffset(language))) != 0; - - private static int GetLanguageIndex(int language) - { - var lang = (LanguageID) language; - if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.ChineseT) - return (int) LanguageID.English; // fallback - return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2; - } - - public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } - - public override IReadOnlyList Moves - { - get => new[] { Move1, Move2, Move3, Move4 }; - set - { - if (value.Count > 0) Move1 = value[0]; - if (value.Count > 1) Move2 = value[1]; - if (value.Count > 2) Move3 = value[2]; - if (value.Count > 3) Move4 = value[3]; - } - } - - public override IReadOnlyList Relearn - { - get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; - set - { - if (value.Count > 0) RelearnMove1 = value[0]; - if (value.Count > 1) RelearnMove2 = value[1]; - if (value.Count > 2) RelearnMove3 = value[2]; - if (value.Count > 3) RelearnMove4 = value[3]; - } - } - - public override string OT_Name { get; set; } = string.Empty; - public string Nickname => string.Empty; - public bool IsNicknamed => false; - public int Language => 2; - - public string GetNickname(int language) => StringConverter8.GetString(Data.AsSpan(GetNicknameOffset(language), 0x1A)); - public void SetNickname(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetNicknameOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - - public string GetOT(int language) => StringConverter8.GetString(Data.AsSpan(GetOTOffset(language), 0x1A)); - public void SetOT(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetOTOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - - private static int GetNicknameOffset(int language) - { - int index = GetLanguageIndex(language); - return 0x30 + (index * 0x1C); - } - - private static int GetOTOffset(int language) - { - int index = GetLanguageIndex(language); - return 0x12C + (index * 0x1C); - } - - public bool IsHOMEGift => CardID >= 9000; - - public bool CanHandleOT(int language) => !GetHasOT(language); - - public override GameVersion Version - { - get => OriginGame != 0 ? (GameVersion)OriginGame : GameVersion.SWSH; - set { } - } - - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - if (!IsEntity) - throw new ArgumentException(nameof(IsEntity)); - - int currentLevel = Level > 0 ? Level : (1 + Util.Rand.Next(100)); - int metLevel = MetLevel > 0 ? MetLevel : currentLevel; - var pi = PersonalTable.SWSH.GetFormEntry(Species, Form); - var language = sav.Language; - var OT = GetOT(language); - bool hasOT = GetHasOT(language); - - var pk = new PK8 - { - EncryptionConstant = EncryptionConstant != 0 || IsHOMEGift ? EncryptionConstant : Util.Rand32(), - TID = TID, - SID = SID, - Species = Species, - Form = Form, - CurrentLevel = currentLevel, - Ball = Ball != 0 ? Ball : 4, // Default is Pokeball - Met_Level = metLevel, - HeldItem = HeldItem, - - EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), - - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - RelearnMove1 = RelearnMove1, - RelearnMove2 = RelearnMove2, - RelearnMove3 = RelearnMove3, - RelearnMove4 = RelearnMove4, - - Version = OriginGame != 0 ? OriginGame : sav.Game, - - OT_Name = OT.Length > 0 ? OT : sav.OT, - OT_Gender = OTGender < 2 ? OTGender : sav.Gender, - HT_Name = hasOT ? sav.OT : string.Empty, - HT_Gender = hasOT ? sav.Gender : 0, - HT_Language = (byte)(hasOT ? language : 0), - CurrentHandler = hasOT ? 1 : 0, - OT_Friendship = pi.BaseFriendship, - - OT_Intensity = OT_Intensity, - OT_Memory = OT_Memory, - OT_TextVar = OT_TextVar, - OT_Feeling = OT_Feeling, - FatefulEncounter = true, - - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPE = EV_SPE, - EV_SPA = EV_SPA, - EV_SPD = EV_SPD, - - CanGigantamax = CanGigantamax, - DynamaxLevel = DynamaxLevel, - - Met_Location = MetLocation, - Egg_Location = EggLocation, - }; - pk.SetMaximumPPCurrent(); - - if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version)) - { - // give random valid game - var rnd = Util.Rand; - do { pk.Version = (int)GameVersion.SW + rnd.Next(2); } - while (!CanBeReceivedByVersion(pk.Version)); - } - - if (OTGender >= 2) - { - pk.TID = sav.TID; - pk.SID = sav.SID; - - if (IsHOMEGift) - { - pk.TrainerSID7 = 0; - while (pk.TSV == 0) - { - pk.TrainerID7 = Util.Rand.Next(16, 999_999 + 1); - } - } - } - - // Official code explicitly corrects for Meowstic - if (pk.Species == (int)Core.Species.Meowstic) - pk.Form = pk.Gender; - - pk.MetDate = IsDateRestricted && EncounterServerDate.WC8Gifts.TryGetValue(CardID, out var dt) ? dt : DateTime.Now; - - var nickname_language = GetLanguage(language); - pk.Language = nickname_language != 0 ? nickname_language : sav.Language; - pk.IsNicknamed = GetIsNicknamed(language); - pk.Nickname = pk.IsNicknamed ? GetNickname(language) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); - - for (var i = 0; i < RibbonBytesCount; i++) - { - var ribbon = GetRibbonAtIndex(i); - if (ribbon != RibbonByteNone) - pk.SetRibbon(ribbon); - } - - SetPINGA(pk, criteria); - - if (IsEgg) - SetEggMetData(pk); - pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; - - if (!IsHOMEGift) - { - pk.HeightScalar = PokeSizeUtil.GetRandomScalar(); - pk.WeightScalar = PokeSizeUtil.GetRandomScalar(); - } - - pk.ResetPartyStats(); - pk.RefreshChecksum(); - return pk; - } - - private void SetEggMetData(PKM pk) - { - pk.IsEgg = true; - pk.EggMetDate = DateTime.Now; - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); - pk.IsNicknamed = true; - } - - private void SetPINGA(PKM pk, EncounterCriteria criteria) - { - var pi = PersonalTable.SWSH.GetFormEntry(Species, Form); - pk.Nature = (int)criteria.GetNature(Nature == -1 ? Core.Nature.Random : (Nature)Nature); - pk.StatNature = pk.Nature; - pk.Gender = criteria.GetGender(Gender, pi); - var av = GetAbilityIndex(criteria); - pk.RefreshAbility(av); - SetPID(pk, pk.MetDate ?? DateTime.UtcNow); - SetIVs(pk); - } - - private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch - { - 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 - 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H - _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), - }; - - public override AbilityPermission Ability => AbilityType switch - { - 0 => AbilityPermission.OnlyFirst, - 1 => AbilityPermission.OnlySecond, - 2 => AbilityPermission.OnlyHidden, - 3 => AbilityPermission.Any12, - _ => AbilityPermission.Any12H, - }; - - private uint GetPID(ITrainerID tr, ShinyType8 type, DateTime date) => type switch - { - ShinyType8.Never => GetAntishiny(tr), // Random, Never Shiny - ShinyType8.Random => Util.Rand32(), // Random, Any - ShinyType8.AlwaysStar => (uint) (((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 1) << 16) | (PID & 0xFFFF)), // Fixed, Force Star - ShinyType8.AlwaysSquare => (uint) (((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 0) << 16) | (PID & 0xFFFF)), // Fixed, Force Square - ShinyType8.FixedValue => GetFixedPID(tr, date), - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; - - private uint GetFixedPID(ITrainerID tr, DateTime date) - { - var pid = PID; - if (pid != 0 && !(TID == 0 && SID == 0)) - return pid; - - if (!tr.IsShiny(pid, 8)) - return pid; - if (IsHOMEGift && !IsHOMEShinyPossible(date)) - return GetAntishinyFixedHOME(tr); - return pid; - } - - private static uint GetAntishinyFixedHOME(ITrainerID tr) - { - var fid = ((uint)(tr.SID << 16) | (uint)tr.TID); - return fid ^ 0x10u; - } - - private static uint GetAntishiny(ITrainerID tr) - { - var pid = Util.Rand32(); - if (tr.IsShiny(pid, 8)) - return pid ^ 0x1000_0000; - return pid; - } - - private void SetPID(PKM pk, DateTime date) - { - pk.PID = GetPID(pk, PIDType, date); - } - - private void SetIVs(PKM pk) - { - Span finalIVs = stackalloc int[6]; - GetIVs(finalIVs); - var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); - var rng = Util.Rand; - if (ivflag == 0) // Random IVs - { - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] > 31) - finalIVs[i] = rng.Next(32); - } - } - else // 1/2/3 perfect IVs - { - int IVCount = ivflag - 0xFB; - do { finalIVs[rng.Next(6)] = 31; } - while (finalIVs.Count(31) < IVCount); - for (int i = 0; i < finalIVs.Length; i++) - { - if (finalIVs[i] != 31) - finalIVs[i] = rng.Next(32); - } - } - pk.SetIVs(finalIVs); - } - - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) - { - if (!IsEgg) - { - if (OTGender < 2) - { - if (SID != pkm.SID) return false; - if (TID != pkm.TID) return false; - if (OTGender != pkm.OT_Gender) return false; - } - - if (!CanBeAnyLanguage() && !CanHaveLanguage(pkm.Language)) - return false; - - var OT = GetOT(pkm.Language); // May not be guaranteed to work. - if (!string.IsNullOrEmpty(OT) && OT != pkm.OT_Name) return false; - if (OriginGame != 0 && OriginGame != pkm.Version) return false; - if (EncryptionConstant != 0) - { - if (EncryptionConstant != pkm.EncryptionConstant) - return false; - } - else if (IsHOMEGift)// 0 - { - // HOME gifts -- PID and EC are zeroes... - if (EncryptionConstant != pkm.EncryptionConstant) - return false; - - if (pkm.TSV == 0) // HOME doesn't assign TSV=0 to accounts. - return false; - - if (IsShiny) - { - if (!pkm.IsShiny) - return false; - } - else // Never or Random (HOME ID specific) - { - if (pkm.IsShiny && !IsHOMEShinyPossible(pkm.MetDate ?? DateTime.UtcNow)) - return false; - } - } - } - - if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pkm.Form, pkm.Format)) - return false; - - var shinyType = Shiny; - if (PIDType == ShinyType8.FixedValue) - shinyType = FixedShinyType(pkm.MetDate ?? DateTime.UtcNow); - if (IsEgg) - { - if (EggLocation != pkm.Egg_Location) // traded - { - if (pkm.Egg_Location != Locations.LinkTrade6) - return false; - if (PIDType == ShinyType8.Random && pkm.IsShiny && pkm.ShinyXor > 1) - return false; // shiny traded egg will always have xor0/1. - } - if (!shinyType.IsValid(pkm)) - { - return false; // can't be traded away for unshiny - } - - if (pkm.IsEgg && !pkm.IsNative) - return false; - } - else - { - if (!shinyType.IsValid(pkm)) return false; - if (!IsMatchEggLocation(pkm)) return false; - if (MetLocation != pkm.Met_Location) return false; - } - - if (MetLevel != 0 && MetLevel != pkm.Met_Level) return false; - if ((Ball == 0 ? 4 : Ball) != pkm.Ball) return false; - if (OTGender < 2 && OTGender != pkm.OT_Gender) return false; - if (Nature != -1 && pkm.Nature != Nature) return false; - if (Gender != 3 && Gender != pkm.Gender) return false; - - if (pkm is PK8 and IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form)) - return false; - - if (pkm is not IDynamaxLevel dl || dl.DynamaxLevel < DynamaxLevel) - return false; - - if (IsHOMEGift && pkm is IScaledSize s) - { - if (s.HeightScalar != 0) - return false; - if (s.WeightScalar != 0) - return false; - } - - // Duplicate card; one with Nickname specified and another without. - if (CardID == 0122 && pkm.IsNicknamed != GetIsNicknamed(pkm.Language)) - return false; - - // PID Types 0 and 1 do not use the fixed PID value. - // Values 2,3 are specific shiny states, and 4 is fixed value. - // 2,3,4 can change if it is a traded egg to ensure the same shiny state. - var type = PIDType; - if (type is ShinyType8.Never or ShinyType8.Random) - return true; - return pkm.PID == GetPID(pkm, type, pkm.MetDate ?? DateTime.UtcNow); - } - - private bool IsHOMEShinyPossible(DateTime date) - { - // no defined TID/SID and having a fixed PID can cause the player's TID/SID to match the PID's shiny calc. - return TID == 0 && SID == 0 && PID != 0 && (CardID < 9015 && date < new DateTime(2022, 5, 18)); - } - - public bool IsDateRestricted => IsHOMEGift; - - protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species; - protected override bool IsMatchPartial(PKM pkm) => false; // no version compatibility checks yet. - - #region Lazy Ribbon Implementation - public bool RibbonEarth { get => this.GetRibbonIndex(Earth); set => this.SetRibbonIndex(Earth, value); } - public bool RibbonNational { get => this.GetRibbonIndex(National); set => this.SetRibbonIndex(National, value); } - public bool RibbonCountry { get => this.GetRibbonIndex(Country); set => this.SetRibbonIndex(Country, value); } - public bool RibbonChampionBattle { get => this.GetRibbonIndex(ChampionBattle); set => this.SetRibbonIndex(ChampionBattle, value); } - public bool RibbonChampionRegional { get => this.GetRibbonIndex(ChampionRegional); set => this.SetRibbonIndex(ChampionRegional, value); } - public bool RibbonChampionNational { get => this.GetRibbonIndex(ChampionNational); set => this.SetRibbonIndex(ChampionNational, value); } - public bool RibbonClassic { get => this.GetRibbonIndex(Classic); set => this.SetRibbonIndex(Classic, value); } - public bool RibbonWishing { get => this.GetRibbonIndex(Wishing); set => this.SetRibbonIndex(Wishing, value); } - public bool RibbonPremier { get => this.GetRibbonIndex(Premier); set => this.SetRibbonIndex(Premier, value); } - public bool RibbonEvent { get => this.GetRibbonIndex(Event); set => this.SetRibbonIndex(Event, value); } - public bool RibbonBirthday { get => this.GetRibbonIndex(Birthday); set => this.SetRibbonIndex(Birthday, value); } - public bool RibbonSpecial { get => this.GetRibbonIndex(Special); set => this.SetRibbonIndex(Special, value); } - public bool RibbonWorld { get => this.GetRibbonIndex(World); set => this.SetRibbonIndex(World, value); } - public bool RibbonChampionWorld { get => this.GetRibbonIndex(ChampionWorld); set => this.SetRibbonIndex(ChampionWorld, value); } - public bool RibbonSouvenir { get => this.GetRibbonIndex(Souvenir); set => this.SetRibbonIndex(Souvenir, value); } - public bool RibbonChampionG3 { get => this.GetRibbonIndex(ChampionG3); set => this.SetRibbonIndex(ChampionG3, value); } - public bool RibbonArtist { get => this.GetRibbonIndex(Artist); set => this.SetRibbonIndex(Artist, value); } - public bool RibbonEffort { get => this.GetRibbonIndex(Effort); set => this.SetRibbonIndex(Effort, value); } - public bool RibbonChampionSinnoh { get => this.GetRibbonIndex(ChampionSinnoh); set => this.SetRibbonIndex(ChampionSinnoh, value); } - public bool RibbonAlert { get => this.GetRibbonIndex(Alert); set => this.SetRibbonIndex(Alert, value); } - public bool RibbonShock { get => this.GetRibbonIndex(Shock); set => this.SetRibbonIndex(Shock, value); } - public bool RibbonDowncast { get => this.GetRibbonIndex(Downcast); set => this.SetRibbonIndex(Downcast, value); } - public bool RibbonCareless { get => this.GetRibbonIndex(Careless); set => this.SetRibbonIndex(Careless, value); } - public bool RibbonRelax { get => this.GetRibbonIndex(Relax); set => this.SetRibbonIndex(Relax, value); } - public bool RibbonSnooze { get => this.GetRibbonIndex(Snooze); set => this.SetRibbonIndex(Snooze, value); } - public bool RibbonSmile { get => this.GetRibbonIndex(Smile); set => this.SetRibbonIndex(Smile, value); } - public bool RibbonGorgeous { get => this.GetRibbonIndex(Gorgeous); set => this.SetRibbonIndex(Gorgeous, value); } - public bool RibbonRoyal { get => this.GetRibbonIndex(Royal); set => this.SetRibbonIndex(Royal, value); } - public bool RibbonGorgeousRoyal { get => this.GetRibbonIndex(GorgeousRoyal); set => this.SetRibbonIndex(GorgeousRoyal, value); } - public bool RibbonFootprint { get => this.GetRibbonIndex(Footprint); set => this.SetRibbonIndex(Footprint, value); } - public bool RibbonRecord { get => this.GetRibbonIndex(Record); set => this.SetRibbonIndex(Record, value); } - public bool RibbonLegend { get => this.GetRibbonIndex(Legend); set => this.SetRibbonIndex(Legend, value); } - public bool RibbonChampionKalos { get => this.GetRibbonIndex(ChampionKalos); set => this.SetRibbonIndex(ChampionKalos, value); } - public bool RibbonChampionG6Hoenn { get => this.GetRibbonIndex(ChampionG6Hoenn); set => this.SetRibbonIndex(ChampionG6Hoenn, value); } - public bool RibbonBestFriends { get => this.GetRibbonIndex(BestFriends); set => this.SetRibbonIndex(BestFriends, value); } - public bool RibbonTraining { get => this.GetRibbonIndex(Training); set => this.SetRibbonIndex(Training, value); } - public bool RibbonBattlerSkillful { get => this.GetRibbonIndex(BattlerSkillful); set => this.SetRibbonIndex(BattlerSkillful, value); } - public bool RibbonBattlerExpert { get => this.GetRibbonIndex(BattlerExpert); set => this.SetRibbonIndex(BattlerExpert, value); } - public bool RibbonContestStar { get => this.GetRibbonIndex(ContestStar); set => this.SetRibbonIndex(ContestStar, value); } - public bool RibbonMasterCoolness { get => this.GetRibbonIndex(MasterCoolness); set => this.SetRibbonIndex(MasterCoolness, value); } - public bool RibbonMasterBeauty { get => this.GetRibbonIndex(MasterBeauty); set => this.SetRibbonIndex(MasterBeauty, value); } - public bool RibbonMasterCuteness { get => this.GetRibbonIndex(MasterCuteness); set => this.SetRibbonIndex(MasterCuteness, value); } - public bool RibbonMasterCleverness { get => this.GetRibbonIndex(MasterCleverness); set => this.SetRibbonIndex(MasterCleverness, value); } - public bool RibbonMasterToughness { get => this.GetRibbonIndex(MasterToughness); set => this.SetRibbonIndex(MasterToughness, value); } - - public int RibbonCountMemoryContest { get => 0; set { } } - public int RibbonCountMemoryBattle { get => 0; set { } } - - public bool RibbonChampionAlola { get => this.GetRibbonIndex(ChampionAlola); set => this.SetRibbonIndex(ChampionAlola, value); } - public bool RibbonBattleRoyale { get => this.GetRibbonIndex(BattleRoyale); set => this.SetRibbonIndex(BattleRoyale, value); } - public bool RibbonBattleTreeGreat { get => this.GetRibbonIndex(BattleTreeGreat); set => this.SetRibbonIndex(BattleTreeGreat, value); } - public bool RibbonBattleTreeMaster { get => this.GetRibbonIndex(BattleTreeMaster); set => this.SetRibbonIndex(BattleTreeMaster, value); } - public bool RibbonChampionGalar { get => this.GetRibbonIndex(ChampionGalar); set => this.SetRibbonIndex(ChampionGalar, value); } - public bool RibbonTowerMaster { get => this.GetRibbonIndex(TowerMaster); set => this.SetRibbonIndex(TowerMaster, value); } - public bool RibbonMasterRank { get => this.GetRibbonIndex(MasterRank); set => this.SetRibbonIndex(MasterRank, value); } - public bool RibbonMarkLunchtime { get => this.GetRibbonIndex(MarkLunchtime); set => this.SetRibbonIndex(MarkLunchtime, value); } - public bool RibbonMarkSleepyTime { get => this.GetRibbonIndex(MarkSleepyTime); set => this.SetRibbonIndex(MarkSleepyTime, value); } - public bool RibbonMarkDusk { get => this.GetRibbonIndex(MarkDusk); set => this.SetRibbonIndex(MarkDusk, value); } - public bool RibbonMarkDawn { get => this.GetRibbonIndex(MarkDawn); set => this.SetRibbonIndex(MarkDawn, value); } - public bool RibbonMarkCloudy { get => this.GetRibbonIndex(MarkCloudy); set => this.SetRibbonIndex(MarkCloudy, value); } - public bool RibbonMarkRainy { get => this.GetRibbonIndex(MarkRainy); set => this.SetRibbonIndex(MarkRainy, value); } - public bool RibbonMarkStormy { get => this.GetRibbonIndex(MarkStormy); set => this.SetRibbonIndex(MarkStormy, value); } - public bool RibbonMarkSnowy { get => this.GetRibbonIndex(MarkSnowy); set => this.SetRibbonIndex(MarkSnowy, value); } - public bool RibbonMarkBlizzard { get => this.GetRibbonIndex(MarkBlizzard); set => this.SetRibbonIndex(MarkBlizzard, value); } - public bool RibbonMarkDry { get => this.GetRibbonIndex(MarkDry); set => this.SetRibbonIndex(MarkDry, value); } - public bool RibbonMarkSandstorm { get => this.GetRibbonIndex(MarkSandstorm); set => this.SetRibbonIndex(MarkSandstorm, value); } - public bool RibbonMarkMisty { get => this.GetRibbonIndex(MarkMisty); set => this.SetRibbonIndex(MarkMisty, value); } - public bool RibbonMarkDestiny { get => this.GetRibbonIndex(MarkDestiny); set => this.SetRibbonIndex(MarkDestiny, value); } - public bool RibbonMarkFishing { get => this.GetRibbonIndex(MarkFishing); set => this.SetRibbonIndex(MarkFishing, value); } - public bool RibbonMarkCurry { get => this.GetRibbonIndex(MarkCurry); set => this.SetRibbonIndex(MarkCurry, value); } - public bool RibbonMarkUncommon { get => this.GetRibbonIndex(MarkUncommon); set => this.SetRibbonIndex(MarkUncommon, value); } - public bool RibbonMarkRare { get => this.GetRibbonIndex(MarkRare); set => this.SetRibbonIndex(MarkRare, value); } - public bool RibbonMarkRowdy { get => this.GetRibbonIndex(MarkRowdy); set => this.SetRibbonIndex(MarkRowdy, value); } - public bool RibbonMarkAbsentMinded { get => this.GetRibbonIndex(MarkAbsentMinded); set => this.SetRibbonIndex(MarkAbsentMinded, value); } - public bool RibbonMarkJittery { get => this.GetRibbonIndex(MarkJittery); set => this.SetRibbonIndex(MarkJittery, value); } - public bool RibbonMarkExcited { get => this.GetRibbonIndex(MarkExcited); set => this.SetRibbonIndex(MarkExcited, value); } - public bool RibbonMarkCharismatic { get => this.GetRibbonIndex(MarkCharismatic); set => this.SetRibbonIndex(MarkCharismatic, value); } - public bool RibbonMarkCalmness { get => this.GetRibbonIndex(MarkCalmness); set => this.SetRibbonIndex(MarkCalmness, value); } - public bool RibbonMarkIntense { get => this.GetRibbonIndex(MarkIntense); set => this.SetRibbonIndex(MarkIntense, value); } - public bool RibbonMarkZonedOut { get => this.GetRibbonIndex(MarkZonedOut); set => this.SetRibbonIndex(MarkZonedOut, value); } - public bool RibbonMarkJoyful { get => this.GetRibbonIndex(MarkJoyful); set => this.SetRibbonIndex(MarkJoyful, value); } - public bool RibbonMarkAngry { get => this.GetRibbonIndex(MarkAngry); set => this.SetRibbonIndex(MarkAngry, value); } - public bool RibbonMarkSmiley { get => this.GetRibbonIndex(MarkSmiley); set => this.SetRibbonIndex(MarkSmiley, value); } - public bool RibbonMarkTeary { get => this.GetRibbonIndex(MarkTeary); set => this.SetRibbonIndex(MarkTeary, value); } - public bool RibbonMarkUpbeat { get => this.GetRibbonIndex(MarkUpbeat); set => this.SetRibbonIndex(MarkUpbeat, value); } - public bool RibbonMarkPeeved { get => this.GetRibbonIndex(MarkPeeved); set => this.SetRibbonIndex(MarkPeeved, value); } - public bool RibbonMarkIntellectual { get => this.GetRibbonIndex(MarkIntellectual); set => this.SetRibbonIndex(MarkIntellectual, value); } - public bool RibbonMarkFerocious { get => this.GetRibbonIndex(MarkFerocious); set => this.SetRibbonIndex(MarkFerocious, value); } - public bool RibbonMarkCrafty { get => this.GetRibbonIndex(MarkCrafty); set => this.SetRibbonIndex(MarkCrafty, value); } - public bool RibbonMarkScowling { get => this.GetRibbonIndex(MarkScowling); set => this.SetRibbonIndex(MarkScowling, value); } - public bool RibbonMarkKindly { get => this.GetRibbonIndex(MarkKindly); set => this.SetRibbonIndex(MarkKindly, value); } - public bool RibbonMarkFlustered { get => this.GetRibbonIndex(MarkFlustered); set => this.SetRibbonIndex(MarkFlustered, value); } - public bool RibbonMarkPumpedUp { get => this.GetRibbonIndex(MarkPumpedUp); set => this.SetRibbonIndex(MarkPumpedUp, value); } - public bool RibbonMarkZeroEnergy { get => this.GetRibbonIndex(MarkZeroEnergy); set => this.SetRibbonIndex(MarkZeroEnergy, value); } - public bool RibbonMarkPrideful { get => this.GetRibbonIndex(MarkPrideful); set => this.SetRibbonIndex(MarkPrideful, value); } - public bool RibbonMarkUnsure { get => this.GetRibbonIndex(MarkUnsure); set => this.SetRibbonIndex(MarkUnsure, value); } - public bool RibbonMarkHumble { get => this.GetRibbonIndex(MarkHumble); set => this.SetRibbonIndex(MarkHumble, value); } - public bool RibbonMarkThorny { get => this.GetRibbonIndex(MarkThorny); set => this.SetRibbonIndex(MarkThorny, value); } - public bool RibbonMarkVigor { get => this.GetRibbonIndex(MarkVigor); set => this.SetRibbonIndex(MarkVigor, value); } - public bool RibbonMarkSlump { get => this.GetRibbonIndex(MarkSlump); set => this.SetRibbonIndex(MarkSlump, value); } - public bool RibbonTwinklingStar { get => this.GetRibbonIndex(TwinklingStar); set => this.SetRibbonIndex(TwinklingStar, value); } - public bool RibbonPioneer { get => this.GetRibbonIndex(Pioneer); set => this.SetRibbonIndex(Pioneer, value); } - - public int GetRibbonByte(int index) => Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z == index); - public bool GetRibbon(int index) => GetRibbonByte(index) >= 0; - - public void SetRibbon(int index, bool value = true) - { - if ((uint)index > (uint)MarkSlump) - throw new ArgumentOutOfRangeException(nameof(index)); - - if (value) - { - if (GetRibbon(index)) - return; - var openIndex = Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z != RibbonByteNone); - if (openIndex < 0) - throw new ArgumentOutOfRangeException(nameof(index)); - SetRibbonAtIndex(openIndex, (byte)index); - } - else - { - var ofs = GetRibbonByte(index); - if (ofs < 0) - return; - SetRibbonAtIndex(ofs, RibbonByteNone); - } - } - #endregion + None = 0, + Pokemon = 1, + Item = 2, + BP = 3, + Clothing = 4, } + + public WC8() : this(new byte[Size]) { } + public WC8(byte[] data) : base(data) { } + + // TODO: public byte RestrictVersion? + + public bool CanBeReceivedByVersion(int v) => v is (int) GameVersion.SW or (int) GameVersion.SH; + + // General Card Properties + public override int CardID + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x8)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x8), (ushort)value); + } + + public byte CardFlags { get => Data[CardStart + 0x10]; set => Data[CardStart + 0x10] = value; } + public GiftType CardType { get => (GiftType)Data[CardStart + 0x11]; set => Data[CardStart + 0x11] = (byte)value; } + public bool GiftRepeatable { get => (CardFlags & 1) == 0; set => CardFlags = (byte)((CardFlags & ~1) | (value ? 0 : 1)); } + public override bool GiftUsed { get => false; set { } } + + public int CardTitleIndex + { + get => Data[CardStart + 0x15]; + set => Data[CardStart + 0x15] = (byte) value; + } + + public override string CardTitle + { + get => "Mystery Gift"; // TODO: Use text string from CardTitleIndex + set => throw new Exception(); + } + + // Item Properties + public override bool IsItem { get => CardType == GiftType.Item; set { if (value) CardType = GiftType.Item; } } + + public override int ItemID + { + get => GetItem(0); + set => SetItem(0, (ushort)value); + } + + public override int Quantity + { + get => GetQuantity(0); + set => SetQuantity(0, (ushort)value); + } + + public int GetItem(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x20 + (0x4 * index))); + public void SetItem(int index, ushort item) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x20 + (4 * index)), item); + public int GetQuantity(int index) => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22 + (0x4 * index))); + public void SetQuantity(int index, ushort quantity) => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22 + (4 * index)), quantity); + + // Pokémon Properties + public override bool IsEntity { get => CardType == GiftType.Pokemon; set { if (value) CardType = GiftType.Pokemon; } } + + public override bool IsShiny => Shiny.IsShiny(); + + public override Shiny Shiny => PIDType switch + { + ShinyType8.FixedValue => FixedShinyType(DateTime.UtcNow), + ShinyType8.Random => Shiny.Random, + ShinyType8.Never => Shiny.Never, + ShinyType8.AlwaysStar => Shiny.AlwaysStar, + ShinyType8.AlwaysSquare => Shiny.AlwaysSquare, + _ => throw new ArgumentOutOfRangeException(), + }; + + private Shiny FixedShinyType(DateTime date) => IsHOMEGift && IsHOMEShinyPossible(date) ? Shiny.Random : GetShinyXor() switch + { + 0 => Shiny.AlwaysSquare, + <= 15 => Shiny.AlwaysStar, + _ => Shiny.Never, + }; + + private int GetShinyXor() + { + // Player owned anti-shiny fixed PID + if (TID == 0 && SID == 0) + return int.MaxValue; + + var pid = PID; + var psv = (int)((pid >> 16) ^ (pid & 0xFFFF)); + var tsv = (TID ^ SID); + return psv ^ tsv; + } + + public override int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x20)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x20), (ushort)value); + } + + public override int SID { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22), (ushort)value); + } + + public int OriginGame + { + get => ReadInt32LittleEndian(Data.AsSpan(CardStart + 0x24)); + set => WriteInt32LittleEndian(Data.AsSpan(CardStart + 0x24), value); + } + + public uint EncryptionConstant + { + get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x28)); + set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x28), value); + } + + public uint PID + { + get => ReadUInt32LittleEndian(Data.AsSpan(CardStart + 0x2C)); + set => WriteUInt32LittleEndian(Data.AsSpan(CardStart + 0x2C), value); + } + + // Nicknames, OT Names 0x30 - 0x228 + public override int EggLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x228)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x228), (ushort)value); } + public int MetLocation { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22A)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22A), (ushort)value); } + + public override int Ball + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22C)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22C), (ushort)value); + } + + public override int HeldItem + { + get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x22E)); + set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x22E), (ushort)value); + } + + public int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x230)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x230), (ushort)value); } + public int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x232)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x232), (ushort)value); } + public int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x234)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x234), (ushort)value); } + public int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x236)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x236), (ushort)value); } + public int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x238)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x238), (ushort)value); } + public int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x23A)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x23A), (ushort)value); } + public int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x23C)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x23C), (ushort)value); } + public int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x23E)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x23E), (ushort)value); } + + public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x240)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x240), (ushort)value); } + public override int Form { get => Data[CardStart + 0x242]; set => Data[CardStart + 0x242] = (byte)value; } + public override int Gender { get => Data[CardStart + 0x243]; set => Data[CardStart + 0x243] = (byte)value; } + public override byte Level { get => Data[CardStart + 0x244]; set => Data[CardStart + 0x244] = value; } + public override bool IsEgg { get => Data[CardStart + 0x245] == 1; set => Data[CardStart + 0x245] = value ? (byte)1 : (byte)0; } + public int Nature { get => (sbyte)Data[CardStart + 0x246]; set => Data[CardStart + 0x246] = (byte)value; } + public override int AbilityType { get => Data[CardStart + 0x247]; set => Data[CardStart + 0x247] = (byte)value; } + + public ShinyType8 PIDType { get => (ShinyType8)Data[CardStart + 0x248]; set => Data[CardStart + 0x248] = (byte)value; } + + public int MetLevel { get => Data[CardStart + 0x249]; set => Data[CardStart + 0x249] = (byte)value; } + public byte DynamaxLevel { get => Data[CardStart + 0x24A]; set => Data[CardStart + 0x24A] = value; } + public bool CanGigantamax { get => Data[CardStart + 0x24B] != 0; set => Data[CardStart + 0x24B] = value ? (byte)1 : (byte)0; } + + // Ribbons 0x24C-0x26C + private const int RibbonBytesOffset = 0x24C; + private const int RibbonBytesCount = 0x20; + private const int RibbonByteNone = 0xFF; // signed -1 + + public bool HasMark() + { + for (int i = 0; i < RibbonBytesCount; i++) + { + var value = Data[RibbonBytesOffset + i]; + if (value == RibbonByteNone) + return false; + if ((RibbonIndex)value is >= MarkLunchtime and <= MarkSlump) + return true; + } + return false; + } + + public byte GetRibbonAtIndex(int byteIndex) + { + if ((uint)byteIndex >= RibbonBytesCount) + throw new ArgumentOutOfRangeException(nameof(byteIndex)); + return Data[RibbonBytesOffset + byteIndex]; + } + + public void SetRibbonAtIndex(int byteIndex, byte ribbonIndex) + { + if ((uint)byteIndex >= RibbonBytesCount) + throw new ArgumentOutOfRangeException(nameof(byteIndex)); + Data[RibbonBytesOffset + byteIndex] = ribbonIndex; + } + + public int IV_HP { get => Data[CardStart + 0x26C]; set => Data[CardStart + 0x26C] = (byte)value; } + public int IV_ATK { get => Data[CardStart + 0x26D]; set => Data[CardStart + 0x26D] = (byte)value; } + public int IV_DEF { get => Data[CardStart + 0x26E]; set => Data[CardStart + 0x26E] = (byte)value; } + public int IV_SPE { get => Data[CardStart + 0x26F]; set => Data[CardStart + 0x26F] = (byte)value; } + public int IV_SPA { get => Data[CardStart + 0x270]; set => Data[CardStart + 0x270] = (byte)value; } + public int IV_SPD { get => Data[CardStart + 0x271]; set => Data[CardStart + 0x271] = (byte)value; } + + public int OTGender { get => Data[CardStart + 0x272]; set => Data[CardStart + 0x272] = (byte)value; } + + public int EV_HP { get => Data[CardStart + 0x273]; set => Data[CardStart + 0x273] = (byte)value; } + public int EV_ATK { get => Data[CardStart + 0x274]; set => Data[CardStart + 0x274] = (byte)value; } + public int EV_DEF { get => Data[CardStart + 0x275]; set => Data[CardStart + 0x275] = (byte)value; } + public int EV_SPE { get => Data[CardStart + 0x276]; set => Data[CardStart + 0x276] = (byte)value; } + public int EV_SPA { get => Data[CardStart + 0x277]; set => Data[CardStart + 0x277] = (byte)value; } + public int EV_SPD { get => Data[CardStart + 0x278]; set => Data[CardStart + 0x278] = (byte)value; } + + public byte OT_Intensity { get => Data[CardStart + 0x279]; set => Data[CardStart + 0x279] = value; } + public byte OT_Memory { get => Data[CardStart + 0x27A]; set => Data[CardStart + 0x27A] = value; } + public byte OT_Feeling { get => Data[CardStart + 0x27B]; set => Data[CardStart + 0x27B] = value; } + public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x27C)); set => WriteUInt16LittleEndian(Data.AsSpan(CardStart + 0x27C), value); } + + // Meta Accessible Properties + public override int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set + { + if (value.Length != 6) return; + IV_HP = value[0]; IV_ATK = value[1]; IV_DEF = value[2]; + IV_SPE = value[3]; IV_SPA = value[4]; IV_SPD = value[5]; + } + } + + public override void GetIVs(Span value) + { + if (value.Length != 6) + return; + value[0] = IV_HP; + value[1] = IV_ATK; + value[2] = IV_DEF; + value[3] = IV_SPE; + value[4] = IV_SPA; + value[5] = IV_SPD; + } + + public int[] EVs + { + get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; + set + { + if (value.Length != 6) return; + EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; + EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; + } + } + + public bool GetIsNicknamed(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetNicknameOffset(language))) != 0; + + public bool CanBeAnyLanguage() + { + for (int i = 0; i < 9; i++) + { + var ofs = GetLanguageOffset(i); + var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); + if (lang != 0) + return false; + } + return true; + } + + public bool CanHaveLanguage(int language) + { + if (language is < (int)LanguageID.Japanese or > (int)LanguageID.ChineseT) + return false; + + if (CanBeAnyLanguage()) + return true; + + for (int i = 0; i < 9; i++) + { + var ofs = GetLanguageOffset(i); + var lang = ReadInt16LittleEndian(Data.AsSpan(ofs)); + if (lang == language) + return true; + } + return GetLanguage(language) == 0; + } + + public int GetLanguage(int redeemLanguage) => Data[GetLanguageOffset(GetLanguageIndex(redeemLanguage))]; + private static int GetLanguageOffset(int index) => 0x30 + (index * 0x1C) + 0x1A; + + public bool GetHasOT(int language) => ReadUInt16LittleEndian(Data.AsSpan(GetOTOffset(language))) != 0; + + private static int GetLanguageIndex(int language) + { + var lang = (LanguageID) language; + if (lang is < LanguageID.Japanese or LanguageID.UNUSED_6 or > LanguageID.ChineseT) + return (int) LanguageID.English; // fallback + return lang < LanguageID.UNUSED_6 ? language - 1 : language - 2; + } + + public override int Location { get => MetLocation; set => MetLocation = (ushort)value; } + + public override IReadOnlyList Moves + { + get => new[] { Move1, Move2, Move3, Move4 }; + set + { + if (value.Count > 0) Move1 = value[0]; + if (value.Count > 1) Move2 = value[1]; + if (value.Count > 2) Move3 = value[2]; + if (value.Count > 3) Move4 = value[3]; + } + } + + public override IReadOnlyList Relearn + { + get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; + set + { + if (value.Count > 0) RelearnMove1 = value[0]; + if (value.Count > 1) RelearnMove2 = value[1]; + if (value.Count > 2) RelearnMove3 = value[2]; + if (value.Count > 3) RelearnMove4 = value[3]; + } + } + + public override string OT_Name { get; set; } = string.Empty; + public string Nickname => string.Empty; + public bool IsNicknamed => false; + public int Language => 2; + + public string GetNickname(int language) => StringConverter8.GetString(Data.AsSpan(GetNicknameOffset(language), 0x1A)); + public void SetNickname(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetNicknameOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + + public string GetOT(int language) => StringConverter8.GetString(Data.AsSpan(GetOTOffset(language), 0x1A)); + public void SetOT(int language, string value) => StringConverter8.SetString(Data.AsSpan(GetOTOffset(language), 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + + private static int GetNicknameOffset(int language) + { + int index = GetLanguageIndex(language); + return 0x30 + (index * 0x1C); + } + + private static int GetOTOffset(int language) + { + int index = GetLanguageIndex(language); + return 0x12C + (index * 0x1C); + } + + public bool IsHOMEGift => CardID >= 9000; + + public bool CanHandleOT(int language) => !GetHasOT(language); + + public override GameVersion Version + { + get => OriginGame != 0 ? (GameVersion)OriginGame : GameVersion.SWSH; + set { } + } + + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + if (!IsEntity) + throw new ArgumentException(nameof(IsEntity)); + + int currentLevel = Level > 0 ? Level : 1 + Util.Rand.Next(100); + int metLevel = MetLevel > 0 ? MetLevel : currentLevel; + var pi = PersonalTable.SWSH.GetFormEntry(Species, Form); + var language = tr.Language; + var OT = GetOT(language); + bool hasOT = GetHasOT(language); + + var pk = new PK8 + { + EncryptionConstant = EncryptionConstant != 0 || IsHOMEGift ? EncryptionConstant : Util.Rand32(), + TID = TID, + SID = SID, + Species = Species, + Form = Form, + CurrentLevel = currentLevel, + Ball = Ball != 0 ? Ball : 4, // Default is Pokeball + Met_Level = metLevel, + HeldItem = HeldItem, + + EXP = Experience.GetEXP(currentLevel, pi.EXPGrowth), + + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + RelearnMove1 = RelearnMove1, + RelearnMove2 = RelearnMove2, + RelearnMove3 = RelearnMove3, + RelearnMove4 = RelearnMove4, + + Version = OriginGame != 0 ? OriginGame : tr.Game, + + OT_Name = OT.Length > 0 ? OT : tr.OT, + OT_Gender = OTGender < 2 ? OTGender : tr.Gender, + HT_Name = hasOT ? tr.OT : string.Empty, + HT_Gender = hasOT ? tr.Gender : 0, + HT_Language = (byte)(hasOT ? language : 0), + CurrentHandler = hasOT ? 1 : 0, + OT_Friendship = pi.BaseFriendship, + + OT_Intensity = OT_Intensity, + OT_Memory = OT_Memory, + OT_TextVar = OT_TextVar, + OT_Feeling = OT_Feeling, + FatefulEncounter = true, + + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPE = EV_SPE, + EV_SPA = EV_SPA, + EV_SPD = EV_SPD, + + CanGigantamax = CanGigantamax, + DynamaxLevel = DynamaxLevel, + + Met_Location = MetLocation, + Egg_Location = EggLocation, + }; + pk.SetMaximumPPCurrent(); + + if ((tr.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version)) + { + // give random valid game + var rnd = Util.Rand; + do { pk.Version = (int)GameVersion.SW + rnd.Next(2); } + while (!CanBeReceivedByVersion(pk.Version)); + } + + if (OTGender >= 2) + { + pk.TID = tr.TID; + pk.SID = tr.SID; + + if (IsHOMEGift) + { + pk.TrainerSID7 = 0; + while (pk.TSV == 0) + { + pk.TrainerID7 = Util.Rand.Next(16, 999_999 + 1); + } + } + } + + // Official code explicitly corrects for Meowstic + if (pk.Species == (int)Core.Species.Meowstic) + pk.Form = pk.Gender; + + pk.MetDate = IsDateRestricted && EncounterServerDate.WC8Gifts.TryGetValue(CardID, out var dt) ? dt : DateTime.Now; + + var nickname_language = GetLanguage(language); + pk.Language = nickname_language != 0 ? nickname_language : tr.Language; + pk.IsNicknamed = GetIsNicknamed(language); + pk.Nickname = pk.IsNicknamed ? GetNickname(language) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); + + for (var i = 0; i < RibbonBytesCount; i++) + { + var ribbon = GetRibbonAtIndex(i); + if (ribbon != RibbonByteNone) + pk.SetRibbon(ribbon); + } + + SetPINGA(pk, criteria); + + if (IsEgg) + SetEggMetData(pk); + pk.CurrentFriendship = pk.IsEgg ? pi.HatchCycles : pi.BaseFriendship; + + if (!IsHOMEGift) + { + pk.HeightScalar = PokeSizeUtil.GetRandomScalar(); + pk.WeightScalar = PokeSizeUtil.GetRandomScalar(); + } + + pk.ResetPartyStats(); + pk.RefreshChecksum(); + return pk; + } + + private void SetEggMetData(PKM pk) + { + pk.IsEgg = true; + pk.EggMetDate = DateTime.Now; + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(0, pk.Language, Generation); + pk.IsNicknamed = true; + } + + private void SetPINGA(PKM pk, EncounterCriteria criteria) + { + var pi = PersonalTable.SWSH.GetFormEntry(Species, Form); + pk.Nature = (int)criteria.GetNature(Nature == -1 ? Core.Nature.Random : (Nature)Nature); + pk.StatNature = pk.Nature; + pk.Gender = criteria.GetGender(Gender, pi); + var av = GetAbilityIndex(criteria); + pk.RefreshAbility(av); + SetPID(pk, pk.MetDate ?? DateTime.UtcNow); + SetIVs(pk); + } + + private int GetAbilityIndex(EncounterCriteria criteria) => AbilityType switch + { + 00 or 01 or 02 => AbilityType, // Fixed 0/1/2 + 03 or 04 => criteria.GetAbilityFromNumber(Ability), // 0/1 or 0/1/H + _ => throw new ArgumentOutOfRangeException(nameof(AbilityType)), + }; + + public override AbilityPermission Ability => AbilityType switch + { + 0 => AbilityPermission.OnlyFirst, + 1 => AbilityPermission.OnlySecond, + 2 => AbilityPermission.OnlyHidden, + 3 => AbilityPermission.Any12, + _ => AbilityPermission.Any12H, + }; + + private uint GetPID(ITrainerID tr, ShinyType8 type, DateTime date) => type switch + { + ShinyType8.Never => GetAntishiny(tr), // Random, Never Shiny + ShinyType8.Random => Util.Rand32(), // Random, Any + ShinyType8.AlwaysStar => (uint) (((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 1) << 16) | (PID & 0xFFFF)), // Fixed, Force Star + ShinyType8.AlwaysSquare => (uint) (((tr.TID ^ tr.SID ^ (PID & 0xFFFF) ^ 0) << 16) | (PID & 0xFFFF)), // Fixed, Force Square + ShinyType8.FixedValue => GetFixedPID(tr, date), + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + private uint GetFixedPID(ITrainerID tr, DateTime date) + { + var pid = PID; + if (pid != 0 && !(TID == 0 && SID == 0)) + return pid; + + if (!tr.IsShiny(pid, 8)) + return pid; + if (IsHOMEGift && !IsHOMEShinyPossible(date)) + return GetAntishinyFixedHOME(tr); + return pid; + } + + private static uint GetAntishinyFixedHOME(ITrainerID tr) + { + var fid = (uint)(tr.SID << 16) | (uint)tr.TID; + return fid ^ 0x10u; + } + + private static uint GetAntishiny(ITrainerID tr) + { + var pid = Util.Rand32(); + if (tr.IsShiny(pid, 8)) + return pid ^ 0x1000_0000; + return pid; + } + + private void SetPID(PKM pk, DateTime date) + { + pk.PID = GetPID(pk, PIDType, date); + } + + private void SetIVs(PKM pk) + { + Span finalIVs = stackalloc int[6]; + GetIVs(finalIVs); + var ivflag = finalIVs.Find(iv => (byte)(iv - 0xFC) < 3); + var rng = Util.Rand; + if (ivflag == 0) // Random IVs + { + for (int i = 0; i < finalIVs.Length; i++) + { + if (finalIVs[i] > 31) + finalIVs[i] = rng.Next(32); + } + } + else // 1/2/3 perfect IVs + { + int IVCount = ivflag - 0xFB; + do { finalIVs[rng.Next(6)] = 31; } + while (finalIVs.Count(31) < IVCount); + for (int i = 0; i < finalIVs.Length; i++) + { + if (finalIVs[i] != 31) + finalIVs[i] = rng.Next(32); + } + } + pk.SetIVs(finalIVs); + } + + public override bool IsMatchExact(PKM pk, EvoCriteria evo) + { + if (!IsEgg) + { + if (OTGender < 2) + { + if (SID != pk.SID) return false; + if (TID != pk.TID) return false; + if (OTGender != pk.OT_Gender) return false; + } + + if (!CanBeAnyLanguage() && !CanHaveLanguage(pk.Language)) + return false; + + var OT = GetOT(pk.Language); // May not be guaranteed to work. + if (!string.IsNullOrEmpty(OT) && OT != pk.OT_Name) return false; + if (OriginGame != 0 && OriginGame != pk.Version) return false; + if (EncryptionConstant != 0) + { + if (EncryptionConstant != pk.EncryptionConstant) + return false; + } + else if (IsHOMEGift)// 0 + { + // HOME gifts -- PID and EC are zeroes... + if (EncryptionConstant != pk.EncryptionConstant) + return false; + + if (pk.TSV == 0) // HOME doesn't assign TSV=0 to accounts. + return false; + + if (IsShiny) + { + if (!pk.IsShiny) + return false; + } + else // Never or Random (HOME ID specific) + { + if (pk.IsShiny && !IsHOMEShinyPossible(pk.MetDate ?? DateTime.UtcNow)) + return false; + } + } + } + + if (Form != evo.Form && !FormInfo.IsFormChangeable(Species, Form, pk.Form, pk.Format)) + return false; + + var shinyType = Shiny; + if (PIDType == ShinyType8.FixedValue) + shinyType = FixedShinyType(pk.MetDate ?? DateTime.UtcNow); + if (IsEgg) + { + if (EggLocation != pk.Egg_Location) // traded + { + if (pk.Egg_Location != Locations.LinkTrade6) + return false; + if (PIDType == ShinyType8.Random && pk.IsShiny && pk.ShinyXor > 1) + return false; // shiny traded egg will always have xor0/1. + } + if (!shinyType.IsValid(pk)) + { + return false; // can't be traded away for unshiny + } + + if (pk.IsEgg && !pk.IsNative) + return false; + } + else + { + if (!shinyType.IsValid(pk)) return false; + if (!IsMatchEggLocation(pk)) return false; + if (MetLocation != pk.Met_Location) return false; + } + + if (MetLevel != 0 && MetLevel != pk.Met_Level) return false; + if ((Ball == 0 ? 4 : Ball) != pk.Ball) return false; + if (OTGender < 2 && OTGender != pk.OT_Gender) return false; + if (Nature != -1 && pk.Nature != Nature) return false; + if (Gender != 3 && Gender != pk.Gender) return false; + + if (pk is PK8 and IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pk.Species, pk.Form, Species, Form)) + return false; + + if (pk is not IDynamaxLevel dl || dl.DynamaxLevel < DynamaxLevel) + return false; + + if (IsHOMEGift && pk is IScaledSize s) + { + if (s.HeightScalar != 0) + return false; + if (s.WeightScalar != 0) + return false; + } + + // Duplicate card; one with Nickname specified and another without. + if (CardID == 0122 && pk.IsNicknamed != GetIsNicknamed(pk.Language)) + return false; + + // PID Types 0 and 1 do not use the fixed PID value. + // Values 2,3 are specific shiny states, and 4 is fixed value. + // 2,3,4 can change if it is a traded egg to ensure the same shiny state. + var type = PIDType; + if (type is ShinyType8.Never or ShinyType8.Random) + return true; + return pk.PID == GetPID(pk, type, pk.MetDate ?? DateTime.UtcNow); + } + + private bool IsHOMEShinyPossible(DateTime date) + { + // no defined TID/SID and having a fixed PID can cause the player's TID/SID to match the PID's shiny calc. + return TID == 0 && SID == 0 && PID != 0 && (CardID < 9015 && date < new DateTime(2022, 5, 18)); + } + + public bool IsDateRestricted => IsHOMEGift; + + protected override bool IsMatchDeferred(PKM pk) => Species != pk.Species; + protected override bool IsMatchPartial(PKM pk) => false; // no version compatibility checks yet. + + #region Lazy Ribbon Implementation + public bool RibbonEarth { get => this.GetRibbonIndex(Earth); set => this.SetRibbonIndex(Earth, value); } + public bool RibbonNational { get => this.GetRibbonIndex(National); set => this.SetRibbonIndex(National, value); } + public bool RibbonCountry { get => this.GetRibbonIndex(Country); set => this.SetRibbonIndex(Country, value); } + public bool RibbonChampionBattle { get => this.GetRibbonIndex(ChampionBattle); set => this.SetRibbonIndex(ChampionBattle, value); } + public bool RibbonChampionRegional { get => this.GetRibbonIndex(ChampionRegional); set => this.SetRibbonIndex(ChampionRegional, value); } + public bool RibbonChampionNational { get => this.GetRibbonIndex(ChampionNational); set => this.SetRibbonIndex(ChampionNational, value); } + public bool RibbonClassic { get => this.GetRibbonIndex(Classic); set => this.SetRibbonIndex(Classic, value); } + public bool RibbonWishing { get => this.GetRibbonIndex(Wishing); set => this.SetRibbonIndex(Wishing, value); } + public bool RibbonPremier { get => this.GetRibbonIndex(Premier); set => this.SetRibbonIndex(Premier, value); } + public bool RibbonEvent { get => this.GetRibbonIndex(Event); set => this.SetRibbonIndex(Event, value); } + public bool RibbonBirthday { get => this.GetRibbonIndex(Birthday); set => this.SetRibbonIndex(Birthday, value); } + public bool RibbonSpecial { get => this.GetRibbonIndex(Special); set => this.SetRibbonIndex(Special, value); } + public bool RibbonWorld { get => this.GetRibbonIndex(World); set => this.SetRibbonIndex(World, value); } + public bool RibbonChampionWorld { get => this.GetRibbonIndex(ChampionWorld); set => this.SetRibbonIndex(ChampionWorld, value); } + public bool RibbonSouvenir { get => this.GetRibbonIndex(Souvenir); set => this.SetRibbonIndex(Souvenir, value); } + public bool RibbonChampionG3 { get => this.GetRibbonIndex(ChampionG3); set => this.SetRibbonIndex(ChampionG3, value); } + public bool RibbonArtist { get => this.GetRibbonIndex(Artist); set => this.SetRibbonIndex(Artist, value); } + public bool RibbonEffort { get => this.GetRibbonIndex(Effort); set => this.SetRibbonIndex(Effort, value); } + public bool RibbonChampionSinnoh { get => this.GetRibbonIndex(ChampionSinnoh); set => this.SetRibbonIndex(ChampionSinnoh, value); } + public bool RibbonAlert { get => this.GetRibbonIndex(Alert); set => this.SetRibbonIndex(Alert, value); } + public bool RibbonShock { get => this.GetRibbonIndex(Shock); set => this.SetRibbonIndex(Shock, value); } + public bool RibbonDowncast { get => this.GetRibbonIndex(Downcast); set => this.SetRibbonIndex(Downcast, value); } + public bool RibbonCareless { get => this.GetRibbonIndex(Careless); set => this.SetRibbonIndex(Careless, value); } + public bool RibbonRelax { get => this.GetRibbonIndex(Relax); set => this.SetRibbonIndex(Relax, value); } + public bool RibbonSnooze { get => this.GetRibbonIndex(Snooze); set => this.SetRibbonIndex(Snooze, value); } + public bool RibbonSmile { get => this.GetRibbonIndex(Smile); set => this.SetRibbonIndex(Smile, value); } + public bool RibbonGorgeous { get => this.GetRibbonIndex(Gorgeous); set => this.SetRibbonIndex(Gorgeous, value); } + public bool RibbonRoyal { get => this.GetRibbonIndex(Royal); set => this.SetRibbonIndex(Royal, value); } + public bool RibbonGorgeousRoyal { get => this.GetRibbonIndex(GorgeousRoyal); set => this.SetRibbonIndex(GorgeousRoyal, value); } + public bool RibbonFootprint { get => this.GetRibbonIndex(Footprint); set => this.SetRibbonIndex(Footprint, value); } + public bool RibbonRecord { get => this.GetRibbonIndex(Record); set => this.SetRibbonIndex(Record, value); } + public bool RibbonLegend { get => this.GetRibbonIndex(Legend); set => this.SetRibbonIndex(Legend, value); } + public bool RibbonChampionKalos { get => this.GetRibbonIndex(ChampionKalos); set => this.SetRibbonIndex(ChampionKalos, value); } + public bool RibbonChampionG6Hoenn { get => this.GetRibbonIndex(ChampionG6Hoenn); set => this.SetRibbonIndex(ChampionG6Hoenn, value); } + public bool RibbonBestFriends { get => this.GetRibbonIndex(BestFriends); set => this.SetRibbonIndex(BestFriends, value); } + public bool RibbonTraining { get => this.GetRibbonIndex(Training); set => this.SetRibbonIndex(Training, value); } + public bool RibbonBattlerSkillful { get => this.GetRibbonIndex(BattlerSkillful); set => this.SetRibbonIndex(BattlerSkillful, value); } + public bool RibbonBattlerExpert { get => this.GetRibbonIndex(BattlerExpert); set => this.SetRibbonIndex(BattlerExpert, value); } + public bool RibbonContestStar { get => this.GetRibbonIndex(ContestStar); set => this.SetRibbonIndex(ContestStar, value); } + public bool RibbonMasterCoolness { get => this.GetRibbonIndex(MasterCoolness); set => this.SetRibbonIndex(MasterCoolness, value); } + public bool RibbonMasterBeauty { get => this.GetRibbonIndex(MasterBeauty); set => this.SetRibbonIndex(MasterBeauty, value); } + public bool RibbonMasterCuteness { get => this.GetRibbonIndex(MasterCuteness); set => this.SetRibbonIndex(MasterCuteness, value); } + public bool RibbonMasterCleverness { get => this.GetRibbonIndex(MasterCleverness); set => this.SetRibbonIndex(MasterCleverness, value); } + public bool RibbonMasterToughness { get => this.GetRibbonIndex(MasterToughness); set => this.SetRibbonIndex(MasterToughness, value); } + + public int RibbonCountMemoryContest { get => 0; set { } } + public int RibbonCountMemoryBattle { get => 0; set { } } + + public bool RibbonChampionAlola { get => this.GetRibbonIndex(ChampionAlola); set => this.SetRibbonIndex(ChampionAlola, value); } + public bool RibbonBattleRoyale { get => this.GetRibbonIndex(BattleRoyale); set => this.SetRibbonIndex(BattleRoyale, value); } + public bool RibbonBattleTreeGreat { get => this.GetRibbonIndex(BattleTreeGreat); set => this.SetRibbonIndex(BattleTreeGreat, value); } + public bool RibbonBattleTreeMaster { get => this.GetRibbonIndex(BattleTreeMaster); set => this.SetRibbonIndex(BattleTreeMaster, value); } + public bool RibbonChampionGalar { get => this.GetRibbonIndex(ChampionGalar); set => this.SetRibbonIndex(ChampionGalar, value); } + public bool RibbonTowerMaster { get => this.GetRibbonIndex(TowerMaster); set => this.SetRibbonIndex(TowerMaster, value); } + public bool RibbonMasterRank { get => this.GetRibbonIndex(MasterRank); set => this.SetRibbonIndex(MasterRank, value); } + public bool RibbonMarkLunchtime { get => this.GetRibbonIndex(MarkLunchtime); set => this.SetRibbonIndex(MarkLunchtime, value); } + public bool RibbonMarkSleepyTime { get => this.GetRibbonIndex(MarkSleepyTime); set => this.SetRibbonIndex(MarkSleepyTime, value); } + public bool RibbonMarkDusk { get => this.GetRibbonIndex(MarkDusk); set => this.SetRibbonIndex(MarkDusk, value); } + public bool RibbonMarkDawn { get => this.GetRibbonIndex(MarkDawn); set => this.SetRibbonIndex(MarkDawn, value); } + public bool RibbonMarkCloudy { get => this.GetRibbonIndex(MarkCloudy); set => this.SetRibbonIndex(MarkCloudy, value); } + public bool RibbonMarkRainy { get => this.GetRibbonIndex(MarkRainy); set => this.SetRibbonIndex(MarkRainy, value); } + public bool RibbonMarkStormy { get => this.GetRibbonIndex(MarkStormy); set => this.SetRibbonIndex(MarkStormy, value); } + public bool RibbonMarkSnowy { get => this.GetRibbonIndex(MarkSnowy); set => this.SetRibbonIndex(MarkSnowy, value); } + public bool RibbonMarkBlizzard { get => this.GetRibbonIndex(MarkBlizzard); set => this.SetRibbonIndex(MarkBlizzard, value); } + public bool RibbonMarkDry { get => this.GetRibbonIndex(MarkDry); set => this.SetRibbonIndex(MarkDry, value); } + public bool RibbonMarkSandstorm { get => this.GetRibbonIndex(MarkSandstorm); set => this.SetRibbonIndex(MarkSandstorm, value); } + public bool RibbonMarkMisty { get => this.GetRibbonIndex(MarkMisty); set => this.SetRibbonIndex(MarkMisty, value); } + public bool RibbonMarkDestiny { get => this.GetRibbonIndex(MarkDestiny); set => this.SetRibbonIndex(MarkDestiny, value); } + public bool RibbonMarkFishing { get => this.GetRibbonIndex(MarkFishing); set => this.SetRibbonIndex(MarkFishing, value); } + public bool RibbonMarkCurry { get => this.GetRibbonIndex(MarkCurry); set => this.SetRibbonIndex(MarkCurry, value); } + public bool RibbonMarkUncommon { get => this.GetRibbonIndex(MarkUncommon); set => this.SetRibbonIndex(MarkUncommon, value); } + public bool RibbonMarkRare { get => this.GetRibbonIndex(MarkRare); set => this.SetRibbonIndex(MarkRare, value); } + public bool RibbonMarkRowdy { get => this.GetRibbonIndex(MarkRowdy); set => this.SetRibbonIndex(MarkRowdy, value); } + public bool RibbonMarkAbsentMinded { get => this.GetRibbonIndex(MarkAbsentMinded); set => this.SetRibbonIndex(MarkAbsentMinded, value); } + public bool RibbonMarkJittery { get => this.GetRibbonIndex(MarkJittery); set => this.SetRibbonIndex(MarkJittery, value); } + public bool RibbonMarkExcited { get => this.GetRibbonIndex(MarkExcited); set => this.SetRibbonIndex(MarkExcited, value); } + public bool RibbonMarkCharismatic { get => this.GetRibbonIndex(MarkCharismatic); set => this.SetRibbonIndex(MarkCharismatic, value); } + public bool RibbonMarkCalmness { get => this.GetRibbonIndex(MarkCalmness); set => this.SetRibbonIndex(MarkCalmness, value); } + public bool RibbonMarkIntense { get => this.GetRibbonIndex(MarkIntense); set => this.SetRibbonIndex(MarkIntense, value); } + public bool RibbonMarkZonedOut { get => this.GetRibbonIndex(MarkZonedOut); set => this.SetRibbonIndex(MarkZonedOut, value); } + public bool RibbonMarkJoyful { get => this.GetRibbonIndex(MarkJoyful); set => this.SetRibbonIndex(MarkJoyful, value); } + public bool RibbonMarkAngry { get => this.GetRibbonIndex(MarkAngry); set => this.SetRibbonIndex(MarkAngry, value); } + public bool RibbonMarkSmiley { get => this.GetRibbonIndex(MarkSmiley); set => this.SetRibbonIndex(MarkSmiley, value); } + public bool RibbonMarkTeary { get => this.GetRibbonIndex(MarkTeary); set => this.SetRibbonIndex(MarkTeary, value); } + public bool RibbonMarkUpbeat { get => this.GetRibbonIndex(MarkUpbeat); set => this.SetRibbonIndex(MarkUpbeat, value); } + public bool RibbonMarkPeeved { get => this.GetRibbonIndex(MarkPeeved); set => this.SetRibbonIndex(MarkPeeved, value); } + public bool RibbonMarkIntellectual { get => this.GetRibbonIndex(MarkIntellectual); set => this.SetRibbonIndex(MarkIntellectual, value); } + public bool RibbonMarkFerocious { get => this.GetRibbonIndex(MarkFerocious); set => this.SetRibbonIndex(MarkFerocious, value); } + public bool RibbonMarkCrafty { get => this.GetRibbonIndex(MarkCrafty); set => this.SetRibbonIndex(MarkCrafty, value); } + public bool RibbonMarkScowling { get => this.GetRibbonIndex(MarkScowling); set => this.SetRibbonIndex(MarkScowling, value); } + public bool RibbonMarkKindly { get => this.GetRibbonIndex(MarkKindly); set => this.SetRibbonIndex(MarkKindly, value); } + public bool RibbonMarkFlustered { get => this.GetRibbonIndex(MarkFlustered); set => this.SetRibbonIndex(MarkFlustered, value); } + public bool RibbonMarkPumpedUp { get => this.GetRibbonIndex(MarkPumpedUp); set => this.SetRibbonIndex(MarkPumpedUp, value); } + public bool RibbonMarkZeroEnergy { get => this.GetRibbonIndex(MarkZeroEnergy); set => this.SetRibbonIndex(MarkZeroEnergy, value); } + public bool RibbonMarkPrideful { get => this.GetRibbonIndex(MarkPrideful); set => this.SetRibbonIndex(MarkPrideful, value); } + public bool RibbonMarkUnsure { get => this.GetRibbonIndex(MarkUnsure); set => this.SetRibbonIndex(MarkUnsure, value); } + public bool RibbonMarkHumble { get => this.GetRibbonIndex(MarkHumble); set => this.SetRibbonIndex(MarkHumble, value); } + public bool RibbonMarkThorny { get => this.GetRibbonIndex(MarkThorny); set => this.SetRibbonIndex(MarkThorny, value); } + public bool RibbonMarkVigor { get => this.GetRibbonIndex(MarkVigor); set => this.SetRibbonIndex(MarkVigor, value); } + public bool RibbonMarkSlump { get => this.GetRibbonIndex(MarkSlump); set => this.SetRibbonIndex(MarkSlump, value); } + public bool RibbonTwinklingStar { get => this.GetRibbonIndex(TwinklingStar); set => this.SetRibbonIndex(TwinklingStar, value); } + public bool RibbonPioneer { get => this.GetRibbonIndex(Pioneer); set => this.SetRibbonIndex(Pioneer, value); } + + public int GetRibbonByte(int index) => Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z == index); + public bool GetRibbon(int index) => GetRibbonByte(index) >= 0; + + public void SetRibbon(int index, bool value = true) + { + if ((uint)index > (uint)MarkSlump) + throw new ArgumentOutOfRangeException(nameof(index)); + + if (value) + { + if (GetRibbon(index)) + return; + var openIndex = Array.FindIndex(Data, RibbonBytesOffset, RibbonBytesCount, z => z != RibbonByteNone); + if (openIndex < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + SetRibbonAtIndex(openIndex, (byte)index); + } + else + { + var ofs = GetRibbonByte(index); + if (ofs < 0) + return; + SetRibbonAtIndex(ofs, RibbonByteNone); + } + } + #endregion } diff --git a/PKHeX.Core/MysteryGifts/WR7.cs b/PKHeX.Core/MysteryGifts/WR7.cs index 66003f7fa..70c44abf4 100644 --- a/PKHeX.Core/MysteryGifts/WR7.cs +++ b/PKHeX.Core/MysteryGifts/WR7.cs @@ -1,154 +1,153 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Record of a received file. +/// +/// +/// A full is not stored in the structure, as it is immediately converted to upon receiving from server. +/// The save file just stores a summary of the received data for the user to look back at. +/// +public sealed class WR7 : DataMysteryGift { - /// - /// Record of a received file. - /// - /// - /// A full is not stored in the structure, as it is immediately converted to upon receiving from server. - /// The save file just stores a summary of the received data for the user to look back at. - /// - public sealed class WR7 : DataMysteryGift + public const int Size = 0x140; + public override int Generation => 7; + + public override GameVersion Version { get => GameVersion.GG; set { } } + + public WR7() : this(new byte[Size]) { } + public WR7(byte[] data) : base(data) { } + + public override AbilityPermission Ability => AbilityPermission.Any12H; // undefined + + public uint Epoch { - public const int Size = 0x140; - public override int Generation => 7; + get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); + } - public override GameVersion Version { get => GameVersion.GG; set { } } + public DateTime Date => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(Epoch); - public WR7() : this(new byte[Size]) { } - public WR7(byte[] data) : base(data) { } + public override int CardID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); + } - public override AbilityPermission Ability => AbilityPermission.Any12H; // undefined + public ushort CardType + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), value); + } - public uint Epoch + public WR7GiftType GiftType + { + get => (WR7GiftType)Data[0x0C]; + set => Data[0x0C] = (byte)value; + } + + public int ItemCount + { + get => Data[0x0D]; + set => Data[0x0D] = (byte)value; + } + + // unknown: region from 0x10 to 0xFF ? + + public override int Species + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x10C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x10C), (ushort)value); + } + + public override bool GiftUsed { get; set; } + + public override byte Level // are moves stored? mew has '1' but this could be move + { + get => Data[0x10E]; + set => Data[0x10E] = value; + } + + public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(0x110)); set => WriteUInt16LittleEndian(Data.AsSpan(0x110), (ushort)value); } + public ushort ItemIDCount { get => ReadUInt16LittleEndian(Data.AsSpan(0x112)); set => WriteUInt16LittleEndian(Data.AsSpan(0x112), value); } + public ushort ItemSet2Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x114)); set => WriteUInt16LittleEndian(Data.AsSpan(0x114), value); } + public ushort ItemSet2Count { get => ReadUInt16LittleEndian(Data.AsSpan(0x116)); set => WriteUInt16LittleEndian(Data.AsSpan(0x116), value); } + public ushort ItemSet3Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x118)); set => WriteUInt16LittleEndian(Data.AsSpan(0x118), value); } + public ushort ItemSet3Count { get => ReadUInt16LittleEndian(Data.AsSpan(0x11A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x11A), value); } + public ushort ItemSet4Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x11C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x11C), value); } + public ushort ItemSet4Count { get => ReadUInt16LittleEndian(Data.AsSpan(0x11E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x11E), value); } + public ushort ItemSet5Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x120)); set => WriteUInt16LittleEndian(Data.AsSpan(0x120), value); } // struct union overlaps OT Name data, beware! + public ushort ItemSet5Count { get => ReadUInt16LittleEndian(Data.AsSpan(0x122)); set => WriteUInt16LittleEndian(Data.AsSpan(0x122), value); } + public ushort ItemSet6Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x124)); set => WriteUInt16LittleEndian(Data.AsSpan(0x124), value); } + public ushort ItemSet6Count { get => ReadUInt16LittleEndian(Data.AsSpan(0x126)); set => WriteUInt16LittleEndian(Data.AsSpan(0x126), value); } + + public override int Gender { get; set; } + public override int Form { get; set; } + public override int TID { get; set; } + public override int SID { get; set; } + + public override string OT_Name + { + get => StringConverter8.GetString(Data.AsSpan(0x120, 0x1A)); + set => StringConverter8.SetString(Data.AsSpan(0x120, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + } + + public LanguageID LanguageReceived + { + get => (LanguageID)Data[0x13A]; + set => Data[0x13A] = (byte)value; + } + + // Mystery Gift implementation, unused. + public override bool IsMatchExact(PKM pk, EvoCriteria evo) => false; + protected override bool IsMatchDeferred(PKM pk) => false; + protected override bool IsMatchPartial(PKM pk) => false; + public override Shiny Shiny => Shiny.Never; + + public override int Location { get; set; } + public override int EggLocation { get; set; } + public override int Ball { get; set; } = 4; + + public override string CardTitle { get => $"{nameof(WB7)} Record ({OT_Name}) [{LanguageReceived}]"; set { } } + + public override bool IsItem + { + get => GiftType == WR7GiftType.Item; + set { - get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); - } - - public DateTime Date => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(Epoch); - - public override int CardID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); - } - - public ushort CardType - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), value); - } - - public WR7GiftType GiftType - { - get => (WR7GiftType)Data[0x0C]; - set => Data[0x0C] = (byte)value; - } - - public int ItemCount - { - get => Data[0x0D]; - set => Data[0x0D] = (byte)value; - } - - // unknown: region from 0x10 to 0xFF ? - - public override int Species - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x10C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x10C), (ushort)value); - } - - public override bool GiftUsed { get; set; } - - public override byte Level // are moves stored? mew has '1' but this could be move - { - get => Data[0x10E]; - set => Data[0x10E] = value; - } - - public override int ItemID { get => ReadUInt16LittleEndian(Data.AsSpan(0x110)); set => WriteUInt16LittleEndian(Data.AsSpan(0x110), (ushort)value); } - public ushort ItemIDCount { get => ReadUInt16LittleEndian(Data.AsSpan(0x112)); set => WriteUInt16LittleEndian(Data.AsSpan(0x112), value); } - public ushort ItemSet2Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x114)); set => WriteUInt16LittleEndian(Data.AsSpan(0x114), value); } - public ushort ItemSet2Count { get => ReadUInt16LittleEndian(Data.AsSpan(0x116)); set => WriteUInt16LittleEndian(Data.AsSpan(0x116), value); } - public ushort ItemSet3Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x118)); set => WriteUInt16LittleEndian(Data.AsSpan(0x118), value); } - public ushort ItemSet3Count { get => ReadUInt16LittleEndian(Data.AsSpan(0x11A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x11A), value); } - public ushort ItemSet4Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x11C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x11C), value); } - public ushort ItemSet4Count { get => ReadUInt16LittleEndian(Data.AsSpan(0x11E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x11E), value); } - public ushort ItemSet5Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x120)); set => WriteUInt16LittleEndian(Data.AsSpan(0x120), value); } // struct union overlaps OT Name data, beware! - public ushort ItemSet5Count { get => ReadUInt16LittleEndian(Data.AsSpan(0x122)); set => WriteUInt16LittleEndian(Data.AsSpan(0x122), value); } - public ushort ItemSet6Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x124)); set => WriteUInt16LittleEndian(Data.AsSpan(0x124), value); } - public ushort ItemSet6Count { get => ReadUInt16LittleEndian(Data.AsSpan(0x126)); set => WriteUInt16LittleEndian(Data.AsSpan(0x126), value); } - - public override int Gender { get; set; } - public override int Form { get; set; } - public override int TID { get; set; } - public override int SID { get; set; } - - public override string OT_Name - { - get => StringConverter8.GetString(Data.AsSpan(0x120, 0x1A)); - set => StringConverter8.SetString(Data.AsSpan(0x120, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - } - - public LanguageID LanguageReceived - { - get => (LanguageID)Data[0x13A]; - set => Data[0x13A] = (byte)value; - } - - // Mystery Gift implementation, unused. - public override bool IsMatchExact(PKM pkm, EvoCriteria evo) => false; - protected override bool IsMatchDeferred(PKM pkm) => false; - protected override bool IsMatchPartial(PKM pkm) => false; - public override Shiny Shiny => Shiny.Never; - - public override int Location { get; set; } - public override int EggLocation { get; set; } - public override int Ball { get; set; } = 4; - - public override string CardTitle { get => $"{nameof(WB7)} Record ({OT_Name}) [{LanguageReceived}]"; set { } } - - public override bool IsItem - { - get => GiftType == WR7GiftType.Item; - set - { - if (value) - GiftType = WR7GiftType.Item; - } - } - - public override bool IsEntity - { - get => GiftType == WR7GiftType.Pokemon; - set - { - if (value) - GiftType = WR7GiftType.Pokemon; - } - } - - public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) - { - // this method shouldn't really be called, use the WB7 data not the WR7 data. - if (!IsEntity) - throw new ArgumentException(nameof(IsEntity)); - - // we'll just generate something as close as we can, since we must return something! - var pk = new PB7(); - sav.ApplyTo(pk); - if (!GameVersion.GG.Contains((GameVersion) sav.Game)) - pk.Version = (int) GameVersion.GP; - - pk.Species = Species; - pk.Met_Level = pk.CurrentLevel = Level; - pk.MetDate = Date; - - return pk; // can't really do much more, just return the rough data + if (value) + GiftType = WR7GiftType.Item; } } + + public override bool IsEntity + { + get => GiftType == WR7GiftType.Pokemon; + set + { + if (value) + GiftType = WR7GiftType.Pokemon; + } + } + + public override PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) + { + // this method shouldn't really be called, use the WB7 data not the WR7 data. + if (!IsEntity) + throw new ArgumentException(nameof(IsEntity)); + + // we'll just generate something as close as we can, since we must return something! + var pk = new PB7(); + tr.ApplyTo(pk); + if (!GameVersion.GG.Contains((GameVersion) tr.Game)) + pk.Version = (int) GameVersion.GP; + + pk.Species = Species; + pk.Met_Level = pk.CurrentLevel = Level; + pk.MetDate = Date; + + return pk; // can't really do much more, just return the rough data + } } diff --git a/PKHeX.Core/PKHeX.Core.csproj b/PKHeX.Core/PKHeX.Core.csproj index 977659b9a..9d095b279 100644 --- a/PKHeX.Core/PKHeX.Core.csproj +++ b/PKHeX.Core/PKHeX.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;net46 diff --git a/PKHeX.Core/PKM/BK4.cs b/PKHeX.Core/PKM/BK4.cs index 6ca236818..c9ce28123 100644 --- a/PKHeX.Core/PKM/BK4.cs +++ b/PKHeX.Core/PKM/BK4.cs @@ -1,307 +1,306 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 4 format, exclusively for Pokémon Battle Revolution. +/// +/// When stored in the save file, these are only shuffled; no xor encryption is performed. +/// Values are stored in Big Endian format rather than Little Endian. Beware. +/// +public sealed class BK4 : G4PKM { - /// Generation 4 format, exclusively for Pokémon Battle Revolution. - /// - /// When stored in the save file, these are only shuffled; no xor encryption is performed. - /// Values are stored in Big Endian format rather than Little Endian. Beware. - /// - public sealed class BK4 : G4PKM + private static readonly ushort[] Unused = { - private static readonly ushort[] Unused = - { - 0x42, 0x43, 0x5E, 0x63, 0x64, 0x65, 0x66, 0x67, 0x87, - }; + 0x42, 0x43, 0x5E, 0x63, 0x64, 0x65, 0x66, 0x67, 0x87, + }; - public override IReadOnlyList ExtraBytes => Unused; + public override IReadOnlyList ExtraBytes => Unused; - public override int SIZE_PARTY => PokeCrypto.SIZE_4STORED; - public override int SIZE_STORED => PokeCrypto.SIZE_4STORED; - public override EntityContext Context => EntityContext.Gen4; - public override PersonalInfo PersonalInfo => PersonalTable.HGSS[Species]; + public override int SIZE_PARTY => PokeCrypto.SIZE_4STORED; + public override int SIZE_STORED => PokeCrypto.SIZE_4STORED; + public override EntityContext Context => EntityContext.Gen4; + public override PersonalInfo PersonalInfo => PersonalTable.HGSS[Species]; - public override byte[] DecryptedBoxData => EncryptedBoxData; + public override byte[] DecryptedBoxData => EncryptedBoxData; - public override bool Valid => ChecksumValid || (Sanity == 0 && Species <= MaxSpeciesID); + public override bool Valid => ChecksumValid || (Sanity == 0 && Species <= MaxSpeciesID); - public static BK4 ReadUnshuffle(ReadOnlySpan data) - { - var unshuffled = PokeCrypto.DecryptArray4BE(data); - var result = new BK4(unshuffled); - result.RefreshChecksum(); - return result; - } - - public BK4(byte[] data) : base(data) - { - Sanity = 0x4000; - ResetPartyStats(); - } - - public BK4() : this(new byte[PokeCrypto.SIZE_4STORED]) { } - - public override PKM Clone() => new BK4((byte[])Data.Clone()); - - // Structure - public override uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x00)); set => WriteUInt32BigEndian(Data.AsSpan(0x00), value); } - public override ushort Sanity { get => ReadUInt16BigEndian(Data.AsSpan(0x04)); set => WriteUInt16BigEndian(Data.AsSpan(0x04), value); } - public override ushort Checksum { get => ReadUInt16BigEndian(Data.AsSpan(0x06)); set => WriteUInt16BigEndian(Data.AsSpan(0x06), value); } - - #region Block A - public override int Species { get => ReadUInt16BigEndian(Data.AsSpan(0x08)); set => WriteUInt16BigEndian(Data.AsSpan(0x08), (ushort)value); } - public override int HeldItem { get => ReadUInt16BigEndian(Data.AsSpan(0x0A)); set => WriteUInt16BigEndian(Data.AsSpan(0x0A), (ushort)value); } - public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(0x0C)); set => WriteUInt16BigEndian(Data.AsSpan(0x0C), (ushort)value); } - public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(0x0E)); set => WriteUInt16BigEndian(Data.AsSpan(0x0E), (ushort)value); } - - public override uint EXP - { - get => ReadUInt32BigEndian(Data.AsSpan(0x10)); - set => WriteUInt32BigEndian(Data.AsSpan(0x10), value); - } - - public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } - public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; } - public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; } - public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; } - public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; } - public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; } - public override int EV_DEF { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } - public override int EV_SPE { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } - public override int EV_SPA { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } - public override int EV_SPD { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } - public override byte CNT_Cool { get => Data[0x1E]; set => Data[0x1E] = value; } - public override byte CNT_Beauty { get => Data[0x1F]; set => Data[0x1F] = value; } - public override byte CNT_Cute { get => Data[0x20]; set => Data[0x20] = value; } - public override byte CNT_Smart { get => Data[0x21]; set => Data[0x21] = value; } - public override byte CNT_Tough { get => Data[0x22]; set => Data[0x22] = value; } - public override byte CNT_Sheen { get => Data[0x23]; set => Data[0x23] = value; } - - private byte RIB3 { get => Data[0x24]; set => Data[0x24] = value; } // Unova 2 - private byte RIB2 { get => Data[0x25]; set => Data[0x25] = value; } // Unova 1 - private byte RIB1 { get => Data[0x26]; set => Data[0x26] = value; } // Sinnoh 2 - private byte RIB0 { get => Data[0x27]; set => Data[0x27] = value; } // Sinnoh 1 - public override bool RibbonChampionSinnoh { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonAbility { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonAbilityGreat { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonAbilityDouble { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonAbilityMulti { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonAbilityPair { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonAbilityWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonAlert { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonShock { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonDowncast { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonCareless { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonRelax { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonSnooze { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonSmile { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonGorgeous { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonRoyal { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonFootprint { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonRecord { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonEvent { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonLegend { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonChampionWorld { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonBirthday { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonSpecial { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonSouvenir { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonWishing { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RIB3_4 { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused - public override bool RIB3_5 { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused - public override bool RIB3_6 { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public override bool RIB3_7 { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - #endregion - - #region Block B - public override int Move1 { get => ReadUInt16BigEndian(Data.AsSpan(0x28)); set => WriteUInt16BigEndian(Data.AsSpan(0x28), (ushort)value); } - public override int Move2 { get => ReadUInt16BigEndian(Data.AsSpan(0x2A)); set => WriteUInt16BigEndian(Data.AsSpan(0x2A), (ushort)value); } - public override int Move3 { get => ReadUInt16BigEndian(Data.AsSpan(0x2C)); set => WriteUInt16BigEndian(Data.AsSpan(0x2C), (ushort)value); } - public override int Move4 { get => ReadUInt16BigEndian(Data.AsSpan(0x2E)); set => WriteUInt16BigEndian(Data.AsSpan(0x2E), (ushort)value); } - public override int Move1_PP { get => Data[0x30]; set => Data[0x30] = (byte)value; } - public override int Move2_PP { get => Data[0x31]; set => Data[0x31] = (byte)value; } - public override int Move3_PP { get => Data[0x32]; set => Data[0x32] = (byte)value; } - public override int Move4_PP { get => Data[0x33]; set => Data[0x33] = (byte)value; } - public override int Move1_PPUps { get => Data[0x34]; set => Data[0x34] = (byte)value; } - public override int Move2_PPUps { get => Data[0x35]; set => Data[0x35] = (byte)value; } - public override int Move3_PPUps { get => Data[0x36]; set => Data[0x36] = (byte)value; } - public override int Move4_PPUps { get => Data[0x37]; set => Data[0x37] = (byte)value; } - private uint IV32 { get => ReadUInt32BigEndian(Data.AsSpan(0x38)); set => WriteUInt32BigEndian(Data.AsSpan(0x38), value); } - public override int IV_SPD { get => (int)(IV32 >> 02) & 0x1F; set => IV32 = ((IV32 & ~(0x1Fu << 02)) | ((value > 31 ? 31u : (uint)value) << 02)); } - public override int IV_SPA { get => (int)(IV32 >> 07) & 0x1F; set => IV32 = ((IV32 & ~(0x1Fu << 07)) | ((value > 31 ? 31u : (uint)value) << 07)); } - public override int IV_SPE { get => (int)(IV32 >> 12) & 0x1F; set => IV32 = ((IV32 & ~(0x1Fu << 12)) | ((value > 31 ? 31u : (uint)value) << 12)); } - public override int IV_DEF { get => (int)(IV32 >> 17) & 0x1F; set => IV32 = ((IV32 & ~(0x1Fu << 17)) | ((value > 31 ? 31u : (uint)value) << 17)); } - public override int IV_ATK { get => (int)(IV32 >> 22) & 0x1F; set => IV32 = ((IV32 & ~(0x1Fu << 22)) | ((value > 31 ? 31u : (uint)value) << 22)); } - public override int IV_HP { get => (int)(IV32 >> 27) & 0x1F; set => IV32 = ((IV32 & ~(0x1Fu << 27)) | ((value > 31 ? 31u : (uint)value) << 27)); } - public override bool IsNicknamed { get => ((IV32 >> 0) & 1) == 1; set => IV32 = ((IV32 & ~0x00000001u) | (value ? 0x00000001u : 0u)); } - public override bool IsEgg { get => ((IV32 >> 1) & 1) == 1; set => IV32 = ((IV32 & ~0x00000002u) | (value ? 0x00000002u : 0u)); } - - private byte RIB7 { get => Data[0x3C]; set => Data[0x3C] = value; } // Hoenn 2b - private byte RIB6 { get => Data[0x3D]; set => Data[0x3D] = value; } // Hoenn 2a - private byte RIB5 { get => Data[0x3E]; set => Data[0x3E] = value; } // Hoenn 1b - private byte RIB4 { get => Data[0x3F]; set => Data[0x3F] = value; } // Hoenn 1a - public override bool RibbonG3Cool { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG3CoolSuper { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG3CoolHyper { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG3CoolMaster { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonG3Beauty { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonG3BeautySuper { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonG3BeautyHyper { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonG3BeautyMaster { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonG3Cute { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG3CuteSuper { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG3CuteHyper { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG3CuteMaster { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonG3Smart { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonG3SmartSuper { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonG3SmartHyper { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonG3SmartMaster { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonG3Tough { get => (RIB6 & (1 << 0)) == 1 << 0; set => RIB6 = (byte)((RIB6 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG3ToughSuper { get => (RIB6 & (1 << 1)) == 1 << 1; set => RIB6 = (byte)((RIB6 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG3ToughHyper { get => (RIB6 & (1 << 2)) == 1 << 2; set => RIB6 = (byte)((RIB6 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG3ToughMaster { get => (RIB6 & (1 << 3)) == 1 << 3; set => RIB6 = (byte)((RIB6 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonChampionG3 { get => (RIB6 & (1 << 4)) == 1 << 4; set => RIB6 = (byte)((RIB6 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonWinning { get => (RIB6 & (1 << 5)) == 1 << 5; set => RIB6 = (byte)((RIB6 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonVictory { get => (RIB6 & (1 << 6)) == 1 << 6; set => RIB6 = (byte)((RIB6 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonArtist { get => (RIB6 & (1 << 7)) == 1 << 7; set => RIB6 = (byte)((RIB6 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonEffort { get => (RIB7 & (1 << 0)) == 1 << 0; set => RIB7 = (byte)((RIB7 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonChampionBattle { get => (RIB7 & (1 << 1)) == 1 << 1; set => RIB7 = (byte)((RIB7 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonChampionRegional { get => (RIB7 & (1 << 2)) == 1 << 2; set => RIB7 = (byte)((RIB7 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonChampionNational { get => (RIB7 & (1 << 3)) == 1 << 3; set => RIB7 = (byte)((RIB7 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonCountry { get => (RIB7 & (1 << 4)) == 1 << 4; set => RIB7 = (byte)((RIB7 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonNational { get => (RIB7 & (1 << 5)) == 1 << 5; set => RIB7 = (byte)((RIB7 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonEarth { get => (RIB7 & (1 << 6)) == 1 << 6; set => RIB7 = (byte)((RIB7 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonWorld { get => (RIB7 & (1 << 7)) == 1 << 7; set => RIB7 = (byte)((RIB7 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - - public override bool FatefulEncounter { get => (Data[0x40] & 0x80) == 0x80; set => Data[0x40] = (byte)((Data[0x40] & ~0x80) | (value ? 0x80 : 0)); } - public override int Gender { get => (Data[0x40] >> 5) & 0x3; set => Data[0x40] = (byte)((Data[0x40] & ~0x60) | ((value & 3) << 5)); } - public override int Form { get => Data[0x40] & 0x1F; set => Data[0x40] = (byte)((Data[0x40] & ~0x1F) | (value & 0x1F)); } - public override int ShinyLeaf { get => Data[0x41]; set => Data[0x41] = (byte)value; } - - // 0x42-0x43 Unused - public override ushort Egg_LocationExtended - { - get => ReadUInt16BigEndian(Data.AsSpan(0x44)); - set => WriteUInt16BigEndian(Data.AsSpan(0x44), value); - } - - public override ushort Met_LocationExtended - { - get => ReadUInt16BigEndian(Data.AsSpan(0x46)); - set => WriteUInt16BigEndian(Data.AsSpan(0x46), value); - } - #endregion - - #region Block C - public override string Nickname { get => StringConverter4GC.GetString(Nickname_Trash); set => StringConverter4GC.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); } - // 0x5E unused - public override int Version { get => Data[0x5F]; set => Data[0x5F] = (byte)value; } - private byte RIB8 { get => Data[0x60]; set => Data[0x60] = value; } // Sinnoh 3 - private byte RIB9 { get => Data[0x61]; set => Data[0x61] = value; } // Sinnoh 4 - private byte RIBA { get => Data[0x62]; set => Data[0x62] = value; } // Sinnoh 5 - private byte RIBB { get => Data[0x63]; set => Data[0x63] = value; } // Sinnoh 6 - public override bool RibbonG4Cool { get => (RIB8 & (1 << 0)) == 1 << 0; set => RIB8 = (byte)((RIB8 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG4CoolGreat { get => (RIB8 & (1 << 1)) == 1 << 1; set => RIB8 = (byte)((RIB8 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG4CoolUltra { get => (RIB8 & (1 << 2)) == 1 << 2; set => RIB8 = (byte)((RIB8 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG4CoolMaster { get => (RIB8 & (1 << 3)) == 1 << 3; set => RIB8 = (byte)((RIB8 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonG4Beauty { get => (RIB8 & (1 << 4)) == 1 << 4; set => RIB8 = (byte)((RIB8 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonG4BeautyGreat { get => (RIB8 & (1 << 5)) == 1 << 5; set => RIB8 = (byte)((RIB8 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonG4BeautyUltra { get => (RIB8 & (1 << 6)) == 1 << 6; set => RIB8 = (byte)((RIB8 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonG4BeautyMaster { get => (RIB8 & (1 << 7)) == 1 << 7; set => RIB8 = (byte)((RIB8 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonG4Cute { get => (RIB9 & (1 << 0)) == 1 << 0; set => RIB9 = (byte)((RIB9 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG4CuteGreat { get => (RIB9 & (1 << 1)) == 1 << 1; set => RIB9 = (byte)((RIB9 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG4CuteUltra { get => (RIB9 & (1 << 2)) == 1 << 2; set => RIB9 = (byte)((RIB9 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG4CuteMaster { get => (RIB9 & (1 << 3)) == 1 << 3; set => RIB9 = (byte)((RIB9 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonG4Smart { get => (RIB9 & (1 << 4)) == 1 << 4; set => RIB9 = (byte)((RIB9 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonG4SmartGreat { get => (RIB9 & (1 << 5)) == 1 << 5; set => RIB9 = (byte)((RIB9 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonG4SmartUltra { get => (RIB9 & (1 << 6)) == 1 << 6; set => RIB9 = (byte)((RIB9 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonG4SmartMaster { get => (RIB9 & (1 << 7)) == 1 << 7; set => RIB9 = (byte)((RIB9 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonG4Tough { get => (RIBA & (1 << 0)) == 1 << 0; set => RIBA = (byte)((RIBA & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG4ToughGreat { get => (RIBA & (1 << 1)) == 1 << 1; set => RIBA = (byte)((RIBA & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG4ToughUltra { get => (RIBA & (1 << 2)) == 1 << 2; set => RIBA = (byte)((RIBA & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG4ToughMaster { get => (RIBA & (1 << 3)) == 1 << 3; set => RIBA = (byte)((RIBA & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RIBA_4 { get => (RIBA & (1 << 4)) == 1 << 4; set => RIBA = (byte)((RIBA & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused - public override bool RIBA_5 { get => (RIBA & (1 << 5)) == 1 << 5; set => RIBA = (byte)((RIBA & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused - public override bool RIBA_6 { get => (RIBA & (1 << 6)) == 1 << 6; set => RIBA = (byte)((RIBA & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public override bool RIBA_7 { get => (RIBA & (1 << 7)) == 1 << 7; set => RIBA = (byte)((RIBA & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - public override bool RIBB_0 { get => (RIBB & (1 << 0)) == 1 << 0; set => RIBB = (byte)((RIBB & ~(1 << 0)) | (value ? 1 << 0 : 0)); } // Unused - public override bool RIBB_1 { get => (RIBB & (1 << 1)) == 1 << 1; set => RIBB = (byte)((RIBB & ~(1 << 1)) | (value ? 1 << 1 : 0)); } // Unused - public override bool RIBB_2 { get => (RIBB & (1 << 2)) == 1 << 2; set => RIBB = (byte)((RIBB & ~(1 << 2)) | (value ? 1 << 2 : 0)); } // Unused - public override bool RIBB_3 { get => (RIBB & (1 << 3)) == 1 << 3; set => RIBB = (byte)((RIBB & ~(1 << 3)) | (value ? 1 << 3 : 0)); } // Unused - public override bool RIBB_4 { get => (RIBB & (1 << 4)) == 1 << 4; set => RIBB = (byte)((RIBB & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused - public override bool RIBB_5 { get => (RIBB & (1 << 5)) == 1 << 5; set => RIBB = (byte)((RIBB & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused - public override bool RIBB_6 { get => (RIBB & (1 << 6)) == 1 << 6; set => RIBB = (byte)((RIBB & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public override bool RIBB_7 { get => (RIBB & (1 << 7)) == 1 << 7; set => RIBB = (byte)((RIBB & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - // 0x64-0x67 Unused - #endregion - - #region Block D - public override string OT_Name { get => StringConverter4GC.GetString(OT_Trash); set => StringConverter4GC.SetString(OT_Trash, value.AsSpan(), 7, StringConverterOption.None); } - public override int Egg_Year { get => Data[0x78]; set => Data[0x78] = (byte)value; } - public override int Egg_Month { get => Data[0x79]; set => Data[0x79] = (byte)value; } - public override int Egg_Day { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } - public override int Met_Year { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } - public override int Met_Month { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } - public override int Met_Day { get => Data[0x7D]; set => Data[0x7D] = (byte)value; } - - public override ushort Egg_LocationDP - { - get => ReadUInt16BigEndian(Data.AsSpan(0x7E)); - set => WriteUInt16BigEndian(Data.AsSpan(0x7E), value); - } - public override ushort Met_LocationDP - { - get => ReadUInt16BigEndian(Data.AsSpan(0x80)); - set => WriteUInt16BigEndian(Data.AsSpan(0x80), value); - } - - private byte PKRS { get => Data[0x82]; set => Data[0x82] = value; } - public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } - public override byte BallDPPt { get => Data[0x83]; set => Data[0x83] = value; } - public override int Met_Level { get => Data[0x84] >> 1; set => Data[0x84] = (byte)((Data[0x84] & 0x1) | value << 1); } - public override int OT_Gender { get => Data[0x84] & 1; set => Data[0x84] = (byte)((Data[0x84] & ~0x1) | (value & 1)); } - public override GroundTileType GroundTile { get => (GroundTileType)Data[0x85]; set => Data[0x85] = (byte)value; } - public override byte BallHGSS { get => Data[0x86]; set => Data[0x86] = value; } - public override byte PokeathlonStat { get => Data[0x87]; set => Data[0x87] = value; } - #endregion - - // Not stored - public override int Status_Condition { get; set; } - public override int Stat_Level { get => CurrentLevel; set {} } - public override int Stat_HPCurrent { get; set; } - public override int Stat_HPMax { get; set; } - public override int Stat_ATK { get; set; } - public override int Stat_DEF { get; set; } - public override int Stat_SPE { get; set; } - public override int Stat_SPA { get; set; } - public override int Stat_SPD { get; set; } - - // Methods - protected override ushort CalculateChecksum() - { - ReadOnlySpan arr = Data.AsSpan(); - ushort chk = 0; - for (int i = 8; i < PokeCrypto.SIZE_4STORED; i += 2) - chk += ReadUInt16BigEndian(arr[i..]); - return chk; - } - - protected override byte[] Encrypt() - { - RefreshChecksum(); - return PokeCrypto.EncryptArray4BE(Data); - } - - public PK4 ConvertToPK4() - { - PK4 pk4 = ConvertTo(); - pk4.RefreshChecksum(); - return pk4; - } + public static BK4 ReadUnshuffle(ReadOnlySpan data) + { + var unshuffled = PokeCrypto.DecryptArray4BE(data); + var result = new BK4(unshuffled); + result.RefreshChecksum(); + return result; } -} \ No newline at end of file + + public BK4(byte[] data) : base(data) + { + Sanity = 0x4000; + ResetPartyStats(); + } + + public BK4() : this(new byte[PokeCrypto.SIZE_4STORED]) { } + + public override PKM Clone() => new BK4((byte[])Data.Clone()); + + // Structure + public override uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x00)); set => WriteUInt32BigEndian(Data.AsSpan(0x00), value); } + public override ushort Sanity { get => ReadUInt16BigEndian(Data.AsSpan(0x04)); set => WriteUInt16BigEndian(Data.AsSpan(0x04), value); } + public override ushort Checksum { get => ReadUInt16BigEndian(Data.AsSpan(0x06)); set => WriteUInt16BigEndian(Data.AsSpan(0x06), value); } + + #region Block A + public override int Species { get => ReadUInt16BigEndian(Data.AsSpan(0x08)); set => WriteUInt16BigEndian(Data.AsSpan(0x08), (ushort)value); } + public override int HeldItem { get => ReadUInt16BigEndian(Data.AsSpan(0x0A)); set => WriteUInt16BigEndian(Data.AsSpan(0x0A), (ushort)value); } + public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(0x0C)); set => WriteUInt16BigEndian(Data.AsSpan(0x0C), (ushort)value); } + public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(0x0E)); set => WriteUInt16BigEndian(Data.AsSpan(0x0E), (ushort)value); } + + public override uint EXP + { + get => ReadUInt32BigEndian(Data.AsSpan(0x10)); + set => WriteUInt32BigEndian(Data.AsSpan(0x10), value); + } + + public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; } + public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; } + public override int EV_DEF { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } + public override int EV_SPE { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } + public override int EV_SPA { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public override int EV_SPD { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + public override byte CNT_Cool { get => Data[0x1E]; set => Data[0x1E] = value; } + public override byte CNT_Beauty { get => Data[0x1F]; set => Data[0x1F] = value; } + public override byte CNT_Cute { get => Data[0x20]; set => Data[0x20] = value; } + public override byte CNT_Smart { get => Data[0x21]; set => Data[0x21] = value; } + public override byte CNT_Tough { get => Data[0x22]; set => Data[0x22] = value; } + public override byte CNT_Sheen { get => Data[0x23]; set => Data[0x23] = value; } + + private byte RIB3 { get => Data[0x24]; set => Data[0x24] = value; } // Unova 2 + private byte RIB2 { get => Data[0x25]; set => Data[0x25] = value; } // Unova 1 + private byte RIB1 { get => Data[0x26]; set => Data[0x26] = value; } // Sinnoh 2 + private byte RIB0 { get => Data[0x27]; set => Data[0x27] = value; } // Sinnoh 1 + public override bool RibbonChampionSinnoh { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonAbility { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonAbilityGreat { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonAbilityDouble { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonAbilityMulti { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonAbilityPair { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonAbilityWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonAlert { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonShock { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonDowncast { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonCareless { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonRelax { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonSnooze { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonSmile { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonGorgeous { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonRoyal { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonFootprint { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonRecord { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonEvent { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonLegend { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonChampionWorld { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonBirthday { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonSpecial { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonSouvenir { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonWishing { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RIB3_4 { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public override bool RIB3_5 { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public override bool RIB3_6 { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public override bool RIB3_7 { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + #endregion + + #region Block B + public override int Move1 { get => ReadUInt16BigEndian(Data.AsSpan(0x28)); set => WriteUInt16BigEndian(Data.AsSpan(0x28), (ushort)value); } + public override int Move2 { get => ReadUInt16BigEndian(Data.AsSpan(0x2A)); set => WriteUInt16BigEndian(Data.AsSpan(0x2A), (ushort)value); } + public override int Move3 { get => ReadUInt16BigEndian(Data.AsSpan(0x2C)); set => WriteUInt16BigEndian(Data.AsSpan(0x2C), (ushort)value); } + public override int Move4 { get => ReadUInt16BigEndian(Data.AsSpan(0x2E)); set => WriteUInt16BigEndian(Data.AsSpan(0x2E), (ushort)value); } + public override int Move1_PP { get => Data[0x30]; set => Data[0x30] = (byte)value; } + public override int Move2_PP { get => Data[0x31]; set => Data[0x31] = (byte)value; } + public override int Move3_PP { get => Data[0x32]; set => Data[0x32] = (byte)value; } + public override int Move4_PP { get => Data[0x33]; set => Data[0x33] = (byte)value; } + public override int Move1_PPUps { get => Data[0x34]; set => Data[0x34] = (byte)value; } + public override int Move2_PPUps { get => Data[0x35]; set => Data[0x35] = (byte)value; } + public override int Move3_PPUps { get => Data[0x36]; set => Data[0x36] = (byte)value; } + public override int Move4_PPUps { get => Data[0x37]; set => Data[0x37] = (byte)value; } + private uint IV32 { get => ReadUInt32BigEndian(Data.AsSpan(0x38)); set => WriteUInt32BigEndian(Data.AsSpan(0x38), value); } + public override int IV_SPD { get => (int)(IV32 >> 02) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 02)) | ((value > 31 ? 31u : (uint)value) << 02); } + public override int IV_SPA { get => (int)(IV32 >> 07) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 07)) | ((value > 31 ? 31u : (uint)value) << 07); } + public override int IV_SPE { get => (int)(IV32 >> 12) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 12)) | ((value > 31 ? 31u : (uint)value) << 12); } + public override int IV_DEF { get => (int)(IV32 >> 17) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 17)) | ((value > 31 ? 31u : (uint)value) << 17); } + public override int IV_ATK { get => (int)(IV32 >> 22) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 22)) | ((value > 31 ? 31u : (uint)value) << 22); } + public override int IV_HP { get => (int)(IV32 >> 27) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 27)) | ((value > 31 ? 31u : (uint)value) << 27); } + public override bool IsNicknamed { get => ((IV32 >> 0) & 1) == 1; set => IV32 = (IV32 & ~0x00000001u) | (value ? 0x00000001u : 0u); } + public override bool IsEgg { get => ((IV32 >> 1) & 1) == 1; set => IV32 = (IV32 & ~0x00000002u) | (value ? 0x00000002u : 0u); } + + private byte RIB7 { get => Data[0x3C]; set => Data[0x3C] = value; } // Hoenn 2b + private byte RIB6 { get => Data[0x3D]; set => Data[0x3D] = value; } // Hoenn 2a + private byte RIB5 { get => Data[0x3E]; set => Data[0x3E] = value; } // Hoenn 1b + private byte RIB4 { get => Data[0x3F]; set => Data[0x3F] = value; } // Hoenn 1a + public override bool RibbonG3Cool { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG3CoolSuper { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG3CoolHyper { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG3CoolMaster { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG3Beauty { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG3BeautySuper { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG3BeautyHyper { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG3BeautyMaster { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG3Cute { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG3CuteSuper { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG3CuteHyper { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG3CuteMaster { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG3Smart { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG3SmartSuper { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG3SmartHyper { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG3SmartMaster { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG3Tough { get => (RIB6 & (1 << 0)) == 1 << 0; set => RIB6 = (byte)((RIB6 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG3ToughSuper { get => (RIB6 & (1 << 1)) == 1 << 1; set => RIB6 = (byte)((RIB6 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG3ToughHyper { get => (RIB6 & (1 << 2)) == 1 << 2; set => RIB6 = (byte)((RIB6 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG3ToughMaster { get => (RIB6 & (1 << 3)) == 1 << 3; set => RIB6 = (byte)((RIB6 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonChampionG3 { get => (RIB6 & (1 << 4)) == 1 << 4; set => RIB6 = (byte)((RIB6 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonWinning { get => (RIB6 & (1 << 5)) == 1 << 5; set => RIB6 = (byte)((RIB6 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonVictory { get => (RIB6 & (1 << 6)) == 1 << 6; set => RIB6 = (byte)((RIB6 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonArtist { get => (RIB6 & (1 << 7)) == 1 << 7; set => RIB6 = (byte)((RIB6 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonEffort { get => (RIB7 & (1 << 0)) == 1 << 0; set => RIB7 = (byte)((RIB7 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonChampionBattle { get => (RIB7 & (1 << 1)) == 1 << 1; set => RIB7 = (byte)((RIB7 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonChampionRegional { get => (RIB7 & (1 << 2)) == 1 << 2; set => RIB7 = (byte)((RIB7 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonChampionNational { get => (RIB7 & (1 << 3)) == 1 << 3; set => RIB7 = (byte)((RIB7 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonCountry { get => (RIB7 & (1 << 4)) == 1 << 4; set => RIB7 = (byte)((RIB7 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonNational { get => (RIB7 & (1 << 5)) == 1 << 5; set => RIB7 = (byte)((RIB7 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonEarth { get => (RIB7 & (1 << 6)) == 1 << 6; set => RIB7 = (byte)((RIB7 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonWorld { get => (RIB7 & (1 << 7)) == 1 << 7; set => RIB7 = (byte)((RIB7 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + + public override bool FatefulEncounter { get => (Data[0x40] & 0x80) == 0x80; set => Data[0x40] = (byte)((Data[0x40] & ~0x80) | (value ? 0x80 : 0)); } + public override int Gender { get => (Data[0x40] >> 5) & 0x3; set => Data[0x40] = (byte)((Data[0x40] & ~0x60) | ((value & 3) << 5)); } + public override int Form { get => Data[0x40] & 0x1F; set => Data[0x40] = (byte)((Data[0x40] & ~0x1F) | (value & 0x1F)); } + public override int ShinyLeaf { get => Data[0x41]; set => Data[0x41] = (byte)value; } + + // 0x42-0x43 Unused + public override ushort Egg_LocationExtended + { + get => ReadUInt16BigEndian(Data.AsSpan(0x44)); + set => WriteUInt16BigEndian(Data.AsSpan(0x44), value); + } + + public override ushort Met_LocationExtended + { + get => ReadUInt16BigEndian(Data.AsSpan(0x46)); + set => WriteUInt16BigEndian(Data.AsSpan(0x46), value); + } + #endregion + + #region Block C + public override string Nickname { get => StringConverter4GC.GetString(Nickname_Trash); set => StringConverter4GC.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); } + // 0x5E unused + public override int Version { get => Data[0x5F]; set => Data[0x5F] = (byte)value; } + private byte RIB8 { get => Data[0x60]; set => Data[0x60] = value; } // Sinnoh 3 + private byte RIB9 { get => Data[0x61]; set => Data[0x61] = value; } // Sinnoh 4 + private byte RIBA { get => Data[0x62]; set => Data[0x62] = value; } // Sinnoh 5 + private byte RIBB { get => Data[0x63]; set => Data[0x63] = value; } // Sinnoh 6 + public override bool RibbonG4Cool { get => (RIB8 & (1 << 0)) == 1 << 0; set => RIB8 = (byte)((RIB8 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG4CoolGreat { get => (RIB8 & (1 << 1)) == 1 << 1; set => RIB8 = (byte)((RIB8 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG4CoolUltra { get => (RIB8 & (1 << 2)) == 1 << 2; set => RIB8 = (byte)((RIB8 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG4CoolMaster { get => (RIB8 & (1 << 3)) == 1 << 3; set => RIB8 = (byte)((RIB8 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG4Beauty { get => (RIB8 & (1 << 4)) == 1 << 4; set => RIB8 = (byte)((RIB8 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG4BeautyGreat { get => (RIB8 & (1 << 5)) == 1 << 5; set => RIB8 = (byte)((RIB8 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG4BeautyUltra { get => (RIB8 & (1 << 6)) == 1 << 6; set => RIB8 = (byte)((RIB8 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG4BeautyMaster { get => (RIB8 & (1 << 7)) == 1 << 7; set => RIB8 = (byte)((RIB8 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG4Cute { get => (RIB9 & (1 << 0)) == 1 << 0; set => RIB9 = (byte)((RIB9 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG4CuteGreat { get => (RIB9 & (1 << 1)) == 1 << 1; set => RIB9 = (byte)((RIB9 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG4CuteUltra { get => (RIB9 & (1 << 2)) == 1 << 2; set => RIB9 = (byte)((RIB9 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG4CuteMaster { get => (RIB9 & (1 << 3)) == 1 << 3; set => RIB9 = (byte)((RIB9 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG4Smart { get => (RIB9 & (1 << 4)) == 1 << 4; set => RIB9 = (byte)((RIB9 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG4SmartGreat { get => (RIB9 & (1 << 5)) == 1 << 5; set => RIB9 = (byte)((RIB9 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG4SmartUltra { get => (RIB9 & (1 << 6)) == 1 << 6; set => RIB9 = (byte)((RIB9 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG4SmartMaster { get => (RIB9 & (1 << 7)) == 1 << 7; set => RIB9 = (byte)((RIB9 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG4Tough { get => (RIBA & (1 << 0)) == 1 << 0; set => RIBA = (byte)((RIBA & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG4ToughGreat { get => (RIBA & (1 << 1)) == 1 << 1; set => RIBA = (byte)((RIBA & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG4ToughUltra { get => (RIBA & (1 << 2)) == 1 << 2; set => RIBA = (byte)((RIBA & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG4ToughMaster { get => (RIBA & (1 << 3)) == 1 << 3; set => RIBA = (byte)((RIBA & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RIBA_4 { get => (RIBA & (1 << 4)) == 1 << 4; set => RIBA = (byte)((RIBA & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public override bool RIBA_5 { get => (RIBA & (1 << 5)) == 1 << 5; set => RIBA = (byte)((RIBA & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public override bool RIBA_6 { get => (RIBA & (1 << 6)) == 1 << 6; set => RIBA = (byte)((RIBA & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public override bool RIBA_7 { get => (RIBA & (1 << 7)) == 1 << 7; set => RIBA = (byte)((RIBA & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + public override bool RIBB_0 { get => (RIBB & (1 << 0)) == 1 << 0; set => RIBB = (byte)((RIBB & ~(1 << 0)) | (value ? 1 << 0 : 0)); } // Unused + public override bool RIBB_1 { get => (RIBB & (1 << 1)) == 1 << 1; set => RIBB = (byte)((RIBB & ~(1 << 1)) | (value ? 1 << 1 : 0)); } // Unused + public override bool RIBB_2 { get => (RIBB & (1 << 2)) == 1 << 2; set => RIBB = (byte)((RIBB & ~(1 << 2)) | (value ? 1 << 2 : 0)); } // Unused + public override bool RIBB_3 { get => (RIBB & (1 << 3)) == 1 << 3; set => RIBB = (byte)((RIBB & ~(1 << 3)) | (value ? 1 << 3 : 0)); } // Unused + public override bool RIBB_4 { get => (RIBB & (1 << 4)) == 1 << 4; set => RIBB = (byte)((RIBB & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public override bool RIBB_5 { get => (RIBB & (1 << 5)) == 1 << 5; set => RIBB = (byte)((RIBB & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public override bool RIBB_6 { get => (RIBB & (1 << 6)) == 1 << 6; set => RIBB = (byte)((RIBB & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public override bool RIBB_7 { get => (RIBB & (1 << 7)) == 1 << 7; set => RIBB = (byte)((RIBB & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + // 0x64-0x67 Unused + #endregion + + #region Block D + public override string OT_Name { get => StringConverter4GC.GetString(OT_Trash); set => StringConverter4GC.SetString(OT_Trash, value.AsSpan(), 7, StringConverterOption.None); } + public override int Egg_Year { get => Data[0x78]; set => Data[0x78] = (byte)value; } + public override int Egg_Month { get => Data[0x79]; set => Data[0x79] = (byte)value; } + public override int Egg_Day { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } + public override int Met_Year { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } + public override int Met_Month { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } + public override int Met_Day { get => Data[0x7D]; set => Data[0x7D] = (byte)value; } + + public override ushort Egg_LocationDP + { + get => ReadUInt16BigEndian(Data.AsSpan(0x7E)); + set => WriteUInt16BigEndian(Data.AsSpan(0x7E), value); + } + public override ushort Met_LocationDP + { + get => ReadUInt16BigEndian(Data.AsSpan(0x80)); + set => WriteUInt16BigEndian(Data.AsSpan(0x80), value); + } + + private byte PKRS { get => Data[0x82]; set => Data[0x82] = value; } + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + public override byte BallDPPt { get => Data[0x83]; set => Data[0x83] = value; } + public override int Met_Level { get => Data[0x84] >> 1; set => Data[0x84] = (byte)((Data[0x84] & 0x1) | (value << 1)); } + public override int OT_Gender { get => Data[0x84] & 1; set => Data[0x84] = (byte)((Data[0x84] & ~0x1) | (value & 1)); } + public override GroundTileType GroundTile { get => (GroundTileType)Data[0x85]; set => Data[0x85] = (byte)value; } + public override byte BallHGSS { get => Data[0x86]; set => Data[0x86] = value; } + public override byte PokeathlonStat { get => Data[0x87]; set => Data[0x87] = value; } + #endregion + + // Not stored + public override int Status_Condition { get; set; } + public override int Stat_Level { get => CurrentLevel; set {} } + public override int Stat_HPCurrent { get; set; } + public override int Stat_HPMax { get; set; } + public override int Stat_ATK { get; set; } + public override int Stat_DEF { get; set; } + public override int Stat_SPE { get; set; } + public override int Stat_SPA { get; set; } + public override int Stat_SPD { get; set; } + + // Methods + protected override ushort CalculateChecksum() + { + ReadOnlySpan arr = Data.AsSpan(); + ushort chk = 0; + for (int i = 8; i < PokeCrypto.SIZE_4STORED; i += 2) + chk += ReadUInt16BigEndian(arr[i..]); + return chk; + } + + protected override byte[] Encrypt() + { + RefreshChecksum(); + return PokeCrypto.EncryptArray4BE(Data); + } + + public PK4 ConvertToPK4() + { + PK4 pk4 = ConvertTo(); + pk4.RefreshChecksum(); + return pk4; + } +} diff --git a/PKHeX.Core/PKM/CK3.cs b/PKHeX.Core/PKM/CK3.cs index 9c781fd94..9ba58e799 100644 --- a/PKHeX.Core/PKM/CK3.cs +++ b/PKHeX.Core/PKM/CK3.cs @@ -2,195 +2,194 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 3 format, exclusively for Pokémon Colosseum. +public sealed class CK3 : G3PKM, IShadowPKM { - /// Generation 3 format, exclusively for Pokémon Colosseum. - public sealed class CK3 : G3PKM, IShadowPKM + private static readonly ushort[] Unused = { - private static readonly ushort[] Unused = - { - 0x11, 0x12, 0x13, - 0x61, 0x62, 0x63, 0x64, - 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xDA, 0xDB, - 0xE4, 0xE5, 0xE6, 0xE7, 0xCE, - 0xFB, // not fateful -- what is it? - 0xD7, // index within party - // 0xFC onwards unused? no, it's some pointers and values used by the game? - }; + 0x11, 0x12, 0x13, + 0x61, 0x62, 0x63, 0x64, + 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xDA, 0xDB, + 0xE4, 0xE5, 0xE6, 0xE7, 0xCE, + 0xFB, // not fateful -- what is it? + 0xD7, // index within party + // 0xFC onwards unused? no, it's some pointers and values used by the game? + }; - public override IReadOnlyList ExtraBytes => Unused; + public override IReadOnlyList ExtraBytes => Unused; - public override int SIZE_PARTY => PokeCrypto.SIZE_3CSTORED; - public override int SIZE_STORED => PokeCrypto.SIZE_3CSTORED; - public override EntityContext Context => EntityContext.Gen3; - public override PersonalInfo PersonalInfo => PersonalTable.RS[Species]; - public CK3(byte[] data) : base(data) { } - public CK3() : this(new byte[PokeCrypto.SIZE_3CSTORED]) { } - public override PKM Clone() => new CK3((byte[])Data.Clone()); + public override int SIZE_PARTY => PokeCrypto.SIZE_3CSTORED; + public override int SIZE_STORED => PokeCrypto.SIZE_3CSTORED; + public override EntityContext Context => EntityContext.Gen3; + public override PersonalInfo PersonalInfo => PersonalTable.RS[Species]; + public CK3(byte[] data) : base(data) { } + public CK3() : this(new byte[PokeCrypto.SIZE_3CSTORED]) { } + public override PKM Clone() => new CK3((byte[])Data.Clone()); - // Trash Bytes - public override Span OT_Trash => Data.AsSpan(0x18, 22); - public override Span Nickname_Trash => Data.AsSpan(0x2E, 22); - public Span NicknameCopy_Trash => Data.AsSpan(0x44, 22); + // Trash Bytes + public override Span OT_Trash => Data.AsSpan(0x18, 22); + public override Span Nickname_Trash => Data.AsSpan(0x2E, 22); + public Span NicknameCopy_Trash => Data.AsSpan(0x44, 22); - // Future Attributes - public override ushort SpeciesID3 { get => ReadUInt16BigEndian(Data.AsSpan(0x00)); set => WriteUInt16BigEndian(Data.AsSpan(0x00), value); } // raw access - public override int Species { get => SpeciesConverter.GetG4Species(SpeciesID3); set => SpeciesID3 = (ushort)SpeciesConverter.GetG3Species(value); } - // 02-04 unused - public override uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x04)); set => WriteUInt32BigEndian(Data.AsSpan(0x04), value); } - public override int Version { get => GetGBAVersionID(Data[0x08]); set => Data[0x08] = GetGCVersionID(value); } - public int CurrentRegion { get => Data[0x09]; set => Data[0x09] = (byte)value; } - public int OriginalRegion { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } - public override int Language { get => Core.Language.GetMainLangIDfromGC(Data[0x0B]); set => Data[0x0B] = Core.Language.GetGCLangIDfromMain((byte)value); } - public override int Met_Location { get => ReadUInt16BigEndian(Data.AsSpan(0x0C)); set => WriteUInt16BigEndian(Data.AsSpan(0x0C), (ushort)value); } - public override int Met_Level { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } - public override int Ball { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } - public override int OT_Gender { get => Data[0x10]; set => Data[0x10] = (byte)value; } - public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(0x14)); set => WriteUInt16BigEndian(Data.AsSpan(0x14), (ushort)value); } - public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(0x16)); set => WriteUInt16BigEndian(Data.AsSpan(0x16), (ushort)value); } - public override string OT_Name { get => StringConverter3GC.GetString(OT_Trash); set => StringConverter3GC.SetString(OT_Trash, value.AsSpan(), 10, StringConverterOption.None); } - public override string Nickname { get => StringConverter3GC.GetString(Nickname_Trash); set { StringConverter3GC.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); NicknameCopy = value; } } - public string NicknameCopy { get => StringConverter3GC.GetString(NicknameCopy_Trash); set => StringConverter3GC.SetString(NicknameCopy_Trash, value.AsSpan(), 10, StringConverterOption.None); } - public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0x5C)); set => WriteUInt32BigEndian(Data.AsSpan(0x5C), value); } - public override int Stat_Level { get => Data[0x60]; set => Data[0x60] = (byte)value; } + // Future Attributes + public override ushort SpeciesID3 { get => ReadUInt16BigEndian(Data.AsSpan(0x00)); set => WriteUInt16BigEndian(Data.AsSpan(0x00), value); } // raw access + public override int Species { get => SpeciesConverter.GetG4Species(SpeciesID3); set => SpeciesID3 = (ushort)SpeciesConverter.GetG3Species(value); } + // 02-04 unused + public override uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x04)); set => WriteUInt32BigEndian(Data.AsSpan(0x04), value); } + public override int Version { get => GetGBAVersionID(Data[0x08]); set => Data[0x08] = GetGCVersionID(value); } + public int CurrentRegion { get => Data[0x09]; set => Data[0x09] = (byte)value; } + public int OriginalRegion { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } + public override int Language { get => Core.Language.GetMainLangIDfromGC(Data[0x0B]); set => Data[0x0B] = Core.Language.GetGCLangIDfromMain((byte)value); } + public override int Met_Location { get => ReadUInt16BigEndian(Data.AsSpan(0x0C)); set => WriteUInt16BigEndian(Data.AsSpan(0x0C), (ushort)value); } + public override int Met_Level { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } + public override int Ball { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } + public override int OT_Gender { get => Data[0x10]; set => Data[0x10] = (byte)value; } + public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(0x14)); set => WriteUInt16BigEndian(Data.AsSpan(0x14), (ushort)value); } + public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(0x16)); set => WriteUInt16BigEndian(Data.AsSpan(0x16), (ushort)value); } + public override string OT_Name { get => StringConverter3GC.GetString(OT_Trash); set => StringConverter3GC.SetString(OT_Trash, value.AsSpan(), 10, StringConverterOption.None); } + public override string Nickname { get => StringConverter3GC.GetString(Nickname_Trash); set { StringConverter3GC.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); NicknameCopy = value; } } + public string NicknameCopy { get => StringConverter3GC.GetString(NicknameCopy_Trash); set => StringConverter3GC.SetString(NicknameCopy_Trash, value.AsSpan(), 10, StringConverterOption.None); } + public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0x5C)); set => WriteUInt32BigEndian(Data.AsSpan(0x5C), value); } + public override int Stat_Level { get => Data[0x60]; set => Data[0x60] = (byte)value; } - // 0x64-0x77 are battle/status related - public override int Status_Condition { get; set; } // where are we - // Not that the program cares + // 0x64-0x77 are battle/status related + public override int Status_Condition { get; set; } // where are we + // Not that the program cares - // Moves - public override int Move1 { get => ReadUInt16BigEndian(Data.AsSpan(0x78)); set => WriteUInt16BigEndian(Data.AsSpan(0x78), (ushort)value); } - public override int Move1_PP { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } - public override int Move1_PPUps { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } - public override int Move2 { get => ReadUInt16BigEndian(Data.AsSpan(0x7C)); set => WriteUInt16BigEndian(Data.AsSpan(0x7C), (ushort)value); } - public override int Move2_PP { get => Data[0x7E]; set => Data[0x7E] = (byte)value; } - public override int Move2_PPUps { get => Data[0x7F]; set => Data[0x7F] = (byte)value; } - public override int Move3 { get => ReadUInt16BigEndian(Data.AsSpan(0x80)); set => WriteUInt16BigEndian(Data.AsSpan(0x80), (ushort)value); } - public override int Move3_PP { get => Data[0x82]; set => Data[0x82] = (byte)value; } - public override int Move3_PPUps { get => Data[0x83]; set => Data[0x83] = (byte)value; } - public override int Move4 { get => ReadUInt16BigEndian(Data.AsSpan(0x84)); set => WriteUInt16BigEndian(Data.AsSpan(0x84), (ushort)value); } - public override int Move4_PP { get => Data[0x86]; set => Data[0x86] = (byte)value; } - public override int Move4_PPUps { get => Data[0x87]; set => Data[0x87] = (byte)value; } + // Moves + public override int Move1 { get => ReadUInt16BigEndian(Data.AsSpan(0x78)); set => WriteUInt16BigEndian(Data.AsSpan(0x78), (ushort)value); } + public override int Move1_PP { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } + public override int Move1_PPUps { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } + public override int Move2 { get => ReadUInt16BigEndian(Data.AsSpan(0x7C)); set => WriteUInt16BigEndian(Data.AsSpan(0x7C), (ushort)value); } + public override int Move2_PP { get => Data[0x7E]; set => Data[0x7E] = (byte)value; } + public override int Move2_PPUps { get => Data[0x7F]; set => Data[0x7F] = (byte)value; } + public override int Move3 { get => ReadUInt16BigEndian(Data.AsSpan(0x80)); set => WriteUInt16BigEndian(Data.AsSpan(0x80), (ushort)value); } + public override int Move3_PP { get => Data[0x82]; set => Data[0x82] = (byte)value; } + public override int Move3_PPUps { get => Data[0x83]; set => Data[0x83] = (byte)value; } + public override int Move4 { get => ReadUInt16BigEndian(Data.AsSpan(0x84)); set => WriteUInt16BigEndian(Data.AsSpan(0x84), (ushort)value); } + public override int Move4_PP { get => Data[0x86]; set => Data[0x86] = (byte)value; } + public override int Move4_PPUps { get => Data[0x87]; set => Data[0x87] = (byte)value; } - public override int SpriteItem => ItemConverter.GetItemFuture3((ushort)HeldItem); - public override int HeldItem { get => ReadUInt16BigEndian(Data.AsSpan(0x88)); set => WriteUInt16BigEndian(Data.AsSpan(0x88), (ushort)value); } + public override int SpriteItem => ItemConverter.GetItemFuture3((ushort)HeldItem); + public override int HeldItem { get => ReadUInt16BigEndian(Data.AsSpan(0x88)); set => WriteUInt16BigEndian(Data.AsSpan(0x88), (ushort)value); } - // More party stats - public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x8A)); set => WriteUInt16BigEndian(Data.AsSpan(0x8A), (ushort)value); } - public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x8C)); set => WriteUInt16BigEndian(Data.AsSpan(0x8C), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x8E)); set => WriteUInt16BigEndian(Data.AsSpan(0x8E), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x90)); set => WriteUInt16BigEndian(Data.AsSpan(0x90), (ushort)value); } - public override int Stat_SPA { get => ReadUInt16BigEndian(Data.AsSpan(0x92)); set => WriteUInt16BigEndian(Data.AsSpan(0x92), (ushort)value); } - public override int Stat_SPD { get => ReadUInt16BigEndian(Data.AsSpan(0x94)); set => WriteUInt16BigEndian(Data.AsSpan(0x94), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x96)); set => WriteUInt16BigEndian(Data.AsSpan(0x96), (ushort)value); } + // More party stats + public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x8A)); set => WriteUInt16BigEndian(Data.AsSpan(0x8A), (ushort)value); } + public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x8C)); set => WriteUInt16BigEndian(Data.AsSpan(0x8C), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x8E)); set => WriteUInt16BigEndian(Data.AsSpan(0x8E), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x90)); set => WriteUInt16BigEndian(Data.AsSpan(0x90), (ushort)value); } + public override int Stat_SPA { get => ReadUInt16BigEndian(Data.AsSpan(0x92)); set => WriteUInt16BigEndian(Data.AsSpan(0x92), (ushort)value); } + public override int Stat_SPD { get => ReadUInt16BigEndian(Data.AsSpan(0x94)); set => WriteUInt16BigEndian(Data.AsSpan(0x94), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x96)); set => WriteUInt16BigEndian(Data.AsSpan(0x96), (ushort)value); } - // EVs - public override int EV_HP { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x98))); - set => WriteUInt16BigEndian(Data.AsSpan(0x98), (ushort)(value & 0xFF)); } + // EVs + public override int EV_HP { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x98))); + set => WriteUInt16BigEndian(Data.AsSpan(0x98), (ushort)(value & 0xFF)); } - public override int EV_ATK { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9A))); - set => WriteUInt16BigEndian(Data.AsSpan(0x9A), (ushort)(value & 0xFF)); } + public override int EV_ATK { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9A))); + set => WriteUInt16BigEndian(Data.AsSpan(0x9A), (ushort)(value & 0xFF)); } - public override int EV_DEF { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9C))); - set => WriteUInt16BigEndian(Data.AsSpan(0x9C), (ushort)(value & 0xFF)); } + public override int EV_DEF { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9C))); + set => WriteUInt16BigEndian(Data.AsSpan(0x9C), (ushort)(value & 0xFF)); } - public override int EV_SPA { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9E))); - set => WriteUInt16BigEndian(Data.AsSpan(0x9E), (ushort)(value & 0xFF)); } + public override int EV_SPA { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9E))); + set => WriteUInt16BigEndian(Data.AsSpan(0x9E), (ushort)(value & 0xFF)); } - public override int EV_SPD { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA0))); - set => WriteUInt16BigEndian(Data.AsSpan(0xA0), (ushort)(value & 0xFF)); } + public override int EV_SPD { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA0))); + set => WriteUInt16BigEndian(Data.AsSpan(0xA0), (ushort)(value & 0xFF)); } - public override int EV_SPE { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA2))); - set => WriteUInt16BigEndian(Data.AsSpan(0xA2), (ushort)(value & 0xFF)); } + public override int EV_SPE { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA2))); + set => WriteUInt16BigEndian(Data.AsSpan(0xA2), (ushort)(value & 0xFF)); } - // IVs - public override int IV_HP { - get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xA4))); - set => WriteUInt16BigEndian(Data.AsSpan(0xA4), (ushort)(value & 0x1F)); } + // IVs + public override int IV_HP { + get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xA4))); + set => WriteUInt16BigEndian(Data.AsSpan(0xA4), (ushort)(value & 0x1F)); } - public override int IV_ATK { - get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xA6))); - set => WriteUInt16BigEndian(Data.AsSpan(0xA6), (ushort)(value & 0x1F)); } + public override int IV_ATK { + get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xA6))); + set => WriteUInt16BigEndian(Data.AsSpan(0xA6), (ushort)(value & 0x1F)); } - public override int IV_DEF { - get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xA8))); - set => WriteUInt16BigEndian(Data.AsSpan(0xA8), (ushort)(value & 0x1F)); } + public override int IV_DEF { + get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xA8))); + set => WriteUInt16BigEndian(Data.AsSpan(0xA8), (ushort)(value & 0x1F)); } - public override int IV_SPA { - get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xAA))); - set => WriteUInt16BigEndian(Data.AsSpan(0xAA), (ushort)(value & 0x1F)); } + public override int IV_SPA { + get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xAA))); + set => WriteUInt16BigEndian(Data.AsSpan(0xAA), (ushort)(value & 0x1F)); } - public override int IV_SPD { - get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xAC))); - set => WriteUInt16BigEndian(Data.AsSpan(0xAC), (ushort)(value & 0x1F)); } + public override int IV_SPD { + get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xAC))); + set => WriteUInt16BigEndian(Data.AsSpan(0xAC), (ushort)(value & 0x1F)); } - public override int IV_SPE { - get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xAE))); - set => WriteUInt16BigEndian(Data.AsSpan(0xAE), (ushort)(value & 0x1F)); } + public override int IV_SPE { + get => Math.Min((ushort)31, ReadUInt16BigEndian(Data.AsSpan(0xAE))); + set => WriteUInt16BigEndian(Data.AsSpan(0xAE), (ushort)(value & 0x1F)); } - public override int OT_Friendship { get => Data[0xB0]; set => Data[0xB0] = (byte)value; } + public override int OT_Friendship { get => Data[0xB0]; set => Data[0xB0] = (byte)value; } - // Contest - public override byte CNT_Cool { get => Data[0xB2]; set => Data[0xB2] = value; } - public override byte CNT_Beauty { get => Data[0xB3]; set => Data[0xB3] = value; } - public override byte CNT_Cute { get => Data[0xB4]; set => Data[0xB4] = value; } - public override byte CNT_Smart { get => Data[0xB5]; set => Data[0xB5] = value; } - public override byte CNT_Tough { get => Data[0xB6]; set => Data[0xB6] = value; } - public override int RibbonCountG3Cool { get => Data[0xB7]; set => Data[0xB7] = (byte)value; } - public override int RibbonCountG3Beauty { get => Data[0xB8]; set => Data[0xB8] = (byte)value; } - public override int RibbonCountG3Cute { get => Data[0xB9]; set => Data[0xB9] = (byte)value; } - public override int RibbonCountG3Smart { get => Data[0xBA]; set => Data[0xBA] = (byte)value; } - public override int RibbonCountG3Tough { get => Data[0xBB]; set => Data[0xBB] = (byte)value; } - public override byte CNT_Sheen { get => Data[0xBC]; set => Data[0xBC] = value; } + // Contest + public override byte CNT_Cool { get => Data[0xB2]; set => Data[0xB2] = value; } + public override byte CNT_Beauty { get => Data[0xB3]; set => Data[0xB3] = value; } + public override byte CNT_Cute { get => Data[0xB4]; set => Data[0xB4] = value; } + public override byte CNT_Smart { get => Data[0xB5]; set => Data[0xB5] = value; } + public override byte CNT_Tough { get => Data[0xB6]; set => Data[0xB6] = value; } + public override int RibbonCountG3Cool { get => Data[0xB7]; set => Data[0xB7] = (byte)value; } + public override int RibbonCountG3Beauty { get => Data[0xB8]; set => Data[0xB8] = (byte)value; } + public override int RibbonCountG3Cute { get => Data[0xB9]; set => Data[0xB9] = (byte)value; } + public override int RibbonCountG3Smart { get => Data[0xBA]; set => Data[0xBA] = (byte)value; } + public override int RibbonCountG3Tough { get => Data[0xBB]; set => Data[0xBB] = (byte)value; } + public override byte CNT_Sheen { get => Data[0xBC]; set => Data[0xBC] = value; } - // Ribbons - public override bool RibbonChampionG3 { get => Data[0xBD] == 1; set => Data[0xBD] = value ? (byte)1 : (byte)0; } - public override bool RibbonWinning { get => Data[0xBE] == 1; set => Data[0xBE] = value ? (byte)1 : (byte)0; } - public override bool RibbonVictory { get => Data[0xBF] == 1; set => Data[0xBF] = value ? (byte)1 : (byte)0; } - public override bool RibbonArtist { get => Data[0xC0] == 1; set => Data[0xC0] = value ? (byte)1 : (byte)0; } - public override bool RibbonEffort { get => Data[0xC1] == 1; set => Data[0xC1] = value ? (byte)1 : (byte)0; } - public override bool RibbonChampionBattle { get => Data[0xC2] == 1; set => Data[0xC2] = value ? (byte)1 : (byte)0; } - public override bool RibbonChampionRegional { get => Data[0xC3] == 1; set => Data[0xC3] = value ? (byte)1 : (byte)0; } - public override bool RibbonChampionNational { get => Data[0xC4] == 1; set => Data[0xC4] = value ? (byte)1 : (byte)0; } - public override bool RibbonCountry { get => Data[0xC5] == 1; set => Data[0xC5] = value ? (byte)1 : (byte)0; } - public override bool RibbonNational { get => Data[0xC6] == 1; set => Data[0xC6] = value ? (byte)1 : (byte)0; } - public override bool RibbonEarth { get => Data[0xC7] == 1; set => Data[0xC7] = value ? (byte)1 : (byte)0; } - public override bool RibbonWorld { get => Data[0xC8] == 1; set => Data[0xC8] = value ? (byte)1 : (byte)0; } - public override bool Unused1 { get => ((Data[0xC9] >> 0) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] & ~1) | (value ? 1 : 0)); } - public override bool Unused2 { get => ((Data[0xC9] >> 1) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] & ~2) | (value ? 2 : 0)); } - public override bool Unused3 { get => ((Data[0xC9] >> 2) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] & ~4) | (value ? 4 : 0)); } - public override bool Unused4 { get => ((Data[0xC9] >> 3) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] & ~8) | (value ? 8 : 0)); } - public override bool FatefulEncounter { get => ((Data[0xC9] >> 4) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] &~16) | (value ?16 : 0)); } + // Ribbons + public override bool RibbonChampionG3 { get => Data[0xBD] == 1; set => Data[0xBD] = value ? (byte)1 : (byte)0; } + public override bool RibbonWinning { get => Data[0xBE] == 1; set => Data[0xBE] = value ? (byte)1 : (byte)0; } + public override bool RibbonVictory { get => Data[0xBF] == 1; set => Data[0xBF] = value ? (byte)1 : (byte)0; } + public override bool RibbonArtist { get => Data[0xC0] == 1; set => Data[0xC0] = value ? (byte)1 : (byte)0; } + public override bool RibbonEffort { get => Data[0xC1] == 1; set => Data[0xC1] = value ? (byte)1 : (byte)0; } + public override bool RibbonChampionBattle { get => Data[0xC2] == 1; set => Data[0xC2] = value ? (byte)1 : (byte)0; } + public override bool RibbonChampionRegional { get => Data[0xC3] == 1; set => Data[0xC3] = value ? (byte)1 : (byte)0; } + public override bool RibbonChampionNational { get => Data[0xC4] == 1; set => Data[0xC4] = value ? (byte)1 : (byte)0; } + public override bool RibbonCountry { get => Data[0xC5] == 1; set => Data[0xC5] = value ? (byte)1 : (byte)0; } + public override bool RibbonNational { get => Data[0xC6] == 1; set => Data[0xC6] = value ? (byte)1 : (byte)0; } + public override bool RibbonEarth { get => Data[0xC7] == 1; set => Data[0xC7] = value ? (byte)1 : (byte)0; } + public override bool RibbonWorld { get => Data[0xC8] == 1; set => Data[0xC8] = value ? (byte)1 : (byte)0; } + public override bool Unused1 { get => ((Data[0xC9] >> 0) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] & ~1) | (value ? 1 : 0)); } + public override bool Unused2 { get => ((Data[0xC9] >> 1) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] & ~2) | (value ? 2 : 0)); } + public override bool Unused3 { get => ((Data[0xC9] >> 2) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] & ~4) | (value ? 4 : 0)); } + public override bool Unused4 { get => ((Data[0xC9] >> 3) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] & ~8) | (value ? 8 : 0)); } + public override bool FatefulEncounter { get => ((Data[0xC9] >> 4) & 1) == 1; set => Data[0xC9] = (byte)((Data[0xC9] &~16) | (value ?16 : 0)); } - public override int PKRS_Strain { get => Data[0xCA] & 0xF; set => Data[0xCA] = (byte)(value & 0xF); } - public override bool IsEgg { get => Data[0xCB] == 1; set => Data[0xCB] = value ? (byte)1 : (byte)0; } - public override bool AbilityBit { get => Data[0xCC] == 1; set => Data[0xCC] = value ? (byte)1 : (byte)0; } - public override bool Valid { get => Data[0xCD] == 0; set => Data[0xCD] = !value ? (byte)1 : (byte)0; } + public override int PKRS_Strain { get => Data[0xCA] & 0xF; set => Data[0xCA] = (byte)(value & 0xF); } + public override bool IsEgg { get => Data[0xCB] == 1; set => Data[0xCB] = value ? (byte)1 : (byte)0; } + public override bool AbilityBit { get => Data[0xCC] == 1; set => Data[0xCC] = value ? (byte)1 : (byte)0; } + public override bool Valid { get => Data[0xCD] == 0; set => Data[0xCD] = !value ? (byte)1 : (byte)0; } - public override int MarkValue { get => SwapBits(Data[0xCF], 1, 2); set => Data[0xCF] = (byte)SwapBits(value, 1, 2); } - public override int PKRS_Days { get => Math.Max((sbyte)Data[0xD0], (sbyte)0); set => Data[0xD0] = (byte)(value == 0 ? 0xFF : value & 0xF); } + public override int MarkValue { get => SwapBits(Data[0xCF], 1, 2); set => Data[0xCF] = (byte)SwapBits(value, 1, 2); } + public override int PKRS_Days { get => Math.Max((sbyte)Data[0xD0], (sbyte)0); set => Data[0xD0] = (byte)(value == 0 ? 0xFF : value & 0xF); } - public int PartySlot { get => Data[0xD7]; set => Data[0xD7] = (byte)value; } // or not; only really used while in party? - public ushort ShadowID { get => ReadUInt16BigEndian(Data.AsSpan(0xD8)); set => WriteUInt16BigEndian(Data.AsSpan(0xD8), value); } - public int Purification { get => ReadInt32BigEndian(Data.AsSpan(0xDC)); set => WriteInt32BigEndian(Data.AsSpan(0xDC), value); } - public uint EXP_Shadow { get => ReadUInt32BigEndian(Data.AsSpan(0xC0)); set => WriteUInt32BigEndian(Data.AsSpan(0xC0), value); } + public int PartySlot { get => Data[0xD7]; set => Data[0xD7] = (byte)value; } // or not; only really used while in party? + public ushort ShadowID { get => ReadUInt16BigEndian(Data.AsSpan(0xD8)); set => WriteUInt16BigEndian(Data.AsSpan(0xD8), value); } + public int Purification { get => ReadInt32BigEndian(Data.AsSpan(0xDC)); set => WriteInt32BigEndian(Data.AsSpan(0xDC), value); } + public uint EXP_Shadow { get => ReadUInt32BigEndian(Data.AsSpan(0xC0)); set => WriteUInt32BigEndian(Data.AsSpan(0xC0), value); } - public const int Purified = -100; - public bool IsShadow => ShadowID != 0 && Purification != Purified; + public const int Purified = -100; + public bool IsShadow => ShadowID != 0 && Purification != Purified; - protected override byte[] Encrypt() => (byte[])Data.Clone(); + protected override byte[] Encrypt() => (byte[])Data.Clone(); - public PK3 ConvertToPK3() - { - var pk = ConvertTo(); - pk.RefreshChecksum(); - return pk; - } + public PK3 ConvertToPK3() + { + var pk = ConvertTo(); + pk.RefreshChecksum(); + return pk; } } diff --git a/PKHeX.Core/PKM/Enums/AlcremieDecoration.cs b/PKHeX.Core/PKM/Enums/AlcremieDecoration.cs index e5f7bf2b8..58c36f9a6 100644 --- a/PKHeX.Core/PKM/Enums/AlcremieDecoration.cs +++ b/PKHeX.Core/PKM/Enums/AlcremieDecoration.cs @@ -1,16 +1,15 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Secondary Form Argument to indicate which decoration is used for +/// +public enum AlcremieDecoration { - /// - /// Secondary Form Argument to indicate which decoration is used for - /// - public enum AlcremieDecoration - { - Strawberry = 0, - Berry = 1, - Love = 2, - Star = 3, - Clover = 4, - Flower = 5, - Ribbon = 6, - } + Strawberry = 0, + Berry = 1, + Love = 2, + Star = 3, + Clover = 4, + Flower = 5, + Ribbon = 6, } diff --git a/PKHeX.Core/PKM/Enums/GroundTileType.cs b/PKHeX.Core/PKM/Enums/GroundTileType.cs index b5a952941..5d2d5d0b1 100644 --- a/PKHeX.Core/PKM/Enums/GroundTileType.cs +++ b/PKHeX.Core/PKM/Enums/GroundTileType.cs @@ -1,56 +1,55 @@ using static PKHeX.Core.GroundTileType; -namespace PKHeX.Core -{ - /// - /// Ground Tile Type the was encountered from. - /// - /// - /// Used in Generation 4 games, this value is set depending on what type of overworld tile the player is standing on when the is obtained. - /// +namespace PKHeX.Core; + +/// +/// Ground Tile Type the was encountered from. +/// +/// +/// Used in Generation 4 games, this value is set depending on what type of overworld tile the player is standing on when the is obtained. +/// #pragma warning disable RCS1234 // Duplicate enum value. - public enum GroundTileType : byte - { - None = 00, // No animation for the tile - Sand = 01, // Obtainable only via HG/SS - Grass = 02, - Puddle = 03, // No encounters from this tile type - Rock = 04, - Cave = 05, - Snow = 06, // No encounters from this tile type - Water = 07, - Ice = 08, // No encounters from this tile type - Building = 09, - Marsh = 10, - Bridge = 11, // No encounters from this tile type - Max_DP = 12, // Unspecific, catch-all for DP undefined tiles. +public enum GroundTileType : byte +{ + None = 00, // No animation for the tile + Sand = 01, // Obtainable only via HG/SS + Grass = 02, + Puddle = 03, // No encounters from this tile type + Rock = 04, + Cave = 05, + Snow = 06, // No encounters from this tile type + Water = 07, + Ice = 08, // No encounters from this tile type + Building = 09, + Marsh = 10, + Bridge = 11, // No encounters from this tile type + Max_DP = 12, // Unspecific, catch-all for DP undefined tiles. - // added tile types in Pt - // no encounters from these tile types - Elite4_1 = 12, // Elite Four Room #1 - Elite4_2 = 13, // Elite Four Room #2 - Elite4_3 = 14, // Elite Four Room #3 - Elite4_4 = 15, // Elite Four Room #4 - Elite4_M = 16, // Elite Four Champion Room - DistortionSideways = 17, // Distortion World (Not Giratina) - BattleTower = 18, - BattleFactory = 19, - BattleArcade = 20, - BattleCastle = 21, - BattleHall = 22, + // added tile types in Pt + // no encounters from these tile types + Elite4_1 = 12, // Elite Four Room #1 + Elite4_2 = 13, // Elite Four Room #2 + Elite4_3 = 14, // Elite Four Room #3 + Elite4_4 = 15, // Elite Four Room #4 + Elite4_M = 16, // Elite Four Champion Room + DistortionSideways = 17, // Distortion World (Not Giratina) + BattleTower = 18, + BattleFactory = 19, + BattleArcade = 20, + BattleCastle = 21, + BattleHall = 22, - Distortion = 23, - Max_Pt = 24, // Unspecific, catch-all for Pt undefined tiles. - } + Distortion = 23, + Max_Pt = 24, // Unspecific, catch-all for Pt undefined tiles. +} #pragma warning restore RCS1234 // Duplicate enum value. - public static class GroundTileTypeExtensions - { - public static bool IsObtainable(this GroundTileType type) => ((0b_1_10000000_00010110_10110111 >> (int) type) & 1) == 1; +public static class GroundTileTypeExtensions +{ + public static bool IsObtainable(this GroundTileType type) => ((0b_1_10000000_00010110_10110111 >> (int) type) & 1) == 1; - public static readonly byte[] ValidTileTypes = - { - (byte)None, (byte)Sand, (byte)Grass, (byte)Rock, (byte)Cave, (byte)Water, (byte)Building, (byte)Marsh, (byte)Max_DP, (byte)Distortion, (byte)Max_Pt, - }; - } + public static readonly byte[] ValidTileTypes = + { + (byte)None, (byte)Sand, (byte)Grass, (byte)Rock, (byte)Cave, (byte)Water, (byte)Building, (byte)Marsh, (byte)Max_DP, (byte)Distortion, (byte)Max_Pt, + }; } diff --git a/PKHeX.Core/PKM/Enums/PokeSize.cs b/PKHeX.Core/PKM/Enums/PokeSize.cs index 707cc31d5..5c22dd123 100644 --- a/PKHeX.Core/PKM/Enums/PokeSize.cs +++ b/PKHeX.Core/PKM/Enums/PokeSize.cs @@ -1,47 +1,46 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum PokeSize { - public enum PokeSize + XS, + S, + AV, + L, + XL, +} + +public static class PokeSizeUtil +{ + /// + /// Compares the sizing scalar to different thresholds to determine the size rating. + /// + /// Sizing scalar (0-255) + /// 0-4 rating + public static PokeSize GetSizeRating(byte scalar) => scalar switch { - XS, - S, - AV, - L, - XL, - } + < 0x10 => PokeSize.XS, // 1/16 = XS + < 0x30 => PokeSize.S, // 2/16 = S + < 0xD0 => PokeSize.AV, // average (10/16) + < 0xF0 => PokeSize.L, // 2/16 = L + _ => PokeSize.XL, // 1/16 = XL + }; - public static class PokeSizeUtil + public static byte GetRandomScalar(this PokeSize size) => size switch { - /// - /// Compares the sizing scalar to different thresholds to determine the size rating. - /// - /// Sizing scalar (0-255) - /// 0-4 rating - public static PokeSize GetSizeRating(byte scalar) => scalar switch - { - < 0x10 => PokeSize.XS, // 1/16 = XS - < 0x30 => PokeSize.S, // 2/16 = S - < 0xD0 => PokeSize.AV, // average (10/16) - < 0xF0 => PokeSize.L, // 2/16 = L - _ => PokeSize.XL, // 1/16 = XL - }; + PokeSize.XS => (byte)(Util.Rand.Next(0x10)), + PokeSize.S => (byte)(Util.Rand.Next(0x20) + 0x10), + PokeSize.AV => (byte)(Util.Rand.Next(0xA0) + 0x30), + PokeSize.L => (byte)(Util.Rand.Next(0x20) + 0xD0), + PokeSize.XL => (byte)(Util.Rand.Next(0x10) + 0xF0), + _ => GetRandomScalar(), + }; - public static byte GetRandomScalar(this PokeSize size) => size switch - { - PokeSize.XS => (byte)(Util.Rand.Next(0x10)), - PokeSize.S => (byte)(Util.Rand.Next(0x20) + 0x10), - PokeSize.AV => (byte)(Util.Rand.Next(0xA0) + 0x30), - PokeSize.L => (byte)(Util.Rand.Next(0x20) + 0xD0), - PokeSize.XL => (byte)(Util.Rand.Next(0x10) + 0xF0), - _ => GetRandomScalar(), - }; - - /// - /// Gets a random size scalar with a triangular distribution (copying official implementation). - /// - public static byte GetRandomScalar() - { - var rnd = Util.Rand; - return (byte)(rnd.Next(0x81) + rnd.Next(0x80)); - } + /// + /// Gets a random size scalar with a triangular distribution (copying official implementation). + /// + public static byte GetRandomScalar() + { + var rnd = Util.Rand; + return (byte)(rnd.Next(0x81) + rnd.Next(0x80)); } } diff --git a/PKHeX.Core/PKM/G8PKM.cs b/PKHeX.Core/PKM/G8PKM.cs index 0feebf98c..2c80e59ae 100644 --- a/PKHeX.Core/PKM/G8PKM.cs +++ b/PKHeX.Core/PKM/G8PKM.cs @@ -1,758 +1,757 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 8 format. +public abstract class G8PKM : PKM, ISanityChecksum, IMoveReset, + IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8, IRibbonSetAffixed, ITechRecord8, ISociability, + IContestStats, IContestStatsMutable, IHyperTrain, IScaledSize, IGigantamax, IFavorite, IDynamaxLevel, IRibbonIndex, IHandlerLanguage, IFormArgument, IHomeTrack, IBattleVersion, ITrainerMemories { - /// Generation 8 format. - public abstract class G8PKM : PKM, ISanityChecksum, IMoveReset, - IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8, IRibbonSetAffixed, ITechRecord8, ISociability, - IContestStats, IContestStatsMutable, IHyperTrain, IScaledSize, IGigantamax, IFavorite, IDynamaxLevel, IRibbonIndex, IHandlerLanguage, IFormArgument, IHomeTrack, IBattleVersion, ITrainerMemories + protected G8PKM() : base(PokeCrypto.SIZE_8PARTY) { } + protected G8PKM(byte[] data) : base(DecryptParty(data)) { } + + public abstract void ResetMoves(); + + private static byte[] DecryptParty(byte[] data) { - protected G8PKM() : base(PokeCrypto.SIZE_8PARTY) { } - protected G8PKM(byte[] data) : base(DecryptParty(data)) { } + PokeCrypto.DecryptIfEncrypted8(ref data); + Array.Resize(ref data, PokeCrypto.SIZE_8PARTY); + return data; + } - public abstract void ResetMoves(); + private ushort CalculateChecksum() + { + ushort chk = 0; + for (int i = 8; i < PokeCrypto.SIZE_8STORED; i += 2) + chk += ReadUInt16LittleEndian(Data.AsSpan(i)); + return chk; + } - private static byte[] DecryptParty(byte[] data) + // Simple Generated Attributes + public ReadOnlySpan TechRecordPermitFlags => PersonalInfo.TMHM.AsSpan(PersonalInfoSWSH.CountTM); + public ReadOnlySpan TechRecordPermitIndexes => Legal.TMHM_SWSH.AsSpan(PersonalInfoSWSH.CountTM); + public override int CurrentFriendship + { + get => CurrentHandler == 0 ? OT_Friendship : HT_Friendship; + set { if (CurrentHandler == 0) OT_Friendship = value; else HT_Friendship = value; } + } + + public override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY; + public override int SIZE_STORED => PokeCrypto.SIZE_8STORED; + + public sealed override bool ChecksumValid => CalculateChecksum() == Checksum; + public sealed override void RefreshChecksum() => Checksum = CalculateChecksum(); + public sealed override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } } + + // Trash Bytes + public override Span Nickname_Trash => Data.AsSpan(0x58, 26); + public override Span HT_Trash => Data.AsSpan(0xA8, 26); + public override Span OT_Trash => Data.AsSpan(0xF8, 26); + + // Maximums + public override int MaxIV => 31; + public override int MaxEV => 252; + public override int OTLength => 12; + public override int NickLength => 12; + + public override int PSV => (int)(((PID >> 16) ^ (PID & 0xFFFF)) >> 4); + public override int TSV => (TID ^ SID) >> 4; + public override bool IsUntraded => Data[0xA8] == 0 && Data[0xA8 + 1] == 0 && Format == Generation; // immediately terminated HT_Name data (\0) + + // Complex Generated Attributes + public override int Characteristic + { + get { - PokeCrypto.DecryptIfEncrypted8(ref data); - Array.Resize(ref data, PokeCrypto.SIZE_8PARTY); - return data; - } - - private ushort CalculateChecksum() - { - ushort chk = 0; - for (int i = 8; i < PokeCrypto.SIZE_8STORED; i += 2) - chk += ReadUInt16LittleEndian(Data.AsSpan(i)); - return chk; - } - - // Simple Generated Attributes - public ReadOnlySpan TechRecordPermitFlags => PersonalInfo.TMHM.AsSpan(PersonalInfoSWSH.CountTM); - public ReadOnlySpan TechRecordPermitIndexes => Legal.TMHM_SWSH.AsSpan(PersonalInfoSWSH.CountTM); - public override int CurrentFriendship - { - get => CurrentHandler == 0 ? OT_Friendship : HT_Friendship; - set { if (CurrentHandler == 0) OT_Friendship = value; else HT_Friendship = value; } - } - - public override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY; - public override int SIZE_STORED => PokeCrypto.SIZE_8STORED; - - public sealed override bool ChecksumValid => CalculateChecksum() == Checksum; - public sealed override void RefreshChecksum() => Checksum = CalculateChecksum(); - public sealed override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } } - - // Trash Bytes - public override Span Nickname_Trash => Data.AsSpan(0x58, 26); - public override Span HT_Trash => Data.AsSpan(0xA8, 26); - public override Span OT_Trash => Data.AsSpan(0xF8, 26); - - // Maximums - public override int MaxIV => 31; - public override int MaxEV => 252; - public override int OTLength => 12; - public override int NickLength => 12; - - public override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 4); - public override int TSV => (TID ^ SID) >> 4; - public override bool IsUntraded => Data[0xA8] == 0 && Data[0xA8 + 1] == 0 && Format == Generation; // immediately terminated HT_Name data (\0) - - // Complex Generated Attributes - public override int Characteristic - { - get + int pm6 = (int)(EncryptionConstant % 6); + int maxIV = MaximumIV; + int pm6stat = 0; + for (int i = 0; i < 6; i++) { - int pm6 = (int)(EncryptionConstant % 6); - int maxIV = MaximumIV; - int pm6stat = 0; - for (int i = 0; i < 6; i++) - { - pm6stat = (pm6 + i) % 6; - if (GetIV(pm6stat) == maxIV) - break; - } - return (pm6stat * 5) + (maxIV % 5); + pm6stat = (pm6 + i) % 6; + if (GetIV(pm6stat) == maxIV) + break; } - } - - // Methods - protected override byte[] Encrypt() - { - RefreshChecksum(); - return PokeCrypto.EncryptArray8(Data); - } - - public void FixRelearn() - { - while (true) - { - if (RelearnMove4 != 0 && RelearnMove3 == 0) - { - RelearnMove3 = RelearnMove4; - RelearnMove4 = 0; - } - if (RelearnMove3 != 0 && RelearnMove2 == 0) - { - RelearnMove2 = RelearnMove3; - RelearnMove3 = 0; - continue; - } - if (RelearnMove2 != 0 && RelearnMove1 == 0) - { - RelearnMove1 = RelearnMove2; - RelearnMove2 = 0; - continue; - } - break; - } - } - - public override uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } - public ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); } - public ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); } - - // Structure - #region Block A - public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); } - public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } - public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); } - public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); } - public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); } - public override int Ability { get => ReadUInt16LittleEndian(Data.AsSpan(0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14), (ushort)value); } - public override int AbilityNumber { get => Data[0x16] & 7; set => Data[0x16] = (byte)((Data[0x16] & ~7) | (value & 7)); } - public bool Favorite { get => (Data[0x16] & 8) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~8) | ((value ? 1 : 0) << 3)); } // unused, was in LGPE but not in SWSH - public bool CanGigantamax { get => (Data[0x16] & 16) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~16) | (value ? 16 : 0)); } - // 0x17 alignment unused - public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); } - // 0x1A alignment unused - // 0x1B alignment unused - public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x1C), value); } - public override int Nature { get => Data[0x20]; set => Data[0x20] = (byte)value; } - public override int StatNature { get => Data[0x21]; set => Data[0x21] = (byte)value; } - public override bool FatefulEncounter { get => (Data[0x22] & 1) == 1; set => Data[0x22] = (byte)((Data[0x22] & ~0x01) | (value ? 1 : 0)); } - public bool Flag2 { get => (Data[0x22] & 2) == 2; set => Data[0x22] = (byte)((Data[0x22] & ~0x02) | (value ? 2 : 0)); } - public override int Gender { get => (Data[0x22] >> 2) & 0x3; set => Data[0x22] = (byte)((Data[0x22] & 0xF3) | (value << 2)); } - // 0x23 alignment unused - - public override int Form { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); } - public override int EV_HP { get => Data[0x26]; set => Data[0x26] = (byte)value; } - public override int EV_ATK { get => Data[0x27]; set => Data[0x27] = (byte)value; } - public override int EV_DEF { get => Data[0x28]; set => Data[0x28] = (byte)value; } - public override int EV_SPE { get => Data[0x29]; set => Data[0x29] = (byte)value; } - public override int EV_SPA { get => Data[0x2A]; set => Data[0x2A] = (byte)value; } - public override int EV_SPD { get => Data[0x2B]; set => Data[0x2B] = (byte)value; } - public byte CNT_Cool { get => Data[0x2C]; set => Data[0x2C] = value; } - public byte CNT_Beauty { get => Data[0x2D]; set => Data[0x2D] = value; } - public byte CNT_Cute { get => Data[0x2E]; set => Data[0x2E] = value; } - public byte CNT_Smart { get => Data[0x2F]; set => Data[0x2F] = value; } - public byte CNT_Tough { get => Data[0x30]; set => Data[0x30] = value; } - public byte CNT_Sheen { get => Data[0x31]; set => Data[0x31] = value; } - private byte PKRS { get => Data[0x32]; set => Data[0x32] = value; } - public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); } - // 0x33 unused padding - - // ribbon u32 - public bool RibbonChampionKalos { get => FlagUtil.GetFlag(Data, 0x34, 0); set => FlagUtil.SetFlag(Data, 0x34, 0, value); } - public bool RibbonChampionG3 { get => FlagUtil.GetFlag(Data, 0x34, 1); set => FlagUtil.SetFlag(Data, 0x34, 1, value); } - public bool RibbonChampionSinnoh { get => FlagUtil.GetFlag(Data, 0x34, 2); set => FlagUtil.SetFlag(Data, 0x34, 2, value); } - public bool RibbonBestFriends { get => FlagUtil.GetFlag(Data, 0x34, 3); set => FlagUtil.SetFlag(Data, 0x34, 3, value); } - public bool RibbonTraining { get => FlagUtil.GetFlag(Data, 0x34, 4); set => FlagUtil.SetFlag(Data, 0x34, 4, value); } - public bool RibbonBattlerSkillful { get => FlagUtil.GetFlag(Data, 0x34, 5); set => FlagUtil.SetFlag(Data, 0x34, 5, value); } - public bool RibbonBattlerExpert { get => FlagUtil.GetFlag(Data, 0x34, 6); set => FlagUtil.SetFlag(Data, 0x34, 6, value); } - public bool RibbonEffort { get => FlagUtil.GetFlag(Data, 0x34, 7); set => FlagUtil.SetFlag(Data, 0x34, 7, value); } - - public bool RibbonAlert { get => FlagUtil.GetFlag(Data, 0x35, 0); set => FlagUtil.SetFlag(Data, 0x35, 0, value); } - public bool RibbonShock { get => FlagUtil.GetFlag(Data, 0x35, 1); set => FlagUtil.SetFlag(Data, 0x35, 1, value); } - public bool RibbonDowncast { get => FlagUtil.GetFlag(Data, 0x35, 2); set => FlagUtil.SetFlag(Data, 0x35, 2, value); } - public bool RibbonCareless { get => FlagUtil.GetFlag(Data, 0x35, 3); set => FlagUtil.SetFlag(Data, 0x35, 3, value); } - public bool RibbonRelax { get => FlagUtil.GetFlag(Data, 0x35, 4); set => FlagUtil.SetFlag(Data, 0x35, 4, value); } - public bool RibbonSnooze { get => FlagUtil.GetFlag(Data, 0x35, 5); set => FlagUtil.SetFlag(Data, 0x35, 5, value); } - public bool RibbonSmile { get => FlagUtil.GetFlag(Data, 0x35, 6); set => FlagUtil.SetFlag(Data, 0x35, 6, value); } - public bool RibbonGorgeous { get => FlagUtil.GetFlag(Data, 0x35, 7); set => FlagUtil.SetFlag(Data, 0x35, 7, value); } - - public bool RibbonRoyal { get => FlagUtil.GetFlag(Data, 0x36, 0); set => FlagUtil.SetFlag(Data, 0x36, 0, value); } - public bool RibbonGorgeousRoyal { get => FlagUtil.GetFlag(Data, 0x36, 1); set => FlagUtil.SetFlag(Data, 0x36, 1, value); } - public bool RibbonArtist { get => FlagUtil.GetFlag(Data, 0x36, 2); set => FlagUtil.SetFlag(Data, 0x36, 2, value); } - public bool RibbonFootprint { get => FlagUtil.GetFlag(Data, 0x36, 3); set => FlagUtil.SetFlag(Data, 0x36, 3, value); } - public bool RibbonRecord { get => FlagUtil.GetFlag(Data, 0x36, 4); set => FlagUtil.SetFlag(Data, 0x36, 4, value); } - public bool RibbonLegend { get => FlagUtil.GetFlag(Data, 0x36, 5); set => FlagUtil.SetFlag(Data, 0x36, 5, value); } - public bool RibbonCountry { get => FlagUtil.GetFlag(Data, 0x36, 6); set => FlagUtil.SetFlag(Data, 0x36, 6, value); } - public bool RibbonNational { get => FlagUtil.GetFlag(Data, 0x36, 7); set => FlagUtil.SetFlag(Data, 0x36, 7, value); } - - public bool RibbonEarth { get => FlagUtil.GetFlag(Data, 0x37, 0); set => FlagUtil.SetFlag(Data, 0x37, 0, value); } - public bool RibbonWorld { get => FlagUtil.GetFlag(Data, 0x37, 1); set => FlagUtil.SetFlag(Data, 0x37, 1, value); } - public bool RibbonClassic { get => FlagUtil.GetFlag(Data, 0x37, 2); set => FlagUtil.SetFlag(Data, 0x37, 2, value); } - public bool RibbonPremier { get => FlagUtil.GetFlag(Data, 0x37, 3); set => FlagUtil.SetFlag(Data, 0x37, 3, value); } - public bool RibbonEvent { get => FlagUtil.GetFlag(Data, 0x37, 4); set => FlagUtil.SetFlag(Data, 0x37, 4, value); } - public bool RibbonBirthday { get => FlagUtil.GetFlag(Data, 0x37, 5); set => FlagUtil.SetFlag(Data, 0x37, 5, value); } - public bool RibbonSpecial { get => FlagUtil.GetFlag(Data, 0x37, 6); set => FlagUtil.SetFlag(Data, 0x37, 6, value); } - public bool RibbonSouvenir { get => FlagUtil.GetFlag(Data, 0x37, 7); set => FlagUtil.SetFlag(Data, 0x37, 7, value); } - - // ribbon u32 - public bool RibbonWishing { get => FlagUtil.GetFlag(Data, 0x38, 0); set => FlagUtil.SetFlag(Data, 0x38, 0, value); } - public bool RibbonChampionBattle { get => FlagUtil.GetFlag(Data, 0x38, 1); set => FlagUtil.SetFlag(Data, 0x38, 1, value); } - public bool RibbonChampionRegional { get => FlagUtil.GetFlag(Data, 0x38, 2); set => FlagUtil.SetFlag(Data, 0x38, 2, value); } - public bool RibbonChampionNational { get => FlagUtil.GetFlag(Data, 0x38, 3); set => FlagUtil.SetFlag(Data, 0x38, 3, value); } - public bool RibbonChampionWorld { get => FlagUtil.GetFlag(Data, 0x38, 4); set => FlagUtil.SetFlag(Data, 0x38, 4, value); } - public bool HasContestMemoryRibbon { get => FlagUtil.GetFlag(Data, 0x38, 5); set => FlagUtil.SetFlag(Data, 0x38, 5, value); } - public bool HasBattleMemoryRibbon { get => FlagUtil.GetFlag(Data, 0x38, 6); set => FlagUtil.SetFlag(Data, 0x38, 6, value); } - public bool RibbonChampionG6Hoenn { get => FlagUtil.GetFlag(Data, 0x38, 7); set => FlagUtil.SetFlag(Data, 0x38, 7, value); } - - public bool RibbonContestStar { get => FlagUtil.GetFlag(Data, 0x39, 0); set => FlagUtil.SetFlag(Data, 0x39, 0, value); } - public bool RibbonMasterCoolness { get => FlagUtil.GetFlag(Data, 0x39, 1); set => FlagUtil.SetFlag(Data, 0x39, 1, value); } - public bool RibbonMasterBeauty { get => FlagUtil.GetFlag(Data, 0x39, 2); set => FlagUtil.SetFlag(Data, 0x39, 2, value); } - public bool RibbonMasterCuteness { get => FlagUtil.GetFlag(Data, 0x39, 3); set => FlagUtil.SetFlag(Data, 0x39, 3, value); } - public bool RibbonMasterCleverness { get => FlagUtil.GetFlag(Data, 0x39, 4); set => FlagUtil.SetFlag(Data, 0x39, 4, value); } - public bool RibbonMasterToughness { get => FlagUtil.GetFlag(Data, 0x39, 5); set => FlagUtil.SetFlag(Data, 0x39, 5, value); } - public bool RibbonChampionAlola { get => FlagUtil.GetFlag(Data, 0x39, 6); set => FlagUtil.SetFlag(Data, 0x39, 6, value); } - public bool RibbonBattleRoyale { get => FlagUtil.GetFlag(Data, 0x39, 7); set => FlagUtil.SetFlag(Data, 0x39, 7, value); } - - public bool RibbonBattleTreeGreat { get => FlagUtil.GetFlag(Data, 0x3A, 0); set => FlagUtil.SetFlag(Data, 0x3A, 0, value); } - public bool RibbonBattleTreeMaster { get => FlagUtil.GetFlag(Data, 0x3A, 1); set => FlagUtil.SetFlag(Data, 0x3A, 1, value); } - public bool RibbonChampionGalar { get => FlagUtil.GetFlag(Data, 0x3A, 2); set => FlagUtil.SetFlag(Data, 0x3A, 2, value); } - public bool RibbonTowerMaster { get => FlagUtil.GetFlag(Data, 0x3A, 3); set => FlagUtil.SetFlag(Data, 0x3A, 3, value); } - public bool RibbonMasterRank { get => FlagUtil.GetFlag(Data, 0x3A, 4); set => FlagUtil.SetFlag(Data, 0x3A, 4, value); } - public bool RibbonMarkLunchtime { get => FlagUtil.GetFlag(Data, 0x3A, 5); set => FlagUtil.SetFlag(Data, 0x3A, 5, value); } - public bool RibbonMarkSleepyTime { get => FlagUtil.GetFlag(Data, 0x3A, 6); set => FlagUtil.SetFlag(Data, 0x3A, 6, value); } - public bool RibbonMarkDusk { get => FlagUtil.GetFlag(Data, 0x3A, 7); set => FlagUtil.SetFlag(Data, 0x3A, 7, value); } - - public bool RibbonMarkDawn { get => FlagUtil.GetFlag(Data, 0x3B, 0); set => FlagUtil.SetFlag(Data, 0x3B, 0, value); } - public bool RibbonMarkCloudy { get => FlagUtil.GetFlag(Data, 0x3B, 1); set => FlagUtil.SetFlag(Data, 0x3B, 1, value); } - public bool RibbonMarkRainy { get => FlagUtil.GetFlag(Data, 0x3B, 2); set => FlagUtil.SetFlag(Data, 0x3B, 2, value); } - public bool RibbonMarkStormy { get => FlagUtil.GetFlag(Data, 0x3B, 3); set => FlagUtil.SetFlag(Data, 0x3B, 3, value); } - public bool RibbonMarkSnowy { get => FlagUtil.GetFlag(Data, 0x3B, 4); set => FlagUtil.SetFlag(Data, 0x3B, 4, value); } - public bool RibbonMarkBlizzard { get => FlagUtil.GetFlag(Data, 0x3B, 5); set => FlagUtil.SetFlag(Data, 0x3B, 5, value); } - public bool RibbonMarkDry { get => FlagUtil.GetFlag(Data, 0x3B, 6); set => FlagUtil.SetFlag(Data, 0x3B, 6, value); } - public bool RibbonMarkSandstorm { get => FlagUtil.GetFlag(Data, 0x3B, 7); set => FlagUtil.SetFlag(Data, 0x3B, 7, value); } - public int RibbonCountMemoryContest { get => Data[0x3C]; set => HasContestMemoryRibbon = (Data[0x3C] = (byte)value) != 0; } - public int RibbonCountMemoryBattle { get => Data[0x3D]; set => HasBattleMemoryRibbon = (Data[0x3D] = (byte)value) != 0; } - // 0x3E padding - // 0x3F padding - - // 0x40 Ribbon 1 - public bool RibbonMarkMisty { get => FlagUtil.GetFlag(Data, 0x40, 0); set => FlagUtil.SetFlag(Data, 0x40, 0, value); } - public bool RibbonMarkDestiny { get => FlagUtil.GetFlag(Data, 0x40, 1); set => FlagUtil.SetFlag(Data, 0x40, 1, value); } - public bool RibbonMarkFishing { get => FlagUtil.GetFlag(Data, 0x40, 2); set => FlagUtil.SetFlag(Data, 0x40, 2, value); } - public bool RibbonMarkCurry { get => FlagUtil.GetFlag(Data, 0x40, 3); set => FlagUtil.SetFlag(Data, 0x40, 3, value); } - public bool RibbonMarkUncommon { get => FlagUtil.GetFlag(Data, 0x40, 4); set => FlagUtil.SetFlag(Data, 0x40, 4, value); } - public bool RibbonMarkRare { get => FlagUtil.GetFlag(Data, 0x40, 5); set => FlagUtil.SetFlag(Data, 0x40, 5, value); } - public bool RibbonMarkRowdy { get => FlagUtil.GetFlag(Data, 0x40, 6); set => FlagUtil.SetFlag(Data, 0x40, 6, value); } - public bool RibbonMarkAbsentMinded { get => FlagUtil.GetFlag(Data, 0x40, 7); set => FlagUtil.SetFlag(Data, 0x40, 7, value); } - - public bool RibbonMarkJittery { get => FlagUtil.GetFlag(Data, 0x41, 0); set => FlagUtil.SetFlag(Data, 0x41, 0, value); } - public bool RibbonMarkExcited { get => FlagUtil.GetFlag(Data, 0x41, 1); set => FlagUtil.SetFlag(Data, 0x41, 1, value); } - public bool RibbonMarkCharismatic { get => FlagUtil.GetFlag(Data, 0x41, 2); set => FlagUtil.SetFlag(Data, 0x41, 2, value); } - public bool RibbonMarkCalmness { get => FlagUtil.GetFlag(Data, 0x41, 3); set => FlagUtil.SetFlag(Data, 0x41, 3, value); } - public bool RibbonMarkIntense { get => FlagUtil.GetFlag(Data, 0x41, 4); set => FlagUtil.SetFlag(Data, 0x41, 4, value); } - public bool RibbonMarkZonedOut { get => FlagUtil.GetFlag(Data, 0x41, 5); set => FlagUtil.SetFlag(Data, 0x41, 5, value); } - public bool RibbonMarkJoyful { get => FlagUtil.GetFlag(Data, 0x41, 6); set => FlagUtil.SetFlag(Data, 0x41, 6, value); } - public bool RibbonMarkAngry { get => FlagUtil.GetFlag(Data, 0x41, 7); set => FlagUtil.SetFlag(Data, 0x41, 7, value); } - - public bool RibbonMarkSmiley { get => FlagUtil.GetFlag(Data, 0x42, 0); set => FlagUtil.SetFlag(Data, 0x42, 0, value); } - public bool RibbonMarkTeary { get => FlagUtil.GetFlag(Data, 0x42, 1); set => FlagUtil.SetFlag(Data, 0x42, 1, value); } - public bool RibbonMarkUpbeat { get => FlagUtil.GetFlag(Data, 0x42, 2); set => FlagUtil.SetFlag(Data, 0x42, 2, value); } - public bool RibbonMarkPeeved { get => FlagUtil.GetFlag(Data, 0x42, 3); set => FlagUtil.SetFlag(Data, 0x42, 3, value); } - public bool RibbonMarkIntellectual { get => FlagUtil.GetFlag(Data, 0x42, 4); set => FlagUtil.SetFlag(Data, 0x42, 4, value); } - public bool RibbonMarkFerocious { get => FlagUtil.GetFlag(Data, 0x42, 5); set => FlagUtil.SetFlag(Data, 0x42, 5, value); } - public bool RibbonMarkCrafty { get => FlagUtil.GetFlag(Data, 0x42, 6); set => FlagUtil.SetFlag(Data, 0x42, 6, value); } - public bool RibbonMarkScowling { get => FlagUtil.GetFlag(Data, 0x42, 7); set => FlagUtil.SetFlag(Data, 0x42, 7, value); } - - public bool RibbonMarkKindly { get => FlagUtil.GetFlag(Data, 0x43, 0); set => FlagUtil.SetFlag(Data, 0x43, 0, value); } - public bool RibbonMarkFlustered { get => FlagUtil.GetFlag(Data, 0x43, 1); set => FlagUtil.SetFlag(Data, 0x43, 1, value); } - public bool RibbonMarkPumpedUp { get => FlagUtil.GetFlag(Data, 0x43, 2); set => FlagUtil.SetFlag(Data, 0x43, 2, value); } - public bool RibbonMarkZeroEnergy { get => FlagUtil.GetFlag(Data, 0x43, 3); set => FlagUtil.SetFlag(Data, 0x43, 3, value); } - public bool RibbonMarkPrideful { get => FlagUtil.GetFlag(Data, 0x43, 4); set => FlagUtil.SetFlag(Data, 0x43, 4, value); } - public bool RibbonMarkUnsure { get => FlagUtil.GetFlag(Data, 0x43, 5); set => FlagUtil.SetFlag(Data, 0x43, 5, value); } - public bool RibbonMarkHumble { get => FlagUtil.GetFlag(Data, 0x43, 6); set => FlagUtil.SetFlag(Data, 0x43, 6, value); } - public bool RibbonMarkThorny { get => FlagUtil.GetFlag(Data, 0x43, 7); set => FlagUtil.SetFlag(Data, 0x43, 7, value); } - // 0x44 Ribbon 2 - - public bool RibbonMarkVigor { get => FlagUtil.GetFlag(Data, 0x44, 0); set => FlagUtil.SetFlag(Data, 0x44, 0, value); } - public bool RibbonMarkSlump { get => FlagUtil.GetFlag(Data, 0x44, 1); set => FlagUtil.SetFlag(Data, 0x44, 1, value); } - public bool RibbonPioneer { get => FlagUtil.GetFlag(Data, 0x44, 2); set => FlagUtil.SetFlag(Data, 0x44, 2, value); } - public bool RibbonTwinklingStar { get => FlagUtil.GetFlag(Data, 0x44, 3); set => FlagUtil.SetFlag(Data, 0x44, 3, value); } - public bool RIB44_4 { get => FlagUtil.GetFlag(Data, 0x44, 4); set => FlagUtil.SetFlag(Data, 0x44, 4, value); } - public bool RIB44_5 { get => FlagUtil.GetFlag(Data, 0x44, 5); set => FlagUtil.SetFlag(Data, 0x44, 5, value); } - public bool RIB44_6 { get => FlagUtil.GetFlag(Data, 0x44, 6); set => FlagUtil.SetFlag(Data, 0x44, 6, value); } - public bool RIB44_7 { get => FlagUtil.GetFlag(Data, 0x44, 7); set => FlagUtil.SetFlag(Data, 0x44, 7, value); } - - public bool RIB45_0 { get => FlagUtil.GetFlag(Data, 0x45, 0); set => FlagUtil.SetFlag(Data, 0x45, 0, value); } - public bool RIB45_1 { get => FlagUtil.GetFlag(Data, 0x45, 1); set => FlagUtil.SetFlag(Data, 0x45, 1, value); } - public bool RIB45_2 { get => FlagUtil.GetFlag(Data, 0x45, 2); set => FlagUtil.SetFlag(Data, 0x45, 2, value); } - public bool RIB45_3 { get => FlagUtil.GetFlag(Data, 0x45, 3); set => FlagUtil.SetFlag(Data, 0x45, 3, value); } - public bool RIB45_4 { get => FlagUtil.GetFlag(Data, 0x45, 4); set => FlagUtil.SetFlag(Data, 0x45, 4, value); } - public bool RIB45_5 { get => FlagUtil.GetFlag(Data, 0x45, 5); set => FlagUtil.SetFlag(Data, 0x45, 5, value); } - public bool RIB45_6 { get => FlagUtil.GetFlag(Data, 0x45, 6); set => FlagUtil.SetFlag(Data, 0x45, 6, value); } - public bool RIB45_7 { get => FlagUtil.GetFlag(Data, 0x45, 7); set => FlagUtil.SetFlag(Data, 0x45, 7, value); } - - public bool RIB46_0 { get => FlagUtil.GetFlag(Data, 0x41, 0); set => FlagUtil.SetFlag(Data, 0x41, 0, value); } - public bool RIB46_1 { get => FlagUtil.GetFlag(Data, 0x46, 1); set => FlagUtil.SetFlag(Data, 0x46, 1, value); } - public bool RIB46_2 { get => FlagUtil.GetFlag(Data, 0x46, 2); set => FlagUtil.SetFlag(Data, 0x46, 2, value); } - public bool RIB46_3 { get => FlagUtil.GetFlag(Data, 0x46, 3); set => FlagUtil.SetFlag(Data, 0x46, 3, value); } - public bool RIB46_4 { get => FlagUtil.GetFlag(Data, 0x46, 4); set => FlagUtil.SetFlag(Data, 0x46, 4, value); } - public bool RIB46_5 { get => FlagUtil.GetFlag(Data, 0x46, 5); set => FlagUtil.SetFlag(Data, 0x46, 5, value); } - public bool RIB46_6 { get => FlagUtil.GetFlag(Data, 0x46, 6); set => FlagUtil.SetFlag(Data, 0x46, 6, value); } - public bool RIB46_7 { get => FlagUtil.GetFlag(Data, 0x46, 7); set => FlagUtil.SetFlag(Data, 0x46, 7, value); } - - public bool RIB47_0 { get => FlagUtil.GetFlag(Data, 0x47, 0); set => FlagUtil.SetFlag(Data, 0x47, 0, value); } - public bool RIB47_1 { get => FlagUtil.GetFlag(Data, 0x47, 1); set => FlagUtil.SetFlag(Data, 0x47, 1, value); } - public bool RIB47_2 { get => FlagUtil.GetFlag(Data, 0x47, 2); set => FlagUtil.SetFlag(Data, 0x47, 2, value); } - public bool RIB47_3 { get => FlagUtil.GetFlag(Data, 0x47, 3); set => FlagUtil.SetFlag(Data, 0x47, 3, value); } - public bool RIB47_4 { get => FlagUtil.GetFlag(Data, 0x47, 4); set => FlagUtil.SetFlag(Data, 0x47, 4, value); } - public bool RIB47_5 { get => FlagUtil.GetFlag(Data, 0x47, 5); set => FlagUtil.SetFlag(Data, 0x47, 5, value); } - public bool RIB47_6 { get => FlagUtil.GetFlag(Data, 0x47, 6); set => FlagUtil.SetFlag(Data, 0x47, 6, value); } - public bool RIB47_7 { get => FlagUtil.GetFlag(Data, 0x47, 7); set => FlagUtil.SetFlag(Data, 0x47, 7, value); } - - public bool HasMark() - { - var d = Data.AsSpan(); - if ((ReadUInt16LittleEndian(d[0x3A..]) & 0xFFE0) != 0) - return true; - if (ReadUInt32LittleEndian(d[0x40..]) != 0) - return true; - return (d[0x44] & 3) != 0; - } - - public uint Sociability { get => ReadUInt32LittleEndian(Data.AsSpan(0x48)); set => WriteUInt32LittleEndian(Data.AsSpan(0x48), value); } - - // 0x4C-0x4F unused - - public byte HeightScalar { get => Data[0x50]; set => Data[0x50] = value; } - public byte WeightScalar { get => Data[0x51]; set => Data[0x51] = value; } - - // 0x52-0x57 unused - - #endregion - #region Block B - public override string Nickname - { - get => StringConverter8.GetString(Nickname_Trash); - set => StringConverter8.SetString(Nickname_Trash, value.AsSpan(), 12, StringConverterOption.None); - } - - // 2 bytes for \0, automatically handled above - - public override int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x72)); set => WriteUInt16LittleEndian(Data.AsSpan(0x72), (ushort)value); } - public override int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x74)); set => WriteUInt16LittleEndian(Data.AsSpan(0x74), (ushort)value); } - public override int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x76)); set => WriteUInt16LittleEndian(Data.AsSpan(0x76), (ushort)value); } - public override int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x78)); set => WriteUInt16LittleEndian(Data.AsSpan(0x78), (ushort)value); } - - public override int Move1_PP { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } - public override int Move2_PP { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } - public override int Move3_PP { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } - public override int Move4_PP { get => Data[0x7D]; set => Data[0x7D] = (byte)value; } - public override int Move1_PPUps { get => Data[0x7E]; set => Data[0x7E] = (byte)value; } - public override int Move2_PPUps { get => Data[0x7F]; set => Data[0x7F] = (byte)value; } - public override int Move3_PPUps { get => Data[0x80]; set => Data[0x80] = (byte)value; } - public override int Move4_PPUps { get => Data[0x81]; set => Data[0x81] = (byte)value; } - - public override int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x82)); set => WriteUInt16LittleEndian(Data.AsSpan(0x82), (ushort)value); } - public override int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x84)); set => WriteUInt16LittleEndian(Data.AsSpan(0x84), (ushort)value); } - public override int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x86)); set => WriteUInt16LittleEndian(Data.AsSpan(0x86), (ushort)value); } - public override int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x88)); set => WriteUInt16LittleEndian(Data.AsSpan(0x88), (ushort)value); } - - public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0x8A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8A), (ushort)value); } - - private uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x8C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x8C), value); } - public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } - public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } - public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } - public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } - public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } - public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } - public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } - public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } - - public byte DynamaxLevel { get => Data[0x90]; set => Data[0x90] = value; } - - // 0x91-0x93 unused - - public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0x94)); set => WriteInt32LittleEndian(Data.AsSpan(0x94), value); } - public int Palma { get => ReadInt32LittleEndian(Data.AsSpan(0x98)); set => WriteInt32LittleEndian(Data.AsSpan(0x98), value); } - - // 0x9C-0xA7 unused - - #endregion - #region Block C - public override string HT_Name - { - get => StringConverter8.GetString(HT_Trash); - set => StringConverter8.SetString(HT_Trash, value.AsSpan(), 12, StringConverterOption.None); - } - - public override int HT_Gender { get => Data[0xC2]; set => Data[0xC2] = (byte)value; } - public byte HT_Language { get => Data[0xC3]; set => Data[0xC3] = value; } - public override int CurrentHandler { get => Data[0xC4]; set => Data[0xC4] = (byte)value; } - // 0xC5 unused (alignment) - public int HT_TrainerID { get => ReadUInt16LittleEndian(Data.AsSpan(0xC6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xC6), (ushort)value); } // unused? - public override int HT_Friendship { get => Data[0xC8]; set => Data[0xC8] = (byte)value; } - public byte HT_Intensity { get => Data[0xC9]; set => Data[0xC9] = value; } - public byte HT_Memory { get => Data[0xCA]; set => Data[0xCA] = value; } - public byte HT_Feeling { get => Data[0xCB]; set => Data[0xCB] = value; } - public ushort HT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xCC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xCC), value); } - - public bool GetPokeJobFlag(int index) - { - if ((uint)index > 112) // 14 bytes, 8 bits - throw new ArgumentOutOfRangeException(nameof(index)); - int ofs = index >> 3; - return FlagUtil.GetFlag(Data, 0xCE + ofs, index & 7); - } - - public void SetPokeJobFlag(int index, bool value) - { - if ((uint)index > 112) // 14 bytes, 8 bits - throw new ArgumentOutOfRangeException(nameof(index)); - int ofs = index >> 3; - FlagUtil.SetFlag(Data, 0xCE + ofs, index & 7, value); - } - - public bool GetPokeJobFlagAny() => Array.FindIndex(Data, 0xCE, 14, z => z != 0) >= 0; - - public void ClearPokeJobFlags() => Data.AsSpan(0xCE, 14).Clear(); - - public override byte Fullness { get => Data[0xDC]; set => Data[0xDC] = value; } - public override byte Enjoyment { get => Data[0xDD]; set => Data[0xDD] = value; } - public override int Version { get => Data[0xDE]; set => Data[0xDE] = (byte)value; } - public byte BattleVersion { get => Data[0xDF]; set => Data[0xDF] = value; } - // public override int Region { get => Data[0xE0]; set => Data[0xE0] = (byte)value; } - // public override int ConsoleRegion { get => Data[0xE1]; set => Data[0xE1] = (byte)value; } - public override int Language { get => Data[0xE2]; set => Data[0xE2] = (byte)value; } - // 0xE3 alignment - public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(0xE4)); set => WriteUInt32LittleEndian(Data.AsSpan(0xE4), value); } - public byte FormArgumentRemain { get => (byte)FormArgument; set => FormArgument = (FormArgument & ~0xFFu) | value; } - public byte FormArgumentElapsed { get => (byte)(FormArgument >> 8); set => FormArgument = (FormArgument & ~0xFF00u) | (uint)(value << 8); } - public byte FormArgumentMaximum { get => (byte)(FormArgument >> 16); set => FormArgument = (FormArgument & ~0xFF0000u) | (uint)(value << 16); } - public sbyte AffixedRibbon { get => (sbyte)Data[0xE8]; set => Data[0xE8] = (byte)value; } // selected ribbon - // remainder unused - - #endregion - #region Block D - public override string OT_Name - { - get => StringConverter8.GetString(OT_Trash); - set => StringConverter8.SetString(OT_Trash, value.AsSpan(), 12, StringConverterOption.None); - } - - public override int OT_Friendship { get => Data[0x112]; set => Data[0x112] = (byte)value; } - public byte OT_Intensity { get => Data[0x113]; set => Data[0x113] = value; } - public byte OT_Memory { get => Data[0x114]; set => Data[0x114] = value; } - // 0x115 unused align - public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0x116)); set => WriteUInt16LittleEndian(Data.AsSpan(0x116), value); } - public byte OT_Feeling { get => Data[0x118]; set => Data[0x118] = value; } - public override int Egg_Year { get => Data[0x119]; set => Data[0x119] = (byte)value; } - public override int Egg_Month { get => Data[0x11A]; set => Data[0x11A] = (byte)value; } - public override int Egg_Day { get => Data[0x11B]; set => Data[0x11B] = (byte)value; } - public override int Met_Year { get => Data[0x11C]; set => Data[0x11C] = (byte)value; } - public override int Met_Month { get => Data[0x11D]; set => Data[0x11D] = (byte)value; } - public override int Met_Day { get => Data[0x11E]; set => Data[0x11E] = (byte)value; } - // 0x11F unused align - public override int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0x120)); set => WriteUInt16LittleEndian(Data.AsSpan(0x120), (ushort)value); } - public override int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0x122)); set => WriteUInt16LittleEndian(Data.AsSpan(0x122), (ushort)value); } - public override int Ball { get => Data[0x124]; set => Data[0x124] = (byte)value; } - public override int Met_Level { get => Data[0x125] & ~0x80; set => Data[0x125] = (byte)((Data[0x125] & 0x80) | value); } - public override int OT_Gender { get => Data[0x125] >> 7; set => Data[0x125] = (byte)((Data[0x125] & ~0x80) | (value << 7)); } - public byte HyperTrainFlags { get => Data[0x126]; set => Data[0x126] = value; } - public bool HT_HP { get => ((HyperTrainFlags >> 0) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 0)) | ((value ? 1 : 0) << 0)); } - public bool HT_ATK { get => ((HyperTrainFlags >> 1) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 1)) | ((value ? 1 : 0) << 1)); } - public bool HT_DEF { get => ((HyperTrainFlags >> 2) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 2)) | ((value ? 1 : 0) << 2)); } - public bool HT_SPA { get => ((HyperTrainFlags >> 3) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 3)) | ((value ? 1 : 0) << 3)); } - public bool HT_SPD { get => ((HyperTrainFlags >> 4) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 4)) | ((value ? 1 : 0) << 4)); } - public bool HT_SPE { get => ((HyperTrainFlags >> 5) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 5)) | ((value ? 1 : 0) << 5)); } - - public bool GetMoveRecordFlag(int index) - { - if ((uint)index > 112) // 14 bytes, 8 bits - throw new ArgumentOutOfRangeException(nameof(index)); - int ofs = index >> 3; - return FlagUtil.GetFlag(Data, 0x127 + ofs, index & 7); - } - - public void SetMoveRecordFlag(int index, bool value) - { - if ((uint)index > 112) // 14 bytes, 8 bits - throw new ArgumentOutOfRangeException(nameof(index)); - int ofs = index >> 3; - FlagUtil.SetFlag(Data, 0x127 + ofs, index & 7, value); - } - - public bool GetMoveRecordFlagAny() => Array.FindIndex(Data, 0x127, 14, z => z != 0) >= 0; - - public void ClearMoveRecordFlags() => Data.AsSpan(0x127, 14).Clear(); - - // Why did you mis-align this field, GameFreak? - public ulong Tracker - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x135)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x135), value); - } - - #endregion - #region Battle Stats - public override int Stat_Level { get => Data[0x148]; set => Data[0x148] = (byte)value; } - // 0x149 unused alignment - public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0x14A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14A), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0x14C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14C), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0x14E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14E), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0x150)); set => WriteUInt16LittleEndian(Data.AsSpan(0x150), (ushort)value); } - public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0x152)); set => WriteUInt16LittleEndian(Data.AsSpan(0x152), (ushort)value); } - public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0x154)); set => WriteUInt16LittleEndian(Data.AsSpan(0x154), (ushort)value); } - #endregion - - public override int MarkingCount => 6; - - public override int GetMarking(int index) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - return (MarkValue >> (index * 2)) & 3; - } - - public override void SetMarking(int index, int value) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - var shift = index * 2; - MarkValue = (MarkValue & ~(0b11 << shift)) | ((value & 3) << shift); - } - - public bool GetRibbon(int index) => FlagUtil.GetFlag(Data, GetRibbonByte(index), index & 7); - public void SetRibbon(int index, bool value = true) => FlagUtil.SetFlag(Data, GetRibbonByte(index), index & 7, value); - - public int GetRibbonByte(int index) - { - if ((uint)index >= 128) - throw new ArgumentOutOfRangeException(nameof(index)); - if (index < 64) - return 0x34 + (index >> 3); - index -= 64; - return 0x40 + (index >> 3); - } - - protected T ConvertTo() where T : G8PKM, new() - { - var pk = new T(); - Data.AsSpan().CopyTo(pk.Data); - pk.Move1_PPUps = pk.Move2_PPUps = pk.Move3_PPUps = pk.Move4_PPUps = 0; - pk.RelearnMove1 = pk.RelearnMove2 = pk.RelearnMove3 = pk.RelearnMove4 = 0; - pk.ClearMoveRecordFlags(); - pk.ClearPokeJobFlags(); - - pk.CanGigantamax = false; - pk.DynamaxLevel = 0; - pk.Sociability = 0; - pk.Fullness = 0; - pk.Data[0x52] = 0; - - pk.ResetMoves(); - pk.ResetPartyStats(); - pk.RefreshChecksum(); - - return pk; - } - - public virtual PA8 ConvertToPA8() - { - var pk = new PA8 - { - EncryptionConstant = EncryptionConstant, - PID = PID, - Species = Species, - Form = Form, - FormArgument = FormArgument, - Gender = Gender, - Nature = Nature, - StatNature = StatNature, - - TID = TID, - SID = SID, - EXP = EXP, - Ability = Ability, - AbilityNumber = AbilityNumber, - Language = Language, - Version = Version, - - IV_HP = IV_HP, - IV_ATK = IV_ATK, - IV_DEF = IV_DEF, - IV_SPE = IV_SPE, - IV_SPA = IV_SPA, - IV_SPD = IV_SPD, - IsEgg = IsEgg, - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPE = EV_SPE, - EV_SPA = EV_SPA, - EV_SPD = EV_SPD, - - OT_Gender = OT_Gender, - OT_Friendship = OT_Friendship, - OT_Intensity = OT_Intensity, - OT_Memory = OT_Memory, - OT_TextVar = OT_TextVar, - OT_Feeling = OT_Feeling, - Egg_Year = Egg_Year, - Egg_Month = Egg_Month, - Egg_Day = Egg_Day, - Met_Year = Met_Year, - Met_Month = Met_Month, - Met_Day = Met_Day, - Ball = Ball, - Egg_Location = Egg_Location, - Met_Location = Met_Location, - Met_Level = Met_Level, - Tracker = Tracker, - - IsNicknamed = IsNicknamed, - CurrentHandler = CurrentHandler, - HT_Gender = HT_Gender, - HT_Language = HT_Language, - HT_Friendship = HT_Friendship, - HT_Intensity = HT_Intensity, - HT_Memory = HT_Memory, - HT_Feeling = HT_Feeling, - HT_TextVar = HT_TextVar, - - FatefulEncounter = FatefulEncounter, - CNT_Cool = CNT_Cool, - CNT_Beauty = CNT_Beauty, - CNT_Cute = CNT_Cute, - CNT_Smart = CNT_Smart, - CNT_Tough = CNT_Tough, - CNT_Sheen = CNT_Sheen, - - RibbonChampionKalos = RibbonChampionKalos, - RibbonChampionG3 = RibbonChampionG3, - RibbonChampionSinnoh = RibbonChampionSinnoh, - RibbonBestFriends = RibbonBestFriends, - RibbonTraining = RibbonTraining, - RibbonBattlerSkillful = RibbonBattlerSkillful, - RibbonBattlerExpert = RibbonBattlerExpert, - RibbonEffort = RibbonEffort, - RibbonAlert = RibbonAlert, - RibbonShock = RibbonShock, - RibbonDowncast = RibbonDowncast, - RibbonCareless = RibbonCareless, - RibbonRelax = RibbonRelax, - RibbonSnooze = RibbonSnooze, - RibbonSmile = RibbonSmile, - RibbonGorgeous = RibbonGorgeous, - RibbonRoyal = RibbonRoyal, - RibbonGorgeousRoyal = RibbonGorgeousRoyal, - RibbonArtist = RibbonArtist, - RibbonFootprint = RibbonFootprint, - RibbonRecord = RibbonRecord, - RibbonLegend = RibbonLegend, - RibbonCountry = RibbonCountry, - RibbonNational = RibbonNational, - RibbonEarth = RibbonEarth, - RibbonWorld = RibbonWorld, - RibbonClassic = RibbonClassic, - RibbonPremier = RibbonPremier, - RibbonEvent = RibbonEvent, - RibbonBirthday = RibbonBirthday, - RibbonSpecial = RibbonSpecial, - RibbonSouvenir = RibbonSouvenir, - RibbonWishing = RibbonWishing, - RibbonChampionBattle = RibbonChampionBattle, - RibbonChampionRegional = RibbonChampionRegional, - RibbonChampionNational = RibbonChampionNational, - RibbonChampionWorld = RibbonChampionWorld, - HasContestMemoryRibbon = HasContestMemoryRibbon, - HasBattleMemoryRibbon = HasBattleMemoryRibbon, - RibbonChampionG6Hoenn = RibbonChampionG6Hoenn, - RibbonContestStar = RibbonContestStar, - RibbonMasterCoolness = RibbonMasterCoolness, - RibbonMasterBeauty = RibbonMasterBeauty, - RibbonMasterCuteness = RibbonMasterCuteness, - RibbonMasterCleverness = RibbonMasterCleverness, - RibbonMasterToughness = RibbonMasterToughness, - RibbonChampionAlola = RibbonChampionAlola, - RibbonBattleRoyale = RibbonBattleRoyale, - RibbonBattleTreeGreat = RibbonBattleTreeGreat, - RibbonBattleTreeMaster = RibbonBattleTreeMaster, - RibbonChampionGalar = RibbonChampionGalar, - RibbonTowerMaster = RibbonTowerMaster, - RibbonMasterRank = RibbonMasterRank, - - RibbonMarkLunchtime = RibbonMarkLunchtime, - RibbonMarkSleepyTime = RibbonMarkSleepyTime, - RibbonMarkDusk = RibbonMarkDusk, - RibbonMarkDawn = RibbonMarkDawn, - RibbonMarkCloudy = RibbonMarkCloudy, - RibbonMarkRainy = RibbonMarkRainy, - RibbonMarkStormy = RibbonMarkStormy, - RibbonMarkSnowy = RibbonMarkSnowy, - RibbonMarkBlizzard = RibbonMarkBlizzard, - RibbonMarkDry = RibbonMarkDry, - RibbonMarkSandstorm = RibbonMarkSandstorm, - RibbonCountMemoryContest = RibbonCountMemoryContest, - RibbonCountMemoryBattle = RibbonCountMemoryBattle, - RibbonMarkMisty = RibbonMarkMisty, - RibbonMarkDestiny = RibbonMarkDestiny, - RibbonMarkFishing = RibbonMarkFishing, - RibbonMarkCurry = RibbonMarkCurry, - RibbonMarkUncommon = RibbonMarkUncommon, - RibbonMarkRare = RibbonMarkRare, - RibbonMarkRowdy = RibbonMarkRowdy, - RibbonMarkAbsentMinded = RibbonMarkAbsentMinded, - RibbonMarkJittery = RibbonMarkJittery, - RibbonMarkExcited = RibbonMarkExcited, - RibbonMarkCharismatic = RibbonMarkCharismatic, - RibbonMarkCalmness = RibbonMarkCalmness, - RibbonMarkIntense = RibbonMarkIntense, - RibbonMarkZonedOut = RibbonMarkZonedOut, - RibbonMarkJoyful = RibbonMarkJoyful, - RibbonMarkAngry = RibbonMarkAngry, - RibbonMarkSmiley = RibbonMarkSmiley, - RibbonMarkTeary = RibbonMarkTeary, - RibbonMarkUpbeat = RibbonMarkUpbeat, - RibbonMarkPeeved = RibbonMarkPeeved, - RibbonMarkIntellectual = RibbonMarkIntellectual, - RibbonMarkFerocious = RibbonMarkFerocious, - RibbonMarkCrafty = RibbonMarkCrafty, - RibbonMarkScowling = RibbonMarkScowling, - RibbonMarkKindly = RibbonMarkKindly, - RibbonMarkFlustered = RibbonMarkFlustered, - RibbonMarkPumpedUp = RibbonMarkPumpedUp, - RibbonMarkZeroEnergy = RibbonMarkZeroEnergy, - RibbonMarkPrideful = RibbonMarkPrideful, - RibbonMarkUnsure = RibbonMarkUnsure, - RibbonMarkHumble = RibbonMarkHumble, - RibbonMarkThorny = RibbonMarkThorny, - RibbonMarkVigor = RibbonMarkVigor, - RibbonMarkSlump = RibbonMarkSlump, - RibbonPioneer = RibbonPioneer, - RibbonTwinklingStar = RibbonTwinklingStar, - - AffixedRibbon = AffixedRibbon, - HyperTrainFlags = HyperTrainFlags, - - BattleVersion = BattleVersion, - PKRS_Days = PKRS_Days, - PKRS_Strain = PKRS_Strain, - HeightScalar = HeightScalar, - WeightScalar = WeightScalar, - - Favorite = Favorite, - MarkValue = MarkValue, - }; - - Nickname_Trash.CopyTo(pk.Nickname_Trash); - OT_Trash.CopyTo(pk.OT_Trash); - HT_Trash.CopyTo(pk.HT_Trash); - - pk.SanitizeImport(); - - pk.ResetMoves(); - pk.ResetPartyStats(); - pk.RefreshChecksum(); - - return pk; + return (pm6stat * 5) + (maxIV % 5); } } + + // Methods + protected override byte[] Encrypt() + { + RefreshChecksum(); + return PokeCrypto.EncryptArray8(Data); + } + + public void FixRelearn() + { + while (true) + { + if (RelearnMove4 != 0 && RelearnMove3 == 0) + { + RelearnMove3 = RelearnMove4; + RelearnMove4 = 0; + } + if (RelearnMove3 != 0 && RelearnMove2 == 0) + { + RelearnMove2 = RelearnMove3; + RelearnMove3 = 0; + continue; + } + if (RelearnMove2 != 0 && RelearnMove1 == 0) + { + RelearnMove1 = RelearnMove2; + RelearnMove2 = 0; + continue; + } + break; + } + } + + public override uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } + public ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); } + public ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); } + + // Structure + #region Block A + public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); } + public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } + public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); } + public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); } + public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); } + public override int Ability { get => ReadUInt16LittleEndian(Data.AsSpan(0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14), (ushort)value); } + public override int AbilityNumber { get => Data[0x16] & 7; set => Data[0x16] = (byte)((Data[0x16] & ~7) | (value & 7)); } + public bool Favorite { get => (Data[0x16] & 8) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~8) | ((value ? 1 : 0) << 3)); } // unused, was in LGPE but not in SWSH + public bool CanGigantamax { get => (Data[0x16] & 16) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~16) | (value ? 16 : 0)); } + // 0x17 alignment unused + public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); } + // 0x1A alignment unused + // 0x1B alignment unused + public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x1C), value); } + public override int Nature { get => Data[0x20]; set => Data[0x20] = (byte)value; } + public override int StatNature { get => Data[0x21]; set => Data[0x21] = (byte)value; } + public override bool FatefulEncounter { get => (Data[0x22] & 1) == 1; set => Data[0x22] = (byte)((Data[0x22] & ~0x01) | (value ? 1 : 0)); } + public bool Flag2 { get => (Data[0x22] & 2) == 2; set => Data[0x22] = (byte)((Data[0x22] & ~0x02) | (value ? 2 : 0)); } + public override int Gender { get => (Data[0x22] >> 2) & 0x3; set => Data[0x22] = (byte)((Data[0x22] & 0xF3) | (value << 2)); } + // 0x23 alignment unused + + public override int Form { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); } + public override int EV_HP { get => Data[0x26]; set => Data[0x26] = (byte)value; } + public override int EV_ATK { get => Data[0x27]; set => Data[0x27] = (byte)value; } + public override int EV_DEF { get => Data[0x28]; set => Data[0x28] = (byte)value; } + public override int EV_SPE { get => Data[0x29]; set => Data[0x29] = (byte)value; } + public override int EV_SPA { get => Data[0x2A]; set => Data[0x2A] = (byte)value; } + public override int EV_SPD { get => Data[0x2B]; set => Data[0x2B] = (byte)value; } + public byte CNT_Cool { get => Data[0x2C]; set => Data[0x2C] = value; } + public byte CNT_Beauty { get => Data[0x2D]; set => Data[0x2D] = value; } + public byte CNT_Cute { get => Data[0x2E]; set => Data[0x2E] = value; } + public byte CNT_Smart { get => Data[0x2F]; set => Data[0x2F] = value; } + public byte CNT_Tough { get => Data[0x30]; set => Data[0x30] = value; } + public byte CNT_Sheen { get => Data[0x31]; set => Data[0x31] = value; } + private byte PKRS { get => Data[0x32]; set => Data[0x32] = value; } + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + // 0x33 unused padding + + // ribbon u32 + public bool RibbonChampionKalos { get => FlagUtil.GetFlag(Data, 0x34, 0); set => FlagUtil.SetFlag(Data, 0x34, 0, value); } + public bool RibbonChampionG3 { get => FlagUtil.GetFlag(Data, 0x34, 1); set => FlagUtil.SetFlag(Data, 0x34, 1, value); } + public bool RibbonChampionSinnoh { get => FlagUtil.GetFlag(Data, 0x34, 2); set => FlagUtil.SetFlag(Data, 0x34, 2, value); } + public bool RibbonBestFriends { get => FlagUtil.GetFlag(Data, 0x34, 3); set => FlagUtil.SetFlag(Data, 0x34, 3, value); } + public bool RibbonTraining { get => FlagUtil.GetFlag(Data, 0x34, 4); set => FlagUtil.SetFlag(Data, 0x34, 4, value); } + public bool RibbonBattlerSkillful { get => FlagUtil.GetFlag(Data, 0x34, 5); set => FlagUtil.SetFlag(Data, 0x34, 5, value); } + public bool RibbonBattlerExpert { get => FlagUtil.GetFlag(Data, 0x34, 6); set => FlagUtil.SetFlag(Data, 0x34, 6, value); } + public bool RibbonEffort { get => FlagUtil.GetFlag(Data, 0x34, 7); set => FlagUtil.SetFlag(Data, 0x34, 7, value); } + + public bool RibbonAlert { get => FlagUtil.GetFlag(Data, 0x35, 0); set => FlagUtil.SetFlag(Data, 0x35, 0, value); } + public bool RibbonShock { get => FlagUtil.GetFlag(Data, 0x35, 1); set => FlagUtil.SetFlag(Data, 0x35, 1, value); } + public bool RibbonDowncast { get => FlagUtil.GetFlag(Data, 0x35, 2); set => FlagUtil.SetFlag(Data, 0x35, 2, value); } + public bool RibbonCareless { get => FlagUtil.GetFlag(Data, 0x35, 3); set => FlagUtil.SetFlag(Data, 0x35, 3, value); } + public bool RibbonRelax { get => FlagUtil.GetFlag(Data, 0x35, 4); set => FlagUtil.SetFlag(Data, 0x35, 4, value); } + public bool RibbonSnooze { get => FlagUtil.GetFlag(Data, 0x35, 5); set => FlagUtil.SetFlag(Data, 0x35, 5, value); } + public bool RibbonSmile { get => FlagUtil.GetFlag(Data, 0x35, 6); set => FlagUtil.SetFlag(Data, 0x35, 6, value); } + public bool RibbonGorgeous { get => FlagUtil.GetFlag(Data, 0x35, 7); set => FlagUtil.SetFlag(Data, 0x35, 7, value); } + + public bool RibbonRoyal { get => FlagUtil.GetFlag(Data, 0x36, 0); set => FlagUtil.SetFlag(Data, 0x36, 0, value); } + public bool RibbonGorgeousRoyal { get => FlagUtil.GetFlag(Data, 0x36, 1); set => FlagUtil.SetFlag(Data, 0x36, 1, value); } + public bool RibbonArtist { get => FlagUtil.GetFlag(Data, 0x36, 2); set => FlagUtil.SetFlag(Data, 0x36, 2, value); } + public bool RibbonFootprint { get => FlagUtil.GetFlag(Data, 0x36, 3); set => FlagUtil.SetFlag(Data, 0x36, 3, value); } + public bool RibbonRecord { get => FlagUtil.GetFlag(Data, 0x36, 4); set => FlagUtil.SetFlag(Data, 0x36, 4, value); } + public bool RibbonLegend { get => FlagUtil.GetFlag(Data, 0x36, 5); set => FlagUtil.SetFlag(Data, 0x36, 5, value); } + public bool RibbonCountry { get => FlagUtil.GetFlag(Data, 0x36, 6); set => FlagUtil.SetFlag(Data, 0x36, 6, value); } + public bool RibbonNational { get => FlagUtil.GetFlag(Data, 0x36, 7); set => FlagUtil.SetFlag(Data, 0x36, 7, value); } + + public bool RibbonEarth { get => FlagUtil.GetFlag(Data, 0x37, 0); set => FlagUtil.SetFlag(Data, 0x37, 0, value); } + public bool RibbonWorld { get => FlagUtil.GetFlag(Data, 0x37, 1); set => FlagUtil.SetFlag(Data, 0x37, 1, value); } + public bool RibbonClassic { get => FlagUtil.GetFlag(Data, 0x37, 2); set => FlagUtil.SetFlag(Data, 0x37, 2, value); } + public bool RibbonPremier { get => FlagUtil.GetFlag(Data, 0x37, 3); set => FlagUtil.SetFlag(Data, 0x37, 3, value); } + public bool RibbonEvent { get => FlagUtil.GetFlag(Data, 0x37, 4); set => FlagUtil.SetFlag(Data, 0x37, 4, value); } + public bool RibbonBirthday { get => FlagUtil.GetFlag(Data, 0x37, 5); set => FlagUtil.SetFlag(Data, 0x37, 5, value); } + public bool RibbonSpecial { get => FlagUtil.GetFlag(Data, 0x37, 6); set => FlagUtil.SetFlag(Data, 0x37, 6, value); } + public bool RibbonSouvenir { get => FlagUtil.GetFlag(Data, 0x37, 7); set => FlagUtil.SetFlag(Data, 0x37, 7, value); } + + // ribbon u32 + public bool RibbonWishing { get => FlagUtil.GetFlag(Data, 0x38, 0); set => FlagUtil.SetFlag(Data, 0x38, 0, value); } + public bool RibbonChampionBattle { get => FlagUtil.GetFlag(Data, 0x38, 1); set => FlagUtil.SetFlag(Data, 0x38, 1, value); } + public bool RibbonChampionRegional { get => FlagUtil.GetFlag(Data, 0x38, 2); set => FlagUtil.SetFlag(Data, 0x38, 2, value); } + public bool RibbonChampionNational { get => FlagUtil.GetFlag(Data, 0x38, 3); set => FlagUtil.SetFlag(Data, 0x38, 3, value); } + public bool RibbonChampionWorld { get => FlagUtil.GetFlag(Data, 0x38, 4); set => FlagUtil.SetFlag(Data, 0x38, 4, value); } + public bool HasContestMemoryRibbon { get => FlagUtil.GetFlag(Data, 0x38, 5); set => FlagUtil.SetFlag(Data, 0x38, 5, value); } + public bool HasBattleMemoryRibbon { get => FlagUtil.GetFlag(Data, 0x38, 6); set => FlagUtil.SetFlag(Data, 0x38, 6, value); } + public bool RibbonChampionG6Hoenn { get => FlagUtil.GetFlag(Data, 0x38, 7); set => FlagUtil.SetFlag(Data, 0x38, 7, value); } + + public bool RibbonContestStar { get => FlagUtil.GetFlag(Data, 0x39, 0); set => FlagUtil.SetFlag(Data, 0x39, 0, value); } + public bool RibbonMasterCoolness { get => FlagUtil.GetFlag(Data, 0x39, 1); set => FlagUtil.SetFlag(Data, 0x39, 1, value); } + public bool RibbonMasterBeauty { get => FlagUtil.GetFlag(Data, 0x39, 2); set => FlagUtil.SetFlag(Data, 0x39, 2, value); } + public bool RibbonMasterCuteness { get => FlagUtil.GetFlag(Data, 0x39, 3); set => FlagUtil.SetFlag(Data, 0x39, 3, value); } + public bool RibbonMasterCleverness { get => FlagUtil.GetFlag(Data, 0x39, 4); set => FlagUtil.SetFlag(Data, 0x39, 4, value); } + public bool RibbonMasterToughness { get => FlagUtil.GetFlag(Data, 0x39, 5); set => FlagUtil.SetFlag(Data, 0x39, 5, value); } + public bool RibbonChampionAlola { get => FlagUtil.GetFlag(Data, 0x39, 6); set => FlagUtil.SetFlag(Data, 0x39, 6, value); } + public bool RibbonBattleRoyale { get => FlagUtil.GetFlag(Data, 0x39, 7); set => FlagUtil.SetFlag(Data, 0x39, 7, value); } + + public bool RibbonBattleTreeGreat { get => FlagUtil.GetFlag(Data, 0x3A, 0); set => FlagUtil.SetFlag(Data, 0x3A, 0, value); } + public bool RibbonBattleTreeMaster { get => FlagUtil.GetFlag(Data, 0x3A, 1); set => FlagUtil.SetFlag(Data, 0x3A, 1, value); } + public bool RibbonChampionGalar { get => FlagUtil.GetFlag(Data, 0x3A, 2); set => FlagUtil.SetFlag(Data, 0x3A, 2, value); } + public bool RibbonTowerMaster { get => FlagUtil.GetFlag(Data, 0x3A, 3); set => FlagUtil.SetFlag(Data, 0x3A, 3, value); } + public bool RibbonMasterRank { get => FlagUtil.GetFlag(Data, 0x3A, 4); set => FlagUtil.SetFlag(Data, 0x3A, 4, value); } + public bool RibbonMarkLunchtime { get => FlagUtil.GetFlag(Data, 0x3A, 5); set => FlagUtil.SetFlag(Data, 0x3A, 5, value); } + public bool RibbonMarkSleepyTime { get => FlagUtil.GetFlag(Data, 0x3A, 6); set => FlagUtil.SetFlag(Data, 0x3A, 6, value); } + public bool RibbonMarkDusk { get => FlagUtil.GetFlag(Data, 0x3A, 7); set => FlagUtil.SetFlag(Data, 0x3A, 7, value); } + + public bool RibbonMarkDawn { get => FlagUtil.GetFlag(Data, 0x3B, 0); set => FlagUtil.SetFlag(Data, 0x3B, 0, value); } + public bool RibbonMarkCloudy { get => FlagUtil.GetFlag(Data, 0x3B, 1); set => FlagUtil.SetFlag(Data, 0x3B, 1, value); } + public bool RibbonMarkRainy { get => FlagUtil.GetFlag(Data, 0x3B, 2); set => FlagUtil.SetFlag(Data, 0x3B, 2, value); } + public bool RibbonMarkStormy { get => FlagUtil.GetFlag(Data, 0x3B, 3); set => FlagUtil.SetFlag(Data, 0x3B, 3, value); } + public bool RibbonMarkSnowy { get => FlagUtil.GetFlag(Data, 0x3B, 4); set => FlagUtil.SetFlag(Data, 0x3B, 4, value); } + public bool RibbonMarkBlizzard { get => FlagUtil.GetFlag(Data, 0x3B, 5); set => FlagUtil.SetFlag(Data, 0x3B, 5, value); } + public bool RibbonMarkDry { get => FlagUtil.GetFlag(Data, 0x3B, 6); set => FlagUtil.SetFlag(Data, 0x3B, 6, value); } + public bool RibbonMarkSandstorm { get => FlagUtil.GetFlag(Data, 0x3B, 7); set => FlagUtil.SetFlag(Data, 0x3B, 7, value); } + public int RibbonCountMemoryContest { get => Data[0x3C]; set => HasContestMemoryRibbon = (Data[0x3C] = (byte)value) != 0; } + public int RibbonCountMemoryBattle { get => Data[0x3D]; set => HasBattleMemoryRibbon = (Data[0x3D] = (byte)value) != 0; } + // 0x3E padding + // 0x3F padding + + // 0x40 Ribbon 1 + public bool RibbonMarkMisty { get => FlagUtil.GetFlag(Data, 0x40, 0); set => FlagUtil.SetFlag(Data, 0x40, 0, value); } + public bool RibbonMarkDestiny { get => FlagUtil.GetFlag(Data, 0x40, 1); set => FlagUtil.SetFlag(Data, 0x40, 1, value); } + public bool RibbonMarkFishing { get => FlagUtil.GetFlag(Data, 0x40, 2); set => FlagUtil.SetFlag(Data, 0x40, 2, value); } + public bool RibbonMarkCurry { get => FlagUtil.GetFlag(Data, 0x40, 3); set => FlagUtil.SetFlag(Data, 0x40, 3, value); } + public bool RibbonMarkUncommon { get => FlagUtil.GetFlag(Data, 0x40, 4); set => FlagUtil.SetFlag(Data, 0x40, 4, value); } + public bool RibbonMarkRare { get => FlagUtil.GetFlag(Data, 0x40, 5); set => FlagUtil.SetFlag(Data, 0x40, 5, value); } + public bool RibbonMarkRowdy { get => FlagUtil.GetFlag(Data, 0x40, 6); set => FlagUtil.SetFlag(Data, 0x40, 6, value); } + public bool RibbonMarkAbsentMinded { get => FlagUtil.GetFlag(Data, 0x40, 7); set => FlagUtil.SetFlag(Data, 0x40, 7, value); } + + public bool RibbonMarkJittery { get => FlagUtil.GetFlag(Data, 0x41, 0); set => FlagUtil.SetFlag(Data, 0x41, 0, value); } + public bool RibbonMarkExcited { get => FlagUtil.GetFlag(Data, 0x41, 1); set => FlagUtil.SetFlag(Data, 0x41, 1, value); } + public bool RibbonMarkCharismatic { get => FlagUtil.GetFlag(Data, 0x41, 2); set => FlagUtil.SetFlag(Data, 0x41, 2, value); } + public bool RibbonMarkCalmness { get => FlagUtil.GetFlag(Data, 0x41, 3); set => FlagUtil.SetFlag(Data, 0x41, 3, value); } + public bool RibbonMarkIntense { get => FlagUtil.GetFlag(Data, 0x41, 4); set => FlagUtil.SetFlag(Data, 0x41, 4, value); } + public bool RibbonMarkZonedOut { get => FlagUtil.GetFlag(Data, 0x41, 5); set => FlagUtil.SetFlag(Data, 0x41, 5, value); } + public bool RibbonMarkJoyful { get => FlagUtil.GetFlag(Data, 0x41, 6); set => FlagUtil.SetFlag(Data, 0x41, 6, value); } + public bool RibbonMarkAngry { get => FlagUtil.GetFlag(Data, 0x41, 7); set => FlagUtil.SetFlag(Data, 0x41, 7, value); } + + public bool RibbonMarkSmiley { get => FlagUtil.GetFlag(Data, 0x42, 0); set => FlagUtil.SetFlag(Data, 0x42, 0, value); } + public bool RibbonMarkTeary { get => FlagUtil.GetFlag(Data, 0x42, 1); set => FlagUtil.SetFlag(Data, 0x42, 1, value); } + public bool RibbonMarkUpbeat { get => FlagUtil.GetFlag(Data, 0x42, 2); set => FlagUtil.SetFlag(Data, 0x42, 2, value); } + public bool RibbonMarkPeeved { get => FlagUtil.GetFlag(Data, 0x42, 3); set => FlagUtil.SetFlag(Data, 0x42, 3, value); } + public bool RibbonMarkIntellectual { get => FlagUtil.GetFlag(Data, 0x42, 4); set => FlagUtil.SetFlag(Data, 0x42, 4, value); } + public bool RibbonMarkFerocious { get => FlagUtil.GetFlag(Data, 0x42, 5); set => FlagUtil.SetFlag(Data, 0x42, 5, value); } + public bool RibbonMarkCrafty { get => FlagUtil.GetFlag(Data, 0x42, 6); set => FlagUtil.SetFlag(Data, 0x42, 6, value); } + public bool RibbonMarkScowling { get => FlagUtil.GetFlag(Data, 0x42, 7); set => FlagUtil.SetFlag(Data, 0x42, 7, value); } + + public bool RibbonMarkKindly { get => FlagUtil.GetFlag(Data, 0x43, 0); set => FlagUtil.SetFlag(Data, 0x43, 0, value); } + public bool RibbonMarkFlustered { get => FlagUtil.GetFlag(Data, 0x43, 1); set => FlagUtil.SetFlag(Data, 0x43, 1, value); } + public bool RibbonMarkPumpedUp { get => FlagUtil.GetFlag(Data, 0x43, 2); set => FlagUtil.SetFlag(Data, 0x43, 2, value); } + public bool RibbonMarkZeroEnergy { get => FlagUtil.GetFlag(Data, 0x43, 3); set => FlagUtil.SetFlag(Data, 0x43, 3, value); } + public bool RibbonMarkPrideful { get => FlagUtil.GetFlag(Data, 0x43, 4); set => FlagUtil.SetFlag(Data, 0x43, 4, value); } + public bool RibbonMarkUnsure { get => FlagUtil.GetFlag(Data, 0x43, 5); set => FlagUtil.SetFlag(Data, 0x43, 5, value); } + public bool RibbonMarkHumble { get => FlagUtil.GetFlag(Data, 0x43, 6); set => FlagUtil.SetFlag(Data, 0x43, 6, value); } + public bool RibbonMarkThorny { get => FlagUtil.GetFlag(Data, 0x43, 7); set => FlagUtil.SetFlag(Data, 0x43, 7, value); } + // 0x44 Ribbon 2 + + public bool RibbonMarkVigor { get => FlagUtil.GetFlag(Data, 0x44, 0); set => FlagUtil.SetFlag(Data, 0x44, 0, value); } + public bool RibbonMarkSlump { get => FlagUtil.GetFlag(Data, 0x44, 1); set => FlagUtil.SetFlag(Data, 0x44, 1, value); } + public bool RibbonPioneer { get => FlagUtil.GetFlag(Data, 0x44, 2); set => FlagUtil.SetFlag(Data, 0x44, 2, value); } + public bool RibbonTwinklingStar { get => FlagUtil.GetFlag(Data, 0x44, 3); set => FlagUtil.SetFlag(Data, 0x44, 3, value); } + public bool RIB44_4 { get => FlagUtil.GetFlag(Data, 0x44, 4); set => FlagUtil.SetFlag(Data, 0x44, 4, value); } + public bool RIB44_5 { get => FlagUtil.GetFlag(Data, 0x44, 5); set => FlagUtil.SetFlag(Data, 0x44, 5, value); } + public bool RIB44_6 { get => FlagUtil.GetFlag(Data, 0x44, 6); set => FlagUtil.SetFlag(Data, 0x44, 6, value); } + public bool RIB44_7 { get => FlagUtil.GetFlag(Data, 0x44, 7); set => FlagUtil.SetFlag(Data, 0x44, 7, value); } + + public bool RIB45_0 { get => FlagUtil.GetFlag(Data, 0x45, 0); set => FlagUtil.SetFlag(Data, 0x45, 0, value); } + public bool RIB45_1 { get => FlagUtil.GetFlag(Data, 0x45, 1); set => FlagUtil.SetFlag(Data, 0x45, 1, value); } + public bool RIB45_2 { get => FlagUtil.GetFlag(Data, 0x45, 2); set => FlagUtil.SetFlag(Data, 0x45, 2, value); } + public bool RIB45_3 { get => FlagUtil.GetFlag(Data, 0x45, 3); set => FlagUtil.SetFlag(Data, 0x45, 3, value); } + public bool RIB45_4 { get => FlagUtil.GetFlag(Data, 0x45, 4); set => FlagUtil.SetFlag(Data, 0x45, 4, value); } + public bool RIB45_5 { get => FlagUtil.GetFlag(Data, 0x45, 5); set => FlagUtil.SetFlag(Data, 0x45, 5, value); } + public bool RIB45_6 { get => FlagUtil.GetFlag(Data, 0x45, 6); set => FlagUtil.SetFlag(Data, 0x45, 6, value); } + public bool RIB45_7 { get => FlagUtil.GetFlag(Data, 0x45, 7); set => FlagUtil.SetFlag(Data, 0x45, 7, value); } + + public bool RIB46_0 { get => FlagUtil.GetFlag(Data, 0x41, 0); set => FlagUtil.SetFlag(Data, 0x41, 0, value); } + public bool RIB46_1 { get => FlagUtil.GetFlag(Data, 0x46, 1); set => FlagUtil.SetFlag(Data, 0x46, 1, value); } + public bool RIB46_2 { get => FlagUtil.GetFlag(Data, 0x46, 2); set => FlagUtil.SetFlag(Data, 0x46, 2, value); } + public bool RIB46_3 { get => FlagUtil.GetFlag(Data, 0x46, 3); set => FlagUtil.SetFlag(Data, 0x46, 3, value); } + public bool RIB46_4 { get => FlagUtil.GetFlag(Data, 0x46, 4); set => FlagUtil.SetFlag(Data, 0x46, 4, value); } + public bool RIB46_5 { get => FlagUtil.GetFlag(Data, 0x46, 5); set => FlagUtil.SetFlag(Data, 0x46, 5, value); } + public bool RIB46_6 { get => FlagUtil.GetFlag(Data, 0x46, 6); set => FlagUtil.SetFlag(Data, 0x46, 6, value); } + public bool RIB46_7 { get => FlagUtil.GetFlag(Data, 0x46, 7); set => FlagUtil.SetFlag(Data, 0x46, 7, value); } + + public bool RIB47_0 { get => FlagUtil.GetFlag(Data, 0x47, 0); set => FlagUtil.SetFlag(Data, 0x47, 0, value); } + public bool RIB47_1 { get => FlagUtil.GetFlag(Data, 0x47, 1); set => FlagUtil.SetFlag(Data, 0x47, 1, value); } + public bool RIB47_2 { get => FlagUtil.GetFlag(Data, 0x47, 2); set => FlagUtil.SetFlag(Data, 0x47, 2, value); } + public bool RIB47_3 { get => FlagUtil.GetFlag(Data, 0x47, 3); set => FlagUtil.SetFlag(Data, 0x47, 3, value); } + public bool RIB47_4 { get => FlagUtil.GetFlag(Data, 0x47, 4); set => FlagUtil.SetFlag(Data, 0x47, 4, value); } + public bool RIB47_5 { get => FlagUtil.GetFlag(Data, 0x47, 5); set => FlagUtil.SetFlag(Data, 0x47, 5, value); } + public bool RIB47_6 { get => FlagUtil.GetFlag(Data, 0x47, 6); set => FlagUtil.SetFlag(Data, 0x47, 6, value); } + public bool RIB47_7 { get => FlagUtil.GetFlag(Data, 0x47, 7); set => FlagUtil.SetFlag(Data, 0x47, 7, value); } + + public bool HasMark() + { + var d = Data.AsSpan(); + if ((ReadUInt16LittleEndian(d[0x3A..]) & 0xFFE0) != 0) + return true; + if (ReadUInt32LittleEndian(d[0x40..]) != 0) + return true; + return (d[0x44] & 3) != 0; + } + + public uint Sociability { get => ReadUInt32LittleEndian(Data.AsSpan(0x48)); set => WriteUInt32LittleEndian(Data.AsSpan(0x48), value); } + + // 0x4C-0x4F unused + + public byte HeightScalar { get => Data[0x50]; set => Data[0x50] = value; } + public byte WeightScalar { get => Data[0x51]; set => Data[0x51] = value; } + + // 0x52-0x57 unused + + #endregion + #region Block B + public override string Nickname + { + get => StringConverter8.GetString(Nickname_Trash); + set => StringConverter8.SetString(Nickname_Trash, value.AsSpan(), 12, StringConverterOption.None); + } + + // 2 bytes for \0, automatically handled above + + public override int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x72)); set => WriteUInt16LittleEndian(Data.AsSpan(0x72), (ushort)value); } + public override int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x74)); set => WriteUInt16LittleEndian(Data.AsSpan(0x74), (ushort)value); } + public override int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x76)); set => WriteUInt16LittleEndian(Data.AsSpan(0x76), (ushort)value); } + public override int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x78)); set => WriteUInt16LittleEndian(Data.AsSpan(0x78), (ushort)value); } + + public override int Move1_PP { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } + public override int Move2_PP { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } + public override int Move3_PP { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } + public override int Move4_PP { get => Data[0x7D]; set => Data[0x7D] = (byte)value; } + public override int Move1_PPUps { get => Data[0x7E]; set => Data[0x7E] = (byte)value; } + public override int Move2_PPUps { get => Data[0x7F]; set => Data[0x7F] = (byte)value; } + public override int Move3_PPUps { get => Data[0x80]; set => Data[0x80] = (byte)value; } + public override int Move4_PPUps { get => Data[0x81]; set => Data[0x81] = (byte)value; } + + public override int RelearnMove1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x82)); set => WriteUInt16LittleEndian(Data.AsSpan(0x82), (ushort)value); } + public override int RelearnMove2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x84)); set => WriteUInt16LittleEndian(Data.AsSpan(0x84), (ushort)value); } + public override int RelearnMove3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x86)); set => WriteUInt16LittleEndian(Data.AsSpan(0x86), (ushort)value); } + public override int RelearnMove4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x88)); set => WriteUInt16LittleEndian(Data.AsSpan(0x88), (ushort)value); } + + public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0x8A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8A), (ushort)value); } + + private uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x8C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x8C), value); } + public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } + public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } + public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } + public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } + public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } + public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } + public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } + public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } + + public byte DynamaxLevel { get => Data[0x90]; set => Data[0x90] = value; } + + // 0x91-0x93 unused + + public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0x94)); set => WriteInt32LittleEndian(Data.AsSpan(0x94), value); } + public int Palma { get => ReadInt32LittleEndian(Data.AsSpan(0x98)); set => WriteInt32LittleEndian(Data.AsSpan(0x98), value); } + + // 0x9C-0xA7 unused + + #endregion + #region Block C + public override string HT_Name + { + get => StringConverter8.GetString(HT_Trash); + set => StringConverter8.SetString(HT_Trash, value.AsSpan(), 12, StringConverterOption.None); + } + + public override int HT_Gender { get => Data[0xC2]; set => Data[0xC2] = (byte)value; } + public byte HT_Language { get => Data[0xC3]; set => Data[0xC3] = value; } + public override int CurrentHandler { get => Data[0xC4]; set => Data[0xC4] = (byte)value; } + // 0xC5 unused (alignment) + public int HT_TrainerID { get => ReadUInt16LittleEndian(Data.AsSpan(0xC6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xC6), (ushort)value); } // unused? + public override int HT_Friendship { get => Data[0xC8]; set => Data[0xC8] = (byte)value; } + public byte HT_Intensity { get => Data[0xC9]; set => Data[0xC9] = value; } + public byte HT_Memory { get => Data[0xCA]; set => Data[0xCA] = value; } + public byte HT_Feeling { get => Data[0xCB]; set => Data[0xCB] = value; } + public ushort HT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xCC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xCC), value); } + + public bool GetPokeJobFlag(int index) + { + if ((uint)index > 112) // 14 bytes, 8 bits + throw new ArgumentOutOfRangeException(nameof(index)); + int ofs = index >> 3; + return FlagUtil.GetFlag(Data, 0xCE + ofs, index & 7); + } + + public void SetPokeJobFlag(int index, bool value) + { + if ((uint)index > 112) // 14 bytes, 8 bits + throw new ArgumentOutOfRangeException(nameof(index)); + int ofs = index >> 3; + FlagUtil.SetFlag(Data, 0xCE + ofs, index & 7, value); + } + + public bool GetPokeJobFlagAny() => Array.FindIndex(Data, 0xCE, 14, z => z != 0) >= 0; + + public void ClearPokeJobFlags() => Data.AsSpan(0xCE, 14).Clear(); + + public override byte Fullness { get => Data[0xDC]; set => Data[0xDC] = value; } + public override byte Enjoyment { get => Data[0xDD]; set => Data[0xDD] = value; } + public override int Version { get => Data[0xDE]; set => Data[0xDE] = (byte)value; } + public byte BattleVersion { get => Data[0xDF]; set => Data[0xDF] = value; } + // public override int Region { get => Data[0xE0]; set => Data[0xE0] = (byte)value; } + // public override int ConsoleRegion { get => Data[0xE1]; set => Data[0xE1] = (byte)value; } + public override int Language { get => Data[0xE2]; set => Data[0xE2] = (byte)value; } + // 0xE3 alignment + public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(0xE4)); set => WriteUInt32LittleEndian(Data.AsSpan(0xE4), value); } + public byte FormArgumentRemain { get => (byte)FormArgument; set => FormArgument = (FormArgument & ~0xFFu) | value; } + public byte FormArgumentElapsed { get => (byte)(FormArgument >> 8); set => FormArgument = (FormArgument & ~0xFF00u) | (uint)(value << 8); } + public byte FormArgumentMaximum { get => (byte)(FormArgument >> 16); set => FormArgument = (FormArgument & ~0xFF0000u) | (uint)(value << 16); } + public sbyte AffixedRibbon { get => (sbyte)Data[0xE8]; set => Data[0xE8] = (byte)value; } // selected ribbon + // remainder unused + + #endregion + #region Block D + public override string OT_Name + { + get => StringConverter8.GetString(OT_Trash); + set => StringConverter8.SetString(OT_Trash, value.AsSpan(), 12, StringConverterOption.None); + } + + public override int OT_Friendship { get => Data[0x112]; set => Data[0x112] = (byte)value; } + public byte OT_Intensity { get => Data[0x113]; set => Data[0x113] = value; } + public byte OT_Memory { get => Data[0x114]; set => Data[0x114] = value; } + // 0x115 unused align + public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0x116)); set => WriteUInt16LittleEndian(Data.AsSpan(0x116), value); } + public byte OT_Feeling { get => Data[0x118]; set => Data[0x118] = value; } + public override int Egg_Year { get => Data[0x119]; set => Data[0x119] = (byte)value; } + public override int Egg_Month { get => Data[0x11A]; set => Data[0x11A] = (byte)value; } + public override int Egg_Day { get => Data[0x11B]; set => Data[0x11B] = (byte)value; } + public override int Met_Year { get => Data[0x11C]; set => Data[0x11C] = (byte)value; } + public override int Met_Month { get => Data[0x11D]; set => Data[0x11D] = (byte)value; } + public override int Met_Day { get => Data[0x11E]; set => Data[0x11E] = (byte)value; } + // 0x11F unused align + public override int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0x120)); set => WriteUInt16LittleEndian(Data.AsSpan(0x120), (ushort)value); } + public override int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0x122)); set => WriteUInt16LittleEndian(Data.AsSpan(0x122), (ushort)value); } + public override int Ball { get => Data[0x124]; set => Data[0x124] = (byte)value; } + public override int Met_Level { get => Data[0x125] & ~0x80; set => Data[0x125] = (byte)((Data[0x125] & 0x80) | value); } + public override int OT_Gender { get => Data[0x125] >> 7; set => Data[0x125] = (byte)((Data[0x125] & ~0x80) | (value << 7)); } + public byte HyperTrainFlags { get => Data[0x126]; set => Data[0x126] = value; } + public bool HT_HP { get => ((HyperTrainFlags >> 0) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 0)) | ((value ? 1 : 0) << 0)); } + public bool HT_ATK { get => ((HyperTrainFlags >> 1) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 1)) | ((value ? 1 : 0) << 1)); } + public bool HT_DEF { get => ((HyperTrainFlags >> 2) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 2)) | ((value ? 1 : 0) << 2)); } + public bool HT_SPA { get => ((HyperTrainFlags >> 3) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 3)) | ((value ? 1 : 0) << 3)); } + public bool HT_SPD { get => ((HyperTrainFlags >> 4) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 4)) | ((value ? 1 : 0) << 4)); } + public bool HT_SPE { get => ((HyperTrainFlags >> 5) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 5)) | ((value ? 1 : 0) << 5)); } + + public bool GetMoveRecordFlag(int index) + { + if ((uint)index > 112) // 14 bytes, 8 bits + throw new ArgumentOutOfRangeException(nameof(index)); + int ofs = index >> 3; + return FlagUtil.GetFlag(Data, 0x127 + ofs, index & 7); + } + + public void SetMoveRecordFlag(int index, bool value = true) + { + if ((uint)index > 112) // 14 bytes, 8 bits + throw new ArgumentOutOfRangeException(nameof(index)); + int ofs = index >> 3; + FlagUtil.SetFlag(Data, 0x127 + ofs, index & 7, value); + } + + public bool GetMoveRecordFlagAny() => Array.FindIndex(Data, 0x127, 14, z => z != 0) >= 0; + + public void ClearMoveRecordFlags() => Data.AsSpan(0x127, 14).Clear(); + + // Why did you mis-align this field, GameFreak? + public ulong Tracker + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x135)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x135), value); + } + + #endregion + #region Battle Stats + public override int Stat_Level { get => Data[0x148]; set => Data[0x148] = (byte)value; } + // 0x149 unused alignment + public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0x14A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14A), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0x14C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14C), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0x14E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x14E), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0x150)); set => WriteUInt16LittleEndian(Data.AsSpan(0x150), (ushort)value); } + public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0x152)); set => WriteUInt16LittleEndian(Data.AsSpan(0x152), (ushort)value); } + public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0x154)); set => WriteUInt16LittleEndian(Data.AsSpan(0x154), (ushort)value); } + #endregion + + public override int MarkingCount => 6; + + public override int GetMarking(int index) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return (MarkValue >> (index * 2)) & 3; + } + + public override void SetMarking(int index, int value) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + var shift = index * 2; + MarkValue = (MarkValue & ~(0b11 << shift)) | ((value & 3) << shift); + } + + public bool GetRibbon(int index) => FlagUtil.GetFlag(Data, GetRibbonByte(index), index & 7); + public void SetRibbon(int index, bool value = true) => FlagUtil.SetFlag(Data, GetRibbonByte(index), index & 7, value); + + public int GetRibbonByte(int index) + { + if ((uint)index >= 128) + throw new ArgumentOutOfRangeException(nameof(index)); + if (index < 64) + return 0x34 + (index >> 3); + index -= 64; + return 0x40 + (index >> 3); + } + + protected T ConvertTo() where T : G8PKM, new() + { + var pk = new T(); + Data.AsSpan().CopyTo(pk.Data); + pk.Move1_PPUps = pk.Move2_PPUps = pk.Move3_PPUps = pk.Move4_PPUps = 0; + pk.RelearnMove1 = pk.RelearnMove2 = pk.RelearnMove3 = pk.RelearnMove4 = 0; + pk.ClearMoveRecordFlags(); + pk.ClearPokeJobFlags(); + + pk.CanGigantamax = false; + pk.DynamaxLevel = 0; + pk.Sociability = 0; + pk.Fullness = 0; + pk.Data[0x52] = 0; + + pk.ResetMoves(); + pk.ResetPartyStats(); + pk.RefreshChecksum(); + + return pk; + } + + public virtual PA8 ConvertToPA8() + { + var pk = new PA8 + { + EncryptionConstant = EncryptionConstant, + PID = PID, + Species = Species, + Form = Form, + FormArgument = FormArgument, + Gender = Gender, + Nature = Nature, + StatNature = StatNature, + + TID = TID, + SID = SID, + EXP = EXP, + Ability = Ability, + AbilityNumber = AbilityNumber, + Language = Language, + Version = Version, + + IV_HP = IV_HP, + IV_ATK = IV_ATK, + IV_DEF = IV_DEF, + IV_SPE = IV_SPE, + IV_SPA = IV_SPA, + IV_SPD = IV_SPD, + IsEgg = IsEgg, + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPE = EV_SPE, + EV_SPA = EV_SPA, + EV_SPD = EV_SPD, + + OT_Gender = OT_Gender, + OT_Friendship = OT_Friendship, + OT_Intensity = OT_Intensity, + OT_Memory = OT_Memory, + OT_TextVar = OT_TextVar, + OT_Feeling = OT_Feeling, + Egg_Year = Egg_Year, + Egg_Month = Egg_Month, + Egg_Day = Egg_Day, + Met_Year = Met_Year, + Met_Month = Met_Month, + Met_Day = Met_Day, + Ball = Ball, + Egg_Location = Egg_Location, + Met_Location = Met_Location, + Met_Level = Met_Level, + Tracker = Tracker, + + IsNicknamed = IsNicknamed, + CurrentHandler = CurrentHandler, + HT_Gender = HT_Gender, + HT_Language = HT_Language, + HT_Friendship = HT_Friendship, + HT_Intensity = HT_Intensity, + HT_Memory = HT_Memory, + HT_Feeling = HT_Feeling, + HT_TextVar = HT_TextVar, + + FatefulEncounter = FatefulEncounter, + CNT_Cool = CNT_Cool, + CNT_Beauty = CNT_Beauty, + CNT_Cute = CNT_Cute, + CNT_Smart = CNT_Smart, + CNT_Tough = CNT_Tough, + CNT_Sheen = CNT_Sheen, + + RibbonChampionKalos = RibbonChampionKalos, + RibbonChampionG3 = RibbonChampionG3, + RibbonChampionSinnoh = RibbonChampionSinnoh, + RibbonBestFriends = RibbonBestFriends, + RibbonTraining = RibbonTraining, + RibbonBattlerSkillful = RibbonBattlerSkillful, + RibbonBattlerExpert = RibbonBattlerExpert, + RibbonEffort = RibbonEffort, + RibbonAlert = RibbonAlert, + RibbonShock = RibbonShock, + RibbonDowncast = RibbonDowncast, + RibbonCareless = RibbonCareless, + RibbonRelax = RibbonRelax, + RibbonSnooze = RibbonSnooze, + RibbonSmile = RibbonSmile, + RibbonGorgeous = RibbonGorgeous, + RibbonRoyal = RibbonRoyal, + RibbonGorgeousRoyal = RibbonGorgeousRoyal, + RibbonArtist = RibbonArtist, + RibbonFootprint = RibbonFootprint, + RibbonRecord = RibbonRecord, + RibbonLegend = RibbonLegend, + RibbonCountry = RibbonCountry, + RibbonNational = RibbonNational, + RibbonEarth = RibbonEarth, + RibbonWorld = RibbonWorld, + RibbonClassic = RibbonClassic, + RibbonPremier = RibbonPremier, + RibbonEvent = RibbonEvent, + RibbonBirthday = RibbonBirthday, + RibbonSpecial = RibbonSpecial, + RibbonSouvenir = RibbonSouvenir, + RibbonWishing = RibbonWishing, + RibbonChampionBattle = RibbonChampionBattle, + RibbonChampionRegional = RibbonChampionRegional, + RibbonChampionNational = RibbonChampionNational, + RibbonChampionWorld = RibbonChampionWorld, + HasContestMemoryRibbon = HasContestMemoryRibbon, + HasBattleMemoryRibbon = HasBattleMemoryRibbon, + RibbonChampionG6Hoenn = RibbonChampionG6Hoenn, + RibbonContestStar = RibbonContestStar, + RibbonMasterCoolness = RibbonMasterCoolness, + RibbonMasterBeauty = RibbonMasterBeauty, + RibbonMasterCuteness = RibbonMasterCuteness, + RibbonMasterCleverness = RibbonMasterCleverness, + RibbonMasterToughness = RibbonMasterToughness, + RibbonChampionAlola = RibbonChampionAlola, + RibbonBattleRoyale = RibbonBattleRoyale, + RibbonBattleTreeGreat = RibbonBattleTreeGreat, + RibbonBattleTreeMaster = RibbonBattleTreeMaster, + RibbonChampionGalar = RibbonChampionGalar, + RibbonTowerMaster = RibbonTowerMaster, + RibbonMasterRank = RibbonMasterRank, + + RibbonMarkLunchtime = RibbonMarkLunchtime, + RibbonMarkSleepyTime = RibbonMarkSleepyTime, + RibbonMarkDusk = RibbonMarkDusk, + RibbonMarkDawn = RibbonMarkDawn, + RibbonMarkCloudy = RibbonMarkCloudy, + RibbonMarkRainy = RibbonMarkRainy, + RibbonMarkStormy = RibbonMarkStormy, + RibbonMarkSnowy = RibbonMarkSnowy, + RibbonMarkBlizzard = RibbonMarkBlizzard, + RibbonMarkDry = RibbonMarkDry, + RibbonMarkSandstorm = RibbonMarkSandstorm, + RibbonCountMemoryContest = RibbonCountMemoryContest, + RibbonCountMemoryBattle = RibbonCountMemoryBattle, + RibbonMarkMisty = RibbonMarkMisty, + RibbonMarkDestiny = RibbonMarkDestiny, + RibbonMarkFishing = RibbonMarkFishing, + RibbonMarkCurry = RibbonMarkCurry, + RibbonMarkUncommon = RibbonMarkUncommon, + RibbonMarkRare = RibbonMarkRare, + RibbonMarkRowdy = RibbonMarkRowdy, + RibbonMarkAbsentMinded = RibbonMarkAbsentMinded, + RibbonMarkJittery = RibbonMarkJittery, + RibbonMarkExcited = RibbonMarkExcited, + RibbonMarkCharismatic = RibbonMarkCharismatic, + RibbonMarkCalmness = RibbonMarkCalmness, + RibbonMarkIntense = RibbonMarkIntense, + RibbonMarkZonedOut = RibbonMarkZonedOut, + RibbonMarkJoyful = RibbonMarkJoyful, + RibbonMarkAngry = RibbonMarkAngry, + RibbonMarkSmiley = RibbonMarkSmiley, + RibbonMarkTeary = RibbonMarkTeary, + RibbonMarkUpbeat = RibbonMarkUpbeat, + RibbonMarkPeeved = RibbonMarkPeeved, + RibbonMarkIntellectual = RibbonMarkIntellectual, + RibbonMarkFerocious = RibbonMarkFerocious, + RibbonMarkCrafty = RibbonMarkCrafty, + RibbonMarkScowling = RibbonMarkScowling, + RibbonMarkKindly = RibbonMarkKindly, + RibbonMarkFlustered = RibbonMarkFlustered, + RibbonMarkPumpedUp = RibbonMarkPumpedUp, + RibbonMarkZeroEnergy = RibbonMarkZeroEnergy, + RibbonMarkPrideful = RibbonMarkPrideful, + RibbonMarkUnsure = RibbonMarkUnsure, + RibbonMarkHumble = RibbonMarkHumble, + RibbonMarkThorny = RibbonMarkThorny, + RibbonMarkVigor = RibbonMarkVigor, + RibbonMarkSlump = RibbonMarkSlump, + RibbonPioneer = RibbonPioneer, + RibbonTwinklingStar = RibbonTwinklingStar, + + AffixedRibbon = AffixedRibbon, + HyperTrainFlags = HyperTrainFlags, + + BattleVersion = BattleVersion, + PKRS_Days = PKRS_Days, + PKRS_Strain = PKRS_Strain, + HeightScalar = HeightScalar, + WeightScalar = WeightScalar, + + Favorite = Favorite, + MarkValue = MarkValue, + }; + + Nickname_Trash.CopyTo(pk.Nickname_Trash); + OT_Trash.CopyTo(pk.OT_Trash); + HT_Trash.CopyTo(pk.HT_Trash); + + pk.SanitizeImport(); + + pk.ResetMoves(); + pk.ResetPartyStats(); + pk.RefreshChecksum(); + + return pk; + } } diff --git a/PKHeX.Core/PKM/HOME/GameDataCore.cs b/PKHeX.Core/PKM/HOME/GameDataCore.cs index a4ebdae3c..97051e3e7 100644 --- a/PKHeX.Core/PKM/HOME/GameDataCore.cs +++ b/PKHeX.Core/PKM/HOME/GameDataCore.cs @@ -1,4 +1,4 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; @@ -53,7 +53,7 @@ public GameDataCore(byte[] data, int offset) public byte CNT_Sheen { get => Data[Offset + 0x32]; set => Data[Offset + 0x32] = value; } private byte PKRS { get => Data[Offset + 0x33]; set => Data[Offset + 0x33] = value; } public int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); } + public int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } private bool GetFlag(int offset, int bit) => FlagUtil.GetFlag(Data, Offset + offset, bit); private void SetFlag(int offset, int bit, bool value) => FlagUtil.SetFlag(Data, Offset + offset, bit, value); diff --git a/PKHeX.Core/PKM/HOME/HomeCrypto.cs b/PKHeX.Core/PKM/HOME/HomeCrypto.cs index 7f94b6444..5e906d970 100644 --- a/PKHeX.Core/PKM/HOME/HomeCrypto.cs +++ b/PKHeX.Core/PKM/HOME/HomeCrypto.cs @@ -99,9 +99,9 @@ public static void DecryptIfEncrypted(ref byte[] data) } } - public static byte[] Encrypt(ReadOnlySpan pkm) + public static byte[] Encrypt(ReadOnlySpan pk) { - var result = Crypt1(pkm, false); + var result = Crypt1(pk, false); RefreshChecksum(result, result); return result; } diff --git a/PKHeX.Core/PKM/HOME/PKH.cs b/PKHeX.Core/PKM/HOME/PKH.cs index 621a152d2..407a1c9a4 100644 --- a/PKHeX.Core/PKM/HOME/PKH.cs +++ b/PKHeX.Core/PKM/HOME/PKH.cs @@ -1,4 +1,4 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; using static PKHeX.Core.GameVersion; using static PKHeX.Core.Locations; @@ -159,7 +159,7 @@ private static byte[] DecryptHome(byte[] data) public override int CurrentFriendship { get => CurrentHandler == 0 ? OT_Friendship : HT_Friendship; set { if (CurrentHandler == 0) OT_Friendship = value; else HT_Friendship = value; } } - public override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 4); + public override int PSV => (int)(((PID >> 16) ^ (PID & 0xFFFF)) >> 4); public override int TSV => (TID ^ SID) >> 4; public override int Characteristic @@ -238,7 +238,7 @@ protected override byte[] Encrypt() return HomeCrypto.Encrypt(result); } - private const int GameDataStart = HomeCrypto.SIZE_1HEADER + (2 + HomeCrypto.SIZE_1CORE) + 2; + private const int GameDataStart = HomeCrypto.SIZE_1HEADER + 2 + HomeCrypto.SIZE_1CORE + 2; public byte[] Rebuild() { diff --git a/PKHeX.Core/PKM/Interfaces/IAffection.cs b/PKHeX.Core/PKM/Interfaces/IAffection.cs index 3920626ff..3dfb7a606 100644 --- a/PKHeX.Core/PKM/Interfaces/IAffection.cs +++ b/PKHeX.Core/PKM/Interfaces/IAffection.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Exposes and properties used by Gen6/7. +/// +public interface IAffection { - /// - /// Exposes and properties used by Gen6/7. - /// - public interface IAffection - { - byte OT_Affection { get; set; } - byte HT_Affection { get; set; } - } + byte OT_Affection { get; set; } + byte HT_Affection { get; set; } } diff --git a/PKHeX.Core/PKM/Interfaces/IAwakened.cs b/PKHeX.Core/PKM/Interfaces/IAwakened.cs index df8d3e9ba..91270f658 100644 --- a/PKHeX.Core/PKM/Interfaces/IAwakened.cs +++ b/PKHeX.Core/PKM/Interfaces/IAwakened.cs @@ -1,159 +1,158 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IAwakened { - public interface IAwakened + byte AV_HP { get; set; } + byte AV_ATK { get; set; } + byte AV_DEF { get; set; } + byte AV_SPE { get; set; } + byte AV_SPA { get; set; } + byte AV_SPD { get; set; } +} + +public static partial class Extensions +{ + /// + /// Sums all values. + /// + /// Data to sum with + public static int AwakeningSum(this IAwakened pk) => pk.AV_HP + pk.AV_ATK + pk.AV_DEF + pk.AV_SPE + pk.AV_SPA + pk.AV_SPD; + + /// + /// Clears all values. + /// + /// Data to clear from + public static void AwakeningClear(this IAwakened pk) => pk.AwakeningSetAllTo(0); + + /// + /// Sets all values to the maximum value. + /// + /// Data to set values for + public static void AwakeningMax(this IAwakened pk) => pk.AwakeningSetAllTo(Legal.AwakeningMax); + + /// + /// Sets all values to the specified value. + /// + /// Data to set values for + /// Value to set all to + public static void AwakeningSetAllTo(this IAwakened pk, byte value) => pk.AV_HP = pk.AV_ATK = pk.AV_DEF = pk.AV_SPE = pk.AV_SPA = pk.AV_SPD = value; + + /// + /// Sets all values to the specified value. + /// + /// Data to set values for + /// Minimum value to set + /// Maximum value to set + public static void AwakeningSetRandom(this IAwakened pk, byte min = 0, int max = Legal.AwakeningMax) { - byte AV_HP { get; set; } - byte AV_ATK { get; set; } - byte AV_DEF { get; set; } - byte AV_SPE { get; set; } - byte AV_SPA { get; set; } - byte AV_SPD { get; set; } + var rnd = Util.Rand; + int randClamp = max + 1; + for (int index = 0; index < 6; index++) + pk.SetAV(index, (byte)rnd.Next(min, randClamp)); } - public static partial class Extensions + /// + /// Gets if all values are within legal limits. + /// + /// Data to check + public static bool AwakeningAllValid(this IAwakened pk) { - /// - /// Sums all values. - /// - /// Data to sum with - public static int AwakeningSum(this IAwakened pk) => pk.AV_HP + pk.AV_ATK + pk.AV_DEF + pk.AV_SPE + pk.AV_SPA + pk.AV_SPD; - - /// - /// Clears all values. - /// - /// Data to clear from - public static void AwakeningClear(this IAwakened pk) => pk.AwakeningSetAllTo(0); - - /// - /// Sets all values to the maximum value. - /// - /// Data to set values for - public static void AwakeningMax(this IAwakened pk) => pk.AwakeningSetAllTo(Legal.AwakeningMax); - - /// - /// Sets all values to the specified value. - /// - /// Data to set values for - /// Value to set all to - public static void AwakeningSetAllTo(this IAwakened pk, byte value) => pk.AV_HP = pk.AV_ATK = pk.AV_DEF = pk.AV_SPE = pk.AV_SPA = pk.AV_SPD = value; - - /// - /// Sets all values to the specified value. - /// - /// Data to set values for - /// Minimum value to set - /// Maximum value to set - public static void AwakeningSetRandom(this IAwakened pk, byte min = 0, int max = Legal.AwakeningMax) - { - var rnd = Util.Rand; - int randClamp = max + 1; - for (int index = 0; index < 6; index++) - pk.SetAV(index, (byte)rnd.Next(min, randClamp)); - } - - /// - /// Gets if all values are within legal limits. - /// - /// Data to check - public static bool AwakeningAllValid(this IAwakened pk) - { - if (pk.AV_HP > Legal.AwakeningMax) - return false; - if (pk.AV_ATK > Legal.AwakeningMax) - return false; - if (pk.AV_DEF > Legal.AwakeningMax) - return false; - if (pk.AV_SPE > Legal.AwakeningMax) - return false; - if (pk.AV_SPA > Legal.AwakeningMax) - return false; - if (pk.AV_SPD > Legal.AwakeningMax) - return false; - return true; - } - - /// - /// Sets one of the values based on its index within the array. - /// - /// Pokémon to modify. - /// Index to set to - /// Value to set - public static byte SetAV(this IAwakened pk, int index, byte value) => index switch - { - 0 => pk.AV_HP = value, - 1 => pk.AV_ATK = value, - 2 => pk.AV_DEF = value, - 3 => pk.AV_SPE = value, - 4 => pk.AV_SPA = value, - 5 => pk.AV_SPD = value, - _ => throw new ArgumentOutOfRangeException(nameof(index)), - }; - - /// - /// Sets one of the values based on its index within the array. - /// - /// Pokémon to check. - /// Index to get - public static int GetAV(this IAwakened pk, int index) => index switch - { - 0 => pk.AV_HP, - 1 => pk.AV_ATK, - 2 => pk.AV_DEF, - 3 => pk.AV_SPE, - 4 => pk.AV_SPA, - 5 => pk.AV_SPD, - _ => throw new ArgumentOutOfRangeException(nameof(index)), - }; - - /// - /// Loads the values to the input span. - /// - public static void GetAVs(this IAwakened pk, Span value) - { - if (value.Length != 6) - return; - value[0] = pk.AV_HP; - value[1] = pk.AV_ATK; - value[2] = pk.AV_DEF; - value[3] = pk.AV_SPE; - value[4] = pk.AV_SPA; - value[5] = pk.AV_SPD; - } - - /// - /// Sets the values based on the current . - /// - /// Accessor for setting the values - /// Retriever for IVs - public static void SetSuggestedAwakenedValues(this IAwakened a, PKM pk) - { - a.AV_HP = Legal.AwakeningMax; - a.AV_ATK = pk.IV_ATK == 0 ? (byte)0 : Legal.AwakeningMax; - a.AV_DEF = Legal.AwakeningMax; - a.AV_SPE = pk.IV_SPE == 0 ? (byte)0 : Legal.AwakeningMax; - a.AV_SPA = Legal.AwakeningMax; - a.AV_SPD = Legal.AwakeningMax; - } - - public static bool IsAwakeningBelow(this IAwakened current, IAwakened initial) => !current.IsAwakeningAboveOrEqual(initial); - - public static bool IsAwakeningAboveOrEqual(this IAwakened current, IAwakened initial) - { - if (current.AV_HP < initial.AV_HP) - return false; - if (current.AV_ATK < initial.AV_ATK) - return false; - if (current.AV_DEF < initial.AV_DEF) - return false; - if (current.AV_SPA < initial.AV_SPA) - return false; - if (current.AV_SPD < initial.AV_SPD) - return false; - if (current.AV_SPE < initial.AV_SPE) - return false; - return true; - } + if (pk.AV_HP > Legal.AwakeningMax) + return false; + if (pk.AV_ATK > Legal.AwakeningMax) + return false; + if (pk.AV_DEF > Legal.AwakeningMax) + return false; + if (pk.AV_SPE > Legal.AwakeningMax) + return false; + if (pk.AV_SPA > Legal.AwakeningMax) + return false; + if (pk.AV_SPD > Legal.AwakeningMax) + return false; + return true; } -} \ No newline at end of file + + /// + /// Sets one of the values based on its index within the array. + /// + /// Pokémon to modify. + /// Index to set to + /// Value to set + public static byte SetAV(this IAwakened pk, int index, byte value) => index switch + { + 0 => pk.AV_HP = value, + 1 => pk.AV_ATK = value, + 2 => pk.AV_DEF = value, + 3 => pk.AV_SPE = value, + 4 => pk.AV_SPA = value, + 5 => pk.AV_SPD = value, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + /// + /// Sets one of the values based on its index within the array. + /// + /// Pokémon to check. + /// Index to get + public static int GetAV(this IAwakened pk, int index) => index switch + { + 0 => pk.AV_HP, + 1 => pk.AV_ATK, + 2 => pk.AV_DEF, + 3 => pk.AV_SPE, + 4 => pk.AV_SPA, + 5 => pk.AV_SPD, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + /// + /// Loads the values to the input span. + /// + public static void GetAVs(this IAwakened pk, Span value) + { + if (value.Length != 6) + return; + value[0] = pk.AV_HP; + value[1] = pk.AV_ATK; + value[2] = pk.AV_DEF; + value[3] = pk.AV_SPE; + value[4] = pk.AV_SPA; + value[5] = pk.AV_SPD; + } + + /// + /// Sets the values based on the current . + /// + /// Accessor for setting the values + /// Retriever for IVs + public static void SetSuggestedAwakenedValues(this IAwakened a, PKM pk) + { + a.AV_HP = Legal.AwakeningMax; + a.AV_ATK = pk.IV_ATK == 0 ? (byte)0 : Legal.AwakeningMax; + a.AV_DEF = Legal.AwakeningMax; + a.AV_SPE = pk.IV_SPE == 0 ? (byte)0 : Legal.AwakeningMax; + a.AV_SPA = Legal.AwakeningMax; + a.AV_SPD = Legal.AwakeningMax; + } + + public static bool IsAwakeningBelow(this IAwakened current, IAwakened initial) => !current.IsAwakeningAboveOrEqual(initial); + + public static bool IsAwakeningAboveOrEqual(this IAwakened current, IAwakened initial) + { + if (current.AV_HP < initial.AV_HP) + return false; + if (current.AV_ATK < initial.AV_ATK) + return false; + if (current.AV_DEF < initial.AV_DEF) + return false; + if (current.AV_SPA < initial.AV_SPA) + return false; + if (current.AV_SPD < initial.AV_SPD) + return false; + if (current.AV_SPE < initial.AV_SPE) + return false; + return true; + } +} diff --git a/PKHeX.Core/PKM/Interfaces/IBattleVersion.cs b/PKHeX.Core/PKM/Interfaces/IBattleVersion.cs index 64839e1bc..6a238b1ae 100644 --- a/PKHeX.Core/PKM/Interfaces/IBattleVersion.cs +++ b/PKHeX.Core/PKM/Interfaces/IBattleVersion.cs @@ -1,53 +1,52 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface that exposes a for allowing a Pokémon into ranked battles if it originated from a prior game. +/// +public interface IBattleVersion { /// - /// Interface that exposes a for allowing a Pokémon into ranked battles if it originated from a prior game. + /// Indicates which the Pokémon's moves were reset on. /// - public interface IBattleVersion + byte BattleVersion { get; set; } +} + +public static class BattleVersionExtensions +{ + public static bool IsBattleVersionValid(this T pk, EvolutionHistory h) where T : PKM, IBattleVersion => pk.BattleVersion switch { - /// - /// Indicates which the Pokémon's moves were reset on. - /// - byte BattleVersion { get; set; } + 0 => true, + (int)GameVersion.SW or (int)GameVersion.SH => !(pk.SWSH || pk.BDSP || pk.LA) && pk.HasVisitedSWSH(h.Gen8), + _ => false, + }; + + /// + /// Resets the 's moves and sets the requested version. + /// + /// Reference to the object to set the + /// Reference to the same object that gets moves reset + /// Version to apply + public static void AdaptToBattleVersion(this IBattleVersion v, PKM pk, GameVersion version) + { + var moves = MoveLevelUp.GetEncounterMoves(pk, pk.CurrentLevel, version); + pk.Move1 = pk.Move2 = pk.Move3 = pk.Move4 = 0; + pk.RelearnMove1 = pk.RelearnMove2 = pk.RelearnMove3 = pk.RelearnMove4 = 0; + pk.SetMoves(moves); + pk.FixMoves(); + v.BattleVersion = (byte) version; } - public static class BattleVersionExtensions + public static int GetMinGeneration(this IBattleVersion v) { - public static bool IsBattleVersionValid(this T pk, EvolutionHistory h) where T : PKM, IBattleVersion => pk.BattleVersion switch - { - 0 => true, - (int)GameVersion.SW or (int)GameVersion.SH => !(pk.SWSH || pk.BDSP || pk.LA) && pk.HasVisitedSWSH(h.Gen8), - _ => false, - }; - - /// - /// Resets the 's moves and sets the requested version. - /// - /// Reference to the object to set the - /// Reference to the same object that gets moves reset - /// Version to apply - public static void AdaptToBattleVersion(this IBattleVersion v, PKM pk, GameVersion version) - { - var moves = MoveLevelUp.GetEncounterMoves(pk, pk.CurrentLevel, version); - pk.Move1 = pk.Move2 = pk.Move3 = pk.Move4 = 0; - pk.RelearnMove1 = pk.RelearnMove2 = pk.RelearnMove3 = pk.RelearnMove4 = 0; - pk.SetMoves(moves); - pk.FixMoves(); - v.BattleVersion = (byte) version; - } - - public static int GetMinGeneration(this IBattleVersion v) - { - var ver = v.BattleVersion; - if (ver == 0) - return 1; - var game = (GameVersion) ver; - if (!game.IsValidSavedVersion()) - return -1; - var gen = game.GetGeneration(); - if (gen >= 8) - return gen; + var ver = v.BattleVersion; + if (ver == 0) + return 1; + var game = (GameVersion) ver; + if (!game.IsValidSavedVersion()) return -1; - } + var gen = game.GetGeneration(); + if (gen >= 8) + return gen; + return -1; } } diff --git a/PKHeX.Core/PKM/Interfaces/ICaughtData2.cs b/PKHeX.Core/PKM/Interfaces/ICaughtData2.cs index 9ca1661a9..4280130c0 100644 --- a/PKHeX.Core/PKM/Interfaces/ICaughtData2.cs +++ b/PKHeX.Core/PKM/Interfaces/ICaughtData2.cs @@ -1,14 +1,13 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 2 met data interface for details introduced by +/// +public interface ICaughtData2 { - /// - /// Generation 2 met data interface for details introduced by - /// - public interface ICaughtData2 - { - ushort CaughtData { get; set; } - int Met_TimeOfDay { get; set; } - int Met_Level { get; set; } - int OT_Gender { get; set; } - int Met_Location { get; set; } - } + ushort CaughtData { get; set; } + int Met_TimeOfDay { get; set; } + int Met_Level { get; set; } + int OT_Gender { get; set; } + int Met_Location { get; set; } } diff --git a/PKHeX.Core/PKM/Interfaces/IContestStats.cs b/PKHeX.Core/PKM/Interfaces/IContestStats.cs index 178183cf2..386ca533b 100644 --- a/PKHeX.Core/PKM/Interfaces/IContestStats.cs +++ b/PKHeX.Core/PKM/Interfaces/IContestStats.cs @@ -1,116 +1,115 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IContestStats { - public interface IContestStats - { - byte CNT_Cool { get; } - byte CNT_Beauty { get; } - byte CNT_Cute { get; } - byte CNT_Smart { get; } - byte CNT_Tough { get; } - byte CNT_Sheen { get; } - } - - public interface IContestStatsMutable - { - byte CNT_Cool { set; } - byte CNT_Beauty { set; } - byte CNT_Cute { set; } - byte CNT_Smart { set; } - byte CNT_Tough { set; } - byte CNT_Sheen { set; } - } - - public static partial class Extensions - { - /// - /// Checks if any contest stat value is nonzero. - /// - /// Object containing contest stat data. - /// True if has any nonzero contest stat, false if all are zero. - public static bool HasContestStats(this IContestStats stats) - { - if (stats.CNT_Cool != 0) - return true; - if (stats.CNT_Beauty != 0) - return true; - if (stats.CNT_Cute != 0) - return true; - if (stats.CNT_Smart != 0) - return true; - if (stats.CNT_Tough != 0) - return true; - if (stats.CNT_Sheen != 0) - return true; - return false; - } - - public static bool IsContestBelow(this IContestStats current, IContestStats initial) => !current.IsContestAboveOrEqual(initial); - - public static bool IsContestAboveOrEqual(this IContestStats current, IContestStats initial) - { - if (current.CNT_Cool < initial.CNT_Cool) - return false; - if (current.CNT_Beauty < initial.CNT_Beauty) - return false; - if (current.CNT_Cute < initial.CNT_Cute) - return false; - if (current.CNT_Smart < initial.CNT_Smart) - return false; - if (current.CNT_Tough < initial.CNT_Tough) - return false; - if (current.CNT_Sheen < initial.CNT_Sheen) - return false; - return true; - } - - public static bool IsContestEqual(this IContestStats current, IContestStats initial) - { - if (current.CNT_Cool != initial.CNT_Cool) - return false; - if (current.CNT_Beauty != initial.CNT_Beauty) - return false; - if (current.CNT_Cute != initial.CNT_Cute) - return false; - if (current.CNT_Smart != initial.CNT_Smart) - return false; - if (current.CNT_Tough != initial.CNT_Tough) - return false; - if (current.CNT_Sheen != initial.CNT_Sheen) - return false; - return true; - } - - public static void CopyContestStatsTo(this IContestStats source, IContestStatsMutable dest) - { - dest.CNT_Cool = source.CNT_Cool; - dest.CNT_Beauty = source.CNT_Beauty; - dest.CNT_Cute = source.CNT_Cute; - dest.CNT_Smart = source.CNT_Smart; - dest.CNT_Tough = source.CNT_Tough; - dest.CNT_Sheen = source.CNT_Sheen; - } - - public static void SetAllContestStatsTo(this IContestStatsMutable dest, byte value, byte sheen) - { - dest.CNT_Cool = value; - dest.CNT_Beauty = value; - dest.CNT_Cute = value; - dest.CNT_Smart = value; - dest.CNT_Tough = value; - dest.CNT_Sheen = sheen; - } - - private const byte CONTEST_MAX = 255; - - /// - /// Check if any contest stat besides is equal to . - /// - /// Entity to check - /// True if any equals - public static bool IsAnyContestStatMax(this IContestStats s) => CONTEST_MAX == s.CNT_Cool - || CONTEST_MAX == s.CNT_Beauty - || CONTEST_MAX == s.CNT_Cute - || CONTEST_MAX == s.CNT_Smart - || CONTEST_MAX == s.CNT_Tough; - } + byte CNT_Cool { get; } + byte CNT_Beauty { get; } + byte CNT_Cute { get; } + byte CNT_Smart { get; } + byte CNT_Tough { get; } + byte CNT_Sheen { get; } +} + +public interface IContestStatsMutable +{ + byte CNT_Cool { set; } + byte CNT_Beauty { set; } + byte CNT_Cute { set; } + byte CNT_Smart { set; } + byte CNT_Tough { set; } + byte CNT_Sheen { set; } +} + +public static partial class Extensions +{ + /// + /// Checks if any contest stat value is nonzero. + /// + /// Object containing contest stat data. + /// True if has any nonzero contest stat, false if all are zero. + public static bool HasContestStats(this IContestStats stats) + { + if (stats.CNT_Cool != 0) + return true; + if (stats.CNT_Beauty != 0) + return true; + if (stats.CNT_Cute != 0) + return true; + if (stats.CNT_Smart != 0) + return true; + if (stats.CNT_Tough != 0) + return true; + if (stats.CNT_Sheen != 0) + return true; + return false; + } + + public static bool IsContestBelow(this IContestStats current, IContestStats initial) => !current.IsContestAboveOrEqual(initial); + + public static bool IsContestAboveOrEqual(this IContestStats current, IContestStats initial) + { + if (current.CNT_Cool < initial.CNT_Cool) + return false; + if (current.CNT_Beauty < initial.CNT_Beauty) + return false; + if (current.CNT_Cute < initial.CNT_Cute) + return false; + if (current.CNT_Smart < initial.CNT_Smart) + return false; + if (current.CNT_Tough < initial.CNT_Tough) + return false; + if (current.CNT_Sheen < initial.CNT_Sheen) + return false; + return true; + } + + public static bool IsContestEqual(this IContestStats current, IContestStats initial) + { + if (current.CNT_Cool != initial.CNT_Cool) + return false; + if (current.CNT_Beauty != initial.CNT_Beauty) + return false; + if (current.CNT_Cute != initial.CNT_Cute) + return false; + if (current.CNT_Smart != initial.CNT_Smart) + return false; + if (current.CNT_Tough != initial.CNT_Tough) + return false; + if (current.CNT_Sheen != initial.CNT_Sheen) + return false; + return true; + } + + public static void CopyContestStatsTo(this IContestStats source, IContestStatsMutable dest) + { + dest.CNT_Cool = source.CNT_Cool; + dest.CNT_Beauty = source.CNT_Beauty; + dest.CNT_Cute = source.CNT_Cute; + dest.CNT_Smart = source.CNT_Smart; + dest.CNT_Tough = source.CNT_Tough; + dest.CNT_Sheen = source.CNT_Sheen; + } + + public static void SetAllContestStatsTo(this IContestStatsMutable dest, byte value, byte sheen) + { + dest.CNT_Cool = value; + dest.CNT_Beauty = value; + dest.CNT_Cute = value; + dest.CNT_Smart = value; + dest.CNT_Tough = value; + dest.CNT_Sheen = sheen; + } + + private const byte CONTEST_MAX = 255; + + /// + /// Check if any contest stat besides is equal to . + /// + /// Entity to check + /// True if any equals + public static bool IsAnyContestStatMax(this IContestStats s) => CONTEST_MAX == s.CNT_Cool + || CONTEST_MAX == s.CNT_Beauty + || CONTEST_MAX == s.CNT_Cute + || CONTEST_MAX == s.CNT_Smart + || CONTEST_MAX == s.CNT_Tough; } diff --git a/PKHeX.Core/PKM/Interfaces/IDynamaxLevel.cs b/PKHeX.Core/PKM/Interfaces/IDynamaxLevel.cs index 4d1c5f14c..351264e72 100644 --- a/PKHeX.Core/PKM/Interfaces/IDynamaxLevel.cs +++ b/PKHeX.Core/PKM/Interfaces/IDynamaxLevel.cs @@ -1,35 +1,34 @@ using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Dynamax Level used by format entity data. +/// +public interface IDynamaxLevel +{ + byte DynamaxLevel { get; set; } +} + +public static class DynamaxLevelExtensions { /// - /// Dynamax Level used by format entity data. + /// Checks if the species is allowed to have a non-zero value for . /// - public interface IDynamaxLevel + public static bool CanHaveDynamaxLevel(this IDynamaxLevel _, PKM pk) { - byte DynamaxLevel { get; set; } + if (pk.IsEgg) + return false; + return pk is PK8 && CanHaveDynamaxLevel(pk.Species); } - public static class DynamaxLevelExtensions + public static byte GetSuggestedDynamaxLevel(this IDynamaxLevel _, PKM pk) => _.CanHaveDynamaxLevel(pk) ? (byte)10 : (byte)0; + + /// + /// Checks if the species is prevented from gaining any via candy in . + /// + private static bool CanHaveDynamaxLevel(int species) { - /// - /// Checks if the species is allowed to have a non-zero value for . - /// - public static bool CanHaveDynamaxLevel(this IDynamaxLevel _, PKM pk) - { - if (pk.IsEgg) - return false; - return pk is PK8 && CanHaveDynamaxLevel(pk.Species); - } - - public static byte GetSuggestedDynamaxLevel(this IDynamaxLevel _, PKM pk) => _.CanHaveDynamaxLevel(pk) ? (byte)10 : (byte)0; - - /// - /// Checks if the species is prevented from gaining any via candy in . - /// - private static bool CanHaveDynamaxLevel(int species) - { - return species is not ((int)Zacian or (int)Zamazenta or (int)Eternatus); - } + return species is not ((int)Zacian or (int)Zamazenta or (int)Eternatus); } } diff --git a/PKHeX.Core/PKM/Interfaces/IFavorite.cs b/PKHeX.Core/PKM/Interfaces/IFavorite.cs index 9eee7e581..18276e5bf 100644 --- a/PKHeX.Core/PKM/Interfaces/IFavorite.cs +++ b/PKHeX.Core/PKM/Interfaces/IFavorite.cs @@ -1,13 +1,12 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Can mark as a "Favorite" in +/// +public interface IFavorite { /// - /// Can mark as a "Favorite" in + /// Marked as a "Favorite" in /// - public interface IFavorite - { - /// - /// Marked as a "Favorite" in - /// - bool Favorite { get; set; } - } -} \ No newline at end of file + bool Favorite { get; set; } +} diff --git a/PKHeX.Core/PKM/Interfaces/IFixedOTFriendship.cs b/PKHeX.Core/PKM/Interfaces/IFixedOTFriendship.cs index 47cec2a54..0e6e990eb 100644 --- a/PKHeX.Core/PKM/Interfaces/IFixedOTFriendship.cs +++ b/PKHeX.Core/PKM/Interfaces/IFixedOTFriendship.cs @@ -1,10 +1,9 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Exposes a friendship value with the original trainer. +/// +public interface IFixedOTFriendship { - /// - /// Exposes a friendship value with the original trainer. - /// - public interface IFixedOTFriendship - { - byte OT_Friendship { get; } - } + byte OT_Friendship { get; } } diff --git a/PKHeX.Core/PKM/Interfaces/IFormArgument.cs b/PKHeX.Core/PKM/Interfaces/IFormArgument.cs index 64799a745..215441d57 100644 --- a/PKHeX.Core/PKM/Interfaces/IFormArgument.cs +++ b/PKHeX.Core/PKM/Interfaces/IFormArgument.cs @@ -1,139 +1,138 @@ using System; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Alternate form data has an associated value. +/// +/// +/// How long (days) the form can last before reverting to Form-0 (5 days max) +/// : How long (days) the form can last before reverting to Form-0 (3 days max) +/// : Topping (Strawberry, Star, etc); [0,7] +/// How much damage the Pokémon has taken as Yamask-1 [0,9999]. +/// How much damage the Pokémon has taken as Yamask-1 [0,9999]. +/// How many times the Pokémon has used Psyshield Bash in the Agile Style [0,9999]. +/// How many times the Pokémon has used Barb Barrage in the Strong Style as Qwilfish-1 [0,9999]. +/// How much damage the Pokémon has taken through recoil as Basculin-2 [0,9999]. +/// +public interface IFormArgument { /// - /// Alternate form data has an associated value. + /// Argument for the associated /// - /// - /// How long (days) the form can last before reverting to Form-0 (5 days max) - /// : How long (days) the form can last before reverting to Form-0 (3 days max) - /// : Topping (Strawberry, Star, etc); [0,7] - /// How much damage the Pokémon has taken as Yamask-1 [0,9999]. - /// How much damage the Pokémon has taken as Yamask-1 [0,9999]. - /// How many times the Pokémon has used Psyshield Bash in the Agile Style [0,9999]. - /// How many times the Pokémon has used Barb Barrage in the Strong Style as Qwilfish-1 [0,9999]. - /// How much damage the Pokémon has taken through recoil as Basculin-2 [0,9999]. - /// - public interface IFormArgument + uint FormArgument { get; set; } + + /// + /// Amount of days the timed will remain active for. + /// + byte FormArgumentRemain { get; set; } + + /// + /// Amount of days the timed has been active for. + /// + byte FormArgumentElapsed { get; set; } + + /// + /// Maximum amount of days the has maintained a without reverting to its base form. + /// + byte FormArgumentMaximum { get; set; } +} + +/// +/// Logic for mutating an object. +/// +public static class FormArgumentUtil +{ + /// + /// Sets the suggested Form Argument to the . + /// + public static void SetSuggestedFormArgument(this PKM pk, int originalSpecies = 0) { - /// - /// Argument for the associated - /// - uint FormArgument { get; set; } - - /// - /// Amount of days the timed will remain active for. - /// - byte FormArgumentRemain { get; set; } - - /// - /// Amount of days the timed has been active for. - /// - byte FormArgumentElapsed { get; set; } - - /// - /// Maximum amount of days the has maintained a without reverting to its base form. - /// - byte FormArgumentMaximum { get; set; } + if (pk is not IFormArgument) + return; + if (!IsFormArgumentTypeDatePair(pk.Species, pk.Form)) + { + uint suggest = originalSpecies switch + { + (int) Yamask when pk.Species == (int) Runerigus => 49u, + (int) Qwilfish when pk.Species == (int) Overqwil => 20u, + (int) Stantler when pk.Species == (int) Wyrdeer => 20u, + (int) Basculin when pk.Species == (int) Basculegion => 294u, + _ => 0u, + }; + pk.ChangeFormArgument(suggest); + return; + } + var max = GetFormArgumentMax(pk.Species, pk.Form, pk.Format); + pk.ChangeFormArgument(max); } /// - /// Logic for mutating an object. + /// Modifies the values for the provided to the requested . /// - public static class FormArgumentUtil + public static void ChangeFormArgument(this PKM pk, uint value) { - /// - /// Sets the suggested Form Argument to the . - /// - public static void SetSuggestedFormArgument(this PKM pkm, int originalSpecies = 0) + if (pk is not IFormArgument f) + return; + f.ChangeFormArgument(pk.Species, pk.Form, pk.Format, value); + } + + /// + /// Modifies the values for the provided inputs to the requested . + /// + /// Form Argument object + /// Entity Species + /// Entity Species + /// Entity current format generation + /// Value to apply + public static void ChangeFormArgument(this IFormArgument f, int species, int form, int generation, uint value) + { + if (!IsFormArgumentTypeDatePair(species, form)) { - if (pkm is not IFormArgument) - return; - if (!IsFormArgumentTypeDatePair(pkm.Species, pkm.Form)) - { - uint suggest = originalSpecies switch - { - (int) Yamask when pkm.Species == (int) Runerigus => 49u, - (int) Qwilfish when pkm.Species == (int) Overqwil => 20u, - (int) Stantler when pkm.Species == (int) Wyrdeer => 20u, - (int) Basculin when pkm.Species == (int) Basculegion => 294u, - _ => 0u, - }; - pkm.ChangeFormArgument(suggest); - return; - } - var max = GetFormArgumentMax(pkm.Species, pkm.Form, pkm.Format); - pkm.ChangeFormArgument(max); + f.FormArgument = value; + return; } - /// - /// Modifies the values for the provided to the requested . - /// - public static void ChangeFormArgument(this PKM pkm, uint value) + var max = GetFormArgumentMax(species, form, generation); + f.FormArgumentRemain = (byte)value; + if (value == max) { - if (pkm is not IFormArgument f) - return; - f.ChangeFormArgument(pkm.Species, pkm.Form, pkm.Format, value); + f.FormArgumentElapsed = f.FormArgumentMaximum = 0; + return; } - /// - /// Modifies the values for the provided inputs to the requested . - /// - /// Form Argument object - /// Entity Species - /// Entity Species - /// Entity current format generation - /// Value to apply - public static void ChangeFormArgument(this IFormArgument f, int species, int form, int generation, uint value) + byte elapsed = max < value ? (byte)0 : (byte)(max - value); + f.FormArgumentElapsed = elapsed; + if (species == (int)Furfrou) + f.FormArgumentMaximum = Math.Max(f.FormArgumentMaximum, elapsed); + } + + public static uint GetFormArgumentMax(int species, int form, int generation) + { + if (generation <= 5) + return 0; + + return species switch { - if (!IsFormArgumentTypeDatePair(species, form)) - { - f.FormArgument = value; - return; - } - - var max = GetFormArgumentMax(species, form, generation); - f.FormArgumentRemain = (byte)value; - if (value == max) - { - f.FormArgumentElapsed = f.FormArgumentMaximum = 0; - return; - } - - byte elapsed = max < value ? (byte)0 : (byte)(max - value); - f.FormArgumentElapsed = elapsed; - if (species == (int)Furfrou) - f.FormArgumentMaximum = Math.Max(f.FormArgumentMaximum, elapsed); - } - - public static uint GetFormArgumentMax(int species, int form, int generation) - { - if (generation <= 5) - return 0; - - return species switch - { - (int)Furfrou when form != 0 => 5, - (int)Hoopa when form == 1 => 3, - (int)Yamask when form == 1 => 9999, - (int)Runerigus when form == 0 => 9999, - (int)Alcremie => (uint)AlcremieDecoration.Ribbon, - (int)Qwilfish when form == 1 && generation == 8 => 9999, // 20 - (int)Overqwil => 9999, // 20 - (int)Stantler or (int)Wyrdeer when generation == 8 => 9999, // 20 - (int)Basculin when form == 2 => 9999, // 294 - (int)Basculegion => 9999, // 294 - _ => 0, - }; - } - - public static bool IsFormArgumentTypeDatePair(int species, int form) => species switch - { - (int)Furfrou when form != 0 => true, - (int)Hoopa when form == 1 => true, - _ => false, + (int)Furfrou when form != 0 => 5, + (int)Hoopa when form == 1 => 3, + (int)Yamask when form == 1 => 9999, + (int)Runerigus when form == 0 => 9999, + (int)Alcremie => (uint)AlcremieDecoration.Ribbon, + (int)Qwilfish when form == 1 && generation == 8 => 9999, // 20 + (int)Overqwil => 9999, // 20 + (int)Stantler or (int)Wyrdeer when generation == 8 => 9999, // 20 + (int)Basculin when form == 2 => 9999, // 294 + (int)Basculegion => 9999, // 294 + _ => 0, }; } + + public static bool IsFormArgumentTypeDatePair(int species, int form) => species switch + { + (int)Furfrou when form != 0 => true, + (int)Hoopa when form == 1 => true, + _ => false, + }; } diff --git a/PKHeX.Core/PKM/Interfaces/IGameValueLimit.cs b/PKHeX.Core/PKM/Interfaces/IGameValueLimit.cs index b3df483a6..9cd90f5b4 100644 --- a/PKHeX.Core/PKM/Interfaces/IGameValueLimit.cs +++ b/PKHeX.Core/PKM/Interfaces/IGameValueLimit.cs @@ -1,20 +1,19 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Metadata indicating the maximums (and minimums) a type of value can be. +/// +public interface IGameValueLimit { - /// - /// Metadata indicating the maximums (and minimums) a type of value can be. - /// - public interface IGameValueLimit - { - int MaxMoveID { get; } - int MaxSpeciesID { get; } - int MaxItemID { get; } - int MaxAbilityID { get; } - int MaxBallID { get; } - int MaxGameID { get; } - int MinGameID { get; } - int MaxIV { get; } - int MaxEV { get; } - int OTLength { get; } - int NickLength { get; } - } + int MaxMoveID { get; } + int MaxSpeciesID { get; } + int MaxItemID { get; } + int MaxAbilityID { get; } + int MaxBallID { get; } + int MaxGameID { get; } + int MinGameID { get; } + int MaxIV { get; } + int MaxEV { get; } + int OTLength { get; } + int NickLength { get; } } diff --git a/PKHeX.Core/PKM/Interfaces/IGeneration.cs b/PKHeX.Core/PKM/Interfaces/IGeneration.cs index 32c616d17..2a9c90975 100644 --- a/PKHeX.Core/PKM/Interfaces/IGeneration.cs +++ b/PKHeX.Core/PKM/Interfaces/IGeneration.cs @@ -1,13 +1,12 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface that exposes a to see which canonical generation the data originated in. +/// +public interface IGeneration { /// - /// Interface that exposes a to see which canonical generation the data originated in. + /// The canonical generation the data originated in. /// - public interface IGeneration - { - /// - /// The canonical generation the data originated in. - /// - int Generation { get; } - } + int Generation { get; } } diff --git a/PKHeX.Core/PKM/Interfaces/IGeoTrack.cs b/PKHeX.Core/PKM/Interfaces/IGeoTrack.cs index 37a39690e..2638999c4 100644 --- a/PKHeX.Core/PKM/Interfaces/IGeoTrack.cs +++ b/PKHeX.Core/PKM/Interfaces/IGeoTrack.cs @@ -1,128 +1,127 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Tracks Geolocation history of a +/// +public interface IGeoTrack : IRegionOrigin { - /// - /// Tracks Geolocation history of a - /// - public interface IGeoTrack : IRegionOrigin + byte Geo1_Region { get; set; } + byte Geo2_Region { get; set; } + byte Geo3_Region { get; set; } + byte Geo4_Region { get; set; } + byte Geo5_Region { get; set; } + byte Geo1_Country { get; set; } + byte Geo2_Country { get; set; } + byte Geo3_Country { get; set; } + byte Geo4_Country { get; set; } + byte Geo5_Country { get; set; } +} + +public static partial class Extensions +{ + public static void ClearGeoLocationData(this IGeoTrack g) { - byte Geo1_Region { get; set; } - byte Geo2_Region { get; set; } - byte Geo3_Region { get; set; } - byte Geo4_Region { get; set; } - byte Geo5_Region { get; set; } - byte Geo1_Country { get; set; } - byte Geo2_Country { get; set; } - byte Geo3_Country { get; set; } - byte Geo4_Country { get; set; } - byte Geo5_Country { get; set; } + g.Geo1_Country = g.Geo2_Country = g.Geo3_Country = g.Geo4_Country = g.Geo5_Country = 0; + g.Geo1_Region = g.Geo2_Region = g.Geo3_Region = g.Geo4_Region = g.Geo5_Region = 0; } - public static partial class Extensions + public static void TradeGeoLocation(this IGeoTrack g, byte GeoCountry, byte GeoRegion) { - public static void ClearGeoLocationData(this IGeoTrack g) + // Trickle existing values up one slot + g.Geo5_Country = g.Geo4_Country; + g.Geo5_Region = g.Geo4_Region; + + g.Geo4_Country = g.Geo3_Country; + g.Geo4_Region = g.Geo3_Region; + + g.Geo3_Country = g.Geo2_Country; + g.Geo3_Region = g.Geo2_Region; + + g.Geo2_Country = g.Geo1_Country; + g.Geo2_Region = g.Geo1_Region; + + g.Geo1_Country = GeoCountry; + g.Geo1_Region = GeoRegion; + } + + public static void SanitizeGeoLocationData(this IGeoTrack g) + { + if (g.Geo1_Country == 0) g.Geo1_Region = 0; + if (g.Geo2_Country == 0) g.Geo2_Region = 0; + if (g.Geo3_Country == 0) g.Geo3_Region = 0; + if (g.Geo4_Country == 0) g.Geo4_Region = 0; + if (g.Geo5_Country == 0) g.Geo5_Region = 0; + + // trickle down empty slots + while (true) { - g.Geo1_Country = g.Geo2_Country = g.Geo3_Country = g.Geo4_Country = g.Geo5_Country = 0; - g.Geo1_Region = g.Geo2_Region = g.Geo3_Region = g.Geo4_Region = g.Geo5_Region = 0; - } - - public static void TradeGeoLocation(this IGeoTrack g, byte GeoCountry, byte GeoRegion) - { - // Trickle existing values up one slot - g.Geo5_Country = g.Geo4_Country; - g.Geo5_Region = g.Geo4_Region; - - g.Geo4_Country = g.Geo3_Country; - g.Geo4_Region = g.Geo3_Region; - - g.Geo3_Country = g.Geo2_Country; - g.Geo3_Region = g.Geo2_Region; - - g.Geo2_Country = g.Geo1_Country; - g.Geo2_Region = g.Geo1_Region; - - g.Geo1_Country = GeoCountry; - g.Geo1_Region = GeoRegion; - } - - public static void SanitizeGeoLocationData(this IGeoTrack g) - { - if (g.Geo1_Country == 0) g.Geo1_Region = 0; - if (g.Geo2_Country == 0) g.Geo2_Region = 0; - if (g.Geo3_Country == 0) g.Geo3_Region = 0; - if (g.Geo4_Country == 0) g.Geo4_Region = 0; - if (g.Geo5_Country == 0) g.Geo5_Region = 0; - - // trickle down empty slots - while (true) + if (g.Geo5_Country != 0 && g.Geo4_Country == 0) { - if (g.Geo5_Country != 0 && g.Geo4_Country == 0) - { - g.Geo4_Country = g.Geo5_Country; - g.Geo4_Region = g.Geo5_Region; - g.Geo5_Country = g.Geo5_Region = 0; - } - if (g.Geo4_Country != 0 && g.Geo3_Country == 0) - { - g.Geo3_Country = g.Geo4_Country; - g.Geo3_Region = g.Geo4_Region; - g.Geo4_Country = g.Geo4_Region = 0; - continue; - } - if (g.Geo3_Country != 0 && g.Geo2_Country == 0) - { - g.Geo2_Country = g.Geo3_Country; - g.Geo2_Region = g.Geo3_Region; - g.Geo3_Country = g.Geo3_Region = 0; - continue; - } - if (g.Geo2_Country != 0 && g.Geo1_Country == 0) - { - g.Geo1_Country = g.Geo2_Country; - g.Geo1_Region = g.Geo2_Region; - g.Geo2_Country = g.Geo2_Region = 0; - continue; - } - break; + g.Geo4_Country = g.Geo5_Country; + g.Geo4_Region = g.Geo5_Region; + g.Geo5_Country = g.Geo5_Region = 0; } + if (g.Geo4_Country != 0 && g.Geo3_Country == 0) + { + g.Geo3_Country = g.Geo4_Country; + g.Geo3_Region = g.Geo4_Region; + g.Geo4_Country = g.Geo4_Region = 0; + continue; + } + if (g.Geo3_Country != 0 && g.Geo2_Country == 0) + { + g.Geo2_Country = g.Geo3_Country; + g.Geo2_Region = g.Geo3_Region; + g.Geo3_Country = g.Geo3_Region = 0; + continue; + } + if (g.Geo2_Country != 0 && g.Geo1_Country == 0) + { + g.Geo1_Country = g.Geo2_Country; + g.Geo1_Region = g.Geo2_Region; + g.Geo2_Country = g.Geo2_Region = 0; + continue; + } + break; } + } - public static bool GetIsValid(this IGeoTrack g) => g.GetValidity() == GeoValid.Valid; - - internal static GeoValid GetValidity(this IGeoTrack g) - { - bool end = false; - GeoValid result; - if ((result = update(g.Geo1_Country, g.Geo1_Region)) != GeoValid.Valid) - return result; - if ((result = update(g.Geo2_Country, g.Geo2_Region)) != GeoValid.Valid) - return result; - if ((result = update(g.Geo3_Country, g.Geo3_Region)) != GeoValid.Valid) - return result; - if ((result = update(g.Geo4_Country, g.Geo4_Region)) != GeoValid.Valid) - return result; - if ((result = update(g.Geo5_Country, g.Geo5_Region)) != GeoValid.Valid) - return result; + public static bool GetIsValid(this IGeoTrack g) => g.GetValidity() == GeoValid.Valid; + internal static GeoValid GetValidity(this IGeoTrack g) + { + bool end = false; + GeoValid result; + if ((result = update(g.Geo1_Country, g.Geo1_Region)) != GeoValid.Valid) + return result; + if ((result = update(g.Geo2_Country, g.Geo2_Region)) != GeoValid.Valid) + return result; + if ((result = update(g.Geo3_Country, g.Geo3_Region)) != GeoValid.Valid) + return result; + if ((result = update(g.Geo4_Country, g.Geo4_Region)) != GeoValid.Valid) + return result; + if ((result = update(g.Geo5_Country, g.Geo5_Region)) != GeoValid.Valid) return result; - GeoValid update(int c, int r) - { - if (end && c != 0) - return GeoValid.CountryAfterPreviousEmpty; - if (c != 0) - return GeoValid.Valid; - if (r != 0) // c == 0 - return GeoValid.RegionWithoutCountry; - end = true; + return result; + + GeoValid update(int c, int r) + { + if (end && c != 0) + return GeoValid.CountryAfterPreviousEmpty; + if (c != 0) return GeoValid.Valid; - } + if (r != 0) // c == 0 + return GeoValid.RegionWithoutCountry; + end = true; + return GeoValid.Valid; } } - - internal enum GeoValid - { - Valid, - CountryAfterPreviousEmpty, - RegionWithoutCountry, - } +} + +internal enum GeoValid +{ + Valid, + CountryAfterPreviousEmpty, + RegionWithoutCountry, } diff --git a/PKHeX.Core/PKM/Interfaces/IGigantamax.cs b/PKHeX.Core/PKM/Interfaces/IGigantamax.cs index 0dedc7ecc..ca8313187 100644 --- a/PKHeX.Core/PKM/Interfaces/IGigantamax.cs +++ b/PKHeX.Core/PKM/Interfaces/IGigantamax.cs @@ -1,86 +1,85 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface that exposes an indication if the Pokémon can Gigantamax. +/// +public interface IGigantamax { /// - /// Interface that exposes an indication if the Pokémon can Gigantamax. + /// Indicates if the Pokémon is capable of Gigantamax as opposed to regular Dynamax. /// - public interface IGigantamax - { - /// - /// Indicates if the Pokémon is capable of Gigantamax as opposed to regular Dynamax. - /// - bool CanGigantamax { get; set; } - } - - public static class GigantamaxExtensions - { - /// - /// Checks if either of the input Species can consume the Gigantamax soup, toggling the flag. - /// - /// Unnecessary, just needed for extension method usage. - /// The current species - /// The current form of the species - /// The original species (what species it was encountered as) - /// The original form of the original species - /// True if either species can toggle Gigantamax potential - public static bool CanToggleGigantamax(this IGigantamax _, int currentSpecies, int currentForm, int originSpecies, int originForm) - { - if (currentSpecies is (int)Species.Meowth or (int)Species.Pikachu) - return currentForm == 0; - - var soup = CanEatMaxSoup; - return soup.Contains(currentSpecies) || (currentSpecies != originSpecies && soup.Contains(originSpecies)); - } - - /// - /// Don't use this method. Use the other overload with multi-species input. - /// - /// Unnecessary, just needed for extension method usage. - /// The current species - /// The current form of the species - /// True if the species can toggle Gigantamax potential - public static bool CanToggleGigantamax(this IGigantamax _, int currentSpecies, int currentForm) - { - if (currentSpecies is (int)Species.Meowth or (int)Species.Pikachu) - return currentForm == 0; - var soup = CanEatMaxSoup; - return soup.Contains(currentSpecies); - } - - private static readonly HashSet CanEatMaxSoup = new() - { - (int)Species.Venusaur, - (int)Species.Charizard, - (int)Species.Blastoise, - (int)Species.Butterfree, - (int)Species.Pikachu, - (int)Species.Meowth, - (int)Species.Machamp, - (int)Species.Gengar, - (int)Species.Lapras, - (int)Species.Eevee, - (int)Species.Snorlax, - (int)Species.Garbodor, - (int)Species.Rillaboom, - (int)Species.Cinderace, - (int)Species.Inteleon, - (int)Species.Drednaw, - (int)Species.Corviknight, - (int)Species.Toxtricity, - (int)Species.Alcremie, - (int)Species.Duraludon, - (int)Species.Orbeetle, - (int)Species.Coalossal, - (int)Species.Sandaconda, - (int)Species.Grimmsnarl, - (int)Species.Flapple, - (int)Species.Appletun, - (int)Species.Hatterene, - (int)Species.Copperajah, - (int)Species.Kingler, - (int)Species.Centiskorch, - (int)Species.Urshifu, - }; - } + bool CanGigantamax { get; set; } +} + +public static class GigantamaxExtensions +{ + /// + /// Checks if either of the input Species can consume the Gigantamax soup, toggling the flag. + /// + /// Unnecessary, just needed for extension method usage. + /// The current species + /// The current form of the species + /// The original species (what species it was encountered as) + /// The original form of the original species + /// True if either species can toggle Gigantamax potential + public static bool CanToggleGigantamax(this IGigantamax _, int currentSpecies, int currentForm, int originSpecies, int originForm) + { + if (currentSpecies is (int)Species.Meowth or (int)Species.Pikachu) + return currentForm == 0; + + var soup = CanEatMaxSoup; + return soup.Contains(currentSpecies) || (currentSpecies != originSpecies && soup.Contains(originSpecies)); + } + + /// + /// Don't use this method. Use the other overload with multi-species input. + /// + /// Unnecessary, just needed for extension method usage. + /// The current species + /// The current form of the species + /// True if the species can toggle Gigantamax potential + public static bool CanToggleGigantamax(this IGigantamax _, int currentSpecies, int currentForm) + { + if (currentSpecies is (int)Species.Meowth or (int)Species.Pikachu) + return currentForm == 0; + var soup = CanEatMaxSoup; + return soup.Contains(currentSpecies); + } + + private static readonly HashSet CanEatMaxSoup = new() + { + (int)Species.Venusaur, + (int)Species.Charizard, + (int)Species.Blastoise, + (int)Species.Butterfree, + (int)Species.Pikachu, + (int)Species.Meowth, + (int)Species.Machamp, + (int)Species.Gengar, + (int)Species.Lapras, + (int)Species.Eevee, + (int)Species.Snorlax, + (int)Species.Garbodor, + (int)Species.Rillaboom, + (int)Species.Cinderace, + (int)Species.Inteleon, + (int)Species.Drednaw, + (int)Species.Corviknight, + (int)Species.Toxtricity, + (int)Species.Alcremie, + (int)Species.Duraludon, + (int)Species.Orbeetle, + (int)Species.Coalossal, + (int)Species.Sandaconda, + (int)Species.Grimmsnarl, + (int)Species.Flapple, + (int)Species.Appletun, + (int)Species.Hatterene, + (int)Species.Copperajah, + (int)Species.Kingler, + (int)Species.Centiskorch, + (int)Species.Urshifu, + }; } diff --git a/PKHeX.Core/PKM/Interfaces/IGroundTile.cs b/PKHeX.Core/PKM/Interfaces/IGroundTile.cs index 19cb108ce..335c29200 100644 --- a/PKHeX.Core/PKM/Interfaces/IGroundTile.cs +++ b/PKHeX.Core/PKM/Interfaces/IGroundTile.cs @@ -1,13 +1,12 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Indicates the Type of Encounter Tile the Pokémon was encountered on. +/// +public interface IGroundTile { /// - /// Indicates the Type of Encounter Tile the Pokémon was encountered on. + /// Type of Encounter Tile the Pokémon was encountered on. /// - public interface IGroundTile - { - /// - /// Type of Encounter Tile the Pokémon was encountered on. - /// - GroundTileType GroundTile { get; set; } - } + GroundTileType GroundTile { get; set; } } diff --git a/PKHeX.Core/PKM/Interfaces/IHyperTrain.cs b/PKHeX.Core/PKM/Interfaces/IHyperTrain.cs index c98058f1b..5d9a8e1b7 100644 --- a/PKHeX.Core/PKM/Interfaces/IHyperTrain.cs +++ b/PKHeX.Core/PKM/Interfaces/IHyperTrain.cs @@ -1,116 +1,115 @@ -using System; +using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IHyperTrain { - public interface IHyperTrain + byte HyperTrainFlags { get; set; } + bool HT_HP { get; set; } + bool HT_ATK { get; set; } + bool HT_DEF { get; set; } + bool HT_SPA { get; set; } + bool HT_SPD { get; set; } + bool HT_SPE { get; set; } +} + +public static partial class Extensions +{ + /// + /// Toggles the Hyper Training flag for a given stat. + /// + /// Hyper Trainable object + /// Battle Stat (H/A/B/S/C/D) + /// Final Hyper Training Flag value + public static bool HyperTrainInvert(this IHyperTrain t, int index) => index switch { - byte HyperTrainFlags { get; set; } - bool HT_HP { get; set; } - bool HT_ATK { get; set; } - bool HT_DEF { get; set; } - bool HT_SPA { get; set; } - bool HT_SPD { get; set; } - bool HT_SPE { get; set; } + 0 => t.HT_HP ^= true, + 1 => t.HT_ATK ^= true, + 2 => t.HT_DEF ^= true, + 3 => t.HT_SPE ^= true, + 4 => t.HT_SPA ^= true, + 5 => t.HT_SPD ^= true, + _ => false, + }; + + public static bool IsHyperTrainedAll(this IHyperTrain t) => t.HyperTrainFlags == 0x3F; + public static void HyperTrainClear(this IHyperTrain t) => t.HyperTrainFlags = 0; + public static bool IsHyperTrained(this IHyperTrain t) => t.HyperTrainFlags != 0; + + /// + /// Gets one of the values based on its index within the array. + /// + /// Entity to check. + /// Index to get + public static bool IsHyperTrained(this IHyperTrain t, int index) => index switch + { + 0 => t.HT_HP, + 1 => t.HT_ATK, + 2 => t.HT_DEF, + 3 => t.HT_SPE, + 4 => t.HT_SPA, + 5 => t.HT_SPD, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + /// + /// Sets to valid values which may best enhance the stats. + /// + /// + /// History of evolutions present as + /// to use (if already known). Will fetch the current if not provided. + public static void SetSuggestedHyperTrainingData(this PKM pk, EvolutionHistory h, ReadOnlySpan IVs) + { + if (pk is not IHyperTrain t) + return; + if (!pk.IsHyperTrainingAvailable(h)) + { + t.HyperTrainFlags = 0; + return; + } + + t.HT_HP = IVs[0] != 31; + t.HT_ATK = IVs[1] != 31 && IVs[1] > 2; + t.HT_DEF = IVs[2] != 31; + t.HT_SPA = IVs[4] != 31; + t.HT_SPD = IVs[5] != 31; + + // sometimes unusual speed IVs are desirable for competitive reasons + // if nothing else was HT'd and the IV isn't too high, it was probably intentional + t.HT_SPE = IVs[3] != 31 && IVs[3] > 2 && + (IVs[3] > 17 || t.HT_HP || t.HT_ATK || t.HT_DEF || t.HT_SPA || t.HT_SPD); + + if (pk is ICombatPower pb) + pb.ResetCP(); } - public static partial class Extensions + /// + public static void SetSuggestedHyperTrainingData(this PKM pk, int[]? IVs = null) => pk.SetSuggestedHyperTrainingData(EvolutionHistory.Empty, IVs ?? pk.IVs); + + /// + /// Indicates if Hyper Training is available for toggling. + /// + /// Entity to train + /// History of evolutions present as + /// True if available, otherwise false. + public static bool IsHyperTrainingAvailable(this IHyperTrain t, EvolutionHistory h) => t switch { - /// - /// Toggles the Hyper Training flag for a given stat. - /// - /// Hyper Trainable object - /// Battle Stat (H/A/B/S/C/D) - /// Final Hyper Training Flag value - public static bool HyperTrainInvert(this IHyperTrain t, int index) => index switch - { - 0 => t.HT_HP ^= true, - 1 => t.HT_ATK ^= true, - 2 => t.HT_DEF ^= true, - 3 => t.HT_SPE ^= true, - 4 => t.HT_SPA ^= true, - 5 => t.HT_SPD ^= true, - _ => false, - }; + // Check for game formats where training is unavailable: + PA8 pa8 => h.Gen7.Length > 0 || pa8.HasVisitedSWSH(h.Gen8) || pa8.HasVisitedBDSP(h.Gen8b), + _ => true, + }; - public static bool IsHyperTrainedAll(this IHyperTrain t) => t.HyperTrainFlags == 0x3F; - public static void HyperTrainClear(this IHyperTrain t) => t.HyperTrainFlags = 0; - public static bool IsHyperTrained(this IHyperTrain t) => t.HyperTrainFlags != 0; + /// + /// Entity data + /// History of evolutions present as + public static bool IsHyperTrainingAvailable(this PKM pk, EvolutionHistory h) + { + if (pk is not IHyperTrain t) + return false; + if (!t.IsHyperTrainingAvailable(h)) + return false; - /// - /// Gets one of the values based on its index within the array. - /// - /// Entity to check. - /// Index to get - public static bool IsHyperTrained(this IHyperTrain t, int index) => index switch - { - 0 => t.HT_HP, - 1 => t.HT_ATK, - 2 => t.HT_DEF, - 3 => t.HT_SPE, - 4 => t.HT_SPA, - 5 => t.HT_SPD, - _ => throw new ArgumentOutOfRangeException(nameof(index)), - }; - - /// - /// Sets to valid values which may best enhance the stats. - /// - /// - /// History of evolutions present as - /// to use (if already known). Will fetch the current if not provided. - public static void SetSuggestedHyperTrainingData(this PKM pk, EvolutionHistory h, ReadOnlySpan IVs) - { - if (pk is not IHyperTrain t) - return; - if (!pk.IsHyperTrainingAvailable(h)) - { - t.HyperTrainFlags = 0; - return; - } - - t.HT_HP = IVs[0] != 31; - t.HT_ATK = IVs[1] != 31 && IVs[1] > 2; - t.HT_DEF = IVs[2] != 31; - t.HT_SPA = IVs[4] != 31; - t.HT_SPD = IVs[5] != 31; - - // sometimes unusual speed IVs are desirable for competitive reasons - // if nothing else was HT'd and the IV isn't too high, it was probably intentional - t.HT_SPE = IVs[3] != 31 && IVs[3] > 2 && - (IVs[3] > 17 || t.HT_HP || t.HT_ATK || t.HT_DEF || t.HT_SPA || t.HT_SPD); - - if (pk is ICombatPower pb) - pb.ResetCP(); - } - - /// - public static void SetSuggestedHyperTrainingData(this PKM pk, int[]? IVs = null) => pk.SetSuggestedHyperTrainingData(EvolutionHistory.Empty, IVs ?? pk.IVs); - - /// - /// Indicates if Hyper Training is available for toggling. - /// - /// Entity to train - /// History of evolutions present as - /// True if available, otherwise false. - public static bool IsHyperTrainingAvailable(this IHyperTrain t, EvolutionHistory h) => t switch - { - // Check for game formats where training is unavailable: - PA8 pa8 => h.Gen7.Length > 0 || pa8.HasVisitedSWSH(h.Gen8) || pa8.HasVisitedBDSP(h.Gen8b), - _ => true, - }; - - /// - /// Entity data - /// History of evolutions present as - public static bool IsHyperTrainingAvailable(this PKM pk, EvolutionHistory h) - { - if (pk is not IHyperTrain t) - return false; - if (!t.IsHyperTrainingAvailable(h)) - return false; - - // Gated behind level 100. - return pk.CurrentLevel == 100; - } + // Gated behind level 100. + return pk.CurrentLevel == 100; } } diff --git a/PKHeX.Core/PKM/Interfaces/ILangNick.cs b/PKHeX.Core/PKM/Interfaces/ILangNick.cs index f9980d793..b2d95b769 100644 --- a/PKHeX.Core/PKM/Interfaces/ILangNick.cs +++ b/PKHeX.Core/PKM/Interfaces/ILangNick.cs @@ -1,9 +1,8 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +internal interface ILangNick { - internal interface ILangNick - { - string Nickname { get; } - bool IsNicknamed { get; } - int Language { get; } - } + string Nickname { get; } + bool IsNicknamed { get; } + int Language { get; } } diff --git a/PKHeX.Core/PKM/Interfaces/ILangNicknamedTemplate.cs b/PKHeX.Core/PKM/Interfaces/ILangNicknamedTemplate.cs index 3bde681ef..0384bdeae 100644 --- a/PKHeX.Core/PKM/Interfaces/ILangNicknamedTemplate.cs +++ b/PKHeX.Core/PKM/Interfaces/ILangNicknamedTemplate.cs @@ -1,12 +1,11 @@ -namespace PKHeX.Core -{ - internal interface ILangNicknamedTemplate - { - string GetNickname(int language); - bool GetIsNicknamed(int language); +namespace PKHeX.Core; - bool CanBeAnyLanguage(); - bool CanHaveLanguage(int language); - bool CanHandleOT(int language); - } +internal interface ILangNicknamedTemplate +{ + string GetNickname(int language); + bool GetIsNicknamed(int language); + + bool CanBeAnyLanguage(); + bool CanHaveLanguage(int language); + bool CanHandleOT(int language); } diff --git a/PKHeX.Core/PKM/Interfaces/IMemoryHT.cs b/PKHeX.Core/PKM/Interfaces/IMemoryHT.cs index 39d2491ed..a4cc21f7d 100644 --- a/PKHeX.Core/PKM/Interfaces/IMemoryHT.cs +++ b/PKHeX.Core/PKM/Interfaces/IMemoryHT.cs @@ -1,46 +1,45 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Exposes memory details for the Handling Trainer (not OT). +/// +public interface IMemoryHT +{ + byte HT_Memory { get; set; } + ushort HT_TextVar { get; set; } + byte HT_Feeling { get; set; } + byte HT_Intensity { get; set; } +} + +public static partial class Extensions { /// - /// Exposes memory details for the Handling Trainer (not OT). + /// Sets a Link Trade memory to the . /// - public interface IMemoryHT + public static void SetTradeMemoryHT6(this IMemoryHT ht, bool bank) { - byte HT_Memory { get; set; } - ushort HT_TextVar { get; set; } - byte HT_Feeling { get; set; } - byte HT_Intensity { get; set; } + ht.HT_Memory = 4; // Link trade to [VAR: General Location] + ht.HT_TextVar = bank ? (byte)0 : (byte)9; // Somewhere (Bank) : Pokécenter (Trade) + ht.HT_Intensity = 1; + ht.HT_Feeling = MemoryContext6.GetRandomFeeling6(4, bank ? 10 : 20); // 0-9 Bank, 0-19 Trade } - public static partial class Extensions + /// + /// Sets a Link Trade memory to the . + /// + public static void SetTradeMemoryHT8(this IMemoryHT ht) { - /// - /// Sets a Link Trade memory to the . - /// - public static void SetTradeMemoryHT6(this IMemoryHT ht, bool bank) - { - ht.HT_Memory = 4; // Link trade to [VAR: General Location] - ht.HT_TextVar = bank ? (byte)0 : (byte)9; // Somewhere (Bank) : Pokécenter (Trade) - ht.HT_Intensity = 1; - ht.HT_Feeling = MemoryContext6.GetRandomFeeling6(4, bank ? 10 : 20); // 0-9 Bank, 0-19 Trade - } + ht.HT_Memory = 4; // Link trade to [VAR: General Location] + ht.HT_TextVar = 9; // Pokécenter (Trade) + ht.HT_Intensity = 1; + ht.HT_Feeling = MemoryContext8.GetRandomFeeling8(4, 20); // 0-19 Trade + } - /// - /// Sets a Link Trade memory to the . - /// - public static void SetTradeMemoryHT8(this IMemoryHT ht) - { - ht.HT_Memory = 4; // Link trade to [VAR: General Location] - ht.HT_TextVar = 9; // Pokécenter (Trade) - ht.HT_Intensity = 1; - ht.HT_Feeling = MemoryContext8.GetRandomFeeling8(4, 20); // 0-19 Trade - } - - /// - /// Sets all values to zero. - /// - public static void ClearMemoriesHT(this IMemoryHT ht) - { - ht.HT_TextVar = ht.HT_Memory = ht.HT_Feeling = ht.HT_Intensity = 0; - } + /// + /// Sets all values to zero. + /// + public static void ClearMemoriesHT(this IMemoryHT ht) + { + ht.HT_TextVar = ht.HT_Memory = ht.HT_Feeling = ht.HT_Intensity = 0; } } diff --git a/PKHeX.Core/PKM/Interfaces/IMemoryOT.cs b/PKHeX.Core/PKM/Interfaces/IMemoryOT.cs index 059cc3cc9..be4f5dc22 100644 --- a/PKHeX.Core/PKM/Interfaces/IMemoryOT.cs +++ b/PKHeX.Core/PKM/Interfaces/IMemoryOT.cs @@ -1,24 +1,23 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Exposes memory details for the Original Trainer. +/// +public interface IMemoryOT +{ + byte OT_Memory { get; set; } + byte OT_Intensity { get; set; } + byte OT_Feeling { get; set; } + ushort OT_TextVar { get; set; } +} + +public static partial class Extensions { /// - /// Exposes memory details for the Original Trainer. + /// Sets all values to zero. /// - public interface IMemoryOT + public static void ClearMemoriesOT(this IMemoryOT ot) { - byte OT_Memory { get; set; } - byte OT_Intensity { get; set; } - byte OT_Feeling { get; set; } - ushort OT_TextVar { get; set; } - } - - public static partial class Extensions - { - /// - /// Sets all values to zero. - /// - public static void ClearMemoriesOT(this IMemoryOT ot) - { - ot.OT_TextVar = ot.OT_Memory = ot.OT_Feeling = ot.OT_Intensity = 0; - } + ot.OT_TextVar = ot.OT_Memory = ot.OT_Feeling = ot.OT_Intensity = 0; } } diff --git a/PKHeX.Core/PKM/Interfaces/INature.cs b/PKHeX.Core/PKM/Interfaces/INature.cs index 36aa1510d..49ed1f9c0 100644 --- a/PKHeX.Core/PKM/Interfaces/INature.cs +++ b/PKHeX.Core/PKM/Interfaces/INature.cs @@ -1,7 +1,6 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface INature { - public interface INature - { - int Nature { get; set; } - } -} \ No newline at end of file + int Nature { get; set; } +} diff --git a/PKHeX.Core/PKM/Interfaces/IRegionOrigin.cs b/PKHeX.Core/PKM/Interfaces/IRegionOrigin.cs index 6299a832e..e4076f0d2 100644 --- a/PKHeX.Core/PKM/Interfaces/IRegionOrigin.cs +++ b/PKHeX.Core/PKM/Interfaces/IRegionOrigin.cs @@ -1,35 +1,34 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IRegionOrigin { - public interface IRegionOrigin + /// Console hardware region. + /// + byte ConsoleRegion { get; set; } + /// Console's configured Country via System Settings. + byte Country { get; set; } + /// Console's configured Region within via System Settings. + byte Region { get; set; } +} + +public static partial class Extensions +{ + public static void SetDefaultRegionOrigins(this IRegionOrigin o) { - /// Console hardware region. - /// - byte ConsoleRegion { get; set; } - /// Console's configured Country via System Settings. - byte Country { get; set; } - /// Console's configured Region within via System Settings. - byte Region { get; set; } + o.ConsoleRegion = 1; // North America + o.Region = 7; // California + o.Country = 49; // USA } - public static partial class Extensions + public static void CopyRegionOrigin(this IRegionOrigin source, IRegionOrigin dest) { - public static void SetDefaultRegionOrigins(this IRegionOrigin o) - { - o.ConsoleRegion = 1; // North America - o.Region = 7; // California - o.Country = 49; // USA - } + dest.ConsoleRegion = source.ConsoleRegion; + dest.Country = source.Country; + dest.Region = source.Region; + } - public static void CopyRegionOrigin(this IRegionOrigin source, IRegionOrigin dest) - { - dest.ConsoleRegion = source.ConsoleRegion; - dest.Country = source.Country; - dest.Region = source.Region; - } - - public static void ClearRegionOrigin(this IRegionOrigin o) - { - o.ConsoleRegion = o.Region = o.Country = 0; - } + public static void ClearRegionOrigin(this IRegionOrigin o) + { + o.ConsoleRegion = o.Region = o.Country = 0; } } diff --git a/PKHeX.Core/PKM/Interfaces/ISanityChecksum.cs b/PKHeX.Core/PKM/Interfaces/ISanityChecksum.cs index 9f130f5c1..99a28ed42 100644 --- a/PKHeX.Core/PKM/Interfaces/ISanityChecksum.cs +++ b/PKHeX.Core/PKM/Interfaces/ISanityChecksum.cs @@ -1,8 +1,7 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface ISanityChecksum { - public interface ISanityChecksum - { - ushort Sanity { get; set; } - ushort Checksum { get; set; } - } + ushort Sanity { get; set; } + ushort Checksum { get; set; } } diff --git a/PKHeX.Core/PKM/Interfaces/IShadowPKM.cs b/PKHeX.Core/PKM/Interfaces/IShadowPKM.cs index ff9274d28..8edef5fd8 100644 --- a/PKHeX.Core/PKM/Interfaces/IShadowPKM.cs +++ b/PKHeX.Core/PKM/Interfaces/IShadowPKM.cs @@ -1,14 +1,13 @@ -namespace PKHeX.Core -{ - /// - /// Interface that exposes Shadow details for the object. - /// - /// Used only for Colosseum/XD that were shadow encounters. - public interface IShadowPKM - { - ushort ShadowID { get; set; } - int Purification { get; set; } +namespace PKHeX.Core; - bool IsShadow { get; } - } -} \ No newline at end of file +/// +/// Interface that exposes Shadow details for the object. +/// +/// Used only for Colosseum/XD that were shadow encounters. +public interface IShadowPKM +{ + ushort ShadowID { get; set; } + int Purification { get; set; } + + bool IsShadow { get; } +} diff --git a/PKHeX.Core/PKM/Interfaces/IShiny.cs b/PKHeX.Core/PKM/Interfaces/IShiny.cs index 6cca43f23..1fc24cc49 100644 --- a/PKHeX.Core/PKM/Interfaces/IShiny.cs +++ b/PKHeX.Core/PKM/Interfaces/IShiny.cs @@ -1,13 +1,12 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface that exposes a boolean indicating if the object is a shiny. +/// +public interface IShiny { /// - /// Interface that exposes a boolean indicating if the object is a shiny. + /// Is definitively a shiny. /// - public interface IShiny - { - /// - /// Is definitively a shiny. - /// - bool IsShiny { get; } - } + bool IsShiny { get; } } diff --git a/PKHeX.Core/PKM/Interfaces/ISpeciesForm.cs b/PKHeX.Core/PKM/Interfaces/ISpeciesForm.cs index 09ae90208..8ff45a576 100644 --- a/PKHeX.Core/PKM/Interfaces/ISpeciesForm.cs +++ b/PKHeX.Core/PKM/Interfaces/ISpeciesForm.cs @@ -1,14 +1,13 @@ -namespace PKHeX.Core -{ - /// - /// Simple identification values for an Pokémon Entity. - /// - public interface ISpeciesForm - { - /// Species ID of the entity (National Dex). - int Species { get; } +namespace PKHeX.Core; - /// Form ID of the entity. - int Form { get; } - } +/// +/// Simple identification values for an Pokémon Entity. +/// +public interface ISpeciesForm +{ + /// Species ID of the entity (National Dex). + int Species { get; } + + /// Form ID of the entity. + int Form { get; } } diff --git a/PKHeX.Core/PKM/Interfaces/ITechRecord8.cs b/PKHeX.Core/PKM/Interfaces/ITechRecord8.cs index 2384abfdd..4f5138776 100644 --- a/PKHeX.Core/PKM/Interfaces/ITechRecord8.cs +++ b/PKHeX.Core/PKM/Interfaces/ITechRecord8.cs @@ -7,6 +7,6 @@ public interface ITechRecord8 ReadOnlySpan TechRecordPermitFlags { get; } ReadOnlySpan TechRecordPermitIndexes { get; } bool GetMoveRecordFlag(int index); - void SetMoveRecordFlag(int index, bool state = true); + void SetMoveRecordFlag(int index, bool value = true); bool GetMoveRecordFlagAny(); } diff --git a/PKHeX.Core/PKM/Interfaces/ITrainerID.cs b/PKHeX.Core/PKM/Interfaces/ITrainerID.cs index 3e4eb87f4..2a934ca83 100644 --- a/PKHeX.Core/PKM/Interfaces/ITrainerID.cs +++ b/PKHeX.Core/PKM/Interfaces/ITrainerID.cs @@ -1,34 +1,33 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Object has Trainer ownership +/// +public interface ITrainerID { - /// - /// Object has Trainer ownership - /// - public interface ITrainerID + int TID { get; set; } + int SID { get; set; } +} + +public static partial class Extensions +{ + public static int GetTrainerIDFormat(this ITrainerID tr) { - int TID { get; set; } - int SID { get; set; } + if (tr is PKM p) + { + var format = p.Generation; + if ((format < 3 && p.Format >= 7) || format <= 0) // VC or bad gen + return 4; // use TID/SID 16bit style + return format; + } + if (tr is SaveFile s) + return s.Generation; + return -1; } - public static partial class Extensions + public static bool IsShiny(this ITrainerID tr, uint pid, int gen = 7) { - public static int GetTrainerIDFormat(this ITrainerID tr) - { - if (tr is PKM p) - { - var format = p.Generation; - if ((format < 3 && p.Format >= 7) || format <= 0) // VC or bad gen - return 4; // use TID/SID 16bit style - return format; - } - if (tr is SaveFile s) - return s.Generation; - return -1; - } - - public static bool IsShiny(this ITrainerID tr, uint pid, int gen = 7) - { - var xor = tr.SID ^ tr.TID ^ (pid >> 16) ^ (pid & 0xFFFF); - return xor < (gen >= 7 ? 16 : 8); - } + var xor = tr.SID ^ tr.TID ^ (pid >> 16) ^ (pid & 0xFFFF); + return xor < (gen >= 7 ? 16 : 8); } } diff --git a/PKHeX.Core/PKM/Interfaces/ITrainerMemories.cs b/PKHeX.Core/PKM/Interfaces/ITrainerMemories.cs index 27680c317..dc66daccf 100644 --- a/PKHeX.Core/PKM/Interfaces/ITrainerMemories.cs +++ b/PKHeX.Core/PKM/Interfaces/ITrainerMemories.cs @@ -1,6 +1,5 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface ITrainerMemories : IMemoryOT, IMemoryHT { - public interface ITrainerMemories : IMemoryOT, IMemoryHT - { - } } diff --git a/PKHeX.Core/PKM/PA8.cs b/PKHeX.Core/PKM/PA8.cs index 30790fe47..5b618d30d 100644 --- a/PKHeX.Core/PKM/PA8.cs +++ b/PKHeX.Core/PKM/PA8.cs @@ -82,7 +82,7 @@ public override int CurrentFriendship public override int OTLength => 12; public override int NickLength => 12; - public override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 4); + public override int PSV => (int)(((PID >> 16) ^ (PID & 0xFFFF)) >> 4); public override int TSV => (TID ^ SID) >> 4; public override bool IsUntraded => Data[0xB8] == 0 && Data[0xB8 + 1] == 0 && Format == Generation; // immediately terminated HT_Name data (\0) @@ -180,7 +180,7 @@ public void FixRelearn() public byte CNT_Sheen { get => Data[0x31]; set => Data[0x31] = value; } private byte PKRS { get => Data[0x32]; set => Data[0x32] = value; } public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } // 0x33 unused padding // ribbon u32 @@ -490,7 +490,7 @@ public bool GetMoveRecordFlag(int index) return FlagUtil.GetFlag(Data, 0x13F + ofs, index & 7); } - public void SetMoveRecordFlag(int index, bool value) + public void SetMoveRecordFlag(int index, bool value = true) { if ((uint)index > 112) // 14 bytes, 8 bits throw new ArgumentOutOfRangeException(nameof(index)); diff --git a/PKHeX.Core/PKM/PB7.cs b/PKHeX.Core/PKM/PB7.cs index 10a7f7daa..f92836461 100644 --- a/PKHeX.Core/PKM/PB7.cs +++ b/PKHeX.Core/PKM/PB7.cs @@ -3,661 +3,660 @@ using System.Runtime.CompilerServices; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 7 format used for . +public sealed class PB7 : G6PKM, IHyperTrain, IAwakened, IScaledSizeValue, ICombatPower, IFavorite, IFormArgument { - /// Generation 7 format used for . - public sealed class PB7 : G6PKM, IHyperTrain, IAwakened, IScaledSizeValue, ICombatPower, IFavorite, IFormArgument + public static readonly ushort[] Unused = { - public static readonly ushort[] Unused = + 0x2A, // Old Marking Value (PelagoEventStatus) + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, // Unused Ribbons + 0x58, 0x59, // Nickname Terminator + 0x73, + 0x90, 0x91, // HT Terminator + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, // Old Geolocation/memories + 0xA7, 0xAA, 0xAB, + 0xAC, 0xAD, // Fatigue, no GUI editing + 0xC8, 0xC9, // OT Terminator + }; + + public override IReadOnlyList ExtraBytes => Unused; + + public override int SIZE_PARTY => SIZE; + public override int SIZE_STORED => SIZE; + private const int SIZE = 260; + public override EntityContext Context => EntityContext.Gen7b; + public override PersonalInfo PersonalInfo => PersonalTable.GG.GetFormEntry(Species, Form); + + public PB7() : base(SIZE) { } + public PB7(byte[] data) : base(DecryptParty(data)) { } + + private static byte[] DecryptParty(byte[] data) + { + PokeCrypto.DecryptIfEncrypted67(ref data); + if (data.Length != SIZE) + Array.Resize(ref data, SIZE); + return data; + } + + public override PKM Clone() => new PB7((byte[])Data.Clone()); + + // Structure + #region Block A + public override uint EncryptionConstant + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); + } + + public override ushort Sanity + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); + } + + public override ushort Checksum + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); + } + + public override int Species + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); + } + + public override int HeldItem + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); + } + + public override int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); + } + + public override int SID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); + } + + public override uint EXP + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); + } + + public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public override int AbilityNumber { get => Data[0x15] & 7; set => Data[0x15] = (byte)((Data[0x15] & ~7) | (value & 7)); } + public bool Favorite { get => (Data[0x15] & 8) != 0; set => Data[0x15] = (byte)((Data[0x15] & ~8) | ((value ? 1 : 0) << 3)); } + public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); } + + public override uint PID + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x18)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x18), value); + } + + public override int Nature { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public override bool FatefulEncounter { get => (Data[0x1D] & 1) == 1; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x01) | (value ? 1 : 0)); } + public override int Gender { get => (Data[0x1D] >> 1) & 0x3; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x06) | (value << 1)); } + public override int Form { get => Data[0x1D] >> 3; set => Data[0x1D] = (byte)((Data[0x1D] & 0x07) | (value << 3)); } + public override int EV_HP { get => Data[0x1E]; set => Data[0x1E] = (byte)value; } + public override int EV_ATK { get => Data[0x1F]; set => Data[0x1F] = (byte)value; } + public override int EV_DEF { get => Data[0x20]; set => Data[0x20] = (byte)value; } + public override int EV_SPE { get => Data[0x21]; set => Data[0x21] = (byte)value; } + public override int EV_SPA { get => Data[0x22]; set => Data[0x22] = (byte)value; } + public override int EV_SPD { get => Data[0x23]; set => Data[0x23] = (byte)value; } + public byte AV_HP { get => Data[0x24]; set => Data[0x24] = value; } + public byte AV_ATK { get => Data[0x25]; set => Data[0x25] = value; } + public byte AV_DEF { get => Data[0x26]; set => Data[0x26] = value; } + public byte AV_SPE { get => Data[0x27]; set => Data[0x27] = value; } + public byte AV_SPA { get => Data[0x28]; set => Data[0x28] = value; } + public byte AV_SPD { get => Data[0x29]; set => Data[0x29] = value; } + public ResortEventState ResortEventStatus { get => (ResortEventState)Data[0x2A]; set => Data[0x2A] = (byte)value; } + private byte PKRS { get => Data[0x2B]; set => Data[0x2B] = value; } + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + public float HeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(0x2C)); set => WriteSingleLittleEndian(Data.AsSpan(0x2C), value); } + // 0x38 Unused + // 0x39 Unused + public byte HeightScalar { get => Data[0x3A]; set => Data[0x3A] = value; } + public byte WeightScalar { get => Data[0x3B]; set => Data[0x3B] = value; } + public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x3C), value); } + public byte FormArgumentRemain { get => (byte)FormArgument; set => FormArgument = (FormArgument & ~0xFFu) | value; } + public byte FormArgumentElapsed { get => (byte)(FormArgument >> 8); set => FormArgument = (FormArgument & ~0xFF00u) | (uint)(value << 8); } + public byte FormArgumentMaximum { get => (byte)(FormArgument >> 16); set => FormArgument = (FormArgument & ~0xFF0000u) | (uint)(value << 16); } + + #endregion + #region Block B + public override string Nickname + { + get => StringConverter8.GetString(Nickname_Trash); + set => StringConverter8.SetString(Nickname_Trash, value.AsSpan(), 12, StringConverterOption.None); + } + + public override int Move1 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), (ushort)value); + } + + public override int Move2 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); + } + + public override int Move3 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); + } + + public override int Move4 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x60)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x60), (ushort)value); + } + + public override int Move1_PP { get => Data[0x62]; set => Data[0x62] = (byte)value; } + public override int Move2_PP { get => Data[0x63]; set => Data[0x63] = (byte)value; } + public override int Move3_PP { get => Data[0x64]; set => Data[0x64] = (byte)value; } + public override int Move4_PP { get => Data[0x65]; set => Data[0x65] = (byte)value; } + public override int Move1_PPUps { get => Data[0x66]; set => Data[0x66] = (byte)value; } + public override int Move2_PPUps { get => Data[0x67]; set => Data[0x67] = (byte)value; } + public override int Move3_PPUps { get => Data[0x68]; set => Data[0x68] = (byte)value; } + public override int Move4_PPUps { get => Data[0x69]; set => Data[0x69] = (byte)value; } + + public override int RelearnMove1 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); + } + + public override int RelearnMove2 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6C), (ushort)value); + } + + public override int RelearnMove3 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6E), (ushort)value); + } + + public override int RelearnMove4 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x70)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x70), (ushort)value); + } + + // 0x72 Unused + // 0x73 Unused + private uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x74)); set => WriteUInt32LittleEndian(Data.AsSpan(0x74), value); } + public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } + public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } + public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } + public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } + public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } + public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } + public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } + public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } + #endregion + #region Block C + public override string HT_Name + { + get => StringConverter8.GetString(HT_Trash); + set => StringConverter8.SetString(HT_Trash, value.AsSpan(), 12, StringConverterOption.None); + } + + public override int HT_Gender { get => Data[0x92]; set => Data[0x92] = (byte)value; } + public override int CurrentHandler { get => Data[0x93]; set => Data[0x93] = (byte)value; } + // 0x94 Unused + // 0x95 Unused + // 0x96 Unused + // 0x97 Unused + // 0x98 Unused + // 0x99 Unused + // 0x9A Unused + // 0x9B Unused + // 0x9C Unused + // 0x9D Unused + // 0x9E Unused + // 0x9F Unused + // 0xA0 Unused + // 0xA1 Unused + public override int HT_Friendship { get => Data[0xA2]; set => Data[0xA2] = (byte)value; } + // 0xA1 HT_Affection Unused + public int HT_Intensity { get => Data[0xA4]; set => Data[0xA4] = (byte)value; } + public int HT_Memory { get => Data[0xA5]; set => Data[0xA5] = (byte)value; } + public int HT_Feeling { get => Data[0xA6]; set => Data[0xA6] = (byte)value; } + // 0xA7 Unused + public int HT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xA8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA8), (ushort)value); } + // 0xAA Unused + // 0xAB Unused + public byte FieldEventFatigue1 { get => Data[0xAC]; set => Data[0xAC] = value; } + public byte FieldEventFatigue2 { get => Data[0xAD]; set => Data[0xAD] = value; } + public override byte Fullness { get => Data[0xAE]; set => Data[0xAE] = value; } + public override byte Enjoyment { get => Data[0xAF]; set => Data[0xAF] = value; } + #endregion + #region Block D + public override string OT_Name + { + get => StringConverter8.GetString(OT_Trash); + set => StringConverter8.SetString(OT_Trash, value.AsSpan(), 12, StringConverterOption.None); + } + + public override int OT_Friendship { get => Data[0xCA]; set => Data[0xCA] = (byte)value; } + // 0xCB Unused + // 0xCC Unused + // 0xCD Unused + // 0xCE Unused + // 0xCF Unused + // 0xD0 Unused + public override int Egg_Year { get => Data[0xD1]; set => Data[0xD1] = (byte)value; } + public override int Egg_Month { get => Data[0xD2]; set => Data[0xD2] = (byte)value; } + public override int Egg_Day { get => Data[0xD3]; set => Data[0xD3] = (byte)value; } + public override int Met_Year { get => Data[0xD4]; set => Data[0xD4] = (byte)value; } + public override int Met_Month { get => Data[0xD5]; set => Data[0xD5] = (byte)value; } + public override int Met_Day { get => Data[0xD6]; set => Data[0xD6] = (byte)value; } + public int Rank { get => Data[0xD7]; set => Data[0xD7] = (byte)value; } // unused but fetched for stat calcs, and set for trpoke data? + public override int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), (ushort)value); } + public override int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), (ushort)value); } + public override int Ball { get => Data[0xDC]; set => Data[0xDC] = (byte)value; } + public override int Met_Level { get => Data[0xDD] & ~0x80; set => Data[0xDD] = (byte)((Data[0xDD] & 0x80) | value); } + public override int OT_Gender { get => Data[0xDD] >> 7; set => Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); } + public byte HyperTrainFlags { get => Data[0xDE]; set => Data[0xDE] = value; } + public bool HT_HP { get => ((HyperTrainFlags >> 0) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 0)) | ((value ? 1 : 0) << 0)); } + public bool HT_ATK { get => ((HyperTrainFlags >> 1) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 1)) | ((value ? 1 : 0) << 1)); } + public bool HT_DEF { get => ((HyperTrainFlags >> 2) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 2)) | ((value ? 1 : 0) << 2)); } + public bool HT_SPA { get => ((HyperTrainFlags >> 3) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 3)) | ((value ? 1 : 0) << 3)); } + public bool HT_SPD { get => ((HyperTrainFlags >> 4) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 4)) | ((value ? 1 : 0) << 4)); } + public bool HT_SPE { get => ((HyperTrainFlags >> 5) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 5)) | ((value ? 1 : 0) << 5)); } + public override int Version { get => Data[0xDF]; set => Data[0xDF] = (byte)value; } + // 0xE0 Unused + // 0xE1 Unused + // 0xE2 Unused + public override int Language { get => Data[0xE3]; set => Data[0xE3] = (byte)value; } + public float WeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(0xE4)); set => WriteSingleLittleEndian(Data.AsSpan(0xE4), value); } + #endregion + #region Battle Stats + public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0xE8)); set => WriteInt32LittleEndian(Data.AsSpan(0xE8), value); } + public override int Stat_Level { get => Data[0xEC]; set => Data[0xEC] = (byte)value; } + public byte DirtType { get => Data[0xED]; set => Data[0xED] = value; } + public byte DirtLocation { get => Data[0xEE]; set => Data[0xEE] = value; } + // 0xEF unused + public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0xF0)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF0), (ushort)value); } + public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0xF2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF2), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0xF4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF4), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0xF6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF6), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0xF8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF8), (ushort)value); } + public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0xFA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFA), (ushort)value); } + public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0xFC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFC), (ushort)value); } + public int Stat_CP { get => ReadUInt16LittleEndian(Data.AsSpan(0xFE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFE), (ushort)value); } + public bool Stat_Mega { get => Data[0x100] != 0; set => Data[0x100] = value ? (byte)1 : (byte)0; } + public int Stat_MegaForm { get => Data[0x101]; set => Data[0x101] = (byte)value; } + // 102/103 unused + #endregion + + public override int MarkingCount => 6; + + public override int GetMarking(int index) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return (MarkValue >> (index * 2)) & 3; + } + + public override void SetMarking(int index, int value) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + var shift = index * 2; + MarkValue = (MarkValue & ~(0b11 << shift)) | ((value & 3) << shift); + } + + protected override bool TradeOT(ITrainerInfo tr) + { + // Check to see if the OT matches the SAV's OT info. + if (!(tr.TID == TID && tr.SID == SID && tr.Gender == OT_Gender && tr.OT == OT_Name)) + return false; + + CurrentHandler = 0; + return true; + } + + protected override void TradeHT(ITrainerInfo tr) + { + if (HT_Name != tr.OT) { - 0x2A, // Old Marking Value (PelagoEventStatus) - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, // Unused Ribbons - 0x58, 0x59, // Nickname Terminator - 0x73, - 0x90, 0x91, // HT Terminator - 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, // Old Geolocation/memories - 0xA7, 0xAA, 0xAB, - 0xAC, 0xAD, // Fatigue, no GUI editing - 0xC8, 0xC9, // OT Terminator - }; - - public override IReadOnlyList ExtraBytes => Unused; - - public override int SIZE_PARTY => SIZE; - public override int SIZE_STORED => SIZE; - private const int SIZE = 260; - public override EntityContext Context => EntityContext.Gen7b; - public override PersonalInfo PersonalInfo => PersonalTable.GG.GetFormEntry(Species, Form); - - public PB7() : base(SIZE) { } - public PB7(byte[] data) : base(DecryptParty(data)) { } - - private static byte[] DecryptParty(byte[] data) - { - PokeCrypto.DecryptIfEncrypted67(ref data); - if (data.Length != SIZE) - Array.Resize(ref data, SIZE); - return data; + HT_Friendship = CurrentFriendship; // copy friendship instead of resetting (don't alter CP) + HT_Name = tr.OT; } + CurrentHandler = 1; + HT_Gender = tr.Gender; + } - public override PKM Clone() => new PB7((byte[])Data.Clone()); + public void FixMemories() + { + if (IsUntraded) + HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = 0; + } - // Structure - #region Block A - public override uint EncryptionConstant - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); - } - - public override ushort Sanity - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); - } - - public override ushort Checksum - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); - } - - public override int Species - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); - } - - public override int HeldItem - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); - } - - public override int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); - } - - public override int SID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); - } - - public override uint EXP - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); - } - - public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; } - public override int AbilityNumber { get => Data[0x15] & 7; set => Data[0x15] = (byte)((Data[0x15] & ~7) | (value & 7)); } - public bool Favorite { get => (Data[0x15] & 8) != 0; set => Data[0x15] = (byte)((Data[0x15] & ~8) | ((value ? 1 : 0) << 3)); } - public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); } - - public override uint PID - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x18)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x18), value); - } - - public override int Nature { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } - public override bool FatefulEncounter { get => (Data[0x1D] & 1) == 1; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x01) | (value ? 1 : 0)); } - public override int Gender { get => (Data[0x1D] >> 1) & 0x3; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x06) | (value << 1)); } - public override int Form { get => Data[0x1D] >> 3; set => Data[0x1D] = (byte)((Data[0x1D] & 0x07) | (value << 3)); } - public override int EV_HP { get => Data[0x1E]; set => Data[0x1E] = (byte)value; } - public override int EV_ATK { get => Data[0x1F]; set => Data[0x1F] = (byte)value; } - public override int EV_DEF { get => Data[0x20]; set => Data[0x20] = (byte)value; } - public override int EV_SPE { get => Data[0x21]; set => Data[0x21] = (byte)value; } - public override int EV_SPA { get => Data[0x22]; set => Data[0x22] = (byte)value; } - public override int EV_SPD { get => Data[0x23]; set => Data[0x23] = (byte)value; } - public byte AV_HP { get => Data[0x24]; set => Data[0x24] = value; } - public byte AV_ATK { get => Data[0x25]; set => Data[0x25] = value; } - public byte AV_DEF { get => Data[0x26]; set => Data[0x26] = value; } - public byte AV_SPE { get => Data[0x27]; set => Data[0x27] = value; } - public byte AV_SPA { get => Data[0x28]; set => Data[0x28] = value; } - public byte AV_SPD { get => Data[0x29]; set => Data[0x29] = value; } - public ResortEventState ResortEventStatus { get => (ResortEventState)Data[0x2A]; set => Data[0x2A] = (byte)value; } - private byte PKRS { get => Data[0x2B]; set => Data[0x2B] = value; } - public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); } - public float HeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(0x2C)); set => WriteSingleLittleEndian(Data.AsSpan(0x2C), value); } - // 0x38 Unused - // 0x39 Unused - public byte HeightScalar { get => Data[0x3A]; set => Data[0x3A] = value; } - public byte WeightScalar { get => Data[0x3B]; set => Data[0x3B] = value; } - public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x3C), value); } - public byte FormArgumentRemain { get => (byte)FormArgument; set => FormArgument = (FormArgument & ~0xFFu) | value; } - public byte FormArgumentElapsed { get => (byte)(FormArgument >> 8); set => FormArgument = (FormArgument & ~0xFF00u) | (uint)(value << 8); } - public byte FormArgumentMaximum { get => (byte)(FormArgument >> 16); set => FormArgument = (FormArgument & ~0xFF0000u) | (uint)(value << 16); } - - #endregion - #region Block B - public override string Nickname - { - get => StringConverter8.GetString(Nickname_Trash); - set => StringConverter8.SetString(Nickname_Trash, value.AsSpan(), 12, StringConverterOption.None); - } - - public override int Move1 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), (ushort)value); - } - - public override int Move2 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); - } - - public override int Move3 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); - } - - public override int Move4 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x60)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x60), (ushort)value); - } - - public override int Move1_PP { get => Data[0x62]; set => Data[0x62] = (byte)value; } - public override int Move2_PP { get => Data[0x63]; set => Data[0x63] = (byte)value; } - public override int Move3_PP { get => Data[0x64]; set => Data[0x64] = (byte)value; } - public override int Move4_PP { get => Data[0x65]; set => Data[0x65] = (byte)value; } - public override int Move1_PPUps { get => Data[0x66]; set => Data[0x66] = (byte)value; } - public override int Move2_PPUps { get => Data[0x67]; set => Data[0x67] = (byte)value; } - public override int Move3_PPUps { get => Data[0x68]; set => Data[0x68] = (byte)value; } - public override int Move4_PPUps { get => Data[0x69]; set => Data[0x69] = (byte)value; } - - public override int RelearnMove1 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); - } - - public override int RelearnMove2 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6C), (ushort)value); - } - - public override int RelearnMove3 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6E), (ushort)value); - } - - public override int RelearnMove4 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x70)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x70), (ushort)value); - } - - // 0x72 Unused - // 0x73 Unused - private uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x74)); set => WriteUInt32LittleEndian(Data.AsSpan(0x74), value); } - public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } - public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } - public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } - public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } - public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } - public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } - public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } - public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } - #endregion - #region Block C - public override string HT_Name - { - get => StringConverter8.GetString(HT_Trash); - set => StringConverter8.SetString(HT_Trash, value.AsSpan(), 12, StringConverterOption.None); - } - - public override int HT_Gender { get => Data[0x92]; set => Data[0x92] = (byte)value; } - public override int CurrentHandler { get => Data[0x93]; set => Data[0x93] = (byte)value; } - // 0x94 Unused - // 0x95 Unused - // 0x96 Unused - // 0x97 Unused - // 0x98 Unused - // 0x99 Unused - // 0x9A Unused - // 0x9B Unused - // 0x9C Unused - // 0x9D Unused - // 0x9E Unused - // 0x9F Unused - // 0xA0 Unused - // 0xA1 Unused - public override int HT_Friendship { get => Data[0xA2]; set => Data[0xA2] = (byte)value; } - // 0xA1 HT_Affection Unused - public int HT_Intensity { get => Data[0xA4]; set => Data[0xA4] = (byte)value; } - public int HT_Memory { get => Data[0xA5]; set => Data[0xA5] = (byte)value; } - public int HT_Feeling { get => Data[0xA6]; set => Data[0xA6] = (byte)value; } - // 0xA7 Unused - public int HT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xA8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA8), (ushort)value); } - // 0xAA Unused - // 0xAB Unused - public byte FieldEventFatigue1 { get => Data[0xAC]; set => Data[0xAC] = value; } - public byte FieldEventFatigue2 { get => Data[0xAD]; set => Data[0xAD] = value; } - public override byte Fullness { get => Data[0xAE]; set => Data[0xAE] = value; } - public override byte Enjoyment { get => Data[0xAF]; set => Data[0xAF] = value; } - #endregion - #region Block D - public override string OT_Name - { - get => StringConverter8.GetString(OT_Trash); - set => StringConverter8.SetString(OT_Trash, value.AsSpan(), 12, StringConverterOption.None); - } - - public override int OT_Friendship { get => Data[0xCA]; set => Data[0xCA] = (byte)value; } - // 0xCB Unused - // 0xCC Unused - // 0xCD Unused - // 0xCE Unused - // 0xCF Unused - // 0xD0 Unused - public override int Egg_Year { get => Data[0xD1]; set => Data[0xD1] = (byte)value; } - public override int Egg_Month { get => Data[0xD2]; set => Data[0xD2] = (byte)value; } - public override int Egg_Day { get => Data[0xD3]; set => Data[0xD3] = (byte)value; } - public override int Met_Year { get => Data[0xD4]; set => Data[0xD4] = (byte)value; } - public override int Met_Month { get => Data[0xD5]; set => Data[0xD5] = (byte)value; } - public override int Met_Day { get => Data[0xD6]; set => Data[0xD6] = (byte)value; } - public int Rank { get => Data[0xD7]; set => Data[0xD7] = (byte)value; } // unused but fetched for stat calcs, and set for trpoke data? - public override int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), (ushort)value); } - public override int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), (ushort)value); } - public override int Ball { get => Data[0xDC]; set => Data[0xDC] = (byte)value; } - public override int Met_Level { get => Data[0xDD] & ~0x80; set => Data[0xDD] = (byte)((Data[0xDD] & 0x80) | value); } - public override int OT_Gender { get => Data[0xDD] >> 7; set => Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); } - public byte HyperTrainFlags { get => Data[0xDE]; set => Data[0xDE] = value; } - public bool HT_HP { get => ((HyperTrainFlags >> 0) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 0)) | ((value ? 1 : 0) << 0)); } - public bool HT_ATK { get => ((HyperTrainFlags >> 1) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 1)) | ((value ? 1 : 0) << 1)); } - public bool HT_DEF { get => ((HyperTrainFlags >> 2) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 2)) | ((value ? 1 : 0) << 2)); } - public bool HT_SPA { get => ((HyperTrainFlags >> 3) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 3)) | ((value ? 1 : 0) << 3)); } - public bool HT_SPD { get => ((HyperTrainFlags >> 4) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 4)) | ((value ? 1 : 0) << 4)); } - public bool HT_SPE { get => ((HyperTrainFlags >> 5) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 5)) | ((value ? 1 : 0) << 5)); } - public override int Version { get => Data[0xDF]; set => Data[0xDF] = (byte)value; } - // 0xE0 Unused - // 0xE1 Unused - // 0xE2 Unused - public override int Language { get => Data[0xE3]; set => Data[0xE3] = (byte)value; } - public float WeightAbsolute { get => ReadSingleLittleEndian(Data.AsSpan(0xE4)); set => WriteSingleLittleEndian(Data.AsSpan(0xE4), value); } - #endregion - #region Battle Stats - public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0xE8)); set => WriteInt32LittleEndian(Data.AsSpan(0xE8), value); } - public override int Stat_Level { get => Data[0xEC]; set => Data[0xEC] = (byte)value; } - public byte DirtType { get => Data[0xED]; set => Data[0xED] = value; } - public byte DirtLocation { get => Data[0xEE]; set => Data[0xEE] = value; } - // 0xEF unused - public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0xF0)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF0), (ushort)value); } - public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0xF2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF2), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0xF4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF4), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0xF6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF6), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0xF8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF8), (ushort)value); } - public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0xFA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFA), (ushort)value); } - public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0xFC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFC), (ushort)value); } - public int Stat_CP { get => ReadUInt16LittleEndian(Data.AsSpan(0xFE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFE), (ushort)value); } - public bool Stat_Mega { get => Data[0x100] != 0; set => Data[0x100] = value ? (byte)1 : (byte)0; } - public int Stat_MegaForm { get => Data[0x101]; set => Data[0x101] = (byte)value; } - // 102/103 unused - #endregion - - public override int MarkingCount => 6; - - public override int GetMarking(int index) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - return (MarkValue >> (index * 2)) & 3; - } - - public override void SetMarking(int index, int value) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - var shift = index * 2; - MarkValue = (MarkValue & ~(0b11 << shift)) | ((value & 3) << shift); - } - - protected override bool TradeOT(ITrainerInfo tr) - { - // Check to see if the OT matches the SAV's OT info. - if (!(tr.TID == TID && tr.SID == SID && tr.Gender == OT_Gender && tr.OT == OT_Name)) - return false; - - CurrentHandler = 0; - return true; - } - - protected override void TradeHT(ITrainerInfo tr) - { - if (HT_Name != tr.OT) - { - HT_Friendship = CurrentFriendship; // copy friendship instead of resetting (don't alter CP) - HT_Name = tr.OT; - } - CurrentHandler = 1; - HT_Gender = tr.Gender; - } - - public void FixMemories() - { - if (IsUntraded) - HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = 0; - } - - // Maximums - public override int MaxMoveID => Legal.MaxMoveID_7b; - public override int MaxSpeciesID => Legal.MaxSpeciesID_7b; - public override int MaxAbilityID => Legal.MaxAbilityID_7_USUM; - public override int MaxItemID => Legal.MaxItemID_7_USUM; - public override int MaxBallID => Legal.MaxBallID_7; - public override int MaxGameID => Legal.MaxGameID_7b; - - public override void LoadStats(PersonalInfo p, Span stats) + // Maximums + public override int MaxMoveID => Legal.MaxMoveID_7b; + public override int MaxSpeciesID => Legal.MaxSpeciesID_7b; + public override int MaxAbilityID => Legal.MaxAbilityID_7_USUM; + public override int MaxItemID => Legal.MaxItemID_7_USUM; + public override int MaxBallID => Legal.MaxBallID_7; + public override int MaxGameID => Legal.MaxGameID_7b; + + public override void LoadStats(PersonalInfo p, Span stats) + { + int level = CurrentLevel; + int nature = Nature; + int friend = CurrentFriendship; // stats +10% depending on friendship! + int scalar = (int)(((friend / 255.0f / 10.0f) + 1.0f) * 100.0f); + stats[0] = (ushort)(AV_HP + GetStat(p.HP, HT_HP ? 31 : IV_HP, level) + 10 + level); + stats[1] = (ushort)(AV_ATK + (scalar * GetStat(p.ATK, HT_ATK ? 31 : IV_ATK, level, nature, 0) / 100)); + stats[2] = (ushort)(AV_DEF + (scalar * GetStat(p.DEF, HT_DEF ? 31 : IV_DEF, level, nature, 1) / 100)); + stats[3] = (ushort)(AV_SPE + (scalar * GetStat(p.SPE, HT_SPE ? 31 : IV_SPE, level, nature, 4) / 100)); + stats[4] = (ushort)(AV_SPA + (scalar * GetStat(p.SPA, HT_SPA ? 31 : IV_SPA, level, nature, 2) / 100)); + stats[5] = (ushort)(AV_SPD + (scalar * GetStat(p.SPD, HT_SPD ? 31 : IV_SPD, level, nature, 3) / 100)); + } + + /// + /// Gets the initial stat value based on the base stat value, IV, and current level. + /// + /// stat. + /// Current IV, already accounted for Hyper Training + /// Current Level + /// Initial Stat + private static int GetStat(int baseStat, int iv, int level) => (iv + (2 * baseStat)) * level / 100; + + /// + /// Gets the initial stat value with nature amplification applied. Used for all stats except HP. + /// + /// stat. + /// Current IV, already accounted for Hyper Training + /// Current Level + /// + /// Stat amp index in the nature amp table + /// Initial Stat with nature amplification applied. + private static int GetStat(int baseStat, int iv, int level, int nature, int statIndex) + { + int initial = GetStat(baseStat, iv, level) + 5; + return AmplifyStat(nature, statIndex, initial); + } + + private static int AmplifyStat(int nature, int index, int initial) => GetNatureAmp(nature, index) switch + { + 1 => 110 * initial / 100, // 110% + -1 => 90 * initial / 100, // 90% + _ => initial, + }; + + private static sbyte GetNatureAmp(int nature, int index) + { + if ((uint)nature >= 25) + return -1; + return NatureAmpTable[(5 * nature) + index]; + } + + private static readonly sbyte[] NatureAmpTable = + { + 0, 0, 0, 0, 0, // Hardy + 1,-1, 0, 0, 0, // Lonely + 1, 0, 0, 0,-1, // Brave + 1, 0,-1, 0, 0, // Adamant + 1, 0, 0,-1, 0, // Naughty + -1, 1, 0, 0, 0, // Bold + 0, 0, 0, 0, 0, // Docile + 0, 1, 0, 0,-1, // Relaxed + 0, 1,-1, 0, 0, // Impish + 0, 1, 0,-1, 0, // Lax + -1, 0, 0, 0, 1, // Timid + 0,-1, 0, 0, 1, // Hasty + 0, 0, 0, 0, 0, // Serious + 0, 0,-1, 0, 1, // Jolly + 0, 0, 0,-1, 1, // Naive + -1, 0, 1, 0, 0, // Modest + 0,-1, 1, 0, 0, // Mild + 0, 0, 1, 0,-1, // Quiet + 0, 0, 0, 0, 0, // Bashful + 0, 0, 1,-1, 0, // Rash + -1, 0, 0, 1, 0, // Calm + 0,-1, 0, 1, 0, // Gentle + 0, 0, 0, 1,-1, // Sassy + 0, 0,-1, 1, 0, // Careful + 0, 0, 0, 0, 0, // Quirky + }; + + public int CalcCP => Math.Min(10000, AwakeCP + BaseCP); + + public int BaseCP + { + get { + var p = PersonalInfo; int level = CurrentLevel; int nature = Nature; - int friend = CurrentFriendship; // stats +10% depending on friendship! - int scalar = (int)(((friend / 255.0f / 10.0f) + 1.0f) * 100.0f); - stats[0] = (ushort)(AV_HP + GetStat(p.HP, HT_HP ? 31 : IV_HP, level) + 10 + level); - stats[1] = (ushort)(AV_ATK + (scalar * GetStat(p.ATK, HT_ATK ? 31 : IV_ATK, level, nature, 0) / 100)); - stats[2] = (ushort)(AV_DEF + (scalar * GetStat(p.DEF, HT_DEF ? 31 : IV_DEF, level, nature, 1) / 100)); - stats[3] = (ushort)(AV_SPE + (scalar * GetStat(p.SPE, HT_SPE ? 31 : IV_SPE, level, nature, 4) / 100)); - stats[4] = (ushort)(AV_SPA + (scalar * GetStat(p.SPA, HT_SPA ? 31 : IV_SPA, level, nature, 2) / 100)); - stats[5] = (ushort)(AV_SPD + (scalar * GetStat(p.SPD, HT_SPD ? 31 : IV_SPD, level, nature, 3) / 100)); - } + int scalar = CPScalar; - /// - /// Gets the initial stat value based on the base stat value, IV, and current level. - /// - /// stat. - /// Current IV, already accounted for Hyper Training - /// Current Level - /// Initial Stat - private static int GetStat(int baseStat, int iv, int level) => (iv + (2 * baseStat)) * level / 100; + // Calculate stats for all, then sum together. + // HP is not overriden to 1 like a regular stat calc for Shedinja. + var statSum = + (ushort)GetStat(p.HP, HT_HP ? 31 : IV_HP, level) + 10 + level + + (ushort)(GetStat(p.ATK, HT_ATK ? 31 : IV_ATK, level, nature, 0) * scalar / 100) + + (ushort)(GetStat(p.DEF, HT_DEF ? 31 : IV_DEF, level, nature, 1) * scalar / 100) + + (ushort)(GetStat(p.SPE, HT_SPE ? 31 : IV_SPE, level, nature, 4) * scalar / 100) + + (ushort)(GetStat(p.SPA, HT_SPA ? 31 : IV_SPA, level, nature, 2) * scalar / 100) + + (ushort)(GetStat(p.SPD, HT_SPD ? 31 : IV_SPD, level, nature, 3) * scalar / 100); - /// - /// Gets the initial stat value with nature amplification applied. Used for all stats except HP. - /// - /// stat. - /// Current IV, already accounted for Hyper Training - /// Current Level - /// - /// Stat amp index in the nature amp table - /// Initial Stat with nature amplification applied. - private static int GetStat(int baseStat, int iv, int level, int nature, int statIndex) - { - int initial = GetStat(baseStat, iv, level) + 5; - return AmplifyStat(nature, statIndex, initial); - } - - private static int AmplifyStat(int nature, int index, int initial) => GetNatureAmp(nature, index) switch - { - 1 => 110 * initial / 100, // 110% - -1 => 90 * initial / 100, // 90% - _ => initial, - }; - - private static sbyte GetNatureAmp(int nature, int index) - { - if ((uint)nature >= 25) - return -1; - return NatureAmpTable[(5 * nature) + index]; - } - - private static readonly sbyte[] NatureAmpTable = - { - 0, 0, 0, 0, 0, // Hardy - 1,-1, 0, 0, 0, // Lonely - 1, 0, 0, 0,-1, // Brave - 1, 0,-1, 0, 0, // Adamant - 1, 0, 0,-1, 0, // Naughty - -1, 1, 0, 0, 0, // Bold - 0, 0, 0, 0, 0, // Docile - 0, 1, 0, 0,-1, // Relaxed - 0, 1,-1, 0, 0, // Impish - 0, 1, 0,-1, 0, // Lax - -1, 0, 0, 0, 1, // Timid - 0,-1, 0, 0, 1, // Hasty - 0, 0, 0, 0, 0, // Serious - 0, 0,-1, 0, 1, // Jolly - 0, 0, 0,-1, 1, // Naive - -1, 0, 1, 0, 0, // Modest - 0,-1, 1, 0, 0, // Mild - 0, 0, 1, 0,-1, // Quiet - 0, 0, 0, 0, 0, // Bashful - 0, 0, 1,-1, 0, // Rash - -1, 0, 0, 1, 0, // Calm - 0,-1, 0, 1, 0, // Gentle - 0, 0, 0, 1,-1, // Sassy - 0, 0,-1, 1, 0, // Careful - 0, 0, 0, 0, 0, // Quirky - }; - - public int CalcCP => Math.Min(10000, AwakeCP + BaseCP); - - public int BaseCP - { - get - { - var p = PersonalInfo; - int level = CurrentLevel; - int nature = Nature; - int scalar = CPScalar; - - // Calculate stats for all, then sum together. - // HP is not overriden to 1 like a regular stat calc for Shedinja. - var statSum = - (ushort)GetStat(p.HP, HT_HP ? 31 : IV_HP, level) + 10 + level - + (ushort)(GetStat(p.ATK, HT_ATK ? 31 : IV_ATK, level, nature, 0) * scalar / 100) - + (ushort)(GetStat(p.DEF, HT_DEF ? 31 : IV_DEF, level, nature, 1) * scalar / 100) - + (ushort)(GetStat(p.SPE, HT_SPE ? 31 : IV_SPE, level, nature, 4) * scalar / 100) - + (ushort)(GetStat(p.SPA, HT_SPA ? 31 : IV_SPA, level, nature, 2) * scalar / 100) - + (ushort)(GetStat(p.SPD, HT_SPD ? 31 : IV_SPD, level, nature, 3) * scalar / 100); - - float result = statSum * 6f; - result *= level; - result /= 100f; - return (int)result; - } - } - - public int CPScalar - { - get - { - int friend = CurrentFriendship; // stats +10% depending on friendship! - float scalar = friend / 255f; - scalar /= 10f; - scalar++; - scalar *= 100f; - return (int)scalar; - } - } - - public int AwakeCP - { - get - { - var sum = this.AwakeningSum(); - if (sum == 0) - return 0; - var lvl = CurrentLevel; - float scalar = lvl * 4f; - scalar /= 100f; - scalar += 2f; - float result = sum * scalar; - return (int)result; - } - } - - public void ResetCP() => Stat_CP = CalcCP; - - public void ResetCalculatedValues() - { - ResetCP(); - ResetHeight(); - ResetWeight(); - } - - // Casts are as per the game code; they may seem redundant but every bit of precision matters? - // This still doesn't precisely match :( -- just use a tolerance check when updating. - // If anyone can figure out how to get all precision to match, feel free to update :) - public float HeightRatio => GetHeightRatio(HeightScalar); - public float WeightRatio => GetWeightRatio(WeightScalar); - - public float CalcHeightAbsolute => GetHeightAbsolute(PersonalInfo, HeightScalar); - public float CalcWeightAbsolute => GetWeightAbsolute(PersonalInfo, HeightScalar, WeightScalar); - - public void ResetHeight() => HeightAbsolute = CalcHeightAbsolute; - public void ResetWeight() => WeightAbsolute = CalcWeightAbsolute; - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - private static float GetHeightRatio(int heightScalar) - { - // + 40%, -20 - float result = heightScalar / 255f; // 0x437F0000 - result *= 0.79999995f; // 0x3F4CCCCC - result += 0.6f; // 0x3F19999A - return result; - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - private static float GetWeightRatio(int weightScalar) - { - // +/- 20% - float result = weightScalar / 255f; // 0x437F0000 - result *= 0.40000004f; // 0x3ECCCCCE - result += 0.8f; // 0x3F4CCCCD - return result; - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public static float GetHeightAbsolute(PersonalInfo p, int heightScalar) - { - float HeightRatio = GetHeightRatio(heightScalar); - return HeightRatio * p.Height; - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public static float GetWeightAbsolute(PersonalInfo p, int heightScalar, int weightScalar) - { - float HeightRatio = GetHeightRatio(heightScalar); - float WeightRatio = GetWeightRatio(weightScalar); - - float weight = WeightRatio * p.Weight; - return HeightRatio * weight; - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public static byte GetHeightScalar(float height, int avgHeight) - { - // height is already *100 - float biasH = avgHeight * -0.6f; - float biasL = avgHeight * 0.79999995f; - float numerator = biasH + height; - float result = numerator / biasL; - result *= 255f; - int value = (int)result; - int unsigned = value & ~(value >> 31); - return (byte)Math.Min(255, unsigned); - } - - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] - public static byte GetWeightScalar(float height, float weight, int avgHeight, int avgWeight) - { - // height is already *100 - // weight is already *10 - float heightRatio = height / avgHeight; - float weightComponent = heightRatio * weight; - float top = avgWeight * -0.8f; - top += weightComponent; - float bot = avgWeight * 0.40000004f; - float result = top / bot; - result *= 255f; - int value = (int)result; - int unsigned = value & ~(value >> 31); - return (byte)Math.Min(255, unsigned); - } - - public PK8 ConvertToPK8() - { - var pk8 = new PK8 - { - EncryptionConstant = EncryptionConstant, - Species = Species, - TID = TID, - SID = SID, - EXP = EXP, - PID = PID, - Ability = Ability, - AbilityNumber = AbilityNumber, - MarkValue = MarkValue & 0b1111_1111_1111, - Language = Language, - 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, - RelearnMove1 = RelearnMove1, - RelearnMove2 = RelearnMove2, - RelearnMove3 = RelearnMove3, - RelearnMove4 = RelearnMove4, - IV_HP = IV_HP, - IV_ATK = IV_ATK, - IV_DEF = IV_DEF, - IV_SPA = IV_SPA, - IV_SPD = IV_SPD, - IV_SPE = IV_SPE, - IsNicknamed = IsNicknamed, - FatefulEncounter = FatefulEncounter, - Gender = Gender, - Form = Form, - Nature = Nature, - Nickname = Nickname, - Version = Version, - OT_Name = OT_Name, - MetDate = MetDate, - Met_Location = Met_Location, - Ball = Ball, - Met_Level = Met_Level, - OT_Gender = OT_Gender, - HyperTrainFlags = HyperTrainFlags, - - // Memories don't exist in LGPE, and no memories are set on transfer. - - OT_Friendship = OT_Friendship, - - // No Ribbons or Markings on transfer. - - StatNature = Nature, - HeightScalar = HeightScalar, - WeightScalar = WeightScalar, - - Favorite = Favorite, - }; - - // Fix PP and Stats - pk8.Heal(); - - // Fix Checksum - pk8.RefreshChecksum(); - - return pk8; // Done! + float result = statSum * 6f; + result *= level; + result /= 100f; + return (int)result; } } -} \ No newline at end of file + + public int CPScalar + { + get + { + int friend = CurrentFriendship; // stats +10% depending on friendship! + float scalar = friend / 255f; + scalar /= 10f; + scalar++; + scalar *= 100f; + return (int)scalar; + } + } + + public int AwakeCP + { + get + { + var sum = this.AwakeningSum(); + if (sum == 0) + return 0; + var lvl = CurrentLevel; + float scalar = lvl * 4f; + scalar /= 100f; + scalar += 2f; + float result = sum * scalar; + return (int)result; + } + } + + public void ResetCP() => Stat_CP = CalcCP; + + public void ResetCalculatedValues() + { + ResetCP(); + ResetHeight(); + ResetWeight(); + } + + // Casts are as per the game code; they may seem redundant but every bit of precision matters? + // This still doesn't precisely match :( -- just use a tolerance check when updating. + // If anyone can figure out how to get all precision to match, feel free to update :) + public float HeightRatio => GetHeightRatio(HeightScalar); + public float WeightRatio => GetWeightRatio(WeightScalar); + + public float CalcHeightAbsolute => GetHeightAbsolute(PersonalInfo, HeightScalar); + public float CalcWeightAbsolute => GetWeightAbsolute(PersonalInfo, HeightScalar, WeightScalar); + + public void ResetHeight() => HeightAbsolute = CalcHeightAbsolute; + public void ResetWeight() => WeightAbsolute = CalcWeightAbsolute; + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private static float GetHeightRatio(int heightScalar) + { + // + 40%, -20 + float result = heightScalar / 255f; // 0x437F0000 + result *= 0.79999995f; // 0x3F4CCCCC + result += 0.6f; // 0x3F19999A + return result; + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private static float GetWeightRatio(int weightScalar) + { + // +/- 20% + float result = weightScalar / 255f; // 0x437F0000 + result *= 0.40000004f; // 0x3ECCCCCE + result += 0.8f; // 0x3F4CCCCD + return result; + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static float GetHeightAbsolute(PersonalInfo p, int heightScalar) + { + float HeightRatio = GetHeightRatio(heightScalar); + return HeightRatio * p.Height; + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static float GetWeightAbsolute(PersonalInfo p, int heightScalar, int weightScalar) + { + float HeightRatio = GetHeightRatio(heightScalar); + float WeightRatio = GetWeightRatio(weightScalar); + + float weight = WeightRatio * p.Weight; + return HeightRatio * weight; + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static byte GetHeightScalar(float height, int avgHeight) + { + // height is already *100 + float biasH = avgHeight * -0.6f; + float biasL = avgHeight * 0.79999995f; + float numerator = biasH + height; + float result = numerator / biasL; + result *= 255f; + int value = (int)result; + int unsigned = value & ~(value >> 31); + return (byte)Math.Min(255, unsigned); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static byte GetWeightScalar(float height, float weight, int avgHeight, int avgWeight) + { + // height is already *100 + // weight is already *10 + float heightRatio = height / avgHeight; + float weightComponent = heightRatio * weight; + float top = avgWeight * -0.8f; + top += weightComponent; + float bot = avgWeight * 0.40000004f; + float result = top / bot; + result *= 255f; + int value = (int)result; + int unsigned = value & ~(value >> 31); + return (byte)Math.Min(255, unsigned); + } + + public PK8 ConvertToPK8() + { + var pk8 = new PK8 + { + EncryptionConstant = EncryptionConstant, + Species = Species, + TID = TID, + SID = SID, + EXP = EXP, + PID = PID, + Ability = Ability, + AbilityNumber = AbilityNumber, + MarkValue = MarkValue & 0b1111_1111_1111, + Language = Language, + 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, + RelearnMove1 = RelearnMove1, + RelearnMove2 = RelearnMove2, + RelearnMove3 = RelearnMove3, + RelearnMove4 = RelearnMove4, + IV_HP = IV_HP, + IV_ATK = IV_ATK, + IV_DEF = IV_DEF, + IV_SPA = IV_SPA, + IV_SPD = IV_SPD, + IV_SPE = IV_SPE, + IsNicknamed = IsNicknamed, + FatefulEncounter = FatefulEncounter, + Gender = Gender, + Form = Form, + Nature = Nature, + Nickname = Nickname, + Version = Version, + OT_Name = OT_Name, + MetDate = MetDate, + Met_Location = Met_Location, + Ball = Ball, + Met_Level = Met_Level, + OT_Gender = OT_Gender, + HyperTrainFlags = HyperTrainFlags, + + // Memories don't exist in LGPE, and no memories are set on transfer. + + OT_Friendship = OT_Friendship, + + // No Ribbons or Markings on transfer. + + StatNature = Nature, + HeightScalar = HeightScalar, + WeightScalar = WeightScalar, + + Favorite = Favorite, + }; + + // Fix PP and Stats + pk8.Heal(); + + // Fix Checksum + pk8.RefreshChecksum(); + + return pk8; // Done! + } +} diff --git a/PKHeX.Core/PKM/PK1.cs b/PKHeX.Core/PKM/PK1.cs index a55a90c2c..b52940c8f 100644 --- a/PKHeX.Core/PKM/PK1.cs +++ b/PKHeX.Core/PKM/PK1.cs @@ -2,242 +2,241 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 1 format. +public sealed class PK1 : GBPKML { - /// Generation 1 format. - public sealed class PK1 : GBPKML + public override PersonalInfo PersonalInfo => PersonalTable.Y[Species]; + + public override bool Valid => Species <= 151 && (Data[0] == 0 || Species != 0); + + public override int SIZE_PARTY => PokeCrypto.SIZE_1PARTY; + public override int SIZE_STORED => PokeCrypto.SIZE_1STORED; + public override bool Korean => false; + + public override EntityContext Context => EntityContext.Gen1; + + public PK1(bool jp = false) : base(PokeCrypto.SIZE_1PARTY, jp) { } + public PK1(byte[] decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { } + + private static byte[] EnsurePartySize(byte[] data) { - public override PersonalInfo PersonalInfo => PersonalTable.Y[Species]; + if (data.Length != PokeCrypto.SIZE_1PARTY) + Array.Resize(ref data, PokeCrypto.SIZE_1PARTY); + return data; + } - public override bool Valid => Species <= 151 && (Data[0] == 0 || Species != 0); + public override PKM Clone() + { + var clone = new PK1((byte[])Data.Clone(), Japanese); + OT_Trash.CopyTo(clone.OT_Trash); + Nickname_Trash.CopyTo(clone.Nickname_Trash); + return clone; + } - public override int SIZE_PARTY => PokeCrypto.SIZE_1PARTY; - public override int SIZE_STORED => PokeCrypto.SIZE_1STORED; - public override bool Korean => false; + protected override byte[] Encrypt() => new PokeList1(this).Write(); - public override EntityContext Context => EntityContext.Gen1; + #region Stored Attributes + public byte SpeciesID1 { get => Data[0]; set => Data[0] = value; } // raw access + public override int Species { get => SpeciesConverter.GetG1Species(SpeciesID1); set => SetSpeciesValues(value); } + public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x1)); set => WriteUInt16BigEndian(Data.AsSpan(0x1), (ushort)value); } + public int Stat_LevelBox { get => Data[3]; set => Data[3] = (byte)value; } + public override int Status_Condition { get => Data[4]; set => Data[4] = (byte)value; } + public int Type_A { get => Data[5]; set => Data[5] = (byte)value; } + public int Type_B { get => Data[6]; set => Data[6] = (byte)value; } + public int Catch_Rate { get => Data[7]; set => Data[7] = (byte)value; } + public override int Move1 { get => Data[8]; set => Data[8] = (byte)value; } + public override int Move2 { get => Data[9]; set => Data[9] = (byte)value; } + public override int Move3 { get => Data[10]; set => Data[10] = (byte)value; } + public override int Move4 { get => Data[11]; set => Data[11] = (byte)value; } + public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(0xC)); set => WriteUInt16BigEndian(Data.AsSpan(0xC), (ushort)value); } + public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0xE)) >> 8; set => WriteUInt32BigEndian(Data.AsSpan(0xE), (value << 8) | Data[0x11]); } + public override int EV_HP { get => ReadUInt16BigEndian(Data.AsSpan(0x11)); set => WriteUInt16BigEndian(Data.AsSpan(0x11), (ushort)value); } + public override int EV_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x13)); set => WriteUInt16BigEndian(Data.AsSpan(0x13), (ushort)value); } + public override int EV_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x15)); set => WriteUInt16BigEndian(Data.AsSpan(0x15), (ushort)value); } + public override int EV_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x17)); set => WriteUInt16BigEndian(Data.AsSpan(0x17), (ushort)value); } + public override int EV_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x19)); set => WriteUInt16BigEndian(Data.AsSpan(0x19), (ushort)value); } + public override ushort DV16 { get => ReadUInt16BigEndian(Data.AsSpan(0x1B)); set => WriteUInt16BigEndian(Data.AsSpan(0x1B), value); } + public override int Move1_PP { get => Data[0x1D] & 0x3F; set => Data[0x1D] = (byte)((Data[0x1D] & 0xC0) | Math.Min(63, value)); } + public override int Move2_PP { get => Data[0x1E] & 0x3F; set => Data[0x1E] = (byte)((Data[0x1E] & 0xC0) | Math.Min(63, value)); } + public override int Move3_PP { get => Data[0x1F] & 0x3F; set => Data[0x1F] = (byte)((Data[0x1F] & 0xC0) | Math.Min(63, value)); } + public override int Move4_PP { get => Data[0x20] & 0x3F; set => Data[0x20] = (byte)((Data[0x20] & 0xC0) | Math.Min(63, value)); } + public override int Move1_PPUps { get => (Data[0x1D] & 0xC0) >> 6; set => Data[0x1D] = (byte)((Data[0x1D] & 0x3F) | ((value & 0x3) << 6)); } + public override int Move2_PPUps { get => (Data[0x1E] & 0xC0) >> 6; set => Data[0x1E] = (byte)((Data[0x1E] & 0x3F) | ((value & 0x3) << 6)); } + public override int Move3_PPUps { get => (Data[0x1F] & 0xC0) >> 6; set => Data[0x1F] = (byte)((Data[0x1F] & 0x3F) | ((value & 0x3) << 6)); } + public override int Move4_PPUps { get => (Data[0x20] & 0xC0) >> 6; set => Data[0x20] = (byte)((Data[0x20] & 0x3F) | ((value & 0x3) << 6)); } + #endregion - public PK1(bool jp = false) : base(PokeCrypto.SIZE_1PARTY, jp) { } - public PK1(byte[] decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { } + #region Party Attributes + public override int Stat_Level { get => Data[0x21]; set => Stat_LevelBox = Data[0x21] = (byte)value; } + public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x22)); set => WriteUInt16BigEndian(Data.AsSpan(0x22), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x24)); set => WriteUInt16BigEndian(Data.AsSpan(0x24), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x26)); set => WriteUInt16BigEndian(Data.AsSpan(0x26), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x28)); set => WriteUInt16BigEndian(Data.AsSpan(0x28), (ushort)value); } + public int Stat_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x2A)); set => WriteUInt16BigEndian(Data.AsSpan(0x2A), (ushort)value); } + // Leave SPA and SPD as alias for SPC + public override int Stat_SPA { get => Stat_SPC; set => Stat_SPC = value; } + public override int Stat_SPD { get => Stat_SPC; set { } } + #endregion - private static byte[] EnsurePartySize(byte[] data) + public static bool IsCatchRateHeldItem(int rate) => rate == 0 || Array.IndexOf(Legal.HeldItems_GSC, (ushort)rate) >= 0; + + private static bool IsCatchRatePreEvolutionRate(int baseSpecies, int finalSpecies, int rate) + { + for (int species = baseSpecies; species <= finalSpecies; species++) { - if (data.Length != PokeCrypto.SIZE_1PARTY) - Array.Resize(ref data, PokeCrypto.SIZE_1PARTY); - return data; - } - - public override PKM Clone() - { - var clone = new PK1((byte[])Data.Clone(), Japanese); - OT_Trash.CopyTo(clone.OT_Trash); - Nickname_Trash.CopyTo(clone.Nickname_Trash); - return clone; - } - - protected override byte[] Encrypt() => new PokeList1(this).Write(); - - #region Stored Attributes - public byte SpeciesID1 { get => Data[0]; set => Data[0] = value; } // raw access - public override int Species { get => SpeciesConverter.GetG1Species(SpeciesID1); set => SetSpeciesValues(value); } - public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x1)); set => WriteUInt16BigEndian(Data.AsSpan(0x1), (ushort)value); } - public int Stat_LevelBox { get => Data[3]; set => Data[3] = (byte)value; } - public override int Status_Condition { get => Data[4]; set => Data[4] = (byte)value; } - public int Type_A { get => Data[5]; set => Data[5] = (byte)value; } - public int Type_B { get => Data[6]; set => Data[6] = (byte)value; } - public int Catch_Rate { get => Data[7]; set => Data[7] = (byte)value; } - public override int Move1 { get => Data[8]; set => Data[8] = (byte)value; } - public override int Move2 { get => Data[9]; set => Data[9] = (byte)value; } - public override int Move3 { get => Data[10]; set => Data[10] = (byte)value; } - public override int Move4 { get => Data[11]; set => Data[11] = (byte)value; } - public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(0xC)); set => WriteUInt16BigEndian(Data.AsSpan(0xC), (ushort)value); } - public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0xE)) >> 8; set => WriteUInt32BigEndian(Data.AsSpan(0xE), (value << 8) | Data[0x11]); } - public override int EV_HP { get => ReadUInt16BigEndian(Data.AsSpan(0x11)); set => WriteUInt16BigEndian(Data.AsSpan(0x11), (ushort)value); } - public override int EV_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x13)); set => WriteUInt16BigEndian(Data.AsSpan(0x13), (ushort)value); } - public override int EV_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x15)); set => WriteUInt16BigEndian(Data.AsSpan(0x15), (ushort)value); } - public override int EV_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x17)); set => WriteUInt16BigEndian(Data.AsSpan(0x17), (ushort)value); } - public override int EV_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x19)); set => WriteUInt16BigEndian(Data.AsSpan(0x19), (ushort)value); } - public override ushort DV16 { get => ReadUInt16BigEndian(Data.AsSpan(0x1B)); set => WriteUInt16BigEndian(Data.AsSpan(0x1B), value); } - public override int Move1_PP { get => Data[0x1D] & 0x3F; set => Data[0x1D] = (byte)((Data[0x1D] & 0xC0) | Math.Min(63, value)); } - public override int Move2_PP { get => Data[0x1E] & 0x3F; set => Data[0x1E] = (byte)((Data[0x1E] & 0xC0) | Math.Min(63, value)); } - public override int Move3_PP { get => Data[0x1F] & 0x3F; set => Data[0x1F] = (byte)((Data[0x1F] & 0xC0) | Math.Min(63, value)); } - public override int Move4_PP { get => Data[0x20] & 0x3F; set => Data[0x20] = (byte)((Data[0x20] & 0xC0) | Math.Min(63, value)); } - public override int Move1_PPUps { get => (Data[0x1D] & 0xC0) >> 6; set => Data[0x1D] = (byte)((Data[0x1D] & 0x3F) | ((value & 0x3) << 6)); } - public override int Move2_PPUps { get => (Data[0x1E] & 0xC0) >> 6; set => Data[0x1E] = (byte)((Data[0x1E] & 0x3F) | ((value & 0x3) << 6)); } - public override int Move3_PPUps { get => (Data[0x1F] & 0xC0) >> 6; set => Data[0x1F] = (byte)((Data[0x1F] & 0x3F) | ((value & 0x3) << 6)); } - public override int Move4_PPUps { get => (Data[0x20] & 0xC0) >> 6; set => Data[0x20] = (byte)((Data[0x20] & 0x3F) | ((value & 0x3) << 6)); } - #endregion - - #region Party Attributes - public override int Stat_Level { get => Data[0x21]; set => Stat_LevelBox = Data[0x21] = (byte)value; } - public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x22)); set => WriteUInt16BigEndian(Data.AsSpan(0x22), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x24)); set => WriteUInt16BigEndian(Data.AsSpan(0x24), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x26)); set => WriteUInt16BigEndian(Data.AsSpan(0x26), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x28)); set => WriteUInt16BigEndian(Data.AsSpan(0x28), (ushort)value); } - public int Stat_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x2A)); set => WriteUInt16BigEndian(Data.AsSpan(0x2A), (ushort)value); } - // Leave SPA and SPD as alias for SPC - public override int Stat_SPA { get => Stat_SPC; set => Stat_SPC = value; } - public override int Stat_SPD { get => Stat_SPC; set { } } - #endregion - - public static bool IsCatchRateHeldItem(int rate) => rate == 0 || Array.IndexOf(Legal.HeldItems_GSC, (ushort)rate) >= 0; - - private static bool IsCatchRatePreEvolutionRate(int baseSpecies, int finalSpecies, int rate) - { - for (int species = baseSpecies; species <= finalSpecies; species++) - { - if (rate == PersonalTable.RB[species].CatchRate || rate == PersonalTable.Y[species].CatchRate) - return true; - } - return false; - } - - private void SetSpeciesValues(int value) - { - var updated = SpeciesConverter.SetG1Species(value); - if (SpeciesID1 == updated) - return; - - SpeciesID1 = updated; - - Type_A = PersonalInfo.Type1; - Type_B = PersonalInfo.Type2; - - // Before updating catch rate, check if non-standard - if (IsValidCatchRateAnyPreEvo(value, Catch_Rate)) - return; - - // Matches nothing possible; just reset to current Species' rate. - Catch_Rate = PersonalTable.RB[value].CatchRate; - } - - private static bool IsValidCatchRateAnyPreEvo(int species, int rate) - { - if (IsCatchRateHeldItem(rate)) + if (rate == PersonalTable.RB[species].CatchRate || rate == PersonalTable.Y[species].CatchRate) return true; - if (species == (int)Core.Species.Pikachu && rate == 0xA3) // Light Ball (starter) - return true; - - var table = EvolutionTree.Evolves1; - var baby = table.GetBaseSpeciesForm(species, 0); - return IsCatchRatePreEvolutionRate(baby, species, rate); } + return false; + } - public override int Version { get => (int)GameVersion.RBY; set { } } - public override int PKRS_Strain { get => 0; set { } } - public override int PKRS_Days { get => 0; set { } } - public override bool CanHoldItem(IReadOnlyList valid) => false; - public override int Met_Location { get => 0; set { } } - public override int OT_Gender { get => 0; set { } } - public override int Met_Level { get => 0; set { } } - public override int CurrentFriendship { get => 0; set { } } - public override bool IsEgg { get => false; set { } } - public override int HeldItem { get => 0; set { } } - public override int OT_Friendship { get => 0; set { } } + private void SetSpeciesValues(int value) + { + var updated = SpeciesConverter.SetG1Species(value); + if (SpeciesID1 == updated) + return; - // Maximums - public override int MaxMoveID => Legal.MaxMoveID_1; - public override int MaxSpeciesID => Legal.MaxSpeciesID_1; - public override int MaxAbilityID => Legal.MaxAbilityID_1; - public override int MaxItemID => Legal.MaxItemID_1; + SpeciesID1 = updated; - // Extra - public int Gen2Item => ItemConverter.GetItemFuture1(Catch_Rate); + Type_A = PersonalInfo.Type1; + Type_B = PersonalInfo.Type2; - public PK2 ConvertToPK2() + // Before updating catch rate, check if non-standard + if (IsValidCatchRateAnyPreEvo(value, Catch_Rate)) + return; + + // Matches nothing possible; just reset to current Species' rate. + Catch_Rate = PersonalTable.RB[value].CatchRate; + } + + private static bool IsValidCatchRateAnyPreEvo(int species, int rate) + { + if (IsCatchRateHeldItem(rate)) + return true; + if (species == (int)Core.Species.Pikachu && rate == 0xA3) // Light Ball (starter) + return true; + + var table = EvolutionTree.Evolves1; + var baby = table.GetBaseSpeciesForm(species, 0); + return IsCatchRatePreEvolutionRate(baby, species, rate); + } + + public override int Version { get => (int)GameVersion.RBY; set { } } + public override int PKRS_Strain { get => 0; set { } } + public override int PKRS_Days { get => 0; set { } } + public override bool CanHoldItem(IReadOnlyList valid) => false; + public override int Met_Location { get => 0; set { } } + public override int OT_Gender { get => 0; set { } } + public override int Met_Level { get => 0; set { } } + public override int CurrentFriendship { get => 0; set { } } + public override bool IsEgg { get => false; set { } } + public override int HeldItem { get => 0; set { } } + public override int OT_Friendship { get => 0; set { } } + + // Maximums + public override int MaxMoveID => Legal.MaxMoveID_1; + public override int MaxSpeciesID => Legal.MaxSpeciesID_1; + public override int MaxAbilityID => Legal.MaxAbilityID_1; + public override int MaxItemID => Legal.MaxItemID_1; + + // Extra + public int Gen2Item => ItemConverter.GetItemFuture1(Catch_Rate); + + public PK2 ConvertToPK2() + { + PK2 pk2 = new(Japanese) {Species = Species}; + Array.Copy(Data, 0x7, pk2.Data, 0x1, 0x1A); + RawOT.CopyTo(pk2.RawOT, 0); + RawNickname.CopyTo(pk2.RawNickname, 0); + + pk2.HeldItem = Gen2Item; + pk2.CurrentFriendship = pk2.PersonalInfo.BaseFriendship; + pk2.Stat_Level = CurrentLevel; + + return pk2; + } + + public PK7 ConvertToPK7() + { + var rnd = Util.Rand; + var pk7 = new PK7 { - PK2 pk2 = new(Japanese) {Species = Species}; - Array.Copy(Data, 0x7, pk2.Data, 0x1, 0x1A); - RawOT.CopyTo(pk2.RawOT, 0); - RawNickname.CopyTo(pk2.RawNickname, 0); + EncryptionConstant = rnd.Rand32(), + Species = Species, + TID = TID, + CurrentLevel = CurrentLevel, + EXP = EXP, + Met_Level = CurrentLevel, + Nature = Experience.GetNatureVC(EXP), + PID = rnd.Rand32(), + Ball = 4, + MetDate = DateTime.Now, + Version = (int)GameVersion.RD, // Default to red + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + Move1_PPUps = Move1_PPUps, + Move2_PPUps = Move2_PPUps, + Move3_PPUps = Move3_PPUps, + Move4_PPUps = Move4_PPUps, + Met_Location = Locations.Transfer1, // "Kanto region", hardcoded. + Gender = Gender, + OT_Name = StringConverter12Transporter.GetString(RawOT, Japanese), + IsNicknamed = false, - pk2.HeldItem = Gen2Item; - pk2.CurrentFriendship = pk2.PersonalInfo.BaseFriendship; - pk2.Stat_Level = CurrentLevel; + CurrentHandler = 1, + HT_Name = RecentTrainerCache.OT_Name, + HT_Gender = RecentTrainerCache.OT_Gender, + }; + RecentTrainerCache.SetConsoleRegionData3DS(pk7); + RecentTrainerCache.SetFirstCountryRegion(pk7); + pk7.HealPP(); + var lang = TransferLanguage(RecentTrainerCache.Language); + pk7.Language = lang; + pk7.Nickname = SpeciesName.GetSpeciesNameGeneration(pk7.Species, lang, pk7.Format); + if (RawOT[0] == StringConverter12.G1TradeOTCode) // In-game Trade + pk7.OT_Name = StringConverter12.G1TradeOTName[lang]; + pk7.OT_Friendship = pk7.HT_Friendship = PersonalTable.SM[Species].BaseFriendship; - return pk2; - } + // IVs + Span finalIVs = stackalloc int[6]; + int flawless = Species == (int)Core.Species.Mew ? 5 : 3; + for (var i = 0; i < finalIVs.Length; i++) + finalIVs[i] = rnd.Next(32); + for (var i = 0; i < flawless; i++) + finalIVs[i] = 31; + Util.Shuffle(finalIVs); + pk7.SetIVs(finalIVs); - public PK7 ConvertToPK7() + switch (IsShiny ? Shiny.Always : Shiny.Never) { - var rnd = Util.Rand; - var pk7 = new PK7 - { - EncryptionConstant = rnd.Rand32(), - Species = Species, - TID = TID, - CurrentLevel = CurrentLevel, - EXP = EXP, - Met_Level = CurrentLevel, - Nature = Experience.GetNatureVC(EXP), - PID = rnd.Rand32(), - Ball = 4, - MetDate = DateTime.Now, - Version = (int)GameVersion.RD, // Default to red - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - Move1_PPUps = Move1_PPUps, - Move2_PPUps = Move2_PPUps, - Move3_PPUps = Move3_PPUps, - Move4_PPUps = Move4_PPUps, - Met_Location = Locations.Transfer1, // "Kanto region", hardcoded. - Gender = Gender, - OT_Name = StringConverter12Transporter.GetString(RawOT, Japanese), - IsNicknamed = false, - - CurrentHandler = 1, - HT_Name = RecentTrainerCache.OT_Name, - HT_Gender = RecentTrainerCache.OT_Gender, - }; - RecentTrainerCache.SetConsoleRegionData3DS(pk7); - RecentTrainerCache.SetFirstCountryRegion(pk7); - pk7.HealPP(); - var lang = TransferLanguage(RecentTrainerCache.Language); - pk7.Language = lang; - pk7.Nickname = SpeciesName.GetSpeciesNameGeneration(pk7.Species, lang, pk7.Format); - if (RawOT[0] == StringConverter12.G1TradeOTCode) // In-game Trade - pk7.OT_Name = StringConverter12.G1TradeOTName[lang]; - pk7.OT_Friendship = pk7.HT_Friendship = PersonalTable.SM[Species].BaseFriendship; - - // IVs - Span finalIVs = stackalloc int[6]; - int flawless = Species == (int)Core.Species.Mew ? 5 : 3; - for (var i = 0; i < finalIVs.Length; i++) - finalIVs[i] = rnd.Next(32); - for (var i = 0; i < flawless; i++) - finalIVs[i] = 31; - Util.Shuffle(finalIVs); - pk7.SetIVs(finalIVs); - - switch (IsShiny ? Shiny.Always : Shiny.Never) - { - case Shiny.Always when !pk7.IsShiny: // Force Square - pk7.PID = (uint)(((pk7.TID ^ 0 ^ (pk7.PID & 0xFFFF) ^ 0) << 16) | (pk7.PID & 0xFFFF)); - break; - case Shiny.Never when pk7.IsShiny: // Force Not Shiny - pk7.PID ^= 0x1000_0000; - break; - } - - int abil = Legal.TransferSpeciesDefaultAbilityGen1(Species) ? 0 : 2; // Hidden - pk7.RefreshAbility(abil); // 0/1/2 (not 1/2/4) - - if (Species == (int)Core.Species.Mew) // Mew gets special treatment. - { - pk7.FatefulEncounter = true; - } - else if (IsNicknamedBank) - { - pk7.IsNicknamed = true; - pk7.Nickname = StringConverter12Transporter.GetString(RawNickname, Japanese); - } - - pk7.SetTradeMemoryHT6(bank:true); // oh no, memories on gen7 pkm - - pk7.RefreshChecksum(); - return pk7; + case Shiny.Always when !pk7.IsShiny: // Force Square + pk7.PID = (uint)(((pk7.TID ^ 0 ^ (pk7.PID & 0xFFFF) ^ 0) << 16) | (pk7.PID & 0xFFFF)); + break; + case Shiny.Never when pk7.IsShiny: // Force Not Shiny + pk7.PID ^= 0x1000_0000; + break; } + + int abil = Legal.TransferSpeciesDefaultAbilityGen1(Species) ? 0 : 2; // Hidden + pk7.RefreshAbility(abil); // 0/1/2 (not 1/2/4) + + if (Species == (int)Core.Species.Mew) // Mew gets special treatment. + { + pk7.FatefulEncounter = true; + } + else if (IsNicknamedBank) + { + pk7.IsNicknamed = true; + pk7.Nickname = StringConverter12Transporter.GetString(RawNickname, Japanese); + } + + pk7.SetTradeMemoryHT6(bank:true); // oh no, memories on gen7 pk + + pk7.RefreshChecksum(); + return pk7; } } diff --git a/PKHeX.Core/PKM/PK2.cs b/PKHeX.Core/PKM/PK2.cs index 1b89e97c9..3540a876e 100644 --- a/PKHeX.Core/PKM/PK2.cs +++ b/PKHeX.Core/PKM/PK2.cs @@ -1,264 +1,263 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 2 format. +public sealed class PK2 : GBPKML, ICaughtData2 { - /// Generation 2 format. - public sealed class PK2 : GBPKML, ICaughtData2 + public override PersonalInfo PersonalInfo => PersonalTable.C[Species]; + + internal const byte EggSpeciesValue = 0xFD; + public override bool Valid => Species is <= Legal.MaxSpeciesID_2 or EggSpeciesValue; // egg + + public override int SIZE_PARTY => PokeCrypto.SIZE_2PARTY; + public override int SIZE_STORED => PokeCrypto.SIZE_2STORED; + public override bool Korean => !Japanese && RawOT[0] <= 0xB; + + public override EntityContext Context => EntityContext.Gen2; + + public PK2(bool jp = false) : base(PokeCrypto.SIZE_2PARTY, jp) { } + public PK2(byte[] decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { } + + private static byte[] EnsurePartySize(byte[] data) { - public override PersonalInfo PersonalInfo => PersonalTable.C[Species]; + if (data.Length != PokeCrypto.SIZE_2PARTY) + Array.Resize(ref data, PokeCrypto.SIZE_2PARTY); + return data; + } - internal const byte EggSpeciesValue = 0xFD; - public override bool Valid => Species is <= Legal.MaxSpeciesID_2 or EggSpeciesValue; // egg + public override PKM Clone() + { + var clone = new PK2((byte[])Data.Clone(), Japanese) { IsEgg = IsEgg }; + OT_Trash.CopyTo(clone.OT_Trash); + Nickname_Trash.CopyTo(clone.Nickname_Trash); + return clone; + } - public override int SIZE_PARTY => PokeCrypto.SIZE_2PARTY; - public override int SIZE_STORED => PokeCrypto.SIZE_2STORED; - public override bool Korean => !Japanese && RawOT[0] <= 0xB; + protected override byte[] Encrypt() => new PokeList2(this).Write(); - public override EntityContext Context => EntityContext.Gen2; + #region Stored Attributes + public override int Species { get => Data[0]; set => Data[0] = (byte)value; } + public override int SpriteItem => ItemConverter.GetItemFuture2((byte)HeldItem); + public override int HeldItem { get => Data[0x1]; set => Data[0x1] = (byte)value; } + public override int Move1 { get => Data[2]; set => Data[2] = (byte)value; } + public override int Move2 { get => Data[3]; set => Data[3] = (byte)value; } + public override int Move3 { get => Data[4]; set => Data[4] = (byte)value; } + public override int Move4 { get => Data[5]; set => Data[5] = (byte)value; } + public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(6)); set => WriteUInt16BigEndian(Data.AsSpan(6), (ushort)value); } + public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0x08)) >> 8; set => WriteUInt32BigEndian(Data.AsSpan(8), (value << 8) | Data[0xB]); } + public override int EV_HP { get => ReadUInt16BigEndian(Data.AsSpan(0x0B)); set => WriteUInt16BigEndian(Data.AsSpan(0xB), (ushort)value); } + public override int EV_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x0D)); set => WriteUInt16BigEndian(Data.AsSpan(0xD), (ushort)value); } + public override int EV_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x0F)); set => WriteUInt16BigEndian(Data.AsSpan(0xF), (ushort)value); } + public override int EV_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x11)); set => WriteUInt16BigEndian(Data.AsSpan(0x11), (ushort)value); } + public override int EV_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x13)); set => WriteUInt16BigEndian(Data.AsSpan(0x13), (ushort)value); } + public override ushort DV16 { get => ReadUInt16BigEndian(Data.AsSpan(0x15)); set => WriteUInt16BigEndian(Data.AsSpan(0x15), value); } + public override int Move1_PP { get => Data[0x17] & 0x3F; set => Data[0x17] = (byte)((Data[0x17] & 0xC0) | Math.Min(63, value)); } + public override int Move2_PP { get => Data[0x18] & 0x3F; set => Data[0x18] = (byte)((Data[0x18] & 0xC0) | Math.Min(63, value)); } + public override int Move3_PP { get => Data[0x19] & 0x3F; set => Data[0x19] = (byte)((Data[0x19] & 0xC0) | Math.Min(63, value)); } + public override int Move4_PP { get => Data[0x1A] & 0x3F; set => Data[0x1A] = (byte)((Data[0x1A] & 0xC0) | Math.Min(63, value)); } + public override int Move1_PPUps { get => (Data[0x17] & 0xC0) >> 6; set => Data[0x17] = (byte)((Data[0x17] & 0x3F) | ((value & 0x3) << 6)); } + public override int Move2_PPUps { get => (Data[0x18] & 0xC0) >> 6; set => Data[0x18] = (byte)((Data[0x18] & 0x3F) | ((value & 0x3) << 6)); } + public override int Move3_PPUps { get => (Data[0x19] & 0xC0) >> 6; set => Data[0x19] = (byte)((Data[0x19] & 0x3F) | ((value & 0x3) << 6)); } + public override int Move4_PPUps { get => (Data[0x1A] & 0xC0) >> 6; set => Data[0x1A] = (byte)((Data[0x1A] & 0x3F) | ((value & 0x3) << 6)); } + public override int CurrentFriendship { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } + private byte PKRS { get => Data[0x1C]; set => Data[0x1C] = value; } + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + // Crystal only Caught Data + public ushort CaughtData { get => ReadUInt16BigEndian(Data.AsSpan(0x1D)); set => WriteUInt16BigEndian(Data.AsSpan(0x1D), value); } + public int Met_TimeOfDay { get => (CaughtData >> 14) & 0x3; set => CaughtData = (ushort)((CaughtData & 0x3FFF) | ((value & 0x3) << 14)); } + public override int Met_Level { get => (CaughtData >> 8) & 0x3F; set => CaughtData = (ushort)((CaughtData & 0xC0FF) | ((value & 0x3F) << 8)); } + public override int OT_Gender { get => (CaughtData >> 7) & 1; set => CaughtData = (ushort)((CaughtData & 0xFF7F) | ((value & 1) << 7)); } + public override int Met_Location { get => CaughtData & 0x7F; set => CaughtData = (ushort)((CaughtData & 0xFF80) | (value & 0x7F)); } - public PK2(bool jp = false) : base(PokeCrypto.SIZE_2PARTY, jp) { } - public PK2(byte[] decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { } + public override int Stat_Level + { + get => Data[0x1F]; + set => Data[0x1F] = (byte)value; + } - private static byte[] EnsurePartySize(byte[] data) + #endregion + + #region Party Attributes + public override int Status_Condition { get => Data[0x20]; set => Data[0x20] = (byte)value; } + + public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x22)); set => WriteUInt16BigEndian(Data.AsSpan(0x22), (ushort)value); } + public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x24)); set => WriteUInt16BigEndian(Data.AsSpan(0x24), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x26)); set => WriteUInt16BigEndian(Data.AsSpan(0x26), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x28)); set => WriteUInt16BigEndian(Data.AsSpan(0x28), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x2A)); set => WriteUInt16BigEndian(Data.AsSpan(0x2A), (ushort)value); } + public override int Stat_SPA { get => ReadUInt16BigEndian(Data.AsSpan(0x2C)); set => WriteUInt16BigEndian(Data.AsSpan(0x2C), (ushort)value); } + public override int Stat_SPD { get => ReadUInt16BigEndian(Data.AsSpan(0x2E)); set => WriteUInt16BigEndian(Data.AsSpan(0x2E), (ushort)value); } + #endregion + + public override bool IsEgg { get; set; } + public override int OT_Friendship { get => CurrentFriendship; set => CurrentFriendship = value; } + public override bool HasOriginalMetLocation => CaughtData != 0; + public override int Version { get => (int)GameVersion.GSC; set { } } + + // Maximums + public override int MaxMoveID => Legal.MaxMoveID_2; + public override int MaxSpeciesID => Legal.MaxSpeciesID_2; + public override int MaxAbilityID => Legal.MaxAbilityID_2; + public override int MaxItemID => Legal.MaxItemID_2; + + public PK1 ConvertToPK1() + { + PK1 pk1 = new(Japanese); + Array.Copy(Data, 0x1, pk1.Data, 0x7, 0x1A); + pk1.Species = Species; // This will take care of Typing :) + + var lvl = Stat_Level; + if (lvl == 0) // no party stats (originated from box format), need to regenerate { - if (data.Length != PokeCrypto.SIZE_2PARTY) - Array.Resize(ref data, PokeCrypto.SIZE_2PARTY); - return data; + pk1.Stat_HPCurrent = GetStat(PersonalInfo.HP, IV_ATK, EV_ATK, Stat_Level); + pk1.Stat_Level = CurrentLevel; } - - public override PKM Clone() + else { - var clone = new PK2((byte[])Data.Clone(), Japanese) { IsEgg = IsEgg }; - OT_Trash.CopyTo(clone.OT_Trash); - Nickname_Trash.CopyTo(clone.Nickname_Trash); - return clone; + pk1.Stat_HPCurrent = Stat_HPCurrent; + pk1.Stat_Level = Stat_Level; } + // Status = 0 + OT_Trash.CopyTo(pk1.OT_Trash); + Nickname_Trash.CopyTo(pk1.Nickname_Trash); - protected override byte[] Encrypt() => new PokeList2(this).Write(); + pk1.ClearInvalidMoves(); - #region Stored Attributes - public override int Species { get => Data[0]; set => Data[0] = (byte)value; } - public override int SpriteItem => ItemConverter.GetItemFuture2((byte)HeldItem); - public override int HeldItem { get => Data[0x1]; set => Data[0x1] = (byte)value; } - public override int Move1 { get => Data[2]; set => Data[2] = (byte)value; } - public override int Move2 { get => Data[3]; set => Data[3] = (byte)value; } - public override int Move3 { get => Data[4]; set => Data[4] = (byte)value; } - public override int Move4 { get => Data[5]; set => Data[5] = (byte)value; } - public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(6)); set => WriteUInt16BigEndian(Data.AsSpan(6), (ushort)value); } - public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0x08)) >> 8; set => WriteUInt32BigEndian(Data.AsSpan(8), (value << 8) | Data[0xB]); } - public override int EV_HP { get => ReadUInt16BigEndian(Data.AsSpan(0x0B)); set => WriteUInt16BigEndian(Data.AsSpan(0xB), (ushort)value); } - public override int EV_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x0D)); set => WriteUInt16BigEndian(Data.AsSpan(0xD), (ushort)value); } - public override int EV_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x0F)); set => WriteUInt16BigEndian(Data.AsSpan(0xF), (ushort)value); } - public override int EV_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x11)); set => WriteUInt16BigEndian(Data.AsSpan(0x11), (ushort)value); } - public override int EV_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x13)); set => WriteUInt16BigEndian(Data.AsSpan(0x13), (ushort)value); } - public override ushort DV16 { get => ReadUInt16BigEndian(Data.AsSpan(0x15)); set => WriteUInt16BigEndian(Data.AsSpan(0x15), value); } - public override int Move1_PP { get => Data[0x17] & 0x3F; set => Data[0x17] = (byte)((Data[0x17] & 0xC0) | Math.Min(63, value)); } - public override int Move2_PP { get => Data[0x18] & 0x3F; set => Data[0x18] = (byte)((Data[0x18] & 0xC0) | Math.Min(63, value)); } - public override int Move3_PP { get => Data[0x19] & 0x3F; set => Data[0x19] = (byte)((Data[0x19] & 0xC0) | Math.Min(63, value)); } - public override int Move4_PP { get => Data[0x1A] & 0x3F; set => Data[0x1A] = (byte)((Data[0x1A] & 0xC0) | Math.Min(63, value)); } - public override int Move1_PPUps { get => (Data[0x17] & 0xC0) >> 6; set => Data[0x17] = (byte)((Data[0x17] & 0x3F) | ((value & 0x3) << 6)); } - public override int Move2_PPUps { get => (Data[0x18] & 0xC0) >> 6; set => Data[0x18] = (byte)((Data[0x18] & 0x3F) | ((value & 0x3) << 6)); } - public override int Move3_PPUps { get => (Data[0x19] & 0xC0) >> 6; set => Data[0x19] = (byte)((Data[0x19] & 0x3F) | ((value & 0x3) << 6)); } - public override int Move4_PPUps { get => (Data[0x1A] & 0xC0) >> 6; set => Data[0x1A] = (byte)((Data[0x1A] & 0x3F) | ((value & 0x3) << 6)); } - public override int CurrentFriendship { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } - private byte PKRS { get => Data[0x1C]; set => Data[0x1C] = value; } - public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); } - // Crystal only Caught Data - public ushort CaughtData { get => ReadUInt16BigEndian(Data.AsSpan(0x1D)); set => WriteUInt16BigEndian(Data.AsSpan(0x1D), value); } - public int Met_TimeOfDay { get => (CaughtData >> 14) & 0x3; set => CaughtData = (ushort)((CaughtData & 0x3FFF) | ((value & 0x3) << 14)); } - public override int Met_Level { get => (CaughtData >> 8) & 0x3F; set => CaughtData = (ushort)((CaughtData & 0xC0FF) | ((value & 0x3F) << 8)); } - public override int OT_Gender { get => (CaughtData >> 7) & 1; set => CaughtData = (ushort)((CaughtData & 0xFF7F) | ((value & 1) << 7)); } - public override int Met_Location { get => CaughtData & 0x7F; set => CaughtData = (ushort)((CaughtData & 0xFF80) | (value & 0x7F)); } + return pk1; + } - public override int Stat_Level - { - get => Data[0x1F]; - set => Data[0x1F] = (byte)value; - } - - #endregion - - #region Party Attributes - public override int Status_Condition { get => Data[0x20]; set => Data[0x20] = (byte)value; } - - public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x22)); set => WriteUInt16BigEndian(Data.AsSpan(0x22), (ushort)value); } - public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x24)); set => WriteUInt16BigEndian(Data.AsSpan(0x24), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x26)); set => WriteUInt16BigEndian(Data.AsSpan(0x26), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x28)); set => WriteUInt16BigEndian(Data.AsSpan(0x28), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x2A)); set => WriteUInt16BigEndian(Data.AsSpan(0x2A), (ushort)value); } - public override int Stat_SPA { get => ReadUInt16BigEndian(Data.AsSpan(0x2C)); set => WriteUInt16BigEndian(Data.AsSpan(0x2C), (ushort)value); } - public override int Stat_SPD { get => ReadUInt16BigEndian(Data.AsSpan(0x2E)); set => WriteUInt16BigEndian(Data.AsSpan(0x2E), (ushort)value); } - #endregion - - public override bool IsEgg { get; set; } - public override int OT_Friendship { get => CurrentFriendship; set => CurrentFriendship = value; } - public override bool HasOriginalMetLocation => CaughtData != 0; - public override int Version { get => (int)GameVersion.GSC; set { } } - - // Maximums - public override int MaxMoveID => Legal.MaxMoveID_2; - public override int MaxSpeciesID => Legal.MaxSpeciesID_2; - public override int MaxAbilityID => Legal.MaxAbilityID_2; - public override int MaxItemID => Legal.MaxItemID_2; - - public PK1 ConvertToPK1() - { - PK1 pk1 = new(Japanese); - Array.Copy(Data, 0x1, pk1.Data, 0x7, 0x1A); - pk1.Species = Species; // This will take care of Typing :) - - var lvl = Stat_Level; - if (lvl == 0) // no party stats (originated from box format), need to regenerate - { - pk1.Stat_HPCurrent = GetStat(PersonalInfo.HP, IV_ATK, EV_ATK, Stat_Level); - pk1.Stat_Level = CurrentLevel; - } - else - { - pk1.Stat_HPCurrent = Stat_HPCurrent; - pk1.Stat_Level = Stat_Level; - } - // Status = 0 - OT_Trash.CopyTo(pk1.OT_Trash); - Nickname_Trash.CopyTo(pk1.Nickname_Trash); - - pk1.ClearInvalidMoves(); - - return pk1; - } - - public PK7 ConvertToPK7() - { - var rnd = Util.Rand; - var pk7 = new PK7 - { - EncryptionConstant = rnd.Rand32(), - Species = Species, - TID = TID, - CurrentLevel = CurrentLevel, - EXP = EXP, - Met_Level = CurrentLevel, - Nature = Experience.GetNatureVC(EXP), - PID = rnd.Rand32(), - Ball = 4, - MetDate = DateTime.Now, - Version = HasOriginalMetLocation ? (int)GameVersion.C : (int)GameVersion.SI, - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - Move1_PPUps = Move1_PPUps, - Move2_PPUps = Move2_PPUps, - Move3_PPUps = Move3_PPUps, - Move4_PPUps = Move4_PPUps, - Met_Location = Locations.Transfer2, // "Johto region", hardcoded. - Gender = Gender, - IsNicknamed = false, - Form = Form, - - CurrentHandler = 1, - HT_Name = RecentTrainerCache.OT_Name, - HT_Gender = RecentTrainerCache.OT_Gender, - }; - RecentTrainerCache.SetConsoleRegionData3DS(pk7); - RecentTrainerCache.SetFirstCountryRegion(pk7); - pk7.HealPP(); - var lang = TransferLanguage(RecentTrainerCache.Language); - pk7.Language = lang; - pk7.Nickname = SpeciesName.GetSpeciesNameGeneration(pk7.Species, lang, pk7.Format); - - // IVs - var special = Species is 151 or 251; - Span finalIVs = stackalloc int[6]; - int flawless = special ? 5 : 3; - for (var i = 0; i < finalIVs.Length; i++) - finalIVs[i] = rnd.Next(32); - for (var i = 0; i < flawless; i++) - finalIVs[i] = 31; - Util.Shuffle(finalIVs); - pk7.SetIVs(finalIVs); - - switch (IsShiny ? Shiny.Always : Shiny.Never) - { - case Shiny.Always when !pk7.IsShiny: // Force Square - pk7.PID = (uint)(((pk7.TID ^ 0 ^ (pk7.PID & 0xFFFF) ^ 0) << 16) | (pk7.PID & 0xFFFF)); - break; - case Shiny.Never when pk7.IsShiny: // Force Not Shiny - pk7.PID ^= 0x1000_0000; - break; - } - - int abil = Legal.TransferSpeciesDefaultAbilityGen2(Species) ? 0 : 2; // Hidden - pk7.RefreshAbility(abil); // 0/1/2 (not 1/2/4) - - if (special) - { - pk7.FatefulEncounter = true; - } - else if (IsNicknamedBank) - { - pk7.IsNicknamed = true; - pk7.Nickname = Korean ? Nickname : StringConverter12Transporter.GetString(RawNickname, Japanese); - } - if (RawOT[0] == StringConverter12.G1TradeOTCode) // In-game Trade - pk7.OT_Name = StringConverter12.G1TradeOTName[lang]; - else - pk7.OT_Name = Korean ? OT_Name : StringConverter12Transporter.GetString(RawOT, Japanese); - pk7.OT_Gender = OT_Gender; // Crystal - pk7.OT_Friendship = pk7.HT_Friendship = PersonalTable.SM[Species].BaseFriendship; - - pk7.SetTradeMemoryHT6(bank: true); // oh no, memories on gen7 pkm - - // Dizzy Punch cannot be transferred - { - var index = pk7.GetMoveIndex(146); // Dizzy Punch - if (index != -1) - { - pk7.SetMove(index, 0); - pk7.FixMoves(); - } - } - - pk7.RefreshChecksum(); - return pk7; - } - - public SK2 ConvertToSK2() => new(Japanese) + public PK7 ConvertToPK7() + { + var rnd = Util.Rand; + var pk7 = new PK7 { + EncryptionConstant = rnd.Rand32(), Species = Species, - HeldItem = HeldItem, + TID = TID, + CurrentLevel = CurrentLevel, + EXP = EXP, + Met_Level = CurrentLevel, + Nature = Experience.GetNatureVC(EXP), + PID = rnd.Rand32(), + Ball = 4, + MetDate = DateTime.Now, + Version = HasOriginalMetLocation ? (int)GameVersion.C : (int)GameVersion.SI, Move1 = Move1, Move2 = Move2, Move3 = Move3, Move4 = Move4, - TID = TID, - EXP = EXP, - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPE = EV_SPE, - EV_SPC = EV_SPC, - DV16 = DV16, - Move1_PP = Move1_PP, - Move2_PP = Move2_PP, - Move3_PP = Move3_PP, - Move4_PP = Move4_PP, Move1_PPUps = Move1_PPUps, Move2_PPUps = Move2_PPUps, Move3_PPUps = Move3_PPUps, Move4_PPUps = Move4_PPUps, - CurrentFriendship = CurrentFriendship, - IsEgg = IsEgg, - Stat_Level = Stat_Level, - PKRS_Days = PKRS_Days, - PKRS_Strain = PKRS_Strain, - CaughtData = CaughtData, + Met_Location = Locations.Transfer2, // "Johto region", hardcoded. + Gender = Gender, + IsNicknamed = false, + Form = Form, - // Only copies until first 0x50 terminator, but just copy everything - Nickname = Nickname, - OT_Name = OT_Name, + CurrentHandler = 1, + HT_Name = RecentTrainerCache.OT_Name, + HT_Gender = RecentTrainerCache.OT_Gender, }; + RecentTrainerCache.SetConsoleRegionData3DS(pk7); + RecentTrainerCache.SetFirstCountryRegion(pk7); + pk7.HealPP(); + var lang = TransferLanguage(RecentTrainerCache.Language); + pk7.Language = lang; + pk7.Nickname = SpeciesName.GetSpeciesNameGeneration(pk7.Species, lang, pk7.Format); + + // IVs + var special = Species is 151 or 251; + Span finalIVs = stackalloc int[6]; + int flawless = special ? 5 : 3; + for (var i = 0; i < finalIVs.Length; i++) + finalIVs[i] = rnd.Next(32); + for (var i = 0; i < flawless; i++) + finalIVs[i] = 31; + Util.Shuffle(finalIVs); + pk7.SetIVs(finalIVs); + + switch (IsShiny ? Shiny.Always : Shiny.Never) + { + case Shiny.Always when !pk7.IsShiny: // Force Square + pk7.PID = (uint)(((pk7.TID ^ 0 ^ (pk7.PID & 0xFFFF) ^ 0) << 16) | (pk7.PID & 0xFFFF)); + break; + case Shiny.Never when pk7.IsShiny: // Force Not Shiny + pk7.PID ^= 0x1000_0000; + break; + } + + int abil = Legal.TransferSpeciesDefaultAbilityGen2(Species) ? 0 : 2; // Hidden + pk7.RefreshAbility(abil); // 0/1/2 (not 1/2/4) + + if (special) + { + pk7.FatefulEncounter = true; + } + else if (IsNicknamedBank) + { + pk7.IsNicknamed = true; + pk7.Nickname = Korean ? Nickname : StringConverter12Transporter.GetString(RawNickname, Japanese); + } + if (RawOT[0] == StringConverter12.G1TradeOTCode) // In-game Trade + pk7.OT_Name = StringConverter12.G1TradeOTName[lang]; + else + pk7.OT_Name = Korean ? OT_Name : StringConverter12Transporter.GetString(RawOT, Japanese); + pk7.OT_Gender = OT_Gender; // Crystal + pk7.OT_Friendship = pk7.HT_Friendship = PersonalTable.SM[Species].BaseFriendship; + + pk7.SetTradeMemoryHT6(bank: true); // oh no, memories on gen7 pk + + // Dizzy Punch cannot be transferred + { + var index = pk7.GetMoveIndex(146); // Dizzy Punch + if (index != -1) + { + pk7.SetMove(index, 0); + pk7.FixMoves(); + } + } + + pk7.RefreshChecksum(); + return pk7; } + + public SK2 ConvertToSK2() => new(Japanese) + { + Species = Species, + HeldItem = HeldItem, + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + TID = TID, + EXP = EXP, + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPE = EV_SPE, + EV_SPC = EV_SPC, + DV16 = DV16, + Move1_PP = Move1_PP, + Move2_PP = Move2_PP, + Move3_PP = Move3_PP, + Move4_PP = Move4_PP, + Move1_PPUps = Move1_PPUps, + Move2_PPUps = Move2_PPUps, + Move3_PPUps = Move3_PPUps, + Move4_PPUps = Move4_PPUps, + CurrentFriendship = CurrentFriendship, + IsEgg = IsEgg, + Stat_Level = Stat_Level, + PKRS_Days = PKRS_Days, + PKRS_Strain = PKRS_Strain, + CaughtData = CaughtData, + + // Only copies until first 0x50 terminator, but just copy everything + Nickname = Nickname, + OT_Name = OT_Name, + }; } diff --git a/PKHeX.Core/PKM/PK3.cs b/PKHeX.Core/PKM/PK3.cs index 5182d01b0..b96a9233d 100644 --- a/PKHeX.Core/PKM/PK3.cs +++ b/PKHeX.Core/PKM/PK3.cs @@ -2,355 +2,354 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 3 format. +public sealed class PK3 : G3PKM, ISanityChecksum { - /// Generation 3 format. - public sealed class PK3 : G3PKM, ISanityChecksum + private static readonly ushort[] Unused = { - private static readonly ushort[] Unused = + 0x2A, 0x2B, + }; + + public override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY; + public override int SIZE_STORED => PokeCrypto.SIZE_3STORED; + public override EntityContext Context => EntityContext.Gen3; + public override PersonalInfo PersonalInfo => PersonalTable.RS[Species]; + + public override IReadOnlyList ExtraBytes => Unused; + + public PK3() : base(PokeCrypto.SIZE_3PARTY) { } + public PK3(byte[] data) : base(DecryptParty(data)) { } + + private static byte[] DecryptParty(byte[] data) + { + PokeCrypto.DecryptIfEncrypted3(ref data); + Array.Resize(ref data, PokeCrypto.SIZE_3PARTY); + return data; + } + + public override PKM Clone() + { + // Don't use the byte[] constructor, the DecryptIfEncrypted call is based on checksum. + // An invalid checksum will shuffle the data; we already know it's un-shuffled. Set up manually. + var pk = new PK3(); + Data.CopyTo(pk.Data, 0); + return pk; + } + + private const string EggNameJapanese = "タマゴ"; + + // Trash Bytes + public override Span Nickname_Trash => Data.AsSpan(0x08, 10); // no inaccessible terminator + public override Span OT_Trash => Data.AsSpan(0x14, 7); // no inaccessible terminator + + // At top for System.Reflection execution order hack + + // 0x20 Intro + public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } + public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), (ushort)value); } + public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), (ushort)value); } + public override string Nickname + { + get => StringConverter3.GetString(Nickname_Trash, Japanese); + set => StringConverter3.SetString(Nickname_Trash, (IsEgg ? EggNameJapanese : value).AsSpan(), 10, Japanese, StringConverterOption.None); + } + public override int Language { get => Data[0x12]; set => Data[0x12] = (byte)value; } + public bool FlagIsBadEgg { get => (Data[0x13] & 1) != 0; set => Data[0x13] = (byte)((Data[0x13] & ~1) | (value ? 1 : 0)); } + public bool FlagHasSpecies { get => (Data[0x13] & 2) != 0; set => Data[0x13] = (byte)((Data[0x13] & ~2) | (value ? 2 : 0)); } + public bool FlagIsEgg { get => (Data[0x13] & 4) != 0; set => Data[0x13] = (byte)((Data[0x13] & ~4) | (value ? 4 : 0)); } + public override string OT_Name + { + get => StringConverter3.GetString(OT_Trash, Japanese); + set => StringConverter3.SetString(OT_Trash, value.AsSpan(), 7, Japanese, StringConverterOption.None); + } + public override int MarkValue { get => SwapBits(Data[0x1B], 1, 2); set => Data[0x1B] = (byte)SwapBits(value, 1, 2); } + public ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), value); } + public ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), value); } + + #region Block A + public override ushort SpeciesID3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x20)); set => WriteUInt16LittleEndian(Data.AsSpan(0x20), value); } // raw access + + public override int Species + { + get => SpeciesConverter.GetG4Species(SpeciesID3); + set { - 0x2A, 0x2B, - }; - - public override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY; - public override int SIZE_STORED => PokeCrypto.SIZE_3STORED; - public override EntityContext Context => EntityContext.Gen3; - public override PersonalInfo PersonalInfo => PersonalTable.RS[Species]; - - public override IReadOnlyList ExtraBytes => Unused; - - public PK3() : base(PokeCrypto.SIZE_3PARTY) { } - public PK3(byte[] data) : base(DecryptParty(data)) { } - - private static byte[] DecryptParty(byte[] data) - { - PokeCrypto.DecryptIfEncrypted3(ref data); - Array.Resize(ref data, PokeCrypto.SIZE_3PARTY); - return data; - } - - public override PKM Clone() - { - // Don't use the byte[] constructor, the DecryptIfEncrypted call is based on checksum. - // An invalid checksum will shuffle the data; we already know it's un-shuffled. Set up manually. - var pk = new PK3(); - Data.CopyTo(pk.Data, 0); - return pk; - } - - private const string EggNameJapanese = "タマゴ"; - - // Trash Bytes - public override Span Nickname_Trash => Data.AsSpan(0x08, 10); // no inaccessible terminator - public override Span OT_Trash => Data.AsSpan(0x14, 7); // no inaccessible terminator - - // At top for System.Reflection execution order hack - - // 0x20 Intro - public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } - public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), (ushort)value); } - public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), (ushort)value); } - public override string Nickname - { - get => StringConverter3.GetString(Nickname_Trash, Japanese); - set => StringConverter3.SetString(Nickname_Trash, (IsEgg ? EggNameJapanese : value).AsSpan(), 10, Japanese, StringConverterOption.None); - } - public override int Language { get => Data[0x12]; set => Data[0x12] = (byte)value; } - public bool FlagIsBadEgg { get => (Data[0x13] & 1) != 0; set => Data[0x13] = (byte)((Data[0x13] & ~1) | (value ? 1 : 0)); } - public bool FlagHasSpecies { get => (Data[0x13] & 2) != 0; set => Data[0x13] = (byte)((Data[0x13] & ~2) | (value ? 2 : 0)); } - public bool FlagIsEgg { get => (Data[0x13] & 4) != 0; set => Data[0x13] = (byte)((Data[0x13] & ~4) | (value ? 4 : 0)); } - public override string OT_Name - { - get => StringConverter3.GetString(OT_Trash, Japanese); - set => StringConverter3.SetString(OT_Trash, value.AsSpan(), 7, Japanese, StringConverterOption.None); - } - public override int MarkValue { get => SwapBits(Data[0x1B], 1, 2); set => Data[0x1B] = (byte)SwapBits(value, 1, 2); } - public ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), value); } - public ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), value); } - - #region Block A - public override ushort SpeciesID3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x20)); set => WriteUInt16LittleEndian(Data.AsSpan(0x20), value); } // raw access - - public override int Species - { - get => SpeciesConverter.GetG4Species(SpeciesID3); - set - { - SpeciesID3 = (ushort)SpeciesConverter.GetG3Species(value); - FlagHasSpecies = Species > 0; - } - } - - public override int SpriteItem => ItemConverter.GetItemFuture3((ushort)HeldItem); - public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); } - - public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x24)); set => WriteUInt32LittleEndian(Data.AsSpan(0x24), value); } - private byte PPUps { get => Data[0x28]; set => Data[0x28] = value; } - public override int Move1_PPUps { get => (PPUps >> 0) & 3; set => PPUps = (byte)((PPUps & ~(3 << 0)) | value << 0); } - public override int Move2_PPUps { get => (PPUps >> 2) & 3; set => PPUps = (byte)((PPUps & ~(3 << 2)) | value << 2); } - public override int Move3_PPUps { get => (PPUps >> 4) & 3; set => PPUps = (byte)((PPUps & ~(3 << 4)) | value << 4); } - public override int Move4_PPUps { get => (PPUps >> 6) & 3; set => PPUps = (byte)((PPUps & ~(3 << 6)) | value << 6); } - public override int OT_Friendship { get => Data[0x29]; set => Data[0x29] = (byte)value; } - // Unused 0x2A 0x2B - #endregion - - #region Block B - public override int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2C), (ushort)value); } - public override int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E), (ushort)value); } - public override int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x30)); set => WriteUInt16LittleEndian(Data.AsSpan(0x30), (ushort)value); } - public override int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x32)); set => WriteUInt16LittleEndian(Data.AsSpan(0x32), (ushort)value); } - public override int Move1_PP { get => Data[0x34]; set => Data[0x34] = (byte)value; } - public override int Move2_PP { get => Data[0x35]; set => Data[0x35] = (byte)value; } - public override int Move3_PP { get => Data[0x36]; set => Data[0x36] = (byte)value; } - public override int Move4_PP { get => Data[0x37]; set => Data[0x37] = (byte)value; } - #endregion - - #region Block C - public override int EV_HP { get => Data[0x38]; set => Data[0x38] = (byte)value; } - public override int EV_ATK { get => Data[0x39]; set => Data[0x39] = (byte)value; } - public override int EV_DEF { get => Data[0x3A]; set => Data[0x3A] = (byte)value; } - public override int EV_SPE { get => Data[0x3B]; set => Data[0x3B] = (byte)value; } - public override int EV_SPA { get => Data[0x3C]; set => Data[0x3C] = (byte)value; } - public override int EV_SPD { get => Data[0x3D]; set => Data[0x3D] = (byte)value; } - public override byte CNT_Cool { get => Data[0x3E]; set => Data[0x3E] = value; } - public override byte CNT_Beauty { get => Data[0x3F]; set => Data[0x3F] = value; } - public override byte CNT_Cute { get => Data[0x40]; set => Data[0x40] = value; } - public override byte CNT_Smart { get => Data[0x41]; set => Data[0x41] = value; } - public override byte CNT_Tough { get => Data[0x42]; set => Data[0x42] = value; } - public override byte CNT_Sheen { get => Data[0x43]; set => Data[0x43] = value; } - #endregion - - #region Block D - private byte PKRS { get => Data[0x44]; set => Data[0x44] = value; } - public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); } - public override int Met_Location { get => Data[0x45]; set => Data[0x45] = (byte)value; } - // Origins - private ushort Origins { get => ReadUInt16LittleEndian(Data.AsSpan(0x46)); set => WriteUInt16LittleEndian(Data.AsSpan(0x46), value); } - public override int Met_Level { get => Origins & 0x7F; set => Origins = (ushort)((Origins & ~0x7F) | value); } - public override int Version { get => (Origins >> 7) & 0xF; set => Origins = (ushort)((Origins & ~0x780) | ((value & 0xF) << 7)); } - public override int Ball { get => (Origins >> 11) & 0xF; set => Origins = (ushort)((Origins & ~0x7800) | ((value & 0xF) << 11)); } - public override int OT_Gender { get => (Origins >> 15) & 1; set => Origins = (ushort)((Origins & ~(1 << 15)) | ((value & 1) << 15)); } - - public uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x48)); set => WriteUInt32LittleEndian(Data.AsSpan(0x48), value); } - public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } - public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } - public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } - public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } - public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } - public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } - - public override bool IsEgg - { - get => ((IV32 >> 30) & 1) == 1; - set - { - IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0); - FlagIsEgg = value; - if (value) - { - Nickname = EggNameJapanese; - Language = (int) LanguageID.Japanese; - } - } - } - - public override bool AbilityBit { get => IV32 >> 31 == 1; set => IV32 = (IV32 & 0x7FFFFFFF) | (value ? 1u << 31 : 0u); } - - private uint RIB0 { get => ReadUInt32LittleEndian(Data.AsSpan(0x4C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x4C), value); } - public override int RibbonCountG3Cool { get => (int)(RIB0 >> 00) & 7; set => RIB0 = ((RIB0 & ~(7u << 00)) | (uint)(value & 7) << 00); } - public override int RibbonCountG3Beauty { get => (int)(RIB0 >> 03) & 7; set => RIB0 = ((RIB0 & ~(7u << 03)) | (uint)(value & 7) << 03); } - public override int RibbonCountG3Cute { get => (int)(RIB0 >> 06) & 7; set => RIB0 = ((RIB0 & ~(7u << 06)) | (uint)(value & 7) << 06); } - public override int RibbonCountG3Smart { get => (int)(RIB0 >> 09) & 7; set => RIB0 = ((RIB0 & ~(7u << 09)) | (uint)(value & 7) << 09); } - public override int RibbonCountG3Tough { get => (int)(RIB0 >> 12) & 7; set => RIB0 = ((RIB0 & ~(7u << 12)) | (uint)(value & 7) << 12); } - public override bool RibbonChampionG3 { get => (RIB0 & (1 << 15)) == 1 << 15; set => RIB0 = ((RIB0 & ~(1u << 15)) | (value ? 1u << 15 : 0u)); } - public override bool RibbonWinning { get => (RIB0 & (1 << 16)) == 1 << 16; set => RIB0 = ((RIB0 & ~(1u << 16)) | (value ? 1u << 16 : 0u)); } - public override bool RibbonVictory { get => (RIB0 & (1 << 17)) == 1 << 17; set => RIB0 = ((RIB0 & ~(1u << 17)) | (value ? 1u << 17 : 0u)); } - public override bool RibbonArtist { get => (RIB0 & (1 << 18)) == 1 << 18; set => RIB0 = ((RIB0 & ~(1u << 18)) | (value ? 1u << 18 : 0u)); } - public override bool RibbonEffort { get => (RIB0 & (1 << 19)) == 1 << 19; set => RIB0 = ((RIB0 & ~(1u << 19)) | (value ? 1u << 19 : 0u)); } - public override bool RibbonChampionBattle { get => (RIB0 & (1 << 20)) == 1 << 20; set => RIB0 = ((RIB0 & ~(1u << 20)) | (value ? 1u << 20 : 0u)); } - public override bool RibbonChampionRegional { get => (RIB0 & (1 << 21)) == 1 << 21; set => RIB0 = ((RIB0 & ~(1u << 21)) | (value ? 1u << 21 : 0u)); } - public override bool RibbonChampionNational { get => (RIB0 & (1 << 22)) == 1 << 22; set => RIB0 = ((RIB0 & ~(1u << 22)) | (value ? 1u << 22 : 0u)); } - public override bool RibbonCountry { get => (RIB0 & (1 << 23)) == 1 << 23; set => RIB0 = ((RIB0 & ~(1u << 23)) | (value ? 1u << 23 : 0u)); } - public override bool RibbonNational { get => (RIB0 & (1 << 24)) == 1 << 24; set => RIB0 = ((RIB0 & ~(1u << 24)) | (value ? 1u << 24 : 0u)); } - public override bool RibbonEarth { get => (RIB0 & (1 << 25)) == 1 << 25; set => RIB0 = ((RIB0 & ~(1u << 25)) | (value ? 1u << 25 : 0u)); } - public override bool RibbonWorld { get => (RIB0 & (1 << 26)) == 1 << 26; set => RIB0 = ((RIB0 & ~(1u << 26)) | (value ? 1u << 26 : 0u)); } - public override bool Unused1 { get => (RIB0 & (1 << 27)) == 1 << 27; set => RIB0 = ((RIB0 & ~(1u << 27)) | (value ? 1u << 27 : 0u)); } - public override bool Unused2 { get => (RIB0 & (1 << 28)) == 1 << 28; set => RIB0 = ((RIB0 & ~(1u << 28)) | (value ? 1u << 28 : 0u)); } - public override bool Unused3 { get => (RIB0 & (1 << 29)) == 1 << 29; set => RIB0 = ((RIB0 & ~(1u << 29)) | (value ? 1u << 29 : 0u)); } - public override bool Unused4 { get => (RIB0 & (1 << 30)) == 1 << 30; set => RIB0 = ((RIB0 & ~(1u << 30)) | (value ? 1u << 30 : 0u)); } - public override bool FatefulEncounter { get => RIB0 >> 31 == 1; set => RIB0 = (RIB0 & ~(1 << 31)) | (uint)(value ? 1 << 31 : 0); } - #endregion - - #region Battle Stats - public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0x50)); set => WriteInt32LittleEndian(Data.AsSpan(0x50), value); } - public override int Stat_Level { get => Data[0x54]; set => Data[0x54] = (byte)value; } - public sbyte HeldMailID { get => (sbyte)Data[0x55]; set => Data[0x55] = (byte)value; } - public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0x56)); set => WriteUInt16LittleEndian(Data.AsSpan(0x56), (ushort)value); } - public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0x58)); set => WriteUInt16LittleEndian(Data.AsSpan(0x58), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); } - public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0x60)); set => WriteUInt16LittleEndian(Data.AsSpan(0x60), (ushort)value); } - public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0x62)); set => WriteUInt16LittleEndian(Data.AsSpan(0x62), (ushort)value); } - #endregion - - protected override byte[] Encrypt() - { - RefreshChecksum(); - return PokeCrypto.EncryptArray3(Data); - } - - public override void RefreshChecksum() - { - FlagIsBadEgg = false; - Checksum = PokeCrypto.GetCHK3(Data); - } - - public override bool ChecksumValid => CalculateChecksum() == Checksum; - private ushort CalculateChecksum() => PokeCrypto.GetCHK3(Data); - - public PK4 ConvertToPK4() - { - PK4 pk4 = new() // Convert away! - { - PID = PID, - Species = Species, - TID = TID, - SID = SID, - EXP = IsEgg ? Experience.GetEXP(5, PersonalInfo.EXPGrowth) : EXP, - Gender = EntityGender.GetFromPID(Species, PID), - Form = Form, - // IsEgg = false, -- already false - OT_Friendship = 70, - MarkValue = MarkValue & 0b1111, - Language = Language, - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPA = EV_SPA, - EV_SPD = EV_SPD, - EV_SPE = EV_SPE, - CNT_Cool = CNT_Cool, - CNT_Beauty = CNT_Beauty, - CNT_Cute = CNT_Cute, - CNT_Smart = CNT_Smart, - CNT_Tough = CNT_Tough, - CNT_Sheen = CNT_Sheen, - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - Move1_PPUps = Move1_PPUps, - Move2_PPUps = Move2_PPUps, - Move3_PPUps = Move3_PPUps, - Move4_PPUps = Move4_PPUps, - IV_HP = IV_HP, - IV_ATK = IV_ATK, - IV_DEF = IV_DEF, - IV_SPA = IV_SPA, - IV_SPD = IV_SPD, - IV_SPE = IV_SPE, - Ability = Ability, - Version = Version, - Ball = Ball, - PKRS_Strain = PKRS_Strain, - PKRS_Days = PKRS_Days, - OT_Gender = OT_Gender, - MetDate = DateTime.Now, - Met_Level = CurrentLevel, - Met_Location = Locations.Transfer3, // Pal Park - - RibbonChampionG3 = RibbonChampionG3, - RibbonWinning = RibbonWinning, - RibbonVictory = RibbonVictory, - RibbonArtist = RibbonArtist, - RibbonEffort = RibbonEffort, - RibbonChampionBattle = RibbonChampionBattle, - RibbonChampionRegional = RibbonChampionRegional, - RibbonChampionNational = RibbonChampionNational, - RibbonCountry = RibbonCountry, - RibbonNational = RibbonNational, - RibbonEarth = RibbonEarth, - RibbonWorld = RibbonWorld, - - // byte -> bool contest ribbons - RibbonG3Cool = RibbonCountG3Cool > 0, - RibbonG3CoolSuper = RibbonCountG3Cool > 1, - RibbonG3CoolHyper = RibbonCountG3Cool > 2, - RibbonG3CoolMaster = RibbonCountG3Cool > 3, - - RibbonG3Beauty = RibbonCountG3Beauty > 0, - RibbonG3BeautySuper = RibbonCountG3Beauty > 1, - RibbonG3BeautyHyper = RibbonCountG3Beauty > 2, - RibbonG3BeautyMaster = RibbonCountG3Beauty > 3, - - RibbonG3Cute = RibbonCountG3Cute > 0, - RibbonG3CuteSuper = RibbonCountG3Cute > 1, - RibbonG3CuteHyper = RibbonCountG3Cute > 2, - RibbonG3CuteMaster = RibbonCountG3Cute > 3, - - RibbonG3Smart = RibbonCountG3Smart > 0, - RibbonG3SmartSuper = RibbonCountG3Smart > 1, - RibbonG3SmartHyper = RibbonCountG3Smart > 2, - RibbonG3SmartMaster = RibbonCountG3Smart > 3, - - RibbonG3Tough = RibbonCountG3Tough > 0, - RibbonG3ToughSuper = RibbonCountG3Tough > 1, - RibbonG3ToughHyper = RibbonCountG3Tough > 2, - RibbonG3ToughMaster = RibbonCountG3Tough > 3, - - FatefulEncounter = FatefulEncounter, - }; - - // Yay for reusing string buffers! The game allocates a buffer and reuses it when creating strings. - // Trash from the {unknown source} is currently in buffer. Set it to the Nickname region. - var trash = StringConverter345.G4TransferTrashBytes; - if (pk4.Language < trash.Length) - trash[pk4.Language].CopyTo(pk4.Data, 0x48 + 4); - pk4.Nickname = IsEgg ? SpeciesName.GetSpeciesNameGeneration(pk4.Species, pk4.Language, 4) : Nickname; - pk4.IsNicknamed = !IsEgg && IsNicknamed; - - // Trash from the current string (Nickname) is in our string buffer. Slap the OT name over-top. - Buffer.BlockCopy(pk4.Data, 0x48, pk4.Data, 0x68, 0x10); - pk4.OT_Name = OT_Name; - - if (HeldItem > 0) - { - ushort item = ItemConverter.GetItemFuture3((ushort)HeldItem); - if (ItemConverter.IsItemTransferable34(item)) - pk4.HeldItem = item; - } - - // Remove HM moves - int[] newMoves = pk4.Moves; - for (int i = 0; i < 4; i++) - { - var move = newMoves[i]; - if (Array.IndexOf(Legal.HM_3, move) != -1) - newMoves[i] = 0; - } - - pk4.Moves = newMoves; - pk4.FixMoves(); - pk4.HealPP(); - - pk4.RefreshChecksum(); - return pk4; - } - - public XK3 ConvertToXK3() - { - var pk = ConvertTo(); - pk.ResetPartyStats(); - return pk; - } - - public CK3 ConvertToCK3() - { - var pk = ConvertTo(); - pk.ResetPartyStats(); - return pk; + SpeciesID3 = (ushort)SpeciesConverter.GetG3Species(value); + FlagHasSpecies = Species > 0; } } + + public override int SpriteItem => ItemConverter.GetItemFuture3((ushort)HeldItem); + public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); } + + public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x24)); set => WriteUInt32LittleEndian(Data.AsSpan(0x24), value); } + private byte PPUps { get => Data[0x28]; set => Data[0x28] = value; } + public override int Move1_PPUps { get => (PPUps >> 0) & 3; set => PPUps = (byte)((PPUps & ~(3 << 0)) | (value << 0)); } + public override int Move2_PPUps { get => (PPUps >> 2) & 3; set => PPUps = (byte)((PPUps & ~(3 << 2)) | (value << 2)); } + public override int Move3_PPUps { get => (PPUps >> 4) & 3; set => PPUps = (byte)((PPUps & ~(3 << 4)) | (value << 4)); } + public override int Move4_PPUps { get => (PPUps >> 6) & 3; set => PPUps = (byte)((PPUps & ~(3 << 6)) | (value << 6)); } + public override int OT_Friendship { get => Data[0x29]; set => Data[0x29] = (byte)value; } + // Unused 0x2A 0x2B + #endregion + + #region Block B + public override int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2C), (ushort)value); } + public override int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E), (ushort)value); } + public override int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x30)); set => WriteUInt16LittleEndian(Data.AsSpan(0x30), (ushort)value); } + public override int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x32)); set => WriteUInt16LittleEndian(Data.AsSpan(0x32), (ushort)value); } + public override int Move1_PP { get => Data[0x34]; set => Data[0x34] = (byte)value; } + public override int Move2_PP { get => Data[0x35]; set => Data[0x35] = (byte)value; } + public override int Move3_PP { get => Data[0x36]; set => Data[0x36] = (byte)value; } + public override int Move4_PP { get => Data[0x37]; set => Data[0x37] = (byte)value; } + #endregion + + #region Block C + public override int EV_HP { get => Data[0x38]; set => Data[0x38] = (byte)value; } + public override int EV_ATK { get => Data[0x39]; set => Data[0x39] = (byte)value; } + public override int EV_DEF { get => Data[0x3A]; set => Data[0x3A] = (byte)value; } + public override int EV_SPE { get => Data[0x3B]; set => Data[0x3B] = (byte)value; } + public override int EV_SPA { get => Data[0x3C]; set => Data[0x3C] = (byte)value; } + public override int EV_SPD { get => Data[0x3D]; set => Data[0x3D] = (byte)value; } + public override byte CNT_Cool { get => Data[0x3E]; set => Data[0x3E] = value; } + public override byte CNT_Beauty { get => Data[0x3F]; set => Data[0x3F] = value; } + public override byte CNT_Cute { get => Data[0x40]; set => Data[0x40] = value; } + public override byte CNT_Smart { get => Data[0x41]; set => Data[0x41] = value; } + public override byte CNT_Tough { get => Data[0x42]; set => Data[0x42] = value; } + public override byte CNT_Sheen { get => Data[0x43]; set => Data[0x43] = value; } + #endregion + + #region Block D + private byte PKRS { get => Data[0x44]; set => Data[0x44] = value; } + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + public override int Met_Location { get => Data[0x45]; set => Data[0x45] = (byte)value; } + // Origins + private ushort Origins { get => ReadUInt16LittleEndian(Data.AsSpan(0x46)); set => WriteUInt16LittleEndian(Data.AsSpan(0x46), value); } + public override int Met_Level { get => Origins & 0x7F; set => Origins = (ushort)((Origins & ~0x7F) | value); } + public override int Version { get => (Origins >> 7) & 0xF; set => Origins = (ushort)((Origins & ~0x780) | ((value & 0xF) << 7)); } + public override int Ball { get => (Origins >> 11) & 0xF; set => Origins = (ushort)((Origins & ~0x7800) | ((value & 0xF) << 11)); } + public override int OT_Gender { get => (Origins >> 15) & 1; set => Origins = (ushort)((Origins & ~(1 << 15)) | ((value & 1) << 15)); } + + public uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x48)); set => WriteUInt32LittleEndian(Data.AsSpan(0x48), value); } + public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } + public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } + public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } + public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } + public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } + public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } + + public override bool IsEgg + { + get => ((IV32 >> 30) & 1) == 1; + set + { + IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0); + FlagIsEgg = value; + if (value) + { + Nickname = EggNameJapanese; + Language = (int) LanguageID.Japanese; + } + } + } + + public override bool AbilityBit { get => IV32 >> 31 == 1; set => IV32 = (IV32 & 0x7FFFFFFF) | (value ? 1u << 31 : 0u); } + + private uint RIB0 { get => ReadUInt32LittleEndian(Data.AsSpan(0x4C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x4C), value); } + public override int RibbonCountG3Cool { get => (int)(RIB0 >> 00) & 7; set => RIB0 = ((RIB0 & ~(7u << 00)) | ((uint)(value & 7) << 00)); } + public override int RibbonCountG3Beauty { get => (int)(RIB0 >> 03) & 7; set => RIB0 = ((RIB0 & ~(7u << 03)) | ((uint)(value & 7) << 03)); } + public override int RibbonCountG3Cute { get => (int)(RIB0 >> 06) & 7; set => RIB0 = ((RIB0 & ~(7u << 06)) | ((uint)(value & 7) << 06)); } + public override int RibbonCountG3Smart { get => (int)(RIB0 >> 09) & 7; set => RIB0 = ((RIB0 & ~(7u << 09)) | ((uint)(value & 7) << 09)); } + public override int RibbonCountG3Tough { get => (int)(RIB0 >> 12) & 7; set => RIB0 = ((RIB0 & ~(7u << 12)) | ((uint)(value & 7) << 12)); } + public override bool RibbonChampionG3 { get => (RIB0 & (1 << 15)) == 1 << 15; set => RIB0 = ((RIB0 & ~(1u << 15)) | (value ? 1u << 15 : 0u)); } + public override bool RibbonWinning { get => (RIB0 & (1 << 16)) == 1 << 16; set => RIB0 = ((RIB0 & ~(1u << 16)) | (value ? 1u << 16 : 0u)); } + public override bool RibbonVictory { get => (RIB0 & (1 << 17)) == 1 << 17; set => RIB0 = ((RIB0 & ~(1u << 17)) | (value ? 1u << 17 : 0u)); } + public override bool RibbonArtist { get => (RIB0 & (1 << 18)) == 1 << 18; set => RIB0 = ((RIB0 & ~(1u << 18)) | (value ? 1u << 18 : 0u)); } + public override bool RibbonEffort { get => (RIB0 & (1 << 19)) == 1 << 19; set => RIB0 = ((RIB0 & ~(1u << 19)) | (value ? 1u << 19 : 0u)); } + public override bool RibbonChampionBattle { get => (RIB0 & (1 << 20)) == 1 << 20; set => RIB0 = ((RIB0 & ~(1u << 20)) | (value ? 1u << 20 : 0u)); } + public override bool RibbonChampionRegional { get => (RIB0 & (1 << 21)) == 1 << 21; set => RIB0 = ((RIB0 & ~(1u << 21)) | (value ? 1u << 21 : 0u)); } + public override bool RibbonChampionNational { get => (RIB0 & (1 << 22)) == 1 << 22; set => RIB0 = ((RIB0 & ~(1u << 22)) | (value ? 1u << 22 : 0u)); } + public override bool RibbonCountry { get => (RIB0 & (1 << 23)) == 1 << 23; set => RIB0 = ((RIB0 & ~(1u << 23)) | (value ? 1u << 23 : 0u)); } + public override bool RibbonNational { get => (RIB0 & (1 << 24)) == 1 << 24; set => RIB0 = ((RIB0 & ~(1u << 24)) | (value ? 1u << 24 : 0u)); } + public override bool RibbonEarth { get => (RIB0 & (1 << 25)) == 1 << 25; set => RIB0 = ((RIB0 & ~(1u << 25)) | (value ? 1u << 25 : 0u)); } + public override bool RibbonWorld { get => (RIB0 & (1 << 26)) == 1 << 26; set => RIB0 = ((RIB0 & ~(1u << 26)) | (value ? 1u << 26 : 0u)); } + public override bool Unused1 { get => (RIB0 & (1 << 27)) == 1 << 27; set => RIB0 = ((RIB0 & ~(1u << 27)) | (value ? 1u << 27 : 0u)); } + public override bool Unused2 { get => (RIB0 & (1 << 28)) == 1 << 28; set => RIB0 = ((RIB0 & ~(1u << 28)) | (value ? 1u << 28 : 0u)); } + public override bool Unused3 { get => (RIB0 & (1 << 29)) == 1 << 29; set => RIB0 = ((RIB0 & ~(1u << 29)) | (value ? 1u << 29 : 0u)); } + public override bool Unused4 { get => (RIB0 & (1 << 30)) == 1 << 30; set => RIB0 = ((RIB0 & ~(1u << 30)) | (value ? 1u << 30 : 0u)); } + public override bool FatefulEncounter { get => RIB0 >> 31 == 1; set => RIB0 = (RIB0 & ~(1 << 31)) | (uint)(value ? 1 << 31 : 0); } + #endregion + + #region Battle Stats + public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0x50)); set => WriteInt32LittleEndian(Data.AsSpan(0x50), value); } + public override int Stat_Level { get => Data[0x54]; set => Data[0x54] = (byte)value; } + public sbyte HeldMailID { get => (sbyte)Data[0x55]; set => Data[0x55] = (byte)value; } + public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0x56)); set => WriteUInt16LittleEndian(Data.AsSpan(0x56), (ushort)value); } + public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0x58)); set => WriteUInt16LittleEndian(Data.AsSpan(0x58), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); } + public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0x60)); set => WriteUInt16LittleEndian(Data.AsSpan(0x60), (ushort)value); } + public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0x62)); set => WriteUInt16LittleEndian(Data.AsSpan(0x62), (ushort)value); } + #endregion + + protected override byte[] Encrypt() + { + RefreshChecksum(); + return PokeCrypto.EncryptArray3(Data); + } + + public override void RefreshChecksum() + { + FlagIsBadEgg = false; + Checksum = PokeCrypto.GetCHK3(Data); + } + + public override bool ChecksumValid => CalculateChecksum() == Checksum; + private ushort CalculateChecksum() => PokeCrypto.GetCHK3(Data); + + public PK4 ConvertToPK4() + { + PK4 pk4 = new() // Convert away! + { + PID = PID, + Species = Species, + TID = TID, + SID = SID, + EXP = IsEgg ? Experience.GetEXP(5, PersonalInfo.EXPGrowth) : EXP, + Gender = EntityGender.GetFromPID(Species, PID), + Form = Form, + // IsEgg = false, -- already false + OT_Friendship = 70, + MarkValue = MarkValue & 0b1111, + Language = Language, + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPA = EV_SPA, + EV_SPD = EV_SPD, + EV_SPE = EV_SPE, + CNT_Cool = CNT_Cool, + CNT_Beauty = CNT_Beauty, + CNT_Cute = CNT_Cute, + CNT_Smart = CNT_Smart, + CNT_Tough = CNT_Tough, + CNT_Sheen = CNT_Sheen, + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + Move1_PPUps = Move1_PPUps, + Move2_PPUps = Move2_PPUps, + Move3_PPUps = Move3_PPUps, + Move4_PPUps = Move4_PPUps, + IV_HP = IV_HP, + IV_ATK = IV_ATK, + IV_DEF = IV_DEF, + IV_SPA = IV_SPA, + IV_SPD = IV_SPD, + IV_SPE = IV_SPE, + Ability = Ability, + Version = Version, + Ball = Ball, + PKRS_Strain = PKRS_Strain, + PKRS_Days = PKRS_Days, + OT_Gender = OT_Gender, + MetDate = DateTime.Now, + Met_Level = CurrentLevel, + Met_Location = Locations.Transfer3, // Pal Park + + RibbonChampionG3 = RibbonChampionG3, + RibbonWinning = RibbonWinning, + RibbonVictory = RibbonVictory, + RibbonArtist = RibbonArtist, + RibbonEffort = RibbonEffort, + RibbonChampionBattle = RibbonChampionBattle, + RibbonChampionRegional = RibbonChampionRegional, + RibbonChampionNational = RibbonChampionNational, + RibbonCountry = RibbonCountry, + RibbonNational = RibbonNational, + RibbonEarth = RibbonEarth, + RibbonWorld = RibbonWorld, + + // byte -> bool contest ribbons + RibbonG3Cool = RibbonCountG3Cool > 0, + RibbonG3CoolSuper = RibbonCountG3Cool > 1, + RibbonG3CoolHyper = RibbonCountG3Cool > 2, + RibbonG3CoolMaster = RibbonCountG3Cool > 3, + + RibbonG3Beauty = RibbonCountG3Beauty > 0, + RibbonG3BeautySuper = RibbonCountG3Beauty > 1, + RibbonG3BeautyHyper = RibbonCountG3Beauty > 2, + RibbonG3BeautyMaster = RibbonCountG3Beauty > 3, + + RibbonG3Cute = RibbonCountG3Cute > 0, + RibbonG3CuteSuper = RibbonCountG3Cute > 1, + RibbonG3CuteHyper = RibbonCountG3Cute > 2, + RibbonG3CuteMaster = RibbonCountG3Cute > 3, + + RibbonG3Smart = RibbonCountG3Smart > 0, + RibbonG3SmartSuper = RibbonCountG3Smart > 1, + RibbonG3SmartHyper = RibbonCountG3Smart > 2, + RibbonG3SmartMaster = RibbonCountG3Smart > 3, + + RibbonG3Tough = RibbonCountG3Tough > 0, + RibbonG3ToughSuper = RibbonCountG3Tough > 1, + RibbonG3ToughHyper = RibbonCountG3Tough > 2, + RibbonG3ToughMaster = RibbonCountG3Tough > 3, + + FatefulEncounter = FatefulEncounter, + }; + + // Yay for reusing string buffers! The game allocates a buffer and reuses it when creating strings. + // Trash from the {unknown source} is currently in buffer. Set it to the Nickname region. + var trash = StringConverter345.G4TransferTrashBytes; + if (pk4.Language < trash.Length) + trash[pk4.Language].CopyTo(pk4.Data, 0x48 + 4); + pk4.Nickname = IsEgg ? SpeciesName.GetSpeciesNameGeneration(pk4.Species, pk4.Language, 4) : Nickname; + pk4.IsNicknamed = !IsEgg && IsNicknamed; + + // Trash from the current string (Nickname) is in our string buffer. Slap the OT name over-top. + Buffer.BlockCopy(pk4.Data, 0x48, pk4.Data, 0x68, 0x10); + pk4.OT_Name = OT_Name; + + if (HeldItem > 0) + { + ushort item = ItemConverter.GetItemFuture3((ushort)HeldItem); + if (ItemConverter.IsItemTransferable34(item)) + pk4.HeldItem = item; + } + + // Remove HM moves + int[] newMoves = pk4.Moves; + for (int i = 0; i < 4; i++) + { + var move = newMoves[i]; + if (Array.IndexOf(Legal.HM_3, move) != -1) + newMoves[i] = 0; + } + + pk4.Moves = newMoves; + pk4.FixMoves(); + pk4.HealPP(); + + pk4.RefreshChecksum(); + return pk4; + } + + public XK3 ConvertToXK3() + { + var pk = ConvertTo(); + pk.ResetPartyStats(); + return pk; + } + + public CK3 ConvertToCK3() + { + var pk = ConvertTo(); + pk.ResetPartyStats(); + return pk; + } } diff --git a/PKHeX.Core/PKM/PK4.cs b/PKHeX.Core/PKM/PK4.cs index d52b18364..ef73448d3 100644 --- a/PKHeX.Core/PKM/PK4.cs +++ b/PKHeX.Core/PKM/PK4.cs @@ -1,372 +1,371 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 4 format. +public sealed class PK4 : G4PKM { - /// Generation 4 format. - public sealed class PK4 : G4PKM + private static readonly ushort[] Unused = { - private static readonly ushort[] Unused = + 0x42, 0x43, 0x5E, 0x63, 0x64, 0x65, 0x66, 0x67, 0x87, + }; + + public override IReadOnlyList ExtraBytes => Unused; + + public override int SIZE_PARTY => PokeCrypto.SIZE_4PARTY; + public override int SIZE_STORED => PokeCrypto.SIZE_4STORED; + public override EntityContext Context => EntityContext.Gen4; + public override PersonalInfo PersonalInfo => PersonalTable.HGSS.GetFormEntry(Species, Form); + + public PK4() : base(PokeCrypto.SIZE_4PARTY) { } + public PK4(byte[] data) : base(DecryptParty(data)) { } + + private static byte[] DecryptParty(byte[] data) + { + PokeCrypto.DecryptIfEncrypted45(ref data); + Array.Resize(ref data, PokeCrypto.SIZE_4PARTY); + return data; + } + + public override PKM Clone() => new PK4((byte[])Data.Clone()); + + // Structure + public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } + public override ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); } + public override ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); } + + #region Block A + public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); } + public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } + public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); } + public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); } + public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); } + public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; } + public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; } + public override int EV_DEF { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } + public override int EV_SPE { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } + public override int EV_SPA { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public override int EV_SPD { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + public override byte CNT_Cool { get => Data[0x1E]; set => Data[0x1E] = value; } + public override byte CNT_Beauty { get => Data[0x1F]; set => Data[0x1F] = value; } + public override byte CNT_Cute { get => Data[0x20]; set => Data[0x20] = value; } + public override byte CNT_Smart { get => Data[0x21]; set => Data[0x21] = value; } + public override byte CNT_Tough { get => Data[0x22]; set => Data[0x22] = value; } + public override byte CNT_Sheen { get => Data[0x23]; set => Data[0x23] = value; } + + private byte RIB0 { get => Data[0x24]; set => Data[0x24] = value; } // Sinnoh 1 + private byte RIB1 { get => Data[0x25]; set => Data[0x25] = value; } // Sinnoh 2 + private byte RIB2 { get => Data[0x26]; set => Data[0x26] = value; } // Unova 1 + private byte RIB3 { get => Data[0x27]; set => Data[0x27] = value; } // Unova 2 + public override bool RibbonChampionSinnoh { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonAbility { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonAbilityGreat { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonAbilityDouble { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonAbilityMulti { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonAbilityPair { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonAbilityWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonAlert { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonShock { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonDowncast { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonCareless { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonRelax { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonSnooze { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonSmile { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonGorgeous { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonRoyal { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonFootprint { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonRecord { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonEvent { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonLegend { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonChampionWorld { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonBirthday { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonSpecial { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonSouvenir { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonWishing { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RIB3_4 { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public override bool RIB3_5 { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public override bool RIB3_6 { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public override bool RIB3_7 { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + #endregion + + #region Block B + public override int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x28)); set => WriteUInt16LittleEndian(Data.AsSpan(0x28), (ushort)value); } + public override int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2A), (ushort)value); } + public override int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2C), (ushort)value); } + public override int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E), (ushort)value); } + public override int Move1_PP { get => Data[0x30]; set => Data[0x30] = (byte)value; } + public override int Move2_PP { get => Data[0x31]; set => Data[0x31] = (byte)value; } + public override int Move3_PP { get => Data[0x32]; set => Data[0x32] = (byte)value; } + public override int Move4_PP { get => Data[0x33]; set => Data[0x33] = (byte)value; } + public override int Move1_PPUps { get => Data[0x34]; set => Data[0x34] = (byte)value; } + public override int Move2_PPUps { get => Data[0x35]; set => Data[0x35] = (byte)value; } + public override int Move3_PPUps { get => Data[0x36]; set => Data[0x36] = (byte)value; } + public override int Move4_PPUps { get => Data[0x37]; set => Data[0x37] = (byte)value; } + public uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x38)); set => WriteUInt32LittleEndian(Data.AsSpan(0x38), value); } + public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } + public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } + public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } + public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } + public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } + public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } + public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } + public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } + + private byte RIB4 { get => Data[0x3C]; set => Data[0x3C] = value; } // Hoenn 1a + private byte RIB5 { get => Data[0x3D]; set => Data[0x3D] = value; } // Hoenn 1b + private byte RIB6 { get => Data[0x3E]; set => Data[0x3E] = value; } // Hoenn 2a + private byte RIB7 { get => Data[0x3F]; set => Data[0x3F] = value; } // Hoenn 2b + public override bool RibbonG3Cool { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG3CoolSuper { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG3CoolHyper { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG3CoolMaster { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG3Beauty { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG3BeautySuper { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG3BeautyHyper { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG3BeautyMaster { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG3Cute { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG3CuteSuper { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG3CuteHyper { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG3CuteMaster { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG3Smart { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG3SmartSuper { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG3SmartHyper { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG3SmartMaster { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG3Tough { get => (RIB6 & (1 << 0)) == 1 << 0; set => RIB6 = (byte)((RIB6 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG3ToughSuper { get => (RIB6 & (1 << 1)) == 1 << 1; set => RIB6 = (byte)((RIB6 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG3ToughHyper { get => (RIB6 & (1 << 2)) == 1 << 2; set => RIB6 = (byte)((RIB6 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG3ToughMaster { get => (RIB6 & (1 << 3)) == 1 << 3; set => RIB6 = (byte)((RIB6 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonChampionG3 { get => (RIB6 & (1 << 4)) == 1 << 4; set => RIB6 = (byte)((RIB6 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonWinning { get => (RIB6 & (1 << 5)) == 1 << 5; set => RIB6 = (byte)((RIB6 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonVictory { get => (RIB6 & (1 << 6)) == 1 << 6; set => RIB6 = (byte)((RIB6 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonArtist { get => (RIB6 & (1 << 7)) == 1 << 7; set => RIB6 = (byte)((RIB6 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonEffort { get => (RIB7 & (1 << 0)) == 1 << 0; set => RIB7 = (byte)((RIB7 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonChampionBattle { get => (RIB7 & (1 << 1)) == 1 << 1; set => RIB7 = (byte)((RIB7 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonChampionRegional { get => (RIB7 & (1 << 2)) == 1 << 2; set => RIB7 = (byte)((RIB7 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonChampionNational { get => (RIB7 & (1 << 3)) == 1 << 3; set => RIB7 = (byte)((RIB7 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonCountry { get => (RIB7 & (1 << 4)) == 1 << 4; set => RIB7 = (byte)((RIB7 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonNational { get => (RIB7 & (1 << 5)) == 1 << 5; set => RIB7 = (byte)((RIB7 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonEarth { get => (RIB7 & (1 << 6)) == 1 << 6; set => RIB7 = (byte)((RIB7 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonWorld { get => (RIB7 & (1 << 7)) == 1 << 7; set => RIB7 = (byte)((RIB7 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + + public override bool FatefulEncounter { get => (Data[0x40] & 1) == 1; set => Data[0x40] = (byte)((Data[0x40] & ~0x01) | (value ? 1 : 0)); } + public override int Gender { get => (Data[0x40] >> 1) & 0x3; set => Data[0x40] = (byte)((Data[0x40] & ~0x06) | (value << 1)); } + public override int Form { get => Data[0x40] >> 3; set => Data[0x40] = (byte)((Data[0x40] & 0x07) | (value << 3)); } + public override int ShinyLeaf { get => Data[0x41]; set => Data[0x41] = (byte) value; } + // 0x42-0x43 Unused + public override ushort Egg_LocationExtended + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x44)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x44), value); + } + + public override ushort Met_LocationExtended + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x46)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x46), value); + } + + #endregion + + #region Block C + public override string Nickname + { + get => StringConverter4.GetString(Nickname_Trash); + set => StringConverter4.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); + } + + // 0x5E unused + public override int Version { get => Data[0x5F]; set => Data[0x5F] = (byte)value; } + private byte RIB8 { get => Data[0x60]; set => Data[0x60] = value; } // Sinnoh 3 + private byte RIB9 { get => Data[0x61]; set => Data[0x61] = value; } // Sinnoh 4 + private byte RIBA { get => Data[0x62]; set => Data[0x62] = value; } // Sinnoh 5 + private byte RIBB { get => Data[0x63]; set => Data[0x63] = value; } // Sinnoh 6 + public override bool RibbonG4Cool { get => (RIB8 & (1 << 0)) == 1 << 0; set => RIB8 = (byte)((RIB8 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG4CoolGreat { get => (RIB8 & (1 << 1)) == 1 << 1; set => RIB8 = (byte)((RIB8 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG4CoolUltra { get => (RIB8 & (1 << 2)) == 1 << 2; set => RIB8 = (byte)((RIB8 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG4CoolMaster { get => (RIB8 & (1 << 3)) == 1 << 3; set => RIB8 = (byte)((RIB8 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG4Beauty { get => (RIB8 & (1 << 4)) == 1 << 4; set => RIB8 = (byte)((RIB8 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG4BeautyGreat { get => (RIB8 & (1 << 5)) == 1 << 5; set => RIB8 = (byte)((RIB8 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG4BeautyUltra { get => (RIB8 & (1 << 6)) == 1 << 6; set => RIB8 = (byte)((RIB8 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG4BeautyMaster { get => (RIB8 & (1 << 7)) == 1 << 7; set => RIB8 = (byte)((RIB8 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG4Cute { get => (RIB9 & (1 << 0)) == 1 << 0; set => RIB9 = (byte)((RIB9 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG4CuteGreat { get => (RIB9 & (1 << 1)) == 1 << 1; set => RIB9 = (byte)((RIB9 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG4CuteUltra { get => (RIB9 & (1 << 2)) == 1 << 2; set => RIB9 = (byte)((RIB9 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG4CuteMaster { get => (RIB9 & (1 << 3)) == 1 << 3; set => RIB9 = (byte)((RIB9 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG4Smart { get => (RIB9 & (1 << 4)) == 1 << 4; set => RIB9 = (byte)((RIB9 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG4SmartGreat { get => (RIB9 & (1 << 5)) == 1 << 5; set => RIB9 = (byte)((RIB9 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG4SmartUltra { get => (RIB9 & (1 << 6)) == 1 << 6; set => RIB9 = (byte)((RIB9 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG4SmartMaster { get => (RIB9 & (1 << 7)) == 1 << 7; set => RIB9 = (byte)((RIB9 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG4Tough { get => (RIBA & (1 << 0)) == 1 << 0; set => RIBA = (byte)((RIBA & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG4ToughGreat { get => (RIBA & (1 << 1)) == 1 << 1; set => RIBA = (byte)((RIBA & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG4ToughUltra { get => (RIBA & (1 << 2)) == 1 << 2; set => RIBA = (byte)((RIBA & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG4ToughMaster { get => (RIBA & (1 << 3)) == 1 << 3; set => RIBA = (byte)((RIBA & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RIBA_4 { get => (RIBA & (1 << 4)) == 1 << 4; set => RIBA = (byte)((RIBA & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public override bool RIBA_5 { get => (RIBA & (1 << 5)) == 1 << 5; set => RIBA = (byte)((RIBA & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public override bool RIBA_6 { get => (RIBA & (1 << 6)) == 1 << 6; set => RIBA = (byte)((RIBA & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public override bool RIBA_7 { get => (RIBA & (1 << 7)) == 1 << 7; set => RIBA = (byte)((RIBA & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + public override bool RIBB_0 { get => (RIBB & (1 << 0)) == 1 << 0; set => RIBB = (byte)((RIBB & ~(1 << 0)) | (value ? 1 << 0 : 0)); } // Unused + public override bool RIBB_1 { get => (RIBB & (1 << 1)) == 1 << 1; set => RIBB = (byte)((RIBB & ~(1 << 1)) | (value ? 1 << 1 : 0)); } // Unused + public override bool RIBB_2 { get => (RIBB & (1 << 2)) == 1 << 2; set => RIBB = (byte)((RIBB & ~(1 << 2)) | (value ? 1 << 2 : 0)); } // Unused + public override bool RIBB_3 { get => (RIBB & (1 << 3)) == 1 << 3; set => RIBB = (byte)((RIBB & ~(1 << 3)) | (value ? 1 << 3 : 0)); } // Unused + public override bool RIBB_4 { get => (RIBB & (1 << 4)) == 1 << 4; set => RIBB = (byte)((RIBB & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public override bool RIBB_5 { get => (RIBB & (1 << 5)) == 1 << 5; set => RIBB = (byte)((RIBB & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public override bool RIBB_6 { get => (RIBB & (1 << 6)) == 1 << 6; set => RIBB = (byte)((RIBB & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public override bool RIBB_7 { get => (RIBB & (1 << 7)) == 1 << 7; set => RIBB = (byte)((RIBB & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + // 0x64-0x67 Unused + #endregion + + #region Block D + public override string OT_Name + { + get => StringConverter4.GetString(OT_Trash); + set => StringConverter4.SetString(OT_Trash, value.AsSpan(), 7, StringConverterOption.None); + } + + public override int Egg_Year { get => Data[0x78]; set => Data[0x78] = (byte)value; } + public override int Egg_Month { get => Data[0x79]; set => Data[0x79] = (byte)value; } + public override int Egg_Day { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } + public override int Met_Year { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } + public override int Met_Month { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } + public override int Met_Day { get => Data[0x7D]; set => Data[0x7D] = (byte)value; } + + public override ushort Egg_LocationDP + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x7E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x7E), value); + } + public override ushort Met_LocationDP + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x80)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x80), value); + } + + private byte PKRS { get => Data[0x82]; set => Data[0x82] = value; } + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + public override byte BallDPPt { get => Data[0x83]; set => Data[0x83] = value; } + public override int Met_Level { get => Data[0x84] & ~0x80; set => Data[0x84] = (byte)((Data[0x84] & 0x80) | value); } + public override int OT_Gender { get => Data[0x84] >> 7; set => Data[0x84] = (byte)((Data[0x84] & ~0x80) | (value << 7)); } + public override GroundTileType GroundTile { get => (GroundTileType)Data[0x85]; set => Data[0x85] = (byte)value; } + public override byte BallHGSS { get => Data[0x86]; set => Data[0x86] = value; } + public override byte PokeathlonStat { get => Data[0x87]; set => Data[0x87] = value; } + #endregion + + #region Battle Stats + public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0x88)); set => WriteInt32LittleEndian(Data.AsSpan(0x88), value); } + public override int Stat_Level { get => Data[0x8C]; set => Data[0x8C] = (byte)value; } + public byte BallCapsuleIndex { get => Data[0x8D]; set => Data[0x8D] = value; } + public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0x8E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8E), (ushort)value); } + public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0x90)); set => WriteUInt16LittleEndian(Data.AsSpan(0x90), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0x92)); set => WriteUInt16LittleEndian(Data.AsSpan(0x92), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0x94)); set => WriteUInt16LittleEndian(Data.AsSpan(0x94), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0x96)); set => WriteUInt16LittleEndian(Data.AsSpan(0x96), (ushort)value); } + public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0x98)); set => WriteUInt16LittleEndian(Data.AsSpan(0x98), (ushort)value); } + public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0x9A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x9A), (ushort)value); } + + public Span HeldMail => Data.AsSpan(0x9C, 0x38); + public Span Seals => Data.AsSpan(0xD4, 0x18); + + #endregion + + // Methods + protected override byte[] Encrypt() + { + RefreshChecksum(); + return PokeCrypto.EncryptArray45(Data); + } + + public BK4 ConvertToBK4() + { + BK4 bk4 = ConvertTo(); + + // Enforce DP content only (no PtHGSS) + if (Form != 0 && !PersonalTable.DP[Species].HasForms && Species != 201) + bk4.Form = 0; + if (HeldItem > Legal.MaxItemID_4_DP) + bk4.HeldItem = 0; + bk4.RefreshChecksum(); + return bk4; + } + + public PK5 ConvertToPK5() + { + // Double Check Location Data to see if we're already a PK5 + if (Data[0x5F] < 0x10 && ReadUInt16LittleEndian(Data.AsSpan(0x80)) > 0x4000) + return new PK5(Data); + + DateTime moment = DateTime.Now; + + PK5 pk5 = new(Data.AsSpan(0, PokeCrypto.SIZE_5PARTY).ToArray()) // Convert away! { - 0x42, 0x43, 0x5E, 0x63, 0x64, 0x65, 0x66, 0x67, 0x87, + JunkByte = 0, + OT_Friendship = 70, + // Apply new met date + MetDate = moment, }; + pk5.HeldMail.Clear(); - public override IReadOnlyList ExtraBytes => Unused; - - public override int SIZE_PARTY => PokeCrypto.SIZE_4PARTY; - public override int SIZE_STORED => PokeCrypto.SIZE_4STORED; - public override EntityContext Context => EntityContext.Gen4; - public override PersonalInfo PersonalInfo => PersonalTable.HGSS.GetFormEntry(Species, Form); - - public PK4() : base(PokeCrypto.SIZE_4PARTY) { } - public PK4(byte[] data) : base(DecryptParty(data)) { } - - private static byte[] DecryptParty(byte[] data) + // Arceus Type Changing -- Plate forcibly removed. + if (pk5.Species == (int)Core.Species.Arceus) { - PokeCrypto.DecryptIfEncrypted45(ref data); - Array.Resize(ref data, PokeCrypto.SIZE_4PARTY); - return data; + pk5.Form = 0; + pk5.HeldItem = 0; + } + else if (Array.IndexOf(Legal.HeldItems_BW, (ushort)HeldItem) == -1) + { + pk5.HeldItem = 0; // if valid, it's already copied } - public override PKM Clone() => new PK4((byte[])Data.Clone()); + // Fix PP + pk5.HealPP(); - // Structure - public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } - public override ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); } - public override ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); } + // Disassociate Nature and PID, pk4 getter does PID%25 + pk5.Nature = Nature; - #region Block A - public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); } - public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } - public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); } - public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); } - public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); } - public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } - public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; } - public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; } - public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; } - public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; } - public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; } - public override int EV_DEF { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } - public override int EV_SPE { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } - public override int EV_SPA { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } - public override int EV_SPD { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } - public override byte CNT_Cool { get => Data[0x1E]; set => Data[0x1E] = value; } - public override byte CNT_Beauty { get => Data[0x1F]; set => Data[0x1F] = value; } - public override byte CNT_Cute { get => Data[0x20]; set => Data[0x20] = value; } - public override byte CNT_Smart { get => Data[0x21]; set => Data[0x21] = value; } - public override byte CNT_Tough { get => Data[0x22]; set => Data[0x22] = value; } - public override byte CNT_Sheen { get => Data[0x23]; set => Data[0x23] = value; } + // Delete Platinum/HGSS Met Location Data + WriteUInt32LittleEndian(pk5.Data.AsSpan(0x44), 0); - private byte RIB0 { get => Data[0x24]; set => Data[0x24] = value; } // Sinnoh 1 - private byte RIB1 { get => Data[0x25]; set => Data[0x25] = value; } // Sinnoh 2 - private byte RIB2 { get => Data[0x26]; set => Data[0x26] = value; } // Unova 1 - private byte RIB3 { get => Data[0x27]; set => Data[0x27] = value; } // Unova 2 - public override bool RibbonChampionSinnoh { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonAbility { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonAbilityGreat { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonAbilityDouble { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonAbilityMulti { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonAbilityPair { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonAbilityWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonAlert { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonShock { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonDowncast { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonCareless { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonRelax { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonSnooze { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonSmile { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonGorgeous { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonRoyal { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonFootprint { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonRecord { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonEvent { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonLegend { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonChampionWorld { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonBirthday { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonSpecial { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonSouvenir { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonWishing { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RIB3_4 { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused - public override bool RIB3_5 { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused - public override bool RIB3_6 { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public override bool RIB3_7 { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - #endregion + // Met / Crown Data Detection + pk5.Met_Location = Legal.GetTransfer45MetLocation(pk5); - #region Block B - public override int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x28)); set => WriteUInt16LittleEndian(Data.AsSpan(0x28), (ushort)value); } - public override int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2A), (ushort)value); } - public override int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2C), (ushort)value); } - public override int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E), (ushort)value); } - public override int Move1_PP { get => Data[0x30]; set => Data[0x30] = (byte)value; } - public override int Move2_PP { get => Data[0x31]; set => Data[0x31] = (byte)value; } - public override int Move3_PP { get => Data[0x32]; set => Data[0x32] = (byte)value; } - public override int Move4_PP { get => Data[0x33]; set => Data[0x33] = (byte)value; } - public override int Move1_PPUps { get => Data[0x34]; set => Data[0x34] = (byte)value; } - public override int Move2_PPUps { get => Data[0x35]; set => Data[0x35] = (byte)value; } - public override int Move3_PPUps { get => Data[0x36]; set => Data[0x36] = (byte)value; } - public override int Move4_PPUps { get => Data[0x37]; set => Data[0x37] = (byte)value; } - public uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x38)); set => WriteUInt32LittleEndian(Data.AsSpan(0x38), value); } - public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } - public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } - public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } - public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } - public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } - public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } - public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } - public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } + // Egg Location is not modified; when clearing Pt/HGSS egg data, the location will revert to Faraway Place + // pk5.Egg_Location = Egg_Location; - private byte RIB4 { get => Data[0x3C]; set => Data[0x3C] = value; } // Hoenn 1a - private byte RIB5 { get => Data[0x3D]; set => Data[0x3D] = value; } // Hoenn 1b - private byte RIB6 { get => Data[0x3E]; set => Data[0x3E] = value; } // Hoenn 2a - private byte RIB7 { get => Data[0x3F]; set => Data[0x3F] = value; } // Hoenn 2b - public override bool RibbonG3Cool { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG3CoolSuper { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG3CoolHyper { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG3CoolMaster { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonG3Beauty { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonG3BeautySuper { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonG3BeautyHyper { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonG3BeautyMaster { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonG3Cute { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG3CuteSuper { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG3CuteHyper { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG3CuteMaster { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonG3Smart { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonG3SmartSuper { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonG3SmartHyper { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonG3SmartMaster { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonG3Tough { get => (RIB6 & (1 << 0)) == 1 << 0; set => RIB6 = (byte)((RIB6 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG3ToughSuper { get => (RIB6 & (1 << 1)) == 1 << 1; set => RIB6 = (byte)((RIB6 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG3ToughHyper { get => (RIB6 & (1 << 2)) == 1 << 2; set => RIB6 = (byte)((RIB6 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG3ToughMaster { get => (RIB6 & (1 << 3)) == 1 << 3; set => RIB6 = (byte)((RIB6 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonChampionG3 { get => (RIB6 & (1 << 4)) == 1 << 4; set => RIB6 = (byte)((RIB6 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonWinning { get => (RIB6 & (1 << 5)) == 1 << 5; set => RIB6 = (byte)((RIB6 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonVictory { get => (RIB6 & (1 << 6)) == 1 << 6; set => RIB6 = (byte)((RIB6 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonArtist { get => (RIB6 & (1 << 7)) == 1 << 7; set => RIB6 = (byte)((RIB6 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonEffort { get => (RIB7 & (1 << 0)) == 1 << 0; set => RIB7 = (byte)((RIB7 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonChampionBattle { get => (RIB7 & (1 << 1)) == 1 << 1; set => RIB7 = (byte)((RIB7 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonChampionRegional { get => (RIB7 & (1 << 2)) == 1 << 2; set => RIB7 = (byte)((RIB7 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonChampionNational { get => (RIB7 & (1 << 3)) == 1 << 3; set => RIB7 = (byte)((RIB7 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonCountry { get => (RIB7 & (1 << 4)) == 1 << 4; set => RIB7 = (byte)((RIB7 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonNational { get => (RIB7 & (1 << 5)) == 1 << 5; set => RIB7 = (byte)((RIB7 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonEarth { get => (RIB7 & (1 << 6)) == 1 << 6; set => RIB7 = (byte)((RIB7 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonWorld { get => (RIB7 & (1 << 7)) == 1 << 7; set => RIB7 = (byte)((RIB7 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + // Delete HGSS Data + WriteUInt16LittleEndian(pk5.Data.AsSpan(0x86), 0); + pk5.Ball = Ball; - public override bool FatefulEncounter { get => (Data[0x40] & 1) == 1; set => Data[0x40] = (byte)((Data[0x40] & ~0x01) | (value ? 1 : 0)); } - public override int Gender { get => (Data[0x40] >> 1) & 0x3; set => Data[0x40] = (byte)((Data[0x40] & ~0x06) | (value << 1)); } - public override int Form { get => Data[0x40] >> 3; set => Data[0x40] = (byte)((Data[0x40] & 0x07) | (value << 3)); } - public override int ShinyLeaf { get => Data[0x41]; set => Data[0x41] = (byte) value; } - // 0x42-0x43 Unused - public override ushort Egg_LocationExtended - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x44)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x44), value); - } + // Transfer Nickname and OT Name, update encoding + pk5.Nickname = Nickname; + pk5.OT_Name = OT_Name; - public override ushort Met_LocationExtended - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x46)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x46), value); - } + // Fix Level + pk5.Met_Level = pk5.CurrentLevel; - #endregion + // Remove HM moves; Defog should be kept if both are learned. + // if has defog, remove whirlpool. + bool hasDefog = HasMove((int) Move.Defog); + var banned = hasDefog ? Legal.HM_HGSS : Legal.HM_DPPt; + if (Array.IndexOf(banned, Move1) != -1) Move1 = 0; + if (Array.IndexOf(banned, Move2) != -1) Move2 = 0; + if (Array.IndexOf(banned, Move3) != -1) Move3 = 0; + if (Array.IndexOf(banned, Move4) != -1) Move4 = 0; + pk5.FixMoves(); - #region Block C - public override string Nickname - { - get => StringConverter4.GetString(Nickname_Trash); - set => StringConverter4.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); - } + // D/P(not Pt)/HG/SS created Shedinja forget to set Gender to Genderless. + if (pk5.Species is (int)Core.Species.Shedinja) + pk5.Gender = 2; // Genderless - // 0x5E unused - public override int Version { get => Data[0x5F]; set => Data[0x5F] = (byte)value; } - private byte RIB8 { get => Data[0x60]; set => Data[0x60] = value; } // Sinnoh 3 - private byte RIB9 { get => Data[0x61]; set => Data[0x61] = value; } // Sinnoh 4 - private byte RIBA { get => Data[0x62]; set => Data[0x62] = value; } // Sinnoh 5 - private byte RIBB { get => Data[0x63]; set => Data[0x63] = value; } // Sinnoh 6 - public override bool RibbonG4Cool { get => (RIB8 & (1 << 0)) == 1 << 0; set => RIB8 = (byte)((RIB8 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG4CoolGreat { get => (RIB8 & (1 << 1)) == 1 << 1; set => RIB8 = (byte)((RIB8 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG4CoolUltra { get => (RIB8 & (1 << 2)) == 1 << 2; set => RIB8 = (byte)((RIB8 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG4CoolMaster { get => (RIB8 & (1 << 3)) == 1 << 3; set => RIB8 = (byte)((RIB8 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonG4Beauty { get => (RIB8 & (1 << 4)) == 1 << 4; set => RIB8 = (byte)((RIB8 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonG4BeautyGreat { get => (RIB8 & (1 << 5)) == 1 << 5; set => RIB8 = (byte)((RIB8 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonG4BeautyUltra { get => (RIB8 & (1 << 6)) == 1 << 6; set => RIB8 = (byte)((RIB8 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonG4BeautyMaster { get => (RIB8 & (1 << 7)) == 1 << 7; set => RIB8 = (byte)((RIB8 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonG4Cute { get => (RIB9 & (1 << 0)) == 1 << 0; set => RIB9 = (byte)((RIB9 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG4CuteGreat { get => (RIB9 & (1 << 1)) == 1 << 1; set => RIB9 = (byte)((RIB9 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG4CuteUltra { get => (RIB9 & (1 << 2)) == 1 << 2; set => RIB9 = (byte)((RIB9 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG4CuteMaster { get => (RIB9 & (1 << 3)) == 1 << 3; set => RIB9 = (byte)((RIB9 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RibbonG4Smart { get => (RIB9 & (1 << 4)) == 1 << 4; set => RIB9 = (byte)((RIB9 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public override bool RibbonG4SmartGreat { get => (RIB9 & (1 << 5)) == 1 << 5; set => RIB9 = (byte)((RIB9 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public override bool RibbonG4SmartUltra { get => (RIB9 & (1 << 6)) == 1 << 6; set => RIB9 = (byte)((RIB9 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public override bool RibbonG4SmartMaster { get => (RIB9 & (1 << 7)) == 1 << 7; set => RIB9 = (byte)((RIB9 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public override bool RibbonG4Tough { get => (RIBA & (1 << 0)) == 1 << 0; set => RIBA = (byte)((RIBA & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public override bool RibbonG4ToughGreat { get => (RIBA & (1 << 1)) == 1 << 1; set => RIBA = (byte)((RIBA & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public override bool RibbonG4ToughUltra { get => (RIBA & (1 << 2)) == 1 << 2; set => RIBA = (byte)((RIBA & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public override bool RibbonG4ToughMaster { get => (RIBA & (1 << 3)) == 1 << 3; set => RIBA = (byte)((RIBA & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public override bool RIBA_4 { get => (RIBA & (1 << 4)) == 1 << 4; set => RIBA = (byte)((RIBA & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused - public override bool RIBA_5 { get => (RIBA & (1 << 5)) == 1 << 5; set => RIBA = (byte)((RIBA & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused - public override bool RIBA_6 { get => (RIBA & (1 << 6)) == 1 << 6; set => RIBA = (byte)((RIBA & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public override bool RIBA_7 { get => (RIBA & (1 << 7)) == 1 << 7; set => RIBA = (byte)((RIBA & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - public override bool RIBB_0 { get => (RIBB & (1 << 0)) == 1 << 0; set => RIBB = (byte)((RIBB & ~(1 << 0)) | (value ? 1 << 0 : 0)); } // Unused - public override bool RIBB_1 { get => (RIBB & (1 << 1)) == 1 << 1; set => RIBB = (byte)((RIBB & ~(1 << 1)) | (value ? 1 << 1 : 0)); } // Unused - public override bool RIBB_2 { get => (RIBB & (1 << 2)) == 1 << 2; set => RIBB = (byte)((RIBB & ~(1 << 2)) | (value ? 1 << 2 : 0)); } // Unused - public override bool RIBB_3 { get => (RIBB & (1 << 3)) == 1 << 3; set => RIBB = (byte)((RIBB & ~(1 << 3)) | (value ? 1 << 3 : 0)); } // Unused - public override bool RIBB_4 { get => (RIBB & (1 << 4)) == 1 << 4; set => RIBB = (byte)((RIBB & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused - public override bool RIBB_5 { get => (RIBB & (1 << 5)) == 1 << 5; set => RIBB = (byte)((RIBB & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused - public override bool RIBB_6 { get => (RIBB & (1 << 6)) == 1 << 6; set => RIBB = (byte)((RIBB & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public override bool RIBB_7 { get => (RIBB & (1 << 7)) == 1 << 7; set => RIBB = (byte)((RIBB & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - // 0x64-0x67 Unused - #endregion - - #region Block D - public override string OT_Name - { - get => StringConverter4.GetString(OT_Trash); - set => StringConverter4.SetString(OT_Trash, value.AsSpan(), 7, StringConverterOption.None); - } - - public override int Egg_Year { get => Data[0x78]; set => Data[0x78] = (byte)value; } - public override int Egg_Month { get => Data[0x79]; set => Data[0x79] = (byte)value; } - public override int Egg_Day { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } - public override int Met_Year { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } - public override int Met_Month { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } - public override int Met_Day { get => Data[0x7D]; set => Data[0x7D] = (byte)value; } - - public override ushort Egg_LocationDP - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x7E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x7E), value); - } - public override ushort Met_LocationDP - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x80)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x80), value); - } - - private byte PKRS { get => Data[0x82]; set => Data[0x82] = value; } - public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } - public override byte BallDPPt { get => Data[0x83]; set => Data[0x83] = value; } - public override int Met_Level { get => Data[0x84] & ~0x80; set => Data[0x84] = (byte)((Data[0x84] & 0x80) | value); } - public override int OT_Gender { get => Data[0x84] >> 7; set => Data[0x84] = (byte)((Data[0x84] & ~0x80) | value << 7); } - public override GroundTileType GroundTile { get => (GroundTileType)Data[0x85]; set => Data[0x85] = (byte)value; } - public override byte BallHGSS { get => Data[0x86]; set => Data[0x86] = value; } - public override byte PokeathlonStat { get => Data[0x87]; set => Data[0x87] = value; } - #endregion - - #region Battle Stats - public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0x88)); set => WriteInt32LittleEndian(Data.AsSpan(0x88), value); } - public override int Stat_Level { get => Data[0x8C]; set => Data[0x8C] = (byte)value; } - public byte BallCapsuleIndex { get => Data[0x8D]; set => Data[0x8D] = value; } - public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0x8E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8E), (ushort)value); } - public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0x90)); set => WriteUInt16LittleEndian(Data.AsSpan(0x90), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0x92)); set => WriteUInt16LittleEndian(Data.AsSpan(0x92), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0x94)); set => WriteUInt16LittleEndian(Data.AsSpan(0x94), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0x96)); set => WriteUInt16LittleEndian(Data.AsSpan(0x96), (ushort)value); } - public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0x98)); set => WriteUInt16LittleEndian(Data.AsSpan(0x98), (ushort)value); } - public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0x9A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x9A), (ushort)value); } - - public Span HeldMail => Data.AsSpan(0x9C, 0x38); - public Span Seals => Data.AsSpan(0xD4, 0x18); - - #endregion - - // Methods - protected override byte[] Encrypt() - { - RefreshChecksum(); - return PokeCrypto.EncryptArray45(Data); - } - - public BK4 ConvertToBK4() - { - BK4 bk4 = ConvertTo(); - - // Enforce DP content only (no PtHGSS) - if (Form != 0 && !PersonalTable.DP[Species].HasForms && Species != 201) - bk4.Form = 0; - if (HeldItem > Legal.MaxItemID_4_DP) - bk4.HeldItem = 0; - bk4.RefreshChecksum(); - return bk4; - } - - public PK5 ConvertToPK5() - { - // Double Check Location Data to see if we're already a PK5 - if (Data[0x5F] < 0x10 && ReadUInt16LittleEndian(Data.AsSpan(0x80)) > 0x4000) - return new PK5(Data); - - DateTime moment = DateTime.Now; - - PK5 pk5 = new(Data.AsSpan(0, PokeCrypto.SIZE_5PARTY).ToArray()) // Convert away! - { - JunkByte = 0, - OT_Friendship = 70, - // Apply new met date - MetDate = moment, - }; - pk5.HeldMail.Clear(); - - // Arceus Type Changing -- Plate forcibly removed. - if (pk5.Species == (int)Core.Species.Arceus) - { - pk5.Form = 0; - pk5.HeldItem = 0; - } - else if (Array.IndexOf(Legal.HeldItems_BW, (ushort)HeldItem) == -1) - { - pk5.HeldItem = 0; // if valid, it's already copied - } - - // Fix PP - pk5.HealPP(); - - // Disassociate Nature and PID, pk4 getter does PID%25 - pk5.Nature = Nature; - - // Delete Platinum/HGSS Met Location Data - WriteUInt32LittleEndian(pk5.Data.AsSpan(0x44), 0); - - // Met / Crown Data Detection - pk5.Met_Location = Legal.GetTransfer45MetLocation(pk5); - - // Egg Location is not modified; when clearing Pt/HGSS egg data, the location will revert to Faraway Place - // pk5.Egg_Location = Egg_Location; - - // Delete HGSS Data - WriteUInt16LittleEndian(pk5.Data.AsSpan(0x86), 0); - pk5.Ball = Ball; - - // Transfer Nickname and OT Name, update encoding - pk5.Nickname = Nickname; - pk5.OT_Name = OT_Name; - - // Fix Level - pk5.Met_Level = pk5.CurrentLevel; - - // Remove HM moves; Defog should be kept if both are learned. - // if has defog, remove whirlpool. - bool hasDefog = HasMove((int) Move.Defog); - var banned = hasDefog ? Legal.HM_HGSS : Legal.HM_DPPt; - if (Array.IndexOf(banned, Move1) != -1) Move1 = 0; - if (Array.IndexOf(banned, Move2) != -1) Move2 = 0; - if (Array.IndexOf(banned, Move3) != -1) Move3 = 0; - if (Array.IndexOf(banned, Move4) != -1) Move4 = 0; - pk5.FixMoves(); - - // D/P(not Pt)/HG/SS created Shedinja forget to set Gender to Genderless. - if (pk5.Species is (int)Core.Species.Shedinja) - pk5.Gender = 2; // Genderless - - pk5.RefreshChecksum(); - return pk5; - } + pk5.RefreshChecksum(); + return pk5; } } diff --git a/PKHeX.Core/PKM/PK5.cs b/PKHeX.Core/PKM/PK5.cs index f476e2b36..690f33b97 100644 --- a/PKHeX.Core/PKM/PK5.cs +++ b/PKHeX.Core/PKM/PK5.cs @@ -2,555 +2,554 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 5 format. +public sealed class PK5 : PKM, ISanityChecksum, + IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetUnique3, IRibbonSetUnique4, IRibbonSetCommon3, IRibbonSetCommon4, + IContestStats, IContestStatsMutable, IGroundTile { - /// Generation 5 format. - public sealed class PK5 : PKM, ISanityChecksum, - IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetUnique3, IRibbonSetUnique4, IRibbonSetCommon3, IRibbonSetCommon4, - IContestStats, IContestStatsMutable, IGroundTile + private static readonly ushort[] Unused = { - private static readonly ushort[] Unused = + 0x43, 0x44, 0x45, 0x46, 0x47, + 0x5E, // unused + 0x63, // last 8 bits of a 32bit ribbonset + 0x64, 0x65, 0x66, 0x67, // unused 32bit ribbonset? + 0x86, // unused + 0x87, // PokeStar Fame + }; + + public override IReadOnlyList ExtraBytes => Unused; + + public override int SIZE_PARTY => PokeCrypto.SIZE_5PARTY; + public override int SIZE_STORED => PokeCrypto.SIZE_5STORED; + public override EntityContext Context => EntityContext.Gen5; + public override PersonalInfo PersonalInfo => PersonalTable.B2W2.GetFormEntry(Species, Form); + + public PK5() : base(PokeCrypto.SIZE_5PARTY) { } + public PK5(byte[] data) : base(DecryptParty(data)) { } + + private static byte[] DecryptParty(byte[] data) + { + PokeCrypto.DecryptIfEncrypted45(ref data); + Array.Resize(ref data, PokeCrypto.SIZE_5PARTY); + return data; + } + + public override PKM Clone() => new PK5((byte[])Data.Clone()); + public override void RefreshChecksum() => Checksum = CalculateChecksum(); + public override bool ChecksumValid => CalculateChecksum() == Checksum; + public override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } } + private ushort CalculateChecksum() => PokeCrypto.GetCHK(Data, PokeCrypto.SIZE_4STORED); + + // Trash Bytes + public override Span Nickname_Trash => Data.AsSpan(0x48, 22); + public override Span OT_Trash => Data.AsSpan(0x68, 16); + + // Future Attributes + public override uint EncryptionConstant { get => PID; set { } } + public override int CurrentFriendship { get => OT_Friendship; set => OT_Friendship = value; } + public override int CurrentHandler { get => 0; set { } } + public override int AbilityNumber { get => HiddenAbility ? 4 : 1 << PIDAbility; set { } } + + // Structure + public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } + public ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); } + public ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); } + + #region Block A + public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); } + public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } + public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); } + public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); } + public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); } + public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; } + public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; } + public override int EV_DEF { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } + public override int EV_SPE { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } + public override int EV_SPA { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public override int EV_SPD { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + public byte CNT_Cool { get => Data[0x1E]; set => Data[0x1E] = value; } + public byte CNT_Beauty { get => Data[0x1F]; set => Data[0x1F] = value; } + public byte CNT_Cute { get => Data[0x20]; set => Data[0x20] = value; } + public byte CNT_Smart { get => Data[0x21]; set => Data[0x21] = value; } + public byte CNT_Tough { get => Data[0x22]; set => Data[0x22] = value; } + public byte CNT_Sheen { get => Data[0x23]; set => Data[0x23] = value; } + + private byte RIB0 { get => Data[0x24]; set => Data[0x24] = value; } // Sinnoh 1 + private byte RIB1 { get => Data[0x25]; set => Data[0x25] = value; } // Sinnoh 2 + private byte RIB2 { get => Data[0x26]; set => Data[0x26] = value; } // Unova 1 + private byte RIB3 { get => Data[0x27]; set => Data[0x27] = value; } // Unova 2 + public bool RibbonChampionSinnoh { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonAbility { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonAbilityGreat { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonAbilityDouble { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonAbilityMulti { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonAbilityPair { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonAbilityWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonAlert { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonShock { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonDowncast { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonCareless { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonRelax { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonSnooze { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonSmile { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonGorgeous { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonRoyal { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonFootprint { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonRecord { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonEvent { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonLegend { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonChampionWorld { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonBirthday { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonSpecial { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonSouvenir { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonWishing { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RIB3_4 { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public bool RIB3_5 { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public bool RIB3_6 { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public bool RIB3_7 { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + #endregion + + #region Block B + public override int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x28)); set => WriteUInt16LittleEndian(Data.AsSpan(0x28), (ushort)value); } + public override int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2A), (ushort)value); } + public override int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2C), (ushort)value); } + public override int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E), (ushort)value); } + public override int Move1_PP { get => Data[0x30]; set => Data[0x30] = (byte)value; } + public override int Move2_PP { get => Data[0x31]; set => Data[0x31] = (byte)value; } + public override int Move3_PP { get => Data[0x32]; set => Data[0x32] = (byte)value; } + public override int Move4_PP { get => Data[0x33]; set => Data[0x33] = (byte)value; } + public override int Move1_PPUps { get => Data[0x34]; set => Data[0x34] = (byte)value; } + public override int Move2_PPUps { get => Data[0x35]; set => Data[0x35] = (byte)value; } + public override int Move3_PPUps { get => Data[0x36]; set => Data[0x36] = (byte)value; } + public override int Move4_PPUps { get => Data[0x37]; set => Data[0x37] = (byte)value; } + private uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x38)); set => WriteUInt32LittleEndian(Data.AsSpan(0x38), value); } + public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } + public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } + public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } + public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } + public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } + public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } + public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } + public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } + + private byte RIB4 { get => Data[0x3C]; set => Data[0x3C] = value; } // Hoenn 1a + private byte RIB5 { get => Data[0x3D]; set => Data[0x3D] = value; } // Hoenn 1b + private byte RIB6 { get => Data[0x3E]; set => Data[0x3E] = value; } // Hoenn 2a + private byte RIB7 { get => Data[0x3F]; set => Data[0x3F] = value; } // Hoenn 2b + public bool RibbonG3Cool { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonG3CoolSuper { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonG3CoolHyper { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonG3CoolMaster { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonG3Beauty { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonG3BeautySuper { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonG3BeautyHyper { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonG3BeautyMaster { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonG3Cute { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonG3CuteSuper { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonG3CuteHyper { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonG3CuteMaster { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonG3Smart { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonG3SmartSuper { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonG3SmartHyper { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonG3SmartMaster { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonG3Tough { get => (RIB6 & (1 << 0)) == 1 << 0; set => RIB6 = (byte)((RIB6 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonG3ToughSuper { get => (RIB6 & (1 << 1)) == 1 << 1; set => RIB6 = (byte)((RIB6 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonG3ToughHyper { get => (RIB6 & (1 << 2)) == 1 << 2; set => RIB6 = (byte)((RIB6 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonG3ToughMaster { get => (RIB6 & (1 << 3)) == 1 << 3; set => RIB6 = (byte)((RIB6 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonChampionG3 { get => (RIB6 & (1 << 4)) == 1 << 4; set => RIB6 = (byte)((RIB6 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonWinning { get => (RIB6 & (1 << 5)) == 1 << 5; set => RIB6 = (byte)((RIB6 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonVictory { get => (RIB6 & (1 << 6)) == 1 << 6; set => RIB6 = (byte)((RIB6 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonArtist { get => (RIB6 & (1 << 7)) == 1 << 7; set => RIB6 = (byte)((RIB6 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonEffort { get => (RIB7 & (1 << 0)) == 1 << 0; set => RIB7 = (byte)((RIB7 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonChampionBattle { get => (RIB7 & (1 << 1)) == 1 << 1; set => RIB7 = (byte)((RIB7 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonChampionRegional { get => (RIB7 & (1 << 2)) == 1 << 2; set => RIB7 = (byte)((RIB7 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonChampionNational { get => (RIB7 & (1 << 3)) == 1 << 3; set => RIB7 = (byte)((RIB7 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonCountry { get => (RIB7 & (1 << 4)) == 1 << 4; set => RIB7 = (byte)((RIB7 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonNational { get => (RIB7 & (1 << 5)) == 1 << 5; set => RIB7 = (byte)((RIB7 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonEarth { get => (RIB7 & (1 << 6)) == 1 << 6; set => RIB7 = (byte)((RIB7 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonWorld { get => (RIB7 & (1 << 7)) == 1 << 7; set => RIB7 = (byte)((RIB7 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + + public override bool FatefulEncounter { get => (Data[0x40] & 1) == 1; set => Data[0x40] = (byte)((Data[0x40] & ~0x01) | (value ? 1 : 0)); } + public override int Gender { get => (Data[0x40] >> 1) & 0x3; set => Data[0x40] = (byte)((Data[0x40] & ~0x06) | (value << 1)); } + public override int Form { get => Data[0x40] >> 3; set => Data[0x40] = (byte)((Data[0x40] & 0x07) | (value << 3)); } + public override int Nature { get => Data[0x41]; set => Data[0x41] = (byte)value; } + public bool HiddenAbility { get => (Data[0x42] & 1) == 1; set => Data[0x42] = (byte)((Data[0x42] & ~0x01) | (value ? 1 : 0)); } + public bool NSparkle { get => (Data[0x42] & 2) == 2; set => Data[0x42] = (byte)((Data[0x42] & ~0x02) | (value ? 2 : 0)); } + // 0x43-0x47 Unused + #endregion + + #region Block C + public override string Nickname { get => StringConverter5.GetString(Nickname_Trash); set => StringConverter5.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); } + // 0x5E unused + public override int Version { get => Data[0x5F]; set => Data[0x5F] = (byte)value; } + private byte RIB8 { get => Data[0x60]; set => Data[0x60] = value; } // Sinnoh 3 + private byte RIB9 { get => Data[0x61]; set => Data[0x61] = value; } // Sinnoh 4 + private byte RIBA { get => Data[0x62]; set => Data[0x62] = value; } // Sinnoh 5 + private byte RIBB { get => Data[0x63]; set => Data[0x63] = value; } // Sinnoh 6 + public bool RibbonG4Cool { get => (RIB8 & (1 << 0)) == 1 << 0; set => RIB8 = (byte)((RIB8 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonG4CoolGreat { get => (RIB8 & (1 << 1)) == 1 << 1; set => RIB8 = (byte)((RIB8 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonG4CoolUltra { get => (RIB8 & (1 << 2)) == 1 << 2; set => RIB8 = (byte)((RIB8 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonG4CoolMaster { get => (RIB8 & (1 << 3)) == 1 << 3; set => RIB8 = (byte)((RIB8 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonG4Beauty { get => (RIB8 & (1 << 4)) == 1 << 4; set => RIB8 = (byte)((RIB8 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonG4BeautyGreat { get => (RIB8 & (1 << 5)) == 1 << 5; set => RIB8 = (byte)((RIB8 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonG4BeautyUltra { get => (RIB8 & (1 << 6)) == 1 << 6; set => RIB8 = (byte)((RIB8 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonG4BeautyMaster { get => (RIB8 & (1 << 7)) == 1 << 7; set => RIB8 = (byte)((RIB8 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonG4Cute { get => (RIB9 & (1 << 0)) == 1 << 0; set => RIB9 = (byte)((RIB9 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonG4CuteGreat { get => (RIB9 & (1 << 1)) == 1 << 1; set => RIB9 = (byte)((RIB9 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonG4CuteUltra { get => (RIB9 & (1 << 2)) == 1 << 2; set => RIB9 = (byte)((RIB9 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonG4CuteMaster { get => (RIB9 & (1 << 3)) == 1 << 3; set => RIB9 = (byte)((RIB9 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonG4Smart { get => (RIB9 & (1 << 4)) == 1 << 4; set => RIB9 = (byte)((RIB9 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonG4SmartGreat { get => (RIB9 & (1 << 5)) == 1 << 5; set => RIB9 = (byte)((RIB9 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonG4SmartUltra { get => (RIB9 & (1 << 6)) == 1 << 6; set => RIB9 = (byte)((RIB9 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonG4SmartMaster { get => (RIB9 & (1 << 7)) == 1 << 7; set => RIB9 = (byte)((RIB9 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonG4Tough { get => (RIBA & (1 << 0)) == 1 << 0; set => RIBA = (byte)((RIBA & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonG4ToughGreat { get => (RIBA & (1 << 1)) == 1 << 1; set => RIBA = (byte)((RIBA & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonG4ToughUltra { get => (RIBA & (1 << 2)) == 1 << 2; set => RIBA = (byte)((RIBA & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonG4ToughMaster { get => (RIBA & (1 << 3)) == 1 << 3; set => RIBA = (byte)((RIBA & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RIBA_4 { get => (RIBA & (1 << 4)) == 1 << 4; set => RIBA = (byte)((RIBA & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public bool RIBA_5 { get => (RIBA & (1 << 5)) == 1 << 5; set => RIBA = (byte)((RIBA & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public bool RIBA_6 { get => (RIBA & (1 << 6)) == 1 << 6; set => RIBA = (byte)((RIBA & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public bool RIBA_7 { get => (RIBA & (1 << 7)) == 1 << 7; set => RIBA = (byte)((RIBA & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + public bool RIBB_0 { get => (RIBB & (1 << 0)) == 1 << 0; set => RIBB = (byte)((RIBB & ~(1 << 0)) | (value ? 1 << 0 : 0)); } // Unused + public bool RIBB_1 { get => (RIBB & (1 << 1)) == 1 << 1; set => RIBB = (byte)((RIBB & ~(1 << 1)) | (value ? 1 << 1 : 0)); } // Unused + public bool RIBB_2 { get => (RIBB & (1 << 2)) == 1 << 2; set => RIBB = (byte)((RIBB & ~(1 << 2)) | (value ? 1 << 2 : 0)); } // Unused + public bool RIBB_3 { get => (RIBB & (1 << 3)) == 1 << 3; set => RIBB = (byte)((RIBB & ~(1 << 3)) | (value ? 1 << 3 : 0)); } // Unused + public bool RIBB_4 { get => (RIBB & (1 << 4)) == 1 << 4; set => RIBB = (byte)((RIBB & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public bool RIBB_5 { get => (RIBB & (1 << 5)) == 1 << 5; set => RIBB = (byte)((RIBB & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public bool RIBB_6 { get => (RIBB & (1 << 6)) == 1 << 6; set => RIBB = (byte)((RIBB & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public bool RIBB_7 { get => (RIBB & (1 << 7)) == 1 << 7; set => RIBB = (byte)((RIBB & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + // 0x64-0x67 Unused + #endregion + + #region Block D + public override string OT_Name { get => StringConverter5.GetString(OT_Trash); set => StringConverter5.SetString(OT_Trash, value.AsSpan(), 7, StringConverterOption.None); } + public override int Egg_Year { get => Data[0x78]; set => Data[0x78] = (byte)value; } + public override int Egg_Month { get => Data[0x79]; set => Data[0x79] = (byte)value; } + public override int Egg_Day { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } + public override int Met_Year { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } + public override int Met_Month { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } + public override int Met_Day { get => Data[0x7D]; set => Data[0x7D] = (byte)value; } + public override int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0x7E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7E), (ushort)value); } + public override int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0x80)); set => WriteUInt16LittleEndian(Data.AsSpan(0x80), (ushort)value); } + private byte PKRS { get => Data[0x82]; set => Data[0x82] = value; } + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + public override int Ball { get => Data[0x83]; set => Data[0x83] = (byte)value; } + public override int Met_Level { get => Data[0x84] & ~0x80; set => Data[0x84] = (byte)((Data[0x84] & 0x80) | value); } + public override int OT_Gender { get => Data[0x84] >> 7; set => Data[0x84] = (byte)((Data[0x84] & ~0x80) | (value << 7)); } + public GroundTileType GroundTile { get => (GroundTileType)Data[0x85]; set => Data[0x85] = (byte)value; } + // 0x86 Unused + public byte PokeStarFame { get => Data[0x87]; set => Data[0x87] = value; } + public bool IsPokeStar { get => PokeStarFame > 250; set => PokeStarFame = value ? (byte)255 : (byte)0; } + #endregion + + #region Battle Stats + public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0x88)); set => WriteInt32LittleEndian(Data.AsSpan(0x88), value); } + public override int Stat_Level { get => Data[0x8C]; set => Data[0x8C] = (byte)value; } + public byte JunkByte { get => Data[0x8D]; set => Data[0x8D] = value; } + public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0x8E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8E), (ushort)value); } + public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0x90)); set => WriteUInt16LittleEndian(Data.AsSpan(0x90), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0x92)); set => WriteUInt16LittleEndian(Data.AsSpan(0x92), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0x94)); set => WriteUInt16LittleEndian(Data.AsSpan(0x94), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0x96)); set => WriteUInt16LittleEndian(Data.AsSpan(0x96), (ushort)value); } + public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0x98)); set => WriteUInt16LittleEndian(Data.AsSpan(0x98), (ushort)value); } + public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0x9A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x9A), (ushort)value); } + + public Span HeldMail => Data.AsSpan(0x9C, 0x38); + public ulong JunkData { get => ReadUInt64LittleEndian(Data.AsSpan(0xD4, 8)); set => WriteUInt64LittleEndian(Data.AsSpan(0xD4, 8), value); } + + #endregion + + // Generated Attributes + public override int PSV => (int)(((PID >> 16) ^ (PID & 0xFFFF)) >> 3); + public override int TSV => (TID ^ SID) >> 3; + + public override int Characteristic + { + get { - 0x43, 0x44, 0x45, 0x46, 0x47, - 0x5E, // unused - 0x63, // last 8 bits of a 32bit ribbonset - 0x64, 0x65, 0x66, 0x67, // unused 32bit ribbonset? - 0x86, // unused - 0x87, // PokeStar Fame - }; - - public override IReadOnlyList ExtraBytes => Unused; - - public override int SIZE_PARTY => PokeCrypto.SIZE_5PARTY; - public override int SIZE_STORED => PokeCrypto.SIZE_5STORED; - public override EntityContext Context => EntityContext.Gen5; - public override PersonalInfo PersonalInfo => PersonalTable.B2W2.GetFormEntry(Species, Form); - - public PK5() : base(PokeCrypto.SIZE_5PARTY) { } - public PK5(byte[] data) : base(DecryptParty(data)) { } - - private static byte[] DecryptParty(byte[] data) - { - PokeCrypto.DecryptIfEncrypted45(ref data); - Array.Resize(ref data, PokeCrypto.SIZE_5PARTY); - return data; - } - - public override PKM Clone() => new PK5((byte[])Data.Clone()); - public override void RefreshChecksum() => Checksum = CalculateChecksum(); - public override bool ChecksumValid => CalculateChecksum() == Checksum; - public override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } } - private ushort CalculateChecksum() => PokeCrypto.GetCHK(Data, PokeCrypto.SIZE_4STORED); - - // Trash Bytes - public override Span Nickname_Trash => Data.AsSpan(0x48, 22); - public override Span OT_Trash => Data.AsSpan(0x68, 16); - - // Future Attributes - public override uint EncryptionConstant { get => PID; set { } } - public override int CurrentFriendship { get => OT_Friendship; set => OT_Friendship = value; } - public override int CurrentHandler { get => 0; set { } } - public override int AbilityNumber { get => HiddenAbility ? 4 : 1 << PIDAbility; set { } } - - // Structure - public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } - public ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); } - public ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); } - - #region Block A - public override int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); } - public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } - public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); } - public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); } - public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); } - public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } - public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; } - public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; } - public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; } - public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; } - public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; } - public override int EV_DEF { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } - public override int EV_SPE { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } - public override int EV_SPA { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } - public override int EV_SPD { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } - public byte CNT_Cool { get => Data[0x1E]; set => Data[0x1E] = value; } - public byte CNT_Beauty { get => Data[0x1F]; set => Data[0x1F] = value; } - public byte CNT_Cute { get => Data[0x20]; set => Data[0x20] = value; } - public byte CNT_Smart { get => Data[0x21]; set => Data[0x21] = value; } - public byte CNT_Tough { get => Data[0x22]; set => Data[0x22] = value; } - public byte CNT_Sheen { get => Data[0x23]; set => Data[0x23] = value; } - - private byte RIB0 { get => Data[0x24]; set => Data[0x24] = value; } // Sinnoh 1 - private byte RIB1 { get => Data[0x25]; set => Data[0x25] = value; } // Sinnoh 2 - private byte RIB2 { get => Data[0x26]; set => Data[0x26] = value; } // Unova 1 - private byte RIB3 { get => Data[0x27]; set => Data[0x27] = value; } // Unova 2 - public bool RibbonChampionSinnoh { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonAbility { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonAbilityGreat { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonAbilityDouble { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonAbilityMulti { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonAbilityPair { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonAbilityWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonAlert { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonShock { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonDowncast { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonCareless { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonRelax { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonSnooze { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonSmile { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonGorgeous { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonRoyal { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonFootprint { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonRecord { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonEvent { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonLegend { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonChampionWorld { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonBirthday { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonSpecial { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonSouvenir { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonWishing { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RIB3_4 { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused - public bool RIB3_5 { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused - public bool RIB3_6 { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public bool RIB3_7 { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - #endregion - - #region Block B - public override int Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x28)); set => WriteUInt16LittleEndian(Data.AsSpan(0x28), (ushort)value); } - public override int Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2A), (ushort)value); } - public override int Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2C), (ushort)value); } - public override int Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E), (ushort)value); } - public override int Move1_PP { get => Data[0x30]; set => Data[0x30] = (byte)value; } - public override int Move2_PP { get => Data[0x31]; set => Data[0x31] = (byte)value; } - public override int Move3_PP { get => Data[0x32]; set => Data[0x32] = (byte)value; } - public override int Move4_PP { get => Data[0x33]; set => Data[0x33] = (byte)value; } - public override int Move1_PPUps { get => Data[0x34]; set => Data[0x34] = (byte)value; } - public override int Move2_PPUps { get => Data[0x35]; set => Data[0x35] = (byte)value; } - public override int Move3_PPUps { get => Data[0x36]; set => Data[0x36] = (byte)value; } - public override int Move4_PPUps { get => Data[0x37]; set => Data[0x37] = (byte)value; } - private uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x38)); set => WriteUInt32LittleEndian(Data.AsSpan(0x38), value); } - public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } - public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } - public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } - public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } - public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } - public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } - public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } - public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } - - private byte RIB4 { get => Data[0x3C]; set => Data[0x3C] = value; } // Hoenn 1a - private byte RIB5 { get => Data[0x3D]; set => Data[0x3D] = value; } // Hoenn 1b - private byte RIB6 { get => Data[0x3E]; set => Data[0x3E] = value; } // Hoenn 2a - private byte RIB7 { get => Data[0x3F]; set => Data[0x3F] = value; } // Hoenn 2b - public bool RibbonG3Cool { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonG3CoolSuper { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonG3CoolHyper { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonG3CoolMaster { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonG3Beauty { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonG3BeautySuper { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonG3BeautyHyper { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonG3BeautyMaster { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonG3Cute { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonG3CuteSuper { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonG3CuteHyper { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonG3CuteMaster { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonG3Smart { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonG3SmartSuper { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonG3SmartHyper { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonG3SmartMaster { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonG3Tough { get => (RIB6 & (1 << 0)) == 1 << 0; set => RIB6 = (byte)((RIB6 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonG3ToughSuper { get => (RIB6 & (1 << 1)) == 1 << 1; set => RIB6 = (byte)((RIB6 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonG3ToughHyper { get => (RIB6 & (1 << 2)) == 1 << 2; set => RIB6 = (byte)((RIB6 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonG3ToughMaster { get => (RIB6 & (1 << 3)) == 1 << 3; set => RIB6 = (byte)((RIB6 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonChampionG3 { get => (RIB6 & (1 << 4)) == 1 << 4; set => RIB6 = (byte)((RIB6 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonWinning { get => (RIB6 & (1 << 5)) == 1 << 5; set => RIB6 = (byte)((RIB6 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonVictory { get => (RIB6 & (1 << 6)) == 1 << 6; set => RIB6 = (byte)((RIB6 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonArtist { get => (RIB6 & (1 << 7)) == 1 << 7; set => RIB6 = (byte)((RIB6 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonEffort { get => (RIB7 & (1 << 0)) == 1 << 0; set => RIB7 = (byte)((RIB7 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonChampionBattle { get => (RIB7 & (1 << 1)) == 1 << 1; set => RIB7 = (byte)((RIB7 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonChampionRegional { get => (RIB7 & (1 << 2)) == 1 << 2; set => RIB7 = (byte)((RIB7 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonChampionNational { get => (RIB7 & (1 << 3)) == 1 << 3; set => RIB7 = (byte)((RIB7 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonCountry { get => (RIB7 & (1 << 4)) == 1 << 4; set => RIB7 = (byte)((RIB7 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonNational { get => (RIB7 & (1 << 5)) == 1 << 5; set => RIB7 = (byte)((RIB7 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonEarth { get => (RIB7 & (1 << 6)) == 1 << 6; set => RIB7 = (byte)((RIB7 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonWorld { get => (RIB7 & (1 << 7)) == 1 << 7; set => RIB7 = (byte)((RIB7 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - - public override bool FatefulEncounter { get => (Data[0x40] & 1) == 1; set => Data[0x40] = (byte)((Data[0x40] & ~0x01) | (value ? 1 : 0)); } - public override int Gender { get => (Data[0x40] >> 1) & 0x3; set => Data[0x40] = (byte)((Data[0x40] & ~0x06) | (value << 1)); } - public override int Form { get => Data[0x40] >> 3; set => Data[0x40] = (byte)((Data[0x40] & 0x07) | (value << 3)); } - public override int Nature { get => Data[0x41]; set => Data[0x41] = (byte)value; } - public bool HiddenAbility { get => (Data[0x42] & 1) == 1; set => Data[0x42] = (byte)((Data[0x42] & ~0x01) | (value ? 1 : 0)); } - public bool NSparkle { get => (Data[0x42] & 2) == 2; set => Data[0x42] = (byte)((Data[0x42] & ~0x02) | (value ? 2 : 0)); } - // 0x43-0x47 Unused - #endregion - - #region Block C - public override string Nickname { get => StringConverter5.GetString(Nickname_Trash); set => StringConverter5.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); } - // 0x5E unused - public override int Version { get => Data[0x5F]; set => Data[0x5F] = (byte)value; } - private byte RIB8 { get => Data[0x60]; set => Data[0x60] = value; } // Sinnoh 3 - private byte RIB9 { get => Data[0x61]; set => Data[0x61] = value; } // Sinnoh 4 - private byte RIBA { get => Data[0x62]; set => Data[0x62] = value; } // Sinnoh 5 - private byte RIBB { get => Data[0x63]; set => Data[0x63] = value; } // Sinnoh 6 - public bool RibbonG4Cool { get => (RIB8 & (1 << 0)) == 1 << 0; set => RIB8 = (byte)((RIB8 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonG4CoolGreat { get => (RIB8 & (1 << 1)) == 1 << 1; set => RIB8 = (byte)((RIB8 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonG4CoolUltra { get => (RIB8 & (1 << 2)) == 1 << 2; set => RIB8 = (byte)((RIB8 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonG4CoolMaster { get => (RIB8 & (1 << 3)) == 1 << 3; set => RIB8 = (byte)((RIB8 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonG4Beauty { get => (RIB8 & (1 << 4)) == 1 << 4; set => RIB8 = (byte)((RIB8 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonG4BeautyGreat { get => (RIB8 & (1 << 5)) == 1 << 5; set => RIB8 = (byte)((RIB8 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonG4BeautyUltra { get => (RIB8 & (1 << 6)) == 1 << 6; set => RIB8 = (byte)((RIB8 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonG4BeautyMaster { get => (RIB8 & (1 << 7)) == 1 << 7; set => RIB8 = (byte)((RIB8 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonG4Cute { get => (RIB9 & (1 << 0)) == 1 << 0; set => RIB9 = (byte)((RIB9 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonG4CuteGreat { get => (RIB9 & (1 << 1)) == 1 << 1; set => RIB9 = (byte)((RIB9 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonG4CuteUltra { get => (RIB9 & (1 << 2)) == 1 << 2; set => RIB9 = (byte)((RIB9 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonG4CuteMaster { get => (RIB9 & (1 << 3)) == 1 << 3; set => RIB9 = (byte)((RIB9 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonG4Smart { get => (RIB9 & (1 << 4)) == 1 << 4; set => RIB9 = (byte)((RIB9 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonG4SmartGreat { get => (RIB9 & (1 << 5)) == 1 << 5; set => RIB9 = (byte)((RIB9 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonG4SmartUltra { get => (RIB9 & (1 << 6)) == 1 << 6; set => RIB9 = (byte)((RIB9 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonG4SmartMaster { get => (RIB9 & (1 << 7)) == 1 << 7; set => RIB9 = (byte)((RIB9 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonG4Tough { get => (RIBA & (1 << 0)) == 1 << 0; set => RIBA = (byte)((RIBA & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonG4ToughGreat { get => (RIBA & (1 << 1)) == 1 << 1; set => RIBA = (byte)((RIBA & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonG4ToughUltra { get => (RIBA & (1 << 2)) == 1 << 2; set => RIBA = (byte)((RIBA & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonG4ToughMaster { get => (RIBA & (1 << 3)) == 1 << 3; set => RIBA = (byte)((RIBA & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RIBA_4 { get => (RIBA & (1 << 4)) == 1 << 4; set => RIBA = (byte)((RIBA & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused - public bool RIBA_5 { get => (RIBA & (1 << 5)) == 1 << 5; set => RIBA = (byte)((RIBA & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused - public bool RIBA_6 { get => (RIBA & (1 << 6)) == 1 << 6; set => RIBA = (byte)((RIBA & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public bool RIBA_7 { get => (RIBA & (1 << 7)) == 1 << 7; set => RIBA = (byte)((RIBA & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - public bool RIBB_0 { get => (RIBB & (1 << 0)) == 1 << 0; set => RIBB = (byte)((RIBB & ~(1 << 0)) | (value ? 1 << 0 : 0)); } // Unused - public bool RIBB_1 { get => (RIBB & (1 << 1)) == 1 << 1; set => RIBB = (byte)((RIBB & ~(1 << 1)) | (value ? 1 << 1 : 0)); } // Unused - public bool RIBB_2 { get => (RIBB & (1 << 2)) == 1 << 2; set => RIBB = (byte)((RIBB & ~(1 << 2)) | (value ? 1 << 2 : 0)); } // Unused - public bool RIBB_3 { get => (RIBB & (1 << 3)) == 1 << 3; set => RIBB = (byte)((RIBB & ~(1 << 3)) | (value ? 1 << 3 : 0)); } // Unused - public bool RIBB_4 { get => (RIBB & (1 << 4)) == 1 << 4; set => RIBB = (byte)((RIBB & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused - public bool RIBB_5 { get => (RIBB & (1 << 5)) == 1 << 5; set => RIBB = (byte)((RIBB & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused - public bool RIBB_6 { get => (RIBB & (1 << 6)) == 1 << 6; set => RIBB = (byte)((RIBB & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public bool RIBB_7 { get => (RIBB & (1 << 7)) == 1 << 7; set => RIBB = (byte)((RIBB & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - // 0x64-0x67 Unused - #endregion - - #region Block D - public override string OT_Name { get => StringConverter5.GetString(OT_Trash); set => StringConverter5.SetString(OT_Trash, value.AsSpan(), 7, StringConverterOption.None); } - public override int Egg_Year { get => Data[0x78]; set => Data[0x78] = (byte)value; } - public override int Egg_Month { get => Data[0x79]; set => Data[0x79] = (byte)value; } - public override int Egg_Day { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } - public override int Met_Year { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } - public override int Met_Month { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } - public override int Met_Day { get => Data[0x7D]; set => Data[0x7D] = (byte)value; } - public override int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0x7E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x7E), (ushort)value); } - public override int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0x80)); set => WriteUInt16LittleEndian(Data.AsSpan(0x80), (ushort)value); } - private byte PKRS { get => Data[0x82]; set => Data[0x82] = value; } - public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } - public override int Ball { get => Data[0x83]; set => Data[0x83] = (byte)value; } - public override int Met_Level { get => Data[0x84] & ~0x80; set => Data[0x84] = (byte)((Data[0x84] & 0x80) | value); } - public override int OT_Gender { get => Data[0x84] >> 7; set => Data[0x84] = (byte)((Data[0x84] & ~0x80) | value << 7); } - public GroundTileType GroundTile { get => (GroundTileType)Data[0x85]; set => Data[0x85] = (byte)value; } - // 0x86 Unused - public byte PokeStarFame { get => Data[0x87]; set => Data[0x87] = value; } - public bool IsPokeStar { get => PokeStarFame > 250; set => PokeStarFame = value ? (byte)255 : (byte)0; } - #endregion - - #region Battle Stats - public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0x88)); set => WriteInt32LittleEndian(Data.AsSpan(0x88), value); } - public override int Stat_Level { get => Data[0x8C]; set => Data[0x8C] = (byte)value; } - public byte JunkByte { get => Data[0x8D]; set => Data[0x8D] = value; } - public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0x8E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8E), (ushort)value); } - public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0x90)); set => WriteUInt16LittleEndian(Data.AsSpan(0x90), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0x92)); set => WriteUInt16LittleEndian(Data.AsSpan(0x92), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0x94)); set => WriteUInt16LittleEndian(Data.AsSpan(0x94), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0x96)); set => WriteUInt16LittleEndian(Data.AsSpan(0x96), (ushort)value); } - public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0x98)); set => WriteUInt16LittleEndian(Data.AsSpan(0x98), (ushort)value); } - public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0x9A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x9A), (ushort)value); } - - public Span HeldMail => Data.AsSpan(0x9C, 0x38); - public ulong JunkData { get => ReadUInt64LittleEndian(Data.AsSpan(0xD4, 8)); set => WriteUInt64LittleEndian(Data.AsSpan(0xD4, 8), value); } - - #endregion - - // Generated Attributes - public override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 3); - public override int TSV => (TID ^ SID) >> 3; - - public override int Characteristic - { - get + int pm6 = (int)(PID % 6); // PID + int maxIV = MaximumIV; + int pm6stat = 0; + for (int i = 0; i < 6; i++) { - int pm6 = (int)(PID % 6); // PID - int maxIV = MaximumIV; - int pm6stat = 0; - for (int i = 0; i < 6; i++) - { - pm6stat = (pm6 + i) % 6; - if (GetIV(pm6stat) == maxIV) - break; - } - return (pm6stat * 5) + (maxIV % 5); + pm6stat = (pm6 + i) % 6; + if (GetIV(pm6stat) == maxIV) + break; } - } - - // Maximums - public override int MaxMoveID => Legal.MaxMoveID_5; - public override int MaxSpeciesID => Legal.MaxSpeciesID_5; - public override int MaxAbilityID => Legal.MaxAbilityID_5; - public override int MaxItemID => Legal.MaxItemID_5_B2W2; - public override int MaxBallID => Legal.MaxBallID_5; - public override int MaxGameID => Legal.MaxGameID_5; // B2 - public override int MaxIV => 31; - public override int MaxEV => 255; - public override int OTLength => 7; - public override int NickLength => 10; - - // Methods - protected override byte[] Encrypt() - { - RefreshChecksum(); - return PokeCrypto.EncryptArray45(Data); - } - - // Synthetic Trading Logic - public bool Trade(string SAV_Trainer, int SAV_TID, int SAV_SID, int SAV_GENDER, int Day = 1, int Month = 1, int Year = 2013) - { - if (IsEgg && !(SAV_Trainer == OT_Name && SAV_TID == TID && SAV_SID == SID && SAV_GENDER == OT_Gender)) - { - SetLinkTradeEgg(Day, Month, Year, Locations.LinkTrade5); - return true; - } - return false; - } - - public override int MarkingCount => 6; - - public override int GetMarking(int index) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - return (MarkValue >> index) & 1; - } - - public override void SetMarking(int index, int value) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - MarkValue = (MarkValue & ~(1 << index)) | ((value & 1) << index); - } - - public override void RefreshAbility(int n) - { - base.RefreshAbility(n); - HiddenAbility = n == 2; - } - - public PK6 ConvertToPK6() - { - PK6 pk6 = new() // Convert away! - { - EncryptionConstant = PID, - Species = Species, - TID = TID, - SID = SID, - EXP = EXP, - PID = PID, - Ability = Ability, - AbilityNumber = 1 << CalculateAbilityIndex(), - MarkValue = MarkValue & 0b_11_1111, - Language = Math.Max((int)LanguageID.Japanese, Language), // Hacked or Bad IngameTrade (Japanese B/W) - - CNT_Cool = CNT_Cool, - CNT_Beauty = CNT_Beauty, - CNT_Cute = CNT_Cute, - CNT_Smart = CNT_Smart, - CNT_Tough = CNT_Tough, - CNT_Sheen = CNT_Sheen, - - // Cap EVs - EV_HP = Math.Min(EV_HP, 252), - EV_ATK = Math.Min(EV_ATK, 252), - EV_DEF = Math.Min(EV_DEF, 252), - EV_SPA = Math.Min(EV_SPA, 252), - EV_SPD = Math.Min(EV_SPD, 252), - EV_SPE = Math.Min(EV_SPE, 252), - - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - - Move1_PPUps = Move1_PPUps, - Move2_PPUps = Move2_PPUps, - Move3_PPUps = Move3_PPUps, - Move4_PPUps = Move4_PPUps, - - IV_HP = IV_HP, - IV_ATK = IV_ATK, - IV_DEF = IV_DEF, - IV_SPA = IV_SPA, - IV_SPD = IV_SPD, - IV_SPE = IV_SPE, - IsEgg = IsEgg, - IsNicknamed = IsNicknamed, - - FatefulEncounter = FatefulEncounter, - Gender = Gender, - Form = Form, - Nature = Nature, - - Version = Version, - OT_Name = OT_Name, - - // Dates are kept upon transfer - MetDate = MetDate, - EggMetDate = EggMetDate, - - // Locations are kept upon transfer - Met_Location = Met_Location, - Egg_Location = Egg_Location, - - PKRS_Strain = PKRS_Strain, - PKRS_Days = PKRS_Days, - Ball = Ball, - - // OT Gender & Encounter Level - Met_Level = Met_Level, - OT_Gender = OT_Gender, - GroundTile = GroundTile, - - // Fill the Ribbon Counter Bytes - RibbonCountMemoryContest = CountContestRibbons(), - RibbonCountMemoryBattle = CountBattleRibbons(), - - // Copy Ribbons to their new locations. - RibbonChampionG3 = RibbonChampionG3, - RibbonChampionSinnoh = RibbonChampionSinnoh, - RibbonEffort = RibbonEffort, - - RibbonAlert = RibbonAlert, - RibbonShock = RibbonShock, - RibbonDowncast = RibbonDowncast, - RibbonCareless = RibbonCareless, - RibbonRelax = RibbonRelax, - RibbonSnooze = RibbonSnooze, - RibbonSmile = RibbonSmile, - RibbonGorgeous = RibbonGorgeous, - - RibbonRoyal = RibbonRoyal, - RibbonGorgeousRoyal = RibbonGorgeousRoyal, - RibbonArtist = RibbonArtist, - RibbonFootprint = RibbonFootprint, - RibbonRecord = RibbonRecord, - RibbonLegend = RibbonLegend, - RibbonCountry = RibbonCountry, - RibbonNational = RibbonNational, - - RibbonEarth = RibbonEarth, - RibbonWorld = RibbonWorld, - RibbonClassic = RibbonClassic, - RibbonPremier = RibbonPremier, - RibbonEvent = RibbonEvent, - RibbonBirthday = RibbonBirthday, - RibbonSpecial = RibbonSpecial, - RibbonSouvenir = RibbonSouvenir, - - RibbonWishing = RibbonWishing, - RibbonChampionBattle = RibbonChampionBattle, - RibbonChampionRegional = RibbonChampionRegional, - RibbonChampionNational = RibbonChampionNational, - RibbonChampionWorld = RibbonChampionWorld, - - // Write the Memories, Friendship, and Origin! - CurrentHandler = 1, - HT_Name = RecentTrainerCache.OT_Name, - HT_Gender = RecentTrainerCache.OT_Gender, - HT_Intensity = 1, - HT_Memory = 4, - HT_Feeling = MemoryContext6.GetRandomFeeling6(4, 10), - }; - - // Write Transfer Location - location is dependent on 3DS system that transfers. - RecentTrainerCache.SetConsoleRegionData3DS(pk6); - RecentTrainerCache.SetFirstCountryRegion(pk6); - - // Apply trash bytes for species name of current app language -- default to PKM's language if no match - int curLang = SpeciesName.GetSpeciesNameLanguage(Species, Nickname, 5); - if (curLang < 0) - curLang = Language; - pk6.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, curLang, 6); - if (IsNicknamed) - pk6.Nickname = Nickname; - - // When transferred, friendship gets reset. - pk6.OT_Friendship = pk6.HT_Friendship = PersonalInfo.BaseFriendship; - - // Gen6 changed the shiny correlation to have 2x the rate. - // If the current PID would be shiny with those increased odds, fix it. - if ((PSV ^ TSV) == 1) - pk6.PID ^= 0x80000000; - - // HMs are not deleted 5->6, transfer away (but fix if blank spots?) - pk6.FixMoves(); - - // Fix PP - pk6.HealPP(); - - // Fix Name Strings - StringConverter345.TransferGlyphs56(pk6.Nickname_Trash); - StringConverter345.TransferGlyphs56(pk6.OT_Trash); - - // Fix Checksum - pk6.RefreshChecksum(); - - return pk6; // Done! - } - - private byte CountBattleRibbons() - { - byte count = 0; - if (RibbonWinning) count++; - if (RibbonVictory) count++; - for (int i = 1; i < 7; i++) // Sinnoh Battle Ribbons - { - if (((Data[0x24] >> i) & 1) == 1) - count++; - } - - return count; - } - - private byte CountContestRibbons() - { - byte count = 0; - for (int i = 0; i < 8; i++) // Sinnoh 3, Hoenn 1 - { - if ((Data[0x60] >> i & 1) == 1) count++; - if (((Data[0x61] >> i) & 1) == 1) count++; - if (((Data[0x3C] >> i) & 1) == 1) count++; - if (((Data[0x3D] >> i) & 1) == 1) count++; - } - - for (int i = 0; i < 4; i++) // Sinnoh 4, Hoenn 2 - { - if (((Data[0x62] >> i) & 1) == 1) count++; - if (((Data[0x3E] >> i) & 1) == 1) count++; - } - - return count; - } - - private int CalculateAbilityIndex() - { - var pi = (PersonalInfoB2W2) PersonalInfo; - if (HiddenAbility) - return 2; - if (pi.Ability1 == Ability) - return 0; - if (pi.Ability2 == Ability) - return 1; - // reset ability, invalid - var pid = PID; - if (Gen5) - pid >>= 16; - return (int)(pid & 1); + return (pm6stat * 5) + (maxIV % 5); } } + + // Maximums + public override int MaxMoveID => Legal.MaxMoveID_5; + public override int MaxSpeciesID => Legal.MaxSpeciesID_5; + public override int MaxAbilityID => Legal.MaxAbilityID_5; + public override int MaxItemID => Legal.MaxItemID_5_B2W2; + public override int MaxBallID => Legal.MaxBallID_5; + public override int MaxGameID => Legal.MaxGameID_5; // B2 + public override int MaxIV => 31; + public override int MaxEV => 255; + public override int OTLength => 7; + public override int NickLength => 10; + + // Methods + protected override byte[] Encrypt() + { + RefreshChecksum(); + return PokeCrypto.EncryptArray45(Data); + } + + // Synthetic Trading Logic + public bool Trade(string SAV_Trainer, int SAV_TID, int SAV_SID, int SAV_GENDER, int Day = 1, int Month = 1, int Year = 2013) + { + if (IsEgg && !(SAV_Trainer == OT_Name && SAV_TID == TID && SAV_SID == SID && SAV_GENDER == OT_Gender)) + { + SetLinkTradeEgg(Day, Month, Year, Locations.LinkTrade5); + return true; + } + return false; + } + + public override int MarkingCount => 6; + + public override int GetMarking(int index) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return (MarkValue >> index) & 1; + } + + public override void SetMarking(int index, int value) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + MarkValue = (MarkValue & ~(1 << index)) | ((value & 1) << index); + } + + public override void RefreshAbility(int n) + { + base.RefreshAbility(n); + HiddenAbility = n == 2; + } + + public PK6 ConvertToPK6() + { + PK6 pk6 = new() // Convert away! + { + EncryptionConstant = PID, + Species = Species, + TID = TID, + SID = SID, + EXP = EXP, + PID = PID, + Ability = Ability, + AbilityNumber = 1 << CalculateAbilityIndex(), + MarkValue = MarkValue & 0b_11_1111, + Language = Math.Max((int)LanguageID.Japanese, Language), // Hacked or Bad IngameTrade (Japanese B/W) + + CNT_Cool = CNT_Cool, + CNT_Beauty = CNT_Beauty, + CNT_Cute = CNT_Cute, + CNT_Smart = CNT_Smart, + CNT_Tough = CNT_Tough, + CNT_Sheen = CNT_Sheen, + + // Cap EVs + EV_HP = Math.Min(EV_HP, 252), + EV_ATK = Math.Min(EV_ATK, 252), + EV_DEF = Math.Min(EV_DEF, 252), + EV_SPA = Math.Min(EV_SPA, 252), + EV_SPD = Math.Min(EV_SPD, 252), + EV_SPE = Math.Min(EV_SPE, 252), + + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + + Move1_PPUps = Move1_PPUps, + Move2_PPUps = Move2_PPUps, + Move3_PPUps = Move3_PPUps, + Move4_PPUps = Move4_PPUps, + + IV_HP = IV_HP, + IV_ATK = IV_ATK, + IV_DEF = IV_DEF, + IV_SPA = IV_SPA, + IV_SPD = IV_SPD, + IV_SPE = IV_SPE, + IsEgg = IsEgg, + IsNicknamed = IsNicknamed, + + FatefulEncounter = FatefulEncounter, + Gender = Gender, + Form = Form, + Nature = Nature, + + Version = Version, + OT_Name = OT_Name, + + // Dates are kept upon transfer + MetDate = MetDate, + EggMetDate = EggMetDate, + + // Locations are kept upon transfer + Met_Location = Met_Location, + Egg_Location = Egg_Location, + + PKRS_Strain = PKRS_Strain, + PKRS_Days = PKRS_Days, + Ball = Ball, + + // OT Gender & Encounter Level + Met_Level = Met_Level, + OT_Gender = OT_Gender, + GroundTile = GroundTile, + + // Fill the Ribbon Counter Bytes + RibbonCountMemoryContest = CountContestRibbons(), + RibbonCountMemoryBattle = CountBattleRibbons(), + + // Copy Ribbons to their new locations. + RibbonChampionG3 = RibbonChampionG3, + RibbonChampionSinnoh = RibbonChampionSinnoh, + RibbonEffort = RibbonEffort, + + RibbonAlert = RibbonAlert, + RibbonShock = RibbonShock, + RibbonDowncast = RibbonDowncast, + RibbonCareless = RibbonCareless, + RibbonRelax = RibbonRelax, + RibbonSnooze = RibbonSnooze, + RibbonSmile = RibbonSmile, + RibbonGorgeous = RibbonGorgeous, + + RibbonRoyal = RibbonRoyal, + RibbonGorgeousRoyal = RibbonGorgeousRoyal, + RibbonArtist = RibbonArtist, + RibbonFootprint = RibbonFootprint, + RibbonRecord = RibbonRecord, + RibbonLegend = RibbonLegend, + RibbonCountry = RibbonCountry, + RibbonNational = RibbonNational, + + RibbonEarth = RibbonEarth, + RibbonWorld = RibbonWorld, + RibbonClassic = RibbonClassic, + RibbonPremier = RibbonPremier, + RibbonEvent = RibbonEvent, + RibbonBirthday = RibbonBirthday, + RibbonSpecial = RibbonSpecial, + RibbonSouvenir = RibbonSouvenir, + + RibbonWishing = RibbonWishing, + RibbonChampionBattle = RibbonChampionBattle, + RibbonChampionRegional = RibbonChampionRegional, + RibbonChampionNational = RibbonChampionNational, + RibbonChampionWorld = RibbonChampionWorld, + + // Write the Memories, Friendship, and Origin! + CurrentHandler = 1, + HT_Name = RecentTrainerCache.OT_Name, + HT_Gender = RecentTrainerCache.OT_Gender, + HT_Intensity = 1, + HT_Memory = 4, + HT_Feeling = MemoryContext6.GetRandomFeeling6(4, 10), + }; + + // Write Transfer Location - location is dependent on 3DS system that transfers. + RecentTrainerCache.SetConsoleRegionData3DS(pk6); + RecentTrainerCache.SetFirstCountryRegion(pk6); + + // Apply trash bytes for species name of current app language -- default to PKM's language if no match + int curLang = SpeciesName.GetSpeciesNameLanguage(Species, Nickname, 5); + if (curLang < 0) + curLang = Language; + pk6.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, curLang, 6); + if (IsNicknamed) + pk6.Nickname = Nickname; + + // When transferred, friendship gets reset. + pk6.OT_Friendship = pk6.HT_Friendship = PersonalInfo.BaseFriendship; + + // Gen6 changed the shiny correlation to have 2x the rate. + // If the current PID would be shiny with those increased odds, fix it. + if ((PSV ^ TSV) == 1) + pk6.PID ^= 0x80000000; + + // HMs are not deleted 5->6, transfer away (but fix if blank spots?) + pk6.FixMoves(); + + // Fix PP + pk6.HealPP(); + + // Fix Name Strings + StringConverter345.TransferGlyphs56(pk6.Nickname_Trash); + StringConverter345.TransferGlyphs56(pk6.OT_Trash); + + // Fix Checksum + pk6.RefreshChecksum(); + + return pk6; // Done! + } + + private byte CountBattleRibbons() + { + byte count = 0; + if (RibbonWinning) count++; + if (RibbonVictory) count++; + for (int i = 1; i < 7; i++) // Sinnoh Battle Ribbons + { + if (((Data[0x24] >> i) & 1) == 1) + count++; + } + + return count; + } + + private byte CountContestRibbons() + { + byte count = 0; + for (int i = 0; i < 8; i++) // Sinnoh 3, Hoenn 1 + { + if (((Data[0x60] >> i) & 1) == 1) count++; + if (((Data[0x61] >> i) & 1) == 1) count++; + if (((Data[0x3C] >> i) & 1) == 1) count++; + if (((Data[0x3D] >> i) & 1) == 1) count++; + } + + for (int i = 0; i < 4; i++) // Sinnoh 4, Hoenn 2 + { + if (((Data[0x62] >> i) & 1) == 1) count++; + if (((Data[0x3E] >> i) & 1) == 1) count++; + } + + return count; + } + + private int CalculateAbilityIndex() + { + var pi = (PersonalInfoB2W2) PersonalInfo; + if (HiddenAbility) + return 2; + if (pi.Ability1 == Ability) + return 0; + if (pi.Ability2 == Ability) + return 1; + // reset ability, invalid + var pid = PID; + if (Gen5) + pid >>= 16; + return (int)(pid & 1); + } } diff --git a/PKHeX.Core/PKM/PK6.cs b/PKHeX.Core/PKM/PK6.cs index 27f8eea3b..9e6bebaec 100644 --- a/PKHeX.Core/PKM/PK6.cs +++ b/PKHeX.Core/PKM/PK6.cs @@ -1,530 +1,529 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 6 format. +public sealed class PK6 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, + IContestStats, IContestStatsMutable, IGeoTrack, ISuperTrain, IFormArgument, ITrainerMemories, IAffection, IGroundTile { - /// Generation 6 format. - public sealed class PK6 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, - IContestStats, IContestStatsMutable, IGeoTrack, ISuperTrain, IFormArgument, ITrainerMemories, IAffection, IGroundTile + private static readonly ushort[] Unused = { - private static readonly ushort[] Unused = - { - 0x36, 0x37, // Unused Ribbons - 0x58, 0x59, 0x73, 0x90, 0x91, 0x9E, 0x9F, 0xA0, 0xA1, 0xA7, 0xAA, 0xAB, 0xAC, 0xAD, 0xC8, 0xC9, 0xD7, 0xE4, 0xE5, 0xE6, 0xE7, - }; + 0x36, 0x37, // Unused Ribbons + 0x58, 0x59, 0x73, 0x90, 0x91, 0x9E, 0x9F, 0xA0, 0xA1, 0xA7, 0xAA, 0xAB, 0xAC, 0xAD, 0xC8, 0xC9, 0xD7, 0xE4, 0xE5, 0xE6, 0xE7, + }; - public override IReadOnlyList ExtraBytes => Unused; - public override EntityContext Context => EntityContext.Gen6; - public override PersonalInfo PersonalInfo => PersonalTable.AO.GetFormEntry(Species, Form); + public override IReadOnlyList ExtraBytes => Unused; + public override EntityContext Context => EntityContext.Gen6; + public override PersonalInfo PersonalInfo => PersonalTable.AO.GetFormEntry(Species, Form); - public PK6() : base(PokeCrypto.SIZE_6PARTY) { } - public PK6(byte[] data) : base(DecryptParty(data)) { } + public PK6() : base(PokeCrypto.SIZE_6PARTY) { } + public PK6(byte[] data) : base(DecryptParty(data)) { } - private static byte[] DecryptParty(byte[] data) - { - PokeCrypto.DecryptIfEncrypted67(ref data); - Array.Resize(ref data, PokeCrypto.SIZE_6PARTY); - return data; - } + private static byte[] DecryptParty(byte[] data) + { + PokeCrypto.DecryptIfEncrypted67(ref data); + Array.Resize(ref data, PokeCrypto.SIZE_6PARTY); + return data; + } - public override PKM Clone() => new PK6((byte[])Data.Clone()); + public override PKM Clone() => new PK6((byte[])Data.Clone()); - // Structure - #region Block A - public override uint EncryptionConstant - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); - } + // Structure + #region Block A + public override uint EncryptionConstant + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); + } - public override ushort Sanity - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); - } + public override ushort Sanity + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); + } - public override ushort Checksum - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); - } + public override ushort Checksum + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); + } - public override int Species - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); - } + public override int Species + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); + } - public override int HeldItem - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); - } + public override int HeldItem + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); + } - public override int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); - } + public override int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); + } - public override int SID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); - } + public override int SID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); + } - public override uint EXP - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); - } + public override uint EXP + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); + } - public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; } - public override int AbilityNumber { get => Data[0x15]; set => Data[0x15] = (byte)value; } - public int TrainingBagHits { get => Data[0x16]; set => Data[0x16] = (byte)value; } - public int TrainingBag { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public override int AbilityNumber { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public int TrainingBagHits { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public int TrainingBag { get => Data[0x17]; set => Data[0x17] = (byte)value; } - public override uint PID - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x18)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x18), value); - } + public override uint PID + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x18)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x18), value); + } - public override int Nature { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } - public override bool FatefulEncounter { get => (Data[0x1D] & 1) == 1; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x01) | (value ? 1 : 0)); } - public override int Gender { get => (Data[0x1D] >> 1) & 0x3; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x06) | (value << 1)); } - public override int Form { get => Data[0x1D] >> 3; set => Data[0x1D] = (byte)((Data[0x1D] & 0x07) | (value << 3)); } - public override int EV_HP { get => Data[0x1E]; set => Data[0x1E] = (byte)value; } - public override int EV_ATK { get => Data[0x1F]; set => Data[0x1F] = (byte)value; } - public override int EV_DEF { get => Data[0x20]; set => Data[0x20] = (byte)value; } - public override int EV_SPE { get => Data[0x21]; set => Data[0x21] = (byte)value; } - public override int EV_SPA { get => Data[0x22]; set => Data[0x22] = (byte)value; } - public override int EV_SPD { get => Data[0x23]; set => Data[0x23] = (byte)value; } - public byte CNT_Cool { get => Data[0x24]; set => Data[0x24] = value; } - public byte CNT_Beauty { get => Data[0x25]; set => Data[0x25] = value; } - public byte CNT_Cute { get => Data[0x26]; set => Data[0x26] = value; } - public byte CNT_Smart { get => Data[0x27]; set => Data[0x27] = value; } - public byte CNT_Tough { get => Data[0x28]; set => Data[0x28] = value; } - public byte CNT_Sheen { get => Data[0x29]; set => Data[0x29] = value; } - public override int MarkValue { get => Data[0x2A]; set => Data[0x2A] = (byte)value; } - private byte PKRS { get => Data[0x2B]; set => Data[0x2B] = value; } - public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); } - private byte ST1 { get => Data[0x2C]; set => Data[0x2C] = value; } - public bool Unused0 { get => (ST1 & (1 << 0)) == 1 << 0; set => ST1 = (byte)((ST1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool Unused1 { get => (ST1 & (1 << 1)) == 1 << 1; set => ST1 = (byte)((ST1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool SuperTrain1_SPA { get => (ST1 & (1 << 2)) == 1 << 2; set => ST1 = (byte)((ST1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool SuperTrain1_HP { get => (ST1 & (1 << 3)) == 1 << 3; set => ST1 = (byte)((ST1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool SuperTrain1_ATK { get => (ST1 & (1 << 4)) == 1 << 4; set => ST1 = (byte)((ST1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool SuperTrain1_SPD { get => (ST1 & (1 << 5)) == 1 << 5; set => ST1 = (byte)((ST1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool SuperTrain1_SPE { get => (ST1 & (1 << 6)) == 1 << 6; set => ST1 = (byte)((ST1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool SuperTrain1_DEF { get => (ST1 & (1 << 7)) == 1 << 7; set => ST1 = (byte)((ST1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - private byte ST2 { get => Data[0x2D]; set => Data[0x2D] = value; } - public bool SuperTrain2_SPA { get => (ST2 & (1 << 0)) == 1 << 0; set => ST2 = (byte)((ST2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool SuperTrain2_HP { get => (ST2 & (1 << 1)) == 1 << 1; set => ST2 = (byte)((ST2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool SuperTrain2_ATK { get => (ST2 & (1 << 2)) == 1 << 2; set => ST2 = (byte)((ST2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool SuperTrain2_SPD { get => (ST2 & (1 << 3)) == 1 << 3; set => ST2 = (byte)((ST2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool SuperTrain2_SPE { get => (ST2 & (1 << 4)) == 1 << 4; set => ST2 = (byte)((ST2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool SuperTrain2_DEF { get => (ST2 & (1 << 5)) == 1 << 5; set => ST2 = (byte)((ST2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool SuperTrain3_SPA { get => (ST2 & (1 << 6)) == 1 << 6; set => ST2 = (byte)((ST2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool SuperTrain3_HP { get => (ST2 & (1 << 7)) == 1 << 7; set => ST2 = (byte)((ST2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - private byte ST3 { get => Data[0x2E]; set => Data[0x2E] = value; } - public bool SuperTrain3_ATK { get => (ST3 & (1 << 0)) == 1 << 0; set => ST3 = (byte)((ST3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool SuperTrain3_SPD { get => (ST3 & (1 << 1)) == 1 << 1; set => ST3 = (byte)((ST3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool SuperTrain3_SPE { get => (ST3 & (1 << 2)) == 1 << 2; set => ST3 = (byte)((ST3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool SuperTrain3_DEF { get => (ST3 & (1 << 3)) == 1 << 3; set => ST3 = (byte)((ST3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool SuperTrain4_1 { get => (ST3 & (1 << 4)) == 1 << 4; set => ST3 = (byte)((ST3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool SuperTrain5_1 { get => (ST3 & (1 << 5)) == 1 << 5; set => ST3 = (byte)((ST3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool SuperTrain5_2 { get => (ST3 & (1 << 6)) == 1 << 6; set => ST3 = (byte)((ST3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool SuperTrain5_3 { get => (ST3 & (1 << 7)) == 1 << 7; set => ST3 = (byte)((ST3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - private byte ST4 { get => Data[0x2F]; set => Data[0x2F] = value; } - public bool SuperTrain5_4 { get => (ST4 & (1 << 0)) == 1 << 0; set => ST4 = (byte)((ST4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool SuperTrain6_1 { get => (ST4 & (1 << 1)) == 1 << 1; set => ST4 = (byte)((ST4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool SuperTrain6_2 { get => (ST4 & (1 << 2)) == 1 << 2; set => ST4 = (byte)((ST4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool SuperTrain6_3 { get => (ST4 & (1 << 3)) == 1 << 3; set => ST4 = (byte)((ST4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool SuperTrain7_1 { get => (ST4 & (1 << 4)) == 1 << 4; set => ST4 = (byte)((ST4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool SuperTrain7_2 { get => (ST4 & (1 << 5)) == 1 << 5; set => ST4 = (byte)((ST4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool SuperTrain7_3 { get => (ST4 & (1 << 6)) == 1 << 6; set => ST4 = (byte)((ST4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool SuperTrain8_1 { get => (ST4 & (1 << 7)) == 1 << 7; set => ST4 = (byte)((ST4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public uint SuperTrainBitFlags { get => ReadUInt32LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x2C), value); } - private byte RIB0 { get => Data[0x30]; set => Data[0x30] = value; } // Ribbons are read as uints, but let's keep them per byte. - private byte RIB1 { get => Data[0x31]; set => Data[0x31] = value; } - private byte RIB2 { get => Data[0x32]; set => Data[0x32] = value; } - private byte RIB3 { get => Data[0x33]; set => Data[0x33] = value; } - private byte RIB4 { get => Data[0x34]; set => Data[0x34] = value; } - private byte RIB5 { get => Data[0x35]; set => Data[0x35] = value; } - public bool RibbonChampionKalos { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonChampionG3 { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonChampionSinnoh { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonBestFriends { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonTraining { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonBattlerSkillful { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonBattlerExpert { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonEffort { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonAlert { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonShock { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonDowncast { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonCareless { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonRelax { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonSnooze { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonSmile { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonGorgeous { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonArtist { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonFootprint { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonRecord { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonLegend { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonCountry { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonNational { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonEarth { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonWorld { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonEvent { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonBirthday { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonSpecial { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonSouvenir { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonWishing { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonChampionBattle { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonChampionRegional { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonChampionNational { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonChampionWorld { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool HasContestMemoryRibbon { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool HasBattleMemoryRibbon { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonChampionG6Hoenn { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonContestStar { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonMasterCoolness { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonMasterBeauty { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonMasterCuteness { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonMasterCleverness { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonMasterToughness { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RIB5_6 { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public bool RIB5_7 { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - public int RibbonCountMemoryContest { get => Data[0x38]; set => HasContestMemoryRibbon = (Data[0x38] = (byte)value) != 0; } - public int RibbonCountMemoryBattle { get => Data[0x39]; set => HasBattleMemoryRibbon = (Data[0x39] = (byte)value) != 0; } - private ushort DistByte { get => ReadUInt16LittleEndian(Data.AsSpan(0x3A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3A), value); } - public bool DistSuperTrain1 { get => (DistByte & (1 << 0)) == 1 << 0; set => DistByte = (byte)((DistByte & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool DistSuperTrain2 { get => (DistByte & (1 << 1)) == 1 << 1; set => DistByte = (byte)((DistByte & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool DistSuperTrain3 { get => (DistByte & (1 << 2)) == 1 << 2; set => DistByte = (byte)((DistByte & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool DistSuperTrain4 { get => (DistByte & (1 << 3)) == 1 << 3; set => DistByte = (byte)((DistByte & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool DistSuperTrain5 { get => (DistByte & (1 << 4)) == 1 << 4; set => DistByte = (byte)((DistByte & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool DistSuperTrain6 { get => (DistByte & (1 << 5)) == 1 << 5; set => DistByte = (byte)((DistByte & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool Dist7 { get => (DistByte & (1 << 6)) == 1 << 6; set => DistByte = (byte)((DistByte & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool Dist8 { get => (DistByte & (1 << 7)) == 1 << 7; set => DistByte = (byte)((DistByte & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x3C), value); } - public byte FormArgumentMaximum { get => (byte)FormArgument; set => FormArgument = value & 0xFFu; } - #endregion - #region Block B - public override string Nickname - { - get => StringConverter6.GetString(Nickname_Trash); - set => StringConverter6.SetString(Nickname_Trash, value.AsSpan(), 12, StringConverterOption.None); - } + public override int Nature { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public override bool FatefulEncounter { get => (Data[0x1D] & 1) == 1; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x01) | (value ? 1 : 0)); } + public override int Gender { get => (Data[0x1D] >> 1) & 0x3; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x06) | (value << 1)); } + public override int Form { get => Data[0x1D] >> 3; set => Data[0x1D] = (byte)((Data[0x1D] & 0x07) | (value << 3)); } + public override int EV_HP { get => Data[0x1E]; set => Data[0x1E] = (byte)value; } + public override int EV_ATK { get => Data[0x1F]; set => Data[0x1F] = (byte)value; } + public override int EV_DEF { get => Data[0x20]; set => Data[0x20] = (byte)value; } + public override int EV_SPE { get => Data[0x21]; set => Data[0x21] = (byte)value; } + public override int EV_SPA { get => Data[0x22]; set => Data[0x22] = (byte)value; } + public override int EV_SPD { get => Data[0x23]; set => Data[0x23] = (byte)value; } + public byte CNT_Cool { get => Data[0x24]; set => Data[0x24] = value; } + public byte CNT_Beauty { get => Data[0x25]; set => Data[0x25] = value; } + public byte CNT_Cute { get => Data[0x26]; set => Data[0x26] = value; } + public byte CNT_Smart { get => Data[0x27]; set => Data[0x27] = value; } + public byte CNT_Tough { get => Data[0x28]; set => Data[0x28] = value; } + public byte CNT_Sheen { get => Data[0x29]; set => Data[0x29] = value; } + public override int MarkValue { get => Data[0x2A]; set => Data[0x2A] = (byte)value; } + private byte PKRS { get => Data[0x2B]; set => Data[0x2B] = value; } + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + private byte ST1 { get => Data[0x2C]; set => Data[0x2C] = value; } + public bool Unused0 { get => (ST1 & (1 << 0)) == 1 << 0; set => ST1 = (byte)((ST1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool Unused1 { get => (ST1 & (1 << 1)) == 1 << 1; set => ST1 = (byte)((ST1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool SuperTrain1_SPA { get => (ST1 & (1 << 2)) == 1 << 2; set => ST1 = (byte)((ST1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool SuperTrain1_HP { get => (ST1 & (1 << 3)) == 1 << 3; set => ST1 = (byte)((ST1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool SuperTrain1_ATK { get => (ST1 & (1 << 4)) == 1 << 4; set => ST1 = (byte)((ST1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool SuperTrain1_SPD { get => (ST1 & (1 << 5)) == 1 << 5; set => ST1 = (byte)((ST1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool SuperTrain1_SPE { get => (ST1 & (1 << 6)) == 1 << 6; set => ST1 = (byte)((ST1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool SuperTrain1_DEF { get => (ST1 & (1 << 7)) == 1 << 7; set => ST1 = (byte)((ST1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + private byte ST2 { get => Data[0x2D]; set => Data[0x2D] = value; } + public bool SuperTrain2_SPA { get => (ST2 & (1 << 0)) == 1 << 0; set => ST2 = (byte)((ST2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool SuperTrain2_HP { get => (ST2 & (1 << 1)) == 1 << 1; set => ST2 = (byte)((ST2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool SuperTrain2_ATK { get => (ST2 & (1 << 2)) == 1 << 2; set => ST2 = (byte)((ST2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool SuperTrain2_SPD { get => (ST2 & (1 << 3)) == 1 << 3; set => ST2 = (byte)((ST2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool SuperTrain2_SPE { get => (ST2 & (1 << 4)) == 1 << 4; set => ST2 = (byte)((ST2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool SuperTrain2_DEF { get => (ST2 & (1 << 5)) == 1 << 5; set => ST2 = (byte)((ST2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool SuperTrain3_SPA { get => (ST2 & (1 << 6)) == 1 << 6; set => ST2 = (byte)((ST2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool SuperTrain3_HP { get => (ST2 & (1 << 7)) == 1 << 7; set => ST2 = (byte)((ST2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + private byte ST3 { get => Data[0x2E]; set => Data[0x2E] = value; } + public bool SuperTrain3_ATK { get => (ST3 & (1 << 0)) == 1 << 0; set => ST3 = (byte)((ST3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool SuperTrain3_SPD { get => (ST3 & (1 << 1)) == 1 << 1; set => ST3 = (byte)((ST3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool SuperTrain3_SPE { get => (ST3 & (1 << 2)) == 1 << 2; set => ST3 = (byte)((ST3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool SuperTrain3_DEF { get => (ST3 & (1 << 3)) == 1 << 3; set => ST3 = (byte)((ST3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool SuperTrain4_1 { get => (ST3 & (1 << 4)) == 1 << 4; set => ST3 = (byte)((ST3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool SuperTrain5_1 { get => (ST3 & (1 << 5)) == 1 << 5; set => ST3 = (byte)((ST3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool SuperTrain5_2 { get => (ST3 & (1 << 6)) == 1 << 6; set => ST3 = (byte)((ST3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool SuperTrain5_3 { get => (ST3 & (1 << 7)) == 1 << 7; set => ST3 = (byte)((ST3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + private byte ST4 { get => Data[0x2F]; set => Data[0x2F] = value; } + public bool SuperTrain5_4 { get => (ST4 & (1 << 0)) == 1 << 0; set => ST4 = (byte)((ST4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool SuperTrain6_1 { get => (ST4 & (1 << 1)) == 1 << 1; set => ST4 = (byte)((ST4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool SuperTrain6_2 { get => (ST4 & (1 << 2)) == 1 << 2; set => ST4 = (byte)((ST4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool SuperTrain6_3 { get => (ST4 & (1 << 3)) == 1 << 3; set => ST4 = (byte)((ST4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool SuperTrain7_1 { get => (ST4 & (1 << 4)) == 1 << 4; set => ST4 = (byte)((ST4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool SuperTrain7_2 { get => (ST4 & (1 << 5)) == 1 << 5; set => ST4 = (byte)((ST4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool SuperTrain7_3 { get => (ST4 & (1 << 6)) == 1 << 6; set => ST4 = (byte)((ST4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool SuperTrain8_1 { get => (ST4 & (1 << 7)) == 1 << 7; set => ST4 = (byte)((ST4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public uint SuperTrainBitFlags { get => ReadUInt32LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x2C), value); } + private byte RIB0 { get => Data[0x30]; set => Data[0x30] = value; } // Ribbons are read as uints, but let's keep them per byte. + private byte RIB1 { get => Data[0x31]; set => Data[0x31] = value; } + private byte RIB2 { get => Data[0x32]; set => Data[0x32] = value; } + private byte RIB3 { get => Data[0x33]; set => Data[0x33] = value; } + private byte RIB4 { get => Data[0x34]; set => Data[0x34] = value; } + private byte RIB5 { get => Data[0x35]; set => Data[0x35] = value; } + public bool RibbonChampionKalos { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonChampionG3 { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonChampionSinnoh { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonBestFriends { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonTraining { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonBattlerSkillful { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonBattlerExpert { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonEffort { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonAlert { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonShock { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonDowncast { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonCareless { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonRelax { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonSnooze { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonSmile { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonGorgeous { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonArtist { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonFootprint { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonRecord { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonLegend { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonCountry { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonNational { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonEarth { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonWorld { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonEvent { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonBirthday { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonSpecial { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonSouvenir { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonWishing { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonChampionBattle { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonChampionRegional { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonChampionNational { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonChampionWorld { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool HasContestMemoryRibbon { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool HasBattleMemoryRibbon { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonChampionG6Hoenn { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonContestStar { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonMasterCoolness { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonMasterBeauty { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonMasterCuteness { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonMasterCleverness { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonMasterToughness { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RIB5_6 { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public bool RIB5_7 { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + public int RibbonCountMemoryContest { get => Data[0x38]; set => HasContestMemoryRibbon = (Data[0x38] = (byte)value) != 0; } + public int RibbonCountMemoryBattle { get => Data[0x39]; set => HasBattleMemoryRibbon = (Data[0x39] = (byte)value) != 0; } + private ushort DistByte { get => ReadUInt16LittleEndian(Data.AsSpan(0x3A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3A), value); } + public bool DistSuperTrain1 { get => (DistByte & (1 << 0)) == 1 << 0; set => DistByte = (byte)((DistByte & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool DistSuperTrain2 { get => (DistByte & (1 << 1)) == 1 << 1; set => DistByte = (byte)((DistByte & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool DistSuperTrain3 { get => (DistByte & (1 << 2)) == 1 << 2; set => DistByte = (byte)((DistByte & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool DistSuperTrain4 { get => (DistByte & (1 << 3)) == 1 << 3; set => DistByte = (byte)((DistByte & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool DistSuperTrain5 { get => (DistByte & (1 << 4)) == 1 << 4; set => DistByte = (byte)((DistByte & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool DistSuperTrain6 { get => (DistByte & (1 << 5)) == 1 << 5; set => DistByte = (byte)((DistByte & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool Dist7 { get => (DistByte & (1 << 6)) == 1 << 6; set => DistByte = (byte)((DistByte & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool Dist8 { get => (DistByte & (1 << 7)) == 1 << 7; set => DistByte = (byte)((DistByte & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x3C), value); } + public byte FormArgumentMaximum { get => (byte)FormArgument; set => FormArgument = value & 0xFFu; } + #endregion + #region Block B + public override string Nickname + { + get => StringConverter6.GetString(Nickname_Trash); + set => StringConverter6.SetString(Nickname_Trash, value.AsSpan(), 12, StringConverterOption.None); + } - public override int Move1 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), (ushort)value); - } + public override int Move1 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), (ushort)value); + } - public override int Move2 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); - } + public override int Move2 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); + } - public override int Move3 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); - } + public override int Move3 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); + } - public override int Move4 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x60)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x60), (ushort)value); - } + public override int Move4 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x60)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x60), (ushort)value); + } - public override int Move1_PP { get => Data[0x62]; set => Data[0x62] = (byte)value; } - public override int Move2_PP { get => Data[0x63]; set => Data[0x63] = (byte)value; } - public override int Move3_PP { get => Data[0x64]; set => Data[0x64] = (byte)value; } - public override int Move4_PP { get => Data[0x65]; set => Data[0x65] = (byte)value; } - public override int Move1_PPUps { get => Data[0x66]; set => Data[0x66] = (byte)value; } - public override int Move2_PPUps { get => Data[0x67]; set => Data[0x67] = (byte)value; } - public override int Move3_PPUps { get => Data[0x68]; set => Data[0x68] = (byte)value; } - public override int Move4_PPUps { get => Data[0x69]; set => Data[0x69] = (byte)value; } + public override int Move1_PP { get => Data[0x62]; set => Data[0x62] = (byte)value; } + public override int Move2_PP { get => Data[0x63]; set => Data[0x63] = (byte)value; } + public override int Move3_PP { get => Data[0x64]; set => Data[0x64] = (byte)value; } + public override int Move4_PP { get => Data[0x65]; set => Data[0x65] = (byte)value; } + public override int Move1_PPUps { get => Data[0x66]; set => Data[0x66] = (byte)value; } + public override int Move2_PPUps { get => Data[0x67]; set => Data[0x67] = (byte)value; } + public override int Move3_PPUps { get => Data[0x68]; set => Data[0x68] = (byte)value; } + public override int Move4_PPUps { get => Data[0x69]; set => Data[0x69] = (byte)value; } - public override int RelearnMove1 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); - } + public override int RelearnMove1 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); + } - public override int RelearnMove2 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6C), (ushort)value); - } + public override int RelearnMove2 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6C), (ushort)value); + } - public override int RelearnMove3 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6E), (ushort)value); - } + public override int RelearnMove3 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6E), (ushort)value); + } - public override int RelearnMove4 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x70)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x70), (ushort)value); - } + public override int RelearnMove4 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x70)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x70), (ushort)value); + } - public bool SecretSuperTrainingUnlocked { get => (Data[0x72] & 1) == 1; set => Data[0x72] = (byte)((Data[0x72] & ~1) | (value ? 1 : 0)); } - public bool SecretSuperTrainingComplete { get => (Data[0x72] & 2) == 2; set => Data[0x72] = (byte)((Data[0x72] & ~2) | (value ? 2 : 0)); } - // 0x73 Unused - private uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x74)); set => WriteUInt32LittleEndian(Data.AsSpan(0x74), value); } - public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } - public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } - public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } - public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } - public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } - public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } - public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } - public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } - #endregion - #region Block C - public override string HT_Name - { - get => StringConverter6.GetString(HT_Trash); - set => StringConverter6.SetString(HT_Trash, value.AsSpan(), 12, StringConverterOption.None); - } - public override int HT_Gender { get => Data[0x92]; set => Data[0x92] = (byte)value; } - public override int CurrentHandler { get => Data[0x93]; set => Data[0x93] = (byte)value; } - public byte Geo1_Region { get => Data[0x94]; set => Data[0x94] = value; } - public byte Geo1_Country { get => Data[0x95]; set => Data[0x95] = value; } - public byte Geo2_Region { get => Data[0x96]; set => Data[0x96] = value; } - public byte Geo2_Country { get => Data[0x97]; set => Data[0x97] = value; } - public byte Geo3_Region { get => Data[0x98]; set => Data[0x98] = value; } - public byte Geo3_Country { get => Data[0x99]; set => Data[0x99] = value; } - public byte Geo4_Region { get => Data[0x9A]; set => Data[0x9A] = value; } - public byte Geo4_Country { get => Data[0x9B]; set => Data[0x9B] = value; } - public byte Geo5_Region { get => Data[0x9C]; set => Data[0x9C] = value; } - public byte Geo5_Country { get => Data[0x9D]; set => Data[0x9D] = value; } - // 0x9E Unused - // 0x9F Unused - // 0xA0 Unused - // 0xA1 Unused - public override int HT_Friendship { get => Data[0xA2]; set => Data[0xA2] = (byte)value; } - public byte HT_Affection { get => Data[0xA3]; set => Data[0xA3] = value; } - public byte HT_Intensity { get => Data[0xA4]; set => Data[0xA4] = value; } - public byte HT_Memory { get => Data[0xA5]; set => Data[0xA5] = value; } - public byte HT_Feeling { get => Data[0xA6]; set => Data[0xA6] = value; } - // 0xA7 Unused - public ushort HT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xA8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA8), value); } - // 0xAA Unused - // 0xAB Unused - // 0xAC Unused - // 0xAD Unused - public override byte Fullness { get => Data[0xAE]; set => Data[0xAE] = value; } - public override byte Enjoyment { get => Data[0xAF]; set => Data[0xAF] = value; } - #endregion - #region Block D - public override string OT_Name - { - get => StringConverter6.GetString(OT_Trash); - set => StringConverter6.SetString(OT_Trash, value.AsSpan(), 12, StringConverterOption.None); - } - public override int OT_Friendship { get => Data[0xCA]; set => Data[0xCA] = (byte)value; } - public byte OT_Affection { get => Data[0xCB]; set => Data[0xCB] = value; } - public byte OT_Intensity { get => Data[0xCC]; set => Data[0xCC] = value; } - public byte OT_Memory { get => Data[0xCD]; set => Data[0xCD] = value; } - public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xCE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xCE), value); } - public byte OT_Feeling { get => Data[0xD0]; set => Data[0xD0] = value; } - public override int Egg_Year { get => Data[0xD1]; set => Data[0xD1] = (byte)value; } - public override int Egg_Month { get => Data[0xD2]; set => Data[0xD2] = (byte)value; } - public override int Egg_Day { get => Data[0xD3]; set => Data[0xD3] = (byte)value; } - public override int Met_Year { get => Data[0xD4]; set => Data[0xD4] = (byte)value; } - public override int Met_Month { get => Data[0xD5]; set => Data[0xD5] = (byte)value; } - public override int Met_Day { get => Data[0xD6]; set => Data[0xD6] = (byte)value; } - // 0xD7 Unused - public override int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), (ushort)value); } - public override int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), (ushort)value); } - public override int Ball { get => Data[0xDC]; set => Data[0xDC] = (byte)value; } - public override int Met_Level { get => Data[0xDD] & ~0x80; set => Data[0xDD] = (byte)((Data[0xDD] & 0x80) | value); } - public override int OT_Gender { get => Data[0xDD] >> 7; set => Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); } - public GroundTileType GroundTile { get => (GroundTileType)Data[0xDE]; set => Data[0xDE] = (byte)value; } - public override int Version { get => Data[0xDF]; set => Data[0xDF] = (byte)value; } - public byte Country { get => Data[0xE0]; set => Data[0xE0] = value; } - public byte Region { get => Data[0xE1]; set => Data[0xE1] = value; } - public byte ConsoleRegion { get => Data[0xE2]; set => Data[0xE2] = value; } - public override int Language { get => Data[0xE3]; set => Data[0xE3] = (byte)value; } - #endregion - #region Battle Stats - public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0xE8)); set => WriteInt32LittleEndian(Data.AsSpan(0xE8), value); } - public override int Stat_Level { get => Data[0xEC]; set => Data[0xEC] = (byte)value; } - public byte FormArgumentRemain { get => Data[0xED]; set => Data[0xED] = value; } - public byte FormArgumentElapsed { get => Data[0xEE]; set => Data[0xEE] = value; } - public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0xF0)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF0), (ushort)value); } - public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0xF2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF2), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0xF4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF4), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0xF6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF6), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0xF8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF8), (ushort)value); } - public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0xFA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFA), (ushort)value); } - public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0xFC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFC), (ushort)value); } - #endregion + public bool SecretSuperTrainingUnlocked { get => (Data[0x72] & 1) == 1; set => Data[0x72] = (byte)((Data[0x72] & ~1) | (value ? 1 : 0)); } + public bool SecretSuperTrainingComplete { get => (Data[0x72] & 2) == 2; set => Data[0x72] = (byte)((Data[0x72] & ~2) | (value ? 2 : 0)); } + // 0x73 Unused + private uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x74)); set => WriteUInt32LittleEndian(Data.AsSpan(0x74), value); } + public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } + public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } + public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } + public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } + public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } + public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } + public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } + public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } + #endregion + #region Block C + public override string HT_Name + { + get => StringConverter6.GetString(HT_Trash); + set => StringConverter6.SetString(HT_Trash, value.AsSpan(), 12, StringConverterOption.None); + } + public override int HT_Gender { get => Data[0x92]; set => Data[0x92] = (byte)value; } + public override int CurrentHandler { get => Data[0x93]; set => Data[0x93] = (byte)value; } + public byte Geo1_Region { get => Data[0x94]; set => Data[0x94] = value; } + public byte Geo1_Country { get => Data[0x95]; set => Data[0x95] = value; } + public byte Geo2_Region { get => Data[0x96]; set => Data[0x96] = value; } + public byte Geo2_Country { get => Data[0x97]; set => Data[0x97] = value; } + public byte Geo3_Region { get => Data[0x98]; set => Data[0x98] = value; } + public byte Geo3_Country { get => Data[0x99]; set => Data[0x99] = value; } + public byte Geo4_Region { get => Data[0x9A]; set => Data[0x9A] = value; } + public byte Geo4_Country { get => Data[0x9B]; set => Data[0x9B] = value; } + public byte Geo5_Region { get => Data[0x9C]; set => Data[0x9C] = value; } + public byte Geo5_Country { get => Data[0x9D]; set => Data[0x9D] = value; } + // 0x9E Unused + // 0x9F Unused + // 0xA0 Unused + // 0xA1 Unused + public override int HT_Friendship { get => Data[0xA2]; set => Data[0xA2] = (byte)value; } + public byte HT_Affection { get => Data[0xA3]; set => Data[0xA3] = value; } + public byte HT_Intensity { get => Data[0xA4]; set => Data[0xA4] = value; } + public byte HT_Memory { get => Data[0xA5]; set => Data[0xA5] = value; } + public byte HT_Feeling { get => Data[0xA6]; set => Data[0xA6] = value; } + // 0xA7 Unused + public ushort HT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xA8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA8), value); } + // 0xAA Unused + // 0xAB Unused + // 0xAC Unused + // 0xAD Unused + public override byte Fullness { get => Data[0xAE]; set => Data[0xAE] = value; } + public override byte Enjoyment { get => Data[0xAF]; set => Data[0xAF] = value; } + #endregion + #region Block D + public override string OT_Name + { + get => StringConverter6.GetString(OT_Trash); + set => StringConverter6.SetString(OT_Trash, value.AsSpan(), 12, StringConverterOption.None); + } + public override int OT_Friendship { get => Data[0xCA]; set => Data[0xCA] = (byte)value; } + public byte OT_Affection { get => Data[0xCB]; set => Data[0xCB] = value; } + public byte OT_Intensity { get => Data[0xCC]; set => Data[0xCC] = value; } + public byte OT_Memory { get => Data[0xCD]; set => Data[0xCD] = value; } + public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xCE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xCE), value); } + public byte OT_Feeling { get => Data[0xD0]; set => Data[0xD0] = value; } + public override int Egg_Year { get => Data[0xD1]; set => Data[0xD1] = (byte)value; } + public override int Egg_Month { get => Data[0xD2]; set => Data[0xD2] = (byte)value; } + public override int Egg_Day { get => Data[0xD3]; set => Data[0xD3] = (byte)value; } + public override int Met_Year { get => Data[0xD4]; set => Data[0xD4] = (byte)value; } + public override int Met_Month { get => Data[0xD5]; set => Data[0xD5] = (byte)value; } + public override int Met_Day { get => Data[0xD6]; set => Data[0xD6] = (byte)value; } + // 0xD7 Unused + public override int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), (ushort)value); } + public override int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), (ushort)value); } + public override int Ball { get => Data[0xDC]; set => Data[0xDC] = (byte)value; } + public override int Met_Level { get => Data[0xDD] & ~0x80; set => Data[0xDD] = (byte)((Data[0xDD] & 0x80) | value); } + public override int OT_Gender { get => Data[0xDD] >> 7; set => Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); } + public GroundTileType GroundTile { get => (GroundTileType)Data[0xDE]; set => Data[0xDE] = (byte)value; } + public override int Version { get => Data[0xDF]; set => Data[0xDF] = (byte)value; } + public byte Country { get => Data[0xE0]; set => Data[0xE0] = value; } + public byte Region { get => Data[0xE1]; set => Data[0xE1] = value; } + public byte ConsoleRegion { get => Data[0xE2]; set => Data[0xE2] = value; } + public override int Language { get => Data[0xE3]; set => Data[0xE3] = (byte)value; } + #endregion + #region Battle Stats + public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0xE8)); set => WriteInt32LittleEndian(Data.AsSpan(0xE8), value); } + public override int Stat_Level { get => Data[0xEC]; set => Data[0xEC] = (byte)value; } + public byte FormArgumentRemain { get => Data[0xED]; set => Data[0xED] = value; } + public byte FormArgumentElapsed { get => Data[0xEE]; set => Data[0xEE] = value; } + public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0xF0)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF0), (ushort)value); } + public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0xF2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF2), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0xF4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF4), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0xF6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF6), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0xF8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF8), (ushort)value); } + public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0xFA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFA), (ushort)value); } + public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0xFC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFC), (ushort)value); } + #endregion - public int SuperTrainingMedalCount(int maxCount = 30) - { - uint value = SuperTrainBitFlags >> 2; + public int SuperTrainingMedalCount(int maxCount = 30) + { + uint value = SuperTrainBitFlags >> 2; #if NET5 return System.Numerics.BitOperations.PopCount(value); #else - int TrainCount = 0; - for (int i = 0; i < maxCount; i++) - { - if ((value & 1) != 0) - TrainCount++; - value >>= 1; - } + int TrainCount = 0; + for (int i = 0; i < maxCount; i++) + { + if ((value & 1) != 0) + TrainCount++; + value >>= 1; + } - return TrainCount; + return TrainCount; #endif - } + } - public bool IsUntradedEvent6 => Geo1_Country == 0 && Geo1_Region == 0 && Met_Location / 10000 == 4 && Gen6; + public bool IsUntradedEvent6 => Geo1_Country == 0 && Geo1_Region == 0 && Met_Location / 10000 == 4 && Gen6; - // Complex Generated Attributes + // Complex Generated Attributes - public void FixMemories() + public void FixMemories() + { + if (IsEgg) // No memories if is egg. { - if (IsEgg) // No memories if is egg. - { - Geo1_Country = Geo2_Country = Geo3_Country = Geo4_Country = Geo5_Country = + Geo1_Country = Geo2_Country = Geo3_Country = Geo4_Country = Geo5_Country = Geo1_Region = Geo2_Region = Geo3_Region = Geo4_Region = Geo5_Region = 0; - HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = 0; - /* OT_Friendship */ OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = OT_Affection = HT_Affection = 0; + HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = 0; + /* OT_Friendship */ OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = OT_Affection = HT_Affection = 0; - // Clear Handler - HT_Trash.Clear(); - return; - } - - if (IsUntraded) - HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = HT_Affection = 0; - if (!Gen6) - OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = 0; // Don't clear OT affection; OR/AS Contest Glitch - - this.SanitizeGeoLocationData(); + // Clear Handler + HT_Trash.Clear(); + return; } - protected override bool TradeOT(ITrainerInfo tr) - { - // Check to see if the OT matches the SAV's OT info. - if (!(tr.TID == TID && tr.SID == SID && tr.Gender == OT_Gender && tr.OT == OT_Name)) - return false; + if (IsUntraded) + HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = HT_Affection = 0; + if (!Gen6) + OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = 0; // Don't clear OT affection; OR/AS Contest Glitch - CurrentHandler = 0; + this.SanitizeGeoLocationData(); + } + + protected override bool TradeOT(ITrainerInfo tr) + { + // Check to see if the OT matches the SAV's OT info. + if (!(tr.TID == TID && tr.SID == SID && tr.Gender == OT_Gender && tr.OT == OT_Name)) + return false; + + CurrentHandler = 0; + if (tr is IRegionOrigin o) + { + if (!IsUntraded && (o.Country != Geo1_Country || o.Region != Geo1_Region)) + this.TradeGeoLocation(o.Country, o.Region); + } + + return true; + } + + protected override void TradeHT(ITrainerInfo tr) + { + if (tr.OT != HT_Name || tr.Gender != HT_Gender || (Geo1_Country == 0 && Geo1_Region == 0 && !IsUntradedEvent6)) + { if (tr is IRegionOrigin o) - { - if (!IsUntraded && (o.Country != Geo1_Country || o.Region != Geo1_Region)) - this.TradeGeoLocation(o.Country, o.Region); - } - - return true; + this.TradeGeoLocation(o.Country, o.Region); } - protected override void TradeHT(ITrainerInfo tr) + if (tr.OT != HT_Name) { - if (tr.OT != HT_Name || tr.Gender != HT_Gender || (Geo1_Country == 0 && Geo1_Region == 0 && !IsUntradedEvent6)) - { - if (tr is IRegionOrigin o) - this.TradeGeoLocation(o.Country, o.Region); - } - - if (tr.OT != HT_Name) - { - HT_Friendship = PersonalInfo.BaseFriendship; - HT_Affection = 0; - HT_Name = tr.OT; - } - CurrentHandler = 1; - HT_Gender = tr.Gender; - - // Make a memory if no memory already exists. Pretty terrible way of doing this but I'd rather not overwrite existing memories. - if (HT_Memory == 0) - this.SetTradeMemoryHT6(false); + HT_Friendship = PersonalInfo.BaseFriendship; + HT_Affection = 0; + HT_Name = tr.OT; } + CurrentHandler = 1; + HT_Gender = tr.Gender; - // Maximums - public override int MaxMoveID => Legal.MaxMoveID_6_AO; - public override int MaxSpeciesID => Legal.MaxSpeciesID_6; - public override int MaxAbilityID => Legal.MaxAbilityID_6_AO; - public override int MaxItemID => Legal.MaxItemID_6_AO; - public override int MaxBallID => Legal.MaxBallID_6; - public override int MaxGameID => Legal.MaxGameID_6; // OR - public override int MarkingCount => 6; + // Make a memory if no memory already exists. Pretty terrible way of doing this but I'd rather not overwrite existing memories. + if (HT_Memory == 0) + this.SetTradeMemoryHT6(false); + } - public override int GetMarking(int index) + // Maximums + public override int MaxMoveID => Legal.MaxMoveID_6_AO; + public override int MaxSpeciesID => Legal.MaxSpeciesID_6; + public override int MaxAbilityID => Legal.MaxAbilityID_6_AO; + public override int MaxItemID => Legal.MaxItemID_6_AO; + public override int MaxBallID => Legal.MaxBallID_6; + public override int MaxGameID => Legal.MaxGameID_6; // OR + public override int MarkingCount => 6; + + public override int GetMarking(int index) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return (MarkValue >> index) & 1; + } + + public override void SetMarking(int index, int value) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + MarkValue = (MarkValue & ~(1 << index)) | ((value & 1) << index); + } + + public PK7 ConvertToPK7() + { + PK7 pk7 = new((byte[])Data.Clone()) { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - return (MarkValue >> index) & 1; - } + ResortEventStatus = 0, // Clears old Marking Value + MarkValue = 0, // Clears old Super Training Bag & Hits Remaining + FormArgument = 0, // Clears old style Form Argument + DirtType = 0, // Clears old Form Argument byte + DirtLocation = 0, // Clears old Form Argument byte + }; - public override void SetMarking(int index, int value) + // Remap boolean markings to the dual-bit format -- set 1 if marked. + for (int i = 0; i < 6; i++) + pk7.SetMarking(i, GetMarking(i)); + + var an = AbilityNumber; + switch (an) { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - MarkValue = (MarkValue & ~(1 << index)) | ((value & 1) << index); + case 1 or 2 or 4: // Valid Ability Numbers + int index = an >> 1; + if (PersonalInfo.Abilities[index] == Ability) // correct pair + pk7.Ability = pk7.PersonalInfo.Abilities[index]; + break; } - public PK7 ConvertToPK7() - { - PK7 pk7 = new((byte[])Data.Clone()) - { - ResortEventStatus = 0, // Clears old Marking Value - MarkValue = 0, // Clears old Super Training Bag & Hits Remaining - FormArgument = 0, // Clears old style Form Argument - DirtType = 0, // Clears old Form Argument byte - DirtLocation = 0, // Clears old Form Argument byte - }; + pk7.SetTradeMemoryHT6(true); // oh no, memories on gen7 pk + RecentTrainerCache.SetFirstCountryRegion(pk7); - // Remap boolean markings to the dual-bit format -- set 1 if marked. - for (int i = 0; i < 6; i++) - pk7.SetMarking(i, GetMarking(i)); + // Bank-accurate data zeroing + var span = pk7.Data.AsSpan(); + span[0x94..0x9E].Clear(); /* Geolocations. */ + span[0xAA..0xB0].Clear(); /* Unused/Amie Fullness & Enjoyment. */ + span[0xE4..0xE8].Clear(); /* Unused. */ + pk7.Data[0x72] &= 0xFC; /* Clear lower two bits of Super training flags. */ + pk7.Data[0xDE] = 0; /* Gen IV encounter type. */ - var an = AbilityNumber; - switch (an) - { - case 1 or 2 or 4: // Valid Ability Numbers - int index = an >> 1; - if (PersonalInfo.Abilities[index] == Ability) // correct pair - pk7.Ability = pk7.PersonalInfo.Abilities[index]; - break; - } + // Copy Form Argument data for Furfrou and Hoopa, since we're nice. + pk7.FormArgumentRemain = FormArgumentRemain; + pk7.FormArgumentElapsed = FormArgumentElapsed; + pk7.FormArgumentMaximum = FormArgumentMaximum; - pk7.SetTradeMemoryHT6(true); // oh no, memories on gen7 pkm - RecentTrainerCache.SetFirstCountryRegion(pk7); + pk7.HealPP(); + // Fix Checksum + pk7.RefreshChecksum(); - // Bank-accurate data zeroing - var span = pk7.Data.AsSpan(); - span[0x94..0x9E].Clear(); /* Geolocations. */ - span[0xAA..0xB0].Clear(); /* Unused/Amie Fullness & Enjoyment. */ - span[0xE4..0xE8].Clear(); /* Unused. */ - pk7.Data[0x72] &= 0xFC; /* Clear lower two bits of Super training flags. */ - pk7.Data[0xDE] = 0; /* Gen IV encounter type. */ - - // Copy Form Argument data for Furfrou and Hoopa, since we're nice. - pk7.FormArgumentRemain = FormArgumentRemain; - pk7.FormArgumentElapsed = FormArgumentElapsed; - pk7.FormArgumentMaximum = FormArgumentMaximum; - - pk7.HealPP(); - // Fix Checksum - pk7.RefreshChecksum(); - - return pk7; // Done! - } + return pk7; // Done! } } diff --git a/PKHeX.Core/PKM/PK7.cs b/PKHeX.Core/PKM/PK7.cs index e83091c5a..81561e2c1 100644 --- a/PKHeX.Core/PKM/PK7.cs +++ b/PKHeX.Core/PKM/PK7.cs @@ -1,688 +1,687 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 7 format. +public sealed class PK7 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, + IContestStats, IContestStatsMutable, IHyperTrain, IGeoTrack, ISuperTrain, IFormArgument, ITrainerMemories, IAffection { - /// Generation 7 format. - public sealed class PK7 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, - IContestStats, IContestStatsMutable, IHyperTrain, IGeoTrack, ISuperTrain, IFormArgument, ITrainerMemories, IAffection + private static readonly ushort[] Unused = { - private static readonly ushort[] Unused = + 0x2A, // Old Marking Value (PelagoEventStatus) + // 0x36, 0x37, // Unused Ribbons + 0x58, 0x59, 0x73, 0x90, 0x91, 0x9E, 0x9F, 0xA0, 0xA1, 0xA7, 0xAA, 0xAB, 0xAC, 0xAD, 0xC8, 0xC9, 0xD7, 0xE4, 0xE5, 0xE6, 0xE7, + }; + + public override IReadOnlyList ExtraBytes => Unused; + public override EntityContext Context => EntityContext.Gen7; + public override PersonalInfo PersonalInfo => PersonalTable.USUM.GetFormEntry(Species, Form); + + public PK7() : base(PokeCrypto.SIZE_6PARTY) { } + public PK7(byte[] data) : base(DecryptParty(data)) { } + + private static byte[] DecryptParty(byte[] data) + { + PokeCrypto.DecryptIfEncrypted67(ref data); + Array.Resize(ref data, PokeCrypto.SIZE_6PARTY); + return data; + } + + public override PKM Clone() => new PK7((byte[])Data.Clone()); + + // Structure + #region Block A + public override uint EncryptionConstant + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); + } + + public override ushort Sanity + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); + } + + public override ushort Checksum + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); + } + + public override int Species + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); + } + + public override int HeldItem + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); + } + + public override int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); + } + + public override int SID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); + } + + public override uint EXP + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); + } + + public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public override int AbilityNumber { get => Data[0x15] & 7; set => Data[0x15] = (byte)((Data[0x15] & ~7) | (value & 7)); } + public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); } + + public override uint PID + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x18)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x18), value); + } + + public override int Nature { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public override bool FatefulEncounter { get => (Data[0x1D] & 1) == 1; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x01) | (value ? 1 : 0)); } + public override int Gender { get => (Data[0x1D] >> 1) & 0x3; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x06) | (value << 1)); } + public override int Form { get => Data[0x1D] >> 3; set => Data[0x1D] = (byte)((Data[0x1D] & 0x07) | (value << 3)); } + public override int EV_HP { get => Data[0x1E]; set => Data[0x1E] = (byte)value; } + public override int EV_ATK { get => Data[0x1F]; set => Data[0x1F] = (byte)value; } + public override int EV_DEF { get => Data[0x20]; set => Data[0x20] = (byte)value; } + public override int EV_SPE { get => Data[0x21]; set => Data[0x21] = (byte)value; } + public override int EV_SPA { get => Data[0x22]; set => Data[0x22] = (byte)value; } + public override int EV_SPD { get => Data[0x23]; set => Data[0x23] = (byte)value; } + public byte CNT_Cool { get => Data[0x24]; set => Data[0x24] = value; } + public byte CNT_Beauty { get => Data[0x25]; set => Data[0x25] = value; } + public byte CNT_Cute { get => Data[0x26]; set => Data[0x26] = value; } + public byte CNT_Smart { get => Data[0x27]; set => Data[0x27] = value; } + public byte CNT_Tough { get => Data[0x28]; set => Data[0x28] = value; } + public byte CNT_Sheen { get => Data[0x29]; set => Data[0x29] = value; } + public ResortEventState ResortEventStatus { get => (ResortEventState)Data[0x2A]; set => Data[0x2A] = (byte)value; } + private byte PKRS { get => Data[0x2B]; set => Data[0x2B] = value; } + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + private byte ST1 { get => Data[0x2C]; set => Data[0x2C] = value; } + public bool Unused0 { get => (ST1 & (1 << 0)) == 1 << 0; set => ST1 = (byte)((ST1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool Unused1 { get => (ST1 & (1 << 1)) == 1 << 1; set => ST1 = (byte)((ST1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool SuperTrain1_SPA { get => (ST1 & (1 << 2)) == 1 << 2; set => ST1 = (byte)((ST1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool SuperTrain1_HP { get => (ST1 & (1 << 3)) == 1 << 3; set => ST1 = (byte)((ST1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool SuperTrain1_ATK { get => (ST1 & (1 << 4)) == 1 << 4; set => ST1 = (byte)((ST1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool SuperTrain1_SPD { get => (ST1 & (1 << 5)) == 1 << 5; set => ST1 = (byte)((ST1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool SuperTrain1_SPE { get => (ST1 & (1 << 6)) == 1 << 6; set => ST1 = (byte)((ST1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool SuperTrain1_DEF { get => (ST1 & (1 << 7)) == 1 << 7; set => ST1 = (byte)((ST1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + private byte ST2 { get => Data[0x2D]; set => Data[0x2D] = value; } + public bool SuperTrain2_SPA { get => (ST2 & (1 << 0)) == 1 << 0; set => ST2 = (byte)((ST2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool SuperTrain2_HP { get => (ST2 & (1 << 1)) == 1 << 1; set => ST2 = (byte)((ST2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool SuperTrain2_ATK { get => (ST2 & (1 << 2)) == 1 << 2; set => ST2 = (byte)((ST2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool SuperTrain2_SPD { get => (ST2 & (1 << 3)) == 1 << 3; set => ST2 = (byte)((ST2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool SuperTrain2_SPE { get => (ST2 & (1 << 4)) == 1 << 4; set => ST2 = (byte)((ST2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool SuperTrain2_DEF { get => (ST2 & (1 << 5)) == 1 << 5; set => ST2 = (byte)((ST2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool SuperTrain3_SPA { get => (ST2 & (1 << 6)) == 1 << 6; set => ST2 = (byte)((ST2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool SuperTrain3_HP { get => (ST2 & (1 << 7)) == 1 << 7; set => ST2 = (byte)((ST2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + private byte ST3 { get => Data[0x2E]; set => Data[0x2E] = value; } + public bool SuperTrain3_ATK { get => (ST3 & (1 << 0)) == 1 << 0; set => ST3 = (byte)((ST3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool SuperTrain3_SPD { get => (ST3 & (1 << 1)) == 1 << 1; set => ST3 = (byte)((ST3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool SuperTrain3_SPE { get => (ST3 & (1 << 2)) == 1 << 2; set => ST3 = (byte)((ST3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool SuperTrain3_DEF { get => (ST3 & (1 << 3)) == 1 << 3; set => ST3 = (byte)((ST3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool SuperTrain4_1 { get => (ST3 & (1 << 4)) == 1 << 4; set => ST3 = (byte)((ST3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool SuperTrain5_1 { get => (ST3 & (1 << 5)) == 1 << 5; set => ST3 = (byte)((ST3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool SuperTrain5_2 { get => (ST3 & (1 << 6)) == 1 << 6; set => ST3 = (byte)((ST3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool SuperTrain5_3 { get => (ST3 & (1 << 7)) == 1 << 7; set => ST3 = (byte)((ST3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + private byte ST4 { get => Data[0x2F]; set => Data[0x2F] = value; } + public bool SuperTrain5_4 { get => (ST4 & (1 << 0)) == 1 << 0; set => ST4 = (byte)((ST4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool SuperTrain6_1 { get => (ST4 & (1 << 1)) == 1 << 1; set => ST4 = (byte)((ST4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool SuperTrain6_2 { get => (ST4 & (1 << 2)) == 1 << 2; set => ST4 = (byte)((ST4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool SuperTrain6_3 { get => (ST4 & (1 << 3)) == 1 << 3; set => ST4 = (byte)((ST4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool SuperTrain7_1 { get => (ST4 & (1 << 4)) == 1 << 4; set => ST4 = (byte)((ST4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool SuperTrain7_2 { get => (ST4 & (1 << 5)) == 1 << 5; set => ST4 = (byte)((ST4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool SuperTrain7_3 { get => (ST4 & (1 << 6)) == 1 << 6; set => ST4 = (byte)((ST4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool SuperTrain8_1 { get => (ST4 & (1 << 7)) == 1 << 7; set => ST4 = (byte)((ST4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public uint SuperTrainBitFlags { get => ReadUInt32LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x2C), value); } + private byte RIB0 { get => Data[0x30]; set => Data[0x30] = value; } // Ribbons are read as uints, but let's keep them per byte. + private byte RIB1 { get => Data[0x31]; set => Data[0x31] = value; } + private byte RIB2 { get => Data[0x32]; set => Data[0x32] = value; } + private byte RIB3 { get => Data[0x33]; set => Data[0x33] = value; } + private byte RIB4 { get => Data[0x34]; set => Data[0x34] = value; } + private byte RIB5 { get => Data[0x35]; set => Data[0x35] = value; } + private byte RIB6 { get => Data[0x36]; set => Data[0x36] = value; } + //private byte RIB7 { get => Data[0x37]; set => Data[0x37] = value; } // Unused + public bool RibbonChampionKalos { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonChampionG3 { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonChampionSinnoh { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonBestFriends { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonTraining { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonBattlerSkillful { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonBattlerExpert { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonEffort { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonAlert { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonShock { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonDowncast { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonCareless { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonRelax { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonSnooze { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonSmile { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonGorgeous { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonArtist { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonFootprint { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonRecord { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonLegend { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonCountry { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonNational { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonEarth { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonWorld { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonEvent { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonBirthday { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonSpecial { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonSouvenir { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonWishing { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonChampionBattle { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonChampionRegional { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonChampionNational { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonChampionWorld { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool HasContestMemoryRibbon { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool HasBattleMemoryRibbon { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonChampionG6Hoenn { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonContestStar { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonMasterCoolness { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RibbonMasterBeauty { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool RibbonMasterCuteness { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool RibbonMasterCleverness { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool RibbonMasterToughness { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool RibbonChampionAlola { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool RibbonBattleRoyale { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public bool RibbonBattleTreeGreat { get => (RIB6 & (1 << 0)) == 1 << 0; set => RIB6 = (byte)((RIB6 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool RibbonBattleTreeMaster { get => (RIB6 & (1 << 1)) == 1 << 1; set => RIB6 = (byte)((RIB6 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool RIB6_2 { get => (RIB6 & (1 << 2)) == 1 << 2; set => RIB6 = (byte)((RIB6 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } // Unused + public bool RIB6_3 { get => (RIB6 & (1 << 3)) == 1 << 3; set => RIB6 = (byte)((RIB6 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } // Unused + public bool RIB6_4 { get => (RIB6 & (1 << 4)) == 1 << 4; set => RIB6 = (byte)((RIB6 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public bool RIB6_5 { get => (RIB6 & (1 << 5)) == 1 << 5; set => RIB6 = (byte)((RIB6 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public bool RIB6_6 { get => (RIB6 & (1 << 6)) == 1 << 6; set => RIB6 = (byte)((RIB6 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public bool RIB6_7 { get => (RIB6 & (1 << 7)) == 1 << 7; set => RIB6 = (byte)((RIB6 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + public int RibbonCountMemoryContest { get => Data[0x38]; set => HasContestMemoryRibbon = (Data[0x38] = (byte)value) != 0; } + public int RibbonCountMemoryBattle { get => Data[0x39]; set => HasBattleMemoryRibbon = (Data[0x39] = (byte)value) != 0; } + private ushort DistByte { get => ReadUInt16LittleEndian(Data.AsSpan(0x3A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3A), value); } + public bool DistSuperTrain1 { get => (DistByte & (1 << 0)) == 1 << 0; set => DistByte = (byte)((DistByte & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public bool DistSuperTrain2 { get => (DistByte & (1 << 1)) == 1 << 1; set => DistByte = (byte)((DistByte & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public bool DistSuperTrain3 { get => (DistByte & (1 << 2)) == 1 << 2; set => DistByte = (byte)((DistByte & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public bool DistSuperTrain4 { get => (DistByte & (1 << 3)) == 1 << 3; set => DistByte = (byte)((DistByte & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public bool DistSuperTrain5 { get => (DistByte & (1 << 4)) == 1 << 4; set => DistByte = (byte)((DistByte & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public bool DistSuperTrain6 { get => (DistByte & (1 << 5)) == 1 << 5; set => DistByte = (byte)((DistByte & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public bool Dist7 { get => (DistByte & (1 << 6)) == 1 << 6; set => DistByte = (byte)((DistByte & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public bool Dist8 { get => (DistByte & (1 << 7)) == 1 << 7; set => DistByte = (byte)((DistByte & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x3C), value); } + public byte FormArgumentRemain { get => (byte)FormArgument; set => FormArgument = (FormArgument & ~0xFFu) | value; } + public byte FormArgumentElapsed { get => (byte)(FormArgument >> 8); set => FormArgument = (FormArgument & ~0xFF00u) | (uint)(value << 8); } + public byte FormArgumentMaximum { get => (byte)(FormArgument >> 16); set => FormArgument = (FormArgument & ~0xFF0000u) | (uint)(value << 16); } + #endregion + #region Block B + public override string Nickname + { + get => StringConverter7.GetString(Nickname_Trash); + set { - 0x2A, // Old Marking Value (PelagoEventStatus) - // 0x36, 0x37, // Unused Ribbons - 0x58, 0x59, 0x73, 0x90, 0x91, 0x9E, 0x9F, 0xA0, 0xA1, 0xA7, 0xAA, 0xAB, 0xAC, 0xAD, 0xC8, 0xC9, 0xD7, 0xE4, 0xE5, 0xE6, 0xE7, - }; - - public override IReadOnlyList ExtraBytes => Unused; - public override EntityContext Context => EntityContext.Gen7; - public override PersonalInfo PersonalInfo => PersonalTable.USUM.GetFormEntry(Species, Form); - - public PK7() : base(PokeCrypto.SIZE_6PARTY) { } - public PK7(byte[] data) : base(DecryptParty(data)) { } - - private static byte[] DecryptParty(byte[] data) - { - PokeCrypto.DecryptIfEncrypted67(ref data); - Array.Resize(ref data, PokeCrypto.SIZE_6PARTY); - return data; - } - - public override PKM Clone() => new PK7((byte[])Data.Clone()); - - // Structure - #region Block A - public override uint EncryptionConstant - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); - } - - public override ushort Sanity - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); - } - - public override ushort Checksum - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); - } - - public override int Species - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); - } - - public override int HeldItem - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); - } - - public override int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); - } - - public override int SID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); - } - - public override uint EXP - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); - } - - public override int Ability { get => Data[0x14]; set => Data[0x14] = (byte)value; } - public override int AbilityNumber { get => Data[0x15] & 7; set => Data[0x15] = (byte)((Data[0x15] & ~7) | (value & 7)); } - public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(0x16), (ushort)value); } - - public override uint PID - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x18)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x18), value); - } - - public override int Nature { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } - public override bool FatefulEncounter { get => (Data[0x1D] & 1) == 1; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x01) | (value ? 1 : 0)); } - public override int Gender { get => (Data[0x1D] >> 1) & 0x3; set => Data[0x1D] = (byte)((Data[0x1D] & ~0x06) | (value << 1)); } - public override int Form { get => Data[0x1D] >> 3; set => Data[0x1D] = (byte)((Data[0x1D] & 0x07) | (value << 3)); } - public override int EV_HP { get => Data[0x1E]; set => Data[0x1E] = (byte)value; } - public override int EV_ATK { get => Data[0x1F]; set => Data[0x1F] = (byte)value; } - public override int EV_DEF { get => Data[0x20]; set => Data[0x20] = (byte)value; } - public override int EV_SPE { get => Data[0x21]; set => Data[0x21] = (byte)value; } - public override int EV_SPA { get => Data[0x22]; set => Data[0x22] = (byte)value; } - public override int EV_SPD { get => Data[0x23]; set => Data[0x23] = (byte)value; } - public byte CNT_Cool { get => Data[0x24]; set => Data[0x24] = value; } - public byte CNT_Beauty { get => Data[0x25]; set => Data[0x25] = value; } - public byte CNT_Cute { get => Data[0x26]; set => Data[0x26] = value; } - public byte CNT_Smart { get => Data[0x27]; set => Data[0x27] = value; } - public byte CNT_Tough { get => Data[0x28]; set => Data[0x28] = value; } - public byte CNT_Sheen { get => Data[0x29]; set => Data[0x29] = value; } - public ResortEventState ResortEventStatus { get => (ResortEventState)Data[0x2A]; set => Data[0x2A] = (byte)value; } - private byte PKRS { get => Data[0x2B]; set => Data[0x2B] = value; } - public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); } - private byte ST1 { get => Data[0x2C]; set => Data[0x2C] = value; } - public bool Unused0 { get => (ST1 & (1 << 0)) == 1 << 0; set => ST1 = (byte)((ST1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool Unused1 { get => (ST1 & (1 << 1)) == 1 << 1; set => ST1 = (byte)((ST1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool SuperTrain1_SPA { get => (ST1 & (1 << 2)) == 1 << 2; set => ST1 = (byte)((ST1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool SuperTrain1_HP { get => (ST1 & (1 << 3)) == 1 << 3; set => ST1 = (byte)((ST1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool SuperTrain1_ATK { get => (ST1 & (1 << 4)) == 1 << 4; set => ST1 = (byte)((ST1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool SuperTrain1_SPD { get => (ST1 & (1 << 5)) == 1 << 5; set => ST1 = (byte)((ST1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool SuperTrain1_SPE { get => (ST1 & (1 << 6)) == 1 << 6; set => ST1 = (byte)((ST1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool SuperTrain1_DEF { get => (ST1 & (1 << 7)) == 1 << 7; set => ST1 = (byte)((ST1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - private byte ST2 { get => Data[0x2D]; set => Data[0x2D] = value; } - public bool SuperTrain2_SPA { get => (ST2 & (1 << 0)) == 1 << 0; set => ST2 = (byte)((ST2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool SuperTrain2_HP { get => (ST2 & (1 << 1)) == 1 << 1; set => ST2 = (byte)((ST2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool SuperTrain2_ATK { get => (ST2 & (1 << 2)) == 1 << 2; set => ST2 = (byte)((ST2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool SuperTrain2_SPD { get => (ST2 & (1 << 3)) == 1 << 3; set => ST2 = (byte)((ST2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool SuperTrain2_SPE { get => (ST2 & (1 << 4)) == 1 << 4; set => ST2 = (byte)((ST2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool SuperTrain2_DEF { get => (ST2 & (1 << 5)) == 1 << 5; set => ST2 = (byte)((ST2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool SuperTrain3_SPA { get => (ST2 & (1 << 6)) == 1 << 6; set => ST2 = (byte)((ST2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool SuperTrain3_HP { get => (ST2 & (1 << 7)) == 1 << 7; set => ST2 = (byte)((ST2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - private byte ST3 { get => Data[0x2E]; set => Data[0x2E] = value; } - public bool SuperTrain3_ATK { get => (ST3 & (1 << 0)) == 1 << 0; set => ST3 = (byte)((ST3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool SuperTrain3_SPD { get => (ST3 & (1 << 1)) == 1 << 1; set => ST3 = (byte)((ST3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool SuperTrain3_SPE { get => (ST3 & (1 << 2)) == 1 << 2; set => ST3 = (byte)((ST3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool SuperTrain3_DEF { get => (ST3 & (1 << 3)) == 1 << 3; set => ST3 = (byte)((ST3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool SuperTrain4_1 { get => (ST3 & (1 << 4)) == 1 << 4; set => ST3 = (byte)((ST3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool SuperTrain5_1 { get => (ST3 & (1 << 5)) == 1 << 5; set => ST3 = (byte)((ST3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool SuperTrain5_2 { get => (ST3 & (1 << 6)) == 1 << 6; set => ST3 = (byte)((ST3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool SuperTrain5_3 { get => (ST3 & (1 << 7)) == 1 << 7; set => ST3 = (byte)((ST3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - private byte ST4 { get => Data[0x2F]; set => Data[0x2F] = value; } - public bool SuperTrain5_4 { get => (ST4 & (1 << 0)) == 1 << 0; set => ST4 = (byte)((ST4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool SuperTrain6_1 { get => (ST4 & (1 << 1)) == 1 << 1; set => ST4 = (byte)((ST4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool SuperTrain6_2 { get => (ST4 & (1 << 2)) == 1 << 2; set => ST4 = (byte)((ST4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool SuperTrain6_3 { get => (ST4 & (1 << 3)) == 1 << 3; set => ST4 = (byte)((ST4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool SuperTrain7_1 { get => (ST4 & (1 << 4)) == 1 << 4; set => ST4 = (byte)((ST4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool SuperTrain7_2 { get => (ST4 & (1 << 5)) == 1 << 5; set => ST4 = (byte)((ST4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool SuperTrain7_3 { get => (ST4 & (1 << 6)) == 1 << 6; set => ST4 = (byte)((ST4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool SuperTrain8_1 { get => (ST4 & (1 << 7)) == 1 << 7; set => ST4 = (byte)((ST4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public uint SuperTrainBitFlags { get => ReadUInt32LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x2C), value); } - private byte RIB0 { get => Data[0x30]; set => Data[0x30] = value; } // Ribbons are read as uints, but let's keep them per byte. - private byte RIB1 { get => Data[0x31]; set => Data[0x31] = value; } - private byte RIB2 { get => Data[0x32]; set => Data[0x32] = value; } - private byte RIB3 { get => Data[0x33]; set => Data[0x33] = value; } - private byte RIB4 { get => Data[0x34]; set => Data[0x34] = value; } - private byte RIB5 { get => Data[0x35]; set => Data[0x35] = value; } - private byte RIB6 { get => Data[0x36]; set => Data[0x36] = value; } - //private byte RIB7 { get => Data[0x37]; set => Data[0x37] = value; } // Unused - public bool RibbonChampionKalos { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonChampionG3 { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonChampionSinnoh { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonBestFriends { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonTraining { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonBattlerSkillful { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonBattlerExpert { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonEffort { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonAlert { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonShock { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonDowncast { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonCareless { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonRelax { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonSnooze { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonSmile { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonGorgeous { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonArtist { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonFootprint { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonRecord { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonLegend { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonCountry { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonNational { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonEarth { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonWorld { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonEvent { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonBirthday { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonSpecial { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonSouvenir { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonWishing { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonChampionBattle { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonChampionRegional { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonChampionNational { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonChampionWorld { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool HasContestMemoryRibbon { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool HasBattleMemoryRibbon { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonChampionG6Hoenn { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonContestStar { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonMasterCoolness { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RibbonMasterBeauty { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool RibbonMasterCuteness { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool RibbonMasterCleverness { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool RibbonMasterToughness { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool RibbonChampionAlola { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool RibbonBattleRoyale { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public bool RibbonBattleTreeGreat { get => (RIB6 & (1 << 0)) == 1 << 0; set => RIB6 = (byte)((RIB6 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool RibbonBattleTreeMaster { get => (RIB6 & (1 << 1)) == 1 << 1; set => RIB6 = (byte)((RIB6 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool RIB6_2 { get => (RIB6 & (1 << 2)) == 1 << 2; set => RIB6 = (byte)((RIB6 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } // Unused - public bool RIB6_3 { get => (RIB6 & (1 << 3)) == 1 << 3; set => RIB6 = (byte)((RIB6 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } // Unused - public bool RIB6_4 { get => (RIB6 & (1 << 4)) == 1 << 4; set => RIB6 = (byte)((RIB6 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused - public bool RIB6_5 { get => (RIB6 & (1 << 5)) == 1 << 5; set => RIB6 = (byte)((RIB6 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused - public bool RIB6_6 { get => (RIB6 & (1 << 6)) == 1 << 6; set => RIB6 = (byte)((RIB6 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused - public bool RIB6_7 { get => (RIB6 & (1 << 7)) == 1 << 7; set => RIB6 = (byte)((RIB6 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused - public int RibbonCountMemoryContest { get => Data[0x38]; set => HasContestMemoryRibbon = (Data[0x38] = (byte)value) != 0; } - public int RibbonCountMemoryBattle { get => Data[0x39]; set => HasBattleMemoryRibbon = (Data[0x39] = (byte)value) != 0; } - private ushort DistByte { get => ReadUInt16LittleEndian(Data.AsSpan(0x3A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3A), value); } - public bool DistSuperTrain1 { get => (DistByte & (1 << 0)) == 1 << 0; set => DistByte = (byte)((DistByte & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - public bool DistSuperTrain2 { get => (DistByte & (1 << 1)) == 1 << 1; set => DistByte = (byte)((DistByte & ~(1 << 1)) | (value ? 1 << 1 : 0)); } - public bool DistSuperTrain3 { get => (DistByte & (1 << 2)) == 1 << 2; set => DistByte = (byte)((DistByte & ~(1 << 2)) | (value ? 1 << 2 : 0)); } - public bool DistSuperTrain4 { get => (DistByte & (1 << 3)) == 1 << 3; set => DistByte = (byte)((DistByte & ~(1 << 3)) | (value ? 1 << 3 : 0)); } - public bool DistSuperTrain5 { get => (DistByte & (1 << 4)) == 1 << 4; set => DistByte = (byte)((DistByte & ~(1 << 4)) | (value ? 1 << 4 : 0)); } - public bool DistSuperTrain6 { get => (DistByte & (1 << 5)) == 1 << 5; set => DistByte = (byte)((DistByte & ~(1 << 5)) | (value ? 1 << 5 : 0)); } - public bool Dist7 { get => (DistByte & (1 << 6)) == 1 << 6; set => DistByte = (byte)((DistByte & ~(1 << 6)) | (value ? 1 << 6 : 0)); } - public bool Dist8 { get => (DistByte & (1 << 7)) == 1 << 7; set => DistByte = (byte)((DistByte & ~(1 << 7)) | (value ? 1 << 7 : 0)); } - public uint FormArgument { get => ReadUInt32LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x3C), value); } - public byte FormArgumentRemain { get => (byte)FormArgument; set => FormArgument = (FormArgument & ~0xFFu) | value; } - public byte FormArgumentElapsed { get => (byte)(FormArgument >> 8); set => FormArgument = (FormArgument & ~0xFF00u) | (uint)(value << 8); } - public byte FormArgumentMaximum { get => (byte)(FormArgument >> 16); set => FormArgument = (FormArgument & ~0xFF0000u) | (uint)(value << 16); } - #endregion - #region Block B - public override string Nickname - { - get => StringConverter7.GetString(Nickname_Trash); - set + if (!IsNicknamed) { - if (!IsNicknamed) + int lang = SpeciesName.GetSpeciesNameLanguage(Species, Language, value, 7); + if (lang is (int)LanguageID.ChineseS or (int)LanguageID.ChineseT) { - int lang = SpeciesName.GetSpeciesNameLanguage(Species, Language, value, 7); - if (lang is (int)LanguageID.ChineseS or (int)LanguageID.ChineseT) - { - StringConverter7.SetString(Nickname_Trash, value.AsSpan(), 12, lang, StringConverterOption.None, chinese: true); - return; - } + StringConverter7.SetString(Nickname_Trash, value.AsSpan(), 12, lang, StringConverterOption.None, chinese: true); + return; } - StringConverter7.SetString(Nickname_Trash, value.AsSpan(), 12, 0, StringConverterOption.None); } + StringConverter7.SetString(Nickname_Trash, value.AsSpan(), 12, 0, StringConverterOption.None); } + } - public override int Move1 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), (ushort)value); - } + public override int Move1 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), (ushort)value); + } - public override int Move2 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); - } + public override int Move2 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); + } - public override int Move3 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); - } + public override int Move3 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); + } - public override int Move4 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x60)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x60), (ushort)value); - } + public override int Move4 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x60)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x60), (ushort)value); + } - public override int Move1_PP { get => Data[0x62]; set => Data[0x62] = (byte)value; } - public override int Move2_PP { get => Data[0x63]; set => Data[0x63] = (byte)value; } - public override int Move3_PP { get => Data[0x64]; set => Data[0x64] = (byte)value; } - public override int Move4_PP { get => Data[0x65]; set => Data[0x65] = (byte)value; } - public override int Move1_PPUps { get => Data[0x66]; set => Data[0x66] = (byte)value; } - public override int Move2_PPUps { get => Data[0x67]; set => Data[0x67] = (byte)value; } - public override int Move3_PPUps { get => Data[0x68]; set => Data[0x68] = (byte)value; } - public override int Move4_PPUps { get => Data[0x69]; set => Data[0x69] = (byte)value; } + public override int Move1_PP { get => Data[0x62]; set => Data[0x62] = (byte)value; } + public override int Move2_PP { get => Data[0x63]; set => Data[0x63] = (byte)value; } + public override int Move3_PP { get => Data[0x64]; set => Data[0x64] = (byte)value; } + public override int Move4_PP { get => Data[0x65]; set => Data[0x65] = (byte)value; } + public override int Move1_PPUps { get => Data[0x66]; set => Data[0x66] = (byte)value; } + public override int Move2_PPUps { get => Data[0x67]; set => Data[0x67] = (byte)value; } + public override int Move3_PPUps { get => Data[0x68]; set => Data[0x68] = (byte)value; } + public override int Move4_PPUps { get => Data[0x69]; set => Data[0x69] = (byte)value; } - public override int RelearnMove1 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); - } + public override int RelearnMove1 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); + } - public override int RelearnMove2 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6C), (ushort)value); - } + public override int RelearnMove2 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6C), (ushort)value); + } - public override int RelearnMove3 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x6E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x6E), (ushort)value); - } + public override int RelearnMove3 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x6E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x6E), (ushort)value); + } - public override int RelearnMove4 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x70)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x70), (ushort)value); - } + public override int RelearnMove4 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x70)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x70), (ushort)value); + } - public bool SecretSuperTrainingUnlocked { get => (Data[0x72] & 1) == 1; set => Data[0x72] = (byte)((Data[0x72] & ~1) | (value ? 1 : 0)); } - public bool SecretSuperTrainingComplete { get => (Data[0x72] & 2) == 2; set => Data[0x72] = (byte)((Data[0x72] & ~2) | (value ? 2 : 0)); } - // 0x73 Unused - private uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x74)); set => WriteUInt32LittleEndian(Data.AsSpan(0x74), value); } - public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } - public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } - public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } - public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } - public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } - public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } - public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } - public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } - #endregion - #region Block C - public override string HT_Name - { - get => StringConverter7.GetString(HT_Trash); - set => StringConverter7.SetString(HT_Trash, value.AsSpan(), 12, 0, StringConverterOption.None); - } + public bool SecretSuperTrainingUnlocked { get => (Data[0x72] & 1) == 1; set => Data[0x72] = (byte)((Data[0x72] & ~1) | (value ? 1 : 0)); } + public bool SecretSuperTrainingComplete { get => (Data[0x72] & 2) == 2; set => Data[0x72] = (byte)((Data[0x72] & ~2) | (value ? 2 : 0)); } + // 0x73 Unused + private uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x74)); set => WriteUInt32LittleEndian(Data.AsSpan(0x74), value); } + public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } + public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } + public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } + public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } + public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } + public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } + public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } + public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } + #endregion + #region Block C + public override string HT_Name + { + get => StringConverter7.GetString(HT_Trash); + set => StringConverter7.SetString(HT_Trash, value.AsSpan(), 12, 0, StringConverterOption.None); + } - public override int HT_Gender { get => Data[0x92]; set => Data[0x92] = (byte)value; } - public override int CurrentHandler { get => Data[0x93]; set => Data[0x93] = (byte)value; } - public byte Geo1_Region { get => Data[0x94]; set => Data[0x94] = value; } - public byte Geo1_Country { get => Data[0x95]; set => Data[0x95] = value; } - public byte Geo2_Region { get => Data[0x96]; set => Data[0x96] = value; } - public byte Geo2_Country { get => Data[0x97]; set => Data[0x97] = value; } - public byte Geo3_Region { get => Data[0x98]; set => Data[0x98] = value; } - public byte Geo3_Country { get => Data[0x99]; set => Data[0x99] = value; } - public byte Geo4_Region { get => Data[0x9A]; set => Data[0x9A] = value; } - public byte Geo4_Country { get => Data[0x9B]; set => Data[0x9B] = value; } - public byte Geo5_Region { get => Data[0x9C]; set => Data[0x9C] = value; } - public byte Geo5_Country { get => Data[0x9D]; set => Data[0x9D] = value; } - // 0x9E Unused - // 0x9F Unused - // 0xA0 Unused - // 0xA1 Unused - public override int HT_Friendship { get => Data[0xA2]; set => Data[0xA2] = (byte)value; } - public byte HT_Affection { get => Data[0xA3]; set => Data[0xA3] = value; } - public byte HT_Intensity { get => Data[0xA4]; set => Data[0xA4] = value; } - public byte HT_Memory { get => Data[0xA5]; set => Data[0xA5] = value; } - public byte HT_Feeling { get => Data[0xA6]; set => Data[0xA6] = value; } - // 0xA7 Unused - public ushort HT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xA8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA8), value); } - // 0xAA Unused - // 0xAB Unused - // 0xAC Unused - // 0xAD Unused - public override byte Fullness { get => Data[0xAE]; set => Data[0xAE] = value; } - public override byte Enjoyment { get => Data[0xAF]; set => Data[0xAF] = value; } - #endregion - #region Block D - public override string OT_Name - { - get => StringConverter7.GetString(OT_Trash); - set => StringConverter7.SetString(OT_Trash, value.AsSpan(), 12, 0, StringConverterOption.None); - } + public override int HT_Gender { get => Data[0x92]; set => Data[0x92] = (byte)value; } + public override int CurrentHandler { get => Data[0x93]; set => Data[0x93] = (byte)value; } + public byte Geo1_Region { get => Data[0x94]; set => Data[0x94] = value; } + public byte Geo1_Country { get => Data[0x95]; set => Data[0x95] = value; } + public byte Geo2_Region { get => Data[0x96]; set => Data[0x96] = value; } + public byte Geo2_Country { get => Data[0x97]; set => Data[0x97] = value; } + public byte Geo3_Region { get => Data[0x98]; set => Data[0x98] = value; } + public byte Geo3_Country { get => Data[0x99]; set => Data[0x99] = value; } + public byte Geo4_Region { get => Data[0x9A]; set => Data[0x9A] = value; } + public byte Geo4_Country { get => Data[0x9B]; set => Data[0x9B] = value; } + public byte Geo5_Region { get => Data[0x9C]; set => Data[0x9C] = value; } + public byte Geo5_Country { get => Data[0x9D]; set => Data[0x9D] = value; } + // 0x9E Unused + // 0x9F Unused + // 0xA0 Unused + // 0xA1 Unused + public override int HT_Friendship { get => Data[0xA2]; set => Data[0xA2] = (byte)value; } + public byte HT_Affection { get => Data[0xA3]; set => Data[0xA3] = value; } + public byte HT_Intensity { get => Data[0xA4]; set => Data[0xA4] = value; } + public byte HT_Memory { get => Data[0xA5]; set => Data[0xA5] = value; } + public byte HT_Feeling { get => Data[0xA6]; set => Data[0xA6] = value; } + // 0xA7 Unused + public ushort HT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xA8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA8), value); } + // 0xAA Unused + // 0xAB Unused + // 0xAC Unused + // 0xAD Unused + public override byte Fullness { get => Data[0xAE]; set => Data[0xAE] = value; } + public override byte Enjoyment { get => Data[0xAF]; set => Data[0xAF] = value; } + #endregion + #region Block D + public override string OT_Name + { + get => StringConverter7.GetString(OT_Trash); + set => StringConverter7.SetString(OT_Trash, value.AsSpan(), 12, 0, StringConverterOption.None); + } - public override int OT_Friendship { get => Data[0xCA]; set => Data[0xCA] = (byte)value; } - public byte OT_Affection { get => Data[0xCB]; set => Data[0xCB] = value; } - public byte OT_Intensity { get => Data[0xCC]; set => Data[0xCC] = value; } - public byte OT_Memory { get => Data[0xCD]; set => Data[0xCD] = value; } - public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xCE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xCE), value); } - public byte OT_Feeling { get => Data[0xD0]; set => Data[0xD0] = value; } - public override int Egg_Year { get => Data[0xD1]; set => Data[0xD1] = (byte)value; } - public override int Egg_Month { get => Data[0xD2]; set => Data[0xD2] = (byte)value; } - public override int Egg_Day { get => Data[0xD3]; set => Data[0xD3] = (byte)value; } - public override int Met_Year { get => Data[0xD4]; set => Data[0xD4] = (byte)value; } - public override int Met_Month { get => Data[0xD5]; set => Data[0xD5] = (byte)value; } - public override int Met_Day { get => Data[0xD6]; set => Data[0xD6] = (byte)value; } - // Unused 0xD7 - public override int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), (ushort)value); } - public override int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), (ushort)value); } - public override int Ball { get => Data[0xDC]; set => Data[0xDC] = (byte)value; } - public override int Met_Level { get => Data[0xDD] & ~0x80; set => Data[0xDD] = (byte)((Data[0xDD] & 0x80) | value); } - public override int OT_Gender { get => Data[0xDD] >> 7; set => Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); } - public byte HyperTrainFlags { get => Data[0xDE]; set => Data[0xDE] = value; } - public bool HT_HP { get => ((HyperTrainFlags >> 0) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 0)) | ((value ? 1 : 0) << 0)); } - public bool HT_ATK { get => ((HyperTrainFlags >> 1) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 1)) | ((value ? 1 : 0) << 1)); } - public bool HT_DEF { get => ((HyperTrainFlags >> 2) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 2)) | ((value ? 1 : 0) << 2)); } - public bool HT_SPA { get => ((HyperTrainFlags >> 3) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 3)) | ((value ? 1 : 0) << 3)); } - public bool HT_SPD { get => ((HyperTrainFlags >> 4) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 4)) | ((value ? 1 : 0) << 4)); } - public bool HT_SPE { get => ((HyperTrainFlags >> 5) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 5)) | ((value ? 1 : 0) << 5)); } - public override int Version { get => Data[0xDF]; set => Data[0xDF] = (byte)value; } - public byte Country { get => Data[0xE0]; set => Data[0xE0] = value; } - public byte Region { get => Data[0xE1]; set => Data[0xE1] = value; } - public byte ConsoleRegion { get => Data[0xE2]; set => Data[0xE2] = value; } - public override int Language { get => Data[0xE3]; set => Data[0xE3] = (byte)value; } - #endregion - #region Battle Stats - public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0xE8)); set => WriteInt32LittleEndian(Data.AsSpan(0xE8), value); } - public override int Stat_Level { get => Data[0xEC]; set => Data[0xEC] = (byte)value; } - public byte DirtType { get => Data[0xED]; set => Data[0xED] = value; } - public byte DirtLocation { get => Data[0xEE]; set => Data[0xEE] = value; } - // 0xEF unused - public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0xF0)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF0), (ushort)value); } - public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0xF2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF2), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0xF4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF4), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0xF6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF6), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0xF8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF8), (ushort)value); } - public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0xFA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFA), (ushort)value); } - public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0xFC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFC), (ushort)value); } - #endregion + public override int OT_Friendship { get => Data[0xCA]; set => Data[0xCA] = (byte)value; } + public byte OT_Affection { get => Data[0xCB]; set => Data[0xCB] = value; } + public byte OT_Intensity { get => Data[0xCC]; set => Data[0xCC] = value; } + public byte OT_Memory { get => Data[0xCD]; set => Data[0xCD] = value; } + public ushort OT_TextVar { get => ReadUInt16LittleEndian(Data.AsSpan(0xCE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xCE), value); } + public byte OT_Feeling { get => Data[0xD0]; set => Data[0xD0] = value; } + public override int Egg_Year { get => Data[0xD1]; set => Data[0xD1] = (byte)value; } + public override int Egg_Month { get => Data[0xD2]; set => Data[0xD2] = (byte)value; } + public override int Egg_Day { get => Data[0xD3]; set => Data[0xD3] = (byte)value; } + public override int Met_Year { get => Data[0xD4]; set => Data[0xD4] = (byte)value; } + public override int Met_Month { get => Data[0xD5]; set => Data[0xD5] = (byte)value; } + public override int Met_Day { get => Data[0xD6]; set => Data[0xD6] = (byte)value; } + // Unused 0xD7 + public override int Egg_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xD8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xD8), (ushort)value); } + public override int Met_Location { get => ReadUInt16LittleEndian(Data.AsSpan(0xDA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xDA), (ushort)value); } + public override int Ball { get => Data[0xDC]; set => Data[0xDC] = (byte)value; } + public override int Met_Level { get => Data[0xDD] & ~0x80; set => Data[0xDD] = (byte)((Data[0xDD] & 0x80) | value); } + public override int OT_Gender { get => Data[0xDD] >> 7; set => Data[0xDD] = (byte)((Data[0xDD] & ~0x80) | (value << 7)); } + public byte HyperTrainFlags { get => Data[0xDE]; set => Data[0xDE] = value; } + public bool HT_HP { get => ((HyperTrainFlags >> 0) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 0)) | ((value ? 1 : 0) << 0)); } + public bool HT_ATK { get => ((HyperTrainFlags >> 1) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 1)) | ((value ? 1 : 0) << 1)); } + public bool HT_DEF { get => ((HyperTrainFlags >> 2) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 2)) | ((value ? 1 : 0) << 2)); } + public bool HT_SPA { get => ((HyperTrainFlags >> 3) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 3)) | ((value ? 1 : 0) << 3)); } + public bool HT_SPD { get => ((HyperTrainFlags >> 4) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 4)) | ((value ? 1 : 0) << 4)); } + public bool HT_SPE { get => ((HyperTrainFlags >> 5) & 1) == 1; set => HyperTrainFlags = (byte)((HyperTrainFlags & ~(1 << 5)) | ((value ? 1 : 0) << 5)); } + public override int Version { get => Data[0xDF]; set => Data[0xDF] = (byte)value; } + public byte Country { get => Data[0xE0]; set => Data[0xE0] = value; } + public byte Region { get => Data[0xE1]; set => Data[0xE1] = value; } + public byte ConsoleRegion { get => Data[0xE2]; set => Data[0xE2] = value; } + public override int Language { get => Data[0xE3]; set => Data[0xE3] = (byte)value; } + #endregion + #region Battle Stats + public override int Status_Condition { get => ReadInt32LittleEndian(Data.AsSpan(0xE8)); set => WriteInt32LittleEndian(Data.AsSpan(0xE8), value); } + public override int Stat_Level { get => Data[0xEC]; set => Data[0xEC] = (byte)value; } + public byte DirtType { get => Data[0xED]; set => Data[0xED] = value; } + public byte DirtLocation { get => Data[0xEE]; set => Data[0xEE] = value; } + // 0xEF unused + public override int Stat_HPCurrent { get => ReadUInt16LittleEndian(Data.AsSpan(0xF0)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF0), (ushort)value); } + public override int Stat_HPMax { get => ReadUInt16LittleEndian(Data.AsSpan(0xF2)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF2), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16LittleEndian(Data.AsSpan(0xF4)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF4), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16LittleEndian(Data.AsSpan(0xF6)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF6), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16LittleEndian(Data.AsSpan(0xF8)); set => WriteUInt16LittleEndian(Data.AsSpan(0xF8), (ushort)value); } + public override int Stat_SPA { get => ReadUInt16LittleEndian(Data.AsSpan(0xFA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFA), (ushort)value); } + public override int Stat_SPD { get => ReadUInt16LittleEndian(Data.AsSpan(0xFC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xFC), (ushort)value); } + #endregion - public int SuperTrainingMedalCount(int maxCount = 30) - { - uint value = SuperTrainBitFlags >> 2; + public int SuperTrainingMedalCount(int maxCount = 30) + { + uint value = SuperTrainBitFlags >> 2; #if NET5 return System.Numerics.BitOperations.PopCount(value); #else - int TrainCount = 0; - for (int i = 0; i < maxCount; i++) - { - if ((value & 1) != 0) - TrainCount++; - value >>= 1; - } + int TrainCount = 0; + for (int i = 0; i < maxCount; i++) + { + if ((value & 1) != 0) + TrainCount++; + value >>= 1; + } - return TrainCount; + return TrainCount; #endif + } + + public bool IsUntradedEvent6 => Geo1_Country == 0 && Geo1_Region == 0 && Met_Location / 10000 == 4 && Gen6; + + public override int MarkingCount => 6; + + public override int GetMarking(int index) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return (MarkValue >> (index * 2)) & 3; + } + + public override void SetMarking(int index, int value) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + var shift = index * 2; + MarkValue = (MarkValue & ~(0b11 << shift)) | ((value & 3) << shift); + } + + public void FixMemories() + { + if (IsEgg) // No memories if is egg. + { + HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = 0; + /* OT_Friendship */ OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = HT_Affection = OT_Affection = 0; + this.ClearGeoLocationData(); + + // Clear Handler + HT_Trash.Clear(); + return; } - public bool IsUntradedEvent6 => Geo1_Country == 0 && Geo1_Region == 0 && Met_Location / 10000 == 4 && Gen6; + if (IsUntraded) + HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = HT_Affection = 0; + if (Generation < 6) + /* OT_Affection = */ OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = 0; - public override int MarkingCount => 6; + this.SanitizeGeoLocationData(); - public override int GetMarking(int index) + if (Generation < 7) // must be transferred via bank, and must have memories { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - return (MarkValue >> (index * 2)) & 3; - } - - public override void SetMarking(int index, int value) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - var shift = index * 2; - MarkValue = (MarkValue & ~(0b11 << shift)) | ((value & 3) << shift); - } - - public void FixMemories() - { - if (IsEgg) // No memories if is egg. - { - HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = 0; - /* OT_Friendship */ OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = HT_Affection = OT_Affection = 0; - this.ClearGeoLocationData(); - - // Clear Handler - HT_Trash.Clear(); - return; - } - - if (IsUntraded) - HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = HT_Affection = 0; - if (Generation < 6) - /* OT_Affection = */ OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = 0; - - this.SanitizeGeoLocationData(); - - if (Generation < 7) // must be transferred via bank, and must have memories - { - this.SetTradeMemoryHT6(true); // oh no, memories on gen7 pkm - // georegions cleared on 6->7, no need to set - } - } - - protected override bool TradeOT(ITrainerInfo tr) - { - // Check to see if the OT matches the SAV's OT info. - if (!(tr.TID == TID && tr.SID == SID && tr.Gender == OT_Gender && tr.OT == OT_Name)) - return false; - - CurrentHandler = 0; - return true; - } - - protected override void TradeHT(ITrainerInfo tr) - { - if (tr.OT != HT_Name || tr.Gender != HT_Gender || (Geo1_Country == 0 && Geo1_Region == 0 && !IsUntradedEvent6)) - { - // No geolocations are set ingame -- except for bank transfers. Don't emulate bank transfers - // this.TradeGeoLocation(tr.Country, tr.SubRegion); - } - - if (HT_Name != tr.OT) - { - HT_Friendship = PersonalInfo.BaseFriendship; - HT_Affection = 0; - HT_Name = tr.OT; - } - CurrentHandler = 1; - HT_Gender = tr.Gender; - } - - // Maximums - public override int MaxMoveID => Legal.MaxMoveID_7_USUM; - public override int MaxSpeciesID => Legal.MaxSpeciesID_7_USUM; - public override int MaxAbilityID => Legal.MaxAbilityID_7_USUM; - public override int MaxItemID => Legal.MaxItemID_7_USUM; - public override int MaxBallID => Legal.MaxBallID_7; - public override int MaxGameID => Legal.MaxGameID_7; - - public PK8 ConvertToPK8() - { - var pk8 = new PK8 - { - EncryptionConstant = EncryptionConstant, - Species = Species, - TID = TID, - SID = SID, - EXP = EXP, - PID = PID, - Ability = Ability, - AbilityNumber = AbilityNumber, - MarkValue = MarkValue & 0b1111_1111_1111, - Language = Language, - 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, - RelearnMove1 = RelearnMove1, - RelearnMove2 = RelearnMove2, - RelearnMove3 = RelearnMove3, - RelearnMove4 = RelearnMove4, - IV_HP = IV_HP, - IV_ATK = IV_ATK, - IV_DEF = IV_DEF, - IV_SPA = IV_SPA, - IV_SPD = IV_SPD, - IV_SPE = IV_SPE, - IsEgg = IsEgg, - IsNicknamed = IsNicknamed, - FatefulEncounter = FatefulEncounter, - Gender = Gender, - Form = Form, - Nature = Nature, - Nickname = IsNicknamed ? Nickname : SpeciesName.GetSpeciesNameGeneration(Species, Language, 8), - Version = Version, - OT_Name = OT_Name, - MetDate = MetDate, - EggMetDate = EggMetDate, - Met_Location = Met_Location, - Egg_Location = Egg_Location, - Ball = Ball, - Met_Level = Met_Level, - OT_Gender = OT_Gender, - HyperTrainFlags = HyperTrainFlags, - - // Locale does not transfer. All Zero - // Country = Country, - // Region = Region, - // ConsoleRegion = ConsoleRegion, - - OT_Memory = OT_Memory, - OT_TextVar = OT_TextVar, - OT_Feeling = OT_Feeling, - OT_Intensity = OT_Intensity, - - PKRS_Strain = PKRS_Strain, - PKRS_Days = PKRS_Days, - CNT_Cool = CNT_Cool, - CNT_Beauty = CNT_Beauty, - CNT_Cute = CNT_Cute, - CNT_Smart = CNT_Smart, - CNT_Tough = CNT_Tough, - CNT_Sheen = CNT_Sheen, - - RibbonChampionG3 = RibbonChampionG3, - RibbonChampionSinnoh = RibbonChampionSinnoh, - RibbonEffort = RibbonEffort, - RibbonAlert = RibbonAlert, - RibbonShock = RibbonShock, - RibbonDowncast = RibbonDowncast, - RibbonCareless = RibbonCareless, - RibbonRelax = RibbonRelax, - RibbonSnooze = RibbonSnooze, - RibbonSmile = RibbonSmile, - RibbonGorgeous = RibbonGorgeous, - RibbonRoyal = RibbonRoyal, - RibbonGorgeousRoyal = RibbonGorgeousRoyal, - RibbonArtist = RibbonArtist, - RibbonFootprint = RibbonFootprint, - RibbonRecord = RibbonRecord, - RibbonLegend = RibbonLegend, - RibbonCountry = RibbonCountry, - RibbonNational = RibbonNational, - RibbonEarth = RibbonEarth, - RibbonWorld = RibbonWorld, - RibbonClassic = RibbonClassic, - RibbonPremier = RibbonPremier, - RibbonEvent = RibbonEvent, - RibbonBirthday = RibbonBirthday, - RibbonSpecial = RibbonSpecial, - RibbonSouvenir = RibbonSouvenir, - RibbonWishing = RibbonWishing, - RibbonChampionBattle = RibbonChampionBattle, - RibbonChampionRegional = RibbonChampionRegional, - RibbonChampionNational = RibbonChampionNational, - RibbonChampionWorld = RibbonChampionWorld, - RibbonChampionKalos = RibbonChampionKalos, - RibbonChampionG6Hoenn = RibbonChampionG6Hoenn, - RibbonBestFriends = RibbonBestFriends, - RibbonTraining = RibbonTraining, - RibbonBattlerSkillful = RibbonBattlerSkillful, - RibbonBattlerExpert = RibbonBattlerExpert, - RibbonContestStar = RibbonContestStar, - RibbonMasterCoolness = RibbonMasterCoolness, - RibbonMasterBeauty = RibbonMasterBeauty, - RibbonMasterCuteness = RibbonMasterCuteness, - RibbonMasterCleverness = RibbonMasterCleverness, - RibbonMasterToughness = RibbonMasterToughness, - RibbonCountMemoryContest = RibbonCountMemoryContest, - RibbonCountMemoryBattle = RibbonCountMemoryBattle, - RibbonChampionAlola = RibbonChampionAlola, - RibbonBattleRoyale = RibbonBattleRoyale, - RibbonBattleTreeGreat = RibbonBattleTreeGreat, - RibbonBattleTreeMaster = RibbonBattleTreeMaster, - - OT_Friendship = OT_Friendship, - - // No Ribbons or Markings on transfer. - - StatNature = Nature, - // HeightScalar = 0, - // WeightScalar = 0, - - // Copy Form Argument data for Furfrou and Hoopa, since we're nice. - FormArgumentRemain = FormArgumentRemain, - FormArgumentElapsed = FormArgumentElapsed, - FormArgumentMaximum = FormArgumentMaximum, - }; - - // Wipe Totem Forms - if (FormInfo.IsTotemForm(Species, Form)) - pk8.Form = FormInfo.GetTotemBaseForm(Species, Form); - - // Fix PP and Stats - pk8.Heal(); - - // Fix Checksum - pk8.RefreshChecksum(); - - return pk8; // Done! + this.SetTradeMemoryHT6(true); // oh no, memories on gen7 pk + // georegions cleared on 6->7, no need to set } } - public enum ResortEventState : byte + protected override bool TradeOT(ITrainerInfo tr) { - NONE = 0, - SEIKAKU = 1, - CARE = 2, - LIKE_RESORT = 3, - LIKE_BATTLE = 4, - LIKE_ADV = 5, - GOOD_FRIEND = 6, - GIM = 7, - HOTSPA = 8, - WILD = 9, - WILD_LOVE = 10, - WILD_LIVE = 11, - POKEMAME_GET1 = 12, - POKEMAME_GET2 = 13, - POKEMAME_GET3 = 14, - KINOMI_HELP = 15, - PLAY_STATE = 16, - HOTSPA_STATE = 17, - HOTSPA_DIZZY = 18, - HOTSPA_EGG_HATCHING = 19, - MAX = 20, + // Check to see if the OT matches the SAV's OT info. + if (!(tr.TID == TID && tr.SID == SID && tr.Gender == OT_Gender && tr.OT == OT_Name)) + return false; + + CurrentHandler = 0; + return true; + } + + protected override void TradeHT(ITrainerInfo tr) + { + if (tr.OT != HT_Name || tr.Gender != HT_Gender || (Geo1_Country == 0 && Geo1_Region == 0 && !IsUntradedEvent6)) + { + // No geolocations are set ingame -- except for bank transfers. Don't emulate bank transfers + // this.TradeGeoLocation(tr.Country, tr.SubRegion); + } + + if (HT_Name != tr.OT) + { + HT_Friendship = PersonalInfo.BaseFriendship; + HT_Affection = 0; + HT_Name = tr.OT; + } + CurrentHandler = 1; + HT_Gender = tr.Gender; + } + + // Maximums + public override int MaxMoveID => Legal.MaxMoveID_7_USUM; + public override int MaxSpeciesID => Legal.MaxSpeciesID_7_USUM; + public override int MaxAbilityID => Legal.MaxAbilityID_7_USUM; + public override int MaxItemID => Legal.MaxItemID_7_USUM; + public override int MaxBallID => Legal.MaxBallID_7; + public override int MaxGameID => Legal.MaxGameID_7; + + public PK8 ConvertToPK8() + { + var pk8 = new PK8 + { + EncryptionConstant = EncryptionConstant, + Species = Species, + TID = TID, + SID = SID, + EXP = EXP, + PID = PID, + Ability = Ability, + AbilityNumber = AbilityNumber, + MarkValue = MarkValue & 0b1111_1111_1111, + Language = Language, + 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, + RelearnMove1 = RelearnMove1, + RelearnMove2 = RelearnMove2, + RelearnMove3 = RelearnMove3, + RelearnMove4 = RelearnMove4, + IV_HP = IV_HP, + IV_ATK = IV_ATK, + IV_DEF = IV_DEF, + IV_SPA = IV_SPA, + IV_SPD = IV_SPD, + IV_SPE = IV_SPE, + IsEgg = IsEgg, + IsNicknamed = IsNicknamed, + FatefulEncounter = FatefulEncounter, + Gender = Gender, + Form = Form, + Nature = Nature, + Nickname = IsNicknamed ? Nickname : SpeciesName.GetSpeciesNameGeneration(Species, Language, 8), + Version = Version, + OT_Name = OT_Name, + MetDate = MetDate, + EggMetDate = EggMetDate, + Met_Location = Met_Location, + Egg_Location = Egg_Location, + Ball = Ball, + Met_Level = Met_Level, + OT_Gender = OT_Gender, + HyperTrainFlags = HyperTrainFlags, + + // Locale does not transfer. All Zero + // Country = Country, + // Region = Region, + // ConsoleRegion = ConsoleRegion, + + OT_Memory = OT_Memory, + OT_TextVar = OT_TextVar, + OT_Feeling = OT_Feeling, + OT_Intensity = OT_Intensity, + + PKRS_Strain = PKRS_Strain, + PKRS_Days = PKRS_Days, + CNT_Cool = CNT_Cool, + CNT_Beauty = CNT_Beauty, + CNT_Cute = CNT_Cute, + CNT_Smart = CNT_Smart, + CNT_Tough = CNT_Tough, + CNT_Sheen = CNT_Sheen, + + RibbonChampionG3 = RibbonChampionG3, + RibbonChampionSinnoh = RibbonChampionSinnoh, + RibbonEffort = RibbonEffort, + RibbonAlert = RibbonAlert, + RibbonShock = RibbonShock, + RibbonDowncast = RibbonDowncast, + RibbonCareless = RibbonCareless, + RibbonRelax = RibbonRelax, + RibbonSnooze = RibbonSnooze, + RibbonSmile = RibbonSmile, + RibbonGorgeous = RibbonGorgeous, + RibbonRoyal = RibbonRoyal, + RibbonGorgeousRoyal = RibbonGorgeousRoyal, + RibbonArtist = RibbonArtist, + RibbonFootprint = RibbonFootprint, + RibbonRecord = RibbonRecord, + RibbonLegend = RibbonLegend, + RibbonCountry = RibbonCountry, + RibbonNational = RibbonNational, + RibbonEarth = RibbonEarth, + RibbonWorld = RibbonWorld, + RibbonClassic = RibbonClassic, + RibbonPremier = RibbonPremier, + RibbonEvent = RibbonEvent, + RibbonBirthday = RibbonBirthday, + RibbonSpecial = RibbonSpecial, + RibbonSouvenir = RibbonSouvenir, + RibbonWishing = RibbonWishing, + RibbonChampionBattle = RibbonChampionBattle, + RibbonChampionRegional = RibbonChampionRegional, + RibbonChampionNational = RibbonChampionNational, + RibbonChampionWorld = RibbonChampionWorld, + RibbonChampionKalos = RibbonChampionKalos, + RibbonChampionG6Hoenn = RibbonChampionG6Hoenn, + RibbonBestFriends = RibbonBestFriends, + RibbonTraining = RibbonTraining, + RibbonBattlerSkillful = RibbonBattlerSkillful, + RibbonBattlerExpert = RibbonBattlerExpert, + RibbonContestStar = RibbonContestStar, + RibbonMasterCoolness = RibbonMasterCoolness, + RibbonMasterBeauty = RibbonMasterBeauty, + RibbonMasterCuteness = RibbonMasterCuteness, + RibbonMasterCleverness = RibbonMasterCleverness, + RibbonMasterToughness = RibbonMasterToughness, + RibbonCountMemoryContest = RibbonCountMemoryContest, + RibbonCountMemoryBattle = RibbonCountMemoryBattle, + RibbonChampionAlola = RibbonChampionAlola, + RibbonBattleRoyale = RibbonBattleRoyale, + RibbonBattleTreeGreat = RibbonBattleTreeGreat, + RibbonBattleTreeMaster = RibbonBattleTreeMaster, + + OT_Friendship = OT_Friendship, + + // No Ribbons or Markings on transfer. + + StatNature = Nature, + // HeightScalar = 0, + // WeightScalar = 0, + + // Copy Form Argument data for Furfrou and Hoopa, since we're nice. + FormArgumentRemain = FormArgumentRemain, + FormArgumentElapsed = FormArgumentElapsed, + FormArgumentMaximum = FormArgumentMaximum, + }; + + // Wipe Totem Forms + if (FormInfo.IsTotemForm(Species, Form)) + pk8.Form = FormInfo.GetTotemBaseForm(Species, Form); + + // Fix PP and Stats + pk8.Heal(); + + // Fix Checksum + pk8.RefreshChecksum(); + + return pk8; // Done! } } + +public enum ResortEventState : byte +{ + NONE = 0, + SEIKAKU = 1, + CARE = 2, + LIKE_RESORT = 3, + LIKE_BATTLE = 4, + LIKE_ADV = 5, + GOOD_FRIEND = 6, + GIM = 7, + HOTSPA = 8, + WILD = 9, + WILD_LOVE = 10, + WILD_LIVE = 11, + POKEMAME_GET1 = 12, + POKEMAME_GET2 = 13, + POKEMAME_GET3 = 14, + KINOMI_HELP = 15, + PLAY_STATE = 16, + HOTSPA_STATE = 17, + HOTSPA_DIZZY = 18, + HOTSPA_EGG_HATCHING = 19, + MAX = 20, +} diff --git a/PKHeX.Core/PKM/PK8.cs b/PKHeX.Core/PKM/PK8.cs index 1cb24a8ae..4c694a1d1 100644 --- a/PKHeX.Core/PKM/PK8.cs +++ b/PKHeX.Core/PKM/PK8.cs @@ -2,194 +2,193 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 8 format. +public sealed class PK8 : G8PKM { - /// Generation 8 format. - public sealed class PK8 : G8PKM + private static readonly ushort[] Unused = { - private static readonly ushort[] Unused = + // Alignment bytes + 0x17, 0x1A, 0x1B, 0x23, 0x33, 0x3E, 0x3F, + 0x4C, 0x4D, 0x4E, 0x4F, + 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + + 0x91, 0x92, 0x93, + 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + + 0xC5, + 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, + 0xE0, 0xE1, // Old Console Region / Region + 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0x115, 0x11F, // Alignment + + 0x13D, 0x13E, 0x13F, + 0x140, 0x141, 0x142, 0x143, 0x144, 0x145, 0x146, 0x147, + }; + + public override IReadOnlyList ExtraBytes => Unused; + public override PersonalInfo PersonalInfo => PersonalTable.SWSH.GetFormEntry(Species, Form); + public override bool IsNative => SWSH; + public override EntityContext Context => EntityContext.Gen8; + + public PK8() => AffixedRibbon = -1; // 00 would make it show Kalos Champion :) + public PK8(byte[] data) : base(data) { } + public override PKM Clone() => new PK8((byte[])Data.Clone()); + + public void Trade(ITrainerInfo tr, int Day = 1, int Month = 1, int Year = 2015) + { + if (IsEgg) { - // Alignment bytes - 0x17, 0x1A, 0x1B, 0x23, 0x33, 0x3E, 0x3F, - 0x4C, 0x4D, 0x4E, 0x4F, - 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, - - 0x91, 0x92, 0x93, - 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, - - 0xC5, - 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, - 0xE0, 0xE1, // Old Console Region / Region - 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, - 0x115, 0x11F, // Alignment - - 0x13D, 0x13E, 0x13F, - 0x140, 0x141, 0x142, 0x143, 0x144, 0x145, 0x146, 0x147, - }; - - public override IReadOnlyList ExtraBytes => Unused; - public override PersonalInfo PersonalInfo => PersonalTable.SWSH.GetFormEntry(Species, Form); - public override bool IsNative => SWSH; - public override EntityContext Context => EntityContext.Gen8; - - public PK8() => AffixedRibbon = -1; // 00 would make it show Kalos Champion :) - public PK8(byte[] data) : base(data) { } - public override PKM Clone() => new PK8((byte[])Data.Clone()); - - public void Trade(ITrainerInfo tr, int Day = 1, int Month = 1, int Year = 2015) - { - if (IsEgg) - { - // Eggs do not have any modifications done if they are traded - // Apply link trade data, only if it left the OT (ignore if dumped & imported, or cloned, etc) - if ((tr.TID != TID) || (tr.SID != SID) || (tr.Gender != OT_Gender) || (tr.OT != OT_Name)) - SetLinkTradeEgg(Day, Month, Year, Locations.LinkTrade6); - return; - } - - // Process to the HT if the OT of the Pokémon does not match the SAV's OT info. - if (!TradeOT(tr)) - TradeHT(tr); + // Eggs do not have any modifications done if they are traded + // Apply link trade data, only if it left the OT (ignore if dumped & imported, or cloned, etc) + if ((tr.TID != TID) || (tr.SID != SID) || (tr.Gender != OT_Gender) || (tr.OT != OT_Name)) + SetLinkTradeEgg(Day, Month, Year, Locations.LinkTrade6); + return; } - public int DynamaxType { get => ReadUInt16LittleEndian(Data.AsSpan(0x156)); set => WriteUInt16LittleEndian(Data.AsSpan(0x156), (ushort)value); } + // Process to the HT if the OT of the Pokémon does not match the SAV's OT info. + if (!TradeOT(tr)) + TradeHT(tr); + } - public void FixMemories() + public int DynamaxType { get => ReadUInt16LittleEndian(Data.AsSpan(0x156)); set => WriteUInt16LittleEndian(Data.AsSpan(0x156), (ushort)value); } + + public void FixMemories() + { + if (IsEgg) // No memories if is egg. { - if (IsEgg) // No memories if is egg. - { - HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = HT_Language = 0; - /* OT_Friendship */ OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = 0; + HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = HT_Language = 0; + /* OT_Friendship */ OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = 0; - // Clear Handler - HT_Trash.Clear(); - return; - } - - if (IsUntraded) - HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = HT_Language = 0; - - int gen = Generation; - if (gen < 6) - OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = 0; - if (gen != 8) // must be transferred via HOME, and must have memories - this.SetTradeMemoryHT8(); // not faking HOME tracker. + // Clear Handler + HT_Trash.Clear(); + return; } - private bool TradeOT(ITrainerInfo tr) - { - // Check to see if the OT matches the SAV's OT info. - if (!(tr.TID == TID && tr.SID == SID && tr.Gender == OT_Gender && tr.OT == OT_Name)) - return false; + if (IsUntraded) + HT_Friendship = HT_TextVar = HT_Memory = HT_Intensity = HT_Feeling = HT_Language = 0; - CurrentHandler = 0; - return true; + int gen = Generation; + if (gen < 6) + OT_TextVar = OT_Memory = OT_Intensity = OT_Feeling = 0; + if (gen != 8) // must be transferred via HOME, and must have memories + this.SetTradeMemoryHT8(); // not faking HOME tracker. + } + + private bool TradeOT(ITrainerInfo tr) + { + // Check to see if the OT matches the SAV's OT info. + if (!(tr.TID == TID && tr.SID == SID && tr.Gender == OT_Gender && tr.OT == OT_Name)) + return false; + + CurrentHandler = 0; + return true; + } + + private void TradeHT(ITrainerInfo tr) + { + if (HT_Name != tr.OT) + { + HT_Friendship = 50; + HT_Name = tr.OT; } + CurrentHandler = 1; + HT_Gender = tr.Gender; + HT_Language = (byte)tr.Language; + this.SetTradeMemoryHT8(); + } - private void TradeHT(ITrainerInfo tr) + // Maximums + public override int MaxMoveID => Legal.MaxMoveID_8; + public override int MaxSpeciesID => Legal.MaxSpeciesID_8; + public override int MaxAbilityID => Legal.MaxAbilityID_8; + public override int MaxItemID => Legal.MaxItemID_8; + public override int MaxBallID => Legal.MaxBallID_8; + public override int MaxGameID => Legal.MaxGameID_8; + + public PB8 ConvertToPB8() + { + var pk = ConvertTo(); + if (pk.Egg_Location == 0) + pk.Egg_Location = Locations.Default8bNone; + UnmapLocation(pk); + return pk; + } + + public override PA8 ConvertToPA8() + { + var pk = base.ConvertToPA8(); + UnmapLocation(pk); + return pk; + } + + private static void UnmapLocation(PKM pk) + { + switch (pk.Met_Location) { - if (HT_Name != tr.OT) - { - HT_Friendship = 50; - HT_Name = tr.OT; - } - CurrentHandler = 1; - HT_Gender = tr.Gender; - HT_Language = (byte)tr.Language; - this.SetTradeMemoryHT8(); - } - - // Maximums - public override int MaxMoveID => Legal.MaxMoveID_8; - public override int MaxSpeciesID => Legal.MaxSpeciesID_8; - public override int MaxAbilityID => Legal.MaxAbilityID_8; - public override int MaxItemID => Legal.MaxItemID_8; - public override int MaxBallID => Legal.MaxBallID_8; - public override int MaxGameID => Legal.MaxGameID_8; - - public PB8 ConvertToPB8() - { - var pk = ConvertTo(); - if (pk.Egg_Location == 0) - pk.Egg_Location = Locations.Default8bNone; - UnmapLocation(pk); - return pk; - } - - public override PA8 ConvertToPA8() - { - var pk = base.ConvertToPA8(); - UnmapLocation(pk); - return pk; - } - - private static void UnmapLocation(PKM pk) - { - switch (pk.Met_Location) - { - case Locations.HOME_SWLA: - pk.Version = (int)GameVersion.PLA; - // Keep location due to bad transfer logic (official) -- server legal. - break; - case Locations.HOME_SWBD: - pk.Version = (int)GameVersion.BD; - pk.Met_Location = 0; // Load whatever value from the server. We don't know. - break; - case Locations.HOME_SHSP: - pk.Version = (int)GameVersion.SP; - pk.Met_Location = 0; // Load whatever value from the server. We don't know. - break; - } - } - - public override void ResetMoves() - { - var learnsets = Legal.LevelUpSWSH; - var table = PersonalTable.SWSH; - - var index = table.GetFormIndex(Species, Form); - var learn = learnsets[index]; - Span moves = stackalloc int[4]; - learn.SetEncounterMoves(CurrentLevel, moves); - SetMoves(moves); - this.SetMaximumPPCurrent(moves); - } - - public override bool BDSP => Met_Location is Locations.HOME_SWBD or Locations.HOME_SHSP; - public override bool LA => Met_Location is Locations.HOME_SWLA; - public override bool HasOriginalMetLocation => base.HasOriginalMetLocation && !(BDSP || LA); - - public void SanitizeImport() - { - var ver = Version; - if (ver is (int)GameVersion.SP) - { - Met_Location = Locations.HOME_SHSP; - Version = (int)GameVersion.SH; - if (Egg_Location != 0) - Egg_Location = Locations.HOME_SHSP; - } - else if (ver is (int)GameVersion.BD) - { - Met_Location = Locations.HOME_SWBD; - Version = (int)GameVersion.SW; - if (Egg_Location != 0) - Egg_Location = Locations.HOME_SWBD; - } - else if (ver is (int)GameVersion.PLA) - { - Met_Location = Locations.HOME_SWLA; - Version = (int)GameVersion.SW; - if (Egg_Location != 0) - Egg_Location = Locations.HOME_SWLA; - } - else if (ver > (int)GameVersion.PLA) - { - Met_Location = Met_Location <= Locations.HOME_SWLA ? Locations.HOME_SWLA : Locations.HOME_SWSHBDSPEgg; - } - - if (Ball > (int)Core.Ball.Beast) - Ball = (int)Core.Ball.Poke; + case Locations.HOME_SWLA: + pk.Version = (int)GameVersion.PLA; + // Keep location due to bad transfer logic (official) -- server legal. + break; + case Locations.HOME_SWBD: + pk.Version = (int)GameVersion.BD; + pk.Met_Location = 0; // Load whatever value from the server. We don't know. + break; + case Locations.HOME_SHSP: + pk.Version = (int)GameVersion.SP; + pk.Met_Location = 0; // Load whatever value from the server. We don't know. + break; } } + + public override void ResetMoves() + { + var learnsets = Legal.LevelUpSWSH; + var table = PersonalTable.SWSH; + + var index = table.GetFormIndex(Species, Form); + var learn = learnsets[index]; + Span moves = stackalloc int[4]; + learn.SetEncounterMoves(CurrentLevel, moves); + SetMoves(moves); + this.SetMaximumPPCurrent(moves); + } + + public override bool BDSP => Met_Location is Locations.HOME_SWBD or Locations.HOME_SHSP; + public override bool LA => Met_Location is Locations.HOME_SWLA; + public override bool HasOriginalMetLocation => base.HasOriginalMetLocation && !(BDSP || LA); + + public void SanitizeImport() + { + var ver = Version; + if (ver is (int)GameVersion.SP) + { + Met_Location = Locations.HOME_SHSP; + Version = (int)GameVersion.SH; + if (Egg_Location != 0) + Egg_Location = Locations.HOME_SHSP; + } + else if (ver is (int)GameVersion.BD) + { + Met_Location = Locations.HOME_SWBD; + Version = (int)GameVersion.SW; + if (Egg_Location != 0) + Egg_Location = Locations.HOME_SWBD; + } + else if (ver is (int)GameVersion.PLA) + { + Met_Location = Locations.HOME_SWLA; + Version = (int)GameVersion.SW; + if (Egg_Location != 0) + Egg_Location = Locations.HOME_SWLA; + } + else if (ver > (int)GameVersion.PLA) + { + Met_Location = Met_Location <= Locations.HOME_SWLA ? Locations.HOME_SWLA : Locations.HOME_SWSHBDSPEgg; + } + + if (Ball > (int)Core.Ball.Beast) + Ball = (int)Core.Ball.Poke; + } } diff --git a/PKHeX.Core/PKM/PKM.cs b/PKHeX.Core/PKM/PKM.cs index a1e62546f..3750b23bc 100644 --- a/PKHeX.Core/PKM/PKM.cs +++ b/PKHeX.Core/PKM/PKM.cs @@ -1,1147 +1,1146 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Object representing a 's data and derived properties. +/// +public abstract class PKM : ISpeciesForm, ITrainerID, IGeneration, IShiny, ILangNick, IGameValueLimit, INature { /// - /// Object representing a 's data and derived properties. + /// Valid file extensions that represent data, without the leading '.' /// - public abstract class PKM : ISpeciesForm, ITrainerID, IGeneration, IShiny, ILangNick, IGameValueLimit, INature + public static readonly string[] Extensions = EntityFileExtension.GetExtensions(); + public abstract int SIZE_PARTY { get; } + public abstract int SIZE_STORED { get; } + public string Extension => GetType().Name.ToLowerInvariant(); + public abstract PersonalInfo PersonalInfo { get; } + public virtual IReadOnlyList ExtraBytes => Array.Empty(); + + // Internal Attributes set on creation + public readonly byte[] Data; // Raw Storage + + protected PKM(byte[] data) => Data = data; + protected PKM(int size) => Data = new byte[size]; + + public virtual byte[] EncryptedPartyData => Encrypt().AsSpan()[..SIZE_PARTY].ToArray(); + public virtual byte[] EncryptedBoxData => Encrypt().AsSpan()[..SIZE_STORED].ToArray(); + public virtual byte[] DecryptedPartyData => Write().AsSpan()[..SIZE_PARTY].ToArray(); + public virtual byte[] DecryptedBoxData => Write().AsSpan()[..SIZE_STORED].ToArray(); + + /// + /// Rough indication if the data is junk or not. + /// + public abstract bool Valid { get; set; } + + // Trash Bytes + public abstract Span Nickname_Trash { get; } + public abstract Span OT_Trash { get; } + public virtual Span HT_Trash => Span.Empty; + + protected abstract byte[] Encrypt(); + public abstract EntityContext Context { get; } + public int Format => Context.Generation(); + + private byte[] Write() { - /// - /// Valid file extensions that represent data, without the leading '.' - /// - public static readonly string[] Extensions = EntityFileExtension.GetExtensions(); - public abstract int SIZE_PARTY { get; } - public abstract int SIZE_STORED { get; } - public string Extension => GetType().Name.ToLowerInvariant(); - public abstract PersonalInfo PersonalInfo { get; } - public virtual IReadOnlyList ExtraBytes => Array.Empty(); + RefreshChecksum(); + return Data; + } - // Internal Attributes set on creation - public readonly byte[] Data; // Raw Storage + // Surface Properties + public abstract int Species { get; set; } + public abstract string Nickname { get; set; } + public abstract int HeldItem { get; set; } + public abstract int Gender { get; set; } + public abstract int Nature { get; set; } + public virtual int StatNature { get => Nature; set => Nature = value; } + public abstract int Ability { get; set; } + public abstract int CurrentFriendship { get; set; } + public abstract int Form { get; set; } + public abstract bool IsEgg { get; set; } + public abstract bool IsNicknamed { get; set; } + public abstract uint EXP { get; set; } + public abstract int TID { get; set; } + public abstract string OT_Name { get; set; } + public abstract int OT_Gender { get; set; } + public abstract int Ball { get; set; } + public abstract int Met_Level { get; set; } - protected PKM(byte[] data) => Data = data; - protected PKM(int size) => Data = new byte[size]; + // Battle + public abstract int Move1 { get; set; } + public abstract int Move2 { get; set; } + public abstract int Move3 { get; set; } + public abstract int Move4 { get; set; } + public abstract int Move1_PP { get; set; } + public abstract int Move2_PP { get; set; } + public abstract int Move3_PP { get; set; } + public abstract int Move4_PP { get; set; } + public abstract int Move1_PPUps { get; set; } + public abstract int Move2_PPUps { get; set; } + public abstract int Move3_PPUps { get; set; } + public abstract int Move4_PPUps { get; set; } + public abstract int EV_HP { get; set; } + public abstract int EV_ATK { get; set; } + public abstract int EV_DEF { get; set; } + public abstract int EV_SPE { get; set; } + public abstract int EV_SPA { get; set; } + public abstract int EV_SPD { get; set; } + public abstract int IV_HP { get; set; } + public abstract int IV_ATK { get; set; } + public abstract int IV_DEF { get; set; } + public abstract int IV_SPE { get; set; } + public abstract int IV_SPA { get; set; } + public abstract int IV_SPD { get; set; } + public abstract int Status_Condition { get; set; } + public abstract int Stat_Level { get; set; } + public abstract int Stat_HPMax { get; set; } + public abstract int Stat_HPCurrent { get; set; } + public abstract int Stat_ATK { get; set; } + public abstract int Stat_DEF { get; set; } + public abstract int Stat_SPE { get; set; } + public abstract int Stat_SPA { get; set; } + public abstract int Stat_SPD { get; set; } - public virtual byte[] EncryptedPartyData => Encrypt().AsSpan()[..SIZE_PARTY].ToArray(); - public virtual byte[] EncryptedBoxData => Encrypt().AsSpan()[..SIZE_STORED].ToArray(); - public virtual byte[] DecryptedPartyData => Write().AsSpan()[..SIZE_PARTY].ToArray(); - public virtual byte[] DecryptedBoxData => Write().AsSpan()[..SIZE_STORED].ToArray(); + // Hidden Properties + public abstract int Version { get; set; } + public abstract int SID { get; set; } + public abstract int PKRS_Strain { get; set; } + public abstract int PKRS_Days { get; set; } - /// - /// Rough indication if the data is junk or not. - /// - public abstract bool Valid { get; set; } + public abstract uint EncryptionConstant { get; set; } + public abstract uint PID { get; set; } - // Trash Bytes - public abstract Span Nickname_Trash { get; } - public abstract Span OT_Trash { get; } - public virtual Span HT_Trash => Span.Empty; + // Misc Properties + public abstract int Language { get; set; } + public abstract bool FatefulEncounter { get; set; } + public abstract int TSV { get; } + public abstract int PSV { get; } + public abstract int Characteristic { get; } + public abstract int MarkValue { get; set; } + public abstract int Met_Location { get; set; } + public abstract int Egg_Location { get; set; } + public abstract int OT_Friendship { get; set; } + public virtual bool Japanese => Language == (int)LanguageID.Japanese; + public virtual bool Korean => Language == (int)LanguageID.Korean; - protected abstract byte[] Encrypt(); - public abstract EntityContext Context { get; } - public int Format => Context.Generation(); + // Future Properties + public virtual int Met_Year { get => 0; set { } } + public virtual int Met_Month { get => 0; set { } } + public virtual int Met_Day { get => 0; set { } } + public virtual string HT_Name { get => string.Empty; set { } } + public virtual int HT_Gender { get => 0; set { } } + public virtual int HT_Friendship { get => 0; set { } } + public virtual byte Enjoyment { get => 0; set { } } + public virtual byte Fullness { get => 0; set { } } + public virtual int AbilityNumber { get => 0; set { } } - private byte[] Write() + /// + /// The date the Pokémon was met. + /// + /// + /// A DateTime representing the date the Pokémon was met. + /// Returns null if either the format does not support dates or the stored date is invalid. + /// + /// Not all types support the property. In these cases, this property will return null. + /// If null is assigned to this property, it will be cleared. + /// + public DateTime? MetDate + { + get { - RefreshChecksum(); - return Data; + // Check to see if date is valid + if (!DateUtil.IsDateValid(2000 + Met_Year, Met_Month, Met_Day)) + return null; + return new DateTime(2000 + Met_Year, Met_Month, Met_Day); } - - // Surface Properties - public abstract int Species { get; set; } - public abstract string Nickname { get; set; } - public abstract int HeldItem { get; set; } - public abstract int Gender { get; set; } - public abstract int Nature { get; set; } - public virtual int StatNature { get => Nature; set => Nature = value; } - public abstract int Ability { get; set; } - public abstract int CurrentFriendship { get; set; } - public abstract int Form { get; set; } - public abstract bool IsEgg { get; set; } - public abstract bool IsNicknamed { get; set; } - public abstract uint EXP { get; set; } - public abstract int TID { get; set; } - public abstract string OT_Name { get; set; } - public abstract int OT_Gender { get; set; } - public abstract int Ball { get; set; } - public abstract int Met_Level { get; set; } - - // Battle - public abstract int Move1 { get; set; } - public abstract int Move2 { get; set; } - public abstract int Move3 { get; set; } - public abstract int Move4 { get; set; } - public abstract int Move1_PP { get; set; } - public abstract int Move2_PP { get; set; } - public abstract int Move3_PP { get; set; } - public abstract int Move4_PP { get; set; } - public abstract int Move1_PPUps { get; set; } - public abstract int Move2_PPUps { get; set; } - public abstract int Move3_PPUps { get; set; } - public abstract int Move4_PPUps { get; set; } - public abstract int EV_HP { get; set; } - public abstract int EV_ATK { get; set; } - public abstract int EV_DEF { get; set; } - public abstract int EV_SPE { get; set; } - public abstract int EV_SPA { get; set; } - public abstract int EV_SPD { get; set; } - public abstract int IV_HP { get; set; } - public abstract int IV_ATK { get; set; } - public abstract int IV_DEF { get; set; } - public abstract int IV_SPE { get; set; } - public abstract int IV_SPA { get; set; } - public abstract int IV_SPD { get; set; } - public abstract int Status_Condition { get; set; } - public abstract int Stat_Level { get; set; } - public abstract int Stat_HPMax { get; set; } - public abstract int Stat_HPCurrent { get; set; } - public abstract int Stat_ATK { get; set; } - public abstract int Stat_DEF { get; set; } - public abstract int Stat_SPE { get; set; } - public abstract int Stat_SPA { get; set; } - public abstract int Stat_SPD { get; set; } - - // Hidden Properties - public abstract int Version { get; set; } - public abstract int SID { get; set; } - public abstract int PKRS_Strain { get; set; } - public abstract int PKRS_Days { get; set; } - - public abstract uint EncryptionConstant { get; set; } - public abstract uint PID { get; set; } - - // Misc Properties - public abstract int Language { get; set; } - public abstract bool FatefulEncounter { get; set; } - public abstract int TSV { get; } - public abstract int PSV { get; } - public abstract int Characteristic { get; } - public abstract int MarkValue { get; set; } - public abstract int Met_Location { get; set; } - public abstract int Egg_Location { get; set; } - public abstract int OT_Friendship { get; set; } - public virtual bool Japanese => Language == (int)LanguageID.Japanese; - public virtual bool Korean => Language == (int)LanguageID.Korean; - - // Future Properties - public virtual int Met_Year { get => 0; set { } } - public virtual int Met_Month { get => 0; set { } } - public virtual int Met_Day { get => 0; set { } } - public virtual string HT_Name { get => string.Empty; set { } } - public virtual int HT_Gender { get => 0; set { } } - public virtual int HT_Friendship { get => 0; set { } } - public virtual byte Enjoyment { get => 0; set { } } - public virtual byte Fullness { get => 0; set { } } - public virtual int AbilityNumber { get => 0; set { } } - - /// - /// The date the Pokémon was met. - /// - /// - /// A DateTime representing the date the Pokémon was met. - /// Returns null if either the format does not support dates or the stored date is invalid. - /// - /// Not all types support the property. In these cases, this property will return null. - /// If null is assigned to this property, it will be cleared. - /// - public DateTime? MetDate + set { - get + if (value.HasValue) { - // Check to see if date is valid - if (!DateUtil.IsDateValid(2000 + Met_Year, Met_Month, Met_Day)) - return null; - return new DateTime(2000 + Met_Year, Met_Month, Met_Day); + // Only update the properties if a value is provided. + Met_Year = value.Value.Year - 2000; + Met_Month = value.Value.Month; + Met_Day = value.Value.Day; } - set - { - if (value.HasValue) - { - // Only update the properties if a value is provided. - Met_Year = value.Value.Year - 2000; - Met_Month = value.Value.Month; - Met_Day = value.Value.Day; - } - else - { - // Clear the Met Date. - // If code tries to access MetDate again, null will be returned. - Met_Year = 0; - Met_Month = 0; - Met_Day = 0; - } - } - } - - public virtual int Egg_Year { get => 0; set { } } - public virtual int Egg_Month { get => 0; set { } } - public virtual int Egg_Day { get => 0; set { } } - - /// - /// The date a Pokémon was met as an egg. - /// - /// - /// A DateTime representing the date the Pokémon was met as an egg. - /// Returns null if either the format does not support dates or the stored date is invalid. - /// - /// Not all types support the property. In these cases, this property will return null. - /// If null is assigned to this property, it will be cleared. - /// - public DateTime? EggMetDate - { - get - { - // Check to see if date is valid - if (!DateUtil.IsDateValid(2000 + Egg_Year, Egg_Month, Egg_Day)) - return null; - return new DateTime(2000 + Egg_Year, Egg_Month, Egg_Day); - } - set - { - if (value.HasValue) - { - // Only update the properties if a value is provided. - Egg_Year = value.Value.Year - 2000; - Egg_Month = value.Value.Month; - Egg_Day = value.Value.Day; - } - else - { - // Clear the Met Date. - // If code tries to access MetDate again, null will be returned. - Egg_Year = 0; - Egg_Month = 0; - Egg_Day = 0; - } - } - } - - public virtual int RelearnMove1 { get => 0; set { } } - public virtual int RelearnMove2 { get => 0; set { } } - public virtual int RelearnMove3 { get => 0; set { } } - public virtual int RelearnMove4 { get => 0; set { } } - - // Exposed but not Present in all - public abstract int CurrentHandler { get; set; } - - // Maximums - public abstract int MaxMoveID { get; } - public abstract int MaxSpeciesID { get; } - public abstract int MaxItemID { get; } - public abstract int MaxAbilityID { get; } - public abstract int MaxBallID { get; } - public abstract int MaxGameID { get; } - public virtual int MinGameID => 0; - public abstract int MaxIV { get; } - public abstract int MaxEV { get; } - public abstract int OTLength { get; } - public abstract int NickLength { get; } - - // Derived - public int SpecForm { get => Species + (Form << 11); set { Species = value & 0x7FF; Form = value >> 11; } } - public virtual int SpriteItem => HeldItem; - public virtual bool IsShiny => TSV == PSV; - public int TrainerID7 { get => (int)((uint)(TID | (SID << 16)) % 1000000); set => SetID7(TrainerSID7, value); } - public int TrainerSID7 { get => (int)((uint)(TID | (SID << 16)) / 1000000); set => SetID7(value, TrainerID7); } - - public uint ShinyXor - { - get - { - var pid = PID; - var upper = (pid >> 16) ^ (uint)SID; - return (pid & 0xFFFF) ^ (uint)TID ^ upper; - } - } - - public int DisplayTID - { - get => Generation >= 7 ? TrainerID7 : TID; - set { if (Generation >= 7) TrainerID7 = value; else TID = value; } - } - - public int DisplaySID - { - get => Generation >= 7 ? TrainerSID7 : SID; - set { if (Generation >= 7) TrainerSID7 = value; else SID = value; } - } - - private void SetID7(int sid7, int tid7) - { - var oid = (sid7 * 1_000_000) + (tid7 % 1_000_000); - TID = (ushort)oid; - SID = oid >> 16; - } - - public bool E => Version == (int)GameVersion.E; - public bool FRLG => Version is (int)FR or (int)LG; - public bool Pt => (int)GameVersion.Pt == Version; - public bool HGSS => Version is (int)HG or (int)SS; - public bool BW => Version is (int)B or (int)W; - public bool B2W2 => Version is (int)B2 or (int)W2; - public bool XY => Version is (int)X or (int)Y; - public bool AO => Version is (int)AS or (int)OR; - public bool SM => Version is (int)SN or (int)MN; - public bool USUM => Version is (int)US or (int)UM; - public bool GO => Version is (int)GameVersion.GO; - public bool VC1 => Version is >= (int)RD and <= (int)YW; - public bool VC2 => Version is >= (int)GD and <= (int)C; - public bool LGPE => Version is (int)GP or (int)GE; - public bool SWSH => Version is (int)SW or (int)SH; - public virtual bool BDSP => Version is (int)BD or (int)SP; - public virtual bool LA => Version is (int)PLA; - - public bool GO_LGPE => GO && Met_Location == Locations.GO7; - public bool GO_HOME => GO && Met_Location == Locations.GO8; - public bool VC => VC1 || VC2; - public bool GG => LGPE || GO_LGPE; - public bool Gen8 => Version is >= 44 and <= 49 || GO_HOME; - public bool Gen7 => Version is >= 30 and <= 33 || GG; - public bool Gen6 => Version is >= 24 and <= 29; - public bool Gen5 => Version is >= 20 and <= 23; - public bool Gen4 => Version is >= 7 and <= 12 and not 9; - public bool Gen3 => Version is >= 1 and <= 5 or 15; - public bool Gen2 => Version == (int)GSC; // Fixed value set by the Gen2 PKM classes - public bool Gen1 => Version == (int)RBY; // Fixed value set by the Gen1 PKM classes - public bool GenU => Generation <= 0; - - public int Generation - { - get - { - if (Gen8) return 8; - if (Gen7) return 7; - if (Gen6) return 6; - if (Gen5) return 5; - if (Gen4) return 4; - if (Gen3) return 3; - if (Gen2) return Format; // 2 - if (Gen1) return Format; // 1 - if (VC1) return 1; - if (VC2) return 2; - return -1; - } - } - - public int DebutGeneration => Legal.GetDebutGeneration(Species); - public bool PKRS_Infected { get => PKRS_Strain != 0; set => PKRS_Strain = value ? Math.Max(PKRS_Strain, 1) : 0; } - - public bool PKRS_Cured - { - get => PKRS_Days == 0 && PKRS_Strain > 0; - set - { - PKRS_Days = value ? 0 : 1; - PKRS_Infected = true; - } - } - - public int CurrentLevel { get => Experience.GetLevel(EXP, PersonalInfo.EXPGrowth); set => EXP = Experience.GetEXP(Stat_Level = value, PersonalInfo.EXPGrowth); } - public int IVTotal => IV_HP + IV_ATK + IV_DEF + IV_SPA + IV_SPD + IV_SPE; - public int EVTotal => EV_HP + EV_ATK + EV_DEF + EV_SPA + EV_SPD + EV_SPE; - public int MaximumIV => Math.Max(Math.Max(Math.Max(Math.Max(Math.Max(IV_HP, IV_ATK), IV_DEF), IV_SPA), IV_SPD), IV_SPE); - - public int FlawlessIVCount - { - get - { - int max = MaxIV; - int ctr = 0; - if (IV_HP == max) ++ctr; - if (IV_ATK == max) ++ctr; - if (IV_DEF == max) ++ctr; - if (IV_SPA == max) ++ctr; - if (IV_SPD == max) ++ctr; - if (IV_SPE == max) ++ctr; - return ctr; - } - } - - public string FileName => $"{FileNameWithoutExtension}.{Extension}"; - - public string FileNameWithoutExtension => EntityFileNamer.GetName(this); - - public int[] IVs - { - get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; - set => SetIVs(value); - } - - public void GetIVs(Span value) - { - if (value.Length != 6) - return; - value[0] = IV_HP; - value[1] = IV_ATK; - value[2] = IV_DEF; - value[3] = IV_SPE; - value[4] = IV_SPA; - value[5] = IV_SPD; - } - - public void SetIVs(ReadOnlySpan value) - { - if (value.Length != 6) - return; - IV_HP = value[0]; - IV_ATK = value[1]; - IV_DEF = value[2]; - IV_SPE = value[3]; - IV_SPA = value[4]; - IV_SPD = value[5]; - } - - public int[] EVs - { - get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; - set - { - if (value.Length != 6) - return; - EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; - EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; - } - } - - public void GetEVs(Span value) - { - if (value.Length != 6) - return; - value[0] = EV_HP; - value[1] = EV_ATK; - value[2] = EV_DEF; - value[3] = EV_SPE; - value[4] = EV_SPA; - value[5] = EV_SPD; - } - - public void SetEVs(ReadOnlySpan value) - { - if (value.Length != 6) - return; - EV_HP = value[0]; - EV_ATK = value[1]; - EV_DEF = value[2]; - EV_SPE = value[3]; - EV_SPA = value[4]; - EV_SPD = value[5]; - } - - public int[] Stats - { - get => new[] { Stat_HPCurrent, Stat_ATK, Stat_DEF, Stat_SPE, Stat_SPA, Stat_SPD }; - set - { - if (value.Length != 6) - return; - Stat_HPCurrent = value[0]; Stat_ATK = value[1]; Stat_DEF = value[2]; - Stat_SPE = value[3]; Stat_SPA = value[4]; Stat_SPD = value[5]; - } - } - - public int[] Moves - { - get => new[] { Move1, Move2, Move3, Move4 }; - set => SetMoves(value.AsSpan()); - } - - public void PushMove(int move) - { - if (move == 0 || (uint)move >= MaxMoveID || HasMove(move)) - return; - - var ct = MoveCount; - if (ct == 4) - ct = 0; - SetMove(ct, move); - HealPPIndex(ct); - } - - public int MoveCount => Convert.ToInt32(Move1 != 0) + Convert.ToInt32(Move2 != 0) + Convert.ToInt32(Move3 != 0) + Convert.ToInt32(Move4 != 0); - - public void GetMoves(Span value) - { - value[3] = Move4; - value[2] = Move3; - value[1] = Move2; - value[0] = Move1; - } - - public void SetMoves(ReadOnlySpan value) - { - Move1 = value.Length > 0 ? value[0] : 0; - Move2 = value.Length > 1 ? value[1] : 0; - Move3 = value.Length > 2 ? value[2] : 0; - Move4 = value.Length > 3 ? value[3] : 0; - } - - public int[] RelearnMoves - { - get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; - set => SetRelearnMoves(value); - } - - public void SetRelearnMoves(IReadOnlyList value) - { - RelearnMove1 = value.Count > 0 ? value[0] : 0; - RelearnMove2 = value.Count > 1 ? value[1] : 0; - RelearnMove3 = value.Count > 2 ? value[2] : 0; - RelearnMove4 = value.Count > 3 ? value[3] : 0; - } - - public int PIDAbility - { - get - { - if (Generation > 5 || Format > 5) - return -1; - - if (Version == (int) CXD) - return PersonalInfo.GetAbilityIndex(Ability); // Can mismatch; not tied to PID - return (int)((Gen5 ? PID >> 16 : PID) & 1); - } - } - - public abstract int MarkingCount { get; } - public abstract int GetMarking(int index); - public abstract void SetMarking(int index, int value); - - private int HPBitValPower => ((IV_HP & 2) >> 1) | ((IV_ATK & 2) >> 0) | ((IV_DEF & 2) << 1) | ((IV_SPE & 2) << 2) | ((IV_SPA & 2) << 3) | ((IV_SPD & 2) << 4); - public virtual int HPPower => Format < 6 ? ((40 * HPBitValPower) / 63) + 30 : 60; - - private int HPBitValType => ((IV_HP & 1) >> 0) | ((IV_ATK & 1) << 1) | ((IV_DEF & 1) << 2) | ((IV_SPE & 1) << 3) | ((IV_SPA & 1) << 4) | ((IV_SPD & 1) << 5); - - public virtual int HPType - { - get => 15 * HPBitValType / 63; - set - { - var arr = HiddenPower.DefaultLowBits; - var bits = (uint)value >= arr.Length ? 0 : arr[value]; - IV_HP = (IV_HP & ~1) + ((bits >> 0) & 1); - IV_ATK = (IV_ATK & ~1) + ((bits >> 1) & 1); - IV_DEF = (IV_DEF & ~1) + ((bits >> 2) & 1); - IV_SPE = (IV_SPE & ~1) + ((bits >> 3) & 1); - IV_SPA = (IV_SPA & ~1) + ((bits >> 4) & 1); - IV_SPD = (IV_SPD & ~1) + ((bits >> 5) & 1); - } - } - - // Misc Egg Facts - public virtual bool WasEgg => IsEgg || Egg_Day != 0; - public bool WasTradedEgg => Egg_Location == GetTradedEggLocation(); - public bool IsTradedEgg => Met_Location == GetTradedEggLocation(); - private int GetTradedEggLocation() => Locations.TradedEggLocation(Generation, (GameVersion)Version); - - public virtual bool IsUntraded => false; - public virtual bool IsNative => Generation == Format; - public bool IsOriginValid => Species <= MaxSpeciesID; - - /// - /// Checks if the could inhabit a set of games. - /// - /// Set of games. - /// - /// True if could inhabit, False if not. - public bool InhabitedGeneration(int generation, int species = -1) - { - if (species < 0) - species = Species; - - var format = Format; - if (format == generation) - return true; - - if (!IsOriginValid) - return false; - - // Sanity Check Species ID - if (species > Legal.GetMaxSpeciesOrigin(generation) && !EvolutionLegality.GetFutureGenEvolutions(generation).Contains(species)) - return false; - - // Trade generation 1 -> 2 - if (format == 2 && generation == 1 && !Korean) - return true; - - // Trade generation 2 -> 1 - if (format == 1 && generation == 2 && ParseSettings.AllowGen1Tradeback) - return true; - - if (format < generation) - return false; // Future - - int gen = Generation; - return generation switch - { - 1 => format == 1 || VC, // species compat checked via sanity above - 2 => format == 2 || VC, - 3 => Gen3, - 4 => gen is >= 3 and <= 4, - 5 => gen is >= 3 and <= 5, - 6 => gen is >= 3 and <= 6, - 7 => gen is >= 3 and <= 7 || VC, - 8 => gen is >= 3 and <= 8 || VC, - _ => false, - }; - } - - /// - /// Checks if the PKM has its original met location. - /// - /// Returns false if the Met Location has been overwritten via generational transfer. - public virtual bool HasOriginalMetLocation => !(Format < 3 || VC || (Generation <= 4 && Format != Generation)); - - /// - /// Checks if the current is valid. - /// - /// True if valid, False if invalid. - public virtual bool IsGenderValid() - { - int gender = Gender; - int gv = PersonalInfo.Gender; - if (gv == PersonalInfo.RatioMagicGenderless) - return gender == 2; - if (gv == PersonalInfo.RatioMagicFemale) - return gender == 1; - if (gv == PersonalInfo.RatioMagicMale) - return gender == 0; - - int gen = Generation; - if (gen is not (3 or 4 or 5)) - return gender == (gender & 1); - - return gender == EntityGender.GetFromPIDAndRatio(PID, gv); - } - - /// - /// Updates the checksum of the . - /// - public abstract void RefreshChecksum(); - - /// - /// Indicates if the data has a proper checksum. - /// - /// Returns true for structures that do not compute or contain a checksum in the structure. - public abstract bool ChecksumValid { get; } - - /// - /// Reorders moves and fixes PP if necessary. - /// - public void FixMoves() - { - ReorderMoves(); - - if (Move1 == 0) Move1_PP = Move1_PPUps = 0; - if (Move2 == 0) Move2_PP = Move2_PPUps = 0; - if (Move3 == 0) Move3_PP = Move3_PPUps = 0; - if (Move4 == 0) Move4_PP = Move4_PPUps = 0; - } - - /// - /// Reorders moves to put Empty entries last. - /// - private void ReorderMoves() - { - if (Move1 == 0 && Move2 != 0) - { - Move1 = Move2; - Move1_PP = Move2_PP; - Move1_PPUps = Move2_PPUps; - Move2 = 0; - } - if (Move2 == 0 && Move3 != 0) - { - Move2 = Move3; - Move2_PP = Move3_PP; - Move2_PPUps = Move3_PPUps; - Move3 = 0; - } - if (Move3 == 0 && Move4 != 0) - { - Move3 = Move4; - Move3_PP = Move4_PP; - Move3_PPUps = Move4_PPUps; - Move4 = 0; - } - } - - /// - /// Applies the desired Ability option. - /// - /// Ability Number (0/1/2) - public virtual void RefreshAbility(int n) - { - AbilityNumber = 1 << n; - var abilities = PersonalInfo.Abilities; - if ((uint)n < abilities.Count) - Ability = abilities[n]; - } - - /// - /// Gets the IV Judge Rating value. - /// - /// IV Judge scales his response 0 (worst) to 3 (best). - public int PotentialRating => IVTotal switch - { - <= 90 => 0, - <= 120 => 1, - <= 150 => 2, - _ => 3, - }; - - /// - /// Gets the current Battle Stats. - /// - /// entry containing Base Stat Info - /// Battle Stats (H/A/B/S/C/D) - public ushort[] GetStats(PersonalInfo p) - { - ushort[] stats = new ushort[6]; - LoadStats(p, stats); - return stats; - } - - public virtual void LoadStats(PersonalInfo p, Span stats) - { - int level = CurrentLevel; // recalculate instead of checking Stat_Level - if (this is IHyperTrain t) - LoadStats(stats, p, t, level); else - LoadStats(stats, p, level); - - // Account for nature - NatureAmp.ModifyStatsForNature(stats, StatNature); + { + // Clear the Met Date. + // If code tries to access MetDate again, null will be returned. + Met_Year = 0; + Met_Month = 0; + Met_Day = 0; + } } + } - private void LoadStats(Span stats, PersonalInfo p, IHyperTrain t, int level) + public virtual int Egg_Year { get => 0; set { } } + public virtual int Egg_Month { get => 0; set { } } + public virtual int Egg_Day { get => 0; set { } } + + /// + /// The date a Pokémon was met as an egg. + /// + /// + /// A DateTime representing the date the Pokémon was met as an egg. + /// Returns null if either the format does not support dates or the stored date is invalid. + /// + /// Not all types support the property. In these cases, this property will return null. + /// If null is assigned to this property, it will be cleared. + /// + public DateTime? EggMetDate + { + get { - stats[0] = (ushort)(p.HP == 1 ? 1 : (((t.HT_HP ? 31 : IV_HP) + (2 * p.HP) + (EV_HP / 4) + 100) * level / 100) + 10); - stats[1] = (ushort)((((t.HT_ATK ? 31 : IV_ATK) + (2 * p.ATK) + (EV_ATK / 4)) * level / 100) + 5); - stats[2] = (ushort)((((t.HT_DEF ? 31 : IV_DEF) + (2 * p.DEF) + (EV_DEF / 4)) * level / 100) + 5); - stats[4] = (ushort)((((t.HT_SPA ? 31 : IV_SPA) + (2 * p.SPA) + (EV_SPA / 4)) * level / 100) + 5); - stats[5] = (ushort)((((t.HT_SPD ? 31 : IV_SPD) + (2 * p.SPD) + (EV_SPD / 4)) * level / 100) + 5); - stats[3] = (ushort)((((t.HT_SPE ? 31 : IV_SPE) + (2 * p.SPE) + (EV_SPE / 4)) * level / 100) + 5); + // Check to see if date is valid + if (!DateUtil.IsDateValid(2000 + Egg_Year, Egg_Month, Egg_Day)) + return null; + return new DateTime(2000 + Egg_Year, Egg_Month, Egg_Day); } - - private void LoadStats(Span stats, PersonalInfo p, int level) + set { - stats[0] = (ushort)(p.HP == 1 ? 1 : ((IV_HP + (2 * p.HP) + (EV_HP / 4) + 100) * level / 100) + 10); - stats[1] = (ushort)(((IV_ATK + (2 * p.ATK) + (EV_ATK / 4)) * level / 100) + 5); - stats[2] = (ushort)(((IV_DEF + (2 * p.DEF) + (EV_DEF / 4)) * level / 100) + 5); - stats[4] = (ushort)(((IV_SPA + (2 * p.SPA) + (EV_SPA / 4)) * level / 100) + 5); - stats[5] = (ushort)(((IV_SPD + (2 * p.SPD) + (EV_SPD / 4)) * level / 100) + 5); - stats[3] = (ushort)(((IV_SPE + (2 * p.SPE) + (EV_SPE / 4)) * level / 100) + 5); + if (value.HasValue) + { + // Only update the properties if a value is provided. + Egg_Year = value.Value.Year - 2000; + Egg_Month = value.Value.Month; + Egg_Day = value.Value.Day; + } + else + { + // Clear the Met Date. + // If code tries to access MetDate again, null will be returned. + Egg_Year = 0; + Egg_Month = 0; + Egg_Day = 0; + } } + } - /// - /// Applies the specified stats to the . - /// - /// Battle Stats (H/A/B/S/C/D) - public void SetStats(ReadOnlySpan stats) + public virtual int RelearnMove1 { get => 0; set { } } + public virtual int RelearnMove2 { get => 0; set { } } + public virtual int RelearnMove3 { get => 0; set { } } + public virtual int RelearnMove4 { get => 0; set { } } + + // Exposed but not Present in all + public abstract int CurrentHandler { get; set; } + + // Maximums + public abstract int MaxMoveID { get; } + public abstract int MaxSpeciesID { get; } + public abstract int MaxItemID { get; } + public abstract int MaxAbilityID { get; } + public abstract int MaxBallID { get; } + public abstract int MaxGameID { get; } + public virtual int MinGameID => 0; + public abstract int MaxIV { get; } + public abstract int MaxEV { get; } + public abstract int OTLength { get; } + public abstract int NickLength { get; } + + // Derived + public int SpecForm { get => Species + (Form << 11); set { Species = value & 0x7FF; Form = value >> 11; } } + public virtual int SpriteItem => HeldItem; + public virtual bool IsShiny => TSV == PSV; + public int TrainerID7 { get => (int)((uint)(TID | (SID << 16)) % 1000000); set => SetID7(TrainerSID7, value); } + public int TrainerSID7 { get => (int)((uint)(TID | (SID << 16)) / 1000000); set => SetID7(value, TrainerID7); } + + public uint ShinyXor + { + get { - Stat_HPMax = Stat_HPCurrent = stats[0]; - Stat_ATK = stats[1]; - Stat_DEF = stats[2]; - Stat_SPE = stats[3]; - Stat_SPA = stats[4]; - Stat_SPD = stats[5]; + var pid = PID; + var upper = (pid >> 16) ^ (uint)SID; + return (pid & 0xFFFF) ^ (uint)TID ^ upper; } + } - /// - /// Indicates if Party Stats are present. False if not initialized (from stored format). - /// - public bool PartyStatsPresent => Stat_HPMax != 0; + public int DisplayTID + { + get => Generation >= 7 ? TrainerID7 : TID; + set { if (Generation >= 7) TrainerID7 = value; else TID = value; } + } - /// - /// Clears any status condition and refreshes the stats. - /// - public void ResetPartyStats() + public int DisplaySID + { + get => Generation >= 7 ? TrainerSID7 : SID; + set { if (Generation >= 7) TrainerSID7 = value; else SID = value; } + } + + private void SetID7(int sid7, int tid7) + { + var oid = (sid7 * 1_000_000) + (tid7 % 1_000_000); + TID = (ushort)oid; + SID = oid >> 16; + } + + public bool E => Version == (int)GameVersion.E; + public bool FRLG => Version is (int)FR or (int)LG; + public bool Pt => (int)GameVersion.Pt == Version; + public bool HGSS => Version is (int)HG or (int)SS; + public bool BW => Version is (int)B or (int)W; + public bool B2W2 => Version is (int)B2 or (int)W2; + public bool XY => Version is (int)X or (int)Y; + public bool AO => Version is (int)AS or (int)OR; + public bool SM => Version is (int)SN or (int)MN; + public bool USUM => Version is (int)US or (int)UM; + public bool GO => Version is (int)GameVersion.GO; + public bool VC1 => Version is >= (int)RD and <= (int)YW; + public bool VC2 => Version is >= (int)GD and <= (int)C; + public bool LGPE => Version is (int)GP or (int)GE; + public bool SWSH => Version is (int)SW or (int)SH; + public virtual bool BDSP => Version is (int)BD or (int)SP; + public virtual bool LA => Version is (int)PLA; + + public bool GO_LGPE => GO && Met_Location == Locations.GO7; + public bool GO_HOME => GO && Met_Location == Locations.GO8; + public bool VC => VC1 || VC2; + public bool GG => LGPE || GO_LGPE; + public bool Gen8 => Version is >= 44 and <= 49 || GO_HOME; + public bool Gen7 => Version is >= 30 and <= 33 || GG; + public bool Gen6 => Version is >= 24 and <= 29; + public bool Gen5 => Version is >= 20 and <= 23; + public bool Gen4 => Version is (>= 7 and <= 12) and not 9; + public bool Gen3 => Version is (>= 1 and <= 5) or 15; + public bool Gen2 => Version == (int)GSC; // Fixed value set by the Gen2 PKM classes + public bool Gen1 => Version == (int)RBY; // Fixed value set by the Gen1 PKM classes + public bool GenU => Generation <= 0; + + public int Generation + { + get { - Span stats = stackalloc ushort[6]; - LoadStats(PersonalInfo, stats); - SetStats(stats); - Stat_Level = CurrentLevel; - Status_Condition = 0; + if (Gen8) return 8; + if (Gen7) return 7; + if (Gen6) return 6; + if (Gen5) return 5; + if (Gen4) return 4; + if (Gen3) return 3; + if (Gen2) return Format; // 2 + if (Gen1) return Format; // 1 + if (VC1) return 1; + if (VC2) return 2; + return -1; } + } - public void Heal() + public int DebutGeneration => Legal.GetDebutGeneration(Species); + public bool PKRS_Infected { get => PKRS_Strain != 0; set => PKRS_Strain = value ? Math.Max(PKRS_Strain, 1) : 0; } + + public bool PKRS_Cured + { + get => PKRS_Days == 0 && PKRS_Strain > 0; + set { - ResetPartyStats(); - HealPP(); + PKRS_Days = value ? 0 : 1; + PKRS_Infected = true; } + } - /// - /// Restores PP to maximum based on the current PP Ups for each move. - /// - public void HealPP() + public int CurrentLevel { get => Experience.GetLevel(EXP, PersonalInfo.EXPGrowth); set => EXP = Experience.GetEXP(Stat_Level = value, PersonalInfo.EXPGrowth); } + public int IVTotal => IV_HP + IV_ATK + IV_DEF + IV_SPA + IV_SPD + IV_SPE; + public int EVTotal => EV_HP + EV_ATK + EV_DEF + EV_SPA + EV_SPD + EV_SPE; + public int MaximumIV => Math.Max(Math.Max(Math.Max(Math.Max(Math.Max(IV_HP, IV_ATK), IV_DEF), IV_SPA), IV_SPD), IV_SPE); + + public int FlawlessIVCount + { + get { - Move1_PP = GetMovePP(Move1, Move1_PPUps); - Move2_PP = GetMovePP(Move2, Move2_PPUps); - Move3_PP = GetMovePP(Move3, Move3_PPUps); - Move4_PP = GetMovePP(Move4, Move4_PPUps); + int max = MaxIV; + int ctr = 0; + if (IV_HP == max) ++ctr; + if (IV_ATK == max) ++ctr; + if (IV_DEF == max) ++ctr; + if (IV_SPA == max) ++ctr; + if (IV_SPD == max) ++ctr; + if (IV_SPE == max) ++ctr; + return ctr; } + } - public int HealPPIndex(int index) => index switch - { - 0 => Move1_PP = GetMovePP(Move1, Move1_PPUps), - 1 => Move2_PP = GetMovePP(Move2, Move2_PPUps), - 2 => Move3_PP = GetMovePP(Move3, Move3_PPUps), - 3 => Move4_PP = GetMovePP(Move4, Move4_PPUps), - _ => throw new ArgumentOutOfRangeException(nameof(index)), - }; + public string FileName => $"{FileNameWithoutExtension}.{Extension}"; - /// - /// Enforces that Party Stat values are present. - /// - /// True if stats were refreshed, false if stats were already present. - public bool ForcePartyData() + public string FileNameWithoutExtension => EntityFileNamer.GetName(this); + + public int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set => SetIVs(value); + } + + public void GetIVs(Span value) + { + if (value.Length != 6) + return; + value[0] = IV_HP; + value[1] = IV_ATK; + value[2] = IV_DEF; + value[3] = IV_SPE; + value[4] = IV_SPA; + value[5] = IV_SPD; + } + + public void SetIVs(ReadOnlySpan value) + { + if (value.Length != 6) + return; + IV_HP = value[0]; + IV_ATK = value[1]; + IV_DEF = value[2]; + IV_SPE = value[3]; + IV_SPA = value[4]; + IV_SPD = value[5]; + } + + public int[] EVs + { + get => new[] { EV_HP, EV_ATK, EV_DEF, EV_SPE, EV_SPA, EV_SPD }; + set { - if (PartyStatsPresent) - return false; - ResetPartyStats(); + if (value.Length != 6) + return; + EV_HP = value[0]; EV_ATK = value[1]; EV_DEF = value[2]; + EV_SPE = value[3]; EV_SPA = value[4]; EV_SPD = value[5]; + } + } + + public void GetEVs(Span value) + { + if (value.Length != 6) + return; + value[0] = EV_HP; + value[1] = EV_ATK; + value[2] = EV_DEF; + value[3] = EV_SPE; + value[4] = EV_SPA; + value[5] = EV_SPD; + } + + public void SetEVs(ReadOnlySpan value) + { + if (value.Length != 6) + return; + EV_HP = value[0]; + EV_ATK = value[1]; + EV_DEF = value[2]; + EV_SPE = value[3]; + EV_SPA = value[4]; + EV_SPD = value[5]; + } + + public int[] Stats + { + get => new[] { Stat_HPCurrent, Stat_ATK, Stat_DEF, Stat_SPE, Stat_SPA, Stat_SPD }; + set + { + if (value.Length != 6) + return; + Stat_HPCurrent = value[0]; Stat_ATK = value[1]; Stat_DEF = value[2]; + Stat_SPE = value[3]; Stat_SPA = value[4]; Stat_SPD = value[5]; + } + } + + public int[] Moves + { + get => new[] { Move1, Move2, Move3, Move4 }; + set => SetMoves(value.AsSpan()); + } + + public void PushMove(int move) + { + if (move == 0 || (uint)move >= MaxMoveID || HasMove(move)) + return; + + var ct = MoveCount; + if (ct == 4) + ct = 0; + SetMove(ct, move); + HealPPIndex(ct); + } + + public int MoveCount => Convert.ToInt32(Move1 != 0) + Convert.ToInt32(Move2 != 0) + Convert.ToInt32(Move3 != 0) + Convert.ToInt32(Move4 != 0); + + public void GetMoves(Span value) + { + value[3] = Move4; + value[2] = Move3; + value[1] = Move2; + value[0] = Move1; + } + + public void SetMoves(ReadOnlySpan value) + { + Move1 = value.Length > 0 ? value[0] : 0; + Move2 = value.Length > 1 ? value[1] : 0; + Move3 = value.Length > 2 ? value[2] : 0; + Move4 = value.Length > 3 ? value[3] : 0; + } + + public int[] RelearnMoves + { + get => new[] { RelearnMove1, RelearnMove2, RelearnMove3, RelearnMove4 }; + set => SetRelearnMoves(value); + } + + public void SetRelearnMoves(IReadOnlyList value) + { + RelearnMove1 = value.Count > 0 ? value[0] : 0; + RelearnMove2 = value.Count > 1 ? value[1] : 0; + RelearnMove3 = value.Count > 2 ? value[2] : 0; + RelearnMove4 = value.Count > 3 ? value[3] : 0; + } + + public int PIDAbility + { + get + { + if (Generation > 5 || Format > 5) + return -1; + + if (Version == (int) CXD) + return PersonalInfo.GetAbilityIndex(Ability); // Can mismatch; not tied to PID + return (int)((Gen5 ? PID >> 16 : PID) & 1); + } + } + + public abstract int MarkingCount { get; } + public abstract int GetMarking(int index); + public abstract void SetMarking(int index, int value); + + private int HPBitValPower => ((IV_HP & 2) >> 1) | ((IV_ATK & 2) >> 0) | ((IV_DEF & 2) << 1) | ((IV_SPE & 2) << 2) | ((IV_SPA & 2) << 3) | ((IV_SPD & 2) << 4); + public virtual int HPPower => Format < 6 ? ((40 * HPBitValPower) / 63) + 30 : 60; + + private int HPBitValType => ((IV_HP & 1) >> 0) | ((IV_ATK & 1) << 1) | ((IV_DEF & 1) << 2) | ((IV_SPE & 1) << 3) | ((IV_SPA & 1) << 4) | ((IV_SPD & 1) << 5); + + public virtual int HPType + { + get => 15 * HPBitValType / 63; + set + { + var arr = HiddenPower.DefaultLowBits; + var bits = (uint)value >= arr.Length ? 0 : arr[value]; + IV_HP = (IV_HP & ~1) + ((bits >> 0) & 1); + IV_ATK = (IV_ATK & ~1) + ((bits >> 1) & 1); + IV_DEF = (IV_DEF & ~1) + ((bits >> 2) & 1); + IV_SPE = (IV_SPE & ~1) + ((bits >> 3) & 1); + IV_SPA = (IV_SPA & ~1) + ((bits >> 4) & 1); + IV_SPD = (IV_SPD & ~1) + ((bits >> 5) & 1); + } + } + + // Misc Egg Facts + public virtual bool WasEgg => IsEgg || Egg_Day != 0; + public bool WasTradedEgg => Egg_Location == GetTradedEggLocation(); + public bool IsTradedEgg => Met_Location == GetTradedEggLocation(); + private int GetTradedEggLocation() => Locations.TradedEggLocation(Generation, (GameVersion)Version); + + public virtual bool IsUntraded => false; + public virtual bool IsNative => Generation == Format; + public bool IsOriginValid => Species <= MaxSpeciesID; + + /// + /// Checks if the could inhabit a set of games. + /// + /// Set of games. + /// + /// True if could inhabit, False if not. + public bool InhabitedGeneration(int generation, int species = -1) + { + if (species < 0) + species = Species; + + var format = Format; + if (format == generation) return true; - } - /// - /// Checks if the can hold its . - /// - /// Items that the can hold. - /// True/False if the can hold its . - public virtual bool CanHoldItem(IReadOnlyList valid) => valid.Contains((ushort)HeldItem); + if (!IsOriginValid) + return false; - /// - /// Deep clones the object. The clone will not have any shared resources with the source. - /// - /// Cloned object - public abstract PKM Clone(); + // Sanity Check Species ID + if (species > Legal.GetMaxSpeciesOrigin(generation) && !EvolutionLegality.GetFutureGenEvolutions(generation).Contains(species)) + return false; - /// - /// Sets Link Trade data for an . - /// - /// Day the was traded. - /// Month the was traded. - /// Day the was traded. - /// Link Trade location value. - protected void SetLinkTradeEgg(int day, int month, int y, int location) + // Trade generation 1 -> 2 + if (format == 2 && generation == 1 && !Korean) + return true; + + // Trade generation 2 -> 1 + if (format == 1 && generation == 2 && ParseSettings.AllowGen1Tradeback) + return true; + + if (format < generation) + return false; // Future + + int gen = Generation; + return generation switch { - Met_Day = day; - Met_Month = month; - Met_Year = y - 2000; - Met_Location = location; - } - - /// - /// Gets the PP of a Move ID with consideration of the amount of PP Ups applied. - /// - /// Move ID - /// PP Ups count - /// Current PP for the move. - public virtual int GetMovePP(int move, int ppUpCount) => GetBasePP(move) * (5 + ppUpCount) / 5; - - /// - /// Gets the base PP of a move ID depending on the 's format. - /// - /// Move ID - /// Amount of PP the move has by default (no PP Ups). - private int GetBasePP(int move) => MoveInfo.GetPP(Context, move); - - /// - /// Applies a shiny to the . - /// - /// - /// If a originated in a generation prior to Generation 6, the is updated. - /// If a is in the format, it will update the instead. - /// - public virtual void SetShiny() - { - var rnd = Util.Rand; - do { PID = EntityPID.GetRandomPID(rnd, Species, Gender, Version, Nature, Form, PID); } - while (!IsShiny); - if (Format >= 6 && (Gen3 || Gen4 || Gen5)) - EncryptionConstant = PID; - } - - /// - /// Applies a shiny to the . - /// - public void SetShinySID(Shiny shiny = Shiny.Random) - { - if (IsShiny && shiny.IsValid(this)) - return; - - var xor = TID ^ (PID >> 16) ^ (PID & 0xFFFF); - var bits = shiny switch - { - Shiny.AlwaysSquare => 0, - Shiny.AlwaysStar => 1, - _ => Util.Rand.Next(8), - }; - - SID = (int)xor ^ bits; - } - - /// - /// Applies a to the according to the specified . - /// - /// to apply - /// - /// If a originated in a generation prior to Generation 6, the is updated. - /// - public void SetPIDGender(int gender) - { - var rnd = Util.Rand; - do PID = EntityPID.GetRandomPID(rnd, Species, gender, Version, Nature, Form, PID); - while (IsShiny); - if (Format >= 6 && (Gen3 || Gen4 || Gen5)) - EncryptionConstant = PID; - } - - /// - /// Applies a to the according to the specified . - /// - /// to apply - /// - /// If a originated in a generation prior to Generation 6, the is updated. - /// - public void SetPIDNature(int nature) - { - var rnd = Util.Rand; - do PID = EntityPID.GetRandomPID(rnd, Species, Gender, Version, nature, Form, PID); - while (IsShiny); - if (Format >= 6 && (Gen3 || Gen4 || Gen5)) - EncryptionConstant = PID; - } - - /// - /// Applies a to the according to the specified . - /// - /// to apply - /// - /// This method should only be used for Unown originating in Generation 3 games. - /// If a originated in a generation prior to Generation 6, the is updated. - /// - public void SetPIDUnown3(int form) - { - var rnd = Util.Rand; - do PID = rnd.Rand32(); while (EntityPID.GetUnownForm3(PID) != form); - if (Format >= 6 && (Gen3 || Gen4 || Gen5)) - EncryptionConstant = PID; - } - - /// - /// Randomizes the IVs within game constraints. - /// - /// Count of flawless IVs to set. If none provided, a count will be detected. - /// Randomized IVs if desired. - public int[] SetRandomIVs(int? flawless = null) - { - if (Version == (int)GameVersion.GO && flawless != 6) - return SetRandomIVsGO(); - - int[] ivs = new int[6]; - var rnd = Util.Rand; - for (int i = 0; i < 6; i++) - ivs[i] = rnd.Next(MaxIV + 1); - - int count = flawless ?? GetFlawlessIVCount(); - if (count != 0) - { - for (int i = 0; i < count; i++) - ivs[i] = MaxIV; - Util.Shuffle(ivs.AsSpan(), 0, ivs.Length, rnd); // Randomize IV order - } - return IVs = ivs; - } - - public int[] SetRandomIVsGO(int minIV = 0, int maxIV = 15) - { - int[] ivs = new int[6]; - var rnd = Util.Rand; - ivs[0] = (rnd.Next(minIV, maxIV + 1) << 1) | 1; // hp - ivs[1] = ivs[4] = (rnd.Next(minIV, maxIV + 1) << 1) | 1; // attack - ivs[2] = ivs[5] = (rnd.Next(minIV, maxIV + 1) << 1) | 1; // defense - ivs[3] = rnd.Next(MaxIV + 1); // speed - return IVs = ivs; - } - - /// - /// Randomizes the IVs within game constraints. - /// - /// IV template to generate from - /// Count of flawless IVs to set. If none provided, a count will be detected. - /// Randomized IVs if desired. - public int[] SetRandomIVs(ReadOnlySpan template, int? flawless = null) - { - int count = flawless ?? GetFlawlessIVCount(); - int[] ivs = new int[6]; - var rnd = Util.Rand; - do - { - for (int i = 0; i < 6; i++) - ivs[i] = template[i] < 0 ? rnd.Next(MaxIV + 1) : template[i]; - } while (ivs.Count(z => z == MaxIV) < count); - - IVs = ivs; - return ivs; - } - - /// - /// Gets the amount of flawless IVs that the should have. - /// - /// Count of IVs that should be max. - public int GetFlawlessIVCount() - { - if (Generation >= 6 && (Legal.Legends.Contains(Species) || Legal.SubLegends.Contains(Species))) - return 3; - if (XY) - { - if (PersonalInfo.EggGroup1 == 15) // Undiscovered - return 3; - if (Met_Location == 148 && Met_Level == 30) // Friend Safari - return 2; - } - if (VC) - return Species is (int)Core.Species.Mew or (int)Core.Species.Celebi ? 5 : 3; - if (this is IAlpha {IsAlpha: true}) - return 3; - return 0; - } - - /// - /// Applies all shared properties from the current to . - /// - /// that receives property values. - public void TransferPropertiesWithReflection(PKM Destination) - { - // Only transfer declared properties not defined in PKM.cs but in the actual type - var srcType = GetType(); - var destType = Destination.GetType(); - - var srcProperties = ReflectUtil.GetPropertiesCanWritePublicDeclared(srcType); - var destProperties = ReflectUtil.GetPropertiesCanWritePublicDeclared(destType); - while (srcType.BaseType != typeof(PKM)) - srcProperties = srcProperties.Concat(ReflectUtil.GetPropertiesCanWritePublicDeclared(srcType = srcType.BaseType)); - while (destType.BaseType != typeof(PKM)) - destProperties = destProperties.Concat(ReflectUtil.GetPropertiesCanWritePublicDeclared(destType = destType.BaseType)); - - // Transfer properties in the order they are defined in the destination PKM format for best conversion - var shared = destProperties.Intersect(srcProperties); - foreach (string property in shared) - { - if (!BatchEditing.TryGetHasProperty(this, property, out var src)) - continue; - var prop = src.GetValue(this); - if (prop is not (byte[] or null) && BatchEditing.TryGetHasProperty(Destination, property, out var pi)) - ReflectUtil.SetValue(pi, Destination, prop); - } - - // set shared properties for the Gen1/2 base class - if (Destination is GBPKM l) - l.ImportFromFuture(this); - } - - /// - /// Checks if the has the in its current move list. - /// - public bool HasMove(int move) => Move1 == move || Move2 == move || Move3 == move || Move4 == move; - - public int GetMoveIndex(int move) => Move1 == move ? 0 : Move2 == move ? 1 : Move3 == move ? 2 : Move4 == move ? 3 : -1; - - public int GetMove(int index) => index switch - { - 0 => Move1, - 1 => Move2, - 2 => Move3, - 3 => Move4, - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - - public int SetMove(int index, int value) => index switch - { - 0 => Move1 = value, - 1 => Move2 = value, - 2 => Move3 = value, - 3 => Move4 = value, - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - - public int GetRelearnMove(int index) => index switch - { - 0 => RelearnMove1, - 1 => RelearnMove2, - 2 => RelearnMove3, - 3 => RelearnMove4, - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - - public int SetRelearnMove(int index, int value) => index switch - { - 0 => RelearnMove1 = value, - 1 => RelearnMove2 = value, - 2 => RelearnMove3 = value, - 3 => RelearnMove4 = value, - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - - /// - /// Clears moves that a may have, possibly from a future generation. - /// - public void ClearInvalidMoves() - { - uint invalid = 0; - var moves = Moves; - for (var i = 0; i < moves.Length; i++) - { - if (moves[i] <= MaxMoveID) - continue; - - invalid++; - moves[i] = 0; - } - if (invalid == 0) - return; - if (invalid == 4) // no moves remain - { - moves[0] = 1; // Pound - Move1_PP = GetMovePP(1, Move1_PPUps); - } - - Moves = moves; - FixMoves(); - } - - /// - /// Gets one of the based on its index within the array. - /// - /// Index to get - public int GetEV(int index) => index switch - { - 0 => EV_HP, - 1 => EV_ATK, - 2 => EV_DEF, - 3 => EV_SPE, - 4 => EV_SPA, - 5 => EV_SPD, - _ => throw new ArgumentOutOfRangeException(nameof(index)), - }; - - /// - /// Gets one of the based on its index within the array. - /// - /// Index to get - public int GetIV(int index) => index switch - { - 0 => IV_HP, - 1 => IV_ATK, - 2 => IV_DEF, - 3 => IV_SPE, - 4 => IV_SPA, - 5 => IV_SPD, - _ => throw new ArgumentOutOfRangeException(nameof(index)), + 1 => format == 1 || VC, // species compat checked via sanity above + 2 => format == 2 || VC, + 3 => Gen3, + 4 => gen is >= 3 and <= 4, + 5 => gen is >= 3 and <= 5, + 6 => gen is >= 3 and <= 6, + 7 => gen is >= 3 and <= 7 || VC, + 8 => gen is >= 3 and <= 8 || VC, + _ => false, }; } + + /// + /// Checks if the PKM has its original met location. + /// + /// Returns false if the Met Location has been overwritten via generational transfer. + public virtual bool HasOriginalMetLocation => !(Format < 3 || VC || (Generation <= 4 && Format != Generation)); + + /// + /// Checks if the current is valid. + /// + /// True if valid, False if invalid. + public virtual bool IsGenderValid() + { + int gender = Gender; + int gv = PersonalInfo.Gender; + if (gv == PersonalInfo.RatioMagicGenderless) + return gender == 2; + if (gv == PersonalInfo.RatioMagicFemale) + return gender == 1; + if (gv == PersonalInfo.RatioMagicMale) + return gender == 0; + + int gen = Generation; + if (gen is not (3 or 4 or 5)) + return gender == (gender & 1); + + return gender == EntityGender.GetFromPIDAndRatio(PID, gv); + } + + /// + /// Updates the checksum of the . + /// + public abstract void RefreshChecksum(); + + /// + /// Indicates if the data has a proper checksum. + /// + /// Returns true for structures that do not compute or contain a checksum in the structure. + public abstract bool ChecksumValid { get; } + + /// + /// Reorders moves and fixes PP if necessary. + /// + public void FixMoves() + { + ReorderMoves(); + + if (Move1 == 0) Move1_PP = Move1_PPUps = 0; + if (Move2 == 0) Move2_PP = Move2_PPUps = 0; + if (Move3 == 0) Move3_PP = Move3_PPUps = 0; + if (Move4 == 0) Move4_PP = Move4_PPUps = 0; + } + + /// + /// Reorders moves to put Empty entries last. + /// + private void ReorderMoves() + { + if (Move1 == 0 && Move2 != 0) + { + Move1 = Move2; + Move1_PP = Move2_PP; + Move1_PPUps = Move2_PPUps; + Move2 = 0; + } + if (Move2 == 0 && Move3 != 0) + { + Move2 = Move3; + Move2_PP = Move3_PP; + Move2_PPUps = Move3_PPUps; + Move3 = 0; + } + if (Move3 == 0 && Move4 != 0) + { + Move3 = Move4; + Move3_PP = Move4_PP; + Move3_PPUps = Move4_PPUps; + Move4 = 0; + } + } + + /// + /// Applies the desired Ability option. + /// + /// Ability Number (0/1/2) + public virtual void RefreshAbility(int n) + { + AbilityNumber = 1 << n; + var abilities = PersonalInfo.Abilities; + if ((uint)n < abilities.Count) + Ability = abilities[n]; + } + + /// + /// Gets the IV Judge Rating value. + /// + /// IV Judge scales his response 0 (worst) to 3 (best). + public int PotentialRating => IVTotal switch + { + <= 90 => 0, + <= 120 => 1, + <= 150 => 2, + _ => 3, + }; + + /// + /// Gets the current Battle Stats. + /// + /// entry containing Base Stat Info + /// Battle Stats (H/A/B/S/C/D) + public ushort[] GetStats(PersonalInfo p) + { + ushort[] stats = new ushort[6]; + LoadStats(p, stats); + return stats; + } + + public virtual void LoadStats(PersonalInfo p, Span stats) + { + int level = CurrentLevel; // recalculate instead of checking Stat_Level + if (this is IHyperTrain t) + LoadStats(stats, p, t, level); + else + LoadStats(stats, p, level); + + // Account for nature + NatureAmp.ModifyStatsForNature(stats, StatNature); + } + + private void LoadStats(Span stats, PersonalInfo p, IHyperTrain t, int level) + { + stats[0] = (ushort)(p.HP == 1 ? 1 : (((t.HT_HP ? 31 : IV_HP) + (2 * p.HP) + (EV_HP / 4) + 100) * level / 100) + 10); + stats[1] = (ushort)((((t.HT_ATK ? 31 : IV_ATK) + (2 * p.ATK) + (EV_ATK / 4)) * level / 100) + 5); + stats[2] = (ushort)((((t.HT_DEF ? 31 : IV_DEF) + (2 * p.DEF) + (EV_DEF / 4)) * level / 100) + 5); + stats[4] = (ushort)((((t.HT_SPA ? 31 : IV_SPA) + (2 * p.SPA) + (EV_SPA / 4)) * level / 100) + 5); + stats[5] = (ushort)((((t.HT_SPD ? 31 : IV_SPD) + (2 * p.SPD) + (EV_SPD / 4)) * level / 100) + 5); + stats[3] = (ushort)((((t.HT_SPE ? 31 : IV_SPE) + (2 * p.SPE) + (EV_SPE / 4)) * level / 100) + 5); + } + + private void LoadStats(Span stats, PersonalInfo p, int level) + { + stats[0] = (ushort)(p.HP == 1 ? 1 : ((IV_HP + (2 * p.HP) + (EV_HP / 4) + 100) * level / 100) + 10); + stats[1] = (ushort)(((IV_ATK + (2 * p.ATK) + (EV_ATK / 4)) * level / 100) + 5); + stats[2] = (ushort)(((IV_DEF + (2 * p.DEF) + (EV_DEF / 4)) * level / 100) + 5); + stats[4] = (ushort)(((IV_SPA + (2 * p.SPA) + (EV_SPA / 4)) * level / 100) + 5); + stats[5] = (ushort)(((IV_SPD + (2 * p.SPD) + (EV_SPD / 4)) * level / 100) + 5); + stats[3] = (ushort)(((IV_SPE + (2 * p.SPE) + (EV_SPE / 4)) * level / 100) + 5); + } + + /// + /// Applies the specified stats to the . + /// + /// Battle Stats (H/A/B/S/C/D) + public void SetStats(ReadOnlySpan stats) + { + Stat_HPMax = Stat_HPCurrent = stats[0]; + Stat_ATK = stats[1]; + Stat_DEF = stats[2]; + Stat_SPE = stats[3]; + Stat_SPA = stats[4]; + Stat_SPD = stats[5]; + } + + /// + /// Indicates if Party Stats are present. False if not initialized (from stored format). + /// + public bool PartyStatsPresent => Stat_HPMax != 0; + + /// + /// Clears any status condition and refreshes the stats. + /// + public void ResetPartyStats() + { + Span stats = stackalloc ushort[6]; + LoadStats(PersonalInfo, stats); + SetStats(stats); + Stat_Level = CurrentLevel; + Status_Condition = 0; + } + + public void Heal() + { + ResetPartyStats(); + HealPP(); + } + + /// + /// Restores PP to maximum based on the current PP Ups for each move. + /// + public void HealPP() + { + Move1_PP = GetMovePP(Move1, Move1_PPUps); + Move2_PP = GetMovePP(Move2, Move2_PPUps); + Move3_PP = GetMovePP(Move3, Move3_PPUps); + Move4_PP = GetMovePP(Move4, Move4_PPUps); + } + + public int HealPPIndex(int index) => index switch + { + 0 => Move1_PP = GetMovePP(Move1, Move1_PPUps), + 1 => Move2_PP = GetMovePP(Move2, Move2_PPUps), + 2 => Move3_PP = GetMovePP(Move3, Move3_PPUps), + 3 => Move4_PP = GetMovePP(Move4, Move4_PPUps), + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + /// + /// Enforces that Party Stat values are present. + /// + /// True if stats were refreshed, false if stats were already present. + public bool ForcePartyData() + { + if (PartyStatsPresent) + return false; + ResetPartyStats(); + return true; + } + + /// + /// Checks if the can hold its . + /// + /// Items that the can hold. + /// True/False if the can hold its . + public virtual bool CanHoldItem(IReadOnlyList valid) => valid.Contains((ushort)HeldItem); + + /// + /// Deep clones the object. The clone will not have any shared resources with the source. + /// + /// Cloned object + public abstract PKM Clone(); + + /// + /// Sets Link Trade data for an . + /// + /// Day the was traded. + /// Month the was traded. + /// Day the was traded. + /// Link Trade location value. + protected void SetLinkTradeEgg(int day, int month, int y, int location) + { + Met_Day = day; + Met_Month = month; + Met_Year = y - 2000; + Met_Location = location; + } + + /// + /// Gets the PP of a Move ID with consideration of the amount of PP Ups applied. + /// + /// Move ID + /// PP Ups count + /// Current PP for the move. + public virtual int GetMovePP(int move, int ppUpCount) => GetBasePP(move) * (5 + ppUpCount) / 5; + + /// + /// Gets the base PP of a move ID depending on the 's format. + /// + /// Move ID + /// Amount of PP the move has by default (no PP Ups). + private int GetBasePP(int move) => MoveInfo.GetPP(Context, move); + + /// + /// Applies a shiny to the . + /// + /// + /// If a originated in a generation prior to Generation 6, the is updated. + /// If a is in the format, it will update the instead. + /// + public virtual void SetShiny() + { + var rnd = Util.Rand; + do { PID = EntityPID.GetRandomPID(rnd, Species, Gender, Version, Nature, Form, PID); } + while (!IsShiny); + if (Format >= 6 && (Gen3 || Gen4 || Gen5)) + EncryptionConstant = PID; + } + + /// + /// Applies a shiny to the . + /// + public void SetShinySID(Shiny shiny = Shiny.Random) + { + if (IsShiny && shiny.IsValid(this)) + return; + + var xor = TID ^ (PID >> 16) ^ (PID & 0xFFFF); + var bits = shiny switch + { + Shiny.AlwaysSquare => 0, + Shiny.AlwaysStar => 1, + _ => Util.Rand.Next(8), + }; + + SID = (int)xor ^ bits; + } + + /// + /// Applies a to the according to the specified . + /// + /// to apply + /// + /// If a originated in a generation prior to Generation 6, the is updated. + /// + public void SetPIDGender(int gender) + { + var rnd = Util.Rand; + do PID = EntityPID.GetRandomPID(rnd, Species, gender, Version, Nature, Form, PID); + while (IsShiny); + if (Format >= 6 && (Gen3 || Gen4 || Gen5)) + EncryptionConstant = PID; + } + + /// + /// Applies a to the according to the specified . + /// + /// to apply + /// + /// If a originated in a generation prior to Generation 6, the is updated. + /// + public void SetPIDNature(int nature) + { + var rnd = Util.Rand; + do PID = EntityPID.GetRandomPID(rnd, Species, Gender, Version, nature, Form, PID); + while (IsShiny); + if (Format >= 6 && (Gen3 || Gen4 || Gen5)) + EncryptionConstant = PID; + } + + /// + /// Applies a to the according to the specified . + /// + /// to apply + /// + /// This method should only be used for Unown originating in Generation 3 games. + /// If a originated in a generation prior to Generation 6, the is updated. + /// + public void SetPIDUnown3(int form) + { + var rnd = Util.Rand; + do PID = rnd.Rand32(); while (EntityPID.GetUnownForm3(PID) != form); + if (Format >= 6 && (Gen3 || Gen4 || Gen5)) + EncryptionConstant = PID; + } + + /// + /// Randomizes the IVs within game constraints. + /// + /// Count of flawless IVs to set. If none provided, a count will be detected. + /// Randomized IVs if desired. + public int[] SetRandomIVs(int? flawless = null) + { + if (Version == (int)GameVersion.GO && flawless != 6) + return SetRandomIVsGO(); + + int[] ivs = new int[6]; + var rnd = Util.Rand; + for (int i = 0; i < 6; i++) + ivs[i] = rnd.Next(MaxIV + 1); + + int count = flawless ?? GetFlawlessIVCount(); + if (count != 0) + { + for (int i = 0; i < count; i++) + ivs[i] = MaxIV; + Util.Shuffle(ivs.AsSpan(), 0, ivs.Length, rnd); // Randomize IV order + } + return IVs = ivs; + } + + public int[] SetRandomIVsGO(int minIV = 0, int maxIV = 15) + { + int[] ivs = new int[6]; + var rnd = Util.Rand; + ivs[0] = (rnd.Next(minIV, maxIV + 1) << 1) | 1; // hp + ivs[1] = ivs[4] = (rnd.Next(minIV, maxIV + 1) << 1) | 1; // attack + ivs[2] = ivs[5] = (rnd.Next(minIV, maxIV + 1) << 1) | 1; // defense + ivs[3] = rnd.Next(MaxIV + 1); // speed + return IVs = ivs; + } + + /// + /// Randomizes the IVs within game constraints. + /// + /// IV template to generate from + /// Count of flawless IVs to set. If none provided, a count will be detected. + /// Randomized IVs if desired. + public int[] SetRandomIVs(ReadOnlySpan template, int? flawless = null) + { + int count = flawless ?? GetFlawlessIVCount(); + int[] ivs = new int[6]; + var rnd = Util.Rand; + do + { + for (int i = 0; i < 6; i++) + ivs[i] = template[i] < 0 ? rnd.Next(MaxIV + 1) : template[i]; + } while (ivs.Count(z => z == MaxIV) < count); + + IVs = ivs; + return ivs; + } + + /// + /// Gets the amount of flawless IVs that the should have. + /// + /// Count of IVs that should be max. + public int GetFlawlessIVCount() + { + if (Generation >= 6 && (Legal.Legends.Contains(Species) || Legal.SubLegends.Contains(Species))) + return 3; + if (XY) + { + if (PersonalInfo.EggGroup1 == 15) // Undiscovered + return 3; + if (Met_Location == 148 && Met_Level == 30) // Friend Safari + return 2; + } + if (VC) + return Species is (int)Core.Species.Mew or (int)Core.Species.Celebi ? 5 : 3; + if (this is IAlpha {IsAlpha: true}) + return 3; + return 0; + } + + /// + /// Applies all shared properties from the current to . + /// + /// that receives property values. + public void TransferPropertiesWithReflection(PKM Destination) + { + // Only transfer declared properties not defined in PKM.cs but in the actual type + var srcType = GetType(); + var destType = Destination.GetType(); + + var srcProperties = ReflectUtil.GetPropertiesCanWritePublicDeclared(srcType); + var destProperties = ReflectUtil.GetPropertiesCanWritePublicDeclared(destType); + while (srcType.BaseType != typeof(PKM)) + srcProperties = srcProperties.Concat(ReflectUtil.GetPropertiesCanWritePublicDeclared(srcType = srcType.BaseType)); + while (destType.BaseType != typeof(PKM)) + destProperties = destProperties.Concat(ReflectUtil.GetPropertiesCanWritePublicDeclared(destType = destType.BaseType)); + + // Transfer properties in the order they are defined in the destination PKM format for best conversion + var shared = destProperties.Intersect(srcProperties); + foreach (string property in shared) + { + if (!BatchEditing.TryGetHasProperty(this, property, out var src)) + continue; + var prop = src.GetValue(this); + if (prop is not (byte[] or null) && BatchEditing.TryGetHasProperty(Destination, property, out var pi)) + ReflectUtil.SetValue(pi, Destination, prop); + } + + // set shared properties for the Gen1/2 base class + if (Destination is GBPKM l) + l.ImportFromFuture(this); + } + + /// + /// Checks if the has the in its current move list. + /// + public bool HasMove(int move) => Move1 == move || Move2 == move || Move3 == move || Move4 == move; + + public int GetMoveIndex(int move) => Move1 == move ? 0 : Move2 == move ? 1 : Move3 == move ? 2 : Move4 == move ? 3 : -1; + + public int GetMove(int index) => index switch + { + 0 => Move1, + 1 => Move2, + 2 => Move3, + 3 => Move4, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + + public int SetMove(int index, int value) => index switch + { + 0 => Move1 = value, + 1 => Move2 = value, + 2 => Move3 = value, + 3 => Move4 = value, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + + public int GetRelearnMove(int index) => index switch + { + 0 => RelearnMove1, + 1 => RelearnMove2, + 2 => RelearnMove3, + 3 => RelearnMove4, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + + public int SetRelearnMove(int index, int value) => index switch + { + 0 => RelearnMove1 = value, + 1 => RelearnMove2 = value, + 2 => RelearnMove3 = value, + 3 => RelearnMove4 = value, + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + + /// + /// Clears moves that a may have, possibly from a future generation. + /// + public void ClearInvalidMoves() + { + uint invalid = 0; + var moves = Moves; + for (var i = 0; i < moves.Length; i++) + { + if (moves[i] <= MaxMoveID) + continue; + + invalid++; + moves[i] = 0; + } + if (invalid == 0) + return; + if (invalid == 4) // no moves remain + { + moves[0] = 1; // Pound + Move1_PP = GetMovePP(1, Move1_PPUps); + } + + Moves = moves; + FixMoves(); + } + + /// + /// Gets one of the based on its index within the array. + /// + /// Index to get + public int GetEV(int index) => index switch + { + 0 => EV_HP, + 1 => EV_ATK, + 2 => EV_DEF, + 3 => EV_SPE, + 4 => EV_SPA, + 5 => EV_SPD, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; + + /// + /// Gets one of the based on its index within the array. + /// + /// Index to get + public int GetIV(int index) => index switch + { + 0 => IV_HP, + 1 => IV_ATK, + 2 => IV_DEF, + 3 => IV_SPE, + 4 => IV_SPA, + 5 => IV_SPD, + _ => throw new ArgumentOutOfRangeException(nameof(index)), + }; } diff --git a/PKHeX.Core/PKM/SK2.cs b/PKHeX.Core/PKM/SK2.cs index 7da238513..efebfd795 100644 --- a/PKHeX.Core/PKM/SK2.cs +++ b/PKHeX.Core/PKM/SK2.cs @@ -1,220 +1,219 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 2 format for . +public sealed class SK2 : GBPKM, ICaughtData2 { - /// Generation 2 format for . - public sealed class SK2 : GBPKM, ICaughtData2 + public override PersonalInfo PersonalInfo => PersonalTable.C[Species]; + + public override bool Valid => Species <= 252; + + public override int SIZE_PARTY => PokeCrypto.SIZE_2STADIUM; + public override int SIZE_STORED => PokeCrypto.SIZE_2STADIUM; + private bool IsEncodingJapanese { get; set; } + public override bool Japanese => IsEncodingJapanese; + public override bool Korean => false; + private const int StringLength = 12; + + public override EntityContext Context => EntityContext.Gen2; + public override int OTLength => StringLength; + public override int NickLength => StringLength; + + public SK2(bool jp = false) : base(PokeCrypto.SIZE_2STADIUM) => IsEncodingJapanese = jp; + public SK2(byte[] data) : this(data, IsJapanese(data)) { } + public SK2(byte[] data, bool jp) : base(data) => IsEncodingJapanese = jp; + + public override PKM Clone() => new SK2((byte[])Data.Clone(), Japanese) { - public override PersonalInfo PersonalInfo => PersonalTable.C[Species]; + IsEgg = IsEgg, + }; - public override bool Valid => Species <= 252; + protected override byte[] Encrypt() => Data; - public override int SIZE_PARTY => PokeCrypto.SIZE_2STADIUM; - public override int SIZE_STORED => PokeCrypto.SIZE_2STADIUM; - private bool IsEncodingJapanese { get; set; } - public override bool Japanese => IsEncodingJapanese; - public override bool Korean => false; - private const int StringLength = 12; + #region Stored Attributes + public override int Species { get => Data[0]; set => Data[0] = (byte)value; } + public override int SpriteItem => ItemConverter.GetItemFuture2((byte)HeldItem); + public override int HeldItem { get => Data[0x1]; set => Data[0x1] = (byte)value; } + public override int Move1 { get => Data[2]; set => Data[2] = (byte)value; } + public override int Move2 { get => Data[3]; set => Data[3] = (byte)value; } + public override int Move3 { get => Data[4]; set => Data[4] = (byte)value; } + public override int Move4 { get => Data[5]; set => Data[5] = (byte)value; } + public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(6)); set => WriteUInt16BigEndian(Data.AsSpan(6), (ushort)value); } + public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(8)); set => WriteUInt32BigEndian(Data.AsSpan(8), value); } // not 3 bytes like in PK2 + public override int EV_HP { get => ReadUInt16BigEndian(Data.AsSpan(0x0C)); set => WriteUInt16BigEndian(Data.AsSpan(0x0C), (ushort)value); } + public override int EV_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x0E)); set => WriteUInt16BigEndian(Data.AsSpan(0x0E), (ushort)value); } + public override int EV_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x10)); set => WriteUInt16BigEndian(Data.AsSpan(0x10), (ushort)value); } + public override int EV_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x12)); set => WriteUInt16BigEndian(Data.AsSpan(0x12), (ushort)value); } + public override int EV_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x14)); set => WriteUInt16BigEndian(Data.AsSpan(0x14), (ushort)value); } + public override ushort DV16 { get => ReadUInt16BigEndian(Data.AsSpan(0x16)); set => WriteUInt16BigEndian(Data.AsSpan(0x16), value); } + public override int Move1_PP { get => Data[0x18] & 0x3F; set => Data[0x18] = (byte)((Data[0x18] & 0xC0) | Math.Min(63, value)); } + public override int Move2_PP { get => Data[0x19] & 0x3F; set => Data[0x19] = (byte)((Data[0x19] & 0xC0) | Math.Min(63, value)); } + public override int Move3_PP { get => Data[0x1A] & 0x3F; set => Data[0x1A] = (byte)((Data[0x1A] & 0xC0) | Math.Min(63, value)); } + public override int Move4_PP { get => Data[0x1B] & 0x3F; set => Data[0x1B] = (byte)((Data[0x1B] & 0xC0) | Math.Min(63, value)); } + public override int Move1_PPUps { get => (Data[0x18] & 0xC0) >> 6; set => Data[0x18] = (byte)((Data[0x18] & 0x3F) | ((value & 0x3) << 6)); } + public override int Move2_PPUps { get => (Data[0x19] & 0xC0) >> 6; set => Data[0x19] = (byte)((Data[0x19] & 0x3F) | ((value & 0x3) << 6)); } + public override int Move3_PPUps { get => (Data[0x1A] & 0xC0) >> 6; set => Data[0x1A] = (byte)((Data[0x1A] & 0x3F) | ((value & 0x3) << 6)); } + public override int Move4_PPUps { get => (Data[0x1B] & 0xC0) >> 6; set => Data[0x1B] = (byte)((Data[0x1B] & 0x3F) | ((value & 0x3) << 6)); } + public override int CurrentFriendship { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } - public override EntityContext Context => EntityContext.Gen2; - public override int OTLength => StringLength; - public override int NickLength => StringLength; + public override int Stat_Level { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + public override bool IsEgg { get => (Data[0x1E] & 1) == 1; set => Data[0x1E] = (byte)((Data[0x1E] & ~1) | (value ? 1 : 0)); } - public SK2(bool jp = false) : base(PokeCrypto.SIZE_2STADIUM) => IsEncodingJapanese = jp; - public SK2(byte[] data) : this(data, IsJapanese(data)) { } - public SK2(byte[] data, bool jp) : base(data) => IsEncodingJapanese = jp; - - public override PKM Clone() => new SK2((byte[])Data.Clone(), Japanese) + public bool IsRental + { + get => (Data[0x1E] & 4) == 4; + set { - IsEgg = IsEgg, - }; - - protected override byte[] Encrypt() => Data; - - #region Stored Attributes - public override int Species { get => Data[0]; set => Data[0] = (byte)value; } - public override int SpriteItem => ItemConverter.GetItemFuture2((byte)HeldItem); - public override int HeldItem { get => Data[0x1]; set => Data[0x1] = (byte)value; } - public override int Move1 { get => Data[2]; set => Data[2] = (byte)value; } - public override int Move2 { get => Data[3]; set => Data[3] = (byte)value; } - public override int Move3 { get => Data[4]; set => Data[4] = (byte)value; } - public override int Move4 { get => Data[5]; set => Data[5] = (byte)value; } - public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(6)); set => WriteUInt16BigEndian(Data.AsSpan(6), (ushort)value); } - public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(8)); set => WriteUInt32BigEndian(Data.AsSpan(8), value); } // not 3 bytes like in PK2 - public override int EV_HP { get => ReadUInt16BigEndian(Data.AsSpan(0x0C)); set => WriteUInt16BigEndian(Data.AsSpan(0x0C), (ushort)value); } - public override int EV_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x0E)); set => WriteUInt16BigEndian(Data.AsSpan(0x0E), (ushort)value); } - public override int EV_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x10)); set => WriteUInt16BigEndian(Data.AsSpan(0x10), (ushort)value); } - public override int EV_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x12)); set => WriteUInt16BigEndian(Data.AsSpan(0x12), (ushort)value); } - public override int EV_SPC { get => ReadUInt16BigEndian(Data.AsSpan(0x14)); set => WriteUInt16BigEndian(Data.AsSpan(0x14), (ushort)value); } - public override ushort DV16 { get => ReadUInt16BigEndian(Data.AsSpan(0x16)); set => WriteUInt16BigEndian(Data.AsSpan(0x16), value); } - public override int Move1_PP { get => Data[0x18] & 0x3F; set => Data[0x18] = (byte)((Data[0x18] & 0xC0) | Math.Min(63, value)); } - public override int Move2_PP { get => Data[0x19] & 0x3F; set => Data[0x19] = (byte)((Data[0x19] & 0xC0) | Math.Min(63, value)); } - public override int Move3_PP { get => Data[0x1A] & 0x3F; set => Data[0x1A] = (byte)((Data[0x1A] & 0xC0) | Math.Min(63, value)); } - public override int Move4_PP { get => Data[0x1B] & 0x3F; set => Data[0x1B] = (byte)((Data[0x1B] & 0xC0) | Math.Min(63, value)); } - public override int Move1_PPUps { get => (Data[0x18] & 0xC0) >> 6; set => Data[0x18] = (byte)((Data[0x18] & 0x3F) | ((value & 0x3) << 6)); } - public override int Move2_PPUps { get => (Data[0x19] & 0xC0) >> 6; set => Data[0x19] = (byte)((Data[0x19] & 0x3F) | ((value & 0x3) << 6)); } - public override int Move3_PPUps { get => (Data[0x1A] & 0xC0) >> 6; set => Data[0x1A] = (byte)((Data[0x1A] & 0x3F) | ((value & 0x3) << 6)); } - public override int Move4_PPUps { get => (Data[0x1B] & 0xC0) >> 6; set => Data[0x1B] = (byte)((Data[0x1B] & 0x3F) | ((value & 0x3) << 6)); } - public override int CurrentFriendship { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } - - public override int Stat_Level { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } - public override bool IsEgg { get => (Data[0x1E] & 1) == 1; set => Data[0x1E] = (byte)((Data[0x1E] & ~1) | (value ? 1 : 0)); } - - public bool IsRental - { - get => (Data[0x1E] & 4) == 4; - set + if (!value) { - if (!value) - { - Data[0x1E] &= 0xFB; - return; - } - - Data[0x1E] |= 4; - OT_Name = string.Empty; + Data[0x1E] &= 0xFB; + return; } + + Data[0x1E] |= 4; + OT_Name = string.Empty; } - - // 0x1F - - private byte PKRS { get => Data[0x20]; set => Data[0x20] = value; } - // Crystal only Caught Data - public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } - public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | value << 4); } - - public ushort CaughtData { get => ReadUInt16BigEndian(Data.AsSpan(0x21)); set => WriteUInt16BigEndian(Data.AsSpan(0x21), value); } - - public int Met_TimeOfDay { get => (CaughtData >> 14) & 0x3; set => CaughtData = (ushort)((CaughtData & 0x3FFF) | ((value & 0x3) << 14)); } - public override int Met_Level { get => (CaughtData >> 8) & 0x3F; set => CaughtData = (ushort)((CaughtData & 0xC0FF) | ((value & 0x3F) << 8)); } - public override int OT_Gender { get => (CaughtData >> 7) & 1; set => CaughtData = (ushort)((CaughtData & 0xFF7F) | ((value & 1) << 7)); } - public override int Met_Location { get => CaughtData & 0x7F; set => CaughtData = (ushort)((CaughtData & 0xFF80) | (value & 0x7F)); } - - public override string Nickname - { - get => StringConverter12.GetString(Nickname_Trash, Japanese); - set => StringConverter12.SetString(Nickname_Trash, value.AsSpan(), 12, Japanese, StringConverterOption.None); - } - - public override string OT_Name - { - get => StringConverter12.GetString(OT_Trash, Japanese); - set - { - if (IsRental) - { - OT_Trash.Clear(); - return; - } - StringConverter12.SetString(OT_Trash, value.AsSpan(), StringLength, Japanese, StringConverterOption.None); - } - } - - public override Span Nickname_Trash => Data.AsSpan(0x24, 12); - public override Span OT_Trash => Data.AsSpan(0x30, 12); - - #endregion - - #region Party Attributes - public override int Status_Condition { get; set; } - public override int Stat_HPCurrent { get; set; } - public override int Stat_HPMax { get; set; } - public override int Stat_ATK { get; set; } - public override int Stat_DEF { get; set; } - public override int Stat_SPE { get; set; } - public override int Stat_SPA { get; set; } - public override int Stat_SPD { get; set; } - #endregion - - public override int OT_Friendship { get => CurrentFriendship; set => CurrentFriendship = value; } - public override bool HasOriginalMetLocation => CaughtData != 0; - public override int Version { get => (int)GameVersion.GSC; set { } } - - protected override byte[] GetNonNickname(int language) - { - var name = SpeciesName.GetSpeciesNameGeneration(Species, language, 2); - byte[] data = new byte[name.Length]; - StringConverter12.SetString(data, name.AsSpan(), data.Length, Japanese, StringConverterOption.Clear50); - return data; - } - - public override void SetNotNicknamed(int language) - { - var name = SpeciesName.GetSpeciesNameGeneration(Species, language, 2); - Nickname_Trash.Clear(); - Nickname = name; - } - - // Maximums - public override int MaxMoveID => Legal.MaxMoveID_2; - public override int MaxSpeciesID => Legal.MaxSpeciesID_2; - public override int MaxAbilityID => Legal.MaxAbilityID_2; - public override int MaxItemID => Legal.MaxItemID_2; - - public PK2 ConvertToPK2() => new(Japanese) - { - Species = Species, - HeldItem = HeldItem, - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - TID = TID, - EXP = EXP, - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPE = EV_SPE, - EV_SPC = EV_SPC, - DV16 = DV16, - Move1_PP = Move1_PP, - Move2_PP = Move2_PP, - Move3_PP = Move3_PP, - Move4_PP = Move4_PP, - Move1_PPUps = Move1_PPUps, - Move2_PPUps = Move2_PPUps, - Move3_PPUps = Move3_PPUps, - Move4_PPUps = Move4_PPUps, - CurrentFriendship = CurrentFriendship, - Stat_Level = Stat_Level, - IsEgg = IsEgg, - PKRS_Days = PKRS_Days, - PKRS_Strain = PKRS_Strain, - CaughtData = CaughtData, - - // Only copies until first 0x50 terminator, but just copy everything - Nickname = Nickname, - OT_Name = IsRental ? Japanese ? "1337" : "PKHeX" : OT_Name, - }; - - private static bool IsJapanese(ReadOnlySpan data) - { - if (!StringConverter12.GetIsG1Japanese(data.Slice(0x30, StringLength))) - return false; - if (!StringConverter12.GetIsG1Japanese(data.Slice(0x24, StringLength))) - return false; - - for (int i = 6; i < 0xC; i++) - { - if (data[0x30 + i] is not (0 or StringConverter12.G1TerminatorCode)) - return false; - if (data[0x24 + i] is not (0 or StringConverter12.G1TerminatorCode)) - return false; - } - return true; - } - - private static bool IsEnglish(ReadOnlySpan data) - { - if (!StringConverter12.GetIsG1English(data.Slice(0x30, StringLength))) - return false; - if (!StringConverter12.GetIsG1English(data.Slice(0x24, StringLength))) - return false; - return true; - } - - public bool IsPossible(bool japanese) => japanese ? IsJapanese(Data) : IsEnglish(Data); - public void SwapLanguage() => IsEncodingJapanese ^= true; } + + // 0x1F + + private byte PKRS { get => Data[0x20]; set => Data[0x20] = value; } + // Crystal only Caught Data + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + + public ushort CaughtData { get => ReadUInt16BigEndian(Data.AsSpan(0x21)); set => WriteUInt16BigEndian(Data.AsSpan(0x21), value); } + + public int Met_TimeOfDay { get => (CaughtData >> 14) & 0x3; set => CaughtData = (ushort)((CaughtData & 0x3FFF) | ((value & 0x3) << 14)); } + public override int Met_Level { get => (CaughtData >> 8) & 0x3F; set => CaughtData = (ushort)((CaughtData & 0xC0FF) | ((value & 0x3F) << 8)); } + public override int OT_Gender { get => (CaughtData >> 7) & 1; set => CaughtData = (ushort)((CaughtData & 0xFF7F) | ((value & 1) << 7)); } + public override int Met_Location { get => CaughtData & 0x7F; set => CaughtData = (ushort)((CaughtData & 0xFF80) | (value & 0x7F)); } + + public override string Nickname + { + get => StringConverter12.GetString(Nickname_Trash, Japanese); + set => StringConverter12.SetString(Nickname_Trash, value.AsSpan(), 12, Japanese, StringConverterOption.None); + } + + public override string OT_Name + { + get => StringConverter12.GetString(OT_Trash, Japanese); + set + { + if (IsRental) + { + OT_Trash.Clear(); + return; + } + StringConverter12.SetString(OT_Trash, value.AsSpan(), StringLength, Japanese, StringConverterOption.None); + } + } + + public override Span Nickname_Trash => Data.AsSpan(0x24, 12); + public override Span OT_Trash => Data.AsSpan(0x30, 12); + + #endregion + + #region Party Attributes + public override int Status_Condition { get; set; } + public override int Stat_HPCurrent { get; set; } + public override int Stat_HPMax { get; set; } + public override int Stat_ATK { get; set; } + public override int Stat_DEF { get; set; } + public override int Stat_SPE { get; set; } + public override int Stat_SPA { get; set; } + public override int Stat_SPD { get; set; } + #endregion + + public override int OT_Friendship { get => CurrentFriendship; set => CurrentFriendship = value; } + public override bool HasOriginalMetLocation => CaughtData != 0; + public override int Version { get => (int)GameVersion.GSC; set { } } + + protected override byte[] GetNonNickname(int language) + { + var name = SpeciesName.GetSpeciesNameGeneration(Species, language, 2); + byte[] data = new byte[name.Length]; + StringConverter12.SetString(data, name.AsSpan(), data.Length, Japanese, StringConverterOption.Clear50); + return data; + } + + public override void SetNotNicknamed(int language) + { + var name = SpeciesName.GetSpeciesNameGeneration(Species, language, 2); + Nickname_Trash.Clear(); + Nickname = name; + } + + // Maximums + public override int MaxMoveID => Legal.MaxMoveID_2; + public override int MaxSpeciesID => Legal.MaxSpeciesID_2; + public override int MaxAbilityID => Legal.MaxAbilityID_2; + public override int MaxItemID => Legal.MaxItemID_2; + + public PK2 ConvertToPK2() => new(Japanese) + { + Species = Species, + HeldItem = HeldItem, + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + TID = TID, + EXP = EXP, + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPE = EV_SPE, + EV_SPC = EV_SPC, + DV16 = DV16, + Move1_PP = Move1_PP, + Move2_PP = Move2_PP, + Move3_PP = Move3_PP, + Move4_PP = Move4_PP, + Move1_PPUps = Move1_PPUps, + Move2_PPUps = Move2_PPUps, + Move3_PPUps = Move3_PPUps, + Move4_PPUps = Move4_PPUps, + CurrentFriendship = CurrentFriendship, + Stat_Level = Stat_Level, + IsEgg = IsEgg, + PKRS_Days = PKRS_Days, + PKRS_Strain = PKRS_Strain, + CaughtData = CaughtData, + + // Only copies until first 0x50 terminator, but just copy everything + Nickname = Nickname, + OT_Name = IsRental ? Japanese ? "1337" : "PKHeX" : OT_Name, + }; + + private static bool IsJapanese(ReadOnlySpan data) + { + if (!StringConverter12.GetIsG1Japanese(data.Slice(0x30, StringLength))) + return false; + if (!StringConverter12.GetIsG1Japanese(data.Slice(0x24, StringLength))) + return false; + + for (int i = 6; i < 0xC; i++) + { + if (data[0x30 + i] is not (0 or StringConverter12.G1TerminatorCode)) + return false; + if (data[0x24 + i] is not (0 or StringConverter12.G1TerminatorCode)) + return false; + } + return true; + } + + private static bool IsEnglish(ReadOnlySpan data) + { + if (!StringConverter12.GetIsG1English(data.Slice(0x30, StringLength))) + return false; + if (!StringConverter12.GetIsG1English(data.Slice(0x24, StringLength))) + return false; + return true; + } + + public bool IsPossible(bool japanese) => japanese ? IsJapanese(Data) : IsEnglish(Data); + public void SwapLanguage() => IsEncodingJapanese ^= true; } diff --git a/PKHeX.Core/PKM/Searching/CloneDetectionMethod.cs b/PKHeX.Core/PKM/Searching/CloneDetectionMethod.cs index 450824f36..3f72f1825 100644 --- a/PKHeX.Core/PKM/Searching/CloneDetectionMethod.cs +++ b/PKHeX.Core/PKM/Searching/CloneDetectionMethod.cs @@ -1,23 +1,22 @@ -namespace PKHeX.Core.Searching +namespace PKHeX.Core.Searching; + +/// +/// Search option to check for clones. +/// +public enum CloneDetectionMethod { /// - /// Search option to check for clones. + /// Skip checking for cloned data. /// - public enum CloneDetectionMethod - { - /// - /// Skip checking for cloned data. - /// - None, + None, - /// - /// Check for clones by creating a hash of file name data. - /// - HashDetails, + /// + /// Check for clones by creating a hash of file name data. + /// + HashDetails, - /// - /// Check for clones by referencing the PID (or IVs for Gen1/2). - /// - HashPID, - } + /// + /// Check for clones by referencing the PID (or IVs for Gen1/2). + /// + HashPID, } diff --git a/PKHeX.Core/PKM/Searching/SearchComparison.cs b/PKHeX.Core/PKM/Searching/SearchComparison.cs index 6c22567c7..adf8963e5 100644 --- a/PKHeX.Core/PKM/Searching/SearchComparison.cs +++ b/PKHeX.Core/PKM/Searching/SearchComparison.cs @@ -1,13 +1,12 @@ -namespace PKHeX.Core.Searching +namespace PKHeX.Core.Searching; + +/// +/// Search comparison operands +/// +public enum SearchComparison { - /// - /// Search comparison operands - /// - public enum SearchComparison - { - None, - Equals, - GreaterThanEquals, - LessThanEquals, - } -} \ No newline at end of file + None, + Equals, + GreaterThanEquals, + LessThanEquals, +} diff --git a/PKHeX.Core/PKM/Searching/SearchSettings.cs b/PKHeX.Core/PKM/Searching/SearchSettings.cs index 801a50382..b4c9a08b5 100644 --- a/PKHeX.Core/PKM/Searching/SearchSettings.cs +++ b/PKHeX.Core/PKM/Searching/SearchSettings.cs @@ -3,207 +3,206 @@ using System.Linq; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core.Searching +namespace PKHeX.Core.Searching; + +/// +/// search settings & searcher +/// +public sealed class SearchSettings { + public int Format { get; init; } + public int Generation { get; init; } + public int Species { get; init; } = -1; + public int Ability { get; init; } = -1; + public int Nature { get; init; } = -1; + public int Item { get; init; } = -1; + public int Version { get; init; } = -1; + public int HiddenPowerType { get; init; } = -1; + + public SearchComparison SearchFormat { get; init; } + public SearchComparison SearchLevel { get; init; } + + public bool? SearchShiny { get; set; } + public bool? SearchLegal { get; set; } + public bool? SearchEgg { get; set; } + public int? ESV { get; set; } + public int? Level { get; init; } + + public int IVType { get; init; } + public int EVType { get; init; } + + public CloneDetectionMethod SearchClones { get; set; } + public IList BatchInstructions { get; init; } = Array.Empty(); + private StringInstruction[] BatchFilters { get; set; } = Array.Empty(); + + public readonly List Moves = new(); + + // ReSharper disable once CollectionNeverUpdated.Global /// - /// search settings & searcher + /// Extra Filters to be checked after all other filters have been checked. /// - public sealed class SearchSettings + /// Collection is iterated right before clones are checked. + public List> ExtraFilters { get; } = new(); + + /// + /// Adds a move to the required move list. + /// + /// + public void AddMove(int move) { - public int Format { private get; init; } - public int Generation { private get; init; } - public int Species { get; init; } = -1; - public int Ability { private get; init; } = -1; - public int Nature { private get; init; } = -1; - public int Item { private get; init; } = -1; - public int Version { private get; init; } = -1; - public int HiddenPowerType { private get; init; } = -1; + if (move > 0 && !Moves.Contains(move)) + Moves.Add(move); + } - public SearchComparison SearchFormat { private get; init; } - public SearchComparison SearchLevel { private get; init; } + /// + /// Searches the input list, filtering out entries as specified by the settings. + /// + /// List of entries to search + /// Search results that match all criteria + public IEnumerable Search(IEnumerable list) + { + BatchFilters = StringInstruction.GetFilters(BatchInstructions).ToArray(); + var result = SearchInner(list); - public bool? SearchShiny { get; set; } - public bool? SearchLegal { private get; set; } - public bool? SearchEgg { get; set; } - public int? ESV { private get; set; } - public int? Level { private get; init; } - - public int IVType { private get; init; } - public int EVType { private get; init; } - - public CloneDetectionMethod SearchClones { private get; set; } - public IList BatchInstructions { private get; init; } = Array.Empty(); - private StringInstruction[] BatchFilters { get; set; } = Array.Empty(); - - public readonly List Moves = new(); - - // ReSharper disable once CollectionNeverUpdated.Global - /// - /// Extra Filters to be checked after all other filters have been checked. - /// - /// Collection is iterated right before clones are checked. - public List> ExtraFilters { get; } = new(); - - /// - /// Adds a move to the required move list. - /// - /// - public void AddMove(int move) + if (SearchClones != CloneDetectionMethod.None) { - if (move > 0 && !Moves.Contains(move)) - Moves.Add(move); + var method = SearchUtil.GetCloneDetectMethod(SearchClones); + result = SearchUtil.GetExtraClones(result, method); } - /// - /// Searches the input list, filtering out entries as specified by the settings. - /// - /// List of entries to search - /// Search results that match all criteria - public IEnumerable Search(IEnumerable list) + return result; + } + + /// + /// Searches the input list, filtering out entries as specified by the settings. + /// + /// List of entries to search + /// Search results that match all criteria + public IEnumerable Search(IEnumerable list) + { + BatchFilters = StringInstruction.GetFilters(BatchInstructions).ToArray(); + var result = SearchInner(list); + + if (SearchClones != CloneDetectionMethod.None) { - BatchFilters = StringInstruction.GetFilters(BatchInstructions).ToArray(); - var result = SearchInner(list); - - if (SearchClones != CloneDetectionMethod.None) - { - var method = SearchUtil.GetCloneDetectMethod(SearchClones); - result = SearchUtil.GetExtraClones(result, method); - } - - return result; + var method = SearchUtil.GetCloneDetectMethod(SearchClones); + string GetHash(SlotCache z) => method(z.Entity); + result = SearchUtil.GetExtraClones(result, GetHash); } - /// - /// Searches the input list, filtering out entries as specified by the settings. - /// - /// List of entries to search - /// Search results that match all criteria - public IEnumerable Search(IEnumerable list) + return result; + } + + private IEnumerable SearchInner(IEnumerable list) + { + foreach (var pk in list) { - BatchFilters = StringInstruction.GetFilters(BatchInstructions).ToArray(); - var result = SearchInner(list); - - if (SearchClones != CloneDetectionMethod.None) - { - var method = SearchUtil.GetCloneDetectMethod(SearchClones); - string GetHash(SlotCache z) => method(z.Entity); - result = SearchUtil.GetExtraClones(result, GetHash); - } - - return result; - } - - private IEnumerable SearchInner(IEnumerable list) - { - foreach (var pk in list) - { - if (!IsSearchMatch(pk)) - continue; - yield return pk; - } - } - - private IEnumerable SearchInner(IEnumerable list) - { - foreach (var entry in list) - { - var pk = entry.Entity; - if (!IsSearchMatch(pk)) - continue; - yield return entry; - } - } - - private bool IsSearchMatch(PKM pk) - { - if (!SearchSimple(pk)) - return false; - if (!SearchIntermediate(pk)) - return false; - if (!SearchComplex(pk)) - return false; - - foreach (var filter in ExtraFilters) - { - if (!filter(pk)) - return false; - } - return true; - } - - private bool SearchSimple(PKM pk) - { - if (Format > 0 && !SearchUtil.SatisfiesFilterFormat(pk, Format, SearchFormat)) - return false; - if (Species > -1 && pk.Species != Species) - return false; - if (Ability > -1 && pk.Ability != Ability) - return false; - if (Nature > -1 && pk.StatNature != Nature) - return false; - if (Item > -1 && pk.HeldItem != Item) - return false; - if (Version > -1 && pk.Version != Version) - return false; - return true; - } - - private bool SearchIntermediate(PKM pk) - { - if (Generation > 0 && !SearchUtil.SatisfiesFilterGeneration(pk, Generation)) return false; - if (Moves.Count > 0 && !SearchUtil.SatisfiesFilterMoves(pk, Moves)) return false; - if (HiddenPowerType > -1 && pk.HPType != HiddenPowerType) return false; - if (SearchShiny != null && pk.IsShiny != SearchShiny) return false; - - if (IVType > 0 && !SearchUtil.SatisfiesFilterIVs(pk, IVType)) return false; - if (EVType > 0 && !SearchUtil.SatisfiesFilterEVs(pk, EVType)) return false; - - return true; - } - - private bool SearchComplex(PKM pk) - { - if (SearchEgg != null && !FilterResultEgg(pk)) return false; - if (Level is { } x and not 0 && !SearchUtil.SatisfiesFilterLevel(pk, SearchLevel, x)) return false; - if (SearchLegal != null && new LegalityAnalysis(pk).Valid != SearchLegal) return false; - if (BatchFilters.Length != 0 && !SearchUtil.SatisfiesFilterBatchInstruction(pk, BatchFilters)) return false; - - return true; - } - - private bool FilterResultEgg(PKM pk) - { - if (SearchEgg == false) - return !pk.IsEgg; - if (ESV != null) - return pk.IsEgg && pk.PSV == ESV; - return pk.IsEgg; - } - - public IReadOnlyList GetVersions(SaveFile sav) => GetVersions(sav, GetFallbackVersion(sav)); - - public IReadOnlyList GetVersions(SaveFile sav, GameVersion fallback) - { - if (Version > 0) - return new[] {(GameVersion) Version}; - - return Generation switch - { - 1 when !ParseSettings.AllowGen1Tradeback => new[] {RD, BU, GN, YW}, - 2 when sav is SAV2 {Korean: true} => new[] {GD, SI}, - 1 or 2 => new[] {RD, BU, GN, YW, /* */ GD, SI, C}, - - _ when fallback.GetGeneration() == Generation => GameUtil.GetVersionsWithinRange(sav, Generation).ToArray(), - _ => GameUtil.GameVersions, - }; - } - - private static GameVersion GetFallbackVersion(ITrainerInfo sav) - { - var parent = GameUtil.GetMetLocationVersionGroup((GameVersion)sav.Game); - if (parent == Invalid) - parent = GameUtil.GetMetLocationVersionGroup(GameUtil.GetVersion(sav.Generation)); - return parent; + if (!IsSearchMatch(pk)) + continue; + yield return pk; } } + + private IEnumerable SearchInner(IEnumerable list) + { + foreach (var entry in list) + { + var pk = entry.Entity; + if (!IsSearchMatch(pk)) + continue; + yield return entry; + } + } + + private bool IsSearchMatch(PKM pk) + { + if (!SearchSimple(pk)) + return false; + if (!SearchIntermediate(pk)) + return false; + if (!SearchComplex(pk)) + return false; + + foreach (var filter in ExtraFilters) + { + if (!filter(pk)) + return false; + } + return true; + } + + private bool SearchSimple(PKM pk) + { + if (Format > 0 && !SearchUtil.SatisfiesFilterFormat(pk, Format, SearchFormat)) + return false; + if (Species > -1 && pk.Species != Species) + return false; + if (Ability > -1 && pk.Ability != Ability) + return false; + if (Nature > -1 && pk.StatNature != Nature) + return false; + if (Item > -1 && pk.HeldItem != Item) + return false; + if (Version > -1 && pk.Version != Version) + return false; + return true; + } + + private bool SearchIntermediate(PKM pk) + { + if (Generation > 0 && !SearchUtil.SatisfiesFilterGeneration(pk, Generation)) return false; + if (Moves.Count > 0 && !SearchUtil.SatisfiesFilterMoves(pk, Moves)) return false; + if (HiddenPowerType > -1 && pk.HPType != HiddenPowerType) return false; + if (SearchShiny != null && pk.IsShiny != SearchShiny) return false; + + if (IVType > 0 && !SearchUtil.SatisfiesFilterIVs(pk, IVType)) return false; + if (EVType > 0 && !SearchUtil.SatisfiesFilterEVs(pk, EVType)) return false; + + return true; + } + + private bool SearchComplex(PKM pk) + { + if (SearchEgg != null && !FilterResultEgg(pk)) return false; + if (Level is { } x and not 0 && !SearchUtil.SatisfiesFilterLevel(pk, SearchLevel, x)) return false; + if (SearchLegal != null && new LegalityAnalysis(pk).Valid != SearchLegal) return false; + if (BatchFilters.Length != 0 && !SearchUtil.SatisfiesFilterBatchInstruction(pk, BatchFilters)) return false; + + return true; + } + + private bool FilterResultEgg(PKM pk) + { + if (SearchEgg == false) + return !pk.IsEgg; + if (ESV != null) + return pk.IsEgg && pk.PSV == ESV; + return pk.IsEgg; + } + + public IReadOnlyList GetVersions(SaveFile sav) => GetVersions(sav, GetFallbackVersion(sav)); + + public IReadOnlyList GetVersions(SaveFile sav, GameVersion fallback) + { + if (Version > 0) + return new[] {(GameVersion) Version}; + + return Generation switch + { + 1 when !ParseSettings.AllowGen1Tradeback => new[] {RD, BU, GN, YW}, + 2 when sav is SAV2 {Korean: true} => new[] {GD, SI}, + 1 or 2 => new[] {RD, BU, GN, YW, /* */ GD, SI, C}, + + _ when fallback.GetGeneration() == Generation => GameUtil.GetVersionsWithinRange(sav, Generation).ToArray(), + _ => GameUtil.GameVersions, + }; + } + + private static GameVersion GetFallbackVersion(ITrainerInfo sav) + { + var parent = GameUtil.GetMetLocationVersionGroup((GameVersion)sav.Game); + if (parent == Invalid) + parent = GameUtil.GetMetLocationVersionGroup(GameUtil.GetVersion(sav.Generation)); + return parent; + } } diff --git a/PKHeX.Core/PKM/Searching/SearchUtil.cs b/PKHeX.Core/PKM/Searching/SearchUtil.cs index 1bd07d4ce..3b839cb6a 100644 --- a/PKHeX.Core/PKM/Searching/SearchUtil.cs +++ b/PKHeX.Core/PKM/Searching/SearchUtil.cs @@ -1,131 +1,130 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core.Searching +namespace PKHeX.Core.Searching; + +/// +/// searching utility +/// +public static class SearchUtil { - /// - /// searching utility - /// - public static class SearchUtil + // Future: Might need to clamp down further for generations that cannot exist in the current format. + public static bool SatisfiesFilterFormat(PKM pk, int format, SearchComparison formatOperand) => formatOperand switch { - // Future: Might need to clamp down further for generations that cannot exist in the current format. - public static bool SatisfiesFilterFormat(PKM pk, int format, SearchComparison formatOperand) => formatOperand switch - { - SearchComparison.GreaterThanEquals when pk.Format < format => false, - SearchComparison.Equals when pk.Format != format => false, - SearchComparison.LessThanEquals when pk.Format > format => false, + SearchComparison.GreaterThanEquals when pk.Format < format => false, + SearchComparison.Equals when pk.Format != format => false, + SearchComparison.LessThanEquals when pk.Format > format => false, - _ when format <= 2 => pk.Format <= 2, // 1-2 - _ when format <= 6 => pk.Format >= 3, // 3-6 + _ when format <= 2 => pk.Format <= 2, // 1-2 + _ when format <= 6 => pk.Format >= 3, // 3-6 + _ => true, + }; + + public static bool SatisfiesFilterGeneration(PKM pk, int generation) => generation switch + { + 1 => pk.VC || pk.Format < 3, + 2 => pk.VC || pk.Format < 3, + _ => pk.Generation == generation, + }; + + public static bool SatisfiesFilterLevel(PKM pk, SearchComparison option, int level) + { + if (level > 100) + return true; // why??? + + return option switch + { + SearchComparison.LessThanEquals => pk.Stat_Level <= level, + SearchComparison.Equals => pk.Stat_Level == level, + SearchComparison.GreaterThanEquals => pk.Stat_Level >= level, _ => true, }; + } - public static bool SatisfiesFilterGeneration(PKM pk, int generation) => generation switch + public static bool SatisfiesFilterEVs(PKM pk, int option) => option switch + { + 1 => pk.EVTotal == 0, // None (0) + 2 => pk.EVTotal is (not 0) and < 128, // Some (127-1) + 3 => pk.EVTotal is >= 128 and < 508, // Half (128-507) + 4 => pk.EVTotal >= 508, // Full (508+) + _ => true, + }; + + public static bool SatisfiesFilterIVs(PKM pk, int option) => option switch + { + 1 => pk.IVTotal <= 90, // <= 90 + 2 => pk.IVTotal is > 90 and <= 120, // 91-120 + 3 => pk.IVTotal is > 120 and <= 150, // 121-150 + 4 => pk.IVTotal is > 150 and < 180, // 151-179 + 5 => pk.IVTotal >= 180, // 180+ + 6 => pk.IVTotal == 186, // == 186 + _ => true, + }; + + public static bool SatisfiesFilterMoves(PKM pk, IEnumerable requiredMoves) + { + foreach (var m in requiredMoves) { - 1 => pk.VC || pk.Format < 3, - 2 => pk.VC || pk.Format < 3, - _ => pk.Generation == generation, - }; - - public static bool SatisfiesFilterLevel(PKM pk, SearchComparison option, int level) - { - if (level > 100) - return true; // why??? - - return option switch - { - SearchComparison.LessThanEquals => pk.Stat_Level <= level, - SearchComparison.Equals => pk.Stat_Level == level, - SearchComparison.GreaterThanEquals => pk.Stat_Level >= level, - _ => true, - }; + if (!pk.HasMove(m)) + return false; } + return true; + } - public static bool SatisfiesFilterEVs(PKM pk, int option) => option switch - { - 1 => pk.EVTotal == 0, // None (0) - 2 => pk.EVTotal is (not 0) and < 128, // Some (127-1) - 3 => pk.EVTotal is >= 128 and < 508, // Half (128-507) - 4 => pk.EVTotal >= 508, // Full (508+) - _ => true, - }; + public static bool SatisfiesFilterBatchInstruction(PKM pk, IReadOnlyList filters) + { + return BatchEditing.IsFilterMatch(filters, pk); // Compare across all filters + } - public static bool SatisfiesFilterIVs(PKM pk, int option) => option switch - { - 1 => pk.IVTotal <= 90, // <= 90 - 2 => pk.IVTotal is > 90 and <= 120, // 91-120 - 3 => pk.IVTotal is > 120 and <= 150, // 121-150 - 4 => pk.IVTotal is > 150 and < 180, // 151-179 - 5 => pk.IVTotal >= 180, // 180+ - 6 => pk.IVTotal == 186, // == 186 - _ => true, - }; + public static Func GetCloneDetectMethod(CloneDetectionMethod method) => method switch + { + CloneDetectionMethod.HashPID => HashByPID, + _ => HashByDetails, + }; - public static bool SatisfiesFilterMoves(PKM pk, IEnumerable requiredMoves) + public static string HashByDetails(PKM pk) => pk.Format switch + { + 1 => $"{pk.Species:000}{((PK1) pk).DV16:X4}", + 2 => $"{pk.Species:000}{((PK2) pk).DV16:X4}", + _ => $"{pk.Species:000}{pk.PID:X8}{string.Join(" ", pk.IVs)}{pk.Form:00}", + }; + + public static string HashByPID(PKM pk) => pk.Format switch + { + 1 => $"{((PK1) pk).DV16:X4}", + 2 => $"{((PK2) pk).DV16:X4}", + _ => $"{pk.PID:X8}", + }; + + public static IEnumerable GetClones(IEnumerable res, CloneDetectionMethod type = CloneDetectionMethod.HashDetails) + { + var method = GetCloneDetectMethod(type); + return GetExtraClones(res, method); + } + + public static IEnumerable GetUniques(IEnumerable db, Func method) + { + var hs = new HashSet(); + foreach (var t in db) { - foreach (var m in requiredMoves) - { - if (!pk.HasMove(m)) - return false; - } - return true; + var hash = method(t); + if (hs.Contains(hash)) + continue; + yield return t; + hs.Add(hash); } + } - public static bool SatisfiesFilterBatchInstruction(PKM pk, IReadOnlyList filters) + public static IEnumerable GetExtraClones(IEnumerable db, Func method) + { + var hs = new HashSet(); + foreach (var t in db) { - return BatchEditing.IsFilterMatch(filters, pk); // Compare across all filters - } - - public static Func GetCloneDetectMethod(CloneDetectionMethod method) => method switch - { - CloneDetectionMethod.HashPID => HashByPID, - _ => HashByDetails, - }; - - public static string HashByDetails(PKM pk) => pk.Format switch - { - 1 => $"{pk.Species:000}{((PK1) pk).DV16:X4}", - 2 => $"{pk.Species:000}{((PK2) pk).DV16:X4}", - _ => $"{pk.Species:000}{pk.PID:X8}{string.Join(" ", pk.IVs)}{pk.Form:00}", - }; - - public static string HashByPID(PKM pk) => pk.Format switch - { - 1 => $"{((PK1) pk).DV16:X4}", - 2 => $"{((PK2) pk).DV16:X4}", - _ => $"{pk.PID:X8}", - }; - - public static IEnumerable GetClones(IEnumerable res, CloneDetectionMethod type = CloneDetectionMethod.HashDetails) - { - var method = GetCloneDetectMethod(type); - return GetExtraClones(res, method); - } - - public static IEnumerable GetUniques(IEnumerable db, Func method) - { - var hs = new HashSet(); - foreach (var t in db) - { - var hash = method(t); - if (hs.Contains(hash)) - continue; + var hash = method(t); + if (hs.Contains(hash)) yield return t; + else hs.Add(hash); - } - } - - public static IEnumerable GetExtraClones(IEnumerable db, Func method) - { - var hs = new HashSet(); - foreach (var t in db) - { - var hash = method(t); - if (hs.Contains(hash)) - yield return t; - else - hs.Add(hash); - } } } } diff --git a/PKHeX.Core/PKM/Shared/G3PKM.cs b/PKHeX.Core/PKM/Shared/G3PKM.cs index fbca7b8bf..c7674f115 100644 --- a/PKHeX.Core/PKM/Shared/G3PKM.cs +++ b/PKHeX.Core/PKM/Shared/G3PKM.cs @@ -1,220 +1,219 @@ -using System; +using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 Base Class +/// +public abstract class G3PKM : PKM, IRibbonSetEvent3, IRibbonSetCommon3, IRibbonSetUnique3, IRibbonSetOnly3, IContestStats, IContestStatsMutable { - /// - /// Generation 3 Base Class - /// - public abstract class G3PKM : PKM, IRibbonSetEvent3, IRibbonSetCommon3, IRibbonSetUnique3, IRibbonSetOnly3, IContestStats, IContestStatsMutable + protected G3PKM(byte[] data) : base(data) { } + protected G3PKM(int size) : base(size) { } + + // Maximums + public sealed override int MaxMoveID => Legal.MaxMoveID_3; + public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_3; + public sealed override int MaxAbilityID => Legal.MaxAbilityID_3; + public sealed override int MaxItemID => Legal.MaxItemID_3; + public sealed override int MaxBallID => Legal.MaxBallID_3; + public sealed override int MaxGameID => Legal.MaxGameID_3; + public sealed override int MaxIV => 31; + public sealed override int MaxEV => 255; + public sealed override int OTLength => 7; + public sealed override int NickLength => 10; + + // Generated Attributes + public sealed override int PSV => (int)(((PID >> 16) ^ (PID & 0xFFFF)) >> 3); + public sealed override int TSV => (TID ^ SID) >> 3; + public sealed override bool Japanese => Language == (int)LanguageID.Japanese; + + public sealed override int Ability { get => ((PersonalInfoG3)PersonalInfo).GetAbility(AbilityBit); set { } } + public sealed override uint EncryptionConstant { get => PID; set { } } + public sealed override int Nature { get => (int)(PID % 25); set { } } + public sealed override bool IsNicknamed { get => SpeciesName.IsNicknamed(Species, Nickname, Language, 3); set { } } + public sealed override int Gender { get => EntityGender.GetFromPID(Species, PID); set { } } + public sealed override int Characteristic => -1; + public sealed override int CurrentFriendship { get => OT_Friendship; set => OT_Friendship = value; } + public sealed override int CurrentHandler { get => 0; set { } } + public sealed override int Egg_Location { get => 0; set { } } + public override int MarkingCount => 4; + + public override int GetMarking(int index) { - protected G3PKM(byte[] data) : base(data) { } - protected G3PKM(int size) : base(size) { } - - // Maximums - public sealed override int MaxMoveID => Legal.MaxMoveID_3; - public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_3; - public sealed override int MaxAbilityID => Legal.MaxAbilityID_3; - public sealed override int MaxItemID => Legal.MaxItemID_3; - public sealed override int MaxBallID => Legal.MaxBallID_3; - public sealed override int MaxGameID => Legal.MaxGameID_3; - public sealed override int MaxIV => 31; - public sealed override int MaxEV => 255; - public sealed override int OTLength => 7; - public sealed override int NickLength => 10; - - // Generated Attributes - public sealed override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 3); - public sealed override int TSV => (TID ^ SID) >> 3; - public sealed override bool Japanese => Language == (int)LanguageID.Japanese; - - public sealed override int Ability { get => ((PersonalInfoG3)PersonalInfo).GetAbility(AbilityBit); set { } } - public sealed override uint EncryptionConstant { get => PID; set { } } - public sealed override int Nature { get => (int)(PID % 25); set { } } - public sealed override bool IsNicknamed { get => SpeciesName.IsNicknamed(Species, Nickname, Language, 3); set { } } - public sealed override int Gender { get => EntityGender.GetFromPID(Species, PID); set { } } - public sealed override int Characteristic => -1; - public sealed override int CurrentFriendship { get => OT_Friendship; set => OT_Friendship = value; } - public sealed override int CurrentHandler { get => 0; set { } } - public sealed override int Egg_Location { get => 0; set { } } - public override int MarkingCount => 4; - - public override int GetMarking(int index) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - return (MarkValue >> index) & 1; - } - - public override void SetMarking(int index, int value) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - MarkValue = (MarkValue & ~(1 << index)) | ((value & 1) << index); - } - - public abstract ushort SpeciesID3 { get; set; } // raw access - - public sealed override int Form - { - get => Species == (int)Core.Species.Unown ? EntityPID.GetUnownForm3(PID) : 0; - set - { - if (Species != (int)Core.Species.Unown) - return; - var rnd = Util.Rand; - while (EntityPID.GetUnownForm3(PID) != value) - PID = rnd.Rand32(); - } - } - - public sealed override int AbilityNumber { get => 1 << (AbilityBit ? 1 : 0); set => AbilityBit = value > 1; } // [0,1]->[1,2] ; [1,x]->[0,1] - public abstract bool AbilityBit { get; set; } - - public sealed override void RefreshAbility(int n) - { - if (Species is (int)Core.Species.Granbull or (int)Core.Species.Vibrava or (int)Core.Species.Flygon) - return; - AbilityBit = n == 1 && ((PersonalInfoG3)PersonalInfo).HasSecondAbility; - } - - public override bool Valid { get => true; set { } } - public override void RefreshChecksum() { } - public override bool ChecksumValid => Valid; - - public abstract bool RibbonEarth { get; set; } - public abstract bool RibbonNational { get; set; } - public abstract bool RibbonCountry { get; set; } - public abstract bool RibbonChampionBattle { get; set; } - public abstract bool RibbonChampionRegional { get; set; } - public abstract bool RibbonChampionNational { get; set; } - public abstract bool RibbonChampionG3 { get; set; } - public abstract bool RibbonArtist { get; set; } - public abstract bool RibbonEffort { get; set; } - public abstract bool RibbonWinning { get; set; } - public abstract bool RibbonVictory { get; set; } - public abstract int RibbonCountG3Cool { get; set; } - public abstract int RibbonCountG3Beauty { get; set; } - public abstract int RibbonCountG3Cute { get; set; } - public abstract int RibbonCountG3Smart { get; set; } - public abstract int RibbonCountG3Tough { get; set; } - public abstract bool RibbonWorld { get; set; } - public abstract bool Unused1 { get; set; } - public abstract bool Unused2 { get; set; } - public abstract bool Unused3 { get; set; } - public abstract bool Unused4 { get; set; } - - public abstract byte CNT_Cool { get; set; } - public abstract byte CNT_Beauty { get; set; } - public abstract byte CNT_Cute { get; set; } - public abstract byte CNT_Smart { get; set; } - public abstract byte CNT_Tough { get; set; } - public abstract byte CNT_Sheen { get; set; } - - /// - /// Swaps bits at a given position - /// - /// Value to swap bits for - /// Position of first bit to be swapped - /// Position of second bit to be swapped - /// Generation 3 marking values are swapped (Square-Triangle, instead of Triangle-Square). - /// Swapped bits value - protected static int SwapBits(int value, int p1, int p2) - { - int bit1 = (value >> p1) & 1; - int bit2 = (value >> p2) & 1; - int x = bit1 ^ bit2; - x = (x << p1) | (x << p2); - return value ^ x; - } - - protected static byte GetGBAVersionID(byte gc) => (byte)((GCVersion)gc).GetG3VersionID(); - protected static byte GetGCVersionID(int gba) => (byte)((GameVersion)gba).GetCXDVersionID(); - - /// - /// Interconversion for Generation 3 formats. - /// - /// Generation 3 format to convert to - /// New object with transferred properties. - protected T ConvertTo() where T : G3PKM, new() => new() - { - SpeciesID3 = SpeciesID3, - Language = Language, - PID = PID, - TID = TID, - SID = SID, - EXP = EXP, - HeldItem = HeldItem, - AbilityBit = AbilityBit, - IsEgg = IsEgg, - FatefulEncounter = FatefulEncounter, - - Met_Location = Met_Location, - Met_Level = Met_Level, - Version = Version, - Ball = Ball, - - Nickname = Nickname, - OT_Name = OT_Name, - OT_Gender = OT_Gender, - OT_Friendship = OT_Friendship, - - Move1_PPUps = Move1_PPUps, - Move2_PPUps = Move2_PPUps, - Move3_PPUps = Move3_PPUps, - Move4_PPUps = Move4_PPUps, - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - Move1_PP = Move1_PP, - Move2_PP = Move2_PP, - Move3_PP = Move3_PP, - Move4_PP = Move4_PP, - - IV_HP = IV_HP, - IV_ATK = IV_ATK, - IV_DEF = IV_DEF, - IV_SPE = IV_SPE, - IV_SPA = IV_SPA, - IV_SPD = IV_SPD, - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPE = EV_SPE, - EV_SPA = EV_SPA, - EV_SPD = EV_SPD, - CNT_Cool = CNT_Cool, - CNT_Beauty = CNT_Beauty, - CNT_Cute = CNT_Cute, - CNT_Smart = CNT_Smart, - CNT_Tough = CNT_Tough, - CNT_Sheen = CNT_Sheen, - - PKRS_Days = PKRS_Days, - PKRS_Strain = PKRS_Strain, - - // Transfer Ribbons - RibbonCountG3Cool = RibbonCountG3Cool, - RibbonCountG3Beauty = RibbonCountG3Beauty, - RibbonCountG3Cute = RibbonCountG3Cute, - RibbonCountG3Smart = RibbonCountG3Smart, - RibbonCountG3Tough = RibbonCountG3Tough, - RibbonChampionG3 = RibbonChampionG3, - RibbonWinning = RibbonWinning, - RibbonVictory = RibbonVictory, - RibbonArtist = RibbonArtist, - RibbonEffort = RibbonEffort, - RibbonChampionBattle = RibbonChampionBattle, - RibbonChampionRegional = RibbonChampionRegional, - RibbonChampionNational = RibbonChampionNational, - RibbonCountry = RibbonCountry, - RibbonNational = RibbonNational, - RibbonEarth = RibbonEarth, - RibbonWorld = RibbonWorld, - Unused1 = Unused1, - Unused2 = Unused2, - Unused3 = Unused3, - Unused4 = Unused4, - }; + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return (MarkValue >> index) & 1; } + + public override void SetMarking(int index, int value) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + MarkValue = (MarkValue & ~(1 << index)) | ((value & 1) << index); + } + + public abstract ushort SpeciesID3 { get; set; } // raw access + + public sealed override int Form + { + get => Species == (int)Core.Species.Unown ? EntityPID.GetUnownForm3(PID) : 0; + set + { + if (Species != (int)Core.Species.Unown) + return; + var rnd = Util.Rand; + while (EntityPID.GetUnownForm3(PID) != value) + PID = rnd.Rand32(); + } + } + + public sealed override int AbilityNumber { get => 1 << (AbilityBit ? 1 : 0); set => AbilityBit = value > 1; } // [0,1]->[1,2] ; [1,x]->[0,1] + public abstract bool AbilityBit { get; set; } + + public sealed override void RefreshAbility(int n) + { + if (Species is (int)Core.Species.Granbull or (int)Core.Species.Vibrava or (int)Core.Species.Flygon) + return; + AbilityBit = n == 1 && ((PersonalInfoG3)PersonalInfo).HasSecondAbility; + } + + public override bool Valid { get => true; set { } } + public override void RefreshChecksum() { } + public override bool ChecksumValid => Valid; + + public abstract bool RibbonEarth { get; set; } + public abstract bool RibbonNational { get; set; } + public abstract bool RibbonCountry { get; set; } + public abstract bool RibbonChampionBattle { get; set; } + public abstract bool RibbonChampionRegional { get; set; } + public abstract bool RibbonChampionNational { get; set; } + public abstract bool RibbonChampionG3 { get; set; } + public abstract bool RibbonArtist { get; set; } + public abstract bool RibbonEffort { get; set; } + public abstract bool RibbonWinning { get; set; } + public abstract bool RibbonVictory { get; set; } + public abstract int RibbonCountG3Cool { get; set; } + public abstract int RibbonCountG3Beauty { get; set; } + public abstract int RibbonCountG3Cute { get; set; } + public abstract int RibbonCountG3Smart { get; set; } + public abstract int RibbonCountG3Tough { get; set; } + public abstract bool RibbonWorld { get; set; } + public abstract bool Unused1 { get; set; } + public abstract bool Unused2 { get; set; } + public abstract bool Unused3 { get; set; } + public abstract bool Unused4 { get; set; } + + public abstract byte CNT_Cool { get; set; } + public abstract byte CNT_Beauty { get; set; } + public abstract byte CNT_Cute { get; set; } + public abstract byte CNT_Smart { get; set; } + public abstract byte CNT_Tough { get; set; } + public abstract byte CNT_Sheen { get; set; } + + /// + /// Swaps bits at a given position + /// + /// Value to swap bits for + /// Position of first bit to be swapped + /// Position of second bit to be swapped + /// Generation 3 marking values are swapped (Square-Triangle, instead of Triangle-Square). + /// Swapped bits value + protected static int SwapBits(int value, int p1, int p2) + { + int bit1 = (value >> p1) & 1; + int bit2 = (value >> p2) & 1; + int x = bit1 ^ bit2; + x = (x << p1) | (x << p2); + return value ^ x; + } + + protected static byte GetGBAVersionID(byte gc) => (byte)((GCVersion)gc).GetG3VersionID(); + protected static byte GetGCVersionID(int gba) => (byte)((GameVersion)gba).GetCXDVersionID(); + + /// + /// Interconversion for Generation 3 formats. + /// + /// Generation 3 format to convert to + /// New object with transferred properties. + protected T ConvertTo() where T : G3PKM, new() => new() + { + SpeciesID3 = SpeciesID3, + Language = Language, + PID = PID, + TID = TID, + SID = SID, + EXP = EXP, + HeldItem = HeldItem, + AbilityBit = AbilityBit, + IsEgg = IsEgg, + FatefulEncounter = FatefulEncounter, + + Met_Location = Met_Location, + Met_Level = Met_Level, + Version = Version, + Ball = Ball, + + Nickname = Nickname, + OT_Name = OT_Name, + OT_Gender = OT_Gender, + OT_Friendship = OT_Friendship, + + Move1_PPUps = Move1_PPUps, + Move2_PPUps = Move2_PPUps, + Move3_PPUps = Move3_PPUps, + Move4_PPUps = Move4_PPUps, + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + Move1_PP = Move1_PP, + Move2_PP = Move2_PP, + Move3_PP = Move3_PP, + Move4_PP = Move4_PP, + + IV_HP = IV_HP, + IV_ATK = IV_ATK, + IV_DEF = IV_DEF, + IV_SPE = IV_SPE, + IV_SPA = IV_SPA, + IV_SPD = IV_SPD, + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPE = EV_SPE, + EV_SPA = EV_SPA, + EV_SPD = EV_SPD, + CNT_Cool = CNT_Cool, + CNT_Beauty = CNT_Beauty, + CNT_Cute = CNT_Cute, + CNT_Smart = CNT_Smart, + CNT_Tough = CNT_Tough, + CNT_Sheen = CNT_Sheen, + + PKRS_Days = PKRS_Days, + PKRS_Strain = PKRS_Strain, + + // Transfer Ribbons + RibbonCountG3Cool = RibbonCountG3Cool, + RibbonCountG3Beauty = RibbonCountG3Beauty, + RibbonCountG3Cute = RibbonCountG3Cute, + RibbonCountG3Smart = RibbonCountG3Smart, + RibbonCountG3Tough = RibbonCountG3Tough, + RibbonChampionG3 = RibbonChampionG3, + RibbonWinning = RibbonWinning, + RibbonVictory = RibbonVictory, + RibbonArtist = RibbonArtist, + RibbonEffort = RibbonEffort, + RibbonChampionBattle = RibbonChampionBattle, + RibbonChampionRegional = RibbonChampionRegional, + RibbonChampionNational = RibbonChampionNational, + RibbonCountry = RibbonCountry, + RibbonNational = RibbonNational, + RibbonEarth = RibbonEarth, + RibbonWorld = RibbonWorld, + Unused1 = Unused1, + Unused2 = Unused2, + Unused3 = Unused3, + Unused4 = Unused4, + }; } diff --git a/PKHeX.Core/PKM/Shared/G4PKM.cs b/PKHeX.Core/PKM/Shared/G4PKM.cs index a69c77d3c..4ceeb7685 100644 --- a/PKHeX.Core/PKM/Shared/G4PKM.cs +++ b/PKHeX.Core/PKM/Shared/G4PKM.cs @@ -1,483 +1,482 @@ -using System; +using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class G4PKM : PKM, + IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetUnique3, IRibbonSetUnique4, IRibbonSetCommon3, IRibbonSetCommon4, + IContestStats, IContestStatsMutable, IGroundTile { - public abstract class G4PKM : PKM, - IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetUnique3, IRibbonSetUnique4, IRibbonSetCommon3, IRibbonSetCommon4, - IContestStats, IContestStatsMutable, IGroundTile + protected G4PKM(byte[] data) : base(data) { } + protected G4PKM(int size) : base(size) { } + + // Maximums + public sealed override int MaxMoveID => Legal.MaxMoveID_4; + public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_4; + public sealed override int MaxAbilityID => Legal.MaxAbilityID_4; + public sealed override int MaxItemID => Legal.MaxItemID_4_HGSS; + public sealed override int MaxBallID => Legal.MaxBallID_4; + public sealed override int MaxGameID => Legal.MaxGameID_4; + public sealed override int MaxIV => 31; + public sealed override int MaxEV => 255; + public sealed override int OTLength => 7; + public sealed override int NickLength => 10; + + public sealed override int PSV => (int)(((PID >> 16) ^ (PID & 0xFFFF)) >> 3); + public sealed override int TSV => (TID ^ SID) >> 3; + + protected bool PtHGSS => Pt || HGSS; + + public sealed override int Characteristic { - protected G4PKM(byte[] data) : base(data) { } - protected G4PKM(int size) : base(size) { } - - // Maximums - public sealed override int MaxMoveID => Legal.MaxMoveID_4; - public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_4; - public sealed override int MaxAbilityID => Legal.MaxAbilityID_4; - public sealed override int MaxItemID => Legal.MaxItemID_4_HGSS; - public sealed override int MaxBallID => Legal.MaxBallID_4; - public sealed override int MaxGameID => Legal.MaxGameID_4; - public sealed override int MaxIV => 31; - public sealed override int MaxEV => 255; - public sealed override int OTLength => 7; - public sealed override int NickLength => 10; - - public sealed override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 3); - public sealed override int TSV => (TID ^ SID) >> 3; - - protected bool PtHGSS => Pt || HGSS; - - public sealed override int Characteristic + get { - get + int pm6 = (int)(EncryptionConstant % 6); // PID + int maxIV = MaximumIV; + int pm6stat = 0; + for (int i = 0; i < 6; i++) { - int pm6 = (int)(EncryptionConstant % 6); // PID - int maxIV = MaximumIV; - int pm6stat = 0; - for (int i = 0; i < 6; i++) - { - pm6stat = (pm6 + i) % 6; - if (GetIV(pm6stat) == maxIV) - break; - } - return (pm6stat * 5) + (maxIV % 5); + pm6stat = (pm6 + i) % 6; + if (GetIV(pm6stat) == maxIV) + break; } - } - - public abstract ushort Sanity { get; set; } - public abstract ushort Checksum { get; set; } - public sealed override void RefreshChecksum() => Checksum = CalculateChecksum(); - public sealed override bool ChecksumValid => CalculateChecksum() == Checksum; - public override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } } - protected virtual ushort CalculateChecksum() => PokeCrypto.GetCHK(Data, PokeCrypto.SIZE_4STORED); - - // Trash Bytes - public sealed override Span Nickname_Trash => Data.AsSpan(0x48, 22); - public sealed override Span OT_Trash => Data.AsSpan(0x68, 16); - - // Future Attributes - public sealed override uint EncryptionConstant { get => PID; set { } } - public sealed override int Nature { get => (int)(PID % 25); set { } } - public sealed override int CurrentFriendship { get => OT_Friendship; set => OT_Friendship = value; } - public sealed override int CurrentHandler { get => 0; set { } } - public sealed override int AbilityNumber { get => 1 << PIDAbility; set { } } - - public abstract int ShinyLeaf { get; set; } - - #region Ribbons - public abstract bool RibbonEarth { get; set; } - public abstract bool RibbonNational { get; set; } - public abstract bool RibbonCountry { get; set; } - public abstract bool RibbonChampionBattle { get; set; } - public abstract bool RibbonChampionRegional { get; set; } - public abstract bool RibbonChampionNational { get; set; } - public abstract bool RibbonClassic { get; set; } - public abstract bool RibbonWishing { get; set; } - public abstract bool RibbonPremier { get; set; } - public abstract bool RibbonEvent { get; set; } - public abstract bool RibbonBirthday { get; set; } - public abstract bool RibbonSpecial { get; set; } - public abstract bool RibbonWorld { get; set; } - public abstract bool RibbonChampionWorld { get; set; } - public abstract bool RibbonSouvenir { get; set; } - public abstract bool RibbonWinning { get; set; } - public abstract bool RibbonVictory { get; set; } - public abstract bool RibbonAbility { get; set; } - public abstract bool RibbonAbilityGreat { get; set; } - public abstract bool RibbonAbilityDouble { get; set; } - public abstract bool RibbonAbilityMulti { get; set; } - public abstract bool RibbonAbilityPair { get; set; } - public abstract bool RibbonAbilityWorld { get; set; } - public abstract bool RibbonG3Cool { get; set; } - public abstract bool RibbonG3CoolSuper { get; set; } - public abstract bool RibbonG3CoolHyper { get; set; } - public abstract bool RibbonG3CoolMaster { get; set; } - public abstract bool RibbonG3Beauty { get; set; } - public abstract bool RibbonG3BeautySuper { get; set; } - public abstract bool RibbonG3BeautyHyper { get; set; } - public abstract bool RibbonG3BeautyMaster { get; set; } - public abstract bool RibbonG3Cute { get; set; } - public abstract bool RibbonG3CuteSuper { get; set; } - public abstract bool RibbonG3CuteHyper { get; set; } - public abstract bool RibbonG3CuteMaster { get; set; } - public abstract bool RibbonG3Smart { get; set; } - public abstract bool RibbonG3SmartSuper { get; set; } - public abstract bool RibbonG3SmartHyper { get; set; } - public abstract bool RibbonG3SmartMaster { get; set; } - public abstract bool RibbonG3Tough { get; set; } - public abstract bool RibbonG3ToughSuper { get; set; } - public abstract bool RibbonG3ToughHyper { get; set; } - public abstract bool RibbonG3ToughMaster { get; set; } - public abstract bool RibbonG4Cool { get; set; } - public abstract bool RibbonG4CoolGreat { get; set; } - public abstract bool RibbonG4CoolUltra { get; set; } - public abstract bool RibbonG4CoolMaster { get; set; } - public abstract bool RibbonG4Beauty { get; set; } - public abstract bool RibbonG4BeautyGreat { get; set; } - public abstract bool RibbonG4BeautyUltra { get; set; } - public abstract bool RibbonG4BeautyMaster { get; set; } - public abstract bool RibbonG4Cute { get; set; } - public abstract bool RibbonG4CuteGreat { get; set; } - public abstract bool RibbonG4CuteUltra { get; set; } - public abstract bool RibbonG4CuteMaster { get; set; } - public abstract bool RibbonG4Smart { get; set; } - public abstract bool RibbonG4SmartGreat { get; set; } - public abstract bool RibbonG4SmartUltra { get; set; } - public abstract bool RibbonG4SmartMaster { get; set; } - public abstract bool RibbonG4Tough { get; set; } - public abstract bool RibbonG4ToughGreat { get; set; } - public abstract bool RibbonG4ToughUltra { get; set; } - public abstract bool RibbonG4ToughMaster { get; set; } - public abstract bool RibbonChampionG3 { get; set; } - public abstract bool RibbonArtist { get; set; } - public abstract bool RibbonEffort { get; set; } - public abstract bool RibbonChampionSinnoh { get; set; } - public abstract bool RibbonAlert { get; set; } - public abstract bool RibbonShock { get; set; } - public abstract bool RibbonDowncast { get; set; } - public abstract bool RibbonCareless { get; set; } - public abstract bool RibbonRelax { get; set; } - public abstract bool RibbonSnooze { get; set; } - public abstract bool RibbonSmile { get; set; } - public abstract bool RibbonGorgeous { get; set; } - public abstract bool RibbonRoyal { get; set; } - public abstract bool RibbonGorgeousRoyal { get; set; } - public abstract bool RibbonFootprint { get; set; } - public abstract bool RibbonRecord { get; set; } - public abstract bool RibbonLegend { get; set; } - - // Unused - public abstract bool RIB3_4 { get; set; } - public abstract bool RIB3_5 { get; set; } - public abstract bool RIB3_6 { get; set; } - public abstract bool RIB3_7 { get; set; } - public abstract bool RIBA_4 { get; set; } - public abstract bool RIBA_5 { get; set; } - public abstract bool RIBA_6 { get; set; } - public abstract bool RIBA_7 { get; set; } - public abstract bool RIBB_0 { get; set; } - public abstract bool RIBB_1 { get; set; } - public abstract bool RIBB_2 { get; set; } - public abstract bool RIBB_3 { get; set; } - public abstract bool RIBB_4 { get; set; } - public abstract bool RIBB_5 { get; set; } - public abstract bool RIBB_6 { get; set; } - public abstract bool RIBB_7 { get; set; } - #endregion - - public abstract byte CNT_Cool { get; set; } - public abstract byte CNT_Beauty { get; set; } - public abstract byte CNT_Cute { get; set; } - public abstract byte CNT_Smart { get; set; } - public abstract byte CNT_Tough { get; set; } - public abstract byte CNT_Sheen { get; set; } - - public abstract GroundTileType GroundTile { get; set; } - public abstract byte BallDPPt { get; set; } - public abstract byte BallHGSS { get; set; } - public abstract byte PokeathlonStat { get; set; } - public override int MarkingCount => 6; - - public override int GetMarking(int index) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - return (MarkValue >> index) & 1; - } - - public override void SetMarking(int index, int value) - { - if ((uint)index >= MarkingCount) - throw new ArgumentOutOfRangeException(nameof(index)); - MarkValue = (MarkValue & ~(1 << index)) | ((value & 1) << index); - } - - public abstract ushort Egg_LocationDP { get; set; } - public abstract ushort Egg_LocationExtended { get; set; } - public abstract ushort Met_LocationDP { get; set; } - public abstract ushort Met_LocationExtended { get; set; } - - public sealed override int Egg_Location - { - get - { - ushort hgssloc = Egg_LocationExtended; - if (hgssloc != 0) - return hgssloc; - return Egg_LocationDP; - } - set - { - if (value == 0) - { - Egg_LocationDP = Egg_LocationExtended = 0; - } - else if (Locations.IsPtHGSSLocation(value) || Locations.IsPtHGSSLocationEgg(value)) - { - // Met location not in DP, set to Faraway Place - Egg_LocationDP = Locations.Faraway4; - Egg_LocationExtended = (ushort)value; - } - else - { - int pthgss = PtHGSS ? value : 0; // only set to PtHGSS loc if encountered in game - Egg_LocationDP = (ushort)value; - Egg_LocationExtended = (ushort)pthgss; - } - } - } - - public sealed override int Met_Location - { - get - { - ushort hgssloc = Met_LocationExtended; - if (hgssloc != 0) - return hgssloc; - return Met_LocationDP; - } - set - { - if (value == 0) - { - Met_LocationDP = Met_LocationExtended = 0; - } - else if (Locations.IsPtHGSSLocation(value) || Locations.IsPtHGSSLocationEgg(value)) - { - // Met location not in DP, set to Faraway Place - Met_LocationDP = Locations.Faraway4; - Met_LocationExtended = (ushort)value; - } - else - { - int pthgss = PtHGSS ? value : 0; // only set to PtHGSS loc if encountered in game - Met_LocationDP = (ushort)value; - Met_LocationExtended = (ushort)pthgss; - } - } - } - - public sealed override int Ball - { - // HG/SS added new ball IDs mid-generation, and the previous Gen4 games couldn't handle invalid ball values. - // Pokémon obtained in HG/SS have the HG/SS ball value set (@0x86) to the capture ball. - // However, this info is not set in event gift data! - // Event gift data contains a pre-formatted PK4 template, which is slightly mutated. - // No HG/SS ball values were used in these event gifts, and no HG/SS ball values are set (0). - - // To get the display ball (assume HG/SS +), return the higher of the two values. - get => Math.Max(BallHGSS, BallDPPt); - set - { - static byte Clamp(int value, Ball max) => (uint)value <= (uint)max ? (byte)value : (byte)Core.Ball.Poke; - - // Ball to display in DPPt - BallDPPt = Clamp(value, Core.Ball.Cherish); - - // Only set the HG/SS value if it originated in HG/SS and was not an event. - if (!HGSS || FatefulEncounter) - BallHGSS = 0; - else - BallHGSS = Clamp(value, Core.Ball.Sport); - } - } - - // Synthetic Trading Logic - public bool Trade(string SAV_Trainer, int SAV_TID, int SAV_SID, int SAV_GENDER, int Day = 1, int Month = 1, int Year = 2009) - { - // Eggs do not have any modifications done if they are traded - if (IsEgg && !(SAV_Trainer == OT_Name && SAV_TID == TID && SAV_SID == SID && SAV_GENDER == OT_Gender)) - { - SetLinkTradeEgg(Day, Month, Year, Locations.LinkTrade4); - return true; - } - return false; - } - - protected T ConvertTo() where T : G4PKM, new() - { - var pk = new T - { - PID = PID, - Species = Species, - HeldItem = HeldItem, - TID = TID, - SID = SID, - EXP = EXP, - OT_Friendship = OT_Friendship, - Ability = Ability, - Language = Language, - - IsEgg = IsEgg, - IsNicknamed = IsNicknamed, - OT_Gender = OT_Gender, - - IV_HP = IV_HP, - IV_ATK = IV_ATK, - IV_DEF = IV_DEF, - IV_SPE = IV_SPE, - IV_SPA = IV_SPA, - IV_SPD = IV_SPD, - EV_HP = EV_HP, - EV_ATK = EV_ATK, - EV_DEF = EV_DEF, - EV_SPE = EV_SPE, - EV_SPA = EV_SPA, - EV_SPD = EV_SPD, - CNT_Cool = CNT_Cool, - CNT_Beauty = CNT_Beauty, - CNT_Cute = CNT_Cute, - CNT_Smart = CNT_Smart, - CNT_Tough = CNT_Tough, - CNT_Sheen = CNT_Sheen, - - Move1 = Move1, - Move2 = Move2, - Move3 = Move3, - Move4 = Move4, - Move1_PP = Move1_PP, - Move2_PP = Move2_PP, - Move3_PP = Move3_PP, - Move4_PP = Move4_PP, - Move1_PPUps = Move1_PPUps, - Move2_PPUps = Move2_PPUps, - Move3_PPUps = Move3_PPUps, - Move4_PPUps = Move4_PPUps, - - Gender = Gender, - Form = Form, - ShinyLeaf = ShinyLeaf, - Version = Version, - PKRS_Days = PKRS_Days, - PKRS_Strain = PKRS_Strain, - BallDPPt = BallDPPt, - BallHGSS = BallHGSS, - GroundTile = GroundTile, - PokeathlonStat = PokeathlonStat, - FatefulEncounter = FatefulEncounter, - - Met_Level = Met_Level, - Met_Location = Met_Location, - Met_Year = Met_Year, - Met_Month = Met_Month, - Met_Day = Met_Day, - - Egg_Location = Egg_Location, - Egg_Year = Egg_Year, - Egg_Month = Egg_Month, - Egg_Day = Egg_Day, - - #region Ribbons - RibbonChampionSinnoh = RibbonChampionSinnoh, - RibbonAbility = RibbonAbility, - RibbonAbilityGreat = RibbonAbilityGreat, - RibbonAbilityDouble = RibbonAbilityDouble, - RibbonAbilityMulti = RibbonAbilityMulti, - RibbonAbilityPair = RibbonAbilityPair, - RibbonAbilityWorld = RibbonAbilityWorld, - RibbonAlert = RibbonAlert, - RibbonShock = RibbonShock, - RibbonDowncast = RibbonDowncast, - RibbonCareless = RibbonCareless, - RibbonRelax = RibbonRelax, - RibbonSnooze = RibbonSnooze, - RibbonSmile = RibbonSmile, - RibbonGorgeous = RibbonGorgeous, - RibbonRoyal = RibbonRoyal, - RibbonGorgeousRoyal = RibbonGorgeousRoyal, - RibbonFootprint = RibbonFootprint, - RibbonRecord = RibbonRecord, - RibbonEvent = RibbonEvent, - RibbonLegend = RibbonLegend, - RibbonChampionWorld = RibbonChampionWorld, - RibbonBirthday = RibbonBirthday, - RibbonSpecial = RibbonSpecial, - RibbonSouvenir = RibbonSouvenir, - RibbonWishing = RibbonWishing, - RibbonClassic = RibbonClassic, - RibbonPremier = RibbonPremier, - RibbonG3Cool = RibbonG3Cool, - RibbonG3CoolSuper = RibbonG3CoolSuper, - RibbonG3CoolHyper = RibbonG3CoolHyper, - RibbonG3CoolMaster = RibbonG3CoolMaster, - RibbonG3Beauty = RibbonG3Beauty, - RibbonG3BeautySuper = RibbonG3BeautySuper, - RibbonG3BeautyHyper = RibbonG3BeautyHyper, - RibbonG3BeautyMaster = RibbonG3BeautyMaster, - RibbonG3Cute = RibbonG3Cute, - RibbonG3CuteSuper = RibbonG3CuteSuper, - RibbonG3CuteHyper = RibbonG3CuteHyper, - RibbonG3CuteMaster = RibbonG3CuteMaster, - RibbonG3Smart = RibbonG3Smart, - RibbonG3SmartSuper = RibbonG3SmartSuper, - RibbonG3SmartHyper = RibbonG3SmartHyper, - RibbonG3SmartMaster = RibbonG3SmartMaster, - RibbonG3Tough = RibbonG3Tough, - RibbonG3ToughSuper = RibbonG3ToughSuper, - RibbonG3ToughHyper = RibbonG3ToughHyper, - RibbonG3ToughMaster = RibbonG3ToughMaster, - RibbonChampionG3 = RibbonChampionG3, - RibbonWinning = RibbonWinning, - RibbonVictory = RibbonVictory, - RibbonArtist = RibbonArtist, - RibbonEffort = RibbonEffort, - RibbonChampionBattle = RibbonChampionBattle, - RibbonChampionRegional = RibbonChampionRegional, - RibbonChampionNational = RibbonChampionNational, - RibbonCountry = RibbonCountry, - RibbonNational = RibbonNational, - RibbonEarth = RibbonEarth, - RibbonWorld = RibbonWorld, - RibbonG4Cool = RibbonG4Cool, - RibbonG4CoolGreat = RibbonG4CoolGreat, - RibbonG4CoolUltra = RibbonG4CoolUltra, - RibbonG4CoolMaster = RibbonG4CoolMaster, - RibbonG4Beauty = RibbonG4Beauty, - RibbonG4BeautyGreat = RibbonG4BeautyGreat, - RibbonG4BeautyUltra = RibbonG4BeautyUltra, - RibbonG4BeautyMaster = RibbonG4BeautyMaster, - RibbonG4Cute = RibbonG4Cute, - RibbonG4CuteGreat = RibbonG4CuteGreat, - RibbonG4CuteUltra = RibbonG4CuteUltra, - RibbonG4CuteMaster = RibbonG4CuteMaster, - RibbonG4Smart = RibbonG4Smart, - RibbonG4SmartGreat = RibbonG4SmartGreat, - RibbonG4SmartUltra = RibbonG4SmartUltra, - RibbonG4SmartMaster = RibbonG4SmartMaster, - RibbonG4Tough = RibbonG4Tough, - RibbonG4ToughGreat = RibbonG4ToughGreat, - RibbonG4ToughUltra = RibbonG4ToughUltra, - RibbonG4ToughMaster = RibbonG4ToughMaster, - RIB3_4 = RIB3_4, - RIB3_5 = RIB3_5, - RIB3_6 = RIB3_6, - RIB3_7 = RIB3_7, - RIBA_4 = RIBA_4, - RIBA_5 = RIBA_5, - RIBA_6 = RIBA_6, - RIBA_7 = RIBA_7, - RIBB_0 = RIBB_0, - RIBB_1 = RIBB_1, - RIBB_2 = RIBB_2, - RIBB_3 = RIBB_3, - RIBB_4 = RIBB_4, - RIBB_5 = RIBB_5, - RIBB_6 = RIBB_6, - RIBB_7 = RIBB_7, - #endregion - }; - - // Transfer Trash Bytes - for (int i = 0; i < 11; i++) // Nickname - { - pk.Data[0x48 + (2 * i)] = Data[0x48 + (2 * i) + 1]; - pk.Data[0x48 + (2 * i) + 1] = Data[0x48 + (2 * i)]; - } - for (int i = 0; i < 8; i++) // OT_Name - { - pk.Data[0x68 + (2 * i)] = Data[0x68 + (2 * i) + 1]; - pk.Data[0x68 + (2 * i) + 1] = Data[0x68 + (2 * i)]; - } - return pk; + return (pm6stat * 5) + (maxIV % 5); } } + + public abstract ushort Sanity { get; set; } + public abstract ushort Checksum { get; set; } + public sealed override void RefreshChecksum() => Checksum = CalculateChecksum(); + public sealed override bool ChecksumValid => CalculateChecksum() == Checksum; + public override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } } + protected virtual ushort CalculateChecksum() => PokeCrypto.GetCHK(Data, PokeCrypto.SIZE_4STORED); + + // Trash Bytes + public sealed override Span Nickname_Trash => Data.AsSpan(0x48, 22); + public sealed override Span OT_Trash => Data.AsSpan(0x68, 16); + + // Future Attributes + public sealed override uint EncryptionConstant { get => PID; set { } } + public sealed override int Nature { get => (int)(PID % 25); set { } } + public sealed override int CurrentFriendship { get => OT_Friendship; set => OT_Friendship = value; } + public sealed override int CurrentHandler { get => 0; set { } } + public sealed override int AbilityNumber { get => 1 << PIDAbility; set { } } + + public abstract int ShinyLeaf { get; set; } + + #region Ribbons + public abstract bool RibbonEarth { get; set; } + public abstract bool RibbonNational { get; set; } + public abstract bool RibbonCountry { get; set; } + public abstract bool RibbonChampionBattle { get; set; } + public abstract bool RibbonChampionRegional { get; set; } + public abstract bool RibbonChampionNational { get; set; } + public abstract bool RibbonClassic { get; set; } + public abstract bool RibbonWishing { get; set; } + public abstract bool RibbonPremier { get; set; } + public abstract bool RibbonEvent { get; set; } + public abstract bool RibbonBirthday { get; set; } + public abstract bool RibbonSpecial { get; set; } + public abstract bool RibbonWorld { get; set; } + public abstract bool RibbonChampionWorld { get; set; } + public abstract bool RibbonSouvenir { get; set; } + public abstract bool RibbonWinning { get; set; } + public abstract bool RibbonVictory { get; set; } + public abstract bool RibbonAbility { get; set; } + public abstract bool RibbonAbilityGreat { get; set; } + public abstract bool RibbonAbilityDouble { get; set; } + public abstract bool RibbonAbilityMulti { get; set; } + public abstract bool RibbonAbilityPair { get; set; } + public abstract bool RibbonAbilityWorld { get; set; } + public abstract bool RibbonG3Cool { get; set; } + public abstract bool RibbonG3CoolSuper { get; set; } + public abstract bool RibbonG3CoolHyper { get; set; } + public abstract bool RibbonG3CoolMaster { get; set; } + public abstract bool RibbonG3Beauty { get; set; } + public abstract bool RibbonG3BeautySuper { get; set; } + public abstract bool RibbonG3BeautyHyper { get; set; } + public abstract bool RibbonG3BeautyMaster { get; set; } + public abstract bool RibbonG3Cute { get; set; } + public abstract bool RibbonG3CuteSuper { get; set; } + public abstract bool RibbonG3CuteHyper { get; set; } + public abstract bool RibbonG3CuteMaster { get; set; } + public abstract bool RibbonG3Smart { get; set; } + public abstract bool RibbonG3SmartSuper { get; set; } + public abstract bool RibbonG3SmartHyper { get; set; } + public abstract bool RibbonG3SmartMaster { get; set; } + public abstract bool RibbonG3Tough { get; set; } + public abstract bool RibbonG3ToughSuper { get; set; } + public abstract bool RibbonG3ToughHyper { get; set; } + public abstract bool RibbonG3ToughMaster { get; set; } + public abstract bool RibbonG4Cool { get; set; } + public abstract bool RibbonG4CoolGreat { get; set; } + public abstract bool RibbonG4CoolUltra { get; set; } + public abstract bool RibbonG4CoolMaster { get; set; } + public abstract bool RibbonG4Beauty { get; set; } + public abstract bool RibbonG4BeautyGreat { get; set; } + public abstract bool RibbonG4BeautyUltra { get; set; } + public abstract bool RibbonG4BeautyMaster { get; set; } + public abstract bool RibbonG4Cute { get; set; } + public abstract bool RibbonG4CuteGreat { get; set; } + public abstract bool RibbonG4CuteUltra { get; set; } + public abstract bool RibbonG4CuteMaster { get; set; } + public abstract bool RibbonG4Smart { get; set; } + public abstract bool RibbonG4SmartGreat { get; set; } + public abstract bool RibbonG4SmartUltra { get; set; } + public abstract bool RibbonG4SmartMaster { get; set; } + public abstract bool RibbonG4Tough { get; set; } + public abstract bool RibbonG4ToughGreat { get; set; } + public abstract bool RibbonG4ToughUltra { get; set; } + public abstract bool RibbonG4ToughMaster { get; set; } + public abstract bool RibbonChampionG3 { get; set; } + public abstract bool RibbonArtist { get; set; } + public abstract bool RibbonEffort { get; set; } + public abstract bool RibbonChampionSinnoh { get; set; } + public abstract bool RibbonAlert { get; set; } + public abstract bool RibbonShock { get; set; } + public abstract bool RibbonDowncast { get; set; } + public abstract bool RibbonCareless { get; set; } + public abstract bool RibbonRelax { get; set; } + public abstract bool RibbonSnooze { get; set; } + public abstract bool RibbonSmile { get; set; } + public abstract bool RibbonGorgeous { get; set; } + public abstract bool RibbonRoyal { get; set; } + public abstract bool RibbonGorgeousRoyal { get; set; } + public abstract bool RibbonFootprint { get; set; } + public abstract bool RibbonRecord { get; set; } + public abstract bool RibbonLegend { get; set; } + + // Unused + public abstract bool RIB3_4 { get; set; } + public abstract bool RIB3_5 { get; set; } + public abstract bool RIB3_6 { get; set; } + public abstract bool RIB3_7 { get; set; } + public abstract bool RIBA_4 { get; set; } + public abstract bool RIBA_5 { get; set; } + public abstract bool RIBA_6 { get; set; } + public abstract bool RIBA_7 { get; set; } + public abstract bool RIBB_0 { get; set; } + public abstract bool RIBB_1 { get; set; } + public abstract bool RIBB_2 { get; set; } + public abstract bool RIBB_3 { get; set; } + public abstract bool RIBB_4 { get; set; } + public abstract bool RIBB_5 { get; set; } + public abstract bool RIBB_6 { get; set; } + public abstract bool RIBB_7 { get; set; } + #endregion + + public abstract byte CNT_Cool { get; set; } + public abstract byte CNT_Beauty { get; set; } + public abstract byte CNT_Cute { get; set; } + public abstract byte CNT_Smart { get; set; } + public abstract byte CNT_Tough { get; set; } + public abstract byte CNT_Sheen { get; set; } + + public abstract GroundTileType GroundTile { get; set; } + public abstract byte BallDPPt { get; set; } + public abstract byte BallHGSS { get; set; } + public abstract byte PokeathlonStat { get; set; } + public override int MarkingCount => 6; + + public override int GetMarking(int index) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return (MarkValue >> index) & 1; + } + + public override void SetMarking(int index, int value) + { + if ((uint)index >= MarkingCount) + throw new ArgumentOutOfRangeException(nameof(index)); + MarkValue = (MarkValue & ~(1 << index)) | ((value & 1) << index); + } + + public abstract ushort Egg_LocationDP { get; set; } + public abstract ushort Egg_LocationExtended { get; set; } + public abstract ushort Met_LocationDP { get; set; } + public abstract ushort Met_LocationExtended { get; set; } + + public sealed override int Egg_Location + { + get + { + ushort hgssloc = Egg_LocationExtended; + if (hgssloc != 0) + return hgssloc; + return Egg_LocationDP; + } + set + { + if (value == 0) + { + Egg_LocationDP = Egg_LocationExtended = 0; + } + else if (Locations.IsPtHGSSLocation(value) || Locations.IsPtHGSSLocationEgg(value)) + { + // Met location not in DP, set to Faraway Place + Egg_LocationDP = Locations.Faraway4; + Egg_LocationExtended = (ushort)value; + } + else + { + int pthgss = PtHGSS ? value : 0; // only set to PtHGSS loc if encountered in game + Egg_LocationDP = (ushort)value; + Egg_LocationExtended = (ushort)pthgss; + } + } + } + + public sealed override int Met_Location + { + get + { + ushort hgssloc = Met_LocationExtended; + if (hgssloc != 0) + return hgssloc; + return Met_LocationDP; + } + set + { + if (value == 0) + { + Met_LocationDP = Met_LocationExtended = 0; + } + else if (Locations.IsPtHGSSLocation(value) || Locations.IsPtHGSSLocationEgg(value)) + { + // Met location not in DP, set to Faraway Place + Met_LocationDP = Locations.Faraway4; + Met_LocationExtended = (ushort)value; + } + else + { + int pthgss = PtHGSS ? value : 0; // only set to PtHGSS loc if encountered in game + Met_LocationDP = (ushort)value; + Met_LocationExtended = (ushort)pthgss; + } + } + } + + public sealed override int Ball + { + // HG/SS added new ball IDs mid-generation, and the previous Gen4 games couldn't handle invalid ball values. + // Pokémon obtained in HG/SS have the HG/SS ball value set (@0x86) to the capture ball. + // However, this info is not set in event gift data! + // Event gift data contains a pre-formatted PK4 template, which is slightly mutated. + // No HG/SS ball values were used in these event gifts, and no HG/SS ball values are set (0). + + // To get the display ball (assume HG/SS +), return the higher of the two values. + get => Math.Max(BallHGSS, BallDPPt); + set + { + static byte Clamp(int value, Ball max) => (uint)value <= (uint)max ? (byte)value : (byte)Core.Ball.Poke; + + // Ball to display in DPPt + BallDPPt = Clamp(value, Core.Ball.Cherish); + + // Only set the HG/SS value if it originated in HG/SS and was not an event. + if (!HGSS || FatefulEncounter) + BallHGSS = 0; + else + BallHGSS = Clamp(value, Core.Ball.Sport); + } + } + + // Synthetic Trading Logic + public bool Trade(string SAV_Trainer, int SAV_TID, int SAV_SID, int SAV_GENDER, int Day = 1, int Month = 1, int Year = 2009) + { + // Eggs do not have any modifications done if they are traded + if (IsEgg && !(SAV_Trainer == OT_Name && SAV_TID == TID && SAV_SID == SID && SAV_GENDER == OT_Gender)) + { + SetLinkTradeEgg(Day, Month, Year, Locations.LinkTrade4); + return true; + } + return false; + } + + protected T ConvertTo() where T : G4PKM, new() + { + var pk = new T + { + PID = PID, + Species = Species, + HeldItem = HeldItem, + TID = TID, + SID = SID, + EXP = EXP, + OT_Friendship = OT_Friendship, + Ability = Ability, + Language = Language, + + IsEgg = IsEgg, + IsNicknamed = IsNicknamed, + OT_Gender = OT_Gender, + + IV_HP = IV_HP, + IV_ATK = IV_ATK, + IV_DEF = IV_DEF, + IV_SPE = IV_SPE, + IV_SPA = IV_SPA, + IV_SPD = IV_SPD, + EV_HP = EV_HP, + EV_ATK = EV_ATK, + EV_DEF = EV_DEF, + EV_SPE = EV_SPE, + EV_SPA = EV_SPA, + EV_SPD = EV_SPD, + CNT_Cool = CNT_Cool, + CNT_Beauty = CNT_Beauty, + CNT_Cute = CNT_Cute, + CNT_Smart = CNT_Smart, + CNT_Tough = CNT_Tough, + CNT_Sheen = CNT_Sheen, + + Move1 = Move1, + Move2 = Move2, + Move3 = Move3, + Move4 = Move4, + Move1_PP = Move1_PP, + Move2_PP = Move2_PP, + Move3_PP = Move3_PP, + Move4_PP = Move4_PP, + Move1_PPUps = Move1_PPUps, + Move2_PPUps = Move2_PPUps, + Move3_PPUps = Move3_PPUps, + Move4_PPUps = Move4_PPUps, + + Gender = Gender, + Form = Form, + ShinyLeaf = ShinyLeaf, + Version = Version, + PKRS_Days = PKRS_Days, + PKRS_Strain = PKRS_Strain, + BallDPPt = BallDPPt, + BallHGSS = BallHGSS, + GroundTile = GroundTile, + PokeathlonStat = PokeathlonStat, + FatefulEncounter = FatefulEncounter, + + Met_Level = Met_Level, + Met_Location = Met_Location, + Met_Year = Met_Year, + Met_Month = Met_Month, + Met_Day = Met_Day, + + Egg_Location = Egg_Location, + Egg_Year = Egg_Year, + Egg_Month = Egg_Month, + Egg_Day = Egg_Day, + + #region Ribbons + RibbonChampionSinnoh = RibbonChampionSinnoh, + RibbonAbility = RibbonAbility, + RibbonAbilityGreat = RibbonAbilityGreat, + RibbonAbilityDouble = RibbonAbilityDouble, + RibbonAbilityMulti = RibbonAbilityMulti, + RibbonAbilityPair = RibbonAbilityPair, + RibbonAbilityWorld = RibbonAbilityWorld, + RibbonAlert = RibbonAlert, + RibbonShock = RibbonShock, + RibbonDowncast = RibbonDowncast, + RibbonCareless = RibbonCareless, + RibbonRelax = RibbonRelax, + RibbonSnooze = RibbonSnooze, + RibbonSmile = RibbonSmile, + RibbonGorgeous = RibbonGorgeous, + RibbonRoyal = RibbonRoyal, + RibbonGorgeousRoyal = RibbonGorgeousRoyal, + RibbonFootprint = RibbonFootprint, + RibbonRecord = RibbonRecord, + RibbonEvent = RibbonEvent, + RibbonLegend = RibbonLegend, + RibbonChampionWorld = RibbonChampionWorld, + RibbonBirthday = RibbonBirthday, + RibbonSpecial = RibbonSpecial, + RibbonSouvenir = RibbonSouvenir, + RibbonWishing = RibbonWishing, + RibbonClassic = RibbonClassic, + RibbonPremier = RibbonPremier, + RibbonG3Cool = RibbonG3Cool, + RibbonG3CoolSuper = RibbonG3CoolSuper, + RibbonG3CoolHyper = RibbonG3CoolHyper, + RibbonG3CoolMaster = RibbonG3CoolMaster, + RibbonG3Beauty = RibbonG3Beauty, + RibbonG3BeautySuper = RibbonG3BeautySuper, + RibbonG3BeautyHyper = RibbonG3BeautyHyper, + RibbonG3BeautyMaster = RibbonG3BeautyMaster, + RibbonG3Cute = RibbonG3Cute, + RibbonG3CuteSuper = RibbonG3CuteSuper, + RibbonG3CuteHyper = RibbonG3CuteHyper, + RibbonG3CuteMaster = RibbonG3CuteMaster, + RibbonG3Smart = RibbonG3Smart, + RibbonG3SmartSuper = RibbonG3SmartSuper, + RibbonG3SmartHyper = RibbonG3SmartHyper, + RibbonG3SmartMaster = RibbonG3SmartMaster, + RibbonG3Tough = RibbonG3Tough, + RibbonG3ToughSuper = RibbonG3ToughSuper, + RibbonG3ToughHyper = RibbonG3ToughHyper, + RibbonG3ToughMaster = RibbonG3ToughMaster, + RibbonChampionG3 = RibbonChampionG3, + RibbonWinning = RibbonWinning, + RibbonVictory = RibbonVictory, + RibbonArtist = RibbonArtist, + RibbonEffort = RibbonEffort, + RibbonChampionBattle = RibbonChampionBattle, + RibbonChampionRegional = RibbonChampionRegional, + RibbonChampionNational = RibbonChampionNational, + RibbonCountry = RibbonCountry, + RibbonNational = RibbonNational, + RibbonEarth = RibbonEarth, + RibbonWorld = RibbonWorld, + RibbonG4Cool = RibbonG4Cool, + RibbonG4CoolGreat = RibbonG4CoolGreat, + RibbonG4CoolUltra = RibbonG4CoolUltra, + RibbonG4CoolMaster = RibbonG4CoolMaster, + RibbonG4Beauty = RibbonG4Beauty, + RibbonG4BeautyGreat = RibbonG4BeautyGreat, + RibbonG4BeautyUltra = RibbonG4BeautyUltra, + RibbonG4BeautyMaster = RibbonG4BeautyMaster, + RibbonG4Cute = RibbonG4Cute, + RibbonG4CuteGreat = RibbonG4CuteGreat, + RibbonG4CuteUltra = RibbonG4CuteUltra, + RibbonG4CuteMaster = RibbonG4CuteMaster, + RibbonG4Smart = RibbonG4Smart, + RibbonG4SmartGreat = RibbonG4SmartGreat, + RibbonG4SmartUltra = RibbonG4SmartUltra, + RibbonG4SmartMaster = RibbonG4SmartMaster, + RibbonG4Tough = RibbonG4Tough, + RibbonG4ToughGreat = RibbonG4ToughGreat, + RibbonG4ToughUltra = RibbonG4ToughUltra, + RibbonG4ToughMaster = RibbonG4ToughMaster, + RIB3_4 = RIB3_4, + RIB3_5 = RIB3_5, + RIB3_6 = RIB3_6, + RIB3_7 = RIB3_7, + RIBA_4 = RIBA_4, + RIBA_5 = RIBA_5, + RIBA_6 = RIBA_6, + RIBA_7 = RIBA_7, + RIBB_0 = RIBB_0, + RIBB_1 = RIBB_1, + RIBB_2 = RIBB_2, + RIBB_3 = RIBB_3, + RIBB_4 = RIBB_4, + RIBB_5 = RIBB_5, + RIBB_6 = RIBB_6, + RIBB_7 = RIBB_7, + #endregion + }; + + // Transfer Trash Bytes + for (int i = 0; i < 11; i++) // Nickname + { + pk.Data[0x48 + (2 * i)] = Data[0x48 + (2 * i) + 1]; + pk.Data[0x48 + (2 * i) + 1] = Data[0x48 + (2 * i)]; + } + for (int i = 0; i < 8; i++) // OT_Name + { + pk.Data[0x68 + (2 * i)] = Data[0x68 + (2 * i) + 1]; + pk.Data[0x68 + (2 * i) + 1] = Data[0x68 + (2 * i)]; + } + return pk; + } } diff --git a/PKHeX.Core/PKM/Shared/G6PKM.cs b/PKHeX.Core/PKM/Shared/G6PKM.cs index 4645a2ab8..3e30584c8 100644 --- a/PKHeX.Core/PKM/Shared/G6PKM.cs +++ b/PKHeX.Core/PKM/Shared/G6PKM.cs @@ -1,135 +1,134 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 6 format. +public abstract class G6PKM : PKM, ISanityChecksum { - /// Generation 6 format. - public abstract class G6PKM : PKM, ISanityChecksum + public override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY; + public override int SIZE_STORED => PokeCrypto.SIZE_6STORED; + protected G6PKM(byte[] data) : base(data) { } + protected G6PKM(int size) : base(size) { } + + // Trash Bytes + public sealed override Span Nickname_Trash => Data.AsSpan(0x40, 26); + public sealed override Span HT_Trash => Data.AsSpan(0x78, 26); + public sealed override Span OT_Trash => Data.AsSpan(0xB0, 26); + + public abstract ushort Sanity { get; set; } + public abstract ushort Checksum { get; set; } + public sealed override void RefreshChecksum() => Checksum = CalculateChecksum(); + public sealed override bool ChecksumValid => CalculateChecksum() == Checksum; + public sealed override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } } + + private ushort CalculateChecksum() { - public override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY; - public override int SIZE_STORED => PokeCrypto.SIZE_6STORED; - protected G6PKM(byte[] data) : base(data) { } - protected G6PKM(int size) : base(size) { } - - // Trash Bytes - public sealed override Span Nickname_Trash => Data.AsSpan(0x40, 26); - public sealed override Span HT_Trash => Data.AsSpan(0x78, 26); - public sealed override Span OT_Trash => Data.AsSpan(0xB0, 26); - - public abstract ushort Sanity { get; set; } - public abstract ushort Checksum { get; set; } - public sealed override void RefreshChecksum() => Checksum = CalculateChecksum(); - public sealed override bool ChecksumValid => CalculateChecksum() == Checksum; - public sealed override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } } - - private ushort CalculateChecksum() - { - ushort chk = 0; - for (int i = 8; i < PokeCrypto.SIZE_6STORED; i += 2) // don't use SIZE_STORED property; pb7 overrides stored size - chk += ReadUInt16LittleEndian(Data.AsSpan(i)); - return chk; - } - - // Simple Generated Attributes - public sealed override int CurrentFriendship - { - get => CurrentHandler == 0 ? OT_Friendship : HT_Friendship; - set { if (CurrentHandler == 0) OT_Friendship = value; else HT_Friendship = value; } - } - - public int OppositeFriendship - { - get => CurrentHandler == 1 ? OT_Friendship : HT_Friendship; - set { if (CurrentHandler == 1) OT_Friendship = value; else HT_Friendship = value; } - } - - public sealed override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 4); - public sealed override int TSV => (TID ^ SID) >> 4; - public sealed override bool IsUntraded => Data[0x78] == 0 && Data[0x78 + 1] == 0 && Format == Generation; // immediately terminated HT_Name data (\0) - - // Complex Generated Attributes - public sealed override int Characteristic - { - get - { - int pm6 = (int)(EncryptionConstant % 6); - int maxIV = MaximumIV; - int pm6stat = 0; - for (int i = 0; i < 6; i++) - { - pm6stat = (pm6 + i) % 6; - if (GetIV(pm6stat) == maxIV) - break; - } - return (pm6stat * 5) + (maxIV % 5); - } - } - - // Methods - protected sealed override byte[] Encrypt() - { - RefreshChecksum(); - return PokeCrypto.EncryptArray6(Data); - } - - // General User-error Fixes - public void FixRelearn() - { - while (true) - { - if (RelearnMove4 != 0 && RelearnMove3 == 0) - { - RelearnMove3 = RelearnMove4; - RelearnMove4 = 0; - } - if (RelearnMove3 != 0 && RelearnMove2 == 0) - { - RelearnMove2 = RelearnMove3; - RelearnMove3 = 0; - continue; - } - if (RelearnMove2 != 0 && RelearnMove1 == 0) - { - RelearnMove1 = RelearnMove2; - RelearnMove2 = 0; - continue; - } - break; - } - } - - // Synthetic Trading Logic - public void Trade(ITrainerInfo tr, int Day = 1, int Month = 1, int Year = 2015) - { - if (IsEgg) - { - // Eggs do not have any modifications done if they are traded - // Apply link trade data, only if it left the OT (ignore if dumped & imported, or cloned, etc) - if ((tr.TID != TID) || (tr.SID != SID) || (tr.Gender != OT_Gender) || (tr.OT != OT_Name)) - SetLinkTradeEgg(Day, Month, Year, Locations.LinkTrade6); - return; - } - - // Process to the HT if the OT of the Pokémon does not match the SAV's OT info. - if (!TradeOT(tr)) - TradeHT(tr); - } - - protected abstract bool TradeOT(ITrainerInfo tr); - protected abstract void TradeHT(ITrainerInfo tr); - - // Maximums - public sealed override int MaxIV => 31; - public sealed override int MaxEV => 252; - public sealed override int OTLength => 12; - public sealed override int NickLength => 12; + ushort chk = 0; + for (int i = 8; i < PokeCrypto.SIZE_6STORED; i += 2) // don't use SIZE_STORED property; pb7 overrides stored size + chk += ReadUInt16LittleEndian(Data.AsSpan(i)); + return chk; } - public interface ISuperTrain + // Simple Generated Attributes + public sealed override int CurrentFriendship { - uint SuperTrainBitFlags { get; set; } - bool SecretSuperTrainingUnlocked { get; set; } - bool SecretSuperTrainingComplete { get; set; } - int SuperTrainingMedalCount(int maxCount = 30); + get => CurrentHandler == 0 ? OT_Friendship : HT_Friendship; + set { if (CurrentHandler == 0) OT_Friendship = value; else HT_Friendship = value; } } + + public int OppositeFriendship + { + get => CurrentHandler == 1 ? OT_Friendship : HT_Friendship; + set { if (CurrentHandler == 1) OT_Friendship = value; else HT_Friendship = value; } + } + + public sealed override int PSV => (int)(((PID >> 16) ^ (PID & 0xFFFF)) >> 4); + public sealed override int TSV => (TID ^ SID) >> 4; + public sealed override bool IsUntraded => Data[0x78] == 0 && Data[0x78 + 1] == 0 && Format == Generation; // immediately terminated HT_Name data (\0) + + // Complex Generated Attributes + public sealed override int Characteristic + { + get + { + int pm6 = (int)(EncryptionConstant % 6); + int maxIV = MaximumIV; + int pm6stat = 0; + for (int i = 0; i < 6; i++) + { + pm6stat = (pm6 + i) % 6; + if (GetIV(pm6stat) == maxIV) + break; + } + return (pm6stat * 5) + (maxIV % 5); + } + } + + // Methods + protected sealed override byte[] Encrypt() + { + RefreshChecksum(); + return PokeCrypto.EncryptArray6(Data); + } + + // General User-error Fixes + public void FixRelearn() + { + while (true) + { + if (RelearnMove4 != 0 && RelearnMove3 == 0) + { + RelearnMove3 = RelearnMove4; + RelearnMove4 = 0; + } + if (RelearnMove3 != 0 && RelearnMove2 == 0) + { + RelearnMove2 = RelearnMove3; + RelearnMove3 = 0; + continue; + } + if (RelearnMove2 != 0 && RelearnMove1 == 0) + { + RelearnMove1 = RelearnMove2; + RelearnMove2 = 0; + continue; + } + break; + } + } + + // Synthetic Trading Logic + public void Trade(ITrainerInfo tr, int Day = 1, int Month = 1, int Year = 2015) + { + if (IsEgg) + { + // Eggs do not have any modifications done if they are traded + // Apply link trade data, only if it left the OT (ignore if dumped & imported, or cloned, etc) + if ((tr.TID != TID) || (tr.SID != SID) || (tr.Gender != OT_Gender) || (tr.OT != OT_Name)) + SetLinkTradeEgg(Day, Month, Year, Locations.LinkTrade6); + return; + } + + // Process to the HT if the OT of the Pokémon does not match the SAV's OT info. + if (!TradeOT(tr)) + TradeHT(tr); + } + + protected abstract bool TradeOT(ITrainerInfo tr); + protected abstract void TradeHT(ITrainerInfo tr); + + // Maximums + public sealed override int MaxIV => 31; + public sealed override int MaxEV => 252; + public sealed override int OTLength => 12; + public sealed override int NickLength => 12; +} + +public interface ISuperTrain +{ + uint SuperTrainBitFlags { get; set; } + bool SecretSuperTrainingUnlocked { get; set; } + bool SecretSuperTrainingComplete { get; set; } + int SuperTrainingMedalCount(int maxCount = 30); } diff --git a/PKHeX.Core/PKM/Shared/GBPKM.cs b/PKHeX.Core/PKM/Shared/GBPKM.cs index de0f2b078..da38a6432 100644 --- a/PKHeX.Core/PKM/Shared/GBPKM.cs +++ b/PKHeX.Core/PKM/Shared/GBPKM.cs @@ -1,249 +1,248 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Base format for Generation 1 & 2 objects. +/// +/// +/// store text buffers with the rest of the data. +/// and store them separately; see . +/// +public abstract class GBPKM : PKM { - /// - /// Base format for Generation 1 & 2 objects. - /// - /// - /// store text buffers with the rest of the data. - /// and store them separately; see . - /// - public abstract class GBPKM : PKM + public sealed override int MaxBallID => -1; + public sealed override int MinGameID => (int)GameVersion.RD; + public sealed override int MaxGameID => (int)GameVersion.C; + public sealed override int MaxIV => 15; + public sealed override int MaxEV => ushort.MaxValue; + + public sealed override IReadOnlyList ExtraBytes => Array.Empty(); + + protected GBPKM(int size) : base(size) { } + protected GBPKM(byte[] data) : base(data) { } + + public sealed override byte[] EncryptedPartyData => Encrypt(); + public sealed override byte[] EncryptedBoxData => Encrypt(); + public sealed override byte[] DecryptedBoxData => Encrypt(); + public sealed override byte[] DecryptedPartyData => Encrypt(); + + public override bool Valid { get => true; set { } } + public sealed override void RefreshChecksum() { } + + protected abstract byte[] GetNonNickname(int language); + + private bool? _isnicknamed; + + public sealed override bool IsNicknamed { - public sealed override int MaxBallID => -1; - public sealed override int MinGameID => (int)GameVersion.RD; - public sealed override int MaxGameID => (int)GameVersion.C; - public sealed override int MaxIV => 15; - public sealed override int MaxEV => ushort.MaxValue; - - public sealed override IReadOnlyList ExtraBytes => Array.Empty(); - - protected GBPKM(int size) : base(size) { } - protected GBPKM(byte[] data) : base(data) { } - - public sealed override byte[] EncryptedPartyData => Encrypt(); - public sealed override byte[] EncryptedBoxData => Encrypt(); - public sealed override byte[] DecryptedBoxData => Encrypt(); - public sealed override byte[] DecryptedPartyData => Encrypt(); - - public override bool Valid { get => true; set { } } - public sealed override void RefreshChecksum() { } - - protected abstract byte[] GetNonNickname(int language); - - private bool? _isnicknamed; - - public sealed override bool IsNicknamed + get => _isnicknamed ??= !Nickname_Trash.SequenceEqual(GetNonNickname(GuessedLanguage())); + set { - get => _isnicknamed ??= !Nickname_Trash.SequenceEqual(GetNonNickname(GuessedLanguage())); - set - { - _isnicknamed = value; - if (_isnicknamed == false) - SetNotNicknamed(GuessedLanguage()); - } - } - - protected bool IsNicknamedBank - { - get - { - var spName = SpeciesName.GetSpeciesNameGeneration(Species, GuessedLanguage(), Format); - return Nickname != spName; - } - } - - public sealed override int Language - { - get - { - if (Japanese) - return (int)LanguageID.Japanese; - if (Korean) - return (int)LanguageID.Korean; - if (StringConverter12.IsG12German(OT_Trash)) - return (int)LanguageID.German; // german - int lang = SpeciesName.GetSpeciesNameLanguage(Species, Nickname, Format); - if (lang > 0) - return lang; - return 0; - } - set - { - if (Japanese) - return; - if (Korean) - return; - - if (IsNicknamed) - return; - SetNotNicknamed(value); - } - } - - public sealed override int Gender - { - get - { - int gv = PersonalInfo.Gender; - return gv switch - { - PersonalInfo.RatioMagicGenderless => 2, - PersonalInfo.RatioMagicFemale => 1, - PersonalInfo.RatioMagicMale => 0, - _ => IV_ATK > gv >> 4 ? 0 : 1, - }; - } - set { } - } - - #region Future, Unused Attributes - public sealed override bool IsGenderValid() => true; // not a separate property, derived via IVs - public sealed override uint EncryptionConstant { get => 0; set { } } - public sealed override uint PID { get => 0; set { } } - public sealed override int Nature { get => 0; set { } } - public sealed override bool ChecksumValid => true; - public sealed override bool FatefulEncounter { get => false; set { } } - public sealed override int TSV => 0x0000; - public sealed override int PSV => 0xFFFF; - public sealed override int Characteristic => -1; - public sealed override int MarkValue { get => 0; set { } } - public sealed override int Ability { get => -1; set { } } - public sealed override int CurrentHandler { get => 0; set { } } - public sealed override int Egg_Location { get => 0; set { } } - public sealed override int Ball { get => 0; set { } } - public sealed override int SID { get => 0; set { } } - #endregion - - public sealed override bool IsShiny => IV_DEF == 10 && IV_SPE == 10 && IV_SPC == 10 && (IV_ATK & 2) == 2; - private int HPBitValPower => ((IV_ATK & 8) >> 0) | ((IV_DEF & 8) >> 1) | ((IV_SPE & 8) >> 2) | ((IV_SPC & 8) >> 3); - public sealed override int HPPower => (((5 * HPBitValPower) + (IV_SPC & 3)) >> 1) + 31; - - public sealed override int HPType - { - get => ((IV_ATK & 3) << 2) | (IV_DEF & 3); - set - { - IV_DEF = ((IV_DEF >> 2) << 2) | (value & 3); - IV_DEF = ((IV_ATK >> 2) << 2) | ((value >> 2) & 3); - } - } - - public sealed override int Form - { - get - { - if (Species != 201) // Unown - return 0; - - uint formeVal = 0; - formeVal |= (uint)((IV_ATK & 0x6) << 5); - formeVal |= (uint)((IV_DEF & 0x6) << 3); - formeVal |= (uint)((IV_SPE & 0x6) << 1); - formeVal |= (uint)((IV_SPC & 0x6) >> 1); - return (int)(formeVal / 10); - } - set - { - if (Species != 201) // Unown - return; - while (Form != value) - SetRandomIVs(0); - } - } - - public abstract int EV_SPC { get; set; } - public sealed override int EV_SPA { get => EV_SPC; set => EV_SPC = value; } - public sealed override int EV_SPD { get => EV_SPC; set { } } - public abstract ushort DV16 { get; set; } - public sealed override int IV_HP { get => ((IV_ATK & 1) << 3) | ((IV_DEF & 1) << 2) | ((IV_SPE & 1) << 1) | ((IV_SPC & 1) << 0); set { } } - public sealed override int IV_ATK { get => (DV16 >> 12) & 0xF; set => DV16 = (ushort)((DV16 & ~(0xF << 12)) | (ushort)((value > 0xF ? 0xF : value) << 12)); } - public sealed override int IV_DEF { get => (DV16 >> 8) & 0xF; set => DV16 = (ushort)((DV16 & ~(0xF << 8)) | (ushort)((value > 0xF ? 0xF : value) << 8)); } - public sealed override int IV_SPE { get => (DV16 >> 4) & 0xF; set => DV16 = (ushort)((DV16 & ~(0xF << 4)) | (ushort)((value > 0xF ? 0xF : value) << 4)); } - public int IV_SPC { get => (DV16 >> 0) & 0xF; set => DV16 = (ushort)((DV16 & ~(0xF << 0)) | (ushort)((value > 0xF ? 0xF : value) << 0)); } - public sealed override int IV_SPA { get => IV_SPC; set => IV_SPC = value; } - public sealed override int IV_SPD { get => IV_SPC; set { } } - public override int MarkingCount => 0; - public override int GetMarking(int index) => 0; - public override void SetMarking(int index, int value) { } - - public void SetNotNicknamed() => SetNotNicknamed(GuessedLanguage()); - public abstract void SetNotNicknamed(int language); - - public int GuessedLanguage(int fallback = (int)LanguageID.English) - { - int lang = Language; - if (lang > 0) - return lang; - if (fallback is (int)LanguageID.French or (int)LanguageID.German) // only other permitted besides English - return fallback; - return (int)LanguageID.English; - } - - /// - /// Tries to guess the source language ID when transferred to future generations (7+) - /// - /// Destination language ID - /// Source language ID - protected int TransferLanguage(int destLanguage) - { - // if the Species name of the destination language matches the current nickname, transfer with that language. - var expect = SpeciesName.GetSpeciesNameGeneration(Species, destLanguage, 2); - if (Nickname == expect) - return destLanguage; - return GuessedLanguage(destLanguage); - } - - public override void LoadStats(PersonalInfo p, Span stats) - { - var lv = Stat_Level; - stats[0] = (ushort)(GetStat(p.HP, IV_HP, EV_HP, lv) + (5 + lv)); // HP - stats[1] = GetStat(p.ATK, IV_ATK, EV_ATK, lv); - stats[2] = GetStat(p.DEF, IV_DEF, EV_DEF, lv); - stats[3] = GetStat(p.SPE, IV_SPE, EV_SPE, lv); - stats[4] = GetStat(p.SPA, IV_SPA, EV_SPA, lv); - stats[5] = GetStat(p.SPD, IV_SPD, EV_SPD, lv); - } - - protected static ushort GetStat(int baseStat, int iv, int effort, int level) - { - effort = (ushort)Math.Min(255, Math.Sqrt(effort) + 1) >> 2; - return (ushort)((((2 * (baseStat + iv)) + effort) * level / 100) + 5); - } - - public sealed override int GetMovePP(int move, int ppUpCount) - { - var pp = base.GetMovePP(move, 0); - return pp + (ppUpCount * Math.Min(7, pp / 5)); - } - - public void MaxEVs() => EV_HP = EV_ATK = EV_DEF = EV_SPC = EV_SPE = MaxEV; - - /// - /// Applies to the to make it shiny. - /// - public sealed override void SetShiny() - { - IV_ATK |= 2; - IV_DEF = 10; - IV_SPE = 10; - IV_SPA = 10; - } - - internal void ImportFromFuture(PKM pkm) - { - Nickname = pkm.Nickname; - OT_Name = pkm.OT_Name; - IV_ATK = pkm.IV_ATK / 2; - IV_DEF = pkm.IV_DEF / 2; - IV_SPC = pkm.IV_SPA / 2; - //IV_SPD = pkm.IV_ATK / 2; - IV_SPE = pkm.IV_SPE / 2; - - if (pkm.HasMove((int)Move.HiddenPower)) - HPType = pkm.HPType; + _isnicknamed = value; + if (_isnicknamed == false) + SetNotNicknamed(GuessedLanguage()); } } + + protected bool IsNicknamedBank + { + get + { + var spName = SpeciesName.GetSpeciesNameGeneration(Species, GuessedLanguage(), Format); + return Nickname != spName; + } + } + + public sealed override int Language + { + get + { + if (Japanese) + return (int)LanguageID.Japanese; + if (Korean) + return (int)LanguageID.Korean; + if (StringConverter12.IsG12German(OT_Trash)) + return (int)LanguageID.German; // german + int lang = SpeciesName.GetSpeciesNameLanguage(Species, Nickname, Format); + if (lang > 0) + return lang; + return 0; + } + set + { + if (Japanese) + return; + if (Korean) + return; + + if (IsNicknamed) + return; + SetNotNicknamed(value); + } + } + + public sealed override int Gender + { + get + { + int gv = PersonalInfo.Gender; + return gv switch + { + PersonalInfo.RatioMagicGenderless => 2, + PersonalInfo.RatioMagicFemale => 1, + PersonalInfo.RatioMagicMale => 0, + _ => IV_ATK > gv >> 4 ? 0 : 1, + }; + } + set { } + } + + #region Future, Unused Attributes + public sealed override bool IsGenderValid() => true; // not a separate property, derived via IVs + public sealed override uint EncryptionConstant { get => 0; set { } } + public sealed override uint PID { get => 0; set { } } + public sealed override int Nature { get => 0; set { } } + public sealed override bool ChecksumValid => true; + public sealed override bool FatefulEncounter { get => false; set { } } + public sealed override int TSV => 0x0000; + public sealed override int PSV => 0xFFFF; + public sealed override int Characteristic => -1; + public sealed override int MarkValue { get => 0; set { } } + public sealed override int Ability { get => -1; set { } } + public sealed override int CurrentHandler { get => 0; set { } } + public sealed override int Egg_Location { get => 0; set { } } + public sealed override int Ball { get => 0; set { } } + public sealed override int SID { get => 0; set { } } + #endregion + + public sealed override bool IsShiny => IV_DEF == 10 && IV_SPE == 10 && IV_SPC == 10 && (IV_ATK & 2) == 2; + private int HPBitValPower => ((IV_ATK & 8) >> 0) | ((IV_DEF & 8) >> 1) | ((IV_SPE & 8) >> 2) | ((IV_SPC & 8) >> 3); + public sealed override int HPPower => (((5 * HPBitValPower) + (IV_SPC & 3)) >> 1) + 31; + + public sealed override int HPType + { + get => ((IV_ATK & 3) << 2) | (IV_DEF & 3); + set + { + IV_DEF = ((IV_DEF >> 2) << 2) | (value & 3); + IV_DEF = ((IV_ATK >> 2) << 2) | ((value >> 2) & 3); + } + } + + public sealed override int Form + { + get + { + if (Species != 201) // Unown + return 0; + + uint formeVal = 0; + formeVal |= (uint)((IV_ATK & 0x6) << 5); + formeVal |= (uint)((IV_DEF & 0x6) << 3); + formeVal |= (uint)((IV_SPE & 0x6) << 1); + formeVal |= (uint)((IV_SPC & 0x6) >> 1); + return (int)(formeVal / 10); + } + set + { + if (Species != 201) // Unown + return; + while (Form != value) + SetRandomIVs(0); + } + } + + public abstract int EV_SPC { get; set; } + public sealed override int EV_SPA { get => EV_SPC; set => EV_SPC = value; } + public sealed override int EV_SPD { get => EV_SPC; set { } } + public abstract ushort DV16 { get; set; } + public sealed override int IV_HP { get => ((IV_ATK & 1) << 3) | ((IV_DEF & 1) << 2) | ((IV_SPE & 1) << 1) | ((IV_SPC & 1) << 0); set { } } + public sealed override int IV_ATK { get => (DV16 >> 12) & 0xF; set => DV16 = (ushort)((DV16 & ~(0xF << 12)) | (ushort)((value > 0xF ? 0xF : value) << 12)); } + public sealed override int IV_DEF { get => (DV16 >> 8) & 0xF; set => DV16 = (ushort)((DV16 & ~(0xF << 8)) | (ushort)((value > 0xF ? 0xF : value) << 8)); } + public sealed override int IV_SPE { get => (DV16 >> 4) & 0xF; set => DV16 = (ushort)((DV16 & ~(0xF << 4)) | (ushort)((value > 0xF ? 0xF : value) << 4)); } + public int IV_SPC { get => (DV16 >> 0) & 0xF; set => DV16 = (ushort)((DV16 & ~(0xF << 0)) | (ushort)((value > 0xF ? 0xF : value) << 0)); } + public sealed override int IV_SPA { get => IV_SPC; set => IV_SPC = value; } + public sealed override int IV_SPD { get => IV_SPC; set { } } + public override int MarkingCount => 0; + public override int GetMarking(int index) => 0; + public override void SetMarking(int index, int value) { } + + public void SetNotNicknamed() => SetNotNicknamed(GuessedLanguage()); + public abstract void SetNotNicknamed(int language); + + public int GuessedLanguage(int fallback = (int)LanguageID.English) + { + int lang = Language; + if (lang > 0) + return lang; + if (fallback is (int)LanguageID.French or (int)LanguageID.German) // only other permitted besides English + return fallback; + return (int)LanguageID.English; + } + + /// + /// Tries to guess the source language ID when transferred to future generations (7+) + /// + /// Destination language ID + /// Source language ID + protected int TransferLanguage(int destLanguage) + { + // if the Species name of the destination language matches the current nickname, transfer with that language. + var expect = SpeciesName.GetSpeciesNameGeneration(Species, destLanguage, 2); + if (Nickname == expect) + return destLanguage; + return GuessedLanguage(destLanguage); + } + + public override void LoadStats(PersonalInfo p, Span stats) + { + var lv = Stat_Level; + stats[0] = (ushort)(GetStat(p.HP, IV_HP, EV_HP, lv) + (5 + lv)); // HP + stats[1] = GetStat(p.ATK, IV_ATK, EV_ATK, lv); + stats[2] = GetStat(p.DEF, IV_DEF, EV_DEF, lv); + stats[3] = GetStat(p.SPE, IV_SPE, EV_SPE, lv); + stats[4] = GetStat(p.SPA, IV_SPA, EV_SPA, lv); + stats[5] = GetStat(p.SPD, IV_SPD, EV_SPD, lv); + } + + protected static ushort GetStat(int baseStat, int iv, int effort, int level) + { + effort = (ushort)Math.Min(255, Math.Sqrt(effort) + 1) >> 2; + return (ushort)((((2 * (baseStat + iv)) + effort) * level / 100) + 5); + } + + public sealed override int GetMovePP(int move, int ppUpCount) + { + var pp = base.GetMovePP(move, 0); + return pp + (ppUpCount * Math.Min(7, pp / 5)); + } + + public void MaxEVs() => EV_HP = EV_ATK = EV_DEF = EV_SPC = EV_SPE = MaxEV; + + /// + /// Applies to the to make it shiny. + /// + public sealed override void SetShiny() + { + IV_ATK |= 2; + IV_DEF = 10; + IV_SPE = 10; + IV_SPA = 10; + } + + internal void ImportFromFuture(PKM pk) + { + Nickname = pk.Nickname; + OT_Name = pk.OT_Name; + IV_ATK = pk.IV_ATK / 2; + IV_DEF = pk.IV_DEF / 2; + IV_SPC = pk.IV_SPA / 2; + //IV_SPD = pk.IV_ATK / 2; + IV_SPE = pk.IV_SPE / 2; + + if (pk.HasMove((int)Move.HiddenPower)) + HPType = pk.HPType; + } } diff --git a/PKHeX.Core/PKM/Shared/GBPKML.cs b/PKHeX.Core/PKM/Shared/GBPKML.cs index 27841f0e0..0699ddc75 100644 --- a/PKHeX.Core/PKM/Shared/GBPKML.cs +++ b/PKHeX.Core/PKM/Shared/GBPKML.cs @@ -1,117 +1,116 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Mainline format for Generation 1 & 2 objects. +/// +/// This format stores and in buffers separate from the rest of the details. +public abstract class GBPKML : GBPKM { - /// - /// Mainline format for Generation 1 & 2 objects. - /// - /// This format stores and in buffers separate from the rest of the details. - public abstract class GBPKML : GBPKM + internal const int StringLengthJapanese = 6; + internal const int StringLengthNotJapan = 11; + public sealed override int OTLength => Japanese ? 5 : 7; + public sealed override int NickLength => Japanese ? 5 : 10; + public sealed override bool Japanese => RawOT.Length == StringLengthJapanese; + + internal readonly byte[] RawOT; + internal readonly byte[] RawNickname; + + // Trash Bytes + public sealed override Span Nickname_Trash => RawNickname; + public sealed override Span OT_Trash => RawOT; + + protected GBPKML(int size, bool jp = false) : base(size) { - internal const int StringLengthJapanese = 6; - internal const int StringLengthNotJapan = 11; - public sealed override int OTLength => Japanese ? 5 : 7; - public sealed override int NickLength => Japanese ? 5 : 10; - public sealed override bool Japanese => RawOT.Length == StringLengthJapanese; + int strLen = jp ? StringLengthJapanese : StringLengthNotJapan; - internal readonly byte[] RawOT; - internal readonly byte[] RawNickname; + // initialize string buffers + RawOT = new byte[strLen]; + RawNickname = new byte[strLen]; + RawOT.AsSpan().Fill(StringConverter12.G1TerminatorCode); + RawNickname.AsSpan().Fill(StringConverter12.G1TerminatorCode); + } - // Trash Bytes - public sealed override Span Nickname_Trash => RawNickname; - public sealed override Span OT_Trash => RawOT; + protected GBPKML(byte[] data, bool jp = false) : base(data) + { + int strLen = jp ? StringLengthJapanese : StringLengthNotJapan; - protected GBPKML(int size, bool jp = false) : base(size) + // initialize string buffers + RawOT = new byte[strLen]; + RawNickname = new byte[strLen]; + RawOT.AsSpan().Fill(StringConverter12.G1TerminatorCode); + RawNickname.AsSpan().Fill(StringConverter12.G1TerminatorCode); + } + + public override void SetNotNicknamed(int language) => GetNonNickname(language).CopyTo(RawNickname); + + protected override byte[] GetNonNickname(int language) + { + var name = SpeciesName.GetSpeciesNameGeneration(Species, language, Format); + var len = Nickname_Trash.Length; + byte[] data = new byte[len]; + SetString(name.AsSpan(), data, len, StringConverterOption.Clear50); + if (!Korean) { - int strLen = jp ? StringLengthJapanese : StringLengthNotJapan; - - // initialize string buffers - RawOT = new byte[strLen]; - RawNickname = new byte[strLen]; - RawOT.AsSpan().Fill(StringConverter12.G1TerminatorCode); - RawNickname.AsSpan().Fill(StringConverter12.G1TerminatorCode); - } - - protected GBPKML(byte[] data, bool jp = false) : base(data) - { - int strLen = jp ? StringLengthJapanese : StringLengthNotJapan; - - // initialize string buffers - RawOT = new byte[strLen]; - RawNickname = new byte[strLen]; - RawOT.AsSpan().Fill(StringConverter12.G1TerminatorCode); - RawNickname.AsSpan().Fill(StringConverter12.G1TerminatorCode); - } - - public override void SetNotNicknamed(int language) => GetNonNickname(language).CopyTo(RawNickname); - - protected override byte[] GetNonNickname(int language) - { - var name = SpeciesName.GetSpeciesNameGeneration(Species, language, Format); - var len = Nickname_Trash.Length; - byte[] data = new byte[len]; - SetString(name.AsSpan(), data, len, StringConverterOption.Clear50); - if (!Korean) + // Decimal point<->period fix + for (int i = 0; i < data.Length; i++) { - // Decimal point<->period fix - for (int i = 0; i < data.Length; i++) - { - if (data[i] == 0xF2) - data[i] = 0xE8; - } + if (data[i] == 0xF2) + data[i] = 0xE8; } - return data; } + return data; + } - private int SetString(ReadOnlySpan value, Span destBuffer, int maxLength, StringConverterOption option = StringConverterOption.None) + private int SetString(ReadOnlySpan value, Span destBuffer, int maxLength, StringConverterOption option = StringConverterOption.None) + { + if (Korean) + return StringConverter2KOR.SetString(destBuffer, value, maxLength, option); + return StringConverter12.SetString(destBuffer, value, maxLength, Japanese, option); + } + + public sealed override string Nickname + { + get { if (Korean) - return StringConverter2KOR.SetString(destBuffer, value, maxLength, option); - return StringConverter12.SetString(destBuffer, value, maxLength, Japanese, option); + return StringConverter2KOR.GetString(RawNickname); + return StringConverter12.GetString(RawNickname, Japanese); } - - public sealed override string Nickname + set { - get - { - if (Korean) - return StringConverter2KOR.GetString(RawNickname); - return StringConverter12.GetString(RawNickname, Japanese); - } - set - { - if (!IsNicknamed && Nickname == value) - return; + if (!IsNicknamed && Nickname == value) + return; - SetStringKeepTerminatorStyle(value.AsSpan(), RawNickname); - } - } - - public sealed override string OT_Name - { - get - { - if (Korean) - return StringConverter2KOR.GetString(RawOT.AsSpan()); - return StringConverter12.GetString(RawOT.AsSpan(), Japanese); - } - set - { - if (value == OT_Name) - return; - SetStringKeepTerminatorStyle(value.AsSpan(), RawOT); - } - } - - private void SetStringKeepTerminatorStyle(ReadOnlySpan value, Span exist) - { - // Reset the destination buffer based on the termination style of the existing string. - bool zeroed = exist.IndexOf((byte)0) != -1; - byte fill = zeroed ? (byte)0 : StringConverter12.G1TerminatorCode; - exist.Fill(fill); - - int finalLength = Math.Min(value.Length + 1, exist.Length); - SetString(value, exist, finalLength); + SetStringKeepTerminatorStyle(value.AsSpan(), RawNickname); } } + + public sealed override string OT_Name + { + get + { + if (Korean) + return StringConverter2KOR.GetString(RawOT.AsSpan()); + return StringConverter12.GetString(RawOT.AsSpan(), Japanese); + } + set + { + if (value == OT_Name) + return; + SetStringKeepTerminatorStyle(value.AsSpan(), RawOT); + } + } + + private void SetStringKeepTerminatorStyle(ReadOnlySpan value, Span exist) + { + // Reset the destination buffer based on the termination style of the existing string. + bool zeroed = exist.IndexOf((byte)0) != -1; + byte fill = zeroed ? (byte)0 : StringConverter12.G1TerminatorCode; + exist.Fill(fill); + + int finalLength = Math.Min(value.Length + 1, exist.Length); + SetString(value, exist, finalLength); + } } diff --git a/PKHeX.Core/PKM/Shared/PokeList1.cs b/PKHeX.Core/PKM/Shared/PokeList1.cs index e9354a1d8..6d5671854 100644 --- a/PKHeX.Core/PKM/Shared/PokeList1.cs +++ b/PKHeX.Core/PKM/Shared/PokeList1.cs @@ -1,29 +1,28 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// List of prefixed by a count. +/// +public sealed class PokeList1 : PokeListGB { - /// - /// List of prefixed by a count. - /// - public sealed class PokeList1 : PokeListGB + protected override byte GetSpeciesBoxIdentifier(PK1 pk) => SpeciesConverter.SetG1Species(pk.Species); + protected override PK1 GetEntry(byte[] dat, ReadOnlySpan otname, ReadOnlySpan nick, bool egg) { - protected override byte GetSpeciesBoxIdentifier(PK1 pk) => SpeciesConverter.SetG1Species(pk.Species); - protected override PK1 GetEntry(byte[] dat, ReadOnlySpan otname, ReadOnlySpan nick, bool egg) - { - var result = new PK1(dat, Japanese); // no eggs - otname.CopyTo(result.OT_Trash); - nick.CopyTo(result.Nickname_Trash); - return result; - } - - protected override int GetEntrySize() => GetEntrySize(IsFormatParty); - - public PokeList1(byte[] d, PokeListType c = PokeListType.Single, bool jp = false) : base(d, c, jp) { } - public PokeList1(PokeListType c = PokeListType.Single, bool jp = false) : base(c, jp) { } - public PokeList1(byte[] d) : base(d, PokeListType.Single, d.Length == PokeCrypto.SIZE_1JLIST) { } - public PokeList1(PK1 pk) : base(pk) { } - - private static int GetEntrySize(bool party) => party ? PokeCrypto.SIZE_1PARTY : PokeCrypto.SIZE_1STORED; - public static int GetDataLength(PokeListType c, bool jp) => GetDataSize(c, jp, GetEntrySize(IsCapacityPartyFormat(c))); + var result = new PK1(dat, Japanese); // no eggs + otname.CopyTo(result.OT_Trash); + nick.CopyTo(result.Nickname_Trash); + return result; } -} \ No newline at end of file + + protected override int GetEntrySize() => GetEntrySize(IsFormatParty); + + public PokeList1(byte[] d, PokeListType c = PokeListType.Single, bool jp = false) : base(d, c, jp) { } + public PokeList1(PokeListType c = PokeListType.Single, bool jp = false) : base(c, jp) { } + public PokeList1(byte[] d) : base(d, PokeListType.Single, d.Length == PokeCrypto.SIZE_1JLIST) { } + public PokeList1(PK1 pk) : base(pk) { } + + private static int GetEntrySize(bool party) => party ? PokeCrypto.SIZE_1PARTY : PokeCrypto.SIZE_1STORED; + public static int GetDataLength(PokeListType c, bool jp) => GetDataSize(c, jp, GetEntrySize(IsCapacityPartyFormat(c))); +} diff --git a/PKHeX.Core/PKM/Shared/PokeList2.cs b/PKHeX.Core/PKM/Shared/PokeList2.cs index 1a02c7b37..5a21188a6 100644 --- a/PKHeX.Core/PKM/Shared/PokeList2.cs +++ b/PKHeX.Core/PKM/Shared/PokeList2.cs @@ -1,29 +1,28 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// List of prefixed by a count. +/// +public sealed class PokeList2 : PokeListGB { - /// - /// List of prefixed by a count. - /// - public sealed class PokeList2 : PokeListGB + protected override byte GetSpeciesBoxIdentifier(PK2 pk) => pk.IsEgg ? PK2.EggSpeciesValue : (byte)pk.Species; + protected override PK2 GetEntry(byte[] dat, ReadOnlySpan otname, ReadOnlySpan nick, bool egg) { - protected override byte GetSpeciesBoxIdentifier(PK2 pk) => pk.IsEgg ? PK2.EggSpeciesValue : (byte)pk.Species; - protected override PK2 GetEntry(byte[] dat, ReadOnlySpan otname, ReadOnlySpan nick, bool egg) - { - var result = new PK2(dat, Japanese) { IsEgg = egg }; - otname.CopyTo(result.OT_Trash); - nick.CopyTo(result.Nickname_Trash); - return result; - } - - protected override int GetEntrySize() => GetEntrySize(IsFormatParty); - - public PokeList2(byte[] d, PokeListType c = PokeListType.Single, bool jp = false) : base(d, c, jp) { } - public PokeList2(PokeListType c = PokeListType.Single, bool jp = false) : base(c, jp) { } - public PokeList2(byte[] d) : base(d, PokeListType.Single, d.Length == PokeCrypto.SIZE_2JLIST) { } - public PokeList2(PK2 pk) : base(pk) { } - - private static int GetEntrySize(bool party) => party ? PokeCrypto.SIZE_2PARTY : PokeCrypto.SIZE_2STORED; - public static int GetDataLength(PokeListType c, bool jp) => GetDataSize(c, jp, GetEntrySize(IsCapacityPartyFormat(c))); + var result = new PK2(dat, Japanese) { IsEgg = egg }; + otname.CopyTo(result.OT_Trash); + nick.CopyTo(result.Nickname_Trash); + return result; } + + protected override int GetEntrySize() => GetEntrySize(IsFormatParty); + + public PokeList2(byte[] d, PokeListType c = PokeListType.Single, bool jp = false) : base(d, c, jp) { } + public PokeList2(PokeListType c = PokeListType.Single, bool jp = false) : base(c, jp) { } + public PokeList2(byte[] d) : base(d, PokeListType.Single, d.Length == PokeCrypto.SIZE_2JLIST) { } + public PokeList2(PK2 pk) : base(pk) { } + + private static int GetEntrySize(bool party) => party ? PokeCrypto.SIZE_2PARTY : PokeCrypto.SIZE_2STORED; + public static int GetDataLength(PokeListType c, bool jp) => GetDataSize(c, jp, GetEntrySize(IsCapacityPartyFormat(c))); } diff --git a/PKHeX.Core/PKM/Shared/PokeListGB.cs b/PKHeX.Core/PKM/Shared/PokeListGB.cs index 74cb900c5..481fc68a1 100644 --- a/PKHeX.Core/PKM/Shared/PokeListGB.cs +++ b/PKHeX.Core/PKM/Shared/PokeListGB.cs @@ -1,162 +1,161 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// List of prefixed by a count. +/// +/// type that inherits from . +public abstract class PokeListGB where T : GBPKML { + // Structure: + // u8 Count of slots filled + // s8[capacity+1] GB Species ID in Slot (-1 = no species) + // pkx[capacity] GB PKM data (no strings) + // str[capacity] Trainer Name + // str[capacity] Nickname + // + // where, + // - str has variable size (jp/int) + // - pkx is different size for gen1/gen2 + + private readonly int StringLength; + private readonly byte[] Data; + private readonly byte Capacity; + private readonly int Entry_Size; + protected readonly bool Japanese; + + public readonly T[] Pokemon; + /// - /// List of prefixed by a count. + /// Count of slots filled in the list /// - /// type that inherits from . - public abstract class PokeListGB where T : GBPKML + public byte Count { get => Data[0]; private set => Data[0] = value > Capacity ? Capacity : value; } + + private const int SLOT_NONE = byte.MaxValue; + + protected PokeListGB(byte[]? d, PokeListType c = PokeListType.Single, bool jp = false) { - // Structure: - // u8 Count of slots filled - // s8[capacity+1] GB Species ID in Slot (-1 = no species) - // pkx[capacity] GB PKM data (no strings) - // str[capacity] Trainer Name - // str[capacity] Nickname - // - // where, - // - str has variable size (jp/int) - // - pkx is different size for gen1/gen2 + Japanese = jp; + Capacity = (byte)c; + Entry_Size = GetEntrySize(); + StringLength = GetStringLength(jp); + byte[] data = d ?? GetEmptyList(c, jp); + var dataSize = 1 + 1 + (Capacity * (Entry_Size + 1 + (2 * StringLength))); - private readonly int StringLength; - private readonly byte[] Data; - private readonly byte Capacity; - private readonly int Entry_Size; - protected readonly bool Japanese; + Array.Resize(ref data, dataSize); + Data = data; - public readonly T[] Pokemon; - - /// - /// Count of slots filled in the list - /// - public byte Count { get => Data[0]; private set => Data[0] = value > Capacity ? Capacity : value; } - - private const int SLOT_NONE = byte.MaxValue; - - protected PokeListGB(byte[]? d, PokeListType c = PokeListType.Single, bool jp = false) - { - Japanese = jp; - Capacity = (byte)c; - Entry_Size = GetEntrySize(); - StringLength = GetStringLength(jp); - byte[] data = d ?? GetEmptyList(c, jp); - var dataSize = 1 + 1 + (Capacity * (Entry_Size + 1 + (2 * StringLength))); - - Array.Resize(ref data, dataSize); - Data = data; - - Pokemon = Read(); - } - - protected PokeListGB(PokeListType c = PokeListType.Single, bool jp = false) - : this(null, c, jp) => Count = 1; - - protected PokeListGB(T pk) - : this(PokeListType.Single, pk.Japanese) - { - this[0] = pk; - Count = 1; - } - - private byte[] GetEmptyList(PokeListType c, bool jp = false) - { - int capacity = (byte)c; - - int size_intro = capacity + 1; - int size_pkm = GetEntrySize() * capacity; - int size_str = 2 * GetStringLength(jp) * capacity; - - // first byte: count (0) - var result = new byte[1 + size_intro + size_pkm + size_str]; - - // species present in slot: none - for (int i = 1; i <= size_intro; i++) - result[i] = SLOT_NONE; - - // fill string buffers with terminators - for (int i = 1 + size_intro + size_pkm; i < result.Length; i++) - result[i] = StringConverter12.G1TerminatorCode; // terminator - - return result; - } - - private int GetOffsetPKMData(int base_ofs, int index) => base_ofs + (Entry_Size * index); - private int GetOffsetPKMOT(int base_ofs, int index) => GetOffsetPKMData(base_ofs, Capacity) + (StringLength * index); - private int GetOffsetPKMNickname(int base_ofs, int index) => GetOffsetPKMOT(base_ofs, Capacity) + (StringLength * index); - - private static int GetStringLength(bool jp) => jp ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan; - protected bool IsFormatParty => IsCapacityPartyFormat((PokeListType)Capacity); - protected static bool IsCapacityPartyFormat(PokeListType Capacity) => Capacity is PokeListType.Single or PokeListType.Party; - - protected static int GetDataSize(PokeListType c, bool jp, int entrySize) - { - var entryLength = 1 + entrySize + (2 * GetStringLength(jp)); - return 2 + ((byte)c * entryLength); - } - - protected abstract int GetEntrySize(); - protected abstract byte GetSpeciesBoxIdentifier(T pk); - protected abstract T GetEntry(byte[] dat, ReadOnlySpan otname, ReadOnlySpan nick, bool egg); - - public T this[int i] - { - get - { - if ((uint)i > Capacity) - throw new ArgumentOutOfRangeException($"Invalid {nameof(PokeListGB)} Access: {i}"); - return Pokemon[i]; - } - set => Pokemon[i] = (T)value.Clone(); - } - - private T[] Read() - { - var arr = new T[Capacity]; - int base_ofs = 2 + Capacity; - for (int i = 0; i < Capacity; i++) - arr[i] = GetEntry(base_ofs, i); - return arr; - } - - public byte[] Write() - { - // Rebuild GB Species ID table - int count = Array.FindIndex(Pokemon, pk => pk.Species == 0); - Count = count < 0 ? Capacity : (byte)count; - int base_ofs = 1 + 1 + Capacity; - for (int i = 0; i < Count; i++) - { - Data[1 + i] = GetSpeciesBoxIdentifier(Pokemon[i]); - SetEntry(base_ofs, i); - } - Data[1 + Count] = SLOT_NONE; - return Data; - } - - private T GetEntry(int base_ofs, int index) - { - int pkOfs = GetOffsetPKMData(base_ofs, index); - int otOfs = GetOffsetPKMOT(base_ofs, index); - int nkOfs = GetOffsetPKMNickname(base_ofs, index); - - var dat = Data.Slice(pkOfs, Entry_Size); - var otname = Data.AsSpan(otOfs, StringLength); - var nick = Data.AsSpan(nkOfs, StringLength); - - return GetEntry(dat, otname, nick, Data[1 + index] == 0xFD); - } - - private void SetEntry(int base_ofs, int index) - { - int pkOfs = GetOffsetPKMData(base_ofs, index); - int otOfs = GetOffsetPKMOT(base_ofs, index); - int nkOfs = GetOffsetPKMNickname(base_ofs, index); - - var pk = Pokemon[index]; - Array.Copy(pk.Data, 0, Data, pkOfs, Entry_Size); - pk.RawOT.CopyTo(Data, otOfs); - pk.RawNickname.CopyTo(Data, nkOfs); - } + Pokemon = Read(); } -} \ No newline at end of file + + protected PokeListGB(PokeListType c = PokeListType.Single, bool jp = false) + : this(null, c, jp) => Count = 1; + + protected PokeListGB(T pk) + : this(PokeListType.Single, pk.Japanese) + { + this[0] = pk; + Count = 1; + } + + private byte[] GetEmptyList(PokeListType c, bool jp = false) + { + int capacity = (byte)c; + + int size_intro = capacity + 1; + int size_pkm = GetEntrySize() * capacity; + int size_str = 2 * GetStringLength(jp) * capacity; + + // first byte: count (0) + var result = new byte[1 + size_intro + size_pkm + size_str]; + + // species present in slot: none + for (int i = 1; i <= size_intro; i++) + result[i] = SLOT_NONE; + + // fill string buffers with terminators + for (int i = 1 + size_intro + size_pkm; i < result.Length; i++) + result[i] = StringConverter12.G1TerminatorCode; // terminator + + return result; + } + + private int GetOffsetPKMData(int base_ofs, int index) => base_ofs + (Entry_Size * index); + private int GetOffsetPKMOT(int base_ofs, int index) => GetOffsetPKMData(base_ofs, Capacity) + (StringLength * index); + private int GetOffsetPKMNickname(int base_ofs, int index) => GetOffsetPKMOT(base_ofs, Capacity) + (StringLength * index); + + private static int GetStringLength(bool jp) => jp ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan; + protected bool IsFormatParty => IsCapacityPartyFormat((PokeListType)Capacity); + protected static bool IsCapacityPartyFormat(PokeListType Capacity) => Capacity is PokeListType.Single or PokeListType.Party; + + protected static int GetDataSize(PokeListType c, bool jp, int entrySize) + { + var entryLength = 1 + entrySize + (2 * GetStringLength(jp)); + return 2 + ((byte)c * entryLength); + } + + protected abstract int GetEntrySize(); + protected abstract byte GetSpeciesBoxIdentifier(T pk); + protected abstract T GetEntry(byte[] dat, ReadOnlySpan otname, ReadOnlySpan nick, bool egg); + + public T this[int i] + { + get + { + if ((uint)i > Capacity) + throw new ArgumentOutOfRangeException($"Invalid {nameof(PokeListGB)} Access: {i}"); + return Pokemon[i]; + } + set => Pokemon[i] = (T)value.Clone(); + } + + private T[] Read() + { + var arr = new T[Capacity]; + int base_ofs = 2 + Capacity; + for (int i = 0; i < Capacity; i++) + arr[i] = GetEntry(base_ofs, i); + return arr; + } + + public byte[] Write() + { + // Rebuild GB Species ID table + int count = Array.FindIndex(Pokemon, pk => pk.Species == 0); + Count = count < 0 ? Capacity : (byte)count; + int base_ofs = 1 + 1 + Capacity; + for (int i = 0; i < Count; i++) + { + Data[1 + i] = GetSpeciesBoxIdentifier(Pokemon[i]); + SetEntry(base_ofs, i); + } + Data[1 + Count] = SLOT_NONE; + return Data; + } + + private T GetEntry(int base_ofs, int index) + { + int pkOfs = GetOffsetPKMData(base_ofs, index); + int otOfs = GetOffsetPKMOT(base_ofs, index); + int nkOfs = GetOffsetPKMNickname(base_ofs, index); + + var dat = Data.Slice(pkOfs, Entry_Size); + var otname = Data.AsSpan(otOfs, StringLength); + var nick = Data.AsSpan(nkOfs, StringLength); + + return GetEntry(dat, otname, nick, Data[1 + index] == 0xFD); + } + + private void SetEntry(int base_ofs, int index) + { + int pkOfs = GetOffsetPKMData(base_ofs, index); + int otOfs = GetOffsetPKMOT(base_ofs, index); + int nkOfs = GetOffsetPKMNickname(base_ofs, index); + + var pk = Pokemon[index]; + Array.Copy(pk.Data, 0, Data, pkOfs, Entry_Size); + pk.RawOT.CopyTo(Data, otOfs); + pk.RawNickname.CopyTo(Data, nkOfs); + } +} diff --git a/PKHeX.Core/PKM/Shared/PokeListType.cs b/PKHeX.Core/PKM/Shared/PokeListType.cs index c9c1a6673..56b2f9276 100644 --- a/PKHeX.Core/PKM/Shared/PokeListType.cs +++ b/PKHeX.Core/PKM/Shared/PokeListType.cs @@ -1,21 +1,23 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Type of that is stored. +/// +/// Acts as a magic capacity value; there aren't any other List sizes used by the games. +public enum PokeListType : byte { - /// - /// Type of that is stored. - /// - /// Acts as a magic capacity value; there aren't any other List sizes used by the games. - public enum PokeListType : byte - { - /// A single is contained in this list. - Single = 1, + /// Invalid type. + None = 0, - /// A full party of is contained in this list. - Party = 6, + /// A single is contained in this list. + Single = 1, - /// A full box of (International format) is contained in this list. - Stored = 20, + /// A full party of is contained in this list. + Party = 6, - /// A full box of (Japanese) is contained in this list. - StoredJP = 30, - } + /// A full box of (International format) is contained in this list. + Stored = 20, + + /// A full box of (Japanese) is contained in this list. + StoredJP = 30, } diff --git a/PKHeX.Core/PKM/Strings/StringConverter4Util.cs b/PKHeX.Core/PKM/Strings/StringConverter4Util.cs index 1ab3e7473..6dbaf367b 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter4Util.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter4Util.cs @@ -32,7 +32,7 @@ public static ushort ConvertChar2ValueG4(ushort chr) } /// - /// Strips diacritics on gen1-4 french pkm names + /// Strips diacritics on gen1-4 french pk names /// /// String to clean /// Cleaned string diff --git a/PKHeX.Core/PKM/Strings/StringConverter7ZH.cs b/PKHeX.Core/PKM/Strings/StringConverter7ZH.cs index f6a3bcf6a..46e43e678 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter7ZH.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter7ZH.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace PKHeX.Core; @@ -18,7 +18,7 @@ public static class StringConverter7ZH private const char Gen7_ZH_Ofs = '\uE800'; private const ushort SM_ZHCharTable_Size = 0x30F; private const ushort USUM_CHS_Size = 0x4; - private static bool GetisG7CHSChar(int idx) => idx is < SM_ZHCharTable_Size or >= SM_ZHCharTable_Size * 2 and < (SM_ZHCharTable_Size * 2) + USUM_CHS_Size; + private static bool GetisG7CHSChar(int idx) => idx is < SM_ZHCharTable_Size or (>= SM_ZHCharTable_Size * 2 and < (SM_ZHCharTable_Size * 2) + USUM_CHS_Size); // Unicode -> u16 conversion private static readonly Dictionary G7_CHS = GetRemapper(true); diff --git a/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs b/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs index 17170f7af..c7ddf2c5c 100644 --- a/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs +++ b/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs @@ -55,12 +55,12 @@ public static bool IsConvertibleToFormat(PKM pk, int format) return pk; } - var pkm = ConvertPKM(pk, destType, fromType, out result); - if (pkm is not null) + var entity = ConvertPKM(pk, destType, fromType, out result); + if (entity is not null) { if (RejuvenateHOME != EntityRejuvenationSetting.None) - RejuvenatorHOME.Rejuvenate(pkm, pk); - return pkm; + RejuvenatorHOME.Rejuvenate(entity, pk); + return entity; } if (AllowIncompatibleConversion != EntityCompatibilitySetting.AllowIncompatibleAll) @@ -72,12 +72,12 @@ public static bool IsConvertibleToFormat(PKM pk, int format) } // Try Incompatible Conversion - pkm = EntityBlank.GetBlank(destType); - pk.TransferPropertiesWithReflection(pkm); - if (!IsCompatibleWithModifications(pkm)) + entity = EntityBlank.GetBlank(destType); + pk.TransferPropertiesWithReflection(entity); + if (!IsCompatibleWithModifications(entity)) return null; // NoTransferRoute result = SuccessIncompatibleReflection; - return pkm; + return entity; } private static PKM? ConvertPKM(PKM pk, Type destType, Type srcType, out EntityConverterResult result) @@ -97,16 +97,16 @@ public static bool IsConvertibleToFormat(PKM pk, int format) private static PKM? ConvertPKM(PKM pk, Type destType, ref EntityConverterResult result) { - PKM? pkm = pk.Clone(); - if (pkm.IsEgg) - pkm.ForceHatchPKM(); + PKM? entity = pk.Clone(); + if (entity.IsEgg) + entity.ForceHatchPKM(); while (true) { - pkm = IntermediaryConvert(pkm, destType, ref result); - if (pkm == null) // fail convert + entity = IntermediaryConvert(entity, destType, ref result); + if (entity == null) // fail convert return null; - if (pkm.GetType() == destType) // finish convert - return pkm; + if (entity.GetType() == destType) // finish convert + return entity; } } diff --git a/PKHeX.Core/PKM/Util/Conversion/FormConverter.cs b/PKHeX.Core/PKM/Util/Conversion/FormConverter.cs index 1c24bbd94..3a16ef15d 100644 --- a/PKHeX.Core/PKM/Util/Conversion/FormConverter.cs +++ b/PKHeX.Core/PKM/Util/Conversion/FormConverter.cs @@ -2,453 +2,453 @@ using System.Collections.Generic; using static PKHeX.Core.Species; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Retrieves localized form names for indicating values. +/// +public static class FormConverter { /// - /// Retrieves localized form names for indicating values. + /// Gets a list of forms that the species can have. /// - public static class FormConverter + /// of the Pokémon. + /// List of type names + /// List of form names + /// List of genders names + /// Generation number for exclusive forms + /// A list of strings corresponding to the forms that a Pokémon can have. + public static string[] GetFormList(int species, IReadOnlyList types, IReadOnlyList forms, IReadOnlyList genders, int generation) { - /// - /// Gets a list of forms that the species can have. - /// - /// of the Pokémon. - /// List of type names - /// List of form names - /// List of genders names - /// Generation number for exclusive forms - /// A list of strings corresponding to the forms that a Pokémon can have. - public static string[] GetFormList(int species, IReadOnlyList types, IReadOnlyList forms, IReadOnlyList genders, int generation) + // Mega List + if (generation < 8 && IsFormListSingleMega(species)) + return GetMegaSingle(types, forms); + + if (generation == 7 && Legal.Totem_USUM.Contains(species)) + return GetFormsTotem(species, types, forms); + + return species switch { - // Mega List - if (generation < 8 && IsFormListSingleMega(species)) - return GetMegaSingle(types, forms); + <= Legal.MaxSpeciesID_1 => GetFormsGen1(species, types, forms, generation), + <= Legal.MaxSpeciesID_2 => GetFormsGen2(species, types, forms, generation), + <= Legal.MaxSpeciesID_3 => GetFormsGen3(species, types, forms, generation), + <= Legal.MaxSpeciesID_4 => GetFormsGen4(species, types, forms, generation), + <= Legal.MaxSpeciesID_5 => GetFormsGen5(species, types, forms, generation), + <= Legal.MaxSpeciesID_6 => GetFormsGen6(species, types, forms, genders, generation), + <= Legal.MaxSpeciesID_7_USUM => GetFormsGen7(species, types, forms, generation), + _ => GetFormsGen8(species, generation, types, forms, genders), + }; + } - if (generation == 7 && Legal.Totem_USUM.Contains(species)) - return GetFormsTotem(species, types, forms); + // this is a hack; depends on currently loaded SaveFile's Game ID + private static bool IsGG() => RecentTrainerCache.Game is (int)GameVersion.GP or (int)GameVersion.GE; - return species switch - { - <= Legal.MaxSpeciesID_1 => GetFormsGen1(species, types, forms, generation), - <= Legal.MaxSpeciesID_2 => GetFormsGen2(species, types, forms, generation), - <= Legal.MaxSpeciesID_3 => GetFormsGen3(species, types, forms, generation), - <= Legal.MaxSpeciesID_4 => GetFormsGen4(species, types, forms, generation), - <= Legal.MaxSpeciesID_5 => GetFormsGen5(species, types, forms, generation), - <= Legal.MaxSpeciesID_6 => GetFormsGen6(species, types, forms, genders, generation), - <= Legal.MaxSpeciesID_7_USUM => GetFormsGen7(species, types, forms, generation), - _ => GetFormsGen8(species, generation, types, forms, genders), - }; - } + private static readonly string[] EMPTY = { string.Empty }; + private const string Starter = nameof(Starter); - // this is a hack; depends on currently loaded SaveFile's Game ID - private static bool IsGG() => RecentTrainerCache.Game is (int)GameVersion.GP or (int)GameVersion.GE; - - private static readonly string[] EMPTY = { string.Empty }; - private const string Starter = nameof(Starter); - - private static string[] GetFormsGen1(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + private static string[] GetFormsGen1(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + { + return (Species)species switch { - return (Species)species switch + Charizard or Mewtwo when generation < 8 => GetMegaXY(types, forms), + Eevee when IsGG() => new[] { - Charizard or Mewtwo when generation < 8 => GetMegaXY(types, forms), - Eevee when IsGG() => new[] - { - types[000], // Normal - Starter, - }, - Pikachu => GetFormsPikachu(generation, types, forms), - Slowbro when generation >= 8 => GetFormsGalarSlowbro(types, forms), + types[000], // Normal + Starter, + }, + Pikachu => GetFormsPikachu(generation, types, forms), + Slowbro when generation >= 8 => GetFormsGalarSlowbro(types, forms), - Weezing or Ponyta or Rapidash or Slowpoke or MrMime or Farfetchd + Weezing or Ponyta or Rapidash or Slowpoke or MrMime or Farfetchd or Articuno or Zapdos or Moltres when generation >= 8 => GetFormsGalar(types, forms), - Growlithe or Arcanine or Voltorb or Electrode when generation >= 8 => GetFormsHisui(species, generation, types, forms), + Growlithe or Arcanine or Voltorb or Electrode when generation >= 8 => GetFormsHisui(species, generation, types, forms), - _ => GetFormsAlolan(generation, types, forms, species), - }; - } + _ => GetFormsAlolan(generation, types, forms, species), + }; + } - private static string[] GetFormsGen2(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + private static string[] GetFormsGen2(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + { + return (Species)species switch { - return (Species)species switch - { - Pichu when generation == 4 => GetFormsPichu(types, forms), - Slowking or Corsola when generation >= 8 => GetFormsGalar(types, forms), - Typhlosion or Qwilfish or Sneasel when generation >= 8 => GetFormsHisui(species, generation, types, forms), - Unown => GetFormsUnown(generation), - _ => EMPTY, - }; - } + Pichu when generation == 4 => GetFormsPichu(types, forms), + Slowking or Corsola when generation >= 8 => GetFormsGalar(types, forms), + Typhlosion or Qwilfish or Sneasel when generation >= 8 => GetFormsHisui(species, generation, types, forms), + Unown => GetFormsUnown(generation), + _ => EMPTY, + }; + } - private static string[] GetFormsGen3(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + private static string[] GetFormsGen3(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + { + return (Species)species switch { - return (Species)species switch - { - Zigzagoon or Linoone when generation >= 8 => GetFormsGalar(types, forms), - Castform => new[] { - types[000], // Normal - forms[889], // Sunny - forms[890], // Rainy - forms[891], // Snowy - }, - Kyogre when generation < 8 => new[] { - types[000], // Normal - forms[899], // Primal - }, - Groudon when generation < 8 => new[] { - types[000], // Normal - forms[899], // Primal - }, - Deoxys => new[] { - types[000], // Normal - forms[902], // Attack - forms[903], // Defense - forms[904], // Speed - }, - _ => EMPTY, - }; - } + Zigzagoon or Linoone when generation >= 8 => GetFormsGalar(types, forms), + Castform => new[] { + types[000], // Normal + forms[889], // Sunny + forms[890], // Rainy + forms[891], // Snowy + }, + Kyogre when generation < 8 => new[] { + types[000], // Normal + forms[899], // Primal + }, + Groudon when generation < 8 => new[] { + types[000], // Normal + forms[899], // Primal + }, + Deoxys => new[] { + types[000], // Normal + forms[902], // Attack + forms[903], // Defense + forms[904], // Speed + }, + _ => EMPTY, + }; + } - private static string[] GetFormsGen4(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + private static string[] GetFormsGen4(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + { + return (Species)species switch { - return (Species)species switch - { - Burmy or Wormadam or Mothim => new[] { - forms[412], // Plant - forms[905], // Sandy - forms[906], // Trash - }, - Cherrim => new[] { - forms[421], // Overcast - forms[909], // Sunshine - }, - Shellos or Gastrodon => new[] { - forms[422], // West - forms[911], // East - }, - Rotom => new[] { - types[000], // Normal - forms[917], // Heat - forms[918], // Wash - forms[919], // Frost - forms[920], // Fan - forms[921], // Mow - }, - Dialga or Palkia when generation >= 8 => new[] { - types[000], // Normal - forms[922], // Origin - }, - Giratina => new[] { - forms[487], // Altered - forms[922], // Origin - }, - Shaymin => new[] { - forms[492], // Land - forms[923], // Sky - }, - Arceus => GetFormsArceus(species, generation, types, forms), - _ => EMPTY, - }; - } + Burmy or Wormadam or Mothim => new[] { + forms[412], // Plant + forms[905], // Sandy + forms[906], // Trash + }, + Cherrim => new[] { + forms[421], // Overcast + forms[909], // Sunshine + }, + Shellos or Gastrodon => new[] { + forms[422], // West + forms[911], // East + }, + Rotom => new[] { + types[000], // Normal + forms[917], // Heat + forms[918], // Wash + forms[919], // Frost + forms[920], // Fan + forms[921], // Mow + }, + Dialga or Palkia when generation >= 8 => new[] { + types[000], // Normal + forms[922], // Origin + }, + Giratina => new[] { + forms[487], // Altered + forms[922], // Origin + }, + Shaymin => new[] { + forms[492], // Land + forms[923], // Sky + }, + Arceus => GetFormsArceus(species, generation, types, forms), + _ => EMPTY, + }; + } - private static string[] GetFormsGen5(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + private static string[] GetFormsGen5(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + { + return (Species)species switch { - return (Species)species switch - { - Samurott or Lilligant or Zorua or Zoroark or Braviary when generation >= 8 => GetFormsHisui(species, generation, types, forms), - Basculin when generation >= 8 => new[] { - forms[550], // Red - forms[942], // Blue - forms[989], // White - }, - Basculin => new[] { - forms[550], // Red - forms[942], // Blue - }, - Darumaka or Stunfisk or Yamask when generation >= 8 => GetFormsGalar(types, forms), - Darmanitan when generation >= 8 => new[] { - forms[555], // Standard - forms[943], // Zen - forms[Galarian], // Standard - forms[Galarian] + " " + forms[943], // Zen - }, - Darmanitan => new[] { - forms[555], // Standard - forms[943], // Zen - }, - Deerling or Sawsbuck => new[] { - forms[585], // Spring - forms[947], // Summer - forms[948], // Autumn - forms[949], // Winter - }, - Tornadus or Thundurus or Landorus => new[] { - forms[641], // Incarnate - forms[952], // Therian - }, - Kyurem => new[] { - types[000], // Normal - forms[953], // White - forms[954], // Black - }, - Keldeo => new[] { - forms[647], // Ordinary - forms[955], // Resolute - }, - Meloetta => new[] { - forms[648], // Aria - forms[956], // Pirouette - }, - Genesect => new[] { - types[000], // Normal - types[010], // Douse (Water) - types[012], // Shock (Electric) - types[009], // Burn (Fire) - types[014], // Chill (Ice) - }, - _ => EMPTY, - }; - } + Samurott or Lilligant or Zorua or Zoroark or Braviary when generation >= 8 => GetFormsHisui(species, generation, types, forms), + Basculin when generation >= 8 => new[] { + forms[550], // Red + forms[942], // Blue + forms[989], // White + }, + Basculin => new[] { + forms[550], // Red + forms[942], // Blue + }, + Darumaka or Stunfisk or Yamask when generation >= 8 => GetFormsGalar(types, forms), + Darmanitan when generation >= 8 => new[] { + forms[555], // Standard + forms[943], // Zen + forms[Galarian], // Standard + forms[Galarian] + " " + forms[943], // Zen + }, + Darmanitan => new[] { + forms[555], // Standard + forms[943], // Zen + }, + Deerling or Sawsbuck => new[] { + forms[585], // Spring + forms[947], // Summer + forms[948], // Autumn + forms[949], // Winter + }, + Tornadus or Thundurus or Landorus => new[] { + forms[641], // Incarnate + forms[952], // Therian + }, + Kyurem => new[] { + types[000], // Normal + forms[953], // White + forms[954], // Black + }, + Keldeo => new[] { + forms[647], // Ordinary + forms[955], // Resolute + }, + Meloetta => new[] { + forms[648], // Aria + forms[956], // Pirouette + }, + Genesect => new[] { + types[000], // Normal + types[010], // Douse (Water) + types[012], // Shock (Electric) + types[009], // Burn (Fire) + types[014], // Chill (Ice) + }, + _ => EMPTY, + }; + } - private static string[] GetFormsGen6(int species, IReadOnlyList types, IReadOnlyList forms, IReadOnlyList genders, int generation) + private static string[] GetFormsGen6(int species, IReadOnlyList types, IReadOnlyList forms, IReadOnlyList genders, int generation) + { + return (Species)species switch { - return (Species)species switch - { - Greninja => new[] { - types[000], // Normal - forms[962], // "Ash", - forms[1012], // "Bonded" - Active - }, - Scatterbug or Spewpa or Vivillon => new[] { - forms[666], // Icy Snow - forms[963], // Polar - forms[964], // Tundra - forms[965], // Continental - forms[966], // Garden - forms[967], // Elegant - forms[968], // Meadow - forms[969], // Modern - forms[970], // Marine - forms[971], // Archipelago - forms[972], // High-Plains - forms[973], // Sandstorm - forms[974], // River - forms[975], // Monsoon - forms[976], // Savanna - forms[977], // Sun - forms[978], // Ocean - forms[979], // Jungle - forms[980], // Fancy - forms[981], // Poké Ball - }, - Flabébé or Florges => new[] { - forms[669], // Red - forms[986], // Yellow - forms[987], // Orange - forms[988], // Blue - forms[989], // White - }, - Floette => new[] { - forms[669], // Red - forms[986], // Yellow - forms[987], // Orange - forms[988], // Blue - forms[989], // White - forms[990], // Eternal - }, - Furfrou => new[] { - forms[676], // Natural - forms[995], // Heart - forms[996], // Star - forms[997], // Diamond - forms[998], // Debutante - forms[999], // Matron - forms[1000], // Dandy - forms[1001], // La Reine - forms[1002], // Kabuki - forms[1003], // Pharaoh - }, - Meowstic => new[] { - genders[000], // Male - genders[001], // Female - }, - Aegislash => new[] { - forms[681], // Shield - forms[1005], // Blade - }, - Sliggoo or Goodra or Avalugg when generation >= 8 => GetFormsHisui(species, generation, types, forms), - Pumpkaboo or Gourgeist => new[] { - forms[710], // Average - forms[1006], // Small - forms[1007], // Large - forms[1008], // Super - }, - Xerneas => new[] { - forms[716], // Neutral - forms[1012], // Active - }, - Hoopa => new[] { - forms[720], // Confined - forms[1018], // Unbound - }, - Zygarde => new[] { - forms[718], // 50% (Aura Break) - forms[1013], // 10% (Aura Break) - forms[1014] + "-C", // 10% Cell (Power Construct) - forms[1015] + "-C", // 50% Cell (Power Construct) - forms[1016], // 100% Cell (Power Construct) - }, - _ => EMPTY, - }; - } + Greninja => new[] { + types[000], // Normal + forms[962], // "Ash", + forms[1012], // "Bonded" - Active + }, + Scatterbug or Spewpa or Vivillon => new[] { + forms[666], // Icy Snow + forms[963], // Polar + forms[964], // Tundra + forms[965], // Continental + forms[966], // Garden + forms[967], // Elegant + forms[968], // Meadow + forms[969], // Modern + forms[970], // Marine + forms[971], // Archipelago + forms[972], // High-Plains + forms[973], // Sandstorm + forms[974], // River + forms[975], // Monsoon + forms[976], // Savanna + forms[977], // Sun + forms[978], // Ocean + forms[979], // Jungle + forms[980], // Fancy + forms[981], // Poké Ball + }, + Flabébé or Florges => new[] { + forms[669], // Red + forms[986], // Yellow + forms[987], // Orange + forms[988], // Blue + forms[989], // White + }, + Floette => new[] { + forms[669], // Red + forms[986], // Yellow + forms[987], // Orange + forms[988], // Blue + forms[989], // White + forms[990], // Eternal + }, + Furfrou => new[] { + forms[676], // Natural + forms[995], // Heart + forms[996], // Star + forms[997], // Diamond + forms[998], // Debutante + forms[999], // Matron + forms[1000], // Dandy + forms[1001], // La Reine + forms[1002], // Kabuki + forms[1003], // Pharaoh + }, + Meowstic => new[] { + genders[000], // Male + genders[001], // Female + }, + Aegislash => new[] { + forms[681], // Shield + forms[1005], // Blade + }, + Sliggoo or Goodra or Avalugg when generation >= 8 => GetFormsHisui(species, generation, types, forms), + Pumpkaboo or Gourgeist => new[] { + forms[710], // Average + forms[1006], // Small + forms[1007], // Large + forms[1008], // Super + }, + Xerneas => new[] { + forms[716], // Neutral + forms[1012], // Active + }, + Hoopa => new[] { + forms[720], // Confined + forms[1018], // Unbound + }, + Zygarde => new[] { + forms[718], // 50% (Aura Break) + forms[1013], // 10% (Aura Break) + forms[1014] + "-C", // 10% Cell (Power Construct) + forms[1015] + "-C", // 50% Cell (Power Construct) + forms[1016], // 100% Cell (Power Construct) + }, + _ => EMPTY, + }; + } - private static string[] GetFormsGen7(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + private static string[] GetFormsGen7(int species, IReadOnlyList types, IReadOnlyList forms, int generation) + { + return (Species)species switch { - return (Species)species switch - { - Decidueye when generation >= 8 => GetFormsHisui(species, generation, types, forms), - Oricorio => new[] { - forms[741], // "RED" - Baile - forms[1021], // "YLW" - Pom-Pom - forms[1022], // "PNK" - Pa'u - forms[1023], // "BLU" - Sensu - }, - Rockruff => new[] { - types[0], // Normal - forms[1064], // Dusk - }, - Lycanroc => new[] { - forms[745], // Midday - forms[1024], // Midnight - forms[1064], // Dusk - }, - Wishiwashi => new[] { - forms[746], - forms[1025], // School - }, - Silvally => GetFormsArceus(species, 7, types, forms), - Minior => new[] { - forms[774], // "R-Meteor", // Meteor Red - forms[1045], // "O-Meteor", // Meteor Orange - forms[1046], // "Y-Meteor", // Meteor Yellow - forms[1047], // "G-Meteor", // Meteor Green - forms[1048], // "B-Meteor", // Meteor Blue - forms[1049], // "I-Meteor", // Meteor Indigo - forms[1050], // "V-Meteor", // Meteor Violet - forms[1051], // "R-Core", // Core Red - forms[1052], // "O-Core", // Core Orange - forms[1053], // "Y-Core", // Core Yellow - forms[1054], // "G-Core", // Core Green - forms[1055], // "B-Core", // Core Blue - forms[1056], // "I-Core", // Core Indigo - forms[1057], // "V-Core", // Core Violet - }, - Mimikyu => new[] { - forms[778], // Disguised - forms[1058], // Busted - }, - Necrozma => new[] { - types[000], // Normal - forms[1065], // Dusk Mane - forms[1066], // Dawn Wings - forms[1067], // Ultra Necrozma - }, - Magearna => new[] { - types[000], - forms[1062], // Original - }, - _ => EMPTY, - }; - } + Decidueye when generation >= 8 => GetFormsHisui(species, generation, types, forms), + Oricorio => new[] { + forms[741], // "RED" - Baile + forms[1021], // "YLW" - Pom-Pom + forms[1022], // "PNK" - Pa'u + forms[1023], // "BLU" - Sensu + }, + Rockruff => new[] { + types[0], // Normal + forms[1064], // Dusk + }, + Lycanroc => new[] { + forms[745], // Midday + forms[1024], // Midnight + forms[1064], // Dusk + }, + Wishiwashi => new[] { + forms[746], + forms[1025], // School + }, + Silvally => GetFormsArceus(species, 7, types, forms), + Minior => new[] { + forms[774], // "R-Meteor", // Meteor Red + forms[1045], // "O-Meteor", // Meteor Orange + forms[1046], // "Y-Meteor", // Meteor Yellow + forms[1047], // "G-Meteor", // Meteor Green + forms[1048], // "B-Meteor", // Meteor Blue + forms[1049], // "I-Meteor", // Meteor Indigo + forms[1050], // "V-Meteor", // Meteor Violet + forms[1051], // "R-Core", // Core Red + forms[1052], // "O-Core", // Core Orange + forms[1053], // "Y-Core", // Core Yellow + forms[1054], // "G-Core", // Core Green + forms[1055], // "B-Core", // Core Blue + forms[1056], // "I-Core", // Core Indigo + forms[1057], // "V-Core", // Core Violet + }, + Mimikyu => new[] { + forms[778], // Disguised + forms[1058], // Busted + }, + Necrozma => new[] { + types[000], // Normal + forms[1065], // Dusk Mane + forms[1066], // Dawn Wings + forms[1067], // Ultra Necrozma + }, + Magearna => new[] { + types[000], + forms[1062], // Original + }, + _ => EMPTY, + }; + } - private static string[] GetFormsGen8(int species, int generation, IReadOnlyList types, IReadOnlyList forms, IReadOnlyList genders) + private static string[] GetFormsGen8(int species, int generation, IReadOnlyList types, IReadOnlyList forms, IReadOnlyList genders) + { + return (Species)species switch { - return (Species)species switch - { - Cramorant => new[] { - types[0], // Normal - forms[Gulping], - forms[Gorging], - }, - Toxtricity => new[] { - forms[(int)Toxtricity], // Amped - forms[LowKey], - }, - Indeedee or Basculegion => new[] { - genders[000], // Male - genders[001], // Female - }, - Sinistea or Polteageist => new[] { - "Phony", - "Antique", - }, - Alcremie => new[] { - forms[(int)Alcremie], // Vanilla Cream - forms[RubyCream], - forms[MatchaCream], - forms[MintCream], - forms[LemonCream], - forms[SaltedCream], - forms[RubySwirl], - forms[CaramelSwirl], - forms[RainbowSwirl], - }, - Morpeko => new[] { - forms[FullBellyMode], - forms[HangryMode], - }, - Eiscue => new[] { - forms[IceFace], - forms[NoiceFace], - }, - Zacian or Zamazenta => new[] { - forms[Hero], - forms[Crowned], - }, - Eternatus => new[] { - types[0], // Normal - forms[Eternamax], - }, - Urshifu => new[] { - forms[SingleStrike], - forms[RapidStrike], - }, - Zarude => new[] { - types[0], // Normal - forms[Dada], - }, - Calyrex => new[] { - types[0], // Normal - forms[CalyIce], - forms[CalyGhost], - }, - Kleavor when generation == 8 => new[] { - types[000], - forms[Lord], - }, - Enamorus => new[] { - forms[641], // Incarnate - forms[952], // Therian - }, - _ => EMPTY, - }; - } + Cramorant => new[] { + types[0], // Normal + forms[Gulping], + forms[Gorging], + }, + Toxtricity => new[] { + forms[(int)Toxtricity], // Amped + forms[LowKey], + }, + Indeedee or Basculegion => new[] { + genders[000], // Male + genders[001], // Female + }, + Sinistea or Polteageist => new[] { + "Phony", + "Antique", + }, + Alcremie => new[] { + forms[(int)Alcremie], // Vanilla Cream + forms[RubyCream], + forms[MatchaCream], + forms[MintCream], + forms[LemonCream], + forms[SaltedCream], + forms[RubySwirl], + forms[CaramelSwirl], + forms[RainbowSwirl], + }, + Morpeko => new[] { + forms[FullBellyMode], + forms[HangryMode], + }, + Eiscue => new[] { + forms[IceFace], + forms[NoiceFace], + }, + Zacian or Zamazenta => new[] { + forms[Hero], + forms[Crowned], + }, + Eternatus => new[] { + types[0], // Normal + forms[Eternamax], + }, + Urshifu => new[] { + forms[SingleStrike], + forms[RapidStrike], + }, + Zarude => new[] { + types[0], // Normal + forms[Dada], + }, + Calyrex => new[] { + types[0], // Normal + forms[CalyIce], + forms[CalyGhost], + }, + Kleavor when generation == 8 => new[] { + types[000], + forms[Lord], + }, + Enamorus => new[] { + forms[641], // Incarnate + forms[952], // Therian + }, + _ => EMPTY, + }; + } - private static string[] GetFormsAlolan(int generation, IReadOnlyList types, IReadOnlyList forms, int species) + private static string[] GetFormsAlolan(int generation, IReadOnlyList types, IReadOnlyList forms, int species) + { + if (generation < 7) + return EMPTY; + + return (Species)species switch { - if (generation < 7) - return EMPTY; + Meowth when generation >= 8 => new[] { + types[000], + forms[810], // Alolan + forms[Galarian], // Alolan + }, - return (Species)species switch - { - Meowth when generation >= 8 => new[] { - types[000], - forms[810], // Alolan - forms[Galarian], // Alolan - }, - - // Only reached when Gen8+, as Totem logic picks up Gen7 earlier. - Rattata or Raticate + // Only reached when Gen8+, as Totem logic picks up Gen7 earlier. + Rattata or Raticate or Raichu or Sandshrew or Sandslash or Vulpix or Ninetales @@ -461,365 +461,364 @@ or Exeggutor types[000], forms[810], // Alolan }, - _ => EMPTY, - }; - } + _ => EMPTY, + }; + } - private static string[] GetFormsPikachu(int generation, IReadOnlyList types, IReadOnlyList forms) + private static string[] GetFormsPikachu(int generation, IReadOnlyList types, IReadOnlyList forms) + { + return generation switch { - return generation switch - { - 6 => new[] { - types[000], // Normal - forms[729], // Rockstar - forms[730], // Belle - forms[731], // Pop - forms[732], // PhD - forms[733], // Libre - forms[734], // Cosplay - }, - 7 when IsGG() => new[] { - types[000], // Normal - forms[813], // Original - forms[814], // Hoenn - forms[815], // Sinnoh - forms[816], // Unova - forms[817], // Kalos - forms[818], // Alola - forms[1063], // Partner - Starter, - }, - 7 => new[] { - types[000], // Normal - forms[813], // Original - forms[814], // Hoenn - forms[815], // Sinnoh - forms[816], // Unova - forms[817], // Kalos - forms[818], // Alola - forms[1063], // Partner - }, - 8 => new[] { - types[000], // Normal - forms[813], // Original - forms[814], // Hoenn - forms[815], // Sinnoh - forms[816], // Unova - forms[817], // Kalos - forms[818], // Alola - forms[1063], // Partner - Starter, - forms[1085], // World - }, - _ => EMPTY, - }; - } + 6 => new[] { + types[000], // Normal + forms[729], // Rockstar + forms[730], // Belle + forms[731], // Pop + forms[732], // PhD + forms[733], // Libre + forms[734], // Cosplay + }, + 7 when IsGG() => new[] { + types[000], // Normal + forms[813], // Original + forms[814], // Hoenn + forms[815], // Sinnoh + forms[816], // Unova + forms[817], // Kalos + forms[818], // Alola + forms[1063], // Partner + Starter, + }, + 7 => new[] { + types[000], // Normal + forms[813], // Original + forms[814], // Hoenn + forms[815], // Sinnoh + forms[816], // Unova + forms[817], // Kalos + forms[818], // Alola + forms[1063], // Partner + }, + 8 => new[] { + types[000], // Normal + forms[813], // Original + forms[814], // Hoenn + forms[815], // Sinnoh + forms[816], // Unova + forms[817], // Kalos + forms[818], // Alola + forms[1063], // Partner + Starter, + forms[1085], // World + }, + _ => EMPTY, + }; + } - private static string[] GetFormsPichu(IReadOnlyList types, IReadOnlyList forms) + private static string[] GetFormsPichu(IReadOnlyList types, IReadOnlyList forms) + { + return new[] + { + types[000], // Normal + forms[000], // Spiky + }; + } + + private static string[] GetFormsArceus(int species, int generation, IReadOnlyList types, IReadOnlyList forms) + { + return generation switch + { + 4 => new[] { + types[00], // Normal + types[01], // Fighting + types[02], // Flying + types[03], // Poison + types[04], // etc + types[05], + types[06], + types[07], + types[08], + "???", // ???-type arceus + types[09], + types[10], + types[11], + types[12], + types[13], + types[14], + types[15], + types[16], // No Fairy Type + }, + 5 => new[] { + types[00], // Normal + types[01], // Fighting + types[02], // Flying + types[03], // Poison + types[04], // etc + types[05], + types[06], + types[07], + types[08], + types[09], + types[10], + types[11], + types[12], + types[13], + types[14], + types[15], + types[16], // No Fairy type + }, + 8 when (Species)species is Arceus => new[] + { + types[00], // Normal + types[01], // Fighting + types[02], // Flying + types[03], // Poison + types[04], // etc + types[05], + types[06], + types[07], + types[08], + types[09], + types[10], + types[11], + types[12], + types[13], + types[14], + types[15], + types[16], + types[17], + forms[Legend], + }, + _ => new[] { + types[00], // Normal + types[01], // Fighting + types[02], // Flying + types[03], // Poison + types[04], // etc + types[05], + types[06], + types[07], + types[08], + types[09], + types[10], + types[11], + types[12], + types[13], + types[14], + types[15], + types[16], + types[17], + }, + }; + } + + private static string[] GetFormsTotem(int species, IReadOnlyList types, IReadOnlyList forms) + { + if ((Species)species == Mimikyu) // Mimikyu { return new[] { - types[000], // Normal - forms[000], // Spiky + forms[778], // Disguised + forms[1058], // Busted + forms[1007], // Large + "*" + forms[1058], // Busted }; } - private static string[] GetFormsArceus(int species, int generation, IReadOnlyList types, IReadOnlyList forms) + if (Legal.Totem_Alolan.Contains(species)) { - return generation switch - { - 4 => new[] { - types[00], // Normal - types[01], // Fighting - types[02], // Flying - types[03], // Poison - types[04], // etc - types[05], - types[06], - types[07], - types[08], - "???", // ???-type arceus - types[09], - types[10], - types[11], - types[12], - types[13], - types[14], - types[15], - types[16], // No Fairy Type - }, - 5 => new[] { - types[00], // Normal - types[01], // Fighting - types[02], // Flying - types[03], // Poison - types[04], // etc - types[05], - types[06], - types[07], - types[08], - types[09], - types[10], - types[11], - types[12], - types[13], - types[14], - types[15], - types[16], // No Fairy type - }, - 8 when (Species)species is Arceus => new[] - { - types[00], // Normal - types[01], // Fighting - types[02], // Flying - types[03], // Poison - types[04], // etc - types[05], - types[06], - types[07], - types[08], - types[09], - types[10], - types[11], - types[12], - types[13], - types[14], - types[15], - types[16], - types[17], - forms[Legend], - }, - _ => new[] { - types[00], // Normal - types[01], // Fighting - types[02], // Flying - types[03], // Poison - types[04], // etc - types[05], - types[06], - types[07], - types[08], - types[09], - types[10], - types[11], - types[12], - types[13], - types[14], - types[15], - types[16], - types[17], - }, - }; - } - - private static string[] GetFormsTotem(int species, IReadOnlyList types, IReadOnlyList forms) - { - if ((Species)species == Mimikyu) // Mimikyu - { - return new[] - { - forms[778], // Disguised - forms[1058], // Busted - forms[1007], // Large - "*" + forms[1058], // Busted - }; - } - - if (Legal.Totem_Alolan.Contains(species)) - { - return new[] - { - types[0], // Normal - forms[810], // Alolan - forms[1007], // Large - }; - } - return new[] { types[0], // Normal + forms[810], // Alolan forms[1007], // Large }; } - private static string[] GetFormsUnown(int generation) + return new[] { - return generation switch - { - 2 => new[] - { - "A", "B", "C", "D", "E", - "F", "G", "H", "I", "J", - "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", - "U", "V", "W", "X", "Y", - "Z", - // "!", "?", not in Gen II - }, - _ => new[] - { - "A", "B", "C", "D", "E", - "F", "G", "H", "I", "J", - "K", "L", "M", "N", "O", - "P", "Q", "R", "S", "T", - "U", "V", "W", "X", "Y", - "Z", - "!", "?", - }, - }; - } - - private static bool IsFormListSingleMega(int species) => Mega_6_Single.Contains(species); - - private static readonly HashSet Mega_6_Single = new() - { - // XY - 003, 009, 065, 094, 115, 127, 130, 142, 181, 212, 214, 229, 248, 257, 282, 303, 306, 308, 310, 354, 359, - 380, 381, 445, 448, 460, - - // AO - 015, 018, 080, 208, 254, 260, 302, 319, 323, 334, 362, 373, 376, 384, 428, 475, 531, 719, + types[0], // Normal + forms[1007], // Large }; + } - private static string[] GetMegaSingle(IReadOnlyList types, IReadOnlyList forms) + private static string[] GetFormsUnown(int generation) + { + return generation switch { - return new[] + 2 => new[] + { + "A", "B", "C", "D", "E", + "F", "G", "H", "I", "J", + "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", + "U", "V", "W", "X", "Y", + "Z", + // "!", "?", not in Gen II + }, + _ => new[] + { + "A", "B", "C", "D", "E", + "F", "G", "H", "I", "J", + "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", + "U", "V", "W", "X", "Y", + "Z", + "!", "?", + }, + }; + } + + private static bool IsFormListSingleMega(int species) => Mega_6_Single.Contains(species); + + private static readonly HashSet Mega_6_Single = new() + { + // XY + 003, 009, 065, 094, 115, 127, 130, 142, 181, 212, 214, 229, 248, 257, 282, 303, 306, 308, 310, 354, 359, + 380, 381, 445, 448, 460, + + // AO + 015, 018, 080, 208, 254, 260, 302, 319, 323, 334, 362, 373, 376, 384, 428, 475, 531, 719, + }; + + private static string[] GetMegaSingle(IReadOnlyList types, IReadOnlyList forms) + { + return new[] + { + types[000], // Normal + forms[804], // Mega + }; + } + + private static string[] GetMegaXY(IReadOnlyList types, IReadOnlyList forms) + { + return new[] + { + types[000], // Normal + forms[805], // Mega X + forms[806], // Mega Y + }; + } + + private static string[] GetFormsGalar(IReadOnlyList types, IReadOnlyList forms) + { + return new[] + { + types[000], // Normal + forms[Galarian], // Galarian + }; + } + + private static string[] GetFormsHisui(int species, int generation, IReadOnlyList types, IReadOnlyList forms) => generation switch + { + 8 => (Species)species switch + { + Lilligant => new[] { types[000], // Normal - forms[804], // Mega - }; - } - - private static string[] GetMegaXY(IReadOnlyList types, IReadOnlyList forms) - { - return new[] + forms[Hisuian], + forms[Lady], + }, + Arcanine or Electrode or Avalugg => new[] { types[000], // Normal - forms[805], // Mega X - forms[806], // Mega Y - }; - } - - private static string[] GetFormsGalar(IReadOnlyList types, IReadOnlyList forms) - { - return new[] - { - types[000], // Normal - forms[Galarian], // Galarian - }; - } - - private static string[] GetFormsHisui(int species, int generation, IReadOnlyList types, IReadOnlyList forms) => generation switch - { - 8 => (Species)species switch - { - Lilligant => new[] - { - types[000], // Normal - forms[Hisuian], - forms[Lady], - }, - Arcanine or Electrode or Avalugg => new[] - { - types[000], // Normal - forms[Hisuian], - forms[Lord], - }, - _ => new[] - { - types[000], // Normal - forms[Hisuian], - }, + forms[Hisuian], + forms[Lord], }, _ => new[] { types[000], // Normal forms[Hisuian], - } - }; - - private static string[] GetFormsGalarSlowbro(IReadOnlyList types, IReadOnlyList forms) + }, + }, + _ => new[] { - return new[] - { - types[000], // Normal - forms[804], // Mega - forms[Galarian], // Galarian - }; + types[000], // Normal + forms[Hisuian], } + }; - private const int Galarian = 1068; - private const int Gigantamax = 1069; - private const int Gulping = 1070; - private const int Gorging = 1071; - private const int LowKey = 1072; - - private const int RubyCream = 1073; - private const int MatchaCream = 1074; - private const int MintCream = 1075; - private const int LemonCream = 1076; - private const int SaltedCream = 1077; - private const int RubySwirl = 1078; - private const int CaramelSwirl = 1079; - private const int RainbowSwirl = 1080; - - private const int IceFace = 1091; - private const int NoiceFace = 1081; - private const int HangryMode = 1082; - private const int FullBellyMode = 1092; - - private const int Hero = 1093; - private const int Crowned = 1083; - private const int Eternamax = 1084; - - private const int SingleStrike = 1086; - private const int RapidStrike = 1087; - private const int Dada = 1088; - private const int CalyIce = 1089; // Ice - private const int CalyGhost = 1090; // Shadow - - private const int Hisuian = 1094; - private const int Lord = 1095; - private const int Lady = 1096; - private const int Legend = 1097; - - public static string GetGigantamaxName(IReadOnlyList forms) => forms[Gigantamax]; - - public static string[] GetAlcremieFormList(IReadOnlyList forms) + private static string[] GetFormsGalarSlowbro(IReadOnlyList types, IReadOnlyList forms) + { + return new[] { - var result = new string[63]; - // seed form0 with the pattern - result[0 * 7] = forms[(int) Alcremie]; // Vanilla Cream - result[1 * 7] = forms[RubyCream]; - result[2 * 7] = forms[MatchaCream]; - result[3 * 7] = forms[MintCream]; - result[4 * 7] = forms[LemonCream]; - result[5 * 7] = forms[SaltedCream]; - result[6 * 7] = forms[RubySwirl]; - result[7 * 7] = forms[CaramelSwirl]; - result[8 * 7] = forms[RainbowSwirl]; - - const int deco = 7; - const int fc = 9; - for (int f = 0; f < fc; f++) - { - int start = f * deco; - // iterate downwards using form0 as pattern ref, replacing on final loop - for (int i = deco - 1; i >= 0; i--) - { - result[start + i] = $"{result[start]} ({(AlcremieDecoration)i})"; - } - } - - return result; - } - - public static bool GetFormArgumentIsNamedIndex(int species) => species == (int)Alcremie; - - public static string[] GetFormArgumentStrings(int species) => species switch - { - (int)Alcremie => Enum.GetNames(typeof(AlcremieDecoration)), - _ => EMPTY, + types[000], // Normal + forms[804], // Mega + forms[Galarian], // Galarian }; } + + private const int Galarian = 1068; + private const int Gigantamax = 1069; + private const int Gulping = 1070; + private const int Gorging = 1071; + private const int LowKey = 1072; + + private const int RubyCream = 1073; + private const int MatchaCream = 1074; + private const int MintCream = 1075; + private const int LemonCream = 1076; + private const int SaltedCream = 1077; + private const int RubySwirl = 1078; + private const int CaramelSwirl = 1079; + private const int RainbowSwirl = 1080; + + private const int IceFace = 1091; + private const int NoiceFace = 1081; + private const int HangryMode = 1082; + private const int FullBellyMode = 1092; + + private const int Hero = 1093; + private const int Crowned = 1083; + private const int Eternamax = 1084; + + private const int SingleStrike = 1086; + private const int RapidStrike = 1087; + private const int Dada = 1088; + private const int CalyIce = 1089; // Ice + private const int CalyGhost = 1090; // Shadow + + private const int Hisuian = 1094; + private const int Lord = 1095; + private const int Lady = 1096; + private const int Legend = 1097; + + public static string GetGigantamaxName(IReadOnlyList forms) => forms[Gigantamax]; + + public static string[] GetAlcremieFormList(IReadOnlyList forms) + { + var result = new string[63]; + // seed form0 with the pattern + result[0 * 7] = forms[(int) Alcremie]; // Vanilla Cream + result[1 * 7] = forms[RubyCream]; + result[2 * 7] = forms[MatchaCream]; + result[3 * 7] = forms[MintCream]; + result[4 * 7] = forms[LemonCream]; + result[5 * 7] = forms[SaltedCream]; + result[6 * 7] = forms[RubySwirl]; + result[7 * 7] = forms[CaramelSwirl]; + result[8 * 7] = forms[RainbowSwirl]; + + const int deco = 7; + const int fc = 9; + for (int f = 0; f < fc; f++) + { + int start = f * deco; + // iterate downwards using form0 as pattern ref, replacing on final loop + for (int i = deco - 1; i >= 0; i--) + { + result[start + i] = $"{result[start]} ({(AlcremieDecoration)i})"; + } + } + + return result; + } + + public static bool GetFormArgumentIsNamedIndex(int species) => species == (int)Alcremie; + + public static string[] GetFormArgumentStrings(int species) => species switch + { + (int)Alcremie => Enum.GetNames(typeof(AlcremieDecoration)), + _ => EMPTY, + }; } diff --git a/PKHeX.Core/PKM/Util/Conversion/ItemConverter.cs b/PKHeX.Core/PKM/Util/Conversion/ItemConverter.cs index 5d4a74bd0..71b2cf526 100644 --- a/PKHeX.Core/PKM/Util/Conversion/ItemConverter.cs +++ b/PKHeX.Core/PKM/Util/Conversion/ItemConverter.cs @@ -1,219 +1,218 @@ -using System; +using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for converting Item IDs between the generation specific value sets. +/// +internal static class ItemConverter { + /// Unused item ID, placeholder for item/sprite finding in Generations 2-4. + private const ushort NaN = 128; + /// - /// Logic for converting Item IDs between the generation specific value sets. + /// Checks if the item can be kept during 3->4 conversion. /// - internal static class ItemConverter + /// Generation 3 Item ID. + /// True if transferable, False if not transferable. + internal static bool IsItemTransferable34(ushort item) => item != NaN && item > 0; + + /// + /// Converts a Generation 3 Item ID to Generation 4+ Item ID. + /// + /// Generation 3 Item ID. + /// Generation 4+ Item ID. + internal static ushort GetItemFuture3(ushort item) => item > arr3.Length ? NaN : arr3[item]; + + /// + /// Converts a Generation 2 Item ID to Generation 4+ Item ID. + /// + /// Generation 2 Item ID. + /// Generation 4+ Item ID. + internal static ushort GetItemFuture2(byte item) => item > arr2.Length ? NaN : arr2[item]; + + /// + /// Converts a Generation 4+ Item ID to Generation 3 Item ID. + /// + /// Generation 4+ Item ID. + /// Generation 3 Item ID. + private static ushort GetItemOld3(ushort item) { - /// Unused item ID, placeholder for item/sprite finding in Generations 2-4. - private const ushort NaN = 128; + if (item == NaN) + return 0; + int index = Array.IndexOf(arr3, item); + return (ushort)Math.Max(0, index); + } - /// - /// Checks if the item can be kept during 3->4 conversion. - /// - /// Generation 3 Item ID. - /// True if transferable, False if not transferable. - internal static bool IsItemTransferable34(ushort item) => item != NaN && item > 0; + /// + /// Converts a Generation 4+ Item ID to Generation 2 Item ID. + /// + /// Generation 4+ Item ID. + /// Generation 2 Item ID. + private static byte GetItemOld2(ushort item) + { + if (item == NaN) + return 0; + int index = Array.IndexOf(arr2, item); + return (byte)Math.Max(0, index); + } - /// - /// Converts a Generation 3 Item ID to Generation 4+ Item ID. - /// - /// Generation 3 Item ID. - /// Generation 4+ Item ID. - internal static ushort GetItemFuture3(ushort item) => item > arr3.Length ? NaN : arr3[item]; + #region Item Mapping Tables + /// Gen2 items (index) and their corresponding Gen4 item ID (value) + private static readonly ushort[] arr2 = + { + 000, 001, 002, 213, 003, 004, NaN, 450, 081, 018, // 0 + 019, 020, 021, 022, 023, 024, 025, 026, 017, 078, // 1 + 079, 041, 082, 083, 084, NaN, 045, 046, 047, 048, // 2 + 256, 049, 050, 060, 085, 257, 092, 063, 027, 028, // 3 + 029, 055, 076, 077, 056, NaN, 030, 031, 032, 057, // 4 + NaN, 058, 059, 061, 444, NaN, NaN, 216, 445, 446, // 5 + NaN, 447, 051, 038, 039, 040, 478, 464, 456, 484, // 6 + NaN, 482, 033, 217, 151, NaN, 237, 244, 149, 153, // 7 + 152, 245, 221, 156, 150, 485, 086, 087, 222, 487, // 8 + NaN, 223, 486, 488, 224, 243, 248, 490, 241, 491, // 9 + NaN, 489, 240, 473, NaN, 259, 228, 246, 242, 157, // 10 + 088, 089, 229, 247, 504, NaN, NaN, 239, 258, 230, // 11 + NaN, 034, 035, 036, 037, 238, 231, 475, 481, NaN, // 12 + NaN, 090, 091, 476, 480, NaN, NaN, NaN, 249, 043, // 13 + 232, NaN, NaN, 233, 250, NaN, 234, NaN, NaN, NaN, // 14 + 154, 235, NaN, NaN, NaN, NaN, 044, 495, NaN, 493, // 15 + NaN, 492, NaN, 236, 497, 498, 496, NaN, NaN, 080, // 16 + NaN, NaN, 252, 155, 158, 477, NaN, 500, 483, NaN, // 17 + NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, // 18 + NaN, 328, 329, 330, 331, 331, 332, 333, 334, 335, // 19 + 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, // 20 + 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, // 21 + 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, // 22 + 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, // 23 + 375, 376, 377, 420, 421, 422, 423, 424, 425, 426, // 24 + 427, NaN, NaN, NaN, NaN, NaN, + }; - /// - /// Converts a Generation 2 Item ID to Generation 4+ Item ID. - /// - /// Generation 2 Item ID. - /// Generation 4+ Item ID. - internal static ushort GetItemFuture2(byte item) => item > arr2.Length ? NaN : arr2[item]; + /// Gen3 items (index) and their corresponding Gen4 item ID (value) + private static readonly ushort[] arr3 = + { + 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, + 010, 011, 012, 017, 018, 019, 020, 021, 022, 023, + 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, + 034, 035, 036, 037, 038, 039, 040, 041, 042, 065, + 066, 067, 068, 069, 043, 044, 070, 071, 072, 073, + 074, 075, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, + NaN, NaN, NaN, 045, 046, 047, 048, 049, 050, 051, + 052, 053, NaN, 055, 056, 057, 058, 059, 060, 061, + 063, 064, NaN, 076, 077, 078, 079, NaN, NaN, NaN, + NaN, NaN, NaN, 080, 081, 082, 083, 084, 085, NaN, + NaN, NaN, NaN, 086, 087, NaN, 088, 089, 090, 091, + 092, 093, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, + NaN, NaN, NaN, 149, 150, 151, 152, 153, 154, 155, + 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, + 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 201, 202, + 203, 204, 205, 206, 207, 208, NaN, NaN, NaN, 213, + 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, + 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, + 254, 255, 256, 257, 258, 259, NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, 260, 261, 262, 263, 264, - /// - /// Converts a Generation 4+ Item ID to Generation 3 Item ID. - /// - /// Generation 4+ Item ID. - /// Generation 3 Item ID. - private static ushort GetItemOld3(ushort item) + NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, + NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, + 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, + 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, + 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, + 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, + 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, + }; + #endregion + + /// + /// Converts a Generation 1 (Teru-sama) Item ID to Generation 2 Item ID. + /// + /// Gen1 Item ID + /// Gen2 Item ID + /// + ///
https://github.com/pret/pokecrystal/blob/edb624c20ceb50eef9d73a5df0ac041cc156dd32/engine/link/link.asm#L1093-L1115
+ ///
https://github.com/pret/pokecrystal/blob/edb624c20ceb50eef9d73a5df0ac041cc156dd32/data/items/catch_rate_items.asm#L5-L17
+ ///
+ private static int GetTeruSamaItem(int value) => value switch + { + 0x19 => 0x92, // Leftovers + 0x2D => 0x53, // Bitter Berry + 0x32 => 0xAE, // Leftovers + 0x5A or 0x64 or 0x78 or 0x87 or 0xBE or 0xC3 or 0xDC or 0xFA or 0xFF => 0xAD, // Berry + _ => value, + }; + + /// + /// Converts a Gen1 value to Gen2 Item. + /// + /// Gen1 Item + /// Gen2 Item + internal static int GetItemFuture1(int value) + { + if (!IsItemTransferable12((ushort) value)) + return GetTeruSamaItem(value); + return value; + } + + private static bool IsItemTransferable12(ushort item) => ((IList) Legal.HeldItems_GSC).Contains(item); + + /// + /// Gets a format specific value depending on the desired format and the provided item index & origin format. + /// + /// Held Item to apply + /// Format from importing + /// Format required for holder + /// destItem + internal static int GetItemForFormat(int srcItem, int srcFormat, int destFormat) + { + if (srcItem <= 0) + return 0; + + if (destFormat == srcFormat) + return srcItem; + + if (destFormat != srcFormat && srcFormat <= 3) // past gen items { - if (item == NaN) + if (destFormat > 3) // try remapping + return srcFormat == 2 ? GetItemFuture2((byte)srcItem) : GetItemFuture3((ushort)srcItem); + + if (destFormat > srcFormat) // can't set past gen items return 0; - int index = Array.IndexOf(arr3, item); - return (ushort)Math.Max(0, index); - } - /// - /// Converts a Generation 4+ Item ID to Generation 2 Item ID. - /// - /// Generation 4+ Item ID. - /// Generation 2 Item ID. - private static byte GetItemOld2(ushort item) - { - if (item == NaN) - return 0; - int index = Array.IndexOf(arr2, item); - return (byte)Math.Max(0, index); - } - - #region Item Mapping Tables - /// Gen2 items (index) and their corresponding Gen4 item ID (value) - private static readonly ushort[] arr2 = - { - 000, 001, 002, 213, 003, 004, NaN, 450, 081, 018, // 0 - 019, 020, 021, 022, 023, 024, 025, 026, 017, 078, // 1 - 079, 041, 082, 083, 084, NaN, 045, 046, 047, 048, // 2 - 256, 049, 050, 060, 085, 257, 092, 063, 027, 028, // 3 - 029, 055, 076, 077, 056, NaN, 030, 031, 032, 057, // 4 - NaN, 058, 059, 061, 444, NaN, NaN, 216, 445, 446, // 5 - NaN, 447, 051, 038, 039, 040, 478, 464, 456, 484, // 6 - NaN, 482, 033, 217, 151, NaN, 237, 244, 149, 153, // 7 - 152, 245, 221, 156, 150, 485, 086, 087, 222, 487, // 8 - NaN, 223, 486, 488, 224, 243, 248, 490, 241, 491, // 9 - NaN, 489, 240, 473, NaN, 259, 228, 246, 242, 157, // 10 - 088, 089, 229, 247, 504, NaN, NaN, 239, 258, 230, // 11 - NaN, 034, 035, 036, 037, 238, 231, 475, 481, NaN, // 12 - NaN, 090, 091, 476, 480, NaN, NaN, NaN, 249, 043, // 13 - 232, NaN, NaN, 233, 250, NaN, 234, NaN, NaN, NaN, // 14 - 154, 235, NaN, NaN, NaN, NaN, 044, 495, NaN, 493, // 15 - NaN, 492, NaN, 236, 497, 498, 496, NaN, NaN, 080, // 16 - NaN, NaN, 252, 155, 158, 477, NaN, 500, 483, NaN, // 17 - NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, // 18 - NaN, 328, 329, 330, 331, 331, 332, 333, 334, 335, // 19 - 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, // 20 - 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, // 21 - 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, // 22 - 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, // 23 - 375, 376, 377, 420, 421, 422, 423, 424, 425, 426, // 24 - 427, NaN, NaN, NaN, NaN, NaN, - }; - - /// Gen3 items (index) and their corresponding Gen4 item ID (value) - private static readonly ushort[] arr3 = - { - 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, - 010, 011, 012, 017, 018, 019, 020, 021, 022, 023, - 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, - 034, 035, 036, 037, 038, 039, 040, 041, 042, 065, - 066, 067, 068, 069, 043, 044, 070, 071, 072, 073, - 074, 075, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, - NaN, NaN, NaN, 045, 046, 047, 048, 049, 050, 051, - 052, 053, NaN, 055, 056, 057, 058, 059, 060, 061, - 063, 064, NaN, 076, 077, 078, 079, NaN, NaN, NaN, - NaN, NaN, NaN, 080, 081, 082, 083, 084, 085, NaN, - NaN, NaN, NaN, 086, 087, NaN, 088, 089, 090, 091, - 092, 093, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, - NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, - NaN, NaN, NaN, 149, 150, 151, 152, 153, 154, 155, - 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, - 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - 176, 177, 178, 179, 180, 181, 182, 183, 201, 202, - 203, 204, 205, 206, 207, 208, NaN, NaN, NaN, 213, - 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, - 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, - 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, - 254, 255, 256, 257, 258, 259, NaN, NaN, NaN, NaN, - NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, - NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, - NaN, NaN, NaN, NaN, 260, 261, 262, 263, 264, - - NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, - NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, - NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, - 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, - 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, - 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, - 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, - 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, - }; - #endregion - - /// - /// Converts a Generation 1 (Teru-sama) Item ID to Generation 2 Item ID. - /// - /// Gen1 Item ID - /// Gen2 Item ID - /// - ///
https://github.com/pret/pokecrystal/blob/edb624c20ceb50eef9d73a5df0ac041cc156dd32/engine/link/link.asm#L1093-L1115
- ///
https://github.com/pret/pokecrystal/blob/edb624c20ceb50eef9d73a5df0ac041cc156dd32/data/items/catch_rate_items.asm#L5-L17
- ///
- private static int GetTeruSamaItem(int value) => value switch - { - 0x19 => 0x92, // Leftovers - 0x2D => 0x53, // Bitter Berry - 0x32 => 0xAE, // Leftovers - 0x5A or 0x64 or 0x78 or 0x87 or 0xBE or 0xC3 or 0xDC or 0xFA or 0xFF => 0xAD, // Berry - _ => value, - }; - - /// - /// Converts a Gen1 value to Gen2 Item. - /// - /// Gen1 Item - /// Gen2 Item - internal static int GetItemFuture1(int value) - { - if (!IsItemTransferable12((ushort) value)) - return GetTeruSamaItem(value); - return value; - } - - private static bool IsItemTransferable12(ushort item) => ((IList) Legal.HeldItems_GSC).Contains(item); - - /// - /// Gets a format specific value depending on the desired format and the provided item index & origin format. - /// - /// Held Item to apply - /// Format from importing - /// Format required for holder - /// destItem - internal static int GetItemForFormat(int srcItem, int srcFormat, int destFormat) - { + // ShowdownSet checks gen3 then gen2. For gen2 collisions (if any?) remap 3->4->2. + srcItem = GetItemFuture3((ushort)srcItem); + srcItem = GetItemOld2((ushort)srcItem); if (srcItem <= 0) return 0; - - if (destFormat == srcFormat) - return srcItem; - - if (destFormat != srcFormat && srcFormat <= 3) // past gen items - { - if (destFormat > 3) // try remapping - return srcFormat == 2 ? GetItemFuture2((byte)srcItem) : GetItemFuture3((ushort)srcItem); - - if (destFormat > srcFormat) // can't set past gen items - return 0; - - // ShowdownSet checks gen3 then gen2. For gen2 collisions (if any?) remap 3->4->2. - srcItem = GetItemFuture3((ushort)srcItem); - srcItem = GetItemOld2((ushort)srcItem); - if (srcItem <= 0) - return 0; - } - - return destFormat switch - { - 1 => 0, - 2 => (byte) srcItem, - 3 => GetItemOld3((ushort) srcItem), - _ => srcItem, - }; } - /// - /// Checks if an item ID is an HM - /// - /// Item ID - /// Generation the exists in - /// True if is an HM - internal static bool IsItemHM(ushort item, int generation) => generation switch + return destFormat switch { - 1 => item is >= 196 and <= 200, // HMs - 2 => item >= 243, // HMs - 3 => item is >= 339 and <= 346, - _ => item is >= 420 and <= 427 or 737, + 1 => 0, + 2 => (byte) srcItem, + 3 => GetItemOld3((ushort) srcItem), + _ => srcItem, }; } + + /// + /// Checks if an item ID is an HM + /// + /// Item ID + /// Generation the exists in + /// True if is an HM + internal static bool IsItemHM(ushort item, int generation) => generation switch + { + 1 => item is (>= 196 and <= 200), + 2 => item is (>= 243 and <= 249), + 3 => item is (>= 339 and <= 346), + _ => item is (>= 420 and <= 427) or 737, + }; } diff --git a/PKHeX.Core/PKM/Util/Conversion/SpeciesConverter.cs b/PKHeX.Core/PKM/Util/Conversion/SpeciesConverter.cs index 38c682279..096dbe5a6 100644 --- a/PKHeX.Core/PKM/Util/Conversion/SpeciesConverter.cs +++ b/PKHeX.Core/PKM/Util/Conversion/SpeciesConverter.cs @@ -1,96 +1,95 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for converting a National Pokédex Species ID to/from generation specific values. +/// +/// Generation 4+ always use the national dex ID. Prior generations do not. +public static class SpeciesConverter { /// - /// Logic for converting a National Pokédex Species ID to/from generation specific values. + /// Converts Generation 1 species ID to National Dex ID. /// - /// Generation 4+ always use the national dex ID. Prior generations do not. - public static class SpeciesConverter + /// Generation 1 species ID. + /// National Dex ID. + public static byte GetG1Species(byte raw) => table1_National[raw]; + + /// + /// Converts a National Dex ID to Generation 1 species ID. + /// + /// National Dex ID. + /// Generation 1 species ID. + public static byte SetG1Species(int species) => (uint)species >= table1_Internal.Length ? (byte)0 : table1_Internal[species]; + + /// + /// Converts a National Dex ID to Generation 3 species ID. + /// + /// National Dex ID + /// Generation 3 species ID. + public static int GetG3Species(int species) => (uint)species >= table3_Internal.Length ? 0 : table3_Internal[species]; + + /// + /// Converts Generation 3 species ID to National Dex ID. + /// + /// Generation 3 species ID. + /// National Dex ID. + public static int GetG4Species(int raw) => (uint)raw >= table3_National.Length ? 0 : table3_National[raw]; + + // both tables are 0x100 bytes to eliminate bounds checks when requesting byte indexes + private static readonly byte[] table1_Internal = { 0x00, 0x99, 0x09, 0x9A, 0xB0, 0xB2, 0xB4, 0xB1, 0xB3, 0x1C, 0x7B, 0x7C, 0x7D, 0x70, 0x71, 0x72, 0x24, 0x96, 0x97, 0xA5, 0xA6, 0x05, 0x23, 0x6C, 0x2D, 0x54, 0x55, 0x60, 0x61, 0x0F, 0xA8, 0x10, 0x03, 0xA7, 0x07, 0x04, 0x8E, 0x52, 0x53, 0x64, 0x65, 0x6B, 0x82, 0xB9, 0xBA, 0xBB, 0x6D, 0x2E, 0x41, 0x77, 0x3B, 0x76, 0x4D, 0x90, 0x2F, 0x80, 0x39, 0x75, 0x21, 0x14, 0x47, 0x6E, 0x6F, 0x94, 0x26, 0x95, 0x6A, 0x29, 0x7E, 0xBC, 0xBD, 0xBE, 0x18, 0x9B, 0xA9, 0x27, 0x31, 0xA3, 0xA4, 0x25, 0x08, 0xAD, 0x36, 0x40, 0x46, 0x74, 0x3A, 0x78, 0x0D, 0x88, 0x17, 0x8B, 0x19, 0x93, 0x0E, 0x22, 0x30, 0x81, 0x4E, 0x8A, 0x06, 0x8D, 0x0C, 0x0A, 0x11, 0x91, 0x2B, 0x2C, 0x0B, 0x37, 0x8F, 0x12, 0x01, 0x28, 0x1E, 0x02, 0x5C, 0x5D, 0x9D, 0x9E, 0x1B, 0x98, 0x2A, 0x1A, 0x48, 0x35, 0x33, 0x1D, 0x3C, 0x85, 0x16, 0x13, 0x4C, 0x66, 0x69, 0x68, 0x67, 0xAA, 0x62, 0x63, 0x5A, 0x5B, 0xAB, 0x84, 0x4A, 0x4B, 0x49, 0x58, 0x59, 0x42, 0x83, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + private static readonly byte[] table1_National = { 0x00, 0x70, 0x73, 0x20, 0x23, 0x15, 0x64, 0x22, 0x50, 0x02, 0x67, 0x6C, 0x66, 0x58, 0x5E, 0x1D, 0x1F, 0x68, 0x6F, 0x83, 0x3B, 0x97, 0x82, 0x5A, 0x48, 0x5C, 0x7B, 0x78, 0x09, 0x7F, 0x72, 0x00, 0x00, 0x3A, 0x5F, 0x16, 0x10, 0x4F, 0x40, 0x4B, 0x71, 0x43, 0x7A, 0x6A, 0x6B, 0x18, 0x2F, 0x36, 0x60, 0x4C, 0x00, 0x7E, 0x00, 0x7D, 0x52, 0x6D, 0x00, 0x38, 0x56, 0x32, 0x80, 0x00, 0x00, 0x00, 0x53, 0x30, 0x95, 0x00, 0x00, 0x00, 0x54, 0x3C, 0x7C, 0x92, 0x90, 0x91, 0x84, 0x34, 0x62, 0x00, 0x00, 0x00, 0x25, 0x26, 0x19, 0x1A, 0x00, 0x00, 0x93, 0x94, 0x8C, 0x8D, 0x74, 0x75, 0x00, 0x00, 0x1B, 0x1C, 0x8A, 0x8B, 0x27, 0x28, 0x85, 0x88, 0x87, 0x86, 0x42, 0x29, 0x17, 0x2E, 0x3D, 0x3E, 0x0D, 0x0E, 0x0F, 0x00, 0x55, 0x39, 0x33, 0x31, 0x57, 0x00, 0x00, 0x0A, 0x0B, 0x0C, 0x44, 0x00, 0x37, 0x61, 0x2A, 0x96, 0x8F, 0x81, 0x00, 0x00, 0x59, 0x00, 0x63, 0x5B, 0x00, 0x65, 0x24, 0x6E, 0x35, 0x69, 0x00, 0x5D, 0x3F, 0x41, 0x11, 0x12, 0x79, 0x01, 0x03, 0x49, 0x00, 0x76, 0x77, 0x00, 0x00, 0x00, 0x00, 0x4D, 0x4E, 0x13, 0x14, 0x21, 0x1E, 0x4A, 0x89, 0x8E, 0x00, 0x51, 0x00, 0x00, 0x04, 0x07, 0x05, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x2C, 0x2D, 0x45, 0x46, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + /// + /// National Dex IDs (index) and the associated Gen3 Species IDs (value) + /// + private static readonly ushort[] table3_Internal = { - /// - /// Converts Generation 1 species ID to National Dex ID. - /// - /// Generation 1 species ID. - /// National Dex ID. - public static byte GetG1Species(byte raw) => table1_National[raw]; + 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, + 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, + 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, + 060, 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, + 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, + 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 304, 305, 309, 310, + 392, 393, 394, 311, 312, 306, 307, 364, 365, 366, 301, 302, 303, 370, 371, 372, 335, 336, 350, 320, + 315, 316, 322, 355, 382, 383, 384, 356, 357, 337, 338, 353, 354, 386, 387, 363, 367, 368, 330, 331, + 313, 314, 339, 340, 321, 351, 352, 308, 332, 333, 334, 344, 345, 358, 359, 380, 379, 348, 349, 323, + 324, 326, 327, 318, 319, 388, 389, 390, 391, 328, 329, 385, 317, 377, 378, 361, 362, 369, 411, 376, + 360, 346, 347, 341, 342, 343, 373, 374, 375, 381, 325, 395, 396, 397, 398, 399, 400, 401, 402, 403, + 407, 408, 404, 405, 406, 409, 410, + }; - /// - /// Converts a National Dex ID to Generation 1 species ID. - /// - /// National Dex ID. - /// Generation 1 species ID. - public static byte SetG1Species(int species) => (uint)species >= table1_Internal.Length ? (byte)0 : table1_Internal[species]; - - /// - /// Converts a National Dex ID to Generation 3 species ID. - /// - /// National Dex ID - /// Generation 3 species ID. - public static int GetG3Species(int species) => (uint)species >= table3_Internal.Length ? 0 : table3_Internal[species]; - - /// - /// Converts Generation 3 species ID to National Dex ID. - /// - /// Generation 3 species ID. - /// National Dex ID. - public static int GetG4Species(int raw) => (uint)raw >= table3_National.Length ? 0 : table3_National[raw]; - - // both tables are 0x100 bytes to eliminate bounds checks when requesting byte indexes - private static readonly byte[] table1_Internal = { 0x00, 0x99, 0x09, 0x9A, 0xB0, 0xB2, 0xB4, 0xB1, 0xB3, 0x1C, 0x7B, 0x7C, 0x7D, 0x70, 0x71, 0x72, 0x24, 0x96, 0x97, 0xA5, 0xA6, 0x05, 0x23, 0x6C, 0x2D, 0x54, 0x55, 0x60, 0x61, 0x0F, 0xA8, 0x10, 0x03, 0xA7, 0x07, 0x04, 0x8E, 0x52, 0x53, 0x64, 0x65, 0x6B, 0x82, 0xB9, 0xBA, 0xBB, 0x6D, 0x2E, 0x41, 0x77, 0x3B, 0x76, 0x4D, 0x90, 0x2F, 0x80, 0x39, 0x75, 0x21, 0x14, 0x47, 0x6E, 0x6F, 0x94, 0x26, 0x95, 0x6A, 0x29, 0x7E, 0xBC, 0xBD, 0xBE, 0x18, 0x9B, 0xA9, 0x27, 0x31, 0xA3, 0xA4, 0x25, 0x08, 0xAD, 0x36, 0x40, 0x46, 0x74, 0x3A, 0x78, 0x0D, 0x88, 0x17, 0x8B, 0x19, 0x93, 0x0E, 0x22, 0x30, 0x81, 0x4E, 0x8A, 0x06, 0x8D, 0x0C, 0x0A, 0x11, 0x91, 0x2B, 0x2C, 0x0B, 0x37, 0x8F, 0x12, 0x01, 0x28, 0x1E, 0x02, 0x5C, 0x5D, 0x9D, 0x9E, 0x1B, 0x98, 0x2A, 0x1A, 0x48, 0x35, 0x33, 0x1D, 0x3C, 0x85, 0x16, 0x13, 0x4C, 0x66, 0x69, 0x68, 0x67, 0xAA, 0x62, 0x63, 0x5A, 0x5B, 0xAB, 0x84, 0x4A, 0x4B, 0x49, 0x58, 0x59, 0x42, 0x83, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - private static readonly byte[] table1_National = { 0x00, 0x70, 0x73, 0x20, 0x23, 0x15, 0x64, 0x22, 0x50, 0x02, 0x67, 0x6C, 0x66, 0x58, 0x5E, 0x1D, 0x1F, 0x68, 0x6F, 0x83, 0x3B, 0x97, 0x82, 0x5A, 0x48, 0x5C, 0x7B, 0x78, 0x09, 0x7F, 0x72, 0x00, 0x00, 0x3A, 0x5F, 0x16, 0x10, 0x4F, 0x40, 0x4B, 0x71, 0x43, 0x7A, 0x6A, 0x6B, 0x18, 0x2F, 0x36, 0x60, 0x4C, 0x00, 0x7E, 0x00, 0x7D, 0x52, 0x6D, 0x00, 0x38, 0x56, 0x32, 0x80, 0x00, 0x00, 0x00, 0x53, 0x30, 0x95, 0x00, 0x00, 0x00, 0x54, 0x3C, 0x7C, 0x92, 0x90, 0x91, 0x84, 0x34, 0x62, 0x00, 0x00, 0x00, 0x25, 0x26, 0x19, 0x1A, 0x00, 0x00, 0x93, 0x94, 0x8C, 0x8D, 0x74, 0x75, 0x00, 0x00, 0x1B, 0x1C, 0x8A, 0x8B, 0x27, 0x28, 0x85, 0x88, 0x87, 0x86, 0x42, 0x29, 0x17, 0x2E, 0x3D, 0x3E, 0x0D, 0x0E, 0x0F, 0x00, 0x55, 0x39, 0x33, 0x31, 0x57, 0x00, 0x00, 0x0A, 0x0B, 0x0C, 0x44, 0x00, 0x37, 0x61, 0x2A, 0x96, 0x8F, 0x81, 0x00, 0x00, 0x59, 0x00, 0x63, 0x5B, 0x00, 0x65, 0x24, 0x6E, 0x35, 0x69, 0x00, 0x5D, 0x3F, 0x41, 0x11, 0x12, 0x79, 0x01, 0x03, 0x49, 0x00, 0x76, 0x77, 0x00, 0x00, 0x00, 0x00, 0x4D, 0x4E, 0x13, 0x14, 0x21, 0x1E, 0x4A, 0x89, 0x8E, 0x00, 0x51, 0x00, 0x00, 0x04, 0x07, 0x05, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x2C, 0x2D, 0x45, 0x46, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - - /// - /// National Dex IDs (index) and the associated Gen3 Species IDs (value) - /// - private static readonly ushort[] table3_Internal = - { - 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, - 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, - 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, - 060, 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, - 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, - 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, - 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 277, 278, 279, 280, 281, 282, 283, 284, - 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 304, 305, 309, 310, - 392, 393, 394, 311, 312, 306, 307, 364, 365, 366, 301, 302, 303, 370, 371, 372, 335, 336, 350, 320, - 315, 316, 322, 355, 382, 383, 384, 356, 357, 337, 338, 353, 354, 386, 387, 363, 367, 368, 330, 331, - 313, 314, 339, 340, 321, 351, 352, 308, 332, 333, 334, 344, 345, 358, 359, 380, 379, 348, 349, 323, - 324, 326, 327, 318, 319, 388, 389, 390, 391, 328, 329, 385, 317, 377, 378, 361, 362, 369, 411, 376, - 360, 346, 347, 341, 342, 343, 373, 374, 375, 381, 325, 395, 396, 397, 398, 399, 400, 401, 402, 403, - 407, 408, 404, 405, 406, 409, 410, - }; - - /// - /// Gen3 Species IDs (index) and the associated National Dex IDs (value) - /// - private static readonly ushort[] table3_National = - { - 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, - 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, - 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, - 060, 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, - 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, - 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, - 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 000, 000, 000, 000, 000, 000, 000, 000, - 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 252, 253, 254, - 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, - 275, 290, 291, 292, 276, 277, 285, 286, 327, 278, 279, 283, 284, 320, 321, 300, 301, 352, 343, 344, - 299, 324, 302, 339, 340, 370, 341, 342, 349, 350, 318, 319, 328, 329, 330, 296, 297, 309, 310, 322, - 323, 363, 364, 365, 331, 332, 361, 362, 337, 338, 298, 325, 326, 311, 312, 303, 307, 308, 333, 334, - 360, 355, 356, 315, 287, 288, 289, 316, 317, 357, 293, 294, 295, 366, 367, 368, 359, 353, 354, 336, - 335, 369, 304, 305, 306, 351, 313, 314, 345, 346, 347, 348, 280, 281, 282, 371, 372, 373, 374, 375, - 376, 377, 378, 379, 382, 383, 384, 380, 381, 385, 386, 358, - }; - } + /// + /// Gen3 Species IDs (index) and the associated National Dex IDs (value) + /// + private static readonly ushort[] table3_National = + { + 000, 001, 002, 003, 004, 005, 006, 007, 008, 009, 010, 011, 012, 013, 014, 015, 016, 017, 018, 019, + 020, 021, 022, 023, 024, 025, 026, 027, 028, 029, 030, 031, 032, 033, 034, 035, 036, 037, 038, 039, + 040, 041, 042, 043, 044, 045, 046, 047, 048, 049, 050, 051, 052, 053, 054, 055, 056, 057, 058, 059, + 060, 061, 062, 063, 064, 065, 066, 067, 068, 069, 070, 071, 072, 073, 074, 075, 076, 077, 078, 079, + 080, 081, 082, 083, 084, 085, 086, 087, 088, 089, 090, 091, 092, 093, 094, 095, 096, 097, 098, 099, + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, + 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 000, 000, 000, 000, 000, 000, 000, 000, + 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 252, 253, 254, + 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 290, 291, 292, 276, 277, 285, 286, 327, 278, 279, 283, 284, 320, 321, 300, 301, 352, 343, 344, + 299, 324, 302, 339, 340, 370, 341, 342, 349, 350, 318, 319, 328, 329, 330, 296, 297, 309, 310, 322, + 323, 363, 364, 365, 331, 332, 361, 362, 337, 338, 298, 325, 326, 311, 312, 303, 307, 308, 333, 334, + 360, 355, 356, 315, 287, 288, 289, 316, 317, 357, 293, 294, 295, 366, 367, 368, 359, 353, 354, 336, + 335, 369, 304, 305, 306, 351, 313, 314, 345, 346, 347, 348, 280, 281, 282, 371, 372, 373, 374, 375, + 376, 377, 378, 379, 382, 383, 384, 380, 381, 385, 386, 358, + }; } diff --git a/PKHeX.Core/PKM/Util/EntityFileNamer.cs b/PKHeX.Core/PKM/Util/EntityFileNamer.cs index 7c4536513..5bd4015d5 100644 --- a/PKHeX.Core/PKM/Util/EntityFileNamer.cs +++ b/PKHeX.Core/PKM/Util/EntityFileNamer.cs @@ -1,61 +1,60 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class EntityFileNamer { - public static class EntityFileNamer - { - /// - /// Object that converts the data into a file name. - /// - public static IFileNamer Namer { get; set; } = new DefaultEntityNamer(); - - /// - /// Gets the file name (without extension) for the input data. - /// - /// Input entity to create a file name for. - /// File name for the data - public static string GetName(PKM pk) => Namer.GetName(pk); - } + /// + /// Object that converts the data into a file name. + /// + public static IFileNamer Namer { get; set; } = new DefaultEntityNamer(); /// - /// PKHeX's default file naming logic. + /// Gets the file name (without extension) for the input data. /// - public sealed class DefaultEntityNamer : IFileNamer + /// Input entity to create a file name for. + /// File name for the data + public static string GetName(PKM pk) => Namer.GetName(pk); +} + +/// +/// PKHeX's default file naming logic. +/// +public sealed class DefaultEntityNamer : IFileNamer +{ + public string GetName(PKM obj) { - public string GetName(PKM obj) - { - if (obj is GBPKM gb) - return GetGBPKM(gb); - return GetRegular(obj); - } - - private static string GetRegular(PKM pk) - { - string form = pk.Form > 0 ? $"-{pk.Form:00}" : string.Empty; - string star = pk.IsShiny ? " ★" : string.Empty; - var chk = pk is ISanityChecksum s ? s.Checksum : PokeCrypto.GetCHK(pk.Data, pk.SIZE_STORED); - return $"{pk.Species:000}{form}{star} - {pk.Nickname} - {chk:X4}{pk.EncryptionConstant:X8}"; - } - - private static string GetGBPKM(GBPKM gb) - { - string form = gb.Form > 0 ? $"-{gb.Form:00}" : string.Empty; - string star = gb.IsShiny ? " ★" : string.Empty; - var raw = gb switch - { - PK1 pk1 => new PokeList1(pk1).Write(), - PK2 pk2 => new PokeList2(pk2).Write(), - _ => gb.Data, - }; - var checksum = Checksums.CRC16_CCITT(raw); - return $"{gb.Species:000}{form}{star} - {gb.Nickname} - {checksum:X4}"; - } + if (obj is GBPKM gb) + return GetGBPKM(gb); + return GetRegular(obj); } - /// - /// Exposes a method to get a file name (no extension) for the type. - /// - /// Type that the implementer can create a file name for. - public interface IFileNamer + private static string GetRegular(PKM pk) { - string GetName(T obj); + string form = pk.Form > 0 ? $"-{pk.Form:00}" : string.Empty; + string star = pk.IsShiny ? " ★" : string.Empty; + var chk = pk is ISanityChecksum s ? s.Checksum : PokeCrypto.GetCHK(pk.Data, pk.SIZE_STORED); + return $"{pk.Species:000}{form}{star} - {pk.Nickname} - {chk:X4}{pk.EncryptionConstant:X8}"; + } + + private static string GetGBPKM(GBPKM gb) + { + string form = gb.Form > 0 ? $"-{gb.Form:00}" : string.Empty; + string star = gb.IsShiny ? " ★" : string.Empty; + var raw = gb switch + { + PK1 pk1 => new PokeList1(pk1).Write(), + PK2 pk2 => new PokeList2(pk2).Write(), + _ => gb.Data, + }; + var checksum = Checksums.CRC16_CCITT(raw); + return $"{gb.Species:000}{form}{star} - {gb.Nickname} - {checksum:X4}"; } } + +/// +/// Exposes a method to get a file name (no extension) for the type. +/// +/// Type that the implementer can create a file name for. +public interface IFileNamer +{ + string GetName(T obj); +} diff --git a/PKHeX.Core/PKM/Util/EntityFormat.cs b/PKHeX.Core/PKM/Util/EntityFormat.cs index c7d708cf9..efd45ab75 100644 --- a/PKHeX.Core/PKM/Util/EntityFormat.cs +++ b/PKHeX.Core/PKM/Util/EntityFormat.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using static PKHeX.Core.PokeCrypto; using static PKHeX.Core.EntityFormatDetected; @@ -171,7 +171,7 @@ private static EntityFormatDetected IsFormatReally7(PK6 pk) return FormatPK6; for (int i = 0; i < 6; i++) { - if ((mb >> (i << 1) & 3) == 3) // markings are 10 or 01 (or 00), never 11 + if (((mb >> (i << 1)) & 3) == 3) // markings are 10 or 01 (or 00), never 11 return FormatPK6; } diff --git a/PKHeX.Core/PKM/Util/EntityPID.cs b/PKHeX.Core/PKM/Util/EntityPID.cs index 06adf0f8e..88a1cbc77 100644 --- a/PKHeX.Core/PKM/Util/EntityPID.cs +++ b/PKHeX.Core/PKM/Util/EntityPID.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace PKHeX.Core; @@ -70,10 +70,9 @@ public static uint GetRandomPID(Random rnd, int species, int gender, int origin, /// /// Personality ID /// Should only be used for 3rd Generation origin specimens. - /// public static int GetUnownForm3(uint pid) { - var value = (pid & 0x3000000) >> 18 | (pid & 0x30000) >> 12 | (pid & 0x300) >> 6 | (pid & 0x3); + var value = ((pid & 0x3000000) >> 18) | ((pid & 0x30000) >> 12) | ((pid & 0x300) >> 6) | (pid & 0x3); return (int)(value % 28); } } diff --git a/PKHeX.Core/PKM/Util/EntitySorting.cs b/PKHeX.Core/PKM/Util/EntitySorting.cs index 456e355bc..aa448b7ea 100644 --- a/PKHeX.Core/PKM/Util/EntitySorting.cs +++ b/PKHeX.Core/PKM/Util/EntitySorting.cs @@ -2,257 +2,256 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Sorting methods for lists. +/// +public static class EntitySorting { /// - /// Sorting methods for lists. + /// Sorts an list of objects with ascending values. /// - public static class EntitySorting + /// Source list to sort + /// Enumerable list that is sorted + public static IEnumerable OrderBySpecies(this IEnumerable list) { - /// - /// Sorts an list of objects with ascending values. - /// - /// Source list to sort - /// Enumerable list that is sorted - public static IEnumerable OrderBySpecies(this IEnumerable list) - { - return list.InitialSortBy() - .ThenBy(p => p.Species) - .FinalSortBy(); - } + return list.InitialSortBy() + .ThenBy(p => p.Species) + .FinalSortBy(); + } - /// - /// Sorts an list of objects with descending values. - /// - /// Source list to sort - /// Enumerable list that is sorted - public static IEnumerable OrderByDescendingSpecies(this IEnumerable list) - { - return list.InitialSortBy() - .ThenByDescending(p => p.Species) - .FinalSortBy(); - } + /// + /// Sorts an list of objects with descending values. + /// + /// Source list to sort + /// Enumerable list that is sorted + public static IEnumerable OrderByDescendingSpecies(this IEnumerable list) + { + return list.InitialSortBy() + .ThenByDescending(p => p.Species) + .FinalSortBy(); + } - /// - /// Sorts an list of objects with ascending values. - /// - /// Source list to sort - /// Enumerable list that is sorted - public static IEnumerable OrderByLevel(this IEnumerable list) - { - return list.InitialSortBy() - .ThenBy(p => p.CurrentLevel) - .ThenBy(p => p.Species) - .FinalSortBy(); - } + /// + /// Sorts an list of objects with ascending values. + /// + /// Source list to sort + /// Enumerable list that is sorted + public static IEnumerable OrderByLevel(this IEnumerable list) + { + return list.InitialSortBy() + .ThenBy(p => p.CurrentLevel) + .ThenBy(p => p.Species) + .FinalSortBy(); + } - /// - /// Sorts an list of objects with descending values. - /// - /// Source list to sort - /// Enumerable list that is sorted - public static IEnumerable OrderByDescendingLevel(this IEnumerable list) - { - return list.InitialSortBy() - .ThenByDescending(p => p.CurrentLevel) - .ThenBy(p => p.Species) - .FinalSortBy(); - } + /// + /// Sorts an list of objects with descending values. + /// + /// Source list to sort + /// Enumerable list that is sorted + public static IEnumerable OrderByDescendingLevel(this IEnumerable list) + { + return list.InitialSortBy() + .ThenByDescending(p => p.CurrentLevel) + .ThenBy(p => p.Species) + .FinalSortBy(); + } - /// - /// Sorts an list of objects by the date they were obtained. - /// - /// Source list to sort - /// Enumerable list that is sorted - public static IEnumerable OrderByDateObtained(this IEnumerable list) - { - return list.InitialSortBy() - .ThenBy(p => p.MetDate ?? p.EggMetDate) - .ThenBy(p => p.Species) - .FinalSortBy(); - } + /// + /// Sorts an list of objects by the date they were obtained. + /// + /// Source list to sort + /// Enumerable list that is sorted + public static IEnumerable OrderByDateObtained(this IEnumerable list) + { + return list.InitialSortBy() + .ThenBy(p => p.MetDate ?? p.EggMetDate) + .ThenBy(p => p.Species) + .FinalSortBy(); + } - /// - /// Sorts an list of objects by the date they were obtained. - /// - /// Source list to sort - /// Enumerable list that is sorted - public static IEnumerable OrderByUsage(this IEnumerable list) - { - return list.InitialSortBy() - .ThenByDescending(GetFriendshipDelta) // friendship raised evaluation - .ThenBy(p => p.Species) - .FinalSortBy(); - } + /// + /// Sorts an list of objects by the date they were obtained. + /// + /// Source list to sort + /// Enumerable list that is sorted + public static IEnumerable OrderByUsage(this IEnumerable list) + { + return list.InitialSortBy() + .ThenByDescending(GetFriendshipDelta) // friendship raised evaluation + .ThenBy(p => p.Species) + .FinalSortBy(); + } - /// - /// Sorts an list of objects alphabetically by name. - /// - /// Source list to sort - /// Names of each species - /// Enumerable list that is sorted - public static IEnumerable OrderBySpeciesName(this IEnumerable list, IReadOnlyList speciesNames) - { - int max = speciesNames.Count - 1; - string SpeciesName(int s) => s > max ? string.Empty : speciesNames[s]; + /// + /// Sorts an list of objects alphabetically by name. + /// + /// Source list to sort + /// Names of each species + /// Enumerable list that is sorted + public static IEnumerable OrderBySpeciesName(this IEnumerable list, IReadOnlyList speciesNames) + { + int max = speciesNames.Count - 1; + string SpeciesName(int s) => s > max ? string.Empty : speciesNames[s]; - return list.InitialSortBy() - .ThenBy(p => p.Species > max) // out of range sanity check - .ThenBy(p => SpeciesName(p.Species)) - .FinalSortBy(); - } + return list.InitialSortBy() + .ThenBy(p => p.Species > max) // out of range sanity check + .ThenBy(p => SpeciesName(p.Species)) + .FinalSortBy(); + } - /// - /// Sorts an list of objects to display those originally obtained by the current . - /// - /// Source list to sort - /// The requesting the sorted data. - /// Enumerable list that is sorted - public static IEnumerable OrderByOwnership(this IEnumerable list, ITrainerInfo trainer) - { - return list.InitialSortBy() - .ThenByDescending(p => trainer.IsOriginalHandler(p, ((GameVersion)trainer.Game).IsValidSavedVersion())) // true first - .ThenByDescending(p => string.Equals(p.OT_Name, trainer.OT, StringComparison.CurrentCultureIgnoreCase)) - .OrderByTrainer() - .ThenBy(p => p.Species) - .FinalSortBy(); - } + /// + /// Sorts an list of objects to display those originally obtained by the current . + /// + /// Source list to sort + /// The requesting the sorted data. + /// Enumerable list that is sorted + public static IEnumerable OrderByOwnership(this IEnumerable list, ITrainerInfo trainer) + { + return list.InitialSortBy() + .ThenByDescending(p => trainer.IsOriginalHandler(p, ((GameVersion)trainer.Game).IsValidSavedVersion())) // true first + .ThenByDescending(p => string.Equals(p.OT_Name, trainer.OT, StringComparison.OrdinalIgnoreCase)) + .OrderByTrainer() + .ThenBy(p => p.Species) + .FinalSortBy(); + } - /// - /// Sorts an list of objects based on the provided filter operations. - /// - /// Source list to sort - /// Save file destination - /// Position check - /// Starting position - /// Enumerable list that is sorted - public static IEnumerable BubbleUp(this IEnumerable list, SaveFile sav, Func check, int start) + /// + /// Sorts an list of objects based on the provided filter operations. + /// + /// Source list to sort + /// Save file destination + /// Position check + /// Starting position + /// Enumerable list that is sorted + public static IEnumerable BubbleUp(this IEnumerable list, SaveFile sav, Func check, int start) + { + var matches = new List(); + var failures = new List(); + var ctr = start; + foreach (var x in list) { - var matches = new List(); - var failures = new List(); - var ctr = start; - foreach (var x in list) - { - while (sav.IsSlotOverwriteProtected(ctr)) - ctr++; - bool isMatch = check(ctr); - var arr = isMatch ? matches : failures; - arr.Add(x); + while (sav.IsSlotOverwriteProtected(ctr)) ctr++; - } - - var result = matches.Concat(failures); - return result.InitialSortBy(); + bool isMatch = check(ctr); + var arr = isMatch ? matches : failures; + arr.Add(x); + ctr++; } - /// - /// Sorts an list of objects based on the provided filter operations. - /// - /// Source list to sort - /// Filter operations to sort with (sorted with ThenBy after the initial sort). - /// Enumerable list that is sorted - public static IEnumerable OrderByCustom(this IEnumerable list, params Func[] filters) - { - var init = list.InitialSortBy(); - return filters.Aggregate(init, (current, f) => current.ThenBy(f)) - .FinalSortBy(); - } + var result = matches.Concat(failures); + return result.InitialSortBy(); + } - /// - /// Sorts an list of objects based on the provided filter operations. - /// - /// Source list to sort - /// Filter operations to sort with (sorted with ThenBy after the initial sort). - /// Enumerable list that is sorted - /// Boolean sort doesn't pair well with , so just keep original sorting order. - public static IEnumerable OrderByCustom(this IEnumerable list, params Func[] filters) - { - var init = list.InitialSortBy(); - return filters.Aggregate(init, (current, f) => current.ThenBy(f)) - ; - } + /// + /// Sorts an list of objects based on the provided filter operations. + /// + /// Source list to sort + /// Filter operations to sort with (sorted with ThenBy after the initial sort). + /// Enumerable list that is sorted + public static IEnumerable OrderByCustom(this IEnumerable list, params Func[] filters) + { + var init = list.InitialSortBy(); + return filters.Aggregate(init, (current, f) => current.ThenBy(f)) + .FinalSortBy(); + } - /// - /// Sorts an list of objects in reverse. - /// - /// Source list to revese sort - /// Enumerable list that is sorted - public static IEnumerable ReverseSort(this IEnumerable list) - { - int i = 0; - return list.InitialSortBy() + /// + /// Sorts an list of objects based on the provided filter operations. + /// + /// Source list to sort + /// Filter operations to sort with (sorted with ThenBy after the initial sort). + /// Enumerable list that is sorted + /// Boolean sort doesn't pair well with , so just keep original sorting order. + public static IEnumerable OrderByCustom(this IEnumerable list, params Func[] filters) + { + var init = list.InitialSortBy(); + return filters.Aggregate(init, (current, f) => current.ThenBy(f)) + ; + } + + /// + /// Sorts an list of objects in reverse. + /// + /// Source list to revese sort + /// Enumerable list that is sorted + public static IEnumerable ReverseSort(this IEnumerable list) + { + int i = 0; + return list.InitialSortBy() .ThenByDescending(_ => i++) - ; // can't sort further - } + ; // can't sort further + } - /// - /// Common pre-filtering of data. - /// - /// Input list of data. - private static IOrderedEnumerable InitialSortBy(this IEnumerable list) - { - return list - .OrderBy(p => p.Species == 0) // empty slots at end - .ThenBy(p => p.IsEgg); // eggs to the end - } + /// + /// Common pre-filtering of data. + /// + /// Input list of data. + private static IOrderedEnumerable InitialSortBy(this IEnumerable list) + { + return list + .OrderBy(p => p.Species == 0) // empty slots at end + .ThenBy(p => p.IsEgg); // eggs to the end + } - /// - /// Common post-filtering of data. - /// - /// Output list of data. - private static IOrderedEnumerable FinalSortBy(this IOrderedEnumerable result) - { - var postSorted = result - .ThenBy(p => p.Form) // forms sorted - .ThenBy(p => p.Gender) // gender sorted - .ThenBy(p => p.IsNicknamed); - return postSorted; - } + /// + /// Common post-filtering of data. + /// + /// Output list of data. + private static IOrderedEnumerable FinalSortBy(this IOrderedEnumerable result) + { + var postSorted = result + .ThenBy(p => p.Form) // forms sorted + .ThenBy(p => p.Gender) // gender sorted + .ThenBy(p => p.IsNicknamed); + return postSorted; + } - /// - /// Common mid-filtering grouping of PKM data according to the Original Trainer details. - /// - /// Output list of data. - private static IOrderedEnumerable OrderByTrainer(this IOrderedEnumerable list) - { - return list.ThenBy(p => p.OT_Name) - .ThenBy(p => p.OT_Gender) - .ThenBy(p => p.TID) - .ThenBy(p => p.SID); - } + /// + /// Common mid-filtering grouping of PKM data according to the Original Trainer details. + /// + /// Output list of data. + private static IOrderedEnumerable OrderByTrainer(this IOrderedEnumerable list) + { + return list.ThenBy(p => p.OT_Name) + .ThenBy(p => p.OT_Gender) + .ThenBy(p => p.TID) + .ThenBy(p => p.SID); + } - /// - /// Gets if the current handler is the original trainer. - /// - /// The requesting the check. - /// Pokémon data - /// Toggle to check the game's version or not - /// True if OT, false if not OT. - public static bool IsOriginalHandler(this ITrainerInfo trainer, PKM pk, bool checkGame) - { - if (pk.Format >= 6) - return pk.CurrentHandler != 1; - if (checkGame && trainer.Game != pk.Version) - return false; - if (trainer.TID != pk.TID || trainer.SID != pk.SID) - return false; - if (trainer.Gender != pk.OT_Gender) - return false; - return trainer.OT == pk.OT_Name; - } + /// + /// Gets if the current handler is the original trainer. + /// + /// The requesting the check. + /// Pokémon data + /// Toggle to check the game's version or not + /// True if OT, false if not OT. + public static bool IsOriginalHandler(this ITrainerInfo trainer, PKM pk, bool checkGame) + { + if (pk.Format >= 6) + return pk.CurrentHandler != 1; + if (checkGame && trainer.Game != pk.Version) + return false; + if (trainer.TID != pk.TID || trainer.SID != pk.SID) + return false; + if (trainer.Gender != pk.OT_Gender) + return false; + return trainer.OT == pk.OT_Name; + } - /// - /// Gets a Friendship delta rating to indicate how much the has been raised vs its base Friendship value. - /// - /// Pokémon data - /// 255 if maxed, else the difference between current & base. - private static int GetFriendshipDelta(PKM pk) - { - var currentFriendship = pk.CurrentFriendship; - if (currentFriendship == 255) - return 255; - var baseFriendship = pk.PersonalInfo.BaseFriendship; - return currentFriendship - baseFriendship; - } + /// + /// Gets a Friendship delta rating to indicate how much the has been raised vs its base Friendship value. + /// + /// Pokémon data + /// 255 if maxed, else the difference between current & base. + private static int GetFriendshipDelta(PKM pk) + { + var currentFriendship = pk.CurrentFriendship; + if (currentFriendship == 255) + return 255; + var baseFriendship = pk.PersonalInfo.BaseFriendship; + return currentFriendship - baseFriendship; } } diff --git a/PKHeX.Core/PKM/Util/Experience.cs b/PKHeX.Core/PKM/Util/Experience.cs index 0e4f8e9ca..3b5ea71e3 100644 --- a/PKHeX.Core/PKM/Util/Experience.cs +++ b/PKHeX.Core/PKM/Util/Experience.cs @@ -1,190 +1,189 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Calculations for and . +/// +public static class Experience { /// - /// Calculations for and . + /// Gets the current level of a species. /// - public static class Experience + /// Experience points + /// Experience growth rate + /// Current level of the species. + public static int GetLevel(uint exp, int growth) { - /// - /// Gets the current level of a species. - /// - /// Experience points - /// Experience growth rate - /// Current level of the species. - public static int GetLevel(uint exp, int growth) - { - if (exp >= ExpTable[99, growth]) - return 100; - int tl = 1; // Initial Level. Iterate upwards to find the level - while (exp >= ExpTable[tl, growth]) - ++tl; - return tl; - } - - /// - /// Gets the minimum Experience points for the specified level. - /// - /// Current level - /// Growth Rate type - /// Experience points needed to have specified level. - public static uint GetEXP(int level, int growth) - { - if (level <= 1) - return 0; - if (level > 100) - level = 100; - return ExpTable[level - 1, growth]; - } - - /// - /// Gets the value for / entries based on the - /// - /// Current Experience - /// Nature ID () - public static int GetNatureVC(uint experience) => (int)(experience % 25); - - /// - /// Gets the amount of EXP to be earned until the next level-up occurs. - /// - /// Current Level - /// Growth Rate type - /// EXP to level up - public static uint GetEXPToLevelUp(int level, int growth) - { - if (level >= 100) - return 0; - var current = ExpTable[level - 1, growth]; - var next = ExpTable[level, growth]; - return next - current; - } - - /// - /// Gets a percentage for Experience Bar progress indication. - /// - /// Current Level - /// Current Experience - /// Growth Rate type - /// Percentage [0,1.00) - public static double GetEXPToLevelUpPercentage(int level, uint exp, int growth) - { - if (level >= 100) - return 0; - var current = ExpTable[level - 1, growth]; - var next = ExpTable[level, growth]; - var amount = next - current; - double progress = exp - current; - return progress / amount; - } - - #region ExpTable - - /// - /// Experience required for next level [0,99] - /// - private static readonly uint[,] ExpTable = - { - {0000000, 0000000, 0000000, 0000000, 0000000, 0000000}, - {0000008, 0000015, 0000004, 0000009, 0000006, 0000010}, - {0000027, 0000052, 0000013, 0000057, 0000021, 0000033}, - {0000064, 0000122, 0000032, 0000096, 0000051, 0000080}, - {0000125, 0000237, 0000065, 0000135, 0000100, 0000156}, - {0000216, 0000406, 0000112, 0000179, 0000172, 0000270}, - {0000343, 0000637, 0000178, 0000236, 0000274, 0000428}, - {0000512, 0000942, 0000276, 0000314, 0000409, 0000640}, - {0000729, 0001326, 0000393, 0000419, 0000583, 0000911}, - {0001000, 0001800, 0000540, 0000560, 0000800, 0001250}, - {0001331, 0002369, 0000745, 0000742, 0001064, 0001663}, - {0001728, 0003041, 0000967, 0000973, 0001382, 0002160}, - {0002197, 0003822, 0001230, 0001261, 0001757, 0002746}, - {0002744, 0004719, 0001591, 0001612, 0002195, 0003430}, - {0003375, 0005737, 0001957, 0002035, 0002700, 0004218}, - {0004096, 0006881, 0002457, 0002535, 0003276, 0005120}, - {0004913, 0008155, 0003046, 0003120, 0003930, 0006141}, - {0005832, 0009564, 0003732, 0003798, 0004665, 0007290}, - {0006859, 0011111, 0004526, 0004575, 0005487, 0008573}, - {0008000, 0012800, 0005440, 0005460, 0006400, 0010000}, - {0009261, 0014632, 0006482, 0006458, 0007408, 0011576}, - {0010648, 0016610, 0007666, 0007577, 0008518, 0013310}, - {0012167, 0018737, 0009003, 0008825, 0009733, 0015208}, - {0013824, 0021012, 0010506, 0010208, 0011059, 0017280}, - {0015625, 0023437, 0012187, 0011735, 0012500, 0019531}, - {0017576, 0026012, 0014060, 0013411, 0014060, 0021970}, - {0019683, 0028737, 0016140, 0015244, 0015746, 0024603}, - {0021952, 0031610, 0018439, 0017242, 0017561, 0027440}, - {0024389, 0034632, 0020974, 0019411, 0019511, 0030486}, - {0027000, 0037800, 0023760, 0021760, 0021600, 0033750}, - {0029791, 0041111, 0026811, 0024294, 0023832, 0037238}, - {0032768, 0044564, 0030146, 0027021, 0026214, 0040960}, - {0035937, 0048155, 0033780, 0029949, 0028749, 0044921}, - {0039304, 0051881, 0037731, 0033084, 0031443, 0049130}, - {0042875, 0055737, 0042017, 0036435, 0034300, 0053593}, - {0046656, 0059719, 0046656, 0040007, 0037324, 0058320}, - {0050653, 0063822, 0050653, 0043808, 0040522, 0063316}, - {0054872, 0068041, 0055969, 0047846, 0043897, 0068590}, - {0059319, 0072369, 0060505, 0052127, 0047455, 0074148}, - {0064000, 0076800, 0066560, 0056660, 0051200, 0080000}, - {0068921, 0081326, 0071677, 0061450, 0055136, 0086151}, - {0074088, 0085942, 0078533, 0066505, 0059270, 0092610}, - {0079507, 0090637, 0084277, 0071833, 0063605, 0099383}, - {0085184, 0095406, 0091998, 0077440, 0068147, 0106480}, - {0091125, 0100237, 0098415, 0083335, 0072900, 0113906}, - {0097336, 0105122, 0107069, 0089523, 0077868, 0121670}, - {0103823, 0110052, 0114205, 0096012, 0083058, 0129778}, - {0110592, 0115015, 0123863, 0102810, 0088473, 0138240}, - {0117649, 0120001, 0131766, 0109923, 0094119, 0147061}, - {0125000, 0125000, 0142500, 0117360, 0100000, 0156250}, - {0132651, 0131324, 0151222, 0125126, 0106120, 0165813}, - {0140608, 0137795, 0163105, 0133229, 0112486, 0175760}, - {0148877, 0144410, 0172697, 0141677, 0119101, 0186096}, - {0157464, 0151165, 0185807, 0150476, 0125971, 0196830}, - {0166375, 0158056, 0196322, 0159635, 0133100, 0207968}, - {0175616, 0165079, 0210739, 0169159, 0140492, 0219520}, - {0185193, 0172229, 0222231, 0179056, 0148154, 0231491}, - {0195112, 0179503, 0238036, 0189334, 0156089, 0243890}, - {0205379, 0186894, 0250562, 0199999, 0164303, 0256723}, - {0216000, 0194400, 0267840, 0211060, 0172800, 0270000}, - {0226981, 0202013, 0281456, 0222522, 0181584, 0283726}, - {0238328, 0209728, 0300293, 0234393, 0190662, 0297910}, - {0250047, 0217540, 0315059, 0246681, 0200037, 0312558}, - {0262144, 0225443, 0335544, 0259392, 0209715, 0327680}, - {0274625, 0233431, 0351520, 0272535, 0219700, 0343281}, - {0287496, 0241496, 0373744, 0286115, 0229996, 0359370}, - {0300763, 0249633, 0390991, 0300140, 0240610, 0375953}, - {0314432, 0257834, 0415050, 0314618, 0251545, 0393040}, - {0328509, 0267406, 0433631, 0329555, 0262807, 0410636}, - {0343000, 0276458, 0459620, 0344960, 0274400, 0428750}, - {0357911, 0286328, 0479600, 0360838, 0286328, 0447388}, - {0373248, 0296358, 0507617, 0377197, 0298598, 0466560}, - {0389017, 0305767, 0529063, 0394045, 0311213, 0486271}, - {0405224, 0316074, 0559209, 0411388, 0324179, 0506530}, - {0421875, 0326531, 0582187, 0429235, 0337500, 0527343}, - {0438976, 0336255, 0614566, 0447591, 0351180, 0548720}, - {0456533, 0346965, 0639146, 0466464, 0365226, 0570666}, - {0474552, 0357812, 0673863, 0485862, 0379641, 0593190}, - {0493039, 0367807, 0700115, 0505791, 0394431, 0616298}, - {0512000, 0378880, 0737280, 0526260, 0409600, 0640000}, - {0531441, 0390077, 0765275, 0547274, 0425152, 0664301}, - {0551368, 0400293, 0804997, 0568841, 0441094, 0689210}, - {0571787, 0411686, 0834809, 0590969, 0457429, 0714733}, - {0592704, 0423190, 0877201, 0613664, 0474163, 0740880}, - {0614125, 0433572, 0908905, 0636935, 0491300, 0767656}, - {0636056, 0445239, 0954084, 0660787, 0508844, 0795070}, - {0658503, 0457001, 0987754, 0685228, 0526802, 0823128}, - {0681472, 0467489, 1035837, 0710266, 0545177, 0851840}, - {0704969, 0479378, 1071552, 0735907, 0563975, 0881211}, - {0729000, 0491346, 1122660, 0762160, 0583200, 0911250}, - {0753571, 0501878, 1160499, 0789030, 0602856, 0941963}, - {0778688, 0513934, 1214753, 0816525, 0622950, 0973360}, - {0804357, 0526049, 1254796, 0844653, 0643485, 1005446}, - {0830584, 0536557, 1312322, 0873420, 0664467, 1038230}, - {0857375, 0548720, 1354652, 0902835, 0685900, 1071718}, - {0884736, 0560922, 1415577, 0932903, 0707788, 1105920}, - {0912673, 0571333, 1460276, 0963632, 0730138, 1140841}, - {0941192, 0583539, 1524731, 0995030, 0752953, 1176490}, - {0970299, 0591882, 1571884, 1027103, 0776239, 1212873}, - {1000000, 0600000, 1640000, 1059860, 0800000, 1250000}, - }; - - #endregion + if (exp >= ExpTable[99, growth]) + return 100; + int tl = 1; // Initial Level. Iterate upwards to find the level + while (exp >= ExpTable[tl, growth]) + ++tl; + return tl; } + + /// + /// Gets the minimum Experience points for the specified level. + /// + /// Current level + /// Growth Rate type + /// Experience points needed to have specified level. + public static uint GetEXP(int level, int growth) + { + if (level <= 1) + return 0; + if (level > 100) + level = 100; + return ExpTable[level - 1, growth]; + } + + /// + /// Gets the value for / entries based on the + /// + /// Current Experience + /// Nature ID () + public static int GetNatureVC(uint experience) => (int)(experience % 25); + + /// + /// Gets the amount of EXP to be earned until the next level-up occurs. + /// + /// Current Level + /// Growth Rate type + /// EXP to level up + public static uint GetEXPToLevelUp(int level, int growth) + { + if (level >= 100) + return 0; + var current = ExpTable[level - 1, growth]; + var next = ExpTable[level, growth]; + return next - current; + } + + /// + /// Gets a percentage for Experience Bar progress indication. + /// + /// Current Level + /// Current Experience + /// Growth Rate type + /// Percentage [0,1.00) + public static double GetEXPToLevelUpPercentage(int level, uint exp, int growth) + { + if (level >= 100) + return 0; + var current = ExpTable[level - 1, growth]; + var next = ExpTable[level, growth]; + var amount = next - current; + double progress = exp - current; + return progress / amount; + } + + #region ExpTable + + /// + /// Experience required for next level [0,99] + /// + private static readonly uint[,] ExpTable = + { + {0000000, 0000000, 0000000, 0000000, 0000000, 0000000}, + {0000008, 0000015, 0000004, 0000009, 0000006, 0000010}, + {0000027, 0000052, 0000013, 0000057, 0000021, 0000033}, + {0000064, 0000122, 0000032, 0000096, 0000051, 0000080}, + {0000125, 0000237, 0000065, 0000135, 0000100, 0000156}, + {0000216, 0000406, 0000112, 0000179, 0000172, 0000270}, + {0000343, 0000637, 0000178, 0000236, 0000274, 0000428}, + {0000512, 0000942, 0000276, 0000314, 0000409, 0000640}, + {0000729, 0001326, 0000393, 0000419, 0000583, 0000911}, + {0001000, 0001800, 0000540, 0000560, 0000800, 0001250}, + {0001331, 0002369, 0000745, 0000742, 0001064, 0001663}, + {0001728, 0003041, 0000967, 0000973, 0001382, 0002160}, + {0002197, 0003822, 0001230, 0001261, 0001757, 0002746}, + {0002744, 0004719, 0001591, 0001612, 0002195, 0003430}, + {0003375, 0005737, 0001957, 0002035, 0002700, 0004218}, + {0004096, 0006881, 0002457, 0002535, 0003276, 0005120}, + {0004913, 0008155, 0003046, 0003120, 0003930, 0006141}, + {0005832, 0009564, 0003732, 0003798, 0004665, 0007290}, + {0006859, 0011111, 0004526, 0004575, 0005487, 0008573}, + {0008000, 0012800, 0005440, 0005460, 0006400, 0010000}, + {0009261, 0014632, 0006482, 0006458, 0007408, 0011576}, + {0010648, 0016610, 0007666, 0007577, 0008518, 0013310}, + {0012167, 0018737, 0009003, 0008825, 0009733, 0015208}, + {0013824, 0021012, 0010506, 0010208, 0011059, 0017280}, + {0015625, 0023437, 0012187, 0011735, 0012500, 0019531}, + {0017576, 0026012, 0014060, 0013411, 0014060, 0021970}, + {0019683, 0028737, 0016140, 0015244, 0015746, 0024603}, + {0021952, 0031610, 0018439, 0017242, 0017561, 0027440}, + {0024389, 0034632, 0020974, 0019411, 0019511, 0030486}, + {0027000, 0037800, 0023760, 0021760, 0021600, 0033750}, + {0029791, 0041111, 0026811, 0024294, 0023832, 0037238}, + {0032768, 0044564, 0030146, 0027021, 0026214, 0040960}, + {0035937, 0048155, 0033780, 0029949, 0028749, 0044921}, + {0039304, 0051881, 0037731, 0033084, 0031443, 0049130}, + {0042875, 0055737, 0042017, 0036435, 0034300, 0053593}, + {0046656, 0059719, 0046656, 0040007, 0037324, 0058320}, + {0050653, 0063822, 0050653, 0043808, 0040522, 0063316}, + {0054872, 0068041, 0055969, 0047846, 0043897, 0068590}, + {0059319, 0072369, 0060505, 0052127, 0047455, 0074148}, + {0064000, 0076800, 0066560, 0056660, 0051200, 0080000}, + {0068921, 0081326, 0071677, 0061450, 0055136, 0086151}, + {0074088, 0085942, 0078533, 0066505, 0059270, 0092610}, + {0079507, 0090637, 0084277, 0071833, 0063605, 0099383}, + {0085184, 0095406, 0091998, 0077440, 0068147, 0106480}, + {0091125, 0100237, 0098415, 0083335, 0072900, 0113906}, + {0097336, 0105122, 0107069, 0089523, 0077868, 0121670}, + {0103823, 0110052, 0114205, 0096012, 0083058, 0129778}, + {0110592, 0115015, 0123863, 0102810, 0088473, 0138240}, + {0117649, 0120001, 0131766, 0109923, 0094119, 0147061}, + {0125000, 0125000, 0142500, 0117360, 0100000, 0156250}, + {0132651, 0131324, 0151222, 0125126, 0106120, 0165813}, + {0140608, 0137795, 0163105, 0133229, 0112486, 0175760}, + {0148877, 0144410, 0172697, 0141677, 0119101, 0186096}, + {0157464, 0151165, 0185807, 0150476, 0125971, 0196830}, + {0166375, 0158056, 0196322, 0159635, 0133100, 0207968}, + {0175616, 0165079, 0210739, 0169159, 0140492, 0219520}, + {0185193, 0172229, 0222231, 0179056, 0148154, 0231491}, + {0195112, 0179503, 0238036, 0189334, 0156089, 0243890}, + {0205379, 0186894, 0250562, 0199999, 0164303, 0256723}, + {0216000, 0194400, 0267840, 0211060, 0172800, 0270000}, + {0226981, 0202013, 0281456, 0222522, 0181584, 0283726}, + {0238328, 0209728, 0300293, 0234393, 0190662, 0297910}, + {0250047, 0217540, 0315059, 0246681, 0200037, 0312558}, + {0262144, 0225443, 0335544, 0259392, 0209715, 0327680}, + {0274625, 0233431, 0351520, 0272535, 0219700, 0343281}, + {0287496, 0241496, 0373744, 0286115, 0229996, 0359370}, + {0300763, 0249633, 0390991, 0300140, 0240610, 0375953}, + {0314432, 0257834, 0415050, 0314618, 0251545, 0393040}, + {0328509, 0267406, 0433631, 0329555, 0262807, 0410636}, + {0343000, 0276458, 0459620, 0344960, 0274400, 0428750}, + {0357911, 0286328, 0479600, 0360838, 0286328, 0447388}, + {0373248, 0296358, 0507617, 0377197, 0298598, 0466560}, + {0389017, 0305767, 0529063, 0394045, 0311213, 0486271}, + {0405224, 0316074, 0559209, 0411388, 0324179, 0506530}, + {0421875, 0326531, 0582187, 0429235, 0337500, 0527343}, + {0438976, 0336255, 0614566, 0447591, 0351180, 0548720}, + {0456533, 0346965, 0639146, 0466464, 0365226, 0570666}, + {0474552, 0357812, 0673863, 0485862, 0379641, 0593190}, + {0493039, 0367807, 0700115, 0505791, 0394431, 0616298}, + {0512000, 0378880, 0737280, 0526260, 0409600, 0640000}, + {0531441, 0390077, 0765275, 0547274, 0425152, 0664301}, + {0551368, 0400293, 0804997, 0568841, 0441094, 0689210}, + {0571787, 0411686, 0834809, 0590969, 0457429, 0714733}, + {0592704, 0423190, 0877201, 0613664, 0474163, 0740880}, + {0614125, 0433572, 0908905, 0636935, 0491300, 0767656}, + {0636056, 0445239, 0954084, 0660787, 0508844, 0795070}, + {0658503, 0457001, 0987754, 0685228, 0526802, 0823128}, + {0681472, 0467489, 1035837, 0710266, 0545177, 0851840}, + {0704969, 0479378, 1071552, 0735907, 0563975, 0881211}, + {0729000, 0491346, 1122660, 0762160, 0583200, 0911250}, + {0753571, 0501878, 1160499, 0789030, 0602856, 0941963}, + {0778688, 0513934, 1214753, 0816525, 0622950, 0973360}, + {0804357, 0526049, 1254796, 0844653, 0643485, 1005446}, + {0830584, 0536557, 1312322, 0873420, 0664467, 1038230}, + {0857375, 0548720, 1354652, 0902835, 0685900, 1071718}, + {0884736, 0560922, 1415577, 0932903, 0707788, 1105920}, + {0912673, 0571333, 1460276, 0963632, 0730138, 1140841}, + {0941192, 0583539, 1524731, 0995030, 0752953, 1176490}, + {0970299, 0591882, 1571884, 1027103, 0776239, 1212873}, + {1000000, 0600000, 1640000, 1059860, 0800000, 1250000}, + }; + + #endregion } diff --git a/PKHeX.Core/PKM/Util/Language.cs b/PKHeX.Core/PKM/Util/Language.cs index 2a744bbdc..7bd7cb38b 100644 --- a/PKHeX.Core/PKM/Util/Language.cs +++ b/PKHeX.Core/PKM/Util/Language.cs @@ -1,100 +1,99 @@ using System; using static PKHeX.Core.LanguageID; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic relating to values. +/// +public static class Language { - /// - /// Logic relating to values. - /// - public static class Language + private static readonly byte[] Languages = { - private static readonly byte[] Languages = - { - (byte)Japanese, - (byte)English, - (byte)French, - (byte)German, - (byte)Spanish, - (byte)Italian, + (byte)Japanese, + (byte)English, + (byte)French, + (byte)German, + (byte)Spanish, + (byte)Italian, - (byte)Korean, // GS + (byte)Korean, // GS - (byte)ChineseS, - (byte)ChineseT, - }; + (byte)ChineseS, + (byte)ChineseT, + }; - // check Korean for the VC case, never possible to match string outside of this case - private static readonly byte[] Languages_GB = Languages.AsSpan(0, 7).ToArray(); // [..KOR] - private static readonly byte[] Languages_3 = Languages.AsSpan(0, 6).ToArray(); // [..KOR) - private const LanguageID SafeLanguage = English; + // check Korean for the VC case, never possible to match string outside of this case + private static readonly byte[] Languages_GB = Languages.AsSpan(0, 7).ToArray(); // [..KOR] + private static readonly byte[] Languages_3 = Languages.AsSpan(0, 6).ToArray(); // [..KOR) + private const LanguageID SafeLanguage = English; - public static ReadOnlySpan GetAvailableGameLanguages(int generation = PKX.Generation) => generation switch - { - 1 => Languages_3, // No KOR - 2 => Languages_GB, - 3 => Languages_3, // No KOR - 4 or 5 or 6 => Languages_GB, - _ => Languages, - }; + public static ReadOnlySpan GetAvailableGameLanguages(int generation = PKX.Generation) => generation switch + { + 1 => Languages_3, // No KOR + 2 => Languages_GB, + 3 => Languages_3, // No KOR + 4 or 5 or 6 => Languages_GB, + _ => Languages, + }; - private static bool HasLanguage(byte[] permitted, byte language) - { - int index = Array.IndexOf(permitted, language); - return index != -1; - } - - public static LanguageID GetSafeLanguage(int generation, LanguageID prefer, GameVersion game = GameVersion.Any) => generation switch - { - 1 when game == GameVersion.BU => Japanese, - 1 => HasLanguage(Languages_3, (byte)prefer) ? prefer : SafeLanguage, - 2 => HasLanguage(Languages_GB, (byte)prefer) && (prefer != Korean || game == GameVersion.C) ? prefer : SafeLanguage, - 3 => HasLanguage(Languages_3 , (byte)prefer) ? prefer : SafeLanguage, - 4 or 5 or 6 => HasLanguage(Languages_GB, (byte)prefer) ? prefer : SafeLanguage, - _ => HasLanguage(Languages, (byte)prefer) ? prefer : SafeLanguage, - }; - - public static string GetLanguage2CharName(this LanguageID language) => language switch - { - Japanese => "ja", - French => "fr", - Italian => "it", - German => "de", - Spanish => "es", - Korean => "ko", - ChineseS or ChineseT => "zh", - _ => GameLanguage.DefaultLanguage, - }; - - /// - /// Gets the Main Series language ID from a GameCube (C/XD) language ID. - /// - /// GameCube (C/XD) language ID. - /// Main Series language ID. - /// If no conversion is possible or maps to the same value, the input is returned. - public static byte GetMainLangIDfromGC(byte value) => (LanguageGC)value switch - { - LanguageGC.German => (byte)German, - LanguageGC.French => (byte)French, - LanguageGC.Italian => (byte)Italian, - LanguageGC.Spanish => (byte)Spanish, - LanguageGC.UNUSED_6 => (byte)UNUSED_6, - _ => value, - }; - - /// - /// Gets the GameCube (C/XD) language ID from a Main Series language ID. - /// - /// Main Series language ID. - /// GameCube (C/XD) language ID. - /// If no conversion is possible or maps to the same value, the input is returned. - public static byte GetGCLangIDfromMain(byte value) => (LanguageID)value switch - { - French => (byte)LanguageGC.French, - Italian => (byte)LanguageGC.Italian, - German => (byte)LanguageGC.German, - UNUSED_6 => (byte)LanguageGC.UNUSED_6, - Spanish => (byte)LanguageGC.Spanish, - _ => value, - }; + private static bool HasLanguage(byte[] permitted, byte language) + { + int index = Array.IndexOf(permitted, language); + return index != -1; } + + public static LanguageID GetSafeLanguage(int generation, LanguageID prefer, GameVersion game = GameVersion.Any) => generation switch + { + 1 when game == GameVersion.BU => Japanese, + 1 => HasLanguage(Languages_3, (byte)prefer) ? prefer : SafeLanguage, + 2 => HasLanguage(Languages_GB, (byte)prefer) && (prefer != Korean || game == GameVersion.C) ? prefer : SafeLanguage, + 3 => HasLanguage(Languages_3 , (byte)prefer) ? prefer : SafeLanguage, + 4 or 5 or 6 => HasLanguage(Languages_GB, (byte)prefer) ? prefer : SafeLanguage, + _ => HasLanguage(Languages, (byte)prefer) ? prefer : SafeLanguage, + }; + + public static string GetLanguage2CharName(this LanguageID language) => language switch + { + Japanese => "ja", + French => "fr", + Italian => "it", + German => "de", + Spanish => "es", + Korean => "ko", + ChineseS or ChineseT => "zh", + _ => GameLanguage.DefaultLanguage, + }; + + /// + /// Gets the Main Series language ID from a GameCube (C/XD) language ID. + /// + /// GameCube (C/XD) language ID. + /// Main Series language ID. + /// If no conversion is possible or maps to the same value, the input is returned. + public static byte GetMainLangIDfromGC(byte value) => (LanguageGC)value switch + { + LanguageGC.German => (byte)German, + LanguageGC.French => (byte)French, + LanguageGC.Italian => (byte)Italian, + LanguageGC.Spanish => (byte)Spanish, + LanguageGC.UNUSED_6 => (byte)UNUSED_6, + _ => value, + }; + + /// + /// Gets the GameCube (C/XD) language ID from a Main Series language ID. + /// + /// Main Series language ID. + /// GameCube (C/XD) language ID. + /// If no conversion is possible or maps to the same value, the input is returned. + public static byte GetGCLangIDfromMain(byte value) => (LanguageID)value switch + { + French => (byte)LanguageGC.French, + Italian => (byte)LanguageGC.Italian, + German => (byte)LanguageGC.German, + UNUSED_6 => (byte)LanguageGC.UNUSED_6, + Spanish => (byte)LanguageGC.Spanish, + _ => value, + }; } diff --git a/PKHeX.Core/PKM/Util/PokeCrypto.cs b/PKHeX.Core/PKM/Util/PokeCrypto.cs index fbd6e69f3..de0c9f675 100644 --- a/PKHeX.Core/PKM/Util/PokeCrypto.cs +++ b/PKHeX.Core/PKM/Util/PokeCrypto.cs @@ -1,457 +1,456 @@ -using System; +using System; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic related to Encrypting and Decrypting Pokémon entity data. +/// +public static class PokeCrypto { + internal const int SIZE_1ULIST = 69; + internal const int SIZE_1JLIST = 59; + internal const int SIZE_1PARTY = 44; + internal const int SIZE_1STORED = 33; + + internal const int SIZE_2ULIST = 73; + internal const int SIZE_2JLIST = 63; + internal const int SIZE_2PARTY = 48; + internal const int SIZE_2STORED = 32; + internal const int SIZE_2STADIUM = 60; + + internal const int SIZE_3CSTORED = 312; + internal const int SIZE_3XSTORED = 196; + internal const int SIZE_3PARTY = 100; + internal const int SIZE_3STORED = 80; + private const int SIZE_3HEADER = 32; + private const int SIZE_3BLOCK = 12; + + internal const int SIZE_4PARTY = 236; + internal const int SIZE_4STORED = 136; + private const int SIZE_4BLOCK = 32; + + internal const int SIZE_5PARTY = 220; + internal const int SIZE_5STORED = 136; + //private const int SIZE_5BLOCK = SIZE_4BLOCK; + + internal const int SIZE_6PARTY = 0x104; + internal const int SIZE_6STORED = 0xE8; + private const int SIZE_6BLOCK = 56; + + // Gen7 Format is the same size as Gen6. + + internal const int SIZE_8STORED = 8 + (4 * SIZE_8BLOCK); // 0x148 + internal const int SIZE_8PARTY = SIZE_8STORED + 0x10; // 0x158 + private const int SIZE_8BLOCK = 80; // 0x50 + + internal const int SIZE_8ASTORED = 8 + (4 * SIZE_8ABLOCK); // 0x168 + internal const int SIZE_8APARTY = SIZE_8ASTORED + 0x10; // 0x178 + private const int SIZE_8ABLOCK = 88; // 0x58 + /// - /// Logic related to Encrypting and Decrypting Pokémon entity data. + /// Positions for shuffling. /// - public static class PokeCrypto + private static readonly byte[] BlockPosition = { - internal const int SIZE_1ULIST = 69; - internal const int SIZE_1JLIST = 59; - internal const int SIZE_1PARTY = 44; - internal const int SIZE_1STORED = 33; + 0, 1, 2, 3, + 0, 1, 3, 2, + 0, 2, 1, 3, + 0, 3, 1, 2, + 0, 2, 3, 1, + 0, 3, 2, 1, + 1, 0, 2, 3, + 1, 0, 3, 2, + 2, 0, 1, 3, + 3, 0, 1, 2, + 2, 0, 3, 1, + 3, 0, 2, 1, + 1, 2, 0, 3, + 1, 3, 0, 2, + 2, 1, 0, 3, + 3, 1, 0, 2, + 2, 3, 0, 1, + 3, 2, 0, 1, + 1, 2, 3, 0, + 1, 3, 2, 0, + 2, 1, 3, 0, + 3, 1, 2, 0, + 2, 3, 1, 0, + 3, 2, 1, 0, - internal const int SIZE_2ULIST = 73; - internal const int SIZE_2JLIST = 63; - internal const int SIZE_2PARTY = 48; - internal const int SIZE_2STORED = 32; - internal const int SIZE_2STADIUM = 60; + // duplicates of 0-7 to eliminate modulus + 0, 1, 2, 3, + 0, 1, 3, 2, + 0, 2, 1, 3, + 0, 3, 1, 2, + 0, 2, 3, 1, + 0, 3, 2, 1, + 1, 0, 2, 3, + 1, 0, 3, 2, + }; - internal const int SIZE_3CSTORED = 312; - internal const int SIZE_3XSTORED = 196; - internal const int SIZE_3PARTY = 100; - internal const int SIZE_3STORED = 80; - private const int SIZE_3HEADER = 32; - private const int SIZE_3BLOCK = 12; + /// + /// Positions for unshuffling. + /// + internal static readonly byte[] blockPositionInvert = + { + 0, 1, 2, 4, 3, 5, 6, 7, 12, 18, 13, 19, 8, 10, 14, 20, 16, 22, 9, 11, 15, 21, 17, 23, + 0, 1, 2, 4, 3, 5, 6, 7, // duplicates of 0-7 to eliminate modulus + }; - internal const int SIZE_4PARTY = 236; - internal const int SIZE_4STORED = 136; - private const int SIZE_4BLOCK = 32; - - internal const int SIZE_5PARTY = 220; - internal const int SIZE_5STORED = 136; - //private const int SIZE_5BLOCK = SIZE_4BLOCK; - - internal const int SIZE_6PARTY = 0x104; - internal const int SIZE_6STORED = 0xE8; - private const int SIZE_6BLOCK = 56; - - // Gen7 Format is the same size as Gen6. - - internal const int SIZE_8STORED = 8 + (4 * SIZE_8BLOCK); // 0x148 - internal const int SIZE_8PARTY = SIZE_8STORED + 0x10; // 0x158 - private const int SIZE_8BLOCK = 80; // 0x50 - - internal const int SIZE_8ASTORED = 8 + (4 * SIZE_8ABLOCK); // 0x168 - internal const int SIZE_8APARTY = SIZE_8ASTORED + 0x10; // 0x178 - private const int SIZE_8ABLOCK = 88; // 0x58 - - /// - /// Positions for shuffling. - /// - private static readonly byte[] BlockPosition = + /// + /// Shuffles a 232 byte array containing Pokémon data. + /// + /// Data to shuffle + /// Block Shuffle order + /// Size of shuffling chunks + /// Shuffled byte array + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] ShuffleArray(ReadOnlySpan data, uint sv, int blockSize) + { + byte[] sdata = data.ToArray(); + uint index = sv * 4; + const int start = 8; + for (int block = 0; block < 4; block++) { - 0, 1, 2, 3, - 0, 1, 3, 2, - 0, 2, 1, 3, - 0, 3, 1, 2, - 0, 2, 3, 1, - 0, 3, 2, 1, - 1, 0, 2, 3, - 1, 0, 3, 2, - 2, 0, 1, 3, - 3, 0, 1, 2, - 2, 0, 3, 1, - 3, 0, 2, 1, - 1, 2, 0, 3, - 1, 3, 0, 2, - 2, 1, 0, 3, - 3, 1, 0, 2, - 2, 3, 0, 1, - 3, 2, 0, 1, - 1, 2, 3, 0, - 1, 3, 2, 0, - 2, 1, 3, 0, - 3, 1, 2, 0, - 2, 3, 1, 0, - 3, 2, 1, 0, - - // duplicates of 0-7 to eliminate modulus - 0, 1, 2, 3, - 0, 1, 3, 2, - 0, 2, 1, 3, - 0, 3, 1, 2, - 0, 2, 3, 1, - 0, 3, 2, 1, - 1, 0, 2, 3, - 1, 0, 3, 2, - }; - - /// - /// Positions for unshuffling. - /// - internal static readonly byte[] blockPositionInvert = - { - 0, 1, 2, 4, 3, 5, 6, 7, 12, 18, 13, 19, 8, 10, 14, 20, 16, 22, 9, 11, 15, 21, 17, 23, - 0, 1, 2, 4, 3, 5, 6, 7, // duplicates of 0-7 to eliminate modulus - }; - - /// - /// Shuffles a 232 byte array containing Pokémon data. - /// - /// Data to shuffle - /// Block Shuffle order - /// Size of shuffling chunks - /// Shuffled byte array - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] ShuffleArray(ReadOnlySpan data, uint sv, int blockSize) - { - byte[] sdata = data.ToArray(); - uint index = sv * 4; - const int start = 8; - for (int block = 0; block < 4; block++) - { - int ofs = BlockPosition[index + block]; - var src = data.Slice(start + (blockSize * ofs), blockSize); - var dest = sdata.AsSpan(start + (blockSize * block), blockSize); - src.CopyTo(dest); - } - return sdata; + int ofs = BlockPosition[index + block]; + var src = data.Slice(start + (blockSize * ofs), blockSize); + var dest = sdata.AsSpan(start + (blockSize * block), blockSize); + src.CopyTo(dest); } + return sdata; + } - /// - /// Decrypts a Gen8 pkm byte array. - /// - /// Encrypted Pokémon data. - /// Decrypted Pokémon data. - /// Encrypted Pokémon data. - public static byte[] DecryptArray8(Span ekm) + /// + /// Decrypts a Gen8 pk byte array. + /// + /// Encrypted Pokémon data. + /// Decrypted Pokémon data. + /// Encrypted Pokémon data. + public static byte[] DecryptArray8(Span ekm) + { + uint pv = ReadUInt32LittleEndian(ekm); + uint sv = (pv >> 13) & 31; + + CryptPKM(ekm, pv, SIZE_8BLOCK); + return ShuffleArray(ekm, sv, SIZE_8BLOCK); + } + + /// + /// Decrypts a Gen8 pk byte array. + /// + /// Encrypted Pokémon data. + /// Decrypted Pokémon data. + /// Encrypted Pokémon data. + public static byte[] DecryptArray8A(Span ekm) + { + uint pv = ReadUInt32LittleEndian(ekm); + uint sv = (pv >> 13) & 31; + + CryptPKM(ekm, pv, SIZE_8ABLOCK); + return ShuffleArray(ekm, sv, SIZE_8ABLOCK); + } + + /// + /// Encrypts a Gen8 pk byte array. + /// + /// Decrypted Pokémon data. + public static byte[] EncryptArray8(ReadOnlySpan pk) + { + uint pv = ReadUInt32LittleEndian(pk); + uint sv = (pv >> 13) & 31; + + byte[] ekm = ShuffleArray(pk, blockPositionInvert[sv], SIZE_8BLOCK); + CryptPKM(ekm, pv, SIZE_8BLOCK); + return ekm; + } + + /// + /// Encrypts a Gen8 pk byte array. + /// + /// Decrypted Pokémon data. + public static byte[] EncryptArray8A(ReadOnlySpan pk) + { + uint pv = ReadUInt32LittleEndian(pk); + uint sv = (pv >> 13) & 31; + + byte[] ekm = ShuffleArray(pk, blockPositionInvert[sv], SIZE_8ABLOCK); + CryptPKM(ekm, pv, SIZE_8ABLOCK); + return ekm; + } + + /// + /// Decrypts a 232 byte + party stat byte array. + /// + /// Encrypted Pokémon data. + /// Decrypted Pokémon data. + /// Encrypted Pokémon data. + public static byte[] DecryptArray6(Span ekm) + { + uint pv = ReadUInt32LittleEndian(ekm); + uint sv = (pv >> 13) & 31; + + CryptPKM(ekm, pv, SIZE_6BLOCK); + return ShuffleArray(ekm, sv, SIZE_6BLOCK); + } + + /// + /// Encrypts a 232 byte + party stat byte array. + /// + /// Decrypted Pokémon data. + public static byte[] EncryptArray6(ReadOnlySpan pk) + { + uint pv = ReadUInt32LittleEndian(pk); + uint sv = (pv >> 13) & 31; + + byte[] ekm = ShuffleArray(pk, blockPositionInvert[sv], SIZE_6BLOCK); + CryptPKM(ekm, pv, SIZE_6BLOCK); + return ekm; + } + + /// + /// Decrypts a 136 byte + party stat byte array. + /// + /// Encrypted Pokémon data. + /// Decrypted Pokémon data. + public static byte[] DecryptArray45(Span ekm) + { + uint pv = ReadUInt32LittleEndian(ekm); + uint chk = ReadUInt16LittleEndian(ekm[6..]); + uint sv = (pv >> 13) & 31; + + CryptPKM45(ekm, pv, chk, SIZE_4BLOCK); + return ShuffleArray(ekm, sv, SIZE_4BLOCK); + } + + /// + /// Encrypts a 136 byte + party stat byte array. + /// + /// Decrypted Pokémon data. + /// Encrypted Pokémon data. + public static byte[] EncryptArray45(ReadOnlySpan pk) + { + uint pv = ReadUInt32LittleEndian(pk); + uint chk = ReadUInt16LittleEndian(pk[6..]); + uint sv = (pv >> 13) & 31; + + byte[] ekm = ShuffleArray(pk, blockPositionInvert[sv], SIZE_4BLOCK); + CryptPKM45(ekm, pv, chk, SIZE_4BLOCK); + return ekm; + } + + /// + /// Decrypts a 136 byte + party stat byte array. + /// + /// Encrypted Pokémon data. + /// Decrypted Pokémon data. + public static byte[] DecryptArray4BE(ReadOnlySpan ekm) + { + uint pv = ReadUInt32BigEndian(ekm); + uint sv = (pv >> 13) & 31; + return ShuffleArray(ekm, sv, SIZE_4BLOCK); + } + + /// + /// Encrypts a 136 byte + party stat byte array. + /// + /// Decrypted Pokémon data. + /// Encrypted Pokémon data. + public static byte[] EncryptArray4BE(ReadOnlySpan pk) + { + uint pv = ReadUInt32BigEndian(pk); + uint sv = (pv >> 13) & 31; + return ShuffleArray(pk, blockPositionInvert[sv], SIZE_4BLOCK); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CryptPKM(Span data, uint pv, int blockSize) + { + const int start = 8; + int end = (4 * blockSize) + start; + CryptArray(data[start..end], pv); // Blocks + if (data.Length > end) + CryptArray(data[end..], pv); // Party Stats + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CryptPKM45(Span data, uint pv, uint chk, int blockSize) + { + const int start = 8; + int end = (4 * blockSize) + start; + CryptArray(data[start..end], chk); // Blocks + if (data.Length > end) + CryptArray(data[end..], pv); // Party Stats + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CryptArray(Span data, uint seed) + { + var reinterpret = MemoryMarshal.Cast(data); + for (int i = 0; i < reinterpret.Length; i++) { - uint pv = ReadUInt32LittleEndian(ekm); - uint sv = pv >> 13 & 31; - - CryptPKM(ekm, pv, SIZE_8BLOCK); - return ShuffleArray(ekm, sv, SIZE_8BLOCK); - } - - /// - /// Decrypts a Gen8 pkm byte array. - /// - /// Encrypted Pokémon data. - /// Decrypted Pokémon data. - /// Encrypted Pokémon data. - public static byte[] DecryptArray8A(Span ekm) - { - uint pv = ReadUInt32LittleEndian(ekm); - uint sv = pv >> 13 & 31; - - CryptPKM(ekm, pv, SIZE_8ABLOCK); - return ShuffleArray(ekm, sv, SIZE_8ABLOCK); - } - - /// - /// Encrypts a Gen8 pkm byte array. - /// - /// Decrypted Pokémon data. - public static byte[] EncryptArray8(ReadOnlySpan pkm) - { - uint pv = ReadUInt32LittleEndian(pkm); - uint sv = pv >> 13 & 31; - - byte[] ekm = ShuffleArray(pkm, blockPositionInvert[sv], SIZE_8BLOCK); - CryptPKM(ekm, pv, SIZE_8BLOCK); - return ekm; - } - - /// - /// Encrypts a Gen8 pkm byte array. - /// - /// Decrypted Pokémon data. - public static byte[] EncryptArray8A(ReadOnlySpan pkm) - { - uint pv = ReadUInt32LittleEndian(pkm); - uint sv = pv >> 13 & 31; - - byte[] ekm = ShuffleArray(pkm, blockPositionInvert[sv], SIZE_8ABLOCK); - CryptPKM(ekm, pv, SIZE_8ABLOCK); - return ekm; - } - - /// - /// Decrypts a 232 byte + party stat byte array. - /// - /// Encrypted Pokémon data. - /// Decrypted Pokémon data. - /// Encrypted Pokémon data. - public static byte[] DecryptArray6(Span ekm) - { - uint pv = ReadUInt32LittleEndian(ekm); - uint sv = pv >> 13 & 31; - - CryptPKM(ekm, pv, SIZE_6BLOCK); - return ShuffleArray(ekm, sv, SIZE_6BLOCK); - } - - /// - /// Encrypts a 232 byte + party stat byte array. - /// - /// Decrypted Pokémon data. - public static byte[] EncryptArray6(ReadOnlySpan pkm) - { - uint pv = ReadUInt32LittleEndian(pkm); - uint sv = pv >> 13 & 31; - - byte[] ekm = ShuffleArray(pkm, blockPositionInvert[sv], SIZE_6BLOCK); - CryptPKM(ekm, pv, SIZE_6BLOCK); - return ekm; - } - - /// - /// Decrypts a 136 byte + party stat byte array. - /// - /// Encrypted Pokémon data. - /// Decrypted Pokémon data. - public static byte[] DecryptArray45(Span ekm) - { - uint pv = ReadUInt32LittleEndian(ekm); - uint chk = ReadUInt16LittleEndian(ekm[6..]); - uint sv = pv >> 13 & 31; - - CryptPKM45(ekm, pv, chk, SIZE_4BLOCK); - return ShuffleArray(ekm, sv, SIZE_4BLOCK); - } - - /// - /// Encrypts a 136 byte + party stat byte array. - /// - /// Decrypted Pokémon data. - /// Encrypted Pokémon data. - public static byte[] EncryptArray45(ReadOnlySpan pkm) - { - uint pv = ReadUInt32LittleEndian(pkm); - uint chk = ReadUInt16LittleEndian(pkm[6..]); - uint sv = pv >> 13 & 31; - - byte[] ekm = ShuffleArray(pkm, blockPositionInvert[sv], SIZE_4BLOCK); - CryptPKM45(ekm, pv, chk, SIZE_4BLOCK); - return ekm; - } - - /// - /// Decrypts a 136 byte + party stat byte array. - /// - /// Encrypted Pokémon data. - /// Decrypted Pokémon data. - public static byte[] DecryptArray4BE(ReadOnlySpan ekm) - { - uint pv = ReadUInt32BigEndian(ekm); - uint sv = pv >> 13 & 31; - return ShuffleArray(ekm, sv, SIZE_4BLOCK); - } - - /// - /// Encrypts a 136 byte + party stat byte array. - /// - /// Decrypted Pokémon data. - /// Encrypted Pokémon data. - public static byte[] EncryptArray4BE(ReadOnlySpan pkm) - { - uint pv = ReadUInt32BigEndian(pkm); - uint sv = pv >> 13 & 31; - return ShuffleArray(pkm, blockPositionInvert[sv], SIZE_4BLOCK); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CryptPKM(Span data, uint pv, int blockSize) - { - const int start = 8; - int end = (4 * blockSize) + start; - CryptArray(data[start..end], pv); // Blocks - if (data.Length > end) - CryptArray(data[end..], pv); // Party Stats - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CryptPKM45(Span data, uint pv, uint chk, int blockSize) - { - const int start = 8; - int end = (4 * blockSize) + start; - CryptArray(data[start..end], chk); // Blocks - if (data.Length > end) - CryptArray(data[end..], pv); // Party Stats - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CryptArray(Span data, uint seed) - { - var reinterpret = MemoryMarshal.Cast(data); - for (int i = 0; i < reinterpret.Length; i++) - { - seed = (0x41C64E6D * seed) + 0x00006073; - var xor = (ushort)(seed >> 16); - if (!BitConverter.IsLittleEndian) - xor = ReverseEndianness(xor); - reinterpret[i] ^= xor; - } - } - - /// - /// Decrypts an 80 byte format Generation 3 Pokémon byte array. - /// - /// Encrypted data. - /// Decrypted data. - public static byte[] DecryptArray3(Span ekm) - { - Debug.Assert(ekm.Length is SIZE_3PARTY or SIZE_3STORED); - - uint PID = ReadUInt32LittleEndian(ekm); - uint OID = ReadUInt32LittleEndian(ekm[4..]); - uint seed = PID ^ OID; - - var toEncrypt = ekm[SIZE_3HEADER..SIZE_3STORED]; - for (int i = 0; i < toEncrypt.Length; i += 4) - { - var span = toEncrypt.Slice(i, 4); - var chunk = ReadUInt32LittleEndian(span); - var update = chunk ^ seed; - WriteUInt32LittleEndian(span, update); - } - return ShuffleArray3(ekm, PID % 24); - } - - /// - /// Shuffles an 80 byte format Generation 3 Pokémon byte array. - /// - /// Un-shuffled data. - /// Block order shuffle value - /// Un-shuffled data. - private static byte[] ShuffleArray3(ReadOnlySpan data, uint sv) - { - byte[] sdata = data.ToArray(); - uint index = sv * 4; - for (int block = 0; block < 4; block++) - { - int ofs = BlockPosition[index + block]; - var src = data.Slice(SIZE_3HEADER + (SIZE_3BLOCK * ofs), SIZE_3BLOCK); - var dest = sdata.AsSpan(SIZE_3HEADER + (SIZE_3BLOCK * block), SIZE_3BLOCK); - src.CopyTo(dest); - } - - return sdata; - } - - /// - /// Encrypts an 80 byte format Generation 3 Pokémon byte array. - /// - /// Decrypted data. - /// Encrypted data. - public static byte[] EncryptArray3(ReadOnlySpan pkm) - { - Debug.Assert(pkm.Length is SIZE_3PARTY or SIZE_3STORED); - - uint PID = ReadUInt32LittleEndian(pkm); - uint OID = ReadUInt32LittleEndian(pkm[4..]); - uint seed = PID ^ OID; - - byte[] ekm = ShuffleArray3(pkm, blockPositionInvert[PID % 24]); - - var toEncrypt = ekm.AsSpan()[SIZE_3HEADER..SIZE_3STORED]; - for (int i = 0; i < toEncrypt.Length; i += 4) - { - var span = toEncrypt.Slice(i, 4); - var chunk = ReadUInt32LittleEndian(span); - var update = chunk ^ seed; - WriteUInt32LittleEndian(span, update); - } - return ekm; - } - - /// - /// Gets the checksum of a 232 byte array. - /// - /// Decrypted Pokémon data. - /// Offset at which the Stored data ends and the Party data starts. - public static ushort GetCHK(ReadOnlySpan data, int partyStart) - { - ushort chk = 0; - var span = data[0x08..partyStart]; - for (int i = 0; i < span.Length; i += 2) - chk += ReadUInt16LittleEndian(span[i..]); - return chk; - } - - /// - /// Gets the checksum of a Generation 3 byte array. - /// - /// Decrypted Pokémon data. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ushort GetCHK3(ReadOnlySpan data) - { - ushort chk = 0; - var span = data[0x20..SIZE_3STORED]; - for (int i = 0; i < span.Length; i += 2) - chk += ReadUInt16LittleEndian(span[i..]); - return chk; - } - - /// - /// Decrypts the input data into a new array if it is encrypted, and updates the reference. - /// - /// Generation 3 Format encryption check which verifies the checksum - public static void DecryptIfEncrypted3(ref byte[] pkm) - { - ushort chk = GetCHK3(pkm); - if (chk != ReadUInt16LittleEndian(pkm.AsSpan(0x1C))) - pkm = DecryptArray3(pkm); - } - - /// - /// Decrypts the input data into a new array if it is encrypted, and updates the reference. - /// - /// Generation 4 & 5 Format encryption check which checks for the unused bytes - public static void DecryptIfEncrypted45(ref byte[] pkm) - { - var span = pkm.AsSpan(); - if (ReadUInt32LittleEndian(span[0x64..]) != 0) - pkm = DecryptArray45(span); - } - - /// - /// Decrypts the input data into a new array if it is encrypted, and updates the reference. - /// - /// Generation 6 & 7 Format encryption check - public static void DecryptIfEncrypted67(ref byte[] pkm) - { - var span = pkm.AsSpan(); - if (ReadUInt16LittleEndian(span[0xC8..]) != 0 || ReadUInt16LittleEndian(span[0x58..]) != 0) - pkm = DecryptArray6(span); - } - - /// - /// Decrypts the input data into a new array if it is encrypted, and updates the reference. - /// - /// Generation 8 Format encryption check - public static void DecryptIfEncrypted8(ref byte[] pkm) - { - var span = pkm.AsSpan(); - if (ReadUInt16LittleEndian(span[0x70..]) != 0 || ReadUInt16LittleEndian(span[0x110..]) != 0) - pkm = DecryptArray8(span); - } - - /// - /// Decrypts the input data into a new array if it is encrypted, and updates the reference. - /// - /// Generation 8 Format encryption check - public static void DecryptIfEncrypted8A(ref byte[] pkm) - { - var span = pkm.AsSpan(); - if (ReadUInt16LittleEndian(span[0x78..]) != 0 || ReadUInt16LittleEndian(span[0x128..]) != 0) - pkm = DecryptArray8A(span); + seed = (0x41C64E6D * seed) + 0x00006073; + var xor = (ushort)(seed >> 16); + if (!BitConverter.IsLittleEndian) + xor = ReverseEndianness(xor); + reinterpret[i] ^= xor; } } + + /// + /// Decrypts an 80 byte format Generation 3 Pokémon byte array. + /// + /// Encrypted data. + /// Decrypted data. + public static byte[] DecryptArray3(Span ekm) + { + Debug.Assert(ekm.Length is SIZE_3PARTY or SIZE_3STORED); + + uint PID = ReadUInt32LittleEndian(ekm); + uint OID = ReadUInt32LittleEndian(ekm[4..]); + uint seed = PID ^ OID; + + var toEncrypt = ekm[SIZE_3HEADER..SIZE_3STORED]; + for (int i = 0; i < toEncrypt.Length; i += 4) + { + var span = toEncrypt.Slice(i, 4); + var chunk = ReadUInt32LittleEndian(span); + var update = chunk ^ seed; + WriteUInt32LittleEndian(span, update); + } + return ShuffleArray3(ekm, PID % 24); + } + + /// + /// Shuffles an 80 byte format Generation 3 Pokémon byte array. + /// + /// Un-shuffled data. + /// Block order shuffle value + /// Un-shuffled data. + private static byte[] ShuffleArray3(ReadOnlySpan data, uint sv) + { + byte[] sdata = data.ToArray(); + uint index = sv * 4; + for (int block = 0; block < 4; block++) + { + int ofs = BlockPosition[index + block]; + var src = data.Slice(SIZE_3HEADER + (SIZE_3BLOCK * ofs), SIZE_3BLOCK); + var dest = sdata.AsSpan(SIZE_3HEADER + (SIZE_3BLOCK * block), SIZE_3BLOCK); + src.CopyTo(dest); + } + + return sdata; + } + + /// + /// Encrypts an 80 byte format Generation 3 Pokémon byte array. + /// + /// Decrypted data. + /// Encrypted data. + public static byte[] EncryptArray3(ReadOnlySpan pk) + { + Debug.Assert(pk.Length is SIZE_3PARTY or SIZE_3STORED); + + uint PID = ReadUInt32LittleEndian(pk); + uint OID = ReadUInt32LittleEndian(pk[4..]); + uint seed = PID ^ OID; + + byte[] ekm = ShuffleArray3(pk, blockPositionInvert[PID % 24]); + + var toEncrypt = ekm.AsSpan()[SIZE_3HEADER..SIZE_3STORED]; + for (int i = 0; i < toEncrypt.Length; i += 4) + { + var span = toEncrypt.Slice(i, 4); + var chunk = ReadUInt32LittleEndian(span); + var update = chunk ^ seed; + WriteUInt32LittleEndian(span, update); + } + return ekm; + } + + /// + /// Gets the checksum of a 232 byte array. + /// + /// Decrypted Pokémon data. + /// Offset at which the Stored data ends and the Party data starts. + public static ushort GetCHK(ReadOnlySpan data, int partyStart) + { + ushort chk = 0; + var span = data[0x08..partyStart]; + for (int i = 0; i < span.Length; i += 2) + chk += ReadUInt16LittleEndian(span[i..]); + return chk; + } + + /// + /// Gets the checksum of a Generation 3 byte array. + /// + /// Decrypted Pokémon data. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort GetCHK3(ReadOnlySpan data) + { + ushort chk = 0; + var span = data[0x20..SIZE_3STORED]; + for (int i = 0; i < span.Length; i += 2) + chk += ReadUInt16LittleEndian(span[i..]); + return chk; + } + + /// + /// Decrypts the input data into a new array if it is encrypted, and updates the reference. + /// + /// Generation 3 Format encryption check which verifies the checksum + public static void DecryptIfEncrypted3(ref byte[] pk) + { + ushort chk = GetCHK3(pk); + if (chk != ReadUInt16LittleEndian(pk.AsSpan(0x1C))) + pk = DecryptArray3(pk); + } + + /// + /// Decrypts the input data into a new array if it is encrypted, and updates the reference. + /// + /// Generation 4 & 5 Format encryption check which checks for the unused bytes + public static void DecryptIfEncrypted45(ref byte[] pk) + { + var span = pk.AsSpan(); + if (ReadUInt32LittleEndian(span[0x64..]) != 0) + pk = DecryptArray45(span); + } + + /// + /// Decrypts the input data into a new array if it is encrypted, and updates the reference. + /// + /// Generation 6 & 7 Format encryption check + public static void DecryptIfEncrypted67(ref byte[] pk) + { + var span = pk.AsSpan(); + if (ReadUInt16LittleEndian(span[0xC8..]) != 0 || ReadUInt16LittleEndian(span[0x58..]) != 0) + pk = DecryptArray6(span); + } + + /// + /// Decrypts the input data into a new array if it is encrypted, and updates the reference. + /// + /// Generation 8 Format encryption check + public static void DecryptIfEncrypted8(ref byte[] pk) + { + var span = pk.AsSpan(); + if (ReadUInt16LittleEndian(span[0x70..]) != 0 || ReadUInt16LittleEndian(span[0x110..]) != 0) + pk = DecryptArray8(span); + } + + /// + /// Decrypts the input data into a new array if it is encrypted, and updates the reference. + /// + /// Generation 8 Format encryption check + public static void DecryptIfEncrypted8A(ref byte[] pk) + { + var span = pk.AsSpan(); + if (ReadUInt16LittleEndian(span[0x78..]) != 0 || ReadUInt16LittleEndian(span[0x128..]) != 0) + pk = DecryptArray8A(span); + } } diff --git a/PKHeX.Core/PKM/Util/RecentTrainerCache.cs b/PKHeX.Core/PKM/Util/RecentTrainerCache.cs index 6fe1d2db4..fde5a6fe6 100644 --- a/PKHeX.Core/PKM/Util/RecentTrainerCache.cs +++ b/PKHeX.Core/PKM/Util/RecentTrainerCache.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; /// /// Caches a reference to the most recently loaded trainer data. @@ -9,7 +9,7 @@ public static class RecentTrainerCache private static ITrainerInfo Trainer = new SimpleTrainerInfo(); private static IRegionOrigin Trainer67 = new SimpleTrainerInfo(GameVersion.SN); - private static IRegionOrigin GetTrainer3DS(ITrainerInfo tr) => tr is IRegionOrigin r ? r : Trainer67; + private static IRegionOrigin GetTrainer3DS(ITrainerInfo tr) => tr as IRegionOrigin ?? Trainer67; /// Most recently loaded . public static string OT_Name => Trainer.OT; diff --git a/PKHeX.Core/PKM/Util/SpeciesName.cs b/PKHeX.Core/PKM/Util/SpeciesName.cs index 6dd7708b8..36fdacf3a 100644 --- a/PKHeX.Core/PKM/Util/SpeciesName.cs +++ b/PKHeX.Core/PKM/Util/SpeciesName.cs @@ -1,275 +1,274 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic related to the name of a . +/// +public static class SpeciesName { /// - /// Logic related to the name of a . + /// Species name lists indexed by the value. /// - public static class SpeciesName + public static readonly IReadOnlyList> SpeciesLang = new[] { - /// - /// Species name lists indexed by the value. - /// - public static readonly IReadOnlyList> SpeciesLang = new[] + Util.GetSpeciesList("ja"), // 0 (unused, invalid) + Util.GetSpeciesList("ja"), // 1 + Util.GetSpeciesList("en"), // 2 + Util.GetSpeciesList("fr"), // 3 + Util.GetSpeciesList("it"), // 4 + Util.GetSpeciesList("de"), // 5 + Util.GetSpeciesList("es"), // 6 (reserved for Gen3 KO?, unused) + Util.GetSpeciesList("es"), // 7 + Util.GetSpeciesList("ko"), // 8 + Util.GetSpeciesList("zh"), // 9 Simplified + Util.GetSpeciesList("zh2"), // 10 Traditional + }; + + /// + /// Egg name list indexed by the value. + /// + /// Indexing matches . + private static readonly string[] EggNames = + { + "タマゴ", + "タマゴ", + "Egg", + "Œuf", + "Uovo", + "Ei", + "Huevo", + "Huevo", + "알", + "蛋", + "蛋", + }; + + /// + /// to table for all values. + /// + public static readonly IReadOnlyList> SpeciesDict = Util.GetMultiDictionary(SpeciesLang); + + /// + /// Gets a Pokémon's default name for the desired language ID. + /// + /// National Dex number of the Pokémon. Should be 0 if an egg. + /// Language ID of the Pokémon + /// The Species name if within expected range, else an empty string. + /// Should only be used externally for message displays; for accurate in-game names use . + public static string GetSpeciesName(int species, int language) + { + if ((uint)language >= SpeciesLang.Count) + return string.Empty; + + if (species == 0) + return EggNames[language]; + + var arr = SpeciesLang[language]; + if ((uint)species >= arr.Count) + return string.Empty; + + return arr[species]; + } + + /// + /// Gets a Pokémon's default name for the desired language ID and generation. + /// + /// National Dex number of the Pokémon. Should be 0 if an egg. + /// Language ID of the Pokémon + /// Generation specific formatting option + /// Generation specific default species name + public static string GetSpeciesNameGeneration(int species, int language, int generation) + { + return generation switch { - Util.GetSpeciesList("ja"), // 0 (unused, invalid) - Util.GetSpeciesList("ja"), // 1 - Util.GetSpeciesList("en"), // 2 - Util.GetSpeciesList("fr"), // 3 - Util.GetSpeciesList("it"), // 4 - Util.GetSpeciesList("de"), // 5 - Util.GetSpeciesList("es"), // 6 (reserved for Gen3 KO?, unused) - Util.GetSpeciesList("es"), // 7 - Util.GetSpeciesList("ko"), // 8 - Util.GetSpeciesList("zh"), // 9 Simplified - Util.GetSpeciesList("zh2"), // 10 Traditional - }; - - /// - /// Egg name list indexed by the value. - /// - /// Indexing matches . - private static readonly string[] EggNames = - { - "タマゴ", - "タマゴ", - "Egg", - "Œuf", - "Uovo", - "Ei", - "Huevo", - "Huevo", - "알", - "蛋", - "蛋", - }; - - /// - /// to table for all values. - /// - public static readonly IReadOnlyList> SpeciesDict = Util.GetMultiDictionary(SpeciesLang); - - /// - /// Gets a Pokémon's default name for the desired language ID. - /// - /// National Dex number of the Pokémon. Should be 0 if an egg. - /// Language ID of the Pokémon - /// The Species name if within expected range, else an empty string. - /// Should only be used externally for message displays; for accurate in-game names use . - public static string GetSpeciesName(int species, int language) - { - if ((uint)language >= SpeciesLang.Count) - return string.Empty; - - if (species == 0) - return EggNames[language]; - - var arr = SpeciesLang[language]; - if ((uint)species >= arr.Count) - return string.Empty; - - return arr[species]; - } - - /// - /// Gets a Pokémon's default name for the desired language ID and generation. - /// - /// National Dex number of the Pokémon. Should be 0 if an egg. - /// Language ID of the Pokémon - /// Generation specific formatting option - /// Generation specific default species name - public static string GetSpeciesNameGeneration(int species, int language, int generation) - { - return generation switch - { - <= 4 => GetSpeciesName1234(species, language, generation), - 7 when language == (int) LanguageID.ChineseS => GetSpeciesName7ZH(species, language), - _ => GetSpeciesName(species, language), - }; - } - - private static string GetSpeciesName1234(int species, int language, int generation) - { - if (species == 0) - return GetEggName1234(species, language, generation); - - string nick = GetSpeciesName(species, language); - switch (language) - { - case (int)LanguageID.Korean when generation == 2: - return StringConverter2KOR.LocalizeKOR2(nick); - case (int)LanguageID.Korean: - case (int)LanguageID.Japanese: - return nick; // No further processing - } - - Span result = stackalloc char[nick.Length]; - nick.AsSpan().CopyTo(result); - - // All names are uppercase. - for (int i = 0; i < result.Length; i++) - result[i] = char.ToUpperInvariant(result[i]); - if (language == (int)LanguageID.French) - StringConverter4Util.StripDiacriticsFR4(result); // strips accents on E and I - - // Gen1/2 species names do not have spaces. - if (generation >= 3) - return new string(result.ToArray()); - - int indexSpace = result.IndexOf(' '); - if (indexSpace != -1) - { - // Shift down. Strings have at most 1 occurrence of a space. - result[(indexSpace+1)..].CopyTo(result[indexSpace..]); - result = result[..^1]; - } - return new string(result.ToArray()); - } - - private static string GetEggName1234(int species, int language, int generation) - { - if (generation == 3) - return "タマゴ"; // All Gen3 eggs are treated as JPN eggs. - - // Gen2 & Gen4 don't use Œuf like in future games - if (language == (int)LanguageID.French) - return generation == 2 ? "OEUF" : "Oeuf"; - - var nick = GetSpeciesName(species, language); - - // All Gen4 egg names are Title cased. - if (generation == 4) - return nick; - - // Gen2: All Caps - return nick.ToUpperInvariant(); - } - - /// - /// Gets the Generation 7 species name for Chinese games. - /// - /// - /// Species Names for Chinese (Simplified) were revised during Generation 8 Crown Tundra DLC (#2). - /// For a Gen7 species name request, return the old species name (hardcoded... yay). - /// In an updated Gen8 game, the species nickname will automatically reset to the correct localization (on save/load ?), fixing existing entries. - /// We don't differentiate patch revisions, just generation; Gen8 will return the latest localization. - /// Gen8 did revise CHT species names, but only for Barraskewda, Urshifu, and Zarude. These species are new (Gen8); we can just use the latest. - /// - private static string GetSpeciesName7ZH(int species, int language) => species switch - { - // Revised in DLC1 - Isle of Armor - // https://cn.portal-pokemon.com/topics/event/200323190120_post_19.html - (int)Species.Porygon2 => "多边兽Ⅱ", // Later changed to 多边兽2型 - (int)Species.PorygonZ => "多边兽Z", // Later changed to 多边兽乙型 - (int)Species.Mimikyu => "谜拟Q", // Later changed to 谜拟丘 - - // Revised in DLC2 - Crown Tundra - // https://cn.portal-pokemon.com/topics/event/201020170000_post_21.html - (int)Species.Cofagrigus => "死神棺", // Later changed to 迭失棺 - (int)Species.Pangoro => "流氓熊猫", // Later changed to 霸道熊猫 - //(int)Species.Nickit => "偷儿狐", // Later changed to 狡小狐 - //(int)Species.Thievul => "狐大盗", // Later changed to 猾大狐 - //(int)Species.Toxel => "毒电婴", // Later changed to 电音婴 - //(int)Species.Runerigus => "死神板", // Later changed to 迭失板 - + <= 4 => GetSpeciesName1234(species, language, generation), + 7 when language == (int) LanguageID.ChineseS => GetSpeciesName7ZH(species, language), _ => GetSpeciesName(species, language), }; + } - /// - /// Checks if the input is not the species name for all languages. - /// - /// National Dex number of the Pokémon. Should be 0 if an egg. - /// Current name - /// Generation specific formatting option - /// True if it does not match any language name, False if not nicknamed - public static bool IsNicknamedAnyLanguage(int species, string nickname, int generation = PKX.Generation) + private static string GetSpeciesName1234(int species, int language, int generation) + { + if (species == 0) + return GetEggName1234(species, language, generation); + + string nick = GetSpeciesName(species, language); + switch (language) { - var langs = Language.GetAvailableGameLanguages(generation); - foreach (var language in langs) - { - if (!IsNicknamed(species, nickname, language, generation)) - return false; - } - return true; + case (int)LanguageID.Korean when generation == 2: + return StringConverter2KOR.LocalizeKOR2(nick); + case (int)LanguageID.Korean: + case (int)LanguageID.Japanese: + return nick; // No further processing } - /// - /// Checks if the input is not the species name. - /// - /// National Dex number of the Pokémon. Should be 0 if an egg. - /// Current name - /// Language ID of the Pokémon - /// Generation specific formatting option - /// True if it does not match the language name, False if not nicknamed (matches). - public static bool IsNicknamed(int species, string nickname, int language, int generation = PKX.Generation) + Span result = stackalloc char[nick.Length]; + nick.AsSpan().CopyTo(result); + + // All names are uppercase. + for (int i = 0; i < result.Length; i++) + result[i] = char.ToUpperInvariant(result[i]); + if (language == (int)LanguageID.French) + StringConverter4Util.StripDiacriticsFR4(result); // strips accents on E and I + + // Gen1/2 species names do not have spaces. + if (generation >= 3) + return new string(result.ToArray()); + + int indexSpace = result.IndexOf(' '); + if (indexSpace != -1) { - return GetSpeciesNameGeneration(species, language, generation) != nickname; + // Shift down. Strings have at most 1 occurrence of a space. + result[(indexSpace+1)..].CopyTo(result[indexSpace..]); + result = result[..^1]; + } + return new string(result.ToArray()); + } + + private static string GetEggName1234(int species, int language, int generation) + { + if (generation == 3) + return "タマゴ"; // All Gen3 eggs are treated as JPN eggs. + + // Gen2 & Gen4 don't use Œuf like in future games + if (language == (int)LanguageID.French) + return generation == 2 ? "OEUF" : "Oeuf"; + + var nick = GetSpeciesName(species, language); + + // All Gen4 egg names are Title cased. + if (generation == 4) + return nick; + + // Gen2: All Caps + return nick.ToUpperInvariant(); + } + + /// + /// Gets the Generation 7 species name for Chinese games. + /// + /// + /// Species Names for Chinese (Simplified) were revised during Generation 8 Crown Tundra DLC (#2). + /// For a Gen7 species name request, return the old species name (hardcoded... yay). + /// In an updated Gen8 game, the species nickname will automatically reset to the correct localization (on save/load ?), fixing existing entries. + /// We don't differentiate patch revisions, just generation; Gen8 will return the latest localization. + /// Gen8 did revise CHT species names, but only for Barraskewda, Urshifu, and Zarude. These species are new (Gen8); we can just use the latest. + /// + private static string GetSpeciesName7ZH(int species, int language) => species switch + { + // Revised in DLC1 - Isle of Armor + // https://cn.portal-pokemon.com/topics/event/200323190120_post_19.html + (int)Species.Porygon2 => "多边兽Ⅱ", // Later changed to 多边兽2型 + (int)Species.PorygonZ => "多边兽Z", // Later changed to 多边兽乙型 + (int)Species.Mimikyu => "谜拟Q", // Later changed to 谜拟丘 + + // Revised in DLC2 - Crown Tundra + // https://cn.portal-pokemon.com/topics/event/201020170000_post_21.html + (int)Species.Cofagrigus => "死神棺", // Later changed to 迭失棺 + (int)Species.Pangoro => "流氓熊猫", // Later changed to 霸道熊猫 + //(int)Species.Nickit => "偷儿狐", // Later changed to 狡小狐 + //(int)Species.Thievul => "狐大盗", // Later changed to 猾大狐 + //(int)Species.Toxel => "毒电婴", // Later changed to 电音婴 + //(int)Species.Runerigus => "死神板", // Later changed to 迭失板 + + _ => GetSpeciesName(species, language), + }; + + /// + /// Checks if the input is not the species name for all languages. + /// + /// National Dex number of the Pokémon. Should be 0 if an egg. + /// Current name + /// Generation specific formatting option + /// True if it does not match any language name, False if not nicknamed + public static bool IsNicknamedAnyLanguage(int species, string nickname, int generation = PKX.Generation) + { + var langs = Language.GetAvailableGameLanguages(generation); + foreach (var language in langs) + { + if (!IsNicknamed(species, nickname, language, generation)) + return false; + } + return true; + } + + /// + /// Checks if the input is not the species name. + /// + /// National Dex number of the Pokémon. Should be 0 if an egg. + /// Current name + /// Language ID of the Pokémon + /// Generation specific formatting option + /// True if it does not match the language name, False if not nicknamed (matches). + public static bool IsNicknamed(int species, string nickname, int language, int generation = PKX.Generation) + { + return GetSpeciesNameGeneration(species, language, generation) != nickname; + } + + /// + /// Gets the Species name Language ID for the current name and generation. + /// + /// National Dex number of the Pokémon. Should be 0 if an egg. + /// Language ID with a higher priority + /// Current name + /// Generation specific formatting option + /// Language ID if it does not match any language name, -1 if no matches + public static int GetSpeciesNameLanguage(int species, int priorityLanguage, string nickname, int generation = PKX.Generation) + { + var langs = Language.GetAvailableGameLanguages(generation); + var priorityIndex = langs.IndexOf((byte)priorityLanguage); + if (priorityIndex != -1) + { + if (GetSpeciesNameGeneration(species, priorityLanguage, generation) == nickname) + return priorityLanguage; } - /// - /// Gets the Species name Language ID for the current name and generation. - /// - /// National Dex number of the Pokémon. Should be 0 if an egg. - /// Language ID with a higher priority - /// Current name - /// Generation specific formatting option - /// Language ID if it does not match any language name, -1 if no matches - public static int GetSpeciesNameLanguage(int species, int priorityLanguage, string nickname, int generation = PKX.Generation) + return GetSpeciesNameLanguage(species, nickname, generation, langs); + } + + /// + /// Gets the Species name Language ID for the current name and generation. + /// + /// National Dex number of the Pokémon. Should be 0 if an egg. + /// Current name + /// Generation specific formatting option + /// Language ID if it does not match any language name, -1 if no matches + public static int GetSpeciesNameLanguage(int species, string nickname, int generation = PKX.Generation) + { + var langs = Language.GetAvailableGameLanguages(generation); + return GetSpeciesNameLanguage(species, nickname, generation, langs); + } + + private static int GetSpeciesNameLanguage(int species, string nickname, int generation, ReadOnlySpan langs) + { + foreach (var lang in langs) { - var langs = Language.GetAvailableGameLanguages(generation); - var priorityIndex = langs.IndexOf((byte)priorityLanguage); - if (priorityIndex != -1) - { - if (GetSpeciesNameGeneration(species, priorityLanguage, generation) == nickname) - return priorityLanguage; - } - - return GetSpeciesNameLanguage(species, nickname, generation, langs); + if (GetSpeciesNameGeneration(species, lang, generation) == nickname) + return lang; } + return -1; + } - /// - /// Gets the Species name Language ID for the current name and generation. - /// - /// National Dex number of the Pokémon. Should be 0 if an egg. - /// Current name - /// Generation specific formatting option - /// Language ID if it does not match any language name, -1 if no matches - public static int GetSpeciesNameLanguage(int species, string nickname, int generation = PKX.Generation) + /// + /// Gets the Species ID for the specified and . + /// + /// Species Name + /// Language the name is from + /// Species ID + /// Only use this for modern era name -> ID fetching. + public static int GetSpeciesID(string speciesName, int language = (int)LanguageID.English) + { + if (SpeciesDict[language].TryGetValue(speciesName, out var value)) + return value; + + // stupid ’, ignore language if we match these. + return speciesName switch { - var langs = Language.GetAvailableGameLanguages(generation); - return GetSpeciesNameLanguage(species, nickname, generation, langs); - } - - private static int GetSpeciesNameLanguage(int species, string nickname, int generation, ReadOnlySpan langs) - { - foreach (var lang in langs) - { - if (GetSpeciesNameGeneration(species, lang, generation) == nickname) - return lang; - } - return -1; - } - - /// - /// Gets the Species ID for the specified and . - /// - /// Species Name - /// Language the name is from - /// Species ID - /// Only use this for modern era name -> ID fetching. - public static int GetSpeciesID(string speciesName, int language = (int)LanguageID.English) - { - if (SpeciesDict[language].TryGetValue(speciesName, out var value)) - return value; - - // stupid ’, ignore language if we match these. - return speciesName switch - { - "Farfetch'd" => (int)Species.Farfetchd, - "Sirfetch'd" => (int)Species.Sirfetchd, - _ => -1, - }; - } + "Farfetch'd" => (int)Species.Farfetchd, + "Sirfetch'd" => (int)Species.Sirfetchd, + _ => -1, + }; } } diff --git a/PKHeX.Core/PKM/XK3.cs b/PKHeX.Core/PKM/XK3.cs index 36533b545..6ce961f2f 100644 --- a/PKHeX.Core/PKM/XK3.cs +++ b/PKHeX.Core/PKM/XK3.cs @@ -2,255 +2,254 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Generation 3 format, exclusively for Pokémon XD. +public sealed class XK3 : G3PKM, IShadowPKM { - /// Generation 3 format, exclusively for Pokémon XD. - public sealed class XK3 : G3PKM, IShadowPKM + private static readonly ushort[] Unused = { - private static readonly ushort[] Unused = + 0x0A, 0x0B, 0x0C, 0x0D, 0x1E, 0x1F, + 0x2A, 0x2B, + 0x7A, 0x7B, + 0x7E, 0x7F, + }; + + public override IReadOnlyList ExtraBytes => Unused; + + public override int SIZE_PARTY => PokeCrypto.SIZE_3XSTORED; + public override int SIZE_STORED => PokeCrypto.SIZE_3XSTORED; + public override EntityContext Context => EntityContext.Gen3; + public override PersonalInfo PersonalInfo => PersonalTable.RS[Species]; + public XK3(byte[] data) : base(data) { } + public XK3() : base(PokeCrypto.SIZE_3XSTORED) { } + public override PKM Clone() => new XK3((byte[])Data.Clone()){Purification = Purification}; + public override void RefreshChecksum() => Valid = true; + + // Trash Bytes + public override Span OT_Trash => Data.AsSpan(0x38, 22); + public override Span Nickname_Trash => Data.AsSpan(0x4E, 22); + public Span NicknameCopy_Trash => Data.AsSpan(0x64, 22); + + public override ushort SpeciesID3 { get => ReadUInt16BigEndian(Data.AsSpan(0x00)); set => WriteUInt16BigEndian(Data.AsSpan(0x00), value); } // raw access + public override int Species { get => SpeciesConverter.GetG4Species(SpeciesID3); set => SpeciesID3 = (ushort)SpeciesConverter.GetG3Species(value); } + public override int SpriteItem => ItemConverter.GetItemFuture3((ushort)HeldItem); + public override int HeldItem { get => ReadUInt16BigEndian(Data.AsSpan(0x02)); set => WriteUInt16BigEndian(Data.AsSpan(0x02), (ushort)value); } + public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x04)); set => WriteUInt16BigEndian(Data.AsSpan(0x04), (ushort)value); } + public override int OT_Friendship { get => ReadUInt16BigEndian(Data.AsSpan(0x06)); set => WriteUInt16BigEndian(Data.AsSpan(0x06), (ushort)value); } + public override int Met_Location { get => ReadUInt16BigEndian(Data.AsSpan(0x08)); set => WriteUInt16BigEndian(Data.AsSpan(0x08), (ushort)value); } + // 0x0A-0x0B Unknown + // 0x0C-0x0D Unknown + public override int Met_Level { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } + public override int Ball { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } + public override int OT_Gender { get => Data[0x10]; set => Data[0x10] = (byte)value; } + public override int Stat_Level { get => Data[0x11]; set => Data[0x11] = (byte)value; } + public override byte CNT_Sheen { get => Data[0x12]; set => Data[0x12] = value; } + public override int PKRS_Strain { get => Data[0x13] & 0xF; set => Data[0x13] = (byte)(value & 0xF); } + public override int MarkValue { get => SwapBits(Data[0x14], 1, 2); set => Data[0x14] = (byte)SwapBits(value, 1, 2); } + public override int PKRS_Days { get => Math.Max((sbyte)Data[0x15], (sbyte)0); set => Data[0x15] = (byte)(value == 0 ? 0xFF : value & 0xF); } + // 0x16-0x1C Battle Related + private int XDPKMFLAGS { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + public bool UnusedFlag0 { get => (XDPKMFLAGS & (1 << 0)) == 1 << 0; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 0)) | (value ? 1 << 0 : 0); } + public bool UnusedFlag1 { get => (XDPKMFLAGS & (1 << 1)) == 1 << 1; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 1)) | (value ? 1 << 1 : 0); } + public bool CapturedFlag { get => (XDPKMFLAGS & (1 << 2)) == 1 << 2; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 2)) | (value ? 1 << 2 : 0); } + public bool UnusedFlag3 { get => (XDPKMFLAGS & (1 << 3)) == 1 << 3; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 3)) | (value ? 1 << 3 : 0); } + public bool BlockTrades { get => (XDPKMFLAGS & (1 << 4)) == 1 << 4; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 4)) | (value ? 1 << 4 : 0); } + public override bool Valid { get => (XDPKMFLAGS & (1 << 5)) == 0; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 5)) | (value ? 0 : 1 << 5); } // invalid flag + public override bool AbilityBit { get => (XDPKMFLAGS & (1 << 6)) == 1 << 6; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 6)) | (value ? 1 << 6 : 0); } + public override bool IsEgg { get => (XDPKMFLAGS & (1 << 7)) == 1 << 7; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 7)) | (value ? 1 << 7 : 0); } + // 0x1E-0x1F Unknown + public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0x20)); set => WriteUInt32BigEndian(Data.AsSpan(0x20), value); } + public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(0x24)); set => WriteUInt16BigEndian(Data.AsSpan(0x24), (ushort)value); } + public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(0x26)); set => WriteUInt16BigEndian(Data.AsSpan(0x26), (ushort)value); } + public override uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x28)); set => WriteUInt32BigEndian(Data.AsSpan(0x28), value); } + // 0x2A-0x2B Unknown + // 0x2C-0x2F Battle Related + public bool Obedient { get => Data[0x30] == 1; set => Data[0x30] = value ? (byte)1 : (byte)0; } + // 0x31-0x32 Unknown + public int EncounterInfo { get => Data[0x33]; set => Data[0x33] = (byte)value; } + + public override bool FatefulEncounter + { + get => EncounterInfo != 0 || Obedient; + set { - 0x0A, 0x0B, 0x0C, 0x0D, 0x1E, 0x1F, - 0x2A, 0x2B, - 0x7A, 0x7B, - 0x7E, 0x7F, - }; - - public override IReadOnlyList ExtraBytes => Unused; - - public override int SIZE_PARTY => PokeCrypto.SIZE_3XSTORED; - public override int SIZE_STORED => PokeCrypto.SIZE_3XSTORED; - public override EntityContext Context => EntityContext.Gen3; - public override PersonalInfo PersonalInfo => PersonalTable.RS[Species]; - public XK3(byte[] data) : base(data) { } - public XK3() : base(PokeCrypto.SIZE_3XSTORED) { } - public override PKM Clone() => new XK3((byte[])Data.Clone()){Purification = Purification}; - public override void RefreshChecksum() => Valid = true; - - // Trash Bytes - public override Span OT_Trash => Data.AsSpan(0x38, 22); - public override Span Nickname_Trash => Data.AsSpan(0x4E, 22); - public Span NicknameCopy_Trash => Data.AsSpan(0x64, 22); - - public override ushort SpeciesID3 { get => ReadUInt16BigEndian(Data.AsSpan(0x00)); set => WriteUInt16BigEndian(Data.AsSpan(0x00), value); } // raw access - public override int Species { get => SpeciesConverter.GetG4Species(SpeciesID3); set => SpeciesID3 = (ushort)SpeciesConverter.GetG3Species(value); } - public override int SpriteItem => ItemConverter.GetItemFuture3((ushort)HeldItem); - public override int HeldItem { get => ReadUInt16BigEndian(Data.AsSpan(0x02)); set => WriteUInt16BigEndian(Data.AsSpan(0x02), (ushort)value); } - public override int Stat_HPCurrent { get => ReadUInt16BigEndian(Data.AsSpan(0x04)); set => WriteUInt16BigEndian(Data.AsSpan(0x04), (ushort)value); } - public override int OT_Friendship { get => ReadUInt16BigEndian(Data.AsSpan(0x06)); set => WriteUInt16BigEndian(Data.AsSpan(0x06), (ushort)value); } - public override int Met_Location { get => ReadUInt16BigEndian(Data.AsSpan(0x08)); set => WriteUInt16BigEndian(Data.AsSpan(0x08), (ushort)value); } - // 0x0A-0x0B Unknown - // 0x0C-0x0D Unknown - public override int Met_Level { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } - public override int Ball { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } - public override int OT_Gender { get => Data[0x10]; set => Data[0x10] = (byte)value; } - public override int Stat_Level { get => Data[0x11]; set => Data[0x11] = (byte)value; } - public override byte CNT_Sheen { get => Data[0x12]; set => Data[0x12] = value; } - public override int PKRS_Strain { get => Data[0x13] & 0xF; set => Data[0x13] = (byte)(value & 0xF); } - public override int MarkValue { get => SwapBits(Data[0x14], 1, 2); set => Data[0x14] = (byte)SwapBits(value, 1, 2); } - public override int PKRS_Days { get => Math.Max((sbyte)Data[0x15], (sbyte)0); set => Data[0x15] = (byte)(value == 0 ? 0xFF : value & 0xF); } - // 0x16-0x1C Battle Related - private int XDPKMFLAGS { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } - public bool UnusedFlag0 { get => (XDPKMFLAGS & (1 << 0)) == 1 << 0; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 0)) | (value ? 1 << 0 : 0); } - public bool UnusedFlag1 { get => (XDPKMFLAGS & (1 << 1)) == 1 << 1; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 1)) | (value ? 1 << 1 : 0); } - public bool CapturedFlag { get => (XDPKMFLAGS & (1 << 2)) == 1 << 2; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 2)) | (value ? 1 << 2 : 0); } - public bool UnusedFlag3 { get => (XDPKMFLAGS & (1 << 3)) == 1 << 3; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 3)) | (value ? 1 << 3 : 0); } - public bool BlockTrades { get => (XDPKMFLAGS & (1 << 4)) == 1 << 4; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 4)) | (value ? 1 << 4 : 0); } - public override bool Valid { get => (XDPKMFLAGS & (1 << 5)) == 0; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 5)) | (value ? 0 : 1 << 5); } // invalid flag - public override bool AbilityBit { get => (XDPKMFLAGS & (1 << 6)) == 1 << 6; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 6)) | (value ? 1 << 6 : 0); } - public override bool IsEgg { get => (XDPKMFLAGS & (1 << 7)) == 1 << 7; set => XDPKMFLAGS = (XDPKMFLAGS & ~(1 << 7)) | (value ? 1 << 7 : 0); } - // 0x1E-0x1F Unknown - public override uint EXP { get => ReadUInt32BigEndian(Data.AsSpan(0x20)); set => WriteUInt32BigEndian(Data.AsSpan(0x20), value); } - public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(0x24)); set => WriteUInt16BigEndian(Data.AsSpan(0x24), (ushort)value); } - public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(0x26)); set => WriteUInt16BigEndian(Data.AsSpan(0x26), (ushort)value); } - public override uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x28)); set => WriteUInt32BigEndian(Data.AsSpan(0x28), value); } - // 0x2A-0x2B Unknown - // 0x2C-0x2F Battle Related - public bool Obedient { get => Data[0x30] == 1; set => Data[0x30] = value ? (byte)1 : (byte)0; } - // 0x31-0x32 Unknown - public int EncounterInfo { get => Data[0x33]; set => Data[0x33] = (byte)value; } - - public override bool FatefulEncounter - { - get => EncounterInfo != 0 || Obedient; - set + if (EncounterInfo != 0) { - if (EncounterInfo != 0) - { - if (!value) - EncounterInfo = 0; - return; - } - EncounterInfo = (byte) ((EncounterInfo & ~(1 << 0)) | (value ? 1 << 0 : 0)); + if (!value) + EncounterInfo = 0; + return; } + EncounterInfo = (byte) ((EncounterInfo & ~(1 << 0)) | (value ? 1 << 0 : 0)); } - - public override int Version { get => GetGBAVersionID(Data[0x34]); set => Data[0x34] = GetGCVersionID(value); } - public int CurrentRegion { get => Data[0x35]; set => Data[0x35] = (byte)value; } - public int OriginalRegion { get => Data[0x36]; set => Data[0x36] = (byte)value; } - public override int Language { get => Core.Language.GetMainLangIDfromGC(Data[0x37]); set => Data[0x37] = Core.Language.GetGCLangIDfromMain((byte)value); } - public override string OT_Name { get => StringConverter3GC.GetString(OT_Trash); set => StringConverter3GC.SetString(OT_Trash, value.AsSpan(), 10, StringConverterOption.None); } - public override string Nickname { get => StringConverter3GC.GetString(Nickname_Trash); set { StringConverter3GC.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); NicknameCopy = value; } } - public string NicknameCopy { get => StringConverter3GC.GetString(NicknameCopy_Trash); set => StringConverter3GC.SetString(NicknameCopy_Trash, value.AsSpan(), 10, StringConverterOption.None); } - // 0x7A-0x7B Unknown - private ushort RIB0 { get => ReadUInt16BigEndian(Data.AsSpan(0x7C)); set => WriteUInt16BigEndian(Data.AsSpan(0x7C), value); } - public override bool RibbonChampionG3 { get => (RIB0 & (1 << 15)) == 1 << 15; set => RIB0 = (ushort)((RIB0 & ~(1 << 15)) | (value ? 1 << 15 : 0)); } - public override bool RibbonWinning { get => (RIB0 & (1 << 14)) == 1 << 14; set => RIB0 = (ushort)((RIB0 & ~(1 << 14)) | (value ? 1 << 14 : 0)); } - public override bool RibbonVictory { get => (RIB0 & (1 << 13)) == 1 << 13; set => RIB0 = (ushort)((RIB0 & ~(1 << 13)) | (value ? 1 << 13 : 0)); } - public override bool RibbonArtist { get => (RIB0 & (1 << 12)) == 1 << 12; set => RIB0 = (ushort)((RIB0 & ~(1 << 12)) | (value ? 1 << 12 : 0)); } - public override bool RibbonEffort { get => (RIB0 & (1 << 11)) == 1 << 11; set => RIB0 = (ushort)((RIB0 & ~(1 << 11)) | (value ? 1 << 11 : 0)); } - public override bool RibbonChampionBattle { get => (RIB0 & (1 << 10)) == 1 << 10; set => RIB0 = (ushort)((RIB0 & ~(1 << 10)) | (value ? 1 << 10 : 0)); } - public override bool RibbonChampionRegional { get => (RIB0 & (1 << 09)) == 1 << 09; set => RIB0 = (ushort)((RIB0 & ~(1 << 09)) | (value ? 1 << 09 : 0)); } - public override bool RibbonChampionNational { get => (RIB0 & (1 << 08)) == 1 << 08; set => RIB0 = (ushort)((RIB0 & ~(1 << 08)) | (value ? 1 << 08 : 0)); } - public override bool RibbonCountry { get => (RIB0 & (1 << 07)) == 1 << 07; set => RIB0 = (ushort)((RIB0 & ~(1 << 07)) | (value ? 1 << 07 : 0)); } - public override bool RibbonNational { get => (RIB0 & (1 << 06)) == 1 << 06; set => RIB0 = (ushort)((RIB0 & ~(1 << 06)) | (value ? 1 << 06 : 0)); } - public override bool RibbonEarth { get => (RIB0 & (1 << 05)) == 1 << 05; set => RIB0 = (ushort)((RIB0 & ~(1 << 05)) | (value ? 1 << 05 : 0)); } - public override bool RibbonWorld { get => (RIB0 & (1 << 04)) == 1 << 04; set => RIB0 = (ushort)((RIB0 & ~(1 << 04)) | (value ? 1 << 04 : 0)); } - public override bool Unused1 { get => (RIB0 & (1 << 03)) == 1 << 03; set => RIB0 = (ushort)((RIB0 & ~(1 << 03)) | (value ? 1 << 03 : 0)); } - public override bool Unused2 { get => (RIB0 & (1 << 02)) == 1 << 02; set => RIB0 = (ushort)((RIB0 & ~(1 << 02)) | (value ? 1 << 02 : 0)); } - public override bool Unused3 { get => (RIB0 & (1 << 01)) == 1 << 01; set => RIB0 = (ushort)((RIB0 & ~(1 << 01)) | (value ? 1 << 01 : 0)); } - public override bool Unused4 { get => (RIB0 & (1 << 00)) == 1 << 00; set => RIB0 = (ushort)((RIB0 & ~(1 << 00)) | (value ? 1 << 00 : 0)); } - // 0x7E-0x7F Unknown - - // Moves - public override int Move1 { get => ReadUInt16BigEndian(Data.AsSpan(0x80)); set => WriteUInt16BigEndian(Data.AsSpan(0x80), (ushort)value); } - public override int Move1_PP { get => Data[0x82]; set => Data[0x82] = (byte)value; } - public override int Move1_PPUps { get => Data[0x83]; set => Data[0x83] = (byte)value; } - public override int Move2 { get => ReadUInt16BigEndian(Data.AsSpan(0x84)); set => WriteUInt16BigEndian(Data.AsSpan(0x84), (ushort)value); } - public override int Move2_PP { get => Data[0x86]; set => Data[0x86] = (byte)value; } - public override int Move2_PPUps { get => Data[0x87]; set => Data[0x87] = (byte)value; } - public override int Move3 { get => ReadUInt16BigEndian(Data.AsSpan(0x88)); set => WriteUInt16BigEndian(Data.AsSpan(0x88), (ushort)value); } - public override int Move3_PP { get => Data[0x8A]; set => Data[0x8A] = (byte)value; } - public override int Move3_PPUps { get => Data[0x8B]; set => Data[0x8B] = (byte)value; } - public override int Move4 { get => ReadUInt16BigEndian(Data.AsSpan(0x8C)); set => WriteUInt16BigEndian(Data.AsSpan(0x8C), (ushort)value); } - public override int Move4_PP { get => Data[0x8E]; set => Data[0x8E] = (byte)value; } - public override int Move4_PPUps { get => Data[0x8F]; set => Data[0x8F] = (byte)value; } - - // More party stats - public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x90)); set => WriteUInt16BigEndian(Data.AsSpan(0x90), (ushort)value); } - public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x92)); set => WriteUInt16BigEndian(Data.AsSpan(0x92), (ushort)value); } - public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x94)); set => WriteUInt16BigEndian(Data.AsSpan(0x94), (ushort)value); } - public override int Stat_SPA { get => ReadUInt16BigEndian(Data.AsSpan(0x96)); set => WriteUInt16BigEndian(Data.AsSpan(0x96), (ushort)value); } - public override int Stat_SPD { get => ReadUInt16BigEndian(Data.AsSpan(0x98)); set => WriteUInt16BigEndian(Data.AsSpan(0x98), (ushort)value); } - public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x9A)); set => WriteUInt16BigEndian(Data.AsSpan(0x9A), (ushort)value); } - - // EVs - public override int EV_HP - { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9C))); - set => WriteUInt16BigEndian(Data.AsSpan(0x9C), (ushort)(value & 0xFF)); - } - - public override int EV_ATK - { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9E))); - set => WriteUInt16BigEndian(Data.AsSpan(0x9E), (ushort)(value & 0xFF)); - } - - public override int EV_DEF - { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA0))); - set => WriteUInt16BigEndian(Data.AsSpan(0xA0), (ushort)(value & 0xFF)); - } - - public override int EV_SPA - { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA2))); - set => WriteUInt16BigEndian(Data.AsSpan(0xA2), (ushort)(value & 0xFF)); - } - - public override int EV_SPD - { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA4))); - set => WriteUInt16BigEndian(Data.AsSpan(0xA4), (ushort)(value & 0xFF)); - } - - public override int EV_SPE - { - get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA6))); - set => WriteUInt16BigEndian(Data.AsSpan(0xA6), (ushort)(value & 0xFF)); - } - - // IVs - public override int IV_HP { get => Data[0xA8]; set => Data[0xA8] = (byte)(value & 0x1F); } - public override int IV_ATK { get => Data[0xA9]; set => Data[0xA9] = (byte)(value & 0x1F); } - public override int IV_DEF { get => Data[0xAA]; set => Data[0xAA] = (byte)(value & 0x1F); } - public override int IV_SPA { get => Data[0xAB]; set => Data[0xAB] = (byte)(value & 0x1F); } - public override int IV_SPD { get => Data[0xAC]; set => Data[0xAC] = (byte)(value & 0x1F); } - public override int IV_SPE { get => Data[0xAD]; set => Data[0xAD] = (byte)(value & 0x1F); } - - // Contest - public override byte CNT_Cool { get => Data[0xAE]; set => Data[0xAE] = value; } - public override byte CNT_Beauty { get => Data[0xAF]; set => Data[0xAF] = value; } - public override byte CNT_Cute { get => Data[0xB0]; set => Data[0xB0] = value; } - public override byte CNT_Smart { get => Data[0xB1]; set => Data[0xB1] = value; } - public override byte CNT_Tough { get => Data[0xB2]; set => Data[0xB2] = value; } - public override int RibbonCountG3Cool { get => Data[0xB3]; set => Data[0xB3] = (byte)value; } - public override int RibbonCountG3Beauty { get => Data[0xB4]; set => Data[0xB4] = (byte)value; } - public override int RibbonCountG3Cute { get => Data[0xB5]; set => Data[0xB5] = (byte)value; } - public override int RibbonCountG3Smart { get => Data[0xB6]; set => Data[0xB6] = (byte)value; } - public override int RibbonCountG3Tough { get => Data[0xB7]; set => Data[0xB7] = (byte)value; } - - public ushort ShadowID { get => ReadUInt16BigEndian(Data.AsSpan(0xBA)); set => WriteUInt16BigEndian(Data.AsSpan(0xBA), value); } - - // Purification information is stored in the save file and accessed based on the Shadow ID. - public int Purification { get; set; } - - // stored in the data, offset undocumented - public override int Status_Condition { get; set; } - - public bool IsShadow => Purification != 0; - - protected override byte[] Encrypt() - { - return (byte[])Data.Clone(); - } - - public PK3 ConvertToPK3() - { - var pk = ConvertTo(); - if (Version == 15) - { - // Transferring XK3 to PK3 when it originates from XD sets the fateful encounter (obedience) flag. - if (ShadowID != 0) - pk.RibbonNational = true; // must be purified before trading away; force purify - if (IsOriginXD()) - pk.FatefulEncounter = true; - } - pk.RefreshChecksum(); - return pk; - } - - private bool IsOriginXD() - { - if (ShadowID != 0) - return true; - return IsOriginXD((ushort)Species, Met_Level); - } - - private static bool IsOriginXD(ushort species, int metLevel) => species switch - { - 296 or 297 => metLevel != 30, // Makuhita 30 Colo 18 XD - 175 or 176 => metLevel != 20, // Togepi 20 Colo 25 XD, also 20 as Togetic in Colo - 179 or 180 or 181 => metLevel is not (37 or 30), // Mareep: 37 Colo 17 XD, Flaafy: 30 Colo - 219 => metLevel != 30, // Magcargo 30 Colo 38 XD (Slugma in Colo) - 195 => metLevel != 30, // Quagsire 30 Colo // ** Wooper XD - 334 => metLevel != 33, // Altaria 33 Colo // 36 XD (Swablu in Colo) - 167 => metLevel != 40, // Ledian 40 Colo // 10 Ledyba XD - 207 => metLevel != 43, // Gligar 43 Colo // ** Gligar XD - 221 => metLevel != 43, // Piloswine 43 Colo // 22 Swinub XD - 205 => metLevel != 43, // Forretress 43 Colo // 20 Pineco XD - 168 => metLevel != 43, // Ariados 43 Colo // 14 Spinarak XD - 229 => metLevel != 48, // Houndoom 48 Colo // 17 Houndour XD - 217 => metLevel != 45, // Ursaring 45 Colo // 11 Teddiursa XD - 212 => metLevel != 50, // Scizor 50 Colo // 40 Scyther XD - 196 => metLevel != 25, // Espeon - 197 => metLevel != 26, // Umbreon - - // Shuckle, Elekid, Larvitar, Meditite - 213 or 239 or 240 or 246 or 247 or 248 or 307 or 308 => metLevel == 20, - - // all other cases handled, if not in Colo's table it's from XD. - _ => !Legal.ValidSpecies_Colo.Contains(species), - }; } + + public override int Version { get => GetGBAVersionID(Data[0x34]); set => Data[0x34] = GetGCVersionID(value); } + public int CurrentRegion { get => Data[0x35]; set => Data[0x35] = (byte)value; } + public int OriginalRegion { get => Data[0x36]; set => Data[0x36] = (byte)value; } + public override int Language { get => Core.Language.GetMainLangIDfromGC(Data[0x37]); set => Data[0x37] = Core.Language.GetGCLangIDfromMain((byte)value); } + public override string OT_Name { get => StringConverter3GC.GetString(OT_Trash); set => StringConverter3GC.SetString(OT_Trash, value.AsSpan(), 10, StringConverterOption.None); } + public override string Nickname { get => StringConverter3GC.GetString(Nickname_Trash); set { StringConverter3GC.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); NicknameCopy = value; } } + public string NicknameCopy { get => StringConverter3GC.GetString(NicknameCopy_Trash); set => StringConverter3GC.SetString(NicknameCopy_Trash, value.AsSpan(), 10, StringConverterOption.None); } + // 0x7A-0x7B Unknown + private ushort RIB0 { get => ReadUInt16BigEndian(Data.AsSpan(0x7C)); set => WriteUInt16BigEndian(Data.AsSpan(0x7C), value); } + public override bool RibbonChampionG3 { get => (RIB0 & (1 << 15)) == 1 << 15; set => RIB0 = (ushort)((RIB0 & ~(1 << 15)) | (value ? 1 << 15 : 0)); } + public override bool RibbonWinning { get => (RIB0 & (1 << 14)) == 1 << 14; set => RIB0 = (ushort)((RIB0 & ~(1 << 14)) | (value ? 1 << 14 : 0)); } + public override bool RibbonVictory { get => (RIB0 & (1 << 13)) == 1 << 13; set => RIB0 = (ushort)((RIB0 & ~(1 << 13)) | (value ? 1 << 13 : 0)); } + public override bool RibbonArtist { get => (RIB0 & (1 << 12)) == 1 << 12; set => RIB0 = (ushort)((RIB0 & ~(1 << 12)) | (value ? 1 << 12 : 0)); } + public override bool RibbonEffort { get => (RIB0 & (1 << 11)) == 1 << 11; set => RIB0 = (ushort)((RIB0 & ~(1 << 11)) | (value ? 1 << 11 : 0)); } + public override bool RibbonChampionBattle { get => (RIB0 & (1 << 10)) == 1 << 10; set => RIB0 = (ushort)((RIB0 & ~(1 << 10)) | (value ? 1 << 10 : 0)); } + public override bool RibbonChampionRegional { get => (RIB0 & (1 << 09)) == 1 << 09; set => RIB0 = (ushort)((RIB0 & ~(1 << 09)) | (value ? 1 << 09 : 0)); } + public override bool RibbonChampionNational { get => (RIB0 & (1 << 08)) == 1 << 08; set => RIB0 = (ushort)((RIB0 & ~(1 << 08)) | (value ? 1 << 08 : 0)); } + public override bool RibbonCountry { get => (RIB0 & (1 << 07)) == 1 << 07; set => RIB0 = (ushort)((RIB0 & ~(1 << 07)) | (value ? 1 << 07 : 0)); } + public override bool RibbonNational { get => (RIB0 & (1 << 06)) == 1 << 06; set => RIB0 = (ushort)((RIB0 & ~(1 << 06)) | (value ? 1 << 06 : 0)); } + public override bool RibbonEarth { get => (RIB0 & (1 << 05)) == 1 << 05; set => RIB0 = (ushort)((RIB0 & ~(1 << 05)) | (value ? 1 << 05 : 0)); } + public override bool RibbonWorld { get => (RIB0 & (1 << 04)) == 1 << 04; set => RIB0 = (ushort)((RIB0 & ~(1 << 04)) | (value ? 1 << 04 : 0)); } + public override bool Unused1 { get => (RIB0 & (1 << 03)) == 1 << 03; set => RIB0 = (ushort)((RIB0 & ~(1 << 03)) | (value ? 1 << 03 : 0)); } + public override bool Unused2 { get => (RIB0 & (1 << 02)) == 1 << 02; set => RIB0 = (ushort)((RIB0 & ~(1 << 02)) | (value ? 1 << 02 : 0)); } + public override bool Unused3 { get => (RIB0 & (1 << 01)) == 1 << 01; set => RIB0 = (ushort)((RIB0 & ~(1 << 01)) | (value ? 1 << 01 : 0)); } + public override bool Unused4 { get => (RIB0 & (1 << 00)) == 1 << 00; set => RIB0 = (ushort)((RIB0 & ~(1 << 00)) | (value ? 1 << 00 : 0)); } + // 0x7E-0x7F Unknown + + // Moves + public override int Move1 { get => ReadUInt16BigEndian(Data.AsSpan(0x80)); set => WriteUInt16BigEndian(Data.AsSpan(0x80), (ushort)value); } + public override int Move1_PP { get => Data[0x82]; set => Data[0x82] = (byte)value; } + public override int Move1_PPUps { get => Data[0x83]; set => Data[0x83] = (byte)value; } + public override int Move2 { get => ReadUInt16BigEndian(Data.AsSpan(0x84)); set => WriteUInt16BigEndian(Data.AsSpan(0x84), (ushort)value); } + public override int Move2_PP { get => Data[0x86]; set => Data[0x86] = (byte)value; } + public override int Move2_PPUps { get => Data[0x87]; set => Data[0x87] = (byte)value; } + public override int Move3 { get => ReadUInt16BigEndian(Data.AsSpan(0x88)); set => WriteUInt16BigEndian(Data.AsSpan(0x88), (ushort)value); } + public override int Move3_PP { get => Data[0x8A]; set => Data[0x8A] = (byte)value; } + public override int Move3_PPUps { get => Data[0x8B]; set => Data[0x8B] = (byte)value; } + public override int Move4 { get => ReadUInt16BigEndian(Data.AsSpan(0x8C)); set => WriteUInt16BigEndian(Data.AsSpan(0x8C), (ushort)value); } + public override int Move4_PP { get => Data[0x8E]; set => Data[0x8E] = (byte)value; } + public override int Move4_PPUps { get => Data[0x8F]; set => Data[0x8F] = (byte)value; } + + // More party stats + public override int Stat_HPMax { get => ReadUInt16BigEndian(Data.AsSpan(0x90)); set => WriteUInt16BigEndian(Data.AsSpan(0x90), (ushort)value); } + public override int Stat_ATK { get => ReadUInt16BigEndian(Data.AsSpan(0x92)); set => WriteUInt16BigEndian(Data.AsSpan(0x92), (ushort)value); } + public override int Stat_DEF { get => ReadUInt16BigEndian(Data.AsSpan(0x94)); set => WriteUInt16BigEndian(Data.AsSpan(0x94), (ushort)value); } + public override int Stat_SPA { get => ReadUInt16BigEndian(Data.AsSpan(0x96)); set => WriteUInt16BigEndian(Data.AsSpan(0x96), (ushort)value); } + public override int Stat_SPD { get => ReadUInt16BigEndian(Data.AsSpan(0x98)); set => WriteUInt16BigEndian(Data.AsSpan(0x98), (ushort)value); } + public override int Stat_SPE { get => ReadUInt16BigEndian(Data.AsSpan(0x9A)); set => WriteUInt16BigEndian(Data.AsSpan(0x9A), (ushort)value); } + + // EVs + public override int EV_HP + { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9C))); + set => WriteUInt16BigEndian(Data.AsSpan(0x9C), (ushort)(value & 0xFF)); + } + + public override int EV_ATK + { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0x9E))); + set => WriteUInt16BigEndian(Data.AsSpan(0x9E), (ushort)(value & 0xFF)); + } + + public override int EV_DEF + { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA0))); + set => WriteUInt16BigEndian(Data.AsSpan(0xA0), (ushort)(value & 0xFF)); + } + + public override int EV_SPA + { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA2))); + set => WriteUInt16BigEndian(Data.AsSpan(0xA2), (ushort)(value & 0xFF)); + } + + public override int EV_SPD + { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA4))); + set => WriteUInt16BigEndian(Data.AsSpan(0xA4), (ushort)(value & 0xFF)); + } + + public override int EV_SPE + { + get => Math.Min(byte.MaxValue, ReadUInt16BigEndian(Data.AsSpan(0xA6))); + set => WriteUInt16BigEndian(Data.AsSpan(0xA6), (ushort)(value & 0xFF)); + } + + // IVs + public override int IV_HP { get => Data[0xA8]; set => Data[0xA8] = (byte)(value & 0x1F); } + public override int IV_ATK { get => Data[0xA9]; set => Data[0xA9] = (byte)(value & 0x1F); } + public override int IV_DEF { get => Data[0xAA]; set => Data[0xAA] = (byte)(value & 0x1F); } + public override int IV_SPA { get => Data[0xAB]; set => Data[0xAB] = (byte)(value & 0x1F); } + public override int IV_SPD { get => Data[0xAC]; set => Data[0xAC] = (byte)(value & 0x1F); } + public override int IV_SPE { get => Data[0xAD]; set => Data[0xAD] = (byte)(value & 0x1F); } + + // Contest + public override byte CNT_Cool { get => Data[0xAE]; set => Data[0xAE] = value; } + public override byte CNT_Beauty { get => Data[0xAF]; set => Data[0xAF] = value; } + public override byte CNT_Cute { get => Data[0xB0]; set => Data[0xB0] = value; } + public override byte CNT_Smart { get => Data[0xB1]; set => Data[0xB1] = value; } + public override byte CNT_Tough { get => Data[0xB2]; set => Data[0xB2] = value; } + public override int RibbonCountG3Cool { get => Data[0xB3]; set => Data[0xB3] = (byte)value; } + public override int RibbonCountG3Beauty { get => Data[0xB4]; set => Data[0xB4] = (byte)value; } + public override int RibbonCountG3Cute { get => Data[0xB5]; set => Data[0xB5] = (byte)value; } + public override int RibbonCountG3Smart { get => Data[0xB6]; set => Data[0xB6] = (byte)value; } + public override int RibbonCountG3Tough { get => Data[0xB7]; set => Data[0xB7] = (byte)value; } + + public ushort ShadowID { get => ReadUInt16BigEndian(Data.AsSpan(0xBA)); set => WriteUInt16BigEndian(Data.AsSpan(0xBA), value); } + + // Purification information is stored in the save file and accessed based on the Shadow ID. + public int Purification { get; set; } + + // stored in the data, offset undocumented + public override int Status_Condition { get; set; } + + public bool IsShadow => Purification != 0; + + protected override byte[] Encrypt() + { + return (byte[])Data.Clone(); + } + + public PK3 ConvertToPK3() + { + var pk = ConvertTo(); + if (Version == 15) + { + // Transferring XK3 to PK3 when it originates from XD sets the fateful encounter (obedience) flag. + if (ShadowID != 0) + pk.RibbonNational = true; // must be purified before trading away; force purify + if (IsOriginXD()) + pk.FatefulEncounter = true; + } + pk.RefreshChecksum(); + return pk; + } + + private bool IsOriginXD() + { + if (ShadowID != 0) + return true; + return IsOriginXD((ushort)Species, Met_Level); + } + + private static bool IsOriginXD(ushort species, int metLevel) => species switch + { + 296 or 297 => metLevel != 30, // Makuhita 30 Colo 18 XD + 175 or 176 => metLevel != 20, // Togepi 20 Colo 25 XD, also 20 as Togetic in Colo + 179 or 180 or 181 => metLevel is not (37 or 30), // Mareep: 37 Colo 17 XD, Flaafy: 30 Colo + 219 => metLevel != 30, // Magcargo 30 Colo 38 XD (Slugma in Colo) + 195 => metLevel != 30, // Quagsire 30 Colo // ** Wooper XD + 334 => metLevel != 33, // Altaria 33 Colo // 36 XD (Swablu in Colo) + 167 => metLevel != 40, // Ledian 40 Colo // 10 Ledyba XD + 207 => metLevel != 43, // Gligar 43 Colo // ** Gligar XD + 221 => metLevel != 43, // Piloswine 43 Colo // 22 Swinub XD + 205 => metLevel != 43, // Forretress 43 Colo // 20 Pineco XD + 168 => metLevel != 43, // Ariados 43 Colo // 14 Spinarak XD + 229 => metLevel != 48, // Houndoom 48 Colo // 17 Houndour XD + 217 => metLevel != 45, // Ursaring 45 Colo // 11 Teddiursa XD + 212 => metLevel != 50, // Scizor 50 Colo // 40 Scyther XD + 196 => metLevel != 25, // Espeon + 197 => metLevel != 26, // Umbreon + + // Shuckle, Elekid, Larvitar, Meditite + 213 or 239 or 240 or 246 or 247 or 248 or 307 or 308 => metLevel == 20, + + // all other cases handled, if not in Colo's table it's from XD. + _ => !Legal.ValidSpecies_Colo.Contains(species), + }; } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfo.cs b/PKHeX.Core/PersonalInfo/PersonalInfo.cs index 49c16e15d..2a018a9fc 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfo.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfo.cs @@ -1,372 +1,370 @@ -using System; +using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Stat/misc data for individual species or their associated alternate form data. +/// +public abstract class PersonalInfo { /// - /// Stat/misc data for individual species or their associated alternate form data. + /// Raw Data /// - public abstract class PersonalInfo + protected readonly byte[] Data; + + protected PersonalInfo(byte[] data) => Data = data; + + /// + /// Writes entry to raw bytes. + /// + public abstract byte[] Write(); + + /// + /// Base HP + /// + public abstract int HP { get; set; } + + /// + /// Base Attack + /// + public abstract int ATK { get; set; } + + /// + /// Base Defense + /// + public abstract int DEF { get; set; } + + /// + /// Base Speed + /// + public abstract int SPE { get; set; } + + /// + /// Base Special Attack + /// + public abstract int SPA { get; set; } + + /// + /// Base Special Defense + /// + public abstract int SPD { get; set; } + + /// + /// Base Stat values + /// + public IReadOnlyList Stats => new[] { HP, ATK, DEF, SPE, SPA, SPD }; + + /// + /// Amount of HP Effort Values to yield when defeating this entry. + /// + public abstract int EV_HP { get; set; } + + /// + /// Amount of Attack Effort Values to yield when defeating this entry. + /// + public abstract int EV_ATK { get; set; } + + /// + /// Amount of Defense Effort Values to yield when defeating this entry. + /// + public abstract int EV_DEF { get; set; } + + /// + /// Amount of Speed Effort Values to yield when defeating this entry. + /// + public abstract int EV_SPE { get; set; } + + /// + /// Amount of Special Attack Effort Values to yield when defeating this entry. + /// + public abstract int EV_SPA { get; set; } + + /// + /// Amount of Special Defense Effort Values to yield when defeating this entry. + /// + public abstract int EV_SPD { get; set; } + + /// + /// Primary Type + /// + public abstract int Type1 { get; set; } + + /// + /// Secondary Type + /// + public abstract int Type2 { get; set; } + + /// + /// First Egg Group + /// + public abstract int EggGroup1 { get; set; } + + /// + /// Second Egg Group + /// + public abstract int EggGroup2 { get; set; } + + /// + /// Catch Rate + /// + public abstract int CatchRate { get; set; } + + /// + /// Evolution Stage value (or equivalent for un-evolved). + /// + public virtual int EvoStage { get; set; } + + /// + /// Held Items the entry can be randomly encountered with. + /// + public abstract IReadOnlyList Items { get; set; } + + /// + /// Gender Ratio value determining if the entry is a fixed gender or bi-gendered. + /// + public abstract int Gender { get; set; } + + /// + /// Amount of Hatching Step Cycles required to hatch if in an egg. + /// + public abstract int HatchCycles { get; set; } + + /// + /// Initial Friendship when captured or received. + /// + public abstract int BaseFriendship { get; set; } + + /// + /// Experience-Level Growth Rate type + /// + public abstract int EXPGrowth { get; set; } + + /// + /// Full list of values the entry can have. + /// + public abstract IReadOnlyList Abilities { get; set; } + + /// + /// Gets the ability index without creating an array and looking through it. + /// + /// Ability ID + /// Ability Index + public abstract int GetAbilityIndex(int abilityID); + + /// + /// Escape factor used for fleeing the Safari Zone or calling for help in SOS Battles. + /// + public abstract int EscapeRate { get; set; } + + /// + /// Count of values the entry can have. + /// + public virtual int FormCount { get; set; } = 1; + + /// + /// Pointer to the first index + /// + protected internal virtual int FormStatsIndex { get; set; } + + /// + /// Pointer to the sprite index. + /// + public virtual int FormSprite { get; set; } + + /// + /// Base Experience Yield factor + /// + public abstract int BaseEXP { get; set; } + + /// + /// Main color ID of the entry. The majority of the Pokémon's color is of this color, usually. + /// + public abstract int Color { get; set; } + + /// + /// Height of the entry in meters (m). + /// + public virtual int Height { get; set; } = 0; + + /// + /// Mass of the entry in kilograms (kg). + /// + public virtual int Weight { get; set; } = 0; + + /// + /// TM/HM learn compatibility flags for individual moves. + /// + public bool[] TMHM = Array.Empty(); + + /// + /// Grass-Fire-Water-Etc typed learn compatibility flags for individual moves. + /// + public bool[] TypeTutors = Array.Empty(); + + /// + /// Special tutor learn compatibility flags for individual moves. + /// + public bool[][] SpecialTutors = Array.Empty(); + + public virtual bool IsPresentInGame { get => HP != 0; set { } } + + protected static bool[] GetBits(ReadOnlySpan data) { - /// - /// Raw Data - /// - protected readonly byte[] Data; - - protected PersonalInfo(byte[] data) => Data = data; - - /// - /// Writes entry to raw bytes. - /// - /// - public abstract byte[] Write(); - - /// - /// Base HP - /// - public abstract int HP { get; set; } - - /// - /// Base Attack - /// - public abstract int ATK { get; set; } - - /// - /// Base Defense - /// - public abstract int DEF { get; set; } - - /// - /// Base Speed - /// - public abstract int SPE { get; set; } - - /// - /// Base Special Attack - /// - public abstract int SPA { get; set; } - - /// - /// Base Special Defense - /// - public abstract int SPD { get; set; } - - /// - /// Base Stat values - /// - public IReadOnlyList Stats => new[] { HP, ATK, DEF, SPE, SPA, SPD }; - - /// - /// Amount of HP Effort Values to yield when defeating this entry. - /// - public abstract int EV_HP { get; set; } - - /// - /// Amount of Attack Effort Values to yield when defeating this entry. - /// - public abstract int EV_ATK { get; set; } - - /// - /// Amount of Defense Effort Values to yield when defeating this entry. - /// - public abstract int EV_DEF { get; set; } - - /// - /// Amount of Speed Effort Values to yield when defeating this entry. - /// - public abstract int EV_SPE { get; set; } - - /// - /// Amount of Special Attack Effort Values to yield when defeating this entry. - /// - public abstract int EV_SPA { get; set; } - - /// - /// Amount of Special Defense Effort Values to yield when defeating this entry. - /// - public abstract int EV_SPD { get; set; } - - /// - /// Primary Type - /// - public abstract int Type1 { get; set; } - - /// - /// Secondary Type - /// - public abstract int Type2 { get; set; } - - /// - /// First Egg Group - /// - public abstract int EggGroup1 { get; set; } - - /// - /// Second Egg Group - /// - public abstract int EggGroup2 { get; set; } - - /// - /// Catch Rate - /// - public abstract int CatchRate { get; set; } - - /// - /// Evolution Stage value (or equivalent for un-evolved). - /// - public virtual int EvoStage { get; set; } - - /// - /// Held Items the entry can be randomly encountered with. - /// - public abstract IReadOnlyList Items { get; set; } - - /// - /// Gender Ratio value determining if the entry is a fixed gender or bi-gendered. - /// - public abstract int Gender { get; set; } - - /// - /// Amount of Hatching Step Cycles required to hatch if in an egg. - /// - public abstract int HatchCycles { get; set; } - - /// - /// Initial Friendship when captured or received. - /// - public abstract int BaseFriendship { get; set; } - - /// - /// Experience-Level Growth Rate type - /// - public abstract int EXPGrowth { get; set; } - - /// - /// Full list of values the entry can have. - /// - public abstract IReadOnlyList Abilities { get; set; } - - /// - /// Gets the ability index without creating an array and looking through it. - /// - /// Ability ID - /// Ability Index - public abstract int GetAbilityIndex(int abilityID); - - /// - /// Escape factor used for fleeing the Safari Zone or calling for help in SOS Battles. - /// - public abstract int EscapeRate { get; set; } - - /// - /// Count of values the entry can have. - /// - public virtual int FormCount { get; set; } = 1; - - /// - /// Pointer to the first index - /// - protected internal virtual int FormStatsIndex { get; set; } - - /// - /// Pointer to the sprite index. - /// - public virtual int FormSprite { get; set; } - - /// - /// Base Experience Yield factor - /// - public abstract int BaseEXP { get; set; } - - /// - /// Main color ID of the entry. The majority of the Pokémon's color is of this color, usually. - /// - public abstract int Color { get; set; } - - /// - /// Height of the entry in meters (m). - /// - public virtual int Height { get; set; } = 0; - - /// - /// Mass of the entry in kilograms (kg). - /// - public virtual int Weight { get; set; } = 0; - - /// - /// TM/HM learn compatibility flags for individual moves. - /// - public bool[] TMHM = Array.Empty(); - - /// - /// Grass-Fire-Water-Etc typed learn compatibility flags for individual moves. - /// - public bool[] TypeTutors = Array.Empty(); - - /// - /// Special tutor learn compatibility flags for individual moves. - /// - public bool[][] SpecialTutors = Array.Empty(); - - public virtual bool IsPresentInGame { get => HP != 0; set { } } - - protected static bool[] GetBits(ReadOnlySpan data) - { - bool[] result = new bool[data.Length << 3]; - for (int i = result.Length - 1; i >= 0; i--) - result[i] = (data[i >> 3] >> (i & 7) & 0x1) == 1; - return result; - } - - protected static void SetBits(bool[] bits, Span data) - { - for (int i = bits.Length - 1; i >= 0; i--) - data[i>>3] |= (byte)(bits[i] ? 1 << (i&0x7) : 0); - } - - /// - /// Injects supplementary TM/HM compatibility which is not present in the generation specific format. - /// - /// Data to read from - internal void AddTMHM(ReadOnlySpan data) => TMHM = GetBits(data); - - /// - /// Injects supplementary Type Tutor compatibility which is not present in the generation specific format. - /// - /// Data to read from - internal void AddTypeTutors(ReadOnlySpan data) => TypeTutors = GetBits(data); - - /// - /// Gets the entry index for the input criteria, with fallback for the original species entry. - /// - /// to retrieve for - /// to retrieve for - /// Index the exists as in the . - public int FormIndex(int species, int form) - { - if (!HasForm(form)) - return species; - return FormStatsIndex + form - 1; - } - - /// - /// Checks if the has the requested entry index available. - /// - /// to retrieve for - public bool HasForm(int form) - { - if (form <= 0) // no form requested - return false; - if (FormStatsIndex <= 0) // no forms present - return false; - if (form >= FormCount) // beyond range of species' forms - return false; - return true; - } - - /// - /// Gets a random valid gender for the entry. - /// - public int RandomGender() - { - var fix = FixedGender; - return fix >= 0 ? fix : Util.Rand.Next(2); - } - - /// - /// Gets a gender value. Returns -1 if the entry . - /// - public int FixedGender - { - get - { - if (Genderless) - return 2; - if (OnlyFemale) - return 1; - if (OnlyMale) - return 0; - return -1; - } - } - - public const int RatioMagicGenderless = 255; - public const int RatioMagicFemale = 254; - public const int RatioMagicMale = 0; - - public static bool IsSingleGender(int gt) => (uint)(gt - 1) >= 253; - - /// - /// Indicates that the entry has two genders. - /// - public bool IsDualGender => (uint)(Gender - 1) < 253; - - /// - /// Indicates that the entry is exclusively Genderless. - /// - public bool Genderless => Gender == RatioMagicGenderless; - - /// - /// Indicates that the entry is exclusively Female gendered. - /// - public bool OnlyFemale => Gender == RatioMagicFemale; - - /// - /// Indicates that the entry is exclusively Male gendered. - /// - public bool OnlyMale => Gender == RatioMagicMale; - - /// - /// Indicates if the entry has forms or not. - /// - public bool HasForms => FormCount > 1; - - /// - /// Base Stat Total sum of all stats. - /// - public int BST => HP + ATK + DEF + SPE + SPA + SPD; - - /// - /// Checks to see if the is valid within the - /// - /// - public bool IsFormWithinRange(int form) - { - if (form == 0) - return true; - return form < FormCount; - } - - /// - /// Checks to see if the provided Types match the entry's types. - /// - /// Input order matters! If input order does not matter, use instead. - /// First type - /// Second type - /// Typing is an exact match - public bool IsValidTypeCombination(int type1, int type2) => Type1 == type1 && Type2 == type2; - - /// - /// Checks if the entry has either type equal to the input type. - /// - /// Type - /// Typing is present in entry - public bool IsType(int type1) => Type1 == type1 || Type2 == type1; - - /// - /// Checks if the entry has either type equal to both input types. - /// - /// Input order does not matter. - /// Type 1 - /// Type 2 - /// Typing is present in entry - public bool IsType(int type1, int type2) => (Type1 == type1 || Type2 == type1) && (Type1 == type2 || Type2 == type2); - - /// - /// Checks if the entry has either egg group equal to the input type. - /// - /// Egg group - /// Egg is present in entry - public bool IsEggGroup(int group) => EggGroup1 == group || EggGroup2 == group; + bool[] result = new bool[data.Length << 3]; + for (int i = result.Length - 1; i >= 0; i--) + result[i] = ((data[i >> 3] >> (i & 7)) & 0x1) == 1; + return result; } + + protected static void SetBits(bool[] bits, Span data) + { + for (int i = bits.Length - 1; i >= 0; i--) + data[i>>3] |= (byte)(bits[i] ? 1 << (i&0x7) : 0); + } + + /// + /// Injects supplementary TM/HM compatibility which is not present in the generation specific format. + /// + /// Data to read from + internal void AddTMHM(ReadOnlySpan data) => TMHM = GetBits(data); + + /// + /// Injects supplementary Type Tutor compatibility which is not present in the generation specific format. + /// + /// Data to read from + internal void AddTypeTutors(ReadOnlySpan data) => TypeTutors = GetBits(data); + + /// + /// Gets the entry index for the input criteria, with fallback for the original species entry. + /// + /// to retrieve for + /// to retrieve for + /// Index the exists as in the . + public int FormIndex(int species, int form) + { + if (!HasForm(form)) + return species; + return FormStatsIndex + form - 1; + } + + /// + /// Checks if the has the requested entry index available. + /// + /// to retrieve for + public bool HasForm(int form) + { + if (form <= 0) // no form requested + return false; + if (FormStatsIndex <= 0) // no forms present + return false; + if (form >= FormCount) // beyond range of species' forms + return false; + return true; + } + + /// + /// Gets a random valid gender for the entry. + /// + public int RandomGender() + { + var fix = FixedGender; + return fix >= 0 ? fix : Util.Rand.Next(2); + } + + /// + /// Gets a gender value. Returns -1 if the entry . + /// + public int FixedGender + { + get + { + if (Genderless) + return 2; + if (OnlyFemale) + return 1; + if (OnlyMale) + return 0; + return -1; + } + } + + public const int RatioMagicGenderless = 255; + public const int RatioMagicFemale = 254; + public const int RatioMagicMale = 0; + + public static bool IsSingleGender(int gt) => (uint)(gt - 1) >= 253; + + /// + /// Indicates that the entry has two genders. + /// + public bool IsDualGender => (uint)(Gender - 1) < 253; + + /// + /// Indicates that the entry is exclusively Genderless. + /// + public bool Genderless => Gender == RatioMagicGenderless; + + /// + /// Indicates that the entry is exclusively Female gendered. + /// + public bool OnlyFemale => Gender == RatioMagicFemale; + + /// + /// Indicates that the entry is exclusively Male gendered. + /// + public bool OnlyMale => Gender == RatioMagicMale; + + /// + /// Indicates if the entry has forms or not. + /// + public bool HasForms => FormCount > 1; + + /// + /// Base Stat Total sum of all stats. + /// + public int BST => HP + ATK + DEF + SPE + SPA + SPD; + + /// + /// Checks to see if the is valid within the + /// + /// + public bool IsFormWithinRange(int form) + { + if (form == 0) + return true; + return form < FormCount; + } + + /// + /// Checks to see if the provided Types match the entry's types. + /// + /// Input order matters! If input order does not matter, use instead. + /// First type + /// Second type + /// Typing is an exact match + public bool IsValidTypeCombination(int type1, int type2) => Type1 == type1 && Type2 == type2; + + /// + /// Checks if the entry has either type equal to the input type. + /// + /// Type + /// Typing is present in entry + public bool IsType(int type1) => Type1 == type1 || Type2 == type1; + + /// + /// Checks if the entry has either type equal to both input types. + /// + /// Input order does not matter. + /// Type 1 + /// Type 2 + /// Typing is present in entry + public bool IsType(int type1, int type2) => (Type1 == type1 || Type2 == type1) && (Type1 == type2 || Type2 == type2); + + /// + /// Checks if the entry has either egg group equal to the input type. + /// + /// Egg group + /// Egg is present in entry + public bool IsEggGroup(int group) => EggGroup1 == group || EggGroup2 == group; } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoB2W2.cs b/PKHeX.Core/PersonalInfo/PersonalInfoB2W2.cs index bf9481218..9c6df2bde 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoB2W2.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoB2W2.cs @@ -1,37 +1,36 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from the Black 2 & White 2 games. +/// +public sealed class PersonalInfoB2W2 : PersonalInfoBW { - /// - /// class with values from the Black 2 & White 2 games. - /// - public sealed class PersonalInfoB2W2 : PersonalInfoBW + public new const int SIZE = 0x4C; + + public PersonalInfoB2W2(byte[] data) : base(data) { - public new const int SIZE = 0x4C; - - public PersonalInfoB2W2(byte[] data) : base(data) + // Unpack TMHM & Tutors + TMHM = GetBits(Data.AsSpan(0x28, 0x10)); + TypeTutors = GetBits(Data.AsSpan(0x38, 0x4)); + SpecialTutors = new[] { - // Unpack TMHM & Tutors - TMHM = GetBits(Data.AsSpan(0x28, 0x10)); - TypeTutors = GetBits(Data.AsSpan(0x38, 0x4)); - SpecialTutors = new[] - { - GetBits(Data.AsSpan(0x3C, 0x04)), - GetBits(Data.AsSpan(0x40, 0x04)), - GetBits(Data.AsSpan(0x44, 0x04)), - GetBits(Data.AsSpan(0x48, 0x04)), - }; - } + GetBits(Data.AsSpan(0x3C, 0x04)), + GetBits(Data.AsSpan(0x40, 0x04)), + GetBits(Data.AsSpan(0x44, 0x04)), + GetBits(Data.AsSpan(0x48, 0x04)), + }; + } - public override byte[] Write() - { - SetBits(TMHM, Data.AsSpan(0x28)); - SetBits(TypeTutors, Data.AsSpan(0x38)); - SetBits(SpecialTutors[0], Data.AsSpan(0x3C)); - SetBits(SpecialTutors[1], Data.AsSpan(0x40)); - SetBits(SpecialTutors[2], Data.AsSpan(0x44)); - SetBits(SpecialTutors[3], Data.AsSpan(0x48)); - return Data; - } + public override byte[] Write() + { + SetBits(TMHM, Data.AsSpan(0x28)); + SetBits(TypeTutors, Data.AsSpan(0x38)); + SetBits(SpecialTutors[0], Data.AsSpan(0x3C)); + SetBits(SpecialTutors[1], Data.AsSpan(0x40)); + SetBits(SpecialTutors[2], Data.AsSpan(0x44)); + SetBits(SpecialTutors[3], Data.AsSpan(0x48)); + return Data; } } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoBDSP.cs b/PKHeX.Core/PersonalInfo/PersonalInfoBDSP.cs index 32d7433f7..7b0ad1e20 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoBDSP.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoBDSP.cs @@ -2,118 +2,116 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from the games. +/// +public sealed class PersonalInfoBDSP : PersonalInfo { - /// - /// class with values from the games. - /// - public sealed class PersonalInfoBDSP : PersonalInfo + public const int SIZE = 0x44; + internal const int CountTM = 100; + + public override int HP { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public override int ATK { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public override int DEF { get => Data[0x02]; set => Data[0x02] = (byte)value; } + public override int SPE { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public override int SPA { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public override int SPD { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } + public override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } + public override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } + public override int EvoStage { get => Data[0x09]; set => Data[0x09] = (byte)value; } + private int EVYield { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } + public override int EV_HP { get => (EVYield >> 0) & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | ((value & 0x3) << 0); } + public override int EV_ATK { get => (EVYield >> 2) & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | ((value & 0x3) << 2); } + public override int EV_DEF { get => (EVYield >> 4) & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | ((value & 0x3) << 4); } + public override int EV_SPE { get => (EVYield >> 6) & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | ((value & 0x3) << 6); } + public override int EV_SPA { get => (EVYield >> 8) & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | ((value & 0x3) << 8); } + public override int EV_SPD { get => (EVYield >> 10) & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | ((value & 0x3) << 10); } + public int Item1 { get => ReadInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteInt16LittleEndian(Data.AsSpan(0x0C), (short)value); } + public int Item2 { get => ReadInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteInt16LittleEndian(Data.AsSpan(0x0E), (short)value); } + public int Item3 { get => ReadInt16LittleEndian(Data.AsSpan(0x10)); set => WriteInt16LittleEndian(Data.AsSpan(0x10), (short)value); } + public override int Gender { get => Data[0x12]; set => Data[0x12] = (byte)value; } + public override int HatchCycles { get => Data[0x13]; set => Data[0x13] = (byte)value; } + public override int BaseFriendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public override int EXPGrowth { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public override int EggGroup1 { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public override int EggGroup2 { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public int Ability1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); } + public int Ability2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), (ushort)value); } + public int AbilityH { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), (ushort)value); } + public override int EscapeRate { get => 0; set { } } // moved? + protected internal override int FormStatsIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } + public override int FormSprite { get => 0; set { } } // No longer defined in personal + public override int FormCount { get => Data[0x20]; set => Data[0x20] = (byte)value; } + public override int Color { get => Data[0x21] & 0x3F; set => Data[0x21] = (byte)((Data[0x21] & 0xC0) | (value & 0x3F)); } + public override bool IsPresentInGame { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); } + public override int BaseEXP { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); } + public override int Height { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); } + public override int Weight { get => ReadUInt16LittleEndian(Data.AsSpan(0x26)); set => WriteUInt16LittleEndian(Data.AsSpan(0x26), (ushort)value); } + + //public uint TM1 { get => ReadUInt32LittleEndian(Data.AsSpan(0x28)); set => WriteUInt16LittleEndian(Data.AsSpan(0x28)); } + //public uint TM2 { get => ReadUInt32LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2C)); } + //public uint TM3 { get => ReadUInt32LittleEndian(Data.AsSpan(0x30)); set => WriteUInt16LittleEndian(Data.AsSpan(0x30)); } + //public uint TM4 { get => ReadUInt32LittleEndian(Data.AsSpan(0x34)); set => WriteUInt16LittleEndian(Data.AsSpan(0x34)); } + //public uint Tutor { get => ReadUInt32LittleEndian(Data.AsSpan(0x38)); set => WriteUInt16LittleEndian(Data.AsSpan(0x38)); } + + public int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3C), (ushort)value); } + public int HatchSpecies { get => ReadUInt16LittleEndian(Data.AsSpan(0x3E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3E), (ushort)value); } + public int HatchFormIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x40)); set => WriteUInt16LittleEndian(Data.AsSpan(0x40), (ushort)value); } + public int PokeDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x42)); set => WriteUInt16LittleEndian(Data.AsSpan(0x42), (ushort)value); } + + public PersonalInfoBDSP(byte[] data) : base(data) { - public const int SIZE = 0x44; - internal const int CountTM = 100; + TMHM = new bool[CountTM]; + for (var i = 0; i < CountTM; i++) + TMHM[i] = FlagUtil.GetFlag(Data, 0x28 + (i >> 3), i); - public override int HP { get => Data[0x00]; set => Data[0x00] = (byte)value; } - public override int ATK { get => Data[0x01]; set => Data[0x01] = (byte)value; } - public override int DEF { get => Data[0x02]; set => Data[0x02] = (byte)value; } - public override int SPE { get => Data[0x03]; set => Data[0x03] = (byte)value; } - public override int SPA { get => Data[0x04]; set => Data[0x04] = (byte)value; } - public override int SPD { get => Data[0x05]; set => Data[0x05] = (byte)value; } - public override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } - public override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } - public override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } - public override int EvoStage { get => Data[0x09]; set => Data[0x09] = (byte)value; } - private int EVYield { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } - public override int EV_HP { get => EVYield >> 0 & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | (value & 0x3) << 0; } - public override int EV_ATK { get => EVYield >> 2 & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | (value & 0x3) << 2; } - public override int EV_DEF { get => EVYield >> 4 & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | (value & 0x3) << 4; } - public override int EV_SPE { get => EVYield >> 6 & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | (value & 0x3) << 6; } - public override int EV_SPA { get => EVYield >> 8 & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | (value & 0x3) << 8; } - public override int EV_SPD { get => EVYield >> 10 & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | (value & 0x3) << 10; } - public int Item1 { get => ReadInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteInt16LittleEndian(Data.AsSpan(0x0C), (short)value); } - public int Item2 { get => ReadInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteInt16LittleEndian(Data.AsSpan(0x0E), (short)value); } - public int Item3 { get => ReadInt16LittleEndian(Data.AsSpan(0x10)); set => WriteInt16LittleEndian(Data.AsSpan(0x10), (short)value); } - public override int Gender { get => Data[0x12]; set => Data[0x12] = (byte)value; } - public override int HatchCycles { get => Data[0x13]; set => Data[0x13] = (byte)value; } - public override int BaseFriendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } - public override int EXPGrowth { get => Data[0x15]; set => Data[0x15] = (byte)value; } - public override int EggGroup1 { get => Data[0x16]; set => Data[0x16] = (byte)value; } - public override int EggGroup2 { get => Data[0x17]; set => Data[0x17] = (byte)value; } - public int Ability1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); } - public int Ability2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), (ushort)value); } - public int AbilityH { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), (ushort)value); } - public override int EscapeRate { get => 0; set { } } // moved? - protected internal override int FormStatsIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } - public override int FormSprite { get => 0; set { } } // No longer defined in personal - public override int FormCount { get => Data[0x20]; set => Data[0x20] = (byte)value; } - public override int Color { get => Data[0x21] & 0x3F; set => Data[0x21] = (byte)((Data[0x21] & 0xC0) | (value & 0x3F)); } - public override bool IsPresentInGame { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); } - public bool SpriteForm { get => false; set { } } // Unspecified in table - public override int BaseEXP { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); } - public override int Height { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); } - public override int Weight { get => ReadUInt16LittleEndian(Data.AsSpan(0x26)); set => WriteUInt16LittleEndian(Data.AsSpan(0x26), (ushort)value); } - - //public uint TM1 { get => ReadUInt32LittleEndian(Data.AsSpan(0x28)); set => WriteUInt16LittleEndian(Data.AsSpan(0x28)); } - //public uint TM2 { get => ReadUInt32LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2C)); } - //public uint TM3 { get => ReadUInt32LittleEndian(Data.AsSpan(0x30)); set => WriteUInt16LittleEndian(Data.AsSpan(0x30)); } - //public uint TM4 { get => ReadUInt32LittleEndian(Data.AsSpan(0x34)); set => WriteUInt16LittleEndian(Data.AsSpan(0x34)); } - //public uint Tutor { get => ReadUInt32LittleEndian(Data.AsSpan(0x38)); set => WriteUInt16LittleEndian(Data.AsSpan(0x38)); } - - public int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x3C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3C), (ushort)value); } - public int HatchSpecies { get => ReadUInt16LittleEndian(Data.AsSpan(0x3E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x3E), (ushort)value); } - public int HatchFormIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x40)); set => WriteUInt16LittleEndian(Data.AsSpan(0x40), (ushort)value); } - public int PokeDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x42)); set => WriteUInt16LittleEndian(Data.AsSpan(0x42), (ushort)value); } - - public PersonalInfoBDSP(byte[] data) : base(data) - { - TMHM = new bool[CountTM]; - for (var i = 0; i < CountTM; i++) - TMHM[i] = FlagUtil.GetFlag(Data, 0x28 + (i >> 3), i); - - // 0x38-0x3B type tutors, but only 8 bits are valid flags. - var typeTutors = new bool[8]; - for (int i = 0; i < typeTutors.Length; i++) - typeTutors[i] = FlagUtil.GetFlag(Data, 0x38, i); - TypeTutors = typeTutors; - } - - public override byte[] Write() - { - for (var i = 0; i < CountTM; i++) - FlagUtil.SetFlag(Data, 0x28 + (i >> 3), i, TMHM[i]); - for (int i = 0; i < TypeTutors.Length; i++) - FlagUtil.SetFlag(Data, 0x38, i, TypeTutors[i]); - return Data; - } - - public override IReadOnlyList Items - { - get => new[] { Item1, Item2, Item3 }; - set - { - if (value.Count != 3) return; - Item1 = value[0]; - Item2 = value[1]; - Item3 = value[2]; - } - } - - public override IReadOnlyList Abilities - { - get => new[] { Ability1, Ability2, AbilityH }; - set - { - if (value.Count != 3) return; - Ability1 = value[0]; - Ability2 = value[1]; - AbilityH = value[2]; - } - } - - public override int GetAbilityIndex(int abilityID) => abilityID == Ability1 ? 0 : abilityID == Ability2 ? 1 : abilityID == AbilityH ? 2 : -1; - - /// - /// Checks if the entry shows up in any of the built-in Pokédex. - /// - public bool IsInDex => PokeDexIndex != 0; + // 0x38-0x3B type tutors, but only 8 bits are valid flags. + var typeTutors = new bool[8]; + for (int i = 0; i < typeTutors.Length; i++) + typeTutors[i] = FlagUtil.GetFlag(Data, 0x38, i); + TypeTutors = typeTutors; } + + public override byte[] Write() + { + for (var i = 0; i < CountTM; i++) + FlagUtil.SetFlag(Data, 0x28 + (i >> 3), i, TMHM[i]); + for (int i = 0; i < TypeTutors.Length; i++) + FlagUtil.SetFlag(Data, 0x38, i, TypeTutors[i]); + return Data; + } + + public override IReadOnlyList Items + { + get => new[] { Item1, Item2, Item3 }; + set + { + if (value.Count != 3) return; + Item1 = value[0]; + Item2 = value[1]; + Item3 = value[2]; + } + } + + public override IReadOnlyList Abilities + { + get => new[] { Ability1, Ability2, AbilityH }; + set + { + if (value.Count != 3) return; + Ability1 = value[0]; + Ability2 = value[1]; + AbilityH = value[2]; + } + } + + public override int GetAbilityIndex(int abilityID) => abilityID == Ability1 ? 0 : abilityID == Ability2 ? 1 : abilityID == AbilityH ? 2 : -1; + + /// + /// Checks if the entry shows up in any of the built-in Pokédex. + /// + public bool IsInDex => PokeDexIndex != 0; } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoBW.cs b/PKHeX.Core/PersonalInfo/PersonalInfoBW.cs index 9ef9130d0..d7e8e0780 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoBW.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoBW.cs @@ -2,98 +2,97 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from the Black & White games. +/// +public class PersonalInfoBW : PersonalInfo { - /// - /// class with values from the Black & White games. - /// - public class PersonalInfoBW : PersonalInfo + public const int SIZE = 0x3C; + + public PersonalInfoBW(byte[] data) : base(data) { - public const int SIZE = 0x3C; - - public PersonalInfoBW(byte[] data) : base(data) - { - // Unpack TMHM & Tutors - TMHM = GetBits(Data.AsSpan(0x28, 0x10)); - TypeTutors = GetBits(Data.AsSpan(0x38, 0x4)); - } - - public override byte[] Write() - { - SetBits(TMHM, Data.AsSpan(0x28)); - SetBits(TypeTutors, Data.AsSpan(0x38)); - return Data; - } - - public sealed override int HP { get => Data[0x00]; set => Data[0x00] = (byte)value; } - public sealed override int ATK { get => Data[0x01]; set => Data[0x01] = (byte)value; } - public sealed override int DEF { get => Data[0x02]; set => Data[0x02] = (byte)value; } - public sealed override int SPE { get => Data[0x03]; set => Data[0x03] = (byte)value; } - public sealed override int SPA { get => Data[0x04]; set => Data[0x04] = (byte)value; } - public sealed override int SPD { get => Data[0x05]; set => Data[0x05] = (byte)value; } - public sealed override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } - public sealed override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } - public sealed override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } - public sealed override int EvoStage { get => Data[0x09]; set => Data[0x09] = (byte)value; } - private int EVYield { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } - public sealed override int EV_HP { get => EVYield >> 0 & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | (value & 0x3) << 0; } - public sealed override int EV_ATK { get => EVYield >> 2 & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | (value & 0x3) << 2; } - public sealed override int EV_DEF { get => EVYield >> 4 & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | (value & 0x3) << 4; } - public sealed override int EV_SPE { get => EVYield >> 6 & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | (value & 0x3) << 6; } - public sealed override int EV_SPA { get => EVYield >> 8 & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | (value & 0x3) << 8; } - public sealed override int EV_SPD { get => EVYield >> 10 & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | (value & 0x3) << 10; } - public bool Telekenesis { get => (EVYield >> 12 & 1) == 1; set => EVYield = (EVYield & ~(0x1 << 12)) | (value ? 1 : 0) << 12; } - public int Item1 { get => ReadInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteInt16LittleEndian(Data.AsSpan(0x0C), (short)value); } - public int Item2 { get => ReadInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteInt16LittleEndian(Data.AsSpan(0x0E), (short)value); } - public int Item3 { get => ReadInt16LittleEndian(Data.AsSpan(0x10)); set => WriteInt16LittleEndian(Data.AsSpan(0x10), (short)value); } - public sealed override int Gender { get => Data[0x12]; set => Data[0x12] = (byte)value; } - public sealed override int HatchCycles { get => Data[0x13]; set => Data[0x13] = (byte)value; } - public sealed override int BaseFriendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } - public sealed override int EXPGrowth { get => Data[0x15]; set => Data[0x15] = (byte)value; } - public sealed override int EggGroup1 { get => Data[0x16]; set => Data[0x16] = (byte)value; } - public sealed override int EggGroup2 { get => Data[0x17]; set => Data[0x17] = (byte)value; } - public int Ability1 { get => Data[0x18]; set => Data[0x18] = (byte)value; } - public int Ability2 { get => Data[0x19]; set => Data[0x19] = (byte)value; } - public int AbilityH { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } - - public sealed override int EscapeRate { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } - protected internal override int FormStatsIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), (ushort)value); } - public sealed override int FormSprite { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } - public sealed override int FormCount { get => Data[0x20]; set => Data[0x20] = (byte)value; } - public sealed override int Color { get => Data[0x21] & 0x3F; set => Data[0x21] = (byte)((Data[0x21] & 0xC0) | (value & 0x3F)); } - public bool SpriteFlip { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); } - public bool SpriteForm { get => ((Data[0x21] >> 7) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x80) | (value ? 0x80 : 0)); } - - public sealed override int BaseEXP { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); } - public sealed override int Height { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); } - public sealed override int Weight { get => ReadUInt16LittleEndian(Data.AsSpan(0x26)); set => WriteUInt16LittleEndian(Data.AsSpan(0x26), (ushort)value); } - - public sealed override IReadOnlyList Items - { - get => new[] { Item1, Item2, Item3 }; - set - { - if (value.Count != 3) return; - Item1 = value[0]; - Item2 = value[1]; - Item3 = value[2]; - } - } - - public sealed override IReadOnlyList Abilities - { - get => new[] { Ability1, Ability2, AbilityH }; - set - { - if (value.Count != 3) return; - Ability1 = (byte)value[0]; - Ability2 = (byte)value[1]; - AbilityH = (byte)value[2]; - } - } - - public sealed override int GetAbilityIndex(int abilityID) => abilityID == Ability1 ? 0 : abilityID == Ability2 ? 1 : abilityID == AbilityH ? 2 : -1; - - public bool HasHiddenAbility => AbilityH != Ability1; + // Unpack TMHM & Tutors + TMHM = GetBits(Data.AsSpan(0x28, 0x10)); + TypeTutors = GetBits(Data.AsSpan(0x38, 0x4)); } + + public override byte[] Write() + { + SetBits(TMHM, Data.AsSpan(0x28)); + SetBits(TypeTutors, Data.AsSpan(0x38)); + return Data; + } + + public sealed override int HP { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public sealed override int ATK { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public sealed override int DEF { get => Data[0x02]; set => Data[0x02] = (byte)value; } + public sealed override int SPE { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public sealed override int SPA { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public sealed override int SPD { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public sealed override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } + public sealed override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } + public sealed override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } + public sealed override int EvoStage { get => Data[0x09]; set => Data[0x09] = (byte)value; } + private int EVYield { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } + public sealed override int EV_HP { get => (EVYield >> 0) & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | ((value & 0x3) << 0); } + public sealed override int EV_ATK { get => (EVYield >> 2) & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | ((value & 0x3) << 2); } + public sealed override int EV_DEF { get => (EVYield >> 4) & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | ((value & 0x3) << 4); } + public sealed override int EV_SPE { get => (EVYield >> 6) & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | ((value & 0x3) << 6); } + public sealed override int EV_SPA { get => (EVYield >> 8) & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | ((value & 0x3) << 8); } + public sealed override int EV_SPD { get => (EVYield >> 10) & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | ((value & 0x3) << 10); } + public bool Telekenesis { get => ((EVYield >> 12) & 1) == 1; set => EVYield = (EVYield & ~(0x1 << 12)) | ((value ? 1 : 0) << 12); } + public int Item1 { get => ReadInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteInt16LittleEndian(Data.AsSpan(0x0C), (short)value); } + public int Item2 { get => ReadInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteInt16LittleEndian(Data.AsSpan(0x0E), (short)value); } + public int Item3 { get => ReadInt16LittleEndian(Data.AsSpan(0x10)); set => WriteInt16LittleEndian(Data.AsSpan(0x10), (short)value); } + public sealed override int Gender { get => Data[0x12]; set => Data[0x12] = (byte)value; } + public sealed override int HatchCycles { get => Data[0x13]; set => Data[0x13] = (byte)value; } + public sealed override int BaseFriendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public sealed override int EXPGrowth { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public sealed override int EggGroup1 { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public sealed override int EggGroup2 { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public int Ability1 { get => Data[0x18]; set => Data[0x18] = (byte)value; } + public int Ability2 { get => Data[0x19]; set => Data[0x19] = (byte)value; } + public int AbilityH { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } + + public sealed override int EscapeRate { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } + protected internal override int FormStatsIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), (ushort)value); } + public sealed override int FormSprite { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } + public sealed override int FormCount { get => Data[0x20]; set => Data[0x20] = (byte)value; } + public sealed override int Color { get => Data[0x21] & 0x3F; set => Data[0x21] = (byte)((Data[0x21] & 0xC0) | (value & 0x3F)); } + public bool SpriteFlip { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); } + public bool SpriteForm { get => ((Data[0x21] >> 7) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x80) | (value ? 0x80 : 0)); } + + public sealed override int BaseEXP { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); } + public sealed override int Height { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); } + public sealed override int Weight { get => ReadUInt16LittleEndian(Data.AsSpan(0x26)); set => WriteUInt16LittleEndian(Data.AsSpan(0x26), (ushort)value); } + + public sealed override IReadOnlyList Items + { + get => new[] { Item1, Item2, Item3 }; + set + { + if (value.Count != 3) return; + Item1 = value[0]; + Item2 = value[1]; + Item3 = value[2]; + } + } + + public sealed override IReadOnlyList Abilities + { + get => new[] { Ability1, Ability2, AbilityH }; + set + { + if (value.Count != 3) return; + Ability1 = (byte)value[0]; + Ability2 = (byte)value[1]; + AbilityH = (byte)value[2]; + } + } + + public sealed override int GetAbilityIndex(int abilityID) => abilityID == Ability1 ? 0 : abilityID == Ability2 ? 1 : abilityID == AbilityH ? 2 : -1; + + public bool HasHiddenAbility => AbilityH != Ability1; } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoG1.cs b/PKHeX.Core/PersonalInfo/PersonalInfoG1.cs index d181e2156..73582fe4e 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoG1.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoG1.cs @@ -1,76 +1,75 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from Generation 1 games. +/// +public sealed class PersonalInfoG1 : PersonalInfo { - /// - /// class with values from Generation 1 games. - /// - public sealed class PersonalInfoG1 : PersonalInfo + public const int SIZE = 0x1C; + + public PersonalInfoG1(byte[] data) : base(data) { - public const int SIZE = 0x1C; + TMHM = GetBits(Data.AsSpan(0x14, 0x8)); + } - public PersonalInfoG1(byte[] data) : base(data) + public override byte[] Write() + { + SetBits(TMHM, Data.AsSpan(0x14)); + return Data; + } + + public int DEX_ID { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public override int HP { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public override int ATK { get => Data[0x02]; set => Data[0x02] = (byte)value; } + public override int DEF { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public override int SPE { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public int SPC { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public override int SPA { get => SPC; set => SPC = value; } + public override int SPD { get => SPC; set => SPC = value; } + public override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } + public override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } + public override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } + public override int BaseEXP { get => Data[0x09]; set => Data[0x09] = (byte)value; } + public int Move1 { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } + public int Move2 { get => Data[0x10]; set => Data[0x10] = (byte)value; } + public int Move3 { get => Data[0x11]; set => Data[0x11] = (byte)value; } + public int Move4 { get => Data[0x12]; set => Data[0x12] = (byte)value; } + public override int EXPGrowth { get => Data[0x13]; set => Data[0x13] = (byte)value; } + + // EV Yields are just aliases for base stats in Gen I + public override int EV_HP { get => HP; set { } } + public override int EV_ATK { get => ATK; set { } } + public override int EV_DEF { get => DEF; set { } } + public override int EV_SPE { get => SPE; set { } } + public int EV_SPC => SPC; + public override int EV_SPA { get => EV_SPC; set { } } + public override int EV_SPD { get => EV_SPC; set { } } + + // Future game values, unused + public override IReadOnlyList Items { get => Array.Empty(); set { } } + public override int EggGroup1 { get => 0; set { } } + public override int EggGroup2 { get => 0; set { } } + public override IReadOnlyList Abilities { get => Array.Empty(); set { } } + public override int GetAbilityIndex(int abilityID) => -1; + public override int Gender { get; set; } + public override int HatchCycles { get => 0; set { } } + public override int BaseFriendship { get => 0; set { } } + public override int EscapeRate { get => 0; set { } } + public override int Color { get => 0; set { } } + + public int[] Moves + { + get => new[] { Move1, Move2, Move3, Move4 }; + set { - TMHM = GetBits(Data.AsSpan(0x14, 0x8)); - } - - public override byte[] Write() - { - SetBits(TMHM, Data.AsSpan(0x14)); - return Data; - } - - public int DEX_ID { get => Data[0x00]; set => Data[0x00] = (byte)value; } - public override int HP { get => Data[0x01]; set => Data[0x01] = (byte)value; } - public override int ATK { get => Data[0x02]; set => Data[0x02] = (byte)value; } - public override int DEF { get => Data[0x03]; set => Data[0x03] = (byte)value; } - public override int SPE { get => Data[0x04]; set => Data[0x04] = (byte)value; } - public int SPC { get => Data[0x05]; set => Data[0x05] = (byte)value; } - public override int SPA { get => SPC; set => SPC = value; } - public override int SPD { get => SPC; set => SPC = value; } - public override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } - public override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } - public override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } - public override int BaseEXP { get => Data[0x09]; set => Data[0x09] = (byte)value; } - public int Move1 { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } - public int Move2 { get => Data[0x10]; set => Data[0x10] = (byte)value; } - public int Move3 { get => Data[0x11]; set => Data[0x11] = (byte)value; } - public int Move4 { get => Data[0x12]; set => Data[0x12] = (byte)value; } - public override int EXPGrowth { get => Data[0x13]; set => Data[0x13] = (byte)value; } - - // EV Yields are just aliases for base stats in Gen I - public override int EV_HP { get => HP; set { } } - public override int EV_ATK { get => ATK; set { } } - public override int EV_DEF { get => DEF; set { } } - public override int EV_SPE { get => SPE; set { } } - public int EV_SPC => SPC; - public override int EV_SPA { get => EV_SPC; set { } } - public override int EV_SPD { get => EV_SPC; set { } } - - // Future game values, unused - public override IReadOnlyList Items { get => Array.Empty(); set { } } - public override int EggGroup1 { get => 0; set { } } - public override int EggGroup2 { get => 0; set { } } - public override IReadOnlyList Abilities { get => Array.Empty(); set { } } - public override int GetAbilityIndex(int abilityID) => -1; - public override int Gender { get; set; } - public override int HatchCycles { get => 0; set { } } - public override int BaseFriendship { get => 0; set { } } - public override int EscapeRate { get => 0; set { } } - public override int Color { get => 0; set { } } - - public int[] Moves - { - get => new[] { Move1, Move2, Move3, Move4 }; - set - { - if (value.Length != 4) return; - Move1 = value[0]; - Move2 = value[1]; - Move3 = value[2]; - Move4 = value[3]; - } + if (value.Length != 4) return; + Move1 = value[0]; + Move2 = value[1]; + Move3 = value[2]; + Move4 = value[3]; } } } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoG2.cs b/PKHeX.Core/PersonalInfo/PersonalInfoG2.cs index eed46d898..6bc363e6e 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoG2.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoG2.cs @@ -1,69 +1,68 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from Generation 2 games. +/// +public sealed class PersonalInfoG2 : PersonalInfo { - /// - /// class with values from Generation 2 games. - /// - public sealed class PersonalInfoG2 : PersonalInfo + public const int SIZE = 0x20; + + public PersonalInfoG2(byte[] data) : base(data) { - public const int SIZE = 0x20; - - public PersonalInfoG2(byte[] data) : base(data) - { - TMHM = GetBits(Data.AsSpan(0x18, 0x8)); - } - - public override byte[] Write() - { - SetBits(TMHM, Data.AsSpan(0x18)); - return Data; - } - - public int DEX_ID { get => Data[0x00]; set => Data[0x00] = (byte)value; } - public override int HP { get => Data[0x01]; set => Data[0x01] = (byte)value; } - public override int ATK { get => Data[0x02]; set => Data[0x02] = (byte)value; } - public override int DEF { get => Data[0x03]; set => Data[0x03] = (byte)value; } - public override int SPE { get => Data[0x04]; set => Data[0x04] = (byte)value; } - public override int SPA { get => Data[0x05]; set => Data[0x05] = (byte)value; } - public override int SPD { get => Data[0x06]; set => Data[0x06] = (byte)value; } - public override int Type1 { get => Data[0x07]; set => Data[0x07] = (byte)value; } - public override int Type2 { get => Data[0x08]; set => Data[0x08] = (byte)value; } - public override int CatchRate { get => Data[0x09]; set => Data[0x09] = (byte)value; } - public override int BaseEXP { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } - public int Item1 { get => Data[0xB]; set => Data[0xB] = (byte)value; } - public int Item2 { get => Data[0xC]; set => Data[0xC] = (byte)value; } - public override int Gender { get => Data[0xD]; set => Data[0xD] = (byte)value; } - public override int HatchCycles { get => Data[0xF]; set => Data[0xF] = (byte)value; } - public override int EXPGrowth { get => Data[0x16]; set => Data[0x16] = (byte)value; } - public override int EggGroup1 { get => Data[0x17] & 0xF; set => Data[0x17] = (byte)((Data[0x17] & 0xF0) | value); } - public override int EggGroup2 { get => Data[0x17] >> 4; set => Data[0x17] = (byte)((Data[0x17] & 0x0F) | value << 4); } - - public override IReadOnlyList Items - { - get => new[] { Item1, Item2 }; - set - { - if (value.Count != 2) return; - Item1 = value[0]; - Item2 = value[1]; - } - } - - // EV Yields are just aliases for base stats in Gen I - public override int EV_HP { get => HP; set { } } - public override int EV_ATK { get => ATK; set { } } - public override int EV_DEF { get => DEF; set { } } - public override int EV_SPE { get => SPE; set { } } - public override int EV_SPA { get => SPA; set { } } - public override int EV_SPD { get => SPD; set { } } - - // Future game values, unused - public override IReadOnlyList Abilities { get => Array.Empty(); set { } } - public override int GetAbilityIndex(int abilityID) => -1; - public override int BaseFriendship { get => 70; set { } } - public override int EscapeRate { get => 0; set { } } - public override int Color { get => 0; set { } } + TMHM = GetBits(Data.AsSpan(0x18, 0x8)); } + + public override byte[] Write() + { + SetBits(TMHM, Data.AsSpan(0x18)); + return Data; + } + + public int DEX_ID { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public override int HP { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public override int ATK { get => Data[0x02]; set => Data[0x02] = (byte)value; } + public override int DEF { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public override int SPE { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public override int SPA { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public override int SPD { get => Data[0x06]; set => Data[0x06] = (byte)value; } + public override int Type1 { get => Data[0x07]; set => Data[0x07] = (byte)value; } + public override int Type2 { get => Data[0x08]; set => Data[0x08] = (byte)value; } + public override int CatchRate { get => Data[0x09]; set => Data[0x09] = (byte)value; } + public override int BaseEXP { get => Data[0x0A]; set => Data[0x0A] = (byte)value; } + public int Item1 { get => Data[0xB]; set => Data[0xB] = (byte)value; } + public int Item2 { get => Data[0xC]; set => Data[0xC] = (byte)value; } + public override int Gender { get => Data[0xD]; set => Data[0xD] = (byte)value; } + public override int HatchCycles { get => Data[0xF]; set => Data[0xF] = (byte)value; } + public override int EXPGrowth { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public override int EggGroup1 { get => Data[0x17] & 0xF; set => Data[0x17] = (byte)((Data[0x17] & 0xF0) | value); } + public override int EggGroup2 { get => Data[0x17] >> 4; set => Data[0x17] = (byte)((Data[0x17] & 0x0F) | (value << 4)); } + + public override IReadOnlyList Items + { + get => new[] { Item1, Item2 }; + set + { + if (value.Count != 2) return; + Item1 = value[0]; + Item2 = value[1]; + } + } + + // EV Yields are just aliases for base stats in Gen I + public override int EV_HP { get => HP; set { } } + public override int EV_ATK { get => ATK; set { } } + public override int EV_DEF { get => DEF; set { } } + public override int EV_SPE { get => SPE; set { } } + public override int EV_SPA { get => SPA; set { } } + public override int EV_SPD { get => SPD; set { } } + + // Future game values, unused + public override IReadOnlyList Abilities { get => Array.Empty(); set { } } + public override int GetAbilityIndex(int abilityID) => -1; + public override int BaseFriendship { get => 70; set { } } + public override int EscapeRate { get => 0; set { } } + public override int Color { get => 0; set { } } } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoG3.cs b/PKHeX.Core/PersonalInfo/PersonalInfoG3.cs index 4c4f556c6..6de4b9833 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoG3.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoG3.cs @@ -2,77 +2,76 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from Generation 3 games. +/// +public class PersonalInfoG3 : PersonalInfo { - /// - /// class with values from Generation 3 games. - /// - public class PersonalInfoG3 : PersonalInfo + public const int SIZE = 0x1C; + + public PersonalInfoG3(byte[] data) : base(data) { - public const int SIZE = 0x1C; - - public PersonalInfoG3(byte[] data) : base(data) - { - } - - public override byte[] Write() => Data; - - public sealed override int HP { get => Data[0x00]; set => Data[0x00] = (byte)value; } - public sealed override int ATK { get => Data[0x01]; set => Data[0x01] = (byte)value; } - public sealed override int DEF { get => Data[0x02]; set => Data[0x02] = (byte)value; } - public sealed override int SPE { get => Data[0x03]; set => Data[0x03] = (byte)value; } - public sealed override int SPA { get => Data[0x04]; set => Data[0x04] = (byte)value; } - public sealed override int SPD { get => Data[0x05]; set => Data[0x05] = (byte)value; } - public sealed override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } - public sealed override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } - public sealed override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } - public sealed override int BaseEXP { get => Data[0x09]; set => Data[0x09] = (byte)value; } - private int EVYield { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } - public sealed override int EV_HP { get => EVYield >> 0 & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | (value & 0x3) << 0; } - public sealed override int EV_ATK { get => EVYield >> 2 & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | (value & 0x3) << 2; } - public sealed override int EV_DEF { get => EVYield >> 4 & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | (value & 0x3) << 4; } - public sealed override int EV_SPE { get => EVYield >> 6 & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | (value & 0x3) << 6; } - public sealed override int EV_SPA { get => EVYield >> 8 & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | (value & 0x3) << 8; } - public sealed override int EV_SPD { get => EVYield >> 10 & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | (value & 0x3) << 10; } - public int Item1 { get => ReadInt16LittleEndian(Data.AsSpan(0xC)); set => WriteInt16LittleEndian(Data.AsSpan(0xC), (short)value); } - public int Item2 { get => ReadInt16LittleEndian(Data.AsSpan(0xE)); set => WriteInt16LittleEndian(Data.AsSpan(0xE), (short)value); } - public sealed override int Gender { get => Data[0x10]; set => Data[0x10] = (byte)value; } - public sealed override int HatchCycles { get => Data[0x11]; set => Data[0x11] = (byte)value; } - public sealed override int BaseFriendship { get => Data[0x12]; set => Data[0x12] = (byte)value; } - public sealed override int EXPGrowth { get => Data[0x13]; set => Data[0x13] = (byte)value; } - public sealed override int EggGroup1 { get => Data[0x14]; set => Data[0x14] = (byte)value; } - public sealed override int EggGroup2 { get => Data[0x15]; set => Data[0x15] = (byte)value; } - public int Ability1 { get => Data[0x16]; set => Data[0x16] = (byte)value; } - public int Ability2 { get => Data[0x17]; set => Data[0x17] = (byte)value; } - public sealed override int EscapeRate { get => Data[0x18]; set => Data[0x18] = (byte)value; } - public sealed override int Color { get => Data[0x19] & 0x7F; set => Data[0x19] = (byte)((Data[0x19] & 0x80) | value); } - public bool NoFlip { get => Data[0x19] >> 7 == 1; set => Data[0x19] = (byte)(Color | (value ? 0x80 : 0)); } - - public sealed override IReadOnlyList Items - { - get => new[] { Item1, Item2 }; - set - { - if (value.Count != 2) return; - Item1 = value[0]; - Item2 = value[1]; - } - } - - public sealed override IReadOnlyList Abilities - { - get => new[] { Ability1, Ability2 }; - set - { - if (value.Count != 2) return; - Ability1 = (byte)value[0]; - Ability2 = (byte)value[1]; - } - } - - public sealed override int GetAbilityIndex(int abilityID) => abilityID == Ability1 ? 0 : abilityID == Ability2 ? 1 : -1; - public int GetAbility(bool second) => second && HasSecondAbility ? Ability2 : Ability1; - - public bool HasSecondAbility => Ability1 != Ability2; } + + public override byte[] Write() => Data; + + public sealed override int HP { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public sealed override int ATK { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public sealed override int DEF { get => Data[0x02]; set => Data[0x02] = (byte)value; } + public sealed override int SPE { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public sealed override int SPA { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public sealed override int SPD { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public sealed override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } + public sealed override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } + public sealed override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } + public sealed override int BaseEXP { get => Data[0x09]; set => Data[0x09] = (byte)value; } + private int EVYield { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } + public sealed override int EV_HP { get => (EVYield >> 0) & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | ((value & 0x3) << 0); } + public sealed override int EV_ATK { get => (EVYield >> 2) & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | ((value & 0x3) << 2); } + public sealed override int EV_DEF { get => (EVYield >> 4) & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | ((value & 0x3) << 4); } + public sealed override int EV_SPE { get => (EVYield >> 6) & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | ((value & 0x3) << 6); } + public sealed override int EV_SPA { get => (EVYield >> 8) & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | ((value & 0x3) << 8); } + public sealed override int EV_SPD { get => (EVYield >> 10) & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | ((value & 0x3) << 10); } + public int Item1 { get => ReadInt16LittleEndian(Data.AsSpan(0xC)); set => WriteInt16LittleEndian(Data.AsSpan(0xC), (short)value); } + public int Item2 { get => ReadInt16LittleEndian(Data.AsSpan(0xE)); set => WriteInt16LittleEndian(Data.AsSpan(0xE), (short)value); } + public sealed override int Gender { get => Data[0x10]; set => Data[0x10] = (byte)value; } + public sealed override int HatchCycles { get => Data[0x11]; set => Data[0x11] = (byte)value; } + public sealed override int BaseFriendship { get => Data[0x12]; set => Data[0x12] = (byte)value; } + public sealed override int EXPGrowth { get => Data[0x13]; set => Data[0x13] = (byte)value; } + public sealed override int EggGroup1 { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public sealed override int EggGroup2 { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public int Ability1 { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public int Ability2 { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public sealed override int EscapeRate { get => Data[0x18]; set => Data[0x18] = (byte)value; } + public sealed override int Color { get => Data[0x19] & 0x7F; set => Data[0x19] = (byte)((Data[0x19] & 0x80) | value); } + public bool NoFlip { get => Data[0x19] >> 7 == 1; set => Data[0x19] = (byte)(Color | (value ? 0x80 : 0)); } + + public sealed override IReadOnlyList Items + { + get => new[] { Item1, Item2 }; + set + { + if (value.Count != 2) return; + Item1 = value[0]; + Item2 = value[1]; + } + } + + public sealed override IReadOnlyList Abilities + { + get => new[] { Ability1, Ability2 }; + set + { + if (value.Count != 2) return; + Ability1 = (byte)value[0]; + Ability2 = (byte)value[1]; + } + } + + public sealed override int GetAbilityIndex(int abilityID) => abilityID == Ability1 ? 0 : abilityID == Ability2 ? 1 : -1; + public int GetAbility(bool second) => second && HasSecondAbility ? Ability2 : Ability1; + + public bool HasSecondAbility => Ability1 != Ability2; } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoG4.cs b/PKHeX.Core/PersonalInfo/PersonalInfoG4.cs index 91b30ef04..b326fe682 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoG4.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoG4.cs @@ -1,30 +1,29 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from Generation 4 games. +/// +public sealed class PersonalInfoG4 : PersonalInfoG3 { - /// - /// class with values from Generation 4 games. - /// - public sealed class PersonalInfoG4 : PersonalInfoG3 + public new const int SIZE = 0x2C; + + public PersonalInfoG4(byte[] data) : base(data) { - public new const int SIZE = 0x2C; - - public PersonalInfoG4(byte[] data) : base(data) - { - // Unpack TMHM & Tutors - TMHM = GetBits(Data.AsSpan(0x1C, 0x0D)); - //TypeTutors = Array.Empty(); // not stored in personal -- default value - } - - public override byte[] Write() - { - SetBits(TMHM, Data.AsSpan(0x1C)); - return Data; - } - - // Manually added attributes - public override int FormCount { get => Data[0x29]; set {} } - protected internal override int FormStatsIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x2A)); set {} } + // Unpack TMHM & Tutors + TMHM = GetBits(Data.AsSpan(0x1C, 0x0D)); + //TypeTutors = Array.Empty(); // not stored in personal -- default value } + + public override byte[] Write() + { + SetBits(TMHM, Data.AsSpan(0x1C)); + return Data; + } + + // Manually added attributes + public override int FormCount { get => Data[0x29]; set {} } + protected internal override int FormStatsIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x2A)); set {} } } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoGG.cs b/PKHeX.Core/PersonalInfo/PersonalInfoGG.cs index a1563e339..5c113d42e 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoGG.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoGG.cs @@ -1,19 +1,18 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core -{ - /// - /// class with values from the games. - /// - public class PersonalInfoGG : PersonalInfoSM - { - public PersonalInfoGG(byte[] data) : base(data) - { - TMHM = GetBits(Data.AsSpan(0x28, 8)); // only 60 TMs used - TypeTutors = GetBits(Data.AsSpan(0x38, 1)); // at most 8 flags used - } +namespace PKHeX.Core; - public int GoSpecies { get => ReadUInt16LittleEndian(Data.AsSpan(0x48)); set => WriteUInt16LittleEndian(Data.AsSpan(0x48), (ushort)value); } +/// +/// class with values from the games. +/// +public class PersonalInfoGG : PersonalInfoSM +{ + public PersonalInfoGG(byte[] data) : base(data) + { + TMHM = GetBits(Data.AsSpan(0x28, 8)); // only 60 TMs used + TypeTutors = GetBits(Data.AsSpan(0x38, 1)); // at most 8 flags used } + + public int GoSpecies { get => ReadUInt16LittleEndian(Data.AsSpan(0x48)); set => WriteUInt16LittleEndian(Data.AsSpan(0x48), (ushort)value); } } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoLA.cs b/PKHeX.Core/PersonalInfo/PersonalInfoLA.cs index 53c1a766b..292bf5988 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoLA.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoLA.cs @@ -43,12 +43,12 @@ public override byte[] Write() public override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } public override int EvoStage { get => Data[0x09]; set => Data[0x09] = (byte)value; } private int EVYield { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } - public override int EV_HP { get => EVYield >> 0 & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | (value & 0x3) << 0; } - public override int EV_ATK { get => EVYield >> 2 & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | (value & 0x3) << 2; } - public override int EV_DEF { get => EVYield >> 4 & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | (value & 0x3) << 4; } - public override int EV_SPE { get => EVYield >> 6 & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | (value & 0x3) << 6; } - public override int EV_SPA { get => EVYield >> 8 & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | (value & 0x3) << 8; } - public override int EV_SPD { get => EVYield >> 10 & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | (value & 0x3) << 10; } + public override int EV_HP { get => (EVYield >> 0) & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | ((value & 0x3) << 0); } + public override int EV_ATK { get => (EVYield >> 2) & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | ((value & 0x3) << 2); } + public override int EV_DEF { get => (EVYield >> 4) & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | ((value & 0x3) << 4); } + public override int EV_SPE { get => (EVYield >> 6) & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | ((value & 0x3) << 6); } + public override int EV_SPA { get => (EVYield >> 8) & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | ((value & 0x3) << 8); } + public override int EV_SPD { get => (EVYield >> 10) & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | ((value & 0x3) << 10); } public int Item1 { get => ReadInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteInt16LittleEndian(Data.AsSpan(0x0C), (short)value); } public int Item2 { get => ReadInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteInt16LittleEndian(Data.AsSpan(0x0E), (short)value); } public int Item3 { get => ReadInt16LittleEndian(Data.AsSpan(0x10)); set => WriteInt16LittleEndian(Data.AsSpan(0x10), (short)value); } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoORAS.cs b/PKHeX.Core/PersonalInfo/PersonalInfoORAS.cs index 9173d6384..36406e638 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoORAS.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoORAS.cs @@ -1,38 +1,37 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from the OR & AS games. +/// +public sealed class PersonalInfoORAS : PersonalInfoXY { - /// - /// class with values from the OR & AS games. - /// - public sealed class PersonalInfoORAS : PersonalInfoXY + public new const int SIZE = 0x50; + + public PersonalInfoORAS(byte[] data) : base(data) { - public new const int SIZE = 0x50; - - public PersonalInfoORAS(byte[] data) : base(data) + // Unpack TMHM & Tutors + TMHM = GetBits(Data.AsSpan(0x28, 0x10)); + TypeTutors = GetBits(Data.AsSpan(0x38, 0x4)); + // 0x3C-0x40 unknown + SpecialTutors = new[] { - // Unpack TMHM & Tutors - TMHM = GetBits(Data.AsSpan(0x28, 0x10)); - TypeTutors = GetBits(Data.AsSpan(0x38, 0x4)); - // 0x3C-0x40 unknown - SpecialTutors = new[] - { - GetBits(Data.AsSpan(0x40, 0x04)), - GetBits(Data.AsSpan(0x44, 0x04)), - GetBits(Data.AsSpan(0x48, 0x04)), - GetBits(Data.AsSpan(0x4C, 0x04)), - }; - } + GetBits(Data.AsSpan(0x40, 0x04)), + GetBits(Data.AsSpan(0x44, 0x04)), + GetBits(Data.AsSpan(0x48, 0x04)), + GetBits(Data.AsSpan(0x4C, 0x04)), + }; + } - public override byte[] Write() - { - SetBits(TMHM, Data.AsSpan(0x28)); - SetBits(TypeTutors, Data.AsSpan(0x38)); - SetBits(SpecialTutors[0], Data.AsSpan(0x40)); - SetBits(SpecialTutors[1], Data.AsSpan(0x44)); - SetBits(SpecialTutors[2], Data.AsSpan(0x48)); - SetBits(SpecialTutors[3], Data.AsSpan(0x4C)); - return Data; - } + public override byte[] Write() + { + SetBits(TMHM, Data.AsSpan(0x28)); + SetBits(TypeTutors, Data.AsSpan(0x38)); + SetBits(SpecialTutors[0], Data.AsSpan(0x40)); + SetBits(SpecialTutors[1], Data.AsSpan(0x44)); + SetBits(SpecialTutors[2], Data.AsSpan(0x48)); + SetBits(SpecialTutors[3], Data.AsSpan(0x4C)); + return Data; } } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoSM.cs b/PKHeX.Core/PersonalInfo/PersonalInfoSM.cs index 4f16fbfc3..d24d441e2 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoSM.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoSM.cs @@ -1,37 +1,36 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from the Sun & Moon games. +/// +public class PersonalInfoSM : PersonalInfoXY { - /// - /// class with values from the Sun & Moon games. - /// - public class PersonalInfoSM : PersonalInfoXY + public new const int SIZE = 0x54; + + public PersonalInfoSM(byte[] data) : base(data) { - public new const int SIZE = 0x54; + TMHM = GetBits(Data.AsSpan(0x28, 0x10)); // 36-39 + TypeTutors = GetBits(Data.AsSpan(0x38, 0x4)); // 40 - public PersonalInfoSM(byte[] data) : base(data) + SpecialTutors = new[] { - TMHM = GetBits(Data.AsSpan(0x28, 0x10)); // 36-39 - TypeTutors = GetBits(Data.AsSpan(0x38, 0x4)); // 40 - - SpecialTutors = new[] - { - GetBits(Data.AsSpan(0x3C, 0x0A)), - }; - } - - public override byte[] Write() - { - SetBits(TMHM, Data.AsSpan(0x28)); - SetBits(TypeTutors, Data.AsSpan(0x38)); - SetBits(SpecialTutors[0], Data.AsSpan(0x3C)); - return Data; - } - - public int SpecialZ_Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x4C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4C), (ushort)value); } - public int SpecialZ_BaseMove { get => ReadUInt16LittleEndian(Data.AsSpan(0x4E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4E), (ushort)value); } - public int SpecialZ_ZMove { get => ReadUInt16LittleEndian(Data.AsSpan(0x50)); set => WriteUInt16LittleEndian(Data.AsSpan(0x50), (ushort)value); } - public bool LocalVariant { get => Data[0x52] == 1; set => Data[0x52] = value ? (byte)1 : (byte)0; } + GetBits(Data.AsSpan(0x3C, 0x0A)), + }; } + + public override byte[] Write() + { + SetBits(TMHM, Data.AsSpan(0x28)); + SetBits(TypeTutors, Data.AsSpan(0x38)); + SetBits(SpecialTutors[0], Data.AsSpan(0x3C)); + return Data; + } + + public int SpecialZ_Item { get => ReadUInt16LittleEndian(Data.AsSpan(0x4C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4C), (ushort)value); } + public int SpecialZ_BaseMove { get => ReadUInt16LittleEndian(Data.AsSpan(0x4E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4E), (ushort)value); } + public int SpecialZ_ZMove { get => ReadUInt16LittleEndian(Data.AsSpan(0x50)); set => WriteUInt16LittleEndian(Data.AsSpan(0x50), (ushort)value); } + public bool LocalVariant { get => Data[0x52] == 1; set => Data[0x52] = value ? (byte)1 : (byte)0; } } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoSWSH.cs b/PKHeX.Core/PersonalInfo/PersonalInfoSWSH.cs index 67cbaa8ed..c9f45a28a 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoSWSH.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoSWSH.cs @@ -1,144 +1,143 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from the games. +/// +public sealed class PersonalInfoSWSH : PersonalInfo { - /// - /// class with values from the games. - /// - public sealed class PersonalInfoSWSH : PersonalInfo + public const int SIZE = 0xB0; + public const int CountTM = 100; + public const int CountTR = 100; + + public PersonalInfoSWSH(byte[] data) : base(data) { - public const int SIZE = 0xB0; - public const int CountTM = 100; - public const int CountTR = 100; - - public PersonalInfoSWSH(byte[] data) : base(data) + TMHM = new bool[200]; + for (var i = 0; i < CountTR; i++) { - TMHM = new bool[200]; - for (var i = 0; i < CountTR; i++) - { - TMHM[i] = FlagUtil.GetFlag(Data, 0x28 + (i >> 3), i); - TMHM[i + CountTM] = FlagUtil.GetFlag(Data, 0x3C + (i >> 3), i); - } - - // 0x38-0x3B type tutors, but only 8 bits are valid flags. - var typeTutors = new bool[8]; - for (int i = 0; i < typeTutors.Length; i++) - typeTutors[i] = FlagUtil.GetFlag(Data, 0x38, i); - TypeTutors = typeTutors; - - // 0xA8-0xAF are armor type tutors, one bit for each type - var armorTutors = new bool[18]; - for (int i = 0; i < armorTutors.Length; i++) - armorTutors[i] = FlagUtil.GetFlag(Data, 0xA8 + (i >> 3), i); - SpecialTutors = new[] - { - armorTutors, - }; + TMHM[i] = FlagUtil.GetFlag(Data, 0x28 + (i >> 3), i); + TMHM[i + CountTM] = FlagUtil.GetFlag(Data, 0x3C + (i >> 3), i); } - public override byte[] Write() + // 0x38-0x3B type tutors, but only 8 bits are valid flags. + var typeTutors = new bool[8]; + for (int i = 0; i < typeTutors.Length; i++) + typeTutors[i] = FlagUtil.GetFlag(Data, 0x38, i); + TypeTutors = typeTutors; + + // 0xA8-0xAF are armor type tutors, one bit for each type + var armorTutors = new bool[18]; + for (int i = 0; i < armorTutors.Length; i++) + armorTutors[i] = FlagUtil.GetFlag(Data, 0xA8 + (i >> 3), i); + SpecialTutors = new[] { - for (var i = 0; i < CountTR; i++) - { - FlagUtil.SetFlag(Data, 0x28 + (i >> 3), i, TMHM[i]); - FlagUtil.SetFlag(Data, 0x3C + (i >> 3), i, TMHM[i + CountTM]); - } - for (int i = 0; i < TypeTutors.Length; i++) - FlagUtil.SetFlag(Data, 0x38, i, TypeTutors[i]); - for (int i = 0; i < SpecialTutors[0].Length; i++) - FlagUtil.SetFlag(Data, 0xA8 + (i >> 3), i, SpecialTutors[0][i]); - return Data; - } - - public override int HP { get => Data[0x00]; set => Data[0x00] = (byte)value; } - public override int ATK { get => Data[0x01]; set => Data[0x01] = (byte)value; } - public override int DEF { get => Data[0x02]; set => Data[0x02] = (byte)value; } - public override int SPE { get => Data[0x03]; set => Data[0x03] = (byte)value; } - public override int SPA { get => Data[0x04]; set => Data[0x04] = (byte)value; } - public override int SPD { get => Data[0x05]; set => Data[0x05] = (byte)value; } - public override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } - public override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } - public override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } - public override int EvoStage { get => Data[0x09]; set => Data[0x09] = (byte)value; } - private int EVYield { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } - public override int EV_HP { get => EVYield >> 0 & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | (value & 0x3) << 0; } - public override int EV_ATK { get => EVYield >> 2 & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | (value & 0x3) << 2; } - public override int EV_DEF { get => EVYield >> 4 & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | (value & 0x3) << 4; } - public override int EV_SPE { get => EVYield >> 6 & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | (value & 0x3) << 6; } - public override int EV_SPA { get => EVYield >> 8 & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | (value & 0x3) << 8; } - public override int EV_SPD { get => EVYield >> 10 & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | (value & 0x3) << 10; } - public int Item1 { get => ReadInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteInt16LittleEndian(Data.AsSpan(0x0C), (short)value); } - public int Item2 { get => ReadInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteInt16LittleEndian(Data.AsSpan(0x0E), (short)value); } - public int Item3 { get => ReadInt16LittleEndian(Data.AsSpan(0x10)); set => WriteInt16LittleEndian(Data.AsSpan(0x10), (short)value); } - public override int Gender { get => Data[0x12]; set => Data[0x12] = (byte)value; } - public override int HatchCycles { get => Data[0x13]; set => Data[0x13] = (byte)value; } - public override int BaseFriendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } - public override int EXPGrowth { get => Data[0x15]; set => Data[0x15] = (byte)value; } - public override int EggGroup1 { get => Data[0x16]; set => Data[0x16] = (byte)value; } - public override int EggGroup2 { get => Data[0x17]; set => Data[0x17] = (byte)value; } - public int Ability1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); } - public int Ability2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), (ushort)value); } - public int AbilityH { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), (ushort)value); } - public override int EscapeRate { get => 0; set { } } // moved? - protected internal override int FormStatsIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } - public override int FormSprite { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } // ??? - public override int FormCount { get => Data[0x20]; set => Data[0x20] = (byte)value; } - public override int Color { get => Data[0x21] & 0x3F; set => Data[0x21] = (byte)((Data[0x21] & 0xC0) | (value & 0x3F)); } - public override bool IsPresentInGame { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); } - public bool SpriteForm { get => ((Data[0x21] >> 7) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x80) | (value ? 0x80 : 0)); } - public override int BaseEXP { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); } - public override int Height { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); } - public override int Weight { get => ReadUInt16LittleEndian(Data.AsSpan(0x26)); set => WriteUInt16LittleEndian(Data.AsSpan(0x26), (ushort)value); } - - public override IReadOnlyList Items - { - get => new[] { Item1, Item2, Item3 }; - set - { - if (value.Count != 3) return; - Item1 = value[0]; - Item2 = value[1]; - Item3 = value[2]; - } - } - - public override IReadOnlyList Abilities - { - get => new[] { Ability1, Ability2, AbilityH }; - set - { - if (value.Count != 3) return; - Ability1 = value[0]; - Ability2 = value[1]; - AbilityH = value[2]; - } - } - - public override int GetAbilityIndex(int abilityID) => abilityID == Ability1 ? 0 : abilityID == Ability2 ? 1 : abilityID == AbilityH ? 2 : -1; - - public int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x4C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4C), (ushort)value); } - - public int HatchSpecies { get => ReadUInt16LittleEndian(Data.AsSpan(0x56)); set => WriteUInt16LittleEndian(Data.AsSpan(0x56), (ushort)value); } - public int LocalFormIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x58)); set => WriteUInt16LittleEndian(Data.AsSpan(0x58), (ushort)value); } // local region base form - public ushort RegionalFlags { get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), value); } - public bool IsRegionalForm { get => (RegionalFlags & 1) == 1; set => RegionalFlags = (ushort)((RegionalFlags & 0xFFFE) | (value ? 1 : 0)); } - public int PokeDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); } - public int RegionalFormIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); } // form index of this entry - public int ArmorDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0xAC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xAC), (ushort)value); } - public int CrownDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0xAE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xAE), (ushort)value); } - - /// - /// Gets the Form that any offspring will hatch with, assuming it is holding an Everstone. - /// - public int HatchFormIndexEverstone => IsRegionalForm ? RegionalFormIndex : LocalFormIndex; - - /// - /// Checks if the entry shows up in any of the built-in Pokédex. - /// - public bool IsInDex => PokeDexIndex != 0 || ArmorDexIndex != 0 || CrownDexIndex != 0; - - public bool HasHiddenAbility => AbilityH != Ability1; + armorTutors, + }; } + + public override byte[] Write() + { + for (var i = 0; i < CountTR; i++) + { + FlagUtil.SetFlag(Data, 0x28 + (i >> 3), i, TMHM[i]); + FlagUtil.SetFlag(Data, 0x3C + (i >> 3), i, TMHM[i + CountTM]); + } + for (int i = 0; i < TypeTutors.Length; i++) + FlagUtil.SetFlag(Data, 0x38, i, TypeTutors[i]); + for (int i = 0; i < SpecialTutors[0].Length; i++) + FlagUtil.SetFlag(Data, 0xA8 + (i >> 3), i, SpecialTutors[0][i]); + return Data; + } + + public override int HP { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public override int ATK { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public override int DEF { get => Data[0x02]; set => Data[0x02] = (byte)value; } + public override int SPE { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public override int SPA { get => Data[0x04]; set => Data[0x04] = (byte)value; } + public override int SPD { get => Data[0x05]; set => Data[0x05] = (byte)value; } + public override int Type1 { get => Data[0x06]; set => Data[0x06] = (byte)value; } + public override int Type2 { get => Data[0x07]; set => Data[0x07] = (byte)value; } + public override int CatchRate { get => Data[0x08]; set => Data[0x08] = (byte)value; } + public override int EvoStage { get => Data[0x09]; set => Data[0x09] = (byte)value; } + private int EVYield { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } + public override int EV_HP { get => (EVYield >> 0) & 0x3; set => EVYield = (EVYield & ~(0x3 << 0)) | ((value & 0x3) << 0); } + public override int EV_ATK { get => (EVYield >> 2) & 0x3; set => EVYield = (EVYield & ~(0x3 << 2)) | ((value & 0x3) << 2); } + public override int EV_DEF { get => (EVYield >> 4) & 0x3; set => EVYield = (EVYield & ~(0x3 << 4)) | ((value & 0x3) << 4); } + public override int EV_SPE { get => (EVYield >> 6) & 0x3; set => EVYield = (EVYield & ~(0x3 << 6)) | ((value & 0x3) << 6); } + public override int EV_SPA { get => (EVYield >> 8) & 0x3; set => EVYield = (EVYield & ~(0x3 << 8)) | ((value & 0x3) << 8); } + public override int EV_SPD { get => (EVYield >> 10) & 0x3; set => EVYield = (EVYield & ~(0x3 << 10)) | ((value & 0x3) << 10); } + public int Item1 { get => ReadInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteInt16LittleEndian(Data.AsSpan(0x0C), (short)value); } + public int Item2 { get => ReadInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteInt16LittleEndian(Data.AsSpan(0x0E), (short)value); } + public int Item3 { get => ReadInt16LittleEndian(Data.AsSpan(0x10)); set => WriteInt16LittleEndian(Data.AsSpan(0x10), (short)value); } + public override int Gender { get => Data[0x12]; set => Data[0x12] = (byte)value; } + public override int HatchCycles { get => Data[0x13]; set => Data[0x13] = (byte)value; } + public override int BaseFriendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public override int EXPGrowth { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public override int EggGroup1 { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public override int EggGroup2 { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public int Ability1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); } + public int Ability2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), (ushort)value); } + public int AbilityH { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), (ushort)value); } + public override int EscapeRate { get => 0; set { } } // moved? + protected internal override int FormStatsIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } + public override int FormSprite { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } // ??? + public override int FormCount { get => Data[0x20]; set => Data[0x20] = (byte)value; } + public override int Color { get => Data[0x21] & 0x3F; set => Data[0x21] = (byte)((Data[0x21] & 0xC0) | (value & 0x3F)); } + public override bool IsPresentInGame { get => ((Data[0x21] >> 6) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x40) | (value ? 0x40 : 0)); } + public bool SpriteForm { get => ((Data[0x21] >> 7) & 1) == 1; set => Data[0x21] = (byte)((Data[0x21] & ~0x80) | (value ? 0x80 : 0)); } + public override int BaseEXP { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); } + public override int Height { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); } + public override int Weight { get => ReadUInt16LittleEndian(Data.AsSpan(0x26)); set => WriteUInt16LittleEndian(Data.AsSpan(0x26), (ushort)value); } + + public override IReadOnlyList Items + { + get => new[] { Item1, Item2, Item3 }; + set + { + if (value.Count != 3) return; + Item1 = value[0]; + Item2 = value[1]; + Item3 = value[2]; + } + } + + public override IReadOnlyList Abilities + { + get => new[] { Ability1, Ability2, AbilityH }; + set + { + if (value.Count != 3) return; + Ability1 = value[0]; + Ability2 = value[1]; + AbilityH = value[2]; + } + } + + public override int GetAbilityIndex(int abilityID) => abilityID == Ability1 ? 0 : abilityID == Ability2 ? 1 : abilityID == AbilityH ? 2 : -1; + + public int Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x4C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4C), (ushort)value); } + + public int HatchSpecies { get => ReadUInt16LittleEndian(Data.AsSpan(0x56)); set => WriteUInt16LittleEndian(Data.AsSpan(0x56), (ushort)value); } + public int LocalFormIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x58)); set => WriteUInt16LittleEndian(Data.AsSpan(0x58), (ushort)value); } // local region base form + public ushort RegionalFlags { get => ReadUInt16LittleEndian(Data.AsSpan(0x5A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5A), value); } + public bool IsRegionalForm { get => (RegionalFlags & 1) == 1; set => RegionalFlags = (ushort)((RegionalFlags & 0xFFFE) | (value ? 1 : 0)); } + public int PokeDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x5C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5C), (ushort)value); } + public int RegionalFormIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0x5E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x5E), (ushort)value); } // form index of this entry + public int ArmorDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0xAC)); set => WriteUInt16LittleEndian(Data.AsSpan(0xAC), (ushort)value); } + public int CrownDexIndex { get => ReadUInt16LittleEndian(Data.AsSpan(0xAE)); set => WriteUInt16LittleEndian(Data.AsSpan(0xAE), (ushort)value); } + + /// + /// Gets the Form that any offspring will hatch with, assuming it is holding an Everstone. + /// + public int HatchFormIndexEverstone => IsRegionalForm ? RegionalFormIndex : LocalFormIndex; + + /// + /// Checks if the entry shows up in any of the built-in Pokédex. + /// + public bool IsInDex => PokeDexIndex != 0 || ArmorDexIndex != 0 || CrownDexIndex != 0; + + public bool HasHiddenAbility => AbilityH != Ability1; } diff --git a/PKHeX.Core/PersonalInfo/PersonalInfoXY.cs b/PKHeX.Core/PersonalInfo/PersonalInfoXY.cs index 59f13fbe4..3191d4b95 100644 --- a/PKHeX.Core/PersonalInfo/PersonalInfoXY.cs +++ b/PKHeX.Core/PersonalInfo/PersonalInfoXY.cs @@ -1,27 +1,26 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// class with values from the X & Y games. +/// +public class PersonalInfoXY : PersonalInfoBW { - /// - /// class with values from the X & Y games. - /// - public class PersonalInfoXY : PersonalInfoBW + public new const int SIZE = 0x40; + + public PersonalInfoXY(byte[] data) : base(data) { - public new const int SIZE = 0x40; + // Unpack TMHM & Tutors + TMHM = GetBits(Data.AsSpan(0x28, 0x10)); + TypeTutors = GetBits(Data.AsSpan(0x38, 0x4)); + // 0x3C-0x40 unknown + } - public PersonalInfoXY(byte[] data) : base(data) - { - // Unpack TMHM & Tutors - TMHM = GetBits(Data.AsSpan(0x28, 0x10)); - TypeTutors = GetBits(Data.AsSpan(0x38, 0x4)); - // 0x3C-0x40 unknown - } - - public override byte[] Write() - { - SetBits(TMHM, Data.AsSpan(0x28)); - SetBits(TypeTutors, Data.AsSpan(0x38)); - return Data; - } + public override byte[] Write() + { + SetBits(TMHM, Data.AsSpan(0x28)); + SetBits(TypeTutors, Data.AsSpan(0x38)); + return Data; } } diff --git a/PKHeX.Core/PersonalInfo/PersonalTable.cs b/PKHeX.Core/PersonalInfo/PersonalTable.cs index 46015fb7b..84b675885 100644 --- a/PKHeX.Core/PersonalInfo/PersonalTable.cs +++ b/PKHeX.Core/PersonalInfo/PersonalTable.cs @@ -1,413 +1,412 @@ using System; using System.Diagnostics; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// table (array). +/// +/// +/// Serves as the main object that is accessed for stat data in a particular generation/game format. +/// +[DebuggerDisplay($"{{{nameof(Game)},nq}}[{{{nameof(TableLength)},nq}}]")] +public sealed class PersonalTable { /// - /// table (array). + /// Personal Table used in . /// - /// - /// Serves as the main object that is accessed for stat data in a particular generation/game format. - /// - [DebuggerDisplay($"{{{nameof(Game)},nq}}[{{{nameof(TableLength)},nq}}]")] - public sealed class PersonalTable + public static readonly PersonalTable LA = GetTable("la", GameVersion.PLA); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable BDSP = GetTable("bdsp", GameVersion.BDSP); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable SWSH = GetTable("swsh", GameVersion.SWSH); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable GG = GetTable("gg", GameVersion.GG); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable USUM = GetTable("uu", GameVersion.USUM); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable SM = GetTable("sm", GameVersion.SM); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable AO = GetTable("ao", GameVersion.ORAS); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable XY = GetTable("xy", GameVersion.XY); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable B2W2 = GetTable("b2w2", GameVersion.B2W2); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable BW = GetTable("bw", GameVersion.BW); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable HGSS = GetTable("hgss", GameVersion.HGSS); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable Pt = GetTable("pt", GameVersion.Pt); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable DP = GetTable("dp", GameVersion.DP); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable LG = GetTable("lg", GameVersion.LG); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable FR = GetTable("fr", GameVersion.FR); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable E = GetTable("e", GameVersion.E); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable RS = GetTable("rs", GameVersion.RS); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable C = GetTable("c", GameVersion.C); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable GS = GetTable("c", GameVersion.GS); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable RB = GetTable("rb", GameVersion.RB); + + /// + /// Personal Table used in . + /// + public static readonly PersonalTable Y = GetTable("y", GameVersion.YW); + + private static PersonalTable GetTable(string game, GameVersion format) => new(Util.GetBinaryResource($"personal_{game}"), format); + + private static Func GetConstructor(GameVersion format) => format switch { - /// - /// Personal Table used in . - /// - public static readonly PersonalTable LA = GetTable("la", GameVersion.PLA); + GameVersion.RB or GameVersion.YW => z => new PersonalInfoG1(z), + GameVersion.GS or GameVersion.C => z => new PersonalInfoG2(z), + GameVersion.RS or GameVersion.E or GameVersion.FR or GameVersion.LG => z => new PersonalInfoG3(z), + GameVersion.DP or GameVersion.Pt or GameVersion.HGSS => z => new PersonalInfoG4(z), + GameVersion.BW => z => new PersonalInfoBW(z), + GameVersion.B2W2 => z => new PersonalInfoB2W2(z), + GameVersion.XY => z => new PersonalInfoXY(z), + GameVersion.ORAS => z => new PersonalInfoORAS(z), + GameVersion.SM or GameVersion.USUM => z => new PersonalInfoSM(z), + GameVersion.GG => z => new PersonalInfoGG(z), + GameVersion.SWSH => z => new PersonalInfoSWSH(z), + GameVersion.PLA => z => new PersonalInfoLA(z), + _ => z => new PersonalInfoBDSP(z), + }; - /// - /// Personal Table used in . - /// - public static readonly PersonalTable BDSP = GetTable("bdsp", GameVersion.BDSP); + private static int GetEntrySize(GameVersion format) => format switch + { + GameVersion.RB or GameVersion.YW => PersonalInfoG1.SIZE, + GameVersion.GS or GameVersion.C => PersonalInfoG2.SIZE, + GameVersion.RS or GameVersion.E or GameVersion.FR or GameVersion.LG => PersonalInfoG3.SIZE, + GameVersion.DP or GameVersion.Pt or GameVersion.HGSS => PersonalInfoG4.SIZE, + GameVersion.BW => PersonalInfoBW.SIZE, + GameVersion.B2W2 => PersonalInfoB2W2.SIZE, + GameVersion.XY => PersonalInfoXY.SIZE, + GameVersion.ORAS => PersonalInfoORAS.SIZE, + GameVersion.SM or GameVersion.USUM or GameVersion.GG => PersonalInfoSM.SIZE, + GameVersion.SWSH => PersonalInfoSWSH.SIZE, + GameVersion.BDSP => PersonalInfoBDSP.SIZE, + GameVersion.PLA => PersonalInfoLA.SIZE, + _ => -1, + }; - /// - /// Personal Table used in . - /// - public static readonly PersonalTable SWSH = GetTable("swsh", GameVersion.SWSH); + static PersonalTable() // Finish Setup + { + FixPersonalTableG1(); + PopulateGen3Tutors(); + PopulateGen4Tutors(); + CopyDexitGenders(); + } - /// - /// Personal Table used in . - /// - public static readonly PersonalTable GG = GetTable("gg", GameVersion.GG); + private static void FixPersonalTableG1() + { + // Load Gen2 Gender Ratios into Gen1 + PersonalInfo[] rb = RB.Table, y = Y.Table, gs = GS.Table; + for (int i = Legal.MaxSpeciesID_1; i >= 0; i--) + rb[i].Gender = y[i].Gender = gs[i].Gender; + } - /// - /// Personal Table used in . - /// - public static readonly PersonalTable USUM = GetTable("uu", GameVersion.USUM); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable SM = GetTable("sm", GameVersion.SM); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable AO = GetTable("ao", GameVersion.ORAS); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable XY = GetTable("xy", GameVersion.XY); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable B2W2 = GetTable("b2w2", GameVersion.B2W2); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable BW = GetTable("bw", GameVersion.BW); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable HGSS = GetTable("hgss", GameVersion.HGSS); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable Pt = GetTable("pt", GameVersion.Pt); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable DP = GetTable("dp", GameVersion.DP); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable LG = GetTable("lg", GameVersion.LG); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable FR = GetTable("fr", GameVersion.FR); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable E = GetTable("e", GameVersion.E); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable RS = GetTable("rs", GameVersion.RS); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable C = GetTable("c", GameVersion.C); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable GS = GetTable("c", GameVersion.GS); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable RB = GetTable("rb", GameVersion.RB); - - /// - /// Personal Table used in . - /// - public static readonly PersonalTable Y = GetTable("y", GameVersion.YW); - - private static PersonalTable GetTable(string game, GameVersion format) => new(Util.GetBinaryResource($"personal_{game}"), format); - - private static Func GetConstructor(GameVersion format) => format switch + private static void PopulateGen3Tutors() + { + // Update Gen3 data with Emerald's data, FR/LG is a subset of Emerald's compatibility. + var machine = BinLinkerAccessor.Get(Util.GetBinaryResource("hmtm_g3.pkl"), "g3"); + var tutors = BinLinkerAccessor.Get(Util.GetBinaryResource("tutors_g3.pkl"), "g3"); + var table = E.Table; + for (int i = Legal.MaxSpeciesID_3; i >= 0; i--) { - GameVersion.RB or GameVersion.YW => z => new PersonalInfoG1(z), - GameVersion.GS or GameVersion.C => z => new PersonalInfoG2(z), - GameVersion.RS or GameVersion.E or GameVersion.FR or GameVersion.LG => z => new PersonalInfoG3(z), - GameVersion.DP or GameVersion.Pt or GameVersion.HGSS => z => new PersonalInfoG4(z), - GameVersion.BW => z => new PersonalInfoBW(z), - GameVersion.B2W2 => z => new PersonalInfoB2W2(z), - GameVersion.XY => z => new PersonalInfoXY(z), - GameVersion.ORAS => z => new PersonalInfoORAS(z), - GameVersion.SM or GameVersion.USUM => z => new PersonalInfoSM(z), - GameVersion.GG => z => new PersonalInfoGG(z), - GameVersion.SWSH => z => new PersonalInfoSWSH(z), - GameVersion.PLA => z => new PersonalInfoLA(z), - _ => z => new PersonalInfoBDSP(z), - }; + var entry = table[i]; + entry.AddTMHM(machine[i]); + entry.AddTypeTutors(tutors[i]); - private static int GetEntrySize(GameVersion format) => format switch - { - GameVersion.RB or GameVersion.YW => PersonalInfoG1.SIZE, - GameVersion.GS or GameVersion.C => PersonalInfoG2.SIZE, - GameVersion.RS or GameVersion.E or GameVersion.FR or GameVersion.LG => PersonalInfoG3.SIZE, - GameVersion.DP or GameVersion.Pt or GameVersion.HGSS => PersonalInfoG4.SIZE, - GameVersion.BW => PersonalInfoBW.SIZE, - GameVersion.B2W2 => PersonalInfoB2W2.SIZE, - GameVersion.XY => PersonalInfoXY.SIZE, - GameVersion.ORAS => PersonalInfoORAS.SIZE, - GameVersion.SM or GameVersion.USUM or GameVersion.GG => PersonalInfoSM.SIZE, - GameVersion.SWSH => PersonalInfoSWSH.SIZE, - GameVersion.BDSP => PersonalInfoBDSP.SIZE, - GameVersion.PLA => PersonalInfoLA.SIZE, - _ => -1, - }; - - static PersonalTable() // Finish Setup - { - FixPersonalTableG1(); - PopulateGen3Tutors(); - PopulateGen4Tutors(); - CopyDexitGenders(); - } - - private static void FixPersonalTableG1() - { - // Load Gen2 Gender Ratios into Gen1 - PersonalInfo[] rb = RB.Table, y = Y.Table, gs = GS.Table; - for (int i = Legal.MaxSpeciesID_1; i >= 0; i--) - rb[i].Gender = y[i].Gender = gs[i].Gender; - } - - private static void PopulateGen3Tutors() - { - // Update Gen3 data with Emerald's data, FR/LG is a subset of Emerald's compatibility. - var machine = BinLinkerAccessor.Get(Util.GetBinaryResource("hmtm_g3.pkl"), "g3"); - var tutors = BinLinkerAccessor.Get(Util.GetBinaryResource("tutors_g3.pkl"), "g3"); - var table = E.Table; - for (int i = Legal.MaxSpeciesID_3; i >= 0; i--) - { - var entry = table[i]; - entry.AddTMHM(machine[i]); - entry.AddTypeTutors(tutors[i]); - - // Copy to other tables - RS.Table[i].TMHM = FR.Table[i].TMHM = LG.Table[i].TMHM = entry.TMHM; - RS.Table[i].TypeTutors = FR.Table[i].TypeTutors = LG.Table[i].TypeTutors = entry.TypeTutors; - } - } - - private static void PopulateGen4Tutors() - { - var tutors = BinLinkerAccessor.Get(Util.GetBinaryResource("tutors_g4.pkl"), "g4"); - var table = HGSS.Table; - var count = tutors.Length; - for (int i = 0; i < count; i++) - table[i].AddTypeTutors(tutors[i]); - } - - /// - /// Sword/Shield do not contain personal data (stubbed) for all species that are not allowed to visit the game. - /// Copy all the genders from 's table for all past species, since we need it for gender lookups for all generations. - /// - private static void CopyDexitGenders() - { - var swsh = SWSH.Table; - var usum = USUM.Table; - - for (int i = 1; i <= Legal.MaxSpeciesID_7_USUM; i++) - { - var ss = swsh[i]; - if (ss.HP == 0) - ss.Gender = usum[i].Gender; - } - - var la = LA; - for (int i = 1; i <= Legal.MaxSpeciesID_8_R2; i++) - { - var e = la.Table[i]; - var fc = e.FormCount; - for (int f = 0; f < fc; f++) - { - var l = (PersonalInfoLA)la.GetFormEntry(i, f); - if (l.HP != 0) - continue; - var s = (PersonalInfoSWSH)SWSH.GetFormEntry(i, f); - l.Ability1 = s.Ability1; - l.Ability2 = s.Ability2; - l.AbilityH = s.AbilityH; - l.Gender = s.Gender; - l.EXPGrowth = s.EXPGrowth; - } - } - } - - public PersonalTable(ReadOnlySpan data, GameVersion format) - { - var get = GetConstructor(format); - int size = GetEntrySize(format); - var count = data.Length / size; - var table = new PersonalInfo[count]; - for (int i = 0; i < table.Length; i++) - table[i] = get(data.Slice(size * i, size).ToArray()); - - Table = table; - MaxSpeciesID = format.GetMaxSpeciesID(); - Game = format; - } - - private readonly PersonalInfo[] Table; - - /// - /// Gets an index from the inner array. - /// - /// Has built in length checks; returns empty (0) entry if out of range. - /// Index to retrieve - /// Requested index entry - public PersonalInfo this[int index] - { - get - { - var table = Table; - if ((uint)index >= table.Length) - return table[0]; - return table[index]; - } - set - { - var table = Table; - if ((uint)index >= table.Length) - return; - table[index] = value; - } - } - - /// - /// Alternate way of fetching . - /// - public PersonalInfo this[int species, int form] => GetFormEntry(species, form); - - /// - /// Gets the entry index for a given and . - /// - /// - /// - /// Entry index for the input criteria - public int GetFormIndex(int species, int form) - { - if ((uint)species <= MaxSpeciesID) - return Table[species].FormIndex(species, form); - Debug.WriteLine($"Requested out of bounds {nameof(species)}: {species} (max={MaxSpeciesID})"); - return 0; - } - - /// - /// Gets the entry for a given and . - /// - /// - /// - /// Entry for the input criteria - public PersonalInfo GetFormEntry(int species, int form) - { - return this[GetFormIndex(species, form)]; - } - - /// - /// Count of entries in the table, which includes default species entries and their separate entries. - /// - public int TableLength => Table.Length; - - /// - /// Maximum Species ID for the Table. - /// - public readonly int MaxSpeciesID; - - /// - /// Game(s) the originated from. - /// - public readonly GameVersion Game; - - /// - /// Gets form names for every species. - /// - /// Raw string resource (Species) for the corresponding table. - /// Max Species ID () - /// Array of species containing an array of form names for that species. - public string[][] GetFormList(string[] species, int MaxSpecies) - { - string[][] FormList = new string[MaxSpecies+1][]; - for (int i = 0; i < FormList.Length; i++) - { - int FormCount = this[i].FormCount; - FormList[i] = new string[FormCount]; - if (FormCount <= 0) - continue; - - FormList[i][0] = species[i]; - for (int j = 1; j < FormCount; j++) - FormList[i][j] = $"{species[i]} {j}"; - } - - return FormList; - } - - /// - /// Gets an arranged list of Form names and indexes for use with the individual values. - /// - /// Raw string resource (Forms) for the corresponding table. - /// Raw string resource (Species) for the corresponding table. - /// Max Species ID () - /// Pointers for base form IDs - /// Pointers for table indexes for each form - /// Sanitized list of species names, and outputs indexes for various lookup purposes. - public string[] GetPersonalEntryList(string[][] forms, string[] species, int MaxSpecies, out int[] baseForm, out int[] formVal) - { - string[] result = new string[Table.Length]; - baseForm = new int[result.Length]; - formVal = new int[result.Length]; - for (int i = 0; i <= MaxSpecies; i++) - { - result[i] = species[i]; - if (forms[i].Length == 0) - continue; - int basePtr = this[i].FormStatsIndex; - if (basePtr <= 0) - continue; - for (int j = 1; j < forms[i].Length; j++) - { - int ptr = basePtr + j - 1; - baseForm[ptr] = i; - formVal[ptr] = j; - result[ptr] = forms[i][j]; - } - } - return result; - } - - /// - /// Checks to see if either of the input type combinations exist in the table. - /// - /// Only useful for checking Generation 1 and properties. - /// First type - /// Second type - /// Indication that the combination exists in the table. - public bool IsValidTypeCombination(int type1, int type2) - { - return Array.Find(Table, p => p.IsValidTypeCombination(type1, type2)) != null; - } - - public bool IsSpeciesInGame(int species) - { - if ((uint)species > MaxSpeciesID) - return false; - var form0 = Table[species]; - if (form0.IsPresentInGame) - return true; - var fc = form0.FormCount; - for (int i = 1; i < fc; i++) - { - if (GetFormEntry(species, i).IsPresentInGame) - return true; - } - return false; - } - - public bool IsPresentInGame(int species, int form) - { - if ((uint)species > MaxSpeciesID) - return false; - var entry = GetFormEntry(species, form); - return entry.IsPresentInGame; + // Copy to other tables + RS.Table[i].TMHM = FR.Table[i].TMHM = LG.Table[i].TMHM = entry.TMHM; + RS.Table[i].TypeTutors = FR.Table[i].TypeTutors = LG.Table[i].TypeTutors = entry.TypeTutors; } } + + private static void PopulateGen4Tutors() + { + var tutors = BinLinkerAccessor.Get(Util.GetBinaryResource("tutors_g4.pkl"), "g4"); + var table = HGSS.Table; + var count = tutors.Length; + for (int i = 0; i < count; i++) + table[i].AddTypeTutors(tutors[i]); + } + + /// + /// Sword/Shield do not contain personal data (stubbed) for all species that are not allowed to visit the game. + /// Copy all the genders from 's table for all past species, since we need it for gender lookups for all generations. + /// + private static void CopyDexitGenders() + { + var swsh = SWSH.Table; + var usum = USUM.Table; + + for (int i = 1; i <= Legal.MaxSpeciesID_7_USUM; i++) + { + var ss = swsh[i]; + if (ss.HP == 0) + ss.Gender = usum[i].Gender; + } + + var la = LA; + for (int i = 1; i <= Legal.MaxSpeciesID_8_R2; i++) + { + var e = la.Table[i]; + var fc = e.FormCount; + for (int f = 0; f < fc; f++) + { + var l = (PersonalInfoLA)la.GetFormEntry(i, f); + if (l.HP != 0) + continue; + var s = (PersonalInfoSWSH)SWSH.GetFormEntry(i, f); + l.Ability1 = s.Ability1; + l.Ability2 = s.Ability2; + l.AbilityH = s.AbilityH; + l.Gender = s.Gender; + l.EXPGrowth = s.EXPGrowth; + } + } + } + + public PersonalTable(ReadOnlySpan data, GameVersion format) + { + var get = GetConstructor(format); + int size = GetEntrySize(format); + var count = data.Length / size; + var table = new PersonalInfo[count]; + for (int i = 0; i < table.Length; i++) + table[i] = get(data.Slice(size * i, size).ToArray()); + + Table = table; + MaxSpeciesID = format.GetMaxSpeciesID(); + Game = format; + } + + private readonly PersonalInfo[] Table; + + /// + /// Gets an index from the inner array. + /// + /// Has built in length checks; returns empty (0) entry if out of range. + /// Index to retrieve + /// Requested index entry + public PersonalInfo this[int index] + { + get + { + var table = Table; + if ((uint)index >= table.Length) + return table[0]; + return table[index]; + } + set + { + var table = Table; + if ((uint)index >= table.Length) + return; + table[index] = value; + } + } + + /// + /// Alternate way of fetching . + /// + public PersonalInfo this[int species, int form] => GetFormEntry(species, form); + + /// + /// Gets the entry index for a given and . + /// + /// + /// + /// Entry index for the input criteria + public int GetFormIndex(int species, int form) + { + if ((uint)species <= MaxSpeciesID) + return Table[species].FormIndex(species, form); + Debug.WriteLine($"Requested out of bounds {nameof(species)}: {species} (max={MaxSpeciesID})"); + return 0; + } + + /// + /// Gets the entry for a given and . + /// + /// + /// + /// Entry for the input criteria + public PersonalInfo GetFormEntry(int species, int form) + { + return this[GetFormIndex(species, form)]; + } + + /// + /// Count of entries in the table, which includes default species entries and their separate entries. + /// + public int TableLength => Table.Length; + + /// + /// Maximum Species ID for the Table. + /// + public readonly int MaxSpeciesID; + + /// + /// Game(s) the originated from. + /// + public readonly GameVersion Game; + + /// + /// Gets form names for every species. + /// + /// Raw string resource (Species) for the corresponding table. + /// Max Species ID () + /// Array of species containing an array of form names for that species. + public string[][] GetFormList(string[] species, int MaxSpecies) + { + string[][] FormList = new string[MaxSpecies+1][]; + for (int i = 0; i < FormList.Length; i++) + { + int FormCount = this[i].FormCount; + FormList[i] = new string[FormCount]; + if (FormCount <= 0) + continue; + + FormList[i][0] = species[i]; + for (int j = 1; j < FormCount; j++) + FormList[i][j] = $"{species[i]} {j}"; + } + + return FormList; + } + + /// + /// Gets an arranged list of Form names and indexes for use with the individual values. + /// + /// Raw string resource (Forms) for the corresponding table. + /// Raw string resource (Species) for the corresponding table. + /// Max Species ID () + /// Pointers for base form IDs + /// Pointers for table indexes for each form + /// Sanitized list of species names, and outputs indexes for various lookup purposes. + public string[] GetPersonalEntryList(string[][] forms, string[] species, int MaxSpecies, out int[] baseForm, out int[] formVal) + { + string[] result = new string[Table.Length]; + baseForm = new int[result.Length]; + formVal = new int[result.Length]; + for (int i = 0; i <= MaxSpecies; i++) + { + result[i] = species[i]; + if (forms[i].Length == 0) + continue; + int basePtr = this[i].FormStatsIndex; + if (basePtr <= 0) + continue; + for (int j = 1; j < forms[i].Length; j++) + { + int ptr = basePtr + j - 1; + baseForm[ptr] = i; + formVal[ptr] = j; + result[ptr] = forms[i][j]; + } + } + return result; + } + + /// + /// Checks to see if either of the input type combinations exist in the table. + /// + /// Only useful for checking Generation 1 and properties. + /// First type + /// Second type + /// Indication that the combination exists in the table. + public bool IsValidTypeCombination(int type1, int type2) + { + return Array.Find(Table, p => p.IsValidTypeCombination(type1, type2)) != null; + } + + public bool IsSpeciesInGame(int species) + { + if ((uint)species > MaxSpeciesID) + return false; + var form0 = Table[species]; + if (form0.IsPresentInGame) + return true; + var fc = form0.FormCount; + for (int i = 1; i < fc; i++) + { + if (GetFormEntry(species, i).IsPresentInGame) + return true; + } + return false; + } + + public bool IsPresentInGame(int species, int form) + { + if ((uint)species > MaxSpeciesID) + return false; + var entry = GetFormEntry(species, form); + return entry.IsPresentInGame; + } } diff --git a/PKHeX.Core/Resources/text/memories/text_Memories_it.txt b/PKHeX.Core/Resources/text/memories/text_Memories_it.txt index 8109bc7f6..435e216dd 100644 --- a/PKHeX.Core/Resources/text/memories/text_Memories_it.txt +++ b/PKHeX.Core/Resources/text/memories/text_Memories_it.txt @@ -87,4 +87,4 @@ Una volta {0} si è agitato così tanto quando {1} ha controllato il suo Box, ch Una volta, mentre era in un Box, {0} ha sentito parlare dell’arrivo di {1} {2} e si è immaginato la scena. {4} {3} Una volta {0} ha litigato con {2}, suo compagno di Box, a proposito di {1}. {4} {3} Una volta, mentre era in un Box, {0} ha riflettuto sul perché {1} gli avesse dato {2}. {4} {3} -Una volta, mentre era in un Box, {0} ha fatto uno strano sogno in cui {1} usava la mossa {2}. {4} {3} \ No newline at end of file +Una volta, mentre era in un Box, {0} ha fatto uno strano sogno in cui {1} usava la mossa {2}. {4} {3} diff --git a/PKHeX.Core/Resources/text/memories/text_Memories_ja.txt b/PKHeX.Core/Resources/text/memories/text_Memories_ja.txt index 988500bb2..c19d575c5 100644 --- a/PKHeX.Core/Resources/text/memories/text_Memories_ja.txt +++ b/PKHeX.Core/Resources/text/memories/text_Memories_ja.txt @@ -87,4 +87,4 @@ {0}は ボックスの なかで {1}が {2}に とうちゃくした という はなしをきき その すがたを そうぞうしながら {3}ことが {4} {0}は ボックスで いっしょになった {2}と {1}の わだいで けんかに なってしまい {3}ことが {4} {0}は ボックスの なかで {1}に {2}を もたされた いみを かんがえながら {3}ことが {4} -{0}は ボックスに いたとき {1}が {2}を つかっている ふしぎな ゆめをみて {3}ことが {4} \ No newline at end of file +{0}は ボックスに いたとき {1}が {2}を つかっている ふしぎな ゆめをみて {3}ことが {4} diff --git a/PKHeX.Core/Resources/text/memories/text_Memories_ko.txt b/PKHeX.Core/Resources/text/memories/text_Memories_ko.txt index ec540653c..70c379c8e 100644 --- a/PKHeX.Core/Resources/text/memories/text_Memories_ko.txt +++ b/PKHeX.Core/Resources/text/memories/text_Memories_ko.txt @@ -87,4 +87,4 @@ {0} 박스 안에서 {1} {2}에 도착했다는 이야기를 듣고 그 모습을 상상하면서 {3} 게 {4} {0} 박스 안에 함께 있던 {2} {1}에 대한 이야기로 다투게 되어 {3} 게 {4} {0} 박스 안에서 {1} {2} 지니고 있게 한 의미를 생각하며 {3} 게 {4} -{0} 박스 안에 있었을 때 {1} {2} 쓰는 이상한 꿈을 꾸고 {3} 게 {4} \ No newline at end of file +{0} 박스 안에 있었을 때 {1} {2} 쓰는 이상한 꿈을 꾸고 {3} 게 {4} diff --git a/PKHeX.Core/Resources/text/program/MessageStrings_de.txt b/PKHeX.Core/Resources/text/program/MessageStrings_de.txt index d4902449d..a0f742bc2 100644 --- a/PKHeX.Core/Resources/text/program/MessageStrings_de.txt +++ b/PKHeX.Core/Resources/text/program/MessageStrings_de.txt @@ -115,7 +115,7 @@ MsgSaveBoxExportYes = Ja: Exportiere alle Boxen MsgSaveBoxExportNo = Nein: Exportiere {0} (Box {1}) MsgSaveBoxExportCancel = Abbrechen: Abbruch MsgSaveBoxExportInvalid = Ungültige Box Daten, Export nicht möglich. -MsgSaveBoxExportPathCount = Box(en) ({0} pkm) exportiert in: +MsgSaveBoxExportPathCount = Box(en) ({0} pk) exportiert in: MsgSaveBoxExportPathInvalid = Ungültiges Verzeichnis. MsgSaveBoxImportModifyIntro = Laden der PKM überschreibt: MsgSaveBoxImportModifyYes = Ja - Ändere .pk* beim setzen in das SAV diff --git a/PKHeX.Core/Resources/text/program/MessageStrings_en.txt b/PKHeX.Core/Resources/text/program/MessageStrings_en.txt index e31008a5a..baa6e366f 100644 --- a/PKHeX.Core/Resources/text/program/MessageStrings_en.txt +++ b/PKHeX.Core/Resources/text/program/MessageStrings_en.txt @@ -116,7 +116,7 @@ MsgSaveBoxExportYes = Yes: Export All Boxes MsgSaveBoxExportNo = No: Export {0} (Box {1}) MsgSaveBoxExportCancel = Cancel: Abort MsgSaveBoxExportInvalid = Invalid Box Data, unable to dump. -MsgSaveBoxExportPathCount = Dumped Box(es) ({0} pkm) to path: +MsgSaveBoxExportPathCount = Dumped Box(es) ({0} pk) to path: MsgSaveBoxExportPathInvalid = Invalid path specified. MsgSaveBoxImportModifyIntro = PKM Loading overrides: MsgSaveBoxImportModifyYes = Yes - Modify .pk* when set to SAV diff --git a/PKHeX.Core/Resources/text/program/MessageStrings_es.txt b/PKHeX.Core/Resources/text/program/MessageStrings_es.txt index 149491c19..c52630433 100644 --- a/PKHeX.Core/Resources/text/program/MessageStrings_es.txt +++ b/PKHeX.Core/Resources/text/program/MessageStrings_es.txt @@ -115,7 +115,7 @@ MsgSaveBoxExportYes = Sí: Exportar todas las cajas MsgSaveBoxExportNo = No: Exportar {0} (Caja {1}) MsgSaveBoxExportCancel = Cancelar: Abortar MsgSaveBoxExportInvalid = Datos de caja no válidos; no se ha podido exportar. -MsgSaveBoxExportPathCount = Exportado Caja(s) ({0} pkm) en ruta: +MsgSaveBoxExportPathCount = Exportado Caja(s) ({0} pk) en ruta: MsgSaveBoxExportPathInvalid = Ruta especificada no válida. MsgSaveBoxImportModifyIntro = La carga del PKM sobreescribe: MsgSaveBoxImportModifyYes = Sí - Modificar .pk* al colocar en SAV diff --git a/PKHeX.Core/Resources/text/program/MessageStrings_it.txt b/PKHeX.Core/Resources/text/program/MessageStrings_it.txt index 8fc6faa05..77b06f05f 100644 --- a/PKHeX.Core/Resources/text/program/MessageStrings_it.txt +++ b/PKHeX.Core/Resources/text/program/MessageStrings_it.txt @@ -115,7 +115,7 @@ MsgSaveBoxExportYes = Si: Esporta Tutti i Box MsgSaveBoxExportNo = No: Esporta {0} (Box {1}) MsgSaveBoxExportCancel = Annulla: Chiudi MsgSaveBoxExportInvalid = Dati del Box invalidi, impossibile eseguire il Dump. -MsgSaveBoxExportPathCount = Dump del(i) Box eseguito ({0} pkm) nel percorso: +MsgSaveBoxExportPathCount = Dump del(i) Box eseguito ({0} pk) nel percorso: MsgSaveBoxExportPathInvalid = È stato specificato un percorso invalido. MsgSaveBoxImportModifyIntro = PKM Loading overrides: MsgSaveBoxImportModifyYes = Si - Modifica .pk* quando applicato nel Salvataggio. diff --git a/PKHeX.Core/Resources/text/program/MessageStrings_ja.txt b/PKHeX.Core/Resources/text/program/MessageStrings_ja.txt index 52a9dae63..ef1018839 100644 --- a/PKHeX.Core/Resources/text/program/MessageStrings_ja.txt +++ b/PKHeX.Core/Resources/text/program/MessageStrings_ja.txt @@ -116,7 +116,7 @@ MsgSaveBoxExportYes = Yes: Export All Boxes MsgSaveBoxExportNo = No: Export {0} (Box {1}) MsgSaveBoxExportCancel = Cancel: Abort MsgSaveBoxExportInvalid = Invalid Box Data, unable to dump. -MsgSaveBoxExportPathCount = Dumped Box(es) ({0} pkm) to path: +MsgSaveBoxExportPathCount = Dumped Box(es) ({0} pk) to path: MsgSaveBoxExportPathInvalid = Invalid path specified. MsgSaveBoxImportModifyIntro = PKM Loading overrides: MsgSaveBoxImportModifyYes = Yes - Modify .pk* when set to SAV diff --git a/PKHeX.Core/Ribbons/IRibbonIndex.cs b/PKHeX.Core/Ribbons/IRibbonIndex.cs index 787fb6a37..48d2f9f7d 100644 --- a/PKHeX.Core/Ribbons/IRibbonIndex.cs +++ b/PKHeX.Core/Ribbons/IRibbonIndex.cs @@ -1,23 +1,22 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides access for reading and writing ribbon states by ribbon index within the structure. +/// +public interface IRibbonIndex { + /// Gets the state of the ribbon at the requested ribbon index. + /// + bool GetRibbon(int index); + + /// Sets the ribbon index to the provided state. + /// + /// value to set + void SetRibbon(int index, bool value = true); + /// - /// Provides access for reading and writing ribbon states by ribbon index within the structure. + /// Gets the value of the byte that has the ribbon . /// - public interface IRibbonIndex - { - /// Gets the state of the ribbon at the requested ribbon index. - /// - bool GetRibbon(int index); - - /// Sets the ribbon index to the provided state. - /// - /// value to set - void SetRibbon(int index, bool value = true); - - /// - /// Gets the value of the byte that has the ribbon . - /// - /// - int GetRibbonByte(int index); - } + /// + int GetRibbonByte(int index); } diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs index 4caae8b9f..bf42d59c7 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetCommon3.cs @@ -1,37 +1,36 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Common Ribbons introduced in Generation 3 +public interface IRibbonSetCommon3 { - /// Common Ribbons introduced in Generation 3 - public interface IRibbonSetCommon3 + bool RibbonChampionG3 { get; set; } + bool RibbonArtist { get; set; } + bool RibbonEffort { get; set; } +} + +internal static partial class RibbonExtensions +{ + private static readonly string[] RibbonSetNamesCommon3 = { - bool RibbonChampionG3 { get; set; } - bool RibbonArtist { get; set; } - bool RibbonEffort { get; set; } + nameof(IRibbonSetCommon3.RibbonChampionG3), nameof(IRibbonSetCommon3.RibbonArtist), nameof(IRibbonSetCommon3.RibbonEffort), + }; + + internal static bool[] RibbonBits(this IRibbonSetCommon3 set) + { + return new[] + { + set.RibbonChampionG3, + set.RibbonArtist, + set.RibbonEffort, + }; } - internal static partial class RibbonExtensions + internal static string[] RibbonNames(this IRibbonSetCommon3 _) => RibbonSetNamesCommon3; + + internal static void CopyRibbonSetCommon3(this IRibbonSetCommon3 set, IRibbonSetCommon3 dest) { - private static readonly string[] RibbonSetNamesCommon3 = - { - nameof(IRibbonSetCommon3.RibbonChampionG3), nameof(IRibbonSetCommon3.RibbonArtist), nameof(IRibbonSetCommon3.RibbonEffort), - }; - - internal static bool[] RibbonBits(this IRibbonSetCommon3 set) - { - return new[] - { - set.RibbonChampionG3, - set.RibbonArtist, - set.RibbonEffort, - }; - } - - internal static string[] RibbonNames(this IRibbonSetCommon3 _) => RibbonSetNamesCommon3; - - internal static void CopyRibbonSetCommon3(this IRibbonSetCommon3 set, IRibbonSetCommon3 dest) - { - dest.RibbonChampionG3 = set.RibbonChampionG3; - dest.RibbonArtist = set.RibbonArtist; - dest.RibbonEffort = set.RibbonEffort; - } + dest.RibbonChampionG3 = set.RibbonChampionG3; + dest.RibbonArtist = set.RibbonArtist; + dest.RibbonEffort = set.RibbonEffort; } } diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs index 4d45fc195..8bb77e06d 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetCommon4.cs @@ -1,99 +1,98 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Common Ribbons introduced in Generation 4 +public interface IRibbonSetCommon4 { - /// Common Ribbons introduced in Generation 4 - public interface IRibbonSetCommon4 + bool RibbonChampionSinnoh { get; set; } + bool RibbonAlert { get; set; } + bool RibbonShock { get; set; } + bool RibbonDowncast { get; set; } + bool RibbonCareless { get; set; } + bool RibbonRelax { get; set; } + bool RibbonSnooze { get; set; } + bool RibbonSmile { get; set; } + bool RibbonGorgeous { get; set; } + bool RibbonRoyal { get; set; } + bool RibbonGorgeousRoyal { get; set; } + bool RibbonFootprint { get; set; } + bool RibbonRecord { get; set; } + bool RibbonLegend { get; set; } +} + +internal static partial class RibbonExtensions +{ + private static readonly string[] RibbonSetNamesCommon4 = { - bool RibbonChampionSinnoh { get; set; } - bool RibbonAlert { get; set; } - bool RibbonShock { get; set; } - bool RibbonDowncast { get; set; } - bool RibbonCareless { get; set; } - bool RibbonRelax { get; set; } - bool RibbonSnooze { get; set; } - bool RibbonSmile { get; set; } - bool RibbonGorgeous { get; set; } - bool RibbonRoyal { get; set; } - bool RibbonGorgeousRoyal { get; set; } - bool RibbonFootprint { get; set; } - bool RibbonRecord { get; set; } - bool RibbonLegend { get; set; } + nameof(IRibbonSetCommon4.RibbonGorgeous), nameof(IRibbonSetCommon4.RibbonRoyal), nameof(IRibbonSetCommon4.RibbonGorgeousRoyal), + }; + + internal static bool[] RibbonBitsCosmetic(this IRibbonSetCommon4 set) + { + return new[] + { + set.RibbonGorgeous, + set.RibbonRoyal, + set.RibbonGorgeousRoyal, + }; } - internal static partial class RibbonExtensions + internal static string[] RibbonNamesCosmetic(this IRibbonSetCommon4 _) => RibbonSetNamesCommon4; + + private static readonly string[] RibbonSetNamesCommon4Only = { - private static readonly string[] RibbonSetNamesCommon4 = + nameof(IRibbonSetCommon4.RibbonRecord), nameof(IRibbonSetCommon4.RibbonChampionSinnoh), nameof(IRibbonSetCommon4.RibbonLegend), + }; + + internal static bool[] RibbonBitsOnly(this IRibbonSetCommon4 set) + { + return new[] { - nameof(IRibbonSetCommon4.RibbonGorgeous), nameof(IRibbonSetCommon4.RibbonRoyal), nameof(IRibbonSetCommon4.RibbonGorgeousRoyal), + set.RibbonRecord, + set.RibbonChampionSinnoh, + set.RibbonLegend, }; + } - internal static bool[] RibbonBitsCosmetic(this IRibbonSetCommon4 set) + internal static string[] RibbonNamesOnly(this IRibbonSetCommon4 _) => RibbonSetNamesCommon4Only; + + private static readonly string[] RibbonSetNamesCommon4Daily = + { + nameof(IRibbonSetCommon4.RibbonAlert), nameof(IRibbonSetCommon4.RibbonShock), + nameof(IRibbonSetCommon4.RibbonDowncast), nameof(IRibbonSetCommon4.RibbonCareless), nameof(IRibbonSetCommon4.RibbonRelax), + nameof(IRibbonSetCommon4.RibbonSnooze), nameof(IRibbonSetCommon4.RibbonSmile), + }; + + internal static bool[] RibbonBitsDaily(this IRibbonSetCommon4 set) + { + return new[] { - return new[] - { - set.RibbonGorgeous, - set.RibbonRoyal, - set.RibbonGorgeousRoyal, - }; - } - - internal static string[] RibbonNamesCosmetic(this IRibbonSetCommon4 _) => RibbonSetNamesCommon4; - - private static readonly string[] RibbonSetNamesCommon4Only = - { - nameof(IRibbonSetCommon4.RibbonRecord), nameof(IRibbonSetCommon4.RibbonChampionSinnoh), nameof(IRibbonSetCommon4.RibbonLegend), + set.RibbonAlert, + set.RibbonShock, + set.RibbonDowncast, + set.RibbonCareless, + set.RibbonRelax, + set.RibbonSnooze, + set.RibbonSmile, }; + } - internal static bool[] RibbonBitsOnly(this IRibbonSetCommon4 set) - { - return new[] - { - set.RibbonRecord, - set.RibbonChampionSinnoh, - set.RibbonLegend, - }; - } + internal static string[] RibbonNamesDaily(this IRibbonSetCommon4 _) => RibbonSetNamesCommon4Daily; - internal static string[] RibbonNamesOnly(this IRibbonSetCommon4 _) => RibbonSetNamesCommon4Only; - - private static readonly string[] RibbonSetNamesCommon4Daily = - { - nameof(IRibbonSetCommon4.RibbonAlert), nameof(IRibbonSetCommon4.RibbonShock), - nameof(IRibbonSetCommon4.RibbonDowncast), nameof(IRibbonSetCommon4.RibbonCareless), nameof(IRibbonSetCommon4.RibbonRelax), - nameof(IRibbonSetCommon4.RibbonSnooze), nameof(IRibbonSetCommon4.RibbonSmile), - }; - - internal static bool[] RibbonBitsDaily(this IRibbonSetCommon4 set) - { - return new[] - { - set.RibbonAlert, - set.RibbonShock, - set.RibbonDowncast, - set.RibbonCareless, - set.RibbonRelax, - set.RibbonSnooze, - set.RibbonSmile, - }; - } - - internal static string[] RibbonNamesDaily(this IRibbonSetCommon4 _) => RibbonSetNamesCommon4Daily; - - internal static void CopyRibbonSetCommon4(this IRibbonSetCommon4 set, IRibbonSetCommon4 dest) - { - dest.RibbonChampionSinnoh = set.RibbonChampionSinnoh; - dest.RibbonAlert = set.RibbonAlert; - dest.RibbonShock = set.RibbonShock; - dest.RibbonDowncast = set.RibbonDowncast; - dest.RibbonCareless = set.RibbonCareless; - dest.RibbonRelax = set.RibbonRelax; - dest.RibbonSnooze = set.RibbonSnooze; - dest.RibbonSmile = set.RibbonSmile; - dest.RibbonGorgeous = set.RibbonGorgeous; - dest.RibbonRoyal = set.RibbonRoyal; - dest.RibbonGorgeousRoyal = set.RibbonGorgeousRoyal; - dest.RibbonFootprint = set.RibbonFootprint; - dest.RibbonRecord = set.RibbonRecord; - dest.RibbonLegend = set.RibbonLegend; - } + internal static void CopyRibbonSetCommon4(this IRibbonSetCommon4 set, IRibbonSetCommon4 dest) + { + dest.RibbonChampionSinnoh = set.RibbonChampionSinnoh; + dest.RibbonAlert = set.RibbonAlert; + dest.RibbonShock = set.RibbonShock; + dest.RibbonDowncast = set.RibbonDowncast; + dest.RibbonCareless = set.RibbonCareless; + dest.RibbonRelax = set.RibbonRelax; + dest.RibbonSnooze = set.RibbonSnooze; + dest.RibbonSmile = set.RibbonSmile; + dest.RibbonGorgeous = set.RibbonGorgeous; + dest.RibbonRoyal = set.RibbonRoyal; + dest.RibbonGorgeousRoyal = set.RibbonGorgeousRoyal; + dest.RibbonFootprint = set.RibbonFootprint; + dest.RibbonRecord = set.RibbonRecord; + dest.RibbonLegend = set.RibbonLegend; } } diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs index 47eae3089..f6c7a86e9 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetCommon6.cs @@ -1,94 +1,93 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Common Ribbons introduced in Generation 6 +public interface IRibbonSetCommon6 { - /// Common Ribbons introduced in Generation 6 - public interface IRibbonSetCommon6 + bool RibbonChampionKalos { get; set; } + bool RibbonChampionG6Hoenn { get; set; } + bool RibbonBestFriends { get; set; } + bool RibbonTraining { get; set; } + bool RibbonBattlerSkillful { get; set; } + bool RibbonBattlerExpert { get; set; } + + bool RibbonContestStar { get; set; } + bool RibbonMasterCoolness { get; set; } + bool RibbonMasterBeauty { get; set; } + bool RibbonMasterCuteness { get; set; } + bool RibbonMasterCleverness { get; set; } + bool RibbonMasterToughness { get; set; } + + int RibbonCountMemoryContest { get; set; } + int RibbonCountMemoryBattle { get; set; } +} + +internal static partial class RibbonExtensions +{ + private static readonly string[] RibbonSetNamesCommon6Bool = { - bool RibbonChampionKalos { get; set; } - bool RibbonChampionG6Hoenn { get; set; } - bool RibbonBestFriends { get; set; } - bool RibbonTraining { get; set; } - bool RibbonBattlerSkillful { get; set; } - bool RibbonBattlerExpert { get; set; } + nameof(IRibbonSetCommon6.RibbonChampionKalos), nameof(IRibbonSetCommon6.RibbonChampionG6Hoenn), // nameof(IRibbonSetCommon6.RibbonBestFriends), + nameof(IRibbonSetCommon6.RibbonTraining), nameof(IRibbonSetCommon6.RibbonBattlerSkillful), nameof(IRibbonSetCommon6.RibbonBattlerExpert), + nameof(IRibbonSetCommon6.RibbonContestStar), nameof(IRibbonSetCommon6.RibbonMasterCoolness), nameof(IRibbonSetCommon6.RibbonMasterBeauty), + nameof(IRibbonSetCommon6.RibbonMasterCuteness), nameof(IRibbonSetCommon6.RibbonMasterCleverness), nameof(IRibbonSetCommon6.RibbonMasterToughness), + }; - bool RibbonContestStar { get; set; } - bool RibbonMasterCoolness { get; set; } - bool RibbonMasterBeauty { get; set; } - bool RibbonMasterCuteness { get; set; } - bool RibbonMasterCleverness { get; set; } - bool RibbonMasterToughness { get; set; } + private static readonly string[] RibbonSetNamesCommon6Contest = + { + nameof(IRibbonSetCommon6.RibbonMasterCoolness), nameof(IRibbonSetCommon6.RibbonMasterBeauty), + nameof(IRibbonSetCommon6.RibbonMasterCuteness), nameof(IRibbonSetCommon6.RibbonMasterCleverness), + nameof(IRibbonSetCommon6.RibbonMasterToughness), + }; - int RibbonCountMemoryContest { get; set; } - int RibbonCountMemoryBattle { get; set; } + internal static bool[] RibbonBits(this IRibbonSetCommon6 set) + { + return new[] + { + set.RibbonChampionKalos, + set.RibbonChampionG6Hoenn, + //set.RibbonBestFriends, + set.RibbonTraining, + set.RibbonBattlerSkillful, + set.RibbonBattlerExpert, + + set.RibbonContestStar, + set.RibbonMasterCoolness, + set.RibbonMasterBeauty, + set.RibbonMasterCuteness, + set.RibbonMasterCleverness, + set.RibbonMasterToughness, + }; } - internal static partial class RibbonExtensions + internal static bool[] RibbonBitsContest(this IRibbonSetCommon6 set) { - private static readonly string[] RibbonSetNamesCommon6Bool = + return new[] { - nameof(IRibbonSetCommon6.RibbonChampionKalos), nameof(IRibbonSetCommon6.RibbonChampionG6Hoenn), // nameof(IRibbonSetCommon6.RibbonBestFriends), - nameof(IRibbonSetCommon6.RibbonTraining), nameof(IRibbonSetCommon6.RibbonBattlerSkillful), nameof(IRibbonSetCommon6.RibbonBattlerExpert), - nameof(IRibbonSetCommon6.RibbonContestStar), nameof(IRibbonSetCommon6.RibbonMasterCoolness), nameof(IRibbonSetCommon6.RibbonMasterBeauty), - nameof(IRibbonSetCommon6.RibbonMasterCuteness), nameof(IRibbonSetCommon6.RibbonMasterCleverness), nameof(IRibbonSetCommon6.RibbonMasterToughness), + set.RibbonMasterCoolness, + set.RibbonMasterBeauty, + set.RibbonMasterCuteness, + set.RibbonMasterCleverness, + set.RibbonMasterToughness, }; + } - private static readonly string[] RibbonSetNamesCommon6Contest = - { - nameof(IRibbonSetCommon6.RibbonMasterCoolness), nameof(IRibbonSetCommon6.RibbonMasterBeauty), - nameof(IRibbonSetCommon6.RibbonMasterCuteness), nameof(IRibbonSetCommon6.RibbonMasterCleverness), - nameof(IRibbonSetCommon6.RibbonMasterToughness), - }; + internal static string[] RibbonNamesBool(this IRibbonSetCommon6 _) => RibbonSetNamesCommon6Bool; + internal static string[] RibbonNamesContest(this IRibbonSetCommon6 _) => RibbonSetNamesCommon6Contest; - internal static bool[] RibbonBits(this IRibbonSetCommon6 set) - { - return new[] - { - set.RibbonChampionKalos, - set.RibbonChampionG6Hoenn, - //set.RibbonBestFriends, - set.RibbonTraining, - set.RibbonBattlerSkillful, - set.RibbonBattlerExpert, - - set.RibbonContestStar, - set.RibbonMasterCoolness, - set.RibbonMasterBeauty, - set.RibbonMasterCuteness, - set.RibbonMasterCleverness, - set.RibbonMasterToughness, - }; - } - - internal static bool[] RibbonBitsContest(this IRibbonSetCommon6 set) - { - return new[] - { - set.RibbonMasterCoolness, - set.RibbonMasterBeauty, - set.RibbonMasterCuteness, - set.RibbonMasterCleverness, - set.RibbonMasterToughness, - }; - } - - internal static string[] RibbonNamesBool(this IRibbonSetCommon6 _) => RibbonSetNamesCommon6Bool; - internal static string[] RibbonNamesContest(this IRibbonSetCommon6 _) => RibbonSetNamesCommon6Contest; - - internal static void CopyRibbonSetCommon6(this IRibbonSetCommon6 set, IRibbonSetCommon6 dest) - { - dest.RibbonChampionKalos = set.RibbonChampionKalos; - dest.RibbonChampionG6Hoenn = set.RibbonChampionG6Hoenn; - dest.RibbonBestFriends = set.RibbonBestFriends; - dest.RibbonTraining = set.RibbonTraining; - dest.RibbonBattlerSkillful = set.RibbonBattlerSkillful; - dest.RibbonBattlerExpert = set.RibbonBattlerExpert; - dest.RibbonContestStar = set.RibbonContestStar; - dest.RibbonMasterCoolness = set.RibbonMasterCoolness; - dest.RibbonMasterBeauty = set.RibbonMasterBeauty; - dest.RibbonMasterCuteness = set.RibbonMasterCuteness; - dest.RibbonMasterCleverness = set.RibbonMasterCleverness; - dest.RibbonMasterToughness = set.RibbonMasterToughness; - dest.RibbonCountMemoryContest = set.RibbonCountMemoryContest; - dest.RibbonCountMemoryBattle = set.RibbonCountMemoryBattle; - } + internal static void CopyRibbonSetCommon6(this IRibbonSetCommon6 set, IRibbonSetCommon6 dest) + { + dest.RibbonChampionKalos = set.RibbonChampionKalos; + dest.RibbonChampionG6Hoenn = set.RibbonChampionG6Hoenn; + dest.RibbonBestFriends = set.RibbonBestFriends; + dest.RibbonTraining = set.RibbonTraining; + dest.RibbonBattlerSkillful = set.RibbonBattlerSkillful; + dest.RibbonBattlerExpert = set.RibbonBattlerExpert; + dest.RibbonContestStar = set.RibbonContestStar; + dest.RibbonMasterCoolness = set.RibbonMasterCoolness; + dest.RibbonMasterBeauty = set.RibbonMasterBeauty; + dest.RibbonMasterCuteness = set.RibbonMasterCuteness; + dest.RibbonMasterCleverness = set.RibbonMasterCleverness; + dest.RibbonMasterToughness = set.RibbonMasterToughness; + dest.RibbonCountMemoryContest = set.RibbonCountMemoryContest; + dest.RibbonCountMemoryBattle = set.RibbonCountMemoryBattle; } } diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs index 96a5782a3..943da5811 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetCommon7.cs @@ -1,41 +1,40 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Common Ribbons introduced in Generation 7 +public interface IRibbonSetCommon7 { - /// Common Ribbons introduced in Generation 7 - public interface IRibbonSetCommon7 + bool RibbonChampionAlola { get; set; } + bool RibbonBattleRoyale { get; set; } + bool RibbonBattleTreeGreat { get; set; } + bool RibbonBattleTreeMaster { get; set; } +} + +internal static partial class RibbonExtensions +{ + private static readonly string[] RibbonSetNamesCommon7 = { - bool RibbonChampionAlola { get; set; } - bool RibbonBattleRoyale { get; set; } - bool RibbonBattleTreeGreat { get; set; } - bool RibbonBattleTreeMaster { get; set; } + nameof(IRibbonSetCommon7.RibbonChampionAlola), nameof(IRibbonSetCommon7.RibbonBattleRoyale), + nameof(IRibbonSetCommon7.RibbonBattleTreeGreat), nameof(IRibbonSetCommon7.RibbonBattleTreeMaster), + }; + + internal static bool[] RibbonBits(this IRibbonSetCommon7 set) + { + return new[] + { + set.RibbonChampionAlola, + set.RibbonBattleRoyale, + set.RibbonBattleTreeGreat, + set.RibbonBattleTreeMaster, + }; } - internal static partial class RibbonExtensions + internal static string[] RibbonNames(this IRibbonSetCommon7 _) => RibbonSetNamesCommon7; + + internal static void CopyRibbonSetCommon7(this IRibbonSetCommon7 set, IRibbonSetCommon7 dest) { - private static readonly string[] RibbonSetNamesCommon7 = - { - nameof(IRibbonSetCommon7.RibbonChampionAlola), nameof(IRibbonSetCommon7.RibbonBattleRoyale), - nameof(IRibbonSetCommon7.RibbonBattleTreeGreat), nameof(IRibbonSetCommon7.RibbonBattleTreeMaster), - }; - - internal static bool[] RibbonBits(this IRibbonSetCommon7 set) - { - return new[] - { - set.RibbonChampionAlola, - set.RibbonBattleRoyale, - set.RibbonBattleTreeGreat, - set.RibbonBattleTreeMaster, - }; - } - - internal static string[] RibbonNames(this IRibbonSetCommon7 _) => RibbonSetNamesCommon7; - - internal static void CopyRibbonSetCommon7(this IRibbonSetCommon7 set, IRibbonSetCommon7 dest) - { - dest.RibbonChampionAlola = set.RibbonChampionAlola; - dest.RibbonBattleRoyale = set.RibbonBattleRoyale; - dest.RibbonBattleTreeGreat = set.RibbonBattleTreeGreat; - dest.RibbonBattleTreeMaster = set.RibbonBattleTreeMaster; - } + dest.RibbonChampionAlola = set.RibbonChampionAlola; + dest.RibbonBattleRoyale = set.RibbonBattleRoyale; + dest.RibbonBattleTreeGreat = set.RibbonBattleTreeGreat; + dest.RibbonBattleTreeMaster = set.RibbonBattleTreeMaster; } } diff --git a/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs b/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs index 8098e2035..4b99a9cce 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetCommon8.cs @@ -1,45 +1,44 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Common Ribbons introduced in Generation 8 +public interface IRibbonSetCommon8 { - /// Common Ribbons introduced in Generation 8 - public interface IRibbonSetCommon8 - { - bool RibbonChampionGalar { get; set; } - bool RibbonTowerMaster { get; set; } - bool RibbonMasterRank { get; set; } - bool RibbonTwinklingStar { get; set; } - bool RibbonPioneer { get; set; } - } + bool RibbonChampionGalar { get; set; } + bool RibbonTowerMaster { get; set; } + bool RibbonMasterRank { get; set; } + bool RibbonTwinklingStar { get; set; } + bool RibbonPioneer { get; set; } +} - internal static partial class RibbonExtensions +internal static partial class RibbonExtensions +{ + private static readonly string[] RibbonSetNamesCommon8 = { - private static readonly string[] RibbonSetNamesCommon8 = + nameof(IRibbonSetCommon8.RibbonChampionGalar), nameof(IRibbonSetCommon8.RibbonTowerMaster), + nameof(IRibbonSetCommon8.RibbonMasterRank), + nameof(IRibbonSetCommon8.RibbonTwinklingStar), nameof(IRibbonSetCommon8.RibbonPioneer), + }; + + internal static bool[] RibbonBits(this IRibbonSetCommon8 set) + { + return new[] { - nameof(IRibbonSetCommon8.RibbonChampionGalar), nameof(IRibbonSetCommon8.RibbonTowerMaster), - nameof(IRibbonSetCommon8.RibbonMasterRank), - nameof(IRibbonSetCommon8.RibbonTwinklingStar), nameof(IRibbonSetCommon8.RibbonPioneer), + set.RibbonChampionGalar, + set.RibbonTowerMaster, + set.RibbonMasterRank, + set.RibbonTwinklingStar, + set.RibbonPioneer, }; - - internal static bool[] RibbonBits(this IRibbonSetCommon8 set) - { - return new[] - { - set.RibbonChampionGalar, - set.RibbonTowerMaster, - set.RibbonMasterRank, - set.RibbonTwinklingStar, - set.RibbonPioneer, - }; - } - - internal static string[] RibbonNames(this IRibbonSetCommon8 _) => RibbonSetNamesCommon8; - - internal static void CopyRibbonSetCommon8(this IRibbonSetCommon8 set, IRibbonSetCommon8 dest) - { - dest.RibbonChampionGalar = set.RibbonChampionGalar; - dest.RibbonTowerMaster = set.RibbonTowerMaster; - dest.RibbonMasterRank = set.RibbonMasterRank; - dest.RibbonTwinklingStar = set.RibbonTwinklingStar; - dest.RibbonPioneer = set.RibbonPioneer; - } } -} \ No newline at end of file + + internal static string[] RibbonNames(this IRibbonSetCommon8 _) => RibbonSetNamesCommon8; + + internal static void CopyRibbonSetCommon8(this IRibbonSetCommon8 set, IRibbonSetCommon8 dest) + { + dest.RibbonChampionGalar = set.RibbonChampionGalar; + dest.RibbonTowerMaster = set.RibbonTowerMaster; + dest.RibbonMasterRank = set.RibbonMasterRank; + dest.RibbonTwinklingStar = set.RibbonTwinklingStar; + dest.RibbonPioneer = set.RibbonPioneer; + } +} diff --git a/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs b/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs index cd08c81cf..7861167d5 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetEvent3.cs @@ -1,47 +1,46 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Ribbons introduced in Generation 3 for Special Events +public interface IRibbonSetEvent3 { - /// Ribbons introduced in Generation 3 for Special Events - public interface IRibbonSetEvent3 + bool RibbonEarth { get; set; } + bool RibbonNational { get; set; } + bool RibbonCountry { get; set; } + bool RibbonChampionBattle { get; set; } + bool RibbonChampionRegional { get; set; } + bool RibbonChampionNational { get; set; } +} + +internal static partial class RibbonExtensions +{ + private static readonly string[] RibbonSetNamesEvent3 = { - bool RibbonEarth { get; set; } - bool RibbonNational { get; set; } - bool RibbonCountry { get; set; } - bool RibbonChampionBattle { get; set; } - bool RibbonChampionRegional { get; set; } - bool RibbonChampionNational { get; set; } + nameof(IRibbonSetEvent3.RibbonEarth), nameof(IRibbonSetEvent3.RibbonNational), nameof(IRibbonSetEvent3.RibbonCountry), + nameof(IRibbonSetEvent3.RibbonChampionBattle), nameof(IRibbonSetEvent3.RibbonChampionRegional), nameof(IRibbonSetEvent3.RibbonChampionNational), + }; + + internal static bool[] RibbonBits(this IRibbonSetEvent3 set) + { + return new[] + { + set.RibbonEarth, + set.RibbonNational, + set.RibbonCountry, + set.RibbonChampionBattle, + set.RibbonChampionRegional, + set.RibbonChampionNational, + }; } - internal static partial class RibbonExtensions + internal static string[] RibbonNames(this IRibbonSetEvent3 _) => RibbonSetNamesEvent3; + + internal static void CopyRibbonSetEvent3(this IRibbonSetEvent3 set, IRibbonSetEvent3 dest) { - private static readonly string[] RibbonSetNamesEvent3 = - { - nameof(IRibbonSetEvent3.RibbonEarth), nameof(IRibbonSetEvent3.RibbonNational), nameof(IRibbonSetEvent3.RibbonCountry), - nameof(IRibbonSetEvent3.RibbonChampionBattle), nameof(IRibbonSetEvent3.RibbonChampionRegional), nameof(IRibbonSetEvent3.RibbonChampionNational), - }; - - internal static bool[] RibbonBits(this IRibbonSetEvent3 set) - { - return new[] - { - set.RibbonEarth, - set.RibbonNational, - set.RibbonCountry, - set.RibbonChampionBattle, - set.RibbonChampionRegional, - set.RibbonChampionNational, - }; - } - - internal static string[] RibbonNames(this IRibbonSetEvent3 _) => RibbonSetNamesEvent3; - - internal static void CopyRibbonSetEvent3(this IRibbonSetEvent3 set, IRibbonSetEvent3 dest) - { - dest.RibbonEarth = set.RibbonEarth; - dest.RibbonNational = set.RibbonNational; - dest.RibbonCountry = set.RibbonCountry; - dest.RibbonChampionBattle = set.RibbonChampionBattle; - dest.RibbonChampionRegional = set.RibbonChampionRegional; - dest.RibbonChampionNational = set.RibbonChampionNational; - } + dest.RibbonEarth = set.RibbonEarth; + dest.RibbonNational = set.RibbonNational; + dest.RibbonCountry = set.RibbonCountry; + dest.RibbonChampionBattle = set.RibbonChampionBattle; + dest.RibbonChampionRegional = set.RibbonChampionRegional; + dest.RibbonChampionNational = set.RibbonChampionNational; } } diff --git a/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs b/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs index 6d25b7e43..048ef8f64 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetEvent4.cs @@ -1,57 +1,56 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Ribbons introduced in Generation 4 for Special Events +public interface IRibbonSetEvent4 { - /// Ribbons introduced in Generation 4 for Special Events - public interface IRibbonSetEvent4 + bool RibbonClassic { get; set; } + bool RibbonWishing { get; set; } + bool RibbonPremier { get; set; } + bool RibbonEvent { get; set; } + bool RibbonBirthday { get; set; } + bool RibbonSpecial { get; set; } + bool RibbonWorld { get; set; } + bool RibbonChampionWorld { get; set; } + bool RibbonSouvenir { get; set; } +} + +internal static partial class RibbonExtensions +{ + private static readonly string[] RibbonSetNamesEvent4 = { - bool RibbonClassic { get; set; } - bool RibbonWishing { get; set; } - bool RibbonPremier { get; set; } - bool RibbonEvent { get; set; } - bool RibbonBirthday { get; set; } - bool RibbonSpecial { get; set; } - bool RibbonWorld { get; set; } - bool RibbonChampionWorld { get; set; } - bool RibbonSouvenir { get; set; } + nameof(IRibbonSetEvent4.RibbonClassic), nameof(IRibbonSetEvent4.RibbonWishing), nameof(IRibbonSetEvent4.RibbonPremier), + nameof(IRibbonSetEvent4.RibbonEvent), nameof(IRibbonSetEvent4.RibbonBirthday), nameof(IRibbonSetEvent4.RibbonSpecial), + nameof(IRibbonSetEvent4.RibbonWorld), nameof(IRibbonSetEvent4.RibbonChampionWorld), nameof(IRibbonSetEvent4.RibbonSouvenir), + }; + + internal static bool[] RibbonBits(this IRibbonSetEvent4 set) + { + return new[] + { + set.RibbonClassic, + set.RibbonWishing, + set.RibbonPremier, + set.RibbonEvent, + set.RibbonBirthday, + set.RibbonSpecial, + set.RibbonWorld, + set.RibbonChampionWorld, + set.RibbonSouvenir, + }; } - internal static partial class RibbonExtensions + internal static string[] RibbonNames(this IRibbonSetEvent4 _) => RibbonSetNamesEvent4; + + internal static void CopyRibbonSetEvent4(this IRibbonSetEvent4 set, IRibbonSetEvent4 dest) { - private static readonly string[] RibbonSetNamesEvent4 = - { - nameof(IRibbonSetEvent4.RibbonClassic), nameof(IRibbonSetEvent4.RibbonWishing), nameof(IRibbonSetEvent4.RibbonPremier), - nameof(IRibbonSetEvent4.RibbonEvent), nameof(IRibbonSetEvent4.RibbonBirthday), nameof(IRibbonSetEvent4.RibbonSpecial), - nameof(IRibbonSetEvent4.RibbonWorld), nameof(IRibbonSetEvent4.RibbonChampionWorld), nameof(IRibbonSetEvent4.RibbonSouvenir), - }; - - internal static bool[] RibbonBits(this IRibbonSetEvent4 set) - { - return new[] - { - set.RibbonClassic, - set.RibbonWishing, - set.RibbonPremier, - set.RibbonEvent, - set.RibbonBirthday, - set.RibbonSpecial, - set.RibbonWorld, - set.RibbonChampionWorld, - set.RibbonSouvenir, - }; - } - - internal static string[] RibbonNames(this IRibbonSetEvent4 _) => RibbonSetNamesEvent4; - - internal static void CopyRibbonSetEvent4(this IRibbonSetEvent4 set, IRibbonSetEvent4 dest) - { - dest.RibbonClassic = set.RibbonClassic; - dest.RibbonWishing = set.RibbonWishing; - dest.RibbonPremier = set.RibbonPremier; - dest.RibbonEvent = set.RibbonEvent; - dest.RibbonBirthday = set.RibbonBirthday; - dest.RibbonSpecial = set.RibbonSpecial; - dest.RibbonWorld = set.RibbonWorld; - dest.RibbonChampionWorld = set.RibbonChampionWorld; - dest.RibbonSouvenir = set.RibbonSouvenir; - } + dest.RibbonClassic = set.RibbonClassic; + dest.RibbonWishing = set.RibbonWishing; + dest.RibbonPremier = set.RibbonPremier; + dest.RibbonEvent = set.RibbonEvent; + dest.RibbonBirthday = set.RibbonBirthday; + dest.RibbonSpecial = set.RibbonSpecial; + dest.RibbonWorld = set.RibbonWorld; + dest.RibbonChampionWorld = set.RibbonChampionWorld; + dest.RibbonSouvenir = set.RibbonSouvenir; } } diff --git a/PKHeX.Core/Ribbons/IRibbonSetMark8.cs b/PKHeX.Core/Ribbons/IRibbonSetMark8.cs index c567a9b67..8a20f1f8e 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetMark8.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetMark8.cs @@ -1,215 +1,214 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Marks introduced in Generation 8 +public interface IRibbonSetMark8 { - /// Marks introduced in Generation 8 - public interface IRibbonSetMark8 - { - bool RibbonMarkLunchtime { get; set; } - bool RibbonMarkSleepyTime { get; set; } - bool RibbonMarkDusk { get; set; } - bool RibbonMarkDawn { get; set; } - bool RibbonMarkCloudy { get; set; } - bool RibbonMarkRainy { get; set; } - bool RibbonMarkStormy { get; set; } - bool RibbonMarkSnowy { get; set; } - bool RibbonMarkBlizzard { get; set; } - bool RibbonMarkDry { get; set; } - bool RibbonMarkSandstorm { get; set; } - bool RibbonMarkMisty { get; set; } - bool RibbonMarkDestiny { get; set; } - bool RibbonMarkFishing { get; set; } - bool RibbonMarkCurry { get; set; } - bool RibbonMarkUncommon { get; set; } - bool RibbonMarkRare { get; set; } - bool RibbonMarkRowdy { get; set; } - bool RibbonMarkAbsentMinded { get; set; } - bool RibbonMarkJittery { get; set; } - bool RibbonMarkExcited { get; set; } - bool RibbonMarkCharismatic { get; set; } - bool RibbonMarkCalmness { get; set; } - bool RibbonMarkIntense { get; set; } - bool RibbonMarkZonedOut { get; set; } - bool RibbonMarkJoyful { get; set; } - bool RibbonMarkAngry { get; set; } - bool RibbonMarkSmiley { get; set; } - bool RibbonMarkTeary { get; set; } - bool RibbonMarkUpbeat { get; set; } - bool RibbonMarkPeeved { get; set; } - bool RibbonMarkIntellectual { get; set; } - bool RibbonMarkFerocious { get; set; } - bool RibbonMarkCrafty { get; set; } - bool RibbonMarkScowling { get; set; } - bool RibbonMarkKindly { get; set; } - bool RibbonMarkFlustered { get; set; } - bool RibbonMarkPumpedUp { get; set; } - bool RibbonMarkZeroEnergy { get; set; } - bool RibbonMarkPrideful { get; set; } - bool RibbonMarkUnsure { get; set; } - bool RibbonMarkHumble { get; set; } - bool RibbonMarkThorny { get; set; } - bool RibbonMarkVigor { get; set; } - bool RibbonMarkSlump { get; set; } + bool RibbonMarkLunchtime { get; set; } + bool RibbonMarkSleepyTime { get; set; } + bool RibbonMarkDusk { get; set; } + bool RibbonMarkDawn { get; set; } + bool RibbonMarkCloudy { get; set; } + bool RibbonMarkRainy { get; set; } + bool RibbonMarkStormy { get; set; } + bool RibbonMarkSnowy { get; set; } + bool RibbonMarkBlizzard { get; set; } + bool RibbonMarkDry { get; set; } + bool RibbonMarkSandstorm { get; set; } + bool RibbonMarkMisty { get; set; } + bool RibbonMarkDestiny { get; set; } + bool RibbonMarkFishing { get; set; } + bool RibbonMarkCurry { get; set; } + bool RibbonMarkUncommon { get; set; } + bool RibbonMarkRare { get; set; } + bool RibbonMarkRowdy { get; set; } + bool RibbonMarkAbsentMinded { get; set; } + bool RibbonMarkJittery { get; set; } + bool RibbonMarkExcited { get; set; } + bool RibbonMarkCharismatic { get; set; } + bool RibbonMarkCalmness { get; set; } + bool RibbonMarkIntense { get; set; } + bool RibbonMarkZonedOut { get; set; } + bool RibbonMarkJoyful { get; set; } + bool RibbonMarkAngry { get; set; } + bool RibbonMarkSmiley { get; set; } + bool RibbonMarkTeary { get; set; } + bool RibbonMarkUpbeat { get; set; } + bool RibbonMarkPeeved { get; set; } + bool RibbonMarkIntellectual { get; set; } + bool RibbonMarkFerocious { get; set; } + bool RibbonMarkCrafty { get; set; } + bool RibbonMarkScowling { get; set; } + bool RibbonMarkKindly { get; set; } + bool RibbonMarkFlustered { get; set; } + bool RibbonMarkPumpedUp { get; set; } + bool RibbonMarkZeroEnergy { get; set; } + bool RibbonMarkPrideful { get; set; } + bool RibbonMarkUnsure { get; set; } + bool RibbonMarkHumble { get; set; } + bool RibbonMarkThorny { get; set; } + bool RibbonMarkVigor { get; set; } + bool RibbonMarkSlump { get; set; } - bool HasMark(); + bool HasMark(); +} + +internal static partial class RibbonExtensions +{ + public static bool HasWeatherMark(this IRibbonSetMark8 m) + { + return m.RibbonMarkCloudy || m.RibbonMarkRainy || m.RibbonMarkStormy || m.RibbonMarkSnowy + || m.RibbonMarkBlizzard || m.RibbonMarkDry || m.RibbonMarkSandstorm || m.RibbonMarkMisty; } - internal static partial class RibbonExtensions + private static readonly string[] RibbonSetNamesMark8 = { - public static bool HasWeatherMark(this IRibbonSetMark8 m) - { - return m.RibbonMarkCloudy || m.RibbonMarkRainy || m.RibbonMarkStormy || m.RibbonMarkSnowy - || m.RibbonMarkBlizzard || m.RibbonMarkDry || m.RibbonMarkSandstorm || m.RibbonMarkMisty; - } + nameof(IRibbonSetMark8.RibbonMarkLunchtime), + nameof(IRibbonSetMark8.RibbonMarkSleepyTime), + nameof(IRibbonSetMark8.RibbonMarkDusk), + nameof(IRibbonSetMark8.RibbonMarkDawn), + nameof(IRibbonSetMark8.RibbonMarkCloudy), + nameof(IRibbonSetMark8.RibbonMarkRainy), + nameof(IRibbonSetMark8.RibbonMarkStormy), + nameof(IRibbonSetMark8.RibbonMarkSnowy), + nameof(IRibbonSetMark8.RibbonMarkBlizzard), + nameof(IRibbonSetMark8.RibbonMarkDry), + nameof(IRibbonSetMark8.RibbonMarkSandstorm), + nameof(IRibbonSetMark8.RibbonMarkMisty), + nameof(IRibbonSetMark8.RibbonMarkDestiny), + nameof(IRibbonSetMark8.RibbonMarkFishing), + nameof(IRibbonSetMark8.RibbonMarkCurry), + nameof(IRibbonSetMark8.RibbonMarkUncommon), + nameof(IRibbonSetMark8.RibbonMarkRare), + nameof(IRibbonSetMark8.RibbonMarkRowdy), + nameof(IRibbonSetMark8.RibbonMarkAbsentMinded), + nameof(IRibbonSetMark8.RibbonMarkJittery), + nameof(IRibbonSetMark8.RibbonMarkExcited), + nameof(IRibbonSetMark8.RibbonMarkCharismatic), + nameof(IRibbonSetMark8.RibbonMarkCalmness), + nameof(IRibbonSetMark8.RibbonMarkIntense), + nameof(IRibbonSetMark8.RibbonMarkZonedOut), + nameof(IRibbonSetMark8.RibbonMarkJoyful), + nameof(IRibbonSetMark8.RibbonMarkAngry), + nameof(IRibbonSetMark8.RibbonMarkSmiley), + nameof(IRibbonSetMark8.RibbonMarkTeary), + nameof(IRibbonSetMark8.RibbonMarkUpbeat), + nameof(IRibbonSetMark8.RibbonMarkPeeved), + nameof(IRibbonSetMark8.RibbonMarkIntellectual), + nameof(IRibbonSetMark8.RibbonMarkFerocious), + nameof(IRibbonSetMark8.RibbonMarkCrafty), + nameof(IRibbonSetMark8.RibbonMarkScowling), + nameof(IRibbonSetMark8.RibbonMarkKindly), + nameof(IRibbonSetMark8.RibbonMarkFlustered), + nameof(IRibbonSetMark8.RibbonMarkPumpedUp), + nameof(IRibbonSetMark8.RibbonMarkZeroEnergy), + nameof(IRibbonSetMark8.RibbonMarkPrideful), + nameof(IRibbonSetMark8.RibbonMarkUnsure), + nameof(IRibbonSetMark8.RibbonMarkHumble), + nameof(IRibbonSetMark8.RibbonMarkThorny), + nameof(IRibbonSetMark8.RibbonMarkVigor), + nameof(IRibbonSetMark8.RibbonMarkSlump), + }; - private static readonly string[] RibbonSetNamesMark8 = + internal static bool[] RibbonBits(this IRibbonSetMark8 set) + { + return new[] { - nameof(IRibbonSetMark8.RibbonMarkLunchtime), - nameof(IRibbonSetMark8.RibbonMarkSleepyTime), - nameof(IRibbonSetMark8.RibbonMarkDusk), - nameof(IRibbonSetMark8.RibbonMarkDawn), - nameof(IRibbonSetMark8.RibbonMarkCloudy), - nameof(IRibbonSetMark8.RibbonMarkRainy), - nameof(IRibbonSetMark8.RibbonMarkStormy), - nameof(IRibbonSetMark8.RibbonMarkSnowy), - nameof(IRibbonSetMark8.RibbonMarkBlizzard), - nameof(IRibbonSetMark8.RibbonMarkDry), - nameof(IRibbonSetMark8.RibbonMarkSandstorm), - nameof(IRibbonSetMark8.RibbonMarkMisty), - nameof(IRibbonSetMark8.RibbonMarkDestiny), - nameof(IRibbonSetMark8.RibbonMarkFishing), - nameof(IRibbonSetMark8.RibbonMarkCurry), - nameof(IRibbonSetMark8.RibbonMarkUncommon), - nameof(IRibbonSetMark8.RibbonMarkRare), - nameof(IRibbonSetMark8.RibbonMarkRowdy), - nameof(IRibbonSetMark8.RibbonMarkAbsentMinded), - nameof(IRibbonSetMark8.RibbonMarkJittery), - nameof(IRibbonSetMark8.RibbonMarkExcited), - nameof(IRibbonSetMark8.RibbonMarkCharismatic), - nameof(IRibbonSetMark8.RibbonMarkCalmness), - nameof(IRibbonSetMark8.RibbonMarkIntense), - nameof(IRibbonSetMark8.RibbonMarkZonedOut), - nameof(IRibbonSetMark8.RibbonMarkJoyful), - nameof(IRibbonSetMark8.RibbonMarkAngry), - nameof(IRibbonSetMark8.RibbonMarkSmiley), - nameof(IRibbonSetMark8.RibbonMarkTeary), - nameof(IRibbonSetMark8.RibbonMarkUpbeat), - nameof(IRibbonSetMark8.RibbonMarkPeeved), - nameof(IRibbonSetMark8.RibbonMarkIntellectual), - nameof(IRibbonSetMark8.RibbonMarkFerocious), - nameof(IRibbonSetMark8.RibbonMarkCrafty), - nameof(IRibbonSetMark8.RibbonMarkScowling), - nameof(IRibbonSetMark8.RibbonMarkKindly), - nameof(IRibbonSetMark8.RibbonMarkFlustered), - nameof(IRibbonSetMark8.RibbonMarkPumpedUp), - nameof(IRibbonSetMark8.RibbonMarkZeroEnergy), - nameof(IRibbonSetMark8.RibbonMarkPrideful), - nameof(IRibbonSetMark8.RibbonMarkUnsure), - nameof(IRibbonSetMark8.RibbonMarkHumble), - nameof(IRibbonSetMark8.RibbonMarkThorny), - nameof(IRibbonSetMark8.RibbonMarkVigor), - nameof(IRibbonSetMark8.RibbonMarkSlump), + set.RibbonMarkLunchtime, + set.RibbonMarkSleepyTime, + set.RibbonMarkDusk, + set.RibbonMarkDawn, + set.RibbonMarkCloudy, + set.RibbonMarkRainy, + set.RibbonMarkStormy, + set.RibbonMarkSnowy, + set.RibbonMarkBlizzard, + set.RibbonMarkDry, + set.RibbonMarkSandstorm, + set.RibbonMarkMisty, + set.RibbonMarkDestiny, + set.RibbonMarkFishing, + set.RibbonMarkCurry, + set.RibbonMarkUncommon, + set.RibbonMarkRare, + set.RibbonMarkRowdy, + set.RibbonMarkAbsentMinded, + set.RibbonMarkJittery, + set.RibbonMarkExcited, + set.RibbonMarkCharismatic, + set.RibbonMarkCalmness, + set.RibbonMarkIntense, + set.RibbonMarkZonedOut, + set.RibbonMarkJoyful, + set.RibbonMarkAngry, + set.RibbonMarkSmiley, + set.RibbonMarkTeary, + set.RibbonMarkUpbeat, + set.RibbonMarkPeeved, + set.RibbonMarkIntellectual, + set.RibbonMarkFerocious, + set.RibbonMarkCrafty, + set.RibbonMarkScowling, + set.RibbonMarkKindly, + set.RibbonMarkFlustered, + set.RibbonMarkPumpedUp, + set.RibbonMarkZeroEnergy, + set.RibbonMarkPrideful, + set.RibbonMarkUnsure, + set.RibbonMarkHumble, + set.RibbonMarkThorny, + set.RibbonMarkVigor, + set.RibbonMarkSlump, }; - - internal static bool[] RibbonBits(this IRibbonSetMark8 set) - { - return new[] - { - set.RibbonMarkLunchtime, - set.RibbonMarkSleepyTime, - set.RibbonMarkDusk, - set.RibbonMarkDawn, - set.RibbonMarkCloudy, - set.RibbonMarkRainy, - set.RibbonMarkStormy, - set.RibbonMarkSnowy, - set.RibbonMarkBlizzard, - set.RibbonMarkDry, - set.RibbonMarkSandstorm, - set.RibbonMarkMisty, - set.RibbonMarkDestiny, - set.RibbonMarkFishing, - set.RibbonMarkCurry, - set.RibbonMarkUncommon, - set.RibbonMarkRare, - set.RibbonMarkRowdy, - set.RibbonMarkAbsentMinded, - set.RibbonMarkJittery, - set.RibbonMarkExcited, - set.RibbonMarkCharismatic, - set.RibbonMarkCalmness, - set.RibbonMarkIntense, - set.RibbonMarkZonedOut, - set.RibbonMarkJoyful, - set.RibbonMarkAngry, - set.RibbonMarkSmiley, - set.RibbonMarkTeary, - set.RibbonMarkUpbeat, - set.RibbonMarkPeeved, - set.RibbonMarkIntellectual, - set.RibbonMarkFerocious, - set.RibbonMarkCrafty, - set.RibbonMarkScowling, - set.RibbonMarkKindly, - set.RibbonMarkFlustered, - set.RibbonMarkPumpedUp, - set.RibbonMarkZeroEnergy, - set.RibbonMarkPrideful, - set.RibbonMarkUnsure, - set.RibbonMarkHumble, - set.RibbonMarkThorny, - set.RibbonMarkVigor, - set.RibbonMarkSlump, - }; - } - - internal static string[] RibbonNames(this IRibbonSetMark8 _) => RibbonSetNamesMark8; - - internal static void CopyRibbonSetMark8(this IRibbonSetMark8 set, IRibbonSetMark8 dest) - { - dest.RibbonMarkLunchtime = set.RibbonMarkLunchtime; - dest.RibbonMarkSleepyTime = set.RibbonMarkSleepyTime; - dest.RibbonMarkDusk = set.RibbonMarkDusk; - dest.RibbonMarkDawn = set.RibbonMarkDawn; - dest.RibbonMarkCloudy = set.RibbonMarkCloudy; - dest.RibbonMarkRainy = set.RibbonMarkRainy; - dest.RibbonMarkStormy = set.RibbonMarkStormy; - dest.RibbonMarkSnowy = set.RibbonMarkSnowy; - dest.RibbonMarkBlizzard = set.RibbonMarkBlizzard; - dest.RibbonMarkDry = set.RibbonMarkDry; - dest.RibbonMarkSandstorm = set.RibbonMarkSandstorm; - dest.RibbonMarkMisty = set.RibbonMarkMisty; - dest.RibbonMarkDestiny = set.RibbonMarkDestiny; - dest.RibbonMarkFishing = set.RibbonMarkFishing; - dest.RibbonMarkCurry = set.RibbonMarkCurry; - dest.RibbonMarkUncommon = set.RibbonMarkUncommon; - dest.RibbonMarkRare = set.RibbonMarkRare; - dest.RibbonMarkRowdy = set.RibbonMarkRowdy; - dest.RibbonMarkAbsentMinded = set.RibbonMarkAbsentMinded; - dest.RibbonMarkJittery = set.RibbonMarkJittery; - dest.RibbonMarkExcited = set.RibbonMarkExcited; - dest.RibbonMarkCharismatic = set.RibbonMarkCharismatic; - dest.RibbonMarkCalmness = set.RibbonMarkCalmness; - dest.RibbonMarkIntense = set.RibbonMarkIntense; - dest.RibbonMarkZonedOut = set.RibbonMarkZonedOut; - dest.RibbonMarkJoyful = set.RibbonMarkJoyful; - dest.RibbonMarkAngry = set.RibbonMarkAngry; - dest.RibbonMarkSmiley = set.RibbonMarkSmiley; - dest.RibbonMarkTeary = set.RibbonMarkTeary; - dest.RibbonMarkUpbeat = set.RibbonMarkUpbeat; - dest.RibbonMarkPeeved = set.RibbonMarkPeeved; - dest.RibbonMarkIntellectual = set.RibbonMarkIntellectual; - dest.RibbonMarkFerocious = set.RibbonMarkFerocious; - dest.RibbonMarkCrafty = set.RibbonMarkCrafty; - dest.RibbonMarkScowling = set.RibbonMarkScowling; - dest.RibbonMarkKindly = set.RibbonMarkKindly; - dest.RibbonMarkFlustered = set.RibbonMarkFlustered; - dest.RibbonMarkPumpedUp = set.RibbonMarkPumpedUp; - dest.RibbonMarkZeroEnergy = set.RibbonMarkZeroEnergy; - dest.RibbonMarkPrideful = set.RibbonMarkPrideful; - dest.RibbonMarkUnsure = set.RibbonMarkUnsure; - dest.RibbonMarkHumble = set.RibbonMarkHumble; - dest.RibbonMarkThorny = set.RibbonMarkThorny; - dest.RibbonMarkVigor = set.RibbonMarkVigor; - dest.RibbonMarkSlump = set.RibbonMarkSlump; - } } -} \ No newline at end of file + + internal static string[] RibbonNames(this IRibbonSetMark8 _) => RibbonSetNamesMark8; + + internal static void CopyRibbonSetMark8(this IRibbonSetMark8 set, IRibbonSetMark8 dest) + { + dest.RibbonMarkLunchtime = set.RibbonMarkLunchtime; + dest.RibbonMarkSleepyTime = set.RibbonMarkSleepyTime; + dest.RibbonMarkDusk = set.RibbonMarkDusk; + dest.RibbonMarkDawn = set.RibbonMarkDawn; + dest.RibbonMarkCloudy = set.RibbonMarkCloudy; + dest.RibbonMarkRainy = set.RibbonMarkRainy; + dest.RibbonMarkStormy = set.RibbonMarkStormy; + dest.RibbonMarkSnowy = set.RibbonMarkSnowy; + dest.RibbonMarkBlizzard = set.RibbonMarkBlizzard; + dest.RibbonMarkDry = set.RibbonMarkDry; + dest.RibbonMarkSandstorm = set.RibbonMarkSandstorm; + dest.RibbonMarkMisty = set.RibbonMarkMisty; + dest.RibbonMarkDestiny = set.RibbonMarkDestiny; + dest.RibbonMarkFishing = set.RibbonMarkFishing; + dest.RibbonMarkCurry = set.RibbonMarkCurry; + dest.RibbonMarkUncommon = set.RibbonMarkUncommon; + dest.RibbonMarkRare = set.RibbonMarkRare; + dest.RibbonMarkRowdy = set.RibbonMarkRowdy; + dest.RibbonMarkAbsentMinded = set.RibbonMarkAbsentMinded; + dest.RibbonMarkJittery = set.RibbonMarkJittery; + dest.RibbonMarkExcited = set.RibbonMarkExcited; + dest.RibbonMarkCharismatic = set.RibbonMarkCharismatic; + dest.RibbonMarkCalmness = set.RibbonMarkCalmness; + dest.RibbonMarkIntense = set.RibbonMarkIntense; + dest.RibbonMarkZonedOut = set.RibbonMarkZonedOut; + dest.RibbonMarkJoyful = set.RibbonMarkJoyful; + dest.RibbonMarkAngry = set.RibbonMarkAngry; + dest.RibbonMarkSmiley = set.RibbonMarkSmiley; + dest.RibbonMarkTeary = set.RibbonMarkTeary; + dest.RibbonMarkUpbeat = set.RibbonMarkUpbeat; + dest.RibbonMarkPeeved = set.RibbonMarkPeeved; + dest.RibbonMarkIntellectual = set.RibbonMarkIntellectual; + dest.RibbonMarkFerocious = set.RibbonMarkFerocious; + dest.RibbonMarkCrafty = set.RibbonMarkCrafty; + dest.RibbonMarkScowling = set.RibbonMarkScowling; + dest.RibbonMarkKindly = set.RibbonMarkKindly; + dest.RibbonMarkFlustered = set.RibbonMarkFlustered; + dest.RibbonMarkPumpedUp = set.RibbonMarkPumpedUp; + dest.RibbonMarkZeroEnergy = set.RibbonMarkZeroEnergy; + dest.RibbonMarkPrideful = set.RibbonMarkPrideful; + dest.RibbonMarkUnsure = set.RibbonMarkUnsure; + dest.RibbonMarkHumble = set.RibbonMarkHumble; + dest.RibbonMarkThorny = set.RibbonMarkThorny; + dest.RibbonMarkVigor = set.RibbonMarkVigor; + dest.RibbonMarkSlump = set.RibbonMarkSlump; + } +} diff --git a/PKHeX.Core/Ribbons/IRibbonSetOnly3.cs b/PKHeX.Core/Ribbons/IRibbonSetOnly3.cs index 9dfb955bd..b8055f1a3 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetOnly3.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetOnly3.cs @@ -1,33 +1,32 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Ribbons that originated in Generation 3 and were only present within that Generation. +public interface IRibbonSetOnly3 { - /// Ribbons that originated in Generation 3 and were only present within that Generation. - public interface IRibbonSetOnly3 - { - int RibbonCountG3Cool { get; set; } - int RibbonCountG3Beauty { get; set; } - int RibbonCountG3Cute { get; set; } - int RibbonCountG3Smart { get; set; } - int RibbonCountG3Tough { get; set; } + int RibbonCountG3Cool { get; set; } + int RibbonCountG3Beauty { get; set; } + int RibbonCountG3Cute { get; set; } + int RibbonCountG3Smart { get; set; } + int RibbonCountG3Tough { get; set; } - bool RibbonWorld { get; set; } - bool Unused1 { get; set; } - bool Unused2 { get; set; } - bool Unused3 { get; set; } - bool Unused4 { get; set; } - } - - internal static partial class RibbonExtensions - { - private static readonly string[] RibbonSetNamesOnly3 = - { - nameof(IRibbonSetOnly3.RibbonCountG3Cool), nameof(IRibbonSetOnly3.RibbonCountG3Beauty), nameof(IRibbonSetOnly3.RibbonCountG3Cute), - nameof(IRibbonSetOnly3.RibbonCountG3Smart), nameof(IRibbonSetOnly3.RibbonCountG3Tough), - - nameof(IRibbonSetOnly3.RibbonWorld), - nameof(IRibbonSetOnly3.Unused1), nameof(IRibbonSetOnly3.Unused2), - nameof(IRibbonSetOnly3.Unused3), nameof(IRibbonSetOnly3.Unused4), - }; - - internal static string[] RibbonNames(this IRibbonSetOnly3 _) => RibbonSetNamesOnly3; - } + bool RibbonWorld { get; set; } + bool Unused1 { get; set; } + bool Unused2 { get; set; } + bool Unused3 { get; set; } + bool Unused4 { get; set; } +} + +internal static partial class RibbonExtensions +{ + private static readonly string[] RibbonSetNamesOnly3 = + { + nameof(IRibbonSetOnly3.RibbonCountG3Cool), nameof(IRibbonSetOnly3.RibbonCountG3Beauty), nameof(IRibbonSetOnly3.RibbonCountG3Cute), + nameof(IRibbonSetOnly3.RibbonCountG3Smart), nameof(IRibbonSetOnly3.RibbonCountG3Tough), + + nameof(IRibbonSetOnly3.RibbonWorld), + nameof(IRibbonSetOnly3.Unused1), nameof(IRibbonSetOnly3.Unused2), + nameof(IRibbonSetOnly3.Unused3), nameof(IRibbonSetOnly3.Unused4), + }; + + internal static string[] RibbonNames(this IRibbonSetOnly3 _) => RibbonSetNamesOnly3; } diff --git a/PKHeX.Core/Ribbons/IRibbonSetUnique3.cs b/PKHeX.Core/Ribbons/IRibbonSetUnique3.cs index ae2e03c64..b4f1d364a 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetUnique3.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetUnique3.cs @@ -1,31 +1,30 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Ribbons introduced in Generation 3 and were transferred to future Generations (4 and 5 only). +public interface IRibbonSetUnique3 { - /// Ribbons introduced in Generation 3 and were transferred to future Generations (4 and 5 only). - public interface IRibbonSetUnique3 - { - /// Ribbon awarded for clearing Hoenn's Battle Tower's Lv. 50 challenge. - bool RibbonWinning { get; set; } + /// Ribbon awarded for clearing Hoenn's Battle Tower's Lv. 50 challenge. + bool RibbonWinning { get; set; } - /// Ribbon awarded for clearing Hoenn's Battle Tower's Lv. 100 challenge. - bool RibbonVictory { get; set; } - } - - internal static partial class RibbonExtensions - { - private static readonly string[] RibbonSetNamesUnique3 = - { - nameof(IRibbonSetUnique3.RibbonWinning), nameof(IRibbonSetUnique3.RibbonVictory), - }; - - internal static bool[] RibbonBits(this IRibbonSetUnique3 set) - { - return new[] - { - set.RibbonWinning, - set.RibbonVictory, - }; - } - - internal static string[] RibbonNames(this IRibbonSetUnique3 _) => RibbonSetNamesUnique3; - } + /// Ribbon awarded for clearing Hoenn's Battle Tower's Lv. 100 challenge. + bool RibbonVictory { get; set; } +} + +internal static partial class RibbonExtensions +{ + private static readonly string[] RibbonSetNamesUnique3 = + { + nameof(IRibbonSetUnique3.RibbonWinning), nameof(IRibbonSetUnique3.RibbonVictory), + }; + + internal static bool[] RibbonBits(this IRibbonSetUnique3 set) + { + return new[] + { + set.RibbonWinning, + set.RibbonVictory, + }; + } + + internal static string[] RibbonNames(this IRibbonSetUnique3 _) => RibbonSetNamesUnique3; } diff --git a/PKHeX.Core/Ribbons/IRibbonSetUnique4.cs b/PKHeX.Core/Ribbons/IRibbonSetUnique4.cs index 74193b1bd..0f0f2f093 100644 --- a/PKHeX.Core/Ribbons/IRibbonSetUnique4.cs +++ b/PKHeX.Core/Ribbons/IRibbonSetUnique4.cs @@ -1,195 +1,194 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// Ribbons introduced in Generation 4 and were transferred to future Generations (4 and 5 only). +public interface IRibbonSetUnique4 { - /// Ribbons introduced in Generation 4 and were transferred to future Generations (4 and 5 only). - public interface IRibbonSetUnique4 - { - bool RibbonAbility { get; set; } - bool RibbonAbilityGreat { get; set; } - bool RibbonAbilityDouble { get; set; } - bool RibbonAbilityMulti { get; set; } - bool RibbonAbilityPair { get; set; } - bool RibbonAbilityWorld { get; set; } + bool RibbonAbility { get; set; } + bool RibbonAbilityGreat { get; set; } + bool RibbonAbilityDouble { get; set; } + bool RibbonAbilityMulti { get; set; } + bool RibbonAbilityPair { get; set; } + bool RibbonAbilityWorld { get; set; } - bool RibbonG3Cool { get; set; } - bool RibbonG3CoolSuper { get; set; } - bool RibbonG3CoolHyper { get; set; } - bool RibbonG3CoolMaster { get; set; } - bool RibbonG3Beauty { get; set; } - bool RibbonG3BeautySuper { get; set; } - bool RibbonG3BeautyHyper { get; set; } - bool RibbonG3BeautyMaster { get; set; } - bool RibbonG3Cute { get; set; } - bool RibbonG3CuteSuper { get; set; } - bool RibbonG3CuteHyper { get; set; } - bool RibbonG3CuteMaster { get; set; } - bool RibbonG3Smart { get; set; } - bool RibbonG3SmartSuper { get; set; } - bool RibbonG3SmartHyper { get; set; } - bool RibbonG3SmartMaster { get; set; } - bool RibbonG3Tough { get; set; } - bool RibbonG3ToughSuper { get; set; } - bool RibbonG3ToughHyper { get; set; } - bool RibbonG3ToughMaster { get; set; } + bool RibbonG3Cool { get; set; } + bool RibbonG3CoolSuper { get; set; } + bool RibbonG3CoolHyper { get; set; } + bool RibbonG3CoolMaster { get; set; } + bool RibbonG3Beauty { get; set; } + bool RibbonG3BeautySuper { get; set; } + bool RibbonG3BeautyHyper { get; set; } + bool RibbonG3BeautyMaster { get; set; } + bool RibbonG3Cute { get; set; } + bool RibbonG3CuteSuper { get; set; } + bool RibbonG3CuteHyper { get; set; } + bool RibbonG3CuteMaster { get; set; } + bool RibbonG3Smart { get; set; } + bool RibbonG3SmartSuper { get; set; } + bool RibbonG3SmartHyper { get; set; } + bool RibbonG3SmartMaster { get; set; } + bool RibbonG3Tough { get; set; } + bool RibbonG3ToughSuper { get; set; } + bool RibbonG3ToughHyper { get; set; } + bool RibbonG3ToughMaster { get; set; } - bool RibbonG4Cool { get; set; } - bool RibbonG4CoolGreat { get; set; } - bool RibbonG4CoolUltra { get; set; } - bool RibbonG4CoolMaster { get; set; } - bool RibbonG4Beauty { get; set; } - bool RibbonG4BeautyGreat { get; set; } - bool RibbonG4BeautyUltra { get; set; } - bool RibbonG4BeautyMaster { get; set; } - bool RibbonG4Cute { get; set; } - bool RibbonG4CuteGreat { get; set; } - bool RibbonG4CuteUltra { get; set; } - bool RibbonG4CuteMaster { get; set; } - bool RibbonG4Smart { get; set; } - bool RibbonG4SmartGreat { get; set; } - bool RibbonG4SmartUltra { get; set; } - bool RibbonG4SmartMaster { get; set; } - bool RibbonG4Tough { get; set; } - bool RibbonG4ToughGreat { get; set; } - bool RibbonG4ToughUltra { get; set; } - bool RibbonG4ToughMaster { get; set; } - } - - internal static partial class RibbonExtensions - { - private static readonly string[] RibbonSetNamesUnique4Ability = - { - nameof(IRibbonSetUnique4.RibbonAbility), - nameof(IRibbonSetUnique4.RibbonAbilityGreat), - nameof(IRibbonSetUnique4.RibbonAbilityDouble), - nameof(IRibbonSetUnique4.RibbonAbilityMulti), - nameof(IRibbonSetUnique4.RibbonAbilityPair), - nameof(IRibbonSetUnique4.RibbonAbilityWorld), - }; - - private static readonly string[] RibbonSetNamesUnique4Contest3 = - { - nameof(IRibbonSetUnique4.RibbonG3Cool), - nameof(IRibbonSetUnique4.RibbonG3CoolSuper), - nameof(IRibbonSetUnique4.RibbonG3CoolHyper), - nameof(IRibbonSetUnique4.RibbonG3CoolMaster), - nameof(IRibbonSetUnique4.RibbonG3Beauty), - nameof(IRibbonSetUnique4.RibbonG3BeautySuper), - nameof(IRibbonSetUnique4.RibbonG3BeautyHyper), - nameof(IRibbonSetUnique4.RibbonG3BeautyMaster), - nameof(IRibbonSetUnique4.RibbonG3Cute), - nameof(IRibbonSetUnique4.RibbonG3CuteSuper), - nameof(IRibbonSetUnique4.RibbonG3CuteHyper), - nameof(IRibbonSetUnique4.RibbonG3CuteMaster), - nameof(IRibbonSetUnique4.RibbonG3Smart), - nameof(IRibbonSetUnique4.RibbonG3SmartSuper), - nameof(IRibbonSetUnique4.RibbonG3SmartHyper), - nameof(IRibbonSetUnique4.RibbonG3SmartMaster), - nameof(IRibbonSetUnique4.RibbonG3Tough), - nameof(IRibbonSetUnique4.RibbonG3ToughSuper), - nameof(IRibbonSetUnique4.RibbonG3ToughHyper), - nameof(IRibbonSetUnique4.RibbonG3ToughMaster), - }; - - private static readonly string[] RibbonSetNamesUnique4Contest4 = - { - nameof(IRibbonSetUnique4.RibbonG4Cool), - nameof(IRibbonSetUnique4.RibbonG4CoolGreat), - nameof(IRibbonSetUnique4.RibbonG4CoolUltra), - nameof(IRibbonSetUnique4.RibbonG4CoolMaster), - nameof(IRibbonSetUnique4.RibbonG4Beauty), - nameof(IRibbonSetUnique4.RibbonG4BeautyGreat), - nameof(IRibbonSetUnique4.RibbonG4BeautyUltra), - nameof(IRibbonSetUnique4.RibbonG4BeautyMaster), - nameof(IRibbonSetUnique4.RibbonG4Cute), - nameof(IRibbonSetUnique4.RibbonG4CuteGreat), - nameof(IRibbonSetUnique4.RibbonG4CuteUltra), - nameof(IRibbonSetUnique4.RibbonG4CuteMaster), - nameof(IRibbonSetUnique4.RibbonG4Smart), - nameof(IRibbonSetUnique4.RibbonG4SmartGreat), - nameof(IRibbonSetUnique4.RibbonG4SmartUltra), - nameof(IRibbonSetUnique4.RibbonG4SmartMaster), - nameof(IRibbonSetUnique4.RibbonG4Tough), - nameof(IRibbonSetUnique4.RibbonG4ToughGreat), - nameof(IRibbonSetUnique4.RibbonG4ToughUltra), - nameof(IRibbonSetUnique4.RibbonG4ToughMaster), - }; - - internal static bool[] RibbonBitsAbility(this IRibbonSetUnique4 set) - { - return new[] - { - set.RibbonAbility, - set.RibbonAbilityGreat, - set.RibbonAbilityDouble, - set.RibbonAbilityMulti, - set.RibbonAbilityPair, - set.RibbonAbilityWorld, - }; - } - - internal static bool[] RibbonBitsContest3(this IRibbonSetUnique4 set) - { - return new[] - { - set.RibbonG3Cool, - set.RibbonG3CoolSuper, - set.RibbonG3CoolHyper, - set.RibbonG3CoolMaster, - - set.RibbonG3Beauty, - set.RibbonG3BeautySuper, - set.RibbonG3BeautyHyper, - set.RibbonG3BeautyMaster, - - set.RibbonG3Cute, - set.RibbonG3CuteSuper, - set.RibbonG3CuteHyper, - set.RibbonG3CuteMaster, - - set.RibbonG3Smart, - set.RibbonG3SmartSuper, - set.RibbonG3SmartHyper, - set.RibbonG3SmartMaster, - - set.RibbonG3Tough, - set.RibbonG3ToughSuper, - set.RibbonG3ToughHyper, - set.RibbonG3ToughMaster, - }; - } - - internal static bool[] RibbonBitsContest4(this IRibbonSetUnique4 set) - { - return new[] - { - set.RibbonG4Cool, - set.RibbonG4CoolGreat, - set.RibbonG4CoolUltra, - set.RibbonG4CoolMaster, - - set.RibbonG4Beauty, - set.RibbonG4BeautyGreat, - set.RibbonG4BeautyUltra, - set.RibbonG4BeautyMaster, - - set.RibbonG4Cute, - set.RibbonG4CuteGreat, - set.RibbonG4CuteUltra, - set.RibbonG4CuteMaster, - - set.RibbonG4Smart, - set.RibbonG4SmartGreat, - set.RibbonG4SmartUltra, - set.RibbonG4SmartMaster, - - set.RibbonG4Tough, - set.RibbonG4ToughGreat, - set.RibbonG4ToughUltra, - set.RibbonG4ToughMaster, - }; - } - - internal static string[] RibbonNamesAbility(this IRibbonSetUnique4 _) => RibbonSetNamesUnique4Ability; - internal static string[] RibbonNamesContest3(this IRibbonSetUnique4 _) => RibbonSetNamesUnique4Contest3; - internal static string[] RibbonNamesContest4(this IRibbonSetUnique4 _) => RibbonSetNamesUnique4Contest4; - } + bool RibbonG4Cool { get; set; } + bool RibbonG4CoolGreat { get; set; } + bool RibbonG4CoolUltra { get; set; } + bool RibbonG4CoolMaster { get; set; } + bool RibbonG4Beauty { get; set; } + bool RibbonG4BeautyGreat { get; set; } + bool RibbonG4BeautyUltra { get; set; } + bool RibbonG4BeautyMaster { get; set; } + bool RibbonG4Cute { get; set; } + bool RibbonG4CuteGreat { get; set; } + bool RibbonG4CuteUltra { get; set; } + bool RibbonG4CuteMaster { get; set; } + bool RibbonG4Smart { get; set; } + bool RibbonG4SmartGreat { get; set; } + bool RibbonG4SmartUltra { get; set; } + bool RibbonG4SmartMaster { get; set; } + bool RibbonG4Tough { get; set; } + bool RibbonG4ToughGreat { get; set; } + bool RibbonG4ToughUltra { get; set; } + bool RibbonG4ToughMaster { get; set; } +} + +internal static partial class RibbonExtensions +{ + private static readonly string[] RibbonSetNamesUnique4Ability = + { + nameof(IRibbonSetUnique4.RibbonAbility), + nameof(IRibbonSetUnique4.RibbonAbilityGreat), + nameof(IRibbonSetUnique4.RibbonAbilityDouble), + nameof(IRibbonSetUnique4.RibbonAbilityMulti), + nameof(IRibbonSetUnique4.RibbonAbilityPair), + nameof(IRibbonSetUnique4.RibbonAbilityWorld), + }; + + private static readonly string[] RibbonSetNamesUnique4Contest3 = + { + nameof(IRibbonSetUnique4.RibbonG3Cool), + nameof(IRibbonSetUnique4.RibbonG3CoolSuper), + nameof(IRibbonSetUnique4.RibbonG3CoolHyper), + nameof(IRibbonSetUnique4.RibbonG3CoolMaster), + nameof(IRibbonSetUnique4.RibbonG3Beauty), + nameof(IRibbonSetUnique4.RibbonG3BeautySuper), + nameof(IRibbonSetUnique4.RibbonG3BeautyHyper), + nameof(IRibbonSetUnique4.RibbonG3BeautyMaster), + nameof(IRibbonSetUnique4.RibbonG3Cute), + nameof(IRibbonSetUnique4.RibbonG3CuteSuper), + nameof(IRibbonSetUnique4.RibbonG3CuteHyper), + nameof(IRibbonSetUnique4.RibbonG3CuteMaster), + nameof(IRibbonSetUnique4.RibbonG3Smart), + nameof(IRibbonSetUnique4.RibbonG3SmartSuper), + nameof(IRibbonSetUnique4.RibbonG3SmartHyper), + nameof(IRibbonSetUnique4.RibbonG3SmartMaster), + nameof(IRibbonSetUnique4.RibbonG3Tough), + nameof(IRibbonSetUnique4.RibbonG3ToughSuper), + nameof(IRibbonSetUnique4.RibbonG3ToughHyper), + nameof(IRibbonSetUnique4.RibbonG3ToughMaster), + }; + + private static readonly string[] RibbonSetNamesUnique4Contest4 = + { + nameof(IRibbonSetUnique4.RibbonG4Cool), + nameof(IRibbonSetUnique4.RibbonG4CoolGreat), + nameof(IRibbonSetUnique4.RibbonG4CoolUltra), + nameof(IRibbonSetUnique4.RibbonG4CoolMaster), + nameof(IRibbonSetUnique4.RibbonG4Beauty), + nameof(IRibbonSetUnique4.RibbonG4BeautyGreat), + nameof(IRibbonSetUnique4.RibbonG4BeautyUltra), + nameof(IRibbonSetUnique4.RibbonG4BeautyMaster), + nameof(IRibbonSetUnique4.RibbonG4Cute), + nameof(IRibbonSetUnique4.RibbonG4CuteGreat), + nameof(IRibbonSetUnique4.RibbonG4CuteUltra), + nameof(IRibbonSetUnique4.RibbonG4CuteMaster), + nameof(IRibbonSetUnique4.RibbonG4Smart), + nameof(IRibbonSetUnique4.RibbonG4SmartGreat), + nameof(IRibbonSetUnique4.RibbonG4SmartUltra), + nameof(IRibbonSetUnique4.RibbonG4SmartMaster), + nameof(IRibbonSetUnique4.RibbonG4Tough), + nameof(IRibbonSetUnique4.RibbonG4ToughGreat), + nameof(IRibbonSetUnique4.RibbonG4ToughUltra), + nameof(IRibbonSetUnique4.RibbonG4ToughMaster), + }; + + internal static bool[] RibbonBitsAbility(this IRibbonSetUnique4 set) + { + return new[] + { + set.RibbonAbility, + set.RibbonAbilityGreat, + set.RibbonAbilityDouble, + set.RibbonAbilityMulti, + set.RibbonAbilityPair, + set.RibbonAbilityWorld, + }; + } + + internal static bool[] RibbonBitsContest3(this IRibbonSetUnique4 set) + { + return new[] + { + set.RibbonG3Cool, + set.RibbonG3CoolSuper, + set.RibbonG3CoolHyper, + set.RibbonG3CoolMaster, + + set.RibbonG3Beauty, + set.RibbonG3BeautySuper, + set.RibbonG3BeautyHyper, + set.RibbonG3BeautyMaster, + + set.RibbonG3Cute, + set.RibbonG3CuteSuper, + set.RibbonG3CuteHyper, + set.RibbonG3CuteMaster, + + set.RibbonG3Smart, + set.RibbonG3SmartSuper, + set.RibbonG3SmartHyper, + set.RibbonG3SmartMaster, + + set.RibbonG3Tough, + set.RibbonG3ToughSuper, + set.RibbonG3ToughHyper, + set.RibbonG3ToughMaster, + }; + } + + internal static bool[] RibbonBitsContest4(this IRibbonSetUnique4 set) + { + return new[] + { + set.RibbonG4Cool, + set.RibbonG4CoolGreat, + set.RibbonG4CoolUltra, + set.RibbonG4CoolMaster, + + set.RibbonG4Beauty, + set.RibbonG4BeautyGreat, + set.RibbonG4BeautyUltra, + set.RibbonG4BeautyMaster, + + set.RibbonG4Cute, + set.RibbonG4CuteGreat, + set.RibbonG4CuteUltra, + set.RibbonG4CuteMaster, + + set.RibbonG4Smart, + set.RibbonG4SmartGreat, + set.RibbonG4SmartUltra, + set.RibbonG4SmartMaster, + + set.RibbonG4Tough, + set.RibbonG4ToughGreat, + set.RibbonG4ToughUltra, + set.RibbonG4ToughMaster, + }; + } + + internal static string[] RibbonNamesAbility(this IRibbonSetUnique4 _) => RibbonSetNamesUnique4Ability; + internal static string[] RibbonNamesContest3(this IRibbonSetUnique4 _) => RibbonSetNamesUnique4Contest3; + internal static string[] RibbonNamesContest4(this IRibbonSetUnique4 _) => RibbonSetNamesUnique4Contest4; } diff --git a/PKHeX.Core/Ribbons/RibbonIndex.cs b/PKHeX.Core/Ribbons/RibbonIndex.cs index 4d4f93146..c1b57c33a 100644 --- a/PKHeX.Core/Ribbons/RibbonIndex.cs +++ b/PKHeX.Core/Ribbons/RibbonIndex.cs @@ -1,132 +1,131 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Ribbon Indexes for Generation 8 +/// +public enum RibbonIndex { - /// - /// Ribbon Indexes for Generation 8 - /// - public enum RibbonIndex - { - ChampionKalos, - ChampionG3, - ChampionSinnoh, - BestFriends, - Training, - BattlerSkillful, - BattlerExpert, - Effort, - Alert, - Shock, - Downcast, - Careless, - Relax, - Snooze, - Smile, - Gorgeous, - Royal, - GorgeousRoyal, - Artist, - Footprint, - Record, - Legend, - Country, - National, - Earth, - World, - Classic, - Premier, - Event, - Birthday, - Special, - Souvenir, - Wishing, - ChampionBattle, - ChampionRegional, - ChampionNational, - ChampionWorld, - CountMemoryContest, - CountMemoryBattle, - ChampionG6Hoenn, - ContestStar, - MasterCoolness, - MasterBeauty, - MasterCuteness, - MasterCleverness, - MasterToughness, - ChampionAlola, - BattleRoyale, - BattleTreeGreat, - BattleTreeMaster, - ChampionGalar, - TowerMaster, - MasterRank, + ChampionKalos, + ChampionG3, + ChampionSinnoh, + BestFriends, + Training, + BattlerSkillful, + BattlerExpert, + Effort, + Alert, + Shock, + Downcast, + Careless, + Relax, + Snooze, + Smile, + Gorgeous, + Royal, + GorgeousRoyal, + Artist, + Footprint, + Record, + Legend, + Country, + National, + Earth, + World, + Classic, + Premier, + Event, + Birthday, + Special, + Souvenir, + Wishing, + ChampionBattle, + ChampionRegional, + ChampionNational, + ChampionWorld, + CountMemoryContest, + CountMemoryBattle, + ChampionG6Hoenn, + ContestStar, + MasterCoolness, + MasterBeauty, + MasterCuteness, + MasterCleverness, + MasterToughness, + ChampionAlola, + BattleRoyale, + BattleTreeGreat, + BattleTreeMaster, + ChampionGalar, + TowerMaster, + MasterRank, - MarkLunchtime, - MarkSleepyTime, - MarkDusk, - MarkDawn, - MarkCloudy, - MarkRainy, - MarkStormy, - MarkSnowy, - MarkBlizzard, - MarkDry, - MarkSandstorm, - MarkMisty, - MarkDestiny, - MarkFishing, - MarkCurry, - MarkUncommon, - MarkRare, - MarkRowdy, - MarkAbsentMinded, - MarkJittery, - MarkExcited, - MarkCharismatic, - MarkCalmness, - MarkIntense, - MarkZonedOut, - MarkJoyful, - MarkAngry, - MarkSmiley, - MarkTeary, - MarkUpbeat, - MarkPeeved, - MarkIntellectual, - MarkFerocious, - MarkCrafty, - MarkScowling, - MarkKindly, - MarkFlustered, - MarkPumpedUp, - MarkZeroEnergy, - MarkPrideful, - MarkUnsure, - MarkHumble, - MarkThorny, - MarkVigor, - MarkSlump, + MarkLunchtime, + MarkSleepyTime, + MarkDusk, + MarkDawn, + MarkCloudy, + MarkRainy, + MarkStormy, + MarkSnowy, + MarkBlizzard, + MarkDry, + MarkSandstorm, + MarkMisty, + MarkDestiny, + MarkFishing, + MarkCurry, + MarkUncommon, + MarkRare, + MarkRowdy, + MarkAbsentMinded, + MarkJittery, + MarkExcited, + MarkCharismatic, + MarkCalmness, + MarkIntense, + MarkZonedOut, + MarkJoyful, + MarkAngry, + MarkSmiley, + MarkTeary, + MarkUpbeat, + MarkPeeved, + MarkIntellectual, + MarkFerocious, + MarkCrafty, + MarkScowling, + MarkKindly, + MarkFlustered, + MarkPumpedUp, + MarkZeroEnergy, + MarkPrideful, + MarkUnsure, + MarkHumble, + MarkThorny, + MarkVigor, + MarkSlump, - Pioneer, - TwinklingStar, + Pioneer, + TwinklingStar, - MAX_COUNT, - } - - public static class RibbonIndexExtensions - { - public static bool GetRibbonIndex(this IRibbonIndex x, RibbonIndex r) => x.GetRibbon((int)r); - public static void SetRibbonIndex(this IRibbonIndex x, RibbonIndex r, bool value = true) => x.SetRibbon((int)r, value); - - public static AreaWeather8 GetWeather8(this RibbonIndex x) => x switch - { - RibbonIndex.MarkCloudy => AreaWeather8.Overcast, - RibbonIndex.MarkRainy => AreaWeather8.Raining, - RibbonIndex.MarkStormy => AreaWeather8.Thunderstorm, - RibbonIndex.MarkDry => AreaWeather8.Intense_Sun, - RibbonIndex.MarkSnowy => AreaWeather8.Snowing, - RibbonIndex.MarkBlizzard => AreaWeather8.Snowstorm, - RibbonIndex.MarkSandstorm => AreaWeather8.Sandstorm, - RibbonIndex.MarkMisty => AreaWeather8.Heavy_Fog, - _ => AreaWeather8.None, - }; - } + MAX_COUNT, +} + +public static class RibbonIndexExtensions +{ + public static bool GetRibbonIndex(this IRibbonIndex x, RibbonIndex r) => x.GetRibbon((int)r); + public static void SetRibbonIndex(this IRibbonIndex x, RibbonIndex r, bool value = true) => x.SetRibbon((int)r, value); + + public static AreaWeather8 GetWeather8(this RibbonIndex x) => x switch + { + RibbonIndex.MarkCloudy => AreaWeather8.Overcast, + RibbonIndex.MarkRainy => AreaWeather8.Raining, + RibbonIndex.MarkStormy => AreaWeather8.Thunderstorm, + RibbonIndex.MarkDry => AreaWeather8.Intense_Sun, + RibbonIndex.MarkSnowy => AreaWeather8.Snowing, + RibbonIndex.MarkBlizzard => AreaWeather8.Snowstorm, + RibbonIndex.MarkSandstorm => AreaWeather8.Sandstorm, + RibbonIndex.MarkMisty => AreaWeather8.Heavy_Fog, + _ => AreaWeather8.None, + }; } diff --git a/PKHeX.Core/Ribbons/RibbonInfo.cs b/PKHeX.Core/Ribbons/RibbonInfo.cs index da45b91ce..e3fa0e55c 100644 --- a/PKHeX.Core/Ribbons/RibbonInfo.cs +++ b/PKHeX.Core/Ribbons/RibbonInfo.cs @@ -1,62 +1,61 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides ribbon information about the state of a given ribbon. +/// +public sealed class RibbonInfo { - /// - /// Provides ribbon information about the state of a given ribbon. - /// - public sealed class RibbonInfo + public const string PropertyPrefix = "Ribbon"; + + public readonly string Name; + public bool HasRibbon { get; set; } + public int RibbonCount { get; set; } + + private RibbonInfo(string name, bool hasRibbon) { - public const string PropertyPrefix = "Ribbon"; + Name = name; + HasRibbon = hasRibbon; + RibbonCount = -1; + } - public readonly string Name; - public bool HasRibbon { get; set; } - public int RibbonCount { get; set; } + private RibbonInfo(string name, int count) + { + Name = name; + HasRibbon = false; + RibbonCount = count; + } - private RibbonInfo(string name, bool hasRibbon) + public int MaxCount + { + get { - Name = name; - HasRibbon = hasRibbon; - RibbonCount = -1; - } - - private RibbonInfo(string name, int count) - { - Name = name; - HasRibbon = false; - RibbonCount = count; - } - - public int MaxCount - { - get + if (RibbonCount < 0) + return -1; + return Name switch { - if (RibbonCount < 0) - return -1; - return Name switch - { - nameof(IRibbonSetCommon6.RibbonCountMemoryContest) => 40, - nameof(IRibbonSetCommon6.RibbonCountMemoryBattle) => 8, - _ => 4, - }; - } - } - - public static IReadOnlyList GetRibbonInfo(PKM pkm) - { - // Get a list of all Ribbon Attributes in the PKM - var riblist = new List(); - var names = ReflectUtil.GetPropertiesStartWithPrefix(pkm.GetType(), PropertyPrefix); - foreach (var name in names) - { - object? RibbonValue = ReflectUtil.GetValue(pkm, name); - if (RibbonValue is int x) - riblist.Add(new RibbonInfo(name, x)); - if (RibbonValue is bool b) - riblist.Add(new RibbonInfo(name, b)); - } - - return riblist; + nameof(IRibbonSetCommon6.RibbonCountMemoryContest) => 40, + nameof(IRibbonSetCommon6.RibbonCountMemoryBattle) => 8, + _ => 4, + }; } } + + public static IReadOnlyList GetRibbonInfo(PKM pk) + { + // Get a list of all Ribbon Attributes in the PKM + var riblist = new List(); + var names = ReflectUtil.GetPropertiesStartWithPrefix(pk.GetType(), PropertyPrefix); + foreach (var name in names) + { + object? RibbonValue = ReflectUtil.GetValue(pk, name); + if (RibbonValue is int x) + riblist.Add(new RibbonInfo(name, x)); + if (RibbonValue is bool b) + riblist.Add(new RibbonInfo(name, b)); + } + + return riblist; + } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock5B2W2.cs b/PKHeX.Core/Saves/Access/ISaveBlock5B2W2.cs index bec3be81c..22a9736e4 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock5B2W2.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock5B2W2.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 5 save file. +/// +public interface ISaveBlock5B2W2 { - /// - /// Interface for Accessing named blocks within a Generation 5 save file. - /// - public interface ISaveBlock5B2W2 - { - PWTBlock5 PWT { get; } - FestaBlock5 Festa { get; } - } + PWTBlock5 PWT { get; } + FestaBlock5 Festa { get; } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock5BW.cs b/PKHeX.Core/Saves/Access/ISaveBlock5BW.cs index 46a564bf0..1f5037082 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock5BW.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock5BW.cs @@ -1,20 +1,19 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 5 save file. +/// +public interface ISaveBlock5BW { - /// - /// Interface for Accessing named blocks within a Generation 5 save file. - /// - public interface ISaveBlock5BW - { - MyItem Items { get; } - Zukan5 Zukan { get; } - Misc5 Misc { get; } - MysteryBlock5 Mystery { get; } - Daycare5 Daycare { get; } - BoxLayout5 BoxLayout { get; } - PlayerData5 PlayerData { get; } - BattleSubway5 BattleSubway { get; } - Entralink5 Entralink { get; } - Musical5 Musical { get; } - Encount5 Encount { get; } - } + MyItem Items { get; } + Zukan5 Zukan { get; } + Misc5 Misc { get; } + MysteryBlock5 Mystery { get; } + Daycare5 Daycare { get; } + BoxLayout5 BoxLayout { get; } + PlayerData5 PlayerData { get; } + BattleSubway5 BattleSubway { get; } + Entralink5 Entralink { get; } + Musical5 Musical { get; } + Encount5 Encount { get; } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock6AO.cs b/PKHeX.Core/Saves/Access/ISaveBlock6AO.cs index ff2c7a54c..ef7264337 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock6AO.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock6AO.cs @@ -1,12 +1,11 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 6 save file. +/// +public interface ISaveBlock6AO : ISaveBlock6Main { - /// - /// Interface for Accessing named blocks within a Generation 6 save file. - /// - public interface ISaveBlock6AO : ISaveBlock6Main - { - Misc6AO Misc { get; } - Zukan6AO Zukan { get; } - SecretBase6Block SecretBase { get; } - } + Misc6AO Misc { get; } + Zukan6AO Zukan { get; } + SecretBase6Block SecretBase { get; } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock6Core.cs b/PKHeX.Core/Saves/Access/ISaveBlock6Core.cs index 274559e17..ba30ab59d 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock6Core.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock6Core.cs @@ -1,17 +1,16 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 6 save file. +/// +/// Used by all Gen 6 games, including . +public interface ISaveBlock6Core { - /// - /// Interface for Accessing named blocks within a Generation 6 save file. - /// - /// Used by all Gen 6 games, including . - public interface ISaveBlock6Core - { - MyItem Items { get; } - ItemInfo6 ItemInfo { get; } - GameTime6 GameTime { get; } - Situation6 Situation { get; } - PlayTime6 Played { get; } - MyStatus6 Status { get; } - RecordBlock6 Records { get; } - } + MyItem Items { get; } + ItemInfo6 ItemInfo { get; } + GameTime6 GameTime { get; } + Situation6 Situation { get; } + PlayTime6 Played { get; } + MyStatus6 Status { get; } + RecordBlock6 Records { get; } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock6Main.cs b/PKHeX.Core/Saves/Access/ISaveBlock6Main.cs index 3f54f3e10..b425c06fd 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock6Main.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock6Main.cs @@ -1,21 +1,20 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 6 save file. +/// +/// Blocks common for and . +public interface ISaveBlock6Main : ISaveBlock6Core { - /// - /// Interface for Accessing named blocks within a Generation 6 save file. - /// - /// Blocks common for and . - public interface ISaveBlock6Main : ISaveBlock6Core - { - Puff6 Puff { get; } - OPower6 OPower { get; } - LinkBlock6 Link { get; } - BoxLayout6 BoxLayout { get; } - BattleBox6 BattleBox { get; } - ConfigSave6 Config { get; } - MysteryBlock6 MysteryGift { get; } - SuperTrainBlock SuperTrain { get; } - MaisonBlock Maison { get; } - SubEventLog6 SUBE { get; } - Encount6 Encount { get; } - } + Puff6 Puff { get; } + OPower6 OPower { get; } + LinkBlock6 Link { get; } + BoxLayout6 BoxLayout { get; } + BattleBox6 BattleBox { get; } + ConfigSave6 Config { get; } + MysteryBlock6 MysteryGift { get; } + SuperTrainBlock SuperTrain { get; } + MaisonBlock Maison { get; } + SubEventLog6 SUBE { get; } + Encount6 Encount { get; } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock6XY.cs b/PKHeX.Core/Saves/Access/ISaveBlock6XY.cs index d09c3991f..0e04d0ef0 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock6XY.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock6XY.cs @@ -1,13 +1,12 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 6 save file. +/// +/// Blocks specific for in addition to the blocks. +public interface ISaveBlock6XY : ISaveBlock6Main { - /// - /// Interface for Accessing named blocks within a Generation 6 save file. - /// - /// Blocks specific for in addition to the blocks. - public interface ISaveBlock6XY : ISaveBlock6Main - { - Misc6XY Misc { get; } - Zukan6XY Zukan { get; } - Fashion6XY Fashion { get; } - } + Misc6XY Misc { get; } + Zukan6XY Zukan { get; } + Fashion6XY Fashion { get; } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock7Main.cs b/PKHeX.Core/Saves/Access/ISaveBlock7Main.cs index 2824689b5..b8f01d076 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock7Main.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock7Main.cs @@ -1,30 +1,29 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 7 save file. +/// +/// Blocks common for and . +public interface ISaveBlock7Main { - /// - /// Interface for Accessing named blocks within a Generation 7 save file. - /// - /// Blocks common for and . - public interface ISaveBlock7Main - { - MyItem Items { get; } - MysteryBlock7 MysteryGift { get; } - PokeFinder7 PokeFinder { get; } - JoinFesta7 Festa { get; } - Daycare7 Daycare { get; } - RecordBlock6 Records { get; } - PlayTime6 Played { get; } - MyStatus7 MyStatus { get; } - FieldMoveModelSave7 Overworld { get; } - Situation7 Situation { get; } - ConfigSave7 Config { get; } - GameTime7 GameTime { get; } - Misc7 Misc { get; } - Zukan7 Zukan { get; } - BoxLayout7 BoxLayout { get; } - BattleTree7 BattleTree { get; } - ResortSave7 ResortSave { get; } - FieldMenu7 FieldMenu { get; } - FashionBlock7 Fashion { get; } - HallOfFame7 Fame { get; } - } + MyItem Items { get; } + MysteryBlock7 MysteryGift { get; } + PokeFinder7 PokeFinder { get; } + JoinFesta7 Festa { get; } + Daycare7 Daycare { get; } + RecordBlock6 Records { get; } + PlayTime6 Played { get; } + MyStatus7 MyStatus { get; } + FieldMoveModelSave7 Overworld { get; } + Situation7 Situation { get; } + ConfigSave7 Config { get; } + GameTime7 GameTime { get; } + Misc7 Misc { get; } + Zukan7 Zukan { get; } + BoxLayout7 BoxLayout { get; } + BattleTree7 BattleTree { get; } + ResortSave7 ResortSave { get; } + FieldMenu7 FieldMenu { get; } + FashionBlock7 Fashion { get; } + HallOfFame7 Fame { get; } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock7SM.cs b/PKHeX.Core/Saves/Access/ISaveBlock7SM.cs index d919afe7c..5fc476f93 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock7SM.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock7SM.cs @@ -1,10 +1,9 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 7 save file. +/// +/// Blocks specific for in addition to the blocks. +public interface ISaveBlock7SM : ISaveBlock7Main { - /// - /// Interface for Accessing named blocks within a Generation 7 save file. - /// - /// Blocks specific for in addition to the blocks. - public interface ISaveBlock7SM : ISaveBlock7Main - { - } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock7USUM.cs b/PKHeX.Core/Saves/Access/ISaveBlock7USUM.cs index 33ba00841..7447d577c 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock7USUM.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock7USUM.cs @@ -1,12 +1,11 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 7 save file. +/// +/// Blocks specific for in addition to the blocks. +public interface ISaveBlock7USUM : ISaveBlock7Main { - /// - /// Interface for Accessing named blocks within a Generation 7 save file. - /// - /// Blocks specific for in addition to the blocks. - public interface ISaveBlock7USUM : ISaveBlock7Main - { - BattleAgency7 BattleAgency { get; } - // FinderStudioSave - } + BattleAgency7 BattleAgency { get; } + // FinderStudioSave } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock7b.cs b/PKHeX.Core/Saves/Access/ISaveBlock7b.cs index 43e13c02b..8ddad3553 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock7b.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock7b.cs @@ -1,20 +1,19 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 7 LGP/E save file. +/// +/// Blocks common for +public interface ISaveBlock7b { - /// - /// Interface for Accessing named blocks within a Generation 7 LGP/E save file. - /// - /// Blocks common for - public interface ISaveBlock7b - { - MyItem Items { get; } - Misc7b Misc { get; } - Zukan7b Zukan { get; } - MyStatus7b Status { get; } - PlayTime7b Played { get; } - ConfigSave7b Config { get; } - EventWork7b EventWork { get; } - PokeListHeader Storage { get; } - WB7Records GiftRecords { get; } - CaptureRecords Captured { get; } - } + MyItem Items { get; } + Misc7b Misc { get; } + Zukan7b Zukan { get; } + MyStatus7b Status { get; } + PlayTime7b Played { get; } + ConfigSave7b Config { get; } + EventWork7b EventWork { get; } + PokeListHeader Storage { get; } + WB7Records GiftRecords { get; } + CaptureRecords Captured { get; } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock8BS.cs b/PKHeX.Core/Saves/Access/ISaveBlock8BS.cs deleted file mode 100644 index e7d76c9c7..000000000 --- a/PKHeX.Core/Saves/Access/ISaveBlock8BS.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace PKHeX.Core -{ - /// - /// Interface for Accessing named blocks within a Generation 8 BD/SP save file. - /// - public interface ISaveBlock8BS - { - } -} diff --git a/PKHeX.Core/Saves/Access/ISaveBlock8Main.cs b/PKHeX.Core/Saves/Access/ISaveBlock8Main.cs index 632ec0c73..8f240b36f 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock8Main.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock8Main.cs @@ -1,26 +1,25 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 8 save file. +/// +public interface ISaveBlock8Main { - /// - /// Interface for Accessing named blocks within a Generation 8 save file. - /// - public interface ISaveBlock8Main - { - Box8 BoxInfo { get; } - Party8 PartyInfo { get; } - MyItem Items { get; } - MyStatus8 MyStatus { get; } - Misc8 Misc { get; } - Zukan8 Zukan { get; } - BoxLayout8 BoxLayout { get; } - PlayTime8 Played { get; } - Fused8 Fused { get; } - Daycare8 Daycare { get; } - Record8 Records { get; } - TrainerCard8 TrainerCard { get; } - RaidSpawnList8 Raid { get; } - RaidSpawnList8 RaidArmor { get; } - RaidSpawnList8 RaidCrown { get; } - TitleScreen8 TitleScreen { get; } - TeamIndexes8 TeamIndexes { get; } - } + Box8 BoxInfo { get; } + Party8 PartyInfo { get; } + MyItem Items { get; } + MyStatus8 MyStatus { get; } + Misc8 Misc { get; } + Zukan8 Zukan { get; } + BoxLayout8 BoxLayout { get; } + PlayTime8 Played { get; } + Fused8 Fused { get; } + Daycare8 Daycare { get; } + Record8 Records { get; } + TrainerCard8 TrainerCard { get; } + RaidSpawnList8 Raid { get; } + RaidSpawnList8 RaidArmor { get; } + RaidSpawnList8 RaidCrown { get; } + TitleScreen8 TitleScreen { get; } + TeamIndexes8 TeamIndexes { get; } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlock8SWSH.cs b/PKHeX.Core/Saves/Access/ISaveBlock8SWSH.cs index d5ccdf9a9..57b8382ca 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlock8SWSH.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlock8SWSH.cs @@ -1,10 +1,9 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a Generation 8 save file. +/// +/// Blocks specific for in addition to the blocks. +public interface ISaveBlock8SWSH : ISaveBlock8Main { - /// - /// Interface for Accessing named blocks within a Generation 8 save file. - /// - /// Blocks specific for in addition to the blocks. - public interface ISaveBlock8SWSH : ISaveBlock8Main - { - } } diff --git a/PKHeX.Core/Saves/Access/ISaveBlockAccessor.cs b/PKHeX.Core/Saves/Access/ISaveBlockAccessor.cs index 51dc5d9da..7b8d30217 100644 --- a/PKHeX.Core/Saves/Access/ISaveBlockAccessor.cs +++ b/PKHeX.Core/Saves/Access/ISaveBlockAccessor.cs @@ -1,15 +1,14 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Interface for Accessing named blocks within a save file. +/// +public interface ISaveBlockAccessor { /// - /// Interface for Accessing named blocks within a save file. + /// List of all known block details. /// - public interface ISaveBlockAccessor - { - /// - /// List of all known block details. - /// - IReadOnlyList BlockInfo { get; } - } + IReadOnlyList BlockInfo { get; } } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor5B2W2.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor5B2W2.cs index 310678867..b7c9cac8a 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor5B2W2.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor5B2W2.cs @@ -1,120 +1,119 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information for Accessing individual blocks within a . +/// +public sealed class SaveBlockAccessor5B2W2 : ISaveBlockAccessor, ISaveBlock5BW, ISaveBlock5B2W2 { - /// - /// Information for Accessing individual blocks within a . - /// - public sealed class SaveBlockAccessor5B2W2 : ISaveBlockAccessor, ISaveBlock5BW, ISaveBlock5B2W2 + private static readonly BlockInfoNDS[] BlocksB2W2 = { - private static readonly BlockInfoNDS[] BlocksB2W2 = - { - new(0x00000, 0x03e0, 0x003E2, 0x25F00), // 00 Box Names - new(0x00400, 0x0ff0, 0x013F2, 0x25F02), // 01 Box 1 - new(0x01400, 0x0ff0, 0x023F2, 0x25F04), // 02 Box 2 - new(0x02400, 0x0ff0, 0x033F2, 0x25F06), // 03 Box 3 - new(0x03400, 0x0ff0, 0x043F2, 0x25F08), // 04 Box 4 - new(0x04400, 0x0ff0, 0x053F2, 0x25F0A), // 05 Box 5 - new(0x05400, 0x0ff0, 0x063F2, 0x25F0C), // 06 Box 6 - new(0x06400, 0x0ff0, 0x073F2, 0x25F0E), // 07 Box 7 - new(0x07400, 0x0ff0, 0x083F2, 0x25F10), // 08 Box 8 - new(0x08400, 0x0ff0, 0x093F2, 0x25F12), // 09 Box 9 - new(0x09400, 0x0ff0, 0x0A3F2, 0x25F14), // 10 Box 10 - new(0x0A400, 0x0ff0, 0x0B3F2, 0x25F16), // 11 Box 11 - new(0x0B400, 0x0ff0, 0x0C3F2, 0x25F18), // 12 Box 12 - new(0x0C400, 0x0ff0, 0x0D3F2, 0x25F1A), // 13 Box 13 - new(0x0D400, 0x0ff0, 0x0E3F2, 0x25F1C), // 14 Box 14 - new(0x0E400, 0x0ff0, 0x0F3F2, 0x25F1E), // 15 Box 15 - new(0x0F400, 0x0ff0, 0x103F2, 0x25F20), // 16 Box 16 - new(0x10400, 0x0ff0, 0x113F2, 0x25F22), // 17 Box 17 - new(0x11400, 0x0ff0, 0x123F2, 0x25F24), // 18 Box 18 - new(0x12400, 0x0ff0, 0x133F2, 0x25F26), // 19 Box 19 - new(0x13400, 0x0ff0, 0x143F2, 0x25F28), // 20 Box 20 - new(0x14400, 0x0ff0, 0x153F2, 0x25F2A), // 21 Box 21 - new(0x15400, 0x0ff0, 0x163F2, 0x25F2C), // 22 Box 22 - new(0x16400, 0x0ff0, 0x173F2, 0x25F2E), // 23 Box 23 - new(0x17400, 0x0ff0, 0x183F2, 0x25F30), // 24 Box 24 - new(0x18400, 0x09ec, 0x18DEE, 0x25F32), // 25 Inventory - new(0x18E00, 0x0534, 0x19336, 0x25F34), // 26 Party Pokemon - new(0x19400, 0x00b0, 0x194B2, 0x25F36), // 27 Trainer Data - new(0x19500, 0x00a8, 0x195AA, 0x25F38), // 28 Trainer Position - new(0x19600, 0x1338, 0x1A93A, 0x25F3A), // 29 Unity Tower and survey stuff - new(0x1AA00, 0x07c4, 0x1B1C6, 0x25F3C), // 30 Pal Pad Player Data - new(0x1B200, 0x0d54, 0x1BF56, 0x25F3E), // 31 Pal Pad Friend Data - new(0x1C000, 0x0094, 0x1C096, 0x25F40), // 32 Options / Skin Info - new(0x1C100, 0x0658, 0x1C75A, 0x25F42), // 33 Trainer Card - new(0x1C800, 0x0a94, 0x1D296, 0x25F44), // 34 Mystery Gift - new(0x1D300, 0x01ac, 0x1D4AE, 0x25F46), // 35 Dream World Stuff (Catalog) - new(0x1D500, 0x03ec, 0x1D8EE, 0x25F48), // 36 Chatter - new(0x1D900, 0x005c, 0x1D95E, 0x25F4A), // 37 Adventure data - new(0x1DA00, 0x01e0, 0x1DBE2, 0x25F4C), // 38 Trainer Card Records - new(0x1DC00, 0x00a8, 0x1DCAA, 0x25F4E), // 39 ??? - new(0x1DD00, 0x0460, 0x1E162, 0x25F50), // 40 Mail - new(0x1E200, 0x1400, 0x1F602, 0x25F52), // 41 Overworld State - new(0x1F700, 0x02a4, 0x1F9A6, 0x25F54), // 42 Musical - new(0x1FA00, 0x00e0, 0x1FAE2, 0x25F56), // 43 White Forest + Black City Data, Fused Reshiram/Zekrom Storage - new(0x1FB00, 0x034c, 0x1FE4E, 0x25F58), // 44 IR - new(0x1FF00, 0x04e0, 0x203E2, 0x25F5A), // 45 EventWork - new(0x20400, 0x00f8, 0x204FA, 0x25F5C), // 46 GTS - new(0x20500, 0x02fc, 0x207FE, 0x25F5E), // 47 Regulation Tournament - new(0x20800, 0x0094, 0x20896, 0x25F60), // 48 Gimmick - new(0x20900, 0x035c, 0x20C5E, 0x25F62), // 49 Battle Box - new(0x20D00, 0x01d4, 0x20ED6, 0x25F64), // 50 Daycare - new(0x20F00, 0x01e0, 0x210E2, 0x25F66), // 51 Strength Boulder Status - new(0x21100, 0x00f0, 0x211F2, 0x25F68), // 52 Misc (Badge Flags, Money, Trainer Sayings) - new(0x21200, 0x01b4, 0x213B6, 0x25F6A), // 53 Entralink (Level & Powers etc) - new(0x21400, 0x04dc, 0x218DE, 0x25F6C), // 54 Pokedex - new(0x21900, 0x0034, 0x21936, 0x25F6E), // 55 Encount (Swarm and other overworld info - 2C - swarm, 2D - repel steps, 2E repel type) - new(0x21A00, 0x003c, 0x21A3E, 0x25F70), // 56 Battle Subway Play Info - new(0x21B00, 0x01ac, 0x21CAE, 0x25F72), // 57 Battle Subway Score Info - new(0x21D00, 0x0b90, 0x22892, 0x25F74), // 58 Battle Subway WiFi Info - new(0x22900, 0x00ac, 0x229AE, 0x25F76), // 59 Online Records - new(0x22A00, 0x0850, 0x23252, 0x25F78), // 60 Entralink Forest pokémon data - new(0x23300, 0x0284, 0x23586, 0x25F7A), // 61 Answered Questions - new(0x23600, 0x0010, 0x23612, 0x25F7C), // 62 Unity Tower - new(0x23700, 0x00a8, 0x237AA, 0x25F7E), // 63 PWT related data - new(0x23800, 0x016c, 0x2396E, 0x25F80), // 64 ??? - new(0x23A00, 0x0080, 0x23A82, 0x25F82), // 65 ??? - new(0x23B00, 0x00fc, 0x23BFE, 0x25F84), // 66 Hollow/Rival Block - new(0x23C00, 0x16a8, 0x252AA, 0x25F86), // 67 Join Avenue Block - new(0x25300, 0x0498, 0x2579A, 0x25F88), // 68 Medal - new(0x25800, 0x0060, 0x25862, 0x25F8A), // 69 Key-related data - new(0x25900, 0x00fc, 0x259FE, 0x25F8C), // 70 Festa Missions - new(0x25A00, 0x03e4, 0x25DE6, 0x25F8E), // 71 Pokestar Studios - new(0x25E00, 0x00f0, 0x25EF2, 0x25F90), // 72 ??? - new(0x25F00, 0x0094, 0x25FA2, 0x25FA2), // 73 Checksum Block - }; + new(0x00000, 0x03e0, 0x003E2, 0x25F00), // 00 Box Names + new(0x00400, 0x0ff0, 0x013F2, 0x25F02), // 01 Box 1 + new(0x01400, 0x0ff0, 0x023F2, 0x25F04), // 02 Box 2 + new(0x02400, 0x0ff0, 0x033F2, 0x25F06), // 03 Box 3 + new(0x03400, 0x0ff0, 0x043F2, 0x25F08), // 04 Box 4 + new(0x04400, 0x0ff0, 0x053F2, 0x25F0A), // 05 Box 5 + new(0x05400, 0x0ff0, 0x063F2, 0x25F0C), // 06 Box 6 + new(0x06400, 0x0ff0, 0x073F2, 0x25F0E), // 07 Box 7 + new(0x07400, 0x0ff0, 0x083F2, 0x25F10), // 08 Box 8 + new(0x08400, 0x0ff0, 0x093F2, 0x25F12), // 09 Box 9 + new(0x09400, 0x0ff0, 0x0A3F2, 0x25F14), // 10 Box 10 + new(0x0A400, 0x0ff0, 0x0B3F2, 0x25F16), // 11 Box 11 + new(0x0B400, 0x0ff0, 0x0C3F2, 0x25F18), // 12 Box 12 + new(0x0C400, 0x0ff0, 0x0D3F2, 0x25F1A), // 13 Box 13 + new(0x0D400, 0x0ff0, 0x0E3F2, 0x25F1C), // 14 Box 14 + new(0x0E400, 0x0ff0, 0x0F3F2, 0x25F1E), // 15 Box 15 + new(0x0F400, 0x0ff0, 0x103F2, 0x25F20), // 16 Box 16 + new(0x10400, 0x0ff0, 0x113F2, 0x25F22), // 17 Box 17 + new(0x11400, 0x0ff0, 0x123F2, 0x25F24), // 18 Box 18 + new(0x12400, 0x0ff0, 0x133F2, 0x25F26), // 19 Box 19 + new(0x13400, 0x0ff0, 0x143F2, 0x25F28), // 20 Box 20 + new(0x14400, 0x0ff0, 0x153F2, 0x25F2A), // 21 Box 21 + new(0x15400, 0x0ff0, 0x163F2, 0x25F2C), // 22 Box 22 + new(0x16400, 0x0ff0, 0x173F2, 0x25F2E), // 23 Box 23 + new(0x17400, 0x0ff0, 0x183F2, 0x25F30), // 24 Box 24 + new(0x18400, 0x09ec, 0x18DEE, 0x25F32), // 25 Inventory + new(0x18E00, 0x0534, 0x19336, 0x25F34), // 26 Party Pokemon + new(0x19400, 0x00b0, 0x194B2, 0x25F36), // 27 Trainer Data + new(0x19500, 0x00a8, 0x195AA, 0x25F38), // 28 Trainer Position + new(0x19600, 0x1338, 0x1A93A, 0x25F3A), // 29 Unity Tower and survey stuff + new(0x1AA00, 0x07c4, 0x1B1C6, 0x25F3C), // 30 Pal Pad Player Data + new(0x1B200, 0x0d54, 0x1BF56, 0x25F3E), // 31 Pal Pad Friend Data + new(0x1C000, 0x0094, 0x1C096, 0x25F40), // 32 Options / Skin Info + new(0x1C100, 0x0658, 0x1C75A, 0x25F42), // 33 Trainer Card + new(0x1C800, 0x0a94, 0x1D296, 0x25F44), // 34 Mystery Gift + new(0x1D300, 0x01ac, 0x1D4AE, 0x25F46), // 35 Dream World Stuff (Catalog) + new(0x1D500, 0x03ec, 0x1D8EE, 0x25F48), // 36 Chatter + new(0x1D900, 0x005c, 0x1D95E, 0x25F4A), // 37 Adventure data + new(0x1DA00, 0x01e0, 0x1DBE2, 0x25F4C), // 38 Trainer Card Records + new(0x1DC00, 0x00a8, 0x1DCAA, 0x25F4E), // 39 ??? + new(0x1DD00, 0x0460, 0x1E162, 0x25F50), // 40 Mail + new(0x1E200, 0x1400, 0x1F602, 0x25F52), // 41 Overworld State + new(0x1F700, 0x02a4, 0x1F9A6, 0x25F54), // 42 Musical + new(0x1FA00, 0x00e0, 0x1FAE2, 0x25F56), // 43 White Forest + Black City Data, Fused Reshiram/Zekrom Storage + new(0x1FB00, 0x034c, 0x1FE4E, 0x25F58), // 44 IR + new(0x1FF00, 0x04e0, 0x203E2, 0x25F5A), // 45 EventWork + new(0x20400, 0x00f8, 0x204FA, 0x25F5C), // 46 GTS + new(0x20500, 0x02fc, 0x207FE, 0x25F5E), // 47 Regulation Tournament + new(0x20800, 0x0094, 0x20896, 0x25F60), // 48 Gimmick + new(0x20900, 0x035c, 0x20C5E, 0x25F62), // 49 Battle Box + new(0x20D00, 0x01d4, 0x20ED6, 0x25F64), // 50 Daycare + new(0x20F00, 0x01e0, 0x210E2, 0x25F66), // 51 Strength Boulder Status + new(0x21100, 0x00f0, 0x211F2, 0x25F68), // 52 Misc (Badge Flags, Money, Trainer Sayings) + new(0x21200, 0x01b4, 0x213B6, 0x25F6A), // 53 Entralink (Level & Powers etc) + new(0x21400, 0x04dc, 0x218DE, 0x25F6C), // 54 Pokedex + new(0x21900, 0x0034, 0x21936, 0x25F6E), // 55 Encount (Swarm and other overworld info - 2C - swarm, 2D - repel steps, 2E repel type) + new(0x21A00, 0x003c, 0x21A3E, 0x25F70), // 56 Battle Subway Play Info + new(0x21B00, 0x01ac, 0x21CAE, 0x25F72), // 57 Battle Subway Score Info + new(0x21D00, 0x0b90, 0x22892, 0x25F74), // 58 Battle Subway WiFi Info + new(0x22900, 0x00ac, 0x229AE, 0x25F76), // 59 Online Records + new(0x22A00, 0x0850, 0x23252, 0x25F78), // 60 Entralink Forest pokémon data + new(0x23300, 0x0284, 0x23586, 0x25F7A), // 61 Answered Questions + new(0x23600, 0x0010, 0x23612, 0x25F7C), // 62 Unity Tower + new(0x23700, 0x00a8, 0x237AA, 0x25F7E), // 63 PWT related data + new(0x23800, 0x016c, 0x2396E, 0x25F80), // 64 ??? + new(0x23A00, 0x0080, 0x23A82, 0x25F82), // 65 ??? + new(0x23B00, 0x00fc, 0x23BFE, 0x25F84), // 66 Hollow/Rival Block + new(0x23C00, 0x16a8, 0x252AA, 0x25F86), // 67 Join Avenue Block + new(0x25300, 0x0498, 0x2579A, 0x25F88), // 68 Medal + new(0x25800, 0x0060, 0x25862, 0x25F8A), // 69 Key-related data + new(0x25900, 0x00fc, 0x259FE, 0x25F8C), // 70 Festa Missions + new(0x25A00, 0x03e4, 0x25DE6, 0x25F8E), // 71 Pokestar Studios + new(0x25E00, 0x00f0, 0x25EF2, 0x25F90), // 72 ??? + new(0x25F00, 0x0094, 0x25FA2, 0x25FA2), // 73 Checksum Block + }; - public SaveBlockAccessor5B2W2(SAV5B2W2 sav) - { - BoxLayout = new BoxLayout5(sav, 0x00000); - Items = new MyItem5B2W2(sav, 0x18400); - PlayerData = new PlayerData5(sav, 0x19400); - Mystery = new MysteryBlock5(sav, 0x1C800); - Musical = new Musical5(sav, 0x1F700); - Daycare = new Daycare5(sav, 0x20D00); - Misc = new Misc5B2W2(sav, 0x21100); - Entralink = new Entralink5B2W2(sav, 0x21200); - Zukan = new Zukan5(sav, 0x21400, 0x328); // form flags size is + 8 from bw with new forms (therians) - Encount = new Encount5B2W2(sav, 0x21900); - BattleSubway = new BattleSubway5(sav, 0x21B00); - PWT = new PWTBlock5(sav, 0x23700); - Festa = new FestaBlock5(sav, 0x25900); - } - - public IReadOnlyList BlockInfo => BlocksB2W2; - public MyItem Items { get; } - public Zukan5 Zukan { get; } - public Misc5 Misc { get; } - public MysteryBlock5 Mystery { get; } - public Daycare5 Daycare { get; } - public BoxLayout5 BoxLayout { get; } - public PlayerData5 PlayerData { get; } - public BattleSubway5 BattleSubway { get; } - public PWTBlock5 PWT { get; } - public Entralink5 Entralink { get; } - public FestaBlock5 Festa { get; } - public Musical5 Musical { get; } - public Encount5 Encount { get; } + public SaveBlockAccessor5B2W2(SAV5B2W2 sav) + { + BoxLayout = new BoxLayout5(sav, 0x00000); + Items = new MyItem5B2W2(sav, 0x18400); + PlayerData = new PlayerData5(sav, 0x19400); + Mystery = new MysteryBlock5(sav, 0x1C800); + Musical = new Musical5(sav, 0x1F700); + Daycare = new Daycare5(sav, 0x20D00); + Misc = new Misc5B2W2(sav, 0x21100); + Entralink = new Entralink5B2W2(sav, 0x21200); + Zukan = new Zukan5(sav, 0x21400, 0x328); // form flags size is + 8 from bw with new forms (therians) + Encount = new Encount5B2W2(sav, 0x21900); + BattleSubway = new BattleSubway5(sav, 0x21B00); + PWT = new PWTBlock5(sav, 0x23700); + Festa = new FestaBlock5(sav, 0x25900); } + + public IReadOnlyList BlockInfo => BlocksB2W2; + public MyItem Items { get; } + public Zukan5 Zukan { get; } + public Misc5 Misc { get; } + public MysteryBlock5 Mystery { get; } + public Daycare5 Daycare { get; } + public BoxLayout5 BoxLayout { get; } + public PlayerData5 PlayerData { get; } + public BattleSubway5 BattleSubway { get; } + public PWTBlock5 PWT { get; } + public Entralink5 Entralink { get; } + public FestaBlock5 Festa { get; } + public Musical5 Musical { get; } + public Encount5 Encount { get; } } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor5BW.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor5BW.cs index 76b4dbfbe..e4b9e7813 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor5BW.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor5BW.cs @@ -1,112 +1,111 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information for Accessing individual blocks within a . +/// +public sealed class SaveBlockAccessor5BW : ISaveBlockAccessor, ISaveBlock5BW { - /// - /// Information for Accessing individual blocks within a . - /// - public sealed class SaveBlockAccessor5BW : ISaveBlockAccessor, ISaveBlock5BW + private static readonly BlockInfoNDS[] BlocksBW = { - private static readonly BlockInfoNDS[] BlocksBW = - { - new(0x00000, 0x03E0, 0x003E2, 0x23F00), // 00 Box Names - new(0x00400, 0x0FF0, 0x013F2, 0x23F02), // 01 Box 1 - new(0x01400, 0x0FF0, 0x023F2, 0x23F04), // 02 Box 2 - new(0x02400, 0x0FF0, 0x033F2, 0x23F06), // 03 Box 3 - new(0x03400, 0x0FF0, 0x043F2, 0x23F08), // 04 Box 4 - new(0x04400, 0x0FF0, 0x053F2, 0x23F0A), // 05 Box 5 - new(0x05400, 0x0FF0, 0x063F2, 0x23F0C), // 06 Box 6 - new(0x06400, 0x0FF0, 0x073F2, 0x23F0E), // 07 Box 7 - new(0x07400, 0x0FF0, 0x083F2, 0x23F10), // 08 Box 8 - new(0x08400, 0x0FF0, 0x093F2, 0x23F12), // 09 Box 9 - new(0x09400, 0x0FF0, 0x0A3F2, 0x23F14), // 10 Box 10 - new(0x0A400, 0x0FF0, 0x0B3F2, 0x23F16), // 11 Box 11 - new(0x0B400, 0x0FF0, 0x0C3F2, 0x23F18), // 12 Box 12 - new(0x0C400, 0x0FF0, 0x0D3F2, 0x23F1A), // 13 Box 13 - new(0x0D400, 0x0FF0, 0x0E3F2, 0x23F1C), // 14 Box 14 - new(0x0E400, 0x0FF0, 0x0F3F2, 0x23F1E), // 15 Box 15 - new(0x0F400, 0x0FF0, 0x103F2, 0x23F20), // 16 Box 16 - new(0x10400, 0x0FF0, 0x113F2, 0x23F22), // 17 Box 17 - new(0x11400, 0x0FF0, 0x123F2, 0x23F24), // 18 Box 18 - new(0x12400, 0x0FF0, 0x133F2, 0x23F26), // 19 Box 19 - new(0x13400, 0x0FF0, 0x143F2, 0x23F28), // 20 Box 20 - new(0x14400, 0x0FF0, 0x153F2, 0x23F2A), // 21 Box 21 - new(0x15400, 0x0FF0, 0x163F2, 0x23F2C), // 22 Box 22 - new(0x16400, 0x0FF0, 0x173F2, 0x23F2E), // 23 Box 23 - new(0x17400, 0x0FF0, 0x183F2, 0x23F30), // 24 Box 24 - new(0x18400, 0x09C0, 0x18DC2, 0x23F32), // 25 Inventory - new(0x18E00, 0x0534, 0x19336, 0x23F34), // 26 Party Pokemon - new(0x19400, 0x0068, 0x1946A, 0x23F36), // 27 Trainer Data - new(0x19500, 0x009C, 0x1959E, 0x23F38), // 28 Trainer Position - new(0x19600, 0x1338, 0x1A93A, 0x23F3A), // 29 Unity Tower and survey stuff - new(0x1AA00, 0x07C4, 0x1B1C6, 0x23F3C), // 30 Pal Pad Player Data - new(0x1B200, 0x0D54, 0x1BF56, 0x23F3E), // 31 Pal Pad Friend Data - new(0x1C000, 0x002C, 0x1C02E, 0x23F40), // 32 Skin Info - new(0x1C100, 0x0658, 0x1C75A, 0x23F42), // 33 ??? Gym badge data - new(0x1C800, 0x0A94, 0x1D296, 0x23F44), // 34 Mystery Gift - new(0x1D300, 0x01AC, 0x1D4AE, 0x23F46), // 35 Dream World Stuff (Catalog) - new(0x1D500, 0x03EC, 0x1D8EE, 0x23F48), // 36 Chatter - new(0x1D900, 0x005C, 0x1D95E, 0x23F4A), // 37 Adventure Info - new(0x1DA00, 0x01E0, 0x1DBE2, 0x23F4C), // 38 Trainer Card Records - new(0x1DC00, 0x00A8, 0x1DCAA, 0x23F4E), // 39 ??? - new(0x1DD00, 0x0460, 0x1E162, 0x23F50), // 40 Mail - new(0x1E200, 0x1400, 0x1F602, 0x23F52), // 41 Overworld State - new(0x1F700, 0x02A4, 0x1F9A6, 0x23F54), // 42 Musical - new(0x1FA00, 0x02DC, 0x1FCDE, 0x23F56), // 43 White Forest + Black City Data - new(0x1FD00, 0x034C, 0x2004E, 0x23F58), // 44 IR - new(0x20100, 0x03EC, 0x204EE, 0x23F5A), // 45 EventWork - new(0x20500, 0x00F8, 0x205FA, 0x23F5C), // 46 GTS - new(0x20600, 0x02FC, 0x208FE, 0x23F5E), // 47 Regulation Tournament - new(0x20900, 0x0094, 0x20996, 0x23F60), // 48 Gimmick - new(0x20A00, 0x035C, 0x20D5E, 0x23F62), // 49 Battle Box - new(0x20E00, 0x01CC, 0x20FCE, 0x23F64), // 50 Daycare - new(0x21000, 0x0168, 0x2116A, 0x23F66), // 51 Strength Boulder Status - new(0x21200, 0x00EC, 0x212EE, 0x23F68), // 52 Badge Flags, Money, Trainer Sayings - new(0x21300, 0x01B0, 0x214B2, 0x23F6A), // 53 Entralink (Level & Powers etc) - new(0x21500, 0x001C, 0x2151E, 0x23F6C), // 54 ??? - new(0x21600, 0x04D4, 0x21AD6, 0x23F6E), // 55 Pokedex - new(0x21B00, 0x0034, 0x21B36, 0x23F70), // 56 Encount Swarm and other overworld info - 2C - swarm, 2D - repel steps, 2E repel type - new(0x21C00, 0x003C, 0x21C3E, 0x23F72), // 57 Battle Subway Play Info - new(0x21D00, 0x01AC, 0x21EAE, 0x23F74), // 58 Battle Subway Score Info - new(0x21F00, 0x0B90, 0x22A92, 0x23F76), // 59 Battle Subway WiFi Info - new(0x22B00, 0x009C, 0x22B9E, 0x23F78), // 60 Online Records - new(0x22C00, 0x0850, 0x23452, 0x23F7A), // 61 Entralink Forest pokémon data - new(0x23500, 0x0028, 0x2352A, 0x23F7C), // 62 ??? - new(0x23600, 0x0284, 0x23886, 0x23F7E), // 63 Answered Questions - new(0x23900, 0x0010, 0x23912, 0x23F80), // 64 Unity Tower - new(0x23A00, 0x005C, 0x23A5E, 0x23F82), // 65 ??? - new(0x23B00, 0x016C, 0x23C6E, 0x23F84), // 66 ??? - new(0x23D00, 0x0040, 0x23D42, 0x23F86), // 67 ??? - new(0x23E00, 0x00FC, 0x23EFE, 0x23F88), // 68 ??? - new(0x23F00, 0x008C, 0x23F9A, 0x23F9A), // 69 Checksums */ - }; + new(0x00000, 0x03E0, 0x003E2, 0x23F00), // 00 Box Names + new(0x00400, 0x0FF0, 0x013F2, 0x23F02), // 01 Box 1 + new(0x01400, 0x0FF0, 0x023F2, 0x23F04), // 02 Box 2 + new(0x02400, 0x0FF0, 0x033F2, 0x23F06), // 03 Box 3 + new(0x03400, 0x0FF0, 0x043F2, 0x23F08), // 04 Box 4 + new(0x04400, 0x0FF0, 0x053F2, 0x23F0A), // 05 Box 5 + new(0x05400, 0x0FF0, 0x063F2, 0x23F0C), // 06 Box 6 + new(0x06400, 0x0FF0, 0x073F2, 0x23F0E), // 07 Box 7 + new(0x07400, 0x0FF0, 0x083F2, 0x23F10), // 08 Box 8 + new(0x08400, 0x0FF0, 0x093F2, 0x23F12), // 09 Box 9 + new(0x09400, 0x0FF0, 0x0A3F2, 0x23F14), // 10 Box 10 + new(0x0A400, 0x0FF0, 0x0B3F2, 0x23F16), // 11 Box 11 + new(0x0B400, 0x0FF0, 0x0C3F2, 0x23F18), // 12 Box 12 + new(0x0C400, 0x0FF0, 0x0D3F2, 0x23F1A), // 13 Box 13 + new(0x0D400, 0x0FF0, 0x0E3F2, 0x23F1C), // 14 Box 14 + new(0x0E400, 0x0FF0, 0x0F3F2, 0x23F1E), // 15 Box 15 + new(0x0F400, 0x0FF0, 0x103F2, 0x23F20), // 16 Box 16 + new(0x10400, 0x0FF0, 0x113F2, 0x23F22), // 17 Box 17 + new(0x11400, 0x0FF0, 0x123F2, 0x23F24), // 18 Box 18 + new(0x12400, 0x0FF0, 0x133F2, 0x23F26), // 19 Box 19 + new(0x13400, 0x0FF0, 0x143F2, 0x23F28), // 20 Box 20 + new(0x14400, 0x0FF0, 0x153F2, 0x23F2A), // 21 Box 21 + new(0x15400, 0x0FF0, 0x163F2, 0x23F2C), // 22 Box 22 + new(0x16400, 0x0FF0, 0x173F2, 0x23F2E), // 23 Box 23 + new(0x17400, 0x0FF0, 0x183F2, 0x23F30), // 24 Box 24 + new(0x18400, 0x09C0, 0x18DC2, 0x23F32), // 25 Inventory + new(0x18E00, 0x0534, 0x19336, 0x23F34), // 26 Party Pokemon + new(0x19400, 0x0068, 0x1946A, 0x23F36), // 27 Trainer Data + new(0x19500, 0x009C, 0x1959E, 0x23F38), // 28 Trainer Position + new(0x19600, 0x1338, 0x1A93A, 0x23F3A), // 29 Unity Tower and survey stuff + new(0x1AA00, 0x07C4, 0x1B1C6, 0x23F3C), // 30 Pal Pad Player Data + new(0x1B200, 0x0D54, 0x1BF56, 0x23F3E), // 31 Pal Pad Friend Data + new(0x1C000, 0x002C, 0x1C02E, 0x23F40), // 32 Skin Info + new(0x1C100, 0x0658, 0x1C75A, 0x23F42), // 33 ??? Gym badge data + new(0x1C800, 0x0A94, 0x1D296, 0x23F44), // 34 Mystery Gift + new(0x1D300, 0x01AC, 0x1D4AE, 0x23F46), // 35 Dream World Stuff (Catalog) + new(0x1D500, 0x03EC, 0x1D8EE, 0x23F48), // 36 Chatter + new(0x1D900, 0x005C, 0x1D95E, 0x23F4A), // 37 Adventure Info + new(0x1DA00, 0x01E0, 0x1DBE2, 0x23F4C), // 38 Trainer Card Records + new(0x1DC00, 0x00A8, 0x1DCAA, 0x23F4E), // 39 ??? + new(0x1DD00, 0x0460, 0x1E162, 0x23F50), // 40 Mail + new(0x1E200, 0x1400, 0x1F602, 0x23F52), // 41 Overworld State + new(0x1F700, 0x02A4, 0x1F9A6, 0x23F54), // 42 Musical + new(0x1FA00, 0x02DC, 0x1FCDE, 0x23F56), // 43 White Forest + Black City Data + new(0x1FD00, 0x034C, 0x2004E, 0x23F58), // 44 IR + new(0x20100, 0x03EC, 0x204EE, 0x23F5A), // 45 EventWork + new(0x20500, 0x00F8, 0x205FA, 0x23F5C), // 46 GTS + new(0x20600, 0x02FC, 0x208FE, 0x23F5E), // 47 Regulation Tournament + new(0x20900, 0x0094, 0x20996, 0x23F60), // 48 Gimmick + new(0x20A00, 0x035C, 0x20D5E, 0x23F62), // 49 Battle Box + new(0x20E00, 0x01CC, 0x20FCE, 0x23F64), // 50 Daycare + new(0x21000, 0x0168, 0x2116A, 0x23F66), // 51 Strength Boulder Status + new(0x21200, 0x00EC, 0x212EE, 0x23F68), // 52 Badge Flags, Money, Trainer Sayings + new(0x21300, 0x01B0, 0x214B2, 0x23F6A), // 53 Entralink (Level & Powers etc) + new(0x21500, 0x001C, 0x2151E, 0x23F6C), // 54 ??? + new(0x21600, 0x04D4, 0x21AD6, 0x23F6E), // 55 Pokedex + new(0x21B00, 0x0034, 0x21B36, 0x23F70), // 56 Encount Swarm and other overworld info - 2C - swarm, 2D - repel steps, 2E repel type + new(0x21C00, 0x003C, 0x21C3E, 0x23F72), // 57 Battle Subway Play Info + new(0x21D00, 0x01AC, 0x21EAE, 0x23F74), // 58 Battle Subway Score Info + new(0x21F00, 0x0B90, 0x22A92, 0x23F76), // 59 Battle Subway WiFi Info + new(0x22B00, 0x009C, 0x22B9E, 0x23F78), // 60 Online Records + new(0x22C00, 0x0850, 0x23452, 0x23F7A), // 61 Entralink Forest pokémon data + new(0x23500, 0x0028, 0x2352A, 0x23F7C), // 62 ??? + new(0x23600, 0x0284, 0x23886, 0x23F7E), // 63 Answered Questions + new(0x23900, 0x0010, 0x23912, 0x23F80), // 64 Unity Tower + new(0x23A00, 0x005C, 0x23A5E, 0x23F82), // 65 ??? + new(0x23B00, 0x016C, 0x23C6E, 0x23F84), // 66 ??? + new(0x23D00, 0x0040, 0x23D42, 0x23F86), // 67 ??? + new(0x23E00, 0x00FC, 0x23EFE, 0x23F88), // 68 ??? + new(0x23F00, 0x008C, 0x23F9A, 0x23F9A), // 69 Checksums */ + }; - public SaveBlockAccessor5BW(SAV5BW sav) - { - BoxLayout = new BoxLayout5(sav, 0x00000); - Items = new MyItem5BW(sav, 0x18400); - PlayerData = new PlayerData5(sav, 0x19400); - Mystery = new MysteryBlock5(sav, 0x1C800); - Musical = new Musical5(sav, 0x1F700); - Daycare = new Daycare5(sav, 0x20E00); - Misc = new Misc5BW(sav, 0x21200); - Entralink = new Entralink5BW(sav, 0x21300); - Zukan = new Zukan5(sav, 0x21600, 0x320); - Encount = new Encount5BW(sav, 0x21B00); - BattleSubway = new BattleSubway5(sav, 0x21D00); - } - - public IReadOnlyList BlockInfo => BlocksBW; - public MyItem Items { get; } - public Zukan5 Zukan { get; } - public Misc5 Misc { get; } - public MysteryBlock5 Mystery { get; } - public Daycare5 Daycare { get; } - public BoxLayout5 BoxLayout { get; } - public PlayerData5 PlayerData { get; } - public BattleSubway5 BattleSubway { get; } - public Entralink5 Entralink { get; } - public Musical5 Musical { get; } - public Encount5 Encount { get; } + public SaveBlockAccessor5BW(SAV5BW sav) + { + BoxLayout = new BoxLayout5(sav, 0x00000); + Items = new MyItem5BW(sav, 0x18400); + PlayerData = new PlayerData5(sav, 0x19400); + Mystery = new MysteryBlock5(sav, 0x1C800); + Musical = new Musical5(sav, 0x1F700); + Daycare = new Daycare5(sav, 0x20E00); + Misc = new Misc5BW(sav, 0x21200); + Entralink = new Entralink5BW(sav, 0x21300); + Zukan = new Zukan5(sav, 0x21600, 0x320); + Encount = new Encount5BW(sav, 0x21B00); + BattleSubway = new BattleSubway5(sav, 0x21D00); } + + public IReadOnlyList BlockInfo => BlocksBW; + public MyItem Items { get; } + public Zukan5 Zukan { get; } + public Misc5 Misc { get; } + public MysteryBlock5 Mystery { get; } + public Daycare5 Daycare { get; } + public BoxLayout5 BoxLayout { get; } + public PlayerData5 PlayerData { get; } + public BattleSubway5 BattleSubway { get; } + public Entralink5 Entralink { get; } + public Musical5 Musical { get; } + public Encount5 Encount { get; } } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor6AO.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor6AO.cs index 537113bdd..9cf936055 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor6AO.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor6AO.cs @@ -1,126 +1,125 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information for Accessing individual blocks within a . +/// +public sealed class SaveBlockAccessor6AO : ISaveBlockAccessor, ISaveBlock6Main { - /// - /// Information for Accessing individual blocks within a . - /// - public sealed class SaveBlockAccessor6AO : ISaveBlockAccessor, ISaveBlock6Main + public const int BlockMetadataOffset = SaveUtil.SIZE_G6ORAS - 0x200; + private const int boAO = BlockMetadataOffset; + + private static readonly BlockInfo6[] BlocksAO = { - public const int BlockMetadataOffset = SaveUtil.SIZE_G6ORAS - 0x200; - private const int boAO = BlockMetadataOffset; + new(boAO, 00, 0x00000, 0x002C8), // 00 Puff + new(boAO, 01, 0x00400, 0x00B90), // 01 MyItem + new(boAO, 02, 0x01000, 0x0002C), // 02 ItemInfo (Select Bound Items) + new(boAO, 03, 0x01200, 0x00038), // 03 GameTime + new(boAO, 04, 0x01400, 0x00150), // 04 Situation + new(boAO, 05, 0x01600, 0x00004), // 05 RandomGroup (rand seeds) + new(boAO, 06, 0x01800, 0x00008), // 06 PlayTime + new(boAO, 07, 0x01A00, 0x001C0), // 07 Fashion + new(boAO, 08, 0x01C00, 0x000BE), // 08 Amie minigame records + new(boAO, 09, 0x01E00, 0x00024), // 09 temp variables (u32 id + 32 u8) + new(boAO, 10, 0x02000, 0x02100), // 10 FieldMoveModelSave + new(boAO, 11, 0x04200, 0x00130), // 11 Misc + new(boAO, 12, 0x04400, 0x00440), // 12 BOX + new(boAO, 13, 0x04A00, 0x00574), // 13 BattleBox + new(boAO, 14, 0x05000, 0x04E28), // 14 PSS1 + new(boAO, 15, 0x0A000, 0x04E28), // 15 PSS2 + new(boAO, 16, 0x0F000, 0x04E28), // 16 PSS3 + new(boAO, 17, 0x14000, 0x00170), // 17 MyStatus + new(boAO, 18, 0x14200, 0x0061C), // 18 PokePartySave + new(boAO, 19, 0x14A00, 0x00504), // 19 EventWork + new(boAO, 20, 0x15000, 0x011CC), // 20 ZukanData + new(boAO, 21, 0x16200, 0x00644), // 21 hologram clips + new(boAO, 22, 0x16A00, 0x00104), // 22 UnionPokemon (Fused) + new(boAO, 23, 0x16C00, 0x00004), // 23 ConfigSave + new(boAO, 24, 0x16E00, 0x00420), // 24 Amie decoration stuff + new(boAO, 25, 0x17400, 0x00064), // 25 OPower + new(boAO, 26, 0x17600, 0x003F0), // 26 Strength Rock position (xyz float: 84 entries, 12bytes/entry) + new(boAO, 27, 0x17A00, 0x0070C), // 27 Trainer PR Video + new(boAO, 28, 0x18200, 0x00180), // 28 GtsData + new(boAO, 29, 0x18400, 0x00004), // 29 Packed Menu Bits + new(boAO, 30, 0x18600, 0x0000C), // 30 PSS Profile Q&A (6*questions, 6*answer) + new(boAO, 31, 0x18800, 0x00048), // 31 Repel Info, (Swarm?) and other overworld info (roamer) + new(boAO, 32, 0x18A00, 0x00054), // 32 BOSS data fetch history (serial/mystery gift), 4byte intro & 20*4byte entries + new(boAO, 33, 0x18C00, 0x00644), // 33 Streetpass history + new(boAO, 34, 0x19400, 0x005C8), // 34 LiveMatchData/BattleSpotData + new(boAO, 35, 0x19A00, 0x002F8), // 35 MAC Address & Network Connection Logging (0x98 per entry, 5 entries) + new(boAO, 36, 0x19E00, 0x01B40), // 36 Dendou (Hall of Fame) + new(boAO, 37, 0x1BA00, 0x001F4), // 37 BattleHouse (Maison) + new(boAO, 38, 0x1BC00, 0x003E0), // 38 Sodateya (Daycare) + new(boAO, 39, 0x1C000, 0x00216), // 39 TrialHouse (Battle Institute) + new(boAO, 40, 0x1C400, 0x00640), // 40 BerryField + new(boAO, 41, 0x1CC00, 0x01A90), // 41 MysteryGiftSave + new(boAO, 42, 0x1E800, 0x00400), // 42 [SubE]vent Log + new(boAO, 43, 0x1EC00, 0x00618), // 43 PokeDiarySave + new(boAO, 44, 0x1F400, 0x0025C), // 44 Record + new(boAO, 45, 0x1F800, 0x00834), // 45 Friend Safari (0x15 per entry, 100 entries) + new(boAO, 46, 0x20200, 0x00318), // 46 SuperTrain + new(boAO, 47, 0x20600, 0x007D0), // 47 Unused (lmao) + new(boAO, 48, 0x20E00, 0x00C48), // 48 LinkInfo + new(boAO, 49, 0x21C00, 0x00078), // 49 PSS usage info + new(boAO, 50, 0x21E00, 0x00200), // 50 GameSyncSave + new(boAO, 51, 0x22000, 0x00C84), // 51 PSS Icon (bool32 data present, 40x40 u16 pic, unused) + new(boAO, 52, 0x22E00, 0x00628), // 52 ValidationSave (updateable Public Key for legal check api calls) + new(boAO, 53, 0x23600, 0x00400), // 53 Contest + new(boAO, 54, 0x23A00, 0x07AD0), // 54 SecretBase + new(boAO, 55, 0x2B600, 0x078B0), // 55 EonTicket + new(boAO, 56, 0x33000, 0x34AD0), // 56 Box + new(boAO, 57, 0x67C00, 0x0E058), // 57 JPEG + }; - private static readonly BlockInfo6[] BlocksAO = - { - new(boAO, 00, 0x00000, 0x002C8), // 00 Puff - new(boAO, 01, 0x00400, 0x00B90), // 01 MyItem - new(boAO, 02, 0x01000, 0x0002C), // 02 ItemInfo (Select Bound Items) - new(boAO, 03, 0x01200, 0x00038), // 03 GameTime - new(boAO, 04, 0x01400, 0x00150), // 04 Situation - new(boAO, 05, 0x01600, 0x00004), // 05 RandomGroup (rand seeds) - new(boAO, 06, 0x01800, 0x00008), // 06 PlayTime - new(boAO, 07, 0x01A00, 0x001C0), // 07 Fashion - new(boAO, 08, 0x01C00, 0x000BE), // 08 Amie minigame records - new(boAO, 09, 0x01E00, 0x00024), // 09 temp variables (u32 id + 32 u8) - new(boAO, 10, 0x02000, 0x02100), // 10 FieldMoveModelSave - new(boAO, 11, 0x04200, 0x00130), // 11 Misc - new(boAO, 12, 0x04400, 0x00440), // 12 BOX - new(boAO, 13, 0x04A00, 0x00574), // 13 BattleBox - new(boAO, 14, 0x05000, 0x04E28), // 14 PSS1 - new(boAO, 15, 0x0A000, 0x04E28), // 15 PSS2 - new(boAO, 16, 0x0F000, 0x04E28), // 16 PSS3 - new(boAO, 17, 0x14000, 0x00170), // 17 MyStatus - new(boAO, 18, 0x14200, 0x0061C), // 18 PokePartySave - new(boAO, 19, 0x14A00, 0x00504), // 19 EventWork - new(boAO, 20, 0x15000, 0x011CC), // 20 ZukanData - new(boAO, 21, 0x16200, 0x00644), // 21 hologram clips - new(boAO, 22, 0x16A00, 0x00104), // 22 UnionPokemon (Fused) - new(boAO, 23, 0x16C00, 0x00004), // 23 ConfigSave - new(boAO, 24, 0x16E00, 0x00420), // 24 Amie decoration stuff - new(boAO, 25, 0x17400, 0x00064), // 25 OPower - new(boAO, 26, 0x17600, 0x003F0), // 26 Strength Rock position (xyz float: 84 entries, 12bytes/entry) - new(boAO, 27, 0x17A00, 0x0070C), // 27 Trainer PR Video - new(boAO, 28, 0x18200, 0x00180), // 28 GtsData - new(boAO, 29, 0x18400, 0x00004), // 29 Packed Menu Bits - new(boAO, 30, 0x18600, 0x0000C), // 30 PSS Profile Q&A (6*questions, 6*answer) - new(boAO, 31, 0x18800, 0x00048), // 31 Repel Info, (Swarm?) and other overworld info (roamer) - new(boAO, 32, 0x18A00, 0x00054), // 32 BOSS data fetch history (serial/mystery gift), 4byte intro & 20*4byte entries - new(boAO, 33, 0x18C00, 0x00644), // 33 Streetpass history - new(boAO, 34, 0x19400, 0x005C8), // 34 LiveMatchData/BattleSpotData - new(boAO, 35, 0x19A00, 0x002F8), // 35 MAC Address & Network Connection Logging (0x98 per entry, 5 entries) - new(boAO, 36, 0x19E00, 0x01B40), // 36 Dendou (Hall of Fame) - new(boAO, 37, 0x1BA00, 0x001F4), // 37 BattleHouse (Maison) - new(boAO, 38, 0x1BC00, 0x003E0), // 38 Sodateya (Daycare) - new(boAO, 39, 0x1C000, 0x00216), // 39 TrialHouse (Battle Institute) - new(boAO, 40, 0x1C400, 0x00640), // 40 BerryField - new(boAO, 41, 0x1CC00, 0x01A90), // 41 MysteryGiftSave - new(boAO, 42, 0x1E800, 0x00400), // 42 [SubE]vent Log - new(boAO, 43, 0x1EC00, 0x00618), // 43 PokeDiarySave - new(boAO, 44, 0x1F400, 0x0025C), // 44 Record - new(boAO, 45, 0x1F800, 0x00834), // 45 Friend Safari (0x15 per entry, 100 entries) - new(boAO, 46, 0x20200, 0x00318), // 46 SuperTrain - new(boAO, 47, 0x20600, 0x007D0), // 47 Unused (lmao) - new(boAO, 48, 0x20E00, 0x00C48), // 48 LinkInfo - new(boAO, 49, 0x21C00, 0x00078), // 49 PSS usage info - new(boAO, 50, 0x21E00, 0x00200), // 50 GameSyncSave - new(boAO, 51, 0x22000, 0x00C84), // 51 PSS Icon (bool32 data present, 40x40 u16 pic, unused) - new(boAO, 52, 0x22E00, 0x00628), // 52 ValidationSave (updateable Public Key for legal check api calls) - new(boAO, 53, 0x23600, 0x00400), // 53 Contest - new(boAO, 54, 0x23A00, 0x07AD0), // 54 SecretBase - new(boAO, 55, 0x2B600, 0x078B0), // 55 EonTicket - new(boAO, 56, 0x33000, 0x34AD0), // 56 Box - new(boAO, 57, 0x67C00, 0x0E058), // 57 JPEG - }; + public IReadOnlyList BlockInfo => BlocksAO; + public MyItem Items { get; } + public ItemInfo6 ItemInfo { get; } + public GameTime6 GameTime { get; } + public Situation6 Situation { get; } + public PlayTime6 Played { get; } + public MyStatus6 Status { get; } + public RecordBlock6 Records { get; } - public IReadOnlyList BlockInfo => BlocksAO; - public MyItem Items { get; } - public ItemInfo6 ItemInfo { get; } - public GameTime6 GameTime { get; } - public Situation6 Situation { get; } - public PlayTime6 Played { get; } - public MyStatus6 Status { get; } - public RecordBlock6 Records { get; } + public Zukan6AO Zukan { get; } + public Puff6 Puff { get; } + public BoxLayout6 BoxLayout { get; } + public BattleBox6 BattleBox { get; } + public OPower6 OPower { get; } + public MysteryBlock6 MysteryGift { get; } + public SangoInfoBlock Sango { get; } + public LinkBlock6 Link { get; } + public Misc6AO Misc { get; } + public SuperTrainBlock SuperTrain { get; } + public MaisonBlock Maison { get; } + public SubEventLog6 SUBE { get; } + public ConfigSave6 Config { get; } + public Encount6 Encount { get; } + public SecretBase6Block SecretBase { get; } - public Zukan6AO Zukan { get; } - public Puff6 Puff { get; } - public BoxLayout6 BoxLayout { get; } - public BattleBox6 BattleBox { get; } - public OPower6 OPower { get; } - public MysteryBlock6 MysteryGift { get; } - public SangoInfoBlock Sango { get; } - public LinkBlock6 Link { get; } - public Misc6AO Misc { get; } - public SuperTrainBlock SuperTrain { get; } - public MaisonBlock Maison { get; } - public SubEventLog6 SUBE { get; } - public ConfigSave6 Config { get; } - public Encount6 Encount { get; } - public SecretBase6Block SecretBase { get; } - - public SaveBlockAccessor6AO(SAV6AO sav) - { - Puff = new Puff6(sav, 0x0000); - Items = new MyItem6AO(sav, 0x00400); - ItemInfo = new ItemInfo6(sav, 0x1000); - GameTime = new GameTime6(sav, 0x01200); - Situation = new Situation6(sav, 0x01400); - Played = new PlayTime6(sav, 0x01800); - Misc = new Misc6AO(sav, 0x04200); - BoxLayout = new BoxLayout6(sav, 0x04400); - BattleBox = new BattleBox6(sav, 0x04A00); - Status = new MyStatus6(sav, 0x14000); - Zukan = new Zukan6AO(sav, 0x15000, 0x400); - Config = new ConfigSave6(sav, 0x16C00); - OPower = new OPower6(sav, 0x17400); - Encount = new Encount6(sav, 0x18800); - Maison = new MaisonBlock(sav, 0x1BA00); - MysteryGift = new MysteryBlock6(sav, 0x1CC00); - SUBE = new SubEventLog6AO(sav, 0x1E800); - Records = new RecordBlock6(sav, 0x1F400); - SuperTrain = new SuperTrainBlock(sav, 0x20200); - Link = new LinkBlock6(sav, 0x20E00); - SecretBase = new SecretBase6Block(sav, 0x23A00); - Sango = new SangoInfoBlock(sav, 0x2B600); - } + public SaveBlockAccessor6AO(SAV6AO sav) + { + Puff = new Puff6(sav, 0x0000); + Items = new MyItem6AO(sav, 0x00400); + ItemInfo = new ItemInfo6(sav, 0x1000); + GameTime = new GameTime6(sav, 0x01200); + Situation = new Situation6(sav, 0x01400); + Played = new PlayTime6(sav, 0x01800); + Misc = new Misc6AO(sav, 0x04200); + BoxLayout = new BoxLayout6(sav, 0x04400); + BattleBox = new BattleBox6(sav, 0x04A00); + Status = new MyStatus6(sav, 0x14000); + Zukan = new Zukan6AO(sav, 0x15000, 0x400); + Config = new ConfigSave6(sav, 0x16C00); + OPower = new OPower6(sav, 0x17400); + Encount = new Encount6(sav, 0x18800); + Maison = new MaisonBlock(sav, 0x1BA00); + MysteryGift = new MysteryBlock6(sav, 0x1CC00); + SUBE = new SubEventLog6AO(sav, 0x1E800); + Records = new RecordBlock6(sav, 0x1F400); + SuperTrain = new SuperTrainBlock(sav, 0x20200); + Link = new LinkBlock6(sav, 0x20E00); + SecretBase = new SecretBase6Block(sav, 0x23A00); + Sango = new SangoInfoBlock(sav, 0x2B600); } } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor6AODemo.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor6AODemo.cs index 77c094da1..053beb5c9 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor6AODemo.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor6AODemo.cs @@ -1,55 +1,54 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information for Accessing individual blocks within a . +/// +public sealed class SaveBlockAccessor6AODemo : ISaveBlockAccessor, ISaveBlock6Core { - /// - /// Information for Accessing individual blocks within a . - /// - public sealed class SaveBlockAccessor6AODemo : ISaveBlockAccessor, ISaveBlock6Core + public const int BlockMetadataOffset = SaveUtil.SIZE_G6ORASDEMO - 0x200; + private const int boAOdemo = BlockMetadataOffset; + + private static readonly BlockInfo6[] BlocksAODemo = { - public const int BlockMetadataOffset = SaveUtil.SIZE_G6ORASDEMO - 0x200; - private const int boAOdemo = BlockMetadataOffset; + new(boAOdemo, 00, 0x00000, 0x00B90), // MyItem + new(boAOdemo, 01, 0x00C00, 0x0002C), // ItemInfo6 + new(boAOdemo, 02, 0x00E00, 0x00038), // GameTime + new(boAOdemo, 03, 0x01000, 0x00150), // Situation + new(boAOdemo, 04, 0x01200, 0x00004), // RandomGroup (rand seeds) + new(boAOdemo, 05, 0x01400, 0x00008), // PlayTime + new(boAOdemo, 06, 0x01600, 0x00024), // temp variables (u32 id + 32 u8) + new(boAOdemo, 07, 0x01800, 0x02100), // FieldMoveModelSave + new(boAOdemo, 08, 0x03A00, 0x00130), // Misc + new(boAOdemo, 09, 0x03C00, 0x00170), // MyStatus + new(boAOdemo, 10, 0x03E00, 0x0061C), // PokePartySave + new(boAOdemo, 11, 0x04600, 0x00504), // EventWork + new(boAOdemo, 12, 0x04C00, 0x00004), // Packed Menu Bits + new(boAOdemo, 13, 0x04E00, 0x00048), // Repel Info, (Swarm?) and other overworld info (roamer) + new(boAOdemo, 14, 0x05000, 0x00400), // PokeDiarySave + new(boAOdemo, 15, 0x05400, 0x0025C), // Record + }; - private static readonly BlockInfo6[] BlocksAODemo = - { - new(boAOdemo, 00, 0x00000, 0x00B90), // MyItem - new(boAOdemo, 01, 0x00C00, 0x0002C), // ItemInfo6 - new(boAOdemo, 02, 0x00E00, 0x00038), // GameTime - new(boAOdemo, 03, 0x01000, 0x00150), // Situation - new(boAOdemo, 04, 0x01200, 0x00004), // RandomGroup (rand seeds) - new(boAOdemo, 05, 0x01400, 0x00008), // PlayTime - new(boAOdemo, 06, 0x01600, 0x00024), // temp variables (u32 id + 32 u8) - new(boAOdemo, 07, 0x01800, 0x02100), // FieldMoveModelSave - new(boAOdemo, 08, 0x03A00, 0x00130), // Misc - new(boAOdemo, 09, 0x03C00, 0x00170), // MyStatus - new(boAOdemo, 10, 0x03E00, 0x0061C), // PokePartySave - new(boAOdemo, 11, 0x04600, 0x00504), // EventWork - new(boAOdemo, 12, 0x04C00, 0x00004), // Packed Menu Bits - new(boAOdemo, 13, 0x04E00, 0x00048), // Repel Info, (Swarm?) and other overworld info (roamer) - new(boAOdemo, 14, 0x05000, 0x00400), // PokeDiarySave - new(boAOdemo, 15, 0x05400, 0x0025C), // Record - }; + public IReadOnlyList BlockInfo => BlocksAODemo; + public MyItem Items { get; } + public ItemInfo6 ItemInfo { get; } + public GameTime6 GameTime { get; } + public Situation6 Situation { get; } + public PlayTime6 Played { get; } + public MyStatus6 Status { get; } + public RecordBlock6 Records { get; } + public Misc6AO Misc { get; } - public IReadOnlyList BlockInfo => BlocksAODemo; - public MyItem Items { get; } - public ItemInfo6 ItemInfo { get; } - public GameTime6 GameTime { get; } - public Situation6 Situation { get; } - public PlayTime6 Played { get; } - public MyStatus6 Status { get; } - public RecordBlock6 Records { get; } - public Misc6AO Misc { get; } - - public SaveBlockAccessor6AODemo(SAV6AODemo sav) - { - Items = new MyItem6AO(sav, 0x00000); - ItemInfo = new ItemInfo6(sav, 0x00C00); - GameTime = new GameTime6(sav, 0x00E00); - Situation = new Situation6(sav, 0x01000); - Played = new PlayTime6(sav, 0x01400); - Status = new MyStatus6(sav, 0x03C00); - Records = new RecordBlock6(sav, 0x05400); - Misc = new Misc6AO(sav, 0x03A00); - } + public SaveBlockAccessor6AODemo(SAV6AODemo sav) + { + Items = new MyItem6AO(sav, 0x00000); + ItemInfo = new ItemInfo6(sav, 0x00C00); + GameTime = new GameTime6(sav, 0x00E00); + Situation = new Situation6(sav, 0x01000); + Played = new PlayTime6(sav, 0x01400); + Status = new MyStatus6(sav, 0x03C00); + Records = new RecordBlock6(sav, 0x05400); + Misc = new Misc6AO(sav, 0x03A00); } } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor6XY.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor6XY.cs index a234e3ada..4697dc47f 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor6XY.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor6XY.cs @@ -1,122 +1,121 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information for Accessing individual blocks within a . +/// +public sealed class SaveBlockAccessor6XY : ISaveBlockAccessor, ISaveBlock6XY { - /// - /// Information for Accessing individual blocks within a . - /// - public sealed class SaveBlockAccessor6XY : ISaveBlockAccessor, ISaveBlock6XY + public const int BlockMetadataOffset = SaveUtil.SIZE_G6XY - 0x200; + private const int boXY = BlockMetadataOffset; + + private static readonly BlockInfo6[] BlocksXY = { - public const int BlockMetadataOffset = SaveUtil.SIZE_G6XY - 0x200; - private const int boXY = BlockMetadataOffset; + new(boXY, 00, 0x00000, 0x002C8), // 00 Puff + new(boXY, 01, 0x00400, 0x00B88), // 01 MyItem + new(boXY, 02, 0x01000, 0x0002C), // 02 ItemInfo (Select Bound Items) + new(boXY, 03, 0x01200, 0x00038), // 03 GameTime + new(boXY, 04, 0x01400, 0x00150), // 04 Situation + new(boXY, 05, 0x01600, 0x00004), // 05 RandomGroup (rand seeds) + new(boXY, 06, 0x01800, 0x00008), // 06 PlayTime + new(boXY, 07, 0x01A00, 0x001C0), // 07 Fashion + new(boXY, 08, 0x01C00, 0x000BE), // 08 Amie minigame records + new(boXY, 09, 0x01E00, 0x00024), // 09 temp variables (u32 id + 32 u8) + new(boXY, 10, 0x02000, 0x02100), // 10 FieldMoveModelSave + new(boXY, 11, 0x04200, 0x00140), // 11 Misc + new(boXY, 12, 0x04400, 0x00440), // 12 BOX + new(boXY, 13, 0x04A00, 0x00574), // 13 BattleBox + new(boXY, 14, 0x05000, 0x04E28), // 14 PSS1 + new(boXY, 15, 0x0A000, 0x04E28), // 15 PSS2 + new(boXY, 16, 0x0F000, 0x04E28), // 16 PSS3 + new(boXY, 17, 0x14000, 0x00170), // 17 MyStatus + new(boXY, 18, 0x14200, 0x0061C), // 18 PokePartySave + new(boXY, 19, 0x14A00, 0x00504), // 19 EventWork + new(boXY, 20, 0x15000, 0x006A0), // 20 ZukanData + new(boXY, 21, 0x15800, 0x00644), // 21 hologram clips + new(boXY, 22, 0x16000, 0x00104), // 22 UnionPokemon (Fused) + new(boXY, 23, 0x16200, 0x00004), // 23 ConfigSave + new(boXY, 24, 0x16400, 0x00420), // 24 Amie decoration stuff + new(boXY, 25, 0x16A00, 0x00064), // 25 OPower + new(boXY, 26, 0x16C00, 0x003F0), // 26 Strength Rock position (xyz float: 84 entries, 12bytes/entry) + new(boXY, 27, 0x17000, 0x0070C), // 27 Trainer PR Video + new(boXY, 28, 0x17800, 0x00180), // 28 GtsData + new(boXY, 29, 0x17A00, 0x00004), // 29 Packed Menu Bits + new(boXY, 30, 0x17C00, 0x0000C), // 30 PSS Profile Q&A (6*questions, 6*answer) + new(boXY, 31, 0x17E00, 0x00048), // 31 Repel Info, (Swarm?) and other overworld info (roamer) + new(boXY, 32, 0x18000, 0x00054), // 32 BOSS data fetch history (serial/mystery gift), 4byte intro & 20*4byte entries + new(boXY, 33, 0x18200, 0x00644), // 33 Streetpass history + new(boXY, 34, 0x18A00, 0x005C8), // 34 LiveMatchData/BattleSpotData + new(boXY, 35, 0x19000, 0x002F8), // 35 MAC Address & Network Connection Logging (0x98 per entry, 5 entries) + new(boXY, 36, 0x19400, 0x01B40), // 36 Dendou (Hall of Fame) + new(boXY, 37, 0x1B000, 0x001F4), // 37 BattleHouse (Maison) + new(boXY, 38, 0x1B200, 0x001F0), // 38 Sodateya (Daycare) + new(boXY, 39, 0x1B400, 0x00216), // 39 TrialHouse (Battle Institute) + new(boXY, 40, 0x1B800, 0x00390), // 40 BerryField + new(boXY, 41, 0x1BC00, 0x01A90), // 41 MysteryGiftSave + new(boXY, 42, 0x1D800, 0x00308), // 42 [SubE]vent Log + new(boXY, 43, 0x1DC00, 0x00618), // 43 PokeDiarySave + new(boXY, 44, 0x1E400, 0x0025C), // 44 Record + new(boXY, 45, 0x1E800, 0x00834), // 45 Friend Safari (0x15 per entry, 100 entries) + new(boXY, 46, 0x1F200, 0x00318), // 46 SuperTrain + new(boXY, 47, 0x1F600, 0x007D0), // 47 Unused (lmao) + new(boXY, 48, 0x1FE00, 0x00C48), // 48 LinkInfo + new(boXY, 49, 0x20C00, 0x00078), // 49 PSS usage info + new(boXY, 50, 0x20E00, 0x00200), // 50 GameSyncSave + new(boXY, 51, 0x21000, 0x00C84), // 51 PSS Icon (bool32 data present, 40x40 u16 pic, unused) + new(boXY, 52, 0x21E00, 0x00628), // 52 ValidationSave (updateable Public Key for legal check api calls) + new(boXY, 53, 0x22600, 0x34AD0), // 53 Box + new(boXY, 54, 0x57200, 0x0E058), // 54 JPEG + }; - private static readonly BlockInfo6[] BlocksXY = - { - new(boXY, 00, 0x00000, 0x002C8), // 00 Puff - new(boXY, 01, 0x00400, 0x00B88), // 01 MyItem - new(boXY, 02, 0x01000, 0x0002C), // 02 ItemInfo (Select Bound Items) - new(boXY, 03, 0x01200, 0x00038), // 03 GameTime - new(boXY, 04, 0x01400, 0x00150), // 04 Situation - new(boXY, 05, 0x01600, 0x00004), // 05 RandomGroup (rand seeds) - new(boXY, 06, 0x01800, 0x00008), // 06 PlayTime - new(boXY, 07, 0x01A00, 0x001C0), // 07 Fashion - new(boXY, 08, 0x01C00, 0x000BE), // 08 Amie minigame records - new(boXY, 09, 0x01E00, 0x00024), // 09 temp variables (u32 id + 32 u8) - new(boXY, 10, 0x02000, 0x02100), // 10 FieldMoveModelSave - new(boXY, 11, 0x04200, 0x00140), // 11 Misc - new(boXY, 12, 0x04400, 0x00440), // 12 BOX - new(boXY, 13, 0x04A00, 0x00574), // 13 BattleBox - new(boXY, 14, 0x05000, 0x04E28), // 14 PSS1 - new(boXY, 15, 0x0A000, 0x04E28), // 15 PSS2 - new(boXY, 16, 0x0F000, 0x04E28), // 16 PSS3 - new(boXY, 17, 0x14000, 0x00170), // 17 MyStatus - new(boXY, 18, 0x14200, 0x0061C), // 18 PokePartySave - new(boXY, 19, 0x14A00, 0x00504), // 19 EventWork - new(boXY, 20, 0x15000, 0x006A0), // 20 ZukanData - new(boXY, 21, 0x15800, 0x00644), // 21 hologram clips - new(boXY, 22, 0x16000, 0x00104), // 22 UnionPokemon (Fused) - new(boXY, 23, 0x16200, 0x00004), // 23 ConfigSave - new(boXY, 24, 0x16400, 0x00420), // 24 Amie decoration stuff - new(boXY, 25, 0x16A00, 0x00064), // 25 OPower - new(boXY, 26, 0x16C00, 0x003F0), // 26 Strength Rock position (xyz float: 84 entries, 12bytes/entry) - new(boXY, 27, 0x17000, 0x0070C), // 27 Trainer PR Video - new(boXY, 28, 0x17800, 0x00180), // 28 GtsData - new(boXY, 29, 0x17A00, 0x00004), // 29 Packed Menu Bits - new(boXY, 30, 0x17C00, 0x0000C), // 30 PSS Profile Q&A (6*questions, 6*answer) - new(boXY, 31, 0x17E00, 0x00048), // 31 Repel Info, (Swarm?) and other overworld info (roamer) - new(boXY, 32, 0x18000, 0x00054), // 32 BOSS data fetch history (serial/mystery gift), 4byte intro & 20*4byte entries - new(boXY, 33, 0x18200, 0x00644), // 33 Streetpass history - new(boXY, 34, 0x18A00, 0x005C8), // 34 LiveMatchData/BattleSpotData - new(boXY, 35, 0x19000, 0x002F8), // 35 MAC Address & Network Connection Logging (0x98 per entry, 5 entries) - new(boXY, 36, 0x19400, 0x01B40), // 36 Dendou (Hall of Fame) - new(boXY, 37, 0x1B000, 0x001F4), // 37 BattleHouse (Maison) - new(boXY, 38, 0x1B200, 0x001F0), // 38 Sodateya (Daycare) - new(boXY, 39, 0x1B400, 0x00216), // 39 TrialHouse (Battle Institute) - new(boXY, 40, 0x1B800, 0x00390), // 40 BerryField - new(boXY, 41, 0x1BC00, 0x01A90), // 41 MysteryGiftSave - new(boXY, 42, 0x1D800, 0x00308), // 42 [SubE]vent Log - new(boXY, 43, 0x1DC00, 0x00618), // 43 PokeDiarySave - new(boXY, 44, 0x1E400, 0x0025C), // 44 Record - new(boXY, 45, 0x1E800, 0x00834), // 45 Friend Safari (0x15 per entry, 100 entries) - new(boXY, 46, 0x1F200, 0x00318), // 46 SuperTrain - new(boXY, 47, 0x1F600, 0x007D0), // 47 Unused (lmao) - new(boXY, 48, 0x1FE00, 0x00C48), // 48 LinkInfo - new(boXY, 49, 0x20C00, 0x00078), // 49 PSS usage info - new(boXY, 50, 0x20E00, 0x00200), // 50 GameSyncSave - new(boXY, 51, 0x21000, 0x00C84), // 51 PSS Icon (bool32 data present, 40x40 u16 pic, unused) - new(boXY, 52, 0x21E00, 0x00628), // 52 ValidationSave (updateable Public Key for legal check api calls) - new(boXY, 53, 0x22600, 0x34AD0), // 53 Box - new(boXY, 54, 0x57200, 0x0E058), // 54 JPEG - }; + public IReadOnlyList BlockInfo => BlocksXY; + public MyItem Items { get; } + public ItemInfo6 ItemInfo { get; } + public GameTime6 GameTime { get; } + public Situation6 Situation { get; } + public PlayTime6 Played { get; } + public MyStatus6 Status { get; } + public RecordBlock6 Records { get; } + public SubEventLog6 SUBE { get; } + public ConfigSave6 Config { get; } + public Encount6 Encount { get; } - public IReadOnlyList BlockInfo => BlocksXY; - public MyItem Items { get; } - public ItemInfo6 ItemInfo { get; } - public GameTime6 GameTime { get; } - public Situation6 Situation { get; } - public PlayTime6 Played { get; } - public MyStatus6 Status { get; } - public RecordBlock6 Records { get; } - public SubEventLog6 SUBE { get; } - public ConfigSave6 Config { get; } - public Encount6 Encount { get; } - - public SaveBlockAccessor6XY(SAV6XY sav) - { - Puff = new Puff6(sav, 0x00000); - Items = new MyItem6XY(sav, 0x00400); - ItemInfo = new ItemInfo6(sav, 0x01000); - GameTime = new GameTime6(sav, 0x01200); - Situation = new Situation6(sav, 0x01400); - Played = new PlayTime6(sav, 0x01800); - Fashion = new Fashion6XY(sav, 0x1A00); - Misc = new Misc6XY(sav, 0x4200); - BoxLayout = new BoxLayout6(sav, 0x4400); - BattleBox = new BattleBox6(sav, 0x04A00); - Status = new MyStatus6XY(sav, 0x14000); - Zukan = new Zukan6XY(sav, 0x15000, 0x3C8); - Config = new ConfigSave6(sav, 0x16200); - OPower = new OPower6(sav, 0x16A00); - Encount = new Encount6(sav, 0x17E00); - MysteryGift = new MysteryBlock6(sav, 0x1BC00); - SUBE = new SubEventLog6XY(sav, 0x1D800); - Records = new RecordBlock6(sav, 0x1E400); - SuperTrain = new SuperTrainBlock(sav, 0x1F200); - Link = new LinkBlock6(sav, 0x1FE00); - Maison = new MaisonBlock(sav, 0x1B000); - } - - public Puff6 Puff { get; } - public BoxLayout6 BoxLayout { get; } - public BattleBox6 BattleBox { get; } - public OPower6 OPower { get; } - public MysteryBlock6 MysteryGift { get; } - public LinkBlock6 Link { get; } - public SuperTrainBlock SuperTrain { get; } - public MaisonBlock Maison { get; } - - public Misc6XY Misc { get; } - public Zukan6XY Zukan { get; } - public Fashion6XY Fashion { get; } + public SaveBlockAccessor6XY(SAV6XY sav) + { + Puff = new Puff6(sav, 0x00000); + Items = new MyItem6XY(sav, 0x00400); + ItemInfo = new ItemInfo6(sav, 0x01000); + GameTime = new GameTime6(sav, 0x01200); + Situation = new Situation6(sav, 0x01400); + Played = new PlayTime6(sav, 0x01800); + Fashion = new Fashion6XY(sav, 0x1A00); + Misc = new Misc6XY(sav, 0x4200); + BoxLayout = new BoxLayout6(sav, 0x4400); + BattleBox = new BattleBox6(sav, 0x04A00); + Status = new MyStatus6XY(sav, 0x14000); + Zukan = new Zukan6XY(sav, 0x15000, 0x3C8); + Config = new ConfigSave6(sav, 0x16200); + OPower = new OPower6(sav, 0x16A00); + Encount = new Encount6(sav, 0x17E00); + MysteryGift = new MysteryBlock6(sav, 0x1BC00); + SUBE = new SubEventLog6XY(sav, 0x1D800); + Records = new RecordBlock6(sav, 0x1E400); + SuperTrain = new SuperTrainBlock(sav, 0x1F200); + Link = new LinkBlock6(sav, 0x1FE00); + Maison = new MaisonBlock(sav, 0x1B000); } + + public Puff6 Puff { get; } + public BoxLayout6 BoxLayout { get; } + public BattleBox6 BattleBox { get; } + public OPower6 OPower { get; } + public MysteryBlock6 MysteryGift { get; } + public LinkBlock6 Link { get; } + public SuperTrainBlock SuperTrain { get; } + public MaisonBlock Maison { get; } + + public Misc6XY Misc { get; } + public Zukan6XY Zukan { get; } + public Fashion6XY Fashion { get; } } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor7SM.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor7SM.cs index ad6d1cca4..4b60fd510 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor7SM.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor7SM.cs @@ -1,103 +1,102 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information for Accessing individual blocks within a . +/// +public sealed class SaveBlockAccessor7SM : ISaveBlockAccessor, ISaveBlock7SM { - /// - /// Information for Accessing individual blocks within a . - /// - public sealed class SaveBlockAccessor7SM : ISaveBlockAccessor, ISaveBlock7SM + public const int BlockMetadataOffset = SaveUtil.SIZE_G7SM - 0x200; + private const int boSM = BlockMetadataOffset; + + private static readonly BlockInfo7[] BlockInfoSM = { - public const int BlockMetadataOffset = SaveUtil.SIZE_G7SM - 0x200; - private const int boSM = BlockMetadataOffset; + new(boSM, 00, 0x00000, 0x00DE0), // 00 MyItem + new(boSM, 01, 0x00E00, 0x0007C), // 01 Situation + new(boSM, 02, 0x01000, 0x00014), // 02 RandomGroup + new(boSM, 03, 0x01200, 0x000C0), // 03 MyStatus + new(boSM, 04, 0x01400, 0x0061C), // 04 PokePartySave + new(boSM, 05, 0x01C00, 0x00E00), // 05 EventWork + new(boSM, 06, 0x02A00, 0x00F78), // 06 ZukanData + new(boSM, 07, 0x03A00, 0x00228), // 07 GtsData + new(boSM, 08, 0x03E00, 0x00104), // 08 UnionPokemon + new(boSM, 09, 0x04000, 0x00200), // 09 Misc + new(boSM, 10, 0x04200, 0x00020), // 10 FieldMenu + new(boSM, 11, 0x04400, 0x00004), // 11 ConfigSave + new(boSM, 12, 0x04600, 0x00058), // 12 GameTime + new(boSM, 13, 0x04800, 0x005E6), // 13 BOX + new(boSM, 14, 0x04E00, 0x36600), // 14 BoxPokemon + new(boSM, 15, 0x3B400, 0x0572C), // 15 ResortSave + new(boSM, 16, 0x40C00, 0x00008), // 16 PlayTime + new(boSM, 17, 0x40E00, 0x01080), // 17 FieldMoveModelSave + new(boSM, 18, 0x42000, 0x01A08), // 18 Fashion + new(boSM, 19, 0x43C00, 0x06408), // 19 JoinFestaPersonalSave + new(boSM, 20, 0x4A200, 0x06408), // 20 JoinFestaPersonalSave + new(boSM, 21, 0x50800, 0x03998), // 21 JoinFestaDataSave + new(boSM, 22, 0x54200, 0x00100), // 22 BerrySpot + new(boSM, 23, 0x54400, 0x00100), // 23 FishingSpot + new(boSM, 24, 0x54600, 0x10528), // 24 LiveMatchData + new(boSM, 25, 0x64C00, 0x00204), // 25 BattleSpotData + new(boSM, 26, 0x65000, 0x00B60), // 26 PokeFinderSave + new(boSM, 27, 0x65C00, 0x03F50), // 27 MysteryGiftSave + new(boSM, 28, 0x69C00, 0x00358), // 28 Record + new(boSM, 29, 0x6A000, 0x00728), // 29 ValidationSave + new(boSM, 30, 0x6A800, 0x00200), // 30 GameSyncSave + new(boSM, 31, 0x6AA00, 0x00718), // 31 PokeDiarySave + new(boSM, 32, 0x6B200, 0x001FC), // 32 BattleInstSave + new(boSM, 33, 0x6B400, 0x00200), // 33 Sodateya + new(boSM, 34, 0x6B600, 0x00120), // 34 WeatherSave + new(boSM, 35, 0x6B800, 0x001C8), // 35 QRReaderSaveData + new(boSM, 36, 0x6BA00, 0x00200), // 36 TurtleSalmonSave + }; - private static readonly BlockInfo7[] BlockInfoSM = - { - new(boSM, 00, 0x00000, 0x00DE0), // 00 MyItem - new(boSM, 01, 0x00E00, 0x0007C), // 01 Situation - new(boSM, 02, 0x01000, 0x00014), // 02 RandomGroup - new(boSM, 03, 0x01200, 0x000C0), // 03 MyStatus - new(boSM, 04, 0x01400, 0x0061C), // 04 PokePartySave - new(boSM, 05, 0x01C00, 0x00E00), // 05 EventWork - new(boSM, 06, 0x02A00, 0x00F78), // 06 ZukanData - new(boSM, 07, 0x03A00, 0x00228), // 07 GtsData - new(boSM, 08, 0x03E00, 0x00104), // 08 UnionPokemon - new(boSM, 09, 0x04000, 0x00200), // 09 Misc - new(boSM, 10, 0x04200, 0x00020), // 10 FieldMenu - new(boSM, 11, 0x04400, 0x00004), // 11 ConfigSave - new(boSM, 12, 0x04600, 0x00058), // 12 GameTime - new(boSM, 13, 0x04800, 0x005E6), // 13 BOX - new(boSM, 14, 0x04E00, 0x36600), // 14 BoxPokemon - new(boSM, 15, 0x3B400, 0x0572C), // 15 ResortSave - new(boSM, 16, 0x40C00, 0x00008), // 16 PlayTime - new(boSM, 17, 0x40E00, 0x01080), // 17 FieldMoveModelSave - new(boSM, 18, 0x42000, 0x01A08), // 18 Fashion - new(boSM, 19, 0x43C00, 0x06408), // 19 JoinFestaPersonalSave - new(boSM, 20, 0x4A200, 0x06408), // 20 JoinFestaPersonalSave - new(boSM, 21, 0x50800, 0x03998), // 21 JoinFestaDataSave - new(boSM, 22, 0x54200, 0x00100), // 22 BerrySpot - new(boSM, 23, 0x54400, 0x00100), // 23 FishingSpot - new(boSM, 24, 0x54600, 0x10528), // 24 LiveMatchData - new(boSM, 25, 0x64C00, 0x00204), // 25 BattleSpotData - new(boSM, 26, 0x65000, 0x00B60), // 26 PokeFinderSave - new(boSM, 27, 0x65C00, 0x03F50), // 27 MysteryGiftSave - new(boSM, 28, 0x69C00, 0x00358), // 28 Record - new(boSM, 29, 0x6A000, 0x00728), // 29 ValidationSave - new(boSM, 30, 0x6A800, 0x00200), // 30 GameSyncSave - new(boSM, 31, 0x6AA00, 0x00718), // 31 PokeDiarySave - new(boSM, 32, 0x6B200, 0x001FC), // 32 BattleInstSave - new(boSM, 33, 0x6B400, 0x00200), // 33 Sodateya - new(boSM, 34, 0x6B600, 0x00120), // 34 WeatherSave - new(boSM, 35, 0x6B800, 0x001C8), // 35 QRReaderSaveData - new(boSM, 36, 0x6BA00, 0x00200), // 36 TurtleSalmonSave - }; + public SaveBlockAccessor7SM(SAV7SM sav) + { + var bi = BlockInfo; - public SaveBlockAccessor7SM(SAV7SM sav) - { - var bi = BlockInfo; - - Items = new MyItem7SM(sav, 0); - Situation = new Situation7(sav, bi[01].Offset); - MyStatus = new MyStatus7(sav, bi[03].Offset); - Fame = new HallOfFame7(sav, bi[05].Offset + 0x9C4); - Zukan = new Zukan7(sav, bi[06].Offset, 0x550); - Misc = new Misc7(sav, bi[09].Offset); - FieldMenu = new FieldMenu7(sav, bi[10].Offset); - Config = new ConfigSave7(sav, bi[11].Offset); - GameTime = new GameTime7(sav, bi[12].Offset); - BoxLayout = new BoxLayout7(sav, bi[13].Offset); - ResortSave = new ResortSave7(sav, bi[15].Offset); - Played = new PlayTime6(sav, bi[16].Offset); - Overworld = new FieldMoveModelSave7(sav, bi[17].Offset); - Fashion = new FashionBlock7(sav, bi[18].Offset); - Festa = new JoinFesta7(sav, bi[21].Offset); - PokeFinder = new PokeFinder7(sav, bi[26].Offset); - MysteryGift = new MysteryBlock7(sav, bi[27].Offset); - Records = new RecordBlock6(sav, bi[28].Offset); - BattleTree = new BattleTree7(sav, bi[32].Offset); - Daycare = new Daycare7(sav, bi[33].Offset); - } - - public IReadOnlyList BlockInfo => BlockInfoSM; - - public MyItem Items { get; } - public MysteryBlock7 MysteryGift { get; } - public PokeFinder7 PokeFinder { get; } - public JoinFesta7 Festa { get; } - public Daycare7 Daycare { get; } - public RecordBlock6 Records { get; } - public PlayTime6 Played { get; } - public MyStatus7 MyStatus { get; } - public FieldMoveModelSave7 Overworld { get; } - public Situation7 Situation { get; } - public ConfigSave7 Config { get; } - public GameTime7 GameTime { get; } - public Misc7 Misc { get; } - public Zukan7 Zukan { get; } - public BoxLayout7 BoxLayout { get; } - public BattleTree7 BattleTree { get; } - public ResortSave7 ResortSave { get; } - public FieldMenu7 FieldMenu { get; } - public FashionBlock7 Fashion { get; } - public HallOfFame7 Fame { get; } + Items = new MyItem7SM(sav, 0); + Situation = new Situation7(sav, bi[01].Offset); + MyStatus = new MyStatus7(sav, bi[03].Offset); + Fame = new HallOfFame7(sav, bi[05].Offset + 0x9C4); + Zukan = new Zukan7(sav, bi[06].Offset, 0x550); + Misc = new Misc7(sav, bi[09].Offset); + FieldMenu = new FieldMenu7(sav, bi[10].Offset); + Config = new ConfigSave7(sav, bi[11].Offset); + GameTime = new GameTime7(sav, bi[12].Offset); + BoxLayout = new BoxLayout7(sav, bi[13].Offset); + ResortSave = new ResortSave7(sav, bi[15].Offset); + Played = new PlayTime6(sav, bi[16].Offset); + Overworld = new FieldMoveModelSave7(sav, bi[17].Offset); + Fashion = new FashionBlock7(sav, bi[18].Offset); + Festa = new JoinFesta7(sav, bi[21].Offset); + PokeFinder = new PokeFinder7(sav, bi[26].Offset); + MysteryGift = new MysteryBlock7(sav, bi[27].Offset); + Records = new RecordBlock6(sav, bi[28].Offset); + BattleTree = new BattleTree7(sav, bi[32].Offset); + Daycare = new Daycare7(sav, bi[33].Offset); } + + public IReadOnlyList BlockInfo => BlockInfoSM; + + public MyItem Items { get; } + public MysteryBlock7 MysteryGift { get; } + public PokeFinder7 PokeFinder { get; } + public JoinFesta7 Festa { get; } + public Daycare7 Daycare { get; } + public RecordBlock6 Records { get; } + public PlayTime6 Played { get; } + public MyStatus7 MyStatus { get; } + public FieldMoveModelSave7 Overworld { get; } + public Situation7 Situation { get; } + public ConfigSave7 Config { get; } + public GameTime7 GameTime { get; } + public Misc7 Misc { get; } + public Zukan7 Zukan { get; } + public BoxLayout7 BoxLayout { get; } + public BattleTree7 BattleTree { get; } + public ResortSave7 ResortSave { get; } + public FieldMenu7 FieldMenu { get; } + public FashionBlock7 Fashion { get; } + public HallOfFame7 Fame { get; } } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor7USUM.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor7USUM.cs index 0d18bb3ec..418f76e26 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor7USUM.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor7USUM.cs @@ -1,107 +1,106 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information for Accessing individual blocks within a . +/// +public sealed class SaveBlockAccessor7USUM : ISaveBlockAccessor, ISaveBlock7USUM { - /// - /// Information for Accessing individual blocks within a . - /// - public sealed class SaveBlockAccessor7USUM : ISaveBlockAccessor, ISaveBlock7USUM + public const int BlockMetadataOffset = SaveUtil.SIZE_G7USUM - 0x200; + private const int boUU = BlockMetadataOffset; + + private static readonly BlockInfo7[] BlockInfoUSUM = { - public const int BlockMetadataOffset = SaveUtil.SIZE_G7USUM - 0x200; - private const int boUU = BlockMetadataOffset; + new(boUU, 00, 0x00000, 0x00E28), // 00 MyItem + new(boUU, 01, 0x01000, 0x0007C), // 01 Situation + new(boUU, 02, 0x01200, 0x00014), // 02 RandomGroup + new(boUU, 03, 0x01400, 0x000C0), // 03 MyStatus + new(boUU, 04, 0x01600, 0x0061C), // 04 PokePartySave + new(boUU, 05, 0x01E00, 0x00E00), // 05 EventWork + new(boUU, 06, 0x02C00, 0x00F78), // 06 ZukanData + new(boUU, 07, 0x03C00, 0x00228), // 07 GtsData + new(boUU, 08, 0x04000, 0x0030C), // 08 UnionPokemon + new(boUU, 09, 0x04400, 0x001FC), // 09 Misc + new(boUU, 10, 0x04600, 0x0004C), // 10 FieldMenu + new(boUU, 11, 0x04800, 0x00004), // 11 ConfigSave + new(boUU, 12, 0x04A00, 0x00058), // 12 GameTime + new(boUU, 13, 0x04C00, 0x005E6), // 13 BOX + new(boUU, 14, 0x05200, 0x36600), // 14 BoxPokemon + new(boUU, 15, 0x3B800, 0x0572C), // 15 ResortSave + new(boUU, 16, 0x41000, 0x00008), // 16 PlayTime + new(boUU, 17, 0x41200, 0x01218), // 17 FieldMoveModelSave + new(boUU, 18, 0x42600, 0x01A08), // 18 Fashion + new(boUU, 19, 0x44200, 0x06408), // 19 JoinFestaPersonalSave + new(boUU, 20, 0x4A800, 0x06408), // 20 JoinFestaPersonalSave + new(boUU, 21, 0x50E00, 0x03998), // 21 JoinFestaDataSave + new(boUU, 22, 0x54800, 0x00100), // 22 BerrySpot + new(boUU, 23, 0x54A00, 0x00100), // 23 FishingSpot + new(boUU, 24, 0x54C00, 0x10528), // 24 LiveMatchData + new(boUU, 25, 0x65200, 0x00204), // 25 BattleSpotData + new(boUU, 26, 0x65600, 0x00B60), // 26 PokeFinderSave + new(boUU, 27, 0x66200, 0x03F50), // 27 MysteryGiftSave + new(boUU, 28, 0x6A200, 0x00358), // 28 Record + new(boUU, 29, 0x6A600, 0x00728), // 29 ValidationSave + new(boUU, 30, 0x6AE00, 0x00200), // 30 GameSyncSave + new(boUU, 31, 0x6B000, 0x00718), // 31 PokeDiarySave + new(boUU, 32, 0x6B800, 0x001FC), // 32 BattleInstSave + new(boUU, 33, 0x6BA00, 0x00200), // 33 Sodateya + new(boUU, 34, 0x6BC00, 0x00120), // 34 WeatherSave + new(boUU, 35, 0x6BE00, 0x001C8), // 35 QRReaderSaveData + new(boUU, 36, 0x6C000, 0x00200), // 36 TurtleSalmonSave + new(boUU, 37, 0x6C200, 0x0039C), // 37 BattleFesSave + new(boUU, 38, 0x6C600, 0x00400), // 38 FinderStudioSave + }; - private static readonly BlockInfo7[] BlockInfoUSUM = - { - new(boUU, 00, 0x00000, 0x00E28), // 00 MyItem - new(boUU, 01, 0x01000, 0x0007C), // 01 Situation - new(boUU, 02, 0x01200, 0x00014), // 02 RandomGroup - new(boUU, 03, 0x01400, 0x000C0), // 03 MyStatus - new(boUU, 04, 0x01600, 0x0061C), // 04 PokePartySave - new(boUU, 05, 0x01E00, 0x00E00), // 05 EventWork - new(boUU, 06, 0x02C00, 0x00F78), // 06 ZukanData - new(boUU, 07, 0x03C00, 0x00228), // 07 GtsData - new(boUU, 08, 0x04000, 0x0030C), // 08 UnionPokemon - new(boUU, 09, 0x04400, 0x001FC), // 09 Misc - new(boUU, 10, 0x04600, 0x0004C), // 10 FieldMenu - new(boUU, 11, 0x04800, 0x00004), // 11 ConfigSave - new(boUU, 12, 0x04A00, 0x00058), // 12 GameTime - new(boUU, 13, 0x04C00, 0x005E6), // 13 BOX - new(boUU, 14, 0x05200, 0x36600), // 14 BoxPokemon - new(boUU, 15, 0x3B800, 0x0572C), // 15 ResortSave - new(boUU, 16, 0x41000, 0x00008), // 16 PlayTime - new(boUU, 17, 0x41200, 0x01218), // 17 FieldMoveModelSave - new(boUU, 18, 0x42600, 0x01A08), // 18 Fashion - new(boUU, 19, 0x44200, 0x06408), // 19 JoinFestaPersonalSave - new(boUU, 20, 0x4A800, 0x06408), // 20 JoinFestaPersonalSave - new(boUU, 21, 0x50E00, 0x03998), // 21 JoinFestaDataSave - new(boUU, 22, 0x54800, 0x00100), // 22 BerrySpot - new(boUU, 23, 0x54A00, 0x00100), // 23 FishingSpot - new(boUU, 24, 0x54C00, 0x10528), // 24 LiveMatchData - new(boUU, 25, 0x65200, 0x00204), // 25 BattleSpotData - new(boUU, 26, 0x65600, 0x00B60), // 26 PokeFinderSave - new(boUU, 27, 0x66200, 0x03F50), // 27 MysteryGiftSave - new(boUU, 28, 0x6A200, 0x00358), // 28 Record - new(boUU, 29, 0x6A600, 0x00728), // 29 ValidationSave - new(boUU, 30, 0x6AE00, 0x00200), // 30 GameSyncSave - new(boUU, 31, 0x6B000, 0x00718), // 31 PokeDiarySave - new(boUU, 32, 0x6B800, 0x001FC), // 32 BattleInstSave - new(boUU, 33, 0x6BA00, 0x00200), // 33 Sodateya - new(boUU, 34, 0x6BC00, 0x00120), // 34 WeatherSave - new(boUU, 35, 0x6BE00, 0x001C8), // 35 QRReaderSaveData - new(boUU, 36, 0x6C000, 0x00200), // 36 TurtleSalmonSave - new(boUU, 37, 0x6C200, 0x0039C), // 37 BattleFesSave - new(boUU, 38, 0x6C600, 0x00400), // 38 FinderStudioSave - }; + public SaveBlockAccessor7USUM(SAV7USUM sav) + { + var bi = BlockInfo; - public SaveBlockAccessor7USUM(SAV7USUM sav) - { - var bi = BlockInfo; - - Items = new MyItem7USUM(sav, 0); - Situation = new Situation7(sav, bi[01].Offset); - MyStatus = new MyStatus7(sav, bi[03].Offset); - Fame = new HallOfFame7(sav, bi[05].Offset + 0xA3C); - Zukan = new Zukan7(sav, bi[06].Offset, 0x550); - Misc = new Misc7(sav, bi[09].Offset); - FieldMenu = new FieldMenu7(sav, bi[10].Offset); - Config = new ConfigSave7(sav, bi[11].Offset); - GameTime = new GameTime7(sav, bi[12].Offset); - BoxLayout = new BoxLayout7(sav, bi[13].Offset); - ResortSave = new ResortSave7(sav, bi[15].Offset); - Played = new PlayTime6(sav, bi[16].Offset); - Overworld = new FieldMoveModelSave7(sav, bi[17].Offset); - Fashion = new FashionBlock7(sav, bi[18].Offset); - Festa = new JoinFesta7(sav, bi[21].Offset); - PokeFinder = new PokeFinder7(sav, bi[26].Offset); - MysteryGift = new MysteryBlock7(sav, bi[27].Offset); - Records = new RecordBlock6(sav, bi[28].Offset); - BattleTree = new BattleTree7(sav, bi[32].Offset); - Daycare = new Daycare7(sav, bi[33].Offset); - BattleAgency = new BattleAgency7(sav, bi[37].Offset); - } - - public IReadOnlyList BlockInfo => BlockInfoUSUM; - - public MyItem Items { get; } - public MysteryBlock7 MysteryGift { get; } - public PokeFinder7 PokeFinder { get; } - public JoinFesta7 Festa { get; } - public Daycare7 Daycare { get; } - public RecordBlock6 Records { get; } - public PlayTime6 Played { get; } - public MyStatus7 MyStatus { get; } - public FieldMoveModelSave7 Overworld { get; } - public Situation7 Situation { get; } - public ConfigSave7 Config { get; } - public GameTime7 GameTime { get; } - public Misc7 Misc { get; } - public Zukan7 Zukan { get; } - public BoxLayout7 BoxLayout { get; } - public BattleTree7 BattleTree { get; } - public ResortSave7 ResortSave { get; } - public FieldMenu7 FieldMenu { get; } - public FashionBlock7 Fashion { get; } - public HallOfFame7 Fame { get; } - public BattleAgency7 BattleAgency { get; } + Items = new MyItem7USUM(sav, 0); + Situation = new Situation7(sav, bi[01].Offset); + MyStatus = new MyStatus7(sav, bi[03].Offset); + Fame = new HallOfFame7(sav, bi[05].Offset + 0xA3C); + Zukan = new Zukan7(sav, bi[06].Offset, 0x550); + Misc = new Misc7(sav, bi[09].Offset); + FieldMenu = new FieldMenu7(sav, bi[10].Offset); + Config = new ConfigSave7(sav, bi[11].Offset); + GameTime = new GameTime7(sav, bi[12].Offset); + BoxLayout = new BoxLayout7(sav, bi[13].Offset); + ResortSave = new ResortSave7(sav, bi[15].Offset); + Played = new PlayTime6(sav, bi[16].Offset); + Overworld = new FieldMoveModelSave7(sav, bi[17].Offset); + Fashion = new FashionBlock7(sav, bi[18].Offset); + Festa = new JoinFesta7(sav, bi[21].Offset); + PokeFinder = new PokeFinder7(sav, bi[26].Offset); + MysteryGift = new MysteryBlock7(sav, bi[27].Offset); + Records = new RecordBlock6(sav, bi[28].Offset); + BattleTree = new BattleTree7(sav, bi[32].Offset); + Daycare = new Daycare7(sav, bi[33].Offset); + BattleAgency = new BattleAgency7(sav, bi[37].Offset); } + + public IReadOnlyList BlockInfo => BlockInfoUSUM; + + public MyItem Items { get; } + public MysteryBlock7 MysteryGift { get; } + public PokeFinder7 PokeFinder { get; } + public JoinFesta7 Festa { get; } + public Daycare7 Daycare { get; } + public RecordBlock6 Records { get; } + public PlayTime6 Played { get; } + public MyStatus7 MyStatus { get; } + public FieldMoveModelSave7 Overworld { get; } + public Situation7 Situation { get; } + public ConfigSave7 Config { get; } + public GameTime7 GameTime { get; } + public Misc7 Misc { get; } + public Zukan7 Zukan { get; } + public BoxLayout7 BoxLayout { get; } + public BattleTree7 BattleTree { get; } + public ResortSave7 ResortSave { get; } + public FieldMenu7 FieldMenu { get; } + public FashionBlock7 Fashion { get; } + public HallOfFame7 Fame { get; } + public BattleAgency7 BattleAgency { get; } } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor7b.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor7b.cs index f1b481afa..5bfa2c92b 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor7b.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor7b.cs @@ -1,66 +1,65 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information for Accessing individual blocks within a . +/// +public sealed class SaveBlockAccessor7b : ISaveBlockAccessor, ISaveBlock7b { - /// - /// Information for Accessing individual blocks within a . - /// - public sealed class SaveBlockAccessor7b : ISaveBlockAccessor, ISaveBlock7b + private const int boGG = 0xB8800 - 0x200; // nowhere near 1MB (savedata.bin size) + + private static readonly BlockInfo7b[] BlockInfoGG = { - private const int boGG = 0xB8800 - 0x200; // nowhere near 1MB (savedata.bin size) + new(boGG, 00, 0x00000, 0x00D90), + new(boGG, 01, 0x00E00, 0x00200), + new(boGG, 02, 0x01000, 0x00168), + new(boGG, 03, 0x01200, 0x01800), + new(boGG, 04, 0x02A00, 0x020E8), + new(boGG, 05, 0x04C00, 0x00930), + new(boGG, 06, 0x05600, 0x00004), + new(boGG, 07, 0x05800, 0x00130), + new(boGG, 08, 0x05A00, 0x00012), + new(boGG, 09, 0x05C00, 0x3F7A0), + new(boGG, 10, 0x45400, 0x00008), + new(boGG, 11, 0x45600, 0x00E90), + new(boGG, 12, 0x46600, 0x010A4), + new(boGG, 13, 0x47800, 0x000F0), + new(boGG, 14, 0x47A00, 0x06010), + new(boGG, 15, 0x4DC00, 0x00200), + new(boGG, 16, 0x4DE00, 0x00098), + new(boGG, 17, 0x4E000, 0x00068), + new(boGG, 18, 0x4E200, 0x69780), + new(boGG, 19, 0xB7A00, 0x000B0), + new(boGG, 20, 0xB7C00, 0x00940), + }; - private static readonly BlockInfo7b[] BlockInfoGG = - { - new(boGG, 00, 0x00000, 0x00D90), - new(boGG, 01, 0x00E00, 0x00200), - new(boGG, 02, 0x01000, 0x00168), - new(boGG, 03, 0x01200, 0x01800), - new(boGG, 04, 0x02A00, 0x020E8), - new(boGG, 05, 0x04C00, 0x00930), - new(boGG, 06, 0x05600, 0x00004), - new(boGG, 07, 0x05800, 0x00130), - new(boGG, 08, 0x05A00, 0x00012), - new(boGG, 09, 0x05C00, 0x3F7A0), - new(boGG, 10, 0x45400, 0x00008), - new(boGG, 11, 0x45600, 0x00E90), - new(boGG, 12, 0x46600, 0x010A4), - new(boGG, 13, 0x47800, 0x000F0), - new(boGG, 14, 0x47A00, 0x06010), - new(boGG, 15, 0x4DC00, 0x00200), - new(boGG, 16, 0x4DE00, 0x00098), - new(boGG, 17, 0x4E000, 0x00068), - new(boGG, 18, 0x4E200, 0x69780), - new(boGG, 19, 0xB7A00, 0x000B0), - new(boGG, 20, 0xB7C00, 0x00940), - }; + public IReadOnlyList BlockInfo => BlockInfoGG; - public IReadOnlyList BlockInfo => BlockInfoGG; - - public SaveBlockAccessor7b(SAV7b sav) - { - Zukan = new Zukan7b(sav, GetBlockOffset(BelugaBlockIndex.Zukan), 0x550); - Config = new ConfigSave7b(sav, GetBlockOffset(BelugaBlockIndex.ConfigSave)); - Items = new MyItem7b(sav, GetBlockOffset(BelugaBlockIndex.MyItem)); - Storage = new PokeListHeader(sav, GetBlockOffset(BelugaBlockIndex.PokeListHeader)); - Status = new MyStatus7b(sav, GetBlockOffset(BelugaBlockIndex.MyStatus)); - Played = new PlayTime7b(sav, GetBlockOffset(BelugaBlockIndex.PlayTime)); - Misc = new Misc7b(sav, GetBlockOffset(BelugaBlockIndex.Misc)); - EventWork = new EventWork7b(sav, GetBlockOffset(BelugaBlockIndex.EventWork)); - GiftRecords = new WB7Records(sav, GetBlockOffset(BelugaBlockIndex.WB7Record)); - Captured = new CaptureRecords(sav, GetBlockOffset(BelugaBlockIndex.CaptureRecord)); - } - - public MyItem Items { get; } - public Misc7b Misc { get; } - public Zukan7b Zukan { get; } - public MyStatus7b Status { get; } - public PlayTime7b Played { get; } - public ConfigSave7b Config { get; } - public EventWork7b EventWork { get; } - public PokeListHeader Storage { get; } - public WB7Records GiftRecords { get; } - public CaptureRecords Captured { get; } - public BlockInfo GetBlock(BelugaBlockIndex index) => BlockInfo[(int)index]; - public int GetBlockOffset(BelugaBlockIndex index) => GetBlock(index).Offset; + public SaveBlockAccessor7b(SAV7b sav) + { + Zukan = new Zukan7b(sav, GetBlockOffset(BelugaBlockIndex.Zukan), 0x550); + Config = new ConfigSave7b(sav, GetBlockOffset(BelugaBlockIndex.ConfigSave)); + Items = new MyItem7b(sav, GetBlockOffset(BelugaBlockIndex.MyItem)); + Storage = new PokeListHeader(sav, GetBlockOffset(BelugaBlockIndex.PokeListHeader)); + Status = new MyStatus7b(sav, GetBlockOffset(BelugaBlockIndex.MyStatus)); + Played = new PlayTime7b(sav, GetBlockOffset(BelugaBlockIndex.PlayTime)); + Misc = new Misc7b(sav, GetBlockOffset(BelugaBlockIndex.Misc)); + EventWork = new EventWork7b(sav, GetBlockOffset(BelugaBlockIndex.EventWork)); + GiftRecords = new WB7Records(sav, GetBlockOffset(BelugaBlockIndex.WB7Record)); + Captured = new CaptureRecords(sav, GetBlockOffset(BelugaBlockIndex.CaptureRecord)); } + + public MyItem Items { get; } + public Misc7b Misc { get; } + public Zukan7b Zukan { get; } + public MyStatus7b Status { get; } + public PlayTime7b Played { get; } + public ConfigSave7b Config { get; } + public EventWork7b EventWork { get; } + public PokeListHeader Storage { get; } + public WB7Records GiftRecords { get; } + public CaptureRecords Captured { get; } + public BlockInfo GetBlock(BelugaBlockIndex index) => BlockInfo[(int)index]; + public int GetBlockOffset(BelugaBlockIndex index) => GetBlock(index).Offset; } diff --git a/PKHeX.Core/Saves/Access/SaveBlockAccessor8SWSH.cs b/PKHeX.Core/Saves/Access/SaveBlockAccessor8SWSH.cs index 4b288e94b..fa3f28fa8 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockAccessor8SWSH.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockAccessor8SWSH.cs @@ -3,556 +3,555 @@ #pragma warning disable IDE0051 // Remove unused private members #pragma warning disable RCS1213 // Remove unused member declaration. -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information for Accessing individual blocks within a . +/// +public sealed class SaveBlockAccessor8SWSH : SCBlockAccessor, ISaveBlock8Main { - /// - /// Information for Accessing individual blocks within a . - /// - public sealed class SaveBlockAccessor8SWSH : SCBlockAccessor, ISaveBlock8Main + public override IReadOnlyList BlockInfo { get; } + public Box8 BoxInfo { get; } + public Party8 PartyInfo { get; } + public MyItem Items { get; } + public MyStatus8 MyStatus { get; } + public Misc8 Misc { get; } + public Zukan8 Zukan { get; } + public BoxLayout8 BoxLayout { get; } + public PlayTime8 Played { get; } + public Fused8 Fused { get; } + public Daycare8 Daycare { get; } + public Record8 Records { get; } + public TrainerCard8 TrainerCard{ get; } + public FashionUnlock8 Fashion { get; } + public RaidSpawnList8 Raid { get; } + public RaidSpawnList8 RaidArmor { get; } + public RaidSpawnList8 RaidCrown { get; } + public TitleScreen8 TitleScreen { get; } + public TeamIndexes8 TeamIndexes { get; } + public HallOfFameTime8 FameTime { get; } + + public SaveBlockAccessor8SWSH(SAV8SWSH sav) { - public override IReadOnlyList BlockInfo { get; } - public Box8 BoxInfo { get; } - public Party8 PartyInfo { get; } - public MyItem Items { get; } - public MyStatus8 MyStatus { get; } - public Misc8 Misc { get; } - public Zukan8 Zukan { get; } - public BoxLayout8 BoxLayout { get; } - public PlayTime8 Played { get; } - public Fused8 Fused { get; } - public Daycare8 Daycare { get; } - public Record8 Records { get; } - public TrainerCard8 TrainerCard{ get; } - public FashionUnlock8 Fashion { get; } - public RaidSpawnList8 Raid { get; } - public RaidSpawnList8 RaidArmor { get; } - public RaidSpawnList8 RaidCrown { get; } - public TitleScreen8 TitleScreen { get; } - public TeamIndexes8 TeamIndexes { get; } - public HallOfFameTime8 FameTime { get; } - - public SaveBlockAccessor8SWSH(SAV8SWSH sav) - { - BlockInfo = sav.AllBlocks; - BoxInfo = new Box8(sav, GetBlock(KBox)); - PartyInfo = new Party8(sav, GetBlock(KParty)); - Items = new MyItem8(sav, GetBlock(KItem)); - Zukan = new Zukan8(sav, GetBlock(KZukan), GetBlockSafe(KZukanR1), GetBlockSafe(KZukanR2)); - MyStatus = new MyStatus8(sav, GetBlock(KMyStatus)); - Misc = new Misc8(sav, GetBlock(KMisc)); - BoxLayout = new BoxLayout8(sav, GetBlock(KBoxLayout)); - TrainerCard = new TrainerCard8(sav, GetBlock(KTrainerCard)); - Played = new PlayTime8(sav, GetBlock(KPlayTime)); - Fused = new Fused8(sav, GetBlock(KFused)); - Daycare = new Daycare8(sav, GetBlock(KDaycare)); - Records = new Record8(sav, GetBlock(KRecord), Core.Records.MaxType_SWSH); - Fashion = new FashionUnlock8(sav, GetBlock(KFashionUnlock)); - Raid = new RaidSpawnList8(sav, GetBlock(KRaidSpawnList), RaidSpawnList8.RaidCountLegal_O0); - RaidArmor = new RaidSpawnList8(sav, GetBlockSafe(KRaidSpawnListR1), RaidSpawnList8.RaidCountLegal_R1); - RaidCrown = new RaidSpawnList8(sav, GetBlockSafe(KRaidSpawnListR2), RaidSpawnList8.RaidCountLegal_R2); - TitleScreen = new TitleScreen8(sav, GetBlock(KTitleScreenTeam)); - TeamIndexes = new TeamIndexes8(sav, GetBlock(KTeamIndexes)); - FameTime = new HallOfFameTime8(sav, GetBlock(KEnteredHallOfFame)); - } - - // Arrays (Blocks) - private const uint KTeamNames = 0x1920C1E4; // Team 1, 2...6 ((10 + terminator)*6 char16 strings) - private const uint KBoxLayout = 0x19722c89; // Box Names - public const uint KBoxWallpapers = 0x2EB1B190; // Box Wallpapers - private const uint KMenuButtons = 0xB1DDDCA8; // X Menu Button Order - - // Objects (Blocks) - private const uint KBox = 0x0d66012c; // Box Data - private const uint KMysteryGift = 0x112d5141; // Mystery Gift Data - private const uint KItem = 0x1177c2c4; // Items - private const uint KCoordinates = 0x16aaa7fa; // Coordinates? - private const uint KMisc = 0x1b882b09; // Money - private const uint KParty = 0x2985fe5d; // Party Data - private const uint KDaycare = 0x2d6fba6a; // Daycare slots (2 daycares) - private const uint KTeamIndexes = 0x33F39467; // Team Indexes for competition - private const uint KRecord = 0x37da95a3; - private const uint KZukan = 0x4716c404; // ZukanData_Pokemon - private const uint KZukanR1 = 0x3F936BA9; // ZukanData_PokemonR1 (Armor) - private const uint KZukanR2 = 0x3C9366F0; // ZukanData_PokemonR2 (Crown) - private const uint KPokedexRecommendation = 0xC3FB9E77; // Pokédex recommendations - private const uint KCurryDex = 0x6EB72940; // Curry Dex - private const uint KTrainerCard = 0x874da6fa; // Trainer Card - private const uint KPlayTime = 0x8cbbfd90; // Time Played - private const uint KRaidSpawnList = 0x9033eb7b; // Nest current values (hash, seed, meta) - private const uint KRaidSpawnListR1 = 0x158DA896; // Raid Data for DLC1 - private const uint KRaidSpawnListR2 = 0x148DA703; // Raid Data for DLC2 - private const uint KFused = 0xc0de5c5f; // Fused PKM (*3) - public const uint KFusedCalyrex = 0xC37F267B; // Fused Horse - private const uint KFashionUnlock = 0xd224f9ac; // Fashion unlock bool array (owned for (each apparel type) * 0x80, then another array for "new") - private const uint KTitleScreenTeam = 0xE9BE28BF; // Title Screen Team details - public const uint KEnteredHallOfFame = 0xE2F6E456; // U64 Unix Timestamp - private const uint KMyStatus = 0xf25c070e; // Trainer Details - private const uint KFriendLeagueCards = 0x28e707f5; // League Cards received from other players - private const uint KNPCLeagueCards = 0xb1c26fb0; // League Cards received from NPCs - private const uint KNPCLeagueCardsR1 = 0xb868ee77; // League Cards received from NPCs on The Isle of Armor - private const uint KNPCLeagueCardsR2 = 0xB968F00A; // League Cards received from NPCs on The Crown Tundra - private const uint KTrainer1EndlessRecordData = 0x79D787CB; // Trainer 1's Data of Best Endless Dynamax Adventure Record - private const uint KTrainer2EndlessRecordData = 0x78D78638; // Trainer 2's Data of Best Endless Dynamax Adventure Record - private const uint KTrainer3EndlessRecordData = 0x7BD78AF1; // Trainer 3's Data of Best Endless Dynamax Adventure Record - private const uint KTrainer4EndlessRecordData = 0x7AD7895E; // Trainer 4's Data of Best Endless Dynamax Adventure Record - private const uint KPokeJobStorage = 0xB25C772B; // Pokémon storage while they are doing Jobs - - // Rental Teams - Objects (Blocks) - private const uint KRentalTeam1 = 0x149A1DD0; - private const uint KRentalTeam2 = 0x179A2289; - private const uint KRentalTeam3 = 0x169A20F6; - private const uint KRentalTeam4 = 0x199A25AF; - private const uint KRentalTeam5 = 0x189A241C; - //private const uint KRentalTeamUnused = 0x159A1F63; // does not exist - - // Download Rules - private const uint KDownloadRules1 = 0xEEF1B186; - private const uint KDownloadRules2 = 0xEFF1B319; - private const uint KDownloadRules3 = 0xF0F1B4AC; - private const uint KDownloadRules4 = 0xF1F1B63F; - private const uint KDownloadRules5 = 0xF2F1B7D2; - private const uint KDownloadRules6 = 0xF3F1B965; - //private const uint KDownloadRulesX = 0xF4F1BAF8; // does not exist - //private const uint KDownloadRulesX = 0xF5F1BC8B; // does not exist - //private const uint KDownloadRulesX = 0xF6F1BE1E; // does not exist - //private const uint KDownloadRulesX = 0xF7F1BFB1; // does not exist - //private const uint KDownloadRulesX = 0xF8F1C144; // does not exist - //private const uint KDownloadRulesX = 0xF9F1C2D7; // does not exist - private const uint KDownloadRulesU1 = 0xFAF1C46A; - private const uint KDownloadRulesU2 = 0xFBF1C5FD; - - private const uint KOfficialCompetition = 0xEEE5A3F8; - - // Raid DLC Flatbuffer Storage Objects (Blocks) - private const uint KDropRewards = 0x680EEB85; // drop_rewards - private const uint KDaiEncount = 0xAD3920F5; // dai_encount - private const uint KNormalEncount = 0xAD9DFA6A; // normal_encount - private const uint KBonusRewards = 0xEFCAE04E; // bonus_rewards - - private const uint KNormalEncountRigel1 = 0x0E615A8C; // normal_encount_rigel1 - private const uint KNormalEncountRigel2 = 0x11615F45; // normal_encount_rigel2 - - // Static Encounter event flags (bool) - public const uint KCapturedGalarianArticuno = 0x4CAB7DA6; // EF_R2_FURIIZAA_GET - public const uint KCapturedGalarianZapdos = 0x284CBECF; // EF_R2_SANDAA_GET - public const uint KCapturedGalarianMoltres = 0xF1E493AA; // EF_R2_FAIYAA_GET - public const uint KCapturedRegirock = 0xEE3F84E6; // FE_CAPTURE_REGIROCK - public const uint KCapturedRegice = 0xDAB3DD3A; // FE_CAPTURE_REGIICE - public const uint KCapturedRegisteel = 0xEE1FD86E; // FE_CAPTURE_REGISTEEL - public const uint KCapturedSpiritomb = 0x11C12005; // FE_CAPTURE_MIKARUGE - public const uint KCapturedRegigigas = 0xC4308A93; // FE_CAPTURE_REGIGIGASU - public const uint KCapturedCobalion = 0xBB305227; // z_wr0312_SymbolEncountPokemonGimmickSpawner_WR03_Sanjyuusi - public const uint KCapturedTerrakion = 0x750C83A4; // z_wr0322_SymbolEncountPokemonGimmickSpawner_WR03_Sanjyuusi - public const uint KCapturedVirizion = 0x1A27DF2C; // z_wr0304_SymbolEncountPokemonGimmickSpawner_WR03_Sanjyuusi - public const uint KCapturedKeldeo = 0xA097DE31; // z_wr0321_SymbolEncountPokemonGimmickSpawner_WR03_Sanjyuusi - public const uint KCapturedRegieleki = 0x4F4AEC32; // FE_CAPTURE_REDEN - public const uint KCapturedRegidrago = 0x4F30F174; // FE_CAPTURE_REDRA - - // Max Lair event flags (bool) - public const uint KCapturedArticuno = 0xF75E52CF; - public const uint KCapturedZapdos = 0xF75E5635; - public const uint KCapturedMoltres = 0xF75E511C; - public const uint KCapturedMewtwo = 0xF75E4DB6; - public const uint KCapturedRaikou = 0xF75E4C03; - public const uint KCapturedEntei = 0xF75E4A50; - public const uint KCapturedSuicune = 0xF75E4F69; - public const uint KCapturedLugia = 0xF75E621A; - public const uint KCapturedHoOh = 0xF75E63CD; - public const uint KCapturedLatias = 0xF760948B; - public const uint KCapturedLatios = 0xF76092D8; - public const uint KCapturedKyogre = 0xF760963E; - public const uint KCapturedGroudon = 0xF76097F1; - public const uint KCapturedRayquaza = 0xF7609B57; - public const uint KCapturedUxie = 0xF76099A4; - public const uint KCapturedMesprit = 0xF7609D0A; - public const uint KCapturedAzelf = 0xF7609EBD; - public const uint KCapturedDialga = 0xF76086F3; - public const uint KCapturedPalkia = 0xF7608540; - public const uint KCapturedHeatran = 0xF7582323; - public const uint KCapturedGiratina = 0xF7582170; - public const uint KCapturedCresselia = 0xF75824D6; - public const uint KCapturedTornadus = 0xF7582689; - public const uint KCapturedThundurus = 0xF758283C; - public const uint KCapturedReshiram = 0xF7582BA2; - public const uint KCapturedZekrom = 0xF7582D55; - public const uint KCapturedLandorus = 0xF75829EF; - public const uint KCapturedKyurem = 0xF7582F08; - public const uint KCapturedXerneas = 0xF75830BB; - public const uint KCapturedYveltal = 0xF75B3AF9; - public const uint KCapturedZygarde = 0xF75B3946; - public const uint KCapturedTapuKoko = 0xF75B3793; - public const uint KCapturedTapuLele = 0xF75B35E0; - public const uint KCapturedTapuBulu = 0xF75B41C5; - public const uint KCapturedTapuFini = 0xF75B4012; - public const uint KCapturedSolgaleo = 0xF75B3E5F; - public const uint KCapturedLunala = 0xF75B3CAC; - public const uint KCapturedNihilego = 0xF75B46DE; - public const uint KCapturedBuzzwole = 0xF769AAC6; - public const uint KCapturedPheromosa = 0xF769AC79; - public const uint KCapturedXurkitree = 0xF769A760; - public const uint KCapturedCelesteela = 0xF769B192; - public const uint KCapturedKartana = 0xF769A913; - public const uint KCapturedGuzzlord = 0xF769B345; - public const uint KCapturedNecrozma = 0xF75B4891; - public const uint KCapturedStakataka = 0xF769B85E; - public const uint KCapturedBlacephalon = 0xF769AFDF; - - // Gift event flags (bool) - public const uint KReceivedGiftBulbasaur = 0x4F240749; - public const uint KReceivedGiftCharmander = 0x178159E5; - public const uint KReceivedGiftSquirtle = 0x08F829F8; - public const uint KReceivedGiftPikachu = 0x9D95E9CA; - public const uint KReceivedGiftEevee = 0x855235FF; - public const uint KReceivedGiftTypeNull = 0x2AB6CECC; - public const uint KReceivedGiftCosmog = 0x52F6F77F; - public const uint KReceivedGiftPoipole = 0x4B3C9063; - public const uint KReceivedGiftToxel = 0xC41B40F7; - - // General purpose event flags (bool) - public const uint KSecretBoxUnlocked = 0x32A339E9; // FSYS_SECRET_BOX - public const uint KUnlockedYComm = 0xDE0EEF6F; // FSYS_LIVE_COMM_OPEN - public const uint KUnlockedJudge = 0xD0E267EB; // FSYS_JUDGE_OPEN - public const uint KUnlockedWaterModeBike = 0x7526A53C; // FSYS_FLAG_NAMINORI_OPEN - public const uint KUnlockedUBsInMaxLair = 0xB99A1E28; // FSYS_CHIKA_UB_OPEN - public const uint KPlayRecordsPikachu = 0x1C74460E; // FSYS_PLAY_LETSGO_PIKACHU - public const uint KPlayRecordsEevee = 0xC804E4AF; // FSYS_PLAY_LETSGO_EEVEE - public const uint KPlayRecordsQuest = 0xBF24DDAE; // FSYS_PLAY_POKEMON_QUEST - - // Values - public const uint KCurrentBox = 0x017C3CBB; // U32 Box Index - public const uint KBoxesUnlocked = 0x71825204; // U32 - public const uint KGameLanguage = 0x0BFDEBA1; // U32 Game Language - public const uint KRepel = 0x9ec079da; // U16 Repel Steps remaining - public const uint KRotoRally = 0x38548020; // U32 Roto Rally Score (99,999 cap) - public const uint KBattleTowerSinglesVictory = 0x436CAF2B; // U32 Singles victories (9,999,999 cap) - public const uint KBattleTowerDoublesVictory = 0x0D477836; // U32 Doubles victories (9,999,999 cap) - public const uint KBattleTowerSinglesStreak = 0x6226F5AD; // U16 Singles Streak (300 cap) - public const uint KBattleTowerDoublesStreak = 0x5F74FCEE; // U16 Doubles Streak (300 cap) - public const uint KStarterChoice = 0x3677602D; // U32 Grookey=0, Scorbunny=1, Sobble=2 - public const uint KDiggingDuoStreakSkill = 0xA0F49CFB; // U32 - public const uint KDiggingDuoStreakStamina = 0x066F38F5; // U32 - public const uint KBirthMonth = 0x0D987D50; // U32 - public const uint KBirthDay = 0x355C8314; // U32 - public const uint KCurrentDexEntry = 0x62743428; // U16 Species ID of last Pokedex entry viewed in Galar Dex - public const uint KCurrentDexEntryR1 = 0x789FF72D; // U16 Species ID of last Pokedex entry viewed in Armor Dex - public const uint KCurrentDexEntryR2 = 0x759FF274; // U16 Species ID of last Pokedex entry viewed in Crown Dex - public const uint KCurrentDex = 0x9CF58395; // U32 Galar=0, Armor=1, Crown=2 - - public const uint KVolumeBackgroundMusic = 0xF8154AC9; // U32 0-10 - public const uint KVolumeSoundEffects = 0x62F05895; // U32 0-10 - public const uint KVolumePokemonCries = 0x1D482A63; // U32 0-10 - - public const uint KRecordCramorantRobo = 0xB9C0ECFC; // cormorant_robo (Cram-o-matic uses) - public const uint KRecordBattleVersion = 0x7A9EF7D9; // battle_rom_mark (Past-gen Pokémon reset for battling in Ranked) - public const uint KRecordSparringTypesCleared = 0xBB1DE8EF; // Number of Types cleared in Restricted Sparring - - public const uint KOptionTextSpeed = 0x92EB0306; // U32 TextSpeedOption - public const uint KOptionBattleEffects = 0xCCC153CD; // U32 OptOut (Show effects by default) - public const uint KOptionBattleStyle = 0x765468C3; // U32 OptOut (Allow Switch by default) - public const uint KOptionSendToBoxes = 0xB1C7C436; // U32 OptIn - public const uint KOptionGiveNicknames = 0x26A1BEDE; // U32 OptOut - public const uint KOptionUseGyroscope = 0x79C56A5C; // U32 OptOut - public const uint KOptionCameraVertical = 0x2846B7DB; // U32 OptOut Invert=1 - public const uint KOptionCameraHorizontal = 0x7D249649; // U32 OptOut Invert=1 - public const uint KOptionCasualControls = 0x3B23B1E2; // U32 OptOut Casual=0 - public const uint KOptionAutoSave = 0xB027F396; // U32 OptOut AutoSave=0 - public const uint KOptionShowNicknames = 0xCA8A8CEE; // U32 OptOut Show=0 - public const uint KOptionShowMoves = 0x9C781AE2; // U32 OptOut Show=0 - public const uint KDojoWattDonationTotal = 0xC7161487; // U32 Amount of Watts donated to Master Dojo - public const uint KDiggingPaWattStreak = 0x68BBA8B1; // U32 Most Watts dug up by the Digging Pa - public const uint KAlolanDiglettFound = 0x4AEA5A7E; // U32 Amount of Alolan Diglett found on Isle of Armor - public const uint KStorySoniaCTQuestProgress = 0xCB135C68; // U32 Swords of Justice Quest progress. Values are from 0-7; 7=completed - public const uint KBikeBoostChargeSteps = 0x57F29628; // U32 Current step counter, fully charged when this value matches KBikeBoostChargeLimit - public const uint KBikeBoostChargeLimit = 0xF64719D9; // U32 Steps to charge bike boost, starts at 128 -> 64 when fully upgraded - - public const uint KSparringStreakNormal = 0xDB5E16CB; // U32 Best Normal-Type Restricted Sparring Streak - public const uint KSparringNormalPartySlot1Species = 0x7BF09DD3; // U16 Species ID of 1st PKM used in party - public const uint KSparringNormalPartySlot2Species = 0x7AF09C40; // U16 Species ID of 2nd PKM used in party - public const uint KSparringNormalPartySlot3Species = 0x7DF0A0F9; // U16 Species ID of 3rd PKM used in party - public const uint KSparringNormalPartySlot1Gender = 0xF8FB2876; // U32 Gender ID of 1st PKM used in party - public const uint KSparringNormalPartySlot2Gender = 0xF9FB2A09; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringNormalPartySlot3Gender = 0xF6FB2550; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringNormalPartySlot1Form = 0xE5181ED2; // U32 Form ID of 1st PKM used in party - public const uint KSparringNormalPartySlot2Form = 0xE6182065; // U32 Form ID of 2nd PKM used in party - public const uint KSparringNormalPartySlot3Form = 0xE3181BAC; // U32 Form ID of 3rd PKM used in party - public const uint KSparringNormalPartySlot1EC = 0xE95199D1; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringNormalPartySlot2EC = 0xE851983E; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringNormalPartySlot3EC = 0xE75196AB; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakFire = 0xD25E08A0; // U32 Best Fire-Type Restricted Sparring Streak - public const uint KSparringFirePartySlot1Species = 0x455C523A; // U16 Species ID of 1st PKM used in party - public const uint KSparringFirePartySlot2Species = 0x465C53CD; // U16 Species ID of 2nd PKM used in party - public const uint KSparringFirePartySlot3Species = 0x435C4F14; // U16 Species ID of 3rd PKM used in party - public const uint KSparringFirePartySlot1Gender = 0x4E5271EF; // U32 Gender ID of 1st PKM used in party - public const uint KSparringFirePartySlot2Gender = 0x4D52705C; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringFirePartySlot3Gender = 0x50527515; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringFirePartySlot1Form = 0x41E9A3FB; // U32 Form ID of 1st PKM used in party - public const uint KSparringFirePartySlot2Form = 0x40E9A268; // U32 Form ID of 2nd PKM used in party - public const uint KSparringFirePartySlot3Form = 0x43E9A721; // U32 Form ID of 3rd PKM used in party - public const uint KSparringFirePartySlot1EC = 0x1F637658; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringFirePartySlot2EC = 0x206377EB; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringFirePartySlot3EC = 0x2163797E; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakWater = 0xD55BCEC2; // U32 Best Water-Type Restricted Sparring Streak - public const uint KSparringWaterPartySlot1Species = 0x30396510; // U16 Species ID of 1st PKM used in party - public const uint KSparringWaterPartySlot2Species = 0x313966A3; // U16 Species ID of 2nd PKM used in party - public const uint KSparringWaterPartySlot3Species = 0x32396836; // U16 Species ID of 3rd PKM used in party - public const uint KSparringWaterPartySlot1Gender = 0xC3264459; // U32 Gender ID of 1st PKM used in party - public const uint KSparringWaterPartySlot2Gender = 0xC22642C6; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringWaterPartySlot3Gender = 0xC1264133; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringWaterPartySlot1Form = 0x9AB09895; // U32 Form ID of 1st PKM used in party - public const uint KSparringWaterPartySlot2Form = 0x99B09702; // U32 Form ID of 2nd PKM used in party - public const uint KSparringWaterPartySlot3Form = 0x98B0956F; // U32 Form ID of 3rd PKM used in party - public const uint KSparringWaterPartySlot1EC = 0x1DE9496E; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringWaterPartySlot2EC = 0x1EE94B01; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringWaterPartySlot3EC = 0x1BE94648; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakElectric = 0xD35BCB9C; // U32 Best Electric-Type Restricted Sparring Streak - public const uint KSparringElectricPartySlot1Species = 0x1E5FB12E; // U16 Species ID of 1st PKM used in party - public const uint KSparringElectricPartySlot2Species = 0x1F5FB2C1; // U16 Species ID of 2nd PKM used in party - public const uint KSparringElectricPartySlot3Species = 0x1C5FAE08; // U16 Species ID of 3rd PKM used in party - public const uint KSparringElectricPartySlot1Gender = 0x3DF2EAF7; // U32 Gender ID of 1st PKM used in party - public const uint KSparringElectricPartySlot2Gender = 0x3CF2E964; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringElectricPartySlot3Gender = 0x3FF2EE1D; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringElectricPartySlot1Form = 0xE74A9573; // U32 Form ID of 1st PKM used in party - public const uint KSparringElectricPartySlot2Form = 0xE64A93E0; // U32 Form ID of 2nd PKM used in party - public const uint KSparringElectricPartySlot3Form = 0xE94A9899; // U32 Form ID of 3rd PKM used in party - public const uint KSparringElectricPartySlot1EC = 0x2FC2FD50; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringElectricPartySlot2EC = 0x30C2FEE3; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringElectricPartySlot3EC = 0x31C30076; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakGrass = 0xD65BD055; // U32 Best Grass-Type Restricted Sparring Streak - public const uint KSparringGrassPartySlot1Species = 0x70973021; // U16 Species ID of 1st PKM used in party - public const uint KSparringGrassPartySlot2Species = 0x6F972E8E; // U16 Species ID of 2nd PKM used in party - public const uint KSparringGrassPartySlot3Species = 0x6E972CFB; // U16 Species ID of 3rd PKM used in party - public const uint KSparringGrassPartySlot1Gender = 0x2454C888; // U32 Gender ID of 1st PKM used in party - public const uint KSparringGrassPartySlot2Gender = 0x2554CA1B; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringGrassPartySlot3Gender = 0x2654CBAE; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringGrassPartySlot1Form = 0xB3FF0924; // U32 Form ID of 1st PKM used in party - public const uint KSparringGrassPartySlot2Form = 0xB4FF0AB7; // U32 Form ID of 2nd PKM used in party - public const uint KSparringGrassPartySlot3Form = 0xB5FF0C4A; // U32 Form ID of 3rd PKM used in party - public const uint KSparringGrassPartySlot1EC = 0x044B26FF; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringGrassPartySlot2EC = 0x034B256C; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringGrassPartySlot3EC = 0x064B2A25; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakIce = 0xD15BC876; // U32 Best Ice-Type Restricted Sparring Streak - public const uint KSparringIcePartySlot1Species = 0x892112D4; // U16 Species ID of 1st PKM used in party - public const uint KSparringIcePartySlot2Species = 0x8A211467; // U16 Species ID of 2nd PKM used in party - public const uint KSparringIcePartySlot3Species = 0x8B2115FA; // U16 Species ID of 3rd PKM used in party - public const uint KSparringIcePartySlot1Gender = 0x355AA71D; // U32 Gender ID of 1st PKM used in party - public const uint KSparringIcePartySlot2Gender = 0x345AA58A; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringIcePartySlot3Gender = 0x335AA3F7; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringIcePartySlot1Form = 0xE1C853B1; // U32 Form ID of 1st PKM used in party - public const uint KSparringIcePartySlot2Form = 0xE0C8521E; // U32 Form ID of 2nd PKM used in party - public const uint KSparringIcePartySlot3Form = 0xDFC8508B; // U32 Form ID of 3rd PKM used in party - public const uint KSparringIcePartySlot1EC = 0xEFCE9172; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringIcePartySlot2EC = 0xF0CE9305; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringIcePartySlot3EC = 0xEDCE8E4C; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakFighting = 0xDA5E1538; // U32 Best Fighting-Type Restricted Sparring Streak - public const uint KSparringFightingPartySlot1Species = 0x153FD7E2; // U16 Species ID of 1st PKM used in party - public const uint KSparringFightingPartySlot2Species = 0x163FD975; // U16 Species ID of 2nd PKM used in party - public const uint KSparringFightingPartySlot3Species = 0x133FD4BC; // U16 Species ID of 3rd PKM used in party - public const uint KSparringFightingPartySlot1Gender = 0x7E6EEC47; // U32 Gender ID of 1st PKM used in party - public const uint KSparringFightingPartySlot2Gender = 0x7D6EEAB4; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringFightingPartySlot3Gender = 0x806EEF6D; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringFightingPartySlot1Form = 0x1EC26C83; // U32 Form ID of 1st PKM used in party - public const uint KSparringFightingPartySlot2Form = 0x1DC26AF0; // U32 Form ID of 2nd PKM used in party - public const uint KSparringFightingPartySlot3Form = 0x20C26FA9; // U32 Form ID of 3rd PKM used in party - public const uint KSparringFightingPartySlot1EC = 0x62A0A180; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringFightingPartySlot2EC = 0x63A0A313; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringFightingPartySlot3EC = 0x64A0A4A6; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakPoison = 0xDC5E185E; // U32 Best Poison-Type Restricted Sparring Streak - public const uint KSparringPoisonPartySlot1Species = 0x3BFF8084; // U16 Species ID of 1st PKM used in party - public const uint KSparringPoisonPartySlot2Species = 0x3CFF8217; // U16 Species ID of 2nd PKM used in party - public const uint KSparringPoisonPartySlot3Species = 0x3DFF83AA; // U16 Species ID of 3rd PKM used in party - public const uint KSparringPoisonPartySlot1Gender = 0x11850B29; // U32 Gender ID of 1st PKM used in party - public const uint KSparringPoisonPartySlot2Gender = 0x10850996; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringPoisonPartySlot3Gender = 0x0F850803; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringPoisonPartySlot1Form = 0xD0EB3B25; // U32 Form ID of 1st PKM used in party - public const uint KSparringPoisonPartySlot2Form = 0xCFEB3992; // U32 Form ID of 2nd PKM used in party - public const uint KSparringPoisonPartySlot3Form = 0xCEEB37FF; // U32 Form ID of 3rd PKM used in party - public const uint KSparringPoisonPartySlot1EC = 0x171AE45E; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringPoisonPartySlot2EC = 0x181AE5F1; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringPoisonPartySlot3EC = 0x151AE138; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakGround = 0xDF5E1D17; // U32 Best Ground-Type Restricted Sparring Streak - public const uint KSparringGroundPartySlot1Species = 0x29BC6D6F; // U16 Species ID of 1st PKM used in party - public const uint KSparringGroundPartySlot2Species = 0x28BC6BDC; // U16 Species ID of 2nd PKM used in party - public const uint KSparringGroundPartySlot3Species = 0x2BBC7095; // U16 Species ID of 3rd PKM used in party - public const uint KSparringGroundPartySlot1Gender = 0x69F256BA; // U32 Gender ID of 1st PKM used in party - public const uint KSparringGroundPartySlot2Gender = 0x6AF2584D; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringGroundPartySlot3Gender = 0x67F25394; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringGroundPartySlot1Form = 0x2C7F8BCE; // U32 Form ID of 1st PKM used in party - public const uint KSparringGroundPartySlot2Form = 0x2D7F8D61; // U32 Form ID of 2nd PKM used in party - public const uint KSparringGroundPartySlot3Form = 0x2A7F88A8; // U32 Form ID of 3rd PKM used in party - public const uint KSparringGroundPartySlot1EC = 0xBA495F35; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringGroundPartySlot2EC = 0xB9495DA2; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringGroundPartySlot3EC = 0xB8495C0F; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakFlying = 0xDD5E19F1; // U32 Best Flying-Type Restricted Sparring Streak - public const uint KSparringFlyingPartySlot1Species = 0xA17311F5; // U16 Species ID of 1st PKM used in party - public const uint KSparringFlyingPartySlot2Species = 0xA0731062; // U16 Species ID of 2nd PKM used in party - public const uint KSparringFlyingPartySlot3Species = 0x9F730ECF; // U16 Species ID of 3rd PKM used in party - public const uint KSparringFlyingPartySlot1Gender = 0x2B232D98; // U32 Gender ID of 1st PKM used in party - public const uint KSparringFlyingPartySlot2Gender = 0x2C232F2B; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringFlyingPartySlot3Gender = 0x2D2330BE; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringFlyingPartySlot1Form = 0x23E747F4; // U32 Form ID of 1st PKM used in party - public const uint KSparringFlyingPartySlot2Form = 0x24E74987; // U32 Form ID of 2nd PKM used in party - public const uint KSparringFlyingPartySlot3Form = 0x25E74B1A; // U32 Form ID of 3rd PKM used in party - public const uint KSparringFlyingPartySlot1EC = 0x4292BAAF; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringFlyingPartySlot2EC = 0x4192B91C; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringFlyingPartySlot3EC = 0x4492BDD5; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakPsychic = 0xD45BCD2F; // U32 Best Psychic-Type Restricted Sparring Streak - public const uint KSparringPsychicPartySlot1Species = 0x04C18EBF; // U16 Species ID of 1st PKM used in party - public const uint KSparringPsychicPartySlot2Species = 0x03C18D2C; // U16 Species ID of 2nd PKM used in party - public const uint KSparringPsychicPartySlot3Species = 0x06C191E5; // U16 Species ID of 3rd PKM used in party - public const uint KSparringPsychicPartySlot1Gender = 0x70EEC566; // U32 Gender ID of 1st PKM used in party - public const uint KSparringPsychicPartySlot2Gender = 0x71EEC6F9; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringPsychicPartySlot3Gender = 0x6EEEC240; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringPsychicPartySlot1Form = 0x81D70402; // U32 Form ID of 1st PKM used in party - public const uint KSparringPsychicPartySlot2Form = 0x82D70595; // U32 Form ID of 2nd PKM used in party - public const uint KSparringPsychicPartySlot3Form = 0x7FD700DC; // U32 Form ID of 3rd PKM used in party - public const uint KSparringPsychicPartySlot1EC = 0x896D7D61; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringPsychicPartySlot2EC = 0x886D7BCE; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringPsychicPartySlot3EC = 0x876D7A3B; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakBug = 0xE15E203D; // U32 Best Bug-Type Restricted Sparring Streak - public const uint KSparringBugPartySlot1Species = 0xE9C80191; // U16 Species ID of 1st PKM used in party - public const uint KSparringBugPartySlot2Species = 0xE8C7FFFE; // U16 Species ID of 2nd PKM used in party - public const uint KSparringBugPartySlot3Species = 0xE7C7FE6B; // U16 Species ID of 3rd PKM used in party - public const uint KSparringBugPartySlot1Gender = 0xFC1AF2FC; // U32 Gender ID of 1st PKM used in party - public const uint KSparringBugPartySlot2Gender = 0xFD1AF48F; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringBugPartySlot3Gender = 0xFE1AF622; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringBugPartySlot1Form = 0x32EF5030; // U32 Form ID of 1st PKM used in party - public const uint KSparringBugPartySlot2Form = 0x33EF51C3; // U32 Form ID of 2nd PKM used in party - public const uint KSparringBugPartySlot3Form = 0x34EF5356; // U32 Form ID of 3rd PKM used in party - public const uint KSparringBugPartySlot1EC = 0x7B7A3613; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringBugPartySlot2EC = 0x7A7A3480; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringBugPartySlot3EC = 0x7D7A3939; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakRock = 0xDE5E1B84; // U32 Best Rock-Type Restricted Sparring Streak - public const uint KSparringRockPartySlot1Species = 0xFE44971E; // U16 Species ID of 1st PKM used in party - public const uint KSparringRockPartySlot2Species = 0xFF4498B1; // U16 Species ID of 2nd PKM used in party - public const uint KSparringRockPartySlot3Species = 0xFC4493F8; // U16 Species ID of 3rd PKM used in party - public const uint KSparringRockPartySlot1Gender = 0x96A7618B; // U32 Gender ID of 1st PKM used in party - public const uint KSparringRockPartySlot2Gender = 0x95A75FF8; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringRockPartySlot3Gender = 0x98A764B1; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringRockPartySlot1Form = 0x92E09FDF; // U32 Form ID of 1st PKM used in party - public const uint KSparringRockPartySlot2Form = 0x91E09E4C; // U32 Form ID of 2nd PKM used in party - public const uint KSparringRockPartySlot3Form = 0x94E0A305; // U32 Form ID of 3rd PKM used in party - public const uint KSparringRockPartySlot1EC = 0x54D5CDC4; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringRockPartySlot2EC = 0x55D5CF57; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringRockPartySlot3EC = 0x56D5D0EA; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakGhost = 0xE05E1EAA; // U32 Best Ghost-Type Restricted Sparring Streak - public const uint KSparringGhostPartySlot1Species = 0x63170940; // U16 Species ID of 1st PKM used in party - public const uint KSparringGhostPartySlot2Species = 0x64170AD3; // U16 Species ID of 2nd PKM used in party - public const uint KSparringGhostPartySlot3Species = 0x65170C66; // U16 Species ID of 3rd PKM used in party - public const uint KSparringGhostPartySlot1Gender = 0xBC29D5AD; // U32 Gender ID of 1st PKM used in party - public const uint KSparringGhostPartySlot2Gender = 0xBB29D41A; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringGhostPartySlot3Gender = 0xBA29D287; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringGhostPartySlot1Form = 0xFEB64141; // U32 Form ID of 1st PKM used in party - public const uint KSparringGhostPartySlot2Form = 0xFDB63FAE; // U32 Form ID of 2nd PKM used in party - public const uint KSparringGhostPartySlot3Form = 0xFCB63E1B; // U32 Form ID of 3rd PKM used in party - public const uint KSparringGhostPartySlot1EC = 0x14C97022; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringGhostPartySlot2EC = 0x15C971B5; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringGhostPartySlot3EC = 0x12C96CFC; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakDragon = 0xD25BCA09; // U32 Best Dragon-Type Restricted Sparring Streak - public const uint KSparringDragonPartySlot1Species = 0xC18E2C05; // U16 Species ID of 1st PKM used in party - public const uint KSparringDragonPartySlot2Species = 0xC08E2A72; // U16 Species ID of 2nd PKM used in party - public const uint KSparringDragonPartySlot3Species = 0xBF8E28DF; // U16 Species ID of 3rd PKM used in party - public const uint KSparringDragonPartySlot1Gender = 0xAEF960AC; // U32 Gender ID of 1st PKM used in party - public const uint KSparringDragonPartySlot2Gender = 0xAFF9623F; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringDragonPartySlot3Gender = 0xB0F963D2; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringDragonPartySlot1Form = 0x5C548FE0; // U32 Form ID of 1st PKM used in party - public const uint KSparringDragonPartySlot2Form = 0x5D549173; // U32 Form ID of 2nd PKM used in party - public const uint KSparringDragonPartySlot3Form = 0x5E549306; // U32 Form ID of 3rd PKM used in party - public const uint KSparringDragonPartySlot1EC = 0x1B9619A3; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringDragonPartySlot2EC = 0x1A961810; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringDragonPartySlot3EC = 0x1D961CC9; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakDark = 0xCF5BC550; // U32 Best Dark-Type Restricted Sparring Streak - public const uint KSparringDarkPartySlot1Species = 0xD6F84432; // U16 Species ID of 1st PKM used in party - public const uint KSparringDarkPartySlot2Species = 0xD7F845C5; // U16 Species ID of 2nd PKM used in party - public const uint KSparringDarkPartySlot3Species = 0xD4F8410C; // U16 Species ID of 3rd PKM used in party - public const uint KSparringDarkPartySlot1Gender = 0x768C477B; // U32 Gender ID of 1st PKM used in party - public const uint KSparringDarkPartySlot2Gender = 0x758C45E8; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringDarkPartySlot3Gender = 0x788C4AA1; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringDarkPartySlot1Form = 0x2F9F850F; // U32 Form ID of 1st PKM used in party - public const uint KSparringDarkPartySlot2Form = 0x2E9F837C; // U32 Form ID of 2nd PKM used in party - public const uint KSparringDarkPartySlot3Form = 0x319F8835; // U32 Form ID of 3rd PKM used in party - public const uint KSparringDarkPartySlot1EC = 0xA1F76014; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringDarkPartySlot2EC = 0xA2F761A7; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringDarkPartySlot3EC = 0xA3F7633A; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakSteel = 0xD35E0A33; // U32 Best Steel-Type Restricted Sparring Streak - public const uint KSparringSteelPartySlot1Species = 0x72115D0B; // U16 Species ID of 1st PKM used in party - public const uint KSparringSteelPartySlot2Species = 0x71115B78; // U16 Species ID of 2nd PKM used in party - public const uint KSparringSteelPartySlot3Species = 0x74116031; // U16 Species ID of 3rd PKM used in party - public const uint KSparringSteelPartySlot1Gender = 0x22DA9B9E; // U32 Gender ID of 1st PKM used in party - public const uint KSparringSteelPartySlot2Gender = 0x23DA9D31; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringSteelPartySlot3Gender = 0x20DA9878; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringSteelPartySlot1Form = 0x1534992A; // U32 Form ID of 1st PKM used in party - public const uint KSparringSteelPartySlot2Form = 0x16349ABD; // U32 Form ID of 2nd PKM used in party - public const uint KSparringSteelPartySlot3Form = 0x13349604; // U32 Form ID of 3rd PKM used in party - public const uint KSparringSteelPartySlot1EC = 0x05C553E9; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringSteelPartySlot2EC = 0x04C55256; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringSteelPartySlot3EC = 0x03C550C3; // U32 Encryption Constant of 3rd PKM used in party - - public const uint KSparringStreakFairy = 0xD05BC6E3; // U32 Best Fairy-Type Restricted Sparring Streak - public const uint KSparringFairyPartySlot1Species = 0x02BFCC63; // U16 Species ID of 1st PKM used in party - public const uint KSparringFairyPartySlot2Species = 0x01BFCAD0; // U16 Species ID of 2nd PKM used in party - public const uint KSparringFairyPartySlot3Species = 0x04BFCF89; // U16 Species ID of 3rd PKM used in party - public const uint KSparringFairyPartySlot1Gender = 0x49D73CAA; // U32 Gender ID of 1st PKM used in party - public const uint KSparringFairyPartySlot2Gender = 0x4AD73E3D; // U32 Gender ID of 2nd PKM used in party - public const uint KSparringFairyPartySlot3Gender = 0x47D73984; // U32 Gender ID of 3rd PKM used in party - public const uint KSparringFairyPartySlot1Form = 0x77442151; // U32 Form ID of 1st PKM used in party - public const uint KSparringFairyPartySlot2Form = 0x76441FBE; // U32 Form ID of 2nd PKM used in party - public const uint KSparringFairyPartySlot3Form = 0x74441C98; // U32 Form ID of 3rd PKM used in party - public const uint KSparringFairyPartySlot1EC = 0xC117C445; // U32 Encryption Constant of 1st PKM used in party - public const uint KSparringFairyPartySlot2EC = 0xC017C2B2; // U32 Encryption Constant of 2nd PKM used in party - public const uint KSparringFairyPartySlot3EC = 0xBF17C11F; // U32 Encryption Constant of 3rd PKM used in party - public const uint KSparringFairyPartySlot1Sweet = 0xB14624FF; // U32 Alcremie Sweet ID if 1st PKM used in party, otherwise -1 - public const uint KSparringFairyPartySlot2Sweet = 0xB046236C; // U32 Alcremie Sweet ID if 2nd PKM used in party, otherwise -1 - public const uint KSparringFairyPartySlot3Sweet = 0xB3462825; // U32 Alcremie Sweet ID if 3rd PKM used in party, otherwise -1 - - public const uint KRegielekiOrRegidragoPattern = 0xCF90B39A; // U32 Chosen Pattern for Split-Decision Ruins (0 = not chosen, 1 = electric, 2 = dragon) - public const uint KFootprintPercentageCobalion = 0x4D50B655; // U32 Footprints of Cobalion collected on Crown Tundra; values go from 0-100 - public const uint KFootprintPercentageTerrakion = 0x771E4C88; // U32 Footprints of Terrakion collected on Crown Tundra; values from 0-100 - public const uint KFootprintPercentageVirizion = 0xAD67A297; // U32 Footprints of Virizion collected on Crown Tundra; values go from 0-100 - public const uint KPlayersInteractedOnline = 0x31A13425; // U32 Number of Players interacted with online - public const uint KMaxLairSpeciesID1Noted = 0x6F669A35; // U32 Max Lair Species 1 Noted - public const uint KMaxLairSpeciesID2Noted = 0x6F66951C; // U32 Max Lair Species 2 Noted - public const uint KMaxLairSpeciesID3Noted = 0x6F6696CF; // U32 Max Lair Species 3 Noted - public const uint KMaxLairEndlessStreak = 0x7F4B4B10; // U32 Endless Dynamax Adventure Best Streak - public const uint KMaxLairDisconnectStreak = 0x8EAEB8FF; // U32 Value of 3 will have you pay a Dynite Ore fee upon entry - public const uint KMaxLairPeoniaSpeciesHint = 0xF26B9151; // U32 Species ID for Peonia to hint - public const uint KMaxLairRentalChoiceSeed = 0x0D74AA40; // U64 seed used to pick Dynamax Adventure rental and encounter templates - - public const uint KGSTVictoriesTotal = 0x9D6727F6; // U32 Total Galarian Star Tournament victories - public const uint KGSTVictoriesAvery = 0x3934BEC0; // U32 Galarian Star Tournament victories with Avery - public const uint KGSTVictoriesKlara = 0xE9131991; // U32 Galarian Star Tournament victories with Klara - public const uint KGSTVictoriesMustard = 0x7742B542; // U32 Galarian Star Tournament victories with Mustard - public const uint KGSTVictoriesPeony = 0x9A535FA0; // U32 Galarian Star Tournament victories with Peony - public const uint KGSTVictoriesLeon = 0xFEE68CE1; // U32 Galarian Star Tournament victories with Leon - public const uint KGSTVictoriesBede = 0x2D0520CD; // U32 Galarian Star Tournament victories with Bede - public const uint KGSTVictoriesMarnie = 0xFB5133BC; // U32 Galarian Star Tournament victories with Marnie - public const uint KGSTVictoriesMilo = 0x24B9E3CC; // U32 Galarian Star Tournament victories with Milo - public const uint KGSTVictoriesNessa = 0xB19386DE; // U32 Galarian Star Tournament victories with Nessa - public const uint KGSTVictoriesKabu = 0x3576EE34; // U32 Galarian Star Tournament victories with Kabu - public const uint KGSTVictoriesPiers = 0xB95B1BB9; // U32 Galarian Star Tournament victories with Piers - public const uint KGSTVictoriesRaihan = 0x343E6FC1; // U32 Galarian Star Tournament victories with Raihan - public const uint KGSTVictoriesBea = 0x6371183B; // U32 Galarian Star Tournament victories with Bea - public const uint KGSTVictoriesGordie = 0xA2C094C7; // U32 Galarian Star Tournament victories with Gordie - public const uint KGSTVictoriesAllister = 0x9E32AE34; // U32 Galarian Star Tournament victories with Allister - public const uint KGSTVictoriesMelony = 0x06C0FBC8; // U32 Galarian Star Tournament victories with Melony - public const uint KGSTVictoriesSordward = 0xE3ED8F16; // U32 Galarian Star Tournament victories with Sordward - public const uint KGSTVictoriesShielbert = 0xC0D49E2D; // U32 Galarian Star Tournament victories with Shielbert - public const uint KGSTVictoriesHop = 0xEB07C276; // U32 Galarian Star Tournament victories with Hop - public const uint KGSTVictoriesOpal = 0xDBE374D7; // U32 Galarian Star Tournament victories with Opal + BlockInfo = sav.AllBlocks; + BoxInfo = new Box8(sav, GetBlock(KBox)); + PartyInfo = new Party8(sav, GetBlock(KParty)); + Items = new MyItem8(sav, GetBlock(KItem)); + Zukan = new Zukan8(sav, GetBlock(KZukan), GetBlockSafe(KZukanR1), GetBlockSafe(KZukanR2)); + MyStatus = new MyStatus8(sav, GetBlock(KMyStatus)); + Misc = new Misc8(sav, GetBlock(KMisc)); + BoxLayout = new BoxLayout8(sav, GetBlock(KBoxLayout)); + TrainerCard = new TrainerCard8(sav, GetBlock(KTrainerCard)); + Played = new PlayTime8(sav, GetBlock(KPlayTime)); + Fused = new Fused8(sav, GetBlock(KFused)); + Daycare = new Daycare8(sav, GetBlock(KDaycare)); + Records = new Record8(sav, GetBlock(KRecord), Core.Records.MaxType_SWSH); + Fashion = new FashionUnlock8(sav, GetBlock(KFashionUnlock)); + Raid = new RaidSpawnList8(sav, GetBlock(KRaidSpawnList), RaidSpawnList8.RaidCountLegal_O0); + RaidArmor = new RaidSpawnList8(sav, GetBlockSafe(KRaidSpawnListR1), RaidSpawnList8.RaidCountLegal_R1); + RaidCrown = new RaidSpawnList8(sav, GetBlockSafe(KRaidSpawnListR2), RaidSpawnList8.RaidCountLegal_R2); + TitleScreen = new TitleScreen8(sav, GetBlock(KTitleScreenTeam)); + TeamIndexes = new TeamIndexes8(sav, GetBlock(KTeamIndexes)); + FameTime = new HallOfFameTime8(sav, GetBlock(KEnteredHallOfFame)); } + + // Arrays (Blocks) + private const uint KTeamNames = 0x1920C1E4; // Team 1, 2...6 ((10 + terminator)*6 char16 strings) + private const uint KBoxLayout = 0x19722c89; // Box Names + public const uint KBoxWallpapers = 0x2EB1B190; // Box Wallpapers + private const uint KMenuButtons = 0xB1DDDCA8; // X Menu Button Order + + // Objects (Blocks) + private const uint KBox = 0x0d66012c; // Box Data + private const uint KMysteryGift = 0x112d5141; // Mystery Gift Data + private const uint KItem = 0x1177c2c4; // Items + private const uint KCoordinates = 0x16aaa7fa; // Coordinates? + private const uint KMisc = 0x1b882b09; // Money + private const uint KParty = 0x2985fe5d; // Party Data + private const uint KDaycare = 0x2d6fba6a; // Daycare slots (2 daycares) + private const uint KTeamIndexes = 0x33F39467; // Team Indexes for competition + private const uint KRecord = 0x37da95a3; + private const uint KZukan = 0x4716c404; // ZukanData_Pokemon + private const uint KZukanR1 = 0x3F936BA9; // ZukanData_PokemonR1 (Armor) + private const uint KZukanR2 = 0x3C9366F0; // ZukanData_PokemonR2 (Crown) + private const uint KPokedexRecommendation = 0xC3FB9E77; // Pokédex recommendations + private const uint KCurryDex = 0x6EB72940; // Curry Dex + private const uint KTrainerCard = 0x874da6fa; // Trainer Card + private const uint KPlayTime = 0x8cbbfd90; // Time Played + private const uint KRaidSpawnList = 0x9033eb7b; // Nest current values (hash, seed, meta) + private const uint KRaidSpawnListR1 = 0x158DA896; // Raid Data for DLC1 + private const uint KRaidSpawnListR2 = 0x148DA703; // Raid Data for DLC2 + private const uint KFused = 0xc0de5c5f; // Fused PKM (*3) + public const uint KFusedCalyrex = 0xC37F267B; // Fused Horse + private const uint KFashionUnlock = 0xd224f9ac; // Fashion unlock bool array (owned for (each apparel type) * 0x80, then another array for "new") + private const uint KTitleScreenTeam = 0xE9BE28BF; // Title Screen Team details + public const uint KEnteredHallOfFame = 0xE2F6E456; // U64 Unix Timestamp + private const uint KMyStatus = 0xf25c070e; // Trainer Details + private const uint KFriendLeagueCards = 0x28e707f5; // League Cards received from other players + private const uint KNPCLeagueCards = 0xb1c26fb0; // League Cards received from NPCs + private const uint KNPCLeagueCardsR1 = 0xb868ee77; // League Cards received from NPCs on The Isle of Armor + private const uint KNPCLeagueCardsR2 = 0xB968F00A; // League Cards received from NPCs on The Crown Tundra + private const uint KTrainer1EndlessRecordData = 0x79D787CB; // Trainer 1's Data of Best Endless Dynamax Adventure Record + private const uint KTrainer2EndlessRecordData = 0x78D78638; // Trainer 2's Data of Best Endless Dynamax Adventure Record + private const uint KTrainer3EndlessRecordData = 0x7BD78AF1; // Trainer 3's Data of Best Endless Dynamax Adventure Record + private const uint KTrainer4EndlessRecordData = 0x7AD7895E; // Trainer 4's Data of Best Endless Dynamax Adventure Record + private const uint KPokeJobStorage = 0xB25C772B; // Pokémon storage while they are doing Jobs + + // Rental Teams - Objects (Blocks) + private const uint KRentalTeam1 = 0x149A1DD0; + private const uint KRentalTeam2 = 0x179A2289; + private const uint KRentalTeam3 = 0x169A20F6; + private const uint KRentalTeam4 = 0x199A25AF; + private const uint KRentalTeam5 = 0x189A241C; + //private const uint KRentalTeamUnused = 0x159A1F63; // does not exist + + // Download Rules + private const uint KDownloadRules1 = 0xEEF1B186; + private const uint KDownloadRules2 = 0xEFF1B319; + private const uint KDownloadRules3 = 0xF0F1B4AC; + private const uint KDownloadRules4 = 0xF1F1B63F; + private const uint KDownloadRules5 = 0xF2F1B7D2; + private const uint KDownloadRules6 = 0xF3F1B965; + //private const uint KDownloadRulesX = 0xF4F1BAF8; // does not exist + //private const uint KDownloadRulesX = 0xF5F1BC8B; // does not exist + //private const uint KDownloadRulesX = 0xF6F1BE1E; // does not exist + //private const uint KDownloadRulesX = 0xF7F1BFB1; // does not exist + //private const uint KDownloadRulesX = 0xF8F1C144; // does not exist + //private const uint KDownloadRulesX = 0xF9F1C2D7; // does not exist + private const uint KDownloadRulesU1 = 0xFAF1C46A; + private const uint KDownloadRulesU2 = 0xFBF1C5FD; + + private const uint KOfficialCompetition = 0xEEE5A3F8; + + // Raid DLC Flatbuffer Storage Objects (Blocks) + private const uint KDropRewards = 0x680EEB85; // drop_rewards + private const uint KDaiEncount = 0xAD3920F5; // dai_encount + private const uint KNormalEncount = 0xAD9DFA6A; // normal_encount + private const uint KBonusRewards = 0xEFCAE04E; // bonus_rewards + + private const uint KNormalEncountRigel1 = 0x0E615A8C; // normal_encount_rigel1 + private const uint KNormalEncountRigel2 = 0x11615F45; // normal_encount_rigel2 + + // Static Encounter event flags (bool) + public const uint KCapturedGalarianArticuno = 0x4CAB7DA6; // EF_R2_FURIIZAA_GET + public const uint KCapturedGalarianZapdos = 0x284CBECF; // EF_R2_SANDAA_GET + public const uint KCapturedGalarianMoltres = 0xF1E493AA; // EF_R2_FAIYAA_GET + public const uint KCapturedRegirock = 0xEE3F84E6; // FE_CAPTURE_REGIROCK + public const uint KCapturedRegice = 0xDAB3DD3A; // FE_CAPTURE_REGIICE + public const uint KCapturedRegisteel = 0xEE1FD86E; // FE_CAPTURE_REGISTEEL + public const uint KCapturedSpiritomb = 0x11C12005; // FE_CAPTURE_MIKARUGE + public const uint KCapturedRegigigas = 0xC4308A93; // FE_CAPTURE_REGIGIGASU + public const uint KCapturedCobalion = 0xBB305227; // z_wr0312_SymbolEncountPokemonGimmickSpawner_WR03_Sanjyuusi + public const uint KCapturedTerrakion = 0x750C83A4; // z_wr0322_SymbolEncountPokemonGimmickSpawner_WR03_Sanjyuusi + public const uint KCapturedVirizion = 0x1A27DF2C; // z_wr0304_SymbolEncountPokemonGimmickSpawner_WR03_Sanjyuusi + public const uint KCapturedKeldeo = 0xA097DE31; // z_wr0321_SymbolEncountPokemonGimmickSpawner_WR03_Sanjyuusi + public const uint KCapturedRegieleki = 0x4F4AEC32; // FE_CAPTURE_REDEN + public const uint KCapturedRegidrago = 0x4F30F174; // FE_CAPTURE_REDRA + + // Max Lair event flags (bool) + public const uint KCapturedArticuno = 0xF75E52CF; + public const uint KCapturedZapdos = 0xF75E5635; + public const uint KCapturedMoltres = 0xF75E511C; + public const uint KCapturedMewtwo = 0xF75E4DB6; + public const uint KCapturedRaikou = 0xF75E4C03; + public const uint KCapturedEntei = 0xF75E4A50; + public const uint KCapturedSuicune = 0xF75E4F69; + public const uint KCapturedLugia = 0xF75E621A; + public const uint KCapturedHoOh = 0xF75E63CD; + public const uint KCapturedLatias = 0xF760948B; + public const uint KCapturedLatios = 0xF76092D8; + public const uint KCapturedKyogre = 0xF760963E; + public const uint KCapturedGroudon = 0xF76097F1; + public const uint KCapturedRayquaza = 0xF7609B57; + public const uint KCapturedUxie = 0xF76099A4; + public const uint KCapturedMesprit = 0xF7609D0A; + public const uint KCapturedAzelf = 0xF7609EBD; + public const uint KCapturedDialga = 0xF76086F3; + public const uint KCapturedPalkia = 0xF7608540; + public const uint KCapturedHeatran = 0xF7582323; + public const uint KCapturedGiratina = 0xF7582170; + public const uint KCapturedCresselia = 0xF75824D6; + public const uint KCapturedTornadus = 0xF7582689; + public const uint KCapturedThundurus = 0xF758283C; + public const uint KCapturedReshiram = 0xF7582BA2; + public const uint KCapturedZekrom = 0xF7582D55; + public const uint KCapturedLandorus = 0xF75829EF; + public const uint KCapturedKyurem = 0xF7582F08; + public const uint KCapturedXerneas = 0xF75830BB; + public const uint KCapturedYveltal = 0xF75B3AF9; + public const uint KCapturedZygarde = 0xF75B3946; + public const uint KCapturedTapuKoko = 0xF75B3793; + public const uint KCapturedTapuLele = 0xF75B35E0; + public const uint KCapturedTapuBulu = 0xF75B41C5; + public const uint KCapturedTapuFini = 0xF75B4012; + public const uint KCapturedSolgaleo = 0xF75B3E5F; + public const uint KCapturedLunala = 0xF75B3CAC; + public const uint KCapturedNihilego = 0xF75B46DE; + public const uint KCapturedBuzzwole = 0xF769AAC6; + public const uint KCapturedPheromosa = 0xF769AC79; + public const uint KCapturedXurkitree = 0xF769A760; + public const uint KCapturedCelesteela = 0xF769B192; + public const uint KCapturedKartana = 0xF769A913; + public const uint KCapturedGuzzlord = 0xF769B345; + public const uint KCapturedNecrozma = 0xF75B4891; + public const uint KCapturedStakataka = 0xF769B85E; + public const uint KCapturedBlacephalon = 0xF769AFDF; + + // Gift event flags (bool) + public const uint KReceivedGiftBulbasaur = 0x4F240749; + public const uint KReceivedGiftCharmander = 0x178159E5; + public const uint KReceivedGiftSquirtle = 0x08F829F8; + public const uint KReceivedGiftPikachu = 0x9D95E9CA; + public const uint KReceivedGiftEevee = 0x855235FF; + public const uint KReceivedGiftTypeNull = 0x2AB6CECC; + public const uint KReceivedGiftCosmog = 0x52F6F77F; + public const uint KReceivedGiftPoipole = 0x4B3C9063; + public const uint KReceivedGiftToxel = 0xC41B40F7; + + // General purpose event flags (bool) + public const uint KSecretBoxUnlocked = 0x32A339E9; // FSYS_SECRET_BOX + public const uint KUnlockedYComm = 0xDE0EEF6F; // FSYS_LIVE_COMM_OPEN + public const uint KUnlockedJudge = 0xD0E267EB; // FSYS_JUDGE_OPEN + public const uint KUnlockedWaterModeBike = 0x7526A53C; // FSYS_FLAG_NAMINORI_OPEN + public const uint KUnlockedUBsInMaxLair = 0xB99A1E28; // FSYS_CHIKA_UB_OPEN + public const uint KPlayRecordsPikachu = 0x1C74460E; // FSYS_PLAY_LETSGO_PIKACHU + public const uint KPlayRecordsEevee = 0xC804E4AF; // FSYS_PLAY_LETSGO_EEVEE + public const uint KPlayRecordsQuest = 0xBF24DDAE; // FSYS_PLAY_POKEMON_QUEST + + // Values + public const uint KCurrentBox = 0x017C3CBB; // U32 Box Index + public const uint KBoxesUnlocked = 0x71825204; // U32 + public const uint KGameLanguage = 0x0BFDEBA1; // U32 Game Language + public const uint KRepel = 0x9ec079da; // U16 Repel Steps remaining + public const uint KRotoRally = 0x38548020; // U32 Roto Rally Score (99,999 cap) + public const uint KBattleTowerSinglesVictory = 0x436CAF2B; // U32 Singles victories (9,999,999 cap) + public const uint KBattleTowerDoublesVictory = 0x0D477836; // U32 Doubles victories (9,999,999 cap) + public const uint KBattleTowerSinglesStreak = 0x6226F5AD; // U16 Singles Streak (300 cap) + public const uint KBattleTowerDoublesStreak = 0x5F74FCEE; // U16 Doubles Streak (300 cap) + public const uint KStarterChoice = 0x3677602D; // U32 Grookey=0, Scorbunny=1, Sobble=2 + public const uint KDiggingDuoStreakSkill = 0xA0F49CFB; // U32 + public const uint KDiggingDuoStreakStamina = 0x066F38F5; // U32 + public const uint KBirthMonth = 0x0D987D50; // U32 + public const uint KBirthDay = 0x355C8314; // U32 + public const uint KCurrentDexEntry = 0x62743428; // U16 Species ID of last Pokedex entry viewed in Galar Dex + public const uint KCurrentDexEntryR1 = 0x789FF72D; // U16 Species ID of last Pokedex entry viewed in Armor Dex + public const uint KCurrentDexEntryR2 = 0x759FF274; // U16 Species ID of last Pokedex entry viewed in Crown Dex + public const uint KCurrentDex = 0x9CF58395; // U32 Galar=0, Armor=1, Crown=2 + + public const uint KVolumeBackgroundMusic = 0xF8154AC9; // U32 0-10 + public const uint KVolumeSoundEffects = 0x62F05895; // U32 0-10 + public const uint KVolumePokemonCries = 0x1D482A63; // U32 0-10 + + public const uint KRecordCramorantRobo = 0xB9C0ECFC; // cormorant_robo (Cram-o-matic uses) + public const uint KRecordBattleVersion = 0x7A9EF7D9; // battle_rom_mark (Past-gen Pokémon reset for battling in Ranked) + public const uint KRecordSparringTypesCleared = 0xBB1DE8EF; // Number of Types cleared in Restricted Sparring + + public const uint KOptionTextSpeed = 0x92EB0306; // U32 TextSpeedOption + public const uint KOptionBattleEffects = 0xCCC153CD; // U32 OptOut (Show effects by default) + public const uint KOptionBattleStyle = 0x765468C3; // U32 OptOut (Allow Switch by default) + public const uint KOptionSendToBoxes = 0xB1C7C436; // U32 OptIn + public const uint KOptionGiveNicknames = 0x26A1BEDE; // U32 OptOut + public const uint KOptionUseGyroscope = 0x79C56A5C; // U32 OptOut + public const uint KOptionCameraVertical = 0x2846B7DB; // U32 OptOut Invert=1 + public const uint KOptionCameraHorizontal = 0x7D249649; // U32 OptOut Invert=1 + public const uint KOptionCasualControls = 0x3B23B1E2; // U32 OptOut Casual=0 + public const uint KOptionAutoSave = 0xB027F396; // U32 OptOut AutoSave=0 + public const uint KOptionShowNicknames = 0xCA8A8CEE; // U32 OptOut Show=0 + public const uint KOptionShowMoves = 0x9C781AE2; // U32 OptOut Show=0 + public const uint KDojoWattDonationTotal = 0xC7161487; // U32 Amount of Watts donated to Master Dojo + public const uint KDiggingPaWattStreak = 0x68BBA8B1; // U32 Most Watts dug up by the Digging Pa + public const uint KAlolanDiglettFound = 0x4AEA5A7E; // U32 Amount of Alolan Diglett found on Isle of Armor + public const uint KStorySoniaCTQuestProgress = 0xCB135C68; // U32 Swords of Justice Quest progress. Values are from 0-7; 7=completed + public const uint KBikeBoostChargeSteps = 0x57F29628; // U32 Current step counter, fully charged when this value matches KBikeBoostChargeLimit + public const uint KBikeBoostChargeLimit = 0xF64719D9; // U32 Steps to charge bike boost, starts at 128 -> 64 when fully upgraded + + public const uint KSparringStreakNormal = 0xDB5E16CB; // U32 Best Normal-Type Restricted Sparring Streak + public const uint KSparringNormalPartySlot1Species = 0x7BF09DD3; // U16 Species ID of 1st PKM used in party + public const uint KSparringNormalPartySlot2Species = 0x7AF09C40; // U16 Species ID of 2nd PKM used in party + public const uint KSparringNormalPartySlot3Species = 0x7DF0A0F9; // U16 Species ID of 3rd PKM used in party + public const uint KSparringNormalPartySlot1Gender = 0xF8FB2876; // U32 Gender ID of 1st PKM used in party + public const uint KSparringNormalPartySlot2Gender = 0xF9FB2A09; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringNormalPartySlot3Gender = 0xF6FB2550; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringNormalPartySlot1Form = 0xE5181ED2; // U32 Form ID of 1st PKM used in party + public const uint KSparringNormalPartySlot2Form = 0xE6182065; // U32 Form ID of 2nd PKM used in party + public const uint KSparringNormalPartySlot3Form = 0xE3181BAC; // U32 Form ID of 3rd PKM used in party + public const uint KSparringNormalPartySlot1EC = 0xE95199D1; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringNormalPartySlot2EC = 0xE851983E; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringNormalPartySlot3EC = 0xE75196AB; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakFire = 0xD25E08A0; // U32 Best Fire-Type Restricted Sparring Streak + public const uint KSparringFirePartySlot1Species = 0x455C523A; // U16 Species ID of 1st PKM used in party + public const uint KSparringFirePartySlot2Species = 0x465C53CD; // U16 Species ID of 2nd PKM used in party + public const uint KSparringFirePartySlot3Species = 0x435C4F14; // U16 Species ID of 3rd PKM used in party + public const uint KSparringFirePartySlot1Gender = 0x4E5271EF; // U32 Gender ID of 1st PKM used in party + public const uint KSparringFirePartySlot2Gender = 0x4D52705C; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringFirePartySlot3Gender = 0x50527515; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringFirePartySlot1Form = 0x41E9A3FB; // U32 Form ID of 1st PKM used in party + public const uint KSparringFirePartySlot2Form = 0x40E9A268; // U32 Form ID of 2nd PKM used in party + public const uint KSparringFirePartySlot3Form = 0x43E9A721; // U32 Form ID of 3rd PKM used in party + public const uint KSparringFirePartySlot1EC = 0x1F637658; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringFirePartySlot2EC = 0x206377EB; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringFirePartySlot3EC = 0x2163797E; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakWater = 0xD55BCEC2; // U32 Best Water-Type Restricted Sparring Streak + public const uint KSparringWaterPartySlot1Species = 0x30396510; // U16 Species ID of 1st PKM used in party + public const uint KSparringWaterPartySlot2Species = 0x313966A3; // U16 Species ID of 2nd PKM used in party + public const uint KSparringWaterPartySlot3Species = 0x32396836; // U16 Species ID of 3rd PKM used in party + public const uint KSparringWaterPartySlot1Gender = 0xC3264459; // U32 Gender ID of 1st PKM used in party + public const uint KSparringWaterPartySlot2Gender = 0xC22642C6; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringWaterPartySlot3Gender = 0xC1264133; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringWaterPartySlot1Form = 0x9AB09895; // U32 Form ID of 1st PKM used in party + public const uint KSparringWaterPartySlot2Form = 0x99B09702; // U32 Form ID of 2nd PKM used in party + public const uint KSparringWaterPartySlot3Form = 0x98B0956F; // U32 Form ID of 3rd PKM used in party + public const uint KSparringWaterPartySlot1EC = 0x1DE9496E; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringWaterPartySlot2EC = 0x1EE94B01; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringWaterPartySlot3EC = 0x1BE94648; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakElectric = 0xD35BCB9C; // U32 Best Electric-Type Restricted Sparring Streak + public const uint KSparringElectricPartySlot1Species = 0x1E5FB12E; // U16 Species ID of 1st PKM used in party + public const uint KSparringElectricPartySlot2Species = 0x1F5FB2C1; // U16 Species ID of 2nd PKM used in party + public const uint KSparringElectricPartySlot3Species = 0x1C5FAE08; // U16 Species ID of 3rd PKM used in party + public const uint KSparringElectricPartySlot1Gender = 0x3DF2EAF7; // U32 Gender ID of 1st PKM used in party + public const uint KSparringElectricPartySlot2Gender = 0x3CF2E964; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringElectricPartySlot3Gender = 0x3FF2EE1D; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringElectricPartySlot1Form = 0xE74A9573; // U32 Form ID of 1st PKM used in party + public const uint KSparringElectricPartySlot2Form = 0xE64A93E0; // U32 Form ID of 2nd PKM used in party + public const uint KSparringElectricPartySlot3Form = 0xE94A9899; // U32 Form ID of 3rd PKM used in party + public const uint KSparringElectricPartySlot1EC = 0x2FC2FD50; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringElectricPartySlot2EC = 0x30C2FEE3; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringElectricPartySlot3EC = 0x31C30076; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakGrass = 0xD65BD055; // U32 Best Grass-Type Restricted Sparring Streak + public const uint KSparringGrassPartySlot1Species = 0x70973021; // U16 Species ID of 1st PKM used in party + public const uint KSparringGrassPartySlot2Species = 0x6F972E8E; // U16 Species ID of 2nd PKM used in party + public const uint KSparringGrassPartySlot3Species = 0x6E972CFB; // U16 Species ID of 3rd PKM used in party + public const uint KSparringGrassPartySlot1Gender = 0x2454C888; // U32 Gender ID of 1st PKM used in party + public const uint KSparringGrassPartySlot2Gender = 0x2554CA1B; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringGrassPartySlot3Gender = 0x2654CBAE; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringGrassPartySlot1Form = 0xB3FF0924; // U32 Form ID of 1st PKM used in party + public const uint KSparringGrassPartySlot2Form = 0xB4FF0AB7; // U32 Form ID of 2nd PKM used in party + public const uint KSparringGrassPartySlot3Form = 0xB5FF0C4A; // U32 Form ID of 3rd PKM used in party + public const uint KSparringGrassPartySlot1EC = 0x044B26FF; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringGrassPartySlot2EC = 0x034B256C; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringGrassPartySlot3EC = 0x064B2A25; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakIce = 0xD15BC876; // U32 Best Ice-Type Restricted Sparring Streak + public const uint KSparringIcePartySlot1Species = 0x892112D4; // U16 Species ID of 1st PKM used in party + public const uint KSparringIcePartySlot2Species = 0x8A211467; // U16 Species ID of 2nd PKM used in party + public const uint KSparringIcePartySlot3Species = 0x8B2115FA; // U16 Species ID of 3rd PKM used in party + public const uint KSparringIcePartySlot1Gender = 0x355AA71D; // U32 Gender ID of 1st PKM used in party + public const uint KSparringIcePartySlot2Gender = 0x345AA58A; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringIcePartySlot3Gender = 0x335AA3F7; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringIcePartySlot1Form = 0xE1C853B1; // U32 Form ID of 1st PKM used in party + public const uint KSparringIcePartySlot2Form = 0xE0C8521E; // U32 Form ID of 2nd PKM used in party + public const uint KSparringIcePartySlot3Form = 0xDFC8508B; // U32 Form ID of 3rd PKM used in party + public const uint KSparringIcePartySlot1EC = 0xEFCE9172; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringIcePartySlot2EC = 0xF0CE9305; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringIcePartySlot3EC = 0xEDCE8E4C; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakFighting = 0xDA5E1538; // U32 Best Fighting-Type Restricted Sparring Streak + public const uint KSparringFightingPartySlot1Species = 0x153FD7E2; // U16 Species ID of 1st PKM used in party + public const uint KSparringFightingPartySlot2Species = 0x163FD975; // U16 Species ID of 2nd PKM used in party + public const uint KSparringFightingPartySlot3Species = 0x133FD4BC; // U16 Species ID of 3rd PKM used in party + public const uint KSparringFightingPartySlot1Gender = 0x7E6EEC47; // U32 Gender ID of 1st PKM used in party + public const uint KSparringFightingPartySlot2Gender = 0x7D6EEAB4; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringFightingPartySlot3Gender = 0x806EEF6D; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringFightingPartySlot1Form = 0x1EC26C83; // U32 Form ID of 1st PKM used in party + public const uint KSparringFightingPartySlot2Form = 0x1DC26AF0; // U32 Form ID of 2nd PKM used in party + public const uint KSparringFightingPartySlot3Form = 0x20C26FA9; // U32 Form ID of 3rd PKM used in party + public const uint KSparringFightingPartySlot1EC = 0x62A0A180; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringFightingPartySlot2EC = 0x63A0A313; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringFightingPartySlot3EC = 0x64A0A4A6; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakPoison = 0xDC5E185E; // U32 Best Poison-Type Restricted Sparring Streak + public const uint KSparringPoisonPartySlot1Species = 0x3BFF8084; // U16 Species ID of 1st PKM used in party + public const uint KSparringPoisonPartySlot2Species = 0x3CFF8217; // U16 Species ID of 2nd PKM used in party + public const uint KSparringPoisonPartySlot3Species = 0x3DFF83AA; // U16 Species ID of 3rd PKM used in party + public const uint KSparringPoisonPartySlot1Gender = 0x11850B29; // U32 Gender ID of 1st PKM used in party + public const uint KSparringPoisonPartySlot2Gender = 0x10850996; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringPoisonPartySlot3Gender = 0x0F850803; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringPoisonPartySlot1Form = 0xD0EB3B25; // U32 Form ID of 1st PKM used in party + public const uint KSparringPoisonPartySlot2Form = 0xCFEB3992; // U32 Form ID of 2nd PKM used in party + public const uint KSparringPoisonPartySlot3Form = 0xCEEB37FF; // U32 Form ID of 3rd PKM used in party + public const uint KSparringPoisonPartySlot1EC = 0x171AE45E; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringPoisonPartySlot2EC = 0x181AE5F1; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringPoisonPartySlot3EC = 0x151AE138; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakGround = 0xDF5E1D17; // U32 Best Ground-Type Restricted Sparring Streak + public const uint KSparringGroundPartySlot1Species = 0x29BC6D6F; // U16 Species ID of 1st PKM used in party + public const uint KSparringGroundPartySlot2Species = 0x28BC6BDC; // U16 Species ID of 2nd PKM used in party + public const uint KSparringGroundPartySlot3Species = 0x2BBC7095; // U16 Species ID of 3rd PKM used in party + public const uint KSparringGroundPartySlot1Gender = 0x69F256BA; // U32 Gender ID of 1st PKM used in party + public const uint KSparringGroundPartySlot2Gender = 0x6AF2584D; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringGroundPartySlot3Gender = 0x67F25394; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringGroundPartySlot1Form = 0x2C7F8BCE; // U32 Form ID of 1st PKM used in party + public const uint KSparringGroundPartySlot2Form = 0x2D7F8D61; // U32 Form ID of 2nd PKM used in party + public const uint KSparringGroundPartySlot3Form = 0x2A7F88A8; // U32 Form ID of 3rd PKM used in party + public const uint KSparringGroundPartySlot1EC = 0xBA495F35; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringGroundPartySlot2EC = 0xB9495DA2; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringGroundPartySlot3EC = 0xB8495C0F; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakFlying = 0xDD5E19F1; // U32 Best Flying-Type Restricted Sparring Streak + public const uint KSparringFlyingPartySlot1Species = 0xA17311F5; // U16 Species ID of 1st PKM used in party + public const uint KSparringFlyingPartySlot2Species = 0xA0731062; // U16 Species ID of 2nd PKM used in party + public const uint KSparringFlyingPartySlot3Species = 0x9F730ECF; // U16 Species ID of 3rd PKM used in party + public const uint KSparringFlyingPartySlot1Gender = 0x2B232D98; // U32 Gender ID of 1st PKM used in party + public const uint KSparringFlyingPartySlot2Gender = 0x2C232F2B; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringFlyingPartySlot3Gender = 0x2D2330BE; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringFlyingPartySlot1Form = 0x23E747F4; // U32 Form ID of 1st PKM used in party + public const uint KSparringFlyingPartySlot2Form = 0x24E74987; // U32 Form ID of 2nd PKM used in party + public const uint KSparringFlyingPartySlot3Form = 0x25E74B1A; // U32 Form ID of 3rd PKM used in party + public const uint KSparringFlyingPartySlot1EC = 0x4292BAAF; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringFlyingPartySlot2EC = 0x4192B91C; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringFlyingPartySlot3EC = 0x4492BDD5; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakPsychic = 0xD45BCD2F; // U32 Best Psychic-Type Restricted Sparring Streak + public const uint KSparringPsychicPartySlot1Species = 0x04C18EBF; // U16 Species ID of 1st PKM used in party + public const uint KSparringPsychicPartySlot2Species = 0x03C18D2C; // U16 Species ID of 2nd PKM used in party + public const uint KSparringPsychicPartySlot3Species = 0x06C191E5; // U16 Species ID of 3rd PKM used in party + public const uint KSparringPsychicPartySlot1Gender = 0x70EEC566; // U32 Gender ID of 1st PKM used in party + public const uint KSparringPsychicPartySlot2Gender = 0x71EEC6F9; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringPsychicPartySlot3Gender = 0x6EEEC240; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringPsychicPartySlot1Form = 0x81D70402; // U32 Form ID of 1st PKM used in party + public const uint KSparringPsychicPartySlot2Form = 0x82D70595; // U32 Form ID of 2nd PKM used in party + public const uint KSparringPsychicPartySlot3Form = 0x7FD700DC; // U32 Form ID of 3rd PKM used in party + public const uint KSparringPsychicPartySlot1EC = 0x896D7D61; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringPsychicPartySlot2EC = 0x886D7BCE; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringPsychicPartySlot3EC = 0x876D7A3B; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakBug = 0xE15E203D; // U32 Best Bug-Type Restricted Sparring Streak + public const uint KSparringBugPartySlot1Species = 0xE9C80191; // U16 Species ID of 1st PKM used in party + public const uint KSparringBugPartySlot2Species = 0xE8C7FFFE; // U16 Species ID of 2nd PKM used in party + public const uint KSparringBugPartySlot3Species = 0xE7C7FE6B; // U16 Species ID of 3rd PKM used in party + public const uint KSparringBugPartySlot1Gender = 0xFC1AF2FC; // U32 Gender ID of 1st PKM used in party + public const uint KSparringBugPartySlot2Gender = 0xFD1AF48F; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringBugPartySlot3Gender = 0xFE1AF622; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringBugPartySlot1Form = 0x32EF5030; // U32 Form ID of 1st PKM used in party + public const uint KSparringBugPartySlot2Form = 0x33EF51C3; // U32 Form ID of 2nd PKM used in party + public const uint KSparringBugPartySlot3Form = 0x34EF5356; // U32 Form ID of 3rd PKM used in party + public const uint KSparringBugPartySlot1EC = 0x7B7A3613; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringBugPartySlot2EC = 0x7A7A3480; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringBugPartySlot3EC = 0x7D7A3939; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakRock = 0xDE5E1B84; // U32 Best Rock-Type Restricted Sparring Streak + public const uint KSparringRockPartySlot1Species = 0xFE44971E; // U16 Species ID of 1st PKM used in party + public const uint KSparringRockPartySlot2Species = 0xFF4498B1; // U16 Species ID of 2nd PKM used in party + public const uint KSparringRockPartySlot3Species = 0xFC4493F8; // U16 Species ID of 3rd PKM used in party + public const uint KSparringRockPartySlot1Gender = 0x96A7618B; // U32 Gender ID of 1st PKM used in party + public const uint KSparringRockPartySlot2Gender = 0x95A75FF8; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringRockPartySlot3Gender = 0x98A764B1; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringRockPartySlot1Form = 0x92E09FDF; // U32 Form ID of 1st PKM used in party + public const uint KSparringRockPartySlot2Form = 0x91E09E4C; // U32 Form ID of 2nd PKM used in party + public const uint KSparringRockPartySlot3Form = 0x94E0A305; // U32 Form ID of 3rd PKM used in party + public const uint KSparringRockPartySlot1EC = 0x54D5CDC4; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringRockPartySlot2EC = 0x55D5CF57; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringRockPartySlot3EC = 0x56D5D0EA; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakGhost = 0xE05E1EAA; // U32 Best Ghost-Type Restricted Sparring Streak + public const uint KSparringGhostPartySlot1Species = 0x63170940; // U16 Species ID of 1st PKM used in party + public const uint KSparringGhostPartySlot2Species = 0x64170AD3; // U16 Species ID of 2nd PKM used in party + public const uint KSparringGhostPartySlot3Species = 0x65170C66; // U16 Species ID of 3rd PKM used in party + public const uint KSparringGhostPartySlot1Gender = 0xBC29D5AD; // U32 Gender ID of 1st PKM used in party + public const uint KSparringGhostPartySlot2Gender = 0xBB29D41A; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringGhostPartySlot3Gender = 0xBA29D287; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringGhostPartySlot1Form = 0xFEB64141; // U32 Form ID of 1st PKM used in party + public const uint KSparringGhostPartySlot2Form = 0xFDB63FAE; // U32 Form ID of 2nd PKM used in party + public const uint KSparringGhostPartySlot3Form = 0xFCB63E1B; // U32 Form ID of 3rd PKM used in party + public const uint KSparringGhostPartySlot1EC = 0x14C97022; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringGhostPartySlot2EC = 0x15C971B5; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringGhostPartySlot3EC = 0x12C96CFC; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakDragon = 0xD25BCA09; // U32 Best Dragon-Type Restricted Sparring Streak + public const uint KSparringDragonPartySlot1Species = 0xC18E2C05; // U16 Species ID of 1st PKM used in party + public const uint KSparringDragonPartySlot2Species = 0xC08E2A72; // U16 Species ID of 2nd PKM used in party + public const uint KSparringDragonPartySlot3Species = 0xBF8E28DF; // U16 Species ID of 3rd PKM used in party + public const uint KSparringDragonPartySlot1Gender = 0xAEF960AC; // U32 Gender ID of 1st PKM used in party + public const uint KSparringDragonPartySlot2Gender = 0xAFF9623F; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringDragonPartySlot3Gender = 0xB0F963D2; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringDragonPartySlot1Form = 0x5C548FE0; // U32 Form ID of 1st PKM used in party + public const uint KSparringDragonPartySlot2Form = 0x5D549173; // U32 Form ID of 2nd PKM used in party + public const uint KSparringDragonPartySlot3Form = 0x5E549306; // U32 Form ID of 3rd PKM used in party + public const uint KSparringDragonPartySlot1EC = 0x1B9619A3; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringDragonPartySlot2EC = 0x1A961810; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringDragonPartySlot3EC = 0x1D961CC9; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakDark = 0xCF5BC550; // U32 Best Dark-Type Restricted Sparring Streak + public const uint KSparringDarkPartySlot1Species = 0xD6F84432; // U16 Species ID of 1st PKM used in party + public const uint KSparringDarkPartySlot2Species = 0xD7F845C5; // U16 Species ID of 2nd PKM used in party + public const uint KSparringDarkPartySlot3Species = 0xD4F8410C; // U16 Species ID of 3rd PKM used in party + public const uint KSparringDarkPartySlot1Gender = 0x768C477B; // U32 Gender ID of 1st PKM used in party + public const uint KSparringDarkPartySlot2Gender = 0x758C45E8; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringDarkPartySlot3Gender = 0x788C4AA1; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringDarkPartySlot1Form = 0x2F9F850F; // U32 Form ID of 1st PKM used in party + public const uint KSparringDarkPartySlot2Form = 0x2E9F837C; // U32 Form ID of 2nd PKM used in party + public const uint KSparringDarkPartySlot3Form = 0x319F8835; // U32 Form ID of 3rd PKM used in party + public const uint KSparringDarkPartySlot1EC = 0xA1F76014; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringDarkPartySlot2EC = 0xA2F761A7; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringDarkPartySlot3EC = 0xA3F7633A; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakSteel = 0xD35E0A33; // U32 Best Steel-Type Restricted Sparring Streak + public const uint KSparringSteelPartySlot1Species = 0x72115D0B; // U16 Species ID of 1st PKM used in party + public const uint KSparringSteelPartySlot2Species = 0x71115B78; // U16 Species ID of 2nd PKM used in party + public const uint KSparringSteelPartySlot3Species = 0x74116031; // U16 Species ID of 3rd PKM used in party + public const uint KSparringSteelPartySlot1Gender = 0x22DA9B9E; // U32 Gender ID of 1st PKM used in party + public const uint KSparringSteelPartySlot2Gender = 0x23DA9D31; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringSteelPartySlot3Gender = 0x20DA9878; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringSteelPartySlot1Form = 0x1534992A; // U32 Form ID of 1st PKM used in party + public const uint KSparringSteelPartySlot2Form = 0x16349ABD; // U32 Form ID of 2nd PKM used in party + public const uint KSparringSteelPartySlot3Form = 0x13349604; // U32 Form ID of 3rd PKM used in party + public const uint KSparringSteelPartySlot1EC = 0x05C553E9; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringSteelPartySlot2EC = 0x04C55256; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringSteelPartySlot3EC = 0x03C550C3; // U32 Encryption Constant of 3rd PKM used in party + + public const uint KSparringStreakFairy = 0xD05BC6E3; // U32 Best Fairy-Type Restricted Sparring Streak + public const uint KSparringFairyPartySlot1Species = 0x02BFCC63; // U16 Species ID of 1st PKM used in party + public const uint KSparringFairyPartySlot2Species = 0x01BFCAD0; // U16 Species ID of 2nd PKM used in party + public const uint KSparringFairyPartySlot3Species = 0x04BFCF89; // U16 Species ID of 3rd PKM used in party + public const uint KSparringFairyPartySlot1Gender = 0x49D73CAA; // U32 Gender ID of 1st PKM used in party + public const uint KSparringFairyPartySlot2Gender = 0x4AD73E3D; // U32 Gender ID of 2nd PKM used in party + public const uint KSparringFairyPartySlot3Gender = 0x47D73984; // U32 Gender ID of 3rd PKM used in party + public const uint KSparringFairyPartySlot1Form = 0x77442151; // U32 Form ID of 1st PKM used in party + public const uint KSparringFairyPartySlot2Form = 0x76441FBE; // U32 Form ID of 2nd PKM used in party + public const uint KSparringFairyPartySlot3Form = 0x74441C98; // U32 Form ID of 3rd PKM used in party + public const uint KSparringFairyPartySlot1EC = 0xC117C445; // U32 Encryption Constant of 1st PKM used in party + public const uint KSparringFairyPartySlot2EC = 0xC017C2B2; // U32 Encryption Constant of 2nd PKM used in party + public const uint KSparringFairyPartySlot3EC = 0xBF17C11F; // U32 Encryption Constant of 3rd PKM used in party + public const uint KSparringFairyPartySlot1Sweet = 0xB14624FF; // U32 Alcremie Sweet ID if 1st PKM used in party, otherwise -1 + public const uint KSparringFairyPartySlot2Sweet = 0xB046236C; // U32 Alcremie Sweet ID if 2nd PKM used in party, otherwise -1 + public const uint KSparringFairyPartySlot3Sweet = 0xB3462825; // U32 Alcremie Sweet ID if 3rd PKM used in party, otherwise -1 + + public const uint KRegielekiOrRegidragoPattern = 0xCF90B39A; // U32 Chosen Pattern for Split-Decision Ruins (0 = not chosen, 1 = electric, 2 = dragon) + public const uint KFootprintPercentageCobalion = 0x4D50B655; // U32 Footprints of Cobalion collected on Crown Tundra; values go from 0-100 + public const uint KFootprintPercentageTerrakion = 0x771E4C88; // U32 Footprints of Terrakion collected on Crown Tundra; values from 0-100 + public const uint KFootprintPercentageVirizion = 0xAD67A297; // U32 Footprints of Virizion collected on Crown Tundra; values go from 0-100 + public const uint KPlayersInteractedOnline = 0x31A13425; // U32 Number of Players interacted with online + public const uint KMaxLairSpeciesID1Noted = 0x6F669A35; // U32 Max Lair Species 1 Noted + public const uint KMaxLairSpeciesID2Noted = 0x6F66951C; // U32 Max Lair Species 2 Noted + public const uint KMaxLairSpeciesID3Noted = 0x6F6696CF; // U32 Max Lair Species 3 Noted + public const uint KMaxLairEndlessStreak = 0x7F4B4B10; // U32 Endless Dynamax Adventure Best Streak + public const uint KMaxLairDisconnectStreak = 0x8EAEB8FF; // U32 Value of 3 will have you pay a Dynite Ore fee upon entry + public const uint KMaxLairPeoniaSpeciesHint = 0xF26B9151; // U32 Species ID for Peonia to hint + public const uint KMaxLairRentalChoiceSeed = 0x0D74AA40; // U64 seed used to pick Dynamax Adventure rental and encounter templates + + public const uint KGSTVictoriesTotal = 0x9D6727F6; // U32 Total Galarian Star Tournament victories + public const uint KGSTVictoriesAvery = 0x3934BEC0; // U32 Galarian Star Tournament victories with Avery + public const uint KGSTVictoriesKlara = 0xE9131991; // U32 Galarian Star Tournament victories with Klara + public const uint KGSTVictoriesMustard = 0x7742B542; // U32 Galarian Star Tournament victories with Mustard + public const uint KGSTVictoriesPeony = 0x9A535FA0; // U32 Galarian Star Tournament victories with Peony + public const uint KGSTVictoriesLeon = 0xFEE68CE1; // U32 Galarian Star Tournament victories with Leon + public const uint KGSTVictoriesBede = 0x2D0520CD; // U32 Galarian Star Tournament victories with Bede + public const uint KGSTVictoriesMarnie = 0xFB5133BC; // U32 Galarian Star Tournament victories with Marnie + public const uint KGSTVictoriesMilo = 0x24B9E3CC; // U32 Galarian Star Tournament victories with Milo + public const uint KGSTVictoriesNessa = 0xB19386DE; // U32 Galarian Star Tournament victories with Nessa + public const uint KGSTVictoriesKabu = 0x3576EE34; // U32 Galarian Star Tournament victories with Kabu + public const uint KGSTVictoriesPiers = 0xB95B1BB9; // U32 Galarian Star Tournament victories with Piers + public const uint KGSTVictoriesRaihan = 0x343E6FC1; // U32 Galarian Star Tournament victories with Raihan + public const uint KGSTVictoriesBea = 0x6371183B; // U32 Galarian Star Tournament victories with Bea + public const uint KGSTVictoriesGordie = 0xA2C094C7; // U32 Galarian Star Tournament victories with Gordie + public const uint KGSTVictoriesAllister = 0x9E32AE34; // U32 Galarian Star Tournament victories with Allister + public const uint KGSTVictoriesMelony = 0x06C0FBC8; // U32 Galarian Star Tournament victories with Melony + public const uint KGSTVictoriesSordward = 0xE3ED8F16; // U32 Galarian Star Tournament victories with Sordward + public const uint KGSTVictoriesShielbert = 0xC0D49E2D; // U32 Galarian Star Tournament victories with Shielbert + public const uint KGSTVictoriesHop = 0xEB07C276; // U32 Galarian Star Tournament victories with Hop + public const uint KGSTVictoriesOpal = 0xDBE374D7; // U32 Galarian Star Tournament victories with Opal } diff --git a/PKHeX.Core/Saves/Access/SaveBlockMetadata.cs b/PKHeX.Core/Saves/Access/SaveBlockMetadata.cs index 93322a259..ad3b3c7ba 100644 --- a/PKHeX.Core/Saves/Access/SaveBlockMetadata.cs +++ b/PKHeX.Core/Saves/Access/SaveBlockMetadata.cs @@ -1,27 +1,26 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Utilizes Reflection to obtain all defined accessor property names and object values. +/// +/// Type of accessor +public sealed class SaveBlockMetadata { - /// - /// Utilizes Reflection to obtain all defined accessor property names and object values. - /// - /// Type of accessor - public sealed class SaveBlockMetadata + private readonly Dictionary BlockList; + + public SaveBlockMetadata(ISaveBlockAccessor accessor) { - private readonly Dictionary BlockList; - - public SaveBlockMetadata(ISaveBlockAccessor accessor) - { - var aType = accessor.GetType(); - BlockList = aType.GetAllPropertiesOfType(accessor); - } - - public IEnumerable GetSortedBlockList() - { - return BlockList.Select(z => z.Value).OrderBy(z => z); - } - - public IDataIndirect GetBlock(string name) => BlockList.First(z => z.Value == name).Key; + var aType = accessor.GetType(); + BlockList = aType.GetAllPropertiesOfType(accessor); } + + public IEnumerable GetSortedBlockList() + { + return BlockList.Select(z => z.Value).OrderBy(z => z); + } + + public IDataIndirect GetBlock(string name) => BlockList.First(z => z.Value == name).Key; } diff --git a/PKHeX.Core/Saves/Blocks/BlockInfo.cs b/PKHeX.Core/Saves/Blocks/BlockInfo.cs index 74a931ad6..f8406c675 100644 --- a/PKHeX.Core/Saves/Blocks/BlockInfo.cs +++ b/PKHeX.Core/Saves/Blocks/BlockInfo.cs @@ -1,89 +1,88 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class BlockInfo { - public abstract class BlockInfo + // General + public uint ID { get; protected init; } + public int Offset { get; protected init; } + public int Length { get; protected init; } + + public string Summary => $"{ID:00}: {Offset:X5}-{Offset + Length - 1:X5}, {Length:X5}"; + + protected abstract bool ChecksumValid(ReadOnlySpan data); + protected abstract void SetChecksum(Span data); + + /// + /// Checks if the currently written checksum values are valid. + /// + /// Block info objects used for offset/length + /// Complete data array + /// True if checksums are valid, false if anything is invalid. + public static bool GetChecksumsValid(IEnumerable blocks, ReadOnlySpan data) { - // General - public uint ID { get; protected init; } - public int Offset { get; protected init; } - public int Length { get; protected init; } - - public string Summary => $"{ID:00}: {Offset:X5}-{Offset + Length - 1:X5}, {Length:X5}"; - - protected abstract bool ChecksumValid(ReadOnlySpan data); - protected abstract void SetChecksum(Span data); - - /// - /// Checks if the currently written checksum values are valid. - /// - /// Block info objects used for offset/length - /// Complete data array - /// True if checksums are valid, false if anything is invalid. - public static bool GetChecksumsValid(IEnumerable blocks, ReadOnlySpan data) + foreach (var b in blocks) { - foreach (var b in blocks) - { - if (b.Length + b.Offset > data.Length) - return false; + if (b.Length + b.Offset > data.Length) + return false; - if (!b.ChecksumValid(data)) - return false; - } - return true; - } - - /// - /// Applies checksums to the object based on the input blocks. - /// - /// Block info objects used for offset/length - /// Complete data array - public static void SetChecksums(IEnumerable blocks, Span data) - { - foreach (var b in blocks) - b.SetChecksum(data); - } - - /// - /// Gets information pertaining to the checksum data for diagnostic purposes. - /// - /// Block info objects used for offset/length - /// Complete data array - /// Multi-line string with data. - public static string GetChecksumInfo(IReadOnlyList blocks, Span data) - { - var invalid = GetInvalidBlockCount(blocks, data, out var list); - list.Add($"SAV: {blocks.Count - invalid}/{blocks.Count}"); - return string.Join(Environment.NewLine, list); - } - - private static int GetInvalidBlockCount(IReadOnlyList blocks, Span data, out List list) - { - int invalid = 0; - list = new List(); - for (int i = 0; i < blocks.Count; i++) - { - if (blocks[i].Length + blocks[i].Offset > data.Length) - { - list.Add($"Block {i} Invalid Offset/Length."); - return invalid; - } - - if (blocks[i].ChecksumValid(data)) - continue; - - invalid++; - list.Add($"Invalid: {i:X2} @ Region {blocks[i].Offset:X5}"); - } - return invalid; + if (!b.ChecksumValid(data)) + return false; } + return true; } - public static partial class Extensions + /// + /// Applies checksums to the object based on the input blocks. + /// + /// Block info objects used for offset/length + /// Complete data array + public static void SetChecksums(IEnumerable blocks, Span data) { - public static bool GetChecksumsValid(this IEnumerable blocks, Span data) => BlockInfo.GetChecksumsValid(blocks, data); - public static void SetChecksums(this IEnumerable blocks, Span data) => BlockInfo.SetChecksums(blocks, data); - public static string GetChecksumInfo(this IReadOnlyList blocks, Span data) => BlockInfo.GetChecksumInfo(blocks, data); + foreach (var b in blocks) + b.SetChecksum(data); + } + + /// + /// Gets information pertaining to the checksum data for diagnostic purposes. + /// + /// Block info objects used for offset/length + /// Complete data array + /// Multi-line string with data. + public static string GetChecksumInfo(IReadOnlyList blocks, Span data) + { + var invalid = GetInvalidBlockCount(blocks, data, out var list); + list.Add($"SAV: {blocks.Count - invalid}/{blocks.Count}"); + return string.Join(Environment.NewLine, list); + } + + private static int GetInvalidBlockCount(IReadOnlyList blocks, Span data, out List list) + { + int invalid = 0; + list = new List(); + for (int i = 0; i < blocks.Count; i++) + { + if (blocks[i].Length + blocks[i].Offset > data.Length) + { + list.Add($"Block {i} Invalid Offset/Length."); + return invalid; + } + + if (blocks[i].ChecksumValid(data)) + continue; + + invalid++; + list.Add($"Invalid: {i:X2} @ Region {blocks[i].Offset:X5}"); + } + return invalid; } } + +public static partial class Extensions +{ + public static bool GetChecksumsValid(this IEnumerable blocks, Span data) => BlockInfo.GetChecksumsValid(blocks, data); + public static void SetChecksums(this IEnumerable blocks, Span data) => BlockInfo.SetChecksums(blocks, data); + public static string GetChecksumInfo(this IReadOnlyList blocks, Span data) => BlockInfo.GetChecksumInfo(blocks, data); +} diff --git a/PKHeX.Core/Saves/Blocks/BlockInfo3DS.cs b/PKHeX.Core/Saves/Blocks/BlockInfo3DS.cs index e38d0f244..9dc0c03df 100644 --- a/PKHeX.Core/Saves/Blocks/BlockInfo3DS.cs +++ b/PKHeX.Core/Saves/Blocks/BlockInfo3DS.cs @@ -1,68 +1,67 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Gen6+ Block Info (inside BEEF chunk) +/// +public abstract class BlockInfo3DS : BlockInfo { - /// - /// Gen6+ Block Info (inside BEEF chunk) - /// - public abstract class BlockInfo3DS : BlockInfo + private readonly int BlockInfoOffset; + + // ==chunk def== @ BlockInfoOffset + // u64 timestamp1 + // u64 timestamp2 + // u8[4] BEEF magic + // n*{blockInfo}, where n varies per sav type + + // ==block info def== + // u32 length + // u16 id + // u16 checksum + + // when stored, each block size is rounded up to nearest 0x200, and the next chunk is immediately after + + protected BlockInfo3DS(int bo, uint id, int ofs, int len) { - private readonly int BlockInfoOffset; - - // ==chunk def== @ BlockInfoOffset - // u64 timestamp1 - // u64 timestamp2 - // u8[4] BEEF magic - // n*{blockInfo}, where n varies per sav type - - // ==block info def== - // u32 length - // u16 id - // u16 checksum - - // when stored, each block size is rounded up to nearest 0x200, and the next chunk is immediately after - - protected BlockInfo3DS(int bo, uint id, int ofs, int len) - { - BlockInfoOffset = bo; - ID = id; - Offset = ofs; - Length = len; - } - - private int ChecksumOffset => BlockInfoOffset + 0x14 + ((int)ID * 8) + 6; - protected abstract ushort GetChecksum(ReadOnlySpan data); - - protected override bool ChecksumValid(ReadOnlySpan data) - { - ushort chk = GetChecksum(data); - var old = ReadUInt16LittleEndian(data[ChecksumOffset..]); - return chk == old; - } - - protected override void SetChecksum(Span data) - { - ushort chk = GetChecksum(data); - WriteUInt16LittleEndian(data[ChecksumOffset..], chk); - } + BlockInfoOffset = bo; + ID = id; + Offset = ofs; + Length = len; } - public sealed class BlockInfo6 : BlockInfo3DS + private int ChecksumOffset => BlockInfoOffset + 0x14 + ((int)ID * 8) + 6; + protected abstract ushort GetChecksum(ReadOnlySpan data); + + protected override bool ChecksumValid(ReadOnlySpan data) { - public BlockInfo6(int bo, uint id, int ofs, int len) : base(bo, id, ofs, len) { } - protected override ushort GetChecksum(ReadOnlySpan data) => Checksums.CRC16_CCITT(data.Slice(Offset, Length)); + ushort chk = GetChecksum(data); + var old = ReadUInt16LittleEndian(data[ChecksumOffset..]); + return chk == old; } - public sealed class BlockInfo7 : BlockInfo3DS + protected override void SetChecksum(Span data) { - public BlockInfo7(int bo, uint id, int ofs, int len) : base(bo, id, ofs, len) { } - protected override ushort GetChecksum(ReadOnlySpan data) => Checksums.CRC16Invert(data.Slice(Offset, Length)); - } - - public sealed class BlockInfo7b : BlockInfo3DS - { - public BlockInfo7b(int bo, uint id, int ofs, int len) : base(bo, id, ofs, len) { } - protected override ushort GetChecksum(ReadOnlySpan data) => Checksums.CRC16NoInvert(data.Slice(Offset, Length)); + ushort chk = GetChecksum(data); + WriteUInt16LittleEndian(data[ChecksumOffset..], chk); } } + +public sealed class BlockInfo6 : BlockInfo3DS +{ + public BlockInfo6(int bo, uint id, int ofs, int len) : base(bo, id, ofs, len) { } + protected override ushort GetChecksum(ReadOnlySpan data) => Checksums.CRC16_CCITT(data.Slice(Offset, Length)); +} + +public sealed class BlockInfo7 : BlockInfo3DS +{ + public BlockInfo7(int bo, uint id, int ofs, int len) : base(bo, id, ofs, len) { } + protected override ushort GetChecksum(ReadOnlySpan data) => Checksums.CRC16Invert(data.Slice(Offset, Length)); +} + +public sealed class BlockInfo7b : BlockInfo3DS +{ + public BlockInfo7b(int bo, uint id, int ofs, int len) : base(bo, id, ofs, len) { } + protected override ushort GetChecksum(ReadOnlySpan data) => Checksums.CRC16NoInvert(data.Slice(Offset, Length)); +} diff --git a/PKHeX.Core/Saves/Blocks/BlockInfoNDS.cs b/PKHeX.Core/Saves/Blocks/BlockInfoNDS.cs index 8aa0b3ae0..f8587c295 100644 --- a/PKHeX.Core/Saves/Blocks/BlockInfoNDS.cs +++ b/PKHeX.Core/Saves/Blocks/BlockInfoNDS.cs @@ -1,41 +1,40 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Gen5 Block Info +/// +public sealed class BlockInfoNDS : BlockInfo { - /// - /// Gen5 Block Info - /// - public sealed class BlockInfoNDS : BlockInfo + private readonly int ChecksumOffset; + private readonly int ChecksumMirror; + + public BlockInfoNDS(int offset, int length, int chkOffset, int chkMirror) { - private readonly int ChecksumOffset; - private readonly int ChecksumMirror; + Offset = offset; + Length = length; + ChecksumOffset = chkOffset; + ChecksumMirror = chkMirror; + } - public BlockInfoNDS(int offset, int length, int chkOffset, int chkMirror) - { - Offset = offset; - Length = length; - ChecksumOffset = chkOffset; - ChecksumMirror = chkMirror; - } + private ushort GetChecksum(ReadOnlySpan data) => Checksums.CRC16_CCITT(data.Slice(Offset, Length)); - private ushort GetChecksum(ReadOnlySpan data) => Checksums.CRC16_CCITT(data.Slice(Offset, Length)); + protected override bool ChecksumValid(ReadOnlySpan data) + { + ushort chk = GetChecksum(data); + if (chk != ReadUInt16LittleEndian(data[ChecksumOffset..])) + return false; + if (chk != ReadUInt16LittleEndian(data[ChecksumMirror..])) + return false; + return true; + } - protected override bool ChecksumValid(ReadOnlySpan data) - { - ushort chk = GetChecksum(data); - if (chk != ReadUInt16LittleEndian(data[ChecksumOffset..])) - return false; - if (chk != ReadUInt16LittleEndian(data[ChecksumMirror..])) - return false; - return true; - } - - protected override void SetChecksum(Span data) - { - ushort chk = GetChecksum(data); - WriteUInt16LittleEndian(data[ChecksumOffset..], chk); - WriteUInt16LittleEndian(data[ChecksumMirror..], chk); - } + protected override void SetChecksum(Span data) + { + ushort chk = GetChecksum(data); + WriteUInt16LittleEndian(data[ChecksumOffset..], chk); + WriteUInt16LittleEndian(data[ChecksumMirror..], chk); } } diff --git a/PKHeX.Core/Saves/Blocks/BlockInfoRSBOX.cs b/PKHeX.Core/Saves/Blocks/BlockInfoRSBOX.cs index db017dfa4..c4b290e68 100644 --- a/PKHeX.Core/Saves/Blocks/BlockInfoRSBOX.cs +++ b/PKHeX.Core/Saves/Blocks/BlockInfoRSBOX.cs @@ -1,44 +1,43 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Gen3 Block Info +/// +public sealed class BlockInfoRSBOX : BlockInfo { - /// - /// Gen3 Block Info - /// - public sealed class BlockInfoRSBOX : BlockInfo + public readonly uint SaveCount; + private const int ChecksumRegionSize = 0x1FF8; + + public BlockInfoRSBOX(ReadOnlySpan data, int offset) { - public readonly uint SaveCount; - private const int ChecksumRegionSize = 0x1FF8; + Offset = offset; + Length = 4 + ChecksumRegionSize; - public BlockInfoRSBOX(ReadOnlySpan data, int offset) - { - Offset = offset; - Length = 4 + ChecksumRegionSize; + // Values stored in Big Endian format + ID = ReadUInt32BigEndian(data[(Offset + 4)..]); + SaveCount = ReadUInt32BigEndian(data[(Offset + 8)..]); + } - // Values stored in Big Endian format - ID = ReadUInt32BigEndian(data[(Offset + 4)..]); - SaveCount = ReadUInt32BigEndian(data[(Offset + 8)..]); - } + protected override bool ChecksumValid(ReadOnlySpan data) + { + var chk = GetChecksum(data); + var old = ReadUInt32BigEndian(data[Offset..]); + return chk == old; + } - protected override bool ChecksumValid(ReadOnlySpan data) - { - var chk = GetChecksum(data); - var old = ReadUInt32BigEndian(data[Offset..]); - return chk == old; - } + protected override void SetChecksum(Span data) + { + var chk = GetChecksum(data); + var span = data[Offset..]; + WriteUInt32BigEndian(span, chk); + } - protected override void SetChecksum(Span data) - { - var chk = GetChecksum(data); - var span = data[Offset..]; - WriteUInt32BigEndian(span, chk); - } - - private uint GetChecksum(ReadOnlySpan data) - { - var span = data.Slice(Offset + 4, ChecksumRegionSize); - return Checksums.CheckSum16BigInvert(span); - } + private uint GetChecksum(ReadOnlySpan data) + { + var span = data.Slice(Offset + 4, ChecksumRegionSize); + return Checksums.CheckSum16BigInvert(span); } } diff --git a/PKHeX.Core/Saves/Blocks/RecordBlock.cs b/PKHeX.Core/Saves/Blocks/RecordBlock.cs index 41a606315..56a0d12fc 100644 --- a/PKHeX.Core/Saves/Blocks/RecordBlock.cs +++ b/PKHeX.Core/Saves/Blocks/RecordBlock.cs @@ -1,23 +1,22 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class RecordBlock : SaveBlock, IRecordStatStorage where T : SaveFile { - public abstract class RecordBlock : SaveBlock, IRecordStatStorage where T : SaveFile + protected abstract IReadOnlyList RecordMax { get; } + public abstract int GetRecord(int recordID); + public abstract void SetRecord(int recordID, int value); + + public int GetRecordMax(int recordID) => Records.GetMax(recordID, RecordMax); + public int GetRecordOffset(int recordID) => Records.GetOffset(Offset, recordID); + public void AddRecord(int recordID, int count = 1) => SetRecord(recordID, GetRecord(recordID) + count); + + protected RecordBlock(T sav) : base(sav) { - protected abstract IReadOnlyList RecordMax { get; } - public abstract int GetRecord(int recordID); - public abstract void SetRecord(int recordID, int value); - - public int GetRecordMax(int recordID) => Records.GetMax(recordID, RecordMax); - public int GetRecordOffset(int recordID) => Records.GetOffset(Offset, recordID); - public void AddRecord(int recordID, int count = 1) => SetRecord(recordID, GetRecord(recordID) + count); - - protected RecordBlock(T sav) : base(sav) - { - } - - protected RecordBlock(T sav, byte[] data) : base(sav, data) - { - } } -} \ No newline at end of file + + protected RecordBlock(T sav, byte[] data) : base(sav, data) + { + } +} diff --git a/PKHeX.Core/Saves/Encryption/GeniusCrypto.cs b/PKHeX.Core/Saves/Encryption/GeniusCrypto.cs index bee5e68b9..054693697 100644 --- a/PKHeX.Core/Saves/Encryption/GeniusCrypto.cs +++ b/PKHeX.Core/Saves/Encryption/GeniusCrypto.cs @@ -1,75 +1,74 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Genius Sonority's logic for and encryption. +/// +public static class GeniusCrypto { - /// - /// Genius Sonority's logic for and encryption. - /// - public static class GeniusCrypto + public static void ReadKeys(ReadOnlySpan input, Span keys) { - public static void ReadKeys(ReadOnlySpan input, Span keys) - { - for (int i = 0; i < keys.Length; i++) - keys[i] = ReadUInt16BigEndian(input[(i * 2)..]); - } + for (int i = 0; i < keys.Length; i++) + keys[i] = ReadUInt16BigEndian(input[(i * 2)..]); + } - public static void Decrypt(ReadOnlySpan input, Span output, Span keys) - { - if (keys.Length != 4) - throw new ArgumentOutOfRangeException(nameof(keys)); + public static void Decrypt(ReadOnlySpan input, Span output, Span keys) + { + if (keys.Length != 4) + throw new ArgumentOutOfRangeException(nameof(keys)); - int i = 0; - do + int i = 0; + do + { + foreach (var key in keys) { - foreach (var key in keys) - { - ushort value = ReadUInt16BigEndian(input[i..]); - value -= key; - WriteUInt16BigEndian(output[i..], value); - i += 2; - } - AdvanceKeys(keys); - } while (i != input.Length); - } + ushort value = ReadUInt16BigEndian(input[i..]); + value -= key; + WriteUInt16BigEndian(output[i..], value); + i += 2; + } + AdvanceKeys(keys); + } while (i != input.Length); + } - public static void Encrypt(ReadOnlySpan input, Span output, Span keys) + public static void Encrypt(ReadOnlySpan input, Span output, Span keys) + { + if (keys.Length != 4) + throw new ArgumentOutOfRangeException(nameof(keys)); + + int i = 0; + do { - if (keys.Length != 4) - throw new ArgumentOutOfRangeException(nameof(keys)); - - int i = 0; - do + foreach (var key in keys) { - foreach (var key in keys) - { - ushort value = ReadUInt16BigEndian(input[i..]); - value += key; - WriteUInt16BigEndian(output[i..], value); - i += 2; - } - AdvanceKeys(keys); - } while (i != input.Length); - } + ushort value = ReadUInt16BigEndian(input[i..]); + value += key; + WriteUInt16BigEndian(output[i..], value); + i += 2; + } + AdvanceKeys(keys); + } while (i != input.Length); + } - private static void AdvanceKeys(Span keys) - { - var k3 = keys[3] + 0x13; - var k2 = keys[2] + 0x17; - var k1 = keys[1] + 0x29; - var k0 = keys[0] + 0x43; + private static void AdvanceKeys(Span keys) + { + var k3 = keys[3] + 0x13; + var k2 = keys[2] + 0x17; + var k1 = keys[1] + 0x29; + var k0 = keys[0] + 0x43; - // Rotate 4bit groups across the diagonal [ / ] after biasing each u16 (no overflow): - // 0123 FB73 - // 4567 EA62 - // 89AB becomes D951 - // CDEF C840 - // We can leave our intermediary types as int as the bit-masks remove any overflow. + // Rotate 4bit groups across the diagonal [ / ] after biasing each u16 (no overflow): + // 0123 FB73 + // 4567 EA62 + // 89AB becomes D951 + // CDEF C840 + // We can leave our intermediary types as int as the bit-masks remove any overflow. - keys[3] = (ushort)((k0 >> 12 & 0xf) | (k1 >> 8 & 0xf0) | (k2 >> 4 & 0xf00) | (k3 & 0xf000)); - keys[2] = (ushort)((k0 >> 08 & 0xf) | (k1 >> 4 & 0xf0) | (k2 & 0xf00) | (k3 << 04 & 0xf000)); - keys[1] = (ushort)((k0 >> 04 & 0xf) | (k1 & 0xf0) | (k2 << 4 & 0xf00) | (k3 << 08 & 0xf000)); - keys[0] = (ushort)((k0 & 0xf) | (k1 << 4 & 0xf0) | (k2 << 8 & 0xf00) | (k3 << 12 & 0xf000)); - } + keys[3] = (ushort)(((k0 >> 12) & 0xf) | ((k1 >> 8) & 0xf0) | ((k2 >> 4) & 0xf00) | ( k3 & 0xf000)); + keys[2] = (ushort)(((k0 >> 08) & 0xf) | ((k1 >> 4) & 0xf0) | ( k2 & 0xf00) | ((k3 << 04) & 0xf000)); + keys[1] = (ushort)(((k0 >> 04) & 0xf) | ( k1 & 0xf0) | ((k2 << 4) & 0xf00) | ((k3 << 08) & 0xf000)); + keys[0] = (ushort)(( k0 & 0xf) | ((k1 << 4) & 0xf0) | ((k2 << 8) & 0xf00) | ((k3 << 12) & 0xf000)); } } diff --git a/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeCrypto.cs b/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeCrypto.cs index 0afec52e0..6e1ed5778 100644 --- a/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeCrypto.cs +++ b/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeCrypto.cs @@ -2,186 +2,185 @@ using System.Security.Cryptography; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// MemeCrypto V1 - The Original Series +/// +/// +/// A variant of encryption and obfuscation used in . +///
The save file stores a dedicated block to contain a hash of the savedata, computed when the block is zeroed.
+///
This signing logic is reused for other authentication; refer to .
+///
The save file first computes a SHA256 Hash over the block checksum region. +/// The logic then applies a SHA1 hash over the SHA256 hash result, encrypts it with a , and signs it with an RSA private key in a non-straightforward manner.
+///
+public static class MemeCrypto { - /// - /// MemeCrypto V1 - The Original Series - /// - /// - /// A variant of encryption and obfuscation used in . - ///
The save file stores a dedicated block to contain a hash of the savedata, computed when the block is zeroed.
- ///
This signing logic is reused for other authentication; refer to .
- ///
The save file first computes a SHA256 Hash over the block checksum region. - /// The logic then applies a SHA1 hash over the SHA256 hash result, encrypts it with a , and signs it with an RSA private key in a non-straightforward manner.
- ///
- public static class MemeCrypto + private const uint POKE = 0x454B4F50; + + public static bool VerifyMemePOKE(ReadOnlySpan input, out byte[] output) { - private const uint POKE = 0x454B4F50; - - public static bool VerifyMemePOKE(ReadOnlySpan input, out byte[] output) + if (input.Length < 0x60) + throw new ArgumentException("Invalid POKE buffer!"); + var memeLen = input.Length - 8; + var memeIndex = MemeKeyIndex.PokedexAndSaveFile; + for (var i = input.Length - 8; i >= 0; i--) { - if (input.Length < 0x60) - throw new ArgumentException("Invalid POKE buffer!"); - var memeLen = input.Length - 8; - var memeIndex = MemeKeyIndex.PokedexAndSaveFile; - for (var i = input.Length - 8; i >= 0; i--) - { - if (ReadUInt32LittleEndian(input[i..]) != POKE) - continue; + if (ReadUInt32LittleEndian(input[i..]) != POKE) + continue; - var keyIndex = ReadInt32LittleEndian(input[(i+4)..]); - if (!MemeKey.IsValidPokeKeyIndex(keyIndex)) - continue; + var keyIndex = ReadInt32LittleEndian(input[(i+4)..]); + if (!MemeKey.IsValidPokeKeyIndex(keyIndex)) + continue; - memeLen = i; - memeIndex = (MemeKeyIndex)keyIndex; - break; - } - - foreach (var len in new[] { memeLen, memeLen - 2 }) // Account for Pokédex QR Edge case - { - if (VerifyMemeData(input, out output, 0, len, memeIndex)) - return true; - - if (VerifyMemeData(input, out output, 0, len, MemeKeyIndex.PokedexAndSaveFile)) - return true; - } - - output = Array.Empty(); - return false; + memeLen = i; + memeIndex = (MemeKeyIndex)keyIndex; + break; } - public static bool VerifyMemeData(ReadOnlySpan input, out byte[] output) + foreach (var len in new[] { memeLen, memeLen - 2 }) // Account for Pokédex QR Edge case { - foreach (MemeKeyIndex keyIndex in Enum.GetValues(typeof(MemeKeyIndex))) - { - if (VerifyMemeData(input, out output, keyIndex)) - return true; - } - output = Array.Empty(); - return false; - } - - public static bool VerifyMemeData(ReadOnlySpan input, out byte[] output, MemeKeyIndex keyIndex) - { - if (input.Length < 0x60) - { - output = Array.Empty(); - return false; - } - var key = new MemeKey(keyIndex); - output = input.ToArray(); - - var sigBuffer = key.RsaPublic(input[^0x60..]); - using var sha1 = SHA1.Create(); - if (DecryptCompare(output, sigBuffer, key, sha1)) - return true; - sigBuffer[0x0] |= 0x80; - if (DecryptCompare(output, sigBuffer, key, sha1)) + if (VerifyMemeData(input, out output, 0, len, memeIndex)) return true; - output = Array.Empty(); - return false; - } - - private static bool DecryptCompare(byte[] output, ReadOnlySpan sigBuffer, MemeKey key, SHA1 sha1) - { - sigBuffer.CopyTo(output.AsSpan(output.Length - 0x60)); - key.AesDecrypt(output).CopyTo(output); - // Check for 8-byte equality. - var hash = sha1.ComputeHash(output, 0, output.Length - 0x8); - var computed = ReadUInt64LittleEndian(hash.AsSpan()); - var existing = ReadUInt64LittleEndian(output.AsSpan(output.Length - 0x8)); - return computed == existing; - } - - public static bool VerifyMemeData(ReadOnlySpan input, out byte[] output, int offset, int length) - { - var data = input.Slice(offset, length).ToArray(); - if (VerifyMemeData(data, out output)) - { - var newOutput = input.ToArray(); - output.CopyTo(newOutput, offset); - output = newOutput; + if (VerifyMemeData(input, out output, 0, len, MemeKeyIndex.PokedexAndSaveFile)) return true; - } - output = Array.Empty(); - return false; } - public static bool VerifyMemeData(ReadOnlySpan input, out byte[] output, int offset, int length, MemeKeyIndex keyIndex) + output = Array.Empty(); + return false; + } + + public static bool VerifyMemeData(ReadOnlySpan input, out byte[] output) + { + foreach (MemeKeyIndex keyIndex in Enum.GetValues(typeof(MemeKeyIndex))) { - var data = input.Slice(offset, length); - if (VerifyMemeData(data, out output, keyIndex)) - { - var newOutput = input.ToArray(); - output.CopyTo(newOutput, offset); - output = newOutput; + if (VerifyMemeData(input, out output, keyIndex)) return true; - } + } + output = Array.Empty(); + return false; + } + + public static bool VerifyMemeData(ReadOnlySpan input, out byte[] output, MemeKeyIndex keyIndex) + { + if (input.Length < 0x60) + { output = Array.Empty(); return false; } + var key = new MemeKey(keyIndex); + output = input.ToArray(); - public static byte[] SignMemeData(ReadOnlySpan input, MemeKeyIndex keyIndex = MemeKeyIndex.PokedexAndSaveFile) + var sigBuffer = key.RsaPublic(input[^0x60..]); + using var sha1 = SHA1.Create(); + if (DecryptCompare(output, sigBuffer, key, sha1)) + return true; + sigBuffer[0x0] |= 0x80; + if (DecryptCompare(output, sigBuffer, key, sha1)) + return true; + + output = Array.Empty(); + return false; + } + + private static bool DecryptCompare(byte[] output, ReadOnlySpan sigBuffer, MemeKey key, SHA1 sha1) + { + sigBuffer.CopyTo(output.AsSpan(output.Length - 0x60)); + key.AesDecrypt(output).CopyTo(output); + // Check for 8-byte equality. + var hash = sha1.ComputeHash(output, 0, output.Length - 0x8); + var computed = ReadUInt64LittleEndian(hash.AsSpan()); + var existing = ReadUInt64LittleEndian(output.AsSpan(output.Length - 0x8)); + return computed == existing; + } + + public static bool VerifyMemeData(ReadOnlySpan input, out byte[] output, int offset, int length) + { + var data = input.Slice(offset, length).ToArray(); + if (VerifyMemeData(data, out output)) { - // Validate Input - if (input.Length < 0x60) - throw new ArgumentException("Cannot memesign a buffer less than 0x60 bytes in size!"); - var key = new MemeKey(keyIndex); - if (!key.CanResign) - throw new ArgumentException("Cannot sign with the specified memekey!"); + var newOutput = input.ToArray(); + output.CopyTo(newOutput, offset); + output = newOutput; + return true; + } + output = Array.Empty(); + return false; + } - var output = input.ToArray(); + public static bool VerifyMemeData(ReadOnlySpan input, out byte[] output, int offset, int length, MemeKeyIndex keyIndex) + { + var data = input.Slice(offset, length); + if (VerifyMemeData(data, out output, keyIndex)) + { + var newOutput = input.ToArray(); + output.CopyTo(newOutput, offset); + output = newOutput; + return true; + } + output = Array.Empty(); + return false; + } - // Copy in the SHA1 signature - using (var sha1 = SHA1.Create()) - { - var hash = sha1.ComputeHash(output, 0, output.Length - 8); - hash.AsSpan(0, 8).CopyTo(output.AsSpan(output.Length - 8, 8)); - } + public static byte[] SignMemeData(ReadOnlySpan input, MemeKeyIndex keyIndex = MemeKeyIndex.PokedexAndSaveFile) + { + // Validate Input + if (input.Length < 0x60) + throw new ArgumentException("Cannot memesign a buffer less than 0x60 bytes in size!"); + var key = new MemeKey(keyIndex); + if (!key.CanResign) + throw new ArgumentException("Cannot sign with the specified memekey!"); - // Perform AES operations - output = key.AesEncrypt(output); - var sigBuffer = output.AsSpan(output.Length - 0x60, 0x60); - sigBuffer[0] &= 0x7F; - var signed = key.RsaPrivate(sigBuffer); - signed.CopyTo(sigBuffer); - return output; + var output = input.ToArray(); + + // Copy in the SHA1 signature + using (var sha1 = SHA1.Create()) + { + var hash = sha1.ComputeHash(output, 0, output.Length - 8); + hash.AsSpan(0, 8).CopyTo(output.AsSpan(output.Length - 8, 8)); } - /// - /// Resigns save data. - /// - /// Save file data to resign - /// The resigned save data. Invalid input returns null. - public static byte[] Resign7(ReadOnlySpan sav7) - { - if (sav7.Length is not (SaveUtil.SIZE_G7SM or SaveUtil.SIZE_G7USUM)) - throw new ArgumentException("Should not be using this for unsupported saves."); + // Perform AES operations + output = key.AesEncrypt(output); + var sigBuffer = output.AsSpan(output.Length - 0x60, 0x60); + sigBuffer[0] &= 0x7F; + var signed = key.RsaPrivate(sigBuffer); + signed.CopyTo(sigBuffer); + return output; + } - // Save Chunks are 0x200 bytes each; Memecrypto signature is 0x100 bytes into the 2nd to last chunk. - var isUSUM = sav7.Length == SaveUtil.SIZE_G7USUM; - var ChecksumTableOffset = sav7.Length - 0x200; - var MemeCryptoOffset = isUSUM ? 0x6C100 : 0x6BB00; - var ChecksumSignatureLength = isUSUM ? 0x150 : 0x140; - const int MemeCryptoSignatureLength = 0x80; + /// + /// Resigns save data. + /// + /// Save file data to resign + /// The resigned save data. Invalid input returns null. + public static byte[] Resign7(ReadOnlySpan sav7) + { + if (sav7.Length is not (SaveUtil.SIZE_G7SM or SaveUtil.SIZE_G7USUM)) + throw new ArgumentException("Should not be using this for unsupported saves."); - var result = sav7.ToArray(); + // Save Chunks are 0x200 bytes each; Memecrypto signature is 0x100 bytes into the 2nd to last chunk. + var isUSUM = sav7.Length == SaveUtil.SIZE_G7USUM; + var ChecksumTableOffset = sav7.Length - 0x200; + var MemeCryptoOffset = isUSUM ? 0x6C100 : 0x6BB00; + var ChecksumSignatureLength = isUSUM ? 0x150 : 0x140; + const int MemeCryptoSignatureLength = 0x80; - // Store current signature - var oldSig = sav7.Slice(MemeCryptoOffset, MemeCryptoSignatureLength).ToArray(); + var result = sav7.ToArray(); - using var sha256 = SHA256.Create(); - var newSig = sha256.ComputeHash(result, ChecksumTableOffset, ChecksumSignatureLength); - Span sigSpan = stackalloc byte[MemeCryptoSignatureLength]; - newSig.CopyTo(sigSpan); + // Store current signature + var oldSig = sav7.Slice(MemeCryptoOffset, MemeCryptoSignatureLength).ToArray(); - if (VerifyMemeData(oldSig, out var memeSig, MemeKeyIndex.PokedexAndSaveFile)) - memeSig.AsSpan()[0x20..0x80].CopyTo(sigSpan[0x20..]); + using var sha256 = SHA256.Create(); + var newSig = sha256.ComputeHash(result, ChecksumTableOffset, ChecksumSignatureLength); + Span sigSpan = stackalloc byte[MemeCryptoSignatureLength]; + newSig.CopyTo(sigSpan); - SignMemeData(sigSpan).CopyTo(result, MemeCryptoOffset); - return result; - } + if (VerifyMemeData(oldSig, out var memeSig, MemeKeyIndex.PokedexAndSaveFile)) + memeSig.AsSpan()[0x20..0x80].CopyTo(sigSpan[0x20..]); + + SignMemeData(sigSpan).CopyTo(result, MemeCryptoOffset); + return result; } } diff --git a/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeKey.cs b/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeKey.cs index b64ffc794..3d9572b0d 100644 --- a/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeKey.cs +++ b/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeKey.cs @@ -4,308 +4,308 @@ using System.Numerics; using System.Security.Cryptography; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Key for crypto with binaries. +/// +public sealed class MemeKey { - /// - /// Key for crypto with binaries. - /// - public sealed class MemeKey + /// Distinguished Encoding Rules + private readonly byte[] DER; + + /// Private Exponent, BigInteger + private readonly BigInteger D; + + /// Public Exponent, BigInteger + private readonly BigInteger E; + + /// Modulus, BigInteger + private readonly BigInteger N; + + // Constructor + public MemeKey(MemeKeyIndex key) { - /// Distinguished Encoding Rules - private readonly byte[] DER; + DER = GetMemeData(key); + var _N = DER.AsSpan(0x18, 0x61).ToArray(); + var _E = DER.AsSpan(0x7B, 3).ToArray(); + _N.AsSpan().Reverse(); + _E.AsSpan().Reverse(); + N = new BigInteger(_N); + E = new BigInteger(_E); - /// Private Exponent, BigInteger - private readonly BigInteger D; - - /// Public Exponent, BigInteger - private readonly BigInteger E; - - /// Modulus, BigInteger - private readonly BigInteger N; - - // Constructor - public MemeKey(MemeKeyIndex key) + if (key == MemeKeyIndex.PokedexAndSaveFile) { - DER = GetMemeData(key); - var _N = DER.AsSpan(0x18, 0x61).ToArray(); - var _E = DER.AsSpan(0x7B, 3).ToArray(); - _N.AsSpan().Reverse(); - _E.AsSpan().Reverse(); - N = new BigInteger(_N); - E = new BigInteger(_E); - - if (key == MemeKeyIndex.PokedexAndSaveFile) - { - var _D = D_3.AsSpan().ToArray(); - _D.AsSpan().Reverse(); - D = new BigInteger(_D); - } - else - { - D = INVALID; - } + var _D = D_3.AsSpan().ToArray(); + _D.AsSpan().Reverse(); + D = new BigInteger(_D); } - - /// - /// Indicates if this key can be used to resign messages. - /// - public bool CanResign => D != INVALID; - - /// - /// Get the AES key for this MemeKey - /// - private byte[] GetAesKey(ReadOnlySpan data) + else { - if (data.Length < 0x60) - throw new ArgumentException("Memebuffers must be atleast 0x60 bytes long!"); - - var buffer = new byte[DER.Length + data.Length - 0x60]; - DER.CopyTo(buffer, 0); - data[..(buffer.Length - DER.Length)].CopyTo(buffer.AsSpan(DER.Length)); - - using var sha1 = SHA1.Create(); - var result = sha1.ComputeHash(buffer); - return result.AsSpan(0, 0x10).ToArray(); + D = INVALID; } - - /// - /// Performs Aes Decryption - /// - internal byte[] AesDecrypt(ReadOnlySpan input) - { - var key = GetAesKey(input); - var data = input[^0x60..]; - var curblock = new byte[0x10]; - var outdata = new byte[data.Length]; - for (var i = data.Length - 0x10; i >= 0; i -= 0x10) // Reverse Phase 2 - { - var slice = data.Slice(i, 0x10); - Xor(curblock, slice, curblock); - curblock = AesEcbDecrypt(key, curblock); - curblock.CopyTo(outdata, i); - } - - // At this point we have Phase1(buf) ^ subkey. - // Subkey is (block first ^ block last) << 1 - // We don't have block first or block last, though? - // How can we derive subkey? - // Well, (a ^ a) = 0. so (block first ^ subkey) ^ (block last ^ subkey) - // = block first ^ block last ;) - var last = outdata.AsSpan(((data.Length / 0x10) - 1) * 0x10, 0x10); - var first = outdata.AsSpan(0, 0x10); - - Span subkey = stackalloc byte[0x10]; - Xor(last, first, curblock); - GetSubKey(curblock, subkey); - for (var i = 0; i < data.Length; i += 0x10) - { - var slice = outdata.AsSpan(i, 0x10); - Xor(slice, subkey, slice); - } - - // Now we have Phase1Encrypt(buf). - curblock.AsSpan().Clear(); // Clear to all zero - Span temp = stackalloc byte[0x10]; - for (var i = 0; i < data.Length; i += 0x10) // Phase 1: CBC Encryption. - { - var slice = outdata.AsSpan(i, 0x10); - slice.CopyTo(curblock); - var temp1 = AesEcbDecrypt(key, curblock); - Xor(temp1, temp, slice); - curblock.CopyTo(temp); - } - - var outbuf = input.ToArray(); - outdata.AsSpan()[..0x60].CopyTo(outbuf.AsSpan()[^0x60..]); - return outbuf; - } - - /// - /// Perform Aes Encryption - /// - internal byte[] AesEncrypt(ReadOnlySpan input) - { - var key = GetAesKey(input); - var data = input[^0x60..]; - var curblock = new byte[0x10]; - var outdata = new byte[data.Length]; - for (var i = 0; i < data.Length; i += 0x10) // Phase 1: CBC Encryption. - { - var slice = data.Slice(i, 0x10); - Xor(curblock, slice, curblock); - curblock = AesEcbEncrypt(key, curblock); - curblock.CopyTo(outdata, i); - } - - // In between - CMAC stuff - var inbet = outdata.AsSpan(0, 0x10); - Xor(curblock, inbet, curblock); - - Span subkey = stackalloc byte[0x10]; - GetSubKey(curblock, subkey); - - Span temp = stackalloc byte[0x10]; - curblock.AsSpan().Clear(); // Memcpy from an all-zero buffer - for (var i = data.Length - 0x10; i >= 0; i -= 0x10) - { - var slice = outdata.AsSpan(i, 0x10); - Xor(slice, subkey, curblock); - byte[] temp1 = AesEcbEncrypt(key, curblock); - Xor(temp1, temp, slice); - curblock.CopyTo(temp); - } - - var outbuf = input.ToArray(); - outdata.AsSpan()[..0x60].CopyTo(outbuf.AsSpan()[^0x60..]); - - return outbuf; - } - - private static void GetSubKey(ReadOnlySpan temp, Span subkey) - { - for (var ofs = 0; ofs < 0x10; ofs += 2) // Imperfect ROL implementation - { - byte b1 = temp[ofs + 0], b2 = temp[ofs + 1]; - subkey[ofs + 0] = (byte)((2 * b1) + (b2 >> 7)); - subkey[ofs + 1] = (byte)(2 * b2); - if (ofs + 2 < temp.Length) - subkey[ofs + 1] += (byte)(temp[ofs + 2] >> 7); - } - if ((temp[0] & 0x80) != 0) - subkey[0xF] ^= 0x87; - } - - private static void Xor(ReadOnlySpan b1, ReadOnlySpan b2, Span result) - { - Debug.Assert(b1.Length == b2.Length); - for (var i = 0; i < b1.Length; i++) - result[i] = (byte)(b1[i] ^ b2[i]); - } - - /// - /// Perform Rsa Decryption - /// - internal byte[] RsaPrivate(ReadOnlySpan data) - { - var _M = new byte[data.Length + 1]; - data.CopyTo(_M.AsSpan(1)); - Array.Reverse(_M); - var M = new BigInteger(_M); - - return Exponentiate(M, D); - } - - /// - /// Perform Rsa Encryption - /// - internal byte[] RsaPublic(ReadOnlySpan data) - { - var _M = new byte[data.Length + 1]; - data.CopyTo(_M.AsSpan(1)); - Array.Reverse(_M); - var M = new BigInteger(_M); - - return Exponentiate(M, E); - } - - #region MemeKey Helper Methods - /// Indicator value for a bad Exponent - private static readonly BigInteger INVALID = BigInteger.MinusOne; - - // Helper method for Modular Exponentiation - private byte[] Exponentiate(BigInteger M, BigInteger Power) - { - var rawSig = BigInteger.ModPow(M, Power, N).ToByteArray(); - Array.Reverse(rawSig); - var outSig = new byte[0x60]; - if (rawSig.Length < 0x60) - Array.Copy(rawSig, 0, outSig, 0x60 - rawSig.Length, rawSig.Length); - else if (rawSig.Length > 0x60) - Array.Copy(rawSig, rawSig.Length - 0x60, outSig, 0, 0x60); - else - Array.Copy(rawSig, outSig, 0x60); - return outSig; - } - - private static byte[] GetMemeData(MemeKeyIndex key) => key switch - { - MemeKeyIndex.LocalWireless => DER_LW, - MemeKeyIndex.FriendlyCompetition => DER_0, - MemeKeyIndex.LiveCompetition => DER_1, - MemeKeyIndex.RentalTeam => DER_2, - MemeKeyIndex.PokedexAndSaveFile => DER_3, - MemeKeyIndex.GaOle => DER_4, - MemeKeyIndex.MagearnaEvent => DER_5, - MemeKeyIndex.MoncolleGet => DER_6, - MemeKeyIndex.IslandScanEventSpecial => DER_7, - MemeKeyIndex.TvTokyoDataBroadcasting => DER_8, - MemeKeyIndex.CapPikachuEvent => DER_9, - MemeKeyIndex.Unknown10 => DER_A, - MemeKeyIndex.Unknown11 => DER_B, - MemeKeyIndex.Unknown12 => DER_C, - MemeKeyIndex.Unknown13 => DER_D, - _ => throw new ArgumentOutOfRangeException(nameof(key), key, null), - }; - - private static readonly byte[] rgbIV = new byte[0x10]; - - // Helper Method to perform AES ECB Encryption - private static byte[] AesEcbEncrypt(byte[] key, byte[] data) - { - using var ms = new MemoryStream(data.Length); - using var aes = Aes.Create(); - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; - - using var cs = new CryptoStream(ms, aes.CreateEncryptor(key, rgbIV), CryptoStreamMode.Write); - cs.Write(data, 0, data.Length); - cs.FlushFinalBlock(); - - return ms.ToArray(); - } - - // Helper Method to perform AES ECB Decryption - private static byte[] AesEcbDecrypt(byte[] key, byte[] data) - { - using var ms = new MemoryStream(data.Length); - using var aes = Aes.Create(); - aes.Mode = CipherMode.ECB; - aes.Padding = PaddingMode.None; - - using var cs = new CryptoStream(ms, aes.CreateDecryptor(key, rgbIV), CryptoStreamMode.Write); - cs.Write(data, 0, data.Length); - cs.FlushFinalBlock(); - - return ms.ToArray(); - } - - public static bool IsValidPokeKeyIndex(int index) - { - if (!Enum.IsDefined(typeof(MemeKeyIndex), index)) - return false; - return (MemeKeyIndex)index != MemeKeyIndex.LocalWireless; - } - #endregion - - #region Official Keydata - private static readonly byte[] DER_0 = "307C300D06092A864886F70D0101010500036B003068026100B3D68C9B1090F6B1B88ECFA9E2F60E9C62C3033B5B64282F262CD393B433D97BD3DB7EBA470B1A77A3DB3C18A1E7616972229BDAD54FB02A19546C65FA4773AABE9B8C926707E7B7DDE4C867C01C0802985E438656168A4430F3F3B9662D7D010203010001".ToByteArray(); - private static readonly byte[] DER_1 = "307C300D06092A864886F70D0101010500036B003068026100C10F4097FD3C781A8FDE101EF3B2F091F82BEE4742324B9206C581766EAF2FBB42C7D60D749B999C529B0E22AD05E0C880231219AD473114EC454380A92898D7A8B54D9432584897D6AFE4860235126190A328DD6525D97B9058D98640B0FA050203010001".ToByteArray(); - private static readonly byte[] DER_2 = "307C300D06092A864886F70D0101010500036B003068026100C3C8D89F55D6A236A115C77594D4B318F0A0A0E3252CC0D6345EB9E33A43A5A56DC9D10B7B59C135396159EC4D01DEBC5FB3A4CAE47853E205FE08982DFCC0C39F0557449F97D41FED13B886AEBEEA918F4767E8FBE0494FFF6F6EE3508E3A3F0203010001".ToByteArray(); - private static readonly byte[] DER_3 = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); - private static readonly byte[] DER_4 = "307C300D06092A864886F70D0101010500036B003068026100A0F2AC80B408E2E4D58916A1C706BEE7A24758A62CE9B50AF1B31409DFCB382E885AA8BB8C0E4AD1BCF6FF64FB3037757D2BEA10E4FE9007C850FFDCF70D2AFAA4C53FAFE38A9917D467862F50FE375927ECFEF433E61BF817A645FA5665D9CF0203010001".ToByteArray(); - private static readonly byte[] DER_5 = "307C300D06092A864886F70D0101010500036B003068026100D046F2872868A5089205B226DE13D86DA552646AC152C84615BE8E0A5897C3EA45871028F451860EA226D53B68DDD5A77D1AD82FAF857EA52CF7933112EEC367A06C0761E580D3D70B6B9C837BAA3F16D1FF7AA20D87A2A5E2BCC6E383BF12D50203010001".ToByteArray(); - private static readonly byte[] DER_6 = "307C300D06092A864886F70D0101010500036B003068026100D379919001D7FF40AC59DF475CF6C6368B1958DD4E870DFD1CE11218D5EA9D88DD7AD530E2806B0B092C02E25DB092518908EDA574A0968D49B0503954B24284FA75445A074CE6E1ABCEC8FD01DAA0D21A0DD97B417BC3E54BEB7253FC06D3F30203010001".ToByteArray(); - private static readonly byte[] DER_7 = "307C300D06092A864886F70D0101010500036B003068026100B751CB7D282625F2961A7138650ABE1A6AA80D69548BA3AE9DFF065B2805EB3675D960C62096C2835B1DF1C290FC19411944AFDF3458E3B1BC81A98C3F3E95D0EE0C20A0259E614399404354D90F0C69111A4E525F425FBB31A38B8C558F23730203010001".ToByteArray(); - private static readonly byte[] DER_8 = "307C300D06092A864886F70D0101010500036B003068026100B328FE4CC41627882B04FBA0A396A15285A8564B6112C1203048766D827E8E4E5655D44B266B2836575AE68C8301632A3E58B1F4362131E97B0AA0AFC38F2F7690CBD4F3F4652072BFD8E9421D2BEEF177873CD7D08B6C0D1022109CA3ED5B630203010001".ToByteArray(); - private static readonly byte[] DER_9 = "307C300D06092A864886F70D0101010500036B003068026100C4B32FD1161CC30D04BD569F409E878AA2815C91DD009A5AE8BFDAEA7D116BF24966BF10FCC0014B258DFEF6614E55FB6DAB2357CD6DF5B63A5F059F724469C0178D83F88F45048982EAE7A7CC249F84667FC393684DA5EFE1856EB10027D1D70203010001".ToByteArray(); - private static readonly byte[] DER_A = "307C300D06092A864886F70D0101010500036B003068026100C5B75401E83352A64EEC8916C4206F17EC338A24A6F7FD515260696D7228496ABC1423E1FF30514149FC199720E95E682539892E510B239A8C7A413DE4EEE74594F073815E9B434711F6807E8B9E7C10C281F89CF3B1C14E3F0ADF83A2805F090203010001".ToByteArray(); - private static readonly byte[] DER_B = "307C300D06092A864886F70D0101010500036B003068026100AC36B88D00C399C660B4846287FFC7F9DF5C07487EAAE3CD4EFD0029D3B86ED3658AD7DEE4C7F5DA25F9F6008885F343122274994CAB647776F0ADCFBA1E0ECEC8BF57CAAB8488BDD59A55195A0167C7D2C4A9CF679D0EFF4A62B5C8568E09770203010001".ToByteArray(); - private static readonly byte[] DER_C = "307C300D06092A864886F70D0101010500036B003068026100CAC0514D4B6A3F70771C461B01BDE3B6D47A0ADA078074DDA50703D8CC28089379DA64FB3A34AD3435D24F7331383BDADC4877662EFB555DA2077619B70AB0342EBE6EE888EBF3CF4B7E8BCCA95C61E993BDD6104C10D11115DC84178A5894350203010001".ToByteArray(); - private static readonly byte[] DER_D = "307C300D06092A864886F70D0101010500036B003068026100B906466740F5A9428DA84B418C7FA6146F7E24C783373D671F9214B40948A4A317C1A4460111B45D2DADD093815401573E52F0178890D35CBD95712EFAAE0D20AD47187648775CD9569431B1FC3C784113E3A48436D30B2CD162218D6781F5ED0203010001".ToByteArray(); - - private static readonly byte[] DER_LW = "307C300D06092A864886F70D0101010500036B003068026100B756E1DCD8CECE78E148107B1BAC115FDB17DE843453CAB7D4E6DF8DD21F5A3D17B4477A8A531D97D57EB558F0D58A4AF5BFADDDA4A0BC1DC22FF87576C7268B942819D4C83F78E1EE92D406662F4E68471E4DE833E5126C32EB63A868345D1D0203010001".ToByteArray(); - - private static readonly byte[] D_3 = "00775455668FFF3CBA3026C2D0B26B8085895958341157AEB03B6B0495EE57803E2186EB6CB2EB62A71DF18A3C9C6579077670961B3A6102DABE5A194AB58C3250AED597FC78978A326DB1D7B28DCCCB2A3E014EDBD397AD33B8F28CD525054251".ToByteArray(); - #endregion } + + /// + /// Indicates if this key can be used to resign messages. + /// + public bool CanResign => D != INVALID; + + /// + /// Get the AES key for this MemeKey + /// + private byte[] GetAesKey(ReadOnlySpan data) + { + if (data.Length < 0x60) + throw new ArgumentException("Memebuffers must be atleast 0x60 bytes long!"); + + var buffer = new byte[DER.Length + data.Length - 0x60]; + DER.CopyTo(buffer, 0); + data[..(buffer.Length - DER.Length)].CopyTo(buffer.AsSpan(DER.Length)); + + using var sha1 = SHA1.Create(); + var result = sha1.ComputeHash(buffer); + return result.AsSpan(0, 0x10).ToArray(); + } + + /// + /// Performs Aes Decryption + /// + internal byte[] AesDecrypt(ReadOnlySpan input) + { + var key = GetAesKey(input); + var data = input[^0x60..]; + var curblock = new byte[0x10]; + var outdata = new byte[data.Length]; + for (var i = data.Length - 0x10; i >= 0; i -= 0x10) // Reverse Phase 2 + { + var slice = data.Slice(i, 0x10); + Xor(curblock, slice, curblock); + curblock = AesEcbDecrypt(key, curblock); + curblock.CopyTo(outdata, i); + } + + // At this point we have Phase1(buf) ^ subkey. + // Subkey is (block first ^ block last) << 1 + // We don't have block first or block last, though? + // How can we derive subkey? + // Well, (a ^ a) = 0. so (block first ^ subkey) ^ (block last ^ subkey) + // = block first ^ block last ;) + var last = outdata.AsSpan(((data.Length / 0x10) - 1) * 0x10, 0x10); + var first = outdata.AsSpan(0, 0x10); + + Span subkey = stackalloc byte[0x10]; + Xor(last, first, curblock); + GetSubKey(curblock, subkey); + for (var i = 0; i < data.Length; i += 0x10) + { + var slice = outdata.AsSpan(i, 0x10); + Xor(slice, subkey, slice); + } + + // Now we have Phase1Encrypt(buf). + curblock.AsSpan().Clear(); // Clear to all zero + Span temp = stackalloc byte[0x10]; + for (var i = 0; i < data.Length; i += 0x10) // Phase 1: CBC Encryption. + { + var slice = outdata.AsSpan(i, 0x10); + slice.CopyTo(curblock); + var temp1 = AesEcbDecrypt(key, curblock); + Xor(temp1, temp, slice); + curblock.CopyTo(temp); + } + + var outbuf = input.ToArray(); + outdata.AsSpan()[..0x60].CopyTo(outbuf.AsSpan()[^0x60..]); + return outbuf; + } + + /// + /// Perform Aes Encryption + /// + internal byte[] AesEncrypt(ReadOnlySpan input) + { + var key = GetAesKey(input); + var data = input[^0x60..]; + var curblock = new byte[0x10]; + var outdata = new byte[data.Length]; + for (var i = 0; i < data.Length; i += 0x10) // Phase 1: CBC Encryption. + { + var slice = data.Slice(i, 0x10); + Xor(curblock, slice, curblock); + curblock = AesEcbEncrypt(key, curblock); + curblock.CopyTo(outdata, i); + } + + // In between - CMAC stuff + var inbet = outdata.AsSpan(0, 0x10); + Xor(curblock, inbet, curblock); + + Span subkey = stackalloc byte[0x10]; + GetSubKey(curblock, subkey); + + Span temp = stackalloc byte[0x10]; + curblock.AsSpan().Clear(); // Memcpy from an all-zero buffer + for (var i = data.Length - 0x10; i >= 0; i -= 0x10) + { + var slice = outdata.AsSpan(i, 0x10); + Xor(slice, subkey, curblock); + byte[] temp1 = AesEcbEncrypt(key, curblock); + Xor(temp1, temp, slice); + curblock.CopyTo(temp); + } + + var outbuf = input.ToArray(); + outdata.AsSpan()[..0x60].CopyTo(outbuf.AsSpan()[^0x60..]); + + return outbuf; + } + + private static void GetSubKey(ReadOnlySpan temp, Span subkey) + { + for (var ofs = 0; ofs < 0x10; ofs += 2) // Imperfect ROL implementation + { + byte b1 = temp[ofs + 0], b2 = temp[ofs + 1]; + subkey[ofs + 0] = (byte)((2 * b1) + (b2 >> 7)); + subkey[ofs + 1] = (byte)(2 * b2); + if (ofs + 2 < temp.Length) + subkey[ofs + 1] += (byte)(temp[ofs + 2] >> 7); + } + if ((temp[0] & 0x80) != 0) + subkey[0xF] ^= 0x87; + } + + private static void Xor(ReadOnlySpan b1, ReadOnlySpan b2, Span result) + { + Debug.Assert(b1.Length == b2.Length); + for (var i = 0; i < b1.Length; i++) + result[i] = (byte)(b1[i] ^ b2[i]); + } + + /// + /// Perform Rsa Decryption + /// + internal byte[] RsaPrivate(ReadOnlySpan data) + { + var _M = new byte[data.Length + 1]; + data.CopyTo(_M.AsSpan(1)); + Array.Reverse(_M); + var M = new BigInteger(_M); + + return Exponentiate(M, D); + } + + /// + /// Perform Rsa Encryption + /// + internal byte[] RsaPublic(ReadOnlySpan data) + { + var _M = new byte[data.Length + 1]; + data.CopyTo(_M.AsSpan(1)); + Array.Reverse(_M); + var M = new BigInteger(_M); + + return Exponentiate(M, E); + } + + #region MemeKey Helper Methods + /// Indicator value for a bad Exponent + private static readonly BigInteger INVALID = BigInteger.MinusOne; + + // Helper method for Modular Exponentiation + private byte[] Exponentiate(BigInteger M, BigInteger Power) + { + var rawSig = BigInteger.ModPow(M, Power, N).ToByteArray(); + Array.Reverse(rawSig); + var outSig = new byte[0x60]; + if (rawSig.Length < 0x60) + Array.Copy(rawSig, 0, outSig, 0x60 - rawSig.Length, rawSig.Length); + else if (rawSig.Length > 0x60) + Array.Copy(rawSig, rawSig.Length - 0x60, outSig, 0, 0x60); + else + Array.Copy(rawSig, outSig, 0x60); + return outSig; + } + + private static byte[] GetMemeData(MemeKeyIndex key) => key switch + { + MemeKeyIndex.LocalWireless => DER_LW, + MemeKeyIndex.FriendlyCompetition => DER_0, + MemeKeyIndex.LiveCompetition => DER_1, + MemeKeyIndex.RentalTeam => DER_2, + MemeKeyIndex.PokedexAndSaveFile => DER_3, + MemeKeyIndex.GaOle => DER_4, + MemeKeyIndex.MagearnaEvent => DER_5, + MemeKeyIndex.MoncolleGet => DER_6, + MemeKeyIndex.IslandScanEventSpecial => DER_7, + MemeKeyIndex.TvTokyoDataBroadcasting => DER_8, + MemeKeyIndex.CapPikachuEvent => DER_9, + MemeKeyIndex.Unknown10 => DER_A, + MemeKeyIndex.Unknown11 => DER_B, + MemeKeyIndex.Unknown12 => DER_C, + MemeKeyIndex.Unknown13 => DER_D, + _ => throw new ArgumentOutOfRangeException(nameof(key), key, null), + }; + + private static readonly byte[] rgbIV = new byte[0x10]; + + // Helper Method to perform AES ECB Encryption + private static byte[] AesEcbEncrypt(byte[] key, byte[] data) + { + using var ms = new MemoryStream(data.Length); + using var aes = Aes.Create(); + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; + + using var enc = aes.CreateEncryptor(key, rgbIV); + using var cs = new CryptoStream(ms, enc, CryptoStreamMode.Write); + cs.Write(data, 0, data.Length); + cs.FlushFinalBlock(); + + return ms.ToArray(); + } + + // Helper Method to perform AES ECB Decryption + private static byte[] AesEcbDecrypt(byte[] key, byte[] data) + { + using var ms = new MemoryStream(data.Length); + using var aes = Aes.Create(); + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; + + using var cs = new CryptoStream(ms, aes.CreateDecryptor(key, rgbIV), CryptoStreamMode.Write); + cs.Write(data, 0, data.Length); + cs.FlushFinalBlock(); + + return ms.ToArray(); + } + + public static bool IsValidPokeKeyIndex(int index) + { + if (!Enum.IsDefined(typeof(MemeKeyIndex), index)) + return false; + return (MemeKeyIndex)index != MemeKeyIndex.LocalWireless; + } + #endregion + + #region Official Keydata + private static readonly byte[] DER_0 = "307C300D06092A864886F70D0101010500036B003068026100B3D68C9B1090F6B1B88ECFA9E2F60E9C62C3033B5B64282F262CD393B433D97BD3DB7EBA470B1A77A3DB3C18A1E7616972229BDAD54FB02A19546C65FA4773AABE9B8C926707E7B7DDE4C867C01C0802985E438656168A4430F3F3B9662D7D010203010001".ToByteArray(); + private static readonly byte[] DER_1 = "307C300D06092A864886F70D0101010500036B003068026100C10F4097FD3C781A8FDE101EF3B2F091F82BEE4742324B9206C581766EAF2FBB42C7D60D749B999C529B0E22AD05E0C880231219AD473114EC454380A92898D7A8B54D9432584897D6AFE4860235126190A328DD6525D97B9058D98640B0FA050203010001".ToByteArray(); + private static readonly byte[] DER_2 = "307C300D06092A864886F70D0101010500036B003068026100C3C8D89F55D6A236A115C77594D4B318F0A0A0E3252CC0D6345EB9E33A43A5A56DC9D10B7B59C135396159EC4D01DEBC5FB3A4CAE47853E205FE08982DFCC0C39F0557449F97D41FED13B886AEBEEA918F4767E8FBE0494FFF6F6EE3508E3A3F0203010001".ToByteArray(); + private static readonly byte[] DER_3 = "307C300D06092A864886F70D0101010500036B003068026100B61E192091F90A8F76A6EAAA9A3CE58C863F39AE253F037816F5975854E07A9A456601E7C94C29759FE155C064EDDFA111443F81EF1A428CF6CD32F9DAC9D48E94CFB3F690120E8E6B9111ADDAF11E7C96208C37C0143FF2BF3D7E831141A9730203010001".ToByteArray(); + private static readonly byte[] DER_4 = "307C300D06092A864886F70D0101010500036B003068026100A0F2AC80B408E2E4D58916A1C706BEE7A24758A62CE9B50AF1B31409DFCB382E885AA8BB8C0E4AD1BCF6FF64FB3037757D2BEA10E4FE9007C850FFDCF70D2AFAA4C53FAFE38A9917D467862F50FE375927ECFEF433E61BF817A645FA5665D9CF0203010001".ToByteArray(); + private static readonly byte[] DER_5 = "307C300D06092A864886F70D0101010500036B003068026100D046F2872868A5089205B226DE13D86DA552646AC152C84615BE8E0A5897C3EA45871028F451860EA226D53B68DDD5A77D1AD82FAF857EA52CF7933112EEC367A06C0761E580D3D70B6B9C837BAA3F16D1FF7AA20D87A2A5E2BCC6E383BF12D50203010001".ToByteArray(); + private static readonly byte[] DER_6 = "307C300D06092A864886F70D0101010500036B003068026100D379919001D7FF40AC59DF475CF6C6368B1958DD4E870DFD1CE11218D5EA9D88DD7AD530E2806B0B092C02E25DB092518908EDA574A0968D49B0503954B24284FA75445A074CE6E1ABCEC8FD01DAA0D21A0DD97B417BC3E54BEB7253FC06D3F30203010001".ToByteArray(); + private static readonly byte[] DER_7 = "307C300D06092A864886F70D0101010500036B003068026100B751CB7D282625F2961A7138650ABE1A6AA80D69548BA3AE9DFF065B2805EB3675D960C62096C2835B1DF1C290FC19411944AFDF3458E3B1BC81A98C3F3E95D0EE0C20A0259E614399404354D90F0C69111A4E525F425FBB31A38B8C558F23730203010001".ToByteArray(); + private static readonly byte[] DER_8 = "307C300D06092A864886F70D0101010500036B003068026100B328FE4CC41627882B04FBA0A396A15285A8564B6112C1203048766D827E8E4E5655D44B266B2836575AE68C8301632A3E58B1F4362131E97B0AA0AFC38F2F7690CBD4F3F4652072BFD8E9421D2BEEF177873CD7D08B6C0D1022109CA3ED5B630203010001".ToByteArray(); + private static readonly byte[] DER_9 = "307C300D06092A864886F70D0101010500036B003068026100C4B32FD1161CC30D04BD569F409E878AA2815C91DD009A5AE8BFDAEA7D116BF24966BF10FCC0014B258DFEF6614E55FB6DAB2357CD6DF5B63A5F059F724469C0178D83F88F45048982EAE7A7CC249F84667FC393684DA5EFE1856EB10027D1D70203010001".ToByteArray(); + private static readonly byte[] DER_A = "307C300D06092A864886F70D0101010500036B003068026100C5B75401E83352A64EEC8916C4206F17EC338A24A6F7FD515260696D7228496ABC1423E1FF30514149FC199720E95E682539892E510B239A8C7A413DE4EEE74594F073815E9B434711F6807E8B9E7C10C281F89CF3B1C14E3F0ADF83A2805F090203010001".ToByteArray(); + private static readonly byte[] DER_B = "307C300D06092A864886F70D0101010500036B003068026100AC36B88D00C399C660B4846287FFC7F9DF5C07487EAAE3CD4EFD0029D3B86ED3658AD7DEE4C7F5DA25F9F6008885F343122274994CAB647776F0ADCFBA1E0ECEC8BF57CAAB8488BDD59A55195A0167C7D2C4A9CF679D0EFF4A62B5C8568E09770203010001".ToByteArray(); + private static readonly byte[] DER_C = "307C300D06092A864886F70D0101010500036B003068026100CAC0514D4B6A3F70771C461B01BDE3B6D47A0ADA078074DDA50703D8CC28089379DA64FB3A34AD3435D24F7331383BDADC4877662EFB555DA2077619B70AB0342EBE6EE888EBF3CF4B7E8BCCA95C61E993BDD6104C10D11115DC84178A5894350203010001".ToByteArray(); + private static readonly byte[] DER_D = "307C300D06092A864886F70D0101010500036B003068026100B906466740F5A9428DA84B418C7FA6146F7E24C783373D671F9214B40948A4A317C1A4460111B45D2DADD093815401573E52F0178890D35CBD95712EFAAE0D20AD47187648775CD9569431B1FC3C784113E3A48436D30B2CD162218D6781F5ED0203010001".ToByteArray(); + + private static readonly byte[] DER_LW = "307C300D06092A864886F70D0101010500036B003068026100B756E1DCD8CECE78E148107B1BAC115FDB17DE843453CAB7D4E6DF8DD21F5A3D17B4477A8A531D97D57EB558F0D58A4AF5BFADDDA4A0BC1DC22FF87576C7268B942819D4C83F78E1EE92D406662F4E68471E4DE833E5126C32EB63A868345D1D0203010001".ToByteArray(); + + private static readonly byte[] D_3 = "00775455668FFF3CBA3026C2D0B26B8085895958341157AEB03B6B0495EE57803E2186EB6CB2EB62A71DF18A3C9C6579077670961B3A6102DABE5A194AB58C3250AED597FC78978A326DB1D7B28DCCCB2A3E014EDBD397AD33B8F28CD525054251".ToByteArray(); + #endregion } diff --git a/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeKeyIndex.cs b/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeKeyIndex.cs index bb8b812e9..08ecbb756 100644 --- a/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeKeyIndex.cs +++ b/PKHeX.Core/Saves/Encryption/MemeCrypto/MemeKeyIndex.cs @@ -1,24 +1,23 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Different Key Types used for +/// +public enum MemeKeyIndex { - /// - /// Different Key Types used for - /// - public enum MemeKeyIndex - { - LocalWireless = -1, - FriendlyCompetition = 0, - LiveCompetition = 1, - RentalTeam = 2, - PokedexAndSaveFile = 3, - GaOle = 4, - MagearnaEvent = 5, - MoncolleGet = 6, - IslandScanEventSpecial = 7, - TvTokyoDataBroadcasting = 8, - CapPikachuEvent = 9, - Unknown10 = 10, - Unknown11 = 11, - Unknown12 = 12, - Unknown13 = 13, - } + LocalWireless = -1, + FriendlyCompetition = 0, + LiveCompetition = 1, + RentalTeam = 2, + PokedexAndSaveFile = 3, + GaOle = 4, + MagearnaEvent = 5, + MoncolleGet = 6, + IslandScanEventSpecial = 7, + TvTokyoDataBroadcasting = 8, + CapPikachuEvent = 9, + Unknown10 = 10, + Unknown11 = 11, + Unknown12 = 12, + Unknown13 = 13, } diff --git a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlock.cs b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlock.cs index 105c01996..89a001694 100644 --- a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlock.cs +++ b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlock.cs @@ -3,218 +3,217 @@ using System.IO; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Block of obtained from a encrypted block storage binary. +/// +public sealed class SCBlock { /// - /// Block of obtained from a encrypted block storage binary. + /// Used to encrypt the rest of the block. /// - public sealed class SCBlock + public readonly uint Key; + + /// + /// What kind of block is it? + /// + public SCTypeCode Type { get; private set; } + + /// + /// For : What kind of array is it? + /// + public readonly SCTypeCode SubType; + + /// + /// Decrypted data for this block. + /// + public readonly byte[] Data; + + /// + /// Changes the block's Boolean type. Will throw if the old / new is not boolean. + /// + /// New boolean type to set. + /// Will throw if the requested block state changes are incorrect. + public void ChangeBooleanType(SCTypeCode value) { - /// - /// Used to encrypt the rest of the block. - /// - public readonly uint Key; + if (Type is not (SCTypeCode.Bool1 or SCTypeCode.Bool2) || value is not (SCTypeCode.Bool1 or SCTypeCode.Bool2)) + throw new InvalidOperationException($"Cannot change {Type} to {value}."); + Type = value; + } - /// - /// What kind of block is it? - /// - public SCTypeCode Type { get; private set; } + /// + /// Replaces the current with a same-sized array . + /// + /// New array to load (copy from). + /// Will throw if the requested block state changes are incorrect. + public void ChangeData(ReadOnlySpan value) + { + if (value.Length != Data.Length) + throw new InvalidOperationException($"Cannot change size of {Type} block from {Data.Length} to {value.Length}."); + value.CopyTo(Data); + } - /// - /// For : What kind of array is it? - /// - public readonly SCTypeCode SubType; + /// + /// Creates a new block reference to indicate a boolean value via the (no data). + /// + /// Hash key + /// Value the block has + internal SCBlock(uint key, SCTypeCode type) : this(key, type, Array.Empty()) + { + } - /// - /// Decrypted data for this block. - /// - public readonly byte[] Data; + /// + /// Creates a new block reference to indicate an object or single primitive value. + /// + /// Hash key + /// Type of data that can be read + /// Backing byte array to interpret as a typed value + internal SCBlock(uint key, SCTypeCode type, byte[] arr) + { + Key = key; + Type = type; + Data = arr; + } - /// - /// Changes the block's Boolean type. Will throw if the old / new is not boolean. - /// - /// New boolean type to set. - /// Will throw if the requested block state changes are incorrect. - public void ChangeBooleanType(SCTypeCode value) + /// + /// Creates a new block reference to indicate an array of primitive values. + /// + /// Hash key + /// Backing byte array to read primitives from + /// Primitive value type + internal SCBlock(uint key, byte[] arr, SCTypeCode subType) + { + Key = key; + Type = SCTypeCode.Array; + Data = arr; + SubType = subType; + } + + /// Indicates if the block represents a single primitive value. + public bool HasValue() => Type > SCTypeCode.Array; + + /// Returns a boxed reference to a single primitive value. + /// Throws an exception if the block does not represent a single primitive value. + public object GetValue() => Type.GetValue(Data); + + /// Sets a boxed primitive value to the block data. + /// Throws an exception if the block does not represent a single primitive value, or if the primitive type does not match. + /// Boxed primitive value to be set to the block + public void SetValue(object value) => Type.SetValue(Data, value); + + /// + /// Creates a deep copy of the block. + /// + public SCBlock Clone() + { + if (Data.Length == 0) + return new SCBlock(Key, Type); + var clone = Data.AsSpan().ToArray(); + if (SubType == 0) + return new SCBlock(Key, Type, clone); + return new SCBlock(Key, clone, SubType); + } + + /// + /// Encrypts the according to the and . + /// + public void WriteBlock(BinaryWriter bw) + { + var xk = new SCXorShift32(Key); + bw.Write(Key); + bw.Write((byte)((byte)Type ^ xk.Next())); + + if (Type == SCTypeCode.Object) { - if (Type is not (SCTypeCode.Bool1 or SCTypeCode.Bool2) || value is not (SCTypeCode.Bool1 or SCTypeCode.Bool2)) - throw new InvalidOperationException($"Cannot change {Type} to {value}."); - Type = value; + bw.Write((uint)Data.Length ^ xk.Next32()); + } + else if (Type == SCTypeCode.Array) + { + var entries = (uint)(Data.Length / SubType.GetTypeSize()); + bw.Write(entries ^ xk.Next32()); + bw.Write((byte)((byte)SubType ^ xk.Next())); } - /// - /// Replaces the current with a same-sized array . - /// - /// New array to load (copy from). - /// Will throw if the requested block state changes are incorrect. - public void ChangeData(ReadOnlySpan value) + foreach (var b in Data) + bw.Write((byte)(b ^ xk.Next())); + } + + /// + /// Reads a new object from the , determining the and during read. + /// + /// Decrypted data + /// Offset the block is to be read from (modified to offset by the amount of bytes consumed). + /// New object containing all info for the block. + public static SCBlock ReadFromOffset(ReadOnlySpan data, ref int offset) + { + // Get key, initialize xorshift to decrypt + var key = ReadUInt32LittleEndian(data[offset..]); + offset += 4; + var xk = new SCXorShift32(key); + + // Parse the block's type + var type = (SCTypeCode)(data[offset++] ^ xk.Next()); + + switch (type) { - if (value.Length != Data.Length) - throw new InvalidOperationException($"Cannot change size of {Type} block from {Data.Length} to {value.Length}."); - value.CopyTo(Data); - } + case SCTypeCode.Bool1: + case SCTypeCode.Bool2: + case SCTypeCode.Bool3: + // Block types are empty, and have no extra data. + Debug.Assert(type != SCTypeCode.Bool3); // invalid type, haven't seen it used yet + return new SCBlock(key, type); - /// - /// Creates a new block reference to indicate a boolean value via the (no data). - /// - /// Hash key - /// Value the block has - internal SCBlock(uint key, SCTypeCode type) : this(key, type, Array.Empty()) - { - } - - /// - /// Creates a new block reference to indicate an object or single primitive value. - /// - /// Hash key - /// Type of data that can be read - /// Backing byte array to interpret as a typed value - internal SCBlock(uint key, SCTypeCode type, byte[] arr) - { - Key = key; - Type = type; - Data = arr; - } - - /// - /// Creates a new block reference to indicate an array of primitive values. - /// - /// Hash key - /// Backing byte array to read primitives from - /// Primitive value type - internal SCBlock(uint key, byte[] arr, SCTypeCode subType) - { - Key = key; - Type = SCTypeCode.Array; - Data = arr; - SubType = subType; - } - - /// Indicates if the block represents a single primitive value. - public bool HasValue() => Type > SCTypeCode.Array; - - /// Returns a boxed reference to a single primitive value. - /// Throws an exception if the block does not represent a single primitive value. - public object GetValue() => Type.GetValue(Data); - - /// Sets a boxed primitive value to the block data. - /// Throws an exception if the block does not represent a single primitive value, or if the primitive type does not match. - /// Boxed primitive value to be set to the block - public void SetValue(object value) => Type.SetValue(Data, value); - - /// - /// Creates a deep copy of the block. - /// - public SCBlock Clone() - { - if (Data.Length == 0) - return new SCBlock(Key, Type); - var clone = Data.AsSpan().ToArray(); - if (SubType == 0) - return new SCBlock(Key, Type, clone); - return new SCBlock(Key, clone, SubType); - } - - /// - /// Encrypts the according to the and . - /// - public void WriteBlock(BinaryWriter bw) - { - var xk = new SCXorShift32(Key); - bw.Write(Key); - bw.Write((byte)((byte)Type ^ xk.Next())); - - if (Type == SCTypeCode.Object) + case SCTypeCode.Object: // Cast raw bytes to Object { - bw.Write((uint)Data.Length ^ xk.Next32()); - } - else if (Type == SCTypeCode.Array) - { - var entries = (uint)(Data.Length / SubType.GetTypeSize()); - bw.Write(entries ^ xk.Next32()); - bw.Write((byte)((byte)SubType ^ xk.Next())); + var num_bytes = ReadInt32LittleEndian(data[offset..]) ^ (int)xk.Next32(); + offset += 4; + var arr = data.Slice(offset, num_bytes).ToArray(); + offset += num_bytes; + for (int i = 0; i < arr.Length; i++) + arr[i] ^= (byte)xk.Next(); + + return new SCBlock(key, type, arr); } - foreach (var b in Data) - bw.Write((byte)(b ^ xk.Next())); - } - - /// - /// Reads a new object from the , determining the and during read. - /// - /// Decrypted data - /// Offset the block is to be read from (modified to offset by the amount of bytes consumed). - /// New object containing all info for the block. - public static SCBlock ReadFromOffset(ReadOnlySpan data, ref int offset) - { - // Get key, initialize xorshift to decrypt - var key = ReadUInt32LittleEndian(data[offset..]); - offset += 4; - var xk = new SCXorShift32(key); - - // Parse the block's type - var type = (SCTypeCode)(data[offset++] ^ xk.Next()); - - switch (type) + case SCTypeCode.Array: // Cast raw bytes to SubType[] { - case SCTypeCode.Bool1: - case SCTypeCode.Bool2: - case SCTypeCode.Bool3: - // Block types are empty, and have no extra data. - Debug.Assert(type != SCTypeCode.Bool3); // invalid type, haven't seen it used yet - return new SCBlock(key, type); + var num_entries = ReadInt32LittleEndian(data[offset..]) ^ (int)xk.Next32(); + offset += 4; + var sub = (SCTypeCode)(data[offset++] ^ xk.Next()); - case SCTypeCode.Object: // Cast raw bytes to Object - { - var num_bytes = ReadInt32LittleEndian(data[offset..]) ^ (int)xk.Next32(); - offset += 4; - var arr = data.Slice(offset, num_bytes).ToArray(); - offset += num_bytes; - for (int i = 0; i < arr.Length; i++) - arr[i] ^= (byte)xk.Next(); - - return new SCBlock(key, type, arr); - } - - case SCTypeCode.Array: // Cast raw bytes to SubType[] - { - var num_entries = ReadInt32LittleEndian(data[offset..]) ^ (int)xk.Next32(); - offset += 4; - var sub = (SCTypeCode)(data[offset++] ^ xk.Next()); - - var num_bytes = num_entries * sub.GetTypeSize(); - var arr = data.Slice(offset, num_bytes).ToArray(); - offset += num_bytes; - for (int i = 0; i < arr.Length; i++) - arr[i] ^= (byte)xk.Next(); + var num_bytes = num_entries * sub.GetTypeSize(); + var arr = data.Slice(offset, num_bytes).ToArray(); + offset += num_bytes; + for (int i = 0; i < arr.Length; i++) + arr[i] ^= (byte)xk.Next(); #if DEBUG - Debug.Assert(sub > SCTypeCode.Array || (sub == SCTypeCode.Bool3 && Array.TrueForAll(arr, z => z <= 2)) || Array.TrueForAll(arr, z => z <= 1)); + Debug.Assert(sub > SCTypeCode.Array || (sub == SCTypeCode.Bool3 && Array.TrueForAll(arr, z => z <= 2)) || Array.TrueForAll(arr, z => z <= 1)); #endif - return new SCBlock(key, arr, sub); - } - - default: // Single Value Storage - { - var num_bytes = type.GetTypeSize(); - var arr = data.Slice(offset, num_bytes).ToArray(); - offset += num_bytes; - for (int i = 0; i < arr.Length; i++) - arr[i] ^= (byte)xk.Next(); - return new SCBlock(key, type, arr); - } + return new SCBlock(key, arr, sub); } - } - /// - /// Merges the properties from into this object. - /// - /// Block to copy all values from. - public void CopyFrom(SCBlock other) - { - if (Type.IsBoolean()) - ChangeBooleanType(other.Type); - else - ChangeData(other.Data); + default: // Single Value Storage + { + var num_bytes = type.GetTypeSize(); + var arr = data.Slice(offset, num_bytes).ToArray(); + offset += num_bytes; + for (int i = 0; i < arr.Length; i++) + arr[i] ^= (byte)xk.Next(); + return new SCBlock(key, type, arr); + } } } + + /// + /// Merges the properties from into this object. + /// + /// Block to copy all values from. + public void CopyFrom(SCBlock other) + { + if (Type.IsBoolean()) + ChangeBooleanType(other.Type); + else + ChangeData(other.Data); + } } diff --git a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockCompare.cs b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockCompare.cs index 63ae4610a..e7780b586 100644 --- a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockCompare.cs +++ b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockCompare.cs @@ -1,130 +1,129 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SCBlockCompare { - public sealed class SCBlockCompare + private readonly List AddedKeys = new(); + private readonly List RemovedKeys = new(); + private readonly List TypesChanged = new(); + private readonly List ValueChanged = new(); + + private readonly Dictionary KeyNames; + private string GetKeyName(uint key) => KeyNames.TryGetValue(key, out var value) ? value : $"{key:X8}"; + + public SCBlockCompare(SCBlockAccessor s1, SCBlockAccessor s2, IEnumerable extraKeyNames) { - private readonly List AddedKeys = new(); - private readonly List RemovedKeys = new(); - private readonly List TypesChanged = new(); - private readonly List ValueChanged = new(); + var b1 = s1.BlockInfo; + var b2 = s2.BlockInfo; + KeyNames = GetKeyNames(s1, b1, b2); + SCBlockMetadata.AddExtraKeyNames(KeyNames, extraKeyNames); - private readonly Dictionary KeyNames; - private string GetKeyName(uint key) => KeyNames.TryGetValue(key, out var value) ? value : $"{key:X8}"; + var hs1 = new HashSet(b1.Select(z => z.Key)); + var hs2 = new HashSet(b2.Select(z => z.Key)); - public SCBlockCompare(SCBlockAccessor s1, SCBlockAccessor s2, IEnumerable extraKeyNames) + LoadAddRemove(s1, s2, hs1, hs2); + hs1.IntersectWith(hs2); + LoadChanged(s1, s2, hs1); + } + + private void LoadAddRemove(SCBlockAccessor s1, SCBlockAccessor s2, ICollection hs1, IEnumerable hs2) + { + var unique = new HashSet(hs1); + unique.SymmetricExceptWith(hs2); + foreach (var k in unique) { - var b1 = s1.BlockInfo; - var b2 = s2.BlockInfo; - KeyNames = GetKeyNames(s1, b1, b2); - SCBlockMetadata.AddExtraKeyNames(KeyNames, extraKeyNames); - - var hs1 = new HashSet(b1.Select(z => z.Key)); - var hs2 = new HashSet(b2.Select(z => z.Key)); - - LoadAddRemove(s1, s2, hs1, hs2); - hs1.IntersectWith(hs2); - LoadChanged(s1, s2, hs1); - } - - private void LoadAddRemove(SCBlockAccessor s1, SCBlockAccessor s2, ICollection hs1, IEnumerable hs2) - { - var unique = new HashSet(hs1); - unique.SymmetricExceptWith(hs2); - foreach (var k in unique) + var name = GetKeyName(k); + if (hs1.Contains(k)) { - var name = GetKeyName(k); - if (hs1.Contains(k)) - { - var b = s1.GetBlock(k); - RemovedKeys.Add($"{name} - {b.Type}"); - } - else - { - var b = s2.GetBlock(k); - AddedKeys.Add($"{name} - {b.Type}"); - } + var b = s1.GetBlock(k); + RemovedKeys.Add($"{name} - {b.Type}"); } - } - - private void LoadChanged(SCBlockAccessor s1, SCBlockAccessor s2, IEnumerable shared) - { - foreach (var k in shared) + else { - var x1 = s1.GetBlock(k); - var x2 = s2.GetBlock(k); - var name = GetKeyName(x1.Key); - if (x1.Type != x2.Type) - { - TypesChanged.Add($"{name} - {x1.Type} => {x2.Type}"); - continue; - } - if (x1.Data.Length != x2.Data.Length) - { - ValueChanged.Add($"{name} - Length: {x1.Data.Length} => {x2.Data.Length}"); - continue; - } - - if (x1.Data.Length == 0) - continue; - - if (x1.Type is SCTypeCode.Object or SCTypeCode.Array) - { - if (!x1.Data.SequenceEqual(x2.Data)) - ValueChanged.Add($"{name} - Bytes Changed"); - continue; - } - - var val1 = x1.GetValue(); - var val2 = x2.GetValue(); - if (Equals(val1, val2)) - continue; - if (val1 is ulong u1 && val2 is ulong u2) - ValueChanged.Add($"{name} - {u1:X8} => {u2:x8}"); - else - ValueChanged.Add($"{name} - {val1} => {val2}"); - } - } - - private static Dictionary GetKeyNames(SCBlockAccessor s1, IEnumerable b1, IEnumerable b2) - { - var aType = s1.GetType(); - var b1n = aType.GetAllPropertiesOfType(s1); - var names = aType.GetAllConstantsOfType(); - Add(b1n, b1); - Add(b1n, b2); - - void Add(Dictionary list, IEnumerable blocks) - { - foreach (var b in blocks) - { - var match = list.FirstOrDefault(z => ReferenceEquals(z.Key.Data, b.Data)); - if (match.Value != null && names.ContainsKey(b.Key)) - names[b.Key] = match.Value; - } - } - return names; - } - - public IReadOnlyList Summary() - { - var result = new List(); - AddIfPresent(result, AddedKeys, "Blocks Added:"); - AddIfPresent(result, RemovedKeys, "Blocks Removed:"); - AddIfPresent(result, TypesChanged, "BlockType Changed:"); - AddIfPresent(result, ValueChanged, "Value Changed:"); - - return result; - - static void AddIfPresent(List result, ICollection list, string hdr) - { - if (list.Count == 0) - return; - result.Add(hdr); - result.AddRange(list); - result.Add(string.Empty); + var b = s2.GetBlock(k); + AddedKeys.Add($"{name} - {b.Type}"); } } } -} \ No newline at end of file + + private void LoadChanged(SCBlockAccessor s1, SCBlockAccessor s2, IEnumerable shared) + { + foreach (var k in shared) + { + var x1 = s1.GetBlock(k); + var x2 = s2.GetBlock(k); + var name = GetKeyName(x1.Key); + if (x1.Type != x2.Type) + { + TypesChanged.Add($"{name} - {x1.Type} => {x2.Type}"); + continue; + } + if (x1.Data.Length != x2.Data.Length) + { + ValueChanged.Add($"{name} - Length: {x1.Data.Length} => {x2.Data.Length}"); + continue; + } + + if (x1.Data.Length == 0) + continue; + + if (x1.Type is SCTypeCode.Object or SCTypeCode.Array) + { + if (!x1.Data.SequenceEqual(x2.Data)) + ValueChanged.Add($"{name} - Bytes Changed"); + continue; + } + + var val1 = x1.GetValue(); + var val2 = x2.GetValue(); + if (Equals(val1, val2)) + continue; + if (val1 is ulong u1 && val2 is ulong u2) + ValueChanged.Add($"{name} - {u1:X8} => {u2:x8}"); + else + ValueChanged.Add($"{name} - {val1} => {val2}"); + } + } + + private static Dictionary GetKeyNames(SCBlockAccessor s1, IEnumerable b1, IEnumerable b2) + { + var aType = s1.GetType(); + var b1n = aType.GetAllPropertiesOfType(s1); + var names = aType.GetAllConstantsOfType(); + Add(b1n, b1); + Add(b1n, b2); + + void Add(Dictionary list, IEnumerable blocks) + { + foreach (var b in blocks) + { + var match = list.FirstOrDefault(z => ReferenceEquals(z.Key.Data, b.Data)); + if (match.Value != null && names.ContainsKey(b.Key)) + names[b.Key] = match.Value; + } + } + return names; + } + + public IReadOnlyList Summary() + { + var result = new List(); + AddIfPresent(result, AddedKeys, "Blocks Added:"); + AddIfPresent(result, RemovedKeys, "Blocks Removed:"); + AddIfPresent(result, TypesChanged, "BlockType Changed:"); + AddIfPresent(result, ValueChanged, "Value Changed:"); + + return result; + + static void AddIfPresent(List result, ICollection list, string hdr) + { + if (list.Count == 0) + return; + result.Add(hdr); + result.AddRange(list); + result.Add(string.Empty); + } + } +} diff --git a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockMetadata.cs b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockMetadata.cs index 4a55b0051..468fb0822 100644 --- a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockMetadata.cs +++ b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockMetadata.cs @@ -4,163 +4,158 @@ using System.Globalization; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides reflection utility for manipulating blocks, providing block names and value wrapping. +/// +public sealed class SCBlockMetadata { + private readonly Dictionary BlockList; + private readonly Dictionary ValueList; + private readonly SCBlockAccessor Accessor; + /// - /// Provides reflection utility for manipulating blocks, providing block names and value wrapping. + /// Creates a new instance of by loading properties and constants declared via reflection. /// - public sealed class SCBlockMetadata + public SCBlockMetadata(SCBlockAccessor accessor, IEnumerable extraKeyNames, params string[] exclusions) { - private readonly Dictionary BlockList; - private readonly Dictionary ValueList; - private readonly SCBlockAccessor Accessor; + var aType = accessor.GetType(); - /// - /// Creates a new instance of by loading properties and constants declared via reflection. - /// - public SCBlockMetadata(SCBlockAccessor accessor, IEnumerable extraKeyNames, params string[] exclusions) + BlockList = aType.GetAllPropertiesOfType(accessor); + ValueList = aType.GetAllConstantsOfType(); + AddExtraKeyNames(ValueList, extraKeyNames); + if (exclusions.Length > 0) + ValueList = ValueList.Where(z => !exclusions.Any(z.Value.Contains)).ToDictionary(z => z.Key, z => z.Value); + Accessor = accessor; + } + + /// + /// Returns a list of block details, ordered by their type and . + /// + public IEnumerable GetSortedBlockKeyList() => Accessor.BlockInfo + .Select((z, i) => new ComboItem(GetBlockHint(z, i), (int)z.Key)) + .OrderBy(z => !(z.Text.Length != 0 && z.Text[0] == '*')) + .ThenBy(z => GetSortKey(z)); + + /// + /// Loads names from an external file to the requested list. + /// + /// Tab separated text file expected. + /// Currently loaded list of block names + /// Tab separated key-value pair list of block names. + public static void AddExtraKeyNames(IDictionary names, IEnumerable lines) + { + foreach (var line in lines) { - var aType = accessor.GetType(); + var split = line.IndexOf('\t'); + if (split < 0) + continue; + var hex = line[..split]; + if (!ulong.TryParse(hex, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out var value)) + continue; - BlockList = aType.GetAllPropertiesOfType(accessor); - ValueList = aType.GetAllConstantsOfType(); - AddExtraKeyNames(ValueList, extraKeyNames); - if (exclusions.Length > 0) - ValueList = ValueList.Where(z => !exclusions.Any(z.Value.Contains)).ToDictionary(z => z.Key, z => z.Value); - Accessor = accessor; + var name = line[(split + 1)..]; + if (!names.ContainsKey((uint) value)) + names[(uint) value] = name; } + } - /// - /// Returns a list of block details, ordered by their type and . - /// - public IEnumerable GetSortedBlockKeyList() - { - var list = Accessor.BlockInfo - .Select((z, i) => new ComboItem(GetBlockHint(z, i), (int)z.Key)) - .OrderBy(z => !z.Text.StartsWith("*")) - .ThenBy(z => GetSortKey(z)); - return list; - } + private static string GetSortKey(in ComboItem item) + { + var text = item.Text; + if (text.Length != 0 && text[0] == '*') + return text; + // key:X8, " - ", "####", " ", type + return text[(8 + 3 + 4 + 1)..]; + } - /// - /// Loads names from an external file to the requested list. - /// - /// Tab separated text file expected. - /// Currently loaded list of block names - /// Tab separated key-value pair list of block names. - public static void AddExtraKeyNames(IDictionary names, IEnumerable lines) + private string GetBlockHint(SCBlock z, int index) + { + var blockName = GetBlockName(z, out _); + var isBool = z.Type.IsBoolean(); + var type = (isBool ? "Bool" : z.Type.ToString()); + if (blockName != null) + return $"*{type} {blockName}"; + var result = $"{z.Key:X8} - {index:0000} {type}"; + if (z.Type is SCTypeCode.Object or SCTypeCode.Array) + result += $" 0x{z.Data.Length:X3}"; + else if (!isBool) + result += $" {z.GetValue()}"; + return result; + } + + /// + /// Searches the to see if a named Save Block originate from the requested . If no block exists, the logic will check for a named stored-value. + /// + /// Block requesting the name of + /// Block that shares the same backing byte array; null if none. + /// Name of the block indicating the purpose that it serves in-game. + public string? GetBlockName(SCBlock block, out IDataIndirect? saveBlock) + { + // See if we have a Block object for this block + if (block.Data.Length != 0) { - foreach (var line in lines) + var obj = BlockList.FirstOrDefault(z => ReferenceEquals(z.Key.Data, block.Data)); + if (obj.Key != null) { - var split = line.IndexOf('\t'); - if (split < 0) - continue; - var hex = line[..split]; - if (!ulong.TryParse(hex, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out var value)) - continue; - - var name = line[(split + 1)..]; - if (!names.ContainsKey((uint) value)) - names[(uint) value] = name; + saveBlock = obj.Key; + return obj.Value; } } - private static string GetSortKey(in ComboItem item) + // See if it's a single-value declaration + if (ValueList.TryGetValue(block.Key, out var blockName)) { - var text = item.Text; - if (text.StartsWith("*")) - return text; - // key:X8, " - ", "####", " ", type - return text[(8 + 3 + 4 + 1)..]; - } - - private string GetBlockHint(SCBlock z, int index) - { - var blockName = GetBlockName(z, out _); - var isBool = z.Type.IsBoolean(); - var type = (isBool ? "Bool" : z.Type.ToString()); - if (blockName != null) - return $"*{type} {blockName}"; - var result = $"{z.Key:X8} - {index:0000} {type}"; - if (z.Type is SCTypeCode.Object or SCTypeCode.Array) - result += $" 0x{z.Data.Length:X3}"; - else if (!isBool) - result += $" {z.GetValue()}"; - return result; - } - - /// - /// Searches the to see if a named Save Block originate from the requested . If no block exists, the logic will check for a named stored-value. - /// - /// Block requesting the name of - /// Block that shares the same backing byte array; null if none. - /// Name of the block indicating the purpose that it serves in-game. - public string? GetBlockName(SCBlock block, out IDataIndirect? saveBlock) - { - // See if we have a Block object for this block - if (block.Data.Length != 0) - { - var obj = BlockList.FirstOrDefault(z => ReferenceEquals(z.Key.Data, block.Data)); - if (obj.Key != null) - { - saveBlock = obj.Key; - return obj.Value; - } - } - - // See if it's a single-value declaration - if (ValueList.TryGetValue(block.Key, out var blockName)) - { - saveBlock = null; - return blockName; - } saveBlock = null; - return null; + return blockName; + } + saveBlock = null; + return null; + } + + /// + /// Returns an object that wraps the block with a Value property to get/set via a PropertyGrid/etc control. + /// + /// Returns null if no wrapping is supported. + public static object? GetEditableBlockObject(SCBlock block) => block.Type switch + { + SCTypeCode.Byte => new WrappedValueView(block, block.GetValue()), + SCTypeCode.UInt16 => new WrappedValueView(block, block.GetValue()), + SCTypeCode.UInt32 => new WrappedValueView(block, block.GetValue()), + SCTypeCode.UInt64 => new WrappedValueView(block, block.GetValue()), + + SCTypeCode.SByte => new WrappedValueView(block, block.GetValue()), + SCTypeCode.Int16 => new WrappedValueView(block, block.GetValue()), + SCTypeCode.Int32 => new WrappedValueView(block, block.GetValue()), + SCTypeCode.Int64 => new WrappedValueView(block, block.GetValue()), + + SCTypeCode.Single => new WrappedValueView(block, block.GetValue()), + SCTypeCode.Double => new WrappedValueView(block, block.GetValue()), + + _ => null, + }; + + private sealed class WrappedValueView where T : struct + { + private readonly SCBlock Parent; + private T _value; + + [Description("Stored Value for this Block")] + public T Value + { + get => _value; + set => Parent.SetValue(_value = value); } - /// - /// Returns an object that wraps the block with a Value property to get/set via a PropertyGrid/etc control. - /// - /// Returns null if no wrapping is supported. - public static object? GetEditableBlockObject(SCBlock block) => block.Type switch + // ReSharper disable once UnusedMember.Local + [Description("Type of Value this Block stores")] + public string ValueType => typeof(T).Name; + + public WrappedValueView(SCBlock block, object currentValue) { - SCTypeCode.Byte => new WrappedValueView(block, block.GetValue()), - SCTypeCode.UInt16 => new WrappedValueView(block, block.GetValue()), - SCTypeCode.UInt32 => new WrappedValueView(block, block.GetValue()), - SCTypeCode.UInt64 => new WrappedValueView(block, block.GetValue()), - - SCTypeCode.SByte => new WrappedValueView(block, block.GetValue()), - SCTypeCode.Int16 => new WrappedValueView(block, block.GetValue()), - SCTypeCode.Int32 => new WrappedValueView(block, block.GetValue()), - SCTypeCode.Int64 => new WrappedValueView(block, block.GetValue()), - - SCTypeCode.Single => new WrappedValueView(block, block.GetValue()), - SCTypeCode.Double => new WrappedValueView(block, block.GetValue()), - - _ => null, - }; - - private sealed class WrappedValueView where T : struct - { - private readonly SCBlock Parent; - private T _value; - - [Description("Stored Value for this Block")] - public T Value - { - get => _value; - set => Parent.SetValue(_value = value); - } - - // ReSharper disable once UnusedMember.Local - [Description("Type of Value this Block stores")] - public string ValueType => typeof(T).Name; - - public WrappedValueView(SCBlock block, object currentValue) - { - Parent = block; - _value = (T)Convert.ChangeType(currentValue, typeof(T)); - } + Parent = block; + _value = (T)Convert.ChangeType(currentValue, typeof(T)); } } } diff --git a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockUtil.cs b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockUtil.cs index b8fdf374f..19c0474fd 100644 --- a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockUtil.cs +++ b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlockUtil.cs @@ -3,151 +3,150 @@ using System.IO; using System.Text; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Utility logic for dumping lists for external analysis +/// +public static class SCBlockUtil { - /// - /// Utility logic for dumping lists for external analysis - /// - public static class SCBlockUtil + public static void ExportAllBlocksAsSingleFile(IReadOnlyList blocks, string path, SCBlockExportOption option = SCBlockExportOption.All) { - public static void ExportAllBlocksAsSingleFile(IReadOnlyList blocks, string path, SCBlockExportOption option = SCBlockExportOption.All) + var data = ExportAllBlocks(blocks, option); + File.WriteAllBytes(path, data); + } + + public static byte[] ExportAllBlocks(IReadOnlyList blocks, SCBlockExportOption option = SCBlockExportOption.None) + { + if (option == SCBlockExportOption.None) + return SwishCrypto.GetDecryptedRawData(blocks); + + using var stream = new MemoryStream(); + using var bw = new BinaryWriter(stream); + for (var i = 0; i < blocks.Count; i++) + ExportBlock(blocks[i], bw, i, option); + return stream.ToArray(); + } + + private static void ExportBlock(SCBlock block, BinaryWriter bw, int blockIndex, SCBlockExportOption option) + { + if (option.HasFlagFast(SCBlockExportOption.DataOnly) && block.Data.Length == 0) + return; + + if (option.HasFlagFast(SCBlockExportOption.FakeHeader)) + bw.Write($"BLOCK{blockIndex:0000} {block.Key:X8}"); + + if (option.HasFlagFast(SCBlockExportOption.Key)) + bw.Write(block.Key); + + if (option.HasFlagFast(SCBlockExportOption.TypeInfo)) { - var data = ExportAllBlocks(blocks, option); - File.WriteAllBytes(path, data); + bw.Write((byte) block.Type); + bw.Write((byte) block.SubType); } - public static byte[] ExportAllBlocks(IReadOnlyList blocks, SCBlockExportOption option = SCBlockExportOption.None) + bw.Write(block.Data); + } + + public static string GetBlockFileNameWithoutExtension(SCBlock block) + { + var key = block.Key; + var name = $"{key:X8}"; + if (block.HasValue()) + name += $" {block.GetValue()}"; + return name; + } + + public static string GetBlockSummary(SCBlock b) + { + var sb = new StringBuilder(64); + sb.Append("Key: ").AppendFormat("{0:X8}", b.Key).AppendLine(); + sb.Append("Type: ").Append(b.Type).AppendLine(); + if (b.Data.Length != 0) + sb.Append("Length: ").AppendFormat("{0:X8}", b.Data.Length).AppendLine(); + + if (b.SubType != 0) + sb.Append("SubType: ").Append(b.SubType).AppendLine(); + else if (b.HasValue()) + sb.Append("Value: ").Append(b.GetValue()).AppendLine(); + + return sb.ToString(); + } + + public static List ImportBlocksFromFolder(string path, ISCBlockArray sav) + { + var failed = new List(); + var files = Directory.EnumerateFiles(path); + foreach (var f in files) { - if (option == SCBlockExportOption.None) - return SwishCrypto.GetDecryptedRawData(blocks); + var fn = Path.GetFileNameWithoutExtension(f); - using var stream = new MemoryStream(); - using var bw = new BinaryWriter(stream); - for (var i = 0; i < blocks.Count; i++) - ExportBlock(blocks[i], bw, i, option); - return stream.ToArray(); - } + // Trim off Value summary if present + var space = fn.IndexOf(' '); + if (space != -1) + fn = fn[..space]; - private static void ExportBlock(SCBlock block, BinaryWriter bw, int blockIndex, SCBlockExportOption option) - { - if (option.HasFlagFast(SCBlockExportOption.DataOnly) && block.Data.Length == 0) - return; - - if (option.HasFlagFast(SCBlockExportOption.FakeHeader)) - bw.Write($"BLOCK{blockIndex:0000} {block.Key:X8}"); - - if (option.HasFlagFast(SCBlockExportOption.Key)) - bw.Write(block.Key); - - if (option.HasFlagFast(SCBlockExportOption.TypeInfo)) + var hex = Util.GetHexValue(fn); + try { - bw.Write((byte) block.Type); - bw.Write((byte) block.SubType); - } - - bw.Write(block.Data); - } - - public static string GetBlockFileNameWithoutExtension(SCBlock block) - { - var key = block.Key; - var name = $"{key:X8}"; - if (block.HasValue()) - name += $" {block.GetValue()}"; - return name; - } - - public static string GetBlockSummary(SCBlock b) - { - var sb = new StringBuilder(64); - sb.Append("Key: ").AppendFormat("{0:X8}", b.Key).AppendLine(); - sb.Append("Type: ").Append(b.Type).AppendLine(); - if (b.Data.Length != 0) - sb.Append("Length: ").AppendFormat("{0:X8}", b.Data.Length).AppendLine(); - - if (b.SubType != 0) - sb.Append("SubType: ").Append(b.SubType).AppendLine(); - else if (b.HasValue()) - sb.Append("Value: ").Append(b.GetValue()).AppendLine(); - - return sb.ToString(); - } - - public static List ImportBlocksFromFolder(string path, ISCBlockArray sav) - { - var failed = new List(); - var files = Directory.EnumerateFiles(path); - foreach (var f in files) - { - var fn = Path.GetFileNameWithoutExtension(f); - - // Trim off Value summary if present - var space = fn.IndexOf(' '); - if (space != -1) - fn = fn[..space]; - - var hex = Util.GetHexValue(fn); - try - { - var block = sav.Accessor.GetBlock(hex); - var len = block.Data.Length; - var fi = new FileInfo(f); - if (fi.Length != len) - { - failed.Add(fn); - continue; - } - - var data = File.ReadAllBytes(f); - block.ChangeData(data); - } - catch + var block = sav.Accessor.GetBlock(hex); + var len = block.Data.Length; + var fi = new FileInfo(f); + if (fi.Length != len) { failed.Add(fn); + continue; } + + var data = File.ReadAllBytes(f); + block.ChangeData(data); + } + catch + { + failed.Add(fn); } - - return failed; } - } - [Flags] - public enum SCBlockExportOption - { - None = 0, - - /// - /// Will only export blocks with backing data. - /// - /// Excludes Bool flags from the dump. - DataOnly = 1, - - /// - /// Includes the Block Key ahead of the data. - /// - Key = 2, - - /// - /// Includes the Block Info ahead of the data. - /// - TypeInfo = 4, - - /// - /// Includes a fake header indicating which block it is in ASCII. - /// - FakeHeader = 8, - - /// - /// Standard export options. - /// - All = DataOnly | Key | TypeInfo | FakeHeader, - } - - internal static class ScBlockExportOptionExtensions - { - public static bool HasFlagFast(this SCBlockExportOption value, SCBlockExportOption flag) - { - return (value & flag) != 0; - } + return failed; + } +} + +[Flags] +public enum SCBlockExportOption +{ + None = 0, + + /// + /// Will only export blocks with backing data. + /// + /// Excludes Bool flags from the dump. + DataOnly = 1, + + /// + /// Includes the Block Key ahead of the data. + /// + Key = 2, + + /// + /// Includes the Block Info ahead of the data. + /// + TypeInfo = 4, + + /// + /// Includes a fake header indicating which block it is in ASCII. + /// + FakeHeader = 8, + + /// + /// Standard export options. + /// + All = DataOnly | Key | TypeInfo | FakeHeader, +} + +internal static class ScBlockExportOptionExtensions +{ + public static bool HasFlagFast(this SCBlockExportOption value, SCBlockExportOption flag) + { + return (value & flag) != 0; } } diff --git a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCTypeCode.cs b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCTypeCode.cs index 7f2d12bca..93fbdb076 100644 --- a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCTypeCode.cs +++ b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCTypeCode.cs @@ -1,117 +1,116 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Block type for a . +/// +public enum SCTypeCode : byte { - /// - /// Block type for a . - /// - public enum SCTypeCode : byte + None = 0, + + Bool1 = 1, // False? + Bool2 = 2, // True? + Bool3 = 3, // Either? (Array boolean type) + + Object = 4, + + Array = 5, + + Byte = 8, + UInt16 = 9, + UInt32 = 10, + UInt64 = 11, + SByte = 12, + Int16 = 13, + Int32 = 14, + Int64 = 15, + Single = 16, + Double = 17, +} + +public static class SCTypeCodeExtensions +{ + public static bool IsBoolean(this SCTypeCode type) => unchecked((uint)type - 1) < 3; + + public static int GetTypeSize(this SCTypeCode type) => type switch { - None = 0, + SCTypeCode.Bool3 => sizeof(bool), - Bool1 = 1, // False? - Bool2 = 2, // True? - Bool3 = 3, // Either? (Array boolean type) + SCTypeCode.Byte => sizeof(byte), + SCTypeCode.UInt16 => sizeof(ushort), + SCTypeCode.UInt32 => sizeof(uint), + SCTypeCode.UInt64 => sizeof(ulong), - Object = 4, + SCTypeCode.SByte => sizeof(sbyte), + SCTypeCode.Int16 => sizeof(short), + SCTypeCode.Int32 => sizeof(int), + SCTypeCode.Int64 => sizeof(long), - Array = 5, + SCTypeCode.Single => sizeof(float), + SCTypeCode.Double => sizeof(double), - Byte = 8, - UInt16 = 9, - UInt32 = 10, - UInt64 = 11, - SByte = 12, - Int16 = 13, - Int32 = 14, - Int64 = 15, - Single = 16, - Double = 17, + _ => throw new ArgumentOutOfRangeException(nameof(type), type.ToString()), + }; + + public static Type GetType(this SCTypeCode type) => type switch + { + SCTypeCode.Byte => typeof(byte), + SCTypeCode.UInt16 => typeof(ushort), + SCTypeCode.UInt32 => typeof(uint), + SCTypeCode.UInt64 => typeof(ulong), + + SCTypeCode.SByte => typeof(sbyte), + SCTypeCode.Int16 => typeof(short), + SCTypeCode.Int32 => typeof(int), + SCTypeCode.Int64 => typeof(long), + + SCTypeCode.Single => typeof(float), + SCTypeCode.Double => typeof(double), + + _ => throw new ArgumentOutOfRangeException(nameof(type), type.ToString()), + }; + + public static object GetValue(this SCTypeCode type, ReadOnlySpan data) + { + // don't use a switch expression here, we want to box our underlying type rather than the last type (double) + switch (type) + { + case SCTypeCode.Byte: return data[0]; + case SCTypeCode.UInt16: return ReadUInt16LittleEndian(data); + case SCTypeCode.UInt32: return ReadUInt32LittleEndian(data); + case SCTypeCode.UInt64: return ReadUInt64LittleEndian(data); + case SCTypeCode.SByte: return (sbyte) data[0]; + case SCTypeCode.Int16: return ReadInt16LittleEndian(data); + case SCTypeCode.Int32: return ReadInt32LittleEndian(data); + case SCTypeCode.Int64: return ReadInt64LittleEndian(data); + case SCTypeCode.Single: return ReadSingleLittleEndian(data); + case SCTypeCode.Double: return ReadDoubleLittleEndian(data); + default: + throw new ArgumentOutOfRangeException(nameof(type), type.ToString()); + } } - public static class SCTypeCodeExtensions + public static void SetValue(this SCTypeCode type, Span data, object value) { - public static bool IsBoolean(this SCTypeCode type) => unchecked((uint)type - 1) < 3; - - public static int GetTypeSize(this SCTypeCode type) => type switch + switch (type) { - SCTypeCode.Bool3 => sizeof(bool), + case SCTypeCode.Byte: data[0] = (byte)value; break; + case SCTypeCode.UInt16: WriteUInt16LittleEndian(data, (ushort)value); break; + case SCTypeCode.UInt32: WriteUInt32LittleEndian(data, (uint)value); break; + case SCTypeCode.UInt64: WriteUInt64LittleEndian(data, (ulong)value); break; - SCTypeCode.Byte => sizeof(byte), - SCTypeCode.UInt16 => sizeof(ushort), - SCTypeCode.UInt32 => sizeof(uint), - SCTypeCode.UInt64 => sizeof(ulong), + case SCTypeCode.SByte: data[0] = (byte)value; break; + case SCTypeCode.Int16: WriteInt16LittleEndian(data, (short)value); break; + case SCTypeCode.Int32: WriteInt32LittleEndian(data, (int)value); break; + case SCTypeCode.Int64: WriteInt64LittleEndian(data, (long)value); break; - SCTypeCode.SByte => sizeof(sbyte), - SCTypeCode.Int16 => sizeof(short), - SCTypeCode.Int32 => sizeof(int), - SCTypeCode.Int64 => sizeof(long), + case SCTypeCode.Single: WriteSingleLittleEndian(data, (float)value); break; + case SCTypeCode.Double: WriteDoubleLittleEndian(data, (double)value); break; - SCTypeCode.Single => sizeof(float), - SCTypeCode.Double => sizeof(double), - - _ => throw new ArgumentOutOfRangeException(nameof(type), type.ToString()), - }; - - public static Type GetType(this SCTypeCode type) => type switch - { - SCTypeCode.Byte => typeof(byte), - SCTypeCode.UInt16 => typeof(ushort), - SCTypeCode.UInt32 => typeof(uint), - SCTypeCode.UInt64 => typeof(ulong), - - SCTypeCode.SByte => typeof(sbyte), - SCTypeCode.Int16 => typeof(short), - SCTypeCode.Int32 => typeof(int), - SCTypeCode.Int64 => typeof(long), - - SCTypeCode.Single => typeof(float), - SCTypeCode.Double => typeof(double), - - _ => throw new ArgumentOutOfRangeException(nameof(type), type.ToString()), - }; - - public static object GetValue(this SCTypeCode type, ReadOnlySpan data) - { - // don't use a switch expression here, we want to box our underlying type rather than the last type (double) - switch (type) - { - case SCTypeCode.Byte: return data[0]; - case SCTypeCode.UInt16: return ReadUInt16LittleEndian(data); - case SCTypeCode.UInt32: return ReadUInt32LittleEndian(data); - case SCTypeCode.UInt64: return ReadUInt64LittleEndian(data); - case SCTypeCode.SByte: return (sbyte) data[0]; - case SCTypeCode.Int16: return ReadInt16LittleEndian(data); - case SCTypeCode.Int32: return ReadInt32LittleEndian(data); - case SCTypeCode.Int64: return ReadInt64LittleEndian(data); - case SCTypeCode.Single: return ReadSingleLittleEndian(data); - case SCTypeCode.Double: return ReadDoubleLittleEndian(data); - default: - throw new ArgumentOutOfRangeException(nameof(type), type.ToString()); - } - } - - public static void SetValue(this SCTypeCode type, Span data, object value) - { - switch (type) - { - case SCTypeCode.Byte: data[0] = (byte)value; break; - case SCTypeCode.UInt16: WriteUInt16LittleEndian(data, (ushort)value); break; - case SCTypeCode.UInt32: WriteUInt32LittleEndian(data, (uint)value); break; - case SCTypeCode.UInt64: WriteUInt64LittleEndian(data, (ulong)value); break; - - case SCTypeCode.SByte: data[0] = (byte)value; break; - case SCTypeCode.Int16: WriteInt16LittleEndian(data, (short)value); break; - case SCTypeCode.Int32: WriteInt32LittleEndian(data, (int)value); break; - case SCTypeCode.Int64: WriteInt64LittleEndian(data, (long)value); break; - - case SCTypeCode.Single: WriteSingleLittleEndian(data, (float)value); break; - case SCTypeCode.Double: WriteDoubleLittleEndian(data, (double)value); break; - - default: - throw new ArgumentOutOfRangeException(nameof(type), type.ToString()); - } + default: + throw new ArgumentOutOfRangeException(nameof(type), type.ToString()); } } } diff --git a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCXorShift32.cs b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCXorShift32.cs index 8612cee48..9e39e09c6 100644 --- a/PKHeX.Core/Saves/Encryption/SwishCrypto/SCXorShift32.cs +++ b/PKHeX.Core/Saves/Encryption/SwishCrypto/SCXorShift32.cs @@ -1,81 +1,80 @@ using System.Runtime.CompilerServices; -namespace PKHeX.Core -{ - /// - /// Self-mutating value that returns a crypto value to be xor-ed with another (unaligned) byte stream. - /// - /// This implementation allows for yielding crypto bytes on demand. - /// - /// - public ref struct SCXorShift32 - { - private int Counter; - private uint Seed; +namespace PKHeX.Core; - public SCXorShift32(uint seed) - { +/// +/// Self-mutating value that returns a crypto value to be xor-ed with another (unaligned) byte stream. +/// +/// This implementation allows for yielding crypto bytes on demand. +/// +/// +public ref struct SCXorShift32 +{ + private int Counter; + private uint Seed; + + public SCXorShift32(uint seed) + { #if NET5 var pop_count = System.Numerics.BitOperations.PopCount(seed); #else - var pop_count = PopCount(seed); + var pop_count = PopCount(seed); #endif - for (var i = 0; i < pop_count; i++) - seed = XorshiftAdvance(seed); + for (var i = 0; i < pop_count; i++) + seed = XorshiftAdvance(seed); + Counter = 0; + Seed = seed; + } + + /// + /// Gets a from the current state. + /// + public uint Next() + { + var c = Counter; + var result = (Seed >> (c << 3)) & 0xFF; + if (c == 3) + { + Seed = XorshiftAdvance(Seed); Counter = 0; - Seed = seed; } - - /// - /// Gets a from the current state. - /// - public uint Next() + else { - var c = Counter; - var result = (Seed >> (c << 3)) & 0xFF; - if (c == 3) - { - Seed = XorshiftAdvance(Seed); - Counter = 0; - } - else - { - ++Counter; - } - return result; + ++Counter; } + return result; + } - /// - /// Gets a from the current state. - /// - public uint Next32() - { - return Next() | (Next() << 8) | (Next() << 16) | (Next() << 24); - } + /// + /// Gets a from the current state. + /// + public uint Next32() + { + return Next() | (Next() << 8) | (Next() << 16) | (Next() << 24); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint XorshiftAdvance(uint key) - { - key ^= key << 2; - key ^= key >> 15; - key ^= key << 13; - return key; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint XorshiftAdvance(uint key) + { + key ^= key << 2; + key ^= key >> 15; + key ^= key << 13; + return key; + } #if !NET5 - /// - /// Count of bits set in value - /// - private static uint PopCount(uint x) - { - x -= ((x >> 1) & 0x55555555u); - x = (x & 0x33333333u) + ((x >> 2) & 0x33333333u); - x = (x + (x >> 4)) & 0x0F0F0F0Fu; - x += (x >> 8); - x += (x >> 16); - return x & 0x0000003Fu; - } -#endif + /// + /// Count of bits set in value + /// + private static uint PopCount(uint x) + { + x -= ((x >> 1) & 0x55555555u); + x = (x & 0x33333333u) + ((x >> 2) & 0x33333333u); + x = (x + (x >> 4)) & 0x0F0F0F0Fu; + x += (x >> 8); + x += (x >> 16); + return x & 0x0000003Fu; } +#endif } diff --git a/PKHeX.Core/Saves/Encryption/SwishCrypto/SwishCrypto.cs b/PKHeX.Core/Saves/Encryption/SwishCrypto/SwishCrypto.cs index fd0e19676..3e4ebc443 100644 --- a/PKHeX.Core/Saves/Encryption/SwishCrypto/SwishCrypto.cs +++ b/PKHeX.Core/Saves/Encryption/SwishCrypto/SwishCrypto.cs @@ -3,65 +3,65 @@ using System.IO; using System.Security.Cryptography; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// MemeCrypto V2 - The Next Generation +/// +/// +/// A variant of encryption and obfuscation used in and . +///
Individual save blocks are stored in a hash map, with some object-type details prefixing the block's raw data.
+///
Once the raw save file data is dumped, the binary is hashed with SHA256 using a static Intro salt and static Outro salt.
+///
With the hash computed, the data is encrypted with a repeating irregular-sized static xor cipher.
+///
+public static class SwishCrypto { - /// - /// MemeCrypto V2 - The Next Generation - /// - /// - /// A variant of encryption and obfuscation used in and . - ///
Individual save blocks are stored in a hash map, with some object-type details prefixing the block's raw data.
- ///
Once the raw save file data is dumped, the binary is hashed with SHA256 using a static Intro salt and static Outro salt.
- ///
With the hash computed, the data is encrypted with a repeating irregular-sized static xor cipher.
- ///
- public static class SwishCrypto + private const int SIZE_HASH = 0x20; + + private static readonly byte[] IntroHashBytes = { - private const int SIZE_HASH = 0x20; + 0x9E, 0xC9, 0x9C, 0xD7, 0x0E, 0xD3, 0x3C, 0x44, 0xFB, 0x93, 0x03, 0xDC, 0xEB, 0x39, 0xB4, 0x2A, + 0x19, 0x47, 0xE9, 0x63, 0x4B, 0xA2, 0x33, 0x44, 0x16, 0xBF, 0x82, 0xA2, 0xBA, 0x63, 0x55, 0xB6, + 0x3D, 0x9D, 0xF2, 0x4B, 0x5F, 0x7B, 0x6A, 0xB2, 0x62, 0x1D, 0xC2, 0x1B, 0x68, 0xE5, 0xC8, 0xB5, + 0x3A, 0x05, 0x90, 0x00, 0xE8, 0xA8, 0x10, 0x3D, 0xE2, 0xEC, 0xF0, 0x0C, 0xB2, 0xED, 0x4F, 0x6D, + }; - private static readonly byte[] IntroHashBytes = - { - 0x9E, 0xC9, 0x9C, 0xD7, 0x0E, 0xD3, 0x3C, 0x44, 0xFB, 0x93, 0x03, 0xDC, 0xEB, 0x39, 0xB4, 0x2A, - 0x19, 0x47, 0xE9, 0x63, 0x4B, 0xA2, 0x33, 0x44, 0x16, 0xBF, 0x82, 0xA2, 0xBA, 0x63, 0x55, 0xB6, - 0x3D, 0x9D, 0xF2, 0x4B, 0x5F, 0x7B, 0x6A, 0xB2, 0x62, 0x1D, 0xC2, 0x1B, 0x68, 0xE5, 0xC8, 0xB5, - 0x3A, 0x05, 0x90, 0x00, 0xE8, 0xA8, 0x10, 0x3D, 0xE2, 0xEC, 0xF0, 0x0C, 0xB2, 0xED, 0x4F, 0x6D, - }; + private static readonly byte[] OutroHashBytes = + { + 0xD6, 0xC0, 0x1C, 0x59, 0x8B, 0xC8, 0xB8, 0xCB, 0x46, 0xE1, 0x53, 0xFC, 0x82, 0x8C, 0x75, 0x75, + 0x13, 0xE0, 0x45, 0xDF, 0x32, 0x69, 0x3C, 0x75, 0xF0, 0x59, 0xF8, 0xD9, 0xA2, 0x5F, 0xB2, 0x17, + 0xE0, 0x80, 0x52, 0xDB, 0xEA, 0x89, 0x73, 0x99, 0x75, 0x79, 0xAF, 0xCB, 0x2E, 0x80, 0x07, 0xE6, + 0xF1, 0x26, 0xE0, 0x03, 0x0A, 0xE6, 0x6F, 0xF6, 0x41, 0xBF, 0x7E, 0x59, 0xC2, 0xAE, 0x55, 0xFD, + }; - private static readonly byte[] OutroHashBytes = - { - 0xD6, 0xC0, 0x1C, 0x59, 0x8B, 0xC8, 0xB8, 0xCB, 0x46, 0xE1, 0x53, 0xFC, 0x82, 0x8C, 0x75, 0x75, - 0x13, 0xE0, 0x45, 0xDF, 0x32, 0x69, 0x3C, 0x75, 0xF0, 0x59, 0xF8, 0xD9, 0xA2, 0x5F, 0xB2, 0x17, - 0xE0, 0x80, 0x52, 0xDB, 0xEA, 0x89, 0x73, 0x99, 0x75, 0x79, 0xAF, 0xCB, 0x2E, 0x80, 0x07, 0xE6, - 0xF1, 0x26, 0xE0, 0x03, 0x0A, 0xE6, 0x6F, 0xF6, 0x41, 0xBF, 0x7E, 0x59, 0xC2, 0xAE, 0x55, 0xFD, - }; + private static readonly byte[] StaticXorpad = + { + 0xA0, 0x92, 0xD1, 0x06, 0x07, 0xDB, 0x32, 0xA1, 0xAE, 0x01, 0xF5, 0xC5, 0x1E, 0x84, 0x4F, 0xE3, + 0x53, 0xCA, 0x37, 0xF4, 0xA7, 0xB0, 0x4D, 0xA0, 0x18, 0xB7, 0xC2, 0x97, 0xDA, 0x5F, 0x53, 0x2B, + 0x75, 0xFA, 0x48, 0x16, 0xF8, 0xD4, 0x8A, 0x6F, 0x61, 0x05, 0xF4, 0xE2, 0xFD, 0x04, 0xB5, 0xA3, + 0x0F, 0xFC, 0x44, 0x92, 0xCB, 0x32, 0xE6, 0x1B, 0xB9, 0xB1, 0x2E, 0x01, 0xB0, 0x56, 0x53, 0x36, + 0xD2, 0xD1, 0x50, 0x3D, 0xDE, 0x5B, 0x2E, 0x0E, 0x52, 0xFD, 0xDF, 0x2F, 0x7B, 0xCA, 0x63, 0x50, + 0xA4, 0x67, 0x5D, 0x23, 0x17, 0xC0, 0x52, 0xE1, 0xA6, 0x30, 0x7C, 0x2B, 0xB6, 0x70, 0x36, 0x5B, + 0x2A, 0x27, 0x69, 0x33, 0xF5, 0x63, 0x7B, 0x36, 0x3F, 0x26, 0x9B, 0xA3, 0xED, 0x7A, 0x53, 0x00, + 0xA4, 0x48, 0xB3, 0x50, 0x9E, 0x14, 0xA0, 0x52, 0xDE, 0x7E, 0x10, 0x2B, 0x1B, 0x77, 0x6E, + }; - private static readonly byte[] StaticXorpad = - { - 0xA0, 0x92, 0xD1, 0x06, 0x07, 0xDB, 0x32, 0xA1, 0xAE, 0x01, 0xF5, 0xC5, 0x1E, 0x84, 0x4F, 0xE3, - 0x53, 0xCA, 0x37, 0xF4, 0xA7, 0xB0, 0x4D, 0xA0, 0x18, 0xB7, 0xC2, 0x97, 0xDA, 0x5F, 0x53, 0x2B, - 0x75, 0xFA, 0x48, 0x16, 0xF8, 0xD4, 0x8A, 0x6F, 0x61, 0x05, 0xF4, 0xE2, 0xFD, 0x04, 0xB5, 0xA3, - 0x0F, 0xFC, 0x44, 0x92, 0xCB, 0x32, 0xE6, 0x1B, 0xB9, 0xB1, 0x2E, 0x01, 0xB0, 0x56, 0x53, 0x36, - 0xD2, 0xD1, 0x50, 0x3D, 0xDE, 0x5B, 0x2E, 0x0E, 0x52, 0xFD, 0xDF, 0x2F, 0x7B, 0xCA, 0x63, 0x50, - 0xA4, 0x67, 0x5D, 0x23, 0x17, 0xC0, 0x52, 0xE1, 0xA6, 0x30, 0x7C, 0x2B, 0xB6, 0x70, 0x36, 0x5B, - 0x2A, 0x27, 0x69, 0x33, 0xF5, 0x63, 0x7B, 0x36, 0x3F, 0x26, 0x9B, 0xA3, 0xED, 0x7A, 0x53, 0x00, - 0xA4, 0x48, 0xB3, 0x50, 0x9E, 0x14, 0xA0, 0x52, 0xDE, 0x7E, 0x10, 0x2B, 0x1B, 0x77, 0x6E, - }; + public static void CryptStaticXorpadBytes(Span data) + { + var xp = StaticXorpad; + var region = data[..^SIZE_HASH]; + for (var i = 0; i < region.Length; i++) + region[i] ^= xp[i % xp.Length]; + } - public static void CryptStaticXorpadBytes(Span data) - { - var xp = StaticXorpad; - var region = data[..^SIZE_HASH]; - for (var i = 0; i < region.Length; i++) - region[i] ^= xp[i % xp.Length]; - } - - private static byte[] ComputeHash(byte[] data) - { + private static byte[] ComputeHash(byte[] data) + { #if !NET46 - using var h = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); - h.AppendData(IntroHashBytes); - h.AppendData(data, 0, data.Length - SIZE_HASH); - h.AppendData(OutroHashBytes); - return h.GetHashAndReset(); + using var h = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); + h.AppendData(IntroHashBytes); + h.AppendData(data, 0, data.Length - SIZE_HASH); + h.AppendData(OutroHashBytes); + return h.GetHashAndReset(); #else var intro = IntroHashBytes; var outro = OutroHashBytes; @@ -73,82 +73,81 @@ private static byte[] ComputeHash(byte[] data) using var sha = SHA256.Create(); return sha.ComputeHash(stream); #endif - } + } - /// - /// Checks if the file is a rough example of a save file. - /// - /// Encrypted save data - /// True if hash matches - public static bool GetIsHashValid(byte[] data) + /// + /// Checks if the file is a rough example of a save file. + /// + /// Encrypted save data + /// True if hash matches + public static bool GetIsHashValid(byte[] data) + { + var hash = ComputeHash(data); + var span = data.AsSpan()[^hash.Length..]; + return span.SequenceEqual(hash); + } + + /// + /// Decrypts the save data in-place, then unpacks the blocks. + /// + /// Encrypted save data + /// Decrypted blocks. + /// + /// Hash is assumed to be valid before calling this method. + /// + public static IReadOnlyList Decrypt(Span data) + { + CryptStaticXorpadBytes(data); + return ReadBlocks(data); + } + + private const int BlockDataRatioEstimate1 = 777; // bytes per block, on average (generous) + private const int BlockDataRatioEstimate2 = 555; // bytes per block, on average (stingy) + + private static IReadOnlyList ReadBlocks(ReadOnlySpan data) + { + var result = new List(data.Length / BlockDataRatioEstimate2); + int offset = 0; + while (offset < data.Length - SIZE_HASH) { - var hash = ComputeHash(data); - var span = data.AsSpan()[^hash.Length..]; - return span.SequenceEqual(hash); + var block = SCBlock.ReadFromOffset(data, ref offset); + result.Add(block); } - /// - /// Decrypts the save data in-place, then unpacks the blocks. - /// - /// Encrypted save data - /// Decrypted blocks. - /// - /// Hash is assumed to be valid before calling this method. - /// - public static IReadOnlyList Decrypt(Span data) - { - CryptStaticXorpadBytes(data); - return ReadBlocks(data); - } + return result; + } - private const int BlockDataRatioEstimate1 = 777; // bytes per block, on average (generous) - private const int BlockDataRatioEstimate2 = 555; // bytes per block, on average (stingy) + /// + /// Tries to encrypt the save data. + /// + /// Decrypted save data + /// Encrypted save data. + public static byte[] Encrypt(IReadOnlyList blocks) + { + var result = GetDecryptedRawData(blocks); + CryptStaticXorpadBytes(result); - private static IReadOnlyList ReadBlocks(ReadOnlySpan data) - { - var result = new List(data.Length / BlockDataRatioEstimate2); - int offset = 0; - while (offset < data.Length - SIZE_HASH) - { - var block = SCBlock.ReadFromOffset(data, ref offset); - result.Add(block); - } + var hash = ComputeHash(result); + hash.CopyTo(result, result.Length - SIZE_HASH); - return result; - } + return result; + } - /// - /// Tries to encrypt the save data. - /// - /// Decrypted save data - /// Encrypted save data. - public static byte[] Encrypt(IReadOnlyList blocks) - { - var result = GetDecryptedRawData(blocks); - CryptStaticXorpadBytes(result); + /// + /// Tries to encrypt the save data. + /// + /// Raw save data without the final xorpad layer. + public static byte[] GetDecryptedRawData(IReadOnlyList blocks) + { + using var ms = new MemoryStream(blocks.Count * BlockDataRatioEstimate1); + using var bw = new BinaryWriter(ms); + foreach (var block in blocks) + block.WriteBlock(bw); - var hash = ComputeHash(result); - hash.CopyTo(result, result.Length - SIZE_HASH); + // Allocate hash bytes at the end + for (int i = 0; i < SIZE_HASH; i++) + bw.Write((byte)0); - return result; - } - - /// - /// Tries to encrypt the save data. - /// - /// Raw save data without the final xorpad layer. - public static byte[] GetDecryptedRawData(IReadOnlyList blocks) - { - using var ms = new MemoryStream(blocks.Count * BlockDataRatioEstimate1); - using var bw = new BinaryWriter(ms); - foreach (var block in blocks) - block.WriteBlock(bw); - - // Allocate hash bytes at the end - for (int i = 0; i < SIZE_HASH; i++) - bw.Write((byte)0); - - return ms.ToArray(); - } + return ms.ToArray(); } } diff --git a/PKHeX.Core/Saves/SAV1.cs b/PKHeX.Core/Saves/SAV1.cs index afeb9b049..6e2ee0886 100644 --- a/PKHeX.Core/Saves/SAV1.cs +++ b/PKHeX.Core/Saves/SAV1.cs @@ -2,581 +2,580 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 1 object. +/// +public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray { - /// - /// Generation 1 object. - /// - public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray + protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}"; + public override string Extension => ".sav"; + public bool IsVirtualConsole => State.Exportable && Metadata.FileName is { } s && s.StartsWith("sav", StringComparison.Ordinal) && s.Contains(".dat"); // default to GB-Era for non-exportable + + public int SaveRevision => Japanese ? 0 : 1; + public string SaveRevisionString => (Japanese ? "J" : "U") + (IsVirtualConsole ? "VC" : "GB"); + public bool Japanese { get; } + public bool Korean => false; + + public override PersonalTable Personal { get; } + public override IReadOnlyList HeldItems => Array.Empty(); + + public override IReadOnlyList PKMExtensions => Array.FindAll(PKM.Extensions, f => { - protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}"; - public override string Extension => ".sav"; - public bool IsVirtualConsole => State.Exportable && Metadata.FileName is { } s && s.StartsWith("sav") && s.Contains(".dat"); // default to GB-Era for non-exportable + int gen = f[^1] - 0x30; + return gen is 1 or 2; + }); - public int SaveRevision => Japanese ? 0 : 1; - public string SaveRevisionString => (Japanese ? "J" : "U") + (IsVirtualConsole ? "VC" : "GB"); - public bool Japanese { get; } - public bool Korean => false; + public SAV1(GameVersion version = GameVersion.RBY, bool japanese = false) : base(SaveUtil.SIZE_G1RAW) + { + Version = version; + Japanese = japanese; + Offsets = Japanese ? SAV1Offsets.JPN : SAV1Offsets.INT; + Personal = version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; + Initialize(version); + ClearBoxes(); + } - public override PersonalTable Personal { get; } - public override IReadOnlyList HeldItems => Array.Empty(); + public SAV1(byte[] data, GameVersion versionOverride = GameVersion.Any) : base(data) + { + Japanese = SaveUtil.GetIsG1SAVJ(Data); + Offsets = Japanese ? SAV1Offsets.JPN : SAV1Offsets.INT; - public override IReadOnlyList PKMExtensions => Array.FindAll(PKM.Extensions, f => + Version = versionOverride != GameVersion.Any ? versionOverride : SaveUtil.GetIsG1SAV(data); + Personal = Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; + if (Version == GameVersion.Invalid) + return; + + Initialize(versionOverride); + } + + private void Initialize(GameVersion versionOverride) + { + // see if RBY can be differentiated + if (Starter != 0 && versionOverride is not (GameVersion.RB or GameVersion.YW)) + Version = Yellow ? GameVersion.YW : GameVersion.RB; + + Box = Data.Length; + Array.Resize(ref Data, Data.Length + SIZE_RESERVED); + Party = GetPartyOffset(0); + + // Stash boxes after the save file's end. + int stored = SIZE_STOREDBOX; + var capacity = Japanese ? PokeListType.StoredJP : PokeListType.Stored; + int baseDest = Data.Length - SIZE_RESERVED; + for (int box = 0; box < BoxCount; box++) { - int gen = f[^1] - 0x30; - return gen is 1 or 2; - }); - - public SAV1(GameVersion version = GameVersion.RBY, bool japanese = false) : base(SaveUtil.SIZE_G1RAW) - { - Version = version; - Japanese = japanese; - Offsets = Japanese ? SAV1Offsets.JPN : SAV1Offsets.INT; - Personal = version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; - Initialize(version); - ClearBoxes(); + int boxOfs = GetBoxRawDataOffset(box); + UnpackBox(boxOfs, baseDest, stored, box, capacity); } - public SAV1(byte[] data, GameVersion versionOverride = GameVersion.Any) : base(data) + if ((uint)CurrentBox < BoxCount) { - Japanese = SaveUtil.GetIsG1SAVJ(Data); - Offsets = Japanese ? SAV1Offsets.JPN : SAV1Offsets.INT; - - Version = versionOverride != GameVersion.Any ? versionOverride : SaveUtil.GetIsG1SAV(data); - Personal = Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB; - if (Version == GameVersion.Invalid) - return; - - Initialize(versionOverride); + // overwrite previously cached box data. + UnpackBox(Offsets.CurrentBox, baseDest, stored, CurrentBox, capacity); } - private void Initialize(GameVersion versionOverride) + var party = GetData(Offsets.Party, SIZE_STOREDPARTY); + var partyPL = new PokeList1(party, PokeListType.Party, Japanese); + for (int i = 0; i < partyPL.Pokemon.Length; i++) { - // see if RBY can be differentiated - if (Starter != 0 && versionOverride is not (GameVersion.RB or GameVersion.YW)) - Version = Yellow ? GameVersion.YW : GameVersion.RB; - - Box = Data.Length; - Array.Resize(ref Data, Data.Length + SIZE_RESERVED); - Party = GetPartyOffset(0); - - // Stash boxes after the save file's end. - int stored = SIZE_STOREDBOX; - var capacity = Japanese ? PokeListType.StoredJP : PokeListType.Stored; - int baseDest = Data.Length - SIZE_RESERVED; - for (int box = 0; box < BoxCount; box++) - { - int boxOfs = GetBoxRawDataOffset(box); - UnpackBox(boxOfs, baseDest, stored, box, capacity); - } - - if ((uint)CurrentBox < BoxCount) - { - // overwrite previously cached box data. - UnpackBox(Offsets.CurrentBox, baseDest, stored, CurrentBox, capacity); - } - - var party = GetData(Offsets.Party, SIZE_STOREDPARTY); - var partyPL = new PokeList1(party, PokeListType.Party, Japanese); - for (int i = 0; i < partyPL.Pokemon.Length; i++) - { - var dest = GetPartyOffset(i); - var pkDat = i < partyPL.Count - ? new PokeList1(partyPL[i]).Write() - : new byte[PokeList1.GetDataLength(PokeListType.Single, Japanese)]; - pkDat.CopyTo(Data, dest); - } - - Span rawDC = stackalloc byte[0x38]; - Data.AsSpan(Offsets.Daycare, rawDC.Length).CopyTo(rawDC); - byte[] TempDaycare = new byte[PokeList1.GetDataLength(PokeListType.Single, Japanese)]; - TempDaycare[0] = rawDC[0]; - - rawDC.Slice(1, StringLength).CopyTo(TempDaycare.AsSpan(2 + 1 + PokeCrypto.SIZE_1PARTY + StringLength)); - rawDC.Slice(1 + StringLength, StringLength).CopyTo(TempDaycare.AsSpan(2 + 1 + PokeCrypto.SIZE_1PARTY)); - rawDC.Slice(1 + (2 * StringLength), PokeCrypto.SIZE_1STORED).CopyTo(TempDaycare.AsSpan(2 + 1)); - - PokeList1 daycareList = new(TempDaycare, PokeListType.Single, Japanese); - daycareList.Write().CopyTo(Data, GetPartyOffset(7)); - DaycareOffset = GetPartyOffset(7); - - // Enable Pokedex editing - PokeDex = 0; + var dest = GetPartyOffset(i); + var pkDat = i < partyPL.Count + ? new PokeList1(partyPL[i]).Write() + : new byte[PokeList1.GetDataLength(PokeListType.Single, Japanese)]; + pkDat.CopyTo(Data, dest); } - private void UnpackBox(int srcOfs, int destOfs, int boxSize, int boxIndex, PokeListType boxCapacity) + Span rawDC = stackalloc byte[0x38]; + Data.AsSpan(Offsets.Daycare, rawDC.Length).CopyTo(rawDC); + byte[] TempDaycare = new byte[PokeList1.GetDataLength(PokeListType.Single, Japanese)]; + TempDaycare[0] = rawDC[0]; + + rawDC.Slice(1, StringLength).CopyTo(TempDaycare.AsSpan(2 + 1 + PokeCrypto.SIZE_1PARTY + StringLength)); + rawDC.Slice(1 + StringLength, StringLength).CopyTo(TempDaycare.AsSpan(2 + 1 + PokeCrypto.SIZE_1PARTY)); + rawDC.Slice(1 + (2 * StringLength), PokeCrypto.SIZE_1STORED).CopyTo(TempDaycare.AsSpan(2 + 1)); + + PokeList1 daycareList = new(TempDaycare, PokeListType.Single, Japanese); + daycareList.Write().CopyTo(Data, GetPartyOffset(7)); + DaycareOffset = GetPartyOffset(7); + + // Enable Pokedex editing + PokeDex = 0; + } + + private void UnpackBox(int srcOfs, int destOfs, int boxSize, int boxIndex, PokeListType boxCapacity) + { + var boxData = GetData(srcOfs, boxSize); + var boxDest = destOfs + (boxIndex * SIZE_BOX); + var boxPL = new PokeList1(boxData, boxCapacity, Japanese); + for (int i = 0; i < boxPL.Pokemon.Length; i++) { - var boxData = GetData(srcOfs, boxSize); - var boxDest = destOfs + (boxIndex * SIZE_BOX); - var boxPL = new PokeList1(boxData, boxCapacity, Japanese); - for (int i = 0; i < boxPL.Pokemon.Length; i++) - { - var slotOfs = boxDest + (i * SIZE_STORED); - var slotData = Data.AsSpan(slotOfs, SIZE_STORED); - if (i < boxPL.Count) - new PokeList1(boxPL[i]).Write().CopyTo(slotData); - else - slotData.Clear(); - } - } - - private void PackBox(int boxDest, int boxIndex, PokeListType boxCapacity) - { - var boxPL = new PokeList1(boxCapacity, Japanese); - int slot = 0; - for (int i = 0; i < boxPL.Pokemon.Length; i++) - { - var slotOfs = boxDest + (i * SIZE_STORED); - var slotData = Data.AsSpan(slotOfs, SIZE_STORED); - PK1 pk = (PK1)GetPKM(slotData.ToArray()); - if (pk.Species > 0) - boxPL[slot++] = pk; - } - - // copy to box location - var boxData = boxPL.Write(); - int boxSrc = GetBoxRawDataOffset(boxIndex); - SetData(Data, boxData, boxSrc); - - // copy to active loc if current box - if (boxIndex == CurrentBox) - SetData(Data, boxData, Offsets.CurrentBox); - } - - private const int SIZE_RESERVED = 0x8000; // unpacked box data - private readonly SAV1Offsets Offsets; - - // Event Flags - public int EventFlagCount => 0xA00; // 320 * 8 - public bool GetEventFlag(int flagNumber) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); - return GetFlag(Offsets.EventFlag + (flagNumber >> 3), flagNumber & 7); - } - - public void SetEventFlag(int flagNumber, bool value) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); - SetFlag(Offsets.EventFlag + (flagNumber >> 3), flagNumber & 7, value); - } - - protected override byte[] GetFinalData() - { - var capacity = Japanese ? PokeListType.StoredJP : PokeListType.Stored; - for (int box = 0; box < BoxCount; box++) - { - var boxOfs = GetBoxOffset(box); - PackBox(boxOfs, box, capacity); - } - - var partyPL = new PokeList1(PokeListType.Party, Japanese); - int pSlot = 0; - for (int i = 0; i < 6; i++) - { - PK1 partyPK = (PK1)GetPKM(GetData(GetPartyOffset(i), SIZE_STORED)); - if (partyPK.Species > 0) - partyPL[pSlot++] = partyPK; - } - partyPL.Write().CopyTo(Data, Offsets.Party); - - // Daycare is read-only, but in case it ever becomes editable, copy it back in. - Span rawDC = Data.AsSpan(GetDaycareSlotOffset(loc: 0, slot: 0), SIZE_STORED); - Span dc = stackalloc byte[1 + (2 * StringLength) + PokeCrypto.SIZE_1STORED]; - dc[0] = IsDaycareOccupied(0, 0) == true ? (byte)1 : (byte)0; - rawDC.Slice(2 + 1 + PokeCrypto.SIZE_1PARTY + StringLength, StringLength).CopyTo(dc[1..]); - rawDC.Slice(2 + 1 + PokeCrypto.SIZE_1PARTY, StringLength).CopyTo(dc[(1 + StringLength)..]); - rawDC.Slice(2 + 1, PokeCrypto.SIZE_1STORED).CopyTo(dc[(1 + (2 * StringLength))..]); - dc.CopyTo(Data.AsSpan(Offsets.Daycare)); - - SetChecksums(); - return Data.AsSpan()[..^SIZE_RESERVED].ToArray(); - } - - private int GetBoxRawDataOffset(int box) - { - if (box < BoxCount / 2) - return 0x4000 + (box * SIZE_STOREDBOX); - return 0x6000 + ((box - (BoxCount / 2)) * SIZE_STOREDBOX); - } - - // Configuration - protected override SaveFile CloneInternal() => new SAV1(Write(), Version); - - protected override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST; - protected override int SIZE_PARTY => Japanese ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST; - private int SIZE_BOX => BoxSlotCount*SIZE_STORED; - private int SIZE_STOREDBOX => PokeList1.GetDataLength(Japanese ? PokeListType.StoredJP : PokeListType.Stored, Japanese); - private int SIZE_STOREDPARTY => PokeList1.GetDataLength(PokeListType.Party, Japanese); - - public override PKM BlankPKM => new PK1(Japanese); - public override Type PKMType => typeof(PK1); - - public override int MaxMoveID => Legal.MaxMoveID_1; - public override int MaxSpeciesID => Legal.MaxSpeciesID_1; - public override int MaxAbilityID => Legal.MaxAbilityID_1; - public override int MaxItemID => Legal.MaxItemID_1; - public override int MaxBallID => 0; // unused - public override int MaxGameID => 99; // unused - public override int MaxMoney => 999999; - public override int MaxCoins => 9999; - - public override int BoxCount => Japanese ? 8 : 12; - public override int MaxEV => 65535; - public override int MaxIV => 15; - public override int Generation => 1; - public override EntityContext Context => EntityContext.Gen1; - protected override int GiftCountMax => 0; - public override int OTLength => Japanese ? 5 : 7; - public override int NickLength => Japanese ? 5 : 10; - public override int BoxSlotCount => Japanese ? 30 : 20; - - public override bool HasParty => true; - private int StringLength => Japanese ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan; - - public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGB(data); - - // Checksums - protected override void SetChecksums() => Data[Offsets.ChecksumOfs] = GetRBYChecksum(Offsets.OT, Offsets.ChecksumOfs); - public override bool ChecksumsValid => Data[Offsets.ChecksumOfs] == GetRBYChecksum(Offsets.OT, Offsets.ChecksumOfs); - public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid"; - - private byte GetRBYChecksum(int start, int end) - { - byte chksum = 0; - for (int i = start; i < end; i++) - chksum += Data[i]; - chksum ^= 0xFF; - return chksum; - } - - // Trainer Info - public override GameVersion Version { get; protected set; } - - public override string OT - { - get => GetString(Offsets.OT, OTLength); - set => SetString(Data.AsSpan(Offsets.OT, OTLength), value.AsSpan(), OTLength, StringConverterOption.Clear50); - } - - public Span OT_Trash { get => Data.AsSpan(Offsets.OT, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.OT)); } } - - public override int Gender - { - get => 0; - set { } - } - - public override int TID - { - get => ReadUInt16BigEndian(Data.AsSpan(Offsets.TID)); - set => WriteUInt16BigEndian(Data.AsSpan(Offsets.TID), (ushort)value); - } - - public override int SID { get => 0; set { } } - - public string Rival - { - get => GetString(Offsets.Rival, OTLength); - set => SetString(Data.AsSpan(Offsets.Rival, OTLength), value.AsSpan(), OTLength, StringConverterOption.Clear50); - } - - public Span Rival_Trash { get => Data.AsSpan(Offsets.Rival, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Rival)); } } - - public bool Yellow => Starter == 0x54; // Pikachu - public int Starter => Data[Offsets.Starter]; - - public byte PikaFriendship - { - get => Data[Offsets.PikaFriendship]; - set => Data[Offsets.PikaFriendship] = value; - } - - public int PikaBeachScore - { - get => BinaryCodedDecimal.ToInt32LE(Data.AsSpan(Offsets.PikaBeachScore, 2)); - set => BinaryCodedDecimal.WriteBytesLE(Data.AsSpan(Offsets.PikaBeachScore, 2), Math.Min(9999, value)); - } - - public override string PlayTimeString => !PlayedMaximum ? base.PlayTimeString : $"{base.PlayTimeString} {Checksums.CRC16_CCITT(Data):X4}"; - - public override int PlayedHours - { - get => Data[Offsets.PlayTime + 0]; - set - { - if (value >= byte.MaxValue) // Set 255:00:00.00 and flag - { - PlayedMaximum = true; - value = byte.MaxValue; - PlayedMinutes = PlayedSeconds = PlayedFrames = 0; - } - Data[Offsets.PlayTime + 0] = (byte) value; - } - } - - public bool PlayedMaximum - { - get => Data[Offsets.PlayTime + 1] != 0; - set => Data[Offsets.PlayTime + 1] = value ? (byte)1 : (byte)0; - } - - public override int PlayedMinutes - { - get => Data[Offsets.PlayTime + 2]; - set => Data[Offsets.PlayTime + 2] = (byte)value; - } - - public override int PlayedSeconds - { - get => Data[Offsets.PlayTime + 3]; - set => Data[Offsets.PlayTime + 3] = (byte)value; - } - - public int PlayedFrames - { - get => Data[Offsets.PlayTime + 4]; - set => Data[Offsets.PlayTime + 4] = (byte)value; - } - - public int Badges - { - get => Data[Offsets.Badges]; - set { if (value < 0) return; Data[Offsets.Badges] = (byte)value; } - } - - private byte Options - { - get => Data[Offsets.Options]; - set => Data[Offsets.Options] = value; - } - - public bool BattleEffects - { - get => (Options & 0x80) == 0; - set => Options = (byte)((Options & 0x7F) | (value ? 0 : 0x80)); - } - - public bool BattleStyleSwitch - { - get => (Options & 0x40) == 0; - set => Options = (byte)((Options & 0xBF) | (value ? 0 : 0x40)); - } - - public int Sound - { - get => (Options & 0x30) >> 4; - set => Options = (byte)((Options & 0xCF) | ((value & 3) << 4)); - } - - public int TextSpeed - { - get => Options & 0x7; - set => Options = (byte)((Options & 0xF8) | (value & 7)); - } - - // yellow only - public byte GBPrinterBrightness { get => Data[Offsets.PrinterBrightness]; set => Data[Offsets.PrinterBrightness] = value; } - - public override uint Money - { - get => (uint)BinaryCodedDecimal.ToInt32BE(Data.AsSpan(Offsets.Money, 3)); - set - { - value = (uint)Math.Min(value, MaxMoney); - BinaryCodedDecimal.WriteBytesBE(Data.AsSpan(Offsets.Money, 3), (int)value); - } - } - - public uint Coin - { - get => (uint)BinaryCodedDecimal.ToInt32BE(Data.AsSpan(Offsets.Coin, 2)); - set - { - value = (ushort)Math.Min(value, MaxCoins); - BinaryCodedDecimal.WriteBytesBE(Data.AsSpan(Offsets.Coin, 2), (int)value); - } - } - - private readonly ushort[] LegalItems = Legal.Pouch_Items_RBY; - - public override IReadOnlyList Inventory - { - get - { - ushort[] legalItems = LegalItems; - InventoryPouch[] pouch = - { - new InventoryPouchGB(InventoryType.Items, legalItems, 99, Offsets.Items, 20), - new InventoryPouchGB(InventoryType.PCItems, legalItems, 99, Offsets.PCItems, 50), - }; - return pouch.LoadAll(Data); - } - set => value.SaveAll(Data); - } - - public override int GetDaycareSlotOffset(int loc, int slot) - { - return DaycareOffset; - } - - public override uint? GetDaycareEXP(int loc, int slot) - { - return null; - } - - public override bool? IsDaycareOccupied(int loc, int slot) - { - if (loc == 0 && slot == 0) - return Data[Offsets.Daycare] == 0x01; - return null; - } - - public override void SetDaycareEXP(int loc, int slot, uint EXP) - { - // todo - } - - public override void SetDaycareOccupied(int loc, int slot, bool occupied) - { - // todo - } - - // Storage - public override int PartyCount - { - get => Data[Offsets.Party]; - protected set => Data[Offsets.Party] = (byte)value; - } - - public override int GetBoxOffset(int box) - { - return Data.Length - SIZE_RESERVED + (box * SIZE_BOX); - } - - public override int GetPartyOffset(int slot) - { - return Data.Length - SIZE_RESERVED + (BoxCount * SIZE_BOX) + (slot * SIZE_STORED); - } - - public override int CurrentBox - { - get => Data[Offsets.CurrentBoxIndex] & 0x7F; - set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x80) | (value & 0x7F)); - } - - public bool CurrentBoxChanged - { - get => (Data[Offsets.CurrentBoxIndex] & 0x80) != 0; - set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x7F) | (byte)(value ? 0x80 : 0)); - } - - public override string GetBoxName(int box) - { - return $"BOX {box + 1}"; - } - - public override void SetBoxName(int box, string value) - { - // Don't allow for custom box names - } - - protected override PKM GetPKM(byte[] data) - { - if (data.Length == SIZE_STORED) - return new PokeList1(data, PokeListType.Single, Japanese)[0]; - return new PK1(data); - } - - protected override byte[] DecryptPKM(byte[] data) - { - return data; - } - - // Pokédex - protected override void SetDex(PKM pkm) - { - int species = pkm.Species; - if (!CanSetDex(species)) - return; - - SetCaught(pkm.Species, true); - SetSeen(pkm.Species, true); - } - - private bool CanSetDex(int species) - { - if (species <= 0) - return false; - if (species > MaxSpeciesID) - return false; - if (Version == GameVersion.Invalid) - return false; - return true; - } - - public override bool GetSeen(int species) => GetDexFlag(Offsets.DexSeen, species); - public override bool GetCaught(int species) => GetDexFlag(Offsets.DexCaught, species); - public override void SetSeen(int species, bool seen) => SetDexFlag(Offsets.DexSeen, species, seen); - public override void SetCaught(int species, bool caught) => SetDexFlag(Offsets.DexCaught, species, caught); - - private bool GetDexFlag(int region, int species) - { - int bit = species - 1; - int ofs = bit >> 3; - return GetFlag(region + ofs, bit & 7); - } - - private void SetDexFlag(int region, int species, bool value) - { - int bit = species - 1; - int ofs = bit >> 3; - SetFlag(region + ofs, bit & 7, value); - } - - public override void WriteSlotFormatStored(PKM pkm, Span data, int offset) - { - // pkm that have never been boxed have yet to save the 'current level' for box indication - // set this value at this time - ((PK1)pkm).Stat_LevelBox = pkm.CurrentLevel; - base.WriteSlotFormatStored(pkm, Data, offset); - } - - public override void WriteBoxSlot(PKM pkm, Span data, int offset) - { - // pkm that have never been boxed have yet to save the 'current level' for box indication - // set this value at this time - ((PK1)pkm).Stat_LevelBox = pkm.CurrentLevel; - base.WriteBoxSlot(pkm, Data, offset); - } - - private const int SpawnFlagCount = 0xF0; - - public bool[] EventSpawnFlags - { - get - { - // RB uses 0xE4 (0xE8) flags, Yellow uses 0xF0 flags. Just grab 0xF0 - bool[] data = new bool[SpawnFlagCount]; - for (int i = 0; i < data.Length; i++) - data[i] = GetFlag(Offsets.ObjectSpawnFlags + (i >> 3), i & 7); - return data; - } - set - { - if (value.Length != SpawnFlagCount) - return; - for (int i = 0; i < value.Length; i++) - SetFlag(Offsets.ObjectSpawnFlags + (i >> 3), i & 7, value[i]); - } - } - - public override string GetString(ReadOnlySpan data) => StringConverter12.GetString(data, Japanese); - - public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter12.SetString(destBuffer, value, maxLength, Japanese, option); + var slotOfs = boxDest + (i * SIZE_STORED); + var slotData = Data.AsSpan(slotOfs, SIZE_STORED); + if (i < boxPL.Count) + new PokeList1(boxPL[i]).Write().CopyTo(slotData); + else + slotData.Clear(); } } + + private void PackBox(int boxDest, int boxIndex, PokeListType boxCapacity) + { + var boxPL = new PokeList1(boxCapacity, Japanese); + int slot = 0; + for (int i = 0; i < boxPL.Pokemon.Length; i++) + { + var slotOfs = boxDest + (i * SIZE_STORED); + var slotData = Data.AsSpan(slotOfs, SIZE_STORED); + PK1 pk = (PK1)GetPKM(slotData.ToArray()); + if (pk.Species > 0) + boxPL[slot++] = pk; + } + + // copy to box location + var boxData = boxPL.Write(); + int boxSrc = GetBoxRawDataOffset(boxIndex); + SetData(Data, boxData, boxSrc); + + // copy to active loc if current box + if (boxIndex == CurrentBox) + SetData(Data, boxData, Offsets.CurrentBox); + } + + private const int SIZE_RESERVED = 0x8000; // unpacked box data + private readonly SAV1Offsets Offsets; + + // Event Flags + public int EventFlagCount => 0xA00; // 320 * 8 + public bool GetEventFlag(int flagNumber) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); + return GetFlag(Offsets.EventFlag + (flagNumber >> 3), flagNumber & 7); + } + + public void SetEventFlag(int flagNumber, bool value) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); + SetFlag(Offsets.EventFlag + (flagNumber >> 3), flagNumber & 7, value); + } + + protected override byte[] GetFinalData() + { + var capacity = Japanese ? PokeListType.StoredJP : PokeListType.Stored; + for (int box = 0; box < BoxCount; box++) + { + var boxOfs = GetBoxOffset(box); + PackBox(boxOfs, box, capacity); + } + + var partyPL = new PokeList1(PokeListType.Party, Japanese); + int pSlot = 0; + for (int i = 0; i < 6; i++) + { + PK1 partyPK = (PK1)GetPKM(GetData(GetPartyOffset(i), SIZE_STORED)); + if (partyPK.Species > 0) + partyPL[pSlot++] = partyPK; + } + partyPL.Write().CopyTo(Data, Offsets.Party); + + // Daycare is read-only, but in case it ever becomes editable, copy it back in. + Span rawDC = Data.AsSpan(GetDaycareSlotOffset(loc: 0, slot: 0), SIZE_STORED); + Span dc = stackalloc byte[1 + (2 * StringLength) + PokeCrypto.SIZE_1STORED]; + dc[0] = IsDaycareOccupied(0, 0) == true ? (byte)1 : (byte)0; + rawDC.Slice(2 + 1 + PokeCrypto.SIZE_1PARTY + StringLength, StringLength).CopyTo(dc[1..]); + rawDC.Slice(2 + 1 + PokeCrypto.SIZE_1PARTY, StringLength).CopyTo(dc[(1 + StringLength)..]); + rawDC.Slice(2 + 1, PokeCrypto.SIZE_1STORED).CopyTo(dc[(1 + (2 * StringLength))..]); + dc.CopyTo(Data.AsSpan(Offsets.Daycare)); + + SetChecksums(); + return Data.AsSpan()[..^SIZE_RESERVED].ToArray(); + } + + private int GetBoxRawDataOffset(int box) + { + if (box < BoxCount / 2) + return 0x4000 + (box * SIZE_STOREDBOX); + return 0x6000 + ((box - (BoxCount / 2)) * SIZE_STOREDBOX); + } + + // Configuration + protected override SaveFile CloneInternal() => new SAV1(Write(), Version); + + protected override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST; + protected override int SIZE_PARTY => Japanese ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST; + private int SIZE_BOX => BoxSlotCount*SIZE_STORED; + private int SIZE_STOREDBOX => PokeList1.GetDataLength(Japanese ? PokeListType.StoredJP : PokeListType.Stored, Japanese); + private int SIZE_STOREDPARTY => PokeList1.GetDataLength(PokeListType.Party, Japanese); + + public override PKM BlankPKM => new PK1(Japanese); + public override Type PKMType => typeof(PK1); + + public override int MaxMoveID => Legal.MaxMoveID_1; + public override int MaxSpeciesID => Legal.MaxSpeciesID_1; + public override int MaxAbilityID => Legal.MaxAbilityID_1; + public override int MaxItemID => Legal.MaxItemID_1; + public override int MaxBallID => 0; // unused + public override int MaxGameID => 99; // unused + public override int MaxMoney => 999999; + public override int MaxCoins => 9999; + + public override int BoxCount => Japanese ? 8 : 12; + public override int MaxEV => 65535; + public override int MaxIV => 15; + public override int Generation => 1; + public override EntityContext Context => EntityContext.Gen1; + protected override int GiftCountMax => 0; + public override int OTLength => Japanese ? 5 : 7; + public override int NickLength => Japanese ? 5 : 10; + public override int BoxSlotCount => Japanese ? 30 : 20; + + public override bool HasParty => true; + private int StringLength => Japanese ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan; + + public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGB(data); + + // Checksums + protected override void SetChecksums() => Data[Offsets.ChecksumOfs] = GetRBYChecksum(Offsets.OT, Offsets.ChecksumOfs); + public override bool ChecksumsValid => Data[Offsets.ChecksumOfs] == GetRBYChecksum(Offsets.OT, Offsets.ChecksumOfs); + public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid"; + + private byte GetRBYChecksum(int start, int end) + { + byte chksum = 0; + for (int i = start; i < end; i++) + chksum += Data[i]; + chksum ^= 0xFF; + return chksum; + } + + // Trainer Info + public override GameVersion Version { get; protected set; } + + public override string OT + { + get => GetString(Offsets.OT, OTLength); + set => SetString(Data.AsSpan(Offsets.OT, OTLength), value.AsSpan(), OTLength, StringConverterOption.Clear50); + } + + public Span OT_Trash { get => Data.AsSpan(Offsets.OT, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.OT)); } } + + public override int Gender + { + get => 0; + set { } + } + + public override int TID + { + get => ReadUInt16BigEndian(Data.AsSpan(Offsets.TID)); + set => WriteUInt16BigEndian(Data.AsSpan(Offsets.TID), (ushort)value); + } + + public override int SID { get => 0; set { } } + + public string Rival + { + get => GetString(Offsets.Rival, OTLength); + set => SetString(Data.AsSpan(Offsets.Rival, OTLength), value.AsSpan(), OTLength, StringConverterOption.Clear50); + } + + public Span Rival_Trash { get => Data.AsSpan(Offsets.Rival, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Rival)); } } + + public bool Yellow => Starter == 0x54; // Pikachu + public int Starter => Data[Offsets.Starter]; + + public byte PikaFriendship + { + get => Data[Offsets.PikaFriendship]; + set => Data[Offsets.PikaFriendship] = value; + } + + public int PikaBeachScore + { + get => BinaryCodedDecimal.ToInt32LE(Data.AsSpan(Offsets.PikaBeachScore, 2)); + set => BinaryCodedDecimal.WriteBytesLE(Data.AsSpan(Offsets.PikaBeachScore, 2), Math.Min(9999, value)); + } + + public override string PlayTimeString => !PlayedMaximum ? base.PlayTimeString : $"{base.PlayTimeString} {Checksums.CRC16_CCITT(Data):X4}"; + + public override int PlayedHours + { + get => Data[Offsets.PlayTime + 0]; + set + { + if (value >= byte.MaxValue) // Set 255:00:00.00 and flag + { + PlayedMaximum = true; + value = byte.MaxValue; + PlayedMinutes = PlayedSeconds = PlayedFrames = 0; + } + Data[Offsets.PlayTime + 0] = (byte) value; + } + } + + public bool PlayedMaximum + { + get => Data[Offsets.PlayTime + 1] != 0; + set => Data[Offsets.PlayTime + 1] = value ? (byte)1 : (byte)0; + } + + public override int PlayedMinutes + { + get => Data[Offsets.PlayTime + 2]; + set => Data[Offsets.PlayTime + 2] = (byte)value; + } + + public override int PlayedSeconds + { + get => Data[Offsets.PlayTime + 3]; + set => Data[Offsets.PlayTime + 3] = (byte)value; + } + + public int PlayedFrames + { + get => Data[Offsets.PlayTime + 4]; + set => Data[Offsets.PlayTime + 4] = (byte)value; + } + + public int Badges + { + get => Data[Offsets.Badges]; + set { if (value < 0) return; Data[Offsets.Badges] = (byte)value; } + } + + private byte Options + { + get => Data[Offsets.Options]; + set => Data[Offsets.Options] = value; + } + + public bool BattleEffects + { + get => (Options & 0x80) == 0; + set => Options = (byte)((Options & 0x7F) | (value ? 0 : 0x80)); + } + + public bool BattleStyleSwitch + { + get => (Options & 0x40) == 0; + set => Options = (byte)((Options & 0xBF) | (value ? 0 : 0x40)); + } + + public int Sound + { + get => (Options & 0x30) >> 4; + set => Options = (byte)((Options & 0xCF) | ((value & 3) << 4)); + } + + public int TextSpeed + { + get => Options & 0x7; + set => Options = (byte)((Options & 0xF8) | (value & 7)); + } + + // yellow only + public byte GBPrinterBrightness { get => Data[Offsets.PrinterBrightness]; set => Data[Offsets.PrinterBrightness] = value; } + + public override uint Money + { + get => (uint)BinaryCodedDecimal.ToInt32BE(Data.AsSpan(Offsets.Money, 3)); + set + { + value = (uint)Math.Min(value, MaxMoney); + BinaryCodedDecimal.WriteBytesBE(Data.AsSpan(Offsets.Money, 3), (int)value); + } + } + + public uint Coin + { + get => (uint)BinaryCodedDecimal.ToInt32BE(Data.AsSpan(Offsets.Coin, 2)); + set + { + value = (ushort)Math.Min(value, MaxCoins); + BinaryCodedDecimal.WriteBytesBE(Data.AsSpan(Offsets.Coin, 2), (int)value); + } + } + + private readonly ushort[] LegalItems = Legal.Pouch_Items_RBY; + + public override IReadOnlyList Inventory + { + get + { + ushort[] legalItems = LegalItems; + InventoryPouch[] pouch = + { + new InventoryPouchGB(InventoryType.Items, legalItems, 99, Offsets.Items, 20), + new InventoryPouchGB(InventoryType.PCItems, legalItems, 99, Offsets.PCItems, 50), + }; + return pouch.LoadAll(Data); + } + set => value.SaveAll(Data); + } + + public override int GetDaycareSlotOffset(int loc, int slot) + { + return DaycareOffset; + } + + public override uint? GetDaycareEXP(int loc, int slot) + { + return null; + } + + public override bool? IsDaycareOccupied(int loc, int slot) + { + if (loc == 0 && slot == 0) + return Data[Offsets.Daycare] == 0x01; + return null; + } + + public override void SetDaycareEXP(int loc, int slot, uint EXP) + { + // todo + } + + public override void SetDaycareOccupied(int loc, int slot, bool occupied) + { + // todo + } + + // Storage + public override int PartyCount + { + get => Data[Offsets.Party]; + protected set => Data[Offsets.Party] = (byte)value; + } + + public override int GetBoxOffset(int box) + { + return Data.Length - SIZE_RESERVED + (box * SIZE_BOX); + } + + public override int GetPartyOffset(int slot) + { + return Data.Length - SIZE_RESERVED + (BoxCount * SIZE_BOX) + (slot * SIZE_STORED); + } + + public override int CurrentBox + { + get => Data[Offsets.CurrentBoxIndex] & 0x7F; + set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x80) | (value & 0x7F)); + } + + public bool CurrentBoxChanged + { + get => (Data[Offsets.CurrentBoxIndex] & 0x80) != 0; + set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x7F) | (byte)(value ? 0x80 : 0)); + } + + public override string GetBoxName(int box) + { + return $"BOX {box + 1}"; + } + + public override void SetBoxName(int box, string value) + { + // Don't allow for custom box names + } + + protected override PKM GetPKM(byte[] data) + { + if (data.Length == SIZE_STORED) + return new PokeList1(data, PokeListType.Single, Japanese)[0]; + return new PK1(data); + } + + protected override byte[] DecryptPKM(byte[] data) + { + return data; + } + + // Pokédex + protected override void SetDex(PKM pk) + { + int species = pk.Species; + if (!CanSetDex(species)) + return; + + SetCaught(pk.Species, true); + SetSeen(pk.Species, true); + } + + private bool CanSetDex(int species) + { + if (species <= 0) + return false; + if (species > MaxSpeciesID) + return false; + if (Version == GameVersion.Invalid) + return false; + return true; + } + + public override bool GetSeen(int species) => GetDexFlag(Offsets.DexSeen, species); + public override bool GetCaught(int species) => GetDexFlag(Offsets.DexCaught, species); + public override void SetSeen(int species, bool seen) => SetDexFlag(Offsets.DexSeen, species, seen); + public override void SetCaught(int species, bool caught) => SetDexFlag(Offsets.DexCaught, species, caught); + + private bool GetDexFlag(int region, int species) + { + int bit = species - 1; + int ofs = bit >> 3; + return GetFlag(region + ofs, bit & 7); + } + + private void SetDexFlag(int region, int species, bool value) + { + int bit = species - 1; + int ofs = bit >> 3; + SetFlag(region + ofs, bit & 7, value); + } + + public override void WriteSlotFormatStored(PKM pk, Span data, int offset) + { + // pk that have never been boxed have yet to save the 'current level' for box indication + // set this value at this time + ((PK1)pk).Stat_LevelBox = pk.CurrentLevel; + base.WriteSlotFormatStored(pk, Data, offset); + } + + public override void WriteBoxSlot(PKM pk, Span data, int offset) + { + // pk that have never been boxed have yet to save the 'current level' for box indication + // set this value at this time + ((PK1)pk).Stat_LevelBox = pk.CurrentLevel; + base.WriteBoxSlot(pk, Data, offset); + } + + private const int SpawnFlagCount = 0xF0; + + public bool[] EventSpawnFlags + { + get + { + // RB uses 0xE4 (0xE8) flags, Yellow uses 0xF0 flags. Just grab 0xF0 + bool[] data = new bool[SpawnFlagCount]; + for (int i = 0; i < data.Length; i++) + data[i] = GetFlag(Offsets.ObjectSpawnFlags + (i >> 3), i & 7); + return data; + } + set + { + if (value.Length != SpawnFlagCount) + return; + for (int i = 0; i < value.Length; i++) + SetFlag(Offsets.ObjectSpawnFlags + (i >> 3), i & 7, value[i]); + } + } + + public override string GetString(ReadOnlySpan data) => StringConverter12.GetString(data, Japanese); + + public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter12.SetString(destBuffer, value, maxLength, Japanese, option); + } } diff --git a/PKHeX.Core/Saves/SAV1Stadium.cs b/PKHeX.Core/Saves/SAV1Stadium.cs index 3170775e4..1c120a994 100644 --- a/PKHeX.Core/Saves/SAV1Stadium.cs +++ b/PKHeX.Core/Saves/SAV1Stadium.cs @@ -2,316 +2,315 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokémon Stadium (Pokémon Stadium 2 in Japan) +/// +public sealed class SAV1Stadium : SAV_STADIUM { - /// - /// Pokémon Stadium (Pokémon Stadium 2 in Japan) - /// - public sealed class SAV1Stadium : SAV_STADIUM + public override int SaveRevision => Japanese ? 0 : 1; + public override string SaveRevisionString => Japanese ? "J" : "U"; + + public override PersonalTable Personal => PersonalTable.Y; + public override int MaxEV => ushort.MaxValue; + public override IReadOnlyList HeldItems => Array.Empty(); + public override GameVersion Version { get; protected set; } = GameVersion.Stadium; + + protected override SaveFile CloneInternal() => new SAV1Stadium((byte[])Data.Clone(), Japanese); + + public override int Generation => 1; + public override EntityContext Context => EntityContext.Gen1; + private int StringLength => Japanese ? StringLengthJ : StringLengthU; + private const int StringLengthJ = 6; + private const int StringLengthU = 11; + public override int OTLength => StringLength; + public override int NickLength => StringLength; + public override int BoxCount => Japanese ? 8 : 12; + public override int BoxSlotCount => Japanese ? 30 : 20; + + public override int MaxMoveID => Legal.MaxMoveID_1; + public override int MaxSpeciesID => Legal.MaxSpeciesID_1; + public override int MaxAbilityID => Legal.MaxAbilityID_1; + public override int MaxItemID => Legal.MaxItemID_1; + + public override Type PKMType => typeof(PK1); + public override PKM BlankPKM => new PK1(Japanese); + private const int SIZE_PK1J = PokeCrypto.SIZE_1STORED + (2 * StringLengthJ); // 0x2D + private const int SIZE_PK1U = PokeCrypto.SIZE_1STORED + (2 * StringLengthU); // 0x37 + protected override int SIZE_STORED => Japanese ? SIZE_PK1J : SIZE_PK1U; + protected override int SIZE_PARTY => Japanese ? SIZE_PK1J : SIZE_PK1U; + + private int ListHeaderSize => Japanese ? 0x0C : 0x10; + private const int ListFooterSize = 6; // POKE + 2byte checksum + + private const int TeamCountU = 10; + private const int TeamCountJ = 12; + private const int TeamCountTypeU = 9; // team-types 1 & 2 are unused + private const int TeamCountTypeJ = 9; + protected override int TeamCount => Japanese ? TeamCountJ * TeamCountTypeJ : TeamCountU * TeamCountTypeU; + private const int TeamSizeJ = 0x0C + (SIZE_PK1J * 6) + ListFooterSize; // 0x120 + private const int TeamSizeU = 0x10 + (SIZE_PK1U * 6) + ListFooterSize; // 0x160 + + private const uint MAGIC_FOOTER = 0x454B4F50; // POKE + + private int BoxSize => Japanese ? BoxSizeJ : BoxSizeU; + //private int ListHeaderSizeBox => Japanese ? 0x0C : 0x10; + private const int BoxSizeJ = 0x0C + (SIZE_PK1J * 30) + ListFooterSize; // 0x558 + private const int BoxSizeU = 0x10 + (SIZE_PK1U * 20) + 6 + ListFooterSize; // 0x468 (6 bytes alignment) + private const int BoxStart = 0xC000; + public override int GetBoxOffset(int box) => Box + ListHeaderSize + (box * BoxSize); + + public SAV1Stadium(byte[] data) : this(data, IsStadiumJ(data)) { } + + public SAV1Stadium(byte[] data, bool japanese) : base(data, japanese, GetIsSwap(data, japanese)) { - public override int SaveRevision => Japanese ? 0 : 1; - public override string SaveRevisionString => Japanese ? "J" : "U"; - - public override PersonalTable Personal => PersonalTable.Y; - public override int MaxEV => ushort.MaxValue; - public override IReadOnlyList HeldItems => Array.Empty(); - public override GameVersion Version { get; protected set; } = GameVersion.Stadium; - - protected override SaveFile CloneInternal() => new SAV1Stadium((byte[])Data.Clone(), Japanese); - - public override int Generation => 1; - public override EntityContext Context => EntityContext.Gen1; - private int StringLength => Japanese ? StringLengthJ : StringLengthU; - private const int StringLengthJ = 6; - private const int StringLengthU = 11; - public override int OTLength => StringLength; - public override int NickLength => StringLength; - public override int BoxCount => Japanese ? 8 : 12; - public override int BoxSlotCount => Japanese ? 30 : 20; - - public override int MaxMoveID => Legal.MaxMoveID_1; - public override int MaxSpeciesID => Legal.MaxSpeciesID_1; - public override int MaxAbilityID => Legal.MaxAbilityID_1; - public override int MaxItemID => Legal.MaxItemID_1; - - public override Type PKMType => typeof(PK1); - public override PKM BlankPKM => new PK1(Japanese); - private const int SIZE_PK1J = PokeCrypto.SIZE_1STORED + (2 * StringLengthJ); // 0x2D - private const int SIZE_PK1U = PokeCrypto.SIZE_1STORED + (2 * StringLengthU); // 0x37 - protected override int SIZE_STORED => Japanese ? SIZE_PK1J : SIZE_PK1U; - protected override int SIZE_PARTY => Japanese ? SIZE_PK1J : SIZE_PK1U; - - private int ListHeaderSize => Japanese ? 0x0C : 0x10; - private const int ListFooterSize = 6; // POKE + 2byte checksum - - private const int TeamCountU = 10; - private const int TeamCountJ = 12; - private const int TeamCountTypeU = 9; // team-types 1 & 2 are unused - private const int TeamCountTypeJ = 9; - protected override int TeamCount => Japanese ? TeamCountJ * TeamCountTypeJ : TeamCountU * TeamCountTypeU; - private const int TeamSizeJ = 0x0C + (SIZE_PK1J * 6) + ListFooterSize; // 0x120 - private const int TeamSizeU = 0x10 + (SIZE_PK1U * 6) + ListFooterSize; // 0x160 - - private const uint MAGIC_FOOTER = 0x454B4F50; // POKE - - private int BoxSize => Japanese ? BoxSizeJ : BoxSizeU; - //private int ListHeaderSizeBox => Japanese ? 0x0C : 0x10; - private const int BoxSizeJ = 0x0C + (SIZE_PK1J * 30) + ListFooterSize; // 0x558 - private const int BoxSizeU = 0x10 + (SIZE_PK1U * 20) + 6 + ListFooterSize; // 0x468 (6 bytes alignment) - private const int BoxStart = 0xC000; - public override int GetBoxOffset(int box) => Box + ListHeaderSize + (box * BoxSize); - - public SAV1Stadium(byte[] data) : this(data, IsStadiumJ(data)) { } - - public SAV1Stadium(byte[] data, bool japanese) : base(data, japanese, GetIsSwap(data, japanese)) - { - Box = BoxStart; - } - - public SAV1Stadium(bool japanese = false) : base(japanese, SaveUtil.SIZE_G1STAD) - { - Box = BoxStart; - ClearBoxes(); - } - - protected override bool GetIsBoxChecksumValid(int box) - { - var boxOfs = GetBoxOffset(box) - ListHeaderSize; - var size = BoxSize - 2; - var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); - var actual = ReadUInt16BigEndian(Data.AsSpan(boxOfs + size)); - return chk == actual; - } - - protected override void SetBoxChecksum(int box) - { - var boxOfs = GetBoxOffset(box) - ListHeaderSize; - var size = BoxSize - 2; - var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); - WriteUInt16BigEndian(Data.AsSpan(boxOfs + size), chk); - } - - protected override void SetBoxMetadata(int box) - { - var bdata = GetBoxOffset(box); - - // Set box count - int count = 0; - for (int s = 0; s < BoxSlotCount; s++) - { - var rel = bdata + (SIZE_STORED * s); - if (Data[rel] != 0) // Species present - count++; - } - - // Last byte of header - Data[bdata - 1] = (byte)count; - } - - protected override PKM GetPKM(byte[] data) - { - int len = StringLength; - var nick = data.AsSpan(PokeCrypto.SIZE_1STORED, len); - var ot = data.AsSpan(PokeCrypto.SIZE_1STORED + len, len); - data = data.Slice(0, PokeCrypto.SIZE_1STORED); - var pk1 = new PK1(data, Japanese); - nick.CopyTo(pk1.RawNickname); - ot.CopyTo(pk1.RawOT); - return pk1; - } - - public override byte[] GetDataForFormatStored(PKM pkm) - { - byte[] result = new byte[SIZE_STORED]; - var gb = (PK1)pkm; - - var data = pkm.Data; - int len = StringLength; - data.CopyTo(result, 0); - gb.RawNickname.CopyTo(result, PokeCrypto.SIZE_1STORED); - gb.RawOT.CopyTo(result, PokeCrypto.SIZE_1STORED + len); - return result; - } - - public override byte[] GetDataForFormatParty(PKM pkm) => GetDataForFormatStored(pkm); - public override byte[] GetDataForParty(PKM pkm) => GetDataForFormatStored(pkm); - public override byte[] GetDataForBox(PKM pkm) => GetDataForFormatStored(pkm); - - public int GetTeamOffset(int team) => Japanese ? GetTeamOffsetJ(team) : GetTeamOffsetU(team); - - private int GetTeamOffsetJ(int team) - { - if ((uint) team > TeamCount) - throw new ArgumentOutOfRangeException(nameof(team)); - return GetTeamTypeOffsetJ(team / TeamCountJ) + (TeamSizeJ * (team % TeamCountJ)); - } - - private int GetTeamOffsetU(int team) - { - if ((uint)team > TeamCount) - throw new ArgumentOutOfRangeException(nameof(team)); - return GetTeamTypeOffsetU(team / TeamCountU) + (TeamSizeU * (team % TeamCountU)); - } - - private static int GetTeamTypeOffsetJ(int team) => team switch - { - 0 => 0x0000, // Anything Goes - 1 => 0x0D80, // Nintendo Cup '97 - 2 => 0x1B00, // Nintendo Cup '98 - 3 => 0x2880, // Nintendo Cup '99 - - 4 => 0x4000, // Petit Cup - 5 => 0x4D80, // Pika Cup - 6 => 0x5B00, // Prime Cup - 7 => 0x6880, // Gym Leader Castle - - 8 => 0x8000, // Vs. Mewtwo - _ => throw new ArgumentOutOfRangeException(nameof(team)), - }; - - private static int GetTeamTypeOffsetU(int team) => team switch - { - 0 => 0x0000, - 1 => 0x0DC0, // Unused - 2 => 0x1B80, // Unused - 3 => 0x2940, // Poke Cup - - 4 => 0x4000, // Petit Cup - 5 => 0x4DC0, // Pika Cup - 6 => 0x5B80, // Prime Cup - 7 => 0x6940, // Gym Leader Castle - - 8 => 0x8000, // Vs. Mewtwo - _ => throw new ArgumentOutOfRangeException(nameof(team)), - }; - - public int GetTeamOffset(Stadium2TeamType type, int team) - { - if (Japanese) - return GetTeamTypeOffsetJ((int)type) + (TeamSizeJ * team); - return GetTeamTypeOffsetU((int)type) + (TeamSizeU * team); - } - - public string GetTeamName(int team) - { - if ((uint)team >= TeamCount) - throw new ArgumentOutOfRangeException(nameof(team)); - - var teamsPerType = Japanese ? TeamCountJ : TeamCountU; - var type = team / teamsPerType; - var index = team % teamsPerType; - var name = $"{GetTeamTypeName(type).Replace('_', ' ')} {index + 1}"; - - var ofs = GetTeamOffset(team); - var otOfs = ofs + (Japanese ? 2 : 1); - var str = GetString(otOfs, Japanese ? 5 : 7); - if (string.IsNullOrWhiteSpace(str)) - return name; - var idOfs = ofs + (Japanese ? 0x8 : 0xC); - var id = ReadUInt16BigEndian(Data.AsSpan(idOfs)); - return $"{name} [{id:D5}:{str}]"; - } - - private string GetTeamTypeName(int type) - { - if (Japanese) - return ((Stadium1TeamType) type).ToString(); - return type switch - { - 1 => "Unused1", - 2 => "Unused2", - 3 => "Poke_Cup", - _ => ((Stadium1TeamType)type).ToString(), - }; - } - - public override SlotGroup[] GetRegisteredTeams() - { - var result = base.GetRegisteredTeams(); - if (Japanese) - return result; - - // Trim out the teams that aren't accessible - var noUnused = new SlotGroup[result.Length - (2 * TeamCountU)]; - Array.Copy(result, 0, noUnused, 0, TeamCountU); - Array.Copy(result, 3 * TeamCountU, noUnused, TeamCountU, noUnused.Length - TeamCountU); - return noUnused; - } - - public override SlotGroup GetTeam(int team) - { - if ((uint)team >= TeamCount) - throw new ArgumentOutOfRangeException(nameof(team)); - - var name = GetTeamName(team); - var members = new PK1[6]; - var ofs = GetTeamOffset(team); - for (int i = 0; i < 6; i++) - { - var rel = ofs + ListHeaderSize + (i * SIZE_STORED); - members[i] = (PK1)GetStoredSlot(Data, rel); - } - return new SlotGroup(name, members); - } - - public override void WriteSlotFormatStored(PKM pkm, Span data, int offset) - { - // pkm that have never been boxed have yet to save the 'current level' for box indication - // set this value at this time - ((PK1)pkm).Stat_LevelBox = pkm.CurrentLevel; - base.WriteSlotFormatStored(pkm, Data, offset); - } - - public override void WriteBoxSlot(PKM pkm, Span data, int offset) - { - // pkm that have never been boxed have yet to save the 'current level' for box indication - // set this value at this time - ((PK1)pkm).Stat_LevelBox = pkm.CurrentLevel; - base.WriteBoxSlot(pkm, Data, offset); - } - - public static bool IsStadium(ReadOnlySpan data) - { - if (data.Length is not (SaveUtil.SIZE_G1STAD or SaveUtil.SIZE_G1STADF)) - return false; - if (IsStadiumJ(data)) - return true; - if (IsStadiumU(data)) - return true; - return false; - } - - private static bool IsStadiumJ(ReadOnlySpan data) => IsStadium(data, TeamSizeJ, BoxSizeJ) != StadiumSaveType.None; - private static bool IsStadiumU(ReadOnlySpan data) => IsStadium(data, TeamSizeU, BoxSizeU) != StadiumSaveType.None; - - private static bool GetIsSwap(ReadOnlySpan data, bool japanese) - { - var result = japanese ? IsStadium(data, TeamSizeJ, BoxSizeJ) : IsStadium(data, TeamSizeU, BoxSizeU); - return result == StadiumSaveType.Swapped; - } - - private static StadiumSaveType IsStadium(ReadOnlySpan data, int teamSize, int boxSize) - { - var isTeam = StadiumUtil.IsMagicPresentEither(data, teamSize, MAGIC_FOOTER, 10); - if (isTeam != StadiumSaveType.None) - return isTeam; - var isBox = StadiumUtil.IsMagicPresentEither(data[BoxStart..], boxSize, MAGIC_FOOTER, 5); - if (isBox != StadiumSaveType.None) - return isBox; - return StadiumSaveType.None; - } + Box = BoxStart; } - public enum Stadium1TeamType + public SAV1Stadium(bool japanese = false) : base(japanese, SaveUtil.SIZE_G1STAD) { - Anything_Goes = 0, - Nintendo_Cup97 = 1, // unused in non-JP - Nintendo_Cup98 = 2, // unused in non-JP - Nintendo_Cup99 = 3, // Poke Cup in non-JP - Petit_Cup = 4, - Pika_Cup = 5, - Prime_Cup = 6, - Gym_Leader_Castle = 7, - Vs_Mewtwo = 8, + Box = BoxStart; + ClearBoxes(); + } + + protected override bool GetIsBoxChecksumValid(int box) + { + var boxOfs = GetBoxOffset(box) - ListHeaderSize; + var size = BoxSize - 2; + var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); + var actual = ReadUInt16BigEndian(Data.AsSpan(boxOfs + size)); + return chk == actual; + } + + protected override void SetBoxChecksum(int box) + { + var boxOfs = GetBoxOffset(box) - ListHeaderSize; + var size = BoxSize - 2; + var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); + WriteUInt16BigEndian(Data.AsSpan(boxOfs + size), chk); + } + + protected override void SetBoxMetadata(int box) + { + var bdata = GetBoxOffset(box); + + // Set box count + int count = 0; + for (int s = 0; s < BoxSlotCount; s++) + { + var rel = bdata + (SIZE_STORED * s); + if (Data[rel] != 0) // Species present + count++; + } + + // Last byte of header + Data[bdata - 1] = (byte)count; + } + + protected override PKM GetPKM(byte[] data) + { + int len = StringLength; + var nick = data.AsSpan(PokeCrypto.SIZE_1STORED, len); + var ot = data.AsSpan(PokeCrypto.SIZE_1STORED + len, len); + data = data.Slice(0, PokeCrypto.SIZE_1STORED); + var pk1 = new PK1(data, Japanese); + nick.CopyTo(pk1.RawNickname); + ot.CopyTo(pk1.RawOT); + return pk1; + } + + public override byte[] GetDataForFormatStored(PKM pk) + { + byte[] result = new byte[SIZE_STORED]; + var gb = (PK1)pk; + + var data = pk.Data; + int len = StringLength; + data.CopyTo(result, 0); + gb.RawNickname.CopyTo(result, PokeCrypto.SIZE_1STORED); + gb.RawOT.CopyTo(result, PokeCrypto.SIZE_1STORED + len); + return result; + } + + public override byte[] GetDataForFormatParty(PKM pk) => GetDataForFormatStored(pk); + public override byte[] GetDataForParty(PKM pk) => GetDataForFormatStored(pk); + public override byte[] GetDataForBox(PKM pk) => GetDataForFormatStored(pk); + + public int GetTeamOffset(int team) => Japanese ? GetTeamOffsetJ(team) : GetTeamOffsetU(team); + + private int GetTeamOffsetJ(int team) + { + if ((uint) team > TeamCount) + throw new ArgumentOutOfRangeException(nameof(team)); + return GetTeamTypeOffsetJ(team / TeamCountJ) + (TeamSizeJ * (team % TeamCountJ)); + } + + private int GetTeamOffsetU(int team) + { + if ((uint)team > TeamCount) + throw new ArgumentOutOfRangeException(nameof(team)); + return GetTeamTypeOffsetU(team / TeamCountU) + (TeamSizeU * (team % TeamCountU)); + } + + private static int GetTeamTypeOffsetJ(int team) => team switch + { + 0 => 0x0000, // Anything Goes + 1 => 0x0D80, // Nintendo Cup '97 + 2 => 0x1B00, // Nintendo Cup '98 + 3 => 0x2880, // Nintendo Cup '99 + + 4 => 0x4000, // Petit Cup + 5 => 0x4D80, // Pika Cup + 6 => 0x5B00, // Prime Cup + 7 => 0x6880, // Gym Leader Castle + + 8 => 0x8000, // Vs. Mewtwo + _ => throw new ArgumentOutOfRangeException(nameof(team)), + }; + + private static int GetTeamTypeOffsetU(int team) => team switch + { + 0 => 0x0000, + 1 => 0x0DC0, // Unused + 2 => 0x1B80, // Unused + 3 => 0x2940, // Poke Cup + + 4 => 0x4000, // Petit Cup + 5 => 0x4DC0, // Pika Cup + 6 => 0x5B80, // Prime Cup + 7 => 0x6940, // Gym Leader Castle + + 8 => 0x8000, // Vs. Mewtwo + _ => throw new ArgumentOutOfRangeException(nameof(team)), + }; + + public int GetTeamOffset(Stadium2TeamType type, int team) + { + if (Japanese) + return GetTeamTypeOffsetJ((int)type) + (TeamSizeJ * team); + return GetTeamTypeOffsetU((int)type) + (TeamSizeU * team); + } + + public string GetTeamName(int team) + { + if ((uint)team >= TeamCount) + throw new ArgumentOutOfRangeException(nameof(team)); + + var teamsPerType = Japanese ? TeamCountJ : TeamCountU; + var type = team / teamsPerType; + var index = team % teamsPerType; + var name = $"{GetTeamTypeName(type).Replace('_', ' ')} {index + 1}"; + + var ofs = GetTeamOffset(team); + var otOfs = ofs + (Japanese ? 2 : 1); + var str = GetString(otOfs, Japanese ? 5 : 7); + if (string.IsNullOrWhiteSpace(str)) + return name; + var idOfs = ofs + (Japanese ? 0x8 : 0xC); + var id = ReadUInt16BigEndian(Data.AsSpan(idOfs)); + return $"{name} [{id:D5}:{str}]"; + } + + private string GetTeamTypeName(int type) + { + if (Japanese) + return ((Stadium1TeamType) type).ToString(); + return type switch + { + 1 => "Unused1", + 2 => "Unused2", + 3 => "Poke_Cup", + _ => ((Stadium1TeamType)type).ToString(), + }; + } + + public override SlotGroup[] GetRegisteredTeams() + { + var result = base.GetRegisteredTeams(); + if (Japanese) + return result; + + // Trim out the teams that aren't accessible + var noUnused = new SlotGroup[result.Length - (2 * TeamCountU)]; + Array.Copy(result, 0, noUnused, 0, TeamCountU); + Array.Copy(result, 3 * TeamCountU, noUnused, TeamCountU, noUnused.Length - TeamCountU); + return noUnused; + } + + public override SlotGroup GetTeam(int team) + { + if ((uint)team >= TeamCount) + throw new ArgumentOutOfRangeException(nameof(team)); + + var name = GetTeamName(team); + var members = new PK1[6]; + var ofs = GetTeamOffset(team); + for (int i = 0; i < 6; i++) + { + var rel = ofs + ListHeaderSize + (i * SIZE_STORED); + members[i] = (PK1)GetStoredSlot(Data, rel); + } + return new SlotGroup(name, members); + } + + public override void WriteSlotFormatStored(PKM pk, Span data, int offset) + { + // pk that have never been boxed have yet to save the 'current level' for box indication + // set this value at this time + ((PK1)pk).Stat_LevelBox = pk.CurrentLevel; + base.WriteSlotFormatStored(pk, Data, offset); + } + + public override void WriteBoxSlot(PKM pk, Span data, int offset) + { + // pk that have never been boxed have yet to save the 'current level' for box indication + // set this value at this time + ((PK1)pk).Stat_LevelBox = pk.CurrentLevel; + base.WriteBoxSlot(pk, Data, offset); + } + + public static bool IsStadium(ReadOnlySpan data) + { + if (data.Length is not (SaveUtil.SIZE_G1STAD or SaveUtil.SIZE_G1STADF)) + return false; + if (IsStadiumJ(data)) + return true; + if (IsStadiumU(data)) + return true; + return false; + } + + private static bool IsStadiumJ(ReadOnlySpan data) => IsStadium(data, TeamSizeJ, BoxSizeJ) != StadiumSaveType.None; + private static bool IsStadiumU(ReadOnlySpan data) => IsStadium(data, TeamSizeU, BoxSizeU) != StadiumSaveType.None; + + private static bool GetIsSwap(ReadOnlySpan data, bool japanese) + { + var result = japanese ? IsStadium(data, TeamSizeJ, BoxSizeJ) : IsStadium(data, TeamSizeU, BoxSizeU); + return result == StadiumSaveType.Swapped; + } + + private static StadiumSaveType IsStadium(ReadOnlySpan data, int teamSize, int boxSize) + { + var isTeam = StadiumUtil.IsMagicPresentEither(data, teamSize, MAGIC_FOOTER, 10); + if (isTeam != StadiumSaveType.None) + return isTeam; + var isBox = StadiumUtil.IsMagicPresentEither(data[BoxStart..], boxSize, MAGIC_FOOTER, 5); + if (isBox != StadiumSaveType.None) + return isBox; + return StadiumSaveType.None; } } + +public enum Stadium1TeamType +{ + Anything_Goes = 0, + Nintendo_Cup97 = 1, // unused in non-JP + Nintendo_Cup98 = 2, // unused in non-JP + Nintendo_Cup99 = 3, // Poke Cup in non-JP + Petit_Cup = 4, + Pika_Cup = 5, + Prime_Cup = 6, + Gym_Leader_Castle = 7, + Vs_Mewtwo = 8, +} diff --git a/PKHeX.Core/Saves/SAV1StadiumJ.cs b/PKHeX.Core/Saves/SAV1StadiumJ.cs index e7daca4f8..cf154f020 100644 --- a/PKHeX.Core/Saves/SAV1StadiumJ.cs +++ b/PKHeX.Core/Saves/SAV1StadiumJ.cs @@ -2,179 +2,178 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pocket Monsters Stadium +/// +public sealed class SAV1StadiumJ : SAV_STADIUM { - /// - /// Pocket Monsters Stadium - /// - public sealed class SAV1StadiumJ : SAV_STADIUM + // Required since PK1 logic comparing a save file assumes the save file can be U/J + public override int SaveRevision => 0; + public override string SaveRevisionString => "0"; // so we're different from Japanese SAV1Stadium naming... + + public override PersonalTable Personal => PersonalTable.Y; + public override int MaxEV => ushort.MaxValue; + public override IReadOnlyList HeldItems => Array.Empty(); + public override GameVersion Version { get; protected set; } = GameVersion.StadiumJ; + + protected override SaveFile CloneInternal() => new SAV1StadiumJ((byte[])Data.Clone()); + + public override int Generation => 1; + public override EntityContext Context => EntityContext.Gen1; + private const int StringLength = 6; // Japanese Only + public override int OTLength => StringLength; + public override int NickLength => StringLength; + public override int BoxCount => 4; // 8 boxes stored sequentially; latter 4 are backups + public override int BoxSlotCount => 30; + + public override int MaxMoveID => Legal.MaxMoveID_1; + public override int MaxSpeciesID => Legal.MaxSpeciesID_1; + public override int MaxAbilityID => Legal.MaxAbilityID_1; + public override int MaxItemID => Legal.MaxItemID_1; + private const int SIZE_PK1J = PokeCrypto.SIZE_1STORED + (2 * StringLength); // 0x2D + protected override int SIZE_STORED => SIZE_PK1J; + protected override int SIZE_PARTY => SIZE_PK1J; + + public override Type PKMType => typeof(PK1); + public override PKM BlankPKM => new PK1(true); + + private const int ListHeaderSize = 0x14; + private const int ListFooterSize = 6; // POKE + 2byte checksum + private const uint MAGIC_FOOTER = 0x454B4F50; // POKE + + protected override int TeamCount => 16; // 32 teams stored sequentially; latter 16 are backups + private const int TeamSizeJ = 0x14 + (SIZE_PK1J * 6) + ListFooterSize; // 0x128 + private const int BoxSizeJ = 0x560; + private const int BoxStart = 0x2500; + + public SAV1StadiumJ(byte[] data) : base(data, true, GetIsSwap(data)) { - // Required since PK1 logic comparing a save file assumes the save file can be U/J - public override int SaveRevision => 0; - public override string SaveRevisionString => "0"; // so we're different from Japanese SAV1Stadium naming... - - public override PersonalTable Personal => PersonalTable.Y; - public override int MaxEV => ushort.MaxValue; - public override IReadOnlyList HeldItems => Array.Empty(); - public override GameVersion Version { get; protected set; } = GameVersion.StadiumJ; - - protected override SaveFile CloneInternal() => new SAV1StadiumJ((byte[])Data.Clone()); - - public override int Generation => 1; - public override EntityContext Context => EntityContext.Gen1; - private const int StringLength = 6; // Japanese Only - public override int OTLength => StringLength; - public override int NickLength => StringLength; - public override int BoxCount => 4; // 8 boxes stored sequentially; latter 4 are backups - public override int BoxSlotCount => 30; - - public override int MaxMoveID => Legal.MaxMoveID_1; - public override int MaxSpeciesID => Legal.MaxSpeciesID_1; - public override int MaxAbilityID => Legal.MaxAbilityID_1; - public override int MaxItemID => Legal.MaxItemID_1; - private const int SIZE_PK1J = PokeCrypto.SIZE_1STORED + (2 * StringLength); // 0x2D - protected override int SIZE_STORED => SIZE_PK1J; - protected override int SIZE_PARTY => SIZE_PK1J; - - public override Type PKMType => typeof(PK1); - public override PKM BlankPKM => new PK1(true); - - private const int ListHeaderSize = 0x14; - private const int ListFooterSize = 6; // POKE + 2byte checksum - private const uint MAGIC_FOOTER = 0x454B4F50; // POKE - - protected override int TeamCount => 16; // 32 teams stored sequentially; latter 16 are backups - private const int TeamSizeJ = 0x14 + (SIZE_PK1J * 6) + ListFooterSize; // 0x128 - private const int BoxSizeJ = 0x560; - private const int BoxStart = 0x2500; - - public SAV1StadiumJ(byte[] data) : base(data, true, GetIsSwap(data)) - { - Box = BoxStart; - } - - public SAV1StadiumJ() : base(true, SaveUtil.SIZE_G1STAD) - { - Box = BoxStart; - ClearBoxes(); - } - - protected override bool GetIsBoxChecksumValid(int box) - { - var boxOfs = GetBoxOffset(box) - ListHeaderSize; - const int size = BoxSizeJ - 2; - var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); - var actual = ReadUInt16BigEndian(Data.AsSpan(boxOfs + size)); - return chk == actual; - } - - protected override void SetBoxChecksum(int box) - { - var boxOfs = GetBoxOffset(box) - ListHeaderSize; - const int size = BoxSizeJ - 2; - var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); - WriteUInt16BigEndian(Data.AsSpan(boxOfs + size), chk); - } - - protected override void SetBoxMetadata(int box) - { - // Not implemented - } - - protected override PKM GetPKM(byte[] data) - { - const int len = StringLength; - var nick = data.AsSpan(0x21, len); - var ot = data.AsSpan(0x21 + len, len); - data = data.Slice(0, 0x21); - var pk1 = new PK1(data, true); - nick.CopyTo(pk1.RawNickname); - ot.CopyTo(pk1.RawOT); - return pk1; - } - - public override byte[] GetDataForFormatStored(PKM pkm) - { - byte[] result = new byte[SIZE_STORED]; - var gb = (PK1)pkm; - - var data = pkm.Data; - const int len = StringLength; - data.CopyTo(result, 0); - gb.RawNickname.CopyTo(result, PokeCrypto.SIZE_1STORED); - gb.RawOT.CopyTo(result, PokeCrypto.SIZE_1STORED + len); - return result; - } - - public override byte[] GetDataForFormatParty(PKM pkm) => GetDataForFormatStored(pkm); - public override byte[] GetDataForParty(PKM pkm) => GetDataForFormatStored(pkm); - public override byte[] GetDataForBox(PKM pkm) => GetDataForFormatStored(pkm); - - public override int GetBoxOffset(int box) => Box + ListHeaderSize + (box * BoxSizeJ); - public static int GetTeamOffset(int team) => 0 + (team * 2 * TeamSizeJ); // backups are after each team - - public string GetTeamName(int team) - { - var name = $"Team {team + 1}"; - - var ofs = GetTeamOffset(team); - var str = GetString(ofs + 2, 5); - if (string.IsNullOrWhiteSpace(str)) - return name; - var id = ReadUInt16BigEndian(Data.AsSpan(ofs + 8)); - return $"{name} [{id:D5}:{str}]"; - } - - public override SlotGroup GetTeam(int team) - { - if ((uint)team >= TeamCount) - throw new ArgumentOutOfRangeException(nameof(team)); - - var name = GetTeamName(team); - var members = new PK1[6]; - var ofs = GetTeamOffset(team); - for (int i = 0; i < 6; i++) - { - var rel = ofs + ListHeaderSize + (i * SIZE_STORED); - members[i] = (PK1)GetStoredSlot(Data, rel); - } - return new SlotGroup(name, members); - } - - public override void WriteSlotFormatStored(PKM pkm, Span data, int offset) - { - // pkm that have never been boxed have yet to save the 'current level' for box indication - // set this value at this time - ((PK1)pkm).Stat_LevelBox = pkm.CurrentLevel; - base.WriteSlotFormatStored(pkm, Data, offset); - } - - public override void WriteBoxSlot(PKM pkm, Span data, int offset) - { - // pkm that have never been boxed have yet to save the 'current level' for box indication - // set this value at this time - ((PK1)pkm).Stat_LevelBox = pkm.CurrentLevel; - base.WriteBoxSlot(pkm, Data, offset); - } - - public static bool IsStadium(ReadOnlySpan data) - { - if (data.Length != SaveUtil.SIZE_G1STADJ) - return false; - return GetType(data) != StadiumSaveType.None; - } - - private static StadiumSaveType GetType(ReadOnlySpan data) - { - var team = StadiumUtil.IsMagicPresentEither(data, TeamSizeJ, MAGIC_FOOTER, 10); - if (team != StadiumSaveType.None) - return team; - var box = StadiumUtil.IsMagicPresentEither(data[BoxStart..], BoxSizeJ, MAGIC_FOOTER, 1); - if (box != StadiumSaveType.None) - return box; - return StadiumSaveType.None; - } - - private static bool GetIsSwap(ReadOnlySpan data) => GetType(data) == StadiumSaveType.Swapped; + Box = BoxStart; } + + public SAV1StadiumJ() : base(true, SaveUtil.SIZE_G1STAD) + { + Box = BoxStart; + ClearBoxes(); + } + + protected override bool GetIsBoxChecksumValid(int box) + { + var boxOfs = GetBoxOffset(box) - ListHeaderSize; + const int size = BoxSizeJ - 2; + var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); + var actual = ReadUInt16BigEndian(Data.AsSpan(boxOfs + size)); + return chk == actual; + } + + protected override void SetBoxChecksum(int box) + { + var boxOfs = GetBoxOffset(box) - ListHeaderSize; + const int size = BoxSizeJ - 2; + var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); + WriteUInt16BigEndian(Data.AsSpan(boxOfs + size), chk); + } + + protected override void SetBoxMetadata(int box) + { + // Not implemented + } + + protected override PKM GetPKM(byte[] data) + { + const int len = StringLength; + var nick = data.AsSpan(0x21, len); + var ot = data.AsSpan(0x21 + len, len); + data = data.Slice(0, 0x21); + var pk1 = new PK1(data, true); + nick.CopyTo(pk1.RawNickname); + ot.CopyTo(pk1.RawOT); + return pk1; + } + + public override byte[] GetDataForFormatStored(PKM pk) + { + byte[] result = new byte[SIZE_STORED]; + var gb = (PK1)pk; + + var data = pk.Data; + const int len = StringLength; + data.CopyTo(result, 0); + gb.RawNickname.CopyTo(result, PokeCrypto.SIZE_1STORED); + gb.RawOT.CopyTo(result, PokeCrypto.SIZE_1STORED + len); + return result; + } + + public override byte[] GetDataForFormatParty(PKM pk) => GetDataForFormatStored(pk); + public override byte[] GetDataForParty(PKM pk) => GetDataForFormatStored(pk); + public override byte[] GetDataForBox(PKM pk) => GetDataForFormatStored(pk); + + public override int GetBoxOffset(int box) => Box + ListHeaderSize + (box * BoxSizeJ); + public static int GetTeamOffset(int team) => 0 + (team * 2 * TeamSizeJ); // backups are after each team + + public string GetTeamName(int team) + { + var name = $"Team {team + 1}"; + + var ofs = GetTeamOffset(team); + var str = GetString(ofs + 2, 5); + if (string.IsNullOrWhiteSpace(str)) + return name; + var id = ReadUInt16BigEndian(Data.AsSpan(ofs + 8)); + return $"{name} [{id:D5}:{str}]"; + } + + public override SlotGroup GetTeam(int team) + { + if ((uint)team >= TeamCount) + throw new ArgumentOutOfRangeException(nameof(team)); + + var name = GetTeamName(team); + var members = new PK1[6]; + var ofs = GetTeamOffset(team); + for (int i = 0; i < 6; i++) + { + var rel = ofs + ListHeaderSize + (i * SIZE_STORED); + members[i] = (PK1)GetStoredSlot(Data, rel); + } + return new SlotGroup(name, members); + } + + public override void WriteSlotFormatStored(PKM pk, Span data, int offset) + { + // pk that have never been boxed have yet to save the 'current level' for box indication + // set this value at this time + ((PK1)pk).Stat_LevelBox = pk.CurrentLevel; + base.WriteSlotFormatStored(pk, Data, offset); + } + + public override void WriteBoxSlot(PKM pk, Span data, int offset) + { + // pk that have never been boxed have yet to save the 'current level' for box indication + // set this value at this time + ((PK1)pk).Stat_LevelBox = pk.CurrentLevel; + base.WriteBoxSlot(pk, Data, offset); + } + + public static bool IsStadium(ReadOnlySpan data) + { + if (data.Length != SaveUtil.SIZE_G1STADJ) + return false; + return GetType(data) != StadiumSaveType.None; + } + + private static StadiumSaveType GetType(ReadOnlySpan data) + { + var team = StadiumUtil.IsMagicPresentEither(data, TeamSizeJ, MAGIC_FOOTER, 10); + if (team != StadiumSaveType.None) + return team; + var box = StadiumUtil.IsMagicPresentEither(data[BoxStart..], BoxSizeJ, MAGIC_FOOTER, 1); + if (box != StadiumSaveType.None) + return box; + return StadiumSaveType.None; + } + + private static bool GetIsSwap(ReadOnlySpan data) => GetType(data) == StadiumSaveType.Swapped; } diff --git a/PKHeX.Core/Saves/SAV2.cs b/PKHeX.Core/Saves/SAV2.cs index 678633d5c..63c3071b2 100644 --- a/PKHeX.Core/Saves/SAV2.cs +++ b/PKHeX.Core/Saves/SAV2.cs @@ -1,777 +1,776 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 2 object. +/// +public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWorkArray { - /// - /// Generation 2 object. - /// - public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWorkArray + protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}"; + public override string Extension => ".sav"; + public bool IsVirtualConsole => State.Exportable && Metadata.FileName is { } s && s.StartsWith("sav", StringComparison.Ordinal) && s.Contains(".dat"); // default to GB-Era for non-exportable + + public int SaveRevision => Japanese ? 0 : !Korean ? 1 : 2; + public string SaveRevisionString => (Japanese ? "J" : !Korean ? "U" : "K") + (IsVirtualConsole ? "VC" : "GB"); + public bool Japanese { get; } + public bool Korean { get; } + + public override PersonalTable Personal { get; } + public override IReadOnlyList HeldItems => Legal.HeldItems_GSC; + + public override IReadOnlyList PKMExtensions => Array.FindAll(PKM.Extensions, f => { - protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}"; - public override string Extension => ".sav"; - public bool IsVirtualConsole => State.Exportable && Metadata.FileName is { } s && s.StartsWith("sav") && s.Contains(".dat"); // default to GB-Era for non-exportable + int gen = f[^1] - 0x30; + if (Korean) + return gen == 2; + return gen is 1 or 2; + }); - public int SaveRevision => Japanese ? 0 : !Korean ? 1 : 2; - public string SaveRevisionString => (Japanese ? "J" : !Korean ? "U" : "K") + (IsVirtualConsole ? "VC" : "GB"); - public bool Japanese { get; } - public bool Korean { get; } - - public override PersonalTable Personal { get; } - public override IReadOnlyList HeldItems => Legal.HeldItems_GSC; - - public override IReadOnlyList PKMExtensions => Array.FindAll(PKM.Extensions, f => + public SAV2(GameVersion version = GameVersion.C, LanguageID lang = LanguageID.English) : base(SaveUtil.SIZE_G2RAW_J) + { + Version = version; + switch (lang) { - int gen = f[^1] - 0x30; - if (Korean) - return gen == 2; - return gen is 1 or 2; - }); - - public SAV2(GameVersion version = GameVersion.C, LanguageID lang = LanguageID.English) : base(SaveUtil.SIZE_G2RAW_J) - { - Version = version; - switch (lang) - { - case LanguageID.Japanese: - Japanese = true; - break; - case LanguageID.Korean: - Korean = true; - break; - // otherwise, both false - } - Offsets = new SAV2Offsets(this); - Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C; - Initialize(); - ClearBoxes(); + case LanguageID.Japanese: + Japanese = true; + break; + case LanguageID.Korean: + Korean = true; + break; + // otherwise, both false } + Offsets = new SAV2Offsets(this); + Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C; + Initialize(); + ClearBoxes(); + } - public SAV2(byte[] data, GameVersion versionOverride = GameVersion.Any) : base(data) + public SAV2(byte[] data, GameVersion versionOverride = GameVersion.Any) : base(data) + { + Version = versionOverride != GameVersion.Any ? versionOverride : SaveUtil.GetIsG2SAV(Data); + Japanese = SaveUtil.GetIsG2SAVJ(Data) != GameVersion.Invalid; + if (Version != GameVersion.C && !Japanese) + Korean = SaveUtil.GetIsG2SAVK(Data) != GameVersion.Invalid; + + Offsets = new SAV2Offsets(this); + Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C; + Initialize(); + } + + private void Initialize() + { + Box = Data.Length; + Array.Resize(ref Data, Data.Length + SIZE_RESERVED); + Party = GetPartyOffset(0); + + // Stash boxes after the save file's end. + int splitAtIndex = (Japanese ? 6 : 7); + int stored = SIZE_STOREDBOX; + int baseDest = Data.Length - SIZE_RESERVED; + var capacity = Japanese ? PokeListType.StoredJP : PokeListType.Stored; + for (int i = 0; i < BoxCount; i++) { - Version = versionOverride != GameVersion.Any ? versionOverride : SaveUtil.GetIsG2SAV(Data); - Japanese = SaveUtil.GetIsG2SAVJ(Data) != GameVersion.Invalid; - if (Version != GameVersion.C && !Japanese) - Korean = SaveUtil.GetIsG2SAVK(Data) != GameVersion.Invalid; - - Offsets = new SAV2Offsets(this); - Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C; - Initialize(); - } - - private void Initialize() - { - Box = Data.Length; - Array.Resize(ref Data, Data.Length + SIZE_RESERVED); - Party = GetPartyOffset(0); - - // Stash boxes after the save file's end. - int splitAtIndex = (Japanese ? 6 : 7); - int stored = SIZE_STOREDBOX; - int baseDest = Data.Length - SIZE_RESERVED; - var capacity = Japanese ? PokeListType.StoredJP : PokeListType.Stored; - for (int i = 0; i < BoxCount; i++) + int ofs = GetBoxRawDataOffset(i, splitAtIndex); + var box = GetData(ofs, stored); + var boxDest = baseDest + (i * SIZE_BOX); + var boxPL = new PokeList2(box, capacity, Japanese); + for (int j = 0; j < boxPL.Pokemon.Length; j++) { - int ofs = GetBoxRawDataOffset(i, splitAtIndex); - var box = GetData(ofs, stored); - var boxDest = baseDest + (i * SIZE_BOX); - var boxPL = new PokeList2(box, capacity, Japanese); - for (int j = 0; j < boxPL.Pokemon.Length; j++) - { - var dest = boxDest + (j * SIZE_STORED); - var pkDat = (j < boxPL.Count) - ? new PokeList2(boxPL[j]).Write() - : new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)]; - pkDat.CopyTo(Data, dest); - } - } - - var current = GetData(Offsets.CurrentBox, stored); - var curBoxPL = new PokeList2(current, capacity, Japanese); - var curDest = baseDest + (CurrentBox * SIZE_BOX); - for (int i = 0; i < curBoxPL.Pokemon.Length; i++) - { - var dest = curDest + (i * SIZE_STORED); - var pkDat = i < curBoxPL.Count - ? new PokeList2(curBoxPL[i]).Write() + var dest = boxDest + (j * SIZE_STORED); + var pkDat = (j < boxPL.Count) + ? new PokeList2(boxPL[j]).Write() : new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)]; pkDat.CopyTo(Data, dest); } - - var party = GetData(Offsets.Party, SIZE_STOREDPARTY); - var partyPL = new PokeList2(party, PokeListType.Party, Japanese); - for (int i = 0; i < partyPL.Pokemon.Length; i++) - { - var dest = GetPartyOffset(i); - var pkDat = i < partyPL.Count - ? new PokeList2(partyPL[i]).Write() - : new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)]; - pkDat.CopyTo(Data, dest); - } - - if (Offsets.Daycare >= 0) - { - int offset = Offsets.Daycare; - - DaycareFlags[0] = Data[offset]; - offset++; - var pk1 = ReadPKMFromOffset(offset); // parent 1 - var daycare1 = new PokeList2(pk1); - offset += (StringLength * 2) + 0x20; // nick/ot/pkm - DaycareFlags[1] = Data[offset]; - offset++; - //byte steps = Data[offset]; - offset++; - //byte BreedMotherOrNonDitto = Data[offset]; - offset++; - var pk2 = ReadPKMFromOffset(offset); // parent 2 - var daycare2 = new PokeList2(pk2); - offset += (StringLength * 2) + PokeCrypto.SIZE_2STORED; // nick/ot/pkm - var pk3 = ReadPKMFromOffset(offset); // egg! - pk3.IsEgg = true; - var daycare3 = new PokeList2(pk3); - - daycare1.Write().CopyTo(Data, GetPartyOffset(7 + (0 * 2))); - daycare2.Write().CopyTo(Data, GetPartyOffset(7 + (1 * 2))); - daycare3.Write().CopyTo(Data, GetPartyOffset(7 + (2 * 2))); - DaycareOffset = Offsets.Daycare; - } - - // Enable Pokedex editing - PokeDex = 0; } - private int EventFlag => Offsets.EventFlag; - private int EventWork => Offsets.EventWork; - - private PK2 ReadPKMFromOffset(int offset) + var current = GetData(Offsets.CurrentBox, stored); + var curBoxPL = new PokeList2(current, capacity, Japanese); + var curDest = baseDest + (CurrentBox * SIZE_BOX); + for (int i = 0; i < curBoxPL.Pokemon.Length; i++) { - var stringLength = StringLength; - var span = Data.AsSpan(offset); - - var pkData = span.Slice(stringLength * 2, PokeCrypto.SIZE_2STORED).ToArray(); - var pk = new PK2(pkData, jp: Japanese); - - var nick = span[..stringLength]; - var ot = span.Slice(stringLength, stringLength); - nick.CopyTo(pk.RawNickname); - ot.CopyTo(pk.RawOT); - - return pk; + var dest = curDest + (i * SIZE_STORED); + var pkDat = i < curBoxPL.Count + ? new PokeList2(curBoxPL[i]).Write() + : new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)]; + pkDat.CopyTo(Data, dest); } - private const int SIZE_RESERVED = 0x8000; // unpacked box data - private readonly SAV2Offsets Offsets; - - private int GetBoxRawDataOffset(int i, int splitAtIndex) + var party = GetData(Offsets.Party, SIZE_STOREDPARTY); + var partyPL = new PokeList2(party, PokeListType.Party, Japanese); + for (int i = 0; i < partyPL.Pokemon.Length; i++) { - if (i < splitAtIndex) - return 0x4000 + (i * (SIZE_STOREDBOX + 2)); - return 0x6000 + ((i - splitAtIndex) * (SIZE_STOREDBOX + 2)); + var dest = GetPartyOffset(i); + var pkDat = i < partyPL.Count + ? new PokeList2(partyPL[i]).Write() + : new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)]; + pkDat.CopyTo(Data, dest); } - protected override byte[] GetFinalData() + if (Offsets.Daycare >= 0) { - int splitAtIndex = (Japanese ? 6 : 7); - for (int i = 0; i < BoxCount; i++) - { - var boxPL = new PokeList2(Japanese ? PokeListType.StoredJP : PokeListType.Stored, Japanese); - int slot = 0; - for (int j = 0; j < boxPL.Pokemon.Length; j++) - { - PK2 boxPK = (PK2) GetPKM(GetData(GetBoxOffset(i) + (j * SIZE_STORED), SIZE_STORED)); - if (boxPK.Species > 0) - boxPL[slot++] = boxPK; - } + int offset = Offsets.Daycare; - int src = GetBoxRawDataOffset(i, splitAtIndex); - boxPL.Write().CopyTo(Data, src); - if (i == CurrentBox) - boxPL.Write().CopyTo(Data, Offsets.CurrentBox); - } + DaycareFlags[0] = Data[offset]; + offset++; + var pk1 = ReadPKMFromOffset(offset); // parent 1 + var daycare1 = new PokeList2(pk1); + offset += (StringLength * 2) + 0x20; // nick/ot/pk + DaycareFlags[1] = Data[offset]; + offset++; + //byte steps = Data[offset]; + offset++; + //byte BreedMotherOrNonDitto = Data[offset]; + offset++; + var pk2 = ReadPKMFromOffset(offset); // parent 2 + var daycare2 = new PokeList2(pk2); + offset += (StringLength * 2) + PokeCrypto.SIZE_2STORED; // nick/ot/pk + var pk3 = ReadPKMFromOffset(offset); // egg! + pk3.IsEgg = true; + var daycare3 = new PokeList2(pk3); - var partyPL = new PokeList2(PokeListType.Party, Japanese); - int pSlot = 0; - for (int i = 0; i < 6; i++) - { - PK2 partyPK = (PK2)GetPKM(GetData(GetPartyOffset(i), SIZE_STORED)); - if (partyPK.Species > 0) - partyPL[pSlot++] = partyPK; - } - partyPL.Write().CopyTo(Data, Offsets.Party); - - SetChecksums(); - if (Japanese) - { - switch (Version) - { - case GameVersion.GS: Data.AsSpan(Offsets.Trainer1, 0xC83).CopyTo(Data.AsSpan(0x7209)); break; - case GameVersion.C: Data.AsSpan(Offsets.Trainer1, 0xADA).CopyTo(Data.AsSpan(0x7209)); break; - } - } - else if (Korean) - { - // Calculate oddball checksum - ushort sum = 0; - Span<(ushort, ushort)> offsetpairs = stackalloc (ushort,ushort)[] - { - (0x106B, 0x1533), - (0x1534, 0x1A12), - (0x1A13, 0x1C38), - (0x3DD8, 0x3F79), - (0x7E39, 0x7E6A), - }; - foreach (var p in offsetpairs) - { - for (int i = p.Item1; i < p.Item2; i++) - sum += Data[i]; - } - WriteUInt16LittleEndian(Data.AsSpan(0x7E6B), sum); - } - else - { - switch (Version) - { - case GameVersion.GS: - Array.Copy(Data, 0x2009, Data, 0x15C7, 0x222F - 0x2009); - Array.Copy(Data, 0x222F, Data, 0x3D69, 0x23D9 - 0x222F); - Array.Copy(Data, 0x23D9, Data, 0x0C6B, 0x2856 - 0x23D9); - Array.Copy(Data, 0x2856, Data, 0x7E39, 0x288A - 0x2856); - Array.Copy(Data, 0x288A, Data, 0x10E8, 0x2D69 - 0x288A); - break; - case GameVersion.C: - Array.Copy(Data, 0x2009, Data, 0x1209, 0xB7A); - break; - } - } - byte[] outData = new byte[Data.Length - SIZE_RESERVED]; - Array.Copy(Data, outData, outData.Length); - return outData; + daycare1.Write().CopyTo(Data, GetPartyOffset(7 + (0 * 2))); + daycare2.Write().CopyTo(Data, GetPartyOffset(7 + (1 * 2))); + daycare3.Write().CopyTo(Data, GetPartyOffset(7 + (2 * 2))); + DaycareOffset = Offsets.Daycare; } - // Configuration - protected override SaveFile CloneInternal() => new SAV2(Write(), Version); + // Enable Pokedex editing + PokeDex = 0; + } - protected override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST; - protected override int SIZE_PARTY => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST; - public override PKM BlankPKM => new PK2(jp: Japanese); - public override Type PKMType => typeof(PK2); + private int EventFlag => Offsets.EventFlag; + private int EventWork => Offsets.EventWork; - private int SIZE_BOX => BoxSlotCount*SIZE_STORED; - private int SIZE_STOREDBOX => PokeList2.GetDataLength(Japanese ? PokeListType.StoredJP : PokeListType.Stored, Japanese); - private int SIZE_STOREDPARTY => PokeList2.GetDataLength(PokeListType.Party, Japanese); + private PK2 ReadPKMFromOffset(int offset) + { + var stringLength = StringLength; + var span = Data.AsSpan(offset); - public override int MaxMoveID => Legal.MaxMoveID_2; - public override int MaxSpeciesID => Legal.MaxSpeciesID_2; - public override int MaxAbilityID => Legal.MaxAbilityID_2; - public override int MaxItemID => Legal.MaxItemID_2; - public override int MaxBallID => 0; // unused - public override int MaxGameID => 99; // unused - public override int MaxMoney => 999999; - public override int MaxCoins => 9999; + var pkData = span.Slice(stringLength * 2, PokeCrypto.SIZE_2STORED).ToArray(); + var pk = new PK2(pkData, jp: Japanese); - public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGB(data); + var nick = span[..stringLength]; + var ot = span.Slice(stringLength, stringLength); + nick.CopyTo(pk.RawNickname); + ot.CopyTo(pk.RawOT); - public int EventWorkCount => 0x100; - public int EventFlagCount => 2000; + return pk; + } - public override int BoxCount => Japanese ? 9 : 14; - public override int MaxEV => 65535; - public override int MaxIV => 15; - public override int Generation => 2; - public override EntityContext Context => EntityContext.Gen2; - protected override int GiftCountMax => 0; - public override int OTLength => Japanese || Korean ? 5 : 7; - public override int NickLength => Japanese || Korean ? 5 : 10; - public override int BoxSlotCount => Japanese ? 30 : 20; + private const int SIZE_RESERVED = 0x8000; // unpacked box data + private readonly SAV2Offsets Offsets; - public override bool HasParty => true; - public override bool HasNamableBoxes => true; - private int StringLength => Japanese ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan; + private int GetBoxRawDataOffset(int i, int splitAtIndex) + { + if (i < splitAtIndex) + return 0x4000 + (i * (SIZE_STOREDBOX + 2)); + return 0x6000 + ((i - splitAtIndex) * (SIZE_STOREDBOX + 2)); + } - // Checksums - private ushort GetChecksum() + protected override byte[] GetFinalData() + { + int splitAtIndex = (Japanese ? 6 : 7); + for (int i = 0; i < BoxCount; i++) { + var boxPL = new PokeList2(Japanese ? PokeListType.StoredJP : PokeListType.Stored, Japanese); + int slot = 0; + for (int j = 0; j < boxPL.Pokemon.Length; j++) + { + PK2 boxPK = (PK2) GetPKM(GetData(GetBoxOffset(i) + (j * SIZE_STORED), SIZE_STORED)); + if (boxPK.Species > 0) + boxPL[slot++] = boxPK; + } + + int src = GetBoxRawDataOffset(i, splitAtIndex); + boxPL.Write().CopyTo(Data, src); + if (i == CurrentBox) + boxPL.Write().CopyTo(Data, Offsets.CurrentBox); + } + + var partyPL = new PokeList2(PokeListType.Party, Japanese); + int pSlot = 0; + for (int i = 0; i < 6; i++) + { + PK2 partyPK = (PK2)GetPKM(GetData(GetPartyOffset(i), SIZE_STORED)); + if (partyPK.Species > 0) + partyPL[pSlot++] = partyPK; + } + partyPL.Write().CopyTo(Data, Offsets.Party); + + SetChecksums(); + if (Japanese) + { + switch (Version) + { + case GameVersion.GS: Data.AsSpan(Offsets.Trainer1, 0xC83).CopyTo(Data.AsSpan(0x7209)); break; + case GameVersion.C: Data.AsSpan(Offsets.Trainer1, 0xADA).CopyTo(Data.AsSpan(0x7209)); break; + } + } + else if (Korean) + { + // Calculate oddball checksum ushort sum = 0; - for (int i = Offsets.Trainer1; i <= Offsets.AccumulatedChecksumEnd; i++) - sum += Data[i]; - return sum; + Span<(ushort, ushort)> offsetpairs = stackalloc (ushort,ushort)[] + { + (0x106B, 0x1533), + (0x1534, 0x1A12), + (0x1A13, 0x1C38), + (0x3DD8, 0x3F79), + (0x7E39, 0x7E6A), + }; + foreach (var p in offsetpairs) + { + for (int i = p.Item1; i < p.Item2; i++) + sum += Data[i]; + } + WriteUInt16LittleEndian(Data.AsSpan(0x7E6B), sum); } + else + { + switch (Version) + { + case GameVersion.GS: + Array.Copy(Data, 0x2009, Data, 0x15C7, 0x222F - 0x2009); + Array.Copy(Data, 0x222F, Data, 0x3D69, 0x23D9 - 0x222F); + Array.Copy(Data, 0x23D9, Data, 0x0C6B, 0x2856 - 0x23D9); + Array.Copy(Data, 0x2856, Data, 0x7E39, 0x288A - 0x2856); + Array.Copy(Data, 0x288A, Data, 0x10E8, 0x2D69 - 0x288A); + break; + case GameVersion.C: + Array.Copy(Data, 0x2009, Data, 0x1209, 0xB7A); + break; + } + } + byte[] outData = new byte[Data.Length - SIZE_RESERVED]; + Array.Copy(Data, outData, outData.Length); + return outData; + } - protected override void SetChecksums() + // Configuration + protected override SaveFile CloneInternal() => new SAV2(Write(), Version); + + protected override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST; + protected override int SIZE_PARTY => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST; + public override PKM BlankPKM => new PK2(jp: Japanese); + public override Type PKMType => typeof(PK2); + + private int SIZE_BOX => BoxSlotCount*SIZE_STORED; + private int SIZE_STOREDBOX => PokeList2.GetDataLength(Japanese ? PokeListType.StoredJP : PokeListType.Stored, Japanese); + private int SIZE_STOREDPARTY => PokeList2.GetDataLength(PokeListType.Party, Japanese); + + public override int MaxMoveID => Legal.MaxMoveID_2; + public override int MaxSpeciesID => Legal.MaxSpeciesID_2; + public override int MaxAbilityID => Legal.MaxAbilityID_2; + public override int MaxItemID => Legal.MaxItemID_2; + public override int MaxBallID => 0; // unused + public override int MaxGameID => 99; // unused + public override int MaxMoney => 999999; + public override int MaxCoins => 9999; + + public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGB(data); + + public int EventWorkCount => 0x100; + public int EventFlagCount => 2000; + + public override int BoxCount => Japanese ? 9 : 14; + public override int MaxEV => 65535; + public override int MaxIV => 15; + public override int Generation => 2; + public override EntityContext Context => EntityContext.Gen2; + protected override int GiftCountMax => 0; + public override int OTLength => Japanese || Korean ? 5 : 7; + public override int NickLength => Japanese || Korean ? 5 : 10; + public override int BoxSlotCount => Japanese ? 30 : 20; + + public override bool HasParty => true; + public override bool HasNamableBoxes => true; + private int StringLength => Japanese ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan; + + // Checksums + private ushort GetChecksum() + { + ushort sum = 0; + for (int i = Offsets.Trainer1; i <= Offsets.AccumulatedChecksumEnd; i++) + sum += Data[i]; + return sum; + } + + protected override void SetChecksums() + { + ushort accum = GetChecksum(); + WriteUInt16LittleEndian(Data.AsSpan(Offsets.OverallChecksumPosition), accum); + WriteUInt16LittleEndian(Data.AsSpan(Offsets.OverallChecksumPosition2), accum); + } + + public override bool ChecksumsValid + { + get { ushort accum = GetChecksum(); - WriteUInt16LittleEndian(Data.AsSpan(Offsets.OverallChecksumPosition), accum); - WriteUInt16LittleEndian(Data.AsSpan(Offsets.OverallChecksumPosition2), accum); - } - - public override bool ChecksumsValid - { - get - { - ushort accum = GetChecksum(); - ushort actual = ReadUInt16LittleEndian(Data.AsSpan(Offsets.OverallChecksumPosition)); - return accum == actual; - } - } - - public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid"; - - // Trainer Info - public override GameVersion Version { get; protected set; } - - public override string OT - { - get => GetString(Offsets.Trainer1 + 2, (Korean ? 2 : 1) * OTLength); - set => SetString(Data.AsSpan(Offsets.Trainer1 + 2, (Korean ? 2 : 1) * OTLength), value.AsSpan(), 8, StringConverterOption.Clear50); - } - - public Span OT_Trash - { - get => Data.AsSpan(Offsets.Trainer1 + 2, StringLength); - set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Trainer1 + 2)); } - } - - public string Rival - { - get => GetString(Offsets.Rival, (Korean ? 2 : 1) * OTLength); - set => SetString(Data.AsSpan(Offsets.Rival, (Korean ? 2 : 1) * OTLength), value.AsSpan(), 8, StringConverterOption.Clear50); - } - - public Span Rival_Trash - { - get => Data.AsSpan(Offsets.Rival, StringLength); - set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Rival)); } - } - - public override int Gender - { - get => Version == GameVersion.C ? Data[Offsets.Gender] : 0; - set - { - if (Version != GameVersion.C) - return; - Data[Offsets.Gender] = (byte) value; - Data[Offsets.Palette] = (byte) value; - } - } - - public override int TID - { - get => ReadUInt16BigEndian(Data.AsSpan(Offsets.Trainer1)); - set => WriteUInt16BigEndian(Data.AsSpan(Offsets.Trainer1), (ushort)value); - } - - public override int SID { get => 0; set { } } - - public override int PlayedHours - { - get => ReadUInt16BigEndian(Data.AsSpan(Offsets.TimePlayed)); - set => WriteUInt16BigEndian(Data.AsSpan(Offsets.TimePlayed), (ushort)value); - } - - public override int PlayedMinutes - { - get => Data[Offsets.TimePlayed + 2]; - set => Data[Offsets.TimePlayed + 2] = (byte)value; - } - - public override int PlayedSeconds - { - get => Data[Offsets.TimePlayed + 3]; - set => Data[Offsets.TimePlayed + 3] = (byte)value; - } - - public int Badges - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offsets.JohtoBadges)); - set { if (value < 0) return; WriteUInt16LittleEndian(Data.AsSpan(Offsets.JohtoBadges), (ushort)value); } - } - - private byte Options - { - get => Data[Offsets.Options]; - set => Data[Offsets.Options] = value; - } - - public bool BattleEffects - { - get => (Options & 0x80) == 0; - set => Options = (byte)((Options & 0x7F) | (value ? 0 : 0x80)); - } - - public bool BattleStyleSwitch - { - get => (Options & 0x40) == 0; - set => Options = (byte)((Options & 0xBF) | (value ? 0 : 0x40)); - } - - public int Sound - { - get => (Options & 0x30) >> 4; - set => Options = (byte)((Options & 0xCF) | ((value != 0 ? 2 : 0) << 4)); // Stereo 2, Mono 0 - } - - public int TextSpeed - { - get => Options & 0x7; - set => Options = (byte)((Options & 0xF8) | (value & 7)); - } - - public bool SaveFileExists - { - get => Data[Offsets.Options + 1] == 1; - set => Data[Offsets.Options + 1] = value ? (byte)1 : (byte)0; - } - - public int TextBoxFrame // 3bits - { - get => Data[Offsets.Options + 2] & 0b0000_0111; - set => Data[Offsets.Options + 2] = (byte)((Data[Offsets.Options + 2] & 0b1111_1000) | (value & 0b0000_0111)); - } - - public int TextBoxFlags { get => Data[Offsets.Options + 3]; set => Data[Offsets.Options + 3] = (byte)value; } - - public bool TextBoxFrameDelay1 // bit 0 - { - get => (TextBoxFlags & 0x01) == 0x01; - set => TextBoxFlags = (TextBoxFlags & ~0x01) | (value ? 0x01 : 0); - } - - public bool TextBoxFrameDelayNone // bit 4 - { - get => (TextBoxFlags & 0x10) == 0x10; - set => TextBoxFlags = (TextBoxFlags & ~0x10) | (value ? 0x10 : 0); - } - - public byte GBPrinterBrightness { get => Data[Offsets.Options + 4]; set => Data[Offsets.Options + 4] = value; } - - public bool MenuAccountOn - { - get => Data[Offsets.Options + 5] == 1; - set => Data[Offsets.Options + 5] = value ? (byte)1 : (byte)0; - } - - // 3 bytes - public override uint Money - { - get => ReadUInt32BigEndian(Data.AsSpan(Offsets.Money)) >> 8; - set - { - var clamp = (uint)Math.Min(value, MaxMoney); - var toWrite = (clamp << 8) | Data[Offsets.Money + 3]; - WriteUInt32BigEndian(Data.AsSpan(Offsets.Money), toWrite); - } - } - - public uint Coin - { - get => ReadUInt16BigEndian(Data.AsSpan(Offsets.Money + 7)); - set - { - var clamped = (ushort)Math.Min(value, MaxCoins); - WriteUInt16BigEndian(Data.AsSpan(Offsets.Money + 7), clamped); - } - } - - public byte BlueCardPoints - { - get - { - int ofs = Offsets.BlueCardPoints; - if (ofs == -1) - return 0; - return Data[ofs]; - } - set - { - int ofs = Offsets.BlueCardPoints; - if (ofs == -1) - return; - Data[ofs] = value; - } - } - - private static ushort[] LegalItems => Legal.Pouch_Items_GSC; - private ushort[] LegalKeyItems => Version == GameVersion.C? Legal.Pouch_Key_C : Legal.Pouch_Key_GS; - private static ushort[] LegalBalls => Legal.Pouch_Ball_GSC; - private static ushort[] LegalTMHMs => Legal.Pouch_TMHM_GSC; - - public override IReadOnlyList Inventory - { - get - { - InventoryPouch[] pouch = - { - new InventoryPouchGB(InventoryType.TMHMs, LegalTMHMs, 99, Offsets.PouchTMHM, 57), - new InventoryPouchGB(InventoryType.Items, LegalItems, 99, Offsets.PouchItem, 20), - new InventoryPouchGB(InventoryType.KeyItems, LegalKeyItems, 99, Offsets.PouchKey, 26), - new InventoryPouchGB(InventoryType.Balls, LegalBalls, 99, Offsets.PouchBall, 12), - new InventoryPouchGB(InventoryType.PCItems, ArrayUtil.ConcatAll(LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs), 99, Offsets.PouchPC, 50), - }; - return pouch.LoadAll(Data); - } - set => value.SaveAll(Data); - } - - private readonly byte[] DaycareFlags = new byte[2]; - public override int GetDaycareSlotOffset(int loc, int slot) => GetPartyOffset(7 + (slot * 2)); - public override uint? GetDaycareEXP(int loc, int slot) => null; - public override bool? IsDaycareOccupied(int loc, int slot) => (DaycareFlags[slot] & 1) != 0; - public override void SetDaycareEXP(int loc, int slot, uint EXP) { } - public override void SetDaycareOccupied(int loc, int slot, bool occupied) { } - - // Storage - public override int PartyCount - { - get => Data[Offsets.Party]; protected set => Data[Offsets.Party] = (byte)value; - } - - public override int GetBoxOffset(int box) - { - return Data.Length - SIZE_RESERVED + (box * SIZE_BOX); - } - - public override int GetPartyOffset(int slot) - { - return Data.Length - SIZE_RESERVED + (BoxCount * SIZE_BOX) + (slot * SIZE_STORED); - } - - public override int CurrentBox - { - get => Data[Offsets.CurrentBoxIndex] & 0x7F; - set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x80) | (value & 0x7F)); - } - - public bool CurrentBoxChanged - { - get => (Data[Offsets.CurrentBoxIndex] & 0x80) != 0; - set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x7F) | (byte)(value ? 0x80 : 0)); - } - - public override string GetBoxName(int box) - { - int len = Korean ? 17 : 9; - return GetString(Offsets.BoxNames + (box * len), len); - } - - public override void SetBoxName(int box, string value) - { - int len = Korean ? 17 : 9; - var span = Data.AsSpan(Offsets.BoxNames + (box * len), len); - SetString(span, value.AsSpan(), 8, StringConverterOption.Clear50); - } - - protected override PKM GetPKM(byte[] data) - { - if (data.Length == SIZE_STORED) - return new PokeList2(data, PokeListType.Single, Japanese)[0]; - return new PK2(data); - } - - protected override byte[] DecryptPKM(byte[] data) - { - return data; - } - - // Pokédex - protected override void SetDex(PKM pkm) - { - int species = pkm.Species; - if (species is 0 or > Legal.MaxSpeciesID_2) - return; - if (pkm.IsEgg) - return; - - SetCaught(pkm.Species, true); - SetSeen(pkm.Species, true); - } - - private void SetUnownFormFlags() - { - // Give all Unown caught to prevent a crash on pokedex view - for (int i = 1; i <= 26; i++) - Data[Offsets.PokedexSeen + 0x1F + i] = (byte)i; - if (UnownFirstSeen == 0) // Invalid - UnownFirstSeen = 1; // A - } - - /// - /// Toggles the availability of Unown letter groups in the Wild - /// - /// - /// Max value of 0x0F, 4 bitflags - /// 1 lsh 0: A, B, C, D, E, F, G, H, I, J, K - /// 1 lsh 1: L, M, N, O, P, Q, R - /// 1 lsh 2: S, T, U, V, W - /// 1 lsh 3: X, Y, Z - /// - public int UnownUnlocked - { - get => Data[Offsets.PokedexSeen + 0x1F + 27]; - set => Data[Offsets.PokedexSeen + 0x1F + 27] = (byte)value; - } - - /// - /// Unlocks all Unown letters/forms in the wild. - /// - public void UnownUnlockAll() => UnownUnlocked = 0x0F; // all 4 bitflags - - /// - /// Flag that determines if Unown Letters are available in the wild: A, B, C, D, E, F, G, H, I, J, K - /// - public bool UnownUnlocked0 - { - get => (UnownUnlocked & 1 << 0) == 1 << 0; - set => UnownUnlocked = (UnownUnlocked & ~(1 << 0)) | ((value ? 1 : 0) << 0); - } - - /// - /// Flag that determines if Unown Letters are available in the wild: L, M, N, O, P, Q, R - /// - public bool UnownUnlocked1 - { - get => (UnownUnlocked & 1 << 1) == 1 << 1; - set => UnownUnlocked = (UnownUnlocked & ~(1 << 1)) | ((value ? 1 : 0) << 1); - } - - /// - /// Flag that determines if Unown Letters are available in the wild: S, T, U, V, W - /// - public bool UnownUnlocked2 - { - get => (UnownUnlocked & 1 << 2) == 1 << 2; - set => UnownUnlocked = (UnownUnlocked & ~(1 << 2)) | ((value ? 1 : 0) << 2); - } - - /// - /// Flag that determines if Unown Letters are available in the wild: X, Y, Z - /// - public bool UnownUnlocked3 - { - get => (UnownUnlocked & 1 << 3) == 1 << 3; - set => UnownUnlocked = (UnownUnlocked & ~(1 << 3)) | ((value ? 1 : 0) << 3); - } - - /// - /// Chooses which Unown sprite to show in the regular Pokédex View - /// - public int UnownFirstSeen - { - get => Data[Offsets.PokedexSeen + 0x1F + 28]; - set => Data[Offsets.PokedexSeen + 0x1F + 28] = (byte)value; - } - - public override bool GetSeen(int species) => GetDexFlag(Offsets.PokedexSeen, species); - public override bool GetCaught(int species) => GetDexFlag(Offsets.PokedexCaught, species); - public override void SetSeen(int species, bool seen) => SetDexFlag(Offsets.PokedexSeen, species, seen); - - public override void SetCaught(int species, bool caught) - { - SetDexFlag(Offsets.PokedexCaught, species, caught); - if (caught && species == (int)Species.Unown) - SetUnownFormFlags(); - } - - private bool GetDexFlag(int region, int species) - { - int bit = species - 1; - int ofs = bit >> 3; - return GetFlag(region + ofs, bit & 7); - } - - private void SetDexFlag(int region, int species, bool value) - { - int bit = species - 1; - int ofs = bit >> 3; - SetFlag(region + ofs, bit & 7, value); - } - - public byte GetWork(int index) => Data[EventWork + index]; - public void SetWork(int index, byte value) => Data[EventWork + index] = value; - public bool GetEventFlag(int flagNumber) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); - return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); - } - - public void SetEventFlag(int flagNumber, bool value) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); - SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); - } - - // Misc - public ushort ResetKey => GetResetKey(); - - private ushort GetResetKey() - { - var value = (TID >> 8) + (TID & 0xFF) + ((Money >> 16) & 0xFF) + ((Money >> 8) & 0xFF) + (Money & 0xFF); - var ot = Data.AsSpan(Offsets.Trainer1 + 2, 5); - var sum = 0; - foreach (var b in ot) - { - if (b == StringConverter12.G1TerminatorCode) - break; - sum += b; - } - return (ushort)(value + sum); - } - - /// - /// Sets the "Time Not Set" flag to the RTC Flag list. - /// - public void ResetRTC() => Data[Offsets.RTCFlags] |= 0x80; - - public void UnlockAllDecorations() - { - for (int i = 676; i <= 721; i++) - SetEventFlag(i, true); - } - - public override string GetString(ReadOnlySpan data) - { - if (Korean) - return StringConverter2KOR.GetString(data); - return StringConverter12.GetString(data, Japanese); - } - - public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - if (Korean) - return StringConverter2KOR.SetString(destBuffer, value, maxLength, option); - return StringConverter12.SetString(destBuffer, value, maxLength, Japanese, option); - } - - public bool IsGBMobileAvailable => Japanese && Version == GameVersion.C; - public bool IsGBMobileEnabled => Japanese && Enum.IsDefined(typeof(GBMobileCableColor), GBMobileCable); - - public GBMobileCableColor GBMobileCable - { - get => (GBMobileCableColor) Data[0xE800]; - set - { - Data[0xE800] = (byte)value; - Data[0x9000] = (byte)(0xFF - value); - } + ushort actual = ReadUInt16LittleEndian(Data.AsSpan(Offsets.OverallChecksumPosition)); + return accum == actual; } } - public enum GBMobileCableColor : byte + public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid"; + + // Trainer Info + public override GameVersion Version { get; protected set; } + + public override string OT { - None = 0, - Blue = 1, - Yellow = 2, - Green = 3, - Red = 4, - Purple = 5, - Black = 6, - Pink = 7, - Gray = 8, - Debug = 0x81, - Disabled = 0xFF, + get => GetString(Offsets.Trainer1 + 2, (Korean ? 2 : 1) * OTLength); + set => SetString(Data.AsSpan(Offsets.Trainer1 + 2, (Korean ? 2 : 1) * OTLength), value.AsSpan(), 8, StringConverterOption.Clear50); + } + + public Span OT_Trash + { + get => Data.AsSpan(Offsets.Trainer1 + 2, StringLength); + set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Trainer1 + 2)); } + } + + public string Rival + { + get => GetString(Offsets.Rival, (Korean ? 2 : 1) * OTLength); + set => SetString(Data.AsSpan(Offsets.Rival, (Korean ? 2 : 1) * OTLength), value.AsSpan(), 8, StringConverterOption.Clear50); + } + + public Span Rival_Trash + { + get => Data.AsSpan(Offsets.Rival, StringLength); + set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Rival)); } + } + + public override int Gender + { + get => Version == GameVersion.C ? Data[Offsets.Gender] : 0; + set + { + if (Version != GameVersion.C) + return; + Data[Offsets.Gender] = (byte) value; + Data[Offsets.Palette] = (byte) value; + } + } + + public override int TID + { + get => ReadUInt16BigEndian(Data.AsSpan(Offsets.Trainer1)); + set => WriteUInt16BigEndian(Data.AsSpan(Offsets.Trainer1), (ushort)value); + } + + public override int SID { get => 0; set { } } + + public override int PlayedHours + { + get => ReadUInt16BigEndian(Data.AsSpan(Offsets.TimePlayed)); + set => WriteUInt16BigEndian(Data.AsSpan(Offsets.TimePlayed), (ushort)value); + } + + public override int PlayedMinutes + { + get => Data[Offsets.TimePlayed + 2]; + set => Data[Offsets.TimePlayed + 2] = (byte)value; + } + + public override int PlayedSeconds + { + get => Data[Offsets.TimePlayed + 3]; + set => Data[Offsets.TimePlayed + 3] = (byte)value; + } + + public int Badges + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offsets.JohtoBadges)); + set { if (value < 0) return; WriteUInt16LittleEndian(Data.AsSpan(Offsets.JohtoBadges), (ushort)value); } + } + + private byte Options + { + get => Data[Offsets.Options]; + set => Data[Offsets.Options] = value; + } + + public bool BattleEffects + { + get => (Options & 0x80) == 0; + set => Options = (byte)((Options & 0x7F) | (value ? 0 : 0x80)); + } + + public bool BattleStyleSwitch + { + get => (Options & 0x40) == 0; + set => Options = (byte)((Options & 0xBF) | (value ? 0 : 0x40)); + } + + public int Sound + { + get => (Options & 0x30) >> 4; + set => Options = (byte)((Options & 0xCF) | ((value != 0 ? 2 : 0) << 4)); // Stereo 2, Mono 0 + } + + public int TextSpeed + { + get => Options & 0x7; + set => Options = (byte)((Options & 0xF8) | (value & 7)); + } + + public bool SaveFileExists + { + get => Data[Offsets.Options + 1] == 1; + set => Data[Offsets.Options + 1] = value ? (byte)1 : (byte)0; + } + + public int TextBoxFrame // 3bits + { + get => Data[Offsets.Options + 2] & 0b0000_0111; + set => Data[Offsets.Options + 2] = (byte)((Data[Offsets.Options + 2] & 0b1111_1000) | (value & 0b0000_0111)); + } + + public int TextBoxFlags { get => Data[Offsets.Options + 3]; set => Data[Offsets.Options + 3] = (byte)value; } + + public bool TextBoxFrameDelay1 // bit 0 + { + get => (TextBoxFlags & 0x01) == 0x01; + set => TextBoxFlags = (TextBoxFlags & ~0x01) | (value ? 0x01 : 0); + } + + public bool TextBoxFrameDelayNone // bit 4 + { + get => (TextBoxFlags & 0x10) == 0x10; + set => TextBoxFlags = (TextBoxFlags & ~0x10) | (value ? 0x10 : 0); + } + + public byte GBPrinterBrightness { get => Data[Offsets.Options + 4]; set => Data[Offsets.Options + 4] = value; } + + public bool MenuAccountOn + { + get => Data[Offsets.Options + 5] == 1; + set => Data[Offsets.Options + 5] = value ? (byte)1 : (byte)0; + } + + // 3 bytes + public override uint Money + { + get => ReadUInt32BigEndian(Data.AsSpan(Offsets.Money)) >> 8; + set + { + var clamp = (uint)Math.Min(value, MaxMoney); + var toWrite = (clamp << 8) | Data[Offsets.Money + 3]; + WriteUInt32BigEndian(Data.AsSpan(Offsets.Money), toWrite); + } + } + + public uint Coin + { + get => ReadUInt16BigEndian(Data.AsSpan(Offsets.Money + 7)); + set + { + var clamped = (ushort)Math.Min(value, MaxCoins); + WriteUInt16BigEndian(Data.AsSpan(Offsets.Money + 7), clamped); + } + } + + public byte BlueCardPoints + { + get + { + int ofs = Offsets.BlueCardPoints; + if (ofs == -1) + return 0; + return Data[ofs]; + } + set + { + int ofs = Offsets.BlueCardPoints; + if (ofs == -1) + return; + Data[ofs] = value; + } + } + + private static ushort[] LegalItems => Legal.Pouch_Items_GSC; + private ushort[] LegalKeyItems => Version == GameVersion.C? Legal.Pouch_Key_C : Legal.Pouch_Key_GS; + private static ushort[] LegalBalls => Legal.Pouch_Ball_GSC; + private static ushort[] LegalTMHMs => Legal.Pouch_TMHM_GSC; + + public override IReadOnlyList Inventory + { + get + { + InventoryPouch[] pouch = + { + new InventoryPouchGB(InventoryType.TMHMs, LegalTMHMs, 99, Offsets.PouchTMHM, 57), + new InventoryPouchGB(InventoryType.Items, LegalItems, 99, Offsets.PouchItem, 20), + new InventoryPouchGB(InventoryType.KeyItems, LegalKeyItems, 99, Offsets.PouchKey, 26), + new InventoryPouchGB(InventoryType.Balls, LegalBalls, 99, Offsets.PouchBall, 12), + new InventoryPouchGB(InventoryType.PCItems, ArrayUtil.ConcatAll(LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs), 99, Offsets.PouchPC, 50), + }; + return pouch.LoadAll(Data); + } + set => value.SaveAll(Data); + } + + private readonly byte[] DaycareFlags = new byte[2]; + public override int GetDaycareSlotOffset(int loc, int slot) => GetPartyOffset(7 + (slot * 2)); + public override uint? GetDaycareEXP(int loc, int slot) => null; + public override bool? IsDaycareOccupied(int loc, int slot) => (DaycareFlags[slot] & 1) != 0; + public override void SetDaycareEXP(int loc, int slot, uint EXP) { } + public override void SetDaycareOccupied(int loc, int slot, bool occupied) { } + + // Storage + public override int PartyCount + { + get => Data[Offsets.Party]; protected set => Data[Offsets.Party] = (byte)value; + } + + public override int GetBoxOffset(int box) + { + return Data.Length - SIZE_RESERVED + (box * SIZE_BOX); + } + + public override int GetPartyOffset(int slot) + { + return Data.Length - SIZE_RESERVED + (BoxCount * SIZE_BOX) + (slot * SIZE_STORED); + } + + public override int CurrentBox + { + get => Data[Offsets.CurrentBoxIndex] & 0x7F; + set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x80) | (value & 0x7F)); + } + + public bool CurrentBoxChanged + { + get => (Data[Offsets.CurrentBoxIndex] & 0x80) != 0; + set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.CurrentBoxIndex] & 0x7F) | (byte)(value ? 0x80 : 0)); + } + + public override string GetBoxName(int box) + { + int len = Korean ? 17 : 9; + return GetString(Offsets.BoxNames + (box * len), len); + } + + public override void SetBoxName(int box, string value) + { + int len = Korean ? 17 : 9; + var span = Data.AsSpan(Offsets.BoxNames + (box * len), len); + SetString(span, value.AsSpan(), 8, StringConverterOption.Clear50); + } + + protected override PKM GetPKM(byte[] data) + { + if (data.Length == SIZE_STORED) + return new PokeList2(data, PokeListType.Single, Japanese)[0]; + return new PK2(data); + } + + protected override byte[] DecryptPKM(byte[] data) + { + return data; + } + + // Pokédex + protected override void SetDex(PKM pk) + { + int species = pk.Species; + if (species is 0 or > Legal.MaxSpeciesID_2) + return; + if (pk.IsEgg) + return; + + SetCaught(pk.Species, true); + SetSeen(pk.Species, true); + } + + private void SetUnownFormFlags() + { + // Give all Unown caught to prevent a crash on pokedex view + for (int i = 1; i <= 26; i++) + Data[Offsets.PokedexSeen + 0x1F + i] = (byte)i; + if (UnownFirstSeen == 0) // Invalid + UnownFirstSeen = 1; // A + } + + /// + /// Toggles the availability of Unown letter groups in the Wild + /// + /// + /// Max value of 0x0F, 4 bitflags + /// 1 lsh 0: A, B, C, D, E, F, G, H, I, J, K + /// 1 lsh 1: L, M, N, O, P, Q, R + /// 1 lsh 2: S, T, U, V, W + /// 1 lsh 3: X, Y, Z + /// + public int UnownUnlocked + { + get => Data[Offsets.PokedexSeen + 0x1F + 27]; + set => Data[Offsets.PokedexSeen + 0x1F + 27] = (byte)value; + } + + /// + /// Unlocks all Unown letters/forms in the wild. + /// + public void UnownUnlockAll() => UnownUnlocked = 0x0F; // all 4 bitflags + + /// + /// Flag that determines if Unown Letters are available in the wild: A, B, C, D, E, F, G, H, I, J, K + /// + public bool UnownUnlocked0 + { + get => (UnownUnlocked & (1 << 0)) == 1 << 0; + set => UnownUnlocked = (UnownUnlocked & ~(1 << 0)) | ((value ? 1 : 0) << 0); + } + + /// + /// Flag that determines if Unown Letters are available in the wild: L, M, N, O, P, Q, R + /// + public bool UnownUnlocked1 + { + get => (UnownUnlocked & (1 << 1)) == 1 << 1; + set => UnownUnlocked = (UnownUnlocked & ~(1 << 1)) | ((value ? 1 : 0) << 1); + } + + /// + /// Flag that determines if Unown Letters are available in the wild: S, T, U, V, W + /// + public bool UnownUnlocked2 + { + get => (UnownUnlocked & (1 << 2)) == 1 << 2; + set => UnownUnlocked = (UnownUnlocked & ~(1 << 2)) | ((value ? 1 : 0) << 2); + } + + /// + /// Flag that determines if Unown Letters are available in the wild: X, Y, Z + /// + public bool UnownUnlocked3 + { + get => (UnownUnlocked & (1 << 3)) == 1 << 3; + set => UnownUnlocked = (UnownUnlocked & ~(1 << 3)) | ((value ? 1 : 0) << 3); + } + + /// + /// Chooses which Unown sprite to show in the regular Pokédex View + /// + public int UnownFirstSeen + { + get => Data[Offsets.PokedexSeen + 0x1F + 28]; + set => Data[Offsets.PokedexSeen + 0x1F + 28] = (byte)value; + } + + public override bool GetSeen(int species) => GetDexFlag(Offsets.PokedexSeen, species); + public override bool GetCaught(int species) => GetDexFlag(Offsets.PokedexCaught, species); + public override void SetSeen(int species, bool seen) => SetDexFlag(Offsets.PokedexSeen, species, seen); + + public override void SetCaught(int species, bool caught) + { + SetDexFlag(Offsets.PokedexCaught, species, caught); + if (caught && species == (int)Species.Unown) + SetUnownFormFlags(); + } + + private bool GetDexFlag(int region, int species) + { + int bit = species - 1; + int ofs = bit >> 3; + return GetFlag(region + ofs, bit & 7); + } + + private void SetDexFlag(int region, int species, bool value) + { + int bit = species - 1; + int ofs = bit >> 3; + SetFlag(region + ofs, bit & 7, value); + } + + public byte GetWork(int index) => Data[EventWork + index]; + public void SetWork(int index, byte value) => Data[EventWork + index] = value; + public bool GetEventFlag(int flagNumber) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); + return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); + } + + public void SetEventFlag(int flagNumber, bool value) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); + SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); + } + + // Misc + public ushort ResetKey => GetResetKey(); + + private ushort GetResetKey() + { + var value = (TID >> 8) + (TID & 0xFF) + ((Money >> 16) & 0xFF) + ((Money >> 8) & 0xFF) + (Money & 0xFF); + var ot = Data.AsSpan(Offsets.Trainer1 + 2, 5); + var sum = 0; + foreach (var b in ot) + { + if (b == StringConverter12.G1TerminatorCode) + break; + sum += b; + } + return (ushort)(value + sum); + } + + /// + /// Sets the "Time Not Set" flag to the RTC Flag list. + /// + public void ResetRTC() => Data[Offsets.RTCFlags] |= 0x80; + + public void UnlockAllDecorations() + { + for (int i = 676; i <= 721; i++) + SetEventFlag(i, true); + } + + public override string GetString(ReadOnlySpan data) + { + if (Korean) + return StringConverter2KOR.GetString(data); + return StringConverter12.GetString(data, Japanese); + } + + public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + if (Korean) + return StringConverter2KOR.SetString(destBuffer, value, maxLength, option); + return StringConverter12.SetString(destBuffer, value, maxLength, Japanese, option); + } + + public bool IsGBMobileAvailable => Japanese && Version == GameVersion.C; + public bool IsGBMobileEnabled => Japanese && Enum.IsDefined(typeof(GBMobileCableColor), GBMobileCable); + + public GBMobileCableColor GBMobileCable + { + get => (GBMobileCableColor) Data[0xE800]; + set + { + Data[0xE800] = (byte)value; + Data[0x9000] = (byte)(0xFF - value); + } } } + +public enum GBMobileCableColor : byte +{ + None = 0, + Blue = 1, + Yellow = 2, + Green = 3, + Red = 4, + Purple = 5, + Black = 6, + Pink = 7, + Gray = 8, + Debug = 0x81, + Disabled = 0xFF, +} diff --git a/PKHeX.Core/Saves/SAV2Stadium.cs b/PKHeX.Core/Saves/SAV2Stadium.cs index 53eedb81b..e13d8ca23 100644 --- a/PKHeX.Core/Saves/SAV2Stadium.cs +++ b/PKHeX.Core/Saves/SAV2Stadium.cs @@ -2,212 +2,211 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokémon Stadium 2 (Pokémon Stadium GS in Japan) +/// +public sealed class SAV2Stadium : SAV_STADIUM { - /// - /// Pokémon Stadium 2 (Pokémon Stadium GS in Japan) - /// - public sealed class SAV2Stadium : SAV_STADIUM + public override int SaveRevision => Japanese ? 0 : 1; + public override string SaveRevisionString => Japanese ? "J" : "U"; + + public override PersonalTable Personal => PersonalTable.C; + public override int MaxEV => ushort.MaxValue; + public override IReadOnlyList HeldItems => Legal.HeldItems_GSC; + public override GameVersion Version { get; protected set; } = GameVersion.Stadium2; + + protected override SaveFile CloneInternal() => new SAV2Stadium((byte[])Data.Clone(), Japanese); + + public override int Generation => 2; + public override EntityContext Context => EntityContext.Gen2; + private const int StringLength = 12; + public override int OTLength => StringLength; + public override int NickLength => StringLength; + public override int BoxCount => Japanese ? 9 : 14; + public override int BoxSlotCount => Japanese ? 30 : 20; + + public override int MaxMoveID => Legal.MaxMoveID_2; + public override int MaxSpeciesID => Legal.MaxSpeciesID_2; + public override int MaxAbilityID => Legal.MaxAbilityID_2; + public override int MaxItemID => Legal.MaxItemID_2; + + public override Type PKMType => typeof(SK2); + public override PKM BlankPKM => new SK2(Japanese); + protected override PKM GetPKM(byte[] data) => new SK2(data, Japanese); + + private const int SIZE_SK2 = PokeCrypto.SIZE_2STADIUM; // 60 + protected override int SIZE_STORED => SIZE_SK2; + protected override int SIZE_PARTY => SIZE_SK2; + + private const int ListHeaderSizeTeam = 0x10; + private const int ListHeaderSizeBox = 0x20; + private const int ListFooterSize = 6; // POKE + 2byte checksum + + protected override int TeamCount => 60; + private const int TeamCountType = 10; + private const int TeamSize = ListHeaderSizeTeam + (SIZE_SK2 * 6) + 2 + ListFooterSize; // 0x180 + + private int BoxSize => Japanese ? BoxSizeJ : BoxSizeU; + private const int BoxSizeJ = ListHeaderSizeBox + (SIZE_SK2 * 30) + 2 + ListFooterSize; // 0x730 + private const int BoxSizeU = ListHeaderSizeBox + (SIZE_SK2 * 20) + 2 + ListFooterSize; // 0x4D8 + + // Box 1 is stored separately from the remainder of the boxes. + private const int BoxStart = 0x5E00; // Box 1 + private const int BoxContinue = 0x8000; // Box 2+ + + private const uint MAGIC_FOOTER = 0x30763350; // P3v0 + + public SAV2Stadium(byte[] data) : this(data, IsStadiumJ(data)) { } + + public SAV2Stadium(byte[] data, bool japanese) : base(data, japanese, GetIsSwap(data, japanese)) { - public override int SaveRevision => Japanese ? 0 : 1; - public override string SaveRevisionString => Japanese ? "J" : "U"; + Box = BoxStart; + } - public override PersonalTable Personal => PersonalTable.C; - public override int MaxEV => ushort.MaxValue; - public override IReadOnlyList HeldItems => Legal.HeldItems_GSC; - public override GameVersion Version { get; protected set; } = GameVersion.Stadium2; + public SAV2Stadium(bool japanese = false) : base(japanese, SaveUtil.SIZE_G2STAD) + { + Box = BoxStart; + ClearBoxes(); + } - protected override SaveFile CloneInternal() => new SAV2Stadium((byte[])Data.Clone(), Japanese); + protected override bool GetIsBoxChecksumValid(int box) + { + var boxOfs = GetBoxOffset(box) - ListHeaderSizeBox; + var size = BoxSize - 2; + var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); + var actual = ReadUInt16BigEndian(Data.AsSpan(boxOfs + size)); + return chk == actual; + } - public override int Generation => 2; - public override EntityContext Context => EntityContext.Gen2; - private const int StringLength = 12; - public override int OTLength => StringLength; - public override int NickLength => StringLength; - public override int BoxCount => Japanese ? 9 : 14; - public override int BoxSlotCount => Japanese ? 30 : 20; + protected override void SetBoxMetadata(int box) + { + var bdata = GetBoxOffset(box); - public override int MaxMoveID => Legal.MaxMoveID_2; - public override int MaxSpeciesID => Legal.MaxSpeciesID_2; - public override int MaxAbilityID => Legal.MaxAbilityID_2; - public override int MaxItemID => Legal.MaxItemID_2; - - public override Type PKMType => typeof(SK2); - public override PKM BlankPKM => new SK2(Japanese); - protected override PKM GetPKM(byte[] data) => new SK2(data, Japanese); - - private const int SIZE_SK2 = PokeCrypto.SIZE_2STADIUM; // 60 - protected override int SIZE_STORED => SIZE_SK2; - protected override int SIZE_PARTY => SIZE_SK2; - - private const int ListHeaderSizeTeam = 0x10; - private const int ListHeaderSizeBox = 0x20; - private const int ListFooterSize = 6; // POKE + 2byte checksum - - protected override int TeamCount => 60; - private const int TeamCountType = 10; - private const int TeamSize = ListHeaderSizeTeam + (SIZE_SK2 * 6) + 2 + ListFooterSize; // 0x180 - - private int BoxSize => Japanese ? BoxSizeJ : BoxSizeU; - private const int BoxSizeJ = ListHeaderSizeBox + (SIZE_SK2 * 30) + 2 + ListFooterSize; // 0x730 - private const int BoxSizeU = ListHeaderSizeBox + (SIZE_SK2 * 20) + 2 + ListFooterSize; // 0x4D8 - - // Box 1 is stored separately from the remainder of the boxes. - private const int BoxStart = 0x5E00; // Box 1 - private const int BoxContinue = 0x8000; // Box 2+ - - private const uint MAGIC_FOOTER = 0x30763350; // P3v0 - - public SAV2Stadium(byte[] data) : this(data, IsStadiumJ(data)) { } - - public SAV2Stadium(byte[] data, bool japanese) : base(data, japanese, GetIsSwap(data, japanese)) + // Set box count + int count = 0; + for (int s = 0; s < BoxSlotCount; s++) { - Box = BoxStart; + var rel = bdata + (SIZE_STORED * s); + if (Data[rel] != 0) // Species present + count++; } - public SAV2Stadium(bool japanese = false) : base(japanese, SaveUtil.SIZE_G2STAD) + var boxOfs = bdata - ListHeaderSizeBox; + if (Data[boxOfs] == 0) { - Box = BoxStart; - ClearBoxes(); + Data[boxOfs] = 1; + Data[boxOfs + 1] = (byte)count; + Data[boxOfs + 4] = StringConverter12.G1TerminatorCode; + StringConverter12.SetString(Data.AsSpan(boxOfs + 0x10, 4), "1234".AsSpan(), 4, Japanese, StringConverterOption.None); } - - protected override bool GetIsBoxChecksumValid(int box) + else { - var boxOfs = GetBoxOffset(box) - ListHeaderSizeBox; - var size = BoxSize - 2; - var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); - var actual = ReadUInt16BigEndian(Data.AsSpan(boxOfs + size)); - return chk == actual; - } - - protected override void SetBoxMetadata(int box) - { - var bdata = GetBoxOffset(box); - - // Set box count - int count = 0; - for (int s = 0; s < BoxSlotCount; s++) - { - var rel = bdata + (SIZE_STORED * s); - if (Data[rel] != 0) // Species present - count++; - } - - var boxOfs = bdata - ListHeaderSizeBox; - if (Data[boxOfs] == 0) - { - Data[boxOfs] = 1; - Data[boxOfs + 1] = (byte)count; - Data[boxOfs + 4] = StringConverter12.G1TerminatorCode; - StringConverter12.SetString(Data.AsSpan(boxOfs + 0x10, 4), "1234".AsSpan(), 4, Japanese, StringConverterOption.None); - } - else - { - Data[boxOfs + 1] = (byte)count; - } - } - - protected override void SetBoxChecksum(int box) - { - var boxOfs = GetBoxOffset(box) - ListHeaderSizeBox; - var size = BoxSize - 2; - var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); - WriteUInt16BigEndian(Data.AsSpan(boxOfs + size), chk); - } - - public static int GetTeamOffset(Stadium2TeamType type, int team) - { - if ((uint)team >= TeamCountType) - throw new ArgumentOutOfRangeException(nameof(team)); - - var index = (TeamCountType * (int)type) + team; - return GetTeamOffset(index); - } - - public static int GetTeamOffset(int team) - { - if (team < 40) - return 0 + (team * TeamSize); - // Teams 41-60 are in a separate chunk - return 0x4000 + ((team - 40) * TeamSize); - } - - public string GetTeamName(int team) - { - var name = $"{((Stadium2TeamType) (team / TeamCountType)).ToString().Replace('_', ' ')} {(team % 10) + 1}"; - - var ofs = GetTeamOffset(team); - var str = GetString(ofs + 4, 7); - if (string.IsNullOrWhiteSpace(str)) - return name; - var id = ReadUInt16BigEndian(Data.AsSpan(ofs + 2)); - return $"{name} [{id:D5}:{str}]"; - } - - public override string GetBoxName(int box) - { - var ofs = GetBoxOffset(box) - 0x10; - var str = GetString(ofs, 0x10); - if (string.IsNullOrWhiteSpace(str)) - return $"Box {box + 1}"; - return str; - } - - public override SlotGroup GetTeam(int team) - { - if ((uint)team >= TeamCount) - throw new ArgumentOutOfRangeException(nameof(team)); - - var name = GetTeamName(team); - var members = new SK2[6]; - var ofs = GetTeamOffset(team); - for (int i = 0; i < 6; i++) - { - var rel = ofs + ListHeaderSizeTeam + (i * SIZE_STORED); - members[i] = (SK2)GetStoredSlot(Data, rel); - } - return new SlotGroup(name, members); - } - - public override int GetBoxOffset(int box) - { - if (box == 0) - return BoxStart + ListHeaderSizeBox; - return BoxContinue + ListHeaderSizeBox + ((box - 1) * BoxSize); - } - - public static bool IsStadium(ReadOnlySpan data) - { - if (data.Length is not (SaveUtil.SIZE_G2STAD or SaveUtil.SIZE_G2STADF)) - return false; - if (IsStadiumJ(data) || IsStadiumU(data)) - return true; - return StadiumUtil.IsMagicPresentEither(data, TeamSize, MAGIC_FOOTER, 1) != StadiumSaveType.None; - } - - // Check Box 1's footer magic. - private static bool IsStadiumJ(ReadOnlySpan data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeJ - ListFooterSize, MAGIC_FOOTER) != StadiumSaveType.None; - private static bool IsStadiumU(ReadOnlySpan data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeU - ListFooterSize, MAGIC_FOOTER) != StadiumSaveType.None; - - private static bool GetIsSwap(ReadOnlySpan data, bool japanese) - { - var teamSwap = StadiumUtil.IsMagicPresentSwap(data, TeamSize, MAGIC_FOOTER, 1); - if (teamSwap) - return true; - var boxSwap = StadiumUtil.IsMagicPresentSwap(data[BoxStart..], japanese ? BoxSizeJ : BoxSizeU, MAGIC_FOOTER, 1); - if (boxSwap) - return true; - return false; + Data[boxOfs + 1] = (byte)count; } } - public enum Stadium2TeamType + protected override void SetBoxChecksum(int box) { - Anything_Goes = 0, - Little_Cup = 1, - Poke_Cup = 2, - Prime_Cup = 3, - GymLeader_Castle = 4, - Vs_Rival = 5, + var boxOfs = GetBoxOffset(box) - ListHeaderSizeBox; + var size = BoxSize - 2; + var chk = Checksums.CheckSum16(new ReadOnlySpan(Data, boxOfs, size)); + WriteUInt16BigEndian(Data.AsSpan(boxOfs + size), chk); + } + + public static int GetTeamOffset(Stadium2TeamType type, int team) + { + if ((uint)team >= TeamCountType) + throw new ArgumentOutOfRangeException(nameof(team)); + + var index = (TeamCountType * (int)type) + team; + return GetTeamOffset(index); + } + + public static int GetTeamOffset(int team) + { + if (team < 40) + return 0 + (team * TeamSize); + // Teams 41-60 are in a separate chunk + return 0x4000 + ((team - 40) * TeamSize); + } + + public string GetTeamName(int team) + { + var name = $"{((Stadium2TeamType) (team / TeamCountType)).ToString().Replace('_', ' ')} {(team % 10) + 1}"; + + var ofs = GetTeamOffset(team); + var str = GetString(ofs + 4, 7); + if (string.IsNullOrWhiteSpace(str)) + return name; + var id = ReadUInt16BigEndian(Data.AsSpan(ofs + 2)); + return $"{name} [{id:D5}:{str}]"; + } + + public override string GetBoxName(int box) + { + var ofs = GetBoxOffset(box) - 0x10; + var str = GetString(ofs, 0x10); + if (string.IsNullOrWhiteSpace(str)) + return $"Box {box + 1}"; + return str; + } + + public override SlotGroup GetTeam(int team) + { + if ((uint)team >= TeamCount) + throw new ArgumentOutOfRangeException(nameof(team)); + + var name = GetTeamName(team); + var members = new SK2[6]; + var ofs = GetTeamOffset(team); + for (int i = 0; i < 6; i++) + { + var rel = ofs + ListHeaderSizeTeam + (i * SIZE_STORED); + members[i] = (SK2)GetStoredSlot(Data, rel); + } + return new SlotGroup(name, members); + } + + public override int GetBoxOffset(int box) + { + if (box == 0) + return BoxStart + ListHeaderSizeBox; + return BoxContinue + ListHeaderSizeBox + ((box - 1) * BoxSize); + } + + public static bool IsStadium(ReadOnlySpan data) + { + if (data.Length is not (SaveUtil.SIZE_G2STAD or SaveUtil.SIZE_G2STADF)) + return false; + if (IsStadiumJ(data) || IsStadiumU(data)) + return true; + return StadiumUtil.IsMagicPresentEither(data, TeamSize, MAGIC_FOOTER, 1) != StadiumSaveType.None; + } + + // Check Box 1's footer magic. + private static bool IsStadiumJ(ReadOnlySpan data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeJ - ListFooterSize, MAGIC_FOOTER) != StadiumSaveType.None; + private static bool IsStadiumU(ReadOnlySpan data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeU - ListFooterSize, MAGIC_FOOTER) != StadiumSaveType.None; + + private static bool GetIsSwap(ReadOnlySpan data, bool japanese) + { + var teamSwap = StadiumUtil.IsMagicPresentSwap(data, TeamSize, MAGIC_FOOTER, 1); + if (teamSwap) + return true; + var boxSwap = StadiumUtil.IsMagicPresentSwap(data[BoxStart..], japanese ? BoxSizeJ : BoxSizeU, MAGIC_FOOTER, 1); + if (boxSwap) + return true; + return false; } } + +public enum Stadium2TeamType +{ + Anything_Goes = 0, + Little_Cup = 1, + Poke_Cup = 2, + Prime_Cup = 3, + GymLeader_Castle = 4, + Vs_Rival = 5, +} diff --git a/PKHeX.Core/Saves/SAV3.cs b/PKHeX.Core/Saves/SAV3.cs index 68d6e2f12..a23ea8e59 100644 --- a/PKHeX.Core/Saves/SAV3.cs +++ b/PKHeX.Core/Saves/SAV3.cs @@ -3,694 +3,693 @@ using System.Runtime.InteropServices; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 object. +/// +public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37 { - /// - /// Generation 3 object. - /// - public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37 + protected internal sealed override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}"; + public sealed override string Extension => ".sav"; + + public int SaveRevision => Japanese ? 0 : 1; + public string SaveRevisionString => Japanese ? "J" : "U"; + public bool Japanese { get; } + public bool Korean => false; + + // Similar to future games, the Generation 3 Mainline save files are comprised of separate objects: + // Object 1 - Small, containing misc configuration data & the Pokédex. + // Object 2 - Large, containing everything else that isn't PC Storage system data. + // Object 3 - Storage, containing all the data for the PC storage system. + + // When the objects are serialized to the savedata, the game fragments each object and saves it to a sector. + // The main save data for a save file occupies 14 sectors; there are a total of two serialized main saves. + // After the serialized main save data, there is "extra data", for stuff like Hall of Fame and battle videos. + // Extra data is always at the same sector, while the main sectors rotate sectors within their region (on each successive save?). + + private const int SIZE_SECTOR = 0x1000; + private const int SIZE_SECTOR_USED = 0xF80; + private const int COUNT_MAIN = 14; // sectors worth of data + private const int SIZE_MAIN = COUNT_MAIN * SIZE_SECTOR; + + // There's no harm having buffers larger than their actual size (per format). + // A checksum consuming extra zeroes does not change the prior checksum result. + public readonly byte[] Small = new byte[1 * SIZE_SECTOR_USED]; // [0x890 RS, 0xf24 FR/LG, 0xf2c E] + public readonly byte[] Large = new byte[4 * SIZE_SECTOR_USED]; //3+[0xc40 RS, 0xee8 FR/LG, 0xf08 E] + public readonly byte[] Storage = new byte[9 * SIZE_SECTOR_USED]; // [0x83D0] + + private readonly int ActiveSlot; + + protected SAV3(bool japanese) => Japanese = japanese; + + protected SAV3(byte[] data) : base(data) { - protected internal sealed override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}"; - public sealed override string Extension => ".sav"; + // Copy sector data to the allocated location + ReadSectors(data, ActiveSlot = GetActiveSlot(data)); - public int SaveRevision => Japanese ? 0 : 1; - public string SaveRevisionString => Japanese ? "J" : "U"; - public bool Japanese { get; } - public bool Korean => false; - - // Similar to future games, the Generation 3 Mainline save files are comprised of separate objects: - // Object 1 - Small, containing misc configuration data & the Pokédex. - // Object 2 - Large, containing everything else that isn't PC Storage system data. - // Object 3 - Storage, containing all the data for the PC storage system. - - // When the objects are serialized to the savedata, the game fragments each object and saves it to a sector. - // The main save data for a save file occupies 14 sectors; there are a total of two serialized main saves. - // After the serialized main save data, there is "extra data", for stuff like Hall of Fame and battle videos. - // Extra data is always at the same sector, while the main sectors rotate sectors within their region (on each successive save?). - - private const int SIZE_SECTOR = 0x1000; - private const int SIZE_SECTOR_USED = 0xF80; - private const int COUNT_MAIN = 14; // sectors worth of data - private const int SIZE_MAIN = COUNT_MAIN * SIZE_SECTOR; - - // There's no harm having buffers larger than their actual size (per format). - // A checksum consuming extra zeroes does not change the prior checksum result. - public readonly byte[] Small = new byte[1 * SIZE_SECTOR_USED]; // [0x890 RS, 0xf24 FR/LG, 0xf2c E] - public readonly byte[] Large = new byte[4 * SIZE_SECTOR_USED]; //3+[0xc40 RS, 0xee8 FR/LG, 0xf08 E] - public readonly byte[] Storage = new byte[9 * SIZE_SECTOR_USED]; // [0x83D0] - - private readonly int ActiveSlot; - - protected SAV3(bool japanese) => Japanese = japanese; - - protected SAV3(byte[] data) : base(data) - { - // Copy sector data to the allocated location - ReadSectors(data, ActiveSlot = GetActiveSlot(data)); - - // OT name is the first 8 bytes of Small. The game fills any unused characters with 0xFF. - // Japanese games are limited to 5 character OT names; INT 7 characters. +1 0xFF terminator. - // Since JPN games don't touch the last 2 bytes (alignment), they end up as zeroes! - Japanese = ReadInt16LittleEndian(Small.AsSpan(0x6)) == 0; - } - - private void ReadSectors(ReadOnlySpan data, int group) - { - int start = group * SIZE_MAIN; - int end = start + SIZE_MAIN; - for (int ofs = start; ofs < end; ofs += SIZE_SECTOR) - { - var id = ReadInt16LittleEndian(data[(ofs + 0xFF4)..]); - switch (id) - { - case >=5: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Storage.AsSpan((id - 5) * SIZE_SECTOR_USED)); break; - case >=1: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Large .AsSpan((id - 1) * SIZE_SECTOR_USED)); break; - default: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Small .AsSpan(0 )); break; - } - } - } - - private void WriteSectors(Span data, int group) - { - int start = group * SIZE_MAIN; - int end = start + SIZE_MAIN; - for (int ofs = start; ofs < end; ofs += SIZE_SECTOR) - { - var id = ReadInt16LittleEndian(data[(ofs + 0xFF4)..]); - switch (id) - { - case >=5: Storage.AsSpan((id - 5) * SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(data[ofs..]); break; - case >=1: Large .AsSpan((id - 1) * SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(data[ofs..]); break; - default: Small .AsSpan(0 , SIZE_SECTOR_USED).CopyTo(data[ofs..]); break; - } - } - } - - /// - /// Checks the input data to see if all required sectors for the main save data are present for the . - /// - /// Data to check - /// Which main to check (primary or secondary) - /// Offset of the sector that has the small object data - public static bool IsAllMainSectorsPresent(ReadOnlySpan data, int slot, out int sector0) - { - System.Diagnostics.Debug.Assert(slot is 0 or 1); - int start = SIZE_MAIN * slot; - int end = start + SIZE_MAIN; - int bitTrack = 0; - sector0 = 0; - for (int ofs = start; ofs < end; ofs += SIZE_SECTOR) - { - var span = data[ofs..]; - var id = ReadInt16LittleEndian(span[0xFF4..]); - bitTrack |= (1 << id); - if (id == 0) - sector0 = ofs; - } - // all 14 fragments present - return bitTrack == 0b_0011_1111_1111_1111; - } - - private static int GetActiveSlot(ReadOnlySpan data) - { - if (data.Length == SaveUtil.SIZE_G3RAWHALF) - return 0; - - var v0 = IsAllMainSectorsPresent(data, 0, out var sectorZero0); - var v1 = IsAllMainSectorsPresent(data, 1, out var sectorZero1); - if (!v0) - return v1 ? 1 : 0; - if (!v1) - return 0; - - var count0 = ReadUInt32LittleEndian(data[(sectorZero0 + 0x0FFC)..]); - var count1 = ReadUInt32LittleEndian(data[(sectorZero1 + 0x0FFC)..]); - // don't care about 32bit overflow. a 10 second save would take 1,000 years to overflow! - return count1 > count0 ? 1 : 0; - } - - protected sealed override byte[] GetFinalData() - { - // Copy Box data back - WriteSectors(Data, ActiveSlot); - return base.GetFinalData(); - } - - protected sealed override int SIZE_STORED => PokeCrypto.SIZE_3STORED; - protected sealed override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY; - public sealed override PKM BlankPKM => new PK3(); - public sealed override Type PKMType => typeof(PK3); - - public sealed override int MaxMoveID => Legal.MaxMoveID_3; - public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_3; - public sealed override int MaxAbilityID => Legal.MaxAbilityID_3; - public sealed override int MaxItemID => Legal.MaxItemID_3; - public sealed override int MaxBallID => Legal.MaxBallID_3; - public sealed override int MaxGameID => Legal.MaxGameID_3; - - public abstract int EventFlagCount { get; } - public abstract int EventWorkCount { get; } - protected abstract int EventFlag { get; } - protected abstract int EventWork { get; } - - /// - /// Force loads a new object to the requested . - /// - /// Version to retrieve for - /// New object. - public SAV3 ForceLoad(GameVersion version) => version switch - { - GameVersion.R or GameVersion.S or GameVersion.RS => new SAV3RS(Data), - GameVersion.E => new SAV3E(Data), - GameVersion.FR or GameVersion.LG or GameVersion.FRLG => new SAV3FRLG(Data), - _ => throw new ArgumentOutOfRangeException(nameof(version)), - }; - - public sealed override IReadOnlyList HeldItems => Legal.HeldItems_RS; - - public sealed override int BoxCount => 14; - public sealed override int MaxEV => 255; - public sealed override int Generation => 3; - public sealed override EntityContext Context => EntityContext.Gen3; - protected sealed override int GiftCountMax => 1; - public sealed override int OTLength => 7; - public sealed override int NickLength => 10; - public sealed override int MaxMoney => 999999; - - public sealed override bool HasParty => true; - - public sealed override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGBA(data); - protected sealed override PKM GetPKM(byte[] data) => new PK3(data); - protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray3(data); - - protected sealed override byte[] BoxBuffer => Storage; - protected sealed override byte[] PartyBuffer => Large; - - private const int COUNT_BOX = 14; - private const int COUNT_SLOTSPERBOX = 30; - - // Checksums - protected sealed override void SetChecksums() - { - int start = ActiveSlot * SIZE_MAIN; - int end = start + SIZE_MAIN; - for (int ofs = start; ofs < end; ofs += SIZE_SECTOR) - { - var sector = Data.AsSpan(ofs, SIZE_SECTOR); - ushort chk = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]); - WriteUInt16LittleEndian(sector[0xFF6..], chk); - } - - if (Data.Length < SaveUtil.SIZE_G3RAW) // don't update HoF for half-sizes - return; - - // Hall of Fame Checksums - { - var sector2 = Data.AsSpan(0x1C000, SIZE_SECTOR); - ushort chk = Checksums.CheckSum32(sector2[..SIZE_SECTOR_USED]); - WriteUInt16LittleEndian(sector2[0xFF4..], chk); - } - { - var sector2 = Data.AsSpan(0x1D000, SIZE_SECTOR); - ushort chk = Checksums.CheckSum32(sector2[..SIZE_SECTOR_USED]); - WriteUInt16LittleEndian(sector2[0xFF4..], chk); - } - } - - public sealed override bool ChecksumsValid - { - get - { - for (int i = 0; i < COUNT_MAIN; i++) - { - if (!IsSectorValid(i)) - return false; - } - - if (Data.Length < SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes - return true; - - if (!IsSectorValidExtra(0x1C000)) - return false; - if (!IsSectorValidExtra(0x1D000)) - return false; - return true; - } - } - - private bool IsSectorValidExtra(int ofs) - { - var sector = Data.AsSpan(ofs, SIZE_SECTOR); - ushort chk = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]); - return chk == ReadUInt16LittleEndian(sector[0xFF4..]); - } - - private bool IsSectorValid(int sectorIndex) - { - int start = ActiveSlot * SIZE_MAIN; - int ofs = start + (sectorIndex * SIZE_SECTOR); - var sector = Data.AsSpan(ofs, SIZE_SECTOR); - ushort chk = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]); - return chk == ReadUInt16LittleEndian(sector[0xFF6..]); - } - - public sealed override string ChecksumInfo - { - get - { - var list = new List(); - for (int i = 0; i < COUNT_MAIN; i++) - { - if (!IsSectorValid(i)) - list.Add($"Sector {i} @ {i*SIZE_SECTOR:X5} invalid."); - } - - if (Data.Length > SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes - { - if (!IsSectorValidExtra(0x1C000)) - list.Add("HoF first sector invalid."); - if (!IsSectorValidExtra(0x1D000)) - list.Add("HoF second sector invalid."); - } - return list.Count != 0 ? string.Join(Environment.NewLine, list) : "Checksums are valid."; - } - } - - public abstract uint SecurityKey { get; set; } - - public sealed override string OT - { - get => GetString(Small.AsSpan(0, 8)); - set - { - int len = Japanese ? 5 : OTLength; - SetString(Small.AsSpan(0, len), value.AsSpan(), len, StringConverterOption.ClearFF); - } - } - - public sealed override int Gender - { - get => Small[8]; - set => Small[8] = (byte)value; - } - - public sealed override int TID - { - get => ReadUInt16LittleEndian(Small.AsSpan(0xA)); - set => WriteUInt16LittleEndian(Small.AsSpan(0xA), (ushort)value); - } - - public sealed override int SID - { - get => ReadUInt16LittleEndian(Small.AsSpan(0xC)); - set => WriteUInt16LittleEndian(Small.AsSpan(0xC), (ushort)value); - } - - public sealed override int PlayedHours - { - get => ReadUInt16LittleEndian(Small.AsSpan(0xE)); - set => WriteUInt16LittleEndian(Small.AsSpan(0xE), (ushort)value); - } - - public sealed override int PlayedMinutes - { - get => Small[0x10]; - set => Small[0x10] = (byte)value; - } - - public sealed override int PlayedSeconds - { - get => Small[0x11]; - set => Small[0x11] = (byte)value; - } - - public int PlayedFrames - { - get => Small[0x12]; - set => Small[0x12] = (byte)value; - } - - #region Event Flag/Event Work - public bool GetEventFlag(int flagNumber) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); - return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); - } - - public void SetEventFlag(int flagNumber, bool value) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); - SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); - } - - public ushort GetWork(int index) => ReadUInt16LittleEndian(Large.AsSpan(EventWork + (index * 2))); - public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Large.AsSpan(EventWork)[(index * 2)..], value); - #endregion - - public sealed override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(Large, offset, bitIndex); - public sealed override void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(Large, offset, bitIndex, value); - - protected abstract int BadgeFlagStart { get; } - public abstract uint Coin { get; set; } - - public int Badges - { - get - { - int startFlag = BadgeFlagStart; - int val = 0; - for (int i = 0; i < 8; i++) - { - if (GetEventFlag(startFlag + i)) - val |= 1 << i; - } - - return val; - } - set - { - int startFlag = BadgeFlagStart; - for (int i = 0; i < 8; i++) - SetEventFlag(startFlag + i, (value & (1 << i)) != 0); - } - } - - public sealed override IReadOnlyList Inventory - { - get - { - var pouch = GetItems(); - foreach (var p in pouch) - { - if (p.Type != InventoryType.PCItems) - p.SecurityKey = SecurityKey; - } - return pouch.LoadAll(Large); - } - set => value.SaveAll(Large); - } - - protected abstract InventoryPouch3[] GetItems(); - - protected abstract int DaycareSlotSize { get; } - - public sealed override uint? GetDaycareEXP(int loc, int slot) => ReadUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(slot))); - public sealed override void SetDaycareEXP(int loc, int slot, uint EXP) => WriteUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(slot)), EXP); - public sealed override bool? IsDaycareOccupied(int loc, int slot) => IsPKMPresent(Large.AsSpan(GetDaycareSlotOffset(loc, slot))); - public sealed override void SetDaycareOccupied(int loc, int slot, bool occupied) { /* todo */ } - public sealed override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + (slot * DaycareSlotSize); - - protected abstract int EggEventFlag { get; } - public sealed override bool? IsDaycareHasEgg(int loc) => GetEventFlag(EggEventFlag); - public sealed override void SetDaycareHasEgg(int loc, bool hasEgg) => SetEventFlag(EggEventFlag, hasEgg); - - protected abstract int GetDaycareEXPOffset(int slot); - - #region Storage - public sealed override int GetBoxOffset(int box) => Box + 4 + (SIZE_STORED * box * COUNT_SLOTSPERBOX); - - public sealed override int CurrentBox - { - get => Storage[0]; - set => Storage[0] = (byte)value; - } - - public sealed override int GetBoxWallpaper(int box) - { - if (box > COUNT_BOX) - return box; - int offset = GetBoxWallpaperOffset(box); - return Storage[offset]; - } - - private const int COUNT_BOXNAME = 8 + 1; - - public sealed override void SetBoxWallpaper(int box, int value) - { - if (box > COUNT_BOX) - return; - int offset = GetBoxWallpaperOffset(box); - Storage[offset] = (byte)value; - } - - protected sealed override int GetBoxWallpaperOffset(int box) - { - int offset = GetBoxOffset(COUNT_BOX); - offset += (COUNT_BOX * COUNT_BOXNAME) + box; - return offset; - } - - public sealed override string GetBoxName(int box) - { - int offset = GetBoxOffset(COUNT_BOX); - return StringConverter3.GetString(Storage.AsSpan(offset + (box * COUNT_BOXNAME), COUNT_BOXNAME), Japanese); - } - - public sealed override void SetBoxName(int box, string value) - { - int offset = GetBoxOffset(COUNT_BOX); - var dest = Storage.AsSpan(offset + (box * COUNT_BOXNAME), COUNT_BOXNAME); - SetString(dest, value.AsSpan(), COUNT_BOXNAME - 1, StringConverterOption.ClearZero); - } - #endregion - - #region Pokédex - protected sealed override void SetDex(PKM pkm) - { - int species = pkm.Species; - if (species is 0 or > Legal.MaxSpeciesID_3) - return; - if (pkm.IsEgg) - return; - - switch (species) - { - case (int)Species.Unown when !GetSeen(species): // Unown - DexPIDUnown = pkm.PID; - break; - case (int)Species.Spinda when !GetSeen(species): // Spinda - DexPIDSpinda = pkm.PID; - break; - } - SetCaught(species, true); - SetSeen(species, true); - } - - public uint DexPIDUnown { get => ReadUInt32LittleEndian(Small.AsSpan(PokeDex + 0x4)); set => WriteUInt32LittleEndian(Small.AsSpan(PokeDex + 0x4), value); } - public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Small.AsSpan(PokeDex + 0x8)); set => WriteUInt32LittleEndian(Small.AsSpan(PokeDex + 0x8), value); } - public int DexUnownForm => EntityPID.GetUnownForm3(DexPIDUnown); - - public sealed override bool GetCaught(int species) - { - int bit = species - 1; - int ofs = bit >> 3; - int caughtOffset = PokeDex + 0x10; - return FlagUtil.GetFlag(Small, caughtOffset + ofs, bit & 7); - } - - public sealed override void SetCaught(int species, bool caught) - { - int bit = species - 1; - int ofs = bit >> 3; - int caughtOffset = PokeDex + 0x10; - FlagUtil.SetFlag(Small, caughtOffset + ofs, bit & 7, caught); - } - - public sealed override bool GetSeen(int species) - { - int bit = species - 1; - int ofs = bit >> 3; - int seenOffset = PokeDex + 0x44; - return FlagUtil.GetFlag(Small, seenOffset + ofs, bit & 7); - } - - protected abstract int SeenOffset2 { get; } - protected abstract int SeenOffset3 { get; } - - public sealed override void SetSeen(int species, bool seen) - { - int bit = species - 1; - int ofs = bit >> 3; - - int seenOffset = PokeDex + 0x44; - FlagUtil.SetFlag(Small, seenOffset + ofs, bit & 7, seen); - FlagUtil.SetFlag(Large, SeenOffset2 + ofs, bit & 7, seen); - FlagUtil.SetFlag(Large, SeenOffset3 + ofs, bit & 7, seen); - } - - public byte PokedexSort - { - get => Small[PokeDex + 0x01]; - set => Small[PokeDex + 0x01] = value; - } - - public byte PokedexMode - { - get => Small[PokeDex + 0x01]; - set => Small[PokeDex + 0x01] = value; - } - - public byte PokedexNationalMagicRSE - { - get => Small[PokeDex + 0x02]; - set => Small[PokeDex + 0x02] = value; - } - - public byte PokedexNationalMagicFRLG - { - get => Small[PokeDex + 0x03]; - set => Small[PokeDex + 0x03] = value; - } - - protected const byte PokedexNationalUnlockRSE = 0xDA; - protected const byte PokedexNationalUnlockFRLG = 0xDA; - protected const ushort PokedexNationalUnlockWorkRSE = 0x0302; - protected const ushort PokedexNationalUnlockWorkFRLG = 0x6258; - - public abstract bool NationalDex { get; set; } - #endregion - - public sealed override string GetString(ReadOnlySpan data) => StringConverter3.GetString(data, Japanese); - - public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter3.SetString(destBuffer, value, maxLength, Japanese, option); - } - - protected abstract int MailOffset { get; } - public int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset; - - public Mail GetMail(int mailIndex) - { - var ofs = GetMailOffset(mailIndex); - var data = Large.Slice(ofs, Mail3.SIZE); - return new Mail3(data, ofs, Japanese); - } - - public abstract string EBerryName { get; } - public abstract bool IsEBerryEngima { get; } - public abstract MysteryEvent3 MysteryEvent { get; set; } - - public byte[] GetHallOfFameData() - { - // HoF Data is split across two sectors - byte[] data = new byte[SIZE_SECTOR_USED * 2]; - Data.AsSpan(0x1C000, SIZE_SECTOR_USED).CopyTo(data.AsSpan(0 , SIZE_SECTOR_USED)); - Data.AsSpan(0x1D000, SIZE_SECTOR_USED).CopyTo(data.AsSpan(SIZE_SECTOR_USED, SIZE_SECTOR_USED)); - return data; - } - - public void SetHallOfFameData(ReadOnlySpan value) - { - if (value.Length != SIZE_SECTOR_USED * 2) - throw new ArgumentException("Invalid size", nameof(value)); - // HoF Data is split across two sav sectors - Span savedata = Data; - value[..SIZE_SECTOR_USED].CopyTo(savedata[0x1C000..]); - value.Slice(SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(savedata[0x1D000..]); - } - - public bool IsCorruptPokedexFF() => MemoryMarshal.Read(Small.AsSpan(0xAC)) == ulong.MaxValue; - - public override void CopyChangesFrom(SaveFile sav) - { - SetData(sav.Data, 0); - var s3 = (SAV3)sav; - SetData(Small, s3.Small, 0); - SetData(Large, s3.Large, 0); - SetData(Storage, s3.Storage, 0); - } - - #region External Connections - protected abstract int ExternalEventData { get; } - protected int ExternalEventFlags => ExternalEventData + 0x14; - - public uint ColosseumRaw1 - { - get => ReadUInt32LittleEndian(Large.AsSpan(ExternalEventData + 7)); - set => WriteUInt32LittleEndian(Large.AsSpan(ExternalEventData + 7), value); - } - - public uint ColosseumRaw2 - { - get => ReadUInt32LittleEndian(Large.AsSpan(ExternalEventData + 11)); - set => WriteUInt32LittleEndian(Large.AsSpan(ExternalEventData + 11), value); - } - - /// - /// PokéCoupons stored by Pokémon Colosseum and XD from Mt. Battle runs. Earned PokéCoupons are also added to . - /// - /// Colosseum/XD caps this at 9,999,999, but will read up to 16,777,215. - public uint ColosseumCoupons - { - get => ColosseumRaw1 >> 8; - set => ColosseumRaw1 = (value << 8) | (ColosseumRaw1 & 0xFF); - } - - /// Master Ball from JP Colosseum Bonus Disc; for reaching 30,000 - public bool ColosseumPokeCouponTitleGold - { - get => (ColosseumRaw2 & (1 << 0)) != 0; - set => ColosseumRaw2 = (ColosseumRaw2 & (1 << 0)) | ((value ? 1u : 0) << 0); - } - - /// Light Ball Pikachu from JP Colosseum Bonus Disc; for reaching 5000 - public bool ColosseumPokeCouponTitleSilver - { - get => (ColosseumRaw2 & (1 << 1)) != 0; - set => ColosseumRaw2 = (ColosseumRaw2 & (1 << 1)) | ((value ? 1u : 0) << 1); - } - - /// PP Max from JP Colosseum Bonus Disc; for reaching 2500 - public bool ColosseumPokeCouponTitleBronze - { - get => (ColosseumRaw2 & (1 << 2)) != 0; - set => ColosseumRaw2 = (ColosseumRaw2 & (1 << 2)) | ((value ? 1u : 0) << 2); - } - - /// Received Celebi Gift from JP Colosseum Bonus Disc - public bool ColosseumReceivedAgeto - { - get => (ColosseumRaw2 & (1 << 3)) != 0; - set => ColosseumRaw2 = (ColosseumRaw2 & (1 << 3)) | ((value ? 1u : 0) << 3); - } - - /// - /// Used by the JP Colosseum bonus disc. Determines PokéCoupon rank to distribute rewards. Unread in International games. - /// - /// - /// Colosseum/XD caps this at 9,999,999. - /// - public uint ColosseumCouponsTotal - { - get => ColosseumRaw2 >> 8; - set => ColosseumRaw2 = (value << 8) | (ColosseumRaw2 & 0xFF); - } - - /// Indicates if this save has connected to RSBOX and triggered the free False Swipe Swablu Egg giveaway. - public bool HasUsedRSBOX - { - get => GetFlag(ExternalEventFlags + 0, 0); - set => SetFlag(ExternalEventFlags + 0, 0, value); - } - - /// - /// 1 for ExtremeSpeed Zigzagoon (at 100 deposited), 2 for Pay Day Skitty (at 500 deposited), 3 for Surf Pichu (at 1499 deposited) - /// - public int RSBoxDepositEggsUnlocked - { - get => (Large[ExternalEventFlags] >> 1) & 3; - set => Large[ExternalEventFlags] = (byte)((Large[ExternalEventFlags] & ~(3 << 1)) | ((value & 3) << 1)); - } - - /// Received Jirachi Gift from Colosseum Bonus Disc - public bool HasReceivedWishmkrJirachi - { - get => GetFlag(ExternalEventFlags + 2, 0); - set => SetFlag(ExternalEventFlags + 2, 0, value); - } - #endregion + // OT name is the first 8 bytes of Small. The game fills any unused characters with 0xFF. + // Japanese games are limited to 5 character OT names; INT 7 characters. +1 0xFF terminator. + // Since JPN games don't touch the last 2 bytes (alignment), they end up as zeroes! + Japanese = ReadInt16LittleEndian(Small.AsSpan(0x6)) == 0; } + + private void ReadSectors(ReadOnlySpan data, int group) + { + int start = group * SIZE_MAIN; + int end = start + SIZE_MAIN; + for (int ofs = start; ofs < end; ofs += SIZE_SECTOR) + { + var id = ReadInt16LittleEndian(data[(ofs + 0xFF4)..]); + switch (id) + { + case >=5: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Storage.AsSpan((id - 5) * SIZE_SECTOR_USED)); break; + case >=1: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Large .AsSpan((id - 1) * SIZE_SECTOR_USED)); break; + default: data.Slice(ofs, SIZE_SECTOR_USED).CopyTo(Small .AsSpan(0 )); break; + } + } + } + + private void WriteSectors(Span data, int group) + { + int start = group * SIZE_MAIN; + int end = start + SIZE_MAIN; + for (int ofs = start; ofs < end; ofs += SIZE_SECTOR) + { + var id = ReadInt16LittleEndian(data[(ofs + 0xFF4)..]); + switch (id) + { + case >=5: Storage.AsSpan((id - 5) * SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(data[ofs..]); break; + case >=1: Large .AsSpan((id - 1) * SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(data[ofs..]); break; + default: Small .AsSpan(0 , SIZE_SECTOR_USED).CopyTo(data[ofs..]); break; + } + } + } + + /// + /// Checks the input data to see if all required sectors for the main save data are present for the . + /// + /// Data to check + /// Which main to check (primary or secondary) + /// Offset of the sector that has the small object data + public static bool IsAllMainSectorsPresent(ReadOnlySpan data, int slot, out int sector0) + { + System.Diagnostics.Debug.Assert(slot is 0 or 1); + int start = SIZE_MAIN * slot; + int end = start + SIZE_MAIN; + int bitTrack = 0; + sector0 = 0; + for (int ofs = start; ofs < end; ofs += SIZE_SECTOR) + { + var span = data[ofs..]; + var id = ReadInt16LittleEndian(span[0xFF4..]); + bitTrack |= (1 << id); + if (id == 0) + sector0 = ofs; + } + // all 14 fragments present + return bitTrack == 0b_0011_1111_1111_1111; + } + + private static int GetActiveSlot(ReadOnlySpan data) + { + if (data.Length == SaveUtil.SIZE_G3RAWHALF) + return 0; + + var v0 = IsAllMainSectorsPresent(data, 0, out var sectorZero0); + var v1 = IsAllMainSectorsPresent(data, 1, out var sectorZero1); + if (!v0) + return v1 ? 1 : 0; + if (!v1) + return 0; + + var count0 = ReadUInt32LittleEndian(data[(sectorZero0 + 0x0FFC)..]); + var count1 = ReadUInt32LittleEndian(data[(sectorZero1 + 0x0FFC)..]); + // don't care about 32bit overflow. a 10 second save would take 1,000 years to overflow! + return count1 > count0 ? 1 : 0; + } + + protected sealed override byte[] GetFinalData() + { + // Copy Box data back + WriteSectors(Data, ActiveSlot); + return base.GetFinalData(); + } + + protected sealed override int SIZE_STORED => PokeCrypto.SIZE_3STORED; + protected sealed override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY; + public sealed override PKM BlankPKM => new PK3(); + public sealed override Type PKMType => typeof(PK3); + + public sealed override int MaxMoveID => Legal.MaxMoveID_3; + public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_3; + public sealed override int MaxAbilityID => Legal.MaxAbilityID_3; + public sealed override int MaxItemID => Legal.MaxItemID_3; + public sealed override int MaxBallID => Legal.MaxBallID_3; + public sealed override int MaxGameID => Legal.MaxGameID_3; + + public abstract int EventFlagCount { get; } + public abstract int EventWorkCount { get; } + protected abstract int EventFlag { get; } + protected abstract int EventWork { get; } + + /// + /// Force loads a new object to the requested . + /// + /// Version to retrieve for + /// New object. + public SAV3 ForceLoad(GameVersion version) => version switch + { + GameVersion.R or GameVersion.S or GameVersion.RS => new SAV3RS(Data), + GameVersion.E => new SAV3E(Data), + GameVersion.FR or GameVersion.LG or GameVersion.FRLG => new SAV3FRLG(Data), + _ => throw new ArgumentOutOfRangeException(nameof(version)), + }; + + public sealed override IReadOnlyList HeldItems => Legal.HeldItems_RS; + + public sealed override int BoxCount => 14; + public sealed override int MaxEV => 255; + public sealed override int Generation => 3; + public sealed override EntityContext Context => EntityContext.Gen3; + protected sealed override int GiftCountMax => 1; + public sealed override int OTLength => 7; + public sealed override int NickLength => 10; + public sealed override int MaxMoney => 999999; + + public sealed override bool HasParty => true; + + public sealed override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGBA(data); + protected sealed override PKM GetPKM(byte[] data) => new PK3(data); + protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray3(data); + + protected sealed override byte[] BoxBuffer => Storage; + protected sealed override byte[] PartyBuffer => Large; + + private const int COUNT_BOX = 14; + private const int COUNT_SLOTSPERBOX = 30; + + // Checksums + protected sealed override void SetChecksums() + { + int start = ActiveSlot * SIZE_MAIN; + int end = start + SIZE_MAIN; + for (int ofs = start; ofs < end; ofs += SIZE_SECTOR) + { + var sector = Data.AsSpan(ofs, SIZE_SECTOR); + ushort chk = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]); + WriteUInt16LittleEndian(sector[0xFF6..], chk); + } + + if (Data.Length < SaveUtil.SIZE_G3RAW) // don't update HoF for half-sizes + return; + + // Hall of Fame Checksums + { + var sector2 = Data.AsSpan(0x1C000, SIZE_SECTOR); + ushort chk = Checksums.CheckSum32(sector2[..SIZE_SECTOR_USED]); + WriteUInt16LittleEndian(sector2[0xFF4..], chk); + } + { + var sector2 = Data.AsSpan(0x1D000, SIZE_SECTOR); + ushort chk = Checksums.CheckSum32(sector2[..SIZE_SECTOR_USED]); + WriteUInt16LittleEndian(sector2[0xFF4..], chk); + } + } + + public sealed override bool ChecksumsValid + { + get + { + for (int i = 0; i < COUNT_MAIN; i++) + { + if (!IsSectorValid(i)) + return false; + } + + if (Data.Length < SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes + return true; + + if (!IsSectorValidExtra(0x1C000)) + return false; + if (!IsSectorValidExtra(0x1D000)) + return false; + return true; + } + } + + private bool IsSectorValidExtra(int ofs) + { + var sector = Data.AsSpan(ofs, SIZE_SECTOR); + ushort chk = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]); + return chk == ReadUInt16LittleEndian(sector[0xFF4..]); + } + + private bool IsSectorValid(int sectorIndex) + { + int start = ActiveSlot * SIZE_MAIN; + int ofs = start + (sectorIndex * SIZE_SECTOR); + var sector = Data.AsSpan(ofs, SIZE_SECTOR); + ushort chk = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]); + return chk == ReadUInt16LittleEndian(sector[0xFF6..]); + } + + public sealed override string ChecksumInfo + { + get + { + var list = new List(); + for (int i = 0; i < COUNT_MAIN; i++) + { + if (!IsSectorValid(i)) + list.Add($"Sector {i} @ {i*SIZE_SECTOR:X5} invalid."); + } + + if (Data.Length > SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes + { + if (!IsSectorValidExtra(0x1C000)) + list.Add("HoF first sector invalid."); + if (!IsSectorValidExtra(0x1D000)) + list.Add("HoF second sector invalid."); + } + return list.Count != 0 ? string.Join(Environment.NewLine, list) : "Checksums are valid."; + } + } + + public abstract uint SecurityKey { get; set; } + + public sealed override string OT + { + get => GetString(Small.AsSpan(0, 8)); + set + { + int len = Japanese ? 5 : OTLength; + SetString(Small.AsSpan(0, len), value.AsSpan(), len, StringConverterOption.ClearFF); + } + } + + public sealed override int Gender + { + get => Small[8]; + set => Small[8] = (byte)value; + } + + public sealed override int TID + { + get => ReadUInt16LittleEndian(Small.AsSpan(0xA)); + set => WriteUInt16LittleEndian(Small.AsSpan(0xA), (ushort)value); + } + + public sealed override int SID + { + get => ReadUInt16LittleEndian(Small.AsSpan(0xC)); + set => WriteUInt16LittleEndian(Small.AsSpan(0xC), (ushort)value); + } + + public sealed override int PlayedHours + { + get => ReadUInt16LittleEndian(Small.AsSpan(0xE)); + set => WriteUInt16LittleEndian(Small.AsSpan(0xE), (ushort)value); + } + + public sealed override int PlayedMinutes + { + get => Small[0x10]; + set => Small[0x10] = (byte)value; + } + + public sealed override int PlayedSeconds + { + get => Small[0x11]; + set => Small[0x11] = (byte)value; + } + + public int PlayedFrames + { + get => Small[0x12]; + set => Small[0x12] = (byte)value; + } + + #region Event Flag/Event Work + public bool GetEventFlag(int flagNumber) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); + return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); + } + + public void SetEventFlag(int flagNumber, bool value) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); + SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); + } + + public ushort GetWork(int index) => ReadUInt16LittleEndian(Large.AsSpan(EventWork + (index * 2))); + public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Large.AsSpan(EventWork)[(index * 2)..], value); + #endregion + + public sealed override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(Large, offset, bitIndex); + public sealed override void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(Large, offset, bitIndex, value); + + protected abstract int BadgeFlagStart { get; } + public abstract uint Coin { get; set; } + + public int Badges + { + get + { + int startFlag = BadgeFlagStart; + int val = 0; + for (int i = 0; i < 8; i++) + { + if (GetEventFlag(startFlag + i)) + val |= 1 << i; + } + + return val; + } + set + { + int startFlag = BadgeFlagStart; + for (int i = 0; i < 8; i++) + SetEventFlag(startFlag + i, (value & (1 << i)) != 0); + } + } + + public sealed override IReadOnlyList Inventory + { + get + { + var pouch = GetItems(); + foreach (var p in pouch) + { + if (p.Type != InventoryType.PCItems) + p.SecurityKey = SecurityKey; + } + return pouch.LoadAll(Large); + } + set => value.SaveAll(Large); + } + + protected abstract InventoryPouch3[] GetItems(); + + protected abstract int DaycareSlotSize { get; } + + public sealed override uint? GetDaycareEXP(int loc, int slot) => ReadUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(slot))); + public sealed override void SetDaycareEXP(int loc, int slot, uint EXP) => WriteUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(slot)), EXP); + public sealed override bool? IsDaycareOccupied(int loc, int slot) => IsPKMPresent(Large.AsSpan(GetDaycareSlotOffset(loc, slot))); + public sealed override void SetDaycareOccupied(int loc, int slot, bool occupied) { /* todo */ } + public sealed override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + (slot * DaycareSlotSize); + + protected abstract int EggEventFlag { get; } + public sealed override bool? IsDaycareHasEgg(int loc) => GetEventFlag(EggEventFlag); + public sealed override void SetDaycareHasEgg(int loc, bool hasEgg) => SetEventFlag(EggEventFlag, hasEgg); + + protected abstract int GetDaycareEXPOffset(int slot); + + #region Storage + public sealed override int GetBoxOffset(int box) => Box + 4 + (SIZE_STORED * box * COUNT_SLOTSPERBOX); + + public sealed override int CurrentBox + { + get => Storage[0]; + set => Storage[0] = (byte)value; + } + + public sealed override int GetBoxWallpaper(int box) + { + if (box > COUNT_BOX) + return box; + int offset = GetBoxWallpaperOffset(box); + return Storage[offset]; + } + + private const int COUNT_BOXNAME = 8 + 1; + + public sealed override void SetBoxWallpaper(int box, int value) + { + if (box > COUNT_BOX) + return; + int offset = GetBoxWallpaperOffset(box); + Storage[offset] = (byte)value; + } + + protected sealed override int GetBoxWallpaperOffset(int box) + { + int offset = GetBoxOffset(COUNT_BOX); + offset += (COUNT_BOX * COUNT_BOXNAME) + box; + return offset; + } + + public sealed override string GetBoxName(int box) + { + int offset = GetBoxOffset(COUNT_BOX); + return StringConverter3.GetString(Storage.AsSpan(offset + (box * COUNT_BOXNAME), COUNT_BOXNAME), Japanese); + } + + public sealed override void SetBoxName(int box, string value) + { + int offset = GetBoxOffset(COUNT_BOX); + var dest = Storage.AsSpan(offset + (box * COUNT_BOXNAME), COUNT_BOXNAME); + SetString(dest, value.AsSpan(), COUNT_BOXNAME - 1, StringConverterOption.ClearZero); + } + #endregion + + #region Pokédex + protected sealed override void SetDex(PKM pk) + { + int species = pk.Species; + if (species is 0 or > Legal.MaxSpeciesID_3) + return; + if (pk.IsEgg) + return; + + switch (species) + { + case (int)Species.Unown when !GetSeen(species): // Unown + DexPIDUnown = pk.PID; + break; + case (int)Species.Spinda when !GetSeen(species): // Spinda + DexPIDSpinda = pk.PID; + break; + } + SetCaught(species, true); + SetSeen(species, true); + } + + public uint DexPIDUnown { get => ReadUInt32LittleEndian(Small.AsSpan(PokeDex + 0x4)); set => WriteUInt32LittleEndian(Small.AsSpan(PokeDex + 0x4), value); } + public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Small.AsSpan(PokeDex + 0x8)); set => WriteUInt32LittleEndian(Small.AsSpan(PokeDex + 0x8), value); } + public int DexUnownForm => EntityPID.GetUnownForm3(DexPIDUnown); + + public sealed override bool GetCaught(int species) + { + int bit = species - 1; + int ofs = bit >> 3; + int caughtOffset = PokeDex + 0x10; + return FlagUtil.GetFlag(Small, caughtOffset + ofs, bit & 7); + } + + public sealed override void SetCaught(int species, bool caught) + { + int bit = species - 1; + int ofs = bit >> 3; + int caughtOffset = PokeDex + 0x10; + FlagUtil.SetFlag(Small, caughtOffset + ofs, bit & 7, caught); + } + + public sealed override bool GetSeen(int species) + { + int bit = species - 1; + int ofs = bit >> 3; + int seenOffset = PokeDex + 0x44; + return FlagUtil.GetFlag(Small, seenOffset + ofs, bit & 7); + } + + protected abstract int SeenOffset2 { get; } + protected abstract int SeenOffset3 { get; } + + public sealed override void SetSeen(int species, bool seen) + { + int bit = species - 1; + int ofs = bit >> 3; + + int seenOffset = PokeDex + 0x44; + FlagUtil.SetFlag(Small, seenOffset + ofs, bit & 7, seen); + FlagUtil.SetFlag(Large, SeenOffset2 + ofs, bit & 7, seen); + FlagUtil.SetFlag(Large, SeenOffset3 + ofs, bit & 7, seen); + } + + public byte PokedexSort + { + get => Small[PokeDex + 0x01]; + set => Small[PokeDex + 0x01] = value; + } + + public byte PokedexMode + { + get => Small[PokeDex + 0x01]; + set => Small[PokeDex + 0x01] = value; + } + + public byte PokedexNationalMagicRSE + { + get => Small[PokeDex + 0x02]; + set => Small[PokeDex + 0x02] = value; + } + + public byte PokedexNationalMagicFRLG + { + get => Small[PokeDex + 0x03]; + set => Small[PokeDex + 0x03] = value; + } + + protected const byte PokedexNationalUnlockRSE = 0xDA; + protected const byte PokedexNationalUnlockFRLG = 0xDA; + protected const ushort PokedexNationalUnlockWorkRSE = 0x0302; + protected const ushort PokedexNationalUnlockWorkFRLG = 0x6258; + + public abstract bool NationalDex { get; set; } + #endregion + + public sealed override string GetString(ReadOnlySpan data) => StringConverter3.GetString(data, Japanese); + + public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter3.SetString(destBuffer, value, maxLength, Japanese, option); + } + + protected abstract int MailOffset { get; } + public int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset; + + public MailDetail GetMail(int mailIndex) + { + var ofs = GetMailOffset(mailIndex); + var data = Large.Slice(ofs, Mail3.SIZE); + return new Mail3(data, ofs, Japanese); + } + + public abstract string EBerryName { get; } + public abstract bool IsEBerryEngima { get; } + public abstract MysteryEvent3 MysteryEvent { get; set; } + + public byte[] GetHallOfFameData() + { + // HoF Data is split across two sectors + byte[] data = new byte[SIZE_SECTOR_USED * 2]; + Data.AsSpan(0x1C000, SIZE_SECTOR_USED).CopyTo(data.AsSpan(0 , SIZE_SECTOR_USED)); + Data.AsSpan(0x1D000, SIZE_SECTOR_USED).CopyTo(data.AsSpan(SIZE_SECTOR_USED, SIZE_SECTOR_USED)); + return data; + } + + public void SetHallOfFameData(ReadOnlySpan value) + { + if (value.Length != SIZE_SECTOR_USED * 2) + throw new ArgumentException("Invalid size", nameof(value)); + // HoF Data is split across two sav sectors + Span savedata = Data; + value[..SIZE_SECTOR_USED].CopyTo(savedata[0x1C000..]); + value.Slice(SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(savedata[0x1D000..]); + } + + public bool IsCorruptPokedexFF() => MemoryMarshal.Read(Small.AsSpan(0xAC)) == ulong.MaxValue; + + public override void CopyChangesFrom(SaveFile sav) + { + SetData(sav.Data, 0); + var s3 = (SAV3)sav; + SetData(Small, s3.Small, 0); + SetData(Large, s3.Large, 0); + SetData(Storage, s3.Storage, 0); + } + + #region External Connections + protected abstract int ExternalEventData { get; } + protected int ExternalEventFlags => ExternalEventData + 0x14; + + public uint ColosseumRaw1 + { + get => ReadUInt32LittleEndian(Large.AsSpan(ExternalEventData + 7)); + set => WriteUInt32LittleEndian(Large.AsSpan(ExternalEventData + 7), value); + } + + public uint ColosseumRaw2 + { + get => ReadUInt32LittleEndian(Large.AsSpan(ExternalEventData + 11)); + set => WriteUInt32LittleEndian(Large.AsSpan(ExternalEventData + 11), value); + } + + /// + /// PokéCoupons stored by Pokémon Colosseum and XD from Mt. Battle runs. Earned PokéCoupons are also added to . + /// + /// Colosseum/XD caps this at 9,999,999, but will read up to 16,777,215. + public uint ColosseumCoupons + { + get => ColosseumRaw1 >> 8; + set => ColosseumRaw1 = (value << 8) | (ColosseumRaw1 & 0xFF); + } + + /// Master Ball from JP Colosseum Bonus Disc; for reaching 30,000 + public bool ColosseumPokeCouponTitleGold + { + get => (ColosseumRaw2 & (1 << 0)) != 0; + set => ColosseumRaw2 = (ColosseumRaw2 & (1 << 0)) | ((value ? 1u : 0) << 0); + } + + /// Light Ball Pikachu from JP Colosseum Bonus Disc; for reaching 5000 + public bool ColosseumPokeCouponTitleSilver + { + get => (ColosseumRaw2 & (1 << 1)) != 0; + set => ColosseumRaw2 = (ColosseumRaw2 & (1 << 1)) | ((value ? 1u : 0) << 1); + } + + /// PP Max from JP Colosseum Bonus Disc; for reaching 2500 + public bool ColosseumPokeCouponTitleBronze + { + get => (ColosseumRaw2 & (1 << 2)) != 0; + set => ColosseumRaw2 = (ColosseumRaw2 & (1 << 2)) | ((value ? 1u : 0) << 2); + } + + /// Received Celebi Gift from JP Colosseum Bonus Disc + public bool ColosseumReceivedAgeto + { + get => (ColosseumRaw2 & (1 << 3)) != 0; + set => ColosseumRaw2 = (ColosseumRaw2 & (1 << 3)) | ((value ? 1u : 0) << 3); + } + + /// + /// Used by the JP Colosseum bonus disc. Determines PokéCoupon rank to distribute rewards. Unread in International games. + /// + /// + /// Colosseum/XD caps this at 9,999,999. + /// + public uint ColosseumCouponsTotal + { + get => ColosseumRaw2 >> 8; + set => ColosseumRaw2 = (value << 8) | (ColosseumRaw2 & 0xFF); + } + + /// Indicates if this save has connected to RSBOX and triggered the free False Swipe Swablu Egg giveaway. + public bool HasUsedRSBOX + { + get => GetFlag(ExternalEventFlags + 0, 0); + set => SetFlag(ExternalEventFlags + 0, 0, value); + } + + /// + /// 1 for ExtremeSpeed Zigzagoon (at 100 deposited), 2 for Pay Day Skitty (at 500 deposited), 3 for Surf Pichu (at 1499 deposited) + /// + public int RSBoxDepositEggsUnlocked + { + get => (Large[ExternalEventFlags] >> 1) & 3; + set => Large[ExternalEventFlags] = (byte)((Large[ExternalEventFlags] & ~(3 << 1)) | ((value & 3) << 1)); + } + + /// Received Jirachi Gift from Colosseum Bonus Disc + public bool HasReceivedWishmkrJirachi + { + get => GetFlag(ExternalEventFlags + 2, 0); + set => SetFlag(ExternalEventFlags + 2, 0, value); + } + #endregion } diff --git a/PKHeX.Core/Saves/SAV3Colosseum.cs b/PKHeX.Core/Saves/SAV3Colosseum.cs index 341d62cf3..4d17601d9 100644 --- a/PKHeX.Core/Saves/SAV3Colosseum.cs +++ b/PKHeX.Core/Saves/SAV3Colosseum.cs @@ -3,400 +3,396 @@ using System.Security.Cryptography; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 object for Pokémon Colosseum saves. +/// +public sealed class SAV3Colosseum : SaveFile, IGCSaveFile { - /// - /// Generation 3 object for Pokémon Colosseum saves. - /// - public sealed class SAV3Colosseum : SaveFile, IGCSaveFile + protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}"; + public override string Extension => this.GCExtension(); + public override PersonalTable Personal => PersonalTable.RS; + public override IReadOnlyList HeldItems => Legal.HeldItems_COLO; + public SAV3GCMemoryCard? MemoryCard { get; init; } + + // 3 Save files are stored + // 0x0000-0x6000 contains memory card data + // 0x6000-0x60000 contains the 3 save slots + // 0x5A000 / 3 = 0x1E000 per save slot + // Checksum is SHA1 over 0-0x1DFD7, stored in the last 20 bytes of the save slot. + // Another SHA1 hash is 0x1DFD8, for 20 bytes. Unknown purpose. + // Checksum is used as the crypto key. + + private const int SLOT_SIZE = 0x1E000; + private const int SLOT_START = 0x6000; + private const int SLOT_COUNT = 3; + + private int SaveCount = -1; + private int SaveIndex = -1; + private readonly StrategyMemo StrategyMemo; + public const int MaxShadowID = 0x80; // 128 + private int Memo; + + private readonly byte[] BAK; + + public SAV3Colosseum() : base(SaveUtil.SIZE_G3COLO) { - protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}"; - public override string Extension => this.GCExtension(); - public override PersonalTable Personal => PersonalTable.RS; - public override IReadOnlyList HeldItems => Legal.HeldItems_COLO; - public SAV3GCMemoryCard? MemoryCard { get; init; } + BAK = Array.Empty(); + StrategyMemo = Initialize(); + ClearBoxes(); + } - // 3 Save files are stored - // 0x0000-0x6000 contains memory card data - // 0x6000-0x60000 contains the 3 save slots - // 0x5A000 / 3 = 0x1E000 per save slot - // Checksum is SHA1 over 0-0x1DFD7, stored in the last 20 bytes of the save slot. - // Another SHA1 hash is 0x1DFD8, for 20 bytes. Unknown purpose. - // Checksum is used as the crypto key. + public SAV3Colosseum(byte[] data) : base(data) + { + BAK = data; + InitializeData(); + StrategyMemo = Initialize(); + } - private const int SLOT_SIZE = 0x1E000; - private const int SLOT_START = 0x6000; - private const int SLOT_COUNT = 3; + private StrategyMemo Initialize() + { + // Trainer1 = 0x00078; + Party = 0x000A8; - private int SaveCount = -1; - private int SaveIndex = -1; - private readonly StrategyMemo StrategyMemo; - public const int MaxShadowID = 0x80; // 128 - private int Memo; + Box = 0x00B90; + DaycareOffset = 0x08170; + Memo = 0x082B0; - private readonly byte[] BAK; - - public SAV3Colosseum() : base(SaveUtil.SIZE_G3COLO) + // Since PartyCount is not stored in the save file, + // Count up how many party slots are active. + for (int i = 0; i < 6; i++) { - BAK = Array.Empty(); - StrategyMemo = Initialize(); - ClearBoxes(); + if (GetPartySlot(Data, GetPartyOffset(i)).Species != 0) + PartyCount++; } - public SAV3Colosseum(byte[] data) : base(data) + var memo = new StrategyMemo(Data, Memo, xd: false); + return memo; + } + + private void InitializeData() + { + // Scan all 3 save slots for the highest counter + for (int i = 0; i < SLOT_COUNT; i++) { - BAK = data; - InitializeData(); - StrategyMemo = Initialize(); + int slotOffset = SLOT_START + (i * SLOT_SIZE); + int SaveCounter = ReadInt32BigEndian(Data.AsSpan(slotOffset + 4)); + if (SaveCounter <= SaveCount) + continue; + + SaveCount = SaveCounter; + SaveIndex = i; } - private StrategyMemo Initialize() + // Decrypt most recent save slot { - // Trainer1 = 0x00078; - Party = 0x000A8; - - Box = 0x00B90; - DaycareOffset = 0x08170; - Memo = 0x082B0; - - // Since PartyCount is not stored in the save file, - // Count up how many party slots are active. - for (int i = 0; i < 6; i++) - { - if (GetPartySlot(Data, GetPartyOffset(i)).Species != 0) - PartyCount++; - } - - var memo = new StrategyMemo(Data, Memo, xd: false); - return memo; - } - - private void InitializeData() - { - // Scan all 3 save slots for the highest counter - for (int i = 0; i < SLOT_COUNT; i++) - { - int slotOffset = SLOT_START + (i * SLOT_SIZE); - int SaveCounter = ReadInt32BigEndian(Data.AsSpan(slotOffset + 4)); - if (SaveCounter <= SaveCount) - continue; - - SaveCount = SaveCounter; - SaveIndex = i; - } - - // Decrypt most recent save slot - { - int slotOffset = SLOT_START + (SaveIndex * SLOT_SIZE); - ReadOnlySpan slot = Data.AsSpan(slotOffset, SLOT_SIZE); - Span digest = stackalloc byte[20]; - slot[^20..].CopyTo(digest); - - // Decrypt Slot - Data = DecryptColosseum(slot, digest); - } - } - - protected override byte[] GetFinalData() - { - var newFile = GetInnerData(); - - // Return the gci if Memory Card is not being exported - if (MemoryCard is null) - return newFile; - - MemoryCard.WriteSaveGameData(newFile); - return MemoryCard.Data; - } - - private byte[] GetInnerData() - { - StrategyMemo.Write().CopyTo(Data, Memo); - SetChecksums(); - - // Get updated save slot data - ReadOnlySpan slot = Data; + int slotOffset = SLOT_START + (SaveIndex * SLOT_SIZE); + ReadOnlySpan slot = Data.AsSpan(slotOffset, SLOT_SIZE); Span digest = stackalloc byte[20]; slot[^20..].CopyTo(digest); - byte[] newSAV = EncryptColosseum(slot, digest); - // Put save slot back in original save data - byte[] newFile = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[])BAK.Clone(); - Array.Copy(newSAV, 0, newFile, SLOT_START + (SaveIndex * SLOT_SIZE), newSAV.Length); + // Decrypt Slot + Data = DecryptColosseum(slot, digest); + } + } + + protected override byte[] GetFinalData() + { + var newFile = GetInnerData(); + + // Return the gci if Memory Card is not being exported + if (MemoryCard is null) return newFile; - } - // Configuration - protected override SaveFile CloneInternal() + MemoryCard.WriteSaveGameData(newFile); + return MemoryCard.Data; + } + + private byte[] GetInnerData() + { + StrategyMemo.Write().CopyTo(Data, Memo); + SetChecksums(); + + // Get updated save slot data + ReadOnlySpan slot = Data; + Span digest = stackalloc byte[20]; + slot[^20..].CopyTo(digest); + byte[] newSAV = EncryptColosseum(slot, digest); + + // Put save slot back in original save data + byte[] newFile = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[])BAK.Clone(); + Array.Copy(newSAV, 0, newFile, SLOT_START + (SaveIndex * SLOT_SIZE), newSAV.Length); + return newFile; + } + + // Configuration + protected override SaveFile CloneInternal() + { + var data = GetInnerData(); + return new SAV3Colosseum(data) { MemoryCard = MemoryCard }; + } + + protected override int SIZE_STORED => PokeCrypto.SIZE_3CSTORED; + protected override int SIZE_PARTY => PokeCrypto.SIZE_3CSTORED; // unused + public override PKM BlankPKM => new CK3(); + public override Type PKMType => typeof(CK3); + + public override int MaxMoveID => Legal.MaxMoveID_3; + public override int MaxSpeciesID => Legal.MaxSpeciesID_3; + public override int MaxAbilityID => Legal.MaxAbilityID_3; + public override int MaxBallID => Legal.MaxBallID_3; + public override int MaxItemID => Legal.MaxItemID_3_COLO; + public override int MaxGameID => Legal.MaxGameID_3; + + public override int MaxEV => 255; + public override int Generation => 3; + public override EntityContext Context => EntityContext.Gen3; + protected override int GiftCountMax => 1; + public override int OTLength => 10; // as evident by Mattle Ho-Oh + public override int NickLength => 10; + public override int MaxMoney => 9999999; + + public override int BoxCount => 3; + public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGC(data); + + private static byte[] EncryptColosseum(ReadOnlySpan input, Span digest) + { + if (input.Length != SLOT_SIZE) + throw new ArgumentException(nameof(input)); + + byte[] output = input.ToArray(); + + // NOT key + for (int i = 0; i < 20; i++) + digest[i] = (byte)~digest[i]; + + using var sha1 = SHA1.Create(); + for (int i = 0x18; i < 0x1DFD8; i += 20) { - var data = GetInnerData(); - return new SAV3Colosseum(data) { MemoryCard = MemoryCard }; + for (int j = 0; j < 20; j++) + output[i + j] ^= digest[j]; + byte[] key = sha1.ComputeHash(output, i, 20); // update digest + key.AsSpan(0, 20).CopyTo(digest); // for use in next loop } + return output; + } - protected override int SIZE_STORED => PokeCrypto.SIZE_3CSTORED; - protected override int SIZE_PARTY => PokeCrypto.SIZE_3CSTORED; // unused - public override PKM BlankPKM => new CK3(); - public override Type PKMType => typeof(CK3); + private static byte[] DecryptColosseum(ReadOnlySpan input, Span digest) + { + if (input.Length != SLOT_SIZE) + throw new ArgumentException(nameof(input)); - public override int MaxMoveID => Legal.MaxMoveID_3; - public override int MaxSpeciesID => Legal.MaxSpeciesID_3; - public override int MaxAbilityID => Legal.MaxAbilityID_3; - public override int MaxBallID => Legal.MaxBallID_3; - public override int MaxItemID => Legal.MaxItemID_3_COLO; - public override int MaxGameID => Legal.MaxGameID_3; + byte[] output = input.ToArray(); - public override int MaxEV => 255; - public override int Generation => 3; - public override EntityContext Context => EntityContext.Gen3; - protected override int GiftCountMax => 1; - public override int OTLength => 10; // as evident by Mattle Ho-Oh - public override int NickLength => 10; - public override int MaxMoney => 9999999; + // NOT key + for (int i = 0; i < 20; i++) + digest[i] = (byte)~digest[i]; - public override int BoxCount => 3; - public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGC(data); - - private static byte[] EncryptColosseum(ReadOnlySpan input, Span digest) + using var sha1 = SHA1.Create(); + for (int i = 0x18; i < 0x1DFD8; i += 20) { - if (input.Length != SLOT_SIZE) - throw new ArgumentException(nameof(input)); - - byte[] output = input.ToArray(); - - // NOT key - for (int i = 0; i < 20; i++) - digest[i] = (byte)~digest[i]; - - using var sha1 = SHA1.Create(); - for (int i = 0x18; i < 0x1DFD8; i += 20) - { - for (int j = 0; j < 20; j++) - output[i + j] ^= digest[j]; - byte[] key = sha1.ComputeHash(output, i, 20); // update digest - key.AsSpan(0, 20).CopyTo(digest); // for use in next loop - } - return output; + byte[] key = sha1.ComputeHash(output, i, 20); // update digest + for (int j = 0; j < 20; j++) + output[i + j] ^= digest[j]; + key.AsSpan(0, 20).CopyTo(digest); // for use in next loop } + return output; + } - private static byte[] DecryptColosseum(ReadOnlySpan input, Span digest) - { - if (input.Length != SLOT_SIZE) - throw new ArgumentException(nameof(input)); - - byte[] output = input.ToArray(); - - // NOT key - for (int i = 0; i < 20; i++) - digest[i] = (byte)~digest[i]; - - using var sha1 = SHA1.Create(); - for (int i = 0x18; i < 0x1DFD8; i += 20) - { - byte[] key = sha1.ComputeHash(output, i, 20); // update digest - for (int j = 0; j < 20; j++) - output[i + j] ^= digest[j]; - key.AsSpan(0, 20).CopyTo(digest); // for use in next loop - } - return output; - } - - protected override void SetChecksums() + protected override void SetChecksums() + { + // Clear Header Checksum + var headerCHK = Data.AsSpan(12); + WriteInt32BigEndian(headerCHK, 0); + // Compute checksum of data + using var sha1 = SHA1.Create(); + byte[] checksum = sha1.ComputeHash(Data, 0, 0x1DFD8); + // Set Checksum to end + var checkSpan = checksum.AsSpan(); + checkSpan.CopyTo(Data.AsSpan(Data.Length - checkSpan.Length)); + + // Compute new header checksum + var header = Data.AsSpan(0, 0x20); + int newHC = 0; + for (int i = 0; i < 0x18; i += 4) + newHC -= ReadInt32BigEndian(header[i..]); + newHC -= ReadInt32BigEndian(header[0x18..]) ^ ~ReadInt32BigEndian(checkSpan); + newHC -= ReadInt32BigEndian(header[0x1C..]) ^ ~ReadInt32BigEndian(checkSpan[4..]); + + // Set Header Checksum + WriteInt32BigEndian(headerCHK, newHC); + } + + public override bool ChecksumsValid => !ChecksumInfo.Contains("Invalid"); + + public override string ChecksumInfo + { + get { + byte[] data = (byte[])Data.Clone(); + var hc = data.AsSpan(12); + int oldHC = ReadInt32BigEndian(hc); // Clear Header Checksum - var headerCHK = Data.AsSpan(12); - WriteInt32BigEndian(headerCHK, 0); - // Compute checksum of data + WriteUInt32BigEndian(hc, 0); using var sha1 = SHA1.Create(); - byte[] checksum = sha1.ComputeHash(Data, 0, 0x1DFD8); - // Set Checksum to end + byte[] checksum = sha1.ComputeHash(data, 0, 0x1DFD8); var checkSpan = checksum.AsSpan(); - checkSpan.CopyTo(Data.AsSpan(Data.Length - checkSpan.Length)); // Compute new header checksum - var header = Data.AsSpan(0, 0x20); + var header = data.AsSpan(0, 0x20); int newHC = 0; for (int i = 0; i < 0x18; i += 4) newHC -= ReadInt32BigEndian(header[i..]); + newHC -= ReadInt32BigEndian(header[0x18..]) ^ ~ReadInt32BigEndian(checkSpan); newHC -= ReadInt32BigEndian(header[0x1C..]) ^ ~ReadInt32BigEndian(checkSpan[4..]); - // Set Header Checksum - WriteInt32BigEndian(headerCHK, newHC); - } + var chk = data.AsSpan(data.Length - 20, 20); - public override bool ChecksumsValid => !ChecksumInfo.Contains("Invalid"); - - public override string ChecksumInfo - { - get - { - byte[] data = (byte[])Data.Clone(); - var hc = data.AsSpan(12); - int oldHC = ReadInt32BigEndian(hc); - // Clear Header Checksum - WriteUInt32BigEndian(hc, 0); - using var sha1 = SHA1.Create(); - byte[] checksum = sha1.ComputeHash(data, 0, 0x1DFD8); - var checkSpan = checksum.AsSpan(); - - // Compute new header checksum - var header = data.AsSpan(0, 0x20); - int newHC = 0; - for (int i = 0; i < 0x18; i += 4) - newHC -= ReadInt32BigEndian(header[i..]); - - newHC -= ReadInt32BigEndian(header[0x18..]) ^ ~ReadInt32BigEndian(checkSpan); - newHC -= ReadInt32BigEndian(header[0x1C..]) ^ ~ReadInt32BigEndian(checkSpan[4..]); - - var chk = data.AsSpan(data.Length - 20, 20); - - bool isHeaderValid = newHC == oldHC; - bool isBodyValid = chk.SequenceEqual(checkSpan); - static string valid(bool s) => s ? "Valid" : "Invalid"; - return $"Header Checksum {valid(isHeaderValid)}, Body Checksum {valid(isBodyValid)}."; - } - } - - // Trainer Info - public override GameVersion Version { get => GameVersion.COLO; protected set { } } - - // Storage - public override int GetPartyOffset(int slot) - { - return Party + (SIZE_STORED * slot); - } - - public override int GetBoxOffset(int box) - { - return Box + (((30 * SIZE_STORED) + 0x14)*box) + 0x14; - } - - public override string GetBoxName(int box) - { - return GetString(Box + (0x24A4 * box), 16); - } - - public override void SetBoxName(int box, string value) - { - SetString(Data.AsSpan(Box + (0x24A4 * box), 16), value.AsSpan(), 8, StringConverterOption.ClearZero); - } - - protected override PKM GetPKM(byte[] data) - { - if (data.Length != SIZE_STORED) - Array.Resize(ref data, SIZE_STORED); - return new CK3(data); - } - - protected override byte[] DecryptPKM(byte[] data) - { - return data; - } - - protected override void SetPKM(PKM pkm, bool isParty = false) - { - if (pkm is not CK3 pk) - return; - - if (pk.CurrentRegion == 0) - pk.CurrentRegion = 2; // NTSC-U - if (pk.OriginalRegion == 0) - pk.OriginalRegion = 2; // NTSC-U - } - - protected override void SetDex(PKM pkm) - { - if (pkm.Species is 0 or > Legal.MaxSpeciesID_3) - return; - if (pkm.IsEgg) - return; - - // Dex Related - var entry = StrategyMemo.GetEntry(pkm.Species); - if (entry.IsEmpty) // Populate - { - entry.Species = pkm.Species; - entry.PID = pkm.PID; - entry.TID = pkm.TID; - entry.SID = pkm.SID; - } - if (entry.Matches(pkm.Species, pkm.PID, pkm.TID, pkm.SID)) - { - entry.Seen = true; - entry.Owned = true; - } - StrategyMemo.SetEntry(entry); - } - - private TimeSpan PlayedSpan - { - get => TimeSpan.FromSeconds((double)(ReadUInt32BigEndian(Data.AsSpan(40)) - 0x47000000) / 128); - set => WriteUInt32BigEndian(Data.AsSpan(40), (uint)(value.TotalSeconds * 128) + 0x47000000); - } - - public override int PlayedHours - { - get => (ushort)PlayedSpan.Hours; - set { var time = PlayedSpan; PlayedSpan = time - TimeSpan.FromHours(time.Hours) + TimeSpan.FromHours(value); } - } - - public override int PlayedMinutes - { - get => (byte)PlayedSpan.Minutes; - set { var time = PlayedSpan; PlayedSpan = time - TimeSpan.FromMinutes(time.Minutes) + TimeSpan.FromMinutes(value); } - } - - public override int PlayedSeconds - { - get => (byte)PlayedSpan.Seconds; - set { var time = PlayedSpan; PlayedSpan = time - TimeSpan.FromSeconds(time.Seconds) + TimeSpan.FromSeconds(value); } - } - - // Trainer Info (offset 0x78, length 0xB18, end @ 0xB90) - public override string OT { get => GetString(0x78, 20); set { SetString(Data.AsSpan(0x78, 20), value.AsSpan(), 10, StringConverterOption.ClearZero); OT2 = value; } } - public string OT2 { get => GetString(0x8C, 20); set => SetString(Data.AsSpan(0x8C, 20), value.AsSpan(), 10, StringConverterOption.ClearZero); } - public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(0xA4)); set => WriteUInt16BigEndian(Data.AsSpan(0xA4), (ushort)value); } - public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(0xA6)); set => WriteUInt16BigEndian(Data.AsSpan(0xA6), (ushort)value); } - - public override int Gender { get => Data[0xAF8]; set => Data[0xAF8] = (byte)value; } - public override uint Money { get => ReadUInt32BigEndian(Data.AsSpan(0xAFC)); set => WriteUInt32BigEndian(Data.AsSpan(0xAFC), value); } - public uint Coupons { get => ReadUInt32BigEndian(Data.AsSpan(0xB00)); set => WriteUInt32BigEndian(Data.AsSpan(0xB00), value); } - public string RUI_Name { get => GetString(0xB3A, 20); set => SetString(Data.AsSpan(0xB3A, 20), value.AsSpan(), 10, StringConverterOption.ClearZero); } - - public override IReadOnlyList Inventory - { - get - { - InventoryPouch[] pouch = - { - new InventoryPouch3GC(InventoryType.Items, Legal.Pouch_Items_COLO, 99, 0x007F8, 20), // 20 COLO, 30 XD - new InventoryPouch3GC(InventoryType.KeyItems, Legal.Pouch_Key_COLO, 1, 0x00848, 43), - new InventoryPouch3GC(InventoryType.Balls, Legal.Pouch_Ball_RS, 99, 0x008F4, 16), - new InventoryPouch3GC(InventoryType.TMHMs, Legal.Pouch_TM_RS, 99, 0x00934, 64), // no HMs - new InventoryPouch3GC(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, 0x00A34, 46), - new InventoryPouch3GC(InventoryType.Medicine, Legal.Pouch_Cologne_COLO, 99, 0x00AEC, 3), // Cologne - }; - return pouch.LoadAll(Data); - } - set => value.SaveAll(Data); - } - - // Daycare Structure: - // 0x00 -- Occupied - // 0x01 -- Deposited Level - // 0x02-0x03 -- unused? - // 0x04-0x07 -- Initial EXP - public override int GetDaycareSlotOffset(int loc, int slot) { return DaycareOffset + 8; } - public override uint? GetDaycareEXP(int loc, int slot) { return null; } - public override bool? IsDaycareOccupied(int loc, int slot) { return null; } - public override void SetDaycareEXP(int loc, int slot, uint EXP) { } - public override void SetDaycareOccupied(int loc, int slot, bool occupied) { } - - public override string GetString(ReadOnlySpan data) => StringConverter3GC.GetString(data); - - public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter3GC.SetString(destBuffer, value, maxLength, option); + bool isHeaderValid = newHC == oldHC; + bool isBodyValid = chk.SequenceEqual(checkSpan); + static string valid(bool s) => s ? "Valid" : "Invalid"; + return $"Header Checksum {valid(isHeaderValid)}, Body Checksum {valid(isBodyValid)}."; } } + + // Trainer Info + public override GameVersion Version { get => GameVersion.COLO; protected set { } } + + // Storage + public override int GetPartyOffset(int slot) + { + return Party + (SIZE_STORED * slot); + } + + public override int GetBoxOffset(int box) + { + return Box + (((30 * SIZE_STORED) + 0x14)*box) + 0x14; + } + + public override string GetBoxName(int box) + { + return GetString(Box + (0x24A4 * box), 16); + } + + public override void SetBoxName(int box, string value) + { + SetString(Data.AsSpan(Box + (0x24A4 * box), 16), value.AsSpan(), 8, StringConverterOption.ClearZero); + } + + protected override PKM GetPKM(byte[] data) + { + if (data.Length != SIZE_STORED) + Array.Resize(ref data, SIZE_STORED); + return new CK3(data); + } + + protected override byte[] DecryptPKM(byte[] data) => data; + + protected override void SetPKM(PKM pk, bool isParty = false) + { + if (pk is not CK3 ck3) + return; + + if (ck3.CurrentRegion == 0) + ck3.CurrentRegion = 2; // NTSC-U + if (ck3.OriginalRegion == 0) + ck3.OriginalRegion = 2; // NTSC-U + } + + protected override void SetDex(PKM pk) + { + if (pk.Species is 0 or > Legal.MaxSpeciesID_3) + return; + if (pk.IsEgg) + return; + + // Dex Related + var entry = StrategyMemo.GetEntry(pk.Species); + if (entry.IsEmpty) // Populate + { + entry.Species = pk.Species; + entry.PID = pk.PID; + entry.TID = pk.TID; + entry.SID = pk.SID; + } + if (entry.Matches(pk.Species, pk.PID, pk.TID, pk.SID)) + { + entry.Seen = true; + entry.Owned = true; + } + StrategyMemo.SetEntry(entry); + } + + private TimeSpan PlayedSpan + { + get => TimeSpan.FromSeconds((double)(ReadUInt32BigEndian(Data.AsSpan(40)) - 0x47000000) / 128); + set => WriteUInt32BigEndian(Data.AsSpan(40), (uint)(value.TotalSeconds * 128) + 0x47000000); + } + + public override int PlayedHours + { + get => (ushort)PlayedSpan.Hours; + set { var time = PlayedSpan; PlayedSpan = time - TimeSpan.FromHours(time.Hours) + TimeSpan.FromHours(value); } + } + + public override int PlayedMinutes + { + get => (byte)PlayedSpan.Minutes; + set { var time = PlayedSpan; PlayedSpan = time - TimeSpan.FromMinutes(time.Minutes) + TimeSpan.FromMinutes(value); } + } + + public override int PlayedSeconds + { + get => (byte)PlayedSpan.Seconds; + set { var time = PlayedSpan; PlayedSpan = time - TimeSpan.FromSeconds(time.Seconds) + TimeSpan.FromSeconds(value); } + } + + // Trainer Info (offset 0x78, length 0xB18, end @ 0xB90) + public override string OT { get => GetString(0x78, 20); set { SetString(Data.AsSpan(0x78, 20), value.AsSpan(), 10, StringConverterOption.ClearZero); OT2 = value; } } + public string OT2 { get => GetString(0x8C, 20); set => SetString(Data.AsSpan(0x8C, 20), value.AsSpan(), 10, StringConverterOption.ClearZero); } + public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(0xA4)); set => WriteUInt16BigEndian(Data.AsSpan(0xA4), (ushort)value); } + public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(0xA6)); set => WriteUInt16BigEndian(Data.AsSpan(0xA6), (ushort)value); } + + public override int Gender { get => Data[0xAF8]; set => Data[0xAF8] = (byte)value; } + public override uint Money { get => ReadUInt32BigEndian(Data.AsSpan(0xAFC)); set => WriteUInt32BigEndian(Data.AsSpan(0xAFC), value); } + public uint Coupons { get => ReadUInt32BigEndian(Data.AsSpan(0xB00)); set => WriteUInt32BigEndian(Data.AsSpan(0xB00), value); } + public string RUI_Name { get => GetString(0xB3A, 20); set => SetString(Data.AsSpan(0xB3A, 20), value.AsSpan(), 10, StringConverterOption.ClearZero); } + + public override IReadOnlyList Inventory + { + get + { + InventoryPouch[] pouch = + { + new InventoryPouch3GC(InventoryType.Items, Legal.Pouch_Items_COLO, 99, 0x007F8, 20), // 20 COLO, 30 XD + new InventoryPouch3GC(InventoryType.KeyItems, Legal.Pouch_Key_COLO, 1, 0x00848, 43), + new InventoryPouch3GC(InventoryType.Balls, Legal.Pouch_Ball_RS, 99, 0x008F4, 16), + new InventoryPouch3GC(InventoryType.TMHMs, Legal.Pouch_TM_RS, 99, 0x00934, 64), // no HMs + new InventoryPouch3GC(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, 0x00A34, 46), + new InventoryPouch3GC(InventoryType.Medicine, Legal.Pouch_Cologne_COLO, 99, 0x00AEC, 3), // Cologne + }; + return pouch.LoadAll(Data); + } + set => value.SaveAll(Data); + } + + // Daycare Structure: + // 0x00 -- Occupied + // 0x01 -- Deposited Level + // 0x02-0x03 -- unused? + // 0x04-0x07 -- Initial EXP + public override int GetDaycareSlotOffset(int loc, int slot) { return DaycareOffset + 8; } + public override uint? GetDaycareEXP(int loc, int slot) { return null; } + public override bool? IsDaycareOccupied(int loc, int slot) { return null; } + public override void SetDaycareEXP(int loc, int slot, uint EXP) { } + public override void SetDaycareOccupied(int loc, int slot, bool occupied) { } + + public override string GetString(ReadOnlySpan data) => StringConverter3GC.GetString(data); + + public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter3GC.SetString(destBuffer, value, maxLength, option); + } } diff --git a/PKHeX.Core/Saves/SAV3E.cs b/PKHeX.Core/Saves/SAV3E.cs index 22c31c288..cfdfe3c3d 100644 --- a/PKHeX.Core/Saves/SAV3E.cs +++ b/PKHeX.Core/Saves/SAV3E.cs @@ -2,235 +2,234 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 object for . +/// +/// +public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful, IGen3Wonder { - /// - /// Generation 3 object for . - /// - /// - public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful, IGen3Wonder + // Configuration + protected override SaveFile CloneInternal() => new SAV3E(Write()); + public override GameVersion Version { get => GameVersion.E; protected set { } } + public override PersonalTable Personal => PersonalTable.E; + + public override int EventFlagCount => 8 * 300; + public override int EventWorkCount => 0x100; + protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp + public override int DaycareSeedSize => 8; // 32bit + protected override int EggEventFlag => 0x86; + protected override int BadgeFlagStart => 0x867; + + public SAV3E(byte[] data) : base(data) => Initialize(); + public SAV3E(bool japanese = false) : base(japanese) => Initialize(); + + protected override int EventFlag => 0x1270; + protected override int EventWork => 0x139C; + + private void Initialize() { - // Configuration - protected override SaveFile CloneInternal() => new SAV3E(Write()); - public override GameVersion Version { get => GameVersion.E; protected set { } } - public override PersonalTable Personal => PersonalTable.E; + // small + PokeDex = 0x18; - public override int EventFlagCount => 8 * 300; - public override int EventWorkCount => 0x100; - protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp - public override int DaycareSeedSize => 8; // 32bit - protected override int EggEventFlag => 0x86; - protected override int BadgeFlagStart => 0x867; + // large + DaycareOffset = 0x3030; - public SAV3E(byte[] data) : base(data) => Initialize(); - public SAV3E(bool japanese = false) : base(japanese) => Initialize(); + // storage + Box = 0; + } - protected override int EventFlag => 0x1270; - protected override int EventWork => 0x139C; - - private void Initialize() + #region Small + public override bool NationalDex + { + get => PokedexNationalMagicRSE == PokedexNationalUnlockRSE; + set { - // small - PokeDex = 0x18; - - // large - DaycareOffset = 0x3030; - - // storage - Box = 0; - } - - #region Small - public override bool NationalDex - { - get => PokedexNationalMagicRSE == PokedexNationalUnlockRSE; - set - { - PokedexMode = value ? (byte)1 : (byte)0; // mode - PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0; // magic - SetEventFlag(0x896, value); - SetWork(0x46, PokedexNationalUnlockWorkRSE); - } - } - - public override uint SecurityKey - { - get => ReadUInt32LittleEndian(Small.AsSpan(0xAC)); - set => WriteUInt32LittleEndian(Small.AsSpan(0xAC), value); - } - - public RTC3 ClockInitial - { - get => new(GetData(Small, 0x98, RTC3.Size)); - set => SetData(Small, value.Data, 0x98); - } - - public RTC3 ClockElapsed - { - get => new(GetData(Small, 0xA0, RTC3.Size)); - set => SetData(Small, value.Data, 0xA0); - } - - public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Small.AsSpan(0x1FC)); set => WriteUInt16LittleEndian(Small.AsSpan(0x1FC), Math.Min((ushort)9999, value)); } - // u16 field2; - public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Small.AsSpan(0x200)); set => WriteUInt16LittleEndian(Small.AsSpan(0x200), Math.Min((ushort)9999, value)); } - public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Small.AsSpan(0x202)); set => WriteUInt16LittleEndian(Small.AsSpan(0x202), Math.Min((ushort)9999, value)); } - // u32 field8; - public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Small.AsSpan(0x208)); set => WriteUInt32LittleEndian(Small.AsSpan(0x208), Math.Min(9999, value)); } - - public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Small.AsSpan(0x20C)); set => WriteUInt32LittleEndian(Small.AsSpan(0x20C), Math.Min(9999, value)); } - public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Small.AsSpan(0x210)); set => WriteUInt16LittleEndian(Small.AsSpan(0x210), Math.Min((ushort)9999, value)); } - public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Small.AsSpan(0x212)); set => WriteUInt16LittleEndian(Small.AsSpan(0x212), Math.Min((ushort)9999, value)); } - - public uint BP - { - get => ReadUInt16LittleEndian(Small.AsSpan(0xEB8)); - set - { - if (value > 9999) - value = 9999; - WriteUInt16LittleEndian(Small.AsSpan(0xEB8), (ushort)value); - } - } - - public uint BPEarned - { - get => ReadUInt16LittleEndian(Small.AsSpan(0xEBA)); - set - { - if (value > 65535) - value = 65535; - WriteUInt16LittleEndian(Small.AsSpan(0xEBA), (ushort)value); - } - } - #endregion - - #region Large - public override int PartyCount { get => Large[0x234]; protected set => Large[0x234] = (byte)value; } - public override int GetPartyOffset(int slot) => 0x238 + (SIZE_PARTY * slot); - - public override uint Money - { - get => ReadUInt32LittleEndian(Large.AsSpan(0x0490)) ^ SecurityKey; - set => WriteUInt32LittleEndian(Large.AsSpan(0x0490), value ^ SecurityKey); - } - - public override uint Coin - { - get => (ushort)(ReadUInt16LittleEndian(Large.AsSpan(0x0494)) ^ SecurityKey); - set => WriteUInt16LittleEndian(Large.AsSpan(0x0494), (ushort)(value ^ SecurityKey)); - } - - private const int OFS_PCItem = 0x0498; - private const int OFS_PouchHeldItem = 0x0560; - private const int OFS_PouchKeyItem = 0x05D8; - private const int OFS_PouchBalls = 0x0650; - private const int OFS_PouchTMHM = 0x0690; - private const int OFS_PouchBerry = 0x0790; - - protected override InventoryPouch3[] GetItems() - { - const int max = 99; - var PCItems = ArrayUtil.ConcatAll(Legal.Pouch_Items_RS, Legal.Pouch_Key_E, Legal.Pouch_Ball_RS, Legal.Pouch_TMHM_RS, Legal.Pouch_Berries_RS); - return new InventoryPouch3[] - { - new(InventoryType.Items, Legal.Pouch_Items_RS, max, OFS_PouchHeldItem, (OFS_PouchKeyItem - OFS_PouchHeldItem) / 4), - new(InventoryType.KeyItems, Legal.Pouch_Key_E, 1, OFS_PouchKeyItem, (OFS_PouchBalls - OFS_PouchKeyItem) / 4), - new(InventoryType.Balls, Legal.Pouch_Ball_RS, max, OFS_PouchBalls, (OFS_PouchTMHM - OFS_PouchBalls) / 4), - new(InventoryType.TMHMs, Legal.Pouch_TMHM_RS, max, OFS_PouchTMHM, (OFS_PouchBerry - OFS_PouchTMHM) / 4), - new(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 46), - new(InventoryType.PCItems, PCItems, 999, OFS_PCItem, (OFS_PouchHeldItem - OFS_PCItem) / 4), - }; - } - - public PokeBlock3Case PokeBlocks - { - get => new(Large, 0x848); - set => SetData(Large, value.Write(), 0x848); - } - - protected override int SeenOffset2 => 0x988; - - public DecorationInventory3 Decorations => new(Large.AsSpan(0x2734, DecorationInventory3.SIZE)); - - public Swarm3 Swarm - { - get => new(Large.Slice(0x2B90, Swarm3.SIZE)); - set => SetData(Large, value.Data, 0x2B90); - } - - private void ClearSwarm() => Large.AsSpan(0x2B90, Swarm3.SIZE).Clear(); - - public IReadOnlyList DefaultSwarms => Swarm3Details.Swarms_E; - - public int SwarmIndex - { - get => Array.FindIndex(Swarm3Details.Swarms_E, z => z.MapNum == Swarm.MapNum); - set - { - var arr = DefaultSwarms; - if ((uint)value >= arr.Count) - ClearSwarm(); - else - Swarm = arr[value]; - } - } - - protected override int MailOffset => 0x2BE0; - - protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, slot + 1) - 4; // @ end of each pkm slot - public override string GetDaycareRNGSeed(int loc) => ReadUInt32LittleEndian(Large.AsSpan(GetDaycareSlotOffset(0, 2))).ToString("X8"); // after the 2 slots, before the step counter - public override void SetDaycareRNGSeed(int loc, string seed) => WriteUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), Util.GetHexValue(seed)); - - protected override int ExternalEventData => 0x31B3; - - #region eBerry - private const int OFFSET_EBERRY = 0x31F8; - private const int SIZE_EBERRY = 0x134; - - public byte[] GetEReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY); - public void SetEReaderBerry(ReadOnlySpan data) => data.CopyTo(Large.AsSpan(OFFSET_EBERRY)); - - public override string EBerryName => GetString(Large.AsSpan(OFFSET_EBERRY, 7)); - public override bool IsEBerryEngima => Large[OFFSET_EBERRY] is 0 or 0xFF; - #endregion - - public int WonderOffset => WonderNewsOffset; - private const int WonderNewsOffset = 0x322C; - private const int WonderCardOffset = WonderNewsOffset + WonderNews3.SIZE; - private const int WonderCardExtraOffset = WonderCardOffset + WonderCard3.SIZE; - - public WonderNews3 WonderNews { get => new(Large.Slice(WonderNewsOffset, WonderNews3.SIZE)); set => SetData(Large, value.Data, WonderOffset); } - public WonderCard3 WonderCard { get => new(Large.Slice(WonderCardOffset, WonderCard3.SIZE)); set => SetData(Large, value.Data, WonderCardOffset); } - public WonderCard3Extra WonderCardExtra { get => new(Large.Slice(WonderCardExtraOffset, WonderCard3Extra.SIZE)); set => SetData(Large, value.Data, WonderCardExtraOffset); } - // 0x338: 4 easy chat words - // 0x340: news MENewsJisanStruct - // 0x344: uint[5], uint[5] tracking? - - public override MysteryEvent3 MysteryEvent - { - get => new(Large.Slice(0x3728, MysteryEvent3.SIZE)); - set => SetData(Large, value.Data, 0x3728); - } - - protected override int SeenOffset3 => 0x3B24; - - private const int Walda = 0x3D70; - public ushort WaldaBackgroundColor { get => ReadUInt16LittleEndian(Large.AsSpan(Walda + 0)); set => WriteUInt16LittleEndian(Large.AsSpan(Walda + 0), value); } - public ushort WaldaForegroundColor { get => ReadUInt16LittleEndian(Large.AsSpan(Walda + 2)); set => WriteUInt16LittleEndian(Large.AsSpan(Walda + 2), value); } - public byte WaldaIconID { get => Large[Walda + 0x14]; set => Large[Walda + 0x14] = value; } - public byte WaldaPatternID { get => Large[Walda + 0x15]; set => Large[Walda + 0x15] = value; } - public bool WaldaUnlocked { get => Large[Walda + 0x16] != 0; set => Large[Walda + 0x16] = (byte)(value ? 1 : 0); } - #endregion - - private const uint EXTRADATA_SENTINEL = 0x0000B39D; - private const int OFS_BV = 31 * 0x1000; // last sector of the save - public bool HasBattleVideo => Data.Length > SaveUtil.SIZE_G3RAWHALF && ReadUInt32LittleEndian(Data.AsSpan(OFS_BV)) == EXTRADATA_SENTINEL; - - public BV3 BattleVideo - { - get => !HasBattleVideo ? new BV3() : new BV3(Data.Slice(OFS_BV + 4, BV3.SIZE)); - set => SetData(Data, value.Data, OFS_BV + 4); + PokedexMode = value ? (byte)1 : (byte)0; // mode + PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0; // magic + SetEventFlag(0x896, value); + SetWork(0x46, PokedexNationalUnlockWorkRSE); } } + + public override uint SecurityKey + { + get => ReadUInt32LittleEndian(Small.AsSpan(0xAC)); + set => WriteUInt32LittleEndian(Small.AsSpan(0xAC), value); + } + + public RTC3 ClockInitial + { + get => new(GetData(Small, 0x98, RTC3.Size)); + set => SetData(Small, value.Data, 0x98); + } + + public RTC3 ClockElapsed + { + get => new(GetData(Small, 0xA0, RTC3.Size)); + set => SetData(Small, value.Data, 0xA0); + } + + public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Small.AsSpan(0x1FC)); set => WriteUInt16LittleEndian(Small.AsSpan(0x1FC), Math.Min((ushort)9999, value)); } + // u16 field2; + public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Small.AsSpan(0x200)); set => WriteUInt16LittleEndian(Small.AsSpan(0x200), Math.Min((ushort)9999, value)); } + public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Small.AsSpan(0x202)); set => WriteUInt16LittleEndian(Small.AsSpan(0x202), Math.Min((ushort)9999, value)); } + // u32 field8; + public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Small.AsSpan(0x208)); set => WriteUInt32LittleEndian(Small.AsSpan(0x208), Math.Min(9999, value)); } + + public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Small.AsSpan(0x20C)); set => WriteUInt32LittleEndian(Small.AsSpan(0x20C), Math.Min(9999, value)); } + public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Small.AsSpan(0x210)); set => WriteUInt16LittleEndian(Small.AsSpan(0x210), Math.Min((ushort)9999, value)); } + public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Small.AsSpan(0x212)); set => WriteUInt16LittleEndian(Small.AsSpan(0x212), Math.Min((ushort)9999, value)); } + + public uint BP + { + get => ReadUInt16LittleEndian(Small.AsSpan(0xEB8)); + set + { + if (value > 9999) + value = 9999; + WriteUInt16LittleEndian(Small.AsSpan(0xEB8), (ushort)value); + } + } + + public uint BPEarned + { + get => ReadUInt16LittleEndian(Small.AsSpan(0xEBA)); + set + { + if (value > 65535) + value = 65535; + WriteUInt16LittleEndian(Small.AsSpan(0xEBA), (ushort)value); + } + } + #endregion + + #region Large + public override int PartyCount { get => Large[0x234]; protected set => Large[0x234] = (byte)value; } + public override int GetPartyOffset(int slot) => 0x238 + (SIZE_PARTY * slot); + + public override uint Money + { + get => ReadUInt32LittleEndian(Large.AsSpan(0x0490)) ^ SecurityKey; + set => WriteUInt32LittleEndian(Large.AsSpan(0x0490), value ^ SecurityKey); + } + + public override uint Coin + { + get => (ushort)(ReadUInt16LittleEndian(Large.AsSpan(0x0494)) ^ SecurityKey); + set => WriteUInt16LittleEndian(Large.AsSpan(0x0494), (ushort)(value ^ SecurityKey)); + } + + private const int OFS_PCItem = 0x0498; + private const int OFS_PouchHeldItem = 0x0560; + private const int OFS_PouchKeyItem = 0x05D8; + private const int OFS_PouchBalls = 0x0650; + private const int OFS_PouchTMHM = 0x0690; + private const int OFS_PouchBerry = 0x0790; + + protected override InventoryPouch3[] GetItems() + { + const int max = 99; + var PCItems = ArrayUtil.ConcatAll(Legal.Pouch_Items_RS, Legal.Pouch_Key_E, Legal.Pouch_Ball_RS, Legal.Pouch_TMHM_RS, Legal.Pouch_Berries_RS); + return new InventoryPouch3[] + { + new(InventoryType.Items, Legal.Pouch_Items_RS, max, OFS_PouchHeldItem, (OFS_PouchKeyItem - OFS_PouchHeldItem) / 4), + new(InventoryType.KeyItems, Legal.Pouch_Key_E, 1, OFS_PouchKeyItem, (OFS_PouchBalls - OFS_PouchKeyItem) / 4), + new(InventoryType.Balls, Legal.Pouch_Ball_RS, max, OFS_PouchBalls, (OFS_PouchTMHM - OFS_PouchBalls) / 4), + new(InventoryType.TMHMs, Legal.Pouch_TMHM_RS, max, OFS_PouchTMHM, (OFS_PouchBerry - OFS_PouchTMHM) / 4), + new(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 46), + new(InventoryType.PCItems, PCItems, 999, OFS_PCItem, (OFS_PouchHeldItem - OFS_PCItem) / 4), + }; + } + + public PokeBlock3Case PokeBlocks + { + get => new(Large, 0x848); + set => SetData(Large, value.Write(), 0x848); + } + + protected override int SeenOffset2 => 0x988; + + public DecorationInventory3 Decorations => new(Large.AsSpan(0x2734, DecorationInventory3.SIZE)); + + public Swarm3 Swarm + { + get => new(Large.Slice(0x2B90, Swarm3.SIZE)); + set => SetData(Large, value.Data, 0x2B90); + } + + private void ClearSwarm() => Large.AsSpan(0x2B90, Swarm3.SIZE).Clear(); + + public IReadOnlyList DefaultSwarms => Swarm3Details.Swarms_E; + + public int SwarmIndex + { + get => Array.FindIndex(Swarm3Details.Swarms_E, z => z.MapNum == Swarm.MapNum); + set + { + var arr = DefaultSwarms; + if ((uint)value >= arr.Count) + ClearSwarm(); + else + Swarm = arr[value]; + } + } + + protected override int MailOffset => 0x2BE0; + + protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, slot + 1) - 4; // @ end of each pk slot + public override string GetDaycareRNGSeed(int loc) => ReadUInt32LittleEndian(Large.AsSpan(GetDaycareSlotOffset(0, 2))).ToString("X8"); // after the 2 slots, before the step counter + public override void SetDaycareRNGSeed(int loc, string seed) => WriteUInt32LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), Util.GetHexValue(seed)); + + protected override int ExternalEventData => 0x31B3; + + #region eBerry + private const int OFFSET_EBERRY = 0x31F8; + private const int SIZE_EBERRY = 0x134; + + public byte[] GetEReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY); + public void SetEReaderBerry(ReadOnlySpan data) => data.CopyTo(Large.AsSpan(OFFSET_EBERRY)); + + public override string EBerryName => GetString(Large.AsSpan(OFFSET_EBERRY, 7)); + public override bool IsEBerryEngima => Large[OFFSET_EBERRY] is 0 or 0xFF; + #endregion + + public int WonderOffset => WonderNewsOffset; + private const int WonderNewsOffset = 0x322C; + private const int WonderCardOffset = WonderNewsOffset + WonderNews3.SIZE; + private const int WonderCardExtraOffset = WonderCardOffset + WonderCard3.SIZE; + + public WonderNews3 WonderNews { get => new(Large.Slice(WonderNewsOffset, WonderNews3.SIZE)); set => SetData(Large, value.Data, WonderOffset); } + public WonderCard3 WonderCard { get => new(Large.Slice(WonderCardOffset, WonderCard3.SIZE)); set => SetData(Large, value.Data, WonderCardOffset); } + public WonderCard3Extra WonderCardExtra { get => new(Large.Slice(WonderCardExtraOffset, WonderCard3Extra.SIZE)); set => SetData(Large, value.Data, WonderCardExtraOffset); } + // 0x338: 4 easy chat words + // 0x340: news MENewsJisanStruct + // 0x344: uint[5], uint[5] tracking? + + public override MysteryEvent3 MysteryEvent + { + get => new(Large.Slice(0x3728, MysteryEvent3.SIZE)); + set => SetData(Large, value.Data, 0x3728); + } + + protected override int SeenOffset3 => 0x3B24; + + private const int Walda = 0x3D70; + public ushort WaldaBackgroundColor { get => ReadUInt16LittleEndian(Large.AsSpan(Walda + 0)); set => WriteUInt16LittleEndian(Large.AsSpan(Walda + 0), value); } + public ushort WaldaForegroundColor { get => ReadUInt16LittleEndian(Large.AsSpan(Walda + 2)); set => WriteUInt16LittleEndian(Large.AsSpan(Walda + 2), value); } + public byte WaldaIconID { get => Large[Walda + 0x14]; set => Large[Walda + 0x14] = value; } + public byte WaldaPatternID { get => Large[Walda + 0x15]; set => Large[Walda + 0x15] = value; } + public bool WaldaUnlocked { get => Large[Walda + 0x16] != 0; set => Large[Walda + 0x16] = (byte)(value ? 1 : 0); } + #endregion + + private const uint EXTRADATA_SENTINEL = 0x0000B39D; + private const int OFS_BV = 31 * 0x1000; // last sector of the save + public bool HasBattleVideo => Data.Length > SaveUtil.SIZE_G3RAWHALF && ReadUInt32LittleEndian(Data.AsSpan(OFS_BV)) == EXTRADATA_SENTINEL; + + public BV3 BattleVideo + { + get => !HasBattleVideo ? new BV3() : new BV3(Data.Slice(OFS_BV + 4, BV3.SIZE)); + set => SetData(Data, value.Data, OFS_BV + 4); + } } diff --git a/PKHeX.Core/Saves/SAV3FRLG.cs b/PKHeX.Core/Saves/SAV3FRLG.cs index 0234be2cd..287754ee5 100644 --- a/PKHeX.Core/Saves/SAV3FRLG.cs +++ b/PKHeX.Core/Saves/SAV3FRLG.cs @@ -1,183 +1,182 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 object for . +/// +/// +public sealed class SAV3FRLG : SAV3, IGen3Joyful, IGen3Wonder { - /// - /// Generation 5 object for . - /// - /// - public sealed class SAV3FRLG : SAV3, IGen3Joyful, IGen3Wonder + // Configuration + protected override SaveFile CloneInternal() => new SAV3FRLG(Write()); + public override GameVersion Version { get; protected set; } = GameVersion.FR; // allow mutation + private PersonalTable _personal = PersonalTable.FR; + public override PersonalTable Personal => _personal; + + public override int EventFlagCount => 8 * 288; + public override int EventWorkCount => 0x100; + protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp + public override int DaycareSeedSize => 4; // 16bit + protected override int EggEventFlag => 0x266; + protected override int BadgeFlagStart => 0x820; + + public SAV3FRLG(bool japanese = false) : base(japanese) => Initialize(); + + public SAV3FRLG(byte[] data) : base(data) { - // Configuration - protected override SaveFile CloneInternal() => new SAV3FRLG(Write()); - public override GameVersion Version { get; protected set; } = GameVersion.FR; // allow mutation - private PersonalTable _personal = PersonalTable.FR; - public override PersonalTable Personal => _personal; + Initialize(); - public override int EventFlagCount => 8 * 288; - public override int EventWorkCount => 0x100; - protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp - public override int DaycareSeedSize => 4; // 16bit - protected override int EggEventFlag => 0x266; - protected override int BadgeFlagStart => 0x820; - - public SAV3FRLG(bool japanese = false) : base(japanese) => Initialize(); - - public SAV3FRLG(byte[] data) : base(data) - { - Initialize(); - - // Fix save files that have an overflow corruption with the Pokédex. - // Future loads of this save file will cause it to be recognized as FR/LG correctly. - if (IsCorruptPokedexFF()) - WriteUInt32LittleEndian(Small.AsSpan(0xAC), 1); - } - - protected override int EventFlag => 0xEE0; - protected override int EventWork => 0x1000; - - private void Initialize() - { - // small - PokeDex = 0x18; - - // large - DaycareOffset = 0x2F80; - - // storage - Box = 0; - } - - public bool ResetPersonal(GameVersion g) - { - if (g is not (GameVersion.FR or GameVersion.LG)) - return false; - _personal = g == GameVersion.FR ? PersonalTable.FR : PersonalTable.LG; - Version = g; - return true; - } - - #region Small - public override bool NationalDex - { - get => PokedexNationalMagicFRLG == PokedexNationalUnlockFRLG; - set - { - PokedexNationalMagicFRLG = value ? PokedexNationalUnlockFRLG : (byte)0; // magic - SetEventFlag(0x840, value); - SetWork(0x4E, PokedexNationalUnlockWorkFRLG); - } - } - - public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Small.AsSpan(0xB00)); set => WriteUInt16LittleEndian(Small.AsSpan(0xB00), Math.Min((ushort)9999, value)); } - // u16 field2; - public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Small.AsSpan(0xB04)); set => WriteUInt16LittleEndian(Small.AsSpan(0xB04), Math.Min((ushort)9999, value)); } - public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Small.AsSpan(0xB06)); set => WriteUInt16LittleEndian(Small.AsSpan(0xB06), Math.Min((ushort)9999, value)); } - // u32 field8; - public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Small.AsSpan(0xB0C)); set => WriteUInt32LittleEndian(Small.AsSpan(0xB0C), Math.Min(9999, value)); } - - public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Small.AsSpan(0xB10)); set => WriteUInt32LittleEndian(Small.AsSpan(0xB10), Math.Min(9999, value)); } - public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Small.AsSpan(0xB14)); set => WriteUInt16LittleEndian(Small.AsSpan(0xB14), Math.Min((ushort)9999, value)); } - public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Small.AsSpan(0xB16)); set => WriteUInt16LittleEndian(Small.AsSpan(0xB16), Math.Min((ushort)9999, value)); } - - public uint BerryPowder - { - get => ReadUInt32LittleEndian(Small.AsSpan(0xAF8)) ^ SecurityKey; - set => WriteUInt32LittleEndian(Small.AsSpan(0xAF8), value ^ SecurityKey); - } - - public override uint SecurityKey - { - get => ReadUInt32LittleEndian(Small.AsSpan(0xF20)); - set => WriteUInt32LittleEndian(Small.AsSpan(0xF20), value); - } - #endregion - - #region Large - public override int PartyCount { get => Large[0x034]; protected set => Large[0x034] = (byte)value; } - public override int GetPartyOffset(int slot) => 0x038 + (SIZE_PARTY * slot); - - public override uint Money - { - get => ReadUInt32LittleEndian(Large.AsSpan(0x0290)) ^ SecurityKey; - set => WriteUInt32LittleEndian(Large.AsSpan(0x0290), value ^ SecurityKey); - } - - public override uint Coin - { - get => (ushort)(ReadUInt16LittleEndian(Large.AsSpan(0x0294)) ^ SecurityKey); - set => WriteUInt16LittleEndian(Large.AsSpan(0x0294), (ushort)(value ^ SecurityKey)); - } - - private const int OFS_PCItem = 0x0298; - private const int OFS_PouchHeldItem = 0x0310; - private const int OFS_PouchKeyItem = 0x03B8; - private const int OFS_PouchBalls = 0x0430; - private const int OFS_PouchTMHM = 0x0464; - private const int OFS_PouchBerry = 0x054C; - - protected override InventoryPouch3[] GetItems() - { - const int max = 999; - var PCItems = ArrayUtil.ConcatAll(Legal.Pouch_Items_RS, Legal.Pouch_Key_FRLG, Legal.Pouch_Ball_RS, Legal.Pouch_TMHM_RS, Legal.Pouch_Berries_RS); - return new InventoryPouch3[] - { - new(InventoryType.Items, Legal.Pouch_Items_RS, max, OFS_PouchHeldItem, (OFS_PouchKeyItem - OFS_PouchHeldItem) / 4), - new(InventoryType.KeyItems, Legal.Pouch_Key_FRLG, 1, OFS_PouchKeyItem, (OFS_PouchBalls - OFS_PouchKeyItem) / 4), - new(InventoryType.Balls, Legal.Pouch_Ball_RS, max, OFS_PouchBalls, (OFS_PouchTMHM - OFS_PouchBalls) / 4), - new(InventoryType.TMHMs, Legal.Pouch_TMHM_RS, max, OFS_PouchTMHM, (OFS_PouchBerry - OFS_PouchTMHM) / 4), - new(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 43), - new(InventoryType.PCItems, PCItems, 999, OFS_PCItem, (OFS_PouchHeldItem - OFS_PCItem) / 4), - }; - } - - protected override int SeenOffset2 => 0x5F8; - protected override int MailOffset => 0x2CD0; - - protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, slot + 1) - 4; // @ end of each pkm slot - public override string GetDaycareRNGSeed(int loc) => ReadUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2))).ToString("X4"); // after the 2nd slot EXP, before the step counter - public override void SetDaycareRNGSeed(int loc, string seed) => WriteUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), (ushort)Util.GetHexValue(seed)); - - protected override int ExternalEventData => 0x30A7; - - #region eBerry - private const int OFFSET_EBERRY = 0x30EC; - private const int SIZE_EBERRY = 0x134; - - public byte[] GetEReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY); - public void SetEReaderBerry(byte[] data) => SetData(Large, data, OFFSET_EBERRY); - - public override string EBerryName => GetString(Large.AsSpan(OFFSET_EBERRY, 7)); - public override bool IsEBerryEngima => Large[OFFSET_EBERRY] is 0 or 0xFF; - #endregion - - public int WonderOffset => WonderNewsOffset; - private const int WonderNewsOffset = 0x3120; - private const int WonderCardOffset = WonderNewsOffset + WonderNews3.SIZE; - private const int WonderCardExtraOffset = WonderCardOffset + WonderCard3.SIZE; - - public WonderNews3 WonderNews { get => new(Large.Slice(WonderNewsOffset, WonderNews3.SIZE)); set => SetData(Large, value.Data, WonderOffset); } - public WonderCard3 WonderCard { get => new(Large.Slice(WonderCardOffset, WonderCard3.SIZE)); set => SetData(Large, value.Data, WonderCardOffset); } - public WonderCard3Extra WonderCardExtra { get => new(Large.Slice(WonderCardExtraOffset, WonderCard3Extra.SIZE)); set => SetData(Large, value.Data, WonderCardExtraOffset); } - // 0x338: 4 easy chat words - // 0x340: news MENewsJisanStruct - // 0x344: uint[5], uint[5] tracking? - - public override MysteryEvent3 MysteryEvent - { - get => new(Large.Slice(0x361C, MysteryEvent3.SIZE)); - set => SetData(Large, value.Data, 0x361C); - } - - protected override int SeenOffset3 => 0x3A18; - - public string RivalName - { - get => GetString(Large.AsSpan(0x3A4C, 8)); - set => SetString(Large.AsSpan(0x3A4C, 8), value.AsSpan(), 7, StringConverterOption.ClearZero); - } - - #endregion + // Fix save files that have an overflow corruption with the Pokédex. + // Future loads of this save file will cause it to be recognized as FR/LG correctly. + if (IsCorruptPokedexFF()) + WriteUInt32LittleEndian(Small.AsSpan(0xAC), 1); } + + protected override int EventFlag => 0xEE0; + protected override int EventWork => 0x1000; + + private void Initialize() + { + // small + PokeDex = 0x18; + + // large + DaycareOffset = 0x2F80; + + // storage + Box = 0; + } + + public bool ResetPersonal(GameVersion g) + { + if (g is not (GameVersion.FR or GameVersion.LG)) + return false; + _personal = g == GameVersion.FR ? PersonalTable.FR : PersonalTable.LG; + Version = g; + return true; + } + + #region Small + public override bool NationalDex + { + get => PokedexNationalMagicFRLG == PokedexNationalUnlockFRLG; + set + { + PokedexNationalMagicFRLG = value ? PokedexNationalUnlockFRLG : (byte)0; // magic + SetEventFlag(0x840, value); + SetWork(0x4E, PokedexNationalUnlockWorkFRLG); + } + } + + public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Small.AsSpan(0xB00)); set => WriteUInt16LittleEndian(Small.AsSpan(0xB00), Math.Min((ushort)9999, value)); } + // u16 field2; + public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Small.AsSpan(0xB04)); set => WriteUInt16LittleEndian(Small.AsSpan(0xB04), Math.Min((ushort)9999, value)); } + public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Small.AsSpan(0xB06)); set => WriteUInt16LittleEndian(Small.AsSpan(0xB06), Math.Min((ushort)9999, value)); } + // u32 field8; + public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Small.AsSpan(0xB0C)); set => WriteUInt32LittleEndian(Small.AsSpan(0xB0C), Math.Min(9999, value)); } + + public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Small.AsSpan(0xB10)); set => WriteUInt32LittleEndian(Small.AsSpan(0xB10), Math.Min(9999, value)); } + public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Small.AsSpan(0xB14)); set => WriteUInt16LittleEndian(Small.AsSpan(0xB14), Math.Min((ushort)9999, value)); } + public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Small.AsSpan(0xB16)); set => WriteUInt16LittleEndian(Small.AsSpan(0xB16), Math.Min((ushort)9999, value)); } + + public uint BerryPowder + { + get => ReadUInt32LittleEndian(Small.AsSpan(0xAF8)) ^ SecurityKey; + set => WriteUInt32LittleEndian(Small.AsSpan(0xAF8), value ^ SecurityKey); + } + + public override uint SecurityKey + { + get => ReadUInt32LittleEndian(Small.AsSpan(0xF20)); + set => WriteUInt32LittleEndian(Small.AsSpan(0xF20), value); + } + #endregion + + #region Large + public override int PartyCount { get => Large[0x034]; protected set => Large[0x034] = (byte)value; } + public override int GetPartyOffset(int slot) => 0x038 + (SIZE_PARTY * slot); + + public override uint Money + { + get => ReadUInt32LittleEndian(Large.AsSpan(0x0290)) ^ SecurityKey; + set => WriteUInt32LittleEndian(Large.AsSpan(0x0290), value ^ SecurityKey); + } + + public override uint Coin + { + get => (ushort)(ReadUInt16LittleEndian(Large.AsSpan(0x0294)) ^ SecurityKey); + set => WriteUInt16LittleEndian(Large.AsSpan(0x0294), (ushort)(value ^ SecurityKey)); + } + + private const int OFS_PCItem = 0x0298; + private const int OFS_PouchHeldItem = 0x0310; + private const int OFS_PouchKeyItem = 0x03B8; + private const int OFS_PouchBalls = 0x0430; + private const int OFS_PouchTMHM = 0x0464; + private const int OFS_PouchBerry = 0x054C; + + protected override InventoryPouch3[] GetItems() + { + const int max = 999; + var PCItems = ArrayUtil.ConcatAll(Legal.Pouch_Items_RS, Legal.Pouch_Key_FRLG, Legal.Pouch_Ball_RS, Legal.Pouch_TMHM_RS, Legal.Pouch_Berries_RS); + return new InventoryPouch3[] + { + new(InventoryType.Items, Legal.Pouch_Items_RS, max, OFS_PouchHeldItem, (OFS_PouchKeyItem - OFS_PouchHeldItem) / 4), + new(InventoryType.KeyItems, Legal.Pouch_Key_FRLG, 1, OFS_PouchKeyItem, (OFS_PouchBalls - OFS_PouchKeyItem) / 4), + new(InventoryType.Balls, Legal.Pouch_Ball_RS, max, OFS_PouchBalls, (OFS_PouchTMHM - OFS_PouchBalls) / 4), + new(InventoryType.TMHMs, Legal.Pouch_TMHM_RS, max, OFS_PouchTMHM, (OFS_PouchBerry - OFS_PouchTMHM) / 4), + new(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 43), + new(InventoryType.PCItems, PCItems, 999, OFS_PCItem, (OFS_PouchHeldItem - OFS_PCItem) / 4), + }; + } + + protected override int SeenOffset2 => 0x5F8; + protected override int MailOffset => 0x2CD0; + + protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, slot + 1) - 4; // @ end of each pk slot + public override string GetDaycareRNGSeed(int loc) => ReadUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2))).ToString("X4"); // after the 2nd slot EXP, before the step counter + public override void SetDaycareRNGSeed(int loc, string seed) => WriteUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), (ushort)Util.GetHexValue(seed)); + + protected override int ExternalEventData => 0x30A7; + + #region eBerry + private const int OFFSET_EBERRY = 0x30EC; + private const int SIZE_EBERRY = 0x134; + + public byte[] GetEReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY); + public void SetEReaderBerry(byte[] data) => SetData(Large, data, OFFSET_EBERRY); + + public override string EBerryName => GetString(Large.AsSpan(OFFSET_EBERRY, 7)); + public override bool IsEBerryEngima => Large[OFFSET_EBERRY] is 0 or 0xFF; + #endregion + + public int WonderOffset => WonderNewsOffset; + private const int WonderNewsOffset = 0x3120; + private const int WonderCardOffset = WonderNewsOffset + WonderNews3.SIZE; + private const int WonderCardExtraOffset = WonderCardOffset + WonderCard3.SIZE; + + public WonderNews3 WonderNews { get => new(Large.Slice(WonderNewsOffset, WonderNews3.SIZE)); set => SetData(Large, value.Data, WonderOffset); } + public WonderCard3 WonderCard { get => new(Large.Slice(WonderCardOffset, WonderCard3.SIZE)); set => SetData(Large, value.Data, WonderCardOffset); } + public WonderCard3Extra WonderCardExtra { get => new(Large.Slice(WonderCardExtraOffset, WonderCard3Extra.SIZE)); set => SetData(Large, value.Data, WonderCardExtraOffset); } + // 0x338: 4 easy chat words + // 0x340: news MENewsJisanStruct + // 0x344: uint[5], uint[5] tracking? + + public override MysteryEvent3 MysteryEvent + { + get => new(Large.Slice(0x361C, MysteryEvent3.SIZE)); + set => SetData(Large, value.Data, 0x361C); + } + + protected override int SeenOffset3 => 0x3A18; + + public string RivalName + { + get => GetString(Large.AsSpan(0x3A4C, 8)); + set => SetString(Large.AsSpan(0x3A4C, 8), value.AsSpan(), 7, StringConverterOption.ClearZero); + } + + #endregion } diff --git a/PKHeX.Core/Saves/SAV3GCMemoryCard.cs b/PKHeX.Core/Saves/SAV3GCMemoryCard.cs index f68692c50..4d5ce7d6d 100644 --- a/PKHeX.Core/Saves/SAV3GCMemoryCard.cs +++ b/PKHeX.Core/Saves/SAV3GCMemoryCard.cs @@ -3,361 +3,365 @@ using System.Text; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core -{ - /* GameCube memory card format data, checksum and code to extract files based on Dolphin code, adapted from C++ to C# - * https://github.com/dolphin-emu/dolphin/ - */ +namespace PKHeX.Core; +/* GameCube memory card format data, checksum and code to extract files based on Dolphin code, adapted from C++ to C# + * https://github.com/dolphin-emu/dolphin/ + */ - /// - /// Flags for indicating what data is present in the Memory Card - /// - public enum GCMemoryCardState +/// +/// Flags for indicating what data is present in the Memory Card +/// +public enum GCMemoryCardState +{ + Invalid, + NoPkmSaveGame, + SaveGameCOLO, + SaveGameXD, + SaveGameRSBOX, + MultipleSaveGame, + DuplicateCOLO, + DuplicateXD, + DuplicateRSBOX, +} + +/// +/// GameCube save container which may or may not contain Generation 3 objects. +/// +public sealed class SAV3GCMemoryCard +{ + private const int BLOCK_SIZE = 0x2000; + private const int MBIT_TO_BLOCKS = 0x10; + private const int DENTRY_STRLEN = 0x20; + private const int DENTRY_SIZE = 0x40; + private const int NumEntries_Directory = BLOCK_SIZE / DENTRY_SIZE; + + private static readonly HashSet ValidMemoryCardSizes = new() { - Invalid, - NoPkmSaveGame, - SaveGameCOLO, - SaveGameXD, - SaveGameRSBOX, - MultipleSaveGame, - DuplicateCOLO, - DuplicateXD, - DuplicateRSBOX, + 0x0080000, // 512KB 59 Blocks Memory Card + 0x0100000, // 1MB + 0x0200000, // 2MB + 0x0400000, // 4MB 251 Blocks Memory Card + 0x0800000, // 8MB + 0x1000000, // 16MB 1019 Blocks Default Dolphin Memory Card + 0x2000000, // 64MB + 0x4000000, // 128MB + }; + + public static bool IsMemoryCardSize(long Size) => ValidMemoryCardSizes.Contains((int)Size); + + public static bool IsMemoryCardSize(ReadOnlySpan Data) + { + if (!IsMemoryCardSize(Data.Length)) + return false; // bad size + if (ReadUInt64BigEndian(Data) == ulong.MaxValue) + return false; // uninitialized + return true; } - /// - /// GameCube save container which may or may not contain Generation 3 objects. - /// - public sealed class SAV3GCMemoryCard + // Control blocks + private const int Header_Block = 0; + private const int Directory_Block = 1; + private const int DirectoryBackup_Block = 2; + private const int BlockAlloc_Block = 3; + private const int BlockAllocBackup_Block = 4; + + private const int Header = BLOCK_SIZE * Header_Block; + private const int Directory = BLOCK_SIZE * Directory_Block; + private const int DirectoryBAK = BLOCK_SIZE * DirectoryBackup_Block; + private const int BlockAlloc = BLOCK_SIZE * BlockAlloc_Block; + private const int BlockAllocBAK = BLOCK_SIZE * BlockAllocBackup_Block; + + public SAV3GCMemoryCard(byte[] data) => Data = data; + + // Checksums + private (ushort Checksum, ushort Inverse) GetChecksum(int block, int offset, int length) { - private const int BLOCK_SIZE = 0x2000; - private const int MBIT_TO_BLOCKS = 0x10; - private const int DENTRY_STRLEN = 0x20; - private const int DENTRY_SIZE = 0x40; - private const int NumEntries_Directory = BLOCK_SIZE / DENTRY_SIZE; + ushort csum = 0; + ushort inv_csum = 0; - private static readonly HashSet ValidMemoryCardSizes = new() + var ofs = (block * BLOCK_SIZE) + offset; + var span = Data.AsSpan(ofs, length); + + for (int i = 0; i < span.Length; i += 2) { - 0x0080000, // 512KB 59 Blocks Memory Card - 0x0100000, // 1MB - 0x0200000, // 2MB - 0x0400000, // 4MB 251 Blocks Memory Card - 0x0800000, // 8MB - 0x1000000, // 16MB 1019 Blocks Default Dolphin Memory Card - 0x2000000, // 64MB - 0x4000000, // 128MB - }; + var value = ReadUInt16BigEndian(span[i..]); + csum += value; + inv_csum += (ushort)~value; + } + if (csum == 0xffff) + csum = 0; + if (inv_csum == 0xffff) + inv_csum = 0; - public static bool IsMemoryCardSize(long Size) => ValidMemoryCardSizes.Contains((int)Size); + return (csum, inv_csum); + } - public static bool IsMemoryCardSize(ReadOnlySpan Data) - { - if (!IsMemoryCardSize(Data.Length)) - return false; // bad size - if (ReadUInt64BigEndian(Data) == ulong.MaxValue) - return false; // uninitialized + private MemoryCardChecksumStatus VerifyChecksums() + { + MemoryCardChecksumStatus results = 0; + + var (chk, inv) = GetChecksum(Header_Block, 0, 0x1FC); + if (Header_Checksum != chk || Header_Checksum_Inv != inv) + results |= MemoryCardChecksumStatus.HeaderBad; + + (chk, inv) = GetChecksum(Directory_Block, 0, 0x1FFC); + if (Directory_Checksum != chk || Directory_Checksum_Inv != inv) + results |= MemoryCardChecksumStatus.DirectoryBad; + + (chk, inv) = GetChecksum(DirectoryBackup_Block, 0, 0x1FFC); + if (DirectoryBAK_Checksum != chk || DirectoryBAK_Checksum_Inv != inv) + results |= MemoryCardChecksumStatus.DirectoryBackupBad; + + (chk, inv) = GetChecksum(BlockAlloc_Block, 4, 0x1FFC); + if (BlockAlloc_Checksum != chk || BlockAlloc_Checksum_Inv != inv) + results |= MemoryCardChecksumStatus.BlockAllocBad; + + (chk, inv) = GetChecksum(BlockAllocBackup_Block, 4, 0x1FFC); + if ((BlockAllocBAK_Checksum != chk) || BlockAllocBAK_Checksum_Inv != inv) + results |= MemoryCardChecksumStatus.BlockAllocBackupBad; + + return results; + } + + // Structure + private int Header_Size => ReadUInt16BigEndian(Data.AsSpan(Header + 0x0022)); + private ushort Header_Checksum => ReadUInt16BigEndian(Data.AsSpan(Header + 0x01fc)); + private ushort Header_Checksum_Inv => ReadUInt16BigEndian(Data.AsSpan(Header + 0x01fe)); + + // Encoding (Windows-1252 or Shift JIS) + private int Header_Encoding => ReadUInt16BigEndian(Data.AsSpan(Header + 0x0024)); + private bool Header_Japanese => Header_Encoding == 1; + private Encoding EncodingType => Header_Japanese ? Encoding.GetEncoding(1252) : Encoding.GetEncoding(932); + + private int Directory_UpdateCounter => ReadUInt16BigEndian(Data.AsSpan(Directory + 0x1ffa)); + private int Directory_Checksum => ReadUInt16BigEndian(Data.AsSpan(Directory + 0x1ffc)); + private int Directory_Checksum_Inv => ReadUInt16BigEndian(Data.AsSpan(Directory + 0x1ffe)); + + private int DirectoryBAK_UpdateCounter => ReadUInt16BigEndian(Data.AsSpan(DirectoryBAK + 0x1ffa)); + private int DirectoryBAK_Checksum => ReadUInt16BigEndian(Data.AsSpan(DirectoryBAK + 0x1ffc)); + private int DirectoryBAK_Checksum_Inv => ReadUInt16BigEndian(Data.AsSpan(DirectoryBAK + 0x1ffe)); + + private int BlockAlloc_Checksum => ReadUInt16BigEndian(Data.AsSpan(BlockAlloc + 0x0000)); + private int BlockAlloc_Checksum_Inv => ReadUInt16BigEndian(Data.AsSpan(BlockAlloc + 0x0002)); + + private int BlockAllocBAK_Checksum => ReadUInt16BigEndian(Data.AsSpan(BlockAllocBAK + 0x0000)); + private int BlockAllocBAK_Checksum_Inv => ReadUInt16BigEndian(Data.AsSpan(BlockAllocBAK + 0x0002)); + + private int DirectoryBlock_Used; + + private int EntryCOLO = -1; + private int EntryXD = -1; + private int EntryRSBOX = -1; + private int EntrySelected = -1; + public bool HasCOLO => EntryCOLO >= 0; + public bool HasXD => EntryXD >= 0; + public bool HasRSBOX => EntryRSBOX >= 0; + public int SaveGameCount { get; private set; } + + [Flags] + public enum MemoryCardChecksumStatus + { + None = 0, + HeaderBad = 1 << 0, + DirectoryBad = 1 << 1, + DirectoryBackupBad = 1 << 2, + BlockAllocBad, + BlockAllocBackupBad, + } + + private bool IsCorruptedMemoryCard() + { + var csums = VerifyChecksums(); + + if ((csums & MemoryCardChecksumStatus.HeaderBad) != 0) return true; + + if ((csums & MemoryCardChecksumStatus.DirectoryBad) != 0) + { + if ((csums & MemoryCardChecksumStatus.DirectoryBackupBad) != 0) // backup is also wrong + return true; // Directory checksum and directory backup checksum failed + + // backup is correct, restore + RestoreBackup(); + csums = VerifyChecksums(); // update checksums } - // Control blocks - private const int Header_Block = 0; - private const int Directory_Block = 1; - private const int DirectoryBackup_Block = 2; - private const int BlockAlloc_Block = 3; - private const int BlockAllocBackup_Block = 4; - - private const int Header = BLOCK_SIZE * Header_Block; - private const int Directory = BLOCK_SIZE * Directory_Block; - private const int DirectoryBAK = BLOCK_SIZE * DirectoryBackup_Block; - private const int BlockAlloc = BLOCK_SIZE * BlockAlloc_Block; - private const int BlockAllocBAK = BLOCK_SIZE * BlockAllocBackup_Block; - - public SAV3GCMemoryCard(byte[] data) => Data = data; - - // Checksums - private void GetChecksum(int block, int offset, int length, out ushort csum, out ushort inv_csum) + if ((csums & MemoryCardChecksumStatus.BlockAllocBad) != 0) { - csum = inv_csum = 0; - var ofs = (block * BLOCK_SIZE) + offset; - - for (int i = 0; i < length; i++) - { - var value = ReadUInt16BigEndian(Data.AsSpan(ofs + (i * 2))); - csum += value; - inv_csum += (ushort)~value; - } - if (csum == 0xffff) - csum = 0; - if (inv_csum == 0xffff) - inv_csum = 0; - } - - private GCChecksumFlag VerifyChecksums() - { - GCChecksumFlag results = 0; - - GetChecksum(Header_Block, 0, 0xFE, out ushort csum, out ushort csum_inv); - if (Header_Checksum != csum || (Header_Checksum_Inv != csum_inv)) - results |= GCChecksumFlag.HeaderBad; - - GetChecksum(Directory_Block, 0, 0xFFE, out csum, out csum_inv); - if (Directory_Checksum != csum || (Directory_Checksum_Inv != csum_inv)) - results |= GCChecksumFlag.DirectoryBad; - - GetChecksum(DirectoryBackup_Block, 0, 0xFFE, out csum, out csum_inv); - if (DirectoryBAK_Checksum != csum || (DirectoryBAK_Checksum_Inv != csum_inv)) - results |= GCChecksumFlag.DirectoryBackupBad; - - GetChecksum(BlockAlloc_Block, 4, 0xFFE, out csum, out csum_inv); - if (BlockAlloc_Checksum != csum || (BlockAlloc_Checksum_Inv != csum_inv)) - results |= GCChecksumFlag.BlockAllocBad; - - GetChecksum(BlockAllocBackup_Block, 4, 0xFFE, out csum, out csum_inv); - if ((BlockAllocBAK_Checksum != csum) || BlockAllocBAK_Checksum_Inv != csum_inv) - results |= GCChecksumFlag.BlockAllocBackupBad; - - return results; - } - - // Structure - private int Header_Size => ReadUInt16BigEndian(Data.AsSpan(Header + 0x0022)); - private ushort Header_Checksum => ReadUInt16BigEndian(Data.AsSpan(Header + 0x01fc)); - private ushort Header_Checksum_Inv => ReadUInt16BigEndian(Data.AsSpan(Header + 0x01fe)); - - // Encoding (Windows-1252 or Shift JIS) - private int Header_Encoding => ReadUInt16BigEndian(Data.AsSpan(Header + 0x0024)); - private bool Header_Japanese => Header_Encoding == 1; - private Encoding EncodingType => Header_Japanese ? Encoding.GetEncoding(1252) : Encoding.GetEncoding(932); - - private int Directory_UpdateCounter => ReadUInt16BigEndian(Data.AsSpan(Directory + 0x1ffa)); - private int Directory_Checksum => ReadUInt16BigEndian(Data.AsSpan(Directory + 0x1ffc)); - private int Directory_Checksum_Inv => ReadUInt16BigEndian(Data.AsSpan(Directory + 0x1ffe)); - - private int DirectoryBAK_UpdateCounter => ReadUInt16BigEndian(Data.AsSpan(DirectoryBAK + 0x1ffa)); - private int DirectoryBAK_Checksum => ReadUInt16BigEndian(Data.AsSpan(DirectoryBAK + 0x1ffc)); - private int DirectoryBAK_Checksum_Inv => ReadUInt16BigEndian(Data.AsSpan(DirectoryBAK + 0x1ffe)); - - private int BlockAlloc_Checksum => ReadUInt16BigEndian(Data.AsSpan(BlockAlloc + 0x0000)); - private int BlockAlloc_Checksum_Inv => ReadUInt16BigEndian(Data.AsSpan(BlockAlloc + 0x0002)); - - private int BlockAllocBAK_Checksum => ReadUInt16BigEndian(Data.AsSpan(BlockAllocBAK + 0x0000)); - private int BlockAllocBAK_Checksum_Inv => ReadUInt16BigEndian(Data.AsSpan(BlockAllocBAK + 0x0002)); - - private int DirectoryBlock_Used; - - private int EntryCOLO = -1; - private int EntryXD = -1; - private int EntryRSBOX = -1; - private int EntrySelected = -1; - public bool HasCOLO => EntryCOLO >= 0; - public bool HasXD => EntryXD >= 0; - public bool HasRSBOX => EntryRSBOX >= 0; - public int SaveGameCount; - - [Flags] - public enum GCChecksumFlag : uint - { - NoIssue = 0, - HeaderBad = 1 << 0, - DirectoryBad = 1 << 1, - DirectoryBackupBad = 1 << 2, - BlockAllocBad, - BlockAllocBackupBad, - } - - private bool IsCorruptedMemoryCard() - { - var csums = VerifyChecksums(); - - if ((csums & GCChecksumFlag.HeaderBad) != 0) + if ((csums & MemoryCardChecksumStatus.BlockAllocBackupBad) != 0) // backup is also wrong return true; - if ((csums & GCChecksumFlag.DirectoryBad) != 0) - { - if ((csums & GCChecksumFlag.DirectoryBackupBad) != 0) // backup is also wrong - return true; // Directory checksum and directory backup checksum failed - - // backup is correct, restore - RestoreBackup(); - csums = VerifyChecksums(); // update checksums - } - - if ((csums & GCChecksumFlag.BlockAllocBad) != 0) - { - if ((csums & GCChecksumFlag.BlockAllocBackupBad) != 0) // backup is also wrong - return true; - - // backup is correct, restore - RestoreBackup(); - } - - return false; + // backup is correct, restore + RestoreBackup(); } - private void RestoreBackup() + return false; + } + + private void RestoreBackup() + { + Array.Copy(Data, DirectoryBackup_Block*BLOCK_SIZE, Data, Directory_Block*BLOCK_SIZE, BLOCK_SIZE); + Array.Copy(Data, BlockAllocBackup_Block*BLOCK_SIZE, Data, BlockAlloc_Block*BLOCK_SIZE, BLOCK_SIZE); + } + + public GCMemoryCardState GetMemoryCardState() + { + if (!IsMemoryCardSize(Data)) + return GCMemoryCardState.Invalid; // Invalid size + + // Size in megabits, not megabytes + int m_sizeMb = Data.Length / BLOCK_SIZE / MBIT_TO_BLOCKS; + if (m_sizeMb != Header_Size) // Memory card file size does not match the header size + return GCMemoryCardState.Invalid; + + if (IsCorruptedMemoryCard()) + return GCMemoryCardState.Invalid; + + // Use the most recent directory block + DirectoryBlock_Used = DirectoryBAK_UpdateCounter > Directory_UpdateCounter + ? DirectoryBackup_Block + : Directory_Block; + + // Search for pokemon savegames in the directory + for (int i = 0; i < NumEntries_Directory; i++) { - Array.Copy(Data, DirectoryBackup_Block*BLOCK_SIZE, Data, Directory_Block*BLOCK_SIZE, BLOCK_SIZE); - Array.Copy(Data, BlockAllocBackup_Block*BLOCK_SIZE, Data, BlockAlloc_Block*BLOCK_SIZE, BLOCK_SIZE); - } + int offset = (DirectoryBlock_Used * BLOCK_SIZE) + (i * DENTRY_SIZE); + if (ReadUInt32BigEndian(Data.AsSpan(offset)) == uint.MaxValue) // empty entry + continue; - public GCMemoryCardState GetMemoryCardState() - { - if (!IsMemoryCardSize(Data)) - return GCMemoryCardState.Invalid; // Invalid size + int FirstBlock = ReadUInt16BigEndian(Data.AsSpan(offset + 0x36)); + if (FirstBlock < 5) + continue; - // Size in megabits, not megabytes - int m_sizeMb = Data.Length / BLOCK_SIZE / MBIT_TO_BLOCKS; - if (m_sizeMb != Header_Size) // Memory card file size does not match the header size - return GCMemoryCardState.Invalid; + int BlockCount = ReadUInt16BigEndian(Data.AsSpan(offset + 0x38)); + var dataEnd = (FirstBlock + BlockCount) * BLOCK_SIZE; - if (IsCorruptedMemoryCard()) - return GCMemoryCardState.Invalid; + // Memory card directory contains info for deleted files with boundaries beyond memory card size, ignore + if (dataEnd > Data.Length) + continue; - // Use the most recent directory block - DirectoryBlock_Used = DirectoryBAK_UpdateCounter > Directory_UpdateCounter - ? DirectoryBackup_Block - : Directory_Block; - - // Search for pokemon savegames in the directory - for (int i = 0; i < NumEntries_Directory; i++) + SaveGameCount = 0; + var gameCode = EncodingType.GetString(Data, offset, 4); + var ver = SaveHandlerGCI.GetGameCode(gameCode); + if (ver == GameVersion.COLO) { - int offset = (DirectoryBlock_Used * BLOCK_SIZE) + (i * DENTRY_SIZE); - if (ReadUInt32BigEndian(Data.AsSpan(offset)) == uint.MaxValue) // empty entry - continue; - - int FirstBlock = ReadUInt16BigEndian(Data.AsSpan(offset + 0x36)); - if (FirstBlock < 5) - continue; - - int BlockCount = ReadUInt16BigEndian(Data.AsSpan(offset + 0x38)); - var dataEnd = (FirstBlock + BlockCount) * BLOCK_SIZE; - - // Memory card directory contains info for deleted files with boundaries beyond memory card size, ignore - if (dataEnd > Data.Length) - continue; - - var gameCode = EncodingType.GetString(Data, offset, 4); - var ver = SaveHandlerGCI.GetGameCode(gameCode); - if (ver == GameVersion.COLO) - { - if (HasCOLO) // another entry already exists - return GCMemoryCardState.DuplicateCOLO; - EntryCOLO = i; - SaveGameCount++; - } - if (ver == GameVersion.XD) - { - if (HasXD) // another entry already exists - return GCMemoryCardState.DuplicateXD; - EntryXD = i; - SaveGameCount++; - } - if (ver == GameVersion.RSBOX) - { - if (HasRSBOX) // another entry already exists - return GCMemoryCardState.DuplicateRSBOX; - EntryRSBOX = i; - SaveGameCount++; - } + if (HasCOLO) // another entry already exists + return GCMemoryCardState.DuplicateCOLO; + EntryCOLO = i; + SaveGameCount++; } - if (SaveGameCount == 0) - return GCMemoryCardState.NoPkmSaveGame; - - if (SaveGameCount > 1) - return GCMemoryCardState.MultipleSaveGame; - - if (HasCOLO) + if (ver == GameVersion.XD) { - EntrySelected = EntryCOLO; - return GCMemoryCardState.SaveGameCOLO; + if (HasXD) // another entry already exists + return GCMemoryCardState.DuplicateXD; + EntryXD = i; + SaveGameCount++; } - if (HasXD) + if (ver == GameVersion.RSBOX) { - EntrySelected = EntryXD; - return GCMemoryCardState.SaveGameXD; - } - EntrySelected = EntryRSBOX; - return GCMemoryCardState.SaveGameRSBOX; - } - - public GameVersion SelectedGameVersion - { - get - { - if (EntrySelected < 0) - return GameVersion.Any; - - if (EntrySelected == EntryCOLO) - return GameVersion.COLO; - if (EntrySelected == EntryXD) - return GameVersion.XD; - if (EntrySelected == EntryRSBOX) - return GameVersion.RSBOX; - return GameVersion.Any; //Default for no game selected + if (HasRSBOX) // another entry already exists + return GCMemoryCardState.DuplicateRSBOX; + EntryRSBOX = i; + SaveGameCount++; } } + if (SaveGameCount == 0) + return GCMemoryCardState.NoPkmSaveGame; - public void SelectSaveGame(GameVersion Game) + if (SaveGameCount > 1) + return GCMemoryCardState.MultipleSaveGame; + + if (HasCOLO) { - switch (Game) - { - case GameVersion.COLO: if (HasCOLO) EntrySelected = EntryCOLO; break; - case GameVersion.XD: if (HasXD) EntrySelected = EntryXD; break; - case GameVersion.RSBOX: if (HasRSBOX) EntrySelected = EntryRSBOX; break; - } + EntrySelected = EntryCOLO; + return GCMemoryCardState.SaveGameCOLO; } - - public string GCISaveName => GCISaveGameName(); - public readonly byte[] Data; - - private string GCISaveGameName() + if (HasXD) { - int offset = (DirectoryBlock_Used * BLOCK_SIZE) + (EntrySelected * DENTRY_SIZE); - string GameCode = EncodingType.GetString(Data, offset, 4); - string Makercode = EncodingType.GetString(Data, offset + 0x04, 2); - string FileName = EncodingType.GetString(Data, offset + 0x08, DENTRY_STRLEN); - - return $"{Makercode}-{GameCode}-{Util.TrimFromZero(FileName)}.gci"; + EntrySelected = EntryXD; + return GCMemoryCardState.SaveGameXD; } + EntrySelected = EntryRSBOX; + return GCMemoryCardState.SaveGameRSBOX; + } - public byte[] ReadSaveGameData() + public GameVersion SelectedGameVersion + { + get { - var entry = EntrySelected; - if (entry < 0) - return Array.Empty(); // No entry selected - return ReadSaveGameData(entry); - } + if (EntrySelected < 0) + return GameVersion.Any; - private byte[] ReadSaveGameData(int entry) - { - int offset = (DirectoryBlock_Used * BLOCK_SIZE) + (entry * DENTRY_SIZE); - var span = Data.AsSpan(offset); - int blockFirst = ReadUInt16BigEndian(span[0x36..]); - int blockCount = ReadUInt16BigEndian(span[0x38..]); - - return Data.AsSpan(blockFirst * BLOCK_SIZE, blockCount * BLOCK_SIZE).ToArray(); - } - - public void WriteSaveGameData(ReadOnlySpan data) - { - var entry = EntrySelected; - if (entry < 0) // Can't write anywhere - return; - WriteSaveGameData(data, entry); - } - - private void WriteSaveGameData(ReadOnlySpan data, int entry) - { - int offset = (DirectoryBlock_Used * BLOCK_SIZE) + (entry * DENTRY_SIZE); - var span = Data.AsSpan(offset); - int blockFirst = ReadUInt16BigEndian(span[0x36..]); - int blockCount = ReadUInt16BigEndian(span[0x38..]); - - if (data.Length != blockCount * BLOCK_SIZE) // Invalid File Size - return; - - var dest = Data.AsSpan(blockFirst * BLOCK_SIZE); - data[..(blockCount * BLOCK_SIZE)].CopyTo(dest); + if (EntrySelected == EntryCOLO) + return GameVersion.COLO; + if (EntrySelected == EntryXD) + return GameVersion.XD; + if (EntrySelected == EntryRSBOX) + return GameVersion.RSBOX; + return GameVersion.Any; //Default for no game selected } } + + public void SelectSaveGame(GameVersion Game) + { + switch (Game) + { + case GameVersion.COLO: if (HasCOLO) EntrySelected = EntryCOLO; break; + case GameVersion.XD: if (HasXD) EntrySelected = EntryXD; break; + case GameVersion.RSBOX: if (HasRSBOX) EntrySelected = EntryRSBOX; break; + } + } + + public string GCISaveName => GCISaveGameName(); + public readonly byte[] Data; + + private string GCISaveGameName() + { + int offset = (DirectoryBlock_Used * BLOCK_SIZE) + (EntrySelected * DENTRY_SIZE); + string GameCode = EncodingType.GetString(Data, offset, 4); + string Makercode = EncodingType.GetString(Data, offset + 0x04, 2); + string FileName = EncodingType.GetString(Data, offset + 0x08, DENTRY_STRLEN); + + return $"{Makercode}-{GameCode}-{Util.TrimFromZero(FileName)}.gci"; + } + + public byte[] ReadSaveGameData() + { + var entry = EntrySelected; + if (entry < 0) + return Array.Empty(); // No entry selected + return ReadSaveGameData(entry); + } + + private byte[] ReadSaveGameData(int entry) + { + int offset = (DirectoryBlock_Used * BLOCK_SIZE) + (entry * DENTRY_SIZE); + var span = Data.AsSpan(offset); + int blockFirst = ReadUInt16BigEndian(span[0x36..]); + int blockCount = ReadUInt16BigEndian(span[0x38..]); + + return Data.AsSpan(blockFirst * BLOCK_SIZE, blockCount * BLOCK_SIZE).ToArray(); + } + + public void WriteSaveGameData(ReadOnlySpan data) + { + var entry = EntrySelected; + if (entry < 0) // Can't write anywhere + return; + WriteSaveGameData(data, entry); + } + + private void WriteSaveGameData(ReadOnlySpan data, int entry) + { + int offset = (DirectoryBlock_Used * BLOCK_SIZE) + (entry * DENTRY_SIZE); + var span = Data.AsSpan(offset); + int blockFirst = ReadUInt16BigEndian(span[0x36..]); + int blockCount = ReadUInt16BigEndian(span[0x38..]); + + if (data.Length != blockCount * BLOCK_SIZE) // Invalid File Size + return; + + var dest = Data.AsSpan(blockFirst * BLOCK_SIZE); + data[..(blockCount * BLOCK_SIZE)].CopyTo(dest); + } } diff --git a/PKHeX.Core/Saves/SAV3RS.cs b/PKHeX.Core/Saves/SAV3RS.cs index 42e9d1093..83d43e946 100644 --- a/PKHeX.Core/Saves/SAV3RS.cs +++ b/PKHeX.Core/Saves/SAV3RS.cs @@ -2,169 +2,168 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 object for . +/// +/// +public sealed class SAV3RS : SAV3, IGen3Hoenn { - /// - /// Generation 3 object for . - /// - /// - public sealed class SAV3RS : SAV3, IGen3Hoenn + // Configuration + protected override SaveFile CloneInternal() => new SAV3RS(Write()); + public override GameVersion Version { get => GameVersion.RS; protected set { } } + public override PersonalTable Personal => PersonalTable.RS; + + public override int EventFlagCount => 8 * 288; + public override int EventWorkCount => 0x100; + protected override int DaycareSlotSize => SIZE_STORED; + public override int DaycareSeedSize => 4; // 16bit + protected override int EggEventFlag => 0x86; + protected override int BadgeFlagStart => 0x807; + + public SAV3RS(byte[] data) : base(data) => Initialize(); + public SAV3RS(bool japanese = false) : base(japanese) => Initialize(); + + protected override int EventFlag => 0x1220; + protected override int EventWork => 0x1340; + + private void Initialize() { - // Configuration - protected override SaveFile CloneInternal() => new SAV3RS(Write()); - public override GameVersion Version { get => GameVersion.RS; protected set { } } - public override PersonalTable Personal => PersonalTable.RS; + // small + PokeDex = 0x18; - public override int EventFlagCount => 8 * 288; - public override int EventWorkCount => 0x100; - protected override int DaycareSlotSize => SIZE_STORED; - public override int DaycareSeedSize => 4; // 16bit - protected override int EggEventFlag => 0x86; - protected override int BadgeFlagStart => 0x807; + // large + DaycareOffset = 0x2F9C; - public SAV3RS(byte[] data) : base(data) => Initialize(); - public SAV3RS(bool japanese = false) : base(japanese) => Initialize(); - - protected override int EventFlag => 0x1220; - protected override int EventWork => 0x1340; - - private void Initialize() - { - // small - PokeDex = 0x18; - - // large - DaycareOffset = 0x2F9C; - - // storage - Box = 0; - } - - #region Small - public override bool NationalDex - { - get => PokedexNationalMagicRSE == PokedexNationalUnlockRSE; - set - { - PokedexMode = value ? (byte)1 : (byte)0; // mode - PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0; // magic - SetEventFlag(0x836, value); - SetWork(0x46, PokedexNationalUnlockWorkRSE); - } - } - - public override uint SecurityKey { get => 0; set { } } - - public RTC3 ClockInitial - { - get => new(GetData(Small, 0x98, RTC3.Size)); - set => SetData(Small, value.Data, 0x98); - } - - public RTC3 ClockElapsed - { - get => new(GetData(Small, 0xA0, RTC3.Size)); - set => SetData(Small, value.Data, 0xA0); - } - #endregion - - #region Large - public override int PartyCount { get => Large[0x234]; protected set => Large[0x234] = (byte)value; } - public override int GetPartyOffset(int slot) => 0x238 + (SIZE_PARTY * slot); - - public override uint Money - { - get => ReadUInt32LittleEndian(Large.AsSpan(0x0490)); - set => WriteUInt32LittleEndian(Large.AsSpan(0x0490), value); - } - - public override uint Coin - { - get => ReadUInt16LittleEndian(Large.AsSpan(0x0494)); - set => WriteUInt16LittleEndian(Large.AsSpan(0x0494), (ushort)(value)); - } - - private const int OFS_PCItem = 0x0498; - private const int OFS_PouchHeldItem = 0x0560; - private const int OFS_PouchKeyItem = 0x05B0; - private const int OFS_PouchBalls = 0x0600; - private const int OFS_PouchTMHM = 0x0640; - private const int OFS_PouchBerry = 0x0740; - - protected override InventoryPouch3[] GetItems() - { - const int max = 99; - var PCItems = ArrayUtil.ConcatAll(Legal.Pouch_Items_RS, Legal.Pouch_Key_RS, Legal.Pouch_Ball_RS, Legal.Pouch_TMHM_RS, Legal.Pouch_Berries_RS); - return new InventoryPouch3[] - { - new(InventoryType.Items, Legal.Pouch_Items_RS, max, OFS_PouchHeldItem, (OFS_PouchKeyItem - OFS_PouchHeldItem) / 4), - new(InventoryType.KeyItems, Legal.Pouch_Key_RS, 1, OFS_PouchKeyItem, (OFS_PouchBalls - OFS_PouchKeyItem) / 4), - new(InventoryType.Balls, Legal.Pouch_Ball_RS, max, OFS_PouchBalls, (OFS_PouchTMHM - OFS_PouchBalls) / 4), - new(InventoryType.TMHMs, Legal.Pouch_TMHM_RS, max, OFS_PouchTMHM, (OFS_PouchBerry - OFS_PouchTMHM) / 4), - new(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 46), - new(InventoryType.PCItems, PCItems, 999, OFS_PCItem, (OFS_PouchHeldItem - OFS_PCItem) / 4), - }; - } - - public PokeBlock3Case PokeBlocks - { - get => new(Large, 0x7F8); - set => SetData(Large, value.Write(), 0x7F8); - } - - protected override int SeenOffset2 => 0x938; - - public DecorationInventory3 Decorations => new(Large.AsSpan(0x26A0, DecorationInventory3.SIZE)); - - public Swarm3 Swarm - { - get => new(Large.Slice(0x2AFC, Swarm3.SIZE)); - set => SetData(Large, value.Data, 0x2AFC); - } - - private void ClearSwarm() => Large.AsSpan(0x2AFC, Swarm3.SIZE).Clear(); - - public IReadOnlyList DefaultSwarms => Swarm3Details.Swarms_RS; - - public int SwarmIndex - { - get => Array.FindIndex(Swarm3Details.Swarms_RS, z => z.MapNum == Swarm.MapNum); - set - { - var arr = DefaultSwarms; - if ((uint)value >= arr.Count) - ClearSwarm(); - else - Swarm = arr[value]; - } - } - - protected override int MailOffset => 0x2B4C; - - protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, 2) + (2 * 0x38) + (4 * slot); // consecutive vals, after both consecutive slots & 2 mail - public override string GetDaycareRNGSeed(int loc) => ReadUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2))).ToString("X4"); - public override void SetDaycareRNGSeed(int loc, string seed) => WriteUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), (ushort)Util.GetHexValue(seed)); - - protected override int ExternalEventData => 0x311B; - - #region eBerry - private const int OFFSET_EBERRY = 0x3160; - private const int SIZE_EBERRY = 0x530; - - public byte[] GetEReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY); - public void SetEReaderBerry(ReadOnlySpan data) => data.CopyTo(Large.AsSpan(OFFSET_EBERRY)); - - public override string EBerryName => GetString(Large.AsSpan(OFFSET_EBERRY, 7)); - public override bool IsEBerryEngima => Large[OFFSET_EBERRY] is 0 or 0xFF; - #endregion - - public override MysteryEvent3 MysteryEvent - { - get => new(Large.Slice(0x3690, MysteryEvent3.SIZE)); - set => SetData(Large, value.Data, 0x3690); - } - - protected override int SeenOffset3 => 0x3A8C; - #endregion + // storage + Box = 0; } + + #region Small + public override bool NationalDex + { + get => PokedexNationalMagicRSE == PokedexNationalUnlockRSE; + set + { + PokedexMode = value ? (byte)1 : (byte)0; // mode + PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0; // magic + SetEventFlag(0x836, value); + SetWork(0x46, PokedexNationalUnlockWorkRSE); + } + } + + public override uint SecurityKey { get => 0; set { } } + + public RTC3 ClockInitial + { + get => new(GetData(Small, 0x98, RTC3.Size)); + set => SetData(Small, value.Data, 0x98); + } + + public RTC3 ClockElapsed + { + get => new(GetData(Small, 0xA0, RTC3.Size)); + set => SetData(Small, value.Data, 0xA0); + } + #endregion + + #region Large + public override int PartyCount { get => Large[0x234]; protected set => Large[0x234] = (byte)value; } + public override int GetPartyOffset(int slot) => 0x238 + (SIZE_PARTY * slot); + + public override uint Money + { + get => ReadUInt32LittleEndian(Large.AsSpan(0x0490)); + set => WriteUInt32LittleEndian(Large.AsSpan(0x0490), value); + } + + public override uint Coin + { + get => ReadUInt16LittleEndian(Large.AsSpan(0x0494)); + set => WriteUInt16LittleEndian(Large.AsSpan(0x0494), (ushort)(value)); + } + + private const int OFS_PCItem = 0x0498; + private const int OFS_PouchHeldItem = 0x0560; + private const int OFS_PouchKeyItem = 0x05B0; + private const int OFS_PouchBalls = 0x0600; + private const int OFS_PouchTMHM = 0x0640; + private const int OFS_PouchBerry = 0x0740; + + protected override InventoryPouch3[] GetItems() + { + const int max = 99; + var PCItems = ArrayUtil.ConcatAll(Legal.Pouch_Items_RS, Legal.Pouch_Key_RS, Legal.Pouch_Ball_RS, Legal.Pouch_TMHM_RS, Legal.Pouch_Berries_RS); + return new InventoryPouch3[] + { + new(InventoryType.Items, Legal.Pouch_Items_RS, max, OFS_PouchHeldItem, (OFS_PouchKeyItem - OFS_PouchHeldItem) / 4), + new(InventoryType.KeyItems, Legal.Pouch_Key_RS, 1, OFS_PouchKeyItem, (OFS_PouchBalls - OFS_PouchKeyItem) / 4), + new(InventoryType.Balls, Legal.Pouch_Ball_RS, max, OFS_PouchBalls, (OFS_PouchTMHM - OFS_PouchBalls) / 4), + new(InventoryType.TMHMs, Legal.Pouch_TMHM_RS, max, OFS_PouchTMHM, (OFS_PouchBerry - OFS_PouchTMHM) / 4), + new(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 46), + new(InventoryType.PCItems, PCItems, 999, OFS_PCItem, (OFS_PouchHeldItem - OFS_PCItem) / 4), + }; + } + + public PokeBlock3Case PokeBlocks + { + get => new(Large, 0x7F8); + set => SetData(Large, value.Write(), 0x7F8); + } + + protected override int SeenOffset2 => 0x938; + + public DecorationInventory3 Decorations => new(Large.AsSpan(0x26A0, DecorationInventory3.SIZE)); + + public Swarm3 Swarm + { + get => new(Large.Slice(0x2AFC, Swarm3.SIZE)); + set => SetData(Large, value.Data, 0x2AFC); + } + + private void ClearSwarm() => Large.AsSpan(0x2AFC, Swarm3.SIZE).Clear(); + + public IReadOnlyList DefaultSwarms => Swarm3Details.Swarms_RS; + + public int SwarmIndex + { + get => Array.FindIndex(Swarm3Details.Swarms_RS, z => z.MapNum == Swarm.MapNum); + set + { + var arr = DefaultSwarms; + if ((uint)value >= arr.Count) + ClearSwarm(); + else + Swarm = arr[value]; + } + } + + protected override int MailOffset => 0x2B4C; + + protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, 2) + (2 * 0x38) + (4 * slot); // consecutive vals, after both consecutive slots & 2 mail + public override string GetDaycareRNGSeed(int loc) => ReadUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2))).ToString("X4"); + public override void SetDaycareRNGSeed(int loc, string seed) => WriteUInt16LittleEndian(Large.AsSpan(GetDaycareEXPOffset(2)), (ushort)Util.GetHexValue(seed)); + + protected override int ExternalEventData => 0x311B; + + #region eBerry + private const int OFFSET_EBERRY = 0x3160; + private const int SIZE_EBERRY = 0x530; + + public byte[] GetEReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY); + public void SetEReaderBerry(ReadOnlySpan data) => data.CopyTo(Large.AsSpan(OFFSET_EBERRY)); + + public override string EBerryName => GetString(Large.AsSpan(OFFSET_EBERRY, 7)); + public override bool IsEBerryEngima => Large[OFFSET_EBERRY] is 0 or 0xFF; + #endregion + + public override MysteryEvent3 MysteryEvent + { + get => new(Large.Slice(0x3690, MysteryEvent3.SIZE)); + set => SetData(Large, value.Data, 0x3690); + } + + protected override int SeenOffset3 => 0x3A8C; + #endregion } diff --git a/PKHeX.Core/Saves/SAV3RSBox.cs b/PKHeX.Core/Saves/SAV3RSBox.cs index 992fdcc40..a56a880a5 100644 --- a/PKHeX.Core/Saves/SAV3RSBox.cs +++ b/PKHeX.Core/Saves/SAV3RSBox.cs @@ -3,211 +3,210 @@ using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 object for Pokémon Ruby Sapphire Box saves. +/// +public sealed class SAV3RSBox : SaveFile, IGCSaveFile { - /// - /// Generation 3 object for Pokémon Ruby Sapphire Box saves. - /// - public sealed class SAV3RSBox : SaveFile, IGCSaveFile + protected internal override string ShortSummary => $"{Version} #{SaveCount:0000}"; + public override string Extension => this.GCExtension(); + public override PersonalTable Personal => PersonalTable.RS; + public override IReadOnlyList HeldItems => Legal.HeldItems_RS; + public SAV3GCMemoryCard? MemoryCard { get; init; } + private readonly bool Japanese; + + public SAV3RSBox(byte[] data, SAV3GCMemoryCard memCard) : this(data) => MemoryCard = memCard; + + public SAV3RSBox(bool japanese = false) : base(SaveUtil.SIZE_G3BOX) { - protected internal override string ShortSummary => $"{Version} #{SaveCount:0000}"; - public override string Extension => this.GCExtension(); - public override PersonalTable Personal => PersonalTable.RS; - public override IReadOnlyList HeldItems => Legal.HeldItems_RS; - public SAV3GCMemoryCard? MemoryCard { get; init; } - private readonly bool Japanese; + Japanese = japanese; + Box = 0; + Blocks = Array.Empty(); + ClearBoxes(); + } - public SAV3RSBox(byte[] data, SAV3GCMemoryCard memCard) : this(data) => MemoryCard = memCard; + public SAV3RSBox(byte[] data) : base(data) + { + Japanese = data[0] == 0x83; // ポケモンボックス R&S + Blocks = ReadBlocks(data); + InitializeData(); + } - public SAV3RSBox(bool japanese = false) : base(SaveUtil.SIZE_G3BOX) + private void InitializeData() + { + // Detect active save + int[] SaveCounts = Array.ConvertAll(Blocks, block => (int)block.SaveCount); + SaveCount = SaveCounts.Max(); + int ActiveSAV = Array.IndexOf(SaveCounts, SaveCount) / BLOCK_COUNT; + Blocks = Blocks.Skip(ActiveSAV * BLOCK_COUNT).Take(BLOCK_COUNT).OrderBy(b => b.ID).ToArray(); + + // Set up PC data buffer beyond end of save file. + Box = Data.Length; + Array.Resize(ref Data, Data.Length + SIZE_RESERVED); // More than enough empty space. + + // Copy block to the allocated location + const int copySize = BLOCK_SIZE - 0x10; + foreach (var b in Blocks) + Array.Copy(Data, b.Offset + 0xC, Data, (int) (Box + (b.ID * copySize)), copySize); + } + + private static BlockInfoRSBOX[] ReadBlocks(byte[] data) + { + var blocks = new BlockInfoRSBOX[2 * BLOCK_COUNT]; + for (int i = 0; i < blocks.Length; i++) { - Japanese = japanese; - Box = 0; - Blocks = Array.Empty(); - ClearBoxes(); + int offset = BLOCK_SIZE + (i * BLOCK_SIZE); + blocks[i] = new BlockInfoRSBOX(data, offset); } - public SAV3RSBox(byte[] data) : base(data) + return blocks; + } + + private BlockInfoRSBOX[] Blocks; + private int SaveCount; + private const int BLOCK_COUNT = 23; + private const int BLOCK_SIZE = 0x2000; + private const int SIZE_RESERVED = BLOCK_COUNT * BLOCK_SIZE; // unpacked box data + + protected override byte[] GetFinalData() + { + var newFile = GetInnerData(); + + // Return the gci if Memory Card is not being exported + if (MemoryCard is null) + return newFile; + + MemoryCard.WriteSaveGameData(newFile); + return MemoryCard.Data; + } + + private byte[] GetInnerData() + { + // Copy Box data back + const int copySize = BLOCK_SIZE - 0x10; + foreach (var b in Blocks) + Array.Copy(Data, (int) (Box + (b.ID * copySize)), Data, b.Offset + 0xC, copySize); + + SetChecksums(); + + return GetData(0, Data.Length - SIZE_RESERVED); + } + + // Configuration + protected override SaveFile CloneInternal() + { + var data = GetInnerData(); + var sav = MemoryCard is not null ? new SAV3RSBox(data, MemoryCard) : new SAV3RSBox(data); + return sav; + } + + protected override int SIZE_STORED => PokeCrypto.SIZE_3STORED + 4; + protected override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY; // unused + public override PKM BlankPKM => new PK3(); + public override Type PKMType => typeof(PK3); + + public override int MaxMoveID => Legal.MaxMoveID_3; + public override int MaxSpeciesID => Legal.MaxSpeciesID_3; + public override int MaxAbilityID => Legal.MaxAbilityID_3; + public override int MaxItemID => Legal.MaxItemID_3; + public override int MaxBallID => Legal.MaxBallID_3; + public override int MaxGameID => Legal.MaxGameID_3; + + public override int MaxEV => 255; + public override int Generation => 3; + public override EntityContext Context => EntityContext.Gen3; + protected override int GiftCountMax => 1; + public override int OTLength => 7; + public override int NickLength => 10; + public override int MaxMoney => 999999; + public override bool HasBoxWallpapers => false; + + public override int BoxCount => 50; + public override bool HasParty => false; + public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGBA(data); + + // Checksums + protected override void SetChecksums() => Blocks.SetChecksums(Data); + public override bool ChecksumsValid => Blocks.GetChecksumsValid(Data); + public override string ChecksumInfo => Blocks.GetChecksumInfo(Data); + + // Trainer Info + public override GameVersion Version { get => GameVersion.RSBOX; protected set { } } + + // Storage + public override int GetPartyOffset(int slot) => -1; + public override int GetBoxOffset(int box) => Box + 8 + (SIZE_STORED * box * 30); + + public override int CurrentBox + { + get => Data[Box + 4] * 2; + set => Data[Box + 4] = (byte)(value / 2); + } + + protected override int GetBoxWallpaperOffset(int box) + { + // Box Wallpaper is directly after the Box Names + int offset = Box + 0x1ED19 + (box / 2); + return offset; + } + + public override string GetBoxName(int box) + { + // Tweaked for the 1-30/31-60 box showing + int lo = (30 *(box%2)) + 1; + int hi = 30*((box % 2) + 1); + string boxName = $"[{lo:00}-{hi:00}] "; + box /= 2; + + int offset = Box + 0x1EC38 + (9 * box); + if (Data[offset] is 0 or 0xFF) + boxName += $"BOX {box + 1}"; + boxName += GetString(offset, 9); + + return boxName; + } + + public override void SetBoxName(int box, string value) + { + int offset = Box + 0x1EC38 + (9 * box); + var span = Data.AsSpan(offset, 9); + if (value == $"BOX {box + 1}") { - Japanese = data[0] == 0x83; // ポケモンボックス R&S - Blocks = ReadBlocks(data); - InitializeData(); + span.Clear(); + return; } + SetString(span, value.AsSpan(), 8, StringConverterOption.ClearZero); + } - private void InitializeData() - { - // Detect active save - int[] SaveCounts = Array.ConvertAll(Blocks, block => (int)block.SaveCount); - SaveCount = SaveCounts.Max(); - int ActiveSAV = Array.IndexOf(SaveCounts, SaveCount) / BLOCK_COUNT; - Blocks = Blocks.Skip(ActiveSAV * BLOCK_COUNT).Take(BLOCK_COUNT).OrderBy(b => b.ID).ToArray(); + protected override PKM GetPKM(byte[] data) + { + if (data.Length != PokeCrypto.SIZE_3STORED) + Array.Resize(ref data, PokeCrypto.SIZE_3STORED); + return new PK3(data); + } - // Set up PC data buffer beyond end of save file. - Box = Data.Length; - Array.Resize(ref Data, Data.Length + SIZE_RESERVED); // More than enough empty space. + protected override byte[] DecryptPKM(byte[] data) + { + if (data.Length != PokeCrypto.SIZE_3STORED) + Array.Resize(ref data, PokeCrypto.SIZE_3STORED); + return PokeCrypto.DecryptArray3(data); + } - // Copy block to the allocated location - const int copySize = BLOCK_SIZE - 0x10; - foreach (var b in Blocks) - Array.Copy(Data, b.Offset + 0xC, Data, (int) (Box + (b.ID * copySize)), copySize); - } + protected override void SetDex(PKM pk) { /* No Pokedex for this game, do nothing */ } - private static BlockInfoRSBOX[] ReadBlocks(byte[] data) - { - var blocks = new BlockInfoRSBOX[2 * BLOCK_COUNT]; - for (int i = 0; i < blocks.Length; i++) - { - int offset = BLOCK_SIZE + (i * BLOCK_SIZE); - blocks[i] = new BlockInfoRSBOX(data, offset); - } + public override void WriteBoxSlot(PKM pk, Span data, int offset) + { + base.WriteBoxSlot(pk, data, offset); + WriteUInt16LittleEndian(data[(PokeCrypto.SIZE_3STORED)..], (ushort)pk.TID); + WriteUInt16LittleEndian(data[(PokeCrypto.SIZE_3STORED + 2)..], (ushort)pk.SID); + } - return blocks; - } + public override string GetString(ReadOnlySpan data) => StringConverter3.GetString(data, Japanese); - private BlockInfoRSBOX[] Blocks; - private int SaveCount; - private const int BLOCK_COUNT = 23; - private const int BLOCK_SIZE = 0x2000; - private const int SIZE_RESERVED = BLOCK_COUNT * BLOCK_SIZE; // unpacked box data - - protected override byte[] GetFinalData() - { - var newFile = GetInnerData(); - - // Return the gci if Memory Card is not being exported - if (MemoryCard is null) - return newFile; - - MemoryCard.WriteSaveGameData(newFile); - return MemoryCard.Data; - } - - private byte[] GetInnerData() - { - // Copy Box data back - const int copySize = BLOCK_SIZE - 0x10; - foreach (var b in Blocks) - Array.Copy(Data, (int) (Box + (b.ID * copySize)), Data, b.Offset + 0xC, copySize); - - SetChecksums(); - - return GetData(0, Data.Length - SIZE_RESERVED); - } - - // Configuration - protected override SaveFile CloneInternal() - { - var data = GetInnerData(); - var sav = MemoryCard is not null ? new SAV3RSBox(data, MemoryCard) : new SAV3RSBox(data); - return sav; - } - - protected override int SIZE_STORED => PokeCrypto.SIZE_3STORED + 4; - protected override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY; // unused - public override PKM BlankPKM => new PK3(); - public override Type PKMType => typeof(PK3); - - public override int MaxMoveID => Legal.MaxMoveID_3; - public override int MaxSpeciesID => Legal.MaxSpeciesID_3; - public override int MaxAbilityID => Legal.MaxAbilityID_3; - public override int MaxItemID => Legal.MaxItemID_3; - public override int MaxBallID => Legal.MaxBallID_3; - public override int MaxGameID => Legal.MaxGameID_3; - - public override int MaxEV => 255; - public override int Generation => 3; - public override EntityContext Context => EntityContext.Gen3; - protected override int GiftCountMax => 1; - public override int OTLength => 7; - public override int NickLength => 10; - public override int MaxMoney => 999999; - public override bool HasBoxWallpapers => false; - - public override int BoxCount => 50; - public override bool HasParty => false; - public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGBA(data); - - // Checksums - protected override void SetChecksums() => Blocks.SetChecksums(Data); - public override bool ChecksumsValid => Blocks.GetChecksumsValid(Data); - public override string ChecksumInfo => Blocks.GetChecksumInfo(Data); - - // Trainer Info - public override GameVersion Version { get => GameVersion.RSBOX; protected set { } } - - // Storage - public override int GetPartyOffset(int slot) => -1; - public override int GetBoxOffset(int box) => Box + 8 + (SIZE_STORED * box * 30); - - public override int CurrentBox - { - get => Data[Box + 4] * 2; - set => Data[Box + 4] = (byte)(value / 2); - } - - protected override int GetBoxWallpaperOffset(int box) - { - // Box Wallpaper is directly after the Box Names - int offset = Box + 0x1ED19 + (box / 2); - return offset; - } - - public override string GetBoxName(int box) - { - // Tweaked for the 1-30/31-60 box showing - int lo = (30 *(box%2)) + 1; - int hi = 30*((box % 2) + 1); - string boxName = $"[{lo:00}-{hi:00}] "; - box /= 2; - - int offset = Box + 0x1EC38 + (9 * box); - if (Data[offset] is 0 or 0xFF) - boxName += $"BOX {box + 1}"; - boxName += GetString(offset, 9); - - return boxName; - } - - public override void SetBoxName(int box, string value) - { - int offset = Box + 0x1EC38 + (9 * box); - var span = Data.AsSpan(offset, 9); - if (value == $"BOX {box + 1}") - { - span.Clear(); - return; - } - SetString(span, value.AsSpan(), 8, StringConverterOption.ClearZero); - } - - protected override PKM GetPKM(byte[] data) - { - if (data.Length != PokeCrypto.SIZE_3STORED) - Array.Resize(ref data, PokeCrypto.SIZE_3STORED); - return new PK3(data); - } - - protected override byte[] DecryptPKM(byte[] data) - { - if (data.Length != PokeCrypto.SIZE_3STORED) - Array.Resize(ref data, PokeCrypto.SIZE_3STORED); - return PokeCrypto.DecryptArray3(data); - } - - protected override void SetDex(PKM pkm) { /* No Pokedex for this game, do nothing */ } - - public override void WriteBoxSlot(PKM pkm, Span data, int offset) - { - base.WriteBoxSlot(pkm, data, offset); - WriteUInt16LittleEndian(data[(PokeCrypto.SIZE_3STORED)..], (ushort)pkm.TID); - WriteUInt16LittleEndian(data[(PokeCrypto.SIZE_3STORED + 2)..], (ushort)pkm.SID); - } - - public override string GetString(ReadOnlySpan data) => StringConverter3.GetString(data, Japanese); - - public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter3.SetString(destBuffer, value, maxLength, Japanese, option); - } + public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter3.SetString(destBuffer, value, maxLength, Japanese, option); } } diff --git a/PKHeX.Core/Saves/SAV3XD.cs b/PKHeX.Core/Saves/SAV3XD.cs index 6b8c64e1a..fd67a964a 100644 --- a/PKHeX.Core/Saves/SAV3XD.cs +++ b/PKHeX.Core/Saves/SAV3XD.cs @@ -1,393 +1,392 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 object for Pokémon XD saves. +/// +public sealed class SAV3XD : SaveFile, IGCSaveFile { - /// - /// Generation 3 object for Pokémon XD saves. - /// - public sealed class SAV3XD : SaveFile, IGCSaveFile + protected internal override string ShortSummary => $"{OT} ({Version}) #{SaveCount:0000}"; + public override string Extension => this.GCExtension(); + public SAV3GCMemoryCard? MemoryCard { get; init; } + + private const int SLOT_SIZE = 0x28000; + private const int SLOT_START = 0x6000; + private const int SLOT_COUNT = 2; + + private int SaveCount = -1; + private int SaveIndex = -1; + private int Trainer1; + private int Memo; + private int Shadow; + private readonly StrategyMemo StrategyMemo; + private readonly ShadowInfoTableXD ShadowInfo; + public int MaxShadowID => ShadowInfo.Count; + private int OFS_PouchHeldItem, OFS_PouchKeyItem, OFS_PouchBalls, OFS_PouchTMHM, OFS_PouchBerry, OFS_PouchCologne, OFS_PouchDisc; + private readonly int[] subOffsets = new int[16]; + private readonly byte[] BAK; + + public SAV3XD() : base(SaveUtil.SIZE_G3XD) { - protected internal override string ShortSummary => $"{OT} ({Version}) #{SaveCount:0000}"; - public override string Extension => this.GCExtension(); - public SAV3GCMemoryCard? MemoryCard { get; init; } + BAK = Array.Empty(); + // create fake objects + StrategyMemo = new StrategyMemo(); + ShadowInfo = new ShadowInfoTableXD(false); + Initialize(); + ClearBoxes(); + } - private const int SLOT_SIZE = 0x28000; - private const int SLOT_START = 0x6000; - private const int SLOT_COUNT = 2; + public SAV3XD(byte[] data) : base(data) + { + BAK = data; + InitializeData(out StrategyMemo, out ShadowInfo); + Initialize(); + } - private int SaveCount = -1; - private int SaveIndex = -1; - private int Trainer1; - private int Memo; - private int Shadow; - private readonly StrategyMemo StrategyMemo; - private readonly ShadowInfoTableXD ShadowInfo; - public int MaxShadowID => ShadowInfo.Count; - private int OFS_PouchHeldItem, OFS_PouchKeyItem, OFS_PouchBalls, OFS_PouchTMHM, OFS_PouchBerry, OFS_PouchCologne, OFS_PouchDisc; - private readonly int[] subOffsets = new int[16]; - private readonly byte[] BAK; + public override PersonalTable Personal => PersonalTable.RS; + public override IReadOnlyList HeldItems => Legal.HeldItems_XD; - public SAV3XD() : base(SaveUtil.SIZE_G3XD) + private void InitializeData(out StrategyMemo memo, out ShadowInfoTableXD info) + { + // Scan all 3 save slots for the highest counter + for (int i = 0; i < SLOT_COUNT; i++) { - BAK = Array.Empty(); - // create fake objects - StrategyMemo = new StrategyMemo(); - ShadowInfo = new ShadowInfoTableXD(false); - Initialize(); - ClearBoxes(); + int slotOffset = SLOT_START + (i * SLOT_SIZE); + int SaveCounter = ReadInt32BigEndian(Data.AsSpan(slotOffset + 4)); + if (SaveCounter <= SaveCount) + continue; + + SaveCount = SaveCounter; + SaveIndex = i; } - public SAV3XD(byte[] data) : base(data) + // Decrypt most recent save slot + Data = ReadSlot(Data, SaveIndex); + + // Get Offset Info + Span subLength = stackalloc ushort[16]; + for (int i = 0; i < 16; i++) { - BAK = data; - InitializeData(out StrategyMemo, out ShadowInfo); - Initialize(); + subLength[i] = ReadUInt16BigEndian(Data.AsSpan(0x20 + (2 * i))); + subOffsets[i] = ReadUInt16BigEndian(Data.AsSpan(0x40 + (4 * i))) | (ReadUInt16BigEndian(Data.AsSpan(0x40 + (4 * i) + 2)) << 16); } - public override PersonalTable Personal => PersonalTable.RS; - public override IReadOnlyList HeldItems => Legal.HeldItems_XD; + // Offsets are displaced by the 0xA8 savedata region + Trainer1 = subOffsets[1] + 0xA8; + Party = Trainer1 + 0x30; + Box = subOffsets[2] + 0xA8; + DaycareOffset = subOffsets[4] + 0xA8; + Memo = subOffsets[5] + 0xA8; + Shadow = subOffsets[7] + 0xA8; + // Purifier = subOffsets[14] + 0xA8; - private void InitializeData(out StrategyMemo memo, out ShadowInfoTableXD info) + bool jp = subLength[7] == 0x1E00; + memo = new StrategyMemo(Data, Memo, xd: true); + info = new ShadowInfoTableXD(Data.AsSpan(Shadow, subLength[7]), jp); + } + + private static byte[] ReadSlot(Span data, int index) + { + int slotOffset = SLOT_START + (index * SLOT_SIZE); + var slot = data.Slice(slotOffset, SLOT_SIZE); + var result = new byte[SLOT_SIZE]; + var destSpan = result.AsSpan(); + + // Decrypt Slot + Span keys = stackalloc ushort[4]; + GeniusCrypto.ReadKeys(slot.Slice(8, keys.Length * 2), keys); + Range r = new(0x10, 0x27FD8); + GeniusCrypto.Decrypt(slot[r], destSpan[r], keys); // body + slot[..0x10].CopyTo(destSpan[..0x10]); // checksums + slot[^0x18..].CopyTo(destSpan[^0x18..]); // tail end + return result; + } + + private void Initialize() + { + OFS_PouchHeldItem = Trainer1 + 0x4C8; + OFS_PouchKeyItem = Trainer1 + 0x540; + OFS_PouchBalls = Trainer1 + 0x5EC; + OFS_PouchTMHM = Trainer1 + 0x62C; + OFS_PouchBerry = Trainer1 + 0x72C; + OFS_PouchCologne = Trainer1 + 0x7E4; + OFS_PouchDisc = Trainer1 + 0x7F0; + + // Since PartyCount is not stored in the save file, + // Count up how many party slots are active. + for (int i = 0; i < 6; i++) { - // Scan all 3 save slots for the highest counter - for (int i = 0; i < SLOT_COUNT; i++) - { - int slotOffset = SLOT_START + (i * SLOT_SIZE); - int SaveCounter = ReadInt32BigEndian(Data.AsSpan(slotOffset + 4)); - if (SaveCounter <= SaveCount) - continue; - - SaveCount = SaveCounter; - SaveIndex = i; - } - - // Decrypt most recent save slot - Data = ReadSlot(Data, SaveIndex); - - // Get Offset Info - Span subLength = stackalloc ushort[16]; - for (int i = 0; i < 16; i++) - { - subLength[i] = ReadUInt16BigEndian(Data.AsSpan(0x20 + (2 * i))); - subOffsets[i] = ReadUInt16BigEndian(Data.AsSpan(0x40 + (4 * i))) | ReadUInt16BigEndian(Data.AsSpan(0x40 + (4 * i) + 2)) << 16; - } - - // Offsets are displaced by the 0xA8 savedata region - Trainer1 = subOffsets[1] + 0xA8; - Party = Trainer1 + 0x30; - Box = subOffsets[2] + 0xA8; - DaycareOffset = subOffsets[4] + 0xA8; - Memo = subOffsets[5] + 0xA8; - Shadow = subOffsets[7] + 0xA8; - // Purifier = subOffsets[14] + 0xA8; - - bool jp = subLength[7] == 0x1E00; - memo = new StrategyMemo(Data, Memo, xd: true); - info = new ShadowInfoTableXD(Data.AsSpan(Shadow, subLength[7]), jp); - } - - private static byte[] ReadSlot(Span data, int index) - { - int slotOffset = SLOT_START + (index * SLOT_SIZE); - var slot = data.Slice(slotOffset, SLOT_SIZE); - var result = new byte[SLOT_SIZE]; - var destSpan = result.AsSpan(); - - // Decrypt Slot - Span keys = stackalloc ushort[4]; - GeniusCrypto.ReadKeys(slot.Slice(8, keys.Length * 2), keys); - Range r = new(0x10, 0x27FD8); - GeniusCrypto.Decrypt(slot[r], destSpan[r], keys); // body - slot[..0x10].CopyTo(destSpan[..0x10]); // checksums - slot[^0x18..].CopyTo(destSpan[^0x18..]); // tail end - return result; - } - - private void Initialize() - { - OFS_PouchHeldItem = Trainer1 + 0x4C8; - OFS_PouchKeyItem = Trainer1 + 0x540; - OFS_PouchBalls = Trainer1 + 0x5EC; - OFS_PouchTMHM = Trainer1 + 0x62C; - OFS_PouchBerry = Trainer1 + 0x72C; - OFS_PouchCologne = Trainer1 + 0x7E4; - OFS_PouchDisc = Trainer1 + 0x7F0; - - // Since PartyCount is not stored in the save file, - // Count up how many party slots are active. - for (int i = 0; i < 6; i++) - { - if (GetPartySlot(Data, GetPartyOffset(i)).Species != 0) - PartyCount++; - } - } - - protected override byte[] GetFinalData() - { - var newFile = GetInnerData(); - - // Return the gci if Memory Card is not being exported - if (MemoryCard is null) - return newFile; - - MemoryCard.WriteSaveGameData(newFile); - return MemoryCard.Data; - } - - private byte[] GetInnerData() - { - // Set Memo Back - StrategyMemo.Write(); // .CopyTo(Data, Memo); - ShadowInfo.Write().CopyTo(Data, Shadow); - SetChecksums(); - - // Put save slot back in original save data - var destOffset = SLOT_START + (SaveIndex * SLOT_SIZE); - byte[] dest = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[])BAK.Clone(); - var destSpan = dest.AsSpan(destOffset, Data.Length); - - // Get updated save slot data - Span slot = Data; - Span keys = stackalloc ushort[4]; - GeniusCrypto.ReadKeys(slot.Slice(8, keys.Length * 2), keys); - Range r = new(0x10, 0x27FD8); - GeniusCrypto.Encrypt(slot[r], destSpan[r], keys); - slot[..0x10].CopyTo(destSpan[..0x10]); // checksum/keys - slot[^0x18..].CopyTo(destSpan[^0x18..]); // tail end - return dest; - } - - // Configuration - protected override SaveFile CloneInternal() - { - var data = GetInnerData(); - return new SAV3XD(data) { MemoryCard = MemoryCard }; - } - - protected override int SIZE_STORED => PokeCrypto.SIZE_3XSTORED; - protected override int SIZE_PARTY => PokeCrypto.SIZE_3XSTORED; // unused - public override PKM BlankPKM => new XK3(); - public override Type PKMType => typeof(XK3); - - public override int MaxMoveID => Legal.MaxMoveID_3; - public override int MaxSpeciesID => Legal.MaxSpeciesID_3; - public override int MaxAbilityID => Legal.MaxAbilityID_3; - public override int MaxBallID => Legal.MaxBallID_3; - public override int MaxItemID => Legal.MaxItemID_3_XD; - public override int MaxGameID => Legal.MaxGameID_3; - - public override int MaxEV => 255; - public override int Generation => 3; - public override EntityContext Context => EntityContext.Gen3; - protected override int GiftCountMax => 1; - public override int OTLength => 7; - public override int NickLength => 10; - public override int MaxMoney => 9999999; - - public override int BoxCount => 8; - - public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGC(data); - - // Checksums - protected override void SetChecksums() - { - Data = SetChecksums(Data, subOffsets[0]); - } - - public override bool ChecksumsValid => !ChecksumInfo.Contains("Invalid"); - - public override string ChecksumInfo - { - get - { - byte[] data = SetChecksums(Data, subOffsets[0]); - - const int start = 0xA8; // 0x88 + 0x20 - int oldHC = ReadInt32BigEndian(Data.AsSpan(start + subOffsets[0] + 0x38)); - int newHC = ReadInt32BigEndian(data.AsSpan(start + subOffsets[0] + 0x38)); - bool header = newHC == oldHC; - - var oldCHK = Data.AsSpan(0x10, 0x10); - var newCHK = data.AsSpan(0x10, 0x10); - bool body = newCHK.SequenceEqual(oldCHK); - return $"Header Checksum {(header ? "V" : "Inv")}alid, Body Checksum {(body ? "V" : "Inv")}alid."; - } - } - - private static byte[] SetChecksums(byte[] input, int subOffset0) - { - if (input.Length != SLOT_SIZE) - throw new ArgumentException("Input should be a slot, not the entire save binary."); - - byte[] data = (byte[])input.Clone(); - const int start = 0xA8; // 0x88 + 0x20 - - // Header Checksum - int newHC = 0; - for (int i = 0; i < 8; i++) - newHC += data[i]; - - WriteInt32BigEndian(data.AsSpan(start + subOffset0 + 0x38), newHC); - - // Body Checksum - data.AsSpan(0x10, 0x10).Clear(); // Clear old Checksum Data - Span checksum = stackalloc uint[4]; - int dt = 8; - for (int i = 0; i < checksum.Length; i++) - { - uint val = 0; - var end = dt + 0x9FF4; - for (int j = dt; j < end; j += 2) - val += ReadUInt16BigEndian(data.AsSpan(j)); - dt = end; - checksum[i] = val; - } - - Span newchks = stackalloc ushort[8]; - for (int i = 0; i < 4; i++) - { - newchks[i*2] = (ushort)(checksum[i] >> 16); - newchks[(i * 2) + 1] = (ushort)checksum[i]; - } - - for (int i = 0; i < newchks.Length; i++) - { - var dest = data.AsSpan(0x10 + (2 * i)); - var chk = newchks[newchks.Length - 1 - i]; - WriteUInt16BigEndian(dest, chk); - } - - return data; - } - // Trainer Info - public override GameVersion Version { get => GameVersion.XD; protected set { } } - public override string OT { get => GetString(Trainer1 + 0x00, 20); set => SetString(Data.AsSpan(Trainer1 + 0x00, 20), value.AsSpan(), 10, StringConverterOption.ClearZero); } - public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2C)); set => WriteUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2C), (ushort)value); } - public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2E)); set => WriteUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2E), (ushort)value); } - - public override int Gender { get => Data[Trainer1 + 0x8E0]; set => Data[Trainer1 + 0x8E0] = (byte)value; } - public override uint Money { get => ReadUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E4)); set => WriteUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E4), value); } - public uint Coupons { get => ReadUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E8)); set => WriteUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E8), value); } - - // Storage - public override int GetPartyOffset(int slot) => Party + (SIZE_STORED * slot); - private int GetBoxInfoOffset(int box) => Box + (((30 * SIZE_STORED) + 0x14) * box); - public override int GetBoxOffset(int box) => GetBoxInfoOffset(box) + 20; - public override string GetBoxName(int box) => GetString(GetBoxInfoOffset(box), 16); - - public override void SetBoxName(int box, string value) - { - SetString(Data.AsSpan(GetBoxInfoOffset(box), 20), value.AsSpan(), 8, StringConverterOption.ClearZero); - } - - protected override PKM GetPKM(byte[] data) - { - if (data.Length != SIZE_STORED) - Array.Resize(ref data, SIZE_STORED); - return new XK3(data); - } - - protected override byte[] DecryptPKM(byte[] data) => data; - public override PKM GetPartySlot(byte[] data, int offset) => GetStoredSlot(data, offset); - - public override PKM GetStoredSlot(byte[] data, int offset) - { - // Get Shadow Data - var pk = (XK3)base.GetStoredSlot(data, offset); - if (pk.ShadowID > 0 && pk.ShadowID < ShadowInfo.Count) - pk.Purification = ShadowInfo[pk.ShadowID].Purification; - return pk; - } - - protected override void SetPKM(PKM pkm, bool isParty = false) - { - if (pkm is not XK3 pk) - return; // shouldn't ever hit - - if (pk.CurrentRegion == 0) - pk.CurrentRegion = 2; // NTSC-U - if (pk.OriginalRegion == 0) - pk.OriginalRegion = 2; // NTSC-U - - // Set Shadow Data back to save - if (pk.ShadowID <= 0 || pk.ShadowID >= ShadowInfo.Count) - return; - - var entry = ShadowInfo[pk.ShadowID]; - entry.Purification = pk.Purification; - entry.Species = pk.Species; - entry.PID = pk.PID; - entry.IV_HP = pk.IV_HP ; - entry.IV_ATK = pk.IV_ATK; - entry.IV_DEF = pk.IV_DEF; - entry.IV_SPA = pk.IV_SPA; - entry.IV_SPD = pk.IV_SPD; - entry.IV_SPE = pk.IV_SPE; - } - - protected override void SetDex(PKM pkm) - { - /* - if (pkm.Species is 0 or > Legal.MaxSpeciesID_3) - return; - if (pkm.IsEgg) - return; - - // Dex Related - var entry = StrategyMemo.GetEntry(pkm.Species); - if (entry.IsEmpty) // Populate - { - entry.Species = pkm.Species; - entry.PID = pkm.PID; - entry.TID = pkm.TID; - entry.SID = pkm.SID; - } - if (entry.Matches(pkm.Species, pkm.PID, pkm.TID, pkm.SID)) - { - entry.Seen = true; - entry.Owned = true; - } - StrategyMemo.SetEntry(entry); - */ - } - - public override IReadOnlyList Inventory - { - get - { - InventoryPouch[] pouch = - { - new InventoryPouch3GC(InventoryType.Items, Legal.Pouch_Items_XD, 999, OFS_PouchHeldItem, 30), // 20 COLO, 30 XD - new InventoryPouch3GC(InventoryType.KeyItems, Legal.Pouch_Key_XD, 1, OFS_PouchKeyItem, 43), - new InventoryPouch3GC(InventoryType.Balls, Legal.Pouch_Ball_RS, 999, OFS_PouchBalls, 16), - new InventoryPouch3GC(InventoryType.TMHMs, Legal.Pouch_TM_RS, 999, OFS_PouchTMHM, 64), - new InventoryPouch3GC(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 46), - new InventoryPouch3GC(InventoryType.Medicine, Legal.Pouch_Cologne_XD, 999, OFS_PouchCologne, 3), // Cologne - new InventoryPouch3GC(InventoryType.BattleItems, Legal.Pouch_Disc_XD, 1, OFS_PouchDisc, 60), - }; - return pouch.LoadAll(Data); - } - set => value.SaveAll(Data); - } - - // Daycare Structure: - // 0x00 -- Occupied - // 0x01 -- Deposited Level - // 0x02-0x03 -- unused? - // 0x04-0x07 -- Initial EXP - public override int GetDaycareSlotOffset(int loc, int slot) { return DaycareOffset + 8; } - public override uint? GetDaycareEXP(int loc, int slot) { return null; } - public override bool? IsDaycareOccupied(int loc, int slot) { return null; } - public override void SetDaycareEXP(int loc, int slot, uint EXP) { /* todo */ } - public override void SetDaycareOccupied(int loc, int slot, bool occupied) { /* todo */ } - - public override string GetString(ReadOnlySpan data) => StringConverter3GC.GetString(data); - - public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter3GC.SetString(destBuffer, value, maxLength, option); + if (GetPartySlot(Data, GetPartyOffset(i)).Species != 0) + PartyCount++; } } + + protected override byte[] GetFinalData() + { + var newFile = GetInnerData(); + + // Return the gci if Memory Card is not being exported + if (MemoryCard is null) + return newFile; + + MemoryCard.WriteSaveGameData(newFile); + return MemoryCard.Data; + } + + private byte[] GetInnerData() + { + // Set Memo Back + StrategyMemo.Write(); // .CopyTo(Data, Memo); + ShadowInfo.Write().CopyTo(Data, Shadow); + SetChecksums(); + + // Put save slot back in original save data + var destOffset = SLOT_START + (SaveIndex * SLOT_SIZE); + byte[] dest = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[])BAK.Clone(); + var destSpan = dest.AsSpan(destOffset, Data.Length); + + // Get updated save slot data + Span slot = Data; + Span keys = stackalloc ushort[4]; + GeniusCrypto.ReadKeys(slot.Slice(8, keys.Length * 2), keys); + Range r = new(0x10, 0x27FD8); + GeniusCrypto.Encrypt(slot[r], destSpan[r], keys); + slot[..0x10].CopyTo(destSpan[..0x10]); // checksum/keys + slot[^0x18..].CopyTo(destSpan[^0x18..]); // tail end + return dest; + } + + // Configuration + protected override SaveFile CloneInternal() + { + var data = GetInnerData(); + return new SAV3XD(data) { MemoryCard = MemoryCard }; + } + + protected override int SIZE_STORED => PokeCrypto.SIZE_3XSTORED; + protected override int SIZE_PARTY => PokeCrypto.SIZE_3XSTORED; // unused + public override PKM BlankPKM => new XK3(); + public override Type PKMType => typeof(XK3); + + public override int MaxMoveID => Legal.MaxMoveID_3; + public override int MaxSpeciesID => Legal.MaxSpeciesID_3; + public override int MaxAbilityID => Legal.MaxAbilityID_3; + public override int MaxBallID => Legal.MaxBallID_3; + public override int MaxItemID => Legal.MaxItemID_3_XD; + public override int MaxGameID => Legal.MaxGameID_3; + + public override int MaxEV => 255; + public override int Generation => 3; + public override EntityContext Context => EntityContext.Gen3; + protected override int GiftCountMax => 1; + public override int OTLength => 7; + public override int NickLength => 10; + public override int MaxMoney => 9999999; + + public override int BoxCount => 8; + + public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentGC(data); + + // Checksums + protected override void SetChecksums() + { + Data = SetChecksums(Data, subOffsets[0]); + } + + public override bool ChecksumsValid => !ChecksumInfo.Contains("Invalid"); + + public override string ChecksumInfo + { + get + { + byte[] data = SetChecksums(Data, subOffsets[0]); + + const int start = 0xA8; // 0x88 + 0x20 + int oldHC = ReadInt32BigEndian(Data.AsSpan(start + subOffsets[0] + 0x38)); + int newHC = ReadInt32BigEndian(data.AsSpan(start + subOffsets[0] + 0x38)); + bool header = newHC == oldHC; + + var oldCHK = Data.AsSpan(0x10, 0x10); + var newCHK = data.AsSpan(0x10, 0x10); + bool body = newCHK.SequenceEqual(oldCHK); + return $"Header Checksum {(header ? "V" : "Inv")}alid, Body Checksum {(body ? "V" : "Inv")}alid."; + } + } + + private static byte[] SetChecksums(byte[] input, int subOffset0) + { + if (input.Length != SLOT_SIZE) + throw new ArgumentException("Input should be a slot, not the entire save binary."); + + byte[] data = (byte[])input.Clone(); + const int start = 0xA8; // 0x88 + 0x20 + + // Header Checksum + int newHC = 0; + for (int i = 0; i < 8; i++) + newHC += data[i]; + + WriteInt32BigEndian(data.AsSpan(start + subOffset0 + 0x38), newHC); + + // Body Checksum + data.AsSpan(0x10, 0x10).Clear(); // Clear old Checksum Data + Span checksum = stackalloc uint[4]; + int dt = 8; + for (int i = 0; i < checksum.Length; i++) + { + uint val = 0; + var end = dt + 0x9FF4; + for (int j = dt; j < end; j += 2) + val += ReadUInt16BigEndian(data.AsSpan(j)); + dt = end; + checksum[i] = val; + } + + Span newchks = stackalloc ushort[8]; + for (int i = 0; i < 4; i++) + { + newchks[i*2] = (ushort)(checksum[i] >> 16); + newchks[(i * 2) + 1] = (ushort)checksum[i]; + } + + for (int i = 0; i < newchks.Length; i++) + { + var dest = data.AsSpan(0x10 + (2 * i)); + var chk = newchks[newchks.Length - 1 - i]; + WriteUInt16BigEndian(dest, chk); + } + + return data; + } + // Trainer Info + public override GameVersion Version { get => GameVersion.XD; protected set { } } + public override string OT { get => GetString(Trainer1 + 0x00, 20); set => SetString(Data.AsSpan(Trainer1 + 0x00, 20), value.AsSpan(), 10, StringConverterOption.ClearZero); } + public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2C)); set => WriteUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2C), (ushort)value); } + public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2E)); set => WriteUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2E), (ushort)value); } + + public override int Gender { get => Data[Trainer1 + 0x8E0]; set => Data[Trainer1 + 0x8E0] = (byte)value; } + public override uint Money { get => ReadUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E4)); set => WriteUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E4), value); } + public uint Coupons { get => ReadUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E8)); set => WriteUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E8), value); } + + // Storage + public override int GetPartyOffset(int slot) => Party + (SIZE_STORED * slot); + private int GetBoxInfoOffset(int box) => Box + (((30 * SIZE_STORED) + 0x14) * box); + public override int GetBoxOffset(int box) => GetBoxInfoOffset(box) + 20; + public override string GetBoxName(int box) => GetString(GetBoxInfoOffset(box), 16); + + public override void SetBoxName(int box, string value) + { + SetString(Data.AsSpan(GetBoxInfoOffset(box), 20), value.AsSpan(), 8, StringConverterOption.ClearZero); + } + + protected override PKM GetPKM(byte[] data) + { + if (data.Length != SIZE_STORED) + Array.Resize(ref data, SIZE_STORED); + return new XK3(data); + } + + protected override byte[] DecryptPKM(byte[] data) => data; + public override PKM GetPartySlot(byte[] data, int offset) => GetStoredSlot(data, offset); + + public override PKM GetStoredSlot(byte[] data, int offset) + { + // Get Shadow Data + var pk = (XK3)base.GetStoredSlot(data, offset); + if (pk.ShadowID > 0 && pk.ShadowID < ShadowInfo.Count) + pk.Purification = ShadowInfo[pk.ShadowID].Purification; + return pk; + } + + protected override void SetPKM(PKM pk, bool isParty = false) + { + if (pk is not XK3 xk3) + return; // shouldn't ever hit + + if (xk3.CurrentRegion == 0) + xk3.CurrentRegion = 2; // NTSC-U + if (xk3.OriginalRegion == 0) + xk3.OriginalRegion = 2; // NTSC-U + + // Set Shadow Data back to save + if (xk3.ShadowID <= 0 || xk3.ShadowID >= ShadowInfo.Count) + return; + + var entry = ShadowInfo[xk3.ShadowID]; + entry.Purification = xk3.Purification; + entry.Species = xk3.Species; + entry.PID = xk3.PID; + entry.IV_HP = xk3.IV_HP ; + entry.IV_ATK = xk3.IV_ATK; + entry.IV_DEF = xk3.IV_DEF; + entry.IV_SPA = xk3.IV_SPA; + entry.IV_SPD = xk3.IV_SPD; + entry.IV_SPE = xk3.IV_SPE; + } + + protected override void SetDex(PKM pk) + { + /* + if (pk.Species is 0 or > Legal.MaxSpeciesID_3) + return; + if (pk.IsEgg) + return; + + // Dex Related + var entry = StrategyMemo.GetEntry(pk.Species); + if (entry.IsEmpty) // Populate + { + entry.Species = pk.Species; + entry.PID = pk.PID; + entry.TID = pk.TID; + entry.SID = pk.SID; + } + if (entry.Matches(pk.Species, pk.PID, pk.TID, pk.SID)) + { + entry.Seen = true; + entry.Owned = true; + } + StrategyMemo.SetEntry(entry); + */ + } + + public override IReadOnlyList Inventory + { + get + { + InventoryPouch[] pouch = + { + new InventoryPouch3GC(InventoryType.Items, Legal.Pouch_Items_XD, 999, OFS_PouchHeldItem, 30), // 20 COLO, 30 XD + new InventoryPouch3GC(InventoryType.KeyItems, Legal.Pouch_Key_XD, 1, OFS_PouchKeyItem, 43), + new InventoryPouch3GC(InventoryType.Balls, Legal.Pouch_Ball_RS, 999, OFS_PouchBalls, 16), + new InventoryPouch3GC(InventoryType.TMHMs, Legal.Pouch_TM_RS, 999, OFS_PouchTMHM, 64), + new InventoryPouch3GC(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 46), + new InventoryPouch3GC(InventoryType.Medicine, Legal.Pouch_Cologne_XD, 999, OFS_PouchCologne, 3), // Cologne + new InventoryPouch3GC(InventoryType.BattleItems, Legal.Pouch_Disc_XD, 1, OFS_PouchDisc, 60), + }; + return pouch.LoadAll(Data); + } + set => value.SaveAll(Data); + } + + // Daycare Structure: + // 0x00 -- Occupied + // 0x01 -- Deposited Level + // 0x02-0x03 -- unused? + // 0x04-0x07 -- Initial EXP + public override int GetDaycareSlotOffset(int loc, int slot) { return DaycareOffset + 8; } + public override uint? GetDaycareEXP(int loc, int slot) { return null; } + public override bool? IsDaycareOccupied(int loc, int slot) { return null; } + public override void SetDaycareEXP(int loc, int slot, uint EXP) { /* todo */ } + public override void SetDaycareOccupied(int loc, int slot, bool occupied) { /* todo */ } + + public override string GetString(ReadOnlySpan data) => StringConverter3GC.GetString(data); + + public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter3GC.SetString(destBuffer, value, maxLength, option); + } } diff --git a/PKHeX.Core/Saves/SAV4.cs b/PKHeX.Core/Saves/SAV4.cs index 16b69a4ef..b811c0c92 100644 --- a/PKHeX.Core/Saves/SAV4.cs +++ b/PKHeX.Core/Saves/SAV4.cs @@ -1,561 +1,560 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 abstract object. +/// +/// +/// Storage data is stored in one contiguous block, and the remaining data is stored in another block. +/// +public abstract class SAV4 : SaveFile, IEventFlag37 { - /// - /// Generation 4 abstract object. - /// - /// - /// Storage data is stored in one contiguous block, and the remaining data is stored in another block. - /// - public abstract class SAV4 : SaveFile, IEventFlag37 + protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}"; + public sealed override string Extension => ".sav"; + + // Blocks & Offsets + private readonly int GeneralBlockPosition; // Small Block + private readonly int StorageBlockPosition; // Big Block + private const int PartitionSize = 0x40000; + + // SaveData is chunked into two pieces. + protected readonly byte[] Storage; + public readonly byte[] General; + protected sealed override byte[] BoxBuffer => Storage; + protected sealed override byte[] PartyBuffer => General; + + protected abstract int StorageStart { get; } + public abstract Zukan4 Dex { get; } + + protected abstract int EventFlag { get; } + protected abstract int EventWork { get; } + public sealed override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(General, offset, bitIndex); + public sealed override void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(General, offset, bitIndex, value); + + protected SAV4(int gSize, int sSize) { - protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}"; - public sealed override string Extension => ".sav"; + General = new byte[gSize]; + Storage = new byte[sSize]; + ClearBoxes(); + } - // Blocks & Offsets - private readonly int GeneralBlockPosition; // Small Block - private readonly int StorageBlockPosition; // Big Block - private const int PartitionSize = 0x40000; + protected SAV4(byte[] data, int gSize, int sSize, int sStart) : base(data) + { + GeneralBlockPosition = GetActiveBlock(data, 0, gSize); + StorageBlockPosition = GetActiveBlock(data, sStart, sSize); - // SaveData is chunked into two pieces. - protected readonly byte[] Storage; - public readonly byte[] General; - protected sealed override byte[] BoxBuffer => Storage; - protected sealed override byte[] PartyBuffer => General; + var gbo = (GeneralBlockPosition == 0 ? 0 : PartitionSize); + var sbo = (StorageBlockPosition == 0 ? 0 : PartitionSize) + sStart; + General = GetData(gbo, gSize); + Storage = GetData(sbo, sSize); + } - protected abstract int StorageStart { get; } - public abstract Zukan4 Dex { get; } + // Configuration + protected sealed override SaveFile CloneInternal() + { + var sav = CloneInternal4(); + SetData(sav.General, General, 0); + SetData(sav.Storage, Storage, 0); + return sav; + } - protected abstract int EventFlag { get; } - protected abstract int EventWork { get; } - public sealed override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(General, offset, bitIndex); - public sealed override void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(General, offset, bitIndex, value); + protected abstract SAV4 CloneInternal4(); - protected SAV4(int gSize, int sSize) + public sealed override void CopyChangesFrom(SaveFile sav) + { + SetData(sav.Data, 0); + var s4 = (SAV4)sav; + SetData(General, s4.General, 0); + SetData(Storage, s4.Storage, 0); + } + + protected sealed override int SIZE_STORED => PokeCrypto.SIZE_4STORED; + protected sealed override int SIZE_PARTY => PokeCrypto.SIZE_4PARTY; + public sealed override PKM BlankPKM => new PK4(); + public sealed override Type PKMType => typeof(PK4); + + public sealed override int BoxCount => 18; + public sealed override int MaxEV => 255; + public sealed override int Generation => 4; + public override EntityContext Context => EntityContext.Gen4; + public int EventFlagCount => 0xB60; // 2912 + public int EventWorkCount => (EventFlag - EventWork) >> 1; + protected sealed override int GiftCountMax => 11; + public sealed override int OTLength => 7; + public sealed override int NickLength => 10; + public sealed override int MaxMoney => 999999; + public sealed override int MaxCoins => 50_000; + + public sealed override int MaxMoveID => Legal.MaxMoveID_4; + public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_4; + // MaxItemID + public sealed override int MaxAbilityID => Legal.MaxAbilityID_4; + public sealed override int MaxBallID => Legal.MaxBallID_4; + public sealed override int MaxGameID => Legal.MaxGameID_4; // Colo/XD + + // Checksums + protected abstract int FooterSize { get; } + private ushort CalcBlockChecksum(ReadOnlySpan data) => Checksums.CRC16_CCITT(data[..^FooterSize]); + private static ushort GetBlockChecksumSaved(ReadOnlySpan data) => ReadUInt16LittleEndian(data[^2..]); + private bool GetBlockChecksumValid(ReadOnlySpan data) => CalcBlockChecksum(data) == GetBlockChecksumSaved(data); + + protected sealed override void SetChecksums() + { + WriteUInt16LittleEndian(General.AsSpan(General.Length - 2), CalcBlockChecksum(General)); + WriteUInt16LittleEndian(Storage.AsSpan(Storage.Length - 2), CalcBlockChecksum(Storage)); + + // Write blocks back + General.CopyTo(Data, GeneralBlockPosition * PartitionSize); + Storage.CopyTo(Data, (StorageBlockPosition * PartitionSize) + StorageStart); + } + + public sealed override bool ChecksumsValid + { + get { - General = new byte[gSize]; - Storage = new byte[sSize]; - ClearBoxes(); + if (!GetBlockChecksumValid(General)) + return false; + if (!GetBlockChecksumValid(Storage)) + return false; + + return true; } + } - protected SAV4(byte[] data, int gSize, int sSize, int sStart) : base(data) + public sealed override string ChecksumInfo + { + get { - GeneralBlockPosition = GetActiveBlock(data, 0, gSize); - StorageBlockPosition = GetActiveBlock(data, sStart, sSize); + var list = new List(); + if (!GetBlockChecksumValid(General)) + list.Add("Small block checksum is invalid"); + if (!GetBlockChecksumValid(Storage)) + list.Add("Large block checksum is invalid"); - var gbo = (GeneralBlockPosition == 0 ? 0 : PartitionSize); - var sbo = (StorageBlockPosition == 0 ? 0 : PartitionSize) + sStart; - General = GetData(gbo, gSize); - Storage = GetData(sbo, sSize); + return list.Count != 0 ? string.Join(Environment.NewLine, list) : "Checksums are valid."; } + } - // Configuration - protected sealed override SaveFile CloneInternal() + private static int GetActiveBlock(ReadOnlySpan data, int begin, int length) + { + int offset = begin + length - 0x14; + return SAV4BlockDetection.CompareFooters(data, offset, offset + PartitionSize); + } + + protected int WondercardFlags = int.MinValue; + protected int AdventureInfo = int.MinValue; + protected int Seal = int.MinValue; + protected int Trainer1; + public int GTS { get; protected set; } = int.MinValue; + + // Storage + public override int PartyCount + { + get => General[Party - 4]; + protected set => General[Party - 4] = (byte)value; + } + + public sealed override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot); + + // Trainer Info + public override string OT + { + get => GetString(General.AsSpan(Trainer1, 16)); + set => SetString(General.AsSpan(Trainer1, 16), value.AsSpan(), OTLength, StringConverterOption.ClearZero); + } + + public override int TID + { + get => ReadUInt16LittleEndian(General.AsSpan(Trainer1 + 0x10)); + set => WriteUInt16LittleEndian(General.AsSpan(Trainer1 + 0x10), (ushort)value); + } + + public override int SID + { + get => ReadUInt16LittleEndian(General.AsSpan(Trainer1 + 0x12)); + set => WriteUInt16LittleEndian(General.AsSpan(Trainer1 + 0x12), (ushort)value); + } + + public override uint Money + { + get => ReadUInt32LittleEndian(General.AsSpan(Trainer1 + 0x14)); + set => WriteUInt32LittleEndian(General.AsSpan(Trainer1 + 0x14), value); + } + + public override int Gender + { + get => General[Trainer1 + 0x18]; + set => General[Trainer1 + 0x18] = (byte)value; + } + + public override int Language + { + get => General[Trainer1 + 0x19]; + set => General[Trainer1 + 0x19] = (byte)value; + } + + public int Badges + { + get => General[Trainer1 + 0x1A]; + set { if (value < 0) return; General[Trainer1 + 0x1A] = (byte)value; } + } + + public int Sprite + { + get => General[Trainer1 + 0x1B]; + set { if (value < 0) return; General[Trainer1 + 0x1B] = (byte)value; } + } + + public uint Coin + { + get => ReadUInt16LittleEndian(General.AsSpan(Trainer1 + 0x20)); + set => WriteUInt16LittleEndian(General.AsSpan(Trainer1 + 0x20), (ushort)value); + } + + public override int PlayedHours + { + get => ReadUInt16LittleEndian(General.AsSpan(Trainer1 + 0x22)); + set => WriteUInt16LittleEndian(General.AsSpan(Trainer1 + 0x22), (ushort)value); + } + + public override int PlayedMinutes + { + get => General[Trainer1 + 0x24]; + set => General[Trainer1 + 0x24] = (byte)value; + } + + public override int PlayedSeconds + { + get => General[Trainer1 + 0x25]; + set => General[Trainer1 + 0x25] = (byte)value; + } + + public abstract int M { get; set; } + public abstract int X { get; set; } + public abstract int Y { get; set; } + + public string Rival + { + get => GetString(Rival_Trash); + set => SetString(Rival_Trash, value.AsSpan(), OTLength, StringConverterOption.ClearZero); + } + + public abstract Span Rival_Trash { get; set; } + + public abstract int X2 { get; set; } + public abstract int Y2 { get; set; } + public abstract int Z { get; set; } + + public override uint SecondsToStart { get => ReadUInt32LittleEndian(General.AsSpan(AdventureInfo + 0x34)); set => WriteUInt32LittleEndian(General.AsSpan(AdventureInfo + 0x34), value); } + public override uint SecondsToFame { get => ReadUInt32LittleEndian(General.AsSpan(AdventureInfo + 0x3C)); set => WriteUInt32LittleEndian(General.AsSpan(AdventureInfo + 0x3C), value); } + + protected sealed override PKM GetPKM(byte[] data) => new PK4(data); + protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data); + + protected sealed override void SetPKM(PKM pk, bool isParty = false) + { + var pk4 = (PK4)pk; + // Apply to this Save File + DateTime Date = DateTime.Now; + if (pk4.Trade(OT, TID, SID, Gender, Date.Day, Date.Month, Date.Year)) + pk.RefreshChecksum(); + } + + // Daycare + public override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + (slot * SIZE_PARTY); + + public override uint? GetDaycareEXP(int loc, int slot) + { + int ofs = DaycareOffset + ((slot+1)*SIZE_PARTY) - 4; + return ReadUInt32LittleEndian(General.AsSpan(ofs)); + } + + public override bool? IsDaycareOccupied(int loc, int slot) => null; // todo + + public override void SetDaycareEXP(int loc, int slot, uint EXP) + { + int ofs = DaycareOffset + ((slot+1)*SIZE_PARTY) - 4; + WriteUInt32LittleEndian(General.AsSpan(ofs), EXP); + } + + public override void SetDaycareOccupied(int loc, int slot, bool occupied) + { + // todo + } + + // Mystery Gift + private bool MysteryGiftActive { get => (General[72] & 1) == 1; set => General[72] = (byte)((General[72] & 0xFE) | (value ? 1 : 0)); } + + private static bool IsMysteryGiftAvailable(DataMysteryGift[] value) + { + for (int i = 0; i < 8; i++) // 8 PGT { - var sav = CloneInternal4(); - SetData(sav.General, General, 0); - SetData(sav.Storage, Storage, 0); - return sav; - } - - protected abstract SAV4 CloneInternal4(); - - public sealed override void CopyChangesFrom(SaveFile sav) - { - SetData(sav.Data, 0); - var s4 = (SAV4)sav; - SetData(General, s4.General, 0); - SetData(Storage, s4.Storage, 0); - } - - protected sealed override int SIZE_STORED => PokeCrypto.SIZE_4STORED; - protected sealed override int SIZE_PARTY => PokeCrypto.SIZE_4PARTY; - public sealed override PKM BlankPKM => new PK4(); - public sealed override Type PKMType => typeof(PK4); - - public sealed override int BoxCount => 18; - public sealed override int MaxEV => 255; - public sealed override int Generation => 4; - public override EntityContext Context => EntityContext.Gen4; - public int EventFlagCount => 0xB60; // 2912 - public int EventWorkCount => (EventFlag - EventWork) >> 1; - protected sealed override int GiftCountMax => 11; - public sealed override int OTLength => 7; - public sealed override int NickLength => 10; - public sealed override int MaxMoney => 999999; - public sealed override int MaxCoins => 50_000; - - public sealed override int MaxMoveID => Legal.MaxMoveID_4; - public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_4; - // MaxItemID - public sealed override int MaxAbilityID => Legal.MaxAbilityID_4; - public sealed override int MaxBallID => Legal.MaxBallID_4; - public sealed override int MaxGameID => Legal.MaxGameID_4; // Colo/XD - - // Checksums - protected abstract int FooterSize { get; } - private ushort CalcBlockChecksum(ReadOnlySpan data) => Checksums.CRC16_CCITT(data[..^FooterSize]); - private static ushort GetBlockChecksumSaved(ReadOnlySpan data) => ReadUInt16LittleEndian(data[^2..]); - private bool GetBlockChecksumValid(ReadOnlySpan data) => CalcBlockChecksum(data) == GetBlockChecksumSaved(data); - - protected sealed override void SetChecksums() - { - WriteUInt16LittleEndian(General.AsSpan(General.Length - 2), CalcBlockChecksum(General)); - WriteUInt16LittleEndian(Storage.AsSpan(Storage.Length - 2), CalcBlockChecksum(Storage)); - - // Write blocks back - General.CopyTo(Data, GeneralBlockPosition * PartitionSize); - Storage.CopyTo(Data, (StorageBlockPosition * PartitionSize) + StorageStart); - } - - public sealed override bool ChecksumsValid - { - get - { - if (!GetBlockChecksumValid(General)) - return false; - if (!GetBlockChecksumValid(Storage)) - return false; - + if (value[i] is PGT {CardType: not 0}) return true; - } } - - public sealed override string ChecksumInfo + for (int i = 8; i < 11; i++) // 3 PCD { - get + if (value[i] is PCD {Gift.CardType: not 0 }) + return true; + } + return false; + } + + private byte[] MatchMysteryGifts(DataMysteryGift[] value) + { + byte[] cardMatch = new byte[8]; + for (int i = 0; i < 8; i++) + { + if (value[i] is not PGT pgt) + continue; + + if (pgt.CardType == 0) // empty { - var list = new List(); - if (!GetBlockChecksumValid(General)) - list.Add("Small block checksum is invalid"); - if (!GetBlockChecksumValid(Storage)) - list.Add("Large block checksum is invalid"); - - return list.Count != 0 ? string.Join(Environment.NewLine, list) : "Checksums are valid."; + cardMatch[i] = pgt.Slot = 0; + continue; } - } - private static int GetActiveBlock(ReadOnlySpan data, int begin, int length) - { - int offset = begin + length - 0x14; - return SAV4BlockDetection.CompareFooters(data, offset, offset + PartitionSize); - } - - protected int WondercardFlags = int.MinValue; - protected int AdventureInfo = int.MinValue; - protected int Seal = int.MinValue; - protected int Trainer1; - public int GTS { get; protected set; } = int.MinValue; - - // Storage - public override int PartyCount - { - get => General[Party - 4]; - protected set => General[Party - 4] = (byte)value; - } - - public sealed override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot); - - // Trainer Info - public override string OT - { - get => GetString(General.AsSpan(Trainer1, 16)); - set => SetString(General.AsSpan(Trainer1, 16), value.AsSpan(), OTLength, StringConverterOption.ClearZero); - } - - public override int TID - { - get => ReadUInt16LittleEndian(General.AsSpan(Trainer1 + 0x10)); - set => WriteUInt16LittleEndian(General.AsSpan(Trainer1 + 0x10), (ushort)value); - } - - public override int SID - { - get => ReadUInt16LittleEndian(General.AsSpan(Trainer1 + 0x12)); - set => WriteUInt16LittleEndian(General.AsSpan(Trainer1 + 0x12), (ushort)value); - } - - public override uint Money - { - get => ReadUInt32LittleEndian(General.AsSpan(Trainer1 + 0x14)); - set => WriteUInt32LittleEndian(General.AsSpan(Trainer1 + 0x14), value); - } - - public override int Gender - { - get => General[Trainer1 + 0x18]; - set => General[Trainer1 + 0x18] = (byte)value; - } - - public override int Language - { - get => General[Trainer1 + 0x19]; - set => General[Trainer1 + 0x19] = (byte)value; - } - - public int Badges - { - get => General[Trainer1 + 0x1A]; - set { if (value < 0) return; General[Trainer1 + 0x1A] = (byte)value; } - } - - public int Sprite - { - get => General[Trainer1 + 0x1B]; - set { if (value < 0) return; General[Trainer1 + 0x1B] = (byte)value; } - } - - public uint Coin - { - get => ReadUInt16LittleEndian(General.AsSpan(Trainer1 + 0x20)); - set => WriteUInt16LittleEndian(General.AsSpan(Trainer1 + 0x20), (ushort)value); - } - - public override int PlayedHours - { - get => ReadUInt16LittleEndian(General.AsSpan(Trainer1 + 0x22)); - set => WriteUInt16LittleEndian(General.AsSpan(Trainer1 + 0x22), (ushort)value); - } - - public override int PlayedMinutes - { - get => General[Trainer1 + 0x24]; - set => General[Trainer1 + 0x24] = (byte)value; - } - - public override int PlayedSeconds - { - get => General[Trainer1 + 0x25]; - set => General[Trainer1 + 0x25] = (byte)value; - } - - public abstract int M { get; set; } - public abstract int X { get; set; } - public abstract int Y { get; set; } - - public string Rival - { - get => GetString(Rival_Trash); - set => SetString(Rival_Trash, value.AsSpan(), OTLength, StringConverterOption.ClearZero); - } - - public abstract Span Rival_Trash { get; set; } - - public abstract int X2 { get; set; } - public abstract int Y2 { get; set; } - public abstract int Z { get; set; } - - public override uint SecondsToStart { get => ReadUInt32LittleEndian(General.AsSpan(AdventureInfo + 0x34)); set => WriteUInt32LittleEndian(General.AsSpan(AdventureInfo + 0x34), value); } - public override uint SecondsToFame { get => ReadUInt32LittleEndian(General.AsSpan(AdventureInfo + 0x3C)); set => WriteUInt32LittleEndian(General.AsSpan(AdventureInfo + 0x3C), value); } - - protected sealed override PKM GetPKM(byte[] data) => new PK4(data); - protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data); - - protected sealed override void SetPKM(PKM pkm, bool isParty = false) - { - var pk4 = (PK4)pkm; - // Apply to this Save File - DateTime Date = DateTime.Now; - if (pk4.Trade(OT, TID, SID, Gender, Date.Day, Date.Month, Date.Year)) - pkm.RefreshChecksum(); - } - - // Daycare - public override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + (slot * SIZE_PARTY); - - public override uint? GetDaycareEXP(int loc, int slot) - { - int ofs = DaycareOffset + ((slot+1)*SIZE_PARTY) - 4; - return ReadUInt32LittleEndian(General.AsSpan(ofs)); - } - - public override bool? IsDaycareOccupied(int loc, int slot) => null; // todo - - public override void SetDaycareEXP(int loc, int slot, uint EXP) - { - int ofs = DaycareOffset + ((slot+1)*SIZE_PARTY) - 4; - WriteUInt32LittleEndian(General.AsSpan(ofs), EXP); - } - - public override void SetDaycareOccupied(int loc, int slot, bool occupied) - { - // todo - } - - // Mystery Gift - private bool MysteryGiftActive { get => (General[72] & 1) == 1; set => General[72] = (byte)((General[72] & 0xFE) | (value ? 1 : 0)); } - - private static bool IsMysteryGiftAvailable(DataMysteryGift[] value) - { - for (int i = 0; i < 8; i++) // 8 PGT + cardMatch[i] = pgt.Slot = 3; + for (byte j = 0; j < 3; j++) { - if (value[i] is PGT {CardType: not 0}) - return true; - } - for (int i = 8; i < 11; i++) // 3 PCD - { - if (value[i] is PCD {Gift.CardType: not 0 }) - return true; - } - return false; - } - - private byte[] MatchMysteryGifts(DataMysteryGift[] value) - { - byte[] cardMatch = new byte[8]; - for (int i = 0; i < 8; i++) - { - if (value[i] is not PGT pgt) + if (value[8 + j] is not PCD pcd) continue; - if (pgt.CardType == 0) // empty - { - cardMatch[i] = pgt.Slot = 0; + // Check if data matches (except Slot @ 0x02) + if (!pcd.GiftEquals(pgt)) continue; - } - cardMatch[i] = pgt.Slot = 3; - for (byte j = 0; j < 3; j++) + if (this is SAV4HGSS) + j++; // hgss 0,1,2; dppt 1,2,3 + cardMatch[i] = pgt.Slot = j; + break; + } + } + return cardMatch; + } + + public override MysteryGiftAlbum GiftAlbum + { + get => new(MysteryGiftCards, MysteryGiftReceivedFlags) {Flags = {[2047] = false}}; + set + { + bool available = IsMysteryGiftAvailable(value.Gifts); + if (available && !MysteryGiftActive) + MysteryGiftActive = true; + value.Flags[2047] = available; + + // Check encryption for each gift (decrypted wc4 sneaking in) + foreach (var g in value.Gifts) + { + if (g is PGT pgt) { - if (value[8 + j] is not PCD pcd) - continue; - - // Check if data matches (except Slot @ 0x02) - if (!pcd.GiftEquals(pgt)) - continue; - - if (this is SAV4HGSS) - j++; // hgss 0,1,2; dppt 1,2,3 - cardMatch[i] = pgt.Slot = j; - break; + pgt.VerifyPKEncryption(); } - } - return cardMatch; - } - - public override MysteryGiftAlbum GiftAlbum - { - get => new(MysteryGiftCards, MysteryGiftReceivedFlags) {Flags = {[2047] = false}}; - set - { - bool available = IsMysteryGiftAvailable(value.Gifts); - if (available && !MysteryGiftActive) - MysteryGiftActive = true; - value.Flags[2047] = available; - - // Check encryption for each gift (decrypted wc4 sneaking in) - foreach (var g in value.Gifts) + else if (g is PCD pcd) { - if (g is PGT pgt) - { - pgt.VerifyPKEncryption(); - } - else if (g is PCD pcd) - { - var dg = pcd.Gift; - if (dg.VerifyPKEncryption()) - pcd.Gift = dg; // set encrypted gift back to PCD. - } - } - - MysteryGiftReceivedFlags = value.Flags; - MysteryGiftCards = value.Gifts; - } - } - - protected sealed override bool[] MysteryGiftReceivedFlags - { - get - { - bool[] result = new bool[GiftFlagMax]; - for (int i = 0; i < result.Length; i++) - result[i] = (General[WondercardFlags + (i >> 3)] >> (i & 7) & 0x1) == 1; - return result; - } - set - { - if (GiftFlagMax != value.Length) - return; - - Span data = General.AsSpan(WondercardFlags, value.Length / 8); - data.Clear(); - for (int i = 0; i < value.Length; i++) - { - if (value[i]) - data[i >> 3] |= (byte)(1 << (i & 7)); + var dg = pcd.Gift; + if (dg.VerifyPKEncryption()) + pcd.Gift = dg; // set encrypted gift back to PCD. } } - } - protected sealed override DataMysteryGift[] MysteryGiftCards + MysteryGiftReceivedFlags = value.Flags; + MysteryGiftCards = value.Gifts; + } + } + + protected sealed override bool[] MysteryGiftReceivedFlags + { + get { - get + bool[] result = new bool[GiftFlagMax]; + for (int i = 0; i < result.Length; i++) + result[i] = ((General[WondercardFlags + (i >> 3)] >> (i & 7)) & 0x1) == 1; + return result; + } + set + { + if (GiftFlagMax != value.Length) + return; + + Span data = General.AsSpan(WondercardFlags, value.Length / 8); + data.Clear(); + for (int i = 0; i < value.Length; i++) { - int pcd = this is SAV4HGSS ? 4 : 3; - DataMysteryGift[] cards = new DataMysteryGift[8 + pcd]; - for (int i = 0; i < 8; i++) // 8 PGT - cards[i] = new PGT(General.Slice(WondercardData + (i * PGT.Size), PGT.Size)); - for (int i = 8; i < 11; i++) // 3 PCD - cards[i] = new PCD(General.Slice(WondercardData + (8 * PGT.Size) + ((i-8) * PCD.Size), PCD.Size)); - if (this is SAV4HGSS hgss) - cards[^1] = hgss.LockCapsuleSlot; - return cards; - } - set - { - var Matches = MatchMysteryGifts(value); // automatically applied - if (Matches.Length == 0) - return; - - for (int i = 0; i < 8; i++) // 8 PGT - { - if (value[i] is PGT) - SetData(General, value[i].Data, WondercardData + (i *PGT.Size)); - } - for (int i = 8; i < 11; i++) // 3 PCD - { - if (value[i] is PCD) - SetData(General, value[i].Data, WondercardData + (8 *PGT.Size) + ((i - 8)*PCD.Size)); - } - if (this is SAV4HGSS hgss && value.Length >= 11 && value[^1] is PCD capsule) - hgss.LockCapsuleSlot = capsule; - } - } - - protected sealed override void SetDex(PKM pkm) => Dex.SetDex(pkm); - public sealed override bool GetCaught(int species) => Dex.GetCaught(species); - public sealed override bool GetSeen(int species) => Dex.GetSeen(species); - - public int DexUpgraded - { - get - { - switch (Version) - { - case GameVersion.DP: - if (General[0x1413] != 0) return 4; - else if (General[0x1415] != 0) return 3; - else if (General[0x1404] != 0) return 2; - else if (General[0x1414] != 0) return 1; - else return 0; - case GameVersion.HGSS: - if (General[0x15ED] != 0) return 3; - else if (General[0x15EF] != 0) return 2; - else if (General[0x15EE] != 0 && (General[0x10D1] & 8) != 0) return 1; - else return 0; - case GameVersion.Pt: - if (General[0x1641] != 0) return 4; - else if (General[0x1643] != 0) return 3; - else if (General[0x1640] != 0) return 2; - else if (General[0x1642] != 0) return 1; - else return 0; - default: return 0; - } - } - set - { - switch (Version) - { - case GameVersion.DP: - General[0x1413] = value == 4 ? (byte)1 : (byte)0; - General[0x1415] = value >= 3 ? (byte)1 : (byte)0; - General[0x1404] = value >= 2 ? (byte)1 : (byte)0; - General[0x1414] = value >= 1 ? (byte)1 : (byte)0; - break; - case GameVersion.HGSS: - General[0x15ED] = value == 3 ? (byte)1 : (byte)0; - General[0x15EF] = value >= 2 ? (byte)1 : (byte)0; - General[0x15EE] = value >= 1 ? (byte)1 : (byte)0; - General[0x10D1] = (byte)((General[0x10D1] & ~8) | (value >= 1 ? 8 : 0)); - break; - case GameVersion.Pt: - General[0x1641] = value == 4 ? (byte)1 : (byte)0; - General[0x1643] = value >= 3 ? (byte)1 : (byte)0; - General[0x1640] = value >= 2 ? (byte)1 : (byte)0; - General[0x1642] = value >= 1 ? (byte)1 : (byte)0; - break; - default: return; - } - } - } - - public sealed override string GetString(ReadOnlySpan data) => StringConverter4.GetString(data); - - public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter4.SetString(destBuffer, value, maxLength, option); - } - - #region Event Flag/Event Work - public bool GetEventFlag(int flagNumber) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); - return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); - } - - public void SetEventFlag(int flagNumber, bool value) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); - SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); - } - - public ushort GetWork(int index) => ReadUInt16LittleEndian(General.AsSpan(EventWork + (index * 2))); - public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(General.AsSpan(EventWork)[(index * 2)..], value); - #endregion - - // Seals - private const byte SealMaxCount = 99; - - public byte[] GetSealCase() => General.Slice(Seal, (int)Seal4.MAX); - public void SetSealCase(byte[] value) => SetData(General, value, Seal); - - public byte GetSealCount(Seal4 id) => General[Seal + (int)id]; - public byte SetSealCount(Seal4 id, byte count) => General[Seal + (int)id] = Math.Min(SealMaxCount, count); - - public void SetAllSeals(byte count, bool unreleased = false) - { - var sealIndexCount = (int)(unreleased ? Seal4.MAX : Seal4.MAXLEGAL); - var clamped = Math.Min(count, SealMaxCount); - for (int i = 0; i < sealIndexCount; i++) - General[Seal + i] = clamped; - } - - public int GetMailOffset(int index) - { - int ofs = (index * Mail4.SIZE); - return Version switch - { - GameVersion.DP => (ofs + 0x4BEC), - GameVersion.Pt => (ofs + 0x4E80), - _ => (ofs + 0x3FA8), - }; - } - - public byte[] GetMailData(int ofs) => General.Slice(ofs, Mail4.SIZE); - - public Mail4 GetMail(int mailIndex) - { - int ofs = GetMailOffset(mailIndex); - return new Mail4(GetMailData(ofs), ofs); - } - - public abstract uint SwarmSeed { get; set; } - public abstract uint SwarmMaxCountModulo { get; } - - public uint SwarmIndex - { - get => SwarmSeed % SwarmMaxCountModulo; - set - { - value %= SwarmMaxCountModulo; - while (SwarmIndex != value) - ++SwarmSeed; + if (value[i]) + data[i >> 3] |= (byte)(1 << (i & 7)); } } } + + protected sealed override DataMysteryGift[] MysteryGiftCards + { + get + { + int pcd = this is SAV4HGSS ? 4 : 3; + DataMysteryGift[] cards = new DataMysteryGift[8 + pcd]; + for (int i = 0; i < 8; i++) // 8 PGT + cards[i] = new PGT(General.Slice(WondercardData + (i * PGT.Size), PGT.Size)); + for (int i = 8; i < 11; i++) // 3 PCD + cards[i] = new PCD(General.Slice(WondercardData + (8 * PGT.Size) + ((i-8) * PCD.Size), PCD.Size)); + if (this is SAV4HGSS hgss) + cards[^1] = hgss.LockCapsuleSlot; + return cards; + } + set + { + var Matches = MatchMysteryGifts(value); // automatically applied + if (Matches.Length == 0) + return; + + for (int i = 0; i < 8; i++) // 8 PGT + { + if (value[i] is PGT) + SetData(General, value[i].Data, WondercardData + (i *PGT.Size)); + } + for (int i = 8; i < 11; i++) // 3 PCD + { + if (value[i] is PCD) + SetData(General, value[i].Data, WondercardData + (8 *PGT.Size) + ((i - 8)*PCD.Size)); + } + if (this is SAV4HGSS hgss && value.Length >= 11 && value[^1] is PCD capsule) + hgss.LockCapsuleSlot = capsule; + } + } + + protected sealed override void SetDex(PKM pk) => Dex.SetDex(pk); + public sealed override bool GetCaught(int species) => Dex.GetCaught(species); + public sealed override bool GetSeen(int species) => Dex.GetSeen(species); + + public int DexUpgraded + { + get + { + switch (Version) + { + case GameVersion.DP: + if (General[0x1413] != 0) return 4; + if (General[0x1415] != 0) return 3; + if (General[0x1404] != 0) return 2; + if (General[0x1414] != 0) return 1; + return 0; + case GameVersion.HGSS: + if (General[0x15ED] != 0) return 3; + if (General[0x15EF] != 0) return 2; + if (General[0x15EE] != 0 && (General[0x10D1] & 8) != 0) return 1; + return 0; + case GameVersion.Pt: + if (General[0x1641] != 0) return 4; + if (General[0x1643] != 0) return 3; + if (General[0x1640] != 0) return 2; + if (General[0x1642] != 0) return 1; + return 0; + default: return 0; + } + } + set + { + switch (Version) + { + case GameVersion.DP: + General[0x1413] = value == 4 ? (byte)1 : (byte)0; + General[0x1415] = value >= 3 ? (byte)1 : (byte)0; + General[0x1404] = value >= 2 ? (byte)1 : (byte)0; + General[0x1414] = value >= 1 ? (byte)1 : (byte)0; + break; + case GameVersion.HGSS: + General[0x15ED] = value == 3 ? (byte)1 : (byte)0; + General[0x15EF] = value >= 2 ? (byte)1 : (byte)0; + General[0x15EE] = value >= 1 ? (byte)1 : (byte)0; + General[0x10D1] = (byte)((General[0x10D1] & ~8) | (value >= 1 ? 8 : 0)); + break; + case GameVersion.Pt: + General[0x1641] = value == 4 ? (byte)1 : (byte)0; + General[0x1643] = value >= 3 ? (byte)1 : (byte)0; + General[0x1640] = value >= 2 ? (byte)1 : (byte)0; + General[0x1642] = value >= 1 ? (byte)1 : (byte)0; + break; + default: return; + } + } + } + + public sealed override string GetString(ReadOnlySpan data) => StringConverter4.GetString(data); + + public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter4.SetString(destBuffer, value, maxLength, option); + } + + #region Event Flag/Event Work + public bool GetEventFlag(int flagNumber) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); + return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); + } + + public void SetEventFlag(int flagNumber, bool value) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); + SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); + } + + public ushort GetWork(int index) => ReadUInt16LittleEndian(General.AsSpan(EventWork + (index * 2))); + public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(General.AsSpan(EventWork)[(index * 2)..], value); + #endregion + + // Seals + private const byte SealMaxCount = 99; + + public byte[] GetSealCase() => General.Slice(Seal, (int)Seal4.MAX); + public void SetSealCase(byte[] value) => SetData(General, value, Seal); + + public byte GetSealCount(Seal4 id) => General[Seal + (int)id]; + public byte SetSealCount(Seal4 id, byte count) => General[Seal + (int)id] = Math.Min(SealMaxCount, count); + + public void SetAllSeals(byte count, bool unreleased = false) + { + var sealIndexCount = (int)(unreleased ? Seal4.MAX : Seal4.MAXLEGAL); + var clamped = Math.Min(count, SealMaxCount); + for (int i = 0; i < sealIndexCount; i++) + General[Seal + i] = clamped; + } + + public int GetMailOffset(int index) + { + int ofs = (index * Mail4.SIZE); + return Version switch + { + GameVersion.DP => (ofs + 0x4BEC), + GameVersion.Pt => (ofs + 0x4E80), + _ => (ofs + 0x3FA8), + }; + } + + public byte[] GetMailData(int ofs) => General.Slice(ofs, Mail4.SIZE); + + public Mail4 GetMail(int mailIndex) + { + int ofs = GetMailOffset(mailIndex); + return new Mail4(GetMailData(ofs), ofs); + } + + public abstract uint SwarmSeed { get; set; } + public abstract uint SwarmMaxCountModulo { get; } + + public uint SwarmIndex + { + get => SwarmSeed % SwarmMaxCountModulo; + set + { + value %= SwarmMaxCountModulo; + while (SwarmIndex != value) + ++SwarmSeed; + } + } } diff --git a/PKHeX.Core/Saves/SAV4BR.cs b/PKHeX.Core/Saves/SAV4BR.cs index d127150ac..977c8fe3f 100644 --- a/PKHeX.Core/Saves/SAV4BR.cs +++ b/PKHeX.Core/Saves/SAV4BR.cs @@ -2,360 +2,359 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 object for Pokémon Battle Revolution saves. +/// +public sealed class SAV4BR : SaveFile { - /// - /// Generation 4 object for Pokémon Battle Revolution saves. - /// - public sealed class SAV4BR : SaveFile + protected internal override string ShortSummary => $"{Version} #{SaveCount:0000}"; + public override string Extension => string.Empty; + public override PersonalTable Personal => PersonalTable.DP; + public override IReadOnlyList HeldItems => Legal.HeldItems_DP; + + private const int SAVE_COUNT = 4; + public const int SIZE_HALF = 0x1C0000; + + public SAV4BR() : base(SaveUtil.SIZE_G4BR) { - protected internal override string ShortSummary => $"{Version} #{SaveCount:0000}"; - public override string Extension => string.Empty; - public override PersonalTable Personal => PersonalTable.DP; - public override IReadOnlyList HeldItems => Legal.HeldItems_DP; - - private const int SAVE_COUNT = 4; - public const int SIZE_HALF = 0x1C0000; - - public SAV4BR() : base(SaveUtil.SIZE_G4BR) - { - ClearBoxes(); - } - - public SAV4BR(byte[] data) : base(data) - { - InitializeData(data); - } - - private void InitializeData(ReadOnlySpan data) - { - Data = DecryptPBRSaveData(data); - - // Detect active save - var first = ReadUInt32BigEndian(Data.AsSpan(0x00004C)); - var second = ReadUInt32BigEndian(Data.AsSpan(0x1C004C)); - SaveCount = Math.Max(second, first); - if (second > first) - { - // swap halves - byte[] tempData = new byte[SIZE_HALF]; - Array.Copy(Data, 0, tempData, 0, SIZE_HALF); - Array.Copy(Data, SIZE_HALF, Data, 0, SIZE_HALF); - tempData.CopyTo(Data, SIZE_HALF); - } - - var names = (string[]) SaveNames; - for (int i = 0; i < SAVE_COUNT; i++) - { - var name = GetOTName(i); - if (string.IsNullOrWhiteSpace(name)) - name = $"Empty {i + 1}"; - else if (_currentSlot == -1) - _currentSlot = i; - names[i] = name; - } - - if (_currentSlot == -1) - _currentSlot = 0; - - CurrentSlot = _currentSlot; - } - - /// Amount of times the primary save has been saved - private uint SaveCount; - - protected override byte[] GetFinalData() - { - SetChecksums(); - return EncryptPBRSaveData(Data); - } - - // Configuration - protected override SaveFile CloneInternal() => new SAV4BR(Write()); - - public readonly IReadOnlyList SaveNames = new string[SAVE_COUNT]; - - private int _currentSlot = -1; - private const int SIZE_SLOT = 0x6FF00; - - public int CurrentSlot - { - get => _currentSlot; - // 4 save slots, data reading depends on current slot - set - { - _currentSlot = value; - var ofs = SIZE_SLOT * _currentSlot; - Box = ofs + 0x978; - Party = ofs + 0x13A54; // first team slot after boxes - BoxName = ofs + 0x58674; - } - } - - protected override int SIZE_STORED => PokeCrypto.SIZE_4STORED; - protected override int SIZE_PARTY => PokeCrypto.SIZE_4STORED + 4; - public override PKM BlankPKM => new BK4(); - public override Type PKMType => typeof(BK4); - - public override int MaxMoveID => 467; - public override int MaxSpeciesID => Legal.MaxSpeciesID_4; - public override int MaxAbilityID => Legal.MaxAbilityID_4; - public override int MaxItemID => Legal.MaxItemID_4_HGSS; - public override int MaxBallID => Legal.MaxBallID_4; - public override int MaxGameID => Legal.MaxGameID_4; - - public override int MaxEV => 255; - public override int Generation => 4; - public override EntityContext Context => EntityContext.Gen4; - protected override int GiftCountMax => 1; - public override int OTLength => 7; - public override int NickLength => 10; - public override int MaxMoney => 999999; - public override int Language => (int)LanguageID.English; // prevent KOR from inhabiting - - public override int BoxCount => 18; - - public override int PartyCount - { - get - { - int ctr = 0; - for (int i = 0; i < 6; i++) - { - if (Data[GetPartyOffset(i) + 4] != 0) // sanity - ctr++; - } - return ctr; - } - protected set - { - // Ignore, value is calculated - } - } - - // Checksums - protected override void SetChecksums() - { - SetChecksum(Data, 0x0000000, 0x0000100, 0x000008); - SetChecksum(Data, 0x0000000, SIZE_HALF, SIZE_HALF - 0x80); - SetChecksum(Data, SIZE_HALF, 0x0000100, SIZE_HALF + 0x000008); - SetChecksum(Data, SIZE_HALF, SIZE_HALF, SIZE_HALF + SIZE_HALF - 0x80); - } - - public override bool ChecksumsValid => IsChecksumsValid(Data); - public override string ChecksumInfo => $"Checksums valid: {ChecksumsValid}."; - - public static bool IsChecksumsValid(Span sav) - { - return VerifyChecksum(sav, 0x0000000, 0x0000100, 0x000008) - && VerifyChecksum(sav, 0x0000000, SIZE_HALF, SIZE_HALF - 0x80) - && VerifyChecksum(sav, SIZE_HALF, 0x0000100, SIZE_HALF + 0x000008) - && VerifyChecksum(sav, SIZE_HALF, SIZE_HALF, SIZE_HALF + SIZE_HALF - 0x80); - } - - // Trainer Info - public override GameVersion Version { get => GameVersion.BATREV; protected set { } } - - private string GetOTName(int slot) - { - var ofs = 0x390 + (0x6FF00 * slot); - var span = Data.AsSpan(ofs, 16); - return GetString(span); - } - - private void SetOTName(int slot, string name) - { - var ofs = 0x390 + (0x6FF00 * slot); - var span = Data.AsSpan(ofs, 16); - SetString(span, name.AsSpan(), 7, StringConverterOption.ClearZero); - } - - public string CurrentOT { get => GetOTName(_currentSlot); set => SetOTName(_currentSlot, value); } - - // Storage - public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot); - public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30); - - public override int TID - { - get => (Data[(_currentSlot * SIZE_SLOT) + 0x12867] << 8) | Data[(_currentSlot * SIZE_SLOT) + 0x12860]; - set - { - Data[(_currentSlot * SIZE_SLOT) + 0x12867] = (byte)(value >> 8); - Data[(_currentSlot * SIZE_SLOT) + 0x12860] = (byte)(value & 0xFF); - } - } - - public override int SID - { - get => (Data[(_currentSlot * SIZE_SLOT) + 0x12865] << 8) | Data[(_currentSlot * SIZE_SLOT) + 0x12866]; - set - { - Data[(_currentSlot * SIZE_SLOT) + 0x12865] = (byte)(value >> 8); - Data[(_currentSlot * SIZE_SLOT) + 0x12866] = (byte)(value & 0xFF); - } - } - - // Save file does not have Box Name / Wallpaper info - private int BoxName = -1; - private const int BoxNameLength = 0x28; - - public override string GetBoxName(int box) - { - if (BoxName < 0) - return $"BOX {box + 1}"; - - int ofs = BoxName + (box * BoxNameLength); - var span = Data.AsSpan(ofs, BoxNameLength); - if (ReadUInt16BigEndian(span) == 0) - return $"BOX {box + 1}"; - return GetString(ofs, BoxNameLength); - } - - public override void SetBoxName(int box, string value) - { - if (BoxName < 0) - return; - - int ofs = BoxName + (box * BoxNameLength); - var span = Data.AsSpan(ofs, BoxNameLength); - if (ReadUInt16BigEndian(span) == 0) - return; - - SetString(span, value.AsSpan(), BoxNameLength / 2, StringConverterOption.ClearZero); - } - - protected override PKM GetPKM(byte[] data) - { - if (data.Length != SIZE_STORED) - Array.Resize(ref data, SIZE_STORED); - return BK4.ReadUnshuffle(data); - } - - protected override byte[] DecryptPKM(byte[] data) => data; - - protected override void SetDex(PKM pkm) { /* There's no PokéDex */ } - - protected override void SetPKM(PKM pkm, bool isParty = false) - { - var pk4 = (BK4)pkm; - // Apply to this Save File - DateTime Date = DateTime.Now; - if (pk4.Trade(OT, TID, SID, Gender, Date.Day, Date.Month, Date.Year)) - pkm.RefreshChecksum(); - } - - protected override void SetPartyValues(PKM pkm, bool isParty) - { - if (pkm is G4PKM g4) - g4.Sanity = isParty ? (ushort)0xC000 : (ushort)0x4000; - } - - public static byte[] DecryptPBRSaveData(ReadOnlySpan input) - { - byte[] output = new byte[input.Length]; - Span keys = stackalloc ushort[4]; - for (int offset = 0; offset < SaveUtil.SIZE_G4BR; offset += SIZE_HALF) - { - var inSlice = input.Slice(offset, SIZE_HALF); - var outSlice = output.AsSpan(offset, SIZE_HALF); - - // First 8 bytes are the encryption keys for this chunk. - var keySlice = inSlice[..(keys.Length * 2)]; - GeniusCrypto.ReadKeys(keySlice, keys); - - // Copy over the keys to the result. - keySlice.CopyTo(outSlice); - - // Decrypt the input, result stored in output. - Range r = new(8, SIZE_HALF); - GeniusCrypto.Decrypt(inSlice[r], outSlice[r], keys); - } - return output; - } - - private static byte[] EncryptPBRSaveData(ReadOnlySpan input) - { - byte[] output = new byte[input.Length]; - Span keys = stackalloc ushort[4]; - for (int offset = 0; offset < SaveUtil.SIZE_G4BR; offset += SIZE_HALF) - { - var inSlice = input.Slice(offset, SIZE_HALF); - var outSlice = output.AsSpan(offset, SIZE_HALF); - - // First 8 bytes are the encryption keys for this chunk. - var keySlice = inSlice[..(keys.Length * 2)]; - GeniusCrypto.ReadKeys(keySlice, keys); - - // Copy over the keys to the result. - keySlice.CopyTo(outSlice); - - // Decrypt the input, result stored in output. - Range r = new(8, SIZE_HALF); - GeniusCrypto.Encrypt(inSlice[r], outSlice[r], keys); - } - return output; - } - - public static bool VerifyChecksum(Span input, int offset, int len, int checksum_offset) - { - // Read original checksum data, and clear it for recomputing - Span originalChecksums = stackalloc uint[16]; - var checkSpan = input.Slice(checksum_offset, 4 * originalChecksums.Length); - for (int i = 0; i < originalChecksums.Length; i++) - { - var chk = checkSpan.Slice(i * 4, 4); - originalChecksums[i] = ReadUInt32BigEndian(chk); - } - checkSpan.Clear(); - - // Compute current checksum of the specified span - Span checksums = stackalloc uint[16]; - var span = input.Slice(offset, len); - ComputeChecksums(span, checksums); - - // Restore original checksums - WriteChecksums(checkSpan, originalChecksums); - - // Check if they match - return checksums.SequenceEqual(originalChecksums); - } - - private static void SetChecksum(Span input, int offset, int len, int checksum_offset) - { - // Wipe Checksum region. - var checkSpan = input.Slice(checksum_offset, 4 * 16); - checkSpan.Clear(); - - // Compute current checksum of the specified span - Span checksums = stackalloc uint[16]; - var span = input.Slice(offset, len); - ComputeChecksums(span, checksums); - - WriteChecksums(checkSpan, checksums); - } - - private static void WriteChecksums(Span span, Span checksums) - { - for (int i = 0; i < checksums.Length; i++) - { - var dest = span[(i * 4)..]; - WriteUInt32BigEndian(dest, checksums[i]); - } - } - - private static void ComputeChecksums(Span span, Span checksums) - { - for (int i = 0; i < span.Length; i += 2) - { - uint value = ReadUInt16BigEndian(span[i..]); - for (int c = 0; c < checksums.Length; c++) - checksums[c] += ((value >> c) & 1); - } - } - - public override string GetString(ReadOnlySpan data) => StringConverter4GC.GetStringUnicode(data); - - public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter4GC.SetStringUnicode(value, destBuffer, maxLength, option); + ClearBoxes(); } + + public SAV4BR(byte[] data) : base(data) + { + InitializeData(data); + } + + private void InitializeData(ReadOnlySpan data) + { + Data = DecryptPBRSaveData(data); + + // Detect active save + var first = ReadUInt32BigEndian(Data.AsSpan(0x00004C)); + var second = ReadUInt32BigEndian(Data.AsSpan(0x1C004C)); + SaveCount = Math.Max(second, first); + if (second > first) + { + // swap halves + byte[] tempData = new byte[SIZE_HALF]; + Array.Copy(Data, 0, tempData, 0, SIZE_HALF); + Array.Copy(Data, SIZE_HALF, Data, 0, SIZE_HALF); + tempData.CopyTo(Data, SIZE_HALF); + } + + var names = (string[]) SaveNames; + for (int i = 0; i < SAVE_COUNT; i++) + { + var name = GetOTName(i); + if (string.IsNullOrWhiteSpace(name)) + name = $"Empty {i + 1}"; + else if (_currentSlot == -1) + _currentSlot = i; + names[i] = name; + } + + if (_currentSlot == -1) + _currentSlot = 0; + + CurrentSlot = _currentSlot; + } + + /// Amount of times the primary save has been saved + private uint SaveCount; + + protected override byte[] GetFinalData() + { + SetChecksums(); + return EncryptPBRSaveData(Data); + } + + // Configuration + protected override SaveFile CloneInternal() => new SAV4BR(Write()); + + public readonly IReadOnlyList SaveNames = new string[SAVE_COUNT]; + + private int _currentSlot = -1; + private const int SIZE_SLOT = 0x6FF00; + + public int CurrentSlot + { + get => _currentSlot; + // 4 save slots, data reading depends on current slot + set + { + _currentSlot = value; + var ofs = SIZE_SLOT * _currentSlot; + Box = ofs + 0x978; + Party = ofs + 0x13A54; // first team slot after boxes + BoxName = ofs + 0x58674; + } + } + + protected override int SIZE_STORED => PokeCrypto.SIZE_4STORED; + protected override int SIZE_PARTY => PokeCrypto.SIZE_4STORED + 4; + public override PKM BlankPKM => new BK4(); + public override Type PKMType => typeof(BK4); + + public override int MaxMoveID => 467; + public override int MaxSpeciesID => Legal.MaxSpeciesID_4; + public override int MaxAbilityID => Legal.MaxAbilityID_4; + public override int MaxItemID => Legal.MaxItemID_4_HGSS; + public override int MaxBallID => Legal.MaxBallID_4; + public override int MaxGameID => Legal.MaxGameID_4; + + public override int MaxEV => 255; + public override int Generation => 4; + public override EntityContext Context => EntityContext.Gen4; + protected override int GiftCountMax => 1; + public override int OTLength => 7; + public override int NickLength => 10; + public override int MaxMoney => 999999; + public override int Language => (int)LanguageID.English; // prevent KOR from inhabiting + + public override int BoxCount => 18; + + public override int PartyCount + { + get + { + int ctr = 0; + for (int i = 0; i < 6; i++) + { + if (Data[GetPartyOffset(i) + 4] != 0) // sanity + ctr++; + } + return ctr; + } + protected set + { + // Ignore, value is calculated + } + } + + // Checksums + protected override void SetChecksums() + { + SetChecksum(Data, 0x0000000, 0x0000100, 0x000008); + SetChecksum(Data, 0x0000000, SIZE_HALF, SIZE_HALF - 0x80); + SetChecksum(Data, SIZE_HALF, 0x0000100, SIZE_HALF + 0x000008); + SetChecksum(Data, SIZE_HALF, SIZE_HALF, SIZE_HALF + SIZE_HALF - 0x80); + } + + public override bool ChecksumsValid => IsChecksumsValid(Data); + public override string ChecksumInfo => $"Checksums valid: {ChecksumsValid}."; + + public static bool IsChecksumsValid(Span sav) + { + return VerifyChecksum(sav, 0x0000000, 0x0000100, 0x000008) + && VerifyChecksum(sav, 0x0000000, SIZE_HALF, SIZE_HALF - 0x80) + && VerifyChecksum(sav, SIZE_HALF, 0x0000100, SIZE_HALF + 0x000008) + && VerifyChecksum(sav, SIZE_HALF, SIZE_HALF, SIZE_HALF + SIZE_HALF - 0x80); + } + + // Trainer Info + public override GameVersion Version { get => GameVersion.BATREV; protected set { } } + + private string GetOTName(int slot) + { + var ofs = 0x390 + (0x6FF00 * slot); + var span = Data.AsSpan(ofs, 16); + return GetString(span); + } + + private void SetOTName(int slot, string name) + { + var ofs = 0x390 + (0x6FF00 * slot); + var span = Data.AsSpan(ofs, 16); + SetString(span, name.AsSpan(), 7, StringConverterOption.ClearZero); + } + + public string CurrentOT { get => GetOTName(_currentSlot); set => SetOTName(_currentSlot, value); } + + // Storage + public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot); + public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30); + + public override int TID + { + get => (Data[(_currentSlot * SIZE_SLOT) + 0x12867] << 8) | Data[(_currentSlot * SIZE_SLOT) + 0x12860]; + set + { + Data[(_currentSlot * SIZE_SLOT) + 0x12867] = (byte)(value >> 8); + Data[(_currentSlot * SIZE_SLOT) + 0x12860] = (byte)(value & 0xFF); + } + } + + public override int SID + { + get => (Data[(_currentSlot * SIZE_SLOT) + 0x12865] << 8) | Data[(_currentSlot * SIZE_SLOT) + 0x12866]; + set + { + Data[(_currentSlot * SIZE_SLOT) + 0x12865] = (byte)(value >> 8); + Data[(_currentSlot * SIZE_SLOT) + 0x12866] = (byte)(value & 0xFF); + } + } + + // Save file does not have Box Name / Wallpaper info + private int BoxName = -1; + private const int BoxNameLength = 0x28; + + public override string GetBoxName(int box) + { + if (BoxName < 0) + return $"BOX {box + 1}"; + + int ofs = BoxName + (box * BoxNameLength); + var span = Data.AsSpan(ofs, BoxNameLength); + if (ReadUInt16BigEndian(span) == 0) + return $"BOX {box + 1}"; + return GetString(ofs, BoxNameLength); + } + + public override void SetBoxName(int box, string value) + { + if (BoxName < 0) + return; + + int ofs = BoxName + (box * BoxNameLength); + var span = Data.AsSpan(ofs, BoxNameLength); + if (ReadUInt16BigEndian(span) == 0) + return; + + SetString(span, value.AsSpan(), BoxNameLength / 2, StringConverterOption.ClearZero); + } + + protected override PKM GetPKM(byte[] data) + { + if (data.Length != SIZE_STORED) + Array.Resize(ref data, SIZE_STORED); + return BK4.ReadUnshuffle(data); + } + + protected override byte[] DecryptPKM(byte[] data) => data; + + protected override void SetDex(PKM pk) { /* There's no PokéDex */ } + + protected override void SetPKM(PKM pk, bool isParty = false) + { + var pk4 = (BK4)pk; + // Apply to this Save File + DateTime Date = DateTime.Now; + if (pk4.Trade(OT, TID, SID, Gender, Date.Day, Date.Month, Date.Year)) + pk.RefreshChecksum(); + } + + protected override void SetPartyValues(PKM pk, bool isParty) + { + if (pk is G4PKM g4) + g4.Sanity = isParty ? (ushort)0xC000 : (ushort)0x4000; + } + + public static byte[] DecryptPBRSaveData(ReadOnlySpan input) + { + byte[] output = new byte[input.Length]; + Span keys = stackalloc ushort[4]; + for (int offset = 0; offset < SaveUtil.SIZE_G4BR; offset += SIZE_HALF) + { + var inSlice = input.Slice(offset, SIZE_HALF); + var outSlice = output.AsSpan(offset, SIZE_HALF); + + // First 8 bytes are the encryption keys for this chunk. + var keySlice = inSlice[..(keys.Length * 2)]; + GeniusCrypto.ReadKeys(keySlice, keys); + + // Copy over the keys to the result. + keySlice.CopyTo(outSlice); + + // Decrypt the input, result stored in output. + Range r = new(8, SIZE_HALF); + GeniusCrypto.Decrypt(inSlice[r], outSlice[r], keys); + } + return output; + } + + private static byte[] EncryptPBRSaveData(ReadOnlySpan input) + { + byte[] output = new byte[input.Length]; + Span keys = stackalloc ushort[4]; + for (int offset = 0; offset < SaveUtil.SIZE_G4BR; offset += SIZE_HALF) + { + var inSlice = input.Slice(offset, SIZE_HALF); + var outSlice = output.AsSpan(offset, SIZE_HALF); + + // First 8 bytes are the encryption keys for this chunk. + var keySlice = inSlice[..(keys.Length * 2)]; + GeniusCrypto.ReadKeys(keySlice, keys); + + // Copy over the keys to the result. + keySlice.CopyTo(outSlice); + + // Decrypt the input, result stored in output. + Range r = new(8, SIZE_HALF); + GeniusCrypto.Encrypt(inSlice[r], outSlice[r], keys); + } + return output; + } + + public static bool VerifyChecksum(Span input, int offset, int len, int chkOffset) + { + // Read original checksum data, and clear it for recomputing + Span originalChecksums = stackalloc uint[16]; + var checkSpan = input.Slice(chkOffset, 4 * originalChecksums.Length); + for (int i = 0; i < originalChecksums.Length; i++) + { + var chk = checkSpan.Slice(i * 4, 4); + originalChecksums[i] = ReadUInt32BigEndian(chk); + } + checkSpan.Clear(); + + // Compute current checksum of the specified span + Span checksums = stackalloc uint[16]; + var span = input.Slice(offset, len); + ComputeChecksums(span, checksums); + + // Restore original checksums + WriteChecksums(checkSpan, originalChecksums); + + // Check if they match + return checksums.SequenceEqual(originalChecksums); + } + + private static void SetChecksum(Span input, int offset, int len, int chkOffset) + { + // Wipe Checksum region. + var checkSpan = input.Slice(chkOffset, 4 * 16); + checkSpan.Clear(); + + // Compute current checksum of the specified span + Span checksums = stackalloc uint[16]; + var span = input.Slice(offset, len); + ComputeChecksums(span, checksums); + + WriteChecksums(checkSpan, checksums); + } + + private static void WriteChecksums(Span span, Span checksums) + { + for (int i = 0; i < checksums.Length; i++) + { + var dest = span[(i * 4)..]; + WriteUInt32BigEndian(dest, checksums[i]); + } + } + + private static void ComputeChecksums(Span span, Span checksums) + { + for (int i = 0; i < span.Length; i += 2) + { + uint value = ReadUInt16BigEndian(span[i..]); + for (int c = 0; c < checksums.Length; c++) + checksums[c] += ((value >> c) & 1); + } + } + + public override string GetString(ReadOnlySpan data) => StringConverter4GC.GetStringUnicode(data); + + public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter4GC.SetStringUnicode(value, destBuffer, maxLength, option); } diff --git a/PKHeX.Core/Saves/SAV4DP.cs b/PKHeX.Core/Saves/SAV4DP.cs index 2ed125b76..76fd58dc4 100644 --- a/PKHeX.Core/Saves/SAV4DP.cs +++ b/PKHeX.Core/Saves/SAV4DP.cs @@ -2,161 +2,160 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// format for +/// +public sealed class SAV4DP : SAV4Sinnoh { - /// - /// format for - /// - public sealed class SAV4DP : SAV4Sinnoh + public SAV4DP() : base(GeneralSize, StorageSize) { - public SAV4DP() : base(GeneralSize, StorageSize) - { - Initialize(); - Dex = new Zukan4(this, PokeDex); - } - - public SAV4DP(byte[] data) : base(data, GeneralSize, StorageSize, GeneralSize) - { - Initialize(); - Dex = new Zukan4(this, PokeDex); - } - - public override Zukan4 Dex { get; } - - protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4DP((byte[])Data.Clone()) : new SAV4DP(); - public override PersonalTable Personal => PersonalTable.DP; - public override IReadOnlyList HeldItems => Legal.HeldItems_DP; - public override int MaxItemID => Legal.MaxItemID_4_DP; - - private const int GeneralSize = 0xC100; - private const int StorageSize = 0x121E0; // Start 0xC100, +4 starts box data - protected override int StorageStart => GeneralSize; - - private void Initialize() - { - Version = GameVersion.DP; - GetSAVOffsets(); - } - - protected override int EventWork => 0xD9C; - protected override int EventFlag => 0xFDC; - - private void GetSAVOffsets() - { - AdventureInfo = 0; - Trainer1 = 0x64; - Party = 0x98; - PokeDex = 0x12DC; - WondercardFlags = 0xA6D0; - WondercardData = 0xA7fC; - - DaycareOffset = 0x141C; - OFS_HONEY = 0x72E4; - - OFS_UG_Stats = 0x3A2C; - OFS_UG_Items = 0x42B0; - - PoketchStart = 0x114C; - Seal = 0x6178; - - OFS_PoffinCase = 0x5050; - - Box = 4; - } - - #region Storage - public override int GetBoxWallpaper(int box) - { - if ((uint)box >= 18) - return 0; - return Storage[GetBoxWallpaperOffset(box)]; - } - - public override void SetBoxWallpaper(int box, int value) - { - if ((uint)box >= 18) - return; - Storage[GetBoxWallpaperOffset(box)] = (byte)value; - } - #endregion - - public override IReadOnlyList Inventory - { - get - { - InventoryPouch[] pouch = - { - new InventoryPouch4(InventoryType.Items, Legal.Pouch_Items_DP, 999, 0x624), - new InventoryPouch4(InventoryType.KeyItems, Legal.Pouch_Key_DP, 1, 0x8B8), - new InventoryPouch4(InventoryType.TMHMs, Legal.Pouch_TMHM_DP, 99, 0x980), - new InventoryPouch4(InventoryType.MailItems, Legal.Pouch_Mail_DP, 999, 0xB10), - new InventoryPouch4(InventoryType.Medicine, Legal.Pouch_Medicine_DP, 999, 0xB40), - new InventoryPouch4(InventoryType.Berries, Legal.Pouch_Berries_DP, 999, 0xBE0), - new InventoryPouch4(InventoryType.Balls, Legal.Pouch_Ball_DP, 999, 0xCE0), - new InventoryPouch4(InventoryType.BattleItems, Legal.Pouch_Battle_DP, 999, 0xD1C), - }; - return pouch.LoadAll(General); - } - set => value.SaveAll(General); - } - - // reverse crc32 polynomial, nice! - private const uint MysteryGiftDPSlotActive = 0xEDB88320; - - public bool[] GetMysteryGiftDPSlotActiveFlags() - { - var span = General.AsSpan(WondercardFlags + 0x100); // skip over flags - bool[] active = new bool[GiftCountMax]; // 8 PGT, 3 PCD - for (int i = 0; i < active.Length; i++) - active[i] = ReadUInt32LittleEndian(span[(4*i)..]) == MysteryGiftDPSlotActive; - - return active; - } - - public void SetMysteryGiftDPSlotActiveFlags(bool[] value) - { - if (value.Length != GiftCountMax) - return; - - var span = General.AsSpan(WondercardFlags + 0x100); // skip over flags - for (int i = 0; i < value.Length; i++) - WriteUInt32LittleEndian(span[(4 * i)..], value[i] ? MysteryGiftDPSlotActive : 0); - } - - public override MysteryGiftAlbum GiftAlbum - { - get => base.GiftAlbum; - set - { - base.GiftAlbum = value; - SetActiveGiftFlags(value.Gifts); - } - } - - private void SetActiveGiftFlags(IReadOnlyList gifts) - { - var arr = new bool[gifts.Count]; - for (int i = 0; i < arr.Length; i++) - arr[i] = !gifts[i].Empty; - SetMysteryGiftDPSlotActiveFlags(arr); - } - - public override int M { get => ReadUInt16LittleEndian(General.AsSpan(0x1238)); set => WriteUInt16LittleEndian(General.AsSpan(0x1238), (ushort)value); } - public override int X { get => ReadUInt16LittleEndian(General.AsSpan(0x1240)); set => WriteUInt16LittleEndian(General.AsSpan(0x1240), (ushort)(X2 = value)); } - public override int Y { get => ReadUInt16LittleEndian(General.AsSpan(0x1244)); set => WriteUInt16LittleEndian(General.AsSpan(0x1244), (ushort)(Y2 = value)); } - - public override Span Rival_Trash - { - get => General.AsSpan(0x25A8, OTLength * 2); - set { if (value.Length == OTLength * 2) value.CopyTo(General.AsSpan(0x25A8)); } - } - - public override int X2 { get => ReadUInt16LittleEndian(General.AsSpan(0x25FA)); set => WriteUInt16LittleEndian(General.AsSpan(0x25FA), (ushort)value); } - public override int Y2 { get => ReadUInt16LittleEndian(General.AsSpan(0x25FE)); set => WriteUInt16LittleEndian(General.AsSpan(0x25FE), (ushort)value); } - public override int Z { get => ReadUInt16LittleEndian(General.AsSpan(0x2602)); set => WriteUInt16LittleEndian(General.AsSpan(0x2602), (ushort)value); } - - public override uint SafariSeed { get => ReadUInt32LittleEndian(General.AsSpan(0x53C4)); set => WriteUInt32LittleEndian(General.AsSpan(0x53C4), value); } - public override uint SwarmSeed { get => ReadUInt32LittleEndian(General.AsSpan(0x53C8)); set => WriteUInt32LittleEndian(General.AsSpan(0x53C8), value); } - public override uint SwarmMaxCountModulo => 28; + Initialize(); + Dex = new Zukan4(this, PokeDex); } + + public SAV4DP(byte[] data) : base(data, GeneralSize, StorageSize, GeneralSize) + { + Initialize(); + Dex = new Zukan4(this, PokeDex); + } + + public override Zukan4 Dex { get; } + + protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4DP((byte[])Data.Clone()) : new SAV4DP(); + public override PersonalTable Personal => PersonalTable.DP; + public override IReadOnlyList HeldItems => Legal.HeldItems_DP; + public override int MaxItemID => Legal.MaxItemID_4_DP; + + private const int GeneralSize = 0xC100; + private const int StorageSize = 0x121E0; // Start 0xC100, +4 starts box data + protected override int StorageStart => GeneralSize; + + private void Initialize() + { + Version = GameVersion.DP; + GetSAVOffsets(); + } + + protected override int EventWork => 0xD9C; + protected override int EventFlag => 0xFDC; + + private void GetSAVOffsets() + { + AdventureInfo = 0; + Trainer1 = 0x64; + Party = 0x98; + PokeDex = 0x12DC; + WondercardFlags = 0xA6D0; + WondercardData = 0xA7fC; + + DaycareOffset = 0x141C; + OFS_HONEY = 0x72E4; + + OFS_UG_Stats = 0x3A2C; + OFS_UG_Items = 0x42B0; + + PoketchStart = 0x114C; + Seal = 0x6178; + + OFS_PoffinCase = 0x5050; + + Box = 4; + } + + #region Storage + public override int GetBoxWallpaper(int box) + { + if ((uint)box >= 18) + return 0; + return Storage[GetBoxWallpaperOffset(box)]; + } + + public override void SetBoxWallpaper(int box, int value) + { + if ((uint)box >= 18) + return; + Storage[GetBoxWallpaperOffset(box)] = (byte)value; + } + #endregion + + public override IReadOnlyList Inventory + { + get + { + InventoryPouch[] pouch = + { + new InventoryPouch4(InventoryType.Items, Legal.Pouch_Items_DP, 999, 0x624), + new InventoryPouch4(InventoryType.KeyItems, Legal.Pouch_Key_DP, 1, 0x8B8), + new InventoryPouch4(InventoryType.TMHMs, Legal.Pouch_TMHM_DP, 99, 0x980), + new InventoryPouch4(InventoryType.MailItems, Legal.Pouch_Mail_DP, 999, 0xB10), + new InventoryPouch4(InventoryType.Medicine, Legal.Pouch_Medicine_DP, 999, 0xB40), + new InventoryPouch4(InventoryType.Berries, Legal.Pouch_Berries_DP, 999, 0xBE0), + new InventoryPouch4(InventoryType.Balls, Legal.Pouch_Ball_DP, 999, 0xCE0), + new InventoryPouch4(InventoryType.BattleItems, Legal.Pouch_Battle_DP, 999, 0xD1C), + }; + return pouch.LoadAll(General); + } + set => value.SaveAll(General); + } + + // reverse crc32 polynomial, nice! + private const uint MysteryGiftDPSlotActive = 0xEDB88320; + + public bool[] GetMysteryGiftDPSlotActiveFlags() + { + var span = General.AsSpan(WondercardFlags + 0x100); // skip over flags + bool[] active = new bool[GiftCountMax]; // 8 PGT, 3 PCD + for (int i = 0; i < active.Length; i++) + active[i] = ReadUInt32LittleEndian(span[(4*i)..]) == MysteryGiftDPSlotActive; + + return active; + } + + public void SetMysteryGiftDPSlotActiveFlags(bool[] value) + { + if (value.Length != GiftCountMax) + return; + + var span = General.AsSpan(WondercardFlags + 0x100); // skip over flags + for (int i = 0; i < value.Length; i++) + WriteUInt32LittleEndian(span[(4 * i)..], value[i] ? MysteryGiftDPSlotActive : 0); + } + + public override MysteryGiftAlbum GiftAlbum + { + get => base.GiftAlbum; + set + { + base.GiftAlbum = value; + SetActiveGiftFlags(value.Gifts); + } + } + + private void SetActiveGiftFlags(IReadOnlyList gifts) + { + var arr = new bool[gifts.Count]; + for (int i = 0; i < arr.Length; i++) + arr[i] = !gifts[i].Empty; + SetMysteryGiftDPSlotActiveFlags(arr); + } + + public override int M { get => ReadUInt16LittleEndian(General.AsSpan(0x1238)); set => WriteUInt16LittleEndian(General.AsSpan(0x1238), (ushort)value); } + public override int X { get => ReadUInt16LittleEndian(General.AsSpan(0x1240)); set => WriteUInt16LittleEndian(General.AsSpan(0x1240), (ushort)(X2 = value)); } + public override int Y { get => ReadUInt16LittleEndian(General.AsSpan(0x1244)); set => WriteUInt16LittleEndian(General.AsSpan(0x1244), (ushort)(Y2 = value)); } + + public override Span Rival_Trash + { + get => General.AsSpan(0x25A8, OTLength * 2); + set { if (value.Length == OTLength * 2) value.CopyTo(General.AsSpan(0x25A8)); } + } + + public override int X2 { get => ReadUInt16LittleEndian(General.AsSpan(0x25FA)); set => WriteUInt16LittleEndian(General.AsSpan(0x25FA), (ushort)value); } + public override int Y2 { get => ReadUInt16LittleEndian(General.AsSpan(0x25FE)); set => WriteUInt16LittleEndian(General.AsSpan(0x25FE), (ushort)value); } + public override int Z { get => ReadUInt16LittleEndian(General.AsSpan(0x2602)); set => WriteUInt16LittleEndian(General.AsSpan(0x2602), (ushort)value); } + + public override uint SafariSeed { get => ReadUInt32LittleEndian(General.AsSpan(0x53C4)); set => WriteUInt32LittleEndian(General.AsSpan(0x53C4), value); } + public override uint SwarmSeed { get => ReadUInt32LittleEndian(General.AsSpan(0x53C8)); set => WriteUInt32LittleEndian(General.AsSpan(0x53C8), value); } + public override uint SwarmMaxCountModulo => 28; } diff --git a/PKHeX.Core/Saves/SAV4HGSS.cs b/PKHeX.Core/Saves/SAV4HGSS.cs index 50f4bb522..f69473a75 100644 --- a/PKHeX.Core/Saves/SAV4HGSS.cs +++ b/PKHeX.Core/Saves/SAV4HGSS.cs @@ -3,253 +3,252 @@ using System.Runtime.InteropServices; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// format for +/// +public sealed class SAV4HGSS : SAV4 { - /// - /// format for - /// - public sealed class SAV4HGSS : SAV4 + public SAV4HGSS() : base(GeneralSize, StorageSize) { - public SAV4HGSS() : base(GeneralSize, StorageSize) - { - Initialize(); - Dex = new Zukan4(this, PokeDex); - } - - public SAV4HGSS(byte[] data) : base(data, GeneralSize, StorageSize, GeneralSize + GeneralGap) - { - Initialize(); - Dex = new Zukan4(this, PokeDex); - } - - public override Zukan4 Dex { get; } - protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4HGSS((byte[])Data.Clone()) : new SAV4HGSS(); - - public override PersonalTable Personal => PersonalTable.HGSS; - public override IReadOnlyList HeldItems => Legal.HeldItems_HGSS; - public override int MaxItemID => Legal.MaxItemID_4_HGSS; - private const int GeneralSize = 0xF628; - private const int StorageSize = 0x12310; // Start 0xF700, +0 starts box data - private const int GeneralGap = 0xD8; - protected override int StorageStart => 0xF700; // unused section right after GeneralSize, alignment? - protected override int FooterSize => 0x10; - - private void Initialize() - { - Version = GameVersion.HGSS; - GetSAVOffsets(); - } - - protected override int EventWork => 0xDE4; - protected override int EventFlag => 0x10C4; - - private void GetSAVOffsets() - { - AdventureInfo = 0; - Trainer1 = 0x64; - Party = 0x98; - PokeDex = 0x12B8; - WondercardFlags = 0x9D3C; - WondercardData = 0x9E3C; - - DaycareOffset = 0x15FC; - Seal = 0x4E20; - - Box = 0; - } - - private Span LockCapsuleSpan => General.AsSpan(0xB064, PCD.Size); - - public PCD LockCapsuleSlot - { - get => new(LockCapsuleSpan.ToArray()); - set => value.Data.CopyTo(LockCapsuleSpan); - } - - #region Storage - // box{pk4[30}[18] - // u32 currentBox - // u32 counter - // g4str[18] boxNames - // byte[18] boxWallpapers - // -- each box is chunked, padded to nearest 0x100 (resulting in 0x10 trailing zeroes) - // -- The final 0x16 bytes in the Storage block are unused (padding to nearest 0x100). - private const int BOX_COUNT = 18; - private const int BOX_SLOTS = 30; - private const int BOX_NAME_LEN = 40; // 20 characters - - private const int BOX_DATA_LEN = (BOX_SLOTS * PokeCrypto.SIZE_4STORED) + 0x10; // 0xFF0, each box chunk is padded to nearest 0x100 - private const int BOX_END = BOX_COUNT * BOX_DATA_LEN; // 18 * 0x1000 - private const int BOX_NAME = 0x12008; // after current & counter - private const int BOX_WP = BOX_NAME + (BOX_COUNT * BOX_NAME_LEN); // 0x122D8; - private const int BOX_FLAGS = 18 + BOX_WP; // 0x122EA; - - public override int GetBoxOffset(int box) => box * 0x1000; - private static int GetBoxNameOffset(int box) => BOX_NAME + (box * BOX_NAME_LEN); - protected override int GetBoxWallpaperOffset(int box) => BOX_WP + box; - - // 8 bytes current box (align 32) & (stored count?) - public override int CurrentBox - { - get => Storage[BOX_END]; - set => Storage[BOX_END] = (byte)value; - } - - public override byte[] BoxFlags - { - get => new[] { Storage[BOX_FLAGS] }; - set => Storage[BOX_FLAGS] = value[0]; - } - - public int Counter - { - get => ReadInt32LittleEndian(Storage.AsSpan(BOX_END + 4)); - set => WriteInt32LittleEndian(Storage.AsSpan(BOX_END + 4), value); - } - - public override string GetBoxName(int box) => GetString(Storage.AsSpan(GetBoxNameOffset(box), BOX_NAME_LEN)); - - public override void SetBoxName(int box, string value) - { - const int maxlen = 8; - var span = Storage.AsSpan(GetBoxNameOffset(box), BOX_NAME_LEN); - SetString(span, value.AsSpan(), maxlen, StringConverterOption.ClearZero); - } - - private static int AdjustWallpaper(int value, int shift) - { - // Pt's Special Wallpapers 1-8 are shifted by +0x8 - // HG/SS Special Wallpapers 1-8 (Primo Phrases) are shifted by +0x10 - if (value >= 0x10) // special - return value + shift; - return value; - } - - public override int GetBoxWallpaper(int box) - { - int offset = GetBoxWallpaperOffset(box); - int value = Storage[offset]; - return AdjustWallpaper(value, -0x10); - } - - public override void SetBoxWallpaper(int box, int value) - { - value = AdjustWallpaper(value, 0x10); - Storage[GetBoxWallpaperOffset(box)] = (byte)value; - } - #endregion - - public override IReadOnlyList Inventory - { - get - { - InventoryPouch[] pouch = - { - new InventoryPouch4(InventoryType.Items, Legal.Pouch_Items_HGSS, 999, 0x644), // 0x644-0x8D7 (0x8CB) - new InventoryPouch4(InventoryType.KeyItems, Legal.Pouch_Key_HGSS, 1, 0x8D8), // 0x8D8-0x99F (0x979) - new InventoryPouch4(InventoryType.TMHMs, Legal.Pouch_TMHM_HGSS, 99, 0x9A0), // 0x9A0-0xB33 (0xB2F) - new InventoryPouch4(InventoryType.MailItems, Legal.Pouch_Mail_HGSS, 999, 0xB34), // 0xB34-0xB63 (0xB63) - new InventoryPouch4(InventoryType.Medicine, Legal.Pouch_Medicine_HGSS, 999, 0xB64), // 0xB64-0xC03 (0xBFB) - new InventoryPouch4(InventoryType.Berries, Legal.Pouch_Berries_HGSS, 999, 0xC04), // 0xC04-0xD03 - new InventoryPouch4(InventoryType.Balls, Legal.Pouch_Ball_HGSS, 999, 0xD04), // 0xD04-0xD63 - new InventoryPouch4(InventoryType.BattleItems, Legal.Pouch_Battle_HGSS, 999, 0xD64), // 0xD64-0xD97 - }; - return pouch.LoadAll(General); - } - set => value.SaveAll(General); - } - - public override int M { get => ReadUInt16LittleEndian(General.AsSpan(0x1234)); set => WriteUInt16LittleEndian(General.AsSpan(0x1234), (ushort)value); } - public override int X { get => ReadUInt16LittleEndian(General.AsSpan(0x123C)); set => WriteUInt16LittleEndian(General.AsSpan(0x123C), (ushort)(X2 = value)); } - public override int Y { get => ReadUInt16LittleEndian(General.AsSpan(0x1240)); set => WriteUInt16LittleEndian(General.AsSpan(0x1240), (ushort)(Y2 = value)); } - - public override Span Rival_Trash - { - get => General.AsSpan(0x22D4, OTLength * 2); - set { if (value.Length == OTLength * 2) value.CopyTo(General.AsSpan(0x22D4)); } - } - - public override int X2 { get => ReadUInt16LittleEndian(General.AsSpan(0x236E)); set => WriteUInt16LittleEndian(General.AsSpan(0x236E), (ushort)value); } - public override int Y2 { get => ReadUInt16LittleEndian(General.AsSpan(0x2372)); set => WriteUInt16LittleEndian(General.AsSpan(0x2372), (ushort)value); } - public override int Z { get => ReadUInt16LittleEndian(General.AsSpan(0x2376)); set => WriteUInt16LittleEndian(General.AsSpan(0x2376), (ushort)value); } - - public int Badges16 - { - get => General[Trainer1 + 0x1F]; - set => General[Trainer1 + 0x1F] = (byte)value; - } - - private const int OFS_GearRolodex = 0xC0EC; - private const byte GearMaxCallers = (byte)(PokegearNumber.Ernest + 1); - - public PokegearNumber GetCallerAtIndex(int index) => (PokegearNumber)General[OFS_GearRolodex + index]; - public void SetCallerAtIndex(int index, PokegearNumber caller) => General[OFS_GearRolodex + index] = (byte)caller; - - public PokegearNumber[] GetPokeGearRoloDex() - { - var arr = General.AsSpan(OFS_GearRolodex, GearMaxCallers); - return MemoryMarshal.Cast(arr).ToArray(); - } - - public void SetPokeGearRoloDex(ReadOnlySpan value) - { - if (value.Length > GearMaxCallers) - throw new ArgumentOutOfRangeException(nameof(value)); - MemoryMarshal.Cast(value).CopyTo(General.AsSpan(OFS_GearRolodex, GearMaxCallers)); - } - - public void PokeGearUnlockAllCallers() - { - for (int i = 0; i < GearMaxCallers; i++) - SetCallerAtIndex(i, (PokegearNumber)i); - } - - public void PokeGearClearAllCallers(int start = 0) - { - for (int i = start; i < GearMaxCallers; i++) - SetCallerAtIndex(i, PokegearNumber.None); - } - - public void PokeGearUnlockAllCallersNoTrainers() - { - var nonTrainers = new[] - { - PokegearNumber.Mother, - PokegearNumber.Professor_Elm, - PokegearNumber.Professor_Oak, - PokegearNumber.Ethan, - PokegearNumber.Lyra, - PokegearNumber.Kurt, - PokegearNumber.Daycare_Man, - PokegearNumber.Daycare_Lady, - PokegearNumber.Bill, - PokegearNumber.Bike_Shop, - PokegearNumber.Baoba, - }; - for (int i = 0; i < nonTrainers.Length; i++) - SetCallerAtIndex(i, nonTrainers[i]); - - // clear remaining callers - PokeGearClearAllCallers(nonTrainers.Length); - } - - // Apricorn Pouch - public int GetApricornCount(int index) => General[0xE558 + index]; - public void SetApricornCount(int index, int count) => General[0xE558 + index] = (byte)count; - - // Pokewalker - public const int WalkerPair = 0xE5E0; - private const int OFS_WALKER = 0xE704; - - public uint PokewalkerSteps { get => ReadUInt32LittleEndian(General.AsSpan(OFS_WALKER)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_WALKER), value); } - public uint PokewalkerWatts { get => ReadUInt32LittleEndian(General.AsSpan(OFS_WALKER + 0x4)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_WALKER + 4), value); } - - public bool[] GetPokewalkerCoursesUnlocked() => ArrayUtil.GitBitFlagArray(General.AsSpan(OFS_WALKER + 0x8), 32); - public void SetPokewalkerCoursesUnlocked(bool[] value) => ArrayUtil.SetBitFlagArray(General.AsSpan(OFS_WALKER + 0x8), value); - - public void PokewalkerCoursesSetAll(uint value = 0x07FF_FFFFu) => WriteUInt32LittleEndian(General.AsSpan(OFS_WALKER + 0x8), value); - - public override uint SwarmSeed { get => ReadUInt32LittleEndian(General.AsSpan(0x68A8)); set => WriteUInt32LittleEndian(General.AsSpan(0x68A8), value); } - public override uint SwarmMaxCountModulo => 20; + Initialize(); + Dex = new Zukan4(this, PokeDex); } + + public SAV4HGSS(byte[] data) : base(data, GeneralSize, StorageSize, GeneralSize + GeneralGap) + { + Initialize(); + Dex = new Zukan4(this, PokeDex); + } + + public override Zukan4 Dex { get; } + protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4HGSS((byte[])Data.Clone()) : new SAV4HGSS(); + + public override PersonalTable Personal => PersonalTable.HGSS; + public override IReadOnlyList HeldItems => Legal.HeldItems_HGSS; + public override int MaxItemID => Legal.MaxItemID_4_HGSS; + private const int GeneralSize = 0xF628; + private const int StorageSize = 0x12310; // Start 0xF700, +0 starts box data + private const int GeneralGap = 0xD8; + protected override int StorageStart => 0xF700; // unused section right after GeneralSize, alignment? + protected override int FooterSize => 0x10; + + private void Initialize() + { + Version = GameVersion.HGSS; + GetSAVOffsets(); + } + + protected override int EventWork => 0xDE4; + protected override int EventFlag => 0x10C4; + + private void GetSAVOffsets() + { + AdventureInfo = 0; + Trainer1 = 0x64; + Party = 0x98; + PokeDex = 0x12B8; + WondercardFlags = 0x9D3C; + WondercardData = 0x9E3C; + + DaycareOffset = 0x15FC; + Seal = 0x4E20; + + Box = 0; + } + + private Span LockCapsuleSpan => General.AsSpan(0xB064, PCD.Size); + + public PCD LockCapsuleSlot + { + get => new(LockCapsuleSpan.ToArray()); + set => value.Data.CopyTo(LockCapsuleSpan); + } + + #region Storage + // box{pk4[30}[18] + // u32 currentBox + // u32 counter + // g4str[18] boxNames + // byte[18] boxWallpapers + // -- each box is chunked, padded to nearest 0x100 (resulting in 0x10 trailing zeroes) + // -- The final 0x16 bytes in the Storage block are unused (padding to nearest 0x100). + private const int BOX_COUNT = 18; + private const int BOX_SLOTS = 30; + private const int BOX_NAME_LEN = 40; // 20 characters + + private const int BOX_DATA_LEN = (BOX_SLOTS * PokeCrypto.SIZE_4STORED) + 0x10; // 0xFF0, each box chunk is padded to nearest 0x100 + private const int BOX_END = BOX_COUNT * BOX_DATA_LEN; // 18 * 0x1000 + private const int BOX_NAME = 0x12008; // after current & counter + private const int BOX_WP = BOX_NAME + (BOX_COUNT * BOX_NAME_LEN); // 0x122D8; + private const int BOX_FLAGS = 18 + BOX_WP; // 0x122EA; + + public override int GetBoxOffset(int box) => box * 0x1000; + private static int GetBoxNameOffset(int box) => BOX_NAME + (box * BOX_NAME_LEN); + protected override int GetBoxWallpaperOffset(int box) => BOX_WP + box; + + // 8 bytes current box (align 32) & (stored count?) + public override int CurrentBox + { + get => Storage[BOX_END]; + set => Storage[BOX_END] = (byte)value; + } + + public override byte[] BoxFlags + { + get => new[] { Storage[BOX_FLAGS] }; + set => Storage[BOX_FLAGS] = value[0]; + } + + public int Counter + { + get => ReadInt32LittleEndian(Storage.AsSpan(BOX_END + 4)); + set => WriteInt32LittleEndian(Storage.AsSpan(BOX_END + 4), value); + } + + public override string GetBoxName(int box) => GetString(Storage.AsSpan(GetBoxNameOffset(box), BOX_NAME_LEN)); + + public override void SetBoxName(int box, string value) + { + const int maxlen = 8; + var span = Storage.AsSpan(GetBoxNameOffset(box), BOX_NAME_LEN); + SetString(span, value.AsSpan(), maxlen, StringConverterOption.ClearZero); + } + + private static int AdjustWallpaper(int value, int shift) + { + // Pt's Special Wallpapers 1-8 are shifted by +0x8 + // HG/SS Special Wallpapers 1-8 (Primo Phrases) are shifted by +0x10 + if (value >= 0x10) // special + return value + shift; + return value; + } + + public override int GetBoxWallpaper(int box) + { + int offset = GetBoxWallpaperOffset(box); + int value = Storage[offset]; + return AdjustWallpaper(value, -0x10); + } + + public override void SetBoxWallpaper(int box, int value) + { + value = AdjustWallpaper(value, 0x10); + Storage[GetBoxWallpaperOffset(box)] = (byte)value; + } + #endregion + + public override IReadOnlyList Inventory + { + get + { + InventoryPouch[] pouch = + { + new InventoryPouch4(InventoryType.Items, Legal.Pouch_Items_HGSS, 999, 0x644), // 0x644-0x8D7 (0x8CB) + new InventoryPouch4(InventoryType.KeyItems, Legal.Pouch_Key_HGSS, 1, 0x8D8), // 0x8D8-0x99F (0x979) + new InventoryPouch4(InventoryType.TMHMs, Legal.Pouch_TMHM_HGSS, 99, 0x9A0), // 0x9A0-0xB33 (0xB2F) + new InventoryPouch4(InventoryType.MailItems, Legal.Pouch_Mail_HGSS, 999, 0xB34), // 0xB34-0xB63 (0xB63) + new InventoryPouch4(InventoryType.Medicine, Legal.Pouch_Medicine_HGSS, 999, 0xB64), // 0xB64-0xC03 (0xBFB) + new InventoryPouch4(InventoryType.Berries, Legal.Pouch_Berries_HGSS, 999, 0xC04), // 0xC04-0xD03 + new InventoryPouch4(InventoryType.Balls, Legal.Pouch_Ball_HGSS, 999, 0xD04), // 0xD04-0xD63 + new InventoryPouch4(InventoryType.BattleItems, Legal.Pouch_Battle_HGSS, 999, 0xD64), // 0xD64-0xD97 + }; + return pouch.LoadAll(General); + } + set => value.SaveAll(General); + } + + public override int M { get => ReadUInt16LittleEndian(General.AsSpan(0x1234)); set => WriteUInt16LittleEndian(General.AsSpan(0x1234), (ushort)value); } + public override int X { get => ReadUInt16LittleEndian(General.AsSpan(0x123C)); set => WriteUInt16LittleEndian(General.AsSpan(0x123C), (ushort)(X2 = value)); } + public override int Y { get => ReadUInt16LittleEndian(General.AsSpan(0x1240)); set => WriteUInt16LittleEndian(General.AsSpan(0x1240), (ushort)(Y2 = value)); } + + public override Span Rival_Trash + { + get => General.AsSpan(0x22D4, OTLength * 2); + set { if (value.Length == OTLength * 2) value.CopyTo(General.AsSpan(0x22D4)); } + } + + public override int X2 { get => ReadUInt16LittleEndian(General.AsSpan(0x236E)); set => WriteUInt16LittleEndian(General.AsSpan(0x236E), (ushort)value); } + public override int Y2 { get => ReadUInt16LittleEndian(General.AsSpan(0x2372)); set => WriteUInt16LittleEndian(General.AsSpan(0x2372), (ushort)value); } + public override int Z { get => ReadUInt16LittleEndian(General.AsSpan(0x2376)); set => WriteUInt16LittleEndian(General.AsSpan(0x2376), (ushort)value); } + + public int Badges16 + { + get => General[Trainer1 + 0x1F]; + set => General[Trainer1 + 0x1F] = (byte)value; + } + + private const int OFS_GearRolodex = 0xC0EC; + private const byte GearMaxCallers = (byte)(PokegearNumber.Ernest + 1); + + public PokegearNumber GetCallerAtIndex(int index) => (PokegearNumber)General[OFS_GearRolodex + index]; + public void SetCallerAtIndex(int index, PokegearNumber caller) => General[OFS_GearRolodex + index] = (byte)caller; + + public PokegearNumber[] GetPokeGearRoloDex() + { + var arr = General.AsSpan(OFS_GearRolodex, GearMaxCallers); + return MemoryMarshal.Cast(arr).ToArray(); + } + + public void SetPokeGearRoloDex(ReadOnlySpan value) + { + if (value.Length > GearMaxCallers) + throw new ArgumentOutOfRangeException(nameof(value)); + MemoryMarshal.Cast(value).CopyTo(General.AsSpan(OFS_GearRolodex, GearMaxCallers)); + } + + public void PokeGearUnlockAllCallers() + { + for (int i = 0; i < GearMaxCallers; i++) + SetCallerAtIndex(i, (PokegearNumber)i); + } + + public void PokeGearClearAllCallers(int start = 0) + { + for (int i = start; i < GearMaxCallers; i++) + SetCallerAtIndex(i, PokegearNumber.None); + } + + public void PokeGearUnlockAllCallersNoTrainers() + { + var nonTrainers = new[] + { + PokegearNumber.Mother, + PokegearNumber.Professor_Elm, + PokegearNumber.Professor_Oak, + PokegearNumber.Ethan, + PokegearNumber.Lyra, + PokegearNumber.Kurt, + PokegearNumber.Daycare_Man, + PokegearNumber.Daycare_Lady, + PokegearNumber.Bill, + PokegearNumber.Bike_Shop, + PokegearNumber.Baoba, + }; + for (int i = 0; i < nonTrainers.Length; i++) + SetCallerAtIndex(i, nonTrainers[i]); + + // clear remaining callers + PokeGearClearAllCallers(nonTrainers.Length); + } + + // Apricorn Pouch + public int GetApricornCount(int index) => General[0xE558 + index]; + public void SetApricornCount(int index, int count) => General[0xE558 + index] = (byte)count; + + // Pokewalker + public const int WalkerPair = 0xE5E0; + private const int OFS_WALKER = 0xE704; + + public uint PokewalkerSteps { get => ReadUInt32LittleEndian(General.AsSpan(OFS_WALKER)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_WALKER), value); } + public uint PokewalkerWatts { get => ReadUInt32LittleEndian(General.AsSpan(OFS_WALKER + 0x4)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_WALKER + 4), value); } + + public bool[] GetPokewalkerCoursesUnlocked() => ArrayUtil.GitBitFlagArray(General.AsSpan(OFS_WALKER + 0x8), 32); + public void SetPokewalkerCoursesUnlocked(bool[] value) => ArrayUtil.SetBitFlagArray(General.AsSpan(OFS_WALKER + 0x8), value); + + public void PokewalkerCoursesSetAll(uint value = 0x07FF_FFFFu) => WriteUInt32LittleEndian(General.AsSpan(OFS_WALKER + 0x8), value); + + public override uint SwarmSeed { get => ReadUInt32LittleEndian(General.AsSpan(0x68A8)); set => WriteUInt32LittleEndian(General.AsSpan(0x68A8), value); } + public override uint SwarmMaxCountModulo => 20; } diff --git a/PKHeX.Core/Saves/SAV4Pt.cs b/PKHeX.Core/Saves/SAV4Pt.cs index 7842cc18a..e65b8082b 100644 --- a/PKHeX.Core/Saves/SAV4Pt.cs +++ b/PKHeX.Core/Saves/SAV4Pt.cs @@ -2,130 +2,129 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// format for +/// +public sealed class SAV4Pt : SAV4Sinnoh { - /// - /// format for - /// - public sealed class SAV4Pt : SAV4Sinnoh + public SAV4Pt() : base(GeneralSize, StorageSize) { - public SAV4Pt() : base(GeneralSize, StorageSize) - { - Initialize(); - Dex = new Zukan4(this, PokeDex); - } - - public SAV4Pt(byte[] data) : base(data, GeneralSize, StorageSize, GeneralSize) - { - Initialize(); - Dex = new Zukan4(this, PokeDex); - } - - public override Zukan4 Dex { get; } - protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4Pt((byte[])Data.Clone()) : new SAV4Pt(); - public override PersonalTable Personal => PersonalTable.Pt; - public override IReadOnlyList HeldItems => Legal.HeldItems_Pt; - public override int MaxItemID => Legal.MaxItemID_4_Pt; - - private const int GeneralSize = 0xCF2C; - private const int StorageSize = 0x121E4; // Start 0xCF2C, +4 starts box data - protected override int StorageStart => GeneralSize; - - private void Initialize() - { - Version = GameVersion.Pt; - GetSAVOffsets(); - } - - protected override int EventWork => 0xDAC; - protected override int EventFlag => 0xFEC; - - private void GetSAVOffsets() - { - AdventureInfo = 0; - Trainer1 = 0x68; - Party = 0xA0; - PokeDex = 0x1328; - WondercardFlags = 0xB4C0; - WondercardData = 0xB5C0; - - DaycareOffset = 0x1654; - OFS_HONEY = 0x7F38; - - OFS_UG_Stats = 0x3CB4; - OFS_UG_Items = 0x4538; - - PoketchStart = 0x1160; - - OFS_PoffinCase = 0x52E8; - Seal = 0x6494; - - Box = 4; - } - - #region Storage - private static int AdjustWallpaper(int value, int shift) - { - // Pt's Special Wallpapers 1-8 are shifted by +0x8 - // HG/SS Special Wallpapers 1-8 (Primo Phrases) are shifted by +0x10 - if (value >= 0x10) // special - return value + shift; - return value; - } - - public override int GetBoxWallpaper(int box) - { - if ((uint)box > 18) - return 0; - int value = Storage[GetBoxWallpaperOffset(box)]; - return AdjustWallpaper(value, -0x08); - } - - public override void SetBoxWallpaper(int box, int value) - { - if ((uint)box >= 18) - return; - value = AdjustWallpaper(value, 0x08); - Storage[GetBoxWallpaperOffset(box)] = (byte)value; - } - #endregion - - public override IReadOnlyList Inventory - { - get - { - InventoryPouch[] pouch = - { - new InventoryPouch4(InventoryType.Items, Legal.Pouch_Items_Pt, 999, 0x630), - new InventoryPouch4(InventoryType.KeyItems, Legal.Pouch_Key_Pt, 1, 0x8C4), - new InventoryPouch4(InventoryType.TMHMs, Legal.Pouch_TMHM_Pt, 99, 0x98C), - new InventoryPouch4(InventoryType.MailItems, Legal.Pouch_Mail_Pt, 999, 0xB1C), - new InventoryPouch4(InventoryType.Medicine, Legal.Pouch_Medicine_Pt, 999, 0xB4C), - new InventoryPouch4(InventoryType.Berries, Legal.Pouch_Berries_Pt, 999, 0xBEC), - new InventoryPouch4(InventoryType.Balls, Legal.Pouch_Ball_Pt, 999, 0xCEC), - new InventoryPouch4(InventoryType.BattleItems, Legal.Pouch_Battle_Pt, 999, 0xD28), - }; - return pouch.LoadAll(General); - } - set => value.SaveAll(General); - } - - public override int M { get => ReadUInt16LittleEndian(General.AsSpan(0x1280)); set => WriteUInt16LittleEndian(General.AsSpan(0x1280), (ushort)value); } - public override int X { get => ReadUInt16LittleEndian(General.AsSpan(0x1288)); set => WriteUInt16LittleEndian(General.AsSpan(0x1288), (ushort)(X2 = value)); } - public override int Y { get => ReadUInt16LittleEndian(General.AsSpan(0x128C)); set => WriteUInt16LittleEndian(General.AsSpan(0x128C), (ushort)(Y2 = value)); } - - public override Span Rival_Trash - { - get => General.AsSpan(0x27E8, OTLength * 2); - set { if (value.Length == OTLength * 2) value.CopyTo(General.AsSpan(0x27E8)); } - } - - public override int X2 { get => ReadUInt16LittleEndian(General.AsSpan(0x287E)); set => WriteUInt16LittleEndian(General.AsSpan(0x287E), (ushort)value); } - public override int Y2 { get => ReadUInt16LittleEndian(General.AsSpan(0x2882)); set => WriteUInt16LittleEndian(General.AsSpan(0x2882), (ushort)value); } - public override int Z { get => ReadUInt16LittleEndian(General.AsSpan(0x2886)); set => WriteUInt16LittleEndian(General.AsSpan(0x2886), (ushort)value); } - - public override uint SafariSeed { get => ReadUInt32LittleEndian(General.AsSpan(0x5660)); set => WriteUInt32LittleEndian(General.AsSpan(0x5660), value); } - public override uint SwarmSeed { get => ReadUInt32LittleEndian(General.AsSpan(0x5664)); set => WriteUInt32LittleEndian(General.AsSpan(0x5664), value); } - public override uint SwarmMaxCountModulo => 22; + Initialize(); + Dex = new Zukan4(this, PokeDex); } + + public SAV4Pt(byte[] data) : base(data, GeneralSize, StorageSize, GeneralSize) + { + Initialize(); + Dex = new Zukan4(this, PokeDex); + } + + public override Zukan4 Dex { get; } + protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4Pt((byte[])Data.Clone()) : new SAV4Pt(); + public override PersonalTable Personal => PersonalTable.Pt; + public override IReadOnlyList HeldItems => Legal.HeldItems_Pt; + public override int MaxItemID => Legal.MaxItemID_4_Pt; + + private const int GeneralSize = 0xCF2C; + private const int StorageSize = 0x121E4; // Start 0xCF2C, +4 starts box data + protected override int StorageStart => GeneralSize; + + private void Initialize() + { + Version = GameVersion.Pt; + GetSAVOffsets(); + } + + protected override int EventWork => 0xDAC; + protected override int EventFlag => 0xFEC; + + private void GetSAVOffsets() + { + AdventureInfo = 0; + Trainer1 = 0x68; + Party = 0xA0; + PokeDex = 0x1328; + WondercardFlags = 0xB4C0; + WondercardData = 0xB5C0; + + DaycareOffset = 0x1654; + OFS_HONEY = 0x7F38; + + OFS_UG_Stats = 0x3CB4; + OFS_UG_Items = 0x4538; + + PoketchStart = 0x1160; + + OFS_PoffinCase = 0x52E8; + Seal = 0x6494; + + Box = 4; + } + + #region Storage + private static int AdjustWallpaper(int value, int shift) + { + // Pt's Special Wallpapers 1-8 are shifted by +0x8 + // HG/SS Special Wallpapers 1-8 (Primo Phrases) are shifted by +0x10 + if (value >= 0x10) // special + return value + shift; + return value; + } + + public override int GetBoxWallpaper(int box) + { + if ((uint)box > 18) + return 0; + int value = Storage[GetBoxWallpaperOffset(box)]; + return AdjustWallpaper(value, -0x08); + } + + public override void SetBoxWallpaper(int box, int value) + { + if ((uint)box >= 18) + return; + value = AdjustWallpaper(value, 0x08); + Storage[GetBoxWallpaperOffset(box)] = (byte)value; + } + #endregion + + public override IReadOnlyList Inventory + { + get + { + InventoryPouch[] pouch = + { + new InventoryPouch4(InventoryType.Items, Legal.Pouch_Items_Pt, 999, 0x630), + new InventoryPouch4(InventoryType.KeyItems, Legal.Pouch_Key_Pt, 1, 0x8C4), + new InventoryPouch4(InventoryType.TMHMs, Legal.Pouch_TMHM_Pt, 99, 0x98C), + new InventoryPouch4(InventoryType.MailItems, Legal.Pouch_Mail_Pt, 999, 0xB1C), + new InventoryPouch4(InventoryType.Medicine, Legal.Pouch_Medicine_Pt, 999, 0xB4C), + new InventoryPouch4(InventoryType.Berries, Legal.Pouch_Berries_Pt, 999, 0xBEC), + new InventoryPouch4(InventoryType.Balls, Legal.Pouch_Ball_Pt, 999, 0xCEC), + new InventoryPouch4(InventoryType.BattleItems, Legal.Pouch_Battle_Pt, 999, 0xD28), + }; + return pouch.LoadAll(General); + } + set => value.SaveAll(General); + } + + public override int M { get => ReadUInt16LittleEndian(General.AsSpan(0x1280)); set => WriteUInt16LittleEndian(General.AsSpan(0x1280), (ushort)value); } + public override int X { get => ReadUInt16LittleEndian(General.AsSpan(0x1288)); set => WriteUInt16LittleEndian(General.AsSpan(0x1288), (ushort)(X2 = value)); } + public override int Y { get => ReadUInt16LittleEndian(General.AsSpan(0x128C)); set => WriteUInt16LittleEndian(General.AsSpan(0x128C), (ushort)(Y2 = value)); } + + public override Span Rival_Trash + { + get => General.AsSpan(0x27E8, OTLength * 2); + set { if (value.Length == OTLength * 2) value.CopyTo(General.AsSpan(0x27E8)); } + } + + public override int X2 { get => ReadUInt16LittleEndian(General.AsSpan(0x287E)); set => WriteUInt16LittleEndian(General.AsSpan(0x287E), (ushort)value); } + public override int Y2 { get => ReadUInt16LittleEndian(General.AsSpan(0x2882)); set => WriteUInt16LittleEndian(General.AsSpan(0x2882), (ushort)value); } + public override int Z { get => ReadUInt16LittleEndian(General.AsSpan(0x2886)); set => WriteUInt16LittleEndian(General.AsSpan(0x2886), (ushort)value); } + + public override uint SafariSeed { get => ReadUInt32LittleEndian(General.AsSpan(0x5660)); set => WriteUInt32LittleEndian(General.AsSpan(0x5660), value); } + public override uint SwarmSeed { get => ReadUInt32LittleEndian(General.AsSpan(0x5664)); set => WriteUInt32LittleEndian(General.AsSpan(0x5664), value); } + public override uint SwarmMaxCountModulo => 22; } diff --git a/PKHeX.Core/Saves/SAV4Sinnoh.cs b/PKHeX.Core/Saves/SAV4Sinnoh.cs index 2d688f135..fb0a88041 100644 --- a/PKHeX.Core/Saves/SAV4Sinnoh.cs +++ b/PKHeX.Core/Saves/SAV4Sinnoh.cs @@ -1,234 +1,233 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Abstract format for and +/// +public abstract class SAV4Sinnoh : SAV4 { - /// - /// Abstract format for and - /// - public abstract class SAV4Sinnoh : SAV4 + protected override int FooterSize => 0x14; + protected SAV4Sinnoh(int gSize, int sSize) : base(gSize, sSize) { } + protected SAV4Sinnoh(byte[] data, int gSize, int sSize, int sStart) : base(data, gSize, sSize, sStart) { } + + #region Storage + // u32 currentBox + // box{pk4[30}[18] + // g4str[18] boxNames + // byte[18] boxWallpapers + private const int BOX_COUNT = 18; + private const int BOX_SLOTS = 30; + private const int BOX_NAME_LEN = 40; // 20 characters + + private const int BOX_DATA_LEN = (BOX_SLOTS * PokeCrypto.SIZE_4STORED); // 0xFF0, no padding between boxes (to nearest 0x100) + private const int BOX_END = BOX_COUNT * BOX_DATA_LEN; // 18 * 0xFF0 + private const int BOX_NAME = 4 + BOX_END; // after box data + private const int BOX_WP = BOX_NAME + (BOX_COUNT * BOX_NAME_LEN); // 0x121B4; + private const int BOX_FLAGS = 18 + BOX_WP; // 0x121C6 + + public override int GetBoxOffset(int box) => 4 + (box * BOX_DATA_LEN); + private static int GetBoxNameOffset(int box) => BOX_NAME + (box * BOX_NAME_LEN); + protected override int GetBoxWallpaperOffset(int box) => BOX_WP + box; + + public override int CurrentBox // (align 32) { - protected override int FooterSize => 0x14; - protected SAV4Sinnoh(int gSize, int sSize) : base(gSize, sSize) { } - protected SAV4Sinnoh(byte[] data, int gSize, int sSize, int sStart) : base(data, gSize, sSize, sStart) { } - - #region Storage - // u32 currentBox - // box{pk4[30}[18] - // g4str[18] boxNames - // byte[18] boxWallpapers - private const int BOX_COUNT = 18; - private const int BOX_SLOTS = 30; - private const int BOX_NAME_LEN = 40; // 20 characters - - private const int BOX_DATA_LEN = (BOX_SLOTS * PokeCrypto.SIZE_4STORED); // 0xFF0, no padding between boxes (to nearest 0x100) - private const int BOX_END = BOX_COUNT * BOX_DATA_LEN; // 18 * 0xFF0 - private const int BOX_NAME = 4 + BOX_END; // after box data - private const int BOX_WP = BOX_NAME + (BOX_COUNT * BOX_NAME_LEN); // 0x121B4; - private const int BOX_FLAGS = 18 + BOX_WP; // 0x121C6 - - public override int GetBoxOffset(int box) => 4 + (box * BOX_DATA_LEN); - private static int GetBoxNameOffset(int box) => BOX_NAME + (box * BOX_NAME_LEN); - protected override int GetBoxWallpaperOffset(int box) => BOX_WP + box; - - public override int CurrentBox // (align 32) - { - get => Storage[0]; - set => Storage[0] = (byte)value; - } - - public override byte[] BoxFlags - { - get => new[] { Storage[BOX_FLAGS] }; - set => Storage[BOX_FLAGS] = value[0]; - } - - public override string GetBoxName(int box) => GetString(Storage.AsSpan(GetBoxNameOffset(box), BOX_NAME_LEN)); - - public override void SetBoxName(int box, string value) - { - const int maxlen = 8; - var span = Storage.AsSpan(GetBoxNameOffset(box), BOX_NAME_LEN); - SetString(span, value.AsSpan(), maxlen, StringConverterOption.ClearZero); - } - #endregion - - #region Poketch - protected int PoketchStart { private get; set; } - private byte PoketchPacked { get => General[PoketchStart]; set => General[PoketchStart] = value; } - - public bool PoketchEnabled { get => (PoketchPacked & 1) != 0; set => PoketchPacked = (byte)(value ? (PoketchPacked | 1) : (PoketchPacked & ~1)); } - public bool PoketchFlag1 { get => (PoketchPacked & 2) != 0; set => PoketchPacked = (byte)(value ? (PoketchPacked | 2) : (PoketchPacked & ~2)); } - public bool PoketchFlag2 { get => (PoketchPacked & 4) != 0; set => PoketchPacked = (byte)(value ? (PoketchPacked | 4) : (PoketchPacked & ~4)); } - - public PoketchColor PoketchColor - { - get => (PoketchColor) ((PoketchPacked >> 3) & 7); - set => PoketchPacked = (byte) ((PoketchPacked & 0xC7) | ((int) value << 3)); - } - - public bool PoketchFlag6 { get => (PoketchPacked & 0x40) != 0; set => PoketchPacked = (byte)(value ? (PoketchPacked | 0x40) : (PoketchPacked & ~0x40)); } - public bool PoketchFlag7 { get => (PoketchPacked & 0x80) != 0; set => PoketchPacked = (byte)(value ? (PoketchPacked | 0x80) : (PoketchPacked & ~0x80)); } - public byte Poketch1 { get => General[PoketchStart + 1]; set => General[PoketchStart + 1] = value; } - public sbyte CurrentPoketchApp { get => (sbyte)General[PoketchStart + 2]; set => General[PoketchStart + 2] = (byte)Math.Min((sbyte)PoketchApp.Alarm_Clock, value); } - - public bool GetPoketchAppUnlocked(PoketchApp index) - { - if (index > PoketchApp.Alarm_Clock) - throw new ArgumentOutOfRangeException(nameof(index)); - return General[PoketchStart + 3 + (int) index] != 0; - } - - public void SetPoketchAppUnlocked(PoketchApp index, bool value = true) - { - if (index > PoketchApp.Alarm_Clock) - throw new ArgumentOutOfRangeException(nameof(index)); - var b = value ? 1 : 0; - General[PoketchStart + 3 + (int)index] = (byte)b; - } - - // 8 bytes unk - - public uint PoketchStepCounter - { - get => ReadUInt32LittleEndian(General.AsSpan(PoketchStart + 0x24)); - set => WriteUInt32LittleEndian(General.AsSpan(PoketchStart + 0x24), value); - } - - // 2 bytes for alarm clock time setting - - public byte[] GetPoketchDotArtistData() => General.Slice(PoketchStart + 0x2A, 120); - - public void SetPoketchDotArtistData(ReadOnlySpan value) - { - if (value.Length != 120) - throw new ArgumentException($"Expected {120} bytes.", nameof(value.Length)); - value.CopyTo(General.AsSpan(PoketchStart + 0x2A)); - } - - // map marking stuff is at the end, unimportant - - #endregion - - #region Honey Trees - protected int OFS_HONEY; - protected const int HONEY_SIZE = 8; - - public HoneyTreeValue GetHoneyTree(int index) - { - if ((uint)index > 21) - throw new ArgumentOutOfRangeException(nameof(index)); - return new HoneyTreeValue(General.Slice(OFS_HONEY + (HONEY_SIZE * index), HONEY_SIZE)); - } - - public void SetHoneyTree(HoneyTreeValue tree, int index) - { - if (index <= 21) - SetData(General, tree.Data, OFS_HONEY + (HONEY_SIZE * index)); - } - - public MunchlaxTreeSet4 GetMunchlaxTrees() => CalculateMunchlaxTrees(TID, SID); - - public static MunchlaxTreeSet4 CalculateMunchlaxTrees(int tid, int sid) - { - int A = (tid >> 8) % 21; - int B = (tid & 0x00FF) % 21; - int C = (sid >> 8) % 21; - int D = (sid & 0x00FF) % 21; - - if (A == B) B = (B + 1) % 21; - if (A == C) C = (C + 1) % 21; - if (B == C) C = (C + 1) % 21; - if (A == D) D = (D + 1) % 21; - if (B == D) D = (D + 1) % 21; - if (C == D) D = (D + 1) % 21; - - return new(A, B, C, D); - } - - #endregion - - public int OFS_PoffinCase { get; protected set; } - - #region Underground - //Underground Scores - protected int OFS_UG_Stats; - public uint UG_PlayersMet { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats), value); } - public uint UG_Gifts { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x4)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x4), value); } - public uint UG_Spheres { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0xC)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0xC), value); } - public uint UG_Fossils { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x10)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x10), value); } - public uint UG_TrapsAvoided { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x18)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x18), value); } - public uint UG_TrapsTriggered { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x1C)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x1C), value); } - public uint UG_Flags { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x34)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x34), value); } - - //Underground Items - protected int OFS_UG_Items; - - public const int UG_POUCH_SIZE = 0x28; // 40 for each of the inventory pouches - - public byte[] GetUGI_Traps() => General.Slice(OFS_UG_Items, UG_POUCH_SIZE); - public void SetUGI_Traps(ReadOnlySpan value) => value.CopyTo(General.AsSpan(OFS_UG_Items)); - - public byte[] GetUGI_Goods() => General.Slice(OFS_UG_Items + 0x28, UG_POUCH_SIZE); - public void SetUGI_Goods(ReadOnlySpan value) => value.CopyTo(General.AsSpan(OFS_UG_Items + 0x28)); - - public byte[] GetUGI_Treasures() => General.Slice(OFS_UG_Items + 0x50, UG_POUCH_SIZE); - public void SetUGI_Treasures(ReadOnlySpan value) => value.CopyTo(General.AsSpan(OFS_UG_Items + 0x50)); - - // first 40 are the sphere type, last 40 are the sphere sizes - public byte[] GetUGI_Spheres() => General.Slice(OFS_UG_Items + 0x78, UG_POUCH_SIZE * 2); - public void SetUGI_Spheres(ReadOnlySpan value) => value.CopyTo(General.AsSpan(OFS_UG_Items + 0x78)); - - #endregion - - public abstract uint SafariSeed { get; set; } - public uint GetSafariIndex(int slot) => (SafariSeed >> (slot * 5)) & 0x1F; - public void SetSafariIndex(int slot, uint value) => SafariSeed = (SafariSeed & ~(0x1Fu << (slot * 5))) | (value << (slot * 5)); + get => Storage[0]; + set => Storage[0] = (byte)value; } - public enum PoketchColor + public override byte[] BoxFlags { - Green = 0, - Yellow = 1, - Orange = 2, - Red = 3, - Purple = 4, - Blue = 5, - Turquoise = 6, - White = 7, + get => new[] { Storage[BOX_FLAGS] }; + set => Storage[BOX_FLAGS] = value[0]; } - public enum PoketchApp + public override string GetBoxName(int box) => GetString(Storage.AsSpan(GetBoxNameOffset(box), BOX_NAME_LEN)); + + public override void SetBoxName(int box, string value) { - Digital_Watch, - Calculator, - Memo_Pad, - Pedometer, - Party, - Friendship_Checker, - Dowsing_Machine, - Berry_Searcher, - Daycare, - History, - Counter, - Analog_Watch, - Marking_Map, - Link_Searcher, - Coin_Toss, - Move_Tester, - Calendar, - Dot_Artist, - Roulette, - Trainer_Counter, - Kitchen_Timer, - Color_Changer, - Matchup_Checker, - Stopwatch, - Alarm_Clock, + const int maxlen = 8; + var span = Storage.AsSpan(GetBoxNameOffset(box), BOX_NAME_LEN); + SetString(span, value.AsSpan(), maxlen, StringConverterOption.ClearZero); + } + #endregion + + #region Poketch + protected int PoketchStart { private get; set; } + private byte PoketchPacked { get => General[PoketchStart]; set => General[PoketchStart] = value; } + + public bool PoketchEnabled { get => (PoketchPacked & 1) != 0; set => PoketchPacked = (byte)(value ? (PoketchPacked | 1) : (PoketchPacked & ~1)); } + public bool PoketchFlag1 { get => (PoketchPacked & 2) != 0; set => PoketchPacked = (byte)(value ? (PoketchPacked | 2) : (PoketchPacked & ~2)); } + public bool PoketchFlag2 { get => (PoketchPacked & 4) != 0; set => PoketchPacked = (byte)(value ? (PoketchPacked | 4) : (PoketchPacked & ~4)); } + + public PoketchColor PoketchColor + { + get => (PoketchColor) ((PoketchPacked >> 3) & 7); + set => PoketchPacked = (byte) ((PoketchPacked & 0xC7) | ((int) value << 3)); } - public readonly record struct MunchlaxTreeSet4(int Tree1, int Tree2, int Tree3, int Tree4) + public bool PoketchFlag6 { get => (PoketchPacked & 0x40) != 0; set => PoketchPacked = (byte)(value ? (PoketchPacked | 0x40) : (PoketchPacked & ~0x40)); } + public bool PoketchFlag7 { get => (PoketchPacked & 0x80) != 0; set => PoketchPacked = (byte)(value ? (PoketchPacked | 0x80) : (PoketchPacked & ~0x80)); } + public byte Poketch1 { get => General[PoketchStart + 1]; set => General[PoketchStart + 1] = value; } + public sbyte CurrentPoketchApp { get => (sbyte)General[PoketchStart + 2]; set => General[PoketchStart + 2] = (byte)Math.Min((sbyte)PoketchApp.Alarm_Clock, value); } + + public bool GetPoketchAppUnlocked(PoketchApp index) { - public int[] ToArray() => new[] { Tree1, Tree2, Tree3, Tree4 }; + if (index > PoketchApp.Alarm_Clock) + throw new ArgumentOutOfRangeException(nameof(index)); + return General[PoketchStart + 3 + (int) index] != 0; } + + public void SetPoketchAppUnlocked(PoketchApp index, bool value = true) + { + if (index > PoketchApp.Alarm_Clock) + throw new ArgumentOutOfRangeException(nameof(index)); + var b = value ? 1 : 0; + General[PoketchStart + 3 + (int)index] = (byte)b; + } + + // 8 bytes unk + + public uint PoketchStepCounter + { + get => ReadUInt32LittleEndian(General.AsSpan(PoketchStart + 0x24)); + set => WriteUInt32LittleEndian(General.AsSpan(PoketchStart + 0x24), value); + } + + // 2 bytes for alarm clock time setting + + public byte[] GetPoketchDotArtistData() => General.Slice(PoketchStart + 0x2A, 120); + + public void SetPoketchDotArtistData(ReadOnlySpan value) + { + if (value.Length != 120) + throw new ArgumentException($"Expected {120} bytes.", nameof(value.Length)); + value.CopyTo(General.AsSpan(PoketchStart + 0x2A)); + } + + // map marking stuff is at the end, unimportant + + #endregion + + #region Honey Trees + protected int OFS_HONEY; + protected const int HONEY_SIZE = 8; + + public HoneyTreeValue GetHoneyTree(int index) + { + if ((uint)index > 21) + throw new ArgumentOutOfRangeException(nameof(index)); + return new HoneyTreeValue(General.Slice(OFS_HONEY + (HONEY_SIZE * index), HONEY_SIZE)); + } + + public void SetHoneyTree(HoneyTreeValue tree, int index) + { + if (index <= 21) + SetData(General, tree.Data, OFS_HONEY + (HONEY_SIZE * index)); + } + + public MunchlaxTreeSet4 GetMunchlaxTrees() => CalculateMunchlaxTrees(TID, SID); + + public static MunchlaxTreeSet4 CalculateMunchlaxTrees(int tid, int sid) + { + int A = (tid >> 8) % 21; + int B = (tid & 0x00FF) % 21; + int C = (sid >> 8) % 21; + int D = (sid & 0x00FF) % 21; + + if (A == B) B = (B + 1) % 21; + if (A == C) C = (C + 1) % 21; + if (B == C) C = (C + 1) % 21; + if (A == D) D = (D + 1) % 21; + if (B == D) D = (D + 1) % 21; + if (C == D) D = (D + 1) % 21; + + return new(A, B, C, D); + } + + #endregion + + public int OFS_PoffinCase { get; protected set; } + + #region Underground + //Underground Scores + protected int OFS_UG_Stats; + public uint UG_PlayersMet { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats), value); } + public uint UG_Gifts { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x4)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x4), value); } + public uint UG_Spheres { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0xC)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0xC), value); } + public uint UG_Fossils { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x10)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x10), value); } + public uint UG_TrapsAvoided { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x18)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x18), value); } + public uint UG_TrapsTriggered { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x1C)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x1C), value); } + public uint UG_Flags { get => ReadUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x34)); set => WriteUInt32LittleEndian(General.AsSpan(OFS_UG_Stats + 0x34), value); } + + //Underground Items + protected int OFS_UG_Items; + + public const int UG_POUCH_SIZE = 0x28; // 40 for each of the inventory pouches + + public byte[] GetUGI_Traps() => General.Slice(OFS_UG_Items, UG_POUCH_SIZE); + public void SetUGI_Traps(ReadOnlySpan value) => value.CopyTo(General.AsSpan(OFS_UG_Items)); + + public byte[] GetUGI_Goods() => General.Slice(OFS_UG_Items + 0x28, UG_POUCH_SIZE); + public void SetUGI_Goods(ReadOnlySpan value) => value.CopyTo(General.AsSpan(OFS_UG_Items + 0x28)); + + public byte[] GetUGI_Treasures() => General.Slice(OFS_UG_Items + 0x50, UG_POUCH_SIZE); + public void SetUGI_Treasures(ReadOnlySpan value) => value.CopyTo(General.AsSpan(OFS_UG_Items + 0x50)); + + // first 40 are the sphere type, last 40 are the sphere sizes + public byte[] GetUGI_Spheres() => General.Slice(OFS_UG_Items + 0x78, UG_POUCH_SIZE * 2); + public void SetUGI_Spheres(ReadOnlySpan value) => value.CopyTo(General.AsSpan(OFS_UG_Items + 0x78)); + + #endregion + + public abstract uint SafariSeed { get; set; } + public uint GetSafariIndex(int slot) => (SafariSeed >> (slot * 5)) & 0x1F; + public void SetSafariIndex(int slot, uint value) => SafariSeed = (SafariSeed & ~(0x1Fu << (slot * 5))) | (value << (slot * 5)); +} + +public enum PoketchColor +{ + Green = 0, + Yellow = 1, + Orange = 2, + Red = 3, + Purple = 4, + Blue = 5, + Turquoise = 6, + White = 7, +} + +public enum PoketchApp +{ + Digital_Watch, + Calculator, + Memo_Pad, + Pedometer, + Party, + Friendship_Checker, + Dowsing_Machine, + Berry_Searcher, + Daycare, + History, + Counter, + Analog_Watch, + Marking_Map, + Link_Searcher, + Coin_Toss, + Move_Tester, + Calendar, + Dot_Artist, + Roulette, + Trainer_Counter, + Kitchen_Timer, + Color_Changer, + Matchup_Checker, + Stopwatch, + Alarm_Clock, +} + +public readonly record struct MunchlaxTreeSet4(int Tree1, int Tree2, int Tree3, int Tree4) +{ + public int[] ToArray() => new[] { Tree1, Tree2, Tree3, Tree4 }; } diff --git a/PKHeX.Core/Saves/SAV5.cs b/PKHeX.Core/Saves/SAV5.cs index f6e644a7e..005b23cfb 100644 --- a/PKHeX.Core/Saves/SAV5.cs +++ b/PKHeX.Core/Saves/SAV5.cs @@ -2,240 +2,238 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 object. +/// +public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37 { - /// - /// Generation 5 object. - /// - public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlag37 + protected override PKM GetPKM(byte[] data) => new PK5(data); + protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data); + + protected internal override string ShortSummary => $"{OT} ({(GameVersion)Game}) - {PlayTimeString}"; + public override string Extension => ".sav"; + + public override IReadOnlyList HeldItems => Legal.HeldItems_BW; + protected override int SIZE_STORED => PokeCrypto.SIZE_5STORED; + protected override int SIZE_PARTY => PokeCrypto.SIZE_5PARTY; + public override PKM BlankPKM => new PK5(); + public override Type PKMType => typeof(PK5); + + public override int BoxCount => 24; + public override int MaxEV => 255; + public override int Generation => 5; + public override EntityContext Context => EntityContext.Gen5; + public override int OTLength => 7; + public override int NickLength => 10; + protected override int GiftCountMax => 12; + public abstract int EventFlagCount { get; } + public abstract int EventWorkCount { get; } + protected abstract int EventFlagOffset { get; } + protected abstract int EventWorkOffset { get; } + + public override int MaxMoveID => Legal.MaxMoveID_5; + public override int MaxSpeciesID => Legal.MaxSpeciesID_5; + public override int MaxAbilityID => Legal.MaxAbilityID_5; + public override int MaxBallID => Legal.MaxBallID_5; + public override int MaxGameID => Legal.MaxGameID_5; // B2 + + protected SAV5(int size) : base(size) { - protected override PKM GetPKM(byte[] data) => new PK5(data); - protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data); + Initialize(); + ClearBoxes(); + } - protected internal override string ShortSummary => $"{OT} ({(GameVersion)Game}) - {PlayTimeString}"; - public override string Extension => ".sav"; + protected SAV5(byte[] data) : base(data) + { + Initialize(); + } - public override IReadOnlyList HeldItems => Legal.HeldItems_BW; - protected override int SIZE_STORED => PokeCrypto.SIZE_5STORED; - protected override int SIZE_PARTY => PokeCrypto.SIZE_5PARTY; - public override PKM BlankPKM => new PK5(); - public override Type PKMType => typeof(PK5); + public override GameVersion Version + { + get => (GameVersion)Game; + protected set => Game = (int)value; + } - public override int BoxCount => 24; - public override int MaxEV => 255; - public override int Generation => 5; - public override EntityContext Context => EntityContext.Gen5; - public override int OTLength => 7; - public override int NickLength => 10; - protected override int GiftCountMax => 12; - public abstract int EventFlagCount { get; } - public abstract int EventWorkCount { get; } - protected abstract int EventFlag { get; } - protected abstract int EventWork { get; } + private void Initialize() + { + Box = 0x400; + Party = 0x18E00; + AdventureInfo = 0x1D900; + } - public override int MaxMoveID => Legal.MaxMoveID_5; - public override int MaxSpeciesID => Legal.MaxSpeciesID_5; - public override int MaxAbilityID => Legal.MaxAbilityID_5; - public override int MaxBallID => Legal.MaxBallID_5; - public override int MaxGameID => Legal.MaxGameID_5; // B2 + // Blocks & Offsets + protected override void SetChecksums() => AllBlocks.SetChecksums(Data); + public override bool ChecksumsValid => AllBlocks.GetChecksumsValid(Data); + public override string ChecksumInfo => AllBlocks.GetChecksumInfo(Data); - protected SAV5(int size) : base(size) + protected int CGearInfoOffset; + protected int CGearDataOffset; + protected int EntreeForestOffset; + private int AdventureInfo; + public abstract int GTS { get; } + public abstract int Fused { get; } + public int PGL => AllBlocks[35].Offset + 8; // Dream World Upload + + // Daycare + public override int DaycareSeedSize => Daycare5.DaycareSeedSize; + public override bool? IsDaycareOccupied(int loc, int slot) => Daycare.IsOccupied(slot); + public override int GetDaycareSlotOffset(int loc, int slot) => Daycare.GetPKMOffset(slot); + public override uint? GetDaycareEXP(int loc, int slot) => Daycare.GetEXP(slot); + public override void SetDaycareEXP(int loc, int slot, uint EXP) => Daycare.SetEXP(slot, EXP); + public override void SetDaycareOccupied(int loc, int slot, bool occupied) => Daycare.SetOccupied(slot, occupied); + public override void SetDaycareRNGSeed(int loc, string seed) => Daycare.SetSeed(seed); + + // Storage + public override int PartyCount + { + get => Data[Party + 4]; + protected set => Data[Party + 4] = (byte)value; + } + + public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30) + (box * 0x10); + public override int GetPartyOffset(int slot) => Party + 8 + (SIZE_PARTY * slot); + + protected override int GetBoxWallpaperOffset(int box) => BoxLayout.GetBoxWallpaperOffset(box); + public override int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box); + public override void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value); + public override string GetBoxName(int box) => BoxLayout[box]; + public override void SetBoxName(int box, string value) => BoxLayout[box] = value; + public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = value; } + + protected int BattleBoxOffset; + + public bool BattleBoxLocked + { + get => Data[BattleBoxOffset + 0x358] != 0; // wifi/live + set => Data[BattleBoxOffset + 0x358] = value ? (byte)1 : (byte)0; + } + + protected override void SetPKM(PKM pk, bool isParty = false) + { + var pk5 = (PK5)pk; + // Apply to this Save File + DateTime Date = DateTime.Now; + if (pk5.Trade(OT, TID, SID, Gender, Date.Day, Date.Month, Date.Year)) + pk.RefreshChecksum(); + } + + // Player Data + public override string OT { get => PlayerData.OT; set => PlayerData.OT = value; } + public override int TID { get => PlayerData.TID; set => PlayerData.TID = value; } + public override int SID { get => PlayerData.SID; set => PlayerData.SID = value; } + public override int Language { get => PlayerData.Language; set => PlayerData.Language = value; } + public override int Game { get => PlayerData.Game; set => PlayerData.Game = value; } + public override int Gender { get => PlayerData.Gender; set => PlayerData.Gender = value; } + public override int PlayedHours { get => PlayerData.PlayedHours; set => PlayerData.PlayedHours = value; } + public override int PlayedMinutes { get => PlayerData.PlayedMinutes; set => PlayerData.PlayedMinutes = value; } + public override int PlayedSeconds { get => PlayerData.PlayedSeconds; set => PlayerData.PlayedSeconds = value; } + public override uint Money { get => Misc.Money; set => Misc.Money = value; } + public override uint SecondsToStart { get => ReadUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x34)); set => WriteUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x34), value); } + public override uint SecondsToFame { get => ReadUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x3C)); set => WriteUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x3C), value); } + public override MysteryGiftAlbum GiftAlbum { get => Mystery.GiftAlbum; set => Mystery.GiftAlbum = (EncryptedMysteryGiftAlbum)value; } + public override IReadOnlyList Inventory { get => Items.Inventory; set => Items.Inventory = value; } + + protected override void SetDex(PKM pk) => Zukan.SetDex(pk); + public override bool GetCaught(int species) => Zukan.GetCaught(species); + public override bool GetSeen(int species) => Zukan.GetSeen(species); + + public sealed override string GetString(ReadOnlySpan data) => StringConverter5.GetString(data); + + public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter5.SetString(destBuffer, value, maxLength, option); + } + + public bool GetEventFlag(int flagNumber) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); + return GetFlag(EventFlagOffset + (flagNumber >> 3), flagNumber & 7); + } + + public void SetEventFlag(int flagNumber, bool value) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); + SetFlag(EventFlagOffset + (flagNumber >> 3), flagNumber & 7, value); + } + + public ushort GetWork(int index) => ReadUInt16LittleEndian(Data.AsSpan(EventWorkOffset + (index * 2))); + public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(EventWorkOffset)[(index * 2)..], value); + + // DLC + private int CGearSkinInfoOffset => CGearInfoOffset + (this is SAV5B2W2 ? 0x10 : 0) + 0x24; + + private bool CGearSkinPresent + { + get => Data[CGearSkinInfoOffset + 2] == 1; + set => Data[CGearSkinInfoOffset + 2] = Data[PlayerData.Offset + (this is SAV5B2W2 ? 0x6C : 0x54)] = value ? (byte)1 : (byte)0; + } + + public byte[] CGearSkinData + { + get { - Initialize(); - ClearBoxes(); + if (CGearSkinPresent) + return Data.AsSpan(CGearDataOffset, CGearBackground.SIZE_CGB).ToArray(); + return new byte[CGearBackground.SIZE_CGB]; } - - protected SAV5(byte[] data) : base(data) + set { - Initialize(); - } + SetData(value, CGearDataOffset); - public override GameVersion Version - { - get => (GameVersion)Game; - protected set => Game = (int)value; - } + ushort chk = Checksums.CRC16_CCITT(value); + var footer = Data.AsSpan(CGearDataOffset + value.Length); - private void Initialize() - { - Box = 0x400; - Party = 0x18E00; - AdventureInfo = 0x1D900; - } + WriteUInt16LittleEndian(footer, 1); // block updated once + WriteUInt16LittleEndian(footer[2..], chk); // checksum + WriteUInt16LittleEndian(footer[0x100..], chk); // second checksum - // Blocks & Offsets - protected override void SetChecksums() => AllBlocks.SetChecksums(Data); - public override bool ChecksumsValid => AllBlocks.GetChecksumsValid(Data); - public override string ChecksumInfo => AllBlocks.GetChecksumInfo(Data); + Span dlcfooter = stackalloc byte[] { 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x27, 0x00, 0x00, 0x27, 0x35, 0x05, 0x31, 0x00, 0x00 }; + dlcfooter.CopyTo(footer[0x102..]); - protected int CGearInfoOffset; - protected int CGearDataOffset; - protected int EntreeForestOffset; - private int AdventureInfo; - public abstract int GTS { get; } - public abstract int Fused { get; } - public int PGL => AllBlocks[35].Offset + 8; // Dream World Upload + ushort skinchkval = Checksums.CRC16_CCITT(footer[0x100..0x104]); + WriteUInt16LittleEndian(footer[0x112..], skinchkval); - // Daycare - public override int DaycareSeedSize => Daycare5.DaycareSeedSize; - public override bool? IsDaycareOccupied(int loc, int slot) => Daycare.IsOccupied(slot); - public override int GetDaycareSlotOffset(int loc, int slot) => Daycare.GetPKMOffset(slot); - public override uint? GetDaycareEXP(int loc, int slot) => Daycare.GetEXP(slot); - public override string GetDaycareRNGSeed(int loc) => Daycare.GetSeed()?.ToString("X16") ?? string.Empty; - public override void SetDaycareEXP(int loc, int slot, uint EXP) => Daycare.SetEXP(slot, EXP); - public override void SetDaycareOccupied(int loc, int slot, bool occupied) => Daycare.SetOccupied(slot, occupied); - public override void SetDaycareRNGSeed(int loc, string seed) => Daycare.SetSeed(seed); + // Indicate in the save file that data is present + WriteUInt16LittleEndian(Data.AsSpan(0x19438), 0xC21E); - // Storage - public override int PartyCount - { - get => Data[Party + 4]; - protected set => Data[Party + 4] = (byte)value; - } + WriteUInt16LittleEndian(Data.AsSpan(CGearSkinInfoOffset), chk); + CGearSkinPresent = true; - public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30) + (box * 0x10); - public override int GetPartyOffset(int slot) => Party + 8 + (SIZE_PARTY * slot); - - protected override int GetBoxWallpaperOffset(int box) => BoxLayout.GetBoxWallpaperOffset(box); - public override int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box); - public override void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value); - public override string GetBoxName(int box) => BoxLayout[box]; - public override void SetBoxName(int box, string value) => BoxLayout[box] = value; - public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = value; } - - protected int BattleBoxOffset; - - public bool BattleBoxLocked - { - get => Data[BattleBoxOffset + 0x358] != 0; // wifi/live - set => Data[BattleBoxOffset + 0x358] = value ? (byte)1 : (byte)0; - } - - protected override void SetPKM(PKM pkm, bool isParty = false) - { - var pk5 = (PK5)pkm; - // Apply to this Save File - DateTime Date = DateTime.Now; - if (pk5.Trade(OT, TID, SID, Gender, Date.Day, Date.Month, Date.Year)) - pkm.RefreshChecksum(); - } - - // Player Data - public override string OT { get => PlayerData.OT; set => PlayerData.OT = value; } - public override int TID { get => PlayerData.TID; set => PlayerData.TID = value; } - public override int SID { get => PlayerData.SID; set => PlayerData.SID = value; } - public override int Language { get => PlayerData.Language; set => PlayerData.Language = value; } - public override int Game { get => PlayerData.Game; set => PlayerData.Game = value; } - public override int Gender { get => PlayerData.Gender; set => PlayerData.Gender = value; } - public override int PlayedHours { get => PlayerData.PlayedHours; set => PlayerData.PlayedHours = value; } - public override int PlayedMinutes { get => PlayerData.PlayedMinutes; set => PlayerData.PlayedMinutes = value; } - public override int PlayedSeconds { get => PlayerData.PlayedSeconds; set => PlayerData.PlayedSeconds = value; } - public override uint Money { get => Misc.Money; set => Misc.Money = value; } - public override uint SecondsToStart { get => ReadUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x34)); set => WriteUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x34), value); } - public override uint SecondsToFame { get => ReadUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x3C)); set => WriteUInt32LittleEndian(Data.AsSpan(AdventureInfo + 0x3C), value); } - public override MysteryGiftAlbum GiftAlbum { get => Mystery.GiftAlbum; set => Mystery.GiftAlbum = (EncryptedMysteryGiftAlbum)value; } - public override IReadOnlyList Inventory { get => Items.Inventory; set => Items.Inventory = value; } - - protected override void SetDex(PKM pkm) => Zukan.SetDex(pkm); - public override bool GetCaught(int species) => Zukan.GetCaught(species); - public override bool GetSeen(int species) => Zukan.GetSeen(species); - - public sealed override string GetString(ReadOnlySpan data) => StringConverter5.GetString(data); - - public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter5.SetString(destBuffer, value, maxLength, option); - } - - public bool GetEventFlag(int flagNumber) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); - return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); - } - - public void SetEventFlag(int flagNumber, bool value) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); - SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); - } - - public ushort GetWork(int index) => ReadUInt16LittleEndian(Data.AsSpan(EventWork + (index * 2))); - public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(EventWork)[(index * 2)..], value); - - // DLC - private int CGearSkinInfoOffset => CGearInfoOffset + (this is SAV5B2W2 ? 0x10 : 0) + 0x24; - - private bool CGearSkinPresent - { - get => Data[CGearSkinInfoOffset + 2] == 1; - set => Data[CGearSkinInfoOffset + 2] = Data[PlayerData.Offset + (this is SAV5B2W2 ? 0x6C : 0x54)] = value ? (byte)1 : (byte)0; - } - - public byte[] CGearSkinData - { - get - { - if (CGearSkinPresent) - return Data.AsSpan(CGearDataOffset, CGearBackground.SIZE_CGB).ToArray(); - return new byte[CGearBackground.SIZE_CGB]; - } - set - { - SetData(value, CGearDataOffset); - - ushort chk = Checksums.CRC16_CCITT(value); - var footer = Data.AsSpan(CGearDataOffset + value.Length); - - WriteUInt16LittleEndian(footer, 1); // block updated once - WriteUInt16LittleEndian(footer[2..], chk); // checksum - WriteUInt16LittleEndian(footer[0x100..], chk); // second checksum - - Span dlcfooter = stackalloc byte[] { 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x27, 0x00, 0x00, 0x27, 0x35, 0x05, 0x31, 0x00, 0x00 }; - dlcfooter.CopyTo(footer[0x102..]); - - ushort skinchkval = Checksums.CRC16_CCITT(footer[0x100..0x104]); - WriteUInt16LittleEndian(footer[0x112..], skinchkval); - - // Indicate in the save file that data is present - WriteUInt16LittleEndian(Data.AsSpan(0x19438), 0xC21E); - - WriteUInt16LittleEndian(Data.AsSpan(CGearSkinInfoOffset), chk); - CGearSkinPresent = true; - - State.Edited = true; - } - } - - public EntreeForest EntreeData - { - get => new(GetData(EntreeForestOffset, EntreeForest.SIZE)); - set => SetData(value.Write(), EntreeForestOffset); - } - - public abstract IReadOnlyList AllBlocks { get; } - public abstract MyItem Items { get; } - public abstract Zukan5 Zukan { get; } - public abstract Misc5 Misc { get; } - public abstract MysteryBlock5 Mystery { get; } - public abstract Daycare5 Daycare { get; } - public abstract BoxLayout5 BoxLayout { get; } - public abstract PlayerData5 PlayerData { get; } - public abstract BattleSubway5 BattleSubway { get; } - public abstract Entralink5 Entralink { get; } - public abstract Musical5 Musical { get; } - public abstract Encount5 Encount { get; } - - public static int GetMailOffset(int index) => (index * Mail5.SIZE) + 0x1DD00; - public byte[] GetMailData(int offset) => GetData(offset, Mail5.SIZE); - public int GetBattleBoxSlot(int slot) => BattleBoxOffset + (slot * SIZE_STORED); - - public Mail GetMail(int mailIndex) - { - int ofs = GetMailOffset(mailIndex); - var data = GetMailData(ofs); - return new Mail5(data, ofs); + State.Edited = true; } } + + public EntreeForest EntreeData + { + get => new(GetData(EntreeForestOffset, EntreeForest.SIZE)); + set => SetData(value.Write(), EntreeForestOffset); + } + + public abstract IReadOnlyList AllBlocks { get; } + public abstract MyItem Items { get; } + public abstract Zukan5 Zukan { get; } + public abstract Misc5 Misc { get; } + public abstract MysteryBlock5 Mystery { get; } + public abstract Daycare5 Daycare { get; } + public abstract BoxLayout5 BoxLayout { get; } + public abstract PlayerData5 PlayerData { get; } + public abstract BattleSubway5 BattleSubway { get; } + public abstract Entralink5 Entralink { get; } + public abstract Musical5 Musical { get; } + public abstract Encount5 Encount { get; } + + public static int GetMailOffset(int index) => (index * Mail5.SIZE) + 0x1DD00; + public byte[] GetMailData(int offset) => GetData(offset, Mail5.SIZE); + public int GetBattleBoxSlot(int slot) => BattleBoxOffset + (slot * SIZE_STORED); + + public MailDetail GetMail(int mailIndex) + { + int ofs = GetMailOffset(mailIndex); + var data = GetMailData(ofs); + return new Mail5(data, ofs); + } } diff --git a/PKHeX.Core/Saves/SAV5B2W2.cs b/PKHeX.Core/Saves/SAV5B2W2.cs index c429186f0..ba8ecab50 100644 --- a/PKHeX.Core/Saves/SAV5B2W2.cs +++ b/PKHeX.Core/Saves/SAV5B2W2.cs @@ -1,73 +1,74 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 object for . +/// +/// +public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2 { - /// - /// Generation 5 object for . - /// - /// - public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2 + public SAV5B2W2() : base(SaveUtil.SIZE_G5RAW) { - public SAV5B2W2() : base(SaveUtil.SIZE_G5RAW) - { - Blocks = new SaveBlockAccessor5B2W2(this); - Initialize(); - } - - public SAV5B2W2(byte[] data) : base(data) - { - Blocks = new SaveBlockAccessor5B2W2(this); - Initialize(); - } - - public override PersonalTable Personal => PersonalTable.B2W2; - public SaveBlockAccessor5B2W2 Blocks { get; } - protected override SaveFile CloneInternal() => new SAV5B2W2((byte[]) Data.Clone()); - public override int EventWorkCount => 0x1AF; // this doesn't seem right? - public override int EventFlagCount => 0xBF8; - protected override int EventWork => 0x1FF00; - protected override int EventFlag => EventWork + 0x35E; - public override int MaxItemID => Legal.MaxItemID_5_B2W2; - - private void Initialize() - { - BattleBoxOffset = 0x20900; - CGearInfoOffset = 0x1C000; - CGearDataOffset = 0x52800; - EntreeForestOffset = 0x22A00; - PokeDex = Blocks.Zukan.PokeDex; - WondercardData = Blocks.Mystery.Offset; - DaycareOffset = Blocks.Daycare.Offset; - } - - public override IReadOnlyList AllBlocks => Blocks.BlockInfo; - public override MyItem Items => Blocks.Items; - public override Zukan5 Zukan => Blocks.Zukan; - public override Misc5 Misc => Blocks.Misc; - public override MysteryBlock5 Mystery => Blocks.Mystery; - public override Daycare5 Daycare => Blocks.Daycare; - public override BoxLayout5 BoxLayout => Blocks.BoxLayout; - public override PlayerData5 PlayerData => Blocks.PlayerData; - public override BattleSubway5 BattleSubway => Blocks.BattleSubway; - public override Entralink5 Entralink => Blocks.Entralink; - public override Musical5 Musical => Blocks.Musical; - public override Encount5 Encount => Blocks.Encount; - public FestaBlock5 Festa => Blocks.Festa; - public PWTBlock5 PWT => Blocks.PWT; - public override int Fused => 0x1FA00 + sizeof(uint); - public override int GTS => 0x20400; - - public string Rival - { - get => GetString(Rival_Trash); - set => SetString(Rival_Trash, value.AsSpan(), OTLength, StringConverterOption.ClearZero); - } - - public Span Rival_Trash - { - get => Data.AsSpan(0x23BA4, OTLength * 2); - set { if (value.Length == OTLength * 2) value.CopyTo(Data.AsSpan(0x23BA4)); } - } + Blocks = new SaveBlockAccessor5B2W2(this); + Initialize(); } + + public SAV5B2W2(byte[] data) : base(data) + { + Blocks = new SaveBlockAccessor5B2W2(this); + Initialize(); + } + + public override PersonalTable Personal => PersonalTable.B2W2; + public SaveBlockAccessor5B2W2 Blocks { get; } + protected override SaveFile CloneInternal() => new SAV5B2W2((byte[]) Data.Clone()); + public override int EventWorkCount => 0x1AF; // this doesn't seem right? + public override int EventFlagCount => 0xBF8; + protected override int EventWorkOffset => 0x1FF00; + protected override int EventFlagOffset => EventWorkOffset + 0x35E; + public override int MaxItemID => Legal.MaxItemID_5_B2W2; + + private void Initialize() + { + BattleBoxOffset = 0x20900; + CGearInfoOffset = 0x1C000; + CGearDataOffset = 0x52800; + EntreeForestOffset = 0x22A00; + PokeDex = Blocks.Zukan.PokeDex; + WondercardData = Blocks.Mystery.Offset; + DaycareOffset = Blocks.Daycare.Offset; + } + + public override IReadOnlyList AllBlocks => Blocks.BlockInfo; + public override MyItem Items => Blocks.Items; + public override Zukan5 Zukan => Blocks.Zukan; + public override Misc5 Misc => Blocks.Misc; + public override MysteryBlock5 Mystery => Blocks.Mystery; + public override Daycare5 Daycare => Blocks.Daycare; + public override BoxLayout5 BoxLayout => Blocks.BoxLayout; + public override PlayerData5 PlayerData => Blocks.PlayerData; + public override BattleSubway5 BattleSubway => Blocks.BattleSubway; + public override Entralink5 Entralink => Blocks.Entralink; + public override Musical5 Musical => Blocks.Musical; + public override Encount5 Encount => Blocks.Encount; + public FestaBlock5 Festa => Blocks.Festa; + public PWTBlock5 PWT => Blocks.PWT; + public override int Fused => 0x1FA00 + sizeof(uint); + public override int GTS => 0x20400; + + public string Rival + { + get => GetString(Rival_Trash); + set => SetString(Rival_Trash, value.AsSpan(), OTLength, StringConverterOption.ClearZero); + } + + public Span Rival_Trash + { + get => Data.AsSpan(0x23BA4, OTLength * 2); + set { if (value.Length == OTLength * 2) value.CopyTo(Data.AsSpan(0x23BA4)); } + } + + public override string GetDaycareRNGSeed(int loc) => $"{Daycare.GetSeed()!:X16}"; } diff --git a/PKHeX.Core/Saves/SAV5BW.cs b/PKHeX.Core/Saves/SAV5BW.cs index 9d13a7cac..e7916a422 100644 --- a/PKHeX.Core/Saves/SAV5BW.cs +++ b/PKHeX.Core/Saves/SAV5BW.cs @@ -1,58 +1,57 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 object for . +/// +/// +public sealed class SAV5BW : SAV5 { - /// - /// Generation 5 object for . - /// - /// - public sealed class SAV5BW : SAV5 + public SAV5BW() : base(SaveUtil.SIZE_G5RAW) { - public SAV5BW() : base(SaveUtil.SIZE_G5RAW) - { - Blocks = new SaveBlockAccessor5BW(this); - Initialize(); - } - - public SAV5BW(byte[] data) : base(data) - { - Blocks = new SaveBlockAccessor5BW(this); - Initialize(); - } - - public override PersonalTable Personal => PersonalTable.BW; - public SaveBlockAccessor5BW Blocks { get; } - protected override SaveFile CloneInternal() => new SAV5BW((byte[])Data.Clone()); - public override int EventWorkCount => 0x13E; - public override int EventFlagCount => 0xB60; - protected override int EventWork => 0x20100; - protected override int EventFlag => EventWork + 0x27C; - public override int MaxItemID => Legal.MaxItemID_5_BW; - - private void Initialize() - { - BattleBoxOffset = 0x20A00; - CGearInfoOffset = 0x1C000; - CGearDataOffset = 0x52000; - EntreeForestOffset = 0x22C00; - PokeDex = Blocks.Zukan.PokeDex; - WondercardData = Blocks.Mystery.Offset; - DaycareOffset = Blocks.Daycare.Offset; - } - - public override IReadOnlyList AllBlocks => Blocks.BlockInfo; - public override MyItem Items => Blocks.Items; - public override Zukan5 Zukan => Blocks.Zukan; - public override Misc5 Misc => Blocks.Misc; - public override MysteryBlock5 Mystery => Blocks.Mystery; - public override Daycare5 Daycare => Blocks.Daycare; - public override BoxLayout5 BoxLayout => Blocks.BoxLayout; - public override PlayerData5 PlayerData => Blocks.PlayerData; - public override BattleSubway5 BattleSubway => Blocks.BattleSubway; - public override Entralink5 Entralink => Blocks.Entralink; - public override Musical5 Musical => Blocks.Musical; - public override Encount5 Encount => Blocks.Encount; - public override int Fused => int.MinValue; - public override int GTS => 0x20500; + Blocks = new SaveBlockAccessor5BW(this); + Initialize(); } + + public SAV5BW(byte[] data) : base(data) + { + Blocks = new SaveBlockAccessor5BW(this); + Initialize(); + } + + public override PersonalTable Personal => PersonalTable.BW; + public SaveBlockAccessor5BW Blocks { get; } + protected override SaveFile CloneInternal() => new SAV5BW((byte[])Data.Clone()); + public override int EventWorkCount => 0x13E; + public override int EventFlagCount => 0xB60; + protected override int EventWorkOffset => 0x20100; + protected override int EventFlagOffset => EventWorkOffset + 0x27C; + public override int MaxItemID => Legal.MaxItemID_5_BW; + + private void Initialize() + { + BattleBoxOffset = 0x20A00; + CGearInfoOffset = 0x1C000; + CGearDataOffset = 0x52000; + EntreeForestOffset = 0x22C00; + PokeDex = Blocks.Zukan.PokeDex; + WondercardData = Blocks.Mystery.Offset; + DaycareOffset = Blocks.Daycare.Offset; + } + + public override IReadOnlyList AllBlocks => Blocks.BlockInfo; + public override MyItem Items => Blocks.Items; + public override Zukan5 Zukan => Blocks.Zukan; + public override Misc5 Misc => Blocks.Misc; + public override MysteryBlock5 Mystery => Blocks.Mystery; + public override Daycare5 Daycare => Blocks.Daycare; + public override BoxLayout5 BoxLayout => Blocks.BoxLayout; + public override PlayerData5 PlayerData => Blocks.PlayerData; + public override BattleSubway5 BattleSubway => Blocks.BattleSubway; + public override Entralink5 Entralink => Blocks.Entralink; + public override Musical5 Musical => Blocks.Musical; + public override Encount5 Encount => Blocks.Encount; + public override int Fused => int.MinValue; + public override int GTS => 0x20500; } diff --git a/PKHeX.Core/Saves/SAV6.cs b/PKHeX.Core/Saves/SAV6.cs index d15c3fbd7..0d6d58f96 100644 --- a/PKHeX.Core/Saves/SAV6.cs +++ b/PKHeX.Core/Saves/SAV6.cs @@ -2,214 +2,213 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 6 object. +/// +public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IRegionOrigin, IGameSync, IEventFlag37 { - /// - /// Generation 6 object. - /// - public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IRegionOrigin, IGameSync, IEventFlag37 + // Save Data Attributes + protected internal override string ShortSummary => $"{OT} ({Version}) - {Played.LastSavedTime}"; + public override string Extension => string.Empty; + + protected SAV6(byte[] data, int biOffset) : base(data, biOffset) { } + protected SAV6(int size, int biOffset) : base(size, biOffset) { } + + // Configuration + protected override int SIZE_STORED => PokeCrypto.SIZE_6STORED; + protected override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY; + public override PKM BlankPKM => new PK6(); + public override Type PKMType => typeof(PK6); + + public override int BoxCount => 31; + public override int MaxEV => 252; + public override int Generation => 6; + public override EntityContext Context => EntityContext.Gen6; + protected override int GiftCountMax => 24; + protected override int GiftFlagMax => 0x100 * 8; + public int EventFlagCount => 8 * 0x1A0; + public int EventWorkCount => (EventFlag - EventWork) / sizeof(ushort); + protected abstract int EventFlag { get; } + protected abstract int EventWork { get; } + public override int OTLength => 12; + public override int NickLength => 12; + + public override int MaxSpeciesID => Legal.MaxSpeciesID_6; + public override int MaxBallID => Legal.MaxBallID_6; + public override int MaxGameID => Legal.MaxGameID_6; // OR + + protected override PKM GetPKM(byte[] data) => new PK6(data); + protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray6(data); + + protected int WondercardFlags { get; set; } = int.MinValue; + protected int JPEG { get; set; } = int.MinValue; + public int PSS { get; protected set; } = int.MinValue; + public int BerryField { get; protected set; } = int.MinValue; + public int HoF { get; protected set; } = int.MinValue; + protected int PCLayout { private get; set; } = int.MinValue; + protected int BattleBoxOffset { get; set; } = int.MinValue; + public int GetBattleBoxSlot(int slot) => BattleBoxOffset + (slot * SIZE_STORED); + + public virtual string JPEGTitle => string.Empty; + public virtual byte[] GetJPEGData() => Array.Empty(); + + protected internal const int LongStringLength = 0x22; // bytes, not characters + protected internal const int ShortStringLength = 0x1A; // bytes, not characters + + // Player Information + public override int TID { get => Status.TID; set => Status.TID = value; } + public override int SID { get => Status.SID; set => Status.SID = value; } + public override int Game { get => Status.Game; set => Status.Game = value; } + public override int Gender { get => Status.Gender; set => Status.Gender = value; } + public override int Language { get => Status.Language; set => Status.Language = value; } + public override string OT { get => Status.OT; set => Status.OT = value; } + public byte Region { get => Status.Region; set => Status.Region = value; } + public byte Country { get => Status.Country; set => Status.Country = value; } + public byte ConsoleRegion { get => Status.ConsoleRegion; set => Status.ConsoleRegion = value; } + public int GameSyncIDSize => MyStatus6.GameSyncIDSize; // 64 bits + public string GameSyncID { get => Status.GameSyncID; set => Status.GameSyncID = value; } + public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = value; } + public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = value; } + public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = value; } + + public abstract int Badges { get; set; } + public abstract int Vivillon { get; set; } + public abstract int BP { get; set; } + // Money + + public override uint SecondsToStart { get => GameTime.SecondsToStart; set => GameTime.SecondsToStart = value; } + public override uint SecondsToFame { get => GameTime.SecondsToFame; set => GameTime.SecondsToFame = value; } + public override IReadOnlyList Inventory { get => Items.Inventory; set => Items.Inventory = value; } + + // Daycare + public override int DaycareSeedSize => 16; + + // Storage + public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot); + + public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30); + + private int GetBoxNameOffset(int box) => PCLayout + (LongStringLength * box); + + public override string GetBoxName(int box) { - // Save Data Attributes - protected internal override string ShortSummary => $"{OT} ({Version}) - {Played.LastSavedTime}"; - public override string Extension => string.Empty; + if (PCLayout < 0) + return $"B{box + 1}"; + return GetString(Data.AsSpan(GetBoxNameOffset(box), LongStringLength)); + } - protected SAV6(byte[] data, int biOffset) : base(data, biOffset) { } - protected SAV6(int size, int biOffset) : base(size, biOffset) { } + public override void SetBoxName(int box, string value) + { + var span = Data.AsSpan(GetBoxNameOffset(box), LongStringLength); + SetString(span, value.AsSpan(), LongStringLength / 2, StringConverterOption.ClearZero); + } - // Configuration - protected override int SIZE_STORED => PokeCrypto.SIZE_6STORED; - protected override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY; - public override PKM BlankPKM => new PK6(); - public override Type PKMType => typeof(PK6); - - public override int BoxCount => 31; - public override int MaxEV => 252; - public override int Generation => 6; - public override EntityContext Context => EntityContext.Gen6; - protected override int GiftCountMax => 24; - protected override int GiftFlagMax => 0x100 * 8; - public int EventFlagCount => 8 * 0x1A0; - public int EventWorkCount => (EventFlag - EventWork) / sizeof(ushort); - protected abstract int EventFlag { get; } - protected abstract int EventWork { get; } - public override int OTLength => 12; - public override int NickLength => 12; - - public override int MaxSpeciesID => Legal.MaxSpeciesID_6; - public override int MaxBallID => Legal.MaxBallID_6; - public override int MaxGameID => Legal.MaxGameID_6; // OR - - protected override PKM GetPKM(byte[] data) => new PK6(data); - protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray6(data); - - protected int WondercardFlags { get; set; } = int.MinValue; - protected int JPEG { get; set; } = int.MinValue; - public int PSS { get; protected set; } = int.MinValue; - public int BerryField { get; protected set; } = int.MinValue; - public int HoF { get; protected set; } = int.MinValue; - protected int PCLayout { private get; set; } = int.MinValue; - protected int BattleBoxOffset { get; set; } = int.MinValue; - public int GetBattleBoxSlot(int slot) => BattleBoxOffset + (slot * SIZE_STORED); - - public virtual string JPEGTitle => string.Empty; - public virtual byte[] GetJPEGData() => Array.Empty(); - - protected internal const int LongStringLength = 0x22; // bytes, not characters - protected internal const int ShortStringLength = 0x1A; // bytes, not characters - - // Player Information - public override int TID { get => Status.TID; set => Status.TID = value; } - public override int SID { get => Status.SID; set => Status.SID = value; } - public override int Game { get => Status.Game; set => Status.Game = value; } - public override int Gender { get => Status.Gender; set => Status.Gender = value; } - public override int Language { get => Status.Language; set => Status.Language = value; } - public override string OT { get => Status.OT; set => Status.OT = value; } - public byte Region { get => Status.Region; set => Status.Region = value; } - public byte Country { get => Status.Country; set => Status.Country = value; } - public byte ConsoleRegion { get => Status.ConsoleRegion; set => Status.ConsoleRegion = value; } - public int GameSyncIDSize => MyStatus6.GameSyncIDSize; // 64 bits - public string GameSyncID { get => Status.GameSyncID; set => Status.GameSyncID = value; } - public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = value; } - public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = value; } - public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = value; } - - public abstract int Badges { get; set; } - public abstract int Vivillon { get; set; } - public abstract int BP { get; set; } - // Money - - public override uint SecondsToStart { get => GameTime.SecondsToStart; set => GameTime.SecondsToStart = value; } - public override uint SecondsToFame { get => GameTime.SecondsToFame; set => GameTime.SecondsToFame = value; } - public override IReadOnlyList Inventory { get => Items.Inventory; set => Items.Inventory = value; } - - // Daycare - public override int DaycareSeedSize => 16; - - // Storage - public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot); - - public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30); - - private int GetBoxNameOffset(int box) => PCLayout + (LongStringLength * box); - - public override string GetBoxName(int box) + protected override void SetPKM(PKM pk, bool isParty = false) + { + PK6 pk6 = (PK6)pk; + // Apply to this Save File + int CT = pk6.CurrentHandler; + DateTime Date = DateTime.Now; + pk6.Trade(this, Date.Day, Date.Month, Date.Year); + if (CT != pk6.CurrentHandler) // Logic updated Friendship { - if (PCLayout < 0) - return $"B{box + 1}"; - return GetString(Data.AsSpan(GetBoxNameOffset(box), LongStringLength)); + // Copy over the Friendship Value only under certain circumstances + if (pk6.HasMove(216)) // Return + pk6.CurrentFriendship = pk6.OppositeFriendship; + else if (pk6.HasMove(218)) // Frustration + pk.CurrentFriendship = pk6.OppositeFriendship; } - public override void SetBoxName(int box, string value) + pk6.FormArgumentElapsed = pk6.FormArgumentMaximum = 0; + pk6.FormArgumentRemain = (byte)GetFormArgument(pk, isParty); + if (!isParty && pk.Form != 0) { - var span = Data.AsSpan(GetBoxNameOffset(box), LongStringLength); - SetString(span, value.AsSpan(), LongStringLength / 2, StringConverterOption.ClearZero); - } - - protected override void SetPKM(PKM pkm, bool isParty = false) - { - PK6 pk6 = (PK6)pkm; - // Apply to this Save File - int CT = pk6.CurrentHandler; - DateTime Date = DateTime.Now; - pk6.Trade(this, Date.Day, Date.Month, Date.Year); - if (CT != pk6.CurrentHandler) // Logic updated Friendship + switch (pk.Species) { - // Copy over the Friendship Value only under certain circumstances - if (pk6.HasMove(216)) // Return - pk6.CurrentFriendship = pk6.OppositeFriendship; - else if (pk6.HasMove(218)) // Frustration - pkm.CurrentFriendship = pk6.OppositeFriendship; - } - - pk6.FormArgumentElapsed = pk6.FormArgumentMaximum = 0; - pk6.FormArgumentRemain = (byte)GetFormArgument(pkm, isParty); - if (!isParty && pkm.Form != 0) - { - switch (pkm.Species) + case (int) Species.Furfrou: + pk.Form = 0; + break; + case (int) Species.Hoopa: { - case (int) Species.Furfrou: - pkm.Form = 0; - break; - case (int) Species.Hoopa: - { - pkm.Form = 0; - var hsf = Array.IndexOf(pkm.Moves, (int) Move.HyperspaceFury); - if (hsf != -1) - pkm.SetMove(hsf, (int) Move.HyperspaceHole); - break; - } + pk.Form = 0; + var hsf = Array.IndexOf(pk.Moves, (int) Move.HyperspaceFury); + if (hsf != -1) + pk.SetMove(hsf, (int) Move.HyperspaceHole); + break; } } - - pkm.RefreshChecksum(); - AddCountAcquired(pkm); } - private void AddCountAcquired(PKM pkm) - { - Records.AddRecord(pkm.WasEgg ? 009 : 007); // egg, capture - if (pkm.CurrentHandler == 1) - Records.AddRecord(012); // trade - if (!pkm.WasEgg) - { - Records.AddRecord(004); // total battles - Records.AddRecord(005); // wild encounters - } - } - - private static uint GetFormArgument(PKM pkm, bool isParty) - { - if (!isParty || pkm.Form == 0) - return 0; - return pkm.Species switch - { - (int)Species.Furfrou => 5u, // Furfrou - (int)Species.Hoopa => 3u, // Hoopa - _ => 0u, - }; - } - - public override int PartyCount - { - get => Data[Party + (6 * SIZE_PARTY)]; - protected set => Data[Party + (6 * SIZE_PARTY)] = (byte)value; - } - - public sealed override string GetString(ReadOnlySpan data) => StringConverter6.GetString(data); - - public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter6.SetString(destBuffer, value, maxLength, option); - } - - public int GetRecord(int recordID) => Records.GetRecord(recordID); - public int GetRecordOffset(int recordID) => Records.GetRecordOffset(recordID); - public int GetRecordMax(int recordID) => Records.GetRecordMax(recordID); - public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value); - public int RecordCount => RecordBlock6.RecordCount; - public abstract MyItem Items { get; } - public abstract ItemInfo6 ItemInfo { get; } - public abstract GameTime6 GameTime { get; } - public abstract Situation6 Situation { get; } - public abstract PlayTime6 Played { get; } - public abstract MyStatus6 Status { get; } - public abstract RecordBlock6 Records { get; } - - public bool GetEventFlag(int flagNumber) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); - return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); - } - - public void SetEventFlag(int flagNumber, bool value) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); - SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); - } - - public ushort GetWork(int index) => ReadUInt16LittleEndian(Data.AsSpan(EventWork + (index * 2))); - public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(EventWork)[(index * 2)..], value); + pk.RefreshChecksum(); + AddCountAcquired(pk); } + + private void AddCountAcquired(PKM pk) + { + Records.AddRecord(pk.WasEgg ? 009 : 007); // egg, capture + if (pk.CurrentHandler == 1) + Records.AddRecord(012); // trade + if (!pk.WasEgg) + { + Records.AddRecord(004); // total battles + Records.AddRecord(005); // wild encounters + } + } + + private static uint GetFormArgument(PKM pk, bool isParty) + { + if (!isParty || pk.Form == 0) + return 0; + return pk.Species switch + { + (int)Species.Furfrou => 5u, // Furfrou + (int)Species.Hoopa => 3u, // Hoopa + _ => 0u, + }; + } + + public override int PartyCount + { + get => Data[Party + (6 * SIZE_PARTY)]; + protected set => Data[Party + (6 * SIZE_PARTY)] = (byte)value; + } + + public sealed override string GetString(ReadOnlySpan data) => StringConverter6.GetString(data); + + public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter6.SetString(destBuffer, value, maxLength, option); + } + + public int GetRecord(int recordID) => Records.GetRecord(recordID); + public int GetRecordOffset(int recordID) => Records.GetRecordOffset(recordID); + public int GetRecordMax(int recordID) => Records.GetRecordMax(recordID); + public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value); + public int RecordCount => RecordBlock6.RecordCount; + public abstract MyItem Items { get; } + public abstract ItemInfo6 ItemInfo { get; } + public abstract GameTime6 GameTime { get; } + public abstract Situation6 Situation { get; } + public abstract PlayTime6 Played { get; } + public abstract MyStatus6 Status { get; } + public abstract RecordBlock6 Records { get; } + + public bool GetEventFlag(int flagNumber) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); + return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); + } + + public void SetEventFlag(int flagNumber, bool value) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); + SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); + } + + public ushort GetWork(int index) => ReadUInt16LittleEndian(Data.AsSpan(EventWork + (index * 2))); + public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(EventWork)[(index * 2)..], value); } diff --git a/PKHeX.Core/Saves/SAV6AO.cs b/PKHeX.Core/Saves/SAV6AO.cs index 3867fc7bf..e425bccfe 100644 --- a/PKHeX.Core/Saves/SAV6AO.cs +++ b/PKHeX.Core/Saves/SAV6AO.cs @@ -2,194 +2,193 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 6 object for . +/// +/// +public sealed class SAV6AO : SAV6, ISaveBlock6AO, IMultiplayerSprite { - /// - /// Generation 6 object for . - /// - /// - public sealed class SAV6AO : SAV6, ISaveBlock6AO, IMultiplayerSprite + public SAV6AO(byte[] data) : base(data, SaveBlockAccessor6AO.BlockMetadataOffset) { - public SAV6AO(byte[] data) : base(data, SaveBlockAccessor6AO.BlockMetadataOffset) - { - Blocks = new SaveBlockAccessor6AO(this); - Initialize(); - } + Blocks = new SaveBlockAccessor6AO(this); + Initialize(); + } - public SAV6AO() : base(SaveUtil.SIZE_G6ORAS, SaveBlockAccessor6AO.BlockMetadataOffset) - { - Blocks = new SaveBlockAccessor6AO(this); - Initialize(); - ClearBoxes(); - } + public SAV6AO() : base(SaveUtil.SIZE_G6ORAS, SaveBlockAccessor6AO.BlockMetadataOffset) + { + Blocks = new SaveBlockAccessor6AO(this); + Initialize(); + ClearBoxes(); + } - public override PersonalTable Personal => PersonalTable.AO; - public override IReadOnlyList HeldItems => Legal.HeldItem_AO; - public SaveBlockAccessor6AO Blocks { get; } - protected override SaveFile CloneInternal() => new SAV6AO((byte[])Data.Clone()); - public override int MaxMoveID => Legal.MaxMoveID_6_AO; - public override int MaxItemID => Legal.MaxItemID_6_AO; - public override int MaxAbilityID => Legal.MaxAbilityID_6_AO; + public override PersonalTable Personal => PersonalTable.AO; + public override IReadOnlyList HeldItems => Legal.HeldItem_AO; + public SaveBlockAccessor6AO Blocks { get; } + protected override SaveFile CloneInternal() => new SAV6AO((byte[])Data.Clone()); + public override int MaxMoveID => Legal.MaxMoveID_6_AO; + public override int MaxItemID => Legal.MaxItemID_6_AO; + public override int MaxAbilityID => Legal.MaxAbilityID_6_AO; - protected override int EventWork => 0x14A00; - protected override int EventFlag => EventWork + 0x2F0; + protected override int EventWork => 0x14A00; + protected override int EventFlag => EventWork + 0x2F0; - private void Initialize() - { - PCLayout = 0x04400; - BattleBoxOffset = 0x04A00; - PSS = 0x05000; - Party = 0x14200; - PokeDex = 0x15000; - HoF = 0x19E00; - DaycareOffset = 0x1BC00; - BerryField = 0x1C400; - WondercardFlags = 0x1CC00; - Box = 0x33000; - JPEG = 0x67C00; + private void Initialize() + { + PCLayout = 0x04400; + BattleBoxOffset = 0x04A00; + PSS = 0x05000; + Party = 0x14200; + PokeDex = 0x15000; + HoF = 0x19E00; + DaycareOffset = 0x1BC00; + BerryField = 0x1C400; + WondercardFlags = 0x1CC00; + Box = 0x33000; + JPEG = 0x67C00; - WondercardData = WondercardFlags + 0x100; - } + WondercardData = WondercardFlags + 0x100; + } - /// Offset of the UnionPokemon block. - public const int Fused = 0x16A00; - /// Offset of the GtsData block. - public const int GTS = 0x18200; - /// Offset of the second daycare structure within the Daycare block. - private const int Daycare2 = 0x1BC00 + 0x1F0; - /// Offset of the Contest data block. - public const int Contest = 0x23600; + /// Offset of the UnionPokemon block. + public const int Fused = 0x16A00; + /// Offset of the GtsData block. + public const int GTS = 0x18200; + /// Offset of the second daycare structure within the Daycare block. + private const int Daycare2 = 0x1BC00 + 0x1F0; + /// Offset of the Contest data block. + public const int Contest = 0x23600; - #region Blocks - public override IReadOnlyList AllBlocks => Blocks.BlockInfo; - public override MyItem Items => Blocks.Items; - public override ItemInfo6 ItemInfo => Blocks.ItemInfo; - public override GameTime6 GameTime => Blocks.GameTime; - public override Situation6 Situation => Blocks.Situation; - public override PlayTime6 Played => Blocks.Played; - public override MyStatus6 Status => Blocks.Status; - public override RecordBlock6 Records => Blocks.Records; - public Puff6 Puff => Blocks.Puff; - public OPower6 OPower => Blocks.OPower; - public LinkBlock6 Link => Blocks.Link; - public BoxLayout6 BoxLayout => Blocks.BoxLayout; - public BattleBox6 BattleBox => Blocks.BattleBox; - public MysteryBlock6 MysteryGift => Blocks.MysteryGift; - public SuperTrainBlock SuperTrain => Blocks.SuperTrain; - public MaisonBlock Maison => Blocks.Maison; - public SubEventLog6 SUBE => Blocks.SUBE; - public ConfigSave6 Config => Blocks.Config; - public Encount6 Encount => Blocks.Encount; + #region Blocks + public override IReadOnlyList AllBlocks => Blocks.BlockInfo; + public override MyItem Items => Blocks.Items; + public override ItemInfo6 ItemInfo => Blocks.ItemInfo; + public override GameTime6 GameTime => Blocks.GameTime; + public override Situation6 Situation => Blocks.Situation; + public override PlayTime6 Played => Blocks.Played; + public override MyStatus6 Status => Blocks.Status; + public override RecordBlock6 Records => Blocks.Records; + public Puff6 Puff => Blocks.Puff; + public OPower6 OPower => Blocks.OPower; + public LinkBlock6 Link => Blocks.Link; + public BoxLayout6 BoxLayout => Blocks.BoxLayout; + public BattleBox6 BattleBox => Blocks.BattleBox; + public MysteryBlock6 MysteryGift => Blocks.MysteryGift; + public SuperTrainBlock SuperTrain => Blocks.SuperTrain; + public MaisonBlock Maison => Blocks.Maison; + public SubEventLog6 SUBE => Blocks.SUBE; + public ConfigSave6 Config => Blocks.Config; + public Encount6 Encount => Blocks.Encount; - public Misc6AO Misc => Blocks.Misc; - public Zukan6AO Zukan => Blocks.Zukan; - public SecretBase6Block SecretBase => Blocks.SecretBase; - #endregion + public Misc6AO Misc => Blocks.Misc; + public Zukan6AO Zukan => Blocks.Zukan; + public SecretBase6Block SecretBase => Blocks.SecretBase; + #endregion - public override GameVersion Version => Game switch - { - (int) GameVersion.AS => GameVersion.AS, - (int) GameVersion.OR => GameVersion.OR, - _ => GameVersion.Invalid, - }; + public override GameVersion Version => Game switch + { + (int) GameVersion.AS => GameVersion.AS, + (int) GameVersion.OR => GameVersion.OR, + _ => GameVersion.Invalid, + }; - public override bool GetCaught(int species) => Blocks.Zukan.GetCaught(species); - public override bool GetSeen(int species) => Blocks.Zukan.GetSeen(species); - public override void SetSeen(int species, bool seen) => Blocks.Zukan.SetSeen(species, seen); - public override void SetCaught(int species, bool caught) => Blocks.Zukan.SetCaught(species, caught); - protected override void SetDex(PKM pkm) => Blocks.Zukan.SetDex(pkm); + public override bool GetCaught(int species) => Blocks.Zukan.GetCaught(species); + public override bool GetSeen(int species) => Blocks.Zukan.GetSeen(species); + public override void SetSeen(int species, bool seen) => Blocks.Zukan.SetSeen(species, seen); + public override void SetCaught(int species, bool caught) => Blocks.Zukan.SetCaught(species, caught); + protected override void SetDex(PKM pk) => Blocks.Zukan.SetDex(pk); - public override uint Money { get => Blocks.Misc.Money; set => Blocks.Misc.Money = value; } - public override int Vivillon { get => Blocks.Misc.Vivillon; set => Blocks.Misc.Vivillon = value; } - public override int Badges { get => Blocks.Misc.Badges; set => Blocks.Misc.Badges = value; } - public override int BP { get => Blocks.Misc.BP; set => Blocks.Misc.BP = value; } + public override uint Money { get => Blocks.Misc.Money; set => Blocks.Misc.Money = value; } + public override int Vivillon { get => Blocks.Misc.Vivillon; set => Blocks.Misc.Vivillon = value; } + public override int Badges { get => Blocks.Misc.Badges; set => Blocks.Misc.Badges = value; } + public override int BP { get => Blocks.Misc.BP; set => Blocks.Misc.BP = value; } - public int MultiplayerSpriteID - { - get => Blocks.Status.MultiplayerSpriteID_1; - set => Blocks.Status.MultiplayerSpriteID_1 = Blocks.Status.MultiplayerSpriteID_2 = value; - } + public int MultiplayerSpriteID + { + get => Blocks.Status.MultiplayerSpriteID_1; + set => Blocks.Status.MultiplayerSpriteID_1 = Blocks.Status.MultiplayerSpriteID_2 = value; + } - // Daycare - public override int DaycareSeedSize => 16; - public override bool HasTwoDaycares => true; + // Daycare + public override int DaycareSeedSize => 16; + public override bool HasTwoDaycares => true; - public override int GetDaycareSlotOffset(int loc, int slot) - { - int ofs = loc == 0 ? DaycareOffset : Daycare2; - return ofs + 8 + (slot * (SIZE_STORED + 8)); - } + public override int GetDaycareSlotOffset(int loc, int slot) + { + int ofs = loc == 0 ? DaycareOffset : Daycare2; + return ofs + 8 + (slot * (SIZE_STORED + 8)); + } - public override uint? GetDaycareEXP(int loc, int slot) - { - int ofs = loc == 0 ? DaycareOffset : Daycare2; - return ReadUInt32LittleEndian(Data.AsSpan(ofs + ((SIZE_STORED + 8) * slot) + 4)); - } + public override uint? GetDaycareEXP(int loc, int slot) + { + int ofs = loc == 0 ? DaycareOffset : Daycare2; + return ReadUInt32LittleEndian(Data.AsSpan(ofs + ((SIZE_STORED + 8) * slot) + 4)); + } - public override bool? IsDaycareOccupied(int loc, int slot) - { - int ofs = loc == 0 ? DaycareOffset : Daycare2; - return Data[ofs + ((SIZE_STORED + 8) * slot)] == 1; - } + public override bool? IsDaycareOccupied(int loc, int slot) + { + int ofs = loc == 0 ? DaycareOffset : Daycare2; + return Data[ofs + ((SIZE_STORED + 8) * slot)] == 1; + } - public override string GetDaycareRNGSeed(int loc) - { - int ofs = loc == 0 ? DaycareOffset : Daycare2; - return Util.GetHexStringFromBytes(Data.AsSpan(ofs + 0x1E8, DaycareSeedSize / 2)); - } + public override string GetDaycareRNGSeed(int loc) + { + int ofs = loc == 0 ? DaycareOffset : Daycare2; + return Util.GetHexStringFromBytes(Data.AsSpan(ofs + 0x1E8, DaycareSeedSize / 2)); + } - public override bool? IsDaycareHasEgg(int loc) - { - int ofs = loc == 0 ? DaycareOffset : Daycare2; - return Data[ofs + 0x1E0] == 1; - } + public override bool? IsDaycareHasEgg(int loc) + { + int ofs = loc == 0 ? DaycareOffset : Daycare2; + return Data[ofs + 0x1E0] == 1; + } - public override void SetDaycareEXP(int loc, int slot, uint EXP) - { - int ofs = loc == 0 ? DaycareOffset : Daycare2; - WriteUInt32LittleEndian(Data.AsSpan(ofs + ((SIZE_STORED + 8) * slot) + 4), EXP); - } + public override void SetDaycareEXP(int loc, int slot, uint EXP) + { + int ofs = loc == 0 ? DaycareOffset : Daycare2; + WriteUInt32LittleEndian(Data.AsSpan(ofs + ((SIZE_STORED + 8) * slot) + 4), EXP); + } - public override void SetDaycareOccupied(int loc, int slot, bool occupied) - { - int ofs = loc == 0 ? DaycareOffset : Daycare2; - Data[ofs + ((SIZE_STORED + 8) * slot)] = occupied ? (byte)1 : (byte)0; - } + public override void SetDaycareOccupied(int loc, int slot, bool occupied) + { + int ofs = loc == 0 ? DaycareOffset : Daycare2; + Data[ofs + ((SIZE_STORED + 8) * slot)] = occupied ? (byte)1 : (byte)0; + } - public override void SetDaycareRNGSeed(int loc, string seed) - { - if (loc != 0) - return; - if (DaycareOffset < 0) - return; - if (seed.Length > DaycareSeedSize) - return; + public override void SetDaycareRNGSeed(int loc, string seed) + { + if (loc != 0) + return; + if (DaycareOffset < 0) + return; + if (seed.Length > DaycareSeedSize) + return; - Util.GetBytesFromHexString(seed).CopyTo(Data, DaycareOffset + 0x1E8); - } + Util.GetBytesFromHexString(seed).CopyTo(Data, DaycareOffset + 0x1E8); + } - public override void SetDaycareHasEgg(int loc, bool hasEgg) - { - int ofs = loc == 0 ? DaycareOffset : Daycare2; - Data[ofs + 0x1E0] = hasEgg ? (byte)1 : (byte)0; - } + public override void SetDaycareHasEgg(int loc, bool hasEgg) + { + int ofs = loc == 0 ? DaycareOffset : Daycare2; + Data[ofs + 0x1E0] = hasEgg ? (byte)1 : (byte)0; + } - public override string JPEGTitle => !HasJPPEGData ? string.Empty : StringConverter6.GetString(Data.AsSpan(JPEG, 0x1A)); - public override byte[] GetJPEGData() => !HasJPPEGData ? Array.Empty() : GetData(JPEG + 0x54, 0xE004); - private bool HasJPPEGData => Data[JPEG + 0x54] == 0xFF; + public override string JPEGTitle => !HasJPPEGData ? string.Empty : StringConverter6.GetString(Data.AsSpan(JPEG, 0x1A)); + public override byte[] GetJPEGData() => !HasJPPEGData ? Array.Empty() : GetData(JPEG + 0x54, 0xE004); + private bool HasJPPEGData => Data[JPEG + 0x54] == 0xFF; - protected override bool[] MysteryGiftReceivedFlags { get => Blocks.MysteryGift.GetReceivedFlags(); set => Blocks.MysteryGift.SetReceivedFlags(value); } - protected override DataMysteryGift[] MysteryGiftCards { get => Blocks.MysteryGift.GetGifts(); set => Blocks.MysteryGift.SetGifts(value); } + protected override bool[] MysteryGiftReceivedFlags { get => Blocks.MysteryGift.GetReceivedFlags(); set => Blocks.MysteryGift.SetReceivedFlags(value); } + protected override DataMysteryGift[] MysteryGiftCards { get => Blocks.MysteryGift.GetGifts(); set => Blocks.MysteryGift.SetGifts(value); } - public override int CurrentBox { get => Blocks.BoxLayout.CurrentBox; set => Blocks.BoxLayout.CurrentBox = value; } - protected override int GetBoxWallpaperOffset(int box) => Blocks.BoxLayout.GetBoxWallpaperOffset(box); - public override int BoxesUnlocked { get => Blocks.BoxLayout.BoxesUnlocked; set => Blocks.BoxLayout.BoxesUnlocked = value; } - public override byte[] BoxFlags { get => Blocks.BoxLayout.BoxFlags; set => Blocks.BoxLayout.BoxFlags = value; } + public override int CurrentBox { get => Blocks.BoxLayout.CurrentBox; set => Blocks.BoxLayout.CurrentBox = value; } + protected override int GetBoxWallpaperOffset(int box) => Blocks.BoxLayout.GetBoxWallpaperOffset(box); + public override int BoxesUnlocked { get => Blocks.BoxLayout.BoxesUnlocked; set => Blocks.BoxLayout.BoxesUnlocked = value; } + public override byte[] BoxFlags { get => Blocks.BoxLayout.BoxFlags; set => Blocks.BoxLayout.BoxFlags = value; } - public bool BattleBoxLocked - { - get => Blocks.BattleBox.Locked; - set => Blocks.BattleBox.Locked = value; - } + public bool BattleBoxLocked + { + get => Blocks.BattleBox.Locked; + set => Blocks.BattleBox.Locked = value; } } diff --git a/PKHeX.Core/Saves/SAV6AODemo.cs b/PKHeX.Core/Saves/SAV6AODemo.cs index 97ad4a061..cbeff6373 100644 --- a/PKHeX.Core/Saves/SAV6AODemo.cs +++ b/PKHeX.Core/Saves/SAV6AODemo.cs @@ -1,58 +1,57 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 6 object for . +/// +/// +public sealed class SAV6AODemo : SAV6 { - /// - /// Generation 6 object for . - /// - /// - public sealed class SAV6AODemo : SAV6 + public SAV6AODemo(byte[] data) : base(data, SaveBlockAccessor6AODemo.BlockMetadataOffset) { - public SAV6AODemo(byte[] data) : base(data, SaveBlockAccessor6AODemo.BlockMetadataOffset) - { - Blocks = new SaveBlockAccessor6AODemo(this); - Initialize(); - } - - public SAV6AODemo() : base(SaveUtil.SIZE_G6ORASDEMO, SaveBlockAccessor6AODemo.BlockMetadataOffset) - { - Blocks = new SaveBlockAccessor6AODemo(this); - Initialize(); - } - - public override PersonalTable Personal => PersonalTable.AO; - public override IReadOnlyList HeldItems => Legal.HeldItem_AO; - protected override SaveFile CloneInternal() => new SAV6AODemo((byte[])Data.Clone()); - public override int MaxMoveID => Legal.MaxMoveID_6_AO; - public override int MaxItemID => Legal.MaxItemID_6_AO; - public override int MaxAbilityID => Legal.MaxAbilityID_6_AO; - protected override int EventWork => 0x04600; - protected override int EventFlag => EventWork + 0x2F0; - public SaveBlockAccessor6AODemo Blocks { get; } - - private void Initialize() - { - Party = 0x03E00; - } - - public override GameVersion Version => Game switch - { - (int) GameVersion.AS => GameVersion.AS, - (int) GameVersion.OR => GameVersion.OR, - _ => GameVersion.Invalid, - }; - - public override uint Money { get => Blocks.Misc.Money; set => Blocks.Misc.Money = value; } - public override int Vivillon { get => Blocks.Misc.Vivillon; set => Blocks.Misc.Vivillon = value; } // unused - public override int Badges { get => Blocks.Misc.Badges; set => Blocks.Misc.Badges = value; } // unused - public override int BP { get => Blocks.Misc.BP; set => Blocks.Misc.BP = value; } // unused - public override MyItem Items => Blocks.Items; - public override ItemInfo6 ItemInfo => Blocks.ItemInfo; - public override GameTime6 GameTime => Blocks.GameTime; - public override Situation6 Situation => Blocks.Situation; - public override PlayTime6 Played => Blocks.Played; - public override MyStatus6 Status => Blocks.Status; - public override RecordBlock6 Records => Blocks.Records; - public override IReadOnlyList AllBlocks => Blocks.BlockInfo; + Blocks = new SaveBlockAccessor6AODemo(this); + Initialize(); } -} \ No newline at end of file + + public SAV6AODemo() : base(SaveUtil.SIZE_G6ORASDEMO, SaveBlockAccessor6AODemo.BlockMetadataOffset) + { + Blocks = new SaveBlockAccessor6AODemo(this); + Initialize(); + } + + public override PersonalTable Personal => PersonalTable.AO; + public override IReadOnlyList HeldItems => Legal.HeldItem_AO; + protected override SaveFile CloneInternal() => new SAV6AODemo((byte[])Data.Clone()); + public override int MaxMoveID => Legal.MaxMoveID_6_AO; + public override int MaxItemID => Legal.MaxItemID_6_AO; + public override int MaxAbilityID => Legal.MaxAbilityID_6_AO; + protected override int EventWork => 0x04600; + protected override int EventFlag => EventWork + 0x2F0; + public SaveBlockAccessor6AODemo Blocks { get; } + + private void Initialize() + { + Party = 0x03E00; + } + + public override GameVersion Version => Game switch + { + (int) GameVersion.AS => GameVersion.AS, + (int) GameVersion.OR => GameVersion.OR, + _ => GameVersion.Invalid, + }; + + public override uint Money { get => Blocks.Misc.Money; set => Blocks.Misc.Money = value; } + public override int Vivillon { get => Blocks.Misc.Vivillon; set => Blocks.Misc.Vivillon = value; } // unused + public override int Badges { get => Blocks.Misc.Badges; set => Blocks.Misc.Badges = value; } // unused + public override int BP { get => Blocks.Misc.BP; set => Blocks.Misc.BP = value; } // unused + public override MyItem Items => Blocks.Items; + public override ItemInfo6 ItemInfo => Blocks.ItemInfo; + public override GameTime6 GameTime => Blocks.GameTime; + public override Situation6 Situation => Blocks.Situation; + public override PlayTime6 Played => Blocks.Played; + public override MyStatus6 Status => Blocks.Status; + public override RecordBlock6 Records => Blocks.Records; + public override IReadOnlyList AllBlocks => Blocks.BlockInfo; +} diff --git a/PKHeX.Core/Saves/SAV6XY.cs b/PKHeX.Core/Saves/SAV6XY.cs index 468b77557..f3eda570a 100644 --- a/PKHeX.Core/Saves/SAV6XY.cs +++ b/PKHeX.Core/Saves/SAV6XY.cs @@ -2,169 +2,168 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 6 object for . +/// +/// +public sealed class SAV6XY : SAV6, ISaveBlock6XY, IMultiplayerSprite { - /// - /// Generation 6 object for . - /// - /// - public sealed class SAV6XY : SAV6, ISaveBlock6XY, IMultiplayerSprite + public SAV6XY(byte[] data) : base(data, SaveBlockAccessor6XY.BlockMetadataOffset) { - public SAV6XY(byte[] data) : base(data, SaveBlockAccessor6XY.BlockMetadataOffset) + Blocks = new SaveBlockAccessor6XY(this); + Initialize(); + } + + public SAV6XY() : base(SaveUtil.SIZE_G6XY, SaveBlockAccessor6XY.BlockMetadataOffset) + { + Blocks = new SaveBlockAccessor6XY(this); + Initialize(); + ClearBoxes(); + } + + public override PersonalTable Personal => PersonalTable.XY; + public override IReadOnlyList HeldItems => Legal.HeldItem_XY; + public SaveBlockAccessor6XY Blocks { get; } + protected override SaveFile CloneInternal() => new SAV6XY((byte[])Data.Clone()); + public override int MaxMoveID => Legal.MaxMoveID_6_XY; + public override int MaxItemID => Legal.MaxItemID_6_XY; + public override int MaxAbilityID => Legal.MaxAbilityID_6_XY; + + protected override int EventWork => 0x14A00; + protected override int EventFlag => EventWork + 0x2F0; + + private void Initialize() + { + // Enable Features + Party = 0x14200; + PCLayout = 0x4400; + BattleBoxOffset = 0x04A00; + PSS = 0x05000; + PokeDex = 0x15000; + HoF = 0x19400; + DaycareOffset = 0x1B200; + BerryField = 0x1B800; + WondercardFlags = 0x1BC00; + Box = 0x22600; + JPEG = 0x57200; + + WondercardData = WondercardFlags + 0x100; + + // Extra Viewable Slots + Fused = 0x16000; + GTS = 0x17800; + } + + public int GTS { get; private set; } = int.MinValue; + public int Fused { get; private set; } = int.MinValue; + + #region Blocks + public override IReadOnlyList AllBlocks => Blocks.BlockInfo; + public override MyItem Items => Blocks.Items; + public override ItemInfo6 ItemInfo => Blocks.ItemInfo; + public override GameTime6 GameTime => Blocks.GameTime; + public override Situation6 Situation => Blocks.Situation; + public override PlayTime6 Played => Blocks.Played; + public override MyStatus6 Status => Blocks.Status; + public override RecordBlock6 Records => Blocks.Records; + public Puff6 Puff => Blocks.Puff; + public OPower6 OPower => Blocks.OPower; + public LinkBlock6 Link => Blocks.Link; + public BoxLayout6 BoxLayout => Blocks.BoxLayout; + public BattleBox6 BattleBox => Blocks.BattleBox; + public MysteryBlock6 MysteryGift => Blocks.MysteryGift; + public SuperTrainBlock SuperTrain => Blocks.SuperTrain; + public MaisonBlock Maison => Blocks.Maison; + public Zukan6XY Zukan => Blocks.Zukan; + public Misc6XY Misc => Blocks.Misc; + public Fashion6XY Fashion => Blocks.Fashion; + public SubEventLog6 SUBE => Blocks.SUBE; + public ConfigSave6 Config => Blocks.Config; + public Encount6 Encount => Blocks.Encount; + #endregion + + protected override void SetDex(PKM pk) => Blocks.Zukan.SetDex(pk); + + // Daycare + public override int DaycareSeedSize => 16; + public override bool HasTwoDaycares => false; + public override bool? IsDaycareOccupied(int loc, int slot) => Data[DaycareOffset + 0 + ((SIZE_STORED + 8) * slot)] == 1; + public override uint? GetDaycareEXP(int loc, int slot) => ReadUInt32LittleEndian(Data.AsSpan(DaycareOffset + 4 + ((SIZE_STORED + 8) * slot))); + + public override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + 8 + (slot * (SIZE_STORED + 8)); + public override bool? IsDaycareHasEgg(int loc) => Data[DaycareOffset + 0x1E0] == 1; + public override void SetDaycareHasEgg(int loc, bool hasEgg) => Data[DaycareOffset + 0x1E0] = hasEgg ? (byte)1 : (byte)0; + public override void SetDaycareOccupied(int loc, int slot, bool occupied) => Data[DaycareOffset + ((SIZE_STORED + 8) * slot)] = occupied ? (byte)1 : (byte)0; + public override void SetDaycareEXP(int loc, int slot, uint EXP) => WriteUInt32LittleEndian(Data.AsSpan(DaycareOffset + 4 + ((SIZE_STORED + 8) * slot)), EXP); + public override string GetDaycareRNGSeed(int loc) => Util.GetHexStringFromBytes(Data.AsSpan(DaycareOffset + 0x1E8, DaycareSeedSize / 2)); + + public override void SetDaycareRNGSeed(int loc, string seed) + { + if (loc != 0) + return; + if (DaycareOffset < 0) + return; + if (seed.Length > DaycareSeedSize) + return; + + Util.GetBytesFromHexString(seed).CopyTo(Data, DaycareOffset + 0x1E8); + } + + public override string JPEGTitle => !HasJPPEGData ? string.Empty : StringConverter6.GetString(Data.AsSpan(JPEG, 0x1A)); + public override byte[] GetJPEGData() => !HasJPPEGData ? Array.Empty() : GetData(JPEG + 0x54, 0xE004); + private bool HasJPPEGData => Data[JPEG + 0x54] == 0xFF; + + public void UnlockAllFriendSafariSlots() + { + // Unlock + reveal all safari slots if friend data is present + const int start = 0x1E7FF; + const int size = 0x15; + for (int i = 1; i < 101; i++) { - Blocks = new SaveBlockAccessor6XY(this); - Initialize(); + int ofs = start + (i * size); + if (Data[ofs] != 0) // no friend data == 0x00 + Data[ofs] = 0x3D; } - public SAV6XY() : base(SaveUtil.SIZE_G6XY, SaveBlockAccessor6XY.BlockMetadataOffset) - { - Blocks = new SaveBlockAccessor6XY(this); - Initialize(); - ClearBoxes(); - } + State.Edited = true; + } - public override PersonalTable Personal => PersonalTable.XY; - public override IReadOnlyList HeldItems => Legal.HeldItem_XY; - public SaveBlockAccessor6XY Blocks { get; } - protected override SaveFile CloneInternal() => new SAV6XY((byte[])Data.Clone()); - public override int MaxMoveID => Legal.MaxMoveID_6_XY; - public override int MaxItemID => Legal.MaxItemID_6_XY; - public override int MaxAbilityID => Legal.MaxAbilityID_6_XY; + public override GameVersion Version => Game switch + { + (int) GameVersion.X => GameVersion.X, + (int) GameVersion.Y => GameVersion.Y, + _ => GameVersion.Invalid, + }; - protected override int EventWork => 0x14A00; - protected override int EventFlag => EventWork + 0x2F0; + protected override bool[] MysteryGiftReceivedFlags { get => Blocks.MysteryGift.GetReceivedFlags(); set => Blocks.MysteryGift.SetReceivedFlags(value); } + protected override DataMysteryGift[] MysteryGiftCards { get => Blocks.MysteryGift.GetGifts(); set => Blocks.MysteryGift.SetGifts(value); } - private void Initialize() - { - // Enable Features - Party = 0x14200; - PCLayout = 0x4400; - BattleBoxOffset = 0x04A00; - PSS = 0x05000; - PokeDex = 0x15000; - HoF = 0x19400; - DaycareOffset = 0x1B200; - BerryField = 0x1B800; - WondercardFlags = 0x1BC00; - Box = 0x22600; - JPEG = 0x57200; + public override bool GetCaught(int species) => Blocks.Zukan.GetCaught(species); + public override bool GetSeen(int species) => Blocks.Zukan.GetSeen(species); + public override void SetSeen(int species, bool seen) => Blocks.Zukan.SetSeen(species, seen); + public override void SetCaught(int species, bool caught) => Blocks.Zukan.SetCaught(species, caught); - WondercardData = WondercardFlags + 0x100; + public override int CurrentBox { get => Blocks.BoxLayout.CurrentBox; set => Blocks.BoxLayout.CurrentBox = value; } + protected override int GetBoxWallpaperOffset(int box) => Blocks.BoxLayout.GetBoxWallpaperOffset(box); + public override int BoxesUnlocked { get => Blocks.BoxLayout.BoxesUnlocked; set => Blocks.BoxLayout.BoxesUnlocked = value; } + public override byte[] BoxFlags { get => Blocks.BoxLayout.BoxFlags; set => Blocks.BoxLayout.BoxFlags = value; } - // Extra Viewable Slots - Fused = 0x16000; - GTS = 0x17800; - } + public bool BattleBoxLocked + { + get => Blocks.BattleBox.Locked; + set => Blocks.BattleBox.Locked = value; + } - public int GTS { get; private set; } = int.MinValue; - public int Fused { get; private set; } = int.MinValue; + public override uint Money { get => Blocks.Misc.Money; set => Blocks.Misc.Money = value; } + public override int Vivillon { get => Blocks.Misc.Vivillon; set => Blocks.Misc.Vivillon = value; } + public override int Badges { get => Blocks.Misc.Badges; set => Blocks.Misc.Badges = value; } + public override int BP { get => Blocks.Misc.BP; set => Blocks.Misc.BP = value; } - #region Blocks - public override IReadOnlyList AllBlocks => Blocks.BlockInfo; - public override MyItem Items => Blocks.Items; - public override ItemInfo6 ItemInfo => Blocks.ItemInfo; - public override GameTime6 GameTime => Blocks.GameTime; - public override Situation6 Situation => Blocks.Situation; - public override PlayTime6 Played => Blocks.Played; - public override MyStatus6 Status => Blocks.Status; - public override RecordBlock6 Records => Blocks.Records; - public Puff6 Puff => Blocks.Puff; - public OPower6 OPower => Blocks.OPower; - public LinkBlock6 Link => Blocks.Link; - public BoxLayout6 BoxLayout => Blocks.BoxLayout; - public BattleBox6 BattleBox => Blocks.BattleBox; - public MysteryBlock6 MysteryGift => Blocks.MysteryGift; - public SuperTrainBlock SuperTrain => Blocks.SuperTrain; - public MaisonBlock Maison => Blocks.Maison; - public Zukan6XY Zukan => Blocks.Zukan; - public Misc6XY Misc => Blocks.Misc; - public Fashion6XY Fashion => Blocks.Fashion; - public SubEventLog6 SUBE => Blocks.SUBE; - public ConfigSave6 Config => Blocks.Config; - public Encount6 Encount => Blocks.Encount; - #endregion - - protected override void SetDex(PKM pkm) => Blocks.Zukan.SetDex(pkm); - - // Daycare - public override int DaycareSeedSize => 16; - public override bool HasTwoDaycares => false; - public override bool? IsDaycareOccupied(int loc, int slot) => Data[DaycareOffset + 0 + ((SIZE_STORED + 8) * slot)] == 1; - public override uint? GetDaycareEXP(int loc, int slot) => ReadUInt32LittleEndian(Data.AsSpan(DaycareOffset + 4 + ((SIZE_STORED + 8) * slot))); - - public override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + 8 + (slot * (SIZE_STORED + 8)); - public override bool? IsDaycareHasEgg(int loc) => Data[DaycareOffset + 0x1E0] == 1; - public override void SetDaycareHasEgg(int loc, bool hasEgg) => Data[DaycareOffset + 0x1E0] = hasEgg ? (byte)1 : (byte)0; - public override void SetDaycareOccupied(int loc, int slot, bool occupied) => Data[DaycareOffset + ((SIZE_STORED + 8) * slot)] = occupied ? (byte)1 : (byte)0; - public override void SetDaycareEXP(int loc, int slot, uint EXP) => WriteUInt32LittleEndian(Data.AsSpan(DaycareOffset + 4 + ((SIZE_STORED + 8) * slot)), EXP); - public override string GetDaycareRNGSeed(int loc) => Util.GetHexStringFromBytes(Data.AsSpan(DaycareOffset + 0x1E8, DaycareSeedSize / 2)); - - public override void SetDaycareRNGSeed(int loc, string seed) - { - if (loc != 0) - return; - if (DaycareOffset < 0) - return; - if (seed.Length > DaycareSeedSize) - return; - - Util.GetBytesFromHexString(seed).CopyTo(Data, DaycareOffset + 0x1E8); - } - - public override string JPEGTitle => !HasJPPEGData ? string.Empty : StringConverter6.GetString(Data.AsSpan(JPEG, 0x1A)); - public override byte[] GetJPEGData() => !HasJPPEGData ? Array.Empty() : GetData(JPEG + 0x54, 0xE004); - private bool HasJPPEGData => Data[JPEG + 0x54] == 0xFF; - - public void UnlockAllFriendSafariSlots() - { - // Unlock + reveal all safari slots if friend data is present - const int start = 0x1E7FF; - const int size = 0x15; - for (int i = 1; i < 101; i++) - { - int ofs = start + (i * size); - if (Data[ofs] != 0) // no friend data == 0x00 - Data[ofs] = 0x3D; - } - - State.Edited = true; - } - - public override GameVersion Version => Game switch - { - (int) GameVersion.X => GameVersion.X, - (int) GameVersion.Y => GameVersion.Y, - _ => GameVersion.Invalid, - }; - - protected override bool[] MysteryGiftReceivedFlags { get => Blocks.MysteryGift.GetReceivedFlags(); set => Blocks.MysteryGift.SetReceivedFlags(value); } - protected override DataMysteryGift[] MysteryGiftCards { get => Blocks.MysteryGift.GetGifts(); set => Blocks.MysteryGift.SetGifts(value); } - - public override bool GetCaught(int species) => Blocks.Zukan.GetCaught(species); - public override bool GetSeen(int species) => Blocks.Zukan.GetSeen(species); - public override void SetSeen(int species, bool seen) => Blocks.Zukan.SetSeen(species, seen); - public override void SetCaught(int species, bool caught) => Blocks.Zukan.SetCaught(species, caught); - - public override int CurrentBox { get => Blocks.BoxLayout.CurrentBox; set => Blocks.BoxLayout.CurrentBox = value; } - protected override int GetBoxWallpaperOffset(int box) => Blocks.BoxLayout.GetBoxWallpaperOffset(box); - public override int BoxesUnlocked { get => Blocks.BoxLayout.BoxesUnlocked; set => Blocks.BoxLayout.BoxesUnlocked = value; } - public override byte[] BoxFlags { get => Blocks.BoxLayout.BoxFlags; set => Blocks.BoxLayout.BoxFlags = value; } - - public bool BattleBoxLocked - { - get => Blocks.BattleBox.Locked; - set => Blocks.BattleBox.Locked = value; - } - - public override uint Money { get => Blocks.Misc.Money; set => Blocks.Misc.Money = value; } - public override int Vivillon { get => Blocks.Misc.Vivillon; set => Blocks.Misc.Vivillon = value; } - public override int Badges { get => Blocks.Misc.Badges; set => Blocks.Misc.Badges = value; } - public override int BP { get => Blocks.Misc.BP; set => Blocks.Misc.BP = value; } - - public int MultiplayerSpriteID - { - get => Blocks.Status.MultiplayerSpriteID_1; - set => Blocks.Status.MultiplayerSpriteID_1 = Blocks.Status.MultiplayerSpriteID_2 = value; - } + public int MultiplayerSpriteID + { + get => Blocks.Status.MultiplayerSpriteID_1; + set => Blocks.Status.MultiplayerSpriteID_1 = Blocks.Status.MultiplayerSpriteID_2 = value; } } diff --git a/PKHeX.Core/Saves/SAV7.cs b/PKHeX.Core/Saves/SAV7.cs index 4aaf5cad8..c0f311e4a 100644 --- a/PKHeX.Core/Saves/SAV7.cs +++ b/PKHeX.Core/Saves/SAV7.cs @@ -3,276 +3,275 @@ using System.Diagnostics; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 object. +/// +public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IRegionOrigin, IGameSync, IEventFlag37 { - /// - /// Generation 7 object. - /// - public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IRegionOrigin, IGameSync, IEventFlag37 + // Save Data Attributes + protected internal override string ShortSummary => $"{OT} ({Version}) - {Played.LastSavedTime}"; + public override string Extension => string.Empty; + + public override IReadOnlyList PKMExtensions => Array.FindAll(PKM.Extensions, f => { - // Save Data Attributes - protected internal override string ShortSummary => $"{OT} ({Version}) - {Played.LastSavedTime}"; - public override string Extension => string.Empty; + int gen = f[^1] - 0x30; + return gen <= 7 && f[1] != 'b'; // ignore PB7 + }); - public override IReadOnlyList PKMExtensions => Array.FindAll(PKM.Extensions, f => - { - int gen = f[^1] - 0x30; - return gen <= 7 && f[1] != 'b'; // ignore PB7 - }); - - protected SAV7(byte[] data, int biOffset) : base(data, biOffset) - { - } - - protected SAV7(int size, int biOffset) : base(size, biOffset) - { - } - - protected void ReloadBattleTeams() - { - var demo = this is SAV7SM && new ReadOnlySpan(Data, BoxLayout.Offset, 0x4C4).IsRangeEmpty(); // up to Battle Box values - if (demo || !State.Exportable) - { - BoxLayout.ClearBattleTeams(); - } - else // Valid slot locking info present - { - BoxLayout.LoadBattleTeams(); - } - } - - #region Blocks - public abstract MyItem Items { get; } - public abstract MysteryBlock7 MysteryGift { get; } - public abstract PokeFinder7 PokeFinder { get; } - public abstract JoinFesta7 Festa { get; } - public abstract Daycare7 Daycare { get; } - public abstract RecordBlock6 Records { get; } - public abstract PlayTime6 Played { get; } - public abstract MyStatus7 MyStatus { get; } - public abstract FieldMoveModelSave7 Overworld { get; } - public abstract Situation7 Situation { get; } - public abstract ConfigSave7 Config { get; } - public abstract GameTime7 GameTime { get; } - public abstract Misc7 Misc { get; } - public abstract Zukan7 Zukan { get; } - public abstract BoxLayout7 BoxLayout { get; } - public abstract BattleTree7 BattleTree { get; } - public abstract ResortSave7 ResortSave { get; } - public abstract FieldMenu7 FieldMenu { get; } - public abstract FashionBlock7 Fashion { get; } - public abstract HallOfFame7 Fame { get; } - #endregion - - // Configuration - protected override int SIZE_STORED => PokeCrypto.SIZE_6STORED; - protected override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY; - public override PKM BlankPKM => new PK7(); - public override Type PKMType => typeof(PK7); - - public override int BoxCount => 32; - public override int MaxEV => 252; - public override int Generation => 7; - public override EntityContext Context => EntityContext.Gen7; - protected override int GiftCountMax => 48; - protected override int GiftFlagMax => 0x100 * 8; - public abstract int EventFlagCount { get; } - public int EventWorkCount => 1000; - private int EventWork => AllBlocks[05].Offset; - private int EventFlag => EventWork + (EventWorkCount * 2); // After Event Const (u16)*n - public override int OTLength => 12; - public override int NickLength => 12; - - public override int MaxBallID => Legal.MaxBallID_7; // 26 - public override int MaxGameID => Legal.MaxGameID_7; - protected override PKM GetPKM(byte[] data) => new PK7(data); - protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray6(data); - - // Feature Overrides - - // Blocks & Offsets - private const int MemeCryptoBlock = 36; - - protected void ClearMemeCrypto() - { - new byte[0x80].CopyTo(Data, AllBlocks[MemeCryptoBlock].Offset + 0x100); - } - - protected override byte[] GetFinalData() - { - BoxLayout.SaveBattleTeams(); - SetChecksums(); - var result = MemeCrypto.Resign7(Data); - Debug.Assert(result != Data); - return result; - } - - public override GameVersion Version => Game switch - { - (int)GameVersion.SN => GameVersion.SN, - (int)GameVersion.MN => GameVersion.MN, - (int)GameVersion.US => GameVersion.US, - (int)GameVersion.UM => GameVersion.UM, - _ => GameVersion.Invalid, - }; - - public sealed override string GetString(ReadOnlySpan data) => StringConverter7.GetString(data); - - public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter7.SetString(destBuffer, value, maxLength, Language, option); - } - - // Player Information - public override int TID { get => MyStatus.TID; set => MyStatus.TID = value; } - public override int SID { get => MyStatus.SID; set => MyStatus.SID = value; } - public override int Game { get => MyStatus.Game; set => MyStatus.Game = value; } - public override int Gender { get => MyStatus.Gender; set => MyStatus.Gender = value; } - public int GameSyncIDSize => MyStatus7.GameSyncIDSize; // 64 bits - public string GameSyncID { get => MyStatus.GameSyncID; set => MyStatus.GameSyncID = value; } - public byte Region { get => MyStatus.Region; set => MyStatus.Region = value; } - public byte Country { get => MyStatus.Country; set => MyStatus.Country = value; } - public byte ConsoleRegion { get => MyStatus.ConsoleRegion; set => MyStatus.ConsoleRegion = value; } - public override int Language { get => MyStatus.Language; set => MyStatus.Language = value; } - public override string OT { get => MyStatus.OT; set => MyStatus.OT = value; } - public int MultiplayerSpriteID { get => MyStatus.MultiplayerSpriteID; set => MyStatus.MultiplayerSpriteID = value; } - public override uint Money { get => Misc.Money; set => Misc.Money = value; } - - public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = value; } - public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = value; } - public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = value; } - public override uint SecondsToStart { get => GameTime.SecondsToStart; set => GameTime.SecondsToStart = value; } - public override uint SecondsToFame { get => GameTime.SecondsToFame; set => GameTime.SecondsToFame = value; } - - // Stat Records - public int RecordCount => 200; - public int GetRecord(int recordID) => Records.GetRecord(recordID); - public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value); - public int GetRecordMax(int recordID) => Records.GetRecordMax(recordID); - public int GetRecordOffset(int recordID) => Records.GetRecordOffset(recordID); - - // Inventory - public override IReadOnlyList Inventory { get => Items.Inventory; set => Items.Inventory = value; } - - // Storage - public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot); - public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30); - protected override int GetBoxWallpaperOffset(int box) => BoxLayout.GetBoxWallpaperOffset(box); - public override int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box); - public override void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value); - public override string GetBoxName(int box) => BoxLayout[box]; - public override void SetBoxName(int box, string value) => BoxLayout[box] = value; - public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = value; } - public override int BoxesUnlocked { get => BoxLayout.BoxesUnlocked; set => BoxLayout.BoxesUnlocked = value; } - public override byte[] BoxFlags { get => BoxLayout.BoxFlags; set => BoxLayout.BoxFlags = value; } - - protected override void SetPKM(PKM pkm, bool isParty = false) - { - PK7 pk7 = (PK7)pkm; - // Apply to this Save File - int CT = pk7.CurrentHandler; - DateTime Date = DateTime.Now; - pk7.Trade(this, Date.Day, Date.Month, Date.Year); - if (CT != pk7.CurrentHandler) // Logic updated Friendship - { - // Copy over the Friendship Value only under certain circumstances - if (pk7.HasMove(216)) // Return - pk7.CurrentFriendship = pk7.OppositeFriendship; - else if (pk7.HasMove(218)) // Frustration - pkm.CurrentFriendship = pk7.OppositeFriendship; - } - - pk7.FormArgumentElapsed = pk7.FormArgumentMaximum = 0; - pk7.FormArgumentRemain = (byte)GetFormArgument(pkm); - - pkm.RefreshChecksum(); - AddCountAcquired(pkm); - } - - private void AddCountAcquired(PKM pkm) - { - Records.AddRecord(pkm.WasEgg ? 008 : 006); // egg, capture - if (pkm.CurrentHandler == 1) - Records.AddRecord(011); // trade - if (!pkm.WasEgg) - { - Records.AddRecord(004); // wild encounters - Records.AddRecord(042); // balls used - } - } - - private static uint GetFormArgument(PKM pkm) - { - if (pkm.Form == 0) - return 0; - // Gen7 allows forms to be stored in the box with the current duration & form - // Just cap out the form duration anyways - return pkm.Species switch - { - (int)Species.Furfrou => 5u, // Furfrou - (int)Species.Hoopa => 3u, // Hoopa - _ => 0u, - }; - } - - protected override void SetDex(PKM pkm) => Zukan.SetDex(pkm); - public override bool GetCaught(int species) => Zukan.GetCaught(species); - public override bool GetSeen(int species) => Zukan.GetSeen(species); - - public override int PartyCount - { - get => Data[Party + (6 * SIZE_PARTY)]; - protected set => Data[Party + (6 * SIZE_PARTY)] = (byte)value; - } - - public override StorageSlotFlag GetSlotFlags(int index) - { - int team = Array.IndexOf(TeamSlots, index); - if (team < 0) - return StorageSlotFlag.None; - - team /= 6; - var result = (StorageSlotFlag)((int)StorageSlotFlag.BattleTeam1 << team); - if (BoxLayout.GetIsTeamLocked(team)) - result |= StorageSlotFlag.Locked; - return result; - } - - private int FusedCount => this is SAV7USUM ? 3 : 1; - - public int GetFusedSlotOffset(int slot) - { - if ((uint)slot >= FusedCount) - return -1; - return AllBlocks[08].Offset + (PokeCrypto.SIZE_6PARTY * slot); // 0x104*slot - } - - public override int DaycareSeedSize => Daycare7.DaycareSeedSize; // 128 bits - public override int GetDaycareSlotOffset(int loc, int slot) => Daycare.GetDaycareSlotOffset(slot); - public override bool? IsDaycareOccupied(int loc, int slot) => Daycare.GetIsOccupied(slot); - public override string GetDaycareRNGSeed(int loc) => Daycare.RNGSeed; - public override bool? IsDaycareHasEgg(int loc) => Daycare.HasEgg; - public override void SetDaycareOccupied(int loc, int slot, bool occupied) => Daycare.SetOccupied(slot, occupied); - public override void SetDaycareRNGSeed(int loc, string seed) => Daycare.RNGSeed = seed; - public override void SetDaycareHasEgg(int loc, bool hasEgg) => Daycare.HasEgg = hasEgg; - - protected override bool[] MysteryGiftReceivedFlags { get => MysteryGift.MysteryGiftReceivedFlags; set => MysteryGift.MysteryGiftReceivedFlags = value; } - protected override DataMysteryGift[] MysteryGiftCards { get => MysteryGift.MysteryGiftCards; set => MysteryGift.MysteryGiftCards = value; } - public bool GetEventFlag(int flagNumber) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); - return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); - } - - public void SetEventFlag(int flagNumber, bool value) - { - if ((uint)flagNumber >= EventFlagCount) - throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); - SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); - } - - public ushort GetWork(int index) => ReadUInt16LittleEndian(Data.AsSpan(EventWork + (index * 2))); - public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(EventWork)[(index * 2)..], value); + protected SAV7(byte[] data, int biOffset) : base(data, biOffset) + { } + + protected SAV7(int size, int biOffset) : base(size, biOffset) + { + } + + protected void ReloadBattleTeams() + { + var demo = this is SAV7SM && new ReadOnlySpan(Data, BoxLayout.Offset, 0x4C4).IsRangeEmpty(); // up to Battle Box values + if (demo || !State.Exportable) + { + BoxLayout.ClearBattleTeams(); + } + else // Valid slot locking info present + { + BoxLayout.LoadBattleTeams(); + } + } + + #region Blocks + public abstract MyItem Items { get; } + public abstract MysteryBlock7 MysteryGift { get; } + public abstract PokeFinder7 PokeFinder { get; } + public abstract JoinFesta7 Festa { get; } + public abstract Daycare7 Daycare { get; } + public abstract RecordBlock6 Records { get; } + public abstract PlayTime6 Played { get; } + public abstract MyStatus7 MyStatus { get; } + public abstract FieldMoveModelSave7 Overworld { get; } + public abstract Situation7 Situation { get; } + public abstract ConfigSave7 Config { get; } + public abstract GameTime7 GameTime { get; } + public abstract Misc7 Misc { get; } + public abstract Zukan7 Zukan { get; } + public abstract BoxLayout7 BoxLayout { get; } + public abstract BattleTree7 BattleTree { get; } + public abstract ResortSave7 ResortSave { get; } + public abstract FieldMenu7 FieldMenu { get; } + public abstract FashionBlock7 Fashion { get; } + public abstract HallOfFame7 Fame { get; } + #endregion + + // Configuration + protected override int SIZE_STORED => PokeCrypto.SIZE_6STORED; + protected override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY; + public override PKM BlankPKM => new PK7(); + public override Type PKMType => typeof(PK7); + + public override int BoxCount => 32; + public override int MaxEV => 252; + public override int Generation => 7; + public override EntityContext Context => EntityContext.Gen7; + protected override int GiftCountMax => 48; + protected override int GiftFlagMax => 0x100 * 8; + public abstract int EventFlagCount { get; } + public int EventWorkCount => 1000; + private int EventWork => AllBlocks[05].Offset; + private int EventFlag => EventWork + (EventWorkCount * 2); // After Event Const (u16)*n + public override int OTLength => 12; + public override int NickLength => 12; + + public override int MaxBallID => Legal.MaxBallID_7; // 26 + public override int MaxGameID => Legal.MaxGameID_7; + protected override PKM GetPKM(byte[] data) => new PK7(data); + protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray6(data); + + // Feature Overrides + + // Blocks & Offsets + private const int MemeCryptoBlock = 36; + + protected void ClearMemeCrypto() + { + new byte[0x80].CopyTo(Data, AllBlocks[MemeCryptoBlock].Offset + 0x100); + } + + protected override byte[] GetFinalData() + { + BoxLayout.SaveBattleTeams(); + SetChecksums(); + var result = MemeCrypto.Resign7(Data); + Debug.Assert(result != Data); + return result; + } + + public override GameVersion Version => Game switch + { + (int)GameVersion.SN => GameVersion.SN, + (int)GameVersion.MN => GameVersion.MN, + (int)GameVersion.US => GameVersion.US, + (int)GameVersion.UM => GameVersion.UM, + _ => GameVersion.Invalid, + }; + + public sealed override string GetString(ReadOnlySpan data) => StringConverter7.GetString(data); + + public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter7.SetString(destBuffer, value, maxLength, Language, option); + } + + // Player Information + public override int TID { get => MyStatus.TID; set => MyStatus.TID = value; } + public override int SID { get => MyStatus.SID; set => MyStatus.SID = value; } + public override int Game { get => MyStatus.Game; set => MyStatus.Game = value; } + public override int Gender { get => MyStatus.Gender; set => MyStatus.Gender = value; } + public int GameSyncIDSize => MyStatus7.GameSyncIDSize; // 64 bits + public string GameSyncID { get => MyStatus.GameSyncID; set => MyStatus.GameSyncID = value; } + public byte Region { get => MyStatus.Region; set => MyStatus.Region = value; } + public byte Country { get => MyStatus.Country; set => MyStatus.Country = value; } + public byte ConsoleRegion { get => MyStatus.ConsoleRegion; set => MyStatus.ConsoleRegion = value; } + public override int Language { get => MyStatus.Language; set => MyStatus.Language = value; } + public override string OT { get => MyStatus.OT; set => MyStatus.OT = value; } + public int MultiplayerSpriteID { get => MyStatus.MultiplayerSpriteID; set => MyStatus.MultiplayerSpriteID = value; } + public override uint Money { get => Misc.Money; set => Misc.Money = value; } + + public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = value; } + public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = value; } + public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = value; } + public override uint SecondsToStart { get => GameTime.SecondsToStart; set => GameTime.SecondsToStart = value; } + public override uint SecondsToFame { get => GameTime.SecondsToFame; set => GameTime.SecondsToFame = value; } + + // Stat Records + public int RecordCount => 200; + public int GetRecord(int recordID) => Records.GetRecord(recordID); + public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value); + public int GetRecordMax(int recordID) => Records.GetRecordMax(recordID); + public int GetRecordOffset(int recordID) => Records.GetRecordOffset(recordID); + + // Inventory + public override IReadOnlyList Inventory { get => Items.Inventory; set => Items.Inventory = value; } + + // Storage + public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot); + public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30); + protected override int GetBoxWallpaperOffset(int box) => BoxLayout.GetBoxWallpaperOffset(box); + public override int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box); + public override void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value); + public override string GetBoxName(int box) => BoxLayout[box]; + public override void SetBoxName(int box, string value) => BoxLayout[box] = value; + public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = value; } + public override int BoxesUnlocked { get => BoxLayout.BoxesUnlocked; set => BoxLayout.BoxesUnlocked = value; } + public override byte[] BoxFlags { get => BoxLayout.BoxFlags; set => BoxLayout.BoxFlags = value; } + + protected override void SetPKM(PKM pk, bool isParty = false) + { + PK7 pk7 = (PK7)pk; + // Apply to this Save File + int CT = pk7.CurrentHandler; + DateTime Date = DateTime.Now; + pk7.Trade(this, Date.Day, Date.Month, Date.Year); + if (CT != pk7.CurrentHandler) // Logic updated Friendship + { + // Copy over the Friendship Value only under certain circumstances + if (pk7.HasMove(216)) // Return + pk7.CurrentFriendship = pk7.OppositeFriendship; + else if (pk7.HasMove(218)) // Frustration + pk.CurrentFriendship = pk7.OppositeFriendship; + } + + pk7.FormArgumentElapsed = pk7.FormArgumentMaximum = 0; + pk7.FormArgumentRemain = (byte)GetFormArgument(pk); + + pk.RefreshChecksum(); + AddCountAcquired(pk); + } + + private void AddCountAcquired(PKM pk) + { + Records.AddRecord(pk.WasEgg ? 008 : 006); // egg, capture + if (pk.CurrentHandler == 1) + Records.AddRecord(011); // trade + if (!pk.WasEgg) + { + Records.AddRecord(004); // wild encounters + Records.AddRecord(042); // balls used + } + } + + private static uint GetFormArgument(PKM pk) + { + if (pk.Form == 0) + return 0; + // Gen7 allows forms to be stored in the box with the current duration & form + // Just cap out the form duration anyways + return pk.Species switch + { + (int)Species.Furfrou => 5u, // Furfrou + (int)Species.Hoopa => 3u, // Hoopa + _ => 0u, + }; + } + + protected override void SetDex(PKM pk) => Zukan.SetDex(pk); + public override bool GetCaught(int species) => Zukan.GetCaught(species); + public override bool GetSeen(int species) => Zukan.GetSeen(species); + + public override int PartyCount + { + get => Data[Party + (6 * SIZE_PARTY)]; + protected set => Data[Party + (6 * SIZE_PARTY)] = (byte)value; + } + + public override StorageSlotSource GetSlotFlags(int index) + { + int team = Array.IndexOf(TeamSlots, index); + if (team < 0) + return StorageSlotSource.None; + + team /= 6; + var result = (StorageSlotSource)((int)StorageSlotSource.BattleTeam1 << team); + if (BoxLayout.GetIsTeamLocked(team)) + result |= StorageSlotSource.Locked; + return result; + } + + private int FusedCount => this is SAV7USUM ? 3 : 1; + + public int GetFusedSlotOffset(int slot) + { + if ((uint)slot >= FusedCount) + return -1; + return AllBlocks[08].Offset + (PokeCrypto.SIZE_6PARTY * slot); // 0x104*slot + } + + public override int DaycareSeedSize => Daycare7.DaycareSeedSize; // 128 bits + public override int GetDaycareSlotOffset(int loc, int slot) => Daycare.GetDaycareSlotOffset(slot); + public override bool? IsDaycareOccupied(int loc, int slot) => Daycare.GetIsOccupied(slot); + public override string GetDaycareRNGSeed(int loc) => Daycare.RNGSeed; + public override bool? IsDaycareHasEgg(int loc) => Daycare.HasEgg; + public override void SetDaycareOccupied(int loc, int slot, bool occupied) => Daycare.SetOccupied(slot, occupied); + public override void SetDaycareRNGSeed(int loc, string seed) => Daycare.RNGSeed = seed; + public override void SetDaycareHasEgg(int loc, bool hasEgg) => Daycare.HasEgg = hasEgg; + + protected override bool[] MysteryGiftReceivedFlags { get => MysteryGift.MysteryGiftReceivedFlags; set => MysteryGift.MysteryGiftReceivedFlags = value; } + protected override DataMysteryGift[] MysteryGiftCards { get => MysteryGift.MysteryGiftCards; set => MysteryGift.MysteryGiftCards = value; } + public bool GetEventFlag(int flagNumber) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})."); + return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7); + } + + public void SetEventFlag(int flagNumber, bool value) + { + if ((uint)flagNumber >= EventFlagCount) + throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})."); + SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value); + } + + public ushort GetWork(int index) => ReadUInt16LittleEndian(Data.AsSpan(EventWork + (index * 2))); + public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(EventWork)[(index * 2)..], value); } diff --git a/PKHeX.Core/Saves/SAV7SM.cs b/PKHeX.Core/Saves/SAV7SM.cs index b68f69691..abe3a8086 100644 --- a/PKHeX.Core/Saves/SAV7SM.cs +++ b/PKHeX.Core/Saves/SAV7SM.cs @@ -2,79 +2,78 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SAV7SM : SAV7, ISaveBlock7SM { - public sealed class SAV7SM : SAV7, ISaveBlock7SM + public SAV7SM(byte[] data) : base(data, SaveBlockAccessor7SM.BlockMetadataOffset) { - public SAV7SM(byte[] data) : base(data, SaveBlockAccessor7SM.BlockMetadataOffset) - { - Blocks = new SaveBlockAccessor7SM(this); - Initialize(); - ClearMemeCrypto(); - } + Blocks = new SaveBlockAccessor7SM(this); + Initialize(); + ClearMemeCrypto(); + } - public SAV7SM() : base(SaveUtil.SIZE_G7SM, SaveBlockAccessor7SM.BlockMetadataOffset) - { - Blocks = new SaveBlockAccessor7SM(this); - Initialize(); - ClearBoxes(); - } + public SAV7SM() : base(SaveUtil.SIZE_G7SM, SaveBlockAccessor7SM.BlockMetadataOffset) + { + Blocks = new SaveBlockAccessor7SM(this); + Initialize(); + ClearBoxes(); + } - private void Initialize() - { - Party = Blocks.BlockInfo[04].Offset; - PokeDex = Blocks.BlockInfo[06].Offset; + private void Initialize() + { + Party = Blocks.BlockInfo[04].Offset; + PokeDex = Blocks.BlockInfo[06].Offset; - TeamSlots = Blocks.BoxLayout.TeamSlots; - Box = Blocks.BlockInfo[14].Offset; - WondercardData = Blocks.MysteryGift.Offset; - DaycareOffset = Blocks.Daycare.Offset; + TeamSlots = Blocks.BoxLayout.TeamSlots; + Box = Blocks.BlockInfo[14].Offset; + WondercardData = Blocks.MysteryGift.Offset; + DaycareOffset = Blocks.Daycare.Offset; - ReloadBattleTeams(); - } + ReloadBattleTeams(); + } - public override PersonalTable Personal => PersonalTable.SM; - public override IReadOnlyList HeldItems => Legal.HeldItems_SM; - protected override SaveFile CloneInternal() => new SAV7SM((byte[])Data.Clone()); + public override PersonalTable Personal => PersonalTable.SM; + public override IReadOnlyList HeldItems => Legal.HeldItems_SM; + protected override SaveFile CloneInternal() => new SAV7SM((byte[])Data.Clone()); - #region Blocks - public SaveBlockAccessor7SM Blocks { get; } - public override IReadOnlyList AllBlocks => Blocks.BlockInfo; - public override MyItem Items => Blocks.Items; - public override MysteryBlock7 MysteryGift => Blocks.MysteryGift; - public override PokeFinder7 PokeFinder => Blocks.PokeFinder; - public override JoinFesta7 Festa => Blocks.Festa; - public override Daycare7 Daycare => Blocks.Daycare; - public override RecordBlock6 Records => Blocks.Records; - public override PlayTime6 Played => Blocks.Played; - public override MyStatus7 MyStatus => Blocks.MyStatus; - public override FieldMoveModelSave7 Overworld => Blocks.Overworld; - public override Situation7 Situation => Blocks.Situation; - public override ConfigSave7 Config => Blocks.Config; - public override GameTime7 GameTime => Blocks.GameTime; - public override Misc7 Misc => Blocks.Misc; - public override Zukan7 Zukan => Blocks.Zukan; - public override BoxLayout7 BoxLayout => Blocks.BoxLayout; - public override BattleTree7 BattleTree => Blocks.BattleTree; - public override ResortSave7 ResortSave => Blocks.ResortSave; - public override FieldMenu7 FieldMenu => Blocks.FieldMenu; - public override FashionBlock7 Fashion => Blocks.Fashion; - public override HallOfFame7 Fame => Blocks.Fame; - #endregion + #region Blocks + public SaveBlockAccessor7SM Blocks { get; } + public override IReadOnlyList AllBlocks => Blocks.BlockInfo; + public override MyItem Items => Blocks.Items; + public override MysteryBlock7 MysteryGift => Blocks.MysteryGift; + public override PokeFinder7 PokeFinder => Blocks.PokeFinder; + public override JoinFesta7 Festa => Blocks.Festa; + public override Daycare7 Daycare => Blocks.Daycare; + public override RecordBlock6 Records => Blocks.Records; + public override PlayTime6 Played => Blocks.Played; + public override MyStatus7 MyStatus => Blocks.MyStatus; + public override FieldMoveModelSave7 Overworld => Blocks.Overworld; + public override Situation7 Situation => Blocks.Situation; + public override ConfigSave7 Config => Blocks.Config; + public override GameTime7 GameTime => Blocks.GameTime; + public override Misc7 Misc => Blocks.Misc; + public override Zukan7 Zukan => Blocks.Zukan; + public override BoxLayout7 BoxLayout => Blocks.BoxLayout; + public override BattleTree7 BattleTree => Blocks.BattleTree; + public override ResortSave7 ResortSave => Blocks.ResortSave; + public override FieldMenu7 FieldMenu => Blocks.FieldMenu; + public override FashionBlock7 Fashion => Blocks.Fashion; + public override HallOfFame7 Fame => Blocks.Fame; + #endregion - public override int EventFlagCount => 4000; - public override int MaxMoveID => Legal.MaxMoveID_7; - public override int MaxSpeciesID => Legal.MaxSpeciesID_7; - public override int MaxItemID => Legal.MaxItemID_7; - public override int MaxAbilityID => Legal.MaxAbilityID_7; + public override int EventFlagCount => 4000; + public override int MaxMoveID => Legal.MaxMoveID_7; + public override int MaxSpeciesID => Legal.MaxSpeciesID_7; + public override int MaxItemID => Legal.MaxItemID_7; + public override int MaxAbilityID => Legal.MaxAbilityID_7; - private const ulong MagearnaConst = 0xCBE05F18356504AC; + private const ulong MagearnaConst = 0xCBE05F18356504AC; - public void UpdateMagearnaConstant() - { - var flag = GetEventFlag(3100); - ulong value = flag ? MagearnaConst : 0ul; - WriteUInt64LittleEndian(Data.AsSpan(Blocks.BlockInfo[35].Offset + 0x168), value); - } + public void UpdateMagearnaConstant() + { + var flag = GetEventFlag(3100); + ulong value = flag ? MagearnaConst : 0ul; + WriteUInt64LittleEndian(Data.AsSpan(Blocks.BlockInfo[35].Offset + 0x168), value); } } diff --git a/PKHeX.Core/Saves/SAV7USUM.cs b/PKHeX.Core/Saves/SAV7USUM.cs index 7aaf43c6d..8e1b74cb2 100644 --- a/PKHeX.Core/Saves/SAV7USUM.cs +++ b/PKHeX.Core/Saves/SAV7USUM.cs @@ -1,70 +1,69 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SAV7USUM : SAV7, ISaveBlock7USUM { - public sealed class SAV7USUM : SAV7, ISaveBlock7USUM + public SAV7USUM(byte[] data) : base(data, SaveBlockAccessor7USUM.BlockMetadataOffset) { - public SAV7USUM(byte[] data) : base(data, SaveBlockAccessor7USUM.BlockMetadataOffset) - { - Blocks = new SaveBlockAccessor7USUM(this); - Initialize(); - ClearMemeCrypto(); - } - - public SAV7USUM() : base(SaveUtil.SIZE_G7USUM, boUU) - { - Blocks = new SaveBlockAccessor7USUM(this); - Initialize(); - } - - private void Initialize() - { - Party = Blocks.BlockInfo[04].Offset; - PokeDex = Blocks.BlockInfo[06].Offset; - - TeamSlots = Blocks.BoxLayout.TeamSlots; - Box = Blocks.BlockInfo[14].Offset; - WondercardData = Blocks.MysteryGift.Offset; - DaycareOffset = Blocks.Daycare.Offset; - - ReloadBattleTeams(); - } - - public override PersonalTable Personal => PersonalTable.USUM; - public override IReadOnlyList HeldItems => Legal.HeldItems_USUM; - protected override SaveFile CloneInternal() => new SAV7USUM((byte[])Data.Clone()); - public override int EventFlagCount => 4960; - public override int MaxMoveID => Legal.MaxMoveID_7_USUM; - public override int MaxSpeciesID => Legal.MaxSpeciesID_7_USUM; - public override int MaxItemID => Legal.MaxItemID_7_USUM; - public override int MaxAbilityID => Legal.MaxAbilityID_7_USUM; - - private const int boUU = SaveUtil.SIZE_G7USUM - 0x200; - - #region Blocks - public SaveBlockAccessor7USUM Blocks { get; } - public override IReadOnlyList AllBlocks => Blocks.BlockInfo; - public override MyItem Items => Blocks.Items; - public override MysteryBlock7 MysteryGift => Blocks.MysteryGift; - public override PokeFinder7 PokeFinder => Blocks.PokeFinder; - public override JoinFesta7 Festa => Blocks.Festa; - public override Daycare7 Daycare => Blocks.Daycare; - public override RecordBlock6 Records => Blocks.Records; - public override PlayTime6 Played => Blocks.Played; - public override MyStatus7 MyStatus => Blocks.MyStatus; - public override FieldMoveModelSave7 Overworld => Blocks.Overworld; - public override Situation7 Situation => Blocks.Situation; - public override ConfigSave7 Config => Blocks.Config; - public override GameTime7 GameTime => Blocks.GameTime; - public override Misc7 Misc => Blocks.Misc; - public override Zukan7 Zukan => Blocks.Zukan; - public override BoxLayout7 BoxLayout => Blocks.BoxLayout; - public override BattleTree7 BattleTree => Blocks.BattleTree; - public override ResortSave7 ResortSave => Blocks.ResortSave; - public override FieldMenu7 FieldMenu => Blocks.FieldMenu; - public override FashionBlock7 Fashion => Blocks.Fashion; - public override HallOfFame7 Fame => Blocks.Fame; - public BattleAgency7 BattleAgency => Blocks.BattleAgency; - #endregion + Blocks = new SaveBlockAccessor7USUM(this); + Initialize(); + ClearMemeCrypto(); } + + public SAV7USUM() : base(SaveUtil.SIZE_G7USUM, boUU) + { + Blocks = new SaveBlockAccessor7USUM(this); + Initialize(); + } + + private void Initialize() + { + Party = Blocks.BlockInfo[04].Offset; + PokeDex = Blocks.BlockInfo[06].Offset; + + TeamSlots = Blocks.BoxLayout.TeamSlots; + Box = Blocks.BlockInfo[14].Offset; + WondercardData = Blocks.MysteryGift.Offset; + DaycareOffset = Blocks.Daycare.Offset; + + ReloadBattleTeams(); + } + + public override PersonalTable Personal => PersonalTable.USUM; + public override IReadOnlyList HeldItems => Legal.HeldItems_USUM; + protected override SaveFile CloneInternal() => new SAV7USUM((byte[])Data.Clone()); + public override int EventFlagCount => 4960; + public override int MaxMoveID => Legal.MaxMoveID_7_USUM; + public override int MaxSpeciesID => Legal.MaxSpeciesID_7_USUM; + public override int MaxItemID => Legal.MaxItemID_7_USUM; + public override int MaxAbilityID => Legal.MaxAbilityID_7_USUM; + + private const int boUU = SaveUtil.SIZE_G7USUM - 0x200; + + #region Blocks + public SaveBlockAccessor7USUM Blocks { get; } + public override IReadOnlyList AllBlocks => Blocks.BlockInfo; + public override MyItem Items => Blocks.Items; + public override MysteryBlock7 MysteryGift => Blocks.MysteryGift; + public override PokeFinder7 PokeFinder => Blocks.PokeFinder; + public override JoinFesta7 Festa => Blocks.Festa; + public override Daycare7 Daycare => Blocks.Daycare; + public override RecordBlock6 Records => Blocks.Records; + public override PlayTime6 Played => Blocks.Played; + public override MyStatus7 MyStatus => Blocks.MyStatus; + public override FieldMoveModelSave7 Overworld => Blocks.Overworld; + public override Situation7 Situation => Blocks.Situation; + public override ConfigSave7 Config => Blocks.Config; + public override GameTime7 GameTime => Blocks.GameTime; + public override Misc7 Misc => Blocks.Misc; + public override Zukan7 Zukan => Blocks.Zukan; + public override BoxLayout7 BoxLayout => Blocks.BoxLayout; + public override BattleTree7 BattleTree => Blocks.BattleTree; + public override ResortSave7 ResortSave => Blocks.ResortSave; + public override FieldMenu7 FieldMenu => Blocks.FieldMenu; + public override FashionBlock7 Fashion => Blocks.Fashion; + public override HallOfFame7 Fame => Blocks.Fame; + public BattleAgency7 BattleAgency => Blocks.BattleAgency; + #endregion } diff --git a/PKHeX.Core/Saves/SAV7b.cs b/PKHeX.Core/Saves/SAV7b.cs index e9a4b137b..c7cb0742e 100644 --- a/PKHeX.Core/Saves/SAV7b.cs +++ b/PKHeX.Core/Saves/SAV7b.cs @@ -1,177 +1,176 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 object for games. +/// +public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IEventFlagArray { - /// - /// Generation 7 object for games. - /// - public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IEventFlagArray + protected internal override string ShortSummary => $"{OT} ({Version}) - {Blocks.Played.LastSavedTime}"; + public override string Extension => ".bin"; + public override IReadOnlyList PKMExtensions => EntityFileExtension.Extensions7b; + + public override Type PKMType => typeof(PB7); + public override PKM BlankPKM => new PB7(); + protected override int SIZE_STORED => PokeCrypto.SIZE_6PARTY; + protected override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY; + public override int SIZE_BOXSLOT => PokeCrypto.SIZE_6PARTY; + public override byte[] GetDataForBox(PKM pk) => pk.EncryptedPartyData; + + public override PersonalTable Personal => PersonalTable.GG; + public override IReadOnlyList HeldItems => Legal.HeldItems_GG; + + protected override SaveFile CloneInternal() => new SAV7b((byte[])Data.Clone()); + + public SaveBlockAccessor7b Blocks { get; } + public override IReadOnlyList AllBlocks => Blocks.BlockInfo; + + public SAV7b() : base(SaveUtil.SIZE_G7GG, 0xB8800) { - protected internal override string ShortSummary => $"{OT} ({Version}) - {Blocks.Played.LastSavedTime}"; - public override string Extension => ".bin"; - public override IReadOnlyList PKMExtensions => EntityFileExtension.Extensions7b; - - public override Type PKMType => typeof(PB7); - public override PKM BlankPKM => new PB7(); - protected override int SIZE_STORED => PokeCrypto.SIZE_6PARTY; - protected override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY; - public override int SIZE_BOXSLOT => PokeCrypto.SIZE_6PARTY; - public override byte[] GetDataForBox(PKM pkm) => pkm.EncryptedPartyData; - - public override PersonalTable Personal => PersonalTable.GG; - public override IReadOnlyList HeldItems => Legal.HeldItems_GG; - - protected override SaveFile CloneInternal() => new SAV7b((byte[])Data.Clone()); - - public SaveBlockAccessor7b Blocks { get; } - public override IReadOnlyList AllBlocks => Blocks.BlockInfo; - - public SAV7b() : base(SaveUtil.SIZE_G7GG, 0xB8800) - { - Blocks = new SaveBlockAccessor7b(this); - Initialize(); - ClearBoxes(); - } - - public SAV7b(byte[] data) : base(data, 0xB8800) - { - Blocks = new SaveBlockAccessor7b(this); - Initialize(); - } - - private void Initialize() - { - Box = Blocks.GetBlockOffset(BelugaBlockIndex.PokeListPokemon); - Party = Blocks.GetBlockOffset(BelugaBlockIndex.PokeListPokemon); - PokeDex = Blocks.GetBlockOffset(BelugaBlockIndex.Zukan); - - WondercardData = Blocks.GiftRecords.Offset; - } - - // Save Block accessors - public MyItem Items => Blocks.Items; - public Misc7b Misc => Blocks.Misc; - public Zukan7b Zukan => Blocks.Zukan; - public MyStatus7b Status => Blocks.Status; - public PlayTime7b Played => Blocks.Played; - public ConfigSave7b Config => Blocks.Config; - public EventWork7b EventWork => Blocks.EventWork; - public PokeListHeader Storage => Blocks.Storage; - public WB7Records GiftRecords => Blocks.GiftRecords; - public CaptureRecords Captured => Blocks.Captured; - - public override IReadOnlyList Inventory { get => Blocks.Items.Inventory; set => Blocks.Items.Inventory = value; } - - // Feature Overrides - public override int Generation => 7; - public override EntityContext Context => EntityContext.Gen7b; - public override int MaxMoveID => Legal.MaxMoveID_7b; - public override int MaxSpeciesID => Legal.MaxSpeciesID_7b; - public override int MaxItemID => Legal.MaxItemID_7b; - public override int MaxBallID => Legal.MaxBallID_7b; - public override int MaxGameID => Legal.MaxGameID_7b; - public override int MaxAbilityID => Legal.MaxAbilityID_7b; - - public override int MaxIV => 31; - public override int MaxEV => 252; - public override int OTLength => 12; - public override int NickLength => 12; - protected override int GiftCountMax => 48; - protected override int GiftFlagMax => 0x100 * 8; - public int EventFlagCount => 4160; // 0xDC0 (true max may be up to 0x7F less. 23A8 starts u64 hashvals) - - public override bool HasParty => false; // handled via team slots - - // BoxSlotCount => 1000 -- why couldn't this be a multiple of 30... - public override int BoxSlotCount => 25; - public override int BoxCount => 40; // 1000/25 - - public bool FixPreWrite() => Blocks.Storage.CompressStorage(); - - protected override void SetPKM(PKM pkm, bool isParty = false) - { - var pk = (PB7)pkm; - // Apply to this Save File - var Date = DateTime.Now; - pk.Trade(this, Date.Day, Date.Month, Date.Year); - pk.RefreshChecksum(); - } - - protected override void SetDex(PKM pkm) => Blocks.Zukan.SetDex(pkm); - public override bool GetCaught(int species) => Blocks.Zukan.GetCaught(species); - public override bool GetSeen(int species) => Blocks.Zukan.GetSeen(species); - - protected override PKM GetPKM(byte[] data) => new PB7(data); - protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray6(data); - public override int GetBoxOffset(int box) => Box + (box * BoxSlotCount * SIZE_BOXSLOT); - protected override IList[] SlotPointers => new[] { Blocks.Storage.PokeListInfo }; - - public override int GetPartyOffset(int slot) => Blocks.Storage.GetPartyOffset(slot); - public override int PartyCount { get => Blocks.Storage.PartyCount; protected set => Blocks.Storage.PartyCount = value; } - protected override void SetPartyValues(PKM pkm, bool isParty) => base.SetPartyValues(pkm, true); - - public override StorageSlotFlag GetSlotFlags(int index) - { - var result = StorageSlotFlag.None; - var header = Blocks.Storage.PokeListInfo; - int position = Array.IndexOf(header, index, 0, 6); - if (position >= 0) - result = (StorageSlotFlag)((int)StorageSlotFlag.Party1 << position); - if (header[PokeListHeader.STARTER] == index) - result |= StorageSlotFlag.Starter; - return result; - } - - public override string GetBoxName(int box) => $"Box {box + 1}"; - public override void SetBoxName(int box, string value) { } - - public override string GetString(ReadOnlySpan data) => StringConverter8.GetString(data); - - public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter8.SetString(destBuffer, value, maxLength); - } - - public override GameVersion Version => Game switch - { - (int)GameVersion.GP => GameVersion.GP, - (int)GameVersion.GE => GameVersion.GE, - _ => GameVersion.Invalid, - }; - - // Player Information - public override int TID { get => Blocks.Status.TID; set => Blocks.Status.TID = value; } - public override int SID { get => Blocks.Status.SID; set => Blocks.Status.SID = value; } - public override int Game { get => Blocks.Status.Game; set => Blocks.Status.Game = value; } - public override int Gender { get => Blocks.Status.Gender; set => Blocks.Status.Gender = value; } - public override int Language { get => Blocks.Status.Language; set => Blocks.Config.Language = Blocks.Status.Language = value; } // stored in multiple places - public override string OT { get => Blocks.Status.OT; set => Blocks.Status.OT = value; } - public override uint Money { get => Blocks.Misc.Money; set => Blocks.Misc.Money = value; } - - public override int PlayedHours { get => Blocks.Played.PlayedHours; set => Blocks.Played.PlayedHours = value; } - public override int PlayedMinutes { get => Blocks.Played.PlayedMinutes; set => Blocks.Played.PlayedMinutes = value; } - public override int PlayedSeconds { get => Blocks.Played.PlayedSeconds; set => Blocks.Played.PlayedSeconds = value; } - - /// - /// Gets the status of a desired Event Flag - /// - /// Event Flag to check - /// Flag is Set (true) or not Set (false) - public bool GetEventFlag(int flagNumber) => Blocks.EventWork.GetFlag(flagNumber); - - /// - /// Sets the status of a desired Event Flag - /// - /// Event Flag to check - /// Event Flag status to set - /// Flag is Set (true) or not Set (false) - public void SetEventFlag(int flagNumber, bool value) => Blocks.EventWork.SetFlag(flagNumber, value); - - protected override bool[] MysteryGiftReceivedFlags { get => Blocks.GiftRecords.GetFlags(); set => Blocks.GiftRecords.SetFlags(value); } - protected override DataMysteryGift[] MysteryGiftCards { get => Blocks.GiftRecords.GetRecords(); set => Blocks.GiftRecords.SetRecords((WR7[])value); } - - public int GameSyncIDSize => MyStatus7b.GameSyncIDSize; // 64 bits - public string GameSyncID { get => Blocks.Status.GameSyncID; set => Blocks.Status.GameSyncID = value; } + Blocks = new SaveBlockAccessor7b(this); + Initialize(); + ClearBoxes(); } + + public SAV7b(byte[] data) : base(data, 0xB8800) + { + Blocks = new SaveBlockAccessor7b(this); + Initialize(); + } + + private void Initialize() + { + Box = Blocks.GetBlockOffset(BelugaBlockIndex.PokeListPokemon); + Party = Blocks.GetBlockOffset(BelugaBlockIndex.PokeListPokemon); + PokeDex = Blocks.GetBlockOffset(BelugaBlockIndex.Zukan); + + WondercardData = Blocks.GiftRecords.Offset; + } + + // Save Block accessors + public MyItem Items => Blocks.Items; + public Misc7b Misc => Blocks.Misc; + public Zukan7b Zukan => Blocks.Zukan; + public MyStatus7b Status => Blocks.Status; + public PlayTime7b Played => Blocks.Played; + public ConfigSave7b Config => Blocks.Config; + public EventWork7b EventWork => Blocks.EventWork; + public PokeListHeader Storage => Blocks.Storage; + public WB7Records GiftRecords => Blocks.GiftRecords; + public CaptureRecords Captured => Blocks.Captured; + + public override IReadOnlyList Inventory { get => Blocks.Items.Inventory; set => Blocks.Items.Inventory = value; } + + // Feature Overrides + public override int Generation => 7; + public override EntityContext Context => EntityContext.Gen7b; + public override int MaxMoveID => Legal.MaxMoveID_7b; + public override int MaxSpeciesID => Legal.MaxSpeciesID_7b; + public override int MaxItemID => Legal.MaxItemID_7b; + public override int MaxBallID => Legal.MaxBallID_7b; + public override int MaxGameID => Legal.MaxGameID_7b; + public override int MaxAbilityID => Legal.MaxAbilityID_7b; + + public override int MaxIV => 31; + public override int MaxEV => 252; + public override int OTLength => 12; + public override int NickLength => 12; + protected override int GiftCountMax => 48; + protected override int GiftFlagMax => 0x100 * 8; + public int EventFlagCount => 4160; // 0xDC0 (true max may be up to 0x7F less. 23A8 starts u64 hashvals) + + public override bool HasParty => false; // handled via team slots + + // BoxSlotCount => 1000 -- why couldn't this be a multiple of 30... + public override int BoxSlotCount => 25; + public override int BoxCount => 40; // 1000/25 + + public bool FixPreWrite() => Blocks.Storage.CompressStorage(); + + protected override void SetPKM(PKM pk, bool isParty = false) + { + var pb7 = (PB7)pk; + // Apply to this Save File + var Date = DateTime.Now; + pb7.Trade(this, Date.Day, Date.Month, Date.Year); + pb7.RefreshChecksum(); + } + + protected override void SetDex(PKM pk) => Blocks.Zukan.SetDex(pk); + public override bool GetCaught(int species) => Blocks.Zukan.GetCaught(species); + public override bool GetSeen(int species) => Blocks.Zukan.GetSeen(species); + + protected override PKM GetPKM(byte[] data) => new PB7(data); + protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray6(data); + public override int GetBoxOffset(int box) => Box + (box * BoxSlotCount * SIZE_BOXSLOT); + protected override IList[] SlotPointers => new[] { Blocks.Storage.PokeListInfo }; + + public override int GetPartyOffset(int slot) => Blocks.Storage.GetPartyOffset(slot); + public override int PartyCount { get => Blocks.Storage.PartyCount; protected set => Blocks.Storage.PartyCount = value; } + protected override void SetPartyValues(PKM pk, bool isParty) => base.SetPartyValues(pk, true); + + public override StorageSlotSource GetSlotFlags(int index) + { + var result = StorageSlotSource.None; + var header = Blocks.Storage.PokeListInfo; + int position = Array.IndexOf(header, index, 0, 6); + if (position >= 0) + result = (StorageSlotSource)((int)StorageSlotSource.Party1 << position); + if (header[PokeListHeader.STARTER] == index) + result |= StorageSlotSource.Starter; + return result; + } + + public override string GetBoxName(int box) => $"Box {box + 1}"; + public override void SetBoxName(int box, string value) { } + + public override string GetString(ReadOnlySpan data) => StringConverter8.GetString(data); + + public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter8.SetString(destBuffer, value, maxLength); + } + + public override GameVersion Version => Game switch + { + (int)GameVersion.GP => GameVersion.GP, + (int)GameVersion.GE => GameVersion.GE, + _ => GameVersion.Invalid, + }; + + // Player Information + public override int TID { get => Blocks.Status.TID; set => Blocks.Status.TID = value; } + public override int SID { get => Blocks.Status.SID; set => Blocks.Status.SID = value; } + public override int Game { get => Blocks.Status.Game; set => Blocks.Status.Game = value; } + public override int Gender { get => Blocks.Status.Gender; set => Blocks.Status.Gender = value; } + public override int Language { get => Blocks.Status.Language; set => Blocks.Config.Language = Blocks.Status.Language = value; } // stored in multiple places + public override string OT { get => Blocks.Status.OT; set => Blocks.Status.OT = value; } + public override uint Money { get => Blocks.Misc.Money; set => Blocks.Misc.Money = value; } + + public override int PlayedHours { get => Blocks.Played.PlayedHours; set => Blocks.Played.PlayedHours = value; } + public override int PlayedMinutes { get => Blocks.Played.PlayedMinutes; set => Blocks.Played.PlayedMinutes = value; } + public override int PlayedSeconds { get => Blocks.Played.PlayedSeconds; set => Blocks.Played.PlayedSeconds = value; } + + /// + /// Gets the status of a desired Event Flag + /// + /// Event Flag to check + /// Flag is Set (true) or not Set (false) + public bool GetEventFlag(int flagNumber) => Blocks.EventWork.GetFlag(flagNumber); + + /// + /// Sets the status of a desired Event Flag + /// + /// Event Flag to check + /// Event Flag status to set + /// Flag is Set (true) or not Set (false) + public void SetEventFlag(int flagNumber, bool value) => Blocks.EventWork.SetFlag(flagNumber, value); + + protected override bool[] MysteryGiftReceivedFlags { get => Blocks.GiftRecords.GetFlags(); set => Blocks.GiftRecords.SetFlags(value); } + protected override DataMysteryGift[] MysteryGiftCards { get => Blocks.GiftRecords.GetRecords(); set => Blocks.GiftRecords.SetRecords((WR7[])value); } + + public int GameSyncIDSize => MyStatus7b.GameSyncIDSize; // 64 bits + public string GameSyncID { get => Blocks.Status.GameSyncID; set => Blocks.Status.GameSyncID = value; } } diff --git a/PKHeX.Core/Saves/SAV8BS.cs b/PKHeX.Core/Saves/SAV8BS.cs index f611915e8..f0145eda7 100644 --- a/PKHeX.Core/Saves/SAV8BS.cs +++ b/PKHeX.Core/Saves/SAV8BS.cs @@ -1,374 +1,373 @@ -using System; +using System; using System.Collections.Generic; using System.Security.Cryptography; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 8 object for games. +/// +public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IEventFlagArray, IEventWorkArray { - /// - /// Generation 8 object for games. - /// - public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IEventFlagArray, IEventWorkArray + // Save Data Attributes + protected internal override string ShortSummary => $"{OT} ({Version}) - {System.LastSavedTime}"; + public override string Extension => string.Empty; + + public override IReadOnlyList PKMExtensions => Array.FindAll(PKM.Extensions, f => { - // Save Data Attributes - protected internal override string ShortSummary => $"{OT} ({Version}) - {System.LastSavedTime}"; - public override string Extension => string.Empty; + int gen = f[^1] - 0x30; + return gen <= 8; + }); - public override IReadOnlyList PKMExtensions => Array.FindAll(PKM.Extensions, f => - { - int gen = f[^1] - 0x30; - return gen <= 8; - }); + public SAV8BS(byte[] data, bool exportable = true) : base(data, exportable) + { + FlagWork = new FlagWork8b(this, 0x00004); + Items = new MyItem8b(this, 0x0563C); + Underground = new UndergroundItemList8b(this, 0x111BC); + SelectBoundItems = new SaveItemShortcut8b(this, 0x14090); // size: 0x8 + PartyInfo = new Party8b(this, 0x14098); + BoxLayout = new BoxLayout8b(this, 0x148AA); // size: 0x64A + // 0x14EF4 - Box[40] - public SAV8BS(byte[] data, bool exportable = true) : base(data, exportable) - { - Work = new FlagWork8b(this, 0x00004); - Items = new MyItem8b(this, 0x0563C); - Underground = new UndergroundItemList8b(this, 0x111BC); - SelectBoundItems = new SaveItemShortcut8b(this, 0x14090); // size: 0x8 - PartyInfo = new Party8b(this, 0x14098); - BoxLayout = new BoxLayout8b(this, 0x148AA); // size: 0x64A - // 0x14EF4 - Box[40] + // PLAYER_DATA: + Config = new ConfigSave8b(this, 0x79B74); // size: 0x40 + MyStatus = new MyStatus8b(this, 0x79BB4); // size: 0x50 + Played = new PlayTime8b(this, 0x79C04); // size: 0x04 + Contest = new Contest8b(this, 0x79C08); // size: 0x720 - // PLAYER_DATA: - Config = new ConfigSave8b(this, 0x79B74); // size: 0x40 - MyStatus = new MyStatus8b(this, 0x79BB4); // size: 0x50 - Played = new PlayTime8b(this, 0x79C04); // size: 0x04 - Contest = new Contest8b(this, 0x79C08); // size: 0x720 + Zukan = new Zukan8b(this, 0x7A328); // size: 0x30B8 + BattleTrainer = new BattleTrainerStatus8b(this, 0x7D3E0); // size: 0x1618 + MenuSelection = new MenuSelect8b(this, 0x7E9F8); // size: 0x44 + FieldObjects = new FieldObjectSave8b(this, 0x7EA3C); // size: 0x109A0 (1000 * 0x44) + Records = new Record8b(this, 0x8F3DC); // size: 0x78 * 12 + Encounter = new EncounterSave8b(this, 0x8F97C); // size: 0x188 + Player = new PlayerData8b(this, 0x8FB04); // 0x80 + SealDeco = new SealBallDecoData8b(this, 0x8FB84); // size: 0x4288 + SealList = new SealList8b(this, 0x93E0C); // size: 0x960 SaveSealData[200] + Random = new RandomGroup8b(this, 0x9476C); // size: 0x630 + FieldGimmick = new FieldGimmickSave8b(this, 0x94D9C); // FieldGimmickSaveData; int[3] gearRotate + BerryTrees = new BerryTreeGrowSave8b(this, 0x94DA8); // size: 0x808 + Poffins = new PoffinSaveData8b(this, 0x955B0); // size: 0x644 + BattleTower = new BattleTowerWork8b(this, 0x95BF4); // size: 0x1B8 + System = new SystemData8b(this, 0x95DAC); // size: 0x138 + Poketch = new Poketch8b(this, 0x95EE4); // todo + Daycare = new Daycare8b(this, 0x96080); // 0x2C0 + // 0x96340 - _DENDOU_SAVEDATA; DENDOU_RECORD[30], POKEMON_DATA_INSIDE[6], ushort[4] ? + // BadgeSaveData; byte[8] + // BoukenNote; byte[24] + // TV_DATA (int[48], TV_STR_DATA[42]), (int[37], bool[37])*2, (int[8], int[8]), TV_STR_DATA[10]; 144 128bit zeroed (900 bytes?)? + UgSaveData = new UgSaveData8b(this, 0x9A89C); // size: 0x27A0 + // 0x9D03C - GMS_DATA // size: 0x31304, (GMS_POINT_DATA[650], ushort, ushort, byte)?; substructure GMS_POINT_HISTORY_DATA[5] + // 0xCE340 - PLAYER_NETWORK_DATA; bcatFlagArray byte[1300] + UnionSave = new UnionSaveData8b(this, 0xCEA10); // size: 0xC + ContestPhotoLanguage = new ContestPhotoLanguage8b(this, 0xCEA1C); // size: 0x18 + ZukanExtra = new ZukanSpinda8b(this, 0xCEA34); // size: 0x64 (100) + // CON_PHOTO_EXT_DATA[5] + // GMS_POINT_HISTORY_EXT_DATA[3250] + UgCount = new UgCountRecord8b(this, 0xE8178); // size: 0x20 + // 0xE8198 - ReBuffnameData; RE_DENDOU_RECORD[30], RE_DENDOU_POKEMON_DATA_INSIDE[6] (0x20) = 0x1680 + // 0xE9818 -- 0x10 byte[] MD5 hash of all savedata; - Zukan = new Zukan8b(this, 0x7A328); // size: 0x30B8 - BattleTrainer = new BattleTrainerStatus8b(this, 0x7D3E0); // size: 0x1618 - MenuSelection = new MenuSelect8b(this, 0x7E9F8); // size: 0x44 - FieldObjects = new FieldObjectSave8b(this, 0x7EA3C); // size: 0x109A0 (1000 * 0x44) - Records = new Record8b(this, 0x8F3DC); // size: 0x78 * 12 - Encounter = new EncounterSave8b(this, 0x8F97C); // size: 0x188 - Player = new PlayerData8b(this, 0x8FB04); // 0x80 - SealDeco = new SealBallDecoData8b(this, 0x8FB84); // size: 0x4288 - SealList = new SealList8b(this, 0x93E0C); // size: 0x960 SaveSealData[200] - Random = new RandomGroup8b(this, 0x9476C); // size: 0x630 - FieldGimmick = new FieldGimmickSave8b(this, 0x94D9C); // FieldGimmickSaveData; int[3] gearRotate - BerryTrees = new BerryTreeGrowSave8b(this, 0x94DA8); // size: 0x808 - Poffins = new PoffinSaveData8b(this, 0x955B0); // size: 0x644 - BattleTower = new BattleTowerWork8b(this, 0x95BF4); // size: 0x1B8 - System = new SystemData8b(this, 0x95DAC); // size: 0x138 - Poketch = new Poketch8b(this, 0x95EE4); // todo - Daycare = new Daycare8b(this, 0x96080); // 0x2C0 - // 0x96340 - _DENDOU_SAVEDATA; DENDOU_RECORD[30], POKEMON_DATA_INSIDE[6], ushort[4] ? - // BadgeSaveData; byte[8] - // BoukenNote; byte[24] - // TV_DATA (int[48], TV_STR_DATA[42]), (int[37], bool[37])*2, (int[8], int[8]), TV_STR_DATA[10]; 144 128bit zeroed (900 bytes?)? - UgSaveData = new UgSaveData8b(this, 0x9A89C); // size: 0x27A0 - // 0x9D03C - GMS_DATA // size: 0x31304, (GMS_POINT_DATA[650], ushort, ushort, byte)?; substructure GMS_POINT_HISTORY_DATA[5] - // 0xCE340 - PLAYER_NETWORK_DATA; bcatFlagArray byte[1300] - UnionSave = new UnionSaveData8b(this, 0xCEA10); // size: 0xC - ContestPhotoLanguage = new ContestPhotoLanguage8b(this, 0xCEA1C); // size: 0x18 - ZukanExtra = new ZukanSpinda8b(this, 0xCEA34); // size: 0x64 (100) - // CON_PHOTO_EXT_DATA[5] - // GMS_POINT_HISTORY_EXT_DATA[3250] - UgCount = new UgCountRecord8b(this, 0xE8178); // size: 0x20 - // 0xE8198 - ReBuffnameData; RE_DENDOU_RECORD[30], RE_DENDOU_POKEMON_DATA_INSIDE[6] (0x20) = 0x1680 - // 0xE9818 -- 0x10 byte[] MD5 hash of all savedata; + // v1.1 additions + RecordAdd = new RecordAddData8b(this, 0xE9828); // size: 0x3C0 + MysteryRecords = new MysteryBlock8b(this, 0xE9BE8); // size: ??? + // POKETCH_POKETORE_COUNT_ARRAY -- (u16 species, u16 unused, i32 count, i32 reserved, i32 reserved)[3] = 0x10bytes + // PLAYREPORT_DATA -- reporting player progress online? 248 bytes? + // MT_DATA mtData; -- 0x400 bytes + // DENDOU_SAVE_ADD -- language tracking of members (hall of fame?); ADD_POKE_MEMBER[30], ADD_POKE[6] - // v1.1 additions - RecordAdd = new RecordAddData8b(this, 0xE9828); // size: 0x3C0 - MysteryRecords = new MysteryBlock8b(this, 0xE9BE8); // size: ??? - // POKETCH_POKETORE_COUNT_ARRAY -- (u16 species, u16 unused, i32 count, i32 reserved, i32 reserved)[3] = 0x10bytes - // PLAYREPORT_DATA -- reporting player progress online? 248 bytes? - // MT_DATA mtData; -- 0x400 bytes - // DENDOU_SAVE_ADD -- language tracking of members (hall of fame?); ADD_POKE_MEMBER[30], ADD_POKE[6] - - // v1.2 additions - // ReBuffnameData reBuffNameDat -- RE_DENDOU_RECORD[], RE_DENDOU_RECORD is an RE_DENDOU_POKEMON_DATA_INSIDE[] with nicknames - // PLAYREPORT_DATA playReportData sizeof(0xF8) - // PLAYREPORT_DATA playReportDataRef sizeof(0xF8) + // v1.2 additions + // ReBuffnameData reBuffNameDat -- RE_DENDOU_RECORD[], RE_DENDOU_RECORD is an RE_DENDOU_POKEMON_DATA_INSIDE[] with nicknames + // PLAYREPORT_DATA playReportData sizeof(0xF8) + // PLAYREPORT_DATA playReportDataRef sizeof(0xF8) Initialize(); - } + } - public SAV8BS() : this(new byte[SaveUtil.SIZE_G8BDSP_3], false) => SaveRevision = (int)Gem8Version.V1_3; + public SAV8BS() : this(new byte[SaveUtil.SIZE_G8BDSP_3], false) => SaveRevision = (int)Gem8Version.V1_3; - private void Initialize() + private void Initialize() + { + Box = 0x14EF4; + Party = PartyInfo.Offset; + PokeDex = Zukan.PokeDex; + DaycareOffset = Daycare.Offset; + + ReloadBattleTeams(); + TeamSlots = BoxLayout.TeamSlots; + } + + // Configuration + protected override int SIZE_STORED => PokeCrypto.SIZE_8STORED; + protected override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY; + public override int SIZE_BOXSLOT => PokeCrypto.SIZE_8PARTY; + public override PKM BlankPKM => new PB8(); + public override Type PKMType => typeof(PB8); + + public override int BoxCount => BoxLayout8b.BoxCount; + public override int MaxEV => 252; + + public override int Generation => 8; + public override EntityContext Context => EntityContext.Gen8b; + public override PersonalTable Personal => PersonalTable.BDSP; + public override int OTLength => 12; + public override int NickLength => 12; + public override int MaxMoveID => Legal.MaxMoveID_8b; + public override int MaxSpeciesID => Legal.MaxSpeciesID_8b; + public override int MaxItemID => Legal.MaxItemID_8b; + public override int MaxBallID => Legal.MaxBallID_8b; + public override int MaxGameID => Legal.MaxGameID_8a; + public override int MaxAbilityID => Legal.MaxAbilityID_8b; + + public bool HasFirstSaveFileExpansion => (Gem8Version)SaveRevision >= Gem8Version.V1_1; + public bool HasSecondSaveFileExpansion => (Gem8Version)SaveRevision >= Gem8Version.V1_2; + + public int SaveRevision + { + get => ReadInt32LittleEndian(Data.AsSpan(0)); + init => WriteInt32LittleEndian(Data.AsSpan(0), value); + } + + public string SaveRevisionString => ((Gem8Version)SaveRevision).GetSuffixString(); + + public override IReadOnlyList HeldItems => Legal.HeldItems_BS; + protected override SaveFile CloneInternal() => new SAV8BS((byte[])(Data.Clone())); + + protected override byte[] GetFinalData() + { + BoxLayout.SaveBattleTeams(); + return base.GetFinalData(); + } + + private void ReloadBattleTeams() + { + if (!State.Exportable) + BoxLayout.ClearBattleTeams(); + else // Valid slot locking info present + BoxLayout.LoadBattleTeams(); + } + + public override StorageSlotSource GetSlotFlags(int index) + { + int team = Array.IndexOf(TeamSlots, index); + if (team < 0) + return StorageSlotSource.None; + + team /= 6; + var result = (StorageSlotSource)((int)StorageSlotSource.BattleTeam1 << team); + if (BoxLayout.GetIsTeamLocked(team)) + result |= StorageSlotSource.Locked; + return result; + } + + #region Checksums + + private const int HashOffset = SaveUtil.SIZE_G8BDSP - 0x10; + private Span CurrentHash => Data.AsSpan(SaveUtil.SIZE_G8BDSP - 0x10, 0x10); + + private byte[] ComputeHash() + { + CurrentHash.Clear(); + using var md5 = new MD5CryptoServiceProvider(); + return md5.ComputeHash(Data); + } + + protected override void SetChecksums() => ComputeHash().CopyTo(Data, HashOffset); + public override string ChecksumInfo => !ChecksumsValid ? "MD5 Hash Invalid" : string.Empty; + + public override bool ChecksumsValid + { + get { - Box = 0x14EF4; - Party = PartyInfo.Offset; - PokeDex = Zukan.PokeDex; - DaycareOffset = Daycare.Offset; - - ReloadBattleTeams(); - TeamSlots = BoxLayout.TeamSlots; - } - - // Configuration - protected override int SIZE_STORED => PokeCrypto.SIZE_8STORED; - protected override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY; - public override int SIZE_BOXSLOT => PokeCrypto.SIZE_8PARTY; - public override PKM BlankPKM => new PB8(); - public override Type PKMType => typeof(PB8); - - public override int BoxCount => BoxLayout8b.BoxCount; - public override int MaxEV => 252; - - public override int Generation => 8; - public override EntityContext Context => EntityContext.Gen8b; - public override PersonalTable Personal => PersonalTable.BDSP; - public override int OTLength => 12; - public override int NickLength => 12; - public override int MaxMoveID => Legal.MaxMoveID_8b; - public override int MaxSpeciesID => Legal.MaxSpeciesID_8b; - public override int MaxItemID => Legal.MaxItemID_8b; - public override int MaxBallID => Legal.MaxBallID_8b; - public override int MaxGameID => Legal.MaxGameID_8a; - public override int MaxAbilityID => Legal.MaxAbilityID_8b; - - public bool HasFirstSaveFileExpansion => (Gem8Version)SaveRevision >= Gem8Version.V1_1; - public bool HasSecondSaveFileExpansion => (Gem8Version)SaveRevision >= Gem8Version.V1_2; - - public int SaveRevision - { - get => ReadInt32LittleEndian(Data.AsSpan(0)); - init => WriteInt32LittleEndian(Data.AsSpan(0), value); - } - - public string SaveRevisionString => ((Gem8Version)SaveRevision).GetSuffixString(); - - public override IReadOnlyList HeldItems => Legal.HeldItems_BS; - protected override SaveFile CloneInternal() => new SAV8BS((byte[])(Data.Clone())); - - protected override byte[] GetFinalData() - { - BoxLayout.SaveBattleTeams(); - return base.GetFinalData(); - } - - private void ReloadBattleTeams() - { - if (!State.Exportable) - BoxLayout.ClearBattleTeams(); - else // Valid slot locking info present - BoxLayout.LoadBattleTeams(); - } - - public override StorageSlotFlag GetSlotFlags(int index) - { - int team = Array.IndexOf(TeamSlots, index); - if (team < 0) - return StorageSlotFlag.None; - - team /= 6; - var result = (StorageSlotFlag)((int)StorageSlotFlag.BattleTeam1 << team); - if (BoxLayout.GetIsTeamLocked(team)) - result |= StorageSlotFlag.Locked; + // Cache hash and restore it after computation + var original = CurrentHash.ToArray(); + var newHash = ComputeHash(); + var result = newHash.AsSpan().SequenceEqual(original); + original.AsSpan().CopyTo(CurrentHash); return result; } - - #region Checksums - - private const int HashOffset = SaveUtil.SIZE_G8BDSP - 0x10; - private Span CurrentHash => Data.AsSpan(SaveUtil.SIZE_G8BDSP - 0x10, 0x10); - - private byte[] ComputeHash() - { - CurrentHash.Clear(); - using var md5 = new MD5CryptoServiceProvider(); - return md5.ComputeHash(Data); - } - - protected override void SetChecksums() => ComputeHash().CopyTo(Data, HashOffset); - public override string ChecksumInfo => !ChecksumsValid ? "MD5 Hash Invalid" : string.Empty; - - public override bool ChecksumsValid - { - get - { - // Cache hash and restore it after computation - var original = CurrentHash.ToArray(); - var newHash = ComputeHash(); - var result = newHash.AsSpan().SequenceEqual(original); - original.AsSpan().CopyTo(CurrentHash); - return result; - } - } - - #endregion - - protected override PKM GetPKM(byte[] data) => new PB8(data); - protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray8(data); - - #region Blocks - // public Box8 BoxInfo { get; } - public FlagWork8b Work { get; } - public MyItem8b Items { get; } - public UndergroundItemList8b Underground { get; } - public SaveItemShortcut8b SelectBoundItems { get; } - public Party8b PartyInfo { get; } - // public MyItem Items { get; } - public BoxLayout8b BoxLayout { get; } - public ConfigSave8b Config { get; } - public MyStatus8b MyStatus { get; } - public PlayTime8b Played { get; } - public Contest8b Contest { get; } - // public Misc8 Misc { get; } - public Zukan8b Zukan { get; } - public BattleTrainerStatus8b BattleTrainer { get; } - public MenuSelect8b MenuSelection { get; } - public FieldObjectSave8b FieldObjects { get; } - public Record8b Records { get; } - public EncounterSave8b Encounter { get; } - public PlayerData8b Player { get; } - public SealBallDecoData8b SealDeco { get; } - public SealList8b SealList { get; } - public RandomGroup8b Random { get; } - public FieldGimmickSave8b FieldGimmick { get; } - public BerryTreeGrowSave8b BerryTrees { get; } - public PoffinSaveData8b Poffins { get; } - public BattleTowerWork8b BattleTower { get; } - public SystemData8b System { get; } - public Poketch8b Poketch { get; } - public Daycare8b Daycare { get; } - public UgSaveData8b UgSaveData { get; } - public UnionSaveData8b UnionSave { get; } - public ContestPhotoLanguage8b ContestPhotoLanguage { get; } - public ZukanSpinda8b ZukanExtra { get; } - public UgCountRecord8b UgCount { get; } - - // First Savedata Expansion! - public RecordAddData8b RecordAdd { get; } - public MysteryBlock8b MysteryRecords { get; } - #endregion - - public override GameVersion Version => Game switch - { - (int)GameVersion.BD => GameVersion.BD, - (int)GameVersion.SP => GameVersion.SP, - _ => GameVersion.Invalid, - }; - - public override string GetString(ReadOnlySpan data) => StringConverter8.GetString(data); - - public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter8.SetString(destBuffer, value, maxLength, option); - } - - public int EventFlagCount => FlagWork8b.COUNT_FLAG; - public bool GetEventFlag(int flagNumber) => Work.GetFlag(flagNumber); - public void SetEventFlag(int flagNumber, bool value) => Work.SetFlag(flagNumber, value); - - // Player Information - public override int TID { get => MyStatus.TID; set => MyStatus.TID = value; } - public override int SID { get => MyStatus.SID; set => MyStatus.SID = value; } - public override int Game { get => MyStatus.Game; set => MyStatus.Game = value; } - public override int Gender { get => MyStatus.Male ? 0 : 1; set => MyStatus.Male = value == 0; } - public override int Language { get => Config.Language; set => Config.Language = value; } - public override string OT { get => MyStatus.OT; set => MyStatus.OT = value; } - public override uint Money { get => MyStatus.Money; set => MyStatus.Money = value; } - - public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = (ushort)value; } - public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = (byte)value; } - public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = (byte)value; } - - // Inventory - public override IReadOnlyList Inventory { get => Items.Inventory; set => Items.Inventory = value; } - - // Storage - public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot); - public override int GetBoxOffset(int box) => Box + (SIZE_PARTY * box * 30); - protected override int GetBoxWallpaperOffset(int box) => BoxLayout.GetBoxWallpaperOffset(box); - public override int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box); - public override void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value); - public override string GetBoxName(int box) => BoxLayout[box]; - public override void SetBoxName(int box, string value) => BoxLayout[box] = value; - public override byte[] GetDataForBox(PKM pkm) => pkm.EncryptedPartyData; - public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = (byte)value; } - public override int BoxesUnlocked { get => BoxLayout.BoxesUnlocked; set => BoxLayout.BoxesUnlocked = (byte)value; } - - public string Rival - { - get => GetString(0x55F4, 0x1A); - set => SetString(Data.AsSpan(0x55F4, 0x1A), value.AsSpan(), OTLength, StringConverterOption.ClearZero); - } - - public short ZoneID // map - { - get => ReadInt16LittleEndian(Data.AsSpan(0x5634)); - set => WriteInt16LittleEndian(Data.AsSpan(0x5634), value); - } - - public float TimeScale // default 1440.0f - { - get => ReadSingleLittleEndian(Data.AsSpan(0x5638)); - set => WriteSingleLittleEndian(Data.AsSpan(0x5638), value); - } - - public uint UnionRoomPenaltyTime // move this into the UnionSaveData block once reversed. - { - get => ReadUInt32LittleEndian(Data.AsSpan(0xCEA14)); - set => WriteSingleLittleEndian(Data.AsSpan(0xCEA14), value); - } - - protected override void SetPKM(PKM pkm, bool isParty = false) - { - var pk = (PB8)pkm; - // Apply to this Save File - DateTime Date = DateTime.Now; - pk.Trade(this, Date.Day, Date.Month, Date.Year); - - pkm.RefreshChecksum(); - AddCountAcquired(pkm); - } - - private void AddCountAcquired(PKM pkm) - { - // There aren't many records, and they only track Capture/Fish/Hatch/Defeat. - Records.AddRecord(pkm.WasEgg ? 004 : 002); // egg, capture - } - - protected override void SetDex(PKM pkm) => Zukan.SetDex(pkm); - public override bool GetCaught(int species) => Zukan.GetCaught(species); - public override bool GetSeen(int species) => Zukan.GetSeen(species); - - public override int PartyCount - { - get => PartyInfo.PartyCount; - protected set => PartyInfo.PartyCount = value; - } - - public override PKM GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data)); - public override PKM GetBoxSlot(int offset) => GetDecryptedPKM(GetData(Data, offset, SIZE_PARTY)); // party format in boxes! - - public enum TopMenuItemType - { - Zukan = 0, - Pokemon = 1, - Bag = 2, - Card = 3, - Map = 4, - Seal = 5, - Setting = 6, - Gift = 7, - } - - public int RecordCount => Record8b.RecordCount; - public int GetRecord(int recordID) => Records.GetRecord(recordID); - public int GetRecordOffset(int recordID) => Records.GetRecordOffset(recordID); - public int GetRecordMax(int recordID) => Record8b.GetMax(recordID); - public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value); - - #region Daycare - public override int DaycareSeedSize => 16; // 8byte - public override int GetDaycareSlotOffset(int loc, int slot) => Daycare.GetParentSlotOffset(slot); - public override uint? GetDaycareEXP(int loc, int slot) => 0; - public override bool? IsDaycareOccupied(int loc, int slot) => Daycare.GetDaycareSlotOccupied(slot); - public override bool? IsDaycareHasEgg(int loc) => Daycare.IsEggAvailable; - public override void SetDaycareEXP(int loc, int slot, uint EXP) { } - public override void SetDaycareOccupied(int loc, int slot, bool occupied) { } - public override void SetDaycareHasEgg(int loc, bool hasEgg) => Daycare.IsEggAvailable = hasEgg; - public override string GetDaycareRNGSeed(int loc) => Daycare.DaycareSeed.ToString("X16"); - public override void SetDaycareRNGSeed(int loc, string seed) => Daycare.DaycareSeed = Util.GetHexValue64(seed); - #endregion - - public int EventWorkCount => FlagWork8b.COUNT_WORK; - public int GetWork(int index) => Work.GetWork(index); - public void SetWork(int index, int value = default) => Work.SetWork(index, value); } + + #endregion + + protected override PKM GetPKM(byte[] data) => new PB8(data); + protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray8(data); + + #region Blocks + // public Box8 BoxInfo { get; } + public FlagWork8b FlagWork { get; } + public MyItem8b Items { get; } + public UndergroundItemList8b Underground { get; } + public SaveItemShortcut8b SelectBoundItems { get; } + public Party8b PartyInfo { get; } + // public MyItem Items { get; } + public BoxLayout8b BoxLayout { get; } + public ConfigSave8b Config { get; } + public MyStatus8b MyStatus { get; } + public PlayTime8b Played { get; } + public Contest8b Contest { get; } + // public Misc8 Misc { get; } + public Zukan8b Zukan { get; } + public BattleTrainerStatus8b BattleTrainer { get; } + public MenuSelect8b MenuSelection { get; } + public FieldObjectSave8b FieldObjects { get; } + public Record8b Records { get; } + public EncounterSave8b Encounter { get; } + public PlayerData8b Player { get; } + public SealBallDecoData8b SealDeco { get; } + public SealList8b SealList { get; } + public RandomGroup8b Random { get; } + public FieldGimmickSave8b FieldGimmick { get; } + public BerryTreeGrowSave8b BerryTrees { get; } + public PoffinSaveData8b Poffins { get; } + public BattleTowerWork8b BattleTower { get; } + public SystemData8b System { get; } + public Poketch8b Poketch { get; } + public Daycare8b Daycare { get; } + public UgSaveData8b UgSaveData { get; } + public UnionSaveData8b UnionSave { get; } + public ContestPhotoLanguage8b ContestPhotoLanguage { get; } + public ZukanSpinda8b ZukanExtra { get; } + public UgCountRecord8b UgCount { get; } + + // First Savedata Expansion! + public RecordAddData8b RecordAdd { get; } + public MysteryBlock8b MysteryRecords { get; } + #endregion + + public override GameVersion Version => Game switch + { + (int)GameVersion.BD => GameVersion.BD, + (int)GameVersion.SP => GameVersion.SP, + _ => GameVersion.Invalid, + }; + + public override string GetString(ReadOnlySpan data) => StringConverter8.GetString(data); + + public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter8.SetString(destBuffer, value, maxLength, option); + } + + public int EventFlagCount => FlagWork8b.COUNT_FLAG; + public bool GetEventFlag(int flagNumber) => FlagWork.GetFlag(flagNumber); + public void SetEventFlag(int flagNumber, bool value) => FlagWork.SetFlag(flagNumber, value); + + // Player Information + public override int TID { get => MyStatus.TID; set => MyStatus.TID = value; } + public override int SID { get => MyStatus.SID; set => MyStatus.SID = value; } + public override int Game { get => MyStatus.Game; set => MyStatus.Game = value; } + public override int Gender { get => MyStatus.Male ? 0 : 1; set => MyStatus.Male = value == 0; } + public override int Language { get => Config.Language; set => Config.Language = value; } + public override string OT { get => MyStatus.OT; set => MyStatus.OT = value; } + public override uint Money { get => MyStatus.Money; set => MyStatus.Money = value; } + + public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = (ushort)value; } + public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = (byte)value; } + public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = (byte)value; } + + // Inventory + public override IReadOnlyList Inventory { get => Items.Inventory; set => Items.Inventory = value; } + + // Storage + public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot); + public override int GetBoxOffset(int box) => Box + (SIZE_PARTY * box * 30); + protected override int GetBoxWallpaperOffset(int box) => BoxLayout.GetBoxWallpaperOffset(box); + public override int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box); + public override void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value); + public override string GetBoxName(int box) => BoxLayout[box]; + public override void SetBoxName(int box, string value) => BoxLayout[box] = value; + public override byte[] GetDataForBox(PKM pk) => pk.EncryptedPartyData; + public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = (byte)value; } + public override int BoxesUnlocked { get => BoxLayout.BoxesUnlocked; set => BoxLayout.BoxesUnlocked = (byte)value; } + + public string Rival + { + get => GetString(0x55F4, 0x1A); + set => SetString(Data.AsSpan(0x55F4, 0x1A), value.AsSpan(), OTLength, StringConverterOption.ClearZero); + } + + public short ZoneID // map + { + get => ReadInt16LittleEndian(Data.AsSpan(0x5634)); + set => WriteInt16LittleEndian(Data.AsSpan(0x5634), value); + } + + public float TimeScale // default 1440.0f + { + get => ReadSingleLittleEndian(Data.AsSpan(0x5638)); + set => WriteSingleLittleEndian(Data.AsSpan(0x5638), value); + } + + public uint UnionRoomPenaltyTime // move this into the UnionSaveData block once reversed. + { + get => ReadUInt32LittleEndian(Data.AsSpan(0xCEA14)); + set => WriteSingleLittleEndian(Data.AsSpan(0xCEA14), value); + } + + protected override void SetPKM(PKM pk, bool isParty = false) + { + var pb8 = (PB8)pk; + // Apply to this Save File + DateTime Date = DateTime.Now; + pb8.Trade(this, Date.Day, Date.Month, Date.Year); + + pb8.RefreshChecksum(); + AddCountAcquired(pb8); + } + + private void AddCountAcquired(PKM pk) + { + // There aren't many records, and they only track Capture/Fish/Hatch/Defeat. + Records.AddRecord(pk.WasEgg ? 004 : 002); // egg, capture + } + + protected override void SetDex(PKM pk) => Zukan.SetDex(pk); + public override bool GetCaught(int species) => Zukan.GetCaught(species); + public override bool GetSeen(int species) => Zukan.GetSeen(species); + + public override int PartyCount + { + get => PartyInfo.PartyCount; + protected set => PartyInfo.PartyCount = value; + } + + public override PKM GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data)); + public override PKM GetBoxSlot(int offset) => GetDecryptedPKM(GetData(Data, offset, SIZE_PARTY)); // party format in boxes! + + public enum TopMenuItemType + { + Zukan = 0, + Pokemon = 1, + Bag = 2, + Card = 3, + Map = 4, + Seal = 5, + Setting = 6, + Gift = 7, + } + + public int RecordCount => Record8b.RecordCount; + public int GetRecord(int recordID) => Records.GetRecord(recordID); + public int GetRecordOffset(int recordID) => Records.GetRecordOffset(recordID); + public int GetRecordMax(int recordID) => Record8b.GetMax(recordID); + public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value); + + #region Daycare + public override int DaycareSeedSize => 16; // 8byte + public override int GetDaycareSlotOffset(int loc, int slot) => Daycare.GetParentSlotOffset(slot); + public override uint? GetDaycareEXP(int loc, int slot) => 0; + public override bool? IsDaycareOccupied(int loc, int slot) => Daycare.GetDaycareSlotOccupied(slot); + public override bool? IsDaycareHasEgg(int loc) => Daycare.IsEggAvailable; + public override void SetDaycareEXP(int loc, int slot, uint EXP) { } + public override void SetDaycareOccupied(int loc, int slot, bool occupied) { } + public override void SetDaycareHasEgg(int loc, bool hasEgg) => Daycare.IsEggAvailable = hasEgg; + public override string GetDaycareRNGSeed(int loc) => $"{Daycare.DaycareSeed:X16}"; + public override void SetDaycareRNGSeed(int loc, string seed) => Daycare.DaycareSeed = Util.GetHexValue64(seed); + #endregion + + public int EventWorkCount => FlagWork8b.COUNT_WORK; + public int GetWork(int index) => FlagWork.GetWork(index); + public void SetWork(int index, int value = default) => FlagWork.SetWork(index, value); } diff --git a/PKHeX.Core/Saves/SAV8LA.cs b/PKHeX.Core/Saves/SAV8LA.cs index f4c946f77..3114e7acb 100644 --- a/PKHeX.Core/Saves/SAV8LA.cs +++ b/PKHeX.Core/Saves/SAV8LA.cs @@ -146,24 +146,24 @@ public override int PartyCount protected set => PartyInfo.PartyCount = value; } - protected override void SetPKM(PKM pkm, bool isParty = false) + protected override void SetPKM(PKM pk, bool isParty = false) { - var pk = (PA8)pkm; + var pa8 = (PA8)pk; // Apply to this Save File - pk.Trade(this); - pkm.RefreshChecksum(); + pa8.Trade(this); + pa8.RefreshChecksum(); } // Zukan - protected override void SetDex(PKM pkm) + protected override void SetDex(PKM pk) { // TODO: Seen in wild? - // Accessor.SetPokeSeenInWild(pkm); + // Accessor.SetPokeSeenInWild(pk); // TODO: Should this update research? What research should it be updating? // TODO: Should this be passing "caught=true" to set caught flags and not just obtain flags? - // For now, if we have never obtained the poke, treat this pkm as obtained-via-trade. - PokedexSave.OnPokeGet_TradeWithoutEvolution(pkm); + // For now, if we have never obtained the poke, treat this pk as obtained-via-trade. + PokedexSave.OnPokeGet_TradeWithoutEvolution(pk); } public override bool GetCaught(int species) diff --git a/PKHeX.Core/Saves/SAV8SWSH.cs b/PKHeX.Core/Saves/SAV8SWSH.cs index 0a62b6c5c..82c72e712 100644 --- a/PKHeX.Core/Saves/SAV8SWSH.cs +++ b/PKHeX.Core/Saves/SAV8SWSH.cs @@ -187,30 +187,30 @@ public override int SetString(Span destBuffer, ReadOnlySpan value, i public override int GetBoxOffset(int box) => Box + (SIZE_PARTY * box * 30); public override string GetBoxName(int box) => BoxLayout[box]; public override void SetBoxName(int box, string value) => BoxLayout[box] = value; - public override byte[] GetDataForBox(PKM pkm) => pkm.EncryptedPartyData; + public override byte[] GetDataForBox(PKM pk) => pk.EncryptedPartyData; - protected override void SetPKM(PKM pkm, bool isParty = false) + protected override void SetPKM(PKM pk, bool isParty = false) { - PK8 pk = (PK8)pkm; + PK8 pk8 = (PK8)pk; // Apply to this Save File DateTime Date = DateTime.Now; - pk.Trade(this, Date.Day, Date.Month, Date.Year); + pk8.Trade(this, Date.Day, Date.Month, Date.Year); - if (FormArgumentUtil.IsFormArgumentTypeDatePair(pk.Species, pk.Form)) + if (FormArgumentUtil.IsFormArgumentTypeDatePair(pk8.Species, pk8.Form)) { - pk.FormArgumentElapsed = pk.FormArgumentMaximum = 0; - pk.FormArgumentRemain = (byte)GetFormArgument(pkm); + pk8.FormArgumentElapsed = pk8.FormArgumentMaximum = 0; + pk8.FormArgumentRemain = (byte)GetFormArgument(pk8); } - pkm.RefreshChecksum(); - AddCountAcquired(pkm); + pk8.RefreshChecksum(); + AddCountAcquired(pk8); } - private static uint GetFormArgument(PKM pkm) + private static uint GetFormArgument(PKM pk) { - if (pkm.Form == 0) + if (pk.Form == 0) return 0; - return pkm.Species switch + return pk.Species switch { (int)Species.Furfrou => 5u, // Furfrou (int)Species.Hoopa => 3u, // Hoopa @@ -218,9 +218,9 @@ private static uint GetFormArgument(PKM pkm) }; } - private void AddCountAcquired(PKM pkm) + private void AddCountAcquired(PKM pk) { - if (pkm.WasEgg) + if (pk.WasEgg) { Records.AddRecord(00); } @@ -231,11 +231,11 @@ private void AddCountAcquired(PKM pkm) Records.AddRecord(16); // wild encountered Records.AddRecord(23); // total battled } - if (pkm.CurrentHandler == 1) + if (pk.CurrentHandler == 1) Records.AddRecord(17, 2); // trade * 2 -- these games count 1 trade as 2 for some reason. } - protected override void SetDex(PKM pkm) => Zukan.SetDex(pkm); + protected override void SetDex(PKM pk) => Zukan.SetDex(pk); public override bool GetCaught(int species) => Zukan.GetCaught(species); public override bool GetSeen(int species) => Zukan.GetSeen(species); @@ -256,16 +256,16 @@ public override int PartyCount public int GetRecordOffset(int recordID) => Records.GetRecordOffset(recordID); public int RecordCount => Record8.RecordCount; - public override StorageSlotFlag GetSlotFlags(int index) + public override StorageSlotSource GetSlotFlags(int index) { int team = Array.IndexOf(TeamIndexes.TeamSlots, index); if (team < 0) - return StorageSlotFlag.None; + return StorageSlotSource.None; team /= 6; - var result = (StorageSlotFlag)((int)StorageSlotFlag.BattleTeam1 << team); + var result = (StorageSlotSource)((int)StorageSlotSource.BattleTeam1 << team); if (TeamIndexes.GetIsTeamLocked(team)) - result |= StorageSlotFlag.Locked; + result |= StorageSlotSource.Locked; return result; } @@ -274,7 +274,7 @@ public override StorageSlotFlag GetSlotFlags(int index) public override byte[] BoxFlags { - get => new [] {Convert.ToByte(Blocks.GetBlock(SaveBlockAccessor8SWSH.KSecretBoxUnlocked).Type - 1)}; + get => new [] {(byte)(Blocks.GetBlock(SaveBlockAccessor8SWSH.KSecretBoxUnlocked).Type - 1)}; set { if (value.Length != 1) diff --git a/PKHeX.Core/Saves/SAV_BEEF.cs b/PKHeX.Core/Saves/SAV_BEEF.cs index 7d8908f2b..e8e47279d 100644 --- a/PKHeX.Core/Saves/SAV_BEEF.cs +++ b/PKHeX.Core/Saves/SAV_BEEF.cs @@ -3,48 +3,47 @@ using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Footer checksum block layout , marked with "BEEF" magic number. +/// +/// Shared logic is used by Gen6 and Gen7 save files. +public abstract class SAV_BEEF : SaveFile, ISecureValueStorage { - /// - /// Footer checksum block layout , marked with "BEEF" magic number. - /// - /// Shared logic is used by Gen6 and Gen7 save files. - public abstract class SAV_BEEF : SaveFile, ISecureValueStorage + protected SAV_BEEF(byte[] data, int biOffset) : base(data) { - protected SAV_BEEF(byte[] data, int biOffset) : base(data) - { - BlockInfoOffset = biOffset; - } + BlockInfoOffset = biOffset; + } - protected SAV_BEEF(int size, int biOffset) : base(size) - { - BlockInfoOffset = biOffset; - } + protected SAV_BEEF(int size, int biOffset) : base(size) + { + BlockInfoOffset = biOffset; + } - public abstract IReadOnlyList AllBlocks { get; } - protected override void SetChecksums() => AllBlocks.SetChecksums(Data); - public override bool ChecksumsValid => AllBlocks.GetChecksumsValid(Data); - public override string ChecksumInfo => AllBlocks.GetChecksumInfo(Data); - public override string MiscSaveInfo() => string.Join(Environment.NewLine, AllBlocks.Select(b => b.Summary)); + public abstract IReadOnlyList AllBlocks { get; } + protected override void SetChecksums() => AllBlocks.SetChecksums(Data); + public override bool ChecksumsValid => AllBlocks.GetChecksumsValid(Data); + public override string ChecksumInfo => AllBlocks.GetChecksumInfo(Data); + public override string MiscSaveInfo() => string.Join(Environment.NewLine, AllBlocks.Select(b => b.Summary)); - protected readonly int BlockInfoOffset; + protected readonly int BlockInfoOffset; - /// - /// Timestamp that the save file was last saved at (Secure Value) - /// - public ulong TimeStampCurrent - { - get => ReadUInt64LittleEndian(Data.AsSpan(BlockInfoOffset)); - set => WriteUInt64LittleEndian(Data.AsSpan(BlockInfoOffset), value); - } + /// + /// Timestamp that the save file was last saved at (Secure Value) + /// + public ulong TimeStampCurrent + { + get => ReadUInt64LittleEndian(Data.AsSpan(BlockInfoOffset)); + set => WriteUInt64LittleEndian(Data.AsSpan(BlockInfoOffset), value); + } - /// - /// Timestamp that the save file was saved at prior to the (Secure Value) - /// - public ulong TimeStampPrevious - { - get => ReadUInt64LittleEndian(Data.AsSpan(BlockInfoOffset)); - set => WriteUInt64LittleEndian(Data.AsSpan(BlockInfoOffset), value); - } + /// + /// Timestamp that the save file was saved at prior to the (Secure Value) + /// + public ulong TimeStampPrevious + { + get => ReadUInt64LittleEndian(Data.AsSpan(BlockInfoOffset)); + set => WriteUInt64LittleEndian(Data.AsSpan(BlockInfoOffset), value); } } diff --git a/PKHeX.Core/Saves/SAV_STADIUM.cs b/PKHeX.Core/Saves/SAV_STADIUM.cs index 18dc11859..1590ba577 100644 --- a/PKHeX.Core/Saves/SAV_STADIUM.cs +++ b/PKHeX.Core/Saves/SAV_STADIUM.cs @@ -2,115 +2,114 @@ using System.Buffers.Binary; using System.Runtime.InteropServices; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Base class for GB Era Stadium files. +/// +public abstract class SAV_STADIUM : SaveFile, ILangDeviantSave { - /// - /// Base class for GB Era Stadium files. - /// - public abstract class SAV_STADIUM : SaveFile, ILangDeviantSave + protected internal sealed override string ShortSummary => $"{OT} ({Version})"; + public sealed override string Extension => ".sav"; + + public abstract int SaveRevision { get; } + public abstract string SaveRevisionString { get; } + public bool Japanese { get; } + public bool Korean => false; + + public sealed override int MaxBallID => 0; // unused + public sealed override int MaxGameID => 99; // unused + public sealed override int MaxMoney => 999999; + public sealed override int MaxCoins => 9999; + + /// If the original input data was swapped endianness. + private readonly bool IsPairSwapped; + + protected abstract int TeamCount { get; } + public sealed override string OT { get; set; } + public sealed override int Language => Japanese ? 1 : 2; + + protected SAV_STADIUM(byte[] data, bool japanese, bool swap) : base(data) { - protected internal sealed override string ShortSummary => $"{OT} ({Version})"; - public sealed override string Extension => ".sav"; + Japanese = japanese; + OT = SaveUtil.GetSafeTrainerName(this, (LanguageID)Language); - public abstract int SaveRevision { get; } - public abstract string SaveRevisionString { get; } - public bool Japanese { get; } - public bool Korean => false; + if (!swap) + return; + ReverseEndianness(Data); + IsPairSwapped = true; + } - public sealed override int MaxBallID => 0; // unused - public sealed override int MaxGameID => 99; // unused - public sealed override int MaxMoney => 999999; - public sealed override int MaxCoins => 9999; + protected SAV_STADIUM(bool japanese, int size) : base(size) + { + Japanese = japanese; + OT = SaveUtil.GetSafeTrainerName(this, (LanguageID)Language); + } - /// If the original input data was swapped endianness. - private readonly bool IsPairSwapped; + protected sealed override byte[] DecryptPKM(byte[] data) => data; + public sealed override int GetPartyOffset(int slot) => -1; + public override string GetBoxName(int box) => $"Box {box + 1}"; + public sealed override void SetBoxName(int box, string value) { } + public sealed override bool ChecksumsValid => GetBoxChecksumsValid(); + public sealed override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid"; + protected abstract void SetBoxChecksum(int box); + protected abstract bool GetIsBoxChecksumValid(int box); + protected sealed override void SetChecksums() => SetBoxChecksums(); + protected abstract void SetBoxMetadata(int box); - protected abstract int TeamCount { get; } - public sealed override string OT { get; set; } - public sealed override int Language => Japanese ? 1 : 2; - - protected SAV_STADIUM(byte[] data, bool japanese, bool swap) : base(data) + protected void SetBoxChecksums() + { + for (int i = 0; i < BoxCount; i++) { - Japanese = japanese; - OT = SaveUtil.GetSafeTrainerName(this, (LanguageID)Language); - - if (!swap) - return; - ReverseEndianness(Data); - IsPairSwapped = true; - } - - protected SAV_STADIUM(bool japanese, int size) : base(size) - { - Japanese = japanese; - OT = SaveUtil.GetSafeTrainerName(this, (LanguageID)Language); - } - - protected sealed override byte[] DecryptPKM(byte[] data) => data; - public sealed override int GetPartyOffset(int slot) => -1; - public override string GetBoxName(int box) => $"Box {box + 1}"; - public sealed override void SetBoxName(int box, string value) { } - public sealed override bool ChecksumsValid => GetBoxChecksumsValid(); - public sealed override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid"; - protected abstract void SetBoxChecksum(int box); - protected abstract bool GetIsBoxChecksumValid(int box); - protected sealed override void SetChecksums() => SetBoxChecksums(); - protected abstract void SetBoxMetadata(int box); - - protected void SetBoxChecksums() - { - for (int i = 0; i < BoxCount; i++) - { - SetBoxMetadata(i); - SetBoxChecksum(i); - } - } - - private bool GetBoxChecksumsValid() - { - for (int i = 0; i < BoxCount; i++) - { - if (!GetIsBoxChecksumValid(i)) - return false; - } - return true; - } - - protected sealed override byte[] GetFinalData() - { - var result = base.GetFinalData(); - if (IsPairSwapped) - ReverseEndianness(result = (byte[])result.Clone()); - return result; - } - - public abstract SlotGroup GetTeam(int team); - - public virtual SlotGroup[] GetRegisteredTeams() - { - var result = new SlotGroup[TeamCount]; - for (int i = 0; i < result.Length; i++) - result[i] = GetTeam(i); - return result; - } - - public sealed override string GetString(ReadOnlySpan data) => StringConverter12.GetString(data, Japanese); - - public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter12.SetString(destBuffer, value, maxLength, Japanese, option); - } - - /// - /// Some emulators emit with system architecture endianness (Little Endian) instead of the original Big Endian ordering. - /// This will efficiently swap 32-bit endianness for the entire span. - /// - /// Full savedata - private static void ReverseEndianness(Span data) - { - var uintArr = MemoryMarshal.Cast(data); - for (int i = 0; i < uintArr.Length; i++) - uintArr[i] = BinaryPrimitives.ReverseEndianness(uintArr[i]); + SetBoxMetadata(i); + SetBoxChecksum(i); } } + + private bool GetBoxChecksumsValid() + { + for (int i = 0; i < BoxCount; i++) + { + if (!GetIsBoxChecksumValid(i)) + return false; + } + return true; + } + + protected sealed override byte[] GetFinalData() + { + var result = base.GetFinalData(); + if (IsPairSwapped) + ReverseEndianness(result = (byte[])result.Clone()); + return result; + } + + public abstract SlotGroup GetTeam(int team); + + public virtual SlotGroup[] GetRegisteredTeams() + { + var result = new SlotGroup[TeamCount]; + for (int i = 0; i < result.Length; i++) + result[i] = GetTeam(i); + return result; + } + + public sealed override string GetString(ReadOnlySpan data) => StringConverter12.GetString(data, Japanese); + + public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter12.SetString(destBuffer, value, maxLength, Japanese, option); + } + + /// + /// Some emulators emit with system architecture endianness (Little Endian) instead of the original Big Endian ordering. + /// This will efficiently swap 32-bit endianness for the entire span. + /// + /// Full savedata + private static void ReverseEndianness(Span data) + { + var uintArr = MemoryMarshal.Cast(data); + for (int i = 0; i < uintArr.Length; i++) + uintArr[i] = BinaryPrimitives.ReverseEndianness(uintArr[i]); + } } diff --git a/PKHeX.Core/Saves/SaveFile.cs b/PKHeX.Core/Saves/SaveFile.cs index a1523cb54..e2d8c6765 100644 --- a/PKHeX.Core/Saves/SaveFile.cs +++ b/PKHeX.Core/Saves/SaveFile.cs @@ -2,918 +2,917 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Base Class for Save Files +/// +public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpaper, IBoxDetailName, IGeneration, IVersion { - /// - /// Base Class for Save Files - /// - public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpaper, IBoxDetailName, IGeneration, IVersion + // General Object Properties + public byte[] Data; + + public SaveFileState State { get; } + public SaveFileMetadata Metadata { get; private set; } + + protected SaveFile(byte[] data, bool exportable = true) { - // General Object Properties - public byte[] Data; + Data = data; + State = new SaveFileState(exportable); + Metadata = new SaveFileMetadata(this); + } - public SaveFileState State { get; } - public SaveFileMetadata Metadata { get; private set; } + protected SaveFile(int size = 0) : this(size == 0 ? Array.Empty() : new byte[size], false) { } - protected SaveFile(byte[] data, bool exportable = true) + protected internal abstract string ShortSummary { get; } + public abstract string Extension { get; } + + protected abstract SaveFile CloneInternal(); + + public SaveFile Clone() + { + var sav = CloneInternal(); + sav.Metadata = Metadata; + return sav; + } + + public virtual string PlayTimeString => $"{PlayedHours}ː{PlayedMinutes:00}ː{PlayedSeconds:00}"; // not : + + public virtual IReadOnlyList PKMExtensions => Array.FindAll(PKM.Extensions, f => + { + int gen = f[^1] - 0x30; + return 3 <= gen && gen <= Generation; + }); + + // General SAV Properties + public byte[] Write(BinaryExportSetting setting = BinaryExportSetting.None) + { + byte[] data = GetFinalData(); + return Metadata.Finalize(data, setting); + } + + protected virtual byte[] GetFinalData() + { + SetChecksums(); + return Data; + } + + #region Metadata & Limits + public virtual string MiscSaveInfo() => string.Empty; + public virtual GameVersion Version { get; protected set; } + public abstract bool ChecksumsValid { get; } + public abstract string ChecksumInfo { get; } + public abstract int Generation { get; } + public abstract EntityContext Context { get; } + #endregion + + #region Savedata Container Handling + public byte[] GetData(int offset, int length) => GetData(Data, offset, length); + protected static byte[] GetData(byte[] data, int offset, int length) => data.Slice(offset, length); + public void SetData(byte[] input, int offset) => SetData(Data, input, offset); + + public void SetData(Span dest, Span input, int offset) + { + input.CopyTo(dest[offset..]); + State.Edited = true; + } + + public abstract string GetString(ReadOnlySpan data); + public string GetString(int offset, int length) => GetString(Data.AsSpan(offset, length)); + public abstract int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option); + #endregion + + public virtual void CopyChangesFrom(SaveFile sav) => SetData(sav.Data, 0); + + // Offsets + + #region Stored PKM Limits + public abstract PersonalTable Personal { get; } + public abstract int OTLength { get; } + public abstract int NickLength { get; } + public abstract int MaxMoveID { get; } + public abstract int MaxSpeciesID { get; } + public abstract int MaxAbilityID { get; } + public abstract int MaxItemID { get; } + public abstract int MaxBallID { get; } + public abstract int MaxGameID { get; } + public virtual int MinGameID => 0; + #endregion + + /// + /// Gets the status of the Flag at the specified offset and index. + /// + /// Offset to read from + /// Bit index to read + /// Flag is Set (true) or not Set (false) + public virtual bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(Data, offset, bitIndex); + + /// + /// Sets the status of the Flag at the specified offset and index. + /// + /// Offset to read from + /// Bit index to read + /// Flag status to set + /// Flag is Set (true) or not Set (false) + public virtual void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(Data, offset, bitIndex, value); + + public virtual IReadOnlyList Inventory { get => Array.Empty(); set { } } + + #region Mystery Gift + protected virtual int GiftCountMax { get; } = int.MinValue; + protected virtual int GiftFlagMax { get; } = 0x800; + protected int WondercardData { get; set; } = int.MinValue; + public bool HasWondercards => WondercardData > -1; + protected virtual bool[] MysteryGiftReceivedFlags { get => Array.Empty(); set { } } + protected virtual DataMysteryGift[] MysteryGiftCards { get => Array.Empty(); set { } } + + public virtual MysteryGiftAlbum GiftAlbum + { + get => new(MysteryGiftCards, MysteryGiftReceivedFlags); + set { - Data = data; - State = new SaveFileState(exportable); - Metadata = new SaveFileMetadata(this); + MysteryGiftReceivedFlags = value.Flags; + MysteryGiftCards = value.Gifts; } + } + #endregion - protected SaveFile(int size = 0) : this(size == 0 ? Array.Empty() : new byte[size], false) { } + #region Player Info + public virtual int Gender { get; set; } + public virtual int Language { get => -1; set { } } + public virtual int Game { get => -1; set { } } + public virtual int TID { get; set; } + public virtual int SID { get; set; } + public virtual string OT { get; set; } = "PKHeX"; + public virtual int PlayedHours { get; set; } + public virtual int PlayedMinutes { get; set; } + public virtual int PlayedSeconds { get; set; } + public virtual uint SecondsToStart { get; set; } + public virtual uint SecondsToFame { get; set; } + public virtual uint Money { get; set; } + public abstract int BoxCount { get; } + public virtual int SlotCount => BoxCount * BoxSlotCount; + public int TrainerID7 { get => (int)((uint)(TID | (SID << 16)) % 1000000); set => SetID7(TrainerSID7, value); } + public int TrainerSID7 { get => (int)((uint)(TID | (SID << 16)) / 1000000); set => SetID7(value, TrainerID7); } + public virtual int MaxMoney => 9999999; + public virtual int MaxCoins => 9999; - protected internal abstract string ShortSummary { get; } - public abstract string Extension { get; } + public int DisplayTID + { + get => Generation >= 7 ? TrainerID7 : TID; + set { if (Generation >= 7) TrainerID7 = value; else TID = value; } + } - protected abstract SaveFile CloneInternal(); + public int DisplaySID + { + get => Generation >= 7 ? TrainerSID7 : SID; + set { if (Generation >= 7) TrainerSID7 = value; else SID = value; } + } + #endregion - public SaveFile Clone() + private void SetID7(int sid7, int tid7) + { + var oid = (sid7 * 1_000_000) + (tid7 % 1_000_000); + TID = (ushort)oid; + SID = oid >> 16; + } + + #region Party + public virtual int PartyCount { get; protected set; } + protected int Party { get; set; } = int.MinValue; + public virtual bool HasParty => Party > -1; + public abstract int GetPartyOffset(int slot); + + public bool IsPartyAllEggs(int except = -1) + { + if (!HasParty) + return false; + + for (int i = 0; i < MaxPartyCount; i++) { - var sav = CloneInternal(); - sav.Metadata = Metadata; - return sav; - } + if (i == except) + continue; - public virtual string PlayTimeString => $"{PlayedHours}ː{PlayedMinutes:00}ː{PlayedSeconds:00}"; // not : - - public virtual IReadOnlyList PKMExtensions => Array.FindAll(PKM.Extensions, f => - { - int gen = f[^1] - 0x30; - return 3 <= gen && gen <= Generation; - }); - - // General SAV Properties - public byte[] Write(ExportFlags flags = ExportFlags.None) - { - byte[] data = GetFinalData(); - return Metadata.Finalize(data, flags); - } - - protected virtual byte[] GetFinalData() - { - SetChecksums(); - return Data; - } - - #region Metadata & Limits - public virtual string MiscSaveInfo() => string.Empty; - public virtual GameVersion Version { get; protected set; } - public abstract bool ChecksumsValid { get; } - public abstract string ChecksumInfo { get; } - public abstract int Generation { get; } - public abstract EntityContext Context { get; } - #endregion - - #region Savedata Container Handling - public byte[] GetData(int offset, int length) => GetData(Data, offset, length); - protected static byte[] GetData(byte[] data, int offset, int length) => data.Slice(offset, length); - public void SetData(byte[] input, int offset) => SetData(Data, input, offset); - - public void SetData(Span dest, Span input, int offset) - { - input.CopyTo(dest[offset..]); - State.Edited = true; - } - - public abstract string GetString(ReadOnlySpan data); - public string GetString(int offset, int length) => GetString(Data.AsSpan(offset, length)); - public abstract int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option); - #endregion - - public virtual void CopyChangesFrom(SaveFile sav) => SetData(sav.Data, 0); - - // Offsets - - #region Stored PKM Limits - public abstract PersonalTable Personal { get; } - public abstract int OTLength { get; } - public abstract int NickLength { get; } - public abstract int MaxMoveID { get; } - public abstract int MaxSpeciesID { get; } - public abstract int MaxAbilityID { get; } - public abstract int MaxItemID { get; } - public abstract int MaxBallID { get; } - public abstract int MaxGameID { get; } - public virtual int MinGameID => 0; - #endregion - - /// - /// Gets the status of the Flag at the specified offset and index. - /// - /// Offset to read from - /// Bit index to read - /// Flag is Set (true) or not Set (false) - public virtual bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(Data, offset, bitIndex); - - /// - /// Sets the status of the Flag at the specified offset and index. - /// - /// Offset to read from - /// Bit index to read - /// Flag status to set - /// Flag is Set (true) or not Set (false) - public virtual void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(Data, offset, bitIndex, value); - - public virtual IReadOnlyList Inventory { get => Array.Empty(); set { } } - - #region Mystery Gift - protected virtual int GiftCountMax { get; } = int.MinValue; - protected virtual int GiftFlagMax { get; } = 0x800; - protected int WondercardData { get; set; } = int.MinValue; - public bool HasWondercards => WondercardData > -1; - protected virtual bool[] MysteryGiftReceivedFlags { get => Array.Empty(); set { } } - protected virtual DataMysteryGift[] MysteryGiftCards { get => Array.Empty(); set { } } - - public virtual MysteryGiftAlbum GiftAlbum - { - get => new(MysteryGiftCards, MysteryGiftReceivedFlags); - set - { - MysteryGiftReceivedFlags = value.Flags; - MysteryGiftCards = value.Gifts; - } - } - #endregion - - #region Player Info - public virtual int Gender { get; set; } - public virtual int Language { get => -1; set { } } - public virtual int Game { get => -1; set { } } - public virtual int TID { get; set; } - public virtual int SID { get; set; } - public virtual string OT { get; set; } = "PKHeX"; - public virtual int PlayedHours { get; set; } - public virtual int PlayedMinutes { get; set; } - public virtual int PlayedSeconds { get; set; } - public virtual uint SecondsToStart { get; set; } - public virtual uint SecondsToFame { get; set; } - public virtual uint Money { get; set; } - public abstract int BoxCount { get; } - public virtual int SlotCount => BoxCount * BoxSlotCount; - public int TrainerID7 { get => (int)((uint)(TID | (SID << 16)) % 1000000); set => SetID7(TrainerSID7, value); } - public int TrainerSID7 { get => (int)((uint)(TID | (SID << 16)) / 1000000); set => SetID7(value, TrainerID7); } - public virtual int MaxMoney => 9999999; - public virtual int MaxCoins => 9999; - - public int DisplayTID - { - get => Generation >= 7 ? TrainerID7 : TID; - set { if (Generation >= 7) TrainerID7 = value; else TID = value; } - } - - public int DisplaySID - { - get => Generation >= 7 ? TrainerSID7 : SID; - set { if (Generation >= 7) TrainerSID7 = value; else SID = value; } - } - #endregion - - private void SetID7(int sid7, int tid7) - { - var oid = (sid7 * 1_000_000) + (tid7 % 1_000_000); - TID = (ushort)oid; - SID = oid >> 16; - } - - #region Party - public virtual int PartyCount { get; protected set; } - protected int Party { get; set; } = int.MinValue; - public virtual bool HasParty => Party > -1; - public abstract int GetPartyOffset(int slot); - - public bool IsPartyAllEggs(int except = -1) - { - if (!HasParty) + if (IsPartySlotNotEggOrEmpty(i)) return false; - - for (int i = 0; i < MaxPartyCount; i++) - { - if (i == except) - continue; - - if (IsPartySlotNotEggOrEmpty(i)) - return false; - } - - return true; } - private bool IsPartySlotNotEggOrEmpty(int index) + return true; + } + + private bool IsPartySlotNotEggOrEmpty(int index) + { + var slot = GetPartySlotAtIndex(index); + return !slot.IsEgg && slot.Species != 0; + } + + private const int MaxPartyCount = 6; + + public IList PartyData + { + get { - var slot = GetPartySlotAtIndex(index); - return !slot.IsEgg && slot.Species != 0; - } + var count = PartyCount; + if ((uint)count > MaxPartyCount) + count = MaxPartyCount; - private const int MaxPartyCount = 6; - - public IList PartyData - { - get - { - var count = PartyCount; - if ((uint)count > MaxPartyCount) - count = MaxPartyCount; - - PKM[] data = new PKM[count]; - for (int i = 0; i < data.Length; i++) - data[i] = GetPartySlot(PartyBuffer, GetPartyOffset(i)); - return data; - } - set - { - if (value.Count is 0 or > MaxPartyCount) - throw new ArgumentOutOfRangeException(nameof(value), $"Expected 1-6, got {value.Count}"); -#if DEBUG - if (value[0].Species == 0) - System.Diagnostics.Debug.WriteLine($"Empty first slot, received {value.Count}."); -#endif - int ctr = 0; - foreach (var exist in value.Where(pk => pk.Species != 0)) - SetPartySlot(exist, PartyBuffer, GetPartyOffset(ctr++)); - for (int i = ctr; i < 6; i++) - SetPartySlot(BlankPKM, PartyBuffer, GetPartyOffset(i)); - } - } - #endregion - - // Varied Methods - protected abstract void SetChecksums(); - - #region Daycare - public bool HasDaycare => DaycareOffset > -1; - protected int DaycareOffset { get; set; } = int.MinValue; - public virtual int DaycareSeedSize { get; } = 0; - public int DaycareIndex = 0; - public virtual bool HasTwoDaycares => false; - public virtual int GetDaycareSlotOffset(int loc, int slot) => -1; - public virtual uint? GetDaycareEXP(int loc, int slot) => null; - public virtual string GetDaycareRNGSeed(int loc) => string.Empty; - public virtual bool? IsDaycareHasEgg(int loc) => null; - public virtual bool? IsDaycareOccupied(int loc, int slot) => null; - - public virtual void SetDaycareEXP(int loc, int slot, uint EXP) { } - public virtual void SetDaycareRNGSeed(int loc, string seed) { } - public virtual void SetDaycareHasEgg(int loc, bool hasEgg) { } - public virtual void SetDaycareOccupied(int loc, int slot, bool occupied) { } - #endregion - - public PKM GetPartySlotAtIndex(int index) => GetPartySlot(PartyBuffer, GetPartyOffset(index)); - - public void SetPartySlotAtIndex(PKM pkm, int index, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) - { - // update party count - if ((uint)index > 5) - throw new ArgumentOutOfRangeException(nameof(index)); - - int currentCount = PartyCount; - if (pkm.Species != 0) - { - if (currentCount <= index) - PartyCount = index + 1; - } - else if (currentCount > index) - { - PartyCount = index; - } - - int offset = GetPartyOffset(index); - SetPartySlot(pkm, PartyBuffer, offset, trade, dex); - } - - public void SetSlotFormatParty(PKM pkm, byte[] data, int offset, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) - { - if (pkm.GetType() != PKMType) - throw new ArgumentException($"PKM Format needs to be {PKMType} when setting to this Save File."); - - UpdatePKM(pkm, isParty: true, trade, dex); - SetPartyValues(pkm, isParty: true); - WritePartySlot(pkm, data, offset); - } - - public void SetPartySlot(PKM pkm, byte[] data, int offset, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) - { - if (pkm.GetType() != PKMType) - throw new ArgumentException($"PKM Format needs to be {PKMType} when setting to this Save File."); - - UpdatePKM(pkm, isParty: true, trade, dex); - SetPartyValues(pkm, isParty: true); - WritePartySlot(pkm, data, offset); - } - - public void SetSlotFormatStored(PKM pkm, Span data, int offset, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) - { - if (pkm.GetType() != PKMType) - throw new ArgumentException($"PKM Format needs to be {PKMType} when setting to this Save File."); - - UpdatePKM(pkm, isParty: false, trade, dex); - SetPartyValues(pkm, isParty: false); - WriteSlotFormatStored(pkm, data, offset); - } - - public void SetBoxSlot(PKM pkm, Span data, int offset, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) - { - if (pkm.GetType() != PKMType) - throw new ArgumentException($"PKM Format needs to be {PKMType} when setting to this Save File."); - - UpdatePKM(pkm, isParty: false, trade, dex); - SetPartyValues(pkm, isParty: false); - WriteBoxSlot(pkm, data, offset); - } - - public void DeletePartySlot(int slot) - { - int newEmpty = PartyCount - 1; - if ((uint)slot > newEmpty) // beyond party range (or empty data already present) - return; - // Move all party slots down one - for (int i = slot + 1; i <= newEmpty; i++) // Slide slots down - { - var current = GetPartySlotAtIndex(i); - SetPartySlotAtIndex(current, i - 1, PKMImportSetting.Skip, PKMImportSetting.Skip); - } - SetPartySlotAtIndex(BlankPKM, newEmpty, PKMImportSetting.Skip, PKMImportSetting.Skip); - // PartyCount will automatically update via above call. Do not adjust. - } - - #region Slot Storing - public static PKMImportSetting SetUpdateDex { protected get; set; } = PKMImportSetting.Update; - public static PKMImportSetting SetUpdatePKM { protected get; set; } = PKMImportSetting.Update; - - public abstract Type PKMType { get; } - protected abstract PKM GetPKM(byte[] data); - protected abstract byte[] DecryptPKM(byte[] data); - public abstract PKM BlankPKM { get; } - protected abstract int SIZE_STORED { get; } - protected abstract int SIZE_PARTY { get; } - public virtual int SIZE_BOXSLOT => SIZE_STORED; - public abstract int MaxEV { get; } - public virtual int MaxIV => 31; - public abstract IReadOnlyList HeldItems { get; } - protected virtual byte[] BoxBuffer => Data; - protected virtual byte[] PartyBuffer => Data; - public virtual bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresent(data); - public virtual PKM GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data)); - public virtual PKM GetPartySlot(byte[] data, int offset) => GetDecryptedPKM(GetData(data, offset, SIZE_PARTY)); - public virtual PKM GetStoredSlot(byte[] data, int offset) => GetDecryptedPKM(GetData(data, offset, SIZE_STORED)); - public virtual PKM GetBoxSlot(int offset) => GetStoredSlot(BoxBuffer, offset); - - public virtual byte[] GetDataForFormatStored(PKM pkm) => pkm.EncryptedBoxData; - public virtual byte[] GetDataForFormatParty(PKM pkm) => pkm.EncryptedPartyData; - public virtual byte[] GetDataForParty(PKM pkm) => pkm.EncryptedPartyData; - public virtual byte[] GetDataForBox(PKM pkm) => pkm.EncryptedBoxData; - - public virtual void WriteSlotFormatStored(PKM pkm, Span data, int offset) => SetData(data, GetDataForFormatStored(pkm), offset); - public virtual void WriteSlotFormatParty(PKM pkm, Span data, int offset) => SetData(data, GetDataForFormatParty(pkm), offset); - public virtual void WritePartySlot(PKM pkm, Span data, int offset) => SetData(data, GetDataForParty(pkm), offset); - public virtual void WriteBoxSlot(PKM pkm, Span data, int offset) => SetData(data, GetDataForBox(pkm), offset); - - protected virtual void SetPartyValues(PKM pkm, bool isParty) - { - if (!isParty) - return; - if (pkm.PartyStatsPresent) // Stats already present - return; - pkm.ResetPartyStats(); - } - - /// - /// Conditions a for this save file as if it was traded to it. - /// - /// Entity to adapt - /// Entity exists in party format - /// Setting on whether or not to adapt - public void AdaptPKM(PKM pkm, bool party = true, PKMImportSetting trade = PKMImportSetting.UseDefault) - { - if (GetTradeUpdateSetting(trade)) - SetPKM(pkm, party); - } - - protected void UpdatePKM(PKM pkm, bool isParty, PKMImportSetting trade, PKMImportSetting dex) - { - AdaptPKM(pkm, isParty, trade); - if (GetDexUpdateSetting(dex)) - SetDex(pkm); - } - - private static bool GetTradeUpdateSetting(PKMImportSetting trade = PKMImportSetting.UseDefault) - { - if (trade == PKMImportSetting.UseDefault) - trade = SetUpdatePKM; - return trade == PKMImportSetting.Update; - } - - private static bool GetDexUpdateSetting(PKMImportSetting trade = PKMImportSetting.UseDefault) - { - if (trade == PKMImportSetting.UseDefault) - trade = SetUpdateDex; - return trade == PKMImportSetting.Update; - } - - protected virtual void SetPKM(PKM pkm, bool isParty = false) { } - protected virtual void SetDex(PKM pkm) { } - #endregion - - #region Pokédex - public int PokeDex { get; protected set; } = int.MinValue; - public bool HasPokeDex => PokeDex > -1; - public virtual bool GetSeen(int species) => false; - public virtual void SetSeen(int species, bool seen) { } - public virtual bool GetCaught(int species) => false; - public virtual void SetCaught(int species, bool caught) { } - public int SeenCount => HasPokeDex ? Enumerable.Range(1, MaxSpeciesID).Count(GetSeen) : 0; - public int CaughtCount => HasPokeDex ? Enumerable.Range(1, MaxSpeciesID).Count(GetCaught) : 0; - public decimal PercentSeen => (decimal) SeenCount / MaxSpeciesID; - public decimal PercentCaught => (decimal)CaughtCount / MaxSpeciesID; - #endregion - - public bool HasBox => Box > -1; - public virtual int BoxSlotCount => 30; - public virtual int BoxesUnlocked { get => -1; set { } } - public virtual byte[] BoxFlags { get => Array.Empty(); set { } } - public virtual int CurrentBox { get; set; } - - #region BoxData - protected int Box { get; set; } = int.MinValue; - - public IList BoxData - { - get - { - PKM[] data = new PKM[BoxCount * BoxSlotCount]; - for (int box = 0; box < BoxCount; box++) - AddBoxData(data, box, box * BoxSlotCount); - return data; - } - set - { - if (value.Count != BoxCount * BoxSlotCount) - throw new ArgumentException($"Expected {BoxCount * BoxSlotCount}, got {value.Count}"); - - for (int b = 0; b < BoxCount; b++) - SetBoxData(value, b, b * BoxSlotCount); - } - } - - public int SetBoxData(IList value, int box, int index = 0) - { - int skipped = 0; - for (int slot = 0; slot < BoxSlotCount; slot++) - { - var flags = GetSlotFlags(box, slot); - if (!flags.IsOverwriteProtected()) - SetBoxSlotAtIndex(value[index + slot], box, slot); - else - ++skipped; - } - - return skipped; - } - - public PKM[] GetBoxData(int box) - { - var data = new PKM[BoxSlotCount]; - AddBoxData(data, box, 0); + PKM[] data = new PKM[count]; + for (int i = 0; i < data.Length; i++) + data[i] = GetPartySlot(PartyBuffer, GetPartyOffset(i)); return data; } - - public void AddBoxData(IList data, int box, int index) + set { - for (int slot = 0; slot < BoxSlotCount; slot++) - { - int i = slot + index; - data[i] = GetBoxSlotAtIndex(box, slot); - } + if (value.Count is 0 or > MaxPartyCount) + throw new ArgumentOutOfRangeException(nameof(value), $"Expected 1-6, got {value.Count}"); +#if DEBUG + if (value[0].Species == 0) + System.Diagnostics.Debug.WriteLine($"Empty first slot, received {value.Count}."); +#endif + int ctr = 0; + foreach (var exist in value.Where(pk => pk.Species != 0)) + SetPartySlot(exist, PartyBuffer, GetPartyOffset(ctr++)); + for (int i = ctr; i < 6; i++) + SetPartySlot(BlankPKM, PartyBuffer, GetPartyOffset(i)); } - #endregion + } + #endregion - #region Storage Health & Metadata - protected int[] TeamSlots = Array.Empty(); + // Varied Methods + protected abstract void SetChecksums(); - /// - /// Slot indexes that are protected from overwriting. - /// - protected virtual IList[] SlotPointers => new[] { TeamSlots }; - public virtual StorageSlotFlag GetSlotFlags(int index) => StorageSlotFlag.None; - public StorageSlotFlag GetSlotFlags(int box, int slot) => GetSlotFlags((box * BoxSlotCount) + slot); - public bool IsSlotLocked(int box, int slot) => GetSlotFlags(box, slot).HasFlagFast(StorageSlotFlag.Locked); - public bool IsSlotLocked(int index) => GetSlotFlags(index).HasFlagFast(StorageSlotFlag.Locked); - public bool IsSlotOverwriteProtected(int box, int slot) => GetSlotFlags(box, slot).IsOverwriteProtected(); - public bool IsSlotOverwriteProtected(int index) => GetSlotFlags(index).IsOverwriteProtected(); + #region Daycare + public bool HasDaycare => DaycareOffset > -1; + protected int DaycareOffset { get; set; } = int.MinValue; + public virtual int DaycareSeedSize { get; } = 0; + public int DaycareIndex = 0; + public virtual bool HasTwoDaycares => false; + public virtual int GetDaycareSlotOffset(int loc, int slot) => -1; + public virtual uint? GetDaycareEXP(int loc, int slot) => null; + public virtual string GetDaycareRNGSeed(int loc) => string.Empty; + public virtual bool? IsDaycareHasEgg(int loc) => null; + public virtual bool? IsDaycareOccupied(int loc, int slot) => null; - private const int StorageFullValue = -1; - public bool IsStorageFull => NextOpenBoxSlot() == StorageFullValue; + public virtual void SetDaycareEXP(int loc, int slot, uint EXP) { } + public virtual void SetDaycareRNGSeed(int loc, string seed) { } + public virtual void SetDaycareHasEgg(int loc, bool hasEgg) { } + public virtual void SetDaycareOccupied(int loc, int slot, bool occupied) { } + #endregion - public int NextOpenBoxSlot(int lastKnownOccupied = -1) + public PKM GetPartySlotAtIndex(int index) => GetPartySlot(PartyBuffer, GetPartyOffset(index)); + + public void SetPartySlotAtIndex(PKM pk, int index, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) + { + // update party count + if ((uint)index > 5) + throw new ArgumentOutOfRangeException(nameof(index)); + + int currentCount = PartyCount; + if (pk.Species != 0) { - var storage = BoxBuffer.AsSpan(); - int count = BoxSlotCount * BoxCount; - for (int i = lastKnownOccupied + 1; i < count; i++) - { - int offset = GetBoxSlotOffset(i); - if (!IsPKMPresent(storage[offset..])) - return i; - } - return StorageFullValue; + if (currentCount <= index) + PartyCount = index + 1; + } + else if (currentCount > index) + { + PartyCount = index; } - protected virtual bool IsSlotSwapProtected(int box, int slot) => false; - - private bool IsRegionOverwriteProtected(int min, int max) - { - foreach (var arrays in SlotPointers) - { - foreach (int slotIndex in arrays) - { - if (!GetSlotFlags(slotIndex).IsOverwriteProtected()) - continue; - if (ArrayUtil.WithinRange(slotIndex, min, max)) - return true; - } - } - - return false; - } - - public bool IsAnySlotLockedInBox(int BoxStart, int BoxEnd) - { - foreach (var arrays in SlotPointers) - { - foreach (int slotIndex in arrays) - { - if (!GetSlotFlags(slotIndex).HasFlagFast(StorageSlotFlag.Locked)) - continue; - if (ArrayUtil.WithinRange(slotIndex, BoxStart * BoxSlotCount, (BoxEnd + 1) * BoxSlotCount)) - return true; - } - } - return false; - } - #endregion - - #region Storage Offsets and Indexing - public abstract int GetBoxOffset(int box); - public int GetBoxSlotOffset(int box, int slot) => GetBoxOffset(box) + (slot * SIZE_BOXSLOT); - public PKM GetBoxSlotAtIndex(int box, int slot) => GetBoxSlot(GetBoxSlotOffset(box, slot)); - - public void GetBoxSlotFromIndex(int index, out int box, out int slot) - { - box = index / BoxSlotCount; - if ((uint)box >= BoxCount) - throw new ArgumentOutOfRangeException(nameof(index)); - slot = index % BoxSlotCount; - } - - public PKM GetBoxSlotAtIndex(int index) - { - GetBoxSlotFromIndex(index, out int box, out int slot); - return GetBoxSlotAtIndex(box, slot); - } - - public int GetBoxSlotOffset(int index) - { - GetBoxSlotFromIndex(index, out int box, out int slot); - return GetBoxSlotOffset(box, slot); - } - - public void SetBoxSlotAtIndex(PKM pkm, int box, int slot, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) - => SetBoxSlot(pkm, BoxBuffer, GetBoxSlotOffset(box, slot), trade, dex); - - public void SetBoxSlotAtIndex(PKM pkm, int index, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) - => SetBoxSlot(pkm, BoxBuffer, GetBoxSlotOffset(index), trade, dex); - #endregion - - #region Storage Manipulations - - public bool MoveBox(int box, int insertBeforeBox) - { - if (box == insertBeforeBox) // no movement required - return true; - if ((uint)box >= BoxCount || (uint)insertBeforeBox >= BoxCount) // invalid box positions - return false; - - MoveBox(box, insertBeforeBox, BoxBuffer); - return true; - } - - private void MoveBox(int box, int insertBeforeBox, byte[] storage) - { - int pos1 = BoxSlotCount * box; - int pos2 = BoxSlotCount * insertBeforeBox; - int min = Math.Min(pos1, pos2); - int max = Math.Max(pos1, pos2); - - int len = BoxSlotCount * SIZE_BOXSLOT; - byte[] boxdata = storage.Slice(GetBoxOffset(0), len * BoxCount); // get all boxes - string[] boxNames = Get(GetBoxName, BoxCount); - int[] boxWallpapers = Get(GetBoxWallpaper, BoxCount); - - static T[] Get(Func act, int count) - { - T[] result = new T[count]; - for (int i = 0; i < result.Length; i++) - result[i] = act(i); - return result; - } - - min /= BoxSlotCount; - max /= BoxSlotCount; - - // move all boxes within range to final spot - for (int i = min, ctr = min; i < max; i++) - { - int b = insertBeforeBox; // if box is the moved box, move to insertion point, else move to unused box. - if (i != box) - { - if (insertBeforeBox == ctr) - ++ctr; - b = ctr++; - } - - Buffer.BlockCopy(boxdata, len * i, storage, GetBoxOffset(b), len); - SetBoxName(b, boxNames[i]); - SetBoxWallpaper(b, boxWallpapers[i]); - } - - SlotPointerUtil.UpdateMove(box, insertBeforeBox, BoxSlotCount, SlotPointers); - } - - public bool SwapBox(int box1, int box2) - { - if (box1 == box2) // no movement required - return true; - if ((uint)box1 >= BoxCount || (uint)box2 >= BoxCount) // invalid box positions - return false; - - if (!IsBoxAbleToMove(box1) || !IsBoxAbleToMove(box2)) - return false; - - SwapBox(box1, box2, BoxBuffer); - return true; - } - - private void SwapBox(int box1, int box2, Span boxData) - { - int b1o = GetBoxOffset(box1); - int b2o = GetBoxOffset(box2); - int len = BoxSlotCount * SIZE_BOXSLOT; - Span b1 = stackalloc byte[len]; - boxData.Slice(b1o, len).CopyTo(b1); - boxData.Slice(b2o, len).CopyTo(boxData[b1o..]); - b1.CopyTo(boxData[b2o..]); - - // Name - string b1n = GetBoxName(box1); - SetBoxName(box1, GetBoxName(box2)); - SetBoxName(box2, b1n); - - // Wallpaper - int b1w = GetBoxWallpaper(box1); - SetBoxWallpaper(box1, GetBoxWallpaper(box2)); - SetBoxWallpaper(box2, b1w); - - // Pointers - SlotPointerUtil.UpdateSwap(box1, box2, BoxSlotCount, SlotPointers); - } - - private bool IsBoxAbleToMove(int box) - { - int min = BoxSlotCount * box; - int max = min + BoxSlotCount; - return !IsRegionOverwriteProtected(min, max); - } - - /// - /// Sorts all present within the range specified by and with the provied . - /// - /// Starting box; if not provided, will iterate from the first box. - /// Ending box; if not provided, will iterate to the end. - /// Sorting logic required to order a with respect to its peers; if not provided, will use a default sorting method. - /// Reverse the sorting order - /// Count of repositioned slots. - public int SortBoxes(int BoxStart = 0, int BoxEnd = -1, Func, int, IEnumerable>? sortMethod = null, bool reverse = false) - { - var BD = BoxData; - int start = BoxSlotCount * BoxStart; - var Section = BD.Skip(start); - if (BoxEnd >= BoxStart) - Section = Section.Take(BoxSlotCount * (BoxEnd - BoxStart + 1)); - - Func skip = IsSlotOverwriteProtected; - Section = Section.Where((_, i) => !skip(start + i)); - var method = sortMethod ?? ((z, _) => z.OrderBySpecies()); - var Sorted = method(Section, start); - if (reverse) - Sorted = Sorted.ReverseSort(); - - var result = Sorted.ToArray(); - var boxclone = new PKM[BD.Count]; - BD.CopyTo(boxclone, 0); - int count = result.CopyTo(boxclone, skip, start); - - SlotPointerUtil.UpdateRepointFrom(boxclone, BD, 0, SlotPointers); - - for (int i = 0; i < boxclone.Length; i++) - { - var pk = boxclone[i]; - SetBoxSlotAtIndex(pk, i, PKMImportSetting.Skip, PKMImportSetting.Skip); - } - return count; - } - - /// - /// Compresses the by pulling out the empty storage slots and putting them at the end, retaining all existing data. - /// - /// Count of actual stored. - /// Important slot pointers that need to be repointed if a slot moves. - /// True if was updated, false if no update done. - public bool CompressStorage(out int storedCount, params IList[] slotPointers) => this.CompressStorage(BoxBuffer, out storedCount, slotPointers); - - /// - /// Removes all present within the range specified by and if the provied is satisfied. - /// - /// Starting box; if not provided, will iterate from the first box. - /// Ending box; if not provided, will iterate to the end. - /// Criteria required to be satisfied for a to be deleted; if not provided, will clear if possible. - /// Count of deleted slots. - public int ClearBoxes(int BoxStart = 0, int BoxEnd = -1, Func? deleteCriteria = null) - { - var storage = BoxBuffer.AsSpan(); - - if (BoxEnd < 0) - BoxEnd = BoxCount - 1; - - var blank = GetDataForBox(BlankPKM); - int deleted = 0; - for (int i = BoxStart; i <= BoxEnd; i++) - { - for (int p = 0; p < BoxSlotCount; p++) - { - if (IsSlotOverwriteProtected(i, p)) - continue; - var ofs = GetBoxSlotOffset(i, p); - if (!IsPKMPresent(storage[ofs..])) - continue; - if (deleteCriteria != null) - { - var pk = GetBoxSlotAtIndex(i, p); - if (!deleteCriteria(pk)) - continue; - } - - SetData(storage, blank, ofs); - ++deleted; - } - } - return deleted; - } - - /// - /// Modifies all present within the range specified by and with the modification routine provided by . - /// - /// Modification to perform on a - /// Starting box; if not provided, will iterate from the first box. - /// Ending box (inclusive); if not provided, will iterate to the end. - /// Count of modified slots. - public int ModifyBoxes(Action action, int BoxStart = 0, int BoxEnd = -1) - { - if (BoxEnd < 0) - BoxEnd = BoxCount - 1; - - var storage = BoxBuffer.AsSpan(); - int modified = 0; - for (int b = BoxStart; b <= BoxEnd; b++) - { - for (int s = 0; s < BoxSlotCount; s++) - { - if (IsSlotOverwriteProtected(b, s)) - continue; - var ofs = GetBoxSlotOffset(b, s); - if (!IsPKMPresent(storage[ofs..])) - continue; - var pk = GetBoxSlotAtIndex(b, s); - action(pk); - ++modified; - SetBoxSlot(pk, storage, ofs, PKMImportSetting.Skip, PKMImportSetting.Skip); - } - } - return modified; - } - #endregion - - #region Storage Name & Decoration - public virtual bool HasBoxWallpapers => GetBoxWallpaperOffset(0) > -1; - public virtual bool HasNamableBoxes => HasBoxWallpapers; - - public abstract string GetBoxName(int box); - public abstract void SetBoxName(int box, string value); - protected virtual int GetBoxWallpaperOffset(int box) => -1; - - public virtual int GetBoxWallpaper(int box) - { - int offset = GetBoxWallpaperOffset(box); - if (offset < 0 || (uint)box > BoxCount) - return box; - return Data[offset]; - } - - public virtual void SetBoxWallpaper(int box, int value) - { - int offset = GetBoxWallpaperOffset(box); - if (offset < 0 || (uint)box > BoxCount) - return; - Data[offset] = (byte)value; - } - #endregion - - #region Box Binaries - public byte[] GetPCBinary() => BoxData.SelectMany(GetDataForBox).ToArray(); - public byte[] GetBoxBinary(int box) => GetBoxData(box).SelectMany(GetDataForBox).ToArray(); - - public bool SetPCBinary(byte[] data) - { - if (IsRegionOverwriteProtected(0, SlotCount)) - return false; - - int expectLength = SlotCount * SIZE_BOXSLOT; - return SetConcatenatedBinary(data, expectLength); - } - - public bool SetBoxBinary(byte[] data, int box) - { - int start = box * BoxSlotCount; - int end = start + BoxSlotCount; - - if (IsRegionOverwriteProtected(start, end)) - return false; - - int expectLength = BoxSlotCount * SIZE_BOXSLOT; - return SetConcatenatedBinary(data, expectLength, start); - } - - private bool SetConcatenatedBinary(byte[] data, int expectLength, int start = 0) - { - if (data.Length != expectLength) - return false; - - var BD = BoxData; - var entryLength = SIZE_BOXSLOT; - var pkdata = ArrayUtil.EnumerateSplit(data, entryLength); - - pkdata.Select(GetPKM).CopyTo(BD, IsSlotOverwriteProtected, start); - BoxData = BD; - return true; - } - #endregion + int offset = GetPartyOffset(index); + SetPartySlot(pk, PartyBuffer, offset, trade, dex); } - public static class StorageUtil + public void SetSlotFormatParty(PKM pk, byte[] data, int offset, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) { - public static bool CompressStorage(this SaveFile sav, Span storage, out int storedCount, IList[] slotPointers) + if (pk.GetType() != PKMType) + throw new ArgumentException($"PKM Format needs to be {PKMType} when setting to this Save File."); + + UpdatePKM(pk, isParty: true, trade, dex); + SetPartyValues(pk, isParty: true); + WritePartySlot(pk, data, offset); + } + + public void SetPartySlot(PKM pk, byte[] data, int offset, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) + { + if (pk.GetType() != PKMType) + throw new ArgumentException($"PKM Format needs to be {PKMType} when setting to this Save File."); + + UpdatePKM(pk, isParty: true, trade, dex); + SetPartyValues(pk, isParty: true); + WritePartySlot(pk, data, offset); + } + + public void SetSlotFormatStored(PKM pk, Span data, int offset, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) + { + if (pk.GetType() != PKMType) + throw new ArgumentException($"PKM Format needs to be {PKMType} when setting to this Save File."); + + UpdatePKM(pk, isParty: false, trade, dex); + SetPartyValues(pk, isParty: false); + WriteSlotFormatStored(pk, data, offset); + } + + public void SetBoxSlot(PKM pk, Span data, int offset, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) + { + if (pk.GetType() != PKMType) + throw new ArgumentException($"PKM Format needs to be {PKMType} when setting to this Save File."); + + UpdatePKM(pk, isParty: false, trade, dex); + SetPartyValues(pk, isParty: false); + WriteBoxSlot(pk, data, offset); + } + + public void DeletePartySlot(int slot) + { + int newEmpty = PartyCount - 1; + if ((uint)slot > newEmpty) // beyond party range (or empty data already present) + return; + // Move all party slots down one + for (int i = slot + 1; i <= newEmpty; i++) // Slide slots down { - // keep track of empty slots, and only write them at the end if slots were shifted (no need otherwise). - var empty = new List(); - bool shiftedSlots = false; + var current = GetPartySlotAtIndex(i); + SetPartySlotAtIndex(current, i - 1, PKMImportSetting.Skip, PKMImportSetting.Skip); + } + SetPartySlotAtIndex(BlankPKM, newEmpty, PKMImportSetting.Skip, PKMImportSetting.Skip); + // PartyCount will automatically update via above call. Do not adjust. + } - ushort ctr = 0; - int size = sav.SIZE_BOXSLOT; - int count = sav.BoxSlotCount * sav.BoxCount; - for (int i = 0; i < count; i++) + #region Slot Storing + public static PKMImportSetting SetUpdateDex { protected get; set; } = PKMImportSetting.Update; + public static PKMImportSetting SetUpdatePKM { protected get; set; } = PKMImportSetting.Update; + + public abstract Type PKMType { get; } + protected abstract PKM GetPKM(byte[] data); + protected abstract byte[] DecryptPKM(byte[] data); + public abstract PKM BlankPKM { get; } + protected abstract int SIZE_STORED { get; } + protected abstract int SIZE_PARTY { get; } + public virtual int SIZE_BOXSLOT => SIZE_STORED; + public abstract int MaxEV { get; } + public virtual int MaxIV => 31; + public abstract IReadOnlyList HeldItems { get; } + protected virtual byte[] BoxBuffer => Data; + protected virtual byte[] PartyBuffer => Data; + public virtual bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresent(data); + public virtual PKM GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data)); + public virtual PKM GetPartySlot(byte[] data, int offset) => GetDecryptedPKM(GetData(data, offset, SIZE_PARTY)); + public virtual PKM GetStoredSlot(byte[] data, int offset) => GetDecryptedPKM(GetData(data, offset, SIZE_STORED)); + public virtual PKM GetBoxSlot(int offset) => GetStoredSlot(BoxBuffer, offset); + + public virtual byte[] GetDataForFormatStored(PKM pk) => pk.EncryptedBoxData; + public virtual byte[] GetDataForFormatParty(PKM pk) => pk.EncryptedPartyData; + public virtual byte[] GetDataForParty(PKM pk) => pk.EncryptedPartyData; + public virtual byte[] GetDataForBox(PKM pk) => pk.EncryptedBoxData; + + public virtual void WriteSlotFormatStored(PKM pk, Span data, int offset) => SetData(data, GetDataForFormatStored(pk), offset); + public virtual void WriteSlotFormatParty(PKM pk, Span data, int offset) => SetData(data, GetDataForFormatParty(pk), offset); + public virtual void WritePartySlot(PKM pk, Span data, int offset) => SetData(data, GetDataForParty(pk), offset); + public virtual void WriteBoxSlot(PKM pk, Span data, int offset) => SetData(data, GetDataForBox(pk), offset); + + protected virtual void SetPartyValues(PKM pk, bool isParty) + { + if (!isParty) + return; + if (pk.PartyStatsPresent) // Stats already present + return; + pk.ResetPartyStats(); + } + + /// + /// Conditions a for this save file as if it was traded to it. + /// + /// Entity to adapt + /// Entity exists in party format + /// Setting on whether or not to adapt + public void AdaptPKM(PKM pk, bool party = true, PKMImportSetting trade = PKMImportSetting.UseDefault) + { + if (GetTradeUpdateSetting(trade)) + SetPKM(pk, party); + } + + protected void UpdatePKM(PKM pk, bool isParty, PKMImportSetting trade, PKMImportSetting dex) + { + AdaptPKM(pk, isParty, trade); + if (GetDexUpdateSetting(dex)) + SetDex(pk); + } + + private static bool GetTradeUpdateSetting(PKMImportSetting trade = PKMImportSetting.UseDefault) + { + if (trade == PKMImportSetting.UseDefault) + trade = SetUpdatePKM; + return trade == PKMImportSetting.Update; + } + + private static bool GetDexUpdateSetting(PKMImportSetting trade = PKMImportSetting.UseDefault) + { + if (trade == PKMImportSetting.UseDefault) + trade = SetUpdateDex; + return trade == PKMImportSetting.Update; + } + + protected virtual void SetPKM(PKM pk, bool isParty = false) { } + protected virtual void SetDex(PKM pk) { } + #endregion + + #region Pokédex + public int PokeDex { get; protected set; } = int.MinValue; + public bool HasPokeDex => PokeDex > -1; + public virtual bool GetSeen(int species) => false; + public virtual void SetSeen(int species, bool seen) { } + public virtual bool GetCaught(int species) => false; + public virtual void SetCaught(int species, bool caught) { } + public int SeenCount => HasPokeDex ? Enumerable.Range(1, MaxSpeciesID).Count(GetSeen) : 0; + public int CaughtCount => HasPokeDex ? Enumerable.Range(1, MaxSpeciesID).Count(GetCaught) : 0; + public decimal PercentSeen => (decimal) SeenCount / MaxSpeciesID; + public decimal PercentCaught => (decimal)CaughtCount / MaxSpeciesID; + #endregion + + public bool HasBox => Box > -1; + public virtual int BoxSlotCount => 30; + public virtual int BoxesUnlocked { get => -1; set { } } + public virtual byte[] BoxFlags { get => Array.Empty(); set { } } + public virtual int CurrentBox { get; set; } + + #region BoxData + protected int Box { get; set; } = int.MinValue; + + public IList BoxData + { + get + { + PKM[] data = new PKM[BoxCount * BoxSlotCount]; + for (int box = 0; box < BoxCount; box++) + AddBoxData(data, box, box * BoxSlotCount); + return data; + } + set + { + if (value.Count != BoxCount * BoxSlotCount) + throw new ArgumentException($"Expected {BoxCount * BoxSlotCount}, got {value.Count}"); + + for (int b = 0; b < BoxCount; b++) + SetBoxData(value, b, b * BoxSlotCount); + } + } + + public int SetBoxData(IList value, int box, int index = 0) + { + int skipped = 0; + for (int slot = 0; slot < BoxSlotCount; slot++) + { + var flags = GetSlotFlags(box, slot); + if (!flags.IsOverwriteProtected()) + SetBoxSlotAtIndex(value[index + slot], box, slot); + else + ++skipped; + } + + return skipped; + } + + public PKM[] GetBoxData(int box) + { + var data = new PKM[BoxSlotCount]; + AddBoxData(data, box, 0); + return data; + } + + public void AddBoxData(IList data, int box, int index) + { + for (int slot = 0; slot < BoxSlotCount; slot++) + { + int i = slot + index; + data[i] = GetBoxSlotAtIndex(box, slot); + } + } + #endregion + + #region Storage Health & Metadata + protected int[] TeamSlots = Array.Empty(); + + /// + /// Slot indexes that are protected from overwriting. + /// + protected virtual IList[] SlotPointers => new[] { TeamSlots }; + public virtual StorageSlotSource GetSlotFlags(int index) => StorageSlotSource.None; + public StorageSlotSource GetSlotFlags(int box, int slot) => GetSlotFlags((box * BoxSlotCount) + slot); + public bool IsSlotLocked(int box, int slot) => GetSlotFlags(box, slot).HasFlagFast(StorageSlotSource.Locked); + public bool IsSlotLocked(int index) => GetSlotFlags(index).HasFlagFast(StorageSlotSource.Locked); + public bool IsSlotOverwriteProtected(int box, int slot) => GetSlotFlags(box, slot).IsOverwriteProtected(); + public bool IsSlotOverwriteProtected(int index) => GetSlotFlags(index).IsOverwriteProtected(); + + private const int StorageFullValue = -1; + public bool IsStorageFull => NextOpenBoxSlot() == StorageFullValue; + + public int NextOpenBoxSlot(int lastKnownOccupied = -1) + { + var storage = BoxBuffer.AsSpan(); + int count = BoxSlotCount * BoxCount; + for (int i = lastKnownOccupied + 1; i < count; i++) + { + int offset = GetBoxSlotOffset(i); + if (!IsPKMPresent(storage[offset..])) + return i; + } + return StorageFullValue; + } + + protected virtual bool IsSlotSwapProtected(int box, int slot) => false; + + private bool IsRegionOverwriteProtected(int min, int max) + { + foreach (var arrays in SlotPointers) + { + foreach (int slotIndex in arrays) { - int offset = sav.GetBoxSlotOffset(i); - if (sav.IsPKMPresent(storage[offset..])) - { - if (ctr != i) // copy required - { - shiftedSlots = true; // appending empty slots afterwards is now required since a rewrite was done - int destOfs = sav.GetBoxSlotOffset(ctr); - storage[offset..(offset + size)].CopyTo(storage[destOfs..(destOfs + size)]); - SlotPointerUtil.UpdateRepointFrom(ctr, i, slotPointers); - } - - ctr++; + if (!GetSlotFlags(slotIndex).IsOverwriteProtected()) continue; + if (ArrayUtil.WithinRange(slotIndex, min, max)) + return true; + } + } + + return false; + } + + public bool IsAnySlotLockedInBox(int BoxStart, int BoxEnd) + { + foreach (var arrays in SlotPointers) + { + foreach (int slotIndex in arrays) + { + if (!GetSlotFlags(slotIndex).HasFlagFast(StorageSlotSource.Locked)) + continue; + if (ArrayUtil.WithinRange(slotIndex, BoxStart * BoxSlotCount, (BoxEnd + 1) * BoxSlotCount)) + return true; + } + } + return false; + } + #endregion + + #region Storage Offsets and Indexing + public abstract int GetBoxOffset(int box); + public int GetBoxSlotOffset(int box, int slot) => GetBoxOffset(box) + (slot * SIZE_BOXSLOT); + public PKM GetBoxSlotAtIndex(int box, int slot) => GetBoxSlot(GetBoxSlotOffset(box, slot)); + + public void GetBoxSlotFromIndex(int index, out int box, out int slot) + { + box = index / BoxSlotCount; + if ((uint)box >= BoxCount) + throw new ArgumentOutOfRangeException(nameof(index)); + slot = index % BoxSlotCount; + } + + public PKM GetBoxSlotAtIndex(int index) + { + GetBoxSlotFromIndex(index, out int box, out int slot); + return GetBoxSlotAtIndex(box, slot); + } + + public int GetBoxSlotOffset(int index) + { + GetBoxSlotFromIndex(index, out int box, out int slot); + return GetBoxSlotOffset(box, slot); + } + + public void SetBoxSlotAtIndex(PKM pk, int box, int slot, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) + => SetBoxSlot(pk, BoxBuffer, GetBoxSlotOffset(box, slot), trade, dex); + + public void SetBoxSlotAtIndex(PKM pk, int index, PKMImportSetting trade = PKMImportSetting.UseDefault, PKMImportSetting dex = PKMImportSetting.UseDefault) + => SetBoxSlot(pk, BoxBuffer, GetBoxSlotOffset(index), trade, dex); + #endregion + + #region Storage Manipulations + + public bool MoveBox(int box, int insertBeforeBox) + { + if (box == insertBeforeBox) // no movement required + return true; + if ((uint)box >= BoxCount || (uint)insertBeforeBox >= BoxCount) // invalid box positions + return false; + + MoveBox(box, insertBeforeBox, BoxBuffer); + return true; + } + + private void MoveBox(int box, int insertBeforeBox, byte[] storage) + { + int pos1 = BoxSlotCount * box; + int pos2 = BoxSlotCount * insertBeforeBox; + int min = Math.Min(pos1, pos2); + int max = Math.Max(pos1, pos2); + + int len = BoxSlotCount * SIZE_BOXSLOT; + byte[] boxdata = storage.Slice(GetBoxOffset(0), len * BoxCount); // get all boxes + string[] boxNames = Get(GetBoxName, BoxCount); + int[] boxWallpapers = Get(GetBoxWallpaper, BoxCount); + + static T[] Get(Func act, int count) + { + T[] result = new T[count]; + for (int i = 0; i < result.Length; i++) + result[i] = act(i); + return result; + } + + min /= BoxSlotCount; + max /= BoxSlotCount; + + // move all boxes within range to final spot + for (int i = min, ctr = min; i < max; i++) + { + int b = insertBeforeBox; // if box is the moved box, move to insertion point, else move to unused box. + if (i != box) + { + if (insertBeforeBox == ctr) + ++ctr; + b = ctr++; + } + + Buffer.BlockCopy(boxdata, len * i, storage, GetBoxOffset(b), len); + SetBoxName(b, boxNames[i]); + SetBoxWallpaper(b, boxWallpapers[i]); + } + + SlotPointerUtil.UpdateMove(box, insertBeforeBox, BoxSlotCount, SlotPointers); + } + + public bool SwapBox(int box1, int box2) + { + if (box1 == box2) // no movement required + return true; + if ((uint)box1 >= BoxCount || (uint)box2 >= BoxCount) // invalid box positions + return false; + + if (!IsBoxAbleToMove(box1) || !IsBoxAbleToMove(box2)) + return false; + + SwapBox(box1, box2, BoxBuffer); + return true; + } + + private void SwapBox(int box1, int box2, Span boxData) + { + int b1o = GetBoxOffset(box1); + int b2o = GetBoxOffset(box2); + int len = BoxSlotCount * SIZE_BOXSLOT; + Span b1 = stackalloc byte[len]; + boxData.Slice(b1o, len).CopyTo(b1); + boxData.Slice(b2o, len).CopyTo(boxData[b1o..]); + b1.CopyTo(boxData[b2o..]); + + // Name + string b1n = GetBoxName(box1); + SetBoxName(box1, GetBoxName(box2)); + SetBoxName(box2, b1n); + + // Wallpaper + int b1w = GetBoxWallpaper(box1); + SetBoxWallpaper(box1, GetBoxWallpaper(box2)); + SetBoxWallpaper(box2, b1w); + + // Pointers + SlotPointerUtil.UpdateSwap(box1, box2, BoxSlotCount, SlotPointers); + } + + private bool IsBoxAbleToMove(int box) + { + int min = BoxSlotCount * box; + int max = min + BoxSlotCount; + return !IsRegionOverwriteProtected(min, max); + } + + /// + /// Sorts all present within the range specified by and with the provied . + /// + /// Starting box; if not provided, will iterate from the first box. + /// Ending box; if not provided, will iterate to the end. + /// Sorting logic required to order a with respect to its peers; if not provided, will use a default sorting method. + /// Reverse the sorting order + /// Count of repositioned slots. + public int SortBoxes(int BoxStart = 0, int BoxEnd = -1, Func, int, IEnumerable>? sortMethod = null, bool reverse = false) + { + var BD = BoxData; + int start = BoxSlotCount * BoxStart; + var Section = BD.Skip(start); + if (BoxEnd >= BoxStart) + Section = Section.Take(BoxSlotCount * (BoxEnd - BoxStart + 1)); + + Func skip = IsSlotOverwriteProtected; + Section = Section.Where((_, i) => !skip(start + i)); + var method = sortMethod ?? ((z, _) => z.OrderBySpecies()); + var Sorted = method(Section, start); + if (reverse) + Sorted = Sorted.ReverseSort(); + + var result = Sorted.ToArray(); + var boxclone = new PKM[BD.Count]; + BD.CopyTo(boxclone, 0); + int count = result.CopyTo(boxclone, skip, start); + + SlotPointerUtil.UpdateRepointFrom(boxclone, BD, 0, SlotPointers); + + for (int i = 0; i < boxclone.Length; i++) + { + var pk = boxclone[i]; + SetBoxSlotAtIndex(pk, i, PKMImportSetting.Skip, PKMImportSetting.Skip); + } + return count; + } + + /// + /// Compresses the by pulling out the empty storage slots and putting them at the end, retaining all existing data. + /// + /// Count of actual stored. + /// Important slot pointers that need to be repointed if a slot moves. + /// True if was updated, false if no update done. + public bool CompressStorage(out int storedCount, params IList[] slotPointers) => this.CompressStorage(BoxBuffer, out storedCount, slotPointers); + + /// + /// Removes all present within the range specified by and if the provied is satisfied. + /// + /// Starting box; if not provided, will iterate from the first box. + /// Ending box; if not provided, will iterate to the end. + /// Criteria required to be satisfied for a to be deleted; if not provided, will clear if possible. + /// Count of deleted slots. + public int ClearBoxes(int BoxStart = 0, int BoxEnd = -1, Func? deleteCriteria = null) + { + var storage = BoxBuffer.AsSpan(); + + if (BoxEnd < 0) + BoxEnd = BoxCount - 1; + + var blank = GetDataForBox(BlankPKM); + int deleted = 0; + for (int i = BoxStart; i <= BoxEnd; i++) + { + for (int p = 0; p < BoxSlotCount; p++) + { + if (IsSlotOverwriteProtected(i, p)) + continue; + var ofs = GetBoxSlotOffset(i, p); + if (!IsPKMPresent(storage[ofs..])) + continue; + if (deleteCriteria != null) + { + var pk = GetBoxSlotAtIndex(i, p); + if (!deleteCriteria(pk)) + continue; } - // pop out an empty slot; save all unused data & preserve order - var data = storage.Slice(offset, size).ToArray(); - empty.Add(data); + SetData(storage, blank, ofs); + ++deleted; } - - storedCount = ctr; - - if (!shiftedSlots) - return false; - - for (int i = ctr; i < count; i++) - { - var data = empty[i - ctr]; - int offset = sav.GetBoxSlotOffset(i); - data.CopyTo(storage[offset..]); - } - - return true; } + return deleted; + } + + /// + /// Modifies all present within the range specified by and with the modification routine provided by . + /// + /// Modification to perform on a + /// Starting box; if not provided, will iterate from the first box. + /// Ending box (inclusive); if not provided, will iterate to the end. + /// Count of modified slots. + public int ModifyBoxes(Action action, int BoxStart = 0, int BoxEnd = -1) + { + if (BoxEnd < 0) + BoxEnd = BoxCount - 1; + + var storage = BoxBuffer.AsSpan(); + int modified = 0; + for (int b = BoxStart; b <= BoxEnd; b++) + { + for (int s = 0; s < BoxSlotCount; s++) + { + if (IsSlotOverwriteProtected(b, s)) + continue; + var ofs = GetBoxSlotOffset(b, s); + if (!IsPKMPresent(storage[ofs..])) + continue; + var pk = GetBoxSlotAtIndex(b, s); + action(pk); + ++modified; + SetBoxSlot(pk, storage, ofs, PKMImportSetting.Skip, PKMImportSetting.Skip); + } + } + return modified; + } + #endregion + + #region Storage Name & Decoration + public virtual bool HasBoxWallpapers => GetBoxWallpaperOffset(0) > -1; + public virtual bool HasNamableBoxes => HasBoxWallpapers; + + public abstract string GetBoxName(int box); + public abstract void SetBoxName(int box, string value); + protected virtual int GetBoxWallpaperOffset(int box) => -1; + + public virtual int GetBoxWallpaper(int box) + { + int offset = GetBoxWallpaperOffset(box); + if (offset < 0 || (uint)box > BoxCount) + return box; + return Data[offset]; + } + + public virtual void SetBoxWallpaper(int box, int value) + { + int offset = GetBoxWallpaperOffset(box); + if (offset < 0 || (uint)box > BoxCount) + return; + Data[offset] = (byte)value; + } + #endregion + + #region Box Binaries + public byte[] GetPCBinary() => BoxData.SelectMany(GetDataForBox).ToArray(); + public byte[] GetBoxBinary(int box) => GetBoxData(box).SelectMany(GetDataForBox).ToArray(); + + public bool SetPCBinary(byte[] data) + { + if (IsRegionOverwriteProtected(0, SlotCount)) + return false; + + int expectLength = SlotCount * SIZE_BOXSLOT; + return SetConcatenatedBinary(data, expectLength); + } + + public bool SetBoxBinary(byte[] data, int box) + { + int start = box * BoxSlotCount; + int end = start + BoxSlotCount; + + if (IsRegionOverwriteProtected(start, end)) + return false; + + int expectLength = BoxSlotCount * SIZE_BOXSLOT; + return SetConcatenatedBinary(data, expectLength, start); + } + + private bool SetConcatenatedBinary(byte[] data, int expectLength, int start = 0) + { + if (data.Length != expectLength) + return false; + + var BD = BoxData; + var entryLength = SIZE_BOXSLOT; + var pkdata = ArrayUtil.EnumerateSplit(data, entryLength); + + pkdata.Select(GetPKM).CopyTo(BD, IsSlotOverwriteProtected, start); + BoxData = BD; + return true; + } + #endregion +} + +public static class StorageUtil +{ + public static bool CompressStorage(this SaveFile sav, Span storage, out int storedCount, IList[] slotPointers) + { + // keep track of empty slots, and only write them at the end if slots were shifted (no need otherwise). + var empty = new List(); + bool shiftedSlots = false; + + ushort ctr = 0; + int size = sav.SIZE_BOXSLOT; + int count = sav.BoxSlotCount * sav.BoxCount; + for (int i = 0; i < count; i++) + { + int offset = sav.GetBoxSlotOffset(i); + if (sav.IsPKMPresent(storage[offset..])) + { + if (ctr != i) // copy required + { + shiftedSlots = true; // appending empty slots afterwards is now required since a rewrite was done + int destOfs = sav.GetBoxSlotOffset(ctr); + storage[offset..(offset + size)].CopyTo(storage[destOfs..(destOfs + size)]); + SlotPointerUtil.UpdateRepointFrom(ctr, i, slotPointers); + } + + ctr++; + continue; + } + + // pop out an empty slot; save all unused data & preserve order + var data = storage.Slice(offset, size).ToArray(); + empty.Add(data); + } + + storedCount = ctr; + + if (!shiftedSlots) + return false; + + for (int i = ctr; i < count; i++) + { + var data = empty[i - ctr]; + int offset = sav.GetBoxSlotOffset(i); + data.CopyTo(storage[offset..]); + } + + return true; } } diff --git a/PKHeX.Core/Saves/SaveFileMetadata.cs b/PKHeX.Core/Saves/SaveFileMetadata.cs index a80a203c1..ddd6ed69a 100644 --- a/PKHeX.Core/Saves/SaveFileMetadata.cs +++ b/PKHeX.Core/Saves/SaveFileMetadata.cs @@ -1,137 +1,136 @@ using System; using System.IO; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Tracks information about where the originated from, and provides logic for saving to a file. +/// +public sealed class SaveFileMetadata { + private readonly SaveFile SAV; + public SaveFileMetadata(SaveFile sav) => SAV = sav; + /// - /// Tracks information about where the originated from, and provides logic for saving to a file. + /// Full path where the originated from. /// - public sealed class SaveFileMetadata + public string? FilePath { get; private set; } + + /// + /// File Name of the . + /// + /// This is not always the original file name. We try to strip out the Backup name markings to get the original filename. + public string? FileName { get; private set; } + + /// + /// Directory in which the was saved in. + /// + public string? FileFolder { get; private set; } + + private byte[] Footer = Array.Empty(); // .dsv + private byte[] Header = Array.Empty(); // .gci + + private string BAKSuffix => $" [{SAV.ShortSummary}].bak"; + + /// + /// Simple summary of the save file, to help differentiate it from other save files with the same filename. + /// + public string BAKName => FileName + BAKSuffix; + + public bool HasHeader => Header.Length != 0; + public bool HasFooter => Footer.Length != 0; + + /// + /// File Dialog filter to help save the file. + /// + public string Filter => $"{SAV.GetType().Name}|{GetSuggestedExtension()}|All Files|*.*"; + + /// + /// Writes the input and appends the and if requested. + /// + /// Finalized save file data (with fixed checksums) to be written to a file + /// Toggle flags + /// Final save file data. + public byte[] Finalize(byte[] data, BinaryExportSetting setting) { - private readonly SaveFile SAV; - public SaveFileMetadata(SaveFile sav) => SAV = sav; + if (Footer.Length > 0 && setting.HasFlagFast(BinaryExportSetting.IncludeFooter)) + return ArrayUtil.ConcatAll(data, Footer); + if (Header.Length > 0 && setting.HasFlagFast(BinaryExportSetting.IncludeHeader)) + return ArrayUtil.ConcatAll(Header, data); + return data; + } - /// - /// Full path where the originated from. - /// - public string? FilePath { get; private set; } + /// + /// Sets the details of any trimmed header and footer arrays to a object. + /// + public void SetExtraInfo(byte[] header, byte[] footer) + { + Header = header; + Footer = footer; + } - /// - /// File Name of the . - /// - /// This is not always the original file name. We try to strip out the Backup name markings to get the original filename. - public string? FileName { get; private set; } - - /// - /// Directory in which the was saved in. - /// - public string? FileFolder { get; private set; } - - private byte[] Footer = Array.Empty(); // .dsv - private byte[] Header = Array.Empty(); // .gci - - private string BAKSuffix => $" [{SAV.ShortSummary}].bak"; - - /// - /// Simple summary of the save file, to help differentiate it from other save files with the same filename. - /// - public string BAKName => FileName + BAKSuffix; - - public bool HasHeader => Header.Length != 0; - public bool HasFooter => Footer.Length != 0; - - /// - /// File Dialog filter to help save the file. - /// - public string Filter => $"{SAV.GetType().Name}|{GetSuggestedExtension()}|All Files|*.*"; - - /// - /// Writes the input and appends the and if requested. - /// - /// Finalized save file data (with fixed checksums) to be written to a file - /// Toggle flags - /// Final save file data. - public byte[] Finalize(byte[] data, ExportFlags flags) + /// + /// Sets the details of a path to a object. + /// + /// Full Path of the file + public void SetExtraInfo(string path) + { + var sav = SAV; + if (!sav.State.Exportable || string.IsNullOrWhiteSpace(path)) // Blank save file { - if (Footer.Length > 0 && flags.HasFlagFast(ExportFlags.IncludeFooter)) - return ArrayUtil.ConcatAll(data, Footer); - if (Header.Length > 0 && flags.HasFlagFast(ExportFlags.IncludeHeader)) - return ArrayUtil.ConcatAll(Header, data); - return data; + sav.Metadata.SetAsBlank(); + return; } - /// - /// Sets the details of any trimmed header and footer arrays to a object. - /// - public void SetExtraInfo(byte[] header, byte[] footer) - { - Header = header; - Footer = footer; - } + SetAsLoadedFile(path); + } - /// - /// Sets the details of a path to a object. - /// - /// Full Path of the file - public void SetExtraInfo(string path) - { - var sav = SAV; - if (!sav.State.Exportable || string.IsNullOrWhiteSpace(path)) // Blank save file - { - sav.Metadata.SetAsBlank(); - return; - } + private void SetAsLoadedFile(string path) + { + FilePath = path; + FileFolder = Path.GetDirectoryName(path); + FileName = GetFileName(path, BAKSuffix); + } - SetAsLoadedFile(path); - } + private static string GetFileName(string path, string bak) + { + var bakName = Util.CleanFileName(bak); + var fn = Path.GetFileName(path); + return fn.EndsWith(bakName, StringComparison.Ordinal) ? fn[..^bakName.Length] : fn; + } - private void SetAsLoadedFile(string path) - { - FilePath = path; - FileFolder = Path.GetDirectoryName(path); - FileName = GetFileName(path, BAKSuffix); - } + private void SetAsBlank() + { + FileFolder = FilePath = string.Empty; + FileName = "Blank Save File"; + } - private static string GetFileName(string path, string bak) - { - var bakName = Util.CleanFileName(bak); - var fn = Path.GetFileName(path); - return fn.EndsWith(bakName) ? fn[..^bakName.Length] : fn; - } + /// + /// Gets the suggested file extension when writing to a saved file. + /// + public string GetSuggestedExtension() + { + var sav = SAV; + var fn = sav.Metadata.FileName; + if (fn != null) + return Path.GetExtension(fn); - private void SetAsBlank() - { - FileFolder = FilePath = string.Empty; - FileName = "Blank Save File"; - } + if ((sav.Generation is 4 or 5) && sav.Metadata.HasFooter) + return ".dsv"; + return sav.Extension; + } - /// - /// Gets the suggested file extension when writing to a saved file. - /// - public string GetSuggestedExtension() - { - var sav = SAV; - var fn = sav.Metadata.FileName; - if (fn != null) - return Path.GetExtension(fn); - - if ((sav.Generation is 4 or 5) && sav.Metadata.HasFooter) - return ".dsv"; - return sav.Extension; - } - - /// - /// Gets suggested export options for the save file. - /// - /// Selected export extension - public ExportFlags GetSuggestedFlags(string? ext = null) - { - var flags = ExportFlags.None; - if (ext == ".dsv") - flags |= ExportFlags.IncludeFooter; - if (ext == ".gci" || SAV is IGCSaveFile {MemoryCard: null}) - flags |= ExportFlags.IncludeHeader; - return flags; - } + /// + /// Gets suggested export options for the save file. + /// + /// Selected export extension + public BinaryExportSetting GetSuggestedFlags(string? ext = null) + { + var flags = BinaryExportSetting.None; + if (ext == ".dsv") + flags |= BinaryExportSetting.IncludeFooter; + if (ext == ".gci" || SAV is IGCSaveFile {MemoryCard: null}) + flags |= BinaryExportSetting.IncludeHeader; + return flags; } } diff --git a/PKHeX.Core/Saves/Storage/Bank3.cs b/PKHeX.Core/Saves/Storage/Bank3.cs index 1de7a918c..0bf3890f0 100644 --- a/PKHeX.Core/Saves/Storage/Bank3.cs +++ b/PKHeX.Core/Saves/Storage/Bank3.cs @@ -1,27 +1,26 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 3 object that reads exported data for Generation 3 PokeStock .gst dumps. +/// +public sealed class Bank3 : BulkStorage { - /// - /// Generation 3 object that reads exported data for Generation 3 PokeStock .gst dumps. - /// - public sealed class Bank3 : BulkStorage - { - public Bank3(byte[] data) : base(data, typeof(PK3), 0) => Version = GameVersion.RS; + public Bank3(byte[] data) : base(data, typeof(PK3), 0) => Version = GameVersion.RS; - public override PersonalTable Personal => PersonalTable.RS; - public override IReadOnlyList HeldItems => Legal.HeldItems_RS; - protected override SaveFile CloneInternal() => new Bank3((byte[])Data.Clone()); - public override string PlayTimeString => Checksums.CRC16Invert(Data).ToString("X4"); - protected internal override string ShortSummary => PlayTimeString; - public override string Extension => ".gst"; + public override PersonalTable Personal => PersonalTable.RS; + public override IReadOnlyList HeldItems => Legal.HeldItems_RS; + protected override SaveFile CloneInternal() => new Bank3((byte[])Data.Clone()); + public override string PlayTimeString => Checksums.CRC16Invert(Data).ToString("X4"); + protected internal override string ShortSummary => PlayTimeString; + public override string Extension => ".gst"; - public override int BoxCount => 64; - private const int BoxNameSize = 9; + public override int BoxCount => 64; + private const int BoxNameSize = 9; - private int BoxDataSize => SlotsPerBox * SIZE_STORED; - public override int GetBoxOffset(int box) => Box + (BoxDataSize * box); - public override string GetBoxName(int box) => GetString(GetBoxNameOffset(box), BoxNameSize); - private static int GetBoxNameOffset(int box) => 0x25800 + (9 * box); - } -} \ No newline at end of file + private int BoxDataSize => SlotsPerBox * SIZE_STORED; + public override int GetBoxOffset(int box) => Box + (BoxDataSize * box); + public override string GetBoxName(int box) => GetString(GetBoxNameOffset(box), BoxNameSize); + private static int GetBoxNameOffset(int box) => 0x25800 + (9 * box); +} diff --git a/PKHeX.Core/Saves/Storage/Bank4.cs b/PKHeX.Core/Saves/Storage/Bank4.cs index 9f0230b70..5ee09672f 100644 --- a/PKHeX.Core/Saves/Storage/Bank4.cs +++ b/PKHeX.Core/Saves/Storage/Bank4.cs @@ -1,27 +1,26 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 object that reads Generation 4 PokeStock .stk dumps. +/// +public sealed class Bank4 : BulkStorage { - /// - /// Generation 4 object that reads Generation 4 PokeStock .stk dumps. - /// - public sealed class Bank4 : BulkStorage - { - public Bank4(byte[] data) : base(data, typeof(PK4), 0) => Version = GameVersion.HGSS; + public Bank4(byte[] data) : base(data, typeof(PK4), 0) => Version = GameVersion.HGSS; - public override PersonalTable Personal => PersonalTable.HGSS; - public override IReadOnlyList HeldItems => Legal.HeldItems_HGSS; - protected override SaveFile CloneInternal() => new Bank4((byte[])Data.Clone()); - public override string PlayTimeString => Checksums.CRC16Invert(Data).ToString("X4"); - protected internal override string ShortSummary => PlayTimeString; - public override string Extension => ".stk"; + public override PersonalTable Personal => PersonalTable.HGSS; + public override IReadOnlyList HeldItems => Legal.HeldItems_HGSS; + protected override SaveFile CloneInternal() => new Bank4((byte[])Data.Clone()); + public override string PlayTimeString => Checksums.CRC16Invert(Data).ToString("X4"); + protected internal override string ShortSummary => PlayTimeString; + public override string Extension => ".stk"; - public override int BoxCount => 64; - private const int BoxNameSize = 0x18; + public override int BoxCount => 64; + private const int BoxNameSize = 0x18; - private int BoxDataSize => SlotsPerBox * SIZE_STORED; - public override int GetBoxOffset(int box) => Box + (BoxDataSize * box); - public override string GetBoxName(int box) => GetString(GetBoxNameOffset(box), BoxNameSize / 2); - private static int GetBoxNameOffset(int box) => 0x3FC00 + (0x19 * box); - } -} \ No newline at end of file + private int BoxDataSize => SlotsPerBox * SIZE_STORED; + public override int GetBoxOffset(int box) => Box + (BoxDataSize * box); + public override string GetBoxName(int box) => GetString(GetBoxNameOffset(box), BoxNameSize / 2); + private static int GetBoxNameOffset(int box) => 0x3FC00 + (0x19 * box); +} diff --git a/PKHeX.Core/Saves/Storage/Bank7.cs b/PKHeX.Core/Saves/Storage/Bank7.cs index 2898e752f..d6d91a17c 100644 --- a/PKHeX.Core/Saves/Storage/Bank7.cs +++ b/PKHeX.Core/Saves/Storage/Bank7.cs @@ -2,56 +2,55 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 7 object that reads from Pokémon Bank savedata (stored on AWS). +/// +public sealed class Bank7 : BulkStorage { - /// - /// Generation 7 object that reads from Pokémon Bank savedata (stored on AWS). - /// - public sealed class Bank7 : BulkStorage + public Bank7(byte[] data, Type t, int start, int slotsPerBox = 30) : base(data, t, start, slotsPerBox) => Version = GameVersion.USUM; + + public override PersonalTable Personal => PersonalTable.USUM; + public override IReadOnlyList HeldItems => Legal.HeldItems_SM; + protected override SaveFile CloneInternal() => new Bank7((byte[])Data.Clone(), PKMType, BoxStart, SlotsPerBox); + public override string PlayTimeString => $"{Year:00}{Month:00}{Day:00}_{Hours:00}ː{Minutes:00}"; + protected internal override string ShortSummary => PlayTimeString; + private const int GroupNameSize = 0x20; + private const int BankNameSize = 0x24; + private const int GroupNameSpacing = GroupNameSize + 2; + private const int BankNameSpacing = BankNameSize + 2; + + public ulong UID => ReadUInt64LittleEndian(Data.AsSpan(0)); + + public string GetGroupName(int group) { - public Bank7(byte[] data, Type t, int start, int slotsPerBox = 30) : base(data, t, start, slotsPerBox) => Version = GameVersion.USUM; - - public override PersonalTable Personal => PersonalTable.USUM; - public override IReadOnlyList HeldItems => Legal.HeldItems_SM; - protected override SaveFile CloneInternal() => new Bank7((byte[])Data.Clone(), PKMType, BoxStart, SlotsPerBox); - public override string PlayTimeString => $"{Year:00}{Month:00}{Day:00}_{Hours:00}ː{Minutes:00}"; - protected internal override string ShortSummary => PlayTimeString; - private const int GroupNameSize = 0x20; - private const int BankNameSize = 0x24; - private const int GroupNameSpacing = GroupNameSize + 2; - private const int BankNameSpacing = BankNameSize + 2; - - public ulong UID => ReadUInt64LittleEndian(Data.AsSpan(0)); - - public string GetGroupName(int group) - { - if ((uint)group > 10) - throw new ArgumentOutOfRangeException(nameof(group), $"{nameof(group)} must be 0-10."); - int offset = 0x8 + (GroupNameSpacing * group) + 2; // skip over " " - return GetString(offset, GroupNameSize / 2); - } - - public override int BoxCount => BankCount; - - private int BankCount - { - get => Data[0x15E]; - set => Data[0x15E] = (byte)value; - } - - private int Year => ReadUInt16LittleEndian(Data.AsSpan(0x160)); - private int Month => Data[0x162]; - private int Day => Data[0x163]; - private int Hours => Data[0x164]; - private int Minutes => Data[0x165]; - - private int BoxDataSize => (SlotsPerBox * SIZE_STORED) + BankNameSpacing; - public override int GetBoxOffset(int box) => Box + (BoxDataSize * box); - public override string GetBoxName(int box) => GetString(GetBoxNameOffset(box), BankNameSize / 2); - public int GetBoxNameOffset(int box) => GetBoxOffset(box) + (SlotsPerBox * SIZE_STORED); - public int GetBoxIndex(int box) => ReadUInt16LittleEndian(Data.AsSpan(GetBoxNameOffset(box) + BankNameSize)); - - private const int BoxStart = 0x17C; - public static Bank7 GetBank7(byte[] data) => new(data, typeof(PK7), BoxStart); + if ((uint)group > 10) + throw new ArgumentOutOfRangeException(nameof(group), $"{nameof(group)} must be 0-10."); + int offset = 0x8 + (GroupNameSpacing * group) + 2; // skip over " " + return GetString(offset, GroupNameSize / 2); } + + public override int BoxCount => BankCount; + + private int BankCount + { + get => Data[0x15E]; + set => Data[0x15E] = (byte)value; + } + + private int Year => ReadUInt16LittleEndian(Data.AsSpan(0x160)); + private int Month => Data[0x162]; + private int Day => Data[0x163]; + private int Hours => Data[0x164]; + private int Minutes => Data[0x165]; + + private int BoxDataSize => (SlotsPerBox * SIZE_STORED) + BankNameSpacing; + public override int GetBoxOffset(int box) => Box + (BoxDataSize * box); + public override string GetBoxName(int box) => GetString(GetBoxNameOffset(box), BankNameSize / 2); + public int GetBoxNameOffset(int box) => GetBoxOffset(box) + (SlotsPerBox * SIZE_STORED); + public int GetBoxIndex(int box) => ReadUInt16LittleEndian(Data.AsSpan(GetBoxNameOffset(box) + BankNameSize)); + + private const int BoxStart = 0x17C; + public static Bank7 GetBank7(byte[] data) => new(data, typeof(PK7), BoxStart); } diff --git a/PKHeX.Core/Saves/Storage/BulkStorage.cs b/PKHeX.Core/Saves/Storage/BulkStorage.cs index 208c725e7..dbe4e02c1 100644 --- a/PKHeX.Core/Saves/Storage/BulkStorage.cs +++ b/PKHeX.Core/Saves/Storage/BulkStorage.cs @@ -1,63 +1,62 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Simple Storage Binary wrapper for a concatenated list of data. +/// +public abstract class BulkStorage : SaveFile { - /// - /// Simple Storage Binary wrapper for a concatenated list of data. - /// - public abstract class BulkStorage : SaveFile + protected BulkStorage(byte[] data, Type t, int start, int slotsPerBox = 30) : base(data) { - protected BulkStorage(byte[] data, Type t, int start, int slotsPerBox = 30) : base(data) - { - Box = start; - SlotsPerBox = slotsPerBox; + Box = start; + SlotsPerBox = slotsPerBox; - blank = EntityBlank.GetBlank(t); - var slots = (Data.Length - Box) / blank.SIZE_STORED; - BoxCount = slots / SlotsPerBox; - } - - protected readonly int SlotsPerBox; - - protected internal override string ShortSummary => $"{Checksums.CRC16Invert(new ReadOnlySpan(Data, Box, Data.Length - Box)):X4}"; - public override string Extension => ".bin"; - public sealed override bool ChecksumsValid => true; - public sealed override string ChecksumInfo => "No Info."; - - private readonly PKM blank; - public sealed override Type PKMType => blank.GetType(); - public sealed override PKM BlankPKM => blank.Clone(); - - protected override PKM GetPKM(byte[] data) => EntityFormat.GetFromBytes(data, prefer: Context) ?? blank; - protected override byte[] DecryptPKM(byte[] data) => GetPKM(data).Data; - - protected override int SIZE_STORED => blank.SIZE_STORED; - protected override int SIZE_PARTY => blank.SIZE_PARTY; - public sealed override int MaxEV => blank.MaxEV; - public sealed override int Generation => blank.Format; - public sealed override EntityContext Context => blank.Context; - public sealed override int MaxMoveID => blank.MaxMoveID; - public sealed override int MaxSpeciesID => blank.MaxSpeciesID; - public sealed override int MaxAbilityID => blank.MaxAbilityID; - public sealed override int MaxItemID => blank.MaxItemID; - public sealed override int MaxBallID => blank.MaxBallID; - public sealed override int MaxGameID => blank.MaxGameID; - public sealed override int OTLength => blank.OTLength; - public sealed override int NickLength => blank.NickLength; - public bool IsBigEndian => blank is BK4 or XK3 or CK3; - - public override int BoxCount { get; } - protected override void SetChecksums() { } - - public override int GetBoxOffset(int box) => Box + (box * (SlotsPerBox * SIZE_STORED)); - public override string GetBoxName(int box) => $"Box {box + 1:d2}"; - public sealed override void SetBoxName(int box, string value) { } - public sealed override int GetPartyOffset(int slot) => int.MinValue; - - public override string GetString(ReadOnlySpan data) - => StringConverter.GetString(data, Generation, blank.Japanese, IsBigEndian); - - public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - => StringConverter.SetString(destBuffer, value, maxLength, option: option, generation: Generation, jp: blank.Japanese, isBigEndian: IsBigEndian, language: Language); + blank = EntityBlank.GetBlank(t); + var slots = (Data.Length - Box) / blank.SIZE_STORED; + BoxCount = slots / SlotsPerBox; } + + protected readonly int SlotsPerBox; + + protected internal override string ShortSummary => $"{Checksums.CRC16Invert(new ReadOnlySpan(Data, Box, Data.Length - Box)):X4}"; + public override string Extension => ".bin"; + public sealed override bool ChecksumsValid => true; + public sealed override string ChecksumInfo => "No Info."; + + private readonly PKM blank; + public sealed override Type PKMType => blank.GetType(); + public sealed override PKM BlankPKM => blank.Clone(); + + protected override PKM GetPKM(byte[] data) => EntityFormat.GetFromBytes(data, prefer: Context) ?? blank; + protected override byte[] DecryptPKM(byte[] data) => GetPKM(data).Data; + + protected override int SIZE_STORED => blank.SIZE_STORED; + protected override int SIZE_PARTY => blank.SIZE_PARTY; + public sealed override int MaxEV => blank.MaxEV; + public sealed override int Generation => blank.Format; + public sealed override EntityContext Context => blank.Context; + public sealed override int MaxMoveID => blank.MaxMoveID; + public sealed override int MaxSpeciesID => blank.MaxSpeciesID; + public sealed override int MaxAbilityID => blank.MaxAbilityID; + public sealed override int MaxItemID => blank.MaxItemID; + public sealed override int MaxBallID => blank.MaxBallID; + public sealed override int MaxGameID => blank.MaxGameID; + public sealed override int OTLength => blank.OTLength; + public sealed override int NickLength => blank.NickLength; + public bool IsBigEndian => blank is BK4 or XK3 or CK3; + + public override int BoxCount { get; } + protected override void SetChecksums() { } + + public override int GetBoxOffset(int box) => Box + (box * (SlotsPerBox * SIZE_STORED)); + public override string GetBoxName(int box) => $"Box {box + 1:d2}"; + public sealed override void SetBoxName(int box, string value) { } + public sealed override int GetPartyOffset(int slot) => int.MinValue; + + public override string GetString(ReadOnlySpan data) + => StringConverter.GetString(data, Generation, blank.Japanese, IsBigEndian); + + public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + => StringConverter.SetString(destBuffer, value, maxLength, option: option, generation: Generation, jp: blank.Japanese, isBigEndian: IsBigEndian, language: Language); } diff --git a/PKHeX.Core/Saves/Storage/PKMImportSetting.cs b/PKHeX.Core/Saves/Storage/PKMImportSetting.cs index 050f5ad87..f6abd8563 100644 --- a/PKHeX.Core/Saves/Storage/PKMImportSetting.cs +++ b/PKHeX.Core/Saves/Storage/PKMImportSetting.cs @@ -1,23 +1,22 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Setting to conditionally update PKM properties when importing to a save file. +/// +public enum PKMImportSetting { /// - /// Setting to conditionally update PKM properties when importing to a save file. + /// Use whatever the global setting is. /// - public enum PKMImportSetting - { - /// - /// Use whatever the global setting is. - /// - UseDefault, + UseDefault, - /// - /// Always update the PKM properties to match the save file. - /// - Update, + /// + /// Always update the PKM properties to match the save file. + /// + Update, - /// - /// Never update the PKM properties to match the save file. - /// - Skip, - } + /// + /// Never update the PKM properties to match the save file. + /// + Skip, } diff --git a/PKHeX.Core/Saves/Storage/SAV4Ranch.cs b/PKHeX.Core/Saves/Storage/SAV4Ranch.cs index b2b168d8a..4de6d490e 100644 --- a/PKHeX.Core/Saves/Storage/SAV4Ranch.cs +++ b/PKHeX.Core/Saves/Storage/SAV4Ranch.cs @@ -3,95 +3,94 @@ using System.Security.Cryptography; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 4 object for My Pokémon Ranch saves. +/// +public sealed class SAV4Ranch : BulkStorage, ISaveFileRevision { - /// - /// Generation 4 object for My Pokmon Ranch saves. - /// - public sealed class SAV4Ranch : BulkStorage, ISaveFileRevision + protected override int SIZE_STORED => 0x88 + 0x1C; + protected override int SIZE_PARTY => SIZE_STORED; + + public int SaveRevision => Version == GameVersion.DP ? 0 : 1; + public string SaveRevisionString => Version == GameVersion.DP ? "-DP" : "-Pt"; + + public override int BoxCount { get; } + public override int SlotCount { get; } + + public override PersonalTable Personal => PersonalTable.Pt; + public override IReadOnlyList HeldItems => Legal.HeldItems_Pt; + protected override SaveFile CloneInternal() => new SAV4Ranch((byte[])Data.Clone()); + public override string PlayTimeString => $"{Checksums.CRC16Invert(Data):X4}"; + protected internal override string ShortSummary => $"{OT} {PlayTimeString}"; + public override string Extension => ".bin"; + + protected override PKM GetPKM(byte[] data) => new PK4(data); + protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data); + public override StorageSlotSource GetSlotFlags(int index) => index >= SlotCount ? StorageSlotSource.Locked : StorageSlotSource.None; + protected override bool IsSlotSwapProtected(int box, int slot) => IsSlotOverwriteProtected(box, slot); + + public SAV4Ranch(byte[] data) : base(data, typeof(PK4), 0) { - protected override int SIZE_STORED => 0x88 + 0x1C; - protected override int SIZE_PARTY => SIZE_STORED; + Version = Data.Length == SaveUtil.SIZE_G4RANCH_PLAT ? GameVersion.Pt : GameVersion.DP; - public int SaveRevision => Version == GameVersion.DP ? 0 : 1; - public string SaveRevisionString => Version == GameVersion.DP ? "-DP" : "-Pt"; + OT = GetString(0x770, 0x12); - public override int BoxCount { get; } - public override int SlotCount { get; } + // 0x18 starts the header table + // Block 00, Offset = ??? + // Block 01, Offset = Mii Data + // Block 02, Offset = Mii Link Data + // Block 03, Offset = Pokemon Data + // Block 04, Offset = ?? - public override PersonalTable Personal => PersonalTable.Pt; - public override IReadOnlyList HeldItems => Legal.HeldItems_Pt; - protected override SaveFile CloneInternal() => new SAV4Ranch((byte[])Data.Clone()); - public override string PlayTimeString => Checksums.CRC16Invert(Data).ToString("X4"); - protected internal override string ShortSummary => $"{OT} {PlayTimeString}"; - public override string Extension => ".bin"; + // Unpack the binary a little: + // size, count, Mii data[count] + // size, count, Mii Link data[count] + // size, count, Pokemon (PK4 + metadata)[count] + // size, count, ??? - protected override PKM GetPKM(byte[] data) => new PK4(data); - protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data); - public override StorageSlotFlag GetSlotFlags(int index) => index >= SlotCount ? StorageSlotFlag.Locked : StorageSlotFlag.None; - protected override bool IsSlotSwapProtected(int box, int slot) => IsSlotOverwriteProtected(box, slot); + /* ====Metadata==== + * uint8_t poke_type;// 01 trainer, 04 hayley, 05 traded + * uint8_t tradeable;// 02 is tradeable, normal 00 + * uint16_t tid; + * uint16_t sid; + * uint32_t name1; + * uint32_t name2; + * uint32_t name3; + * uint32_t name4; + */ - public SAV4Ranch(byte[] data) : base(data, typeof(PK4), 0) - { - Version = Data.Length == SaveUtil.SIZE_G4RANCH_PLAT ? GameVersion.Pt : GameVersion.DP; + var pkCountOffset = ReadInt32BigEndian(Data.AsSpan(0x34)) + 4; + SlotCount = ReadInt32BigEndian(Data.AsSpan(pkCountOffset)); + BoxCount = (int)Math.Ceiling((decimal)SlotCount / SlotsPerBox); - OT = GetString(0x770, 0x12); + Box = pkCountOffset + 4; - // 0x18 starts the header table - // Block 00, Offset = ??? - // Block 01, Offset = Mii Data - // Block 02, Offset = Mii Link Data - // Block 03, Offset = Pokemon Data - // Block 04, Offset = ?? + FinalCountOffset = ReadInt32BigEndian(Data.AsSpan(0x3C)); + FinalCount = ReadInt32BigEndian(Data.AsSpan(FinalCountOffset)); + } - // Unpack the binary a little: - // size, count, Mii data[count] - // size, count, Mii Link data[count] - // size, count, Pokemon (PK4 + metadata)[count] - // size, count, ??? + private readonly int FinalCount; + private readonly int FinalCountOffset; - /* ====Metadata==== - * uint8_t poke_type;// 01 trainer, 04 hayley, 05 traded - * uint8_t tradeable;// 02 is tradeable, normal 00 - * uint16_t tid; - * uint16_t sid; - * uint32_t name1; - * uint32_t name2; - * uint32_t name3; - * uint32_t name4; - */ + protected override void SetChecksums() + { + // ensure the final data is written if the user screws stuff up + WriteInt32BigEndian(Data.AsSpan(FinalCountOffset), FinalCount); + var goodlen = (FinalCountOffset + 4); + Array.Clear(Data, goodlen, Data.Length - goodlen); - var pkCountOffset = ReadInt32BigEndian(Data.AsSpan(0x34)) + 4; - SlotCount = ReadInt32BigEndian(Data.AsSpan(pkCountOffset)); - BoxCount = (int)Math.Ceiling((decimal)SlotCount / SlotsPerBox); + // 20 byte SHA checksum at the top of the file, which covers all data that follows. + using var hash = SHA1.Create(); + var result = hash.ComputeHash(Data, 20, Data.Length - 20); + SetData(result, 0); + } - Box = pkCountOffset + 4; + public override string GetString(ReadOnlySpan data) => StringConverter4GC.GetStringUnicode(data); - FinalCountOffset = ReadInt32BigEndian(Data.AsSpan(0x3C)); - FinalCount = ReadInt32BigEndian(Data.AsSpan(FinalCountOffset)); - } - - private readonly int FinalCount; - private readonly int FinalCountOffset; - - protected override void SetChecksums() - { - // ensure the final data is written if the user screws stuff up - WriteInt32BigEndian(Data.AsSpan(FinalCountOffset), FinalCount); - var goodlen = (FinalCountOffset + 4); - Array.Clear(Data, goodlen, Data.Length - goodlen); - - // 20 byte SHA checksum at the top of the file, which covers all data that follows. - using var hash = SHA1.Create(); - var result = hash.ComputeHash(Data, 20, Data.Length - 20); - SetData(result, 0); - } - - public override string GetString(ReadOnlySpan data) => StringConverter4GC.GetStringUnicode(data); - - public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - { - return StringConverter4GC.SetStringUnicode(value, destBuffer, maxLength, option); - } + public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) + { + return StringConverter4GC.SetStringUnicode(value, destBuffer, maxLength, option); } } diff --git a/PKHeX.Core/Saves/Storage/SlotPointerUtil.cs b/PKHeX.Core/Saves/Storage/SlotPointerUtil.cs index 15007dd0e..54bdbccfb 100644 --- a/PKHeX.Core/Saves/Storage/SlotPointerUtil.cs +++ b/PKHeX.Core/Saves/Storage/SlotPointerUtil.cs @@ -1,100 +1,99 @@ using System.Collections.Generic; using System.Diagnostics; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for rearranging pointers for Box Storage utility +/// +public static class SlotPointerUtil { - /// - /// Logic for rearranging pointers for Box Storage utility - /// - public static class SlotPointerUtil + private static bool WithinRange(int slot, int min, int max) => min <= slot && slot < max; + + public static void UpdateSwap(int b1, int b2, int slotsPerBox, params IList[] ptrset) { - private static bool WithinRange(int slot, int min, int max) => min <= slot && slot < max; + int diff = (b1 - b2) * slotsPerBox; - public static void UpdateSwap(int b1, int b2, int slotsPerBox, params IList[] ptrset) + int b1s = b1 * slotsPerBox; + int b1e = b1s + slotsPerBox; + int b12 = b1 > b2 ? -diff : diff; + + int b2s = b2 * slotsPerBox; + int b2e = b2s + slotsPerBox; + int b21 = b2 > b1 ? -diff : diff; + foreach (var list in ptrset) { - int diff = (b1 - b2) * slotsPerBox; - - int b1s = b1 * slotsPerBox; - int b1e = b1s + slotsPerBox; - int b12 = b1 > b2 ? -diff : diff; - - int b2s = b2 * slotsPerBox; - int b2e = b2s + slotsPerBox; - int b21 = b2 > b1 ? -diff : diff; - foreach (var list in ptrset) + for (int s = 0; s < list.Count; s++) { - for (int s = 0; s < list.Count; s++) - { - if (WithinRange(b1s, b1e, list[s])) - list[s] += b12; - else if (WithinRange(b2s, b2e, list[s])) - list[s] += b21; - } + if (WithinRange(b1s, b1e, list[s])) + list[s] += b12; + else if (WithinRange(b2s, b2e, list[s])) + list[s] += b21; } } + } - public static void UpdateRepointFrom(IList result, IList bd, int start, params IList[] slotPointers) + public static void UpdateRepointFrom(IList result, IList bd, int start, params IList[] slotPointers) + { + foreach (var p in slotPointers) { - foreach (var p in slotPointers) + for (int i = 0; i < p.Count; i++) { - for (int i = 0; i < p.Count; i++) - { - var index = p[i]; - if ((uint)index >= bd.Count) - continue; - var pk = bd[index]; - var newIndex = result.IndexOf(pk); - if (newIndex < 0) - continue; + var index = p[i]; + if ((uint)index >= bd.Count) + continue; + var pk = bd[index]; + var newIndex = result.IndexOf(pk); + if (newIndex < 0) + continue; - Debug.WriteLine($"Re-pointing {pk.Nickname} from {index} to {newIndex}"); - Debug.WriteLine($"{result[newIndex]}"); - p[i] = start + newIndex; - } + Debug.WriteLine($"Re-pointing {pk.Nickname} from {index} to {newIndex}"); + Debug.WriteLine($"{result[newIndex]}"); + p[i] = start + newIndex; } } + } - public static void UpdateRepointFrom(int newIndex, int oldIndex, params IList[] slotPointers) + public static void UpdateRepointFrom(int newIndex, int oldIndex, params IList[] slotPointers) + { + foreach (var p in slotPointers) { - foreach (var p in slotPointers) + for (int s = 0; s < p.Count; s++) { - for (int s = 0; s < p.Count; s++) - { - if (p[s] != oldIndex) - continue; - p[s] = newIndex; - break; - } + if (p[s] != oldIndex) + continue; + p[s] = newIndex; + break; } } + } - public static void UpdateMove(int bMove, int cMove, int slotsPerBox, params IList[] ptrset) + public static void UpdateMove(int bMove, int cMove, int slotsPerBox, params IList[] ptrset) + { + int bStart = bMove * slotsPerBox; + int cStart = cMove * slotsPerBox; + int bEnd = bStart + slotsPerBox; + int cEnd = cStart + slotsPerBox; + int cShift; + int bShift; + if (bMove < cMove) // shift chunk down, shift box up { - int bStart = bMove * slotsPerBox; - int cStart = cMove * slotsPerBox; - int bEnd = bStart + slotsPerBox; - int cEnd = cStart + slotsPerBox; - int cShift; - int bShift; - if (bMove < cMove) // shift chunk down, shift box up + cShift = -slotsPerBox; + bShift = (cStart - bStart); + } + else // shift box down, shift chunk up + { + cShift = slotsPerBox; + bShift = -(bStart - cStart); + } + foreach (var list in ptrset) + { + for (int s = 0; s < list.Count; s++) { - cShift = -slotsPerBox; - bShift = (cStart - bStart); - } - else // shift box down, shift chunk up - { - cShift = slotsPerBox; - bShift = -(bStart - cStart); - } - foreach (var list in ptrset) - { - for (int s = 0; s < list.Count; s++) - { - if (WithinRange(cStart, cEnd, list[s])) - list[s] += cShift; - if (WithinRange(bStart, bEnd, list[s])) - list[s] += bShift; - } + if (WithinRange(cStart, cEnd, list[s])) + list[s] += cShift; + if (WithinRange(bStart, bEnd, list[s])) + list[s] += bShift; } } } diff --git a/PKHeX.Core/Saves/Storage/StorageSlotFlag.cs b/PKHeX.Core/Saves/Storage/StorageSlotFlag.cs deleted file mode 100644 index ccc8eae37..000000000 --- a/PKHeX.Core/Saves/Storage/StorageSlotFlag.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; - -namespace PKHeX.Core -{ - /// - /// Flags describing special attributes for a based on its origin from the parent . - /// - /// If , then it's not a special slot. - [Flags] - public enum StorageSlotFlag - { - None, - - Party = 1 << 0, - Party1 = Party << 0, - Party2 = Party << 1, - Party3 = Party << 2, - Party4 = Party << 3, - Party5 = Party << 4, - Party6 = Party << 5, - - BattleTeam = 1 << 6, - BattleTeam1 = BattleTeam << 0, - BattleTeam2 = BattleTeam << 1, - BattleTeam3 = BattleTeam << 2, - BattleTeam4 = BattleTeam << 3, - BattleTeam5 = BattleTeam << 4, - BattleTeam6 = BattleTeam << 5, - - Starter = 1 << 29, - Locked = 1 << 30, - } - - public static class StorageSlotFlagExtensions - { - public static bool HasFlagFast(this StorageSlotFlag value, StorageSlotFlag flag) => (value & flag) != 0; - - /// - /// Checks to see if the prevents the corresponding slot from being overwritten. - /// - /// Flag value - /// True if write protected - public static bool IsOverwriteProtected(this StorageSlotFlag value) - { - if (value.HasFlagFast(StorageSlotFlag.Locked)) - return true; - - if (value.HasFlagFast(StorageSlotFlag.Starter)) - return true; - - return value.IsBattleTeam() >= 0; - } - - /// - /// Gets the Battle Team ID the belongs to - /// - /// Flag value - /// Battle Team ID if valid, -1 otherwise. - public static int IsBattleTeam(this StorageSlotFlag value) - { - if (value.HasFlagFast(StorageSlotFlag.BattleTeam1)) - return 0; - if (value.HasFlagFast(StorageSlotFlag.BattleTeam2)) - return 1; - if (value.HasFlagFast(StorageSlotFlag.BattleTeam3)) - return 2; - if (value.HasFlagFast(StorageSlotFlag.BattleTeam4)) - return 3; - if (value.HasFlagFast(StorageSlotFlag.BattleTeam5)) - return 4; - if (value.HasFlagFast(StorageSlotFlag.BattleTeam6)) - return 5; - - return -1; - } - - /// - /// Gets the Party Slot Index the belongs to - /// - /// Flag value - /// [0,5] if valid, -1 otherwise. - public static int IsParty(this StorageSlotFlag value) - { - if (value.HasFlagFast(StorageSlotFlag.Party1)) - return 0; - if (value.HasFlagFast(StorageSlotFlag.Party2)) - return 1; - if (value.HasFlagFast(StorageSlotFlag.Party3)) - return 2; - if (value.HasFlagFast(StorageSlotFlag.Party4)) - return 3; - if (value.HasFlagFast(StorageSlotFlag.Party5)) - return 4; - if (value.HasFlagFast(StorageSlotFlag.Party6)) - return 5; - - return -1; - } - } -} diff --git a/PKHeX.Core/Saves/Storage/StorageSlotSource.cs b/PKHeX.Core/Saves/Storage/StorageSlotSource.cs new file mode 100644 index 000000000..2583af8e1 --- /dev/null +++ b/PKHeX.Core/Saves/Storage/StorageSlotSource.cs @@ -0,0 +1,99 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Flags describing special attributes for a based on its origin from the parent . +/// +/// If , then it's not a special slot. +[Flags] +public enum StorageSlotSource +{ + None, + + Party = 1 << 0, + Party1 = Party << 0, + Party2 = Party << 1, + Party3 = Party << 2, + Party4 = Party << 3, + Party5 = Party << 4, + Party6 = Party << 5, + + BattleTeam = 1 << 6, + BattleTeam1 = BattleTeam << 0, + BattleTeam2 = BattleTeam << 1, + BattleTeam3 = BattleTeam << 2, + BattleTeam4 = BattleTeam << 3, + BattleTeam5 = BattleTeam << 4, + BattleTeam6 = BattleTeam << 5, + + Starter = 1 << 29, + Locked = 1 << 30, +} + +public static class StorageSlotSourceExtensions +{ + public static bool HasFlagFast(this StorageSlotSource value, StorageSlotSource source) => (value & source) != 0; + + /// + /// Checks to see if the prevents the corresponding slot from being overwritten. + /// + /// Flag value + /// True if write protected + public static bool IsOverwriteProtected(this StorageSlotSource value) + { + if (value.HasFlagFast(StorageSlotSource.Locked)) + return true; + + if (value.HasFlagFast(StorageSlotSource.Starter)) + return true; + + return value.IsBattleTeam() >= 0; + } + + /// + /// Gets the Battle Team ID the belongs to + /// + /// Flag value + /// Battle Team ID if valid, -1 otherwise. + public static int IsBattleTeam(this StorageSlotSource value) + { + if (value.HasFlagFast(StorageSlotSource.BattleTeam1)) + return 0; + if (value.HasFlagFast(StorageSlotSource.BattleTeam2)) + return 1; + if (value.HasFlagFast(StorageSlotSource.BattleTeam3)) + return 2; + if (value.HasFlagFast(StorageSlotSource.BattleTeam4)) + return 3; + if (value.HasFlagFast(StorageSlotSource.BattleTeam5)) + return 4; + if (value.HasFlagFast(StorageSlotSource.BattleTeam6)) + return 5; + + return -1; + } + + /// + /// Gets the Party Slot Index the belongs to + /// + /// Flag value + /// [0,5] if valid, -1 otherwise. + public static int IsParty(this StorageSlotSource value) + { + if (value.HasFlagFast(StorageSlotSource.Party1)) + return 0; + if (value.HasFlagFast(StorageSlotSource.Party2)) + return 1; + if (value.HasFlagFast(StorageSlotSource.Party3)) + return 2; + if (value.HasFlagFast(StorageSlotSource.Party4)) + return 3; + if (value.HasFlagFast(StorageSlotSource.Party5)) + return 4; + if (value.HasFlagFast(StorageSlotSource.Party6)) + return 5; + + return -1; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BV3.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BV3.cs index 31f899d19..4369d8af4 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BV3.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BV3.cs @@ -1,106 +1,105 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class BV3 : BattleVideo { - public sealed class BV3 : BattleVideo + internal const int SIZE = 0xF80; + public override int Generation => 3; + + public override IReadOnlyList BattlePKMs => PlayerTeams.SelectMany(z => z).ToArray(); + + public readonly byte[] Data; + + internal new static bool IsValid(ReadOnlySpan data) { - internal const int SIZE = 0xF80; - public override int Generation => 3; + if (data.Length != SIZE) + return false; + var chk = ReadUInt32LittleEndian(data[(SIZE-4)..]); + if (chk > 0xF7080) + return false; // max if all are FF + var expect = GetChecksum8(data); + return chk == expect; + } - public override IReadOnlyList BattlePKMs => PlayerTeams.SelectMany(z => z).ToArray(); + public BV3(byte[] data) => Data = (byte[])data.Clone(); + public BV3() : this(new byte[SIZE]) { } - public readonly byte[] Data; - - internal new static bool IsValid(ReadOnlySpan data) + public IReadOnlyList PlayerTeams + { + get => new[] { - if (data.Length != SIZE) - return false; - var chk = ReadUInt32LittleEndian(data[(SIZE-4)..]); - if (chk > 0xF7080) - return false; // max if all are FF - var expect = GetChecksum8(data); - return chk == expect; - } - - public BV3(byte[] data) => Data = (byte[])data.Clone(); - public BV3() : this(new byte[SIZE]) { } - - public IReadOnlyList PlayerTeams + GetTeam(0), + GetTeam(1), + }; + set { - get => new[] - { - GetTeam(0), - GetTeam(1), - }; - set - { - SetTeam(value[0], 0); - SetTeam(value[1], 1); - } - } - - public PK3[] GetTeam(int teamIndex) - { - if ((uint)teamIndex > 2) - throw new ArgumentOutOfRangeException(nameof(teamIndex)); - - var ofs = (6 * PokeCrypto.SIZE_3PARTY) * teamIndex; - var team = new PK3[6]; - for (int p = 0; p < 6; p++) - { - int offset = ofs + (PokeCrypto.SIZE_3PARTY * p); - team[p] = new PK3(Data.Slice(offset, PokeCrypto.SIZE_3PARTY)); - } - - return team; - } - - public void SetTeam(IReadOnlyList team, int teamIndex) - { - var ofs = (6 * PokeCrypto.SIZE_3PARTY) * teamIndex; - for (int p = 0; p < 6; p++) - { - int offset = ofs + (PokeCrypto.SIZE_3PARTY * p); - team[p].EncryptedPartyData.CopyTo(Data, offset); - } - } - - // 0x4B0 - string3[4][8] Trainer Names - // 0x4D0 - u8[4] Trainer Genders - // 0x4D4 - u32[4] Trainer IDs - // 0x4E4 - u8[4] Trainer Languages - - public uint Seed - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x4E8)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x4E8), value); - } - - public uint Mode - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x4EC)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x4EC), value); - } - - // ... - - public uint Checksum - { - get => ReadUInt32LittleEndian(Data.AsSpan(SIZE - 4)); - set => WriteUInt32LittleEndian(Data.AsSpan(SIZE - 4), value); - } - - public bool IsChecksumValid() => Checksum == GetChecksum8(Data); - - public static uint GetChecksum8(ReadOnlySpan data) - { - uint result = 0; - for (int i = 0; i < data.Length - 4; i++) - result += data[i]; - return result; + SetTeam(value[0], 0); + SetTeam(value[1], 1); } } + + public PK3[] GetTeam(int teamIndex) + { + if ((uint)teamIndex > 2) + throw new ArgumentOutOfRangeException(nameof(teamIndex)); + + var ofs = 6 * PokeCrypto.SIZE_3PARTY * teamIndex; + var team = new PK3[6]; + for (int p = 0; p < 6; p++) + { + int offset = ofs + (PokeCrypto.SIZE_3PARTY * p); + team[p] = new PK3(Data.Slice(offset, PokeCrypto.SIZE_3PARTY)); + } + + return team; + } + + public void SetTeam(IReadOnlyList team, int teamIndex) + { + var ofs = 6 * PokeCrypto.SIZE_3PARTY * teamIndex; + for (int p = 0; p < 6; p++) + { + int offset = ofs + (PokeCrypto.SIZE_3PARTY * p); + team[p].EncryptedPartyData.CopyTo(Data, offset); + } + } + + // 0x4B0 - string3[4][8] Trainer Names + // 0x4D0 - u8[4] Trainer Genders + // 0x4D4 - u32[4] Trainer IDs + // 0x4E4 - u8[4] Trainer Languages + + public uint Seed + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x4E8)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x4E8), value); + } + + public uint Mode + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x4EC)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x4EC), value); + } + + // ... + + public uint Checksum + { + get => ReadUInt32LittleEndian(Data.AsSpan(SIZE - 4)); + set => WriteUInt32LittleEndian(Data.AsSpan(SIZE - 4), value); + } + + public bool IsChecksumValid() => Checksum == GetChecksum8(Data); + + public static uint GetChecksum8(ReadOnlySpan data) + { + uint result = 0; + for (int i = 0; i < data.Length - 4; i++) + result += data[i]; + return result; + } } diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BV6.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BV6.cs index 4264dbb62..850eb9d4d 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BV6.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BV6.cs @@ -4,248 +4,247 @@ using static System.Buffers.Binary.BinaryPrimitives; // ReSharper disable UnusedType.Local -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class BV6 : BattleVideo { - public sealed class BV6 : BattleVideo + internal const int SIZE = 0x2E60; + private const string NPC = "NPC"; + private readonly byte[] Data; + private const int PlayerCount = 4; + + public override IReadOnlyList BattlePKMs => PlayerTeams.SelectMany(t => t).ToArray(); + public override int Generation => 6; + + internal new static bool IsValid(ReadOnlySpan data) { - internal const int SIZE = 0x2E60; - private const string NPC = "NPC"; - private readonly byte[] Data; - private const int PlayerCount = 4; + if (data.Length != SIZE) + return false; + return ReadUInt64LittleEndian(data[0xE18..]) != 0 && ReadUInt16LittleEndian(data[0xE12..]) == 0; + } - public override IReadOnlyList BattlePKMs => PlayerTeams.SelectMany(t => t).ToArray(); - public override int Generation => 6; + public BV6(byte[] data) => Data = (byte[])data.Clone(); + public int Mode { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public int Style { get => Data[0x01]; set => Data[0x01] = (byte)value; } - internal new static bool IsValid(ReadOnlySpan data) + public string Debug1 + { + get => StringConverter6.GetString(Data.AsSpan(0x6, 0x1A)); + set => StringConverter6.SetString(Data.AsSpan(0x6, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + } + + public string Debug2 + { + get => StringConverter6.GetString(Data.AsSpan(0x50, 0x1A)); + set => StringConverter6.SetString(Data.AsSpan(0x50, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + } + + public ulong RNGConst1 { get => ReadUInt64LittleEndian(Data.AsSpan(0x1A0)); set => WriteUInt64LittleEndian(Data.AsSpan(0x1A0), value); } + public ulong RNGConst2 { get => ReadUInt64LittleEndian(Data.AsSpan(0x1A4)); set => WriteUInt64LittleEndian(Data.AsSpan(0x1A4), value); } + public ulong RNGSeed1 { get => ReadUInt64LittleEndian(Data.AsSpan(0x1A8)); set => WriteUInt64LittleEndian(Data.AsSpan(0x1A8), value); } + public ulong RNGSeed2 { get => ReadUInt64LittleEndian(Data.AsSpan(0x1B0)); set => WriteUInt64LittleEndian(Data.AsSpan(0x1B0), value); } + + public int Background { get => ReadInt32LittleEndian(Data.AsSpan(0x1BC)); set => WriteInt32LittleEndian(Data.AsSpan(0x1BC), value); } + public int Unk1CE { get => ReadUInt16LittleEndian(Data.AsSpan(0x1CE)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1CE), (ushort)value); } + public int IntroID { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E4)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E4), (ushort)value); } + public int MusicID { get => ReadUInt16LittleEndian(Data.AsSpan(0x1F0)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1F0), (ushort)value); } + + public string[] GetPlayerNames() + { + string[] trainers = new string[PlayerCount]; + for (int i = 0; i < PlayerCount; i++) { - if (data.Length != SIZE) - return false; - return ReadUInt64LittleEndian(data[0xE18..]) != 0 && ReadUInt16LittleEndian(data[0xE12..]) == 0; + var span = Data.AsSpan(0xEC + (0x1A * i), 0x1A); + var str = StringConverter6.GetString(span); + trainers[i] = string.IsNullOrWhiteSpace(str) ? NPC : str; } + return trainers; + } - public BV6(byte[] data) => Data = (byte[])data.Clone(); - public int Mode { get => Data[0x00]; set => Data[0x00] = (byte)value; } - public int Style { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public void SetPlayerNames(IReadOnlyList value) + { + if (value.Count != PlayerCount) + return; - public string Debug1 + for (int i = 0; i < PlayerCount; i++) { - get => StringConverter6.GetString(Data.AsSpan(0x6, 0x1A)); - set => StringConverter6.SetString(Data.AsSpan(0x6, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - } - - public string Debug2 - { - get => StringConverter6.GetString(Data.AsSpan(0x50, 0x1A)); - set => StringConverter6.SetString(Data.AsSpan(0x50, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - } - - public ulong RNGConst1 { get => ReadUInt64LittleEndian(Data.AsSpan(0x1A0)); set => WriteUInt64LittleEndian(Data.AsSpan(0x1A0), value); } - public ulong RNGConst2 { get => ReadUInt64LittleEndian(Data.AsSpan(0x1A4)); set => WriteUInt64LittleEndian(Data.AsSpan(0x1A4), value); } - public ulong RNGSeed1 { get => ReadUInt64LittleEndian(Data.AsSpan(0x1A8)); set => WriteUInt64LittleEndian(Data.AsSpan(0x1A8), value); } - public ulong RNGSeed2 { get => ReadUInt64LittleEndian(Data.AsSpan(0x1B0)); set => WriteUInt64LittleEndian(Data.AsSpan(0x1B0), value); } - - public int Background { get => ReadInt32LittleEndian(Data.AsSpan(0x1BC)); set => WriteInt32LittleEndian(Data.AsSpan(0x1BC), value); } - public int Unk1CE { get => ReadUInt16LittleEndian(Data.AsSpan(0x1CE)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1CE), (ushort)value); } - public int IntroID { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E4)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E4), (ushort)value); } - public int MusicID { get => ReadUInt16LittleEndian(Data.AsSpan(0x1F0)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1F0), (ushort)value); } - - public string[] GetPlayerNames() - { - string[] trainers = new string[PlayerCount]; - for (int i = 0; i < PlayerCount; i++) - { - var span = Data.AsSpan(0xEC + (0x1A * i), 0x1A); - var str = StringConverter6.GetString(span); - trainers[i] = string.IsNullOrWhiteSpace(str) ? NPC : str; - } - return trainers; - } - - public void SetPlayerNames(IReadOnlyList value) - { - if (value.Count != PlayerCount) - return; - - for (int i = 0; i < PlayerCount; i++) - { - var span = Data.AsSpan(0xEC + (0x1A * i), 0x1A); - string tr = value[i] == NPC ? string.Empty : value[i]; - StringConverter6.SetString(span, tr.AsSpan(), 12, StringConverterOption.ClearZero); - } - } - - public IReadOnlyList PlayerTeams - { - get - { - var Teams = new PKM[PlayerCount][]; - for (int t = 0; t < PlayerCount; t++) - Teams[t] = GetTeam(t); - return Teams; - } - set - { - var Teams = value; - for (int t = 0; t < PlayerCount; t++) - SetTeam(Teams[t], t); - } - } - - public PKM[] GetTeam(int t) - { - var team = new PKM[6]; - const int start = 0xE18; - for (int p = 0; p < 6; p++) - { - int offset = start + (PokeCrypto.SIZE_6PARTY * ((t * 6) + p)); - offset += 8 * (((t * 6) + p) / 6); // 8 bytes padding between teams - team[p] = new PK6(Data.Slice(offset, PokeCrypto.SIZE_6PARTY)); - } - - return team; - } - - public void SetTeam(IReadOnlyList team, int t) - { - const int start = 0xE18; - for (int p = 0; p < 6; p++) - { - int offset = start + (PokeCrypto.SIZE_6PARTY * ((t * 6) + p)); - offset += 8 * (((t * 6) + p) / 6); // 8 bytes padding between teams - team[p].EncryptedPartyData.CopyTo(Data, offset); - } - } - - public int MatchYear { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E50)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E50), (ushort)value); } - public int MatchDay { get => Data[0x2E52]; set => Data[0x2E52] = (byte)value; } - public int MatchMonth { get => Data[0x2E53]; set => Data[0x2E53] = (byte)value; } - public int MatchHour { get => Data[0x2E54]; set => Data[0x2E54] = (byte)value; } - public int MatchMinute { get => Data[0x2E55]; set => Data[0x2E55] = (byte)value; } - public int MatchSecond { get => Data[0x2E56]; set => Data[0x2E56] = (byte)value; } - public int MatchFlags { get => Data[0x2E57]; set => Data[0x2E57] = (byte)value; } - - public int UploadYear { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E58)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E58), (ushort)value); } - public int UploadDay { get => Data[0x2E5A]; set => Data[0x2E5A] = (byte)value; } - public int UploadMonth { get => Data[0x2E5B]; set => Data[0x2E5B] = (byte)value; } - public int UploadHour { get => Data[0x2E5C]; set => Data[0x2E5C] = (byte)value; } - public int UploadMinute { get => Data[0x2E5D]; set => Data[0x2E5D] = (byte)value; } - public int UploadSecond { get => Data[0x2E5E]; set => Data[0x2E5E] = (byte)value; } - public int UploadFlags { get => Data[0x2E5F]; set => Data[0x2E5F] = (byte)value; } - - public DateTime? MatchStamp - { - get - { - if (!DateUtil.IsDateValid(MatchYear, MatchMonth, MatchDay)) - return null; - return new DateTime(MatchYear, MatchMonth, MatchDay, MatchHour, MatchMinute, MatchSecond); - } - set - { - if (value.HasValue) - { - MatchYear = value.Value.Year; - MatchDay = value.Value.Day; - MatchMonth = value.Value.Month; - MatchHour = value.Value.Hour; - MatchMinute = value.Value.Minute; - MatchSecond = value.Value.Second; - } - else - { - MatchYear = MatchDay = MatchMonth = MatchHour = MatchMinute = MatchSecond = MatchFlags = 0; - } - } - } - - public DateTime? UploadStamp - { - get - { - if (!DateUtil.IsDateValid(UploadYear, UploadMonth, UploadDay)) - return null; - return new DateTime(UploadYear, UploadMonth, UploadDay, UploadHour, UploadMinute, UploadSecond); - } - set - { - if (value.HasValue) - { - UploadYear = value.Value.Year; - UploadDay = value.Value.Day; - UploadMonth = value.Value.Month; - UploadHour = value.Value.Hour; - UploadMinute = value.Value.Minute; - UploadSecond = value.Value.Second; - } - else - { - UploadYear = UploadDay = UploadMonth = UploadHour = UploadMinute = UploadSecond = UploadFlags = 0; - } - } - } - - private enum TurnAction - { - None = 0, - Fight = 1, - Unk2 = 2, - Switch = 3, - Run = 4, - Unk5 = 5, - Rotate = 6, - Unk7 = 7, - MegaEvolve = 8, - } - - private enum TurnTarget - { - U0 = 0, - U1 = 1, - U2 = 2, - U3 = 3, - U4 = 4, - U5 = 5, - U6 = 6, - U7 = 7, - U8 = 8, - U9 = 9, - OppositeEnemy, - U11 = 11, - U12 = 12, - U13 = 13, - AllExceptUser = 14, - Everyone = 15, - } - - private enum TurnRotate - { - None, - Right, - Left, - Unk3, - } - - public enum BVType - { - Link = 0, - Maison = 1, - SuperMaison = 2, - BattleSpotFree = 3, - BattleSpotRating = 4, - BattleSpotSpecial = 5, - UNUSED = 6, - JP1 = 7, - JP2 = 8, - BROKEN = 9, - } - - public enum BVStyle - { - Single = 0, - Double = 1, - Triple = 2, - Rotation = 3, - Multi = 4, + var span = Data.AsSpan(0xEC + (0x1A * i), 0x1A); + string tr = value[i] == NPC ? string.Empty : value[i]; + StringConverter6.SetString(span, tr.AsSpan(), 12, StringConverterOption.ClearZero); } } + + public IReadOnlyList PlayerTeams + { + get + { + var Teams = new PKM[PlayerCount][]; + for (int t = 0; t < PlayerCount; t++) + Teams[t] = GetTeam(t); + return Teams; + } + set + { + var Teams = value; + for (int t = 0; t < PlayerCount; t++) + SetTeam(Teams[t], t); + } + } + + public PKM[] GetTeam(int t) + { + var team = new PKM[6]; + const int start = 0xE18; + for (int p = 0; p < 6; p++) + { + int offset = start + (PokeCrypto.SIZE_6PARTY * ((t * 6) + p)); + offset += 8 * (((t * 6) + p) / 6); // 8 bytes padding between teams + team[p] = new PK6(Data.Slice(offset, PokeCrypto.SIZE_6PARTY)); + } + + return team; + } + + public void SetTeam(IReadOnlyList team, int t) + { + const int start = 0xE18; + for (int p = 0; p < 6; p++) + { + int offset = start + (PokeCrypto.SIZE_6PARTY * ((t * 6) + p)); + offset += 8 * (((t * 6) + p) / 6); // 8 bytes padding between teams + team[p].EncryptedPartyData.CopyTo(Data, offset); + } + } + + public int MatchYear { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E50)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E50), (ushort)value); } + public int MatchDay { get => Data[0x2E52]; set => Data[0x2E52] = (byte)value; } + public int MatchMonth { get => Data[0x2E53]; set => Data[0x2E53] = (byte)value; } + public int MatchHour { get => Data[0x2E54]; set => Data[0x2E54] = (byte)value; } + public int MatchMinute { get => Data[0x2E55]; set => Data[0x2E55] = (byte)value; } + public int MatchSecond { get => Data[0x2E56]; set => Data[0x2E56] = (byte)value; } + public int MatchFlags { get => Data[0x2E57]; set => Data[0x2E57] = (byte)value; } + + public int UploadYear { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E58)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E58), (ushort)value); } + public int UploadDay { get => Data[0x2E5A]; set => Data[0x2E5A] = (byte)value; } + public int UploadMonth { get => Data[0x2E5B]; set => Data[0x2E5B] = (byte)value; } + public int UploadHour { get => Data[0x2E5C]; set => Data[0x2E5C] = (byte)value; } + public int UploadMinute { get => Data[0x2E5D]; set => Data[0x2E5D] = (byte)value; } + public int UploadSecond { get => Data[0x2E5E]; set => Data[0x2E5E] = (byte)value; } + public int UploadFlags { get => Data[0x2E5F]; set => Data[0x2E5F] = (byte)value; } + + public DateTime? MatchStamp + { + get + { + if (!DateUtil.IsDateValid(MatchYear, MatchMonth, MatchDay)) + return null; + return new DateTime(MatchYear, MatchMonth, MatchDay, MatchHour, MatchMinute, MatchSecond); + } + set + { + if (value.HasValue) + { + MatchYear = value.Value.Year; + MatchDay = value.Value.Day; + MatchMonth = value.Value.Month; + MatchHour = value.Value.Hour; + MatchMinute = value.Value.Minute; + MatchSecond = value.Value.Second; + } + else + { + MatchYear = MatchDay = MatchMonth = MatchHour = MatchMinute = MatchSecond = MatchFlags = 0; + } + } + } + + public DateTime? UploadStamp + { + get + { + if (!DateUtil.IsDateValid(UploadYear, UploadMonth, UploadDay)) + return null; + return new DateTime(UploadYear, UploadMonth, UploadDay, UploadHour, UploadMinute, UploadSecond); + } + set + { + if (value.HasValue) + { + UploadYear = value.Value.Year; + UploadDay = value.Value.Day; + UploadMonth = value.Value.Month; + UploadHour = value.Value.Hour; + UploadMinute = value.Value.Minute; + UploadSecond = value.Value.Second; + } + else + { + UploadYear = UploadDay = UploadMonth = UploadHour = UploadMinute = UploadSecond = UploadFlags = 0; + } + } + } + + private enum TurnAction + { + None = 0, + Fight = 1, + Unk2 = 2, + Switch = 3, + Run = 4, + Unk5 = 5, + Rotate = 6, + Unk7 = 7, + MegaEvolve = 8, + } + + private enum TurnTarget + { + U0 = 0, + U1 = 1, + U2 = 2, + U3 = 3, + U4 = 4, + U5 = 5, + U6 = 6, + U7 = 7, + U8 = 8, + U9 = 9, + OppositeEnemy, + U11 = 11, + U12 = 12, + U13 = 13, + AllExceptUser = 14, + Everyone = 15, + } + + private enum TurnRotate + { + None, + Right, + Left, + Unk3, + } + + public enum BVType + { + Link = 0, + Maison = 1, + SuperMaison = 2, + BattleSpotFree = 3, + BattleSpotRating = 4, + BattleSpotSpecial = 5, + UNUSED = 6, + JP1 = 7, + JP2 = 8, + BROKEN = 9, + } + + public enum BVStyle + { + Single = 0, + Double = 1, + Triple = 2, + Rotation = 3, + Multi = 4, + } } diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BV7.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BV7.cs index 028ad851b..ba198f2ea 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BV7.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BV7.cs @@ -3,122 +3,121 @@ using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class BV7 : BattleVideo { - public sealed class BV7 : BattleVideo + internal const int SIZE = 0x2BC0; + private const string NPC = "NPC"; + private const int PlayerCount = 4; + + public override int Generation => 7; + private readonly byte[] Data; + + public override IReadOnlyList BattlePKMs => PlayerTeams.SelectMany(t => t).ToArray(); + internal new static bool IsValid(ReadOnlySpan data) => data.Length == SIZE; + + public BV7(byte[] data) => Data = (byte[])data.Clone(); + + private static readonly int[] offsets = { 0xE41, 0x145E, 0x1A7B, 0x2098 }; + + public IReadOnlyList PlayerTeams { - internal const int SIZE = 0x2BC0; - private const string NPC = "NPC"; - private const int PlayerCount = 4; - - public override int Generation => 7; - private readonly byte[] Data; - - public override IReadOnlyList BattlePKMs => PlayerTeams.SelectMany(t => t).ToArray(); - internal new static bool IsValid(ReadOnlySpan data) => data.Length == SIZE; - - public BV7(byte[] data) => Data = (byte[])data.Clone(); - - private static readonly int[] offsets = { 0xE41, 0x145E, 0x1A7B, 0x2098 }; - - public IReadOnlyList PlayerTeams + get { - get - { - var Teams = new PKM[PlayerCount][]; - for (int t = 0; t < PlayerCount; t++) - Teams[t] = GetTeam(t); - return Teams; - } - set - { - for (int t = 0; t < PlayerCount; t++) - SetTeam(value[t], t); - } + var Teams = new PKM[PlayerCount][]; + for (int t = 0; t < PlayerCount; t++) + Teams[t] = GetTeam(t); + return Teams; } - - public PKM[] GetTeam(int teamIndex) + set { - var team = new PKM[6]; - var ofs = offsets[teamIndex]; - for (int p = 0; p < 6; p++) - { - int offset = ofs + (PokeCrypto.SIZE_6PARTY * p); - team[p] = new PK7(Data.Slice(offset, PokeCrypto.SIZE_6STORED)); - } - - return team; + for (int t = 0; t < PlayerCount; t++) + SetTeam(value[t], t); } - - public void SetTeam(IReadOnlyList team, int teamIndex) - { - var ofs = offsets[teamIndex]; - for (int p = 0; p < 6; p++) - { - int offset = ofs + (PokeCrypto.SIZE_6PARTY * p); - team[p].EncryptedPartyData.CopyTo(Data, offset); - } - } - - public string[] GetPlayerNames() - { - string[] trainers = new string[PlayerCount]; - for (int i = 0; i < PlayerCount; i++) - { - var span = Data.AsSpan(0x12C + +(0x1A * i), 0x1A); - var str = StringConverter7.GetString(span); - trainers[i] = string.IsNullOrWhiteSpace(trainers[i]) ? NPC : str; - } - return trainers; - } - - public void SetPlayerNames(IReadOnlyList value) - { - if (value.Count != PlayerCount) - return; - - for (int i = 0; i < PlayerCount; i++) - { - string tr = value[i] == NPC ? string.Empty : value[i]; - var span = Data.AsSpan(0x12C + +(0x1A * i), 0x1A); - StringConverter7.SetString(span, tr.AsSpan(), 12, 0, StringConverterOption.ClearZero); - } - } - - private int MatchYear { get => ReadUInt16LittleEndian(Data.AsSpan(0x2BB0)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2BB0), (ushort)value); } - private int MatchDay { get => Data[0x2BB3]; set => Data[0x2BB3] = (byte)value; } - private int MatchMonth { get => Data[0x2BB2]; set => Data[0x2BB2] = (byte)value; } - private int MatchHour { get => Data[0x2BB4]; set => Data[0x2BB4] = (byte)value; } - private int MatchMinute { get => Data[0x2BB5]; set => Data[0x2BB5] = (byte)value; } - private int MatchSecond { get => Data[0x2BB6]; set => Data[0x2BB6] = (byte)value; } - - public DateTime? MatchStamp - { - get - { - if (!DateUtil.IsDateValid(MatchYear, MatchMonth, MatchDay)) - return null; - return new DateTime(MatchYear, MatchMonth, MatchDay, MatchHour, MatchMinute, MatchSecond); - } - set - { - if (value.HasValue) - { - MatchYear = value.Value.Year; - MatchDay = value.Value.Day; - MatchMonth = value.Value.Month; - MatchHour = value.Value.Hour; - MatchMinute = value.Value.Minute; - MatchSecond = value.Value.Second; - } - else - { - MatchYear = MatchDay = MatchMonth = MatchHour = MatchMinute = MatchSecond = 0; - } - } - } - - public int MusicID { get => Data[0x21C]; set => Data[0x21C] = (byte)value; } - public bool SilentBGM { get => MusicID == 0xFF; set => MusicID = (byte)(value ? 0xFF : MusicID); } } + + public PKM[] GetTeam(int teamIndex) + { + var team = new PKM[6]; + var ofs = offsets[teamIndex]; + for (int p = 0; p < 6; p++) + { + int offset = ofs + (PokeCrypto.SIZE_6PARTY * p); + team[p] = new PK7(Data.Slice(offset, PokeCrypto.SIZE_6STORED)); + } + + return team; + } + + public void SetTeam(IReadOnlyList team, int teamIndex) + { + var ofs = offsets[teamIndex]; + for (int p = 0; p < 6; p++) + { + int offset = ofs + (PokeCrypto.SIZE_6PARTY * p); + team[p].EncryptedPartyData.CopyTo(Data, offset); + } + } + + public string[] GetPlayerNames() + { + string[] trainers = new string[PlayerCount]; + for (int i = 0; i < PlayerCount; i++) + { + var span = Data.AsSpan(0x12C + +(0x1A * i), 0x1A); + var str = StringConverter7.GetString(span); + trainers[i] = string.IsNullOrWhiteSpace(trainers[i]) ? NPC : str; + } + return trainers; + } + + public void SetPlayerNames(IReadOnlyList value) + { + if (value.Count != PlayerCount) + return; + + for (int i = 0; i < PlayerCount; i++) + { + string tr = value[i] == NPC ? string.Empty : value[i]; + var span = Data.AsSpan(0x12C + +(0x1A * i), 0x1A); + StringConverter7.SetString(span, tr.AsSpan(), 12, 0, StringConverterOption.ClearZero); + } + } + + private int MatchYear { get => ReadUInt16LittleEndian(Data.AsSpan(0x2BB0)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2BB0), (ushort)value); } + private int MatchDay { get => Data[0x2BB3]; set => Data[0x2BB3] = (byte)value; } + private int MatchMonth { get => Data[0x2BB2]; set => Data[0x2BB2] = (byte)value; } + private int MatchHour { get => Data[0x2BB4]; set => Data[0x2BB4] = (byte)value; } + private int MatchMinute { get => Data[0x2BB5]; set => Data[0x2BB5] = (byte)value; } + private int MatchSecond { get => Data[0x2BB6]; set => Data[0x2BB6] = (byte)value; } + + public DateTime? MatchStamp + { + get + { + if (!DateUtil.IsDateValid(MatchYear, MatchMonth, MatchDay)) + return null; + return new DateTime(MatchYear, MatchMonth, MatchDay, MatchHour, MatchMinute, MatchSecond); + } + set + { + if (value.HasValue) + { + MatchYear = value.Value.Year; + MatchDay = value.Value.Day; + MatchMonth = value.Value.Month; + MatchHour = value.Value.Hour; + MatchMinute = value.Value.Minute; + MatchSecond = value.Value.Second; + } + else + { + MatchYear = MatchDay = MatchMonth = MatchHour = MatchMinute = MatchSecond = 0; + } + } + } + + public int MusicID { get => Data[0x21C]; set => Data[0x21C] = (byte)value; } + public bool SilentBGM { get => MusicID == 0xFF; set => MusicID = (byte)(value ? 0xFF : MusicID); } } diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BVRequestUtil.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BVRequestUtil.cs index bb4da9b22..e9ceec317 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BVRequestUtil.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BVRequestUtil.cs @@ -2,110 +2,109 @@ using System.Diagnostics; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class BVRequestUtil { - public static class BVRequestUtil + public static string GetSMBattleVideoURL(string code) { - public static string GetSMBattleVideoURL(string code) - { - code = code.Replace("-", string.Empty); - Debug.Assert(code.Length == 16); - var video_id = StrToU64(code, out bool valid); - if (!valid) - return string.Empty; - return $"https://ctr-bnda-live.s3.amazonaws.com/10.CTR_BNDA_datastore/ds/1/data/{video_id:D11}-00001"; // Sun datastore - } - - public static ulong StrToU64(string input, out bool valid) - { - var chk = Pull(0, 4) >> 4; // first four chars are checksum bits - var result = Pull(4, input.Length); // next 12 chars are the 70 value bits - - Span temp = stackalloc byte[8]; - WriteUInt64LittleEndian(temp, result); - var actual = Checksums.CRC16_CCITT(temp); - valid = chk == actual; - return result; - - ulong Pull(int start, int count) - { - ulong val = 0; - for (int i = start; i < count; i++) - { - var c = input[i]; - if (c == '-') - continue; - - val <<= 5; - val |= Get5BitFromChar(c) & 0b11111; - } - return val; - } - } - - public static string U64ToStr(ulong input, bool insertDash) - { - Span temp = stackalloc byte[8]; - WriteUInt64LittleEndian(temp, input); - uint chk = Checksums.CRC16_CCITT(temp); - var buff = new char[16]; - int ctr = 15; - Push(input, 12); // store value bits - Push(chk << 4, 4); // store checksum bits - return !insertDash ? string.Concat(buff) : GetStringWithDashesEvery(buff, 4); - - void Push(ulong v, int count) - { - for (int i = 0; i < count; i++) - { - buff[ctr--] = Set5BitToChar((char)(v & 0b11111)); - v >>= 5; - } - } - } - - private static string GetStringWithDashesEvery(char[] buff, int spacer) - { - var buff2 = new char[buff.Length + ((buff.Length / spacer) - 1)]; - for (int i = 0, ctr = 0; i < buff.Length; i++) - { - buff2[ctr++] = buff[i]; - if (i % spacer == 3 && ctr < buff2.Length) - buff2[ctr++] = '-'; // add dash between every chunk of size {spacer} - } - return string.Concat(buff2); - } - - private static char Set5BitToChar(char c) - { - var shift = c > 9 ? '7' : '0'; - c += shift; - return MapToChar(c); - } - - private static uint Get5BitFromChar(char c) - { - c = MapFromChar(c); - var shift = c >= 'A' ? '7' : '0'; - return (uint)(c - shift); - } - - private static char MapToChar(char c) => c switch - { - '0' => 'W', - '1' => 'X', - 'I' => 'Y', - 'O' => 'Z', - _ => c, - }; - - private static char MapFromChar(char c) => c switch - { - 'W' => '0', - 'X' => '1', - 'Y' => 'I', - 'Z' => 'O', - _ => c, - }; + code = code.Replace("-", string.Empty); + Debug.Assert(code.Length == 16); + var video_id = StrToU64(code, out bool valid); + if (!valid) + return string.Empty; + return $"https://ctr-bnda-live.s3.amazonaws.com/10.CTR_BNDA_datastore/ds/1/data/{video_id:D11}-00001"; // Sun datastore } + + public static ulong StrToU64(string input, out bool valid) + { + var chk = Pull(0, 4) >> 4; // first four chars are checksum bits + var result = Pull(4, input.Length); // next 12 chars are the 70 value bits + + Span temp = stackalloc byte[8]; + WriteUInt64LittleEndian(temp, result); + var actual = Checksums.CRC16_CCITT(temp); + valid = chk == actual; + return result; + + ulong Pull(int start, int count) + { + ulong val = 0; + for (int i = start; i < count; i++) + { + var c = input[i]; + if (c == '-') + continue; + + val <<= 5; + val |= Get5BitFromChar(c) & 0b11111; + } + return val; + } + } + + public static string U64ToStr(ulong input, bool insertDash) + { + Span temp = stackalloc byte[8]; + WriteUInt64LittleEndian(temp, input); + uint chk = Checksums.CRC16_CCITT(temp); + var buff = new char[16]; + int ctr = 15; + Push(input, 12); // store value bits + Push(chk << 4, 4); // store checksum bits + return !insertDash ? string.Concat(buff) : GetStringWithDashesEvery(buff, 4); + + void Push(ulong v, int count) + { + for (int i = 0; i < count; i++) + { + buff[ctr--] = Set5BitToChar((char)(v & 0b11111)); + v >>= 5; + } + } + } + + private static string GetStringWithDashesEvery(char[] buff, int spacer) + { + var buff2 = new char[buff.Length + ((buff.Length / spacer) - 1)]; + for (int i = 0, ctr = 0; i < buff.Length; i++) + { + buff2[ctr++] = buff[i]; + if (i % spacer == 3 && ctr < buff2.Length) + buff2[ctr++] = '-'; // add dash between every chunk of size {spacer} + } + return string.Concat(buff2); + } + + private static char Set5BitToChar(char c) + { + var shift = c > 9 ? '7' : '0'; + c += shift; + return MapToChar(c); + } + + private static uint Get5BitFromChar(char c) + { + c = MapFromChar(c); + var shift = c >= 'A' ? '7' : '0'; + return (uint)(c - shift); + } + + private static char MapToChar(char c) => c switch + { + '0' => 'W', + '1' => 'X', + 'I' => 'Y', + 'O' => 'Z', + _ => c, + }; + + private static char MapFromChar(char c) => c switch + { + 'W' => '0', + 'X' => '1', + 'Y' => 'I', + 'Z' => 'O', + _ => c, + }; } diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleMode.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleMode.cs index 0997f5221..7ece6c269 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleMode.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleMode.cs @@ -1,16 +1,15 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum BattleMode { - public enum BattleMode - { - Link, - Maison, - MaisonSuper, - BattleSpotFree, - BattleSpotRating, - BattleSpotSpecial, - UNUSED, - JP1, - JP2, - BAD, - } -} \ No newline at end of file + Link, + Maison, + MaisonSuper, + BattleSpotFree, + BattleSpotRating, + BattleSpotSpecial, + UNUSED, + JP1, + JP2, + BAD, +} diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleStyle6.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleStyle6.cs index 2a25d962d..791c078c7 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleStyle6.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleStyle6.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum BattleStyle6 { - public enum BattleStyle6 - { - Single, - Double, - Triple, - Rotation, - Multi, - } + Single, + Double, + Triple, + Rotation, + Multi, } diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo.cs index f88bb7811..456a5c613 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo.cs @@ -1,35 +1,34 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class BattleVideo : IPokeGroup { - public abstract class BattleVideo : IPokeGroup + public abstract IReadOnlyList BattlePKMs { get; } + public abstract int Generation { get; } + + public IEnumerable Contents => BattlePKMs; + + public static BattleVideo? GetVariantBattleVideo(byte[] data) { - public abstract IReadOnlyList BattlePKMs { get; } - public abstract int Generation { get; } + if (BV6.IsValid(data)) + return new BV6(data); + if (BV7.IsValid(data)) + return new BV7(data); + if (BV3.IsValid(data)) + return new BV3(data); + return null; + } - public IEnumerable Contents => BattlePKMs; - - public static BattleVideo? GetVariantBattleVideo(byte[] data) - { - if (BV6.IsValid(data)) - return new BV6(data); - if (BV7.IsValid(data)) - return new BV7(data); - if (BV3.IsValid(data)) - return new BV3(data); - return null; - } - - public static bool IsValid(ReadOnlySpan data) - { - if (BV6.IsValid(data)) - return true; - if (BV7.IsValid(data)) - return true; - if (BV3.IsValid(data)) - return true; - return false; - } + public static bool IsValid(ReadOnlySpan data) + { + if (BV6.IsValid(data)) + return true; + if (BV7.IsValid(data)) + return true; + if (BV3.IsValid(data)) + return true; + return false; } } diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/MoveTarget.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/MoveTarget.cs index 920762334..8eeac9a9b 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/MoveTarget.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/MoveTarget.cs @@ -1,23 +1,22 @@ -namespace PKHeX.Core -{ - public enum MoveTarget : byte - { - // Specific target - _0 = 0, - _1 = 1, - _2 = 2, - _3 = 3, - _4 = 4, - _5 = 5, - _6 = 6, - Self = 7, - _8 = 8, - _9 = 9, +namespace PKHeX.Core; - // No pkm target - _10 = 10, - _11 = 11, - _12 = 12, - Counter = 13, - } -} \ No newline at end of file +public enum MoveTarget : byte +{ + // Specific target + _0 = 0, + _1 = 1, + _2 = 2, + _3 = 3, + _4 = 4, + _5 = 5, + _6 = 6, + Self = 7, + _8 = 8, + _9 = 9, + + // No pk target + _10 = 10, + _11 = 11, + _12 = 12, + Counter = 13, +} diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/RotateDirection.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/RotateDirection.cs index c55b56361..1750a8042 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/RotateDirection.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/RotateDirection.cs @@ -1,9 +1,8 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum RotateDirection { - public enum RotateDirection - { - None, - Right, - Left, - } + None, + Right, + Left, } diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/TurnStartCode.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/TurnStartCode.cs index a8d0baacf..e0f58f014 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/TurnStartCode.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/TurnStartCode.cs @@ -1,8 +1,8 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum TurnStartCode { - public enum TurnStartCode - { - Fight = 1, - Switch = 9, - } + None = 0, + Fight = 1, + Switch = 9, } diff --git a/PKHeX.Core/Saves/Substructures/Gen12/G1OverworldSpawner.cs b/PKHeX.Core/Saves/Substructures/Gen12/G1OverworldSpawner.cs index bbbd8f1df..ccfdbac40 100644 --- a/PKHeX.Core/Saves/Substructures/Gen12/G1OverworldSpawner.cs +++ b/PKHeX.Core/Saves/Substructures/Gen12/G1OverworldSpawner.cs @@ -1,147 +1,146 @@ using System.Collections.Generic; // ReSharper disable UnusedAutoPropertyAccessor.Local -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class G1OverworldSpawner { - public sealed class G1OverworldSpawner + private readonly SAV1 SAV; + private readonly bool[] EventFlags; + private readonly bool[] SpawnFlags; + + public G1OverworldSpawner(SAV1 sav) { - private readonly SAV1 SAV; - private readonly bool[] EventFlags; - private readonly bool[] SpawnFlags; + SAV = sav; + EventFlags = sav.GetEventFlags(); + SpawnFlags = sav.EventSpawnFlags; + bool yellow = SAV.Yellow; - public G1OverworldSpawner(SAV1 sav) + // FlagPairs set for Red/Blue when appropriate. + FlagEevee = new FlagPairG1(0x45); + FlagAerodactyl = new FlagPairG1(0x069, 0x34); + FlagHitmonlee = new FlagPairG1(0x356, 0x4A); + FlagHitmonchan = new FlagPairG1(0x357, 0x4B); + FlagVoltorb_1 = new FlagPairG1(0x461, 0x4D); + FlagVoltorb_2 = new FlagPairG1(0x462, 0x4E); + FlagVoltorb_3 = new FlagPairG1(0x463, 0x4F); + FlagElectrode_1 = new FlagPairG1(0x464, 0x50); + FlagVoltorb_4 = new FlagPairG1(0x465, 0x51); + FlagVoltorb_5 = new FlagPairG1(0x466, 0x52); + FlagElectrode_2 = new FlagPairG1(0x467, 0x53); + FlagVoltorb_6 = new FlagPairG1(0x468, 0x54); + FlagZapdos = new FlagPairG1(0x469, 0x55); + FlagMoltres = new FlagPairG1(0x53E, 0x5B); + FlagKabuto = new FlagPairG1(0x57E, 0x6D); + FlagOmanyte = new FlagPairG1(0x57F, 0x6E); + FlagMewtwo = new FlagPairG1(0x8C1, 0xD1); + FlagArticuno = new FlagPairG1(0x9DA, 0xE3); + + if (yellow) // slightly different { - SAV = sav; - EventFlags = sav.GetEventFlags(); - SpawnFlags = sav.EventSpawnFlags; - bool yellow = SAV.Yellow; + FlagKabuto = new FlagPairG1(0x578, 0x6D); + FlagAerodactyl = new FlagPairG1(0x069, 0x33); + FlagMewtwo = new FlagPairG1(0x8C1, 0xD7); + FlagArticuno = new FlagPairG1(0x9DA, 0xEB); + FlagKabuto = new FlagPairG1(0x57E, 0x6F); + FlagOmanyte = new FlagPairG1(0x57F, 0x70); - // FlagPairs set for Red/Blue when appropriate. - FlagEevee = new FlagPairG1(0x45); - FlagAerodactyl = new FlagPairG1(0x069, 0x34); - FlagHitmonlee = new FlagPairG1(0x356, 0x4A); - FlagHitmonchan = new FlagPairG1(0x357, 0x4B); - FlagVoltorb_1 = new FlagPairG1(0x461, 0x4D); - FlagVoltorb_2 = new FlagPairG1(0x462, 0x4E); - FlagVoltorb_3 = new FlagPairG1(0x463, 0x4F); - FlagElectrode_1 = new FlagPairG1(0x464, 0x50); - FlagVoltorb_4 = new FlagPairG1(0x465, 0x51); - FlagVoltorb_5 = new FlagPairG1(0x466, 0x52); - FlagElectrode_2 = new FlagPairG1(0x467, 0x53); - FlagVoltorb_6 = new FlagPairG1(0x468, 0x54); - FlagZapdos = new FlagPairG1(0x469, 0x55); - FlagMoltres = new FlagPairG1(0x53E, 0x5B); - FlagKabuto = new FlagPairG1(0x57E, 0x6D); - FlagOmanyte = new FlagPairG1(0x57F, 0x6E); - FlagMewtwo = new FlagPairG1(0x8C1, 0xD1); - FlagArticuno = new FlagPairG1(0x9DA, 0xE3); - - if (yellow) // slightly different - { - FlagKabuto = new FlagPairG1(0x578, 0x6D); - FlagAerodactyl = new FlagPairG1(0x069, 0x33); - FlagMewtwo = new FlagPairG1(0x8C1, 0xD7); - FlagArticuno = new FlagPairG1(0x9DA, 0xEB); - FlagKabuto = new FlagPairG1(0x57E, 0x6F); - FlagOmanyte = new FlagPairG1(0x57F, 0x70); - - FlagBulbasaur = new FlagPairG1(0x0A8, 0x34); - FlagSquirtle = new FlagPairG1(0x147, 0); // Given by Officer Jenny after badged - FlagCharmander = new FlagPairG1(0x54F, 0); // Given by Damian, doesn't despawn - } + FlagBulbasaur = new FlagPairG1(0x0A8, 0x34); + FlagSquirtle = new FlagPairG1(0x147, 0); // Given by Officer Jenny after badged + FlagCharmander = new FlagPairG1(0x54F, 0); // Given by Damian, doesn't despawn } + } #pragma warning disable IDE0052 // Remove unread private members - public const string FlagPropertyPrefix = "Flag"; // reflection - private FlagPairG1 FlagMewtwo { get; } - private FlagPairG1 FlagArticuno { get; } - private FlagPairG1 FlagZapdos { get; } - private FlagPairG1 FlagMoltres { get; } - private FlagPairG1 FlagVoltorb_1 { get; } - private FlagPairG1 FlagVoltorb_2 { get; } - private FlagPairG1 FlagVoltorb_3 { get; } - private FlagPairG1 FlagVoltorb_4 { get; } - private FlagPairG1 FlagVoltorb_5 { get; } - private FlagPairG1 FlagVoltorb_6 { get; } - private FlagPairG1 FlagElectrode_1 { get; } - private FlagPairG1 FlagElectrode_2 { get; } - private FlagPairG1 FlagHitmonchan { get; } - private FlagPairG1 FlagHitmonlee { get; } - private FlagPairG1 FlagEevee { get; } - private FlagPairG1 FlagKabuto { get; } - private FlagPairG1 FlagOmanyte { get; } - private FlagPairG1 FlagAerodactyl { get; } - private FlagPairG1? FlagBulbasaur { get; } - private FlagPairG1? FlagSquirtle { get; } - private FlagPairG1? FlagCharmander { get; } + public const string FlagPropertyPrefix = "Flag"; // reflection + private FlagPairG1 FlagMewtwo { get; } + private FlagPairG1 FlagArticuno { get; } + private FlagPairG1 FlagZapdos { get; } + private FlagPairG1 FlagMoltres { get; } + private FlagPairG1 FlagVoltorb_1 { get; } + private FlagPairG1 FlagVoltorb_2 { get; } + private FlagPairG1 FlagVoltorb_3 { get; } + private FlagPairG1 FlagVoltorb_4 { get; } + private FlagPairG1 FlagVoltorb_5 { get; } + private FlagPairG1 FlagVoltorb_6 { get; } + private FlagPairG1 FlagElectrode_1 { get; } + private FlagPairG1 FlagElectrode_2 { get; } + private FlagPairG1 FlagHitmonchan { get; } + private FlagPairG1 FlagHitmonlee { get; } + private FlagPairG1 FlagEevee { get; } + private FlagPairG1 FlagKabuto { get; } + private FlagPairG1 FlagOmanyte { get; } + private FlagPairG1 FlagAerodactyl { get; } + private FlagPairG1? FlagBulbasaur { get; } + private FlagPairG1? FlagSquirtle { get; } + private FlagPairG1? FlagCharmander { get; } #pragma warning restore IDE0052 // Remove unread private members - public void Save() - { - SAV.SetEventFlags(EventFlags); - SAV.EventSpawnFlags = SpawnFlags; - } - - public IEnumerable GetFlagPairs() - { - var pz = ReflectUtil.GetPropertiesStartWithPrefix(GetType(), FlagPropertyPrefix); - - foreach (var pair in pz) - { - if (ReflectUtil.GetValue(this, pair) is not FlagPairG1 p) - continue; - yield return new FlagPairG1Detail(p, pair, EventFlags, SpawnFlags); - } - } + public void Save() + { + SAV.SetEventFlags(EventFlags); + SAV.EventSpawnFlags = SpawnFlags; } - public sealed class FlagPairG1 + public IEnumerable GetFlagPairs() { - internal readonly int SpawnFlag; - internal readonly int EventFlag; + var pz = ReflectUtil.GetPropertiesStartWithPrefix(GetType(), FlagPropertyPrefix); - internal FlagPairG1(int script, int hide) : this(hide) => EventFlag = script; - internal FlagPairG1(int hide) => SpawnFlag = hide; - } - - public sealed class FlagPairG1Detail - { - private readonly FlagPairG1 Backing; - public readonly string Name; - - private readonly bool[] Event; - private readonly bool[] Spawn; - - public FlagPairG1Detail(FlagPairG1 back, string name, bool[] ev, bool[] spawn) + foreach (var pair in pz) { - Backing = back; - Name = name; - Event = ev; - Spawn = spawn; - } - - public void Invert() => SetState(!IsHidden); - public void Reset() => SetState(false); - - public void SetState(bool hide) - { - if (Backing.EventFlag != 0) - Event[Backing.EventFlag] = hide; - if (Backing.SpawnFlag != 0) - Spawn[Backing.SpawnFlag] = hide; - } - - public bool IsHidden - { - get - { - bool result = false; - if (Backing.EventFlag != 0) - result |= Event[Backing.EventFlag]; - if (Backing.SpawnFlag != 0) - result |= Spawn[Backing.SpawnFlag]; - return result; - } + if (ReflectUtil.GetValue(this, pair) is not FlagPairG1 p) + continue; + yield return new FlagPairG1Detail(p, pair, EventFlags, SpawnFlags); + } + } +} + +public sealed class FlagPairG1 +{ + internal readonly int SpawnFlag; + internal readonly int EventFlag; + + internal FlagPairG1(int script, int hide) : this(hide) => EventFlag = script; + internal FlagPairG1(int hide) => SpawnFlag = hide; +} + +public sealed class FlagPairG1Detail +{ + private readonly FlagPairG1 Backing; + public readonly string Name; + + private readonly bool[] Event; + private readonly bool[] Spawn; + + public FlagPairG1Detail(FlagPairG1 back, string name, bool[] ev, bool[] spawn) + { + Backing = back; + Name = name; + Event = ev; + Spawn = spawn; + } + + public void Invert() => SetState(!IsHidden); + public void Reset() => SetState(false); + + public void SetState(bool hide) + { + if (Backing.EventFlag != 0) + Event[Backing.EventFlag] = hide; + if (Backing.SpawnFlag != 0) + Spawn[Backing.SpawnFlag] = hide; + } + + public bool IsHidden + { + get + { + bool result = false; + if (Backing.EventFlag != 0) + result |= Event[Backing.EventFlag]; + if (Backing.SpawnFlag != 0) + result |= Spawn[Backing.SpawnFlag]; + return result; } } } diff --git a/PKHeX.Core/Saves/Substructures/Gen12/SAV1Offsets.cs b/PKHeX.Core/Saves/Substructures/Gen12/SAV1Offsets.cs index c446248a8..886a5ed89 100644 --- a/PKHeX.Core/Saves/Substructures/Gen12/SAV1Offsets.cs +++ b/PKHeX.Core/Saves/Substructures/Gen12/SAV1Offsets.cs @@ -1,84 +1,83 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +internal sealed class SAV1Offsets { - internal sealed class SAV1Offsets + public static readonly SAV1Offsets INT = GetINT(); + public static readonly SAV1Offsets JPN = GetJPN(); + + private static SAV1Offsets GetINT() => new() { - public static readonly SAV1Offsets INT = GetINT(); - public static readonly SAV1Offsets JPN = GetJPN(); + DexCaught = 0x25A3, + DexSeen = 0x25B6, + Items = 0x25C9, + Money = 0x25F3, + Rival = 0x25F6, + Options = 0x2601, + Badges = 0x2602, + TID = 0x2605, + PikaFriendship = 0x271C, + PikaBeachScore = 0x2741, + PrinterBrightness = 0x2744, + PCItems = 0x27E6, + CurrentBoxIndex = 0x284C, + Coin = 0x2850, + ObjectSpawnFlags = 0x2852, // 2 bytes after Coin + Starter = 0x29C3, + EventFlag = 0x29F3, + PlayTime = 0x2CED, + Daycare = 0x2CF4, + Party = 0x2F2C, + CurrentBox = 0x30C0, + ChecksumOfs = 0x3523, + }; - private static SAV1Offsets GetINT() => new() - { - DexCaught = 0x25A3, - DexSeen = 0x25B6, - Items = 0x25C9, - Money = 0x25F3, - Rival = 0x25F6, - Options = 0x2601, - Badges = 0x2602, - TID = 0x2605, - PikaFriendship = 0x271C, - PikaBeachScore = 0x2741, - PrinterBrightness = 0x2744, - PCItems = 0x27E6, - CurrentBoxIndex = 0x284C, - Coin = 0x2850, - ObjectSpawnFlags = 0x2852, // 2 bytes after Coin - Starter = 0x29C3, - EventFlag = 0x29F3, - PlayTime = 0x2CED, - Daycare = 0x2CF4, - Party = 0x2F2C, - CurrentBox = 0x30C0, - ChecksumOfs = 0x3523, - }; + private static SAV1Offsets GetJPN() => new() + { + DexCaught = 0x259E, + DexSeen = 0x25B1, + Items = 0x25C4, + Money = 0x25EE, + Rival = 0x25F1, + Options = 0x25F7, + Badges = 0x25F8, + TID = 0x25FB, + PikaFriendship = 0x2712, + PikaBeachScore = 0x2737, + PrinterBrightness = 0x273A, + PCItems = 0x27DC, + CurrentBoxIndex = 0x2842, + Coin = 0x2846, + ObjectSpawnFlags = 0x2848, // 2 bytes after Coin + Starter = 0x29B9, + EventFlag = 0x29E9, + PlayTime = 0x2CA0, + Daycare = 0x2CA7, + Party = 0x2ED5, + CurrentBox = 0x302D, + ChecksumOfs = 0x3594, + }; - private static SAV1Offsets GetJPN() => new() - { - DexCaught = 0x259E, - DexSeen = 0x25B1, - Items = 0x25C4, - Money = 0x25EE, - Rival = 0x25F1, - Options = 0x25F7, - Badges = 0x25F8, - TID = 0x25FB, - PikaFriendship = 0x2712, - PikaBeachScore = 0x2737, - PrinterBrightness = 0x273A, - PCItems = 0x27DC, - CurrentBoxIndex = 0x2842, - Coin = 0x2846, - ObjectSpawnFlags = 0x2848, // 2 bytes after Coin - Starter = 0x29B9, - EventFlag = 0x29E9, - PlayTime = 0x2CA0, - Daycare = 0x2CA7, - Party = 0x2ED5, - CurrentBox = 0x302D, - ChecksumOfs = 0x3594, - }; - - public int OT { get; } = 0x2598; - public int DexCaught { get; private init; } - public int DexSeen { get; private init; } - public int Items { get; private init; } - public int Money { get; private init; } - public int Rival { get; private init; } - public int Options { get; private init; } - public int Badges { get; private init; } - public int TID { get; private init; } - public int PikaFriendship { get; private init; } - public int PikaBeachScore { get; private init; } - public int PrinterBrightness { get; private init; } - public int PCItems { get; private init; } - public int CurrentBoxIndex { get; private init; } - public int Coin { get; private init; } - public int ObjectSpawnFlags { get; private init; } - public int Starter { get; private init; } - public int EventFlag { get; private init; } - public int PlayTime { get; private init; } - public int Daycare { get; private init; } - public int Party { get; private init; } - public int CurrentBox { get; private init; } - public int ChecksumOfs { get; private init; } - } + public int OT { get; } = 0x2598; + public int DexCaught { get; private init; } + public int DexSeen { get; private init; } + public int Items { get; private init; } + public int Money { get; private init; } + public int Rival { get; private init; } + public int Options { get; private init; } + public int Badges { get; private init; } + public int TID { get; private init; } + public int PikaFriendship { get; private init; } + public int PikaBeachScore { get; private init; } + public int PrinterBrightness { get; private init; } + public int PCItems { get; private init; } + public int CurrentBoxIndex { get; private init; } + public int Coin { get; private init; } + public int ObjectSpawnFlags { get; private init; } + public int Starter { get; private init; } + public int EventFlag { get; private init; } + public int PlayTime { get; private init; } + public int Daycare { get; private init; } + public int Party { get; private init; } + public int CurrentBox { get; private init; } + public int ChecksumOfs { get; private init; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen12/SAV2Offsets.cs b/PKHeX.Core/Saves/Substructures/Gen12/SAV2Offsets.cs index ab8ed1788..60bbe5f52 100644 --- a/PKHeX.Core/Saves/Substructures/Gen12/SAV2Offsets.cs +++ b/PKHeX.Core/Saves/Substructures/Gen12/SAV2Offsets.cs @@ -1,218 +1,217 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal sealed class SAV2Offsets { - internal sealed class SAV2Offsets + public SAV2Offsets(SAV2 sav) { - public SAV2Offsets(SAV2 sav) + Options = 0x2000; + Trainer1 = 0x2009; + + if (sav.Japanese) + LoadOffsetsJapanese(sav.Version); + else if (sav.Korean) + LoadOffsetsKorean(); + else + LoadOffsetsInternational(sav.Version); + Daycare = PokedexSeen + 0x1F + 28 + 1; // right after first unown seen + EventWork = EventFlag - 0x100; + } + + public int RTCFlags { get; private set; } = -1; + + public int Options { get; } + public int Trainer1 { get; } + public int Rival { get; private set; } + + public int DaylightSavings { get; private set; } = -1; + public int TimePlayed { get; private set; } = -1; + public int Palette { get; private set; } = -1; + public int Money { get; private set; } = -1; + public int JohtoBadges { get; private set; } = -1; + public int CurrentBoxIndex { get; private set; } = -1; + public int BoxNames { get; private set; } = -1; + public int Party { get; private set; } = -1; + public int PokedexCaught { get; private set; } = -1; + public int PokedexSeen { get; private set; } = -1; + public int CurrentBox { get; private set; } = -1; + public int OtherCurrentBox { get; private set; } + public int Gender { get; private set; } = -1; + public int AccumulatedChecksumEnd { get; private set; } = -1; + public int OverallChecksumPosition { get; private set; } = -1; + public int EventFlag { get; private set; } = -1; + public int EventWork { get; } + public int Daycare { get; } + + public int BlueCardPoints { get; private set; } = -1; + + public int PouchTMHM { get; private set; } = -1; + public int PouchItem { get; private set; } = -1; + public int PouchKey { get; private set; } = -1; + public int PouchBall { get; private set; } = -1; + public int PouchPC { get; private set; } = -1; + + private void LoadOffsetsInternational(GameVersion version) + { + RTCFlags = 0x0C60; + + Rival = 0x2021; + DaylightSavings = 0x2042; + OtherCurrentBox = 0x284C; + switch (version) { - Options = 0x2000; - Trainer1 = 0x2009; + case GameVersion.GS: + TimePlayed = 0x2053; + Palette = 0x206B; + Money = 0x23DB; + JohtoBadges = 0x23E4; + CurrentBoxIndex = 0x2724; + BoxNames = 0x2727; + Party = 0x288A; + PokedexCaught = 0x2A4C; + PokedexSeen = 0x2A6C; + CurrentBox = 0x2D6C; + Gender = -1; // No gender in GS + AccumulatedChecksumEnd = 0x2D68; + OverallChecksumPosition = 0x2D69; + OverallChecksumPosition2 = 0x7E6D; - if (sav.Japanese) - LoadOffsetsJapanese(sav.Version); - else if (sav.Korean) - LoadOffsetsKorean(); - else - LoadOffsetsInternational(sav.Version); - Daycare = PokedexSeen + 0x1F + 28 + 1; // right after first unown seen - EventWork = EventFlag - 0x100; - } + PouchTMHM = 0x23E6; + PouchItem = 0x241F; + PouchKey = 0x2449; + PouchBall = 0x2464; + PouchPC = 0x247E; - public int RTCFlags { get; private set; } = -1; + EventFlag = CurrentBoxIndex - 0x105; + break; + case GameVersion.C: + TimePlayed = 0x2052; + Palette = 0x206A; + Money = 0x23DC; + JohtoBadges = 0x23E5; + CurrentBoxIndex = 0x2700; + BoxNames = 0x2703; + BlueCardPoints = 0x27D9; + Party = 0x2865; + PokedexCaught = 0x2A27; + PokedexSeen = 0x2A47; + CurrentBox = 0x2D10; + Gender = 0x3E3D; + AccumulatedChecksumEnd = 0x2B82; + OverallChecksumPosition = 0x2D0D; + OverallChecksumPosition2 = 0x7F0D; - public int Options { get; } - public int Trainer1 { get; } - public int Rival { get; private set; } + PouchTMHM = 0x23E7; + PouchItem = 0x2420; + PouchKey = 0x244A; + PouchBall = 0x2465; + PouchPC = 0x247F; - public int DaylightSavings { get; private set; } = -1; - public int TimePlayed { get; private set; } = -1; - public int Palette { get; private set; } = -1; - public int Money { get; private set; } = -1; - public int JohtoBadges { get; private set; } = -1; - public int CurrentBoxIndex { get; private set; } = -1; - public int BoxNames { get; private set; } = -1; - public int Party { get; private set; } = -1; - public int PokedexCaught { get; private set; } = -1; - public int PokedexSeen { get; private set; } = -1; - public int CurrentBox { get; private set; } = -1; - public int OtherCurrentBox { get; private set; } - public int Gender { get; private set; } = -1; - public int AccumulatedChecksumEnd { get; private set; } = -1; - public int OverallChecksumPosition { get; private set; } = -1; - public int EventFlag { get; private set; } = -1; - public int EventWork { get; } - public int Daycare { get; } + EventFlag = CurrentBoxIndex - 0x100; + break; - public int BlueCardPoints { get; private set; } = -1; - - public int PouchTMHM { get; private set; } = -1; - public int PouchItem { get; private set; } = -1; - public int PouchKey { get; private set; } = -1; - public int PouchBall { get; private set; } = -1; - public int PouchPC { get; private set; } = -1; - - private void LoadOffsetsInternational(GameVersion version) - { - RTCFlags = 0x0C60; - - Rival = 0x2021; - DaylightSavings = 0x2042; - OtherCurrentBox = 0x284C; - switch (version) - { - case GameVersion.GS: - TimePlayed = 0x2053; - Palette = 0x206B; - Money = 0x23DB; - JohtoBadges = 0x23E4; - CurrentBoxIndex = 0x2724; - BoxNames = 0x2727; - Party = 0x288A; - PokedexCaught = 0x2A4C; - PokedexSeen = 0x2A6C; - CurrentBox = 0x2D6C; - Gender = -1; // No gender in GS - AccumulatedChecksumEnd = 0x2D68; - OverallChecksumPosition = 0x2D69; - OverallChecksumPosition2 = 0x7E6D; - - PouchTMHM = 0x23E6; - PouchItem = 0x241F; - PouchKey = 0x2449; - PouchBall = 0x2464; - PouchPC = 0x247E; - - EventFlag = CurrentBoxIndex - 0x105; - break; - case GameVersion.C: - TimePlayed = 0x2052; - Palette = 0x206A; - Money = 0x23DC; - JohtoBadges = 0x23E5; - CurrentBoxIndex = 0x2700; - BoxNames = 0x2703; - BlueCardPoints = 0x27D9; - Party = 0x2865; - PokedexCaught = 0x2A27; - PokedexSeen = 0x2A47; - CurrentBox = 0x2D10; - Gender = 0x3E3D; - AccumulatedChecksumEnd = 0x2B82; - OverallChecksumPosition = 0x2D0D; - OverallChecksumPosition2 = 0x7F0D; - - PouchTMHM = 0x23E7; - PouchItem = 0x2420; - PouchKey = 0x244A; - PouchBall = 0x2465; - PouchPC = 0x247F; - - EventFlag = CurrentBoxIndex - 0x100; - break; - - default: - throw new ArgumentException(nameof(version)); - } - } - - private void LoadOffsetsJapanese(GameVersion version) - { - Rival = 0x2017; - DaylightSavings = 0x2029; - TimePlayed = 0x2034; - Palette = 0x204C; - CurrentBox = 0x2D10; - OtherCurrentBox = 0x2842; - - switch (version) - { - case GameVersion.GS: - RTCFlags = 0x1000; - - Money = 0x23BC; - JohtoBadges = 0x23C5; - CurrentBoxIndex = 0x2705; - BoxNames = 0x2708; - Party = 0x283E; - PokedexCaught = 0x29CE; - PokedexSeen = 0x29EE; - Gender = -1; // No gender in GS - AccumulatedChecksumEnd = 0x2C8B; - OverallChecksumPosition = 0x2D0D; - OverallChecksumPosition2 = 0x7F0D; - - PouchTMHM = 0x23C7; - PouchItem = 0x2400; - PouchKey = 0x242A; - PouchBall = 0x2445; - PouchPC = 0x245F; - - EventFlag = CurrentBoxIndex - 0x105; - break; - case GameVersion.C: - RTCFlags = 0x0C80; - - Money = 0x23BE; - JohtoBadges = 0x23C7; - CurrentBoxIndex = 0x26E2; - BoxNames = 0x26E5; - BlueCardPoints = 0x278E; - Party = 0x281A; - PokedexCaught = 0x29AA; - PokedexSeen = 0x29CA; - Gender = 0x8000; - AccumulatedChecksumEnd = 0x2AE2; - OverallChecksumPosition = 0x2D0D; - OverallChecksumPosition2 = 0x7F0D; - - PouchTMHM = 0x23C9; - PouchItem = 0x2402; - PouchKey = 0x242C; - PouchBall = 0x2447; - PouchPC = 0x2461; - - EventFlag = CurrentBoxIndex - 0x100; - break; - - default: - throw new ArgumentException(nameof(version)); - } - } - - public int OverallChecksumPosition2 { get; set; } - - private void LoadOffsetsKorean() - { - RTCFlags = 0x1060; - - // No Crystal Version - Rival = 0x2021; - DaylightSavings = 0x2042; - OtherCurrentBox = 0x284C; - - TimePlayed = 0x204D; - Palette = 0x2065; - Money = 0x23D3; - JohtoBadges = 0x23DC; - BoxNames = 0x26FF; - Party = 0x28CC; - PokedexCaught = 0x2A8E; - PokedexSeen = 0x2AAE; - CurrentBox = 0x2DAE; - CurrentBoxIndex = 0x26FC; - Gender = -1; // No gender in GS - AccumulatedChecksumEnd = 0x2DAA; - OverallChecksumPosition = 0x2DAB; - OverallChecksumPosition2 = 0x7E6B; - - PouchTMHM = 0x23DE; - PouchItem = 0x2417; - PouchKey = 0x2441; - PouchBall = 0x245C; - PouchPC = 0x2476; - - EventFlag = CurrentBoxIndex - 0x105; + default: + throw new ArgumentException(nameof(version)); } } + + private void LoadOffsetsJapanese(GameVersion version) + { + Rival = 0x2017; + DaylightSavings = 0x2029; + TimePlayed = 0x2034; + Palette = 0x204C; + CurrentBox = 0x2D10; + OtherCurrentBox = 0x2842; + + switch (version) + { + case GameVersion.GS: + RTCFlags = 0x1000; + + Money = 0x23BC; + JohtoBadges = 0x23C5; + CurrentBoxIndex = 0x2705; + BoxNames = 0x2708; + Party = 0x283E; + PokedexCaught = 0x29CE; + PokedexSeen = 0x29EE; + Gender = -1; // No gender in GS + AccumulatedChecksumEnd = 0x2C8B; + OverallChecksumPosition = 0x2D0D; + OverallChecksumPosition2 = 0x7F0D; + + PouchTMHM = 0x23C7; + PouchItem = 0x2400; + PouchKey = 0x242A; + PouchBall = 0x2445; + PouchPC = 0x245F; + + EventFlag = CurrentBoxIndex - 0x105; + break; + case GameVersion.C: + RTCFlags = 0x0C80; + + Money = 0x23BE; + JohtoBadges = 0x23C7; + CurrentBoxIndex = 0x26E2; + BoxNames = 0x26E5; + BlueCardPoints = 0x278E; + Party = 0x281A; + PokedexCaught = 0x29AA; + PokedexSeen = 0x29CA; + Gender = 0x8000; + AccumulatedChecksumEnd = 0x2AE2; + OverallChecksumPosition = 0x2D0D; + OverallChecksumPosition2 = 0x7F0D; + + PouchTMHM = 0x23C9; + PouchItem = 0x2402; + PouchKey = 0x242C; + PouchBall = 0x2447; + PouchPC = 0x2461; + + EventFlag = CurrentBoxIndex - 0x100; + break; + + default: + throw new ArgumentException(nameof(version)); + } + } + + public int OverallChecksumPosition2 { get; set; } + + private void LoadOffsetsKorean() + { + RTCFlags = 0x1060; + + // No Crystal Version + Rival = 0x2021; + DaylightSavings = 0x2042; + OtherCurrentBox = 0x284C; + + TimePlayed = 0x204D; + Palette = 0x2065; + Money = 0x23D3; + JohtoBadges = 0x23DC; + BoxNames = 0x26FF; + Party = 0x28CC; + PokedexCaught = 0x2A8E; + PokedexSeen = 0x2AAE; + CurrentBox = 0x2DAE; + CurrentBoxIndex = 0x26FC; + Gender = -1; // No gender in GS + AccumulatedChecksumEnd = 0x2DAA; + OverallChecksumPosition = 0x2DAB; + OverallChecksumPosition2 = 0x7E6B; + + PouchTMHM = 0x23DE; + PouchItem = 0x2417; + PouchKey = 0x2441; + PouchBall = 0x245C; + PouchPC = 0x2476; + + EventFlag = CurrentBoxIndex - 0x105; + } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryColo.cs b/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryColo.cs index f46b74e19..4950c8e26 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryColo.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryColo.cs @@ -1,18 +1,17 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class ShadowInfoEntryColo { - public sealed class ShadowInfoEntryColo - { - public readonly byte[] Data; - private const int SIZE_ENTRY = 12; + public readonly byte[] Data; + private const int SIZE_ENTRY = 12; - public ShadowInfoEntryColo() => Data = new byte[SIZE_ENTRY]; - public ShadowInfoEntryColo(byte[] data) => Data = data; + public ShadowInfoEntryColo() => Data = new byte[SIZE_ENTRY]; + public ShadowInfoEntryColo(byte[] data) => Data = data; - public uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x00)); set => WriteUInt32BigEndian(Data.AsSpan(0x00), value); } - public int Met_Location { get => ReadUInt16BigEndian(Data.AsSpan(0x06)); set => WriteUInt16BigEndian(Data.AsSpan(0x06), (ushort)value); } - public uint Unk08 { get => ReadUInt32BigEndian(Data.AsSpan(0x08)); set => WriteUInt32BigEndian(Data.AsSpan(0x08), value); } - } + public uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x00)); set => WriteUInt32BigEndian(Data.AsSpan(0x00), value); } + public int Met_Location { get => ReadUInt16BigEndian(Data.AsSpan(0x06)); set => WriteUInt16BigEndian(Data.AsSpan(0x06), (ushort)value); } + public uint Unk08 { get => ReadUInt32BigEndian(Data.AsSpan(0x08)); set => WriteUInt32BigEndian(Data.AsSpan(0x08), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryXD.cs b/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryXD.cs index 96c32545d..b8bed0396 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryXD.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoEntryXD.cs @@ -1,64 +1,63 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class ShadowInfoEntryXD { - public abstract class ShadowInfoEntryXD + public readonly byte[] Data; + + protected ShadowInfoEntryXD(byte[] data) => Data = data; + + public bool IsSnagged => Data[0] >> 6 != 0; + public bool IsPurified { get => Data[0] >> 7 == 1; set { Data[0] &= 0x7F; if (value) Data[0] |= 0x80; } } + + public int IV_HP { get => Data[0x0B]; set => Data[0x0B] = (byte)value; } + public int IV_ATK { get => Data[0x0C]; set => Data[0x0C] = (byte)value; } + public int IV_DEF { get => Data[0x0D]; set => Data[0x0D] = (byte)value; } + public int IV_SPA { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } + public int IV_SPD { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } + public int IV_SPE { get => Data[0x10]; set => Data[0x10] = (byte)value; } + + // Gen3 Species ID + public ushort RawSpecies { get => ReadUInt16BigEndian(Data.AsSpan(0x1A)); set => WriteUInt16BigEndian(Data.AsSpan(0x1A), value); } + public int Species { get => SpeciesConverter.GetG4Species(RawSpecies); set => RawSpecies = (ushort)SpeciesConverter.GetG3Species(value); } + public uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x1C)); set => WriteUInt32BigEndian(Data.AsSpan(0x1C), value); } + public int Purification { get => ReadInt32BigEndian(Data.AsSpan(0x24)); set => WriteInt32BigEndian(Data.AsSpan(0x24), value); } + + public uint EXP { - public readonly byte[] Data; - - protected ShadowInfoEntryXD(byte[] data) => Data = data; - - public bool IsSnagged => Data[0] >> 6 != 0; - public bool IsPurified { get => Data[0] >> 7 == 1; set { Data[0] &= 0x7F; if (value) Data[0] |= 0x80; } } - - public int IV_HP { get => Data[0x0B]; set => Data[0x0B] = (byte)value; } - public int IV_ATK { get => Data[0x0C]; set => Data[0x0C] = (byte)value; } - public int IV_DEF { get => Data[0x0D]; set => Data[0x0D] = (byte)value; } - public int IV_SPA { get => Data[0x0E]; set => Data[0x0E] = (byte)value; } - public int IV_SPD { get => Data[0x0F]; set => Data[0x0F] = (byte)value; } - public int IV_SPE { get => Data[0x10]; set => Data[0x10] = (byte)value; } - - // Gen3 Species ID - public ushort RawSpecies { get => ReadUInt16BigEndian(Data.AsSpan(0x1A)); set => WriteUInt16BigEndian(Data.AsSpan(0x1A), value); } - public int Species { get => SpeciesConverter.GetG4Species(RawSpecies); set => RawSpecies = (ushort)SpeciesConverter.GetG3Species(value); } - public uint PID { get => ReadUInt32BigEndian(Data.AsSpan(0x1C)); set => WriteUInt32BigEndian(Data.AsSpan(0x1C), value); } - public int Purification { get => ReadInt32BigEndian(Data.AsSpan(0x24)); set => WriteInt32BigEndian(Data.AsSpan(0x24), value); } - - public uint EXP - { - get => ReadUInt32BigEndian(Data.AsSpan(0x04)) >> 12; - set => WriteUInt32BigEndian(Data.AsSpan(0x04), (ReadUInt32BigEndian(Data.AsSpan(0x04)) & 0xFFF) | (value << 12)); - } - - public bool IsEmpty => Species == 0; - - public abstract byte Index { get; set; } - - public override string ToString() => $"{(Species) Species} 0x{PID:X8} {Purification}"; + get => ReadUInt32BigEndian(Data.AsSpan(0x04)) >> 12; + set => WriteUInt32BigEndian(Data.AsSpan(0x04), (ReadUInt32BigEndian(Data.AsSpan(0x04)) & 0xFFF) | (value << 12)); } - public sealed class ShadowInfoEntry3J : ShadowInfoEntryXD - { - internal const int SIZE_ENTRY = 60; // -12 from U + public bool IsEmpty => Species == 0; - public ShadowInfoEntry3J() : base(new byte[SIZE_ENTRY]) { } - public ShadowInfoEntry3J(byte[] data) : base(data) { } + public abstract byte Index { get; set; } - public override byte Index { get => Data[0x35]; set => Data[0x35] = value; } - - public override string ToString() => $"{(Species)Species} 0x{PID:X8} {Purification}"; - } - - public sealed class ShadowInfoEntry3U : ShadowInfoEntryXD - { - internal const int SIZE_ENTRY = 72; // -12 from U - - public ShadowInfoEntry3U() : base(new byte[SIZE_ENTRY]) { } - public ShadowInfoEntry3U(byte[] data) : base(data) { } - - public override byte Index { get => Data[0x3F]; set => Data[0x3F] = value; } - - public override string ToString() => $"{(Species)Species} 0x{PID:X8} {Purification}"; - } + public override string ToString() => $"{(Species) Species} 0x{PID:X8} {Purification}"; +} + +public sealed class ShadowInfoEntry3J : ShadowInfoEntryXD +{ + internal const int SIZE_ENTRY = 60; // -12 from U + + public ShadowInfoEntry3J() : base(new byte[SIZE_ENTRY]) { } + public ShadowInfoEntry3J(byte[] data) : base(data) { } + + public override byte Index { get => Data[0x35]; set => Data[0x35] = value; } + + public override string ToString() => $"{(Species)Species} 0x{PID:X8} {Purification}"; +} + +public sealed class ShadowInfoEntry3U : ShadowInfoEntryXD +{ + internal const int SIZE_ENTRY = 72; // -12 from U + + public ShadowInfoEntry3U() : base(new byte[SIZE_ENTRY]) { } + public ShadowInfoEntry3U(byte[] data) : base(data) { } + + public override byte Index { get => Data[0x3F]; set => Data[0x3F] = value; } + + public override string ToString() => $"{(Species)Species} 0x{PID:X8} {Purification}"; } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoTableXD.cs b/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoTableXD.cs index fe5e2a73f..d178012e4 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoTableXD.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/CXD/ShadowInfoTableXD.cs @@ -1,38 +1,37 @@ using System; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class ShadowInfoTableXD { - public sealed class ShadowInfoTableXD + private readonly ShadowInfoEntryXD[] Entries; + private readonly int MaxLength; + private readonly int SIZE_ENTRY; + private const int MaxCount = 128; + + public ShadowInfoTableXD(ReadOnlySpan data, bool jp) { - private readonly ShadowInfoEntryXD[] Entries; - private readonly int MaxLength; - private readonly int SIZE_ENTRY; - private const int MaxCount = 128; - - public ShadowInfoTableXD(ReadOnlySpan data, bool jp) - { - SIZE_ENTRY = GetEntrySize(jp); - MaxLength = data.Length; - int eCount = data.Length/SIZE_ENTRY; - Entries = new ShadowInfoEntryXD[eCount]; - for (int i = 0; i < eCount; i++) - Entries[i] = GetEntry(data, i, jp); - } - - private static int GetEntrySize(bool jp) => jp ? ShadowInfoEntry3J.SIZE_ENTRY : ShadowInfoEntry3U.SIZE_ENTRY; - - public ShadowInfoTableXD(bool jp) : this(new byte[GetEntrySize(jp) * MaxCount], jp) { } - - private ShadowInfoEntryXD GetEntry(ReadOnlySpan data, int index, bool jp) - { - var slice = data.Slice(index * SIZE_ENTRY, SIZE_ENTRY).ToArray(); - return jp ? new ShadowInfoEntry3J(slice) : new ShadowInfoEntry3U(slice); - } - - public byte[] Write() => Entries.SelectMany(entry => entry.Data).Take(MaxLength).ToArray(); - - public ShadowInfoEntryXD this[int index] { get => Entries[index]; set => Entries[index] = value; } - public int Count => Entries.Length; + SIZE_ENTRY = GetEntrySize(jp); + MaxLength = data.Length; + int eCount = data.Length/SIZE_ENTRY; + Entries = new ShadowInfoEntryXD[eCount]; + for (int i = 0; i < eCount; i++) + Entries[i] = GetEntry(data, i, jp); } + + private static int GetEntrySize(bool jp) => jp ? ShadowInfoEntry3J.SIZE_ENTRY : ShadowInfoEntry3U.SIZE_ENTRY; + + public ShadowInfoTableXD(bool jp) : this(new byte[GetEntrySize(jp) * MaxCount], jp) { } + + private ShadowInfoEntryXD GetEntry(ReadOnlySpan data, int index, bool jp) + { + var slice = data.Slice(index * SIZE_ENTRY, SIZE_ENTRY).ToArray(); + return jp ? new ShadowInfoEntry3J(slice) : new ShadowInfoEntry3U(slice); + } + + public byte[] Write() => Entries.SelectMany(entry => entry.Data).Take(MaxLength).ToArray(); + + public ShadowInfoEntryXD this[int index] { get => Entries[index]; set => Entries[index] = value; } + public int Count => Entries.Length; } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/Decoration3.cs b/PKHeX.Core/Saves/Substructures/Gen3/Decoration3.cs index 145cb77f3..5edc04ee6 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/Decoration3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/Decoration3.cs @@ -1,127 +1,126 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum Decoration3 : byte { - public enum Decoration3 : byte - { - NONE, - SMALL_DESK, - POKEMON_DESK, - HEAVY_DESK, - RAGGED_DESK, - COMFORT_DESK, - PRETTY_DESK, - BRICK_DESK, - CAMP_DESK, - HARD_DESK, - SMALL_CHAIR, - POKEMON_CHAIR, - HEAVY_CHAIR, - PRETTY_CHAIR, - COMFORT_CHAIR, - RAGGED_CHAIR, - BRICK_CHAIR, - CAMP_CHAIR, - HARD_CHAIR, - RED_PLANT, - TROPICAL_PLANT, - PRETTY_FLOWERS, - COLORFUL_PLANT, - BIG_PLANT, - GORGEOUS_PLANT, - RED_BRICK, - YELLOW_BRICK, - BLUE_BRICK, - RED_BALLOON, - BLUE_BALLOON, - YELLOW_BALLOON, - RED_TENT, - BLUE_TENT, - SOLID_BOARD, - SLIDE, - FENCE_LENGTH, - FENCE_WIDTH, - TIRE, - STAND, - MUD_BALL, - BREAKABLE_DOOR, - SAND_ORNAMENT, - SILVER_SHIELD, - GOLD_SHIELD, - GLASS_ORNAMENT, - TV, - ROUND_TV, - CUTE_TV, - GLITTER_MAT, - JUMP_MAT, - SPIN_MAT, - C_LOW_NOTE_MAT, - D_NOTE_MAT, - E_NOTE_MAT, - F_NOTE_MAT, - G_NOTE_MAT, - A_NOTE_MAT, - B_NOTE_MAT, - C_HIGH_NOTE_MAT, - SURF_MAT, - THUNDER_MAT, - FIRE_BLAST_MAT, - POWDER_SNOW_MAT, - ATTRACT_MAT, - FISSURE_MAT, - SPIKES_MAT, - BALL_POSTER, - GREEN_POSTER, - RED_POSTER, - BLUE_POSTER, - CUTE_POSTER, - PIKA_POSTER, - LONG_POSTER, - SEA_POSTER, - SKY_POSTER, - KISS_POSTER, - PICHU_DOLL, - PIKACHU_DOLL, - MARILL_DOLL, - TOGEPI_DOLL, - CYNDAQUIL_DOLL, - CHIKORITA_DOLL, - TOTODILE_DOLL, - JIGGLYPUFF_DOLL, - MEOWTH_DOLL, - CLEFAIRY_DOLL, - DITTO_DOLL, - SMOOCHUM_DOLL, - TREECKO_DOLL, - TORCHIC_DOLL, - MUDKIP_DOLL, - DUSKULL_DOLL, - WYNAUT_DOLL, - BALTOY_DOLL, - KECLEON_DOLL, - AZURILL_DOLL, - SKITTY_DOLL, - SWABLU_DOLL, - GULPIN_DOLL, - LOTAD_DOLL, - SEEDOT_DOLL, - PIKA_CUSHION, - ROUND_CUSHION, - KISS_CUSHION, - ZIGZAG_CUSHION, - SPIN_CUSHION, - DIAMOND_CUSHION, - BALL_CUSHION, - GRASS_CUSHION, - FIRE_CUSHION, - WATER_CUSHION, - SNORLAX_DOLL, - RHYDON_DOLL, - LAPRAS_DOLL, - VENUSAUR_DOLL, - CHARIZARD_DOLL, - BLASTOISE_DOLL, - WAILMER_DOLL, - REGIROCK_DOLL, - REGICE_DOLL, - REGISTEEL_DOLL, - } -} \ No newline at end of file + NONE, + SMALL_DESK, + POKEMON_DESK, + HEAVY_DESK, + RAGGED_DESK, + COMFORT_DESK, + PRETTY_DESK, + BRICK_DESK, + CAMP_DESK, + HARD_DESK, + SMALL_CHAIR, + POKEMON_CHAIR, + HEAVY_CHAIR, + PRETTY_CHAIR, + COMFORT_CHAIR, + RAGGED_CHAIR, + BRICK_CHAIR, + CAMP_CHAIR, + HARD_CHAIR, + RED_PLANT, + TROPICAL_PLANT, + PRETTY_FLOWERS, + COLORFUL_PLANT, + BIG_PLANT, + GORGEOUS_PLANT, + RED_BRICK, + YELLOW_BRICK, + BLUE_BRICK, + RED_BALLOON, + BLUE_BALLOON, + YELLOW_BALLOON, + RED_TENT, + BLUE_TENT, + SOLID_BOARD, + SLIDE, + FENCE_LENGTH, + FENCE_WIDTH, + TIRE, + STAND, + MUD_BALL, + BREAKABLE_DOOR, + SAND_ORNAMENT, + SILVER_SHIELD, + GOLD_SHIELD, + GLASS_ORNAMENT, + TV, + ROUND_TV, + CUTE_TV, + GLITTER_MAT, + JUMP_MAT, + SPIN_MAT, + C_LOW_NOTE_MAT, + D_NOTE_MAT, + E_NOTE_MAT, + F_NOTE_MAT, + G_NOTE_MAT, + A_NOTE_MAT, + B_NOTE_MAT, + C_HIGH_NOTE_MAT, + SURF_MAT, + THUNDER_MAT, + FIRE_BLAST_MAT, + POWDER_SNOW_MAT, + ATTRACT_MAT, + FISSURE_MAT, + SPIKES_MAT, + BALL_POSTER, + GREEN_POSTER, + RED_POSTER, + BLUE_POSTER, + CUTE_POSTER, + PIKA_POSTER, + LONG_POSTER, + SEA_POSTER, + SKY_POSTER, + KISS_POSTER, + PICHU_DOLL, + PIKACHU_DOLL, + MARILL_DOLL, + TOGEPI_DOLL, + CYNDAQUIL_DOLL, + CHIKORITA_DOLL, + TOTODILE_DOLL, + JIGGLYPUFF_DOLL, + MEOWTH_DOLL, + CLEFAIRY_DOLL, + DITTO_DOLL, + SMOOCHUM_DOLL, + TREECKO_DOLL, + TORCHIC_DOLL, + MUDKIP_DOLL, + DUSKULL_DOLL, + WYNAUT_DOLL, + BALTOY_DOLL, + KECLEON_DOLL, + AZURILL_DOLL, + SKITTY_DOLL, + SWABLU_DOLL, + GULPIN_DOLL, + LOTAD_DOLL, + SEEDOT_DOLL, + PIKA_CUSHION, + ROUND_CUSHION, + KISS_CUSHION, + ZIGZAG_CUSHION, + SPIN_CUSHION, + DIAMOND_CUSHION, + BALL_CUSHION, + GRASS_CUSHION, + FIRE_CUSHION, + WATER_CUSHION, + SNORLAX_DOLL, + RHYDON_DOLL, + LAPRAS_DOLL, + VENUSAUR_DOLL, + CHARIZARD_DOLL, + BLASTOISE_DOLL, + WAILMER_DOLL, + REGIROCK_DOLL, + REGICE_DOLL, + REGISTEEL_DOLL, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen3/DecorationInventory3.cs b/PKHeX.Core/Saves/Substructures/Gen3/DecorationInventory3.cs index 3932095f0..62de6cbaf 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/DecorationInventory3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/DecorationInventory3.cs @@ -1,23 +1,22 @@ using System; using System.Runtime.InteropServices; -namespace PKHeX.Core +namespace PKHeX.Core; + +[StructLayout(LayoutKind.Sequential)] +public readonly ref struct DecorationInventory3 { - [StructLayout(LayoutKind.Sequential)] - public readonly ref struct DecorationInventory3 - { - public const int SIZE = 150; - private readonly Span Data; + public const int SIZE = 150; + private readonly Span Data; - public DecorationInventory3(Span data) => Data = data; + public DecorationInventory3(Span data) => Data = data; - public Span Desk => MemoryMarshal.Cast(Data[..10]); - public Span Chair => MemoryMarshal.Cast(Data.Slice(10, 10)); - public Span Plant => MemoryMarshal.Cast(Data.Slice(20, 10)); - public Span Ornament => MemoryMarshal.Cast(Data.Slice(30, 30)); - public Span Mat => MemoryMarshal.Cast(Data.Slice(60, 30)); - public Span Poster => MemoryMarshal.Cast(Data.Slice(90, 10)); - public Span Doll => MemoryMarshal.Cast(Data.Slice(100, 40)); - public Span Cushion => MemoryMarshal.Cast(Data.Slice(140, 10)); - } + public Span Desk => MemoryMarshal.Cast(Data[..10]); + public Span Chair => MemoryMarshal.Cast(Data.Slice(10, 10)); + public Span Plant => MemoryMarshal.Cast(Data.Slice(20, 10)); + public Span Ornament => MemoryMarshal.Cast(Data.Slice(30, 30)); + public Span Mat => MemoryMarshal.Cast(Data.Slice(60, 30)); + public Span Poster => MemoryMarshal.Cast(Data.Slice(90, 10)); + public Span Doll => MemoryMarshal.Cast(Data.Slice(100, 40)); + public Span Cushion => MemoryMarshal.Cast(Data.Slice(140, 10)); } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/EReaderBerrySettings.cs b/PKHeX.Core/Saves/Substructures/Gen3/EReaderBerrySettings.cs index 6a99d5a99..808870c7f 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/EReaderBerrySettings.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/EReaderBerrySettings.cs @@ -1,119 +1,118 @@ using System.Collections.Generic; using static PKHeX.Core.EReaderBerryMatch; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Stores details about the Gen3 e-Reader Berry values. +/// +public static class EReaderBerrySettings { + /// e-Reader Berry is Enigma or special berry (from e-Reader data) + public static bool IsEnigma { get; set; } = true; + + /// e-Reader Berry Name + private static string Name { get; set; } = string.Empty; + + /// e-Reader Berry Name formatted in Title Case + public static string DisplayName => string.Format(LegalityCheckStrings.L_XEnigmaBerry_0, Util.ToTitleCase(Name)); + + private static int Language { get; set; } + /// - /// Stores details about the Gen3 e-Reader Berry values. + /// Checks if the most recently loaded Generation 3 Save File has a proper Enigma Berry that matches known distributions. /// - public static class EReaderBerrySettings + public static EReaderBerryMatch GetStatus() { - /// e-Reader Berry is Enigma or special berry (from e-Reader data) - public static bool IsEnigma { get; set; } = true; + if (IsEnigma) // no e-Reader Berry data provided, can't hold berry. + return NoData; - /// e-Reader Berry Name - private static string Name { get; set; } = string.Empty; - - /// e-Reader Berry Name formatted in Title Case - public static string DisplayName => string.Format(LegalityCheckStrings.L_XEnigmaBerry_0, Util.ToTitleCase(Name)); - - private static int Language { get; set; } - - /// - /// Checks if the most recently loaded Generation 3 Save File has a proper Enigma Berry that matches known distributions. - /// - public static EReaderBerryMatch GetStatus() + var name = Name; + if (EReaderBerriesNames_USA.Contains(name)) { - if (IsEnigma) // no e-Reader Berry data provided, can't hold berry. - return NoData; - - var name = Name; - if (EReaderBerriesNames_USA.Contains(name)) + return Language switch { - return Language switch - { - <= 0 => ValidAny, - not 1 => ValidUSA, - _ => InvalidUSA, - }; - } - if (EReaderBerriesNames_JP.Contains(name)) - { - return Language switch - { - <= 0 => ValidAny, - 1 => ValidJPN, - _ => InvalidJPN, - }; - } - return NoMatch; + <= 0 => ValidAny, + not 1 => ValidUSA, + _ => InvalidUSA, + }; } - - private static readonly HashSet EReaderBerriesNames_USA = new() + if (EReaderBerriesNames_JP.Contains(name)) { - // USA Series 1 - "PUMKIN", - "DRASH", - "EGGANT", - "STRIB", - "CHILAN", - "NUTPEA", - }; - - private static readonly HashSet EReaderBerriesNames_JP = new() - { - // JP Series 1 - "カチャ", // PUMKIN - "ブ-カ", // DRASH - "ビスナ", // EGGANT - "エドマ", // STRIB - "ホズ", // CHILAN - "ラッカ", // NUTPEA - "クオ", // KU - // JP Series 2 - "ギネマ", // GINEMA - "クオ", // KUO - "ヤゴ", // YAGO - "トウガ", // TOUGA - "ニニク", // NINIKU - "トポ", // TOPO - }; - - public static void LoadFrom(SAV3 sav3) - { - Language = sav3.Japanese ? (int)LanguageID.Japanese : (int)LanguageID.English; - if (sav3.IsEBerryEngima) + return Language switch { - IsEnigma = true; - Name = string.Empty; - } - else - { - IsEnigma = false; - Name = sav3.EBerryName; - } + <= 0 => ValidAny, + 1 => ValidJPN, + _ => InvalidJPN, + }; } + return NoMatch; } - /// - /// For use in validating the current e-Reader Berry settings. - /// - public enum EReaderBerryMatch : byte + private static readonly HashSet EReaderBerriesNames_USA = new() { - /// Invalid: Doesn't match either language - NoMatch, - /// Invalid: No data provided from a save file - NoData, - /// Invalid: USA berry name on JPN save file - InvalidUSA, - /// Invalid: JPN berry name on USA save file - InvalidJPN, + // USA Series 1 + "PUMKIN", + "DRASH", + "EGGANT", + "STRIB", + "CHILAN", + "NUTPEA", + }; - /// Valid: Berry name matches either USA/JPN (Ambiguous Save File Language) - ValidAny, - /// Valid: Berry name matches a USA berry name - ValidUSA, - /// Valid: Berry name matches a JPN berry name - ValidJPN, + private static readonly HashSet EReaderBerriesNames_JP = new() + { + // JP Series 1 + "カチャ", // PUMKIN + "ブ-カ", // DRASH + "ビスナ", // EGGANT + "エドマ", // STRIB + "ホズ", // CHILAN + "ラッカ", // NUTPEA + "クオ", // KU + // JP Series 2 + "ギネマ", // GINEMA + "クオ", // KUO + "ヤゴ", // YAGO + "トウガ", // TOUGA + "ニニク", // NINIKU + "トポ", // TOPO + }; + + public static void LoadFrom(SAV3 sav3) + { + Language = sav3.Japanese ? (int)LanguageID.Japanese : (int)LanguageID.English; + if (sav3.IsEBerryEngima) + { + IsEnigma = true; + Name = string.Empty; + } + else + { + IsEnigma = false; + Name = sav3.EBerryName; + } } } + +/// +/// For use in validating the current e-Reader Berry settings. +/// +public enum EReaderBerryMatch : byte +{ + /// Invalid: Doesn't match either language + NoMatch, + /// Invalid: No data provided from a save file + NoData, + /// Invalid: USA berry name on JPN save file + InvalidUSA, + /// Invalid: JPN berry name on USA save file + InvalidJPN, + + /// Valid: Berry name matches either USA/JPN (Ambiguous Save File Language) + ValidAny, + /// Valid: Berry name matches a USA berry name + ValidUSA, + /// Valid: Berry name matches a JPN berry name + ValidJPN, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen3/Events/MysteryEvent3.cs b/PKHeX.Core/Saves/Substructures/Gen3/Events/MysteryEvent3.cs index 9046edd7f..bf4726cd6 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/Events/MysteryEvent3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/Events/MysteryEvent3.cs @@ -1,20 +1,19 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MysteryEvent3 : Gen3MysteryData { - public sealed class MysteryEvent3 : Gen3MysteryData + public const int SIZE = sizeof(uint) + 1000; // total 0x3EC + + public MysteryEvent3(byte[] data) : base(data) { - public const int SIZE = sizeof(uint) + 1000; // total 0x3EC - - public MysteryEvent3(byte[] data) : base(data) - { - if (data.Length != SIZE) - throw new ArgumentException("Invalid size.", nameof(data)); - } - - public byte Magic { get => Data[4]; set => Data[4] = value; } - public byte MapGroup { get => Data[5]; set => Data[5] = value; } - public byte MapNumber { get => Data[6]; set => Data[6] = value; } - public byte ObjectID { get => Data[7]; set => Data[7] = value; } + if (data.Length != SIZE) + throw new ArgumentException("Invalid size.", nameof(data)); } + + public byte Magic { get => Data[4]; set => Data[4] = value; } + public byte MapGroup { get => Data[5]; set => Data[5] = value; } + public byte MapNumber { get => Data[6]; set => Data[6] = value; } + public byte ObjectID { get => Data[7]; set => Data[7] = value; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderCard3.cs b/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderCard3.cs index 4753f25bc..3ff2a4b03 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderCard3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderCard3.cs @@ -1,29 +1,28 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class WonderCard3 : Gen3MysteryData { - public sealed class WonderCard3 : Gen3MysteryData + /// + /// 0x150: Total Size of this object + /// + public const int SIZE = sizeof(uint) + 332; + + public WonderCard3(byte[] data) : base(data) { - /// - /// 0x150: Total Size of this object - /// - public const int SIZE = sizeof(uint) + 332; - - public WonderCard3(byte[] data) : base(data) - { - if (data.Length != SIZE) - throw new ArgumentException("Invalid size.", nameof(data)); - } - - public ushort CardID { get => ReadUInt16LittleEndian(Data.AsSpan(4)); set => WriteUInt16LittleEndian(Data.AsSpan(4), value); } - public ushort Icon { get => ReadUInt16LittleEndian(Data.AsSpan(6)); set => WriteUInt16LittleEndian(Data.AsSpan(6), value); } - public uint Count { get => ReadUInt16LittleEndian(Data.AsSpan(8)); set => WriteUInt32LittleEndian(Data.AsSpan(8), value); } - - public byte Type { get => (byte)(Data[0xC] & 0b11); set => Data[0xC] = (byte)((Data[0xC] & ~0b11) | (value & 0b11)); } - public byte Color { get => (byte)(Data[0xC] & 0b00111100); set => Data[0xC] = (byte)((Data[0xC] & ~0b00111100) | (value & 0b00111100)); } - public byte ShareState { get => (byte)(Data[0xC] & 0b11000000); set => Data[0xC] = (byte)((Data[0xC] & ~0b11000000) | (value & 0b11000000)); } - - public byte Count2 { get => Data[0xD]; set => Data[0xD] = value; } + if (data.Length != SIZE) + throw new ArgumentException("Invalid size.", nameof(data)); } + + public ushort CardID { get => ReadUInt16LittleEndian(Data.AsSpan(4)); set => WriteUInt16LittleEndian(Data.AsSpan(4), value); } + public ushort Icon { get => ReadUInt16LittleEndian(Data.AsSpan(6)); set => WriteUInt16LittleEndian(Data.AsSpan(6), value); } + public uint Count { get => ReadUInt16LittleEndian(Data.AsSpan(8)); set => WriteUInt32LittleEndian(Data.AsSpan(8), value); } + + public byte Type { get => (byte)(Data[0xC] & 0b11); set => Data[0xC] = (byte)((Data[0xC] & ~0b11) | (value & 0b11)); } + public byte Color { get => (byte)(Data[0xC] & 0b00111100); set => Data[0xC] = (byte)((Data[0xC] & ~0b00111100) | (value & 0b00111100)); } + public byte ShareState { get => (byte)(Data[0xC] & 0b11000000); set => Data[0xC] = (byte)((Data[0xC] & ~0b11000000) | (value & 0b11000000)); } + + public byte Count2 { get => Data[0xD]; set => Data[0xD] = value; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderCard3Extra.cs b/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderCard3Extra.cs index 1386cde80..74e919986 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderCard3Extra.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderCard3Extra.cs @@ -1,26 +1,25 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class WonderCard3Extra : Gen3MysteryData { - public sealed class WonderCard3Extra : Gen3MysteryData + /// + /// 0x28: Total Size of this object + /// + public const int SIZE = sizeof(uint) + 36; + + public WonderCard3Extra(byte[] data) : base(data) { - /// - /// 0x28: Total Size of this object - /// - public const int SIZE = sizeof(uint) + 36; - - public WonderCard3Extra(byte[] data) : base(data) - { - if (data.Length != SIZE) - throw new ArgumentException("Invalid size.", nameof(data)); - } - - public ushort Wins { get => ReadUInt16LittleEndian(Data.AsSpan(0x4)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4), value); } - public ushort Losses { get => ReadUInt16LittleEndian(Data.AsSpan(0x6)); set => WriteUInt16LittleEndian(Data.AsSpan(0x6), value); } - public ushort Trades { get => ReadUInt16LittleEndian(Data.AsSpan(0x8)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8), value); } - public ushort Unk { get => ReadUInt16LittleEndian(Data.AsSpan(0xA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA), value); } - - // u16 distributedMons[2][7] + if (data.Length != SIZE) + throw new ArgumentException("Invalid size.", nameof(data)); } + + public ushort Wins { get => ReadUInt16LittleEndian(Data.AsSpan(0x4)); set => WriteUInt16LittleEndian(Data.AsSpan(0x4), value); } + public ushort Losses { get => ReadUInt16LittleEndian(Data.AsSpan(0x6)); set => WriteUInt16LittleEndian(Data.AsSpan(0x6), value); } + public ushort Trades { get => ReadUInt16LittleEndian(Data.AsSpan(0x8)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8), value); } + public ushort Unk { get => ReadUInt16LittleEndian(Data.AsSpan(0xA)); set => WriteUInt16LittleEndian(Data.AsSpan(0xA), value); } + + // u16 distributedMons[2][7] } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderNews3.cs b/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderNews3.cs index ee12edcb9..2cba1f6cd 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderNews3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/Events/WonderNews3.cs @@ -1,23 +1,22 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class WonderNews3 : Gen3MysteryData { - public sealed class WonderNews3 : Gen3MysteryData + /// + /// 0x1C0: Total Size of this object + /// + public const int SIZE = sizeof(uint) + 444; + + public WonderNews3(byte[] data) : base(data) { - /// - /// 0x1C0: Total Size of this object - /// - public const int SIZE = sizeof(uint) + 444; - - public WonderNews3(byte[] data) : base(data) - { - if (data.Length != SIZE) - throw new ArgumentException("Invalid size.", nameof(data)); - } - - public ushort NewsID { get => ReadUInt16LittleEndian(Data.AsSpan(4)); set => WriteUInt16LittleEndian(Data.AsSpan(4), value); } - public byte Flag { get => Data[6]; set => Data[6] = value; } - public byte Color { get => Data[7]; set => Data[7] = value; } + if (data.Length != SIZE) + throw new ArgumentException("Invalid size.", nameof(data)); } + + public ushort NewsID { get => ReadUInt16LittleEndian(Data.AsSpan(4)); set => WriteUInt16LittleEndian(Data.AsSpan(4), value); } + public byte Flag { get => Data[6]; set => Data[6] = value; } + public byte Color { get => Data[7]; set => Data[7] = value; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/Gen3MysteryData.cs b/PKHeX.Core/Saves/Substructures/Gen3/Gen3MysteryData.cs index 59632f1e7..8b41f8cc3 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/Gen3MysteryData.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/Gen3MysteryData.cs @@ -1,33 +1,32 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class Gen3MysteryData { - public abstract class Gen3MysteryData + public readonly byte[] Data; + + protected Gen3MysteryData(byte[] data) => Data = data; + + public uint Checksum { - public readonly byte[] Data; + get => ReadUInt32LittleEndian(Data.AsSpan(0)); + set => WriteUInt32LittleEndian(Data.AsSpan(0), value); + } - protected Gen3MysteryData(byte[] data) => Data = data; + public bool IsChecksumValid() => Checksum == GetChecksum(Data); + public void FixChecksum() => Checksum = GetChecksum(Data); - public uint Checksum + private static uint GetChecksum(ReadOnlySpan data) + { + uint sum = 0; + for (var i = 4; i < data.Length; i++) { - get => ReadUInt32LittleEndian(Data.AsSpan(0)); - set => WriteUInt32LittleEndian(Data.AsSpan(0), value); + var b = data[i]; + sum += b; } - public bool IsChecksumValid() => Checksum == GetChecksum(Data); - public void FixChecksum() => Checksum = GetChecksum(Data); - - private static uint GetChecksum(ReadOnlySpan data) - { - uint sum = 0; - for (var i = 4; i < data.Length; i++) - { - var b = data[i]; - sum += b; - } - - return sum; - } + return sum; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/HallFame3.cs b/PKHeX.Core/Saves/Substructures/Gen3/HallFame3.cs index f7c78c27b..c2f8eb019 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/HallFame3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/HallFame3.cs @@ -2,84 +2,83 @@ using System.Diagnostics; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class HallFame3Entry { - public sealed class HallFame3Entry + private readonly byte[] Parent; + private readonly bool Japanese; + private readonly int Offset; + + private const int Count = 6; + public const int SIZE = Count * HallFame3PKM.SIZE; + + public HallFame3Entry(byte[] data, int offset, bool japanese) { - private readonly byte[] Parent; - private readonly bool Japanese; - private readonly int Offset; + Parent = data; + Japanese = japanese; + Offset = offset; + } - private const int Count = 6; - public const int SIZE = Count * HallFame3PKM.SIZE; + private int GetMemberOffset(int index) => Offset + (index * HallFame3PKM.SIZE); + private HallFame3PKM GetMember(int index) => new(Parent, GetMemberOffset(index), Japanese); - public HallFame3Entry(byte[] data, int offset, bool japanese) + public HallFame3PKM[] Team + { + get { - Parent = data; - Japanese = japanese; - Offset = offset; - } - - private int GetMemberOffset(int index) => Offset + (index * HallFame3PKM.SIZE); - private HallFame3PKM GetMember(int index) => new(Parent, GetMemberOffset(index), Japanese); - - public HallFame3PKM[] Team - { - get - { - var team = new HallFame3PKM[6]; - for (int i = 0; i < Count; i++) - team[i] = GetMember(i); - return team; - } - } - - private const int MaxEntries = 50; - private const int MaxLength = MaxEntries * SIZE; - - public static HallFame3Entry[] GetEntries(SAV3 sav) - { - byte[] data = sav.GetHallOfFameData(); - Debug.Assert(data.Length > MaxLength); - bool Japanese = sav.Japanese; - - var entries = new HallFame3Entry[MaxEntries]; - for (int i = 0; i < entries.Length; i++) - entries[i] = new HallFame3Entry(data, SIZE, Japanese); - return entries; - } - - public static void SetEntries(SAV3 sav, HallFame3Entry[] entries) - { - byte[] data = entries[0].Team[0].Data; - sav.SetHallOfFameData(data); + var team = new HallFame3PKM[6]; + for (int i = 0; i < Count; i++) + team[i] = GetMember(i); + return team; } } - public sealed class HallFame3PKM + private const int MaxEntries = 50; + private const int MaxLength = MaxEntries * SIZE; + + public static HallFame3Entry[] GetEntries(SAV3 sav) { - public const int SIZE = 20; + byte[] data = sav.GetHallOfFameData(); + Debug.Assert(data.Length > MaxLength); + bool Japanese = sav.Japanese; - public HallFame3PKM(byte[] data, int offset, bool jp) - { - Data = data; - Offset = offset; - Japanese = jp; - } + var entries = new HallFame3Entry[MaxEntries]; + for (int i = 0; i < entries.Length; i++) + entries[i] = new HallFame3Entry(data, SIZE, Japanese); + return entries; + } - public readonly byte[] Data; - private readonly int Offset; - private readonly bool Japanese; - - public int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0 + Offset)); set => WriteUInt16LittleEndian(Data.AsSpan(0 + Offset), (ushort)value); } - public int SID { get => ReadUInt16LittleEndian(Data.AsSpan(2 + Offset)); set => WriteUInt16LittleEndian(Data.AsSpan(2 + Offset), (ushort)value); } - public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(4 + Offset)); set => WriteUInt32LittleEndian(Data.AsSpan(4 + Offset), value); } - private int SpecLevel { get => ReadUInt16LittleEndian(Data.AsSpan(8 + Offset)); set => WriteUInt16LittleEndian(Data.AsSpan(8 + Offset), (ushort)value); } - - private Span Nickname_Trash => Data.AsSpan(10 + Offset, 10); - public string Nickname { get => StringConverter3.GetString(Nickname_Trash, Japanese); set => StringConverter3.SetString(Nickname_Trash, value.AsSpan(), 10, Japanese, StringConverterOption.ClearZero); } - - public int Species { get => SpecLevel & 0x1FF; set => SpecLevel = (SpecLevel & 0xFE00) | value; } - public int Level { get => SpecLevel >> 9; set => SpecLevel = (SpecLevel & 0x1FF) | (value << 9); } + public static void SetEntries(SAV3 sav, HallFame3Entry[] entries) + { + byte[] data = entries[0].Team[0].Data; + sav.SetHallOfFameData(data); } } + +public sealed class HallFame3PKM +{ + public const int SIZE = 20; + + public HallFame3PKM(byte[] data, int offset, bool jp) + { + Data = data; + Offset = offset; + Japanese = jp; + } + + public readonly byte[] Data; + private readonly int Offset; + private readonly bool Japanese; + + public int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0 + Offset)); set => WriteUInt16LittleEndian(Data.AsSpan(0 + Offset), (ushort)value); } + public int SID { get => ReadUInt16LittleEndian(Data.AsSpan(2 + Offset)); set => WriteUInt16LittleEndian(Data.AsSpan(2 + Offset), (ushort)value); } + public uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(4 + Offset)); set => WriteUInt32LittleEndian(Data.AsSpan(4 + Offset), value); } + private int SpecLevel { get => ReadUInt16LittleEndian(Data.AsSpan(8 + Offset)); set => WriteUInt16LittleEndian(Data.AsSpan(8 + Offset), (ushort)value); } + + private Span Nickname_Trash => Data.AsSpan(10 + Offset, 10); + public string Nickname { get => StringConverter3.GetString(Nickname_Trash, Japanese); set => StringConverter3.SetString(Nickname_Trash, value.AsSpan(), 10, Japanese, StringConverterOption.ClearZero); } + + public int Species { get => SpecLevel & 0x1FF; set => SpecLevel = (SpecLevel & 0xFE00) | value; } + public int Level { get => SpecLevel >> 9; set => SpecLevel = (SpecLevel & 0x1FF) | (value << 9); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen3/IGen3Hoenn.cs b/PKHeX.Core/Saves/Substructures/Gen3/IGen3Hoenn.cs index e67b7b3e4..27714b7d0 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/IGen3Hoenn.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/IGen3Hoenn.cs @@ -1,19 +1,18 @@ using System.Collections.Generic; -namespace PKHeX.Core -{ - /// - /// Properties common to RS & Emerald save files. - /// - public interface IGen3Hoenn - { - RTC3 ClockInitial { get; set; } - RTC3 ClockElapsed { get; set; } - PokeBlock3Case PokeBlocks { get; set; } - DecorationInventory3 Decorations { get; } - Swarm3 Swarm { get; set; } +namespace PKHeX.Core; - IReadOnlyList DefaultSwarms { get; } - int SwarmIndex { get; set; } - } +/// +/// Properties common to RS & Emerald save files. +/// +public interface IGen3Hoenn +{ + RTC3 ClockInitial { get; set; } + RTC3 ClockElapsed { get; set; } + PokeBlock3Case PokeBlocks { get; set; } + DecorationInventory3 Decorations { get; } + Swarm3 Swarm { get; set; } + + IReadOnlyList DefaultSwarms { get; } + int SwarmIndex { get; set; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/IGen3Joyful.cs b/PKHeX.Core/Saves/Substructures/Gen3/IGen3Joyful.cs index 4b940eef1..de3e5093e 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/IGen3Joyful.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/IGen3Joyful.cs @@ -1,14 +1,13 @@ -namespace PKHeX.Core -{ - public interface IGen3Joyful - { - ushort JoyfulJumpInRow { get; set; } - ushort JoyfulJump5InRow { get; set; } - ushort JoyfulJumpGamesMaxPlayers { get; set; } - uint JoyfulJumpScore { get; set; } +namespace PKHeX.Core; - uint JoyfulBerriesScore { get; set; } - ushort JoyfulBerriesInRow { get; set; } - ushort JoyfulBerries5InRow { get; set; } - } +public interface IGen3Joyful +{ + ushort JoyfulJumpInRow { get; set; } + ushort JoyfulJump5InRow { get; set; } + ushort JoyfulJumpGamesMaxPlayers { get; set; } + uint JoyfulJumpScore { get; set; } + + uint JoyfulBerriesScore { get; set; } + ushort JoyfulBerriesInRow { get; set; } + ushort JoyfulBerries5InRow { get; set; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/IGen3Wonder.cs b/PKHeX.Core/Saves/Substructures/Gen3/IGen3Wonder.cs index 73ae06124..7cd2fe33c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/IGen3Wonder.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/IGen3Wonder.cs @@ -1,10 +1,9 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IGen3Wonder { - public interface IGen3Wonder - { - int WonderOffset { get; } - WonderNews3 WonderNews { get; set; } - WonderCard3 WonderCard { get; set; } - WonderCard3Extra WonderCardExtra { get; set; } - } + int WonderOffset { get; } + WonderNews3 WonderNews { get; set; } + WonderCard3 WonderCard { get; set; } + WonderCard3Extra WonderCardExtra { get; set; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3.cs b/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3.cs index 3340f3584..319de6d40 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3.cs @@ -1,41 +1,40 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class PokeBlock3 { - public sealed class PokeBlock3 + public const int SIZE = 7; + private readonly byte[] Data; + public PokeBlock3(byte[] data) => Data = data; + + public PokeBlock3Color Color { get => (PokeBlock3Color)Data[0]; set => Data[0] = (byte)value; } + public int Spicy { get => Data[1]; set => Data[1] = (byte)value; } + public int Dry { get => Data[2]; set => Data[2] = (byte)value; } + public int Sweet { get => Data[3]; set => Data[3] = (byte)value; } + public int Bitter { get => Data[4]; set => Data[4] = (byte)value; } + public int Sour { get => Data[5]; set => Data[5] = (byte)value; } + public int Feel { get => Data[6]; set => Data[6] = (byte)value; } + + public void Delete() { - public const int SIZE = 7; - private readonly byte[] Data; - public PokeBlock3(byte[] data) => Data = data; - - public PokeBlock3Color Color { get => (PokeBlock3Color)Data[0]; set => Data[0] = (byte)value; } - public int Spicy { get => Data[1]; set => Data[1] = (byte)value; } - public int Dry { get => Data[2]; set => Data[2] = (byte)value; } - public int Sweet { get => Data[3]; set => Data[3] = (byte)value; } - public int Bitter { get => Data[4]; set => Data[4] = (byte)value; } - public int Sour { get => Data[5]; set => Data[5] = (byte)value; } - public int Feel { get => Data[6]; set => Data[6] = (byte)value; } - - public void Delete() - { - for (int i = 0; i < Data.Length; i++) - Data[i] = 0; - } - - public void Maximize(bool create = false) - { - if (Color == 0 && !create) - return; - Spicy = Dry = Sweet = Bitter = Sour = Feel = 255; - Color = PokeBlock3Color.Gold; - } - - public void SetBlock(byte[] data, int offset) => Data.CopyTo(data, offset); - - public static PokeBlock3 GetBlock(ReadOnlySpan data, int offset) - { - byte[] result = data.Slice(offset, SIZE).ToArray(); - return new PokeBlock3(result); - } + for (int i = 0; i < Data.Length; i++) + Data[i] = 0; } -} \ No newline at end of file + + public void Maximize(bool create = false) + { + if (Color == 0 && !create) + return; + Spicy = Dry = Sweet = Bitter = Sour = Feel = 255; + Color = PokeBlock3Color.Gold; + } + + public void SetBlock(byte[] data, int offset) => Data.CopyTo(data, offset); + + public static PokeBlock3 GetBlock(ReadOnlySpan data, int offset) + { + byte[] result = data.Slice(offset, SIZE).ToArray(); + return new PokeBlock3(result); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3Case.cs b/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3Case.cs index 598115203..8a6f952f1 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3Case.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3Case.cs @@ -1,37 +1,36 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class PokeBlock3Case { - public sealed class PokeBlock3Case + private const int Count = 40; + public readonly PokeBlock3[] Blocks; + + public PokeBlock3Case(ReadOnlySpan data, int offset) { - private const int Count = 40; - public readonly PokeBlock3[] Blocks; - - public PokeBlock3Case(ReadOnlySpan data, int offset) - { - Blocks = new PokeBlock3[Count]; - for (int i = 0; i < Blocks.Length; i++) - Blocks[i] = PokeBlock3.GetBlock(data, offset + (i * PokeBlock3.SIZE)); - } - - public byte[] Write() - { - byte[] result = new byte[Count*PokeBlock3.SIZE]; - for (int i = 0; i < Blocks.Length; i++) - Blocks[i].SetBlock(result, i * PokeBlock3.SIZE); - return result; - } - - public void DeleteAll() - { - foreach (var b in Blocks) - b.Delete(); - } - - public void MaximizeAll(bool createMissing = false) - { - foreach (var b in Blocks) - b.Maximize(createMissing); - } + Blocks = new PokeBlock3[Count]; + for (int i = 0; i < Blocks.Length; i++) + Blocks[i] = PokeBlock3.GetBlock(data, offset + (i * PokeBlock3.SIZE)); } -} \ No newline at end of file + + public byte[] Write() + { + byte[] result = new byte[Count*PokeBlock3.SIZE]; + for (int i = 0; i < Blocks.Length; i++) + Blocks[i].SetBlock(result, i * PokeBlock3.SIZE); + return result; + } + + public void DeleteAll() + { + foreach (var b in Blocks) + b.Delete(); + } + + public void MaximizeAll(bool createMissing = false) + { + foreach (var b in Blocks) + b.Maximize(createMissing); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3Color.cs b/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3Color.cs index cc0cf9649..ed063f30a 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3Color.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/PokeBlock3Color.cs @@ -1,21 +1,20 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum PokeBlock3Color : byte { - public enum PokeBlock3Color : byte - { - NoBlock = 0, - Red = 1, - Blue = 2, - Pink = 3, - Green = 4, - Yellow = 5, - Purple = 6, - Indigo = 7, - Brown = 8, - LightBlue = 9, - Olive = 10, - Gray = 11, - Black = 12, - White = 13, - Gold = 14, - } + NoBlock = 0, + Red = 1, + Blue = 2, + Pink = 3, + Green = 4, + Yellow = 5, + Purple = 6, + Indigo = 7, + Brown = 8, + LightBlue = 9, + Olive = 10, + Gray = 11, + Black = 12, + White = 13, + Gold = 14, } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/RTC3.cs b/PKHeX.Core/Saves/Substructures/Gen3/RTC3.cs index 05dec15c4..353450683 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/RTC3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/RTC3.cs @@ -1,18 +1,17 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class RTC3 { - public sealed class RTC3 - { - public readonly byte[] Data; - public const int Size = 8; + public readonly byte[] Data; + public const int Size = 8; - public RTC3(byte[] data) => Data = data; + public RTC3(byte[] data) => Data = data; - public int Day { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } - public int Hour { get => Data[2]; set => Data[2] = (byte)value; } - public int Minute { get => Data[3]; set => Data[3] = (byte)value; } - public int Second { get => Data[4]; set => Data[4] = (byte)value; } - } + public int Day { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } + public int Hour { get => Data[2]; set => Data[2] = (byte)value; } + public int Minute { get => Data[3]; set => Data[3] = (byte)value; } + public int Second { get => Data[4]; set => Data[4] = (byte)value; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/Record3.cs b/PKHeX.Core/Saves/Substructures/Gen3/Record3.cs index 53f898380..0664429e5 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/Record3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/Record3.cs @@ -2,247 +2,246 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Record3 { - public sealed class Record3 + private readonly SAV3 SAV; + + public uint GetRecord(int record) => ReadUInt32LittleEndian(SAV.Large.AsSpan(GetRecordOffset(record))) ^ SAV.SecurityKey; + public void SetRecord(int record, uint value) => WriteUInt32LittleEndian(SAV.Large.AsSpan(GetRecordOffset(record)), value ^ SAV.SecurityKey); + + private int GetRecordOffset(int record) { - private readonly SAV3 SAV; + var baseOffset = GetOffset(SAV.Version); + var offset = baseOffset + (4 * record); + return offset; + } - public uint GetRecord(int record) => ReadUInt32LittleEndian(SAV.Large.AsSpan(GetRecordOffset(record))) ^ SAV.SecurityKey; - public void SetRecord(int record, uint value) => WriteUInt32LittleEndian(SAV.Large.AsSpan(GetRecordOffset(record)), value ^ SAV.SecurityKey); + public Record3(SAV3 sav) => SAV = sav; - private int GetRecordOffset(int record) + public static int GetOffset(GameVersion ver) => ver switch + { + GameVersion.RS or GameVersion.R or GameVersion.S => 0x1540, + GameVersion.E => 0x159C, + GameVersion.FRLG or GameVersion.FR or GameVersion.LG => 0x1200, + _ => throw new ArgumentException(nameof(ver)), + }; + + private static Type GetEnumType(GameVersion ver) => ver switch + { + GameVersion.RS or GameVersion.R or GameVersion.S => typeof(RecID3RuSa), + GameVersion.FRLG or GameVersion.FR or GameVersion.LG => typeof(RecID3FRLG), + GameVersion.E => typeof(RecID3Emerald), + _ => throw new ArgumentException(nameof(ver)), + }; + + public static int[] GetEnumValues(GameVersion ver) => (int[])Enum.GetValues(GetEnumType(ver)); + public static string[] GetEnumNames(GameVersion ver) => Enum.GetNames(GetEnumType(ver)); + + public static IList GetItems(SAV3 sav) + { + var ver = sav.Version; + var names = GetEnumNames(ver); + var values = GetEnumValues(ver); + + var result = new ComboItem[values.Length]; + for (int i = 0; i < result.Length; i++) { - var baseOffset = GetOffset(SAV.Version); - var offset = baseOffset + (4 * record); - return offset; + var replaced = names[i].Replace('_', ' '); + var titled = Util.ToTitleCase(replaced); + result[i] = new ComboItem(titled, values[i]); } - - public Record3(SAV3 sav) => SAV = sav; - - public static int GetOffset(GameVersion ver) => ver switch - { - GameVersion.RS or GameVersion.R or GameVersion.S => 0x1540, - GameVersion.E => 0x159C, - GameVersion.FRLG or GameVersion.FR or GameVersion.LG => 0x1200, - _ => throw new ArgumentException(nameof(ver)), - }; - - private static Type GetEnumType(GameVersion ver) => ver switch - { - GameVersion.RS or GameVersion.R or GameVersion.S => typeof(RecID3RuSa), - GameVersion.FRLG or GameVersion.FR or GameVersion.LG => typeof(RecID3FRLG), - GameVersion.E => typeof(RecID3Emerald), - _ => throw new ArgumentException(nameof(ver)), - }; - - public static int[] GetEnumValues(GameVersion ver) => (int[])Enum.GetValues(GetEnumType(ver)); - public static string[] GetEnumNames(GameVersion ver) => Enum.GetNames(GetEnumType(ver)); - - public static IList GetItems(SAV3 sav) - { - var ver = sav.Version; - var names = GetEnumNames(ver); - var values = GetEnumValues(ver); - - var result = new ComboItem[values.Length]; - for (int i = 0; i < result.Length; i++) - { - var replaced = names[i].Replace('_', ' '); - var titled = Util.ToTitleCase(replaced); - result[i] = new ComboItem(titled, values[i]); - } - return result; - } - } - - /// - /// Record IDs for and - /// - /// - /// https://github.com/pret/pokeruby/blob/f839afb24aa2c7b70e9c28a5c069aacc46993099/include/constants/game_stat.h - /// - public enum RecID3RuSa - { - SAVED_GAME = 0, - FIRST_HOF_PLAY_TIME = 1, - STARTED_TRENDS = 2, - PLANTED_BERRIES = 3, - TRADED_BIKES = 4, - STEPS = 5, - GOT_INTERVIEWED = 6, - TOTAL_BATTLES = 7, - WILD_BATTLES = 8, - TRAINER_BATTLES = 9, - ENTERED_HOF = 10, - POKEMON_CAPTURES = 11, - FISHING_CAPTURES = 12, - HATCHED_EGGS = 13, - EVOLVED_POKEMON = 14, - USED_POKECENTER = 15, - RESTED_AT_HOME = 16, - ENTERED_SAFARI_ZONE = 17, - USED_CUT = 18, - USED_ROCK_SMASH = 19, - MOVED_SECRET_BASE = 20, - POKEMON_TRADES = 21, - UNKNOWN_22 = 22, - LINK_BATTLE_WINS = 23, - LINK_BATTLE_LOSSES = 24, - LINK_BATTLE_DRAWS = 25, - USED_SPLASH = 26, - USED_STRUGGLE = 27, - SLOT_JACKPOTS = 28, - CONSECUTIVE_ROULETTE_WINS = 29, - ENTERED_BATTLE_TOWER = 30, - UNKNOWN_31 = 31, - BATTLE_TOWER_BEST_STREAK = 32, - POKEBLOCKS = 33, - POKEBLOCKS_WITH_FRIENDS = 34, - WON_LINK_CONTEST = 35, - ENTERED_CONTEST = 36, - WON_CONTEST = 37, - SHOPPED = 38, - USED_ITEMFINDER = 39, - GOT_RAINED_ON = 40, - CHECKED_POKEDEX = 41, - RECEIVED_RIBBONS = 42, - JUMPED_DOWN_LEDGES = 43, - WATCHED_TV = 44, - CHECKED_CLOCK = 45, - WON_POKEMON_LOTTERY = 46, - USED_DAYCARE = 47, - RODE_CABLE_CAR = 48, - ENTERED_HOT_SPRINGS = 49, - // NUM_GAME_STATS = 50 - } - - /// - /// Record IDs for - /// - /// - /// https://github.com/pret/pokeemerald/blob/3a40f5203baafb29f94dda8abdce6489d81635ae/include/constants/game_stat.h - /// - public enum RecID3Emerald - { - SAVED_GAME = 0, - FIRST_HOF_PLAY_TIME = 1, - STARTED_TRENDS = 2, - PLANTED_BERRIES = 3, - TRADED_BIKES = 4, - STEPS = 5, - GOT_INTERVIEWED = 6, - TOTAL_BATTLES = 7, - WILD_BATTLES = 8, - TRAINER_BATTLES = 9, - ENTERED_HOF = 10, - POKEMON_CAPTURES = 11, - FISHING_CAPTURES = 12, - HATCHED_EGGS = 13, - EVOLVED_POKEMON = 14, - USED_POKECENTER = 15, - RESTED_AT_HOME = 16, - ENTERED_SAFARI_ZONE = 17, - USED_CUT = 18, - USED_ROCK_SMASH = 19, - MOVED_SECRET_BASE = 20, - POKEMON_TRADES = 21, - UNKNOWN_22 = 22, - LINK_BATTLE_WINS = 23, - LINK_BATTLE_LOSSES = 24, - LINK_BATTLE_DRAWS = 25, - USED_SPLASH = 26, - USED_STRUGGLE = 27, - SLOT_JACKPOTS = 28, - CONSECUTIVE_ROULETTE_WINS = 29, - ENTERED_BATTLE_TOWER = 30, - UNKNOWN_31 = 31, - BATTLE_TOWER_BEST_STREAK = 32, - POKEBLOCKS = 33, - POKEBLOCKS_WITH_FRIENDS = 34, - WON_LINK_CONTEST = 35, - ENTERED_CONTEST = 36, - WON_CONTEST = 37, - SHOPPED = 38, - USED_ITEMFINDER = 39, - GOT_RAINED_ON = 40, - CHECKED_POKEDEX = 41, - RECEIVED_RIBBONS = 42, - JUMPED_DOWN_LEDGES = 43, - WATCHED_TV = 44, - CHECKED_CLOCK = 45, - WON_POKEMON_LOTTERY = 46, - USED_DAYCARE = 47, - RODE_CABLE_CAR = 48, - ENTERED_HOT_SPRINGS = 49, - UNION_WITH_FRIENDS = 50, - BERRY_CRUSH_WITH_FRIENDS = 51, - - // NUM_USED_GAME_STATS = 52, - // NUM_GAME_STATS = 64 - } - - /// - /// Record IDs for and - /// - /// - /// https://github.com/pret/pokefirered/blob/8367b0015fbf99070cc5a5244d8213420419d2c8/include/constants/game_stat.h - /// - public enum RecID3FRLG - { - SAVED_GAME = 0, - FIRST_HOF_PLAY_TIME = 1, - STARTED_TRENDS = 2, - PLANTED_BERRIES = 3, - TRADED_BIKES = 4, - STEPS = 5, - GOT_INTERVIEWED = 6, - TOTAL_BATTLES = 7, - WILD_BATTLES = 8, - TRAINER_BATTLES = 9, - ENTERED_HOF = 10, - POKEMON_CAPTURES = 11, - FISHING_CAPTURES = 12, - HATCHED_EGGS = 13, - EVOLVED_POKEMON = 14, - USED_POKECENTER = 15, - RESTED_AT_HOME = 16, - ENTERED_SAFARI_ZONE = 17, - USED_CUT = 18, - USED_ROCK_SMASH = 19, - MOVED_SECRET_BASE = 20, - POKEMON_TRADES = 21, - UNKNOWN_22 = 22, - LINK_BATTLE_WINS = 23, - LINK_BATTLE_LOSSES = 24, - LINK_BATTLE_DRAWS = 25, - USED_SPLASH = 26, - USED_STRUGGLE = 27, - SLOT_JACKPOTS = 28, - CONSECUTIVE_ROULETTE_WINS = 29, - ENTERED_BATTLE_TOWER = 30, - UNKNOWN_31 = 31, - BATTLE_TOWER_BEST_STREAK = 32, - POKEBLOCKS = 33, - POKEBLOCKS_WITH_FRIENDS = 34, - WON_LINK_CONTEST = 35, - ENTERED_CONTEST = 36, - WON_CONTEST = 37, - SHOPPED = 38, - USED_ITEMFINDER = 39, - GOT_RAINED_ON = 40, - CHECKED_POKEDEX = 41, - RECEIVED_RIBBONS = 42, - JUMPED_DOWN_LEDGES = 43, - WATCHED_TV = 44, - CHECKED_CLOCK = 45, - WON_POKEMON_LOTTERY = 46, - USED_DAYCARE = 47, - RODE_CABLE_CAR = 48, - ENTERED_HOT_SPRINGS = 49, - UNION_WITH_FRIENDS = 50, - BERRY_CRUSH_WITH_FRIENDS = 51, - - // NUM_GAME_STATS = 64, + return result; } } + +/// +/// Record IDs for and +/// +/// +/// https://github.com/pret/pokeruby/blob/f839afb24aa2c7b70e9c28a5c069aacc46993099/include/constants/game_stat.h +/// +public enum RecID3RuSa +{ + SAVED_GAME = 0, + FIRST_HOF_PLAY_TIME = 1, + STARTED_TRENDS = 2, + PLANTED_BERRIES = 3, + TRADED_BIKES = 4, + STEPS = 5, + GOT_INTERVIEWED = 6, + TOTAL_BATTLES = 7, + WILD_BATTLES = 8, + TRAINER_BATTLES = 9, + ENTERED_HOF = 10, + POKEMON_CAPTURES = 11, + FISHING_CAPTURES = 12, + HATCHED_EGGS = 13, + EVOLVED_POKEMON = 14, + USED_POKECENTER = 15, + RESTED_AT_HOME = 16, + ENTERED_SAFARI_ZONE = 17, + USED_CUT = 18, + USED_ROCK_SMASH = 19, + MOVED_SECRET_BASE = 20, + POKEMON_TRADES = 21, + UNKNOWN_22 = 22, + LINK_BATTLE_WINS = 23, + LINK_BATTLE_LOSSES = 24, + LINK_BATTLE_DRAWS = 25, + USED_SPLASH = 26, + USED_STRUGGLE = 27, + SLOT_JACKPOTS = 28, + CONSECUTIVE_ROULETTE_WINS = 29, + ENTERED_BATTLE_TOWER = 30, + UNKNOWN_31 = 31, + BATTLE_TOWER_BEST_STREAK = 32, + POKEBLOCKS = 33, + POKEBLOCKS_WITH_FRIENDS = 34, + WON_LINK_CONTEST = 35, + ENTERED_CONTEST = 36, + WON_CONTEST = 37, + SHOPPED = 38, + USED_ITEMFINDER = 39, + GOT_RAINED_ON = 40, + CHECKED_POKEDEX = 41, + RECEIVED_RIBBONS = 42, + JUMPED_DOWN_LEDGES = 43, + WATCHED_TV = 44, + CHECKED_CLOCK = 45, + WON_POKEMON_LOTTERY = 46, + USED_DAYCARE = 47, + RODE_CABLE_CAR = 48, + ENTERED_HOT_SPRINGS = 49, + // NUM_GAME_STATS = 50 +} + +/// +/// Record IDs for +/// +/// +/// https://github.com/pret/pokeemerald/blob/3a40f5203baafb29f94dda8abdce6489d81635ae/include/constants/game_stat.h +/// +public enum RecID3Emerald +{ + SAVED_GAME = 0, + FIRST_HOF_PLAY_TIME = 1, + STARTED_TRENDS = 2, + PLANTED_BERRIES = 3, + TRADED_BIKES = 4, + STEPS = 5, + GOT_INTERVIEWED = 6, + TOTAL_BATTLES = 7, + WILD_BATTLES = 8, + TRAINER_BATTLES = 9, + ENTERED_HOF = 10, + POKEMON_CAPTURES = 11, + FISHING_CAPTURES = 12, + HATCHED_EGGS = 13, + EVOLVED_POKEMON = 14, + USED_POKECENTER = 15, + RESTED_AT_HOME = 16, + ENTERED_SAFARI_ZONE = 17, + USED_CUT = 18, + USED_ROCK_SMASH = 19, + MOVED_SECRET_BASE = 20, + POKEMON_TRADES = 21, + UNKNOWN_22 = 22, + LINK_BATTLE_WINS = 23, + LINK_BATTLE_LOSSES = 24, + LINK_BATTLE_DRAWS = 25, + USED_SPLASH = 26, + USED_STRUGGLE = 27, + SLOT_JACKPOTS = 28, + CONSECUTIVE_ROULETTE_WINS = 29, + ENTERED_BATTLE_TOWER = 30, + UNKNOWN_31 = 31, + BATTLE_TOWER_BEST_STREAK = 32, + POKEBLOCKS = 33, + POKEBLOCKS_WITH_FRIENDS = 34, + WON_LINK_CONTEST = 35, + ENTERED_CONTEST = 36, + WON_CONTEST = 37, + SHOPPED = 38, + USED_ITEMFINDER = 39, + GOT_RAINED_ON = 40, + CHECKED_POKEDEX = 41, + RECEIVED_RIBBONS = 42, + JUMPED_DOWN_LEDGES = 43, + WATCHED_TV = 44, + CHECKED_CLOCK = 45, + WON_POKEMON_LOTTERY = 46, + USED_DAYCARE = 47, + RODE_CABLE_CAR = 48, + ENTERED_HOT_SPRINGS = 49, + UNION_WITH_FRIENDS = 50, + BERRY_CRUSH_WITH_FRIENDS = 51, + + // NUM_USED_GAME_STATS = 52, + // NUM_GAME_STATS = 64 +} + +/// +/// Record IDs for and +/// +/// +/// https://github.com/pret/pokefirered/blob/8367b0015fbf99070cc5a5244d8213420419d2c8/include/constants/game_stat.h +/// +public enum RecID3FRLG +{ + SAVED_GAME = 0, + FIRST_HOF_PLAY_TIME = 1, + STARTED_TRENDS = 2, + PLANTED_BERRIES = 3, + TRADED_BIKES = 4, + STEPS = 5, + GOT_INTERVIEWED = 6, + TOTAL_BATTLES = 7, + WILD_BATTLES = 8, + TRAINER_BATTLES = 9, + ENTERED_HOF = 10, + POKEMON_CAPTURES = 11, + FISHING_CAPTURES = 12, + HATCHED_EGGS = 13, + EVOLVED_POKEMON = 14, + USED_POKECENTER = 15, + RESTED_AT_HOME = 16, + ENTERED_SAFARI_ZONE = 17, + USED_CUT = 18, + USED_ROCK_SMASH = 19, + MOVED_SECRET_BASE = 20, + POKEMON_TRADES = 21, + UNKNOWN_22 = 22, + LINK_BATTLE_WINS = 23, + LINK_BATTLE_LOSSES = 24, + LINK_BATTLE_DRAWS = 25, + USED_SPLASH = 26, + USED_STRUGGLE = 27, + SLOT_JACKPOTS = 28, + CONSECUTIVE_ROULETTE_WINS = 29, + ENTERED_BATTLE_TOWER = 30, + UNKNOWN_31 = 31, + BATTLE_TOWER_BEST_STREAK = 32, + POKEBLOCKS = 33, + POKEBLOCKS_WITH_FRIENDS = 34, + WON_LINK_CONTEST = 35, + ENTERED_CONTEST = 36, + WON_CONTEST = 37, + SHOPPED = 38, + USED_ITEMFINDER = 39, + GOT_RAINED_ON = 40, + CHECKED_POKEDEX = 41, + RECEIVED_RIBBONS = 42, + JUMPED_DOWN_LEDGES = 43, + WATCHED_TV = 44, + CHECKED_CLOCK = 45, + WON_POKEMON_LOTTERY = 46, + USED_DAYCARE = 47, + RODE_CABLE_CAR = 48, + ENTERED_HOT_SPRINGS = 49, + UNION_WITH_FRIENDS = 50, + BERRY_CRUSH_WITH_FRIENDS = 51, + + // NUM_GAME_STATS = 64, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen3/Roamer3.cs b/PKHeX.Core/Saves/Substructures/Gen3/Roamer3.cs index 3856a6e9c..3b8d108da 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/Roamer3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/Roamer3.cs @@ -1,120 +1,119 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Roamer3 : IContestStats, IContestStatsMutable { - public sealed class Roamer3 : IContestStats, IContestStatsMutable + private readonly int Offset; + public bool IsGlitched { get; } + private readonly byte[] Data; + private readonly SAV3 SAV; + + public Roamer3(SAV3 sav) { - private readonly int Offset; - public bool IsGlitched { get; } - private readonly byte[] Data; - private readonly SAV3 SAV; - - public Roamer3(SAV3 sav) + Data = (SAV = sav).Large; + Offset = sav.Version switch { - Data = (SAV = sav).Large; - Offset = sav.Version switch - { - GameVersion.RS => 0x3144, - GameVersion.E => 0x31DC, - _ => 0x30D0, // FRLG - }; - IsGlitched = sav.Version != GameVersion.E; - } + GameVersion.RS => 0x3144, + GameVersion.E => 0x31DC, + _ => 0x30D0, // FRLG + }; + IsGlitched = sav.Version != GameVersion.E; + } - private uint IV32 + private uint IV32 + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value); + } + + public uint PID + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 4)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 4), value); + } + + public int Species + { + get => SpeciesConverter.GetG4Species(ReadInt16LittleEndian(Data.AsSpan(Offset + 8))); + set => WriteInt16LittleEndian(Data.AsSpan(Offset + 8), (short)SpeciesConverter.GetG3Species(value)); + } + + public int HP_Current + { + get => ReadInt16LittleEndian(Data.AsSpan(Offset + 10)); + set => WriteInt16LittleEndian(Data.AsSpan(Offset + 10), (short)value); + } + + public int CurrentLevel + { + get => Data[Offset + 12]; + set => Data[Offset + 12] = (byte)value; + } + + public int Status { get => Data[Offset + 0x0D]; set => Data[Offset + 0x0D] = (byte)value; } + + public byte CNT_Cool { get => Data[Offset + 0x0E]; set => Data[Offset + 0x0E] = value; } + public byte CNT_Beauty { get => Data[Offset + 0x0F]; set => Data[Offset + 0x0F] = value; } + public byte CNT_Cute { get => Data[Offset + 0x10]; set => Data[Offset + 0x10] = value; } + public byte CNT_Smart { get => Data[Offset + 0x11]; set => Data[Offset + 0x11] = value; } + public byte CNT_Tough { get => Data[Offset + 0x12]; set => Data[Offset + 0x12] = value; } + public byte CNT_Sheen { get => 0; set { } } + public bool Active { get => Data[Offset + 0x13] == 1; set => Data[Offset + 0x13] = value ? (byte)1 : (byte)0; } + + // Derived Properties + private int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 00)) | (uint)((value > 31 ? 31 : value) << 00)); } + private int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 05)) | (uint)((value > 31 ? 31 : value) << 05)); } + private int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 10)) | (uint)((value > 31 ? 31 : value) << 10)); } + private int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 15)) | (uint)((value > 31 ? 31 : value) << 15)); } + private int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 20)) | (uint)((value > 31 ? 31 : value) << 20)); } + private int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 25)) | (uint)((value > 31 ? 31 : value) << 25)); } + + /// + /// Roamer's IVs. + /// + public int[] IVs + { + get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; + set => SetIVs(value); + } + + public void SetIVs(ReadOnlySpan value) + { + if (value.Length != 6) + return; + IV_HP = value[0]; + IV_ATK = value[1]; + IV_DEF = value[2]; + IV_SPE = value[3]; + IV_SPA = value[4]; + IV_SPD = value[5]; + } + + /// + /// Indicates if the Roamer is shiny with the 's Trainer Details. + /// + /// PID to check for + /// Indication if the PID is shiny for the trainer. + public bool IsShiny(uint pid) + { + var xor = (ushort)(SAV.SID ^ SAV.TID ^ (pid >> 16) ^ pid); + return xor < 8; + } + + /// + /// Gets the glitched Roamer IVs, where only 1 byte of IV data is loaded when encountered. + /// + public int[] IVsGlitch + { + get { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value); - } - - public uint PID - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 4)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 4), value); - } - - public int Species - { - get => SpeciesConverter.GetG4Species(ReadInt16LittleEndian(Data.AsSpan(Offset + 8))); - set => WriteInt16LittleEndian(Data.AsSpan(Offset + 8), (short)SpeciesConverter.GetG3Species(value)); - } - - public int HP_Current - { - get => ReadInt16LittleEndian(Data.AsSpan(Offset + 10)); - set => WriteInt16LittleEndian(Data.AsSpan(Offset + 10), (short)value); - } - - public int CurrentLevel - { - get => Data[Offset + 12]; - set => Data[Offset + 12] = (byte)value; - } - - public int Status { get => Data[Offset + 0x0D]; set => Data[Offset + 0x0D] = (byte)value; } - - public byte CNT_Cool { get => Data[Offset + 0x0E]; set => Data[Offset + 0x0E] = value; } - public byte CNT_Beauty { get => Data[Offset + 0x0F]; set => Data[Offset + 0x0F] = value; } - public byte CNT_Cute { get => Data[Offset + 0x10]; set => Data[Offset + 0x10] = value; } - public byte CNT_Smart { get => Data[Offset + 0x11]; set => Data[Offset + 0x11] = value; } - public byte CNT_Tough { get => Data[Offset + 0x12]; set => Data[Offset + 0x12] = value; } - public byte CNT_Sheen { get => 0; set { } } - public bool Active { get => Data[Offset + 0x13] == 1; set => Data[Offset + 0x13] = value ? (byte)1 : (byte)0; } - - // Derived Properties - private int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 00)) | (uint)((value > 31 ? 31 : value) << 00)); } - private int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 05)) | (uint)((value > 31 ? 31 : value) << 05)); } - private int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 10)) | (uint)((value > 31 ? 31 : value) << 10)); } - private int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 15)) | (uint)((value > 31 ? 31 : value) << 15)); } - private int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 20)) | (uint)((value > 31 ? 31 : value) << 20)); } - private int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (uint)((IV32 & ~(0x1F << 25)) | (uint)((value > 31 ? 31 : value) << 25)); } - - /// - /// Roamer's IVs. - /// - public int[] IVs - { - get => new[] { IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD }; - set => SetIVs(value); - } - - public void SetIVs(ReadOnlySpan value) - { - if (value.Length != 6) - return; - IV_HP = value[0]; - IV_ATK = value[1]; - IV_DEF = value[2]; - IV_SPE = value[3]; - IV_SPA = value[4]; - IV_SPD = value[5]; - } - - /// - /// Indicates if the Roamer is shiny with the 's Trainer Details. - /// - /// PID to check for - /// Indication if the PID is shiny for the trainer. - public bool IsShiny(uint pid) - { - var xor = (ushort)(SAV.SID ^ SAV.TID ^ (pid >> 16) ^ pid); - return xor < 8; - } - - /// - /// Gets the glitched Roamer IVs, where only 1 byte of IV data is loaded when encountered. - /// - public int[] IVsGlitch - { - get - { - var ivs = IV32; // store for restoration later - IV32 &= 0xFF; // only 1 byte is loaded to the encounter - var glitch = IVs; // get glitched IVs - IV32 = ivs; // restore unglitched IVs - return glitch; - } + var ivs = IV32; // store for restoration later + IV32 &= 0xFF; // only 1 byte is loaded to the encounter + var glitch = IVs; // get glitched IVs + IV32 = ivs; // restore unglitched IVs + return glitch; } } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3.cs b/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3.cs index 87b655ff5..304fac559 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3.cs @@ -1,86 +1,85 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SecretBase3 { - public sealed class SecretBase3 + private readonly byte[] Data; + private readonly int Offset; + private bool Japanese => Language == (int) LanguageID.Japanese; + + public SecretBase3(byte[] data, int offset) { - private readonly byte[] Data; - private readonly int Offset; - private bool Japanese => Language == (int) LanguageID.Japanese; + Data = data; + Offset = offset; + } - public SecretBase3(byte[] data, int offset) - { - Data = data; - Offset = offset; - } + public int SecretBaseLocation { get => Data[Offset + 0]; set => Data[Offset + 0] = (byte) value; } - public int SecretBaseLocation { get => Data[Offset + 0]; set => Data[Offset + 0] = (byte) value; } + public int OT_Gender + { + get => (Data[Offset + 1] >> 4) & 1; + set => Data[Offset + 1] = (byte) ((Data[Offset + 1] & 0xEF) | ((value & 1) << 4)); + } - public int OT_Gender - { - get => (Data[Offset + 1] >> 4) & 1; - set => Data[Offset + 1] = (byte) ((Data[Offset + 1] & 0xEF) | (value & 1) << 4); - } + public bool BattledToday + { + get => ((Data[Offset + 1] >> 5) & 1) == 1; + set => Data[Offset + 1] = (byte)((Data[Offset + 1] & 0xDF) | ((value ? 1 : 0) << 5)); + } - public bool BattledToday - { - get => ((Data[Offset + 1] >> 5) & 1) == 1; - set => Data[Offset + 1] = (byte)((Data[Offset + 1] & 0xDF) | (value ? 1 : 0) << 5); - } + public int RegistryStatus + { + get => (Data[Offset + 1] >> 6) & 3; + set => Data[Offset + 1] = (byte)((Data[Offset + 1] & 0x3F) | ((value & 3) << 6)); + } - public int RegistryStatus - { - get => (Data[Offset + 1] >> 6) & 3; - set => Data[Offset + 1] = (byte)((Data[Offset + 1] & 0x3F) | (value & 3) << 6); - } + public string OT_Name + { + get => StringConverter3.GetString(Data.AsSpan(Offset + 2, 7), Japanese); + set => StringConverter3.SetString(Data.AsSpan(Offset + 2, 7), value.AsSpan(), 7, Japanese, StringConverterOption.ClearFF); + } - public string OT_Name - { - get => StringConverter3.GetString(Data.AsSpan(Offset + 2, 7), Japanese); - set => StringConverter3.SetString(Data.AsSpan(Offset + 2, 7), value.AsSpan(), 7, Japanese, StringConverterOption.ClearFF); - } + public uint OT_ID + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 9)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 9), value); + } - public uint OT_ID - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 9)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 9), value); - } + public int OT_Class => Data[Offset + 9] % 5; + public int Language { get => Data[Offset + 0x0D]; set => Data[Offset + 0x0D] = (byte)value; } - public int OT_Class => Data[Offset + 9] % 5; - public int Language { get => Data[Offset + 0x0D]; set => Data[Offset + 0x0D] = (byte)value; } + public ushort SecretBasesReceived + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0E)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0E), value); + } - public ushort SecretBasesReceived - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0E)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0E), value); - } + public byte TimesEntered { get => Data[Offset + 0x10]; set => Data[Offset + 0x10] = value; } + public int Unused11 { get => Data[Offset + 0x11]; set => Data[Offset + 0x11] = (byte)value; } // alignment padding - public byte TimesEntered { get => Data[Offset + 0x10]; set => Data[Offset + 0x10] = value; } - public int Unused11 { get => Data[Offset + 0x11]; set => Data[Offset + 0x11] = (byte)value; } // alignment padding + public byte[] GetDecorations() => Data.Slice(Offset + 0x12, 0x10); + public void SetDecorations(byte[] value) => value.CopyTo(Data, Offset + 0x12); - public byte[] GetDecorations() => Data.Slice(Offset + 0x12, 0x10); - public void SetDecorations(byte[] value) => value.CopyTo(Data, Offset + 0x12); + public byte[] GetDecorationCoordinates() => Data.Slice(Offset + 0x22, 0x10); + public void SetDecorationCoordinates(byte[] value) => value.CopyTo(Data, Offset + 0x22); - public byte[] GetDecorationCoordinates() => Data.Slice(Offset + 0x22, 0x10); - public void SetDecorationCoordinates(byte[] value) => value.CopyTo(Data, Offset + 0x22); + public SecretBase3Team Team + { + get => new(Data.Slice(Offset + 50, 72)); + set => value.Write().CopyTo(Data, Offset + 50); + } - public SecretBase3Team Team - { - get => new(Data.Slice(Offset + 50, 72)); - set => value.Write().CopyTo(Data, Offset + 50); - } + public int TID + { + get => (ushort)OT_ID; + set => OT_ID = (ushort)(SID | (ushort)value); + } - public int TID - { - get => (ushort)OT_ID; - set => OT_ID = (ushort)(SID | (ushort)value); - } - - public int SID - { - get => (ushort)OT_ID >> 8; - set => OT_ID = (ushort)(((ushort)value << 16) | TID); - } + public int SID + { + get => (ushort)OT_ID >> 8; + set => OT_ID = (ushort)(((ushort)value << 16) | TID); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3PKM.cs b/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3PKM.cs index af829ccd7..2e26e8bb9 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3PKM.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3PKM.cs @@ -1,32 +1,31 @@ using System; using System.Linq; -namespace PKHeX.Core -{ - public sealed class SecretBase3PKM - { - public uint PID { get; set; } - public int Move1 { get; set; } - public int Move2 { get; set; } - public int Move3 { get; set; } - public int Move4 { get; set; } - public int Species { get; set; } - public int HeldItem { get; set; } - public int Level { get; set; } - public int EVAll { get; set; } - public int[] Moves => new[] { Move1, Move2, Move3, Move4 }; +namespace PKHeX.Core; - public string Summary +public sealed class SecretBase3PKM +{ + public uint PID { get; set; } + public int Move1 { get; set; } + public int Move2 { get; set; } + public int Move3 { get; set; } + public int Move4 { get; set; } + public int Species { get; set; } + public int HeldItem { get; set; } + public int Level { get; set; } + public int EVAll { get; set; } + public int[] Moves => new[] { Move1, Move2, Move3, Move4 }; + + public string Summary + { + get { - get - { - var first = $"{Species:000} - {GameInfo.Strings.Species[Species]}"; - if (HeldItem != 0) - first += $"@ {GameInfo.Strings.Item[HeldItem]}"; - var second = $"Moves: {string.Join(" / ", Moves.Select(z => GameInfo.Strings.Move[z]))}"; - var third = $"Level: {Level}, EVs: {EVAll}, PID: {PID}"; - return first + Environment.NewLine + second + Environment.NewLine + third; - } + var first = $"{Species:000} - {GameInfo.Strings.Species[Species]}"; + if (HeldItem != 0) + first += $"@ {GameInfo.Strings.Item[HeldItem]}"; + var second = $"Moves: {string.Join(" / ", Moves.Select(z => GameInfo.Strings.Move[z]))}"; + var third = $"Level: {Level}, EVs: {EVAll}, PID: {PID}"; + return first + Environment.NewLine + second + Environment.NewLine + third; } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3Team.cs b/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3Team.cs index c2968d476..9996fb247 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3Team.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/SecretBase3Team.cs @@ -1,65 +1,64 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SecretBase3Team { - public sealed class SecretBase3Team + private const int O_PID = 0; + private const int O_Moves = 0x18; + private const int O_Species = 0x24; + private const int O_Item = 0x30; + private const int O_Level = 0x3C; + private const int O_EV = 0x42; + + private static int GetOffsetPID(int index) => O_PID + (index * 4); + private static int GetOffsetMove(int index, int move) => O_Moves + (index * 8) + (move * 2); + private static int GetOffsetSpecies(int index) => O_Species + (index * 2); + private static int GetOffsetItem(int index) => O_Item + (index * 2); + + public readonly SecretBase3PKM[] Team; + private readonly byte[] Data; + + public SecretBase3Team(byte[] data) { - private const int O_PID = 0; - private const int O_Moves = 0x18; - private const int O_Species = 0x24; - private const int O_Item = 0x30; - private const int O_Level = 0x3C; - private const int O_EV = 0x42; - - private static int GetOffsetPID(int index) => O_PID + (index * 4); - private static int GetOffsetMove(int index, int move) => O_Moves + (index * 8) + (move * 2); - private static int GetOffsetSpecies(int index) => O_Species + (index * 2); - private static int GetOffsetItem(int index) => O_Item + (index * 2); - - public readonly SecretBase3PKM[] Team; - private readonly byte[] Data; - - public SecretBase3Team(byte[] data) - { - Team = new SecretBase3PKM[6]; - for (int i = 0; i < Team.Length; i++) - Team[i] = GetPKM(i); - Data = data; - } - - public byte[] Write() - { - for (int i = 0; i < Team.Length; i++) - SetPKM(i); - return Data; - } - - private SecretBase3PKM GetPKM(int index) => new() - { - PID = ReadUInt32LittleEndian(Data.AsSpan(GetOffsetPID(index))), - Species = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetSpecies(index))), - HeldItem = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetItem(index))), - Move1 = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 0))), - Move2 = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 1))), - Move3 = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 2))), - Move4 = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 3))), - Level = Data[O_Level + index], - EVAll = Data[O_EV + index], - }; - - private void SetPKM(int index) - { - var pk = Team[index]; - WriteUInt32LittleEndian(Data.AsSpan(GetOffsetPID(index)), pk.PID); - WriteUInt16LittleEndian(Data.AsSpan(GetOffsetSpecies(index)), (ushort)pk.Species); - WriteUInt16LittleEndian(Data.AsSpan(GetOffsetItem(index)), (ushort)pk.HeldItem); - WriteUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 0)), (ushort)pk.Move1); - WriteUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 1)), (ushort)pk.Move2); - WriteUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 2)), (ushort)pk.Move3); - WriteUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 3)), (ushort)pk.Move4); - Data[O_Level + index] = (byte)pk.Level; - Data[O_EV + index] = (byte)pk.EVAll; - } + Team = new SecretBase3PKM[6]; + for (int i = 0; i < Team.Length; i++) + Team[i] = GetPKM(i); + Data = data; } -} \ No newline at end of file + + public byte[] Write() + { + for (int i = 0; i < Team.Length; i++) + SetPKM(i); + return Data; + } + + private SecretBase3PKM GetPKM(int index) => new() + { + PID = ReadUInt32LittleEndian(Data.AsSpan(GetOffsetPID(index))), + Species = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetSpecies(index))), + HeldItem = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetItem(index))), + Move1 = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 0))), + Move2 = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 1))), + Move3 = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 2))), + Move4 = ReadUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 3))), + Level = Data[O_Level + index], + EVAll = Data[O_EV + index], + }; + + private void SetPKM(int index) + { + var pk = Team[index]; + WriteUInt32LittleEndian(Data.AsSpan(GetOffsetPID(index)), pk.PID); + WriteUInt16LittleEndian(Data.AsSpan(GetOffsetSpecies(index)), (ushort)pk.Species); + WriteUInt16LittleEndian(Data.AsSpan(GetOffsetItem(index)), (ushort)pk.HeldItem); + WriteUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 0)), (ushort)pk.Move1); + WriteUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 1)), (ushort)pk.Move2); + WriteUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 2)), (ushort)pk.Move3); + WriteUInt16LittleEndian(Data.AsSpan(GetOffsetMove(index, 3)), (ushort)pk.Move4); + Data[O_Level + index] = (byte)pk.Level; + Data[O_EV + index] = (byte)pk.EVAll; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen3/StrategyMemo.cs b/PKHeX.Core/Saves/Substructures/Gen3/StrategyMemo.cs index e95ab5af3..001bf3651 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/StrategyMemo.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/StrategyMemo.cs @@ -2,137 +2,136 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class StrategyMemo { - public sealed class StrategyMemo + private readonly bool XD; + public const int SIZE_ENTRY = 12; + private readonly List Entries; + public const int MAX_COUNT = 500; + private StrategyMemoEntry? this[int Species] => Entries.Find(e => e.Species == Species); + private readonly ushort _unk; + + public StrategyMemo(bool xd = true) : this(new byte[4], 0, xd) { } + + public StrategyMemo(byte[] input, int offset, bool xd) { - private readonly bool XD; - public const int SIZE_ENTRY = 12; - private readonly List Entries; - public const int MAX_COUNT = 500; - private StrategyMemoEntry? this[int Species] => Entries.Find(e => e.Species == Species); - private readonly ushort _unk; + XD = xd; + int count = ReadUInt16BigEndian(input.AsSpan(offset)); + if (count > MAX_COUNT) + count = MAX_COUNT; + _unk = ReadUInt16BigEndian(input.AsSpan(offset + 2)); - public StrategyMemo(bool xd = true) : this(new byte[4], 0, xd) { } - - public StrategyMemo(byte[] input, int offset, bool xd) + Entries = new List(count); + for (int i = 0; i < count; i++) { - XD = xd; - int count = ReadUInt16BigEndian(input.AsSpan(offset)); - if (count > MAX_COUNT) - count = MAX_COUNT; - _unk = ReadUInt16BigEndian(input.AsSpan(offset + 2)); - - Entries = new List(count); - for (int i = 0; i < count; i++) - { - var entry = Read(input, offset, i); - Entries.Add(entry); - } - } - - private StrategyMemoEntry Read(byte[] input, int offset, int index) - { - var ofs = 4 + offset + (SIZE_ENTRY * index); - var span = input.AsSpan(ofs, SIZE_ENTRY); - var data = span.ToArray(); - return new StrategyMemoEntry(XD, data); - } - - public byte[] Write() - { - var result = new byte[4 + (Entries.Count * SIZE_ENTRY)]; - WriteInt16BigEndian(result.AsSpan(0), (short)Entries.Count); - WriteInt16BigEndian(result.AsSpan(2), (short)_unk); - - var count = Math.Min(MAX_COUNT, Entries.Count); - for (int i = 0; i < count; i++) - Entries[i].Data.CopyTo(result, 4 + (i * SIZE_ENTRY)); - return result; - } - - public StrategyMemoEntry GetEntry(int Species) - { - return this[Species] ?? new StrategyMemoEntry(XD); - } - - public void SetEntry(StrategyMemoEntry entry) - { - int index = Entries.FindIndex(ent => ent.Species == entry.Species); - if (index >= 0) - Entries[index] = entry; - else - Entries.Add(entry); + var entry = Read(input, offset, i); + Entries.Add(entry); } } - public sealed class StrategyMemoEntry + private StrategyMemoEntry Read(byte[] input, int offset, int index) { - public readonly byte[] Data; - private readonly bool XD; + var ofs = 4 + offset + (SIZE_ENTRY * index); + var span = input.AsSpan(ofs, SIZE_ENTRY); + var data = span.ToArray(); + return new StrategyMemoEntry(XD, data); + } - public StrategyMemoEntry(bool xd) : this(xd, new byte[StrategyMemo.SIZE_ENTRY]) { } + public byte[] Write() + { + var result = new byte[4 + (Entries.Count * SIZE_ENTRY)]; + WriteInt16BigEndian(result.AsSpan(0), (short)Entries.Count); + WriteInt16BigEndian(result.AsSpan(2), (short)_unk); - public StrategyMemoEntry(bool xd, byte[] data) - { - Data = data; - XD = xd; - } + var count = Math.Min(MAX_COUNT, Entries.Count); + for (int i = 0; i < count; i++) + Entries[i].Data.CopyTo(result, 4 + (i * SIZE_ENTRY)); + return result; + } - public int Species - { - get - { - int val = ReadUInt16BigEndian(Data.AsSpan(0)) & 0x1FF; - return SpeciesConverter.GetG4Species(val); - } - set - { - value = SpeciesConverter.GetG3Species(value); - int cval = ReadUInt16BigEndian(Data.AsSpan(0)); - cval &= 0xE00; value &= 0x1FF; cval |= value; - WriteUInt16BigEndian(Data.AsSpan(0x00), (ushort)cval); - } - } + public StrategyMemoEntry GetEntry(int Species) + { + return this[Species] ?? new StrategyMemoEntry(XD); + } - private bool Flag0 { get => Data[0] >> 6 == 1; set { Data[0] &= 0xBF; if (value) Data[0] |= 0x40; } } // Unused - private bool Flag1 { get => Data[0] >> 7 == 1; set { Data[0] &= 0x7F; if (value) Data[0] |= 0x80; } } // Complete Entry - public int SID { get => ReadUInt16BigEndian(Data.AsSpan(4)); set => WriteUInt16BigEndian(Data.AsSpan(4), (ushort)value); } - public int TID { get => ReadUInt16BigEndian(Data.AsSpan(6)); set => WriteUInt16BigEndian(Data.AsSpan(6), (ushort)value); } - public uint PID { get => ReadUInt32BigEndian(Data.AsSpan(8)); set => WriteUInt32BigEndian(Data.AsSpan(8), value); } - - public bool Seen - { - get - { - if (XD) return !Flag1; - return Species != 0; - } - set - { - if (XD) - Flag1 = !value; - else if (!value) - new byte[StrategyMemo.SIZE_ENTRY].CopyTo(Data, 0); - } - } - - public bool Owned - { - get - { - if (XD) return false; - return Flag0 || !Flag1; - } - set - { - if (XD) return; - if (!value) - Flag1 = true; - } - } - - public bool IsEmpty => Species == 0; - public bool Matches(int species, uint pid, int tid, int sid) => Species == species && PID == pid && TID == tid && SID == sid; + public void SetEntry(StrategyMemoEntry entry) + { + int index = Entries.FindIndex(ent => ent.Species == entry.Species); + if (index >= 0) + Entries[index] = entry; + else + Entries.Add(entry); } } + +public sealed class StrategyMemoEntry +{ + public readonly byte[] Data; + private readonly bool XD; + + public StrategyMemoEntry(bool xd) : this(xd, new byte[StrategyMemo.SIZE_ENTRY]) { } + + public StrategyMemoEntry(bool xd, byte[] data) + { + Data = data; + XD = xd; + } + + public int Species + { + get + { + int val = ReadUInt16BigEndian(Data.AsSpan(0)) & 0x1FF; + return SpeciesConverter.GetG4Species(val); + } + set + { + value = SpeciesConverter.GetG3Species(value); + int cval = ReadUInt16BigEndian(Data.AsSpan(0)); + cval &= 0xE00; value &= 0x1FF; cval |= value; + WriteUInt16BigEndian(Data.AsSpan(0x00), (ushort)cval); + } + } + + private bool Flag0 { get => Data[0] >> 6 == 1; set { Data[0] &= 0xBF; if (value) Data[0] |= 0x40; } } // Unused + private bool Flag1 { get => Data[0] >> 7 == 1; set { Data[0] &= 0x7F; if (value) Data[0] |= 0x80; } } // Complete Entry + public int SID { get => ReadUInt16BigEndian(Data.AsSpan(4)); set => WriteUInt16BigEndian(Data.AsSpan(4), (ushort)value); } + public int TID { get => ReadUInt16BigEndian(Data.AsSpan(6)); set => WriteUInt16BigEndian(Data.AsSpan(6), (ushort)value); } + public uint PID { get => ReadUInt32BigEndian(Data.AsSpan(8)); set => WriteUInt32BigEndian(Data.AsSpan(8), value); } + + public bool Seen + { + get + { + if (XD) return !Flag1; + return Species != 0; + } + set + { + if (XD) + Flag1 = !value; + else if (!value) + new byte[StrategyMemo.SIZE_ENTRY].CopyTo(Data, 0); + } + } + + public bool Owned + { + get + { + if (XD) return false; + return Flag0 || !Flag1; + } + set + { + if (XD) return; + if (!value) + Flag1 = true; + } + } + + public bool IsEmpty => Species == 0; + public bool Matches(int species, uint pid, int tid, int sid) => Species == species && PID == pid && TID == tid && SID == sid; +} diff --git a/PKHeX.Core/Saves/Substructures/Gen3/Swarm3.cs b/PKHeX.Core/Saves/Substructures/Gen3/Swarm3.cs index 7bb3c2e0c..46c886f4a 100644 --- a/PKHeX.Core/Saves/Substructures/Gen3/Swarm3.cs +++ b/PKHeX.Core/Saves/Substructures/Gen3/Swarm3.cs @@ -4,64 +4,63 @@ using static PKHeX.Core.Species; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +[StructLayout(LayoutKind.Sequential, Pack = 4, Size = SIZE)] +public sealed class Swarm3 { - [StructLayout(LayoutKind.Sequential, Pack = 4, Size = SIZE)] - public sealed class Swarm3 + public const int SIZE = 0x14; + public readonly byte[] Data; + + private Span Raw => Data.AsSpan(); + + public ushort Gen3Species { get => ReadUInt16LittleEndian(Raw); set => WriteUInt16LittleEndian(Raw, value); } + public byte MapNum { get => Raw[2]; set => Raw[2] = value; } + public byte MapGroup { get => Raw[3]; set => Raw[3] = value; } + public byte Level { get => Raw[4]; set => Raw[4] = value; } + public byte Unused1 { get => Raw[5]; set => Raw[5] = value; } + public ushort Unused2 { get => ReadUInt16LittleEndian(Raw[0x6..]); set => WriteUInt16LittleEndian(Raw[0x6..], value); } + public ushort Move1 { get => ReadUInt16LittleEndian(Raw[0x8..]); set => WriteUInt16LittleEndian(Raw[0x8..], value); } + public ushort Move2 { get => ReadUInt16LittleEndian(Raw[0xA..]); set => WriteUInt16LittleEndian(Raw[0xA..], value); } + public ushort Move3 { get => ReadUInt16LittleEndian(Raw[0xC..]); set => WriteUInt16LittleEndian(Raw[0xC..], value); } + public ushort Move4 { get => ReadUInt16LittleEndian(Raw[0xE..]); set => WriteUInt16LittleEndian(Raw[0xE..], value); } + public byte Unused3 { get => Raw[0x10]; set => Raw[0x10] = value; } + public byte EncounterProbability { get => Raw[0x11]; set => Raw[0x11] = value; } + public ushort DaysLeft { get => ReadUInt16LittleEndian(Raw[0x12..]); set => WriteUInt16LittleEndian(Raw[0x12..], value); } + + public Swarm3(byte[] data) => Data = data; + + public Swarm3(Species species, byte level, byte map, Move m1, Move m2 = 0, Move m3 = 0, Move m4 = 0) : this(new byte[SIZE]) { - public const int SIZE = 0x14; - public readonly byte[] Data; - - private Span Raw => Data.AsSpan(); - - public ushort Gen3Species { get => ReadUInt16LittleEndian(Raw); set => WriteUInt16LittleEndian(Raw, value); } - public byte MapNum { get => Raw[2]; set => Raw[2] = value; } - public byte MapGroup { get => Raw[3]; set => Raw[3] = value; } - public byte Level { get => Raw[4]; set => Raw[4] = value; } - public byte Unused1 { get => Raw[5]; set => Raw[5] = value; } - public ushort Unused2 { get => ReadUInt16LittleEndian(Raw[0x6..]); set => WriteUInt16LittleEndian(Raw[0x6..], value); } - public ushort Move1 { get => ReadUInt16LittleEndian(Raw[0x8..]); set => WriteUInt16LittleEndian(Raw[0x8..], value); } - public ushort Move2 { get => ReadUInt16LittleEndian(Raw[0xA..]); set => WriteUInt16LittleEndian(Raw[0xA..], value); } - public ushort Move3 { get => ReadUInt16LittleEndian(Raw[0xC..]); set => WriteUInt16LittleEndian(Raw[0xC..], value); } - public ushort Move4 { get => ReadUInt16LittleEndian(Raw[0xE..]); set => WriteUInt16LittleEndian(Raw[0xE..], value); } - public byte Unused3 { get => Raw[0x10]; set => Raw[0x10] = value; } - public byte EncounterProbability { get => Raw[0x11]; set => Raw[0x11] = value; } - public ushort DaysLeft { get => ReadUInt16LittleEndian(Raw[0x12..]); set => WriteUInt16LittleEndian(Raw[0x12..], value); } - - public Swarm3(byte[] data) => Data = data; - - public Swarm3(Species species, byte level, byte map, Move m1, Move m2 = 0, Move m3 = 0, Move m4 = 0) : this(new byte[SIZE]) - { - Gen3Species = (ushort)SpeciesConverter.GetG3Species((int)species); - Level = level; - MapNum = map; - Move1 = (ushort)m1; - Move2 = (ushort)m2; - Move3 = (ushort)m3; - Move4 = (ushort)m4; - EncounterProbability = 50; - DaysLeft = 1337; - } - } - - public static class Swarm3Details - { - public static readonly Swarm3[] Swarms_RS = - { - new(Surskit, 03, 0x11, Bubble, QuickAttack), // Route 102 - new(Surskit, 15, 0x1D, Bubble, QuickAttack), // Route 114 - new(Surskit, 15, 0x20, Bubble, QuickAttack), // Route 117 - new(Surskit, 28, 0x23, Bubble, QuickAttack), // Route 120 - new(Skitty, 15, 0x1F, Growl, Tackle), // Route 116 - }; - - public static readonly Swarm3[] Swarms_E = - { - new(Seedot, 03, 0x11, Bide, Harden, LeechSeed), // Route 102 - new(Nuzleaf, 15, 0x1D, Harden, Growth, NaturePower, LeechSeed), // Route 114 - new(Seedot, 13, 0x20, Harden, Growth, NaturePower, LeechSeed), // Route 117 - new(Seedot, 25, 0x23, GigaDrain, Frustration, SolarBeam, LeechSeed), // Route 120 - new(Skitty, 08, 0x1F, Growl, Tackle, TailWhip, Attract), // Route 116 - }; + Gen3Species = (ushort)SpeciesConverter.GetG3Species((int)species); + Level = level; + MapNum = map; + Move1 = (ushort)m1; + Move2 = (ushort)m2; + Move3 = (ushort)m3; + Move4 = (ushort)m4; + EncounterProbability = 50; + DaysLeft = 1337; } } + +public static class Swarm3Details +{ + public static readonly Swarm3[] Swarms_RS = + { + new(Surskit, 03, 0x11, Bubble, QuickAttack), // Route 102 + new(Surskit, 15, 0x1D, Bubble, QuickAttack), // Route 114 + new(Surskit, 15, 0x20, Bubble, QuickAttack), // Route 117 + new(Surskit, 28, 0x23, Bubble, QuickAttack), // Route 120 + new(Skitty, 15, 0x1F, Growl, Tackle), // Route 116 + }; + + public static readonly Swarm3[] Swarms_E = + { + new(Seedot, 03, 0x11, Bide, Harden, LeechSeed), // Route 102 + new(Nuzleaf, 15, 0x1D, Harden, Growth, NaturePower, LeechSeed), // Route 114 + new(Seedot, 13, 0x20, Harden, Growth, NaturePower, LeechSeed), // Route 117 + new(Seedot, 25, 0x23, GigaDrain, Frustration, SolarBeam, LeechSeed), // Route 120 + new(Skitty, 08, 0x1F, Growl, Tackle, TailWhip, Attract), // Route 116 + }; +} diff --git a/PKHeX.Core/Saves/Substructures/Gen4/HoneyTreeValue.cs b/PKHeX.Core/Saves/Substructures/Gen4/HoneyTreeValue.cs index d1701a68a..b9d1db823 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/HoneyTreeValue.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/HoneyTreeValue.cs @@ -1,42 +1,41 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Honey Tree in Sinnoh (Gen4) +/// +public sealed class HoneyTreeValue { - /// - /// Honey Tree in Sinnoh (Gen4) - /// - public sealed class HoneyTreeValue + public const int Size = 8; + + public readonly byte[] Data; + + public uint Time { get => ReadUInt32LittleEndian(Data.AsSpan(0)); set => WriteUInt32LittleEndian(Data.AsSpan(0), value); } + public int Slot { get => Data[4]; set => Data[4] = (byte)value; } + public int SubTable { get => Data[5]; set => Data[5] = (byte)value; } // offset by 1 with respect to Group + public int Group { get => Data[6]; set { Data[6] = (byte)value; SubTable = Math.Max(0, Group - 1); } } + public int Shake { get => Data[7]; set => Data[7] = (byte)value; } + + public HoneyTreeValue(byte[] data) { - public const int Size = 8; - - public readonly byte[] Data; - - public uint Time { get => ReadUInt32LittleEndian(Data.AsSpan(0)); set => WriteUInt32LittleEndian(Data.AsSpan(0), value); } - public int Slot { get => Data[4]; set => Data[4] = (byte)value; } - public int SubTable { get => Data[5]; set => Data[5] = (byte)value; } // offset by 1 with respect to Group - public int Group { get => Data[6]; set { Data[6] = (byte)value; SubTable = Math.Max(0, Group - 1); } } - public int Shake { get => Data[7]; set => Data[7] = (byte)value; } - - public HoneyTreeValue(byte[] data) - { - Data = data; - } - - public static readonly int[][] TableDP = - { - new[] {000, 000, 000, 000, 000, 000}, - new[] {265, 266, 415, 412, 420, 190}, - new[] {415, 412, 420, 190, 214, 265}, - new[] {446, 446, 446, 446, 446, 446}, - }; - - public static readonly int[][] TablePt = - { - TableDP[0], - new[] {415, 265, 412, 420, 190, 190}, - new[] {412, 420, 415, 190, 190, 214}, - TableDP[3], - }; + Data = data; } + + public static readonly int[][] TableDP = + { + new[] {000, 000, 000, 000, 000, 000}, + new[] {265, 266, 415, 412, 420, 190}, + new[] {415, 412, 420, 190, 214, 265}, + new[] {446, 446, 446, 446, 446, 446}, + }; + + public static readonly int[][] TablePt = + { + TableDP[0], + new[] {415, 265, 412, 420, 190, 190}, + new[] {412, 420, 415, 190, 190, 214}, + TableDP[3], + }; } diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Poffin4.cs b/PKHeX.Core/Saves/Substructures/Gen4/Poffin4.cs index de70832aa..9782cdaeb 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/Poffin4.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/Poffin4.cs @@ -1,65 +1,64 @@ using System; using System.ComponentModel; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Poffin4 { - public sealed class Poffin4 + public const int SIZE = 8; + public readonly byte[] Data; + + public Poffin4(byte[] data) => Data = data; + public Poffin4(byte[] data, int offset) : this(data.AsSpan(offset, SIZE).ToArray()) { - public const int SIZE = 8; - public readonly byte[] Data; - - public Poffin4(byte[] data) => Data = data; - public Poffin4(byte[] data, int offset) : this(data.AsSpan(offset, SIZE).ToArray()) - { - } - - private const string Stats = nameof(Stats); - - public PoffinFlavor4 Type{ get => (PoffinFlavor4)Data[0]; set => Data[0] = (byte)value; } - - [Category(Stats), Description("Cool Stat Boost")] - public byte BoostSpicy { get => Data[1]; set => Data[1] = value; } - - [Category(Stats), Description("Beauty Stat Boost")] - public byte BoostDry { get => Data[2]; set => Data[2] = value; } - - [Category(Stats), Description("Cute Stat Boost")] - public byte BoostSweet { get => Data[3]; set => Data[3] = value; } - - [Category(Stats), Description("Smart/Clever Stat Boost")] - public byte BoostBitter { get => Data[4]; set => Data[4] = value; } - - [Category(Stats), Description("Tough Stat Boost")] - public byte BoostSour { get => Data[5]; set => Data[5] = value; } - - [Category(Stats), Description("Sheen Stat Boost")] - public byte Smoothness { get => Data[6]; set => Data[6] = value; } - // public byte Unused { get => Data[7]; set => Data[7] = value; } - - public bool IsManyStat => Type >= PoffinFlavor4.Rich; - public PoffinFlavor4 StatPrimary => IsManyStat ? PoffinFlavor4.None : (PoffinFlavor4)(((byte) Type / 5) * 6); - public PoffinFlavor4 StatSecondary => IsManyStat ? PoffinFlavor4.None : (PoffinFlavor4)(((byte)Type % 5) * 6); - - public void SetAll(byte value = 255, PoffinFlavor4 type = PoffinFlavor4.Rich) - { - Type = type; - BoostSpicy = BoostDry = BoostSweet = BoostBitter = BoostSour = Smoothness = value; - } - - public void SetStat(int stat, byte value) - { - if ((uint) stat > 5) - throw new ArgumentOutOfRangeException(nameof(stat)); - Data[1 + stat] = value; - } - - public byte GetStat(int stat) - { - if ((uint)stat > 5) - throw new ArgumentOutOfRangeException(nameof(stat)); - return Data[1 + stat]; - } - - public void Delete() => SetAll(0, PoffinFlavor4.None); } + + private const string Stats = nameof(Stats); + + public PoffinFlavor4 Type{ get => (PoffinFlavor4)Data[0]; set => Data[0] = (byte)value; } + + [Category(Stats), Description("Cool Stat Boost")] + public byte BoostSpicy { get => Data[1]; set => Data[1] = value; } + + [Category(Stats), Description("Beauty Stat Boost")] + public byte BoostDry { get => Data[2]; set => Data[2] = value; } + + [Category(Stats), Description("Cute Stat Boost")] + public byte BoostSweet { get => Data[3]; set => Data[3] = value; } + + [Category(Stats), Description("Smart/Clever Stat Boost")] + public byte BoostBitter { get => Data[4]; set => Data[4] = value; } + + [Category(Stats), Description("Tough Stat Boost")] + public byte BoostSour { get => Data[5]; set => Data[5] = value; } + + [Category(Stats), Description("Sheen Stat Boost")] + public byte Smoothness { get => Data[6]; set => Data[6] = value; } + // public byte Unused { get => Data[7]; set => Data[7] = value; } + + public bool IsManyStat => Type >= PoffinFlavor4.Rich; + public PoffinFlavor4 StatPrimary => IsManyStat ? PoffinFlavor4.None : (PoffinFlavor4)(((byte) Type / 5) * 6); + public PoffinFlavor4 StatSecondary => IsManyStat ? PoffinFlavor4.None : (PoffinFlavor4)(((byte)Type % 5) * 6); + + public void SetAll(byte value = 255, PoffinFlavor4 type = PoffinFlavor4.Rich) + { + Type = type; + BoostSpicy = BoostDry = BoostSweet = BoostBitter = BoostSour = Smoothness = value; + } + + public void SetStat(int stat, byte value) + { + if ((uint) stat > 5) + throw new ArgumentOutOfRangeException(nameof(stat)); + Data[1 + stat] = value; + } + + public byte GetStat(int stat) + { + if ((uint)stat > 5) + throw new ArgumentOutOfRangeException(nameof(stat)); + return Data[1 + stat]; + } + + public void Delete() => SetAll(0, PoffinFlavor4.None); } diff --git a/PKHeX.Core/Saves/Substructures/Gen4/PoffinCase4.cs b/PKHeX.Core/Saves/Substructures/Gen4/PoffinCase4.cs index dceb21a9a..0b4fcb010 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/PoffinCase4.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/PoffinCase4.cs @@ -1,54 +1,53 @@ using System.Collections.Generic; using System.Diagnostics; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Storage for all the trainer has. +/// +public sealed class PoffinCase4 { - /// - /// Storage for all the trainer has. - /// - public sealed class PoffinCase4 + private readonly SAV4Sinnoh SAV; + private readonly int Offset; + public readonly Poffin4[] Poffins; + + private const int Count = 100; + + public PoffinCase4(SAV4Sinnoh sav) { - private readonly SAV4Sinnoh SAV; - private readonly int Offset; - public readonly Poffin4[] Poffins; + SAV = sav; - private const int Count = 100; - - public PoffinCase4(SAV4Sinnoh sav) - { - SAV = sav; - - Offset = sav.OFS_PoffinCase; - Poffins = ReadPoffins(SAV, Offset); - } - - public void Save() => WritePoffins(SAV, Offset, Poffins); - - private static Poffin4[] ReadPoffins(SAV4Sinnoh sav, int offset) - { - var Poffins = new Poffin4[Count]; - for (int i = 0; i < Poffins.Length; i++) - Poffins[i] = new Poffin4(sav.General, offset + (i * Poffin4.SIZE)); - return Poffins; - } - - private static void WritePoffins(SAV4Sinnoh sav, int offset, IReadOnlyList poffins) - { - Debug.Assert(poffins.Count == Count); - for (int i = 0; i < poffins.Count; i++) - sav.SetData(sav.General, poffins[i].Data, offset + (i * Poffin4.SIZE)); - } - - public void FillCase() - { - foreach (var p in Poffins) - p.SetAll(); - } - - public void DeleteAll() - { - foreach (var p in Poffins) - p.Delete(); - } + Offset = sav.OFS_PoffinCase; + Poffins = ReadPoffins(SAV, Offset); } -} \ No newline at end of file + + public void Save() => WritePoffins(SAV, Offset, Poffins); + + private static Poffin4[] ReadPoffins(SAV4Sinnoh sav, int offset) + { + var Poffins = new Poffin4[Count]; + for (int i = 0; i < Poffins.Length; i++) + Poffins[i] = new Poffin4(sav.General, offset + (i * Poffin4.SIZE)); + return Poffins; + } + + private static void WritePoffins(SAV4Sinnoh sav, int offset, IReadOnlyList poffins) + { + Debug.Assert(poffins.Count == Count); + for (int i = 0; i < poffins.Count; i++) + sav.SetData(sav.General, poffins[i].Data, offset + (i * Poffin4.SIZE)); + } + + public void FillCase() + { + foreach (var p in Poffins) + p.SetAll(); + } + + public void DeleteAll() + { + foreach (var p in Poffins) + p.Delete(); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen4/PoffinFlavor4.cs b/PKHeX.Core/Saves/Substructures/Gen4/PoffinFlavor4.cs index 98ae2b425..5d64b74f0 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/PoffinFlavor4.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/PoffinFlavor4.cs @@ -1,40 +1,39 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Different Flavors a Gen4 Poffin can have. +/// +public enum PoffinFlavor4 : byte { - /// - /// Different Flavors a Gen4 Poffin can have. - /// - public enum PoffinFlavor4 : byte - { - Spicy, - Spicy_Dry, - Spicy_Sweet, - Spicy_Bitter, - Spicy_Sour, - Dry_Spicy, - Dry, - Dry_Sweet, - Dry_Bitter, - Dry_Sour, - Sweet_Spicy, - Sweet_Dry, - Sweet, - Sweet_Bitter, - Sweet_Sour, - Bitter_Spicy, - Bitter_Dry, - Bitter_Sweet, - Bitter, - Bitter_Sour, - Sour_Spicy, - Sour_Dry, - Sour_Sweet, - Sour_Bitter, - Sour, - Rich, - Overripe, - Foul, - Mild, - FLAVOR_MAX, - None, - } -} \ No newline at end of file + Spicy, + Spicy_Dry, + Spicy_Sweet, + Spicy_Bitter, + Spicy_Sour, + Dry_Spicy, + Dry, + Dry_Sweet, + Dry_Bitter, + Dry_Sour, + Sweet_Spicy, + Sweet_Dry, + Sweet, + Sweet_Bitter, + Sweet_Sour, + Bitter_Spicy, + Bitter_Dry, + Bitter_Sweet, + Bitter, + Bitter_Sour, + Sour_Spicy, + Sour_Dry, + Sour_Sweet, + Sour_Bitter, + Sour, + Rich, + Overripe, + Foul, + Mild, + FLAVOR_MAX, + None, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen4/PokegearNumber.cs b/PKHeX.Core/Saves/Substructures/Gen4/PokegearNumber.cs index 8b547f9e9..8ae9e5ba5 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/PokegearNumber.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/PokegearNumber.cs @@ -1,85 +1,84 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Caller IDs for the HeartGold/SoulSilver Pokégear feature. +/// +public enum PokegearNumber : sbyte { - /// - /// Caller IDs for the HeartGold/SoulSilver Pokégear feature. - /// - public enum PokegearNumber : sbyte - { - None = -1, - Mother, - Professor_Elm, - Professor_Oak, - Ethan, - Lyra, - Kurt, - Daycare_Man, - Daycare_Lady, - Buena, - Bill, - Joey, - Ralph, - Liz, - Wade, - Anthony, - Bike_Shop, - Kenji, - Whitney, - Falkner, - Jack, - Chad, - Brent, - Todd, - Arnie, - Baoba, - Irwin, - Janine, - Clair, - Erika, - Misty, - Blaine, - Blue, - Chuck, - Brock, - Bugsy, - Sabrina, - Lieutenant_Surge, - Morty, - Jasmine, - Pryce, - Huey, - Gaven, - Jamie, - Reena, - Vance, - Parry, - Erin, - Beverly, - Jose, - Gina, - Alan, - Dana, - Derek, - Tully, - Tiffany, - Wilton, - Krise, - Ian, - Walt, - Alfred, - Doug, - Rob, - Kyle, - Kyler, - Tim_and_Sue, - Kenny, - Tanner, - Josh, - Torin, - Hillary, - Billy, - Kay_and_Tia, - Reese, - Aiden, - Ernest, - } + None = -1, + Mother, + Professor_Elm, + Professor_Oak, + Ethan, + Lyra, + Kurt, + Daycare_Man, + Daycare_Lady, + Buena, + Bill, + Joey, + Ralph, + Liz, + Wade, + Anthony, + Bike_Shop, + Kenji, + Whitney, + Falkner, + Jack, + Chad, + Brent, + Todd, + Arnie, + Baoba, + Irwin, + Janine, + Clair, + Erika, + Misty, + Blaine, + Blue, + Chuck, + Brock, + Bugsy, + Sabrina, + Lieutenant_Surge, + Morty, + Jasmine, + Pryce, + Huey, + Gaven, + Jamie, + Reena, + Vance, + Parry, + Erin, + Beverly, + Jose, + Gina, + Alan, + Dana, + Derek, + Tully, + Tiffany, + Wilton, + Krise, + Ian, + Walt, + Alfred, + Doug, + Rob, + Kyle, + Kyler, + Tim_and_Sue, + Kenny, + Tanner, + Josh, + Torin, + Hillary, + Billy, + Kay_and_Tia, + Reese, + Aiden, + Ernest, } diff --git a/PKHeX.Core/Saves/Substructures/Gen4/SAV4BlockDetection.cs b/PKHeX.Core/Saves/Substructures/Gen4/SAV4BlockDetection.cs index 4bfc013e4..3ab0a8526 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/SAV4BlockDetection.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/SAV4BlockDetection.cs @@ -1,52 +1,51 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Finds the index of the most recent save block for blocks. +/// +public static class SAV4BlockDetection { + private const int First = 0; + private const int Second = 1; + private const int Same = 2; + /// - /// Finds the index of the most recent save block for blocks. + /// Compares the footers of the two blocks to determine which is newest. /// - public static class SAV4BlockDetection + /// 0=Primary, 1=Secondary. + public static int CompareFooters(ReadOnlySpan data, int offset1, int offset2) { - private const int First = 0; - private const int Second = 1; - private const int Same = 2; + // Major Counters + var major1 = ReadUInt32LittleEndian(data[offset1..]); + var major2 = ReadUInt32LittleEndian(data[offset2..]); + var result1 = CompareCounters(major1, major2); + if (result1 != Same) + return result1; - /// - /// Compares the footers of the two blocks to determine which is newest. - /// - /// 0=Primary, 1=Secondary. - public static int CompareFooters(ReadOnlySpan data, int offset1, int offset2) - { - // Major Counters - var major1 = ReadUInt32LittleEndian(data[offset1..]); - var major2 = ReadUInt32LittleEndian(data[offset2..]); - var result1 = CompareCounters(major1, major2); - if (result1 != Same) - return result1; + // Minor Counters + var minor1 = ReadUInt32LittleEndian(data[(offset1 + 4)..]); + var minor2 = ReadUInt32LittleEndian(data[(offset2 + 4)..]); + var result2 = CompareCounters(minor1, minor2); + return result2 == Second ? Second : First; // Same -> First, shouldn't happen for valid saves. + } - // Minor Counters - var minor1 = ReadUInt32LittleEndian(data[(offset1 + 4)..]); - var minor2 = ReadUInt32LittleEndian(data[(offset2 + 4)..]); - var result2 = CompareCounters(minor1, minor2); - return result2 == Second ? Second : First; // Same -> First, shouldn't happen for valid saves. - } + private static int CompareCounters(uint counter1, uint counter2) + { + // Uninitialized -- only continue if a rollover case (humanly impossible) + if (counter1 == uint.MaxValue && counter2 != uint.MaxValue - 1) + return Second; + if (counter2 == uint.MaxValue && counter1 != uint.MaxValue - 1) + return First; - private static int CompareCounters(uint counter1, uint counter2) - { - // Uninitialized -- only continue if a rollover case (humanly impossible) - if (counter1 == uint.MaxValue && counter2 != uint.MaxValue - 1) - return Second; - if (counter2 == uint.MaxValue && counter1 != uint.MaxValue - 1) - return First; + // Different + if (counter1 > counter2) + return First; + if (counter1 < counter2) + return Second; - // Different - if (counter1 > counter2) - return First; - if (counter1 < counter2) - return Second; - - return Same; - } + return Same; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Seal4.cs b/PKHeX.Core/Saves/Substructures/Gen4/Seal4.cs index 8a9b3c6e9..45d34dd38 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/Seal4.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/Seal4.cs @@ -1,106 +1,105 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Ball Capsule Seals used in Generation 4 save files. +/// +/// 80 bytes, one for each seal. +public enum Seal4 { - /// - /// Ball Capsule Seals used in Generation 4 save files. - /// - /// 80 bytes, one for each seal. - public enum Seal4 - { - HeartA, - HeartB, - HeartC, - HeartD, - HeartE, - HeartF, + HeartA, + HeartB, + HeartC, + HeartD, + HeartE, + HeartF, - StarA, - StarB, - StarC, - StarD, - StarE, - StarF, + StarA, + StarB, + StarC, + StarD, + StarE, + StarF, - LineA, - LineB, - LineC, - LineD, + LineA, + LineB, + LineC, + LineD, - SmokeA, - SmokeB, - SmokeC, - SmokeD, + SmokeA, + SmokeB, + SmokeC, + SmokeD, - ElectricA, - ElectricB, - ElectricC, - ElectricD, + ElectricA, + ElectricB, + ElectricC, + ElectricD, - FoamyA, - FoamyB, - FoamyC, - FoamyD, + FoamyA, + FoamyB, + FoamyC, + FoamyD, - FireA, - FireB, - FireC, - FireD, + FireA, + FireB, + FireC, + FireD, - PartyA, - PartyB, - PartyC, - PartyD, + PartyA, + PartyB, + PartyC, + PartyD, - FloraA, - FloraB, - FloraC, - FloraD, - FloraE, - FloraF, + FloraA, + FloraB, + FloraC, + FloraD, + FloraE, + FloraF, - SongA, - SongB, - SongC, - SongD, - SongE, - SongF, - SongG, + SongA, + SongB, + SongC, + SongD, + SongE, + SongF, + SongG, - LetterA, - LetterB, - LetterC, - LetterD, - LetterE, - LetterF, - LetterG, - LetterH, - LetterI, - LetterJ, - LetterK, - LetterL, - LetterM, - LetterN, - LetterO, - LetterP, - LetterQ, - LetterR, - LetterS, - LetterT, - LetterU, - LetterV, - LetterW, - LetterX, - LetterY, - LetterZ, + LetterA, + LetterB, + LetterC, + LetterD, + LetterE, + LetterF, + LetterG, + LetterH, + LetterI, + LetterJ, + LetterK, + LetterL, + LetterM, + LetterN, + LetterO, + LetterP, + LetterQ, + LetterR, + LetterS, + LetterT, + LetterU, + LetterV, + LetterW, + LetterX, + LetterY, + LetterZ, - Shock, - Mystery, + Shock, + Mystery, - // Unreleased - Liquid, - Burst, - Twinkle, + // Unreleased + Liquid, + Burst, + Twinkle, - MAX, - MAXLEGAL = Liquid, - } + MAX, + MAXLEGAL = Liquid, } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/BoxLayout5.cs b/PKHeX.Core/Saves/Substructures/Gen5/BoxLayout5.cs index a2ce8683c..69e8614c7 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/BoxLayout5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/BoxLayout5.cs @@ -1,48 +1,47 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class BoxLayout5 : SaveBlock { - public sealed class BoxLayout5 : SaveBlock + public BoxLayout5(SAV5BW sav, int offset) : base(sav) => Offset = offset; + public BoxLayout5(SAV5B2W2 sav, int offset) : base(sav) => Offset = offset; + + public int CurrentBox { get => Data[Offset]; set => Data[Offset] = (byte)value; } + public int GetBoxNameOffset(int box) => Offset + (0x28 * box) + 4; + public int GetBoxWallpaperOffset(int box) => Offset + 0x3C4 + box; + + public int GetBoxWallpaper(int box) { - public BoxLayout5(SAV5BW sav, int offset) : base(sav) => Offset = offset; - public BoxLayout5(SAV5B2W2 sav, int offset) : base(sav) => Offset = offset; + if ((uint)box > SAV.BoxCount) + return 0; + return Data[GetBoxWallpaperOffset(box)]; + } - public int CurrentBox { get => Data[Offset]; set => Data[Offset] = (byte)value; } - public int GetBoxNameOffset(int box) => Offset + (0x28 * box) + 4; - public int GetBoxWallpaperOffset(int box) => Offset + 0x3C4 + box; + public void SetBoxWallpaper(int box, int value) + { + if ((uint)box > SAV.BoxCount) + return; + Data[GetBoxWallpaperOffset(box)] = (byte)value; + } - public int GetBoxWallpaper(int box) - { - if ((uint)box > SAV.BoxCount) - return 0; - return Data[GetBoxWallpaperOffset(box)]; - } + private Span GetBoxNameSpan(int box) => Data.AsSpan(GetBoxNameOffset(box), 0x14); - public void SetBoxWallpaper(int box, int value) - { - if ((uint)box > SAV.BoxCount) - return; - Data[GetBoxWallpaperOffset(box)] = (byte)value; - } + public string GetBoxName(int box) + { + if (box >= SAV.BoxCount) + return string.Empty; + return SAV.GetString(GetBoxNameSpan(box)); + } - private Span GetBoxNameSpan(int box) => Data.AsSpan(GetBoxNameOffset(box), 0x14); + public void SetBoxName(int box, string value) + { + SAV.SetString(GetBoxNameSpan(box), value.AsSpan(), 13, StringConverterOption.ClearZero); + } - public string GetBoxName(int box) - { - if (box >= SAV.BoxCount) - return string.Empty; - return SAV.GetString(GetBoxNameSpan(box)); - } - - public void SetBoxName(int box, string value) - { - SAV.SetString(GetBoxNameSpan(box), value.AsSpan(), 13, StringConverterOption.ClearZero); - } - - public string this[int i] - { - get => GetBoxName(i); - set => SetBoxName(i, value); - } + public string this[int i] + { + get => GetBoxName(i); + set => SetBoxName(i, value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/CGearBackground.cs b/PKHeX.Core/Saves/Substructures/Gen5/CGearBackground.cs index f1e0d3a39..29c0b52c0 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/CGearBackground.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/CGearBackground.cs @@ -1,530 +1,529 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 C-Gear Background Image +/// +public sealed class CGearBackground { + public const string Extension = "cgb"; + public const string Filter = "C-Gear Background|*.cgb"; + public const int Width = 256; // px + public const int Height = 192; // px + public const int SIZE_CGB = 0x2600; + private const int ColorCount = 0x10; + private const int TileSize = 8; + private const int TileCount = (Width / TileSize) * (Height / TileSize); // 0x300 + + /* CGearBackground Documentation + * CGearBackgrounds (.cgb) are tiled images. + * Tiles are 8x8, and serve as a tileset for building the image. + * The first 0x2000 bytes are the tile building region. + * A tile to have two pixels defined in one byte of space. + * A tile takes up 64 pixels, 32 bytes, 0x20 chunks. + * The last tile is actually the colors used in the image (16bit). + * Only 16 colors can be used for the entire image. + * 255 tiles may be chosen from, as (0x2000-(0x20))/0x20 = 0xFF + * The last 0x600 bytes are the tiles used. + * 256/8 = 32, 192/8 = 24 + * 32 * 24 = 0x300 + * The tiles are chosen based on the 16bit index of the tile. + * 0x300 * 2 = 0x600! + * + * CGearBackgrounds tilemap (when stored on BW) employs some obfuscation. + * BW obfuscates by adding 0xA0A0. + * The obfuscated number is then tweaked by adding 15*(i/17) + * To reverse, use a similar reverse calculation + * PSK files are basically raw game rips (obfuscated) + * CGB files are un-obfuscated / B2W2. + * Due to BW and B2W2 using different obfuscation adds, PSK files are incompatible between the versions. + */ + + public CGearBackground(byte[] data) + { + if (data.Length != SIZE_CGB) + throw new ArgumentException(nameof(data)); + + // decode for easy handling + if (!IsCGB(data)) + { + _psk = data; + data = PSKtoCGB(data); + } + else + { + _cgb = data; + } + + var Region1 = data.AsSpan(0, 0x1FE0); + var ColorData = data.Slice(0x1FE0, 0x20); + var Region2 = data.Slice(0x2000, 0x600); + + ColorPalette = new int[ColorCount]; + for (int i = 0; i < ColorPalette.Length; i++) + ColorPalette[i] = GetRGB555_16(ReadUInt16LittleEndian(ColorData.AsSpan(i * 2))); + + Tiles = new Tile[0xFF]; + for (int i = 0; i < Tiles.Length; i++) + { + byte[] tiledata = Region1.Slice(i * Tile.SIZE_TILE, Tile.SIZE_TILE).ToArray(); + Tiles[i] = new Tile(tiledata); + Tiles[i].SetTile(ColorPalette); + } + + Map = new TileMap(Region2); + } + + public readonly int[] ColorPalette; + public readonly Tile[] Tiles; + public readonly TileMap Map; + + // Track the original data + private readonly byte[]? _cgb; + private readonly byte[]? _psk; + /// - /// Generation 5 C-Gear Background Image + /// Writes the data to a binary form. /// - public sealed class CGearBackground + /// True if the destination game is , otherwise false for . + /// Serialized skin data for writing to the save file + public byte[] GetSkin(bool B2W2) => B2W2 ? GetCGB() : GetPSK(); + + private byte[] GetCGB() => _cgb ?? Write(); + private byte[] GetPSK() => _psk ?? CGBtoPSK(Write()); + + private byte[] Write() { - public const string Extension = "cgb"; - public const string Filter = "C-Gear Background|*.cgb"; - public const int Width = 256; // px - public const int Height = 192; // px - public const int SIZE_CGB = 0x2600; - private const int ColorCount = 0x10; - private const int TileSize = 8; - private const int TileCount = (Width / TileSize) * (Height / TileSize); // 0x300 + byte[] data = new byte[SIZE_CGB]; + for (int i = 0; i < Tiles.Length; i++) + Array.Copy(Tiles[i].Write(), 0, data, i*Tile.SIZE_TILE, Tile.SIZE_TILE); - /* CGearBackground Documentation - * CGearBackgrounds (.cgb) are tiled images. - * Tiles are 8x8, and serve as a tileset for building the image. - * The first 0x2000 bytes are the tile building region. - * A tile to have two pixels defined in one byte of space. - * A tile takes up 64 pixels, 32 bytes, 0x20 chunks. - * The last tile is actually the colors used in the image (16bit). - * Only 16 colors can be used for the entire image. - * 255 tiles may be chosen from, as (0x2000-(0x20))/0x20 = 0xFF - * The last 0x600 bytes are the tiles used. - * 256/8 = 32, 192/8 = 24 - * 32 * 24 = 0x300 - * The tiles are chosen based on the 16bit index of the tile. - * 0x300 * 2 = 0x600! - * - * CGearBackgrounds tilemap (when stored on BW) employs some obfuscation. - * BW obfuscates by adding 0xA0A0. - * The obfuscated number is then tweaked by adding 15*(i/17) - * To reverse, use a similar reverse calculation - * PSK files are basically raw game rips (obfuscated) - * CGB files are un-obfuscated / B2W2. - * Due to BW and B2W2 using different obfuscation adds, PSK files are incompatible between the versions. - */ - - public CGearBackground(byte[] data) + for (int i = 0; i < ColorPalette.Length; i++) { - if (data.Length != SIZE_CGB) - throw new ArgumentException(nameof(data)); - - // decode for easy handling - if (!IsCGB(data)) - { - _psk = data; - data = PSKtoCGB(data); - } - else - { - _cgb = data; - } - - var Region1 = data.AsSpan(0, 0x1FE0); - var ColorData = data.Slice(0x1FE0, 0x20); - var Region2 = data.Slice(0x2000, 0x600); - - ColorPalette = new int[ColorCount]; - for (int i = 0; i < ColorPalette.Length; i++) - ColorPalette[i] = GetRGB555_16(ReadUInt16LittleEndian(ColorData.AsSpan(i * 2))); - - Tiles = new Tile[0xFF]; - for (int i = 0; i < Tiles.Length; i++) - { - byte[] tiledata = Region1.Slice(i * Tile.SIZE_TILE, Tile.SIZE_TILE).ToArray(); - Tiles[i] = new Tile(tiledata); - Tiles[i].SetTile(ColorPalette); - } - - Map = new TileMap(Region2); + var value = GetRGB555(ColorPalette[i]); + var span = data.AsSpan(0x1FE0 + (i * 2)); + WriteUInt16LittleEndian(span, value); } - public readonly int[] ColorPalette; - public readonly Tile[] Tiles; - public readonly TileMap Map; + Array.Copy(Map.Write(), 0, data, 0x2000, 0x600); - // Track the original data - private readonly byte[]? _cgb; - private readonly byte[]? _psk; + return data; + } - /// - /// Writes the data to a binary form. - /// - /// True if the destination game is , otherwise false for . - /// Serialized skin data for writing to the save file - public byte[] GetSkin(bool B2W2) => B2W2 ? GetCGB() : GetPSK(); + private static bool IsCGB(IReadOnlyList data) + { + if (data.Count != SIZE_CGB) + return false; - private byte[] GetCGB() => _cgb ?? Write(); - private byte[] GetPSK() => _psk ?? CGBtoPSK(Write()); - - private byte[] Write() + // check odd bytes for anything not rotation flag + for (int i = 0x2000; i < 0x2600; i += 2) { - byte[] data = new byte[SIZE_CGB]; - for (int i = 0; i < Tiles.Length; i++) - Array.Copy(Tiles[i].Write(), 0, data, i*Tile.SIZE_TILE, Tile.SIZE_TILE); - - for (int i = 0; i < ColorPalette.Length; i++) - { - var value = GetRGB555(ColorPalette[i]); - var span = data.AsSpan(0x1FE0 + (i * 2)); - WriteUInt16LittleEndian(span, value); - } - - Array.Copy(Map.Write(), 0, data, 0x2000, 0x600); - - return data; - } - - private static bool IsCGB(IReadOnlyList data) - { - if (data.Count != SIZE_CGB) + if ((data[i + 1] & ~0b1100) != 0) return false; - - // check odd bytes for anything not rotation flag - for (int i = 0x2000; i < 0x2600; i += 2) - { - if ((data[i + 1] & ~0b1100) != 0) - return false; - } - return true; } + return true; + } - private static byte[] CGBtoPSK(ReadOnlySpan cgb) + private static byte[] CGBtoPSK(ReadOnlySpan cgb) + { + byte[] psk = cgb.ToArray(); + for (int i = 0x2000; i < 0x2600; i += 2) { - byte[] psk = cgb.ToArray(); - for (int i = 0x2000; i < 0x2600; i += 2) - { - var span = psk.AsSpan(i); - var tileVal = ReadUInt16LittleEndian(span); - int val = GetPSKValue(tileVal); - WriteUInt16LittleEndian(span, (ushort)val); - } - return psk; + var span = psk.AsSpan(i); + var tileVal = ReadUInt16LittleEndian(span); + int val = GetPSKValue(tileVal); + WriteUInt16LittleEndian(span, (ushort)val); } + return psk; + } - private static int GetPSKValue(ushort val) + private static int GetPSKValue(ushort val) + { + int rot = val & 0xFF00; + int tile = val & 0x00FF; + if (tile == 0xFF) // invalid tile? + tile = 0; + + return tile + (15 * (tile / 17)) + 0xA0A0 + rot; + } + + private static byte[] PSKtoCGB(ReadOnlySpan psk) + { + byte[] cgb = psk.ToArray(); + for (int i = 0x2000; i < 0x2600; i += 2) { - int rot = val & 0xFF00; - int tile = val & 0x00FF; - if (tile == 0xFF) // invalid tile? + int val = ReadUInt16LittleEndian(psk[i..]); + int index = ValToIndex(val); + + byte tile = (byte)index; + byte rot = (byte)(index >> 8); + if (tile == 0xFF) tile = 0; - - return tile + (15 * (tile / 17)) + 0xA0A0 + rot; - } - - private static byte[] PSKtoCGB(ReadOnlySpan psk) - { - byte[] cgb = psk.ToArray(); - for (int i = 0x2000; i < 0x2600; i += 2) - { - int val = ReadUInt16LittleEndian(psk[i..]); - int index = ValToIndex(val); - - byte tile = (byte)index; - byte rot = (byte)(index >> 8); - if (tile == 0xFF) - tile = 0; - cgb[i] = tile; - cgb[i + 1] = rot; - } - return cgb; - } - - private static int ValToIndex(int val) - { - var trunc = (val & 0x3FF); - if (trunc is < 0xA0 or > 0x280) - return (val & 0x5C00) | 0xFF; - return ((val % 0x20) + (17 * ((trunc - 0xA0) / 0x20))) | (val & 0x5C00); - } - - private static byte Convert8to5(int colorval) - { - byte i = 0; - while (colorval > Convert5To8[i]) i++; - return i; - } - - private static int GetRGB555_32(int val) - { - var R = (val >> 00) & 0xFF; - var G = (val >> 08) & 0xFF; - var B = (val >> 16) & 0xFF; - return 0xFF << 24 | B << 16 | G << 8 | R; - } - - private static int GetRGB555_16(ushort val) - { - int R = (val >> 0) & 0x1F; - int G = (val >> 5) & 0x1F; - int B = (val >> 10) & 0x1F; - - R = Convert5To8[R]; - G = Convert5To8[G]; - B = Convert5To8[B]; - - return 0xFF << 24 | R << 16 | G << 8 | B; - } - - private static ushort GetRGB555(int v) - { - var R = (byte)(v >> 16); - var G = (byte)(v >> 8); - var B = (byte)(v >> 0); - - int val = 0; - val |= Convert8to5(R) << 0; - val |= Convert8to5(G) << 5; - val |= Convert8to5(B) << 10; - return (ushort)val; - } - - private static readonly int[] Convert5To8 = { 0x00,0x08,0x10,0x18,0x20,0x29,0x31,0x39, - 0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B, - 0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD, - 0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF }; - - /// - /// Creates a new C-Gear Background object from an input image data byte array - /// - /// Image data - /// Bytes per pixel - /// new C-Gear Background object - public static CGearBackground GetBackground(byte[] data, int bpp = 4) - { - int[] pixels = new int[data.Length / bpp]; - Buffer.BlockCopy(data, 0, pixels, 0, data.Length); - var colors = GetColorData(pixels); - - var Palette = colors.Distinct().ToArray(); - if (Palette.Length > ColorCount) - throw new ArgumentException($"Too many unique colors. Expected <= 16, got {Palette.Length}"); - - var tiles = GetTiles(colors, Palette); - GetTileList(tiles, out List tilelist, out TileMap tm); - if (tilelist.Count >= 0xFF) - throw new ArgumentException($"Too many unique tiles. Expected < 256, received {tilelist.Count}."); - - // Finished! - return new CGearBackground(Palette, tilelist.ToArray(), tm); - } - - private static int[] GetColorData(IReadOnlyList pixels) - { - int[] colors = new int[pixels.Count]; - for (int i = 0; i < pixels.Count; i++) - colors[i] = GetRGB555_32(pixels[i]); - return colors; - } - - private static Tile[] GetTiles(IReadOnlyList colors, int[] palette) - { - var tiles = new Tile[TileCount]; - for (int i = 0; i < tiles.Length; i++) - tiles[i] = GetTile(colors, palette, i); - return tiles; - } - - private static Tile GetTile(IReadOnlyList colors, int[] palette, int tileIndex) - { - int x = (tileIndex * 8) % Width; - int y = 8 * ((tileIndex * 8) / Width); - - var t = new Tile(); - for (uint ix = 0; ix < 8; ix++) - { - for (uint iy = 0; iy < 8; iy++) - { - int index = ((int) (y + iy) * Width) + (int) (x + ix); - int c = colors[index]; - - t.ColorChoices[(ix % 8) + (iy * 8)] = Array.IndexOf(palette, c); - } - } - - t.SetTile(palette); - return t; - } - - private static void GetTileList(IReadOnlyList tiles, out List tilelist, out TileMap tm) - { - tilelist = new List { tiles[0] }; - tm = new TileMap(new byte[2 * Width * Height / 64]); - - // start at 1 as the 0th tile is always non-duplicate - for (int i = 1; i < tm.TileChoices.Length; i++) - FindPossibleRotatedTile(tiles[i], tilelist, tm, i); - } - - private static void FindPossibleRotatedTile(Tile t, IList tilelist, TileMap tm, int tileIndex) - { - // Test all tiles currently in the list - for (int j = 0; j < tilelist.Count; j++) - { - int rotVal = t.GetRotationValue(tilelist[j].ColorChoices); - if (rotVal <= -1) - continue; - tm.TileChoices[tileIndex] = j; - tm.Rotations[tileIndex] = rotVal; - return; - } - - // No tile found, add to list - tilelist.Add(t); - tm.TileChoices[tileIndex] = tilelist.Count - 1; - tm.Rotations[tileIndex] = 0; - } - - private CGearBackground(int[] Palette, Tile[] tilelist, TileMap tm) - { - Map = tm; - ColorPalette = Palette; - Tiles = tilelist; - } - - public byte[] GetImageData() - { - byte[] data = new byte[4 * Width * Height]; - for (int i = 0; i < Map.TileChoices.Length; i++) - { - int x = (i * 8) % Width; - int y = 8 * ((i * 8) / Width); - var choice = Map.TileChoices[i] % (Tiles.Length + 1); - var tile = Tiles[choice]; - var tileData = tile.Rotate(Map.Rotations[i]); - for (int iy = 0; iy < 8; iy++) - { - int src = iy * (4 * 8); - int dest = (((y+iy) * Width) + x) * 4; - Array.Copy(tileData, src, data, dest, 4*8); - } - } - return data; + cgb[i] = tile; + cgb[i + 1] = rot; } + return cgb; } - public sealed class Tile + private static int ValToIndex(int val) { - internal const int SIZE_TILE = 0x20; - private const int TileWidth = 8; - private const int TileHeight = 8; - internal readonly int[] ColorChoices; - private byte[] PixelData; - private byte[]? PixelDataX; - private byte[]? PixelDataY; - - internal Tile() : this(new byte[SIZE_TILE]) { } - - internal Tile(byte[] data) - { - if (data.Length != SIZE_TILE) - throw new ArgumentException(nameof(data)); - - ColorChoices = new int[TileWidth * TileHeight]; - for (int i = 0; i < data.Length; i++) - { - var ofs = i * 2; - ColorChoices[ofs + 0] = data[i] & 0xF; - ColorChoices[ofs + 1] = data[i] >> 4; - } - PixelData = Array.Empty(); - } - - internal void SetTile(int[] Palette) => PixelData = GetTileData(Palette); - - private byte[] GetTileData(IReadOnlyList Palette) - { - const int pixels = TileWidth * TileHeight; - byte[] data = new byte[pixels * 4]; - for (int i = 0; i < pixels; i++) - { - var choice = ColorChoices[i]; - var value = Palette[choice]; - var span = data.AsSpan(4 * i, 4); - WriteInt32LittleEndian(span, value); - } - return data; - } - - internal byte[] Write() - { - byte[] data = new byte[SIZE_TILE]; - for (int i = 0; i < data.Length; i++) - { - var span = ColorChoices.AsSpan(i * 2, 2); - data[i] = (byte)((span[0] & 0xF) | ((span[1] & 0xF) << 4)); - } - return data; - } - - public byte[] Rotate(int rotFlip) - { - if (rotFlip == 0) - return PixelData; - if ((rotFlip & 4) > 0) - return PixelDataX ??= FlipX(PixelData, TileWidth); - if ((rotFlip & 8) > 0) - return PixelDataY ??= FlipY(PixelData, TileHeight); - return PixelData; - } - - private static byte[] FlipX(IReadOnlyList data, int width, int bpp = 4) - { - byte[] result = new byte[data.Count]; - int pixels = data.Count / bpp; - for (int i = 0; i < pixels; i++) - { - int x = i % width; - int y = i / width; - - x = width - x - 1; // flip x - int dest = ((y * width) + x) * bpp; - - var o = 4 * i; - result[dest + 0] = data[o + 0]; - result[dest + 1] = data[o + 1]; - result[dest + 2] = data[o + 2]; - result[dest + 3] = data[o + 3]; - } - return result; - } - - private static byte[] FlipY(IReadOnlyList data, int height, int bpp = 4) - { - byte[] result = new byte[data.Count]; - int pixels = data.Count / bpp; - int width = pixels / height; - for (int i = 0; i < pixels; i++) - { - int x = i % width; - int y = i / width; - - y = height - y - 1; // flip x - int dest = ((y * width) + x) * bpp; - - var o = 4 * i; - result[dest + 0] = data[o + 0]; - result[dest + 1] = data[o + 1]; - result[dest + 2] = data[o + 2]; - result[dest + 3] = data[o + 3]; - } - return result; - } - - internal int GetRotationValue(ReadOnlySpan tileColors) - { - // Check all rotation types - if (tileColors.SequenceEqual(ColorChoices)) - return 0; - - if (IsMirrorX(tileColors)) - return 4; - if (IsMirrorY(tileColors)) - return 8; - if (IsMirrorXY(tileColors)) - return 12; - - return -1; - } - - private bool IsMirrorX(ReadOnlySpan tileColors) - { - for (int i = 0; i < 64; i++) - { - if (ColorChoices[(7 - (i & 7)) + (8 * (i / 8))] != tileColors[i]) - return false; - } - - return true; - } - - private bool IsMirrorY(ReadOnlySpan tileColors) - { - for (int i = 0; i < 64; i++) - { - if (ColorChoices[64 - (8 * (1 + (i / 8))) + (i & 7)] != tileColors[i]) - return false; - } - - return true; - } - - private bool IsMirrorXY(ReadOnlySpan tileColors) - { - for (int i = 0; i < 64; i++) - { - if (ColorChoices[63 - i] != tileColors[i]) - return false; - } - - return true; - } + var trunc = (val & 0x3FF); + if (trunc is < 0xA0 or > 0x280) + return (val & 0x5C00) | 0xFF; + return ((val % 0x20) + (17 * ((trunc - 0xA0) / 0x20))) | (val & 0x5C00); } - public sealed class TileMap + private static byte Convert8to5(int colorval) { - public readonly int[] TileChoices; - public readonly int[] Rotations; + byte i = 0; + while (colorval > Convert5To8[i]) i++; + return i; + } - internal TileMap(byte[] data) + private static int GetRGB555_32(int val) + { + var R = (val >> 00) & 0xFF; + var G = (val >> 08) & 0xFF; + var B = (val >> 16) & 0xFF; + return (0xFF << 24) | (B << 16) | (G << 8) | R; + } + + private static int GetRGB555_16(ushort val) + { + int R = (val >> 0) & 0x1F; + int G = (val >> 5) & 0x1F; + int B = (val >> 10) & 0x1F; + + R = Convert5To8[R]; + G = Convert5To8[G]; + B = Convert5To8[B]; + + return (0xFF << 24) | (R << 16) | (G << 8) | B; + } + + private static ushort GetRGB555(int v) + { + var R = (byte)(v >> 16); + var G = (byte)(v >> 8); + var B = (byte)(v >> 0); + + int val = 0; + val |= Convert8to5(R) << 0; + val |= Convert8to5(G) << 5; + val |= Convert8to5(B) << 10; + return (ushort)val; + } + + private static readonly int[] Convert5To8 = { 0x00,0x08,0x10,0x18,0x20,0x29,0x31,0x39, + 0x41,0x4A,0x52,0x5A,0x62,0x6A,0x73,0x7B, + 0x83,0x8B,0x94,0x9C,0xA4,0xAC,0xB4,0xBD, + 0xC5,0xCD,0xD5,0xDE,0xE6,0xEE,0xF6,0xFF }; + + /// + /// Creates a new C-Gear Background object from an input image data byte array + /// + /// Image data + /// Bytes per pixel + /// new C-Gear Background object + public static CGearBackground GetBackground(byte[] data, int bpp = 4) + { + int[] pixels = new int[data.Length / bpp]; + Buffer.BlockCopy(data, 0, pixels, 0, data.Length); + var colors = GetColorData(pixels); + + var Palette = colors.Distinct().ToArray(); + if (Palette.Length > ColorCount) + throw new ArgumentException($"Too many unique colors. Expected <= 16, got {Palette.Length}"); + + var tiles = GetTiles(colors, Palette); + GetTileList(tiles, out List tilelist, out TileMap tm); + if (tilelist.Count >= 0xFF) + throw new ArgumentException($"Too many unique tiles. Expected < 256, received {tilelist.Count}."); + + // Finished! + return new CGearBackground(Palette, tilelist.ToArray(), tm); + } + + private static int[] GetColorData(IReadOnlyList pixels) + { + int[] colors = new int[pixels.Count]; + for (int i = 0; i < pixels.Count; i++) + colors[i] = GetRGB555_32(pixels[i]); + return colors; + } + + private static Tile[] GetTiles(IReadOnlyList colors, int[] palette) + { + var tiles = new Tile[TileCount]; + for (int i = 0; i < tiles.Length; i++) + tiles[i] = GetTile(colors, palette, i); + return tiles; + } + + private static Tile GetTile(IReadOnlyList colors, int[] palette, int tileIndex) + { + int x = (tileIndex * 8) % Width; + int y = 8 * ((tileIndex * 8) / Width); + + var t = new Tile(); + for (uint ix = 0; ix < 8; ix++) { - TileChoices = new int[data.Length / 2]; - Rotations = new int[data.Length / 2]; - for (int i = 0; i < data.Length; i += 2) + for (uint iy = 0; iy < 8; iy++) { - TileChoices[i / 2] = data[i]; - Rotations[i / 2] = data[i + 1]; + int index = ((int) (y + iy) * Width) + (int) (x + ix); + int c = colors[index]; + + t.ColorChoices[(ix % 8) + (iy * 8)] = Array.IndexOf(palette, c); } } - internal byte[] Write() + t.SetTile(palette); + return t; + } + + private static void GetTileList(IReadOnlyList tiles, out List tilelist, out TileMap tm) + { + tilelist = new List { tiles[0] }; + tm = new TileMap(new byte[2 * Width * Height / 64]); + + // start at 1 as the 0th tile is always non-duplicate + for (int i = 1; i < tm.TileChoices.Length; i++) + FindPossibleRotatedTile(tiles[i], tilelist, tm, i); + } + + private static void FindPossibleRotatedTile(Tile t, IList tilelist, TileMap tm, int tileIndex) + { + // Test all tiles currently in the list + for (int j = 0; j < tilelist.Count; j++) { - byte[] data = new byte[TileChoices.Length * 2]; - for (int i = 0; i < data.Length; i += 2) - { - data[i] = (byte)TileChoices[i / 2]; - data[i + 1] = (byte)Rotations[i / 2]; - } - return data; + int rotVal = t.GetRotationValue(tilelist[j].ColorChoices); + if (rotVal <= -1) + continue; + tm.TileChoices[tileIndex] = j; + tm.Rotations[tileIndex] = rotVal; + return; } + + // No tile found, add to list + tilelist.Add(t); + tm.TileChoices[tileIndex] = tilelist.Count - 1; + tm.Rotations[tileIndex] = 0; + } + + private CGearBackground(int[] Palette, Tile[] tilelist, TileMap tm) + { + Map = tm; + ColorPalette = Palette; + Tiles = tilelist; + } + + public byte[] GetImageData() + { + byte[] data = new byte[4 * Width * Height]; + for (int i = 0; i < Map.TileChoices.Length; i++) + { + int x = (i * 8) % Width; + int y = 8 * ((i * 8) / Width); + var choice = Map.TileChoices[i] % (Tiles.Length + 1); + var tile = Tiles[choice]; + var tileData = tile.Rotate(Map.Rotations[i]); + for (int iy = 0; iy < 8; iy++) + { + int src = iy * (4 * 8); + int dest = (((y+iy) * Width) + x) * 4; + Array.Copy(tileData, src, data, dest, 4*8); + } + } + return data; + } +} + +public sealed class Tile +{ + internal const int SIZE_TILE = 0x20; + private const int TileWidth = 8; + private const int TileHeight = 8; + internal readonly int[] ColorChoices; + private byte[] PixelData; + private byte[]? PixelDataX; + private byte[]? PixelDataY; + + internal Tile() : this(new byte[SIZE_TILE]) { } + + internal Tile(byte[] data) + { + if (data.Length != SIZE_TILE) + throw new ArgumentException(nameof(data)); + + ColorChoices = new int[TileWidth * TileHeight]; + for (int i = 0; i < data.Length; i++) + { + var ofs = i * 2; + ColorChoices[ofs + 0] = data[i] & 0xF; + ColorChoices[ofs + 1] = data[i] >> 4; + } + PixelData = Array.Empty(); + } + + internal void SetTile(int[] Palette) => PixelData = GetTileData(Palette); + + private byte[] GetTileData(IReadOnlyList Palette) + { + const int pixels = TileWidth * TileHeight; + byte[] data = new byte[pixels * 4]; + for (int i = 0; i < pixels; i++) + { + var choice = ColorChoices[i]; + var value = Palette[choice]; + var span = data.AsSpan(4 * i, 4); + WriteInt32LittleEndian(span, value); + } + return data; + } + + internal byte[] Write() + { + byte[] data = new byte[SIZE_TILE]; + for (int i = 0; i < data.Length; i++) + { + var span = ColorChoices.AsSpan(i * 2, 2); + data[i] = (byte)((span[0] & 0xF) | ((span[1] & 0xF) << 4)); + } + return data; + } + + public byte[] Rotate(int rotFlip) + { + if (rotFlip == 0) + return PixelData; + if ((rotFlip & 4) > 0) + return PixelDataX ??= FlipX(PixelData, TileWidth); + if ((rotFlip & 8) > 0) + return PixelDataY ??= FlipY(PixelData, TileHeight); + return PixelData; + } + + private static byte[] FlipX(IReadOnlyList data, int width, int bpp = 4) + { + byte[] result = new byte[data.Count]; + int pixels = data.Count / bpp; + for (int i = 0; i < pixels; i++) + { + int x = i % width; + int y = i / width; + + x = width - x - 1; // flip x + int dest = ((y * width) + x) * bpp; + + var o = 4 * i; + result[dest + 0] = data[o + 0]; + result[dest + 1] = data[o + 1]; + result[dest + 2] = data[o + 2]; + result[dest + 3] = data[o + 3]; + } + return result; + } + + private static byte[] FlipY(IReadOnlyList data, int height, int bpp = 4) + { + byte[] result = new byte[data.Count]; + int pixels = data.Count / bpp; + int width = pixels / height; + for (int i = 0; i < pixels; i++) + { + int x = i % width; + int y = i / width; + + y = height - y - 1; // flip x + int dest = ((y * width) + x) * bpp; + + var o = 4 * i; + result[dest + 0] = data[o + 0]; + result[dest + 1] = data[o + 1]; + result[dest + 2] = data[o + 2]; + result[dest + 3] = data[o + 3]; + } + return result; + } + + internal int GetRotationValue(ReadOnlySpan tileColors) + { + // Check all rotation types + if (tileColors.SequenceEqual(ColorChoices)) + return 0; + + if (IsMirrorX(tileColors)) + return 4; + if (IsMirrorY(tileColors)) + return 8; + if (IsMirrorXY(tileColors)) + return 12; + + return -1; + } + + private bool IsMirrorX(ReadOnlySpan tileColors) + { + for (int i = 0; i < 64; i++) + { + if (ColorChoices[(7 - (i & 7)) + (8 * (i / 8))] != tileColors[i]) + return false; + } + + return true; + } + + private bool IsMirrorY(ReadOnlySpan tileColors) + { + for (int i = 0; i < 64; i++) + { + if (ColorChoices[64 - (8 * (1 + (i / 8))) + (i & 7)] != tileColors[i]) + return false; + } + + return true; + } + + private bool IsMirrorXY(ReadOnlySpan tileColors) + { + for (int i = 0; i < 64; i++) + { + if (ColorChoices[63 - i] != tileColors[i]) + return false; + } + + return true; + } +} + +public sealed class TileMap +{ + public readonly int[] TileChoices; + public readonly int[] Rotations; + + internal TileMap(byte[] data) + { + TileChoices = new int[data.Length / 2]; + Rotations = new int[data.Length / 2]; + for (int i = 0; i < data.Length; i += 2) + { + TileChoices[i / 2] = data[i]; + Rotations[i / 2] = data[i + 1]; + } + } + + internal byte[] Write() + { + byte[] data = new byte[TileChoices.Length * 2]; + for (int i = 0; i < data.Length; i += 2) + { + data[i] = (byte)TileChoices[i / 2]; + data[i + 1] = (byte)Rotations[i / 2]; + } + return data; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/Daycare5.cs b/PKHeX.Core/Saves/Substructures/Gen5/Daycare5.cs index 302872526..5b2bc854d 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/Daycare5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/Daycare5.cs @@ -1,47 +1,46 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Daycare5 : SaveBlock { - public sealed class Daycare5 : SaveBlock + // struct daycareSlot + // bool32 occupied + // pk5party pk + // u32 expGained + private const int SlotSize = 4 + PokeCrypto.SIZE_5PARTY + 4; // occupied u32 flag, pk5, exp + + // struct daycare + // daycareSlot[2] + // ???->end ??? + + public const int DaycareSeedSize = 16; // 8 bytes, b2w2 only + + public Daycare5(SAV5 sav, int offset) : base(sav) => Offset = offset; + + public ulong? GetSeed() { - // struct daycareSlot - // bool32 occupied - // pk5party pkm - // u32 expGained - private const int SlotSize = 4 + PokeCrypto.SIZE_5PARTY + 4; // occupied u32 flag, pk5, exp - - // struct daycare - // daycareSlot[2] - // ???->end ??? - - public const int DaycareSeedSize = 16; // 8 bytes, b2w2 only - - public Daycare5(SAV5 sav, int offset) : base(sav) => Offset = offset; - - public ulong? GetSeed() - { - if (SAV is not SAV5B2W2) - return null; - return ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x1CC)); - } - - public void SetSeed(string value) - { - if (SAV is not SAV5B2W2) - return; - var data = Util.GetBytesFromHexString(value); - SAV.SetData(data, Offset + 0x1CC); - } - - private int GetDaycareSlotOffset(int slot) => Offset + (SlotSize * slot); - public int GetPKMOffset(int slot) => GetDaycareSlotOffset(slot) + 4; - private int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(slot) + 4 + PokeCrypto.SIZE_5PARTY; - - public bool? IsOccupied(int slot) => ReadUInt32LittleEndian(Data.AsSpan(GetDaycareSlotOffset(slot))) == 1; - public void SetOccupied(int slot, bool occupied) => WriteUInt32LittleEndian(Data.AsSpan(GetDaycareSlotOffset(slot)), (uint)(occupied ? 1 : 0)); - - public uint? GetEXP(int slot) => ReadUInt32LittleEndian(Data.AsSpan(GetDaycareEXPOffset(slot))); - public void SetEXP(int slot, uint EXP) => WriteUInt32LittleEndian(Data.AsSpan(GetDaycareEXPOffset(slot)), EXP); + if (SAV is not SAV5B2W2) + return null; + return ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x1CC)); } -} \ No newline at end of file + + public void SetSeed(string value) + { + if (SAV is not SAV5B2W2) + return; + var data = Util.GetBytesFromHexString(value); + SAV.SetData(data, Offset + 0x1CC); + } + + private int GetDaycareSlotOffset(int slot) => Offset + (SlotSize * slot); + public int GetPKMOffset(int slot) => GetDaycareSlotOffset(slot) + 4; + private int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(slot) + 4 + PokeCrypto.SIZE_5PARTY; + + public bool? IsOccupied(int slot) => ReadUInt32LittleEndian(Data.AsSpan(GetDaycareSlotOffset(slot))) == 1; + public void SetOccupied(int slot, bool occupied) => WriteUInt32LittleEndian(Data.AsSpan(GetDaycareSlotOffset(slot)), (uint)(occupied ? 1 : 0)); + + public uint? GetEXP(int slot) => ReadUInt32LittleEndian(Data.AsSpan(GetDaycareEXPOffset(slot))); + public void SetEXP(int slot, uint EXP) => WriteUInt32LittleEndian(Data.AsSpan(GetDaycareEXPOffset(slot)), EXP); +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/Encount5.cs b/PKHeX.Core/Saves/Substructures/Gen5/Encount5.cs index 413b529da..f327f6f99 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/Encount5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/Encount5.cs @@ -1,32 +1,31 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class Encount5 : SaveBlock { - public abstract class Encount5 : SaveBlock + protected Encount5(SAV5 SAV, int offset) : base(SAV) => Offset = offset; + + public abstract byte SwarmSeed { get; set; } + public abstract uint SwarmMaxCountModulo { get; } + + public uint SwarmIndex { - protected Encount5(SAV5 SAV, int offset) : base(SAV) => Offset = offset; - - public abstract byte SwarmSeed { get; set; } - public abstract uint SwarmMaxCountModulo { get; } - - public uint SwarmIndex - { - get => SwarmSeed % SwarmMaxCountModulo; - set => SwarmSeed = (byte)(value % SwarmMaxCountModulo); - } - } - - public sealed class Encount5BW : Encount5 - { - public Encount5BW(SAV5BW SAV, int offset) : base(SAV, offset) { } - - public override byte SwarmSeed { get => Data[Offset + 0x30]; set => Data[Offset + 0x30] = value; } - public override uint SwarmMaxCountModulo => 17; - } - - public sealed class Encount5B2W2 : Encount5 - { - public Encount5B2W2(SAV5B2W2 SAV, int offset) : base(SAV, offset) { } - - public override byte SwarmSeed { get => Data[Offset + 0x2C]; set => Data[Offset + 0x2C] = value; } - public override uint SwarmMaxCountModulo => 19; + get => SwarmSeed % SwarmMaxCountModulo; + set => SwarmSeed = (byte)(value % SwarmMaxCountModulo); } } + +public sealed class Encount5BW : Encount5 +{ + public Encount5BW(SAV5BW SAV, int offset) : base(SAV, offset) { } + + public override byte SwarmSeed { get => Data[Offset + 0x30]; set => Data[Offset + 0x30] = value; } + public override uint SwarmMaxCountModulo => 17; +} + +public sealed class Encount5B2W2 : Encount5 +{ + public Encount5B2W2(SAV5B2W2 SAV, int offset) : base(SAV, offset) { } + + public override byte SwarmSeed { get => Data[Offset + 0x2C]; set => Data[Offset + 0x2C] = value; } + public override uint SwarmMaxCountModulo => 19; +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/Entralink5.cs b/PKHeX.Core/Saves/Substructures/Gen5/Entralink5.cs index c07768929..10bce29b2 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/Entralink5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/Entralink5.cs @@ -1,50 +1,49 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class Entralink5 : SaveBlock { - public abstract class Entralink5 : SaveBlock + protected Entralink5(SAV5 SAV, int offset) : base(SAV) => Offset = offset; + + public ushort WhiteForestLevel { - protected Entralink5(SAV5 SAV, int offset) : base(SAV) => Offset = offset; - - public ushort WhiteForestLevel - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0C)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0C), value); - } - - public ushort BlackCityLevel - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0E)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0E), value); - } + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0C)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0C), value); } - public sealed class Entralink5BW : Entralink5 + public ushort BlackCityLevel { - public Entralink5BW(SAV5BW SAV, int offset) : base(SAV, offset) { } - } - - public sealed class Entralink5B2W2 : Entralink5 - { - public Entralink5B2W2(SAV5B2W2 SAV, int offset) : base(SAV, offset) { } - - public byte PassPower1 - { - get => Data[Offset + 0x1A0]; - set => Data[Offset + 0x1A0] = value; - } - - public byte PassPower2 - { - get => Data[Offset + 0x1A1]; - set => Data[Offset + 0x1A1] = value; - } - - public byte PassPower3 - { - get => Data[Offset + 0x1A2]; - set => Data[Offset + 0x1A2] = value; - } + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0E)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0E), value); + } +} + +public sealed class Entralink5BW : Entralink5 +{ + public Entralink5BW(SAV5BW SAV, int offset) : base(SAV, offset) { } +} + +public sealed class Entralink5B2W2 : Entralink5 +{ + public Entralink5B2W2(SAV5B2W2 SAV, int offset) : base(SAV, offset) { } + + public byte PassPower1 + { + get => Data[Offset + 0x1A0]; + set => Data[Offset + 0x1A0] = value; + } + + public byte PassPower2 + { + get => Data[Offset + 0x1A1]; + set => Data[Offset + 0x1A1] = value; + } + + public byte PassPower3 + { + get => Data[Offset + 0x1A2]; + set => Data[Offset + 0x1A2] = value; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeForest.cs b/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeForest.cs index fd9a82288..1464be3ad 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeForest.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeForest.cs @@ -1,122 +1,121 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 Entree Forest +/// +public sealed class EntreeForest { /// - /// Generation 5 Entree Forest + /// Areas 1 thru 8 have 20 slots. /// - public sealed class EntreeForest + private const byte Count18 = 20; + + /// + /// 9th Area has only 10 slots. + /// + private const byte Count9 = 10; + + private const int TotalSlots = Count18 + (3 * 8 * Count18) + (3 * Count9); // 530 + + /// + /// Areas 3 thru 8 can be unlocked (set a value 0 to 6). + /// + private const byte MaxUnlock38Areas = 6; + + private const int EncryptionSeedOffset = SIZE - 4; // 0x84C + public const int SIZE = 0x850; + + private readonly byte[] Data; + + public EntreeForest(byte[] data) => CryptRegion(Data = data); + + public byte[] Write() { - /// - /// Areas 1 thru 8 have 20 slots. - /// - private const byte Count18 = 20; - - /// - /// 9th Area has only 10 slots. - /// - private const byte Count9 = 10; - - private const int TotalSlots = Count18 + (3 * 8 * Count18) + (3 * Count9); // 530 - - /// - /// Areas 3 thru 8 can be unlocked (set a value 0 to 6). - /// - private const byte MaxUnlock38Areas = 6; - - private const int EncryptionSeedOffset = SIZE - 4; // 0x84C - public const int SIZE = 0x850; - - private readonly byte[] Data; - - public EntreeForest(byte[] data) => CryptRegion(Data = data); - - public byte[] Write() - { - byte[] data = (byte[])Data.Clone(); - CryptRegion(data); - return data; - } - - private void CryptRegion(Span data) => PokeCrypto.CryptArray(data[..EncryptionSeedOffset], EncryptionSeed); - - /// - /// Gets all Entree Slot data. - /// - public EntreeSlot[] Slots - { - get - { - var slots = new EntreeSlot[TotalSlots]; - for (int i = 0; i < slots.Length; i++) - slots[i] = new EntreeSlot(Data, i * 4) { Area = GetSlotArea(i) }; - return slots; - } - } - - /// - /// Determines if the 9th Area is available to enter. - /// - public bool Unlock9thArea - { - get => Data[0x848] == 1; - set => Data[0x848] = value ? (byte)1 : (byte)0; - } - - /// - /// Determines how many extra areas are available to enter. Areas 1 & 2 are already available by default. - /// - public int Unlock38Areas - { - get => Data[0x849] & 7; - set => Data[0x849] = (byte)((Data[0x849] & ~7) | Math.Min(MaxUnlock38Areas, value)); - } - - public uint EncryptionSeed - { - get => ReadUInt32LittleEndian(Data.AsSpan(EncryptionSeedOffset)); - private set => WriteUInt32LittleEndian(Data.AsSpan(EncryptionSeedOffset), value); - } - - public void UnlockAllAreas() - { - Unlock38Areas = MaxUnlock38Areas; - Unlock9thArea = true; - } - - public void DeleteAll() - { - foreach (var e in Slots) - e.Delete(); - } - - private static EntreeForestArea GetSlotArea(int index) - { - if (index < Count18) - return EntreeForestArea.Deepest; - index -= Count18; - - const int slots9 = 3 * Count9; - if (index < slots9) - return EntreeForestArea.Ninth | GetSlotPosition(index / Count9); - index -= slots9; - - const int slots18 = 3 * Count18; - int area = index / slots18; - if (area >= 8) - throw new ArgumentOutOfRangeException(nameof(index)); - index %= slots18; - - return (EntreeForestArea)((int)EntreeForestArea.First << area) | GetSlotPosition(index / Count18); - } - - private static EntreeForestArea GetSlotPosition(int index) => index switch - { - 0 => EntreeForestArea.Center, - 1 => EntreeForestArea.Left, - 2 => EntreeForestArea.Right, - _ => throw new ArgumentOutOfRangeException(), - }; + byte[] data = (byte[])Data.Clone(); + CryptRegion(data); + return data; } + + private void CryptRegion(Span data) => PokeCrypto.CryptArray(data[..EncryptionSeedOffset], EncryptionSeed); + + /// + /// Gets all Entree Slot data. + /// + public EntreeSlot[] Slots + { + get + { + var slots = new EntreeSlot[TotalSlots]; + for (int i = 0; i < slots.Length; i++) + slots[i] = new EntreeSlot(Data, i * 4) { Area = GetSlotArea(i) }; + return slots; + } + } + + /// + /// Determines if the 9th Area is available to enter. + /// + public bool Unlock9thArea + { + get => Data[0x848] == 1; + set => Data[0x848] = value ? (byte)1 : (byte)0; + } + + /// + /// Determines how many extra areas are available to enter. Areas 1 & 2 are already available by default. + /// + public int Unlock38Areas + { + get => Data[0x849] & 7; + set => Data[0x849] = (byte)((Data[0x849] & ~7) | Math.Min(MaxUnlock38Areas, value)); + } + + public uint EncryptionSeed + { + get => ReadUInt32LittleEndian(Data.AsSpan(EncryptionSeedOffset)); + private set => WriteUInt32LittleEndian(Data.AsSpan(EncryptionSeedOffset), value); + } + + public void UnlockAllAreas() + { + Unlock38Areas = MaxUnlock38Areas; + Unlock9thArea = true; + } + + public void DeleteAll() + { + foreach (var e in Slots) + e.Delete(); + } + + private static EntreeForestArea GetSlotArea(int index) + { + if (index < Count18) + return EntreeForestArea.Deepest; + index -= Count18; + + const int slots9 = 3 * Count9; + if (index < slots9) + return EntreeForestArea.Ninth | GetSlotPosition(index / Count9); + index -= slots9; + + const int slots18 = 3 * Count18; + int area = index / slots18; + if (area >= 8) + throw new ArgumentOutOfRangeException(nameof(index)); + index %= slots18; + + return (EntreeForestArea)((int)EntreeForestArea.First << area) | GetSlotPosition(index / Count18); + } + + private static EntreeForestArea GetSlotPosition(int index) => index switch + { + 0 => EntreeForestArea.Center, + 1 => EntreeForestArea.Left, + 2 => EntreeForestArea.Right, + _ => throw new ArgumentOutOfRangeException(), + }; } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeForestArea.cs b/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeForestArea.cs index 3029cf3e1..0b75b17d0 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeForestArea.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeForestArea.cs @@ -1,24 +1,23 @@ using System; -namespace PKHeX.Core -{ - [Flags] - public enum EntreeForestArea - { - None, - Deepest = 1 << 0, - First = 1 << 1, - Second = 1 << 2, - Third = 1 << 3, - Fourth = 1 << 4, - Fifth = 1 << 5, - Sixth = 1 << 6, - Seventh = 1 << 7, - Eighth = 1 << 8, - Ninth = 1 << 9, +namespace PKHeX.Core; - Left = 1 << 10, - Right = 1 << 11, - Center = 1 << 12, - } -} \ No newline at end of file +[Flags] +public enum EntreeForestArea +{ + None, + Deepest = 1 << 0, + First = 1 << 1, + Second = 1 << 2, + Third = 1 << 3, + Fourth = 1 << 4, + Fifth = 1 << 5, + Sixth = 1 << 6, + Seventh = 1 << 7, + Eighth = 1 << 8, + Ninth = 1 << 9, + + Left = 1 << 10, + Right = 1 << 11, + Center = 1 << 12, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeSlot.cs b/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeSlot.cs index 818142ae9..a7995e1be 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeSlot.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/Entree/EntreeSlot.cs @@ -1,87 +1,86 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 5 slot +/// +public sealed class EntreeSlot : ISpeciesForm { /// - /// Generation 5 slot + /// index /// - public sealed class EntreeSlot : ISpeciesForm + public int Species // bits 0-10 { - /// - /// index - /// - public int Species // bits 0-10 - { - get => (int)(RawValue & 0x3FF) >> 0; - set => RawValue = (RawValue & 0xFFFF_F800) | ((uint)(value & 0x3FF) << 0); - } - - /// - /// Special Move - /// - public int Move // bits 11-20 - { - get => (int)(RawValue & 0x001F_F800) >> 11; - set => RawValue = (RawValue & 0xFFE0_07FF) | ((uint)(value & 0x3FF) << 11); - } - - /// - /// index - /// - public int Gender // bits 21-22 - { - get => (int)(RawValue & 0x0060_0000) >> 21; - set => RawValue = (RawValue & 0xFF9F_FFFF) | ((uint)(value & 0x3) << 21); - } - - /// - /// index - /// - public int Form // bits 23-27 - { - get => (int)(RawValue & 0x0F80_0000) >> 23; - set => RawValue = (RawValue & 0xF07F_FFFF) | ((uint)(value & 0x1F) << 23); - } - - /// - /// Visibility Flag - /// - public bool Invisible // bit 28 - { - get => ((RawValue >> 28) & 1) == 1; - set => RawValue = (RawValue & 0xEFFFFFFF) | (value ? 0 : 1u << 28); - } - - /// - /// Animation Leash (How many steps it can deviate from its spawn location). - /// - public int Animation // bits 29-31 - { - get => (int)(RawValue >> 29); - set => RawValue = (RawValue << 3) >> 3 | (uint)((value & 0x7) << 29); - } - - private readonly byte[] Data; - private readonly int Offset; - - /// - /// Raw Data Value - /// - public uint RawValue - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value); - } - - public void Delete() => RawValue = 0; - - public EntreeForestArea Area { get; init; } - - public EntreeSlot(byte[] data, int ofs) - { - Data = data; - Offset = ofs; - } + get => (int)(RawValue & 0x3FF) >> 0; + set => RawValue = (RawValue & 0xFFFF_F800) | ((uint)(value & 0x3FF) << 0); } -} \ No newline at end of file + + /// + /// Special Move + /// + public int Move // bits 11-20 + { + get => (int)(RawValue & 0x001F_F800) >> 11; + set => RawValue = (RawValue & 0xFFE0_07FF) | ((uint)(value & 0x3FF) << 11); + } + + /// + /// index + /// + public int Gender // bits 21-22 + { + get => (int)(RawValue & 0x0060_0000) >> 21; + set => RawValue = (RawValue & 0xFF9F_FFFF) | ((uint)(value & 0x3) << 21); + } + + /// + /// index + /// + public int Form // bits 23-27 + { + get => (int)(RawValue & 0x0F80_0000) >> 23; + set => RawValue = (RawValue & 0xF07F_FFFF) | ((uint)(value & 0x1F) << 23); + } + + /// + /// Visibility Flag + /// + public bool Invisible // bit 28 + { + get => ((RawValue >> 28) & 1) == 1; + set => RawValue = (RawValue & 0xEFFFFFFF) | (value ? 0 : 1u << 28); + } + + /// + /// Animation Leash (How many steps it can deviate from its spawn location). + /// + public int Animation // bits 29-31 + { + get => (int)(RawValue >> 29); + set => RawValue = ((RawValue << 3) >> 3) | (uint)((value & 0x7) << 29); + } + + private readonly byte[] Data; + private readonly int Offset; + + /// + /// Raw Data Value + /// + public uint RawValue + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value); + } + + public void Delete() => RawValue = 0; + + public EntreeForestArea Area { get; init; } + + public EntreeSlot(byte[] data, int ofs) + { + Data = data; + Offset = ofs; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/FestaBlock5.cs b/PKHeX.Core/Saves/Substructures/Gen5/FestaBlock5.cs index d555745e2..94fb8d800 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/FestaBlock5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/FestaBlock5.cs @@ -1,270 +1,264 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class FestaBlock5 : SaveBlock { - public sealed class FestaBlock5 : SaveBlock + public FestaBlock5(SAV5B2W2 SAV, int offset) : base(SAV) => Offset = offset; + + public const ushort MaxScore = 9999; + public const int FunfestFlag = 2438; + + public const int MaxMissionIndex = (int)Funfest5Mission.TheBerryHuntingAdventure; + + public ushort Hosted { - public FestaBlock5(SAV5B2W2 SAV, int offset) : base(SAV) => Offset = offset; - - public const ushort MaxScore = 9999; - public const int FunfestFlag = 2438; - - public const int MaxMissionIndex = (int)Funfest5Mission.TheBerryHuntingAdventure; - - public ushort Hosted - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF0)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF0), Math.Min(MaxScore, value)); - } - - public ushort Participated - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF2)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF2), Math.Min(MaxScore, value)); - } - - public ushort Completed - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF4)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF4), Math.Min(MaxScore, value)); - } - - public ushort TopScores - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF6)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF6), Math.Min(MaxScore, value)); - } - - public byte WhiteEXP - { - get => Data[Offset + 0xF8]; - set => Data[Offset + 0xF8] = value; - } - - public byte BlackEXP - { - get => Data[Offset + 0xF9]; - set => Data[Offset + 0xF9] = value; - } - - public byte Participants - { - get => Data[Offset + 0xFA]; - set => Data[Offset + 0xFA] = value; - } - - private static int GetMissionRecordOffset(int mission) - { - if ((uint)mission > MaxMissionIndex) - throw new ArgumentOutOfRangeException(nameof(mission)); - return mission * sizeof(uint); - } - - public Funfest5Score GetMissionRecord(int mission) - { - var raw = ReadUInt32LittleEndian(Data.AsSpan(Offset + GetMissionRecordOffset(mission))); - return new Funfest5Score(raw); - } - - public void SetMissionRecord(int mission, Funfest5Score score) - { - var value = score.RawValue; - WriteUInt32LittleEndian(Data.AsSpan(Offset + GetMissionRecordOffset(mission)), value); - } - - public bool IsFunfestMissionsUnlocked - { - get => SAV.GetEventFlag(FunfestFlag); - set => SAV.SetEventFlag(FunfestFlag, value); - } - - public bool IsFunfestMissionUnlocked(int mission) - { - if ((uint) mission > MaxMissionIndex) - throw new ArgumentOutOfRangeException(nameof(mission)); - - if (mission == 0) - return !IsFunfestMissionsUnlocked; - - var req = FunfestMissionUnlockFlagRequired[mission]; - foreach (var f in req) - { - if (!SAV.GetEventFlag(f)) - return false; - } - return true; - } - - public void UnlockFunfestMission(int mission) - { - if ((uint)mission > MaxMissionIndex) - throw new ArgumentOutOfRangeException(nameof(mission)); - - IsFunfestMissionsUnlocked = true; - var req = FunfestMissionUnlockFlagRequired[mission]; - foreach (var f in req) - SAV.SetEventFlag(f, true); - } - - public void UnlockAllFunfestMissions() - { - for (int i = 0; i < MaxMissionIndex; i++) - UnlockFunfestMission(i); - } - - private readonly int[][] FunfestMissionUnlockFlagRequired = - { - Array.Empty(), // 00 - Array.Empty(), // 01 - new[] { 2444 }, // 02 - Array.Empty(), // 03 - new[] { 2445 }, // 04 - Array.Empty(), // 05 - new[] { 2462 }, // 06 - new[] { 2452, 2476 }, // 07 - new[] { 2476, 2548 }, // 08 - new[] { 2447 }, new[] { 2447 }, // 09 - new[] { 2453 }, new[] { 2453 }, // 10 - new[] { 2504 }, // 11 - new[] { 2457, 2507 }, // 12 - new[] { 2458, 2478 }, // 13 - new[] { 2456, 2508 }, // 14 - new[] { 2448 }, new[] { 2448 }, // 15 - new[] { 2549 }, // 16 - new[] { 2449 }, // 17 - new[] { 2479, 2513 }, // 18 - new[] { 2479, 2550 }, // 19 - new[] { 2481 }, // 20 - new[] { 2459 }, // 21 - new[] { 2454 }, // 22 - new[] { 2551 }, // 23 - new[] { 2400 }, // 24 - new[] { 2400 }, // 25 - new[] { 2400 }, new[] { 2400 }, // 26 - new[] { 2400 }, new[] { 2400 }, // 27 - new[] { 2400 }, // 28 - new[] { 2400, 2460 }, // 29 - new[] { 2400 }, // 30 - new[] { 2400, 2461 }, new[] { 2400, 2461 }, // 31 - new[] { 2437 }, // 32 - new[] { 2450 }, // 33 - new[] { 2451 }, // 34 - new[] { 2455 }, // 35 - new[] { 0105 }, // 36 - new[] { 2400 }, // 37 - new[] { 2557 }, // 38 - }; - - public static int GetExpNeededForLevelUp(int lv) - { - return lv > 8 ? 50 : (lv * 5) + 5; - } - - public static int GetTotalEntreeExp(int lv) - { - if (lv < 9) - return lv * (lv + 1) * 5 / 2; - return ((lv - 9) * 50) + 225; - } + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF0)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF0), Math.Min(MaxScore, value)); } - public enum Funfest5Mission + public ushort Participated { - TheFirstBerrySearch = 0, - CollectBerries = 1, - FindLostItems = 2, - FindLostBoys = 3, - EnjoyShopping = 4, - FindAudino = 5, - SearchFor3Pokemon = 6, - TrainwithMartialArtists = 7, - Sparringwith10Trainers = 8, - B_GetRichQuick = 9, - W_TreasureHunting = 10, - B_ExcitingTrading = 11, - W_ExhilaratingTrading = 12, - FindEmolga = 13, - WingsFallingontheDrawbridge = 14, - FindTreasures = 15, - MushroomsHideAndSeek = 16, - B_FindMysteriousOres = 17, - W_FindShiningOres = 18, - The2LostTreasures = 19, - BigHarvestofBerries = 20, - RingtheBell = 21, - TheBellthatRings3Times = 22, - PathtoanAce = 23, - ShockingShopping = 24, - MemoryTraining = 25, - PushtheLimitofYourMemory = 26, - FindRustlingGrass = 27, - FindShards = 28, - B_ForgottenLostItems = 29, - W_NotFoundLostItems = 30, - B_WhatistheBestPrice = 31, - W_WhatistheRealPrice = 32, - GivemetheItem = 33, - DoaGreatTradeUp = 34, - SearchHiddenGrottes = 35, - B_NoisyHiddenGrottes = 36, - W_QuietHiddenGrottes = 37, - FishingCompetition = 38, - MulchCollector = 39, - WhereareFlutteringHearts = 40, - RockPaperScissorsCompetition = 41, - TakeaWalkwithEggs = 42, - FindSteelix = 43, - TheBerryHuntingAdventure = 44, + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF2)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF2), Math.Min(MaxScore, value)); } - public struct Funfest5Score + public ushort Completed { - public uint RawValue; - public Funfest5Score(uint raw) => RawValue = raw; + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF4)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF4), Math.Min(MaxScore, value)); + } - public Funfest5Score(int total, int score, int level, bool isNew) + public ushort TopScores + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xF6)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xF6), Math.Min(MaxScore, value)); + } + + public byte WhiteEXP + { + get => Data[Offset + 0xF8]; + set => Data[Offset + 0xF8] = value; + } + + public byte BlackEXP + { + get => Data[Offset + 0xF9]; + set => Data[Offset + 0xF9] = value; + } + + public byte Participants + { + get => Data[Offset + 0xFA]; + set => Data[Offset + 0xFA] = value; + } + + private static int GetMissionRecordOffset(int mission) + { + if ((uint)mission > MaxMissionIndex) + throw new ArgumentOutOfRangeException(nameof(mission)); + return mission * sizeof(uint); + } + + public Funfest5Score GetMissionRecord(int mission) + { + var raw = ReadUInt32LittleEndian(Data.AsSpan(Offset + GetMissionRecordOffset(mission))); + return new Funfest5Score(raw); + } + + public void SetMissionRecord(int mission, Funfest5Score score) + { + var value = score.RawValue; + WriteUInt32LittleEndian(Data.AsSpan(Offset + GetMissionRecordOffset(mission)), value); + } + + public bool IsFunfestMissionsUnlocked + { + get => SAV.GetEventFlag(FunfestFlag); + set => SAV.SetEventFlag(FunfestFlag, value); + } + + public bool IsFunfestMissionUnlocked(int mission) + { + if ((uint) mission > MaxMissionIndex) + throw new ArgumentOutOfRangeException(nameof(mission)); + + if (mission == 0) + return !IsFunfestMissionsUnlocked; + + var req = FunfestMissionUnlockFlagRequired[mission]; + foreach (var f in req) { - RawValue = 0; - Total = total; - Score = score; - Level = level; - IsNew = isNew; + if (!SAV.GetEventFlag(f)) + return false; } + return true; + } - // Structure - 32bits - // u32 bestTotal:14 - // u32 bestScore:14 - // u32 level:3 - // u32 isNew:1 + public void UnlockFunfestMission(int mission) + { + if ((uint)mission > MaxMissionIndex) + throw new ArgumentOutOfRangeException(nameof(mission)); - public int Total - { - get => (int)(RawValue & 0x3FFFu); - set => RawValue = (RawValue & ~0x3FFFu) | ((uint)value & 0x3FFFu); - } + IsFunfestMissionsUnlocked = true; + var req = FunfestMissionUnlockFlagRequired[mission]; + foreach (var f in req) + SAV.SetEventFlag(f, true); + } - public int Score - { - get => (int)((RawValue >> 14) & 0x3FFFu); - set => RawValue = (RawValue & 0xF0003FFFu) | (((uint)value & 0x3FFFu) << 14); - } + public void UnlockAllFunfestMissions() + { + for (int i = 0; i < MaxMissionIndex; i++) + UnlockFunfestMission(i); + } - public int Level - { - get => (int)((RawValue >> 28) & 0x7u); - set => RawValue = (RawValue & 0x8FFFFFFFu) | (((uint)value & 0x7u) << 28); - } + private readonly int[][] FunfestMissionUnlockFlagRequired = + { + Array.Empty(), // 00 + Array.Empty(), // 01 + new[] { 2444 }, // 02 + Array.Empty(), // 03 + new[] { 2445 }, // 04 + Array.Empty(), // 05 + new[] { 2462 }, // 06 + new[] { 2452, 2476 }, // 07 + new[] { 2476, 2548 }, // 08 + new[] { 2447 }, new[] { 2447 }, // 09 + new[] { 2453 }, new[] { 2453 }, // 10 + new[] { 2504 }, // 11 + new[] { 2457, 2507 }, // 12 + new[] { 2458, 2478 }, // 13 + new[] { 2456, 2508 }, // 14 + new[] { 2448 }, new[] { 2448 }, // 15 + new[] { 2549 }, // 16 + new[] { 2449 }, // 17 + new[] { 2479, 2513 }, // 18 + new[] { 2479, 2550 }, // 19 + new[] { 2481 }, // 20 + new[] { 2459 }, // 21 + new[] { 2454 }, // 22 + new[] { 2551 }, // 23 + new[] { 2400 }, // 24 + new[] { 2400 }, // 25 + new[] { 2400 }, new[] { 2400 }, // 26 + new[] { 2400 }, new[] { 2400 }, // 27 + new[] { 2400 }, // 28 + new[] { 2400, 2460 }, // 29 + new[] { 2400 }, // 30 + new[] { 2400, 2461 }, new[] { 2400, 2461 }, // 31 + new[] { 2437 }, // 32 + new[] { 2450 }, // 33 + new[] { 2451 }, // 34 + new[] { 2455 }, // 35 + new[] { 0105 }, // 36 + new[] { 2400 }, // 37 + new[] { 2557 }, // 38 + }; - public bool IsNew - { - get => RawValue >> 31 == 1; - set => RawValue = (RawValue & 0x7FFFFFFFu) | ((value ? 1u : 0) << 31); - } + public static int GetExpNeededForLevelUp(int lv) + { + return lv > 8 ? 50 : (lv * 5) + 5; + } - public override bool Equals(object obj) => obj is Funfest5Score f && f == this; - public override int GetHashCode() => RawValue.GetHashCode(); - public static bool operator ==(Funfest5Score left, Funfest5Score right) => left.RawValue == right.RawValue; - public static bool operator !=(Funfest5Score left, Funfest5Score right) => !(left == right); + public static int GetTotalEntreeExp(int lv) + { + if (lv < 9) + return lv * (lv + 1) * 5 / 2; + return ((lv - 9) * 50) + 225; + } +} + +public enum Funfest5Mission +{ + TheFirstBerrySearch = 0, + CollectBerries = 1, + FindLostItems = 2, + FindLostBoys = 3, + EnjoyShopping = 4, + FindAudino = 5, + SearchFor3Pokemon = 6, + TrainwithMartialArtists = 7, + Sparringwith10Trainers = 8, + B_GetRichQuick = 9, + W_TreasureHunting = 10, + B_ExcitingTrading = 11, + W_ExhilaratingTrading = 12, + FindEmolga = 13, + WingsFallingontheDrawbridge = 14, + FindTreasures = 15, + MushroomsHideAndSeek = 16, + B_FindMysteriousOres = 17, + W_FindShiningOres = 18, + The2LostTreasures = 19, + BigHarvestofBerries = 20, + RingtheBell = 21, + TheBellthatRings3Times = 22, + PathtoanAce = 23, + ShockingShopping = 24, + MemoryTraining = 25, + PushtheLimitofYourMemory = 26, + FindRustlingGrass = 27, + FindShards = 28, + B_ForgottenLostItems = 29, + W_NotFoundLostItems = 30, + B_WhatistheBestPrice = 31, + W_WhatistheRealPrice = 32, + GivemetheItem = 33, + DoaGreatTradeUp = 34, + SearchHiddenGrottes = 35, + B_NoisyHiddenGrottes = 36, + W_QuietHiddenGrottes = 37, + FishingCompetition = 38, + MulchCollector = 39, + WhereareFlutteringHearts = 40, + RockPaperScissorsCompetition = 41, + TakeaWalkwithEggs = 42, + FindSteelix = 43, + TheBerryHuntingAdventure = 44, +} + +public record struct Funfest5Score +{ + public uint RawValue { get; set; } + public Funfest5Score(uint raw) => RawValue = raw; + + public Funfest5Score(int total, int score, int level, bool isNew) + { + RawValue = 0; + Total = total; + Score = score; + Level = level; + IsNew = isNew; + } + + // Structure - 32bits + // u32 bestTotal:14 + // u32 bestScore:14 + // u32 level:3 + // u32 isNew:1 + + public int Total + { + get => (int)(RawValue & 0x3FFFu); + set => RawValue = (RawValue & ~0x3FFFu) | ((uint)value & 0x3FFFu); + } + + public int Score + { + get => (int)((RawValue >> 14) & 0x3FFFu); + set => RawValue = (RawValue & 0xF0003FFFu) | (((uint)value & 0x3FFFu) << 14); + } + + public int Level + { + get => (int)((RawValue >> 28) & 0x7u); + set => RawValue = (RawValue & 0x8FFFFFFFu) | (((uint)value & 0x7u) << 28); + } + + public bool IsNew + { + get => RawValue >> 31 == 1; + set => RawValue = (RawValue & 0x7FFFFFFFu) | ((value ? 1u : 0) << 31); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/Misc5.cs b/PKHeX.Core/Saves/Substructures/Gen5/Misc5.cs index 1168ae467..60b68ccd7 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/Misc5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/Misc5.cs @@ -1,67 +1,66 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class Misc5 : SaveBlock, IGymTeamInfo { - public abstract class Misc5 : SaveBlock, IGymTeamInfo + protected Misc5(SAV5 sav, int offset) : base(sav) => Offset = offset; + + public uint Money { - protected Misc5(SAV5 sav, int offset) : base(sav) => Offset = offset; - - public uint Money - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value); - } - - public int Badges - { - get => Data[Offset + 0x4]; - set => Data[Offset + 0x4] = (byte)value; - } - - public ushort PokeTransferMinigameScore - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + TransferMinigameScoreOffset)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + TransferMinigameScoreOffset), value); - } - - protected abstract int BadgeVictoryOffset { get; } - protected abstract int TransferMinigameScoreOffset { get; } - - private int GetBadgeVictorySpeciesOffset(uint badge, uint slot) - { - if (badge >= 8) - throw new ArgumentOutOfRangeException(nameof(badge)); - if (slot >= 6) - throw new ArgumentOutOfRangeException(nameof(slot)); - - return Offset + BadgeVictoryOffset + (int)(((6 * badge) + slot) * sizeof(ushort)); - } - - public ushort GetBadgeVictorySpecies(uint badge, uint slot) - { - var ofs = GetBadgeVictorySpeciesOffset(badge, slot); - return ReadUInt16LittleEndian(Data.AsSpan(ofs)); - } - - public void SetBadgeVictorySpecies(uint badge, uint slot, ushort species) - { - var ofs = GetBadgeVictorySpeciesOffset(badge, slot); - WriteUInt16LittleEndian(SAV.Data.AsSpan(ofs), species); - } + get => ReadUInt32LittleEndian(Data.AsSpan(Offset)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value); } - public sealed class Misc5BW : Misc5 + public int Badges { - public Misc5BW(SAV5BW sav, int offset) : base(sav, offset) { } - protected override int TransferMinigameScoreOffset => 0x14; - protected override int BadgeVictoryOffset => 0x58; // thru 0xB7 + get => Data[Offset + 0x4]; + set => Data[Offset + 0x4] = (byte)value; } - public sealed class Misc5B2W2 : Misc5 + public ushort PokeTransferMinigameScore { - public Misc5B2W2(SAV5B2W2 sav, int offset) : base(sav, offset) { } - protected override int TransferMinigameScoreOffset => 0x18; - protected override int BadgeVictoryOffset => 0x5C; // thru 0xBB + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + TransferMinigameScoreOffset)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + TransferMinigameScoreOffset), value); + } + + protected abstract int BadgeVictoryOffset { get; } + protected abstract int TransferMinigameScoreOffset { get; } + + private int GetBadgeVictorySpeciesOffset(uint badge, uint slot) + { + if (badge >= 8) + throw new ArgumentOutOfRangeException(nameof(badge)); + if (slot >= 6) + throw new ArgumentOutOfRangeException(nameof(slot)); + + return Offset + BadgeVictoryOffset + (int)(((6 * badge) + slot) * sizeof(ushort)); + } + + public ushort GetBadgeVictorySpecies(uint badge, uint slot) + { + var ofs = GetBadgeVictorySpeciesOffset(badge, slot); + return ReadUInt16LittleEndian(Data.AsSpan(ofs)); + } + + public void SetBadgeVictorySpecies(uint badge, uint slot, ushort species) + { + var ofs = GetBadgeVictorySpeciesOffset(badge, slot); + WriteUInt16LittleEndian(SAV.Data.AsSpan(ofs), species); } } + +public sealed class Misc5BW : Misc5 +{ + public Misc5BW(SAV5BW sav, int offset) : base(sav, offset) { } + protected override int TransferMinigameScoreOffset => 0x14; + protected override int BadgeVictoryOffset => 0x58; // thru 0xB7 +} + +public sealed class Misc5B2W2 : Misc5 +{ + public Misc5B2W2(SAV5B2W2 sav, int offset) : base(sav, offset) { } + protected override int TransferMinigameScoreOffset => 0x18; + protected override int BadgeVictoryOffset => 0x5C; // thru 0xBB +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/Musical5.cs b/PKHeX.Core/Saves/Substructures/Gen5/Musical5.cs index 09150eb1e..662cbe3fb 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/Musical5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/Musical5.cs @@ -1,33 +1,32 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Musical5 : SaveBlock { - public sealed class Musical5 : SaveBlock + public Musical5(SAV5BW SAV, int offset) : base(SAV) => Offset = offset; + public Musical5(SAV5B2W2 SAV, int offset) : base(SAV) => Offset = offset; + + private const int PropOffset = 0x258; + + public void UnlockAllMusicalProps() { - public Musical5(SAV5BW SAV, int offset) : base(SAV) => Offset = offset; - public Musical5(SAV5B2W2 SAV, int offset) : base(SAV) => Offset = offset; + // 101 props, which is 12.X bytes of bitflags. + var bitFieldOffset = Offset + PropOffset; + for (int i = 0; i < 0xC; i++) + Data[bitFieldOffset + i] = 0xFF; + Data[bitFieldOffset + 0xC] = 0x1F; // top 3 bits unset, to complete multiple of 8 (101=>104 bits). + } - private const int PropOffset = 0x258; + public bool GetHasProp(int prop) + { + var bitFieldOffset = Offset + PropOffset; + var bitOffset = prop >> 3; + return SAV.GetFlag(bitFieldOffset + bitOffset, prop & 7); + } - public void UnlockAllMusicalProps() - { - // 101 props, which is 12.X bytes of bitflags. - var bitFieldOffset = Offset + PropOffset; - for (int i = 0; i < 0xC; i++) - Data[bitFieldOffset + i] = 0xFF; - Data[bitFieldOffset + 0xC] = 0x1F; // top 3 bits unset, to complete multiple of 8 (101=>104 bits). - } - - public bool GetHasProp(int prop) - { - var bitFieldOffset = Offset + PropOffset; - var bitOffset = prop >> 3; - return SAV.GetFlag(bitFieldOffset + bitOffset, prop & 7); - } - - public void SetHasProp(int prop, bool value = true) - { - var bitFieldOffset = Offset + PropOffset; - var bitOffset = prop >> 3; - SAV.SetFlag(bitFieldOffset + bitOffset, prop & 7, value); - } + public void SetHasProp(int prop, bool value = true) + { + var bitFieldOffset = Offset + PropOffset; + var bitOffset = prop >> 3; + SAV.SetFlag(bitFieldOffset + bitOffset, prop & 7, value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/MyItem5B2W2.cs b/PKHeX.Core/Saves/Substructures/Gen5/MyItem5B2W2.cs index 70084dff9..1fc00f1f1 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/MyItem5B2W2.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/MyItem5B2W2.cs @@ -1,39 +1,38 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyItem5B2W2 : MyItem { - public sealed class MyItem5B2W2 : MyItem + // offsets/pouch sizes are the same for both BW and B2W2, but Key Item permissions are different + private const int HeldItem = 0x000; // 0 + private const int KeyItem = 0x4D8; // 1 + private const int TMHM = 0x624; // 2 + private const int Medicine = 0x7D8; // 3 + private const int Berry = 0x898; // 4 + + private static readonly ushort[] LegalItems = Legal.Pouch_Items_BW; + private static readonly ushort[] LegalKeyItems = Legal.Pouch_Key_B2W2; + private static readonly ushort[] LegalTMHMs = Legal.Pouch_TMHM_BW; + private static readonly ushort[] LegalMedicine = Legal.Pouch_Medicine_BW; + private static readonly ushort[] LegalBerries = Legal.Pouch_Berries_BW; + + public MyItem5B2W2(SaveFile SAV, int offset) : base(SAV) => Offset = offset; + + public override IReadOnlyList Inventory { - // offsets/pouch sizes are the same for both BW and B2W2, but Key Item permissions are different - private const int HeldItem = 0x000; // 0 - private const int KeyItem = 0x4D8; // 1 - private const int TMHM = 0x624; // 2 - private const int Medicine = 0x7D8; // 3 - private const int Berry = 0x898; // 4 - - private static readonly ushort[] LegalItems = Legal.Pouch_Items_BW; - private static readonly ushort[] LegalKeyItems = Legal.Pouch_Key_B2W2; - private static readonly ushort[] LegalTMHMs = Legal.Pouch_TMHM_BW; - private static readonly ushort[] LegalMedicine = Legal.Pouch_Medicine_BW; - private static readonly ushort[] LegalBerries = Legal.Pouch_Berries_BW; - - public MyItem5B2W2(SaveFile SAV, int offset) : base(SAV) => Offset = offset; - - public override IReadOnlyList Inventory + get { - get + InventoryPouch[] pouch = { - InventoryPouch[] pouch = - { - new InventoryPouch4(InventoryType.Items, LegalItems, 999, Offset + HeldItem), - new InventoryPouch4(InventoryType.KeyItems, LegalKeyItems, 1, Offset + KeyItem), - new InventoryPouch4(InventoryType.TMHMs, LegalTMHMs, 1, Offset + TMHM), - new InventoryPouch4(InventoryType.Medicine, LegalMedicine, 999, Offset + Medicine), - new InventoryPouch4(InventoryType.Berries, LegalBerries, 999, Offset + Berry), - }; - return pouch.LoadAll(Data); - } - set => value.SaveAll(Data); + new InventoryPouch4(InventoryType.Items, LegalItems, 999, Offset + HeldItem), + new InventoryPouch4(InventoryType.KeyItems, LegalKeyItems, 1, Offset + KeyItem), + new InventoryPouch4(InventoryType.TMHMs, LegalTMHMs, 1, Offset + TMHM), + new InventoryPouch4(InventoryType.Medicine, LegalMedicine, 999, Offset + Medicine), + new InventoryPouch4(InventoryType.Berries, LegalBerries, 999, Offset + Berry), + }; + return pouch.LoadAll(Data); } + set => value.SaveAll(Data); } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/MyItem5BW.cs b/PKHeX.Core/Saves/Substructures/Gen5/MyItem5BW.cs index e8dd67032..085a7114a 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/MyItem5BW.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/MyItem5BW.cs @@ -1,39 +1,38 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyItem5BW : MyItem { - public sealed class MyItem5BW : MyItem + // offsets/pouch sizes are the same for both BW and B2W2, but Key Item permissions are different + private const int HeldItem = 0x000; // 0 + private const int KeyItem = 0x4D8; // 1 + private const int TMHM = 0x624; // 2 + private const int Medicine = 0x7D8; // 3 + private const int Berry = 0x898; // 4 + + private static readonly ushort[] LegalItems = Legal.Pouch_Items_BW; + private static readonly ushort[] LegalKeyItems = Legal.Pouch_Key_BW; + private static readonly ushort[] LegalTMHMs = Legal.Pouch_TMHM_BW; + private static readonly ushort[] LegalMedicine = Legal.Pouch_Medicine_BW; + private static readonly ushort[] LegalBerries = Legal.Pouch_Berries_BW; + + public MyItem5BW(SaveFile SAV, int offset) : base(SAV) => Offset = offset; + + public override IReadOnlyList Inventory { - // offsets/pouch sizes are the same for both BW and B2W2, but Key Item permissions are different - private const int HeldItem = 0x000; // 0 - private const int KeyItem = 0x4D8; // 1 - private const int TMHM = 0x624; // 2 - private const int Medicine = 0x7D8; // 3 - private const int Berry = 0x898; // 4 - - private static readonly ushort[] LegalItems = Legal.Pouch_Items_BW; - private static readonly ushort[] LegalKeyItems = Legal.Pouch_Key_BW; - private static readonly ushort[] LegalTMHMs = Legal.Pouch_TMHM_BW; - private static readonly ushort[] LegalMedicine = Legal.Pouch_Medicine_BW; - private static readonly ushort[] LegalBerries = Legal.Pouch_Berries_BW; - - public MyItem5BW(SaveFile SAV, int offset) : base(SAV) => Offset = offset; - - public override IReadOnlyList Inventory + get { - get + InventoryPouch[] pouch = { - InventoryPouch[] pouch = - { - new InventoryPouch4(InventoryType.Items, LegalItems, 999, Offset + HeldItem), - new InventoryPouch4(InventoryType.KeyItems, LegalKeyItems, 1, Offset + KeyItem), - new InventoryPouch4(InventoryType.TMHMs, LegalTMHMs, 1, Offset + TMHM), - new InventoryPouch4(InventoryType.Medicine, LegalMedicine, 999, Offset + Medicine), - new InventoryPouch4(InventoryType.Berries, LegalBerries, 999, Offset + Berry), - }; - return pouch.LoadAll(Data); - } - set => value.SaveAll(Data); + new InventoryPouch4(InventoryType.Items, LegalItems, 999, Offset + HeldItem), + new InventoryPouch4(InventoryType.KeyItems, LegalKeyItems, 1, Offset + KeyItem), + new InventoryPouch4(InventoryType.TMHMs, LegalTMHMs, 1, Offset + TMHM), + new InventoryPouch4(InventoryType.Medicine, LegalMedicine, 999, Offset + Medicine), + new InventoryPouch4(InventoryType.Berries, LegalBerries, 999, Offset + Berry), + }; + return pouch.LoadAll(Data); } + set => value.SaveAll(Data); } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/MysteryBlock5.cs b/PKHeX.Core/Saves/Substructures/Gen5/MysteryBlock5.cs index 5d2ae6538..18b3995be 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/MysteryBlock5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/MysteryBlock5.cs @@ -1,79 +1,78 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MysteryBlock5 : SaveBlock { - public sealed class MysteryBlock5 : SaveBlock + private const int FlagStart = 0; + private const int MaxReceivedFlag = 2048; + private const int MaxCardsPresent = 12; + private const int FlagRegionSize = (MaxReceivedFlag / 8); // 0x100 + private const int CardStart = FlagStart + FlagRegionSize; + + private const int DataSize = 0xA90; + private int SeedOffset => Offset + DataSize; + + // Everything is stored encrypted, and only decrypted on demand. Only crypt on object fetch... + public MysteryBlock5(SAV5BW sav, int offset) : base(sav) => Offset = offset; + public MysteryBlock5(SAV5B2W2 sav, int offset) : base(sav) => Offset = offset; + + public EncryptedMysteryGiftAlbum GiftAlbum { - private const int FlagStart = 0; - private const int MaxReceivedFlag = 2048; - private const int MaxCardsPresent = 12; - private const int FlagRegionSize = (MaxReceivedFlag / 8); // 0x100 - private const int CardStart = FlagStart + FlagRegionSize; - - private const int DataSize = 0xA90; - private int SeedOffset => Offset + DataSize; - - // Everything is stored encrypted, and only decrypted on demand. Only crypt on object fetch... - public MysteryBlock5(SAV5BW sav, int offset) : base(sav) => Offset = offset; - public MysteryBlock5(SAV5B2W2 sav, int offset) : base(sav) => Offset = offset; - - public EncryptedMysteryGiftAlbum GiftAlbum + get { - get - { - uint seed = ReadUInt32LittleEndian(Data.AsSpan(SeedOffset)); - byte[] wcData = SAV.GetData(Offset + FlagStart, 0xA90); // Encrypted, Decrypt - return GetAlbum(seed, wcData); - } - set - { - var wcData = SetAlbum(value); - // Write Back - wcData.CopyTo(Data, Offset + FlagStart); - WriteUInt32LittleEndian(Data.AsSpan(SeedOffset), value.Seed); - } + uint seed = ReadUInt32LittleEndian(Data.AsSpan(SeedOffset)); + byte[] wcData = SAV.GetData(Offset + FlagStart, 0xA90); // Encrypted, Decrypt + return GetAlbum(seed, wcData); } - - private static EncryptedMysteryGiftAlbum GetAlbum(uint seed, byte[] wcData) + set { - PokeCrypto.CryptArray(wcData, seed); - - var flags = new bool[MaxReceivedFlag]; - var gifts = new DataMysteryGift[MaxCardsPresent]; - var Info = new EncryptedMysteryGiftAlbum(gifts, flags, seed); - - // 0x100 Bytes for Used Flags - for (int i = 0; i < Info.Flags.Length; i++) - Info.Flags[i] = (wcData[i / 8] >> i % 8 & 0x1) == 1; - // 12 PGFs - for (int i = 0; i < Info.Gifts.Length; i++) - { - var data = wcData.AsSpan(CardStart + (i * PGF.Size), PGF.Size).ToArray(); - Info.Gifts[i] = new PGF(data); - } - - return Info; - } - - private static byte[] SetAlbum(EncryptedMysteryGiftAlbum value) - { - byte[] wcData = new byte[0xA90]; - - // Toss back into byte[] - for (int i = 0; i < value.Flags.Length; i++) - { - if (value.Flags[i]) - wcData[i / 8] |= (byte) (1 << (i & 7)); - } - - var span = wcData.AsSpan(CardStart); - for (int i = 0; i < value.Gifts.Length; i++) - value.Gifts[i].Data.CopyTo(span[(i * PGF.Size)..]); - - // Decrypted, Encrypt - PokeCrypto.CryptArray(wcData, value.Seed); - return wcData; + var wcData = SetAlbum(value); + // Write Back + wcData.CopyTo(Data, Offset + FlagStart); + WriteUInt32LittleEndian(Data.AsSpan(SeedOffset), value.Seed); } } -} \ No newline at end of file + + private static EncryptedMysteryGiftAlbum GetAlbum(uint seed, byte[] wcData) + { + PokeCrypto.CryptArray(wcData, seed); + + var flags = new bool[MaxReceivedFlag]; + var gifts = new DataMysteryGift[MaxCardsPresent]; + var Info = new EncryptedMysteryGiftAlbum(gifts, flags, seed); + + // 0x100 Bytes for Used Flags + for (int i = 0; i < Info.Flags.Length; i++) + Info.Flags[i] = ((wcData[i / 8] >> (i % 8)) & 0x1) == 1; + // 12 PGFs + for (int i = 0; i < Info.Gifts.Length; i++) + { + var data = wcData.AsSpan(CardStart + (i * PGF.Size), PGF.Size).ToArray(); + Info.Gifts[i] = new PGF(data); + } + + return Info; + } + + private static byte[] SetAlbum(EncryptedMysteryGiftAlbum value) + { + byte[] wcData = new byte[0xA90]; + + // Toss back into byte[] + for (int i = 0; i < value.Flags.Length; i++) + { + if (value.Flags[i]) + wcData[i / 8] |= (byte) (1 << (i & 7)); + } + + var span = wcData.AsSpan(CardStart); + for (int i = 0; i < value.Gifts.Length; i++) + value.Gifts[i].Data.CopyTo(span[(i * PGF.Size)..]); + + // Decrypted, Encrypt + PokeCrypto.CryptArray(wcData, value.Seed); + return wcData; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/PWTBlock5.cs b/PKHeX.Core/Saves/Substructures/Gen5/PWTBlock5.cs index 9a6ebe5b2..b5482d74b 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/PWTBlock5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/PWTBlock5.cs @@ -1,30 +1,29 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class PWTBlock5 : SaveBlock { - public sealed class PWTBlock5 : SaveBlock + public PWTBlock5(SAV5B2W2 sav, int offset) : base(sav) => Offset = offset; + + public ushort GetPWTRecord(int id) => GetPWTRecord((PWTRecordID)id); + + public ushort GetPWTRecord(PWTRecordID id) { - public PWTBlock5(SAV5B2W2 sav, int offset) : base(sav) => Offset = offset; - - public ushort GetPWTRecord(int id) => GetPWTRecord((PWTRecordID)id); - - public ushort GetPWTRecord(PWTRecordID id) - { - if (id is < PWTRecordID.Normal or > PWTRecordID.MixMaster) - throw new ArgumentOutOfRangeException(nameof(id)); - int ofs = Offset + 0x5C + ((int)id * 2); - return ReadUInt16LittleEndian(Data.AsSpan(ofs)); - } - - public void SetPWTRecord(int id, ushort value) => SetPWTRecord((PWTRecordID)id, value); - - public void SetPWTRecord(PWTRecordID id, ushort value) - { - if (id is < PWTRecordID.Normal or > PWTRecordID.MixMaster) - throw new ArgumentOutOfRangeException(nameof(id)); - int ofs = Offset + 0x5C + ((int)id * 2); - WriteUInt16LittleEndian(Data.AsSpan(ofs), value); - } + if (id is < PWTRecordID.Normal or > PWTRecordID.MixMaster) + throw new ArgumentOutOfRangeException(nameof(id)); + int ofs = Offset + 0x5C + ((int)id * 2); + return ReadUInt16LittleEndian(Data.AsSpan(ofs)); } -} \ No newline at end of file + + public void SetPWTRecord(int id, ushort value) => SetPWTRecord((PWTRecordID)id, value); + + public void SetPWTRecord(PWTRecordID id, ushort value) + { + if (id is < PWTRecordID.Normal or > PWTRecordID.MixMaster) + throw new ArgumentOutOfRangeException(nameof(id)); + int ofs = Offset + 0x5C + ((int)id * 2); + WriteUInt16LittleEndian(Data.AsSpan(ofs), value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen5/PWTRecordID.cs b/PKHeX.Core/Saves/Substructures/Gen5/PWTRecordID.cs index f7dd14f88..022010380 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/PWTRecordID.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/PWTRecordID.cs @@ -1,35 +1,34 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum PWTRecordID { - public enum PWTRecordID - { - Normal, - Fighting, - Flying, - Poison, - Ground, - Rock, - Bug, - Ghost, - Steel, - Fire, - Water, - Grass, - Electric, - Psychic, - Ice, - Dragon, - Dark, - Champion, - Driftveil, - Unova, - Kanto, - Johto, - Hoenn, - Sinnoh, - World, - Rental, - RentalMaster, - Mix, - MixMaster, - } + Normal, + Fighting, + Flying, + Poison, + Ground, + Rock, + Bug, + Ghost, + Steel, + Fire, + Water, + Grass, + Electric, + Psychic, + Ice, + Dragon, + Dark, + Champion, + Driftveil, + Unova, + Kanto, + Johto, + Hoenn, + Sinnoh, + World, + Rental, + RentalMaster, + Mix, + MixMaster, } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/PassPower5.cs b/PKHeX.Core/Saves/Substructures/Gen5/PassPower5.cs index 01cdaca4d..9d1083d01 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/PassPower5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/PassPower5.cs @@ -1,77 +1,76 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Text File 263 in Black2/White2 +/// +public enum PassPower5 { - /// - /// Text File 263 in Black2/White2 - /// - public enum PassPower5 - { - Encounter_Plus1 = 0, - Encounter_Plus2 = 1, - Encounter_Plus3 = 2, - Encounter_Negative1 = 3, - Encounter_Negative2 = 4, - Encounter_Negative3 = 5, - Hatching_Plus1 = 6, - Hatching_Plus2 = 7, - Hatching_Plus3 = 8, - Befriending_Plus1 = 9, - Befriending_Plus2 = 10, - Befriending_Plus3 = 11, - Bargain_Plus1 = 12, - Bargain_Plus2 = 13, - Bargain_Plus3 = 14, - HP_Plus1 = 15, - HP_Plus2 = 16, - HP_Plus3 = 17, - PP_Plus1 = 18, - PP_Plus2 = 19, - PP_Plus3 = 20, - EXP_Plus1 = 21, - EXP_Plus2 = 22, - EXP_Plus3 = 23, - EXP_Negative1 = 24, - EXP_Negative2 = 25, - EXP_Negative3 = 26, - PrizeMoney_Plus1 = 27, - PrizeMoney_Plus2 = 28, - PrizeMoney_Plus3 = 29, - Capture_Plus1 = 30, - Capture_Plus2 = 31, - Capture_Plus3 = 32, + Encounter_Plus1 = 0, + Encounter_Plus2 = 1, + Encounter_Plus3 = 2, + Encounter_Negative1 = 3, + Encounter_Negative2 = 4, + Encounter_Negative3 = 5, + Hatching_Plus1 = 6, + Hatching_Plus2 = 7, + Hatching_Plus3 = 8, + Befriending_Plus1 = 9, + Befriending_Plus2 = 10, + Befriending_Plus3 = 11, + Bargain_Plus1 = 12, + Bargain_Plus2 = 13, + Bargain_Plus3 = 14, + HP_Plus1 = 15, + HP_Plus2 = 16, + HP_Plus3 = 17, + PP_Plus1 = 18, + PP_Plus2 = 19, + PP_Plus3 = 20, + EXP_Plus1 = 21, + EXP_Plus2 = 22, + EXP_Plus3 = 23, + EXP_Negative1 = 24, + EXP_Negative2 = 25, + EXP_Negative3 = 26, + PrizeMoney_Plus1 = 27, + PrizeMoney_Plus2 = 28, + PrizeMoney_Plus3 = 29, + Capture_Plus1 = 30, + Capture_Plus2 = 31, + Capture_Plus3 = 32, - Hatching_S = 33, - Bargain_S = 34, - Befriending_S = 35, - EXP_S = 36, - PrizeMoney_S = 37, - Capture_S = 38, - HPFullRecovery_S = 39, - Hatch_MAX = 40, - Bargain_MAX = 41, - Befriending_MAX = 42, - EXP_MAX = 43, - PrizeMoney_MAX = 44, - Capture_MAX = 45, - // 46 - // 47 - None = 48, + Hatching_S = 33, + Bargain_S = 34, + Befriending_S = 35, + EXP_S = 36, + PrizeMoney_S = 37, + Capture_S = 38, + HPFullRecovery_S = 39, + Hatch_MAX = 40, + Bargain_MAX = 41, + Befriending_MAX = 42, + EXP_MAX = 43, + PrizeMoney_MAX = 44, + Capture_MAX = 45, + // 46 + // 47 + None = 48, - // B2W2 - Search_Plus1 = 49, - Search_Plus2 = 50, - Search_Plus3 = 51, - HiddenGrotto_Plus1 = 52, - HiddenGrotto_Plus2 = 53, - HiddenGrotto_Plus3 = 54, - Charm_Plus1 = 55, - Charm_Plus2 = 56, - Charm_Plus3 = 57, + // B2W2 + Search_Plus1 = 49, + Search_Plus2 = 50, + Search_Plus3 = 51, + HiddenGrotto_Plus1 = 52, + HiddenGrotto_Plus2 = 53, + HiddenGrotto_Plus3 = 54, + Charm_Plus1 = 55, + Charm_Plus2 = 56, + Charm_Plus3 = 57, - Search_S = 58, - Search_MAX = 59, - HiddenGrotto_S = 60, - HiddenGrotto_MAX = 61, - Charm_S = 62, - Charm_MAX = 63, - } + Search_S = 58, + Search_MAX = 59, + HiddenGrotto_S = 60, + HiddenGrotto_MAX = 61, + Charm_S = 62, + Charm_MAX = 63, } diff --git a/PKHeX.Core/Saves/Substructures/Gen5/PlayerData5.cs b/PKHeX.Core/Saves/Substructures/Gen5/PlayerData5.cs index 93f535ed8..40d151ef8 100644 --- a/PKHeX.Core/Saves/Substructures/Gen5/PlayerData5.cs +++ b/PKHeX.Core/Saves/Substructures/Gen5/PlayerData5.cs @@ -1,96 +1,95 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Combined save block; 0x100 for first, 0x100 for second. +/// +public sealed class PlayerData5 : SaveBlock { - /// - /// Combined save block; 0x100 for first, 0x100 for second. - /// - public sealed class PlayerData5 : SaveBlock + public PlayerData5(SAV5BW sav, int offset) : base(sav) => Offset = offset; + public PlayerData5(SAV5B2W2 sav, int offset) : base(sav) => Offset = offset; + + private Span OT_Trash => Data.AsSpan(Offset + 4, 0x10); + + public string OT { - public PlayerData5(SAV5BW sav, int offset) : base(sav) => Offset = offset; - public PlayerData5(SAV5B2W2 sav, int offset) : base(sav) => Offset = offset; - - private Span OT_Trash => Data.AsSpan(Offset + 4, 0x10); - - public string OT - { - get => SAV.GetString(OT_Trash); - set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); - } - - public int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14 + 0)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14 + 0), (ushort)value); - } - - public int SID - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14 + 2)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14 + 2), (ushort)value); - } - - public int Language - { - get => Data[Offset + 0x1E]; - set => Data[Offset + 0x1E] = (byte)value; - } - - public int Game - { - get => Data[Offset + 0x1F]; - set => Data[Offset + 0x1F] = (byte)value; - } - - public int Gender - { - get => Data[Offset + 0x21]; - set => Data[Offset + 0x21] = (byte)value; - } - - // 22,23 ?? - - public int PlayedHours - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x24)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x24), (ushort)value); - } - - public int PlayedMinutes - { - get => Data[Offset + 0x24 + 2]; - set => Data[Offset + 0x24 + 2] = (byte)value; - } - - public int PlayedSeconds - { - get => Data[Offset + 0x24 + 3]; - set => Data[Offset + 0x24 + 3] = (byte)value; - } - - public int M - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x180)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x180), (ushort)value); - } - - public int X - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x186)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x186), (ushort)value); - } - - public int Z - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x18A)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x18A), (ushort)value); - } - - public int Y - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x18E)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x18E), (ushort)value); - } + get => SAV.GetString(OT_Trash); + set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); } -} \ No newline at end of file + + public int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14 + 0)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14 + 0), (ushort)value); + } + + public int SID + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14 + 2)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14 + 2), (ushort)value); + } + + public int Language + { + get => Data[Offset + 0x1E]; + set => Data[Offset + 0x1E] = (byte)value; + } + + public int Game + { + get => Data[Offset + 0x1F]; + set => Data[Offset + 0x1F] = (byte)value; + } + + public int Gender + { + get => Data[Offset + 0x21]; + set => Data[Offset + 0x21] = (byte)value; + } + + // 22,23 ?? + + public int PlayedHours + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x24)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x24), (ushort)value); + } + + public int PlayedMinutes + { + get => Data[Offset + 0x24 + 2]; + set => Data[Offset + 0x24 + 2] = (byte)value; + } + + public int PlayedSeconds + { + get => Data[Offset + 0x24 + 3]; + set => Data[Offset + 0x24 + 3] = (byte)value; + } + + public int M + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x180)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x180), (ushort)value); + } + + public int X + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x186)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x186), (ushort)value); + } + + public int Z + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x18A)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x18A), (ushort)value); + } + + public int Y + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x18E)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x18E), (ushort)value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/BattleBox6.cs b/PKHeX.Core/Saves/Substructures/Gen6/BattleBox6.cs index b653704e8..a25aa560e 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/BattleBox6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/BattleBox6.cs @@ -1,28 +1,27 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class BattleBox6 : SaveBlock { - public sealed class BattleBox6 : SaveBlock + public BattleBox6(SAV6XY SAV, int offset) : base(SAV) => Offset = offset; + public BattleBox6(SAV6AO SAV, int offset) : base(SAV) => Offset = offset; + + private int LockedFlagOffset => Offset + (6 * PokeCrypto.SIZE_6STORED); + + public bool Locked { - public BattleBox6(SAV6XY SAV, int offset) : base(SAV) => Offset = offset; - public BattleBox6(SAV6AO SAV, int offset) : base(SAV) => Offset = offset; + get => LockedWiFiTournament || LockedLiveTournament; + set => LockedWiFiTournament = LockedLiveTournament = value; + } - private int LockedFlagOffset => Offset + (6 * PokeCrypto.SIZE_6STORED); + public bool LockedWiFiTournament + { + get => (Data[LockedFlagOffset] & 1) != 0; + set => Data[LockedFlagOffset] = (byte)((Data[Offset + LockedFlagOffset] & ~1) | (value ? 1 : 0)); + } - public bool Locked - { - get => LockedWiFiTournament || LockedLiveTournament; - set => LockedWiFiTournament = LockedLiveTournament = value; - } - - public bool LockedWiFiTournament - { - get => (Data[LockedFlagOffset] & 1) != 0; - set => Data[LockedFlagOffset] = (byte)((Data[Offset + LockedFlagOffset] & ~1) | (value ? 1 : 0)); - } - - public bool LockedLiveTournament - { - get => (Data[LockedFlagOffset] & 2) != 0; - set => Data[LockedFlagOffset] = (byte)((Data[Offset + LockedFlagOffset] & ~2) | (value ? 2 : 0)); - } + public bool LockedLiveTournament + { + get => (Data[LockedFlagOffset] & 2) != 0; + set => Data[LockedFlagOffset] = (byte)((Data[Offset + LockedFlagOffset] & ~2) | (value ? 2 : 0)); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/BoxLayout6.cs b/PKHeX.Core/Saves/Substructures/Gen6/BoxLayout6.cs index ed70f82c1..773ed5698 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/BoxLayout6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/BoxLayout6.cs @@ -1,79 +1,78 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class BoxLayout6 : SaveBlock, IBoxDetailName, IBoxDetailWallpaper { - public sealed class BoxLayout6 : SaveBlock, IBoxDetailName, IBoxDetailWallpaper + // gfstr5[31] boxNames; + // byte[31] wallpapers; + // byte Flags:7; + // byte FinalBoxUnlocked:1; + // byte UnlockedCount; + // byte CurrentBox; + + private const int StringMaxByteCount = SAV6.LongStringLength; // same for both games + private const int StringMaxLength = StringMaxByteCount / 2; + private const int BoxCount = 31; + private const int PCBackgrounds = BoxCount * StringMaxByteCount; // 0x41E; + private const int PCFlags = PCBackgrounds + BoxCount; // 0x43D; + private const int Unlocked = PCFlags + 1; // 0x43E; + private const int LastViewedBoxOffset = Unlocked + 1; // 0x43F; + + public BoxLayout6(SAV6XY sav, int offset) : base(sav) => Offset = offset; + public BoxLayout6(SAV6AO sav, int offset) : base(sav) => Offset = offset; + + public int GetBoxWallpaperOffset(int box) => Offset + PCBackgrounds + box; + + public int GetBoxWallpaper(int box) { - // gfstr5[31] boxNames; - // byte[31] wallpapers; - // byte Flags:7; - // byte FinalBoxUnlocked:1; - // byte UnlockedCount; - // byte CurrentBox; - - private const int StringMaxByteCount = SAV6.LongStringLength; // same for both games - private const int StringMaxLength = StringMaxByteCount / 2; - private const int BoxCount = 31; - private const int PCBackgrounds = BoxCount * StringMaxByteCount; // 0x41E; - private const int PCFlags = PCBackgrounds + BoxCount; // 0x43D; - private const int Unlocked = PCFlags + 1; // 0x43E; - private const int LastViewedBoxOffset = Unlocked + 1; // 0x43F; - - public BoxLayout6(SAV6XY sav, int offset) : base(sav) => Offset = offset; - public BoxLayout6(SAV6AO sav, int offset) : base(sav) => Offset = offset; - - public int GetBoxWallpaperOffset(int box) => Offset + PCBackgrounds + box; - - public int GetBoxWallpaper(int box) - { - if ((uint)box >= SAV.BoxCount) - return 0; - return Data[GetBoxWallpaperOffset(box)]; - } - - public void SetBoxWallpaper(int box, int value) - { - if ((uint)box >= SAV.BoxCount) - return; - Data[GetBoxWallpaperOffset(box)] = (byte)value; - } - - private int GetBoxNameOffset(int box) => Offset + (StringMaxByteCount * box); - - public string GetBoxName(int box) => SAV.GetString(Data.AsSpan(GetBoxNameOffset(box), StringMaxByteCount)); - - public void SetBoxName(int box, string value) - { - var span = Data.AsSpan(GetBoxNameOffset(box) + (StringMaxByteCount * box), StringMaxByteCount); - SAV.SetString(span, value.AsSpan(), StringMaxLength, StringConverterOption.ClearZero); - } - - public byte[] BoxFlags - { - get => new[] { Data[Offset + PCFlags] }; // 7 bits for wallpaper unlocks, top bit to unlock final box (delta episode) - set - { - if (value.Length != 1) - return; - Data[Offset + PCFlags] = value[0]; - } - } - - public int BoxesUnlocked - { - get => Data[Offset + Unlocked]; - set - { - if (value > BoxCount) - value = BoxCount; - if (value == BoxCount) - Data[Offset + PCFlags] |= 0x80; // set final box unlocked flag - else - Data[Offset + PCFlags] &= 0x7F; // clear final box unlocked flag - Data[Offset + Unlocked] = (byte)value; - } - } - - public int CurrentBox { get => Data[Offset + LastViewedBoxOffset]; set => Data[Offset + LastViewedBoxOffset] = (byte)value; } + if ((uint)box >= SAV.BoxCount) + return 0; + return Data[GetBoxWallpaperOffset(box)]; } -} \ No newline at end of file + + public void SetBoxWallpaper(int box, int value) + { + if ((uint)box >= SAV.BoxCount) + return; + Data[GetBoxWallpaperOffset(box)] = (byte)value; + } + + private int GetBoxNameOffset(int box) => Offset + (StringMaxByteCount * box); + + public string GetBoxName(int box) => SAV.GetString(Data.AsSpan(GetBoxNameOffset(box), StringMaxByteCount)); + + public void SetBoxName(int box, string value) + { + var span = Data.AsSpan(GetBoxNameOffset(box) + (StringMaxByteCount * box), StringMaxByteCount); + SAV.SetString(span, value.AsSpan(), StringMaxLength, StringConverterOption.ClearZero); + } + + public byte[] BoxFlags + { + get => new[] { Data[Offset + PCFlags] }; // 7 bits for wallpaper unlocks, top bit to unlock final box (delta episode) + set + { + if (value.Length != 1) + return; + Data[Offset + PCFlags] = value[0]; + } + } + + public int BoxesUnlocked + { + get => Data[Offset + Unlocked]; + set + { + if (value > BoxCount) + value = BoxCount; + if (value == BoxCount) + Data[Offset + PCFlags] |= 0x80; // set final box unlocked flag + else + Data[Offset + PCFlags] &= 0x7F; // clear final box unlocked flag + Data[Offset + Unlocked] = (byte)value; + } + } + + public int CurrentBox { get => Data[Offset + LastViewedBoxOffset]; set => Data[Offset + LastViewedBoxOffset] = (byte)value; } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/ConfigSave6.cs b/PKHeX.Core/Saves/Substructures/Gen6/ConfigSave6.cs index 94dc12dc6..bb73c9938 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/ConfigSave6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/ConfigSave6.cs @@ -1,116 +1,115 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class ConfigSave6 : SaveBlock { - public sealed class ConfigSave6 : SaveBlock + /* ===32 bits=== + * talkSpeed:2 0,1 + * battleAnim:1 2 + * battleStyle:1 3 + * unknown:4 4..7 + * battleBG:5 8..12 + * buttonMode:2 13,14 + * forcedSave:1 15 + * flag1:1 16 + * enablePSS:1 17 + * enablePR:1 18 + * unknown:14 19..31 + */ + + public ConfigSave6(SAV6XY sav, int offset) : base(sav) => Offset = offset; + public ConfigSave6(SAV6AO sav, int offset) : base(sav) => Offset = offset; + + public int ConfigValue { - /* ===32 bits=== - * talkSpeed:2 0,1 - * battleAnim:1 2 - * battleStyle:1 3 - * unknown:4 4..7 - * battleBG:5 8..12 - * buttonMode:2 13,14 - * forcedSave:1 15 - * flag1:1 16 - * enablePSS:1 17 - * enablePR:1 18 - * unknown:14 19..31 - */ + get => ReadInt32LittleEndian(Data.AsSpan(Offset)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset), value); + } - public ConfigSave6(SAV6XY sav, int offset) : base(sav) => Offset = offset; - public ConfigSave6(SAV6AO sav, int offset) : base(sav) => Offset = offset; + public int TalkingSpeed + { + get => ConfigValue & 3; + set => ConfigValue = (ConfigValue & ~3) | (value & 3); + } - public int ConfigValue - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset), value); - } + public BattleAnimationSetting BattleAnimation + { + // Effects OFF = 1, Effects ON = 0 + get => (BattleAnimationSetting)((ConfigValue >> 2) & 1); + set => ConfigValue = (ConfigValue & ~(1 << 2)) | ((byte)value << 2); + } - public int TalkingSpeed - { - get => ConfigValue & 3; - set => ConfigValue = (ConfigValue & ~3) | (value & 3); - } + public BattleStyleSetting BattleStyle + { + // SET = 1, SWITCH = 0 + get => (BattleStyleSetting)((ConfigValue >> 3) & 1); + set => ConfigValue = (ConfigValue & ~(1 << 3)) | ((byte)value << 3); + } - public BattleAnimationSetting BattleAnimation - { - // Effects OFF = 1, Effects ON = 0 - get => (BattleAnimationSetting)((ConfigValue >> 2) & 1); - set => ConfigValue = (ConfigValue & ~(1 << 2)) | ((byte)value << 2); - } + // UNKNOWN? - public BattleStyleSetting BattleStyle - { - // SET = 1, SWITCH = 0 - get => (BattleStyleSetting)((ConfigValue >> 3) & 1); - set => ConfigValue = (ConfigValue & ~(1 << 3)) | ((byte)value << 3); - } + public int BattleBackground + { + // Only values 0-14 are used. + get => (ConfigValue >> 8) & 0x1F; + set => ConfigValue = (ConfigValue & ~(0x1F << 8)) | (value << 8); + } - // UNKNOWN? + public int ButtonMode + { + get => (ConfigValue >> 13) & 3; + set => ConfigValue = (ConfigValue & ~(1 << 13)) | (value << 13); + } - public int BattleBackground - { - // Only values 0-14 are used. - get => (ConfigValue >> 8) & 0x1F; - set => ConfigValue = (ConfigValue & ~(0x1F << 8)) | (value << 8); - } + public int ForceSaveBeforeOnline + { + get => (ConfigValue >> 15) & 1; + set => ConfigValue = (ConfigValue & ~(1 << 15)) | (value << 15); + } - public int ButtonMode - { - get => (ConfigValue >> 13) & 3; - set => ConfigValue = (ConfigValue & ~(1 << 13)) | (value << 13); - } + public int EnableFlag1 + { + get => (ConfigValue >> 16) & 1; + set => ConfigValue = (ConfigValue & ~(1 << 16)) | (value << 16); + } - public int ForceSaveBeforeOnline - { - get => (ConfigValue >> 15) & 1; - set => ConfigValue = (ConfigValue & ~(1 << 15)) | (value << 15); - } + public int EnablePSSFlag + { + get => (ConfigValue >> 17) & 1; + set => ConfigValue = (ConfigValue & ~(1 << 17)) | (value << 17); + } - public int EnableFlag1 - { - get => (ConfigValue >> 16) & 1; - set => ConfigValue = (ConfigValue & ~(1 << 16)) | (value << 16); - } + public int EnableTrainerPRFlag + { + get => (ConfigValue >> 18) & 1; + set => ConfigValue = (ConfigValue & ~(1 << 18)) | (value << 18); + } - public int EnablePSSFlag - { - get => (ConfigValue >> 17) & 1; - set => ConfigValue = (ConfigValue & ~(1 << 17)) | (value << 17); - } + // NOTE: BELOW COMES FROM LGPE. MAYBE THIS IS WHAT THEY USE THE FLAGS FOR? - public int EnableTrainerPRFlag - { - get => (ConfigValue >> 18) & 1; - set => ConfigValue = (ConfigValue & ~(1 << 18)) | (value << 18); - } + /// + /// for messages, stored with skipped in the enumeration. + /// + public int Language + { + get => GetLanguageID((ConfigValue >> 4) & 0xF); + set => ConfigValue = ((ConfigValue & ~0xF0) | (SetLanguageID(value) << 4)); + } - // NOTE: BELOW COMES FROM LGPE. MAYBE THIS IS WHAT THEY USE THE FLAGS FOR? + private static int GetLanguageID(int rawValue) => rawValue >= (int)LanguageID.UNUSED_6 ? rawValue + 1 : rawValue; // sets langBank to LanguageID + private static int SetLanguageID(int rawValue) => rawValue > (int)LanguageID.UNUSED_6 ? rawValue - 1 : rawValue; // sets LanguageID to langBank - /// - /// for messages, stored with skipped in the enumeration. - /// - public int Language - { - get => GetLanguageID((ConfigValue >> 4) & 0xF); - set => ConfigValue = ((ConfigValue & ~0xF0) | SetLanguageID(value) << 4); - } + public enum BattleAnimationSetting + { + EffectsON, + EffectsOFF, + } - private static int GetLanguageID(int rawValue) => rawValue >= (int)LanguageID.UNUSED_6 ? rawValue + 1 : rawValue; // sets langBank to LanguageID - private static int SetLanguageID(int rawValue) => rawValue > (int)LanguageID.UNUSED_6 ? rawValue - 1 : rawValue; // sets LanguageID to langBank - - public enum BattleAnimationSetting - { - EffectsON, - EffectsOFF, - } - - public enum BattleStyleSetting - { - SWITCH, - SET, - } + public enum BattleStyleSetting + { + SWITCH, + SET, } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/Encount6.cs b/PKHeX.Core/Saves/Substructures/Gen6/Encount6.cs index 944aba742..f572e3436 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/Encount6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/Encount6.cs @@ -2,135 +2,134 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Swarm and other overworld info +/// +public sealed class Encount6 : SaveBlock { - /// - /// Swarm and other overworld info - /// - public sealed class Encount6 : SaveBlock + public Encount6(SAV6XY SAV, int offset) : base(SAV) => Offset = offset; + public Encount6(SAV6AO SAV, int offset) : base(SAV) => Offset = offset; + + public ushort RepelItemUsed { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), value); } + public byte RepelSteps { get => Data[Offset + 0x02]; set => Data[Offset + 0x02] = value; } + + // 0x04 + + public PokeRadar6 Radar { - public Encount6(SAV6XY SAV, int offset) : base(SAV) => Offset = offset; - public Encount6(SAV6AO SAV, int offset) : base(SAV) => Offset = offset; - - public ushort RepelItemUsed { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), value); } - public byte RepelSteps { get => Data[Offset + 0x02]; set => Data[Offset + 0x02] = value; } - - // 0x04 - - public PokeRadar6 Radar - { - get => new(Data.Slice(Offset + 0x04, PokeRadar6.SIZE)); - set => value.Data.CopyTo(Data, Offset + 0x04); - } - - // 0x1C - - public Roamer6 Roamer - { - get => new(Data.Slice(Offset + 0x1C, Roamer6.SIZE)); - set => value.Data.CopyTo(Data, Offset + 0x1C); - } - - // 0x44 - - // 4 bytes at end?? + get => new(Data.Slice(Offset + 0x04, PokeRadar6.SIZE)); + set => value.Data.CopyTo(Data, Offset + 0x04); } - [TypeConverter(typeof(ValueTypeTypeConverter))] - public sealed class PokeRadar6 + // 0x1C + + public Roamer6 Roamer { - public const int SIZE = 2 + (RecordCount * PokeRadarRecord.SIZE); // 0x18 - - private const int MaxCharge = 50; - private const int RecordCount = 5; - - public readonly byte[] Data; - - public PokeRadar6(byte[] data) => Data = data; - public override string ToString() => ((Species)PokeRadarSpecies).ToString(); - - public ushort PokeRadarSpecies { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), value); } - private ushort PokeRadarPacked { get => ReadUInt16LittleEndian(Data.AsSpan(0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(0x02), value); } - - public int PokeRadarCharge { get => PokeRadarPacked & 0x3FFF; set => PokeRadarPacked = (ushort)((PokeRadarPacked & ~0x3FFF) | Math.Min(MaxCharge, value)); } - public bool PokeRadarFlag1 { get => PokeRadarPacked >> 14 != 0; set => PokeRadarPacked = (ushort)((PokeRadarPacked & ~(1 << 14)) | (value ? (1 << 14) : 0)); } - public bool PokeRadarFlag2 { get => PokeRadarPacked >> 15 != 0; set => PokeRadarPacked = (ushort)((PokeRadarPacked & ~(1 << 15)) | (value ? (1 << 15) : 0)); } - - public PokeRadarRecord GetRecord(int index) => PokeRadarRecord.ReadRecord(Data.AsSpan(GetRecordOffset(index))); - public void SetRecord(PokeRadarRecord record, int index) => record.WriteRecord(Data.AsSpan(GetRecordOffset(index))); - - private static int GetRecordOffset(int index) - { - if ((uint) index >= RecordCount) - throw new ArgumentOutOfRangeException(nameof(index)); - - return 6 + (index * 2); - } - - public PokeRadarRecord Record1 { get => GetRecord(0); set => SetRecord(value, 0); } - public PokeRadarRecord Record2 { get => GetRecord(1); set => SetRecord(value, 1); } - public PokeRadarRecord Record3 { get => GetRecord(2); set => SetRecord(value, 2); } - public PokeRadarRecord Record4 { get => GetRecord(3); set => SetRecord(value, 3); } - public PokeRadarRecord Record5 { get => GetRecord(4); set => SetRecord(value, 4); } + get => new(Data.Slice(Offset + 0x1C, Roamer6.SIZE)); + set => value.Data.CopyTo(Data, Offset + 0x1C); } - [TypeConverter(typeof(ValueTypeTypeConverter))] - public sealed class PokeRadarRecord + // 0x44 + + // 4 bytes at end?? +} + +[TypeConverter(typeof(ValueTypeTypeConverter))] +public sealed class PokeRadar6 +{ + public const int SIZE = 2 + (RecordCount * PokeRadarRecord.SIZE); // 0x18 + + private const int MaxCharge = 50; + private const int RecordCount = 5; + + public readonly byte[] Data; + + public PokeRadar6(byte[] data) => Data = data; + public override string ToString() => ((Species)PokeRadarSpecies).ToString(); + + public ushort PokeRadarSpecies { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), value); } + private ushort PokeRadarPacked { get => ReadUInt16LittleEndian(Data.AsSpan(0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(0x02), value); } + + public int PokeRadarCharge { get => PokeRadarPacked & 0x3FFF; set => PokeRadarPacked = (ushort)((PokeRadarPacked & ~0x3FFF) | Math.Min(MaxCharge, value)); } + public bool PokeRadarFlag1 { get => PokeRadarPacked >> 14 != 0; set => PokeRadarPacked = (ushort)((PokeRadarPacked & ~(1 << 14)) | (value ? (1 << 14) : 0)); } + public bool PokeRadarFlag2 { get => PokeRadarPacked >> 15 != 0; set => PokeRadarPacked = (ushort)((PokeRadarPacked & ~(1 << 15)) | (value ? (1 << 15) : 0)); } + + public PokeRadarRecord GetRecord(int index) => PokeRadarRecord.ReadRecord(Data.AsSpan(GetRecordOffset(index))); + public void SetRecord(PokeRadarRecord record, int index) => record.WriteRecord(Data.AsSpan(GetRecordOffset(index))); + + private static int GetRecordOffset(int index) { - public const int SIZE = 4; - public override string ToString() => ((Species)Species).ToString(); + if ((uint) index >= RecordCount) + throw new ArgumentOutOfRangeException(nameof(index)); - public ushort Species { get; set; } - public ushort Count { get; set; } - - private PokeRadarRecord(ushort species, ushort count) - { - Species = species; - Count = count; - } - - public static PokeRadarRecord ReadRecord(ReadOnlySpan data) - { - var species = ReadUInt16LittleEndian(data); - var count = ReadUInt16LittleEndian(data[2..]); - return new PokeRadarRecord(species, count); - } - - public void WriteRecord(Span data) - { - WriteUInt16LittleEndian(data, Species); - WriteUInt16LittleEndian(data[2..], Count); - } + return 6 + (index * 2); } - [TypeConverter(typeof(ValueTypeTypeConverter))] - public sealed class Roamer6 + public PokeRadarRecord Record1 { get => GetRecord(0); set => SetRecord(value, 0); } + public PokeRadarRecord Record2 { get => GetRecord(1); set => SetRecord(value, 1); } + public PokeRadarRecord Record3 { get => GetRecord(2); set => SetRecord(value, 2); } + public PokeRadarRecord Record4 { get => GetRecord(3); set => SetRecord(value, 3); } + public PokeRadarRecord Record5 { get => GetRecord(4); set => SetRecord(value, 4); } +} + +[TypeConverter(typeof(ValueTypeTypeConverter))] +public sealed class PokeRadarRecord +{ + public const int SIZE = 4; + public override string ToString() => ((Species)Species).ToString(); + + public ushort Species { get; set; } + public ushort Count { get; set; } + + private PokeRadarRecord(ushort species, ushort count) { - public const int SIZE = 0x28; - - public readonly byte[] Data; - - public Roamer6(byte[] data) => Data = data; - public override string ToString() => ((Species)Species).ToString(); - - private ushort SpecForm { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), value); } - public int Species { get => SpecForm & 0x3FF; set => SpecForm = (ushort)((SpecForm & ~0x3FF) | (value & 0x3FF)); } - public bool Flag1 { get => SpecForm >> 14 != 0; set => SpecForm = (ushort)((SpecForm & 0xBFFF) | (value ? (1 << 14) : 0)); } - public bool Flag2 { get => SpecForm >> 15 != 0; set => SpecForm = (ushort)((SpecForm & 0x7FFF) | (value ? (1 << 15) : 0)); } - - public int CurrentLevel { get => Data[0x04]; set => Data[0x04] = (byte)value; } - private int Status { get => Data[0x07]; set => Data[0x07] = (byte)value; } - public Roamer6State RoamStatus { get => (Roamer6State)((Status >> 4) & 0xF); set => Status = (Status & 0x0F) | (((int)value << 4) & 0xF0); } - - public uint TimesEncountered { get => ReadUInt32LittleEndian(Data.AsSpan(0x24)); set => WriteUInt32LittleEndian(Data.AsSpan(0x24), value); } + Species = species; + Count = count; } - public enum Roamer6State + public static PokeRadarRecord ReadRecord(ReadOnlySpan data) { - Inactive, - Roaming, - Stationary, - Defeated, - Captured, + var species = ReadUInt16LittleEndian(data); + var count = ReadUInt16LittleEndian(data[2..]); + return new PokeRadarRecord(species, count); + } + + public void WriteRecord(Span data) + { + WriteUInt16LittleEndian(data, Species); + WriteUInt16LittleEndian(data[2..], Count); } } + +[TypeConverter(typeof(ValueTypeTypeConverter))] +public sealed class Roamer6 +{ + public const int SIZE = 0x28; + + public readonly byte[] Data; + + public Roamer6(byte[] data) => Data = data; + public override string ToString() => ((Species)Species).ToString(); + + private ushort SpecForm { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), value); } + public int Species { get => SpecForm & 0x3FF; set => SpecForm = (ushort)((SpecForm & ~0x3FF) | (value & 0x3FF)); } + public bool Flag1 { get => SpecForm >> 14 != 0; set => SpecForm = (ushort)((SpecForm & 0xBFFF) | (value ? (1 << 14) : 0)); } + public bool Flag2 { get => SpecForm >> 15 != 0; set => SpecForm = (ushort)((SpecForm & 0x7FFF) | (value ? (1 << 15) : 0)); } + + public int CurrentLevel { get => Data[0x04]; set => Data[0x04] = (byte)value; } + private int Status { get => Data[0x07]; set => Data[0x07] = (byte)value; } + public Roamer6State RoamStatus { get => (Roamer6State)((Status >> 4) & 0xF); set => Status = (Status & 0x0F) | (((int)value << 4) & 0xF0); } + + public uint TimesEncountered { get => ReadUInt32LittleEndian(Data.AsSpan(0x24)); set => WriteUInt32LittleEndian(Data.AsSpan(0x24), value); } +} + +public enum Roamer6State +{ + Inactive, + Roaming, + Stationary, + Defeated, + Captured, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/Fashion6XY.cs b/PKHeX.Core/Saves/Substructures/Gen6/Fashion6XY.cs index b2a4d17ea..4a62bbfe9 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/Fashion6XY.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/Fashion6XY.cs @@ -1,23 +1,22 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Fashion6XY : SaveBlock { - public sealed class Fashion6XY : SaveBlock + public Fashion6XY(SAV6XY sav, int offset) : base(sav) => Offset = offset; + + public void UnlockAllAccessories() { - public Fashion6XY(SAV6XY sav, int offset) : base(sav) => Offset = offset; - - public void UnlockAllAccessories() - { - SAV.SetData(AllAccessories, Offset); - } - - private static readonly byte[] AllAccessories = - { - 0xFE,0xFF,0xFF,0x7E,0xFF,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xEF,0xFF,0xFF,0xFF,0xF9,0xFF,0xFB,0xFF,0xF7,0xFF,0xFF,0x0F,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF, - 0xFF,0x7E,0xFF,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEF, - 0xFF,0xFF,0xFF,0xF9,0xFF,0xFB,0xFF,0xF7,0xFF,0xFF,0x0F,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - }; + SAV.SetData(AllAccessories, Offset); } + + private static readonly byte[] AllAccessories = + { + 0xFE,0xFF,0xFF,0x7E,0xFF,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xEF,0xFF,0xFF,0xFF,0xF9,0xFF,0xFB,0xFF,0xF7,0xFF,0xFF,0x0F,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF, + 0xFF,0x7E,0xFF,0xFD,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEF, + 0xFF,0xFF,0xFF,0xF9,0xFF,0xFB,0xFF,0xF7,0xFF,0xFF,0x0F,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + }; } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/GameTime6.cs b/PKHeX.Core/Saves/Substructures/Gen6/GameTime6.cs index ddb999856..2c6673b2b 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/GameTime6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/GameTime6.cs @@ -1,19 +1,18 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core -{ - public sealed class GameTime6 : SaveBlock - { - public GameTime6(SAV6 sav, int offset) : base(sav) => Offset = offset; +namespace PKHeX.Core; - public int ResumeYear { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } - public int ResumeMonth { get => Data[Offset + 0x8]; set => Data[Offset + 0x8] = (byte)value; } - public int ResumeDay { get => Data[Offset + 0x9]; set => Data[Offset + 0x9] = (byte)value; } - public int ResumeHour { get => Data[Offset + 0xB]; set => Data[Offset + 0xB] = (byte)value; } - public int ResumeMinute { get => Data[Offset + 0xC]; set => Data[Offset + 0xC] = (byte)value; } - public int ResumeSeconds { get => Data[Offset + 0xD]; set => Data[Offset + 0xD] = (byte)value; } - public uint SecondsToStart { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x18), value); } - public uint SecondsToFame { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x20)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x20), value); } - } -} \ No newline at end of file +public sealed class GameTime6 : SaveBlock +{ + public GameTime6(SAV6 sav, int offset) : base(sav) => Offset = offset; + + public int ResumeYear { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } + public int ResumeMonth { get => Data[Offset + 0x8]; set => Data[Offset + 0x8] = (byte)value; } + public int ResumeDay { get => Data[Offset + 0x9]; set => Data[Offset + 0x9] = (byte)value; } + public int ResumeHour { get => Data[Offset + 0xB]; set => Data[Offset + 0xB] = (byte)value; } + public int ResumeMinute { get => Data[Offset + 0xC]; set => Data[Offset + 0xC] = (byte)value; } + public int ResumeSeconds { get => Data[Offset + 0xD]; set => Data[Offset + 0xD] = (byte)value; } + public uint SecondsToStart { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x18), value); } + public uint SecondsToFame { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x20)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x20), value); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/IGymTeamInfo.cs b/PKHeX.Core/Saves/Substructures/Gen6/IGymTeamInfo.cs index b02cd12a9..3a9703c8f 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/IGymTeamInfo.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/IGymTeamInfo.cs @@ -1,39 +1,38 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IGymTeamInfo { - public interface IGymTeamInfo + ushort GetBadgeVictorySpecies(uint badge, uint slot); + void SetBadgeVictorySpecies(uint badge, uint slot, ushort species); +} + +public static class GymTeamInfoExtensions +{ + public static ushort[][] GetGymTeams(this IGymTeamInfo info) { - ushort GetBadgeVictorySpecies(uint badge, uint slot); - void SetBadgeVictorySpecies(uint badge, uint slot, ushort species); + ushort[][] teams = new ushort[8][]; + for (uint badge = 0; badge < teams.Length; badge++) + teams[badge] = GetGymTeam(info, badge); + return teams; } - public static class GymTeamInfoExtensions + private static ushort[] GetGymTeam(IGymTeamInfo info, uint badge) { - public static ushort[][] GetGymTeams(this IGymTeamInfo info) - { - ushort[][] teams = new ushort[8][]; - for (uint badge = 0; badge < teams.Length; badge++) - teams[badge] = GetGymTeam(info, badge); - return teams; - } - - private static ushort[] GetGymTeam(IGymTeamInfo info, uint badge) - { - var team = new ushort[6]; - for (uint slot = 0; slot < team.Length; slot++) - team[slot] = info.GetBadgeVictorySpecies(badge, slot); - return team; - } - - public static void SetGymTeams(this IGymTeamInfo info, ushort[][] teams) - { - for (uint badge = 0; badge < teams.Length; badge++) - info.SetGymTeam(badge, teams[badge]); - } - - public static void SetGymTeam(this IGymTeamInfo info, uint badge, ushort[] team) - { - for (uint slot = 0; slot < team.Length; slot++) - info.SetBadgeVictorySpecies(badge, slot, team[slot]); - } + var team = new ushort[6]; + for (uint slot = 0; slot < team.Length; slot++) + team[slot] = info.GetBadgeVictorySpecies(badge, slot); + return team; } -} \ No newline at end of file + + public static void SetGymTeams(this IGymTeamInfo info, ushort[][] teams) + { + for (uint badge = 0; badge < teams.Length; badge++) + info.SetGymTeam(badge, teams[badge]); + } + + public static void SetGymTeam(this IGymTeamInfo info, uint badge, ushort[] team) + { + for (uint slot = 0; slot < team.Length; slot++) + info.SetBadgeVictorySpecies(badge, slot, team[slot]); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/ItemInfo6.cs b/PKHeX.Core/Saves/Substructures/Gen6/ItemInfo6.cs index 76dc12d8b..926c3d84b 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/ItemInfo6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/ItemInfo6.cs @@ -1,55 +1,54 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class ItemInfo6 : SaveBlock { - public sealed class ItemInfo6 : SaveBlock + public ItemInfo6(SAV6 sav, int offset) : base(sav) => Offset = offset; + + private const int BoundItemCount = 4; + private const int RecentItemCount = 12; + + public int[] SelectItems { - public ItemInfo6(SAV6 sav, int offset) : base(sav) => Offset = offset; - - private const int BoundItemCount = 4; - private const int RecentItemCount = 12; - - public int[] SelectItems + // UP,RIGHT,DOWN,LEFT + get { - // UP,RIGHT,DOWN,LEFT - get - { - var span = Data.AsSpan(Offset + 10); - int[] list = new int[BoundItemCount]; - for (int i = 0; i < list.Length; i++) - list[i] = ReadUInt16LittleEndian(span[(2 * i)..]); - return list; - } - set - { - if (value.Length != BoundItemCount) - throw new ArgumentException(nameof(value)); - var span = Data.AsSpan(Offset + 10); - for (int i = 0; i < value.Length; i++) - WriteUInt16LittleEndian(span[(2 * i)..], (ushort)value[i]); - } + var span = Data.AsSpan(Offset + 10); + int[] list = new int[BoundItemCount]; + for (int i = 0; i < list.Length; i++) + list[i] = ReadUInt16LittleEndian(span[(2 * i)..]); + return list; } - - public int[] RecentItems + set { - // Items recently interacted with (Give, Use) - get - { - var span = Data.AsSpan(Offset + 20); - int[] list = new int[RecentItemCount]; - for (int i = 0; i < list.Length; i++) - list[i] = ReadUInt16LittleEndian(span[(2 * i)..]); - return list; - } - set - { - if (value.Length != RecentItemCount) - throw new ArgumentException(nameof(value)); - var span = Data.AsSpan(Offset + 20); - for (int i = 0; i < value.Length; i++) - WriteUInt16LittleEndian(span[(2 * i)..], (ushort)value[i]); - } + if (value.Length != BoundItemCount) + throw new ArgumentException(nameof(value)); + var span = Data.AsSpan(Offset + 10); + for (int i = 0; i < value.Length; i++) + WriteUInt16LittleEndian(span[(2 * i)..], (ushort)value[i]); + } + } + + public int[] RecentItems + { + // Items recently interacted with (Give, Use) + get + { + var span = Data.AsSpan(Offset + 20); + int[] list = new int[RecentItemCount]; + for (int i = 0; i < list.Length; i++) + list[i] = ReadUInt16LittleEndian(span[(2 * i)..]); + return list; + } + set + { + if (value.Length != RecentItemCount) + throw new ArgumentException(nameof(value)); + var span = Data.AsSpan(Offset + 20); + for (int i = 0; i < value.Length; i++) + WriteUInt16LittleEndian(span[(2 * i)..], (ushort)value[i]); } } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/LinkBlock6.cs b/PKHeX.Core/Saves/Substructures/Gen6/LinkBlock6.cs index b2df53e2f..f10f56912 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/LinkBlock6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/LinkBlock6.cs @@ -1,36 +1,35 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class LinkBlock6 : SaveBlock { - public sealed class LinkBlock6 : SaveBlock + public LinkBlock6(SAV6XY sav, int offset) : base(sav) => Offset = offset; + public LinkBlock6(SAV6AO sav, int offset) : base(sav) => Offset = offset; + + public byte[] GetLinkInfoData() => Data.Slice(Offset + 0x1FF, PL6.Size); + public PL6 GetLinkInfo() => new(GetLinkInfoData()); + + public void SetLinkInfoData(ReadOnlySpan data) { - public LinkBlock6(SAV6XY sav, int offset) : base(sav) => Offset = offset; - public LinkBlock6(SAV6AO sav, int offset) : base(sav) => Offset = offset; - - public byte[] GetLinkInfoData() => Data.Slice(Offset + 0x1FF, PL6.Size); - public PL6 GetLinkInfo() => new(GetLinkInfoData()); - - public void SetLinkInfoData(ReadOnlySpan data) - { - data.CopyTo(Data.AsSpan(Offset)); - Checksum = GetCalculatedChecksum(); // [app,chk) - } - - public void SetLinkInfo(PL6 pl6) - { - pl6.Data.CopyTo(Data, Offset + 0x1FF); - Checksum = GetCalculatedChecksum(); // [app,chk) - } - - private ushort GetCalculatedChecksum() => Checksums.CRC16_CCITT(new ReadOnlySpan(Data, Offset + 0x200, 0xC48 - 4 - 0x200)); // [app,chk) - - private int GetChecksumOffset() => Offset + 0xC48 - 4; - - public ushort Checksum - { - get => ReadUInt16LittleEndian(Data.AsSpan(GetChecksumOffset())); - set => WriteUInt16LittleEndian(Data.AsSpan(GetChecksumOffset()), value); - } + data.CopyTo(Data.AsSpan(Offset)); + Checksum = GetCalculatedChecksum(); // [app,chk) } -} \ No newline at end of file + + public void SetLinkInfo(PL6 pl6) + { + pl6.Data.CopyTo(Data, Offset + 0x1FF); + Checksum = GetCalculatedChecksum(); // [app,chk) + } + + private ushort GetCalculatedChecksum() => Checksums.CRC16_CCITT(new ReadOnlySpan(Data, Offset + 0x200, 0xC48 - 4 - 0x200)); // [app,chk) + + private int GetChecksumOffset() => Offset + 0xC48 - 4; + + public ushort Checksum + { + get => ReadUInt16LittleEndian(Data.AsSpan(GetChecksumOffset())); + set => WriteUInt16LittleEndian(Data.AsSpan(GetChecksumOffset()), value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/MaisonBlock.cs b/PKHeX.Core/Saves/Substructures/Gen6/MaisonBlock.cs index 48706e7c4..9371d29ca 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/MaisonBlock.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/MaisonBlock.cs @@ -1,21 +1,20 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MaisonBlock : SaveBlock { - public sealed class MaisonBlock : SaveBlock - { - public MaisonBlock(SAV6XY sav, int offset) : base(sav) => Offset = offset; - public MaisonBlock(SAV6AO sav, int offset) : base(sav) => Offset = offset; + public MaisonBlock(SAV6XY sav, int offset) : base(sav) => Offset = offset; + public MaisonBlock(SAV6AO sav, int offset) : base(sav) => Offset = offset; - // 5 * [u16*4: normal,super,normalStreak,superStreak] - public const int MaisonStatCount = 20; + // 5 * [u16*4: normal,super,normalStreak,superStreak] + public const int MaisonStatCount = 20; - public ushort GetMaisonStat(int index) => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1C0 + (2 * index))); - public void SetMaisonStat(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1C0 + (2 * index)), value); + public ushort GetMaisonStat(int index) => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1C0 + (2 * index))); + public void SetMaisonStat(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1C0 + (2 * index)), value); - private static int GetMaisonStatIndex(BattleStyle6 type, bool streak, bool super) => ((int)type << 2) | (streak ? 2 : 0) | (super ? 1 : 0); - public ushort GetMaisonStat(BattleStyle6 type, bool streak, bool super) => GetMaisonStat(GetMaisonStatIndex(type, streak, super)); - public void SetMaisonStat(BattleStyle6 type, bool streak, bool super, ushort value) => SetMaisonStat(GetMaisonStatIndex(type, streak, super), value); - } + private static int GetMaisonStatIndex(BattleStyle6 type, bool streak, bool super) => ((int)type << 2) | (streak ? 2 : 0) | (super ? 1 : 0); + public ushort GetMaisonStat(BattleStyle6 type, bool streak, bool super) => GetMaisonStat(GetMaisonStatIndex(type, streak, super)); + public void SetMaisonStat(BattleStyle6 type, bool streak, bool super, ushort value) => SetMaisonStat(GetMaisonStatIndex(type, streak, super), value); } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/Misc6AO.cs b/PKHeX.Core/Saves/Substructures/Gen6/Misc6AO.cs index a59e579a3..cc48caa57 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/Misc6AO.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/Misc6AO.cs @@ -1,40 +1,39 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IMisc6 { - public interface IMisc6 + uint Money { get; set; } +} + +public sealed class Misc6AO : SaveBlock, IMisc6 +{ + public Misc6AO(SAV6AO sav, int offset) : base(sav) => Offset = offset; + public Misc6AO(SAV6AODemo sav, int offset) : base(sav) => Offset = offset; + + public uint Money { - uint Money { get; set; } + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x8)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x8), value); } - public sealed class Misc6AO : SaveBlock, IMisc6 + public int Badges { - public Misc6AO(SAV6AO sav, int offset) : base(sav) => Offset = offset; - public Misc6AO(SAV6AODemo sav, int offset) : base(sav) => Offset = offset; - - public uint Money - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x8)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x8), value); - } - - public int Badges - { - get => Data[Offset + 0xC]; - set => Data[Offset + 0xC] = (byte)value; - } - - public int BP - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x30)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x30), (ushort)value); - } - - public int Vivillon - { - get => Data[Offset + 0x44]; - set => Data[Offset + 0x44] = (byte)value; - } + get => Data[Offset + 0xC]; + set => Data[Offset + 0xC] = (byte)value; } -} \ No newline at end of file + + public int BP + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x30)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x30), (ushort)value); + } + + public int Vivillon + { + get => Data[Offset + 0x44]; + set => Data[Offset + 0x44] = (byte)value; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/Misc6XY.cs b/PKHeX.Core/Saves/Substructures/Gen6/Misc6XY.cs index 6f7ba0e65..1d6e5e8b1 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/Misc6XY.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/Misc6XY.cs @@ -1,34 +1,33 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Misc6XY : SaveBlock, IMisc6 { - public sealed class Misc6XY : SaveBlock, IMisc6 + public Misc6XY(SAV6XY sav, int offset) : base(sav) => Offset = offset; + + public uint Money { - public Misc6XY(SAV6XY sav, int offset) : base(sav) => Offset = offset; - - public uint Money - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x8)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x8), value); - } - - public int Badges - { - get => Data[Offset + 0xC]; - set => Data[Offset + 0xC] = (byte)value; - } - - public int BP - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x3C)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x3C), (ushort)value); - } - - public int Vivillon - { - get => Data[Offset + 0x50]; - set => Data[Offset + 0x50] = (byte)value; - } + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x8)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x8), value); } -} \ No newline at end of file + + public int Badges + { + get => Data[Offset + 0xC]; + set => Data[Offset + 0xC] = (byte)value; + } + + public int BP + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x3C)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x3C), (ushort)value); + } + + public int Vivillon + { + get => Data[Offset + 0x50]; + set => Data[Offset + 0x50] = (byte)value; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/MyItem6AO.cs b/PKHeX.Core/Saves/Substructures/Gen6/MyItem6AO.cs index b32009b97..ce99d2c19 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/MyItem6AO.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/MyItem6AO.cs @@ -1,33 +1,32 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyItem6AO : MyItem { - public sealed class MyItem6AO : MyItem + private const int HeldItem = 0; // 0 + private const int KeyItem = 0x640; // 1 + private const int TMHM = 0x7C0; // 2 + private const int Medicine = 0x970; // 3, +2 items shift because 2 HMs added + private const int Berry = 0xA70; // 4 + + public MyItem6AO(SAV6AO SAV, int offset) : base(SAV) => Offset = offset; + public MyItem6AO(SAV6AODemo SAV, int offset) : base(SAV) => Offset = offset; + + public override IReadOnlyList Inventory { - private const int HeldItem = 0; // 0 - private const int KeyItem = 0x640; // 1 - private const int TMHM = 0x7C0; // 2 - private const int Medicine = 0x970; // 3, +2 items shift because 2 HMs added - private const int Berry = 0xA70; // 4 - - public MyItem6AO(SAV6AO SAV, int offset) : base(SAV) => Offset = offset; - public MyItem6AO(SAV6AODemo SAV, int offset) : base(SAV) => Offset = offset; - - public override IReadOnlyList Inventory + get { - get + InventoryPouch[] pouch = { - InventoryPouch[] pouch = - { - new InventoryPouch4(InventoryType.Items, Legal.Pouch_Items_AO, 999, Offset + HeldItem), - new InventoryPouch4(InventoryType.KeyItems, Legal.Pouch_Key_AO, 1, Offset + KeyItem), - new InventoryPouch4(InventoryType.TMHMs, Legal.Pouch_TMHM_AO, 1, Offset + TMHM), - new InventoryPouch4(InventoryType.Medicine, Legal.Pouch_Medicine_AO, 999, Offset + Medicine), - new InventoryPouch4(InventoryType.Berries, Legal.Pouch_Berry_XY, 999, Offset + Berry), - }; - return pouch.LoadAll(Data); - } - set => value.SaveAll(Data); + new InventoryPouch4(InventoryType.Items, Legal.Pouch_Items_AO, 999, Offset + HeldItem), + new InventoryPouch4(InventoryType.KeyItems, Legal.Pouch_Key_AO, 1, Offset + KeyItem), + new InventoryPouch4(InventoryType.TMHMs, Legal.Pouch_TMHM_AO, 1, Offset + TMHM), + new InventoryPouch4(InventoryType.Medicine, Legal.Pouch_Medicine_AO, 999, Offset + Medicine), + new InventoryPouch4(InventoryType.Berries, Legal.Pouch_Berry_XY, 999, Offset + Berry), + }; + return pouch.LoadAll(Data); } + set => value.SaveAll(Data); } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/MyItem6XY.cs b/PKHeX.Core/Saves/Substructures/Gen6/MyItem6XY.cs index 0bc1b014c..ba213cca5 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/MyItem6XY.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/MyItem6XY.cs @@ -1,32 +1,31 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyItem6XY : MyItem { - public sealed class MyItem6XY : MyItem + private const int HeldItem = 0; // 0 + private const int KeyItem = 0x640; // 1 + private const int TMHM = 0x7C0; // 2 + private const int Medicine = 0x968; // 3 + private const int Berry = 0xA68; // 4 + + public MyItem6XY(SaveFile SAV, int offset) : base(SAV) => Offset = offset; + + public override IReadOnlyList Inventory { - private const int HeldItem = 0; // 0 - private const int KeyItem = 0x640; // 1 - private const int TMHM = 0x7C0; // 2 - private const int Medicine = 0x968; // 3 - private const int Berry = 0xA68; // 4 - - public MyItem6XY(SaveFile SAV, int offset) : base(SAV) => Offset = offset; - - public override IReadOnlyList Inventory + get { - get + InventoryPouch[] pouch = { - InventoryPouch[] pouch = - { - new InventoryPouch4(InventoryType.Items, Legal.Pouch_Items_XY, 999, Offset + HeldItem), - new InventoryPouch4(InventoryType.KeyItems, Legal.Pouch_Key_XY, 1, Offset + KeyItem), - new InventoryPouch4(InventoryType.TMHMs, Legal.Pouch_TMHM_XY, 1, Offset + TMHM), - new InventoryPouch4(InventoryType.Medicine, Legal.Pouch_Medicine_XY, 999, Offset + Medicine), - new InventoryPouch4(InventoryType.Berries, Legal.Pouch_Berry_XY, 999, Offset + Berry), - }; - return pouch.LoadAll(Data); - } - set => value.SaveAll(Data); + new InventoryPouch4(InventoryType.Items, Legal.Pouch_Items_XY, 999, Offset + HeldItem), + new InventoryPouch4(InventoryType.KeyItems, Legal.Pouch_Key_XY, 1, Offset + KeyItem), + new InventoryPouch4(InventoryType.TMHMs, Legal.Pouch_TMHM_XY, 1, Offset + TMHM), + new InventoryPouch4(InventoryType.Medicine, Legal.Pouch_Medicine_XY, 999, Offset + Medicine), + new InventoryPouch4(InventoryType.Berries, Legal.Pouch_Berry_XY, 999, Offset + Berry), + }; + return pouch.LoadAll(Data); } + set => value.SaveAll(Data); } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/MyStatus6.cs b/PKHeX.Core/Saves/Substructures/Gen6/MyStatus6.cs index b376cdb39..dd6464b92 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/MyStatus6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/MyStatus6.cs @@ -1,128 +1,127 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public class MyStatus6 : SaveBlock, IRegionOrigin { - public class MyStatus6 : SaveBlock, IRegionOrigin + public MyStatus6(SAV6 sav, int offset) : base(sav) => Offset = offset; + + public int TID { - public MyStatus6(SAV6 sav, int offset) : base(sav) => Offset = offset; + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), (ushort)value); + } - public int TID + public int SID + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 2)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 2), (ushort)value); + } + + public int Game + { + get => Data[Offset + 4]; + set => Data[Offset + 4] = (byte)value; + } + + public int Gender + { + get => Data[Offset + 5]; + set => Data[Offset + 5] = (byte)value; + } + + public int MultiplayerSpriteID_1 + { + get => Data[Offset + 6]; + set => Data[Offset + 6] = (byte)value; + } + + public int MultiplayerSpriteID_2 + { + get => Data[Offset + 7]; + set => Data[Offset + 7] = (byte)value; + } + + public const int GameSyncIDSize = 16; // 64 bits + + public string GameSyncID + { + get => Util.GetHexStringFromBytes(Data, Offset + 0x08, GameSyncIDSize / 2); + set { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), (ushort)value); - } + if (value.Length != GameSyncIDSize) + throw new ArgumentException(nameof(value)); - public int SID - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 2)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 2), (ushort)value); - } - - public int Game - { - get => Data[Offset + 4]; - set => Data[Offset + 4] = (byte)value; - } - - public int Gender - { - get => Data[Offset + 5]; - set => Data[Offset + 5] = (byte)value; - } - - public int MultiplayerSpriteID_1 - { - get => Data[Offset + 6]; - set => Data[Offset + 6] = (byte)value; - } - - public int MultiplayerSpriteID_2 - { - get => Data[Offset + 7]; - set => Data[Offset + 7] = (byte)value; - } - - public const int GameSyncIDSize = 16; // 64 bits - - public string GameSyncID - { - get => Util.GetHexStringFromBytes(Data, Offset + 0x08, GameSyncIDSize / 2); - set - { - if (value.Length != GameSyncIDSize) - throw new ArgumentException(nameof(value)); - - var data = Util.GetBytesFromHexString(value); - SAV.SetData(data, Offset + 0x08); - } - } - - public byte Region - { - get => Data[Offset + 0x26]; - set => Data[Offset + 0x26] = value; - } - - public byte Country - { - get => Data[Offset + 0x27]; - set => Data[Offset + 0x27] = value; - } - - public decimal Latitude // don't use the setters - { - get => (ReadInt16LittleEndian(Data.AsSpan(Offset + 0x28)) * 180m) / 0x8000; - set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0x28), (short)((value * 0x8000) / 180m)); - } - - public decimal Longitude // don't use the setters - { - get => (ReadInt16LittleEndian(Data.AsSpan(Offset + 0x2A)) * 180m) / 0x8000; - set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0x2A), (short)((value * 0x8000) / 180m)); - } - - public byte ConsoleRegion - { - get => Data[Offset + 0x2C]; - set => Data[Offset + 0x2C] = value; - } - - public int Language - { - get => Data[Offset + 0x2D]; - set => Data[Offset + 0x2D] = (byte)value; - } - - private Span OT_Trash => Data.AsSpan(Offset + 0x48, 0x1A); - - public string OT - { - get => SAV.GetString(OT_Trash); - set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); - } - - private Span GetSayingSpan(int say) => Data.AsSpan(GetSayingOffset(say), SAV6.LongStringLength); - private int GetSayingOffset(int say) => Offset + 0x7C + (SAV6.LongStringLength * say); - private string GetSaying(int say) => SAV.GetString(GetSayingSpan(say)); - private void SetSaying(int say, string value) => SAV.SetString(GetSayingSpan(say), value.AsSpan(), SAV6.LongStringLength / 2, StringConverterOption.ClearZero); - - public string Saying1 { get => GetSaying(0); set => SetSaying(0, value); } - public string Saying2 { get => GetSaying(1); set => SetSaying(1, value); } - public string Saying3 { get => GetSaying(2); set => SetSaying(2, value); } - public string Saying4 { get => GetSaying(3); set => SetSaying(3, value); } - public string Saying5 { get => GetSaying(4); set => SetSaying(4, value); } - - public bool IsMegaEvolutionUnlocked - { - get => (Data[Offset + 0x14A] & 0x01) != 0; - set => Data[Offset + 0x14A] = (byte)((Data[Offset + 0x14A] & 0xFE) | (value ? 1 : 0)); // in battle - } - - public bool IsMegaRayquazaUnlocked - { - get => (Data[Offset + 0x14A] & 0x02) != 0; - set => Data[Offset + 0x14A] = (byte)((Data[Offset + 0x14A] & ~2) | (value ? 2 : 0)); // in battle + var data = Util.GetBytesFromHexString(value); + SAV.SetData(data, Offset + 0x08); } } -} \ No newline at end of file + + public byte Region + { + get => Data[Offset + 0x26]; + set => Data[Offset + 0x26] = value; + } + + public byte Country + { + get => Data[Offset + 0x27]; + set => Data[Offset + 0x27] = value; + } + + public decimal Latitude // don't use the setters + { + get => (ReadInt16LittleEndian(Data.AsSpan(Offset + 0x28)) * 180m) / 0x8000; + set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0x28), (short)((value * 0x8000) / 180m)); + } + + public decimal Longitude // don't use the setters + { + get => (ReadInt16LittleEndian(Data.AsSpan(Offset + 0x2A)) * 180m) / 0x8000; + set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0x2A), (short)((value * 0x8000) / 180m)); + } + + public byte ConsoleRegion + { + get => Data[Offset + 0x2C]; + set => Data[Offset + 0x2C] = value; + } + + public int Language + { + get => Data[Offset + 0x2D]; + set => Data[Offset + 0x2D] = (byte)value; + } + + private Span OT_Trash => Data.AsSpan(Offset + 0x48, 0x1A); + + public string OT + { + get => SAV.GetString(OT_Trash); + set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); + } + + private Span GetSayingSpan(int say) => Data.AsSpan(GetSayingOffset(say), SAV6.LongStringLength); + private int GetSayingOffset(int say) => Offset + 0x7C + (SAV6.LongStringLength * say); + private string GetSaying(int say) => SAV.GetString(GetSayingSpan(say)); + private void SetSaying(int say, string value) => SAV.SetString(GetSayingSpan(say), value.AsSpan(), SAV6.LongStringLength / 2, StringConverterOption.ClearZero); + + public string Saying1 { get => GetSaying(0); set => SetSaying(0, value); } + public string Saying2 { get => GetSaying(1); set => SetSaying(1, value); } + public string Saying3 { get => GetSaying(2); set => SetSaying(2, value); } + public string Saying4 { get => GetSaying(3); set => SetSaying(3, value); } + public string Saying5 { get => GetSaying(4); set => SetSaying(4, value); } + + public bool IsMegaEvolutionUnlocked + { + get => (Data[Offset + 0x14A] & 0x01) != 0; + set => Data[Offset + 0x14A] = (byte)((Data[Offset + 0x14A] & 0xFE) | (value ? 1 : 0)); // in battle + } + + public bool IsMegaRayquazaUnlocked + { + get => (Data[Offset + 0x14A] & 0x02) != 0; + set => Data[Offset + 0x14A] = (byte)((Data[Offset + 0x14A] & ~2) | (value ? 2 : 0)); // in battle + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/MyStatus6XY.cs b/PKHeX.Core/Saves/Substructures/Gen6/MyStatus6XY.cs index 8ce8c85d6..6cbbc1334 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/MyStatus6XY.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/MyStatus6XY.cs @@ -1,34 +1,33 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// XY specific features for +/// +/// These properties are technically included in OR/AS but they are unused; assumed backwards compatibility for communications with XY +public sealed class MyStatus6XY : MyStatus6 { - /// - /// XY specific features for - /// - /// These properties are technically included in OR/AS but they are unused; assumed backwards compatibility for communications with XY - public sealed class MyStatus6XY : MyStatus6 + public MyStatus6XY(SAV6XY sav, int offset) : base(sav, offset) { } + + public TrainerFashion6 Fashion { - public MyStatus6XY(SAV6XY sav, int offset) : base(sav, offset) { } + get => TrainerFashion6.GetFashion(SAV.Data, Offset + 0x30, SAV.Gender); + set => value.Write(Data, Offset + 0x30); + } - public TrainerFashion6 Fashion - { - get => TrainerFashion6.GetFashion(SAV.Data, Offset + 0x30, SAV.Gender); - set => value.Write(Data, Offset + 0x30); - } + private Span Nickname_Trash => Data.AsSpan(Offset + 0x62, SAV6.ShortStringLength); - private Span Nickname_Trash => Data.AsSpan(Offset + 0x62, SAV6.ShortStringLength); + public string OT_Nick + { + get => SAV.GetString(Nickname_Trash); + set => SAV.SetString(Nickname_Trash, value.AsSpan(), 12, StringConverterOption.ClearZero); + } - public string OT_Nick - { - get => SAV.GetString(Nickname_Trash); - set => SAV.SetString(Nickname_Trash, value.AsSpan(), 12, StringConverterOption.ClearZero); - } - - public short EyeColor - { - get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0x148)); - set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0x148), value); - } + public short EyeColor + { + get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0x148)); + set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0x148), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/MysteryBlock6.cs b/PKHeX.Core/Saves/Substructures/Gen6/MysteryBlock6.cs index 298fee2ec..6e612a813 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/MysteryBlock6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/MysteryBlock6.cs @@ -1,75 +1,74 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MysteryBlock6 : SaveBlock { - public sealed class MysteryBlock6 : SaveBlock + private const int FlagStart = 0; + private const int MaxReceivedFlag = 2048; + private const int MaxCardsPresent = 24; + // private const int FlagRegionSize = (MaxReceivedFlag / 8); // 0x100 + private const int CardStart = FlagStart + (MaxReceivedFlag / 8); + + public MysteryBlock6(SAV6XY sav, int offset) : base(sav) => Offset = offset; + public MysteryBlock6(SAV6AO sav, int offset) : base(sav) => Offset = offset; + + public bool[] GetReceivedFlags() => ArrayUtil.GitBitFlagArray(Data.AsSpan(Offset + FlagStart), MaxReceivedFlag); + + public void SetReceivedFlags(bool[] value) { - private const int FlagStart = 0; - private const int MaxReceivedFlag = 2048; - private const int MaxCardsPresent = 24; - // private const int FlagRegionSize = (MaxReceivedFlag / 8); // 0x100 - private const int CardStart = FlagStart + (MaxReceivedFlag / 8); - - public MysteryBlock6(SAV6XY sav, int offset) : base(sav) => Offset = offset; - public MysteryBlock6(SAV6AO sav, int offset) : base(sav) => Offset = offset; - - public bool[] GetReceivedFlags() => ArrayUtil.GitBitFlagArray(Data.AsSpan(Offset + FlagStart), MaxReceivedFlag); - - public void SetReceivedFlags(bool[] value) - { - if (value.Length != MaxReceivedFlag) - return; - ArrayUtil.SetBitFlagArray(Data.AsSpan(Offset + FlagStart), value); - SAV.State.Edited = true; - } - - public DataMysteryGift[] GetGifts() - { - var cards = new DataMysteryGift[MaxCardsPresent]; - for (int i = 0; i < cards.Length; i++) - cards[i] = GetGift(i); - return cards; - } - - public void SetGifts(DataMysteryGift[] value) - { - int count = Math.Min(MaxCardsPresent, value.Length); - for (int i = 0; i < count; i++) - SetGift(value[i], i); - for (int i = value.Length; i < MaxCardsPresent; i++) - SetGift(new WC6(), i); - } - - public DataMysteryGift GetGift(int index) - { - if ((uint)index > MaxCardsPresent) - throw new ArgumentOutOfRangeException(nameof(index)); - - var offset = GetGiftOffset(index); - var data = SAV.GetData(offset, WC6.Size); - return new WC6(data); - } - - private int GetGiftOffset(int index) => Offset + CardStart + (index * WC6.Size); - - public void SetGift(DataMysteryGift wc6, int index) - { - if ((uint)index > MaxCardsPresent) - throw new ArgumentOutOfRangeException(nameof(index)); - if (wc6.Data.Length != WC6.Size) - throw new InvalidCastException(nameof(wc6)); - - if (wc6.CardID == 2048 && wc6.ItemID == 726) // Eon Ticket (OR/AS) - { - if (SAV is not SAV6AO ao) - return; - // Set the special received data - var info = ao.Blocks.Sango; - info.ReceiveEon(); - info.EnableSendEon(); - } - - SAV.SetData(wc6.Data, GetGiftOffset(index)); - } + if (value.Length != MaxReceivedFlag) + return; + ArrayUtil.SetBitFlagArray(Data.AsSpan(Offset + FlagStart), value); + SAV.State.Edited = true; } -} \ No newline at end of file + + public DataMysteryGift[] GetGifts() + { + var cards = new DataMysteryGift[MaxCardsPresent]; + for (int i = 0; i < cards.Length; i++) + cards[i] = GetGift(i); + return cards; + } + + public void SetGifts(DataMysteryGift[] value) + { + int count = Math.Min(MaxCardsPresent, value.Length); + for (int i = 0; i < count; i++) + SetGift(value[i], i); + for (int i = value.Length; i < MaxCardsPresent; i++) + SetGift(new WC6(), i); + } + + public DataMysteryGift GetGift(int index) + { + if ((uint)index > MaxCardsPresent) + throw new ArgumentOutOfRangeException(nameof(index)); + + var offset = GetGiftOffset(index); + var data = SAV.GetData(offset, WC6.Size); + return new WC6(data); + } + + private int GetGiftOffset(int index) => Offset + CardStart + (index * WC6.Size); + + public void SetGift(DataMysteryGift wc6, int index) + { + if ((uint)index > MaxCardsPresent) + throw new ArgumentOutOfRangeException(nameof(index)); + if (wc6.Data.Length != WC6.Size) + throw new InvalidCastException(nameof(wc6)); + + if (wc6.CardID == 2048 && wc6.ItemID == 726) // Eon Ticket (OR/AS) + { + if (SAV is not SAV6AO ao) + return; + // Set the special received data + var info = ao.Blocks.Sango; + info.ReceiveEon(); + info.EnableSendEon(); + } + + SAV.SetData(wc6.Data, GetGiftOffset(index)); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6.cs b/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6.cs index 68eb43431..88688deda 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6.cs @@ -1,86 +1,85 @@ using System; using static PKHeX.Core.OPower6Type; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class OPower6 : SaveBlock { - public sealed class OPower6 : SaveBlock + private static readonly OPowerFlagSet[] Mapping = { - private static readonly OPowerFlagSet[] Mapping = - { - // Skip unused byte - new(5, Hatching) {Offset = 1}, - new(5, Bargain) {Offset = 6}, - new(5, Prize_Money) {Offset = 11}, - new(5, Exp_Point) {Offset = 16}, - new(5, Capture) {Offset = 21}, + // Skip unused byte + new(5, Hatching) {Offset = 1}, + new(5, Bargain) {Offset = 6}, + new(5, Prize_Money) {Offset = 11}, + new(5, Exp_Point) {Offset = 16}, + new(5, Capture) {Offset = 21}, - new(3, Encounter) {Offset = 26}, - new(3, Stealth) {Offset = 29}, - new(3, HP_Restoring) {Offset = 32}, - new(3, PP_Restoring) {Offset = 35}, + new(3, Encounter) {Offset = 26}, + new(3, Stealth) {Offset = 29}, + new(3, HP_Restoring) {Offset = 32}, + new(3, PP_Restoring) {Offset = 35}, - new(1, Full_Recovery) {Offset = 38}, + new(1, Full_Recovery) {Offset = 38}, - new(5, Befriending) {Offset = 39}, + new(5, Befriending) {Offset = 39}, - new(3, Attack) {Offset = 44}, - new(3, Defense) {Offset = 47}, - new(3, Sp_Attack) {Offset = 50}, - new(3, Sp_Defense) {Offset = 53}, - new(3, Speed) {Offset = 56}, - new(3, Critical) {Offset = 59}, - new(3, Accuracy) {Offset = 62}, - }; + new(3, Attack) {Offset = 44}, + new(3, Defense) {Offset = 47}, + new(3, Sp_Attack) {Offset = 50}, + new(3, Sp_Defense) {Offset = 53}, + new(3, Speed) {Offset = 56}, + new(3, Critical) {Offset = 59}, + new(3, Accuracy) {Offset = 62}, + }; - public OPower6(SAV6XY sav, int offset) : base(sav) => Offset = offset; - public OPower6(SAV6AO sav, int offset) : base(sav) => Offset = offset; + public OPower6(SAV6XY sav, int offset) : base(sav) => Offset = offset; + public OPower6(SAV6AO sav, int offset) : base(sav) => Offset = offset; - private static OPowerFlagSet Get(OPower6Type type) => Array.Find(Mapping, t => t.Identifier == type); - public static int GetOPowerCount(OPower6Type type) => Get(type).BaseCount; - public int GetOPowerLevel(OPower6Type type) => Get(type).GetOPowerLevel(Data.AsSpan(Offset)); + private static OPowerFlagSet Get(OPower6Type type) => Array.Find(Mapping, t => t.Identifier == type); + public static int GetOPowerCount(OPower6Type type) => Get(type).BaseCount; + public int GetOPowerLevel(OPower6Type type) => Get(type).GetOPowerLevel(Data.AsSpan(Offset)); - public static bool GetHasOPowerS(OPower6Type type) => Get(type).HasOPowerS; - public static bool GetHasOPowerMAX(OPower6Type type) => Get(type).HasOPowerMAX; - public bool GetOPowerS(OPower6Type type) => Get(type).GetOPowerS(Data.AsSpan(Offset)); - public bool GetOPowerMAX(OPower6Type type) => Get(type).GetOPowerMAX(Data.AsSpan(Offset)); + public static bool GetHasOPowerS(OPower6Type type) => Get(type).HasOPowerS; + public static bool GetHasOPowerMAX(OPower6Type type) => Get(type).HasOPowerMAX; + public bool GetOPowerS(OPower6Type type) => Get(type).GetOPowerS(Data.AsSpan(Offset)); + public bool GetOPowerMAX(OPower6Type type) => Get(type).GetOPowerMAX(Data.AsSpan(Offset)); - public void SetOPowerLevel(OPower6Type type, int lvl) => Get(type).SetOPowerLevel(Data.AsSpan(Offset), lvl); - public void SetOPowerS(OPower6Type type, bool value) => Get(type).SetOPowerS(Data.AsSpan(Offset), value); - public void SetOPowerMAX(OPower6Type type, bool value) => Get(type).SetOPowerMAX(Data.AsSpan(Offset), value); + public void SetOPowerLevel(OPower6Type type, int lvl) => Get(type).SetOPowerLevel(Data.AsSpan(Offset), lvl); + public void SetOPowerS(OPower6Type type, bool value) => Get(type).SetOPowerS(Data.AsSpan(Offset), value); + public void SetOPowerMAX(OPower6Type type, bool value) => Get(type).SetOPowerMAX(Data.AsSpan(Offset), value); - public bool MasterFlag - { - get => Data[Offset] == 1; - set => Data[Offset] = (byte) (value ? OPowerFlagState.Unlocked : OPowerFlagState.Locked); - } - - public void UnlockAll() => ToggleFlags(allEvents: true); - public void UnlockRegular() => ToggleFlags(); - public void ClearAll() => ToggleFlags(clearOnly: true); - - private void ToggleFlags(bool allEvents = false, bool clearOnly = false) - { - var span = Data.AsSpan(Offset); - foreach (var m in Mapping) - { - // Clear before applying new value - m.SetOPowerLevel(span, 0); - m.SetOPowerS(span, false); - m.SetOPowerMAX(span, false); - - if (clearOnly) - continue; - - int lvl = allEvents ? m.BaseCount : (m.BaseCount != 1 ? 3 : 0); // Full_Recovery is ORAS/event only @ 1 level - m.SetOPowerLevel(span, lvl); - if (!allEvents) - continue; - - m.SetOPowerS(span, true); - m.SetOPowerMAX(span, true); - } - } - - public byte[] Write() => Data; + public bool MasterFlag + { + get => Data[Offset] == 1; + set => Data[Offset] = (byte) (value ? OPowerFlagState.Unlocked : OPowerFlagState.Locked); } + + public void UnlockAll() => ToggleFlags(allEvents: true); + public void UnlockRegular() => ToggleFlags(); + public void ClearAll() => ToggleFlags(clearOnly: true); + + private void ToggleFlags(bool allEvents = false, bool clearOnly = false) + { + var span = Data.AsSpan(Offset); + foreach (var m in Mapping) + { + // Clear before applying new value + m.SetOPowerLevel(span, 0); + m.SetOPowerS(span, false); + m.SetOPowerMAX(span, false); + + if (clearOnly) + continue; + + int lvl = allEvents ? m.BaseCount : (m.BaseCount != 1 ? 3 : 0); // Full_Recovery is ORAS/event only @ 1 level + m.SetOPowerLevel(span, lvl); + if (!allEvents) + continue; + + m.SetOPowerS(span, true); + m.SetOPowerMAX(span, true); + } + } + + public byte[] Write() => Data; } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6Type.cs b/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6Type.cs index 4210f8a69..e5fa40a62 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6Type.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6Type.cs @@ -1,24 +1,23 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum OPower6Type { - public enum OPower6Type - { - Hatching, - Bargain, - Prize_Money, - Exp_Point, - Capture, - Encounter, - Stealth, - HP_Restoring, - PP_Restoring, - Full_Recovery, - Befriending, - Attack, - Defense, - Sp_Attack, - Sp_Defense, - Speed, - Critical, - Accuracy, - } -} \ No newline at end of file + Hatching, + Bargain, + Prize_Money, + Exp_Point, + Capture, + Encounter, + Stealth, + HP_Restoring, + PP_Restoring, + Full_Recovery, + Befriending, + Attack, + Defense, + Sp_Attack, + Sp_Defense, + Speed, + Critical, + Accuracy, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6Value.cs b/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6Value.cs index bb2f0e5a5..f2c937d5a 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6Value.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPower6Value.cs @@ -1,12 +1,11 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum OPower6Value { - public enum OPower6Value - { - Locked, - Lv1, - Lv2, - Lv3, - S, - MAX, - } -} \ No newline at end of file + Locked, + Lv1, + Lv2, + Lv3, + S, + MAX, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPowerFlagSet.cs b/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPowerFlagSet.cs index e24febdde..0deab4174 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPowerFlagSet.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPowerFlagSet.cs @@ -1,59 +1,58 @@ using System; using System.Diagnostics; -namespace PKHeX.Core +namespace PKHeX.Core; + +internal sealed class OPowerFlagSet { - internal sealed class OPowerFlagSet + public readonly OPower6Type Identifier; + public readonly int Count; + public int Offset { get; set; } + public int BaseCount => Math.Min(3, Count); + public bool HasOPowerS => Count > 3; + public bool HasOPowerMAX => Count == 5; + + public OPowerFlagSet(int count, OPower6Type ident) { - public readonly OPower6Type Identifier; - public readonly int Count; - public int Offset { get; set; } - public int BaseCount => Math.Min(3, Count); - public bool HasOPowerS => Count > 3; - public bool HasOPowerMAX => Count == 5; - - public OPowerFlagSet(int count, OPower6Type ident) - { - Identifier = ident; - Count = count; - } - - public int GetOPowerLevel(ReadOnlySpan data) - { - for (int i = 0; i < BaseCount; i++) - { - if (GetFlag(data, i)) - continue; - Debug.WriteLine($"Fetched {Identifier}: {i}"); - return i; - } - - Debug.WriteLine($"Fetched {Identifier}: {BaseCount}"); - return BaseCount; - } - - public void SetOPowerLevel(Span data, int value) - { - Debug.WriteLine($"Setting {Identifier}: {value}"); - for (int i = 0; i < BaseCount; i++) - SetFlag(data, i, i + 1 <= value); - Debug.Assert(value == GetOPowerLevel(data)); - } - - public bool GetOPowerS(ReadOnlySpan data) => HasOPowerS && GetFlag(data, 3); - public bool GetOPowerMAX(ReadOnlySpan data) => HasOPowerMAX && GetFlag(data, 4); - public void SetOPowerS(Span data, bool value) => SetFlag(data, 3, value); - public void SetOPowerMAX(Span data, bool value) => SetFlag(data, 4, value); - - private bool GetFlag(ReadOnlySpan data, int index) - { - return data[Offset + index] == (byte)OPowerFlagState.Unlocked; - } - - private void SetFlag(Span data, int index, bool value) - { - if (index < Count) - data[Offset + index] = (byte)(value ? OPowerFlagState.Unlocked : OPowerFlagState.Locked); - } + Identifier = ident; + Count = count; } -} \ No newline at end of file + + public int GetOPowerLevel(ReadOnlySpan data) + { + for (int i = 0; i < BaseCount; i++) + { + if (GetFlag(data, i)) + continue; + Debug.WriteLine($"Fetched {Identifier}: {i}"); + return i; + } + + Debug.WriteLine($"Fetched {Identifier}: {BaseCount}"); + return BaseCount; + } + + public void SetOPowerLevel(Span data, int value) + { + Debug.WriteLine($"Setting {Identifier}: {value}"); + for (int i = 0; i < BaseCount; i++) + SetFlag(data, i, i + 1 <= value); + Debug.Assert(value == GetOPowerLevel(data)); + } + + public bool GetOPowerS(ReadOnlySpan data) => HasOPowerS && GetFlag(data, 3); + public bool GetOPowerMAX(ReadOnlySpan data) => HasOPowerMAX && GetFlag(data, 4); + public void SetOPowerS(Span data, bool value) => SetFlag(data, 3, value); + public void SetOPowerMAX(Span data, bool value) => SetFlag(data, 4, value); + + private bool GetFlag(ReadOnlySpan data, int index) + { + return data[Offset + index] == (byte)OPowerFlagState.Unlocked; + } + + private void SetFlag(Span data, int index, bool value) + { + if (index < Count) + data[Offset + index] = (byte)(value ? OPowerFlagState.Unlocked : OPowerFlagState.Locked); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPowerFlagState.cs b/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPowerFlagState.cs index 2f709812d..7cbfd5124 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPowerFlagState.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/OPower/OPowerFlagState.cs @@ -1,8 +1,7 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +internal enum OPowerFlagState : byte { - internal enum OPowerFlagState : byte - { - Locked = 0, - Unlocked = 1, - } -} \ No newline at end of file + Locked = 0, + Unlocked = 1, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/PSS6.cs b/PKHeX.Core/Saves/Substructures/Gen6/PSS6.cs index aad52f630..304bac1de 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/PSS6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/PSS6.cs @@ -2,88 +2,87 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class PSS6 { - public static class PSS6 + private const string Header = "PSS List"; + private static readonly string[] headers = { "PSS Data - Friends", "PSS Data - Acquaintances", "PSS Data - Passerby" }; + + public static List GetPSSParse(SAV6 SAV) { - private const string Header = "PSS List"; - private static readonly string[] headers = { "PSS Data - Friends", "PSS Data - Acquaintances", "PSS Data - Passerby" }; - - public static List GetPSSParse(SAV6 SAV) + var result = new List {Header}; + int offset = SAV.PSS; + var data = SAV.Data; + for (int g = 0; g < 3; g++) { - var result = new List {Header}; - int offset = SAV.PSS; - var data = SAV.Data; - for (int g = 0; g < 3; g++) - { - result.Add("----"); - result.Add(headers[g]); - result.Add("----"); - // uint count = ReadUInt32LittleEndian(data.AsSpan(offset + 0x4E20)); - ReadTrainers(result, data, offset, 100); - offset += 0x5000; // Advance to next block - } - - return result; + result.Add("----"); + result.Add(headers[g]); + result.Add("----"); + // uint count = ReadUInt32LittleEndian(data.AsSpan(offset + 0x4E20)); + ReadTrainers(result, data, offset, 100); + offset += 0x5000; // Advance to next block } - private static void ReadTrainers(ICollection result, byte[] data, int offset, int count) + return result; + } + + private static void ReadTrainers(ICollection result, byte[] data, int offset, int count) + { + int r_offset = offset; + const int size = 0xC8; + for (int i = 0; i < count; i++) { - int r_offset = offset; - const int size = 0xC8; - for (int i = 0; i < count; i++) - { - if (!ReadTrainer(result, data.AsSpan(r_offset, size))) - break; // No data present here + if (!ReadTrainer(result, data.AsSpan(r_offset, size))) + break; // No data present here - if (i > 0) - result.Add(string.Empty); + if (i > 0) + result.Add(string.Empty); - r_offset += size; // Advance to next entry - } - } - - private static bool ReadTrainer(ICollection result, ReadOnlySpan data) - { - ulong pssID = ReadUInt64LittleEndian(data); - if (pssID == 0) - return false; // no data - - string otname = StringConverter6.GetString(data.Slice(0x08, 0x1A)); - string message = StringConverter6.GetString(data.Slice(0x22, 0x22)); - - // Trim terminated - - // uint unk1 = ReadUInt32LittleEndian(data[0x44..]); - // ulong unk2 = ReadUInt64LittleEndian(data[0x48..]); - // uint unk3 = ReadUInt32LittleEndian(data[0x50..]); - // uint unk4 = ReadUInt16LittleEndian(data[0x54..]); - byte regionID = data[0x56]; - byte countryID = data[0x57]; - byte game = data[0x5A]; - // ulong outfit = ReadUInt64LittleEndian(data.AsSpan(ofs + 0x5C)); - int favpkm = ReadUInt16LittleEndian(data[0x9C..]) & 0x7FF; - - string gamename = GetGameName(game); - var (country, region) = GeoLocation.GetCountryRegionText(countryID, regionID, GameInfo.CurrentLanguage); - result.Add($"OT: {otname}"); - result.Add($"Message: {message}"); - result.Add($"Game: {gamename}"); - result.Add($"Country: {country}"); - result.Add($"Region: {region}"); - result.Add($"Favorite: {GameInfo.Strings.specieslist[favpkm]}"); - return false; - } - - private static string GetGameName(int game) - { - const string unk = "UNKNOWN GAME"; - if (game < 0) - return unk; - var list = GameInfo.Strings.gamelist; - if (game >= list.Length) - return unk; - return list[game]; + r_offset += size; // Advance to next entry } } + + private static bool ReadTrainer(ICollection result, ReadOnlySpan data) + { + ulong pssID = ReadUInt64LittleEndian(data); + if (pssID == 0) + return false; // no data + + string otname = StringConverter6.GetString(data.Slice(0x08, 0x1A)); + string message = StringConverter6.GetString(data.Slice(0x22, 0x22)); + + // Trim terminated + + // uint unk1 = ReadUInt32LittleEndian(data[0x44..]); + // ulong unk2 = ReadUInt64LittleEndian(data[0x48..]); + // uint unk3 = ReadUInt32LittleEndian(data[0x50..]); + // uint unk4 = ReadUInt16LittleEndian(data[0x54..]); + byte regionID = data[0x56]; + byte countryID = data[0x57]; + byte game = data[0x5A]; + // ulong outfit = ReadUInt64LittleEndian(data.AsSpan(ofs + 0x5C)); + int favpkm = ReadUInt16LittleEndian(data[0x9C..]) & 0x7FF; + + string gamename = GetGameName(game); + var (country, region) = GeoLocation.GetCountryRegionText(countryID, regionID, GameInfo.CurrentLanguage); + result.Add($"OT: {otname}"); + result.Add($"Message: {message}"); + result.Add($"Game: {gamename}"); + result.Add($"Country: {country}"); + result.Add($"Region: {region}"); + result.Add($"Favorite: {GameInfo.Strings.specieslist[favpkm]}"); + return false; + } + + private static string GetGameName(int game) + { + const string unk = "UNKNOWN GAME"; + if (game < 0) + return unk; + var list = GameInfo.Strings.gamelist; + if (game >= list.Length) + return unk; + return list[game]; + } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/PlayTime6.cs b/PKHeX.Core/Saves/Substructures/Gen6/PlayTime6.cs index e7b525e7d..90c4ec0fe 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/PlayTime6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/PlayTime6.cs @@ -1,66 +1,65 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class PlayTime6 : SaveBlock { - public sealed class PlayTime6 : SaveBlock + public PlayTime6(SAV6 sav, int offset) : base(sav) => Offset = offset; + public PlayTime6(SAV7 sav, int offset) : base(sav) => Offset = offset; + + public int PlayedHours { - public PlayTime6(SAV6 sav, int offset) : base(sav) => Offset = offset; - public PlayTime6(SAV7 sav, int offset) : base(sav) => Offset = offset; + get => ReadUInt16LittleEndian(Data.AsSpan(Offset)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset), (ushort)value); + } - public int PlayedHours + public int PlayedMinutes + { + get => Data[Offset + 2]; + set => Data[Offset + 2] = (byte)value; + } + + public int PlayedSeconds + { + get => Data[Offset + 3]; + set => Data[Offset + 3] = (byte)value; + } + + private uint LastSaved { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } + private int LastSavedYear { get => (int)(LastSaved & 0xFFF); set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)value; } + private int LastSavedMonth { get => (int)((LastSaved >> 12) & 0xF); set => LastSaved = (LastSaved & 0xFFFF0FFF) | (((uint)value & 0xF) << 12); } + private int LastSavedDay { get => (int)((LastSaved >> 16) & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | (((uint)value & 0x1F) << 16); } + private int LastSavedHour { get => (int)((LastSaved >> 21) & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | (((uint)value & 0x1F) << 21); } + private int LastSavedMinute { get => (int)((LastSaved >> 26) & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | (((uint)value & 0x3F) << 26); } + public string LastSavedTime => $"{LastSavedYear:0000}-{LastSavedMonth:00}-{LastSavedDay:00} {LastSavedHour:00}ː{LastSavedMinute:00}"; // not : + + public DateTime? LastSavedDate + { + get => !DateUtil.IsDateValid(LastSavedYear, LastSavedMonth, LastSavedDay) + ? null + : new DateTime(LastSavedYear, LastSavedMonth, LastSavedDay, LastSavedHour, LastSavedMinute, 0); + set { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset), (ushort)value); - } - - public int PlayedMinutes - { - get => Data[Offset + 2]; - set => Data[Offset + 2] = (byte)value; - } - - public int PlayedSeconds - { - get => Data[Offset + 3]; - set => Data[Offset + 3] = (byte)value; - } - - private uint LastSaved { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } - private int LastSavedYear { get => (int)(LastSaved & 0xFFF); set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)value; } - private int LastSavedMonth { get => (int)(LastSaved >> 12 & 0xF); set => LastSaved = (LastSaved & 0xFFFF0FFF) | ((uint)value & 0xF) << 12; } - private int LastSavedDay { get => (int)(LastSaved >> 16 & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | ((uint)value & 0x1F) << 16; } - private int LastSavedHour { get => (int)(LastSaved >> 21 & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | ((uint)value & 0x1F) << 21; } - private int LastSavedMinute { get => (int)(LastSaved >> 26 & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | ((uint)value & 0x3F) << 26; } - public string LastSavedTime => $"{LastSavedYear:0000}-{LastSavedMonth:00}-{LastSavedDay:00} {LastSavedHour:00}ː{LastSavedMinute:00}"; // not : - - public DateTime? LastSavedDate - { - get => !DateUtil.IsDateValid(LastSavedYear, LastSavedMonth, LastSavedDay) - ? null - : new DateTime(LastSavedYear, LastSavedMonth, LastSavedDay, LastSavedHour, LastSavedMinute, 0); - set + // Only update the properties if a value is provided. + if (value.HasValue) { - // Only update the properties if a value is provided. - if (value.HasValue) - { - var dt = value.Value; - LastSavedYear = dt.Year; - LastSavedMonth = dt.Month; - LastSavedDay = dt.Day; - LastSavedHour = dt.Hour; - LastSavedMinute = dt.Minute; - } - else // Clear the date. - { - // If code tries to access MetDate again, null will be returned. - LastSavedYear = 0; - LastSavedMonth = 0; - LastSavedDay = 0; - LastSavedHour = 0; - LastSavedMinute = 0; - } + var dt = value.Value; + LastSavedYear = dt.Year; + LastSavedMonth = dt.Month; + LastSavedDay = dt.Day; + LastSavedHour = dt.Hour; + LastSavedMinute = dt.Minute; + } + else // Clear the date. + { + // If code tries to access MetDate again, null will be returned. + LastSavedYear = 0; + LastSavedMonth = 0; + LastSavedDay = 0; + LastSavedHour = 0; + LastSavedMinute = 0; } } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/Puff6.cs b/PKHeX.Core/Saves/Substructures/Gen6/Puff6.cs index e5746684c..1fbbccd49 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/Puff6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/Puff6.cs @@ -1,58 +1,57 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Puff6 : SaveBlock { - public sealed class Puff6 : SaveBlock + private const int MaxPuffID = 26; // Supreme Winter Poké Puff + private const int PuffSlots = 100; + + public Puff6(SAV6 SAV, int offset) : base(SAV) => Offset = offset; + + public byte[] GetPuffs() => SAV.GetData(Offset, PuffSlots); + public void SetPuffs(byte[] value) => SAV.SetData(value, Offset); + + public int PuffCount { - private const int MaxPuffID = 26; // Supreme Winter Poké Puff - private const int PuffSlots = 100; + get => ReadInt32LittleEndian(Data.AsSpan(Offset + PuffSlots)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + PuffSlots), value); + } - public Puff6(SAV6 SAV, int offset) : base(SAV) => Offset = offset; + public void Reset() + { + Array.Clear(Data, Offset, PuffSlots); + // Set the first few default Puffs + Data[Offset + 0] = 1; + Data[Offset + 1] = 2; + Data[Offset + 2] = 3; + Data[Offset + 3] = 4; + Data[Offset + 4] = 5; + PuffCount = 5; + } - public byte[] GetPuffs() => SAV.GetData(Offset, PuffSlots); - public void SetPuffs(byte[] value) => SAV.SetData(value, Offset); - - public int PuffCount + public void MaxCheat(bool special = false) + { + var rnd = Util.Rand; + if (special) { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + PuffSlots)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + PuffSlots), value); + for (int i = 0; i < PuffSlots; i++) + Data[Offset + i] = (byte)(21 + rnd.Next(2)); // Supreme Wish or Honor } - - public void Reset() + else { - Array.Clear(Data, Offset, PuffSlots); - // Set the first few default Puffs - Data[Offset + 0] = 1; - Data[Offset + 1] = 2; - Data[Offset + 2] = 3; - Data[Offset + 3] = 4; - Data[Offset + 4] = 5; - PuffCount = 5; + for (int i = 0; i < PuffSlots; i++) + Data[Offset + i] = (byte)((i % MaxPuffID) + 1); + Util.Shuffle(Data.AsSpan(), Offset, Offset + PuffSlots, rnd); } + PuffCount = PuffSlots; + } - public void MaxCheat(bool special = false) - { - var rnd = Util.Rand; - if (special) - { - for (int i = 0; i < PuffSlots; i++) - Data[Offset + i] = (byte)(21 + rnd.Next(2)); // Supreme Wish or Honor - } - else - { - for (int i = 0; i < PuffSlots; i++) - Data[Offset + i] = (byte)((i % MaxPuffID) + 1); - Util.Shuffle(Data.AsSpan(), Offset, Offset + PuffSlots, rnd); - } - PuffCount = PuffSlots; - } - - public void Sort(bool reverse = false) - { - Array.Sort(Data, Offset, PuffCount); - if (reverse) - Array.Reverse(Data, Offset, PuffCount); - } + public void Sort(bool reverse = false) + { + Array.Sort(Data, Offset, PuffCount); + if (reverse) + Array.Reverse(Data, Offset, PuffCount); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/RecordBlock6.cs b/PKHeX.Core/Saves/Substructures/Gen6/RecordBlock6.cs index b785378d3..95ab3e375 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/RecordBlock6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/RecordBlock6.cs @@ -3,82 +3,81 @@ using System.Diagnostics; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class RecordBlock6 : RecordBlock // 6 or 7 { - public sealed class RecordBlock6 : RecordBlock // 6 or 7 + public const int RecordCount = 200; + protected override IReadOnlyList RecordMax { get; } + + // Structure: + // uint[100]; + // ushort[100]; + + public RecordBlock6(SAV6XY sav, int offset) : base(sav) { - public const int RecordCount = 200; - protected override IReadOnlyList RecordMax { get; } + Offset = offset; + RecordMax = Records.MaxType_XY; + } - // Structure: - // uint[100]; - // ushort[100]; + public RecordBlock6(SAV6AO sav, int offset) : base(sav) + { + Offset = offset; + RecordMax = Records.MaxType_AO; + } - public RecordBlock6(SAV6XY sav, int offset) : base(sav) + public RecordBlock6(SAV6AODemo sav, int offset) : base(sav) + { + Offset = offset; + RecordMax = Records.MaxType_AO; + } + + public RecordBlock6(SAV7SM sav, int offset) : base(sav) + { + Offset = offset; + RecordMax = Records.MaxType_SM; + } + + public RecordBlock6(SAV7USUM sav, int offset) : base(sav) + { + Offset = offset; + RecordMax = Records.MaxType_USUM; + } + + public override int GetRecord(int recordID) + { + int ofs = Records.GetOffset(Offset, recordID); + switch (recordID) { - Offset = offset; - RecordMax = Records.MaxType_XY; + case < 100: + return ReadInt32LittleEndian(Data.AsSpan(ofs)); + case < 200: + return ReadInt16LittleEndian(Data.AsSpan(ofs)); + default: + Trace.Fail(nameof(recordID)); + return 0; } + } - public RecordBlock6(SAV6AO sav, int offset) : base(sav) + public override void SetRecord(int recordID, int value) + { + if ((uint)recordID >= RecordCount) + throw new ArgumentOutOfRangeException(nameof(recordID)); + int ofs = GetRecordOffset(recordID); + int max = GetRecordMax(recordID); + if (value > max) + value = max; + switch (recordID) { - Offset = offset; - RecordMax = Records.MaxType_AO; - } - - public RecordBlock6(SAV6AODemo sav, int offset) : base(sav) - { - Offset = offset; - RecordMax = Records.MaxType_AO; - } - - public RecordBlock6(SAV7SM sav, int offset) : base(sav) - { - Offset = offset; - RecordMax = Records.MaxType_SM; - } - - public RecordBlock6(SAV7USUM sav, int offset) : base(sav) - { - Offset = offset; - RecordMax = Records.MaxType_USUM; - } - - public override int GetRecord(int recordID) - { - int ofs = Records.GetOffset(Offset, recordID); - switch (recordID) - { - case < 100: - return ReadInt32LittleEndian(Data.AsSpan(ofs)); - case < 200: - return ReadInt16LittleEndian(Data.AsSpan(ofs)); - default: - Trace.Fail(nameof(recordID)); - return 0; - } - } - - public override void SetRecord(int recordID, int value) - { - if ((uint)recordID >= RecordCount) - throw new ArgumentOutOfRangeException(nameof(recordID)); - int ofs = GetRecordOffset(recordID); - int max = GetRecordMax(recordID); - if (value > max) - value = max; - switch (recordID) - { - case < 100: - WriteInt32LittleEndian(Data.AsSpan(ofs), value); - break; - case < 200: - WriteUInt16LittleEndian(Data.AsSpan(ofs), (ushort)value); - break; - default: - Trace.Fail(nameof(recordID)); - break; - } + case < 100: + WriteInt32LittleEndian(Data.AsSpan(ofs), value); + break; + case < 200: + WriteUInt16LittleEndian(Data.AsSpan(ofs), (ushort)value); + break; + default: + Trace.Fail(nameof(recordID)); + break; } } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/SangoInfoBlock.cs b/PKHeX.Core/Saves/Substructures/Gen6/SangoInfoBlock.cs index 40fc57ccd..0361a9d64 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/SangoInfoBlock.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/SangoInfoBlock.cs @@ -1,33 +1,32 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SangoInfoBlock : SaveBlock { - public sealed class SangoInfoBlock : SaveBlock + public SangoInfoBlock(SAV6AO SAV, int offset) : base(SAV) => Offset = offset; + + private const uint EON_MAGIC = WC6.EonTicketConst; + + public uint EonTicketReceivedMagic // 0x319B8 { - public SangoInfoBlock(SAV6AO SAV, int offset) : base(SAV) => Offset = offset; - - private const uint EON_MAGIC = WC6.EonTicketConst; - - public uint EonTicketReceivedMagic // 0x319B8 - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x63B8)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x63B8), value); - } - - public string SecretBaseQRText // 0x319BC -- 17*u16 - { - get => SAV.GetString(Data.AsSpan(Offset + 0x63BC, 36)); - set => SAV.SetString(Data.AsSpan(Offset + 0x63BC, 36), value.AsSpan(), 0x10, StringConverterOption.ClearZero); - } - - public uint EonTicketSendMagic // 0x319DE - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x63DE)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x63DE), value); - } - - public void ReceiveEon() => EonTicketReceivedMagic = EON_MAGIC; - public void EnableSendEon() => EonTicketSendMagic = EON_MAGIC; + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x63B8)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x63B8), value); } + + public string SecretBaseQRText // 0x319BC -- 17*u16 + { + get => SAV.GetString(Data.AsSpan(Offset + 0x63BC, 36)); + set => SAV.SetString(Data.AsSpan(Offset + 0x63BC, 36), value.AsSpan(), 0x10, StringConverterOption.ClearZero); + } + + public uint EonTicketSendMagic // 0x319DE + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x63DE)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x63DE), value); + } + + public void ReceiveEon() => EonTicketReceivedMagic = EON_MAGIC; + public void EnableSendEon() => EonTicketSendMagic = EON_MAGIC; } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6.cs b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6.cs index 3c95f7a1e..e11c8b9d1 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6.cs @@ -1,249 +1,248 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Secret base format for +/// +public class SecretBase6 { - /// - /// Secret base format for - /// - public class SecretBase6 + public const int SIZE = 0x310; + public const int COUNT_GOODS = 28; + public const int MinLocationID = -1; + public const int MaxLocationID = 85; + + protected readonly byte[] Data; + protected readonly int Offset; + + // structure: (first at 23D24 in sav) + // [000-001] u8 IsNew + // [002-003] s16 Location + // [004-153] DecorationPosition[28] (150, 12 bytes each) + + // [154... ] ??? + + // [21A-233] Trainer Name + // [234-255] Flavor1 + // [256-277] Flavor2 + // [278-299] Saying1 + // [29A-2BB] Saying2 + // [2BC-2E9] Saying3 + // [2EA-2FF] Saying4 + // [300-303] Secret Base Rank + // [304-307] u32 TotalFlagsFromFriends + // [308-30B] u32 TotalFlagsFromOther + // [30C] byte CollectedFlagsToday + // [30D] byte CollectedFlagsYesterday + // 2 bytes alignment for u32 + + public SecretBase6(byte[] data, int offset = 0) { - public const int SIZE = 0x310; - public const int COUNT_GOODS = 28; - public const int MinLocationID = -1; - public const int MaxLocationID = 85; - - protected readonly byte[] Data; - protected readonly int Offset; - - // structure: (first at 23D24 in sav) - // [000-001] u8 IsNew - // [002-003] s16 Location - // [004-153] DecorationPosition[28] (150, 12 bytes each) - - // [154... ] ??? - - // [21A-233] Trainer Name - // [234-255] Flavor1 - // [256-277] Flavor2 - // [278-299] Saying1 - // [29A-2BB] Saying2 - // [2BC-2E9] Saying3 - // [2EA-2FF] Saying4 - // [300-303] Secret Base Rank - // [304-307] u32 TotalFlagsFromFriends - // [308-30B] u32 TotalFlagsFromOther - // [30C] byte CollectedFlagsToday - // [30D] byte CollectedFlagsYesterday - // 2 bytes alignment for u32 - - public SecretBase6(byte[] data, int offset = 0) - { - Data = data; - Offset = offset; - } - - public bool IsNew - { - get => Data[Offset] == 1; - set => WriteUInt16LittleEndian(Data.AsSpan(Offset), (ushort)(value ? 1 : 0)); - } - - private int RawLocation - { - get => ReadInt16LittleEndian(Data.AsSpan(Offset + 2)); - set => WriteInt16LittleEndian(Data.AsSpan(Offset + 2), (short)value); - } - - public int BaseLocation - { - get => RawLocation; - set => RawLocation = value switch - { - 1 or 2 => 0, - > MaxLocationID => MaxLocationID, - < MinLocationID => -1, - _ => value, - }; - } - - public SecretBase6GoodPlacement GetPlacement(int index) => new(Data.AsSpan(Offset + GetPlacementOffset(index))); - - public void SetPlacement(int index, SecretBase6GoodPlacement value) => value.Write(Data.AsSpan(Offset + GetPlacementOffset(index))); - - private static int GetPlacementOffset(int index) - { - if ((uint) index >= COUNT_GOODS) - throw new ArgumentOutOfRangeException(nameof(index)); - return 4 + (index * SecretBase6GoodPlacement.SIZE); - } - - public byte BoppoyamaScore - { - get => Data[Offset + 0x174]; - set => Data[Offset + 0x174] = value; - } - - private const int NameLengthBytes = 0x1A; - private const int MessageLengthBytes = 0x22; - private const int NameLength = (0x1A / 2) - 1; // + terminator - private const int MessageLength = (0x22 / 2) - 1; // + terminator - - public string TrainerName - { - get => StringConverter6.GetString(Data.AsSpan(Offset + 0x21A, NameLengthBytes)); - set => StringConverter6.SetString(Data.AsSpan(Offset + 0x21A, NameLengthBytes), value.AsSpan(), NameLength, StringConverterOption.ClearZero); - } - - private Span GetMessageSpan(int index) => Data.AsSpan(Offset + 0x234 + (MessageLengthBytes * index), MessageLengthBytes); - private string GetMessage(int index) => StringConverter6.GetString(GetMessageSpan(index)); - private void SetMessage(int index, string value) => StringConverter6.SetString(GetMessageSpan(index), value.AsSpan(), MessageLength, StringConverterOption.ClearZero); - - public string TeamName { get => GetMessage(0); set => SetMessage(0, value); } - public string TeamSlogan { get => GetMessage(1); set => SetMessage(1, value); } - public string SayHappy { get => GetMessage(2); set => SetMessage(2, value); } - public string SayEncourage { get => GetMessage(3); set => SetMessage(3, value); } - public string SayBlackboard { get => GetMessage(4); set => SetMessage(4, value); } - public string SayConfettiBall { get => GetMessage(5); set => SetMessage(5, value); } - - public SecretBase6Rank Rank - { - get => (SecretBase6Rank) ReadInt32LittleEndian(Data.AsSpan(Offset + 0x300)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x300), (int)value); - } - - public uint TotalFlagsFromFriends - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x304)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x304), value); - } - - public uint TotalFlagsFromOther - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x308)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x308), value); - } - - public byte CollectedFlagsToday { get => Data[Offset + 0x30C]; set => Data[Offset + 0x30C] = value; } - public byte CollectedFlagsYesterday { get => Data[Offset + 0x30D]; set => Data[Offset + 0x30D] = value; } - - // Derived Values - - public bool IsDummiedBaseLocation => !IsEmpty && BaseLocation < 3; - public bool IsEmpty => BaseLocation <= 0; - - protected virtual void LoadOther(SecretBase6Other other) => LoadSelf(other); - private void LoadSelf(SecretBase6 other) => other.Data.AsSpan(other.Offset, SIZE).CopyTo(Data.AsSpan(Offset)); - - public void Load(SecretBase6 other) - { - if (other is SecretBase6Other o) - LoadOther(o); - else - LoadSelf(other); - } - - public virtual byte[] Write() => Data.Slice(Offset, SIZE); - - public static SecretBase6? Read(byte[] data) - { - return data.Length switch - { - SIZE => new SecretBase6(data), - SecretBase6Other.SIZE => new SecretBase6Other(data), - _ => null, - }; - } + Data = data; + Offset = offset; } - /// - /// An expanded structure of containing extra fields to describe another trainer's base. - /// - public sealed class SecretBase6Other : SecretBase6 + public bool IsNew { - public new const int SIZE = 0x3E0; + get => Data[Offset] == 1; + set => WriteUInt16LittleEndian(Data.AsSpan(Offset), (ushort)(value ? 1 : 0)); + } - public SecretBase6Other(byte[] data, int offset = 0) : base(data, offset) + private int RawLocation + { + get => ReadInt16LittleEndian(Data.AsSpan(Offset + 2)); + set => WriteInt16LittleEndian(Data.AsSpan(Offset + 2), (short)value); + } + + public int BaseLocation + { + get => RawLocation; + set => RawLocation = value switch { - } + 1 or 2 => 0, + > MaxLocationID => MaxLocationID, + < MinLocationID => -1, + _ => value, + }; + } - // [310-31F] u128 key struct - // [320] byte language - // [321] byte trainer gender - // 2 bytes unused alignment - // [324-327] u32 ??? - // [328-32D] byte[6] ??? - // 2 bytes unused alignment + public SecretBase6GoodPlacement GetPlacement(int index) => new(Data.AsSpan(Offset + GetPlacementOffset(index))); - // [330-3CB] SecretBase6PKM[3] (0x9C bytes, 0x34 each) - // [0 @ 330] - // [1 @ 364] - // [2 @ 398] + public void SetPlacement(int index, SecretBase6GoodPlacement value) => value.Write(Data.AsSpan(Offset + GetPlacementOffset(index))); - // [3CC-3D3] s64 ??? struct? - // [3D4-3D5] s16 ??? - // [3D6] byte ??? - // [3D7] byte ??? - // [3D8-3D9] flags - // remainder alignment + private static int GetPlacementOffset(int index) + { + if ((uint) index >= COUNT_GOODS) + throw new ArgumentOutOfRangeException(nameof(index)); + return 4 + (index * SecretBase6GoodPlacement.SIZE); + } - public byte Language + public byte BoppoyamaScore + { + get => Data[Offset + 0x174]; + set => Data[Offset + 0x174] = value; + } + + private const int NameLengthBytes = 0x1A; + private const int MessageLengthBytes = 0x22; + private const int NameLength = (0x1A / 2) - 1; // + terminator + private const int MessageLength = (0x22 / 2) - 1; // + terminator + + public string TrainerName + { + get => StringConverter6.GetString(Data.AsSpan(Offset + 0x21A, NameLengthBytes)); + set => StringConverter6.SetString(Data.AsSpan(Offset + 0x21A, NameLengthBytes), value.AsSpan(), NameLength, StringConverterOption.ClearZero); + } + + private Span GetMessageSpan(int index) => Data.AsSpan(Offset + 0x234 + (MessageLengthBytes * index), MessageLengthBytes); + private string GetMessage(int index) => StringConverter6.GetString(GetMessageSpan(index)); + private void SetMessage(int index, string value) => StringConverter6.SetString(GetMessageSpan(index), value.AsSpan(), MessageLength, StringConverterOption.ClearZero); + + public string TeamName { get => GetMessage(0); set => SetMessage(0, value); } + public string TeamSlogan { get => GetMessage(1); set => SetMessage(1, value); } + public string SayHappy { get => GetMessage(2); set => SetMessage(2, value); } + public string SayEncourage { get => GetMessage(3); set => SetMessage(3, value); } + public string SayBlackboard { get => GetMessage(4); set => SetMessage(4, value); } + public string SayConfettiBall { get => GetMessage(5); set => SetMessage(5, value); } + + public SecretBase6Rank Rank + { + get => (SecretBase6Rank) ReadInt32LittleEndian(Data.AsSpan(Offset + 0x300)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x300), (int)value); + } + + public uint TotalFlagsFromFriends + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x304)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x304), value); + } + + public uint TotalFlagsFromOther + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x308)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x308), value); + } + + public byte CollectedFlagsToday { get => Data[Offset + 0x30C]; set => Data[Offset + 0x30C] = value; } + public byte CollectedFlagsYesterday { get => Data[Offset + 0x30D]; set => Data[Offset + 0x30D] = value; } + + // Derived Values + + public bool IsDummiedBaseLocation => !IsEmpty && BaseLocation < 3; + public bool IsEmpty => BaseLocation <= 0; + + protected virtual void LoadOther(SecretBase6Other other) => LoadSelf(other); + private void LoadSelf(SecretBase6 other) => other.Data.AsSpan(other.Offset, SIZE).CopyTo(Data.AsSpan(Offset)); + + public void Load(SecretBase6 other) + { + if (other is SecretBase6Other o) + LoadOther(o); + else + LoadSelf(other); + } + + public virtual byte[] Write() => Data.Slice(Offset, SIZE); + + public static SecretBase6? Read(byte[] data) + { + return data.Length switch { - get => Data[Offset + 0x320]; - set => Data[Offset + 0x320] = value; - } - - public byte Gender - { - get => Data[Offset + 0x321]; - set => Data[Offset + 0x321] = value; - } - - public const int COUNT_TEAM = 3; - - public SecretBase6PKM GetParticipant(int index) - { - var data = Data.Slice(GetParticipantOffset(index), SecretBase6PKM.SIZE); - return new SecretBase6PKM(data); - } - - public void SetParticipant(int index, SecretBase6PKM pkm) - { - var ofs = GetParticipantOffset(index); - pkm.Data.CopyTo(Data, ofs); - } - - public int GetParticipantOffset(int index) - { - if ((uint) index >= COUNT_TEAM) - throw new ArgumentOutOfRangeException(nameof(index)); - return Offset + 0x330 + (index * SecretBase6PKM.SIZE); - } - - public SecretBase6PKM[] GetTeam() - { - var result = new SecretBase6PKM[COUNT_TEAM]; - ReadTeam(result); - return result; - } - - public void ReadTeam(SecretBase6PKM[] result) - { - for (int i = 0; i < COUNT_TEAM; i++) - result[i] = GetParticipant(i); - } - - public void SetTeam(SecretBase6PKM[] arr) - { - if (arr.Length != COUNT_TEAM) - throw new ArgumentOutOfRangeException(nameof(arr)); - - for (int i = 0; i < arr.Length; i++) - SetParticipant(i, arr[i]); - } - - protected override void LoadOther(SecretBase6Other other) => other.Data.AsSpan(other.Offset, SIZE).CopyTo(Data.AsSpan(Offset)); - - public override byte[] Write() => Data.Slice(Offset, SIZE); + SIZE => new SecretBase6(data), + SecretBase6Other.SIZE => new SecretBase6Other(data), + _ => null, + }; } } + +/// +/// An expanded structure of containing extra fields to describe another trainer's base. +/// +public sealed class SecretBase6Other : SecretBase6 +{ + public new const int SIZE = 0x3E0; + + public SecretBase6Other(byte[] data, int offset = 0) : base(data, offset) + { + } + + // [310-31F] u128 key struct + // [320] byte language + // [321] byte trainer gender + // 2 bytes unused alignment + // [324-327] u32 ??? + // [328-32D] byte[6] ??? + // 2 bytes unused alignment + + // [330-3CB] SecretBase6PKM[3] (0x9C bytes, 0x34 each) + // [0 @ 330] + // [1 @ 364] + // [2 @ 398] + + // [3CC-3D3] s64 ??? struct? + // [3D4-3D5] s16 ??? + // [3D6] byte ??? + // [3D7] byte ??? + // [3D8-3D9] flags + // remainder alignment + + public byte Language + { + get => Data[Offset + 0x320]; + set => Data[Offset + 0x320] = value; + } + + public byte Gender + { + get => Data[Offset + 0x321]; + set => Data[Offset + 0x321] = value; + } + + public const int COUNT_TEAM = 3; + + public SecretBase6PKM GetParticipant(int index) + { + var data = Data.Slice(GetParticipantOffset(index), SecretBase6PKM.SIZE); + return new SecretBase6PKM(data); + } + + public void SetParticipant(int index, SecretBase6PKM pk) + { + var ofs = GetParticipantOffset(index); + pk.Data.CopyTo(Data, ofs); + } + + public int GetParticipantOffset(int index) + { + if ((uint) index >= COUNT_TEAM) + throw new ArgumentOutOfRangeException(nameof(index)); + return Offset + 0x330 + (index * SecretBase6PKM.SIZE); + } + + public SecretBase6PKM[] GetTeam() + { + var result = new SecretBase6PKM[COUNT_TEAM]; + ReadTeam(result); + return result; + } + + public void ReadTeam(SecretBase6PKM[] result) + { + for (int i = 0; i < COUNT_TEAM; i++) + result[i] = GetParticipant(i); + } + + public void SetTeam(SecretBase6PKM[] arr) + { + if (arr.Length != COUNT_TEAM) + throw new ArgumentOutOfRangeException(nameof(arr)); + + for (int i = 0; i < arr.Length; i++) + SetParticipant(i, arr[i]); + } + + protected override void LoadOther(SecretBase6Other other) => other.Data.AsSpan(other.Offset, SIZE).CopyTo(Data.AsSpan(Offset)); + + public override byte[] Write() => Data.Slice(Offset, SIZE); +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6GoodPlacement.cs b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6GoodPlacement.cs index 6d5a92aba..6a6c0be62 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6GoodPlacement.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6GoodPlacement.cs @@ -1,41 +1,40 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SecretBase6GoodPlacement { - public sealed class SecretBase6GoodPlacement + public const int SIZE = 12; + + public ushort Good { get; set; } + public ushort X { get; set; } + public ushort Y { get; set; } + public byte Rotation { get; set; } + // byte unused + + public ushort Param1 { get; set; } + public ushort Param2 { get; set; } + + public SecretBase6GoodPlacement(ReadOnlySpan data) { - public const int SIZE = 12; + Good = ReadUInt16LittleEndian(data); + X = ReadUInt16LittleEndian(data[2..]); + Y = ReadUInt16LittleEndian(data[4..]); + Rotation = data[6]; - public ushort Good { get; set; } - public ushort X { get; set; } - public ushort Y { get; set; } - public byte Rotation { get; set; } - // byte unused + Param1 = ReadUInt16LittleEndian(data[8..]); + Param2 = ReadUInt16LittleEndian(data[10..]); + } - public ushort Param1 { get; set; } - public ushort Param2 { get; set; } + public void Write(Span data) + { + WriteUInt16LittleEndian(data, Good); + WriteUInt16LittleEndian(data[2..], X); + WriteUInt16LittleEndian(data[4..], Y); + data[6] = Rotation; - public SecretBase6GoodPlacement(ReadOnlySpan data) - { - Good = ReadUInt16LittleEndian(data); - X = ReadUInt16LittleEndian(data[2..]); - Y = ReadUInt16LittleEndian(data[4..]); - Rotation = data[6]; - - Param1 = ReadUInt16LittleEndian(data[8..]); - Param2 = ReadUInt16LittleEndian(data[10..]); - } - - public void Write(Span data) - { - WriteUInt16LittleEndian(data, Good); - WriteUInt16LittleEndian(data[2..], X); - WriteUInt16LittleEndian(data[4..], Y); - data[6] = Rotation; - - WriteUInt16LittleEndian(data[8..], Param1); - WriteUInt16LittleEndian(data[10..], Param2); - } + WriteUInt16LittleEndian(data[8..], Param1); + WriteUInt16LittleEndian(data[10..], Param2); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6GoodStock.cs b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6GoodStock.cs index 181508bbc..802906b16 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6GoodStock.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6GoodStock.cs @@ -1,29 +1,28 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generation 6 Secret Base Decoration Good Inventory stock for a given good-index. +/// +public sealed class SecretBase6GoodStock { - /// - /// Generation 6 Secret Base Decoration Good Inventory stock for a given good-index. - /// - public sealed class SecretBase6GoodStock + public const int SIZE = 4; + + public ushort Count { get; set; } + public bool IsNew { get; set; } + + public SecretBase6GoodStock(ReadOnlySpan data) { - public const int SIZE = 4; + Count = ReadUInt16LittleEndian(data); + IsNew = data[2] != 0; + } - public ushort Count { get; set; } - public bool IsNew { get; set; } - - public SecretBase6GoodStock(ReadOnlySpan data) - { - Count = ReadUInt16LittleEndian(data); - IsNew = data[2] != 0; - } - - public void Write(Span data) - { - WriteUInt16LittleEndian(data, Count); - data[2] = (byte)(IsNew ? 1 : 0); - data[3] = 0; - } + public void Write(Span data) + { + WriteUInt16LittleEndian(data, Count); + data[2] = (byte)(IsNew ? 1 : 0); + data[3] = 0; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6PKM.cs b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6PKM.cs index 522858a42..95b225cae 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6PKM.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6PKM.cs @@ -1,110 +1,109 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SecretBase6PKM : ISanityChecksum { - public sealed class SecretBase6PKM : ISanityChecksum + public const int SIZE = 0x34; + public readonly byte[] Data; + + public SecretBase6PKM(byte[] data) => Data = data; + public SecretBase6PKM() => Data = new byte[SIZE]; + + public uint EncryptionConstant { - public const int SIZE = 0x34; - public readonly byte[] Data; - - public SecretBase6PKM(byte[] data) => Data = data; - public SecretBase6PKM() => Data = new byte[SIZE]; - - public uint EncryptionConstant - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); - } - - public ushort Sanity - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); - } - - public ushort Checksum - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); - } - - public int Species - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); - } - - public int HeldItem - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); - } - - public int Ability { get => Data[0x0C]; set => Data[0x0C] = (byte)value; } - public int AbilityNumber { get => Data[0x0D]; set => Data[0x0D] = (byte)value; } - - public uint PID - { - get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); - set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); - } - - public int Nature { get => Data[0x14]; set => Data[0x14] = (byte)value; } - - public bool FatefulEncounter { get => (Data[0x15] & 1) == 1; set => Data[0x15] = (byte)((Data[0x15] & ~0x01) | (value ? 1 : 0)); } - public int Gender { get => (Data[0x15] >> 1) & 0x3; set => Data[0x15] = (byte)((Data[0x15] & ~0x06) | (value << 1)); } - public int Form { get => Data[0x15] >> 3; set => Data[0x15] = (byte)((Data[0x15] & 0x07) | (value << 3)); } - - public int EV_HP { get => Data[0x16]; set => Data[0x16] = (byte)value; } - public int EV_ATK { get => Data[0x17]; set => Data[0x17] = (byte)value; } - public int EV_DEF { get => Data[0x18]; set => Data[0x18] = (byte)value; } - public int EV_SPE { get => Data[0x19]; set => Data[0x19] = (byte)value; } - public int EV_SPA { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } - public int EV_SPD { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } - - public int Move1 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), (ushort)value); - } - - public int Move2 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); - } - - public int Move3 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x20)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x20), (ushort)value); - } - - public int Move4 - { - get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); - set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); - } - - public int Move1_PPUps { get => Data[0x24]; set => Data[0x24] = (byte)value; } - public int Move2_PPUps { get => Data[0x25]; set => Data[0x25] = (byte)value; } - public int Move3_PPUps { get => Data[0x26]; set => Data[0x26] = (byte)value; } - public int Move4_PPUps { get => Data[0x27]; set => Data[0x27] = (byte)value; } - - // they messed up their bit struct and these ended up as individual fields? (5bits per byte) - public int IV_HP { get => Data[0x28]; set => Data[0x28] = (byte)value; } - public int IV_ATK { get => Data[0x29]; set => Data[0x29] = (byte)value; } - public int IV_DEF { get => Data[0x2A]; set => Data[0x2A] = (byte)value; } - public int IV_SPE { get => Data[0x2B]; set => Data[0x2B] = (byte)value; } - public int IV_SPA { get => Data[0x2C]; set => Data[0x2C] = (byte)value; } - public int IV_SPD { get => Data[0x2D] & 0x1F; set => Data[0x2D] = (byte)((Data[0x2D] & ~31) | value); } - public bool IsEgg { get => ((Data[0x2D] >> 5) & 1) == 1; set => Data[0x2D] = (byte)((Data[0x2D] & ~32) | (value ? 32 : 0)); } - public bool IsShiny { get => ((Data[0x2D] >> 5) & 1) == 1; set => Data[0x2D] = (byte)((Data[0x2D] & ~32) | (value ? 32 : 0)); } - - public int CurrentFriendship { get => Data[0x2E]; set => Data[0x2E] = (byte)value; } - public int Ball { get => Data[0x2F]; set => Data[0x2F] = (byte)value; } - public int CurrentLevel { get => Data[0x30]; set => Data[0x30] = (byte)value; } - // 0x31,0x32,0x33 unused (alignment padding to u32) + get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } + + public ushort Sanity + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); + } + + public ushort Checksum + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); + } + + public int Species + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x08), (ushort)value); + } + + public int HeldItem + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); + } + + public int Ability { get => Data[0x0C]; set => Data[0x0C] = (byte)value; } + public int AbilityNumber { get => Data[0x0D]; set => Data[0x0D] = (byte)value; } + + public uint PID + { + get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); + set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); + } + + public int Nature { get => Data[0x14]; set => Data[0x14] = (byte)value; } + + public bool FatefulEncounter { get => (Data[0x15] & 1) == 1; set => Data[0x15] = (byte)((Data[0x15] & ~0x01) | (value ? 1 : 0)); } + public int Gender { get => (Data[0x15] >> 1) & 0x3; set => Data[0x15] = (byte)((Data[0x15] & ~0x06) | (value << 1)); } + public int Form { get => Data[0x15] >> 3; set => Data[0x15] = (byte)((Data[0x15] & 0x07) | (value << 3)); } + + public int EV_HP { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public int EV_ATK { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public int EV_DEF { get => Data[0x18]; set => Data[0x18] = (byte)value; } + public int EV_SPE { get => Data[0x19]; set => Data[0x19] = (byte)value; } + public int EV_SPA { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } + public int EV_SPD { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } + + public int Move1 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), (ushort)value); + } + + public int Move2 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); + } + + public int Move3 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x20)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x20), (ushort)value); + } + + public int Move4 + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); + } + + public int Move1_PPUps { get => Data[0x24]; set => Data[0x24] = (byte)value; } + public int Move2_PPUps { get => Data[0x25]; set => Data[0x25] = (byte)value; } + public int Move3_PPUps { get => Data[0x26]; set => Data[0x26] = (byte)value; } + public int Move4_PPUps { get => Data[0x27]; set => Data[0x27] = (byte)value; } + + // they messed up their bit struct and these ended up as individual fields? (5bits per byte) + public int IV_HP { get => Data[0x28]; set => Data[0x28] = (byte)value; } + public int IV_ATK { get => Data[0x29]; set => Data[0x29] = (byte)value; } + public int IV_DEF { get => Data[0x2A]; set => Data[0x2A] = (byte)value; } + public int IV_SPE { get => Data[0x2B]; set => Data[0x2B] = (byte)value; } + public int IV_SPA { get => Data[0x2C]; set => Data[0x2C] = (byte)value; } + public int IV_SPD { get => Data[0x2D] & 0x1F; set => Data[0x2D] = (byte)((Data[0x2D] & ~31) | value); } + public bool IsEgg { get => ((Data[0x2D] >> 5) & 1) == 1; set => Data[0x2D] = (byte)((Data[0x2D] & ~32) | (value ? 32 : 0)); } + public bool IsShiny { get => ((Data[0x2D] >> 5) & 1) == 1; set => Data[0x2D] = (byte)((Data[0x2D] & ~32) | (value ? 32 : 0)); } + + public int CurrentFriendship { get => Data[0x2E]; set => Data[0x2E] = (byte)value; } + public int Ball { get => Data[0x2F]; set => Data[0x2F] = (byte)value; } + public int CurrentLevel { get => Data[0x30]; set => Data[0x30] = (byte)value; } + // 0x31,0x32,0x33 unused (alignment padding to u32) } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6Rank.cs b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6Rank.cs index c90704ac0..8c4e8c79b 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6Rank.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6Rank.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum SecretBase6Rank { - public enum SecretBase6Rank - { - Default = 0, - Bronze = 1, - Silver = 2, - Gold = 3, - Platinum = 4, - } + Default = 0, + Bronze = 1, + Silver = 2, + Gold = 3, + Platinum = 4, } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase6Block.cs b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase6Block.cs index 93aaecb97..f343cda64 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase6Block.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase6Block.cs @@ -1,84 +1,83 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SecretBase6Block : SaveBlock { - public sealed class SecretBase6Block : SaveBlock + public SecretBase6Block(SAV6AO sav, int offset) : base(sav) => Offset = offset; + + // structure: 0x7AD0 bytes total + // [0000-031F] SecretBaseGoodStock[200] (800 bytes) + // [0320-0321] u16 SecretBaseSelfLocation (-1 if not created) + // [0322-0323] u16 alignment padding + // [0324-0633] SecretBase6Self (0x310 bytes) + // [0634-0637] alignment for next field + // [0638-7A77] SecretBase6Other[30] (0x7440 bytes, 0x3E0 each) + // each SecretBase6Other is a SecretBaseSelf followed by extra fields + // [7A78-7AC7] u128[5] keys? + // [7AC8.. ] u8 SecretBaseHasFlag + + public const int Count_Goods = 200; + public const int Count_Goods_Used = 173; + + public SecretBase6GoodStock GetGood(int index) => new(Data.AsSpan(Offset + GetGoodOffset(index))); + public void SetGood(SecretBase6GoodStock good, int index) => good.Write(Data.AsSpan(Offset + GetGoodOffset(index))); + + private static int GetGoodOffset(int index) { - public SecretBase6Block(SAV6AO sav, int offset) : base(sav) => Offset = offset; + if ((uint) index >= Count_Goods) + throw new ArgumentOutOfRangeException(nameof(index)); + return SecretBase6GoodStock.SIZE * index; + } - // structure: 0x7AD0 bytes total - // [0000-031F] SecretBaseGoodStock[200] (800 bytes) - // [0320-0321] u16 SecretBaseSelfLocation (-1 if not created) - // [0322-0323] u16 alignment padding - // [0324-0633] SecretBase6Self (0x310 bytes) - // [0634-0637] alignment for next field - // [0638-7A77] SecretBase6Other[30] (0x7440 bytes, 0x3E0 each) - // each SecretBase6Other is a SecretBaseSelf followed by extra fields - // [7A78-7AC7] u128[5] keys? - // [7AC8.. ] u8 SecretBaseHasFlag + public void GiveAllGoods() + { + const uint value = 25u | (1 << 16); // count: 25, new flag. + for (int i = 0; i < Count_Goods_Used; i++) + WriteUInt32LittleEndian(Data.AsSpan(Offset + GetGoodOffset(i)), value); + } - public const int Count_Goods = 200; - public const int Count_Goods_Used = 173; + public ushort SecretBaseSelfLocation + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 800)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 800), value); + } - public SecretBase6GoodStock GetGood(int index) => new(Data.AsSpan(Offset + GetGoodOffset(index))); - public void SetGood(SecretBase6GoodStock good, int index) => good.Write(Data.AsSpan(Offset + GetGoodOffset(index))); + public const int OtherSecretBaseCount = 30; + private const int OtherSecretStart = 0x638; + public SecretBase6 GetSecretBaseSelf() => new(Data, Offset + 0x324); + public SecretBase6Other GetSecretBaseOther(int index) => new(Data, Offset + OtherSecretStart + GetSecretBaseOtherOffset(index)); - private static int GetGoodOffset(int index) + private static int GetSecretBaseOtherOffset(int index) + { + if ((uint) index >= OtherSecretBaseCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return SecretBase6Other.SIZE * index; + } + + public bool SecretBaseHasFlag + { + get => Data[Offset + 0x7AC8] == 1; + set => Data[Offset + 0x7AC8] = (byte) (value ? 1 : 0); + } + + public void DeleteOther(int index) + { + int baseOffset = Offset + OtherSecretStart; + const int maxBaseIndex = OtherSecretBaseCount - 1; + const int size = SecretBase6Other.SIZE; + int offset = baseOffset + GetSecretBaseOtherOffset(index); + var arr = SAV.Data; + if ((uint)index < OtherSecretBaseCount) { - if ((uint) index >= Count_Goods) - throw new ArgumentOutOfRangeException(nameof(index)); - return SecretBase6GoodStock.SIZE * index; + int shiftDownCount = maxBaseIndex - index; + int shiftDownLength = size * shiftDownCount; + Array.Copy(arr, offset + size, arr, offset, shiftDownLength); } - public void GiveAllGoods() - { - const uint value = 25u | (1 << 16); // count: 25, new flag. - for (int i = 0; i < Count_Goods_Used; i++) - WriteUInt32LittleEndian(Data.AsSpan(Offset + GetGoodOffset(i)), value); - } - - public ushort SecretBaseSelfLocation - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 800)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 800), value); - } - - public const int OtherSecretBaseCount = 30; - private const int OtherSecretStart = 0x638; - public SecretBase6 GetSecretBaseSelf() => new(Data, Offset + 0x324); - public SecretBase6Other GetSecretBaseOther(int index) => new(Data, Offset + OtherSecretStart + GetSecretBaseOtherOffset(index)); - - private static int GetSecretBaseOtherOffset(int index) - { - if ((uint) index >= OtherSecretBaseCount) - throw new ArgumentOutOfRangeException(nameof(index)); - return SecretBase6Other.SIZE * index; - } - - public bool SecretBaseHasFlag - { - get => Data[Offset + 0x7AC8] == 1; - set => Data[Offset + 0x7AC8] = (byte) (value ? 1 : 0); - } - - public void DeleteOther(int index) - { - int baseOffset = Offset + OtherSecretStart; - const int maxBaseIndex = OtherSecretBaseCount - 1; - const int size = SecretBase6Other.SIZE; - int offset = baseOffset + GetSecretBaseOtherOffset(index); - var arr = SAV.Data; - if ((uint)index < OtherSecretBaseCount) - { - int shiftDownCount = maxBaseIndex - index; - int shiftDownLength = size * shiftDownCount; - Array.Copy(arr, offset + size, arr, offset, shiftDownLength); - } - - // Ensure Last Entry is Cleared - int lastBaseOffset = baseOffset + (size * maxBaseIndex); - arr.AsSpan(lastBaseOffset, size).Clear(); - } + // Ensure Last Entry is Cleared + int lastBaseOffset = baseOffset + (size * maxBaseIndex); + arr.AsSpan(lastBaseOffset, size).Clear(); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/Situation6.cs b/PKHeX.Core/Saves/Substructures/Gen6/Situation6.cs index bbc28429e..2e71b1495 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/Situation6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/Situation6.cs @@ -1,61 +1,60 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Situation6 : SaveBlock { - public sealed class Situation6 : SaveBlock + public Situation6(SAV6 SAV, int offset) : base(SAV) => Offset = offset; + + public int M { - public Situation6(SAV6 SAV, int offset) : base(SAV) => Offset = offset; - - public int M + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x02)); + set { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x02)); - set - { - var span = Data.AsSpan(Offset + 0x02); - WriteUInt16LittleEndian(span, (ushort)value); - WriteUInt16LittleEndian(span[0xF4..], (ushort)value); - } - } - - public float X - { - get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x10)) / 18; - set - { - var span = Data.AsSpan(Offset + 0x10); - WriteSingleLittleEndian(span, value * 18); - WriteSingleLittleEndian(span[0xF4..], value * 18); - } - } - - public float Z - { - get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x14)); - set - { - var span = Data.AsSpan(Offset + 0x14); - WriteSingleLittleEndian(span, value * 18); - WriteSingleLittleEndian(span[0xF4..], value * 18); - } - } - - public float Y - { - get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x18)) / 18; - set - { - var span = Data.AsSpan(Offset + 0x18); - WriteSingleLittleEndian(span, value * 18); - WriteSingleLittleEndian(span[0xF4..], value * 18); - } - } - - // xy only - public int Style - { - get => Data[Offset + 0x14D]; - set => Data[Offset + 0x14D] = (byte)value; + var span = Data.AsSpan(Offset + 0x02); + WriteUInt16LittleEndian(span, (ushort)value); + WriteUInt16LittleEndian(span[0xF4..], (ushort)value); } } + + public float X + { + get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x10)) / 18; + set + { + var span = Data.AsSpan(Offset + 0x10); + WriteSingleLittleEndian(span, value * 18); + WriteSingleLittleEndian(span[0xF4..], value * 18); + } + } + + public float Z + { + get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x14)); + set + { + var span = Data.AsSpan(Offset + 0x14); + WriteSingleLittleEndian(span, value * 18); + WriteSingleLittleEndian(span[0xF4..], value * 18); + } + } + + public float Y + { + get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x18)) / 18; + set + { + var span = Data.AsSpan(Offset + 0x18); + WriteSingleLittleEndian(span, value * 18); + WriteSingleLittleEndian(span[0xF4..], value * 18); + } + } + + // xy only + public int Style + { + get => Data[Offset + 0x14D]; + set => Data[Offset + 0x14D] = (byte)value; + } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/SubEventLog6.cs b/PKHeX.Core/Saves/Substructures/Gen6/SubEventLog6.cs index c42edf5a3..19fe2a142 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/SubEventLog6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/SubEventLog6.cs @@ -1,155 +1,154 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// SUBE block that stores in-game event results. +/// +public abstract class SubEventLog6 : SaveBlock, IGymTeamInfo { + protected SubEventLog6(SAV6 sav, int offset) : base(sav) => Offset = offset; + + protected abstract int BadgeVictoryOffset { get; } + /// - /// SUBE block that stores in-game event results. + /// Absolute offset of the that the player has given an NPC. /// - public abstract class SubEventLog6 : SaveBlock, IGymTeamInfo + public abstract int Give { get; } + + /// + /// Absolute offset of the that is unreferenced? + /// + public abstract int UnusedPKM { get; } + + private int GetBadgeVictorySpeciesOffset(uint badge, uint slot) { - protected SubEventLog6(SAV6 sav, int offset) : base(sav) => Offset = offset; + if (badge >= 8) + throw new ArgumentOutOfRangeException(nameof(badge)); + if (slot >= 6) + throw new ArgumentOutOfRangeException(nameof(slot)); - protected abstract int BadgeVictoryOffset { get; } - - /// - /// Absolute offset of the that the player has given an NPC. - /// - public abstract int Give { get; } - - /// - /// Absolute offset of the that is unreferenced? - /// - public abstract int UnusedPKM { get; } - - private int GetBadgeVictorySpeciesOffset(uint badge, uint slot) - { - if (badge >= 8) - throw new ArgumentOutOfRangeException(nameof(badge)); - if (slot >= 6) - throw new ArgumentOutOfRangeException(nameof(slot)); - - return Offset + BadgeVictoryOffset + (int)(((6 * badge) + slot) * sizeof(ushort)); - } - - public ushort GetBadgeVictorySpecies(uint badge, uint slot) - { - var ofs = GetBadgeVictorySpeciesOffset(badge, slot); - return ReadUInt16LittleEndian(Data.AsSpan(ofs)); - } - - public void SetBadgeVictorySpecies(uint badge, uint slot, ushort species) - { - var ofs = GetBadgeVictorySpeciesOffset(badge, slot); - WriteUInt16LittleEndian(Data.AsSpan(ofs), species); - } + return Offset + BadgeVictoryOffset + (int)(((6 * badge) + slot) * sizeof(ushort)); } - public sealed class SubEventLog6XY : SubEventLog6 + public ushort GetBadgeVictorySpecies(uint badge, uint slot) { - public SubEventLog6XY(SAV6XY sav, int offset) : base(sav, offset) { } - - // Structure: - - // 0x00 - // u8[0x28] chateau data - private ushort ChateauValue - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset), value); - } - - public ushort ChateauRank - { - get => (ushort)(ChateauValue & 0xF); - set => ChateauValue = (ushort)((ChateauValue & ~0xFu) | (value & 0xFu)); - } - - public ushort ChateauPoints - { - get => (ushort)(ChateauValue >> 4); - set => ChateauValue = (ushort)((ushort)(value << 4) | (ChateauValue & 0xFu)); - } - // other chateau data? - // u32 SUBE @ 0x28 - - // 0x2C - protected override int BadgeVictoryOffset => 0x2C; // thru 0x8B - // u16[6 * 8] trainer teams for gyms - // u32 SUBE @ 0x8C - - // 0x90 - // u8[0xE8] pkm? - public override int Give => 0x90 + Offset; - // u32 SUBE @ 0x178 - - // 0x17C - // u8[0xE8] pkm? - public override int UnusedPKM => 0x17C + Offset; - // u32 SUBE @ 0x264 - - // 0x268 - // u8[0xA0] unused? + var ofs = GetBadgeVictorySpeciesOffset(badge, slot); + return ReadUInt16LittleEndian(Data.AsSpan(ofs)); } - public sealed class SubEventLog6AO : SubEventLog6 + public void SetBadgeVictorySpecies(uint badge, uint slot, ushort species) { - public SubEventLog6AO(SAV6AO sav, int offset) : base(sav, offset) { } - - // Structure: - - // 0x00 - // u8[0x5A] trainer rematch flags - // u8[2] unused (alignment) - // u32 SUBE @ 0x5C - - // 0x60 - protected override int BadgeVictoryOffset => 0x60; // thru 0xBF - // u16[6 * 8] trainer teams for gyms - // u32 SUBE @ 0xC0 - - // 0xC4 - // u8[0xE8] pkm? - public override int Give => 0xC4 + Offset; - // u32 SUBE @ 0x1AC - - // 0x1B0 - // u8[0xE8] pkm? - public override int UnusedPKM => 0x1B0 + Offset; - // u32 SUBE @ 0x298 - - // 0x29C - // u16 SeasideCyclingRoadTimeMilliseconds 29C - public ushort SeasideCyclingRoadTimeMilliseconds - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x29C)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x29C), value); - } - // u16 SeasideCyclingRoadCollisions 29E - public ushort SeasideCyclingRoadCollisions - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x29E)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x29E), value); - } - // u16[7] 2A0 - // u16[7] 2AE - // u16[17] 2BC - // u16[7] 2EA - // u16 2EC - // u16 2EE - // u16 2F0 - // u16 2F2 - // u32 SUBE @ 0x2F4 - - // 0x2F8 - // u64[27] Ending Scroll ec-specform data -- player pkm used & NPC used during storyline battles - // u32 ec - // u32 species:10 - // u32 form:5 - // u32 gender:2 - // u32 isShiny:1 - // u32 unused:14 - // u8[16] - // u8[32] unused? + var ofs = GetBadgeVictorySpeciesOffset(badge, slot); + WriteUInt16LittleEndian(Data.AsSpan(ofs), species); } } + +public sealed class SubEventLog6XY : SubEventLog6 +{ + public SubEventLog6XY(SAV6XY sav, int offset) : base(sav, offset) { } + + // Structure: + + // 0x00 + // u8[0x28] chateau data + private ushort ChateauValue + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset), value); + } + + public ushort ChateauRank + { + get => (ushort)(ChateauValue & 0xF); + set => ChateauValue = (ushort)((ChateauValue & ~0xFu) | (value & 0xFu)); + } + + public ushort ChateauPoints + { + get => (ushort)(ChateauValue >> 4); + set => ChateauValue = (ushort)((ushort)(value << 4) | (ChateauValue & 0xFu)); + } + // other chateau data? + // u32 SUBE @ 0x28 + + // 0x2C + protected override int BadgeVictoryOffset => 0x2C; // thru 0x8B + // u16[6 * 8] trainer teams for gyms + // u32 SUBE @ 0x8C + + // 0x90 + // u8[0xE8] pk? + public override int Give => 0x90 + Offset; + // u32 SUBE @ 0x178 + + // 0x17C + // u8[0xE8] pk? + public override int UnusedPKM => 0x17C + Offset; + // u32 SUBE @ 0x264 + + // 0x268 + // u8[0xA0] unused? +} + +public sealed class SubEventLog6AO : SubEventLog6 +{ + public SubEventLog6AO(SAV6AO sav, int offset) : base(sav, offset) { } + + // Structure: + + // 0x00 + // u8[0x5A] trainer rematch flags + // u8[2] unused (alignment) + // u32 SUBE @ 0x5C + + // 0x60 + protected override int BadgeVictoryOffset => 0x60; // thru 0xBF + // u16[6 * 8] trainer teams for gyms + // u32 SUBE @ 0xC0 + + // 0xC4 + // u8[0xE8] pk? + public override int Give => 0xC4 + Offset; + // u32 SUBE @ 0x1AC + + // 0x1B0 + // u8[0xE8] pk? + public override int UnusedPKM => 0x1B0 + Offset; + // u32 SUBE @ 0x298 + + // 0x29C + // u16 SeasideCyclingRoadTimeMilliseconds 29C + public ushort SeasideCyclingRoadTimeMilliseconds + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x29C)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x29C), value); + } + // u16 SeasideCyclingRoadCollisions 29E + public ushort SeasideCyclingRoadCollisions + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x29E)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x29E), value); + } + // u16[7] 2A0 + // u16[7] 2AE + // u16[17] 2BC + // u16[7] 2EA + // u16 2EC + // u16 2EE + // u16 2F0 + // u16 2F2 + // u32 SUBE @ 0x2F4 + + // 0x2F8 + // u64[27] Ending Scroll ec-specform data -- player pk used & NPC used during storyline battles + // u32 ec + // u32 species:10 + // u32 form:5 + // u32 gender:2 + // u32 isShiny:1 + // u32 unused:14 + // u8[16] + // u8[32] unused? +} diff --git a/PKHeX.Core/Saves/Substructures/Gen6/SuperTrainBlock.cs b/PKHeX.Core/Saves/Substructures/Gen6/SuperTrainBlock.cs index 9c2402eda..524fc508c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/SuperTrainBlock.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/SuperTrainBlock.cs @@ -1,341 +1,340 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SuperTrainBlock : SaveBlock { - public sealed class SuperTrainBlock : SaveBlock + public SuperTrainBlock(SAV6XY sav, int offset) : base(sav) => Offset = offset; + public SuperTrainBlock(SAV6AO sav, int offset) : base(sav) => Offset = offset; + + // Structure: + // 6 bytes stage unlock flags + // 1 byte distribution stage unlock flags + // 1 byte counter (max value = 10) + // float[48] recordScore1; // 0x08, 4byte/entry + // float[48] recordScore2; // 0xC8, 4byte/entry + // SS-F-G[48] recordHolder1; // 0x188, 4byte/entry + // SS-F-G[48] recordHolder2; // 0x248, 4byte/entry + // byte[12] bags // 0x308 + // u32 tutorial tracker (max value = 6) // 0x314 + + // 0x318 total size + + // SS-F-G = u16 species, u8 form, u8 gender + + private const int MAX = 48; + private const int MAX_RELEASED = 32; + private const int MAX_DIST = 6; + private const int MAX_BAG = 12; + + /// + /// Checks if a Regimen is unlocked. + /// + /// Index of regimen. + /// Is Unlocked + public bool GetIsRegimenUnlocked(int index) { - public SuperTrainBlock(SAV6XY sav, int offset) : base(sav) => Offset = offset; - public SuperTrainBlock(SAV6AO sav, int offset) : base(sav) => Offset = offset; - - // Structure: - // 6 bytes stage unlock flags - // 1 byte distribution stage unlock flags - // 1 byte counter (max value = 10) - // float[48] recordScore1; // 0x08, 4byte/entry - // float[48] recordScore2; // 0xC8, 4byte/entry - // SS-F-G[48] recordHolder1; // 0x188, 4byte/entry - // SS-F-G[48] recordHolder2; // 0x248, 4byte/entry - // byte[12] bags // 0x308 - // u32 tutorial tracker (max value = 6) // 0x314 - - // 0x318 total size - - // SS-F-G = u16 species, u8 form, u8 gender - - private const int MAX = 48; - private const int MAX_RELEASED = 32; - private const int MAX_DIST = 6; - private const int MAX_BAG = 12; - - /// - /// Checks if a Regimen is unlocked. - /// - /// Index of regimen. - /// Is Unlocked - public bool GetIsRegimenUnlocked(int index) - { - if ((uint)index >= MAX) - throw new ArgumentOutOfRangeException(nameof(index)); - return SAV.GetFlag(Offset, index); - } - - /// - /// Sets a Regimen to the desired unlocked state. - /// - /// Index of regimen. - /// Is Unlocked - public void SetIsRegimenUnlocked(int index, bool value) - { - if ((uint)index >= MAX) - throw new ArgumentOutOfRangeException(nameof(index)); - SAV.SetFlag(Offset, index, value); - } - - /// - /// Checks if a Distribution Regimen is unlocked. - /// - /// Index of regimen. - /// Is Unlocked - public bool GetIsDistributionUnlocked(int index) - { - if ((uint)index >= MAX_DIST) - throw new ArgumentOutOfRangeException(nameof(index)); - return SAV.GetFlag(Offset + 6, index); - } - - /// - /// Sets a Distribution Regimen to the desired unlocked state. - /// - /// Index of regimen. - /// Is Unlocked - public void SetIsDistributionUnlocked(int index, bool value) - { - if ((uint)index >= MAX_DIST) - throw new ArgumentOutOfRangeException(nameof(index)); - SAV.SetFlag(Offset + 6, index, value); - } - - /// - /// Counts something up to 10 (overall stages unlocked?) - /// - public byte Counter - { - get => Data[Offset + 7]; - set => Data[Offset + 7] = Math.Min((byte)10, value); - } - - /// - /// Gets the record time. - /// - /// Index of the record. - public float GetTime1(int index) - { - if ((uint) index >= MAX) - throw new ArgumentOutOfRangeException(nameof(index)); - - return ReadSingleLittleEndian(Data.AsSpan(Offset + 0x08 + (4 * index))); - } - - /// - /// Sets the record time. - /// - /// Index of the record. - /// Value to set. - public void SetTime1(int index, float value = 0) - { - if ((uint)index >= MAX) - throw new ArgumentOutOfRangeException(nameof(index)); - - WriteSingleLittleEndian(Data.AsSpan(Offset + 0x08 + (4 * index)), value); - } - - /// - /// Gets the record time. - /// - /// Index of the record. - public float GetTime2(int index) - { - if ((uint)index >= MAX) - throw new ArgumentOutOfRangeException(nameof(index)); - - return ReadSingleLittleEndian(Data.AsSpan(Offset + 0xC8 + (4 * index))); - } - - /// - /// Sets the record time. - /// - /// Index of the record. - /// Value to set. - public void SetTime2(int index, float value = 0) - { - if ((uint)index >= MAX) - throw new ArgumentOutOfRangeException(nameof(index)); - - WriteSingleLittleEndian(Data.AsSpan(Offset + 0xC8 + (4 * index)), value); - } - - /// - /// Returns an object which will edit the record directly from data. - /// - /// Index of the record. - /// Object that will edit the record data if modified. - public SuperTrainingSpeciesRecord GetHolder1(int index) - { - if ((uint)index >= MAX) - throw new ArgumentOutOfRangeException(nameof(index)); - - return new SuperTrainingSpeciesRecord(Data, Offset + 0x188 + (4 * index)); - } - - /// - /// Returns an object which will edit the record directly from data. - /// - /// Index of the record. - /// Object that will edit the record data if modified. - public SuperTrainingSpeciesRecord GetHolder2(int index) - { - if ((uint)index >= MAX) - throw new ArgumentOutOfRangeException(nameof(index)); - - return new SuperTrainingSpeciesRecord(Data, Offset + 0x248 + (4 * index)); - } - - /// - /// Gets the bag from the desired . - /// - /// Bag index - public byte GetBag(int index) - { - if ((uint)index >= MAX_BAG) - throw new ArgumentOutOfRangeException(nameof(index)); - return Data[Offset + 0x308 + index]; - } - - /// - /// Sets a bag to the desired . - /// - /// Bag index - /// Bag ID - public void SetBag(int index, byte value) - { - if ((uint)index >= MAX_BAG) - throw new ArgumentOutOfRangeException(nameof(index)); - Data[Offset + 0x308 + index] = value; - } - - /// - /// Gets the next open bag index. - /// - /// Bag index that is empty - public int GetOpenBagIndex() - { - for (int i = 0; i < MAX_BAG; i++) - { - if (GetBag(i) != 0) - return i; - } - - return -1; - } - - /// - /// Adds a bag to the list of bags. - /// - /// Bag type - /// Bag was added or not - public bool AddBag(byte bag) - { - int index = GetOpenBagIndex(); - if (index < 0) - return false; - SetBag(index, bag); - return true; - } - - /// - /// Removes a bag from the list of bags. - /// - /// Bag index - public void RemoveBag(int index) - { - if ((uint)index >= MAX_BAG) - throw new ArgumentOutOfRangeException(nameof(index)); - for (int i = index; i < MAX_BAG - 1; i++) - { - var next = GetBag(i + 1); - SetBag(i, next); - } - SetBag(MAX_BAG - 1, 0); - } - - public uint TutorialIndex - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x314)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x314), value); - } - - /// - /// Clears all data for the record. - /// - /// Index of the record. - public void ClearRecord1(int index) - { - if ((uint)index >= MAX) - throw new ArgumentOutOfRangeException(nameof(index)); - - SetTime1(index, 0f); - GetHolder1(index).Clear(); - } - - /// - /// Clears all data for the record. - /// - /// Index of the record. - public void ClearRecord2(int index) - { - if ((uint)index >= MAX) - throw new ArgumentOutOfRangeException(nameof(index)); - - SetTime2(index, 0f); - GetHolder2(index).Clear(); - } - - /// - /// Unlocks all stages. - /// - /// Unlock all Distribution stages too. - public void UnlockAllStages(bool dist) - { - for (int i = 0; i < MAX_RELEASED; i++) - SetIsRegimenUnlocked(i, true); - - if (!dist) - return; - - for (int i = 0; i < MAX_DIST; i++) - SetIsDistributionUnlocked(i, true); - } - - /// - /// Clears all data in the block. - /// - public void ClearBlock() => Array.Clear(Data, Offset, 0x318); + if ((uint)index >= MAX) + throw new ArgumentOutOfRangeException(nameof(index)); + return SAV.GetFlag(Offset, index); } - public sealed class SuperTrainingSpeciesRecord : ISpeciesForm + /// + /// Sets a Regimen to the desired unlocked state. + /// + /// Index of regimen. + /// Is Unlocked + public void SetIsRegimenUnlocked(int index, bool value) { - private readonly byte[] Data; - private readonly int Offset; + if ((uint)index >= MAX) + throw new ArgumentOutOfRangeException(nameof(index)); + SAV.SetFlag(Offset, index, value); + } - public SuperTrainingSpeciesRecord(byte[] data, int offset) + /// + /// Checks if a Distribution Regimen is unlocked. + /// + /// Index of regimen. + /// Is Unlocked + public bool GetIsDistributionUnlocked(int index) + { + if ((uint)index >= MAX_DIST) + throw new ArgumentOutOfRangeException(nameof(index)); + return SAV.GetFlag(Offset + 6, index); + } + + /// + /// Sets a Distribution Regimen to the desired unlocked state. + /// + /// Index of regimen. + /// Is Unlocked + public void SetIsDistributionUnlocked(int index, bool value) + { + if ((uint)index >= MAX_DIST) + throw new ArgumentOutOfRangeException(nameof(index)); + SAV.SetFlag(Offset + 6, index, value); + } + + /// + /// Counts something up to 10 (overall stages unlocked?) + /// + public byte Counter + { + get => Data[Offset + 7]; + set => Data[Offset + 7] = Math.Min((byte)10, value); + } + + /// + /// Gets the record time. + /// + /// Index of the record. + public float GetTime1(int index) + { + if ((uint) index >= MAX) + throw new ArgumentOutOfRangeException(nameof(index)); + + return ReadSingleLittleEndian(Data.AsSpan(Offset + 0x08 + (4 * index))); + } + + /// + /// Sets the record time. + /// + /// Index of the record. + /// Value to set. + public void SetTime1(int index, float value = 0) + { + if ((uint)index >= MAX) + throw new ArgumentOutOfRangeException(nameof(index)); + + WriteSingleLittleEndian(Data.AsSpan(Offset + 0x08 + (4 * index)), value); + } + + /// + /// Gets the record time. + /// + /// Index of the record. + public float GetTime2(int index) + { + if ((uint)index >= MAX) + throw new ArgumentOutOfRangeException(nameof(index)); + + return ReadSingleLittleEndian(Data.AsSpan(Offset + 0xC8 + (4 * index))); + } + + /// + /// Sets the record time. + /// + /// Index of the record. + /// Value to set. + public void SetTime2(int index, float value = 0) + { + if ((uint)index >= MAX) + throw new ArgumentOutOfRangeException(nameof(index)); + + WriteSingleLittleEndian(Data.AsSpan(Offset + 0xC8 + (4 * index)), value); + } + + /// + /// Returns an object which will edit the record directly from data. + /// + /// Index of the record. + /// Object that will edit the record data if modified. + public SuperTrainingSpeciesRecord GetHolder1(int index) + { + if ((uint)index >= MAX) + throw new ArgumentOutOfRangeException(nameof(index)); + + return new SuperTrainingSpeciesRecord(Data, Offset + 0x188 + (4 * index)); + } + + /// + /// Returns an object which will edit the record directly from data. + /// + /// Index of the record. + /// Object that will edit the record data if modified. + public SuperTrainingSpeciesRecord GetHolder2(int index) + { + if ((uint)index >= MAX) + throw new ArgumentOutOfRangeException(nameof(index)); + + return new SuperTrainingSpeciesRecord(Data, Offset + 0x248 + (4 * index)); + } + + /// + /// Gets the bag from the desired . + /// + /// Bag index + public byte GetBag(int index) + { + if ((uint)index >= MAX_BAG) + throw new ArgumentOutOfRangeException(nameof(index)); + return Data[Offset + 0x308 + index]; + } + + /// + /// Sets a bag to the desired . + /// + /// Bag index + /// Bag ID + public void SetBag(int index, byte value) + { + if ((uint)index >= MAX_BAG) + throw new ArgumentOutOfRangeException(nameof(index)); + Data[Offset + 0x308 + index] = value; + } + + /// + /// Gets the next open bag index. + /// + /// Bag index that is empty + public int GetOpenBagIndex() + { + for (int i = 0; i < MAX_BAG; i++) { - Data = data; - Offset = offset; + if (GetBag(i) != 0) + return i; } - /// - /// of the Record Holder. - /// - public int Species - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), (ushort)value); - } + return -1; + } - /// - /// of the Record Holder. - /// - public int Form - { - get => Data[Offset + 2]; - set => Data[Offset + 2] = (byte)value; - } + /// + /// Adds a bag to the list of bags. + /// + /// Bag type + /// Bag was added or not + public bool AddBag(byte bag) + { + int index = GetOpenBagIndex(); + if (index < 0) + return false; + SetBag(index, bag); + return true; + } - /// - /// of the Record Holder. - /// - /// - public int Gender + /// + /// Removes a bag from the list of bags. + /// + /// Bag index + public void RemoveBag(int index) + { + if ((uint)index >= MAX_BAG) + throw new ArgumentOutOfRangeException(nameof(index)); + for (int i = index; i < MAX_BAG - 1; i++) { - get => Data[Offset + 3]; - set => Data[Offset + 3] = (byte)value; + var next = GetBag(i + 1); + SetBag(i, next); } + SetBag(MAX_BAG - 1, 0); + } - /// - /// Wipes the record holder's pkm-related data. - /// - public void Clear() => Species = Form = Gender = 0; + public uint TutorialIndex + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x314)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x314), value); + } - /// - /// Sets the data to match what is in the provided reference. - /// - /// Reference to load from. - public void LoadFrom(PKM pkm) - { - Species = pkm.Species; - Form = pkm.Form; - Gender = pkm.Gender; - } + /// + /// Clears all data for the record. + /// + /// Index of the record. + public void ClearRecord1(int index) + { + if ((uint)index >= MAX) + throw new ArgumentOutOfRangeException(nameof(index)); + + SetTime1(index, 0f); + GetHolder1(index).Clear(); + } + + /// + /// Clears all data for the record. + /// + /// Index of the record. + public void ClearRecord2(int index) + { + if ((uint)index >= MAX) + throw new ArgumentOutOfRangeException(nameof(index)); + + SetTime2(index, 0f); + GetHolder2(index).Clear(); + } + + /// + /// Unlocks all stages. + /// + /// Unlock all Distribution stages too. + public void UnlockAllStages(bool dist) + { + for (int i = 0; i < MAX_RELEASED; i++) + SetIsRegimenUnlocked(i, true); + + if (!dist) + return; + + for (int i = 0; i < MAX_DIST; i++) + SetIsDistributionUnlocked(i, true); + } + + /// + /// Clears all data in the block. + /// + public void ClearBlock() => Array.Clear(Data, Offset, 0x318); +} + +public sealed class SuperTrainingSpeciesRecord : ISpeciesForm +{ + private readonly byte[] Data; + private readonly int Offset; + + public SuperTrainingSpeciesRecord(byte[] data, int offset) + { + Data = data; + Offset = offset; + } + + /// + /// of the Record Holder. + /// + public int Species + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), (ushort)value); + } + + /// + /// of the Record Holder. + /// + public int Form + { + get => Data[Offset + 2]; + set => Data[Offset + 2] = (byte)value; + } + + /// + /// of the Record Holder. + /// + /// + public int Gender + { + get => Data[Offset + 3]; + set => Data[Offset + 3] = (byte)value; + } + + /// + /// Wipes the record holder's pk-related data. + /// + public void Clear() => Species = Form = Gender = 0; + + /// + /// Sets the data to match what is in the provided reference. + /// + /// Reference to load from. + public void LoadFrom(PKM pk) + { + Species = pk.Species; + Form = pk.Form; + Gender = pk.Gender; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/TrainerFashion6.cs b/PKHeX.Core/Saves/Substructures/Gen6/TrainerFashion6.cs index 348bb82b4..614d07b42 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/TrainerFashion6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/TrainerFashion6.cs @@ -1,594 +1,593 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class TrainerFashion6 { - public abstract class TrainerFashion6 + protected uint data0; + protected uint data1; + protected uint data2; + protected uint data3; + + protected TrainerFashion6(ReadOnlySpan data, int offset) : this(data[offset..]) { } + + private TrainerFashion6(ReadOnlySpan span) { - protected uint data0; - protected uint data1; - protected uint data2; - protected uint data3; - - protected TrainerFashion6(ReadOnlySpan data, int offset) : this(data[offset..]) { } - - private TrainerFashion6(ReadOnlySpan span) - { - data0 = ReadUInt32LittleEndian(span); - data1 = ReadUInt32LittleEndian(span[04..]); - data2 = ReadUInt32LittleEndian(span[08..]); - data3 = ReadUInt32LittleEndian(span[12..]); - } - - public static TrainerFashion6 GetFashion(byte[] data, int offset, int gender) - { - if (gender == 0) // m - return new Fashion6Male(data, offset); - return new Fashion6Female(data, offset); - } - - public void Write(byte[] data, int offset) - { - var span = data.AsSpan(offset); - WriteUInt32LittleEndian(span, data0); - WriteUInt32LittleEndian(span[04..], data1); - WriteUInt32LittleEndian(span[08..], data2); - WriteUInt32LittleEndian(span[12..], data3); - } - - protected static uint GetBits(uint value, int startPos, int bits) - { - uint mask = ((1u << bits) - 1) << startPos; - return (value & mask) >> startPos; - } - - protected static uint SetBits(uint value, int startPos, int bits, uint bitValue) - { - uint mask = ((1u << bits) - 1) << startPos; - bitValue &= mask >> startPos; - return (value & ~mask) | (bitValue << startPos); - } - - public enum F6HairColor - { - Black, - Brown, - Honey, - Orange, - Blond, - } - - public enum F6ContactLens - { - Brown, - Hazel, - None, - Green, - Blue, - } - - public enum F6Skin - { - White, - Light, - Tan, - Dark, - } + data0 = ReadUInt32LittleEndian(span); + data1 = ReadUInt32LittleEndian(span[04..]); + data2 = ReadUInt32LittleEndian(span[08..]); + data3 = ReadUInt32LittleEndian(span[12..]); } - public sealed class Fashion6Male : TrainerFashion6 + public static TrainerFashion6 GetFashion(byte[] data, int offset, int gender) { - public Fashion6Male(byte[] data, int offset) - : base(data, offset) { } - - public uint Version { get => GetBits(data0, 0, 3); set => data0 = SetBits(data0, 0, 3, value); } - public uint Model { get => GetBits(data0, 3, 3); set => data0 = SetBits(data0, 3, 3, value); } - public F6Skin Skin { get => (F6Skin)GetBits(data0, 6, 2); set => data0 = SetBits(data0, 6, 2, (uint)value); } - public F6HairColor HairColor { get => (F6HairColor)GetBits(data0, 8, 3); set => data0 = SetBits(data0, 8, 3, (uint)value); } - public F6Hat Hat { get => (F6Hat)GetBits(data0, 11, 5); set => data0 = SetBits(data0, 11, 5, (uint)value); } - public F6HairStyleFront Front { get => (F6HairStyleFront)GetBits(data0, 16, 3); set => data0 = SetBits(data0, 16, 3, (uint)value); } - public F6HairStyle Hair { get => (F6HairStyle)GetBits(data0, 19, 4); set => data0 = SetBits(data0, 19, 4, (uint)value); } - public uint Face { get => GetBits(data0, 23, 3); set => data0 = SetBits(data0, 23, 3, value); } - public uint Arms { get => GetBits(data0, 26, 2); set => data0 = SetBits(data0, 26, 2, value); } - public uint Unknown0 { get => GetBits(data0, 28, 2); set => data0 = SetBits(data0, 28, 2, value); } - public uint Unused0 { get => GetBits(data0, 30, 2); set => data0 = SetBits(data0, 30, 2, value); } - - public F6Top Top { get => (F6Top)GetBits(data1, 0, 6); set => data1 = SetBits(data1, 0, 6, (uint)value); } - public F6Bottoms Legs { get => (F6Bottoms)GetBits(data1, 6, 5); set => data1 = SetBits(data1, 6, 5, (uint)value); } - public F6Socks Socks { get => (F6Socks)GetBits(data1, 11, 3); set => data1 = SetBits(data1, 11, 3, (uint)value); } - public F6Shoes Shoes { get => (F6Shoes)GetBits(data1, 14, 5); set => data1 = SetBits(data1, 14, 5, (uint)value); } - public F6Bag Bag { get => (F6Bag)GetBits(data1, 19, 4); set => data1 = SetBits(data1, 19, 4, (uint)value); } - public F6Accessory AHat { get => (F6Accessory)GetBits(data1, 23, 4); set => data1 = SetBits(data1, 23, 4, (uint)value); } - public uint Unknown1 { get => GetBits(data1, 27, 2); set => data1 = SetBits(data1, 27, 2, value); } - public uint Unused1 { get => GetBits(data1, 29, 3); set => data1 = SetBits(data1, 29, 3, value); } - - public bool Contacts { get => GetBits(data2, 0, 1) == 1; set => data2 = SetBits(data2, 0, 1, value ? 1u : 0); } - public uint FacialHair { get => GetBits(data2, 1, 3); set => data2 = SetBits(data2, 1, 3, value); } - public F6ContactLens ColorContacts { get => (F6ContactLens)GetBits(data2, 4, 3); set => data2 = SetBits(data2, 4, 3, (uint)value); } - public uint FacialColor { get => GetBits(data2, 7, 3); set => data2 = SetBits(data2, 7, 3, value); } - public uint PaintLeft { get => GetBits(data2, 10, 4); set => data2 = SetBits(data2, 10, 4, value); } - public uint PaintRight { get => GetBits(data2, 14, 4); set => data2 = SetBits(data2, 14, 4, value); } - public uint PaintLeftC { get => GetBits(data2, 18, 3); set => data2 = SetBits(data2, 18, 3, value); } - public uint PaintRightC { get => GetBits(data2, 21, 3); set => data2 = SetBits(data2, 21, 3, value); } - public bool Freckles { get => GetBits(data2, 24, 2) == 1; set => data2 = SetBits(data2, 24, 2, value ? 1u : 0); } - public uint ColorFreckles { get => GetBits(data2, 26, 3); set => data2 = SetBits(data2, 26, 3, value); } - public uint Unused2 { get => GetBits(data2, 29, 3); set => data2 = SetBits(data2, 29, 3, value); } - - public enum F6Top - { - _0, - Zipped_Jacket_Blue, - Zipped_Jacket_Red, - Zipped_Jacket_Green, - Zipped_Jacket_Black, - Zipped_Jacket_Navy_Blue, - Zipped_Jacket_Orange, - Down_Jacket_Black, - Down_Jacket_Red, - Down_Jacket_Aqua, - Pajama_Top, - Striped_Shirt_Combo_Purple, - Striped_Shirt_Combo_Red, - Striped_Shirt_Combo_Aqua, - Striped_Shirt_Combo_Pale_Pink, - Plaid_Shirt_Combo_Red, - Plaid_Shirt_Combo_Gray, - Zipped_Shirt_Combo_Black, - Zipped_Shirt_Combo_White, - Hoodie_Olive, - Hoodie_Aqua, - Hoodie_Yellow, - V_Neck_T_Shirt_Black, - V_Neck_T_Shirt_White, - V_Neck_T_Shirt_Pink, - V_Neck_T_Shirt_Aqua, - Logo_T_Shirt_White, - Logo_T_Shirt_Orange, - Logo_T_Shirt_Green, - Logo_T_Shirt_Blue, - Logo_T_Shirt_Yellow, - Splatter_Paint_T_Shirt_Black, - Splatter_Paint_T_Shirt_Red, - Splatter_Paint_T_Shirt_Purple, - King_T_Shirt, - Twin_T_Shirt, - } - - public enum F6Socks - { - None, - Ankle_Socks_Black, - Ankle_Socks_Red, - Ankle_Socks_Green, - Ankle_Socks_Purple, - } - - public enum F6Shoes - { - _0, - Barefoot, - _2, - Short_Boots_Black, - Short_Boots_Red, - Short_Boots_Brown, - _6, - Loafers_Brown, - Loafers_Black, - Sneakers_Black, - Sneakers_White, - Sneakers_Red, - Sneakers_Blue, - Sneakers_Yellow, - } - - public enum F6Hat - { - None, - Logo_Cap_Glitched, // hacked - Logo_Cap_Black, - Logo_Cap_Blue, - Logo_Cap_Red, - Logo_Cap_Green, - Fedora_Glitched, // hacked - Fedora_Red, - Fedora_Gray, - Fedora_Black, - Checkered_Fedora_Black, - Outdoors_Cap_Glitched, // hacked - Outdoors_Cap_Red, - Outdoors_Cap_Black, - Outdoors_Cap_Olive, - Outdoors_Cap_Beige, - Knit_Cap_Glitched, // hacked - Knit_Cap_White, - Knit_Cap_Black, - Knit_Cap_Orange, - Knit_Cap_Purple, - Camo_Cap_Olive, - Camo_Cap_Aqua, - Bamboo_Sprig_Hat, - } - - public enum F6HairStyle - { - _0, - Medium, - Medium_Perm, - Short, - Very_Short, - } - - public enum F6Bottoms - { - _0, - Chinos_Black, - Chinos_Beige, - Checked_Pants_Red, - Checked_Pants_Gray, - Cuffed_Jeans, - Damaged_Jeans, - Vinyl_Pants, - Pajama_Pants, - Short_Cargo_Pants_Black, - Short_Cargo_Pants_Olive, - Short_Cargo_Pants_Purple, - Skinny_Jeans_Blue, - Skinny_Jeans_Brown, - Skinny_Jeans_Beige, - Skinny_Jeans_Red, - Camo_Pants_Green, - Camo_Pants_Gray, - } - - public enum F6Bag - { - None, - Two_Tone_Bag_Black, - Two_Tone_Bag_Red, - Two_Tone_Bag_Olive, - Two_Tone_Bag_Aqua, - Two_Tone_Bag_Orange, - Vinyl_Messenger_Bag_Black, - Vinyl_Messenger_Bag_Brown, - } - - public enum F6Accessory - { - None, - Button_Accessory_Gray, - Button_Accessory_Pink, - Button_Accessory_Purple, - Button_Accessory_Yellow, - Button_Accessory_Lime_Green, - Wide_Frame_Sunglasses_Black, - Wide_Frame_Sunglasses_Yellow, - Wide_Frame_Sunglasses_Red, - Wide_Frame_Sunglasses_White, - Feather_Accessory_Black, - Feather_Accessory_Red, - Feather_Accessory_Green, - } - - public enum F6HairStyleFront - { - _0, - Default, - } + if (gender == 0) // m + return new Fashion6Male(data, offset); + return new Fashion6Female(data, offset); } - public sealed class Fashion6Female : TrainerFashion6 + public void Write(byte[] data, int offset) { - public Fashion6Female(byte[] data, int offset) - : base(data, offset) { } + var span = data.AsSpan(offset); + WriteUInt32LittleEndian(span, data0); + WriteUInt32LittleEndian(span[04..], data1); + WriteUInt32LittleEndian(span[08..], data2); + WriteUInt32LittleEndian(span[12..], data3); + } - public uint Version { get => GetBits(data0, 0, 3); set => data0 = SetBits(data0, 0, 3, value); } - public uint Model { get => GetBits(data0, 3, 3); set => data0 = SetBits(data0, 3, 3, value); } - public F6Skin Skin { get => (F6Skin)GetBits(data0, 6, 2); set => data0 = SetBits(data0, 6, 2, (uint)value); } - public F6HairColor HairColor{ get => (F6HairColor)GetBits(data0, 8, 3); set => data0 = SetBits(data0, 8, 3, (uint)value); } - public F6Hat Hat { get => (F6Hat)GetBits(data0, 11, 6); set => data0 = SetBits(data0, 11, 6, (uint)value); } - public F6HairStyleFront Front { get => (F6HairStyleFront)GetBits(data0, 17, 3); set => data0 = SetBits(data0, 17, 3, (uint)value); } - public F6HairStyle Hair { get => (F6HairStyle)GetBits(data0, 20, 4); set => data0 = SetBits(data0, 20, 4, (uint)value); } - public uint Face { get => GetBits(data0, 24, 3); set => data0 = SetBits(data0, 24, 3, value); } - public uint Arms { get => GetBits(data0, 27, 2); set => data0 = SetBits(data0, 27, 2, value); } - public uint Unknown0 { get => GetBits(data0, 29, 2); set => data0 = SetBits(data0, 29, 2, value); } - public uint Unused0 { get => GetBits(data0, 31, 1); set => data0 = SetBits(data0, 31, 1, value); } + protected static uint GetBits(uint value, int startPos, int bits) + { + uint mask = ((1u << bits) - 1) << startPos; + return (value & mask) >> startPos; + } - public F6Top Top { get => (F6Top)GetBits(data1, 0, 6); set => data1 = SetBits(data1, 0, 6, (uint)value); } - public F6Bottom Legs { get => (F6Bottom)GetBits(data1, 6, 7); set => data1 = SetBits(data1, 6, 7, (uint)value); } - public F6Dress Dress { get => (F6Dress)GetBits(data1, 13, 4); set => data1 = SetBits(data1, 13, 4, (uint)value); } - public F6Socks Socks { get => (F6Socks)GetBits(data1, 17, 5); set => data1 = SetBits(data1, 17, 5, (uint)value); } - public F6Shoes Shoes { get => (F6Shoes)GetBits(data1, 22, 6); set => data1 = SetBits(data1, 22, 6, (uint)value); } - public uint Unknown1 { get => GetBits(data1, 28, 2); set => data1 = SetBits(data1, 28, 2, value); } - public uint Unused1 { get => GetBits(data1, 30, 2); set => data1 = SetBits(data1, 30, 2, value); } + protected static uint SetBits(uint value, int startPos, int bits, uint bitValue) + { + uint mask = ((1u << bits) - 1) << startPos; + bitValue &= mask >> startPos; + return (value & ~mask) | (bitValue << startPos); + } - public F6Bag Bag { get => (F6Bag)GetBits(data2, 0, 5); set => data2 = SetBits(data2, 0, 5, (uint)value); } - public F6Accessory AHat { get => (F6Accessory)GetBits(data2, 5, 5); set => data2 = SetBits(data2, 5, 5, (uint)value); } - public bool Contacts { get => GetBits(data2, 10, 1) == 1; set => data2 = SetBits(data2, 10, 1, value ? 1u : 0); } - public uint MascaraType { get => GetBits(data2, 11, 2); set => data2 = SetBits(data2, 11, 2, value); } - public bool Eyeliner { get => GetBits(data2, 13, 2) == 1; set => data2 = SetBits(data2, 13, 2, value ? 1u : 0); } - public bool Cheek { get => GetBits(data2, 15, 2) == 1; set => data2 = SetBits(data2, 15, 2, value ? 1u : 0); } - public bool Lips { get => GetBits(data2, 17, 2) == 1; set => data2 = SetBits(data2, 17, 2, value ? 1u : 0); } - public F6ContactLens ColorContacts { get => (F6ContactLens)GetBits(data2, 19, 3); set => data2 = SetBits(data2, 19, 3, (uint)value); } - public bool Mascara { get => GetBits(data2, 22, 3) == 1; set => data2 = SetBits(data2, 22, 3, value ? 1u : 0); } - public uint ColorEyeliner { get => GetBits(data2, 25, 3); set => data2 = SetBits(data2, 25, 3, value); } - public uint ColorCheek { get => GetBits(data2, 28, 3); set => data2 = SetBits(data2, 28, 3, value); } - public uint Unused2 { get => GetBits(data2, 31, 1); set => data2 = SetBits(data2, 31, 1, value); } + public enum F6HairColor + { + Black, + Brown, + Honey, + Orange, + Blond, + } - public uint ColorLips { get => GetBits(data3, 0, 2); set => data3 = SetBits(data3, 0, 2, value); } - public uint ColorFreckles { get => GetBits(data3, 2, 3); set => data3 = SetBits(data3, 2, 3, value); } - public bool Freckles { get => GetBits(data3, 5, 3) == 1; set => data3 = SetBits(data3, 5, 3, value ? 1u : 0); } - public uint Unused3 { get => GetBits(data3, 8, 24); set => data3 = SetBits(data3, 8, 24, value); } + public enum F6ContactLens + { + Brown, + Hazel, + None, + Green, + Blue, + } - public enum F6Top - { - None, - Ruffled_Camisole_Pale_Pink, - Ruffled_Camisole_Aqua, - Ruffled_Camisole_Yellow, - Ruffled_Tank_Top_Black, - Striped_Tank_Top_Black, - Striped_Tank_Top_Blue, - Striped_Tank_Top_Pink, - Midriff_Halter_Top_Aqua, - Midriff_Halter_Top_Orange, - Sleeveless_Turtleneck_Black, - Sleeveless_Turtleneck_White, - Pajama_Top, - Short_Parka_Red, - Short_Parka_Pink, - Short_Parka_Lime_Green, - Short_Parka_Blue, - Tie_Neck_Blouse_Red, - Tie_Neck_Blouse_Gray, - Ribbon_Smock_Top_Brown, - Ribbon_Smock_Top_Pale_Pink, - Ribbon_Smock_Top_Yellow, - Ribbon_Smock_Top_Purple, - Exotic_Top_Lime_Green, - Exotic_Top_Orange, - Scarf_Top_Yellow, - Scarf_Top_Pale_Pink, - Scarf_Top_Purple, - Glitzy_Scarf_Top, - Shirt_and_Tie_Gray, - Shirt_and_Tie_Green, - Shirt_and_Tie_Blue, - Poke_Ball_Baby_Doll_Tee_Aqua, - Poke_Ball_Baby_Doll_Tee_Purple, - Poke_Ball_Baby_Doll_Tee_Yellow, - Poke_Ball_Baby_Doll_Tee_Green, - } - - public enum F6Socks - { - _0, - Knee_Socks_Black, - Knee_Socks_White, - Knee_Socks_Red, - Knee_Socks_Blue, - Knee_Socks_Green, - Knee_Socks_Pink, - Knee_Socks_Yellow, - No_Socks, - OTK_Socks_Black, - OTK_Socks_White, - OTK_Socks_Green, - OTK_Socks_Gray, - OTK_Socks_Pink, - OTK_Socks_Red, - OTK_Socks_Brown, - Single_Stripe_OTK_Socks, - Wide_Stripe_OTK_Socks_Black, - Wide_Stripe_OTK_Socks_Pale_Pink, - Punk_OTK_Socks, - Camo_OTK_Socks, - Leggings, - Tights_Black, - Tights_Orange, - Tights_Pale_Pink, - Tights_Navy_Blue, - Tights_Pink, - Tights_Purple, - } - - public enum F6Shoes - { - None, - Riding_Boots_Pink, - Riding_Boots_Black, - Riding_Boots_White, - Riding_Boots_Gray, - Riding_Boots_Brown, - Riding_Boots_Beige, - Laced_Boots_Brown, - Laced_Boots_Black, - Zipped_Boots, - Barefoot, - _11, - High_Tops_Black, - High_Tops_Pink, - High_Tops_Yellow, - High_Tops_Purple, - Bow_Shoes_Black, - Bow_Shoes_Brown, - Saddle_Shoes_White, - Saddle_Shoes_Brown, - Saddle_Shoes_Navy_Blue, - Mary_Janes_Black, - Mary_Janes_Red, - Mary_Janes_Purple, - Mary_Janes_White, - Mary_Janes_Pale_Pink, - Mary_Janes_Aqua, - } - - public enum F6Hat - { - None, - Felt_Hat_Glitched, // hacked - Felt_Hat_Black, - Felt_Hat_White, - Felt_Hat_Gray, - Felt_Hat_Navy_Blue, - Felt_Hat_Brown, - Felt_Hat_Pink, - Felt_Hat_Pale_Pink, - Felt_Hat_Beige, - Felt_Hat_Aqua, - Boater_Red, - Boater_Blue, - Sports_Cap_Glitched, // hacked - Sports_Cap_Aqua, - Sports_Cap_Yellow, - Sports_Cap_Green, - Logo_Cap_Pink, - Logo_Cap_Black, - Cycling_Cap_Glitched, // hacked - Cycling_Cap_Blue, - Cycling_Cap_White, - Cycling_Cap_Beige, - Exotic_Cap_Brown, - Exotic_Cap_Purple, - Fedora_Glitched, // hacked - Fedora_White, - Fedora_Brown, - Fedora_Red, - Fedora_Yellow, - Fedora_Green, - Fedora_Purple, - } - - public enum F6HairStyle - { - _0, - Bob, - Long, - Medium, - Ponytail, - Short, - Pigtails, - } - - public enum F6HairStyleFront - { - _0, - Bangs, - Sideswept, - } - - public enum F6Dress - { - None, - Single_Front_Coat_Dress, - Double_Front_Coat_Dress, - Trench_Coat_Beige, - Trench_Coat_Black, - Sundae_Dress, - Frilly_Dress, - Sparkly_Bolero_Dress, - Little_Black_Dress, - High_Waisted_Outfit_Black, - High_Waisted_Outfit_White, - } - - public enum F6Bottom - { - No_Bottom, - Denim_Miniskirt_Blue, - Denim_Miniskirt_Olive, - Denim_Miniskirt_Black, - Scalloped_Skirt_Orange, - Scalloped_Skirt_Red, - Skinny_Jeans_Aqua, - Skinny_Jeans_White, - Skinny_Jeans_Olive, - Skinny_Jeans_Blue, - Skinny_Jeans_Beige, - Skinny_Jeans_Black, - Skinny_Jeans_Red, - Damaged_Skinny_Jeans, - Accented_Jeans_Blue, - Accented_Jeans_Yellow, - Accented_Jeans_Red, - Accented_Jeans_Lime_Green, - Bold_Striped_Pants_Gray, - Bold_Striped_Pants_Green, - Bold_Striped_Pants_Blue, - Pajama_Pants, - Pleated_Skirt_Black, - Pleated_Skirt_White, - Pleated_Skirt_Red, - Pleated_Skirt_Blue, - Striped_Pleated_Skirt_Pink, - Striped_Pleated_Skirt_Aqua, - Striped_Pleated_Skirt_Yellow, - Pleated_Kilt_Skirt_Gray, - Pleated_Kilt_Skirt_Red, - Scalloped_Tiered_Skirt_Pink, - Scalloped_Tiered_Skirt_Yellow, - Scalloped_Tiered_Skirt_White, - Tiered_Satin_Skirt_Purple, - Tiered_Satin_Skirt_White, - Jean_Shorts_Gray, - Jean_Shorts_Aqua, - Jean_Shorts_White, - Jean_Shorts_Brown, - Jean_Shorts_Black, - Jean_Shorts_Pink, - Jean_Shorts_Orange, - Damaged_Jean_Shorts, - Cross_Laced_Shorts_Olive, - Cross_Laced_Shorts_Brown, - } - - public enum F6Bag - { - None, - Tote_Bag_Pink, - Tote_Bag_Red, - Tote_Bag_Orange, - Tote_Bag_Yellow, - Tote_Bag_White, - Enamel_Striped_Purse_Red, - Enamel_Striped_Purse_Blue, - Ribbon_Purse_Pale_Pink, - Ribbon_Purse_Aqua, - Strappy_Purse_Black, - Strappy_Purse_Brown, - Strappy_Purse_Beige, - Strappy_Purse_Purple, - Strappy_Purse_White, - Tassel_Purse_Purple, - Tassel_Purse_Green, - } - - public enum F6Accessory - { - None, - Button_Accessory_Gray, - Button_Accessory_Pink, - Button_Accessory_Purple, - Button_Accessory_Yellow, - Button_Accessory_Lime_Green, - Artificial_Flower_Pin_Pink, - Artificial_Flower_Pin_Pale_Pink, - Artificial_Flower_Pin_Yellow, - Artificial_Flower_Pin_Aqua, - Wide_Frame_Sunglasses_White, - Wide_Frame_Sunglasses_Red, - Wide_Frame_Sunglasses_Blue, - Wide_Frame_Sunglasses_Yellow, - Metal_Pin_Gold, - Metal_Pin_Silver, - Metal_Pin_Black, - Hat_Ribbon_Accessory_Black, - Hat_Ribbon_Accessory_Red, - Hat_Ribbon_Accessory_White, - Hat_Ribbon_Accessory_Blue, - Hat_Ribbon_Accessory_Pale_Pink, - Feather_Accessory_Black, - Feather_Accessory_Red, - Feather_Accessory_Green, - } + public enum F6Skin + { + White, + Light, + Tan, + Dark, + } +} + +public sealed class Fashion6Male : TrainerFashion6 +{ + public Fashion6Male(byte[] data, int offset) + : base(data, offset) { } + + public uint Version { get => GetBits(data0, 0, 3); set => data0 = SetBits(data0, 0, 3, value); } + public uint Model { get => GetBits(data0, 3, 3); set => data0 = SetBits(data0, 3, 3, value); } + public F6Skin Skin { get => (F6Skin)GetBits(data0, 6, 2); set => data0 = SetBits(data0, 6, 2, (uint)value); } + public F6HairColor HairColor { get => (F6HairColor)GetBits(data0, 8, 3); set => data0 = SetBits(data0, 8, 3, (uint)value); } + public F6Hat Hat { get => (F6Hat)GetBits(data0, 11, 5); set => data0 = SetBits(data0, 11, 5, (uint)value); } + public F6HairStyleFront Front { get => (F6HairStyleFront)GetBits(data0, 16, 3); set => data0 = SetBits(data0, 16, 3, (uint)value); } + public F6HairStyle Hair { get => (F6HairStyle)GetBits(data0, 19, 4); set => data0 = SetBits(data0, 19, 4, (uint)value); } + public uint Face { get => GetBits(data0, 23, 3); set => data0 = SetBits(data0, 23, 3, value); } + public uint Arms { get => GetBits(data0, 26, 2); set => data0 = SetBits(data0, 26, 2, value); } + public uint Unknown0 { get => GetBits(data0, 28, 2); set => data0 = SetBits(data0, 28, 2, value); } + public uint Unused0 { get => GetBits(data0, 30, 2); set => data0 = SetBits(data0, 30, 2, value); } + + public F6Top Top { get => (F6Top)GetBits(data1, 0, 6); set => data1 = SetBits(data1, 0, 6, (uint)value); } + public F6Bottoms Legs { get => (F6Bottoms)GetBits(data1, 6, 5); set => data1 = SetBits(data1, 6, 5, (uint)value); } + public F6Socks Socks { get => (F6Socks)GetBits(data1, 11, 3); set => data1 = SetBits(data1, 11, 3, (uint)value); } + public F6Shoes Shoes { get => (F6Shoes)GetBits(data1, 14, 5); set => data1 = SetBits(data1, 14, 5, (uint)value); } + public F6Bag Bag { get => (F6Bag)GetBits(data1, 19, 4); set => data1 = SetBits(data1, 19, 4, (uint)value); } + public F6Accessory AHat { get => (F6Accessory)GetBits(data1, 23, 4); set => data1 = SetBits(data1, 23, 4, (uint)value); } + public uint Unknown1 { get => GetBits(data1, 27, 2); set => data1 = SetBits(data1, 27, 2, value); } + public uint Unused1 { get => GetBits(data1, 29, 3); set => data1 = SetBits(data1, 29, 3, value); } + + public bool Contacts { get => GetBits(data2, 0, 1) == 1; set => data2 = SetBits(data2, 0, 1, value ? 1u : 0); } + public uint FacialHair { get => GetBits(data2, 1, 3); set => data2 = SetBits(data2, 1, 3, value); } + public F6ContactLens ColorContacts { get => (F6ContactLens)GetBits(data2, 4, 3); set => data2 = SetBits(data2, 4, 3, (uint)value); } + public uint FacialColor { get => GetBits(data2, 7, 3); set => data2 = SetBits(data2, 7, 3, value); } + public uint PaintLeft { get => GetBits(data2, 10, 4); set => data2 = SetBits(data2, 10, 4, value); } + public uint PaintRight { get => GetBits(data2, 14, 4); set => data2 = SetBits(data2, 14, 4, value); } + public uint PaintLeftC { get => GetBits(data2, 18, 3); set => data2 = SetBits(data2, 18, 3, value); } + public uint PaintRightC { get => GetBits(data2, 21, 3); set => data2 = SetBits(data2, 21, 3, value); } + public bool Freckles { get => GetBits(data2, 24, 2) == 1; set => data2 = SetBits(data2, 24, 2, value ? 1u : 0); } + public uint ColorFreckles { get => GetBits(data2, 26, 3); set => data2 = SetBits(data2, 26, 3, value); } + public uint Unused2 { get => GetBits(data2, 29, 3); set => data2 = SetBits(data2, 29, 3, value); } + + public enum F6Top + { + _0, + Zipped_Jacket_Blue, + Zipped_Jacket_Red, + Zipped_Jacket_Green, + Zipped_Jacket_Black, + Zipped_Jacket_Navy_Blue, + Zipped_Jacket_Orange, + Down_Jacket_Black, + Down_Jacket_Red, + Down_Jacket_Aqua, + Pajama_Top, + Striped_Shirt_Combo_Purple, + Striped_Shirt_Combo_Red, + Striped_Shirt_Combo_Aqua, + Striped_Shirt_Combo_Pale_Pink, + Plaid_Shirt_Combo_Red, + Plaid_Shirt_Combo_Gray, + Zipped_Shirt_Combo_Black, + Zipped_Shirt_Combo_White, + Hoodie_Olive, + Hoodie_Aqua, + Hoodie_Yellow, + V_Neck_T_Shirt_Black, + V_Neck_T_Shirt_White, + V_Neck_T_Shirt_Pink, + V_Neck_T_Shirt_Aqua, + Logo_T_Shirt_White, + Logo_T_Shirt_Orange, + Logo_T_Shirt_Green, + Logo_T_Shirt_Blue, + Logo_T_Shirt_Yellow, + Splatter_Paint_T_Shirt_Black, + Splatter_Paint_T_Shirt_Red, + Splatter_Paint_T_Shirt_Purple, + King_T_Shirt, + Twin_T_Shirt, + } + + public enum F6Socks + { + None, + Ankle_Socks_Black, + Ankle_Socks_Red, + Ankle_Socks_Green, + Ankle_Socks_Purple, + } + + public enum F6Shoes + { + _0, + Barefoot, + _2, + Short_Boots_Black, + Short_Boots_Red, + Short_Boots_Brown, + _6, + Loafers_Brown, + Loafers_Black, + Sneakers_Black, + Sneakers_White, + Sneakers_Red, + Sneakers_Blue, + Sneakers_Yellow, + } + + public enum F6Hat + { + None, + Logo_Cap_Glitched, // hacked + Logo_Cap_Black, + Logo_Cap_Blue, + Logo_Cap_Red, + Logo_Cap_Green, + Fedora_Glitched, // hacked + Fedora_Red, + Fedora_Gray, + Fedora_Black, + Checkered_Fedora_Black, + Outdoors_Cap_Glitched, // hacked + Outdoors_Cap_Red, + Outdoors_Cap_Black, + Outdoors_Cap_Olive, + Outdoors_Cap_Beige, + Knit_Cap_Glitched, // hacked + Knit_Cap_White, + Knit_Cap_Black, + Knit_Cap_Orange, + Knit_Cap_Purple, + Camo_Cap_Olive, + Camo_Cap_Aqua, + Bamboo_Sprig_Hat, + } + + public enum F6HairStyle + { + _0, + Medium, + Medium_Perm, + Short, + Very_Short, + } + + public enum F6Bottoms + { + _0, + Chinos_Black, + Chinos_Beige, + Checked_Pants_Red, + Checked_Pants_Gray, + Cuffed_Jeans, + Damaged_Jeans, + Vinyl_Pants, + Pajama_Pants, + Short_Cargo_Pants_Black, + Short_Cargo_Pants_Olive, + Short_Cargo_Pants_Purple, + Skinny_Jeans_Blue, + Skinny_Jeans_Brown, + Skinny_Jeans_Beige, + Skinny_Jeans_Red, + Camo_Pants_Green, + Camo_Pants_Gray, + } + + public enum F6Bag + { + None, + Two_Tone_Bag_Black, + Two_Tone_Bag_Red, + Two_Tone_Bag_Olive, + Two_Tone_Bag_Aqua, + Two_Tone_Bag_Orange, + Vinyl_Messenger_Bag_Black, + Vinyl_Messenger_Bag_Brown, + } + + public enum F6Accessory + { + None, + Button_Accessory_Gray, + Button_Accessory_Pink, + Button_Accessory_Purple, + Button_Accessory_Yellow, + Button_Accessory_Lime_Green, + Wide_Frame_Sunglasses_Black, + Wide_Frame_Sunglasses_Yellow, + Wide_Frame_Sunglasses_Red, + Wide_Frame_Sunglasses_White, + Feather_Accessory_Black, + Feather_Accessory_Red, + Feather_Accessory_Green, + } + + public enum F6HairStyleFront + { + _0, + Default, + } +} + +public sealed class Fashion6Female : TrainerFashion6 +{ + public Fashion6Female(byte[] data, int offset) + : base(data, offset) { } + + public uint Version { get => GetBits(data0, 0, 3); set => data0 = SetBits(data0, 0, 3, value); } + public uint Model { get => GetBits(data0, 3, 3); set => data0 = SetBits(data0, 3, 3, value); } + public F6Skin Skin { get => (F6Skin)GetBits(data0, 6, 2); set => data0 = SetBits(data0, 6, 2, (uint)value); } + public F6HairColor HairColor{ get => (F6HairColor)GetBits(data0, 8, 3); set => data0 = SetBits(data0, 8, 3, (uint)value); } + public F6Hat Hat { get => (F6Hat)GetBits(data0, 11, 6); set => data0 = SetBits(data0, 11, 6, (uint)value); } + public F6HairStyleFront Front { get => (F6HairStyleFront)GetBits(data0, 17, 3); set => data0 = SetBits(data0, 17, 3, (uint)value); } + public F6HairStyle Hair { get => (F6HairStyle)GetBits(data0, 20, 4); set => data0 = SetBits(data0, 20, 4, (uint)value); } + public uint Face { get => GetBits(data0, 24, 3); set => data0 = SetBits(data0, 24, 3, value); } + public uint Arms { get => GetBits(data0, 27, 2); set => data0 = SetBits(data0, 27, 2, value); } + public uint Unknown0 { get => GetBits(data0, 29, 2); set => data0 = SetBits(data0, 29, 2, value); } + public uint Unused0 { get => GetBits(data0, 31, 1); set => data0 = SetBits(data0, 31, 1, value); } + + public F6Top Top { get => (F6Top)GetBits(data1, 0, 6); set => data1 = SetBits(data1, 0, 6, (uint)value); } + public F6Bottom Legs { get => (F6Bottom)GetBits(data1, 6, 7); set => data1 = SetBits(data1, 6, 7, (uint)value); } + public F6Dress Dress { get => (F6Dress)GetBits(data1, 13, 4); set => data1 = SetBits(data1, 13, 4, (uint)value); } + public F6Socks Socks { get => (F6Socks)GetBits(data1, 17, 5); set => data1 = SetBits(data1, 17, 5, (uint)value); } + public F6Shoes Shoes { get => (F6Shoes)GetBits(data1, 22, 6); set => data1 = SetBits(data1, 22, 6, (uint)value); } + public uint Unknown1 { get => GetBits(data1, 28, 2); set => data1 = SetBits(data1, 28, 2, value); } + public uint Unused1 { get => GetBits(data1, 30, 2); set => data1 = SetBits(data1, 30, 2, value); } + + public F6Bag Bag { get => (F6Bag)GetBits(data2, 0, 5); set => data2 = SetBits(data2, 0, 5, (uint)value); } + public F6Accessory AHat { get => (F6Accessory)GetBits(data2, 5, 5); set => data2 = SetBits(data2, 5, 5, (uint)value); } + public bool Contacts { get => GetBits(data2, 10, 1) == 1; set => data2 = SetBits(data2, 10, 1, value ? 1u : 0); } + public uint MascaraType { get => GetBits(data2, 11, 2); set => data2 = SetBits(data2, 11, 2, value); } + public bool Eyeliner { get => GetBits(data2, 13, 2) == 1; set => data2 = SetBits(data2, 13, 2, value ? 1u : 0); } + public bool Cheek { get => GetBits(data2, 15, 2) == 1; set => data2 = SetBits(data2, 15, 2, value ? 1u : 0); } + public bool Lips { get => GetBits(data2, 17, 2) == 1; set => data2 = SetBits(data2, 17, 2, value ? 1u : 0); } + public F6ContactLens ColorContacts { get => (F6ContactLens)GetBits(data2, 19, 3); set => data2 = SetBits(data2, 19, 3, (uint)value); } + public bool Mascara { get => GetBits(data2, 22, 3) == 1; set => data2 = SetBits(data2, 22, 3, value ? 1u : 0); } + public uint ColorEyeliner { get => GetBits(data2, 25, 3); set => data2 = SetBits(data2, 25, 3, value); } + public uint ColorCheek { get => GetBits(data2, 28, 3); set => data2 = SetBits(data2, 28, 3, value); } + public uint Unused2 { get => GetBits(data2, 31, 1); set => data2 = SetBits(data2, 31, 1, value); } + + public uint ColorLips { get => GetBits(data3, 0, 2); set => data3 = SetBits(data3, 0, 2, value); } + public uint ColorFreckles { get => GetBits(data3, 2, 3); set => data3 = SetBits(data3, 2, 3, value); } + public bool Freckles { get => GetBits(data3, 5, 3) == 1; set => data3 = SetBits(data3, 5, 3, value ? 1u : 0); } + public uint Unused3 { get => GetBits(data3, 8, 24); set => data3 = SetBits(data3, 8, 24, value); } + + public enum F6Top + { + None, + Ruffled_Camisole_Pale_Pink, + Ruffled_Camisole_Aqua, + Ruffled_Camisole_Yellow, + Ruffled_Tank_Top_Black, + Striped_Tank_Top_Black, + Striped_Tank_Top_Blue, + Striped_Tank_Top_Pink, + Midriff_Halter_Top_Aqua, + Midriff_Halter_Top_Orange, + Sleeveless_Turtleneck_Black, + Sleeveless_Turtleneck_White, + Pajama_Top, + Short_Parka_Red, + Short_Parka_Pink, + Short_Parka_Lime_Green, + Short_Parka_Blue, + Tie_Neck_Blouse_Red, + Tie_Neck_Blouse_Gray, + Ribbon_Smock_Top_Brown, + Ribbon_Smock_Top_Pale_Pink, + Ribbon_Smock_Top_Yellow, + Ribbon_Smock_Top_Purple, + Exotic_Top_Lime_Green, + Exotic_Top_Orange, + Scarf_Top_Yellow, + Scarf_Top_Pale_Pink, + Scarf_Top_Purple, + Glitzy_Scarf_Top, + Shirt_and_Tie_Gray, + Shirt_and_Tie_Green, + Shirt_and_Tie_Blue, + Poke_Ball_Baby_Doll_Tee_Aqua, + Poke_Ball_Baby_Doll_Tee_Purple, + Poke_Ball_Baby_Doll_Tee_Yellow, + Poke_Ball_Baby_Doll_Tee_Green, + } + + public enum F6Socks + { + None, + Knee_Socks_Black, + Knee_Socks_White, + Knee_Socks_Red, + Knee_Socks_Blue, + Knee_Socks_Green, + Knee_Socks_Pink, + Knee_Socks_Yellow, + No_Socks, + OTK_Socks_Black, + OTK_Socks_White, + OTK_Socks_Green, + OTK_Socks_Gray, + OTK_Socks_Pink, + OTK_Socks_Red, + OTK_Socks_Brown, + Single_Stripe_OTK_Socks, + Wide_Stripe_OTK_Socks_Black, + Wide_Stripe_OTK_Socks_Pale_Pink, + Punk_OTK_Socks, + Camo_OTK_Socks, + Leggings, + Tights_Black, + Tights_Orange, + Tights_Pale_Pink, + Tights_Navy_Blue, + Tights_Pink, + Tights_Purple, + } + + public enum F6Shoes + { + None, + Riding_Boots_Pink, + Riding_Boots_Black, + Riding_Boots_White, + Riding_Boots_Gray, + Riding_Boots_Brown, + Riding_Boots_Beige, + Laced_Boots_Brown, + Laced_Boots_Black, + Zipped_Boots, + Barefoot, + _11, + High_Tops_Black, + High_Tops_Pink, + High_Tops_Yellow, + High_Tops_Purple, + Bow_Shoes_Black, + Bow_Shoes_Brown, + Saddle_Shoes_White, + Saddle_Shoes_Brown, + Saddle_Shoes_Navy_Blue, + Mary_Janes_Black, + Mary_Janes_Red, + Mary_Janes_Purple, + Mary_Janes_White, + Mary_Janes_Pale_Pink, + Mary_Janes_Aqua, + } + + public enum F6Hat + { + None, + Felt_Hat_Glitched, // hacked + Felt_Hat_Black, + Felt_Hat_White, + Felt_Hat_Gray, + Felt_Hat_Navy_Blue, + Felt_Hat_Brown, + Felt_Hat_Pink, + Felt_Hat_Pale_Pink, + Felt_Hat_Beige, + Felt_Hat_Aqua, + Boater_Red, + Boater_Blue, + Sports_Cap_Glitched, // hacked + Sports_Cap_Aqua, + Sports_Cap_Yellow, + Sports_Cap_Green, + Logo_Cap_Pink, + Logo_Cap_Black, + Cycling_Cap_Glitched, // hacked + Cycling_Cap_Blue, + Cycling_Cap_White, + Cycling_Cap_Beige, + Exotic_Cap_Brown, + Exotic_Cap_Purple, + Fedora_Glitched, // hacked + Fedora_White, + Fedora_Brown, + Fedora_Red, + Fedora_Yellow, + Fedora_Green, + Fedora_Purple, + } + + public enum F6HairStyle + { + None, + Bob, + Long, + Medium, + Ponytail, + Short, + Pigtails, + } + + public enum F6HairStyleFront + { + None, + Bangs, + Sideswept, + } + + public enum F6Dress + { + None, + Single_Front_Coat_Dress, + Double_Front_Coat_Dress, + Trench_Coat_Beige, + Trench_Coat_Black, + Sundae_Dress, + Frilly_Dress, + Sparkly_Bolero_Dress, + Little_Black_Dress, + High_Waisted_Outfit_Black, + High_Waisted_Outfit_White, + } + + public enum F6Bottom + { + None, + Denim_Miniskirt_Blue, + Denim_Miniskirt_Olive, + Denim_Miniskirt_Black, + Scalloped_Skirt_Orange, + Scalloped_Skirt_Red, + Skinny_Jeans_Aqua, + Skinny_Jeans_White, + Skinny_Jeans_Olive, + Skinny_Jeans_Blue, + Skinny_Jeans_Beige, + Skinny_Jeans_Black, + Skinny_Jeans_Red, + Damaged_Skinny_Jeans, + Accented_Jeans_Blue, + Accented_Jeans_Yellow, + Accented_Jeans_Red, + Accented_Jeans_Lime_Green, + Bold_Striped_Pants_Gray, + Bold_Striped_Pants_Green, + Bold_Striped_Pants_Blue, + Pajama_Pants, + Pleated_Skirt_Black, + Pleated_Skirt_White, + Pleated_Skirt_Red, + Pleated_Skirt_Blue, + Striped_Pleated_Skirt_Pink, + Striped_Pleated_Skirt_Aqua, + Striped_Pleated_Skirt_Yellow, + Pleated_Kilt_Skirt_Gray, + Pleated_Kilt_Skirt_Red, + Scalloped_Tiered_Skirt_Pink, + Scalloped_Tiered_Skirt_Yellow, + Scalloped_Tiered_Skirt_White, + Tiered_Satin_Skirt_Purple, + Tiered_Satin_Skirt_White, + Jean_Shorts_Gray, + Jean_Shorts_Aqua, + Jean_Shorts_White, + Jean_Shorts_Brown, + Jean_Shorts_Black, + Jean_Shorts_Pink, + Jean_Shorts_Orange, + Damaged_Jean_Shorts, + Cross_Laced_Shorts_Olive, + Cross_Laced_Shorts_Brown, + } + + public enum F6Bag + { + None, + Tote_Bag_Pink, + Tote_Bag_Red, + Tote_Bag_Orange, + Tote_Bag_Yellow, + Tote_Bag_White, + Enamel_Striped_Purse_Red, + Enamel_Striped_Purse_Blue, + Ribbon_Purse_Pale_Pink, + Ribbon_Purse_Aqua, + Strappy_Purse_Black, + Strappy_Purse_Brown, + Strappy_Purse_Beige, + Strappy_Purse_Purple, + Strappy_Purse_White, + Tassel_Purse_Purple, + Tassel_Purse_Green, + } + + public enum F6Accessory + { + None, + Button_Accessory_Gray, + Button_Accessory_Pink, + Button_Accessory_Purple, + Button_Accessory_Yellow, + Button_Accessory_Lime_Green, + Artificial_Flower_Pin_Pink, + Artificial_Flower_Pin_Pale_Pink, + Artificial_Flower_Pin_Yellow, + Artificial_Flower_Pin_Aqua, + Wide_Frame_Sunglasses_White, + Wide_Frame_Sunglasses_Red, + Wide_Frame_Sunglasses_Blue, + Wide_Frame_Sunglasses_Yellow, + Metal_Pin_Gold, + Metal_Pin_Silver, + Metal_Pin_Black, + Hat_Ribbon_Accessory_Black, + Hat_Ribbon_Accessory_Red, + Hat_Ribbon_Accessory_White, + Hat_Ribbon_Accessory_Blue, + Hat_Ribbon_Accessory_Pale_Pink, + Feather_Accessory_Black, + Feather_Accessory_Red, + Feather_Accessory_Green, } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/TrainerSprite6.cs b/PKHeX.Core/Saves/Substructures/Gen6/TrainerSprite6.cs index d7f1a417d..a28cc6bb2 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/TrainerSprite6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/TrainerSprite6.cs @@ -1,83 +1,82 @@ -namespace PKHeX.Core -{ - public enum TrainerSprite6 - { - Serena = 00, - Calem = 01, - Sycamore = 02, - Diantha = 03, - Wikstrom = 04, - Malva = 05, - Drasna = 06, - Siebold = 07, - Viola = 08, - Grant = 09, - Korrina = 10, - Ramos = 11, - Clemont = 12, - Valerie = 13, - Olympia = 14, - Wulfric = 15, - Youngster_XY = 16, - // Unused 17 - Lass_XY = 18, - Lady_XY = 19, - Schoolgirl_XY = 20, - Battle_Girl_XY = 21, - Schoolboy_XY = 22, - Rich_Boy_XY = 23, - Female_Ace_Trainer_XY = 24, - // Unused 25 - Female_Ranger_XY = 26, - Male_Ace_Trainer_XY = 27, - Male_Ranger_XY = 28, - Madame = 29, - Monsieur = 30, - Black_Belt_XY = 31, - Male_Punk_XY = 32, - Fairy_Tale_Girl_XY = 33, - Shauna = 34, - Tierno = 35, - Trevor = 36, - Brendan = 37, - May = 38, - // Unused 39 - Hiker = 40, - Aroma_Lady = 41, - Male_Schoolkid = 42, - Female_Schoolkid = 43, - Black_Belt_ORAS = 44, - Battle_Girl_ORAS = 45, - Pokemaniac_ORAS = 46, - Fairy_Tale_Girl_ORAS = 47, - Victor_Winstrate = 48, - Victoria_Winstrate = 49, - Male_Ranger_ORAS = 50, - Female_Ranger_ORAS = 51, - Male_Swimmer_ORAS = 52, - Hex_Maniac = 53, - Male_Ace_Trainer_ORAS = 54, - Female_Ace_Trainer_ORAS = 55, - Street_Thug = 56, - Delinquent = 57, - Male_Expert = 58, - Female_Expert = 59, - Lady_ORAS = 60, - Rich_Boy_ORAS = 61, - Ninja_Boy = 62, - Beauty_ORAS = 63, - Guitarist = 64, - Lass_ORAS = 65, - Male_Breeder_ORAS = 66, - Female_Breeder_ORAS = 67, - Camper = 68, - Picnicker = 69, - Wally = 70, - Steven = 71, - Maxie = 72, - Archie = 73, +namespace PKHeX.Core; - Pokemon_Center = 0x80, - Gift = 0x81, - } +public enum TrainerSprite6 +{ + Serena = 00, + Calem = 01, + Sycamore = 02, + Diantha = 03, + Wikstrom = 04, + Malva = 05, + Drasna = 06, + Siebold = 07, + Viola = 08, + Grant = 09, + Korrina = 10, + Ramos = 11, + Clemont = 12, + Valerie = 13, + Olympia = 14, + Wulfric = 15, + Youngster_XY = 16, + // Unused 17 + Lass_XY = 18, + Lady_XY = 19, + Schoolgirl_XY = 20, + Battle_Girl_XY = 21, + Schoolboy_XY = 22, + Rich_Boy_XY = 23, + Female_Ace_Trainer_XY = 24, + // Unused 25 + Female_Ranger_XY = 26, + Male_Ace_Trainer_XY = 27, + Male_Ranger_XY = 28, + Madame = 29, + Monsieur = 30, + Black_Belt_XY = 31, + Male_Punk_XY = 32, + Fairy_Tale_Girl_XY = 33, + Shauna = 34, + Tierno = 35, + Trevor = 36, + Brendan = 37, + May = 38, + // Unused 39 + Hiker = 40, + Aroma_Lady = 41, + Male_Schoolkid = 42, + Female_Schoolkid = 43, + Black_Belt_ORAS = 44, + Battle_Girl_ORAS = 45, + Pokemaniac_ORAS = 46, + Fairy_Tale_Girl_ORAS = 47, + Victor_Winstrate = 48, + Victoria_Winstrate = 49, + Male_Ranger_ORAS = 50, + Female_Ranger_ORAS = 51, + Male_Swimmer_ORAS = 52, + Hex_Maniac = 53, + Male_Ace_Trainer_ORAS = 54, + Female_Ace_Trainer_ORAS = 55, + Street_Thug = 56, + Delinquent = 57, + Male_Expert = 58, + Female_Expert = 59, + Lady_ORAS = 60, + Rich_Boy_ORAS = 61, + Ninja_Boy = 62, + Beauty_ORAS = 63, + Guitarist = 64, + Lass_ORAS = 65, + Male_Breeder_ORAS = 66, + Female_Breeder_ORAS = 67, + Camper = 68, + Picnicker = 69, + Wally = 70, + Steven = 71, + Maxie = 72, + Archie = 73, + + Pokemon_Center = 0x80, + Gift = 0x81, } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/BattleAgency7.cs b/PKHeX.Core/Saves/Substructures/Gen7/BattleAgency7.cs index aa4ebcfee..4a987b126 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/BattleAgency7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/BattleAgency7.cs @@ -1,17 +1,16 @@ using System; -namespace PKHeX.Core -{ - public sealed class BattleAgency7 : SaveBlock - { - public BattleAgency7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; +namespace PKHeX.Core; - public int GetSlotOffset(int slot) => Offset + slot switch - { - 0 => 0, - 1 => PokeCrypto.SIZE_6STORED, - 2 => 0x220, - _ => throw new ArgumentOutOfRangeException(nameof(slot)), - }; - } +public sealed class BattleAgency7 : SaveBlock +{ + public BattleAgency7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + + public int GetSlotOffset(int slot) => Offset + slot switch + { + 0 => 0, + 1 => PokeCrypto.SIZE_6STORED, + 2 => 0x220, + _ => throw new ArgumentOutOfRangeException(nameof(slot)), + }; } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/BattleTree7.cs b/PKHeX.Core/Saves/Substructures/Gen7/BattleTree7.cs index 90efb9f6e..7a6d252ec 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/BattleTree7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/BattleTree7.cs @@ -2,128 +2,127 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class BattleTree7 : SaveBlock { - public sealed class BattleTree7 : SaveBlock + public BattleTree7(SAV7SM sav, int offset) : base(sav) => Offset = offset; + public BattleTree7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + + public int GetTreeStreak(int battletype, bool super, bool max) { - public BattleTree7(SAV7SM sav, int offset) : base(sav) => Offset = offset; - public BattleTree7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + if (battletype > 3) + throw new ArgumentOutOfRangeException(nameof(battletype)); - public int GetTreeStreak(int battletype, bool super, bool max) - { - if (battletype > 3) - throw new ArgumentOutOfRangeException(nameof(battletype)); - - var offset = GetStreakOffset(battletype, super, max); - return ReadUInt16LittleEndian(Data.AsSpan(Offset + offset)); - } - - public void SetTreeStreak(int value, int battletype, bool super, bool max) - { - if (battletype > 3) - throw new ArgumentOutOfRangeException(nameof(battletype)); - - if (value > ushort.MaxValue) - value = ushort.MaxValue; - - var offset = GetStreakOffset(battletype, super, max); - WriteUInt16LittleEndian(Data.AsSpan(Offset + offset), (ushort)value); - } - - private static int GetStreakOffset(int battletype, bool super, bool max) - { - int offset = 8 * battletype; - if (super) - offset += 2; - if (max) - offset += 4; - return offset; - } - - private const int ScoutCount = 50; - - public BattleTreeTrainer GetTrainer(in int index) - { - if ((uint)index >= ScoutCount) - throw new ArgumentOutOfRangeException(nameof(index)); - - var id = ReadInt16LittleEndian(Data.AsSpan(Offset + 0x24 + (index * 2))); - var p1 = ReadInt16LittleEndian(Data.AsSpan(Offset + 0x88 + (index * 2))); - var p2 = ReadInt16LittleEndian(Data.AsSpan(Offset + 0xEC + (index * 2))); - - var a1 = (sbyte)Data[Offset + 0x154 + index]; - var a2 = (sbyte)Data[Offset + 0x186 + index]; - - var poke1 = new BattleTreePokemon(p1, a1); - var poke2 = new BattleTreePokemon(p2, a2); - return new BattleTreeTrainer(id, poke1, poke2); - } - - public void SetTrainer(BattleTreeTrainer tr, in int index) - { - if ((uint)index >= ScoutCount) - throw new ArgumentOutOfRangeException(nameof(index)); - - WriteInt16LittleEndian(Data.AsSpan(Offset + 0x24 + (index * 2)), tr.ID ); - WriteInt16LittleEndian(Data.AsSpan(Offset + 0x88 + (index * 2)), tr.Poke1.ID); - WriteInt16LittleEndian(Data.AsSpan(Offset + 0xEC + (index * 2)), tr.Poke2.ID); - - Data[Offset + 0x154 + index] = (byte)tr.Poke1.AbilityIndex; - Data[Offset + 0x186 + index] = (byte)tr.Poke2.AbilityIndex; - } - - public int Music - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x18)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x18), value); - } - - public BattleTreeTrainer[] ScoutedTrainers - { - get - { - var result = new BattleTreeTrainer[ScoutCount]; - for (int i = 0; i < result.Length; i++) - result[i] = GetTrainer(i); - return result; - } - set - { - for (int i = 0; i < value.Length; i++) - SetTrainer(value[i], i); - } - } + var offset = GetStreakOffset(battletype, super, max); + return ReadUInt16LittleEndian(Data.AsSpan(Offset + offset)); } - [TypeConverter(typeof(ValueTypeTypeConverter))] - public sealed class BattleTreeTrainer + public void SetTreeStreak(int value, int battletype, bool super, bool max) { - public short ID { get; set; } - public BattleTreePokemon Poke1 { get; set; } - public BattleTreePokemon Poke2 { get; set; } + if (battletype > 3) + throw new ArgumentOutOfRangeException(nameof(battletype)); - public BattleTreeTrainer(short id, BattleTreePokemon poke1, BattleTreePokemon poke2) - { - ID = id; - Poke1 = poke1; - Poke2 = poke2; - } + if (value > ushort.MaxValue) + value = ushort.MaxValue; - public override string ToString() => $"{ID}: [{Poke1}] & [{Poke2}]"; + var offset = GetStreakOffset(battletype, super, max); + WriteUInt16LittleEndian(Data.AsSpan(Offset + offset), (ushort)value); } - [TypeConverter(typeof(ValueTypeTypeConverter))] - public sealed class BattleTreePokemon + private static int GetStreakOffset(int battletype, bool super, bool max) { - public short ID { get; set; } - public sbyte AbilityIndex { get; set; } + int offset = 8 * battletype; + if (super) + offset += 2; + if (max) + offset += 4; + return offset; + } - public BattleTreePokemon(short p1, sbyte a1) + private const int ScoutCount = 50; + + public BattleTreeTrainer GetTrainer(in int index) + { + if ((uint)index >= ScoutCount) + throw new ArgumentOutOfRangeException(nameof(index)); + + var id = ReadInt16LittleEndian(Data.AsSpan(Offset + 0x24 + (index * 2))); + var p1 = ReadInt16LittleEndian(Data.AsSpan(Offset + 0x88 + (index * 2))); + var p2 = ReadInt16LittleEndian(Data.AsSpan(Offset + 0xEC + (index * 2))); + + var a1 = (sbyte)Data[Offset + 0x154 + index]; + var a2 = (sbyte)Data[Offset + 0x186 + index]; + + var poke1 = new BattleTreePokemon(p1, a1); + var poke2 = new BattleTreePokemon(p2, a2); + return new BattleTreeTrainer(id, poke1, poke2); + } + + public void SetTrainer(BattleTreeTrainer tr, in int index) + { + if ((uint)index >= ScoutCount) + throw new ArgumentOutOfRangeException(nameof(index)); + + WriteInt16LittleEndian(Data.AsSpan(Offset + 0x24 + (index * 2)), tr.ID ); + WriteInt16LittleEndian(Data.AsSpan(Offset + 0x88 + (index * 2)), tr.Poke1.ID); + WriteInt16LittleEndian(Data.AsSpan(Offset + 0xEC + (index * 2)), tr.Poke2.ID); + + Data[Offset + 0x154 + index] = (byte)tr.Poke1.AbilityIndex; + Data[Offset + 0x186 + index] = (byte)tr.Poke2.AbilityIndex; + } + + public int Music + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x18)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x18), value); + } + + public BattleTreeTrainer[] ScoutedTrainers + { + get { - ID = p1; - AbilityIndex = a1; + var result = new BattleTreeTrainer[ScoutCount]; + for (int i = 0; i < result.Length; i++) + result[i] = GetTrainer(i); + return result; + } + set + { + for (int i = 0; i < value.Length; i++) + SetTrainer(value[i], i); } - - public override string ToString() => $"{ID},{AbilityIndex}"; } } + +[TypeConverter(typeof(ValueTypeTypeConverter))] +public sealed class BattleTreeTrainer +{ + public short ID { get; set; } + public BattleTreePokemon Poke1 { get; set; } + public BattleTreePokemon Poke2 { get; set; } + + public BattleTreeTrainer(short id, BattleTreePokemon poke1, BattleTreePokemon poke2) + { + ID = id; + Poke1 = poke1; + Poke2 = poke2; + } + + public override string ToString() => $"{ID}: [{Poke1}] & [{Poke2}]"; +} + +[TypeConverter(typeof(ValueTypeTypeConverter))] +public sealed class BattleTreePokemon +{ + public short ID { get; set; } + public sbyte AbilityIndex { get; set; } + + public BattleTreePokemon(short p1, sbyte a1) + { + ID = p1; + AbilityIndex = a1; + } + + public override string ToString() => $"{ID},{AbilityIndex}"; +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/BoxLayout7.cs b/PKHeX.Core/Saves/Substructures/Gen7/BoxLayout7.cs index c40412adc..19eafe351 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/BoxLayout7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/BoxLayout7.cs @@ -1,129 +1,128 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class BoxLayout7 : SaveBlock, IBoxDetailName, IBoxDetailWallpaper, ITeamIndexSet { - public sealed class BoxLayout7 : SaveBlock, IBoxDetailName, IBoxDetailWallpaper, ITeamIndexSet + private const int BoxCount = 32; + + private const int BattleBoxFlags = 0x4C4; + private const int PCBackgrounds = 0x5C0; + private const int PCFlags = 0x5E0; + private const int Unlocked = 0x5E1; + private const int LastViewedBoxOffset = 0x5E3; + + private const int StringMaxLength = SAV6.LongStringLength / 2; + + private const int TeamCount = 6; + private const int NONE_SELECTED = -1; + public readonly int[] TeamSlots = new int[TeamCount * 6]; + + public BoxLayout7(SAV7SM sav, int offset) : base(sav) => Offset = offset; + public BoxLayout7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + + public int GetBoxWallpaperOffset(int box) => Offset + PCBackgrounds + box; + + public int GetBoxWallpaper(int box) { - private const int BoxCount = 32; + if ((uint)box > SAV.BoxCount) + return 0; + return Data[GetBoxWallpaperOffset(box)]; + } - private const int BattleBoxFlags = 0x4C4; - private const int PCBackgrounds = 0x5C0; - private const int PCFlags = 0x5E0; - private const int Unlocked = 0x5E1; - private const int LastViewedBoxOffset = 0x5E3; + public void SetBoxWallpaper(int box, int value) + { + if ((uint)box > SAV.BoxCount) + return; + Data[GetBoxWallpaperOffset(box)] = (byte)value; + } - private const int StringMaxLength = SAV6.LongStringLength / 2; + private Span GetBoxNameSpan(int box) => Data.AsSpan(GetBoxNameOffset(box), SAV6.LongStringLength); + private int GetBoxNameOffset(int box) => Offset + (SAV6.LongStringLength * box); + public string GetBoxName(int box) => SAV.GetString(GetBoxNameSpan(box)); + public void SetBoxName(int box, string value) => SAV.SetString(GetBoxNameSpan(box), value.AsSpan(), StringMaxLength, StringConverterOption.ClearZero); - private const int TeamCount = 6; - private const int NONE_SELECTED = -1; - public readonly int[] TeamSlots = new int[TeamCount * 6]; - - public BoxLayout7(SAV7SM sav, int offset) : base(sav) => Offset = offset; - public BoxLayout7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; - - public int GetBoxWallpaperOffset(int box) => Offset + PCBackgrounds + box; - - public int GetBoxWallpaper(int box) + public byte[] BoxFlags + { + get => new[] { Data[Offset + PCFlags] }; // bits for wallpaper unlocks + set { - if ((uint)box > SAV.BoxCount) - return 0; - return Data[GetBoxWallpaperOffset(box)]; - } - - public void SetBoxWallpaper(int box, int value) - { - if ((uint)box > SAV.BoxCount) + if (value.Length != 1) return; - Data[GetBoxWallpaperOffset(box)] = (byte)value; - } - - private Span GetBoxNameSpan(int box) => Data.AsSpan(GetBoxNameOffset(box), SAV6.LongStringLength); - private int GetBoxNameOffset(int box) => Offset + (SAV6.LongStringLength * box); - public string GetBoxName(int box) => SAV.GetString(GetBoxNameSpan(box)); - public void SetBoxName(int box, string value) => SAV.SetString(GetBoxNameSpan(box), value.AsSpan(), StringMaxLength, StringConverterOption.ClearZero); - - public byte[] BoxFlags - { - get => new[] { Data[Offset + PCFlags] }; // bits for wallpaper unlocks - set - { - if (value.Length != 1) - return; - Data[Offset + PCFlags] = value[0]; - } - } - - public int BoxesUnlocked - { - get => Data[Offset + Unlocked]; - set - { - if (value > BoxCount) - value = BoxCount; - Data[Offset + Unlocked] = (byte)value; - } - } - - public int CurrentBox { get => Data[Offset + LastViewedBoxOffset]; set => Data[Offset + LastViewedBoxOffset] = (byte)value; } - - public void LoadBattleTeams() - { - for (int i = 0; i < TeamCount * 6; i++) - { - short val = ReadInt16LittleEndian(Data.AsSpan(Offset + BattleBoxFlags + (i * 2))); - if (val < 0) - { - TeamSlots[i] = NONE_SELECTED; - continue; - } - - int box = val >> 8; - int slot = val & 0xFF; - int index = (SAV.BoxSlotCount * box) + slot; - TeamSlots[i] = index & 0xFFFF; - } - } - - public void ClearBattleTeams() - { - for (int i = 0; i < TeamSlots.Length; i++) - TeamSlots[i] = NONE_SELECTED; - for (int i = 0; i < TeamCount; i++) - SetIsTeamLocked(i, false); - } - - public void SaveBattleTeams() - { - var span = Data.AsSpan(Offset + BattleBoxFlags); - for (int i = 0; i < TeamCount * 6; i++) - { - int index = TeamSlots[i]; - if (index < 0) - { - WriteInt16LittleEndian(span[(i*2)..], (short)index); - continue; - } - - SAV.GetBoxSlotFromIndex(index, out var box, out var slot); - index = (box << 8) | slot; - WriteInt16LittleEndian(span[(i * 2)..], (short)index); - } - } - - public void UnlockAllTeams() - { - for (int i = 0; i < TeamCount; i++) - SetIsTeamLocked(i, false); - } - - public bool GetIsTeamLocked(int team) => Data[Offset + PCBackgrounds - TeamCount - team] == 1; - public void SetIsTeamLocked(int team, bool value) => Data[Offset + PCBackgrounds - TeamCount - team] = value ? (byte)1 : (byte)0; - - public string this[int i] - { - get => GetBoxName(i); - set => SetBoxName(i, value); + Data[Offset + PCFlags] = value[0]; } } -} \ No newline at end of file + + public int BoxesUnlocked + { + get => Data[Offset + Unlocked]; + set + { + if (value > BoxCount) + value = BoxCount; + Data[Offset + Unlocked] = (byte)value; + } + } + + public int CurrentBox { get => Data[Offset + LastViewedBoxOffset]; set => Data[Offset + LastViewedBoxOffset] = (byte)value; } + + public void LoadBattleTeams() + { + for (int i = 0; i < TeamCount * 6; i++) + { + short val = ReadInt16LittleEndian(Data.AsSpan(Offset + BattleBoxFlags + (i * 2))); + if (val < 0) + { + TeamSlots[i] = NONE_SELECTED; + continue; + } + + int box = val >> 8; + int slot = val & 0xFF; + int index = (SAV.BoxSlotCount * box) + slot; + TeamSlots[i] = index & 0xFFFF; + } + } + + public void ClearBattleTeams() + { + for (int i = 0; i < TeamSlots.Length; i++) + TeamSlots[i] = NONE_SELECTED; + for (int i = 0; i < TeamCount; i++) + SetIsTeamLocked(i, false); + } + + public void SaveBattleTeams() + { + var span = Data.AsSpan(Offset + BattleBoxFlags); + for (int i = 0; i < TeamCount * 6; i++) + { + int index = TeamSlots[i]; + if (index < 0) + { + WriteInt16LittleEndian(span[(i*2)..], (short)index); + continue; + } + + SAV.GetBoxSlotFromIndex(index, out var box, out var slot); + index = (box << 8) | slot; + WriteInt16LittleEndian(span[(i * 2)..], (short)index); + } + } + + public void UnlockAllTeams() + { + for (int i = 0; i < TeamCount; i++) + SetIsTeamLocked(i, false); + } + + public bool GetIsTeamLocked(int team) => Data[Offset + PCBackgrounds - TeamCount - team] == 1; + public void SetIsTeamLocked(int team, bool value) => Data[Offset + PCBackgrounds - TeamCount - team] = value ? (byte)1 : (byte)0; + + public string this[int i] + { + get => GetBoxName(i); + set => SetBoxName(i, value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/ConfigSave7.cs b/PKHeX.Core/Saves/Substructures/Gen7/ConfigSave7.cs index 0e1c1e9a9..fa3d265d7 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/ConfigSave7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/ConfigSave7.cs @@ -1,88 +1,87 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class ConfigSave7 : SaveBlock { - public sealed class ConfigSave7 : SaveBlock + /* ===32 bits=== + * talkSpeed:2 0,1 + * battleAnim:1 2 + * battleStyle:1 3 + * unknown:9 4..12 + * buttonMode:2 13,14 + * boxStatus:1 15 + * everything else: unknown + */ + + public ConfigSave7(SAV7SM sav, int offset) : base(sav) => Offset = offset; + public ConfigSave7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + + public int ConfigValue { - /* ===32 bits=== - * talkSpeed:2 0,1 - * battleAnim:1 2 - * battleStyle:1 3 - * unknown:9 4..12 - * buttonMode:2 13,14 - * boxStatus:1 15 - * everything else: unknown - */ + get => ReadInt32LittleEndian(Data.AsSpan(Offset)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset), value); + } - public ConfigSave7(SAV7SM sav, int offset) : base(sav) => Offset = offset; - public ConfigSave7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + public int TalkingSpeed + { + get => ConfigValue & 3; + set => ConfigValue = (ConfigValue & ~3) | (value & 3); + } - public int ConfigValue - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset), value); - } + public BattleAnimationSetting BattleAnimation + { + // Effects OFF = 1, Effects ON = 0 + get => (BattleAnimationSetting)((ConfigValue >> 2) & 1); + set => ConfigValue = (ConfigValue & ~(1 << 2)) | ((byte)value << 2); + } - public int TalkingSpeed - { - get => ConfigValue & 3; - set => ConfigValue = (ConfigValue & ~3) | (value & 3); - } + public BattleStyleSetting BattleStyle + { + // SET = 1, SWITCH = 0 + get => (BattleStyleSetting)((ConfigValue >> 3) & 1); + set => ConfigValue = (ConfigValue & ~(1 << 3)) | ((byte)value << 3); + } - public BattleAnimationSetting BattleAnimation - { - // Effects OFF = 1, Effects ON = 0 - get => (BattleAnimationSetting)((ConfigValue >> 2) & 1); - set => ConfigValue = (ConfigValue & ~(1 << 2)) | ((byte)value << 2); - } + // UNKNOWN? - public BattleStyleSetting BattleStyle - { - // SET = 1, SWITCH = 0 - get => (BattleStyleSetting)((ConfigValue >> 3) & 1); - set => ConfigValue = (ConfigValue & ~(1 << 3)) | ((byte)value << 3); - } + public int ButtonMode + { + get => (ConfigValue >> 13) & 3; + set => ConfigValue = (ConfigValue & ~(1 << 13)) | (value << 13); + } - // UNKNOWN? + public int BoxStatus + { + // MANUAL = 1, AUTOMATIC = 0 + get => (ConfigValue >> 15) & 1; + set => ConfigValue = (ConfigValue & ~(1 << 15)) | (value << 15); + } - public int ButtonMode - { - get => (ConfigValue >> 13) & 3; - set => ConfigValue = (ConfigValue & ~(1 << 13)) | (value << 13); - } + // NOTE: BELOW COMES FROM LGPE. MAYBE THIS IS WHAT THEY USE THE FLAGS FOR? - public int BoxStatus - { - // MANUAL = 1, AUTOMATIC = 0 - get => (ConfigValue >> 15) & 1; - set => ConfigValue = (ConfigValue & ~(1 << 15)) | (value << 15); - } + /// + /// for messages, stored with skipped in the enumeration. + /// + public int Language + { + get => GetLanguageID((ConfigValue >> 4) & 0xF); + set => ConfigValue = ((ConfigValue & ~0xF0) | (SetLanguageID(value) << 4)); + } - // NOTE: BELOW COMES FROM LGPE. MAYBE THIS IS WHAT THEY USE THE FLAGS FOR? + private static int GetLanguageID(int rawValue) => rawValue >= (int)LanguageID.UNUSED_6 ? rawValue + 1 : rawValue; // sets langBank to LanguageID + private static int SetLanguageID(int rawValue) => rawValue > (int)LanguageID.UNUSED_6 ? rawValue - 1 : rawValue; // sets LanguageID to langBank - /// - /// for messages, stored with skipped in the enumeration. - /// - public int Language - { - get => GetLanguageID((ConfigValue >> 4) & 0xF); - set => ConfigValue = ((ConfigValue & ~0xF0) | SetLanguageID(value) << 4); - } + public enum BattleAnimationSetting + { + EffectsON, + EffectsOFF, + } - private static int GetLanguageID(int rawValue) => rawValue >= (int)LanguageID.UNUSED_6 ? rawValue + 1 : rawValue; // sets langBank to LanguageID - private static int SetLanguageID(int rawValue) => rawValue > (int)LanguageID.UNUSED_6 ? rawValue - 1 : rawValue; // sets LanguageID to langBank - - public enum BattleAnimationSetting - { - EffectsON, - EffectsOFF, - } - - public enum BattleStyleSetting - { - SWITCH, - SET, - } + public enum BattleStyleSetting + { + SWITCH, + SET, } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/Daycare7.cs b/PKHeX.Core/Saves/Substructures/Gen7/Daycare7.cs index bf5961a7d..cdc9fd4d7 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/Daycare7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/Daycare7.cs @@ -1,44 +1,43 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Daycare7 : SaveBlock { - public sealed class Daycare7 : SaveBlock + public const int DaycareSeedSize = 32; // 128 bits + + public Daycare7(SAV7SM sav, int offset) : base(sav) => Offset = offset; + public Daycare7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + + public bool GetIsOccupied(int slot) { - public const int DaycareSeedSize = 32; // 128 bits + return Data[Offset + ((PokeCrypto.SIZE_6STORED + 1) * slot)] != 0; + } - public Daycare7(SAV7SM sav, int offset) : base(sav) => Offset = offset; - public Daycare7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + public void SetOccupied(int slot, bool occupied) + { + Data[Offset + ((PokeCrypto.SIZE_6STORED + 1) * slot)] = occupied ? (byte)1 : (byte)0; + } - public bool GetIsOccupied(int slot) + public int GetDaycareSlotOffset(int slot) + { + return Offset + 1 + (slot * (PokeCrypto.SIZE_6STORED + 1)); + } + + public bool HasEgg + { + get => Data[Offset + 0x1D8] == 1; + set => Data[Offset + 0x1D8] = value ? (byte)1 : (byte)0; + } + + public string RNGSeed + { + get => Util.GetHexStringFromBytes(Data, Offset + 0x1DC, DaycareSeedSize / 2); + set { - return Data[Offset + ((PokeCrypto.SIZE_6STORED + 1) * slot)] != 0; - } + if (value.Length != DaycareSeedSize) + return; - public void SetOccupied(int slot, bool occupied) - { - Data[Offset + ((PokeCrypto.SIZE_6STORED + 1) * slot)] = occupied ? (byte)1 : (byte)0; - } - - public int GetDaycareSlotOffset(int slot) - { - return Offset + 1 + (slot * (PokeCrypto.SIZE_6STORED + 1)); - } - - public bool HasEgg - { - get => Data[Offset + 0x1D8] == 1; - set => Data[Offset + 0x1D8] = value ? (byte)1 : (byte)0; - } - - public string RNGSeed - { - get => Util.GetHexStringFromBytes(Data, Offset + 0x1DC, DaycareSeedSize / 2); - set - { - if (value.Length != DaycareSeedSize) - return; - - var data = Util.GetBytesFromHexString(value); - SAV.SetData(data, Offset + 0x1DC); - } + var data = Util.GetBytesFromHexString(value); + SAV.SetData(data, Offset + 0x1DC); } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/EventVarType.cs b/PKHeX.Core/Saves/Substructures/Gen7/EventVarType.cs index 3d03f3748..655f5cddd 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/EventVarType.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/EventVarType.cs @@ -1,34 +1,33 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum EventVarType { - public enum EventVarType - { - /// - /// Toggles data in the map. - /// - Zone, + /// + /// Toggles data in the map. + /// + Zone, - /// - /// Toggles certain game features on and off. - /// - System, + /// + /// Toggles certain game features on and off. + /// + System, - /// - /// Hides overworld entities if flag is set. - /// - /// Flag only - Vanish, + /// + /// Hides overworld entities if flag is set. + /// + /// Flag only + Vanish, - /// - /// Tweaks overworld entities depending on the work value. - /// - /// Work Value only - /// - /// - Scene = Vanish, + /// + /// Tweaks overworld entities depending on the work value. + /// + /// Work Value only + /// + /// + Scene = Vanish, - /// - /// Tracks game progress. - /// - Event, - } -} \ No newline at end of file + /// + /// Tracks game progress. + /// + Event, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/FashionBlock7.cs b/PKHeX.Core/Saves/Substructures/Gen7/FashionBlock7.cs index 482d35ad7..91efcdae4 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/FashionBlock7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/FashionBlock7.cs @@ -1,62 +1,61 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class FashionBlock7 : SaveBlock { - public sealed class FashionBlock7 : SaveBlock + public FashionBlock7(SAV7SM sav, int offset) : base(sav) => Offset = offset; + public FashionBlock7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + + private const int FashionLength = 0x1A08; + + public FashionItem7[] Wardrobe { - public FashionBlock7(SAV7SM sav, int offset) : base(sav) => Offset = offset; - public FashionBlock7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; - - private const int FashionLength = 0x1A08; - - public FashionItem7[] Wardrobe + get { - get - { - var data = SAV.GetData(Offset, 0x5A8); - return Array.ConvertAll(data, b => new FashionItem7(b)); - } - set - { - if (value.Length != 0x5A8) - throw new ArgumentOutOfRangeException($"Unexpected size: 0x{value.Length:X}"); - var arr = Array.ConvertAll(value, z => z.Value); - SAV.SetData(arr, Offset); - } + var data = SAV.GetData(Offset, 0x5A8); + return Array.ConvertAll(data, b => new FashionItem7(b)); } - - public void Clear() => Array.Clear(Data, Offset, FashionLength); - - /// - /// Resets the fashion unlocks to default values. - /// - public void Reset() + set { - var offsetList = SAV is SAV7USUM - ? (SAV.Gender == 0 - ? new[] { 0x03A, 0x109, 0x1DA, 0x305, 0x3D9, 0x4B1, 0x584 } // M - : new[] { 0x05E, 0x208, 0x264, 0x395, 0x3B4, 0x4F9, 0x5A8 }) // F - : (SAV.Gender == 0 - ? new[] { 0x000, 0x0FB, 0x124, 0x28F, 0x3B4, 0x452, 0x517 } // M - : new[] { 0x000, 0x100, 0x223, 0x288, 0x3B4, 0x452, 0x517 }); // F - - foreach (var ofs in offsetList) - SAV.Data[Offset + ofs] = 3; // owned | new + if (value.Length != 0x5A8) + throw new ArgumentOutOfRangeException($"Unexpected size: 0x{value.Length:X}"); + var arr = Array.ConvertAll(value, z => z.Value); + SAV.SetData(arr, Offset); } } - // Every fashion item is 2 bits, New Flag (high) & Owned Flag (low) - public sealed class FashionItem7 + public void Clear() => Array.Clear(Data, Offset, FashionLength); + + /// + /// Resets the fashion unlocks to default values. + /// + public void Reset() { - public bool IsOwned { get; set; } - public bool IsNew { get; set; } + var offsetList = SAV is SAV7USUM + ? (SAV.Gender == 0 + ? new[] { 0x03A, 0x109, 0x1DA, 0x305, 0x3D9, 0x4B1, 0x584 } // M + : new[] { 0x05E, 0x208, 0x264, 0x395, 0x3B4, 0x4F9, 0x5A8 }) // F + : (SAV.Gender == 0 + ? new[] { 0x000, 0x0FB, 0x124, 0x28F, 0x3B4, 0x452, 0x517 } // M + : new[] { 0x000, 0x100, 0x223, 0x288, 0x3B4, 0x452, 0x517 }); // F - public FashionItem7(byte b) - { - IsOwned = (b & 1) != 0; - IsNew = (b & 2) != 0; - } - - public byte Value => (byte)((IsOwned ? 1 : 0) | (IsNew ? 2 : 0)); + foreach (var ofs in offsetList) + SAV.Data[Offset + ofs] = 3; // owned | new } -} \ No newline at end of file +} + +// Every fashion item is 2 bits, New Flag (high) & Owned Flag (low) +public sealed class FashionItem7 +{ + public bool IsOwned { get; set; } + public bool IsNew { get; set; } + + public FashionItem7(byte b) + { + IsOwned = (b & 1) != 0; + IsNew = (b & 2) != 0; + } + + public byte Value => (byte)((IsOwned ? 1 : 0) | (IsNew ? 2 : 0)); +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/FestaFacility.cs b/PKHeX.Core/Saves/Substructures/Gen7/FestaFacility.cs index 7a42e83a2..e2dd869ee 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/FestaFacility.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/FestaFacility.cs @@ -1,60 +1,59 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class FestaFacility { - public sealed class FestaFacility + private const int SIZE = 0x48; + private readonly byte[] Data; + private readonly int Language; + private readonly int ofs; + + public FestaFacility(SAV7 sav, int index) { - private const int SIZE = 0x48; - private readonly byte[] Data; - private readonly int Language; - private readonly int ofs; - - public FestaFacility(SAV7 sav, int index) - { - ofs = (index * SIZE) + sav.Festa.Offset + 0x310; - Data = sav.GetData(ofs, SIZE); - Language = sav.Language; - } - - public void CopyTo(SAV7 sav) => sav.SetData(Data, ofs); - - public int Type { get => Data[0x00]; set => Data[0x00] = (byte)value; } - public int Color { get => Data[0x01]; set => Data[0x01] = (byte)value; } - public bool IsIntroduced { get => Data[0x02] != 0; set => Data[0x02] = value ? (byte)1 : (byte)0; } - public int Gender { get => Data[0x03]; set => Data[0x03] = (byte)value; } - public string OT_Name { get => StringConverter7.GetString(Data.AsSpan(0x04, 0x1A)); set => StringConverter7.SetString(Data.AsSpan(0x04, 0x1A), value.AsSpan(), 12, Language, StringConverterOption.ClearZero); } - - private int MessageMeet { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } - private int MessagePart { get => ReadUInt16LittleEndian(Data.AsSpan(0x20)); set => WriteUInt16LittleEndian(Data.AsSpan(0x20), (ushort)value); } - private int MessageMoved { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); } - private int MessageDisappointed { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); } - public int UsedLuckyRank { get => Data[0x26]; set => Data[0x26] = (byte)value; } // 1:a bit, 2:a whole lot, 3:a whole ton - public int UsedLuckyPlace { get => Data[0x27]; set => Data[0x27] = (byte)value; } // 1:GTS, ... 7:haunted house - public uint UsedFlags { get => ReadUInt32LittleEndian(Data.AsSpan(0x28)); set => WriteUInt32LittleEndian(Data.AsSpan(0x28), value); } - public uint UsedRandStat { get => ReadUInt32LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x2C), value); } - - public int NPC { get => Math.Max(0, ReadInt32LittleEndian(Data.AsSpan(0x30))); set => WriteInt32LittleEndian(Data.AsSpan(0x30), Math.Max(0, value)); } - public byte[] TrainerFesID { get => Data.Slice(0x34, 0xC); set => value.CopyTo(Data, 0x34); } - public void ClearTrainerFesID() => Data.AsSpan(0x34, 0xC).Clear(); - public int ExchangeLeftCount { get => Data[0x40]; set => Data[0x40] = (byte)value; } // used when Type=Exchange - - public int GetMessage(int index) => index switch - { - 0 => MessageMeet, - 1 => MessagePart, - 2 => MessageMoved, - 3 => MessageDisappointed, - _ => 0, - }; - - public int SetMessage(int index, ushort value) => index switch - { - 0 => MessageMeet = value, - 1 => MessagePart = value, - 2 => MessageMoved = value, - 3 => MessageDisappointed = value, - _ => -1, - }; + ofs = (index * SIZE) + sav.Festa.Offset + 0x310; + Data = sav.GetData(ofs, SIZE); + Language = sav.Language; } + + public void CopyTo(SAV7 sav) => sav.SetData(Data, ofs); + + public int Type { get => Data[0x00]; set => Data[0x00] = (byte)value; } + public int Color { get => Data[0x01]; set => Data[0x01] = (byte)value; } + public bool IsIntroduced { get => Data[0x02] != 0; set => Data[0x02] = value ? (byte)1 : (byte)0; } + public int Gender { get => Data[0x03]; set => Data[0x03] = (byte)value; } + public string OT_Name { get => StringConverter7.GetString(Data.AsSpan(0x04, 0x1A)); set => StringConverter7.SetString(Data.AsSpan(0x04, 0x1A), value.AsSpan(), 12, Language, StringConverterOption.ClearZero); } + + private int MessageMeet { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)value); } + private int MessagePart { get => ReadUInt16LittleEndian(Data.AsSpan(0x20)); set => WriteUInt16LittleEndian(Data.AsSpan(0x20), (ushort)value); } + private int MessageMoved { get => ReadUInt16LittleEndian(Data.AsSpan(0x22)); set => WriteUInt16LittleEndian(Data.AsSpan(0x22), (ushort)value); } + private int MessageDisappointed { get => ReadUInt16LittleEndian(Data.AsSpan(0x24)); set => WriteUInt16LittleEndian(Data.AsSpan(0x24), (ushort)value); } + public int UsedLuckyRank { get => Data[0x26]; set => Data[0x26] = (byte)value; } // 1:a bit, 2:a whole lot, 3:a whole ton + public int UsedLuckyPlace { get => Data[0x27]; set => Data[0x27] = (byte)value; } // 1:GTS, ... 7:haunted house + public uint UsedFlags { get => ReadUInt32LittleEndian(Data.AsSpan(0x28)); set => WriteUInt32LittleEndian(Data.AsSpan(0x28), value); } + public uint UsedRandStat { get => ReadUInt32LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x2C), value); } + + public int NPC { get => Math.Max(0, ReadInt32LittleEndian(Data.AsSpan(0x30))); set => WriteInt32LittleEndian(Data.AsSpan(0x30), Math.Max(0, value)); } + public byte[] TrainerFesID { get => Data.Slice(0x34, 0xC); set => value.CopyTo(Data, 0x34); } + public void ClearTrainerFesID() => Data.AsSpan(0x34, 0xC).Clear(); + public int ExchangeLeftCount { get => Data[0x40]; set => Data[0x40] = (byte)value; } // used when Type=Exchange + + public int GetMessage(int index) => index switch + { + 0 => MessageMeet, + 1 => MessagePart, + 2 => MessageMoved, + 3 => MessageDisappointed, + _ => 0, + }; + + public int SetMessage(int index, ushort value) => index switch + { + 0 => MessageMeet = value, + 1 => MessagePart = value, + 2 => MessageMoved = value, + 3 => MessageDisappointed = value, + _ => -1, + }; } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/FieldMenu7.cs b/PKHeX.Core/Saves/Substructures/Gen7/FieldMenu7.cs index d63046238..c94977fbd 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/FieldMenu7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/FieldMenu7.cs @@ -1,27 +1,26 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class FieldMenu7 : SaveBlock { - public sealed class FieldMenu7 : SaveBlock + public FieldMenu7(SAV7SM sav, int offset) : base(sav) => Offset = offset; + public FieldMenu7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + + // USUM ONLY + public ushort RotomAffection { - public FieldMenu7(SAV7SM sav, int offset) : base(sav) => Offset = offset; - public FieldMenu7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; - - // USUM ONLY - public ushort RotomAffection - { - get => ReadUInt16LittleEndian(SAV.Data.AsSpan(Offset + 0x1A)); - set => WriteUInt16LittleEndian(SAV.Data.AsSpan(Offset + 0x1A), Math.Min((ushort)1000, value)); - } - - public bool RotomLoto1 { get => (SAV.Data[Offset + 0x2A] & 1) == 1; set => SAV.Data[Offset + 0x2A] = (byte)((SAV.Data[Offset + 0x2A] & ~1) | (value ? 1 : 0)); } - public bool RotomLoto2 { get => (SAV.Data[Offset + 0x2A] & 2) == 2; set => SAV.Data[Offset + 0x2A] = (byte)((SAV.Data[Offset + 0x2A] & ~2) | (value ? 2 : 0)); } - - public string RotomOT - { - get => SAV.GetString(Offset + 0x30, 0x1A); - set => SAV.SetString(Data.AsSpan(Offset + 0x30, 0x1A), value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); - } + get => ReadUInt16LittleEndian(SAV.Data.AsSpan(Offset + 0x1A)); + set => WriteUInt16LittleEndian(SAV.Data.AsSpan(Offset + 0x1A), Math.Min((ushort)1000, value)); } -} \ No newline at end of file + + public bool RotomLoto1 { get => (SAV.Data[Offset + 0x2A] & 1) == 1; set => SAV.Data[Offset + 0x2A] = (byte)((SAV.Data[Offset + 0x2A] & ~1) | (value ? 1 : 0)); } + public bool RotomLoto2 { get => (SAV.Data[Offset + 0x2A] & 2) == 2; set => SAV.Data[Offset + 0x2A] = (byte)((SAV.Data[Offset + 0x2A] & ~2) | (value ? 2 : 0)); } + + public string RotomOT + { + get => SAV.GetString(Offset + 0x30, 0x1A); + set => SAV.SetString(Data.AsSpan(Offset + 0x30, 0x1A), value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/FieldMoveModelSave7.cs b/PKHeX.Core/Saves/Substructures/Gen7/FieldMoveModelSave7.cs index 87c01fd5f..ee5f5af1e 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/FieldMoveModelSave7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/FieldMoveModelSave7.cs @@ -1,17 +1,16 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core -{ - public sealed class FieldMoveModelSave7 : SaveBlock - { - public FieldMoveModelSave7(SAV7SM sav, int offset) : base(sav) => Offset = offset; - public FieldMoveModelSave7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; +namespace PKHeX.Core; - public int M { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), (ushort)value); } - public float X { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x08), value); } - public float Z { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x10), value); } - public float Y { get => (int)ReadSingleLittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x18), value); } - public float R { get => (int)ReadSingleLittleEndian(Data.AsSpan(Offset + 0x20)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x20), value); } - } -} \ No newline at end of file +public sealed class FieldMoveModelSave7 : SaveBlock +{ + public FieldMoveModelSave7(SAV7SM sav, int offset) : base(sav) => Offset = offset; + public FieldMoveModelSave7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + + public int M { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), (ushort)value); } + public float X { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x08), value); } + public float Z { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x10), value); } + public float Y { get => (int)ReadSingleLittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x18), value); } + public float R { get => (int)ReadSingleLittleEndian(Data.AsSpan(Offset + 0x20)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x20), value); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/GameTime7.cs b/PKHeX.Core/Saves/Substructures/Gen7/GameTime7.cs index 6dae3b78c..6f1a8ad0b 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/GameTime7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/GameTime7.cs @@ -1,20 +1,19 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core -{ - public sealed class GameTime7 : SaveBlock - { - public GameTime7(SAV7 sav, int offset) : base(sav) => Offset = offset; - public int ResumeYear { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } - public int ResumeMonth { get => Data[Offset + 0x8]; set => Data[Offset + 0x8] = (byte)value; } - public int ResumeDay { get => Data[Offset + 0x9]; set => Data[Offset + 0x9] = (byte)value; } - public int ResumeHour { get => Data[Offset + 0xB]; set => Data[Offset + 0xB] = (byte)value; } - public int ResumeMinute { get => Data[Offset + 0xC]; set => Data[Offset + 0xC] = (byte)value; } - public int ResumeSeconds { get => Data[Offset + 0xD]; set => Data[Offset + 0xD] = (byte)value; } - public uint SecondsToStart { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x28)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x28), value); } - public uint SecondsToFame { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x30)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x30), value); } +namespace PKHeX.Core; - public ulong AlolaTime { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x48)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x48), value); } - } -} \ No newline at end of file +public sealed class GameTime7 : SaveBlock +{ + public GameTime7(SAV7 sav, int offset) : base(sav) => Offset = offset; + public int ResumeYear { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } + public int ResumeMonth { get => Data[Offset + 0x8]; set => Data[Offset + 0x8] = (byte)value; } + public int ResumeDay { get => Data[Offset + 0x9]; set => Data[Offset + 0x9] = (byte)value; } + public int ResumeHour { get => Data[Offset + 0xB]; set => Data[Offset + 0xB] = (byte)value; } + public int ResumeMinute { get => Data[Offset + 0xC]; set => Data[Offset + 0xC] = (byte)value; } + public int ResumeSeconds { get => Data[Offset + 0xD]; set => Data[Offset + 0xD] = (byte)value; } + public uint SecondsToStart { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x28)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x28), value); } + public uint SecondsToFame { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x30)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x30), value); } + + public ulong AlolaTime { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x48)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x48), value); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/HallOfFame7.cs b/PKHeX.Core/Saves/Substructures/Gen7/HallOfFame7.cs index 9a8da9eaa..287291d98 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/HallOfFame7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/HallOfFame7.cs @@ -1,41 +1,40 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class HallOfFame7 : SaveBlock { - public sealed class HallOfFame7 : SaveBlock + public HallOfFame7(SAV7SM sav, int offset) : base(sav) => Offset = offset; + public HallOfFame7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + + // this HoF region is immediately after the Event Flags + private const int MaxCount = 12; + public int First1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), (ushort)value); } + public int First2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x02), (ushort)value); } + public int First3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x04), (ushort)value); } + public int First4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x04), (ushort)value); } + public int First5 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x06), (ushort)value); } + public int First6 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x08), (ushort)value); } + + public int Current1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0A), (ushort)value); } + public int Current2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0C), (ushort)value); } + public int Current3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0E), (ushort)value); } + public int Current4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x10), (ushort)value); } + public int Current5 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x12)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x12), (ushort)value); } + public int Current6 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14), (ushort)value); } + + public int GetEntry(int index) { - public HallOfFame7(SAV7SM sav, int offset) : base(sav) => Offset = offset; - public HallOfFame7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + if ((uint)index >= MaxCount) + throw new ArgumentOutOfRangeException(nameof(index)); + return ReadUInt16LittleEndian(SAV.Data.AsSpan(Offset + (index * 2))); + } - // this HoF region is immediately after the Event Flags - private const int MaxCount = 12; - public int First1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), (ushort)value); } - public int First2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x02)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x02), (ushort)value); } - public int First3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x04), (ushort)value); } - public int First4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x04), (ushort)value); } - public int First5 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x06), (ushort)value); } - public int First6 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x08), (ushort)value); } - - public int Current1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0A), (ushort)value); } - public int Current2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0C), (ushort)value); } - public int Current3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x0E), (ushort)value); } - public int Current4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x10), (ushort)value); } - public int Current5 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x12)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x12), (ushort)value); } - public int Current6 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14), (ushort)value); } - - public int GetEntry(int index) - { - if ((uint)index >= MaxCount) - throw new ArgumentOutOfRangeException(nameof(index)); - return ReadUInt16LittleEndian(SAV.Data.AsSpan(Offset + (index * 2))); - } - - public void SetEntry(int index, ushort value) - { - if ((uint)index >= MaxCount) - throw new ArgumentOutOfRangeException(nameof(index)); - WriteUInt16LittleEndian(SAV.Data.AsSpan(Offset + (index * 2)), value); - } + public void SetEntry(int index, ushort value) + { + if ((uint)index >= MaxCount) + throw new ArgumentOutOfRangeException(nameof(index)); + WriteUInt16LittleEndian(SAV.Data.AsSpan(Offset + (index * 2)), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/JoinFesta7.cs b/PKHeX.Core/Saves/Substructures/Gen7/JoinFesta7.cs index 6c5a1bf49..e1032911d 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/JoinFesta7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/JoinFesta7.cs @@ -1,83 +1,82 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class JoinFesta7 : SaveBlock { - public sealed class JoinFesta7 : SaveBlock + public JoinFesta7(SAV7SM sav, int offset) : base(sav) => Offset = offset; + public JoinFesta7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; + + public int FestaCoins { - public JoinFesta7(SAV7SM sav, int offset) : base(sav) => Offset = offset; - public JoinFesta7(SAV7USUM sav, int offset) : base(sav) => Offset = offset; - - public int FestaCoins + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x508)); + set { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x508)); - set + if (value > 9999999) + value = 9999999; + WriteInt32LittleEndian(Data.AsSpan(Offset + 0x508), value); + + TotalFestaCoins = SAV.GetRecord(038) + value; // UsedFestaCoins + } + } + + public int TotalFestaCoins + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x50C)); + set + { + if (value > 9999999) + value = 9999999; + WriteInt32LittleEndian(Data.AsSpan(Offset + 0x50C), value); + } + } + + private Span FestivalPlazaNameSpan => Data.AsSpan(Offset + 0x510, 0x2A); + + public string FestivalPlazaName + { + get => StringConverter7.GetString(FestivalPlazaNameSpan); + set => StringConverter7.SetString(FestivalPlazaNameSpan, value.AsSpan(), 20, 0, StringConverterOption.ClearZero); + } + + public ushort FestaRank { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x53A)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x53A), value); } + public ushort GetFestaMessage(int index) => ReadUInt16LittleEndian(Data.AsSpan(Offset + (index * 2))); + public void SetFestaMessage(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(Offset + (index * 2)), value); + public bool GetFestaPhraseUnlocked(int index) => Data[Offset + 0x2A50 + index] != 0; //index: 0 to 105:commonPhrases, 106:Lv100! + + public void SetFestaPhraseUnlocked(int index, bool value) + { + if (GetFestaPhraseUnlocked(index) != value) + Data[Offset + 0x2A50 + index] = value ? (byte)1 : (byte)0; + } + + public byte GetFestPrizeReceived(int index) => Data[Offset + 0x53C + index]; + public void SetFestaPrizeReceived(int index, byte value) => Data[Offset + 0x53C + index] = value; + private int FestaYear { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x2F0)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x2F0), value); } + private int FestaMonth { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x2F4)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x2F4), value); } + private int FestaDay { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x2F8)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x2F8), value); } + private int FestaHour { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x300)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x300), value); } + private int FestaMinute { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x304)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x304), value); } + private int FestaSecond { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x308)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x308), value); } + + public DateTime? FestaDate + { + get => FestaYear >= 0 && FestaMonth > 0 && FestaDay > 0 && FestaHour >= 0 && FestaMinute >= 0 && FestaSecond >= 0 && DateUtil.IsDateValid(FestaYear, FestaMonth, FestaDay) + ? new DateTime(FestaYear, FestaMonth, FestaDay, FestaHour, FestaMinute, FestaSecond) + : null; + set + { + if (value.HasValue) { - if (value > 9999999) - value = 9999999; - WriteInt32LittleEndian(Data.AsSpan(Offset + 0x508), value); - - TotalFestaCoins = SAV.GetRecord(038) + value; // UsedFestaCoins - } - } - - public int TotalFestaCoins - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x50C)); - set - { - if (value > 9999999) - value = 9999999; - WriteInt32LittleEndian(Data.AsSpan(Offset + 0x50C), value); - } - } - - private Span FestivalPlazaNameSpan => Data.AsSpan(Offset + 0x510, 0x2A); - - public string FestivalPlazaName - { - get => StringConverter7.GetString(FestivalPlazaNameSpan); - set => StringConverter7.SetString(FestivalPlazaNameSpan, value.AsSpan(), 20, 0, StringConverterOption.ClearZero); - } - - public ushort FestaRank { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x53A)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x53A), value); } - public ushort GetFestaMessage(int index) => ReadUInt16LittleEndian(Data.AsSpan(Offset + (index * 2))); - public void SetFestaMessage(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(Offset + (index * 2)), value); - public bool GetFestaPhraseUnlocked(int index) => Data[Offset + 0x2A50 + index] != 0; //index: 0 to 105:commonPhrases, 106:Lv100! - - public void SetFestaPhraseUnlocked(int index, bool value) - { - if (GetFestaPhraseUnlocked(index) != value) - Data[Offset + 0x2A50 + index] = value ? (byte)1 : (byte)0; - } - - public byte GetFestPrizeReceived(int index) => Data[Offset + 0x53C + index]; - public void SetFestaPrizeReceived(int index, byte value) => Data[Offset + 0x53C + index] = value; - private int FestaYear { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x2F0)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x2F0), value); } - private int FestaMonth { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x2F4)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x2F4), value); } - private int FestaDay { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x2F8)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x2F8), value); } - private int FestaHour { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x300)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x300), value); } - private int FestaMinute { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x304)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x304), value); } - private int FestaSecond { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x308)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x308), value); } - - public DateTime? FestaDate - { - get => FestaYear >= 0 && FestaMonth > 0 && FestaDay > 0 && FestaHour >= 0 && FestaMinute >= 0 && FestaSecond >= 0 && DateUtil.IsDateValid(FestaYear, FestaMonth, FestaDay) - ? new DateTime(FestaYear, FestaMonth, FestaDay, FestaHour, FestaMinute, FestaSecond) - : null; - set - { - if (value.HasValue) - { - DateTime dt = value.Value; - FestaYear = dt.Year; - FestaMonth = dt.Month; - FestaDay = dt.Day; - FestaHour = dt.Hour; - FestaMinute = dt.Minute; - FestaSecond = dt.Second; - } + DateTime dt = value.Value; + FestaYear = dt.Year; + FestaMonth = dt.Month; + FestaDay = dt.Day; + FestaHour = dt.Hour; + FestaMinute = dt.Minute; + FestaSecond = dt.Second; } } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/BelugaBlockIndex.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/BelugaBlockIndex.cs index 4c290b6d1..e2cd9a763 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/BelugaBlockIndex.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/BelugaBlockIndex.cs @@ -1,27 +1,26 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum BelugaBlockIndex { - public enum BelugaBlockIndex - { - /* 00 @ 0x00000, len = 0x00D90 */ MyItem, - /* 01 @ 0x00E00, len = 0x00200 */ _01, - /* 02 @ 0x01000, len = 0x00168 */ MyStatus, - /* 03 @ 0x01200, len = 0x01800 */ EventWork, - /* 04 @ 0x02A00, len = 0x020E8 */ Zukan, - /* 05 @ 0x04C00, len = 0x00930 */ Misc, // rival stuff - /* 06 @ 0x05600, len = 0x00004 */ ConfigSave, - /* 07 @ 0x05800, len = 0x00130 */ PlayerGeoLocation, - /* 08 @ 0x05A00, len = 0x00012 */ PokeListHeader, - /* 09 @ 0x05C00, len = 0x3F7A0 */ PokeListPokemon, - /* 10 @ 0x45400, len = 0x00008 */ PlayTime, - /* 11 @ 0x45600, len = 0x00E90 */ WB7Record, - /* 12 @ 0x46600, len = 0x010A4 */ CaptureRecord, - /* 13 @ 0x47800, len = 0x000F0 */ Daycare, - /* 14 @ 0x47A00, len = 0x06010 */ _14, - /* 15 @ 0x4DC00, len = 0x00200 */ _15, // stuff containing data about recent captures? - /* 16 @ 0x4DE00, len = 0x00098 */ _16, - /* 17 @ 0x4E000, len = 0x00068 */ _17, - /* 18 @ 0x4E200, len = 0x69780 */ GoParkEntities, - /* 19 @ 0xB7A00, len = 0x000B0 */ GoGoParkNames, - /* 20 @ 0xB7C00, len = 0x00940 */ _20, // Go Park Names - } + /* 00 @ 0x00000, len = 0x00D90 */ MyItem, + /* 01 @ 0x00E00, len = 0x00200 */ _01, + /* 02 @ 0x01000, len = 0x00168 */ MyStatus, + /* 03 @ 0x01200, len = 0x01800 */ EventWork, + /* 04 @ 0x02A00, len = 0x020E8 */ Zukan, + /* 05 @ 0x04C00, len = 0x00930 */ Misc, // rival stuff + /* 06 @ 0x05600, len = 0x00004 */ ConfigSave, + /* 07 @ 0x05800, len = 0x00130 */ PlayerGeoLocation, + /* 08 @ 0x05A00, len = 0x00012 */ PokeListHeader, + /* 09 @ 0x05C00, len = 0x3F7A0 */ PokeListPokemon, + /* 10 @ 0x45400, len = 0x00008 */ PlayTime, + /* 11 @ 0x45600, len = 0x00E90 */ WB7Record, + /* 12 @ 0x46600, len = 0x010A4 */ CaptureRecord, + /* 13 @ 0x47800, len = 0x000F0 */ Daycare, + /* 14 @ 0x47A00, len = 0x06010 */ _14, + /* 15 @ 0x4DC00, len = 0x00200 */ _15, // stuff containing data about recent captures? + /* 16 @ 0x4DE00, len = 0x00098 */ _16, + /* 17 @ 0x4E000, len = 0x00068 */ _17, + /* 18 @ 0x4E200, len = 0x69780 */ GoParkEntities, + /* 19 @ 0xB7A00, len = 0x000B0 */ GoGoParkNames, + /* 20 @ 0xB7C00, len = 0x00940 */ _20, // Go Park Names } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/CaptureRecords.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/CaptureRecords.cs index f72e67fd9..4fc88bd3b 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/CaptureRecords.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/CaptureRecords.cs @@ -1,149 +1,148 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class CaptureRecords : SaveBlock { - public sealed class CaptureRecords : SaveBlock + public CaptureRecords(SAV7b sav, int offset) : base(sav) => Offset = offset; + + private const int ENTRY_COUNT = 153; + private const int MAX_COUNT_ENTRY_CAPTURE = 9_999; + private const int MAX_COUNT_ENTRY_TRANSFER = 999_999_999; + private const int MAX_COUNT_TOTAL = 999_999_999; + + // 0x468A8 to 0x46B0B contains 153 entries (u32) denoting how many of said Species you've caught (each cap out at 9,999). + private const int CapturedOffset = 0x2A8; + // 0x46B0C to 0x46D6F contains 153 entries (u32) denoting how many of said Species you've transferred to Professor Oak (each cap out at 999,999,999). + private const int TransferredOffset = CapturedOffset + (ENTRY_COUNT * sizeof(uint)); // 0x50C + + // 0x770 ?? + + // 0x46D94 is a u32 stores how many total Pokémon you've caught (caps out at 999,999,999). + private const int TotalCapturedOffset = 0x794; + + // 0x46DA8 is a u32 that stores how many Pokémon you've transferred to Professor Oak. + // This value is equal to the sum of all individual transferred Species, but caps out at 999,999,999 even if the sum of all individual Species exceeds this. + private const int TotalTransferredOffset = 0x7A8; + + // Calling into these directly, you should be sure that you're less than ENTRY_COUNT. + private int GetCapturedOffset(int index) => Offset + CapturedOffset + (index * 4); + private int GetTransferredOffset(int index) => Offset + TransferredOffset + (index * 4); + public uint GetCapturedCountIndex(int index) => ReadUInt32LittleEndian(Data.AsSpan(GetCapturedOffset(index))); + public uint GetTransferredCountIndex(int index) => ReadUInt32LittleEndian(Data.AsSpan(GetTransferredOffset(index))); + public void SetCapturedCountIndex(int index, uint value) => WriteUInt32LittleEndian(Data.AsSpan(GetCapturedOffset(index)), Math.Min(MAX_COUNT_ENTRY_CAPTURE, value)); + public void SetTransferredCountIndex(int index, uint value) => WriteUInt32LittleEndian(Data.AsSpan(GetTransferredOffset(index)), Math.Min(MAX_COUNT_ENTRY_TRANSFER, value)); + + public static int GetSpeciesIndex(int species) => (uint)species switch { - public CaptureRecords(SAV7b sav, int offset) : base(sav) => Offset = offset; + <= (int) Species.Mew => species - 1, + (int) Species.Meltan or (int) Species.Melmetal => species - 657, // 151, 152 + _ => -1, + }; - private const int ENTRY_COUNT = 153; - private const int MAX_COUNT_ENTRY_CAPTURE = 9_999; - private const int MAX_COUNT_ENTRY_TRANSFER = 999_999_999; - private const int MAX_COUNT_TOTAL = 999_999_999; + public static int GetIndexSpecies(int index) + { + if (index < (int) Species.Mew) + return index + 1; + return index + 657; + } - // 0x468A8 to 0x46B0B contains 153 entries (u32) denoting how many of said Species you've caught (each cap out at 9,999). - private const int CapturedOffset = 0x2A8; - // 0x46B0C to 0x46D6F contains 153 entries (u32) denoting how many of said Species you've transferred to Professor Oak (each cap out at 999,999,999). - private const int TransferredOffset = CapturedOffset + (ENTRY_COUNT * sizeof(uint)); // 0x50C + public uint GetCapturedCount(int species) + { + int index = GetSpeciesIndex(species); + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(species)); + return GetCapturedCountIndex(index); + } - // 0x770 ?? + public void SetCapturedCount(int species, uint value) + { + int index = GetSpeciesIndex(species); + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(species)); + SetCapturedCountIndex(index, value); + } - // 0x46D94 is a u32 stores how many total Pokémon you've caught (caps out at 999,999,999). - private const int TotalCapturedOffset = 0x794; + public uint GetTransferredCount(int species) + { + int index = GetSpeciesIndex(species); + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(species)); + return GetTransferredCountIndex(index); + } - // 0x46DA8 is a u32 that stores how many Pokémon you've transferred to Professor Oak. - // This value is equal to the sum of all individual transferred Species, but caps out at 999,999,999 even if the sum of all individual Species exceeds this. - private const int TotalTransferredOffset = 0x7A8; + public void SetTransferredCount(int species, uint value) + { + int index = GetSpeciesIndex(species); + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(species)); + SetTransferredCountIndex(index, value); + } - // Calling into these directly, you should be sure that you're less than ENTRY_COUNT. - private int GetCapturedOffset(int index) => Offset + CapturedOffset + (index * 4); - private int GetTransferredOffset(int index) => Offset + TransferredOffset + (index * 4); - public uint GetCapturedCountIndex(int index) => ReadUInt32LittleEndian(Data.AsSpan(GetCapturedOffset(index))); - public uint GetTransferredCountIndex(int index) => ReadUInt32LittleEndian(Data.AsSpan(GetTransferredOffset(index))); - public void SetCapturedCountIndex(int index, uint value) => WriteUInt32LittleEndian(Data.AsSpan(GetCapturedOffset(index)), Math.Min(MAX_COUNT_ENTRY_CAPTURE, value)); - public void SetTransferredCountIndex(int index, uint value) => WriteUInt32LittleEndian(Data.AsSpan(GetTransferredOffset(index)), Math.Min(MAX_COUNT_ENTRY_TRANSFER, value)); + public uint TotalCaptured + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + TotalCapturedOffset)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + TotalCapturedOffset), Math.Min(MAX_COUNT_TOTAL, value)); + } - public static int GetSpeciesIndex(int species) => (uint)species switch + public uint TotalTransferred + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + TotalTransferredOffset)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + TotalTransferredOffset), Math.Min(MAX_COUNT_TOTAL, value)); + } + + public uint CalculateTotalCaptured() + { + uint total = 0; + for (int i = 0; i < ENTRY_COUNT; i++) + total += GetCapturedCountIndex(i); + return Math.Min(total, MAX_COUNT_TOTAL); + } + + public uint CalculateTotalTransferred() + { + ulong total = 0; + for (int i = 0; i < ENTRY_COUNT; i++) + total += GetTransferredCountIndex(i); + return (uint)Math.Min(total, MAX_COUNT_TOTAL); + } + + public void SetAllCaptured(uint count = MAX_COUNT_ENTRY_CAPTURE, Zukan7b? dex = null) + { + uint total = 0; + count = Math.Min(count, MAX_COUNT_ENTRY_CAPTURE); + for (int i = 0; i < ENTRY_COUNT; i++) { - <= (int) Species.Mew => species - 1, - (int) Species.Meltan or (int) Species.Melmetal => species - 657, // 151, 152 - _ => -1, - }; - - public static int GetIndexSpecies(int index) - { - if (index < (int) Species.Mew) - return index + 1; - return index + 657; - } - - public uint GetCapturedCount(int species) - { - int index = GetSpeciesIndex(species); - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(species)); - return GetCapturedCountIndex(index); - } - - public void SetCapturedCount(int species, uint value) - { - int index = GetSpeciesIndex(species); - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(species)); - SetCapturedCountIndex(index, value); - } - - public uint GetTransferredCount(int species) - { - int index = GetSpeciesIndex(species); - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(species)); - return GetTransferredCountIndex(index); - } - - public void SetTransferredCount(int species, uint value) - { - int index = GetSpeciesIndex(species); - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(species)); - SetTransferredCountIndex(index, value); - } - - public uint TotalCaptured - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + TotalCapturedOffset)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + TotalCapturedOffset), Math.Min(MAX_COUNT_TOTAL, value)); - } - - public uint TotalTransferred - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + TotalTransferredOffset)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + TotalTransferredOffset), Math.Min(MAX_COUNT_TOTAL, value)); - } - - public uint CalculateTotalCaptured() - { - uint total = 0; - for (int i = 0; i < ENTRY_COUNT; i++) + int species = GetIndexSpecies(i); + if (count != 0 && dex?.GetCaught(species) == false) + { total += GetCapturedCountIndex(i); - return Math.Min(total, MAX_COUNT_TOTAL); + continue; + } + SetCapturedCountIndex(i, count); + total += count; } + if (total < TotalCaptured) + TotalCaptured = total; + } - public uint CalculateTotalTransferred() + public void SetAllTransferred(uint count = MAX_COUNT_ENTRY_TRANSFER, Zukan7b? dex = null) + { + uint total = 0; + count = Math.Min(count, MAX_COUNT_ENTRY_TRANSFER); + for (int i = 0; i < ENTRY_COUNT; i++) { - ulong total = 0; - for (int i = 0; i < ENTRY_COUNT; i++) + int species = GetIndexSpecies(i); + if (count != 0 && dex?.GetCaught(species) == false) + { total += GetTransferredCountIndex(i); - return (uint)Math.Min(total, MAX_COUNT_TOTAL); - } - - public void SetAllCaptured(uint count = MAX_COUNT_ENTRY_CAPTURE, Zukan7b? dex = null) - { - uint total = 0; - count = Math.Min(count, MAX_COUNT_ENTRY_CAPTURE); - for (int i = 0; i < ENTRY_COUNT; i++) - { - int species = GetIndexSpecies(i); - if (count != 0 && dex?.GetCaught(species) == false) - { - total += GetCapturedCountIndex(i); - continue; - } - SetCapturedCountIndex(i, count); - total += count; + continue; } - if (total < TotalCaptured) - TotalCaptured = total; - } - - public void SetAllTransferred(uint count = MAX_COUNT_ENTRY_TRANSFER, Zukan7b? dex = null) - { - uint total = 0; - count = Math.Min(count, MAX_COUNT_ENTRY_TRANSFER); - for (int i = 0; i < ENTRY_COUNT; i++) - { - int species = GetIndexSpecies(i); - if (count != 0 && dex?.GetCaught(species) == false) - { - total += GetTransferredCountIndex(i); - continue; - } - SetTransferredCountIndex(i, count); - total += count; - } - if (total < TotalTransferred) - TotalTransferred = total; + SetTransferredCountIndex(i, count); + total += count; } + if (total < TotalTransferred) + TotalTransferred = total; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/ConfigSave7b.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/ConfigSave7b.cs index e29fd6dd6..98fcb7b99 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/ConfigSave7b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/ConfigSave7b.cs @@ -1,69 +1,68 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class ConfigSave7b : SaveBlock { - public sealed class ConfigSave7b : SaveBlock + /* ===First 8 bits=== + * talkSpeed:2 + * battleAnim:1 + * battleStyle:1 + * language:4 + * + * everything else: unknown + */ + + public ConfigSave7b(SAV7b sav, int offset) : base(sav) => Offset = offset; + + public int ConfigValue { - /* ===First 8 bits=== - * talkSpeed:2 - * battleAnim:1 - * battleStyle:1 - * language:4 - * - * everything else: unknown - */ - - public ConfigSave7b(SAV7b sav, int offset) : base(sav) => Offset = offset; - - public int ConfigValue - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset), value); - } - - public int TalkingSpeed - { - get => ConfigValue & 3; - set => ConfigValue = (ConfigValue & ~3) | (value & 3); - } - - public BattleAnimationSetting BattleAnimation - { - // Effects OFF = 1, Effects ON = 0 - get => (BattleAnimationSetting)((ConfigValue >> 2) & 1); - set => ConfigValue = (ConfigValue & ~(1 << 2)) | ((byte)value << 2); - } - - public BattleStyleSetting BattleStyle - { - // SET = 1, SWITCH = 0 - get => (BattleStyleSetting)((ConfigValue >> 3) & 1); - set => ConfigValue = (ConfigValue & ~(1 << 3)) | ((byte)value << 3); - } - - /// - /// for messages, stored with skipped in the enumeration. - /// - public int Language - { - get => GetLanguageID((ConfigValue >> 4) & 0xF); - set => ConfigValue = ((ConfigValue & ~0xF0) | SetLanguageID(value) << 4); - } - - private static int GetLanguageID(int rawValue) => rawValue >= (int) LanguageID.UNUSED_6 ? rawValue + 1 : rawValue; // sets langBank to LanguageID - private static int SetLanguageID(int rawValue) => rawValue > (int) LanguageID.UNUSED_6 ? rawValue - 1 : rawValue; // sets LanguageID to langBank - - public enum BattleAnimationSetting - { - EffectsON, - EffectsOFF, - } - - public enum BattleStyleSetting - { - SWITCH, - SET, - } + get => ReadInt32LittleEndian(Data.AsSpan(Offset)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset), value); } -} \ No newline at end of file + + public int TalkingSpeed + { + get => ConfigValue & 3; + set => ConfigValue = (ConfigValue & ~3) | (value & 3); + } + + public BattleAnimationSetting BattleAnimation + { + // Effects OFF = 1, Effects ON = 0 + get => (BattleAnimationSetting)((ConfigValue >> 2) & 1); + set => ConfigValue = (ConfigValue & ~(1 << 2)) | ((byte)value << 2); + } + + public BattleStyleSetting BattleStyle + { + // SET = 1, SWITCH = 0 + get => (BattleStyleSetting)((ConfigValue >> 3) & 1); + set => ConfigValue = (ConfigValue & ~(1 << 3)) | ((byte)value << 3); + } + + /// + /// for messages, stored with skipped in the enumeration. + /// + public int Language + { + get => GetLanguageID((ConfigValue >> 4) & 0xF); + set => ConfigValue = ((ConfigValue & ~0xF0) | (SetLanguageID(value) << 4)); + } + + private static int GetLanguageID(int rawValue) => rawValue >= (int) LanguageID.UNUSED_6 ? rawValue + 1 : rawValue; // sets langBank to LanguageID + private static int SetLanguageID(int rawValue) => rawValue > (int) LanguageID.UNUSED_6 ? rawValue - 1 : rawValue; // sets LanguageID to langBank + + public enum BattleAnimationSetting + { + EffectsON, + EffectsOFF, + } + + public enum BattleStyleSetting + { + SWITCH, + SET, + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/EventWork7b.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/EventWork7b.cs index 709aab50c..487849080 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/EventWork7b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/EventWork7b.cs @@ -1,193 +1,192 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class EventWork7b : SaveBlock, IEventVar { - public sealed class EventWork7b : SaveBlock, IEventVar + public EventWork7b(SAV7b sav, int offset) : base(sav) { - public EventWork7b(SAV7b sav, int offset) : base(sav) - { - Offset = offset; - // Zone @ 0x21A0 - 0x21AF (128 flags) - // System @ 0x21B0 - 0x21EF (512 flags) -- is this really 256 instead, with another 256 region after for the small vanish? - // Vanish @ 0x21F0 - 0x22AF (1536 flags) - // Event @ 0x22B0 - 0x23A7 (rest of the flags) (512) -- I think trainer flags are afterwards.... For now, this is a catch-all + Offset = offset; + // Zone @ 0x21A0 - 0x21AF (128 flags) + // System @ 0x21B0 - 0x21EF (512 flags) -- is this really 256 instead, with another 256 region after for the small vanish? + // Vanish @ 0x21F0 - 0x22AF (1536 flags) + // Event @ 0x22B0 - 0x23A7 (rest of the flags) (512) -- I think trainer flags are afterwards.... For now, this is a catch-all - // time flags (39 used flags of 42) = 6 bytes 0x22F0-0x22F5 - // trainer flags (???) = 0x22F6 - end? + // time flags (39 used flags of 42) = 6 bytes 0x22F0-0x22F5 + // trainer flags (???) = 0x22F6 - end? - // Title flags @ 0x2498 - 0x24AB (160 flags): unlocked Master Trainer Titles (last 4 unused) - } + // Title flags @ 0x2498 - 0x24AB (160 flags): unlocked Master Trainer Titles (last 4 unused) + } - // Overall Layout - private const int WorkCount = 1000; - private const int WorkSize = sizeof(int); - private const int FlagStart = WorkCount * WorkSize; - private const int FlagCount = EventFlagStart + EventFlagCount; + // Overall Layout + private const int WorkCount = 1000; + private const int WorkSize = sizeof(int); + private const int FlagStart = WorkCount * WorkSize; + private const int FlagCount = EventFlagStart + EventFlagCount; - // Breakdown! - private const int ZoneWorkCount = 0x20; // 32 - private const int SystemWorkCount = 0x80; // 128 - private const int SceneWorkCount = 0x200; // 512 - private const int EventWorkCount = 0x100; // 256 - // private const int UnusedWorkCount = 72; + // Breakdown! + private const int ZoneWorkCount = 0x20; // 32 + private const int SystemWorkCount = 0x80; // 128 + private const int SceneWorkCount = 0x200; // 512 + private const int EventWorkCount = 0x100; // 256 + // private const int UnusedWorkCount = 72; - private const int ZoneWorkStart = 0; - private const int SystemWorkStart = ZoneWorkStart + ZoneWorkCount; - private const int SceneWorkStart = SystemWorkStart + SystemWorkCount; - private const int EventWorkStart = SceneWorkStart + SceneWorkCount; + private const int ZoneWorkStart = 0; + private const int SystemWorkStart = ZoneWorkStart + ZoneWorkCount; + private const int SceneWorkStart = SystemWorkStart + SystemWorkCount; + private const int EventWorkStart = SceneWorkStart + SceneWorkCount; - private const int ZoneFlagCount = 0x80; // 128 - private const int SystemFlagCount = 0x200; // 512 - private const int VanishFlagCount = 0x600; // 1536 - private const int EventFlagCount = 0x7C0; // 1984 + private const int ZoneFlagCount = 0x80; // 128 + private const int SystemFlagCount = 0x200; // 512 + private const int VanishFlagCount = 0x600; // 1536 + private const int EventFlagCount = 0x7C0; // 1984 - private const int ZoneFlagStart = 0; - private const int SystemFlagStart = ZoneFlagStart + ZoneFlagCount; - private const int VanishFlagStart = SystemFlagStart + SystemFlagCount; - private const int EventFlagStart = VanishFlagStart + VanishFlagCount; + private const int ZoneFlagStart = 0; + private const int SystemFlagStart = ZoneFlagStart + ZoneFlagCount; + private const int VanishFlagStart = SystemFlagStart + SystemFlagCount; + private const int EventFlagStart = VanishFlagStart + VanishFlagCount; - // Work/Flag ends at 0x11A8 (relative to block start). Other data undocumented unless noted below. + // Work/Flag ends at 0x11A8 (relative to block start). Other data undocumented unless noted below. - private const int TitleFlagStart = 0x1298; // 0x1298 - public const int MaxTitleFlag = 156; // Trainer, [1..153], Grand, Battle + private const int TitleFlagStart = 0x1298; // 0x1298 + public const int MaxTitleFlag = 156; // Trainer, [1..153], Grand, Battle - public int CountFlag => FlagCount; - public int CountWork => WorkCount; + public int CountFlag => FlagCount; + public int CountWork => WorkCount; - public int GetWork(int index) => ReadInt32LittleEndian(Data.AsSpan(Offset + (index * WorkSize))); - public void SetWork(int index, int value) => WriteInt32LittleEndian(Data.AsSpan(Offset + (index * WorkSize)), value); - public int GetWork(EventVarType type, int index) => GetWork(GetWorkRawIndex(type, index)); - public void SetWork(EventVarType type, int index, int value) => SetWork(GetWorkRawIndex(type, index), value); - public bool GetFlag(EventVarType type, int index) => GetFlag(GetFlagRawIndex(type, index)); - public void SetFlag(EventVarType type, int index, bool value = true) => SetFlag(GetFlagRawIndex(type, index), value); - - public int GetFlagRawIndex(EventVarType type, int index) - { - int max = GetFlagCount(type); - if ((uint)index > max) - throw new ArgumentOutOfRangeException(nameof(index)); - var start = GetFlagStart(type); - return start + index; - } - - public int GetWorkRawIndex(EventVarType type, int index) - { - int max = GetWorkCount(type); - if ((uint)index > max) - throw new ArgumentOutOfRangeException(nameof(index)); - var start = GetWorkStart(type); - return start + index; - } - - public bool GetFlag(int index) - { - var offset = Offset + FlagStart + (index >> 3); - return FlagUtil.GetFlag(Data, offset, index); - } - - public void SetFlag(int index, bool value = true) - { - var offset = Offset + FlagStart + (index >> 3); - FlagUtil.SetFlag(Data, offset, index, value); - } - - public EventVarType GetFlagType(int index, out int subIndex) - { - subIndex = index; - if (index < ZoneFlagCount) - return EventVarType.Zone; - subIndex -= ZoneFlagCount; - - if (subIndex < SystemFlagCount) - return EventVarType.System; - subIndex -= SystemFlagCount; - - if (subIndex < VanishFlagCount) - return EventVarType.Vanish; - subIndex -= VanishFlagCount; - - if (subIndex < EventFlagCount) - return EventVarType.Event; + public int GetWork(int index) => ReadInt32LittleEndian(Data.AsSpan(Offset + (index * WorkSize))); + public void SetWork(int index, int value) => WriteInt32LittleEndian(Data.AsSpan(Offset + (index * WorkSize)), value); + public int GetWork(EventVarType type, int index) => GetWork(GetWorkRawIndex(type, index)); + public void SetWork(EventVarType type, int index, int value) => SetWork(GetWorkRawIndex(type, index), value); + public bool GetFlag(EventVarType type, int index) => GetFlag(GetFlagRawIndex(type, index)); + public void SetFlag(EventVarType type, int index, bool value = true) => SetFlag(GetFlagRawIndex(type, index), value); + public int GetFlagRawIndex(EventVarType type, int index) + { + int max = GetFlagCount(type); + if ((uint)index > max) throw new ArgumentOutOfRangeException(nameof(index)); - } - - public EventVarType GetWorkType(int index, out int subIndex) - { - subIndex = index; - if (subIndex < ZoneWorkCount) - return EventVarType.Zone; - subIndex -= ZoneWorkCount; - - if (subIndex < SystemWorkCount) - return EventVarType.System; - subIndex -= SystemWorkCount; - - if (subIndex < SceneWorkCount) - return EventVarType.Scene; - subIndex -= SceneWorkCount; - - if (subIndex < EventWorkCount) - return EventVarType.Event; + var start = GetFlagStart(type); + return start + index; + } + public int GetWorkRawIndex(EventVarType type, int index) + { + int max = GetWorkCount(type); + if ((uint)index > max) throw new ArgumentOutOfRangeException(nameof(index)); - } + var start = GetWorkStart(type); + return start + index; + } - private static int GetFlagStart(EventVarType type) => type switch - { - EventVarType.Zone => ZoneFlagStart, - EventVarType.System => SystemFlagStart, - EventVarType.Vanish => VanishFlagStart, - EventVarType.Event => EventFlagStart, - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; + public bool GetFlag(int index) + { + var offset = Offset + FlagStart + (index >> 3); + return FlagUtil.GetFlag(Data, offset, index); + } - private static int GetWorkStart(EventVarType type) => type switch - { - EventVarType.Zone => ZoneWorkStart, - EventVarType.System => SystemWorkStart, - EventVarType.Scene => SceneWorkStart, - EventVarType.Event => EventWorkStart, - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; + public void SetFlag(int index, bool value = true) + { + var offset = Offset + FlagStart + (index >> 3); + FlagUtil.SetFlag(Data, offset, index, value); + } - private static int GetFlagCount(EventVarType type) => type switch - { - EventVarType.Zone => ZoneFlagCount, - EventVarType.System => SystemFlagCount, - EventVarType.Vanish => VanishFlagCount, - EventVarType.Event => EventFlagCount, - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; + public EventVarType GetFlagType(int index, out int subIndex) + { + subIndex = index; + if (index < ZoneFlagCount) + return EventVarType.Zone; + subIndex -= ZoneFlagCount; - private static int GetWorkCount(EventVarType type) => type switch - { - EventVarType.Zone => ZoneWorkCount, - EventVarType.System => SystemWorkCount, - EventVarType.Scene => SceneWorkCount, - EventVarType.Event => EventWorkCount, - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; + if (subIndex < SystemFlagCount) + return EventVarType.System; + subIndex -= SystemFlagCount; - public bool GetTitleFlag(int index) - { - if ((uint)index >= MaxTitleFlag) - throw new ArgumentOutOfRangeException(nameof(index)); - return FlagUtil.GetFlag(Data, Offset + TitleFlagStart + (index >> 3), index); - } + if (subIndex < VanishFlagCount) + return EventVarType.Vanish; + subIndex -= VanishFlagCount; - public void SetTitleFlag(int index, bool value = true) - { - if ((uint)index >= MaxTitleFlag) - throw new ArgumentOutOfRangeException(nameof(index)); - FlagUtil.SetFlag(Data, Offset + TitleFlagStart + (index >> 3), index, value); - } + if (subIndex < EventFlagCount) + return EventVarType.Event; - public void UnlockAllTitleFlags() - { - for (int i = 0; i < MaxTitleFlag; i++) - SetTitleFlag(i); - } + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public EventVarType GetWorkType(int index, out int subIndex) + { + subIndex = index; + if (subIndex < ZoneWorkCount) + return EventVarType.Zone; + subIndex -= ZoneWorkCount; + + if (subIndex < SystemWorkCount) + return EventVarType.System; + subIndex -= SystemWorkCount; + + if (subIndex < SceneWorkCount) + return EventVarType.Scene; + subIndex -= SceneWorkCount; + + if (subIndex < EventWorkCount) + return EventVarType.Event; + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + private static int GetFlagStart(EventVarType type) => type switch + { + EventVarType.Zone => ZoneFlagStart, + EventVarType.System => SystemFlagStart, + EventVarType.Vanish => VanishFlagStart, + EventVarType.Event => EventFlagStart, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + private static int GetWorkStart(EventVarType type) => type switch + { + EventVarType.Zone => ZoneWorkStart, + EventVarType.System => SystemWorkStart, + EventVarType.Scene => SceneWorkStart, + EventVarType.Event => EventWorkStart, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + private static int GetFlagCount(EventVarType type) => type switch + { + EventVarType.Zone => ZoneFlagCount, + EventVarType.System => SystemFlagCount, + EventVarType.Vanish => VanishFlagCount, + EventVarType.Event => EventFlagCount, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + private static int GetWorkCount(EventVarType type) => type switch + { + EventVarType.Zone => ZoneWorkCount, + EventVarType.System => SystemWorkCount, + EventVarType.Scene => SceneWorkCount, + EventVarType.Event => EventWorkCount, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + public bool GetTitleFlag(int index) + { + if ((uint)index >= MaxTitleFlag) + throw new ArgumentOutOfRangeException(nameof(index)); + return FlagUtil.GetFlag(Data, Offset + TitleFlagStart + (index >> 3), index); + } + + public void SetTitleFlag(int index, bool value = true) + { + if ((uint)index >= MaxTitleFlag) + throw new ArgumentOutOfRangeException(nameof(index)); + FlagUtil.SetFlag(Data, Offset + TitleFlagStart + (index >> 3), index, value); + } + + public void UnlockAllTitleFlags() + { + for (int i = 0; i < MaxTitleFlag; i++) + SetTitleFlag(i); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs index 4833f63c3..51b5a5f89 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GP1.cs @@ -3,204 +3,203 @@ using System.Text; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Go Park Entity transferred from to . +/// +public sealed class GP1 : IEncounterInfo, IFixedAbilityNumber { - /// - /// Go Park Entity transferred from to . - /// - public sealed class GP1 : IEncounterInfo, IFixedAbilityNumber + public const int SIZE = 0x1B0; + public readonly byte[] Data; + + public GameVersion Version => GameVersion.GO; + public bool EggEncounter => false; + public byte LevelMin => Level; + public byte LevelMax => Level; + public int Generation => 7; + public AbilityPermission Ability => AbilityPermission.Any12; + public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPB7(tr); + public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPB7(tr, criteria); + + public GP1(byte[] data) => Data = data; + public GP1() : this((byte[])Blank.Clone()) { } + public void WriteTo(byte[] data, int offset) => Data.CopyTo(data, offset); + + public static GP1 FromData(byte[] data, int offset) { - public const int SIZE = 0x1B0; - public readonly byte[] Data; + var span = data.AsSpan(offset); + return FromData(span); + } - public GameVersion Version => GameVersion.GO; - public bool EggEncounter => false; - public byte LevelMin => Level; - public byte LevelMax => Level; - public int Generation => 7; - public AbilityPermission Ability => AbilityPermission.Any12; - public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPB7(sav); - public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) => ConvertToPB7(sav, criteria); + private static GP1 FromData(ReadOnlySpan span) + { + var gpkm = new GP1(); + span[..SIZE].CopyTo(gpkm.Data); + return gpkm; + } - public GP1(byte[] data) => Data = data; - public GP1() : this((byte[])Blank.Clone()) { } - public void WriteTo(byte[] data, int offset) => Data.CopyTo(data, offset); + /// + /// First 0x20 bytes of an empty , all other bytes are 0. + /// + private static readonly byte[] Blank20 = + { + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x3F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x85, 0xEC, 0x33, 0x01, + }; - public static GP1 FromData(byte[] data, int offset) + public static readonly byte[] Blank = GetBlank(); + + public static byte[] GetBlank() + { + byte[] data = new byte[SIZE]; + Blank20.CopyTo(data, 0x20); + return data; + } + + public string Username1 => Util.TrimFromZero(Encoding.ASCII.GetString(Data, 0x00, 0x10)); + public string Username2 => Util.TrimFromZero(Encoding.ASCII.GetString(Data, 0x10, 0x20)); + + public int Species => ReadInt32LittleEndian(Data.AsSpan(0x28)); + public int CP => ReadInt32LittleEndian(Data.AsSpan(0x2C)); + public float LevelF => ReadSingleLittleEndian(Data.AsSpan(0x30)); + public byte Level => Math.Max((byte)1, (byte)Math.Round(LevelF)); + public int Stat_HP => ReadInt32LittleEndian(Data.AsSpan(0x34)); + // geolocation data 0x38-0x47? + public float HeightF => ReadSingleLittleEndian(Data.AsSpan(0x48)); + public float WeightF => ReadSingleLittleEndian(Data.AsSpan(0x4C)); + + public byte HeightScalar + { + get { - var span = data.AsSpan(offset); - return FromData(span); - } - - private static GP1 FromData(ReadOnlySpan span) - { - var gpkm = new GP1(); - span[..SIZE].CopyTo(gpkm.Data); - return gpkm; - } - - /// - /// First 0x20 bytes of an empty , all other bytes are 0. - /// - private static readonly byte[] Blank20 = - { - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x80, 0x3F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x85, 0xEC, 0x33, 0x01, - }; - - public static readonly byte[] Blank = GetBlank(); - - public static byte[] GetBlank() - { - byte[] data = new byte[SIZE]; - Blank20.CopyTo(data, 0x20); - return data; - } - - public string Username1 => Util.TrimFromZero(Encoding.ASCII.GetString(Data, 0x00, 0x10)); - public string Username2 => Util.TrimFromZero(Encoding.ASCII.GetString(Data, 0x10, 0x20)); - - public int Species => ReadInt32LittleEndian(Data.AsSpan(0x28)); - public int CP => ReadInt32LittleEndian(Data.AsSpan(0x2C)); - public float LevelF => ReadSingleLittleEndian(Data.AsSpan(0x30)); - public byte Level => Math.Max((byte)1, (byte)Math.Round(LevelF)); - public int Stat_HP => ReadInt32LittleEndian(Data.AsSpan(0x34)); - // geolocation data 0x38-0x47? - public float HeightF => ReadSingleLittleEndian(Data.AsSpan(0x48)); - public float WeightF => ReadSingleLittleEndian(Data.AsSpan(0x4C)); - - public byte HeightScalar - { - get - { - var height = HeightF * 100f; - var pi = PersonalTable.GG.GetFormEntry(Species, Form); - var avgHeight = pi.Height; - return PB7.GetHeightScalar(height, avgHeight); - } - } - - public byte WeightScalar - { - get - { - var height = HeightF * 100f; - var weight = WeightF * 10f; - var pi = PersonalTable.GG.GetFormEntry(Species, Form); - var avgHeight = pi.Height; - var avgWeight = pi.Weight; - return PB7.GetWeightScalar(height, weight, avgHeight, avgWeight); - } - } - - public int IV_HP => ReadInt32LittleEndian(Data.AsSpan(0x50)); - public int IV_ATK => ReadInt32LittleEndian(Data.AsSpan(0x54)); - public int IV_DEF => ReadInt32LittleEndian(Data.AsSpan(0x58)); - public int Date => ReadInt32LittleEndian(Data.AsSpan(0x5C)); // ####.##.## YYYY.MM.DD - public int Year => Date / 1_00_00; - public int Month => (Date / 1_00) % 1_00; - public int Day => Date % 1_00; - - public int Gender => Data[0x70] - 1; // M=1, F=2, G=3 ;; shift down by 1. - - public int Form => Data[0x72]; - public bool IsShiny => Data[0x73] == 1; - - // https://bulbapedia.bulbagarden.net/wiki/List_of_moves_in_Pok%C3%A9mon_GO - public int Move1 => ReadInt32LittleEndian(Data.AsSpan(0x74)); // uses Go Indexes - public int Move2 => ReadInt32LittleEndian(Data.AsSpan(0x78)); // uses Go Indexes - - public string GeoCityName => Util.TrimFromZero(Encoding.ASCII.GetString(Data, 0x7C, 0x60)); // dunno length - - public string Nickname => Util.TrimFromZero(Encoding.ASCII.GetString(Data, 0x12D, 0x20)); // dunno length - - public static readonly IReadOnlyList Genders = GameInfo.GenderSymbolASCII; - public string GenderString => (uint) Gender >= Genders.Count ? string.Empty : Genders[Gender]; - public string ShinyString => IsShiny ? "★ " : string.Empty; - public string FormString => Form != 0 ? $"-{Form}" : string.Empty; - private string NickStr => string.IsNullOrWhiteSpace(Nickname) ? SpeciesName.GetSpeciesNameGeneration(Species, (int)LanguageID.English, 7) : Nickname; - public string FileName => $"{FileNameWithoutExtension}.gp1"; - - public string FileNameWithoutExtension - { - get - { - string form = Form > 0 ? $"-{Form:00}" : string.Empty; - string star = IsShiny ? " ★" : string.Empty; - return $"{Species:000}{form}{star} - {NickStr} - Lv. {Level:00} - {IV_HP:00}.{IV_ATK:00}.{IV_DEF:00} - CP {CP:0000} (Moves {Move1:000}, {Move2:000})"; - } - } - - public string GeoTime => $"Captured in {GeoCityName} by {Username1} on {Year}/{Month:00}/{Day:00}"; - public string StatMove => $"{IV_HP:00}/{IV_ATK:00}/{IV_DEF:00}, CP {CP:0000} (Moves {Move1:000}, {Move2:000})"; - public string Dump(IReadOnlyList speciesNames, int index) => $"{index:000} {Nickname} ({speciesNames[Species]}{FormString} {ShinyString}[{GenderString}]) @ Lv. {Level:00} - {StatMove}, {GeoTime}."; - - public PB7 ConvertToPB7(ITrainerInfo sav) => ConvertToPB7(sav, EncounterCriteria.Unrestricted); - - public PB7 ConvertToPB7(ITrainerInfo sav, EncounterCriteria criteria) - { - var rnd = Util.Rand; - var pk = new PB7 - { - EncryptionConstant = rnd.Rand32(), - PID = rnd.Rand32(), - Version = (int) GameVersion.GO, - Species = Species, - Form = Form, - Met_Location = 50, // Go complex - Met_Year = Year - 2000, - Met_Month = Month, - Met_Day = Day, - CurrentLevel = Level, - Met_Level = Level, - TID = sav.TID, - SID = sav.SID, - OT_Name = sav.OT, - Ball = 4, - Language = sav.Language, - }; - - var nick = Nickname; - if (!string.IsNullOrWhiteSpace(nick)) - { - pk.Nickname = nick; - pk.IsNicknamed = true; - } - else - { - pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, sav.Language, 7); - } - - pk.IV_DEF = pk.IV_SPD = (IV_DEF * 2) + 1; - pk.IV_ATK = pk.IV_SPA = (IV_ATK * 2) + 1; - pk.IV_HP = (IV_HP * 2) + 1; - pk.IV_SPE = Util.Rand.Next(32); - - var pi = pk.PersonalInfo; - pk.Gender = criteria.GetGender(Gender, pi); - pk.Nature = (int)criteria.GetNature(Nature.Random); - pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); - - bool isShiny = pk.IsShiny; - if (IsShiny && !isShiny) // Force Square - pk.PID = (uint)(((sav.TID ^ sav.SID ^ (pk.PID & 0xFFFF) ^ 0) << 16) | (pk.PID & 0xFFFF)); - else if (isShiny) - pk.PID ^= 0x1000_0000; - - var moves = MoveLevelUp.GetEncounterMoves(pk, Level, GameVersion.GO); - pk.Moves = moves; - pk.SetMaximumPPCurrent(moves); - pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; - - pk.HeightScalar = HeightScalar; - pk.WeightScalar = WeightScalar; - - pk.AwakeningSetAllTo(2); - pk.ResetCalculatedValues(); - - return pk; + var height = HeightF * 100f; + var pi = PersonalTable.GG.GetFormEntry(Species, Form); + var avgHeight = pi.Height; + return PB7.GetHeightScalar(height, avgHeight); } } -} \ No newline at end of file + + public byte WeightScalar + { + get + { + var height = HeightF * 100f; + var weight = WeightF * 10f; + var pi = PersonalTable.GG.GetFormEntry(Species, Form); + var avgHeight = pi.Height; + var avgWeight = pi.Weight; + return PB7.GetWeightScalar(height, weight, avgHeight, avgWeight); + } + } + + public int IV_HP => ReadInt32LittleEndian(Data.AsSpan(0x50)); + public int IV_ATK => ReadInt32LittleEndian(Data.AsSpan(0x54)); + public int IV_DEF => ReadInt32LittleEndian(Data.AsSpan(0x58)); + public int Date => ReadInt32LittleEndian(Data.AsSpan(0x5C)); // ####.##.## YYYY.MM.DD + public int Year => Date / 1_00_00; + public int Month => (Date / 1_00) % 1_00; + public int Day => Date % 1_00; + + public int Gender => Data[0x70] - 1; // M=1, F=2, G=3 ;; shift down by 1. + + public int Form => Data[0x72]; + public bool IsShiny => Data[0x73] == 1; + + // https://bulbapedia.bulbagarden.net/wiki/List_of_moves_in_Pok%C3%A9mon_GO + public int Move1 => ReadInt32LittleEndian(Data.AsSpan(0x74)); // uses Go Indexes + public int Move2 => ReadInt32LittleEndian(Data.AsSpan(0x78)); // uses Go Indexes + + public string GeoCityName => Util.TrimFromZero(Encoding.ASCII.GetString(Data, 0x7C, 0x60)); // dunno length + + public string Nickname => Util.TrimFromZero(Encoding.ASCII.GetString(Data, 0x12D, 0x20)); // dunno length + + public static readonly IReadOnlyList Genders = GameInfo.GenderSymbolASCII; + public string GenderString => (uint) Gender >= Genders.Count ? string.Empty : Genders[Gender]; + public string ShinyString => IsShiny ? "★ " : string.Empty; + public string FormString => Form != 0 ? $"-{Form}" : string.Empty; + private string NickStr => string.IsNullOrWhiteSpace(Nickname) ? SpeciesName.GetSpeciesNameGeneration(Species, (int)LanguageID.English, 7) : Nickname; + public string FileName => $"{FileNameWithoutExtension}.gp1"; + + public string FileNameWithoutExtension + { + get + { + string form = Form > 0 ? $"-{Form:00}" : string.Empty; + string star = IsShiny ? " ★" : string.Empty; + return $"{Species:000}{form}{star} - {NickStr} - Lv. {Level:00} - {IV_HP:00}.{IV_ATK:00}.{IV_DEF:00} - CP {CP:0000} (Moves {Move1:000}, {Move2:000})"; + } + } + + public string GeoTime => $"Captured in {GeoCityName} by {Username1} on {Year}/{Month:00}/{Day:00}"; + public string StatMove => $"{IV_HP:00}/{IV_ATK:00}/{IV_DEF:00}, CP {CP:0000} (Moves {Move1:000}, {Move2:000})"; + public string Dump(IReadOnlyList speciesNames, int index) => $"{index:000} {Nickname} ({speciesNames[Species]}{FormString} {ShinyString}[{GenderString}]) @ Lv. {Level:00} - {StatMove}, {GeoTime}."; + + public PB7 ConvertToPB7(ITrainerInfo sav) => ConvertToPB7(sav, EncounterCriteria.Unrestricted); + + public PB7 ConvertToPB7(ITrainerInfo sav, EncounterCriteria criteria) + { + var rnd = Util.Rand; + var pk = new PB7 + { + EncryptionConstant = rnd.Rand32(), + PID = rnd.Rand32(), + Version = (int) GameVersion.GO, + Species = Species, + Form = Form, + Met_Location = 50, // Go complex + Met_Year = Year - 2000, + Met_Month = Month, + Met_Day = Day, + CurrentLevel = Level, + Met_Level = Level, + TID = sav.TID, + SID = sav.SID, + OT_Name = sav.OT, + Ball = 4, + Language = sav.Language, + }; + + var nick = Nickname; + if (!string.IsNullOrWhiteSpace(nick)) + { + pk.Nickname = nick; + pk.IsNicknamed = true; + } + else + { + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, sav.Language, 7); + } + + pk.IV_DEF = pk.IV_SPD = (IV_DEF * 2) + 1; + pk.IV_ATK = pk.IV_SPA = (IV_ATK * 2) + 1; + pk.IV_HP = (IV_HP * 2) + 1; + pk.IV_SPE = Util.Rand.Next(32); + + var pi = pk.PersonalInfo; + pk.Gender = criteria.GetGender(Gender, pi); + pk.Nature = (int)criteria.GetNature(Nature.Random); + pk.RefreshAbility(criteria.GetAbilityFromNumber(Ability)); + + bool isShiny = pk.IsShiny; + if (IsShiny && !isShiny) // Force Square + pk.PID = (uint)(((sav.TID ^ sav.SID ^ (pk.PID & 0xFFFF) ^ 0) << 16) | (pk.PID & 0xFFFF)); + else if (isShiny) + pk.PID ^= 0x1000_0000; + + var moves = MoveLevelUp.GetEncounterMoves(pk, Level, GameVersion.GO); + pk.Moves = moves; + pk.SetMaximumPPCurrent(moves); + pk.OT_Friendship = pk.PersonalInfo.BaseFriendship; + + pk.HeightScalar = HeightScalar; + pk.WeightScalar = WeightScalar; + + pk.AwakeningSetAllTo(2); + pk.ResetCalculatedValues(); + + return pk; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GoParkStorage.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GoParkStorage.cs index 3ec85ec55..3efc786dc 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GoParkStorage.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/GoParkStorage.cs @@ -3,61 +3,60 @@ using System.Diagnostics; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class GoParkStorage : SaveBlock, IEnumerable { - public sealed class GoParkStorage : SaveBlock, IEnumerable + public GoParkStorage(SAV7b sav) : base(sav) { - public GoParkStorage(SAV7b sav) : base(sav) + Offset = sav.Blocks.GetBlockOffset(BelugaBlockIndex.GoParkEntities); + } + + public const int SlotsPerArea = 50; + public const int Areas = 20; + public const int Count = SlotsPerArea * Areas; // 1000 + + public GP1 this[int index] + { + get { - Offset = sav.Blocks.GetBlockOffset(BelugaBlockIndex.GoParkEntities); + Debug.Assert(index < Count); + return GP1.FromData(Data, Offset + (GP1.SIZE * index)); } - - public const int SlotsPerArea = 50; - public const int Areas = 20; - public const int Count = SlotsPerArea * Areas; // 1000 - - public GP1 this[int index] + set { - get - { - Debug.Assert(index < Count); - return GP1.FromData(Data, Offset + (GP1.SIZE * index)); - } - set - { - Debug.Assert(index < Count); - value.WriteTo(Data, Offset + (GP1.SIZE * index)); - } - } - - public GP1[] GetAllEntities() - { - var value = new GP1[Count]; - for (int i = 0; i < value.Length; i++) - value[i] = this[i]; - return value; - } - - public void SetAllEntities(IReadOnlyList value) - { - Debug.Assert(value.Count == Count); - for (int i = 0; i < value.Count; i++) - this[i] = value[i]; - } - - public IEnumerable DumpAll(IReadOnlyList speciesNames) => GetAllEntities() - .Select((z, i) => (Entry: z, Index: i)) - .Where(z => z.Entry.Species > 0) - .Select(z => z.Entry.Dump(speciesNames, z.Index)); - - public IEnumerator GetEnumerator() => (IEnumerator)GetAllEntities().GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetAllEntities().GetEnumerator(); - - public void DeleteAll() - { - var blank = new GP1(); - for (int i = 0; i < Count; i++) - this[i] = blank; + Debug.Assert(index < Count); + value.WriteTo(Data, Offset + (GP1.SIZE * index)); } } -} \ No newline at end of file + + public GP1[] GetAllEntities() + { + var value = new GP1[Count]; + for (int i = 0; i < value.Length; i++) + value[i] = this[i]; + return value; + } + + public void SetAllEntities(IReadOnlyList value) + { + Debug.Assert(value.Count == Count); + for (int i = 0; i < value.Count; i++) + this[i] = value[i]; + } + + public IEnumerable DumpAll(IReadOnlyList speciesNames) => GetAllEntities() + .Select((z, i) => (Entry: z, Index: i)) + .Where(z => z.Entry.Species > 0) + .Select(z => z.Entry.Dump(speciesNames, z.Index)); + + public IEnumerator GetEnumerator() => (IEnumerator)GetAllEntities().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetAllEntities().GetEnumerator(); + + public void DeleteAll() + { + var blank = new GP1(); + for (int i = 0; i < Count; i++) + this[i] = blank; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/IEventWork.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/IEventWork.cs index 32949346e..3e6c3b876 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/IEventWork.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/IEventWork.cs @@ -1,37 +1,36 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IEventVar : IEventFlag, IEventWork { - public interface IEventVar : IEventFlag, IEventWork - { - T GetWork(EventVarType type, int index); - void SetWork(EventVarType type, int index, T value); + T GetWork(EventVarType type, int index); + void SetWork(EventVarType type, int index, T value); - bool GetFlag(EventVarType type, int index); - void SetFlag(EventVarType type, int index, bool value = true); + bool GetFlag(EventVarType type, int index); + void SetFlag(EventVarType type, int index, bool value = true); - EventVarType GetFlagType(int index, out int subIndex); - EventVarType GetWorkType(int index, out int subIndex); - int GetFlagRawIndex(EventVarType type, int index); - int GetWorkRawIndex(EventVarType type, int index); - } - - public interface IEventFlag - { - bool GetFlag(int index); - void SetFlag(int index, bool value = true); - int CountFlag { get; } - } - - public interface ISystemFlag - { - bool GetSystemFlag(int index); - void SetSystemFlag(int index, bool value = true); - int CountSystem { get; } - } - - public interface IEventWork - { - T GetWork(int index); - void SetWork(int index, T value); - int CountWork { get; } - } + EventVarType GetFlagType(int index, out int subIndex); + EventVarType GetWorkType(int index, out int subIndex); + int GetFlagRawIndex(EventVarType type, int index); + int GetWorkRawIndex(EventVarType type, int index); +} + +public interface IEventFlag +{ + bool GetFlag(int index); + void SetFlag(int index, bool value = true); + int CountFlag { get; } +} + +public interface ISystemFlag +{ + bool GetSystemFlag(int index); + void SetSystemFlag(int index, bool value = true); + int CountSystem { get; } +} + +public interface IEventWork +{ + T GetWork(int index); + void SetWork(int index, T value); + int CountWork { get; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/Misc7b.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/Misc7b.cs index bf75a0a46..4a03844dd 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/Misc7b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/Misc7b.cs @@ -1,24 +1,23 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Misc7b : SaveBlock { - public sealed class Misc7b : SaveBlock + public Misc7b(SAV7b sav, int offset) : base(sav) => Offset = offset; + + public uint Money { - public Misc7b(SAV7b sav, int offset) : base(sav) => Offset = offset; + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 4)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 4), value); + } - public uint Money - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 4)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 4), value); - } + private Span Rival_Trash => Data.AsSpan(Offset + 0x200, 0x1A); - private Span Rival_Trash => Data.AsSpan(Offset + 0x200, 0x1A); - - public string Rival - { - get => SAV.GetString(Rival_Trash); - set => SAV.SetString(Rival_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); - } + public string Rival + { + get => SAV.GetString(Rival_Trash); + set => SAV.SetString(Rival_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/MyItem7b.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/MyItem7b.cs index a04720eff..69511e473 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/MyItem7b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/MyItem7b.cs @@ -1,41 +1,40 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyItem7b : MyItem { - public sealed class MyItem7b : MyItem + private const int Medicine = 0x0000; // 0 + private const int TM = 0x00F0; // 1 + private const int Candy = 0x02A0; // 2 + private const int PowerUp = 0x05C0; // 3 + private const int Catching = 0x0818; // 4 + private const int Battle = 0x08E0; // 5 + private const int Key = 0x0B38; // 6 + + public MyItem7b(SAV7b sav, int offset) : base(sav) => Offset = offset; + + public override IReadOnlyList Inventory { - private const int Medicine = 0x0000; // 0 - private const int TM = 0x00F0; // 1 - private const int Candy = 0x02A0; // 2 - private const int PowerUp = 0x05C0; // 3 - private const int Catching = 0x0818; // 4 - private const int Battle = 0x08E0; // 5 - private const int Key = 0x0B38; // 6 - - public MyItem7b(SAV7b sav, int offset) : base(sav) => Offset = offset; - - public override IReadOnlyList Inventory + get { - get + var pouch = new[] { - var pouch = new[] - { - new InventoryPouch7b(InventoryType.Medicine, Legal.Pouch_Medicine_GG, 999, Medicine, PouchSize7b.Medicine), - new InventoryPouch7b(InventoryType.TMHMs, Legal.Pouch_TM_GG, 1, TM, PouchSize7b.TM), - new InventoryPouch7b(InventoryType.Balls, Legal.Pouch_Catching_GG, 999, Catching, PouchSize7b.Catching), - new InventoryPouch7b(InventoryType.Items, Legal.Pouch_Regular_GG, 999, Key, PouchSize7b.Items), - new InventoryPouch7b(InventoryType.BattleItems, Legal.Pouch_Battle_GG, 999, Battle, PouchSize7b.Battle), - new InventoryPouch7b(InventoryType.ZCrystals, Legal.Pouch_PowerUp_GG, 999, PowerUp, PouchSize7b.PowerUp), - new InventoryPouch7b(InventoryType.Candy, Legal.Pouch_Candy_GG, 999, Candy, PouchSize7b.Candy), - }; - return pouch.LoadAll(Data); - } - set - { - foreach (var p in value) - ((InventoryPouch7b)p).SanitizeCounts(); - value.SaveAll(Data); - } + new InventoryPouch7b(InventoryType.Medicine, Legal.Pouch_Medicine_GG, 999, Medicine, PouchSize7b.Medicine), + new InventoryPouch7b(InventoryType.TMHMs, Legal.Pouch_TM_GG, 1, TM, PouchSize7b.TM), + new InventoryPouch7b(InventoryType.Balls, Legal.Pouch_Catching_GG, 999, Catching, PouchSize7b.Catching), + new InventoryPouch7b(InventoryType.Items, Legal.Pouch_Regular_GG, 999, Key, PouchSize7b.Items), + new InventoryPouch7b(InventoryType.BattleItems, Legal.Pouch_Battle_GG, 999, Battle, PouchSize7b.Battle), + new InventoryPouch7b(InventoryType.ZCrystals, Legal.Pouch_PowerUp_GG, 999, PowerUp, PouchSize7b.PowerUp), + new InventoryPouch7b(InventoryType.Candy, Legal.Pouch_Candy_GG, 999, Candy, PouchSize7b.Candy), + }; + return pouch.LoadAll(Data); + } + set + { + foreach (var p in value) + ((InventoryPouch7b)p).SanitizeCounts(); + value.SaveAll(Data); } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/MyStatus7b.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/MyStatus7b.cs index 63360256d..3d583df21 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/MyStatus7b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/MyStatus7b.cs @@ -1,99 +1,98 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyStatus7b : SaveBlock { - public sealed class MyStatus7b : SaveBlock + public MyStatus7b(SAV7b sav, int offset) : base(sav) => Offset = offset; + + // Player Information + + // idb uint8 offset: 0x58 + + public int TID { - public MyStatus7b(SAV7b sav, int offset) : base(sav) => Offset = offset; + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), (ushort)value); + } - // Player Information + public int SID + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 2)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 2), (ushort)value); + } - // idb uint8 offset: 0x58 + public int Game + { + get => Data[Offset + 4]; + set => Data[Offset + 4] = (byte)value; + } - public int TID + public int Gender + { + get => Data[Offset + 5]; + set => Data[Offset + 5] = OverworldGender = (byte)value; + } + + public const int GameSyncIDSize = 16; // 8 bytes + + public string GameSyncID + { + get => Util.GetHexStringFromBytes(Data, Offset + 0x10, GameSyncIDSize / 2); + set { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), (ushort)value); - } + if (value.Length > 16) + throw new ArgumentException(nameof(value)); - public int SID - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 2)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 2), (ushort)value); - } - - public int Game - { - get => Data[Offset + 4]; - set => Data[Offset + 4] = (byte)value; - } - - public int Gender - { - get => Data[Offset + 5]; - set => Data[Offset + 5] = OverworldGender = (byte)value; - } - - public const int GameSyncIDSize = 16; // 8 bytes - - public string GameSyncID - { - get => Util.GetHexStringFromBytes(Data, Offset + 0x10, GameSyncIDSize / 2); - set - { - if (value.Length > 16) - throw new ArgumentException(nameof(value)); - - var data = Util.GetBytesFromHexString(value); - SAV.SetData(data, Offset + 0x10); - } - } - - public int Language - { - get => Data[Offset + 0x35]; - set => Data[Offset + 0x35] = (byte)value; - } - - private Span OT_Trash => Data.AsSpan(Offset + 0x38, 0x1A); - - public string OT - { - get => SAV.GetString(OT_Trash); - set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); - } - - // The value here corresponds to a Trainer Class string (ranging from 000 to 383, use pkNX to get a full list). - public byte TrainerClassIndex - { - get => Data[Offset + 0x076]; - set => Data[Offset + 0x076] = value; - } - - public StarterChoice7b StarterChoice - { - get => (StarterChoice7b)Data[Offset + 0x0B8]; - set => Data[Offset + 0x0B8] = (byte)value; - } - - public byte StarterGender - { - get => Data[Offset + 0x0B9]; - set => Data[Offset + 0x0B9] = value; - } - - public byte OverworldGender // Model - { - get => Data[Offset + 0x108]; - set => Data[Offset + 0x108] = value; - } - - public enum StarterChoice7b : byte - { - None = 0, - Pikachu = 1, - Eevee = 2, + var data = Util.GetBytesFromHexString(value); + SAV.SetData(data, Offset + 0x10); } } + + public int Language + { + get => Data[Offset + 0x35]; + set => Data[Offset + 0x35] = (byte)value; + } + + private Span OT_Trash => Data.AsSpan(Offset + 0x38, 0x1A); + + public string OT + { + get => SAV.GetString(OT_Trash); + set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); + } + + // The value here corresponds to a Trainer Class string (ranging from 000 to 383, use pkNX to get a full list). + public byte TrainerClassIndex + { + get => Data[Offset + 0x076]; + set => Data[Offset + 0x076] = value; + } + + public StarterChoice7b StarterChoice + { + get => (StarterChoice7b)Data[Offset + 0x0B8]; + set => Data[Offset + 0x0B8] = (byte)value; + } + + public byte StarterGender + { + get => Data[Offset + 0x0B9]; + set => Data[Offset + 0x0B9] = value; + } + + public byte OverworldGender // Model + { + get => Data[Offset + 0x108]; + set => Data[Offset + 0x108] = value; + } + + public enum StarterChoice7b : byte + { + None = 0, + Pikachu = 1, + Eevee = 2, + } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PlayTime7b.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PlayTime7b.cs index fa3a8fbaa..0e8575de0 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PlayTime7b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PlayTime7b.cs @@ -1,65 +1,64 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class PlayTime7b : SaveBlock { - public sealed class PlayTime7b : SaveBlock + public PlayTime7b(SAV7b sav, int offset) : base(sav) => Offset = offset; + + public int PlayedHours { - public PlayTime7b(SAV7b sav, int offset) : base(sav) => Offset = offset; + get => ReadUInt16LittleEndian(Data.AsSpan(Offset)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset), (ushort)value); + } - public int PlayedHours + public int PlayedMinutes + { + get => Data[Offset + 2]; + set => Data[Offset + 2] = (byte)value; + } + + public int PlayedSeconds + { + get => Data[Offset + 3]; + set => Data[Offset + 3] = (byte)value; + } + + private uint LastSaved { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } + private int LastSavedYear { get => (int)(LastSaved & 0xFFF) + 1900; set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)(value - 1900); } + private int LastSavedMonth { get => (int)((LastSaved >> 12) & 0xF) + 1; set => LastSaved = (LastSaved & 0xFFFF0FFF) | (((uint)(value - 1) & 0xF) << 12); } + private int LastSavedDay { get => (int)((LastSaved >> 16) & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | (((uint)value & 0x1F) << 16); } + private int LastSavedHour { get => (int)((LastSaved >> 21) & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | (((uint)value & 0x1F) << 21); } + private int LastSavedMinute { get => (int)((LastSaved >> 26) & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | (((uint)value & 0x3F) << 26); } + public string LastSavedTime => $"{LastSavedYear:0000}-{LastSavedMonth:00}-{LastSavedDay:00} {LastSavedHour:00}ː{LastSavedMinute:00}"; // not : + + public DateTime? LastSavedDate + { + get => !DateUtil.IsDateValid(LastSavedYear, LastSavedMonth, LastSavedDay) + ? null + : new DateTime(LastSavedYear, LastSavedMonth, LastSavedDay, LastSavedHour, LastSavedMinute, 0); + set { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset), (ushort)value); - } - - public int PlayedMinutes - { - get => Data[Offset + 2]; - set => Data[Offset + 2] = (byte)value; - } - - public int PlayedSeconds - { - get => Data[Offset + 3]; - set => Data[Offset + 3] = (byte)value; - } - - private uint LastSaved { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } - private int LastSavedYear { get => (int)(LastSaved & 0xFFF) + 1900; set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)(value - 1900); } - private int LastSavedMonth { get => (int)(LastSaved >> 12 & 0xF) + 1; set => LastSaved = (LastSaved & 0xFFFF0FFF) | ((uint)(value - 1) & 0xF) << 12; } - private int LastSavedDay { get => (int)(LastSaved >> 16 & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | ((uint)value & 0x1F) << 16; } - private int LastSavedHour { get => (int)(LastSaved >> 21 & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | ((uint)value & 0x1F) << 21; } - private int LastSavedMinute { get => (int)(LastSaved >> 26 & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | ((uint)value & 0x3F) << 26; } - public string LastSavedTime => $"{LastSavedYear:0000}-{LastSavedMonth:00}-{LastSavedDay:00} {LastSavedHour:00}ː{LastSavedMinute:00}"; // not : - - public DateTime? LastSavedDate - { - get => !DateUtil.IsDateValid(LastSavedYear, LastSavedMonth, LastSavedDay) - ? null - : new DateTime(LastSavedYear, LastSavedMonth, LastSavedDay, LastSavedHour, LastSavedMinute, 0); - set + // Only update the properties if a value is provided. + if (value.HasValue) { - // Only update the properties if a value is provided. - if (value.HasValue) - { - var dt = value.Value; - LastSavedYear = dt.Year; - LastSavedMonth = dt.Month; - LastSavedDay = dt.Day; - LastSavedHour = dt.Hour; - LastSavedMinute = dt.Minute; - } - else // Clear the date. - { - // If code tries to access MetDate again, null will be returned. - LastSavedYear = 0; - LastSavedMonth = 0; - LastSavedDay = 0; - LastSavedHour = 0; - LastSavedMinute = 0; - } + var dt = value.Value; + LastSavedYear = dt.Year; + LastSavedMonth = dt.Month; + LastSavedDay = dt.Day; + LastSavedHour = dt.Hour; + LastSavedMinute = dt.Minute; + } + else // Clear the date. + { + // If code tries to access MetDate again, null will be returned. + LastSavedYear = 0; + LastSavedMonth = 0; + LastSavedDay = 0; + LastSavedHour = 0; + LastSavedMinute = 0; } } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PokeListHeader.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PokeListHeader.cs index 9f14e0f08..987c8bdb8 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PokeListHeader.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PokeListHeader.cs @@ -4,158 +4,157 @@ using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Header information for the stored data. +/// +/// +/// This block simply contains the following: +/// u16 Party Pointers * 6: Indicates which index occupies this slot. if nothing in slot. +/// u16 Starter Pointer: Indicates which index is the starter. if no starter yet. +/// u16 List Count: Points to the next empty slot, and indicates how many slots are stored in the list. +/// +public sealed class PokeListHeader : SaveBlock { /// - /// Header information for the stored data. + /// Raw representation of data, cast to ushort[]. /// - /// - /// This block simply contains the following: - /// u16 Party Pointers * 6: Indicates which index occupies this slot. if nothing in slot. - /// u16 Starter Pointer: Indicates which index is the starter. if no starter yet. - /// u16 List Count: Points to the next empty slot, and indicates how many slots are stored in the list. - /// - public sealed class PokeListHeader : SaveBlock + internal readonly int[] PokeListInfo; + + public const int STARTER = 6; + private const int COUNT = 7; + private const int MAX_SLOTS = 1000; + private const int SLOT_EMPTY = 1001; + + public PokeListHeader(SAV7b sav, int offset) : base(sav) { - /// - /// Raw representation of data, cast to ushort[]. - /// - internal readonly int[] PokeListInfo; - - public const int STARTER = 6; - private const int COUNT = 7; - private const int MAX_SLOTS = 1000; - private const int SLOT_EMPTY = 1001; - - public PokeListHeader(SAV7b sav, int offset) : base(sav) + Offset = offset; + var info = PokeListInfo = LoadPointerData(); + if (!sav.State.Exportable) { - Offset = offset; - var info = PokeListInfo = LoadPointerData(); - if (!sav.State.Exportable) + for (int i = 0; i < COUNT; i++) + info[i] = SLOT_EMPTY; + } + else + { + for (int i = 0; i < 6; i++) { - for (int i = 0; i < COUNT; i++) - info[i] = SLOT_EMPTY; + if (info[i] < MAX_SLOTS) + ++_partyCount; } - else - { - for (int i = 0; i < 6; i++) - { - if (info[i] < MAX_SLOTS) - ++_partyCount; - } - } - } - - private int _partyCount; - - public int PartyCount - { - get => _partyCount; - set - { - if (_partyCount > value) - { - for (int i = _partyCount; i < value; i++) - ClearPartySlot(i); - } - _partyCount = value; - } - } - - public bool ClearPartySlot(int slot) - { - if (slot >= 6 || PartyCount <= 1) - return false; - - if (slot > PartyCount) - { - slot = PartyCount; - } - else if (slot != PartyCount - 1) - { - int countShiftDown = PartyCount - 1 - slot; - Array.Copy(PokeListInfo, slot + 1, PokeListInfo, slot, countShiftDown); - slot = PartyCount - 1; - } - PokeListInfo[slot] = SLOT_EMPTY; - PartyCount--; - return true; - } - - public void RemoveStarter() => StarterIndex = SLOT_EMPTY; - - public int StarterIndex - { - get => PokeListInfo[STARTER]; - set - { - if ((ushort)value > 1000 && value != SLOT_EMPTY) - throw new ArgumentOutOfRangeException(nameof(value)); - PokeListInfo[STARTER] = (ushort)value; - } - } - - public int Count - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + (COUNT * 2))); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + (COUNT * 2)), (ushort)value); - } - - private int[] LoadPointerData() - { - var span = Data.AsSpan(Offset); - var list = new int[7]; - for (int i = 0; i < list.Length; i++) - list[i] = ReadUInt16LittleEndian(span[(i * 2)..]); - return list; - } - - private void SetPointerData(IList vals) - { - var span = Data.AsSpan(Offset); - for (int i = 0; i < vals.Count; i++) - WriteUInt16LittleEndian(span[(i*2)..], (ushort)vals[i]); - vals.CopyTo(PokeListInfo); - } - - public int GetPartyOffset(int slot) - { - if ((uint)slot >= 6) - throw new ArgumentOutOfRangeException(nameof(slot) + " expected to be < 6."); - int position = PokeListInfo[slot]; - return SAV.GetBoxSlotOffset(position); - } - - private int GetPartyIndex(int slotIndex) - { - if ((uint) slotIndex >= MAX_SLOTS) - return MAX_SLOTS; - var index = Array.IndexOf(PokeListInfo, slotIndex); - return index >= 0 ? index : MAX_SLOTS; - } - - public bool IsParty(int slotIndex) => GetPartyIndex(slotIndex) != MAX_SLOTS; - - public bool CompressStorage() - { - // Box Data is stored as a list, instead of an array. Empty interstitials are not legal. - // Fix stored slots! - var arr = PokeListInfo.Slice(0, 7); - var result = SAV.CompressStorage(out int count, arr); - Debug.Assert(count <= MAX_SLOTS); - Count = count; - if (StarterIndex > count && StarterIndex != SLOT_EMPTY) - { - // uh oh, we lost the starter! might have been moved out of its proper slot incorrectly. - var species = SAV.Version == GameVersion.GP ? 25 : 133; - int index = Array.FindIndex(SAV.BoxData.ToArray(), z => z.Species == species && z.Form != 0); - if (index >= 0) - arr[6] = index; - } - arr.CopyTo(PokeListInfo); - - SetPointerData(PokeListInfo); - return result; } } + + private int _partyCount; + + public int PartyCount + { + get => _partyCount; + set + { + if (_partyCount > value) + { + for (int i = _partyCount; i < value; i++) + ClearPartySlot(i); + } + _partyCount = value; + } + } + + public bool ClearPartySlot(int slot) + { + if (slot >= 6 || PartyCount <= 1) + return false; + + if (slot > PartyCount) + { + slot = PartyCount; + } + else if (slot != PartyCount - 1) + { + int countShiftDown = PartyCount - 1 - slot; + Array.Copy(PokeListInfo, slot + 1, PokeListInfo, slot, countShiftDown); + slot = PartyCount - 1; + } + PokeListInfo[slot] = SLOT_EMPTY; + PartyCount--; + return true; + } + + public void RemoveStarter() => StarterIndex = SLOT_EMPTY; + + public int StarterIndex + { + get => PokeListInfo[STARTER]; + set + { + if ((ushort)value > 1000 && value != SLOT_EMPTY) + throw new ArgumentOutOfRangeException(nameof(value)); + PokeListInfo[STARTER] = (ushort)value; + } + } + + public int Count + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + (COUNT * 2))); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + (COUNT * 2)), (ushort)value); + } + + private int[] LoadPointerData() + { + var span = Data.AsSpan(Offset); + var list = new int[7]; + for (int i = 0; i < list.Length; i++) + list[i] = ReadUInt16LittleEndian(span[(i * 2)..]); + return list; + } + + private void SetPointerData(IList vals) + { + var span = Data.AsSpan(Offset); + for (int i = 0; i < vals.Count; i++) + WriteUInt16LittleEndian(span[(i*2)..], (ushort)vals[i]); + vals.CopyTo(PokeListInfo); + } + + public int GetPartyOffset(int slot) + { + if ((uint)slot >= 6) + throw new ArgumentOutOfRangeException(nameof(slot) + " expected to be < 6."); + int position = PokeListInfo[slot]; + return SAV.GetBoxSlotOffset(position); + } + + private int GetPartyIndex(int slotIndex) + { + if ((uint) slotIndex >= MAX_SLOTS) + return MAX_SLOTS; + var index = Array.IndexOf(PokeListInfo, slotIndex); + return index >= 0 ? index : MAX_SLOTS; + } + + public bool IsParty(int slotIndex) => GetPartyIndex(slotIndex) != MAX_SLOTS; + + public bool CompressStorage() + { + // Box Data is stored as a list, instead of an array. Empty interstitials are not legal. + // Fix stored slots! + var arr = PokeListInfo.Slice(0, 7); + var result = SAV.CompressStorage(out int count, arr); + Debug.Assert(count <= MAX_SLOTS); + Count = count; + if (StarterIndex > count && StarterIndex != SLOT_EMPTY) + { + // uh oh, we lost the starter! might have been moved out of its proper slot incorrectly. + var species = SAV.Version == GameVersion.GP ? 25 : 133; + int index = Array.FindIndex(SAV.BoxData.ToArray(), z => z.Species == species && z.Form != 0); + if (index >= 0) + arr[6] = index; + } + arr.CopyTo(PokeListInfo); + + SetPointerData(PokeListInfo); + return result; + } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PouchSize7b.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PouchSize7b.cs index fb4ebcf81..b90f67c43 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PouchSize7b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/PouchSize7b.cs @@ -1,40 +1,39 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class PouchSize7b { - public static class PouchSize7b - { - /// - /// Pouch0 Item Max Capacity - /// - public const int Medicine = 60; + /// + /// Pouch0 Item Max Capacity + /// + public const int Medicine = 60; - /// - /// Pouch1 Item Max Capacity - /// - public const int TM = 108; + /// + /// Pouch1 Item Max Capacity + /// + public const int TM = 108; - /// - /// Pouch2 Item Max Capacity - /// - public const int Candy = 200; + /// + /// Pouch2 Item Max Capacity + /// + public const int Candy = 200; - /// - /// Pouch3 Item Max Capacity - /// - public const int PowerUp = 150; + /// + /// Pouch3 Item Max Capacity + /// + public const int PowerUp = 150; - /// - /// Pouch4 Item Max Capacity - /// - public const int Catching = 50; + /// + /// Pouch4 Item Max Capacity + /// + public const int Catching = 50; - /// - /// Pouch5 Item Max Capacity - /// - public const int Battle = 150; + /// + /// Pouch5 Item Max Capacity + /// + public const int Battle = 150; - /// - /// Pouch6 Item Max Capacity - /// - public const int Items = 150; - } -} \ No newline at end of file + /// + /// Pouch6 Item Max Capacity + /// + public const int Items = 150; +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/WB7Records.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/WB7Records.cs index 796517b4d..2ecd568f6 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/WB7Records.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/WB7Records.cs @@ -1,87 +1,86 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class WB7Records : SaveBlock { - public sealed class WB7Records : SaveBlock + public WB7Records(SAV7b sav, int offset) : base(sav) => Offset = offset; + + private const int RecordMax = 10; // 0xE90 > (0x140 * 0xA = 0xC80), not sure what final 0x210 bytes are used for + private const int FlagCountMax = 0x1C00; // (7168) end of the block? + + private int FlagStart => Offset + (RecordMax * WR7.Size); + + private int GetRecordOffset(int index) { - public WB7Records(SAV7b sav, int offset) : base(sav) => Offset = offset; + if (index >= RecordMax) + throw new ArgumentOutOfRangeException(nameof(index)); - private const int RecordMax = 10; // 0xE90 > (0x140 * 0xA = 0xC80), not sure what final 0x210 bytes are used for - private const int FlagCountMax = 0x1C00; // (7168) end of the block? + return Offset + (index * WR7.Size); + } - private int FlagStart => Offset + (RecordMax * WR7.Size); + private int GetFlagOffset(int flag) + { + if (flag >= FlagCountMax) + throw new ArgumentOutOfRangeException(nameof(flag)); + return FlagStart + (flag / 8); + } - private int GetRecordOffset(int index) - { - if (index >= RecordMax) - throw new ArgumentOutOfRangeException(nameof(index)); + public WR7 GetRecord(int index) + { + int ofs = GetRecordOffset(index); + byte[] data = Data.AsSpan(ofs, WR7.Size).ToArray(); + return new WR7(data); + } - return Offset + (index * WR7.Size); - } + public void SetRecord(WR7 record, int index) + { + int ofs = GetRecordOffset(index); + record.Data.CopyTo(Data, ofs); + } - private int GetFlagOffset(int flag) - { - if (flag >= FlagCountMax) - throw new ArgumentOutOfRangeException(nameof(flag)); - return FlagStart + (flag / 8); - } + public WR7[] GetRecords() + { + var arr = new WR7[RecordMax]; + for (int i = 0; i < arr.Length; i++) + arr[i] = GetRecord(i); + return arr; + } - public WR7 GetRecord(int index) - { - int ofs = GetRecordOffset(index); - byte[] data = Data.AsSpan(ofs, WR7.Size).ToArray(); - return new WR7(data); - } + public void SetRecords(WR7[] value) + { + for (int i = 0; i < value.Length; i++) + SetRecord(value[i], i); + } - public void SetRecord(WR7 record, int index) - { - int ofs = GetRecordOffset(index); - record.Data.CopyTo(Data, ofs); - } + public bool GetFlag(int flag) + { + int ofs = GetFlagOffset(flag); + var mask = 1 << (flag & 7); + return (Data[ofs] & mask) != 0; + } - public WR7[] GetRecords() - { - var arr = new WR7[RecordMax]; - for (int i = 0; i < arr.Length; i++) - arr[i] = GetRecord(i); - return arr; - } + public void SetFlag(int flag, bool value) + { + int ofs = GetFlagOffset(flag); + var mask = 1 << (flag & 7); + if (value) + Data[ofs] |= (byte)mask; + else + Data[ofs] &= (byte)~mask; + } - public void SetRecords(WR7[] value) - { - for (int i = 0; i < value.Length; i++) - SetRecord(value[i], i); - } + public bool[] GetFlags() + { + var value = new bool[FlagCountMax]; + for (int i = 0; i < value.Length; i++) + value[i] = GetFlag(i); + return value; + } - public bool GetFlag(int flag) - { - int ofs = GetFlagOffset(flag); - var mask = 1 << (flag & 7); - return (Data[ofs] & mask) != 0; - } - - public void SetFlag(int flag, bool value) - { - int ofs = GetFlagOffset(flag); - var mask = 1 << (flag & 7); - if (value) - Data[ofs] |= (byte)mask; - else - Data[ofs] &= (byte)~mask; - } - - public bool[] GetFlags() - { - var value = new bool[FlagCountMax]; - for (int i = 0; i < value.Length; i++) - value[i] = GetFlag(i); - return value; - } - - public void SetFlags(bool[] value) - { - for (int i = 0; i < value.Length; i++) - SetFlag(i, value[i]); - } + public void SetFlags(bool[] value) + { + for (int i = 0; i < value.Length; i++) + SetFlag(i, value[i]); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/WR7GiftType.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/WR7GiftType.cs index e46fe9746..c7c78388d 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/WR7GiftType.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/WR7GiftType.cs @@ -1,9 +1,8 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum WR7GiftType : byte { - public enum WR7GiftType : byte - { - None = 0, - Pokemon = 1, - Item = 2, - } -} \ No newline at end of file + None = 0, + Pokemon = 1, + Item = 2, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/Misc7.cs b/PKHeX.Core/Saves/Substructures/Gen7/Misc7.cs index a3b292c86..f32a33ab6 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/Misc7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/Misc7.cs @@ -1,75 +1,74 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Misc7 : SaveBlock { - public sealed class Misc7 : SaveBlock + public Misc7(SAV7 sav, int offset) : base(sav) => Offset = offset; + + public uint Money { - public Misc7(SAV7 sav, int offset) : base(sav) => Offset = offset; - - public uint Money + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x4)); + set { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x4)); - set - { - if (value > 9_999_999) - value = 9_999_999; - WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); - } - } - - public uint Stamps - { - get => (ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x08)) << 13) >> 17; // 15 stamps; discard top13, lowest4 - set - { - uint flags = ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x08)) & 0xFFF8000F; - flags |= (value & 0x7FFF) << 4; - WriteUInt32LittleEndian(SAV.Data.AsSpan(Offset + 0x08), flags); - } - } - - public uint BP - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x11C)); - set - { - if (value > 9999) - value = 9999; - WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x11C), value); - } - } - - public int Vivillon - { - get => Data[Offset + 0x130] & 0x1F; - set => Data[Offset + 0x130] = (byte)((Data[Offset + 0x130] & ~0x1F) | (value & 0x1F)); - } - - public uint StarterEncryptionConstant - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x148)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x148), value); - } - - public int DaysFromRefreshed - { - get => Data[Offset + 0x123]; - set => Data[Offset + 0x123] = (byte)value; - } - - public int GetSurfScore(int recordID) - { - if ((uint)recordID >= 4) - recordID = 0; - return ReadInt32LittleEndian(Data.AsSpan(Offset + 0x138 + (4 * recordID))); - } - - public void SetSurfScore(int recordID, int score) - { - if ((uint)recordID >= 4) - recordID = 0; - WriteInt32LittleEndian(Data.AsSpan(Offset + 0x138 + (4 * recordID)), score); + if (value > 9_999_999) + value = 9_999_999; + WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } } + + public uint Stamps + { + get => (ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x08)) << 13) >> 17; // 15 stamps; discard top13, lowest4 + set + { + uint flags = ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x08)) & 0xFFF8000F; + flags |= (value & 0x7FFF) << 4; + WriteUInt32LittleEndian(SAV.Data.AsSpan(Offset + 0x08), flags); + } + } + + public uint BP + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x11C)); + set + { + if (value > 9999) + value = 9999; + WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x11C), value); + } + } + + public int Vivillon + { + get => Data[Offset + 0x130] & 0x1F; + set => Data[Offset + 0x130] = (byte)((Data[Offset + 0x130] & ~0x1F) | (value & 0x1F)); + } + + public uint StarterEncryptionConstant + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x148)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x148), value); + } + + public int DaysFromRefreshed + { + get => Data[Offset + 0x123]; + set => Data[Offset + 0x123] = (byte)value; + } + + public int GetSurfScore(int recordID) + { + if ((uint)recordID >= 4) + recordID = 0; + return ReadInt32LittleEndian(Data.AsSpan(Offset + 0x138 + (4 * recordID))); + } + + public void SetSurfScore(int recordID, int score) + { + if ((uint)recordID >= 4) + recordID = 0; + WriteInt32LittleEndian(Data.AsSpan(Offset + 0x138 + (4 * recordID)), score); + } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/MyItem7SM.cs b/PKHeX.Core/Saves/Substructures/Gen7/MyItem7SM.cs index 3154e295e..71dadde09 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/MyItem7SM.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/MyItem7SM.cs @@ -1,34 +1,33 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyItem7SM : MyItem { - public sealed class MyItem7SM : MyItem + private const int HeldItem = 0; // 430 (Case 0) + private const int KeyItem = HeldItem + (4 * 430); // 184 (Case 4) + private const int TMHM = KeyItem + (4 * 184); // 108 (Case 2) + private const int Medicine = TMHM + (4 * 108); // 64 (Case 1) + private const int Berry = Medicine + (4 * 64); // 72 (Case 3) + private const int ZCrystals = Berry + (4 * 72); // 30 (Case 5) + + public MyItem7SM(SAV7SM SAV, int offset) : base(SAV) => Offset = offset; + + public override IReadOnlyList Inventory { - private const int HeldItem = 0; // 430 (Case 0) - private const int KeyItem = HeldItem + (4 * 430); // 184 (Case 4) - private const int TMHM = KeyItem + (4 * 184); // 108 (Case 2) - private const int Medicine = TMHM + (4 * 108); // 64 (Case 1) - private const int Berry = Medicine + (4 * 64); // 72 (Case 3) - private const int ZCrystals = Berry + (4 * 72); // 30 (Case 5) - - public MyItem7SM(SAV7SM SAV, int offset) : base(SAV) => Offset = offset; - - public override IReadOnlyList Inventory + get { - get + InventoryPouch[] pouch = { - InventoryPouch[] pouch = - { - new InventoryPouch7(InventoryType.Medicine, Legal.Pouch_Medicine_SM, 999, Offset + Medicine), - new InventoryPouch7(InventoryType.Items, Legal.Pouch_Items_SM, 999, Offset + HeldItem), - new InventoryPouch7(InventoryType.TMHMs, Legal.Pouch_TMHM_SM, 1, Offset + TMHM), - new InventoryPouch7(InventoryType.Berries, Legal.Pouch_Berries_SM, 999, Offset + Berry), - new InventoryPouch7(InventoryType.KeyItems, Legal.Pouch_Key_SM, 1, Offset + KeyItem), - new InventoryPouch7(InventoryType.ZCrystals, Legal.Pouch_ZCrystal_SM, 1, Offset + ZCrystals), - }; - return pouch.LoadAll(Data); - } - set => value.SaveAll(Data); + new InventoryPouch7(InventoryType.Medicine, Legal.Pouch_Medicine_SM, 999, Offset + Medicine), + new InventoryPouch7(InventoryType.Items, Legal.Pouch_Items_SM, 999, Offset + HeldItem), + new InventoryPouch7(InventoryType.TMHMs, Legal.Pouch_TMHM_SM, 1, Offset + TMHM), + new InventoryPouch7(InventoryType.Berries, Legal.Pouch_Berries_SM, 999, Offset + Berry), + new InventoryPouch7(InventoryType.KeyItems, Legal.Pouch_Key_SM, 1, Offset + KeyItem), + new InventoryPouch7(InventoryType.ZCrystals, Legal.Pouch_ZCrystal_SM, 1, Offset + ZCrystals), + }; + return pouch.LoadAll(Data); } + set => value.SaveAll(Data); } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/MyItem7USUM.cs b/PKHeX.Core/Saves/Substructures/Gen7/MyItem7USUM.cs index 8955cc693..5359688f0 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/MyItem7USUM.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/MyItem7USUM.cs @@ -1,36 +1,35 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyItem7USUM : MyItem { - public sealed class MyItem7USUM : MyItem + private const int HeldItem = 0; // 427 (Case 0) + private const int KeyItem = HeldItem + (4 * 427); // 198 (Case 4) + private const int TMHM = KeyItem + (4 * 198); // 108 (Case 2) + private const int Medicine = TMHM + (4 * 108); // 60 (Case 1) + private const int Berry = Medicine + (4 * 60); // 67 (Case 3) + private const int ZCrystals = Berry + (4 * 67); // 35 (Case 5) + private const int BattleItems = ZCrystals + (4 * 35); // 11 (Case 6) + + public MyItem7USUM(SaveFile SAV, int offset) : base(SAV) => Offset = offset; + + public override IReadOnlyList Inventory { - private const int HeldItem = 0; // 427 (Case 0) - private const int KeyItem = HeldItem + (4 * 427); // 198 (Case 4) - private const int TMHM = KeyItem + (4 * 198); // 108 (Case 2) - private const int Medicine = TMHM + (4 * 108); // 60 (Case 1) - private const int Berry = Medicine + (4 * 60); // 67 (Case 3) - private const int ZCrystals = Berry + (4 * 67); // 35 (Case 5) - private const int BattleItems = ZCrystals + (4 * 35); // 11 (Case 6) - - public MyItem7USUM(SaveFile SAV, int offset) : base(SAV) => Offset = offset; - - public override IReadOnlyList Inventory + get { - get + InventoryPouch[] pouch = { - InventoryPouch[] pouch = - { - new InventoryPouch7(InventoryType.Medicine, Legal.Pouch_Medicine_SM, 999, Medicine), - new InventoryPouch7(InventoryType.Items, Legal.Pouch_Items_SM, 999, HeldItem), - new InventoryPouch7(InventoryType.TMHMs, Legal.Pouch_TMHM_SM, 1, TMHM), - new InventoryPouch7(InventoryType.Berries, Legal.Pouch_Berries_SM, 999, Berry), - new InventoryPouch7(InventoryType.KeyItems, Legal.Pouch_Key_USUM, 1, KeyItem), - new InventoryPouch7(InventoryType.ZCrystals, Legal.Pouch_ZCrystal_USUM, 1, ZCrystals), - new InventoryPouch7(InventoryType.BattleItems, Legal.Pouch_Roto_USUM, 999, BattleItems), - }; - return pouch.LoadAll(Data); - } - set => value.SaveAll(Data); + new InventoryPouch7(InventoryType.Medicine, Legal.Pouch_Medicine_SM, 999, Medicine), + new InventoryPouch7(InventoryType.Items, Legal.Pouch_Items_SM, 999, HeldItem), + new InventoryPouch7(InventoryType.TMHMs, Legal.Pouch_TMHM_SM, 1, TMHM), + new InventoryPouch7(InventoryType.Berries, Legal.Pouch_Berries_SM, 999, Berry), + new InventoryPouch7(InventoryType.KeyItems, Legal.Pouch_Key_USUM, 1, KeyItem), + new InventoryPouch7(InventoryType.ZCrystals, Legal.Pouch_ZCrystal_USUM, 1, ZCrystals), + new InventoryPouch7(InventoryType.BattleItems, Legal.Pouch_Roto_USUM, 999, BattleItems), + }; + return pouch.LoadAll(Data); } + set => value.SaveAll(Data); } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/MyStatus7.cs b/PKHeX.Core/Saves/Substructures/Gen7/MyStatus7.cs index 1d617781d..7826639e6 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/MyStatus7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/MyStatus7.cs @@ -1,131 +1,130 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyStatus7 : SaveBlock, IRegionOrigin { - public sealed class MyStatus7 : SaveBlock, IRegionOrigin + public const int GameSyncIDSize = 16; // 64 bits + public const int NexUniqueIDSize = 32; // 128 bits + + public MyStatus7(SAV7 sav, int offset) : base(sav) => Offset = offset; + + public int TID { - public const int GameSyncIDSize = 16; // 64 bits - public const int NexUniqueIDSize = 32; // 128 bits + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), (ushort)value); + } - public MyStatus7(SAV7 sav, int offset) : base(sav) => Offset = offset; + public int SID + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 2)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 2), (ushort)value); + } - public int TID + public int Game + { + get => Data[Offset + 4]; + set => Data[Offset + 4] = (byte)value; + } + + public int Gender + { + get => Data[Offset + 5]; + set => Data[Offset + 5] = (byte)value; + } + + public string GameSyncID + { + get => Util.GetHexStringFromBytes(Data, Offset + 0x10, GameSyncIDSize / 2); + set { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), (ushort)value); - } + if (value.Length != GameSyncIDSize) + throw new ArgumentException(nameof(value)); - public int SID - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 2)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 2), (ushort)value); - } - - public int Game - { - get => Data[Offset + 4]; - set => Data[Offset + 4] = (byte)value; - } - - public int Gender - { - get => Data[Offset + 5]; - set => Data[Offset + 5] = (byte)value; - } - - public string GameSyncID - { - get => Util.GetHexStringFromBytes(Data, Offset + 0x10, GameSyncIDSize / 2); - set - { - if (value.Length != GameSyncIDSize) - throw new ArgumentException(nameof(value)); - - var data = Util.GetBytesFromHexString(value); - SAV.SetData(data, Offset + 0x10); - } - } - - public string NexUniqueID - { - get => Util.GetHexStringFromBytes(Data, Offset + 0x18, NexUniqueIDSize / 2); - set - { - if (value.Length != NexUniqueIDSize) - throw new ArgumentException(nameof(value)); - - var data = Util.GetBytesFromHexString(value); - SAV.SetData(data, Offset + 0x18); - } - } - - public uint FestaID - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x28)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x28), value); - } - - public byte Region - { - get => Data[Offset + 0x2E]; - set => Data[Offset + 0x2E] = value; - } - - public byte Country - { - get => Data[Offset + 0x2F]; - set => Data[Offset + 0x2F] = value; - } - - public byte ConsoleRegion - { - get => Data[Offset + 0x34]; - set => Data[Offset + 0x34] = value; - } - - public int Language - { - get => Data[Offset + 0x35]; - set => Data[Offset + 0x35] = (byte)value; - } - - private Span OT_Trash => Data.AsSpan(Offset + 0x38, 0x1A); - - public string OT - { - get => SAV.GetString(OT_Trash); - set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); - } - - public int DressUpSkinColor - { - get => (Data[Offset + 0x54] >> 2) & 7; - set => Data[Offset + 0x54] = (byte)((Data[Offset + 0x54] & ~(7 << 2)) | (value << 2)); - } - - public int MultiplayerSpriteID // holdover from Gen6 - { - get => Data[Offset + 0x58]; - set => Data[Offset + 0x58] = (byte)value; - } - - public bool MegaUnlocked - { - get => (Data[Offset + 0x78] & 0x01) != 0; - set => Data[Offset + 0x78] = (byte)((Data[Offset + 0x78] & 0xFE) | (value ? 1 : 0)); // in battle - } - - public bool ZMoveUnlocked - { - get => (Data[Offset + 0x78] & 2) != 0; - set => Data[Offset + 0x78] = (byte)((Data[Offset + 0x78] & ~2) | (value ? 2 : 0)); // in battle - } - - public int BallThrowType - { - get => Data[Offset + 0x7A]; - set => Data[Offset + 0x7A] = (byte)(value > 8 ? 0 : value); + var data = Util.GetBytesFromHexString(value); + SAV.SetData(data, Offset + 0x10); } } -} \ No newline at end of file + + public string NexUniqueID + { + get => Util.GetHexStringFromBytes(Data, Offset + 0x18, NexUniqueIDSize / 2); + set + { + if (value.Length != NexUniqueIDSize) + throw new ArgumentException(nameof(value)); + + var data = Util.GetBytesFromHexString(value); + SAV.SetData(data, Offset + 0x18); + } + } + + public uint FestaID + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x28)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x28), value); + } + + public byte Region + { + get => Data[Offset + 0x2E]; + set => Data[Offset + 0x2E] = value; + } + + public byte Country + { + get => Data[Offset + 0x2F]; + set => Data[Offset + 0x2F] = value; + } + + public byte ConsoleRegion + { + get => Data[Offset + 0x34]; + set => Data[Offset + 0x34] = value; + } + + public int Language + { + get => Data[Offset + 0x35]; + set => Data[Offset + 0x35] = (byte)value; + } + + private Span OT_Trash => Data.AsSpan(Offset + 0x38, 0x1A); + + public string OT + { + get => SAV.GetString(OT_Trash); + set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); + } + + public int DressUpSkinColor + { + get => (Data[Offset + 0x54] >> 2) & 7; + set => Data[Offset + 0x54] = (byte)((Data[Offset + 0x54] & ~(7 << 2)) | (value << 2)); + } + + public int MultiplayerSpriteID // holdover from Gen6 + { + get => Data[Offset + 0x58]; + set => Data[Offset + 0x58] = (byte)value; + } + + public bool MegaUnlocked + { + get => (Data[Offset + 0x78] & 0x01) != 0; + set => Data[Offset + 0x78] = (byte)((Data[Offset + 0x78] & 0xFE) | (value ? 1 : 0)); // in battle + } + + public bool ZMoveUnlocked + { + get => (Data[Offset + 0x78] & 2) != 0; + set => Data[Offset + 0x78] = (byte)((Data[Offset + 0x78] & ~2) | (value ? 2 : 0)); // in battle + } + + public int BallThrowType + { + get => Data[Offset + 0x7A]; + set => Data[Offset + 0x7A] = (byte)(value > 8 ? 0 : value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/MysteryBlock7.cs b/PKHeX.Core/Saves/Substructures/Gen7/MysteryBlock7.cs index 916f79039..92c863ad0 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/MysteryBlock7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/MysteryBlock7.cs @@ -1,69 +1,68 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MysteryBlock7 : SaveBlock { - public sealed class MysteryBlock7 : SaveBlock + private const int FlagStart = 0; + private const int MaxReceivedFlag = 2048; + private const int MaxCardsPresent = 48; + // private const int FlagRegionSize = (MaxReceivedFlag / 8); // 0x100 + private const int CardStart = FlagStart + (MaxReceivedFlag / 8); + + public MysteryBlock7(SAV7 sav, int offset) : base(sav) => Offset = offset; + + // Mystery Gift + public bool[] MysteryGiftReceivedFlags { - private const int FlagStart = 0; - private const int MaxReceivedFlag = 2048; - private const int MaxCardsPresent = 48; - // private const int FlagRegionSize = (MaxReceivedFlag / 8); // 0x100 - private const int CardStart = FlagStart + (MaxReceivedFlag / 8); - - public MysteryBlock7(SAV7 sav, int offset) : base(sav) => Offset = offset; - - // Mystery Gift - public bool[] MysteryGiftReceivedFlags + get => ArrayUtil.GitBitFlagArray(Data.AsSpan(Offset + FlagStart), MaxReceivedFlag); + set { - get => ArrayUtil.GitBitFlagArray(Data.AsSpan(Offset + FlagStart), MaxReceivedFlag); - set - { - if (value.Length != MaxReceivedFlag) - return; - ArrayUtil.SetBitFlagArray(Data.AsSpan(Offset + FlagStart), value); - SAV.State.Edited = true; - } - } - - public DataMysteryGift[] MysteryGiftCards - { - get - { - var cards = new DataMysteryGift[MaxCardsPresent]; - for (int i = 0; i < cards.Length; i++) - cards[i] = GetGift(i); - return cards; - } - set - { - int count = Math.Min(MaxCardsPresent, value.Length); - for (int i = 0; i < count; i++) - SetGift((WC7)value[i], i); - for (int i = value.Length; i < MaxCardsPresent; i++) - SetGift(new WC7(), i); - } - } - - private WC7 GetGift(int index) - { - if ((uint)index > MaxCardsPresent) - throw new ArgumentOutOfRangeException(nameof(index)); - - var offset = GetGiftOffset(index); - var data = SAV.GetData(offset, WC7.Size); - return new WC7(data); - } - - private int GetGiftOffset(int index) => Offset + CardStart + (index * WC7.Size); - - private void SetGift(WC7 wc7, int index) - { - if ((uint)index > MaxCardsPresent) - throw new ArgumentOutOfRangeException(nameof(index)); - if (wc7.Data.Length != WC7.Size) - throw new InvalidCastException(nameof(wc7)); - - SAV.SetData(wc7.Data, GetGiftOffset(index)); + if (value.Length != MaxReceivedFlag) + return; + ArrayUtil.SetBitFlagArray(Data.AsSpan(Offset + FlagStart), value); + SAV.State.Edited = true; } } -} \ No newline at end of file + + public DataMysteryGift[] MysteryGiftCards + { + get + { + var cards = new DataMysteryGift[MaxCardsPresent]; + for (int i = 0; i < cards.Length; i++) + cards[i] = GetGift(i); + return cards; + } + set + { + int count = Math.Min(MaxCardsPresent, value.Length); + for (int i = 0; i < count; i++) + SetGift((WC7)value[i], i); + for (int i = value.Length; i < MaxCardsPresent; i++) + SetGift(new WC7(), i); + } + } + + private WC7 GetGift(int index) + { + if ((uint)index > MaxCardsPresent) + throw new ArgumentOutOfRangeException(nameof(index)); + + var offset = GetGiftOffset(index); + var data = SAV.GetData(offset, WC7.Size); + return new WC7(data); + } + + private int GetGiftOffset(int index) => Offset + CardStart + (index * WC7.Size); + + private void SetGift(WC7 wc7, int index) + { + if ((uint)index > MaxCardsPresent) + throw new ArgumentOutOfRangeException(nameof(index)); + if (wc7.Data.Length != WC7.Size) + throw new InvalidCastException(nameof(wc7)); + + SAV.SetData(wc7.Data, GetGiftOffset(index)); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/PlayerBattleStyle7.cs b/PKHeX.Core/Saves/Substructures/Gen7/PlayerBattleStyle7.cs index 56776e73a..59cfbdb50 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/PlayerBattleStyle7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/PlayerBattleStyle7.cs @@ -1,19 +1,18 @@ -namespace PKHeX.Core -{ - public enum PlayerBattleStyle7 - { - Normal, - Elegant, - Girlish, - Reverent, - Smug, - LeftHanded, - Passionate, - Idol, +namespace PKHeX.Core; - /// - /// USUM Only - /// - Nihilist, - } +public enum PlayerBattleStyle7 +{ + Normal, + Elegant, + Girlish, + Reverent, + Smug, + LeftHanded, + Passionate, + Idol, + + /// + /// USUM Only + /// + Nihilist, } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/PlayerSkinColor7.cs b/PKHeX.Core/Saves/Substructures/Gen7/PlayerSkinColor7.cs index 389cb24d5..db3383d0c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/PlayerSkinColor7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/PlayerSkinColor7.cs @@ -1,14 +1,13 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum PlayerSkinColor7 { - public enum PlayerSkinColor7 - { - M_Pale, - F_Pale, - M_Default, - F_Default, - M_Tan, - F_Tan, - M_Dark, - F_Dark, - } + M_Pale, + F_Pale, + M_Default, + F_Default, + M_Tan, + F_Tan, + M_Dark, + F_Dark, } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/PokeFinder7.cs b/PKHeX.Core/Saves/Substructures/Gen7/PokeFinder7.cs index 7f45bcd84..16765b8f5 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/PokeFinder7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/PokeFinder7.cs @@ -1,59 +1,58 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class PokeFinder7 : SaveBlock { - public sealed class PokeFinder7 : SaveBlock + public PokeFinder7(SAV7 sav, int offset) : base(sav) => Offset = offset; + + public ushort CameraVersion { - public PokeFinder7(SAV7 sav, int offset) : base(sav) => Offset = offset; + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), value); + } - public ushort CameraVersion + public bool GyroFlag + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x02)) == 1; + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x02), (ushort)(value ? 1 : 0)); + } + + public uint SnapCount + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x04)); + set { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), value); - } - - public bool GyroFlag - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x02)) == 1; - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x02), (ushort)(value ? 1 : 0)); - } - - public uint SnapCount - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x04)); - set - { - if (value > 9999999) // Top bound is unchecked, check anyway - value = 9999999; - WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); - } - } - - public uint ThumbsTotalValue - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x0C)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x0C), value); - } - - public uint ThumbsHighValue - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x10)); - set - { - if (value > 9_999_999) - value = 9_999_999; - WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x10), value); - - if (value > ThumbsTotalValue) - ThumbsTotalValue = value; - } - } - - public ushort TutorialFlags - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14), value); + if (value > 9999999) // Top bound is unchecked, check anyway + value = 9999999; + WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); } } -} \ No newline at end of file + + public uint ThumbsTotalValue + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x0C)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x0C), value); + } + + public uint ThumbsHighValue + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x10)); + set + { + if (value > 9_999_999) + value = 9_999_999; + WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x10), value); + + if (value > ThumbsTotalValue) + ThumbsTotalValue = value; + } + } + + public ushort TutorialFlags + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14), value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/QR7.cs b/PKHeX.Core/Saves/Substructures/Gen7/QR7.cs index 670b9a782..4e4024bbd 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/QR7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/QR7.cs @@ -2,99 +2,97 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; +// anatomy of a QR7: +// u32 magic; // POKE +// u32 _0xFF; +// u32 box; +// u32 slot; +// u32 num_copies; +// u8 reserved[0x1C]; +// u8 ek7[0x104]; +// u8 dex_data[0x60]; +// u16 crc16 +// sizeof(QR7) == 0x1A2 + +/// +/// Generation 7 QR format (readable by the in-game QR Scanner feature) +/// +public static class QR7 { - // anatomy of a QR7: - // u32 magic; // POKE - // u32 _0xFF; - // u32 box; - // u32 slot; - // u32 num_copies; - // u8 reserved[0x1C]; - // u8 ek7[0x104]; - // u8 dex_data[0x60]; - // u16 crc16 - // sizeof(QR7) == 0x1A2 - - /// - /// Generation 7 QR format (readable by the in-game QR Scanner feature) - /// - public static class QR7 + private static readonly HashSet GenderDifferences = new() { - private static readonly HashSet GenderDifferences = new() - { - 003, 012, 019, 020, 025, 026, 041, 042, 044, 045, - 064, 065, 084, 085, 097, 111, 112, 118, 119, 123, - 129, 130, 154, 165, 166, 178, 185, 186, 190, 194, - 195, 198, 202, 203, 207, 208, 212, 214, 215, 217, - 221, 224, 229, 232, 255, 256, 257, 267, 269, 272, - 274, 275, 307, 308, 315, 316, 317, 322, 323, 332, - 350, 369, 396, 397, 398, 399, 400, 401, 402, 403, - 404, 405, 407, 415, 417, 418, 419, 424, 443, 444, - 445, 449, 450, 453, 454, 456, 457, 459, 460, 461, - 464, 465, 473, 521, 592, 593, 668, 678, - }; + 003, 012, 019, 020, 025, 026, 041, 042, 044, 045, + 064, 065, 084, 085, 097, 111, 112, 118, 119, 123, + 129, 130, 154, 165, 166, 178, 185, 186, 190, 194, + 195, 198, 202, 203, 207, 208, 212, 214, 215, 217, + 221, 224, 229, 232, 255, 256, 257, 267, 269, 272, + 274, 275, 307, 308, 315, 316, 317, 322, 323, 332, + 350, 369, 396, 397, 398, 399, 400, 401, 402, 403, + 404, 405, 407, 415, 417, 418, 419, 424, 443, 444, + 445, 449, 450, 453, 454, 456, 457, 459, 460, 461, + 464, 465, 473, 521, 592, 593, 668, 678, + }; - private static readonly byte[] BaseQR = - { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; + private static readonly byte[] BaseQR = + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD2, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; - private static byte[] GetRawQR(int species, int form, bool shiny, int gender) - { - var basedata = (byte[])BaseQR.Clone(); - WriteUInt16LittleEndian(basedata.AsSpan(0x28), (ushort)species); + private static byte[] GetRawQR(int species, int form, bool shiny, int gender) + { + var basedata = (byte[])BaseQR.Clone(); + WriteUInt16LittleEndian(basedata.AsSpan(0x28), (ushort)species); - var pi = PersonalTable.USUM.GetFormEntry(species, form); - bool biGender = false; - if (pi.OnlyMale) - gender = 0; - else if (pi.OnlyFemale) - gender = 1; - else if (pi.Genderless) - gender = 2; - else - biGender = !GenderDifferences.Contains(species); + var pi = PersonalTable.USUM.GetFormEntry(species, form); + bool biGender = false; + if (pi.OnlyMale) + gender = 0; + else if (pi.OnlyFemale) + gender = 1; + else if (pi.Genderless) + gender = 2; + else + biGender = !GenderDifferences.Contains(species); - basedata[0x2A] = (byte)form; - basedata[0x2B] = (byte)gender; - basedata[0x2C] = shiny ? (byte)1 : (byte)0; - basedata[0x2D] = biGender ? (byte)1 : (byte)0; - return basedata; - } + basedata[0x2A] = (byte)form; + basedata[0x2B] = (byte)gender; + basedata[0x2C] = shiny ? (byte)1 : (byte)0; + basedata[0x2D] = biGender ? (byte)1 : (byte)0; + return basedata; + } - public static byte[] GenerateQRData(PK7 pk7, int box = 0, int slot = 0, int num_copies = 1) - { - if (box > 31) - box = 31; - if (slot > 29) - slot = 29; - if (box < 0) - box = 0; - if (slot < 0) - slot = 0; - if (num_copies < 0) - num_copies = 1; + public static byte[] GenerateQRData(PK7 pk7, int box = 0, int slot = 0, int num_copies = 1) + { + if (box > 31) + box = 31; + if (slot > 29) + slot = 29; + if (box < 0) + box = 0; + if (slot < 0) + slot = 0; + if (num_copies < 0) + num_copies = 1; - byte[] data = new byte[0x1A2]; - var span = data.AsSpan(); - WriteUInt32LittleEndian(span, 0x454B4F50); // POKE magic - data[0x4] = 0xFF; // QR Type - WriteInt32LittleEndian(span[0x08..], box); - WriteInt32LittleEndian(span[0x0C..], slot); - WriteInt32LittleEndian(span[0x10..], num_copies); // No need to check max num_copies, payload parser handles it on-console. + byte[] data = new byte[0x1A2]; + var span = data.AsSpan(); + WriteUInt32LittleEndian(span, 0x454B4F50); // POKE magic + data[0x4] = 0xFF; // QR Type + WriteInt32LittleEndian(span[0x08..], box); + WriteInt32LittleEndian(span[0x0C..], slot); + WriteInt32LittleEndian(span[0x10..], num_copies); // No need to check max num_copies, payload parser handles it on-console. - pk7.EncryptedPartyData.CopyTo(span[0x30..]); // Copy in pokemon data - GetRawQR(pk7.Species, pk7.Form, pk7.IsShiny, pk7.Gender).CopyTo(span[0x140..]); + pk7.EncryptedPartyData.CopyTo(span[0x30..]); // Copy in pokemon data + GetRawQR(pk7.Species, pk7.Form, pk7.IsShiny, pk7.Gender).CopyTo(span[0x140..]); - var chk = Checksums.CRC16Invert(span[..0x1A0]); - WriteUInt16LittleEndian(span[0x1A0..], chk); - return data; - } + var chk = Checksums.CRC16Invert(span[..0x1A0]); + WriteUInt16LittleEndian(span[0x1A0..], chk); + return data; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/ResortSave7.cs b/PKHeX.Core/Saves/Substructures/Gen7/ResortSave7.cs index 9433385d6..32bec0b16 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/ResortSave7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/ResortSave7.cs @@ -1,84 +1,83 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class ResortSave7 : SaveBlock { - public sealed class ResortSave7 : SaveBlock + public ResortSave7(SAV7 sav, int offset) : base(sav) => Offset = offset; + + public const int ResortCount = 93; + public int GetResortSlotOffset(int slot) => Offset + 0x16 + (slot * PokeCrypto.SIZE_6STORED); + + public PK7[] ResortPKM { - public ResortSave7(SAV7 sav, int offset) : base(sav) => Offset = offset; - - public const int ResortCount = 93; - public int GetResortSlotOffset(int slot) => Offset + 0x16 + (slot * PokeCrypto.SIZE_6STORED); - - public PK7[] ResortPKM + get { - get + PK7[] data = new PK7[ResortCount]; + for (int i = 0; i < data.Length; i++) { - PK7[] data = new PK7[ResortCount]; - for (int i = 0; i < data.Length; i++) - { - var bytes = SAV.GetData(GetResortSlotOffset(i), PokeCrypto.SIZE_6STORED); - data[i] = new PK7(bytes); - } - return data; - } - set - { - if (value.Length != ResortCount) - throw new ArgumentException(nameof(ResortCount)); - - for (int i = 0; i < value.Length; i++) - SAV.SetSlotFormatStored(value[i], Data, GetResortSlotOffset(i)); + var bytes = SAV.GetData(GetResortSlotOffset(i), PokeCrypto.SIZE_6STORED); + data[i] = new PK7(bytes); } + return data; } - - public const int BEANS_MAX = 15; - public Span GetBeans() => Data.AsSpan(Offset + 0x564C, BEANS_MAX); - public void ClearBeans() => GetBeans().Clear(); - public void FillBeans(byte value = 255) => GetBeans().Fill(value); - - public int GetPokebeanCount(int bean_id) => GetBeans()[bean_id]; - - public void SetPokebeanCount(int bean_id, int count) + set { - if (count < 0) - count = 0; - if (count > 255) - count = 255; - GetBeans()[bean_id] = (byte)count; - } + if (value.Length != ResortCount) + throw new ArgumentException(nameof(ResortCount)); - /// - /// Utility to indicate the bean pouch indexes. - /// - public static string[] GetBeanIndexNames() - { - var colors = Enum.GetNames(typeof(BeanColor7)); - return GetBeanIndexNames(colors); - } - - private static string[] GetBeanIndexNames(string[] colors) - { - // 7 regular, 7 patterned, one rainbow - var beans = new string[(colors.Length * 2) + 1]; - for (int i = 0; i < colors.Length; i++) - { - var z = colors[i]; - beans[i] = $"{z} Bean"; - beans[i + colors.Length] = $"{z} Patterned Bean"; - } - beans[^1] = "Rainbow Bean"; - return beans; + for (int i = 0; i < value.Length; i++) + SAV.SetSlotFormatStored(value[i], Data, GetResortSlotOffset(i)); } } - public enum BeanColor7 : byte + public const int BEANS_MAX = 15; + public Span GetBeans() => Data.AsSpan(Offset + 0x564C, BEANS_MAX); + public void ClearBeans() => GetBeans().Clear(); + public void FillBeans(byte value = 255) => GetBeans().Fill(value); + + public int GetPokebeanCount(int bean_id) => GetBeans()[bean_id]; + + public void SetPokebeanCount(int bean_id, int count) { - Red, - Blue, - LightBlue, - Green, - Yellow, - Purple, - Orange, + if (count < 0) + count = 0; + if (count > 255) + count = 255; + GetBeans()[bean_id] = (byte)count; + } + + /// + /// Utility to indicate the bean pouch indexes. + /// + public static string[] GetBeanIndexNames() + { + var colors = Enum.GetNames(typeof(BeanColor7)); + return GetBeanIndexNames(colors); + } + + private static string[] GetBeanIndexNames(string[] colors) + { + // 7 regular, 7 patterned, one rainbow + var beans = new string[(colors.Length * 2) + 1]; + for (int i = 0; i < colors.Length; i++) + { + var z = colors[i]; + beans[i] = $"{z} Bean"; + beans[i + colors.Length] = $"{z} Patterned Bean"; + } + beans[^1] = "Rainbow Bean"; + return beans; } } + +public enum BeanColor7 : byte +{ + Red, + Blue, + LightBlue, + Green, + Yellow, + Purple, + Orange, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/SAV7BlockIndex.cs b/PKHeX.Core/Saves/Substructures/Gen7/SAV7BlockIndex.cs index 2c6b0ddee..fb233ddd7 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/SAV7BlockIndex.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/SAV7BlockIndex.cs @@ -1,47 +1,46 @@ -namespace PKHeX.Core -{ - public enum SAV7BlockIndex - { - MyItem, - Situation, - RandomGroup, - MyStatus, - PokePartySave, - EventWork, - ZukanData, - GtsData, - UnionPokemon, - Misc, - FieldMenu, - ConfigSave, - GameTime, - BOX, - BoxPokemon, - ResortSave, - PlayTime, - FieldMoveModelSave, - Fashion, - JoinFestaPersonalSave1, - JoinFestaPersonalSave2, - JoinFestaDataSave, - BerrySpot, - FishingSpot, - LiveMatchData, - BattleSpotData, - PokeFinderSave, - MysteryGiftSave, - Record, - ValidationSave, - GameSyncSave, - PokeDiarySave, - BattleInstSave, - Sodateya, - WeatherSave, - QRReaderSaveData, - TurtleSalmonSave, +namespace PKHeX.Core; - // Ultra Sun/Ultra Moon only - BattleFesSave, - FinderStudioSave, - } -} \ No newline at end of file +public enum SAV7BlockIndex +{ + MyItem, + Situation, + RandomGroup, + MyStatus, + PokePartySave, + EventWork, + ZukanData, + GtsData, + UnionPokemon, + Misc, + FieldMenu, + ConfigSave, + GameTime, + BOX, + BoxPokemon, + ResortSave, + PlayTime, + FieldMoveModelSave, + Fashion, + JoinFestaPersonalSave1, + JoinFestaPersonalSave2, + JoinFestaDataSave, + BerrySpot, + FishingSpot, + LiveMatchData, + BattleSpotData, + PokeFinderSave, + MysteryGiftSave, + Record, + ValidationSave, + GameSyncSave, + PokeDiarySave, + BattleInstSave, + Sodateya, + WeatherSave, + QRReaderSaveData, + TurtleSalmonSave, + + // Ultra Sun/Ultra Moon only + BattleFesSave, + FinderStudioSave, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/Situation7.cs b/PKHeX.Core/Saves/Substructures/Gen7/Situation7.cs index 34a328176..1410f0464 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/Situation7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/Situation7.cs @@ -1,63 +1,62 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Situation7 : SaveBlock { - public sealed class Situation7 : SaveBlock + public Situation7(SAV7 sav, int offset) : base(sav) => Offset = offset; + + // "StartLocation" + public int M { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), (ushort)value); } + public float X { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x08), value); } + public float Z { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x10), value); } + public float Y { get => (int)ReadSingleLittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x18), value); } + public float R { get => (int)ReadSingleLittleEndian(Data.AsSpan(Offset + 0x20)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x20), value); } + + public void UpdateOverworldCoordinates() { - public Situation7(SAV7 sav, int offset) : base(sav) => Offset = offset; - - // "StartLocation" - public int M { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x00), (ushort)value); } - public float X { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x08), value); } - public float Z { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x10), value); } - public float Y { get => (int)ReadSingleLittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x18), value); } - public float R { get => (int)ReadSingleLittleEndian(Data.AsSpan(Offset + 0x20)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x20), value); } - - public void UpdateOverworldCoordinates() - { - var o = SAV.Overworld; - o.M = M; - o.X = X; - o.Z = Z; - o.Y = Y; - o.R = R; - } - - public int SpecialLocation - { - get => Data[Offset + 0x24]; - set => Data[Offset + 0x24] = (byte)value; - } - - public int WarpContinueRequest - { - get => Data[Offset + 0x6E]; - set => Data[Offset + 0x6E] = (byte)value; - } - - public int StepCountEgg - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x70)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x70), value); - } - - public int LastZoneID - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x74)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x74), (ushort)value); - } - - public int StepCountFriendship - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x76)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x76), (ushort)value); - } - - public int StepCountAffection // Kawaigari - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x78)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x78), (ushort)value); - } + var o = SAV.Overworld; + o.M = M; + o.X = X; + o.Z = Z; + o.Y = Y; + o.R = R; } -} \ No newline at end of file + + public int SpecialLocation + { + get => Data[Offset + 0x24]; + set => Data[Offset + 0x24] = (byte)value; + } + + public int WarpContinueRequest + { + get => Data[Offset + 0x6E]; + set => Data[Offset + 0x6E] = (byte)value; + } + + public int StepCountEgg + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x70)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x70), value); + } + + public int LastZoneID + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x74)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x74), (ushort)value); + } + + public int StepCountFriendship + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x76)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x76), (ushort)value); + } + + public int StepCountAffection // Kawaigari + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x78)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x78), (ushort)value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen7/Stamp7.cs b/PKHeX.Core/Saves/Substructures/Gen7/Stamp7.cs index 4f5e0ed69..9be627b13 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/Stamp7.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/Stamp7.cs @@ -1,21 +1,20 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum Stamp7 { - public enum Stamp7 - { - Official_Pokemon_Trainer, - Melemele_Trial_Completion, - Akala_Trial_Completion, - Ula_ula_Trial_Completion, - Poni_Trial_Completion, - Island_Challenge_Completion, - Melemele_Pokedex_Completion, - Akala_Pokedex_Completion, - Ula_ula_Pokedex_Completion, - Poni_Pokedex_Completion, - Alola_Pokedex_Completion, - _50_Consecutive_Single_Battle_Wins, - _50_Consecutive_Double_Battle_Wins, - _50_Consecutive_Multi_Battle_Wins, - Poke_Finder_Pro, - } + Official_Pokemon_Trainer, + Melemele_Trial_Completion, + Akala_Trial_Completion, + Ula_ula_Trial_Completion, + Poni_Trial_Completion, + Island_Challenge_Completion, + Melemele_Pokedex_Completion, + Akala_Pokedex_Completion, + Ula_ula_Pokedex_Completion, + Poni_Pokedex_Completion, + Alola_Pokedex_Completion, + _50_Consecutive_Single_Battle_Wins, + _50_Consecutive_Double_Battle_Wins, + _50_Consecutive_Multi_Battle_Wins, + Poke_Finder_Pro, } diff --git a/PKHeX.Core/Saves/Substructures/Gen7/WormholeInfoReader.cs b/PKHeX.Core/Saves/Substructures/Gen7/WormholeInfoReader.cs index 47a379f3b..8cddeaf2c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/WormholeInfoReader.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/WormholeInfoReader.cs @@ -1,145 +1,144 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class WormholeInfoReader { - public sealed class WormholeInfoReader + public readonly SAV7 SAV; + public WormholeInfoReader(SAV7 sav) => SAV = sav; + + // Wormhole shininess & flags found by @PP-theSLAYER + // https://projectpokemon.org/home/forums/topic/39433-gen-7-save-research-thread/?page=3&tab=comments#comment-239090 + public bool WormholeShininess // 0x4535 = Misc (0x4400 in USUM) + 0x0135 { - public readonly SAV7 SAV; - public WormholeInfoReader(SAV7 sav) => SAV = sav; + get => SAV.Data[SAV.Misc.Offset + 0x0135] == 1; + set => SAV.Data[SAV.Misc.Offset + 0x0135] = value ? (byte)1 : (byte)0; + } - // Wormhole shininess & flags found by @PP-theSLAYER - // https://projectpokemon.org/home/forums/topic/39433-gen-7-save-research-thread/?page=3&tab=comments#comment-239090 - public bool WormholeShininess // 0x4535 = Misc (0x4400 in USUM) + 0x0135 + public const int WormholeSlotMax = 15; + + // Slots currently use digits 1 through 15 inclusively. + // This follows the flag numbers used for slots. + public int WormholeSlot + { + get { - get => SAV.Data[SAV.Misc.Offset + 0x0135] == 1; - set => SAV.Data[SAV.Misc.Offset + 0x0135] = value ? (byte)1 : (byte)0; - } - - public const int WormholeSlotMax = 15; - - // Slots currently use digits 1 through 15 inclusively. - // This follows the flag numbers used for slots. - public int WormholeSlot - { - get + for (int i = 1; i <= WormholeSlotMax; i++) { - for (int i = 1; i <= WormholeSlotMax; i++) - { - if (!SAV.GetEventFlag(i)) - return i; - } - return -1; - } - set - { - if (value is < 1 or > WormholeSlotMax) - return; - for (int i = 1; i <= WormholeSlotMax; i++) - { - SAV.SetEventFlag(i, value != i); - } + if (!SAV.GetEventFlag(i)) + return i; } + return -1; } - - public static readonly int[] StandardWormholes = + set { - 256, // Red - 257, // Green - 258, // Yellow - 259, // Blue - }; - - public static readonly int[] WormholeSlotsRed = - { - -1, // filler used for indexing with slot number - 144, // Articuno - 145, // Zapdos - 146, // Moltres - 250, // Ho-Oh - 384, // Rayquaza - 488, // Cresselia - 641, // Tornadus - 642, // Thundurus - 645, // Landorus - 717, // Yveltal - 334, // Altaria - 469, // Yanmega - 561, // Sigilyph - 581, // Swanna - 277, // Swellow - }; - - public static readonly int[] WormholeSlotsGreen = - { - -1, // filler used for indexing with slot number - 150, // Mewtwo - 243, // Raikou - 244, // Entei - 483, // Dialga - 638, // Cobalion - 639, // Terrakion - 640, // Virizion - 643, // Reshiram - 644, // Zekrom - 716, // Xerneas - 452, // Drapion - 531, // Audino - 695, // Heliolisk - 274, // Nuzleaf - 326, // Grumpig - }; - - public static readonly int[] WormholeSlotsYellow = - { - -1, // filler used for indexing with slot number - 377, // Regirock - 378, // Regice - 379, // Registeel - 383, // Groudon - 485, // Heatran - 486, // Regigigas - 484, // Palkia - 487, // Giratina - -1, // unused - -1, // unused - 460, // Abomasnow - 308, // Medicham - 450, // Hippowdon - 558, // Crustle - 219, // Magcargo - }; - - public static readonly int[] WormholeSlotsBlue = - { - -1, // filler used for indexing with slot number - 245, // Suicune - 249, // Lugia - 380, // Latias - 381, // Latios - 382, // Kyogre - 480, // Uxie - 481, // Mesprit - 482, // Azelf - 646, // Kyurem - -1, // unused - 689, // Barbaracle - 271, // Lombre - 618, // Stunfisk - 419, // Floatzel - 195, // Quagsire - }; - - public static int WormholeSlotToPokemon(int mapid, int slot) - { - if (slot is < 1 or > WormholeSlotMax) - return -1; - - return mapid switch + if (value is < 1 or > WormholeSlotMax) + return; + for (int i = 1; i <= WormholeSlotMax; i++) { - 256 => WormholeSlotsRed[slot], - 257 => WormholeSlotsGreen[slot], - 258 => WormholeSlotsYellow[slot], - 259 => WormholeSlotsBlue[slot], - _ => -1, - }; + SAV.SetEventFlag(i, value != i); + } } } -} \ No newline at end of file + + public static readonly int[] StandardWormholes = + { + 256, // Red + 257, // Green + 258, // Yellow + 259, // Blue + }; + + public static readonly int[] WormholeSlotsRed = + { + -1, // filler used for indexing with slot number + 144, // Articuno + 145, // Zapdos + 146, // Moltres + 250, // Ho-Oh + 384, // Rayquaza + 488, // Cresselia + 641, // Tornadus + 642, // Thundurus + 645, // Landorus + 717, // Yveltal + 334, // Altaria + 469, // Yanmega + 561, // Sigilyph + 581, // Swanna + 277, // Swellow + }; + + public static readonly int[] WormholeSlotsGreen = + { + -1, // filler used for indexing with slot number + 150, // Mewtwo + 243, // Raikou + 244, // Entei + 483, // Dialga + 638, // Cobalion + 639, // Terrakion + 640, // Virizion + 643, // Reshiram + 644, // Zekrom + 716, // Xerneas + 452, // Drapion + 531, // Audino + 695, // Heliolisk + 274, // Nuzleaf + 326, // Grumpig + }; + + public static readonly int[] WormholeSlotsYellow = + { + -1, // filler used for indexing with slot number + 377, // Regirock + 378, // Regice + 379, // Registeel + 383, // Groudon + 485, // Heatran + 486, // Regigigas + 484, // Palkia + 487, // Giratina + -1, // unused + -1, // unused + 460, // Abomasnow + 308, // Medicham + 450, // Hippowdon + 558, // Crustle + 219, // Magcargo + }; + + public static readonly int[] WormholeSlotsBlue = + { + -1, // filler used for indexing with slot number + 245, // Suicune + 249, // Lugia + 380, // Latias + 381, // Latios + 382, // Kyogre + 480, // Uxie + 481, // Mesprit + 482, // Azelf + 646, // Kyurem + -1, // unused + 689, // Barbaracle + 271, // Lombre + 618, // Stunfisk + 419, // Floatzel + 195, // Quagsire + }; + + public static int WormholeSlotToPokemon(int mapid, int slot) + { + if (slot is < 1 or > WormholeSlotMax) + return -1; + + return mapid switch + { + 256 => WormholeSlotsRed[slot], + 257 => WormholeSlotsGreen[slot], + 258 => WormholeSlotsYellow[slot], + 259 => WormholeSlotsBlue[slot], + _ => -1, + }; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/BattleTowerWork8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/BattleTowerWork8b.cs index 82d2885ba..7766d9698 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/BattleTowerWork8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/BattleTowerWork8b.cs @@ -3,99 +3,98 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Stores progress within the Battle Tower for the four battle modes. +/// +/// size: 0x1B8 +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class BattleTowerWork8b : SaveBlock { - /// - /// Stores progress within the Battle Tower for the four battle modes. - /// - /// size: 0x1B8 - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class BattleTowerWork8b : SaveBlock + private const int OFS_ClassData = 20; + private const int COUNT_CLASSDATA = 4; + + public BattleTowerWork8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + // Structure: + // uint max_master_rank; + // int play_mode; + // int old_playmode; + // uint btl_point; + // uint day_challeng_cnt; + // BTLTOWER_CLASSDATA[4] class_data; + // uint challenge_cnt; + public int MasterRankMax { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x0)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x0), value); } // max_master_rank + public int PlayMode { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); }// play_mode + public int PlayModeOld { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x8)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x8), value); } // old_playmode + public uint BP { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xC)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xC), value); } // btl_point + + public uint ChallengeCount { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x1B4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x1B4), value); } // challenge_cnt + + public BattleTowerClassData8b[] Records { - private const int OFS_ClassData = 20; - private const int COUNT_CLASSDATA = 4; - - public BattleTowerWork8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - // Structure: - // uint max_master_rank; - // int play_mode; - // int old_playmode; - // uint btl_point; - // uint day_challeng_cnt; - // BTLTOWER_CLASSDATA[4] class_data; - // uint challenge_cnt; - public int MasterRankMax { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x0)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x0), value); } // max_master_rank - public int PlayMode { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); }// play_mode - public int PlayModeOld { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x8)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x8), value); } // old_playmode - public uint BP { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xC)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xC), value); } // btl_point - - public uint ChallengeCount { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x1B4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x1B4), value); } // challenge_cnt - - public BattleTowerClassData8b[] Records - { - get => GetRecords(); - set => SetRecords(value); - } - - private BattleTowerClassData8b[] GetRecords() - { - var result = new BattleTowerClassData8b[COUNT_CLASSDATA]; - for (int i = 0; i < result.Length; i++) - result[i] = new BattleTowerClassData8b(Data, Offset + OFS_ClassData + (i * BattleTowerClassData8b.SIZE)); - return result; - } - - private static void SetRecords(IReadOnlyList value) - { - if (value.Count != COUNT_CLASSDATA) - throw new ArgumentOutOfRangeException(nameof(value.Count)); - // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. - } + get => GetRecords(); + set => SetRecords(value); } - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class BattleTowerClassData8b + private BattleTowerClassData8b[] GetRecords() { - public const int SIZE = 0x68; + var result = new BattleTowerClassData8b[COUNT_CLASSDATA]; + for (int i = 0; i < result.Length; i++) + result[i] = new BattleTowerClassData8b(Data, Offset + OFS_ClassData + (i * BattleTowerClassData8b.SIZE)); + return result; + } - private readonly int Offset; - private readonly byte[] Data; - - public BattleTowerClassData8b(byte[] data, int offset) - { - Data = data; - Offset = offset; - } - - public override string ToString() => $"Rank: {Rank}, Streak: {RenshouCount} (Max {RenshouCountOld}), Wins: {TotalWins}|{TotalWinsLoop}|{TotalWinsLose}"; - - public byte Cleared - { - get => Data[Offset + 0x00]; - set => Data[Offset]= value; - } - public bool Suspended - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)) == 1; - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x04), value ? 1u : 0u); - } - public ulong BattlePlaySeed { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x08), value); } - public uint Rank { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x10), value); } - public uint RankDownLose { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x14)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x14), value); } - - public ulong TrainerSeed1 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x1C)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x1C), value); } - public ulong TrainerSeed2 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x24)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x24), value); } - public ulong TrainerSeed3 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x2C)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x2C), value); } - public ulong TrainerSeed4 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x34)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x34), value); } - public ulong TrainerSeed5 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x3C)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x3C), value); } - public ulong TrainerSeed6 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x44)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x44), value); } - public ulong TrainerSeed7 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x4C)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x4C), value); } - - public uint TotalWins { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x54)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x54), value); } - public uint TotalWinsLoop { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x58)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x58), value); } - public uint TotalWinsLose { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x5C)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x5C), value); } - public uint RenshouCountOld { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x60)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x60), value); } - public uint RenshouCount { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x64)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x64), value); } + private static void SetRecords(IReadOnlyList value) + { + if (value.Count != COUNT_CLASSDATA) + throw new ArgumentOutOfRangeException(nameof(value.Count)); + // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. } } + +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class BattleTowerClassData8b +{ + public const int SIZE = 0x68; + + private readonly int Offset; + private readonly byte[] Data; + + public BattleTowerClassData8b(byte[] data, int offset) + { + Data = data; + Offset = offset; + } + + public override string ToString() => $"Rank: {Rank}, Streak: {RenshouCount} (Max {RenshouCountOld}), Wins: {TotalWins}|{TotalWinsLoop}|{TotalWinsLose}"; + + public byte Cleared + { + get => Data[Offset + 0x00]; + set => Data[Offset]= value; + } + public bool Suspended + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)) == 1; + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x04), value ? 1u : 0u); + } + public ulong BattlePlaySeed { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x08), value); } + public uint Rank { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x10), value); } + public uint RankDownLose { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x14)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x14), value); } + + public ulong TrainerSeed1 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x1C)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x1C), value); } + public ulong TrainerSeed2 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x24)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x24), value); } + public ulong TrainerSeed3 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x2C)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x2C), value); } + public ulong TrainerSeed4 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x34)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x34), value); } + public ulong TrainerSeed5 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x3C)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x3C), value); } + public ulong TrainerSeed6 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x44)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x44), value); } + public ulong TrainerSeed7 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x4C)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x4C), value); } + + public uint TotalWins { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x54)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x54), value); } + public uint TotalWinsLoop { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x58)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x58), value); } + public uint TotalWinsLose { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x5C)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x5C), value); } + public uint RenshouCountOld { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x60)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x60), value); } + public uint RenshouCount { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x64)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x64), value); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/BattleTrainerStatus8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/BattleTrainerStatus8b.cs index ba23f50cf..c3ffec035 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/BattleTrainerStatus8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/BattleTrainerStatus8b.cs @@ -2,58 +2,57 @@ using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Defeated Status for all trainers (Dpr.Trainer.TrainerID) +/// +/// size: 0x1618 +public sealed class BattleTrainerStatus8b : SaveBlock { + public BattleTrainerStatus8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + // Structure: + // (bool IsWin, bool IsBattleSearcher)[707]; + private const int COUNT_TRAINER = 707; + private const int SIZE_TRAINER = 8; // bool,bool + + public bool AnyDefeated => Enumerable.Range(0, COUNT_TRAINER).Any(GetIsWin); + public bool AnyUndefeated => Enumerable.Range(0, COUNT_TRAINER).Any(z => !GetIsWin(z)); + /// - /// Defeated Status for all trainers (Dpr.Trainer.TrainerID) + /// Don't use this unless you've finished the post-game. /// - /// size: 0x1618 - public sealed class BattleTrainerStatus8b : SaveBlock + public void DefeatAll() { - public BattleTrainerStatus8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - // Structure: - // (bool IsWin, bool IsBattleSearcher)[707]; - private const int COUNT_TRAINER = 707; - private const int SIZE_TRAINER = 8; // bool,bool - - public bool AnyDefeated => Enumerable.Range(0, COUNT_TRAINER).Any(GetIsWin); - public bool AnyUndefeated => Enumerable.Range(0, COUNT_TRAINER).Any(z => !GetIsWin(z)); - - /// - /// Don't use this unless you've finished the post-game. - /// - public void DefeatAll() + for (int i = 0; i < COUNT_TRAINER; i++) { - for (int i = 0; i < COUNT_TRAINER; i++) - { - SetIsWin(i, true); - SetIsBattleSearcher(i, false); - } + SetIsWin(i, true); + SetIsBattleSearcher(i, false); } - - /// - /// Don't use this unless you've finished the post-game. - /// - public void RebattleAll() - { - for (int i = 0; i < COUNT_TRAINER; i++) - { - SetIsWin(i, false); - SetIsBattleSearcher(i, true); - } - } - - private int GetTrainerOffset(int trainer) - { - if ((uint)trainer >= COUNT_TRAINER) - throw new ArgumentOutOfRangeException(nameof(trainer)); - return Offset + (trainer * SIZE_TRAINER); - } - - public bool GetIsWin(int trainer) => ReadUInt32LittleEndian(Data.AsSpan(GetTrainerOffset(trainer))) == 1; - public bool GetIsBattleSearcher(int trainer) => ReadUInt32LittleEndian(Data.AsSpan(GetTrainerOffset(trainer) + 4)) == 1; - public void SetIsWin(int trainer, bool value) => WriteUInt32LittleEndian(Data.AsSpan(GetTrainerOffset(trainer)), value ? 1u : 0u); - public void SetIsBattleSearcher(int trainer, bool value) => WriteUInt32LittleEndian(Data.AsSpan(GetTrainerOffset(trainer) + 4), value ? 1u : 0u); } + + /// + /// Don't use this unless you've finished the post-game. + /// + public void RebattleAll() + { + for (int i = 0; i < COUNT_TRAINER; i++) + { + SetIsWin(i, false); + SetIsBattleSearcher(i, true); + } + } + + private int GetTrainerOffset(int trainer) + { + if ((uint)trainer >= COUNT_TRAINER) + throw new ArgumentOutOfRangeException(nameof(trainer)); + return Offset + (trainer * SIZE_TRAINER); + } + + public bool GetIsWin(int trainer) => ReadUInt32LittleEndian(Data.AsSpan(GetTrainerOffset(trainer))) == 1; + public bool GetIsBattleSearcher(int trainer) => ReadUInt32LittleEndian(Data.AsSpan(GetTrainerOffset(trainer) + 4)) == 1; + public void SetIsWin(int trainer, bool value) => WriteUInt32LittleEndian(Data.AsSpan(GetTrainerOffset(trainer)), value ? 1u : 0u); + public void SetIsBattleSearcher(int trainer, bool value) => WriteUInt32LittleEndian(Data.AsSpan(GetTrainerOffset(trainer) + 4), value ? 1u : 0u); } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/BerryTreeGrowSave8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/BerryTreeGrowSave8b.cs index c5a298c5f..5823f5b84 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/BerryTreeGrowSave8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/BerryTreeGrowSave8b.cs @@ -2,24 +2,23 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Details about berry tree plots. +/// +/// size: 0x808 +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class BerryTreeGrowSave8b : SaveBlock { - /// - /// Details about berry tree plots. - /// - /// size: 0x808 - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class BerryTreeGrowSave8b : SaveBlock - { - public BerryTreeGrowSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + public BerryTreeGrowSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - public const int KinomiGrowsCount = 128; + public const int KinomiGrowsCount = 128; - public const int KinomiSize = 0x10; - // structure: - // KinomiGrow[] kinomiGrows; // 0x0 - // long LastUpdateMinutes; // 0x8 + public const int KinomiSize = 0x10; + // structure: + // KinomiGrow[] kinomiGrows; // 0x0 + // long LastUpdateMinutes; // 0x8 - public long LastUpdateMinutes { get => ReadInt64LittleEndian(Data.AsSpan(Offset + 0x800)); set => WriteInt64LittleEndian(Data.AsSpan(Offset + 0x800), value); } - } + public long LastUpdateMinutes { get => ReadInt64LittleEndian(Data.AsSpan(Offset + 0x800)); set => WriteInt64LittleEndian(Data.AsSpan(Offset + 0x800), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/BoxLayout8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/BoxLayout8b.cs index d650ddd6a..889595e39 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/BoxLayout8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/BoxLayout8b.cs @@ -1,160 +1,159 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Information about the storage boxes. +/// +/// Structure name: SaveBoxData, size: 0x64A +public sealed class BoxLayout8b : SaveBlock, IBoxDetailName { - /// - /// Information about the storage boxes. - /// - /// Structure name: SaveBoxData, size: 0x64A - public sealed class BoxLayout8b : SaveBlock, IBoxDetailName + public const int BoxCount = 40; + + private const int StringMaxLength = SAV6.LongStringLength / 2; + public readonly int[] TeamSlots = new int[TeamCount * TeamSlotCount]; + private const int TeamNameLength = 0x16; + + public BoxLayout8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + private static int GetBoxNameOffset(int box) => SAV6.LongStringLength * box; + private static int GetTeamNameOffset(int box) => GetBoxNameOffset(BoxCount) + (TeamNameLength * box); + + public string GetBoxName(int box) { - public const int BoxCount = 40; + var span = Data.AsSpan(Offset + GetBoxNameOffset(box), SAV6.LongStringLength); + if (ReadUInt16LittleEndian(span) == 0) + return $"Box {box + 1}"; + return SAV.GetString(span); + } - private const int StringMaxLength = SAV6.LongStringLength / 2; - public readonly int[] TeamSlots = new int[TeamCount * TeamSlotCount]; - private const int TeamNameLength = 0x16; + public void SetBoxName(int box, string value) + { + var span = Data.AsSpan(Offset + GetBoxNameOffset(box), SAV6.LongStringLength); + SAV.SetString(span, value.AsSpan(), StringMaxLength, StringConverterOption.ClearZero); + } - public BoxLayout8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + public string GetTeamName(int team) + { + var offset = Offset + GetTeamNameOffset(team); + var span = Data.AsSpan(offset, TeamNameLength); + if (ReadUInt16LittleEndian(span) == 0) + return $"Team {team + 1}"; + return SAV.GetString(span); + } - private static int GetBoxNameOffset(int box) => SAV6.LongStringLength * box; - private static int GetTeamNameOffset(int box) => GetBoxNameOffset(BoxCount) + (TeamNameLength * box); + public void SetTeamName(int team, string value) + { + var offset = Offset + GetTeamNameOffset(team); + var span = Data.AsSpan(offset, TeamNameLength); + SAV.SetString(span, value.AsSpan(), TeamNameLength/2, StringConverterOption.ClearZero); + } - public string GetBoxName(int box) + public string this[int i] + { + get => GetBoxName(i); + set => SetBoxName(i, value); + } + + public const int TeamPositionOffset = 0x5D4; + public const int TeamCount = 6; + public const int TeamSlotCount = 6; + private const short NONE_SELECTED = -1; + + public void LoadBattleTeams() + { + for (int i = 0; i < TeamCount * TeamSlotCount; i++) { - var span = Data.AsSpan(Offset + GetBoxNameOffset(box), SAV6.LongStringLength); - if (ReadUInt16LittleEndian(span) == 0) - return $"Box {box + 1}"; - return SAV.GetString(span); - } - - public void SetBoxName(int box, string value) - { - var span = Data.AsSpan(Offset + GetBoxNameOffset(box), SAV6.LongStringLength); - SAV.SetString(span, value.AsSpan(), StringMaxLength, StringConverterOption.ClearZero); - } - - public string GetTeamName(int team) - { - var offset = Offset + GetTeamNameOffset(team); - var span = Data.AsSpan(offset, TeamNameLength); - if (ReadUInt16LittleEndian(span) == 0) - return $"Team {team + 1}"; - return SAV.GetString(span); - } - - public void SetTeamName(int team, string value) - { - var offset = Offset + GetTeamNameOffset(team); - var span = Data.AsSpan(offset, TeamNameLength); - SAV.SetString(span, value.AsSpan(), TeamNameLength/2, StringConverterOption.ClearZero); - } - - public string this[int i] - { - get => GetBoxName(i); - set => SetBoxName(i, value); - } - - public const int TeamPositionOffset = 0x5D4; - public const int TeamCount = 6; - public const int TeamSlotCount = 6; - private const short NONE_SELECTED = -1; - - public void LoadBattleTeams() - { - for (int i = 0; i < TeamCount * TeamSlotCount; i++) + short val = ReadInt16LittleEndian(Data.AsSpan(Offset + TeamPositionOffset + (i * 2))); + if (val < 0) { - short val = ReadInt16LittleEndian(Data.AsSpan(Offset + TeamPositionOffset + (i * 2))); - if (val < 0) - { - TeamSlots[i] = NONE_SELECTED; - continue; - } - - int box = val >> 8; - int slot = val & 0xFF; - int index = (SAV.BoxSlotCount * box) + slot; - TeamSlots[i] = index & 0xFFFF; - } - } - - public void ClearBattleTeams() - { - for (int i = 0; i < TeamSlots.Length; i++) TeamSlots[i] = NONE_SELECTED; - LockedTeam = 0; - } - - public void SaveBattleTeams() - { - var span = Data.AsSpan(Offset + TeamPositionOffset); - for (int i = 0; i < TeamCount * 6; i++) - { - int index = TeamSlots[i]; - if (index < 0) - { - WriteInt16LittleEndian(span[(i * 2)..], (short)index); - continue; - } - - SAV.GetBoxSlotFromIndex(index, out var box, out var slot); - index = (box << 8) | slot; - WriteInt16LittleEndian(span[(i * 2)..], (short)index); + continue; } - } - // bitflags - public byte LockedTeam - { - get => Data[Offset + 0x61C]; - set - { - if (value > BoxCount) - value = BoxCount; - Data[Offset + 0x61C] = value; - } - } - - public byte BoxesUnlocked - { - get => Data[Offset + 0x61D]; - set - { - if (value > BoxCount) - value = BoxCount; - Data[Offset + 0x61D] = value; - } - } - - public byte CurrentBox - { - get => Data[Offset + 0x61E]; - set => Data[Offset + 0x61E] = value; - } - - public bool GetIsTeamLocked(int team) => (LockedTeam & (1 << team)) != 0; - - public int GetBoxWallpaperOffset(int box) => Offset + 0x620 + box; - - public int GetBoxWallpaper(int box) - { - if ((uint)box > BoxCount) - return 0; - return Data[GetBoxWallpaperOffset(box)] - 1; - } - - public void SetBoxWallpaper(int box, int value) - { - if ((uint)box > BoxCount) - return; - Data[GetBoxWallpaperOffset(box)] = (byte)(value + 1); - } - - public ushort StatusPut - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x648)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x648), value); + int box = val >> 8; + int slot = val & 0xFF; + int index = (SAV.BoxSlotCount * box) + slot; + TeamSlots[i] = index & 0xFFFF; } } + + public void ClearBattleTeams() + { + for (int i = 0; i < TeamSlots.Length; i++) + TeamSlots[i] = NONE_SELECTED; + LockedTeam = 0; + } + + public void SaveBattleTeams() + { + var span = Data.AsSpan(Offset + TeamPositionOffset); + for (int i = 0; i < TeamCount * 6; i++) + { + int index = TeamSlots[i]; + if (index < 0) + { + WriteInt16LittleEndian(span[(i * 2)..], (short)index); + continue; + } + + SAV.GetBoxSlotFromIndex(index, out var box, out var slot); + index = (box << 8) | slot; + WriteInt16LittleEndian(span[(i * 2)..], (short)index); + } + } + + // bitflags + public byte LockedTeam + { + get => Data[Offset + 0x61C]; + set + { + if (value > BoxCount) + value = BoxCount; + Data[Offset + 0x61C] = value; + } + } + + public byte BoxesUnlocked + { + get => Data[Offset + 0x61D]; + set + { + if (value > BoxCount) + value = BoxCount; + Data[Offset + 0x61D] = value; + } + } + + public byte CurrentBox + { + get => Data[Offset + 0x61E]; + set => Data[Offset + 0x61E] = value; + } + + public bool GetIsTeamLocked(int team) => (LockedTeam & (1 << team)) != 0; + + public int GetBoxWallpaperOffset(int box) => Offset + 0x620 + box; + + public int GetBoxWallpaper(int box) + { + if ((uint)box > BoxCount) + return 0; + return Data[GetBoxWallpaperOffset(box)] - 1; + } + + public void SetBoxWallpaper(int box, int value) + { + if ((uint)box > BoxCount) + return; + Data[GetBoxWallpaperOffset(box)] = (byte)(value + 1); + } + + public ushort StatusPut + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x648)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x648), value); + } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/ConfigSave8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/ConfigSave8b.cs index 3498ffb7b..e77d256e9 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/ConfigSave8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/ConfigSave8b.cs @@ -2,112 +2,111 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Configuration settings for player preference. +/// +/// size 0x40, struct_name CONFIG +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class ConfigSave8b : SaveBlock { - /// - /// Configuration settings for player preference. - /// - /// size 0x40, struct_name CONFIG - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class ConfigSave8b : SaveBlock + public ConfigSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public TextSpeedOption TextSpeed { - public ConfigSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + get => (TextSpeedOption)ReadInt32LittleEndian(Data.AsSpan(Offset + 0)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0), (int)value); + } - public TextSpeedOption TextSpeed - { - get => (TextSpeedOption)ReadInt32LittleEndian(Data.AsSpan(Offset + 0)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0), (int)value); - } + public int Language + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 4)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 4), value); + } - public int Language - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 4)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 4), value); - } + public bool IsKanji { get => Data[Offset + 8] == 1; set => Data[Offset + 8] = (byte)(value ? 1 : 0); } - public bool IsKanji { get => Data[Offset + 8] == 1; set => Data[Offset + 8] = (byte)(value ? 1 : 0); } + public int WindowType + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x0C)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x0C), value); + } - public int WindowType - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x0C)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x0C), value); - } + public BattleAnimationSetting MoveAnimations + { + get => (BattleAnimationSetting)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x10)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x10), (int)value); + } - public BattleAnimationSetting MoveAnimations - { - get => (BattleAnimationSetting)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x10)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x10), (int)value); - } + public BattleStyleSetting BattleStyle + { + get => (BattleStyleSetting)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x14)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x14), (int)value); + } - public BattleStyleSetting BattleStyle - { - get => (BattleStyleSetting)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x14)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x14), (int)value); - } + public PartyBoxSetting PartyBox + { + get => (PartyBoxSetting)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x18)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x18), (int)value); + } - public PartyBoxSetting PartyBox - { - get => (PartyBoxSetting)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x18)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x18), (int)value); - } + // 4 byte bool, nice + public bool RegistNickname { get => Data[Offset + 0x1C] == 1; set => Data[Offset + 0x1C] = (byte)(value ? 1 : 0); } + public bool GyroSensor { get => Data[Offset + 0x20] == 1; set => Data[Offset + 0x20] = (byte)(value ? 1 : 0); } + public bool CameraShakeOfFossil { get => Data[Offset + 0x24] == 1; set => Data[Offset + 0x24] = (byte)(value ? 1 : 0); } - // 4 byte bool, nice - public bool RegistNickname { get => Data[Offset + 0x1C] == 1; set => Data[Offset + 0x1C] = (byte)(value ? 1 : 0); } - public bool GyroSensor { get => Data[Offset + 0x20] == 1; set => Data[Offset + 0x20] = (byte)(value ? 1 : 0); } - public bool CameraShakeOfFossil { get => Data[Offset + 0x24] == 1; set => Data[Offset + 0x24] = (byte)(value ? 1 : 0); } + public CameraInputMode CameraUpDown + { + get => (CameraInputMode)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x28)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x28), (int)value); + } - public CameraInputMode CameraUpDown - { - get => (CameraInputMode)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x28)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x28), (int)value); - } + public CameraInputMode CamerLeftRight + { + get => (CameraInputMode)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x2C)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x2C), (int)value); + } - public CameraInputMode CamerLeftRight - { - get => (CameraInputMode)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x2C)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x2C), (int)value); - } + public bool AutoReport { get => Data[Offset + 0x30] == 1; set => Data[Offset + 0x30] = (byte)(value ? 1 : 0); } - public bool AutoReport { get => Data[Offset + 0x30] == 1; set => Data[Offset + 0x30] = (byte)(value ? 1 : 0); } + public InputMode Input + { + get => (InputMode)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x34)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x34), (int)value); + } - public InputMode Input - { - get => (InputMode)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x34)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x34), (int)value); - } + public bool ShowNicknames { get => Data[Offset + 0x38] == 1; set => Data[Offset + 0x38] = (byte)(value ? 1 : 0); } + public byte VolumeBGM { get => Data[Offset + 0x3C]; set => Data[Offset + 0x3C] = value; } + public byte VolumeSoundEffects { get => Data[Offset + 0x3D]; set => Data[Offset + 0x3D] = value; } + public byte VolumeVoice { get => Data[Offset + 0x3E]; set => Data[Offset + 0x3E] = value; } - public bool ShowNicknames { get => Data[Offset + 0x38] == 1; set => Data[Offset + 0x38] = (byte)(value ? 1 : 0); } - public byte VolumeBGM { get => Data[Offset + 0x3C]; set => Data[Offset + 0x3C] = value; } - public byte VolumeSoundEffects { get => Data[Offset + 0x3D]; set => Data[Offset + 0x3D] = value; } - public byte VolumeVoice { get => Data[Offset + 0x3E]; set => Data[Offset + 0x3E] = value; } + public enum CameraInputMode + { + Normal, + Reverse, + } + public enum InputMode + { + Easy, + Normal, + } - public enum CameraInputMode - { - Normal, - Reverse, - } - public enum InputMode - { - Easy, - Normal, - } + public enum BattleAnimationSetting + { + EffectsON, + EffectsOFF, + } - public enum BattleAnimationSetting - { - EffectsON, - EffectsOFF, - } + public enum BattleStyleSetting + { + SWITCH, + SET, + } - public enum BattleStyleSetting - { - SWITCH, - SET, - } - - public enum PartyBoxSetting - { - Select, - SendBox, - } + public enum PartyBoxSetting + { + Select, + SendBox, } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/Contest8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/Contest8b.cs index c50d98496..147d3de7a 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/Contest8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/Contest8b.cs @@ -2,22 +2,21 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contest photo and Rank Point storage. +/// +/// size 0x720, struct_name CONTEST_DATA +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class Contest8b : SaveBlock { - /// - /// Contest photo and Rank Point storage. - /// - /// size 0x720, struct_name CONTEST_DATA - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class Contest8b : SaveBlock - { - public const int SIZE_CONTEST_PHOTO = 0x16C; - public const int PHOTO_MAX = 5; + public const int SIZE_CONTEST_PHOTO = 0x16C; + public const int PHOTO_MAX = 5; - public Contest8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + public Contest8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - private const int OFS_RANK = SIZE_CONTEST_PHOTO * PHOTO_MAX; // 0x71C; + private const int OFS_RANK = SIZE_CONTEST_PHOTO * PHOTO_MAX; // 0x71C; - public uint ContestRankPoint { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_RANK)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_RANK), value); } - } + public uint ContestRankPoint { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_RANK)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_RANK), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/Daycare8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/Daycare8b.cs index 3ef9e377e..d1ed05337 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/Daycare8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/Daycare8b.cs @@ -2,59 +2,58 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Storage for the in-game daycare structure. +/// +/// size: 0x2C0 +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class Daycare8b : SaveBlock { - /// - /// Storage for the in-game daycare structure. - /// - /// size: 0x2C0 - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class Daycare8b : SaveBlock + public Daycare8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + // BLOCK STRUCTURE + // PB8[2] Parents; + // bool32 eggExist; + // ulong eggSeed; -- setter puts only 32 bits! + // int32 eggStepCount; + + private const int SlotCount = 2; + private const int ExtraDataOffset = PokeCrypto.SIZE_8PARTY * SlotCount; + + public bool GetDaycareSlotOccupied(int slot) => GetSlot(slot).Species != 0; + + public int GetParentSlotOffset(int slot) { - public Daycare8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + if ((uint)slot >= SlotCount) + throw new ArgumentOutOfRangeException(nameof(slot)); - // BLOCK STRUCTURE - // PB8[2] Parents; - // bool32 eggExist; - // ulong eggSeed; -- setter puts only 32 bits! - // int32 eggStepCount; + return Offset + (slot * PokeCrypto.SIZE_8PARTY); + } - private const int SlotCount = 2; - private const int ExtraDataOffset = PokeCrypto.SIZE_8PARTY * SlotCount; + public PB8 GetSlot(int slot) + { + var offset = GetParentSlotOffset(slot); + var data = Data.AsSpan(offset, PokeCrypto.SIZE_8PARTY).ToArray(); + return new PB8(data); + } - public bool GetDaycareSlotOccupied(int slot) => GetSlot(slot).Species != 0; + public bool IsEggAvailable + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + ExtraDataOffset)) == 1; + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + ExtraDataOffset), value ? 1u : 0u); + } - public int GetParentSlotOffset(int slot) - { - if ((uint)slot >= SlotCount) - throw new IndexOutOfRangeException(nameof(slot)); + public ulong DaycareSeed + { + get => ReadUInt64LittleEndian(Data.AsSpan(Offset + ExtraDataOffset + 4)); + set => WriteUInt64LittleEndian(Data.AsSpan(Offset + ExtraDataOffset + 4), value); + } - return Offset + (slot * PokeCrypto.SIZE_8PARTY); - } - - public PB8 GetSlot(int slot) - { - var offset = GetParentSlotOffset(slot); - var data = Data.AsSpan(offset, PokeCrypto.SIZE_8PARTY).ToArray(); - return new PB8(data); - } - - public bool IsEggAvailable - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + ExtraDataOffset)) == 1; - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + ExtraDataOffset), value ? 1u : 0u); - } - - public ulong DaycareSeed - { - get => ReadUInt64LittleEndian(Data.AsSpan(Offset + ExtraDataOffset + 4)); - set => WriteUInt64LittleEndian(Data.AsSpan(Offset + ExtraDataOffset + 4), value); - } - - public int EggStepCount - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + ExtraDataOffset + 4 + 8)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + ExtraDataOffset + 4 + 8), value); - } + public int EggStepCount + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + ExtraDataOffset + 4 + 8)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + ExtraDataOffset + 4 + 8), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/EncounterSave8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/EncounterSave8b.cs index 2a313f7a7..25018eaba 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/EncounterSave8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/EncounterSave8b.cs @@ -3,185 +3,184 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Encounter Save Data +/// +/// size 0x188, struct_name ENC_SV_DATA +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class EncounterSave8b : SaveBlock { - /// - /// Encounter Save Data - /// - /// size 0x188, struct_name ENC_SV_DATA - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class EncounterSave8b : SaveBlock + public const int COUNT_HONEYTREE = 21; + private const int SIZE_HillBackData = 0x8; + private const int SIZE_HoneyTree = 8 + 4 + (COUNT_HONEYTREE * HoneyTree8b.SIZE); // 0x108 + private const int SIZE_Roamer = 0x20; + private const int SIZE_Closing = 10 + 2; // 2 padding alignment + + private const int OFS_HillBackData = 0xC; + private const int OFS_HoneyTree = OFS_HillBackData + SIZE_HillBackData; // 0x14 + private const int OFS_SWAY = OFS_HoneyTree + SIZE_HoneyTree; // 0x11C + private const int OFS_ZONEHISTORY = OFS_SWAY + (4 * 6); // 0x134 + private const int OFS_ROAM1 = OFS_ZONEHISTORY + 4 + 4; // 0x13C + private const int OFS_ROAM2 = OFS_ROAM1 + SIZE_Roamer; // 0x15C + private const int OFS_CLOSING = OFS_ROAM2 + SIZE_Roamer; // 0x17C + private const int SIZE = OFS_CLOSING + SIZE_Closing; // 0x188 + + public EncounterSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public void Clear() => Data.AsSpan(Offset, SIZE).Clear(); + + public int EncounterWalkCount { - public const int COUNT_HONEYTREE = 21; - private const int SIZE_HillBackData = 0x8; - private const int SIZE_HoneyTree = 8 + 4 + (COUNT_HONEYTREE * HoneyTree8b.SIZE); // 0x108 - private const int SIZE_Roamer = 0x20; - private const int SIZE_Closing = 10 + 2; // 2 padding alignment - - private const int OFS_HillBackData = 0xC; - private const int OFS_HoneyTree = OFS_HillBackData + SIZE_HillBackData; // 0x14 - private const int OFS_SWAY = OFS_HoneyTree + SIZE_HoneyTree; // 0x11C - private const int OFS_ZONEHISTORY = OFS_SWAY + (4 * 6); // 0x134 - private const int OFS_ROAM1 = OFS_ZONEHISTORY + 4 + 4; // 0x13C - private const int OFS_ROAM2 = OFS_ROAM1 + SIZE_Roamer; // 0x15C - private const int OFS_CLOSING = OFS_ROAM2 + SIZE_Roamer; // 0x17C - private const int SIZE = OFS_CLOSING + SIZE_Closing; // 0x188 - - public EncounterSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public void Clear() => Data.AsSpan(Offset, SIZE).Clear(); - - public int EncounterWalkCount - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x00), value); - } - - public uint SafariRandSeed - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x04)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); - } - - public uint GenerateRandSeed - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x08)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); - } - - // HILL_BACK_DATA - public bool HillTalkFlag - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x00)) == 1; - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x00), value ? 1u : 0u); - } - public ushort HillEncTblIdx1 - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x04)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x04), value); - } - public ushort HillEncTblIdx2 - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x06)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x06), value); - } - - // HONEY_TREE - public long HoneyLastUpdateMinutes - { - get => ReadInt64LittleEndian(Data.AsSpan(Offset + OFS_HoneyTree + 0x00)); - set => WriteInt64LittleEndian(Data.AsSpan(Offset + OFS_HoneyTree + 0x00), value); - } - public byte HoneyTreeNo - { - get => Data[Offset + OFS_HoneyTree + 0x08]; - set => Data[Offset + OFS_HoneyTree + 0x08] = value; - } - - public HoneyTree8b[] HoneyTrees - { - get => GetTrees(); - set => SetTrees(value); - } - - private HoneyTree8b[] GetTrees() - { - var result = new HoneyTree8b[COUNT_HONEYTREE]; - for (int i = 0; i < result.Length; i++) - result[i] = new HoneyTree8b(Data, Offset + OFS_HoneyTree + 0xC + (i * HoneyTree8b.SIZE)); - return result; - } - - private static void SetTrees(IReadOnlyList value) - { - if (value.Count != COUNT_HONEYTREE) - throw new ArgumentOutOfRangeException(nameof(value.Count)); - // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. - } - - public uint Radar1Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x00), value); } - public uint Radar1Chain { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x04)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x04), value); } - public uint Radar2Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x08)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x08), value); } - public uint Radar2Chain { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x0C)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x0C), value); } - public uint Radar3Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x10), value); } - public uint Radar3Chain { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x14)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x14), value); } - - public int BeforeZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_ZONEHISTORY + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_ZONEHISTORY + 0x00), value); } - public int OldZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_ZONEHISTORY + 0x04)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_ZONEHISTORY + 0x04), value); } - - // Mesprit - public int Roamer1ZoneID { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x00), value); } - public ulong Roamer1Seed { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x04)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x04), value); } - public uint Roamer1Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x0C)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x0C), value); } - public uint Roamer1HP { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x10), value); } - public byte Roamer1Level { get => Data[Offset + OFS_ROAM1 + 0x14]; set => Data[Offset + OFS_ROAM1 + 0x14] = value; } - public uint Roamer1Status { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x18)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x18), value); } - public byte Roamer1Encount { get => Data[Offset + OFS_ROAM1 + 0x1C]; set => Data[Offset + OFS_ROAM1 + 0x1C] = value; } - - // Cresselia - public int Roamer2ZoneID { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x00), value); } - public ulong Roamer2Seed { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x04)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x04), value); } - public uint Roamer2Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x0C)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x0C), value); } - public uint Roamer2HP { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x10), value); } - public byte Roamer2Level { get => Data[Offset + OFS_ROAM2 + 0x14]; set => Data[Offset + OFS_ROAM2 + 0x14] = value; } - public uint Roamer2Status { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x18)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x18), value); } - public byte Roamer2Encount { get => Data[Offset + OFS_ROAM2 + 0x1C]; set => Data[Offset + OFS_ROAM2 + 0x1C] = value; } - - public bool GenerateValid - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_CLOSING + 0)) == 1; - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_CLOSING + 0), value ? 1u : 0u); - } - public short SprayCount - { - get => ReadInt16LittleEndian(Data.AsSpan(Offset + OFS_CLOSING + 4)); - set => WriteInt16LittleEndian(Data.AsSpan(Offset + OFS_CLOSING + 4), value); - } - public byte SprayType { get => Data[Offset + OFS_CLOSING + 6]; set => Data[Offset + OFS_CLOSING + 6] = value; } - public byte VsSeekerCharge { get => Data[Offset + OFS_CLOSING + 7]; set => Data[Offset + OFS_CLOSING + 7] = value; } // max 100 - public byte PokeRadarCharge { get => Data[Offset + OFS_CLOSING + 8]; set => Data[Offset + OFS_CLOSING + 8] = value; } // max 50 - public byte FluteType { get => Data[Offset + OFS_CLOSING + 9]; set => Data[Offset + OFS_CLOSING + 9] = value; } // vidro + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x00), value); } - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class HoneyTree8b + public uint SafariRandSeed { - public const int SIZE = 0xC; - - private readonly int Offset; - private readonly byte[] Data; - - public HoneyTree8b(byte[] data, int offset) - { - Data = data; - Offset = offset; - } - - public bool Spreaded - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x00)) == 1; - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x00), value ? 1u : 0u); - } - public int Minutes - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); - } - public byte TblMonsNo - { - get => Data[Offset + 0x08]; - set => Data[Offset + 0x08] = value; - } - public byte RareLv - { - get => Data[Offset + 0x09]; - set => Data[Offset + 0x09] = value; - } - public byte SwayLv - { - get => Data[Offset + 0x0A]; - set => Data[Offset + 0x0A] = value; - } - // 0xB alignment + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x04)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); } + + public uint GenerateRandSeed + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x08)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); + } + + // HILL_BACK_DATA + public bool HillTalkFlag + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x00)) == 1; + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x00), value ? 1u : 0u); + } + public ushort HillEncTblIdx1 + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x04)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x04), value); + } + public ushort HillEncTblIdx2 + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x06)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + OFS_HillBackData + 0x06), value); + } + + // HONEY_TREE + public long HoneyLastUpdateMinutes + { + get => ReadInt64LittleEndian(Data.AsSpan(Offset + OFS_HoneyTree + 0x00)); + set => WriteInt64LittleEndian(Data.AsSpan(Offset + OFS_HoneyTree + 0x00), value); + } + public byte HoneyTreeNo + { + get => Data[Offset + OFS_HoneyTree + 0x08]; + set => Data[Offset + OFS_HoneyTree + 0x08] = value; + } + + public HoneyTree8b[] HoneyTrees + { + get => GetTrees(); + set => SetTrees(value); + } + + private HoneyTree8b[] GetTrees() + { + var result = new HoneyTree8b[COUNT_HONEYTREE]; + for (int i = 0; i < result.Length; i++) + result[i] = new HoneyTree8b(Data, Offset + OFS_HoneyTree + 0xC + (i * HoneyTree8b.SIZE)); + return result; + } + + private static void SetTrees(IReadOnlyList value) + { + if (value.Count != COUNT_HONEYTREE) + throw new ArgumentOutOfRangeException(nameof(value.Count)); + // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. + } + + public uint Radar1Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x00), value); } + public uint Radar1Chain { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x04)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x04), value); } + public uint Radar2Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x08)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x08), value); } + public uint Radar2Chain { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x0C)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x0C), value); } + public uint Radar3Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x10), value); } + public uint Radar3Chain { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x14)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_SWAY + 0x14), value); } + + public int BeforeZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_ZONEHISTORY + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_ZONEHISTORY + 0x00), value); } + public int OldZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_ZONEHISTORY + 0x04)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_ZONEHISTORY + 0x04), value); } + + // Mesprit + public int Roamer1ZoneID { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x00), value); } + public ulong Roamer1Seed { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x04)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x04), value); } + public uint Roamer1Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x0C)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x0C), value); } + public uint Roamer1HP { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x10), value); } + public byte Roamer1Level { get => Data[Offset + OFS_ROAM1 + 0x14]; set => Data[Offset + OFS_ROAM1 + 0x14] = value; } + public uint Roamer1Status { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x18)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM1 + 0x18), value); } + public byte Roamer1Encount { get => Data[Offset + OFS_ROAM1 + 0x1C]; set => Data[Offset + OFS_ROAM1 + 0x1C] = value; } + + // Cresselia + public int Roamer2ZoneID { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x00), value); } + public ulong Roamer2Seed { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x04)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x04), value); } + public uint Roamer2Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x0C)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x0C), value); } + public uint Roamer2HP { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x10), value); } + public byte Roamer2Level { get => Data[Offset + OFS_ROAM2 + 0x14]; set => Data[Offset + OFS_ROAM2 + 0x14] = value; } + public uint Roamer2Status { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x18)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_ROAM2 + 0x18), value); } + public byte Roamer2Encount { get => Data[Offset + OFS_ROAM2 + 0x1C]; set => Data[Offset + OFS_ROAM2 + 0x1C] = value; } + + public bool GenerateValid + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_CLOSING + 0)) == 1; + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_CLOSING + 0), value ? 1u : 0u); + } + public short SprayCount + { + get => ReadInt16LittleEndian(Data.AsSpan(Offset + OFS_CLOSING + 4)); + set => WriteInt16LittleEndian(Data.AsSpan(Offset + OFS_CLOSING + 4), value); + } + public byte SprayType { get => Data[Offset + OFS_CLOSING + 6]; set => Data[Offset + OFS_CLOSING + 6] = value; } + public byte VsSeekerCharge { get => Data[Offset + OFS_CLOSING + 7]; set => Data[Offset + OFS_CLOSING + 7] = value; } // max 100 + public byte PokeRadarCharge { get => Data[Offset + OFS_CLOSING + 8]; set => Data[Offset + OFS_CLOSING + 8] = value; } // max 50 + public byte FluteType { get => Data[Offset + OFS_CLOSING + 9]; set => Data[Offset + OFS_CLOSING + 9] = value; } // vidro +} + +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class HoneyTree8b +{ + public const int SIZE = 0xC; + + private readonly int Offset; + private readonly byte[] Data; + + public HoneyTree8b(byte[] data, int offset) + { + Data = data; + Offset = offset; + } + + public bool Spreaded + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x00)) == 1; + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x00), value ? 1u : 0u); + } + public int Minutes + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); + } + public byte TblMonsNo + { + get => Data[Offset + 0x08]; + set => Data[Offset + 0x08] = value; + } + public byte RareLv + { + get => Data[Offset + 0x09]; + set => Data[Offset + 0x09] = value; + } + public byte SwayLv + { + get => Data[Offset + 0x0A]; + set => Data[Offset + 0x0A] = value; + } + // 0xB alignment } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/FieldGimmickSave8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/FieldGimmickSave8b.cs index b9c5baa19..fa537a205 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/FieldGimmickSave8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/FieldGimmickSave8b.cs @@ -2,18 +2,17 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core -{ - /// - /// size: 0xC - /// - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class FieldGimmickSave8b : SaveBlock - { - public FieldGimmickSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; +namespace PKHeX.Core; - public int Value0 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x00), value); } - public int Value1 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); } - public int Value2 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); } - } +/// +/// size: 0xC +/// +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class FieldGimmickSave8b : SaveBlock +{ + public FieldGimmickSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public int Value0 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x00), value); } + public int Value1 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); } + public int Value2 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/FieldObjectSave8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/FieldObjectSave8b.cs index 6a92b5850..9d26014e4 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/FieldObjectSave8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/FieldObjectSave8b.cs @@ -3,76 +3,75 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Stores 1000 field objects to spawn into the map. +/// +/// size: 0x109A0 (1000 * 4*17) +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class FieldObjectSave8b : SaveBlock { - /// - /// Stores 1000 field objects to spawn into the map. - /// - /// size: 0x109A0 (1000 * 4*17) - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class FieldObjectSave8b : SaveBlock + private const int COUNT_OBJECTS = 1_000; + + public FieldObjectSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public FieldObject8b[] AllObjects { - private const int COUNT_OBJECTS = 1_000; - - public FieldObjectSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public FieldObject8b[] AllObjects - { - get => GetObjects(); - set => SetObjects(value); - } - - private FieldObject8b[] GetObjects() - { - var result = new FieldObject8b[COUNT_OBJECTS]; - for (int i = 0; i < result.Length; i++) - result[i] = new FieldObject8b(Data, Offset + (i * FieldObject8b.SIZE)); - return result; - } - - private static void SetObjects(IReadOnlyList value) - { - if (value.Count != COUNT_OBJECTS) - throw new ArgumentOutOfRangeException(nameof(value.Count)); - // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. - } + get => GetObjects(); + set => SetObjects(value); } - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class FieldObject8b + private FieldObject8b[] GetObjects() { - public const int SIZE = 4 * 17; + var result = new FieldObject8b[COUNT_OBJECTS]; + for (int i = 0; i < result.Length; i++) + result[i] = new FieldObject8b(Data, Offset + (i * FieldObject8b.SIZE)); + return result; + } - private readonly byte[] Data = new byte[SIZE]; - - public override string ToString() => $"{NameHash:X8} @ ({GridX:000},{GridY:000}) - {(Active ? "✓" : "✕")}"; - - public FieldObject8b(byte[] data, int offset) - { - data.AsSpan(offset, SIZE).CopyTo(Data); - } - - public byte Count // cnt - { - get => Data[0] ; - set => Data[0] = value; - } - - public int NameHash { get => ReadInt32LittleEndian(Data.AsSpan(0x04)); set => WriteInt32LittleEndian(Data.AsSpan(0x04), value); } - public int GridX { get => ReadInt32LittleEndian(Data.AsSpan(0x08)); set => WriteInt32LittleEndian(Data.AsSpan(0x08), value); } - public int GridY { get => ReadInt32LittleEndian(Data.AsSpan(0x0C)); set => WriteInt32LittleEndian(Data.AsSpan(0x0C), value); } - public int Height { get => ReadInt32LittleEndian(Data.AsSpan(0x10)); set => WriteInt32LittleEndian(Data.AsSpan(0x10), value); } - public int Angle { get => ReadInt32LittleEndian(Data.AsSpan(0x14)); set => WriteInt32LittleEndian(Data.AsSpan(0x14), value); } - public bool Active { get => ReadInt32LittleEndian(Data.AsSpan(0x18)) == 1; set => WriteUInt32LittleEndian(Data.AsSpan(0x18), value ? 1u : 0u); } - public int MoveCode { get => ReadInt32LittleEndian(Data.AsSpan(0x1C)); set => WriteInt32LittleEndian(Data.AsSpan(0x1C), value); } - public int DirHead { get => ReadInt32LittleEndian(Data.AsSpan(0x20)); set => WriteInt32LittleEndian(Data.AsSpan(0x20), value); } - public int MvParam0 { get => ReadInt32LittleEndian(Data.AsSpan(0x24)); set => WriteInt32LittleEndian(Data.AsSpan(0x24), value); } - public int MvParam1 { get => ReadInt32LittleEndian(Data.AsSpan(0x28)); set => WriteInt32LittleEndian(Data.AsSpan(0x28), value); } - public int MvParam2 { get => ReadInt32LittleEndian(Data.AsSpan(0x2C)); set => WriteInt32LittleEndian(Data.AsSpan(0x2C), value); } - public int LimitX { get => ReadInt32LittleEndian(Data.AsSpan(0x30)); set => WriteInt32LittleEndian(Data.AsSpan(0x30), value); } - public int LimitZ { get => ReadInt32LittleEndian(Data.AsSpan(0x34)); set => WriteInt32LittleEndian(Data.AsSpan(0x34), value); } - public int EvType { get => ReadInt32LittleEndian(Data.AsSpan(0x38)); set => WriteInt32LittleEndian(Data.AsSpan(0x38), value); } - public int MvOldDir { get => ReadInt32LittleEndian(Data.AsSpan(0x3C)); set => WriteInt32LittleEndian(Data.AsSpan(0x3C), value); } - public int MvDir { get => ReadInt32LittleEndian(Data.AsSpan(0x40)); set => WriteInt32LittleEndian(Data.AsSpan(0x40), value); } + private static void SetObjects(IReadOnlyList value) + { + if (value.Count != COUNT_OBJECTS) + throw new ArgumentOutOfRangeException(nameof(value.Count)); + // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. } } + +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class FieldObject8b +{ + public const int SIZE = 4 * 17; + + private readonly byte[] Data = new byte[SIZE]; + + public override string ToString() => $"{NameHash:X8} @ ({GridX:000},{GridY:000}) - {(Active ? "✓" : "✕")}"; + + public FieldObject8b(byte[] data, int offset) + { + data.AsSpan(offset, SIZE).CopyTo(Data); + } + + public byte Count // cnt + { + get => Data[0] ; + set => Data[0] = value; + } + + public int NameHash { get => ReadInt32LittleEndian(Data.AsSpan(0x04)); set => WriteInt32LittleEndian(Data.AsSpan(0x04), value); } + public int GridX { get => ReadInt32LittleEndian(Data.AsSpan(0x08)); set => WriteInt32LittleEndian(Data.AsSpan(0x08), value); } + public int GridY { get => ReadInt32LittleEndian(Data.AsSpan(0x0C)); set => WriteInt32LittleEndian(Data.AsSpan(0x0C), value); } + public int Height { get => ReadInt32LittleEndian(Data.AsSpan(0x10)); set => WriteInt32LittleEndian(Data.AsSpan(0x10), value); } + public int Angle { get => ReadInt32LittleEndian(Data.AsSpan(0x14)); set => WriteInt32LittleEndian(Data.AsSpan(0x14), value); } + public bool Active { get => ReadInt32LittleEndian(Data.AsSpan(0x18)) == 1; set => WriteUInt32LittleEndian(Data.AsSpan(0x18), value ? 1u : 0u); } + public int MoveCode { get => ReadInt32LittleEndian(Data.AsSpan(0x1C)); set => WriteInt32LittleEndian(Data.AsSpan(0x1C), value); } + public int DirHead { get => ReadInt32LittleEndian(Data.AsSpan(0x20)); set => WriteInt32LittleEndian(Data.AsSpan(0x20), value); } + public int MvParam0 { get => ReadInt32LittleEndian(Data.AsSpan(0x24)); set => WriteInt32LittleEndian(Data.AsSpan(0x24), value); } + public int MvParam1 { get => ReadInt32LittleEndian(Data.AsSpan(0x28)); set => WriteInt32LittleEndian(Data.AsSpan(0x28), value); } + public int MvParam2 { get => ReadInt32LittleEndian(Data.AsSpan(0x2C)); set => WriteInt32LittleEndian(Data.AsSpan(0x2C), value); } + public int LimitX { get => ReadInt32LittleEndian(Data.AsSpan(0x30)); set => WriteInt32LittleEndian(Data.AsSpan(0x30), value); } + public int LimitZ { get => ReadInt32LittleEndian(Data.AsSpan(0x34)); set => WriteInt32LittleEndian(Data.AsSpan(0x34), value); } + public int EvType { get => ReadInt32LittleEndian(Data.AsSpan(0x38)); set => WriteInt32LittleEndian(Data.AsSpan(0x38), value); } + public int MvOldDir { get => ReadInt32LittleEndian(Data.AsSpan(0x3C)); set => WriteInt32LittleEndian(Data.AsSpan(0x3C), value); } + public int MvDir { get => ReadInt32LittleEndian(Data.AsSpan(0x40)); set => WriteInt32LittleEndian(Data.AsSpan(0x40), value); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/FlagWork8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/FlagWork8b.cs index 189100c19..4e6c9ea63 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/FlagWork8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/FlagWork8b.cs @@ -1,99 +1,98 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Structure that manages the 3 event variable storage arrays. +/// +/// Comprised of 3 fixed-sized arrays, we do our read/write in-place. Each element in an array is 4 bytes. Total size: 0x55F0 +public sealed class FlagWork8b : SaveBlock, IEventFlag, ISystemFlag, IEventWork { - /// - /// Structure that manages the 3 event variable storage arrays. - /// - /// Comprised of 3 fixed-sized arrays, we do our read/write in-place. Each element in an array is 4 bytes. Total size: 0x55F0 - public sealed class FlagWork8b : SaveBlock, IEventFlag, ISystemFlag, IEventWork + public const int COUNT_WORK = 500; + public const int COUNT_FLAG = 4000; + public const int COUNT_SYSTEM = 1000; + + public const int OFS_WORK = 0; + public const int OFS_FLAG = OFS_WORK + (COUNT_WORK * 4); + public const int OFS_SYSTEM = OFS_FLAG + (COUNT_FLAG * 4); + + public const int FH_START = 0; + public const int FH_END = 63; + public const int FE_FLAG_START = 63; + public const int FE_END = 314; + public const int FV_FLAG_START = 314; + public const int FV_END = 603; + public const int FT_START = 603; + public const int FT_END = 859; + public const int FV_FLD_START = 859; + public const int FV_FLD_END = 1115; + public const int TMFLG_START = 1115; + + private const int BASE_VANISH = FV_FLAG_START; + private const int END_VANISH = FV_END - 1; + private const int COUNT_VANISH = END_VANISH - BASE_VANISH; // 0x120 + + public FlagWork8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + public int CountFlag => COUNT_FLAG; + public int CountSystem => COUNT_SYSTEM; + public int CountWork => COUNT_WORK; + + private int GetOffsetFlag(int index) { - public const int COUNT_WORK = 500; - public const int COUNT_FLAG = 4000; - public const int COUNT_SYSTEM = 1000; + if ((uint)index >= COUNT_FLAG) + throw new ArgumentOutOfRangeException(nameof(index), $"Expected a number below {COUNT_FLAG}, not {index}."); + return Offset + OFS_FLAG + (4 * index); + } + private int GetOffsetSystem(int index) + { + if ((uint)index >= COUNT_SYSTEM) + throw new ArgumentOutOfRangeException(nameof(index), $"Expected a number below {COUNT_SYSTEM}, not {index}."); + return Offset + OFS_SYSTEM + (4 * index); + } - public const int OFS_WORK = 0; - public const int OFS_FLAG = OFS_WORK + (COUNT_WORK * 4); - public const int OFS_SYSTEM = OFS_FLAG + (COUNT_FLAG * 4); + private int GetOffsetWork(int index) + { + if ((uint)index >= COUNT_WORK) + throw new ArgumentOutOfRangeException(nameof(index), $"Expected a number below {COUNT_WORK}, not {index}."); + return Offset + OFS_WORK + (4 * index); + } - public const int FH_START = 0; - public const int FH_END = 63; - public const int FE_FLAG_START = 63; - public const int FE_END = 314; - public const int FV_FLAG_START = 314; - public const int FV_END = 603; - public const int FT_START = 603; - public const int FT_END = 859; - public const int FV_FLD_START = 859; - public const int FV_FLD_END = 1115; - public const int TMFLG_START = 1115; + public bool GetFlag (int index) => ReadInt32LittleEndian(Data.AsSpan(GetOffsetFlag(index))) == 1; + public bool GetSystemFlag(int index) => ReadInt32LittleEndian(Data.AsSpan(GetOffsetSystem(index))) == 1; + public int GetWork (int index) => ReadInt32LittleEndian(Data.AsSpan(GetOffsetWork(index))); + public float GetFloatWork(int index) => ReadSingleLittleEndian(Data.AsSpan(GetOffsetWork(index))); - private const int BASE_VANISH = FV_FLAG_START; - private const int END_VANISH = FV_END - 1; - private const int COUNT_VANISH = END_VANISH - BASE_VANISH; // 0x120 + public void SetFlag (int index, bool value) => WriteUInt32LittleEndian(Data.AsSpan(GetOffsetFlag(index)), value ? 1u : 0u); + public void SetSystemFlag(int index, bool value) => WriteUInt32LittleEndian(Data.AsSpan(GetOffsetSystem(index)), value ? 1u : 0u); + public void SetWork (int index, int value) => WriteInt32LittleEndian(Data.AsSpan(GetOffsetWork(index)), value); + public void SetFloatWork (int index, float value) => WriteSingleLittleEndian(Data.AsSpan(GetOffsetWork(index)), value); - public FlagWork8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - public int CountFlag => COUNT_FLAG; - public int CountSystem => COUNT_SYSTEM; - public int CountWork => COUNT_WORK; + public void ResetFlag (int index) => SetFlag(index, false); + public void ResetVanishFlag(int index) => SetVanishFlag(index, false); + public void ResetSystemFlag(int index) => SetSystemFlag(index, false); + public void ResetWork (int index) => SetWork(index, 0); - private int GetOffsetFlag(int flagNo) - { - if ((uint)flagNo >= COUNT_FLAG) - throw new ArgumentOutOfRangeException(nameof(flagNo), $"Expected a number below {COUNT_FLAG}, not {flagNo}."); - return Offset + OFS_FLAG + (4 * flagNo); - } - private int GetOffsetSystem(int flagNo) - { - if ((uint)flagNo >= COUNT_SYSTEM) - throw new ArgumentOutOfRangeException(nameof(flagNo), $"Expected a number below {COUNT_SYSTEM}, not {flagNo}."); - return Offset + OFS_SYSTEM + (4 * flagNo); - } + public bool GetVanishFlag(int index) + { + if ((uint)index >= COUNT_VANISH) + throw new ArgumentOutOfRangeException(nameof(index), $"Expected a number below {COUNT_VANISH}, not {index}."); + return GetFlag(BASE_VANISH + index); + } - private int GetOffsetWork(int flagNo) - { - if ((uint)flagNo >= COUNT_WORK) - throw new ArgumentOutOfRangeException(nameof(flagNo), $"Expected a number below {COUNT_WORK}, not {flagNo}."); - return Offset + OFS_WORK + (4 * flagNo); - } + public void SetVanishFlag(int index, bool value) + { + if ((uint)index >= COUNT_VANISH) + throw new ArgumentOutOfRangeException(nameof(index), $"Expected a number below {COUNT_VANISH}, not {index}."); + SetFlag(BASE_VANISH + index, value); + } - public bool GetFlag (int flagNo) => ReadInt32LittleEndian(Data.AsSpan(GetOffsetFlag(flagNo))) == 1; - public bool GetSystemFlag(int flagNo) => ReadInt32LittleEndian(Data.AsSpan(GetOffsetSystem(flagNo))) == 1; - public int GetWork (int workNo) => ReadInt32LittleEndian(Data.AsSpan(GetOffsetWork(workNo))); - public float GetFloatWork(int workNo) => ReadSingleLittleEndian(Data.AsSpan(GetOffsetWork(workNo))); - - public void SetFlag (int flagNo, bool value) => WriteUInt32LittleEndian(Data.AsSpan(GetOffsetFlag(flagNo)), value ? 1u : 0u); - public void SetSystemFlag(int flagNo, bool value) => WriteUInt32LittleEndian(Data.AsSpan(GetOffsetSystem(flagNo)), value ? 1u : 0u); - public void SetWork (int workNo, int value) => WriteInt32LittleEndian(Data.AsSpan(GetOffsetWork(workNo)), value); - public void SetFloatWork (int workNo, float value) => WriteSingleLittleEndian(Data.AsSpan(GetOffsetWork(workNo)), value); - - public void ResetFlag (int flagNo) => SetFlag(flagNo, false); - public void ResetVanishFlag(int flagNo) => SetVanishFlag(flagNo, false); - public void ResetSystemFlag(int flagNo) => SetSystemFlag(flagNo, false); - public void ResetWork (int workNo) => SetWork(workNo, 0); - - public bool GetVanishFlag(int flagNo) - { - if ((uint)flagNo >= COUNT_VANISH) - throw new ArgumentOutOfRangeException(nameof(flagNo), $"Expected a number below {COUNT_VANISH}, not {flagNo}."); - return GetFlag(BASE_VANISH + flagNo); - } - - public void SetVanishFlag(int flagNo, bool value) - { - if ((uint)flagNo >= COUNT_VANISH) - throw new ArgumentOutOfRangeException(nameof(flagNo), $"Expected a number below {COUNT_VANISH}, not {flagNo}."); - SetFlag(BASE_VANISH + flagNo, value); - } - - public int BadgeCount() - { - // system flags 124-131 - int ctr = 0; - for (int i = 0; i < 8; i++) - ctr += GetSystemFlag(124 + i) ? 1 : 0; - return ctr; - } + public int BadgeCount() + { + // system flags 124-131 + int ctr = 0; + for (int i = 0; i < 8; i++) + ctr += GetSystemFlag(124 + i) ? 1 : 0; + return ctr; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/Gem8Version.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/Gem8Version.cs index 5348e9242..2e268bd3a 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/Gem8Version.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/Gem8Version.cs @@ -1,51 +1,52 @@ using System; using static PKHeX.Core.Gem8Version; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Indicates various revision values which change on each major patch update, updating the structure of the stored player save data. +/// +public enum Gem8Version +{ + None = 0, + + /// + /// Initial cartridge version shipped. + /// + /// + V1_0 = 0x25, // 37 + + /// + /// Pre-release patch. + /// + /// + V1_1 = 0x2C, // 44 + + /// + /// February patch. + /// + /// + V1_2 = 0x32, // 50 + + /// + /// March patch. + /// + /// + V1_3 = 0x34, // 52 +} + +public static class Gem8VersionExtensions { /// - /// Indicates various revision values which change on each major patch update, updating the structure of the stored player save data. + /// Returns a string to append to the savedata type info, indicating the revision of the player save data. /// - public enum Gem8Version + /// Stored version value in the save data. + public static string GetSuffixString(this Gem8Version version) => version switch { - /// - /// Initial cartridge version shipped. - /// - /// - V1_0 = 0x25, // 37 - - /// - /// Pre-release patch. - /// - /// - V1_1 = 0x2C, // 44 - - /// - /// February patch. - /// - /// - V1_2 = 0x32, // 50 - - /// - /// March patch. - /// - /// - V1_3 = 0x34, // 52 - } - - public static class Gem8VersionExtensions - { - /// - /// Returns a string to append to the savedata type info, indicating the revision of the player save data. - /// - /// Stored version value in the save data. - public static string GetSuffixString(this Gem8Version version) => version switch - { - V1_0 => "-1.0.0", // Launch Revision - V1_1 => "-1.1.0", // 1.1.0 - V1_2 => "-1.2.0", // 1.2.0 - V1_3 => "-1.3.0", // 1.3.0 - _ => throw new ArgumentOutOfRangeException(nameof(version)), - }; - } + V1_0 => "-1.0.0", // Launch Revision + V1_1 => "-1.1.0", // 1.1.0 + V1_2 => "-1.2.0", // 1.2.0 + V1_3 => "-1.3.0", // 1.3.0 + _ => throw new ArgumentOutOfRangeException(nameof(version)), + }; } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/MenuSelect8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/MenuSelect8b.cs index 5deb48824..4341c421a 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/MenuSelect8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/MenuSelect8b.cs @@ -2,50 +2,49 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Tracks the main menu items. Size: 0x44 +/// +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class MenuSelect8b : SaveBlock { - /// - /// Tracks the main menu items. Size: 0x44 - /// - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class MenuSelect8b : SaveBlock + // (TopMenuItemTypeInt32, bool IsNew)[8], TopMenuItemTypeInt32 LastSelected + private const int COUNT_ITEMS = 8; + private const int SIZE_TUPLE = 4 + 4; // int,bool32 + public MenuSelect8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public int GetMenuItem(int index) { - // (TopMenuItemTypeInt32, bool IsNew)[8], TopMenuItemTypeInt32 LastSelected - private const int COUNT_ITEMS = 8; - private const int SIZE_TUPLE = 4 + 4; // int,bool32 - public MenuSelect8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public int GetMenuItem(int index) - { - int ofs = GetOffset(index); - return ReadInt32LittleEndian(Data.AsSpan(Offset + ofs)); - } - - public void SetMenuItem(int index, int value) - { - int ofs = GetOffset(index); - WriteInt32LittleEndian(Data.AsSpan(Offset + ofs), value); - } - - public bool GetMenuItemIsNew(int index) - { - int ofs = GetOffset(index); - return ReadInt32LittleEndian(Data.AsSpan(Offset + ofs + 4)) == 1; - } - - public void SetMenuItemIsNew(int index, bool value) - { - int ofs = GetOffset(index); - WriteInt32LittleEndian(Data.AsSpan(Offset + ofs + 4), value ? 1 : 0); - } - - private static int GetOffset(int index) - { - if ((uint)index >= COUNT_ITEMS) - throw new ArgumentOutOfRangeException(nameof(index)); - return index * SIZE_TUPLE; - } - - public int LastSelectedMenu { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x40)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x40), value); } + int ofs = GetOffset(index); + return ReadInt32LittleEndian(Data.AsSpan(Offset + ofs)); } + + public void SetMenuItem(int index, int value) + { + int ofs = GetOffset(index); + WriteInt32LittleEndian(Data.AsSpan(Offset + ofs), value); + } + + public bool GetMenuItemIsNew(int index) + { + int ofs = GetOffset(index); + return ReadInt32LittleEndian(Data.AsSpan(Offset + ofs + 4)) == 1; + } + + public void SetMenuItemIsNew(int index, bool value) + { + int ofs = GetOffset(index); + WriteInt32LittleEndian(Data.AsSpan(Offset + ofs + 4), value ? 1 : 0); + } + + private static int GetOffset(int index) + { + if ((uint)index >= COUNT_ITEMS) + throw new ArgumentOutOfRangeException(nameof(index)); + return index * SIZE_TUPLE; + } + + public int LastSelectedMenu { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x40)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x40), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/MyItem8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/MyItem8b.cs index 01ff444a9..3bcc59ad9 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/MyItem8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/MyItem8b.cs @@ -2,143 +2,142 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Player item pouches storage +/// +/// size=0xBB80 ( items) +public sealed class MyItem8b : MyItem { - /// - /// Player item pouches storage - /// - /// size=0xBB80 ( items) - public sealed class MyItem8b : MyItem + public const int ItemSaveSize = 3000; + + public MyItem8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public int GetItemQuantity(ushort itemIndex) { - public const int ItemSaveSize = 3000; - - public MyItem8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public int GetItemQuantity(ushort itemIndex) - { - var ofs = InventoryPouch8b.GetItemOffset(itemIndex, Offset); - var span = Data.AsSpan(ofs, InventoryItem8b.SIZE); - var item = InventoryItem8b.Read(itemIndex, span); - return item.Count; - } - - public void SetItemQuantity(ushort itemIndex, int quantity) - { - var ofs = InventoryPouch8b.GetItemOffset(itemIndex, Offset); - var span = Data.AsSpan(ofs, InventoryItem8b.SIZE); - var item = InventoryItem8b.Read(itemIndex, span); - item.Count = quantity; - if (!item.IsValidSaveSortNumberCount) // not yet obtained - { - var type = GetType(itemIndex); - item.SortOrder = GetNextSortIndex(type); - } - item.Write(span); - } - - public static InventoryType GetType(ushort itemIndex) - { - var types = new[] - { - InventoryType.Items, InventoryType.KeyItems, InventoryType.TMHMs, InventoryType.Medicine, - InventoryType.Berries, InventoryType.Balls, InventoryType.BattleItems, InventoryType.Treasure, - }; - return Array.Find(types, z => GetLegal(z).Contains(itemIndex)); - } - - public ushort GetNextSortIndex(InventoryType type) - { - ushort max = 0; - foreach (var itemID in GetLegal(type)) - { - var ofs = InventoryPouch8b.GetItemOffset(itemID, Offset); - var span = Data.AsSpan(ofs, InventoryItem8b.SIZE); - var item = InventoryItem8b.Read(itemID, span); - if (item.SortOrder > max) - max = item.SortOrder; - } - return ++max; - } - - public override IReadOnlyList Inventory { get => ConvertToPouches(); set => LoadFromPouches(value); } - - private IReadOnlyList ConvertToPouches() - { - var pouches = new[] - { - MakePouch(InventoryType.Items, IsHeldItemLegal), - MakePouch(InventoryType.KeyItems), - MakePouch(InventoryType.TMHMs, IsHeldItemLegal), - MakePouch(InventoryType.Medicine, IsHeldItemLegal), - MakePouch(InventoryType.Berries, IsHeldItemLegal), - MakePouch(InventoryType.Balls, IsHeldItemLegal), - MakePouch(InventoryType.BattleItems, IsHeldItemLegal), - MakePouch(InventoryType.Treasure, IsHeldItemLegal), - }; - return pouches.LoadAll(Data); - } - - private void LoadFromPouches(IReadOnlyList value) - { - value.SaveAll(Data); - CleanIllegalSlots(); - } - - private void CleanIllegalSlots() - { - var all = new[] - { - GetLegal(InventoryType.Items), - GetLegal(InventoryType.KeyItems), - GetLegal(InventoryType.TMHMs), - GetLegal(InventoryType.Medicine), - GetLegal(InventoryType.Berries), - GetLegal(InventoryType.Balls), - GetLegal(InventoryType.BattleItems), - GetLegal(InventoryType.Treasure), - }.SelectMany(z => z).Distinct(); - - var hashSet = new HashSet(all); - for (ushort i = 0; i < (ushort)SAV.MaxItemID; i++) // even though there are 3000, just overwrite the ones that people will mess up. - { - if (!hashSet.Contains(i)) - InventoryItem8b.Clear(Data, InventoryPouch8b.GetItemOffset(i, Offset)); - } - } - - private InventoryPouch8b MakePouch(InventoryType type, Func? isLegal = null) - { - ushort[] legal = GetLegal(type); - var max = GetMax(type); - return new InventoryPouch8b(type, legal, max, Offset, isLegal); - } - - public static bool IsHeldItemLegal(ushort item) => !Legal.HeldItems_BS.Contains(item) || Legal.ReleasedHeldItems_8b[item]; - - private static int GetMax(InventoryType type) => type switch - { - InventoryType.Items => 999, - InventoryType.KeyItems => 1, - InventoryType.TMHMs => 999, - InventoryType.Medicine => 999, - InventoryType.Berries => 999, - InventoryType.Balls => 999, - InventoryType.BattleItems => 999, - InventoryType.Treasure => 999, - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; - - private static ushort[] GetLegal(InventoryType type) => type switch - { - InventoryType.Items => Legal.Pouch_Regular_BS, - InventoryType.KeyItems => Legal.Pouch_Key_BS, - InventoryType.TMHMs => Legal.Pouch_TMHM_BS, - InventoryType.Medicine => Legal.Pouch_Medicine_BS, - InventoryType.Berries => Legal.Pouch_Berries_BS, - InventoryType.Balls => Legal.Pouch_Ball_BS, - InventoryType.BattleItems => Legal.Pouch_Battle_BS, - InventoryType.Treasure => Legal.Pouch_Treasure_BS, - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; + var ofs = InventoryPouch8b.GetItemOffset(itemIndex, Offset); + var span = Data.AsSpan(ofs, InventoryItem8b.SIZE); + var item = InventoryItem8b.Read(itemIndex, span); + return item.Count; } + + public void SetItemQuantity(ushort itemIndex, int quantity) + { + var ofs = InventoryPouch8b.GetItemOffset(itemIndex, Offset); + var span = Data.AsSpan(ofs, InventoryItem8b.SIZE); + var item = InventoryItem8b.Read(itemIndex, span); + item.Count = quantity; + if (!item.IsValidSaveSortNumberCount) // not yet obtained + { + var type = GetType(itemIndex); + item.SortOrder = GetNextSortIndex(type); + } + item.Write(span); + } + + public static InventoryType GetType(ushort itemIndex) + { + var types = new[] + { + InventoryType.Items, InventoryType.KeyItems, InventoryType.TMHMs, InventoryType.Medicine, + InventoryType.Berries, InventoryType.Balls, InventoryType.BattleItems, InventoryType.Treasure, + }; + return Array.Find(types, z => GetLegal(z).Contains(itemIndex)); + } + + public ushort GetNextSortIndex(InventoryType type) + { + ushort max = 0; + foreach (var itemID in GetLegal(type)) + { + var ofs = InventoryPouch8b.GetItemOffset(itemID, Offset); + var span = Data.AsSpan(ofs, InventoryItem8b.SIZE); + var item = InventoryItem8b.Read(itemID, span); + if (item.SortOrder > max) + max = item.SortOrder; + } + return ++max; + } + + public override IReadOnlyList Inventory { get => ConvertToPouches(); set => LoadFromPouches(value); } + + private IReadOnlyList ConvertToPouches() + { + var pouches = new[] + { + MakePouch(InventoryType.Items, IsHeldItemLegal), + MakePouch(InventoryType.KeyItems), + MakePouch(InventoryType.TMHMs, IsHeldItemLegal), + MakePouch(InventoryType.Medicine, IsHeldItemLegal), + MakePouch(InventoryType.Berries, IsHeldItemLegal), + MakePouch(InventoryType.Balls, IsHeldItemLegal), + MakePouch(InventoryType.BattleItems, IsHeldItemLegal), + MakePouch(InventoryType.Treasure, IsHeldItemLegal), + }; + return pouches.LoadAll(Data); + } + + private void LoadFromPouches(IReadOnlyList value) + { + value.SaveAll(Data); + CleanIllegalSlots(); + } + + private void CleanIllegalSlots() + { + var all = new[] + { + GetLegal(InventoryType.Items), + GetLegal(InventoryType.KeyItems), + GetLegal(InventoryType.TMHMs), + GetLegal(InventoryType.Medicine), + GetLegal(InventoryType.Berries), + GetLegal(InventoryType.Balls), + GetLegal(InventoryType.BattleItems), + GetLegal(InventoryType.Treasure), + }.SelectMany(z => z).Distinct(); + + var hashSet = new HashSet(all); + for (ushort i = 0; i < (ushort)SAV.MaxItemID; i++) // even though there are 3000, just overwrite the ones that people will mess up. + { + if (!hashSet.Contains(i)) + InventoryItem8b.Clear(Data, InventoryPouch8b.GetItemOffset(i, Offset)); + } + } + + private InventoryPouch8b MakePouch(InventoryType type, Func? isLegal = null) + { + ushort[] legal = GetLegal(type); + var max = GetMax(type); + return new InventoryPouch8b(type, legal, max, Offset, isLegal); + } + + public static bool IsHeldItemLegal(ushort item) => !Legal.HeldItems_BS.Contains(item) || Legal.ReleasedHeldItems_8b[item]; + + private static int GetMax(InventoryType type) => type switch + { + InventoryType.Items => 999, + InventoryType.KeyItems => 1, + InventoryType.TMHMs => 999, + InventoryType.Medicine => 999, + InventoryType.Berries => 999, + InventoryType.Balls => 999, + InventoryType.BattleItems => 999, + InventoryType.Treasure => 999, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + + private static ushort[] GetLegal(InventoryType type) => type switch + { + InventoryType.Items => Legal.Pouch_Regular_BS, + InventoryType.KeyItems => Legal.Pouch_Key_BS, + InventoryType.TMHMs => Legal.Pouch_TMHM_BS, + InventoryType.Medicine => Legal.Pouch_Medicine_BS, + InventoryType.Berries => Legal.Pouch_Berries_BS, + InventoryType.Balls => Legal.Pouch_Ball_BS, + InventoryType.BattleItems => Legal.Pouch_Battle_BS, + InventoryType.Treasure => Legal.Pouch_Treasure_BS, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/MyStatus8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/MyStatus8b.cs index d709c3c42..60f43c09e 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/MyStatus8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/MyStatus8b.cs @@ -2,121 +2,120 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Player status and info about the trainer +/// +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class MyStatus8b : SaveBlock { - /// - /// Player status and info about the trainer - /// - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class MyStatus8b : SaveBlock + public const int MAX_MONEY = 999_999; + public const byte MAX_BADGE = 8; + // public const byte MAX_RANK = 5; // unused? + + public MyStatus8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + private Span OT_Trash => Data.AsSpan(Offset + 0, 0x1A); + + public string OT { - public const int MAX_MONEY = 999_999; - public const byte MAX_BADGE = 8; - // public const byte MAX_RANK = 5; // unused? + get => SAV.GetString(OT_Trash); + set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); + } - public MyStatus8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + public int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1C)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1C), (ushort)value); + } - private Span OT_Trash => Data.AsSpan(Offset + 0, 0x1A); + public int SID + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1E)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1E), (ushort)value); + } - public string OT + public uint Money + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x20)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x20), Math.Min(MAX_MONEY, value)); + } + + public bool Male { get => Data[Offset + 0x24] == 1; set => Data[Offset + 0x24] = (byte)(value ? 1 : 0); } + // 3byte align + public byte RegionCode { get => Data[Offset + 0x28]; set => Data[Offset + 0x28] = value; } + public byte BadgeCount { get => Data[Offset + 0x29]; set => Data[Offset + 0x29] = Math.Min(MAX_BADGE, value); } + public byte TrainerView { get => Data[Offset + 0x2A]; set => Data[Offset + 0x2A] = value; } + public byte ROMCode { get => Data[Offset + 0x2B]; set => Data[Offset + 0x2B] = value; } + public bool GameClear { get => Data[Offset + 0x2C] == 1; set => Data[Offset + 0x2C] = (byte)(value ? 1 : 0); } + // 3byte align + public byte BodyType { get => Data[Offset + 0x30]; set => Data[Offset + 0x30] = value; } + public Fashion FashionID { get => (Fashion)Data[Offset + 0x31]; set => Data[Offset + 0x31] = (byte)value; } + // 2byte align + + public MoveType StarterType + { + get => (MoveType)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x34)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x34), (int)value); + } + + public bool DSPlayer { get => Data[Offset + 0x38] == 1; set => Data[Offset + 0x38] = (byte)(value ? 1 : 0); } + // 3byte align + + /// turewalkMemberIndex + public int FollowIndex { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x3C)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x3C), value); } + public int X { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x40)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x40), value); } + public int Y { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x44)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x44), value); } + public float Height { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x48)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x48), value); } + public float Rotation { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x4C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x4C), value); } + + // end structure! + + public int Game + { + get => ROMCode switch { - get => SAV.GetString(OT_Trash); - set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); - } - - public int TID + 0 => (int)GameVersion.BD, + 1 => (int)GameVersion.SP, + _ => throw new ArgumentOutOfRangeException(nameof(Game)), + }; + set => ROMCode = (GameVersion)value switch { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1C)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1C), (ushort)value); - } + GameVersion.BD => 0, + GameVersion.SP => 1, + _ => throw new ArgumentOutOfRangeException(nameof(Game)), + }; + } - public int SID - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1E)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1E), (ushort)value); - } + public enum Fashion : byte + { + Everyday_M = 0, + Contest_M = 1, + Pikachu_Hoodie_M = 2, + Platinum_M = 3, + Overalls_M = 4, + Eevee_Jacket_M = 5, + Gengar_Jacket_M = 6, + Cyber_M = 7, + Summer_M = 8, + Winter_M = 9, + Spring_M = 10, + Casual_M = 11, + Leather_Jacket_M = 12, - public uint Money - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x20)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x20), Math.Min(MAX_MONEY, value)); - } - - public bool Male { get => Data[Offset + 0x24] == 1; set => Data[Offset + 0x24] = (byte)(value ? 1 : 0); } - // 3byte align - public byte RegionCode { get => Data[Offset + 0x28]; set => Data[Offset + 0x28] = value; } - public byte BadgeCount { get => Data[Offset + 0x29]; set => Data[Offset + 0x29] = Math.Min(MAX_BADGE, value); } - public byte TrainerView { get => Data[Offset + 0x2A]; set => Data[Offset + 0x2A] = value; } - public byte ROMCode { get => Data[Offset + 0x2B]; set => Data[Offset + 0x2B] = value; } - public bool GameClear { get => Data[Offset + 0x2C] == 1; set => Data[Offset + 0x2C] = (byte)(value ? 1 : 0); } - // 3byte align - public byte BodyType { get => Data[Offset + 0x30]; set => Data[Offset + 0x30] = value; } - public Fashion FashionID { get => (Fashion)Data[Offset + 0x31]; set => Data[Offset + 0x31] = (byte)value; } - // 2byte align - - public MoveType StarterType - { - get => (MoveType)ReadInt32LittleEndian(Data.AsSpan(Offset + 0x34)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x34), (int)value); - } - - public bool DSPlayer { get => Data[Offset + 0x38] == 1; set => Data[Offset + 0x38] = (byte)(value ? 1 : 0); } - // 3byte align - - /// turewalkMemberIndex - public int FollowIndex { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x3C)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x3C), value); } - public int X { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x40)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x40), value); } - public int Y { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x44)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x44), value); } - public float Height { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x48)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x48), value); } - public float Rotation { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x4C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x4C), value); } - - // end structure! - - public int Game - { - get => ROMCode switch - { - 0 => (int)GameVersion.BD, - 1 => (int)GameVersion.SP, - _ => throw new ArgumentOutOfRangeException(nameof(Game)), - }; - set => ROMCode = (GameVersion)value switch - { - GameVersion.BD => 0, - GameVersion.SP => 1, - _ => throw new ArgumentOutOfRangeException(nameof(Game)), - }; - } - - public enum Fashion : byte - { - Everyday_M = 0, - Contest_M = 1, - Pikachu_Hoodie_M = 2, - Platinum_M = 3, - Overalls_M = 4, - Eevee_Jacket_M = 5, - Gengar_Jacket_M = 6, - Cyber_M = 7, - Summer_M = 8, - Winter_M = 9, - Spring_M = 10, - Casual_M = 11, - Leather_Jacket_M = 12, - - Everyday_F = 100, - Contest_F = 101, - Pikachu_Hoodie_F = 102, - Platinum_F = 103, - Overalls_F = 104, - Eevee_Jacket_F = 105, - Gengar_Jacket_F = 106, - Cyber_F = 107, - Summer_F = 108, - Winter_F = 109, - Spring_F = 110, - Casual_F = 111, - Leather_Jacket_F = 112, - } + Everyday_F = 100, + Contest_F = 101, + Pikachu_Hoodie_F = 102, + Platinum_F = 103, + Overalls_F = 104, + Eevee_Jacket_F = 105, + Gengar_Jacket_F = 106, + Cyber_F = 107, + Summer_F = 108, + Winter_F = 109, + Spring_F = 110, + Casual_F = 111, + Leather_Jacket_F = 112, } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/MysteryBlock8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/MysteryBlock8b.cs index 974a413a3..41a25ede3 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/MysteryBlock8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/MysteryBlock8b.cs @@ -3,262 +3,261 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Stores mystery gift OneDay information. +/// +/// size ???, struct_name CONTEST_DATA +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class MysteryBlock8b : SaveBlock { - /// - /// Stores mystery gift OneDay information. - /// - /// size ???, struct_name CONTEST_DATA - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class MysteryBlock8b : SaveBlock + public MysteryBlock8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public const int RecvDataMax = 50; + public const int OneDayMax = 10; + public const int SerialDataNoMax = 895; + public const int ReserveSize = 66; + public const int FlagSize = 0x100; + public const int FlagMax = 2048; // 0x100 * 8 bitflags + + public const int OFS_RECV = 0; + public const int OFS_RECVFLAG = OFS_RECV + (RecvDataMax * RecvData8b.SIZE); // 0x2BC0 + public const int OFS_ONEDAY = OFS_RECVFLAG + FlagSize; // 0x2CC0 + public const int OFS_SERIALLOCK = OFS_ONEDAY + (OneDayMax * OneDay8b.SIZE); // 0x2D60 + + // Structure: + // RecvData[50] recvDatas; + // byte[0x100] receiveFlag; + // OneDayData[10] oneDayDatas; + // long serialLockTimestamp; + // Runtime only: bool ngFlag; -- lockout of Serial Gifts + // Runtime only: byte ngCounter; -- count of bad Serials entered + // ushort[66] reserved_ushorts; + // uint[66] reserve; + + private int GetRecvDataOffset(int index) { - public MysteryBlock8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public const int RecvDataMax = 50; - public const int OneDayMax = 10; - public const int SerialDataNoMax = 895; - public const int ReserveSize = 66; - public const int FlagSize = 0x100; - public const int FlagMax = 2048; // 0x100 * 8 bitflags - - public const int OFS_RECV = 0; - public const int OFS_RECVFLAG = OFS_RECV + (RecvDataMax * RecvData8b.SIZE); // 0x2BC0 - public const int OFS_ONEDAY = OFS_RECVFLAG + FlagSize; // 0x2CC0 - public const int OFS_SERIALLOCK = OFS_ONEDAY + (OneDayMax * OneDay8b.SIZE); // 0x2D60 - - // Structure: - // RecvData[50] recvDatas; - // byte[0x100] receiveFlag; - // OneDayData[10] oneDayDatas; - // long serialLockTimestamp; - // Runtime only: bool ngFlag; -- lockout of Serial Gifts - // Runtime only: byte ngCounter; -- count of bad Serials entered - // ushort[66] reserved_ushorts; - // uint[66] reserve; - - private int GetRecvDataOffset(int index) - { - if ((uint)index >= RecvDataMax) - throw new ArgumentOutOfRangeException(nameof(index)); - return Offset + OFS_RECV + (index * RecvData8b.SIZE); - } - private int GetFlagOffset(int flag) - { - if ((uint)flag >= FlagMax) - throw new ArgumentOutOfRangeException(nameof(flag)); - return Offset + OFS_RECVFLAG + (flag >> 8); - } - private int GetOneDayOffset(int index) - { - if ((uint)index >= OneDayMax) - throw new ArgumentOutOfRangeException(nameof(index)); - return Offset + OFS_ONEDAY + (index * OneDay8b.SIZE); - } - - public RecvData8b GetReceived(int index) => new(Data, GetRecvDataOffset(index)); - public OneDay8b GetOneDay(int index) => new(Data, GetOneDayOffset(index)); - public bool GetFlag(int flag) => FlagUtil.GetFlag(Data, GetFlagOffset(flag), flag & 7); - public void SetFlag(int flag, bool value) => FlagUtil.SetFlag(Data, GetFlagOffset(flag), flag & 7, value); - public void SetReceived(int index, RecvData8b data) => data.CopyTo(Data, GetRecvDataOffset(index)); - public void SetOneDay(int index, OneDay8b data) => data.CopyTo(Data, GetOneDayOffset(index)); - - public long TicksSerialLock { get => ReadInt64LittleEndian(Data.AsSpan(Offset + OFS_SERIALLOCK)); set => WriteInt64LittleEndian(Data.AsSpan(Offset + OFS_SERIALLOCK), value); } - public DateTime TimestampSerialLock { get => DateTime.FromFileTimeUtc(TicksSerialLock); set => TicksSerialLock = value.ToFileTimeUtc(); } - public DateTime LocalTimestampSerialLock { get => TimestampSerialLock.ToLocalTime(); set => TimestampSerialLock = value.ToUniversalTime(); } - public void ResetLock() => TicksSerialLock = 0; - - public List ReceivedFlagIndexes() - { - var result = new List(); - for (int i = 0; i < FlagSize; i++) - { - if (GetFlag(i)) - result.Add(i); - } - return result; - } - - #region Received Array - public RecvData8b[] Received - { - get => GetReceived(); - set => SetReceived(value); - } - - private RecvData8b[] GetReceived() - { - var result = new RecvData8b[RecvDataMax]; - for (int i = 0; i < result.Length; i++) - result[i] = GetReceived(i); - return result; - } - private void SetReceived(IReadOnlyList value) - { - if (value.Count != RecvDataMax) - throw new ArgumentOutOfRangeException(nameof(value.Count)); - for (int i = 0; i < value.Count; i++) - SetReceived(i, value[i]); - } - #endregion - - #region Flag Array - public bool[] ReceivedFlags - { - get => GetFlags(); - set => SetFlags(value); - } - - private bool[] GetFlags() - { - var result = new bool[FlagSize]; - for (int i = 0; i < result.Length; i++) - result[i] = GetFlag(i); - return result; - } - private void SetFlags(IReadOnlyList value) - { - if (value.Count != FlagSize) - throw new ArgumentOutOfRangeException(nameof(value.Count)); - for (int i = 0; i < value.Count; i++) - SetFlag(i, value[i]); - } - #endregion - - #region OneDay Array - public OneDay8b[] OneDay - { - get => GetOneDay(); - set => SetOneDay(value); - } - - private OneDay8b[] GetOneDay() - { - var result = new OneDay8b[OneDayMax]; - for (int i = 0; i < result.Length; i++) - result[i] = GetOneDay(i); - return result; - } - - private void SetOneDay(IReadOnlyList value) - { - if (value.Count != OneDayMax) - throw new ArgumentOutOfRangeException(nameof(value.Count)); - for (int i = 0; i < value.Count; i++) - SetOneDay(i, value[i]); - } - #endregion + if ((uint)index >= RecvDataMax) + throw new ArgumentOutOfRangeException(nameof(index)); + return Offset + OFS_RECV + (index * RecvData8b.SIZE); + } + private int GetFlagOffset(int flag) + { + if ((uint)flag >= FlagMax) + throw new ArgumentOutOfRangeException(nameof(flag)); + return Offset + OFS_RECVFLAG + (flag >> 8); + } + private int GetOneDayOffset(int index) + { + if ((uint)index >= OneDayMax) + throw new ArgumentOutOfRangeException(nameof(index)); + return Offset + OFS_ONEDAY + (index * OneDay8b.SIZE); } - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class RecvData8b + public RecvData8b GetReceived(int index) => new(Data, GetRecvDataOffset(index)); + public OneDay8b GetOneDay(int index) => new(Data, GetOneDayOffset(index)); + public bool GetFlag(int flag) => FlagUtil.GetFlag(Data, GetFlagOffset(flag), flag & 7); + public void SetFlag(int flag, bool value) => FlagUtil.SetFlag(Data, GetFlagOffset(flag), flag & 7, value); + public void SetReceived(int index, RecvData8b data) => data.CopyTo(Data, GetRecvDataOffset(index)); + public void SetOneDay(int index, OneDay8b data) => data.CopyTo(Data, GetOneDayOffset(index)); + + public long TicksSerialLock { get => ReadInt64LittleEndian(Data.AsSpan(Offset + OFS_SERIALLOCK)); set => WriteInt64LittleEndian(Data.AsSpan(Offset + OFS_SERIALLOCK), value); } + public DateTime TimestampSerialLock { get => DateTime.FromFileTimeUtc(TicksSerialLock); set => TicksSerialLock = value.ToFileTimeUtc(); } + public DateTime LocalTimestampSerialLock { get => TimestampSerialLock.ToLocalTime(); set => TimestampSerialLock = value.ToUniversalTime(); } + public void ResetLock() => TicksSerialLock = 0; + + public List ReceivedFlagIndexes() { - public const int SIZE = 0xE0; - // private const int ItemCount = 7; - // private const int DressIDCount = 7; - - private readonly int Offset; - private readonly byte[] Data; - - public RecvData8b(byte[] data, int offset = 0) + var result = new List(); + for (int i = 0; i < FlagSize; i++) { - Data = data; - Offset = offset; + if (GetFlag(i)) + result.Add(i); } - - public override string ToString() => $"{DeliveryID:0000} @ {LocalTimestamp:F}"; - public void CopyTo(Span destination, int offset) => Data.AsSpan(Offset, SIZE).CopyTo(destination[offset..]); - public void Clear() => Data.AsSpan(Offset, SIZE).Clear(); - - public long Ticks { get => ReadInt64LittleEndian(Data.AsSpan(Offset)); set => WriteInt64LittleEndian(Data.AsSpan(Offset), value); } - public DateTime Timestamp { get => DateTime.FromFileTimeUtc(Ticks); set => Ticks = value.ToFileTimeUtc(); } - public DateTime LocalTimestamp { get => Timestamp.ToLocalTime(); set => Timestamp = value.ToUniversalTime(); } - - public ushort DeliveryID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x8)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x8), value); } - public ushort TextID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xA)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xA), value); } - public byte DataType { get => Data[Offset + 0xC]; set => Data[Offset + 0xC] = value; } - public byte ReservedByte1 { get => Data[Offset + 0xD]; set => Data[Offset + 0xD] = value; } - public short ReservedShort1 { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0xE)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0xE), value); } - - #region MonsData: 0x30 Bytes - public ushort Species { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x10), value); } - public ushort Form { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x12)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x12), value); } - public ushort HeldItem{ get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14), value); } - public ushort Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x16), value); } - public ushort Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x18), value); } - public ushort Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1A), value); } - public ushort Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1C), value); } - - public string OT - { - get => StringConverter8.GetString(Data.AsSpan(Offset + 0x1E, 0x1A)); - set => StringConverter8.SetString(Data.AsSpan(Offset + 0x1E, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - } - - public byte OT_Gender { get => Data[Offset + 0x38]; set => Data[Offset + 0x38] = value; } - public byte IsEgg { get => Data[Offset + 0x39]; set => Data[Offset + 0x39] = value; } - public byte TwoRibbon { get => Data[Offset + 0x3A]; set => Data[Offset + 0x3A] = value; } - public byte Gender { get => Data[Offset + 0x3B]; set => Data[Offset + 0x3B] = value; } - public byte Language { get => Data[Offset + 0x3C]; set => Data[Offset + 0x3C] = value; } - // 0x3D 0x3E 0x3F reserved - #endregion - - public ushort Item1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x40)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x40), value); } - public ushort Item2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x50)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x50), value); } - public ushort Item3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x60)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x60), value); } - public ushort Item4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x70)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x70), value); } - public ushort Item5 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x80)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x80), value); } - public ushort Item6 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x90)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x90), value); } - public ushort Item7 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xA0)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xA0), value); } - public ushort Item1Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x42)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x42), value); } - public ushort Item2Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x52)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x52), value); } - public ushort Item3Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x62)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x62), value); } - public ushort Item4Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x72)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x72), value); } - public ushort Item5Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x82)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x82), value); } - public ushort Item6Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x92)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x92), value); } - public ushort Item7Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xA2)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xA2), value); } - // 0xC reserved for each item index - - public uint DressID1 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xB0)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xB0), value); } - public uint DressID2 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xB4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xB4), value); } - public uint DressID3 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xB8)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xB8), value); } - public uint DressID4 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xBC)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xBC), value); } - public uint DressID5 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xC0)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xC0), value); } - public uint DressID6 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xC4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xC4), value); } - public uint DressID7 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xC8)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xC8), value); } - - public uint MoneyData { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xCC)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xCC), value); } - public int Reserved1 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0xD0)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0xD0), value); } - public int Reserved2 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0xD4)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0xD4), value); } - public int Reserved3 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0xD8)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0xD8), value); } - public int Reserved4 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0xDC)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0xDC), value); } + return result; } - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class OneDay8b + #region Received Array + public RecvData8b[] Received { - public const int SIZE = 0x10; - - private readonly int Offset; - private readonly byte[] Data; - - public OneDay8b(byte[] data, int offset = 0) - { - Data = data; - Offset = offset; - } - - public override string ToString() => $"{DeliveryID:0000} @ {LocalTimestamp:F}"; - - public void CopyTo(Span destination, int offset) => Data.AsSpan(Offset, SIZE).CopyTo(destination[offset..]); - public void Clear() => Data.AsSpan(Offset, SIZE).Clear(); - - public long Ticks { get => ReadInt64LittleEndian(Data.AsSpan(Offset)); set => WriteInt64LittleEndian(Data.AsSpan(Offset), value); } - public DateTime Timestamp { get => DateTime.FromFileTimeUtc(Ticks); set => Ticks = value.ToFileTimeUtc(); } - public DateTime LocalTimestamp { get => Timestamp.ToLocalTime(); set => Timestamp = value.ToUniversalTime(); } - - public ushort DeliveryID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x8)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x8), value); } - public short Reserved1 { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0xA)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0xA), value); } - public short Reserved2 { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0xC)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0xC), value); } - public short Reserved3 { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0xE)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0xE), value); } + get => GetReceived(); + set => SetReceived(value); } + + private RecvData8b[] GetReceived() + { + var result = new RecvData8b[RecvDataMax]; + for (int i = 0; i < result.Length; i++) + result[i] = GetReceived(i); + return result; + } + private void SetReceived(IReadOnlyList value) + { + if (value.Count != RecvDataMax) + throw new ArgumentOutOfRangeException(nameof(value.Count)); + for (int i = 0; i < value.Count; i++) + SetReceived(i, value[i]); + } + #endregion + + #region Flag Array + public bool[] ReceivedFlags + { + get => GetFlags(); + set => SetFlags(value); + } + + private bool[] GetFlags() + { + var result = new bool[FlagSize]; + for (int i = 0; i < result.Length; i++) + result[i] = GetFlag(i); + return result; + } + private void SetFlags(IReadOnlyList value) + { + if (value.Count != FlagSize) + throw new ArgumentOutOfRangeException(nameof(value.Count)); + for (int i = 0; i < value.Count; i++) + SetFlag(i, value[i]); + } + #endregion + + #region OneDay Array + public OneDay8b[] OneDay + { + get => GetOneDay(); + set => SetOneDay(value); + } + + private OneDay8b[] GetOneDay() + { + var result = new OneDay8b[OneDayMax]; + for (int i = 0; i < result.Length; i++) + result[i] = GetOneDay(i); + return result; + } + + private void SetOneDay(IReadOnlyList value) + { + if (value.Count != OneDayMax) + throw new ArgumentOutOfRangeException(nameof(value.Count)); + for (int i = 0; i < value.Count; i++) + SetOneDay(i, value[i]); + } + #endregion +} + +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class RecvData8b +{ + public const int SIZE = 0xE0; + // private const int ItemCount = 7; + // private const int DressIDCount = 7; + + private readonly int Offset; + private readonly byte[] Data; + + public RecvData8b(byte[] data, int offset = 0) + { + Data = data; + Offset = offset; + } + + public override string ToString() => $"{DeliveryID:0000} @ {LocalTimestamp:F}"; + public void CopyTo(Span destination, int offset) => Data.AsSpan(Offset, SIZE).CopyTo(destination[offset..]); + public void Clear() => Data.AsSpan(Offset, SIZE).Clear(); + + public long Ticks { get => ReadInt64LittleEndian(Data.AsSpan(Offset)); set => WriteInt64LittleEndian(Data.AsSpan(Offset), value); } + public DateTime Timestamp { get => DateTime.FromFileTimeUtc(Ticks); set => Ticks = value.ToFileTimeUtc(); } + public DateTime LocalTimestamp { get => Timestamp.ToLocalTime(); set => Timestamp = value.ToUniversalTime(); } + + public ushort DeliveryID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x8)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x8), value); } + public ushort TextID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xA)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xA), value); } + public byte DataType { get => Data[Offset + 0xC]; set => Data[Offset + 0xC] = value; } + public byte ReservedByte1 { get => Data[Offset + 0xD]; set => Data[Offset + 0xD] = value; } + public short ReservedShort1 { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0xE)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0xE), value); } + + #region MonsData: 0x30 Bytes + public ushort Species { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x10), value); } + public ushort Form { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x12)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x12), value); } + public ushort HeldItem{ get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x14)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x14), value); } + public ushort Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x16)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x16), value); } + public ushort Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x18), value); } + public ushort Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1A), value); } + public ushort Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x1C), value); } + + public string OT + { + get => StringConverter8.GetString(Data.AsSpan(Offset + 0x1E, 0x1A)); + set => StringConverter8.SetString(Data.AsSpan(Offset + 0x1E, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + } + + public byte OT_Gender { get => Data[Offset + 0x38]; set => Data[Offset + 0x38] = value; } + public byte IsEgg { get => Data[Offset + 0x39]; set => Data[Offset + 0x39] = value; } + public byte TwoRibbon { get => Data[Offset + 0x3A]; set => Data[Offset + 0x3A] = value; } + public byte Gender { get => Data[Offset + 0x3B]; set => Data[Offset + 0x3B] = value; } + public byte Language { get => Data[Offset + 0x3C]; set => Data[Offset + 0x3C] = value; } + // 0x3D 0x3E 0x3F reserved + #endregion + + public ushort Item1 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x40)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x40), value); } + public ushort Item2 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x50)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x50), value); } + public ushort Item3 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x60)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x60), value); } + public ushort Item4 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x70)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x70), value); } + public ushort Item5 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x80)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x80), value); } + public ushort Item6 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x90)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x90), value); } + public ushort Item7 { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xA0)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xA0), value); } + public ushort Item1Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x42)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x42), value); } + public ushort Item2Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x52)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x52), value); } + public ushort Item3Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x62)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x62), value); } + public ushort Item4Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x72)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x72), value); } + public ushort Item5Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x82)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x82), value); } + public ushort Item6Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x92)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x92), value); } + public ushort Item7Count { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0xA2)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0xA2), value); } + // 0xC reserved for each item index + + public uint DressID1 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xB0)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xB0), value); } + public uint DressID2 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xB4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xB4), value); } + public uint DressID3 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xB8)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xB8), value); } + public uint DressID4 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xBC)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xBC), value); } + public uint DressID5 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xC0)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xC0), value); } + public uint DressID6 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xC4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xC4), value); } + public uint DressID7 { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xC8)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xC8), value); } + + public uint MoneyData { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xCC)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xCC), value); } + public int Reserved1 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0xD0)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0xD0), value); } + public int Reserved2 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0xD4)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0xD4), value); } + public int Reserved3 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0xD8)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0xD8), value); } + public int Reserved4 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0xDC)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0xDC), value); } +} + +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class OneDay8b +{ + public const int SIZE = 0x10; + + private readonly int Offset; + private readonly byte[] Data; + + public OneDay8b(byte[] data, int offset = 0) + { + Data = data; + Offset = offset; + } + + public override string ToString() => $"{DeliveryID:0000} @ {LocalTimestamp:F}"; + + public void CopyTo(Span destination, int offset) => Data.AsSpan(Offset, SIZE).CopyTo(destination[offset..]); + public void Clear() => Data.AsSpan(Offset, SIZE).Clear(); + + public long Ticks { get => ReadInt64LittleEndian(Data.AsSpan(Offset)); set => WriteInt64LittleEndian(Data.AsSpan(Offset), value); } + public DateTime Timestamp { get => DateTime.FromFileTimeUtc(Ticks); set => Ticks = value.ToFileTimeUtc(); } + public DateTime LocalTimestamp { get => Timestamp.ToLocalTime(); set => Timestamp = value.ToUniversalTime(); } + + public ushort DeliveryID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x8)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x8), value); } + public short Reserved1 { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0xA)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0xA), value); } + public short Reserved2 { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0xC)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0xC), value); } + public short Reserved3 { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0xE)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0xE), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/Party8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/Party8b.cs index 2eeca2ac0..7b81d95b8 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/Party8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/Party8b.cs @@ -1,22 +1,21 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Party data storage and metadata +/// +public sealed class Party8b : SaveBlock { - /// - /// Party data storage and metadata - /// - public sealed class Party8b : SaveBlock + public Party8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public int PartyCount { - public Party8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + get => Data[Offset + (6 * PokeCrypto.SIZE_8PARTY)]; + set => Data[Offset + (6 * PokeCrypto.SIZE_8PARTY)] = (byte)value; + } - public int PartyCount - { - get => Data[Offset + (6 * PokeCrypto.SIZE_8PARTY)]; - set => Data[Offset + (6 * PokeCrypto.SIZE_8PARTY)] = (byte)value; - } - - public int MarkingIndex - { - get => Data[Offset + (6 * PokeCrypto.SIZE_8PARTY) + 1]; - set => Data[Offset + (6 * PokeCrypto.SIZE_8PARTY) + 1] = (byte)value; - } + public int MarkingIndex + { + get => Data[Offset + (6 * PokeCrypto.SIZE_8PARTY) + 1]; + set => Data[Offset + (6 * PokeCrypto.SIZE_8PARTY) + 1] = (byte)value; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayTime8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayTime8b.cs index 0de740b8e..4943216a0 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayTime8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayTime8b.cs @@ -2,24 +2,23 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Playtime storage +/// +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class PlayTime8b : SaveBlock { - /// - /// Playtime storage - /// - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class PlayTime8b : SaveBlock + public PlayTime8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public ushort PlayedHours { - public PlayTime8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public ushort PlayedHours - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset), value); - } - - public byte PlayedMinutes { get => Data[Offset + 2]; set => Data[Offset + 2] = value; } - public byte PlayedSeconds { get => Data[Offset + 3]; set => Data[Offset + 3] = value; } - public string LastSavedTime => $"{PlayedHours:0000}ː{PlayedMinutes:00}ː{PlayedSeconds:00}"; // not : + get => ReadUInt16LittleEndian(Data.AsSpan(Offset)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset), value); } + + public byte PlayedMinutes { get => Data[Offset + 2]; set => Data[Offset + 2] = value; } + public byte PlayedSeconds { get => Data[Offset + 3]; set => Data[Offset + 3] = value; } + public string LastSavedTime => $"{PlayedHours:0000}ː{PlayedMinutes:00}ː{PlayedSeconds:00}"; // not : } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayerData8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayerData8b.cs index 91ae217a2..552850925 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayerData8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayerData8b.cs @@ -2,95 +2,94 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Player Map Location Save Data +/// +/// size 0x80, struct_name PLAYER_SAVE_DATA +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class PlayerData8b : SaveBlock { - /// - /// Player Map Location Save Data - /// - /// size 0x80, struct_name PLAYER_SAVE_DATA - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class PlayerData8b : SaveBlock + private const int SIZE_LOCATION = 4 + (4 * 3) + 4; // 20 (0x14) + + private const int OFS_LOC1 = 0x10; + private const int OFS_LOC2 = OFS_LOC1 + SIZE_LOCATION; + private const int OFS_LOC3 = OFS_LOC2 + SIZE_LOCATION; + private const int OFS_PART2 = OFS_LOC3 + SIZE_LOCATION; + private const int OFS_MAP = OFS_PART2 + 4 + 4; + private const int OFS_TOKUSHU_BOOL = OFS_MAP + SIZE_LOCATION; + private const int OFS_TOKUSHU = OFS_TOKUSHU_BOOL + 4; + private const int SIZE = OFS_TOKUSHU + SIZE_LOCATION; // 0x80 + + public PlayerData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public void Clear() => Data.AsSpan(Offset, SIZE).Clear(); + + public bool GearType { - private const int SIZE_LOCATION = 4 + (4 * 3) + 4; // 20 (0x14) - - private const int OFS_LOC1 = 0x10; - private const int OFS_LOC2 = OFS_LOC1 + SIZE_LOCATION; - private const int OFS_LOC3 = OFS_LOC2 + SIZE_LOCATION; - private const int OFS_PART2 = OFS_LOC3 + SIZE_LOCATION; - private const int OFS_MAP = OFS_PART2 + 4 + 4; - private const int OFS_TOKUSHU_BOOL = OFS_MAP + SIZE_LOCATION; - private const int OFS_TOKUSHU = OFS_TOKUSHU_BOOL + 4; - private const int SIZE = OFS_TOKUSHU + SIZE_LOCATION; // 0x80 - - public PlayerData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public void Clear() => Data.AsSpan(Offset, SIZE).Clear(); - - public bool GearType - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x00)) == 1; - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x00), value ? 1u : 0); - } - public bool ShoesFlag - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x04)) == 1; - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x04), value ? 1u : 0); - } - public uint Form - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x08)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); - } - public byte BikeColor { get => Data[Offset + 0x0C]; set => Data[Offset + 0x0C] = value; } - // 0x10: WorpPoint - Teleport - // 0x10: WorpPoint - Zenmetu - // 0x10: WorpPoint - Ananuke - - public int WarpTeleportZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x00), value); } - public float WarpTeleportX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x04)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x04), value); } - public float WarpTeleportY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x08), value); } - public float WarpTeleportZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x0C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x0C), value); } - public int WarpTeleportDir { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x10), value); } - - public int WarpZenmetuZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x00), value); } - public float WarpZenmetuX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x04)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x04), value); } - public float WarpZenmetuY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x08), value); } - public float WarpZenmetuZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x0C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x0C), value); } - public int WarpZenmetuDir { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x10), value); } - - public int WarpAnanukeZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x00), value); } - public float WarpAnanukeX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x04)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x04), value); } - public float WarpAnanukeY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x08), value); } - public float WarpAnanukeZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x0C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x0C), value); } - public int WarpAnanukeDir { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x10), value); } - - public float WalkCount - { - get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_PART2 + 0x00)); - set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_PART2 + 0x04), value); - } - public int NatukiWalkCount - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_PART2 + 0x04)); - set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_PART2 + 0x04), value); - } - - public int TownMapZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x00), value); } - public float TownMapX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x04)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x04), value); } - public float TownMapY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x08), value); } - public float TownMapZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x0C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x0C), value); } - public int TownMapDir { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x10), value); } - - public bool IsTokushuLocation - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU_BOOL + 0x00)) == 1; - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU_BOOL + 0x00), value ? 1u : 0u); - } - - public int TokushuZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x00), value); } - public float TokushuX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x04)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x04), value); } - public float TokushuY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x08), value); } - public float TokushuZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x0C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x0C), value); } - public int TokushuDir { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x10), value); } + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x00)) == 1; + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x00), value ? 1u : 0); } + public bool ShoesFlag + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x04)) == 1; + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x04), value ? 1u : 0); + } + public uint Form + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x08)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); + } + public byte BikeColor { get => Data[Offset + 0x0C]; set => Data[Offset + 0x0C] = value; } + // 0x10: WorpPoint - Teleport + // 0x10: WorpPoint - Zenmetu + // 0x10: WorpPoint - Ananuke + + public int WarpTeleportZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x00), value); } + public float WarpTeleportX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x04)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x04), value); } + public float WarpTeleportY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x08), value); } + public float WarpTeleportZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x0C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x0C), value); } + public int WarpTeleportDir { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC1 + 0x10), value); } + + public int WarpZenmetuZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x00), value); } + public float WarpZenmetuX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x04)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x04), value); } + public float WarpZenmetuY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x08), value); } + public float WarpZenmetuZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x0C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x0C), value); } + public int WarpZenmetuDir { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC2 + 0x10), value); } + + public int WarpAnanukeZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x00), value); } + public float WarpAnanukeX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x04)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x04), value); } + public float WarpAnanukeY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x08), value); } + public float WarpAnanukeZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x0C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x0C), value); } + public int WarpAnanukeDir { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_LOC3 + 0x10), value); } + + public float WalkCount + { + get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_PART2 + 0x00)); + set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_PART2 + 0x04), value); + } + public int NatukiWalkCount + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_PART2 + 0x04)); + set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_PART2 + 0x04), value); + } + + public int TownMapZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x00), value); } + public float TownMapX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x04)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x04), value); } + public float TownMapY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x08), value); } + public float TownMapZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x0C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x0C), value); } + public int TownMapDir { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_MAP + 0x10), value); } + + public bool IsTokushuLocation + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU_BOOL + 0x00)) == 1; + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU_BOOL + 0x00), value ? 1u : 0u); + } + + public int TokushuZone { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x00), value); } + public float TokushuX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x04)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x04), value); } + public float TokushuY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x08)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x08), value); } + public float TokushuZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x0C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x0C), value); } + public int TokushuDir { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_TOKUSHU + 0x10), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/PoffinSaveData8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/PoffinSaveData8b.cs index 372cf0491..de4b0155d 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/PoffinSaveData8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/PoffinSaveData8b.cs @@ -4,83 +4,82 @@ using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Details about the Poketch corner app. +/// +/// size: 0x644 +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class PoffinSaveData8b : SaveBlock { - /// - /// Details about the Poketch corner app. - /// - /// size: 0x644 - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class PoffinSaveData8b : SaveBlock + public PoffinSaveData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + // structure: + // PoffinData[] Poffins; + // int CookingCount; + + // 0x640 bytes of data is for poffins + public const int COUNT_POFFIN = 100; + + private int GetPoffinOffset(int index) { - public PoffinSaveData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - // structure: - // PoffinData[] Poffins; - // int CookingCount; - - // 0x640 bytes of data is for poffins - public const int COUNT_POFFIN = 100; - - private int GetPoffinOffset(int index) - { - if ((uint)index >= COUNT_POFFIN) - throw new ArgumentOutOfRangeException(nameof(index)); - return Offset + (index * Poffin8b.SIZE); - } - - public Poffin8b GetPoffin(int index) => new(Data, GetPoffinOffset(index)); - public void SetPoffin(int index, Poffin8b poffin) => poffin.CopyTo(Data, GetPoffinOffset(index)); - public int CookingCount { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x640)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x640), value); } - - public Poffin8b[] GetPoffins() - { - var result = new Poffin8b[COUNT_POFFIN]; - for (int i = 0; i < result.Length; i++) - result[i] = GetPoffin(i); - return result; - } - - public void SetPoffins(IReadOnlyCollection value) - { - if (value.Count != COUNT_POFFIN) - throw new ArgumentOutOfRangeException(nameof(value.Count)); - var ordered = value.OrderBy(z => z.IsNull).ThenBy(z => z.IsNew); - int ctr = 0; - foreach (var p in ordered) - SetPoffin(ctr++, p); - } - - public int CountPoffins() => GetPoffins().Count(z => z.IsNull); + if ((uint)index >= COUNT_POFFIN) + throw new ArgumentOutOfRangeException(nameof(index)); + return Offset + (index * Poffin8b.SIZE); } - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class Poffin8b + public Poffin8b GetPoffin(int index) => new(Data, GetPoffinOffset(index)); + public void SetPoffin(int index, Poffin8b poffin) => poffin.CopyTo(Data, GetPoffinOffset(index)); + public int CookingCount { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x640)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x640), value); } + + public Poffin8b[] GetPoffins() { - public const int SIZE = 0x10; - - private readonly byte[] Data = new byte[SIZE]; - - public Poffin8b(byte[] data, int offset) - { - data.AsSpan(offset, SIZE).CopyTo(Data); - } - - public void Clear() => Data.AsSpan().Clear(); - public void CopyTo(byte[] data, int offset) => Data.CopyTo(data, offset); - public void ToNull() => MstID = 0xFF; - public bool IsNull => MstID == 0xFF; - - public byte MstID { get => Data[0x00]; set => Data[0x00] = value; } - public byte Level { get => Data[0x01]; set => Data[0x01] = value; } - public byte Taste { get => Data[0x02]; set => Data[0x02] = value; } // Smoothness/feel of the Poffin => +sheen - - public bool IsNew { get => ReadUInt32LittleEndian(Data.AsSpan(0x04)) == 1; set => WriteUInt32LittleEndian(Data.AsSpan(0x04), value ? 1u : 0u); } - - public byte FlavorSpicy { get => Data[0x08]; set => Data[0x08] = value; } - public byte FlavorDry { get => Data[0x09]; set => Data[0x09] = value; } - public byte FlavorSweet { get => Data[0x0A]; set => Data[0x0A] = value; } - public byte FlavorBitter { get => Data[0x0B]; set => Data[0x0B] = value; } - public byte FlavorSour { get => Data[0x0C]; set => Data[0x0C] = value; } + var result = new Poffin8b[COUNT_POFFIN]; + for (int i = 0; i < result.Length; i++) + result[i] = GetPoffin(i); + return result; } + + public void SetPoffins(IReadOnlyCollection value) + { + if (value.Count != COUNT_POFFIN) + throw new ArgumentOutOfRangeException(nameof(value.Count)); + var ordered = value.OrderBy(z => z.IsNull).ThenBy(z => z.IsNew); + int ctr = 0; + foreach (var p in ordered) + SetPoffin(ctr++, p); + } + + public int CountPoffins() => GetPoffins().Count(z => z.IsNull); +} + +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class Poffin8b +{ + public const int SIZE = 0x10; + + private readonly byte[] Data = new byte[SIZE]; + + public Poffin8b(byte[] data, int offset) + { + data.AsSpan(offset, SIZE).CopyTo(Data); + } + + public void Clear() => Data.AsSpan().Clear(); + public void CopyTo(byte[] data, int offset) => Data.CopyTo(data, offset); + public void ToNull() => MstID = 0xFF; + public bool IsNull => MstID == 0xFF; + + public byte MstID { get => Data[0x00]; set => Data[0x00] = value; } + public byte Level { get => Data[0x01]; set => Data[0x01] = value; } + public byte Taste { get => Data[0x02]; set => Data[0x02] = value; } // Smoothness/feel of the Poffin => +sheen + + public bool IsNew { get => ReadUInt32LittleEndian(Data.AsSpan(0x04)) == 1; set => WriteUInt32LittleEndian(Data.AsSpan(0x04), value ? 1u : 0u); } + + public byte FlavorSpicy { get => Data[0x08]; set => Data[0x08] = value; } + public byte FlavorDry { get => Data[0x09]; set => Data[0x09] = value; } + public byte FlavorSweet { get => Data[0x0A]; set => Data[0x0A] = value; } + public byte FlavorBitter { get => Data[0x0B]; set => Data[0x0B] = value; } + public byte FlavorSour { get => Data[0x0C]; set => Data[0x0C] = value; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/Poketch8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/Poketch8b.cs index f60d52b7c..9b65bc3b1 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/Poketch8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/Poketch8b.cs @@ -1,45 +1,44 @@ using System.ComponentModel; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Details about the Poketch corner app. +/// +/// size: 0x19C +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class Poketch8b : SaveBlock { - /// - /// Details about the Poketch corner app. - /// - /// size: 0x19C - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class Poketch8b : SaveBlock + public const int APP_REGIST_MAX = 20; // bool array unlock flags + public const int POKETCH_MAP_MARK_MAX = 6; // mark_map_pos[6] + public const int POKETCH_DOTART_DATA_BYTESIZE = 192; // dotart data bytes + public const int POKETCH_POKE_HISTORY_COUNT_MAX = 12; // poke_history[12] + public const int POKETCH_PEDOMETER_MAX = 99999; + public const int POKETCH_CALENDER_MONTH_MAX = 12; // calendar markbit uint[12] + + public Poketch8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public enum PoketchApp8b { - public const int APP_REGIST_MAX = 20; // bool array unlock flags - public const int POKETCH_MAP_MARK_MAX = 6; // mark_map_pos[6] - public const int POKETCH_DOTART_DATA_BYTESIZE = 192; // dotart data bytes - public const int POKETCH_POKE_HISTORY_COUNT_MAX = 12; // poke_history[12] - public const int POKETCH_PEDOMETER_MAX = 99999; - public const int POKETCH_CALENDER_MONTH_MAX = 12; // calendar markbit uint[12] - - public Poketch8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public enum PoketchApp8b - { - DWATCH = 0, - CALC = 1, - MEMO = 2, - PEDOMETER = 3, - POKELIST = 4, - NATSUKI_CHECK = 5, - DOWSING = 6, - SODATEYA_CAMERA = 7, - POKEMON_HISTORY = 8, - COUNTER = 9, - AWATCH = 10, - MAP_MARKING = 11, - COINTOSS = 12, - CALENDER = 13, - DOTART = 14, - ROULETTE = 15, - POKEMON_COUNTER = 16, - KITCHEN_TIMER = 17, - COLOR_CHANGER = 18, - HIDEN_WAZA = 19, - } + DWATCH = 0, + CALC = 1, + MEMO = 2, + PEDOMETER = 3, + POKELIST = 4, + NATSUKI_CHECK = 5, + DOWSING = 6, + SODATEYA_CAMERA = 7, + POKEMON_HISTORY = 8, + COUNTER = 9, + AWATCH = 10, + MAP_MARKING = 11, + COINTOSS = 12, + CALENDER = 13, + DOTART = 14, + ROULETTE = 15, + POKEMON_COUNTER = 16, + KITCHEN_TIMER = 17, + COLOR_CHANGER = 18, + HIDEN_WAZA = 19, } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/RandomGroup8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/RandomGroup8b.cs index 1f63ed0f7..bd698239c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/RandomGroup8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/RandomGroup8b.cs @@ -3,114 +3,113 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Array storing all the Random Seed group data. +/// +/// size: 0x630 +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class RandomGroup8b : SaveBlock { - /// - /// Array storing all the Random Seed group data. - /// - /// size: 0x630 - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class RandomGroup8b : SaveBlock + public const int COUNT_GROUP = 12; + + public RandomGroup8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public RandomSeed8b[] Seeds { - public const int COUNT_GROUP = 12; - - public RandomGroup8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public RandomSeed8b[] Seeds - { - get => GetSeeds(); - set => SetSeeds(value); - } - - private RandomSeed8b[] GetSeeds() - { - var result = new RandomSeed8b[COUNT_GROUP]; - for (int i = 0; i < result.Length; i++) - result[i] = new RandomSeed8b(Data, Offset + (i * RandomSeed8b.SIZE)); - return result; - } - - private static void SetSeeds(IReadOnlyList value) - { - if (value.Count != COUNT_GROUP) - throw new ArgumentOutOfRangeException(nameof(value.Count)); - // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. - } + get => GetSeeds(); + set => SetSeeds(value); } - /// - /// Random Seed data. - /// - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class RandomSeed8b + private RandomSeed8b[] GetSeeds() { - public const int GROUP_NAME_SIZE = 16; // chars - public const int PERSON_NAME_SIZE = 32; // chars + var result = new RandomSeed8b[COUNT_GROUP]; + for (int i = 0; i < result.Length; i++) + result[i] = new RandomSeed8b(Data, Offset + (i * RandomSeed8b.SIZE)); + return result; + } - private const int OFS_GROUPNAME = 0; // 0x00 - private const int OFS_PLAYERNAME = OFS_GROUPNAME + (GROUP_NAME_SIZE * 2); // 0x20 - private const int OFS_GENDER = OFS_PLAYERNAME + (PERSON_NAME_SIZE * 2); // 0x60 - private const int OFS_REGION = OFS_GENDER + 4; // 0x68 - private const int OFS_SEED = OFS_REGION + 4; // 0x6C - private const int OFS_RAND = OFS_SEED + 8; // 0x70 - private const int OFS_TICK = OFS_RAND + 8; // 0x78 - private const int OFS_UID = OFS_TICK + 8; // 0x80 - public const int SIZE = OFS_UID + 4; // 0x84 - - private readonly int Offset; - private readonly byte[] Data; - - public RandomSeed8b(byte[] data, int offset) - { - Data = data; - Offset = offset; - } - - public string GroupName - { - get => StringConverter8.GetString(Data.AsSpan(Offset + OFS_GROUPNAME, GROUP_NAME_SIZE * 2)); - set => StringConverter8.SetString(Data.AsSpan(Offset + OFS_GROUPNAME, GROUP_NAME_SIZE * 2), value.AsSpan(), GROUP_NAME_SIZE, StringConverterOption.ClearZero); - } - - public string PlayerName - { - get => StringConverter8.GetString(Data.AsSpan(Offset + OFS_PLAYERNAME, PERSON_NAME_SIZE * 2)); - set => StringConverter8.SetString(Data.AsSpan(Offset + OFS_PLAYERNAME, PERSON_NAME_SIZE * 2), value.AsSpan(), PERSON_NAME_SIZE, StringConverterOption.ClearZero); - } - - public bool Male { get => Data[Offset + OFS_GENDER] == 1; set => Data[Offset + OFS_GENDER] = (byte)(value ? 1 : 0); } - - public int RegionCode - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_REGION)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_REGION), value); - } - - public ulong Seed - { - get => ReadUInt64LittleEndian(Data.AsSpan(Offset + OFS_SEED)); - set => WriteUInt64LittleEndian(Data.AsSpan(Offset + OFS_SEED), value); - } - - public ulong Random - { - get => ReadUInt64LittleEndian(Data.AsSpan(Offset + OFS_RAND)); - set => WriteUInt64LittleEndian(Data.AsSpan(Offset + OFS_RAND), value); - } - - public long Ticks - { - get => ReadInt64LittleEndian(Data.AsSpan(Offset + OFS_TICK)); - set => WriteInt64LittleEndian(Data.AsSpan(Offset + OFS_TICK), value); - } - - public int UserID - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_UID)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_UID), value); - } - - public DateTime Timestamp { get => DateTime.FromFileTimeUtc(Ticks); set => Ticks = value.ToFileTimeUtc(); } - public DateTime LocalTimestamp { get => Timestamp.ToLocalTime(); set => Timestamp = value.ToUniversalTime(); } + private static void SetSeeds(IReadOnlyList value) + { + if (value.Count != COUNT_GROUP) + throw new ArgumentOutOfRangeException(nameof(value)); + // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. } } + +/// +/// Random Seed data. +/// +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class RandomSeed8b +{ + public const int GROUP_NAME_SIZE = 16; // chars + public const int PERSON_NAME_SIZE = 32; // chars + + private const int OFS_GROUPNAME = 0; // 0x00 + private const int OFS_PLAYERNAME = OFS_GROUPNAME + (GROUP_NAME_SIZE * 2); // 0x20 + private const int OFS_GENDER = OFS_PLAYERNAME + (PERSON_NAME_SIZE * 2); // 0x60 + private const int OFS_REGION = OFS_GENDER + 4; // 0x68 + private const int OFS_SEED = OFS_REGION + 4; // 0x6C + private const int OFS_RAND = OFS_SEED + 8; // 0x70 + private const int OFS_TICK = OFS_RAND + 8; // 0x78 + private const int OFS_UID = OFS_TICK + 8; // 0x80 + public const int SIZE = OFS_UID + 4; // 0x84 + + private readonly int Offset; + private readonly byte[] Data; + + public RandomSeed8b(byte[] data, int offset) + { + Data = data; + Offset = offset; + } + + public string GroupName + { + get => StringConverter8.GetString(Data.AsSpan(Offset + OFS_GROUPNAME, GROUP_NAME_SIZE * 2)); + set => StringConverter8.SetString(Data.AsSpan(Offset + OFS_GROUPNAME, GROUP_NAME_SIZE * 2), value.AsSpan(), GROUP_NAME_SIZE, StringConverterOption.ClearZero); + } + + public string PlayerName + { + get => StringConverter8.GetString(Data.AsSpan(Offset + OFS_PLAYERNAME, PERSON_NAME_SIZE * 2)); + set => StringConverter8.SetString(Data.AsSpan(Offset + OFS_PLAYERNAME, PERSON_NAME_SIZE * 2), value.AsSpan(), PERSON_NAME_SIZE, StringConverterOption.ClearZero); + } + + public bool Male { get => Data[Offset + OFS_GENDER] == 1; set => Data[Offset + OFS_GENDER] = (byte)(value ? 1 : 0); } + + public int RegionCode + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_REGION)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_REGION), value); + } + + public ulong Seed + { + get => ReadUInt64LittleEndian(Data.AsSpan(Offset + OFS_SEED)); + set => WriteUInt64LittleEndian(Data.AsSpan(Offset + OFS_SEED), value); + } + + public ulong Random + { + get => ReadUInt64LittleEndian(Data.AsSpan(Offset + OFS_RAND)); + set => WriteUInt64LittleEndian(Data.AsSpan(Offset + OFS_RAND), value); + } + + public long Ticks + { + get => ReadInt64LittleEndian(Data.AsSpan(Offset + OFS_TICK)); + set => WriteInt64LittleEndian(Data.AsSpan(Offset + OFS_TICK), value); + } + + public int UserID + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_UID)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_UID), value); + } + + public DateTime Timestamp { get => DateTime.FromFileTimeUtc(Ticks); set => Ticks = value.ToFileTimeUtc(); } + public DateTime LocalTimestamp { get => Timestamp.ToLocalTime(); set => Timestamp = value.ToUniversalTime(); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/Record8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/Record8b.cs index 45659cfa2..6bd3b079c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/Record8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/Record8b.cs @@ -1,50 +1,49 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Stores 12 different sets of record data, with the earliest entry being called the "head" record index. +/// +/// size: 0x5A0 (12 * 4*30) +public sealed class Record8b : SaveBlock, IRecordStatStorage { - /// - /// Stores 12 different sets of record data, with the earliest entry being called the "head" record index. - /// - /// size: 0x5A0 (12 * 4*30) - public sealed class Record8b : SaveBlock, IRecordStatStorage + public const int RecordIndexCount = 12; // There's a total of 12 uint[30] record entries. The head one is used, not sure about the others. + public const int RecordCount = 30; + public const int RecordMaxValue = 999_999; + + public Record8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public static int GetMax(int recordID) => Records.MaxValue_BDSP[recordID]; + + private static int ClampRecord(int recordID, int value) { - public const int RecordIndexCount = 12; // There's a total of 12 uint[30] record entries. The head one is used, not sure about the others. - public const int RecordCount = 30; - public const int RecordMaxValue = 999_999; - - public Record8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public static int GetMax(int recordID) => Records.MaxValue_BDSP[recordID]; - - private static int ClampRecord(int recordID, int value) - { - var max = Records.MaxValue_BDSP[recordID]; - return Math.Min(max, value); - } - - public int GetRecordOffset(int recordID) - { - if ((uint)recordID >= RecordCount) - throw new ArgumentOutOfRangeException(nameof(recordID)); - return Offset + (sizeof(int) * recordID); - } - - public int GetRecord(int recordID) - { - var value = ReadInt32LittleEndian(Data.AsSpan(GetRecordOffset(recordID))); - if (recordID != 0) - value = ClampRecord(recordID, value); - return value; - } - - public void SetRecord(int recordID, int value) - { - if (recordID != 0) - value = Math.Min(RecordMaxValue, value); - WriteInt32LittleEndian(Data.AsSpan(GetRecordOffset(recordID)), value); - } - - public void AddRecord(int recordID, int count = 1) => SetRecord(recordID, GetRecord(recordID) + count); + var max = Records.MaxValue_BDSP[recordID]; + return Math.Min(max, value); } + + public int GetRecordOffset(int recordID) + { + if ((uint)recordID >= RecordCount) + throw new ArgumentOutOfRangeException(nameof(recordID)); + return Offset + (sizeof(int) * recordID); + } + + public int GetRecord(int recordID) + { + var value = ReadInt32LittleEndian(Data.AsSpan(GetRecordOffset(recordID))); + if (recordID != 0) + value = ClampRecord(recordID, value); + return value; + } + + public void SetRecord(int recordID, int value) + { + if (recordID != 0) + value = Math.Min(RecordMaxValue, value); + WriteInt32LittleEndian(Data.AsSpan(GetRecordOffset(recordID)), value); + } + + public void AddRecord(int recordID, int count = 1) => SetRecord(recordID, GetRecord(recordID) + count); } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/RecordAddData8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/RecordAddData8b.cs index 7428668a8..4a48cc863 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/RecordAddData8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/RecordAddData8b.cs @@ -1,102 +1,101 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Stores additional record data. +/// +/// size: 0x3C0 +public sealed class RecordAddData8b : SaveBlock { - /// - /// Stores additional record data. - /// - /// size: 0x3C0 - public sealed class RecordAddData8b : SaveBlock + // RECORD_ADD_DATA: 0x30-sized[12] (0x240 bytes), and 12*byte[32] (0x180), total 0x3C0 + public RecordAddData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + private const int COUNT_RECORD_ADD = 12; + private const int COUNT_RECORD_RANKING = 12; + private const int COUNT_RECORD_RANKING_FLAG = 32; + + public RecordAdd8b GetRecord(int index) { - // RECORD_ADD_DATA: 0x30-sized[12] (0x240 bytes), and 12*byte[32] (0x180), total 0x3C0 - public RecordAddData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - private const int COUNT_RECORD_ADD = 12; - private const int COUNT_RECORD_RANKING = 12; - private const int COUNT_RECORD_RANKING_FLAG = 32; - - public RecordAdd8b GetRecord(int index) - { - if ((uint)index >= COUNT_RECORD_ADD) - throw new ArgumentOutOfRangeException(nameof(index)); - return new RecordAdd8b(Data, Offset + (index * RecordAdd8b.SIZE)); - } - - public RecordAdd8b[] GetRecords() - { - var result = new RecordAdd8b[COUNT_RECORD_ADD]; - for (int i = 0; i < result.Length; i++) - result[i] = GetRecord(i); - return result; - } - - public void ReplaceOT(ITrainerInfo oldTrainer, ITrainerInfo newTrainer) - { - foreach (var r in GetRecords()) - { - if (string.IsNullOrWhiteSpace(r.OT)) - continue; - - if (oldTrainer.OT != r.OT || oldTrainer.TID != r.TID || oldTrainer.SID != r.SID) - continue; - - r.OT = newTrainer.OT; - r.SID = newTrainer.SID; - r.TID = newTrainer.TID; - } - } + if ((uint)index >= COUNT_RECORD_ADD) + throw new ArgumentOutOfRangeException(nameof(index)); + return new RecordAdd8b(Data, Offset + (index * RecordAdd8b.SIZE)); } - public sealed class RecordAdd8b + public RecordAdd8b[] GetRecords() { - public const int SIZE = 0x30; + var result = new RecordAdd8b[COUNT_RECORD_ADD]; + for (int i = 0; i < result.Length; i++) + result[i] = GetRecord(i); + return result; + } - public readonly byte[] Data; - private readonly int Offset; - - public RecordAdd8b(byte[] data, int offset) + public void ReplaceOT(ITrainerInfo oldTrainer, ITrainerInfo newTrainer) + { + foreach (var r in GetRecords()) { - Data = data; - Offset = offset; - } - public string OT - { - get => StringConverter8.GetString(Data.AsSpan(Offset + 0, 0x1A)); - set => StringConverter8.SetString(Data.AsSpan(Offset + 0, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); - } - // 1A reserved byte - // 1B reserved byte + if (string.IsNullOrWhiteSpace(r.OT)) + continue; - public int Language - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x1C)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x1C), value); + if (oldTrainer.OT != r.OT || oldTrainer.TID != r.TID || oldTrainer.SID != r.SID) + continue; + + r.OT = newTrainer.OT; + r.SID = newTrainer.SID; + r.TID = newTrainer.TID; } - - public byte Gender { get => Data[Offset + 0x20]; set => Data[Offset + 0x20] = value; } - // 21 - // 22 - // 23 - - public int BodyType - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x24)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x24), value); - } - - public int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x28)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x28), (ushort)value); - } - - public int SID - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x2A)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x2A), (ushort)value); - } - - // 0x2C int32 reserved } } + +public sealed class RecordAdd8b +{ + public const int SIZE = 0x30; + + public readonly byte[] Data; + private readonly int Offset; + + public RecordAdd8b(byte[] data, int offset) + { + Data = data; + Offset = offset; + } + public string OT + { + get => StringConverter8.GetString(Data.AsSpan(Offset + 0, 0x1A)); + set => StringConverter8.SetString(Data.AsSpan(Offset + 0, 0x1A), value.AsSpan(), 12, StringConverterOption.ClearZero); + } + // 1A reserved byte + // 1B reserved byte + + public int Language + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x1C)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x1C), value); + } + + public byte Gender { get => Data[Offset + 0x20]; set => Data[Offset + 0x20] = value; } + // 21 + // 22 + // 23 + + public int BodyType + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x24)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x24), value); + } + + public int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x28)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x28), (ushort)value); + } + + public int SID + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x2A)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x2A), (ushort)value); + } + + // 0x2C int32 reserved +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/SaveItemShortcut8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/SaveItemShortcut8b.cs index 842232c01..540813612 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/SaveItemShortcut8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/SaveItemShortcut8b.cs @@ -2,19 +2,18 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core -{ - /// - /// Tracks the 4 select bound item slots. Size: 0x8 (4 * u16) - /// - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class SaveItemShortcut8b : SaveBlock - { - public SaveItemShortcut8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; +namespace PKHeX.Core; - public int Item0 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x00), value); } - public int Item1 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x02)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x02), value); } - public int Item2 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); } - public int Item3 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x06)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x06), value); } - } +/// +/// Tracks the 4 select bound item slots. Size: 0x8 (4 * u16) +/// +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class SaveItemShortcut8b : SaveBlock +{ + public SaveItemShortcut8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public int Item0 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x00), value); } + public int Item1 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x02)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x02), value); } + public int Item2 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); } + public int Item3 { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x06)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x06), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/SealDeco8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/SealDeco8b.cs index 7fa166a79..3bc84d042 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/SealDeco8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/SealDeco8b.cs @@ -3,108 +3,107 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Stores customized ball seal configurations. +/// +/// size 0x4288 +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class SealBallDecoData8b : SaveBlock { - /// - /// Stores customized ball seal configurations. - /// - /// size 0x4288 - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class SealBallDecoData8b : SaveBlock + public const int COUNT_CAPSULE = 99; // CapsuleData[99] + + private const int SIZE = 4 + (COUNT_CAPSULE * SealCapsule8b.SIZE); // 0x4288 + + public SealBallDecoData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public void Clear() => Data.AsSpan(Offset, SIZE).Clear(); + + public byte CapsuleCount { get => Data[Offset]; set => Data[Offset] = value; } + + public SealCapsule8b[] Capsules { - public const int COUNT_CAPSULE = 99; // CapsuleData[99] - - private const int SIZE = 4 + (COUNT_CAPSULE * SealCapsule8b.SIZE); // 0x4288 - - public SealBallDecoData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public void Clear() => Data.AsSpan(Offset, SIZE).Clear(); - - public byte CapsuleCount { get => Data[Offset]; set => Data[Offset] = value; } - - public SealCapsule8b[] Capsules - { - get => GetCapsules(); - set => SetCapsules(value); - } - - private SealCapsule8b[] GetCapsules() - { - var result = new SealCapsule8b[COUNT_CAPSULE]; - for (int i = 0; i < result.Length; i++) - result[i] = new SealCapsule8b(Data, Offset + 4 + (i * SealCapsule8b.SIZE)); - return result; - } - - private static void SetCapsules(IReadOnlyList value) - { - if (value.Count != COUNT_CAPSULE) - throw new ArgumentOutOfRangeException(nameof(value.Count)); - // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. - } + get => GetCapsules(); + set => SetCapsules(value); } - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class SealCapsule8b + private SealCapsule8b[] GetCapsules() { - public const int COUNT_SEAL = 20; // AffixSealData[20] - public const int SIZE = 12 + (COUNT_SEAL * AffixSealData8b.SIZE); // 0xAC - - private readonly int Offset; - private readonly byte[] Data; - - public override string ToString() => $"{(Species)Species}-{EncryptionConstant:X8}-{Unknown}"; - - public SealCapsule8b(byte[] data, int offset) - { - Data = data; - Offset = offset; - } - public uint Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0), value); } - public uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 4), value); } - public uint Unknown { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 8)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 8), value); } - - public AffixSealData8b[] Seals - { - get => GetSeals(); - set => SetSeals(value); - } - - private AffixSealData8b[] GetSeals() - { - var result = new AffixSealData8b[COUNT_SEAL]; - for (int i = 0; i < result.Length; i++) - result[i] = new AffixSealData8b(Data, Offset + 0xC + (i * AffixSealData8b.SIZE)); - return result; - } - - private static void SetSeals(IReadOnlyList value) - { - if (value.Count != COUNT_SEAL) - throw new ArgumentOutOfRangeException(nameof(value.Count)); - // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. - } + var result = new SealCapsule8b[COUNT_CAPSULE]; + for (int i = 0; i < result.Length; i++) + result[i] = new SealCapsule8b(Data, Offset + 4 + (i * SealCapsule8b.SIZE)); + return result; } - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class AffixSealData8b + private static void SetCapsules(IReadOnlyList value) { - public const int SIZE = 8; // u16 id, s16 x,y,z - - private readonly int Offset; - private readonly byte[] Data; - - public override string ToString() => $"{(Seal8b)SealID}-({X},{Y},{Z})"; - - public AffixSealData8b(byte[] data, int offset) - { - Data = data; - Offset = offset; - } - - public ushort SealID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), value); } - public short X { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 2)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 2), value); } - public short Y { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 4)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 4), value); } - public short Z { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 6)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 6), value); } + if (value.Count != COUNT_CAPSULE) + throw new ArgumentOutOfRangeException(nameof(value.Count)); + // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. } } + +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class SealCapsule8b +{ + public const int COUNT_SEAL = 20; // AffixSealData[20] + public const int SIZE = 12 + (COUNT_SEAL * AffixSealData8b.SIZE); // 0xAC + + private readonly int Offset; + private readonly byte[] Data; + + public override string ToString() => $"{(Species)Species}-{EncryptionConstant:X8}-{Unknown}"; + + public SealCapsule8b(byte[] data, int offset) + { + Data = data; + Offset = offset; + } + public uint Species { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0), value); } + public uint EncryptionConstant { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 4), value); } + public uint Unknown { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 8)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 8), value); } + + public AffixSealData8b[] Seals + { + get => GetSeals(); + set => SetSeals(value); + } + + private AffixSealData8b[] GetSeals() + { + var result = new AffixSealData8b[COUNT_SEAL]; + for (int i = 0; i < result.Length; i++) + result[i] = new AffixSealData8b(Data, Offset + 0xC + (i * AffixSealData8b.SIZE)); + return result; + } + + private static void SetSeals(IReadOnlyList value) + { + if (value.Count != COUNT_SEAL) + throw new ArgumentOutOfRangeException(nameof(value.Count)); + // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays. + } +} + +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class AffixSealData8b +{ + public const int SIZE = 8; // u16 id, s16 x,y,z + + private readonly int Offset; + private readonly byte[] Data; + + public override string ToString() => $"{(Seal8b)SealID}-({X},{Y},{Z})"; + + public AffixSealData8b(byte[] data, int offset) + { + Data = data; + Offset = offset; + } + + public ushort SealID { get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0)); set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0), value); } + public short X { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 2)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 2), value); } + public short Y { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 4)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 4), value); } + public short Z { get => ReadInt16LittleEndian(Data.AsSpan(Offset + 6)); set => WriteInt16LittleEndian(Data.AsSpan(Offset + 6), value); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/SealList8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/SealList8b.cs index deed355ef..94d9bbe14 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/SealList8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/SealList8b.cs @@ -1,34 +1,33 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Array storing all the seal sticker counts the player has collected. +/// +/// size: 0x960 +public sealed class SealList8b : SaveBlock { - /// - /// Array storing all the seal sticker counts the player has collected. - /// - /// size: 0x960 - public sealed class SealList8b : SaveBlock + public const int SealSaveSize = 200; + public const int SealMaxCount = 99; + + public SealList8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public IReadOnlyList ReadItems() { - public const int SealSaveSize = 200; - public const int SealMaxCount = 99; + var result = new SealSticker8b[SealSaveSize]; + for (int i = 0; i < result.Length; i++) + result[i] = new SealSticker8b(Data, Offset, i); + return result; + } - public SealList8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public IReadOnlyList ReadItems() - { - var result = new SealSticker8b[SealSaveSize]; - for (int i = 0; i < result.Length; i++) - result[i] = new SealSticker8b(Data, Offset, i); - return result; - } - - public void WriteItems(IReadOnlyList items) - { - if (items.Count != SealSaveSize) - throw new ArgumentOutOfRangeException(nameof(items.Count)); - foreach (var item in items) - item.Write(Data, Offset); - SAV.State.Edited = true; - } + public void WriteItems(IReadOnlyList items) + { + if (items.Count != SealSaveSize) + throw new ArgumentOutOfRangeException(nameof(items.Count)); + foreach (var item in items) + item.Write(Data, Offset); + SAV.State.Edited = true; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/SealSticker8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/SealSticker8b.cs index d52fa6889..9d30e138c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/SealSticker8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/SealSticker8b.cs @@ -1,146 +1,145 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class SealSticker8b { - public sealed class SealSticker8b + private const int SIZE = 0xC; + public readonly int Index; // not serialized + public const int MaxValue = SealList8b.SealMaxCount; + + public bool IsGet { get; set; } + public int Count { get; set; } + public int TotalCount { get; set; } + + public SealSticker8b(byte[] data, int baseOffset, int index) { - private const int SIZE = 0xC; - public readonly int Index; // not serialized - public const int MaxValue = SealList8b.SealMaxCount; - - public bool IsGet { get; set; } - public int Count { get; set; } - public int TotalCount { get; set; } - - public SealSticker8b(byte[] data, int baseOffset, int index) - { - Index = index; - var offset = baseOffset + (SIZE * index); - var span = data.AsSpan(offset, SIZE); - Read(span); - } - - private void Read(ReadOnlySpan span) - { - IsGet = ReadUInt32LittleEndian(span) == 1; - Count = ReadInt32LittleEndian(span[4..]); - TotalCount = ReadInt32LittleEndian(span[8..]); - } - - public void Write(byte[] data, int baseOffset) - { - var offset = baseOffset + (SIZE * Index); - var span = data.AsSpan(offset, SIZE); - Write(span); - } - - private void Write(Span span) - { - WriteUInt32LittleEndian(span, IsGet ? 1u : 0u); - WriteInt32LittleEndian(span[4..], Count); - WriteInt32LittleEndian(span[8..], TotalCount); - } + Index = index; + var offset = baseOffset + (SIZE * index); + var span = data.AsSpan(offset, SIZE); + Read(span); } - public enum Seal8b + private void Read(ReadOnlySpan span) { - UNKNOWN = 0, - HEART_A = 1, - HEART_B = 2, - HEART_C = 3, - HEART_D = 4, - HEART_E = 5, - HEART_F = 6, - STAR_A = 7, - STAR_B = 8, - STAR_C = 9, - STAR_D = 10, - STAR_E = 11, - STAR_F = 12, - LINE_A = 13, - LINE_B = 14, - LINE_C = 15, - LINE_D = 16, - SMOKE_A = 17, - SMOKE_B = 18, - SMOKE_C = 19, - SMOKE_D = 20, - ELECTRIC_A = 21, - ELECTRIC_B = 22, - ELECTRIC_C = 23, - ELECTRIC_D = 24, - BUBBLE_A = 25, - BUBBLE_B = 26, - BUBBLE_C = 27, - BUBBLE_D = 28, - FIRE_A = 29, - FIRE_B = 30, - FIRE_C = 31, - FIRE_D = 32, - PARTY_A = 33, - PARTY_B = 34, - PARTY_C = 35, - PARTY_D = 36, - FLOWER_A = 37, - FLOWER_B = 38, - FLOWER_C = 39, - FLOWER_D = 40, - FLOWER_E = 41, - FLOWER_F = 42, - SONG_A = 43, - SONG_B = 44, - SONG_C = 45, - SONG_D = 46, - SONG_E = 47, - SONG_F = 48, - SONG_G = 49, - DARK_A = 50, - DARK_B = 51, - DARK_C = 52, - PRETTY_A = 53, - PRETTY_B = 54, - PRETTY_C = 55, - COOL_A = 56, - COOL_B = 57, - COOL_C = 58, - BURNING_A = 59, - BURNING_B = 60, - BURNING_C = 61, - SKY_A = 62, - SKY_B = 63, - SKY_C = 64, - ROCK_A = 65, - ROCK_B = 66, - ROCK_C = 67, - LEAF_A = 68, - LEAF_B = 69, - LEAF_C = 70, - SPARK_A = 71, - SPARK_B = 72, - SPARK_C = 73, - DRESSER_A = 74, - KAKKOYOSA_A = 75, - KAKKOYOSA_B = 76, - KAKKOYOSA_C = 77, - KAKKOYOSA_D = 78, - BEAUTY_A = 79, - BEAUTY_B = 80, - BEAUTY_C = 81, - BEAUTY_D = 82, - SMART_A = 83, - SMART_B = 84, - SMART_C = 85, - SMART_D = 86, - STRENGTH_A = 87, - STRENGTH_B = 88, - STRENGTH_C = 89, - STRENGTH_D = 90, - CUTE_A = 91, - CUTE_B = 92, - CUTE_C = 93, - CUTE_D = 94, - SHOWMASTER_A = 95, - CHAMPION_A = 96, + IsGet = ReadUInt32LittleEndian(span) == 1; + Count = ReadInt32LittleEndian(span[4..]); + TotalCount = ReadInt32LittleEndian(span[8..]); + } + + public void Write(byte[] data, int baseOffset) + { + var offset = baseOffset + (SIZE * Index); + var span = data.AsSpan(offset, SIZE); + Write(span); + } + + private void Write(Span span) + { + WriteUInt32LittleEndian(span, IsGet ? 1u : 0u); + WriteInt32LittleEndian(span[4..], Count); + WriteInt32LittleEndian(span[8..], TotalCount); } } + +public enum Seal8b +{ + UNKNOWN = 0, + HEART_A = 1, + HEART_B = 2, + HEART_C = 3, + HEART_D = 4, + HEART_E = 5, + HEART_F = 6, + STAR_A = 7, + STAR_B = 8, + STAR_C = 9, + STAR_D = 10, + STAR_E = 11, + STAR_F = 12, + LINE_A = 13, + LINE_B = 14, + LINE_C = 15, + LINE_D = 16, + SMOKE_A = 17, + SMOKE_B = 18, + SMOKE_C = 19, + SMOKE_D = 20, + ELECTRIC_A = 21, + ELECTRIC_B = 22, + ELECTRIC_C = 23, + ELECTRIC_D = 24, + BUBBLE_A = 25, + BUBBLE_B = 26, + BUBBLE_C = 27, + BUBBLE_D = 28, + FIRE_A = 29, + FIRE_B = 30, + FIRE_C = 31, + FIRE_D = 32, + PARTY_A = 33, + PARTY_B = 34, + PARTY_C = 35, + PARTY_D = 36, + FLOWER_A = 37, + FLOWER_B = 38, + FLOWER_C = 39, + FLOWER_D = 40, + FLOWER_E = 41, + FLOWER_F = 42, + SONG_A = 43, + SONG_B = 44, + SONG_C = 45, + SONG_D = 46, + SONG_E = 47, + SONG_F = 48, + SONG_G = 49, + DARK_A = 50, + DARK_B = 51, + DARK_C = 52, + PRETTY_A = 53, + PRETTY_B = 54, + PRETTY_C = 55, + COOL_A = 56, + COOL_B = 57, + COOL_C = 58, + BURNING_A = 59, + BURNING_B = 60, + BURNING_C = 61, + SKY_A = 62, + SKY_B = 63, + SKY_C = 64, + ROCK_A = 65, + ROCK_B = 66, + ROCK_C = 67, + LEAF_A = 68, + LEAF_B = 69, + LEAF_C = 70, + SPARK_A = 71, + SPARK_B = 72, + SPARK_C = 73, + DRESSER_A = 74, + KAKKOYOSA_A = 75, + KAKKOYOSA_B = 76, + KAKKOYOSA_C = 77, + KAKKOYOSA_D = 78, + BEAUTY_A = 79, + BEAUTY_B = 80, + BEAUTY_C = 81, + BEAUTY_D = 82, + SMART_A = 83, + SMART_B = 84, + SMART_C = 85, + SMART_D = 86, + STRENGTH_A = 87, + STRENGTH_B = 88, + STRENGTH_C = 89, + STRENGTH_D = 90, + CUTE_A = 91, + CUTE_B = 92, + CUTE_C = 93, + CUTE_D = 94, + SHOWMASTER_A = 95, + CHAMPION_A = 96, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/SystemData8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/SystemData8b.cs index d4d66b5fd..964204e90 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/SystemData8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/SystemData8b.cs @@ -2,63 +2,62 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Details about the Console and specific timestamps. +/// +/// size: 0x138 +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class SystemData8b : SaveBlock { - /// - /// Details about the Console and specific timestamps. - /// - /// size: 0x138 - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class SystemData8b : SaveBlock + // Structure: + // (u32 count, u64 FILETIME) Start Time + // (u32 count, u64 FILETIME) Latest Save Time + // (u32 count, u64 FILETIME) Penalty Timeout Time + // (u32 count, u64 FILETIME) Last Daily Event Time + // byte[208] ClockSnapshot (char[0x24]) + // u32 "fd_bgmEvnet" + // s64[6] reserved + private const int SIZE_GMTIME = 12; + private const int SIZE_SNAPSHOT = 0xD0; + + private const int OFS_SNAPSHOT = 4 + (3 * SIZE_GMTIME) + SIZE_GMTIME; // 0x34 + private const int OFS_FDBGM = OFS_SNAPSHOT + SIZE_SNAPSHOT; + private const int OFS_RESERVED = OFS_FDBGM + 4; + private const int SIZE_TOTAL = OFS_RESERVED + (6 * 8); // 0x138 + + public SystemData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public uint CountStart { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0 + (0 * SIZE_GMTIME))); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0 + (0 * SIZE_GMTIME)), value); } + public long TicksStart { get => ReadInt64LittleEndian(Data.AsSpan(Offset + 4 + (0 * SIZE_GMTIME))); set => WriteInt64LittleEndian(Data.AsSpan(Offset + 4 + (0 * SIZE_GMTIME)), value); } + public uint CountLatest { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0 + (1 * SIZE_GMTIME))); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0 + (1 * SIZE_GMTIME)), value); } + public long TicksLatest { get => ReadInt64LittleEndian(Data.AsSpan(Offset + 4 + (1 * SIZE_GMTIME))); set => WriteInt64LittleEndian(Data.AsSpan(Offset + 4 + (1 * SIZE_GMTIME)), value); } + public uint CountPenalty { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0 + (2 * SIZE_GMTIME))); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0 + (2 * SIZE_GMTIME)), value); } + public long TicksPenalty { get => ReadInt64LittleEndian(Data.AsSpan(Offset + 4 + (2 * SIZE_GMTIME))); set => WriteInt64LittleEndian(Data.AsSpan(Offset + 4 + (2 * SIZE_GMTIME)), value); } + public uint CountDaily { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0 + (3 * SIZE_GMTIME))); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0 + (3 * SIZE_GMTIME)), value); } + public long TicksDaily { get => ReadInt64LittleEndian(Data.AsSpan(Offset + 4 + (3 * SIZE_GMTIME))); set => WriteInt64LittleEndian(Data.AsSpan(Offset + 4 + (3 * SIZE_GMTIME)), value); } + + // byte[] nxSnapshot + // u32 fd_bgmEvnet + + // THESE ARE IN UTC + public DateTime TimestampStart { get => DateTime.FromFileTimeUtc(TicksStart); set => TicksStart = value.ToFileTimeUtc(); } + public DateTime TimestampLatest { get => DateTime.FromFileTimeUtc(TicksLatest); set => TicksLatest = value.ToFileTimeUtc(); } + public DateTime TimestampPenalty { get => DateTime.FromFileTimeUtc(TicksPenalty); set => TicksPenalty = value.ToFileTimeUtc(); } + public DateTime TimestampDaily { get => DateTime.FromFileTimeUtc(TicksDaily); set => TicksDaily = value.ToFileTimeUtc(); } + + public DateTime LocalTimestampStart { get => TimestampStart .ToLocalTime(); set => TimestampStart = value.ToUniversalTime(); } + public DateTime LocalTimestampLatest { get => TimestampLatest .ToLocalTime(); set => TimestampLatest = value.ToUniversalTime(); } + public DateTime LocalTimestampPenalty { get => TimestampPenalty.ToLocalTime(); set => TimestampPenalty = value.ToUniversalTime(); } + public DateTime LocalTimestampDaily { get => TimestampDaily .ToLocalTime(); set => TimestampDaily = value.ToUniversalTime(); } + + public string LastSavedTime { - // Structure: - // (u32 count, u64 FILETIME) Start Time - // (u32 count, u64 FILETIME) Latest Save Time - // (u32 count, u64 FILETIME) Penalty Timeout Time - // (u32 count, u64 FILETIME) Last Daily Event Time - // byte[208] ClockSnapshot (char[0x24]) - // u32 "fd_bgmEvnet" - // s64[6] reserved - private const int SIZE_GMTIME = 12; - private const int SIZE_SNAPSHOT = 0xD0; - - private const int OFS_SNAPSHOT = 4 + (3 * SIZE_GMTIME) + SIZE_GMTIME; // 0x34 - private const int OFS_FDBGM = OFS_SNAPSHOT + SIZE_SNAPSHOT; - private const int OFS_RESERVED = OFS_FDBGM + 4; - private const int SIZE_TOTAL = OFS_RESERVED + (6 * 8); // 0x138 - - public SystemData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public uint CountStart { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0 + (0 * SIZE_GMTIME))); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0 + (0 * SIZE_GMTIME)), value); } - public long TicksStart { get => ReadInt64LittleEndian(Data.AsSpan(Offset + 4 + (0 * SIZE_GMTIME))); set => WriteInt64LittleEndian(Data.AsSpan(Offset + 4 + (0 * SIZE_GMTIME)), value); } - public uint CountLatest { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0 + (1 * SIZE_GMTIME))); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0 + (1 * SIZE_GMTIME)), value); } - public long TicksLatest { get => ReadInt64LittleEndian(Data.AsSpan(Offset + 4 + (1 * SIZE_GMTIME))); set => WriteInt64LittleEndian(Data.AsSpan(Offset + 4 + (1 * SIZE_GMTIME)), value); } - public uint CountPenalty { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0 + (2 * SIZE_GMTIME))); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0 + (2 * SIZE_GMTIME)), value); } - public long TicksPenalty { get => ReadInt64LittleEndian(Data.AsSpan(Offset + 4 + (2 * SIZE_GMTIME))); set => WriteInt64LittleEndian(Data.AsSpan(Offset + 4 + (2 * SIZE_GMTIME)), value); } - public uint CountDaily { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0 + (3 * SIZE_GMTIME))); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0 + (3 * SIZE_GMTIME)), value); } - public long TicksDaily { get => ReadInt64LittleEndian(Data.AsSpan(Offset + 4 + (3 * SIZE_GMTIME))); set => WriteInt64LittleEndian(Data.AsSpan(Offset + 4 + (3 * SIZE_GMTIME)), value); } - - // byte[] nxSnapshot - // u32 fd_bgmEvnet - - // THESE ARE IN UTC - public DateTime TimestampStart { get => DateTime.FromFileTimeUtc(TicksStart); set => TicksStart = value.ToFileTimeUtc(); } - public DateTime TimestampLatest { get => DateTime.FromFileTimeUtc(TicksLatest); set => TicksLatest = value.ToFileTimeUtc(); } - public DateTime TimestampPenalty { get => DateTime.FromFileTimeUtc(TicksPenalty); set => TicksPenalty = value.ToFileTimeUtc(); } - public DateTime TimestampDaily { get => DateTime.FromFileTimeUtc(TicksDaily); set => TicksDaily = value.ToFileTimeUtc(); } - - public DateTime LocalTimestampStart { get => TimestampStart .ToLocalTime(); set => TimestampStart = value.ToUniversalTime(); } - public DateTime LocalTimestampLatest { get => TimestampLatest .ToLocalTime(); set => TimestampLatest = value.ToUniversalTime(); } - public DateTime LocalTimestampPenalty { get => TimestampPenalty.ToLocalTime(); set => TimestampPenalty = value.ToUniversalTime(); } - public DateTime LocalTimestampDaily { get => TimestampDaily .ToLocalTime(); set => TimestampDaily = value.ToUniversalTime(); } - - public string LastSavedTime + get { - get - { - var stamp = LocalTimestampLatest; - return $"{stamp.Year:0000}-{stamp.Month:00}-{stamp.Day:00} {stamp.Hour:00}ː{stamp.Minute:00}ː{stamp.Second:00}"; // not : - } + var stamp = LocalTimestampLatest; + return $"{stamp.Year:0000}-{stamp.Month:00}-{stamp.Day:00} {stamp.Hour:00}ː{stamp.Minute:00}ː{stamp.Second:00}"; // not : } } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgCountRecord8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgCountRecord8b.cs index 1c7f56072..8d77b6f3c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgCountRecord8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgCountRecord8b.cs @@ -2,51 +2,50 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core -{ - /// - /// Underground player metadata counts. - /// - /// size 0x20, struct_name UgCountRecord - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class UgCountRecord8b : SaveBlock - { - public UgCountRecord8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; +namespace PKHeX.Core; - public short DigFossilPlayCount - { - get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0x00)); - set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0x00), value); - } - public short NumStatueBroadcastOnTV - { - get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0x02)); - set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0x02), value); - } - public int NumTimesSecretBaseBroadcastOnTVWereLiked - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); - } - public int SomeoneSecretBaseLikeCount - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x08)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); - } - public int NumSuccessfulLightStoneSearches - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x0C)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x0C), value); - } - public long Reserved1 - { - get => ReadInt64LittleEndian(Data.AsSpan(Offset + 0x10)); - set => WriteInt64LittleEndian(Data.AsSpan(Offset + 0x10), value); - } - public long Reserved2 - { - get => ReadInt64LittleEndian(Data.AsSpan(Offset + 0x18)); - set => WriteInt64LittleEndian(Data.AsSpan(Offset + 0x18), value); - } +/// +/// Underground player metadata counts. +/// +/// size 0x20, struct_name UgCountRecord +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class UgCountRecord8b : SaveBlock +{ + public UgCountRecord8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public short DigFossilPlayCount + { + get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0x00)); + set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0x00), value); + } + public short NumStatueBroadcastOnTV + { + get => ReadInt16LittleEndian(Data.AsSpan(Offset + 0x02)); + set => WriteInt16LittleEndian(Data.AsSpan(Offset + 0x02), value); + } + public int NumTimesSecretBaseBroadcastOnTVWereLiked + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); + } + public int SomeoneSecretBaseLikeCount + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x08)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); + } + public int NumSuccessfulLightStoneSearches + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x0C)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x0C), value); + } + public long Reserved1 + { + get => ReadInt64LittleEndian(Data.AsSpan(Offset + 0x10)); + set => WriteInt64LittleEndian(Data.AsSpan(Offset + 0x10), value); + } + public long Reserved2 + { + get => ReadInt64LittleEndian(Data.AsSpan(Offset + 0x18)); + set => WriteInt64LittleEndian(Data.AsSpan(Offset + 0x18), value); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemType.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemType.cs index e50d4984e..c1fbd93e8 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemType.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemType.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum UgItemType { - public enum UgItemType - { - None, - Item, - Sphere, - Statue, - Pedestal, - } + None, + Item, + Sphere, + Statue, + Pedestal, } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemUtil.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemUtil.cs index 54f12449b..8a34ba7fa 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemUtil.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemUtil.cs @@ -1,1000 +1,999 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class UgItemUtil { - public static class UgItemUtil + public static UgItemType GetType(int ugItemID) { - public static UgItemType GetType(int ugItemID) - { - if ((uint)ugItemID >= Items.Count) - return UgItemType.None; - return Items[ugItemID].Type; - } - - public static int GetMax(int ugItemID) => GetType(ugItemID) switch - { - UgItemType.Sphere => UndergroundItemList8b.ItemMaxCount, - UgItemType.Statue => UndergroundItemList8b.StatueMaxCount, - UgItemType.Pedestal => UndergroundItemList8b.StatueMaxCount, - _ => UndergroundItemList8b.ItemMaxCount, - }; - - // ReSharper disable once NotAccessedPositionalProperty.Local - private record UgItemDef(int UgItemID, int ItemID, int SphereID, int PedestalID, int StatueID) - { - private bool IsSphere => SphereID > 0; - private bool IsItem => ItemID > 0; - private bool IsStatue => StatueID > 0; - private bool IsPedestal => PedestalID > 0; - - public UgItemType Type => - IsSphere ? UgItemType.Sphere : - IsItem ? UgItemType.Item : - IsStatue ? UgItemType.Statue : - IsPedestal ? UgItemType.Pedestal : UgItemType.None; - } - - #region Table - private static readonly IReadOnlyList Items = new UgItemDef[] - { - new(000,000,000,000,000), // None - new(001, -1,001, -1, -1), // Red Sphere S - new(002, -1,002, -1, -1), // Blue Sphere S - new(003, -1,003, -1, -1), // Green Sphere S - new(004, -1,004, -1, -1), // Prism Sphere S - new(005, -1,005, -1, -1), // Pale Sphere S - new(006, -1,006, -1, -1), // Red Sphere L - new(007, -1,007, -1, -1), // Blue Sphere L - new(008, -1,008, -1, -1), // Green Sphere L - new(009, -1,009, -1, -1), // Prism Sphere L - new(010, -1,010, -1, -1), // Pale Sphere L - new(011,028, -1, -1, -1), // Revive - new(012,029, -1, -1, -1), // Max Revive - new(013,072, -1, -1, -1), // Red Shard - new(014,073, -1, -1, -1), // Blue Shard - new(015,074, -1, -1, -1), // Yellow Shard - new(016,075, -1, -1, -1), // Green Shard - new(017,080, -1, -1, -1), // Sun Stone - new(018,081, -1, -1, -1), // Moon Stone - new(019,082, -1, -1, -1), // Fire Stone - new(020,083, -1, -1, -1), // Thunder Stone - new(021,084, -1, -1, -1), // Water Stone - new(022,085, -1, -1, -1), // Leaf Stone - new(023,110, -1, -1, -1), // Oval Stone - new(024,229, -1, -1, -1), // Everstone - new(025,111, -1, -1, -1), // Odd Keystone - new(026,091, -1, -1, -1), // Star Piece - new(027,093, -1, -1, -1), // Heart Scale - new(028,099, -1, -1, -1), // Root Fossil - new(029,100, -1, -1, -1), // Claw Fossil - new(030,101, -1, -1, -1), // Helix Fossil - new(031,102, -1, -1, -1), // Dome Fossil - new(032,103, -1, -1, -1), // Old Amber - new(033,104, -1, -1, -1), // Armor Fossil - new(034,105, -1, -1, -1), // Skull Fossil - new(035,106, -1, -1, -1), // Rare Bone - new(036,269, -1, -1, -1), // Light Clay - new(037,278, -1, -1, -1), // Iron Ball - new(038,282, -1, -1, -1), // Icy Rock - new(039,283, -1, -1, -1), // Smooth Rock - new(040,284, -1, -1, -1), // Heat Rock - new(041,285, -1, -1, -1), // Damp Rock - new(042,1808, -1, -1, -1), // Mysterious Shard S - new(043,1809, -1, -1, -1), // Mysterious Shard L - new(044,238, -1, -1, -1), // Hard Stone - new(045,298, -1, -1, -1), // Flame Plate - new(046,299, -1, -1, -1), // Splash Plate - new(047,300, -1, -1, -1), // Zap Plate - new(048,301, -1, -1, -1), // Meadow Plate - new(049,302, -1, -1, -1), // Icicle Plate - new(050,303, -1, -1, -1), // Fist Plate - new(051,304, -1, -1, -1), // Toxic Plate - new(052,305, -1, -1, -1), // Earth Plate - new(053,306, -1, -1, -1), // Sky Plate - new(054,307, -1, -1, -1), // Mind Plate - new(055,308, -1, -1, -1), // Insect Plate - new(056,309, -1, -1, -1), // Stone Plate - new(057,310, -1, -1, -1), // Spooky Plate - new(058,311, -1, -1, -1), // Draco Plate - new(059,312, -1, -1, -1), // Dread Plate - new(060,313, -1, -1, -1), // Iron Plate - new(061,644, -1, -1, -1), // Pixie Plate - new(062, -1, -1, -1,000), // - new(063, -1, -1, -1,000), // - new(064, -1, -1, -1,000), // - new(065, -1, -1, -1,000), // - new(066, -1, -1, -1,000), // - new(067, -1, -1, -1,000), // - new(068, -1, -1, -1,000), // - new(069, -1, -1, -1,000), // - new(070, -1, -1, -1,000), // - new(071, -1, -1, -1,000), // - new(072, -1, -1, -1,000), // - new(073, -1, -1, -1,000), // - new(074, -1, -1, -1,000), // - new(075, -1, -1, -1,000), // - new(076, -1, -1, -1,000), // - new(077, -1, -1, -1,000), // - new(078, -1, -1, -1,000), // - new(079, -1, -1, -1,000), // - new(080, -1, -1, -1,000), // - new(081, -1, -1, -1,000), // - new(082, -1, -1, -1,000), // - new(083, -1, -1, -1,000), // - new(084, -1, -1, -1,000), // - new(085, -1, -1, -1,000), // - new(086, -1, -1, -1,000), // - new(087, -1, -1, -1,000), // - new(088, -1, -1, -1,000), // - new(089, -1, -1, -1,000), // - new(090, -1, -1, -1,000), // - new(091, -1, -1, -1,000), // - new(092, -1, -1, -1,000), // - new(093, -1, -1, -1,000), // - new(094, -1, -1, -1,000), // - new(095, -1, -1, -1,000), // - new(096, -1, -1, -1,000), // - new(097, -1, -1, -1,000), // - new(098, -1, -1, -1,000), // - new(099, -1, -1, -1,000), // - new(100, -1, -1, -1,000), // - new(101, -1, -1, -1,000), // - new(102, -1, -1, -1,000), // - new(103, -1, -1, -1,000), // - new(104, -1, -1, -1,000), // - new(105, -1, -1, -1,000), // - new(106, -1, -1, -1,000), // - new(107, -1, -1, -1,000), // - new(108, -1, -1, -1,000), // - new(109, -1, -1, -1,000), // - new(110, -1, -1, -1,000), // - new(111, -1, -1, -1,000), // - new(112, -1, -1, -1,000), // - new(113, -1, -1, -1,000), // - new(114, -1, -1, -1,000), // - new(115, -1, -1, -1,000), // - new(116, -1, -1, -1,000), // - new(117, -1, -1, -1,000), // - new(118, -1, -1, -1,000), // - new(119, -1, -1, -1,000), // - new(120, -1, -1, -1,000), // - new(121, -1, -1, -1,000), // - new(122, -1, -1, -1,000), // - new(123, -1, -1, -1,000), // - new(124, -1, -1, -1,000), // - new(125, -1, -1, -1,000), // - new(126, -1, -1, -1,000), // - new(127, -1, -1, -1,000), // - new(128, -1, -1, -1,000), // - new(129, -1, -1, -1,000), // - new(130, -1, -1, -1,000), // - new(131, -1, -1, -1,000), // - new(132, -1, -1, -1,000), // - new(133, -1, -1, -1,000), // - new(134, -1, -1, -1,000), // - new(135, -1, -1, -1,000), // - new(136, -1, -1, -1,000), // - new(137, -1, -1, -1,000), // - new(138, -1, -1, -1,000), // - new(139, -1, -1, -1,000), // - new(140, -1, -1, -1,000), // - new(141, -1, -1, -1,000), // - new(142, -1, -1, -1,000), // - new(143, -1, -1, -1,000), // - new(144, -1, -1, -1,000), // - new(145, -1, -1, -1,000), // - new(146, -1, -1, -1,000), // - new(147, -1, -1, -1,000), // - new(148, -1, -1, -1,000), // - new(149, -1, -1, -1,000), // - new(150, -1, -1, -1,000), // - new(151, -1, -1, -1,000), // - new(152, -1, -1, -1,000), // - new(153, -1, -1, -1,000), // - new(154, -1, -1, -1,000), // - new(155, -1, -1, -1,000), // - new(156, -1, -1, -1,000), // - new(157, -1, -1, -1,000), // - new(158, -1, -1, -1,000), // - new(159, -1, -1, -1,000), // - new(160, -1, -1, -1,000), // - new(161, -1, -1, -1,000), // - new(162, -1, -1, -1,000), // - new(163, -1, -1, -1,000), // - new(164, -1, -1, -1,000), // - new(165, -1, -1, -1,000), // - new(166, -1, -1, -1,000), // - new(167, -1, -1, -1,000), // - new(168, -1, -1, -1,000), // - new(169, -1, -1, -1,000), // - new(170, -1, -1, -1,000), // - new(171, -1, -1, -1,000), // - new(172, -1, -1, -1,000), // - new(173, -1, -1, -1,000), // - new(174, -1, -1, -1,000), // - new(175, -1, -1, -1,000), // - new(176, -1, -1, -1,000), // - new(177, -1, -1, -1,000), // - new(178, -1, -1, -1,000), // - new(179, -1, -1, -1,000), // - new(180, -1, -1, -1,000), // - new(181, -1, -1, -1,000), // - new(182, -1, -1, -1,000), // - new(183, -1, -1, -1,000), // - new(184, -1, -1, -1,000), // - new(185, -1, -1, -1,000), // - new(186, -1, -1, -1,000), // - new(187, -1, -1, -1,000), // - new(188, -1, -1, -1,000), // - new(189, -1, -1, -1,000), // - new(190, -1, -1, -1,000), // - new(191, -1, -1, -1,000), // - new(192, -1, -1, -1,000), // - new(193, -1, -1, -1,000), // - new(194, -1, -1, -1,000), // - new(195, -1, -1, -1,000), // - new(196, -1, -1, -1,000), // - new(197, -1, -1, -1,000), // - new(198, -1, -1, -1,000), // - new(199, -1, -1, -1,000), // - new(200, -1, -1, -1,000), // - new(201, -1, -1, -1,000), // - new(202, -1, -1, -1,000), // - new(203, -1, -1, -1,000), // - new(204, -1, -1, -1,000), // - new(205, -1, -1, -1,000), // - new(206, -1, -1, -1,000), // - new(207, -1, -1, -1,000), // - new(208, -1, -1, -1,000), // - new(209, -1, -1, -1,000), // - new(210, -1, -1, -1,000), // - new(211, -1, -1, -1,000), // - new(212, -1, -1, -1,000), // - new(213, -1, -1, -1,000), // - new(214, -1, -1, -1,000), // - new(215, -1, -1, -1,000), // - new(216, -1, -1, -1,000), // - new(217, -1, -1, -1,000), // - new(218, -1, -1, -1,000), // - new(219, -1, -1, -1,000), // - new(220, -1, -1, -1,000), // - new(221, -1, -1, -1,000), // - new(222, -1, -1, -1,000), // - new(223, -1, -1, -1,000), // - new(224, -1, -1, -1,000), // - new(225, -1, -1, -1,000), // - new(226, -1, -1, -1,000), // - new(227, -1, -1, -1,000), // - new(228, -1, -1, -1,000), // - new(229, -1, -1, -1,000), // - new(230, -1, -1, -1,000), // - new(231, -1, -1, -1,000), // - new(232, -1, -1, -1,000), // - new(233, -1, -1, -1,000), // - new(234, -1, -1, -1,000), // - new(235, -1, -1, -1,000), // - new(236, -1, -1, -1,000), // - new(237, -1, -1, -1,000), // - new(238, -1, -1, -1,000), // - new(239, -1, -1, -1,000), // - new(240, -1, -1, -1,000), // - new(241, -1, -1, -1,000), // - new(242, -1, -1, -1,000), // - new(243, -1, -1, -1,000), // - new(244, -1, -1, -1,000), // - new(245, -1, -1, -1,000), // - new(246, -1, -1, -1,000), // - new(247, -1, -1, -1,000), // - new(248, -1, -1, -1,000), // - new(249, -1, -1, -1,000), // - new(250, -1, -1, -1,000), // - new(251, -1, -1, -1,000), // - new(252, -1, -1, -1,000), // - new(253, -1, -1, -1,000), // - new(254, -1, -1, -1,000), // - new(255, -1, -1, -1,000), // - new(256, -1, -1, -1,000), // - new(257, -1, -1, -1,000), // - new(258, -1, -1, -1,000), // - new(259, -1, -1, -1,000), // - new(260, -1, -1, -1,000), // - new(261, -1, -1, -1,000), // - new(262, -1, -1, -1,000), // - new(263, -1, -1, -1,000), // - new(264, -1, -1, -1,000), // - new(265, -1, -1, -1,000), // - new(266, -1, -1, -1,000), // - new(267, -1, -1, -1,000), // - new(268, -1, -1, -1,000), // - new(269, -1, -1, -1,000), // - new(270, -1, -1, -1,000), // - new(271, -1, -1, -1,000), // - new(272, -1, -1, -1,000), // - new(273, -1, -1, -1,000), // - new(274, -1, -1, -1,000), // - new(275, -1, -1, -1,000), // - new(276, -1, -1, -1,000), // - new(277, -1, -1, -1,000), // - new(278, -1, -1, -1,000), // - new(279, -1, -1, -1,000), // - new(280, -1, -1, -1,000), // - new(281, -1, -1, -1,000), // - new(282, -1, -1, -1,000), // - new(283, -1, -1, -1,000), // - new(284, -1, -1, -1,000), // - new(285, -1, -1, -1,000), // - new(286, -1, -1, -1,000), // - new(287, -1, -1, -1,000), // - new(288, -1, -1, -1,000), // - new(289, -1, -1, -1,000), // - new(290, -1, -1, -1,000), // - new(291, -1, -1, -1,000), // - new(292, -1, -1, -1,000), // - new(293, -1, -1, -1,000), // - new(294, -1, -1, -1,000), // - new(295, -1, -1, -1,000), // - new(296, -1, -1, -1,000), // - new(297, -1, -1, -1,000), // - new(298, -1, -1, -1,000), // - new(299, -1, -1, -1,000), // - new(300, -1, -1, -1,000), // - new(301, -1, -1, -1,000), // - new(302, -1, -1, -1,000), // - new(303, -1, -1, -1,000), // - new(304, -1, -1, -1,000), // - new(305, -1, -1, -1,000), // - new(306, -1, -1, -1,000), // - new(307, -1, -1, -1,000), // - new(308, -1, -1, -1,000), // - new(309, -1, -1, -1,000), // - new(310, -1, -1, -1,000), // - new(311, -1, -1, -1,000), // - new(312, -1, -1, -1,000), // - new(313, -1, -1, -1,000), // - new(314, -1, -1, -1,000), // - new(315, -1, -1, -1,000), // - new(316, -1, -1, -1,000), // - new(317, -1, -1, -1,000), // - new(318, -1, -1, -1,000), // - new(319, -1, -1, -1,000), // - new(320, -1, -1, -1,000), // - new(321, -1, -1, -1,000), // - new(322, -1, -1, -1,000), // - new(323, -1, -1, -1,000), // - new(324, -1, -1, -1,000), // - new(325, -1, -1, -1,000), // - new(326, -1, -1, -1,000), // - new(327, -1, -1, -1,000), // - new(328, -1, -1, -1,000), // - new(329, -1, -1, -1,000), // - new(330, -1, -1, -1,000), // - new(331, -1, -1, -1,000), // - new(332, -1, -1, -1,000), // - new(333, -1, -1, -1,000), // - new(334, -1, -1, -1,000), // - new(335, -1, -1, -1,000), // - new(336, -1, -1, -1,000), // - new(337, -1, -1, -1,000), // - new(338, -1, -1, -1,000), // - new(339, -1, -1, -1,000), // - new(340, -1, -1, -1,000), // - new(341, -1, -1, -1,000), // - new(342, -1, -1, -1,000), // - new(343, -1, -1, -1,000), // - new(344, -1, -1, -1,000), // - new(345, -1, -1, -1,000), // - new(346, -1, -1, -1,000), // - new(347, -1, -1, -1,000), // - new(348, -1, -1, -1,000), // - new(349, -1, -1, -1,000), // - new(350, -1, -1, -1,000), // - new(351, -1, -1, -1,000), // - new(352, -1, -1, -1,000), // - new(353, -1, -1, -1,000), // - new(354, -1, -1, -1,000), // - new(355, -1, -1, -1,000), // - new(356, -1, -1, -1,000), // - new(357, -1, -1, -1,000), // - new(358, -1, -1, -1,000), // - new(359, -1, -1, -1,000), // - new(360, -1, -1, -1,000), // - new(361, -1, -1, -1,000), // - new(362, -1, -1, -1,000), // - new(363, -1, -1, -1,000), // - new(364, -1, -1, -1,000), // - new(365, -1, -1, -1,000), // - new(366, -1, -1, -1,000), // - new(367, -1, -1, -1,000), // - new(368, -1, -1, -1,000), // - new(369, -1, -1, -1,000), // - new(370, -1, -1,001, -1), // Square Pedestal XS - new(371, -1, -1,002, -1), // Square Pedestal S - new(372, -1, -1,003, -1), // Square Pedestal M - new(373, -1, -1,004, -1), // Square Pedestal L - new(374, -1, -1,005, -1), // Round Pedestal XS - new(375, -1, -1,006, -1), // Round Pedestal M - new(376, -1, -1,007, -1), // Round Pedestal L - new(377, -1, -1,008, -1), // Sturdy Pedestal XS - new(378, -1, -1,009, -1), // Sturdy Pedestal S - new(379, -1, -1,010, -1), // Sturdy Pedestal M - new(380, -1, -1,011, -1), // Sturdy Pedestal L - new(381, -1, -1,012, -1), // Clear Pedestal XS - new(382, -1, -1,013, -1), // Clear Pedestal S - new(383, -1, -1,014, -1), // Clear Pedestal M - new(384, -1, -1,015, -1), // Clear Pedestal L - new(385, -1, -1,016, -1), // Dawn Pedestal L - new(386, -1, -1,017, -1), // Dawn Pedestal XL - new(387, -1, -1,018, -1), // Night Pedestal L - new(388, -1, -1,019, -1), // Night Pedestal XL - new(389, -1, -1,020, -1), // Diamond Pedestal L - new(390, -1, -1,021, -1), // Diamond Pedestal XL - new(391, -1, -1,022, -1), // Pearl Pedestal L - new(392, -1, -1,023, -1), // Pearl Pedestal XL - new(393, -1, -1,024, -1), // Spin Pedestal XS - new(394, -1, -1,025, -1), // Spin Pedestal M - new(395, -1, -1,026, -1), // Spin Pedestal L - new(396, -1, -1,027, -1), // Spinback Pedestal XS - new(397, -1, -1,028, -1), // Spinback Pedestal M - new(398, -1, -1,029, -1), // Spinback Pedestal L - new(399,1810, -1, -1, -1), // Digger Drill - new(400,328, -1, -1, -1), // TM01 - new(401,329, -1, -1, -1), // TM02 - new(402,330, -1, -1, -1), // TM03 - new(403,331, -1, -1, -1), // TM04 - new(404,332, -1, -1, -1), // TM05 - new(405,333, -1, -1, -1), // TM06 - new(406,334, -1, -1, -1), // TM07 - new(407,335, -1, -1, -1), // TM08 - new(408,336, -1, -1, -1), // TM09 - new(409,337, -1, -1, -1), // TM10 - new(410,338, -1, -1, -1), // TM11 - new(411,339, -1, -1, -1), // TM12 - new(412,340, -1, -1, -1), // TM13 - new(413,341, -1, -1, -1), // TM14 - new(414,342, -1, -1, -1), // TM15 - new(415,343, -1, -1, -1), // TM16 - new(416,344, -1, -1, -1), // TM17 - new(417,345, -1, -1, -1), // TM18 - new(418,346, -1, -1, -1), // TM19 - new(419,347, -1, -1, -1), // TM20 - new(420,348, -1, -1, -1), // TM21 - new(421,349, -1, -1, -1), // TM22 - new(422,350, -1, -1, -1), // TM23 - new(423,351, -1, -1, -1), // TM24 - new(424,352, -1, -1, -1), // TM25 - new(425,353, -1, -1, -1), // TM26 - new(426,354, -1, -1, -1), // TM27 - new(427,355, -1, -1, -1), // TM28 - new(428,356, -1, -1, -1), // TM29 - new(429,357, -1, -1, -1), // TM30 - new(430,358, -1, -1, -1), // TM31 - new(431,359, -1, -1, -1), // TM32 - new(432,360, -1, -1, -1), // TM33 - new(433,361, -1, -1, -1), // TM34 - new(434,362, -1, -1, -1), // TM35 - new(435,363, -1, -1, -1), // TM36 - new(436,364, -1, -1, -1), // TM37 - new(437,365, -1, -1, -1), // TM38 - new(438,366, -1, -1, -1), // TM39 - new(439,367, -1, -1, -1), // TM40 - new(440,368, -1, -1, -1), // TM41 - new(441,369, -1, -1, -1), // TM42 - new(442,370, -1, -1, -1), // TM43 - new(443,371, -1, -1, -1), // TM44 - new(444,372, -1, -1, -1), // TM45 - new(445,373, -1, -1, -1), // TM46 - new(446,374, -1, -1, -1), // TM47 - new(447,375, -1, -1, -1), // TM48 - new(448,376, -1, -1, -1), // TM49 - new(449,377, -1, -1, -1), // TM50 - new(450,378, -1, -1, -1), // TM51 - new(451,379, -1, -1, -1), // TM52 - new(452,380, -1, -1, -1), // TM53 - new(453,381, -1, -1, -1), // TM54 - new(454,382, -1, -1, -1), // TM55 - new(455,383, -1, -1, -1), // TM56 - new(456,384, -1, -1, -1), // TM57 - new(457,385, -1, -1, -1), // TM58 - new(458,386, -1, -1, -1), // TM59 - new(459,387, -1, -1, -1), // TM60 - new(460,388, -1, -1, -1), // TM61 - new(461,389, -1, -1, -1), // TM62 - new(462,390, -1, -1, -1), // TM63 - new(463,391, -1, -1, -1), // TM64 - new(464,392, -1, -1, -1), // TM65 - new(465,393, -1, -1, -1), // TM66 - new(466,394, -1, -1, -1), // TM67 - new(467,395, -1, -1, -1), // TM68 - new(468,396, -1, -1, -1), // TM69 - new(469,397, -1, -1, -1), // TM70 - new(470,398, -1, -1, -1), // TM71 - new(471,399, -1, -1, -1), // TM72 - new(472,400, -1, -1, -1), // TM73 - new(473,401, -1, -1, -1), // TM74 - new(474,402, -1, -1, -1), // TM75 - new(475,403, -1, -1, -1), // TM76 - new(476,404, -1, -1, -1), // TM77 - new(477,405, -1, -1, -1), // TM78 - new(478,406, -1, -1, -1), // TM79 - new(479,407, -1, -1, -1), // TM80 - new(480,408, -1, -1, -1), // TM81 - new(481,409, -1, -1, -1), // TM82 - new(482,410, -1, -1, -1), // TM83 - new(483,411, -1, -1, -1), // TM84 - new(484,412, -1, -1, -1), // TM85 - new(485,413, -1, -1, -1), // TM86 - new(486,414, -1, -1, -1), // TM87 - new(487,415, -1, -1, -1), // TM88 - new(488,416, -1, -1, -1), // TM89 - new(489,417, -1, -1, -1), // TM90 - new(490,418, -1, -1, -1), // TM91 - new(491,419, -1, -1, -1), // TM92 - new(492, -1, -1, -1,001), // Bulbasaur Statue - new(493, -1, -1, -1,002), // Ivysaur Statue - new(494, -1, -1, -1,003), // Venusaur Statue - new(495, -1, -1, -1,004), // Charmander Statue - new(496, -1, -1, -1,005), // Charmeleon Statue - new(497, -1, -1, -1,006), // Charizard Statue - new(498, -1, -1, -1,007), // Squirtle Statue - new(499, -1, -1, -1,008), // Wartortle Statue - new(500, -1, -1, -1,009), // Blastoise Statue - new(501, -1, -1, -1,010), // Pikachu Statue - new(502, -1, -1, -1,011), // Nidoqueen Statue - new(503, -1, -1, -1,012), // Nidoking Statue - new(504, -1, -1, -1,013), // Clefairy Statue - new(505, -1, -1, -1,014), // Vulpix Statue - new(506, -1, -1, -1,015), // Jigglypuff Statue - new(507, -1, -1, -1,016), // Oddish Statue - new(508, -1, -1, -1,017), // Meowth Statue - new(509, -1, -1, -1,018), // Psyduck Statue - new(510, -1, -1, -1,019), // Arcanine Statue - new(511, -1, -1, -1,020), // Poliwrath Statue - new(512, -1, -1, -1,021), // Alakazam Statue - new(513, -1, -1, -1,022), // Machamp Statue - new(514, -1, -1, -1,023), // Tentacruel Statue - new(515, -1, -1, -1,024), // Geodude Statue - new(516, -1, -1, -1,025), // Rapidash Statue - new(517, -1, -1, -1,026), // Slowpoke Statue - new(518, -1, -1, -1,027), // Farfetch’d Statue - new(519, -1, -1, -1,028), // Gengar Statue - new(520, -1, -1, -1,029), // Cubone Statue - new(521, -1, -1, -1,030), // Lickitung Statue - new(522, -1, -1, -1,031), // Weezing Statue - new(523, -1, -1, -1,032), // Chansey Statue - new(524, -1, -1, -1,033), // Kangaskhan Statue - new(525, -1, -1, -1,034), // Seaking Statue - new(526, -1, -1, -1,035), // Mr. Mime Statue - new(527, -1, -1, -1,036), // Tauros Statue - new(528, -1, -1, -1,037), // Magikarp Statue - new(529, -1, -1, -1,038), // Gyarados Statue - new(530, -1, -1, -1,039), // Lapras Statue - new(531, -1, -1, -1,040), // Ditto Statue - new(532, -1, -1, -1,041), // Eevee Statue - new(533, -1, -1, -1,042), // Vaporeon Statue - new(534, -1, -1, -1,043), // Jolteon Statue - new(535, -1, -1, -1,044), // Flareon Statue - new(536, -1, -1, -1,045), // Porygon Statue - new(537, -1, -1, -1,046), // Omastar Statue - new(538, -1, -1, -1,047), // Snorlax Statue - new(539, -1, -1, -1,048), // Dragonite Statue - new(540, -1, -1, -1,049), // Chikorita Statue - new(541, -1, -1, -1,050), // Bayleef Statue - new(542, -1, -1, -1,051), // Meganium Statue - new(543, -1, -1, -1,052), // Cyndaquil Statue - new(544, -1, -1, -1,053), // Quilava Statue - new(545, -1, -1, -1,054), // Typhlosion Statue - new(546, -1, -1, -1,055), // Totodile Statue - new(547, -1, -1, -1,056), // Croconaw Statue - new(548, -1, -1, -1,057), // Feraligatr Statue - new(549, -1, -1, -1,058), // Furret Statue - new(550, -1, -1, -1,059), // Noctowl Statue - new(551, -1, -1, -1,060), // Crobat Statue - new(552, -1, -1, -1,061), // Togepi Statue - new(553, -1, -1, -1,062), // Ampharos Statue - new(554, -1, -1, -1,063), // Bellossom Statue - new(555, -1, -1, -1,064), // Marill Statue - new(556, -1, -1, -1,065), // Sudowoodo Statue - new(557, -1, -1, -1,066), // Aipom Statue - new(558, -1, -1, -1,067), // Sunkern Statue - new(559, -1, -1, -1,068), // Quagsire Statue - new(560, -1, -1, -1,069), // Espeon Statue - new(561, -1, -1, -1,070), // Umbreon Statue - new(562, -1, -1, -1,071), // Murkrow Statue - new(563, -1, -1, -1,072), // Unown Statue - new(564, -1, -1, -1,073), // Wobbuffet Statue - new(565, -1, -1, -1,074), // Girafarig Statue - new(566, -1, -1, -1,075), // Dunsparce Statue - new(567, -1, -1, -1,076), // Steelix Statue - new(568, -1, -1, -1,077), // Snubbull Statue - new(569, -1, -1, -1,078), // Scizor Statue - new(570, -1, -1, -1,079), // Heracross Statue - new(571, -1, -1, -1,080), // Magcargo Statue - new(572, -1, -1, -1,081), // Octillery Statue - new(573, -1, -1, -1,082), // Mantine Statue - new(574, -1, -1, -1,083), // Smeargle Statue - new(575, -1, -1, -1,084), // Miltank Statue - new(576, -1, -1, -1,085), // Tyranitar Statue - new(577, -1, -1, -1,086), // Treecko Statue - new(578, -1, -1, -1,087), // Grovyle Statue - new(579, -1, -1, -1,088), // Sceptile Statue - new(580, -1, -1, -1,089), // Torchic Statue - new(581, -1, -1, -1,090), // Combusken Statue - new(582, -1, -1, -1,091), // Blaziken Statue - new(583, -1, -1, -1,092), // Mudkip Statue - new(584, -1, -1, -1,093), // Marshtomp Statue - new(585, -1, -1, -1,094), // Swampert Statue - new(586, -1, -1, -1,095), // Beautifly Statue - new(587, -1, -1, -1,096), // Dustox Statue - new(588, -1, -1, -1,097), // Ludicolo Statue - new(589, -1, -1, -1,098), // Pelipper Statue - new(590, -1, -1, -1,099), // Gardevoir Statue - new(591, -1, -1, -1,100), // Shroomish Statue - new(592, -1, -1, -1,101), // Slaking Statue - new(593, -1, -1, -1,102), // Ninjask Statue - new(594, -1, -1, -1,103), // Exploud Statue - new(595, -1, -1, -1,104), // Skitty Statue - new(596, -1, -1, -1,105), // Sableye Statue - new(597, -1, -1, -1,106), // Mawile Statue - new(598, -1, -1, -1,107), // Aggron Statue - new(599, -1, -1, -1,108), // Medicham Statue - new(600, -1, -1, -1,109), // Manectric Statue - new(601, -1, -1, -1,110), // Plusle Statue - new(602, -1, -1, -1,111), // Minun Statue - new(603, -1, -1, -1,112), // Volbeat Statue - new(604, -1, -1, -1,113), // Illumise Statue - new(605, -1, -1, -1,114), // Roselia Statue - new(606, -1, -1, -1,115), // Sharpedo Statue - new(607, -1, -1, -1,116), // Wailmer Statue - new(608, -1, -1, -1,117), // Camerupt Statue - new(609, -1, -1, -1,118), // Torkoal Statue - new(610, -1, -1, -1,119), // Spinda Statue - new(611, -1, -1, -1,120), // Flygon Statue - new(612, -1, -1, -1,121), // Cacturne Statue - new(613, -1, -1, -1,122), // Altaria Statue - new(614, -1, -1, -1,123), // Zangoose Statue - new(615, -1, -1, -1,124), // Seviper Statue - new(616, -1, -1, -1,125), // Lunatone Statue - new(617, -1, -1, -1,126), // Solrock Statue - new(618, -1, -1, -1,127), // Whiscash Statue - new(619, -1, -1, -1,128), // Claydol Statue - new(620, -1, -1, -1,129), // Cradily Statue - new(621, -1, -1, -1,130), // Milotic Statue - new(622, -1, -1, -1,131), // Castform Statue - new(623, -1, -1, -1,132), // Kecleon Statue - new(624, -1, -1, -1,133), // Tropius Statue - new(625, -1, -1, -1,134), // Chimecho Statue - new(626, -1, -1, -1,135), // Absol Statue - new(627, -1, -1, -1,136), // Spheal Statue - new(628, -1, -1, -1,137), // Clamperl Statue - new(629, -1, -1, -1,138), // Relicanth Statue - new(630, -1, -1, -1,139), // Luvdisc Statue - new(631, -1, -1, -1,140), // Salamence Statue - new(632, -1, -1, -1,141), // Metagross Statue - new(633, -1, -1, -1,142), // Turtwig Statue - new(634, -1, -1, -1,143), // Grotle Statue - new(635, -1, -1, -1,144), // Torterra Statue - new(636, -1, -1, -1,145), // Chimchar Statue - new(637, -1, -1, -1,146), // Monferno Statue - new(638, -1, -1, -1,147), // Infernape Statue - new(639, -1, -1, -1,148), // Piplup Statue - new(640, -1, -1, -1,149), // Prinplup Statue - new(641, -1, -1, -1,150), // Empoleon Statue - new(642, -1, -1, -1,151), // Staraptor Statue - new(643, -1, -1, -1,152), // Bibarel Statue - new(644, -1, -1, -1,153), // Kricketot Statue - new(645, -1, -1, -1,154), // Luxray Statue - new(646, -1, -1, -1,155), // Budew Statue - new(647, -1, -1, -1,156), // Roserade Statue - new(648, -1, -1, -1,157), // Cranidos Statue - new(649, -1, -1, -1,158), // Bastiodon Statue - new(650, -1, -1, -1,159), // Burmy Statue - new(651, -1, -1, -1,160), // Wormadam Statue - new(652, -1, -1, -1,161), // Mothim Statue - new(653, -1, -1, -1,162), // Vespiquen Statue - new(654, -1, -1, -1,163), // Pachirisu Statue - new(655, -1, -1, -1,164), // Floatzel Statue - new(656, -1, -1, -1,165), // Cherubi Statue - new(657, -1, -1, -1,166), // Gastrodon Statue - new(658, -1, -1, -1,167), // Ambipom Statue - new(659, -1, -1, -1,168), // Drifloon Statue - new(660, -1, -1, -1,169), // Buneary Statue - new(661, -1, -1, -1,170), // Mismagius Statue - new(662, -1, -1, -1,171), // Honchkrow Statue - new(663, -1, -1, -1,172), // Purugly Statue - new(664, -1, -1, -1,173), // Skuntank Statue - new(665, -1, -1, -1,174), // Bronzong Statue - new(666, -1, -1, -1,175), // Happiny Statue - new(667, -1, -1, -1,176), // Chatot Statue - new(668, -1, -1, -1,177), // Spiritomb Statue - new(669, -1, -1, -1,178), // Garchomp Statue - new(670, -1, -1, -1,179), // Lucario Statue - new(671, -1, -1, -1,180), // Hippowdon Statue - new(672, -1, -1, -1,181), // Drapion Statue - new(673, -1, -1, -1,182), // Croagunk Statue - new(674, -1, -1, -1,183), // Carnivine Statue - new(675, -1, -1, -1,184), // Lumineon Statue - new(676, -1, -1, -1,185), // Abomasnow Statue - new(677, -1, -1, -1,186), // Weavile Statue - new(678, -1, -1, -1,187), // Magnezone Statue - new(679, -1, -1, -1,188), // Rhyperior Statue - new(680, -1, -1, -1,189), // Tangrowth Statue - new(681, -1, -1, -1,190), // Electivire Statue - new(682, -1, -1, -1,191), // Magmortar Statue - new(683, -1, -1, -1,192), // Togekiss Statue - new(684, -1, -1, -1,193), // Yanmega Statue - new(685, -1, -1, -1,194), // Leafeon Statue - new(686, -1, -1, -1,195), // Glaceon Statue - new(687, -1, -1, -1,196), // Gliscor Statue - new(688, -1, -1, -1,197), // Mamoswine Statue - new(689, -1, -1, -1,198), // Porygon-Z Statue - new(690, -1, -1, -1,199), // Gallade Statue - new(691, -1, -1, -1,200), // Probopass Statue - new(692, -1, -1, -1,201), // Dusknoir Statue - new(693, -1, -1, -1,202), // Froslass Statue - new(694, -1, -1, -1,203), // Rotom Statue - new(695, -1, -1, -1,204), // Rotom Statue - new(696, -1, -1, -1,205), // Rotom Statue - new(697, -1, -1, -1,206), // Rotom Statue - new(698, -1, -1, -1,207), // Rotom Statue - new(699, -1, -1, -1,208), // Rotom Statue - new(700, -1, -1, -1,209), // Articuno Statue - new(701, -1, -1, -1,210), // Zapdos Statue - new(702, -1, -1, -1,211), // Moltres Statue - new(703, -1, -1, -1,212), // Mewtwo Statue - new(704, -1, -1, -1,213), // Raikou Statue - new(705, -1, -1, -1,214), // Entei Statue - new(706, -1, -1, -1,215), // Suicune Statue - new(707, -1, -1, -1,216), // Lugia Statue - new(708, -1, -1, -1,217), // Ho-Oh Statue - new(709, -1, -1, -1,218), // Regirock Statue - new(710, -1, -1, -1,219), // Regice Statue - new(711, -1, -1, -1,220), // Registeel Statue - new(712, -1, -1, -1,221), // Latias Statue - new(713, -1, -1, -1,222), // Latios Statue - new(714, -1, -1, -1,223), // Kyogre Statue - new(715, -1, -1, -1,224), // Groudon Statue - new(716, -1, -1, -1,225), // Rayquaza Statue - new(717, -1, -1, -1,226), // Bulbasaur Statue  - new(718, -1, -1, -1,227), // Ivysaur Statue  - new(719, -1, -1, -1,228), // Venusaur Statue  - new(720, -1, -1, -1,229), // Charmander Statue  - new(721, -1, -1, -1,230), // Charmeleon Statue  - new(722, -1, -1, -1,231), // Charizard Statue  - new(723, -1, -1, -1,232), // Squirtle Statue  - new(724, -1, -1, -1,233), // Wartortle Statue  - new(725, -1, -1, -1,234), // Blastoise Statue  - new(726, -1, -1, -1,235), // Pikachu Statue  - new(727, -1, -1, -1,236), // Nidoqueen Statue  - new(728, -1, -1, -1,237), // Nidoking Statue  - new(729, -1, -1, -1,238), // Clefairy Statue  - new(730, -1, -1, -1,239), // Vulpix Statue  - new(731, -1, -1, -1,240), // Jigglypuff Statue  - new(732, -1, -1, -1,241), // Oddish Statue  - new(733, -1, -1, -1,242), // Meowth Statue  - new(734, -1, -1, -1,243), // Psyduck Statue  - new(735, -1, -1, -1,244), // Arcanine Statue  - new(736, -1, -1, -1,245), // Poliwrath Statue  - new(737, -1, -1, -1,246), // Alakazam Statue  - new(738, -1, -1, -1,247), // Machamp Statue  - new(739, -1, -1, -1,248), // Tentacruel Statue  - new(740, -1, -1, -1,249), // Geodude Statue  - new(741, -1, -1, -1,250), // Rapidash Statue  - new(742, -1, -1, -1,251), // Slowpoke Statue  - new(743, -1, -1, -1,252), // Farfetch’d Statue  - new(744, -1, -1, -1,253), // Gengar Statue  - new(745, -1, -1, -1,254), // Cubone Statue  - new(746, -1, -1, -1,255), // Lickitung Statue  - new(747, -1, -1, -1,256), // Weezing Statue  - new(748, -1, -1, -1,257), // Chansey Statue  - new(749, -1, -1, -1,258), // Kangaskhan Statue  - new(750, -1, -1, -1,259), // Seaking Statue  - new(751, -1, -1, -1,260), // Mr. Mime Statue  - new(752, -1, -1, -1,261), // Tauros Statue  - new(753, -1, -1, -1,262), // Magikarp Statue  - new(754, -1, -1, -1,263), // Gyarados Statue  - new(755, -1, -1, -1,264), // Lapras Statue  - new(756, -1, -1, -1,265), // Ditto Statue  - new(757, -1, -1, -1,266), // Eevee Statue  - new(758, -1, -1, -1,267), // Vaporeon Statue  - new(759, -1, -1, -1,268), // Jolteon Statue  - new(760, -1, -1, -1,269), // Flareon Statue  - new(761, -1, -1, -1,270), // Porygon Statue  - new(762, -1, -1, -1,271), // Omastar Statue  - new(763, -1, -1, -1,272), // Snorlax Statue  - new(764, -1, -1, -1,273), // Dragonite Statue  - new(765, -1, -1, -1,274), // Chikorita Statue  - new(766, -1, -1, -1,275), // Bayleef Statue  - new(767, -1, -1, -1,276), // Meganium Statue  - new(768, -1, -1, -1,277), // Cyndaquil Statue  - new(769, -1, -1, -1,278), // Quilava Statue  - new(770, -1, -1, -1,279), // Typhlosion Statue  - new(771, -1, -1, -1,280), // Totodile Statue  - new(772, -1, -1, -1,281), // Croconaw Statue  - new(773, -1, -1, -1,282), // Feraligatr Statue  - new(774, -1, -1, -1,283), // Furret Statue  - new(775, -1, -1, -1,284), // Noctowl Statue  - new(776, -1, -1, -1,285), // Crobat Statue  - new(777, -1, -1, -1,286), // Togepi Statue  - new(778, -1, -1, -1,287), // Ampharos Statue  - new(779, -1, -1, -1,288), // Bellossom Statue  - new(780, -1, -1, -1,289), // Marill Statue  - new(781, -1, -1, -1,290), // Sudowoodo Statue  - new(782, -1, -1, -1,291), // Aipom Statue  - new(783, -1, -1, -1,292), // Sunkern Statue  - new(784, -1, -1, -1,293), // Quagsire Statue  - new(785, -1, -1, -1,294), // Espeon Statue  - new(786, -1, -1, -1,295), // Umbreon Statue  - new(787, -1, -1, -1,296), // Murkrow Statue  - new(788, -1, -1, -1,297), // Unown Statue  - new(789, -1, -1, -1,298), // Wobbuffet Statue  - new(790, -1, -1, -1,299), // Girafarig Statue  - new(791, -1, -1, -1,300), // Dunsparce Statue  - new(792, -1, -1, -1,301), // Steelix Statue  - new(793, -1, -1, -1,302), // Snubbull Statue  - new(794, -1, -1, -1,303), // Scizor Statue  - new(795, -1, -1, -1,304), // Heracross Statue  - new(796, -1, -1, -1,305), // Magcargo Statue  - new(797, -1, -1, -1,306), // Octillery Statue  - new(798, -1, -1, -1,307), // Mantine Statue  - new(799, -1, -1, -1,308), // Smeargle Statue  - new(800, -1, -1, -1,309), // Miltank Statue  - new(801, -1, -1, -1,310), // Tyranitar Statue  - new(802, -1, -1, -1,311), // Treecko Statue  - new(803, -1, -1, -1,312), // Grovyle Statue  - new(804, -1, -1, -1,313), // Sceptile Statue  - new(805, -1, -1, -1,314), // Torchic Statue  - new(806, -1, -1, -1,315), // Combusken Statue  - new(807, -1, -1, -1,316), // Blaziken Statue  - new(808, -1, -1, -1,317), // Mudkip Statue  - new(809, -1, -1, -1,318), // Marshtomp Statue  - new(810, -1, -1, -1,319), // Swampert Statue  - new(811, -1, -1, -1,320), // Beautifly Statue  - new(812, -1, -1, -1,321), // Dustox Statue  - new(813, -1, -1, -1,322), // Ludicolo Statue  - new(814, -1, -1, -1,323), // Pelipper Statue  - new(815, -1, -1, -1,324), // Gardevoir Statue  - new(816, -1, -1, -1,325), // Shroomish Statue  - new(817, -1, -1, -1,326), // Slaking Statue  - new(818, -1, -1, -1,327), // Ninjask Statue  - new(819, -1, -1, -1,328), // Exploud Statue  - new(820, -1, -1, -1,329), // Skitty Statue  - new(821, -1, -1, -1,330), // Sableye Statue  - new(822, -1, -1, -1,331), // Mawile Statue  - new(823, -1, -1, -1,332), // Aggron Statue  - new(824, -1, -1, -1,333), // Medicham Statue  - new(825, -1, -1, -1,334), // Manectric Statue  - new(826, -1, -1, -1,335), // Plusle Statue  - new(827, -1, -1, -1,336), // Minun Statue  - new(828, -1, -1, -1,337), // Volbeat Statue  - new(829, -1, -1, -1,338), // Illumise Statue  - new(830, -1, -1, -1,339), // Roselia Statue  - new(831, -1, -1, -1,340), // Sharpedo Statue  - new(832, -1, -1, -1,341), // Wailmer Statue  - new(833, -1, -1, -1,342), // Camerupt Statue  - new(834, -1, -1, -1,343), // Torkoal Statue  - new(835, -1, -1, -1,344), // Spinda Statue  - new(836, -1, -1, -1,345), // Flygon Statue  - new(837, -1, -1, -1,346), // Cacturne Statue  - new(838, -1, -1, -1,347), // Altaria Statue  - new(839, -1, -1, -1,348), // Zangoose Statue  - new(840, -1, -1, -1,349), // Seviper Statue  - new(841, -1, -1, -1,350), // Lunatone Statue  - new(842, -1, -1, -1,351), // Solrock Statue  - new(843, -1, -1, -1,352), // Whiscash Statue  - new(844, -1, -1, -1,353), // Claydol Statue  - new(845, -1, -1, -1,354), // Cradily Statue  - new(846, -1, -1, -1,355), // Milotic Statue  - new(847, -1, -1, -1,356), // Castform Statue  - new(848, -1, -1, -1,357), // Kecleon Statue  - new(849, -1, -1, -1,358), // Tropius Statue  - new(850, -1, -1, -1,359), // Chimecho Statue  - new(851, -1, -1, -1,360), // Absol Statue  - new(852, -1, -1, -1,361), // Spheal Statue  - new(853, -1, -1, -1,362), // Clamperl Statue  - new(854, -1, -1, -1,363), // Relicanth Statue  - new(855, -1, -1, -1,364), // Luvdisc Statue  - new(856, -1, -1, -1,365), // Salamence Statue  - new(857, -1, -1, -1,366), // Metagross Statue  - new(858, -1, -1, -1,367), // Turtwig Statue  - new(859, -1, -1, -1,368), // Grotle Statue  - new(860, -1, -1, -1,369), // Torterra Statue  - new(861, -1, -1, -1,370), // Chimchar Statue  - new(862, -1, -1, -1,371), // Monferno Statue  - new(863, -1, -1, -1,372), // Infernape Statue  - new(864, -1, -1, -1,373), // Piplup Statue  - new(865, -1, -1, -1,374), // Prinplup Statue  - new(866, -1, -1, -1,375), // Empoleon Statue  - new(867, -1, -1, -1,376), // Staraptor Statue  - new(868, -1, -1, -1,377), // Bibarel Statue  - new(869, -1, -1, -1,378), // Kricketot Statue  - new(870, -1, -1, -1,379), // Luxray Statue  - new(871, -1, -1, -1,380), // Budew Statue  - new(872, -1, -1, -1,381), // Roserade Statue  - new(873, -1, -1, -1,382), // Cranidos Statue  - new(874, -1, -1, -1,383), // Bastiodon Statue  - new(875, -1, -1, -1,384), // Burmy Statue  - new(876, -1, -1, -1,385), // Wormadam Statue  - new(877, -1, -1, -1,386), // Mothim Statue  - new(878, -1, -1, -1,387), // Vespiquen Statue  - new(879, -1, -1, -1,388), // Pachirisu Statue  - new(880, -1, -1, -1,389), // Floatzel Statue  - new(881, -1, -1, -1,390), // Cherubi Statue  - new(882, -1, -1, -1,391), // Gastrodon Statue  - new(883, -1, -1, -1,392), // Ambipom Statue  - new(884, -1, -1, -1,393), // Drifloon Statue  - new(885, -1, -1, -1,394), // Buneary Statue  - new(886, -1, -1, -1,395), // Mismagius Statue  - new(887, -1, -1, -1,396), // Honchkrow Statue  - new(888, -1, -1, -1,397), // Purugly Statue  - new(889, -1, -1, -1,398), // Skuntank Statue  - new(890, -1, -1, -1,399), // Bronzong Statue  - new(891, -1, -1, -1,400), // Happiny Statue  - new(892, -1, -1, -1,401), // Chatot Statue  - new(893, -1, -1, -1,402), // Spiritomb Statue  - new(894, -1, -1, -1,403), // Garchomp Statue  - new(895, -1, -1, -1,404), // Lucario Statue  - new(896, -1, -1, -1,405), // Hippowdon Statue  - new(897, -1, -1, -1,406), // Drapion Statue  - new(898, -1, -1, -1,407), // Croagunk Statue  - new(899, -1, -1, -1,408), // Carnivine Statue  - new(900, -1, -1, -1,409), // Lumineon Statue  - new(901, -1, -1, -1,410), // Abomasnow Statue  - new(902, -1, -1, -1,411), // Weavile Statue  - new(903, -1, -1, -1,412), // Magnezone Statue  - new(904, -1, -1, -1,413), // Rhyperior Statue  - new(905, -1, -1, -1,414), // Tangrowth Statue  - new(906, -1, -1, -1,415), // Electivire Statue  - new(907, -1, -1, -1,416), // Magmortar Statue  - new(908, -1, -1, -1,417), // Togekiss Statue  - new(909, -1, -1, -1,418), // Yanmega Statue  - new(910, -1, -1, -1,419), // Leafeon Statue  - new(911, -1, -1, -1,420), // Glaceon Statue  - new(912, -1, -1, -1,421), // Gliscor Statue  - new(913, -1, -1, -1,422), // Mamoswine Statue  - new(914, -1, -1, -1,423), // Porygon-Z Statue  - new(915, -1, -1, -1,424), // Gallade Statue  - new(916, -1, -1, -1,425), // Probopass Statue  - new(917, -1, -1, -1,426), // Dusknoir Statue  - new(918, -1, -1, -1,427), // Froslass Statue  - new(919, -1, -1, -1,428), // Rotom Statue  - new(920, -1, -1, -1,429), // Rotom Statue  - new(921, -1, -1, -1,430), // Rotom Statue  - new(922, -1, -1, -1,431), // Rotom Statue  - new(923, -1, -1, -1,432), // Rotom Statue  - new(924, -1, -1, -1,433), // Rotom Statue  - new(925,420, -1, -1, -1), // TM93 - new(926,421, -1, -1, -1), // TM94 - new(927,422, -1, -1, -1), // TM95 - new(928,423, -1, -1, -1), // TM96 - new(929,424, -1, -1, -1), // TM97 - new(930,425, -1, -1, -1), // TM98 - new(931,426, -1, -1, -1), // TM99 - new(932,427, -1, -1, -1), // TM100 - new(933, -1, -1,030, -1), // Square Pedestal XL - new(934, -1, -1,031, -1), // Round Pedestal XL - new(935, -1, -1,032, -1), // Sturdy Pedestal XL - new(936, -1, -1,033, -1), // Clear Pedestal XL - new(937, -1, -1,034, -1), // Spin Pedestal XL - new(938, -1, -1,035, -1), // Spinback Pedestal XL - new(939, -1, -1, -1,434), // Giratina Statue (Origin Form) - new(940, -1, -1, -1, -1), // - new(941, -1, -1, -1, -1), // - new(942, -1, -1, -1, -1), // - new(943, -1, -1, -1, -1), // - new(944, -1, -1, -1, -1), // - new(945, -1, -1, -1, -1), // - new(946, -1, -1, -1, -1), // - new(947, -1, -1, -1, -1), // - new(948, -1, -1, -1, -1), // - new(949, -1, -1, -1, -1), // - new(950, -1, -1, -1, -1), // - new(951, -1, -1, -1, -1), // - new(952, -1, -1, -1, -1), // - new(953, -1, -1, -1, -1), // - new(954, -1, -1, -1, -1), // - new(955, -1, -1, -1, -1), // - new(956, -1, -1, -1, -1), // - }; - #endregion + if ((uint)ugItemID >= Items.Count) + return UgItemType.None; + return Items[ugItemID].Type; } -} \ No newline at end of file + + public static int GetMax(int ugItemID) => GetType(ugItemID) switch + { + UgItemType.Sphere => UndergroundItemList8b.ItemMaxCount, + UgItemType.Statue => UndergroundItemList8b.StatueMaxCount, + UgItemType.Pedestal => UndergroundItemList8b.StatueMaxCount, + _ => UndergroundItemList8b.ItemMaxCount, + }; + + // ReSharper disable once NotAccessedPositionalProperty.Local + private record UgItemDef(int UgItemID, int ItemID, int SphereID, int PedestalID, int StatueID) + { + private bool IsSphere => SphereID > 0; + private bool IsItem => ItemID > 0; + private bool IsStatue => StatueID > 0; + private bool IsPedestal => PedestalID > 0; + + public UgItemType Type => + IsSphere ? UgItemType.Sphere : + IsItem ? UgItemType.Item : + IsStatue ? UgItemType.Statue : + IsPedestal ? UgItemType.Pedestal : UgItemType.None; + } + + #region Table + private static readonly IReadOnlyList Items = new UgItemDef[] + { + new(000,000,000,000,000), // None + new(001, -1,001, -1, -1), // Red Sphere S + new(002, -1,002, -1, -1), // Blue Sphere S + new(003, -1,003, -1, -1), // Green Sphere S + new(004, -1,004, -1, -1), // Prism Sphere S + new(005, -1,005, -1, -1), // Pale Sphere S + new(006, -1,006, -1, -1), // Red Sphere L + new(007, -1,007, -1, -1), // Blue Sphere L + new(008, -1,008, -1, -1), // Green Sphere L + new(009, -1,009, -1, -1), // Prism Sphere L + new(010, -1,010, -1, -1), // Pale Sphere L + new(011,028, -1, -1, -1), // Revive + new(012,029, -1, -1, -1), // Max Revive + new(013,072, -1, -1, -1), // Red Shard + new(014,073, -1, -1, -1), // Blue Shard + new(015,074, -1, -1, -1), // Yellow Shard + new(016,075, -1, -1, -1), // Green Shard + new(017,080, -1, -1, -1), // Sun Stone + new(018,081, -1, -1, -1), // Moon Stone + new(019,082, -1, -1, -1), // Fire Stone + new(020,083, -1, -1, -1), // Thunder Stone + new(021,084, -1, -1, -1), // Water Stone + new(022,085, -1, -1, -1), // Leaf Stone + new(023,110, -1, -1, -1), // Oval Stone + new(024,229, -1, -1, -1), // Everstone + new(025,111, -1, -1, -1), // Odd Keystone + new(026,091, -1, -1, -1), // Star Piece + new(027,093, -1, -1, -1), // Heart Scale + new(028,099, -1, -1, -1), // Root Fossil + new(029,100, -1, -1, -1), // Claw Fossil + new(030,101, -1, -1, -1), // Helix Fossil + new(031,102, -1, -1, -1), // Dome Fossil + new(032,103, -1, -1, -1), // Old Amber + new(033,104, -1, -1, -1), // Armor Fossil + new(034,105, -1, -1, -1), // Skull Fossil + new(035,106, -1, -1, -1), // Rare Bone + new(036,269, -1, -1, -1), // Light Clay + new(037,278, -1, -1, -1), // Iron Ball + new(038,282, -1, -1, -1), // Icy Rock + new(039,283, -1, -1, -1), // Smooth Rock + new(040,284, -1, -1, -1), // Heat Rock + new(041,285, -1, -1, -1), // Damp Rock + new(042,1808, -1, -1, -1), // Mysterious Shard S + new(043,1809, -1, -1, -1), // Mysterious Shard L + new(044,238, -1, -1, -1), // Hard Stone + new(045,298, -1, -1, -1), // Flame Plate + new(046,299, -1, -1, -1), // Splash Plate + new(047,300, -1, -1, -1), // Zap Plate + new(048,301, -1, -1, -1), // Meadow Plate + new(049,302, -1, -1, -1), // Icicle Plate + new(050,303, -1, -1, -1), // Fist Plate + new(051,304, -1, -1, -1), // Toxic Plate + new(052,305, -1, -1, -1), // Earth Plate + new(053,306, -1, -1, -1), // Sky Plate + new(054,307, -1, -1, -1), // Mind Plate + new(055,308, -1, -1, -1), // Insect Plate + new(056,309, -1, -1, -1), // Stone Plate + new(057,310, -1, -1, -1), // Spooky Plate + new(058,311, -1, -1, -1), // Draco Plate + new(059,312, -1, -1, -1), // Dread Plate + new(060,313, -1, -1, -1), // Iron Plate + new(061,644, -1, -1, -1), // Pixie Plate + new(062, -1, -1, -1,000), // + new(063, -1, -1, -1,000), // + new(064, -1, -1, -1,000), // + new(065, -1, -1, -1,000), // + new(066, -1, -1, -1,000), // + new(067, -1, -1, -1,000), // + new(068, -1, -1, -1,000), // + new(069, -1, -1, -1,000), // + new(070, -1, -1, -1,000), // + new(071, -1, -1, -1,000), // + new(072, -1, -1, -1,000), // + new(073, -1, -1, -1,000), // + new(074, -1, -1, -1,000), // + new(075, -1, -1, -1,000), // + new(076, -1, -1, -1,000), // + new(077, -1, -1, -1,000), // + new(078, -1, -1, -1,000), // + new(079, -1, -1, -1,000), // + new(080, -1, -1, -1,000), // + new(081, -1, -1, -1,000), // + new(082, -1, -1, -1,000), // + new(083, -1, -1, -1,000), // + new(084, -1, -1, -1,000), // + new(085, -1, -1, -1,000), // + new(086, -1, -1, -1,000), // + new(087, -1, -1, -1,000), // + new(088, -1, -1, -1,000), // + new(089, -1, -1, -1,000), // + new(090, -1, -1, -1,000), // + new(091, -1, -1, -1,000), // + new(092, -1, -1, -1,000), // + new(093, -1, -1, -1,000), // + new(094, -1, -1, -1,000), // + new(095, -1, -1, -1,000), // + new(096, -1, -1, -1,000), // + new(097, -1, -1, -1,000), // + new(098, -1, -1, -1,000), // + new(099, -1, -1, -1,000), // + new(100, -1, -1, -1,000), // + new(101, -1, -1, -1,000), // + new(102, -1, -1, -1,000), // + new(103, -1, -1, -1,000), // + new(104, -1, -1, -1,000), // + new(105, -1, -1, -1,000), // + new(106, -1, -1, -1,000), // + new(107, -1, -1, -1,000), // + new(108, -1, -1, -1,000), // + new(109, -1, -1, -1,000), // + new(110, -1, -1, -1,000), // + new(111, -1, -1, -1,000), // + new(112, -1, -1, -1,000), // + new(113, -1, -1, -1,000), // + new(114, -1, -1, -1,000), // + new(115, -1, -1, -1,000), // + new(116, -1, -1, -1,000), // + new(117, -1, -1, -1,000), // + new(118, -1, -1, -1,000), // + new(119, -1, -1, -1,000), // + new(120, -1, -1, -1,000), // + new(121, -1, -1, -1,000), // + new(122, -1, -1, -1,000), // + new(123, -1, -1, -1,000), // + new(124, -1, -1, -1,000), // + new(125, -1, -1, -1,000), // + new(126, -1, -1, -1,000), // + new(127, -1, -1, -1,000), // + new(128, -1, -1, -1,000), // + new(129, -1, -1, -1,000), // + new(130, -1, -1, -1,000), // + new(131, -1, -1, -1,000), // + new(132, -1, -1, -1,000), // + new(133, -1, -1, -1,000), // + new(134, -1, -1, -1,000), // + new(135, -1, -1, -1,000), // + new(136, -1, -1, -1,000), // + new(137, -1, -1, -1,000), // + new(138, -1, -1, -1,000), // + new(139, -1, -1, -1,000), // + new(140, -1, -1, -1,000), // + new(141, -1, -1, -1,000), // + new(142, -1, -1, -1,000), // + new(143, -1, -1, -1,000), // + new(144, -1, -1, -1,000), // + new(145, -1, -1, -1,000), // + new(146, -1, -1, -1,000), // + new(147, -1, -1, -1,000), // + new(148, -1, -1, -1,000), // + new(149, -1, -1, -1,000), // + new(150, -1, -1, -1,000), // + new(151, -1, -1, -1,000), // + new(152, -1, -1, -1,000), // + new(153, -1, -1, -1,000), // + new(154, -1, -1, -1,000), // + new(155, -1, -1, -1,000), // + new(156, -1, -1, -1,000), // + new(157, -1, -1, -1,000), // + new(158, -1, -1, -1,000), // + new(159, -1, -1, -1,000), // + new(160, -1, -1, -1,000), // + new(161, -1, -1, -1,000), // + new(162, -1, -1, -1,000), // + new(163, -1, -1, -1,000), // + new(164, -1, -1, -1,000), // + new(165, -1, -1, -1,000), // + new(166, -1, -1, -1,000), // + new(167, -1, -1, -1,000), // + new(168, -1, -1, -1,000), // + new(169, -1, -1, -1,000), // + new(170, -1, -1, -1,000), // + new(171, -1, -1, -1,000), // + new(172, -1, -1, -1,000), // + new(173, -1, -1, -1,000), // + new(174, -1, -1, -1,000), // + new(175, -1, -1, -1,000), // + new(176, -1, -1, -1,000), // + new(177, -1, -1, -1,000), // + new(178, -1, -1, -1,000), // + new(179, -1, -1, -1,000), // + new(180, -1, -1, -1,000), // + new(181, -1, -1, -1,000), // + new(182, -1, -1, -1,000), // + new(183, -1, -1, -1,000), // + new(184, -1, -1, -1,000), // + new(185, -1, -1, -1,000), // + new(186, -1, -1, -1,000), // + new(187, -1, -1, -1,000), // + new(188, -1, -1, -1,000), // + new(189, -1, -1, -1,000), // + new(190, -1, -1, -1,000), // + new(191, -1, -1, -1,000), // + new(192, -1, -1, -1,000), // + new(193, -1, -1, -1,000), // + new(194, -1, -1, -1,000), // + new(195, -1, -1, -1,000), // + new(196, -1, -1, -1,000), // + new(197, -1, -1, -1,000), // + new(198, -1, -1, -1,000), // + new(199, -1, -1, -1,000), // + new(200, -1, -1, -1,000), // + new(201, -1, -1, -1,000), // + new(202, -1, -1, -1,000), // + new(203, -1, -1, -1,000), // + new(204, -1, -1, -1,000), // + new(205, -1, -1, -1,000), // + new(206, -1, -1, -1,000), // + new(207, -1, -1, -1,000), // + new(208, -1, -1, -1,000), // + new(209, -1, -1, -1,000), // + new(210, -1, -1, -1,000), // + new(211, -1, -1, -1,000), // + new(212, -1, -1, -1,000), // + new(213, -1, -1, -1,000), // + new(214, -1, -1, -1,000), // + new(215, -1, -1, -1,000), // + new(216, -1, -1, -1,000), // + new(217, -1, -1, -1,000), // + new(218, -1, -1, -1,000), // + new(219, -1, -1, -1,000), // + new(220, -1, -1, -1,000), // + new(221, -1, -1, -1,000), // + new(222, -1, -1, -1,000), // + new(223, -1, -1, -1,000), // + new(224, -1, -1, -1,000), // + new(225, -1, -1, -1,000), // + new(226, -1, -1, -1,000), // + new(227, -1, -1, -1,000), // + new(228, -1, -1, -1,000), // + new(229, -1, -1, -1,000), // + new(230, -1, -1, -1,000), // + new(231, -1, -1, -1,000), // + new(232, -1, -1, -1,000), // + new(233, -1, -1, -1,000), // + new(234, -1, -1, -1,000), // + new(235, -1, -1, -1,000), // + new(236, -1, -1, -1,000), // + new(237, -1, -1, -1,000), // + new(238, -1, -1, -1,000), // + new(239, -1, -1, -1,000), // + new(240, -1, -1, -1,000), // + new(241, -1, -1, -1,000), // + new(242, -1, -1, -1,000), // + new(243, -1, -1, -1,000), // + new(244, -1, -1, -1,000), // + new(245, -1, -1, -1,000), // + new(246, -1, -1, -1,000), // + new(247, -1, -1, -1,000), // + new(248, -1, -1, -1,000), // + new(249, -1, -1, -1,000), // + new(250, -1, -1, -1,000), // + new(251, -1, -1, -1,000), // + new(252, -1, -1, -1,000), // + new(253, -1, -1, -1,000), // + new(254, -1, -1, -1,000), // + new(255, -1, -1, -1,000), // + new(256, -1, -1, -1,000), // + new(257, -1, -1, -1,000), // + new(258, -1, -1, -1,000), // + new(259, -1, -1, -1,000), // + new(260, -1, -1, -1,000), // + new(261, -1, -1, -1,000), // + new(262, -1, -1, -1,000), // + new(263, -1, -1, -1,000), // + new(264, -1, -1, -1,000), // + new(265, -1, -1, -1,000), // + new(266, -1, -1, -1,000), // + new(267, -1, -1, -1,000), // + new(268, -1, -1, -1,000), // + new(269, -1, -1, -1,000), // + new(270, -1, -1, -1,000), // + new(271, -1, -1, -1,000), // + new(272, -1, -1, -1,000), // + new(273, -1, -1, -1,000), // + new(274, -1, -1, -1,000), // + new(275, -1, -1, -1,000), // + new(276, -1, -1, -1,000), // + new(277, -1, -1, -1,000), // + new(278, -1, -1, -1,000), // + new(279, -1, -1, -1,000), // + new(280, -1, -1, -1,000), // + new(281, -1, -1, -1,000), // + new(282, -1, -1, -1,000), // + new(283, -1, -1, -1,000), // + new(284, -1, -1, -1,000), // + new(285, -1, -1, -1,000), // + new(286, -1, -1, -1,000), // + new(287, -1, -1, -1,000), // + new(288, -1, -1, -1,000), // + new(289, -1, -1, -1,000), // + new(290, -1, -1, -1,000), // + new(291, -1, -1, -1,000), // + new(292, -1, -1, -1,000), // + new(293, -1, -1, -1,000), // + new(294, -1, -1, -1,000), // + new(295, -1, -1, -1,000), // + new(296, -1, -1, -1,000), // + new(297, -1, -1, -1,000), // + new(298, -1, -1, -1,000), // + new(299, -1, -1, -1,000), // + new(300, -1, -1, -1,000), // + new(301, -1, -1, -1,000), // + new(302, -1, -1, -1,000), // + new(303, -1, -1, -1,000), // + new(304, -1, -1, -1,000), // + new(305, -1, -1, -1,000), // + new(306, -1, -1, -1,000), // + new(307, -1, -1, -1,000), // + new(308, -1, -1, -1,000), // + new(309, -1, -1, -1,000), // + new(310, -1, -1, -1,000), // + new(311, -1, -1, -1,000), // + new(312, -1, -1, -1,000), // + new(313, -1, -1, -1,000), // + new(314, -1, -1, -1,000), // + new(315, -1, -1, -1,000), // + new(316, -1, -1, -1,000), // + new(317, -1, -1, -1,000), // + new(318, -1, -1, -1,000), // + new(319, -1, -1, -1,000), // + new(320, -1, -1, -1,000), // + new(321, -1, -1, -1,000), // + new(322, -1, -1, -1,000), // + new(323, -1, -1, -1,000), // + new(324, -1, -1, -1,000), // + new(325, -1, -1, -1,000), // + new(326, -1, -1, -1,000), // + new(327, -1, -1, -1,000), // + new(328, -1, -1, -1,000), // + new(329, -1, -1, -1,000), // + new(330, -1, -1, -1,000), // + new(331, -1, -1, -1,000), // + new(332, -1, -1, -1,000), // + new(333, -1, -1, -1,000), // + new(334, -1, -1, -1,000), // + new(335, -1, -1, -1,000), // + new(336, -1, -1, -1,000), // + new(337, -1, -1, -1,000), // + new(338, -1, -1, -1,000), // + new(339, -1, -1, -1,000), // + new(340, -1, -1, -1,000), // + new(341, -1, -1, -1,000), // + new(342, -1, -1, -1,000), // + new(343, -1, -1, -1,000), // + new(344, -1, -1, -1,000), // + new(345, -1, -1, -1,000), // + new(346, -1, -1, -1,000), // + new(347, -1, -1, -1,000), // + new(348, -1, -1, -1,000), // + new(349, -1, -1, -1,000), // + new(350, -1, -1, -1,000), // + new(351, -1, -1, -1,000), // + new(352, -1, -1, -1,000), // + new(353, -1, -1, -1,000), // + new(354, -1, -1, -1,000), // + new(355, -1, -1, -1,000), // + new(356, -1, -1, -1,000), // + new(357, -1, -1, -1,000), // + new(358, -1, -1, -1,000), // + new(359, -1, -1, -1,000), // + new(360, -1, -1, -1,000), // + new(361, -1, -1, -1,000), // + new(362, -1, -1, -1,000), // + new(363, -1, -1, -1,000), // + new(364, -1, -1, -1,000), // + new(365, -1, -1, -1,000), // + new(366, -1, -1, -1,000), // + new(367, -1, -1, -1,000), // + new(368, -1, -1, -1,000), // + new(369, -1, -1, -1,000), // + new(370, -1, -1,001, -1), // Square Pedestal XS + new(371, -1, -1,002, -1), // Square Pedestal S + new(372, -1, -1,003, -1), // Square Pedestal M + new(373, -1, -1,004, -1), // Square Pedestal L + new(374, -1, -1,005, -1), // Round Pedestal XS + new(375, -1, -1,006, -1), // Round Pedestal M + new(376, -1, -1,007, -1), // Round Pedestal L + new(377, -1, -1,008, -1), // Sturdy Pedestal XS + new(378, -1, -1,009, -1), // Sturdy Pedestal S + new(379, -1, -1,010, -1), // Sturdy Pedestal M + new(380, -1, -1,011, -1), // Sturdy Pedestal L + new(381, -1, -1,012, -1), // Clear Pedestal XS + new(382, -1, -1,013, -1), // Clear Pedestal S + new(383, -1, -1,014, -1), // Clear Pedestal M + new(384, -1, -1,015, -1), // Clear Pedestal L + new(385, -1, -1,016, -1), // Dawn Pedestal L + new(386, -1, -1,017, -1), // Dawn Pedestal XL + new(387, -1, -1,018, -1), // Night Pedestal L + new(388, -1, -1,019, -1), // Night Pedestal XL + new(389, -1, -1,020, -1), // Diamond Pedestal L + new(390, -1, -1,021, -1), // Diamond Pedestal XL + new(391, -1, -1,022, -1), // Pearl Pedestal L + new(392, -1, -1,023, -1), // Pearl Pedestal XL + new(393, -1, -1,024, -1), // Spin Pedestal XS + new(394, -1, -1,025, -1), // Spin Pedestal M + new(395, -1, -1,026, -1), // Spin Pedestal L + new(396, -1, -1,027, -1), // Spinback Pedestal XS + new(397, -1, -1,028, -1), // Spinback Pedestal M + new(398, -1, -1,029, -1), // Spinback Pedestal L + new(399,1810, -1, -1, -1), // Digger Drill + new(400,328, -1, -1, -1), // TM01 + new(401,329, -1, -1, -1), // TM02 + new(402,330, -1, -1, -1), // TM03 + new(403,331, -1, -1, -1), // TM04 + new(404,332, -1, -1, -1), // TM05 + new(405,333, -1, -1, -1), // TM06 + new(406,334, -1, -1, -1), // TM07 + new(407,335, -1, -1, -1), // TM08 + new(408,336, -1, -1, -1), // TM09 + new(409,337, -1, -1, -1), // TM10 + new(410,338, -1, -1, -1), // TM11 + new(411,339, -1, -1, -1), // TM12 + new(412,340, -1, -1, -1), // TM13 + new(413,341, -1, -1, -1), // TM14 + new(414,342, -1, -1, -1), // TM15 + new(415,343, -1, -1, -1), // TM16 + new(416,344, -1, -1, -1), // TM17 + new(417,345, -1, -1, -1), // TM18 + new(418,346, -1, -1, -1), // TM19 + new(419,347, -1, -1, -1), // TM20 + new(420,348, -1, -1, -1), // TM21 + new(421,349, -1, -1, -1), // TM22 + new(422,350, -1, -1, -1), // TM23 + new(423,351, -1, -1, -1), // TM24 + new(424,352, -1, -1, -1), // TM25 + new(425,353, -1, -1, -1), // TM26 + new(426,354, -1, -1, -1), // TM27 + new(427,355, -1, -1, -1), // TM28 + new(428,356, -1, -1, -1), // TM29 + new(429,357, -1, -1, -1), // TM30 + new(430,358, -1, -1, -1), // TM31 + new(431,359, -1, -1, -1), // TM32 + new(432,360, -1, -1, -1), // TM33 + new(433,361, -1, -1, -1), // TM34 + new(434,362, -1, -1, -1), // TM35 + new(435,363, -1, -1, -1), // TM36 + new(436,364, -1, -1, -1), // TM37 + new(437,365, -1, -1, -1), // TM38 + new(438,366, -1, -1, -1), // TM39 + new(439,367, -1, -1, -1), // TM40 + new(440,368, -1, -1, -1), // TM41 + new(441,369, -1, -1, -1), // TM42 + new(442,370, -1, -1, -1), // TM43 + new(443,371, -1, -1, -1), // TM44 + new(444,372, -1, -1, -1), // TM45 + new(445,373, -1, -1, -1), // TM46 + new(446,374, -1, -1, -1), // TM47 + new(447,375, -1, -1, -1), // TM48 + new(448,376, -1, -1, -1), // TM49 + new(449,377, -1, -1, -1), // TM50 + new(450,378, -1, -1, -1), // TM51 + new(451,379, -1, -1, -1), // TM52 + new(452,380, -1, -1, -1), // TM53 + new(453,381, -1, -1, -1), // TM54 + new(454,382, -1, -1, -1), // TM55 + new(455,383, -1, -1, -1), // TM56 + new(456,384, -1, -1, -1), // TM57 + new(457,385, -1, -1, -1), // TM58 + new(458,386, -1, -1, -1), // TM59 + new(459,387, -1, -1, -1), // TM60 + new(460,388, -1, -1, -1), // TM61 + new(461,389, -1, -1, -1), // TM62 + new(462,390, -1, -1, -1), // TM63 + new(463,391, -1, -1, -1), // TM64 + new(464,392, -1, -1, -1), // TM65 + new(465,393, -1, -1, -1), // TM66 + new(466,394, -1, -1, -1), // TM67 + new(467,395, -1, -1, -1), // TM68 + new(468,396, -1, -1, -1), // TM69 + new(469,397, -1, -1, -1), // TM70 + new(470,398, -1, -1, -1), // TM71 + new(471,399, -1, -1, -1), // TM72 + new(472,400, -1, -1, -1), // TM73 + new(473,401, -1, -1, -1), // TM74 + new(474,402, -1, -1, -1), // TM75 + new(475,403, -1, -1, -1), // TM76 + new(476,404, -1, -1, -1), // TM77 + new(477,405, -1, -1, -1), // TM78 + new(478,406, -1, -1, -1), // TM79 + new(479,407, -1, -1, -1), // TM80 + new(480,408, -1, -1, -1), // TM81 + new(481,409, -1, -1, -1), // TM82 + new(482,410, -1, -1, -1), // TM83 + new(483,411, -1, -1, -1), // TM84 + new(484,412, -1, -1, -1), // TM85 + new(485,413, -1, -1, -1), // TM86 + new(486,414, -1, -1, -1), // TM87 + new(487,415, -1, -1, -1), // TM88 + new(488,416, -1, -1, -1), // TM89 + new(489,417, -1, -1, -1), // TM90 + new(490,418, -1, -1, -1), // TM91 + new(491,419, -1, -1, -1), // TM92 + new(492, -1, -1, -1,001), // Bulbasaur Statue + new(493, -1, -1, -1,002), // Ivysaur Statue + new(494, -1, -1, -1,003), // Venusaur Statue + new(495, -1, -1, -1,004), // Charmander Statue + new(496, -1, -1, -1,005), // Charmeleon Statue + new(497, -1, -1, -1,006), // Charizard Statue + new(498, -1, -1, -1,007), // Squirtle Statue + new(499, -1, -1, -1,008), // Wartortle Statue + new(500, -1, -1, -1,009), // Blastoise Statue + new(501, -1, -1, -1,010), // Pikachu Statue + new(502, -1, -1, -1,011), // Nidoqueen Statue + new(503, -1, -1, -1,012), // Nidoking Statue + new(504, -1, -1, -1,013), // Clefairy Statue + new(505, -1, -1, -1,014), // Vulpix Statue + new(506, -1, -1, -1,015), // Jigglypuff Statue + new(507, -1, -1, -1,016), // Oddish Statue + new(508, -1, -1, -1,017), // Meowth Statue + new(509, -1, -1, -1,018), // Psyduck Statue + new(510, -1, -1, -1,019), // Arcanine Statue + new(511, -1, -1, -1,020), // Poliwrath Statue + new(512, -1, -1, -1,021), // Alakazam Statue + new(513, -1, -1, -1,022), // Machamp Statue + new(514, -1, -1, -1,023), // Tentacruel Statue + new(515, -1, -1, -1,024), // Geodude Statue + new(516, -1, -1, -1,025), // Rapidash Statue + new(517, -1, -1, -1,026), // Slowpoke Statue + new(518, -1, -1, -1,027), // Farfetch’d Statue + new(519, -1, -1, -1,028), // Gengar Statue + new(520, -1, -1, -1,029), // Cubone Statue + new(521, -1, -1, -1,030), // Lickitung Statue + new(522, -1, -1, -1,031), // Weezing Statue + new(523, -1, -1, -1,032), // Chansey Statue + new(524, -1, -1, -1,033), // Kangaskhan Statue + new(525, -1, -1, -1,034), // Seaking Statue + new(526, -1, -1, -1,035), // Mr. Mime Statue + new(527, -1, -1, -1,036), // Tauros Statue + new(528, -1, -1, -1,037), // Magikarp Statue + new(529, -1, -1, -1,038), // Gyarados Statue + new(530, -1, -1, -1,039), // Lapras Statue + new(531, -1, -1, -1,040), // Ditto Statue + new(532, -1, -1, -1,041), // Eevee Statue + new(533, -1, -1, -1,042), // Vaporeon Statue + new(534, -1, -1, -1,043), // Jolteon Statue + new(535, -1, -1, -1,044), // Flareon Statue + new(536, -1, -1, -1,045), // Porygon Statue + new(537, -1, -1, -1,046), // Omastar Statue + new(538, -1, -1, -1,047), // Snorlax Statue + new(539, -1, -1, -1,048), // Dragonite Statue + new(540, -1, -1, -1,049), // Chikorita Statue + new(541, -1, -1, -1,050), // Bayleef Statue + new(542, -1, -1, -1,051), // Meganium Statue + new(543, -1, -1, -1,052), // Cyndaquil Statue + new(544, -1, -1, -1,053), // Quilava Statue + new(545, -1, -1, -1,054), // Typhlosion Statue + new(546, -1, -1, -1,055), // Totodile Statue + new(547, -1, -1, -1,056), // Croconaw Statue + new(548, -1, -1, -1,057), // Feraligatr Statue + new(549, -1, -1, -1,058), // Furret Statue + new(550, -1, -1, -1,059), // Noctowl Statue + new(551, -1, -1, -1,060), // Crobat Statue + new(552, -1, -1, -1,061), // Togepi Statue + new(553, -1, -1, -1,062), // Ampharos Statue + new(554, -1, -1, -1,063), // Bellossom Statue + new(555, -1, -1, -1,064), // Marill Statue + new(556, -1, -1, -1,065), // Sudowoodo Statue + new(557, -1, -1, -1,066), // Aipom Statue + new(558, -1, -1, -1,067), // Sunkern Statue + new(559, -1, -1, -1,068), // Quagsire Statue + new(560, -1, -1, -1,069), // Espeon Statue + new(561, -1, -1, -1,070), // Umbreon Statue + new(562, -1, -1, -1,071), // Murkrow Statue + new(563, -1, -1, -1,072), // Unown Statue + new(564, -1, -1, -1,073), // Wobbuffet Statue + new(565, -1, -1, -1,074), // Girafarig Statue + new(566, -1, -1, -1,075), // Dunsparce Statue + new(567, -1, -1, -1,076), // Steelix Statue + new(568, -1, -1, -1,077), // Snubbull Statue + new(569, -1, -1, -1,078), // Scizor Statue + new(570, -1, -1, -1,079), // Heracross Statue + new(571, -1, -1, -1,080), // Magcargo Statue + new(572, -1, -1, -1,081), // Octillery Statue + new(573, -1, -1, -1,082), // Mantine Statue + new(574, -1, -1, -1,083), // Smeargle Statue + new(575, -1, -1, -1,084), // Miltank Statue + new(576, -1, -1, -1,085), // Tyranitar Statue + new(577, -1, -1, -1,086), // Treecko Statue + new(578, -1, -1, -1,087), // Grovyle Statue + new(579, -1, -1, -1,088), // Sceptile Statue + new(580, -1, -1, -1,089), // Torchic Statue + new(581, -1, -1, -1,090), // Combusken Statue + new(582, -1, -1, -1,091), // Blaziken Statue + new(583, -1, -1, -1,092), // Mudkip Statue + new(584, -1, -1, -1,093), // Marshtomp Statue + new(585, -1, -1, -1,094), // Swampert Statue + new(586, -1, -1, -1,095), // Beautifly Statue + new(587, -1, -1, -1,096), // Dustox Statue + new(588, -1, -1, -1,097), // Ludicolo Statue + new(589, -1, -1, -1,098), // Pelipper Statue + new(590, -1, -1, -1,099), // Gardevoir Statue + new(591, -1, -1, -1,100), // Shroomish Statue + new(592, -1, -1, -1,101), // Slaking Statue + new(593, -1, -1, -1,102), // Ninjask Statue + new(594, -1, -1, -1,103), // Exploud Statue + new(595, -1, -1, -1,104), // Skitty Statue + new(596, -1, -1, -1,105), // Sableye Statue + new(597, -1, -1, -1,106), // Mawile Statue + new(598, -1, -1, -1,107), // Aggron Statue + new(599, -1, -1, -1,108), // Medicham Statue + new(600, -1, -1, -1,109), // Manectric Statue + new(601, -1, -1, -1,110), // Plusle Statue + new(602, -1, -1, -1,111), // Minun Statue + new(603, -1, -1, -1,112), // Volbeat Statue + new(604, -1, -1, -1,113), // Illumise Statue + new(605, -1, -1, -1,114), // Roselia Statue + new(606, -1, -1, -1,115), // Sharpedo Statue + new(607, -1, -1, -1,116), // Wailmer Statue + new(608, -1, -1, -1,117), // Camerupt Statue + new(609, -1, -1, -1,118), // Torkoal Statue + new(610, -1, -1, -1,119), // Spinda Statue + new(611, -1, -1, -1,120), // Flygon Statue + new(612, -1, -1, -1,121), // Cacturne Statue + new(613, -1, -1, -1,122), // Altaria Statue + new(614, -1, -1, -1,123), // Zangoose Statue + new(615, -1, -1, -1,124), // Seviper Statue + new(616, -1, -1, -1,125), // Lunatone Statue + new(617, -1, -1, -1,126), // Solrock Statue + new(618, -1, -1, -1,127), // Whiscash Statue + new(619, -1, -1, -1,128), // Claydol Statue + new(620, -1, -1, -1,129), // Cradily Statue + new(621, -1, -1, -1,130), // Milotic Statue + new(622, -1, -1, -1,131), // Castform Statue + new(623, -1, -1, -1,132), // Kecleon Statue + new(624, -1, -1, -1,133), // Tropius Statue + new(625, -1, -1, -1,134), // Chimecho Statue + new(626, -1, -1, -1,135), // Absol Statue + new(627, -1, -1, -1,136), // Spheal Statue + new(628, -1, -1, -1,137), // Clamperl Statue + new(629, -1, -1, -1,138), // Relicanth Statue + new(630, -1, -1, -1,139), // Luvdisc Statue + new(631, -1, -1, -1,140), // Salamence Statue + new(632, -1, -1, -1,141), // Metagross Statue + new(633, -1, -1, -1,142), // Turtwig Statue + new(634, -1, -1, -1,143), // Grotle Statue + new(635, -1, -1, -1,144), // Torterra Statue + new(636, -1, -1, -1,145), // Chimchar Statue + new(637, -1, -1, -1,146), // Monferno Statue + new(638, -1, -1, -1,147), // Infernape Statue + new(639, -1, -1, -1,148), // Piplup Statue + new(640, -1, -1, -1,149), // Prinplup Statue + new(641, -1, -1, -1,150), // Empoleon Statue + new(642, -1, -1, -1,151), // Staraptor Statue + new(643, -1, -1, -1,152), // Bibarel Statue + new(644, -1, -1, -1,153), // Kricketot Statue + new(645, -1, -1, -1,154), // Luxray Statue + new(646, -1, -1, -1,155), // Budew Statue + new(647, -1, -1, -1,156), // Roserade Statue + new(648, -1, -1, -1,157), // Cranidos Statue + new(649, -1, -1, -1,158), // Bastiodon Statue + new(650, -1, -1, -1,159), // Burmy Statue + new(651, -1, -1, -1,160), // Wormadam Statue + new(652, -1, -1, -1,161), // Mothim Statue + new(653, -1, -1, -1,162), // Vespiquen Statue + new(654, -1, -1, -1,163), // Pachirisu Statue + new(655, -1, -1, -1,164), // Floatzel Statue + new(656, -1, -1, -1,165), // Cherubi Statue + new(657, -1, -1, -1,166), // Gastrodon Statue + new(658, -1, -1, -1,167), // Ambipom Statue + new(659, -1, -1, -1,168), // Drifloon Statue + new(660, -1, -1, -1,169), // Buneary Statue + new(661, -1, -1, -1,170), // Mismagius Statue + new(662, -1, -1, -1,171), // Honchkrow Statue + new(663, -1, -1, -1,172), // Purugly Statue + new(664, -1, -1, -1,173), // Skuntank Statue + new(665, -1, -1, -1,174), // Bronzong Statue + new(666, -1, -1, -1,175), // Happiny Statue + new(667, -1, -1, -1,176), // Chatot Statue + new(668, -1, -1, -1,177), // Spiritomb Statue + new(669, -1, -1, -1,178), // Garchomp Statue + new(670, -1, -1, -1,179), // Lucario Statue + new(671, -1, -1, -1,180), // Hippowdon Statue + new(672, -1, -1, -1,181), // Drapion Statue + new(673, -1, -1, -1,182), // Croagunk Statue + new(674, -1, -1, -1,183), // Carnivine Statue + new(675, -1, -1, -1,184), // Lumineon Statue + new(676, -1, -1, -1,185), // Abomasnow Statue + new(677, -1, -1, -1,186), // Weavile Statue + new(678, -1, -1, -1,187), // Magnezone Statue + new(679, -1, -1, -1,188), // Rhyperior Statue + new(680, -1, -1, -1,189), // Tangrowth Statue + new(681, -1, -1, -1,190), // Electivire Statue + new(682, -1, -1, -1,191), // Magmortar Statue + new(683, -1, -1, -1,192), // Togekiss Statue + new(684, -1, -1, -1,193), // Yanmega Statue + new(685, -1, -1, -1,194), // Leafeon Statue + new(686, -1, -1, -1,195), // Glaceon Statue + new(687, -1, -1, -1,196), // Gliscor Statue + new(688, -1, -1, -1,197), // Mamoswine Statue + new(689, -1, -1, -1,198), // Porygon-Z Statue + new(690, -1, -1, -1,199), // Gallade Statue + new(691, -1, -1, -1,200), // Probopass Statue + new(692, -1, -1, -1,201), // Dusknoir Statue + new(693, -1, -1, -1,202), // Froslass Statue + new(694, -1, -1, -1,203), // Rotom Statue + new(695, -1, -1, -1,204), // Rotom Statue + new(696, -1, -1, -1,205), // Rotom Statue + new(697, -1, -1, -1,206), // Rotom Statue + new(698, -1, -1, -1,207), // Rotom Statue + new(699, -1, -1, -1,208), // Rotom Statue + new(700, -1, -1, -1,209), // Articuno Statue + new(701, -1, -1, -1,210), // Zapdos Statue + new(702, -1, -1, -1,211), // Moltres Statue + new(703, -1, -1, -1,212), // Mewtwo Statue + new(704, -1, -1, -1,213), // Raikou Statue + new(705, -1, -1, -1,214), // Entei Statue + new(706, -1, -1, -1,215), // Suicune Statue + new(707, -1, -1, -1,216), // Lugia Statue + new(708, -1, -1, -1,217), // Ho-Oh Statue + new(709, -1, -1, -1,218), // Regirock Statue + new(710, -1, -1, -1,219), // Regice Statue + new(711, -1, -1, -1,220), // Registeel Statue + new(712, -1, -1, -1,221), // Latias Statue + new(713, -1, -1, -1,222), // Latios Statue + new(714, -1, -1, -1,223), // Kyogre Statue + new(715, -1, -1, -1,224), // Groudon Statue + new(716, -1, -1, -1,225), // Rayquaza Statue + new(717, -1, -1, -1,226), // Bulbasaur Statue  + new(718, -1, -1, -1,227), // Ivysaur Statue  + new(719, -1, -1, -1,228), // Venusaur Statue  + new(720, -1, -1, -1,229), // Charmander Statue  + new(721, -1, -1, -1,230), // Charmeleon Statue  + new(722, -1, -1, -1,231), // Charizard Statue  + new(723, -1, -1, -1,232), // Squirtle Statue  + new(724, -1, -1, -1,233), // Wartortle Statue  + new(725, -1, -1, -1,234), // Blastoise Statue  + new(726, -1, -1, -1,235), // Pikachu Statue  + new(727, -1, -1, -1,236), // Nidoqueen Statue  + new(728, -1, -1, -1,237), // Nidoking Statue  + new(729, -1, -1, -1,238), // Clefairy Statue  + new(730, -1, -1, -1,239), // Vulpix Statue  + new(731, -1, -1, -1,240), // Jigglypuff Statue  + new(732, -1, -1, -1,241), // Oddish Statue  + new(733, -1, -1, -1,242), // Meowth Statue  + new(734, -1, -1, -1,243), // Psyduck Statue  + new(735, -1, -1, -1,244), // Arcanine Statue  + new(736, -1, -1, -1,245), // Poliwrath Statue  + new(737, -1, -1, -1,246), // Alakazam Statue  + new(738, -1, -1, -1,247), // Machamp Statue  + new(739, -1, -1, -1,248), // Tentacruel Statue  + new(740, -1, -1, -1,249), // Geodude Statue  + new(741, -1, -1, -1,250), // Rapidash Statue  + new(742, -1, -1, -1,251), // Slowpoke Statue  + new(743, -1, -1, -1,252), // Farfetch’d Statue  + new(744, -1, -1, -1,253), // Gengar Statue  + new(745, -1, -1, -1,254), // Cubone Statue  + new(746, -1, -1, -1,255), // Lickitung Statue  + new(747, -1, -1, -1,256), // Weezing Statue  + new(748, -1, -1, -1,257), // Chansey Statue  + new(749, -1, -1, -1,258), // Kangaskhan Statue  + new(750, -1, -1, -1,259), // Seaking Statue  + new(751, -1, -1, -1,260), // Mr. Mime Statue  + new(752, -1, -1, -1,261), // Tauros Statue  + new(753, -1, -1, -1,262), // Magikarp Statue  + new(754, -1, -1, -1,263), // Gyarados Statue  + new(755, -1, -1, -1,264), // Lapras Statue  + new(756, -1, -1, -1,265), // Ditto Statue  + new(757, -1, -1, -1,266), // Eevee Statue  + new(758, -1, -1, -1,267), // Vaporeon Statue  + new(759, -1, -1, -1,268), // Jolteon Statue  + new(760, -1, -1, -1,269), // Flareon Statue  + new(761, -1, -1, -1,270), // Porygon Statue  + new(762, -1, -1, -1,271), // Omastar Statue  + new(763, -1, -1, -1,272), // Snorlax Statue  + new(764, -1, -1, -1,273), // Dragonite Statue  + new(765, -1, -1, -1,274), // Chikorita Statue  + new(766, -1, -1, -1,275), // Bayleef Statue  + new(767, -1, -1, -1,276), // Meganium Statue  + new(768, -1, -1, -1,277), // Cyndaquil Statue  + new(769, -1, -1, -1,278), // Quilava Statue  + new(770, -1, -1, -1,279), // Typhlosion Statue  + new(771, -1, -1, -1,280), // Totodile Statue  + new(772, -1, -1, -1,281), // Croconaw Statue  + new(773, -1, -1, -1,282), // Feraligatr Statue  + new(774, -1, -1, -1,283), // Furret Statue  + new(775, -1, -1, -1,284), // Noctowl Statue  + new(776, -1, -1, -1,285), // Crobat Statue  + new(777, -1, -1, -1,286), // Togepi Statue  + new(778, -1, -1, -1,287), // Ampharos Statue  + new(779, -1, -1, -1,288), // Bellossom Statue  + new(780, -1, -1, -1,289), // Marill Statue  + new(781, -1, -1, -1,290), // Sudowoodo Statue  + new(782, -1, -1, -1,291), // Aipom Statue  + new(783, -1, -1, -1,292), // Sunkern Statue  + new(784, -1, -1, -1,293), // Quagsire Statue  + new(785, -1, -1, -1,294), // Espeon Statue  + new(786, -1, -1, -1,295), // Umbreon Statue  + new(787, -1, -1, -1,296), // Murkrow Statue  + new(788, -1, -1, -1,297), // Unown Statue  + new(789, -1, -1, -1,298), // Wobbuffet Statue  + new(790, -1, -1, -1,299), // Girafarig Statue  + new(791, -1, -1, -1,300), // Dunsparce Statue  + new(792, -1, -1, -1,301), // Steelix Statue  + new(793, -1, -1, -1,302), // Snubbull Statue  + new(794, -1, -1, -1,303), // Scizor Statue  + new(795, -1, -1, -1,304), // Heracross Statue  + new(796, -1, -1, -1,305), // Magcargo Statue  + new(797, -1, -1, -1,306), // Octillery Statue  + new(798, -1, -1, -1,307), // Mantine Statue  + new(799, -1, -1, -1,308), // Smeargle Statue  + new(800, -1, -1, -1,309), // Miltank Statue  + new(801, -1, -1, -1,310), // Tyranitar Statue  + new(802, -1, -1, -1,311), // Treecko Statue  + new(803, -1, -1, -1,312), // Grovyle Statue  + new(804, -1, -1, -1,313), // Sceptile Statue  + new(805, -1, -1, -1,314), // Torchic Statue  + new(806, -1, -1, -1,315), // Combusken Statue  + new(807, -1, -1, -1,316), // Blaziken Statue  + new(808, -1, -1, -1,317), // Mudkip Statue  + new(809, -1, -1, -1,318), // Marshtomp Statue  + new(810, -1, -1, -1,319), // Swampert Statue  + new(811, -1, -1, -1,320), // Beautifly Statue  + new(812, -1, -1, -1,321), // Dustox Statue  + new(813, -1, -1, -1,322), // Ludicolo Statue  + new(814, -1, -1, -1,323), // Pelipper Statue  + new(815, -1, -1, -1,324), // Gardevoir Statue  + new(816, -1, -1, -1,325), // Shroomish Statue  + new(817, -1, -1, -1,326), // Slaking Statue  + new(818, -1, -1, -1,327), // Ninjask Statue  + new(819, -1, -1, -1,328), // Exploud Statue  + new(820, -1, -1, -1,329), // Skitty Statue  + new(821, -1, -1, -1,330), // Sableye Statue  + new(822, -1, -1, -1,331), // Mawile Statue  + new(823, -1, -1, -1,332), // Aggron Statue  + new(824, -1, -1, -1,333), // Medicham Statue  + new(825, -1, -1, -1,334), // Manectric Statue  + new(826, -1, -1, -1,335), // Plusle Statue  + new(827, -1, -1, -1,336), // Minun Statue  + new(828, -1, -1, -1,337), // Volbeat Statue  + new(829, -1, -1, -1,338), // Illumise Statue  + new(830, -1, -1, -1,339), // Roselia Statue  + new(831, -1, -1, -1,340), // Sharpedo Statue  + new(832, -1, -1, -1,341), // Wailmer Statue  + new(833, -1, -1, -1,342), // Camerupt Statue  + new(834, -1, -1, -1,343), // Torkoal Statue  + new(835, -1, -1, -1,344), // Spinda Statue  + new(836, -1, -1, -1,345), // Flygon Statue  + new(837, -1, -1, -1,346), // Cacturne Statue  + new(838, -1, -1, -1,347), // Altaria Statue  + new(839, -1, -1, -1,348), // Zangoose Statue  + new(840, -1, -1, -1,349), // Seviper Statue  + new(841, -1, -1, -1,350), // Lunatone Statue  + new(842, -1, -1, -1,351), // Solrock Statue  + new(843, -1, -1, -1,352), // Whiscash Statue  + new(844, -1, -1, -1,353), // Claydol Statue  + new(845, -1, -1, -1,354), // Cradily Statue  + new(846, -1, -1, -1,355), // Milotic Statue  + new(847, -1, -1, -1,356), // Castform Statue  + new(848, -1, -1, -1,357), // Kecleon Statue  + new(849, -1, -1, -1,358), // Tropius Statue  + new(850, -1, -1, -1,359), // Chimecho Statue  + new(851, -1, -1, -1,360), // Absol Statue  + new(852, -1, -1, -1,361), // Spheal Statue  + new(853, -1, -1, -1,362), // Clamperl Statue  + new(854, -1, -1, -1,363), // Relicanth Statue  + new(855, -1, -1, -1,364), // Luvdisc Statue  + new(856, -1, -1, -1,365), // Salamence Statue  + new(857, -1, -1, -1,366), // Metagross Statue  + new(858, -1, -1, -1,367), // Turtwig Statue  + new(859, -1, -1, -1,368), // Grotle Statue  + new(860, -1, -1, -1,369), // Torterra Statue  + new(861, -1, -1, -1,370), // Chimchar Statue  + new(862, -1, -1, -1,371), // Monferno Statue  + new(863, -1, -1, -1,372), // Infernape Statue  + new(864, -1, -1, -1,373), // Piplup Statue  + new(865, -1, -1, -1,374), // Prinplup Statue  + new(866, -1, -1, -1,375), // Empoleon Statue  + new(867, -1, -1, -1,376), // Staraptor Statue  + new(868, -1, -1, -1,377), // Bibarel Statue  + new(869, -1, -1, -1,378), // Kricketot Statue  + new(870, -1, -1, -1,379), // Luxray Statue  + new(871, -1, -1, -1,380), // Budew Statue  + new(872, -1, -1, -1,381), // Roserade Statue  + new(873, -1, -1, -1,382), // Cranidos Statue  + new(874, -1, -1, -1,383), // Bastiodon Statue  + new(875, -1, -1, -1,384), // Burmy Statue  + new(876, -1, -1, -1,385), // Wormadam Statue  + new(877, -1, -1, -1,386), // Mothim Statue  + new(878, -1, -1, -1,387), // Vespiquen Statue  + new(879, -1, -1, -1,388), // Pachirisu Statue  + new(880, -1, -1, -1,389), // Floatzel Statue  + new(881, -1, -1, -1,390), // Cherubi Statue  + new(882, -1, -1, -1,391), // Gastrodon Statue  + new(883, -1, -1, -1,392), // Ambipom Statue  + new(884, -1, -1, -1,393), // Drifloon Statue  + new(885, -1, -1, -1,394), // Buneary Statue  + new(886, -1, -1, -1,395), // Mismagius Statue  + new(887, -1, -1, -1,396), // Honchkrow Statue  + new(888, -1, -1, -1,397), // Purugly Statue  + new(889, -1, -1, -1,398), // Skuntank Statue  + new(890, -1, -1, -1,399), // Bronzong Statue  + new(891, -1, -1, -1,400), // Happiny Statue  + new(892, -1, -1, -1,401), // Chatot Statue  + new(893, -1, -1, -1,402), // Spiritomb Statue  + new(894, -1, -1, -1,403), // Garchomp Statue  + new(895, -1, -1, -1,404), // Lucario Statue  + new(896, -1, -1, -1,405), // Hippowdon Statue  + new(897, -1, -1, -1,406), // Drapion Statue  + new(898, -1, -1, -1,407), // Croagunk Statue  + new(899, -1, -1, -1,408), // Carnivine Statue  + new(900, -1, -1, -1,409), // Lumineon Statue  + new(901, -1, -1, -1,410), // Abomasnow Statue  + new(902, -1, -1, -1,411), // Weavile Statue  + new(903, -1, -1, -1,412), // Magnezone Statue  + new(904, -1, -1, -1,413), // Rhyperior Statue  + new(905, -1, -1, -1,414), // Tangrowth Statue  + new(906, -1, -1, -1,415), // Electivire Statue  + new(907, -1, -1, -1,416), // Magmortar Statue  + new(908, -1, -1, -1,417), // Togekiss Statue  + new(909, -1, -1, -1,418), // Yanmega Statue  + new(910, -1, -1, -1,419), // Leafeon Statue  + new(911, -1, -1, -1,420), // Glaceon Statue  + new(912, -1, -1, -1,421), // Gliscor Statue  + new(913, -1, -1, -1,422), // Mamoswine Statue  + new(914, -1, -1, -1,423), // Porygon-Z Statue  + new(915, -1, -1, -1,424), // Gallade Statue  + new(916, -1, -1, -1,425), // Probopass Statue  + new(917, -1, -1, -1,426), // Dusknoir Statue  + new(918, -1, -1, -1,427), // Froslass Statue  + new(919, -1, -1, -1,428), // Rotom Statue  + new(920, -1, -1, -1,429), // Rotom Statue  + new(921, -1, -1, -1,430), // Rotom Statue  + new(922, -1, -1, -1,431), // Rotom Statue  + new(923, -1, -1, -1,432), // Rotom Statue  + new(924, -1, -1, -1,433), // Rotom Statue  + new(925,420, -1, -1, -1), // TM93 + new(926,421, -1, -1, -1), // TM94 + new(927,422, -1, -1, -1), // TM95 + new(928,423, -1, -1, -1), // TM96 + new(929,424, -1, -1, -1), // TM97 + new(930,425, -1, -1, -1), // TM98 + new(931,426, -1, -1, -1), // TM99 + new(932,427, -1, -1, -1), // TM100 + new(933, -1, -1,030, -1), // Square Pedestal XL + new(934, -1, -1,031, -1), // Round Pedestal XL + new(935, -1, -1,032, -1), // Sturdy Pedestal XL + new(936, -1, -1,033, -1), // Clear Pedestal XL + new(937, -1, -1,034, -1), // Spin Pedestal XL + new(938, -1, -1,035, -1), // Spinback Pedestal XL + new(939, -1, -1, -1,434), // Giratina Statue (Origin Form) + new(940, -1, -1, -1, -1), // + new(941, -1, -1, -1, -1), // + new(942, -1, -1, -1, -1), // + new(943, -1, -1, -1, -1), // + new(944, -1, -1, -1, -1), // + new(945, -1, -1, -1, -1), // + new(946, -1, -1, -1, -1), // + new(947, -1, -1, -1, -1), // + new(948, -1, -1, -1, -1), // + new(949, -1, -1, -1, -1), // + new(950, -1, -1, -1, -1), // + new(951, -1, -1, -1, -1), // + new(952, -1, -1, -1, -1), // + new(953, -1, -1, -1, -1), // + new(954, -1, -1, -1, -1), // + new(955, -1, -1, -1, -1), // + new(956, -1, -1, -1, -1), // + }; + #endregion +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgSaveData8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgSaveData8b.cs index 922103b13..4b2fe97e2 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgSaveData8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgSaveData8b.cs @@ -2,139 +2,138 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Details about the Player's underground area state. +/// +/// size: 0x27A0 +[TypeConverter(typeof(ExpandableObjectConverter))] +public sealed class UgSaveData8b : SaveBlock { - /// - /// Details about the Player's underground area state. - /// - /// size: 0x27A0 - [TypeConverter(typeof(ExpandableObjectConverter))] - public sealed class UgSaveData8b : SaveBlock + public const int COUNT_DIGPOINTS = 10; + public const int COUNT_ENCOUNTERS = 15; + public const int COUNT_FRIENDS = 100; // 100 total with Friends+Others, savedata ctor allocates 100 for FriendPlayerList + public const int COUNT_OTHERS = 0; // unused + public const int COUNT_TRAINERS = 100; + + public const int SIZE_SECRETBASE = 0x14 + (MAX_STONE_STATUE * SIZE_STATUE) + 4; // 0x270 + public const int SIZE_PLAYERLISTENTRY = 0x28; + + // structure: + // public int ReturnZoneID; // 0 + // int ReturnGridPosX; // 4 + // int ReturnPosY; // 8 + // int ReturnGridPosZ; // C + // LOCATION_WORK ReturnZenmetsu_Ground; // 0x10-0x23 + // (short X, short Y)[10] DigPoints; // 0x24-0x4B + // SerializedPokemonFull[15] EncountPokes; // 0x4C-0x1473 (PB8 party 0x158 per entry) + // Vector3[15] EncountPokePositions; // 0x1474-0x1527 (3 floats, 0xC per entry) + // int ReturnUgZoneID; // 0x1528 + + // UGRecord ugRecord; (secret base, see below documentation), size 0x270 + // UgPlayerInfo[100] FriendPlayerList; + // UgPlayerInfo[0] OtherPlayerList; // unused + // byte[100] TalkedNPCsID; + + private const int OFS_DIGPOINT = 0x24; + private const int OFS_ENCOUNTPOKE = OFS_DIGPOINT + (COUNT_DIGPOINTS * (2 + 2)); // 0x4C + private const int OFS_ENCOUNTPOS = OFS_ENCOUNTPOKE + (COUNT_ENCOUNTERS * PokeCrypto.SIZE_8PARTY); // 0x1474 + private const int OFS_ReturnUgZoneID = OFS_ENCOUNTPOS + (COUNT_ENCOUNTERS * (4 + 4 + 4)); // 0x1528 + private const int OFS_UgRecord = OFS_ReturnUgZoneID + 4; // 0x152C + private const int OFS_FRIENDS = OFS_UgRecord + SIZE_SECRETBASE; // 0x179C + private const int OFS_OTHERS = OFS_FRIENDS + (COUNT_FRIENDS * SIZE_PLAYERLISTENTRY); // 0x1F6C + + private const int OFS_NPC = OFS_OTHERS + (COUNT_OTHERS * SIZE_PLAYERLISTENTRY); // 0x273C + public const int TOTAL_SIZE = OFS_NPC + COUNT_TRAINERS; + + public const int MAX_STONE_STATUE = 30; + private const int SIZE_STATUE = 0x14; + // UGRecord - Secret Base Structure: + // uint talkPlayerDataID; // 0x0 + // uint talkPlayerCount; // 0x4 + // UgSecretBase myBase; // 0x8 + // short zoneID; // 0x8 + // short posX; // 0xA + // short posY; // 0xC + // byte direction; // 0xE + // byte expansionStatus; // 0xF + // + // int goodCount; // 0x10 + // UgStoneStatue[30] ugStoneStatue; 0x14 + // int StatueID, int PedestalID, int X, int Y, int Direction (size: 20 bytes) + // bool isEnable; // 0x26C + + public UgSaveData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public int ReturnZoneID { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x0), value); } + public int ReturnGridPositionX { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } + public int ReturnGridPositionY { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x8), value); } + public int ReturnGridPositionZ { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x0C)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0xC), value); } + + public int ZenmetsuZoneID { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x10), value); } + public float ZenmetsuPositionX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x14)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x14), value); } + public float ZenmetsuPositionY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x18), value); } + public float ZenmetsuPositionZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x1C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x1C), value); } + public int ZenmetsuDirection { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x20)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x20), value); } + + private Span GetDigPoints() => Data.AsSpan(Offset + 0x24, COUNT_DIGPOINTS); + public void ClearDigPoints() => GetDigPoints().Fill(0xFF); + + public int GetSlotOffset(int slot) { - public const int COUNT_DIGPOINTS = 10; - public const int COUNT_ENCOUNTERS = 15; - public const int COUNT_FRIENDS = 100; // 100 total with Friends+Others, savedata ctor allocates 100 for FriendPlayerList - public const int COUNT_OTHERS = 0; // unused - public const int COUNT_TRAINERS = 100; - - public const int SIZE_SECRETBASE = 0x14 + (MAX_STONE_STATUE * SIZE_STATUE) + 4; // 0x270 - public const int SIZE_PLAYERLISTENTRY = 0x28; - - // structure: - // public int ReturnZoneID; // 0 - // int ReturnGridPosX; // 4 - // int ReturnPosY; // 8 - // int ReturnGridPosZ; // C - // LOCATION_WORK ReturnZenmetsu_Ground; // 0x10-0x23 - // (short X, short Y)[10] DigPoints; // 0x24-0x4B - // SerializedPokemonFull[15] EncountPokes; // 0x4C-0x1473 (PB8 party 0x158 per entry) - // Vector3[15] EncountPokePositions; // 0x1474-0x1527 (3 floats, 0xC per entry) - // int ReturnUgZoneID; // 0x1528 - - // UGRecord ugRecord; (secret base, see below documentation), size 0x270 - // UgPlayerInfo[100] FriendPlayerList; - // UgPlayerInfo[0] OtherPlayerList; // unused - // byte[100] TalkedNPCsID; - - private const int OFS_DIGPOINT = 0x24; - private const int OFS_ENCOUNTPOKE = OFS_DIGPOINT + (COUNT_DIGPOINTS * (2 + 2)); // 0x4C - private const int OFS_ENCOUNTPOS = OFS_ENCOUNTPOKE + (COUNT_ENCOUNTERS * PokeCrypto.SIZE_8PARTY); // 0x1474 - private const int OFS_ReturnUgZoneID = OFS_ENCOUNTPOS + (COUNT_ENCOUNTERS * (4 + 4 + 4)); // 0x1528 - private const int OFS_UgRecord = OFS_ReturnUgZoneID + 4; // 0x152C - private const int OFS_FRIENDS = OFS_UgRecord + SIZE_SECRETBASE; // 0x179C - private const int OFS_OTHERS = OFS_FRIENDS + (COUNT_FRIENDS * SIZE_PLAYERLISTENTRY); // 0x1F6C - - private const int OFS_NPC = OFS_OTHERS + (COUNT_OTHERS * SIZE_PLAYERLISTENTRY); // 0x273C - public const int TOTAL_SIZE = OFS_NPC + COUNT_TRAINERS; - - public const int MAX_STONE_STATUE = 30; - private const int SIZE_STATUE = 0x14; - // UGRecord - Secret Base Structure: - // uint talkPlayerDataID; // 0x0 - // uint talkPlayerCount; // 0x4 - // UgSecretBase myBase; // 0x8 - // short zoneID; // 0x8 - // short posX; // 0xA - // short posY; // 0xC - // byte direction; // 0xE - // byte expansionStatus; // 0xF - // - // int goodCount; // 0x10 - // UgStoneStatue[30] ugStoneStatue; 0x14 - // int StatueID, int PedestalID, int X, int Y, int Direction (size: 20 bytes) - // bool isEnable; // 0x26C - - public UgSaveData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public int ReturnZoneID { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x0), value); } - public int ReturnGridPositionX { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } - public int ReturnGridPositionY { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x8), value); } - public int ReturnGridPositionZ { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x0C)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0xC), value); } - - public int ZenmetsuZoneID { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x10), value); } - public float ZenmetsuPositionX { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x14)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x14), value); } - public float ZenmetsuPositionY { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x18), value); } - public float ZenmetsuPositionZ { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x1C)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x1C), value); } - public int ZenmetsuDirection { get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x20)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x20), value); } - - private Span GetDigPoints() => Data.AsSpan(Offset + 0x24, COUNT_DIGPOINTS); - public void ClearDigPoints() => GetDigPoints().Fill(0xFF); - - public int GetSlotOffset(int slot) - { - if ((uint)slot >= COUNT_ENCOUNTERS) - throw new ArgumentOutOfRangeException(nameof(slot)); - return Offset + OFS_ENCOUNTPOKE + (slot * PokeCrypto.SIZE_8PARTY); - } - - public int ReturnUgZoneID { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_ReturnUgZoneID)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_ReturnUgZoneID), value); } - public uint TalkPlayerDataID { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_UgRecord + 0x0)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_UgRecord + 0x0), value); } - public uint TalkPlayerCount { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_UgRecord + 0x4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_UgRecord + 0x4), value); } - - #region Seen NPCs - - public Span GetTrainers() => Data.AsSpan(Offset + OFS_NPC, COUNT_TRAINERS); - - public void SetTrainers(ReadOnlySpan data) - { - if (Data.Length > COUNT_TRAINERS) - throw new ArgumentOutOfRangeException(nameof(data.Length)); - data.CopyTo(GetTrainers()); - } - - public byte GetNPCSeen(int index) => Data[Offset + OFS_NPC + index]; - public void SetNPCSeen(int index, byte value) => Data[Offset + OFS_NPC + index] = value; - - public void ClearNPC() => GetTrainers().Clear(); - public void ClearNPC(int start, int count = COUNT_TRAINERS) => FillNPC(0, start, count); - - public int TalkedNPC // Spiritomb Trainer Count (duplicates OK) - { - get - { - var tr = GetTrainers(); - int ctr = 0; - foreach (var t in tr) - { - if (t != 0) - ctr++; - } - return ctr; - } - } - - public void FillNPC(byte value, int start = 0, int count = COUNT_TRAINERS) - { - if ((uint)start + (uint)count > COUNT_TRAINERS) - throw new ArgumentOutOfRangeException(nameof(count)); - if ((uint)start > COUNT_TRAINERS) - throw new ArgumentOutOfRangeException(nameof(start)); - - var ofs = Offset + OFS_NPC + start; - for (int i = 0; i < count; i++) - Data[ofs + i] = value; - } - - #endregion + if ((uint)slot >= COUNT_ENCOUNTERS) + throw new ArgumentOutOfRangeException(nameof(slot)); + return Offset + OFS_ENCOUNTPOKE + (slot * PokeCrypto.SIZE_8PARTY); } + + public int ReturnUgZoneID { get => ReadInt32LittleEndian(Data.AsSpan(Offset + OFS_ReturnUgZoneID)); set => WriteInt32LittleEndian(Data.AsSpan(Offset + OFS_ReturnUgZoneID), value); } + public uint TalkPlayerDataID { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_UgRecord + 0x0)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_UgRecord + 0x0), value); } + public uint TalkPlayerCount { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_UgRecord + 0x4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + OFS_UgRecord + 0x4), value); } + + #region Seen NPCs + + public Span GetTrainers() => Data.AsSpan(Offset + OFS_NPC, COUNT_TRAINERS); + + public void SetTrainers(ReadOnlySpan data) + { + if (Data.Length > COUNT_TRAINERS) + throw new ArgumentOutOfRangeException(nameof(data.Length)); + data.CopyTo(GetTrainers()); + } + + public byte GetNPCSeen(int index) => Data[Offset + OFS_NPC + index]; + public void SetNPCSeen(int index, byte value) => Data[Offset + OFS_NPC + index] = value; + + public void ClearNPC() => GetTrainers().Clear(); + public void ClearNPC(int start, int count = COUNT_TRAINERS) => FillNPC(0, start, count); + + public int TalkedNPC // Spiritomb Trainer Count (duplicates OK) + { + get + { + var tr = GetTrainers(); + int ctr = 0; + foreach (var t in tr) + { + if (t != 0) + ctr++; + } + return ctr; + } + } + + public void FillNPC(byte value, int start = 0, int count = COUNT_TRAINERS) + { + if ((uint)start + (uint)count > COUNT_TRAINERS) + throw new ArgumentOutOfRangeException(nameof(count)); + if ((uint)start > COUNT_TRAINERS) + throw new ArgumentOutOfRangeException(nameof(start)); + + var ofs = Offset + OFS_NPC + start; + for (int i = 0; i < count; i++) + Data[ofs + i] = value; + } + + #endregion } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/UndergroundItem8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/UndergroundItem8b.cs index 2c06e4083..ec435cd6d 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/UndergroundItem8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/UndergroundItem8b.cs @@ -1,46 +1,45 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class UndergroundItem8b { - public sealed class UndergroundItem8b + private const int SIZE = 0xC; + public readonly int Index; // not serialized + public UgItemType Type => UgItemUtil.GetType(Index); + public int MaxValue => UgItemUtil.GetMax(Index); + + public int Count { get; set; } + public bool HideNewFlag { get; set; } + public bool IsFavoriteFlag { get; set; } + + public UndergroundItem8b(ReadOnlySpan data, int baseOffset, int index) { - private const int SIZE = 0xC; - public readonly int Index; // not serialized - public UgItemType Type => UgItemUtil.GetType(Index); - public int MaxValue => UgItemUtil.GetMax(Index); + Index = index; + var offset = baseOffset + (SIZE * index); + var span = data.Slice(offset, SIZE); + Read(span); + } - public int Count { get; set; } - public bool HideNewFlag { get; set; } - public bool IsFavoriteFlag { get; set; } + public void Write(Span data, int baseOffset) + { + var offset = baseOffset + (SIZE * Index); + var span = data.Slice(offset, SIZE); + Write(span); + } - public UndergroundItem8b(ReadOnlySpan data, int baseOffset, int index) - { - Index = index; - var offset = baseOffset + (SIZE * index); - var span = data.Slice(offset, SIZE); - Read(span); - } + private void Read(ReadOnlySpan span) + { + Count = ReadInt32LittleEndian(span); + HideNewFlag = ReadUInt32LittleEndian(span[4..]) == 1; + IsFavoriteFlag = ReadUInt32LittleEndian(span[8..]) == 1; + } - public void Write(Span data, int baseOffset) - { - var offset = baseOffset + (SIZE * Index); - var span = data.Slice(offset, SIZE); - Write(span); - } - - private void Read(ReadOnlySpan span) - { - Count = ReadInt32LittleEndian(span); - HideNewFlag = ReadUInt32LittleEndian(span[4..]) == 1; - IsFavoriteFlag = ReadUInt32LittleEndian(span[8..]) == 1; - } - - private void Write(Span span) - { - WriteInt32LittleEndian(span, Count); - WriteUInt32LittleEndian(span[4..], HideNewFlag ? 1u : 0u); - WriteUInt32LittleEndian(span[8..], IsFavoriteFlag ? 1u : 0u); - } + private void Write(Span span) + { + WriteInt32LittleEndian(span, Count); + WriteUInt32LittleEndian(span[4..], HideNewFlag ? 1u : 0u); + WriteUInt32LittleEndian(span[8..], IsFavoriteFlag ? 1u : 0u); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/UndergroundItemList8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/UndergroundItemList8b.cs index b2f192228..a76973d4c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/UndergroundItemList8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/UndergroundItemList8b.cs @@ -1,31 +1,30 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class UndergroundItemList8b : SaveBlock { - public sealed class UndergroundItemList8b : SaveBlock + public const int ItemSaveSize = 999; + public const int ItemMaxCount = 999; + public const int StatueMaxCount = 99; + + public UndergroundItemList8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; + + public IReadOnlyList ReadItems() { - public const int ItemSaveSize = 999; - public const int ItemMaxCount = 999; - public const int StatueMaxCount = 99; + var result = new UndergroundItem8b[ItemSaveSize]; + for (int i = 0; i < result.Length; i++) + result[i] = new UndergroundItem8b(Data, Offset, i); + return result; + } - public UndergroundItemList8b(SAV8BS sav, int offset) : base(sav) => Offset = offset; - - public IReadOnlyList ReadItems() - { - var result = new UndergroundItem8b[ItemSaveSize]; - for (int i = 0; i < result.Length; i++) - result[i] = new UndergroundItem8b(Data, Offset, i); - return result; - } - - public void WriteItems(IReadOnlyList items) - { - if (items.Count != ItemSaveSize) - throw new ArgumentOutOfRangeException(nameof(items.Count)); - foreach (var item in items) - item.Write(Data, Offset); - SAV.State.Edited = true; - } + public void WriteItems(IReadOnlyList items) + { + if (items.Count != ItemSaveSize) + throw new ArgumentOutOfRangeException(nameof(items.Count)); + foreach (var item in items) + item.Write(Data, Offset); + SAV.State.Edited = true; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/Zukan8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/Zukan8b.cs index 96f22c7e6..c70894eb4 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/Zukan8b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/Zukan8b.cs @@ -1,488 +1,487 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokédex structure used for Brilliant Diamond & Shining Pearl. +/// +/// size 0x30B8, struct_name ZUKAN_WORK +public sealed class Zukan8b : ZukanBase { - /// - /// Pokédex structure used for Brilliant Diamond & Shining Pearl. - /// - /// size 0x30B8, struct_name ZUKAN_WORK - public sealed class Zukan8b : ZukanBase + /* Structure Notes: + u32 [493] state: None/HeardOf/Seen/Captured + bool[493] maleShiny + bool[493] femaleShiny + bool[493] male + bool[493] female + bool[28] Unown Form + bool[28] Unown FormShiny + bool[4] Castform + bool[4] Castform + bool[4] Deoxys + bool[4] Deoxys + bool[3] Burmy + bool[3] Burmy + bool[3] Wormadam + bool[3] Wormadam + bool[3] Wormadam + bool[3] Wormadam + bool[3] Mothim + bool[3] Mothim + bool[2] Cherrim + bool[2] Cherrim + bool[2] Shellos + bool[2] Shellos + bool[2] Gastrodon + bool[2] Gastrodon + bool[6] Rotom + bool[6] Rotom + bool[2] Giratina + bool[2] Giratina + bool[2] Shaymin + bool[2] Shaymin + bool[18] Arceus + bool[18] Arceus + u32 [493] language flags + bool regional dex obtained + bool national dex obtained + */ + + private const int COUNT_SPECIES = 493; + private const int COUNT_UNOWN = 28; + private const int COUNT_CASTFORM = 4; + private const int COUNT_DEOXYS = 4; + private const int COUNT_BURMY = 3; + private const int COUNT_WORMADAM = 3; + private const int COUNT_MOTHIM = 3; + private const int COUNT_CHERRIM = 2; + private const int COUNT_SHELLOS = 2; + private const int COUNT_GASTRODON = 2; + private const int COUNT_ROTOM = 6; + private const int COUNT_GIRATINA = 2; + private const int COUNT_SHAYMIN = 2; + private const int COUNT_ARCEUS = 18; + + private const int SIZE_SPECIES = sizeof(ZukanState8b) * COUNT_SPECIES; + private const int ALIGN_BOOLARRAY = 4; + private const int SIZE_SPECIESBOOL = ALIGN_BOOLARRAY * COUNT_SPECIES; + private const int SIZE_UNOWN = ALIGN_BOOLARRAY * COUNT_UNOWN; + private const int SIZE_CASTFORM = ALIGN_BOOLARRAY * COUNT_CASTFORM; + private const int SIZE_DEOXYS = ALIGN_BOOLARRAY * COUNT_DEOXYS; + private const int SIZE_BURMY = ALIGN_BOOLARRAY * COUNT_BURMY; + private const int SIZE_WORMADAM = ALIGN_BOOLARRAY * COUNT_WORMADAM; + private const int SIZE_MOTHIM = ALIGN_BOOLARRAY * COUNT_MOTHIM; + private const int SIZE_CHERRIM = ALIGN_BOOLARRAY * COUNT_CHERRIM; + private const int SIZE_SHELLOS = ALIGN_BOOLARRAY * COUNT_SHELLOS; + private const int SIZE_GASTRODON = ALIGN_BOOLARRAY * COUNT_GASTRODON; + private const int SIZE_ROTOM = ALIGN_BOOLARRAY * COUNT_ROTOM; + private const int SIZE_GIRATINA = ALIGN_BOOLARRAY * COUNT_GIRATINA; + private const int SIZE_SHAYMIN = ALIGN_BOOLARRAY * COUNT_SHAYMIN; + private const int SIZE_ARCEUS = ALIGN_BOOLARRAY * COUNT_ARCEUS; + private const int SIZE_LANGUAGE = sizeof(int) * COUNT_SPECIES; + + private const int OFS_STATE = 0; + private const int OFS_MALESHINY = OFS_STATE + SIZE_SPECIES; + private const int OFS_FEMALESHINY = OFS_MALESHINY + SIZE_SPECIESBOOL; + private const int OFS_MALE = OFS_FEMALESHINY + SIZE_SPECIESBOOL; + private const int OFS_FEMALE = OFS_MALE + SIZE_SPECIESBOOL; + + private const int OFS_UNOWN = OFS_FEMALE + SIZE_SPECIESBOOL; + private const int OFS_SUNOWN = OFS_UNOWN + SIZE_UNOWN; + private const int OFS_CASTFORM = OFS_SUNOWN + SIZE_UNOWN; + private const int OFS_SCASTFORM = OFS_CASTFORM + SIZE_CASTFORM; + private const int OFS_DEOXYS = OFS_SCASTFORM + SIZE_CASTFORM; + private const int OFS_SDEOXYS = OFS_DEOXYS + SIZE_DEOXYS; + private const int OFS_BURMY = OFS_SDEOXYS + SIZE_DEOXYS; + private const int OFS_SBURMY = OFS_BURMY + SIZE_BURMY; + private const int OFS_WORMADAM = OFS_SBURMY + SIZE_BURMY; + private const int OFS_SWORMADAM = OFS_WORMADAM + SIZE_WORMADAM; + private const int OFS_MOTHIM = OFS_SWORMADAM + SIZE_WORMADAM; + private const int OFS_SMOTHIM = OFS_MOTHIM + SIZE_MOTHIM; + private const int OFS_CHERRIM = OFS_SMOTHIM + SIZE_MOTHIM; + private const int OFS_SCHERRIM = OFS_CHERRIM + SIZE_CHERRIM; + private const int OFS_SHELLOS = OFS_SCHERRIM + SIZE_CHERRIM; + private const int OFS_SSHELLOS = OFS_SHELLOS + SIZE_SHELLOS; + private const int OFS_GASTRODON = OFS_SSHELLOS + SIZE_SHELLOS; + private const int OFS_SGASTRODON = OFS_GASTRODON + SIZE_GASTRODON; + private const int OFS_ROTOM = OFS_SGASTRODON + SIZE_GASTRODON; + private const int OFS_SROTOM = OFS_ROTOM + SIZE_ROTOM; + private const int OFS_GIRATINA = OFS_SROTOM + SIZE_ROTOM; + private const int OFS_SGIRATINA = OFS_GIRATINA + SIZE_GIRATINA; + private const int OFS_SHAYMIN = OFS_SGIRATINA + SIZE_GIRATINA; + private const int OFS_SSHAYMIN = OFS_SHAYMIN + SIZE_SHAYMIN; + private const int OFS_ARCEUS = OFS_SSHAYMIN + SIZE_SHAYMIN; + private const int OFS_SARCEUS = OFS_ARCEUS + SIZE_ARCEUS; + + private const int OFS_LANGUAGE = OFS_SARCEUS + SIZE_ARCEUS; + private const int OFS_FLAG_REGIONAL = OFS_LANGUAGE + SIZE_LANGUAGE; // 0x30B0 + private const int OFS_FLAG_NATIONAL = OFS_FLAG_REGIONAL + 4; // 0x30B4 + // sizeof(this) = 0x30B8 + + private const int LANGUAGE_NONE = 0; + private const int LANGUAGE_ALL = // 0x1FF + (1 << ((int)LanguageID.Japanese - 1)) | + (1 << ((int)LanguageID.English - 1)) | + (1 << ((int)LanguageID.French - 1)) | + (1 << ((int)LanguageID.Italian - 1)) | + (1 << ((int)LanguageID.German - 1)) | + (1 << ((int)LanguageID.Spanish - 2)) | // Skip over Language 6 (unused) + (1 << ((int)LanguageID.Korean - 2)) | + (1 << ((int)LanguageID.ChineseS - 2)) | + (1 << ((int)LanguageID.ChineseT - 2)); + + private static PersonalTable Personal => PersonalTable.BDSP; + + public Zukan8b(SAV8BS sav, int dex) : base(sav, dex) { } + + public ZukanState8b GetState(int species) { - /* Structure Notes: - u32 [493] state: None/HeardOf/Seen/Captured - bool[493] maleShiny - bool[493] femaleShiny - bool[493] male - bool[493] female - bool[28] Unown Form - bool[28] Unown FormShiny - bool[4] Castform - bool[4] Castform - bool[4] Deoxys - bool[4] Deoxys - bool[3] Burmy - bool[3] Burmy - bool[3] Wormadam - bool[3] Wormadam - bool[3] Wormadam - bool[3] Wormadam - bool[3] Mothim - bool[3] Mothim - bool[2] Cherrim - bool[2] Cherrim - bool[2] Shellos - bool[2] Shellos - bool[2] Gastrodon - bool[2] Gastrodon - bool[6] Rotom - bool[6] Rotom - bool[2] Giratina - bool[2] Giratina - bool[2] Shaymin - bool[2] Shaymin - bool[18] Arceus - bool[18] Arceus - u32 [493] language flags - bool regional dex obtained - bool national dex obtained - */ + if ((uint)species > Legal.MaxSpeciesID_4) + throw new ArgumentOutOfRangeException(nameof(species)); - private const int COUNT_SPECIES = 493; - private const int COUNT_UNOWN = 28; - private const int COUNT_CASTFORM = 4; - private const int COUNT_DEOXYS = 4; - private const int COUNT_BURMY = 3; - private const int COUNT_WORMADAM = 3; - private const int COUNT_MOTHIM = 3; - private const int COUNT_CHERRIM = 2; - private const int COUNT_SHELLOS = 2; - private const int COUNT_GASTRODON = 2; - private const int COUNT_ROTOM = 6; - private const int COUNT_GIRATINA = 2; - private const int COUNT_SHAYMIN = 2; - private const int COUNT_ARCEUS = 18; + var index = species - 1; + var offset = OFS_STATE + (sizeof(int) * index); + return (ZukanState8b)ReadInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset)); + } - private const int SIZE_SPECIES = sizeof(ZukanState8b) * COUNT_SPECIES; - private const int ALIGN_BOOLARRAY = 4; - private const int SIZE_SPECIESBOOL = ALIGN_BOOLARRAY * COUNT_SPECIES; - private const int SIZE_UNOWN = ALIGN_BOOLARRAY * COUNT_UNOWN; - private const int SIZE_CASTFORM = ALIGN_BOOLARRAY * COUNT_CASTFORM; - private const int SIZE_DEOXYS = ALIGN_BOOLARRAY * COUNT_DEOXYS; - private const int SIZE_BURMY = ALIGN_BOOLARRAY * COUNT_BURMY; - private const int SIZE_WORMADAM = ALIGN_BOOLARRAY * COUNT_WORMADAM; - private const int SIZE_MOTHIM = ALIGN_BOOLARRAY * COUNT_MOTHIM; - private const int SIZE_CHERRIM = ALIGN_BOOLARRAY * COUNT_CHERRIM; - private const int SIZE_SHELLOS = ALIGN_BOOLARRAY * COUNT_SHELLOS; - private const int SIZE_GASTRODON = ALIGN_BOOLARRAY * COUNT_GASTRODON; - private const int SIZE_ROTOM = ALIGN_BOOLARRAY * COUNT_ROTOM; - private const int SIZE_GIRATINA = ALIGN_BOOLARRAY * COUNT_GIRATINA; - private const int SIZE_SHAYMIN = ALIGN_BOOLARRAY * COUNT_SHAYMIN; - private const int SIZE_ARCEUS = ALIGN_BOOLARRAY * COUNT_ARCEUS; - private const int SIZE_LANGUAGE = sizeof(int) * COUNT_SPECIES; + public void SetState(int species, ZukanState8b state) + { + if ((uint)species > Legal.MaxSpeciesID_4) + throw new ArgumentOutOfRangeException(nameof(species)); - private const int OFS_STATE = 0; - private const int OFS_MALESHINY = OFS_STATE + SIZE_SPECIES; - private const int OFS_FEMALESHINY = OFS_MALESHINY + SIZE_SPECIESBOOL; - private const int OFS_MALE = OFS_FEMALESHINY + SIZE_SPECIESBOOL; - private const int OFS_FEMALE = OFS_MALE + SIZE_SPECIESBOOL; + var index = species - 1; + var offset = OFS_STATE + (sizeof(int) * index); + WriteInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset), (int)state); + } - private const int OFS_UNOWN = OFS_FEMALE + SIZE_SPECIESBOOL; - private const int OFS_SUNOWN = OFS_UNOWN + SIZE_UNOWN; - private const int OFS_CASTFORM = OFS_SUNOWN + SIZE_UNOWN; - private const int OFS_SCASTFORM = OFS_CASTFORM + SIZE_CASTFORM; - private const int OFS_DEOXYS = OFS_SCASTFORM + SIZE_CASTFORM; - private const int OFS_SDEOXYS = OFS_DEOXYS + SIZE_DEOXYS; - private const int OFS_BURMY = OFS_SDEOXYS + SIZE_DEOXYS; - private const int OFS_SBURMY = OFS_BURMY + SIZE_BURMY; - private const int OFS_WORMADAM = OFS_SBURMY + SIZE_BURMY; - private const int OFS_SWORMADAM = OFS_WORMADAM + SIZE_WORMADAM; - private const int OFS_MOTHIM = OFS_SWORMADAM + SIZE_WORMADAM; - private const int OFS_SMOTHIM = OFS_MOTHIM + SIZE_MOTHIM; - private const int OFS_CHERRIM = OFS_SMOTHIM + SIZE_MOTHIM; - private const int OFS_SCHERRIM = OFS_CHERRIM + SIZE_CHERRIM; - private const int OFS_SHELLOS = OFS_SCHERRIM + SIZE_CHERRIM; - private const int OFS_SSHELLOS = OFS_SHELLOS + SIZE_SHELLOS; - private const int OFS_GASTRODON = OFS_SSHELLOS + SIZE_SHELLOS; - private const int OFS_SGASTRODON = OFS_GASTRODON + SIZE_GASTRODON; - private const int OFS_ROTOM = OFS_SGASTRODON + SIZE_GASTRODON; - private const int OFS_SROTOM = OFS_ROTOM + SIZE_ROTOM; - private const int OFS_GIRATINA = OFS_SROTOM + SIZE_ROTOM; - private const int OFS_SGIRATINA = OFS_GIRATINA + SIZE_GIRATINA; - private const int OFS_SHAYMIN = OFS_SGIRATINA + SIZE_GIRATINA; - private const int OFS_SSHAYMIN = OFS_SHAYMIN + SIZE_SHAYMIN; - private const int OFS_ARCEUS = OFS_SSHAYMIN + SIZE_SHAYMIN; - private const int OFS_SARCEUS = OFS_ARCEUS + SIZE_ARCEUS; + private bool GetBoolean(int index, int baseOffset, int max) + { + if ((uint)index > (uint)max) + throw new ArgumentOutOfRangeException(nameof(index)); - private const int OFS_LANGUAGE = OFS_SARCEUS + SIZE_ARCEUS; - private const int OFS_FLAG_REGIONAL = OFS_LANGUAGE + SIZE_LANGUAGE; // 0x30B0 - private const int OFS_FLAG_NATIONAL = OFS_FLAG_REGIONAL + 4; // 0x30B4 - // sizeof(this) = 0x30B8 + var offset = baseOffset + (ALIGN_BOOLARRAY * index); + return ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset)) == 1; + } - private const int LANGUAGE_NONE = 0; - private const int LANGUAGE_ALL = // 0x1FF - 1 << (int)LanguageID.Japanese - 1 | - 1 << (int)LanguageID.English - 1 | - 1 << (int)LanguageID.French - 1 | - 1 << (int)LanguageID.Italian - 1 | - 1 << (int)LanguageID.German - 1 | - 1 << (int)LanguageID.Spanish - 2 | // Skip over Language 6 (unused) - 1 << (int)LanguageID.Korean - 2 | - 1 << (int)LanguageID.ChineseS - 2 | - 1 << (int)LanguageID.ChineseT - 2; + private void SetBoolean(int index, int baseOffset, int max, bool value) + { + if ((uint)index > (uint)max) + throw new ArgumentOutOfRangeException(nameof(index)); - private static PersonalTable Personal => PersonalTable.BDSP; + var offset = baseOffset + (ALIGN_BOOLARRAY * index); + WriteUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset), value ? 1u : 0u); + } - public Zukan8b(SAV8BS sav, int dex) : base(sav, dex) { } + public void GetGenderFlags(int species, out bool m, out bool f, out bool ms, out bool fs) + { + m = GetBoolean(species - 1, OFS_MALE, COUNT_SPECIES - 1); + f = GetBoolean(species - 1, OFS_FEMALE, COUNT_SPECIES - 1); + ms = GetBoolean(species - 1, OFS_MALESHINY, COUNT_SPECIES - 1); + fs = GetBoolean(species - 1, OFS_FEMALESHINY, COUNT_SPECIES - 1); + } - public ZukanState8b GetState(int species) + public void SetGenderFlags(int species, bool m, bool f, bool ms, bool fs) + { + SetBoolean(species - 1, OFS_MALE, COUNT_SPECIES - 1, m); + SetBoolean(species - 1, OFS_FEMALE, COUNT_SPECIES - 1, f); + SetBoolean(species - 1, OFS_MALESHINY, COUNT_SPECIES - 1, ms); + SetBoolean(species - 1, OFS_FEMALESHINY, COUNT_SPECIES - 1, fs); + } + + public bool GetLanguageFlag(int species, int language) + { + if ((uint)species > Legal.MaxSpeciesID_4) + throw new ArgumentOutOfRangeException(nameof(species)); + var languageBit = GetLanguageBit(language); + if (languageBit == -1) + return false; + + var index = species - 1; + var offset = OFS_LANGUAGE + (sizeof(int) * index); + var current = ReadInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset)); + return (current & (1 << languageBit)) != 0; + } + + public void SetLanguageFlag(int species, int language, bool value) + { + if ((uint)species > Legal.MaxSpeciesID_4) + throw new ArgumentOutOfRangeException(nameof(species)); + var languageBit = GetLanguageBit(language); + if (languageBit == -1) + return; + + var index = species - 1; + var offset = OFS_LANGUAGE + (sizeof(int) * index); + var current = ReadInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset)); + var mask = (1 << languageBit); + var update = value ? current | mask : current & ~(mask); + WriteInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset), update); + } + + public void SetLanguageFlags(int species, int value) + { + if ((uint)species > Legal.MaxSpeciesID_4) + throw new ArgumentOutOfRangeException(nameof(species)); + + var index = species - 1; + var offset = OFS_LANGUAGE + (sizeof(int) * index); + WriteInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset), value); + } + + private static int GetLanguageBit(int language) + { + if (language is 0 or (int)LanguageID.UNUSED_6 or > (int)LanguageID.ChineseT) + return -1; + if (language >= (int)LanguageID.Spanish) + return language - 2; + return language - 1; + } + + public bool HasRegionalDex + { + get => ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + OFS_FLAG_REGIONAL)) == 1; + set => WriteUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + OFS_FLAG_REGIONAL), value ? 1u : 0u); + } + + public bool HasNationalDex + { + get => ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + OFS_FLAG_NATIONAL)) == 1; + set => WriteUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + OFS_FLAG_NATIONAL), value ? 1u : 0u); + } + + public bool GetHasFormFlag(int species, int form, bool shiny) + { + var ct = GetFormCount(species); + if (ct == 0) + return false; + + var baseOffset = GetFormOffset(species); + var sizeShift = shiny ? GetFormSize(species) : 0; + var offset = baseOffset + sizeShift + (ALIGN_BOOLARRAY * form); + return ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset)) == 1; + } + + public void SetHasFormFlag(int species, int form, bool shiny, bool value) + { + var formCount = GetFormCount(species); + if (formCount is 0 || (uint)form >= formCount) + return; + + var baseOffset = GetFormOffset(species); + var sizeShift = shiny ? GetFormSize(species) : 0; + var offset = baseOffset + sizeShift + (ALIGN_BOOLARRAY * form); + WriteUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset), value ? 1u : 0u); + } + + public static int GetFormCount(int species) => species switch + { + (int)Species.Unown => COUNT_UNOWN, + (int)Species.Castform => COUNT_CASTFORM, + (int)Species.Deoxys => COUNT_DEOXYS, + (int)Species.Burmy => COUNT_BURMY, + (int)Species.Wormadam => COUNT_WORMADAM, + (int)Species.Mothim => COUNT_MOTHIM, + (int)Species.Cherrim => COUNT_CHERRIM, + (int)Species.Shellos => COUNT_SHELLOS, + (int)Species.Gastrodon => COUNT_GASTRODON, + (int)Species.Rotom => COUNT_ROTOM, + (int)Species.Giratina => COUNT_GIRATINA, + (int)Species.Shaymin => COUNT_SHAYMIN, + (int)Species.Arceus => COUNT_ARCEUS, + _ => 0, + }; + + private static int GetFormSize(int species) => species switch + { + (int)Species.Unown => SIZE_UNOWN, + (int)Species.Castform => SIZE_CASTFORM, + (int)Species.Deoxys => SIZE_DEOXYS, + (int)Species.Burmy => SIZE_BURMY, + (int)Species.Wormadam => SIZE_WORMADAM, + (int)Species.Mothim => SIZE_MOTHIM, + (int)Species.Cherrim => SIZE_CHERRIM, + (int)Species.Shellos => SIZE_SHELLOS, + (int)Species.Gastrodon => SIZE_GASTRODON, + (int)Species.Rotom => SIZE_ROTOM, + (int)Species.Giratina => SIZE_GIRATINA, + (int)Species.Shaymin => SIZE_SHAYMIN, + (int)Species.Arceus => SIZE_ARCEUS, + _ => 0, + }; + + private static int GetFormOffset(int species) => species switch + { + (int)Species.Unown => OFS_UNOWN, + (int)Species.Castform => OFS_CASTFORM, + (int)Species.Deoxys => OFS_DEOXYS, + (int)Species.Burmy => OFS_BURMY, + (int)Species.Wormadam => OFS_WORMADAM, + (int)Species.Mothim => OFS_MOTHIM, + (int)Species.Cherrim => OFS_CHERRIM, + (int)Species.Shellos => OFS_SHELLOS, + (int)Species.Gastrodon => OFS_GASTRODON, + (int)Species.Rotom => OFS_ROTOM, + (int)Species.Giratina => OFS_GIRATINA, + (int)Species.Shaymin => OFS_SHAYMIN, + (int)Species.Arceus => OFS_ARCEUS, + _ => 0, + }; + + public bool GetHeard(int species) => GetState(species) >= ZukanState8b.HeardOf; + public override bool GetSeen(int species) => GetState(species) >= ZukanState8b.Seen; + public override bool GetCaught(int species) => GetState(species) >= ZukanState8b.Caught; + + public override void SetDex(PKM pk) + { + int species = pk.Species; + if (species is 0 or > Legal.MaxSpeciesID_4) + return; + if (pk.IsEgg) // do not add + return; + + var originalState = GetState(species); + bool shiny = pk.IsShiny; + SetState(species, ZukanState8b.Caught); + SetGenderFlag(species, pk.Gender, shiny); + SetLanguageFlag(species, pk.Language, true); + SetHasFormFlag(species, pk.Form, shiny, true); + if (species is (int)Species.Spinda) + ((SAV8BS)SAV).ZukanExtra.SetDex(originalState, pk.EncryptionConstant, pk.Gender, shiny); + } + + private void SetGenderFlag(int species, int gender, bool shiny) + { + switch (gender) { - if ((uint)species > Legal.MaxSpeciesID_4) - throw new ArgumentOutOfRangeException(nameof(species)); - - var index = species - 1; - var offset = OFS_STATE + (sizeof(int) * index); - return (ZukanState8b)ReadInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset)); + case 0: SetGenderFlagMale(species, shiny); break; + case 1: SetGenderFlagFemale(species, shiny); break; + case 2: // Yep, sets both gender flags. + SetGenderFlagMale(species, shiny); + SetGenderFlagFemale(species, shiny); + break; } + } - public void SetState(int species, ZukanState8b state) + private void SetGenderFlagMale(int species, bool shiny) => SetBoolean(species - 1, shiny ? OFS_MALESHINY : OFS_MALE, COUNT_SPECIES - 1, true); + private void SetGenderFlagFemale(int species, bool shiny) => SetBoolean(species - 1, shiny ? OFS_FEMALESHINY : OFS_FEMALE, COUNT_SPECIES - 1, true); + + public override void SeenNone() + { + for (int species = 1; species <= Legal.MaxSpeciesID_4; species++) + ClearDexEntryAll(species); + } + + public override void CaughtNone() + { + for (int species = 1; species <= Legal.MaxSpeciesID_4; species++) { - if ((uint)species > Legal.MaxSpeciesID_4) - throw new ArgumentOutOfRangeException(nameof(species)); - - var index = species - 1; - var offset = OFS_STATE + (sizeof(int) * index); - WriteInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset), (int)state); + if (GetCaught(species)) + SetState(species, ZukanState8b.Seen); + SetLanguageFlags(species, 0); } + } - private bool GetBoolean(int index, int baseOffset, int max) + public override void SeenAll(bool shinyToo = false) + { + var pt = Personal; + for (int i = 1; i <= Legal.MaxSpeciesID_4; i++) { - if ((uint)index > (uint)max) - throw new ArgumentOutOfRangeException(nameof(index)); - - var offset = baseOffset + (ALIGN_BOOLARRAY * index); - return ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset)) == 1; + if (!GetSeen(i)) + SetState(i, ZukanState8b.Seen); + var pi = pt[i]; + var m = !pi.OnlyFemale; + var f = !pi.OnlyMale; + SetGenderFlags(i, m, f, m && shinyToo, f && shinyToo); } + } - private void SetBoolean(int index, int baseOffset, int max, bool value) - { - if ((uint)index > (uint)max) - throw new ArgumentOutOfRangeException(nameof(index)); + public override void CompleteDex(bool shinyToo = false) + { + for (int species = 1; species <= Legal.MaxSpeciesID_4; species++) + SetDexEntryAll(species, shinyToo); + } - var offset = baseOffset + (ALIGN_BOOLARRAY * index); - WriteUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset), value ? 1u : 0u); - } - - public void GetGenderFlags(int species, out bool m, out bool f, out bool ms, out bool fs) - { - m = GetBoolean(species - 1, OFS_MALE, COUNT_SPECIES - 1); - f = GetBoolean(species - 1, OFS_FEMALE, COUNT_SPECIES - 1); - ms = GetBoolean(species - 1, OFS_MALESHINY, COUNT_SPECIES - 1); - fs = GetBoolean(species - 1, OFS_FEMALESHINY, COUNT_SPECIES - 1); - } - - public void SetGenderFlags(int species, bool m, bool f, bool ms, bool fs) - { - SetBoolean(species - 1, OFS_MALE, COUNT_SPECIES - 1, m); - SetBoolean(species - 1, OFS_FEMALE, COUNT_SPECIES - 1, f); - SetBoolean(species - 1, OFS_MALESHINY, COUNT_SPECIES - 1, ms); - SetBoolean(species - 1, OFS_FEMALESHINY, COUNT_SPECIES - 1, fs); - } - - public bool GetLanguageFlag(int species, int language) - { - if ((uint)species > Legal.MaxSpeciesID_4) - throw new ArgumentOutOfRangeException(nameof(species)); - var languageBit = GetLanguageBit(language); - if (languageBit == -1) - return false; - - var index = species - 1; - var offset = OFS_LANGUAGE + (sizeof(int) * index); - var current = ReadInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset)); - return (current & (1 << languageBit)) != 0; - } - - public void SetLanguageFlag(int species, int language, bool value) - { - if ((uint)species > Legal.MaxSpeciesID_4) - throw new ArgumentOutOfRangeException(nameof(species)); - var languageBit = GetLanguageBit(language); - if (languageBit == -1) - return; - - var index = species - 1; - var offset = OFS_LANGUAGE + (sizeof(int) * index); - var current = ReadInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset)); - var mask = (1 << languageBit); - var update = value ? current | mask : current & ~(mask); - WriteInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset), update); - } - - public void SetLanguageFlags(int species, int value) - { - if ((uint)species > Legal.MaxSpeciesID_4) - throw new ArgumentOutOfRangeException(nameof(species)); - - var index = species - 1; - var offset = OFS_LANGUAGE + (sizeof(int) * index); - WriteInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset), value); - } - - private static int GetLanguageBit(int language) - { - if (language is 0 or (int)LanguageID.UNUSED_6 or > (int)LanguageID.ChineseT) - return -1; - if (language >= (int)LanguageID.Spanish) - return language - 2; - return language - 1; - } - - public bool HasRegionalDex - { - get => ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + OFS_FLAG_REGIONAL)) == 1; - set => WriteUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + OFS_FLAG_REGIONAL), value ? 1u : 0u); - } - - public bool HasNationalDex - { - get => ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + OFS_FLAG_NATIONAL)) == 1; - set => WriteUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + OFS_FLAG_NATIONAL), value ? 1u : 0u); - } - - public bool GetHasFormFlag(int species, int form, bool shiny) - { - var ct = GetFormCount(species); - if (ct == 0) - return false; - - var baseOffset = GetFormOffset(species); - var sizeShift = shiny ? GetFormSize(species) : 0; - var offset = baseOffset + sizeShift + (ALIGN_BOOLARRAY * form); - return ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset)) == 1; - } - - public void SetHasFormFlag(int species, int form, bool shiny, bool value) - { - var formCount = GetFormCount(species); - if (formCount is 0 || (uint)form >= formCount) - return; - - var baseOffset = GetFormOffset(species); - var sizeShift = shiny ? GetFormSize(species) : 0; - var offset = baseOffset + sizeShift + (ALIGN_BOOLARRAY * form); - WriteUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + offset), value ? 1u : 0u); - } - - public static int GetFormCount(int species) => species switch - { - (int)Species.Unown => COUNT_UNOWN, - (int)Species.Castform => COUNT_CASTFORM, - (int)Species.Deoxys => COUNT_DEOXYS, - (int)Species.Burmy => COUNT_BURMY, - (int)Species.Wormadam => COUNT_WORMADAM, - (int)Species.Mothim => COUNT_MOTHIM, - (int)Species.Cherrim => COUNT_CHERRIM, - (int)Species.Shellos => COUNT_SHELLOS, - (int)Species.Gastrodon => COUNT_GASTRODON, - (int)Species.Rotom => COUNT_ROTOM, - (int)Species.Giratina => COUNT_GIRATINA, - (int)Species.Shaymin => COUNT_SHAYMIN, - (int)Species.Arceus => COUNT_ARCEUS, - _ => 0, - }; - - private static int GetFormSize(int species) => species switch - { - (int)Species.Unown => SIZE_UNOWN, - (int)Species.Castform => SIZE_CASTFORM, - (int)Species.Deoxys => SIZE_DEOXYS, - (int)Species.Burmy => SIZE_BURMY, - (int)Species.Wormadam => SIZE_WORMADAM, - (int)Species.Mothim => SIZE_MOTHIM, - (int)Species.Cherrim => SIZE_CHERRIM, - (int)Species.Shellos => SIZE_SHELLOS, - (int)Species.Gastrodon => SIZE_GASTRODON, - (int)Species.Rotom => SIZE_ROTOM, - (int)Species.Giratina => SIZE_GIRATINA, - (int)Species.Shaymin => SIZE_SHAYMIN, - (int)Species.Arceus => SIZE_ARCEUS, - _ => 0, - }; - - private static int GetFormOffset(int species) => species switch - { - (int)Species.Unown => OFS_UNOWN, - (int)Species.Castform => OFS_CASTFORM, - (int)Species.Deoxys => OFS_DEOXYS, - (int)Species.Burmy => OFS_BURMY, - (int)Species.Wormadam => OFS_WORMADAM, - (int)Species.Mothim => OFS_MOTHIM, - (int)Species.Cherrim => OFS_CHERRIM, - (int)Species.Shellos => OFS_SHELLOS, - (int)Species.Gastrodon => OFS_GASTRODON, - (int)Species.Rotom => OFS_ROTOM, - (int)Species.Giratina => OFS_GIRATINA, - (int)Species.Shaymin => OFS_SHAYMIN, - (int)Species.Arceus => OFS_ARCEUS, - _ => 0, - }; - - public bool GetHeard(int species) => GetState(species) >= ZukanState8b.HeardOf; - public override bool GetSeen(int species) => GetState(species) >= ZukanState8b.Seen; - public override bool GetCaught(int species) => GetState(species) >= ZukanState8b.Caught; - - public override void SetDex(PKM pkm) - { - int species = pkm.Species; - if (species is 0 or > Legal.MaxSpeciesID_4) - return; - if (pkm.IsEgg) // do not add - return; - - var originalState = GetState(species); - bool shiny = pkm.IsShiny; - SetState(species, ZukanState8b.Caught); - SetGenderFlag(species, pkm.Gender, shiny); - SetLanguageFlag(species, pkm.Language, true); - SetHasFormFlag(species, pkm.Form, shiny, true); - if (species is (int)Species.Spinda) - ((SAV8BS)SAV).ZukanExtra.SetDex(originalState, pkm.EncryptionConstant, pkm.Gender, shiny); - } - - private void SetGenderFlag(int species, int gender, bool shiny) - { - switch (gender) - { - case 0: SetGenderFlagMale(species, shiny); break; - case 1: SetGenderFlagFemale(species, shiny); break; - case 2: // Yep, sets both gender flags. - SetGenderFlagMale(species, shiny); - SetGenderFlagFemale(species, shiny); - break; - } - } - - private void SetGenderFlagMale(int species, bool shiny) => SetBoolean(species - 1, shiny ? OFS_MALESHINY : OFS_MALE, COUNT_SPECIES - 1, true); - private void SetGenderFlagFemale(int species, bool shiny) => SetBoolean(species - 1, shiny ? OFS_FEMALESHINY : OFS_FEMALE, COUNT_SPECIES - 1, true); - - public override void SeenNone() - { - for (int species = 1; species <= Legal.MaxSpeciesID_4; species++) - ClearDexEntryAll(species); - } - - public override void CaughtNone() - { - for (int species = 1; species <= Legal.MaxSpeciesID_4; species++) - { - if (GetCaught(species)) - SetState(species, ZukanState8b.Seen); - SetLanguageFlags(species, 0); - } - } - - public override void SeenAll(bool shinyToo = false) - { - var pt = Personal; - for (int i = 1; i <= Legal.MaxSpeciesID_4; i++) - { - if (!GetSeen(i)) - SetState(i, ZukanState8b.Seen); - var pi = pt[i]; - var m = !pi.OnlyFemale; - var f = !pi.OnlyMale; - SetGenderFlags(i, m, f, m && shinyToo, f && shinyToo); - } - } - - public override void CompleteDex(bool shinyToo = false) - { - for (int species = 1; species <= Legal.MaxSpeciesID_4; species++) - SetDexEntryAll(species, shinyToo); - } - - public override void CaughtAll(bool shinyToo = false) - { - var pt = Personal; - for (int species = 1; species <= Legal.MaxSpeciesID_4; species++) - { - SetState(species, ZukanState8b.Caught); - var pi = pt[species]; - var m = !pi.OnlyFemale; - var f = !pi.OnlyMale; - SetGenderFlags(species, m, f, m && shinyToo, f && shinyToo); - SetLanguageFlag(species, SAV.Language, true); - } - } - - public override void SetAllSeen(bool value = true, bool shinyToo = false) - { - var pt = Personal; - for (int species = 1; species <= Legal.MaxSpeciesID_4; species++) - { - if (value) - { - if (!GetSeen(species)) - SetState(species, ZukanState8b.Seen); - var pi = pt[species]; - var m = !pi.OnlyFemale; - var f = !pi.OnlyMale; - SetGenderFlags(species, m, f, m && shinyToo, f && shinyToo); - } - else - { - ClearDexEntryAll(species); - } - } - } - - public override void SetDexEntryAll(int species, bool shinyToo = false) + public override void CaughtAll(bool shinyToo = false) + { + var pt = Personal; + for (int species = 1; species <= Legal.MaxSpeciesID_4; species++) { SetState(species, ZukanState8b.Caught); - - var pt = Personal; var pi = pt[species]; var m = !pi.OnlyFemale; var f = !pi.OnlyMale; SetGenderFlags(species, m, f, m && shinyToo, f && shinyToo); - - var formCount = GetFormCount(species); - if (formCount is not 0) - { - for (int form = 0; form < formCount; form++) - { - SetHasFormFlag(species, form, false, true); - if (shinyToo) - SetHasFormFlag(species, form, true, true); - } - } - SetLanguageFlags(species, LANGUAGE_ALL); - } - - public override void ClearDexEntryAll(int species) - { - SetState(species, ZukanState8b.None); - SetGenderFlags(species, false, false, false, false); - - var formCount = GetFormCount(species); - if (formCount is not 0) - { - for (int form = 0; form < formCount; form++) - { - SetHasFormFlag(species, form, false, false); - SetHasFormFlag(species, form, true, false); - } - } - SetLanguageFlags(species, LANGUAGE_NONE); + SetLanguageFlag(species, SAV.Language, true); } } - public enum ZukanState8b + public override void SetAllSeen(bool value = true, bool shinyToo = false) { - None, - HeardOf, - Seen, - Caught, + var pt = Personal; + for (int species = 1; species <= Legal.MaxSpeciesID_4; species++) + { + if (value) + { + if (!GetSeen(species)) + SetState(species, ZukanState8b.Seen); + var pi = pt[species]; + var m = !pi.OnlyFemale; + var f = !pi.OnlyMale; + SetGenderFlags(species, m, f, m && shinyToo, f && shinyToo); + } + else + { + ClearDexEntryAll(species); + } + } + } + + public override void SetDexEntryAll(int species, bool shinyToo = false) + { + SetState(species, ZukanState8b.Caught); + + var pt = Personal; + var pi = pt[species]; + var m = !pi.OnlyFemale; + var f = !pi.OnlyMale; + SetGenderFlags(species, m, f, m && shinyToo, f && shinyToo); + + var formCount = GetFormCount(species); + if (formCount is not 0) + { + for (int form = 0; form < formCount; form++) + { + SetHasFormFlag(species, form, false, true); + if (shinyToo) + SetHasFormFlag(species, form, true, true); + } + } + SetLanguageFlags(species, LANGUAGE_ALL); + } + + public override void ClearDexEntryAll(int species) + { + SetState(species, ZukanState8b.None); + SetGenderFlags(species, false, false, false, false); + + var formCount = GetFormCount(species); + if (formCount is not 0) + { + for (int form = 0; form < formCount; form++) + { + SetHasFormFlag(species, form, false, false); + SetHasFormFlag(species, form, true, false); + } + } + SetLanguageFlags(species, LANGUAGE_NONE); } } + +public enum ZukanState8b +{ + None, + HeardOf, + Seen, + Caught, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/LA/LastSaved8a.cs b/PKHeX.Core/Saves/Substructures/Gen8/LA/LastSaved8a.cs index 3a9b6575b..27a815e51 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/LA/LastSaved8a.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/LA/LastSaved8a.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; @@ -17,10 +17,10 @@ public sealed class LastSaved8a : SaveBlock public LastSaved8a(SAV8LA sav, SCBlock block) : base(sav, block.Data) { } private uint LastSaved { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x0)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x0), value); } private int LastSavedYear { get => (int)(LastSaved & 0xFFF); set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)value; } - private int LastSavedMonth { get => (int)(LastSaved >> 12 & 0xF); set => LastSaved = (LastSaved & 0xFFFF0FFF) | ((uint)value & 0xF) << 12; } - private int LastSavedDay { get => (int)(LastSaved >> 16 & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | ((uint)value & 0x1F) << 16; } - private int LastSavedHour { get => (int)(LastSaved >> 21 & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | ((uint)value & 0x1F) << 21; } - private int LastSavedMinute { get => (int)(LastSaved >> 26 & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | ((uint)value & 0x3F) << 26; } + private int LastSavedMonth { get => (int)((LastSaved >> 12) & 0xF); set => LastSaved = (LastSaved & 0xFFFF0FFF) | (((uint)value & 0xF) << 12); } + private int LastSavedDay { get => (int)((LastSaved >> 16) & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | (((uint)value & 0x1F) << 16); } + private int LastSavedHour { get => (int)((LastSaved >> 21) & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | (((uint)value & 0x1F) << 21); } + private int LastSavedMinute { get => (int)((LastSaved >> 26) & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | (((uint)value & 0x3F) << 26); } public string LastSavedTime => $"{LastSavedYear+1900:0000}-{LastSavedMonth+1:00}-{LastSavedDay:00} {LastSavedHour:00}ː{LastSavedMinute:00}"; // not : public DateTime? LastSavedDate diff --git a/PKHeX.Core/Saves/Substructures/Gen8/LA/Pokedex/PokedexSave8a.cs b/PKHeX.Core/Saves/Substructures/Gen8/LA/Pokedex/PokedexSave8a.cs index 1b7eab9ea..7f3de2e53 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/LA/Pokedex/PokedexSave8a.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/LA/Pokedex/PokedexSave8a.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -75,7 +75,7 @@ public static int GetDexIndex(PokedexType8a which, int species) public bool IsPokedexPerfect(PokedexType8a which) => SaveData.IsPokedexPerfect(which); - public int GetDexTotalCount(PokedexType8a which) + public static int GetDexTotalCount(PokedexType8a which) { var count = 0; for (var species = 1; species <= Personal.MaxSpeciesID; species++) @@ -490,7 +490,7 @@ public int GetResearchTaskLevel(int species, int taskIndex, out int reportedLeve { ObtainForms => GetObtainedFormCounts(species), PartOfArceus => GetPartOfArceusValue(task.Hash_08), - SpeciesQuest => (GetSpeciesQuestState(task.Hash_06) == 0xFF) ? 1 : 0, + SpeciesQuest => GetSpeciesQuestState(task.Hash_06) == 0xFF ? 1 : 0, _ => speciesEntry.GetCurrentResearchLevel(task.Task, task.Index), }; @@ -907,8 +907,8 @@ public void OnPokeGet(PKM pk, bool sleeping, bool inAir, bool notSpotted, bool s public static bool IsPokeLarge(int species, float height) => TryGetTriggeredTask(species, CatchLarge, out var task) && height >= task.Threshold; public static bool IsPokeSmall(int species, float height) => TryGetTriggeredTask(species, CatchSmall, out var task) && height <= task.Threshold; - public static bool IsPokeHeavy(int species, float weight) => TryGetTriggeredTask(species, CatchHeavy, out var task) && weight >= (task.Threshold / 10.0); - public static bool IsPokeLight(int species, float weight) => TryGetTriggeredTask(species, CatchLight, out var task) && weight <= (task.Threshold / 10.0); + public static bool IsPokeHeavy(int species, float weight) => TryGetTriggeredTask(species, CatchHeavy, out var task) && weight >= task.Threshold / 10.0; + public static bool IsPokeLight(int species, float weight) => TryGetTriggeredTask(species, CatchLight, out var task) && weight <= task.Threshold / 10.0; private void OnPokeCaughtSleeping(int species) => IncrementResearchTaskProgress(species, CatchSleeping); @@ -1189,7 +1189,7 @@ public void SetSelectedGenderForm(int species, int form, bool gender1, bool shin if (SaveData.TryGetStatisticsEntry(species, form, out var statEntry)) { - var flags = (speciesEntry.NumObtained > 0) ? statEntry.ObtainFlags : statEntry.SeenInWildFlags; + var flags = speciesEntry.NumObtained > 0 ? statEntry.ObtainFlags : statEntry.SeenInWildFlags; var ofs = (shiny ? 4 : 0) + (alpha ? 2 : 0); diff --git a/PKHeX.Core/Saves/Substructures/Gen8/Meta8.cs b/PKHeX.Core/Saves/Substructures/Gen8/Meta8.cs index 885ddc7cd..4df00bae7 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/Meta8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/Meta8.cs @@ -1,401 +1,400 @@ using System.Collections.Generic; -namespace PKHeX.Core -{ - public static class Meta8 - { - public static SCBlock[] GetBlankDataSWSH() => GetBlankBlockArray(DefaultChunkSizesSWSH); - public static SCBlock[] GetBlankDataLA() => GetBlankBlockArray(DefaultChunkSizesLA); +namespace PKHeX.Core; - /// - /// Create a blank block array using the provided definition. - /// - /// Block specification tuples (key, size) - /// List of blocks - private static SCBlock[] GetBlankBlockArray(IReadOnlyList arr) +public static class Meta8 +{ + public static SCBlock[] GetBlankDataSWSH() => GetBlankBlockArray(DefaultChunkSizesSWSH); + public static SCBlock[] GetBlankDataLA() => GetBlankBlockArray(DefaultChunkSizesLA); + + /// + /// Create a blank block array using the provided definition. + /// + /// Block specification tuples (key, size) + /// List of blocks + private static SCBlock[] GetBlankBlockArray(IReadOnlyList arr) + { + var blocks = new SCBlock[arr.Count / 2]; + for (int i = 0; i < blocks.Length; i++) { - var blocks = new SCBlock[arr.Count / 2]; - for (int i = 0; i < blocks.Length; i++) - { - int index = i * 2; - var key = arr[index]; - var length = (int) arr[index + 1]; - var dummy = new byte[length]; - blocks[i] = new SCBlock(key, SCTypeCode.None, dummy); - } - return blocks; + int index = i * 2; + var key = arr[index]; + var length = (int) arr[index + 1]; + var dummy = new byte[length]; + blocks[i] = new SCBlock(key, SCTypeCode.None, dummy); } + return blocks; + } #if DEBUG - public static IEnumerable RipSizes(IReadOnlyCollection blocks) + public static IEnumerable RipSizes(IReadOnlyCollection blocks) + { + int ctr = 0; + foreach (var block in blocks) { - int ctr = 0; - foreach (var block in blocks) + if (block.Data.Length == 0) + continue; + if (ctr == 4) { - if (block.Data.Length == 0) - continue; - if (ctr == 4) - { - yield return System.Environment.NewLine; - ctr = 0; - } - yield return $"0x{block.Key:X8}, 0x{block.Data.Length:X5}, "; - ctr++; + yield return System.Environment.NewLine; + ctr = 0; } + yield return $"0x{block.Key:X8}, 0x{block.Data.Length:X5}, "; + ctr++; } + } #endif - private static readonly uint[] DefaultChunkSizesSWSH = - { - 0x00A1F55B, 0x00004, 0x0123EA7A, 0x00004, 0x017C3CBB, 0x00001, 0x0192A204, 0x00009, - 0x01A1F6EE, 0x00004, 0x01BFCAD0, 0x00002, 0x02B647B4, 0x00004, 0x02BFCC63, 0x00002, - 0x02C163FD, 0x00004, 0x02DD636A, 0x00004, 0x034B256C, 0x00004, 0x03C18D2C, 0x00002, - 0x03C550C3, 0x00004, 0x03C69A96, 0x00004, 0x044B26FF, 0x00004, 0x0490A3C3, 0x056F8, - 0x04BFCF89, 0x00002, 0x04C18EBF, 0x00002, 0x04C55256, 0x00004, 0x051A8415, 0x00004, - 0x053EF7F5, 0x00004, 0x05C553E9, 0x00004, 0x06498466, 0x00004, 0x064B2A25, 0x00004, - 0x066F38F5, 0x00004, 0x06C0FBC8, 0x00004, 0x06C191E5, 0x00002, 0x07165742, 0x00004, - 0x071A3599, 0x00004, 0x097D2359, 0x00004, 0x0A01FC6C, 0x00004, 0x0A01FE1F, 0x00004, - 0x0A0204EB, 0x00004, 0x0A02069E, 0x00004, 0x0A020851, 0x00004, 0x0A0515F5, 0x00004, - 0x0A0517A8, 0x00004, 0x0A05195B, 0x00004, 0x0A051B0E, 0x00004, 0x0A051CC1, 0x00004, - 0x0A051E74, 0x00004, 0x0A052027, 0x00004, 0x0A0521DA, 0x00004, 0x0A05238D, 0x00004, - 0x0AD1037C, 0x00004, 0x0B90EFF8, 0x00004, 0x0BFDEBA1, 0x00004, 0x0C7C3E38, 0x00004, - 0x0CBEB855, 0x00004, 0x0CF6D916, 0x00004, 0x0D0E00C9, 0x00004, 0x0D477836, 0x00004, - 0x0D591902, 0x00004, 0x0D66012C, 0x50A00, 0x0D74AA40, 0x00008, 0x0D987D50, 0x00004, - 0x0E11ED8C, 0x00004, 0x0E411743, 0x00001, 0x0E591A95, 0x00004, 0x0E615A8C, 0x023D4, - 0x0E85795E, 0x00004, 0x0EA7CF37, 0x00004, 0x0F022C05, 0x00004, 0x0F0F2271, 0x00004, - 0x0F591C28, 0x00004, 0x0F850803, 0x00001, 0x0FA7D0CA, 0x00004, 0x10347218, 0x00004, - 0x10591DBB, 0x00004, 0x10850996, 0x00001, 0x108657DC, 0x00004, 0x10A7D25D, 0x00004, - 0x112D5141, 0x017C8, 0x11591F4E, 0x00004, 0x11615F45, 0x023D4, 0x1177C2C4, 0x012F8, - 0x11850B29, 0x00001, 0x125920E1, 0x00004, 0x127D6F1B, 0x00004, 0x12C96CFC, 0x00004, - 0x13349604, 0x00001, 0x133FD4BC, 0x00002, 0x13592274, 0x00004, 0x14592407, 0x00004, - 0x148DA703, 0x00A68, 0x149A1DD0, 0x00880, 0x14C97022, 0x00004, 0x14D0124C, 0x00648, - 0x151AE138, 0x00004, 0x1534992A, 0x00001, 0x153FD7E2, 0x00002, 0x1559259A, 0x00004, - 0x158DA896, 0x00A68, 0x15C971B5, 0x00004, 0x15D013DF, 0x00648, 0x15E9A450, 0x00004, - 0x15E9A603, 0x00004, 0x15E9A7B6, 0x00004, 0x15E9AB1C, 0x00004, 0x15E9ACCF, 0x00004, - 0x15E9AE82, 0x00004, 0x15E9B035, 0x00004, 0x15E9B701, 0x00004, 0x160CDE80, 0x00004, - 0x16349ABD, 0x00001, 0x163FD975, 0x00002, 0x1659272D, 0x00004, 0x169A20F6, 0x00880, - 0x16AAA7FA, 0x06010, 0x171AE45E, 0x00004, 0x175573FB, 0x00004, 0x177786BA, 0x00004, - 0x179A2289, 0x00880, 0x17C4CBAC, 0x00004, 0x181AE5F1, 0x00004, 0x184014A4, 0x00004, - 0x189A241C, 0x00880, 0x1920C1E4, 0x00084, 0x19401637, 0x00004, 0x19722C89, 0x00440, - 0x199A25AF, 0x00880, 0x1A961810, 0x00004, 0x1B40195D, 0x00004, 0x1B4C150C, 0x00001, - 0x1B882B09, 0x0021C, 0x1B9619A3, 0x00004, 0x1BE94648, 0x00004, 0x1C5FAE08, 0x00002, - 0x1D482A63, 0x00004, 0x1D961CC9, 0x00004, 0x1DC26AF0, 0x00001, 0x1DD41D8C, 0x00004, - 0x1DE9496E, 0x00004, 0x1E5FB12E, 0x00002, 0x1EC26C83, 0x00001, 0x1EE94B01, 0x00004, - 0x1F5FB2C1, 0x00002, 0x1F637658, 0x00004, 0x206377EB, 0x00004, 0x20C26FA9, 0x00001, - 0x20DA9878, 0x00001, 0x20FA6183, 0x00132, 0x2163797E, 0x00004, 0x224A1BD2, 0x00008, - 0x22DA9B9E, 0x00001, 0x23DA9D31, 0x00001, 0x23E747F4, 0x00001, 0x24280E24, 0x00004, - 0x2454C888, 0x00001, 0x24ADE637, 0x00004, 0x24B9E3CC, 0x00004, 0x24E74987, 0x00001, - 0x24F1F93C, 0x00004, 0x24F1FAEF, 0x00004, 0x24F1FCA2, 0x00004, 0x2554CA1B, 0x00001, - 0x2575BD5B, 0x00004, 0x25A7CED1, 0x00004, 0x25E74B1A, 0x00001, 0x2654CBAE, 0x00001, - 0x26A1BEDE, 0x00004, 0x26B99C02, 0x00004, 0x26FE3299, 0x00004, 0x26FE3B18, 0x00004, - 0x26FE3CCB, 0x00004, 0x26FE3E7E, 0x00004, 0x26FE41E4, 0x00004, 0x26FE4397, 0x00004, - 0x26FE454A, 0x00004, 0x26FE46FD, 0x00004, 0x2846B7DB, 0x00004, 0x28BC6BDC, 0x00002, - 0x28E707F5, 0x21FC0, 0x28F03D03, 0x00004, 0x29036436, 0x00004, 0x2985FE5D, 0x00814, - 0x29A93E10, 0x00004, 0x29BC6D6F, 0x00002, 0x29E026C7, 0x00001, 0x2A7F88A8, 0x00001, - 0x2B07632B, 0x00004, 0x2B103B06, 0x00004, 0x2B232D98, 0x00001, 0x2BBC7095, 0x00002, - 0x2C232F2B, 0x00001, 0x2C7F8BCE, 0x00001, 0x2D0520CD, 0x00004, 0x2D2330BE, 0x00001, - 0x2D6FBA6A, 0x00570, 0x2D7F8D61, 0x00001, 0x2DFA8C4E, 0x00004, 0x2E9F837C, 0x00001, - 0x2EB1B190, 0x00020, 0x2F773C75, 0x00004, 0x2F9F850F, 0x00001, 0x2FC2FD50, 0x00004, - 0x30080BB6, 0x00004, 0x30396510, 0x00002, 0x30671AD9, 0x00008, 0x30C2FEE3, 0x00004, - 0x313966A3, 0x00002, 0x319F8835, 0x00001, 0x31A13425, 0x00004, 0x31C30076, 0x00004, - 0x32396836, 0x00002, 0x32C4F443, 0x00004, 0x32EF5030, 0x00001, 0x33068788, 0x00004, - 0x331EF563, 0x00004, 0x331EF716, 0x00004, 0x335AA3F7, 0x00001, 0x33EF51C3, 0x00001, - 0x33F39467, 0x00048, 0x343E6FC1, 0x00004, 0x345AA58A, 0x00001, 0x3468783B, 0x00004, - 0x34E60D88, 0x00004, 0x34EF5356, 0x00001, 0x355AA71D, 0x00001, 0x355C8314, 0x00004, - 0x356879CE, 0x00004, 0x3576EE34, 0x00004, 0x363D064C, 0x00004, 0x36687B61, 0x00004, - 0x3677602D, 0x00004, 0x36E2F1C8, 0x00004, 0x374090B6, 0x00004, 0x375A5D09, 0x00004, - 0x37A13A2A, 0x00004, 0x37DA95A3, 0x000C8, 0x37E17D13, 0x00004, 0x38548020, 0x00004, - 0x39227A79, 0x00D50, 0x3934BEC0, 0x00004, 0x39B43CEF, 0x00004, 0x3A313F00, 0x00004, - 0x3A3140B3, 0x00004, 0x3A314266, 0x00004, 0x3A314419, 0x00004, 0x3A31477F, 0x00004, - 0x3A314932, 0x00004, 0x3A314AE5, 0x00004, 0x3A315364, 0x00004, 0x3A315517, 0x00004, - 0x3A345DA2, 0x00004, 0x3A345F55, 0x00004, 0x3A377578, 0x00004, 0x3A37772B, 0x00004, - 0x3A377A91, 0x00004, 0x3A377FAA, 0x00004, 0x3A3A90B4, 0x00004, 0x3A68EA36, 0x00004, - 0x3A6B2A3F, 0x00004, 0x3A6F5768, 0x00004, 0x3B23B1E2, 0x00004, 0x3BFF8084, 0x00002, - 0x3C6F5A8E, 0x00004, 0x3C7119C4, 0x00004, 0x3C9366F0, 0x02760, 0x3CF2E964, 0x00001, - 0x3CFF8217, 0x00002, 0x3D6F5C21, 0x00004, 0x3DBE736D, 0x00004, 0x3DF2EAF7, 0x00001, - 0x3DFF83AA, 0x00002, 0x3E711CEA, 0x00004, 0x3E8CFA15, 0x033D0, 0x3F64E8A0, 0x00004, - 0x3F680229, 0x00004, 0x3F6A4232, 0x00004, 0x3F6D5BBB, 0x00004, 0x3F711E7D, 0x00004, - 0x3F8120BA, 0x00002, 0x3F936BA9, 0x02790, 0x3FE99FE1, 0x00004, 0x3FF17E2F, 0x00004, - 0x3FF2EE1D, 0x00001, 0x4033C7DB, 0x00001, 0x4034BE27, 0x00004, 0x40B20AF8, 0x00004, - 0x40CC5A21, 0x00002, 0x40E9A268, 0x00001, 0x41309084, 0x00001, 0x4192B91C, 0x00004, - 0x41DAB84F, 0x00004, 0x41E9A3FB, 0x00001, 0x41F566B2, 0x00004, 0x4292BAAF, 0x00004, - 0x435C4F14, 0x00002, 0x436CAF2B, 0x00004, 0x437E8C7C, 0x00004, 0x439116A4, 0x00001, - 0x43D3E2C3, 0x00004, 0x43E9A721, 0x00001, 0x443FB19E, 0x00038, 0x4492BDD5, 0x00004, - 0x45462EBF, 0x00004, 0x455C523A, 0x00002, 0x4584784A, 0x00004, 0x465C53CD, 0x00002, - 0x46ACC334, 0x00028, 0x46BC63C5, 0x00004, 0x46D1FDC7, 0x00004, 0x4716C404, 0x04B00, - 0x47D73984, 0x00001, 0x48043955, 0x00004, 0x495E3674, 0x00004, 0x49D73CAA, 0x00001, - 0x4A0F21AD, 0x00004, 0x4A3BED80, 0x00004, 0x4A69631D, 0x00004, 0x4A741A7A, 0x00004, - 0x4AD73E3D, 0x00001, 0x4ADD5E5C, 0x00001, 0x4AEA5A7E, 0x00004, 0x4B8B80F6, 0x00004, - 0x4BC891F4, 0x00004, 0x4BD31BDB, 0x00004, 0x4C3BF0A6, 0x00004, 0x4C54B0CF, 0x00004, - 0x4C67BA2E, 0x00004, 0x4CDFE1B5, 0x00004, 0x4D3BF239, 0x00004, 0x4D50B655, 0x00004, - 0x4D52705C, 0x00001, 0x4D8DD022, 0x00004, 0x4DBB9B79, 0x00004, 0x4E5271EF, 0x00001, - 0x4EF25E14, 0x00004, 0x4FD773E2, 0x00004, 0x4FF25FA7, 0x00004, 0x50359D63, 0x00004, - 0x50527515, 0x00001, 0x50925E91, 0x02964, 0x50F2613A, 0x00004, 0x52689B80, 0x00004, - 0x533DF099, 0x00004, 0x539DD549, 0x00004, 0x54AC4B39, 0x00004, 0x54D5CDC4, 0x00004, - 0x5586B8F0, 0x02304, 0x55D5CF57, 0x00004, 0x5686BA83, 0x02304, 0x56D5D0EA, 0x00004, - 0x5786BC16, 0x02304, 0x57F29628, 0x00004, 0x582E4A5C, 0x00004, 0x5886BDA9, 0x02304, - 0x5986BF3C, 0x02304, 0x5A86C0CF, 0x02304, 0x5B86C262, 0x02304, 0x5BA54B4B, 0x00004, - 0x5BAAACCC, 0x00004, 0x5C548FE0, 0x00001, 0x5C63C0C5, 0x00004, 0x5C86C3F5, 0x02304, - 0x5D549173, 0x00001, 0x5D6C0AED, 0x00004, 0x5D77D781, 0x00004, 0x5DCF71D9, 0x00004, - 0x5E549306, 0x00001, 0x5ECE9F76, 0x00004, 0x5F135643, 0x00004, 0x5F1D8945, 0x00004, - 0x5F74FCEE, 0x00002, 0x5FCEA109, 0x00004, 0x605EBC30, 0x00001, 0x6106657D, 0x00004, - 0x6148F6AC, 0x00810, 0x6186CBD4, 0x02304, 0x61952B51, 0x00004, 0x61BC9BB4, 0x00004, - 0x61D277C7, 0x00004, 0x61D2797A, 0x00004, 0x61D27B2D, 0x00004, 0x6226F5AD, 0x00002, - 0x624C7B30, 0x00004, 0x62743428, 0x00002, 0x6286CD67, 0x02304, 0x62A0A180, 0x00004, - 0x62C9FAAF, 0x00012, 0x62F05895, 0x00004, 0x63170940, 0x00002, 0x6371183B, 0x00004, - 0x63A0A313, 0x00004, 0x64170AD3, 0x00002, 0x64A0A4A6, 0x00004, 0x64CEA8E8, 0x00004, - 0x65170C66, 0x00002, 0x65CEAA7B, 0x00004, 0x66CEAC0E, 0x00004, 0x6701AF09, 0x00004, - 0x67A659C4, 0x00004, 0x67CEADA1, 0x00004, 0x67F25394, 0x00001, 0x680EEB85, 0x0426C, - 0x6816DFDB, 0x00004, 0x68BBA8B1, 0x00004, 0x68CEAF34, 0x00004, 0x68DE4CD2, 0x00004, - 0x697141AC, 0x00004, 0x697D1E29, 0x00004, 0x69CEB0C7, 0x00004, 0x69F256BA, 0x00001, - 0x6A09346C, 0x00132, 0x6A262628, 0x00004, 0x6A614361, 0x00004, 0x6ACEB25A, 0x00004, - 0x6AF2584D, 0x00001, 0x6B2627BB, 0x00004, 0x6BCEB3ED, 0x00004, 0x6C12BF1F, 0x00004, - 0x6C3B5E62, 0x00004, 0x6C447903, 0x00004, 0x6C99F9A0, 0x00002, 0x6CD15C44, 0x00004, - 0x6D12C0B2, 0x00004, 0x6D262AE1, 0x00004, 0x6D601EDD, 0x00004, 0x6DF7A8FB, 0x00004, - 0x6E081D6F, 0x00004, 0x6E12C245, 0x00004, 0x6E972CFB, 0x00002, 0x6EB72940, 0x067D0, - 0x6EEEC240, 0x00001, 0x6F411E56, 0x00004, 0x6F66951C, 0x00004, 0x6F6696CF, 0x00004, - 0x6F669A35, 0x00004, 0x6F884079, 0x00004, 0x6F972E8E, 0x00002, 0x70973021, 0x00002, - 0x70EEC566, 0x00001, 0x71115B78, 0x00002, 0x71599CC8, 0x00004, 0x7169F53F, 0x00004, - 0x71825204, 0x00001, 0x718621A5, 0x00132, 0x71EEC6F9, 0x00001, 0x72115D0B, 0x00002, - 0x721AE36F, 0x00004, 0x736639B3, 0x00004, 0x737D1964, 0x00004, 0x74116031, 0x00002, - 0x74441C98, 0x00001, 0x748543C7, 0x0000B, 0x758C45E8, 0x00001, 0x759FF274, 0x00002, - 0x76441FBE, 0x00001, 0x765468C3, 0x00004, 0x768C477B, 0x00001, 0x76921EEB, 0x00001, - 0x7701D95E, 0x04610, 0x771E4C88, 0x00004, 0x77275404, 0x00004, 0x7742B542, 0x00004, - 0x77442151, 0x00001, 0x77BD26F2, 0x00004, 0x780BF029, 0x00004, 0x783D2039, 0x00004, - 0x783EEFDC, 0x00004, 0x7875451A, 0x01F50, 0x788C4AA1, 0x00001, 0x789FF72D, 0x00002, - 0x78D78638, 0x00040, 0x79151C13, 0x00004, 0x7964A5A9, 0x00004, 0x7999DAFD, 0x00004, - 0x79C56A5C, 0x00004, 0x79D787CB, 0x00040, 0x7A7A3480, 0x00004, 0x7A9B68E2, 0x00004, - 0x7A9EF7D9, 0x00004, 0x7AADA3E3, 0x00004, 0x7AD7895E, 0x00040, 0x7AF09C40, 0x00002, - 0x7B022344, 0x00004, 0x7B7A3613, 0x00004, 0x7BD78AF1, 0x00040, 0x7BE8A4C6, 0x00020, - 0x7BF09DD3, 0x00002, 0x7C86783F, 0x00008, 0x7CB6E900, 0x00004, 0x7D249649, 0x00004, - 0x7D6EEAB4, 0x00001, 0x7D7A3939, 0x00004, 0x7DF0A0F9, 0x00002, 0x7E6EEC47, 0x00001, - 0x7E7E36B9, 0x00004, 0x7EF9C424, 0x00004, 0x7F4B4B10, 0x00004, 0x7FD700DC, 0x00001, - 0x806EEF6D, 0x00001, 0x81961C6F, 0x00004, 0x81AD2AF6, 0x00004, 0x81D70402, 0x00001, - 0x82D70595, 0x00001, 0x83978C43, 0x00004, 0x84B301C2, 0x00004, 0x84B4A11A, 0x00004, - 0x84FD4F59, 0x00132, 0x85FDF16A, 0x00004, 0x865605E6, 0x00004, 0x86F9272C, 0x00004, - 0x874DA6FA, 0x001D0, 0x87560779, 0x00004, 0x876D7A3B, 0x00004, 0x87F611F8, 0x00004, - 0x87F613AB, 0x00004, 0x87F61711, 0x00004, 0x87F92D34, 0x00004, 0x886D7BCE, 0x00004, - 0x88F6D6AE, 0x033D0, 0x8902E727, 0x00004, 0x89048303, 0x033D0, 0x892112D4, 0x00002, - 0x896D7D61, 0x00004, 0x89A3A2B6, 0x00004, 0x8A211467, 0x00002, 0x8A468BEC, 0x00004, - 0x8A4C1426, 0x00004, 0x8A4C178C, 0x00004, 0x8A4C193F, 0x00004, 0x8A4C1CA5, 0x00004, - 0x8AD859BF, 0x00004, 0x8B2115FA, 0x00002, 0x8B4600C9, 0x00004, 0x8B468D7F, 0x00004, - 0x8B93CB0E, 0x00004, 0x8C096F9E, 0x00004, 0x8C468F12, 0x00004, 0x8C560F58, 0x00004, - 0x8C696110, 0x00004, 0x8C804D7B, 0x00004, 0x8CA88020, 0x00038, 0x8CBBFD90, 0x00008, - 0x8CD9973A, 0x00004, 0x8D321AB8, 0x000D0, 0x8D5610EB, 0x00004, 0x8DBF8019, 0x00004, - 0x8E1D74E8, 0x00004, 0x8E56127E, 0x00004, 0x8EAEB8FF, 0x00004, 0x8EF4E556, 0x00009, - 0x8F3C763E, 0x00004, 0x8F561411, 0x00004, 0x9033EB7B, 0x00A68, 0x905615A4, 0x00004, - 0x91561737, 0x00004, 0x91AC63B3, 0x00004, 0x91E09E4C, 0x00001, 0x91E89ACF, 0x00004, - 0x925618CA, 0x00004, 0x92B2F3F3, 0x00004, 0x92E09FDF, 0x00001, 0x92EB0306, 0x00004, - 0x92F4F7F0, 0x00055, 0x93490AAF, 0x00004, 0x93561A5D, 0x00004, 0x93868FC6, 0x00004, - 0x93D4B32C, 0x00004, 0x93D4B845, 0x00004, 0x944481DD, 0x00132, 0x9497A849, 0x00004, - 0x94C400E0, 0x00004, 0x94C40293, 0x00004, 0x94C40446, 0x00004, 0x94E0A305, 0x00001, - 0x95A75FF8, 0x00001, 0x95F4FCA9, 0x00019, 0x96A7618B, 0x00001, 0x96D3F39F, 0x00002, - 0x9751BABE, 0x00400, 0x97D712D3, 0x00004, 0x982972C2, 0x00004, 0x982FCB42, 0x00004, - 0x98A764B1, 0x00001, 0x98B0956F, 0x00001, 0x996BEEAA, 0x00004, 0x99B09702, 0x00001, - 0x99B9F60B, 0x00004, 0x99F197E9, 0x00004, 0x9A39C8FC, 0x00004, 0x9A535FA0, 0x00004, - 0x9AB09895, 0x00001, 0x9AC98C4C, 0x00004, 0x9ACC0AEC, 0x00004, 0x9ACC0E52, 0x00004, - 0x9ACC1005, 0x00004, 0x9C781AE2, 0x00004, 0x9CC98F72, 0x00004, 0x9CE5CDB5, 0x00132, - 0x9CF58395, 0x00001, 0x9D6727F6, 0x00004, 0x9DC99105, 0x00004, 0x9E32AE34, 0x00004, - 0x9EC079DA, 0x00002, 0x9EED59DE, 0x00004, 0x9F730ECF, 0x00002, 0xA0731062, 0x00002, - 0xA0F49CFB, 0x00004, 0xA17311F5, 0x00002, 0xA1D06D0C, 0x00004, 0xA1F76014, 0x00004, - 0xA202729F, 0x00004, 0xA2954E3A, 0x00004, 0xA2C094C7, 0x00004, 0xA2D06E9F, 0x00004, - 0xA2F761A7, 0x00004, 0xA3419FC7, 0x00019, 0xA3F7633A, 0x00004, 0xA4042B23, 0x00004, - 0xA448D9F0, 0x00004, 0xA4B74E00, 0x00004, 0xA4D071C5, 0x00004, 0xA4D5B8C0, 0x00004, - 0xA4E3B2CF, 0x00004, 0xA5D0453A, 0x00004, 0xA7301FBD, 0x00004, 0xA7B8C97B, 0x00004, - 0xA8067616, 0x00004, 0xA83021C1, 0x00268, 0xA8533433, 0x00004, 0xA8EB523D, 0x00004, - 0xA9B04C50, 0x00004, 0xAA86248B, 0x00004, 0xAB07B11E, 0x00004, 0xAD2750BE, 0x00280, - 0xAD3920F5, 0x1241C, 0xAD4E4275, 0x00004, 0xAD67A297, 0x00004, 0xAD6F9196, 0x00004, - 0xAD9DFA6A, 0x023D4, 0xAE1283B0, 0x00004, 0xAE3497CF, 0x00004, 0xAE474936, 0x00004, - 0xAEF960AC, 0x00001, 0xAF0E8346, 0x00004, 0xAF37666F, 0x00004, 0xAF376822, 0x00004, - 0xAF3769D5, 0x00004, 0xAFF9623F, 0x00001, 0xB027F396, 0x00004, 0xB046236C, 0x00004, - 0xB0CD214B, 0x00004, 0xB0F963D2, 0x00001, 0xB125CDDC, 0x00004, 0xB14624FF, 0x00004, - 0xB189D2B8, 0x00004, 0xB189D46B, 0x00004, 0xB189D7D1, 0x00004, 0xB19386DE, 0x00004, - 0xB1C26FB0, 0x000F8, 0xB1C7C436, 0x00004, 0xB1DDDCA8, 0x00028, 0xB2137AFE, 0x00004, - 0xB25C772B, 0x50C20, 0xB3462825, 0x00004, 0xB3FF0924, 0x00001, 0xB42F0EDC, 0x00004, - 0xB47E1776, 0x0001E, 0xB4FF0AB7, 0x00001, 0xB58354DB, 0x00004, 0xB5C02173, 0x00004, - 0xB5FF0C4A, 0x00001, 0xB6C02306, 0x00004, 0xB6C76A95, 0x00004, 0xB7C02499, 0x00004, - 0xB8495C0F, 0x00004, 0xB868EE77, 0x00040, 0xB9495DA2, 0x00004, 0xB95B1BB9, 0x00004, - 0xB968F00A, 0x00010, 0xB98F962B, 0x00004, 0xB9C0ECFC, 0x00004, 0xB9E1DE0C, 0x00004, - 0xBA29D287, 0x00001, 0xBA495F35, 0x00004, 0xBB1DE8EF, 0x00004, 0xBB29D41A, 0x00001, - 0xBB7B57FB, 0x00004, 0xBC29D5AD, 0x00001, 0xBC4ADA83, 0x00004, 0xBE3BD183, 0x00004, - 0xBF17C11F, 0x00004, 0xBF8E28DF, 0x00002, 0xC017C2B2, 0x00004, 0xC0362EDD, 0x00004, - 0xC08E2A72, 0x00002, 0xC0D49E2D, 0x00004, 0xC0DE5C5F, 0x00408, 0xC10889B1, 0x00004, - 0xC117C445, 0x00004, 0xC1264133, 0x00001, 0xC18E2C05, 0x00002, 0xC1BA2375, 0x00004, - 0xC22642C6, 0x00001, 0xC23AF741, 0x00004, 0xC2EFEC6A, 0x00004, 0xC3264459, 0x00001, - 0xC35F6291, 0x00004, 0xC37F267B, 0x00158, 0xC3FB9E77, 0x000A0, 0xC4F35C2E, 0x00004, - 0xC5FC19DC, 0x00002, 0xC6102A59, 0x00004, 0xC69945F9, 0x00004, 0xC7161487, 0x00004, - 0xC7652F1C, 0x00004, 0xC7911610, 0x00004, 0xC87219F6, 0x00132, 0xC89117A3, 0x00004, - 0xC8E44448, 0x00004, 0xC94D1C48, 0x00004, 0xC9541EB3, 0x00002, 0xC96FEA27, 0x00004, - 0xC9A133BC, 0x00004, 0xC9A138D5, 0x00004, 0xC9E6E098, 0x00004, 0xCA0279C5, 0x00004, - 0xCA5FA1A3, 0x00004, 0xCA8A8CEE, 0x00004, 0xCA911AC9, 0x00004, 0xCAE4476E, 0x00004, - 0xCB135C68, 0x00004, 0xCBE44901, 0x00004, 0xCC45DA54, 0x00004, 0xCCC153CD, 0x00004, - 0xCE07D358, 0x0001C, 0xCE4AC6FA, 0x00001, 0xCE76E539, 0x00004, 0xCEEB37FF, 0x00001, - 0xCF5BC550, 0x00004, 0xCF90B39A, 0x00004, 0xCFEB3992, 0x00001, 0xD033E667, 0x00004, - 0xD05BC6E3, 0x00004, 0xD08391B9, 0x00004, 0xD0EB3B25, 0x00001, 0xD105027A, 0x00132, - 0xD14C95F9, 0x00004, 0xD15BC876, 0x00004, 0xD224F9AC, 0x00F1C, 0xD25BCA09, 0x00004, - 0xD25E08A0, 0x00004, 0xD2FC64C7, 0x00004, 0xD35BCB9C, 0x00004, 0xD35E0A33, 0x00004, - 0xD3ED94AF, 0x00004, 0xD45BCD2F, 0x00004, 0xD4F8410C, 0x00002, 0xD55BCEC2, 0x00004, - 0xD65BD055, 0x00004, 0xD6F84432, 0x00002, 0xD7F845C5, 0x00002, 0xD81C8A46, 0x00004, - 0xDA5E1538, 0x00004, 0xDB5E16CB, 0x00004, 0xDBE374D7, 0x00004, 0xDBEF0145, 0x00004, - 0xDC5E185E, 0x00004, 0xDC82C7C9, 0x00004, 0xDCBDFB50, 0x00004, 0xDD12C9A5, 0x00008, - 0xDD5E19F1, 0x00004, 0xDDBDFCE3, 0x00004, 0xDE5E1B84, 0x00004, 0xDEBDFE76, 0x00004, - 0xDF5E1D17, 0x00004, 0xDFAAEECD, 0x00004, 0xDFC8508B, 0x00001, 0xE048C668, 0x00008, - 0xE05E1EAA, 0x00004, 0xE07F44FC, 0x02304, 0xE0C8521E, 0x00001, 0xE15E203D, 0x00004, - 0xE17F468F, 0x02304, 0xE1C853B1, 0x00001, 0xE2003E60, 0x00280, 0xE250A142, 0x00004, - 0xE2798DDE, 0x00001, 0xE2950DA8, 0x00001, 0xE2ED6B1D, 0x00320, 0xE2F6E456, 0x00008, - 0xE3181BAC, 0x00001, 0xE3ABBA00, 0x00004, 0xE3ED8F16, 0x00004, 0xE40A0EF4, 0x00004, - 0xE479EE37, 0x00810, 0xE47F4B48, 0x02304, 0xE5004319, 0x00280, 0xE5181ED2, 0x00001, - 0xE579EFCA, 0x00810, 0xE57F4CDB, 0x02304, 0xE6182065, 0x00001, 0xE64A93E0, 0x00001, - 0xE67F4E6E, 0x02304, 0xE74A9573, 0x00001, 0xE75196AB, 0x00004, 0xE77F5001, 0x02304, - 0xE7C7FE6B, 0x00002, 0xE851983E, 0x00004, 0xE85BEA57, 0x00004, 0xE87F5194, 0x02304, - 0xE8C7FFFE, 0x00002, 0xE9131991, 0x00004, 0xE94A9899, 0x00001, 0xE95199D1, 0x00004, - 0xE97F5327, 0x02304, 0xE9BE28BF, 0x000F0, 0xE9C80191, 0x00002, 0xEA40157B, 0x00014, - 0xEA7F54BA, 0x02304, 0xEB07C276, 0x00004, 0xEB7F564D, 0x02304, 0xECF1AE60, 0x02304, - 0xEDCE8E4C, 0x00004, 0xEDF1AFF3, 0x02304, 0xEE7B2ABD, 0x00001, 0xEE7D64D5, 0x00538, - 0xEE841484, 0x00004, 0xEEE5A3F8, 0x033D0, 0xEEF1B186, 0x02304, 0xEFCAE04E, 0x116C4, - 0xEFCE9172, 0x00004, 0xEFDC1AB1, 0x00004, 0xEFF1B319, 0x02304, 0xF0159FB2, 0x00004, - 0xF0576532, 0x00002, 0xF06213E4, 0x00004, 0xF0CE9305, 0x00004, 0xF0F1B4AC, 0x02304, - 0xF19F7785, 0x06000, 0xF1C4948B, 0x00004, 0xF1F1B63F, 0x02304, 0xF239E881, 0x02955, - 0xF25C070E, 0x00110, 0xF26B9151, 0x00004, 0xF273F5B7, 0x00004, 0xF2F1B7D2, 0x02304, - 0xF36C4BC3, 0x00004, 0xF3F1B965, 0x02304, 0xF5A72492, 0x00004, 0xF5BACCEC, 0x00004, - 0xF64719D9, 0x00004, 0xF6C672E9, 0x00004, 0xF6FB2550, 0x00001, 0xF73CDD1F, 0x00004, - 0xF75FC566, 0x00004, 0xF75FC719, 0x00004, 0xF7DFDD95, 0x00004, 0xF8154AC9, 0x00004, - 0xF8A1B32F, 0x00004, 0xF8E07F9B, 0x00004, 0xF8FB2876, 0x00001, 0xF9FB2A09, 0x00001, - 0xFA43BB59, 0x00004, 0xFAA7378A, 0x00004, 0xFAF1C46A, 0x02304, 0xFB2AE32B, 0x03208, - 0xFB5133BC, 0x00004, 0xFBF1C5FD, 0x02304, 0xFBFF61F2, 0x00004, 0xFC1AF2FC, 0x00001, - 0xFC4493F8, 0x00002, 0xFCB63E1B, 0x00001, 0xFD1AF48F, 0x00001, 0xFDB63FAE, 0x00001, - 0xFE1AF622, 0x00001, 0xFE2E9869, 0x00004, 0xFE44971E, 0x00002, 0xFE4D59C7, 0x00004, - 0xFEB13D33, 0x00002, 0xFEB64141, 0x00001, 0xFEE68CE1, 0x00004, 0xFF4498B1, 0x00002, - 0xFFA1F3C8, 0x00004, - }; + private static readonly uint[] DefaultChunkSizesSWSH = + { + 0x00A1F55B, 0x00004, 0x0123EA7A, 0x00004, 0x017C3CBB, 0x00001, 0x0192A204, 0x00009, + 0x01A1F6EE, 0x00004, 0x01BFCAD0, 0x00002, 0x02B647B4, 0x00004, 0x02BFCC63, 0x00002, + 0x02C163FD, 0x00004, 0x02DD636A, 0x00004, 0x034B256C, 0x00004, 0x03C18D2C, 0x00002, + 0x03C550C3, 0x00004, 0x03C69A96, 0x00004, 0x044B26FF, 0x00004, 0x0490A3C3, 0x056F8, + 0x04BFCF89, 0x00002, 0x04C18EBF, 0x00002, 0x04C55256, 0x00004, 0x051A8415, 0x00004, + 0x053EF7F5, 0x00004, 0x05C553E9, 0x00004, 0x06498466, 0x00004, 0x064B2A25, 0x00004, + 0x066F38F5, 0x00004, 0x06C0FBC8, 0x00004, 0x06C191E5, 0x00002, 0x07165742, 0x00004, + 0x071A3599, 0x00004, 0x097D2359, 0x00004, 0x0A01FC6C, 0x00004, 0x0A01FE1F, 0x00004, + 0x0A0204EB, 0x00004, 0x0A02069E, 0x00004, 0x0A020851, 0x00004, 0x0A0515F5, 0x00004, + 0x0A0517A8, 0x00004, 0x0A05195B, 0x00004, 0x0A051B0E, 0x00004, 0x0A051CC1, 0x00004, + 0x0A051E74, 0x00004, 0x0A052027, 0x00004, 0x0A0521DA, 0x00004, 0x0A05238D, 0x00004, + 0x0AD1037C, 0x00004, 0x0B90EFF8, 0x00004, 0x0BFDEBA1, 0x00004, 0x0C7C3E38, 0x00004, + 0x0CBEB855, 0x00004, 0x0CF6D916, 0x00004, 0x0D0E00C9, 0x00004, 0x0D477836, 0x00004, + 0x0D591902, 0x00004, 0x0D66012C, 0x50A00, 0x0D74AA40, 0x00008, 0x0D987D50, 0x00004, + 0x0E11ED8C, 0x00004, 0x0E411743, 0x00001, 0x0E591A95, 0x00004, 0x0E615A8C, 0x023D4, + 0x0E85795E, 0x00004, 0x0EA7CF37, 0x00004, 0x0F022C05, 0x00004, 0x0F0F2271, 0x00004, + 0x0F591C28, 0x00004, 0x0F850803, 0x00001, 0x0FA7D0CA, 0x00004, 0x10347218, 0x00004, + 0x10591DBB, 0x00004, 0x10850996, 0x00001, 0x108657DC, 0x00004, 0x10A7D25D, 0x00004, + 0x112D5141, 0x017C8, 0x11591F4E, 0x00004, 0x11615F45, 0x023D4, 0x1177C2C4, 0x012F8, + 0x11850B29, 0x00001, 0x125920E1, 0x00004, 0x127D6F1B, 0x00004, 0x12C96CFC, 0x00004, + 0x13349604, 0x00001, 0x133FD4BC, 0x00002, 0x13592274, 0x00004, 0x14592407, 0x00004, + 0x148DA703, 0x00A68, 0x149A1DD0, 0x00880, 0x14C97022, 0x00004, 0x14D0124C, 0x00648, + 0x151AE138, 0x00004, 0x1534992A, 0x00001, 0x153FD7E2, 0x00002, 0x1559259A, 0x00004, + 0x158DA896, 0x00A68, 0x15C971B5, 0x00004, 0x15D013DF, 0x00648, 0x15E9A450, 0x00004, + 0x15E9A603, 0x00004, 0x15E9A7B6, 0x00004, 0x15E9AB1C, 0x00004, 0x15E9ACCF, 0x00004, + 0x15E9AE82, 0x00004, 0x15E9B035, 0x00004, 0x15E9B701, 0x00004, 0x160CDE80, 0x00004, + 0x16349ABD, 0x00001, 0x163FD975, 0x00002, 0x1659272D, 0x00004, 0x169A20F6, 0x00880, + 0x16AAA7FA, 0x06010, 0x171AE45E, 0x00004, 0x175573FB, 0x00004, 0x177786BA, 0x00004, + 0x179A2289, 0x00880, 0x17C4CBAC, 0x00004, 0x181AE5F1, 0x00004, 0x184014A4, 0x00004, + 0x189A241C, 0x00880, 0x1920C1E4, 0x00084, 0x19401637, 0x00004, 0x19722C89, 0x00440, + 0x199A25AF, 0x00880, 0x1A961810, 0x00004, 0x1B40195D, 0x00004, 0x1B4C150C, 0x00001, + 0x1B882B09, 0x0021C, 0x1B9619A3, 0x00004, 0x1BE94648, 0x00004, 0x1C5FAE08, 0x00002, + 0x1D482A63, 0x00004, 0x1D961CC9, 0x00004, 0x1DC26AF0, 0x00001, 0x1DD41D8C, 0x00004, + 0x1DE9496E, 0x00004, 0x1E5FB12E, 0x00002, 0x1EC26C83, 0x00001, 0x1EE94B01, 0x00004, + 0x1F5FB2C1, 0x00002, 0x1F637658, 0x00004, 0x206377EB, 0x00004, 0x20C26FA9, 0x00001, + 0x20DA9878, 0x00001, 0x20FA6183, 0x00132, 0x2163797E, 0x00004, 0x224A1BD2, 0x00008, + 0x22DA9B9E, 0x00001, 0x23DA9D31, 0x00001, 0x23E747F4, 0x00001, 0x24280E24, 0x00004, + 0x2454C888, 0x00001, 0x24ADE637, 0x00004, 0x24B9E3CC, 0x00004, 0x24E74987, 0x00001, + 0x24F1F93C, 0x00004, 0x24F1FAEF, 0x00004, 0x24F1FCA2, 0x00004, 0x2554CA1B, 0x00001, + 0x2575BD5B, 0x00004, 0x25A7CED1, 0x00004, 0x25E74B1A, 0x00001, 0x2654CBAE, 0x00001, + 0x26A1BEDE, 0x00004, 0x26B99C02, 0x00004, 0x26FE3299, 0x00004, 0x26FE3B18, 0x00004, + 0x26FE3CCB, 0x00004, 0x26FE3E7E, 0x00004, 0x26FE41E4, 0x00004, 0x26FE4397, 0x00004, + 0x26FE454A, 0x00004, 0x26FE46FD, 0x00004, 0x2846B7DB, 0x00004, 0x28BC6BDC, 0x00002, + 0x28E707F5, 0x21FC0, 0x28F03D03, 0x00004, 0x29036436, 0x00004, 0x2985FE5D, 0x00814, + 0x29A93E10, 0x00004, 0x29BC6D6F, 0x00002, 0x29E026C7, 0x00001, 0x2A7F88A8, 0x00001, + 0x2B07632B, 0x00004, 0x2B103B06, 0x00004, 0x2B232D98, 0x00001, 0x2BBC7095, 0x00002, + 0x2C232F2B, 0x00001, 0x2C7F8BCE, 0x00001, 0x2D0520CD, 0x00004, 0x2D2330BE, 0x00001, + 0x2D6FBA6A, 0x00570, 0x2D7F8D61, 0x00001, 0x2DFA8C4E, 0x00004, 0x2E9F837C, 0x00001, + 0x2EB1B190, 0x00020, 0x2F773C75, 0x00004, 0x2F9F850F, 0x00001, 0x2FC2FD50, 0x00004, + 0x30080BB6, 0x00004, 0x30396510, 0x00002, 0x30671AD9, 0x00008, 0x30C2FEE3, 0x00004, + 0x313966A3, 0x00002, 0x319F8835, 0x00001, 0x31A13425, 0x00004, 0x31C30076, 0x00004, + 0x32396836, 0x00002, 0x32C4F443, 0x00004, 0x32EF5030, 0x00001, 0x33068788, 0x00004, + 0x331EF563, 0x00004, 0x331EF716, 0x00004, 0x335AA3F7, 0x00001, 0x33EF51C3, 0x00001, + 0x33F39467, 0x00048, 0x343E6FC1, 0x00004, 0x345AA58A, 0x00001, 0x3468783B, 0x00004, + 0x34E60D88, 0x00004, 0x34EF5356, 0x00001, 0x355AA71D, 0x00001, 0x355C8314, 0x00004, + 0x356879CE, 0x00004, 0x3576EE34, 0x00004, 0x363D064C, 0x00004, 0x36687B61, 0x00004, + 0x3677602D, 0x00004, 0x36E2F1C8, 0x00004, 0x374090B6, 0x00004, 0x375A5D09, 0x00004, + 0x37A13A2A, 0x00004, 0x37DA95A3, 0x000C8, 0x37E17D13, 0x00004, 0x38548020, 0x00004, + 0x39227A79, 0x00D50, 0x3934BEC0, 0x00004, 0x39B43CEF, 0x00004, 0x3A313F00, 0x00004, + 0x3A3140B3, 0x00004, 0x3A314266, 0x00004, 0x3A314419, 0x00004, 0x3A31477F, 0x00004, + 0x3A314932, 0x00004, 0x3A314AE5, 0x00004, 0x3A315364, 0x00004, 0x3A315517, 0x00004, + 0x3A345DA2, 0x00004, 0x3A345F55, 0x00004, 0x3A377578, 0x00004, 0x3A37772B, 0x00004, + 0x3A377A91, 0x00004, 0x3A377FAA, 0x00004, 0x3A3A90B4, 0x00004, 0x3A68EA36, 0x00004, + 0x3A6B2A3F, 0x00004, 0x3A6F5768, 0x00004, 0x3B23B1E2, 0x00004, 0x3BFF8084, 0x00002, + 0x3C6F5A8E, 0x00004, 0x3C7119C4, 0x00004, 0x3C9366F0, 0x02760, 0x3CF2E964, 0x00001, + 0x3CFF8217, 0x00002, 0x3D6F5C21, 0x00004, 0x3DBE736D, 0x00004, 0x3DF2EAF7, 0x00001, + 0x3DFF83AA, 0x00002, 0x3E711CEA, 0x00004, 0x3E8CFA15, 0x033D0, 0x3F64E8A0, 0x00004, + 0x3F680229, 0x00004, 0x3F6A4232, 0x00004, 0x3F6D5BBB, 0x00004, 0x3F711E7D, 0x00004, + 0x3F8120BA, 0x00002, 0x3F936BA9, 0x02790, 0x3FE99FE1, 0x00004, 0x3FF17E2F, 0x00004, + 0x3FF2EE1D, 0x00001, 0x4033C7DB, 0x00001, 0x4034BE27, 0x00004, 0x40B20AF8, 0x00004, + 0x40CC5A21, 0x00002, 0x40E9A268, 0x00001, 0x41309084, 0x00001, 0x4192B91C, 0x00004, + 0x41DAB84F, 0x00004, 0x41E9A3FB, 0x00001, 0x41F566B2, 0x00004, 0x4292BAAF, 0x00004, + 0x435C4F14, 0x00002, 0x436CAF2B, 0x00004, 0x437E8C7C, 0x00004, 0x439116A4, 0x00001, + 0x43D3E2C3, 0x00004, 0x43E9A721, 0x00001, 0x443FB19E, 0x00038, 0x4492BDD5, 0x00004, + 0x45462EBF, 0x00004, 0x455C523A, 0x00002, 0x4584784A, 0x00004, 0x465C53CD, 0x00002, + 0x46ACC334, 0x00028, 0x46BC63C5, 0x00004, 0x46D1FDC7, 0x00004, 0x4716C404, 0x04B00, + 0x47D73984, 0x00001, 0x48043955, 0x00004, 0x495E3674, 0x00004, 0x49D73CAA, 0x00001, + 0x4A0F21AD, 0x00004, 0x4A3BED80, 0x00004, 0x4A69631D, 0x00004, 0x4A741A7A, 0x00004, + 0x4AD73E3D, 0x00001, 0x4ADD5E5C, 0x00001, 0x4AEA5A7E, 0x00004, 0x4B8B80F6, 0x00004, + 0x4BC891F4, 0x00004, 0x4BD31BDB, 0x00004, 0x4C3BF0A6, 0x00004, 0x4C54B0CF, 0x00004, + 0x4C67BA2E, 0x00004, 0x4CDFE1B5, 0x00004, 0x4D3BF239, 0x00004, 0x4D50B655, 0x00004, + 0x4D52705C, 0x00001, 0x4D8DD022, 0x00004, 0x4DBB9B79, 0x00004, 0x4E5271EF, 0x00001, + 0x4EF25E14, 0x00004, 0x4FD773E2, 0x00004, 0x4FF25FA7, 0x00004, 0x50359D63, 0x00004, + 0x50527515, 0x00001, 0x50925E91, 0x02964, 0x50F2613A, 0x00004, 0x52689B80, 0x00004, + 0x533DF099, 0x00004, 0x539DD549, 0x00004, 0x54AC4B39, 0x00004, 0x54D5CDC4, 0x00004, + 0x5586B8F0, 0x02304, 0x55D5CF57, 0x00004, 0x5686BA83, 0x02304, 0x56D5D0EA, 0x00004, + 0x5786BC16, 0x02304, 0x57F29628, 0x00004, 0x582E4A5C, 0x00004, 0x5886BDA9, 0x02304, + 0x5986BF3C, 0x02304, 0x5A86C0CF, 0x02304, 0x5B86C262, 0x02304, 0x5BA54B4B, 0x00004, + 0x5BAAACCC, 0x00004, 0x5C548FE0, 0x00001, 0x5C63C0C5, 0x00004, 0x5C86C3F5, 0x02304, + 0x5D549173, 0x00001, 0x5D6C0AED, 0x00004, 0x5D77D781, 0x00004, 0x5DCF71D9, 0x00004, + 0x5E549306, 0x00001, 0x5ECE9F76, 0x00004, 0x5F135643, 0x00004, 0x5F1D8945, 0x00004, + 0x5F74FCEE, 0x00002, 0x5FCEA109, 0x00004, 0x605EBC30, 0x00001, 0x6106657D, 0x00004, + 0x6148F6AC, 0x00810, 0x6186CBD4, 0x02304, 0x61952B51, 0x00004, 0x61BC9BB4, 0x00004, + 0x61D277C7, 0x00004, 0x61D2797A, 0x00004, 0x61D27B2D, 0x00004, 0x6226F5AD, 0x00002, + 0x624C7B30, 0x00004, 0x62743428, 0x00002, 0x6286CD67, 0x02304, 0x62A0A180, 0x00004, + 0x62C9FAAF, 0x00012, 0x62F05895, 0x00004, 0x63170940, 0x00002, 0x6371183B, 0x00004, + 0x63A0A313, 0x00004, 0x64170AD3, 0x00002, 0x64A0A4A6, 0x00004, 0x64CEA8E8, 0x00004, + 0x65170C66, 0x00002, 0x65CEAA7B, 0x00004, 0x66CEAC0E, 0x00004, 0x6701AF09, 0x00004, + 0x67A659C4, 0x00004, 0x67CEADA1, 0x00004, 0x67F25394, 0x00001, 0x680EEB85, 0x0426C, + 0x6816DFDB, 0x00004, 0x68BBA8B1, 0x00004, 0x68CEAF34, 0x00004, 0x68DE4CD2, 0x00004, + 0x697141AC, 0x00004, 0x697D1E29, 0x00004, 0x69CEB0C7, 0x00004, 0x69F256BA, 0x00001, + 0x6A09346C, 0x00132, 0x6A262628, 0x00004, 0x6A614361, 0x00004, 0x6ACEB25A, 0x00004, + 0x6AF2584D, 0x00001, 0x6B2627BB, 0x00004, 0x6BCEB3ED, 0x00004, 0x6C12BF1F, 0x00004, + 0x6C3B5E62, 0x00004, 0x6C447903, 0x00004, 0x6C99F9A0, 0x00002, 0x6CD15C44, 0x00004, + 0x6D12C0B2, 0x00004, 0x6D262AE1, 0x00004, 0x6D601EDD, 0x00004, 0x6DF7A8FB, 0x00004, + 0x6E081D6F, 0x00004, 0x6E12C245, 0x00004, 0x6E972CFB, 0x00002, 0x6EB72940, 0x067D0, + 0x6EEEC240, 0x00001, 0x6F411E56, 0x00004, 0x6F66951C, 0x00004, 0x6F6696CF, 0x00004, + 0x6F669A35, 0x00004, 0x6F884079, 0x00004, 0x6F972E8E, 0x00002, 0x70973021, 0x00002, + 0x70EEC566, 0x00001, 0x71115B78, 0x00002, 0x71599CC8, 0x00004, 0x7169F53F, 0x00004, + 0x71825204, 0x00001, 0x718621A5, 0x00132, 0x71EEC6F9, 0x00001, 0x72115D0B, 0x00002, + 0x721AE36F, 0x00004, 0x736639B3, 0x00004, 0x737D1964, 0x00004, 0x74116031, 0x00002, + 0x74441C98, 0x00001, 0x748543C7, 0x0000B, 0x758C45E8, 0x00001, 0x759FF274, 0x00002, + 0x76441FBE, 0x00001, 0x765468C3, 0x00004, 0x768C477B, 0x00001, 0x76921EEB, 0x00001, + 0x7701D95E, 0x04610, 0x771E4C88, 0x00004, 0x77275404, 0x00004, 0x7742B542, 0x00004, + 0x77442151, 0x00001, 0x77BD26F2, 0x00004, 0x780BF029, 0x00004, 0x783D2039, 0x00004, + 0x783EEFDC, 0x00004, 0x7875451A, 0x01F50, 0x788C4AA1, 0x00001, 0x789FF72D, 0x00002, + 0x78D78638, 0x00040, 0x79151C13, 0x00004, 0x7964A5A9, 0x00004, 0x7999DAFD, 0x00004, + 0x79C56A5C, 0x00004, 0x79D787CB, 0x00040, 0x7A7A3480, 0x00004, 0x7A9B68E2, 0x00004, + 0x7A9EF7D9, 0x00004, 0x7AADA3E3, 0x00004, 0x7AD7895E, 0x00040, 0x7AF09C40, 0x00002, + 0x7B022344, 0x00004, 0x7B7A3613, 0x00004, 0x7BD78AF1, 0x00040, 0x7BE8A4C6, 0x00020, + 0x7BF09DD3, 0x00002, 0x7C86783F, 0x00008, 0x7CB6E900, 0x00004, 0x7D249649, 0x00004, + 0x7D6EEAB4, 0x00001, 0x7D7A3939, 0x00004, 0x7DF0A0F9, 0x00002, 0x7E6EEC47, 0x00001, + 0x7E7E36B9, 0x00004, 0x7EF9C424, 0x00004, 0x7F4B4B10, 0x00004, 0x7FD700DC, 0x00001, + 0x806EEF6D, 0x00001, 0x81961C6F, 0x00004, 0x81AD2AF6, 0x00004, 0x81D70402, 0x00001, + 0x82D70595, 0x00001, 0x83978C43, 0x00004, 0x84B301C2, 0x00004, 0x84B4A11A, 0x00004, + 0x84FD4F59, 0x00132, 0x85FDF16A, 0x00004, 0x865605E6, 0x00004, 0x86F9272C, 0x00004, + 0x874DA6FA, 0x001D0, 0x87560779, 0x00004, 0x876D7A3B, 0x00004, 0x87F611F8, 0x00004, + 0x87F613AB, 0x00004, 0x87F61711, 0x00004, 0x87F92D34, 0x00004, 0x886D7BCE, 0x00004, + 0x88F6D6AE, 0x033D0, 0x8902E727, 0x00004, 0x89048303, 0x033D0, 0x892112D4, 0x00002, + 0x896D7D61, 0x00004, 0x89A3A2B6, 0x00004, 0x8A211467, 0x00002, 0x8A468BEC, 0x00004, + 0x8A4C1426, 0x00004, 0x8A4C178C, 0x00004, 0x8A4C193F, 0x00004, 0x8A4C1CA5, 0x00004, + 0x8AD859BF, 0x00004, 0x8B2115FA, 0x00002, 0x8B4600C9, 0x00004, 0x8B468D7F, 0x00004, + 0x8B93CB0E, 0x00004, 0x8C096F9E, 0x00004, 0x8C468F12, 0x00004, 0x8C560F58, 0x00004, + 0x8C696110, 0x00004, 0x8C804D7B, 0x00004, 0x8CA88020, 0x00038, 0x8CBBFD90, 0x00008, + 0x8CD9973A, 0x00004, 0x8D321AB8, 0x000D0, 0x8D5610EB, 0x00004, 0x8DBF8019, 0x00004, + 0x8E1D74E8, 0x00004, 0x8E56127E, 0x00004, 0x8EAEB8FF, 0x00004, 0x8EF4E556, 0x00009, + 0x8F3C763E, 0x00004, 0x8F561411, 0x00004, 0x9033EB7B, 0x00A68, 0x905615A4, 0x00004, + 0x91561737, 0x00004, 0x91AC63B3, 0x00004, 0x91E09E4C, 0x00001, 0x91E89ACF, 0x00004, + 0x925618CA, 0x00004, 0x92B2F3F3, 0x00004, 0x92E09FDF, 0x00001, 0x92EB0306, 0x00004, + 0x92F4F7F0, 0x00055, 0x93490AAF, 0x00004, 0x93561A5D, 0x00004, 0x93868FC6, 0x00004, + 0x93D4B32C, 0x00004, 0x93D4B845, 0x00004, 0x944481DD, 0x00132, 0x9497A849, 0x00004, + 0x94C400E0, 0x00004, 0x94C40293, 0x00004, 0x94C40446, 0x00004, 0x94E0A305, 0x00001, + 0x95A75FF8, 0x00001, 0x95F4FCA9, 0x00019, 0x96A7618B, 0x00001, 0x96D3F39F, 0x00002, + 0x9751BABE, 0x00400, 0x97D712D3, 0x00004, 0x982972C2, 0x00004, 0x982FCB42, 0x00004, + 0x98A764B1, 0x00001, 0x98B0956F, 0x00001, 0x996BEEAA, 0x00004, 0x99B09702, 0x00001, + 0x99B9F60B, 0x00004, 0x99F197E9, 0x00004, 0x9A39C8FC, 0x00004, 0x9A535FA0, 0x00004, + 0x9AB09895, 0x00001, 0x9AC98C4C, 0x00004, 0x9ACC0AEC, 0x00004, 0x9ACC0E52, 0x00004, + 0x9ACC1005, 0x00004, 0x9C781AE2, 0x00004, 0x9CC98F72, 0x00004, 0x9CE5CDB5, 0x00132, + 0x9CF58395, 0x00001, 0x9D6727F6, 0x00004, 0x9DC99105, 0x00004, 0x9E32AE34, 0x00004, + 0x9EC079DA, 0x00002, 0x9EED59DE, 0x00004, 0x9F730ECF, 0x00002, 0xA0731062, 0x00002, + 0xA0F49CFB, 0x00004, 0xA17311F5, 0x00002, 0xA1D06D0C, 0x00004, 0xA1F76014, 0x00004, + 0xA202729F, 0x00004, 0xA2954E3A, 0x00004, 0xA2C094C7, 0x00004, 0xA2D06E9F, 0x00004, + 0xA2F761A7, 0x00004, 0xA3419FC7, 0x00019, 0xA3F7633A, 0x00004, 0xA4042B23, 0x00004, + 0xA448D9F0, 0x00004, 0xA4B74E00, 0x00004, 0xA4D071C5, 0x00004, 0xA4D5B8C0, 0x00004, + 0xA4E3B2CF, 0x00004, 0xA5D0453A, 0x00004, 0xA7301FBD, 0x00004, 0xA7B8C97B, 0x00004, + 0xA8067616, 0x00004, 0xA83021C1, 0x00268, 0xA8533433, 0x00004, 0xA8EB523D, 0x00004, + 0xA9B04C50, 0x00004, 0xAA86248B, 0x00004, 0xAB07B11E, 0x00004, 0xAD2750BE, 0x00280, + 0xAD3920F5, 0x1241C, 0xAD4E4275, 0x00004, 0xAD67A297, 0x00004, 0xAD6F9196, 0x00004, + 0xAD9DFA6A, 0x023D4, 0xAE1283B0, 0x00004, 0xAE3497CF, 0x00004, 0xAE474936, 0x00004, + 0xAEF960AC, 0x00001, 0xAF0E8346, 0x00004, 0xAF37666F, 0x00004, 0xAF376822, 0x00004, + 0xAF3769D5, 0x00004, 0xAFF9623F, 0x00001, 0xB027F396, 0x00004, 0xB046236C, 0x00004, + 0xB0CD214B, 0x00004, 0xB0F963D2, 0x00001, 0xB125CDDC, 0x00004, 0xB14624FF, 0x00004, + 0xB189D2B8, 0x00004, 0xB189D46B, 0x00004, 0xB189D7D1, 0x00004, 0xB19386DE, 0x00004, + 0xB1C26FB0, 0x000F8, 0xB1C7C436, 0x00004, 0xB1DDDCA8, 0x00028, 0xB2137AFE, 0x00004, + 0xB25C772B, 0x50C20, 0xB3462825, 0x00004, 0xB3FF0924, 0x00001, 0xB42F0EDC, 0x00004, + 0xB47E1776, 0x0001E, 0xB4FF0AB7, 0x00001, 0xB58354DB, 0x00004, 0xB5C02173, 0x00004, + 0xB5FF0C4A, 0x00001, 0xB6C02306, 0x00004, 0xB6C76A95, 0x00004, 0xB7C02499, 0x00004, + 0xB8495C0F, 0x00004, 0xB868EE77, 0x00040, 0xB9495DA2, 0x00004, 0xB95B1BB9, 0x00004, + 0xB968F00A, 0x00010, 0xB98F962B, 0x00004, 0xB9C0ECFC, 0x00004, 0xB9E1DE0C, 0x00004, + 0xBA29D287, 0x00001, 0xBA495F35, 0x00004, 0xBB1DE8EF, 0x00004, 0xBB29D41A, 0x00001, + 0xBB7B57FB, 0x00004, 0xBC29D5AD, 0x00001, 0xBC4ADA83, 0x00004, 0xBE3BD183, 0x00004, + 0xBF17C11F, 0x00004, 0xBF8E28DF, 0x00002, 0xC017C2B2, 0x00004, 0xC0362EDD, 0x00004, + 0xC08E2A72, 0x00002, 0xC0D49E2D, 0x00004, 0xC0DE5C5F, 0x00408, 0xC10889B1, 0x00004, + 0xC117C445, 0x00004, 0xC1264133, 0x00001, 0xC18E2C05, 0x00002, 0xC1BA2375, 0x00004, + 0xC22642C6, 0x00001, 0xC23AF741, 0x00004, 0xC2EFEC6A, 0x00004, 0xC3264459, 0x00001, + 0xC35F6291, 0x00004, 0xC37F267B, 0x00158, 0xC3FB9E77, 0x000A0, 0xC4F35C2E, 0x00004, + 0xC5FC19DC, 0x00002, 0xC6102A59, 0x00004, 0xC69945F9, 0x00004, 0xC7161487, 0x00004, + 0xC7652F1C, 0x00004, 0xC7911610, 0x00004, 0xC87219F6, 0x00132, 0xC89117A3, 0x00004, + 0xC8E44448, 0x00004, 0xC94D1C48, 0x00004, 0xC9541EB3, 0x00002, 0xC96FEA27, 0x00004, + 0xC9A133BC, 0x00004, 0xC9A138D5, 0x00004, 0xC9E6E098, 0x00004, 0xCA0279C5, 0x00004, + 0xCA5FA1A3, 0x00004, 0xCA8A8CEE, 0x00004, 0xCA911AC9, 0x00004, 0xCAE4476E, 0x00004, + 0xCB135C68, 0x00004, 0xCBE44901, 0x00004, 0xCC45DA54, 0x00004, 0xCCC153CD, 0x00004, + 0xCE07D358, 0x0001C, 0xCE4AC6FA, 0x00001, 0xCE76E539, 0x00004, 0xCEEB37FF, 0x00001, + 0xCF5BC550, 0x00004, 0xCF90B39A, 0x00004, 0xCFEB3992, 0x00001, 0xD033E667, 0x00004, + 0xD05BC6E3, 0x00004, 0xD08391B9, 0x00004, 0xD0EB3B25, 0x00001, 0xD105027A, 0x00132, + 0xD14C95F9, 0x00004, 0xD15BC876, 0x00004, 0xD224F9AC, 0x00F1C, 0xD25BCA09, 0x00004, + 0xD25E08A0, 0x00004, 0xD2FC64C7, 0x00004, 0xD35BCB9C, 0x00004, 0xD35E0A33, 0x00004, + 0xD3ED94AF, 0x00004, 0xD45BCD2F, 0x00004, 0xD4F8410C, 0x00002, 0xD55BCEC2, 0x00004, + 0xD65BD055, 0x00004, 0xD6F84432, 0x00002, 0xD7F845C5, 0x00002, 0xD81C8A46, 0x00004, + 0xDA5E1538, 0x00004, 0xDB5E16CB, 0x00004, 0xDBE374D7, 0x00004, 0xDBEF0145, 0x00004, + 0xDC5E185E, 0x00004, 0xDC82C7C9, 0x00004, 0xDCBDFB50, 0x00004, 0xDD12C9A5, 0x00008, + 0xDD5E19F1, 0x00004, 0xDDBDFCE3, 0x00004, 0xDE5E1B84, 0x00004, 0xDEBDFE76, 0x00004, + 0xDF5E1D17, 0x00004, 0xDFAAEECD, 0x00004, 0xDFC8508B, 0x00001, 0xE048C668, 0x00008, + 0xE05E1EAA, 0x00004, 0xE07F44FC, 0x02304, 0xE0C8521E, 0x00001, 0xE15E203D, 0x00004, + 0xE17F468F, 0x02304, 0xE1C853B1, 0x00001, 0xE2003E60, 0x00280, 0xE250A142, 0x00004, + 0xE2798DDE, 0x00001, 0xE2950DA8, 0x00001, 0xE2ED6B1D, 0x00320, 0xE2F6E456, 0x00008, + 0xE3181BAC, 0x00001, 0xE3ABBA00, 0x00004, 0xE3ED8F16, 0x00004, 0xE40A0EF4, 0x00004, + 0xE479EE37, 0x00810, 0xE47F4B48, 0x02304, 0xE5004319, 0x00280, 0xE5181ED2, 0x00001, + 0xE579EFCA, 0x00810, 0xE57F4CDB, 0x02304, 0xE6182065, 0x00001, 0xE64A93E0, 0x00001, + 0xE67F4E6E, 0x02304, 0xE74A9573, 0x00001, 0xE75196AB, 0x00004, 0xE77F5001, 0x02304, + 0xE7C7FE6B, 0x00002, 0xE851983E, 0x00004, 0xE85BEA57, 0x00004, 0xE87F5194, 0x02304, + 0xE8C7FFFE, 0x00002, 0xE9131991, 0x00004, 0xE94A9899, 0x00001, 0xE95199D1, 0x00004, + 0xE97F5327, 0x02304, 0xE9BE28BF, 0x000F0, 0xE9C80191, 0x00002, 0xEA40157B, 0x00014, + 0xEA7F54BA, 0x02304, 0xEB07C276, 0x00004, 0xEB7F564D, 0x02304, 0xECF1AE60, 0x02304, + 0xEDCE8E4C, 0x00004, 0xEDF1AFF3, 0x02304, 0xEE7B2ABD, 0x00001, 0xEE7D64D5, 0x00538, + 0xEE841484, 0x00004, 0xEEE5A3F8, 0x033D0, 0xEEF1B186, 0x02304, 0xEFCAE04E, 0x116C4, + 0xEFCE9172, 0x00004, 0xEFDC1AB1, 0x00004, 0xEFF1B319, 0x02304, 0xF0159FB2, 0x00004, + 0xF0576532, 0x00002, 0xF06213E4, 0x00004, 0xF0CE9305, 0x00004, 0xF0F1B4AC, 0x02304, + 0xF19F7785, 0x06000, 0xF1C4948B, 0x00004, 0xF1F1B63F, 0x02304, 0xF239E881, 0x02955, + 0xF25C070E, 0x00110, 0xF26B9151, 0x00004, 0xF273F5B7, 0x00004, 0xF2F1B7D2, 0x02304, + 0xF36C4BC3, 0x00004, 0xF3F1B965, 0x02304, 0xF5A72492, 0x00004, 0xF5BACCEC, 0x00004, + 0xF64719D9, 0x00004, 0xF6C672E9, 0x00004, 0xF6FB2550, 0x00001, 0xF73CDD1F, 0x00004, + 0xF75FC566, 0x00004, 0xF75FC719, 0x00004, 0xF7DFDD95, 0x00004, 0xF8154AC9, 0x00004, + 0xF8A1B32F, 0x00004, 0xF8E07F9B, 0x00004, 0xF8FB2876, 0x00001, 0xF9FB2A09, 0x00001, + 0xFA43BB59, 0x00004, 0xFAA7378A, 0x00004, 0xFAF1C46A, 0x02304, 0xFB2AE32B, 0x03208, + 0xFB5133BC, 0x00004, 0xFBF1C5FD, 0x02304, 0xFBFF61F2, 0x00004, 0xFC1AF2FC, 0x00001, + 0xFC4493F8, 0x00002, 0xFCB63E1B, 0x00001, 0xFD1AF48F, 0x00001, 0xFDB63FAE, 0x00001, + 0xFE1AF622, 0x00001, 0xFE2E9869, 0x00004, 0xFE44971E, 0x00002, 0xFE4D59C7, 0x00004, + 0xFEB13D33, 0x00002, 0xFEB64141, 0x00001, 0xFEE68CE1, 0x00004, 0xFF4498B1, 0x00002, + 0xFFA1F3C8, 0x00004, + }; - private static readonly uint[] DefaultChunkSizesLA = - { - 0x00EF4BAE, 0x00140, 0x017C3CBB, 0x00001, 0x02168706, 0x1E460, 0x022A2253, 0x00001, - 0x024C8CF3, 0x00004, 0x033D60DA, 0x00004, 0x0493AF74, 0x00004, 0x04AE4181, 0x00001, - 0x04EABECF, 0x00004, 0x050E9D63, 0x00004, 0x05E7EBEB, 0x02EE0, 0x062AF6C2, 0x00020, - 0x069EAD42, 0x00004, 0x070F34AD, 0x00020, 0x0821C372, 0x00004, 0x0A40A680, 0x00004, - 0x0A49F0EF, 0x01F40, 0x0B3C5217, 0x00004, 0x0BFDEBA1, 0x00004, 0x0E6246BE, 0x00004, - 0x0F3AD63C, 0x00004, 0x10A052BA, 0x00008, 0x10A0546D, 0x00008, 0x10D148DE, 0x00004, - 0x111933CD, 0x00004, 0x11B37EC9, 0x000C8, 0x13A0E18B, 0x00004, 0x17833C07, 0x00004, - 0x17EB7C4D, 0x00004, 0x180A4E9F, 0x00004, 0x19722C89, 0x00440, 0x1B1E3D8B, 0x00004, - 0x1B5E0528, 0x00001, 0x1B65ABFD, 0x00004, 0x1BF70FCB, 0x00004, 0x1D482A63, 0x00004, - 0x1E0F1BA3, 0x00190, 0x203A7F34, 0x00008, 0x2085565F, 0x00001, 0x20855812, 0x00001, - 0x208559C5, 0x00001, 0x208D511F, 0x00004, 0x2123FE4A, 0x00004, 0x2137FAFF, 0x00004, - 0x22540FF0, 0x00004, 0x22DEF108, 0x00004, 0x23AA6AE3, 0x00004, 0x24E0D195, 0x002F2, - 0x250F3C75, 0x00004, 0x267DD9DA, 0x00070, 0x2728E588, 0x00004, 0x27931A29, 0x00004, - 0x27986623, 0x00004, 0x279EA6CD, 0x00004, 0x27C9C8C2, 0x00100, 0x2846B7DB, 0x00004, - 0x2969F5EB, 0x00004, 0x296C9DB8, 0x00004, 0x2985FE5D, 0x008D4, 0x298D9297, 0x00004, - 0x29FB8D78, 0x00004, 0x2BBF9423, 0x00001, 0x2BBF9789, 0x00001, 0x2BBF9CA2, 0x00001, - 0x2BBF9E55, 0x00001, 0x2C0B9BF3, 0x00004, 0x2C24C5F2, 0x00020, 0x2DBE7204, 0x04B00, - 0x2EB1B190, 0x00020, 0x2F85E20D, 0x00004, 0x305FD79A, 0x00008, 0x30967638, 0x00001, - 0x3096799E, 0x00001, 0x30967B51, 0x00001, 0x30B884F9, 0x00004, 0x3279D927, 0x00004, - 0x35BDC76F, 0x00001, 0x35BDC922, 0x00001, 0x3745DA43, 0x00004, 0x37B18444, 0x00004, - 0x385F9860, 0x00004, 0x388E378D, 0x00004, 0x3AAF5E5E, 0x00004, 0x3ADB8A98, 0x000C8, - 0x3B4D705E, 0x00001, 0x3B956C1A, 0x00004, 0x3BFC2C3C, 0x000D0, 0x3C4AADD3, 0x00008, - 0x3EBEE1A7, 0x00004, 0x3F5225A0, 0x00004, 0x3F7CC8A4, 0x00004, 0x3F8120BA, 0x00002, - 0x402FAC1D, 0x00001, 0x4033C7DB, 0x00001, 0x40892A39, 0x00004, 0x40CC5A21, 0x00002, - 0x40E13871, 0x00004, 0x41309084, 0x00001, 0x416A4820, 0x00004, 0x416A49D3, 0x00004, - 0x416A4D39, 0x00004, 0x416A5252, 0x00004, 0x416A5405, 0x00004, 0x431018F0, 0x00004, - 0x43749288, 0x00010, 0x444D8A2C, 0x00001, 0x44552BFF, 0x00004, 0x451E1BAF, 0x00020, - 0x457495AE, 0x00010, 0x45851092, 0x00064, 0x4618E7E4, 0x00004, 0x46459F4E, 0x00004, - 0x46749741, 0x00010, 0x477498D4, 0x00010, 0x47E1CEAB, 0x54600, 0x481DB963, 0x00001, - 0x481DBCC9, 0x00001, 0x481DBE7C, 0x00001, 0x481DC02F, 0x00001, 0x481DCA61, 0x00001, - 0x4820CC20, 0x00001, 0x4820CDD3, 0x00001, 0x4820CF86, 0x00001, 0x4820D139, 0x00001, - 0x4820D2EC, 0x00001, 0x4820D49F, 0x00001, 0x4820D805, 0x00001, 0x4820E3EA, 0x00001, - 0x4820E59D, 0x00001, 0x4823EE28, 0x00001, 0x4823EFDB, 0x00001, 0x4823F18E, 0x00001, - 0x4823F341, 0x00001, 0x4823F4F4, 0x00001, 0x4823F6A7, 0x00001, 0x4826224C, 0x00001, - 0x48262918, 0x00001, 0x48262ACB, 0x00001, 0x48262C7E, 0x00001, 0x48262E31, 0x00001, - 0x48263197, 0x00001, 0x4826334A, 0x00001, 0x482634FD, 0x00001, 0x48749A67, 0x00010, - 0x48CE01F7, 0x000FC, 0x48DDB755, 0x00006, 0x4918E303, 0x00001, 0x49749BFA, 0x00010, - 0x4A6B888D, 0x00004, 0x4A749D8D, 0x00010, 0x4AA3F543, 0x00004, 0x4AA3F6F6, 0x00004, - 0x4AAF7FBE, 0x00004, 0x4BD70B32, 0x041A0, 0x4C5C85AB, 0x00004, 0x4CF1F5D3, 0x00004, - 0x4D7EADDD, 0x00004, 0x4DB28157, 0x00004, 0x4EB3ECBB, 0x00004, 0x4EE2B115, 0x00008, - 0x4FBDB5FF, 0x00008, 0x500164D0, 0x00004, 0x50016683, 0x00004, 0x500687A1, 0x00004, - 0x509A1AC8, 0x00004, 0x509A1C7B, 0x00004, 0x509A1FE1, 0x00004, 0x50FE632A, 0x00004, - 0x511622B3, 0x88040, 0x5297D400, 0x00001, 0x5297D766, 0x00001, 0x5297D919, 0x00001, - 0x5297DACC, 0x00001, 0x5297DC7F, 0x00001, 0x5297EBCA, 0x00001, 0x5297ED7D, 0x00001, - 0x529AE870, 0x00001, 0x529AEA23, 0x00001, 0x529AF608, 0x00001, 0x529AF7BB, 0x00001, - 0x529AF96E, 0x00001, 0x529AFB21, 0x00001, 0x529AFCD4, 0x00001, 0x529AFE87, 0x00001, - 0x529B003A, 0x00001, 0x529B01ED, 0x00001, 0x52A96788, 0x00001, 0x52A9693B, 0x00001, - 0x52A96AEE, 0x00001, 0x52A96CA1, 0x00001, 0x52A96E54, 0x00001, 0x52A97007, 0x00001, - 0x52A971BA, 0x00001, 0x52A9736D, 0x00001, 0x52AC71C6, 0x00001, 0x52AC7379, 0x00001, - 0x52AC7BF8, 0x00001, 0x52AC7DAB, 0x00001, 0x52AC7F5E, 0x00001, 0x52AC8111, 0x00001, - 0x52AC82C4, 0x00001, 0x52AC8477, 0x00001, 0x52AC862A, 0x00001, 0x52AC87DD, 0x00001, - 0x52AF8D02, 0x00001, 0x52AF8EB5, 0x00001, 0x52AF9068, 0x00001, 0x52AF921B, 0x00001, - 0x52AF93CE, 0x00001, 0x52AF9581, 0x00001, 0x52AF9734, 0x00001, 0x52AF98E7, 0x00001, - 0x52AF9C4D, 0x00001, 0x52B27C10, 0x00001, 0x52B27DC3, 0x00001, 0x52B27F76, 0x00001, - 0x52B28129, 0x00001, 0x52B282DC, 0x00001, 0x52B2848F, 0x00001, 0x52B28642, 0x00001, - 0x52B287F5, 0x00001, 0x52B289A8, 0x00001, 0x52B28B5B, 0x00001, 0x52B4B700, 0x00001, - 0x52B4B8B3, 0x00001, 0x52B4BA66, 0x00001, 0x52B4BDCC, 0x00001, 0x52B4BF7F, 0x00001, - 0x52B4C132, 0x00001, 0x52B4C2E5, 0x00001, 0x52B4CB64, 0x00001, 0x52B4CD17, 0x00001, - 0x52B7CB70, 0x00001, 0x52B7CD23, 0x00001, 0x52B7D089, 0x00001, 0x52B7D23C, 0x00001, - 0x52B7D5A2, 0x00001, 0x52B7D755, 0x00001, 0x52BAE346, 0x00001, 0x52BAE4F9, 0x00001, - 0x52BAED78, 0x00001, 0x52BAEF2B, 0x00001, 0x52BAF291, 0x00001, 0x52BAF444, 0x00001, - 0x52BAF5F7, 0x00001, 0x52BAF7AA, 0x00001, 0x52BAF95D, 0x00001, 0x52BDFB1C, 0x00001, - 0x52BDFCCF, 0x00001, 0x52BE039B, 0x00001, 0x52BE054E, 0x00001, 0x52BE0701, 0x00001, - 0x52BE08B4, 0x00001, 0x52BE0A67, 0x00001, 0x52BE0C1A, 0x00001, 0x53DB799F, 0x00004, - 0x5423DAA0, 0x00004, 0x549B6033, 0x03000, 0x54DAE9C5, 0x00004, 0x567F1330, 0x00001, - 0x567F14E3, 0x00001, 0x567F1696, 0x00001, 0x567F1849, 0x00001, 0x567F19FC, 0x00001, - 0x56823385, 0x00001, 0x56878ECA, 0x00001, 0x5687907D, 0x00001, 0x57A07D08, 0x00004, - 0x57B1D097, 0x00004, 0x5898095A, 0x00004, 0x58AB6233, 0x00064, 0x58DC8855, 0x00004, - 0x590CD38E, 0x00004, 0x5979158E, 0x00004, 0x5988DF78, 0x00004, 0x59A4D0C3, 0x00190, - 0x5A39A553, 0x00004, 0x5B1F53F3, 0x00004, 0x5C283C72, 0x00008, 0x5C283E25, 0x00008, - 0x5C283FD8, 0x00008, 0x5C28418B, 0x00008, 0x5C28433E, 0x00008, 0x5C2844F1, 0x00008, - 0x5C2846A4, 0x00008, 0x5C284857, 0x00008, 0x5C284BBD, 0x00008, 0x61A7A35B, 0x00004, - 0x62E91A65, 0x00004, 0x62F05895, 0x00004, 0x636A5ABD, 0x000C8, 0x64A1A5B0, 0x00004, - 0x64A1A763, 0x00004, 0x64A1AE2F, 0x00004, 0x64A1AFE2, 0x00004, 0x64A1B195, 0x00004, - 0x6506EE96, 0x06D60, 0x651D61A2, 0x004B0, 0x67692BB8, 0x00004, 0x67692D6B, 0x00004, - 0x67692F1E, 0x00004, 0x676935EA, 0x00004, 0x6769379D, 0x00004, 0x6960C6EF, 0x00004, - 0x6AFB0A16, 0x00004, 0x6B35BADB, 0x00060, 0x6B734EFD, 0x00004, 0x6C03D4A8, 0x00014, - 0x6C99F9A0, 0x00002, 0x6EB3E8A0, 0x00004, 0x6F36A3AC, 0x00004, 0x717DDAA3, 0x00008, - 0x71825204, 0x00001, 0x72391B04, 0x00004, 0x727AE2EE, 0x00004, 0x727AE4A1, 0x00004, - 0x727AE654, 0x00004, 0x727AE807, 0x00004, 0x727AE9BA, 0x00004, 0x74026290, 0x00004, - 0x744447B4, 0x00004, 0x75931048, 0x00004, 0x75931561, 0x00004, 0x75CE2CF6, 0x00004, - 0x7659EC88, 0x00004, 0x76AB1B01, 0x00004, 0x76ABB5CD, 0x00004, 0x77675FA0, 0x00320, - 0x7799EB86, 0x03980, 0x77B752BC, 0x00004, 0x77B75622, 0x00004, 0x77B757D5, 0x00004, - 0x78848293, 0x00001, 0x78848446, 0x00001, 0x788485F9, 0x00001, 0x78E0935E, 0x00004, - 0x79448B5D, 0x00004, 0x79C56A5C, 0x00004, 0x7A8530FD, 0x00004, 0x7ACB8CB5, 0x00004, - 0x7B8CCB0B, 0x00180, 0x7CA9D9FA, 0x00004, 0x7D249649, 0x00004, 0x7D87DC83, 0x00004, - 0x7E82513F, 0x00004, 0x8048A7DC, 0x00004, 0x8184EFB4, 0x00004, 0x81EC3A78, 0x00004, - 0x82AD5F84, 0x00004, 0x82D57F17, 0x000C8, 0x8507839C, 0x00004, 0x85166DE2, 0x00004, - 0x877CB98F, 0x009B0, 0x885E5F53, 0x00010, 0x89C1C8DE, 0x00004, 0x8A0E9425, 0x004B0, - 0x8B18A566, 0x00004, 0x8B18A719, 0x00004, 0x8B18A8CC, 0x00004, 0x8B18AA7F, 0x00004, - 0x8B18AC32, 0x00004, 0x8B18ADE5, 0x00004, 0x8B8FB439, 0x00004, 0x8BDFF0F3, 0x00040, - 0x8BEEF106, 0x00004, 0x8C46768E, 0x00004, 0x8C5F59E8, 0x00004, 0x8D781241, 0x00008, - 0x8E434F0D, 0x002D0, 0x8F0D8720, 0x00004, 0x8FC0A045, 0x00004, 0x92EB0306, 0x00004, - 0x92F697ED, 0x00004, 0x95013114, 0x00008, 0x96993D83, 0x00008, 0x96F6F453, 0x00004, - 0x9751BABE, 0x00400, 0x98785EE4, 0x00008, 0x98786097, 0x00008, 0x987863FD, 0x00008, - 0x99E1625E, 0x07EB0, 0x9AB5F3D9, 0x00001, 0x9B986D2E, 0x00004, 0x9C41123A, 0x00004, - 0x9C808BD3, 0x00004, 0x9D5D1CA5, 0x00004, 0x9E45BE99, 0x00004, 0x9E4635BB, 0x00004, - 0x9EC079DA, 0x00002, 0x9FE2790A, 0x00A8C, 0xA00A8ABB, 0x00008, 0xA2AA5B41, 0x00004, - 0xA373DA53, 0x01900, 0xA4317061, 0x00004, 0xA69E079B, 0x00004, 0xA94E1F5F, 0x00004, - 0xA9F1368B, 0x00004, 0xAB48C136, 0x00004, 0xAD319811, 0x00004, 0xAE7B3CA1, 0x00004, - 0xAE89206E, 0x00001, 0xAEE903A2, 0x00008, 0xB027F396, 0x00004, 0xB0B2A5AA, 0x00004, - 0xB1EE7CF5, 0x00004, 0xB568265C, 0x00004, 0xB79EF1FE, 0x00004, 0xB7AAB47E, 0x00004, - 0xB7DB15CC, 0x00004, 0xB8E961AD, 0x000FB, 0xB9075BC9, 0x00008, 0xB9252862, 0x00110, - 0xBA230941, 0x00004, 0xBB0B39CF, 0x00004, 0xBCC66014, 0x00004, 0xBCC72306, 0x00004, - 0xBCE74BFD, 0x00004, 0xBDDC386E, 0x00004, 0xC02AC847, 0x00004, 0xC25B0D5A, 0x00004, - 0xC2F1C1B9, 0x02580, 0xC4C6417F, 0x000C0, 0xC4FA7C8C, 0x00004, 0xC5277828, 0x00008, - 0xC5919BF6, 0x00004, 0xC5D7112B, 0x0DCA8, 0xC69F66B6, 0x00FA0, 0xC74A3FE7, 0x00010, - 0xC7652F1C, 0x00004, 0xC7F8E3BC, 0x00008, 0xC7F8E56F, 0x00008, 0xC7F8EA88, 0x00008, - 0xC7F8EC3B, 0x00008, 0xC7F8EDEE, 0x00008, 0xC7F8EFA1, 0x00008, 0xC7F8F154, 0x00008, - 0xC7F8F4BA, 0x00008, 0xC7F8F66D, 0x00008, 0xC9541EB3, 0x00002, 0xC9A81578, 0x00010, - 0xCC022CEC, 0x00008, 0xCC022E9F, 0x00008, 0xCC023052, 0x00008, 0xCD8ADF1D, 0x00004, - 0xCFC9AA03, 0x00004, 0xCFEB27B3, 0x009C4, 0xCFED4C69, 0x00004, 0xD0068A74, 0x00004, - 0xD03A595A, 0x009B0, 0xD06FD1EA, 0x00004, 0xD11C1F59, 0x00004, 0xD20DF4A0, 0x00004, - 0xD2E5D408, 0x00004, 0xD3C06782, 0x00008, 0xD48FDC48, 0x00004, 0xD6546A02, 0x00038, - 0xD6C95683, 0x00960, 0xD6F1B724, 0x00004, 0xD8048E00, 0x00004, 0xD94D71D7, 0x00004, - 0xDD548BB1, 0x00B58, 0xDED70F11, 0x00004, 0xDFA7E2D8, 0x00004, 0xE0FB1EE7, 0x00008, - 0xE1DF0672, 0x005B8, 0xE2798DDE, 0x00001, 0xE36D5700, 0x00064, 0xE450FEF7, 0x0001A, - 0xE4E75089, 0x00040, 0xE5169DA1, 0x00004, 0xE668F1A6, 0x00001, 0xE668F359, 0x00001, - 0xE668F50C, 0x00001, 0xE668F6BF, 0x00001, 0xE668FA25, 0x00001, 0xE72BDCEC, 0x00004, - 0xE733FE4D, 0x00004, 0xE7425CFF, 0x00004, 0xE793EEAE, 0x00004, 0xEA7C87F4, 0x00020, - 0xEB550C12, 0x00004, 0xEC13DFD7, 0x00004, 0xEE10F128, 0x00004, 0xEFB533F7, 0x00001, - 0xF25C070E, 0x00080, 0xF32218C2, 0x00004, 0xF3CF94FF, 0x00004, 0xF41CFF61, 0x00200, - 0xF45C3F59, 0x00004, 0xF4954B60, 0x00004, 0xF5D9F4A5, 0x00118, 0xF626721E, 0x00004, - 0xF62D79D3, 0x00004, 0xF8154AC9, 0x00004, 0xFB58B1A7, 0x00064, 0xFB5AE87D, 0x00004, - 0xFC374750, 0x00004, 0xFF400328, 0x00004, 0xFFCC05C2, 0x00001, - }; - } + private static readonly uint[] DefaultChunkSizesLA = + { + 0x00EF4BAE, 0x00140, 0x017C3CBB, 0x00001, 0x02168706, 0x1E460, 0x022A2253, 0x00001, + 0x024C8CF3, 0x00004, 0x033D60DA, 0x00004, 0x0493AF74, 0x00004, 0x04AE4181, 0x00001, + 0x04EABECF, 0x00004, 0x050E9D63, 0x00004, 0x05E7EBEB, 0x02EE0, 0x062AF6C2, 0x00020, + 0x069EAD42, 0x00004, 0x070F34AD, 0x00020, 0x0821C372, 0x00004, 0x0A40A680, 0x00004, + 0x0A49F0EF, 0x01F40, 0x0B3C5217, 0x00004, 0x0BFDEBA1, 0x00004, 0x0E6246BE, 0x00004, + 0x0F3AD63C, 0x00004, 0x10A052BA, 0x00008, 0x10A0546D, 0x00008, 0x10D148DE, 0x00004, + 0x111933CD, 0x00004, 0x11B37EC9, 0x000C8, 0x13A0E18B, 0x00004, 0x17833C07, 0x00004, + 0x17EB7C4D, 0x00004, 0x180A4E9F, 0x00004, 0x19722C89, 0x00440, 0x1B1E3D8B, 0x00004, + 0x1B5E0528, 0x00001, 0x1B65ABFD, 0x00004, 0x1BF70FCB, 0x00004, 0x1D482A63, 0x00004, + 0x1E0F1BA3, 0x00190, 0x203A7F34, 0x00008, 0x2085565F, 0x00001, 0x20855812, 0x00001, + 0x208559C5, 0x00001, 0x208D511F, 0x00004, 0x2123FE4A, 0x00004, 0x2137FAFF, 0x00004, + 0x22540FF0, 0x00004, 0x22DEF108, 0x00004, 0x23AA6AE3, 0x00004, 0x24E0D195, 0x002F2, + 0x250F3C75, 0x00004, 0x267DD9DA, 0x00070, 0x2728E588, 0x00004, 0x27931A29, 0x00004, + 0x27986623, 0x00004, 0x279EA6CD, 0x00004, 0x27C9C8C2, 0x00100, 0x2846B7DB, 0x00004, + 0x2969F5EB, 0x00004, 0x296C9DB8, 0x00004, 0x2985FE5D, 0x008D4, 0x298D9297, 0x00004, + 0x29FB8D78, 0x00004, 0x2BBF9423, 0x00001, 0x2BBF9789, 0x00001, 0x2BBF9CA2, 0x00001, + 0x2BBF9E55, 0x00001, 0x2C0B9BF3, 0x00004, 0x2C24C5F2, 0x00020, 0x2DBE7204, 0x04B00, + 0x2EB1B190, 0x00020, 0x2F85E20D, 0x00004, 0x305FD79A, 0x00008, 0x30967638, 0x00001, + 0x3096799E, 0x00001, 0x30967B51, 0x00001, 0x30B884F9, 0x00004, 0x3279D927, 0x00004, + 0x35BDC76F, 0x00001, 0x35BDC922, 0x00001, 0x3745DA43, 0x00004, 0x37B18444, 0x00004, + 0x385F9860, 0x00004, 0x388E378D, 0x00004, 0x3AAF5E5E, 0x00004, 0x3ADB8A98, 0x000C8, + 0x3B4D705E, 0x00001, 0x3B956C1A, 0x00004, 0x3BFC2C3C, 0x000D0, 0x3C4AADD3, 0x00008, + 0x3EBEE1A7, 0x00004, 0x3F5225A0, 0x00004, 0x3F7CC8A4, 0x00004, 0x3F8120BA, 0x00002, + 0x402FAC1D, 0x00001, 0x4033C7DB, 0x00001, 0x40892A39, 0x00004, 0x40CC5A21, 0x00002, + 0x40E13871, 0x00004, 0x41309084, 0x00001, 0x416A4820, 0x00004, 0x416A49D3, 0x00004, + 0x416A4D39, 0x00004, 0x416A5252, 0x00004, 0x416A5405, 0x00004, 0x431018F0, 0x00004, + 0x43749288, 0x00010, 0x444D8A2C, 0x00001, 0x44552BFF, 0x00004, 0x451E1BAF, 0x00020, + 0x457495AE, 0x00010, 0x45851092, 0x00064, 0x4618E7E4, 0x00004, 0x46459F4E, 0x00004, + 0x46749741, 0x00010, 0x477498D4, 0x00010, 0x47E1CEAB, 0x54600, 0x481DB963, 0x00001, + 0x481DBCC9, 0x00001, 0x481DBE7C, 0x00001, 0x481DC02F, 0x00001, 0x481DCA61, 0x00001, + 0x4820CC20, 0x00001, 0x4820CDD3, 0x00001, 0x4820CF86, 0x00001, 0x4820D139, 0x00001, + 0x4820D2EC, 0x00001, 0x4820D49F, 0x00001, 0x4820D805, 0x00001, 0x4820E3EA, 0x00001, + 0x4820E59D, 0x00001, 0x4823EE28, 0x00001, 0x4823EFDB, 0x00001, 0x4823F18E, 0x00001, + 0x4823F341, 0x00001, 0x4823F4F4, 0x00001, 0x4823F6A7, 0x00001, 0x4826224C, 0x00001, + 0x48262918, 0x00001, 0x48262ACB, 0x00001, 0x48262C7E, 0x00001, 0x48262E31, 0x00001, + 0x48263197, 0x00001, 0x4826334A, 0x00001, 0x482634FD, 0x00001, 0x48749A67, 0x00010, + 0x48CE01F7, 0x000FC, 0x48DDB755, 0x00006, 0x4918E303, 0x00001, 0x49749BFA, 0x00010, + 0x4A6B888D, 0x00004, 0x4A749D8D, 0x00010, 0x4AA3F543, 0x00004, 0x4AA3F6F6, 0x00004, + 0x4AAF7FBE, 0x00004, 0x4BD70B32, 0x041A0, 0x4C5C85AB, 0x00004, 0x4CF1F5D3, 0x00004, + 0x4D7EADDD, 0x00004, 0x4DB28157, 0x00004, 0x4EB3ECBB, 0x00004, 0x4EE2B115, 0x00008, + 0x4FBDB5FF, 0x00008, 0x500164D0, 0x00004, 0x50016683, 0x00004, 0x500687A1, 0x00004, + 0x509A1AC8, 0x00004, 0x509A1C7B, 0x00004, 0x509A1FE1, 0x00004, 0x50FE632A, 0x00004, + 0x511622B3, 0x88040, 0x5297D400, 0x00001, 0x5297D766, 0x00001, 0x5297D919, 0x00001, + 0x5297DACC, 0x00001, 0x5297DC7F, 0x00001, 0x5297EBCA, 0x00001, 0x5297ED7D, 0x00001, + 0x529AE870, 0x00001, 0x529AEA23, 0x00001, 0x529AF608, 0x00001, 0x529AF7BB, 0x00001, + 0x529AF96E, 0x00001, 0x529AFB21, 0x00001, 0x529AFCD4, 0x00001, 0x529AFE87, 0x00001, + 0x529B003A, 0x00001, 0x529B01ED, 0x00001, 0x52A96788, 0x00001, 0x52A9693B, 0x00001, + 0x52A96AEE, 0x00001, 0x52A96CA1, 0x00001, 0x52A96E54, 0x00001, 0x52A97007, 0x00001, + 0x52A971BA, 0x00001, 0x52A9736D, 0x00001, 0x52AC71C6, 0x00001, 0x52AC7379, 0x00001, + 0x52AC7BF8, 0x00001, 0x52AC7DAB, 0x00001, 0x52AC7F5E, 0x00001, 0x52AC8111, 0x00001, + 0x52AC82C4, 0x00001, 0x52AC8477, 0x00001, 0x52AC862A, 0x00001, 0x52AC87DD, 0x00001, + 0x52AF8D02, 0x00001, 0x52AF8EB5, 0x00001, 0x52AF9068, 0x00001, 0x52AF921B, 0x00001, + 0x52AF93CE, 0x00001, 0x52AF9581, 0x00001, 0x52AF9734, 0x00001, 0x52AF98E7, 0x00001, + 0x52AF9C4D, 0x00001, 0x52B27C10, 0x00001, 0x52B27DC3, 0x00001, 0x52B27F76, 0x00001, + 0x52B28129, 0x00001, 0x52B282DC, 0x00001, 0x52B2848F, 0x00001, 0x52B28642, 0x00001, + 0x52B287F5, 0x00001, 0x52B289A8, 0x00001, 0x52B28B5B, 0x00001, 0x52B4B700, 0x00001, + 0x52B4B8B3, 0x00001, 0x52B4BA66, 0x00001, 0x52B4BDCC, 0x00001, 0x52B4BF7F, 0x00001, + 0x52B4C132, 0x00001, 0x52B4C2E5, 0x00001, 0x52B4CB64, 0x00001, 0x52B4CD17, 0x00001, + 0x52B7CB70, 0x00001, 0x52B7CD23, 0x00001, 0x52B7D089, 0x00001, 0x52B7D23C, 0x00001, + 0x52B7D5A2, 0x00001, 0x52B7D755, 0x00001, 0x52BAE346, 0x00001, 0x52BAE4F9, 0x00001, + 0x52BAED78, 0x00001, 0x52BAEF2B, 0x00001, 0x52BAF291, 0x00001, 0x52BAF444, 0x00001, + 0x52BAF5F7, 0x00001, 0x52BAF7AA, 0x00001, 0x52BAF95D, 0x00001, 0x52BDFB1C, 0x00001, + 0x52BDFCCF, 0x00001, 0x52BE039B, 0x00001, 0x52BE054E, 0x00001, 0x52BE0701, 0x00001, + 0x52BE08B4, 0x00001, 0x52BE0A67, 0x00001, 0x52BE0C1A, 0x00001, 0x53DB799F, 0x00004, + 0x5423DAA0, 0x00004, 0x549B6033, 0x03000, 0x54DAE9C5, 0x00004, 0x567F1330, 0x00001, + 0x567F14E3, 0x00001, 0x567F1696, 0x00001, 0x567F1849, 0x00001, 0x567F19FC, 0x00001, + 0x56823385, 0x00001, 0x56878ECA, 0x00001, 0x5687907D, 0x00001, 0x57A07D08, 0x00004, + 0x57B1D097, 0x00004, 0x5898095A, 0x00004, 0x58AB6233, 0x00064, 0x58DC8855, 0x00004, + 0x590CD38E, 0x00004, 0x5979158E, 0x00004, 0x5988DF78, 0x00004, 0x59A4D0C3, 0x00190, + 0x5A39A553, 0x00004, 0x5B1F53F3, 0x00004, 0x5C283C72, 0x00008, 0x5C283E25, 0x00008, + 0x5C283FD8, 0x00008, 0x5C28418B, 0x00008, 0x5C28433E, 0x00008, 0x5C2844F1, 0x00008, + 0x5C2846A4, 0x00008, 0x5C284857, 0x00008, 0x5C284BBD, 0x00008, 0x61A7A35B, 0x00004, + 0x62E91A65, 0x00004, 0x62F05895, 0x00004, 0x636A5ABD, 0x000C8, 0x64A1A5B0, 0x00004, + 0x64A1A763, 0x00004, 0x64A1AE2F, 0x00004, 0x64A1AFE2, 0x00004, 0x64A1B195, 0x00004, + 0x6506EE96, 0x06D60, 0x651D61A2, 0x004B0, 0x67692BB8, 0x00004, 0x67692D6B, 0x00004, + 0x67692F1E, 0x00004, 0x676935EA, 0x00004, 0x6769379D, 0x00004, 0x6960C6EF, 0x00004, + 0x6AFB0A16, 0x00004, 0x6B35BADB, 0x00060, 0x6B734EFD, 0x00004, 0x6C03D4A8, 0x00014, + 0x6C99F9A0, 0x00002, 0x6EB3E8A0, 0x00004, 0x6F36A3AC, 0x00004, 0x717DDAA3, 0x00008, + 0x71825204, 0x00001, 0x72391B04, 0x00004, 0x727AE2EE, 0x00004, 0x727AE4A1, 0x00004, + 0x727AE654, 0x00004, 0x727AE807, 0x00004, 0x727AE9BA, 0x00004, 0x74026290, 0x00004, + 0x744447B4, 0x00004, 0x75931048, 0x00004, 0x75931561, 0x00004, 0x75CE2CF6, 0x00004, + 0x7659EC88, 0x00004, 0x76AB1B01, 0x00004, 0x76ABB5CD, 0x00004, 0x77675FA0, 0x00320, + 0x7799EB86, 0x03980, 0x77B752BC, 0x00004, 0x77B75622, 0x00004, 0x77B757D5, 0x00004, + 0x78848293, 0x00001, 0x78848446, 0x00001, 0x788485F9, 0x00001, 0x78E0935E, 0x00004, + 0x79448B5D, 0x00004, 0x79C56A5C, 0x00004, 0x7A8530FD, 0x00004, 0x7ACB8CB5, 0x00004, + 0x7B8CCB0B, 0x00180, 0x7CA9D9FA, 0x00004, 0x7D249649, 0x00004, 0x7D87DC83, 0x00004, + 0x7E82513F, 0x00004, 0x8048A7DC, 0x00004, 0x8184EFB4, 0x00004, 0x81EC3A78, 0x00004, + 0x82AD5F84, 0x00004, 0x82D57F17, 0x000C8, 0x8507839C, 0x00004, 0x85166DE2, 0x00004, + 0x877CB98F, 0x009B0, 0x885E5F53, 0x00010, 0x89C1C8DE, 0x00004, 0x8A0E9425, 0x004B0, + 0x8B18A566, 0x00004, 0x8B18A719, 0x00004, 0x8B18A8CC, 0x00004, 0x8B18AA7F, 0x00004, + 0x8B18AC32, 0x00004, 0x8B18ADE5, 0x00004, 0x8B8FB439, 0x00004, 0x8BDFF0F3, 0x00040, + 0x8BEEF106, 0x00004, 0x8C46768E, 0x00004, 0x8C5F59E8, 0x00004, 0x8D781241, 0x00008, + 0x8E434F0D, 0x002D0, 0x8F0D8720, 0x00004, 0x8FC0A045, 0x00004, 0x92EB0306, 0x00004, + 0x92F697ED, 0x00004, 0x95013114, 0x00008, 0x96993D83, 0x00008, 0x96F6F453, 0x00004, + 0x9751BABE, 0x00400, 0x98785EE4, 0x00008, 0x98786097, 0x00008, 0x987863FD, 0x00008, + 0x99E1625E, 0x07EB0, 0x9AB5F3D9, 0x00001, 0x9B986D2E, 0x00004, 0x9C41123A, 0x00004, + 0x9C808BD3, 0x00004, 0x9D5D1CA5, 0x00004, 0x9E45BE99, 0x00004, 0x9E4635BB, 0x00004, + 0x9EC079DA, 0x00002, 0x9FE2790A, 0x00A8C, 0xA00A8ABB, 0x00008, 0xA2AA5B41, 0x00004, + 0xA373DA53, 0x01900, 0xA4317061, 0x00004, 0xA69E079B, 0x00004, 0xA94E1F5F, 0x00004, + 0xA9F1368B, 0x00004, 0xAB48C136, 0x00004, 0xAD319811, 0x00004, 0xAE7B3CA1, 0x00004, + 0xAE89206E, 0x00001, 0xAEE903A2, 0x00008, 0xB027F396, 0x00004, 0xB0B2A5AA, 0x00004, + 0xB1EE7CF5, 0x00004, 0xB568265C, 0x00004, 0xB79EF1FE, 0x00004, 0xB7AAB47E, 0x00004, + 0xB7DB15CC, 0x00004, 0xB8E961AD, 0x000FB, 0xB9075BC9, 0x00008, 0xB9252862, 0x00110, + 0xBA230941, 0x00004, 0xBB0B39CF, 0x00004, 0xBCC66014, 0x00004, 0xBCC72306, 0x00004, + 0xBCE74BFD, 0x00004, 0xBDDC386E, 0x00004, 0xC02AC847, 0x00004, 0xC25B0D5A, 0x00004, + 0xC2F1C1B9, 0x02580, 0xC4C6417F, 0x000C0, 0xC4FA7C8C, 0x00004, 0xC5277828, 0x00008, + 0xC5919BF6, 0x00004, 0xC5D7112B, 0x0DCA8, 0xC69F66B6, 0x00FA0, 0xC74A3FE7, 0x00010, + 0xC7652F1C, 0x00004, 0xC7F8E3BC, 0x00008, 0xC7F8E56F, 0x00008, 0xC7F8EA88, 0x00008, + 0xC7F8EC3B, 0x00008, 0xC7F8EDEE, 0x00008, 0xC7F8EFA1, 0x00008, 0xC7F8F154, 0x00008, + 0xC7F8F4BA, 0x00008, 0xC7F8F66D, 0x00008, 0xC9541EB3, 0x00002, 0xC9A81578, 0x00010, + 0xCC022CEC, 0x00008, 0xCC022E9F, 0x00008, 0xCC023052, 0x00008, 0xCD8ADF1D, 0x00004, + 0xCFC9AA03, 0x00004, 0xCFEB27B3, 0x009C4, 0xCFED4C69, 0x00004, 0xD0068A74, 0x00004, + 0xD03A595A, 0x009B0, 0xD06FD1EA, 0x00004, 0xD11C1F59, 0x00004, 0xD20DF4A0, 0x00004, + 0xD2E5D408, 0x00004, 0xD3C06782, 0x00008, 0xD48FDC48, 0x00004, 0xD6546A02, 0x00038, + 0xD6C95683, 0x00960, 0xD6F1B724, 0x00004, 0xD8048E00, 0x00004, 0xD94D71D7, 0x00004, + 0xDD548BB1, 0x00B58, 0xDED70F11, 0x00004, 0xDFA7E2D8, 0x00004, 0xE0FB1EE7, 0x00008, + 0xE1DF0672, 0x005B8, 0xE2798DDE, 0x00001, 0xE36D5700, 0x00064, 0xE450FEF7, 0x0001A, + 0xE4E75089, 0x00040, 0xE5169DA1, 0x00004, 0xE668F1A6, 0x00001, 0xE668F359, 0x00001, + 0xE668F50C, 0x00001, 0xE668F6BF, 0x00001, 0xE668FA25, 0x00001, 0xE72BDCEC, 0x00004, + 0xE733FE4D, 0x00004, 0xE7425CFF, 0x00004, 0xE793EEAE, 0x00004, 0xEA7C87F4, 0x00020, + 0xEB550C12, 0x00004, 0xEC13DFD7, 0x00004, 0xEE10F128, 0x00004, 0xEFB533F7, 0x00001, + 0xF25C070E, 0x00080, 0xF32218C2, 0x00004, 0xF3CF94FF, 0x00004, 0xF41CFF61, 0x00200, + 0xF45C3F59, 0x00004, 0xF4954B60, 0x00004, 0xF5D9F4A5, 0x00118, 0xF626721E, 0x00004, + 0xF62D79D3, 0x00004, 0xF8154AC9, 0x00004, 0xFB58B1A7, 0x00064, 0xFB5AE87D, 0x00004, + 0xFC374750, 0x00004, 0xFF400328, 0x00004, 0xFFCC05C2, 0x00001, + }; } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Box8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Box8.cs index 0b805679f..16f52f319 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Box8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Box8.cs @@ -1,7 +1,6 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Box8 : SaveBlock { - public sealed class Box8 : SaveBlock - { - public Box8(SaveFile sav, SCBlock block) : base(sav, block.Data) { } - } + public Box8(SaveFile sav, SCBlock block) : base(sav, block.Data) { } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/BoxLayout8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/BoxLayout8.cs index a3849379a..c4777ef9f 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/BoxLayout8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/BoxLayout8.cs @@ -1,30 +1,29 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class BoxLayout8 : SaveBlock, IBoxDetailName { - public sealed class BoxLayout8 : SaveBlock, IBoxDetailName + public const int BoxCount = 32; + + private const int StringMaxLength = SAV6.LongStringLength / 2; + + public BoxLayout8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + + private static int GetBoxNameOffset(int box) => SAV6.LongStringLength * box; + private Span GetBoxNameSpan(int box) => Data.AsSpan(GetBoxNameOffset(box), SAV6.LongStringLength); + public string GetBoxName(int box) => SAV.GetString(GetBoxNameSpan(box)); + public void SetBoxName(int box, string value) => SAV.SetString(GetBoxNameSpan(box), value.AsSpan(), StringMaxLength, StringConverterOption.ClearZero); + + public string this[int i] { - public const int BoxCount = 32; - - private const int StringMaxLength = SAV6.LongStringLength / 2; - - public BoxLayout8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } - - private static int GetBoxNameOffset(int box) => SAV6.LongStringLength * box; - private Span GetBoxNameSpan(int box) => Data.AsSpan(GetBoxNameOffset(box), SAV6.LongStringLength); - public string GetBoxName(int box) => SAV.GetString(GetBoxNameSpan(box)); - public void SetBoxName(int box, string value) => SAV.SetString(GetBoxNameSpan(box), value.AsSpan(), StringMaxLength, StringConverterOption.ClearZero); - - public string this[int i] - { - get => GetBoxName(i); - set => SetBoxName(i, value); - } - - public int CurrentBox - { - get => SAV.GetValue(SaveBlockAccessor8SWSH.KCurrentBox); - set => SAV.SetValue(SaveBlockAccessor8SWSH.KCurrentBox, (byte)value); - } + get => GetBoxName(i); + set => SetBoxName(i, value); } -} \ No newline at end of file + + public int CurrentBox + { + get => SAV.GetValue(SaveBlockAccessor8SWSH.KCurrentBox); + set => SAV.SetValue(SaveBlockAccessor8SWSH.KCurrentBox, (byte)value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Daycare8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Daycare8.cs index ba37494ad..901bb48ce 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Daycare8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Daycare8.cs @@ -1,93 +1,92 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Storage for the two in-game daycare structures. +/// +public sealed class Daycare8 : SaveBlock { + public Daycare8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + + // BLOCK STRUCTURE: + // bool8 present + // pk8 entry1 + // bool8 present + // pk8 entry2 + // daycare metadata (0x26) + // bool1 present + // pk8 entry1 + // bool1 present + // pk8 entry2 + // daycare metadata (0x26) + + // daycare metadata: + // u16 ?? + // u32 ?step count since last update? + // u64 RNG seed + // 0x18 unk + /// - /// Storage for the two in-game daycare structures. + /// Size of each PKM data stored (bool, pk8) /// - public sealed class Daycare8 : SaveBlock + private const int STRUCT_SIZE = 1 + PokeCrypto.SIZE_8STORED; + + /// + /// Size of each daycare (both entries & metadata) + /// + private const int DAYCARE_SIZE = (2 * STRUCT_SIZE) + 0x26; + + private const int META_1 = (2 * STRUCT_SIZE); + private const int META_2 = DAYCARE_SIZE + META_1; + + public bool GetDaycare1SlotOccupied(int slot) { - public Daycare8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + if ((uint)slot >= 2) + throw new ArgumentOutOfRangeException(nameof(slot), slot, "Expected 0/1"); - // BLOCK STRUCTURE: - // bool8 present - // pk8 entry1 - // bool8 present - // pk8 entry2 - // daycare metadata (0x26) - // bool1 present - // pk8 entry1 - // bool1 present - // pk8 entry2 - // daycare metadata (0x26) - - // daycare metadata: - // u16 ?? - // u32 ?step count since last update? - // u64 RNG seed - // 0x18 unk - - /// - /// Size of each PKM data stored (bool, pk8) - /// - private const int STRUCT_SIZE = 1 + PokeCrypto.SIZE_8STORED; - - /// - /// Size of each daycare (both entries & metadata) - /// - private const int DAYCARE_SIZE = (2 * STRUCT_SIZE) + 0x26; - - private const int META_1 = (2 * STRUCT_SIZE); - private const int META_2 = DAYCARE_SIZE + META_1; - - public bool GetDaycare1SlotOccupied(int slot) - { - if ((uint) slot >= 2) - throw new IndexOutOfRangeException(nameof(slot)); - - return Data[GetDaycare1StructOffset(slot)] == 1; - } - - public bool GetDaycare2SlotOccupied(int slot) - { - if ((uint)slot >= 2) - throw new IndexOutOfRangeException(nameof(slot)); - - return Data[GetDaycare2StructOffset(slot)] == 1; - } - - public static int GetDaycare1StructOffset(int slot) - { - if ((uint)slot >= 2) - throw new IndexOutOfRangeException(nameof(slot)); - - return 0 + (slot * STRUCT_SIZE); - } - - public static int GetDaycare2StructOffset(int slot) - { - if ((uint)slot >= 2) - throw new IndexOutOfRangeException(nameof(slot)); - - return DAYCARE_SIZE + (slot * STRUCT_SIZE); - } - - public static int GetDaycareSlotOffset(int daycare, int slot) => daycare switch - { - 0 => (1 + GetDaycare1StructOffset(slot)), - 1 => (1 + GetDaycare2StructOffset(slot)), - _ => throw new IndexOutOfRangeException(nameof(daycare)), - }; - - public static int GetDaycareMetadataOffset(int daycare) => daycare switch - { - 0 => META_1, - 1 => META_2, - _ => throw new IndexOutOfRangeException(nameof(daycare)), - }; - - public ulong GetDaycareSeed(int daycare) => ReadUInt64LittleEndian(Data.AsSpan(GetDaycareMetadataOffset(daycare) + 6)); - public void SetDaycareSeed(int daycare, ulong value) => WriteUInt64LittleEndian(Data.AsSpan(GetDaycareMetadataOffset(daycare) + 6), value); + return Data[GetDaycare1StructOffset(slot)] == 1; } -} \ No newline at end of file + + public bool GetDaycare2SlotOccupied(int slot) + { + if ((uint)slot >= 2) + throw new ArgumentOutOfRangeException(nameof(slot), slot, "Expected 0/1"); + + return Data[GetDaycare2StructOffset(slot)] == 1; + } + + public static int GetDaycare1StructOffset(int slot) + { + if ((uint)slot >= 2) + throw new ArgumentOutOfRangeException(nameof(slot), slot, "Expected 0/1"); + + return 0 + (slot * STRUCT_SIZE); + } + + public static int GetDaycare2StructOffset(int slot) + { + if ((uint)slot >= 2) + throw new ArgumentOutOfRangeException(nameof(slot), slot, "Expected 0/1"); + + return DAYCARE_SIZE + (slot * STRUCT_SIZE); + } + + public static int GetDaycareSlotOffset(int daycare, int slot) => daycare switch + { + 0 => (1 + GetDaycare1StructOffset(slot)), + 1 => (1 + GetDaycare2StructOffset(slot)), + _ => throw new ArgumentOutOfRangeException(nameof(daycare), daycare, "Expected 0/1"), + }; + + public static int GetDaycareMetadataOffset(int daycare) => daycare switch + { + 0 => META_1, + 1 => META_2, + _ => throw new ArgumentOutOfRangeException(nameof(daycare), daycare, "Expected 0/1"), + }; + + public ulong GetDaycareSeed(int daycare) => ReadUInt64LittleEndian(Data.AsSpan(GetDaycareMetadataOffset(daycare) + 6)); + public void SetDaycareSeed(int daycare, ulong value) => WriteUInt64LittleEndian(Data.AsSpan(GetDaycareMetadataOffset(daycare) + 6), value); +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/FashionUnlock8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/FashionUnlock8.cs index 489a591fd..c032abb5f 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/FashionUnlock8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/FashionUnlock8.cs @@ -1,43 +1,53 @@ using System; using System.Collections.Generic; -using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class FashionUnlock8 : SaveBlock { - public sealed class FashionUnlock8 : SaveBlock + private const int SIZE_ENTRY = 0x80; + private const int REGIONS = 15; + + public FashionUnlock8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + + public bool[] GetArrayOwnedFlag(int region) => ArrayUtil.GitBitFlagArray(Data.AsSpan(region * SIZE_ENTRY), SIZE_ENTRY * 8); + public bool[] GetArrayNewFlag(int region) => ArrayUtil.GitBitFlagArray(Data.AsSpan((region + REGIONS) * SIZE_ENTRY), SIZE_ENTRY * 8); + public int[] GetIndexesOwnedFlag(int region) => GetIndexes(GetArrayOwnedFlag(region)); + public int[] GetIndexesNewFlag(int region) => GetIndexes(GetArrayNewFlag(region)); + + public void SetArrayOwnedFlag(int region, bool[] value) => ArrayUtil.SetBitFlagArray(Data.AsSpan(region * SIZE_ENTRY), value); + public void SetArrayNewFlag(int region, bool[] value) => ArrayUtil.SetBitFlagArray(Data.AsSpan((region + REGIONS) * SIZE_ENTRY), value); + public void SetIndexesOwnedFlag(int region, int[] value) => SetArrayOwnedFlag(region, SetIndexes(value)); + public void SetIndexesNewFlag(int region, int[] value) => SetArrayNewFlag(region, SetIndexes(value)); + + public static int[] GetIndexes(bool[] arr) { - private const int SIZE_ENTRY = 0x80; - private const int REGIONS = 15; - - public FashionUnlock8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } - - public bool[] GetArrayOwned(int region) => ArrayUtil.GitBitFlagArray(Data.AsSpan(region * SIZE_ENTRY), SIZE_ENTRY * 8); - public bool[] GetArrayNew(int region) => ArrayUtil.GitBitFlagArray(Data.AsSpan((region + REGIONS) * SIZE_ENTRY), SIZE_ENTRY * 8); - public int[] GetIndexesOwned(int region) => GetIndexes(GetArrayOwned(region)); - public int[] GetIndexesNew(int region) => GetIndexes(GetArrayNew(region)); - - public void SetArrayOwned(int region, bool[] value) => ArrayUtil.SetBitFlagArray(Data.AsSpan(region * SIZE_ENTRY), value); - public void SetArrayNew(int region, bool[] value) => ArrayUtil.SetBitFlagArray(Data.AsSpan((region + REGIONS) * SIZE_ENTRY), value); - public void SetIndexesOwned(int region, int[] value) => SetArrayOwned(region, SetIndexes(value)); - public void SetIndexesNew(int region, int[] value) => SetArrayNew(region, SetIndexes(value)); - - public static int[] GetIndexes(bool[] arr) + var list = new List(); + for (int i = 0; i < arr.Length; i++) { - var list = new List(); - for (int i = 0; i < arr.Length; i++) - { - if (arr[i]) - list.Add(i); - } - return list.ToArray(); - } - - public static bool[] SetIndexes(int[] arr) - { - var result = new bool[arr.Max()]; - foreach (var index in arr) - result[index] = true; - return result; + if (arr[i]) + list.Add(i); } + return list.ToArray(); } -} \ No newline at end of file + + public static bool[] SetIndexes(int[] arr) + { + var max = GetMax(arr); + var result = new bool[max]; + foreach (var index in arr) + result[index] = true; + return result; + } + + private static int GetMax(int[] arr) + { + int max = -1; + foreach (var x in arr) + { + if (x > max) + max = x; + } + return max; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/FieldMoveModelSave8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/FieldMoveModelSave8.cs index 19a8ecc87..ae88b2dd9 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/FieldMoveModelSave8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/FieldMoveModelSave8.cs @@ -1,16 +1,15 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core -{ - public sealed class FieldMoveModelSave8 : SaveBlock - { - public FieldMoveModelSave8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } +namespace PKHeX.Core; - public int M { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } - public float X { get => ReadSingleLittleEndian(Data.AsSpan(0x08)); set => WriteSingleLittleEndian(Data.AsSpan(0x08), value); } - public float Z { get => ReadSingleLittleEndian(Data.AsSpan(0x10)); set => WriteSingleLittleEndian(Data.AsSpan(0x10), value); } - public float Y { get => (int)ReadSingleLittleEndian(Data.AsSpan(0x18)); set => WriteSingleLittleEndian(Data.AsSpan(0x18), value); } - public float R { get => (int)ReadSingleLittleEndian(Data.AsSpan(0x20)); set => WriteSingleLittleEndian(Data.AsSpan(0x20), value); } - } -} \ No newline at end of file +public sealed class FieldMoveModelSave8 : SaveBlock +{ + public FieldMoveModelSave8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + + public int M { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), (ushort)value); } + public float X { get => ReadSingleLittleEndian(Data.AsSpan(0x08)); set => WriteSingleLittleEndian(Data.AsSpan(0x08), value); } + public float Z { get => ReadSingleLittleEndian(Data.AsSpan(0x10)); set => WriteSingleLittleEndian(Data.AsSpan(0x10), value); } + public float Y { get => (int)ReadSingleLittleEndian(Data.AsSpan(0x18)); set => WriteSingleLittleEndian(Data.AsSpan(0x18), value); } + public float R { get => (int)ReadSingleLittleEndian(Data.AsSpan(0x20)); set => WriteSingleLittleEndian(Data.AsSpan(0x20), value); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Fused8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Fused8.cs index 7c38493bc..70016f9ae 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Fused8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Fused8.cs @@ -1,35 +1,34 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Storage for the species that was fused into and . +/// +public sealed class Fused8 : SaveBlock { - /// - /// Storage for the species that was fused into and . - /// - public sealed class Fused8 : SaveBlock + public Fused8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + + public static int GetFusedSlotOffset(int slot) { - public Fused8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + if ((uint)slot >= 3) + return -1; + return PokeCrypto.SIZE_8PARTY * slot; + } - public static int GetFusedSlotOffset(int slot) - { - if ((uint)slot >= 3) - return -1; - return PokeCrypto.SIZE_8PARTY * slot; - } + public PK8 Kyurem + { + get => (PK8) SAV.GetStoredSlot(Data, GetFusedSlotOffset(0)); + set => value.EncryptedBoxData.CopyTo(Data, GetFusedSlotOffset(0)); + } - public PK8 Kyurem - { - get => (PK8) SAV.GetStoredSlot(Data, GetFusedSlotOffset(0)); - set => value.EncryptedBoxData.CopyTo(Data, GetFusedSlotOffset(0)); - } + public PK8 NecrozmaSolgaleo + { + get => (PK8)SAV.GetStoredSlot(Data, GetFusedSlotOffset(1)); + set => value.EncryptedBoxData.CopyTo(Data, GetFusedSlotOffset(1)); + } - public PK8 NecrozmaSolgaleo - { - get => (PK8)SAV.GetStoredSlot(Data, GetFusedSlotOffset(1)); - set => value.EncryptedBoxData.CopyTo(Data, GetFusedSlotOffset(1)); - } - - public PK8 NecrozmaLunala - { - get => (PK8)SAV.GetStoredSlot(Data, GetFusedSlotOffset(2)); - set => value.EncryptedBoxData.CopyTo(Data, GetFusedSlotOffset(2)); - } + public PK8 NecrozmaLunala + { + get => (PK8)SAV.GetStoredSlot(Data, GetFusedSlotOffset(2)); + set => value.EncryptedBoxData.CopyTo(Data, GetFusedSlotOffset(2)); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/HallOfFameTime8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/HallOfFameTime8.cs index 628d78338..d2ac328b2 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/HallOfFameTime8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/HallOfFameTime8.cs @@ -3,39 +3,38 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class HallOfFameTime8 : SaveBlock { - public sealed class HallOfFameTime8 : SaveBlock + public HallOfFameTime8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { - public HallOfFameTime8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) + Debug.Assert(Data.Length == 8); + } + + private const string General = nameof(General); + + [Category(General), Description("Raw amount of seconds since 1970 (Unix Timestamp)")] + public long TimeStamp + { + get => ReadInt64LittleEndian(Data.AsSpan(Offset + 0)); + set => WriteInt64LittleEndian(Data.AsSpan(Offset), value); + } + + [Category(General), Description("Date and Time (UTC) the player entered the Hall Of Fame")] + public DateTime Date + { + get { - Debug.Assert(Data.Length == 8); + var epoch = new DateTime(1970, 1, 1); + var date = epoch.AddSeconds(TimeStamp); + return date; } - - private const string General = nameof(General); - - [Category(General), Description("Raw amount of seconds since 1970 (Unix Timestamp)")] - public long TimeStamp + set { - get => ReadInt64LittleEndian(Data.AsSpan(Offset + 0)); - set => WriteInt64LittleEndian(Data.AsSpan(Offset), value); - } - - [Category(General), Description("Date and Time (UTC) the player entered the Hall Of Fame")] - public DateTime Date - { - get - { - var epoch = new DateTime(1970, 1, 1); - var date = epoch.AddSeconds(TimeStamp); - return date; - } - set - { - var epoch = new DateTime(1970, 1, 1); - var delta = value.Subtract(epoch); - TimeStamp = (long)delta.TotalSeconds; - } + var epoch = new DateTime(1970, 1, 1); + var delta = value.Subtract(epoch); + TimeStamp = (long)delta.TotalSeconds; } } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Misc8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Misc8.cs index 93bfbd073..ad45a5014 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Misc8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Misc8.cs @@ -1,33 +1,32 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Misc8 : SaveBlock { - public sealed class Misc8 : SaveBlock + public Misc8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + + public int Badges { - public Misc8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + get => Data[Offset + 0x00]; + set => Data[Offset + 0x00] = (byte)value; + } - public int Badges + public uint Money + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x04)); + set { - get => Data[Offset + 0x00]; - set => Data[Offset + 0x00] = (byte)value; - } - - public uint Money - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x04)); - set - { - if (value > 9999999) - value = 9999999; - WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); - } - } - - public int BP - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x11C)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x11C), (ushort)value); + if (value > 9999999) + value = 9999999; + WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); } } -} \ No newline at end of file + + public int BP + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x11C)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x11C), (ushort)value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/MyItem8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/MyItem8.cs index 4b695742b..1ad4d0bec 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/MyItem8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/MyItem8.cs @@ -1,55 +1,54 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyItem8 : MyItem { - public sealed class MyItem8 : MyItem + public const int Medicine = 0; + public const int Balls = Medicine + (4 * PouchSize8.Medicine); + public const int Battle = Balls + (4 * PouchSize8.Balls); + public const int Berries = Battle + (4 * PouchSize8.Battle); + public const int Items = Berries + (4 * PouchSize8.Berries); + public const int TMs = Items + (4 * PouchSize8.Items); + public const int Treasures = TMs + (4 * PouchSize8.TMs); + public const int Ingredients = Treasures + (4 * PouchSize8.Treasures); + public const int Key = Ingredients + (4 * PouchSize8.Ingredients); + + public MyItem8(SaveFile SAV, SCBlock block) : base(SAV, block.Data) { } + + public override IReadOnlyList Inventory { - public const int Medicine = 0; - public const int Balls = Medicine + (4 * PouchSize8.Medicine); - public const int Battle = Balls + (4 * PouchSize8.Balls); - public const int Berries = Battle + (4 * PouchSize8.Battle); - public const int Items = Berries + (4 * PouchSize8.Berries); - public const int TMs = Items + (4 * PouchSize8.Items); - public const int Treasures = TMs + (4 * PouchSize8.TMs); - public const int Ingredients = Treasures + (4 * PouchSize8.Treasures); - public const int Key = Ingredients + (4 * PouchSize8.Ingredients); - - public MyItem8(SaveFile SAV, SCBlock block) : base(SAV, block.Data) { } - - public override IReadOnlyList Inventory + get { - get + var pouch = new InventoryPouch[] { - var pouch = new InventoryPouch[] - { - new InventoryPouch8(InventoryType.Medicine, Legal.Pouch_Medicine_SWSH, 999, Medicine, PouchSize8.Medicine, IsItemLegal), - new InventoryPouch8(InventoryType.Balls, Legal.Pouch_Ball_SWSH, 999, Balls, PouchSize8.Balls, IsItemLegal), - new InventoryPouch8(InventoryType.BattleItems, Legal.Pouch_Battle_SWSH, 999, Battle, PouchSize8.Battle, IsItemLegal), - new InventoryPouch8(InventoryType.Berries, Legal.Pouch_Berries_SWSH, 999, Berries, PouchSize8.Berries, IsItemLegal), - new InventoryPouch8(InventoryType.Items, Legal.Pouch_Regular_SWSH, 999, Items, PouchSize8.Items, IsItemLegal), - new InventoryPouch8(InventoryType.TMHMs, Legal.Pouch_TMHM_SWSH, 999, TMs, PouchSize8.TMs, IsItemLegal), - new InventoryPouch8(InventoryType.MailItems, Legal.Pouch_Treasure_SWSH, 999, Treasures, PouchSize8.Treasures, IsItemLegal), - new InventoryPouch8(InventoryType.Candy, Legal.Pouch_Ingredients_SWSH, 999, Ingredients, PouchSize8.Ingredients, IsItemLegal), - new InventoryPouch8(InventoryType.KeyItems, Legal.Pouch_Key_SWSH, 1, Key, PouchSize8.Key), - }; - return pouch.LoadAll(Data); - } - set - { - foreach (var p in value) - ((InventoryPouch8)p).SanitizeCounts(); - value.SaveAll(Data); - } + new InventoryPouch8(InventoryType.Medicine, Legal.Pouch_Medicine_SWSH, 999, Medicine, PouchSize8.Medicine, IsItemLegal), + new InventoryPouch8(InventoryType.Balls, Legal.Pouch_Ball_SWSH, 999, Balls, PouchSize8.Balls, IsItemLegal), + new InventoryPouch8(InventoryType.BattleItems, Legal.Pouch_Battle_SWSH, 999, Battle, PouchSize8.Battle, IsItemLegal), + new InventoryPouch8(InventoryType.Berries, Legal.Pouch_Berries_SWSH, 999, Berries, PouchSize8.Berries, IsItemLegal), + new InventoryPouch8(InventoryType.Items, Legal.Pouch_Regular_SWSH, 999, Items, PouchSize8.Items, IsItemLegal), + new InventoryPouch8(InventoryType.TMHMs, Legal.Pouch_TMHM_SWSH, 999, TMs, PouchSize8.TMs, IsItemLegal), + new InventoryPouch8(InventoryType.MailItems, Legal.Pouch_Treasure_SWSH, 999, Treasures, PouchSize8.Treasures, IsItemLegal), + new InventoryPouch8(InventoryType.Candy, Legal.Pouch_Ingredients_SWSH, 999, Ingredients, PouchSize8.Ingredients, IsItemLegal), + new InventoryPouch8(InventoryType.KeyItems, Legal.Pouch_Key_SWSH, 1, Key, PouchSize8.Key), + }; + return pouch.LoadAll(Data); } - - public static bool IsItemLegal(ushort item) + set { - if (Legal.IsDynamaxCrystal(item)) - return Legal.IsDynamaxCrystalAvailable(item); - if (!Legal.HeldItems_SWSH.Contains(item)) - return true; - return Legal.ReleasedHeldItems_8[item]; + foreach (var p in value) + ((InventoryPouch8)p).SanitizeCounts(); + value.SaveAll(Data); } } + + public static bool IsItemLegal(ushort item) + { + if (Legal.IsDynamaxCrystal(item)) + return Legal.IsDynamaxCrystalAvailable(item); + if (!Legal.HeldItems_SWSH.Contains(item)) + return true; + return Legal.ReleasedHeldItems_8[item]; + } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/MyStatus8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/MyStatus8.cs index 892817ba3..54d945e24 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/MyStatus8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/MyStatus8.cs @@ -2,184 +2,183 @@ using System.Text; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class MyStatus8 : SaveBlock { - public sealed class MyStatus8 : SaveBlock + public const uint MaxWatt = 9999999; + + public MyStatus8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + + public string Number { - public const uint MaxWatt = 9999999; - - public MyStatus8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } - - public string Number + get => Encoding.ASCII.GetString(Data, 0x01, 3); + set { - get => Encoding.ASCII.GetString(Data, 0x01, 3); - set - { - for (int i = 0; i < 3; i++) - Data[0x01 + i] = (byte)(value.Length > i ? value[i] : '\0'); - SAV.State.Edited = true; - } + for (int i = 0; i < 3; i++) + Data[0x01 + i] = (byte)(value.Length > i ? value[i] : '\0'); + SAV.State.Edited = true; } + } - public ulong Skin // aka the base model + public ulong Skin // aka the base model + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x08)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x08), value); + } + + public ulong Hair + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x10)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x10), value); + } + + public ulong Brow + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x18)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x18), value); + } + + public ulong Lashes + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x20)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x20), value); + } + + public ulong Contacts + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x28)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x28), value); + } + + public ulong Lips + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x30)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x30), value); + } + + public ulong Glasses + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x38)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x38), value); + } + + public ulong Hat + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x40)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x40), value); + } + + public ulong Jacket + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x48)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x48), value); + } + + public ulong Top + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x50)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x50), value); + } + + public ulong Bag + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x58)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x58), value); + } + + public ulong Gloves + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x60)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x60), value); + } + + public ulong BottomOrDress + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x68)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x68), value); + } + + public ulong Sock + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x70)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x70), value); + } + + public ulong Shoe + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x78)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x78), value); + } + + // 80 - 87 + + public ulong MomSkin // aka the base model + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x88)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x88), value); + } + + // 8C - 9F + + public int TID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0xA0)); + set => WriteUInt16LittleEndian(Data.AsSpan(0xA0), (ushort)value); + } + + public int SID + { + get => ReadUInt16LittleEndian(Data.AsSpan(0xA2)); + set => WriteUInt16LittleEndian(Data.AsSpan(0xA2), (ushort)value); + } + + public int Game + { + get => Data[0xA4]; + set => Data[0xA4] = (byte)value; + } + + public int Gender + { + get => Data[0xA5]; + set => Data[0xA5] = (byte)value; + } + + // A6 + public int Language + { + get => Data[Offset + 0xA7]; + set { - get => ReadUInt64LittleEndian(Data.AsSpan(0x08)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x08), value); + if (value == Language) + return; + Data[Offset + 0xA7] = (byte) value; + + // For runtime language, the game shifts all languages above Language 6 (unused) down one. + if (value >= 6) + value--; + SAV.SetValue(SaveBlockAccessor8SWSH.KGameLanguage, (uint)value); } + } - public ulong Hair + private Span OT_Trash => Data.AsSpan(0xB0, 0x1A); + + public string OT + { + get => SAV.GetString(OT_Trash); + set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); + } + + // D0 + public uint Watt + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xD0)); + set { - get => ReadUInt64LittleEndian(Data.AsSpan(0x10)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x10), value); - } - - public ulong Brow - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x18)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x18), value); - } - - public ulong Lashes - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x20)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x20), value); - } - - public ulong Contacts - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x28)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x28), value); - } - - public ulong Lips - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x30)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x30), value); - } - - public ulong Glasses - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x38)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x38), value); - } - - public ulong Hat - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x40)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x40), value); - } - - public ulong Jacket - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x48)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x48), value); - } - - public ulong Top - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x50)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x50), value); - } - - public ulong Bag - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x58)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x58), value); - } - - public ulong Gloves - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x60)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x60), value); - } - - public ulong BottomOrDress - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x68)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x68), value); - } - - public ulong Sock - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x70)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x70), value); - } - - public ulong Shoe - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x78)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x78), value); - } - - // 80 - 87 - - public ulong MomSkin // aka the base model - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x88)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x88), value); - } - - // 8C - 9F - - public int TID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0xA0)); - set => WriteUInt16LittleEndian(Data.AsSpan(0xA0), (ushort)value); - } - - public int SID - { - get => ReadUInt16LittleEndian(Data.AsSpan(0xA2)); - set => WriteUInt16LittleEndian(Data.AsSpan(0xA2), (ushort)value); - } - - public int Game - { - get => Data[0xA4]; - set => Data[0xA4] = (byte)value; - } - - public int Gender - { - get => Data[0xA5]; - set => Data[0xA5] = (byte)value; - } - - // A6 - public int Language - { - get => Data[Offset + 0xA7]; - set - { - if (value == Language) - return; - Data[Offset + 0xA7] = (byte) value; - - // For runtime language, the game shifts all languages above Language 6 (unused) down one. - if (value >= 6) - value--; - SAV.SetValue(SaveBlockAccessor8SWSH.KGameLanguage, (uint)value); - } - } - - private Span OT_Trash => Data.AsSpan(0xB0, 0x1A); - - public string OT - { - get => SAV.GetString(OT_Trash); - set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); - } - - // D0 - public uint Watt - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0xD0)); - set - { - if (value > MaxWatt) - value = MaxWatt; - WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xD0), value); - } + if (value > MaxWatt) + value = MaxWatt; + WriteUInt32LittleEndian(Data.AsSpan(Offset + 0xD0), value); } } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Party8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Party8.cs index aa61648e1..882208fe0 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Party8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Party8.cs @@ -1,13 +1,12 @@ -namespace PKHeX.Core -{ - public sealed class Party8 : SaveBlock - { - public Party8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } +namespace PKHeX.Core; - public int PartyCount - { - get => Data[6 * PokeCrypto.SIZE_8PARTY]; - set => Data[6 * PokeCrypto.SIZE_8PARTY] = (byte)value; - } +public sealed class Party8 : SaveBlock +{ + public Party8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + + public int PartyCount + { + get => Data[6 * PokeCrypto.SIZE_8PARTY]; + set => Data[6 * PokeCrypto.SIZE_8PARTY] = (byte)value; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/PlayTime8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/PlayTime8.cs index b34d0d28b..e179522a2 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/PlayTime8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/PlayTime8.cs @@ -1,66 +1,65 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class PlayTime8 : SaveBlock { - public sealed class PlayTime8 : SaveBlock + public PlayTime8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + + public int PlayedHours { - public PlayTime8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + get => ReadUInt16LittleEndian(Data.AsSpan(Offset)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset), (ushort)value); + } - public int PlayedHours + public int PlayedMinutes + { + get => Data[Offset + 2]; + set => Data[Offset + 2] = (byte)value; + } + + public int PlayedSeconds + { + get => Data[Offset + 3]; + set => Data[Offset + 3] = (byte)value; + } + + // TODO + private uint LastSaved { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } + private int LastSavedYear { get => (int)(LastSaved & 0xFFF) + 1900; set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)(value - 1900); } + private int LastSavedMonth { get => (int)((LastSaved >> 12) & 0xF) + 1; set => LastSaved = (LastSaved & 0xFFFF0FFF) | (((uint)(value - 1) & 0xF) << 12); } + private int LastSavedDay { get => (int)((LastSaved >> 16) & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | (((uint)value & 0x1F) << 16); } + private int LastSavedHour { get => (int)((LastSaved >> 21) & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | (((uint)value & 0x1F) << 21); } + private int LastSavedMinute { get => (int)((LastSaved >> 26) & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | (((uint)value & 0x3F) << 26); } + public string LastSavedTime => $"{LastSavedYear:0000}-{LastSavedMonth:00}-{LastSavedDay:00} {LastSavedHour:00}ː{LastSavedMinute:00}"; // not : + + public DateTime? LastSavedDate + { + get => !DateUtil.IsDateValid(LastSavedYear, LastSavedMonth, LastSavedDay) + ? null + : new DateTime(LastSavedYear, LastSavedMonth, LastSavedDay, LastSavedHour, LastSavedMinute, 0); + set { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset), (ushort)value); - } - - public int PlayedMinutes - { - get => Data[Offset + 2]; - set => Data[Offset + 2] = (byte)value; - } - - public int PlayedSeconds - { - get => Data[Offset + 3]; - set => Data[Offset + 3] = (byte)value; - } - - // TODO - private uint LastSaved { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x4)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x4), value); } - private int LastSavedYear { get => (int)(LastSaved & 0xFFF) + 1900; set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)(value - 1900); } - private int LastSavedMonth { get => (int)(LastSaved >> 12 & 0xF) + 1; set => LastSaved = (LastSaved & 0xFFFF0FFF) | ((uint)(value - 1) & 0xF) << 12; } - private int LastSavedDay { get => (int)(LastSaved >> 16 & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | ((uint)value & 0x1F) << 16; } - private int LastSavedHour { get => (int)(LastSaved >> 21 & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | ((uint)value & 0x1F) << 21; } - private int LastSavedMinute { get => (int)(LastSaved >> 26 & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | ((uint)value & 0x3F) << 26; } - public string LastSavedTime => $"{LastSavedYear:0000}-{LastSavedMonth:00}-{LastSavedDay:00} {LastSavedHour:00}ː{LastSavedMinute:00}"; // not : - - public DateTime? LastSavedDate - { - get => !DateUtil.IsDateValid(LastSavedYear, LastSavedMonth, LastSavedDay) - ? null - : new DateTime(LastSavedYear, LastSavedMonth, LastSavedDay, LastSavedHour, LastSavedMinute, 0); - set + // Only update the properties if a value is provided. + if (value.HasValue) { - // Only update the properties if a value is provided. - if (value.HasValue) - { - var dt = value.Value; - LastSavedYear = dt.Year; - LastSavedMonth = dt.Month; - LastSavedDay = dt.Day; - LastSavedHour = dt.Hour; - LastSavedMinute = dt.Minute; - } - else // Clear the date. - { - // If code tries to access MetDate again, null will be returned. - LastSavedYear = 0; - LastSavedMonth = 0; - LastSavedDay = 0; - LastSavedHour = 0; - LastSavedMinute = 0; - } + var dt = value.Value; + LastSavedYear = dt.Year; + LastSavedMonth = dt.Month; + LastSavedDay = dt.Day; + LastSavedHour = dt.Hour; + LastSavedMinute = dt.Minute; + } + else // Clear the date. + { + // If code tries to access MetDate again, null will be returned. + LastSavedYear = 0; + LastSavedMonth = 0; + LastSavedDay = 0; + LastSavedHour = 0; + LastSavedMinute = 0; } } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/PouchSize8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/PouchSize8.cs index bd72a7f27..92cb3ff10 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/PouchSize8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/PouchSize8.cs @@ -1,53 +1,52 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains information pertaining to Inventory Pouch capacity in Generation 8. +/// +public static class PouchSize8 { /// - /// Contains information pertaining to Inventory Pouch capacity in Generation 8. + /// Pouch0 Item Max Capacity /// - public static class PouchSize8 - { - /// - /// Pouch0 Item Max Capacity - /// - public const int Medicine = 60; + public const int Medicine = 60; - /// - /// Pouch1 Item Max Capacity - /// - public const int Balls = 30; + /// + /// Pouch1 Item Max Capacity + /// + public const int Balls = 30; - /// - /// Pouch2 Item Max Capacity - /// - public const int Battle = 20; + /// + /// Pouch2 Item Max Capacity + /// + public const int Battle = 20; - /// - /// Pouch3 Item Max Capacity - /// - public const int Berries = 80; + /// + /// Pouch3 Item Max Capacity + /// + public const int Berries = 80; - /// - /// Pouch4 Item Max Capacity - /// - public const int Items = 550; + /// + /// Pouch4 Item Max Capacity + /// + public const int Items = 550; - /// - /// Pouch5 Item Max Capacity - /// - public const int TMs = 210; + /// + /// Pouch5 Item Max Capacity + /// + public const int TMs = 210; - /// - /// Pouch5 Item Max Capacity - /// - public const int Treasures = 100; + /// + /// Pouch5 Item Max Capacity + /// + public const int Treasures = 100; - /// - /// Pouch5 Item Max Capacity - /// - public const int Ingredients = 100; + /// + /// Pouch5 Item Max Capacity + /// + public const int Ingredients = 100; - /// - /// Pouch5 Item Max Capacity - /// - public const int Key = 64; - } -} \ No newline at end of file + /// + /// Pouch5 Item Max Capacity + /// + public const int Key = 64; +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/RaidSpawnList8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/RaidSpawnList8.cs index 8ede06f71..ddc09d6b4 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/RaidSpawnList8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/RaidSpawnList8.cs @@ -2,231 +2,230 @@ using System.ComponentModel; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class RaidSpawnList8 : SaveBlock { - public sealed class RaidSpawnList8 : SaveBlock + public readonly int CountAll; + public readonly int CountUsed; + + public RaidSpawnList8(SAV8SWSH sav, SCBlock block, int legal) : base(sav, block.Data) { - public readonly int CountAll; - public readonly int CountUsed; + CountAll = block.Data.Length / RaidSpawnDetail.SIZE; + CountUsed = legal; + } - public RaidSpawnList8(SAV8SWSH sav, SCBlock block, int legal) : base(sav, block.Data) + public const int RaidCountLegal_O0 = 100; + public const int RaidCountLegal_R1 = 90; + public const int RaidCountLegal_R2 = 86; + + public RaidSpawnDetail GetRaid(int entry) => new(Data, entry * RaidSpawnDetail.SIZE); + + public RaidSpawnDetail[] GetAllRaids() + { + var result = new RaidSpawnDetail[CountAll]; + for (int i = 0; i < result.Length; i++) + result[i] = GetRaid(i); + return result; + } + + public void DectivateAllRaids() + { + for (int i = 0; i < CountUsed; i++) { - CountAll = block.Data.Length / RaidSpawnDetail.SIZE; - CountUsed = legal; - } - - public const int RaidCountLegal_O0 = 100; - public const int RaidCountLegal_R1 = 90; - public const int RaidCountLegal_R2 = 86; - - public RaidSpawnDetail GetRaid(int entry) => new(Data, entry * RaidSpawnDetail.SIZE); - - public RaidSpawnDetail[] GetAllRaids() - { - var result = new RaidSpawnDetail[CountAll]; - for (int i = 0; i < result.Length; i++) - result[i] = GetRaid(i); - return result; - } - - public void DectivateAllRaids() - { - for (int i = 0; i < CountUsed; i++) - { - if (i == 16) // Watchtower, special - continue; - GetRaid(i).Deactivate(); - } - } - - public void ActivateAllRaids(bool rare, bool isEvent) - { - var rnd = Util.Rand; - for (int i = 0; i < CountUsed; i++) - { - if (i == 16) // Watchtower, special - continue; - var star = (byte)rnd.Next(0, 5); - var rand = (byte)rnd.Next(1, 101); - GetRaid(i).Activate(star, rand, rare, isEvent); - } - } - - public string[] DumpAll() - { - var raids = GetAllRaids(); - var result = new string[CountAll]; - for (int i = 0; i < result.Length; i++) - result[i] = raids[i].Dump(); - return result; + if (i == 16) // Watchtower, special + continue; + GetRaid(i).Deactivate(); } } - public sealed class RaidSpawnDetail + public void ActivateAllRaids(bool rare, bool isEvent) { - public const int SIZE = 0x18; - - private readonly byte[] Data; - private readonly int Offset; - - public RaidSpawnDetail(byte[] data, int ofs) + var rnd = Util.Rand; + for (int i = 0; i < CountUsed; i++) { - Data = data; - Offset = ofs; + if (i == 16) // Watchtower, special + continue; + var star = (byte)rnd.Next(0, 5); + var rand = (byte)rnd.Next(1, 101); + GetRaid(i).Activate(star, rand, rare, isEvent); } - - private const string General = nameof(General); - private const string Derived = nameof(Derived); - - [Category(General), Description("FNV Hash for fetching the Raid data table (64bit)."), TypeConverter(typeof(TypeConverterU64))] - public ulong Hash - { - get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0)); - set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0), value); - } - - [Category(General), Description("RNG Seed for generating the Raid's content (64bit)."), TypeConverter(typeof(TypeConverterU64))] - public ulong Seed - { - get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 8)); - set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 8), value); - } - - [Category(General), Description("Star Count for the Raid's content (0-4).")] - public byte Stars - { - get => Data[Offset + 0x10]; - set => Data[Offset + 0x10] = value; - } - - [Category(General), Description("Random value which picks out the encounter from the Raid data table (1-100).")] - public byte RandRoll - { - get => Data[Offset + 0x11]; - set => Data[Offset + 0x11] = value; - } - - [Category(General), Description("First set of Den Flags.")] - public RaidType DenType - { - get => (RaidType)Data[Offset + 0x12]; - set - { - Data[Offset + 0x12] = (byte)value; - if (value == RaidType.Event) - { - IsEvent = true; - } - else if (value != RaidType.CommonWish) - { - IsEvent = false; - } - } - } - - [Category(General), Description("Second set of Den Flags.")] - public byte Flags - { - get => Data[Offset + 0x13]; - set => Data[Offset + 0x13] = value; - } - - [Category(Derived), Description("Active Nest")] - public bool IsActive => DenType > 0; - - [Category(Derived), Description("Rare encounter details used instead of Common details.")] - public bool IsRare - { - get => DenType is RaidType.Rare or RaidType.RareWish; - set - { - if (value) - { - DenType = IsWishingPiece ? RaidType.RareWish : RaidType.Rare; - } - else - { - DenType = IsWishingPiece ? RaidType.CommonWish : RaidType.Common; - } - } - } - - [Category(Derived), Description("Wishing Piece was used for Raid encounter.")] - public bool IsWishingPiece - { - get => DenType is RaidType.CommonWish or RaidType.RareWish; - set - { - if (value) - { - DenType = IsRare ? RaidType.RareWish : RaidType.CommonWish; - } - else - { - DenType = IsRare ? RaidType.Rare : RaidType.Common; - } - } - } - - [Category(Derived), Description("Has watts already been harvested.")] - public bool WattsHarvested - { - get => IsActive && (Flags & 1) == 1; - set => Flags = (byte)((Flags & ~1) | (value ? 1 : 0)); - } - - [Category(Derived), Description("Distribution (event) details used for Raid encounter.")] - public bool IsEvent - { - get => IsActive && (Flags & 2) == 2; - set - { - Flags = (byte)((Flags & ~2) | (value ? 2 : 0)); - if (value) - { - if (DenType != RaidType.CommonWish && DenType != RaidType.Event) - { - DenType = RaidType.Event; - } - } - else - { - if (DenType == RaidType.Event) - { - DenType = RaidType.Common; - } - } - } - } - - public void Activate(byte star, byte rand, bool rare = false, bool isEvent = false) - { - Stars = star; - RandRoll = rand; - IsRare = rare; - IsEvent = isEvent; - } - - public void Deactivate() - { - DenType = RaidType.None; - Stars = 0; - RandRoll = 0; - } - - public string Dump() => $"{Hash:X16}\t{Seed:X16}\t{Stars}\t{RandRoll:00}\t{DenType}\t{Flags:X2}"; - - // The games use a xoroshiro RNG to create the PKM from the stored seed. } - public enum RaidType : byte + public string[] DumpAll() { - None = 0, - Common = 1, - Rare = 2, - CommonWish = 3, - RareWish = 4, - Event = 5, - DynamaxCrystal = 6, + var raids = GetAllRaids(); + var result = new string[CountAll]; + for (int i = 0; i < result.Length; i++) + result[i] = raids[i].Dump(); + return result; } } + +public sealed class RaidSpawnDetail +{ + public const int SIZE = 0x18; + + private readonly byte[] Data; + private readonly int Offset; + + public RaidSpawnDetail(byte[] data, int ofs) + { + Data = data; + Offset = ofs; + } + + private const string General = nameof(General); + private const string Derived = nameof(Derived); + + [Category(General), Description("FNV Hash for fetching the Raid data table (64bit)."), TypeConverter(typeof(TypeConverterU64))] + public ulong Hash + { + get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0)); + set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0), value); + } + + [Category(General), Description("RNG Seed for generating the Raid's content (64bit)."), TypeConverter(typeof(TypeConverterU64))] + public ulong Seed + { + get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 8)); + set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 8), value); + } + + [Category(General), Description("Star Count for the Raid's content (0-4).")] + public byte Stars + { + get => Data[Offset + 0x10]; + set => Data[Offset + 0x10] = value; + } + + [Category(General), Description("Random value which picks out the encounter from the Raid data table (1-100).")] + public byte RandRoll + { + get => Data[Offset + 0x11]; + set => Data[Offset + 0x11] = value; + } + + [Category(General), Description("First set of Den Flags.")] + public RaidType DenType + { + get => (RaidType)Data[Offset + 0x12]; + set + { + Data[Offset + 0x12] = (byte)value; + if (value == RaidType.Event) + { + IsEvent = true; + } + else if (value != RaidType.CommonWish) + { + IsEvent = false; + } + } + } + + [Category(General), Description("Second set of Den Flags.")] + public byte Flags + { + get => Data[Offset + 0x13]; + set => Data[Offset + 0x13] = value; + } + + [Category(Derived), Description("Active Nest")] + public bool IsActive => DenType > 0; + + [Category(Derived), Description("Rare encounter details used instead of Common details.")] + public bool IsRare + { + get => DenType is RaidType.Rare or RaidType.RareWish; + set + { + if (value) + { + DenType = IsWishingPiece ? RaidType.RareWish : RaidType.Rare; + } + else + { + DenType = IsWishingPiece ? RaidType.CommonWish : RaidType.Common; + } + } + } + + [Category(Derived), Description("Wishing Piece was used for Raid encounter.")] + public bool IsWishingPiece + { + get => DenType is RaidType.CommonWish or RaidType.RareWish; + set + { + if (value) + { + DenType = IsRare ? RaidType.RareWish : RaidType.CommonWish; + } + else + { + DenType = IsRare ? RaidType.Rare : RaidType.Common; + } + } + } + + [Category(Derived), Description("Has watts already been harvested.")] + public bool WattsHarvested + { + get => IsActive && (Flags & 1) == 1; + set => Flags = (byte)((Flags & ~1) | (value ? 1 : 0)); + } + + [Category(Derived), Description("Distribution (event) details used for Raid encounter.")] + public bool IsEvent + { + get => IsActive && (Flags & 2) == 2; + set + { + Flags = (byte)((Flags & ~2) | (value ? 2 : 0)); + if (value) + { + if (DenType != RaidType.CommonWish && DenType != RaidType.Event) + { + DenType = RaidType.Event; + } + } + else + { + if (DenType == RaidType.Event) + { + DenType = RaidType.Common; + } + } + } + } + + public void Activate(byte star, byte rand, bool rare = false, bool isEvent = false) + { + Stars = star; + RandRoll = rand; + IsRare = rare; + IsEvent = isEvent; + } + + public void Deactivate() + { + DenType = RaidType.None; + Stars = 0; + RandRoll = 0; + } + + public string Dump() => $"{Hash:X16}\t{Seed:X16}\t{Stars}\t{RandRoll:00}\t{DenType}\t{Flags:X2}"; + + // The games use a xoroshiro RNG to create the PKM from the stored seed. +} + +public enum RaidType : byte +{ + None = 0, + Common = 1, + Rare = 2, + CommonWish = 3, + RareWish = 4, + Event = 5, + DynamaxCrystal = 6, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Record8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Record8.cs index 04412268e..35b598008 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Record8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/Record8.cs @@ -3,38 +3,37 @@ using System.Diagnostics; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Record8 : RecordBlock { - public sealed class Record8 : RecordBlock + public const int RecordCount = 50; + public const int WattTotal = 22; + protected override IReadOnlyList RecordMax { get; } + + public Record8(SAV8SWSH sav, SCBlock block, IReadOnlyList maxes) : base(sav, block.Data) => + RecordMax = maxes; + + public override int GetRecord(int recordID) { - public const int RecordCount = 50; - public const int WattTotal = 22; - protected override IReadOnlyList RecordMax { get; } - - public Record8(SAV8SWSH sav, SCBlock block, IReadOnlyList maxes) : base(sav, block.Data) => - RecordMax = maxes; - - public override int GetRecord(int recordID) - { - int ofs = Records.GetOffset(Offset, recordID); - if (recordID < RecordCount) - return ReadInt32LittleEndian(Data.AsSpan(ofs)); - Trace.Fail(nameof(recordID)); - return 0; - } - - public override void SetRecord(int recordID, int value) - { - if ((uint)recordID >= RecordCount) - throw new ArgumentOutOfRangeException(nameof(recordID)); - int ofs = GetRecordOffset(recordID); - int max = GetRecordMax(recordID); - if (value > max) - value = max; - if (recordID < RecordCount) - WriteInt32LittleEndian(Data.AsSpan(ofs), value); - else - Trace.Fail(nameof(recordID)); - } + int ofs = Records.GetOffset(Offset, recordID); + if (recordID < RecordCount) + return ReadInt32LittleEndian(Data.AsSpan(ofs)); + Trace.Fail(nameof(recordID)); + return 0; } -} \ No newline at end of file + + public override void SetRecord(int recordID, int value) + { + if ((uint)recordID >= RecordCount) + throw new ArgumentOutOfRangeException(nameof(recordID)); + int ofs = GetRecordOffset(recordID); + int max = GetRecordMax(recordID); + if (value > max) + value = max; + if (recordID < RecordCount) + WriteInt32LittleEndian(Data.AsSpan(ofs), value); + else + Trace.Fail(nameof(recordID)); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/RentalTeam8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/RentalTeam8.cs index 5e8658176..be9b92305 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/RentalTeam8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/RentalTeam8.cs @@ -2,84 +2,83 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Container block for Generation 8 saved Rental Teams +/// +public sealed class RentalTeam8 : IRentalTeam, IPokeGroup { - /// - /// Container block for Generation 8 saved Rental Teams - /// - public sealed class RentalTeam8 : IRentalTeam, IPokeGroup + public const int SIZE = 0x880; + + private const int LEN_META = 0x56; + private const int LEN_STORED = PokeCrypto.SIZE_8STORED; // 0x148 + private const int LEN_POKE = PokeCrypto.SIZE_8PARTY; // 0x158 + private const int LEN_PARTYSTAT = LEN_POKE - PokeCrypto.SIZE_8STORED; // 0x10 + private const int COUNT_POKE = 6; + + private const int OFS_META = 0; + private const int OFS_1 = OFS_META + LEN_META; + private const int OFS_2 = OFS_1 + LEN_POKE; + private const int OFS_3 = OFS_2 + LEN_POKE; + private const int OFS_4 = OFS_3 + LEN_POKE; + private const int OFS_5 = OFS_4 + LEN_POKE; + private const int OFS_6 = OFS_5 + LEN_POKE; + private const int POST_META = OFS_6 + LEN_POKE; + + private readonly byte[] Data; + + public RentalTeam8(byte[] data) => Data = data; + + public PK8 GetSlot(int slot) { - public const int SIZE = 0x880; + var ofs = GetSlotOffset(slot); + var data = Data.Slice(ofs, LEN_POKE); + var pk8 = new PK8(data); + pk8.ResetPartyStats(); + return pk8; + } - private const int LEN_META = 0x56; - private const int LEN_STORED = PokeCrypto.SIZE_8STORED; // 0x148 - private const int LEN_POKE = PokeCrypto.SIZE_8PARTY; // 0x158 - private const int LEN_PARTYSTAT = LEN_POKE - PokeCrypto.SIZE_8STORED; // 0x10 - private const int COUNT_POKE = 6; + public void SetSlot(int slot, PK8 pk) + { + var ofs = GetSlotOffset(slot); + var data = pk.EncryptedPartyData; + // Wipe Party Stats + Array.Clear(data, LEN_STORED, LEN_PARTYSTAT); + data.CopyTo(Data, ofs); + } - private const int OFS_META = 0; - private const int OFS_1 = OFS_META + LEN_META; - private const int OFS_2 = OFS_1 + LEN_POKE; - private const int OFS_3 = OFS_2 + LEN_POKE; - private const int OFS_4 = OFS_3 + LEN_POKE; - private const int OFS_5 = OFS_4 + LEN_POKE; - private const int OFS_6 = OFS_5 + LEN_POKE; - private const int POST_META = OFS_6 + LEN_POKE; + public PK8[] GetTeam() + { + var team = new PK8[COUNT_POKE]; + for (int i = 0; i < team.Length; i++) + team[i] = GetSlot(i); + return team; + } - private readonly byte[] Data; + public void SetTeam(IReadOnlyList team) + { + for (int i = 0; i < team.Count; i++) + SetSlot(i, team[i]); + } - public RentalTeam8(byte[] data) => Data = data; + public static int GetSlotOffset(int slot) + { + if ((uint)slot >= COUNT_POKE) + throw new ArgumentOutOfRangeException(nameof(slot)); + return OFS_1 + (LEN_POKE * slot); + } - public PK8 GetSlot(int slot) - { - var ofs = GetSlotOffset(slot); - var data = Data.Slice(ofs, LEN_POKE); - var pk8 = new PK8(data); - pk8.ResetPartyStats(); - return pk8; - } + public Span GetMetadataStart() => Data.AsSpan(OFS_META, LEN_META); + public Span GetMetadataEnd() => Data.AsSpan(POST_META); - public void SetSlot(int slot, PK8 pkm) - { - var ofs = GetSlotOffset(slot); - var data = pkm.EncryptedPartyData; - // Wipe Party Stats - Array.Clear(data, LEN_STORED, LEN_PARTYSTAT); - data.CopyTo(Data, ofs); - } + public IEnumerable Contents => GetTeam(); - public PK8[] GetTeam() - { - var team = new PK8[COUNT_POKE]; - for (int i = 0; i < team.Length; i++) - team[i] = GetSlot(i); - return team; - } - - public void SetTeam(IReadOnlyList team) - { - for (int i = 0; i < team.Count; i++) - SetSlot(i, team[i]); - } - - public static int GetSlotOffset(int slot) - { - if ((uint)slot >= COUNT_POKE) - throw new ArgumentOutOfRangeException(nameof(slot)); - return OFS_1 + (LEN_POKE * slot); - } - - public Span GetMetadataStart() => Data.AsSpan(OFS_META, LEN_META); - public Span GetMetadataEnd() => Data.AsSpan(POST_META); - - public IEnumerable Contents => GetTeam(); - - public static bool IsRentalTeam(byte[] data) - { - if (data.Length != SIZE) - return false; - var team = new RentalTeam8(data).GetTeam(); - return team.All(x => x.ChecksumValid); - } + public static bool IsRentalTeam(byte[] data) + { + if (data.Length != SIZE) + return false; + var team = new RentalTeam8(data).GetTeam(); + return team.All(x => x.ChecksumValid); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TeamIndexes8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TeamIndexes8.cs index 03ae813bc..44dbbba35 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TeamIndexes8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TeamIndexes8.cs @@ -1,71 +1,70 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class TeamIndexes8 : SaveBlock, ITeamIndexSet { - public sealed class TeamIndexes8 : SaveBlock, ITeamIndexSet + private const int TeamCount = 6; + private const int NONE_SELECTED = -1; + public readonly int[] TeamSlots = new int[TeamCount * 6]; + + public TeamIndexes8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + + public void LoadBattleTeams() { - private const int TeamCount = 6; - private const int NONE_SELECTED = -1; - public readonly int[] TeamSlots = new int[TeamCount * 6]; - - public TeamIndexes8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } - - public void LoadBattleTeams() + if (!SAV.State.Exportable) { - if (!SAV.State.Exportable) - { - ClearBattleTeams(); - return; - } - - for (int i = 0; i < TeamCount * 6; i++) - { - short val = ReadInt16LittleEndian(Data.AsSpan(Offset + (i * 2))); - if (val < 0) - { - TeamSlots[i] = NONE_SELECTED; - continue; - } - - int box = val >> 8; - int slot = val & 0xFF; - int index = (SAV.BoxSlotCount * box) + slot; - TeamSlots[i] = index & 0xFFFF; - } + ClearBattleTeams(); + return; } - public void ClearBattleTeams() + for (int i = 0; i < TeamCount * 6; i++) { - for (int i = 0; i < TeamSlots.Length; i++) + short val = ReadInt16LittleEndian(Data.AsSpan(Offset + (i * 2))); + if (val < 0) + { TeamSlots[i] = NONE_SELECTED; - } - - public void UnlockAllTeams() - { - for (int i = 0; i < TeamCount; i++) - SetIsTeamLocked(i, false); - } - - public void SaveBattleTeams() - { - var span = Data.AsSpan(Offset); - for (int i = 0; i < TeamCount * 6; i++) - { - int index = TeamSlots[i]; - if (index < 0) - { - WriteInt16LittleEndian(span[(i * 2)..], (short)index); - continue; - } - - SAV.GetBoxSlotFromIndex(index, out var box, out var slot); - index = (box << 8) | slot; - WriteInt16LittleEndian(span[(i * 2)..], (short)index); + continue; } - } - public bool GetIsTeamLocked(int team) => true; - public void SetIsTeamLocked(int team, bool value) { } + int box = val >> 8; + int slot = val & 0xFF; + int index = (SAV.BoxSlotCount * box) + slot; + TeamSlots[i] = index & 0xFFFF; + } } + + public void ClearBattleTeams() + { + for (int i = 0; i < TeamSlots.Length; i++) + TeamSlots[i] = NONE_SELECTED; + } + + public void UnlockAllTeams() + { + for (int i = 0; i < TeamCount; i++) + SetIsTeamLocked(i, false); + } + + public void SaveBattleTeams() + { + var span = Data.AsSpan(Offset); + for (int i = 0; i < TeamCount * 6; i++) + { + int index = TeamSlots[i]; + if (index < 0) + { + WriteInt16LittleEndian(span[(i * 2)..], (short)index); + continue; + } + + SAV.GetBoxSlotFromIndex(index, out var box, out var slot); + index = (box << 8) | slot; + WriteInt16LittleEndian(span[(i * 2)..], (short)index); + } + } + + public bool GetIsTeamLocked(int team) => true; + public void SetIsTeamLocked(int team, bool value) { } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TitleScreen8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TitleScreen8.cs index 795113edf..27950c0c5 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TitleScreen8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TitleScreen8.cs @@ -2,131 +2,130 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokémon Team that shows up at the title screen. +/// +public sealed class TitleScreen8 : SaveBlock { + public TitleScreen8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } + /// - /// Pokémon Team that shows up at the title screen. + /// Gets an object that exposes the data of the corresponding party . /// - public sealed class TitleScreen8 : SaveBlock + public TitleScreen8Poke ViewPoke(int index) { - public TitleScreen8(SAV8SWSH sav, SCBlock block) : base(sav, block.Data) { } - - /// - /// Gets an object that exposes the data of the corresponding party . - /// - public TitleScreen8Poke ViewPoke(int index) - { - if ((uint)index >= 6) - throw new ArgumentOutOfRangeException(nameof(index)); - return new TitleScreen8Poke(Data, Offset + 0x00 + (index * TitleScreen8Poke.SIZE)); - } - - /// - /// Applies the current to the block. - /// - public void SetPartyData() => LoadTeamData(SAV.PartyData); - - public void LoadTeamData(IList party) - { - for (int i = 0; i < party.Count; i++) - ViewPoke(i).LoadFrom(party[i]); - for (int i = party.Count; i < 6; i++) - ViewPoke(i).Clear(); - } + if ((uint)index >= 6) + throw new ArgumentOutOfRangeException(nameof(index)); + return new TitleScreen8Poke(Data, Offset + 0x00 + (index * TitleScreen8Poke.SIZE)); } - public sealed class TitleScreen8Poke : ISpeciesForm + /// + /// Applies the current to the block. + /// + public void SetPartyData() => LoadTeamData(SAV.PartyData); + + public void LoadTeamData(IList party) { - public const int SIZE = 0x28; - private readonly byte[] Data; - private readonly int Offset; - - public TitleScreen8Poke(byte[] data, int offset) - { - Data = data; - Offset = offset; - } - - public int Species - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x00), value); - } - - public int Form - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); - } - - public int Gender - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x08)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); - } - - public bool IsShiny - { - get => Data[Offset + 0xC] != 0; - set => Data[Offset + 0xC] = value ? (byte)1 : (byte)0; - } - - public uint EncryptionConstant - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x10)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x10), value); - } - - public int FormArgument - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x14)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x14), value); - } - - public uint Unknown18 - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x18)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x18), value); - } - - public uint Unknown1C - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x1C)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x1C), value); - } - - public uint Unknown20 - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x20)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x20), value); - } - - public uint Unknown24 - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x24)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x24), value); - } - - public void Clear() => Array.Clear(Data, Offset, SIZE); - - public void LoadFrom(PKM pkm) - { - Species = pkm.Species; - Form = pkm.Form; - Gender = pkm.Gender; - IsShiny = pkm.IsShiny; - EncryptionConstant = pkm.EncryptionConstant; - FormArgument = pkm is IFormArgument f && pkm.Species == (int)Core.Species.Alcremie ? (int)f.FormArgument : -1; - } - - public void LoadFrom(TrainerCard8Poke pkm) - { - Species = pkm.Species; - Form = pkm.Form; - Gender = pkm.Gender; - IsShiny = pkm.IsShiny; - EncryptionConstant = pkm.EncryptionConstant; - FormArgument = pkm.FormArgument; - } + for (int i = 0; i < party.Count; i++) + ViewPoke(i).LoadFrom(party[i]); + for (int i = party.Count; i < 6; i++) + ViewPoke(i).Clear(); } -} \ No newline at end of file +} + +public sealed class TitleScreen8Poke : ISpeciesForm +{ + public const int SIZE = 0x28; + private readonly byte[] Data; + private readonly int Offset; + + public TitleScreen8Poke(byte[] data, int offset) + { + Data = data; + Offset = offset; + } + + public int Species + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x00), value); + } + + public int Form + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); + } + + public int Gender + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x08)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); + } + + public bool IsShiny + { + get => Data[Offset + 0xC] != 0; + set => Data[Offset + 0xC] = value ? (byte)1 : (byte)0; + } + + public uint EncryptionConstant + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x10)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x10), value); + } + + public int FormArgument + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x14)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x14), value); + } + + public uint Unknown18 + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x18)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x18), value); + } + + public uint Unknown1C + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x1C)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x1C), value); + } + + public uint Unknown20 + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x20)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x20), value); + } + + public uint Unknown24 + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x24)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x24), value); + } + + public void Clear() => Array.Clear(Data, Offset, SIZE); + + public void LoadFrom(PKM pk) + { + Species = pk.Species; + Form = pk.Form; + Gender = pk.Gender; + IsShiny = pk.IsShiny; + EncryptionConstant = pk.EncryptionConstant; + FormArgument = pk is IFormArgument f && pk.Species == (int)Core.Species.Alcremie ? (int)f.FormArgument : -1; + } + + public void LoadFrom(TrainerCard8Poke pk) + { + Species = pk.Species; + Form = pk.Form; + Gender = pk.Gender; + IsShiny = pk.IsShiny; + EncryptionConstant = pk.EncryptionConstant; + FormArgument = pk.FormArgument; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TrainerCard8.cs b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TrainerCard8.cs index 2c66a8b43..2ddbd1236 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TrainerCard8.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/SWSH/TrainerCard8.cs @@ -3,352 +3,351 @@ using System.Text; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class TrainerCard8 : SaveBlock { - public sealed class TrainerCard8 : SaveBlock + public TrainerCard8(SAV8SWSH sav, SCBlock block) : base (sav, block.Data) { } + + private Span OT_Trash => Data.AsSpan(0x00, 0x1A); + + public string OT { - public TrainerCard8(SAV8SWSH sav, SCBlock block) : base (sav, block.Data) { } + get => SAV.GetString(OT_Trash); + set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); + } - private Span OT_Trash => Data.AsSpan(0x00, 0x1A); + public byte Language + { + get => Data[0x1B]; + set => Data[0x1B] = value; // languageID + } - public string OT + public int TrainerID + { + get => ReadInt32LittleEndian(Data.AsSpan(0x1C)); + set => WriteInt32LittleEndian(Data.AsSpan(0x1C), value); + } + + public ushort PokeDexOwned + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x20)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x20), value); + } + + public ushort ShinyPokemonFound + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x22)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x22), value); + } + + public byte Game + { + get => Data[0x24]; + set => Data[0x24] = value; // 0 = Sword, 1 = Shield + } + + public byte Starter + { + get => Data[0x25]; + set => Data[0x25] = value; // Grookey=0, Scorbunny=1, Sobble=2 + } + + public ushort CurryTypesOwned + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x26)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x26), value); + } + + public const int RotoRallyScoreMax = 99_999; + + public int RotoRallyScore + { + get => ReadInt32LittleEndian(Data.AsSpan(0x28)); + set { - get => SAV.GetString(OT_Trash); - set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero); - } - - public byte Language - { - get => Data[0x1B]; - set => Data[0x1B] = value; // languageID - } - - public int TrainerID - { - get => ReadInt32LittleEndian(Data.AsSpan(0x1C)); - set => WriteInt32LittleEndian(Data.AsSpan(0x1C), value); - } - - public ushort PokeDexOwned - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x20)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x20), value); - } - - public ushort ShinyPokemonFound - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x22)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x22), value); - } - - public byte Game - { - get => Data[0x24]; - set => Data[0x24] = value; // 0 = Sword, 1 = Shield - } - - public byte Starter - { - get => Data[0x25]; - set => Data[0x25] = value; // Grookey=0, Scorbunny=1, Sobble=2 - } - - public ushort CurryTypesOwned - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x26)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x26), value); - } - - public const int RotoRallyScoreMax = 99_999; - - public int RotoRallyScore - { - get => ReadInt32LittleEndian(Data.AsSpan(0x28)); - set - { - if (value > RotoRallyScoreMax) - value = RotoRallyScoreMax; - WriteInt32LittleEndian(Data.AsSpan(0x28), value); - // set to the other block since it doesn't have an accessor - SAV.SetValue(SaveBlockAccessor8SWSH.KRotoRally, (uint)value); - } - } - - public const int MaxPokemonCaught = 99_999; - - public int CaughtPokemon - { - get => ReadInt32LittleEndian(Data.AsSpan(0x2C)); - set - { - if (value > MaxPokemonCaught) - value = MaxPokemonCaught; - WriteInt32LittleEndian(Data.AsSpan(0x2C), value); - } - } - - public bool PokeDexComplete - { - get => Data[Offset + 0x30] == 1; - set => Data[Offset + 0x30] = value ? (byte)1 : (byte)0; - } - - public bool ArmorDexComplete - { - get => Data[Offset + 0x1B4] == 1; - set => Data[Offset + 0x1B4] = value ? (byte)1 : (byte)0; - } - - public bool CrownDexComplete - { - get => Data[Offset + 0x1B5] == 1; - set => Data[Offset + 0x1B5] = value ? (byte)1 : (byte)0; - } - - public int Gender - { - get => Data[0x38]; - set => Data[0x38] = (byte)value; - } - - public string Number - { - get => Encoding.ASCII.GetString(Data, 0x39, 3); - set - { - for (int i = 0; i < 3; i++) - Data[0x39 + i] = (byte) (value.Length > i ? value[i] : '\0'); - SAV.State.Edited = true; - } - } - - public ulong Skin // aka the base model - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x40)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x40), value); - } - - public ulong Hair - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x48)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x48), value); - } - - public ulong Brow - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x50)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x50), value); - } - - public ulong Lashes - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x58)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x58), value); - } - - public ulong Contacts - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x60)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x60), value); - } - - public ulong Lips - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x68)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x68), value); - } - - public ulong Glasses - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x70)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x70), value); - } - - public ulong Hat - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x78)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x78), value); - } - - public ulong Jacket - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x80)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x80), value); - } - - public ulong Top - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x88)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x88), value); - } - - public ulong Bag - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x90)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x90), value); - } - - public ulong Gloves - { - get => ReadUInt64LittleEndian(Data.AsSpan(0x98)); - set => WriteUInt64LittleEndian(Data.AsSpan(0x98), value); - } - - public ulong BottomOrDress - { - get => ReadUInt64LittleEndian(Data.AsSpan(0xA0)); - set => WriteUInt64LittleEndian(Data.AsSpan(0xA0), value); - } - - public ulong Sock - { - get => ReadUInt64LittleEndian(Data.AsSpan(0xA8)); - set => WriteUInt64LittleEndian(Data.AsSpan(0xA8), value); - } - - public ulong Shoe - { - get => ReadUInt64LittleEndian(Data.AsSpan(0xB0)); - set => WriteUInt64LittleEndian(Data.AsSpan(0xB0), value); - } - - public ulong MomSkin // aka the base model - { - get => ReadUInt64LittleEndian(Data.AsSpan(0xC0)); - set => WriteUInt64LittleEndian(Data.AsSpan(0xC0), value); - } - - // Trainer Card Pokemon - // 0xC8 - 0xE3 (0x1C) - // 0xE4 - // 0x100 - // 0x11C - // 0x138 - // 0x154 - 0x16F - - /// - /// Gets an object that exposes the data of the corresponding party . - /// - public TrainerCard8Poke ViewPoke(int index) - { - if ((uint) index >= 6) - throw new ArgumentOutOfRangeException(nameof(index)); - return new TrainerCard8Poke(Data, Offset + 0xC8 + (index * TrainerCard8Poke.SIZE)); - } - - /// - /// Applies the current to the block. - /// - public void SetPartyData() => LoadTeamData(SAV.PartyData); - - public void LoadTeamData(IList party) - { - for (int i = 0; i < party.Count; i++) - ViewPoke(i).LoadFrom(party[i]); - for (int i = party.Count; i < 6; i++) - ViewPoke(i).Clear(); - } - - public ushort StartedYear - { - get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x170)); - set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x170), value); - } - - public byte StartedMonth - { - get => Data[Offset + 0x172]; - set => Data[Offset + 0x172] = value; - } - - public byte StartedDay - { - get => Data[Offset + 0x173]; - set => Data[Offset + 0x173] = value; - } - - public uint TimestampPrinted - { - // should this be a ulong? - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x1A8)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x1A8), value); + if (value > RotoRallyScoreMax) + value = RotoRallyScoreMax; + WriteInt32LittleEndian(Data.AsSpan(0x28), value); + // set to the other block since it doesn't have an accessor + SAV.SetValue(SaveBlockAccessor8SWSH.KRotoRally, (uint)value); } } - public sealed class TrainerCard8Poke : ISpeciesForm + public const int MaxPokemonCaught = 99_999; + + public int CaughtPokemon { - public const int SIZE = 0x1C; - private readonly byte[] Data; - private readonly int Offset; - - public TrainerCard8Poke(byte[] data, int offset) + get => ReadInt32LittleEndian(Data.AsSpan(0x2C)); + set { - Data = data; - Offset = offset; + if (value > MaxPokemonCaught) + value = MaxPokemonCaught; + WriteInt32LittleEndian(Data.AsSpan(0x2C), value); } + } - public int Species + public bool PokeDexComplete + { + get => Data[Offset + 0x30] == 1; + set => Data[Offset + 0x30] = value ? (byte)1 : (byte)0; + } + + public bool ArmorDexComplete + { + get => Data[Offset + 0x1B4] == 1; + set => Data[Offset + 0x1B4] = value ? (byte)1 : (byte)0; + } + + public bool CrownDexComplete + { + get => Data[Offset + 0x1B5] == 1; + set => Data[Offset + 0x1B5] = value ? (byte)1 : (byte)0; + } + + public int Gender + { + get => Data[0x38]; + set => Data[0x38] = (byte)value; + } + + public string Number + { + get => Encoding.ASCII.GetString(Data, 0x39, 3); + set { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x00), value); + for (int i = 0; i < 3; i++) + Data[0x39 + i] = (byte) (value.Length > i ? value[i] : '\0'); + SAV.State.Edited = true; } + } - public int Form - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); - } + public ulong Skin // aka the base model + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x40)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x40), value); + } - public int Gender - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x08)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); - } + public ulong Hair + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x48)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x48), value); + } - public bool IsShiny - { - get => Data[Offset + 0xC] != 0; - set => Data[Offset + 0xC] = value ? (byte)1 : (byte)0; - } + public ulong Brow + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x50)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x50), value); + } - public uint EncryptionConstant - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x10)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x10), value); - } + public ulong Lashes + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x58)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x58), value); + } - public uint Unknown - { - get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x14)); - set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x14), value); - } + public ulong Contacts + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x60)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x60), value); + } - public int FormArgument - { - get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x18)); - set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x18), value); - } + public ulong Lips + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x68)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x68), value); + } - public void Clear() => Array.Clear(Data, Offset, SIZE); + public ulong Glasses + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x70)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x70), value); + } - public void LoadFrom(PKM pkm) - { - Species = pkm.Species; - Form = pkm.Form; - Gender = pkm.Gender; - IsShiny = pkm.IsShiny; - EncryptionConstant = pkm.EncryptionConstant; - FormArgument = pkm is IFormArgument f && pkm.Species == (int) Core.Species.Alcremie ? (int)f.FormArgument : -1; - } + public ulong Hat + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x78)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x78), value); + } - public void LoadFrom(TitleScreen8Poke pkm) - { - Species = pkm.Species; - Form = pkm.Form; - Gender = pkm.Gender; - IsShiny = pkm.IsShiny; - EncryptionConstant = pkm.EncryptionConstant; - FormArgument = pkm.FormArgument; - } + public ulong Jacket + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x80)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x80), value); + } + + public ulong Top + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x88)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x88), value); + } + + public ulong Bag + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x90)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x90), value); + } + + public ulong Gloves + { + get => ReadUInt64LittleEndian(Data.AsSpan(0x98)); + set => WriteUInt64LittleEndian(Data.AsSpan(0x98), value); + } + + public ulong BottomOrDress + { + get => ReadUInt64LittleEndian(Data.AsSpan(0xA0)); + set => WriteUInt64LittleEndian(Data.AsSpan(0xA0), value); + } + + public ulong Sock + { + get => ReadUInt64LittleEndian(Data.AsSpan(0xA8)); + set => WriteUInt64LittleEndian(Data.AsSpan(0xA8), value); + } + + public ulong Shoe + { + get => ReadUInt64LittleEndian(Data.AsSpan(0xB0)); + set => WriteUInt64LittleEndian(Data.AsSpan(0xB0), value); + } + + public ulong MomSkin // aka the base model + { + get => ReadUInt64LittleEndian(Data.AsSpan(0xC0)); + set => WriteUInt64LittleEndian(Data.AsSpan(0xC0), value); + } + + // Trainer Card Pokemon + // 0xC8 - 0xE3 (0x1C) + // 0xE4 + // 0x100 + // 0x11C + // 0x138 + // 0x154 - 0x16F + + /// + /// Gets an object that exposes the data of the corresponding party . + /// + public TrainerCard8Poke ViewPoke(int index) + { + if ((uint) index >= 6) + throw new ArgumentOutOfRangeException(nameof(index)); + return new TrainerCard8Poke(Data, Offset + 0xC8 + (index * TrainerCard8Poke.SIZE)); + } + + /// + /// Applies the current to the block. + /// + public void SetPartyData() => LoadTeamData(SAV.PartyData); + + public void LoadTeamData(IList party) + { + for (int i = 0; i < party.Count; i++) + ViewPoke(i).LoadFrom(party[i]); + for (int i = party.Count; i < 6; i++) + ViewPoke(i).Clear(); + } + + public ushort StartedYear + { + get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x170)); + set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x170), value); + } + + public byte StartedMonth + { + get => Data[Offset + 0x172]; + set => Data[Offset + 0x172] = value; + } + + public byte StartedDay + { + get => Data[Offset + 0x173]; + set => Data[Offset + 0x173] = value; + } + + public uint TimestampPrinted + { + // should this be a ulong? + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x1A8)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x1A8), value); + } +} + +public sealed class TrainerCard8Poke : ISpeciesForm +{ + public const int SIZE = 0x1C; + private readonly byte[] Data; + private readonly int Offset; + + public TrainerCard8Poke(byte[] data, int offset) + { + Data = data; + Offset = offset; + } + + public int Species + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x00)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x00), value); + } + + public int Form + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x04)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x04), value); + } + + public int Gender + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x08)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x08), value); + } + + public bool IsShiny + { + get => Data[Offset + 0xC] != 0; + set => Data[Offset + 0xC] = value ? (byte)1 : (byte)0; + } + + public uint EncryptionConstant + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x10)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x10), value); + } + + public uint Unknown + { + get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x14)); + set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x14), value); + } + + public int FormArgument + { + get => ReadInt32LittleEndian(Data.AsSpan(Offset + 0x18)); + set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x18), value); + } + + public void Clear() => Array.Clear(Data, Offset, SIZE); + + public void LoadFrom(PKM pk) + { + Species = pk.Species; + Form = pk.Form; + Gender = pk.Gender; + IsShiny = pk.IsShiny; + EncryptionConstant = pk.EncryptionConstant; + FormArgument = pk is IFormArgument f && pk.Species == (int) Core.Species.Alcremie ? (int)f.FormArgument : -1; + } + + public void LoadFrom(TitleScreen8Poke pk) + { + Species = pk.Species; + Form = pk.Form; + Gender = pk.Gender; + IsShiny = pk.IsShiny; + EncryptionConstant = pk.EncryptionConstant; + FormArgument = pk.FormArgument; } } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/OptInOption.cs b/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/OptInOption.cs index aec42f0fa..a98493f77 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/OptInOption.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/OptInOption.cs @@ -1,8 +1,7 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum OptInOption : uint { - public enum OptInOption : uint - { - No = 0, - Yes = 1, - } + No = 0, + Yes = 1, } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/OptOutOption.cs b/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/OptOutOption.cs index b225d766c..79c49f801 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/OptOutOption.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/OptOutOption.cs @@ -1,8 +1,7 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum OptOutOption : uint { - public enum OptOutOption : uint - { - Yes = 0, - No = 1, - } + Yes = 0, + No = 1, } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/TextSpeedOption.cs b/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/TextSpeedOption.cs index bc82739fb..a2e85e1c6 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/TextSpeedOption.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/ValueBlocks/TextSpeedOption.cs @@ -1,10 +1,9 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public enum TextSpeedOption : uint { - public enum TextSpeedOption : uint - { - Slow = 0, - Normal = 1, - Fast = 2, - Instant = 3, // Any other value, really. - } + Slow = 0, + Normal = 1, + Fast = 2, + Instant = 3, // Any other value, really. } diff --git a/PKHeX.Core/Saves/Substructures/ITrainerStatRecord.cs b/PKHeX.Core/Saves/Substructures/ITrainerStatRecord.cs index c48c8f1a9..1a39c6aad 100644 --- a/PKHeX.Core/Saves/Substructures/ITrainerStatRecord.cs +++ b/PKHeX.Core/Saves/Substructures/ITrainerStatRecord.cs @@ -1,14 +1,13 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides an API for fluent record editors. +/// +public interface ITrainerStatRecord { - /// - /// Provides an API for fluent record editors. - /// - public interface ITrainerStatRecord - { - int GetRecord(int recordID); - int GetRecordOffset(int recordID); - int GetRecordMax(int recordID); - void SetRecord(int recordID, int value); - int RecordCount { get; } - } + int GetRecord(int recordID); + int GetRecordOffset(int recordID); + int GetRecordMax(int recordID); + void SetRecord(int recordID, int value); + int RecordCount { get; } } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/IItemNew.cs b/PKHeX.Core/Saves/Substructures/Inventory/IItemNewFlag.cs similarity index 83% rename from PKHeX.Core/Saves/Substructures/Inventory/IItemNew.cs rename to PKHeX.Core/Saves/Substructures/Inventory/IItemNewFlag.cs index 67beedeba..6d8c5341f 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/IItemNew.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/IItemNewFlag.cs @@ -1,6 +1,6 @@ namespace PKHeX.Core; -public interface IItemNew +public interface IItemNewFlag { /// Indicates if the item is "NEW"ly obtained and not yet viewed. bool IsNew { get; set; } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/InventoryType.cs b/PKHeX.Core/Saves/Substructures/Inventory/InventoryType.cs index 4e902ca37..df25f6034 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/InventoryType.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/InventoryType.cs @@ -1,25 +1,24 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Type of items the contains. +/// +/// Used by the Inventory Editor as the index for sprite lookup. +public enum InventoryType { - /// - /// Type of items the contains. - /// - /// Used by the Inventory Editor as the index for sprite lookup. - public enum InventoryType - { - None, - Items, - KeyItems, - TMHMs, - Medicine, - Berries, - Balls, - BattleItems, - MailItems, - PCItems, - FreeSpace, - ZCrystals, - Candy, - Treasure, - Ingredients, - } -} \ No newline at end of file + None, + Items, + KeyItems, + TMHMs, + Medicine, + Berries, + Balls, + BattleItems, + MailItems, + PCItems, + FreeSpace, + ZCrystals, + Candy, + Treasure, + Ingredients, +} diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem7.cs b/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem7.cs index da6c30b09..13364fe82 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem7.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem7.cs @@ -2,7 +2,7 @@ namespace PKHeX.Core; -public sealed record InventoryItem7 : InventoryItem, IItemFreeSpaceIndex, IItemNew +public sealed record InventoryItem7 : InventoryItem, IItemFreeSpaceIndex, IItemNewFlag { public uint FreeSpaceIndex { get; set; } public bool IsNew { get; set; } @@ -22,9 +22,9 @@ public static InventoryItem7 GetValue(uint value) => new() // 1 bit new flag // 1 bit reserved Index = (int)(value & 0x3FF), - Count = (int)(value >> 10 & 0x3FF), + Count = (int)((value >> 10) & 0x3FF), IsNew = (value & 0x40000000) != 0, // 30th bit is "NEW" - FreeSpaceIndex = (value >> 20 & 0x3FF), // "FREE SPACE" sortIndex + FreeSpaceIndex = ((value >> 20) & 0x3FF), // "FREE SPACE" sortIndex }; public uint GetValue(bool setNew, ICollection original) diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem7b.cs b/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem7b.cs index c1621b5a8..2d4a903b3 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem7b.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem7b.cs @@ -1,61 +1,60 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed record InventoryItem7b : InventoryItem, IItemFreeSpace, IItemNewFlag { - public sealed record InventoryItem7b : InventoryItem, IItemFreeSpace, IItemNew + public bool IsFreeSpace { get; set; } + public bool IsNew { get; set; } + + public override void Clear() { - public bool IsFreeSpace { get; set; } - public bool IsNew { get; set; } + Index = Count = 0; + IsFreeSpace = IsNew = false; + } - public override void Clear() - { - Index = Count = 0; - IsFreeSpace = IsNew = false; - } + public static InventoryItem7b GetValue(uint value) => new() + { + // 15bit itemID + // 15bit count + // 1 bit new flag + // 1 bit reserved + Index = (int)(value & 0x7FF), + Count = (int)((value >> 15) & 0x3FF), // clamp to sane values + IsNew = (value & 0x40000000) != 0, // 30th bit is "NEW" + }; - public static InventoryItem7b GetValue(uint value) => new() - { - // 15bit itemID - // 15bit count - // 1 bit new flag - // 1 bit reserved - Index = (int)(value & 0x7FF), - Count = (int)(value >> 15 & 0x3FF), // clamp to sane values - IsNew = (value & 0x40000000) != 0, // 30th bit is "NEW" - }; + public uint GetValue(bool setNew, ICollection original) + { + // 15bit itemID + // 15bit count + // 1 bit new flag + // 1 bit reserved + uint value = 0; + value |= (uint)(Index & 0x7FF); + value |= (uint)(Count & 0x3FF) << 15; // clamped to sane limit - public uint GetValue(bool setNew, ICollection original) - { - // 15bit itemID - // 15bit count - // 1 bit new flag - // 1 bit reserved - uint value = 0; - value |= (uint)(Index & 0x7FF); - value |= (uint)(Count & 0x3FF) << 15; // clamped to sane limit + bool isNew = IsNew || (setNew && !original.Contains(Index)); + if (isNew) + value |= 0x40000000; + return value; + } - bool isNew = IsNew || (setNew && !original.Contains(Index)); - if (isNew) - value |= 0x40000000; - return value; - } + public override void SetNewDetails(int count) + { + base.SetNewDetails(count); + IsNew = true; + } - public override void SetNewDetails(int count) - { - base.SetNewDetails(count); - IsNew = true; - } - - /// - /// Item has been matched to a previously existing item. Copy over the misc details. - /// - public override void MergeOverwrite(T other) - { - base.MergeOverwrite(other); - if (other is not InventoryItem7b item) - return; - IsNew = item.IsNew; - IsFreeSpace = item.IsFreeSpace; - } + /// + /// Item has been matched to a previously existing item. Copy over the misc details. + /// + public override void MergeOverwrite(T other) + { + base.MergeOverwrite(other); + if (other is not InventoryItem7b item) + return; + IsNew = item.IsNew; + IsFreeSpace = item.IsFreeSpace; } } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8.cs b/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8.cs index e5451007e..ab2d47cdd 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8.cs @@ -1,65 +1,64 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed record InventoryItem8 : InventoryItem, IItemFavorite, IItemNewFlag { - public sealed record InventoryItem8 : InventoryItem, IItemFavorite, IItemNew + public bool IsFavorite { get; set; } + public bool IsNew { get; set; } + + public override void Clear() { - public bool IsFavorite { get; set; } - public bool IsNew { get; set; } + Index = Count = 0; + IsFavorite = IsNew = false; + } - public override void Clear() - { - Index = Count = 0; - IsFavorite = IsNew = false; - } + public static InventoryItem8 GetValue(uint value) => new() + { + // 15bit itemID + // 15bit count + // 1 bit new flag + // 1 bit favorite flag + Index = (int)(value & 0x7FF), + Count = (int)((value >> 15) & 0x3FF), // clamp to sane values + IsNew = (value & 0x40000000) != 0, // 30th bit is "NEW" + IsFavorite = (value & 0x80000000) != 0, // 31th bit is "FAVORITE" + }; - public static InventoryItem8 GetValue(uint value) => new() - { - // 15bit itemID - // 15bit count - // 1 bit new flag - // 1 bit favorite flag - Index = (int)(value & 0x7FF), - Count = (int)(value >> 15 & 0x3FF), // clamp to sane values - IsNew = (value & 0x40000000) != 0, // 30th bit is "NEW" - IsFavorite = (value & 0x80000000) != 0, // 31th bit is "FAVORITE" - }; + public uint GetValue(bool setNew, ICollection original) + { + // 15bit itemID + // 15bit count + // 1 bit new flag + // 1 bit favorite flag + uint val = 0; + val |= (uint)(Index & 0x7FF); + val |= (uint)(Count & 0x3FF) << 15; // clamped to sane limit - public uint GetValue(bool setNew, ICollection original) - { - // 15bit itemID - // 15bit count - // 1 bit new flag - // 1 bit favorite flag - uint val = 0; - val |= (uint)(Index & 0x7FF); - val |= (uint)(Count & 0x3FF) << 15; // clamped to sane limit + bool isNew = IsNew || (setNew && !original.Contains(Index)); + if (isNew) + val |= 0x40000000; + if (IsFavorite) + val |= 0x80000000; + return val; + } - bool isNew = IsNew || (setNew && !original.Contains(Index)); - if (isNew) - val |= 0x40000000; - if (IsFavorite) - val |= 0x80000000; - return val; - } + public override void SetNewDetails(int count) + { + base.SetNewDetails(count); + IsNew = true; + IsFavorite = false; + } - public override void SetNewDetails(int count) - { - base.SetNewDetails(count); - IsNew = true; - IsFavorite = false; - } - - /// - /// Item has been matched to a previously existing item. Copy over the misc details. - /// - public override void MergeOverwrite(T other) - { - base.MergeOverwrite(other); - if (other is not InventoryItem8 item) - return; - IsNew = item.IsNew; - IsFavorite = item.IsFavorite; - } + /// + /// Item has been matched to a previously existing item. Copy over the misc details. + /// + public override void MergeOverwrite(T other) + { + base.MergeOverwrite(other); + if (other is not InventoryItem8 item) + return; + IsNew = item.IsNew; + IsFavorite = item.IsFavorite; } } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8b.cs b/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8b.cs index e7cd23979..5363e84f6 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8b.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Item/InventoryItem8b.cs @@ -1,74 +1,73 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed record InventoryItem8b : InventoryItem, IItemFavorite, IItemNewFlag { - public sealed record InventoryItem8b : InventoryItem, IItemFavorite, IItemNew + public const int SIZE = 0x10; + private const ushort SortOrderNone = 0; + + public bool IsFavorite { get; set; } + public bool IsNew { get; set; } + public ushort SortOrder { get; set; } + + public override string ToString() => $"{SortOrder:00} - {Index:000} x{Count}{(IsNew ? "*" : "")}{(IsFavorite ? "F" : "")}"; + + public override void Clear() { - public const int SIZE = 0x10; - private const ushort SortOrderNone = 0; + Index = Count = 0; + IsFavorite = false; + IsNew = true; + SortOrder = SortOrderNone; + } - public bool IsFavorite { get; set; } - public bool IsNew { get; set; } - public ushort SortOrder { get; set; } + /// + /// Indicates if the item has been acquired by the player. + /// + public bool IsValidSaveSortNumberCount => SortOrder != SortOrderNone; - public override string ToString() => $"{SortOrder:00} - {Index:000} x{Count}{(IsNew ? "*" : "")}{(IsFavorite ? "F" : "")}"; + public static InventoryItem8b Read(ushort index, ReadOnlySpan data) => new() + { + Index = index, + Count = ReadInt32LittleEndian(data), + IsNew = ReadInt32LittleEndian(data[4..]) == 0, + IsFavorite = ReadInt32LittleEndian(data[8..]) == 1, + SortOrder = ReadUInt16LittleEndian(data[12..]), + // 0xE alignment + }; - public override void Clear() - { - Index = Count = 0; - IsFavorite = false; - IsNew = true; - SortOrder = SortOrderNone; - } + public void Write(Span data) + { + // Index is not saved. + WriteUInt32LittleEndian(data, (uint)Count); + WriteUInt32LittleEndian(data[4..], IsNew ? 0u : 1u); + WriteUInt32LittleEndian(data[8..], IsFavorite ? 1u : 0u); + WriteUInt16LittleEndian(data[12..], SortOrder); + WriteUInt16LittleEndian(data[14..], 0); + } - /// - /// Indicates if the item has been acquired by the player. - /// - public bool IsValidSaveSortNumberCount => SortOrder != SortOrderNone; + public static void Clear(Span data, int offset) => data.Slice(offset, SIZE).Clear(); - public static InventoryItem8b Read(ushort index, ReadOnlySpan data) => new() - { - Index = index, - Count = ReadInt32LittleEndian(data), - IsNew = ReadInt32LittleEndian(data[4..]) == 0, - IsFavorite = ReadInt32LittleEndian(data[8..]) == 1, - SortOrder = ReadUInt16LittleEndian(data[12..]), - // 0xE alignment - }; + public override void SetNewDetails(int count) + { + base.SetNewDetails(count); + if (IsValidSaveSortNumberCount) + return; + IsNew = true; + IsFavorite = false; + } - public void Write(Span data) - { - // Index is not saved. - WriteUInt32LittleEndian(data, (uint)Count); - WriteUInt32LittleEndian(data[4..], IsNew ? 0u : 1u); - WriteUInt32LittleEndian(data[8..], IsFavorite ? 1u : 0u); - WriteUInt16LittleEndian(data[12..], SortOrder); - WriteUInt16LittleEndian(data[14..], 0); - } - - public static void Clear(Span data, int offset) => data.Slice(offset, SIZE).Clear(); - - public override void SetNewDetails(int count) - { - base.SetNewDetails(count); - if (IsValidSaveSortNumberCount) - return; - IsNew = true; - IsFavorite = false; - } - - /// - /// Item has been matched to a previously existing item. Copy over the misc details. - /// - public override void MergeOverwrite(T other) - { - base.MergeOverwrite(other); - if (other is not InventoryItem8b item) - return; - SortOrder = item.SortOrder; - IsNew = item.IsNew; - IsFavorite = item.IsFavorite; - } + /// + /// Item has been matched to a previously existing item. Copy over the misc details. + /// + public override void MergeOverwrite(T other) + { + base.MergeOverwrite(other); + if (other is not InventoryItem8b item) + return; + SortOrder = item.SortOrder; + IsNew = item.IsNew; + IsFavorite = item.IsFavorite; } } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/MyItem.cs b/PKHeX.Core/Saves/Substructures/Inventory/MyItem.cs index f8108c2f1..4e78e56bb 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/MyItem.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/MyItem.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public abstract class MyItem : SaveBlock { - public abstract class MyItem : SaveBlock - { - public abstract IReadOnlyList Inventory { get; set; } - protected MyItem(SaveFile SAV) : base(SAV) { } - protected MyItem(SaveFile SAV, byte[] data) : base(SAV, data) { } - } -} \ No newline at end of file + public abstract IReadOnlyList Inventory { get; set; } + protected MyItem(SaveFile SAV) : base(SAV) { } + protected MyItem(SaveFile SAV, byte[] data) : base(SAV, data) { } +} diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch.cs b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch.cs index ebaefe126..1e7dbb470 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch.cs @@ -2,287 +2,286 @@ using System.Collections.Generic; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Represents the data in a pouch pocket containing items of a similar type group. +/// +public abstract class InventoryPouch { - /// - /// Represents the data in a pouch pocket containing items of a similar type group. - /// - public abstract class InventoryPouch + /// + public readonly InventoryType Type; + + /// Valid item IDs that may be stored in the pouch. + public readonly ushort[] LegalItems; + + public readonly Func? IsItemLegal; + + /// Max quantity for a given item that can be stored in the pouch. + public readonly int MaxCount; + + /// Count of item slots occupied in the pouch. + public int Count => Items.Count(it => it.Count > 0); + + /// Checks if the player may run out of bag space when there are too many unique items to fit into the pouch. + public bool IsCramped => LegalItems.Length > Items.Length; + + public InventoryItem[] Items; + + /// Offset the items were read from. + protected readonly int Offset; + /// Size of the backing byte array that represents the pouch. + protected readonly int PouchDataSize; + + protected InventoryPouch(InventoryType type, ushort[] legal, int maxCount, int offset, int size = -1, Func? isLegal = null) { - /// - public readonly InventoryType Type; + Items = Array.Empty(); + Type = type; + LegalItems = legal; + MaxCount = maxCount; + Offset = offset; + PouchDataSize = size > -1 ? size : legal.Length; + IsItemLegal = isLegal; + } - /// Valid item IDs that may be stored in the pouch. - public readonly ushort[] LegalItems; + /// Reads the pouch from the backing . + public abstract void GetPouch(ReadOnlySpan data); + /// Writes the pouch to the backing . + public abstract void SetPouch(Span data); - public readonly Func? IsItemLegal; + /// Orders the based on + public void SortByCount(bool reverse = false) => Array.Sort(Items, (x, y) => Compare(x.Count, y.Count, reverse)); - /// Max quantity for a given item that can be stored in the pouch. - public readonly int MaxCount; + /// Orders the based on + public void SortByIndex(bool reverse = false) => Array.Sort(Items, (x, y) => Compare(x.Index, y.Index, reverse)); + public void SortByName(string[] names, bool reverse = false) => Array.Sort(Items, (x, y) => Compare(x.Index, y.Index, names, reverse)); + public void SortByEmpty() => Array.Sort(Items, (x, y) => (x.Count == 0).CompareTo(y.Count == 0)); + public void SortBy(Func selector) where TItem : InventoryItem where TCompare : IComparable => Array.Sort(Items, (x, y) => selector((TItem)x).CompareTo(selector((TItem)y))); - /// Count of item slots occupied in the pouch. - public int Count => Items.Count(it => it.Count > 0); + private static int Compare(int i1, int i2, IReadOnlyList n, bool rev) where TCompare : IComparable + { + if (i1 == 0 || i1 >= n.Count) + return 1; + if (i2 == 0 || i2 >= n.Count) + return -1; + return rev + ? n[i2].CompareTo(n[i1]) + : n[i1].CompareTo(n[i2]); + } - /// Checks if the player may run out of bag space when there are too many unique items to fit into the pouch. - public bool IsCramped => LegalItems.Length > Items.Length; + private static int Compare(int i1, int i2, bool rev) + { + if (i1 == 0) + return 1; + if (i2 == 0) + return -1; + return rev + ? i2.CompareTo(i1) + : i1.CompareTo(i2); + } - public InventoryItem[] Items; - - /// Offset the items were read from. - protected readonly int Offset; - /// Size of the backing byte array that represents the pouch. - protected readonly int PouchDataSize; - - protected InventoryPouch(InventoryType type, ushort[] legal, int maxCount, int offset, int size = -1, Func? isLegal = null) + /// + /// Clears invalid items and clamps any item quantity that is out of range. + /// + /// Max item ID that exists in the game + /// Allow maximum-but-illegal quantities. + public void Sanitize(int maxItemID, bool HaX = false) + { + int ctr = 0; + for (int i = 0; i < Items.Length; i++) { - Items = Array.Empty(); - Type = type; - LegalItems = legal; - MaxCount = maxCount; - Offset = offset; - PouchDataSize = size > -1 ? size : legal.Length; - IsItemLegal = isLegal; + if (Items[i].IsValid(LegalItems, maxItemID, HaX)) + Items[ctr++] = Items[i]; + } + while (ctr < Items.Length) + Items[ctr++].Clear(); + } + + /// + /// Clears all item slots with a quantity of zero and shifts any subsequent item slot up. + /// + public void ClearCount0() + { + int ctr = 0; + for (int i = 0; i < Items.Length; i++) + { + if (Items[i].Count != 0) + Items[ctr++] = Items[i]; } - /// Reads the pouch from the backing . - public abstract void GetPouch(ReadOnlySpan data); - /// Writes the pouch to the backing . - public abstract void SetPouch(Span data); + while (ctr < Items.Length) + Items[ctr++] = GetEmpty(); + } - /// Orders the based on - public void SortByCount(bool reverse = false) => Array.Sort(Items, (x, y) => Compare(x.Count, y.Count, reverse)); + /// + /// Clears all items in the pouch. + /// + public void RemoveAll() + { + foreach (var item in Items) + item.Clear(); + } - /// Orders the based on - public void SortByIndex(bool reverse = false) => Array.Sort(Items, (x, y) => Compare(x.Index, y.Index, reverse)); - public void SortByName(string[] names, bool reverse = false) => Array.Sort(Items, (x, y) => Compare(x.Index, y.Index, names, reverse)); - public void SortByEmpty() => Array.Sort(Items, (x, y) => (x.Count == 0).CompareTo(y.Count == 0)); - public void SortBy(Func selector) where TItem : InventoryItem where TCompare : IComparable => Array.Sort(Items, (x, y) => selector((TItem)x).CompareTo(selector((TItem)y))); + /// + /// Clears all items in the pouch that match the criteria to delete. + /// + public void RemoveAll(Func deleteCriteria) + { + foreach (var item in Items.Where(deleteCriteria)) + item.Clear(); + } - private static int Compare(int i1, int i2, IReadOnlyList n, bool rev) where TCompare : IComparable + /// + /// Clears all items in the pouch that match the criteria to delete, considering index within the pouch. + /// + public void RemoveAll(Func deleteCriteria) + { + foreach (var item in Items.Where(deleteCriteria)) + item.Clear(); + } + + /// + /// Changes all the item quantities of present items to the specified . + /// + public void ModifyAllCount(int value) + { + foreach (var item in Items.Where(z => z.Count != 0)) + item.Count = value; + } + + /// + /// Changes all the item quantities of items that match the criteria to the specified . + /// + public void ModifyAllCount(int value, Func modifyCriteria) + { + foreach (var item in Items.Where(z => z.Count != 0).Where(modifyCriteria)) + item.Count = value; + } + + /// + /// Changes all the item quantities of items that match the criteria to the specified , considering index within the pouch. + /// + public void ModifyAllCount(int value, Func modifyCriteria) + { + foreach (var item in Items.Where(z => z.Index != 0).Where(modifyCriteria)) + item.Count = value; + } + + public void ModifyAllCount(SaveFile sav, int count = -1) + { + if (count <= 0) + count = 1; + foreach (var item in Items.Where(z => z.Index != 0)) + item.Count = GetSuggestedItemCount(sav, item.Index, count); + } + + public void ModifyAllCount(Func modification) + { + foreach (var item in Items.Where(z => z.Index != 0)) + item.Count = modification(item); + } + + public void GiveAllItems(ReadOnlySpan newItems, Func getSuggestedItemCount, int count = -1) + { + GiveAllItems(newItems, count); + ModifyAllCount(getSuggestedItemCount); + } + + public void GiveAllItems(SaveFile sav, ReadOnlySpan items, int count = -1) + { + GiveAllItems(items, count); + ModifyAllCount(item => GetSuggestedItemCount(sav, item.Index, count)); + } + + public void GiveAllItems(SaveFile sav, int count = -1) => GiveAllItems(sav, LegalItems, count); + + private void GiveAllItems(ReadOnlySpan newItems, int count = -1) + { + if (count < 0) + count = MaxCount; + + var current = (InventoryItem[]) Items.Clone(); + var itemEnd = Math.Min(Items.Length, newItems.Length); + var iterate = newItems[..itemEnd]; + + int ctr = 0; + foreach (var newItemID in iterate) { - if (i1 == 0 || i1 >= n.Count) - return 1; - if (i2 == 0 || i2 >= n.Count) - return -1; - return rev - ? n[i2].CompareTo(n[i1]) - : n[i1].CompareTo(n[i2]); - } + if (IsItemLegal?.Invoke(newItemID) == false) + continue; - private static int Compare(int i1, int i2, bool rev) - { - if (i1 == 0) - return 1; - if (i2 == 0) - return -1; - return rev - ? i2.CompareTo(i1) - : i1.CompareTo(i2); - } - - /// - /// Clears invalid items and clamps any item quantity that is out of range. - /// - /// Max item ID that exists in the game - /// Allow maximum-but-illegal quantities. - public void Sanitize(int maxItemID, bool HaX = false) - { - int ctr = 0; - for (int i = 0; i < Items.Length; i++) + var item = Items[ctr++] = GetEmpty(newItemID); + var match = Array.Find(current, z => z.Index == newItemID); + if (match == null) { - if (Items[i].IsValid(LegalItems, maxItemID, HaX)) - Items[ctr++] = Items[i]; - } - while (ctr < Items.Length) - Items[ctr++].Clear(); - } - - /// - /// Clears all item slots with a quantity of zero and shifts any subsequent item slot up. - /// - public void ClearCount0() - { - int ctr = 0; - for (int i = 0; i < Items.Length; i++) - { - if (Items[i].Count != 0) - Items[ctr++] = Items[i]; + item.SetNewDetails(count); + continue; } - while (ctr < Items.Length) - Items[ctr++] = GetEmpty(); + // load old values + item.MergeOverwrite(match); } - /// - /// Clears all items in the pouch. - /// - public void RemoveAll() + for (int i = ctr; i < Items.Length; i++) + Items[i] = GetEmpty(); + } + + public bool IsValidItemAndCount(ITrainerInfo sav, int item, bool HasNew, bool HaX, ref int count) + { + if (HaX && sav.Generation != 7) // Gen7 has true cap at 1023, keep 999 cap. { - foreach (var item in Items) - item.Clear(); - } - - /// - /// Clears all items in the pouch that match the criteria to delete. - /// - public void RemoveAll(Func deleteCriteria) - { - foreach (var item in Items.Where(deleteCriteria)) - item.Clear(); - } - - /// - /// Clears all items in the pouch that match the criteria to delete, considering index within the pouch. - /// - public void RemoveAll(Func deleteCriteria) - { - foreach (var item in Items.Where(deleteCriteria)) - item.Clear(); - } - - /// - /// Changes all the item quantities of present items to the specified . - /// - public void ModifyAllCount(int value) - { - foreach (var item in Items.Where(z => z.Count != 0)) - item.Count = value; - } - - /// - /// Changes all the item quantities of items that match the criteria to the specified . - /// - public void ModifyAllCount(int value, Func modifyCriteria) - { - foreach (var item in Items.Where(z => z.Count != 0).Where(modifyCriteria)) - item.Count = value; - } - - /// - /// Changes all the item quantities of items that match the criteria to the specified , considering index within the pouch. - /// - public void ModifyAllCount(int value, Func modifyCriteria) - { - foreach (var item in Items.Where(z => z.Index != 0).Where(modifyCriteria)) - item.Count = value; - } - - public void ModifyAllCount(SaveFile sav, int count = -1) - { - if (count <= 0) - count = 1; - foreach (var item in Items.Where(z => z.Index != 0)) - item.Count = GetSuggestedItemCount(sav, item.Index, count); - } - - public void ModifyAllCount(Func modification) - { - foreach (var item in Items.Where(z => z.Index != 0)) - item.Count = modification(item); - } - - public void GiveAllItems(ReadOnlySpan newItems, Func getSuggestedItemCount, int count = -1) - { - GiveAllItems(newItems, count); - ModifyAllCount(getSuggestedItemCount); - } - - public void GiveAllItems(SaveFile sav, ReadOnlySpan items, int count = -1) - { - GiveAllItems(items, count); - ModifyAllCount(item => GetSuggestedItemCount(sav, item.Index, count)); - } - - public void GiveAllItems(SaveFile sav, int count = -1) => GiveAllItems(sav, LegalItems, count); - - private void GiveAllItems(ReadOnlySpan newItems, int count = -1) - { - if (count < 0) - count = MaxCount; - - var current = (InventoryItem[]) Items.Clone(); - var itemEnd = Math.Min(Items.Length, newItems.Length); - var iterate = newItems[..itemEnd]; - - int ctr = 0; - foreach (var newItemID in iterate) + count = sav.Generation switch { - if (IsItemLegal?.Invoke(newItemID) == false) - continue; - - var item = Items[ctr++] = GetEmpty(newItemID); - var match = Array.Find(current, z => z.Index == newItemID); - if (match == null) - { - item.SetNewDetails(count); - continue; - } - - // load old values - item.MergeOverwrite(match); - } - - for (int i = ctr; i < Items.Length; i++) - Items[i] = GetEmpty(); - } - - public bool IsValidItemAndCount(ITrainerInfo sav, int item, bool HasNew, bool HaX, ref int count) - { - if (HaX && sav.Generation != 7) // Gen7 has true cap at 1023, keep 999 cap. - { - count = sav.Generation switch - { - // Cap at absolute maximum - <= 2 when count > byte.MaxValue => byte.MaxValue, - >= 3 when count > ushort.MaxValue => ushort.MaxValue, - _ => count, - }; - return true; - } - - if (count > MaxCount) - { - if (item == 797 && count >= 2) // Edge case when for some reason the item count for Z-Ring was 2 in an unedited save and set 1 after using PKHeX - count = 2; - else - count = MaxCount; // Cap at pouch maximum - } - else if (count <= 0 && !HasNew) - { - return false; - } - - count = GetSuggestedItemCount(sav, item, count); + // Cap at absolute maximum + <= 2 when count > byte.MaxValue => byte.MaxValue, + >= 3 when count > ushort.MaxValue => ushort.MaxValue, + _ => count, + }; return true; } - private int GetSuggestedItemCount(ITrainerInfo sav, int item, int requestVal = 1) + if (count > MaxCount) { - if (sav is SAV7b) // mixed pouch count caps - return InventoryPouch7b.GetSuggestedCount(Type, item, requestVal); - if (sav is SAV8SWSH) - return InventoryPouch8.GetSuggestedCount(Type, item, requestVal); - if (sav is not SAV8BS && ItemConverter.IsItemHM((ushort)item, sav.Generation)) - return 1; - return Math.Min(MaxCount, requestVal); + if (item == 797 && count >= 2) // Edge case when for some reason the item count for Z-Ring was 2 in an unedited save and set 1 after using PKHeX + count = 2; + else + count = MaxCount; // Cap at pouch maximum + } + else if (count <= 0 && !HasNew) + { + return false; } - public abstract InventoryItem GetEmpty(int itemID = 0, int count = 0); + count = GetSuggestedItemCount(sav, item, count); + return true; } - public static class InventoryPouchExtensions + private int GetSuggestedItemCount(ITrainerInfo sav, int item, int requestVal = 1) { - public static IReadOnlyList LoadAll(this IReadOnlyList value, byte[] data) - { - foreach (var p in value) - p.GetPouch(data); - return value; - } + if (sav is SAV7b) // mixed pouch count caps + return InventoryPouch7b.GetSuggestedCount(Type, item, requestVal); + if (sav is SAV8SWSH) + return InventoryPouch8.GetSuggestedCount(Type, item, requestVal); + if (sav is not SAV8BS && ItemConverter.IsItemHM((ushort)item, sav.Generation)) + return 1; + return Math.Min(MaxCount, requestVal); + } - public static void SaveAll(this IReadOnlyList value, byte[] data) - { - foreach (var p in value) - p.SetPouch(data); - } + public abstract InventoryItem GetEmpty(int itemID = 0, int count = 0); +} + +public static class InventoryPouchExtensions +{ + public static IReadOnlyList LoadAll(this IReadOnlyList value, byte[] data) + { + foreach (var p in value) + p.GetPouch(data); + return value; + } + + public static void SaveAll(this IReadOnlyList value, byte[] data) + { + foreach (var p in value) + p.SetPouch(data); } } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch3.cs b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch3.cs index 6a0165dd6..f76a5ccbd 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch3.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch3.cs @@ -1,48 +1,47 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class InventoryPouch3 : InventoryPouch { - public sealed class InventoryPouch3 : InventoryPouch + public uint SecurityKey { private get; set; } // = 0 // Gen3 Only + + public InventoryPouch3(InventoryType type, ushort[] legal, int maxCount, int offset, int size) + : base(type, legal, maxCount, offset, size) { - public uint SecurityKey { private get; set; } // = 0 // Gen3 Only - - public InventoryPouch3(InventoryType type, ushort[] legal, int maxCount, int offset, int size) - : base(type, legal, maxCount, offset, size) - { - } - - public override void GetPouch(ReadOnlySpan data) - { - var span = data[Offset..]; - var items = new InventoryItem[PouchDataSize]; - for (int i = 0; i < items.Length; i++) - { - var entry = span.Slice(i * 4, 4); - items[i] = new InventoryItem - { - Index = ReadUInt16LittleEndian(entry), - Count = ReadUInt16LittleEndian(entry[2..]) ^ (ushort) SecurityKey, - }; - } - Items = items; - } - - public override void SetPouch(Span data) - { - if (Items.Length != PouchDataSize) - throw new ArgumentException("Item array length does not match original pouch size."); - - var span = data[Offset..]; - for (int i = 0; i < Items.Length; i++) - { - var item = Items[i]; - var entry = span.Slice(i * 4, 4); - WriteUInt16LittleEndian(entry, (ushort)item.Index); - WriteUInt16LittleEndian(entry[2..], (ushort)(item.Count ^ (int)SecurityKey)); - } - } - - public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new() { Index = itemID, Count = count }; } + + public override void GetPouch(ReadOnlySpan data) + { + var span = data[Offset..]; + var items = new InventoryItem[PouchDataSize]; + for (int i = 0; i < items.Length; i++) + { + var entry = span.Slice(i * 4, 4); + items[i] = new InventoryItem + { + Index = ReadUInt16LittleEndian(entry), + Count = ReadUInt16LittleEndian(entry[2..]) ^ (ushort) SecurityKey, + }; + } + Items = items; + } + + public override void SetPouch(Span data) + { + if (Items.Length != PouchDataSize) + throw new ArgumentException("Item array length does not match original pouch size."); + + var span = data[Offset..]; + for (int i = 0; i < Items.Length; i++) + { + var item = Items[i]; + var entry = span.Slice(i * 4, 4); + WriteUInt16LittleEndian(entry, (ushort)item.Index); + WriteUInt16LittleEndian(entry[2..], (ushort)(item.Count ^ (int)SecurityKey)); + } + } + + public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new() { Index = itemID, Count = count }; } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch3GC.cs b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch3GC.cs index e72f0a851..78fab88cd 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch3GC.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch3GC.cs @@ -1,46 +1,45 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class InventoryPouch3GC : InventoryPouch { - public sealed class InventoryPouch3GC : InventoryPouch + public InventoryPouch3GC(InventoryType type, ushort[] legal, int maxCount, int offset, int size) + : base(type, legal, maxCount, offset, size) { - public InventoryPouch3GC(InventoryType type, ushort[] legal, int maxCount, int offset, int size) - : base(type, legal, maxCount, offset, size) - { - } + } - public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new() { Index = itemID, Count = count }; + public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new() { Index = itemID, Count = count }; - public override void GetPouch(ReadOnlySpan data) + public override void GetPouch(ReadOnlySpan data) + { + var span = data[Offset..]; + var items = new InventoryItem[PouchDataSize]; + for (int i = 0; i < items.Length; i++) { - var span = data[Offset..]; - var items = new InventoryItem[PouchDataSize]; - for (int i = 0; i < items.Length; i++) + var item = span.Slice(i * 4, 4); + items[i] = new InventoryItem { - var item = span.Slice(i * 4, 4); - items[i] = new InventoryItem - { - Index = ReadUInt16BigEndian(item), - Count = ReadUInt16BigEndian(item[2..]), - }; - } - Items = items; + Index = ReadUInt16BigEndian(item), + Count = ReadUInt16BigEndian(item[2..]), + }; } + Items = items; + } - public override void SetPouch(Span data) + public override void SetPouch(Span data) + { + if (Items.Length != PouchDataSize) + throw new ArgumentException("Item array length does not match original pouch size."); + + var span = data[Offset..]; + for (int i = 0; i < Items.Length; i++) { - if (Items.Length != PouchDataSize) - throw new ArgumentException("Item array length does not match original pouch size."); - - var span = data[Offset..]; - for (int i = 0; i < Items.Length; i++) - { - var item = Items[i]; - var slice = span.Slice(i * 4, 4); - WriteUInt16BigEndian(slice, (ushort)item.Index); - WriteUInt16BigEndian(slice[2..], (ushort)item.Count); - } + var item = Items[i]; + var slice = span.Slice(i * 4, 4); + WriteUInt16BigEndian(slice, (ushort)item.Index); + WriteUInt16BigEndian(slice[2..], (ushort)item.Count); } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch4.cs b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch4.cs index e8bbacc7c..331980085 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch4.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch4.cs @@ -1,53 +1,52 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Inventory Pouch with 4 bytes per item (u16 ID, u16 count) +/// +public sealed class InventoryPouch4 : InventoryPouch { - /// - /// Inventory Pouch with 4 bytes per item (u16 ID, u16 count) - /// - public sealed class InventoryPouch4 : InventoryPouch + public InventoryPouch4(InventoryType type, ushort[] legal, int maxCount, int offset) + : base(type, legal, maxCount, offset) { - public InventoryPouch4(InventoryType type, ushort[] legal, int maxCount, int offset) - : base(type, legal, maxCount, offset) + } + + // size: 32bit + // u16 id + // u16 count + + public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new() { Index = itemID, Count = count }; + + public override void GetPouch(ReadOnlySpan data) + { + var span = data[Offset..]; + var items = new InventoryItem[PouchDataSize]; + for (int i = 0; i < items.Length; i++) { - } - - // size: 32bit - // u16 id - // u16 count - - public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new() { Index = itemID, Count = count }; - - public override void GetPouch(ReadOnlySpan data) - { - var span = data[Offset..]; - var items = new InventoryItem[PouchDataSize]; - for (int i = 0; i < items.Length; i++) + var entry = span.Slice(i * 4, 4); + items[i] = new InventoryItem { - var entry = span.Slice(i * 4, 4); - items[i] = new InventoryItem - { - Index = ReadUInt16LittleEndian(entry), - Count = ReadUInt16LittleEndian(entry[2..]), - }; - } - Items = items; + Index = ReadUInt16LittleEndian(entry), + Count = ReadUInt16LittleEndian(entry[2..]), + }; } + Items = items; + } - public override void SetPouch(Span data) + public override void SetPouch(Span data) + { + if (Items.Length != PouchDataSize) + throw new ArgumentException("Item array length does not match original pouch size."); + + var span = data[Offset..]; + for (int i = 0; i < Items.Length; i++) { - if (Items.Length != PouchDataSize) - throw new ArgumentException("Item array length does not match original pouch size."); - - var span = data[Offset..]; - for (int i = 0; i < Items.Length; i++) - { - var item = Items[i]; - var entry = span.Slice(i * 4, 4); - WriteUInt16LittleEndian(entry, (ushort)item.Index); - WriteUInt16LittleEndian(entry[2..], (ushort)item.Count); - } + var item = Items[i]; + var entry = span.Slice(i * 4, 4); + WriteUInt16LittleEndian(entry, (ushort)item.Index); + WriteUInt16LittleEndian(entry[2..], (ushort)item.Count); } } -} \ No newline at end of file +} diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch7.cs b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch7.cs index e0f9c4483..46351d5df 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch7.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch7.cs @@ -1,45 +1,44 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class InventoryPouch7 : InventoryPouch { - public sealed class InventoryPouch7 : InventoryPouch + public bool SetNew { get; set; } + private int[] OriginalItems = Array.Empty(); + + public InventoryPouch7(InventoryType type, ushort[] legal, int maxCount, int offset) + : base(type, legal, maxCount, offset) { } + + public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new InventoryItem7 { Index = itemID, Count = count }; + + public override void GetPouch(ReadOnlySpan data) { - public bool SetNew { get; set; } - private int[] OriginalItems = Array.Empty(); - - public InventoryPouch7(InventoryType type, ushort[] legal, int maxCount, int offset) - : base(type, legal, maxCount, offset) { } - - public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new InventoryItem7 { Index = itemID, Count = count }; - - public override void GetPouch(ReadOnlySpan data) + var span = data[Offset..]; + var items = new InventoryItem7[PouchDataSize]; + for (int i = 0; i < items.Length; i++) { - var span = data[Offset..]; - var items = new InventoryItem7[PouchDataSize]; - for (int i = 0; i < items.Length; i++) - { - var item = span.Slice(i * 4, 4); - uint val = ReadUInt32LittleEndian(item); - items[i] = InventoryItem7.GetValue(val); - } - Items = items; - OriginalItems = Array.ConvertAll(items, z => z.Index); + var item = span.Slice(i * 4, 4); + uint val = ReadUInt32LittleEndian(item); + items[i] = InventoryItem7.GetValue(val); } + Items = items; + OriginalItems = Array.ConvertAll(items, z => z.Index); + } - public override void SetPouch(Span data) + public override void SetPouch(Span data) + { + if (Items.Length != PouchDataSize) + throw new ArgumentException("Item array length does not match original pouch size."); + + var span = data[Offset..]; + var items = (InventoryItem7[])Items; + for (int i = 0; i < Items.Length; i++) { - if (Items.Length != PouchDataSize) - throw new ArgumentException("Item array length does not match original pouch size."); - - var span = data[Offset..]; - var items = (InventoryItem7[])Items; - for (int i = 0; i < Items.Length; i++) - { - var item = span.Slice(i * 4, 4); - var value = items[i].GetValue(SetNew, OriginalItems); - WriteUInt32LittleEndian(item, value); - } + var item = span.Slice(i * 4, 4); + var value = items[i].GetValue(SetNew, OriginalItems); + WriteUInt32LittleEndian(item, value); } } } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch7b.cs b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch7b.cs index 7cda8a180..34e596f62 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch7b.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch7b.cs @@ -1,75 +1,74 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Inventory Pouch used by +/// +public sealed class InventoryPouch7b : InventoryPouch { - /// - /// Inventory Pouch used by - /// - public sealed class InventoryPouch7b : InventoryPouch + public bool SetNew { get; set; } + private int[] OriginalItems = Array.Empty(); + + public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new InventoryItem7b { Index = itemID, Count = count }; + + public InventoryPouch7b(InventoryType type, ushort[] legal, int maxCount, int offset, int size) + : base(type, legal, maxCount, offset, size) { } + + public override void GetPouch(ReadOnlySpan data) { - public bool SetNew { get; set; } - private int[] OriginalItems = Array.Empty(); - - public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new InventoryItem7b { Index = itemID, Count = count }; - - public InventoryPouch7b(InventoryType type, ushort[] legal, int maxCount, int offset, int size) - : base(type, legal, maxCount, offset, size) { } - - public override void GetPouch(ReadOnlySpan data) + var span = data[Offset..]; + var items = new InventoryItem7b[PouchDataSize]; + for (int i = 0; i < items.Length; i++) { - var span = data[Offset..]; - var items = new InventoryItem7b[PouchDataSize]; - for (int i = 0; i < items.Length; i++) - { - var item = span.Slice(i * 4, 4); - uint val = ReadUInt32LittleEndian(item); - items[i] = InventoryItem7b.GetValue(val); - } - Items = items; - OriginalItems = Array.ConvertAll(items, z => z.Index); + var item = span.Slice(i * 4, 4); + uint val = ReadUInt32LittleEndian(item); + items[i] = InventoryItem7b.GetValue(val); } + Items = items; + OriginalItems = Array.ConvertAll(items, z => z.Index); + } - public override void SetPouch(Span data) + public override void SetPouch(Span data) + { + if (Items.Length != PouchDataSize) + throw new ArgumentException("Item array length does not match original pouch size."); + + var span = data[Offset..]; + var items = (InventoryItem7b[])Items; + for (int i = 0; i < items.Length; i++) { - if (Items.Length != PouchDataSize) - throw new ArgumentException("Item array length does not match original pouch size."); - - var span = data[Offset..]; - var items = (InventoryItem7b[])Items; - for (int i = 0; i < items.Length; i++) - { - var item = span.Slice(i * 4, 4); - uint val = items[i].GetValue(SetNew, OriginalItems); - WriteUInt32LittleEndian(item, val); - } + var item = span.Slice(i * 4, 4); + uint val = items[i].GetValue(SetNew, OriginalItems); + WriteUInt32LittleEndian(item, val); } + } - /// - /// Checks pouch contents for bad count values. - /// - /// - /// Certain pouches contain a mix of count-limited items and uncapped regular items. - /// - internal void SanitizeCounts() + /// + /// Checks pouch contents for bad count values. + /// + /// + /// Certain pouches contain a mix of count-limited items and uncapped regular items. + /// + internal void SanitizeCounts() + { + foreach (var item in Items) + item.Count = GetSuggestedCount(Type, item.Index, item.Count); + } + + public static int GetSuggestedCount(InventoryType t, int item, int requestVal) + { + switch (t) { - foreach (var item in Items) - item.Count = GetSuggestedCount(Type, item.Index, item.Count); - } + // mixed regular battle items & mega stones + case InventoryType.BattleItems when item > 100: + // mixed regular items & key items + case InventoryType.Items when Array.IndexOf(Legal.Pouch_Regular_GG_Key, (ushort)item) != -1: + return Math.Min(1, requestVal); - public static int GetSuggestedCount(InventoryType t, int item, int requestVal) - { - switch (t) - { - // mixed regular battle items & mega stones - case InventoryType.BattleItems when item > 100: - // mixed regular items & key items - case InventoryType.Items when Array.IndexOf(Legal.Pouch_Regular_GG_Key, (ushort)item) != -1: - return Math.Min(1, requestVal); - - default: - return requestVal; - } + default: + return requestVal; } } } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch8.cs b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch8.cs index 8fb5d0325..8a3d09fe1 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch8.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch8.cs @@ -1,67 +1,66 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Inventory Pouch used by +/// +public sealed class InventoryPouch8 : InventoryPouch { - /// - /// Inventory Pouch used by - /// - public sealed class InventoryPouch8 : InventoryPouch + public bool SetNew { get; set; } + private int[] OriginalItems = Array.Empty(); + + public InventoryPouch8(InventoryType type, ushort[] legal, int maxCount, int offset, int size, Func? isLegal = null) + : base(type, legal, maxCount, offset, size, isLegal) { } + + public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new InventoryItem8 { Index = itemID, Count = count }; + + public override void GetPouch(ReadOnlySpan data) { - public bool SetNew { get; set; } - private int[] OriginalItems = Array.Empty(); - - public InventoryPouch8(InventoryType type, ushort[] legal, int maxCount, int offset, int size, Func? isLegal = null) - : base(type, legal, maxCount, offset, size, isLegal) { } - - public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new InventoryItem8 { Index = itemID, Count = count }; - - public override void GetPouch(ReadOnlySpan data) + var span = data[Offset..]; + var items = new InventoryItem8[PouchDataSize]; + for (int i = 0; i < items.Length; i++) { - var span = data[Offset..]; - var items = new InventoryItem8[PouchDataSize]; - for (int i = 0; i < items.Length; i++) - { - var item = span.Slice(i * 4, 4); - uint value = ReadUInt32LittleEndian(item); - items[i] = InventoryItem8.GetValue(value); - } - Items = items; - OriginalItems = Array.ConvertAll(items, z => z.Index); + var item = span.Slice(i * 4, 4); + uint value = ReadUInt32LittleEndian(item); + items[i] = InventoryItem8.GetValue(value); } - - public override void SetPouch(Span data) - { - if (Items.Length != PouchDataSize) - throw new ArgumentException("Item array length does not match original pouch size."); - - var span = data[Offset..]; - var items = (InventoryItem8[])Items; - for (int i = 0; i < items.Length; i++) - { - var item = span.Slice(i * 4, 4); - uint val = items[i].GetValue(SetNew, OriginalItems); - WriteUInt32LittleEndian(item, val); - } - } - - /// - /// Checks pouch contents for bad count values. - /// - /// - /// Certain pouches contain a mix of count-limited items and uncapped regular items. - /// - internal void SanitizeCounts() - { - foreach (var item in Items) - item.Count = GetSuggestedCount(Type, item.Index, item.Count); - } - - public static int GetSuggestedCount(InventoryType t, int item, int requestVal) => t switch - { - // TMs are clamped to 1, let TRs be whatever - InventoryType.TMHMs => item is >= 1130 and <= 1229 ? requestVal : 1, - _ => requestVal, - }; + Items = items; + OriginalItems = Array.ConvertAll(items, z => z.Index); } + + public override void SetPouch(Span data) + { + if (Items.Length != PouchDataSize) + throw new ArgumentException("Item array length does not match original pouch size."); + + var span = data[Offset..]; + var items = (InventoryItem8[])Items; + for (int i = 0; i < items.Length; i++) + { + var item = span.Slice(i * 4, 4); + uint val = items[i].GetValue(SetNew, OriginalItems); + WriteUInt32LittleEndian(item, val); + } + } + + /// + /// Checks pouch contents for bad count values. + /// + /// + /// Certain pouches contain a mix of count-limited items and uncapped regular items. + /// + internal void SanitizeCounts() + { + foreach (var item in Items) + item.Count = GetSuggestedCount(Type, item.Index, item.Count); + } + + public static int GetSuggestedCount(InventoryType t, int item, int requestVal) => t switch + { + // TMs are clamped to 1, let TRs be whatever + InventoryType.TMHMs => item is >= 1130 and <= 1229 ? requestVal : 1, + _ => requestVal, + }; } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch8b.cs b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch8b.cs index 08d9b1cb9..933f29f6f 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch8b.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouch8b.cs @@ -2,88 +2,87 @@ using System.Collections.Generic; using System.Diagnostics; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class InventoryPouch8b : InventoryPouch { - public sealed class InventoryPouch8b : InventoryPouch + public bool SetNew { get; set; } + + public InventoryPouch8b(InventoryType type, ushort[] legal, int maxCount, int offset, + Func? isLegal) + : base(type, legal, maxCount, offset, isLegal: isLegal) { } + + public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new InventoryItem8b { Index = itemID, Count = count, IsNew = true }; + + public override void GetPouch(ReadOnlySpan data) { - public bool SetNew { get; set; } + var items = new InventoryItem8b[LegalItems.Length]; - public InventoryPouch8b(InventoryType type, ushort[] legal, int maxCount, int offset, - Func? isLegal) - : base(type, legal, maxCount, offset, isLegal: isLegal) { } - - public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new InventoryItem8b { Index = itemID, Count = count, IsNew = true }; - - public override void GetPouch(ReadOnlySpan data) + int ctr = 0; + foreach (var index in LegalItems) { - var items = new InventoryItem8b[LegalItems.Length]; - - int ctr = 0; - foreach (var index in LegalItems) - { - var item = GetItem(data, index); - if (!item.IsValidSaveSortNumberCount) - continue; - items[ctr++] = item; - } - while (ctr != LegalItems.Length) - items[ctr++] = new InventoryItem8b(); - - Items = items; - SortBy(z => !z.IsValidSaveSortNumberCount ? (ushort)0xFFFF : z.SortOrder); + var item = GetItem(data, index); + if (!item.IsValidSaveSortNumberCount) + continue; + items[ctr++] = item; } + while (ctr != LegalItems.Length) + items[ctr++] = new InventoryItem8b(); - public InventoryItem8b GetItem(ReadOnlySpan data, ushort itemID) - { - var ofs = GetItemOffset(itemID, Offset); - return InventoryItem8b.Read(itemID, data[ofs..]); - } - - public override void SetPouch(Span data) - { - HashSet processed = new(); - - // Write all the item slots still present in the pouch. Keep track of the item IDs processed. - var items = (InventoryItem8b[])Items; - for (int i = 0; i < items.Length; i++) - items[i].SortOrder = (ushort)(i + 1); - - foreach (var item in items) - { - var index = (ushort)item.Index; - if (index == 0) - continue; - var isInLegal = Array.IndexOf(LegalItems, index); - if (isInLegal == -1) - { - Debug.WriteLine($"Invalid Item ID returned within this pouch: {index}"); - continue; - } - - if (SetNew && item.Index != 0) - { - var original = GetItem(data, (ushort)item.Index); - item.IsNew |= !original.IsValidSaveSortNumberCount; - } - - var ofs = GetItemOffset(index, Offset); - item.Write(data[ofs..]); - - if (!processed.Contains(index)) // we will allow duplicate item definitions, but they'll overwrite instead of sum/separate. - processed.Add(index); - } - - // For all the items that were not present in the pouch, clear the data for them. - foreach (var index in LegalItems) - { - if (processed.Contains(index)) - continue; - ClearItem(data, index); - } - } - - public static int GetItemOffset(ushort index, int baseOffset) => baseOffset + (InventoryItem8b.SIZE * index); - - public void ClearItem(Span data, ushort index) => InventoryItem8b.Clear(data, GetItemOffset(index, Offset)); + Items = items; + SortBy(z => !z.IsValidSaveSortNumberCount ? (ushort)0xFFFF : z.SortOrder); } + + public InventoryItem8b GetItem(ReadOnlySpan data, ushort itemID) + { + var ofs = GetItemOffset(itemID, Offset); + return InventoryItem8b.Read(itemID, data[ofs..]); + } + + public override void SetPouch(Span data) + { + HashSet processed = new(); + + // Write all the item slots still present in the pouch. Keep track of the item IDs processed. + var items = (InventoryItem8b[])Items; + for (int i = 0; i < items.Length; i++) + items[i].SortOrder = (ushort)(i + 1); + + foreach (var item in items) + { + var index = (ushort)item.Index; + if (index == 0) + continue; + var isInLegal = Array.IndexOf(LegalItems, index); + if (isInLegal == -1) + { + Debug.WriteLine($"Invalid Item ID returned within this pouch: {index}"); + continue; + } + + if (SetNew && item.Index != 0) + { + var original = GetItem(data, (ushort)item.Index); + item.IsNew |= !original.IsValidSaveSortNumberCount; + } + + var ofs = GetItemOffset(index, Offset); + item.Write(data[ofs..]); + + if (!processed.Contains(index)) // we will allow duplicate item definitions, but they'll overwrite instead of sum/separate. + processed.Add(index); + } + + // For all the items that were not present in the pouch, clear the data for them. + foreach (var index in LegalItems) + { + if (processed.Contains(index)) + continue; + ClearItem(data, index); + } + } + + public static int GetItemOffset(ushort index, int baseOffset) => baseOffset + (InventoryItem8b.SIZE * index); + + public void ClearItem(Span data, ushort index) => InventoryItem8b.Clear(data, GetItemOffset(index, Offset)); } diff --git a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouchGB.cs b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouchGB.cs index 87cf53891..a5011be3f 100644 --- a/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouchGB.cs +++ b/PKHeX.Core/Saves/Substructures/Inventory/Pouch/InventoryPouchGB.cs @@ -1,105 +1,104 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class InventoryPouchGB : InventoryPouch { - public sealed class InventoryPouchGB : InventoryPouch + public InventoryPouchGB(InventoryType type, ushort[] legal, int maxCount, int offset, int size) + : base(type, legal, maxCount, offset, size) { - public InventoryPouchGB(InventoryType type, ushort[] legal, int maxCount, int offset, int size) - : base(type, legal, maxCount, offset, size) - { - } + } - public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new() { Index = itemID, Count = count }; + public override InventoryItem GetEmpty(int itemID = 0, int count = 0) => new() { Index = itemID, Count = count }; - public override void GetPouch(ReadOnlySpan data) + public override void GetPouch(ReadOnlySpan data) + { + var items = new InventoryItem[PouchDataSize]; + if (Type == InventoryType.TMHMs) { - var items = new InventoryItem[PouchDataSize]; - if (Type == InventoryType.TMHMs) + int slot = 0; + for (int i = 0; i < items.Length; i++) { - int slot = 0; - for (int i = 0; i < items.Length; i++) - { - if (data[Offset + i] != 0) - { - items[slot++] = new InventoryItem - { - Index = LegalItems[i], - Count = data[Offset + i], - }; - } - } - while (slot < items.Length) + if (data[Offset + i] != 0) { items[slot++] = new InventoryItem { - Index = 0, - Count = 0, + Index = LegalItems[i], + Count = data[Offset + i], }; } } - else + while (slot < items.Length) { - int numStored = data[Offset]; - if (numStored > PouchDataSize) // uninitialized yellow (0xFF), sanity check for out-of-bounds values - numStored = 0; - for (int i = 0; i < numStored; i++) + items[slot++] = new InventoryItem { - items[i] = Type switch - { - InventoryType.KeyItems => - new InventoryItem {Index = data[Offset + i + 1], Count = 1}, - _ => - new InventoryItem {Index = data[Offset + (i * 2) + 1], Count = data[Offset + (i * 2) + 2]}, - }; - } - for (int i = numStored; i < items.Length; i++) - { - items[i] = new InventoryItem - { - Index = 0, - Count = 0, - }; - } + Index = 0, + Count = 0, + }; } - Items = items; } - - public override void SetPouch(Span data) + else { - if (Items.Length != PouchDataSize) - throw new ArgumentException("Item array length does not match original pouch size."); - - ClearCount0(); - - switch (Type) + int numStored = data[Offset]; + if (numStored > PouchDataSize) // uninitialized yellow (0xFF), sanity check for out-of-bounds values + numStored = 0; + for (int i = 0; i < numStored; i++) { - case InventoryType.TMHMs: - foreach (InventoryItem t in Items) - { - int index = Array.FindIndex(LegalItems, it => t.Index == it); - if (index < 0) // enforce correct pouch - continue; - data[Offset + index] = (byte)t.Count; - } - break; - case InventoryType.KeyItems: - for (int i = 0; i < Items.Length; i++) - { - data[Offset + i + 1] = (byte)Items[i].Index; - } - data[Offset] = (byte)Count; - data[Offset + 1 + Count] = 0xFF; - break; - default: - for (int i = 0; i < Items.Length; i++) - { - data[Offset + (i * 2) + 1] = (byte)Items[i].Index; - data[Offset + (i * 2) + 2] = (byte)Items[i].Count; - } - data[Offset] = (byte)Count; - data[Offset + 1 + (2 * Count)] = 0xFF; - break; + items[i] = Type switch + { + InventoryType.KeyItems => + new InventoryItem {Index = data[Offset + i + 1], Count = 1}, + _ => + new InventoryItem {Index = data[Offset + (i * 2) + 1], Count = data[Offset + (i * 2) + 2]}, + }; } + for (int i = numStored; i < items.Length; i++) + { + items[i] = new InventoryItem + { + Index = 0, + Count = 0, + }; + } + } + Items = items; + } + + public override void SetPouch(Span data) + { + if (Items.Length != PouchDataSize) + throw new ArgumentException("Item array length does not match original pouch size."); + + ClearCount0(); + + switch (Type) + { + case InventoryType.TMHMs: + foreach (InventoryItem t in Items) + { + int index = Array.FindIndex(LegalItems, it => t.Index == it); + if (index < 0) // enforce correct pouch + continue; + data[Offset + index] = (byte)t.Count; + } + break; + case InventoryType.KeyItems: + for (int i = 0; i < Items.Length; i++) + { + data[Offset + i + 1] = (byte)Items[i].Index; + } + data[Offset] = (byte)Count; + data[Offset + 1 + Count] = 0xFF; + break; + default: + for (int i = 0; i < Items.Length; i++) + { + data[Offset + (i * 2) + 1] = (byte)Items[i].Index; + data[Offset + (i * 2) + 2] = (byte)Items[i].Count; + } + data[Offset] = (byte)Count; + data[Offset + 1 + (2 * Count)] = 0xFF; + break; } } } diff --git a/PKHeX.Core/Saves/Substructures/Mail/Mail.cs b/PKHeX.Core/Saves/Substructures/Mail/Mail.cs deleted file mode 100644 index 44191ec3b..000000000 --- a/PKHeX.Core/Saves/Substructures/Mail/Mail.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace PKHeX.Core -{ - public abstract class Mail - { - protected readonly byte[] Data; - protected readonly int DataOffset; - - protected Mail(byte[] data, int offset = 0) - { - Data = data; - DataOffset = offset; - } - - public virtual void CopyTo(SaveFile sav) => sav.SetData(Data, DataOffset); - public virtual void CopyTo(PK4 pk4) { } - public virtual void CopyTo(PK5 pk5) { } - public virtual string GetMessage(bool isLastLine) => string.Empty; - public virtual ushort GetMessage(int index1, int index2) => 0; - public virtual void SetMessage(string line1, string line2) { } - public virtual void SetMessage(int index1, int index2, ushort value) { } - public virtual string AuthorName { get; set; } = string.Empty; - public virtual ushort AuthorTID { get; set; } - public virtual ushort AuthorSID { get; set; } - public virtual byte AuthorVersion { get; set; } - public virtual byte AuthorLanguage { get; set; } - public virtual byte AuthorGender { get; set; } - public virtual int AppearPKM { get; set; } - public virtual int MailType { get; set; } - public abstract bool? IsEmpty { get; } // true: empty, false: legal mail, null: illegal mail - public virtual void SetBlank() { } - } -} \ No newline at end of file diff --git a/PKHeX.Core/Saves/Substructures/Mail/Mail2.cs b/PKHeX.Core/Saves/Substructures/Mail/Mail2.cs index fbdbc54d6..421d3f21b 100644 --- a/PKHeX.Core/Saves/Substructures/Mail/Mail2.cs +++ b/PKHeX.Core/Saves/Substructures/Mail/Mail2.cs @@ -4,7 +4,7 @@ namespace PKHeX.Core; // warning: international only -public sealed class Mail2 : Mail +public sealed class Mail2 : MailDetail { private readonly bool US; private bool Japanese => !US; diff --git a/PKHeX.Core/Saves/Substructures/Mail/Mail3.cs b/PKHeX.Core/Saves/Substructures/Mail/Mail3.cs index 236d1e1bc..02238151d 100644 --- a/PKHeX.Core/Saves/Substructures/Mail/Mail3.cs +++ b/PKHeX.Core/Saves/Substructures/Mail/Mail3.cs @@ -1,57 +1,56 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Mail3 : MailDetail { - public sealed class Mail3 : Mail + public const int SIZE = 0x24; + private readonly bool JP; + + public Mail3() : base(new byte[SIZE], -1) => ResetData(); + public Mail3(byte[] data, int ofs, bool japanese) : base(data, ofs) => JP = japanese; + + private void ResetData() { - public const int SIZE = 0x24; - private readonly bool JP; - - public Mail3() : base(new byte[SIZE], -1) => ResetData(); - public Mail3(byte[] data, int ofs, bool japanese) : base(data, ofs) => JP = japanese; - - private void ResetData() + for (int y = 0; y < 3; y++) { - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 3; x++) - SetMessage(y, x, 0xFFFF); - } - - AuthorName = string.Empty; - AuthorTID = 0; - AuthorTID = 0; - AppearPKM = 1; - MailType = 0; + for (int x = 0; x < 3; x++) + SetMessage(y, x, 0xFFFF); } - public override ushort GetMessage(int index1, int index2) => ReadUInt16LittleEndian(Data.AsSpan(((index1 * 3) + index2) * 2)); - public override void SetMessage(int index1, int index2, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(((index1 * 3) + index2) * 2), value); - public override void CopyTo(SaveFile sav) => sav.SetData(((SAV3)sav).Large, DataOffset); - - public override string AuthorName - { - get => StringConverter3.GetString(Data.AsSpan(0x12, 7), JP); - set - { - var span = Data.AsSpan(0x12, 8); - StringConverter3.SetString(span, value.AsSpan(), 7, JP, StringConverterOption.ClearFF); - } - } - - public override ushort AuthorTID { get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), value); } - public override ushort AuthorSID { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), value); } - public override int AppearPKM { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)(value == 0 ? 1 : value)); } - public override int MailType { get => ReadUInt16LittleEndian(Data.AsSpan(0x20)); set => WriteUInt16LittleEndian(Data.AsSpan(0x20), (ushort)value); } - - public override bool? IsEmpty => MailType switch - { - 0 => true, - >= 0x79 and <= 0x84 => false, - _ => null, - }; - - public override void SetBlank() => (new Mail3()).Data.CopyTo(Data, 0); + AuthorName = string.Empty; + AuthorTID = 0; + AuthorTID = 0; + AppearPKM = 1; + MailType = 0; } -} \ No newline at end of file + + public override ushort GetMessage(int index1, int index2) => ReadUInt16LittleEndian(Data.AsSpan(((index1 * 3) + index2) * 2)); + public override void SetMessage(int index1, int index2, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(((index1 * 3) + index2) * 2), value); + public override void CopyTo(SaveFile sav) => sav.SetData(((SAV3)sav).Large, DataOffset); + + public override string AuthorName + { + get => StringConverter3.GetString(Data.AsSpan(0x12, 7), JP); + set + { + var span = Data.AsSpan(0x12, 8); + StringConverter3.SetString(span, value.AsSpan(), 7, JP, StringConverterOption.ClearFF); + } + } + + public override ushort AuthorTID { get => ReadUInt16LittleEndian(Data.AsSpan(0x1A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1A), value); } + public override ushort AuthorSID { get => ReadUInt16LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1C), value); } + public override int AppearPKM { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), (ushort)(value == 0 ? 1 : value)); } + public override int MailType { get => ReadUInt16LittleEndian(Data.AsSpan(0x20)); set => WriteUInt16LittleEndian(Data.AsSpan(0x20), (ushort)value); } + + public override bool? IsEmpty => MailType switch + { + 0 => true, + >= 0x79 and <= 0x84 => false, + _ => null, + }; + + public override void SetBlank() => (new Mail3()).Data.CopyTo(Data, 0); +} diff --git a/PKHeX.Core/Saves/Substructures/Mail/Mail4.cs b/PKHeX.Core/Saves/Substructures/Mail/Mail4.cs index 68f43aecd..ff58f758f 100644 --- a/PKHeX.Core/Saves/Substructures/Mail/Mail4.cs +++ b/PKHeX.Core/Saves/Substructures/Mail/Mail4.cs @@ -1,68 +1,67 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Mail4 : MailDetail { - public sealed class Mail4 : Mail + public const int SIZE = 0x38; + + public Mail4(byte[] data, int ofs) : base(data, ofs) { } + + public Mail4(byte[] data) : base(data, -1) { } + + public Mail4(byte? lang, byte? ver) : base(new byte[SIZE]) { - public const int SIZE = 0x38; + if (lang != null) AuthorLanguage = (byte)lang; + if (ver != null) AuthorVersion = (byte)ver; + ResetData(); + } - public Mail4(byte[] data, int ofs) : base(data, ofs) { } + public override void CopyTo(SaveFile sav) => sav.SetData(((SAV4)sav).General, Data, DataOffset); - public Mail4(byte[] data) : base(data, -1) { } - - public Mail4(byte? lang, byte? ver) : base(new byte[SIZE]) + private void ResetData() + { + AuthorTID = 0; + AuthorSID = 0; + AuthorGender = 0; + MailType = 0xFF; + AuthorName = string.Empty; + for (int i = 0; i < 3; i++) + SetAppearSpecies(i, 0xFFFF); + for (int y = 0; y < 3; y++) { - if (lang != null) AuthorLanguage = (byte)lang; - if (ver != null) AuthorVersion = (byte)ver; - ResetData(); - } - - public override void CopyTo(SaveFile sav) => sav.SetData(((SAV4)sav).General, Data, DataOffset); - - private void ResetData() - { - AuthorTID = 0; - AuthorSID = 0; - AuthorGender = 0; - MailType = 0xFF; - AuthorName = string.Empty; - for (int i = 0; i < 3; i++) - SetAppearPKM(i, 0xFFFF); - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 4; x++) - SetMessage(y, x, x == 1 ? (ushort)0 : (ushort)0xFFFF); - } - } - - public override void CopyTo(PK4 pk4) => Data.CopyTo(pk4.HeldMail); - public override ushort AuthorTID { get => ReadUInt16LittleEndian(Data.AsSpan(0)); set => WriteUInt16LittleEndian(Data.AsSpan(0), value); } - public override ushort AuthorSID { get => ReadUInt16LittleEndian(Data.AsSpan(2)); set => WriteUInt16LittleEndian(Data.AsSpan(2), value); } - public override byte AuthorGender { get => Data[4]; set => Data[4] = value; } - public override byte AuthorLanguage { get => Data[5]; set => Data[5] = value; } - public override byte AuthorVersion { get => Data[6]; set => Data[6] = value; } - public override int MailType { get => Data[7]; set => Data[7] = (byte)value; } - public override string AuthorName { get => StringConverter4.GetString(Data.AsSpan(8, 0x10)); set => StringConverter4.SetString(Data.AsSpan(8, 0x10), value.AsSpan(), 7, StringConverterOption.ClearFF); } - public int GetAppearPKM(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2))); - public void SetAppearPKM(int index, int value) => WriteUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2)), (ushort)(value == 0 ? 0xFFFF : value)); - public override ushort GetMessage(int index1, int index2) => ReadUInt16LittleEndian(Data.AsSpan(0x20 + (((index1 * 4) + index2) * 2))); - public override void SetMessage(int index1, int index2, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(0x20 + (((index1 * 4) + index2) * 2)), value); - - public override bool? IsEmpty => MailType switch - { - 0xFF => true, - <= 11 => false, - _ => null, - }; - - public override void SetBlank() => SetBlank(0, 0); - - public void SetBlank(byte lang, byte ver) - { - Array.Clear(Data, 0, Data.Length); - AuthorLanguage = lang; - AuthorVersion = ver; + for (int x = 0; x < 4; x++) + SetMessage(y, x, x == 1 ? (ushort)0 : (ushort)0xFFFF); } } -} \ No newline at end of file + + public override void CopyTo(PK4 pk4) => Data.CopyTo(pk4.HeldMail); + public override ushort AuthorTID { get => ReadUInt16LittleEndian(Data.AsSpan(0)); set => WriteUInt16LittleEndian(Data.AsSpan(0), value); } + public override ushort AuthorSID { get => ReadUInt16LittleEndian(Data.AsSpan(2)); set => WriteUInt16LittleEndian(Data.AsSpan(2), value); } + public override byte AuthorGender { get => Data[4]; set => Data[4] = value; } + public override byte AuthorLanguage { get => Data[5]; set => Data[5] = value; } + public override byte AuthorVersion { get => Data[6]; set => Data[6] = value; } + public override int MailType { get => Data[7]; set => Data[7] = (byte)value; } + public override string AuthorName { get => StringConverter4.GetString(Data.AsSpan(8, 0x10)); set => StringConverter4.SetString(Data.AsSpan(8, 0x10), value.AsSpan(), 7, StringConverterOption.ClearFF); } + public int GetAppearSpecies(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2))); + public void SetAppearSpecies(int index, int value) => WriteUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2)), (ushort)(value == 0 ? 0xFFFF : value)); + public override ushort GetMessage(int index1, int index2) => ReadUInt16LittleEndian(Data.AsSpan(0x20 + (((index1 * 4) + index2) * 2))); + public override void SetMessage(int index1, int index2, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(0x20 + (((index1 * 4) + index2) * 2)), value); + + public override bool? IsEmpty => MailType switch + { + 0xFF => true, + <= 11 => false, + _ => null, + }; + + public override void SetBlank() => SetBlank(0, 0); + + public void SetBlank(byte lang, byte ver) + { + Array.Clear(Data, 0, Data.Length); + AuthorLanguage = lang; + AuthorVersion = ver; + } +} diff --git a/PKHeX.Core/Saves/Substructures/Mail/Mail5.cs b/PKHeX.Core/Saves/Substructures/Mail/Mail5.cs index a3c695487..0bc91e2d7 100644 --- a/PKHeX.Core/Saves/Substructures/Mail/Mail5.cs +++ b/PKHeX.Core/Saves/Substructures/Mail/Mail5.cs @@ -1,60 +1,59 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +public sealed class Mail5 : MailDetail { - public sealed class Mail5 : Mail + public const int SIZE = 0x38; + + public Mail5(byte[] data, int ofs = -1) : base(data, ofs) { } + + public Mail5(byte? lang, byte? ver) : base(new byte[SIZE]) { - public const int SIZE = 0x38; - - public Mail5(byte[] data, int ofs = -1) : base(data, ofs) { } - - public Mail5(byte? lang, byte? ver) : base(new byte[SIZE]) - { - if (lang != null) AuthorLanguage = (byte)lang; - if (ver != null) AuthorVersion = (byte)ver; - ResetData(); - } - - private void ResetData() - { - AuthorTID = 0; - AuthorSID = 0; - AuthorGender = 0; - MailType = 0xFF; - AuthorName = string.Empty; - for (int i = 0; i < 3; i++) - SetMisc(i, 0); - MessageEnding = 0xFFFF; - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 4; x++) - SetMessage(y, x, x == 1 ? (ushort)0 : (ushort)0xFFFF); - } - } - - public override void CopyTo(PK5 pk5) => Data.CopyTo(pk5.HeldMail); - public override ushort AuthorTID { get => ReadUInt16LittleEndian(Data.AsSpan(0)); set => WriteUInt16LittleEndian(Data.AsSpan(0), value); } - public override ushort AuthorSID { get => ReadUInt16LittleEndian(Data.AsSpan(2)); set => WriteUInt16LittleEndian(Data.AsSpan(2), value); } - public override byte AuthorGender { get => Data[4]; set => Data[4] = value; } - public override byte AuthorLanguage { get => Data[5]; set => Data[5] = value; } - public override byte AuthorVersion { get => Data[6]; set => Data[6] = value; } - public override int MailType { get => Data[7]; set => Data[7] = (byte)value; } - public override string AuthorName { get => StringConverter5.GetString(Data.AsSpan(8, 0x10)); set => StringConverter5.SetString(Data.AsSpan(8, 0x10), value.AsSpan(), 7, StringConverterOption.ClearZero); } - public int GetMisc(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2))); - public void SetMisc(int index, int value) => WriteUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2)), (ushort)value); - public ushort MessageEnding { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), value); } - public override ushort GetMessage(int index1, int index2) => ReadUInt16LittleEndian(Data.AsSpan(0x20 + (((index1 * 4) + index2) * 2))); - public override void SetMessage(int index1, int index2, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(0x20 + (((index1 * 4) + index2) * 2)), value); - - public override bool? IsEmpty => MailType switch - { - 0xFF => true, - <= 11 => false, - _ => null, - }; - - public override void SetBlank() => SetBlank(null, null); - public void SetBlank(byte? lang, byte? ver) => new Mail5(lang: lang, ver: ver).Data.CopyTo(Data, 0); + if (lang != null) AuthorLanguage = (byte)lang; + if (ver != null) AuthorVersion = (byte)ver; + ResetData(); } + + private void ResetData() + { + AuthorTID = 0; + AuthorSID = 0; + AuthorGender = 0; + MailType = 0xFF; + AuthorName = string.Empty; + for (int i = 0; i < 3; i++) + SetMisc(i, 0); + MessageEnding = 0xFFFF; + for (int y = 0; y < 3; y++) + { + for (int x = 0; x < 4; x++) + SetMessage(y, x, x == 1 ? (ushort)0 : (ushort)0xFFFF); + } + } + + public override void CopyTo(PK5 pk5) => Data.CopyTo(pk5.HeldMail); + public override ushort AuthorTID { get => ReadUInt16LittleEndian(Data.AsSpan(0)); set => WriteUInt16LittleEndian(Data.AsSpan(0), value); } + public override ushort AuthorSID { get => ReadUInt16LittleEndian(Data.AsSpan(2)); set => WriteUInt16LittleEndian(Data.AsSpan(2), value); } + public override byte AuthorGender { get => Data[4]; set => Data[4] = value; } + public override byte AuthorLanguage { get => Data[5]; set => Data[5] = value; } + public override byte AuthorVersion { get => Data[6]; set => Data[6] = value; } + public override int MailType { get => Data[7]; set => Data[7] = (byte)value; } + public override string AuthorName { get => StringConverter5.GetString(Data.AsSpan(8, 0x10)); set => StringConverter5.SetString(Data.AsSpan(8, 0x10), value.AsSpan(), 7, StringConverterOption.ClearZero); } + public int GetMisc(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2))); + public void SetMisc(int index, int value) => WriteUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2)), (ushort)value); + public ushort MessageEnding { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), value); } + public override ushort GetMessage(int index1, int index2) => ReadUInt16LittleEndian(Data.AsSpan(0x20 + (((index1 * 4) + index2) * 2))); + public override void SetMessage(int index1, int index2, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(0x20 + (((index1 * 4) + index2) * 2)), value); + + public override bool? IsEmpty => MailType switch + { + 0xFF => true, + <= 11 => false, + _ => null, + }; + + public override void SetBlank() => SetBlank(null, null); + public void SetBlank(byte? lang, byte? ver) => new Mail5(lang: lang, ver: ver).Data.CopyTo(Data, 0); } diff --git a/PKHeX.Core/Saves/Substructures/Mail/MailDetail.cs b/PKHeX.Core/Saves/Substructures/Mail/MailDetail.cs new file mode 100644 index 000000000..cfb338252 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Mail/MailDetail.cs @@ -0,0 +1,31 @@ +namespace PKHeX.Core; + +public abstract class MailDetail +{ + protected readonly byte[] Data; + protected readonly int DataOffset; + + protected MailDetail(byte[] data, int offset = 0) + { + Data = data; + DataOffset = offset; + } + + public virtual void CopyTo(SaveFile sav) => sav.SetData(Data, DataOffset); + public virtual void CopyTo(PK4 pk4) { } + public virtual void CopyTo(PK5 pk5) { } + public virtual string GetMessage(bool isLastLine) => string.Empty; + public virtual ushort GetMessage(int index1, int index2) => 0; + public virtual void SetMessage(string line1, string line2) { } + public virtual void SetMessage(int index1, int index2, ushort value) { } + public virtual string AuthorName { get; set; } = string.Empty; + public virtual ushort AuthorTID { get; set; } + public virtual ushort AuthorSID { get; set; } + public virtual byte AuthorVersion { get; set; } + public virtual byte AuthorLanguage { get; set; } + public virtual byte AuthorGender { get; set; } + public virtual int AppearPKM { get; set; } + public virtual int MailType { get; set; } + public abstract bool? IsEmpty { get; } // true: empty, false: legal mail, null: illegal mail + public virtual void SetBlank() { } +} diff --git a/PKHeX.Core/Saves/Substructures/Misc/IBoxDetailName.cs b/PKHeX.Core/Saves/Substructures/Misc/IBoxDetailName.cs index a844db8c6..69984ee4f 100644 --- a/PKHeX.Core/Saves/Substructures/Misc/IBoxDetailName.cs +++ b/PKHeX.Core/Saves/Substructures/Misc/IBoxDetailName.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides details about box names within the save file. +/// +public interface IBoxDetailName { - /// - /// Provides details about box names within the save file. - /// - public interface IBoxDetailName - { - public string GetBoxName(int box); - public void SetBoxName(int box, string value); - } + public string GetBoxName(int box); + public void SetBoxName(int box, string value); } diff --git a/PKHeX.Core/Saves/Substructures/Misc/IBoxDetailWallpaper.cs b/PKHeX.Core/Saves/Substructures/Misc/IBoxDetailWallpaper.cs index d59377295..0205df33d 100644 --- a/PKHeX.Core/Saves/Substructures/Misc/IBoxDetailWallpaper.cs +++ b/PKHeX.Core/Saves/Substructures/Misc/IBoxDetailWallpaper.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides details about box wallpaper values within the save file. +/// +public interface IBoxDetailWallpaper { - /// - /// Provides details about box wallpaper values within the save file. - /// - public interface IBoxDetailWallpaper - { - public int GetBoxWallpaper(int box); - public void SetBoxWallpaper(int box, int value); - } + public int GetBoxWallpaper(int box); + public void SetBoxWallpaper(int box, int value); } diff --git a/PKHeX.Core/Saves/Substructures/Misc/IGameSync.cs b/PKHeX.Core/Saves/Substructures/Misc/IGameSync.cs index 16d32032d..32923baf5 100644 --- a/PKHeX.Core/Saves/Substructures/Misc/IGameSync.cs +++ b/PKHeX.Core/Saves/Substructures/Misc/IGameSync.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides details about the save file's Game Sync identifier. +/// +public interface IGameSync { - /// - /// Provides details about the save file's Game Sync identifier. - /// - public interface IGameSync - { - int GameSyncIDSize { get; } - string GameSyncID { get; set; } - } + int GameSyncIDSize { get; } + string GameSyncID { get; set; } } diff --git a/PKHeX.Core/Saves/Substructures/Misc/IPokeGroup.cs b/PKHeX.Core/Saves/Substructures/Misc/IPokeGroup.cs index 3632929d7..9abc34249 100644 --- a/PKHeX.Core/Saves/Substructures/Misc/IPokeGroup.cs +++ b/PKHeX.Core/Saves/Substructures/Misc/IPokeGroup.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface IPokeGroup { - public interface IPokeGroup - { - IEnumerable Contents { get; } - } + IEnumerable Contents { get; } } diff --git a/PKHeX.Core/Saves/Substructures/Misc/IRecordStatStorage.cs b/PKHeX.Core/Saves/Substructures/Misc/IRecordStatStorage.cs index 3ccad57cb..324296657 100644 --- a/PKHeX.Core/Saves/Substructures/Misc/IRecordStatStorage.cs +++ b/PKHeX.Core/Saves/Substructures/Misc/IRecordStatStorage.cs @@ -1,12 +1,11 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Provides a minimal API for mutating stat records. +/// +public interface IRecordStatStorage { - /// - /// Provides a minimal API for mutating stat records. - /// - public interface IRecordStatStorage - { - int GetRecord(int recordID); - void SetRecord(int recordID, int value); - void AddRecord(int recordID, int count = 1); - } + int GetRecord(int recordID); + void SetRecord(int recordID, int value); + void AddRecord(int recordID, int count = 1); } diff --git a/PKHeX.Core/Saves/Substructures/Misc/IRentalTeam.cs b/PKHeX.Core/Saves/Substructures/Misc/IRentalTeam.cs index c4b446a50..ac592870a 100644 --- a/PKHeX.Core/Saves/Substructures/Misc/IRentalTeam.cs +++ b/PKHeX.Core/Saves/Substructures/Misc/IRentalTeam.cs @@ -1,13 +1,12 @@ using System.Collections.Generic; -namespace PKHeX.Core -{ - public interface IRentalTeam where T : PKM - { - T GetSlot(int slot); - void SetSlot(int slot, T pkm); +namespace PKHeX.Core; - T[] GetTeam(); - void SetTeam(IReadOnlyList team); - } +public interface IRentalTeam where T : PKM +{ + T GetSlot(int slot); + void SetSlot(int slot, T pk); + + T[] GetTeam(); + void SetTeam(IReadOnlyList team); } diff --git a/PKHeX.Core/Saves/Substructures/Misc/ISaveFileRevision.cs b/PKHeX.Core/Saves/Substructures/Misc/ISaveFileRevision.cs index 479a136ea..767c0aa8d 100644 --- a/PKHeX.Core/Saves/Substructures/Misc/ISaveFileRevision.cs +++ b/PKHeX.Core/Saves/Substructures/Misc/ISaveFileRevision.cs @@ -1,8 +1,7 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface ISaveFileRevision { - public interface ISaveFileRevision - { - int SaveRevision { get; } - string SaveRevisionString { get; } - } + int SaveRevision { get; } + string SaveRevisionString { get; } } diff --git a/PKHeX.Core/Saves/Substructures/Misc/ISecureValueStorage.cs b/PKHeX.Core/Saves/Substructures/Misc/ISecureValueStorage.cs index 811b5b3dc..4d784bbd4 100644 --- a/PKHeX.Core/Saves/Substructures/Misc/ISecureValueStorage.cs +++ b/PKHeX.Core/Saves/Substructures/Misc/ISecureValueStorage.cs @@ -1,8 +1,7 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +public interface ISecureValueStorage { - public interface ISecureValueStorage - { - ulong TimeStampPrevious { get; set; } - ulong TimeStampCurrent { get; set; } - } + ulong TimeStampPrevious { get; set; } + ulong TimeStampCurrent { get; set; } } diff --git a/PKHeX.Core/Saves/Substructures/Misc/ITeamIndexSet.cs b/PKHeX.Core/Saves/Substructures/Misc/ITeamIndexSet.cs index ea69b41df..189f668bd 100644 --- a/PKHeX.Core/Saves/Substructures/Misc/ITeamIndexSet.cs +++ b/PKHeX.Core/Saves/Substructures/Misc/ITeamIndexSet.cs @@ -1,12 +1,11 @@ -namespace PKHeX.Core -{ - public interface ITeamIndexSet - { - bool GetIsTeamLocked(int team); - void SetIsTeamLocked(int team, bool value); +namespace PKHeX.Core; - void ClearBattleTeams(); - void SaveBattleTeams(); - void UnlockAllTeams(); - } +public interface ITeamIndexSet +{ + bool GetIsTeamLocked(int team); + void SetIsTeamLocked(int team, bool value); + + void ClearBattleTeams(); + void SaveBattleTeams(); + void UnlockAllTeams(); } diff --git a/PKHeX.Core/Saves/Substructures/MysteryGiftAlbum.cs b/PKHeX.Core/Saves/Substructures/MysteryGiftAlbum.cs index 36350503d..a5505240f 100644 --- a/PKHeX.Core/Saves/Substructures/MysteryGiftAlbum.cs +++ b/PKHeX.Core/Saves/Substructures/MysteryGiftAlbum.cs @@ -1,37 +1,36 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Structure containing the Mystery Gift Data +/// +public class MysteryGiftAlbum { /// - /// Structure containing the Mystery Gift Data + /// Mystery Gift data received /// - public class MysteryGiftAlbum + public readonly DataMysteryGift[] Gifts; + + /// + /// Received Flag list + /// + /// + /// this[index] == true iff index= has been received already. + /// + public readonly bool[] Flags; + + public MysteryGiftAlbum(DataMysteryGift[] gifts, bool[] flags) { - /// - /// Mystery Gift data received - /// - public readonly DataMysteryGift[] Gifts; - - /// - /// Received Flag list - /// - /// - /// this[index] == true iff index= has been received already. - /// - public readonly bool[] Flags; - - public MysteryGiftAlbum(DataMysteryGift[] gifts, bool[] flags) - { - Flags = flags; - Gifts = gifts; - } - } - - public sealed class EncryptedMysteryGiftAlbum : MysteryGiftAlbum - { - /// - /// Encryption Seed (only used in Generation 5 to encrypt the stored data) - /// - public readonly uint Seed; - - public EncryptedMysteryGiftAlbum(DataMysteryGift[] gifts, bool[] flags, uint seed) : base(gifts, flags) => Seed = seed; + Flags = flags; + Gifts = gifts; } } + +public sealed class EncryptedMysteryGiftAlbum : MysteryGiftAlbum +{ + /// + /// Encryption Seed (only used in Generation 5 to encrypt the stored data) + /// + public readonly uint Seed; + + public EncryptedMysteryGiftAlbum(DataMysteryGift[] gifts, bool[] flags, uint seed) : base(gifts, flags) => Seed = seed; +} diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan.cs index 0736b4c3e..1dc0d610f 100644 --- a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan.cs +++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan.cs @@ -1,291 +1,290 @@ using System; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Base class for Pokdex logic operations. +/// +public abstract class ZukanBase { - /// - /// Base class for Pokdex logic operations. - /// - public abstract class ZukanBase + protected readonly SaveFile SAV; + public readonly int PokeDex; + + protected ZukanBase(SaveFile sav, int dex) { - protected readonly SaveFile SAV; - public readonly int PokeDex; - - protected ZukanBase(SaveFile sav, int dex) - { - SAV = sav; - PokeDex = dex; - } - - #region Overall Info - /// Count of unique Species Seen - public int SeenCount => Enumerable.Range(1, SAV.MaxSpeciesID).Count(GetSeen); - /// Count of unique Species Caught (Owned) - public int CaughtCount => Enumerable.Range(1, SAV.MaxSpeciesID).Count(GetCaught); - - public decimal PercentSeen => (decimal)SeenCount / SAV.MaxSpeciesID; - public decimal PercentCaught => (decimal)CaughtCount / SAV.MaxSpeciesID; - #endregion - - /// Gets if the Species has been Seen by the player. - public abstract bool GetSeen(int species); - /// Gets if the Species has been Caught (Owned) by the player. - public abstract bool GetCaught(int species); - - /// Adds the Pokmon's information to the Pokdex. - public abstract void SetDex(PKM pkm); - - #region Overall Manipulation - public abstract void SeenNone(); - public abstract void CaughtNone(); - - public abstract void SeenAll(bool shinyToo = false); - public abstract void CompleteDex(bool shinyToo = false); - public abstract void CaughtAll(bool shinyToo = false); - public abstract void SetAllSeen(bool value = true, bool shinyToo = false); - - public abstract void SetDexEntryAll(int species, bool shinyToo = false); - public abstract void ClearDexEntryAll(int species); - #endregion + SAV = sav; + PokeDex = dex; } - /// - /// Base class for Pokdex operations, exposing the shared structure features used by Generations 5, 6, and 7. - /// - public abstract class Zukan : ZukanBase + #region Overall Info + /// Count of unique Species Seen + public int SeenCount => Enumerable.Range(1, SAV.MaxSpeciesID).Count(GetSeen); + /// Count of unique Species Caught (Owned) + public int CaughtCount => Enumerable.Range(1, SAV.MaxSpeciesID).Count(GetCaught); + + public decimal PercentSeen => (decimal)SeenCount / SAV.MaxSpeciesID; + public decimal PercentCaught => (decimal)CaughtCount / SAV.MaxSpeciesID; + #endregion + + /// Gets if the Species has been Seen by the player. + public abstract bool GetSeen(int species); + /// Gets if the Species has been Caught (Owned) by the player. + public abstract bool GetCaught(int species); + + /// Adds the Pokmon's information to the Pokdex. + public abstract void SetDex(PKM pk); + + #region Overall Manipulation + public abstract void SeenNone(); + public abstract void CaughtNone(); + + public abstract void SeenAll(bool shinyToo = false); + public abstract void CompleteDex(bool shinyToo = false); + public abstract void CaughtAll(bool shinyToo = false); + public abstract void SetAllSeen(bool value = true, bool shinyToo = false); + + public abstract void SetDexEntryAll(int species, bool shinyToo = false); + public abstract void ClearDexEntryAll(int species); + #endregion +} + +/// +/// Base class for Pokdex operations, exposing the shared structure features used by Generations 5, 6, and 7. +/// +public abstract class Zukan : ZukanBase +{ + protected readonly int PokeDexLanguageFlags; + + protected Zukan(SaveFile sav, int dex, int langflag) : base(sav, dex) { - protected readonly int PokeDexLanguageFlags; + PokeDexLanguageFlags = langflag; + if (langflag > dex) + throw new ArgumentOutOfRangeException(nameof(langflag)); + } - protected Zukan(SaveFile sav, int dex, int langflag) : base(sav, dex) + protected abstract int OFS_SEEN { get; } + protected abstract int OFS_CAUGHT { get; } + protected abstract int BitSeenSize { get; } + protected abstract int DexLangFlagByteCount { get; } + protected abstract int DexLangIDCount { get; } + protected abstract int GetDexLangFlag(int lang); + + protected abstract bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn); + protected virtual void SetSpindaDexData(PKM pk, bool alreadySeen) { } + protected abstract void SetAllDexFlagsLanguage(int bit, int lang, bool value = true); + protected abstract void SetAllDexSeenFlags(int baseBit, int form, int gender, bool isShiny, bool value = true); + + protected bool GetFlag(int ofs, int bitIndex) => SAV.GetFlag(PokeDex + ofs + (bitIndex >> 3), bitIndex); + protected void SetFlag(int ofs, int bitIndex, bool value = true) => SAV.SetFlag(PokeDex + ofs + (bitIndex >> 3), bitIndex, value); + + public override bool GetCaught(int species) => GetFlag(OFS_CAUGHT, species - 1); + public virtual void SetCaught(int species, bool value = true) => SetFlag(OFS_CAUGHT, species - 1, value); + + public override bool GetSeen(int species) + { + // check all 4 seen flags (gender/shiny) + for (int i = 0; i < 4; i++) { - PokeDexLanguageFlags = langflag; - if (langflag > dex) - throw new ArgumentOutOfRangeException(nameof(langflag)); + if (GetSeen(species, i)) + return true; + } + return false; + } + + public bool GetSeen(int species, int bitRegion) => GetFlag(OFS_SEEN + (bitRegion * BitSeenSize), species - 1); + public void SetSeen(int species, int bitRegion, bool value) => SetFlag(OFS_SEEN + (bitRegion * BitSeenSize), species - 1, value); + + public bool GetDisplayed(int bit, int bitRegion) => GetFlag(OFS_SEEN + ((bitRegion + 4) * BitSeenSize), bit); + public void SetDisplayed(int bit, int bitRegion, bool value) => SetFlag(OFS_SEEN + ((bitRegion + 4) * BitSeenSize), bit, value); + + public bool GetLanguageFlag(int bit, int lang) => GetFlag(PokeDexLanguageFlags, (bit * DexLangIDCount) + lang); + public void SetLanguageFlag(int bit, int lang, bool value) => SetFlag(PokeDexLanguageFlags, (bit * DexLangIDCount) + lang, value); + + public virtual void SetSeen(int species, bool value = true) + { + if (!value) + { + ClearSeen(species); + return; } - protected abstract int OFS_SEEN { get; } - protected abstract int OFS_CAUGHT { get; } - protected abstract int BitSeenSize { get; } - protected abstract int DexLangFlagByteCount { get; } - protected abstract int DexLangIDCount { get; } - protected abstract int GetDexLangFlag(int lang); - - protected abstract bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn); - protected virtual void SetSpindaDexData(PKM pkm, bool alreadySeen) { } - protected abstract void SetAllDexFlagsLanguage(int bit, int lang, bool value = true); - protected abstract void SetAllDexSeenFlags(int baseBit, int form, int gender, bool isShiny, bool value = true); - - protected bool GetFlag(int ofs, int bitIndex) => SAV.GetFlag(PokeDex + ofs + (bitIndex >> 3), bitIndex); - protected void SetFlag(int ofs, int bitIndex, bool value = true) => SAV.SetFlag(PokeDex + ofs + (bitIndex >> 3), bitIndex, value); - - public override bool GetCaught(int species) => GetFlag(OFS_CAUGHT, species - 1); - public virtual void SetCaught(int species, bool value = true) => SetFlag(OFS_CAUGHT, species - 1, value); - - public override bool GetSeen(int species) + // check all 4 seen flags (gender/shiny) + for (int i = 0; i < 4; i++) { - // check all 4 seen flags (gender/shiny) - for (int i = 0; i < 4; i++) - { - if (GetSeen(species, i)) - return true; - } + if (GetFlag(OFS_SEEN + (i * BitSeenSize), species - 1)) + return; + } + var gender = SAV.Personal[species].RandomGender() & 1; + SetAllDexSeenFlags(species - 1, 0, gender, false); + } + + private void ClearSeen(int species) + { + SetCaught(species, false); + for (int i = 0; i < 4; i++) + SetFlag(OFS_SEEN + (i * BitSeenSize), species - 1, false); + } + + public override void SetDex(PKM pk) + { + if ((uint)(pk.Species - 1) >= (uint)SAV.MaxSpeciesID) // out of range + return; + if (pk.IsEgg) // do not add + return; + + int species = pk.Species; + if (species == (int)Species.Spinda) + SetSpindaDexData(pk, GetSeen(species)); + + int bit = pk.Species - 1; + int form = pk.Form; + int gender = pk.Gender & 1; + bool shiny = pk.IsShiny; + int lang = pk.Language; + SetDex(species, bit, form, gender, shiny, lang); + } + + protected virtual void SetDex(int species, int bit, int form, int gender, bool shiny, int lang) + { + SetCaught(species); // Set the Owned Flag + SetAllDexSeenFlags(bit, form, gender, shiny); // genderless -> male + SetAllDexFlagsLanguage(bit, lang); + } + + protected void SetDexFlags(int baseBit, int formBit, int gender, int shiny, bool value = true) + { + int shift = (gender & 1) | (shiny << 1); + + // Set the [Species/Gender/Shiny] Seen Flag + SetFlag(OFS_SEEN + (shift * BitSeenSize), baseBit, value); + + // Set the Display flag if none are set + SetDisplayedFlag(baseBit, formBit, value, shift); + } + + protected virtual void SetDisplayedFlag(int baseBit, int formBit, bool value, int shift) + { + var bit = formBit >= 0 ? formBit : baseBit; + if (!value) + { + SetDisplayed(bit, shift, false); + return; + } + + bool displayed = GetIsSpeciesFormAnyDisplayed(baseBit, formBit); + if (displayed) + return; // no need to set another bit + + SetDisplayed(bit, shift, true); + } + + private bool GetIsSpeciesFormAnyDisplayed(int baseBit, int formBit) + { + // Check Displayed Status for base form + for (int i = 0; i < 4; i++) + { + if (GetDisplayed(baseBit, i)) + return true; + } + if (baseBit == formBit) return false; - } - public bool GetSeen(int species, int bitRegion) => GetFlag(OFS_SEEN + (bitRegion * BitSeenSize), species - 1); - public void SetSeen(int species, int bitRegion, bool value) => SetFlag(OFS_SEEN + (bitRegion * BitSeenSize), species - 1, value); - - public bool GetDisplayed(int bit, int bitRegion) => GetFlag(OFS_SEEN + ((bitRegion + 4) * BitSeenSize), bit); - public void SetDisplayed(int bit, int bitRegion, bool value) => SetFlag(OFS_SEEN + ((bitRegion + 4) * BitSeenSize), bit, value); - - public bool GetLanguageFlag(int bit, int lang) => GetFlag(PokeDexLanguageFlags, (bit * DexLangIDCount) + lang); - public void SetLanguageFlag(int bit, int lang, bool value) => SetFlag(PokeDexLanguageFlags, (bit * DexLangIDCount) + lang, value); - - public virtual void SetSeen(int species, bool value = true) + // If form is not base form, check form too + for (int i = 0; i < 4; i++) { - if (!value) - { - ClearSeen(species); - return; - } - - // check all 4 seen flags (gender/shiny) - for (int i = 0; i < 4; i++) - { - if (GetFlag(OFS_SEEN + (i * BitSeenSize), species - 1)) - return; - } - var gender = SAV.Personal[species].RandomGender() & 1; - SetAllDexSeenFlags(species - 1, 0, gender, false); + if (GetDisplayed(formBit, i)) + return true; } + return false; + } - private void ClearSeen(int species) + // Bulk Manipulation + public override void SeenNone() => SetDexEntriesAll(false, shinyToo: true); + public override void CaughtNone() => SetAllCaught(false, true); + public override void SeenAll(bool shinyToo = false) => SetAllSeen(shinyToo); + public override void CompleteDex(bool shinyToo = false) => SetDexEntriesAll(shinyToo: shinyToo); + + public override void CaughtAll(bool shinyToo = false) + { + SetAllSeen(true, shinyToo); + SetAllCaught(true, shinyToo); + } + + public void SetAllCaught(bool value = true, bool shinyToo = false) + { + for (int i = 0; i < SAV.MaxSpeciesID; i++) { - SetCaught(species, false); - for (int i = 0; i < 4; i++) - SetFlag(OFS_SEEN + (i * BitSeenSize), species - 1, false); - } - - public override void SetDex(PKM pkm) - { - if ((uint)(pkm.Species - 1) >= (uint)SAV.MaxSpeciesID) // out of range - return; - if (pkm.IsEgg) // do not add - return; - - int species = pkm.Species; - if (species == (int)Species.Spinda) - SetSpindaDexData(pkm, GetSeen(species)); - - int bit = pkm.Species - 1; - int form = pkm.Form; - int gender = pkm.Gender & 1; - bool shiny = pkm.IsShiny; - int lang = pkm.Language; - SetDex(species, bit, form, gender, shiny, lang); - } - - protected virtual void SetDex(int species, int bit, int form, int gender, bool shiny, int lang) - { - SetCaught(species); // Set the Owned Flag - SetAllDexSeenFlags(bit, form, gender, shiny); // genderless -> male - SetAllDexFlagsLanguage(bit, lang); - } - - protected void SetDexFlags(int baseBit, int formBit, int gender, int shiny, bool value = true) - { - int shift = (gender & 1) | (shiny << 1); - - // Set the [Species/Gender/Shiny] Seen Flag - SetFlag(OFS_SEEN + (shift * BitSeenSize), baseBit, value); - - // Set the Display flag if none are set - SetDisplayedFlag(baseBit, formBit, value, shift); - } - - protected virtual void SetDisplayedFlag(int baseBit, int formBit, bool value, int shift) - { - var bit = formBit >= 0 ? formBit : baseBit; - if (!value) - { - SetDisplayed(bit, shift, false); - return; - } - - bool displayed = GetIsSpeciesFormAnyDisplayed(baseBit, formBit); - if (displayed) - return; // no need to set another bit - - SetDisplayed(bit, shift, true); - } - - private bool GetIsSpeciesFormAnyDisplayed(int baseBit, int formBit) - { - // Check Displayed Status for base form - for (int i = 0; i < 4; i++) - { - if (GetDisplayed(baseBit, i)) - return true; - } - if (baseBit == formBit) - return false; - - // If form is not base form, check form too - for (int i = 0; i < 4; i++) - { - if (GetDisplayed(formBit, i)) - return true; - } - return false; - } - - // Bulk Manipulation - public override void SeenNone() => SetDexEntriesAll(false, shinyToo: true); - public override void CaughtNone() => SetAllCaught(false, true); - public override void SeenAll(bool shinyToo = false) => SetAllSeen(shinyToo); - public override void CompleteDex(bool shinyToo = false) => SetDexEntriesAll(shinyToo: shinyToo); - - public override void CaughtAll(bool shinyToo = false) - { - SetAllSeen(true, shinyToo); - SetAllCaught(true, shinyToo); - } - - public void SetAllCaught(bool value = true, bool shinyToo = false) - { - for (int i = 0; i < SAV.MaxSpeciesID; i++) - { - int species = i + 1; - SetCaught(species, value); // Set the Owned Flag - SetSeenSingle(i + 1, value, shinyToo); - } - } - - public override void SetAllSeen(bool value = true, bool shinyToo = false) - { - for (int i = 0; i < SAV.MaxSpeciesID; i++) - SetSeenSingle(i + 1, value, shinyToo); - } - - public override void SetDexEntryAll(int species, bool shinyToo = false) - { - SetSeenSingle(species, true, shinyToo); - SetCaughtSingle(species); - } - - public override void ClearDexEntryAll(int species) - { - SetSeenSingle(species, false); - SetCaughtSingle(species, false); - } - - public void SetDexEntriesAll(bool value = true, int max = -1, bool shinyToo = false) - { - if (max <= 0) - max = SAV.MaxSpeciesID; - - for (int i = 1; i <= max; i++) - { - SetSeenSingle(i, value, shinyToo); - SetCaughtSingle(i, value); - } - } - - public void SetCaughtSingle(int species, bool value = true) - { - SetCaught(species, value); - int baseBit = species - 1; - SetAllDexFlagsLanguage(baseBit, value); - } - - public void SetSeenSingle(int species, bool seen = true, bool shinyToo = false) - { - SetSeen(species, seen); - - var entry = SAV.Personal[species]; - int baseBit = species - 1; - int fc = entry.FormCount; - for (int f = 0; f < fc; f++) - { - if (!entry.OnlyFemale) - { - SetAllDexSeenFlags(baseBit, f, 0, false, seen); - if (shinyToo) - SetAllDexSeenFlags(baseBit, f, 0, true, seen); - } - if (!entry.OnlyMale && !entry.Genderless) - { - SetAllDexSeenFlags(baseBit, f, 1, false, seen); - if (shinyToo) - SetAllDexSeenFlags(baseBit, f, 1, true, seen); - } - } - } - - protected void SetAllDexFlagsLanguage(int bit, bool value = true) - { - for (int i = 1; i <= DexLangIDCount + 1; i++) - SetAllDexFlagsLanguage(bit, i, value); + int species = i + 1; + SetCaught(species, value); // Set the Owned Flag + SetSeenSingle(i + 1, value, shinyToo); } } -} \ No newline at end of file + + public override void SetAllSeen(bool value = true, bool shinyToo = false) + { + for (int i = 0; i < SAV.MaxSpeciesID; i++) + SetSeenSingle(i + 1, value, shinyToo); + } + + public override void SetDexEntryAll(int species, bool shinyToo = false) + { + SetSeenSingle(species, true, shinyToo); + SetCaughtSingle(species); + } + + public override void ClearDexEntryAll(int species) + { + SetSeenSingle(species, false); + SetCaughtSingle(species, false); + } + + public void SetDexEntriesAll(bool value = true, int max = -1, bool shinyToo = false) + { + if (max <= 0) + max = SAV.MaxSpeciesID; + + for (int i = 1; i <= max; i++) + { + SetSeenSingle(i, value, shinyToo); + SetCaughtSingle(i, value); + } + } + + public void SetCaughtSingle(int species, bool value = true) + { + SetCaught(species, value); + int baseBit = species - 1; + SetAllDexFlagsLanguage(baseBit, value); + } + + public void SetSeenSingle(int species, bool seen = true, bool shinyToo = false) + { + SetSeen(species, seen); + + var entry = SAV.Personal[species]; + int baseBit = species - 1; + int fc = entry.FormCount; + for (int f = 0; f < fc; f++) + { + if (!entry.OnlyFemale) + { + SetAllDexSeenFlags(baseBit, f, 0, false, seen); + if (shinyToo) + SetAllDexSeenFlags(baseBit, f, 0, true, seen); + } + if (!entry.OnlyMale && !entry.Genderless) + { + SetAllDexSeenFlags(baseBit, f, 1, false, seen); + if (shinyToo) + SetAllDexSeenFlags(baseBit, f, 1, true, seen); + } + } + } + + protected void SetAllDexFlagsLanguage(int bit, bool value = true) + { + for (int i = 1; i <= DexLangIDCount + 1; i++) + SetAllDexFlagsLanguage(bit, i, value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan4.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan4.cs index a1f82bcee..f330cd76c 100644 --- a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan4.cs +++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan4.cs @@ -1,542 +1,541 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokédex structure used by games. +/// +public sealed class Zukan4 : ZukanBase { - /// - /// Pokédex structure used by games. - /// - public sealed class Zukan4 : ZukanBase + private readonly byte[] Data; + private readonly int Offset; + + // General structure: u32 magic, 4*bitflags, u32 spinda, form flags, language flags, more form flags, upgrade flags + + /* 4 BitRegions with 0x40*8 bits + * Region 0: Caught (Captured/Owned) flags + * Region 1: Seen flags + * Region 2: First Seen Gender + * Region 3: Second Seen Gender + * When setting a newly seen species (first time), we set the gender bit to both First and Second regions. + * When setting an already-seen species, we set the Second region bit if the now-seen gender-bit is not equal to the first-seen bit. + * 4 possible states: 00, 01, 10, 11 + * 00 - 1Seen: Male Only + * 01 - 2Seen: Male First, Female Second + * 10 - 2Seen: Female First, Male Second + * 11 - 1Seen: Female Only + * assuming the species is seen, (bit1 ^ bit2) + 1 = genders in dex + */ + + public const string GENDERLESS = "Genderless"; + public const string MALE = "Male"; + public const string FEMALE = "Female"; + private const int SIZE_REGION = 0x40; + private const int COUNT_REGION = 4; + private const int OFS_SPINDA = sizeof(uint) + (COUNT_REGION * SIZE_REGION); + private const int OFS_FORM1 = OFS_SPINDA + sizeof(uint); + + private bool HGSS => SAV is SAV4HGSS; + private bool DP => SAV is SAV4DP; + + public Zukan4(SAV4 sav, int offset) : base(sav, offset) { - private readonly byte[] Data; - private readonly int Offset; + Data = sav.General; + Offset = offset; + } - // General structure: u32 magic, 4*bitflags, u32 spinda, form flags, language flags, more form flags, upgrade flags + public uint Magic { get => ReadUInt32LittleEndian(Data.AsSpan(Offset)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value); } - /* 4 BitRegions with 0x40*8 bits - * Region 0: Caught (Captured/Owned) flags - * Region 1: Seen flags - * Region 2: First Seen Gender - * Region 3: Second Seen Gender - * When setting a newly seen species (first time), we set the gender bit to both First and Second regions. - * When setting an already-seen species, we set the Second region bit if the now-seen gender-bit is not equal to the first-seen bit. - * 4 possible states: 00, 01, 10, 11 - * 00 - 1Seen: Male Only - * 01 - 2Seen: Male First, Female Second - * 10 - 2Seen: Female First, Male Second - * 11 - 1Seen: Female Only - * assuming the species is seen, (bit1 ^ bit2) + 1 = genders in dex - */ + public override bool GetCaught(int species) => GetRegionFlag(0, species - 1); + public override bool GetSeen(int species) => GetRegionFlag(1, species - 1); + public int GetSeenGenderFirst(int species) => GetRegionFlag(2, species - 1) ? 1 : 0; + public int GetSeenGenderSecond(int species) => GetRegionFlag(3, species - 1) ? 1 : 0; + public bool GetSeenSingleGender(int species) => GetSeenGenderFirst(species) == GetSeenGenderSecond(species); - public const string GENDERLESS = "Genderless"; - public const string MALE = "Male"; - public const string FEMALE = "Female"; - private const int SIZE_REGION = 0x40; - private const int COUNT_REGION = 4; - private const int OFS_SPINDA = sizeof(uint) + (COUNT_REGION * SIZE_REGION); - private const int OFS_FORM1 = OFS_SPINDA + sizeof(uint); + private bool GetRegionFlag(int region, int index) + { + var ofs = Offset + 4 + (region * SIZE_REGION) + (index >> 3); + return FlagUtil.GetFlag(Data, ofs, index); + } - private bool HGSS => SAV is SAV4HGSS; - private bool DP => SAV is SAV4DP; + public void SetCaught(int species, bool value = true) => SetRegionFlag(0, species - 1, value); + public void SetSeen(int species, bool value = true) => SetRegionFlag(1, species - 1, value); + public void SetSeenGenderFirst(int species, int value = 0) => SetRegionFlag(2, species - 1, value == 1); + public void SetSeenGenderSecond(int species, int value = 0) => SetRegionFlag(3, species - 1, value == 1); - public Zukan4(SAV4 sav, int offset) : base(sav, offset) + private void SetRegionFlag(int region, int index, bool value) + { + var ofs = Offset + 4 + (region * SIZE_REGION) + (index >> 3); + FlagUtil.SetFlag(Data, ofs, index, value); + } + + public uint SpindaPID { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SPINDA)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value); } + + public static string[] GetFormNames4Dex(int species) + { + string[] formNames = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, Array.Empty(), 4); + if (species == (int)Species.Pichu) + formNames = new[] { MALE, FEMALE, formNames[1] }; // Spiky + return formNames; + } + + public int[] GetForms(int species) + { + const int brSize = 0x40; + if (species == (int)Species.Deoxys) { - Data = sav.General; - Offset = offset; + uint val = (uint)(Data[Offset + 0x4 + (1 * brSize) - 1] | (Data[Offset + 0x4 + (2 * brSize) - 1] << 8)); + return GetDexFormValues(val, 4, 4); } - public uint Magic { get => ReadUInt32LittleEndian(Data.AsSpan(Offset)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value); } - - public override bool GetCaught(int species) => GetRegionFlag(0, species - 1); - public override bool GetSeen(int species) => GetRegionFlag(1, species - 1); - public int GetSeenGenderFirst(int species) => GetRegionFlag(2, species - 1) ? 1 : 0; - public int GetSeenGenderSecond(int species) => GetRegionFlag(3, species - 1) ? 1 : 0; - public bool GetSeenSingleGender(int species) => GetSeenGenderFirst(species) == GetSeenGenderSecond(species); - - private bool GetRegionFlag(int region, int index) + int FormOffset1 = Offset + 4 + (4 * brSize) + 4; + switch (species) { - var ofs = Offset + 4 + (region * SIZE_REGION) + (index >> 3); - return FlagUtil.GetFlag(Data, ofs, index); + case (int)Species.Shellos: // Shellos + return GetDexFormValues(Data[FormOffset1 + 0], 1, 2); + case (int)Species.Gastrodon: // Gastrodon + return GetDexFormValues(Data[FormOffset1 + 1], 1, 2); + case (int)Species.Burmy: // Burmy + return GetDexFormValues(Data[FormOffset1 + 2], 2, 3); + case (int)Species.Wormadam: // Wormadam + return GetDexFormValues(Data[FormOffset1 + 3], 2, 3); + case (int)Species.Unown: // Unown + int[] result = new int[0x1C]; + var slice = Data.AsSpan(FormOffset1 + 4); + for (int i = 0; i < result.Length; i++) + result[i] = slice[i]; + return result; } + if (DP) + return Array.Empty(); - public void SetCaught(int species, bool value = true) => SetRegionFlag(0, species - 1, value); - public void SetSeen(int species, bool value = true) => SetRegionFlag(1, species - 1, value); - public void SetSeenGenderFirst(int species, int value = 0) => SetRegionFlag(2, species - 1, value == 1); - public void SetSeenGenderSecond(int species, int value = 0) => SetRegionFlag(3, species - 1, value == 1); - - private void SetRegionFlag(int region, int index, bool value) + int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); + int FormOffset2 = PokeDexLanguageFlags + 0x1F4; + return species switch { - var ofs = Offset + 4 + (region * SIZE_REGION) + (index >> 3); - FlagUtil.SetFlag(Data, ofs, index, value); - } - - public uint SpindaPID { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + OFS_SPINDA)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset), value); } - - public static string[] GetFormNames4Dex(int species) - { - string[] formNames = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, Array.Empty(), 4); - if (species == (int)Species.Pichu) - formNames = new[] { MALE, FEMALE, formNames[1] }; // Spiky - return formNames; - } - - public int[] GetForms(int species) - { - const int brSize = 0x40; - if (species == (int)Species.Deoxys) - { - uint val = (uint)(Data[Offset + 0x4 + (1 * brSize) - 1] | Data[Offset + 0x4 + (2 * brSize) - 1] << 8); - return GetDexFormValues(val, 4, 4); - } - - int FormOffset1 = Offset + 4 + (4 * brSize) + 4; - switch (species) - { - case (int)Species.Shellos: // Shellos - return GetDexFormValues(Data[FormOffset1 + 0], 1, 2); - case (int)Species.Gastrodon: // Gastrodon - return GetDexFormValues(Data[FormOffset1 + 1], 1, 2); - case (int)Species.Burmy: // Burmy - return GetDexFormValues(Data[FormOffset1 + 2], 2, 3); - case (int)Species.Wormadam: // Wormadam - return GetDexFormValues(Data[FormOffset1 + 3], 2, 3); - case (int)Species.Unown: // Unown - int[] result = new int[0x1C]; - var slice = Data.AsSpan(FormOffset1 + 4); - for (int i = 0; i < result.Length; i++) - result[i] = slice[i]; - return result; - } - if (DP) - return Array.Empty(); - - int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); - int FormOffset2 = PokeDexLanguageFlags + 0x1F4; - return species switch - { - (int)Species.Rotom => GetDexFormValues(ReadUInt32LittleEndian(Data.AsSpan(FormOffset2)), 3, 6), - (int)Species.Shaymin => GetDexFormValues(Data[FormOffset2 + 4], 1, 2), - (int)Species.Giratina => GetDexFormValues(Data[FormOffset2 + 5], 1, 2), - (int)Species.Pichu when HGSS => GetDexFormValues(Data[FormOffset2 + 6], 2, 3), - _ => Array.Empty(), - }; - } - - public void SetForms(int species, ReadOnlySpan forms) - { - const int brSize = 0x40; - switch (species) - { - case (int)Species.Deoxys: // Deoxys - uint newval = SetDexFormValues(forms, 4, 4); - Data[Offset + 0x4 + (1 * brSize) - 1] = (byte)(newval & 0xFF); - Data[Offset + 0x4 + (2 * brSize) - 1] = (byte)((newval >> 8) & 0xFF); - break; - } - - int FormOffset1 = Offset + OFS_FORM1; - switch (species) - { - case (int)Species.Shellos: // Shellos - Data[FormOffset1 + 0] = (byte)SetDexFormValues(forms, 1, 2); - return; - case (int)Species.Gastrodon: // Gastrodon - Data[FormOffset1 + 1] = (byte)SetDexFormValues(forms, 1, 2); - return; - case (int)Species.Burmy: // Burmy - Data[FormOffset1 + 2] = (byte)SetDexFormValues(forms, 2, 3); - return; - case (int)Species.Wormadam: // Wormadam - Data[FormOffset1 + 3] = (byte)SetDexFormValues(forms, 2, 3); - return; - case (int)Species.Unown: // Unown - int ofs = FormOffset1 + 4; - int len = forms.Length; - Span unown = stackalloc byte[0x1C]; - for (int i = 0; i < len; i++) - unown[i] = (byte)forms[i]; - for (int i = len; i < forms.Length; i++) - unown[i] = 0xFF; - unown.CopyTo(Data.AsSpan(ofs)); - return; - } - - if (DP) - return; - - int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); - int FormOffset2 = PokeDexLanguageFlags + 0x1F4; - switch (species) - { - case (int)Species.Rotom: // Rotom - var value = SetDexFormValues(forms, 3, 6); - WriteUInt32LittleEndian(Data.AsSpan(FormOffset2), value); - return; - case (int)Species.Shaymin: // Shaymin - Data[FormOffset2 + 4] = (byte)SetDexFormValues(forms, 1, 2); - return; - case (int)Species.Giratina: // Giratina - Data[FormOffset2 + 5] = (byte)SetDexFormValues(forms, 1, 2); - return; - case (int)Species.Pichu when HGSS: // Pichu - Data[FormOffset2 + 6] = (byte)SetDexFormValues(forms, 2, 3); - return; - } - } - - private static int[] GetDexFormValues(uint Value, int BitsPerForm, int readCt) - { - int[] Forms = new int[readCt]; - int n1 = 0xFF >> (8 - BitsPerForm); - for (int i = 0; i < Forms.Length; i++) - { - int val = (int)(Value >> (i * BitsPerForm)) & n1; - if (n1 == val && BitsPerForm > 1) - Forms[i] = -1; - else - Forms[i] = val; - } - - // (BitsPerForm > 1) was already handled, handle (BitsPerForm == 1) - if (BitsPerForm == 1 && Forms[0] == Forms[1] && Forms[0] == 1) - Forms[0] = Forms[1] = -1; - - return Forms; - } - - private static uint SetDexFormValues(ReadOnlySpan Forms, int BitsPerForm, int readCt) - { - int n1 = 0xFF >> (8 - BitsPerForm); - uint Value = 0xFFFFFFFF << (readCt * BitsPerForm); - for (int i = 0; i < Forms.Length; i++) - { - int val = Forms[i]; - if (val == -1) - val = n1; - - Value |= (uint)(val << (BitsPerForm * i)); - if (i >= readCt) - throw new ArgumentOutOfRangeException(nameof(readCt), "Array count should be less than bitfield count"); - } - return Value; - } - - private static bool TryInsertForm(Span forms, int form) - { - if (forms.IndexOf(form) >= 0) - return false; // already in list - - // insert at first empty - var index = forms.IndexOf(-1); - if (index < 0) - return false; // no free slots? - - forms[index] = form; - return true; - } - - public int GetUnownFormIndex(int form) - { - var ofs = Offset + OFS_FORM1 + 4; - for (int i = 0; i < 0x1C; i++) - { - byte val = Data[ofs + i]; - if (val == form) - return i; - if (val == 0xFF) - return -1; - } - return -1; - } - - public int GetUnownFormIndexNext(int form) - { - var ofs = Offset + OFS_FORM1 + 4; - for (int i = 0; i < 0x1C; i++) - { - byte val = Data[ofs + i]; - if (val == form) - return i; - if (val == 0xFF) - return i; - } - - return -1; - } - - public void ClearUnownForms() - { - var ofs = Offset + OFS_FORM1 + 4; - for (int i = 0; i < 0x1C; i++) - Data[ofs + i] = 0xFF; - } - - public bool GetUnownForm(int form) => GetUnownFormIndex(form) != -1; - - public void AddUnownForm(int form) - { - var index = GetUnownFormIndexNext(form); - if (index == -1) - return; - - var ofs = Offset + OFS_FORM1 + 4; - Data[ofs + index] = (byte)form; - } - - public override void SetDex(PKM pkm) - { - int species = pkm.Species; - if (species is 0 or > Legal.MaxSpeciesID_4) - return; - if (pkm.IsEgg) // do not add - return; - - var gender = pkm.Gender; - var form = pkm.Form; - var language = pkm.Language; - SetDex(species, gender, form, language); - } - - private void SetDex(int species, int gender, int form, int language) - { - SetCaught(species); - SetSeenGender(species, gender); - SetSeen(species); - SetForms(species, form, gender); - SetLanguage(species, language); - } - - public void SetSeenGender(int species, int gender) - { - if (!GetSeen(species)) - SetSeenGenderNew(species, gender); - else if (GetSeenSingleGender(species)) - SetSeenGenderSecond(species, gender); - } - - public void SetSeenGenderNew(int species, int gender) - { - SetSeenGenderFirst(species, gender); - SetSeenGenderSecond(species, gender); - } - - public void SetSeenGenderNeither(int species) - { - SetSeenGenderFirst(species, 0); - SetSeenGenderSecond(species, 0); - } - - private void SetForms(int species, int form, int gender) - { - if (species == (int)Species.Unown) // Unown - { - AddUnownForm(form); - return; - } - - Span forms = GetForms(species); - if (forms.Length == 0) - return; - - if (species == (int)Species.Pichu && HGSS) // Pichu (HGSS Only) - { - int formID = form == 1 ? 2 : gender; - if (TryInsertForm(forms, formID)) - SetForms(species, forms); - } - else - { - if (TryInsertForm(forms, form)) - SetForms(species, forms); - } - } - - public void SetLanguage(int species, int language, bool value = true) - { - int lang = GetGen4LanguageBitIndex(language); - SetLanguageBitIndex(species, lang, value); - } - - public bool GetLanguageBitIndex(int species, int lang) - { - int dpl = 1 + Array.IndexOf(DPLangSpecies, species); - if (DP && dpl < 0) - return false; - int FormOffset1 = Offset + OFS_FORM1; - int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); - - var ofs = PokeDexLanguageFlags + (DP ? dpl : species); - return FlagUtil.GetFlag(Data, ofs, lang & 7); - } - - public void SetLanguageBitIndex(int species, int lang, bool value) - { - int dpl = 1 + Array.IndexOf(DPLangSpecies, species); - if (DP && dpl <= 0) - return; - int FormOffset1 = Offset + OFS_FORM1; - int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); - - var ofs = PokeDexLanguageFlags + (DP ? dpl : species); - FlagUtil.SetFlag(Data, ofs, lang & 7, value); - } - - public bool HasLanguage(int species) => GetSpeciesLanguageByteIndex(species) >= 0; - - private int GetSpeciesLanguageByteIndex(int species) - { - if (DP) - return Array.IndexOf(DPLangSpecies, species); - return species; - } - - private static readonly int[] DPLangSpecies = { 23, 25, 54, 77, 120, 129, 202, 214, 215, 216, 228, 278, 287, 315 }; - - public static int GetGen4LanguageBitIndex(int lang) => --lang switch - { - 3 => 4, // invert ITA/GER - 4 => 3, // invert ITA/GER - > 5 => 0, // Japanese - < 0 => 1, // English - _ => lang, + (int)Species.Rotom => GetDexFormValues(ReadUInt32LittleEndian(Data.AsSpan(FormOffset2)), 3, 6), + (int)Species.Shaymin => GetDexFormValues(Data[FormOffset2 + 4], 1, 2), + (int)Species.Giratina => GetDexFormValues(Data[FormOffset2 + 5], 1, 2), + (int)Species.Pichu when HGSS => GetDexFormValues(Data[FormOffset2 + 6], 2, 3), + _ => Array.Empty(), }; + } - [Flags] - public enum SetDexArgs + public void SetForms(int species, ReadOnlySpan forms) + { + const int brSize = 0x40; + switch (species) { - None, - SeenAll = 1 << 0, - - CaughtNone = 1 << 1, - CaughtAll = 1 << 2, - - SetNoLanguages = 1 << 3, - SetAllLanguages = 1 << 4, - SetSingleLanguage = 1 << 5, - - SetAllForms = 1 << 6, - - Complete = SeenAll | CaughtAll | SetAllLanguages | SetAllForms, + case (int)Species.Deoxys: // Deoxys + uint newval = SetDexFormValues(forms, 4, 4); + Data[Offset + 0x4 + (1 * brSize) - 1] = (byte)(newval & 0xFF); + Data[Offset + 0x4 + (2 * brSize) - 1] = (byte)((newval >> 8) & 0xFF); + break; } - public void ModifyAll(int species, SetDexArgs args, int lang = 0) + int FormOffset1 = Offset + OFS_FORM1; + switch (species) { - if (args == SetDexArgs.None) - { - ClearSeen(species); + case (int)Species.Shellos: // Shellos + Data[FormOffset1 + 0] = (byte)SetDexFormValues(forms, 1, 2); return; - } - if ((args & SetDexArgs.SeenAll) != 0) - CompleteSeen(species); - - if ((args & SetDexArgs.CaughtNone) != 0) - { - SetCaught(species, false); - ClearLanguages(species); - } - else if ((args & SetDexArgs.CaughtAll) != 0) - { - SetCaught(species); - } - - if ((args & SetDexArgs.SetNoLanguages) != 0) - { - ClearLanguages(species); - } - if ((args & SetDexArgs.SetAllLanguages) != 0) - { - SetLanguages(species); - } - else if ((args & SetDexArgs.SetSingleLanguage) != 0) - { - SetLanguage(species, lang); - } - - if ((args & SetDexArgs.SetAllForms) != 0) - { - CompleteForms(species); - } - } - - private void CompleteForms(int species) - { - var forms = GetFormNames4Dex(species); - if (forms.Length <= 1) + case (int)Species.Gastrodon: // Gastrodon + Data[FormOffset1 + 1] = (byte)SetDexFormValues(forms, 1, 2); return; - - Span values = stackalloc int[forms.Length]; - for (int i = 1; i < values.Length; i++) - values[i] = i; - SetForms(species, values); - } - - private void CompleteSeen(int species) - { - SetSeen(species); - var pi = PersonalTable.HGSS[species]; - if (pi.IsDualGender) - { - SetSeenGenderFirst(species, 0); - SetSeenGenderSecond(species, 1); - } - else - { - SetSeenGender(species, pi.FixedGender & 1); - } - } - - public void ClearSeen(int species) - { - SetCaught(species, false); - SetSeen(species, false); - SetSeenGenderNeither(species); - - SetForms(species, Array.Empty()); - ClearLanguages(species); - } - - private const int LangCount = 6; - private void ClearLanguages(int species) - { - for (int i = 0; i < 8; i++) - SetLanguageBitIndex(species, i, false); - } - - private void SetLanguages(int species, bool value = true) - { - for (int i = 0; i < LangCount; i++) - SetLanguageBitIndex(species, i, value); - } - - // Bulk Manipulation - public override void CompleteDex(bool shinyToo = false) => IterateAll(z => ModifyAll(z, SetDexArgs.Complete)); - public override void SeenNone() => IterateAll(ClearSeen); - public override void CaughtNone() => IterateAll(z => SetCaught(z, false)); - public override void SeenAll(bool shinyToo = false) => IterateAll(CompleteSeen); - - public override void SetDexEntryAll(int species, bool shinyToo = false) => ModifyAll(species, SetDexArgs.Complete); - public override void ClearDexEntryAll(int species) => ModifyAll(species, SetDexArgs.None); - - private static void IterateAll(Action a) - { - for (int i = 1; i <= Legal.MaxSpeciesID_4; i++) - a(i); - } - - public override void SetAllSeen(bool value = true, bool shinyToo = false) - { - if (!value) - { - SeenNone(); + case (int)Species.Burmy: // Burmy + Data[FormOffset1 + 2] = (byte)SetDexFormValues(forms, 2, 3); + return; + case (int)Species.Wormadam: // Wormadam + Data[FormOffset1 + 3] = (byte)SetDexFormValues(forms, 2, 3); + return; + case (int)Species.Unown: // Unown + int ofs = FormOffset1 + 4; + int len = forms.Length; + Span unown = stackalloc byte[0x1C]; + for (int i = 0; i < len; i++) + unown[i] = (byte)forms[i]; + for (int i = len; i < forms.Length; i++) + unown[i] = 0xFF; + unown.CopyTo(Data.AsSpan(ofs)); return; - } - IterateAll(CompleteSeen); } - public override void CaughtAll(bool shinyToo = false) + if (DP) + return; + + int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); + int FormOffset2 = PokeDexLanguageFlags + 0x1F4; + switch (species) { - SeenAll(); - IterateAll(z => SetCaught(z)); + case (int)Species.Rotom: // Rotom + var value = SetDexFormValues(forms, 3, 6); + WriteUInt32LittleEndian(Data.AsSpan(FormOffset2), value); + return; + case (int)Species.Shaymin: // Shaymin + Data[FormOffset2 + 4] = (byte)SetDexFormValues(forms, 1, 2); + return; + case (int)Species.Giratina: // Giratina + Data[FormOffset2 + 5] = (byte)SetDexFormValues(forms, 1, 2); + return; + case (int)Species.Pichu when HGSS: // Pichu + Data[FormOffset2 + 6] = (byte)SetDexFormValues(forms, 2, 3); + return; } } + + private static int[] GetDexFormValues(uint Value, int BitsPerForm, int readCt) + { + int[] Forms = new int[readCt]; + int n1 = 0xFF >> (8 - BitsPerForm); + for (int i = 0; i < Forms.Length; i++) + { + int val = (int)(Value >> (i * BitsPerForm)) & n1; + if (n1 == val && BitsPerForm > 1) + Forms[i] = -1; + else + Forms[i] = val; + } + + // (BitsPerForm > 1) was already handled, handle (BitsPerForm == 1) + if (BitsPerForm == 1 && Forms[0] == Forms[1] && Forms[0] == 1) + Forms[0] = Forms[1] = -1; + + return Forms; + } + + private static uint SetDexFormValues(ReadOnlySpan Forms, int BitsPerForm, int readCt) + { + int n1 = 0xFF >> (8 - BitsPerForm); + uint Value = 0xFFFFFFFF << (readCt * BitsPerForm); + for (int i = 0; i < Forms.Length; i++) + { + int val = Forms[i]; + if (val == -1) + val = n1; + + Value |= (uint)(val << (BitsPerForm * i)); + if (i >= readCt) + throw new ArgumentOutOfRangeException(nameof(readCt), "Array count should be less than bitfield count"); + } + return Value; + } + + private static bool TryInsertForm(Span forms, int form) + { + if (forms.IndexOf(form) >= 0) + return false; // already in list + + // insert at first empty + var index = forms.IndexOf(-1); + if (index < 0) + return false; // no free slots? + + forms[index] = form; + return true; + } + + public int GetUnownFormIndex(int form) + { + var ofs = Offset + OFS_FORM1 + 4; + for (int i = 0; i < 0x1C; i++) + { + byte val = Data[ofs + i]; + if (val == form) + return i; + if (val == 0xFF) + return -1; + } + return -1; + } + + public int GetUnownFormIndexNext(int form) + { + var ofs = Offset + OFS_FORM1 + 4; + for (int i = 0; i < 0x1C; i++) + { + byte val = Data[ofs + i]; + if (val == form) + return i; + if (val == 0xFF) + return i; + } + + return -1; + } + + public void ClearUnownForms() + { + var ofs = Offset + OFS_FORM1 + 4; + for (int i = 0; i < 0x1C; i++) + Data[ofs + i] = 0xFF; + } + + public bool GetUnownForm(int form) => GetUnownFormIndex(form) != -1; + + public void AddUnownForm(int form) + { + var index = GetUnownFormIndexNext(form); + if (index == -1) + return; + + var ofs = Offset + OFS_FORM1 + 4; + Data[ofs + index] = (byte)form; + } + + public override void SetDex(PKM pk) + { + int species = pk.Species; + if (species is 0 or > Legal.MaxSpeciesID_4) + return; + if (pk.IsEgg) // do not add + return; + + var gender = pk.Gender; + var form = pk.Form; + var language = pk.Language; + SetDex(species, gender, form, language); + } + + private void SetDex(int species, int gender, int form, int language) + { + SetCaught(species); + SetSeenGender(species, gender); + SetSeen(species); + SetForms(species, form, gender); + SetLanguage(species, language); + } + + public void SetSeenGender(int species, int gender) + { + if (!GetSeen(species)) + SetSeenGenderNewFlag(species, gender); + else if (GetSeenSingleGender(species)) + SetSeenGenderSecond(species, gender); + } + + public void SetSeenGenderNewFlag(int species, int gender) + { + SetSeenGenderFirst(species, gender); + SetSeenGenderSecond(species, gender); + } + + public void SetSeenGenderNeither(int species) + { + SetSeenGenderFirst(species, 0); + SetSeenGenderSecond(species, 0); + } + + private void SetForms(int species, int form, int gender) + { + if (species == (int)Species.Unown) // Unown + { + AddUnownForm(form); + return; + } + + Span forms = GetForms(species); + if (forms.Length == 0) + return; + + if (species == (int)Species.Pichu && HGSS) // Pichu (HGSS Only) + { + int formID = form == 1 ? 2 : gender; + if (TryInsertForm(forms, formID)) + SetForms(species, forms); + } + else + { + if (TryInsertForm(forms, form)) + SetForms(species, forms); + } + } + + public void SetLanguage(int species, int language, bool value = true) + { + int lang = GetGen4LanguageBitIndex(language); + SetLanguageBitIndex(species, lang, value); + } + + public bool GetLanguageBitIndex(int species, int lang) + { + int dpl = 1 + Array.IndexOf(DPLangSpecies, species); + if (DP && dpl < 0) + return false; + int FormOffset1 = Offset + OFS_FORM1; + int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); + + var ofs = PokeDexLanguageFlags + (DP ? dpl : species); + return FlagUtil.GetFlag(Data, ofs, lang & 7); + } + + public void SetLanguageBitIndex(int species, int lang, bool value) + { + int dpl = 1 + Array.IndexOf(DPLangSpecies, species); + if (DP && dpl <= 0) + return; + int FormOffset1 = Offset + OFS_FORM1; + int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20); + + var ofs = PokeDexLanguageFlags + (DP ? dpl : species); + FlagUtil.SetFlag(Data, ofs, lang & 7, value); + } + + public bool HasLanguage(int species) => GetSpeciesLanguageByteIndex(species) >= 0; + + private int GetSpeciesLanguageByteIndex(int species) + { + if (DP) + return Array.IndexOf(DPLangSpecies, species); + return species; + } + + private static readonly int[] DPLangSpecies = { 23, 25, 54, 77, 120, 129, 202, 214, 215, 216, 228, 278, 287, 315 }; + + public static int GetGen4LanguageBitIndex(int lang) => --lang switch + { + 3 => 4, // invert ITA/GER + 4 => 3, // invert ITA/GER + > 5 => 0, // Japanese + < 0 => 1, // English + _ => lang, + }; + + [Flags] + public enum SetDexArgs + { + None, + SeenAll = 1 << 0, + + CaughtNone = 1 << 1, + CaughtAll = 1 << 2, + + SetNoLanguages = 1 << 3, + SetAllLanguages = 1 << 4, + SetSingleLanguage = 1 << 5, + + SetAllForms = 1 << 6, + + Complete = SeenAll | CaughtAll | SetAllLanguages | SetAllForms, + } + + public void ModifyAll(int species, SetDexArgs args, int lang = 0) + { + if (args == SetDexArgs.None) + { + ClearSeen(species); + return; + } + if ((args & SetDexArgs.SeenAll) != 0) + CompleteSeen(species); + + if ((args & SetDexArgs.CaughtNone) != 0) + { + SetCaught(species, false); + ClearLanguages(species); + } + else if ((args & SetDexArgs.CaughtAll) != 0) + { + SetCaught(species); + } + + if ((args & SetDexArgs.SetNoLanguages) != 0) + { + ClearLanguages(species); + } + if ((args & SetDexArgs.SetAllLanguages) != 0) + { + SetLanguages(species); + } + else if ((args & SetDexArgs.SetSingleLanguage) != 0) + { + SetLanguage(species, lang); + } + + if ((args & SetDexArgs.SetAllForms) != 0) + { + CompleteForms(species); + } + } + + private void CompleteForms(int species) + { + var forms = GetFormNames4Dex(species); + if (forms.Length <= 1) + return; + + Span values = stackalloc int[forms.Length]; + for (int i = 1; i < values.Length; i++) + values[i] = i; + SetForms(species, values); + } + + private void CompleteSeen(int species) + { + SetSeen(species); + var pi = PersonalTable.HGSS[species]; + if (pi.IsDualGender) + { + SetSeenGenderFirst(species, 0); + SetSeenGenderSecond(species, 1); + } + else + { + SetSeenGender(species, pi.FixedGender & 1); + } + } + + public void ClearSeen(int species) + { + SetCaught(species, false); + SetSeen(species, false); + SetSeenGenderNeither(species); + + SetForms(species, Array.Empty()); + ClearLanguages(species); + } + + private const int LangCount = 6; + private void ClearLanguages(int species) + { + for (int i = 0; i < 8; i++) + SetLanguageBitIndex(species, i, false); + } + + private void SetLanguages(int species, bool value = true) + { + for (int i = 0; i < LangCount; i++) + SetLanguageBitIndex(species, i, value); + } + + // Bulk Manipulation + public override void CompleteDex(bool shinyToo = false) => IterateAll(z => ModifyAll(z, SetDexArgs.Complete)); + public override void SeenNone() => IterateAll(ClearSeen); + public override void CaughtNone() => IterateAll(z => SetCaught(z, false)); + public override void SeenAll(bool shinyToo = false) => IterateAll(CompleteSeen); + + public override void SetDexEntryAll(int species, bool shinyToo = false) => ModifyAll(species, SetDexArgs.Complete); + public override void ClearDexEntryAll(int species) => ModifyAll(species, SetDexArgs.None); + + private static void IterateAll(Action a) + { + for (int i = 1; i <= Legal.MaxSpeciesID_4; i++) + a(i); + } + + public override void SetAllSeen(bool value = true, bool shinyToo = false) + { + if (!value) + { + SeenNone(); + return; + } + IterateAll(CompleteSeen); + } + + public override void CaughtAll(bool shinyToo = false) + { + SeenAll(); + IterateAll(z => SetCaught(z)); + } } diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan5.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan5.cs index d37096c8b..db1eccf11 100644 --- a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan5.cs +++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan5.cs @@ -1,199 +1,198 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokédex structure used for Generation 5 games. +/// +public sealed class Zukan5 : Zukan { - /// - /// Pokédex structure used for Generation 5 games. - /// - public sealed class Zukan5 : Zukan + protected override int OFS_SEEN => OFS_CAUGHT + BitSeenSize; + protected override int OFS_CAUGHT => 0x8; + protected override int BitSeenSize => 0x54; + protected override int DexLangFlagByteCount => 7; + protected override int DexLangIDCount => 7; + + public Zukan5(SAV5B2W2 sav, int dex, int langflag) : base(sav, dex, langflag) { - protected override int OFS_SEEN => OFS_CAUGHT + BitSeenSize; - protected override int OFS_CAUGHT => 0x8; - protected override int BitSeenSize => 0x54; - protected override int DexLangFlagByteCount => 7; - protected override int DexLangIDCount => 7; + DexFormIndexFetcher = DexFormUtil.GetDexFormIndexB2W2; + } - public Zukan5(SAV5B2W2 sav, int dex, int langflag) : base(sav, dex, langflag) + public Zukan5(SAV5BW sav, int dex, int langflag) : base(sav, dex, langflag) + { + DexFormIndexFetcher = DexFormUtil.GetDexFormIndexBW; + } + + public readonly Func DexFormIndexFetcher; + + protected override int GetDexLangFlag(int lang) + { + lang--; + if (lang > 5) + lang--; // 0-6 language values + if ((uint)lang > 5) + return -1; + return lang; + } + + protected override bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn) + { + formStart = 0; + formEnd = 0; + return false; + } + + protected override void SetSpindaDexData(PKM pk, bool alreadySeen) + { + } + + protected override void SetAllDexFlagsLanguage(int bit, int lang, bool value = true) + { + lang = GetDexLangFlag(lang); + if (lang < 0) + return; + + // Set the Language + int lbit = (bit * DexLangIDCount) + lang; + if (bit < 493) // shifted by 1, Gen5 species do not have international language bits + SetFlag(PokeDexLanguageFlags, lbit, value); + } + + protected override void SetAllDexSeenFlags(int baseBit, int form, int gender, bool isShiny, bool value = true) + { + var shiny = isShiny ? 1 : 0; + SetDexFlags(baseBit, baseBit, gender, shiny); + SetFormFlags(baseBit + 1, form, shiny, value); + } + + public override void SetDex(PKM pk) + { + if (pk.Species is 0 or > Legal.MaxSpeciesID_5) + return; + if (pk.IsEgg) // do not add + return; + + int bit = pk.Species - 1; + SetCaughtFlag(bit); + + // Set the [Species/Gender/Shiny] Seen Flag + SetAllDexSeenFlags(bit, pk.Form, pk.Gender, pk.IsShiny); + SetAllDexFlagsLanguage(bit, pk.Language); + SetFormFlags(pk); + } + + private void SetCaughtFlag(int bit) => SetFlag(OFS_CAUGHT, bit); + + protected override void SetDisplayedFlag(int baseBit, int formBit, bool value, int shift) + { + if (!value) { - DexFormIndexFetcher = DexFormUtil.GetDexFormIndexB2W2; + SetDisplayed(baseBit, shift, false); + return; } - public Zukan5(SAV5BW sav, int dex, int langflag) : base(sav, dex, langflag) + bool displayed = GetIsSpeciesAnyDisplayed(baseBit); + if (displayed) + return; // no need to set another bit + + SetDisplayed(baseBit, shift, true); + } + + private bool GetIsSpeciesAnyDisplayed(int baseBit) + { + // Check Displayed Status for base form + for (int i = 0; i < 4; i++) { - DexFormIndexFetcher = DexFormUtil.GetDexFormIndexBW; + if (GetDisplayed(baseBit, i)) + return true; } + return false; + } - public readonly Func DexFormIndexFetcher; + private int FormLen => SAV is SAV5B2W2 ? 0xB : 0x9; + private int FormDex => 0x8 + (BitSeenSize * 9); - protected override int GetDexLangFlag(int lang) + private void SetFormFlags(PKM pk) + { + int species = pk.Species; + int form = pk.Form; + var shiny = pk.IsShiny ? 1 : 0; + SetFormFlags(species, form, shiny); + } + + private void SetFormFlags(int species, int form, int shiny, bool value = true) + { + int fc = SAV.Personal[species].FormCount; + int f = DexFormIndexFetcher(species, fc); + if (f < 0) + return; + + var bit = f + form; + + // Set Form Seen Flag + SetFormFlag(bit, shiny, value); + + // Set Displayed Flag if necessary, check all flags + if (!value || !GetIsFormDisplayed(f, fc)) + SetFormFlag(bit, 2 + shiny, value); + } + + public bool GetFormFlag(int formIndex, int flagRegion) => GetFlag(FormDex + (FormLen * flagRegion), formIndex); + public void SetFormFlag(int formIndex, int flagRegion, bool value = true) => SetFlag(FormDex + (FormLen * flagRegion), formIndex, value); + + private bool GetIsFormDisplayed(int f, int fc) + { + for (int i = 0; i < fc; i++) { - lang--; - if (lang > 5) - lang--; // 0-6 language values - if ((uint)lang > 5) - return -1; - return lang; + var index = f + i; + if (GetFormFlag(index, 2)) // Nonshiny + return true; // already set + if (GetFormFlag(index, 3)) // Shiny + return true; // already set } + return false; + } - protected override bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn) + public bool[] GetLanguageBitflags(int species) + { + var result = new bool[DexLangIDCount]; + int bit = species - 1; + for (int i = 0; i < DexLangIDCount; i++) { - formStart = 0; - formEnd = 0; - return false; + int lbit = (bit * DexLangIDCount) + i; + result[i] = GetFlag(PokeDexLanguageFlags, lbit); } + return result; + } - protected override void SetSpindaDexData(PKM pkm, bool alreadySeen) + public void SetLanguageBitflags(int species, bool[] value) + { + int bit = species - 1; + for (int i = 0; i < DexLangIDCount; i++) { - } - - protected override void SetAllDexFlagsLanguage(int bit, int lang, bool value = true) - { - lang = GetDexLangFlag(lang); - if (lang < 0) - return; - - // Set the Language - int lbit = (bit * DexLangIDCount) + lang; - if (bit < 493) // shifted by 1, Gen5 species do not have international language bits - SetFlag(PokeDexLanguageFlags, lbit, value); - } - - protected override void SetAllDexSeenFlags(int baseBit, int form, int gender, bool isShiny, bool value = true) - { - var shiny = isShiny ? 1 : 0; - SetDexFlags(baseBit, baseBit, gender, shiny); - SetFormFlags(baseBit + 1, form, shiny, value); - } - - public override void SetDex(PKM pkm) - { - if (pkm.Species is 0 or > Legal.MaxSpeciesID_5) - return; - if (pkm.IsEgg) // do not add - return; - - int bit = pkm.Species - 1; - SetCaughtFlag(bit); - - // Set the [Species/Gender/Shiny] Seen Flag - SetAllDexSeenFlags(bit, pkm.Form, pkm.Gender, pkm.IsShiny); - SetAllDexFlagsLanguage(bit, pkm.Language); - SetFormFlags(pkm); - } - - private void SetCaughtFlag(int bit) => SetFlag(OFS_CAUGHT, bit); - - protected override void SetDisplayedFlag(int baseBit, int formBit, bool value, int shift) - { - if (!value) - { - SetDisplayed(baseBit, shift, false); - return; - } - - bool displayed = GetIsSpeciesAnyDisplayed(baseBit); - if (displayed) - return; // no need to set another bit - - SetDisplayed(baseBit, shift, true); - } - - private bool GetIsSpeciesAnyDisplayed(int baseBit) - { - // Check Displayed Status for base form - for (int i = 0; i < 4; i++) - { - if (GetDisplayed(baseBit, i)) - return true; - } - return false; - } - - private int FormLen => SAV is SAV5B2W2 ? 0xB : 0x9; - private int FormDex => 0x8 + (BitSeenSize * 9); - - private void SetFormFlags(PKM pkm) - { - int species = pkm.Species; - int form = pkm.Form; - var shiny = pkm.IsShiny ? 1 : 0; - SetFormFlags(species, form, shiny); - } - - private void SetFormFlags(int species, int form, int shiny, bool value = true) - { - int fc = SAV.Personal[species].FormCount; - int f = DexFormIndexFetcher(species, fc); - if (f < 0) - return; - - var bit = f + form; - - // Set Form Seen Flag - SetFormFlag(bit, shiny, value); - - // Set Displayed Flag if necessary, check all flags - if (!value || !GetIsFormDisplayed(f, fc)) - SetFormFlag(bit, 2 + shiny, value); - } - - public bool GetFormFlag(int formIndex, int flagRegion) => GetFlag(FormDex + (FormLen * flagRegion), formIndex); - public void SetFormFlag(int formIndex, int flagRegion, bool value = true) => SetFlag(FormDex + (FormLen * flagRegion), formIndex, value); - - private bool GetIsFormDisplayed(int f, int fc) - { - for (int i = 0; i < fc; i++) - { - var index = f + i; - if (GetFormFlag(index, 2)) // Nonshiny - return true; // already set - if (GetFormFlag(index, 3)) // Shiny - return true; // already set - } - return false; - } - - public bool[] GetLanguageBitflags(int species) - { - var result = new bool[DexLangIDCount]; - int bit = species - 1; - for (int i = 0; i < DexLangIDCount; i++) - { - int lbit = (bit * DexLangIDCount) + i; - result[i] = GetFlag(PokeDexLanguageFlags, lbit); - } - return result; - } - - public void SetLanguageBitflags(int species, bool[] value) - { - int bit = species - 1; - for (int i = 0; i < DexLangIDCount; i++) - { - int lbit = (bit * DexLangIDCount) + i; - SetFlag(PokeDexLanguageFlags, lbit, value[i]); - } - } - - public void ToggleLanguageFlagsAll(bool value) - { - var arr = GetBlankLanguageBits(value); - for (int i = 1; i <= SAV.MaxSpeciesID; i++) - SetLanguageBitflags(i, arr); - } - - public void ToggleLanguageFlagsSingle(int species, bool value) - { - var arr = GetBlankLanguageBits(value); - SetLanguageBitflags(species, arr); - } - - private bool[] GetBlankLanguageBits(bool value) - { - var result = new bool[DexLangIDCount]; - for (int i = 0; i < DexLangIDCount; i++) - result[i] = value; - return result; + int lbit = (bit * DexLangIDCount) + i; + SetFlag(PokeDexLanguageFlags, lbit, value[i]); } } -} \ No newline at end of file + + public void ToggleLanguageFlagsAll(bool value) + { + var arr = GetBlankLanguageBits(value); + for (int i = 1; i <= SAV.MaxSpeciesID; i++) + SetLanguageBitflags(i, arr); + } + + public void ToggleLanguageFlagsSingle(int species, bool value) + { + var arr = GetBlankLanguageBits(value); + SetLanguageBitflags(species, arr); + } + + private bool[] GetBlankLanguageBits(bool value) + { + var result = new bool[DexLangIDCount]; + for (int i = 0; i < DexLangIDCount; i++) + result[i] = value; + return result; + } +} diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan6.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan6.cs index e199d73fa..bd58e1056 100644 --- a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan6.cs +++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan6.cs @@ -2,241 +2,240 @@ using System.Diagnostics; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokdex structure used for Generation 6 games. +/// +public abstract class Zukan6 : Zukan { - /// - /// Pokdex structure used for Generation 6 games. - /// - public abstract class Zukan6 : Zukan + protected override int OFS_SEEN => OFS_CAUGHT + BitSeenSize; + protected override int OFS_CAUGHT => 0x8; + protected override int BitSeenSize => 0x60; + protected override int DexLangFlagByteCount => 631; // 721 * 7, rounded up + protected override int DexLangIDCount => 7; + protected int SpindaOffset { get; init; } + + protected Zukan6(SAV6XY sav, int dex, int langflag) : base(sav, dex, langflag) { - protected override int OFS_SEEN => OFS_CAUGHT + BitSeenSize; - protected override int OFS_CAUGHT => 0x8; - protected override int BitSeenSize => 0x60; - protected override int DexLangFlagByteCount => 631; // 721 * 7, rounded up - protected override int DexLangIDCount => 7; - protected int SpindaOffset { get; init; } + DexFormIndexFetcher = DexFormUtil.GetDexFormIndexXY; + } - protected Zukan6(SAV6XY sav, int dex, int langflag) : base(sav, dex, langflag) + private Func DexFormIndexFetcher { get; } + + protected Zukan6(SAV6AO sav, int dex, int langflag) : base(sav, dex, langflag) + { + DexFormIndexFetcher = DexFormUtil.GetDexFormIndexORAS; + } + + protected override int GetDexLangFlag(int lang) + { + lang--; + if (lang > 5) + lang--; // 0-6 language values + if ((uint)lang > 5) + return -1; + return lang; + } + + protected override bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn) + { + formStart = 0; + formEnd = 0; + return false; + } + + protected override void SetSpindaDexData(PKM pk, bool alreadySeen) + { + } + + protected override void SetAllDexFlagsLanguage(int bit, int lang, bool value = true) + { + lang = GetDexLangFlag(lang); + if (lang < 0) + return; + + int lbit = (bit * DexLangIDCount) + lang; + if (lbit < DexLangFlagByteCount << 3) // Sanity check for max length of region + SetFlag(PokeDexLanguageFlags, lbit, value); + } + + protected override void SetAllDexSeenFlags(int baseBit, int form, int gender, bool isShiny, bool value = true) + { + var shiny = isShiny ? 1 : 0; + SetDexFlags(baseBit, baseBit, gender, shiny); + SetFormFlags(baseBit + 1, form, shiny, value); + } + + public override void SetDex(PKM pk) + { + if (pk.Species is 0 or > Legal.MaxSpeciesID_6) + return; + if (pk.IsEgg) // do not add + return; + + int bit = pk.Species - 1; + SetCaughtFlag(bit, pk.Version); + + // Set the [Species/Gender/Shiny] Seen Flag + SetAllDexSeenFlags(pk.Species - 1, pk.Form, pk.Gender, pk.IsShiny); + SetAllDexFlagsLanguage(bit, pk.Language); + SetFormFlags(pk); + } + + protected abstract void SetCaughtFlag(int bit, int origin); + + private int FormLen => SAV is SAV6XY ? 0x18 : 0x26; + private int FormDex => 0x8 + (BitSeenSize * 9); + public bool GetFormFlag(int formIndex, int flagRegion) => GetFlag(FormDex + (FormLen * flagRegion), formIndex); + public void SetFormFlag(int formIndex, int flagRegion, bool value = true) => SetFlag(FormDex + (FormLen * flagRegion), formIndex, value); + + private void SetFormFlags(PKM pk) + { + int species = pk.Species; + int form = pk.Form; + var shiny = pk.IsShiny ? 1 : 0; + SetFormFlags(species, form, shiny); + } + + private void SetFormFlags(int species, int form, int shiny, bool value = true) + { + int fc = SAV.Personal[species].FormCount; + int f = DexFormIndexFetcher(species, fc); + if (f < 0) + return; + + var bit = f + form; + + // Set Form Seen Flag + SetFormFlag(bit, shiny, value); + + // Set Displayed Flag if necessary, check all flags + if (!value || !GetIsFormDisplayed(f, fc)) + SetFormFlag(bit, 2 + shiny, value); + } + + private bool GetIsFormDisplayed(int f, int fc) + { + for (int i = 0; i < fc; i++) { - DexFormIndexFetcher = DexFormUtil.GetDexFormIndexXY; + var index = f + i; + if (GetFormFlag(index, 2)) // Nonshiny + return true; // already set + if (GetFormFlag(index, 3)) // Shiny + return true; // already set } + return false; + } - private Func DexFormIndexFetcher { get; } + public uint SpindaPID + { + get => ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + SpindaOffset)); + set => WriteUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + SpindaOffset), value); + } - protected Zukan6(SAV6AO sav, int dex, int langflag) : base(sav, dex, langflag) + public bool[] GetLanguageBitflags(int species) + { + var result = new bool[DexLangIDCount]; + int bit = species - 1; + for (int i = 0; i < DexLangIDCount; i++) { - DexFormIndexFetcher = DexFormUtil.GetDexFormIndexORAS; + int lbit = (bit * DexLangIDCount) + i; + result[i] = GetFlag(PokeDexLanguageFlags, lbit); } + return result; + } - protected override int GetDexLangFlag(int lang) + public void SetLanguageBitflags(int species, bool[] value) + { + int bit = species - 1; + for (int i = 0; i < DexLangIDCount; i++) { - lang--; - if (lang > 5) - lang--; // 0-6 language values - if ((uint)lang > 5) - return -1; - return lang; - } - - protected override bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn) - { - formStart = 0; - formEnd = 0; - return false; - } - - protected override void SetSpindaDexData(PKM pkm, bool alreadySeen) - { - } - - protected override void SetAllDexFlagsLanguage(int bit, int lang, bool value = true) - { - lang = GetDexLangFlag(lang); - if (lang < 0) - return; - - int lbit = (bit * DexLangIDCount) + lang; - if (lbit < DexLangFlagByteCount << 3) // Sanity check for max length of region - SetFlag(PokeDexLanguageFlags, lbit, value); - } - - protected override void SetAllDexSeenFlags(int baseBit, int form, int gender, bool isShiny, bool value = true) - { - var shiny = isShiny ? 1 : 0; - SetDexFlags(baseBit, baseBit, gender, shiny); - SetFormFlags(baseBit + 1, form, shiny, value); - } - - public override void SetDex(PKM pkm) - { - if (pkm.Species is 0 or > Legal.MaxSpeciesID_6) - return; - if (pkm.IsEgg) // do not add - return; - - int bit = pkm.Species - 1; - SetCaughtFlag(bit, pkm.Version); - - // Set the [Species/Gender/Shiny] Seen Flag - SetAllDexSeenFlags(pkm.Species - 1, pkm.Form, pkm.Gender, pkm.IsShiny); - SetAllDexFlagsLanguage(bit, pkm.Language); - SetFormFlags(pkm); - } - - protected abstract void SetCaughtFlag(int bit, int origin); - - private int FormLen => SAV is SAV6XY ? 0x18 : 0x26; - private int FormDex => 0x8 + (BitSeenSize * 9); - public bool GetFormFlag(int formIndex, int flagRegion) => GetFlag(FormDex + (FormLen * flagRegion), formIndex); - public void SetFormFlag(int formIndex, int flagRegion, bool value = true) => SetFlag(FormDex + (FormLen * flagRegion), formIndex, value); - - private void SetFormFlags(PKM pkm) - { - int species = pkm.Species; - int form = pkm.Form; - var shiny = pkm.IsShiny ? 1 : 0; - SetFormFlags(species, form, shiny); - } - - private void SetFormFlags(int species, int form, int shiny, bool value = true) - { - int fc = SAV.Personal[species].FormCount; - int f = DexFormIndexFetcher(species, fc); - if (f < 0) - return; - - var bit = f + form; - - // Set Form Seen Flag - SetFormFlag(bit, shiny, value); - - // Set Displayed Flag if necessary, check all flags - if (!value || !GetIsFormDisplayed(f, fc)) - SetFormFlag(bit, 2 + shiny, value); - } - - private bool GetIsFormDisplayed(int f, int fc) - { - for (int i = 0; i < fc; i++) - { - var index = f + i; - if (GetFormFlag(index, 2)) // Nonshiny - return true; // already set - if (GetFormFlag(index, 3)) // Shiny - return true; // already set - } - return false; - } - - public uint SpindaPID - { - get => ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + SpindaOffset)); - set => WriteUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + SpindaOffset), value); - } - - public bool[] GetLanguageBitflags(int species) - { - var result = new bool[DexLangIDCount]; - int bit = species - 1; - for (int i = 0; i < DexLangIDCount; i++) - { - int lbit = (bit * DexLangIDCount) + i; - result[i] = GetFlag(PokeDexLanguageFlags, lbit); - } - return result; - } - - public void SetLanguageBitflags(int species, bool[] value) - { - int bit = species - 1; - for (int i = 0; i < DexLangIDCount; i++) - { - int lbit = (bit * DexLangIDCount) + i; - SetFlag(PokeDexLanguageFlags, lbit, value[i]); - } - } - - public void ToggleLanguageFlagsAll(bool value) - { - var arr = GetBlankLanguageBits(value); - for (int i = 1; i <= SAV.MaxSpeciesID; i++) - SetLanguageBitflags(i, arr); - } - - public void ToggleLanguageFlagsSingle(int species, bool value) - { - var arr = GetBlankLanguageBits(value); - SetLanguageBitflags(species, arr); - } - - private bool[] GetBlankLanguageBits(bool value) - { - var result = new bool[DexLangIDCount]; - for (int i = 0; i < DexLangIDCount; i++) - result[i] = value; - return result; + int lbit = (bit * DexLangIDCount) + i; + SetFlag(PokeDexLanguageFlags, lbit, value[i]); } } - /// - /// Pokdex structure used for . - /// - public sealed class Zukan6AO : Zukan6 + public void ToggleLanguageFlagsAll(bool value) { - public Zukan6AO(SAV6AO sav, int dex, int langflag) : base(sav, dex, langflag) - { - SpindaOffset = 0x680; - } + var arr = GetBlankLanguageBits(value); + for (int i = 1; i <= SAV.MaxSpeciesID; i++) + SetLanguageBitflags(i, arr); + } - protected override void SetCaughtFlag(int bit, int origin) - { + public void ToggleLanguageFlagsSingle(int species, bool value) + { + var arr = GetBlankLanguageBits(value); + SetLanguageBitflags(species, arr); + } + + private bool[] GetBlankLanguageBits(bool value) + { + var result = new bool[DexLangIDCount]; + for (int i = 0; i < DexLangIDCount; i++) + result[i] = value; + return result; + } +} + +/// +/// Pokdex structure used for . +/// +public sealed class Zukan6AO : Zukan6 +{ + public Zukan6AO(SAV6AO sav, int dex, int langflag) : base(sav, dex, langflag) + { + SpindaOffset = 0x680; + } + + protected override void SetCaughtFlag(int bit, int origin) + { + SetFlag(OFS_CAUGHT, bit); + if (GetEncounterCount(bit) == 0) + SetEncounterCount(bit, 1); + } + + public ushort GetEncounterCount(int index) + { + var ofs = PokeDex + 0x686 + (index * 2); + return ReadUInt16LittleEndian(SAV.Data.AsSpan(ofs)); + } + + public void SetEncounterCount(int index, ushort value) + { + var ofs = PokeDex + 0x686 + (index * 2); + WriteUInt16LittleEndian(SAV.Data.AsSpan(ofs), value); + } +} + +/// +/// Pokdex structure used for . +/// +public sealed class Zukan6XY : Zukan6 +{ + public Zukan6XY(SAV6XY sav, int dex, int langflag) : base(sav, dex, langflag) + { + SpindaOffset = 0x648; + } + + protected override void SetCaughtFlag(int bit, int origin) + { + // Species: 1-649 for X/Y, and not for ORAS; Set the Foreign Owned Flag + if (origin < (int)GameVersion.X && bit < (int)Species.Genesect) + SetForeignFlag(bit); + else SetFlag(OFS_CAUGHT, bit); - if (GetEncounterCount(bit) == 0) - SetEncounterCount(bit, 1); - } - - public ushort GetEncounterCount(int index) - { - var ofs = PokeDex + 0x686 + (index * 2); - return ReadUInt16LittleEndian(SAV.Data.AsSpan(ofs)); - } - - public void SetEncounterCount(int index, ushort value) - { - var ofs = PokeDex + 0x686 + (index * 2); - WriteUInt16LittleEndian(SAV.Data.AsSpan(ofs), value); - } } - /// - /// Pokdex structure used for . - /// - public sealed class Zukan6XY : Zukan6 + public bool GetForeignFlag(int bit) { - public Zukan6XY(SAV6XY sav, int dex, int langflag) : base(sav, dex, langflag) - { - SpindaOffset = 0x648; - } - - protected override void SetCaughtFlag(int bit, int origin) - { - // Species: 1-649 for X/Y, and not for ORAS; Set the Foreign Owned Flag - if (origin < (int)GameVersion.X && bit < (int)Species.Genesect) - SetForeignFlag(bit); - else - SetFlag(OFS_CAUGHT, bit); - } - - public bool GetForeignFlag(int bit) - { - Debug.Assert(bit < (int)Species.Genesect); - return GetFlag(0x64C, bit); - } - - public void SetForeignFlag(int bit, bool value = true) - { - Debug.Assert(bit < (int)Species.Genesect); - SetFlag(0x64C, bit, value); - } + Debug.Assert(bit < (int)Species.Genesect); + return GetFlag(0x64C, bit); } -} \ No newline at end of file + + public void SetForeignFlag(int bit, bool value = true) + { + Debug.Assert(bit < (int)Species.Genesect); + SetFlag(0x64C, bit, value); + } +} diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7.cs index cf4535e91..73a30095c 100644 --- a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7.cs +++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7.cs @@ -3,319 +3,317 @@ using System.Diagnostics; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokédex structure used for Generation 7 games. +/// > +public class Zukan7 : Zukan { - /// - /// Pokdex structure used for Generation 7 games. - /// > - public class Zukan7 : Zukan + private const int MAGIC = 0x2F120F17; + private const int SIZE_MAGIC = 4; + private const int SIZE_FLAGS = 4; + private const int SIZE_MISC = 0x80; // Misc Data (1024 bits) + private const int SIZE_CAUGHT = 0x68; // 832 bits + + protected sealed override int OFS_CAUGHT => SIZE_MAGIC + SIZE_FLAGS + SIZE_MISC; + protected sealed override int OFS_SEEN => OFS_CAUGHT + SIZE_CAUGHT; + + protected sealed override int BitSeenSize => 0x8C; // 1120 bits + protected sealed override int DexLangFlagByteCount => 920; // 0x398 = 817*9, top off the savedata block. + protected sealed override int DexLangIDCount => 9; // CHT, skipping langID 6 (unused) + + private readonly IList FormBaseSpecies; + + public Zukan7(SAV7SM sav, int dex, int langflag) : this(sav, dex, langflag, DexFormUtil.GetDexFormIndexSM) { } + public Zukan7(SAV7USUM sav, int dex, int langflag) : this(sav, dex, langflag, DexFormUtil.GetDexFormIndexUSUM) { } + protected Zukan7(SAV7b sav, int dex, int langflag) : this(sav, dex, langflag, DexFormUtil.GetDexFormIndexGG) { } + + private Zukan7(SaveFile sav, int dex, int langflag, Func form) : base(sav, dex, langflag) { - private const int MAGIC = 0x2F120F17; - private const int SIZE_MAGIC = 4; - private const int SIZE_FLAGS = 4; - private const int SIZE_MISC = 0x80; // Misc Data (1024 bits) - private const int SIZE_CAUGHT = 0x68; // 832 bits + DexFormIndexFetcher = form; + FormBaseSpecies = GetFormIndexBaseSpeciesList(); + Debug.Assert(!SAV.State.Exportable || ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex)) == MAGIC); + } - protected sealed override int OFS_CAUGHT => SIZE_MAGIC + SIZE_FLAGS + SIZE_MISC; - protected sealed override int OFS_SEEN => OFS_CAUGHT + SIZE_CAUGHT; + public Func DexFormIndexFetcher { get; } - protected sealed override int BitSeenSize => 0x8C; // 1120 bits - protected sealed override int DexLangFlagByteCount => 920; // 0x398 = 817*9, top off the savedata block. - protected sealed override int DexLangIDCount => 9; // CHT, skipping langID 6 (unused) + protected sealed override void SetAllDexSeenFlags(int baseBit, int form, int gender, bool isShiny, bool value = true) + { + int species = baseBit + 1; - private readonly IList FormBaseSpecies; + if (species == (int)Species.Castform) + isShiny = false; - public Zukan7(SAV7SM sav, int dex, int langflag) : this(sav, dex, langflag, DexFormUtil.GetDexFormIndexSM) { } - public Zukan7(SAV7USUM sav, int dex, int langflag) : this(sav, dex, langflag, DexFormUtil.GetDexFormIndexUSUM) { } - protected Zukan7(SAV7b sav, int dex, int langflag) : this(sav, dex, langflag, DexFormUtil.GetDexFormIndexGG) { } - - private Zukan7(SaveFile sav, int dex, int langflag, Func form) : base(sav, dex, langflag) + // Starting with Gen7, form bits are stored in the same region as the species flags. + int formstart = form; + int formend = form; + bool reset = GetSaneFormsToIterate(species, out int fs, out int fe, formstart); + if (reset) { - DexFormIndexFetcher = form; - FormBaseSpecies = GetFormIndexBaseSpeciesList(); - Debug.Assert(!SAV.State.Exportable || ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex)) == MAGIC); + formstart = fs; + formend = fe; } - public Func DexFormIndexFetcher { get; } - - protected sealed override void SetAllDexSeenFlags(int baseBit, int form, int gender, bool isShiny, bool value = true) + int shiny = isShiny ? 1 : 0; + for (int f = formstart; f <= formend; f++) { - int species = baseBit + 1; - - if (species == (int)Species.Castform) - isShiny = false; - - // Starting with Gen7, form bits are stored in the same region as the species flags. - int formstart = form; - int formend = form; - bool reset = GetSaneFormsToIterate(species, out int fs, out int fe, formstart); - if (reset) + int formBit = baseBit; + if (f > 0) // Override the bit to overwrite { - formstart = fs; - formend = fe; - } - - int shiny = isShiny ? 1 : 0; - for (int f = formstart; f <= formend; f++) - { - int formBit = baseBit; - if (f > 0) // Override the bit to overwrite + int fc = SAV.Personal[species].FormCount; + if (fc > 1) // actually has forms { - int fc = SAV.Personal[species].FormCount; - if (fc > 1) // actually has forms - { - int index = DexFormIndexFetcher(species, fc, SAV.MaxSpeciesID - 1); - if (index >= 0) // bit index valid - formBit = index + f; - } - } - SetDexFlags(baseBit, formBit, gender, shiny, value); - } - } - - protected override bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn) - { - return SanitizeFormsToIterate(species, out formStart, out formEnd, formIn, SAV is SAV7USUM); - } - - public static bool SanitizeFormsToIterate(int species, out int formStart, out int formEnd, int formIn, bool USUM) - { - // 004AA370 in Moon - // Simplified in terms of usage -- only overrides to give all the battle forms for a pkm - switch (species) - { - case 351: // Castform - formStart = 0; - formEnd = 3; - return true; - - case 421: // Cherrim - case 555: // Darmanitan - case 648: // Meloetta - case 746: // Wishiwashi - case 778: // Mimikyu - // Alolans - case 020: // Raticate - case 105: // Marowak - formStart = 0; - formEnd = 1; - return true; - - case 735: // Gumshoos - case 758: // Salazzle - case 754: // Lurantis - case 738: // Vikavolt - case 784: // Kommo-o - case 752: // Araquanid - case 777: // Togedemaru - case 743: // Ribombee - case 744: // Rockruff - break; - - case 774 when formIn <= 6: // Minior - break; // don't give meteor forms except the first - - case 718 when formIn > 1: - break; - default: - int count = USUM ? DexFormUtil.GetDexFormCountUSUM(species) : DexFormUtil.GetDexFormCountSM(species); - formStart = formEnd = 0; - return count < formIn; - } - formStart = 0; - formEnd = 0; - return true; - } - - protected sealed override int GetDexLangFlag(int lang) => lang switch - { - > 10 or 6 or <= 0 => -1, // invalid language - // skip over langID 0 (unused) => [0-8] - // skip over langID 6 (unused) - >= 7 => lang - 2, - _ => lang - 1, - }; - - protected sealed override void SetSpindaDexData(PKM pkm, bool alreadySeen) - { - int shift = (pkm.Gender & 1) | (pkm.IsShiny ? 2 : 0); - if (alreadySeen) // update? - { - var flag1 = (1 << (shift + 4)); - if ((SAV.Data[PokeDex + 0x84] & flag1) != 0) // Already showing this one - return; - - var span = SAV.Data.AsSpan(PokeDex + 0x8E8 + (shift * 4)); - WriteUInt32LittleEndian(span, pkm.EncryptionConstant); - SAV.Data[PokeDex + 0x84] |= (byte)(flag1 | (1 << shift)); - } - else if ((SAV.Data[PokeDex + 0x84] & (1 << shift)) == 0) - { - var span = SAV.Data.AsSpan(PokeDex + 0x8E8 + (shift * 4)); - WriteUInt32LittleEndian(span, pkm.EncryptionConstant); - SAV.Data[PokeDex + 0x84] |= (byte)(1 << shift); - } - } - - // Dex Flags - public bool NationalDex - { - get => (SAV.Data[PokeDex + 4] & 1) == 1; - set => SAV.Data[PokeDex + 4] = (byte)((SAV.Data[PokeDex + 4] & 0xFE) | (value ? 1 : 0)); - } - - /// - /// Gets the last viewed dex entry in the Pokedex (by National Dex ID), internally called DefaultMons - /// - public uint CurrentViewedDex => ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + 4)) >> 9 & 0x3FF; - - public IEnumerable GetAllFormEntries(int species) - { - var fc = SAV.Personal[species].FormCount; - for (int j = 1; j < fc; j++) - { - int start = j; - int end = j; - if (GetSaneFormsToIterate(species, out int s, out int n, j)) - { - start = s; - end = n; - } - start = Math.Max(1, start); - for (int f = start; f <= end; f++) - { - int x = GetDexFormIndex(species, fc, f); - if (x >= 0) - yield return x; + int index = DexFormIndexFetcher(species, fc, SAV.MaxSpeciesID - 1); + if (index >= 0) // bit index valid + formBit = index + f; } } - } - - public int GetDexFormIndex(int species, int fc, int f) - { - var index = DexFormIndexFetcher(species, fc, f); - if (index < 0) - return index; - return index + SAV.MaxSpeciesID - 1; - } - - public IList GetEntryNames(IReadOnlyList speciesNames) - { - var names = new List(); - var max = SAV.MaxSpeciesID; - for (int i = 1; i <= max; i++) - names.Add($"{i:000} - {speciesNames[i]}"); - - // Add forms - int ctr = max + 1; - for (int species = 1; species <= max; species++) - { - int c = SAV.Personal[species].FormCount; - for (int f = 1; f < c; f++) - { - int x = GetDexFormIndex(species, c, f); - if (x >= 0) - names.Add($"{ctr++:000} - {speciesNames[species]}-{f}"); - } - } - return names; - } - - /// - /// Gets a list of Species IDs that a given dex-form index corresponds to. - /// - /// - private List GetFormIndexBaseSpeciesList() - { - var baseSpecies = new List(); - for (int species = 1; species <= SAV.MaxSpeciesID; species++) - { - int c = SAV.Personal[species].FormCount; - for (int f = 1; f < c; f++) - { - int x = GetDexFormIndex(species, c, f); - if (x >= 0) - baseSpecies.Add(species); - } - } - return baseSpecies; - } - - public int GetBaseSpeciesGenderValue(int index) - { - // meowstic special handling - const int meow = 678; - if (index == meow - 1 || (index >= SAV.MaxSpeciesID && FormBaseSpecies[index - SAV.MaxSpeciesID] == meow)) - return index < SAV.MaxSpeciesID ? PersonalInfo.RatioMagicMale : PersonalInfo.RatioMagicFemale; // M : F - - if (index < SAV.MaxSpeciesID) - return SAV.Personal[index + 1].Gender; - - index -= SAV.MaxSpeciesID; - int species = FormBaseSpecies[index]; - return SAV.Personal[species].Gender; - } - - public int GetBaseSpecies(int index) - { - if (index <= SAV.MaxSpeciesID) - return index; - - return FormBaseSpecies[index - SAV.MaxSpeciesID - 1]; - } - - protected sealed override void SetAllDexFlagsLanguage(int bit, int lang, bool value = true) - { - lang = GetDexLangFlag(lang); - if (lang < 0) - return; - - int lbit = (bit * DexLangIDCount) + lang; - if (lbit < DexLangFlagByteCount << 3) // Sanity check for max length of region - SetFlag(PokeDexLanguageFlags, lbit, value); - } - - public bool[] GetLanguageBitflags(int species) - { - var result = new bool[DexLangIDCount]; - int bit = species - 1; - for (int i = 0; i < DexLangIDCount; i++) - { - int lbit = (bit * DexLangIDCount) + i; - result[i] = GetFlag(PokeDexLanguageFlags, lbit); - } - return result; - } - - public void SetLanguageBitflags(int species, bool[] value) - { - int bit = species - 1; - for (int i = 0; i < DexLangIDCount; i++) - { - int lbit = (bit * DexLangIDCount) + i; - SetFlag(PokeDexLanguageFlags, lbit, value[i]); - } - } - - public void ToggleLanguageFlagsAll(bool value) - { - var arr = GetBlankLanguageBits(value); - for (int i = 1; i <= SAV.MaxSpeciesID; i++) - SetLanguageBitflags(i, arr); - } - - public void ToggleLanguageFlagsSingle(int species, bool value) - { - var arr = GetBlankLanguageBits(value); - SetLanguageBitflags(species, arr); - } - - private bool[] GetBlankLanguageBits(bool value) - { - var result = new bool[DexLangIDCount]; - for (int i = 0; i < DexLangIDCount; i++) - result[i] = value; - return result; + SetDexFlags(baseBit, formBit, gender, shiny, value); } } -} \ No newline at end of file + + protected override bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn) + { + return SanitizeFormsToIterate(species, out formStart, out formEnd, formIn, SAV is SAV7USUM); + } + + public static bool SanitizeFormsToIterate(int species, out int formStart, out int formEnd, int formIn, bool USUM) + { + // 004AA370 in Moon + // Simplified in terms of usage -- only overrides to give all the battle forms for a pk + switch (species) + { + case 351: // Castform + formStart = 0; + formEnd = 3; + return true; + + case 421: // Cherrim + case 555: // Darmanitan + case 648: // Meloetta + case 746: // Wishiwashi + case 778: // Mimikyu + // Alolans + case 020: // Raticate + case 105: // Marowak + formStart = 0; + formEnd = 1; + return true; + + case 735: // Gumshoos + case 758: // Salazzle + case 754: // Lurantis + case 738: // Vikavolt + case 784: // Kommo-o + case 752: // Araquanid + case 777: // Togedemaru + case 743: // Ribombee + case 744: // Rockruff + break; + + case 774 when formIn <= 6: // Minior + break; // don't give meteor forms except the first + + case 718 when formIn > 1: + break; + default: + int count = USUM ? DexFormUtil.GetDexFormCountUSUM(species) : DexFormUtil.GetDexFormCountSM(species); + formStart = formEnd = 0; + return count < formIn; + } + formStart = 0; + formEnd = 0; + return true; + } + + protected sealed override int GetDexLangFlag(int lang) => lang switch + { + > 10 or 6 or <= 0 => -1, // invalid language + // skip over langID 0 (unused) => [0-8] + // skip over langID 6 (unused) + >= 7 => lang - 2, + _ => lang - 1, + }; + + protected sealed override void SetSpindaDexData(PKM pk, bool alreadySeen) + { + int shift = (pk.Gender & 1) | (pk.IsShiny ? 2 : 0); + if (alreadySeen) // update? + { + var flag1 = (1 << (shift + 4)); + if ((SAV.Data[PokeDex + 0x84] & flag1) != 0) // Already showing this one + return; + + var span = SAV.Data.AsSpan(PokeDex + 0x8E8 + (shift * 4)); + WriteUInt32LittleEndian(span, pk.EncryptionConstant); + SAV.Data[PokeDex + 0x84] |= (byte)(flag1 | (1 << shift)); + } + else if ((SAV.Data[PokeDex + 0x84] & (1 << shift)) == 0) + { + var span = SAV.Data.AsSpan(PokeDex + 0x8E8 + (shift * 4)); + WriteUInt32LittleEndian(span, pk.EncryptionConstant); + SAV.Data[PokeDex + 0x84] |= (byte)(1 << shift); + } + } + + // Dex Flags + public bool NationalDex + { + get => (SAV.Data[PokeDex + 4] & 1) == 1; + set => SAV.Data[PokeDex + 4] = (byte)((SAV.Data[PokeDex + 4] & 0xFE) | (value ? 1 : 0)); + } + + /// + /// Gets the last viewed dex entry in the Pokedex (by National Dex ID), internally called DefaultMons + /// + public uint CurrentViewedDex => (ReadUInt32LittleEndian(SAV.Data.AsSpan(PokeDex + 4)) >> 9) & 0x3FF; + + public IEnumerable GetAllFormEntries(int species) + { + var fc = SAV.Personal[species].FormCount; + for (int j = 1; j < fc; j++) + { + int start = j; + int end = j; + if (GetSaneFormsToIterate(species, out int s, out int n, j)) + { + start = s; + end = n; + } + start = Math.Max(1, start); + for (int f = start; f <= end; f++) + { + int x = GetDexFormIndex(species, fc, f); + if (x >= 0) + yield return x; + } + } + } + + public int GetDexFormIndex(int species, int fc, int f) + { + var index = DexFormIndexFetcher(species, fc, f); + if (index < 0) + return index; + return index + SAV.MaxSpeciesID - 1; + } + + public IList GetEntryNames(IReadOnlyList speciesNames) + { + var names = new List(); + var max = SAV.MaxSpeciesID; + for (int i = 1; i <= max; i++) + names.Add($"{i:000} - {speciesNames[i]}"); + + // Add forms + int ctr = max + 1; + for (int species = 1; species <= max; species++) + { + int c = SAV.Personal[species].FormCount; + for (int f = 1; f < c; f++) + { + int x = GetDexFormIndex(species, c, f); + if (x >= 0) + names.Add($"{ctr++:000} - {speciesNames[species]}-{f}"); + } + } + return names; + } + + /// + /// Gets a list of Species IDs that a given dex-form index corresponds to. + /// + private List GetFormIndexBaseSpeciesList() + { + var baseSpecies = new List(); + for (int species = 1; species <= SAV.MaxSpeciesID; species++) + { + int c = SAV.Personal[species].FormCount; + for (int f = 1; f < c; f++) + { + int x = GetDexFormIndex(species, c, f); + if (x >= 0) + baseSpecies.Add(species); + } + } + return baseSpecies; + } + + public int GetBaseSpeciesGenderValue(int index) + { + // meowstic special handling + const int meow = 678; + if (index == meow - 1 || (index >= SAV.MaxSpeciesID && FormBaseSpecies[index - SAV.MaxSpeciesID] == meow)) + return index < SAV.MaxSpeciesID ? PersonalInfo.RatioMagicMale : PersonalInfo.RatioMagicFemale; // M : F + + if (index < SAV.MaxSpeciesID) + return SAV.Personal[index + 1].Gender; + + index -= SAV.MaxSpeciesID; + int species = FormBaseSpecies[index]; + return SAV.Personal[species].Gender; + } + + public int GetBaseSpecies(int index) + { + if (index <= SAV.MaxSpeciesID) + return index; + + return FormBaseSpecies[index - SAV.MaxSpeciesID - 1]; + } + + protected sealed override void SetAllDexFlagsLanguage(int bit, int lang, bool value = true) + { + lang = GetDexLangFlag(lang); + if (lang < 0) + return; + + int lbit = (bit * DexLangIDCount) + lang; + if (lbit < DexLangFlagByteCount << 3) // Sanity check for max length of region + SetFlag(PokeDexLanguageFlags, lbit, value); + } + + public bool[] GetLanguageBitflags(int species) + { + var result = new bool[DexLangIDCount]; + int bit = species - 1; + for (int i = 0; i < DexLangIDCount; i++) + { + int lbit = (bit * DexLangIDCount) + i; + result[i] = GetFlag(PokeDexLanguageFlags, lbit); + } + return result; + } + + public void SetLanguageBitflags(int species, bool[] value) + { + int bit = species - 1; + for (int i = 0; i < DexLangIDCount; i++) + { + int lbit = (bit * DexLangIDCount) + i; + SetFlag(PokeDexLanguageFlags, lbit, value[i]); + } + } + + public void ToggleLanguageFlagsAll(bool value) + { + var arr = GetBlankLanguageBits(value); + for (int i = 1; i <= SAV.MaxSpeciesID; i++) + SetLanguageBitflags(i, arr); + } + + public void ToggleLanguageFlagsSingle(int species, bool value) + { + var arr = GetBlankLanguageBits(value); + SetLanguageBitflags(species, arr); + } + + private bool[] GetBlankLanguageBits(bool value) + { + var result = new bool[DexLangIDCount]; + for (int i = 0; i < DexLangIDCount; i++) + result[i] = value; + return result; + } +} diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7b.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7b.cs index 1a1376ac6..46f303198 100644 --- a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7b.cs +++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan7b.cs @@ -1,222 +1,221 @@ using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokdex structure used for games, slightly modified from . +/// > +public sealed class Zukan7b : Zukan7 { - /// - /// Pokdex structure used for games, slightly modified from . - /// > - public sealed class Zukan7b : Zukan7 + public Zukan7b(SAV7b sav, int dex, int langflag) : base(sav, dex, langflag) { - public Zukan7b(SAV7b sav, int dex, int langflag) : base(sav, dex, langflag) + } + + public override void SetDex(PKM pk) + { + if (!TryGetSizeEntryIndex(pk.Species, pk.Form, out _)) + return; + SetSizeData((PB7)pk); + base.SetDex(pk); + } + + protected override void SetDex(int species, int bit, int form, int gender, bool shiny, int lang) + { + if (IsBuddy(species, form)) + form = 0; + base.SetDex(species, bit, form, gender, shiny, lang); + } + + private static bool IsBuddy(int species, int form) => (species == (int)Species.Pikachu && form == 8) || (species == (int)Species.Eevee && form == 1); + + public const int DefaultEntryValue = 0x7F; + + public bool GetSizeData(DexSizeType group, int species, int form, out int height, out int weight) + { + height = weight = DefaultEntryValue; + if (TryGetSizeEntryIndex(species, form, out var index)) + return GetSizeData(group, index, out height, out weight); + return false; + } + + public bool GetSizeData(DexSizeType group, int index, out int height, out int weight) + { + var ofs = GetDexSizeOffset(group, index); + var entry = SAV.Data.AsSpan(ofs); + height = ReadUInt16LittleEndian(entry) >> 1; + weight = ReadUInt16LittleEndian(entry[2..]); + return !IsEntryUnset(height, weight); + } + + private static bool IsEntryUnset(int height, int weight) => height == DefaultEntryValue && weight == DefaultEntryValue; + + private void SetSizeData(PB7 pk) + { + int species = pk.Species; + int form = pk.Form; + if (!TryGetSizeEntryIndex(species, form, out int index)) + return; + + if (Math.Round(pk.HeightAbsolute) < pk.PersonalInfo.Height) // possible minimum height { - } - - public override void SetDex(PKM pkm) - { - if (!TryGetSizeEntryIndex(pkm.Species, pkm.Form, out _)) - return; - SetSizeData((PB7)pkm); - base.SetDex(pkm); - } - - protected override void SetDex(int species, int bit, int form, int gender, bool shiny, int lang) - { - if (IsBuddy(species, form)) - form = 0; - base.SetDex(species, bit, form, gender, shiny, lang); - } - - private static bool IsBuddy(int species, int form) => (species == (int)Species.Pikachu && form == 8) || (species == (int)Species.Eevee && form == 1); - - public const int DefaultEntryValue = 0x7F; - - public bool GetSizeData(DexSizeType group, int species, int form, out int height, out int weight) - { - height = weight = DefaultEntryValue; - if (TryGetSizeEntryIndex(species, form, out var index)) - return GetSizeData(group, index, out height, out weight); - return false; - } - - public bool GetSizeData(DexSizeType group, int index, out int height, out int weight) - { - var ofs = GetDexSizeOffset(group, index); + int ofs = GetDexSizeOffset(DexSizeType.MinHeight, index); var entry = SAV.Data.AsSpan(ofs); - height = ReadUInt16LittleEndian(entry) >> 1; - weight = ReadUInt16LittleEndian(entry[2..]); - return !IsEntryUnset(height, weight); + var minHeight = ReadUInt16LittleEndian(entry) >> 1; + var calcHeight = PB7.GetHeightAbsolute(pk.PersonalInfo, minHeight); + if (Math.Round(pk.HeightAbsolute) < Math.Round(calcHeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset + SetSizeData(pk, DexSizeType.MinHeight); + } + else if (Math.Round(pk.HeightAbsolute) > pk.PersonalInfo.Height) // possible maximum height + { + int ofs = GetDexSizeOffset(DexSizeType.MaxHeight, index); + var entry = SAV.Data.AsSpan(ofs); + var maxHeight = ReadUInt16LittleEndian(entry) >> 1; + var calcHeight = PB7.GetHeightAbsolute(pk.PersonalInfo, maxHeight); + if (Math.Round(pk.HeightAbsolute) > Math.Round(calcHeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset + SetSizeData(pk, DexSizeType.MaxHeight); } - private static bool IsEntryUnset(int height, int weight) => height == DefaultEntryValue && weight == DefaultEntryValue; - - private void SetSizeData(PB7 pkm) + if (Math.Round(pk.WeightAbsolute) < pk.PersonalInfo.Weight) // possible minimum weight { - int species = pkm.Species; - int form = pkm.Form; - if (!TryGetSizeEntryIndex(species, form, out int index)) - return; - - if (Math.Round(pkm.HeightAbsolute) < pkm.PersonalInfo.Height) // possible minimum height - { - int ofs = GetDexSizeOffset(DexSizeType.MinHeight, index); - var entry = SAV.Data.AsSpan(ofs); - var minHeight = ReadUInt16LittleEndian(entry) >> 1; - var calcHeight = PB7.GetHeightAbsolute(pkm.PersonalInfo, minHeight); - if (Math.Round(pkm.HeightAbsolute) < Math.Round(calcHeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset - SetSizeData(pkm, DexSizeType.MinHeight); - } - else if (Math.Round(pkm.HeightAbsolute) > pkm.PersonalInfo.Height) // possible maximum height - { - int ofs = GetDexSizeOffset(DexSizeType.MaxHeight, index); - var entry = SAV.Data.AsSpan(ofs); - var maxHeight = ReadUInt16LittleEndian(entry) >> 1; - var calcHeight = PB7.GetHeightAbsolute(pkm.PersonalInfo, maxHeight); - if (Math.Round(pkm.HeightAbsolute) > Math.Round(calcHeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset - SetSizeData(pkm, DexSizeType.MaxHeight); - } - - if (Math.Round(pkm.WeightAbsolute) < pkm.PersonalInfo.Weight) // possible minimum weight - { - int ofs = GetDexSizeOffset(DexSizeType.MinWeight, index); - var entry = SAV.Data.AsSpan(ofs); - var minHeight = ReadUInt16LittleEndian(entry) >> 1; - var minWeight = ReadUInt16LittleEndian(entry[2..]); - var calcWeight = PB7.GetWeightAbsolute(pkm.PersonalInfo, minHeight, minWeight); - if (Math.Round(pkm.WeightAbsolute) < Math.Round(calcWeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset - SetSizeData(pkm, DexSizeType.MinWeight); - } - else if (Math.Round(pkm.WeightAbsolute) > pkm.PersonalInfo.Weight) // possible maximum weight - { - int ofs = GetDexSizeOffset(DexSizeType.MaxWeight, index); - var entry = SAV.Data.AsSpan(ofs); - var maxHeight = ReadUInt16LittleEndian(entry) >> 1; - var maxWeight = ReadUInt16LittleEndian(entry[2..]); - var calcWeight = PB7.GetWeightAbsolute(pkm.PersonalInfo, maxHeight, maxWeight); - if (Math.Round(pkm.WeightAbsolute) > Math.Round(calcWeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset - SetSizeData(pkm, DexSizeType.MaxWeight); - } + int ofs = GetDexSizeOffset(DexSizeType.MinWeight, index); + var entry = SAV.Data.AsSpan(ofs); + var minHeight = ReadUInt16LittleEndian(entry) >> 1; + var minWeight = ReadUInt16LittleEndian(entry[2..]); + var calcWeight = PB7.GetWeightAbsolute(pk.PersonalInfo, minHeight, minWeight); + if (Math.Round(pk.WeightAbsolute) < Math.Round(calcWeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset + SetSizeData(pk, DexSizeType.MinWeight); } - - private static int GetDexSizeOffset(DexSizeType group, int index) => 0x3978 + (index * 6) + ((int)group * 0x45C); // blockofs + 0xF78 + ([186*6]*n) + x*6 - - private void SetSizeData(PB7 pkm, DexSizeType group) + else if (Math.Round(pk.WeightAbsolute) > pk.PersonalInfo.Weight) // possible maximum weight { - var tree = EvolutionTree.Evolves7b; - int species = pkm.Species; - int form = pkm.Form; - - int height = pkm.HeightScalar; - int weight = pkm.WeightScalar; - - // update for all species in potential lineage - var allspec = tree.GetEvolutionsAndPreEvolutions(species, form); - foreach (var sf in allspec) - { - var s = sf & 0x7FF; - var f = sf >> 11; - SetSizeData(group, s, f, height, weight); - } - } - - public void SetSizeData(DexSizeType group, int species, int form, int height, int weight) - { - if (TryGetSizeEntryIndex(species, form, out var index)) - SetSizeData(group, index, height, weight); - } - - public void SetSizeData(DexSizeType group, int index, int height, int weight) - { - var ofs = GetDexSizeOffset(group, index); - var span = SAV.Data.AsSpan(ofs); - WriteUInt16LittleEndian(span, (ushort)(height << 1)); - WriteUInt16LittleEndian(span[2..], (ushort)weight); - } - - public static bool TryGetSizeEntryIndex(int species, int form, out int index) - { - index = -1; - if (form == 0 && species <= 151) - { - index = species - 1; - return true; - } - for (int i = 0; i < SizeDexInfoTable.Length; i += 3) - { - if (SizeDexInfoTable[i] != species) - continue; - if (SizeDexInfoTable[i + 1] != form) - continue; - index = SizeDexInfoTable[i + 2]; - return true; - } - return false; - } - - private static readonly ushort[] SizeDexInfoTable = - { - // species, form, index - 808, 0, 151, - 809, 0, 152, - - 003, 1, 153, - 006, 1, 154, - 006, 2, 155, - 009, 1, 156, - 015, 1, 157, - 018, 1, 158, - 019, 1, 159, - 020, 1, 160, - 026, 1, 161, - 027, 1, 162, - 028, 1, 163, - 037, 1, 164, - 038, 1, 165, - 050, 1, 166, - 051, 1, 167, - 052, 1, 168, - 053, 1, 169, - 065, 1, 170, - 074, 1, 171, - 075, 1, 172, - 076, 1, 173, - 080, 1, 174, - 088, 1, 175, - 089, 1, 176, - 094, 1, 177, - 103, 1, 178, - 105, 1, 179, - 115, 1, 180, - 127, 1, 181, - 130, 1, 182, - 142, 1, 183, - 150, 1, 184, - 150, 2, 185, - }; - - protected override bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn) - { - switch (species) - { - // Totems with Alolan Forms - case 020 or 105: // Raticate or Marowak - formStart = 0; - formEnd = 1; - return true; - - default: - int count = DexFormUtil.GetDexFormCountGG(species); - formStart = formEnd = 0; - return count < formIn; - } + int ofs = GetDexSizeOffset(DexSizeType.MaxWeight, index); + var entry = SAV.Data.AsSpan(ofs); + var maxHeight = ReadUInt16LittleEndian(entry) >> 1; + var maxWeight = ReadUInt16LittleEndian(entry[2..]); + var calcWeight = PB7.GetWeightAbsolute(pk.PersonalInfo, maxHeight, maxWeight); + if (Math.Round(pk.WeightAbsolute) > Math.Round(calcWeight) || ReadUInt32LittleEndian(entry) == 0x007F00FE) // unset + SetSizeData(pk, DexSizeType.MaxWeight); } } - public enum DexSizeType + private static int GetDexSizeOffset(DexSizeType group, int index) => 0x3978 + (index * 6) + ((int)group * 0x45C); // blockofs + 0xF78 + ([186*6]*n) + x*6 + + private void SetSizeData(PB7 pk, DexSizeType group) { - MinHeight, - MaxHeight, - MinWeight, - MaxWeight, + var tree = EvolutionTree.Evolves7b; + int species = pk.Species; + int form = pk.Form; + + int height = pk.HeightScalar; + int weight = pk.WeightScalar; + + // update for all species in potential lineage + var allspec = tree.GetEvolutionsAndPreEvolutions(species, form); + foreach (var sf in allspec) + { + var s = sf & 0x7FF; + var f = sf >> 11; + SetSizeData(group, s, f, height, weight); + } + } + + public void SetSizeData(DexSizeType group, int species, int form, int height, int weight) + { + if (TryGetSizeEntryIndex(species, form, out var index)) + SetSizeData(group, index, height, weight); + } + + public void SetSizeData(DexSizeType group, int index, int height, int weight) + { + var ofs = GetDexSizeOffset(group, index); + var span = SAV.Data.AsSpan(ofs); + WriteUInt16LittleEndian(span, (ushort)(height << 1)); + WriteUInt16LittleEndian(span[2..], (ushort)weight); + } + + public static bool TryGetSizeEntryIndex(int species, int form, out int index) + { + index = -1; + if (form == 0 && species <= 151) + { + index = species - 1; + return true; + } + for (int i = 0; i < SizeDexInfoTable.Length; i += 3) + { + if (SizeDexInfoTable[i] != species) + continue; + if (SizeDexInfoTable[i + 1] != form) + continue; + index = SizeDexInfoTable[i + 2]; + return true; + } + return false; + } + + private static readonly ushort[] SizeDexInfoTable = + { + // species, form, index + 808, 0, 151, + 809, 0, 152, + + 003, 1, 153, + 006, 1, 154, + 006, 2, 155, + 009, 1, 156, + 015, 1, 157, + 018, 1, 158, + 019, 1, 159, + 020, 1, 160, + 026, 1, 161, + 027, 1, 162, + 028, 1, 163, + 037, 1, 164, + 038, 1, 165, + 050, 1, 166, + 051, 1, 167, + 052, 1, 168, + 053, 1, 169, + 065, 1, 170, + 074, 1, 171, + 075, 1, 172, + 076, 1, 173, + 080, 1, 174, + 088, 1, 175, + 089, 1, 176, + 094, 1, 177, + 103, 1, 178, + 105, 1, 179, + 115, 1, 180, + 127, 1, 181, + 130, 1, 182, + 142, 1, 183, + 150, 1, 184, + 150, 2, 185, + }; + + protected override bool GetSaneFormsToIterate(int species, out int formStart, out int formEnd, int formIn) + { + switch (species) + { + // Totems with Alolan Forms + case 020 or 105: // Raticate or Marowak + formStart = 0; + formEnd = 1; + return true; + + default: + int count = DexFormUtil.GetDexFormCountGG(species); + formStart = formEnd = 0; + return count < formIn; + } } } + +public enum DexSizeType +{ + MinHeight, + MaxHeight, + MinWeight, + MaxWeight, +} diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan8.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan8.cs index fd7c8246c..48b54f64f 100644 --- a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan8.cs +++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan8.cs @@ -2,815 +2,816 @@ using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Pokdex structure used for . +/// > +public sealed class Zukan8 : ZukanBase { + private readonly SCBlock Galar; + private readonly SCBlock Rigel1; + private readonly SCBlock Rigel2; + /// - /// Pokdex structure used for . - /// > - public sealed class Zukan8 : ZukanBase + /// Reverses a Species into the component information. + ///
+ public readonly IReadOnlyDictionary DexLookup; + + public Zukan8(SAV8SWSH sav, SCBlock galar, SCBlock rigel1, SCBlock rigel2) : base(sav, 0) { - private readonly SCBlock Galar; - private readonly SCBlock Rigel1; - private readonly SCBlock Rigel2; + Galar = galar; + Rigel1 = rigel1; + Rigel2 = rigel2; + var revision = GetRevision(); + DexLookup = GetDexLookup(PersonalTable.SWSH, revision); + } - /// - /// Reverses a Species into the component information. - /// - public readonly IReadOnlyDictionary DexLookup; + /// + /// Checks how much DLC patches have been installed by detecting if DLC blocks are present. + /// + public int GetRevision() + { + if (Rigel1.Data.Length == 0) + return 0; // No DLC1 data allocated + if (Rigel2.Data.Length == 0) + return 1; // No DLC2 data allocated + return 2; + } - public Zukan8(SAV8SWSH sav, SCBlock galar, SCBlock rigel1, SCBlock rigel2) : base(sav, 0) + private byte[] GetDexBlock(Zukan8Type infoDexType) => infoDexType switch + { + Zukan8Type.Galar => Galar.Data, + Zukan8Type.Armor => Rigel1.Data, + Zukan8Type.Crown => Rigel2.Data, + _ => throw new ArgumentOutOfRangeException(nameof(infoDexType), infoDexType, null), + }; + + private static bool GetFlag(byte[] data, int offset, int bitIndex) => FlagUtil.GetFlag(data, offset + (bitIndex >> 3), bitIndex); + private static void SetFlag(byte[] data, int offset, int bitIndex, bool value = true) => FlagUtil.SetFlag(data, offset + (bitIndex >> 3), bitIndex, value); + + private static Dictionary GetDexLookup(PersonalTable pt, int dexRevision) + { + var lookup = new Dictionary(); + for (int i = 1; i <= pt.MaxSpeciesID; i++) { - Galar = galar; - Rigel1 = rigel1; - Rigel2 = rigel2; - var revision = GetRevision(); - DexLookup = GetDexLookup(PersonalTable.SWSH, revision); - } - - /// - /// Checks how much DLC patches have been installed by detecting if DLC blocks are present. - /// - public int GetRevision() - { - if (Rigel1.Data.Length == 0) - return 0; // No DLC1 data allocated - if (Rigel2.Data.Length == 0) - return 1; // No DLC2 data allocated - return 2; - } - - private byte[] GetDexBlock(Zukan8Type infoDexType) => infoDexType switch - { - Zukan8Type.Galar => Galar.Data, - Zukan8Type.Armor => Rigel1.Data, - Zukan8Type.Crown => Rigel2.Data, - _ => throw new ArgumentOutOfRangeException(nameof(infoDexType), infoDexType, null), - }; - - private static bool GetFlag(byte[] data, int offset, int bitIndex) => FlagUtil.GetFlag(data, offset + (bitIndex >> 3), bitIndex); - private static void SetFlag(byte[] data, int offset, int bitIndex, bool value = true) => FlagUtil.SetFlag(data, offset + (bitIndex >> 3), bitIndex, value); - - private static Dictionary GetDexLookup(PersonalTable pt, int dexRevision) - { - var lookup = new Dictionary(); - for (int i = 1; i <= pt.MaxSpeciesID; i++) + var p = (PersonalInfoSWSH) pt[i]; + var index = p.PokeDexIndex; + if (index != 0) { - var p = (PersonalInfoSWSH) pt[i]; - var index = p.PokeDexIndex; - if (index != 0) - { - lookup.Add(i, new Zukan8Index(Zukan8Type.Galar, index)); - continue; - } - - if (dexRevision == 0) - continue; - - var armor = p.ArmorDexIndex; - if (armor != 0) - { - lookup.Add(i, new Zukan8Index(Zukan8Type.Armor, armor)); - continue; - } - - if (dexRevision == 1) - continue; - - var crown = p.CrownDexIndex; - if (crown != 0) - { - lookup.Add(i, new Zukan8Index(Zukan8Type.Crown, crown)); - // continue; - } + lookup.Add(i, new Zukan8Index(Zukan8Type.Galar, index)); + continue; } - return lookup; - } - public static List GetRawIndexes(PersonalTable pt, int dexRevision) - { - var result = new List(); - for (int i = 1; i <= pt.MaxSpeciesID; i++) - { - var p = (PersonalInfoSWSH)pt[i]; - var index = p.PokeDexIndex; - if (index != 0) - result.Add(new Zukan8EntryInfo(i, new Zukan8Index(Zukan8Type.Galar, index))); - } if (dexRevision == 0) - return result; + continue; - for (int i = 1; i <= pt.MaxSpeciesID; i++) + var armor = p.ArmorDexIndex; + if (armor != 0) { - var p = (PersonalInfoSWSH)pt[i]; - var index = p.ArmorDexIndex; - if (index != 0) - result.Add(new Zukan8EntryInfo(i, new Zukan8Index(Zukan8Type.Armor, index))); + lookup.Add(i, new Zukan8Index(Zukan8Type.Armor, armor)); + continue; } + if (dexRevision == 1) - return result; + continue; - for (int i = 1; i <= pt.MaxSpeciesID; i++) + var crown = p.CrownDexIndex; + if (crown != 0) { - var p = (PersonalInfoSWSH)pt[i]; - var index = p.CrownDexIndex; - if (index != 0) - result.Add(new Zukan8EntryInfo(i, new Zukan8Index(Zukan8Type.Crown, index))); + lookup.Add(i, new Zukan8Index(Zukan8Type.Crown, crown)); + // continue; } - return result; } + return lookup; + } - private static int GetDexLangFlag(int lang) => lang switch + public static List GetRawIndexes(PersonalTable pt, int dexRevision) + { + var result = new List(); + for (int i = 1; i <= pt.MaxSpeciesID; i++) { - > 10 or 6 or <= 0 => -1, // invalid language - // skip over langID 0 (unused) => [0-8] - // skip over langID 6 (unused) - >= 7 => lang - 2, - _ => lang - 1, - }; + var p = (PersonalInfoSWSH)pt[i]; + var index = p.PokeDexIndex; + if (index != 0) + result.Add(new Zukan8EntryInfo(i, new Zukan8Index(Zukan8Type.Galar, index))); + } + if (dexRevision == 0) + return result; + + for (int i = 1; i <= pt.MaxSpeciesID; i++) + { + var p = (PersonalInfoSWSH)pt[i]; + var index = p.ArmorDexIndex; + if (index != 0) + result.Add(new Zukan8EntryInfo(i, new Zukan8Index(Zukan8Type.Armor, index))); + } + if (dexRevision == 1) + return result; + + for (int i = 1; i <= pt.MaxSpeciesID; i++) + { + var p = (PersonalInfoSWSH)pt[i]; + var index = p.CrownDexIndex; + if (index != 0) + result.Add(new Zukan8EntryInfo(i, new Zukan8Index(Zukan8Type.Crown, index))); + } + return result; + } + + private static int GetDexLangFlag(int lang) => lang switch + { + > 10 or 6 or <= 0 => -1, // invalid language + // skip over langID 0 (unused) => [0-8] + // skip over langID 6 (unused) + >= 7 => lang - 2, + _ => lang - 1, + }; #if DEBUG - public IList GetEntryNames(IReadOnlyList speciesNames) + public IList GetEntryNames(IReadOnlyList speciesNames) + { + var dex = new List(); + foreach (var d in DexLookup) { - var dex = new List(); - foreach (var d in DexLookup) - { - var species = d.Key; - var entry = d.Value; - var name = entry.GetEntryName(speciesNames, species); - dex.Add(name); - } - dex.Sort(); - return dex; + var species = d.Key; + var entry = d.Value; + var name = entry.GetEntryName(speciesNames, species); + dex.Add(name); } + dex.Sort(); + return dex; + } #endif - #region Structure + #region Structure - internal const int EntrySize = 0x30; + internal const int EntrySize = 0x30; - // First 0x20 bytes are for seen flags, allocated as 4 QWORD values. - private const int SeenRegionCount = 4; - private const int SeenRegionSize = sizeof(ulong); - // not_shiny_gender_0, - // not_shiny_gender_1, - // shiny_gender_0, - // shiny_gender_1 - // Each QWORD stores the following bits: - // - FormsSeen[63], default form is index 0. - // - Gigantamax:1 -- for Urshifu, they store a bit prior for the second Gigantamax form... + // First 0x20 bytes are for seen flags, allocated as 4 QWORD values. + private const int SeenRegionCount = 4; + private const int SeenRegionSize = sizeof(ulong); + // not_shiny_gender_0, + // not_shiny_gender_1, + // shiny_gender_0, + // shiny_gender_1 + // Each QWORD stores the following bits: + // - FormsSeen[63], default form is index 0. + // - Gigantamax:1 -- for Urshifu, they store a bit prior for the second Gigantamax form... - // Next 4 bytes are for obtained info (u32) - private const int OFS_CAUGHT = 0x20; - // Owned:1 (posessed by player) - // OwnedGigantamax:1 (Gigantamaxed by player in battle) - // LanguagesObtained:2-14 (flags) - // DisplayFormID:15-27 (value) - // DisplayGigantamaxInstead:28 (flag) - // DisplayGender:29/30 (m/f) - // DisplayShiny:31 (flag) + // Next 4 bytes are for obtained info (u32) + private const int OFS_CAUGHT = 0x20; + // Owned:1 (posessed by player) + // OwnedGigantamax:1 (Gigantamaxed by player in battle) + // LanguagesObtained:2-14 (flags) + // DisplayFormID:15-27 (value) + // DisplayGigantamaxInstead:28 (flag) + // DisplayGender:29/30 (m/f) + // DisplayShiny:31 (flag) - // Next 4 bytes are Battled Count (u32) - private const int OFS_BATTLED = 0x24; + // Next 4 bytes are Battled Count (u32) + private const int OFS_BATTLED = 0x24; - // Next 4 bytes are unused/reserved for future updates. - private const int OFS_UNK1 = 0x28; - // Seen Gigantamax-1 Form:1 (Urshifu) + // Next 4 bytes are unused/reserved for future updates. + private const int OFS_UNK1 = 0x28; + // Seen Gigantamax-1 Form:1 (Urshifu) - // Next 4 bytes are Unused(?) - private const int OFS_UNK2 = 0x2C; + // Next 4 bytes are Unused(?) + private const int OFS_UNK2 = 0x2C; - public bool GetEntry(int species, out Zukan8Index entry) => DexLookup.TryGetValue(species, out entry); - - public override bool GetSeen(int species) - { - if (!GetEntry(species, out var entry)) - return false; - - return GetSeen(entry); - } - - public bool GetSeen(Zukan8Index entry) - { - byte[] data = GetDexBlock(entry.DexType); - int offset = entry.Offset; - for (int i = 0; i < SeenRegionCount; i++) - { - var ofs = offset + (SeenRegionSize * i); - if (ReadUInt64LittleEndian(data.AsSpan(ofs)) != 0) - return true; - } + public bool GetEntry(int species, out Zukan8Index entry) => DexLookup.TryGetValue(species, out entry); + public override bool GetSeen(int species) + { + if (!GetEntry(species, out var entry)) return false; + + return GetSeen(entry); + } + + public bool GetSeen(Zukan8Index entry) + { + byte[] data = GetDexBlock(entry.DexType); + int offset = entry.Offset; + for (int i = 0; i < SeenRegionCount; i++) + { + var ofs = offset + (SeenRegionSize * i); + if (ReadUInt64LittleEndian(data.AsSpan(ofs)) != 0) + return true; } - public bool GetSeenRegion(int species, int form, int region) - { - if (!GetEntry(species, out var entry)) - return false; + return false; + } - return GetSeenRegion(entry, form, region); + public bool GetSeenRegion(int species, int form, int region) + { + if (!GetEntry(species, out var entry)) + return false; + + return GetSeenRegion(entry, form, region); + } + + public bool GetSeenRegion(Zukan8Index entry, int form, int region) + { + if ((uint)region >= SeenRegionCount) + throw new ArgumentOutOfRangeException(nameof(region)); + if ((uint)form > 63) + return false; + + var dex = entry.DexType; + var offset = entry.Offset; + var data = GetDexBlock(dex); + var ofs = SeenRegionSize * region; + return GetFlag(data, offset + ofs, form); + } + + public void SetSeenRegion(int species, int form, int region, bool value = true) + { + if (!GetEntry(species, out var entry)) + return; + + SetSeenRegion(entry, form, region, value); + } + + public void SetSeenRegion(Zukan8Index entry, int form, int region, bool value = true) + { + if ((uint) region >= SeenRegionCount) + throw new ArgumentOutOfRangeException(nameof(region)); + if ((uint) form > 63) + return; + + var data = GetDexBlock(entry.DexType); + int index = entry.Offset; + var ofs = SeenRegionSize * region; + SetFlag(data, index + ofs, form, value); + } + + public override bool GetCaught(int species) => GetCaughtFlagID(species, 0); + public void SetCaught(int species, bool value = true) => SetCaughtFlagID(species, 0, value); + public bool GetCaughtGigantamaxed(int species) => GetCaughtFlagID(species, 1); + public void SetCaughtGigantamax(int species, bool value = true) => SetCaughtFlagID(species, 1, value); + public bool GetIsLanguageIndexObtained(int species, int langIndex) => GetCaughtFlagID(species, 2 + langIndex); + public void SetIsLanguageIndexObtained(int species, int langIndex, bool value = true) => SetCaughtFlagID(species, 2 + langIndex, value); + + public bool GetCaught(Zukan8Index entry) => GetCaughtFlagID(entry, 0); + public void SetCaught(Zukan8Index entry, bool value = true) => SetCaughtFlagID(entry, 0, value); + public bool GetCaughtGigantamaxed(Zukan8Index entry) => GetCaughtFlagID(entry, 1); + public void SetCaughtGigantamax(Zukan8Index entry, bool value = true) => SetCaughtFlagID(entry, 1, value); + public bool GetIsLanguageIndexObtained(Zukan8Index entry, int langIndex) => GetCaughtFlagID(entry, 2 + langIndex); + public void SetIsLanguageIndexObtained(Zukan8Index entry, int langIndex, bool value = true) => SetCaughtFlagID(entry, 2 + langIndex, value); + + private bool GetCaughtFlagID(int species, int bit) + { + if (!GetEntry(species, out var entry)) + return false; + + return GetCaughtFlagID(entry, bit); + } + + private bool GetCaughtFlagID(Zukan8Index entry, int bit) + { + var data = GetDexBlock(entry.DexType); + return GetFlag(data, entry.Offset + OFS_CAUGHT, bit); + } + + public void SetCaughtFlagID(int species, int bit, bool value = true) + { + if (!GetEntry(species, out var entry)) + return; + + SetCaughtFlagID(entry, bit, value); + } + + public void SetCaughtFlagID(Zukan8Index entry, int bit, bool value = true) + { + var data = GetDexBlock(entry.DexType); + SetFlag(data, entry.Offset + OFS_CAUGHT, bit, value); + } + + public bool GetIsLanguageObtained(int species, int language) + { + int langIndex = GetDexLangFlag(language); + if (langIndex < 0) + return false; + + return GetIsLanguageIndexObtained(species, langIndex); + } + + public void SetIsLanguageObtained(int species, int language, bool value = true) + { + int langIndex = GetDexLangFlag(language); + if (langIndex < 0) + return; + + SetIsLanguageIndexObtained(species, langIndex, value); + } + + public uint GetFormDisplayed(int species) + { + if (!GetEntry(species, out var entry)) + return 0; + + return GetFormDisplayed(entry); + } + + public uint GetFormDisplayed(Zukan8Index entry) + { + var data = GetDexBlock(entry.DexType); + var index = entry.Offset; + var value = ReadUInt32LittleEndian(data.AsSpan(index + OFS_CAUGHT)); + return (value >> 15) & 0x1FFF; // (0x1FFF is really overkill, GameFreak) + } + + public void SetFormDisplayed(int species, uint value = 0) + { + if (!GetEntry(species, out var entry)) + return; + + SetFormDisplayed(entry, value); + } + + public void SetFormDisplayed(Zukan8Index entry, uint value = 0) + { + var data = GetDexBlock(entry.DexType); + var index = entry.Offset; + var span = data.AsSpan(index + OFS_CAUGHT); + var current = ReadUInt32LittleEndian(span); + uint update = (current & ~(0x1FFFu << 15)) | ((value & 0x1FFF) << 15); + WriteUInt32LittleEndian(span, update); + } + + public uint GetGenderDisplayed(int species) + { + if (!GetEntry(species, out var entry)) + return 0; + + return GetGenderDisplayed(entry); + } + + public uint GetGenderDisplayed(Zukan8Index entry) + { + var data = GetDexBlock(entry.DexType); + var index = entry.Offset; + var value = ReadUInt32LittleEndian(data.AsSpan(index + OFS_CAUGHT)); + return (value >> 29) & 3; + } + + public void SetGenderDisplayed(int species, uint value = 0) + { + if (!GetEntry(species, out var entry)) + return; + + SetGenderDisplayed(entry, value); + } + + public void SetGenderDisplayed(Zukan8Index entry, uint value = 0) + { + var data = GetDexBlock(entry.DexType); + var index = entry.Offset; + var span = data.AsSpan(index + OFS_CAUGHT); + var current = ReadUInt32LittleEndian(span); + uint update = (current & ~(3u << 29)) | ((value & 3) << 29); + WriteUInt32LittleEndian(span, update); + } + + public bool GetDisplayDynamaxInstead(int species) => GetCaughtFlagID(species, 28); + public void SetDisplayDynamaxInstead(int species, bool value = true) => SetCaughtFlagID(species, 28, value); + public bool GetDisplayShiny(int species) => GetCaughtFlagID(species, 31); + public void SetDisplayShiny(int species, bool value = true) => SetCaughtFlagID(species, 31, value); + + public void SetCaughtFlags32(int species, uint value) => SetU32(species, value, OFS_CAUGHT); + public uint GetBattledCount(int species) => GetU32(species, OFS_BATTLED); + public void SetBattledCount(int species, uint value) => SetU32(species, value, OFS_BATTLED); + + public uint GetUnk1Count(int species) => GetU32(species, OFS_UNK1); + public void SetUnk1Count(int species, uint value) => SetU32(species, value, OFS_UNK1); + public uint GetUnk2Count(int species) => GetU32(species, OFS_UNK2); + public void SetUnk2Count(int species, uint value) => SetU32(species, value, OFS_UNK2); + public bool GetCaughtGigantamax1(int species) => GetFlag28(species, 0); + public void SetCaughtGigantamax1(int species, bool value = true) => SetFlag28(species, 0, value); + public bool GetCaughtGigantamax1(Zukan8Index entry) => GetFlag28(entry, 0); + public void SetCaughtGigantamax1(Zukan8Index entry, bool value = true) => SetFlag28(entry, 0, value); + + private bool GetFlag28(int species, int bit) + { + if (!GetEntry(species, out var entry)) + return false; + + return GetFlag28(entry, bit); + } + + public void SetFlag28(int species, int bit, bool value = true) + { + if (!GetEntry(species, out var entry)) + return; + + SetFlag28(entry, bit, value); + } + + private bool GetFlag28(Zukan8Index entry, int bit) + { + var data = GetDexBlock(entry.DexType); + return GetFlag(data, entry.Offset + OFS_UNK1, bit); + } + + public void SetFlag28(Zukan8Index entry, int bit, bool value = true) + { + var data = GetDexBlock(entry.DexType); + SetFlag(data, entry.Offset + OFS_UNK1, bit, value); + } + + public bool GetDisplayDynamaxInstead(Zukan8Index entry) => GetCaughtFlagID(entry, 28); + public void SetDisplayDynamaxInstead(Zukan8Index entry, bool value = true) => SetCaughtFlagID(entry, 28, value); + public bool GetDisplayShiny(Zukan8Index entry) => GetCaughtFlagID(entry, 31); + public void SetDisplayShiny(Zukan8Index entry, bool value = true) => SetCaughtFlagID(entry, 31, value); + + public void SetCaughtFlags32(Zukan8Index entry, uint value) => SetU32(entry, value, OFS_CAUGHT); + public uint GetBattledCount(Zukan8Index entry) => GetU32(entry, OFS_BATTLED); + public void SetBattledCount(Zukan8Index entry, uint value) => SetU32(entry, value, OFS_BATTLED); + public uint GetUnk1Count(Zukan8Index entry) => GetU32(entry, OFS_UNK1); + public void SetUnk1Count(Zukan8Index entry, uint value) => SetU32(entry, value, OFS_UNK1); + public uint GetUnk2Count(Zukan8Index entry) => GetU32(entry, OFS_UNK2); + public void SetUnk2Count(Zukan8Index entry, uint value) => SetU32(entry, value, OFS_UNK2); + + private uint GetU32(int species, int ofs) + { + if (!GetEntry(species, out var entry)) + return 0; + + return GetU32(entry, ofs); + } + + private uint GetU32(Zukan8Index entry, int ofs) + { + var dex = entry.DexType; + var index = entry.Offset; + var data = GetDexBlock(dex); + return ReadUInt32LittleEndian(data.AsSpan(index + ofs)); + } + + private void SetU32(int species, uint value, int ofs) + { + if (!GetEntry(species, out var entry)) + return; + + SetU32(entry, value, ofs); + } + + private void SetU32(Zukan8Index entry, uint value, int ofs) + { + var dex = entry.DexType; + var index = entry.Offset; + var data = GetDexBlock(dex); + WriteUInt32LittleEndian(data.AsSpan(index + ofs), value); + } + + #endregion + + #region Inherited + public override void SetDex(PKM pk) + { + int species = pk.Species; + if (!GetEntry(species, out _)) + return; + if (pk.IsEgg) // do not add + return; + + bool owned = GetCaught(species); + + var gender = pk.Gender; + bool shiny = pk.IsShiny; + int form = pk.Form; + var language = pk.Language; + + var g = gender & 1; + var s = shiny ? 2 : 0; + if (species == (int)Species.Alcremie) + { + form *= 7; + form += (int)((PK8)pk).FormArgument; // alteration byte + } + else if (species == (int) Species.Eternatus && form == 1) + { + form = 0; + SetSeenRegion(species, 63, g | s); } - public bool GetSeenRegion(Zukan8Index entry, int form, int region) + SetSeenRegion(species, form, g | s); + SetCaught(species); + SetIsLanguageObtained(species, language); + if (!owned) { - if ((uint)region >= SeenRegionCount) - throw new ArgumentOutOfRangeException(nameof(region)); - if ((uint)form > 63) - return false; - - var dex = entry.DexType; - var offset = entry.Offset; - var data = GetDexBlock(dex); - var ofs = SeenRegionSize * region; - return GetFlag(data, offset + ofs, form); + SetFormDisplayed(species, (byte)form); + if (shiny) + SetDisplayShiny(species); + SetGenderDisplayed(species, (uint)g); } - public void SetSeenRegion(int species, int form, int region, bool value = true) + var count = GetBattledCount(species); + if (count == 0) + SetBattledCount(species, 1); + } + + public override void SeenNone() + { + Array.Clear(Galar.Data, 0, Galar.Data.Length); + Array.Clear(Rigel1.Data, 0, Rigel1.Data.Length); + Array.Clear(Rigel2.Data, 0, Rigel2.Data.Length); + } + + public override void CaughtNone() + { + foreach (var kvp in DexLookup) + CaughtNone(kvp.Key); + } + + private void CaughtNone(int species) + { + SetCaughtFlags32(species, 0); + SetUnk1Count(species, 0); + SetUnk2Count(species, 0); + } + + public override void SeenAll(bool shinyToo = false) + { + SetAllSeen(true, shinyToo); + } + + private void SeenAll(int species, int fc, bool shinyToo, bool value = true) + { + var pt = PersonalTable.SWSH; + for (int form = 0; form < fc; form++) { - if (!GetEntry(species, out var entry)) + var pi = pt.GetFormEntry(species, form); + SeenAll(species, form, value, pi, shinyToo); + } + + if (species == (int)Species.Slowbro) // Clear Mega Slowbro always + SeenAll(species, 1, false, pt[species], shinyToo); + + if (!value) + ClearGigantamaxFlags(species); + } + + private void SeenAll(int species, int bitIndex, bool value, PersonalInfo pi, bool shinyToo) + { + if (pi.IsDualGender || !value) + { + SetSeenRegion(species, bitIndex, 0, value); + SetSeenRegion(species, bitIndex, 1, value); + if (!shinyToo && value) return; - - SetSeenRegion(entry, form, region, value); + SetSeenRegion(species, bitIndex, 2, value); + SetSeenRegion(species, bitIndex, 3, value); } - - public void SetSeenRegion(Zukan8Index entry, int form, int region, bool value = true) + else { - if ((uint) region >= SeenRegionCount) - throw new ArgumentOutOfRangeException(nameof(region)); - if ((uint) form > 63) + var index = pi.OnlyFemale ? 1 : 0; + SetSeenRegion(species, bitIndex, 0 + index); + if (!shinyToo) return; - - var data = GetDexBlock(entry.DexType); - int index = entry.Offset; - var ofs = SeenRegionSize * region; - SetFlag(data, index + ofs, form, value); + SetSeenRegion(species, bitIndex, 2 + index); } + } - public override bool GetCaught(int species) => GetCaughtFlagID(species, 0); - public void SetCaught(int species, bool value = true) => SetCaughtFlagID(species, 0, value); - public bool GetCaughtGigantamaxed(int species) => GetCaughtFlagID(species, 1); - public void SetCaughtGigantamax(int species, bool value = true) => SetCaughtFlagID(species, 1, value); - public bool GetIsLanguageIndexObtained(int species, int langIndex) => GetCaughtFlagID(species, 2 + langIndex); - public void SetIsLanguageIndexObtained(int species, int langIndex, bool value = true) => SetCaughtFlagID(species, 2 + langIndex, value); + private void ClearGigantamaxFlags(int species) + { + SetSeenRegion(species, 63, 0, false); + SetSeenRegion(species, 63, 1, false); + SetSeenRegion(species, 63, 2, false); + SetSeenRegion(species, 63, 3, false); + } - public bool GetCaught(Zukan8Index entry) => GetCaughtFlagID(entry, 0); - public void SetCaught(Zukan8Index entry, bool value = true) => SetCaughtFlagID(entry, 0, value); - public bool GetCaughtGigantamaxed(Zukan8Index entry) => GetCaughtFlagID(entry, 1); - public void SetCaughtGigantamax(Zukan8Index entry, bool value = true) => SetCaughtFlagID(entry, 1, value); - public bool GetIsLanguageIndexObtained(Zukan8Index entry, int langIndex) => GetCaughtFlagID(entry, 2 + langIndex); - public void SetIsLanguageIndexObtained(Zukan8Index entry, int langIndex, bool value = true) => SetCaughtFlagID(entry, 2 + langIndex, value); + public override void CompleteDex(bool shinyToo = false) + { + foreach (var kvp in DexLookup) + SetDexEntryAll(kvp.Key, shinyToo); + } - private bool GetCaughtFlagID(int species, int bit) + public override void CaughtAll(bool shinyToo = false) + { + SeenAll(shinyToo); + foreach (var kvp in DexLookup) { - if (!GetEntry(species, out var entry)) - return false; - - return GetCaughtFlagID(entry, bit); + var species = kvp.Key; + SetAllCaught(species, true, shinyToo); } + } - private bool GetCaughtFlagID(Zukan8Index entry, int bit) - { - var data = GetDexBlock(entry.DexType); - return GetFlag(data, entry.Offset + OFS_CAUGHT, bit); - } + private void SetAllCaught(int species, bool value = true, bool shinyToo = false) + { + SetCaught(species); + for (int i = 0; i < 11; i++) + SetIsLanguageObtained(species, i, value); - public void SetCaughtFlagID(int species, int bit, bool value = true) - { - if (!GetEntry(species, out var entry)) - return; - - SetCaughtFlagID(entry, bit, value); - } - - public void SetCaughtFlagID(Zukan8Index entry, int bit, bool value = true) - { - var data = GetDexBlock(entry.DexType); - SetFlag(data, entry.Offset + OFS_CAUGHT, bit, value); - } - - public bool GetIsLanguageObtained(int species, int language) - { - int langIndex = GetDexLangFlag(language); - if (langIndex < 0) - return false; - - return GetIsLanguageIndexObtained(species, langIndex); - } - - public void SetIsLanguageObtained(int species, int language, bool value = true) - { - int langIndex = GetDexLangFlag(language); - if (langIndex < 0) - return; - - SetIsLanguageIndexObtained(species, langIndex, value); - } - - public uint GetFormDisplayed(int species) - { - if (!GetEntry(species, out var entry)) - return 0; - - return GetFormDisplayed(entry); - } - - public uint GetFormDisplayed(Zukan8Index entry) - { - var data = GetDexBlock(entry.DexType); - var index = entry.Offset; - var value = ReadUInt32LittleEndian(data.AsSpan(index + OFS_CAUGHT)); - return (value >> 15) & 0x1FFF; // (0x1FFF is really overkill, GameFreak) - } - - public void SetFormDisplayed(int species, uint value = 0) - { - if (!GetEntry(species, out var entry)) - return; - - SetFormDisplayed(entry, value); - } - - public void SetFormDisplayed(Zukan8Index entry, uint value = 0) - { - var data = GetDexBlock(entry.DexType); - var index = entry.Offset; - var span = data.AsSpan(index + OFS_CAUGHT); - var current = ReadUInt32LittleEndian(span); - uint update = (current & ~(0x1FFFu << 15)) | ((value & 0x1FFF) << 15); - WriteUInt32LittleEndian(span, update); - } - - public uint GetGenderDisplayed(int species) - { - if (!GetEntry(species, out var entry)) - return 0; - - return GetGenderDisplayed(entry); - } - - public uint GetGenderDisplayed(Zukan8Index entry) - { - var data = GetDexBlock(entry.DexType); - var index = entry.Offset; - var value = ReadUInt32LittleEndian(data.AsSpan(index + OFS_CAUGHT)); - return (value >> 29) & 3; - } - - public void SetGenderDisplayed(int species, uint value = 0) - { - if (!GetEntry(species, out var entry)) - return; - - SetGenderDisplayed(entry, value); - } - - public void SetGenderDisplayed(Zukan8Index entry, uint value = 0) - { - var data = GetDexBlock(entry.DexType); - var index = entry.Offset; - var span = data.AsSpan(index + OFS_CAUGHT); - var current = ReadUInt32LittleEndian(span); - uint update = (current & ~(3u << 29)) | ((value & 3) << 29); - WriteUInt32LittleEndian(span, update); - } - - public bool GetDisplayDynamaxInstead(int species) => GetCaughtFlagID(species, 28); - public void SetDisplayDynamaxInstead(int species, bool value = true) => SetCaughtFlagID(species, 28, value); - public bool GetDisplayShiny(int species) => GetCaughtFlagID(species, 31); - public void SetDisplayShiny(int species, bool value = true) => SetCaughtFlagID(species, 31, value); - - public void SetCaughtFlags32(int species, uint value) => SetU32(species, value, OFS_CAUGHT); - public uint GetBattledCount(int species) => GetU32(species, OFS_BATTLED); - public void SetBattledCount(int species, uint value) => SetU32(species, value, OFS_BATTLED); - - public uint GetUnk1Count(int species) => GetU32(species, OFS_UNK1); - public void SetUnk1Count(int species, uint value) => SetU32(species, value, OFS_UNK1); - public uint GetUnk2Count(int species) => GetU32(species, OFS_UNK2); - public void SetUnk2Count(int species, uint value) => SetU32(species, value, OFS_UNK2); - public bool GetCaughtGigantamax1(int species) => GetFlag28(species, 0); - public void SetCaughtGigantamax1(int species, bool value = true) => SetFlag28(species, 0, value); - public bool GetCaughtGigantamax1(Zukan8Index entry) => GetFlag28(entry, 0); - public void SetCaughtGigantamax1(Zukan8Index entry, bool value = true) => SetFlag28(entry, 0, value); - - private bool GetFlag28(int species, int bit) - { - if (!GetEntry(species, out var entry)) - return false; - - return GetFlag28(entry, bit); - } - - public void SetFlag28(int species, int bit, bool value = true) - { - if (!GetEntry(species, out var entry)) - return; - - SetFlag28(entry, bit, value); - } - - private bool GetFlag28(Zukan8Index entry, int bit) - { - var data = GetDexBlock(entry.DexType); - return GetFlag(data, entry.Offset + OFS_UNK1, bit); - } - - public void SetFlag28(Zukan8Index entry, int bit, bool value = true) - { - var data = GetDexBlock(entry.DexType); - SetFlag(data, entry.Offset + OFS_UNK1, bit, value); - } - - public bool GetDisplayDynamaxInstead(Zukan8Index entry) => GetCaughtFlagID(entry, 28); - public void SetDisplayDynamaxInstead(Zukan8Index entry, bool value = true) => SetCaughtFlagID(entry, 28, value); - public bool GetDisplayShiny(Zukan8Index entry) => GetCaughtFlagID(entry, 31); - public void SetDisplayShiny(Zukan8Index entry, bool value = true) => SetCaughtFlagID(entry, 31, value); - - public void SetCaughtFlags32(Zukan8Index entry, uint value) => SetU32(entry, value, OFS_CAUGHT); - public uint GetBattledCount(Zukan8Index entry) => GetU32(entry, OFS_BATTLED); - public void SetBattledCount(Zukan8Index entry, uint value) => SetU32(entry, value, OFS_BATTLED); - public uint GetUnk1Count(Zukan8Index entry) => GetU32(entry, OFS_UNK1); - public void SetUnk1Count(Zukan8Index entry, uint value) => SetU32(entry, value, OFS_UNK1); - public uint GetUnk2Count(Zukan8Index entry) => GetU32(entry, OFS_UNK2); - public void SetUnk2Count(Zukan8Index entry, uint value) => SetU32(entry, value, OFS_UNK2); - - private uint GetU32(int species, int ofs) - { - if (!GetEntry(species, out var entry)) - return 0; - - return GetU32(entry, ofs); - } - - private uint GetU32(Zukan8Index entry, int ofs) - { - var dex = entry.DexType; - var index = entry.Offset; - var data = GetDexBlock(dex); - return ReadUInt32LittleEndian(data.AsSpan(index + ofs)); - } - - private void SetU32(int species, uint value, int ofs) - { - if (!GetEntry(species, out var entry)) - return; - - SetU32(entry, value, ofs); - } - - private void SetU32(Zukan8Index entry, uint value, int ofs) - { - var dex = entry.DexType; - var index = entry.Offset; - var data = GetDexBlock(dex); - WriteUInt32LittleEndian(data.AsSpan(index + ofs), value); - } - - #endregion - - #region Inherited - public override void SetDex(PKM pkm) - { - int species = pkm.Species; - if (!GetEntry(species, out _)) - return; - if (pkm.IsEgg) // do not add - return; - - bool owned = GetCaught(species); - - var gender = pkm.Gender; - bool shiny = pkm.IsShiny; - int form = pkm.Form; - var language = pkm.Language; - - var g = gender & 1; - var s = shiny ? 2 : 0; - if (species == (int)Species.Alcremie) - { - form *= 7; - form += (int)((PK8)pkm).FormArgument; // alteration byte - } - else if (species == (int) Species.Eternatus && form == 1) - { - form = 0; - SetSeenRegion(species, 63, g | s); - } - - SetSeenRegion(species, form, g | s); - SetCaught(species); - SetIsLanguageObtained(species, language); - if (!owned) - { - SetFormDisplayed(species, (byte)form); - if (shiny) - SetDisplayShiny(species); - SetGenderDisplayed(species, (uint)g); - } - - var count = GetBattledCount(species); - if (count == 0) - SetBattledCount(species, 1); - } - - public override void SeenNone() - { - Array.Clear(Galar.Data, 0, Galar.Data.Length); - Array.Clear(Rigel1.Data, 0, Rigel1.Data.Length); - Array.Clear(Rigel2.Data, 0, Rigel2.Data.Length); - } - - public override void CaughtNone() - { - foreach (var kvp in DexLookup) - CaughtNone(kvp.Key); - } - - private void CaughtNone(int species) - { - SetCaughtFlags32(species, 0); - SetUnk1Count(species, 0); - SetUnk2Count(species, 0); - } - - public override void SeenAll(bool shinyToo = false) - { - SetAllSeen(true, shinyToo); - } - - private void SeenAll(int species, int fc, bool shinyToo, bool value = true) - { - var pt = PersonalTable.SWSH; - for (int form = 0; form < fc; form++) - { - var pi = pt.GetFormEntry(species, form); - SeenAll(species, form, value, pi, shinyToo); - } - - if (species == (int)Species.Slowbro) // Clear Mega Slowbro always - SeenAll(species, 1, false, pt[species], shinyToo); - - if (!value) - ClearGigantamaxFlags(species); - } - - private void SeenAll(int species, int bitIndex, bool value, PersonalInfo pi, bool shinyToo) - { - if (pi.IsDualGender || !value) - { - SetSeenRegion(species, bitIndex, 0, value); - SetSeenRegion(species, bitIndex, 1, value); - if (!shinyToo && value) - return; - SetSeenRegion(species, bitIndex, 2, value); - SetSeenRegion(species, bitIndex, 3, value); - } - else - { - var index = pi.OnlyFemale ? 1 : 0; - SetSeenRegion(species, bitIndex, 0 + index); - if (!shinyToo) - return; - SetSeenRegion(species, bitIndex, 2 + index); - } - } - - private void ClearGigantamaxFlags(int species) - { - SetSeenRegion(species, 63, 0, false); - SetSeenRegion(species, 63, 1, false); - SetSeenRegion(species, 63, 2, false); - SetSeenRegion(species, 63, 3, false); - } - - public override void CompleteDex(bool shinyToo = false) - { - foreach (var kvp in DexLookup) - SetDexEntryAll(kvp.Key, shinyToo); - } - - public override void CaughtAll(bool shinyToo = false) - { - SeenAll(shinyToo); - foreach (var kvp in DexLookup) - { - var species = kvp.Key; - SetAllCaught(species, true, shinyToo); - } - } - - private void SetAllCaught(int species, bool value = true, bool shinyToo = false) - { - SetCaught(species); - for (int i = 0; i < 11; i++) - SetIsLanguageObtained(species, i, value); - - if (value) - { - var pi = PersonalTable.SWSH[species]; - if (shinyToo) - SetDisplayShiny(species); - - SetGenderDisplayed(species, (uint)pi.RandomGender()); - } - else - { - SetDisplayShiny(species, false); - SetDisplayDynamaxInstead(species, false); - SetGenderDisplayed(species, 0); - } - } - - public override void SetAllSeen(bool value = true, bool shinyToo = false) - { - foreach (var kvp in DexLookup) - { - var species = kvp.Key; - SetAllSeen(species, value, shinyToo); - } - } - - private void SetAllSeen(int species, bool value = true, bool shinyToo = false) + if (value) { var pi = PersonalTable.SWSH[species]; - var fc = pi.FormCount; - if (species == (int) Species.Eternatus) - fc = 1; // ignore gigantamax - SeenAll(species, fc, shinyToo, value); + if (shinyToo) + SetDisplayShiny(species); - if (species == (int) Species.Alcremie) - { - // Alcremie forms - const int deco = 7; - const int forms = 9; - for (int i = 0; i < deco * forms; i++) // 0-62 - SeenAll(species, i, value, pi, shinyToo); - } - - if (SpeciesWithGigantamaxData.Contains(species)) - { - SeenAll(species, 63, value, pi, shinyToo); - if (species == (int)Species.Urshifu) - { - SeenAll(species, 62, value, pi, shinyToo); - SetCaughtGigantamax1(species); - } - SetCaughtGigantamax(species); - } + SetGenderDisplayed(species, (uint)pi.RandomGender()); } - - public override void SetDexEntryAll(int species, bool shinyToo = false) + else { - SetAllSeen(species, true, shinyToo); - SetAllCaught(species, true); + SetDisplayShiny(species, false); + SetDisplayDynamaxInstead(species, false); + SetGenderDisplayed(species, 0); } - - public override void ClearDexEntryAll(int species) - { - if (!GetEntry(species, out var entry)) - return; - - ClearDexEntryAll(entry); - } - - private void ClearDexEntryAll(Zukan8Index entry) - { - var data = GetDexBlock(entry.DexType); - Array.Clear(data, entry.Offset, EntrySize); - } - - public void SetAllBattledCount(uint count = 500) - { - foreach (var kvp in DexLookup) - { - var species = kvp.Key; - SetBattledCount(species, count); - } - } - - private static readonly HashSet SpeciesWithGigantamaxData = new() - { - (int)Species.Charizard, - (int)Species.Butterfree, - (int)Species.Pikachu, - (int)Species.Meowth, - (int)Species.Machamp, - (int)Species.Gengar, - (int)Species.Kingler, - (int)Species.Lapras, - (int)Species.Eevee, - (int)Species.Snorlax, - (int)Species.Garbodor, - (int)Species.Corviknight, - (int)Species.Orbeetle, - (int)Species.Drednaw, - (int)Species.Coalossal, - (int)Species.Flapple, - (int)Species.Appletun, - (int)Species.Sandaconda, - (int)Species.Toxtricity, - (int)Species.Centiskorch, - (int)Species.Hatterene, - (int)Species.Grimmsnarl, - (int)Species.Alcremie, - (int)Species.Copperajah, - (int)Species.Duraludon, - (int)Species.Eternatus, - - // DLC 1 - (int)Species.Rillaboom, - (int)Species.Cinderace, - (int)Species.Inteleon, - (int)Species.Urshifu, - }; - #endregion } - /// - /// Indicates which block will store the entry, and at what index. - /// - /// Which block stores the Pokdex entry. - /// Index that the Pokdex entry is stored at. - public readonly record struct Zukan8Index(Zukan8Type DexType, int Index) + public override void SetAllSeen(bool value = true, bool shinyToo = false) { - public override string ToString() => $"{Index:000} - {DexType}"; - - private int GetSavedIndex() + foreach (var kvp in DexLookup) { - var index = Index; - if (index < 1) - throw new IndexOutOfRangeException(); + var species = kvp.Key; + SetAllSeen(species, value, shinyToo); + } + } - return index - 1; + private void SetAllSeen(int species, bool value = true, bool shinyToo = false) + { + var pi = PersonalTable.SWSH[species]; + var fc = pi.FormCount; + if (species == (int) Species.Eternatus) + fc = 1; // ignore gigantamax + SeenAll(species, fc, shinyToo, value); + + if (species == (int) Species.Alcremie) + { + // Alcremie forms + const int deco = 7; + const int forms = 9; + for (int i = 0; i < deco * forms; i++) // 0-62 + SeenAll(species, i, value, pi, shinyToo); } - public int Offset => GetSavedIndex() * Zukan8.EntrySize; + if (SpeciesWithGigantamaxData.Contains(species)) + { + SeenAll(species, 63, value, pi, shinyToo); + if (species == (int)Species.Urshifu) + { + SeenAll(species, 62, value, pi, shinyToo); + SetCaughtGigantamax1(species); + } + SetCaughtGigantamax(species); + } + } - private const int GalarCount = 400; // Count within Galar dex - private const int Rigel1Count = 211; // Count within Armor dex - private const int Rigel2Count = 210; // Count within Crown dex + public override void SetDexEntryAll(int species, bool shinyToo = false) + { + SetAllSeen(species, true, shinyToo); + SetAllCaught(species, true); + } + + public override void ClearDexEntryAll(int species) + { + if (!GetEntry(species, out var entry)) + return; + + ClearDexEntryAll(entry); + } + + private void ClearDexEntryAll(Zukan8Index entry) + { + var data = GetDexBlock(entry.DexType); + Array.Clear(data, entry.Offset, EntrySize); + } + + public void SetAllBattledCount(uint count = 500) + { + foreach (var kvp in DexLookup) + { + var species = kvp.Key; + SetBattledCount(species, count); + } + } + + private static readonly HashSet SpeciesWithGigantamaxData = new() + { + (int)Species.Charizard, + (int)Species.Butterfree, + (int)Species.Pikachu, + (int)Species.Meowth, + (int)Species.Machamp, + (int)Species.Gengar, + (int)Species.Kingler, + (int)Species.Lapras, + (int)Species.Eevee, + (int)Species.Snorlax, + (int)Species.Garbodor, + (int)Species.Corviknight, + (int)Species.Orbeetle, + (int)Species.Drednaw, + (int)Species.Coalossal, + (int)Species.Flapple, + (int)Species.Appletun, + (int)Species.Sandaconda, + (int)Species.Toxtricity, + (int)Species.Centiskorch, + (int)Species.Hatterene, + (int)Species.Grimmsnarl, + (int)Species.Alcremie, + (int)Species.Copperajah, + (int)Species.Duraludon, + (int)Species.Eternatus, + + // DLC 1 + (int)Species.Rillaboom, + (int)Species.Cinderace, + (int)Species.Inteleon, + (int)Species.Urshifu, + }; + #endregion +} + +/// +/// Indicates which block will store the entry, and at what index. +/// +/// Which block stores the Pokdex entry. +/// Index that the Pokdex entry is stored at. +public readonly record struct Zukan8Index(Zukan8Type DexType, int Index) +{ + public override string ToString() => $"{Index:000} - {DexType}"; + + private int GetSavedIndex() + { + var index = Index; + if (index < 1) + throw new ArgumentOutOfRangeException(nameof(Index)); + + return index - 1; + } + + public int Offset => GetSavedIndex() * Zukan8.EntrySize; + + private const int GalarCount = 400; // Count within Galar dex + private const int Rigel1Count = 211; // Count within Armor dex + private const int Rigel2Count = 210; // Count within Crown dex #if DEBUG - public const int TotalCount = GalarCount + Rigel1Count + Rigel2Count; - /// - /// Gets the from the absolute (overall) dex index. Don't use this method unless you're analyzing things. - /// - /// Unique Pokdex index (incremental). Should be 0-indexed. - public static Zukan8Index GetFromAbsoluteIndex(int index) - { - if (index < 0) - return new Zukan8Index(); + public const int TotalCount = GalarCount + Rigel1Count + Rigel2Count; + /// + /// Gets the from the absolute (overall) dex index. Don't use this method unless you're analyzing things. + /// + /// Unique Pokdex index (incremental). Should be 0-indexed. + public static Zukan8Index GetFromAbsoluteIndex(int index) + { + if (index < 0) + return new Zukan8Index(); - if (index < GalarCount) - return new Zukan8Index(Zukan8Type.Galar, index + 1); - index -= GalarCount; + if (index < GalarCount) + return new Zukan8Index(Zukan8Type.Galar, index + 1); + index -= GalarCount; - if (index < Rigel1Count) - return new Zukan8Index(Zukan8Type.Armor, index + 1); - index -= Rigel1Count; + if (index < Rigel1Count) + return new Zukan8Index(Zukan8Type.Armor, index + 1); + index -= Rigel1Count; - if (index < Rigel2Count) - return new Zukan8Index(Zukan8Type.Crown, index + 1); + if (index < Rigel2Count) + return new Zukan8Index(Zukan8Type.Crown, index + 1); - throw new ArgumentOutOfRangeException(nameof(index)); - } + throw new ArgumentOutOfRangeException(nameof(index)); + } #endif - public string DexPrefix => DexType.GetZukanTypeInternalPrefix(); + public string DexPrefix => DexType.GetZukanTypeInternalPrefix(); - public int AbsoluteIndex => DexType switch - { - Zukan8Type.Galar => Index, - Zukan8Type.Armor => Index + GalarCount, - Zukan8Type.Crown => Index + GalarCount + Rigel1Count, - _ => throw new ArgumentOutOfRangeException(nameof(DexType)), - }; + public int AbsoluteIndex => GetAbsoluteIndex(DexType); - public string GetEntryName(IReadOnlyList speciesNames, int species) - { - return $"{DexPrefix}.{Index:000} - {speciesNames[species]}"; - } - } - - public readonly record struct Zukan8EntryInfo(int Species, Zukan8Index Entry) + private int GetAbsoluteIndex(Zukan8Type type) => type switch { - public string GetEntryName(IReadOnlyList speciesNames) => Entry.GetEntryName(speciesNames, Species); - } + Zukan8Type.Galar => Index, + Zukan8Type.Armor => Index + GalarCount, + Zukan8Type.Crown => Index + GalarCount + Rigel1Count, + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; - public enum Zukan8Type : sbyte + public string GetEntryName(IReadOnlyList speciesNames, int species) { - None = 0, - Galar, - Armor, - Crown, - } - - public static class Zukan8TypeExtensions - { - public static string GetZukanTypeInternalPrefix(this Zukan8Type type) => type switch - { - Zukan8Type.Galar => "O0", - Zukan8Type.Armor => "R1", - Zukan8Type.Crown => "R2", - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; + return $"{DexPrefix}.{Index:000} - {speciesNames[species]}"; } } + +public readonly record struct Zukan8EntryInfo(int Species, Zukan8Index Entry) +{ + public string GetEntryName(IReadOnlyList speciesNames) => Entry.GetEntryName(speciesNames, Species); +} + +public enum Zukan8Type : sbyte +{ + None = 0, + Galar, + Armor, + Crown, +} + +public static class Zukan8TypeExtensions +{ + public static string GetZukanTypeInternalPrefix(this Zukan8Type type) => type switch + { + Zukan8Type.Galar => "O0", + Zukan8Type.Armor => "R1", + Zukan8Type.Crown => "R2", + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; +} diff --git a/PKHeX.Core/Saves/Substructures/Records.cs b/PKHeX.Core/Saves/Substructures/Records.cs index cbb198427..8d86231f1 100644 --- a/PKHeX.Core/Saves/Substructures/Records.cs +++ b/PKHeX.Core/Saves/Substructures/Records.cs @@ -1,663 +1,662 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// lifetime stat tracking +/// +public static class Records { + private const byte LargeRecordCount = 100; // int32 + private const byte SmallRecordCount = 100; // int16 + private const byte Count = LargeRecordCount + SmallRecordCount; + /// - /// lifetime stat tracking + /// Gets the maximum value for the specified record using the provided maximum list. /// - public static class Records + /// Record ID to retrieve the maximum for + /// Maximum enum values for each record + /// Maximum the record can be + public static int GetMax(int recordID, IReadOnlyList maxes) { - private const byte LargeRecordCount = 100; // int32 - private const byte SmallRecordCount = 100; // int16 - private const byte Count = LargeRecordCount + SmallRecordCount; - - /// - /// Gets the maximum value for the specified record using the provided maximum list. - /// - /// Record ID to retrieve the maximum for - /// Maximum enum values for each record - /// Maximum the record can be - public static int GetMax(int recordID, IReadOnlyList maxes) - { - if ((byte)recordID >= Count) - return 0; - return MaxByType[maxes[recordID]]; - } - - public static int GetOffset(int baseOfs, int recordID) => recordID switch - { - < LargeRecordCount => baseOfs + (recordID * sizeof(int)), - < Count => baseOfs + (LargeRecordCount * sizeof(int)) + ((recordID - LargeRecordCount) * sizeof(ushort)), - _ => -1, - }; - - private static readonly int[] MaxByType = {999_999_999, 9_999_999, 999_999, 99_999, 65535, 9_999, 999, 7}; - - public static byte[] DailyPairs_6 = {29, 30, 110, 111, 112, 113, 114, 115, 116, 117}; - public static byte[] DailyPairs_7 = {22, 23, 110, 111, 112, 113, 114, 115, 116, 117}; - - /// - /// Festa pairs; if updating the lower index record, update the Festa Mission record if currently active? - /// - public static byte[] FestaPairs_7 = - { - 175, 6, - 176, 33, - 177, 8, - 179, 38, - 181, 74, - 182, 73, - 183, 7, - 184, 159, - 185, 9, - }; - - public static readonly IReadOnlyList MaxType_XY = new byte[] - { - 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, - 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 7, 5, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - }; - - public static readonly IReadOnlyList MaxType_AO = new byte[] - { - 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, - 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, - - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 7, 5, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 6, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 7, 7, 7, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - }; - - public static readonly IReadOnlyList MaxType_SM = new byte[] - { - 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2, 2, 2, 0, 0, 0, 2, 2, 0, - 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - }; - - public static readonly IReadOnlyList MaxType_USUM = new byte[] - { - 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2, 2, 2, 0, 0, 0, 2, 2, 0, - 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, - 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 4, 4, 4, 5, 5, 4, 5, 5, - }; - - public static readonly IReadOnlyList MaxType_SWSH = new byte[] - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - }; - - public static readonly Dictionary RecordList_6 = new() - { - {000, "Steps Taken"}, - {001, "Times Saved"}, - {002, "Storyline Completed Time"}, - {003, "Times Bicycled"}, - {004, "Total Battles"}, - {005, "Wild Pokémon Battles"}, - {006, "Trainer Battles"}, - {007, "Pokemon Caught"}, - {008, "Pokemon Caught Fishing"}, - {009, "Eggs Hatched"}, - {010, "Pokémon Evolved"}, - {011, "Pokémon Healed at Pokémon Centers"}, - {012, "Link Trades"}, - {013, "Link Battles"}, - {014, "Link Battle Wins"}, - {015, "Link Battle Losses"}, - {016, "WiFi Trades"}, - {017, "WiFi Battles"}, - {018, "WiFi Battle Wins"}, - {019, "WiFi Battle Losses"}, - {020, "IR Trades"}, - {021, "IR Battles"}, - {022, "IR Battle Wins"}, - {023, "IR Battle Losses"}, - {024, "Mart Stack Purchases"}, - {025, "Money Spent"}, - {026, "Times watched TV"}, - {027, "Pokémon deposited at Nursery"}, - {028, "Pokémon Defeated"}, - {029, "Exp. Points Collected (Highest)"}, - {030, "Exp. Points Collected (Today)"}, - {031, "Deposited in the GTS"}, - {032, "Nicknames Given"}, - {033, "Bonus Premier Balls Received"}, - {034, "Battle Points Earned"}, - {035, "Battle Points Spent"}, - - {037, "Tips at Restaurant: ★☆☆"}, - {038, "Tips at Restaurant: ★★☆"}, - {039, "Tips at Restaurant: ★★★"}, - {040, "Tips at Restaurant: Sushi High Roller"}, - {041, "Tips at Café 1"}, - {042, "Tips at Café 2"}, - {043, "Tips at Café 3"}, - {044, "Tips at Cameraman"}, - {045, "Tips at Drink Vendors"}, - {046, "Tips at Poet"}, - {047, "Tips at Furfrou Trimmer"}, - {048, "Tips at Battle Maison 1"}, - {049, "Tips at Battle Maison 2"}, - {050, "Tips at Battle Maison 3"}, - {051, "Tips at Battle Maison 4"}, - {052, "Tips at Maid"}, - {053, "Tips at Butler"}, - {054, "Tips at Scary House"}, - {055, "Tips at Traveling Minstrel"}, - {056, "Tips at Special BGM 1"}, - {057, "Tips at Special BGM 2"}, - {058, "Tips at Frieser Furfrou"}, - {059, "Nice! Received"}, - {060, "Birthday Wishes"}, - {061, "Total People Met Online"}, - {062, "Total People Passed By"}, - {063, "Current Pokemiles"}, - {064, "Total Pokemiles Received"}, - {065, "Total Pokemiles sent to PGL"}, - {066, "Total Super Training Attempts"}, - {067, "Total Super Training Cleared"}, - {068, "IV Judge Evaluations"}, - {069, "Trash Cans inspected"}, - - {070, "Inverse Battles"}, - {071, "Maison Battles"}, - {072, "Times changed character clothing"}, - {073, "Times changed character hairstyle"}, - {074, "Berries harvested"}, - {075, "Berry Field mutations"}, - {076, "PR Videos"}, - {077, "Friend Safari Encounters"}, - {078, "O-Powers Used"}, - - {079, "Secret Base Updates"}, - {080, "Secret Base Flags Captured"}, - {081, "Contests Participated Count"}, - {082, "GTS Trades"}, - {083, "Wonder Trades"}, - {084, "Steps Sneaked"}, - {085, "Multiplayer Contests"}, - {086, "Pokeblocks used"}, - {087, "Times AreaNav Used"}, - {088, "Times DexNav Used"}, - {089, "Times BuzzNav Used"}, - {090, "Times PlayNav Used"}, - - {100, "Champion Title Defense"}, - {101, "Times rested at home"}, - {102, "Times Splash used"}, - {103, "Times Struggle used"}, - {104, "Moves used with No Effect"}, - {105, "Own Fainted Pokémon"}, - {106, "Times attacked ally in battle"}, - {107, "Failed Run Attempts"}, - {108, "Wild encounters that fled"}, - {109, "Failed Fishing Attempts"}, - {110, "Pokemon Defeated (Highest)"}, - {111, "Pokemon Defeated (Today)"}, - {112, "Pokemon Caught (Highest)"}, - {113, "Pokemon Caught (Today)"}, - {114, "Trainers Battled (Highest)"}, - {115, "Trainers Battled (Today)"}, - {116, "Pokemon Evolved (Highest)"}, - {117, "Pokemon Evolved (Today)"}, - {118, "Fossils Restored"}, - {119, "Sweet Scent Encounters"}, - {120, "Battle Institute Tests"}, - {121, "Battle Institute Rank"}, - {122, "Battle Institute Score"}, - - {123, "Last Tip at Restaurant: ★☆☆"}, - {124, "Last Tip at Restaurant: ★★☆"}, - {125, "Last Tip at Restaurant: ★★★"}, - {126, "Last Tip at Restaurant: Sushi High Roller"}, - {127, "Last Tip at Café 1"}, - {128, "Last Tip at Café 2"}, - {129, "Last Tip at Café 3"}, - {130, "Last Tip at Cameraman"}, - {131, "Last Tip at Drink Vendors"}, - {132, "Last Tip at Poet"}, - {133, "Last Tip at Furfrou Trimmer"}, - {134, "Last Tip at Battle Maison 1"}, - {135, "Last Tip at Battle Maison 2"}, - {136, "Last Tip at Battle Maison 3"}, - {137, "Last Tip at Battle Maison 4"}, - {138, "Last Tip at Maid"}, - {139, "Last Tip at Butler"}, - {140, "Last Tip at Scary House"}, - {141, "Last Tip at Traveling Minstrel"}, - {142, "Last Tip at Special BGM 1"}, - {143, "Last Tip at Special BGM 2"}, - {144, "Last Tip at Frieser Furfrou"}, - - {145, "Photos Taken"}, - {146, "Sky Wild Battles (?)"}, - {147, "Battle Maison Streak: Singles"}, - {148, "Battle Maison Streak: Doubles"}, - {149, "Battle Maison Streak: Triples"}, - {150, "Battle Maison Streak: Rotation"}, - {151, "Battle Maison Streak: Multi"}, - {152, "Loto-ID Wins"}, - {153, "PP Ups used"}, - {154, "PSS Passerby Count (Today)"}, - {155, "Amie Used"}, - - {156, "Roller Skate Count: Spin Left"}, - {157, "Roller Skate Count: Spin Right"}, - {158, "Roller Skate Count: Running Start"}, - {159, "Roller Skate Count: Parallel Swizzle"}, - {160, "Roller Skate Count: Drift-and-dash"}, - {161, "Roller Skate Count: 360 right"}, - {162, "Roller Skate Count: 360 left"}, - {163, "Roller Skate Count: Flips"}, - {164, "Roller Skate Count: Grind"}, - {165, "Roller Skate Count: Combos"}, - - {166, "Fishing Chains"}, - {167, "Secret Base Battles in your base"}, - {168, "Secret Base Battles in another base"}, - {169, "Contest Spectacular Photos taken"}, - {170, "Times used Fly"}, - {171, "Times used Soaring in the Sky"}, - {172, "Times used Dive"}, - {173, "Times used Sky Holes"}, - {174, "Times healed by Mom"}, - {175, "Times used Escape Rope"}, - {176, "Times used Dowsing Machine"}, - {177, "Trainer's Eye Rematches"}, - {178, "FUREAI Interest ???"}, // similar to USUM idb - - {179, "Shiny Pokemon Encountered"}, - {180, "Trick House Clears"}, - {181, "Eon Ticket 1 (SpotPass)"}, - {182, "Eon Ticket 2 (Mystery Gift)"}, - }; - - public static readonly Dictionary RecordList_7 = new() - { - {000, "Steps Taken"}, - {001, "Times Saved"}, - {002, "Storyline Completed Time"}, - {003, "Total Battles"}, - {004, "Wild Pokémon Battles"}, - {005, "Trainer Battles"}, - {006, "Pokemon Caught"}, - {007, "Pokemon Caught Fishing"}, - {008, "Eggs Hatched"}, - {009, "Pokémon Evolved"}, - {010, "Pokémon Healed at Pokémon Centers"}, - {011, "Link Trades"}, - {012, "Link Battles"}, - {013, "Link Battle Wins"}, - {014, "Link Battle Losses"}, - {015, "Battle Spot Battles"}, - {016, "Battle Spot Wins"}, - {017, "Battle Spot Losses"}, - {018, "Mart Stack Purchases"}, - {019, "Money Spent"}, - {020, "Pokémon deposited at Nursery"}, - {021, "Pokémon Defeated"}, - {022, "Exp. Points Collected (Highest)"}, - {023, "Exp. Points Collected (Today)"}, - {024, "Deposited in the GTS"}, - {025, "Nicknames Given"}, - {026, "Bonus Premier Balls Received"}, - {027, "Battle Points Earned"}, - {028, "Battle Points Spent"}, - {029, "Super Effective Moves Used"}, - {030, "Clothing Count"}, - {031, "Salon Uses"}, - {032, "Berry Harvests"}, - {033, "Trades at the GTS"}, - {034, "Wonder Trades"}, - {035, "Quick Links"}, - {036, "Pokemon Rides"}, - {037, "Beans Given"}, - {038, "Festival Coins Spent"}, - {039, "Poke Beans Collected"}, - {040, "Battle Tree Challenges"}, - {041, "Z-Moves Used"}, - {042, "Balls Used"}, - {043, "Items Thieved"}, - {044, "Moves Used"}, - {045, "Levels Raised"}, - {046, "Ran From Battles"}, - {047, "Rock Smash Items"}, - {048, "Medicine Used"}, - {049, "Pay Day Money Received"}, - {050, "Total Thumbs-Ups"}, - {051, "Times Twirled (Pirouette)"}, - {052, "Record Thumbs-ups"}, - {053, "Pokemon Petted"}, - {054, "Poké Pelago Visits"}, - {055, "Poké Pelago Bean Trades"}, - {056, "Poké Pelago Tapped Pokémon"}, - {057, "Poké Pelago Bean Stacks put in Crate"}, - {058, "Poké Pelago Levels Gained"}, - {059, "Poké Pelago Friendship Increased"}, - {060, "Poké Pelago Eggs Hatched"}, - {061, "Poké Pelago ???"}, - {062, "Battle Video QR Teams Scanned"}, - {063, "Battle Videos Watched"}, - {064, "Battle Videos Rebattled"}, - {065, "RotomDex Interactions"}, - {066, "Guests Interacted With"}, - {067, "Berry Piles (not full) Collected"}, - {068, "Berry Piles (full) Collected"}, - {069, "Items Reeled In"}, - // USUM - {070, "Roto Lotos"}, - - {072, "Stickers Collected"}, - {073, "Mantine Surf BP Earned"}, - {074, "Battle Agency Wins"}, - - {100, "Champion Title Defense"}, - {101, "Times rested at home"}, - {102, "Times Splash used"}, - {103, "Times Struggle used"}, - {104, "Moves used with No Effect"}, - {105, "Own Fainted Pokémon"}, - {106, "Times attacked ally in battle"}, - {107, "Failed Run Attempts"}, - {108, "Wild encounters that fled"}, - {109, "Failed Fishing Attempts"}, - {110, "Pokemon Defeated (Highest)"}, - {111, "Pokemon Defeated (Today)"}, - {112, "Pokemon Caught (Highest)"}, - {113, "Pokemon Caught (Today)"}, - {114, "Trainers Battled (Highest)"}, - {115, "Trainers Battled (Today)"}, - {116, "Pokemon Evolved (Highest)"}, - {117, "Pokemon Evolved (Today)"}, - {118, "Fossils Restored"}, - {119, "Photos Rated"}, - {120, "Best (Super) Singles Streak"}, - {121, "Best (Super) Doubles Streak"}, - {122, "Best (Super) Multi Streak"}, - {123, "Loto-ID Wins"}, - {124, "PP Raised"}, - {125, "Amie Used"}, - {126, "Fishing Chains"}, - {127, "Shiny Pokemon Encountered"}, - {128, "Missions Participated In"}, - {129, "Facilities Hosted"}, - {130, "QR Code Scans"}, - {131, "Moves learned with TMs"}, - {132, "Café Drinks Bought"}, - {133, "Trainer Card Photos Taken"}, - {134, "Evolutions Cancelled"}, - {135, "SOS Battle Allies Called"}, - {136, "Friendship Raised"}, - {137, "Battle Royal Dome Battles"}, - {138, "Items Picked Up after Battle"}, - {139, "Ate in Malasadas Shop"}, - {140, "Hyper Trainings Received"}, - {141, "Dishes eaten in Battle Buffet"}, - {142, "Pokémon Refresh Accessed"}, - {143, "Pokémon Storage System Log-outs"}, - {144, "Lomi Lomi Massages"}, - {145, "Times laid down in Ilima's Bed"}, - {146, "Times laid down in Guzma's Bed"}, - {147, "Times laid down in Kiawe's Bed"}, - {148, "Times laid down in Lana's Bed"}, - {149, "Times laid down in Mallow's Bed"}, - {150, "Times laid down in Olivia's Bed"}, - {151, "Times laid down in Hapu's Bed"}, - {152, "Times laid down in Lusamine's Bed"}, - {153, "Ambush/Smash post-battle items received"}, - {154, "Rustling Tree Encounters"}, - {155, "Ledges Jumped Down"}, - {156, "Water Splash Encounters"}, - {157, "Sand Cloud Encounters"}, - {158, "Outfit Changes"}, - {159, "Battle Royal Dome Wins"}, - {160, "Pelago Treasure Hunts"}, - {161, "Pelago Training Sessions"}, - {162, "Pelago Hot Spring Sessions"}, - {163, "Special QR 1"}, - {164, "Special QR 2"}, - {165, "Special QR Code Scans"}, - {166, "Island Scans"}, - {167, "Rustling Bush Encounters"}, - {168, "Fly Shadow Encounters"}, - {169, "Rustling Grass Encounters"}, - {170, "Dirt Cloud Encounters"}, - {171, "Wimpod Chases"}, - {172, "Berry Tree Battles won"}, - {173, "Bubbling Spot Encounters/Items"}, - {174, "Times laid down in Own Bed"}, - // global missions - {175, "Catch a lot of Pokémon!"}, - {176, "Trade Pokémon at the GTS!"}, - {177, "Hatch a lot of Eggs!"}, - {178, "Harvest Poké Beans!"}, - {179, "Get high scores with your Poké Finder!"}, - {180, "Find Pokémon using Island Scan!"}, - {181, "Catch Crabrawler!"}, - {182, "Defend your Champion title!"}, - {183, "Fish Pokémon at rare spots!"}, - {184, "Battle Royal!"}, - {185, "Try your luck!"}, - {186, "Get BP at the Battle Tree!"}, - {187, "Catch a lot of Pokémon!"}, - - // USUM - {188, "Ultra Wormhole Travels"}, - {189, "Mantine Surf Plays"}, - {190, "Photo Club Photos saved"}, - {191, "Battle Agency Battles"}, - // 192-194 unknown - {195, "Photo Club Sticker usage"}, - {196, "Photo Club Photo Shoots"}, - {197, "Highest Wormhole Travel Distance"}, - {198, "Highest Mantine Surf BP Earned"}, - }; - - public static readonly Dictionary RecordList_8 = new() - { - {00, "egg_hatching"}, - {01, "capture_wild"}, - {02, "capture_symbol"}, - {03, "capture_raid"}, - {04, "capture_camp"}, - {05, "capture_fishing"}, - {06, "total_capture"}, - {07, "dress_up"}, - {08, "training"}, - {09, "personal_change"}, - {10, "rotomu_circuit"}, - {11, "npc_trade"}, - {12, "pretty"}, - {13, "chain_encount"}, - {14, "hall_of_fame"}, - {15, "fossil_restore"}, - {16, "wild_pokemon_encount"}, - {17, "trade"}, - {18, "magical_trade"}, - {19, "one_day_captured"}, - {20, "one_day_evolution"}, - {21, "total_walk"}, - {22, "total_watt"}, - {23, "total_all_battle"}, - {24, "campin"}, - {25, "battle_point"}, - {26, "win_battle_point"}, - {27, "license_trade"}, - {28, "use_skill_record"}, - {29, "use_exp_ball"}, - {30, "use_personal_change_item"}, - {31, "clothes"}, - {32, "evolution"}, - {33, "net_battle"}, - {34, "cooking"}, - {35, "poke_job_return"}, - {36, "get_rare_item"}, - {37, "whistle"}, - {38, "bike_dash"}, - {39, "tree_shake"}, - {40, "tree_nut"}, - {41, "battle_lose"}, - {42, "recipe"}, - {43, "raid_battle"}, - {44, "total_money"}, - {45, "create_license_card"}, - {46, "change_hair"}, - /* 47 */ {G8BattleTowerSingleWin, "battle_tower_single_win"}, - /* 48 */ {G8BattleTowerDoubleWin, "battle_tower_double_win"}, - {49, "now_money"}, - - // The Records Block only stores 50 entries. - // Record IDs for future expansion content is instead handled separately. - - // DLC - {50, "cormorant_robo"}, // saved in B9C0ECFC - {51, "battle_rom_mark"}, // saved in BB1DE8EF - }; - - public const int G8BattleTowerSingleWin = 47; - public const int G8BattleTowerDoubleWin = 48; - - public static readonly IReadOnlyList MaxValue_BDSP = new[] - { - int.MaxValue, // CLEAR_TIME - 9_999, // DENDOU_CNT - 999_999, // CAPTURE_POKE - 999_999, // FISHING_SUCCESS - 999_999, // TAMAGO_HATCHING - 999_999, // BEAT_DOWN_POKE - 9_999, // RENSHOU_SINGLE - 9_999, // RENSHOU_SINGLE_NOW - 9_999, // RENSHOU_DOUBLE - 9_999, // RENSHOU_DOUBLE_NOW - 9_999, // RENSHOU_MASTER_SINGLE - 9_999, // RENSHOU_MASTER_SINGLE_NOW - 9_999, // RENSHOU_MASTER_DOUBLE - 9_999, // RENSHOU_MASTER_DOUBLE_NOW - 7, // BTL_TOWER_AVERAGE - 5, // CONTEST_STYLE_RANK - 5, // CONTEST_BEATIFUL_RANK - 5, // CONTEST_CUTE_RANK - 5, // CONTEST_CLEVER_RANK - 5, // CONTEST_STRONG_RANK - 9_999, // CONTEST_PLAY_SINGLE - 9_999, // CONTEST_PLAY_LOCAL - 9_999, // CONTEST_PLAY_NETWORK - 9_999, // CONTEST_WIN_SINGLE - 9_999, // CONTEST_WIN_LOCAL - 9_999, // CONTEST_WIN_NETWORK - 100, // CONTEST_RATE_SINGLE - 100, // CONTEST_RATE_LOCAL - 100, // CONTEST_RATE_NETWORK - 65_536,// CONTEST_GET_RIBBON - }; - - public static readonly Dictionary RecordList_8b = new() - { - { 00, "CLEAR_TIME" }, - { 01, "DENDOU_CNT" }, - { 02, "CAPTURE_POKE" }, - { 03, "FISHING_SUCCESS" }, - { 04, "TAMAGO_HATCHING" }, - { 05, "BEAT_DOWN_POKE" }, - { 06, "RENSHOU_SINGLE" }, - { 07, "RENSHOU_SINGLE_NOW" }, - { 08, "RENSHOU_DOUBLE" }, - { 09, "RENSHOU_DOUBLE_NOW" }, - { 10, "RENSHOU_MASTER_SINGLE" }, - { 11, "RENSHOU_MASTER_SINGLE_NOW" }, - { 12, "RENSHOU_MASTER_DOUBLE" }, - { 13, "RENSHOU_MASTER_DOUBLE_NOW" }, - { 14, "BTL_TOWER_AVERAGE" }, - { 15, "CONTEST_STYLE_RANK" }, - { 16, "CONTEST_BEATIFUL_RANK" }, - { 17, "CONTEST_CUTE_RANK" }, - { 18, "CONTEST_CLEVER_RANK" }, - { 19, "CONTEST_STRONG_RANK" }, - { 20, "CONTEST_PLAY_SINGLE" }, - { 21, "CONTEST_PLAY_LOCAL" }, - { 22, "CONTEST_PLAY_NETWORK" }, - { 23, "CONTEST_WIN_SINGLE" }, - { 24, "CONTEST_WIN_LOCAL" }, - { 25, "CONTEST_WIN_NETWORK" }, - { 26, "CONTEST_RATE_SINGLE" }, - { 27, "CONTEST_RATE_LOCAL" }, - { 28, "CONTEST_RATE_NETWORK" }, - { 29, "CONTEST_GET_RIBBON" }, - }; + if ((byte)recordID >= Count) + return 0; + return MaxByType[maxes[recordID]]; } + + public static int GetOffset(int baseOfs, int recordID) => recordID switch + { + < LargeRecordCount => baseOfs + (recordID * sizeof(int)), + < Count => baseOfs + (LargeRecordCount * sizeof(int)) + ((recordID - LargeRecordCount) * sizeof(ushort)), + _ => -1, + }; + + private static readonly int[] MaxByType = {999_999_999, 9_999_999, 999_999, 99_999, 65535, 9_999, 999, 7}; + + public static byte[] DailyPairs_6 = {29, 30, 110, 111, 112, 113, 114, 115, 116, 117}; + public static byte[] DailyPairs_7 = {22, 23, 110, 111, 112, 113, 114, 115, 116, 117}; + + /// + /// Festa pairs; if updating the lower index record, update the Festa Mission record if currently active? + /// + public static byte[] FestaPairs_7 = + { + 175, 6, + 176, 33, + 177, 8, + 179, 38, + 181, 74, + 182, 73, + 183, 7, + 184, 159, + 185, 9, + }; + + public static readonly IReadOnlyList MaxType_XY = new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, + 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 7, 5, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + }; + + public static readonly IReadOnlyList MaxType_AO = new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, + 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 7, 5, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 6, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 7, 7, 7, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + }; + + public static readonly IReadOnlyList MaxType_SM = new byte[] + { + 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 2, 2, 0, 0, 0, 2, 2, 0, + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + }; + + public static readonly IReadOnlyList MaxType_USUM = new byte[] + { + 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 2, 2, 0, 0, 0, 2, 2, 0, + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, + 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 4, 4, 4, 5, 5, 4, 5, 5, + }; + + public static readonly IReadOnlyList MaxType_SWSH = new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + public static readonly Dictionary RecordList_6 = new() + { + {000, "Steps Taken"}, + {001, "Times Saved"}, + {002, "Storyline Completed Time"}, + {003, "Times Bicycled"}, + {004, "Total Battles"}, + {005, "Wild Pokémon Battles"}, + {006, "Trainer Battles"}, + {007, "Pokemon Caught"}, + {008, "Pokemon Caught Fishing"}, + {009, "Eggs Hatched"}, + {010, "Pokémon Evolved"}, + {011, "Pokémon Healed at Pokémon Centers"}, + {012, "Link Trades"}, + {013, "Link Battles"}, + {014, "Link Battle Wins"}, + {015, "Link Battle Losses"}, + {016, "WiFi Trades"}, + {017, "WiFi Battles"}, + {018, "WiFi Battle Wins"}, + {019, "WiFi Battle Losses"}, + {020, "IR Trades"}, + {021, "IR Battles"}, + {022, "IR Battle Wins"}, + {023, "IR Battle Losses"}, + {024, "Mart Stack Purchases"}, + {025, "Money Spent"}, + {026, "Times watched TV"}, + {027, "Pokémon deposited at Nursery"}, + {028, "Pokémon Defeated"}, + {029, "Exp. Points Collected (Highest)"}, + {030, "Exp. Points Collected (Today)"}, + {031, "Deposited in the GTS"}, + {032, "Nicknames Given"}, + {033, "Bonus Premier Balls Received"}, + {034, "Battle Points Earned"}, + {035, "Battle Points Spent"}, + + {037, "Tips at Restaurant: ★☆☆"}, + {038, "Tips at Restaurant: ★★☆"}, + {039, "Tips at Restaurant: ★★★"}, + {040, "Tips at Restaurant: Sushi High Roller"}, + {041, "Tips at Café 1"}, + {042, "Tips at Café 2"}, + {043, "Tips at Café 3"}, + {044, "Tips at Cameraman"}, + {045, "Tips at Drink Vendors"}, + {046, "Tips at Poet"}, + {047, "Tips at Furfrou Trimmer"}, + {048, "Tips at Battle Maison 1"}, + {049, "Tips at Battle Maison 2"}, + {050, "Tips at Battle Maison 3"}, + {051, "Tips at Battle Maison 4"}, + {052, "Tips at Maid"}, + {053, "Tips at Butler"}, + {054, "Tips at Scary House"}, + {055, "Tips at Traveling Minstrel"}, + {056, "Tips at Special BGM 1"}, + {057, "Tips at Special BGM 2"}, + {058, "Tips at Frieser Furfrou"}, + {059, "Nice! Received"}, + {060, "Birthday Wishes"}, + {061, "Total People Met Online"}, + {062, "Total People Passed By"}, + {063, "Current Pokemiles"}, + {064, "Total Pokemiles Received"}, + {065, "Total Pokemiles sent to PGL"}, + {066, "Total Super Training Attempts"}, + {067, "Total Super Training Cleared"}, + {068, "IV Judge Evaluations"}, + {069, "Trash Cans inspected"}, + + {070, "Inverse Battles"}, + {071, "Maison Battles"}, + {072, "Times changed character clothing"}, + {073, "Times changed character hairstyle"}, + {074, "Berries harvested"}, + {075, "Berry Field mutations"}, + {076, "PR Videos"}, + {077, "Friend Safari Encounters"}, + {078, "O-Powers Used"}, + + {079, "Secret Base Updates"}, + {080, "Secret Base Flags Captured"}, + {081, "Contests Participated Count"}, + {082, "GTS Trades"}, + {083, "Wonder Trades"}, + {084, "Steps Sneaked"}, + {085, "Multiplayer Contests"}, + {086, "Pokeblocks used"}, + {087, "Times AreaNav Used"}, + {088, "Times DexNav Used"}, + {089, "Times BuzzNav Used"}, + {090, "Times PlayNav Used"}, + + {100, "Champion Title Defense"}, + {101, "Times rested at home"}, + {102, "Times Splash used"}, + {103, "Times Struggle used"}, + {104, "Moves used with No Effect"}, + {105, "Own Fainted Pokémon"}, + {106, "Times attacked ally in battle"}, + {107, "Failed Run Attempts"}, + {108, "Wild encounters that fled"}, + {109, "Failed Fishing Attempts"}, + {110, "Pokemon Defeated (Highest)"}, + {111, "Pokemon Defeated (Today)"}, + {112, "Pokemon Caught (Highest)"}, + {113, "Pokemon Caught (Today)"}, + {114, "Trainers Battled (Highest)"}, + {115, "Trainers Battled (Today)"}, + {116, "Pokemon Evolved (Highest)"}, + {117, "Pokemon Evolved (Today)"}, + {118, "Fossils Restored"}, + {119, "Sweet Scent Encounters"}, + {120, "Battle Institute Tests"}, + {121, "Battle Institute Rank"}, + {122, "Battle Institute Score"}, + + {123, "Last Tip at Restaurant: ★☆☆"}, + {124, "Last Tip at Restaurant: ★★☆"}, + {125, "Last Tip at Restaurant: ★★★"}, + {126, "Last Tip at Restaurant: Sushi High Roller"}, + {127, "Last Tip at Café 1"}, + {128, "Last Tip at Café 2"}, + {129, "Last Tip at Café 3"}, + {130, "Last Tip at Cameraman"}, + {131, "Last Tip at Drink Vendors"}, + {132, "Last Tip at Poet"}, + {133, "Last Tip at Furfrou Trimmer"}, + {134, "Last Tip at Battle Maison 1"}, + {135, "Last Tip at Battle Maison 2"}, + {136, "Last Tip at Battle Maison 3"}, + {137, "Last Tip at Battle Maison 4"}, + {138, "Last Tip at Maid"}, + {139, "Last Tip at Butler"}, + {140, "Last Tip at Scary House"}, + {141, "Last Tip at Traveling Minstrel"}, + {142, "Last Tip at Special BGM 1"}, + {143, "Last Tip at Special BGM 2"}, + {144, "Last Tip at Frieser Furfrou"}, + + {145, "Photos Taken"}, + {146, "Sky Wild Battles (?)"}, + {147, "Battle Maison Streak: Singles"}, + {148, "Battle Maison Streak: Doubles"}, + {149, "Battle Maison Streak: Triples"}, + {150, "Battle Maison Streak: Rotation"}, + {151, "Battle Maison Streak: Multi"}, + {152, "Loto-ID Wins"}, + {153, "PP Ups used"}, + {154, "PSS Passerby Count (Today)"}, + {155, "Amie Used"}, + + {156, "Roller Skate Count: Spin Left"}, + {157, "Roller Skate Count: Spin Right"}, + {158, "Roller Skate Count: Running Start"}, + {159, "Roller Skate Count: Parallel Swizzle"}, + {160, "Roller Skate Count: Drift-and-dash"}, + {161, "Roller Skate Count: 360 right"}, + {162, "Roller Skate Count: 360 left"}, + {163, "Roller Skate Count: Flips"}, + {164, "Roller Skate Count: Grind"}, + {165, "Roller Skate Count: Combos"}, + + {166, "Fishing Chains"}, + {167, "Secret Base Battles in your base"}, + {168, "Secret Base Battles in another base"}, + {169, "Contest Spectacular Photos taken"}, + {170, "Times used Fly"}, + {171, "Times used Soaring in the Sky"}, + {172, "Times used Dive"}, + {173, "Times used Sky Holes"}, + {174, "Times healed by Mom"}, + {175, "Times used Escape Rope"}, + {176, "Times used Dowsing Machine"}, + {177, "Trainer's Eye Rematches"}, + {178, "FUREAI Interest ???"}, // similar to USUM idb + + {179, "Shiny Pokemon Encountered"}, + {180, "Trick House Clears"}, + {181, "Eon Ticket 1 (SpotPass)"}, + {182, "Eon Ticket 2 (Mystery Gift)"}, + }; + + public static readonly Dictionary RecordList_7 = new() + { + {000, "Steps Taken"}, + {001, "Times Saved"}, + {002, "Storyline Completed Time"}, + {003, "Total Battles"}, + {004, "Wild Pokémon Battles"}, + {005, "Trainer Battles"}, + {006, "Pokemon Caught"}, + {007, "Pokemon Caught Fishing"}, + {008, "Eggs Hatched"}, + {009, "Pokémon Evolved"}, + {010, "Pokémon Healed at Pokémon Centers"}, + {011, "Link Trades"}, + {012, "Link Battles"}, + {013, "Link Battle Wins"}, + {014, "Link Battle Losses"}, + {015, "Battle Spot Battles"}, + {016, "Battle Spot Wins"}, + {017, "Battle Spot Losses"}, + {018, "Mart Stack Purchases"}, + {019, "Money Spent"}, + {020, "Pokémon deposited at Nursery"}, + {021, "Pokémon Defeated"}, + {022, "Exp. Points Collected (Highest)"}, + {023, "Exp. Points Collected (Today)"}, + {024, "Deposited in the GTS"}, + {025, "Nicknames Given"}, + {026, "Bonus Premier Balls Received"}, + {027, "Battle Points Earned"}, + {028, "Battle Points Spent"}, + {029, "Super Effective Moves Used"}, + {030, "Clothing Count"}, + {031, "Salon Uses"}, + {032, "Berry Harvests"}, + {033, "Trades at the GTS"}, + {034, "Wonder Trades"}, + {035, "Quick Links"}, + {036, "Pokemon Rides"}, + {037, "Beans Given"}, + {038, "Festival Coins Spent"}, + {039, "Poke Beans Collected"}, + {040, "Battle Tree Challenges"}, + {041, "Z-Moves Used"}, + {042, "Balls Used"}, + {043, "Items Thieved"}, + {044, "Moves Used"}, + {045, "Levels Raised"}, + {046, "Ran From Battles"}, + {047, "Rock Smash Items"}, + {048, "Medicine Used"}, + {049, "Pay Day Money Received"}, + {050, "Total Thumbs-Ups"}, + {051, "Times Twirled (Pirouette)"}, + {052, "Record Thumbs-ups"}, + {053, "Pokemon Petted"}, + {054, "Poké Pelago Visits"}, + {055, "Poké Pelago Bean Trades"}, + {056, "Poké Pelago Tapped Pokémon"}, + {057, "Poké Pelago Bean Stacks put in Crate"}, + {058, "Poké Pelago Levels Gained"}, + {059, "Poké Pelago Friendship Increased"}, + {060, "Poké Pelago Eggs Hatched"}, + {061, "Poké Pelago ???"}, + {062, "Battle Video QR Teams Scanned"}, + {063, "Battle Videos Watched"}, + {064, "Battle Videos Rebattled"}, + {065, "RotomDex Interactions"}, + {066, "Guests Interacted With"}, + {067, "Berry Piles (not full) Collected"}, + {068, "Berry Piles (full) Collected"}, + {069, "Items Reeled In"}, + // USUM + {070, "Roto Lotos"}, + + {072, "Stickers Collected"}, + {073, "Mantine Surf BP Earned"}, + {074, "Battle Agency Wins"}, + + {100, "Champion Title Defense"}, + {101, "Times rested at home"}, + {102, "Times Splash used"}, + {103, "Times Struggle used"}, + {104, "Moves used with No Effect"}, + {105, "Own Fainted Pokémon"}, + {106, "Times attacked ally in battle"}, + {107, "Failed Run Attempts"}, + {108, "Wild encounters that fled"}, + {109, "Failed Fishing Attempts"}, + {110, "Pokemon Defeated (Highest)"}, + {111, "Pokemon Defeated (Today)"}, + {112, "Pokemon Caught (Highest)"}, + {113, "Pokemon Caught (Today)"}, + {114, "Trainers Battled (Highest)"}, + {115, "Trainers Battled (Today)"}, + {116, "Pokemon Evolved (Highest)"}, + {117, "Pokemon Evolved (Today)"}, + {118, "Fossils Restored"}, + {119, "Photos Rated"}, + {120, "Best (Super) Singles Streak"}, + {121, "Best (Super) Doubles Streak"}, + {122, "Best (Super) Multi Streak"}, + {123, "Loto-ID Wins"}, + {124, "PP Raised"}, + {125, "Amie Used"}, + {126, "Fishing Chains"}, + {127, "Shiny Pokemon Encountered"}, + {128, "Missions Participated In"}, + {129, "Facilities Hosted"}, + {130, "QR Code Scans"}, + {131, "Moves learned with TMs"}, + {132, "Café Drinks Bought"}, + {133, "Trainer Card Photos Taken"}, + {134, "Evolutions Cancelled"}, + {135, "SOS Battle Allies Called"}, + {136, "Friendship Raised"}, + {137, "Battle Royal Dome Battles"}, + {138, "Items Picked Up after Battle"}, + {139, "Ate in Malasadas Shop"}, + {140, "Hyper Trainings Received"}, + {141, "Dishes eaten in Battle Buffet"}, + {142, "Pokémon Refresh Accessed"}, + {143, "Pokémon Storage System Log-outs"}, + {144, "Lomi Lomi Massages"}, + {145, "Times laid down in Ilima's Bed"}, + {146, "Times laid down in Guzma's Bed"}, + {147, "Times laid down in Kiawe's Bed"}, + {148, "Times laid down in Lana's Bed"}, + {149, "Times laid down in Mallow's Bed"}, + {150, "Times laid down in Olivia's Bed"}, + {151, "Times laid down in Hapu's Bed"}, + {152, "Times laid down in Lusamine's Bed"}, + {153, "Ambush/Smash post-battle items received"}, + {154, "Rustling Tree Encounters"}, + {155, "Ledges Jumped Down"}, + {156, "Water Splash Encounters"}, + {157, "Sand Cloud Encounters"}, + {158, "Outfit Changes"}, + {159, "Battle Royal Dome Wins"}, + {160, "Pelago Treasure Hunts"}, + {161, "Pelago Training Sessions"}, + {162, "Pelago Hot Spring Sessions"}, + {163, "Special QR 1"}, + {164, "Special QR 2"}, + {165, "Special QR Code Scans"}, + {166, "Island Scans"}, + {167, "Rustling Bush Encounters"}, + {168, "Fly Shadow Encounters"}, + {169, "Rustling Grass Encounters"}, + {170, "Dirt Cloud Encounters"}, + {171, "Wimpod Chases"}, + {172, "Berry Tree Battles won"}, + {173, "Bubbling Spot Encounters/Items"}, + {174, "Times laid down in Own Bed"}, + // global missions + {175, "Catch a lot of Pokémon!"}, + {176, "Trade Pokémon at the GTS!"}, + {177, "Hatch a lot of Eggs!"}, + {178, "Harvest Poké Beans!"}, + {179, "Get high scores with your Poké Finder!"}, + {180, "Find Pokémon using Island Scan!"}, + {181, "Catch Crabrawler!"}, + {182, "Defend your Champion title!"}, + {183, "Fish Pokémon at rare spots!"}, + {184, "Battle Royal!"}, + {185, "Try your luck!"}, + {186, "Get BP at the Battle Tree!"}, + {187, "Catch a lot of Pokémon!"}, + + // USUM + {188, "Ultra Wormhole Travels"}, + {189, "Mantine Surf Plays"}, + {190, "Photo Club Photos saved"}, + {191, "Battle Agency Battles"}, + // 192-194 unknown + {195, "Photo Club Sticker usage"}, + {196, "Photo Club Photo Shoots"}, + {197, "Highest Wormhole Travel Distance"}, + {198, "Highest Mantine Surf BP Earned"}, + }; + + public static readonly Dictionary RecordList_8 = new() + { + {00, "egg_hatching"}, + {01, "capture_wild"}, + {02, "capture_symbol"}, + {03, "capture_raid"}, + {04, "capture_camp"}, + {05, "capture_fishing"}, + {06, "total_capture"}, + {07, "dress_up"}, + {08, "training"}, + {09, "personal_change"}, + {10, "rotomu_circuit"}, + {11, "npc_trade"}, + {12, "pretty"}, + {13, "chain_encount"}, + {14, "hall_of_fame"}, + {15, "fossil_restore"}, + {16, "wild_pokemon_encount"}, + {17, "trade"}, + {18, "magical_trade"}, + {19, "one_day_captured"}, + {20, "one_day_evolution"}, + {21, "total_walk"}, + {22, "total_watt"}, + {23, "total_all_battle"}, + {24, "campin"}, + {25, "battle_point"}, + {26, "win_battle_point"}, + {27, "license_trade"}, + {28, "use_skill_record"}, + {29, "use_exp_ball"}, + {30, "use_personal_change_item"}, + {31, "clothes"}, + {32, "evolution"}, + {33, "net_battle"}, + {34, "cooking"}, + {35, "poke_job_return"}, + {36, "get_rare_item"}, + {37, "whistle"}, + {38, "bike_dash"}, + {39, "tree_shake"}, + {40, "tree_nut"}, + {41, "battle_lose"}, + {42, "recipe"}, + {43, "raid_battle"}, + {44, "total_money"}, + {45, "create_license_card"}, + {46, "change_hair"}, + /* 47 */ {G8BattleTowerSingleWin, "battle_tower_single_win"}, + /* 48 */ {G8BattleTowerDoubleWin, "battle_tower_double_win"}, + {49, "now_money"}, + + // The Records Block only stores 50 entries. + // Record IDs for future expansion content is instead handled separately. + + // DLC + {50, "cormorant_robo"}, // saved in B9C0ECFC + {51, "battle_rom_mark"}, // saved in BB1DE8EF + }; + + public const int G8BattleTowerSingleWin = 47; + public const int G8BattleTowerDoubleWin = 48; + + public static readonly IReadOnlyList MaxValue_BDSP = new[] + { + int.MaxValue, // CLEAR_TIME + 9_999, // DENDOU_CNT + 999_999, // CAPTURE_POKE + 999_999, // FISHING_SUCCESS + 999_999, // TAMAGO_HATCHING + 999_999, // BEAT_DOWN_POKE + 9_999, // RENSHOU_SINGLE + 9_999, // RENSHOU_SINGLE_NOW + 9_999, // RENSHOU_DOUBLE + 9_999, // RENSHOU_DOUBLE_NOW + 9_999, // RENSHOU_MASTER_SINGLE + 9_999, // RENSHOU_MASTER_SINGLE_NOW + 9_999, // RENSHOU_MASTER_DOUBLE + 9_999, // RENSHOU_MASTER_DOUBLE_NOW + 7, // BTL_TOWER_AVERAGE + 5, // CONTEST_STYLE_RANK + 5, // CONTEST_BEATIFUL_RANK + 5, // CONTEST_CUTE_RANK + 5, // CONTEST_CLEVER_RANK + 5, // CONTEST_STRONG_RANK + 9_999, // CONTEST_PLAY_SINGLE + 9_999, // CONTEST_PLAY_LOCAL + 9_999, // CONTEST_PLAY_NETWORK + 9_999, // CONTEST_WIN_SINGLE + 9_999, // CONTEST_WIN_LOCAL + 9_999, // CONTEST_WIN_NETWORK + 100, // CONTEST_RATE_SINGLE + 100, // CONTEST_RATE_LOCAL + 100, // CONTEST_RATE_NETWORK + 65_536,// CONTEST_GET_RIBBON + }; + + public static readonly Dictionary RecordList_8b = new() + { + { 00, "CLEAR_TIME" }, + { 01, "DENDOU_CNT" }, + { 02, "CAPTURE_POKE" }, + { 03, "FISHING_SUCCESS" }, + { 04, "TAMAGO_HATCHING" }, + { 05, "BEAT_DOWN_POKE" }, + { 06, "RENSHOU_SINGLE" }, + { 07, "RENSHOU_SINGLE_NOW" }, + { 08, "RENSHOU_DOUBLE" }, + { 09, "RENSHOU_DOUBLE_NOW" }, + { 10, "RENSHOU_MASTER_SINGLE" }, + { 11, "RENSHOU_MASTER_SINGLE_NOW" }, + { 12, "RENSHOU_MASTER_DOUBLE" }, + { 13, "RENSHOU_MASTER_DOUBLE_NOW" }, + { 14, "BTL_TOWER_AVERAGE" }, + { 15, "CONTEST_STYLE_RANK" }, + { 16, "CONTEST_BEATIFUL_RANK" }, + { 17, "CONTEST_CUTE_RANK" }, + { 18, "CONTEST_CLEVER_RANK" }, + { 19, "CONTEST_STRONG_RANK" }, + { 20, "CONTEST_PLAY_SINGLE" }, + { 21, "CONTEST_PLAY_LOCAL" }, + { 22, "CONTEST_PLAY_NETWORK" }, + { 23, "CONTEST_WIN_SINGLE" }, + { 24, "CONTEST_WIN_LOCAL" }, + { 25, "CONTEST_WIN_NETWORK" }, + { 26, "CONTEST_RATE_SINGLE" }, + { 27, "CONTEST_RATE_LOCAL" }, + { 28, "CONTEST_RATE_NETWORK" }, + { 29, "CONTEST_GET_RIBBON" }, + }; } diff --git a/PKHeX.Core/Saves/Util/BinaryExportSetting.cs b/PKHeX.Core/Saves/Util/BinaryExportSetting.cs new file mode 100644 index 000000000..c85db1ed2 --- /dev/null +++ b/PKHeX.Core/Saves/Util/BinaryExportSetting.cs @@ -0,0 +1,16 @@ +using System; + +namespace PKHeX.Core; + +[Flags] +public enum BinaryExportSetting +{ + None, + IncludeFooter = 1 << 0, + IncludeHeader = 1 << 1, +} + +public static class BinaryExportSettingExtensions +{ + public static bool HasFlagFast(this BinaryExportSetting value, BinaryExportSetting setting) => (value & setting) != 0; +} diff --git a/PKHeX.Core/Saves/Util/BoxUtil.cs b/PKHeX.Core/Saves/Util/BoxUtil.cs index 880fea48f..a6dd017c3 100644 --- a/PKHeX.Core/Saves/Util/BoxUtil.cs +++ b/PKHeX.Core/Saves/Util/BoxUtil.cs @@ -4,237 +4,236 @@ using static PKHeX.Core.MessageStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Contains extension methods for use with a . +/// +public static class BoxUtil { /// - /// Contains extension methods for use with a . + /// Dumps a folder of files to the . /// - public static class BoxUtil + /// that is being dumped from. + /// Folder to store files. + /// Option to save in child folders with the Box Name as the folder name. + /// -1 if aborted, otherwise the amount of files dumped. + public static int DumpBoxes(this SaveFile sav, string path, bool boxFolders = false) { - /// - /// Dumps a folder of files to the . - /// - /// that is being dumped from. - /// Folder to store files. - /// Option to save in child folders with the Box Name as the folder name. - /// -1 if aborted, otherwise the amount of files dumped. - public static int DumpBoxes(this SaveFile sav, string path, bool boxFolders = false) - { - if (!sav.HasBox) - return -1; + if (!sav.HasBox) + return -1; - var boxData = sav.BoxData; - int boxSlotCount = sav.BoxSlotCount; - var ctr = 0; - for (var slot = 0; slot < boxData.Count; slot++) + var boxData = sav.BoxData; + int boxSlotCount = sav.BoxSlotCount; + var ctr = 0; + for (var slot = 0; slot < boxData.Count; slot++) + { + var pk = boxData[slot]; + var box = slot / boxSlotCount; + if (pk.Species == 0 || !pk.Valid) + continue; + + var boxFolder = path; + if (boxFolders) { - var pk = boxData[slot]; - var box = slot / boxSlotCount; - if (pk.Species == 0 || !pk.Valid) - continue; - - var boxFolder = path; - if (boxFolders) - { - var boxName = Util.CleanFileName(sav.GetBoxName(box)); - boxFolder = Path.Combine(path, boxName); - Directory.CreateDirectory(boxFolder); - } - - var fileName = Util.CleanFileName(pk.FileName); - var fn = Path.Combine(boxFolder, fileName); - if (File.Exists(fn)) - continue; - - File.WriteAllBytes(fn, pk.DecryptedPartyData); - ctr++; - } - return ctr; - } - - /// - /// Dumps the to a folder with individual decrypted files. - /// - /// that is being dumped from. - /// Folder to store files. - /// Box contents to be dumped. - /// -1 if aborted, otherwise the amount of files dumped. - public static int DumpBox(this SaveFile sav, string path, int currentBox) - { - if (!sav.HasBox) - return -1; - - var boxData = sav.BoxData; - int boxSlotCount = sav.BoxSlotCount; - var ctr = 0; - for (var slot = 0; slot < boxData.Count; slot++) - { - var pk = boxData[slot]; - var box = slot / boxSlotCount; - if (pk.Species == 0 || !pk.Valid || box != currentBox) - continue; - - var fileName = Path.Combine(path, Util.CleanFileName(pk.FileName)); - if (File.Exists(fileName)) - continue; - - File.WriteAllBytes(fileName, pk.DecryptedPartyData); - ctr++; - } - return ctr; - } - - /// - /// Loads a folder of files to the . - /// - /// to load folder to. - /// Folder to load files from. Files are only loaded from the top directory. - /// Result message from the method. - /// First box to start loading to. All prior boxes are not modified. - /// Instruction to clear boxes after the starting box. - /// Overwrite existing full slots. If true, will only overwrite empty slots. - /// Bypass option to not modify properties when setting to Save File. - /// Enumerate all files even in sub-folders. - /// Count of files imported. - public static int LoadBoxes(this SaveFile sav, string path, out string result, int boxStart = 0, bool boxClear = false, bool overwrite = false, PKMImportSetting noSetb = PKMImportSetting.UseDefault, bool all = false) - { - if (string.IsNullOrWhiteSpace(path) || !Directory.Exists(path)) - { result = MsgSaveBoxExportPathInvalid; return -1; } - - var option = all ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; - var files = Directory.EnumerateFiles(path, "*.*", option); - return sav.LoadBoxes(files, out result, boxStart, boxClear, overwrite, noSetb); - } - - /// - /// Loads a folder of files to the . - /// - /// to load folder to. - /// Files to load files from. - /// Result message from the method. - /// First box to start loading to. All prior boxes are not modified. - /// Instruction to clear boxes after the starting box. - /// Overwrite existing full slots. If true, will only overwrite empty slots. - /// Bypass option to not modify properties when setting to Save File. - /// Count of files imported. - public static int LoadBoxes(this SaveFile sav, IEnumerable files, out string result, int boxStart = 0, bool boxClear = false, bool overwrite = false, PKMImportSetting noSetb = PKMImportSetting.UseDefault) - { - var pks = GetPossiblePKMsFromPaths(sav, files); - return sav.LoadBoxes(pks, out result, boxStart, boxClear, overwrite, noSetb); - } - - /// - /// Loads a folder of files to the . - /// - /// to load folder to. - /// Encounters to create files from. - /// Result message from the method. - /// First box to start loading to. All prior boxes are not modified. - /// Instruction to clear boxes after the starting box. - /// Overwrite existing full slots. If true, will only overwrite empty slots. - /// Bypass option to not modify properties when setting to Save File. - /// Count of files imported. - public static int LoadBoxes(this SaveFile sav, IEnumerable encounters, out string result, int boxStart = 0, bool boxClear = false, bool overwrite = false, PKMImportSetting noSetb = PKMImportSetting.UseDefault) - { - var pks = encounters.Select(z => z.ConvertToPKM(sav)); - return sav.LoadBoxes(pks, out result, boxStart, boxClear, overwrite, noSetb); - } - - /// - /// Loads a folder of files to the . - /// - /// to load folder to. - /// Unconverted objects to load. - /// Result message from the method. - /// First box to start loading to. All prior boxes are not modified. - /// Instruction to clear boxes after the starting box. - /// Overwrite existing full slots. If true, will only overwrite empty slots. - /// Bypass option to not modify properties when setting to Save File. - /// True if any files are imported. - public static int LoadBoxes(this SaveFile sav, IEnumerable pks, out string result, int boxStart = 0, bool boxClear = false, bool overwrite = false, PKMImportSetting noSetb = PKMImportSetting.UseDefault) - { - if (!sav.HasBox) - { result = MsgSaveBoxFailNone; return -1; } - - var compat = sav.GetCompatible(pks); - if (boxClear) - sav.ClearBoxes(boxStart); - - int ctr = sav.ImportPKMs(compat, overwrite, boxStart, noSetb); - if (ctr <= 0) - { - result = MsgSaveBoxImportNoFiles; - return -1; + var boxName = Util.CleanFileName(sav.GetBoxName(box)); + boxFolder = Path.Combine(path, boxName); + Directory.CreateDirectory(boxFolder); } - result = string.Format(MsgSaveBoxImportSuccess, ctr); - return ctr; + var fileName = Util.CleanFileName(pk.FileName); + var fn = Path.Combine(boxFolder, fileName); + if (File.Exists(fn)) + continue; + + File.WriteAllBytes(fn, pk.DecryptedPartyData); + ctr++; + } + return ctr; + } + + /// + /// Dumps the to a folder with individual decrypted files. + /// + /// that is being dumped from. + /// Folder to store files. + /// Box contents to be dumped. + /// -1 if aborted, otherwise the amount of files dumped. + public static int DumpBox(this SaveFile sav, string path, int currentBox) + { + if (!sav.HasBox) + return -1; + + var boxData = sav.BoxData; + int boxSlotCount = sav.BoxSlotCount; + var ctr = 0; + for (var slot = 0; slot < boxData.Count; slot++) + { + var pk = boxData[slot]; + var box = slot / boxSlotCount; + if (pk.Species == 0 || !pk.Valid || box != currentBox) + continue; + + var fileName = Path.Combine(path, Util.CleanFileName(pk.FileName)); + if (File.Exists(fileName)) + continue; + + File.WriteAllBytes(fileName, pk.DecryptedPartyData); + ctr++; + } + return ctr; + } + + /// + /// Loads a folder of files to the . + /// + /// to load folder to. + /// Folder to load files from. Files are only loaded from the top directory. + /// Result message from the method. + /// First box to start loading to. All prior boxes are not modified. + /// Instruction to clear boxes after the starting box. + /// Overwrite existing full slots. If true, will only overwrite empty slots. + /// Bypass option to not modify properties when setting to Save File. + /// Enumerate all files even in sub-folders. + /// Count of files imported. + public static int LoadBoxes(this SaveFile sav, string path, out string result, int boxStart = 0, bool boxClear = false, bool overwrite = false, PKMImportSetting noSetb = PKMImportSetting.UseDefault, bool all = false) + { + if (string.IsNullOrWhiteSpace(path) || !Directory.Exists(path)) + { result = MsgSaveBoxExportPathInvalid; return -1; } + + var option = all ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; + var files = Directory.EnumerateFiles(path, "*.*", option); + return sav.LoadBoxes(files, out result, boxStart, boxClear, overwrite, noSetb); + } + + /// + /// Loads a folder of files to the . + /// + /// to load folder to. + /// Files to load files from. + /// Result message from the method. + /// First box to start loading to. All prior boxes are not modified. + /// Instruction to clear boxes after the starting box. + /// Overwrite existing full slots. If true, will only overwrite empty slots. + /// Bypass option to not modify properties when setting to Save File. + /// Count of files imported. + public static int LoadBoxes(this SaveFile sav, IEnumerable files, out string result, int boxStart = 0, bool boxClear = false, bool overwrite = false, PKMImportSetting noSetb = PKMImportSetting.UseDefault) + { + var pks = GetPossiblePKMsFromPaths(sav, files); + return sav.LoadBoxes(pks, out result, boxStart, boxClear, overwrite, noSetb); + } + + /// + /// Loads a folder of files to the . + /// + /// to load folder to. + /// Encounters to create files from. + /// Result message from the method. + /// First box to start loading to. All prior boxes are not modified. + /// Instruction to clear boxes after the starting box. + /// Overwrite existing full slots. If true, will only overwrite empty slots. + /// Bypass option to not modify properties when setting to Save File. + /// Count of files imported. + public static int LoadBoxes(this SaveFile sav, IEnumerable encounters, out string result, int boxStart = 0, bool boxClear = false, bool overwrite = false, PKMImportSetting noSetb = PKMImportSetting.UseDefault) + { + var pks = encounters.Select(z => z.ConvertToPKM(sav)); + return sav.LoadBoxes(pks, out result, boxStart, boxClear, overwrite, noSetb); + } + + /// + /// Loads a folder of files to the . + /// + /// to load folder to. + /// Unconverted objects to load. + /// Result message from the method. + /// First box to start loading to. All prior boxes are not modified. + /// Instruction to clear boxes after the starting box. + /// Overwrite existing full slots. If true, will only overwrite empty slots. + /// Bypass option to not modify properties when setting to Save File. + /// True if any files are imported. + public static int LoadBoxes(this SaveFile sav, IEnumerable pks, out string result, int boxStart = 0, bool boxClear = false, bool overwrite = false, PKMImportSetting noSetb = PKMImportSetting.UseDefault) + { + if (!sav.HasBox) + { result = MsgSaveBoxFailNone; return -1; } + + var compat = sav.GetCompatible(pks); + if (boxClear) + sav.ClearBoxes(boxStart); + + int ctr = sav.ImportPKMs(compat, overwrite, boxStart, noSetb); + if (ctr <= 0) + { + result = MsgSaveBoxImportNoFiles; + return -1; } - public static IEnumerable GetPKMsFromPaths(IEnumerable files, EntityContext generation) + result = string.Format(MsgSaveBoxImportSuccess, ctr); + return ctr; + } + + public static IEnumerable GetPKMsFromPaths(IEnumerable files, EntityContext generation) + { + foreach (var f in files) { - foreach (var f in files) - { - var fi = new FileInfo(f); - if (!fi.Exists) - continue; - if (!EntityDetection.IsSizePlausible(fi.Length)) - continue; - var data = File.ReadAllBytes(f); - var prefer = EntityFileExtension.GetContextFromExtension(fi.Extension, generation); - var pkm = EntityFormat.GetFromBytes(data, prefer); - if (pkm?.Species is > 0) - yield return pkm; - } - } - - private static IEnumerable GetPossiblePKMsFromPaths(SaveFile sav, IEnumerable files) - { - foreach (var f in files) - { - var obj = FileUtil.GetSupportedFile(f, sav); - switch (obj) - { - case PKM pk: - yield return pk; - break; - case MysteryGift {IsEntity: true} g: - yield return g.ConvertToPKM(sav); - break; - case GP1 g when g.Species != 0: - yield return g.ConvertToPB7(sav); - break; - case IPokeGroup g: - foreach (var p in g.Contents) - yield return p; - break; - } - } - } - - /// - /// Gets box names for all boxes in the save file. - /// - /// that box names are being dumped for. - /// Returns default English box names in the event the save file does not have names (not exportable), or fails to return a box name. - public static string[] GetBoxNames(SaveFile sav) - { - int count = sav.BoxCount; - var result = new string[count]; - if (!sav.State.Exportable) - { - for (int i = 0; i < count; i++) - result[i] = $"Box {i + 1}"; - return result; - } - - for (int i = 0; i < count; i++) - { - try { result[i] = sav.GetBoxName(i); } - catch { result[i] = $"Box {i + 1}"; } - } - - return result; + var fi = new FileInfo(f); + if (!fi.Exists) + continue; + if (!EntityDetection.IsSizePlausible(fi.Length)) + continue; + var data = File.ReadAllBytes(f); + var prefer = EntityFileExtension.GetContextFromExtension(fi.Extension, generation); + var pk = EntityFormat.GetFromBytes(data, prefer); + if (pk?.Species is > 0) + yield return pk; } } + + private static IEnumerable GetPossiblePKMsFromPaths(SaveFile sav, IEnumerable files) + { + foreach (var f in files) + { + var obj = FileUtil.GetSupportedFile(f, sav); + switch (obj) + { + case PKM pk: + yield return pk; + break; + case MysteryGift {IsEntity: true} g: + yield return g.ConvertToPKM(sav); + break; + case GP1 g when g.Species != 0: + yield return g.ConvertToPB7(sav); + break; + case IPokeGroup g: + foreach (var p in g.Contents) + yield return p; + break; + } + } + } + + /// + /// Gets box names for all boxes in the save file. + /// + /// that box names are being dumped for. + /// Returns default English box names in the event the save file does not have names (not exportable), or fails to return a box name. + public static string[] GetBoxNames(SaveFile sav) + { + int count = sav.BoxCount; + var result = new string[count]; + if (!sav.State.Exportable) + { + for (int i = 0; i < count; i++) + result[i] = $"Box {i + 1}"; + return result; + } + + for (int i = 0; i < count; i++) + { + try { result[i] = sav.GetBoxName(i); } + catch { result[i] = $"Box {i + 1}"; } + } + + return result; + } } diff --git a/PKHeX.Core/Saves/Util/Checksums.cs b/PKHeX.Core/Saves/Util/Checksums.cs index a8054a71a..16e0d812b 100644 --- a/PKHeX.Core/Saves/Util/Checksums.cs +++ b/PKHeX.Core/Saves/Util/Checksums.cs @@ -1,179 +1,178 @@ -using System; +using System; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Checksum algorithms used by the games. +/// +public static class Checksums { - /// - /// Checksum algorithms used by the games. - /// - public static class Checksums + /// Calculates the CRC16-CCITT checksum over an input byte array. + /// Input byte array + /// Checksum + public static ushort CRC16_CCITT(ReadOnlySpan data) { - /// Calculates the CRC16-CCITT checksum over an input byte array. - /// Input byte array - /// Checksum - public static ushort CRC16_CCITT(ReadOnlySpan data) + byte top = 0xFF; + byte bot = 0xFF; + foreach (var b in data) { - byte top = 0xFF; - byte bot = 0xFF; - foreach (var b in data) - { - var x = b ^ top; - x ^= (x >> 4); - top = (byte)(bot ^ (x >> 3) ^ (x << 4)); - bot = (byte)(x ^ (x << 5)); - } - return (ushort)(top << 8 | bot); + var x = b ^ top; + x ^= (x >> 4); + top = (byte)(bot ^ (x >> 3) ^ (x << 4)); + bot = (byte)(x ^ (x << 5)); } + return (ushort)((top << 8) | bot); + } - private static readonly ushort[] crc16 = - { - 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, - 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, - 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, - 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, - 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, - 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, - 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, - 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, - 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, - 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, - 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, - 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, - 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, - 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, - 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, - 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, - 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, - 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, - 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, - 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, - 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, - 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, - 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, - 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, - 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, - 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, - 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, - 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, - 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, - 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, - 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, - 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040, - }; + private static readonly ushort[] crc16 = + { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, + 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, + 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, + 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, + 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, + 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, + 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, + 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, + 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, + 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, + 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, + 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, + 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, + 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, + 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, + 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, + 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, + 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, + 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, + 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, + 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, + 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, + 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, + 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, + 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, + 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, + 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, + 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, + 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040, + }; - private static readonly uint[] crc32 = - { - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, - }; + private static readonly uint[] crc32 = + { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, + }; - /// Calculates the 16bit checksum over an input byte array. - /// Input byte array - /// Initial value for checksum - /// Checksum - private static ushort CRC16(ReadOnlySpan data, ushort initial) - { - ushort chk = initial; - foreach (var b in data) - chk = (ushort)(crc16[(b ^ chk) & 0xFF] ^ chk >> 8); - return chk; - } + /// Calculates the 16bit checksum over an input byte array. + /// Input byte array + /// Initial value for checksum + /// Checksum + private static ushort CRC16(ReadOnlySpan data, ushort initial) + { + ushort chk = initial; + foreach (var b in data) + chk = (ushort)(crc16[(b ^ chk) & 0xFF] ^ (chk >> 8)); + return chk; + } - /// Calculates the 16bit checksum over an input byte array. - /// Input byte array - public static ushort CRC16Invert(ReadOnlySpan data) => (ushort)~CRC16(data, unchecked((ushort)~0)); + /// Calculates the 16bit checksum over an input byte array. + /// Input byte array + public static ushort CRC16Invert(ReadOnlySpan data) => (ushort)~CRC16(data, unchecked((ushort)~0)); - /// Calculates the 16bit checksum over an input byte array. - /// Input byte array - public static ushort CRC16NoInvert(ReadOnlySpan data) => CRC16(data, 0); + /// Calculates the 16bit checksum over an input byte array. + /// Input byte array + public static ushort CRC16NoInvert(ReadOnlySpan data) => CRC16(data, 0); - /// Calculates the 32bit checksum over an input byte array. Used in GBA save files. - /// Input byte array - /// Initial value for checksum - /// Checksum - public static ushort CheckSum32(ReadOnlySpan data, uint initial = 0) - { - uint val = initial; - for (int i = 0; i < data.Length; i += 4) - val += ReadUInt32LittleEndian(data[i..]); - return (ushort)(val + (val >> 16)); - } + /// Calculates the 32bit checksum over an input byte array. Used in GBA save files. + /// Input byte array + /// Initial value for checksum + /// Checksum + public static ushort CheckSum32(ReadOnlySpan data, uint initial = 0) + { + uint val = initial; + for (int i = 0; i < data.Length; i += 4) + val += ReadUInt32LittleEndian(data[i..]); + return (ushort)(val + (val >> 16)); + } - /// Calculates the 32bit checksum over an input byte array. - /// Input byte array - /// Initial value for checksum - /// Checksum - private static uint CRC32(ReadOnlySpan data, uint initial) - { - uint chk = initial; - foreach (var b in data) - chk = (crc32[(b ^ chk) & 0xFF] ^ chk >> 8); - return chk; - } + /// Calculates the 32bit checksum over an input byte array. + /// Input byte array + /// Initial value for checksum + /// Checksum + private static uint CRC32(ReadOnlySpan data, uint initial) + { + uint chk = initial; + foreach (var b in data) + chk = crc32[(b ^ chk) & 0xFF] ^ (chk >> 8); + return chk; + } - /// Calculates the 16bit checksum over an input byte array. - /// Input byte array - public static uint CRC32Invert(ReadOnlySpan data) => ~CRC32(data, unchecked((uint)~0)); + /// Calculates the 16bit checksum over an input byte array. + /// Input byte array + public static uint CRC32Invert(ReadOnlySpan data) => ~CRC32(data, unchecked((uint)~0)); - /// Calculates the 16bit checksum over an input byte array. - /// Input byte array - public static uint CRC32NoInvert(ReadOnlySpan data) => CRC32(data, 0); + /// Calculates the 16bit checksum over an input byte array. + /// Input byte array + public static uint CRC32NoInvert(ReadOnlySpan data) => CRC32(data, 0); - /// Calculates the 16bit checksum over an input byte array. Used in N64 Stadium save files. - /// Input byte array - /// Initial value for checksum - /// Checksum - public static ushort CheckSum16(ReadOnlySpan data, ushort initial = 0) - { - ushort acc = initial; - foreach (byte b in data) - acc += b; - return acc; - } + /// Calculates the 16bit checksum over an input byte array. Used in N64 Stadium save files. + /// Input byte array + /// Initial value for checksum + /// Checksum + public static ushort CheckSum16(ReadOnlySpan data, ushort initial = 0) + { + ushort acc = initial; + foreach (byte b in data) + acc += b; + return acc; + } - /// Calculates the 32bit checksum over an input byte array. Used in GC R/S BOX. - /// Input byte array - /// Checksum - public static uint CheckSum16BigInvert(ReadOnlySpan data) - { - if ((data.Length & 1) != 0) - data = data[..^2]; + /// Calculates the 32bit checksum over an input byte array. Used in GC R/S BOX. + /// Input byte array + /// Checksum + public static uint CheckSum16BigInvert(ReadOnlySpan data) + { + if ((data.Length & 1) != 0) + data = data[..^2]; - ushort chk = 0; // initial value - for (int i = 0; i < data.Length; i += 2) - chk += ReadUInt16BigEndian(data[i..]); + ushort chk = 0; // initial value + for (int i = 0; i < data.Length; i += 2) + chk += ReadUInt16BigEndian(data[i..]); - return (uint)(chk << 16 | (ushort)(0xF004u - chk)); - } + return (uint)((chk << 16) | (ushort)(0xF004u - chk)); } } diff --git a/PKHeX.Core/Saves/Util/DexFormUtil.cs b/PKHeX.Core/Saves/Util/DexFormUtil.cs index c64d73811..6930cf378 100644 --- a/PKHeX.Core/Saves/Util/DexFormUtil.cs +++ b/PKHeX.Core/Saves/Util/DexFormUtil.cs @@ -1,244 +1,243 @@ using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for interacting with Pokédex Form flags +/// +public static class DexFormUtil { - /// - /// Logic for interacting with Pokédex Form flags - /// - public static class DexFormUtil + public static int GetDexFormIndexSM(int species, int formCount, int start) => GetDexFormBitIndex(species, formCount, start, formtable_SM); + public static int GetDexFormIndexUSUM(int species, int formCount, int start) => GetDexFormBitIndex(species, formCount, start, formtable_USUM); + public static int GetDexFormIndexGG(int species, int formCount, int start) => GetDexFormBitIndex(species, formCount, start, formtable_GG); + public static int GetDexFormCountSM(int species) => GetDexFormCount(species, formtable_SM); + public static int GetDexFormCountUSUM(int species) => GetDexFormCount(species, formtable_USUM); + public static int GetDexFormCountGG(int species) => GetDexFormCount(species, formtable_GG); + + private static readonly ushort[] formtable_SM = // u16 species, u16 formcount { - public static int GetDexFormIndexSM(int species, int formCount, int start) => GetDexFormBitIndex(species, formCount, start, formtable_SM); - public static int GetDexFormIndexUSUM(int species, int formCount, int start) => GetDexFormBitIndex(species, formCount, start, formtable_USUM); - public static int GetDexFormIndexGG(int species, int formCount, int start) => GetDexFormBitIndex(species, formCount, start, formtable_GG); - public static int GetDexFormCountSM(int species) => GetDexFormCount(species, formtable_SM); - public static int GetDexFormCountUSUM(int species) => GetDexFormCount(species, formtable_USUM); - public static int GetDexFormCountGG(int species) => GetDexFormCount(species, formtable_GG); + 0x0003, 0x0002, 0x0006, 0x0003, 0x0009, 0x0002, 0x000F, 0x0002, + 0x0012, 0x0002, 0x0013, 0x0002, 0x0014, 0x0003, 0x0019, 0x0007, + 0x001A, 0x0002, 0x001B, 0x0002, 0x001C, 0x0002, 0x0025, 0x0002, + 0x0026, 0x0002, 0x0032, 0x0002, 0x0033, 0x0002, 0x0034, 0x0002, + 0x0035, 0x0002, 0x0041, 0x0002, 0x004A, 0x0002, 0x004B, 0x0002, + 0x004C, 0x0002, 0x0050, 0x0002, 0x0058, 0x0002, 0x0059, 0x0002, + 0x005E, 0x0002, 0x0067, 0x0002, 0x0069, 0x0002, 0x0073, 0x0002, + 0x007F, 0x0002, 0x0082, 0x0002, 0x008E, 0x0002, 0x0096, 0x0003, + 0x00B5, 0x0002, 0x00C9, 0x001C, 0x00D0, 0x0002, 0x00D4, 0x0002, + 0x00D6, 0x0002, 0x00E5, 0x0002, 0x00F8, 0x0002, 0x00FE, 0x0002, + 0x0101, 0x0002, 0x0104, 0x0002, 0x011A, 0x0002, 0x012E, 0x0002, + 0x012F, 0x0002, 0x0132, 0x0002, 0x0134, 0x0002, 0x0136, 0x0002, + 0x013F, 0x0002, 0x0143, 0x0002, 0x014E, 0x0002, 0x015F, 0x0004, + 0x0162, 0x0002, 0x0167, 0x0002, 0x016A, 0x0002, 0x0175, 0x0002, + 0x0178, 0x0002, 0x017C, 0x0002, 0x017D, 0x0002, 0x017E, 0x0002, + 0x017F, 0x0002, 0x0180, 0x0002, 0x0182, 0x0004, 0x019C, 0x0003, + 0x019D, 0x0003, 0x01A5, 0x0002, 0x01A6, 0x0002, 0x01A7, 0x0002, + 0x01AC, 0x0002, 0x01BD, 0x0002, 0x01C0, 0x0002, 0x01CC, 0x0002, + 0x01DB, 0x0002, 0x01DF, 0x0006, 0x01E7, 0x0002, 0x01EC, 0x0002, + 0x01ED, 0x0012, 0x0213, 0x0002, 0x0226, 0x0002, 0x022B, 0x0002, + 0x0249, 0x0004, 0x024A, 0x0004, 0x0281, 0x0002, 0x0282, 0x0002, + 0x0285, 0x0002, 0x0286, 0x0003, 0x0287, 0x0002, 0x0288, 0x0002, + 0x0289, 0x0005, 0x0292, 0x0003, 0x029A, 0x0014, 0x029D, 0x0005, + 0x029E, 0x0006, 0x029F, 0x0005, 0x02A4, 0x000A, 0x02A6, 0x0002, + 0x02A9, 0x0002, 0x02C6, 0x0004, 0x02C7, 0x0004, 0x02CC, 0x0002, + 0x02CE, 0x0005, 0x02CF, 0x0002, 0x02D0, 0x0002, 0x02DF, 0x0002, + 0x02E2, 0x0002, 0x02E5, 0x0004, 0x02E9, 0x0002, 0x02EA, 0x0002, + 0x02F2, 0x0002, 0x02F6, 0x0002, 0x0305, 0x0012, 0x0306, 0x000E, + 0x030A, 0x0004, 0x0310, 0x0002, 0x0321, 0x0002, + }; - private static readonly ushort[] formtable_SM = // u16 species, u16 formcount + private static readonly ushort[] formtable_USUM = // u16 species, u16 formcount + { + 0x0003, 0x0002, 0x0006, 0x0003, 0x0009, 0x0002, 0x000F, 0x0002, + 0x0012, 0x0002, 0x0013, 0x0002, 0x0014, 0x0003, 0x0019, 0x0008, + 0x001A, 0x0002, 0x001B, 0x0002, 0x001C, 0x0002, 0x0025, 0x0002, + 0x0026, 0x0002, 0x0032, 0x0002, 0x0033, 0x0002, 0x0034, 0x0002, + 0x0035, 0x0002, 0x0041, 0x0002, 0x004A, 0x0002, 0x004B, 0x0002, + 0x004C, 0x0002, 0x0050, 0x0002, 0x0058, 0x0002, 0x0059, 0x0002, + 0x005E, 0x0002, 0x0067, 0x0002, 0x0069, 0x0003, 0x0073, 0x0002, + 0x007F, 0x0002, 0x0082, 0x0002, 0x008E, 0x0002, 0x0096, 0x0003, + 0x00B5, 0x0002, 0x00C9, 0x001C, 0x00D0, 0x0002, 0x00D4, 0x0002, + 0x00D6, 0x0002, 0x00E5, 0x0002, 0x00F8, 0x0002, 0x00FE, 0x0002, + 0x0101, 0x0002, 0x0104, 0x0002, 0x011A, 0x0002, 0x012E, 0x0002, + 0x012F, 0x0002, 0x0132, 0x0002, 0x0134, 0x0002, 0x0136, 0x0002, + 0x013F, 0x0002, 0x0143, 0x0002, 0x014E, 0x0002, 0x015F, 0x0004, + 0x0162, 0x0002, 0x0167, 0x0002, 0x016A, 0x0002, 0x0175, 0x0002, + 0x0178, 0x0002, 0x017C, 0x0002, 0x017D, 0x0002, 0x017E, 0x0002, + 0x017F, 0x0002, 0x0180, 0x0002, 0x0182, 0x0004, 0x019C, 0x0003, + 0x019D, 0x0003, 0x019E, 0x0003, 0x01A5, 0x0002, 0x01A6, 0x0002, + 0x01A7, 0x0002, 0x01AC, 0x0002, 0x01BD, 0x0002, 0x01C0, 0x0002, + 0x01CC, 0x0002, 0x01DB, 0x0002, 0x01DF, 0x0006, 0x01E7, 0x0002, + 0x01EC, 0x0002, 0x01ED, 0x0012, 0x0213, 0x0002, 0x0226, 0x0002, + 0x022B, 0x0002, 0x0249, 0x0004, 0x024A, 0x0004, 0x0281, 0x0002, + 0x0282, 0x0002, 0x0285, 0x0002, 0x0286, 0x0003, 0x0287, 0x0002, + 0x0288, 0x0002, 0x0289, 0x0005, 0x0292, 0x0003, 0x0298, 0x0014, + 0x0299, 0x0014, 0x029A, 0x0014, 0x029D, 0x0005, 0x029E, 0x0006, + 0x029F, 0x0005, 0x02A4, 0x000A, 0x02A6, 0x0002, 0x02A9, 0x0002, + 0x02C6, 0x0004, 0x02C7, 0x0004, 0x02CC, 0x0002, 0x02CE, 0x0005, + 0x02CF, 0x0002, 0x02D0, 0x0002, 0x02DF, 0x0002, 0x02E2, 0x0002, + 0x02E5, 0x0004, 0x02E7, 0x0002, 0x02E8, 0x0002, 0x02E9, 0x0003, + 0x02EA, 0x0002, 0x02F0, 0x0002, 0x02F2, 0x0002, 0x02F6, 0x0002, + 0x0305, 0x0012, 0x0306, 0x000E, 0x0309, 0x0002, 0x030A, 0x0004, + 0x0310, 0x0002, 0x0320, 0x0004, 0x0321, 0x0002, + }; + + private static readonly ushort[] formtable_GG = // u16 species, u16 formcount + { + 0x0003, 0x0002, 0x0006, 0x0003, 0x0009, 0x0002, 0x000F, 0x0002, + 0x0012, 0x0002, 0x0013, 0x0002, 0x0014, 0x0003, 0x0019, 0x0009, + 0x001A, 0x0002, 0x001B, 0x0002, 0x001C, 0x0002, 0x0025, 0x0002, + 0x0026, 0x0002, 0x0032, 0x0002, 0x0033, 0x0002, 0x0034, 0x0002, + 0x0035, 0x0002, 0x0041, 0x0002, 0x004A, 0x0002, 0x004B, 0x0002, + 0x004C, 0x0002, 0x0050, 0x0002, 0x0058, 0x0002, 0x0059, 0x0002, + 0x005E, 0x0002, 0x0067, 0x0002, 0x0069, 0x0003, 0x0073, 0x0002, + 0x007F, 0x0002, 0x0082, 0x0002, 0x008E, 0x0002, 0x0096, 0x0003, + }; + + private static int GetDexFormBitIndex(int species, int formCount, int start, IReadOnlyList formTable) + { + int formIndex = start; + for (int i = 0; i < formTable.Count; i += 2) { - 0x0003, 0x0002, 0x0006, 0x0003, 0x0009, 0x0002, 0x000F, 0x0002, - 0x0012, 0x0002, 0x0013, 0x0002, 0x0014, 0x0003, 0x0019, 0x0007, - 0x001A, 0x0002, 0x001B, 0x0002, 0x001C, 0x0002, 0x0025, 0x0002, - 0x0026, 0x0002, 0x0032, 0x0002, 0x0033, 0x0002, 0x0034, 0x0002, - 0x0035, 0x0002, 0x0041, 0x0002, 0x004A, 0x0002, 0x004B, 0x0002, - 0x004C, 0x0002, 0x0050, 0x0002, 0x0058, 0x0002, 0x0059, 0x0002, - 0x005E, 0x0002, 0x0067, 0x0002, 0x0069, 0x0002, 0x0073, 0x0002, - 0x007F, 0x0002, 0x0082, 0x0002, 0x008E, 0x0002, 0x0096, 0x0003, - 0x00B5, 0x0002, 0x00C9, 0x001C, 0x00D0, 0x0002, 0x00D4, 0x0002, - 0x00D6, 0x0002, 0x00E5, 0x0002, 0x00F8, 0x0002, 0x00FE, 0x0002, - 0x0101, 0x0002, 0x0104, 0x0002, 0x011A, 0x0002, 0x012E, 0x0002, - 0x012F, 0x0002, 0x0132, 0x0002, 0x0134, 0x0002, 0x0136, 0x0002, - 0x013F, 0x0002, 0x0143, 0x0002, 0x014E, 0x0002, 0x015F, 0x0004, - 0x0162, 0x0002, 0x0167, 0x0002, 0x016A, 0x0002, 0x0175, 0x0002, - 0x0178, 0x0002, 0x017C, 0x0002, 0x017D, 0x0002, 0x017E, 0x0002, - 0x017F, 0x0002, 0x0180, 0x0002, 0x0182, 0x0004, 0x019C, 0x0003, - 0x019D, 0x0003, 0x01A5, 0x0002, 0x01A6, 0x0002, 0x01A7, 0x0002, - 0x01AC, 0x0002, 0x01BD, 0x0002, 0x01C0, 0x0002, 0x01CC, 0x0002, - 0x01DB, 0x0002, 0x01DF, 0x0006, 0x01E7, 0x0002, 0x01EC, 0x0002, - 0x01ED, 0x0012, 0x0213, 0x0002, 0x0226, 0x0002, 0x022B, 0x0002, - 0x0249, 0x0004, 0x024A, 0x0004, 0x0281, 0x0002, 0x0282, 0x0002, - 0x0285, 0x0002, 0x0286, 0x0003, 0x0287, 0x0002, 0x0288, 0x0002, - 0x0289, 0x0005, 0x0292, 0x0003, 0x029A, 0x0014, 0x029D, 0x0005, - 0x029E, 0x0006, 0x029F, 0x0005, 0x02A4, 0x000A, 0x02A6, 0x0002, - 0x02A9, 0x0002, 0x02C6, 0x0004, 0x02C7, 0x0004, 0x02CC, 0x0002, - 0x02CE, 0x0005, 0x02CF, 0x0002, 0x02D0, 0x0002, 0x02DF, 0x0002, - 0x02E2, 0x0002, 0x02E5, 0x0004, 0x02E9, 0x0002, 0x02EA, 0x0002, - 0x02F2, 0x0002, 0x02F6, 0x0002, 0x0305, 0x0012, 0x0306, 0x000E, - 0x030A, 0x0004, 0x0310, 0x0002, 0x0321, 0x0002, + int s = formTable[i]; + int f = formTable[i + 1]; + if (s == species) + return f > formCount ? -1 : formIndex; + + formIndex += f - 1; + } + return -1; + } + + private static int GetDexFormCount(int species, IReadOnlyList formTable) + { + for (int i = 0; i < formTable.Count; i += 2) + { + if (formTable[i] == species) + return formTable[i + 1]; + } + return 0; + } + + public static int GetDexFormIndexBW(int species, int formCount) + { + if (formCount < 1 || species < 0) + return -1; // invalid + return species switch + { + 201 => 000, // 28 Unown + 386 => 028, // 4 Deoxys + 492 => 032, // 2 Shaymin + 487 => 034, // 2 Giratina + 479 => 036, // 6 Rotom + 422 => 042, // 2 Shellos + 423 => 044, // 2 Gastrodon + 412 => 046, // 3 Burmy + 413 => 049, // 3 Wormadam + 351 => 052, // 4 Castform + 421 => 056, // 2 Cherrim + 585 => 058, // 4 Deerling + 586 => 062, // 4 Sawsbuck + 648 => 066, // 2 Meloetta + 555 => 068, // 2 Darmanitan + 550 => 070, // 2 Basculin + _ => -1, }; + } - private static readonly ushort[] formtable_USUM = // u16 species, u16 formcount + public static int GetDexFormIndexB2W2(int species, int formCount) + { + if (formCount < 1 || species < 0) + return -1; // invalid + return species switch { - 0x0003, 0x0002, 0x0006, 0x0003, 0x0009, 0x0002, 0x000F, 0x0002, - 0x0012, 0x0002, 0x0013, 0x0002, 0x0014, 0x0003, 0x0019, 0x0008, - 0x001A, 0x0002, 0x001B, 0x0002, 0x001C, 0x0002, 0x0025, 0x0002, - 0x0026, 0x0002, 0x0032, 0x0002, 0x0033, 0x0002, 0x0034, 0x0002, - 0x0035, 0x0002, 0x0041, 0x0002, 0x004A, 0x0002, 0x004B, 0x0002, - 0x004C, 0x0002, 0x0050, 0x0002, 0x0058, 0x0002, 0x0059, 0x0002, - 0x005E, 0x0002, 0x0067, 0x0002, 0x0069, 0x0003, 0x0073, 0x0002, - 0x007F, 0x0002, 0x0082, 0x0002, 0x008E, 0x0002, 0x0096, 0x0003, - 0x00B5, 0x0002, 0x00C9, 0x001C, 0x00D0, 0x0002, 0x00D4, 0x0002, - 0x00D6, 0x0002, 0x00E5, 0x0002, 0x00F8, 0x0002, 0x00FE, 0x0002, - 0x0101, 0x0002, 0x0104, 0x0002, 0x011A, 0x0002, 0x012E, 0x0002, - 0x012F, 0x0002, 0x0132, 0x0002, 0x0134, 0x0002, 0x0136, 0x0002, - 0x013F, 0x0002, 0x0143, 0x0002, 0x014E, 0x0002, 0x015F, 0x0004, - 0x0162, 0x0002, 0x0167, 0x0002, 0x016A, 0x0002, 0x0175, 0x0002, - 0x0178, 0x0002, 0x017C, 0x0002, 0x017D, 0x0002, 0x017E, 0x0002, - 0x017F, 0x0002, 0x0180, 0x0002, 0x0182, 0x0004, 0x019C, 0x0003, - 0x019D, 0x0003, 0x019E, 0x0003, 0x01A5, 0x0002, 0x01A6, 0x0002, - 0x01A7, 0x0002, 0x01AC, 0x0002, 0x01BD, 0x0002, 0x01C0, 0x0002, - 0x01CC, 0x0002, 0x01DB, 0x0002, 0x01DF, 0x0006, 0x01E7, 0x0002, - 0x01EC, 0x0002, 0x01ED, 0x0012, 0x0213, 0x0002, 0x0226, 0x0002, - 0x022B, 0x0002, 0x0249, 0x0004, 0x024A, 0x0004, 0x0281, 0x0002, - 0x0282, 0x0002, 0x0285, 0x0002, 0x0286, 0x0003, 0x0287, 0x0002, - 0x0288, 0x0002, 0x0289, 0x0005, 0x0292, 0x0003, 0x0298, 0x0014, - 0x0299, 0x0014, 0x029A, 0x0014, 0x029D, 0x0005, 0x029E, 0x0006, - 0x029F, 0x0005, 0x02A4, 0x000A, 0x02A6, 0x0002, 0x02A9, 0x0002, - 0x02C6, 0x0004, 0x02C7, 0x0004, 0x02CC, 0x0002, 0x02CE, 0x0005, - 0x02CF, 0x0002, 0x02D0, 0x0002, 0x02DF, 0x0002, 0x02E2, 0x0002, - 0x02E5, 0x0004, 0x02E7, 0x0002, 0x02E8, 0x0002, 0x02E9, 0x0003, - 0x02EA, 0x0002, 0x02F0, 0x0002, 0x02F2, 0x0002, 0x02F6, 0x0002, - 0x0305, 0x0012, 0x0306, 0x000E, 0x0309, 0x0002, 0x030A, 0x0004, - 0x0310, 0x0002, 0x0320, 0x0004, 0x0321, 0x0002, + 646 => 072, // 3 Kyurem + 647 => 075, // 2 Keldeo + 642 => 077, // 2 Thundurus + 641 => 079, // 2 Tornadus + 645 => 081, // 2 Landorus + _ => GetDexFormIndexBW(species, formCount), }; + } - private static readonly ushort[] formtable_GG = // u16 species, u16 formcount + public static int GetDexFormIndexXY(int species, int formCount) + { + if (formCount < 1 || species < 0) + return -1; // invalid + return species switch { - 0x0003, 0x0002, 0x0006, 0x0003, 0x0009, 0x0002, 0x000F, 0x0002, - 0x0012, 0x0002, 0x0013, 0x0002, 0x0014, 0x0003, 0x0019, 0x0009, - 0x001A, 0x0002, 0x001B, 0x0002, 0x001C, 0x0002, 0x0025, 0x0002, - 0x0026, 0x0002, 0x0032, 0x0002, 0x0033, 0x0002, 0x0034, 0x0002, - 0x0035, 0x0002, 0x0041, 0x0002, 0x004A, 0x0002, 0x004B, 0x0002, - 0x004C, 0x0002, 0x0050, 0x0002, 0x0058, 0x0002, 0x0059, 0x0002, - 0x005E, 0x0002, 0x0067, 0x0002, 0x0069, 0x0003, 0x0073, 0x0002, - 0x007F, 0x0002, 0x0082, 0x0002, 0x008E, 0x0002, 0x0096, 0x0003, + 666 => 083, // 20 Vivillion + 669 => 103, // 5 Flabébé + 670 => 108, // 6 Floette + 671 => 114, // 5 Florges + 710 => 119, // 4 Pumpkaboo + 711 => 123, // 4 Gourgeist + 681 => 127, // 2 Aegislash + 716 => 129, // 2 Xerneas + 003 => 131, // 2 Venusaur + 006 => 133, // 3 Charizard + 009 => 136, // 2 Blastoise + 065 => 138, // 2 Alakazam + 094 => 140, // 2 Gengar + 115 => 142, // 2 Kangaskhan + 127 => 144, // 2 Pinsir + 130 => 146, // 2 Gyarados + 142 => 148, // 2 Aerodactyl + 150 => 150, // 3 Mewtwo + 181 => 153, // 2 Ampharos + 212 => 155, // 2 Scizor + 214 => 157, // 2 Heracros + 229 => 159, // 2 Houndoom + 248 => 161, // 2 Tyranitar + 257 => 163, // 2 Blaziken + 282 => 165, // 2 Gardevoir + 303 => 167, // 2 Mawile + 306 => 169, // 2 Aggron + 308 => 171, // 2 Medicham + 310 => 173, // 2 Manetric + 354 => 175, // 2 Banette + 359 => 177, // 2 Absol + 380 => 179, // 2 Latias + 381 => 181, // 2 Latios + 445 => 183, // 2 Garchomp + 448 => 185, // 2 Lucario + 460 => 187, // 2 Abomasnow + _ => GetDexFormIndexB2W2(species, formCount), }; + } - private static int GetDexFormBitIndex(int species, int formCount, int start, IReadOnlyList formTable) + public static int GetDexFormIndexORAS(int species, int formCount) + { + if (formCount < 1 || species < 0) + return -1; // invalid + return species switch { - int formIndex = start; - for (int i = 0; i < formTable.Count; i += 2) - { - int s = formTable[i]; - int f = formTable[i + 1]; - if (s == species) - return f > formCount ? -1 : formIndex; - - formIndex += f - 1; - } - return -1; - } - - private static int GetDexFormCount(int species, IReadOnlyList formTable) - { - for (int i = 0; i < formTable.Count; i += 2) - { - if (formTable[i] == species) - return formTable[i + 1]; - } - return 0; - } - - public static int GetDexFormIndexBW(int species, int formCount) - { - if (formCount < 1 || species < 0) - return -1; // invalid - return species switch - { - 201 => 000, // 28 Unown - 386 => 028, // 4 Deoxys - 492 => 032, // 2 Shaymin - 487 => 034, // 2 Giratina - 479 => 036, // 6 Rotom - 422 => 042, // 2 Shellos - 423 => 044, // 2 Gastrodon - 412 => 046, // 3 Burmy - 413 => 049, // 3 Wormadam - 351 => 052, // 4 Castform - 421 => 056, // 2 Cherrim - 585 => 058, // 4 Deerling - 586 => 062, // 4 Sawsbuck - 648 => 066, // 2 Meloetta - 555 => 068, // 2 Darmanitan - 550 => 070, // 2 Basculin - _ => -1, - }; - } - - public static int GetDexFormIndexB2W2(int species, int formCount) - { - if (formCount < 1 || species < 0) - return -1; // invalid - return species switch - { - 646 => 072, // 3 Kyurem - 647 => 075, // 2 Keldeo - 642 => 077, // 2 Thundurus - 641 => 079, // 2 Tornadus - 645 => 081, // 2 Landorus - _ => GetDexFormIndexBW(species, formCount), - }; - } - - public static int GetDexFormIndexXY(int species, int formCount) - { - if (formCount < 1 || species < 0) - return -1; // invalid - return species switch - { - 666 => 083, // 20 Vivillion - 669 => 103, // 5 Flabébé - 670 => 108, // 6 Floette - 671 => 114, // 5 Florges - 710 => 119, // 4 Pumpkaboo - 711 => 123, // 4 Gourgeist - 681 => 127, // 2 Aegislash - 716 => 129, // 2 Xerneas - 003 => 131, // 2 Venusaur - 006 => 133, // 3 Charizard - 009 => 136, // 2 Blastoise - 065 => 138, // 2 Alakazam - 094 => 140, // 2 Gengar - 115 => 142, // 2 Kangaskhan - 127 => 144, // 2 Pinsir - 130 => 146, // 2 Gyarados - 142 => 148, // 2 Aerodactyl - 150 => 150, // 3 Mewtwo - 181 => 153, // 2 Ampharos - 212 => 155, // 2 Scizor - 214 => 157, // 2 Heracros - 229 => 159, // 2 Houndoom - 248 => 161, // 2 Tyranitar - 257 => 163, // 2 Blaziken - 282 => 165, // 2 Gardevoir - 303 => 167, // 2 Mawile - 306 => 169, // 2 Aggron - 308 => 171, // 2 Medicham - 310 => 173, // 2 Manetric - 354 => 175, // 2 Banette - 359 => 177, // 2 Absol - 380 => 179, // 2 Latias - 381 => 181, // 2 Latios - 445 => 183, // 2 Garchomp - 448 => 185, // 2 Lucario - 460 => 187, // 2 Abomasnow - _ => GetDexFormIndexB2W2(species, formCount), - }; - } - - public static int GetDexFormIndexORAS(int species, int formCount) - { - if (formCount < 1 || species < 0) - return -1; // invalid - return species switch - { - 025 => 189, // 7 Pikachu - 720 => 196, // 2 Hoopa - 015 => 198, // 2 Beedrill - 018 => 200, // 2 Pidgeot - 080 => 202, // 2 Slowbro - 208 => 204, // 2 Steelix - 254 => 206, // 2 Sceptile - 260 => 208, // 2 Swampert - 302 => 210, // 2 Sableye - 319 => 212, // 2 Sharpedo - 323 => 214, // 2 Camerupt - 334 => 216, // 2 Altaria - 362 => 218, // 2 Glalie - 373 => 220, // 2 Salamence - 376 => 222, // 2 Metagross - 384 => 224, // 2 Rayquaza - 428 => 226, // 2 Lopunny - 475 => 228, // 2 Gallade - 531 => 230, // 2 Audino - 719 => 232, // 2 Diancie - 382 => 234, // 2 Kyogre - 383 => 236, // 2 Groudon - 493 => 238, // 18 Arceus - 649 => 256, // 5 Genesect - 676 => 261, // 10 Furfrou - _ => GetDexFormIndexXY(species, formCount), - }; - } + 025 => 189, // 7 Pikachu + 720 => 196, // 2 Hoopa + 015 => 198, // 2 Beedrill + 018 => 200, // 2 Pidgeot + 080 => 202, // 2 Slowbro + 208 => 204, // 2 Steelix + 254 => 206, // 2 Sceptile + 260 => 208, // 2 Swampert + 302 => 210, // 2 Sableye + 319 => 212, // 2 Sharpedo + 323 => 214, // 2 Camerupt + 334 => 216, // 2 Altaria + 362 => 218, // 2 Glalie + 373 => 220, // 2 Salamence + 376 => 222, // 2 Metagross + 384 => 224, // 2 Rayquaza + 428 => 226, // 2 Lopunny + 475 => 228, // 2 Gallade + 531 => 230, // 2 Audino + 719 => 232, // 2 Diancie + 382 => 234, // 2 Kyogre + 383 => 236, // 2 Groudon + 493 => 238, // 18 Arceus + 649 => 256, // 5 Genesect + 676 => 261, // 10 Furfrou + _ => GetDexFormIndexXY(species, formCount), + }; } } diff --git a/PKHeX.Core/Saves/Util/ExportFlags.cs b/PKHeX.Core/Saves/Util/ExportFlags.cs deleted file mode 100644 index 34010dfa4..000000000 --- a/PKHeX.Core/Saves/Util/ExportFlags.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace PKHeX.Core -{ - [Flags] - public enum ExportFlags - { - None, - IncludeFooter = 1 << 0, - IncludeHeader = 1 << 1, - } - - public static class ExportFlagsExtensions - { - public static bool HasFlagFast(this ExportFlags value, ExportFlags flag) => (value & flag) != 0; - } -} \ No newline at end of file diff --git a/PKHeX.Core/Saves/Util/IGCSaveFile.cs b/PKHeX.Core/Saves/Util/IGCSaveFile.cs index ae3235e08..4794dc43f 100644 --- a/PKHeX.Core/Saves/Util/IGCSaveFile.cs +++ b/PKHeX.Core/Saves/Util/IGCSaveFile.cs @@ -1,31 +1,30 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// GameCube save file interface for memory cards. +/// +public interface IGCSaveFile { /// - /// GameCube save file interface for memory cards. + /// GameCube Memory Card the save file was read from. /// - public interface IGCSaveFile + SAV3GCMemoryCard? MemoryCard { get; } +} + +public static class GCSaveExtensions +{ + /// + /// Gets an export filter for a GameCube file. + /// + public static string GCFilter(this IGCSaveFile gc) { - /// - /// GameCube Memory Card the save file was read from. - /// - SAV3GCMemoryCard? MemoryCard { get; } + const string regular = "GameCube Save File|*.gci|All Files|*.*"; + const string memcard = "Memory Card Raw File|*.raw|Memory Card Binary File|*.bin|"; + return gc.MemoryCard is not null ? memcard + regular : regular; } - public static class GCSaveExtensions - { - /// - /// Gets an export filter for a GameCube file. - /// - public static string GCFilter(this IGCSaveFile gc) - { - const string regular = "GameCube Save File|*.gci|All Files|*.*"; - const string memcard = "Memory Card Raw File|*.raw|Memory Card Binary File|*.bin|"; - return gc.MemoryCard is not null ? memcard + regular : regular; - } - - /// - /// Gets the export extension for a GameCube file. - /// - public static string GCExtension(this IGCSaveFile gc) => gc.MemoryCard is not null ? ".raw" : ".gci"; - } -} \ No newline at end of file + /// + /// Gets the export extension for a GameCube file. + /// + public static string GCExtension(this IGCSaveFile gc) => gc.MemoryCard is not null ? ".raw" : ".gci"; +} diff --git a/PKHeX.Core/Saves/Util/ILangDeviantSave.cs b/PKHeX.Core/Saves/Util/ILangDeviantSave.cs index 4b3199910..a4e3bb7e1 100644 --- a/PKHeX.Core/Saves/Util/ILangDeviantSave.cs +++ b/PKHeX.Core/Saves/Util/ILangDeviantSave.cs @@ -1,11 +1,10 @@ -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// behaves differently for different languages (different structure layout). +/// +public interface ILangDeviantSave : ISaveFileRevision { - /// - /// behaves differently for different languages (different structure layout). - /// - public interface ILangDeviantSave : ISaveFileRevision - { - bool Japanese { get; } - bool Korean { get; } - } -} \ No newline at end of file + bool Japanese { get; } + bool Korean { get; } +} diff --git a/PKHeX.Core/Saves/Util/Recognition/ISaveHandler.cs b/PKHeX.Core/Saves/Util/Recognition/ISaveHandler.cs index a95697544..f3b56fa6a 100644 --- a/PKHeX.Core/Saves/Util/Recognition/ISaveHandler.cs +++ b/PKHeX.Core/Saves/Util/Recognition/ISaveHandler.cs @@ -1,40 +1,38 @@ -namespace PKHeX.Core -{ +namespace PKHeX.Core; #if !(EXCLUDE_EMULATOR_FORMATS && EXCLUDE_HACKS) +/// +/// Provides handling for recognizing atypical save file formats. +/// +public interface ISaveHandler +{ /// - /// Provides handling for recognizing atypical save file formats. + /// Checks if the requested file size is one that can be recognized by this handler. /// - public interface ISaveHandler - { - /// - /// Checks if the requested file size is one that can be recognized by this handler. - /// - /// File size - /// True if recognized, false if not recognized. - bool IsRecognized(int size); + /// File size + /// True if recognized, false if not recognized. + bool IsRecognized(int size); - /// - /// Tries splitting up the into header/footer/data components. Returns null if not a valid save file for this handler. - /// - /// Combined data - /// Null if not a valid save file for this handler's format. Returns an object containing header, footer, and inner data references. - SaveHandlerSplitResult? TrySplit(byte[] input); - } + /// + /// Tries splitting up the into header/footer/data components. Returns null if not a valid save file for this handler. + /// + /// Combined data + /// Null if not a valid save file for this handler's format. Returns an object containing header, footer, and inner data references. + SaveHandlerSplitResult? TrySplit(byte[] input); +} #endif #if !EXCLUDE_HACKS +/// +/// Provides handling for recognizing atypical save file formats. +/// +public interface ISaveReader : ISaveHandler +{ /// - /// Provides handling for recognizing atypical save file formats. + /// Reads a save file from the /// - public interface ISaveReader : ISaveHandler - { - /// - /// Reads a save file from the - /// - /// Raw input data - /// Optional file path. - /// Save File object, or null if invalid. Check if it is compatible first. - SaveFile? ReadSaveFile(byte[] data, string? path = null); - } -#endif + /// Raw input data + /// Optional file path. + /// Save File object, or null if invalid. Check if it is compatible first. + SaveFile? ReadSaveFile(byte[] data, string? path = null); } +#endif \ No newline at end of file diff --git a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerARDS.cs b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerARDS.cs index 2cf967652..c7adc1ab2 100644 --- a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerARDS.cs +++ b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerARDS.cs @@ -1,23 +1,22 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for recognizing .duc save files dumped via an ARDS. +/// +public sealed class SaveHandlerARDS : ISaveHandler { - /// - /// Logic for recognizing .duc save files dumped via an ARDS. - /// - public sealed class SaveHandlerARDS : ISaveHandler + private const int sizeHeader = 0xA4; + private const int ExpectedSize = SaveUtil.SIZE_G4RAW + sizeHeader; // 0x800A4 + + public bool IsRecognized(int size) => size is ExpectedSize; + + public SaveHandlerSplitResult TrySplit(byte[] input) { - private const int sizeHeader = 0xA4; - private const int ExpectedSize = SaveUtil.SIZE_G4RAW + sizeHeader; // 0x800A4 - - public bool IsRecognized(int size) => size is ExpectedSize; - - public SaveHandlerSplitResult TrySplit(byte[] input) - { - // No authentication to see if it actually is a header; no size collisions expected. - var header = input.Slice(0, sizeHeader); - input = input.SliceEnd(sizeHeader); - return new SaveHandlerSplitResult(input, header, Array.Empty()); - } + // No authentication to see if it actually is a header; no size collisions expected. + var header = input.Slice(0, sizeHeader); + input = input.SliceEnd(sizeHeader); + return new SaveHandlerSplitResult(input, header, Array.Empty()); } } diff --git a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerDeSmuME.cs b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerDeSmuME.cs index 8994fef64..70d5589fe 100644 --- a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerDeSmuME.cs +++ b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerDeSmuME.cs @@ -1,36 +1,35 @@ using System; using System.Text; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for recognizing .dsv save files from DeSmuME. +/// +public sealed class SaveHandlerDeSmuME : ISaveHandler { - /// - /// Logic for recognizing .dsv save files from DeSmuME. - /// - public sealed class SaveHandlerDeSmuME : ISaveHandler + private const int sizeFooter = 0x7A; + private const int ExpectedSize = SaveUtil.SIZE_G4RAW + sizeFooter; + + private static readonly byte[] FOOTER_DSV = Encoding.ASCII.GetBytes("|-DESMUME SAVE-|"); + + private static bool GetHasFooterDSV(byte[] input) { - private const int sizeFooter = 0x7A; - private const int ExpectedSize = SaveUtil.SIZE_G4RAW + sizeFooter; + var signature = FOOTER_DSV; + var start = input.Length - signature.Length; + return input.AsSpan(start).SequenceEqual(signature); + } - private static readonly byte[] FOOTER_DSV = Encoding.ASCII.GetBytes("|-DESMUME SAVE-|"); + public bool IsRecognized(int size) => size is ExpectedSize; - private static bool GetHasFooterDSV(byte[] input) - { - var signature = FOOTER_DSV; - var start = input.Length - signature.Length; - return input.AsSpan(start).SequenceEqual(signature); - } + public SaveHandlerSplitResult? TrySplit(byte[] input) + { + if (!GetHasFooterDSV(input)) + return null; - public bool IsRecognized(int size) => size is ExpectedSize; + var footer = input.SliceEnd(SaveUtil.SIZE_G4RAW); + input = input.Slice(0, SaveUtil.SIZE_G4RAW); - public SaveHandlerSplitResult? TrySplit(byte[] input) - { - if (!GetHasFooterDSV(input)) - return null; - - var footer = input.SliceEnd(SaveUtil.SIZE_G4RAW); - input = input.Slice(0, SaveUtil.SIZE_G4RAW); - - return new SaveHandlerSplitResult(input, Array.Empty(), footer); - } + return new SaveHandlerSplitResult(input, Array.Empty(), footer); } } diff --git a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerGCI.cs b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerGCI.cs index f0894ec6b..98409901a 100644 --- a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerGCI.cs +++ b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerGCI.cs @@ -3,59 +3,58 @@ using System.Linq; using System.Text; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for recognizing .gci save files. +/// +public sealed class SaveHandlerGCI : ISaveHandler { - /// - /// Logic for recognizing .gci save files. - /// - public sealed class SaveHandlerGCI : ISaveHandler + private const int headerSize = 0x40; + private const int SIZE_G3BOXGCI = headerSize + SaveUtil.SIZE_G3BOX; // GCI data + private const int SIZE_G3COLOGCI = headerSize + SaveUtil.SIZE_G3COLO; // GCI data + private const int SIZE_G3XDGCI = headerSize + SaveUtil.SIZE_G3XD; // GCI data + + private static readonly string[] HEADER_COLO = { "GC6J", "GC6E", "GC6P" }; // NTSC-J, NTSC-U, PAL + private static readonly string[] HEADER_XD = { "GXXJ", "GXXE", "GXXP" }; // NTSC-J, NTSC-U, PAL + private static readonly string[] HEADER_RSBOX = { "GPXJ", "GPXE", "GPXP" }; // NTSC-J, NTSC-U, PAL + + private static bool IsGameMatchHeader(IEnumerable headers, byte[] data) => headers.Contains(Encoding.ASCII.GetString(data, 0, 4)); + + public bool IsRecognized(int size) => size is SIZE_G3BOXGCI or SIZE_G3COLOGCI or SIZE_G3XDGCI; + + public SaveHandlerSplitResult? TrySplit(byte[] input) { - private const int headerSize = 0x40; - private const int SIZE_G3BOXGCI = headerSize + SaveUtil.SIZE_G3BOX; // GCI data - private const int SIZE_G3COLOGCI = headerSize + SaveUtil.SIZE_G3COLO; // GCI data - private const int SIZE_G3XDGCI = headerSize + SaveUtil.SIZE_G3XD; // GCI data - - private static readonly string[] HEADER_COLO = { "GC6J", "GC6E", "GC6P" }; // NTSC-J, NTSC-U, PAL - private static readonly string[] HEADER_XD = { "GXXJ", "GXXE", "GXXP" }; // NTSC-J, NTSC-U, PAL - private static readonly string[] HEADER_RSBOX = { "GPXJ", "GPXE", "GPXP" }; // NTSC-J, NTSC-U, PAL - - private static bool IsGameMatchHeader(IEnumerable headers, byte[] data) => headers.Contains(Encoding.ASCII.GetString(data, 0, 4)); - - public bool IsRecognized(int size) => size is SIZE_G3BOXGCI or SIZE_G3COLOGCI or SIZE_G3XDGCI; - - public SaveHandlerSplitResult? TrySplit(byte[] input) + switch (input.Length) { - switch (input.Length) - { - case SIZE_G3COLOGCI when IsGameMatchHeader(HEADER_COLO , input): - case SIZE_G3XDGCI when IsGameMatchHeader(HEADER_XD , input): - case SIZE_G3BOXGCI when IsGameMatchHeader(HEADER_RSBOX, input): - break; - default: - return null; - } - - byte[] header = input.Slice(0, headerSize); - input = input.SliceEnd(headerSize); - - return new SaveHandlerSplitResult(input, header, Array.Empty()); + case SIZE_G3COLOGCI when IsGameMatchHeader(HEADER_COLO , input): + case SIZE_G3XDGCI when IsGameMatchHeader(HEADER_XD , input): + case SIZE_G3BOXGCI when IsGameMatchHeader(HEADER_RSBOX, input): + break; + default: + return null; } - /// - /// Checks if the game code is one of the recognizable versions. - /// - /// 4 character game code string - /// Magic version ID enumeration; if no match. - public static GameVersion GetGameCode(string gameCode) - { - if (HEADER_COLO.Contains(gameCode)) - return GameVersion.COLO; - if (HEADER_XD.Contains(gameCode)) - return GameVersion.XD; - if (HEADER_RSBOX.Contains(gameCode)) - return GameVersion.RSBOX; + byte[] header = input.Slice(0, headerSize); + input = input.SliceEnd(headerSize); - return GameVersion.Unknown; - } + return new SaveHandlerSplitResult(input, header, Array.Empty()); + } + + /// + /// Checks if the game code is one of the recognizable versions. + /// + /// 4 character game code string + /// Magic version ID enumeration; if no match. + public static GameVersion GetGameCode(string gameCode) + { + if (HEADER_COLO.Contains(gameCode)) + return GameVersion.COLO; + if (HEADER_XD.Contains(gameCode)) + return GameVersion.XD; + if (HEADER_RSBOX.Contains(gameCode)) + return GameVersion.RSBOX; + + return GameVersion.Unknown; } } diff --git a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerSplitResult.cs b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerSplitResult.cs index 85f7a2fcb..29f99e4d1 100644 --- a/PKHeX.Core/Saves/Util/Recognition/SaveHandlerSplitResult.cs +++ b/PKHeX.Core/Saves/Util/Recognition/SaveHandlerSplitResult.cs @@ -1,16 +1,15 @@ -namespace PKHeX.Core -{ - public sealed class SaveHandlerSplitResult - { - public readonly byte[] Header; - public readonly byte[] Footer; - public readonly byte[] Data; +namespace PKHeX.Core; - public SaveHandlerSplitResult(byte[] data, byte[] header, byte[] footer) - { - Data = data; - Header = header; - Footer = footer; - } +public sealed class SaveHandlerSplitResult +{ + public readonly byte[] Header; + public readonly byte[] Footer; + public readonly byte[] Data; + + public SaveHandlerSplitResult(byte[] data, byte[] header, byte[] footer) + { + Data = data; + Header = header; + Footer = footer; } } diff --git a/PKHeX.Core/Saves/Util/SaveExtensions.cs b/PKHeX.Core/Saves/Util/SaveExtensions.cs index c72c33d16..b874c6631 100644 --- a/PKHeX.Core/Saves/Util/SaveExtensions.cs +++ b/PKHeX.Core/Saves/Util/SaveExtensions.cs @@ -6,208 +6,207 @@ using static PKHeX.Core.MessageStrings; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Extension methods for syntax sugar. +/// +public static class SaveExtensions { /// - /// Extension methods for syntax sugar. + /// Evaluates a file for compatibility to the . /// - public static class SaveExtensions + /// that is being checked. + /// that is being tested for compatibility. + public static IReadOnlyList EvaluateCompatibility(this SaveFile sav, PKM pk) { - /// - /// Evaluates a file for compatibility to the . - /// - /// that is being checked. - /// that is being tested for compatibility. - public static IReadOnlyList EvaluateCompatibility(this SaveFile sav, PKM pk) + return sav.GetSaveFileErrata(pk, GameInfo.Strings); + } + + /// + /// Checks a file for compatibility to the . + /// + /// that is being checked. + /// that is being tested for compatibility. + public static bool IsCompatiblePKM(this SaveFile sav, PKM pk) + { + if (sav.PKMType != pk.GetType()) + return false; + + if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(pk, il.Japanese, pk.Japanese)) + return false; + + return true; + } + + private static IReadOnlyList GetSaveFileErrata(this SaveFile sav, PKM pk, IBasicStrings strings) + { + var errata = new List(); + ushort held = (ushort)pk.HeldItem; + if (sav.Generation > 1 && held != 0) { - return sav.GetSaveFileErrata(pk, GameInfo.Strings); - } - - /// - /// Checks a file for compatibility to the . - /// - /// that is being checked. - /// that is being tested for compatibility. - public static bool IsCompatiblePKM(this SaveFile sav, PKM pk) - { - if (sav.PKMType != pk.GetType()) - return false; - - if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(pk, il.Japanese, pk.Japanese)) - return false; - - return true; - } - - private static IReadOnlyList GetSaveFileErrata(this SaveFile sav, PKM pk, IBasicStrings strings) - { - var errata = new List(); - ushort held = (ushort)pk.HeldItem; - if (sav.Generation > 1 && held != 0) + string? msg = null; + if (held > sav.MaxItemID) + msg = MsgIndexItemGame; + else if (!pk.CanHoldItem(sav.HeldItems)) + msg = MsgIndexItemHeld; + if (msg != null) { - string? msg = null; - if (held > sav.MaxItemID) - msg = MsgIndexItemGame; - else if (!pk.CanHoldItem(sav.HeldItems)) - msg = MsgIndexItemHeld; - if (msg != null) - { - var itemstr = GameInfo.Strings.GetItemStrings(pk.Format, (GameVersion)pk.Version); - errata.Add($"{msg} {(held >= itemstr.Length ? held.ToString() : itemstr[held])}"); - } + var itemstr = GameInfo.Strings.GetItemStrings(pk.Format, (GameVersion)pk.Version); + errata.Add($"{msg} {(held >= itemstr.Length ? held.ToString() : itemstr[held])}"); } - - if (pk.Species > strings.Species.Count) - errata.Add($"{MsgIndexSpeciesRange} {pk.Species}"); - else if (sav.MaxSpeciesID < pk.Species) - errata.Add($"{MsgIndexSpeciesGame} {strings.Species[pk.Species]}"); - - if (!sav.Personal[pk.Species].IsFormWithinRange(pk.Form) && !FormInfo.IsValidOutOfBoundsForm(pk.Species, pk.Form, pk.Generation)) - errata.Add(string.Format(LegalityCheckStrings.LFormInvalidRange, Math.Max(0, sav.Personal[pk.Species].FormCount - 1), pk.Form)); - - if (pk.Moves.Any(m => m > strings.Move.Count)) - errata.Add($"{MsgIndexMoveRange} {string.Join(", ", pk.Moves.Where(m => m > strings.Move.Count).Select(m => m.ToString()))}"); - else if (pk.Moves.Any(m => m > sav.MaxMoveID)) - errata.Add($"{MsgIndexMoveGame} {string.Join(", ", pk.Moves.Where(m => m > sav.MaxMoveID).Select(m => strings.Move[m]))}"); - - if (pk.Ability > strings.Ability.Count) - errata.Add($"{MsgIndexAbilityRange} {pk.Ability}"); - else if (pk.Ability > sav.MaxAbilityID) - errata.Add($"{MsgIndexAbilityGame} {strings.Ability[pk.Ability]}"); - - return errata; } - /// - /// Imports compatible data to the , starting at the provided box. - /// - /// Save File that will receive the data. - /// Compatible data that can be set to the without conversion. - /// Overwrite existing full slots. If true, will only overwrite empty slots. - /// First box to start loading to. All prior boxes are not modified. - /// Bypass option to not modify properties when setting to Save File. - /// Count of injected . - public static int ImportPKMs(this SaveFile sav, IEnumerable compat, bool overwrite = false, int boxStart = 0, PKMImportSetting noSetb = PKMImportSetting.UseDefault) + if (pk.Species > strings.Species.Count) + errata.Add($"{MsgIndexSpeciesRange} {pk.Species}"); + else if (sav.MaxSpeciesID < pk.Species) + errata.Add($"{MsgIndexSpeciesGame} {strings.Species[pk.Species]}"); + + if (!sav.Personal[pk.Species].IsFormWithinRange(pk.Form) && !FormInfo.IsValidOutOfBoundsForm(pk.Species, pk.Form, pk.Generation)) + errata.Add(string.Format(LegalityCheckStrings.LFormInvalidRange, Math.Max(0, sav.Personal[pk.Species].FormCount - 1), pk.Form)); + + if (pk.Moves.Any(m => m > strings.Move.Count)) + errata.Add($"{MsgIndexMoveRange} {string.Join(", ", pk.Moves.Where(m => m > strings.Move.Count).Select(m => m.ToString()))}"); + else if (pk.Moves.Any(m => m > sav.MaxMoveID)) + errata.Add($"{MsgIndexMoveGame} {string.Join(", ", pk.Moves.Where(m => m > sav.MaxMoveID).Select(m => strings.Move[m]))}"); + + if (pk.Ability > strings.Ability.Count) + errata.Add($"{MsgIndexAbilityRange} {pk.Ability}"); + else if (pk.Ability > sav.MaxAbilityID) + errata.Add($"{MsgIndexAbilityGame} {strings.Ability[pk.Ability]}"); + + return errata; + } + + /// + /// Imports compatible data to the , starting at the provided box. + /// + /// Save File that will receive the data. + /// Compatible data that can be set to the without conversion. + /// Overwrite existing full slots. If true, will only overwrite empty slots. + /// First box to start loading to. All prior boxes are not modified. + /// Bypass option to not modify properties when setting to Save File. + /// Count of injected . + public static int ImportPKMs(this SaveFile sav, IEnumerable compat, bool overwrite = false, int boxStart = 0, PKMImportSetting noSetb = PKMImportSetting.UseDefault) + { + int startCount = boxStart * sav.BoxSlotCount; + int maxCount = sav.SlotCount; + int index = startCount; + int nonOverwriteImport = 0; + + foreach (var pk in compat) { - int startCount = boxStart * sav.BoxSlotCount; - int maxCount = sav.SlotCount; - int index = startCount; - int nonOverwriteImport = 0; - - foreach (var pk in compat) + if (overwrite) { - if (overwrite) - { - while (sav.IsSlotOverwriteProtected(index)) - ++index; + while (sav.IsSlotOverwriteProtected(index)) + ++index; - // The above will return false if out of range. We need to double-check. - if (index >= maxCount) // Boxes full! - break; - - sav.SetBoxSlotAtIndex(pk, index, noSetb); - } - else - { - index = sav.NextOpenBoxSlot(index-1); - if (index < 0) // Boxes full! - break; - - sav.SetBoxSlotAtIndex(pk, index, noSetb); - nonOverwriteImport++; - } - - if (++index == maxCount) // Boxes full! + // The above will return false if out of range. We need to double-check. + if (index >= maxCount) // Boxes full! break; + + sav.SetBoxSlotAtIndex(pk, index, noSetb); } - return overwrite ? index - startCount : nonOverwriteImport; // actual imported count - } - - public static IEnumerable GetCompatible(this SaveFile sav, IEnumerable pks) - { - var savtype = sav.PKMType; - - foreach (var temp in pks) + else { - var pk = EntityConverter.ConvertToType(temp, savtype, out var c); - if (pk == null) - { - Debug.WriteLine(c.GetDisplayString(temp, savtype)); - continue; - } + index = sav.NextOpenBoxSlot(index-1); + if (index < 0) // Boxes full! + break; - if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese)) - { - var str = EntityConverterResult.IncompatibleLanguageGB.GetIncompatibleGBMessage(pk, il.Japanese); - Debug.WriteLine(str); - continue; - } - - var compat = sav.EvaluateCompatibility(pk); - if (compat.Count > 0) - continue; - - yield return pk; + sav.SetBoxSlotAtIndex(pk, index, noSetb); + nonOverwriteImport++; } + + if (++index == maxCount) // Boxes full! + break; } + return overwrite ? index - startCount : nonOverwriteImport; // actual imported count + } - /// - /// Gets a compatible for editing with a new . - /// - /// SaveFile to receive the compatible - /// Current Pokémon being edited - /// Current Pokémon, assuming conversion is possible. If conversion is not possible, a blank will be obtained from the . - public static PKM GetCompatiblePKM(this SaveFile sav, PKM pk) + public static IEnumerable GetCompatible(this SaveFile sav, IEnumerable pks) + { + var savtype = sav.PKMType; + + foreach (var temp in pks) { - if (pk.Format >= 3 || sav.Generation >= 7) - return EntityConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM; - // gen1-2 compatibility check - if (pk.Japanese != ((ILangDeviantSave)sav).Japanese) - return sav.BlankPKM; - if (sav is SAV2 s2 && s2.Korean != pk.Korean) - return sav.BlankPKM; - return EntityConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM; - } + var pk = EntityConverter.ConvertToType(temp, savtype, out var c); + if (pk == null) + { + Debug.WriteLine(c.GetDisplayString(temp, savtype)); + continue; + } - /// - /// Gets a blank file for the save file. Adapts it to the save file. - /// - /// Save File to fetch a template for - /// Template if it exists, or a blank from the - private static PKM LoadTemplateInternal(this SaveFile sav) - { - var pk = sav.BlankPKM; - EntityTemplates.TemplateFields(pk, sav); - return pk; - } + if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese)) + { + var str = EntityConverterResult.IncompatibleLanguageGB.GetIncompatibleGBMessage(pk, il.Japanese); + Debug.WriteLine(str); + continue; + } - /// - /// Gets a blank file for the save file. If the template path exists, a template load will be attempted. - /// - /// Save File to fetch a template for - /// Path to look for a template in - /// Template if it exists, or a blank from the - public static PKM LoadTemplate(this SaveFile sav, string? templatePath = null) - { - if (templatePath == null || !Directory.Exists(templatePath)) - return LoadTemplateInternal(sav); + var compat = sav.EvaluateCompatibility(pk); + if (compat.Count > 0) + continue; - var di = new DirectoryInfo(templatePath); - string path = Path.Combine(templatePath, $"{di.Name}.{sav.PKMType.Name.ToLowerInvariant()}"); - - if (!File.Exists(path)) - return LoadTemplateInternal(sav); - var fi = new FileInfo(path); - if (!EntityDetection.IsSizePlausible(fi.Length)) - return LoadTemplateInternal(sav); - - var data = File.ReadAllBytes(path); - var prefer = EntityFileExtension.GetContextFromExtension(fi.Extension, sav.Context); - var pk = EntityFormat.GetFromBytes(data, prefer); - if (pk?.Species is not > 0) - return LoadTemplateInternal(sav); - - return EntityConverter.ConvertToType(pk, sav.BlankPKM.GetType(), out _) ?? LoadTemplateInternal(sav); + yield return pk; } } + + /// + /// Gets a compatible for editing with a new . + /// + /// SaveFile to receive the compatible + /// Current Pokémon being edited + /// Current Pokémon, assuming conversion is possible. If conversion is not possible, a blank will be obtained from the . + public static PKM GetCompatiblePKM(this SaveFile sav, PKM pk) + { + if (pk.Format >= 3 || sav.Generation >= 7) + return EntityConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM; + // gen1-2 compatibility check + if (pk.Japanese != ((ILangDeviantSave)sav).Japanese) + return sav.BlankPKM; + if (sav is SAV2 s2 && s2.Korean != pk.Korean) + return sav.BlankPKM; + return EntityConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM; + } + + /// + /// Gets a blank file for the save file. Adapts it to the save file. + /// + /// Save File to fetch a template for + /// Template if it exists, or a blank from the + private static PKM LoadTemplateInternal(this SaveFile sav) + { + var pk = sav.BlankPKM; + EntityTemplates.TemplateFields(pk, sav); + return pk; + } + + /// + /// Gets a blank file for the save file. If the template path exists, a template load will be attempted. + /// + /// Save File to fetch a template for + /// Path to look for a template in + /// Template if it exists, or a blank from the + public static PKM LoadTemplate(this SaveFile sav, string? templatePath = null) + { + if (templatePath == null || !Directory.Exists(templatePath)) + return LoadTemplateInternal(sav); + + var di = new DirectoryInfo(templatePath); + string path = Path.Combine(templatePath, $"{di.Name}.{sav.PKMType.Name.ToLowerInvariant()}"); + + if (!File.Exists(path)) + return LoadTemplateInternal(sav); + var fi = new FileInfo(path); + if (!EntityDetection.IsSizePlausible(fi.Length)) + return LoadTemplateInternal(sav); + + var data = File.ReadAllBytes(path); + var prefer = EntityFileExtension.GetContextFromExtension(fi.Extension, sav.Context); + var pk = EntityFormat.GetFromBytes(data, prefer); + if (pk?.Species is not > 0) + return LoadTemplateInternal(sav); + + return EntityConverter.ConvertToType(pk, sav.BlankPKM.GetType(), out _) ?? LoadTemplateInternal(sav); + } } diff --git a/PKHeX.Core/Saves/Util/SaveFinder.cs b/PKHeX.Core/Saves/Util/SaveFinder.cs index 606598425..c45271f6f 100644 --- a/PKHeX.Core/Saves/Util/SaveFinder.cs +++ b/PKHeX.Core/Saves/Util/SaveFinder.cs @@ -4,181 +4,180 @@ using System.IO; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Utility logic for detecting a from various locations on the host machine. +/// +public static class SaveFinder { /// - /// Utility logic for detecting a from various locations on the host machine. + /// Searches the provided to find a valid 3DS drive, usually from an inserted SD card. /// - public static class SaveFinder + /// List of drives on the host machine. + /// Optional parameter to skip the first drive. + /// The first drive is usually the system hard drive, or can be a floppy disk drive (slower to check, never has expected data). + /// Folder path pointing to the Nintendo 3DS folder. + public static string? Get3DSLocation(IEnumerable drives, bool skipFirstDrive = true) => + FindConsoleRootFolder(drives, "Nintendo 3DS", skipFirstDrive); + + /// + /// Searches the provided to find a valid Switch drive, usually from an inserted SD card. + /// + /// List of drives on the host machine. + /// Optional parameter to skip the first drive. + /// The first drive is usually the system hard drive, or can be a floppy disk drive (slower to check, never has expected data). + /// Folder path pointing to the Nintendo folder. + public static string? GetSwitchLocation(IEnumerable drives, bool skipFirstDrive = true) => + FindConsoleRootFolder(drives, "Nintendo", skipFirstDrive); + + private static string? FindConsoleRootFolder(IEnumerable drives, string path, bool skipFirstDrive) { - /// - /// Searches the provided to find a valid 3DS drive, usually from an inserted SD card. - /// - /// List of drives on the host machine. - /// Optional parameter to skip the first drive. - /// The first drive is usually the system hard drive, or can be a floppy disk drive (slower to check, never has expected data). - /// Folder path pointing to the Nintendo 3DS folder. - public static string? Get3DSLocation(IEnumerable drives, bool skipFirstDrive = true) => - FindConsoleRootFolder(drives, "Nintendo 3DS", skipFirstDrive); + if (skipFirstDrive) + drives = drives.Skip(1); - /// - /// Searches the provided to find a valid Switch drive, usually from an inserted SD card. - /// - /// List of drives on the host machine. - /// Optional parameter to skip the first drive. - /// The first drive is usually the system hard drive, or can be a floppy disk drive (slower to check, never has expected data). - /// Folder path pointing to the Nintendo folder. - public static string? GetSwitchLocation(IEnumerable drives, bool skipFirstDrive = true) => - FindConsoleRootFolder(drives, "Nintendo", skipFirstDrive); + var paths = drives.Select(drive => Path.Combine(drive, path)); + return paths.FirstOrDefault(Directory.Exists); + } - private static string? FindConsoleRootFolder(IEnumerable drives, string path, bool skipFirstDrive) + /// + /// Gets a list of 3DS save backup paths for the storage device. + /// + /// Root location of device + /// List of possible 3DS save backup paths. + public static IEnumerable Get3DSBackupPaths(string root) + { + yield return Path.Combine(root, "saveDataBackup"); + yield return Path.Combine(root, "filer", "UserSaveData"); + yield return Path.Combine(root, "JKSV", "Saves"); + yield return Path.Combine(root, "TWLSaveTool"); + yield return Path.Combine(root, "fbi", "save"); + yield return Path.Combine(root, "gm9", "out"); + yield return Path.Combine(root, "3ds", "Checkpoint", "saves"); + } + + /// + /// Gets a list of Switch save backup paths for the storage device. + /// + /// Root location of device + /// List of possible 3DS save backup paths. + public static IEnumerable GetSwitchBackupPaths(string root) + { + yield return Path.Combine(root, "switch", "Checkpoint", "saves"); + } + + /// + /// Extra list of Backup Paths used for detecting a save file. + /// + public static readonly List CustomBackupPaths = new(); + + /// + /// Finds a compatible save file that was most recently saved (by file write time). + /// + /// List of drives on the host machine. + /// Paths to check in addition to the default paths + /// Reference to a valid save file, if any. + public static SaveFile? FindMostRecentSaveFile(IReadOnlyList drives, params string[] extra) + => FindMostRecentSaveFile(drives, (IEnumerable)extra); + + /// + /// Finds a compatible save file that was most recently saved (by file write time). + /// + /// List of drives on the host machine. + /// Paths to check in addition to the default paths + /// Reference to a valid save file, if any. + public static SaveFile? FindMostRecentSaveFile(IReadOnlyList drives, IEnumerable extra) + { + var foldersToCheck = GetFoldersToCheck(drives, extra); + var result = GetSaveFilePathsFromFolders(foldersToCheck, true, out var possiblePaths); + if (!result) + throw new FileNotFoundException(string.Join(Environment.NewLine, possiblePaths)); // `possiblePaths` contains the error message + + // return newest save file path that is valid + var byMostRecent = possiblePaths.OrderByDescending(File.GetLastWriteTimeUtc); + var saves = byMostRecent.Select(SaveUtil.GetVariantSAV); + return saves.FirstOrDefault(z => z?.ChecksumsValid == true); + } + + /// + /// Gets all detectable save files ordered by most recently saved (by file write time). + /// + /// List of drives on the host machine. + /// Detect save files stored in common SD card homebrew locations. + /// Paths to check in addition to the default paths + /// Option to ignore backup files. + /// Valid save files, if any. + public static IEnumerable GetSaveFiles(IReadOnlyList drives, bool detect, IEnumerable extra, bool ignoreBackups) + { + var paths = detect ? GetFoldersToCheck(drives, extra) : extra; + var result = GetSaveFilePathsFromFolders(paths, ignoreBackups, out var possiblePaths); + if (!result) + yield break; + + var byMostRecent = possiblePaths.OrderByDescending(File.GetLastWriteTimeUtc); + foreach (var s in byMostRecent) { - if (skipFirstDrive) - drives = drives.Skip(1); - - var paths = drives.Select(drive => Path.Combine(drive, path)); - return paths.FirstOrDefault(Directory.Exists); - } - - /// - /// Gets a list of 3DS save backup paths for the storage device. - /// - /// Root location of device - /// List of possible 3DS save backup paths. - public static IEnumerable Get3DSBackupPaths(string root) - { - yield return Path.Combine(root, "saveDataBackup"); - yield return Path.Combine(root, "filer", "UserSaveData"); - yield return Path.Combine(root, "JKSV", "Saves"); - yield return Path.Combine(root, "TWLSaveTool"); - yield return Path.Combine(root, "fbi", "save"); - yield return Path.Combine(root, "gm9", "out"); - yield return Path.Combine(root, "3ds", "Checkpoint", "saves"); - } - - /// - /// Gets a list of Switch save backup paths for the storage device. - /// - /// Root location of device - /// List of possible 3DS save backup paths. - public static IEnumerable GetSwitchBackupPaths(string root) - { - yield return Path.Combine(root, "switch", "Checkpoint", "saves"); - } - - /// - /// Extra list of Backup Paths used for detecting a save file. - /// - public static readonly List CustomBackupPaths = new(); - - /// - /// Finds a compatible save file that was most recently saved (by file write time). - /// - /// List of drives on the host machine. - /// Paths to check in addition to the default paths - /// Reference to a valid save file, if any. - public static SaveFile? FindMostRecentSaveFile(IReadOnlyList drives, params string[] extra) - => FindMostRecentSaveFile(drives, (IEnumerable)extra); - - /// - /// Finds a compatible save file that was most recently saved (by file write time). - /// - /// List of drives on the host machine. - /// Paths to check in addition to the default paths - /// Reference to a valid save file, if any. - public static SaveFile? FindMostRecentSaveFile(IReadOnlyList drives, IEnumerable extra) - { - var foldersToCheck = GetFoldersToCheck(drives, extra); - var result = GetSaveFilePathsFromFolders(foldersToCheck, true, out var possiblePaths); - if (!result) - throw new Exception(string.Join(Environment.NewLine, possiblePaths)); // `possiblePaths` contains the error message - - // return newest save file path that is valid - var byMostRecent = possiblePaths.OrderByDescending(File.GetLastWriteTimeUtc); - var saves = byMostRecent.Select(SaveUtil.GetVariantSAV); - return saves.FirstOrDefault(z => z?.ChecksumsValid == true); - } - - /// - /// Gets all detectable save files ordered by most recently saved (by file write time). - /// - /// List of drives on the host machine. - /// Detect save files stored in common SD card homebrew locations. - /// Paths to check in addition to the default paths - /// Option to ignore backup files. - /// Valid save files, if any. - public static IEnumerable GetSaveFiles(IReadOnlyList drives, bool detect, IEnumerable extra, bool ignoreBackups) - { - var paths = detect ? GetFoldersToCheck(drives, extra) : extra; - var result = GetSaveFilePathsFromFolders(paths, ignoreBackups, out var possiblePaths); - if (!result) - yield break; - - var byMostRecent = possiblePaths.OrderByDescending(File.GetLastWriteTimeUtc); - foreach (var s in byMostRecent) - { - var sav = SaveUtil.GetVariantSAV(s); - if (sav != null) - yield return sav; - } - } - - public static IEnumerable GetFoldersToCheck(IReadOnlyList drives, IEnumerable extra) - { - var foldersToCheck = extra.Where(f => !string.IsNullOrWhiteSpace(f)).Concat(CustomBackupPaths); - - string? path3DS = Path.GetPathRoot(Get3DSLocation(drives)); - if (!string.IsNullOrEmpty(path3DS)) // check for Homebrew/CFW backups - foldersToCheck = foldersToCheck.Concat(Get3DSBackupPaths(path3DS)); - - string? pathNX = Path.GetPathRoot(GetSwitchLocation(drives)); - if (!string.IsNullOrEmpty(pathNX)) // check for Homebrew/CFW backups - foldersToCheck = foldersToCheck.Concat(GetSwitchBackupPaths(pathNX)); - - return foldersToCheck; - } - - private static bool GetSaveFilePathsFromFolders(IEnumerable foldersToCheck, bool ignoreBackups, out IEnumerable possible) - { - var possiblePaths = new List(); - foreach (var folder in foldersToCheck) - { - if (!SaveUtil.GetSavesFromFolder(folder, true, out IEnumerable files, ignoreBackups)) - { - if (files is not string[] msg) // should always return string[] - continue; - if (msg.Length == 0) // folder doesn't exist - continue; - possible = msg; - return false; - } - possiblePaths.AddRange(files); - } - possible = possiblePaths; - return true; - } - - /// - public static SaveFile? FindMostRecentSaveFile() => FindMostRecentSaveFile(Environment.GetLogicalDrives(), CustomBackupPaths); - - /// - public static IEnumerable DetectSaveFiles() => GetSaveFiles(Environment.GetLogicalDrives(), true, CustomBackupPaths, true); - - /// - public static bool TryDetectSaveFile([NotNullWhen(true)] out SaveFile? sav) => TryDetectSaveFile(Environment.GetLogicalDrives(), out sav); - - public static bool TryDetectSaveFile(IReadOnlyList drives, [NotNullWhen(true)] out SaveFile? sav) - { - var result = FindMostRecentSaveFile(drives, CustomBackupPaths); - if (result == null) - { - sav = null; - return false; - } - - var path = result.Metadata.FilePath!; - sav = result; - return File.Exists(path); + var sav = SaveUtil.GetVariantSAV(s); + if (sav != null) + yield return sav; } } + + public static IEnumerable GetFoldersToCheck(IReadOnlyList drives, IEnumerable extra) + { + var foldersToCheck = extra.Where(f => !string.IsNullOrWhiteSpace(f)).Concat(CustomBackupPaths); + + string? path3DS = Path.GetPathRoot(Get3DSLocation(drives)); + if (!string.IsNullOrEmpty(path3DS)) // check for Homebrew/CFW backups + foldersToCheck = foldersToCheck.Concat(Get3DSBackupPaths(path3DS)); + + string? pathNX = Path.GetPathRoot(GetSwitchLocation(drives)); + if (!string.IsNullOrEmpty(pathNX)) // check for Homebrew/CFW backups + foldersToCheck = foldersToCheck.Concat(GetSwitchBackupPaths(pathNX)); + + return foldersToCheck; + } + + private static bool GetSaveFilePathsFromFolders(IEnumerable foldersToCheck, bool ignoreBackups, out IEnumerable possible) + { + var possiblePaths = new List(); + foreach (var folder in foldersToCheck) + { + if (!SaveUtil.GetSavesFromFolder(folder, true, out IEnumerable files, ignoreBackups)) + { + if (files is not string[] msg) // should always return string[] + continue; + if (msg.Length == 0) // folder doesn't exist + continue; + possible = msg; + return false; + } + possiblePaths.AddRange(files); + } + possible = possiblePaths; + return true; + } + + /// + public static SaveFile? FindMostRecentSaveFile() => FindMostRecentSaveFile(Environment.GetLogicalDrives(), CustomBackupPaths); + + /// + public static IEnumerable DetectSaveFiles() => GetSaveFiles(Environment.GetLogicalDrives(), true, CustomBackupPaths, true); + + /// + public static bool TryDetectSaveFile([NotNullWhen(true)] out SaveFile? sav) => TryDetectSaveFile(Environment.GetLogicalDrives(), out sav); + + public static bool TryDetectSaveFile(IReadOnlyList drives, [NotNullWhen(true)] out SaveFile? sav) + { + var result = FindMostRecentSaveFile(drives, CustomBackupPaths); + if (result == null) + { + sav = null; + return false; + } + + var path = result.Metadata.FilePath!; + sav = result; + return File.Exists(path); + } } diff --git a/PKHeX.Core/Saves/Util/SaveUtil.cs b/PKHeX.Core/Saves/Util/SaveUtil.cs index 0136c06e9..def38ba88 100644 --- a/PKHeX.Core/Saves/Util/SaveUtil.cs +++ b/PKHeX.Core/Saves/Util/SaveUtil.cs @@ -6,840 +6,839 @@ using static PKHeX.Core.MessageStrings; using static PKHeX.Core.GameVersion; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for data loading and manipulation. +/// +public static class SaveUtil { + public const int BEEF = 0x42454546; + + public const int SIZE_G8LA = 0x136DDE; + public const int SIZE_G8LA_1 = 0x13AD06; + + public const int SIZE_G8BDSP = 0xE9828; + public const int SIZE_G8BDSP_1 = 0xEDC20; + public const int SIZE_G8BDSP_2 = 0xEED8C; + public const int SIZE_G8BDSP_3 = 0xEF0A4; + + public const int SIZE_G8SWSH = 0x1716B3; // 1.0 + public const int SIZE_G8SWSH_1 = 0x17195E; // 1.0 -> 1.1 + public const int SIZE_G8SWSH_2 = 0x180B19; // 1.0 -> 1.1 -> 1.2 + public const int SIZE_G8SWSH_2B = 0x180AD0; // 1.0 -> 1.2 + public const int SIZE_G8SWSH_3 = 0x1876B1; // 1.0 -> 1.1 -> 1.2 -> 1.3 + public const int SIZE_G8SWSH_3A = 0x187693; // 1.0 -> 1.1 -> 1.3 + public const int SIZE_G8SWSH_3B = 0x187668; // 1.0 -> 1.2 -> 1.3 + public const int SIZE_G8SWSH_3C = 0x18764A; // 1.0 -> 1.3 + + public const int SIZE_G7GG = 0x100000; + public const int SIZE_G7USUM = 0x6CC00; + public const int SIZE_G7SM = 0x6BE00; + public const int SIZE_G6XY = 0x65600; + public const int SIZE_G6ORAS = 0x76000; + public const int SIZE_G6ORASDEMO = 0x5A00; + public const int SIZE_G5RAW = 0x80000; + public const int SIZE_G5BW = 0x24000; + public const int SIZE_G5B2W2 = 0x26000; + public const int SIZE_G4BR = 0x380000; + public const int SIZE_G4RAW = 0x80000; + public const int SIZE_G3BOX = 0x76000; + public const int SIZE_G3COLO = 0x60000; + public const int SIZE_G3XD = 0x56000; + public const int SIZE_G3RAW = 0x20000; + public const int SIZE_G3RAWHALF = 0x10000; + public const int SIZE_G2STAD = 0x20000; // same as G3RAW_U + public const int SIZE_G2STADF = 0x1FF00; + public const int SIZE_G2RAW_U = 0x8000; + public const int SIZE_G2VC_U = 0x8010; + public const int SIZE_G2BAT_U = 0x802C; + public const int SIZE_G2EMU_U = 0x8030; + public const int SIZE_G2RAW_J = 0x10000; + public const int SIZE_G2VC_J = 0x10010; + public const int SIZE_G2BAT_J = 0x1002C; + public const int SIZE_G2EMU_J = 0x10030; + public const int SIZE_G1STAD = 0x20000; // same as G3RAW_U + public const int SIZE_G1STADF = 0x1FF00; + public const int SIZE_G1STADJ = 0x8000; // same as G1RAW + public const int SIZE_G1RAW = 0x8000; + public const int SIZE_G1BAT = 0x802C; + + // Bank Binaries + public const int SIZE_G7BANK = 0xACA48; + public const int SIZE_G4BANK = 0x405C4; + public const int SIZE_G4RANCH = 0x54000; + public const int SIZE_G4RANCH_PLAT = 0x7C000; + + private static readonly SaveHandlerGCI DolphinHandler = new(); + +#if !EXCLUDE_HACKS /// - /// Logic for data loading and manipulation. + /// Specialized readers for loading save files from non-standard games (e.g. hacks). /// - public static class SaveUtil + // ReSharper disable once CollectionNeverUpdated.Global + public static readonly List CustomSaveReaders = new(); +#endif + +#if !EXCLUDE_EMULATOR_FORMATS + /// + /// Pre-formatters for loading save files from non-standard formats (e.g. emulators). + /// + public static readonly ICollection Handlers = new List { - public const int BEEF = 0x42454546; - - public const int SIZE_G8LA = 0x136DDE; - public const int SIZE_G8LA_1 = 0x13AD06; - - public const int SIZE_G8BDSP = 0xE9828; - public const int SIZE_G8BDSP_1 = 0xEDC20; - public const int SIZE_G8BDSP_2 = 0xEED8C; - public const int SIZE_G8BDSP_3 = 0xEF0A4; - - public const int SIZE_G8SWSH = 0x1716B3; // 1.0 - public const int SIZE_G8SWSH_1 = 0x17195E; // 1.0 -> 1.1 - public const int SIZE_G8SWSH_2 = 0x180B19; // 1.0 -> 1.1 -> 1.2 - public const int SIZE_G8SWSH_2B = 0x180AD0; // 1.0 -> 1.2 - public const int SIZE_G8SWSH_3 = 0x1876B1; // 1.0 -> 1.1 -> 1.2 -> 1.3 - public const int SIZE_G8SWSH_3A = 0x187693; // 1.0 -> 1.1 -> 1.3 - public const int SIZE_G8SWSH_3B = 0x187668; // 1.0 -> 1.2 -> 1.3 - public const int SIZE_G8SWSH_3C = 0x18764A; // 1.0 -> 1.3 - - public const int SIZE_G7GG = 0x100000; - public const int SIZE_G7USUM = 0x6CC00; - public const int SIZE_G7SM = 0x6BE00; - public const int SIZE_G6XY = 0x65600; - public const int SIZE_G6ORAS = 0x76000; - public const int SIZE_G6ORASDEMO = 0x5A00; - public const int SIZE_G5RAW = 0x80000; - public const int SIZE_G5BW = 0x24000; - public const int SIZE_G5B2W2 = 0x26000; - public const int SIZE_G4BR = 0x380000; - public const int SIZE_G4RAW = 0x80000; - public const int SIZE_G3BOX = 0x76000; - public const int SIZE_G3COLO = 0x60000; - public const int SIZE_G3XD = 0x56000; - public const int SIZE_G3RAW = 0x20000; - public const int SIZE_G3RAWHALF = 0x10000; - public const int SIZE_G2STAD = 0x20000; // same as G3RAW_U - public const int SIZE_G2STADF = 0x1FF00; - public const int SIZE_G2RAW_U = 0x8000; - public const int SIZE_G2VC_U = 0x8010; - public const int SIZE_G2BAT_U = 0x802C; - public const int SIZE_G2EMU_U = 0x8030; - public const int SIZE_G2RAW_J = 0x10000; - public const int SIZE_G2VC_J = 0x10010; - public const int SIZE_G2BAT_J = 0x1002C; - public const int SIZE_G2EMU_J = 0x10030; - public const int SIZE_G1STAD = 0x20000; // same as G3RAW_U - public const int SIZE_G1STADF = 0x1FF00; - public const int SIZE_G1STADJ = 0x8000; // same as G1RAW - public const int SIZE_G1RAW = 0x8000; - public const int SIZE_G1BAT = 0x802C; - - // Bank Binaries - public const int SIZE_G7BANK = 0xACA48; - public const int SIZE_G4BANK = 0x405C4; - public const int SIZE_G4RANCH = 0x54000; - public const int SIZE_G4RANCH_PLAT = 0x7C000; - - private static readonly SaveHandlerGCI DolphinHandler = new(); - -#if !EXCLUDE_HACKS - /// - /// Specialized readers for loading save files from non-standard games (e.g. hacks). - /// - // ReSharper disable once CollectionNeverUpdated.Global - public static readonly List CustomSaveReaders = new(); + DolphinHandler, + new SaveHandlerDeSmuME(), + new SaveHandlerARDS(), + }; #endif -#if !EXCLUDE_EMULATOR_FORMATS - /// - /// Pre-formatters for loading save files from non-standard formats (e.g. emulators). - /// - public static readonly ICollection Handlers = new List - { - DolphinHandler, - new SaveHandlerDeSmuME(), - new SaveHandlerARDS(), - }; -#endif + internal static readonly HashSet SizesSWSH = new() + { + SIZE_G8SWSH, SIZE_G8SWSH_1, SIZE_G8SWSH_2, SIZE_G8SWSH_2B, SIZE_G8SWSH_3, SIZE_G8SWSH_3A, SIZE_G8SWSH_3B, SIZE_G8SWSH_3C, + }; - internal static readonly HashSet SizesSWSH = new() - { - SIZE_G8SWSH, SIZE_G8SWSH_1, SIZE_G8SWSH_2, SIZE_G8SWSH_2B, SIZE_G8SWSH_3, SIZE_G8SWSH_3A, SIZE_G8SWSH_3B, SIZE_G8SWSH_3C, - }; + private static readonly HashSet SizesGen2 = new() + { + SIZE_G2RAW_U, SIZE_G2VC_U, SIZE_G2BAT_U, SIZE_G2EMU_U, SIZE_G2RAW_J, SIZE_G2BAT_J, SIZE_G2EMU_J, SIZE_G2VC_J, + }; - private static readonly HashSet SizesGen2 = new() - { - SIZE_G2RAW_U, SIZE_G2VC_U, SIZE_G2BAT_U, SIZE_G2EMU_U, SIZE_G2RAW_J, SIZE_G2BAT_J, SIZE_G2EMU_J, SIZE_G2VC_J, - }; + private static readonly HashSet Sizes = new(SizesGen2.Concat(SizesSWSH)) + { + SIZE_G8LA, SIZE_G8LA_1, SIZE_G8BDSP, SIZE_G8BDSP_1, SIZE_G8BDSP_2, SIZE_G8BDSP_3, + // SizesSWSH covers gen8 sizes since there's so many + SIZE_G7SM, SIZE_G7USUM, SIZE_G7GG, + SIZE_G6XY, SIZE_G6ORAS, SIZE_G6ORASDEMO, + SIZE_G5RAW, SIZE_G5BW, SIZE_G5B2W2, + SIZE_G4BR, SIZE_G4RAW, + SIZE_G3BOX, SIZE_G3COLO, SIZE_G3XD, SIZE_G3RAW, SIZE_G3RAWHALF, + // SizesGen2 covers gen2 sizes since there's so many + SIZE_G1RAW, SIZE_G1BAT, - private static readonly HashSet Sizes = new(SizesGen2.Concat(SizesSWSH)) - { - SIZE_G8LA, SIZE_G8LA_1, SIZE_G8BDSP, SIZE_G8BDSP_1, SIZE_G8BDSP_2, SIZE_G8BDSP_3, - // SizesSWSH covers gen8 sizes since there's so many - SIZE_G7SM, SIZE_G7USUM, SIZE_G7GG, - SIZE_G6XY, SIZE_G6ORAS, SIZE_G6ORASDEMO, - SIZE_G5RAW, SIZE_G5BW, SIZE_G5B2W2, - SIZE_G4BR, SIZE_G4RAW, - SIZE_G3BOX, SIZE_G3COLO, SIZE_G3XD, SIZE_G3RAW, SIZE_G3RAWHALF, - // SizesGen2 covers gen2 sizes since there's so many - SIZE_G1RAW, SIZE_G1BAT, + SIZE_G7BANK, SIZE_G4BANK, SIZE_G4RANCH, SIZE_G4RANCH_PLAT, + }; - SIZE_G7BANK, SIZE_G4BANK, SIZE_G4RANCH, SIZE_G4RANCH_PLAT, - }; - - /// Determines the type of the provided save data. - /// Save data of which to determine the origins of - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetSAVType(byte[] data) - { - GameVersion ver; - if ((ver = GetIsG1SAV(data)) != Invalid) - return ver; - if ((ver = GetIsG2SAV(data)) != Invalid) - return ver; - if ((ver = GetIsG3SAV(data)) != Invalid) - return ver; - if ((ver = GetIsG4SAV(data)) != Invalid) - return ver; - if ((ver = GetIsG5SAV(data)) != Invalid) - return ver; - if ((ver = GetIsG6SAV(data)) != Invalid) - return ver; - if ((ver = GetIsG7SAV(data)) != Invalid) - return ver; - - if (GetIsBelugaSAV(data) != Invalid) - return GG; - if (GetIsG3COLOSAV(data) != Invalid) - return COLO; - if (GetIsG3XDSAV(data) != Invalid) - return XD; - if (GetIsG3BOXSAV(data) != Invalid) - return RSBOX; - if (GetIsG4BRSAV(data) != Invalid) - return BATREV; - - if (GetIsBank7(data)) // pokebank - return Gen7; - if (GetIsBank4(data)) // pokestock - return Gen4; - if (GetIsBank3(data)) // pokestock - return Gen3; - if (GetIsRanch4(data)) // ranch - return DPPt; - if (SAV2Stadium.IsStadium(data)) - return Stadium2; - if (SAV1Stadium.IsStadium(data)) - return Stadium; - if (SAV1StadiumJ.IsStadium(data)) - return StadiumJ; - - if ((ver = GetIsG8SAV(data)) != Invalid) - return ver; - if ((ver = GetIsG8SAV_BDSP(data)) != Invalid) - return ver; - if ((ver = GetIsG8SAV_LA(data)) != Invalid) - return ver; - - return Invalid; - } - - /// - /// Determines if a Gen1/2 Pokémon List is Invalid - /// - /// Save data - /// Offset the list starts at - /// Max count of Pokémon in the list - /// True if a valid list, False otherwise - private static bool IsG12ListValid(ReadOnlySpan data, int offset, int listCount) - { - byte num_entries = data[offset]; - return num_entries <= listCount && data[offset + 1 + num_entries] == 0xFF; - } - - /// Checks to see if the data belongs to a Gen1 save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - internal static GameVersion GetIsG1SAV(ReadOnlySpan data) - { - if (data.Length is not (SIZE_G1RAW or SIZE_G1BAT)) - return Invalid; - - // Check if it's not an american save or a japanese save - if (!(GetIsG1SAVU(data) || GetIsG1SAVJ(data))) - return Invalid; - // I can't actually detect which game version, because it's not stored anywhere. - // If you can think of anything to do here, please implement :) - return RBY; - } - - /// Checks to see if the data belongs to an International Gen1 save - /// Save data of which to determine the region - /// True if a valid International save, False otherwise. - private static bool GetIsG1SAVU(ReadOnlySpan data) - { - return IsG12ListValid(data, 0x2F2C, 20) && IsG12ListValid(data, 0x30C0, 20); - } - - /// Checks to see if the data belongs to a Japanese Gen1 save - /// Save data of which to determine the region - /// True if a valid Japanese save, False otherwise. - internal static bool GetIsG1SAVJ(ReadOnlySpan data) - { - return IsG12ListValid(data, 0x2ED5, 30) && IsG12ListValid(data, 0x302D, 30); - } - - /// Checks to see if the data belongs to a Gen2 save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - internal static GameVersion GetIsG2SAV(ReadOnlySpan data) - { - if (!SizesGen2.Contains(data.Length)) - return Invalid; - - // Check if it's not an International, Japanese, or Korean save file - GameVersion result; - if ((result = GetIsG2SAVU(data)) != Invalid) - return result; - if ((result = GetIsG2SAVJ(data)) != Invalid) - return result; - if ((result = GetIsG2SAVK(data)) != Invalid) - return result; - return Invalid; - } - - /// Checks to see if the data belongs to an International (not Japanese or Korean) Gen2 save - /// Save data of which to determine the region - /// True if a valid International save, False otherwise. - private static GameVersion GetIsG2SAVU(ReadOnlySpan data) - { - if (IsG12ListValid(data, 0x288A, 20) && IsG12ListValid(data, 0x2D6C, 20)) - return GS; - if (IsG12ListValid(data, 0x2865, 20) && IsG12ListValid(data, 0x2D10, 20)) - return C; - return Invalid; - } - - /// Checks to see if the data belongs to a Japanese Gen2 save - /// Save data of which to determine the region - /// True if a valid Japanese save, False otherwise. - internal static GameVersion GetIsG2SAVJ(ReadOnlySpan data) - { - if (!IsG12ListValid(data, 0x2D10, 30)) - return Invalid; - if (IsG12ListValid(data, 0x283E, 30)) - return GS; - if (IsG12ListValid(data, 0x281A, 30)) - return C; - return Invalid; - } - - /// Checks to see if the data belongs to a Korean Gen2 save - /// Save data of which to determine the region - /// True if a valid Korean save, False otherwise. - internal static GameVersion GetIsG2SAVK(ReadOnlySpan data) - { - if (IsG12ListValid(data, 0x2DAE, 20) && IsG12ListValid(data, 0x28CC, 20)) - return GS; - return Invalid; - } - - /// Checks to see if the data belongs to a Gen3 save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsG3SAV(ReadOnlySpan data) - { - if (data.Length is not (SIZE_G3RAW or SIZE_G3RAWHALF)) - return Invalid; - - // check the save file(s) - int count = data.Length/SIZE_G3RAWHALF; - for (int slot = 0; slot < count; slot++) - { - if (!SAV3.IsAllMainSectorsPresent(data, slot, out var smallOffset)) - continue; - - // Detect RS/E/FRLG - return GetVersionG3SAV(data[smallOffset..]); - } - return Invalid; - } - - /// - /// Checks the input to see which game is for this file. - /// - /// Data to check - /// RS, E, or FR/LG. - private static GameVersion GetVersionG3SAV(ReadOnlySpan data) - { - // 0xAC - // RS: Battle Tower Data, which will never match the FR/LG fixed value. - // E: Encryption Key - // FR/LG @ 0xAC has a fixed value (01 00 00 00) - // RS has battle tower data (variable) - uint _0xAC = ReadUInt32LittleEndian(data[0xAC..]); - switch (_0xAC) - { - case 1: return FRLG; // fixed value - case 0: return RS; // save has no battle tower record data - default: - // RS data structure only extends 0x890 bytes; check if any data is present afterwards. - for (int i = 0x890; i < 0xF2C; i += 4) - { - if (ReadUInt64LittleEndian(data[i..]) != 0) - return E; - } - return RS; - } - } - - /// Checks to see if the data belongs to a Gen3 Box RS save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsG3BOXSAV(ReadOnlySpan data) - { - if (data.Length is not SIZE_G3BOX) - return Invalid; - - // Verify first checksum - const int offset = 0x2000; - var span = data.Slice(offset, 0x1FFC); - var actual = ReadUInt32BigEndian(span); - var chk = Checksums.CheckSum16BigInvert(span[4..]); - return chk == actual ? RSBOX : Invalid; - } - - /// Checks to see if the data belongs to a Colosseum save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsG3COLOSAV(ReadOnlySpan data) - { - if (data.Length is not SIZE_G3COLO) - return Invalid; - - // Check the intro bytes for each save slot - const int offset = 0x6000; - for (int i = 0; i < 3; i++) - { - var ofs = offset + (0x1E000 * i); - if (ReadUInt32LittleEndian(data[ofs..]) != 0x00000101) - return Invalid; - } - return COLO; - } - - /// Checks to see if the data belongs to a Gen3 XD save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsG3XDSAV(ReadOnlySpan data) - { - if (data.Length is not SIZE_G3XD) - return Invalid; - - // Check the intro bytes for each save slot - const int offset = 0x6000; - for (int i = 0; i < 2; i++) - { - var ofs = offset + (0x28000 * i); - if ((ReadUInt32LittleEndian(data[ofs..]) & 0xFFFE_FFFF) != 0x00000101) - return Invalid; - } - return XD; - } - - /// Checks to see if the data belongs to a Gen4 save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsG4SAV(ReadOnlySpan data) - { - if (data.Length != SIZE_G4RAW) - return Invalid; - - // The block footers contain a u32 'size' followed by a u32 binary-coded-decimal timestamp(?) - // Korean saves have a different timestamp from other localizations. - static bool validSequence(ReadOnlySpan data, int offset) - { - var size = ReadUInt32LittleEndian(data[(offset - 0xC)..]); - if (size != (offset & 0xFFFF)) - return false; - var sdk = ReadUInt32LittleEndian(data[(offset - 0x8)..]); - - const int DATE_INT = 0x20060623; - const int DATE_KO = 0x20070903; - return sdk is DATE_INT or DATE_KO; - } - - // Check the other save -- first save is done to the latter half of the binary. - // The second save should be all that is needed to check. - if (validSequence(data, 0x4C100)) - return DP; - if (validSequence(data, 0x4CF2C)) - return Pt; - if (validSequence(data, 0x4F628)) - return HGSS; - - return Invalid; - } - - /// Checks to see if the data belongs to a Gen4 Battle Revolution save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsG4BRSAV(ReadOnlySpan data) - { - if (data.Length != SIZE_G4BR) - return Invalid; - - byte[] sav = SAV4BR.DecryptPBRSaveData(data); - return SAV4BR.IsChecksumsValid(sav) ? BATREV : Invalid; - } - - /// Checks to see if the data belongs to a Gen5 save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsG5SAV(ReadOnlySpan data) - { - if (data.Length != SIZE_G5RAW) - return Invalid; - - // check the checksum footer block validity; nobody would normally modify this region - if (IsValidFooter(data, SIZE_G5BW, 0x8C)) - return BW; - if (IsValidFooter(data, SIZE_G5B2W2, 0x94)) - return B2W2; - return Invalid; - - static bool IsValidFooter(ReadOnlySpan data, int mainSize, int infoLength) - { - var footer = data.Slice(mainSize - 0x100, infoLength + 0x10); - ushort stored = ReadUInt16LittleEndian(footer[^2..]); - ushort actual = Checksums.CRC16_CCITT(footer[..infoLength]); - return stored == actual; - } - } - - /// Checks to see if the data belongs to a Gen6 save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsG6SAV(ReadOnlySpan data) - { - if (data.Length is not (SIZE_G6XY or SIZE_G6ORAS or SIZE_G6ORASDEMO)) - return Invalid; - - if (ReadUInt32LittleEndian(data[^0x1F0..]) != BEEF) - return Invalid; - - return data.Length switch - { - SIZE_G6XY => XY, - SIZE_G6ORAS => ORAS, - _ => ORASDEMO, // least likely - }; - } - - /// Checks to see if the data belongs to a Gen7 save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsG7SAV(ReadOnlySpan data) - { - if (data.Length is not (SIZE_G7SM or SIZE_G7USUM)) - return Invalid; - - if (ReadUInt32LittleEndian(data[^0x1F0..]) != BEEF) - return Invalid; - - return data.Length == SIZE_G7SM ? SM : USUM; - } - - /// Determines if the input data belongs to a save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsBelugaSAV(ReadOnlySpan data) - { - if (data.Length != SIZE_G7GG) - return Invalid; - - const int actualLength = 0xB8800; - if (ReadUInt32LittleEndian(data[(actualLength - 0x1F0)..]) != BEEF) // beef table start - return Invalid; - if (ReadUInt16LittleEndian(data[(actualLength - 0x200 + 0xB0)..]) != 0x13) // check a block number to double check - return Invalid; + /// Determines the type of the provided save data. + /// Save data of which to determine the origins of + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetSAVType(byte[] data) + { + GameVersion ver; + if ((ver = GetIsG1SAV(data)) != Invalid) + return ver; + if ((ver = GetIsG2SAV(data)) != Invalid) + return ver; + if ((ver = GetIsG3SAV(data)) != Invalid) + return ver; + if ((ver = GetIsG4SAV(data)) != Invalid) + return ver; + if ((ver = GetIsG5SAV(data)) != Invalid) + return ver; + if ((ver = GetIsG6SAV(data)) != Invalid) + return ver; + if ((ver = GetIsG7SAV(data)) != Invalid) + return ver; + if (GetIsBelugaSAV(data) != Invalid) return GG; - } + if (GetIsG3COLOSAV(data) != Invalid) + return COLO; + if (GetIsG3XDSAV(data) != Invalid) + return XD; + if (GetIsG3BOXSAV(data) != Invalid) + return RSBOX; + if (GetIsG4BRSAV(data) != Invalid) + return BATREV; - /// Checks to see if the data belongs to a Gen8 save - /// Save data of which to determine the type - /// Version Identifier or Invalid if type cannot be determined. - private static GameVersion GetIsG8SAV(byte[] data) + if (GetIsBank7(data)) // pokebank + return Gen7; + if (GetIsBank4(data)) // pokestock + return Gen4; + if (GetIsBank3(data)) // pokestock + return Gen3; + if (GetIsRanch4(data)) // ranch + return DPPt; + if (SAV2Stadium.IsStadium(data)) + return Stadium2; + if (SAV1Stadium.IsStadium(data)) + return Stadium; + if (SAV1StadiumJ.IsStadium(data)) + return StadiumJ; + + if ((ver = GetIsG8SAV(data)) != Invalid) + return ver; + if ((ver = GetIsG8SAV_BDSP(data)) != Invalid) + return ver; + if ((ver = GetIsG8SAV_LA(data)) != Invalid) + return ver; + + return Invalid; + } + + /// + /// Determines if a Gen1/2 Pokémon List is Invalid + /// + /// Save data + /// Offset the list starts at + /// Max count of Pokémon in the list + /// True if a valid list, False otherwise + private static bool IsG12ListValid(ReadOnlySpan data, int offset, int listCount) + { + byte num_entries = data[offset]; + return num_entries <= listCount && data[offset + 1 + num_entries] == 0xFF; + } + + /// Checks to see if the data belongs to a Gen1 save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + internal static GameVersion GetIsG1SAV(ReadOnlySpan data) + { + if (data.Length is not (SIZE_G1RAW or SIZE_G1BAT)) + return Invalid; + + // Check if it's not an american save or a japanese save + if (!(GetIsG1SAVU(data) || GetIsG1SAVJ(data))) + return Invalid; + // I can't actually detect which game version, because it's not stored anywhere. + // If you can think of anything to do here, please implement :) + return RBY; + } + + /// Checks to see if the data belongs to an International Gen1 save + /// Save data of which to determine the region + /// True if a valid International save, False otherwise. + private static bool GetIsG1SAVU(ReadOnlySpan data) + { + return IsG12ListValid(data, 0x2F2C, 20) && IsG12ListValid(data, 0x30C0, 20); + } + + /// Checks to see if the data belongs to a Japanese Gen1 save + /// Save data of which to determine the region + /// True if a valid Japanese save, False otherwise. + internal static bool GetIsG1SAVJ(ReadOnlySpan data) + { + return IsG12ListValid(data, 0x2ED5, 30) && IsG12ListValid(data, 0x302D, 30); + } + + /// Checks to see if the data belongs to a Gen2 save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + internal static GameVersion GetIsG2SAV(ReadOnlySpan data) + { + if (!SizesGen2.Contains(data.Length)) + return Invalid; + + // Check if it's not an International, Japanese, or Korean save file + GameVersion result; + if ((result = GetIsG2SAVU(data)) != Invalid) + return result; + if ((result = GetIsG2SAVJ(data)) != Invalid) + return result; + if ((result = GetIsG2SAVK(data)) != Invalid) + return result; + return Invalid; + } + + /// Checks to see if the data belongs to an International (not Japanese or Korean) Gen2 save + /// Save data of which to determine the region + /// True if a valid International save, False otherwise. + private static GameVersion GetIsG2SAVU(ReadOnlySpan data) + { + if (IsG12ListValid(data, 0x288A, 20) && IsG12ListValid(data, 0x2D6C, 20)) + return GS; + if (IsG12ListValid(data, 0x2865, 20) && IsG12ListValid(data, 0x2D10, 20)) + return C; + return Invalid; + } + + /// Checks to see if the data belongs to a Japanese Gen2 save + /// Save data of which to determine the region + /// True if a valid Japanese save, False otherwise. + internal static GameVersion GetIsG2SAVJ(ReadOnlySpan data) + { + if (!IsG12ListValid(data, 0x2D10, 30)) + return Invalid; + if (IsG12ListValid(data, 0x283E, 30)) + return GS; + if (IsG12ListValid(data, 0x281A, 30)) + return C; + return Invalid; + } + + /// Checks to see if the data belongs to a Korean Gen2 save + /// Save data of which to determine the region + /// True if a valid Korean save, False otherwise. + internal static GameVersion GetIsG2SAVK(ReadOnlySpan data) + { + if (IsG12ListValid(data, 0x2DAE, 20) && IsG12ListValid(data, 0x28CC, 20)) + return GS; + return Invalid; + } + + /// Checks to see if the data belongs to a Gen3 save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsG3SAV(ReadOnlySpan data) + { + if (data.Length is not (SIZE_G3RAW or SIZE_G3RAWHALF)) + return Invalid; + + // check the save file(s) + int count = data.Length/SIZE_G3RAWHALF; + for (int slot = 0; slot < count; slot++) { - if (!SizesSWSH.Contains(data.Length)) + if (!SAV3.IsAllMainSectorsPresent(data, slot, out var smallOffset)) + continue; + + // Detect RS/E/FRLG + return GetVersionG3SAV(data[smallOffset..]); + } + return Invalid; + } + + /// + /// Checks the input to see which game is for this file. + /// + /// Data to check + /// RS, E, or FR/LG. + private static GameVersion GetVersionG3SAV(ReadOnlySpan data) + { + // 0xAC + // RS: Battle Tower Data, which will never match the FR/LG fixed value. + // E: Encryption Key + // FR/LG @ 0xAC has a fixed value (01 00 00 00) + // RS has battle tower data (variable) + uint _0xAC = ReadUInt32LittleEndian(data[0xAC..]); + switch (_0xAC) + { + case 1: return FRLG; // fixed value + case 0: return RS; // save has no battle tower record data + default: + // RS data structure only extends 0x890 bytes; check if any data is present afterwards. + for (int i = 0x890; i < 0xF2C; i += 4) + { + if (ReadUInt64LittleEndian(data[i..]) != 0) + return E; + } + return RS; + } + } + + /// Checks to see if the data belongs to a Gen3 Box RS save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsG3BOXSAV(ReadOnlySpan data) + { + if (data.Length is not SIZE_G3BOX) + return Invalid; + + // Verify first checksum + const int offset = 0x2000; + var span = data.Slice(offset, 0x1FFC); + var actual = ReadUInt32BigEndian(span); + var chk = Checksums.CheckSum16BigInvert(span[4..]); + return chk == actual ? RSBOX : Invalid; + } + + /// Checks to see if the data belongs to a Colosseum save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsG3COLOSAV(ReadOnlySpan data) + { + if (data.Length is not SIZE_G3COLO) + return Invalid; + + // Check the intro bytes for each save slot + const int offset = 0x6000; + for (int i = 0; i < 3; i++) + { + var ofs = offset + (0x1E000 * i); + if (ReadUInt32LittleEndian(data[ofs..]) != 0x00000101) return Invalid; - - return SwishCrypto.GetIsHashValid(data) ? SWSH : Invalid; } + return COLO; + } - private static GameVersion GetIsG8SAV_BDSP(ReadOnlySpan data) + /// Checks to see if the data belongs to a Gen3 XD save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsG3XDSAV(ReadOnlySpan data) + { + if (data.Length is not SIZE_G3XD) + return Invalid; + + // Check the intro bytes for each save slot + const int offset = 0x6000; + for (int i = 0; i < 2; i++) { - if (data.Length is not (SIZE_G8BDSP or SIZE_G8BDSP_1 or SIZE_G8BDSP_2 or SIZE_G8BDSP_3)) + var ofs = offset + (0x28000 * i); + if ((ReadUInt32LittleEndian(data[ofs..]) & 0xFFFE_FFFF) != 0x00000101) return Invalid; + } + return XD; + } - var ver = (Gem8Version)ReadUInt32LittleEndian(data); - if (ver is not (Gem8Version.V1_0 or Gem8Version.V1_1 or Gem8Version.V1_2 or Gem8Version.V1_3)) - return Invalid; + /// Checks to see if the data belongs to a Gen4 save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsG4SAV(ReadOnlySpan data) + { + if (data.Length != SIZE_G4RAW) + return Invalid; - return BDSP; + // The block footers contain a u32 'size' followed by a u32 binary-coded-decimal timestamp(?) + // Korean saves have a different timestamp from other localizations. + static bool validSequence(ReadOnlySpan data, int offset) + { + var size = ReadUInt32LittleEndian(data[(offset - 0xC)..]); + if (size != (offset & 0xFFFF)) + return false; + var sdk = ReadUInt32LittleEndian(data[(offset - 0x8)..]); + + const int DATE_INT = 0x20060623; + const int DATE_KO = 0x20070903; + return sdk is DATE_INT or DATE_KO; } - private static GameVersion GetIsG8SAV_LA(byte[] data) - { - if (data.Length is not (SIZE_G8LA or SIZE_G8LA_1)) - return Invalid; + // Check the other save -- first save is done to the latter half of the binary. + // The second save should be all that is needed to check. + if (validSequence(data, 0x4C100)) + return DP; + if (validSequence(data, 0x4CF2C)) + return Pt; + if (validSequence(data, 0x4F628)) + return HGSS; - return SwishCrypto.GetIsHashValid(data) ? PLA : Invalid; + return Invalid; + } + + /// Checks to see if the data belongs to a Gen4 Battle Revolution save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsG4BRSAV(ReadOnlySpan data) + { + if (data.Length != SIZE_G4BR) + return Invalid; + + byte[] sav = SAV4BR.DecryptPBRSaveData(data); + return SAV4BR.IsChecksumsValid(sav) ? BATREV : Invalid; + } + + /// Checks to see if the data belongs to a Gen5 save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsG5SAV(ReadOnlySpan data) + { + if (data.Length != SIZE_G5RAW) + return Invalid; + + // check the checksum footer block validity; nobody would normally modify this region + if (IsValidFooter(data, SIZE_G5BW, 0x8C)) + return BW; + if (IsValidFooter(data, SIZE_G5B2W2, 0x94)) + return B2W2; + return Invalid; + + static bool IsValidFooter(ReadOnlySpan data, int mainSize, int infoLength) + { + var footer = data.Slice(mainSize - 0x100, infoLength + 0x10); + ushort stored = ReadUInt16LittleEndian(footer[^2..]); + ushort actual = Checksums.CRC16_CCITT(footer[..infoLength]); + return stored == actual; } + } - private static bool GetIsBank7(ReadOnlySpan data) => data.Length == SIZE_G7BANK && data[0] != 0; - private static bool GetIsBank4(ReadOnlySpan data) => data.Length == SIZE_G4BANK && ReadUInt32LittleEndian(data[0x3FC00..]) != 0; // box name present - private static bool GetIsBank3(ReadOnlySpan data) => data.Length == SIZE_G4BANK && ReadUInt32LittleEndian(data[0x3FC00..]) == 0; // size collision with ^ - private static bool GetIsRanchDP(ReadOnlySpan data) => data.Length == SIZE_G4RANCH && ReadUInt32BigEndian(data[0x22AC..]) != 0; - private static bool GetIsRanchPlat(ReadOnlySpan data) => data.Length == SIZE_G4RANCH_PLAT && ReadUInt32BigEndian(data[0x268C..]) != 0; - private static bool GetIsRanch4(ReadOnlySpan data) => GetIsRanchDP(data) || GetIsRanchPlat(data); + /// Checks to see if the data belongs to a Gen6 save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsG6SAV(ReadOnlySpan data) + { + if (data.Length is not (SIZE_G6XY or SIZE_G6ORAS or SIZE_G6ORASDEMO)) + return Invalid; - /// Creates an instance of a SaveFile using the given save data. - /// File location from which to create a SaveFile. - /// An appropriate type of save file for the given data, or null if the save data is invalid. - public static SaveFile? GetVariantSAV(string path) + if (ReadUInt32LittleEndian(data[^0x1F0..]) != BEEF) + return Invalid; + + return data.Length switch { - // Many things can go wrong with loading save data (file no longer present toc-tou, or bad save layout). - try - { - var data = File.ReadAllBytes(path); - var sav = GetVariantSAV(data, path); - sav?.Metadata.SetExtraInfo(path); - return sav; - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine(ex); - return null; - } + SIZE_G6XY => XY, + SIZE_G6ORAS => ORAS, + _ => ORASDEMO, // least likely + }; + } + + /// Checks to see if the data belongs to a Gen7 save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsG7SAV(ReadOnlySpan data) + { + if (data.Length is not (SIZE_G7SM or SIZE_G7USUM)) + return Invalid; + + if (ReadUInt32LittleEndian(data[^0x1F0..]) != BEEF) + return Invalid; + + return data.Length == SIZE_G7SM ? SM : USUM; + } + + /// Determines if the input data belongs to a save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsBelugaSAV(ReadOnlySpan data) + { + if (data.Length != SIZE_G7GG) + return Invalid; + + const int actualLength = 0xB8800; + if (ReadUInt32LittleEndian(data[(actualLength - 0x1F0)..]) != BEEF) // beef table start + return Invalid; + if (ReadUInt16LittleEndian(data[(actualLength - 0x200 + 0xB0)..]) != 0x13) // check a block number to double check + return Invalid; + + return GG; + } + + /// Checks to see if the data belongs to a Gen8 save + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + private static GameVersion GetIsG8SAV(byte[] data) + { + if (!SizesSWSH.Contains(data.Length)) + return Invalid; + + return SwishCrypto.GetIsHashValid(data) ? SWSH : Invalid; + } + + private static GameVersion GetIsG8SAV_BDSP(ReadOnlySpan data) + { + if (data.Length is not (SIZE_G8BDSP or SIZE_G8BDSP_1 or SIZE_G8BDSP_2 or SIZE_G8BDSP_3)) + return Invalid; + + var ver = (Gem8Version)ReadUInt32LittleEndian(data); + if (ver is not (Gem8Version.V1_0 or Gem8Version.V1_1 or Gem8Version.V1_2 or Gem8Version.V1_3)) + return Invalid; + + return BDSP; + } + + private static GameVersion GetIsG8SAV_LA(byte[] data) + { + if (data.Length is not (SIZE_G8LA or SIZE_G8LA_1)) + return Invalid; + + return SwishCrypto.GetIsHashValid(data) ? PLA : Invalid; + } + + private static bool GetIsBank7(ReadOnlySpan data) => data.Length == SIZE_G7BANK && data[0] != 0; + private static bool GetIsBank4(ReadOnlySpan data) => data.Length == SIZE_G4BANK && ReadUInt32LittleEndian(data[0x3FC00..]) != 0; // box name present + private static bool GetIsBank3(ReadOnlySpan data) => data.Length == SIZE_G4BANK && ReadUInt32LittleEndian(data[0x3FC00..]) == 0; // size collision with ^ + private static bool GetIsRanchDP(ReadOnlySpan data) => data.Length == SIZE_G4RANCH && ReadUInt32BigEndian(data[0x22AC..]) != 0; + private static bool GetIsRanchPlat(ReadOnlySpan data) => data.Length == SIZE_G4RANCH_PLAT && ReadUInt32BigEndian(data[0x268C..]) != 0; + private static bool GetIsRanch4(ReadOnlySpan data) => GetIsRanchDP(data) || GetIsRanchPlat(data); + + /// Creates an instance of a SaveFile using the given save data. + /// File location from which to create a SaveFile. + /// An appropriate type of save file for the given data, or null if the save data is invalid. + public static SaveFile? GetVariantSAV(string path) + { + // Many things can go wrong with loading save data (file no longer present toc-tou, or bad save layout). + try + { + var data = File.ReadAllBytes(path); + var sav = GetVariantSAV(data, path); + sav?.Metadata.SetExtraInfo(path); + return sav; } - - /// Creates an instance of a SaveFile using the given save data. - /// Save data from which to create a SaveFile. - /// Optional save file path, may help initialize a non-standard save file format. - /// An appropriate type of save file for the given data, or null if the save data is invalid. - public static SaveFile? GetVariantSAV(byte[] data, string? path = null) + catch (Exception ex) { -#if !EXCLUDE_HACKS - foreach (var h in CustomSaveReaders) - { - if (!h.IsRecognized(data.Length)) - continue; - - var custom = h.ReadSaveFile(data, path); - if (custom != null) - return custom; - } -#endif - - var sav = GetVariantSAVInternal(data); - if (sav != null) - return sav; - -#if !EXCLUDE_EMULATOR_FORMATS - foreach (var h in Handlers) - { - if (!h.IsRecognized(data.Length)) - continue; - - var split = h.TrySplit(data); - if (split == null) - continue; - - sav = GetVariantSAVInternal(split.Data); - if (sav == null) - continue; - - var meta = sav.Metadata; - meta.SetExtraInfo(split.Header, split.Footer); - if (path is not null) - meta.SetExtraInfo(path); - return sav; - } -#endif - - // unrecognized. + System.Diagnostics.Debug.WriteLine(ex); return null; } + } - private static SaveFile? GetVariantSAVInternal(byte[] data) + /// Creates an instance of a SaveFile using the given save data. + /// Save data from which to create a SaveFile. + /// Optional save file path, may help initialize a non-standard save file format. + /// An appropriate type of save file for the given data, or null if the save data is invalid. + public static SaveFile? GetVariantSAV(byte[] data, string? path = null) + { +#if !EXCLUDE_HACKS + foreach (var h in CustomSaveReaders) { - var type = GetSAVType(data); - return type switch - { - // Main Games - RBY => new SAV1(data, type), - GS or C => new SAV2(data, type), + if (!h.IsRecognized(data.Length)) + continue; - RS => new SAV3RS(data), - E => new SAV3E(data), - FRLG => new SAV3FRLG(data), - - DP => new SAV4DP(data), - Pt => new SAV4Pt(data), - HGSS => new SAV4HGSS(data), - - BW => new SAV5BW(data), - B2W2 => new SAV5B2W2(data), - - XY => new SAV6XY(data), - ORAS => new SAV6AO(data), - ORASDEMO => new SAV6AODemo(data), - - SM => new SAV7SM(data), - USUM => new SAV7USUM(data), - GG => new SAV7b(data), - - SWSH => new SAV8SWSH(data), - BDSP => new SAV8BS(data), - PLA => new SAV8LA(data), - - // Side Games - COLO => new SAV3Colosseum(data), - XD => new SAV3XD(data), - RSBOX => new SAV3RSBox(data), - BATREV => new SAV4BR(data), - Stadium2 => new SAV2Stadium(data), - Stadium => new SAV1Stadium(data), - StadiumJ => new SAV1StadiumJ(data), - - // Bulk Storage - Gen3 => new Bank3(data), - DPPt => new SAV4Ranch(data), - Gen4 => new Bank4(data), - Gen7 => Bank7.GetBank7(data), - - // No pattern matched - _ => null, - }; + var custom = h.ReadSaveFile(data, path); + if (custom != null) + return custom; } +#endif - public static SaveFile? GetVariantSAV(SAV3GCMemoryCard memCard) + var sav = GetVariantSAVInternal(data); + if (sav != null) + return sav; + +#if !EXCLUDE_EMULATOR_FORMATS + foreach (var h in Handlers) { - // Pre-check for header/footer signatures - byte[] data = memCard.ReadSaveGameData(); - if (data.Length == 0) - return null; + if (!h.IsRecognized(data.Length)) + continue; - var split = DolphinHandler.TrySplit(data); - if (split != null) - data = split.Data; + var split = h.TrySplit(data); + if (split == null) + continue; - SaveFile sav; - switch (memCard.SelectedGameVersion) - { - // Side Games - case COLO: sav = new SAV3Colosseum(data) { MemoryCard = memCard }; break; - case XD: sav = new SAV3XD(data) { MemoryCard = memCard }; break; - case RSBOX: sav = new SAV3RSBox(data, memCard) { MemoryCard = memCard }; break; + sav = GetVariantSAVInternal(split.Data); + if (sav == null) + continue; - // No pattern matched - default: return null; - } - - if (split != null) - sav.Metadata.SetExtraInfo(split.Header, split.Footer); + var meta = sav.Metadata; + meta.SetExtraInfo(split.Header, split.Footer); + if (path is not null) + meta.SetExtraInfo(path); return sav; } +#endif - /// - /// Returns a that feels best for the save file's language. - /// - public static LanguageID GetSafeLanguage(SaveFile? sav) => sav switch + // unrecognized. + return null; + } + + private static SaveFile? GetVariantSAVInternal(byte[] data) + { + var type = GetSAVType(data); + return type switch { - null => LanguageID.English, - ILangDeviantSave s => s.Japanese ? LanguageID.Japanese : s.Korean ? LanguageID.Korean : LanguageID.English, - _ => (uint)sav.Language <= Legal.GetMaxLanguageID(sav.Generation) ? (LanguageID)sav.Language : LanguageID.English, + // Main Games + RBY => new SAV1(data, type), + GS or C => new SAV2(data, type), + + RS => new SAV3RS(data), + E => new SAV3E(data), + FRLG => new SAV3FRLG(data), + + DP => new SAV4DP(data), + Pt => new SAV4Pt(data), + HGSS => new SAV4HGSS(data), + + BW => new SAV5BW(data), + B2W2 => new SAV5B2W2(data), + + XY => new SAV6XY(data), + ORAS => new SAV6AO(data), + ORASDEMO => new SAV6AODemo(data), + + SM => new SAV7SM(data), + USUM => new SAV7USUM(data), + GG => new SAV7b(data), + + SWSH => new SAV8SWSH(data), + BDSP => new SAV8BS(data), + PLA => new SAV8LA(data), + + // Side Games + COLO => new SAV3Colosseum(data), + XD => new SAV3XD(data), + RSBOX => new SAV3RSBox(data), + BATREV => new SAV4BR(data), + Stadium2 => new SAV2Stadium(data), + Stadium => new SAV1Stadium(data), + StadiumJ => new SAV1StadiumJ(data), + + // Bulk Storage + Gen3 => new Bank3(data), + DPPt => new SAV4Ranch(data), + Gen4 => new Bank4(data), + Gen7 => Bank7.GetBank7(data), + + // No pattern matched + _ => null, }; + } - /// - /// Returns a Trainer Name that feels best for the save file's language. - /// - public static string GetSafeTrainerName(SaveFile? sav, LanguageID lang) => lang switch + public static SaveFile? GetVariantSAV(SAV3GCMemoryCard memCard) + { + // Pre-check for header/footer signatures + byte[] data = memCard.ReadSaveGameData(); + if (data.Length == 0) + return null; + + var split = DolphinHandler.TrySplit(data); + if (split != null) + data = split.Data; + + SaveFile sav; + switch (memCard.SelectedGameVersion) { - LanguageID.Japanese => sav?.Generation >= 3 ? "PKHeX" : "1337", - _ => "PKHeX", - }; + // Side Games + case COLO: sav = new SAV3Colosseum(data) { MemoryCard = memCard }; break; + case XD: sav = new SAV3XD(data) { MemoryCard = memCard }; break; + case RSBOX: sav = new SAV3RSBox(data, memCard) { MemoryCard = memCard }; break; - /// - /// Creates an instance of a SaveFile with a blank base. - /// - /// Version to create the save file for. - /// Trainer Name - /// Language to initialize with - /// Blank save file from the requested game, null if no game exists for that . - public static SaveFile GetBlankSAV(GameVersion game, string trainerName, LanguageID language = LanguageID.English) - { - var sav = GetBlankSAV(game, language); - sav.Game = (int)game; - sav.OT = trainerName; - if (sav.Generation >= 4) - sav.Language = (int)language; + // No pattern matched + default: return null; + } - // Secondary Properties may not be used but can be filled in as template. - if (sav.Generation >= 7) - { - sav.TrainerID7 = 123456; - sav.TrainerSID7 = 1234; - } - else - { - sav.TID = 12345; - sav.SID = 54321; - } + if (split != null) + sav.Metadata.SetExtraInfo(split.Header, split.Footer); + return sav; + } + + /// + /// Returns a that feels best for the save file's language. + /// + public static LanguageID GetSafeLanguage(SaveFile? sav) => sav switch + { + null => LanguageID.English, + ILangDeviantSave s => s.Japanese ? LanguageID.Japanese : s.Korean ? LanguageID.Korean : LanguageID.English, + _ => (uint)sav.Language <= Legal.GetMaxLanguageID(sav.Generation) ? (LanguageID)sav.Language : LanguageID.English, + }; + + /// + /// Returns a Trainer Name that feels best for the save file's language. + /// + public static string GetSafeTrainerName(SaveFile? sav, LanguageID lang) => lang switch + { + LanguageID.Japanese => sav?.Generation >= 3 ? "PKHeX" : "1337", + _ => "PKHeX", + }; + + /// + /// Creates an instance of a SaveFile with a blank base. + /// + /// Version to create the save file for. + /// Trainer Name + /// Language to initialize with + /// Blank save file from the requested game, null if no game exists for that . + public static SaveFile GetBlankSAV(GameVersion game, string trainerName, LanguageID language = LanguageID.English) + { + var sav = GetBlankSAV(game, language); + sav.Game = (int)game; + sav.OT = trainerName; + if (sav.Generation >= 4) sav.Language = (int)language; - // Only set geolocation data for 3DS titles - if (sav is IRegionOrigin o) - o.SetDefaultRegionOrigins(); - - return sav; - } - - /// - /// Creates an instance of a SaveFile with a blank base. - /// - /// Version to create the save file for. - /// Save file language to initialize for - /// Blank save file from the requested game, null if no game exists for that . - private static SaveFile GetBlankSAV(GameVersion game, LanguageID language) => game switch + // Secondary Properties may not be used but can be filled in as template. + if (sav.Generation >= 7) { - RD or BU or GN or YW or RBY => new SAV1(version: game, japanese: language == LanguageID.Japanese || game == BU), - StadiumJ => new SAV1StadiumJ(), - Stadium => new SAV1Stadium(language == LanguageID.Japanese), - - GD or SI or GS => new SAV2(version: GS, lang: language), - C or GSC => new SAV2(version: C, lang: language), - Stadium2 => new SAV2Stadium(language == LanguageID.Japanese), - - R or S or RS => new SAV3RS(language == LanguageID.Japanese), - E or RSE => new SAV3E(language == LanguageID.Japanese), - FR or LG or FRLG => new SAV3FRLG(language == LanguageID.Japanese), - - CXD or COLO => new SAV3Colosseum(), - XD => new SAV3XD(), - RSBOX => new SAV3RSBox(), - - D or P or DP => new SAV4DP(), - Pt or DPPt => new SAV4Pt(), - HG or SS or HGSS => new SAV4HGSS(), - BATREV => new SAV4BR(), - - B or W or BW => new SAV5BW(), - B2 or W2 or B2W2 => new SAV5B2W2(), - - X or Y or XY => new SAV6XY(), - ORASDEMO => new SAV6AODemo(), - OR or AS or ORAS => new SAV6AO(), - - SN or MN or SM => new SAV7SM(), - US or UM or USUM => new SAV7USUM(), - GP or GE or GG or GO => new SAV7b(), - - SW or SH or SWSH => new SAV8SWSH(), - BD or SP or BDSP => new SAV8BS(), - PLA => new SAV8LA(), - - _ => throw new ArgumentOutOfRangeException(nameof(game)), - }; - - /// - /// Creates an instance of a SaveFile with a blank base. - /// - /// Context of the Save File. - /// Trainer Name - /// Save file language to initialize for - /// Save File for that generation. - public static SaveFile GetBlankSAV(EntityContext context, string trainerName, LanguageID language = LanguageID.English) - { - var ver = context.GetSingleGameVersion(); - return GetBlankSAV(ver, trainerName, language); + sav.TrainerID7 = 123456; + sav.TrainerSID7 = 1234; } - - /// - /// Retrieves possible save file paths from the provided . - /// - /// Folder to look within - /// Search all subfolders - /// If this function returns true, full path of all that match criteria. If this function returns false, the error message, or null if the directory could not be found - /// Option to ignore files with backup names and extensions - /// Boolean indicating whether or not operation was successful. - public static bool GetSavesFromFolder(string folderPath, bool deep, out IEnumerable result, bool ignoreBackups = true) + else { - if (!Directory.Exists(folderPath)) - { - result = Array.Empty(); - return false; - } - try - { - var searchOption = deep ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; - // force evaluation so that an invalid path will throw before we return true/false. - // EnumerateFiles throws an exception while iterating, which won't be caught by the try-catch here. - var files = Directory.GetFiles(folderPath, "*", searchOption); - result = files.Where(f => !(ignoreBackups && IsBackup(f)) && IsSizeValid(FileUtil.GetFileSize(f))); - return true; - } - catch (Exception ex) - { - result = new[] - { - MsgFileLoadFailAuto + Environment.NewLine + folderPath, - MsgFileLoadFailAutoAdvise + Environment.NewLine + MsgFileLoadFailAutoCause, - ex.Message, - }; - return false; - } + sav.TID = 12345; + sav.SID = 54321; } + sav.Language = (int)language; - public static bool IsBackup(string path) => Path.GetFileNameWithoutExtension(path).Equals("backup", StringComparison.InvariantCultureIgnoreCase) || Path.GetExtension(path) is ".bak"; + // Only set geolocation data for 3DS titles + if (sav is IRegionOrigin o) + o.SetDefaultRegionOrigins(); - /// - /// Determines whether the save data size is valid for automatically detecting saves. - /// - /// Size in bytes of the save data - /// A boolean indicating whether or not the save data size is valid. - public static bool IsSizeValid(int size) => Sizes.Contains(size) || Handlers.Any(z => z.IsRecognized(size)); + return sav; } + + /// + /// Creates an instance of a SaveFile with a blank base. + /// + /// Version to create the save file for. + /// Save file language to initialize for + /// Blank save file from the requested game, null if no game exists for that . + private static SaveFile GetBlankSAV(GameVersion game, LanguageID language) => game switch + { + RD or BU or GN or YW or RBY => new SAV1(version: game, japanese: language == LanguageID.Japanese || game == BU), + StadiumJ => new SAV1StadiumJ(), + Stadium => new SAV1Stadium(language == LanguageID.Japanese), + + GD or SI or GS => new SAV2(version: GS, lang: language), + C or GSC => new SAV2(version: C, lang: language), + Stadium2 => new SAV2Stadium(language == LanguageID.Japanese), + + R or S or RS => new SAV3RS(language == LanguageID.Japanese), + E or RSE => new SAV3E(language == LanguageID.Japanese), + FR or LG or FRLG => new SAV3FRLG(language == LanguageID.Japanese), + + CXD or COLO => new SAV3Colosseum(), + XD => new SAV3XD(), + RSBOX => new SAV3RSBox(), + + D or P or DP => new SAV4DP(), + Pt or DPPt => new SAV4Pt(), + HG or SS or HGSS => new SAV4HGSS(), + BATREV => new SAV4BR(), + + B or W or BW => new SAV5BW(), + B2 or W2 or B2W2 => new SAV5B2W2(), + + X or Y or XY => new SAV6XY(), + ORASDEMO => new SAV6AODemo(), + OR or AS or ORAS => new SAV6AO(), + + SN or MN or SM => new SAV7SM(), + US or UM or USUM => new SAV7USUM(), + GP or GE or GG or GO => new SAV7b(), + + SW or SH or SWSH => new SAV8SWSH(), + BD or SP or BDSP => new SAV8BS(), + PLA => new SAV8LA(), + + _ => throw new ArgumentOutOfRangeException(nameof(game)), + }; + + /// + /// Creates an instance of a SaveFile with a blank base. + /// + /// Context of the Save File. + /// Trainer Name + /// Save file language to initialize for + /// Save File for that generation. + public static SaveFile GetBlankSAV(EntityContext context, string trainerName, LanguageID language = LanguageID.English) + { + var ver = context.GetSingleGameVersion(); + return GetBlankSAV(ver, trainerName, language); + } + + /// + /// Retrieves possible save file paths from the provided . + /// + /// Folder to look within + /// Search all subfolders + /// If this function returns true, full path of all that match criteria. If this function returns false, the error message, or null if the directory could not be found + /// Option to ignore files with backup names and extensions + /// Boolean indicating whether or not operation was successful. + public static bool GetSavesFromFolder(string folderPath, bool deep, out IEnumerable result, bool ignoreBackups = true) + { + if (!Directory.Exists(folderPath)) + { + result = Array.Empty(); + return false; + } + try + { + var searchOption = deep ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; + // force evaluation so that an invalid path will throw before we return true/false. + // EnumerateFiles throws an exception while iterating, which won't be caught by the try-catch here. + var files = Directory.GetFiles(folderPath, "*", searchOption); + result = files.Where(f => !(ignoreBackups && IsBackup(f)) && IsSizeValid(FileUtil.GetFileSize(f))); + return true; + } + catch (Exception ex) + { + result = new[] + { + MsgFileLoadFailAuto + Environment.NewLine + folderPath, + MsgFileLoadFailAutoAdvise + Environment.NewLine + MsgFileLoadFailAutoCause, + ex.Message, + }; + return false; + } + } + + public static bool IsBackup(string path) => Path.GetFileNameWithoutExtension(path).Equals("backup", StringComparison.OrdinalIgnoreCase) || Path.GetExtension(path) is ".bak"; + + /// + /// Determines whether the save data size is valid for automatically detecting saves. + /// + /// Size in bytes of the save data + /// A boolean indicating whether or not the save data size is valid. + public static bool IsSizeValid(int size) => Sizes.Contains(size) || Handlers.Any(z => z.IsRecognized(size)); } diff --git a/PKHeX.Core/Util/ArrayUtil.cs b/PKHeX.Core/Util/ArrayUtil.cs index 868253dbc..0716f3556 100644 --- a/PKHeX.Core/Util/ArrayUtil.cs +++ b/PKHeX.Core/Util/ArrayUtil.cs @@ -1,202 +1,201 @@ -using System; +using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Array reusable logic +/// +public static class ArrayUtil { - /// - /// Array reusable logic - /// - public static class ArrayUtil + public static bool IsRangeEmpty(this ReadOnlySpan data, byte value = 0) { - public static bool IsRangeEmpty(this ReadOnlySpan data, byte value = 0) + for (int i = data.Length - 1; i >= 0; i--) { - for (int i = data.Length - 1; i >= 0; i--) - { - if (data[i] != value) - return false; - } - return true; + if (data[i] != value) + return false; } + return true; + } - public static int Count(this Span data, T value) where T : IEquatable + public static int Count(this Span data, T value) where T : IEquatable + { + return ((ReadOnlySpan)data).Count(value); + } + + public static T Find(this Span data, Func value) where T : unmanaged + { + foreach (var x in data) { - return ((ReadOnlySpan)data).Count(value); + if (value(x)) + return x; } + return default; + } - public static T Find(this Span data, Func value) where T : unmanaged + public static int Count(this ReadOnlySpan data, T value) where T : IEquatable + { + int count = 0; + for (int i = data.Length - 1; i >= 0; i--) { - foreach (var x in data) - { - if (value(x)) - return x; - } - return default; + if (data[i].Equals(value)) + count++; } + return count; + } - public static int Count(this ReadOnlySpan data, T value) where T : IEquatable + public static byte[] Slice(this byte[] src, int offset, int length) => src.AsSpan(offset, length).ToArray(); + public static byte[] SliceEnd(this byte[] src, int offset) => src.AsSpan(offset).ToArray(); + public static T[] Slice(this T[] src, int offset, int length) => src.AsSpan(offset, length).ToArray(); + public static T[] SliceEnd(this T[] src, int offset) => src.AsSpan(offset).ToArray(); + + public static bool WithinRange(int value, int min, int max) => min <= value && value < max; + + public static T[][] Split(this ReadOnlySpan data, int size) + { + var result = new T[data.Length / size][]; + for (int i = 0; i < data.Length; i += size) + result[i / size] = data.Slice(i, size).ToArray(); + return result; + } + + public static IEnumerable EnumerateSplit(T[] bin, int size, int start = 0) + { + for (int i = start; i < bin.Length; i += size) + yield return bin.Slice(i, size); + } + + public static IEnumerable EnumerateSplit(T[] bin, int size, int start, int end) + { + if (end < 0) + end = bin.Length; + for (int i = start; i < end; i += size) + yield return bin.Slice(i, size); + } + + public static bool[] GitBitFlagArray(ReadOnlySpan data, int count) + { + bool[] result = new bool[count]; + for (int i = 0; i < result.Length; i++) + result[i] = ((data[i >> 3] >> (i & 7)) & 0x1) == 1; + return result; + } + + public static void SetBitFlagArray(Span data, bool[] value) + { + for (int i = 0; i < value.Length; i++) { - int count = 0; - for (int i = data.Length - 1; i >= 0; i--) - { - if (data[i].Equals(value)) - count++; - } - return count; - } - - public static byte[] Slice(this byte[] src, int offset, int length) => src.AsSpan(offset, length).ToArray(); - public static byte[] SliceEnd(this byte[] src, int offset) => src.AsSpan(offset).ToArray(); - public static T[] Slice(this T[] src, int offset, int length) => src.AsSpan(offset, length).ToArray(); - public static T[] SliceEnd(this T[] src, int offset) => src.AsSpan(offset).ToArray(); - - public static bool WithinRange(int value, int min, int max) => min <= value && value < max; - - public static T[][] Split(this ReadOnlySpan data, int size) - { - var result = new T[data.Length / size][]; - for (int i = 0; i < data.Length; i += size) - result[i / size] = data.Slice(i, size).ToArray(); - return result; - } - - public static IEnumerable EnumerateSplit(T[] bin, int size, int start = 0) - { - for (int i = start; i < bin.Length; i += size) - yield return bin.Slice(i, size); - } - - public static IEnumerable EnumerateSplit(T[] bin, int size, int start, int end) - { - if (end < 0) - end = bin.Length; - for (int i = start; i < end; i += size) - yield return bin.Slice(i, size); - } - - public static bool[] GitBitFlagArray(ReadOnlySpan data, int count) - { - bool[] result = new bool[count]; - for (int i = 0; i < result.Length; i++) - result[i] = (data[i >> 3] >> (i & 7) & 0x1) == 1; - return result; - } - - public static void SetBitFlagArray(Span data, bool[] value) - { - for (int i = 0; i < value.Length; i++) - { - var ofs = i >> 3; - var mask = (1 << (i & 7)); - if (value[i]) - data[ofs] |= (byte)mask; - else - data[ofs] &= (byte)~mask; - } - } - - /// - /// Copies a list to the destination list, with an option to copy to a starting point. - /// - /// Source list to copy from - /// Destination list/array - /// Criteria for skipping a slot - /// Starting point to copy to - /// Count of copied. - public static int CopyTo(this IEnumerable list, IList dest, Func skip, int start = 0) - { - int ctr = start; - int skipped = 0; - foreach (var z in list) - { - // seek forward to next open slot - int next = FindNextValidIndex(dest, skip, ctr); - if (next == -1) - break; - skipped += next - ctr; - ctr = next; - dest[ctr++] = z; - } - return ctr - start - skipped; - } - - public static int FindNextValidIndex(IList dest, Func skip, int ctr) - { - while (true) - { - if ((uint)ctr >= dest.Count) - return -1; - var exist = dest[ctr]; - if (exist == null || !skip(ctr)) - return ctr; - ctr++; - } - } - - /// - /// Copies an list to the destination list, with an option to copy to a starting point. - /// - /// Typed object to copy - /// Source list to copy from - /// Destination list/array - /// Starting point to copy to - /// Count of copied. - public static int CopyTo(this IEnumerable list, IList dest, int start = 0) - { - int ctr = start; - foreach (var z in list) - { - if ((uint)ctr >= dest.Count) - break; - dest[ctr++] = z; - } - return ctr - start; - } - - internal static T[] ConcatAll(params T[][] arr) - { - int len = 0; - foreach (var a in arr) - len += a.Length; - - var result = new T[len]; - - int ctr = 0; - foreach (var a in arr) - { - a.CopyTo(result, ctr); - ctr += a.Length; - } - - return result; - } - - internal static T[] ConcatAll(T[] arr1, T[] arr2) - { - int len = arr1.Length + arr2.Length; - var result = new T[len]; - arr1.CopyTo(result, 0); - arr2.CopyTo(result, arr1.Length); - return result; - } - - internal static T[] ConcatAll(T[] arr1, T[] arr2, T[] arr3) - { - int len = arr1.Length + arr2.Length + arr3.Length; - var result = new T[len]; - arr1.CopyTo(result, 0); - arr2.CopyTo(result, arr1.Length); - arr3.CopyTo(result, arr1.Length + arr2.Length); - return result; - } - - internal static T[] ConcatAll(T[] arr1, T[] arr2, ReadOnlySpan arr3) - { - int len = arr1.Length + arr2.Length + arr3.Length; - var result = new T[len]; - arr1.CopyTo(result, 0); - arr2.CopyTo(result, arr1.Length); - arr3.CopyTo(result.AsSpan(arr1.Length + arr2.Length)); - return result; + var ofs = i >> 3; + var mask = (1 << (i & 7)); + if (value[i]) + data[ofs] |= (byte)mask; + else + data[ofs] &= (byte)~mask; } } + + /// + /// Copies a list to the destination list, with an option to copy to a starting point. + /// + /// Source list to copy from + /// Destination list/array + /// Criteria for skipping a slot + /// Starting point to copy to + /// Count of copied. + public static int CopyTo(this IEnumerable list, IList dest, Func skip, int start = 0) + { + int ctr = start; + int skipped = 0; + foreach (var z in list) + { + // seek forward to next open slot + int next = FindNextValidIndex(dest, skip, ctr); + if (next == -1) + break; + skipped += next - ctr; + ctr = next; + dest[ctr++] = z; + } + return ctr - start - skipped; + } + + public static int FindNextValidIndex(IList dest, Func skip, int ctr) + { + while (true) + { + if ((uint)ctr >= dest.Count) + return -1; + var exist = dest[ctr]; + if (exist == null || !skip(ctr)) + return ctr; + ctr++; + } + } + + /// + /// Copies an list to the destination list, with an option to copy to a starting point. + /// + /// Typed object to copy + /// Source list to copy from + /// Destination list/array + /// Starting point to copy to + /// Count of copied. + public static int CopyTo(this IEnumerable list, IList dest, int start = 0) + { + int ctr = start; + foreach (var z in list) + { + if ((uint)ctr >= dest.Count) + break; + dest[ctr++] = z; + } + return ctr - start; + } + + internal static T[] ConcatAll(params T[][] arr) + { + int len = 0; + foreach (var a in arr) + len += a.Length; + + var result = new T[len]; + + int ctr = 0; + foreach (var a in arr) + { + a.CopyTo(result, ctr); + ctr += a.Length; + } + + return result; + } + + internal static T[] ConcatAll(T[] arr1, T[] arr2) + { + int len = arr1.Length + arr2.Length; + var result = new T[len]; + arr1.CopyTo(result, 0); + arr2.CopyTo(result, arr1.Length); + return result; + } + + internal static T[] ConcatAll(T[] arr1, T[] arr2, T[] arr3) + { + int len = arr1.Length + arr2.Length + arr3.Length; + var result = new T[len]; + arr1.CopyTo(result, 0); + arr2.CopyTo(result, arr1.Length); + arr3.CopyTo(result, arr1.Length + arr2.Length); + return result; + } + + internal static T[] ConcatAll(T[] arr1, T[] arr2, ReadOnlySpan arr3) + { + int len = arr1.Length + arr2.Length + arr3.Length; + var result = new T[len]; + arr1.CopyTo(result, 0); + arr2.CopyTo(result, arr1.Length); + arr3.CopyTo(result.AsSpan(arr1.Length + arr2.Length)); + return result; + } } diff --git a/PKHeX.Core/Util/BinaryCodedDecimal.cs b/PKHeX.Core/Util/BinaryCodedDecimal.cs index 029e75207..20e6b6374 100644 --- a/PKHeX.Core/Util/BinaryCodedDecimal.cs +++ b/PKHeX.Core/Util/BinaryCodedDecimal.cs @@ -1,53 +1,52 @@ using System; using System.Runtime.CompilerServices; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// 4-bit decimal encoding used by some Generation 1 save file values. +/// +public static class BinaryCodedDecimal { /// - /// 4-bit decimal encoding used by some Generation 1 save file values. + /// Returns a 32-bit signed integer converted from bytes in a Binary Coded Decimal format byte array. /// - public static class BinaryCodedDecimal + /// Input byte array to read from. + public static int ToInt32BE(ReadOnlySpan input) { - /// - /// Returns a 32-bit signed integer converted from bytes in a Binary Coded Decimal format byte array. - /// - /// Input byte array to read from. - public static int ToInt32BE(ReadOnlySpan input) - { - int result = 0; - foreach (var b in input) - PushDigits(ref result, b); - return result; - } + int result = 0; + foreach (var b in input) + PushDigits(ref result, b); + return result; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void PushDigits(ref int result, byte b) => result = (result * 100) + (10 * (b >> 4)) + (b & 0xf); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void PushDigits(ref int result, byte b) => result = (result * 100) + (10 * (b >> 4)) + (b & 0xf); - /// - /// Writes the to the buffer. - /// - public static void WriteBytesBE(Span data, int value) - { - for (int i = data.Length - 1; i >= 0; i--, value /= 100) - data[i] = (byte)((((value / 10) % 10) << 4) | (value % 10)); - } + /// + /// Writes the to the buffer. + /// + public static void WriteBytesBE(Span data, int value) + { + for (int i = data.Length - 1; i >= 0; i--, value /= 100) + data[i] = (byte)((((value / 10) % 10) << 4) | (value % 10)); + } - /// - public static int ToInt32LE(ReadOnlySpan input) - { - int result = 0; - for (int i = input.Length - 1; i >= 0; i--) - PushDigits(ref result, input[i]); - return result; - } + /// + public static int ToInt32LE(ReadOnlySpan input) + { + int result = 0; + for (int i = input.Length - 1; i >= 0; i--) + PushDigits(ref result, input[i]); + return result; + } - /// - /// Writes the to the buffer. - /// - public static void WriteBytesLE(Span data, int value) - { - for (int i = 0; i < data.Length; i++, value /= 100) - data[i] = (byte)((((value / 10) % 10) << 4) | (value % 10)); - } + /// + /// Writes the to the buffer. + /// + public static void WriteBytesLE(Span data, int value) + { + for (int i = 0; i < data.Length; i++, value /= 100) + data[i] = (byte)((((value / 10) % 10) << 4) | (value % 10)); } } diff --git a/PKHeX.Core/Util/ComboItemUtil.cs b/PKHeX.Core/Util/ComboItemUtil.cs index 3a9757d50..37a5e9a9d 100644 --- a/PKHeX.Core/Util/ComboItemUtil.cs +++ b/PKHeX.Core/Util/ComboItemUtil.cs @@ -1,130 +1,129 @@ using System; using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Util { - public static partial class Util + public static List GetCountryRegionList(string textFile, string lang) { - public static List GetCountryRegionList(string textFile, string lang) + string[] inputCSV = GetStringList(textFile); + int index = GeoLocation.GetLanguageIndex(lang); + var list = GetCBListFromCSV(inputCSV, index + 1); + list.Sort(Comparer); + return list; + } + + private static List GetCBListFromCSV(IReadOnlyList inputCSV, int index) + { + var arr = new List(inputCSV.Count); + foreach (var line in inputCSV) { - string[] inputCSV = GetStringList(textFile); - int index = GeoLocation.GetLanguageIndex(lang); - var list = GetCBListFromCSV(inputCSV, index + 1); - list.Sort(Comparer); - return list; + var text = StringUtil.GetNthEntry(line.AsSpan(), index, 4); + var value = line.AsSpan(0, 3); + var item = new ComboItem(text, ToInt32(value)); + arr.Add(item); + } + return arr; + } + + public static List GetCBList(ReadOnlySpan inStrings) + { + var list = new List(inStrings.Length); + for (int i = 0; i < inStrings.Length; i++) + list.Add(new ComboItem(inStrings[i], i)); + list.Sort(Comparer); + return list; + } + + public static List GetCBList(IReadOnlyList inStrings, IReadOnlyList allowed) + { + var list = new List(allowed.Count + 1) { new(inStrings[0], 0) }; + foreach (var index in allowed) + list.Add(new ComboItem(inStrings[index], index)); + list.Sort(Comparer); + return list; + } + + public static List GetCBList(IReadOnlyList inStrings, int index, int offset = 0) + { + var list = new List(); + AddCBWithOffset(list, inStrings, offset, index); + return list; + } + + public static IReadOnlyList GetUnsortedCBList(IReadOnlyList inStrings, ReadOnlySpan allowed) + { + var count = allowed.Length; + var list = new ComboItem[count]; + for (var i = 0; i < allowed.Length; i++) + { + var index = allowed[i]; + var item = new ComboItem(inStrings[index], index); + list[i] = item; } - private static List GetCBListFromCSV(IReadOnlyList inputCSV, int index) - { - var arr = new List(inputCSV.Count); - foreach (var line in inputCSV) - { - var text = StringUtil.GetNthEntry(line.AsSpan(), index, 4); - var value = line.AsSpan(0, 3); - var item = new ComboItem(text, ToInt32(value)); - arr.Add(item); - } - return arr; - } + return list; + } - public static List GetCBList(ReadOnlySpan inStrings) - { - var list = new List(inStrings.Length); - for (int i = 0; i < inStrings.Length; i++) - list.Add(new ComboItem(inStrings[i], i)); - list.Sort(Comparer); - return list; - } + public static void AddCBWithOffset(List list, IReadOnlyList inStrings, int offset, int index) + { + var item = new ComboItem(inStrings[index - offset], index); + list.Add(item); + } - public static List GetCBList(IReadOnlyList inStrings, IReadOnlyList allowed) - { - var list = new List(allowed.Count + 1) { new(inStrings[0], 0) }; - foreach (var index in allowed) - list.Add(new ComboItem(inStrings[index], index)); - list.Sort(Comparer); - return list; - } - - public static List GetCBList(IReadOnlyList inStrings, int index, int offset = 0) - { - var list = new List(); - AddCBWithOffset(list, inStrings, offset, index); - return list; - } - - public static IReadOnlyList GetUnsortedCBList(IReadOnlyList inStrings, ReadOnlySpan allowed) - { - var count = allowed.Length; - var list = new ComboItem[count]; - for (var i = 0; i < allowed.Length; i++) - { - var index = allowed[i]; - var item = new ComboItem(inStrings[index], index); - list[i] = item; - } - - return list; - } - - public static void AddCBWithOffset(List list, IReadOnlyList inStrings, int offset, int index) + public static void AddCBWithOffset(List cbList, IReadOnlyList inStrings, int offset, ushort[] allowed) + { + int beginCount = cbList.Count; + cbList.Capacity += allowed.Length; + foreach (var index in allowed) { var item = new ComboItem(inStrings[index - offset], index); - list.Add(item); + cbList.Add(item); } + cbList.Sort(beginCount, allowed.Length, Comparer); + } - public static void AddCBWithOffset(List cbList, IReadOnlyList inStrings, int offset, ushort[] allowed) + public static void AddCBWithOffset(List cbList, ReadOnlySpan inStrings, int offset) + { + int beginCount = cbList.Count; + cbList.Capacity += inStrings.Length; + for (int i = 0; i < inStrings.Length; i++) { - int beginCount = cbList.Count; - cbList.Capacity += allowed.Length; - foreach (var index in allowed) - { - var item = new ComboItem(inStrings[index - offset], index); - cbList.Add(item); - } - cbList.Sort(beginCount, allowed.Length, Comparer); + var x = inStrings[i]; + var item = new ComboItem(x, i + offset); + cbList.Add(item); } + cbList.Sort(beginCount, inStrings.Length, Comparer); + } - public static void AddCBWithOffset(List cbList, ReadOnlySpan inStrings, int offset) + public static ComboItem[] GetVariedCBListBall(string[] inStrings, ReadOnlySpan stringNum, ReadOnlySpan stringVal) + { + const int forcedTop = 3; // 3 Balls are preferentially first + var list = new ComboItem[forcedTop + stringNum.Length]; + list[0] = new ComboItem(inStrings[4], (int)Ball.Poke); + list[1] = new ComboItem(inStrings[3], (int)Ball.Great); + list[2] = new ComboItem(inStrings[2], (int)Ball.Ultra); + + for (int i = 0; i < stringNum.Length; i++) { - int beginCount = cbList.Count; - cbList.Capacity += inStrings.Length; - for (int i = 0; i < inStrings.Length; i++) - { - var x = inStrings[i]; - var item = new ComboItem(x, i + offset); - cbList.Add(item); - } - cbList.Sort(beginCount, inStrings.Length, Comparer); + int index = stringNum[i]; + var value = stringVal[i]; + var text = inStrings[index]; + list[i + 3] = new ComboItem(text, value); } - public static ComboItem[] GetVariedCBListBall(string[] inStrings, ReadOnlySpan stringNum, ReadOnlySpan stringVal) - { - const int forcedTop = 3; // 3 Balls are preferentially first - var list = new ComboItem[forcedTop + stringNum.Length]; - list[0] = new ComboItem(inStrings[4], (int)Ball.Poke); - list[1] = new ComboItem(inStrings[3], (int)Ball.Great); - list[2] = new ComboItem(inStrings[2], (int)Ball.Ultra); + Array.Sort(list, 3, list.Length - 3, Comparer); + return list; + } - for (int i = 0; i < stringNum.Length; i++) - { - int index = stringNum[i]; - var value = stringVal[i]; - var text = inStrings[index]; - list[i + 3] = new ComboItem(text, value); - } + private static readonly FunctorComparer Comparer = + new((a, b) => string.CompareOrdinal(a.Text, b.Text)); - Array.Sort(list, 3, list.Length - 3, Comparer); - return list; - } - - private static readonly FunctorComparer Comparer = - new((a, b) => string.CompareOrdinal(a.Text, b.Text)); - - private sealed class FunctorComparer : IComparer - { - private readonly Comparison Comparison; - public FunctorComparer(Comparison comparison) => Comparison = comparison; - public int Compare(T x, T y) => Comparison(x, y); - } + private sealed class FunctorComparer : IComparer + { + private readonly Comparison Comparison; + public FunctorComparer(Comparison comparison) => Comparison = comparison; + public int Compare(T x, T y) => Comparison(x, y); } } diff --git a/PKHeX.Core/Util/DateUtil.cs b/PKHeX.Core/Util/DateUtil.cs index 0fc2e4a0b..25d1434a9 100644 --- a/PKHeX.Core/Util/DateUtil.cs +++ b/PKHeX.Core/Util/DateUtil.cs @@ -1,83 +1,82 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class DateUtil { - public static class DateUtil + /// + /// Determines whether or not the given date components are valid. + /// + /// The year of the date of which to check the validity. + /// The month of the date of which to check the validity. + /// The day of the date of which to check the validity. + /// A boolean indicating whether or not the date is valid. + public static bool IsDateValid(int year, int month, int day) { - /// - /// Determines whether or not the given date components are valid. - /// - /// The year of the date of which to check the validity. - /// The month of the date of which to check the validity. - /// The day of the date of which to check the validity. - /// A boolean indicating whether or not the date is valid. - public static bool IsDateValid(int year, int month, int day) - { - if (year is <= 0 or > 9999) - return false; - if (month is < 1 or > 12) - return false; - if (day < 1 || day > DateTime.DaysInMonth(year, month)) - return false; + if (year is <= 0 or > 9999) + return false; + if (month is < 1 or > 12) + return false; + if (day < 1 || day > DateTime.DaysInMonth(year, month)) + return false; - return true; - } - - /// - /// Determines whether or not the given date components are valid. - /// - /// The year of the date of which to check the validity. - /// The month of the date of which to check the validity. - /// The day of the date of which to check the validity. - /// A boolean indicating whether or not the date is valid. - public static bool IsDateValid(uint year, uint month, uint day) - { - return year < int.MaxValue && month < int.MaxValue && day < int.MaxValue && IsDateValid((int)year, (int)month, (int)day); - } - - private static readonly DateTime Epoch2000 = new(2000, 1, 1); - private const int SecondsPerDay = 60*60*24; // 86400 - - public static int GetSecondsFrom2000(DateTime date, DateTime time) - { - int seconds = (int)(date - Epoch2000).TotalSeconds; - seconds -= seconds % SecondsPerDay; - seconds += (int)(time - Epoch2000).TotalSeconds; - return seconds; - } - - public static void GetDateTime2000(uint seconds, out DateTime date, out DateTime time) - { - date = Epoch2000.AddSeconds(seconds); - time = Epoch2000.AddSeconds(seconds % SecondsPerDay); - } - - public static string ConvertDateValueToString(int value, int secondsBias = -1) - { - var sb = new System.Text.StringBuilder(); - if (value >= SecondsPerDay) - sb.Append(value / SecondsPerDay).Append("d "); - sb.Append(new DateTime(0).AddSeconds(value).ToString("HH:mm:ss")); - if (secondsBias >= 0) - sb.Append(Environment.NewLine).Append("Date: ").Append(Epoch2000.AddSeconds(value + secondsBias)); - return sb.ToString(); - } - - /// - /// Gets a random day within the random range of and days, inclusive. - /// - /// First valid date - /// Last valid date - /// Random to use - /// Date within the specified range, inclusive. - public static DateTime GetRandomDateWithin(DateTime start, DateTime end, Random r) - { - var delta = end - start; - var bias = r.Next(delta.Days + 1); - return start.AddDays(bias); - } - - /// - public static DateTime GetRandomDateWithin(DateTime start, DateTime end) => GetRandomDateWithin(start, end, Util.Rand); + return true; } + + /// + /// Determines whether or not the given date components are valid. + /// + /// The year of the date of which to check the validity. + /// The month of the date of which to check the validity. + /// The day of the date of which to check the validity. + /// A boolean indicating whether or not the date is valid. + public static bool IsDateValid(uint year, uint month, uint day) + { + return year < int.MaxValue && month < int.MaxValue && day < int.MaxValue && IsDateValid((int)year, (int)month, (int)day); + } + + private static readonly DateTime Epoch2000 = new(2000, 1, 1); + private const int SecondsPerDay = 60*60*24; // 86400 + + public static int GetSecondsFrom2000(DateTime date, DateTime time) + { + int seconds = (int)(date - Epoch2000).TotalSeconds; + seconds -= seconds % SecondsPerDay; + seconds += (int)(time - Epoch2000).TotalSeconds; + return seconds; + } + + public static void GetDateTime2000(uint seconds, out DateTime date, out DateTime time) + { + date = Epoch2000.AddSeconds(seconds); + time = Epoch2000.AddSeconds(seconds % SecondsPerDay); + } + + public static string ConvertDateValueToString(int value, int secondsBias = -1) + { + var sb = new System.Text.StringBuilder(); + if (value >= SecondsPerDay) + sb.Append(value / SecondsPerDay).Append("d "); + sb.Append(new DateTime(0).AddSeconds(value).ToString("HH:mm:ss")); + if (secondsBias >= 0) + sb.Append(Environment.NewLine).Append("Date: ").Append(Epoch2000.AddSeconds(value + secondsBias)); + return sb.ToString(); + } + + /// + /// Gets a random day within the random range of and days, inclusive. + /// + /// First valid date + /// Last valid date + /// Random to use + /// Date within the specified range, inclusive. + public static DateTime GetRandomDateWithin(DateTime start, DateTime end, Random r) + { + var delta = end - start; + var bias = r.Next(delta.Days + 1); + return start.AddDays(bias); + } + + /// + public static DateTime GetRandomDateWithin(DateTime start, DateTime end) => GetRandomDateWithin(start, end, Util.Rand); } diff --git a/PKHeX.Core/Util/FileUtil.cs b/PKHeX.Core/Util/FileUtil.cs index dba5909d4..04845504f 100644 --- a/PKHeX.Core/Util/FileUtil.cs +++ b/PKHeX.Core/Util/FileUtil.cs @@ -5,264 +5,263 @@ using System.IO; using static System.Buffers.Binary.BinaryPrimitives; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Logic for detecting supported binary object formats. +/// +public static class FileUtil { /// - /// Logic for detecting supported binary object formats. + /// Attempts to get a binary object from the provided path. /// - public static class FileUtil + /// + /// Reference savefile used for PC Binary compatibility checks. + /// Supported file object reference, null if none found. + public static object? GetSupportedFile(string path, SaveFile? reference = null) { - /// - /// Attempts to get a binary object from the provided path. - /// - /// - /// Reference savefile used for PC Binary compatibility checks. - /// Supported file object reference, null if none found. - public static object? GetSupportedFile(string path, SaveFile? reference = null) + try { - try - { - var fi = new FileInfo(path); - if (!fi.Exists || IsFileTooBig(fi.Length) || IsFileTooSmall(fi.Length)) - return null; - - var data = File.ReadAllBytes(path); - var ext = Path.GetExtension(path); - return GetSupportedFile(data, ext, reference); - } - // User input data can be fuzzed; if anything blows up, just fail safely. - catch (Exception e) - { - Debug.WriteLine(MessageStrings.MsgFileInUse); - Debug.WriteLine(e.Message); + var fi = new FileInfo(path); + if (!fi.Exists || IsFileTooBig(fi.Length) || IsFileTooSmall(fi.Length)) return null; - } - } - /// - /// Attempts to get a binary object from the provided inputs. - /// - /// Binary data for the file. - /// File extension used as a hint. - /// Reference savefile used for PC Binary compatibility checks. - /// Supported file object reference, null if none found. - public static object? GetSupportedFile(byte[] data, string ext, SaveFile? reference = null) + var data = File.ReadAllBytes(path); + var ext = Path.GetExtension(path); + return GetSupportedFile(data, ext, reference); + } + // User input data can be fuzzed; if anything blows up, just fail safely. + catch (Exception e) { - if (TryGetSAV(data, out var sav)) - return sav; - if (TryGetMemoryCard(data, out var mc)) - return mc; - if (TryGetPKM(data, out var pk, ext)) - return pk; - if (TryGetPCBoxBin(data, out IEnumerable pks, reference)) - return pks; - if (TryGetBattleVideo(data, out var bv)) - return bv; - if (TryGetMysteryGift(data, out var g, ext)) - return g; - if (TryGetGP1(data, out var gp)) - return gp; - if (TryGetBundle(data, out var bundle)) - return bundle; + Debug.WriteLine(MessageStrings.MsgFileInUse); + Debug.WriteLine(e.Message); return null; } + } - public static bool IsFileLocked(string path) + /// + /// Attempts to get a binary object from the provided inputs. + /// + /// Binary data for the file. + /// File extension used as a hint. + /// Reference savefile used for PC Binary compatibility checks. + /// Supported file object reference, null if none found. + public static object? GetSupportedFile(byte[] data, string ext, SaveFile? reference = null) + { + if (TryGetSAV(data, out var sav)) + return sav; + if (TryGetMemoryCard(data, out var mc)) + return mc; + if (TryGetPKM(data, out var pk, ext)) + return pk; + if (TryGetPCBoxBin(data, out IEnumerable pks, reference)) + return pks; + if (TryGetBattleVideo(data, out var bv)) + return bv; + if (TryGetMysteryGift(data, out var g, ext)) + return g; + if (TryGetGP1(data, out var gp)) + return gp; + if (TryGetBundle(data, out var bundle)) + return bundle; + return null; + } + + public static bool IsFileLocked(string path) + { + try { return (File.GetAttributes(path) & FileAttributes.ReadOnly) != 0; } + catch { return true; } + } + + public static int GetFileSize(string path) + { + try { - try { return (File.GetAttributes(path) & FileAttributes.ReadOnly) != 0; } - catch { return true; } + var size = new FileInfo(path).Length; + if (size > int.MaxValue) + return -1; + return (int)size; } + catch { return -1; } // Bad File / Locked + } - public static int GetFileSize(string path) - { - try - { - var size = new FileInfo(path).Length; - if (size > int.MaxValue) - return -1; - return (int)size; - } - catch { return -1; } // Bad File / Locked - } + private static bool TryGetGP1(byte[] data, [NotNullWhen(true)] out GP1? gp1) + { + gp1 = null; + if (data.Length != GP1.SIZE || ReadUInt32LittleEndian(data.AsSpan(0x28)) == 0) + return false; + gp1 = new GP1(data); + return true; + } - private static bool TryGetGP1(byte[] data, [NotNullWhen(true)] out GP1? gp1) + private static bool TryGetBundle(byte[] data, [NotNullWhen(true)] out IPokeGroup? result) + { + result = null; + if (RentalTeam8.IsRentalTeam(data)) { - gp1 = null; - if (data.Length != GP1.SIZE || ReadUInt32LittleEndian(data.AsSpan(0x28)) == 0) - return false; - gp1 = new GP1(data); + result = new RentalTeam8(data); return true; } + return false; + } - private static bool TryGetBundle(byte[] data, [NotNullWhen(true)] out IPokeGroup? result) + /// + /// Checks if the length is too big to be a detectable file. + /// + /// File size + public static bool IsFileTooBig(long length) + { + if (length <= 0x10_0000) // 1 MB + return false; + if (length > int.MaxValue) + return true; + if (SaveUtil.IsSizeValid((int)length)) + return false; + if (SAV3GCMemoryCard.IsMemoryCardSize(length)) + return false; // pbr/GC have size > 1MB + return true; + } + + /// + /// Checks if the length is too small to be a detectable file. + /// + /// File size + public static bool IsFileTooSmall(long length) => length < 0x20; // bigger than PK1 + + /// + /// Tries to get an object from the input parameters. + /// + /// Binary data + /// Output result + /// True if file object reference is valid, false if none found. + public static bool TryGetSAV(byte[] data, [NotNullWhen(true)] out SaveFile? sav) + { + sav = SaveUtil.GetVariantSAV(data); + return sav != null; + } + + /// + /// Tries to get an object from the input parameters. + /// + /// Binary data + /// Output result + /// True if file object reference is valid, false if none found. + public static bool TryGetMemoryCard(byte[] data, [NotNullWhen(true)] out SAV3GCMemoryCard? memcard) + { + if (!SAV3GCMemoryCard.IsMemoryCardSize(data)) { - result = null; - if (RentalTeam8.IsRentalTeam(data)) - { - result = new RentalTeam8(data); - return true; - } + memcard = null; return false; } + memcard = new SAV3GCMemoryCard(data); + return true; + } - /// - /// Checks if the length is too big to be a detectable file. - /// - /// File size - public static bool IsFileTooBig(long length) + /// + /// Tries to get an object from the input parameters. + /// + /// Binary data + /// Output result + /// Format hint + /// Reference savefile used for PC Binary compatibility checks. + /// True if file object reference is valid, false if none found. + public static bool TryGetPKM(byte[] data, [NotNullWhen(true)] out PKM? pk, string ext, ITrainerInfo? sav = null) + { + if (ext == ".pgt") // size collision with pk6 { - if (length <= 0x10_0000) // 1 MB - return false; - if (length > int.MaxValue) - return true; - if (SaveUtil.IsSizeValid((int)length)) - return false; - if (SAV3GCMemoryCard.IsMemoryCardSize(length)) - return false; // pbr/GC have size > 1MB - return true; + pk = null; + return false; } + var format = EntityFileExtension.GetContextFromExtension(ext, sav?.Context ?? EntityContext.Gen6); + pk = EntityFormat.GetFromBytes(data, prefer: format); + return pk != null; + } - /// - /// Checks if the length is too small to be a detectable file. - /// - /// File size - public static bool IsFileTooSmall(long length) => length < 0x20; // bigger than PK1 - - /// - /// Tries to get an object from the input parameters. - /// - /// Binary data - /// Output result - /// True if file object reference is valid, false if none found. - public static bool TryGetSAV(byte[] data, [NotNullWhen(true)] out SaveFile? sav) + /// + /// Tries to get an object from the input parameters. + /// + /// Binary data + /// Output result + /// Reference savefile used for PC Binary compatibility checks. + /// True if file object reference is valid, false if none found. + public static bool TryGetPCBoxBin(byte[] data, out IEnumerable pkms, SaveFile? sav) + { + if (sav == null) { - sav = SaveUtil.GetVariantSAV(data); - return sav != null; - } - - /// - /// Tries to get an object from the input parameters. - /// - /// Binary data - /// Output result - /// True if file object reference is valid, false if none found. - public static bool TryGetMemoryCard(byte[] data, [NotNullWhen(true)] out SAV3GCMemoryCard? memcard) - { - if (!SAV3GCMemoryCard.IsMemoryCardSize(data)) - { - memcard = null; - return false; - } - memcard = new SAV3GCMemoryCard(data); - return true; - } - - /// - /// Tries to get an object from the input parameters. - /// - /// Binary data - /// Output result - /// Format hint - /// Reference savefile used for PC Binary compatibility checks. - /// True if file object reference is valid, false if none found. - public static bool TryGetPKM(byte[] data, [NotNullWhen(true)] out PKM? pk, string ext, ITrainerInfo? sav = null) - { - if (ext == ".pgt") // size collision with pk6 - { - pk = null; - return false; - } - var format = EntityFileExtension.GetContextFromExtension(ext, sav?.Context ?? EntityContext.Gen6); - pk = EntityFormat.GetFromBytes(data, prefer: format); - return pk != null; - } - - /// - /// Tries to get an object from the input parameters. - /// - /// Binary data - /// Output result - /// Reference savefile used for PC Binary compatibility checks. - /// True if file object reference is valid, false if none found. - public static bool TryGetPCBoxBin(byte[] data, out IEnumerable pkms, SaveFile? sav) - { - if (sav == null) - { - pkms = Array.Empty(); - return false; - } - var length = data.Length; - if (EntityDetection.IsSizePlausible(length / sav.SlotCount) || EntityDetection.IsSizePlausible(length / sav.BoxSlotCount)) - { - pkms = ArrayUtil.EnumerateSplit(data, length); - return true; - } pkms = Array.Empty(); return false; } - - /// - /// Tries to get a object from the input parameters. - /// - /// Binary data - /// Output result - /// True if file object reference is valid, false if none found. - public static bool TryGetBattleVideo(byte[] data, [NotNullWhen(true)] out BattleVideo? bv) + var length = data.Length; + if (EntityDetection.IsSizePlausible(length / sav.SlotCount) || EntityDetection.IsSizePlausible(length / sav.BoxSlotCount)) { - bv = BattleVideo.GetVariantBattleVideo(data); - return bv != null; + pkms = ArrayUtil.EnumerateSplit(data, length); + return true; } + pkms = Array.Empty(); + return false; + } - /// - /// Tries to get a object from the input parameters. - /// - /// Binary data - /// Output result - /// Format hint - /// True if file object reference is valid, false if none found. - public static bool TryGetMysteryGift(byte[] data, [NotNullWhen(true)] out MysteryGift? mg, string ext) - { - mg = MysteryGift.GetMysteryGift(data, ext); - return mg != null; - } + /// + /// Tries to get a object from the input parameters. + /// + /// Binary data + /// Output result + /// True if file object reference is valid, false if none found. + public static bool TryGetBattleVideo(byte[] data, [NotNullWhen(true)] out BattleVideo? bv) + { + bv = BattleVideo.GetVariantBattleVideo(data); + return bv != null; + } - /// - /// Gets a Temp location File Name for the . - /// - /// Data to be exported - /// Data is to be encrypted - /// Path to temporary file location to write to. - public static string GetPKMTempFileName(PKM pk, bool encrypt) - { - string fn = pk.FileNameWithoutExtension; - string filename = fn + (encrypt ? $".ek{pk.Format}" : $".{pk.Extension}"); + /// + /// Tries to get a object from the input parameters. + /// + /// Binary data + /// Output result + /// Format hint + /// True if file object reference is valid, false if none found. + public static bool TryGetMysteryGift(byte[] data, [NotNullWhen(true)] out MysteryGift? mg, string ext) + { + mg = MysteryGift.GetMysteryGift(data, ext); + return mg != null; + } - return Path.Combine(Path.GetTempPath(), Util.CleanFileName(filename)); - } + /// + /// Gets a Temp location File Name for the . + /// + /// Data to be exported + /// Data is to be encrypted + /// Path to temporary file location to write to. + public static string GetPKMTempFileName(PKM pk, bool encrypt) + { + string fn = pk.FileNameWithoutExtension; + string filename = fn + (encrypt ? $".ek{pk.Format}" : $".{pk.Extension}"); - /// - /// Gets a from the provided path, which is to be loaded to the . - /// - /// or file path. - /// Generation Info - /// New reference from the file. - public static PKM? GetSingleFromPath(string file, ITrainerInfo sav) - { - var fi = new FileInfo(file); - if (!fi.Exists) - return null; - if (fi.Length == GP1.SIZE && TryGetGP1(File.ReadAllBytes(file), out var gp1)) - return gp1.ConvertToPB7(sav); - if (!EntityDetection.IsSizePlausible(fi.Length) && !MysteryGift.IsMysteryGift(fi.Length)) - return null; - var data = File.ReadAllBytes(file); - var ext = fi.Extension; - var mg = MysteryGift.GetMysteryGift(data, ext); - var gift = mg?.ConvertToPKM(sav); - if (gift != null) - return gift; - _ = TryGetPKM(data, out var pk, ext, sav); - return pk; - } + return Path.Combine(Path.GetTempPath(), Util.CleanFileName(filename)); + } + + /// + /// Gets a from the provided path, which is to be loaded to the . + /// + /// or file path. + /// Generation Info + /// New reference from the file. + public static PKM? GetSingleFromPath(string file, ITrainerInfo sav) + { + var fi = new FileInfo(file); + if (!fi.Exists) + return null; + if (fi.Length == GP1.SIZE && TryGetGP1(File.ReadAllBytes(file), out var gp1)) + return gp1.ConvertToPB7(sav); + if (!EntityDetection.IsSizePlausible(fi.Length) && !MysteryGift.IsMysteryGift(fi.Length)) + return null; + var data = File.ReadAllBytes(file); + var ext = fi.Extension; + var mg = MysteryGift.GetMysteryGift(data, ext); + var gift = mg?.ConvertToPKM(sav); + if (gift != null) + return gift; + _ = TryGetPKM(data, out var pk, ext, sav); + return pk; } } diff --git a/PKHeX.Core/Util/FlagUtil.cs b/PKHeX.Core/Util/FlagUtil.cs index f4ce976c4..5e7b16eb1 100644 --- a/PKHeX.Core/Util/FlagUtil.cs +++ b/PKHeX.Core/Util/FlagUtil.cs @@ -1,37 +1,36 @@ -using System; +using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Utility logic for dealing with bitflags in a byte array. +/// +public static class FlagUtil { /// - /// Utility logic for dealing with bitflags in a byte array. + /// Gets the requested from the byte at . /// - public static class FlagUtil + /// Buffer to read + /// Offset of the byte + /// Bit to read + public static bool GetFlag(ReadOnlySpan arr, int offset, int bitIndex) { - /// - /// Gets the requested from the byte at . - /// - /// Buffer to read - /// Offset of the byte - /// Bit to read - public static bool GetFlag(ReadOnlySpan arr, int offset, int bitIndex) - { - bitIndex &= 7; // ensure bit access is 0-7 - return (arr[offset] >> bitIndex & 1) != 0; - } + bitIndex &= 7; // ensure bit access is 0-7 + return ((arr[offset] >> bitIndex) & 1) != 0; + } - /// - /// Sets the requested value to the byte at . - /// - /// Buffer to modify - /// Offset of the byte - /// Bit to write - /// Bit flag value to set - public static void SetFlag(Span arr, int offset, int bitIndex, bool value) - { - bitIndex &= 7; // ensure bit access is 0-7 - var current = arr[offset] & ~(1 << bitIndex); - var newValue = current | ((value ? 1 : 0) << bitIndex); - arr[offset] = (byte)newValue; - } + /// + /// Sets the requested value to the byte at . + /// + /// Buffer to modify + /// Offset of the byte + /// Bit to write + /// Bit flag value to set + public static void SetFlag(Span arr, int offset, int bitIndex, bool value) + { + bitIndex &= 7; // ensure bit access is 0-7 + var current = arr[offset] & ~(1 << bitIndex); + var newValue = current | ((value ? 1 : 0) << bitIndex); + arr[offset] = (byte)newValue; } } diff --git a/PKHeX.Core/Util/FrameworkUtil.cs b/PKHeX.Core/Util/FrameworkUtil.cs index e21b1c2df..1c60f8e97 100644 --- a/PKHeX.Core/Util/FrameworkUtil.cs +++ b/PKHeX.Core/Util/FrameworkUtil.cs @@ -5,6 +5,14 @@ using System; using System.Runtime.InteropServices; +namespace System +{ + public static class FutureFeatures + { + public static bool StartsWith(this string str, char value) => str.Length != 0 && str[0] == value; + } +} + namespace PKHeX.Core.Buffers.Binary.Extra { internal static class BinaryPrimitives diff --git a/PKHeX.Core/Util/Localization/LocalizationUtil.cs b/PKHeX.Core/Util/Localization/LocalizationUtil.cs index 97c9fee26..9563e64df 100644 --- a/PKHeX.Core/Util/Localization/LocalizationUtil.cs +++ b/PKHeX.Core/Util/Localization/LocalizationUtil.cs @@ -3,109 +3,108 @@ using System.Diagnostics; using System.Linq; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class LocalizationUtil { - public static class LocalizationUtil + private const string TranslationSplitter = " = "; + + /// + /// Gets the names of the properties defined in the given input + /// + /// Enumerable of translation definitions in the form "Property = Value". + private static string[] GetProperties(IEnumerable input) { - private const string TranslationSplitter = " = "; - - /// - /// Gets the names of the properties defined in the given input - /// - /// Enumerable of translation definitions in the form "Property = Value". - private static string[] GetProperties(IEnumerable input) + static string AfterSplit(string l) { - static string AfterSplit(string l) + var split = l.IndexOf(TranslationSplitter, StringComparison.Ordinal); + return l[..split]; + } + return input.Select(AfterSplit).ToArray(); + } + + private static IEnumerable DumpStrings(Type t) + { + var props = ReflectUtil.GetPropertiesStartWithPrefix(t, string.Empty); + return props.Select(p => $"{p}{TranslationSplitter}{ReflectUtil.GetValue(t, p)}"); + } + + /// + /// Gets the current localization in a static class containing language-specific strings + /// + /// + public static string[] GetLocalization(Type t) => DumpStrings(t).ToArray(); + + /// + /// Gets the current localization in a static class containing language-specific strings + /// + /// + /// Existing localization lines (if provided) + public static string[] GetLocalization(Type t, string[] existingLines) + { + var currentLines = GetLocalization(t); + var existing = GetProperties(existingLines); + var current = GetProperties(currentLines); + + var result = new string[currentLines.Length]; + for (int i = 0; i < current.Length; i++) + { + int index = Array.IndexOf(existing, current[i]); + result[i] = index < 0 ? currentLines[i] : existingLines[index]; + } + return result; + } + + /// + /// Applies localization to a static class containing language-specific strings. + /// + /// Type of the static class containing the desired strings. + /// Lines containing the localized strings + private static void SetLocalization(Type t, IReadOnlyCollection lines) + { + if (lines.Count == 0) + return; + foreach (var line in lines) + { + var index = line.IndexOf(TranslationSplitter, StringComparison.Ordinal); + if (index < 0) + continue; + var prop = line[..index]; + var value = line[(index + TranslationSplitter.Length)..]; + + try { - var split = l.IndexOf(TranslationSplitter, StringComparison.Ordinal); - return l[..split]; + ReflectUtil.SetValue(t, prop, value); } - return input.Select(AfterSplit).ToArray(); - } - - private static IEnumerable DumpStrings(Type t) - { - var props = ReflectUtil.GetPropertiesStartWithPrefix(t, string.Empty); - return props.Select(p => $"{p}{TranslationSplitter}{ReflectUtil.GetValue(t, p)}"); - } - - /// - /// Gets the current localization in a static class containing language-specific strings - /// - /// - public static string[] GetLocalization(Type t) => DumpStrings(t).ToArray(); - - /// - /// Gets the current localization in a static class containing language-specific strings - /// - /// - /// Existing localization lines (if provided) - public static string[] GetLocalization(Type t, string[] existingLines) - { - var currentLines = GetLocalization(t); - var existing = GetProperties(existingLines); - var current = GetProperties(currentLines); - - var result = new string[currentLines.Length]; - for (int i = 0; i < current.Length; i++) + // Malformed translation files, log + catch (Exception e) { - int index = Array.IndexOf(existing, current[i]); - result[i] = index < 0 ? currentLines[i] : existingLines[index]; + Debug.WriteLine($"Property not present: {prop} || Value written: {value}"); + Debug.WriteLine(e.Message); } - return result; - } - - /// - /// Applies localization to a static class containing language-specific strings. - /// - /// Type of the static class containing the desired strings. - /// Lines containing the localized strings - private static void SetLocalization(Type t, IReadOnlyCollection lines) - { - if (lines.Count == 0) - return; - foreach (var line in lines) - { - var index = line.IndexOf(TranslationSplitter, StringComparison.Ordinal); - if (index < 0) - continue; - var prop = line[..index]; - var value = line[(index + TranslationSplitter.Length)..]; - - try - { - ReflectUtil.SetValue(t, prop, value); - } - // Malformed translation files, log - catch (Exception e) - { - Debug.WriteLine($"Property not present: {prop} || Value written: {value}"); - Debug.WriteLine(e.Message); - } - } - } - - /// - /// Applies localization to a static class containing language-specific strings. - /// - /// Type of the static class containing the desired strings. - /// Prefix of the language file to use. Example: if the target is legality_en.txt, should be "legality". - /// Culture information - private static void SetLocalization(Type t, string languageFilePrefix, string currentCultureCode) - { - var lines = Util.GetStringList($"{languageFilePrefix}_{currentCultureCode}"); - SetLocalization(t, lines); - } - - /// - /// Applies localization to a static class containing language-specific strings. - /// - /// Type of the static class containing the desired strings. - /// The values used to translate the given static class are retrieved from [TypeName]_[CurrentLangCode2].txt in the resource manager of PKHeX.Core. - /// Culture information - public static void SetLocalization(Type t, string currentCultureCode) - { - SetLocalization(t, t.Name, currentCultureCode); } } + + /// + /// Applies localization to a static class containing language-specific strings. + /// + /// Type of the static class containing the desired strings. + /// Prefix of the language file to use. Example: if the target is legality_en.txt, should be "legality". + /// Culture information + private static void SetLocalization(Type t, string languageFilePrefix, string currentCultureCode) + { + var lines = Util.GetStringList($"{languageFilePrefix}_{currentCultureCode}"); + SetLocalization(t, lines); + } + + /// + /// Applies localization to a static class containing language-specific strings. + /// + /// Type of the static class containing the desired strings. + /// The values used to translate the given static class are retrieved from [TypeName]_[CurrentLangCode2].txt in the resource manager of PKHeX.Core. + /// Culture information + public static void SetLocalization(Type t, string currentCultureCode) + { + SetLocalization(t, t.Name, currentCultureCode); + } } diff --git a/PKHeX.Core/Util/Localization/LocalizeUtil.cs b/PKHeX.Core/Util/Localization/LocalizeUtil.cs index 2e5226863..dafe6b265 100644 --- a/PKHeX.Core/Util/Localization/LocalizeUtil.cs +++ b/PKHeX.Core/Util/Localization/LocalizeUtil.cs @@ -1,31 +1,30 @@ using System.Threading.Tasks; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class LocalizeUtil { - public static class LocalizeUtil + /// + /// Initializes PKHeX's runtime strings to the specified language. + /// + /// 2-char language ID + /// Save data (optional) + /// Permit illegal things (items, only) + public static void InitializeStrings(string lang, SaveFile? sav = null, bool hax = false) { - /// - /// Initializes PKHeX's runtime strings to the specified language. - /// - /// 2-char language ID - /// Save data (optional) - /// Permit illegal things (items, only) - public static void InitializeStrings(string lang, SaveFile? sav = null, bool hax = false) + var str = GameInfo.Strings = GameInfo.GetStrings(lang); + if (sav != null) + GameInfo.FilteredSources = new FilteredGameDataSource(sav, GameInfo.Sources, hax); + + // Update Legality Analysis strings + ParseSettings.ChangeLocalizationStrings(str.movelist, str.specieslist); + + // Update Legality Strings + Task.Run(() => { - var str = GameInfo.Strings = GameInfo.GetStrings(lang); - if (sav != null) - GameInfo.FilteredSources = new FilteredGameDataSource(sav, GameInfo.Sources, hax); - - // Update Legality Analysis strings - ParseSettings.ChangeLocalizationStrings(str.movelist, str.specieslist); - - // Update Legality Strings - Task.Run(() => - { - RibbonStrings.ResetDictionary(str.ribbons); - LocalizationUtil.SetLocalization(typeof(LegalityCheckStrings), lang); - LocalizationUtil.SetLocalization(typeof(MessageStrings), lang); - }); - } + RibbonStrings.ResetDictionary(str.ribbons); + LocalizationUtil.SetLocalization(typeof(LegalityCheckStrings), lang); + LocalizationUtil.SetLocalization(typeof(MessageStrings), lang); + }); } } diff --git a/PKHeX.Core/Util/Localization/LocalizedDescriptionAttribute.cs b/PKHeX.Core/Util/Localization/LocalizedDescriptionAttribute.cs index 6abc0e489..80abab047 100644 --- a/PKHeX.Core/Util/Localization/LocalizedDescriptionAttribute.cs +++ b/PKHeX.Core/Util/Localization/LocalizedDescriptionAttribute.cs @@ -3,22 +3,21 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -namespace PKHeX.Core +namespace PKHeX.Core; + +[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Delegate | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter)] +public sealed class LocalizedDescriptionAttribute : DescriptionAttribute { - [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Delegate | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter)] - public sealed class LocalizedDescriptionAttribute : DescriptionAttribute + public static IReadOnlyDictionary Localizer { private get; set; } = new Dictionary(); + + public readonly string Fallback; + public readonly string Key; + + public LocalizedDescriptionAttribute(string fallback, [CallerMemberName] string? key = null) { - public static IReadOnlyDictionary Localizer { private get; set; } = new Dictionary(); - - public readonly string Fallback; - public readonly string Key; - - public LocalizedDescriptionAttribute(string fallback, [CallerMemberName] string? key = null) - { - Key = $"LocalizedDescription.{key!}"; - Fallback = fallback; - } - - public override string Description => Localizer.TryGetValue(Key, out var result) ? result : Fallback; + Key = $"LocalizedDescription.{key!}"; + Fallback = fallback; } + + public override string Description => Localizer.TryGetValue(Key, out var result) ? result : Fallback; } diff --git a/PKHeX.Core/Util/MessageStrings.cs b/PKHeX.Core/Util/MessageStrings.cs index db7c1118d..8c5911372 100644 --- a/PKHeX.Core/Util/MessageStrings.cs +++ b/PKHeX.Core/Util/MessageStrings.cs @@ -1,329 +1,328 @@ // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Generic Message Strings used for messages shown to the user. +/// +public static class MessageStrings { - /// - /// Generic Message Strings used for messages shown to the user. - /// - public static class MessageStrings - { - #region Generic Program Messages + #region Generic Program Messages - public static string MsgProgramRestart { get; set; } = "Please restart the program."; - public static string MsgProgramIllegalModeBehave { get; set; } = "Please behave."; - public static string MsgProgramIllegalModeActive { get; set; } = "Illegal mode activated."; - public static string MsgProgramUpdateAvailable { get; set; } = "New Update Available!"; - public static string MsgProgramCloseUnsaved { get; set; } = "Any unsaved changes will be lost."; - public static string MsgProgramCloseConfirm { get; set; } = "Are you sure you want to close PKHeX?"; - public static string MsgProgramSaveFileConfirm { get; set; } = "Are you sure you want to load a new save file?"; - public static string MsgProgramError { get; set; } = "ERROR"; - public static string MsgProgramErrorExpectedHex { get; set; } = "Expected Text containing only the following characters (0-9, A-F)."; + public static string MsgProgramRestart { get; set; } = "Please restart the program."; + public static string MsgProgramIllegalModeBehave { get; set; } = "Please behave."; + public static string MsgProgramIllegalModeActive { get; set; } = "Illegal mode activated."; + public static string MsgProgramUpdateAvailable { get; set; } = "New Update Available!"; + public static string MsgProgramCloseUnsaved { get; set; } = "Any unsaved changes will be lost."; + public static string MsgProgramCloseConfirm { get; set; } = "Are you sure you want to close PKHeX?"; + public static string MsgProgramSaveFileConfirm { get; set; } = "Are you sure you want to load a new save file?"; + public static string MsgProgramError { get; set; } = "ERROR"; + public static string MsgProgramErrorExpectedHex { get; set; } = "Expected Text containing only the following characters (0-9, A-F)."; - public static string MsgSettingsLoadFail { get; set; } = "Unable to load settings."; - public static string MsgSettingsResetCorrupt { get; set; } = "Program settings are corrupt. Would you like to reset the settings?"; - public static string MsgSettingsResetPrompt { get; set; } = "Yes to delete the program settings, or No to close the program."; - public static string MsgSettingsResetSuccess { get; set; } = "Program settings have been deleted."; + public static string MsgSettingsLoadFail { get; set; } = "Unable to load settings."; + public static string MsgSettingsResetCorrupt { get; set; } = "Program settings are corrupt. Would you like to reset the settings?"; + public static string MsgSettingsResetPrompt { get; set; } = "Yes to delete the program settings, or No to close the program."; + public static string MsgSettingsResetSuccess { get; set; } = "Program settings have been deleted."; - public static string MsgAny { get; set; } = "Any"; - public static string MsgAll { get; set; } = "All"; - public static string MsgYes { get; set; } = "Yes"; - public static string MsgNo { get; set; } = "No"; - public static string MsgContinue { get; set; } = "Continue?"; + public static string MsgAny { get; set; } = "Any"; + public static string MsgAll { get; set; } = "All"; + public static string MsgYes { get; set; } = "Yes"; + public static string MsgNo { get; set; } = "No"; + public static string MsgContinue { get; set; } = "Continue?"; - public static string MsgGameColosseum { get; set; } = "Colosseum"; - public static string MsgGameXD { get; set; } = "XD"; - public static string MsgGameRSBOX { get; set; } = "RS Box"; + public static string MsgGameColosseum { get; set; } = "Colosseum"; + public static string MsgGameXD { get; set; } = "XD"; + public static string MsgGameRSBOX { get; set; } = "RS Box"; - public static string MsgFileDeleteCount { get; set; } = "{0} files deleted."; - public static string MsgFolderNotFound { get; set; } = "Can't find folder:"; - public static string MsgWindowClose { get; set; } = "The current window will now close."; + public static string MsgFileDeleteCount { get; set; } = "{0} files deleted."; + public static string MsgFolderNotFound { get; set; } = "Can't find folder:"; + public static string MsgWindowClose { get; set; } = "The current window will now close."; - public static string MsgResearchRequired { get; set; } = "Needs more research."; + public static string MsgResearchRequired { get; set; } = "Needs more research."; - #endregion + #endregion - #region Main Window + #region Main Window - public static string MsgFileLoad { get; set; } = "File Loaded:"; - public static string MsgFileLoadFail { get; set; } = "Unable to load file."; - public static string MsgFileLoadFailAuto { get; set; } = "An error occurred while attempting to auto-load your save file."; - public static string MsgFileLoadFailAutoAdvise { get; set; } = "It is advised to manually remove bad filenames from the folder."; - public static string MsgFileLoadFailAutoCause { get; set; } = "This is likely caused by Homebrew creating invalid filenames."; - public static string MsgFileLoadVersionDetect { get; set; } = "Generation {0} Save File detected."; - public static string MsgFileLoadEncrypted { get; set; } = "PKHeX only edits decrypted save files."; - public static string MsgFileLoadEncryptedFail { get; set; } = "This save file is not decrypted."; - public static string MsgFileLoadIncompatible { get; set; } = "Binary is not compatible with save file."; + public static string MsgFileLoad { get; set; } = "File Loaded:"; + public static string MsgFileLoadFail { get; set; } = "Unable to load file."; + public static string MsgFileLoadFailAuto { get; set; } = "An error occurred while attempting to auto-load your save file."; + public static string MsgFileLoadFailAutoAdvise { get; set; } = "It is advised to manually remove bad filenames from the folder."; + public static string MsgFileLoadFailAutoCause { get; set; } = "This is likely caused by Homebrew creating invalid filenames."; + public static string MsgFileLoadVersionDetect { get; set; } = "Generation {0} Save File detected."; + public static string MsgFileLoadEncrypted { get; set; } = "PKHeX only edits decrypted save files."; + public static string MsgFileLoadEncryptedFail { get; set; } = "This save file is not decrypted."; + public static string MsgFileLoadIncompatible { get; set; } = "Binary is not compatible with save file."; - public static string MsgFileLoadSaveFail { get; set; } = "The data file is not a valid save file."; - public static string MsgFileLoadSaveLoadFail { get; set; } = "Invalid save file loaded. Aborting."; - public static string MsgFileLoadSaveDetectReload { get; set; } = "Open save file from the following location?"; - public static string MsgFileLoadSaveSelectVersion { get; set; } = "Select the version."; - public static string MsgFileLoadSaveSelectGame { get; set; } = "Select a game to edit."; - public static string MsgFileLoadSaveMultiple { get; set; } = "Multiple games detected"; - public static string MsgFileGameCubeBad { get; set; } = "Invalid or corrupted GC Memory Card. Aborting."; - public static string MsgFileGameCubeDuplicate { get; set; } = "GC Memory Card with duplicated game save files. Aborting."; - public static string MsgFileGameCubeNoGames { get; set; } = "GC Memory Card without any Pokémon save file. Aborting."; + public static string MsgFileLoadSaveFail { get; set; } = "The data file is not a valid save file."; + public static string MsgFileLoadSaveLoadFail { get; set; } = "Invalid save file loaded. Aborting."; + public static string MsgFileLoadSaveDetectReload { get; set; } = "Open save file from the following location?"; + public static string MsgFileLoadSaveSelectVersion { get; set; } = "Select the version."; + public static string MsgFileLoadSaveSelectGame { get; set; } = "Select a game to edit."; + public static string MsgFileLoadSaveMultiple { get; set; } = "Multiple games detected"; + public static string MsgFileGameCubeBad { get; set; } = "Invalid or corrupted GC Memory Card. Aborting."; + public static string MsgFileGameCubeDuplicate { get; set; } = "GC Memory Card with duplicated game save files. Aborting."; + public static string MsgFileGameCubeNoGames { get; set; } = "GC Memory Card without any Pokémon save file. Aborting."; - public static string MsgFileSize { get; set; } = "File Size: {0} bytes"; - public static string MsgFileSizeLarge { get; set; } = "Input file is too large."; - public static string MsgFileSizeSmall { get; set; } = "Input file is too small."; - public static string MsgFileWriteFail { get; set; } = "Unable to save file."; - public static string MsgFileWriteProtected { get; set; } = "File's location is write protected:"; - public static string MsgFileWriteProtectedAdvice { get; set; } = "If the file is on a removable disk (SD card), please ensure the write protection switch is not set."; - public static string MsgFileInUse { get; set; } = "Unable to load file. It could be in use by another program."; - public static string MsgFileUnsupported { get; set; } = "Attempted to load an unsupported file type/size. This could mean PKHeX doesn't support your save file or your save file is corrupt."; - public static string MsgPKMUnsupported { get; set; } = "Attempted to load an unsupported file type/size. This could be caused by loading a different generation Pokemon file on an unsupported generation or your file is corrupt."; + public static string MsgFileSize { get; set; } = "File Size: {0} bytes"; + public static string MsgFileSizeLarge { get; set; } = "Input file is too large."; + public static string MsgFileSizeSmall { get; set; } = "Input file is too small."; + public static string MsgFileWriteFail { get; set; } = "Unable to save file."; + public static string MsgFileWriteProtected { get; set; } = "File's location is write protected:"; + public static string MsgFileWriteProtectedAdvice { get; set; } = "If the file is on a removable disk (SD card), please ensure the write protection switch is not set."; + public static string MsgFileInUse { get; set; } = "Unable to load file. It could be in use by another program."; + public static string MsgFileUnsupported { get; set; } = "Attempted to load an unsupported file type/size. This could mean PKHeX doesn't support your save file or your save file is corrupt."; + public static string MsgPKMUnsupported { get; set; } = "Attempted to load an unsupported file type/size. This could be caused by loading a different generation Pokemon file on an unsupported generation or your file is corrupt."; - public static string MsgPKMConvertSuccess { get; set; } = "Converted from {0} to {1}."; - public static string MsgPKMConvertFail { get; set; } = "Conversion failed."; - public static string MsgPKMMysteryGiftFail { get; set; } = "Mystery Gift is not a Pokémon."; - public static string MsgPKMConvertFailFormat { get; set; } = "Cannot convert a {0} to {1}"; - public static string MsgPKMConvertFailBackwards { get; set; } = "Can't load {0}s to Gen{1} saves."; - public static string MsgPKMConvertFailForm { get; set; } = "Form cannot be transferred to future games."; - public static string MsgPKMConvertFailNoMethod { get; set; } = "Cannot transfer this format to the requested format."; - public static string MsgPKMConvertIncompatible { get; set; } = "Cannot load {0} {1}s to {2} saves."; - public static string MsgPKMConvertInternational { get; set; } = "International"; - public static string MsgPKMConvertJapanese { get; set; } = "Japanese"; + public static string MsgPKMConvertSuccess { get; set; } = "Converted from {0} to {1}."; + public static string MsgPKMConvertFail { get; set; } = "Conversion failed."; + public static string MsgPKMMysteryGiftFail { get; set; } = "Mystery Gift is not a Pokémon."; + public static string MsgPKMConvertFailFormat { get; set; } = "Cannot convert a {0} to {1}"; + public static string MsgPKMConvertFailBackwards { get; set; } = "Can't load {0}s to Gen{1} saves."; + public static string MsgPKMConvertFailForm { get; set; } = "Form cannot be transferred to future games."; + public static string MsgPKMConvertFailNoMethod { get; set; } = "Cannot transfer this format to the requested format."; + public static string MsgPKMConvertIncompatible { get; set; } = "Cannot load {0} {1}s to {2} saves."; + public static string MsgPKMConvertInternational { get; set; } = "International"; + public static string MsgPKMConvertJapanese { get; set; } = "Japanese"; - public static string MsgClipboardLegalityExport { get; set; } = "Copy report to Clipboard?"; - public static string MsgClipboardFailRead { get; set; } = "Clipboard does not contain text."; - public static string MsgClipboardFailWrite { get; set; } = "Unable to set text to Clipboard."; + public static string MsgClipboardLegalityExport { get; set; } = "Copy report to Clipboard?"; + public static string MsgClipboardFailRead { get; set; } = "Clipboard does not contain text."; + public static string MsgClipboardFailWrite { get; set; } = "Unable to set text to Clipboard."; - public static string MsgSimulatorFailClipboard { get; set; } = "Set data not found in clipboard."; - public static string MsgSimulatorLoad { get; set; } = "Import this set?"; - public static string MsgSimulatorInvalid { get; set; } = "Invalid lines detected:"; - public static string MsgSimulatorExportBadFields { get; set; } = "Fix data before exporting."; - public static string MsgSimulatorExportFail { get; set; } = "Try exporting again."; - public static string MsgSimulatorExportSuccess { get; set; } = "Exported Showdown Set to Clipboard:"; - public static string MsgSimulatorExportParty { get; set; } = "Showdown Team (Party) set to Clipboard."; - public static string MsgSimulatorExportBattleBox { get; set; } = "Showdown Team (Battle Box) set to Clipboard."; - public static string MsgSimulatorExportList { get; set; } = "Showdown Sets copied to Clipboard."; + public static string MsgSimulatorFailClipboard { get; set; } = "Set data not found in clipboard."; + public static string MsgSimulatorLoad { get; set; } = "Import this set?"; + public static string MsgSimulatorInvalid { get; set; } = "Invalid lines detected:"; + public static string MsgSimulatorExportBadFields { get; set; } = "Fix data before exporting."; + public static string MsgSimulatorExportFail { get; set; } = "Try exporting again."; + public static string MsgSimulatorExportSuccess { get; set; } = "Exported Showdown Set to Clipboard:"; + public static string MsgSimulatorExportParty { get; set; } = "Showdown Team (Party) set to Clipboard."; + public static string MsgSimulatorExportBattleBox { get; set; } = "Showdown Team (Battle Box) set to Clipboard."; + public static string MsgSimulatorExportList { get; set; } = "Showdown Sets copied to Clipboard."; - public static string MsgBackupCreateLocation { get; set; } = "PKHeX can perform automatic backups if you create a folder with the name '{0}' in the same folder as PKHeX's executable."; - public static string MsgBackupCreateQuestion { get; set; } = "Would you like to create the backup folder now?"; - public static string MsgBackupDelete { get; set; } = "If you wish to no longer automatically back up save files, delete the '{0}' folder."; - public static string MsgBackupUnable { get; set; } = "Unable to create backup folder"; - public static string MsgBackupSuccess { get; set; } = "Backup folder created!"; + public static string MsgBackupCreateLocation { get; set; } = "PKHeX can perform automatic backups if you create a folder with the name '{0}' in the same folder as PKHeX's executable."; + public static string MsgBackupCreateQuestion { get; set; } = "Would you like to create the backup folder now?"; + public static string MsgBackupDelete { get; set; } = "If you wish to no longer automatically back up save files, delete the '{0}' folder."; + public static string MsgBackupUnable { get; set; } = "Unable to create backup folder"; + public static string MsgBackupSuccess { get; set; } = "Backup folder created!"; - public static string MsgDatabase { get; set; } = "PKHeX's database was not found."; - public static string MsgDatabaseAdvice { get; set; } = "Please dump all boxes from a save file, then ensure the '{0}' folder exists."; - public static string MsgDatabaseExport { get; set; } = "Save to PKHeX's database?"; - public static string MsgDatabaseLoad { get; set; } = "Load from PKHeX's database?"; + public static string MsgDatabase { get; set; } = "PKHeX's database was not found."; + public static string MsgDatabaseAdvice { get; set; } = "Please dump all boxes from a save file, then ensure the '{0}' folder exists."; + public static string MsgDatabaseExport { get; set; } = "Save to PKHeX's database?"; + public static string MsgDatabaseLoad { get; set; } = "Load from PKHeX's database?"; - #endregion + #endregion - #region PKM Editor + #region PKM Editor - public static string MsgPKMLoadNull { get; set; } = "Attempted to load a null file."; - public static string MsgPKMSuggestionFormat { get; set; } = "Suggestions are not enabled for this PKM format."; - public static string MsgPKMSuggestionMoves { get; set; } = "Apply suggested current moves?"; - public static string MsgPKMSuggestionRelearn { get; set; } = "Apply suggested relearn moves?"; - public static string MsgPKMSuggestionNone { get; set; } = "Unable to provide a suggestion."; - public static string MsgPKMSuggestionStart { get; set; } = "Suggested:"; - public static string MsgPKMSuggestionMetLocation { get; set; } = "Met Location:"; - public static string MsgPKMSuggestionMetLevel { get; set; } = "Met Level:"; - public static string MsgPKMSuggestionLevel { get; set; } = "Current Level:"; + public static string MsgPKMLoadNull { get; set; } = "Attempted to load a null file."; + public static string MsgPKMSuggestionFormat { get; set; } = "Suggestions are not enabled for this PKM format."; + public static string MsgPKMSuggestionMoves { get; set; } = "Apply suggested current moves?"; + public static string MsgPKMSuggestionRelearn { get; set; } = "Apply suggested relearn moves?"; + public static string MsgPKMSuggestionNone { get; set; } = "Unable to provide a suggestion."; + public static string MsgPKMSuggestionStart { get; set; } = "Suggested:"; + public static string MsgPKMSuggestionMetLocation { get; set; } = "Met Location:"; + public static string MsgPKMSuggestionMetLevel { get; set; } = "Met Level:"; + public static string MsgPKMSuggestionLevel { get; set; } = "Current Level:"; - #endregion + #endregion - #region Save Editor + #region Save Editor - public static string MsgSaveExportSuccessPath { get; set; } = "SAV exported to:"; - public static string MsgSaveExportContinue { get; set; } = "Continue saving?"; + public static string MsgSaveExportSuccessPath { get; set; } = "SAV exported to:"; + public static string MsgSaveExportContinue { get; set; } = "Continue saving?"; - public static string MsgSaveSlotEmpty { get; set; } = "Can't have an empty/egg party."; - public static string MsgSaveSlotLocked { get; set; } = "Can't modify a locked slot."; - public static string MsgSaveSlotBadData { get; set; } = "Unable to set to this slot."; + public static string MsgSaveSlotEmpty { get; set; } = "Can't have an empty/egg party."; + public static string MsgSaveSlotLocked { get; set; } = "Can't modify a locked slot."; + public static string MsgSaveSlotBadData { get; set; } = "Unable to set to this slot."; - public static string MsgSaveBackup { get; set; } = "Saved Backup of current SAV to:"; - public static string MsgSaveBackupNotFound { get; set; } = "Original file has been moved; unable to copy a backup."; - public static string MsgSaveCurrentGeneration { get; set; } = "Current SAV Generation: {0}"; + public static string MsgSaveBackup { get; set; } = "Saved Backup of current SAV to:"; + public static string MsgSaveBackupNotFound { get; set; } = "Original file has been moved; unable to copy a backup."; + public static string MsgSaveCurrentGeneration { get; set; } = "Current SAV Generation: {0}"; - public static string MsgSaveBoxCloneFromTabs { get; set; } = "Clone Pokemon from Editing Tabs to all slots in {0}?"; - public static string MsgSaveBoxSortCurrent { get; set; } = "Sort Current Box?"; - public static string MsgSaveBoxSortCurrentFailBattle { get; set; } = "Battle Box slots prevent the sorting of box."; - public static string MsgSaveBoxSortCurrentSuccess { get; set; } = "Current Box sorted!"; - public static string MsgSaveBoxSortAll { get; set; } = "Sort ALL Boxes?!"; - public static string MsgSaveBoxSortAllFailBattle { get; set; } = "Battle Box slots prevent the sorting of all boxes."; - public static string MsgSaveBoxSortAllSuccess { get; set; } = "Boxes sorted!"; - public static string MsgSaveBoxClearCurrent { get; set; } = "Clear Current Box?"; - public static string MsgSaveBoxClearCurrentFailBattle { get; set; } = "Battle Box slots prevent the clearing of box."; - public static string MsgSaveBoxClearCurrentSuccess { get; set; } = "Current Box cleared!"; - public static string MsgSaveBoxClearAll { get; set; } = "Clear ALL Boxes?!"; - public static string MsgSaveBoxClearAllFailBattle { get; set; } = "Battle Box slots prevent the clearing of all boxes."; - public static string MsgSaveBoxClearAllSuccess { get; set; } = "Boxes cleared!"; + public static string MsgSaveBoxCloneFromTabs { get; set; } = "Clone Pokemon from Editing Tabs to all slots in {0}?"; + public static string MsgSaveBoxSortCurrent { get; set; } = "Sort Current Box?"; + public static string MsgSaveBoxSortCurrentFailBattle { get; set; } = "Battle Box slots prevent the sorting of box."; + public static string MsgSaveBoxSortCurrentSuccess { get; set; } = "Current Box sorted!"; + public static string MsgSaveBoxSortAll { get; set; } = "Sort ALL Boxes?!"; + public static string MsgSaveBoxSortAllFailBattle { get; set; } = "Battle Box slots prevent the sorting of all boxes."; + public static string MsgSaveBoxSortAllSuccess { get; set; } = "Boxes sorted!"; + public static string MsgSaveBoxClearCurrent { get; set; } = "Clear Current Box?"; + public static string MsgSaveBoxClearCurrentFailBattle { get; set; } = "Battle Box slots prevent the clearing of box."; + public static string MsgSaveBoxClearCurrentSuccess { get; set; } = "Current Box cleared!"; + public static string MsgSaveBoxClearAll { get; set; } = "Clear ALL Boxes?!"; + public static string MsgSaveBoxClearAllFailBattle { get; set; } = "Battle Box slots prevent the clearing of all boxes."; + public static string MsgSaveBoxClearAllSuccess { get; set; } = "Boxes cleared!"; - public static string MsgSaveBoxFailNone { get; set; } = "The currently loaded save file does not have boxes!"; - public static string MsgSaveBoxExportYes { get; set; } = "Yes: Export All Boxes"; - public static string MsgSaveBoxExportNo { get; set; } = "No: Export {0} (Box {1})"; - public static string MsgSaveBoxExportCancel { get; set; } = "Cancel: Abort"; - public static string MsgSaveBoxExportInvalid { get; set; } = "Invalid Box Data, unable to dump."; - public static string MsgSaveBoxExportPathCount { get; set; } = "Dumped Box(es) ({0} pkm) to path:"; - public static string MsgSaveBoxExportPathInvalid { get; set; } = "Invalid path specified."; - public static string MsgSaveBoxImportModifyIntro { get; set; } = "PKM Loading overrides:"; - public static string MsgSaveBoxImportModifyYes { get; set; } = "Yes - Modify .pk* when set to SAV"; - public static string MsgSaveBoxImportModifyCurrent { get; set; } = "Cancel - Use current settings ({0})"; - public static string MsgSaveBoxImportModifyNo { get; set; } = "No - Don't modify .pk*"; - public static string MsgSaveBoxImportNoFiles { get; set; } = "No files loaded."; - public static string MsgSaveBoxImportSuccess { get; set; } = "Loaded {0} files to boxes."; - public static string MsgSaveBoxImportClear { get; set; } = "Clear subsequent boxes when importing data?"; - public static string MsgSaveBoxImportClearNo { get; set; } = "If you only want to overwrite for new data, press no."; - public static string MsgSaveBoxImportPCBinary { get; set; } = "PC Binary loaded."; - public static string MsgSaveBoxImportPCFailBattle { get; set; } = "Battle Box slots prevent loading of PC data."; - public static string MsgSaveBoxImportBoxBinary { get; set; } = "Box Binary loaded."; - public static string MsgSaveBoxImportBoxFailBattle { get; set; } = "Battle Box slots in box prevent loading of box data."; - public static string MsgSaveBoxImportGroup { get; set; } = "Load Pokémon data from the file to the current box ({0})?"; - public static string MsgSaveBoxImportGroupSuccess { get; set; } = "Pokémon data from the file was loaded to box slots."; - public static string MsgSaveBoxImportOverwrite { get; set; } = "The current box will be overwritten."; - public static string MsgSaveBoxImportSkippedLocked { get; set; } = "Skipped {0} locked slot(s)."; + public static string MsgSaveBoxFailNone { get; set; } = "The currently loaded save file does not have boxes!"; + public static string MsgSaveBoxExportYes { get; set; } = "Yes: Export All Boxes"; + public static string MsgSaveBoxExportNo { get; set; } = "No: Export {0} (Box {1})"; + public static string MsgSaveBoxExportCancel { get; set; } = "Cancel: Abort"; + public static string MsgSaveBoxExportInvalid { get; set; } = "Invalid Box Data, unable to dump."; + public static string MsgSaveBoxExportPathCount { get; set; } = "Dumped Box(es) ({0} pk) to path:"; + public static string MsgSaveBoxExportPathInvalid { get; set; } = "Invalid path specified."; + public static string MsgSaveBoxImportModifyIntro { get; set; } = "PKM Loading overrides:"; + public static string MsgSaveBoxImportModifyYes { get; set; } = "Yes - Modify .pk* when set to SAV"; + public static string MsgSaveBoxImportModifyCurrent { get; set; } = "Cancel - Use current settings ({0})"; + public static string MsgSaveBoxImportModifyNo { get; set; } = "No - Don't modify .pk*"; + public static string MsgSaveBoxImportNoFiles { get; set; } = "No files loaded."; + public static string MsgSaveBoxImportSuccess { get; set; } = "Loaded {0} files to boxes."; + public static string MsgSaveBoxImportClear { get; set; } = "Clear subsequent boxes when importing data?"; + public static string MsgSaveBoxImportClearNo { get; set; } = "If you only want to overwrite for new data, press no."; + public static string MsgSaveBoxImportPCBinary { get; set; } = "PC Binary loaded."; + public static string MsgSaveBoxImportPCFailBattle { get; set; } = "Battle Box slots prevent loading of PC data."; + public static string MsgSaveBoxImportBoxBinary { get; set; } = "Box Binary loaded."; + public static string MsgSaveBoxImportBoxFailBattle { get; set; } = "Battle Box slots in box prevent loading of box data."; + public static string MsgSaveBoxImportGroup { get; set; } = "Load Pokémon data from the file to the current box ({0})?"; + public static string MsgSaveBoxImportGroupSuccess { get; set; } = "Pokémon data from the file was loaded to box slots."; + public static string MsgSaveBoxImportOverwrite { get; set; } = "The current box will be overwritten."; + public static string MsgSaveBoxImportSkippedLocked { get; set; } = "Skipped {0} locked slot(s)."; - public static string MsgSaveSwitchDaycareView { get; set; } = "Would you like to switch the view to the other Daycare?"; - public static string MsgSaveSwitchDaycareCurrent { get; set; } = "Currently viewing daycare {0}."; - public static string MsgSaveGen6Passerby { get; set; } = "Export Passerby Info to Clipboard?"; - public static string MsgSaveGen6FriendSafari { get; set; } = "No editing support for Friend Safari :("; - public static string MsgSaveGen6FriendSafariCheatDesc { get; set; } = "Unlock all 3 slots for each friend?"; - public static string MsgSaveGen2RTCResetPassword { get; set; } = "RTC Reset Password: {0:00000}"; - public static string MsgSaveGen2RTCResetBitflag { get; set; } = "Would you like to reset the RTC?"; - public static string MsgSaveJPEGExportFail { get; set; } = "No picture data found in the save file!"; + public static string MsgSaveSwitchDaycareView { get; set; } = "Would you like to switch the view to the other Daycare?"; + public static string MsgSaveSwitchDaycareCurrent { get; set; } = "Currently viewing daycare {0}."; + public static string MsgSaveGen6Passerby { get; set; } = "Export Passerby Info to Clipboard?"; + public static string MsgSaveGen6FriendSafari { get; set; } = "No editing support for Friend Safari :("; + public static string MsgSaveGen6FriendSafariCheatDesc { get; set; } = "Unlock all 3 slots for each friend?"; + public static string MsgSaveGen2RTCResetPassword { get; set; } = "RTC Reset Password: {0:00000}"; + public static string MsgSaveGen2RTCResetBitflag { get; set; } = "Would you like to reset the RTC?"; + public static string MsgSaveJPEGExportFail { get; set; } = "No picture data found in the save file!"; - public static string MsgSaveChecksumFailEdited { get; set; } = "Save has been edited. Cannot integrity check."; - public static string MsgSaveChecksumValid { get; set; } = "Checksums are valid."; - public static string MsgSaveChecksumFailExport { get; set; } = "Export Checksum Info to Clipboard?"; + public static string MsgSaveChecksumFailEdited { get; set; } = "Save has been edited. Cannot integrity check."; + public static string MsgSaveChecksumValid { get; set; } = "Checksums are valid."; + public static string MsgSaveChecksumFailExport { get; set; } = "Export Checksum Info to Clipboard?"; - public static string MsgIndexItemRange { get; set; } = "Item Index beyond range:"; - public static string MsgIndexItemGame { get; set; } = "Game can't obtain item:"; - public static string MsgIndexItemHeld { get; set; } = "Game can't hold item:"; - public static string MsgIndexSpeciesRange { get; set; } = "Species Index beyond range:"; - public static string MsgIndexSpeciesGame { get; set; } = "Game can't obtain species:"; - public static string MsgIndexMoveRange { get; set; } = "Move Index beyond range:"; - public static string MsgIndexMoveGame { get; set; } = "Game can't obtain move:"; - public static string MsgIndexAbilityRange { get; set; } = "Ability Index beyond range:"; - public static string MsgIndexAbilityGame { get; set; } = "Ability Index beyond range:"; + public static string MsgIndexItemRange { get; set; } = "Item Index beyond range:"; + public static string MsgIndexItemGame { get; set; } = "Game can't obtain item:"; + public static string MsgIndexItemHeld { get; set; } = "Game can't hold item:"; + public static string MsgIndexSpeciesRange { get; set; } = "Species Index beyond range:"; + public static string MsgIndexSpeciesGame { get; set; } = "Game can't obtain species:"; + public static string MsgIndexMoveRange { get; set; } = "Move Index beyond range:"; + public static string MsgIndexMoveGame { get; set; } = "Game can't obtain move:"; + public static string MsgIndexAbilityRange { get; set; } = "Ability Index beyond range:"; + public static string MsgIndexAbilityGame { get; set; } = "Ability Index beyond range:"; - #endregion + #endregion - #region QR Codes + #region QR Codes - public static string MsgQRDecodeFail { get; set; } = "Decoded data not a valid PKM/Gift."; - public static string MsgQRDecodeSize { get; set; } = "QR Data Size: {0} bytes"; - public static string MsgQRDeprecated { get; set; } = "QR codes are deprecated in favor of other methods."; - public static string MsgQRAlternative { get; set; } = "Consider utilizing homebrew or on-the-fly RAM editing custom firmware (PKMN-NTR)."; - public static string MsgQRClipboardUrl { get; set; } = "Copy QR URL to Clipboard?"; - public static string MsgQRClipboardImage { get; set; } = "Copy QR Image to Clipboard?"; - public static string MsgQRClipboardFail { get; set; } = "Failed to set Image to Clipboard"; - public static string MsgQRUrlFailPath { get; set; } = "Clipboard text is not a valid URL:"; - public static string MsgQRUrlFailImage { get; set; } = "Reader could not find QR data in the image."; - public static string MsgQRUrlFailType { get; set; } = "Input URL is not valid. Double check that it is an image (jpg/png)."; - public static string MsgQRUrlFailConnection { get; set; } = "Unable to connect to the internet to decode QR code."; - public static string MsgQRUrlFailConvert { get; set; } = "QR string to Data failed."; + public static string MsgQRDecodeFail { get; set; } = "Decoded data not a valid PKM/Gift."; + public static string MsgQRDecodeSize { get; set; } = "QR Data Size: {0} bytes"; + public static string MsgQRDeprecated { get; set; } = "QR codes are deprecated in favor of other methods."; + public static string MsgQRAlternative { get; set; } = "Consider utilizing homebrew or on-the-fly RAM editing custom firmware (PKMN-NTR)."; + public static string MsgQRClipboardUrl { get; set; } = "Copy QR URL to Clipboard?"; + public static string MsgQRClipboardImage { get; set; } = "Copy QR Image to Clipboard?"; + public static string MsgQRClipboardFail { get; set; } = "Failed to set Image to Clipboard"; + public static string MsgQRUrlFailPath { get; set; } = "Clipboard text is not a valid URL:"; + public static string MsgQRUrlFailImage { get; set; } = "Reader could not find QR data in the image."; + public static string MsgQRUrlFailType { get; set; } = "Input URL is not valid. Double check that it is an image (jpg/png)."; + public static string MsgQRUrlFailConnection { get; set; } = "Unable to connect to the internet to decode QR code."; + public static string MsgQRUrlFailConvert { get; set; } = "QR string to Data failed."; - #endregion + #endregion - #region Database + #region Database - public static string MsgDBDeleteFailBackup { get; set; } = "Can't delete from a backup save."; - public static string MsgDBDeleteFailModified { get; set; } = "Database slot data does not match save data!"; - public static string MsgDBDeleteFailWarning { get; set; } = "Don't move Pokémon after initializing the Database, please re-open the Database viewer."; - public static string MsgDBAddFailExistsFile { get; set; } = "File already exists in database!"; - public static string MsgDBAddFailExistsPKM { get; set; } = "Pokémon already exists in database."; - public static string MsgDBAddFromTabsSuccess { get; set; } = "Added Pokémon from tabs to database."; - public static string MsgDBCreateReportPrompt { get; set; } = "Generate a Report on all data?"; - public static string MsgDBCreateReportWarning { get; set; } = "This may take a while..."; - public static string MsgDBCreateReportFail { get; set; } = "No results to export."; - public static string MsgDBExportResultsPrompt { get; set; } = "Export results to a folder?"; - public static string MsgDBSearchLegalityWordfilter { get; set; } = "Check wordfilter legality?"; - public static string MsgDBSearchFail { get; set; } = "No data source to search!"; - public static string MsgDBSearchNone { get; set; } = "No results found!"; - public static string MsgDBDeleteCloneWarning { get; set; } = "Deleting clones from database is not reversible."; - public static string MsgDBDeleteCloneAdvice { get; set; } = "If a PKM is deemed a clone, only the newest file (date modified) will be kept."; - public static string MsgDBDeleteCloneFail { get; set; } = "Unable to delete clone:"; - public static string MsgDBDeleteCloneNone { get; set; } = "No clones detected or deleted."; - public static string MsgExportWC3DataFail { get; set; } = "Unable to save WC3 data. No data to save!"; + public static string MsgDBDeleteFailBackup { get; set; } = "Can't delete from a backup save."; + public static string MsgDBDeleteFailModified { get; set; } = "Database slot data does not match save data!"; + public static string MsgDBDeleteFailWarning { get; set; } = "Don't move Pokémon after initializing the Database, please re-open the Database viewer."; + public static string MsgDBAddFailExistsFile { get; set; } = "File already exists in database!"; + public static string MsgDBAddFailExistsPKM { get; set; } = "Pokémon already exists in database."; + public static string MsgDBAddFromTabsSuccess { get; set; } = "Added Pokémon from tabs to database."; + public static string MsgDBCreateReportPrompt { get; set; } = "Generate a Report on all data?"; + public static string MsgDBCreateReportWarning { get; set; } = "This may take a while..."; + public static string MsgDBCreateReportFail { get; set; } = "No results to export."; + public static string MsgDBExportResultsPrompt { get; set; } = "Export results to a folder?"; + public static string MsgDBSearchLegalityWordfilter { get; set; } = "Check wordfilter legality?"; + public static string MsgDBSearchFail { get; set; } = "No data source to search!"; + public static string MsgDBSearchNone { get; set; } = "No results found!"; + public static string MsgDBDeleteCloneWarning { get; set; } = "Deleting clones from database is not reversible."; + public static string MsgDBDeleteCloneAdvice { get; set; } = "If a PKM is deemed a clone, only the newest file (date modified) will be kept."; + public static string MsgDBDeleteCloneFail { get; set; } = "Unable to delete clone:"; + public static string MsgDBDeleteCloneNone { get; set; } = "No clones detected or deleted."; + public static string MsgExportWC3DataFail { get; set; } = "Unable to save WC3 data. No data to save!"; - #endregion + #endregion - #region Batch Editor + #region Batch Editor - public static string MsgBEToolTipPropName { get; set; } = "Property of a given PKM to modify."; - public static string MsgBEToolTipPropType { get; set; } = "PropertyType of the currently loaded PKM in the main window."; - public static string MsgBEToolTipPropValue { get; set; } = "PropertyValue of the currently loaded PKM in the main window."; + public static string MsgBEToolTipPropName { get; set; } = "Property of a given PKM to modify."; + public static string MsgBEToolTipPropType { get; set; } = "PropertyType of the currently loaded PKM in the main window."; + public static string MsgBEToolTipPropValue { get; set; } = "PropertyValue of the currently loaded PKM in the main window."; - public static string MsgBEFilterEmpty { get; set; } = "Empty Filter Value detected."; - public static string MsgBEPropertyEmpty { get; set; } = "Empty Property Value(s) detected:"; - public static string MsgBEPropertyInvalid { get; set; } = "Invalid property selected."; - public static string MsgBEInstructionInvalid { get; set; } = "Line length error in instruction list."; - public static string MsgBEInstructionNone { get; set; } = "No instructions defined for a modification set."; + public static string MsgBEFilterEmpty { get; set; } = "Empty Filter Value detected."; + public static string MsgBEPropertyEmpty { get; set; } = "Empty Property Value(s) detected:"; + public static string MsgBEPropertyInvalid { get; set; } = "Invalid property selected."; + public static string MsgBEInstructionInvalid { get; set; } = "Line length error in instruction list."; + public static string MsgBEInstructionNone { get; set; } = "No instructions defined for a modification set."; - public static string MsgBEModifySuccess { get; set; } = "Modified {0}{1}/{2} files."; - public static string MsgBEModifyFailBlocked { get; set; } = "Skipped a file due to disallowed value:"; - public static string MsgBEModifyFailError { get; set; } = "{0} files ignored due to an internal error."; - public static string MsgBEModifyFail { get; set; } = "Unable to modify {0} to {1}."; - public static string MsgBEModifyFailCompare { get; set; } = "Unable to compare {0} to {1}."; + public static string MsgBEModifySuccess { get; set; } = "Modified {0}{1}/{2} files."; + public static string MsgBEModifyFailBlocked { get; set; } = "Skipped a file due to disallowed value:"; + public static string MsgBEModifyFailError { get; set; } = "{0} files ignored due to an internal error."; + public static string MsgBEModifyFail { get; set; } = "Unable to modify {0} to {1}."; + public static string MsgBEModifyFailCompare { get; set; } = "Unable to compare {0} to {1}."; - public static string MsgExportFolder { get; set; } = "Please select the folder where the files will be saved to."; - public static string MsgExportFolderAdvice { get; set; } = "This can be the same folder as the source of PKM files."; + public static string MsgExportFolder { get; set; } = "Please select the folder where the files will be saved to."; + public static string MsgExportFolderAdvice { get; set; } = "This can be the same folder as the source of PKM files."; - #endregion + #endregion - #region Misc + #region Misc - public static string MsgReportExportTable { get; set; } = "Copy as formatted table?"; - public static string MsgReportExportCSV { get; set; } = "Save all the data to CSV?"; - public static string MsgReportColumnHide { get; set; } = "Hide Column"; - public static string MsgReportColumnRestore { get; set; } = "Restore Columns"; - public static string MsgReportColumnHideFail { get; set; } = "No cells/Columns selected."; - public static string MsgReportColumnRestoreSuccess { get; set; } = "Column visibility restored."; + public static string MsgReportExportTable { get; set; } = "Copy as formatted table?"; + public static string MsgReportExportCSV { get; set; } = "Save all the data to CSV?"; + public static string MsgReportColumnHide { get; set; } = "Hide Column"; + public static string MsgReportColumnRestore { get; set; } = "Restore Columns"; + public static string MsgReportColumnHideFail { get; set; } = "No cells/Columns selected."; + public static string MsgReportColumnRestoreSuccess { get; set; } = "Column visibility restored."; - #endregion + #endregion - #region Mystery Gift + #region Mystery Gift - public static string MsgMsyteryGiftUsedAlert { get; set; } = "Mystery Gift is marked as USED and will not be able to be picked up in-game."; - public static string MsgMysteryGiftUsedFix { get; set; } = "Do you want to remove the USED flag so that it is UNUSED?"; - public static string MsgMysteryGiftInvalid { get; set; } = "File is not a Mystery Gift:"; - public static string MsgMysteryGiftTypeUnexpected { get; set; } = "Expected Mystery Gift Type(s):"; - public static string MsgMysteryGiftTypeDetails { get; set; } = "Gift Details are not compatible with the save file."; - public static string MsgMysteryGiftTypeIncompatible { get; set; } = "Gift type is not compatible with the save file."; - public static string MsgMysteryGiftParseFail { get; set; } = "Unable to create gift description."; - public static string MsgMysteryGiftParseTypeUnknown { get; set; } = "Unknown Mystery Gift Type!"; + public static string MsgMsyteryGiftUsedAlert { get; set; } = "Mystery Gift is marked as USED and will not be able to be picked up in-game."; + public static string MsgMysteryGiftUsedFix { get; set; } = "Do you want to remove the USED flag so that it is UNUSED?"; + public static string MsgMysteryGiftInvalid { get; set; } = "File is not a Mystery Gift:"; + public static string MsgMysteryGiftTypeUnexpected { get; set; } = "Expected Mystery Gift Type(s):"; + public static string MsgMysteryGiftTypeDetails { get; set; } = "Gift Details are not compatible with the save file."; + public static string MsgMysteryGiftTypeIncompatible { get; set; } = "Gift type is not compatible with the save file."; + public static string MsgMysteryGiftParseFail { get; set; } = "Unable to create gift description."; + public static string MsgMysteryGiftParseTypeUnknown { get; set; } = "Unknown Mystery Gift Type!"; - public static string MsgMysteryGiftSlotEmpty { get; set; } = "Empty Slot. No data!"; - public static string MsgMysteryGiftSlotFail { get; set; } = "Unable to insert the Mystery Gift."; - public static string MsgMysteryGiftSlotNone { get; set; } = "No Mystery Gift data found in loaded slot!"; - public static string MsgMysteryGiftSlotSpecialReject { get; set; } = "Does this Mystery Gift really belong to this game?"; - public static string MsgMysteryGiftSlotAlternate { get; set; } = "{0} gift was set to to a {1} slot instead."; - public static string MsgMysteryGiftSlotFailSwap { get; set; } = "Can't swap a {0} with a {1}."; + public static string MsgMysteryGiftSlotEmpty { get; set; } = "Empty Slot. No data!"; + public static string MsgMysteryGiftSlotFail { get; set; } = "Unable to insert the Mystery Gift."; + public static string MsgMysteryGiftSlotNone { get; set; } = "No Mystery Gift data found in loaded slot!"; + public static string MsgMysteryGiftSlotSpecialReject { get; set; } = "Does this Mystery Gift really belong to this game?"; + public static string MsgMysteryGiftSlotAlternate { get; set; } = "{0} gift was set to to a {1} slot instead."; + public static string MsgMysteryGiftSlotFailSwap { get; set; } = "Can't swap a {0} with a {1}."; - public static string MsgMysteryGiftQRTypeLength { get; set; } = "Decoded data length not valid for loaded save file."; - public static string MsgMysteryGiftQRReceived { get; set; } = "QR Gift Type:"; - public static string MsgMysteryGiftQREonTicket { get; set; } = "Eon Ticket Mystery Gift will not import correctly when received via QR."; - public static string MsgMysteryGiftQREonTicketAdvice { get; set; } = "Inject the Eon Ticket Mystery Gift to the save file instead."; + public static string MsgMysteryGiftQRTypeLength { get; set; } = "Decoded data length not valid for loaded save file."; + public static string MsgMysteryGiftQRReceived { get; set; } = "QR Gift Type:"; + public static string MsgMysteryGiftQREonTicket { get; set; } = "Eon Ticket Mystery Gift will not import correctly when received via QR."; + public static string MsgMysteryGiftQREonTicketAdvice { get; set; } = "Inject the Eon Ticket Mystery Gift to the save file instead."; - #endregion + #endregion - #region Inventory + #region Inventory - public static string MsgItemPouchUnknown { get; set; } = "Unknown item detected."; - public static string MsgItemPouchCountUpdated { get; set; } = "Item count modified."; - public static string MsgItemCleared { get; set; } = "Items cleared."; - public static string MsgItemPouchSizeSmall { get; set; } = "Pouch is too small for all items."; - public static string MsgItemPouchRandom { get; set; } = "Yes: Give by Item ID{0}No: Random assortment"; - public static string MsgItemPouchWarning { get; set; } = "If you save changes, the item(s) will no longer be in the pouch."; - public static string MsgItemPouchRemoved { get; set; } = "The following item(s) have been removed from the {0} pouch."; + public static string MsgItemPouchUnknown { get; set; } = "Unknown item detected."; + public static string MsgItemPouchCountUpdated { get; set; } = "Item count modified."; + public static string MsgItemCleared { get; set; } = "Items cleared."; + public static string MsgItemPouchSizeSmall { get; set; } = "Pouch is too small for all items."; + public static string MsgItemPouchRandom { get; set; } = "Yes: Give by Item ID{0}No: Random assortment"; + public static string MsgItemPouchWarning { get; set; } = "If you save changes, the item(s) will no longer be in the pouch."; + public static string MsgItemPouchRemoved { get; set; } = "The following item(s) have been removed from the {0} pouch."; - #endregion + #endregion - #region Misc + #region Misc - public static string MsgSaveDifferentTypes { get; set; } = "Save File types are different."; - public static string MsgSaveDifferentVersions { get; set; } = "Save File versions are not the same."; - public static string MsgSaveNumberInvalid { get; set; } = "Save File {0} is not valid."; + public static string MsgSaveDifferentTypes { get; set; } = "Save File types are different."; + public static string MsgSaveDifferentVersions { get; set; } = "Save File versions are not the same."; + public static string MsgSaveNumberInvalid { get; set; } = "Save File {0} is not valid."; - public static string MsgSecretBaseDeleteConfirm { get; set; } = "Delete {0}'s secret base (Entry {1:00}) from your records?"; - public static string MsgSecretBaseDeleteSelf { get; set; } = "Cannot delete your Secret Base."; + public static string MsgSecretBaseDeleteConfirm { get; set; } = "Delete {0}'s secret base (Entry {1:00}) from your records?"; + public static string MsgSecretBaseDeleteSelf { get; set; } = "Cannot delete your Secret Base."; - public static string MsgPluginFailLoad { get; set; } = "Plugins failed to load. Please refer to the error message to identify the faulty plugin. A plugin may be out of date / incompatible with this build of the program."; + public static string MsgPluginFailLoad { get; set; } = "Plugins failed to load. Please refer to the error message to identify the faulty plugin. A plugin may be out of date / incompatible with this build of the program."; - #endregion - } + #endregion } diff --git a/PKHeX.Core/Util/NetUtil.cs b/PKHeX.Core/Util/NetUtil.cs index bf440aa75..a969ed6e0 100644 --- a/PKHeX.Core/Util/NetUtil.cs +++ b/PKHeX.Core/Util/NetUtil.cs @@ -3,38 +3,37 @@ using System.IO; using System.Net; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class NetUtil { - public static class NetUtil + public static string? GetStringFromURL(Uri url) { - public static string? GetStringFromURL(string url) + try { - try - { - var stream = GetStreamFromURL(url); - if (stream == null) - return null; - - using var reader = new StreamReader(stream); - return reader.ReadToEnd(); - } - // No internet? - catch (Exception e) - { - Debug.WriteLine(e.Message); + var stream = GetStreamFromURL(url); + if (stream == null) return null; - } + + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); } - - private static Stream? GetStreamFromURL(string url) + // No internet? + catch (Exception e) { - var httpWebRequest = (HttpWebRequest)WebRequest.Create(url); - - // The GitHub API will fail if no user agent is provided - httpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"; - - var httpWebResponse = httpWebRequest.GetResponse(); - return httpWebResponse.GetResponseStream(); + Debug.WriteLine(e.Message); + return null; } } + + private static Stream? GetStreamFromURL(Uri url) + { + var httpWebRequest = (HttpWebRequest)WebRequest.Create(url); + + // The GitHub API will fail if no user agent is provided + httpWebRequest.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"; + + var httpWebResponse = httpWebRequest.GetResponse(); + return httpWebResponse.GetResponseStream(); + } } diff --git a/PKHeX.Core/Util/PathUtil.cs b/PKHeX.Core/Util/PathUtil.cs index d32e852cf..02e19c14f 100644 --- a/PKHeX.Core/Util/PathUtil.cs +++ b/PKHeX.Core/Util/PathUtil.cs @@ -1,16 +1,15 @@ using System.IO; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Util { - public static partial class Util + /// + /// Cleans the local by removing any invalid filename characters. + /// + /// New string without any invalid characters. + public static string CleanFileName(string fileName) { - /// - /// Cleans the local by removing any invalid filename characters. - /// - /// New string without any invalid characters. - public static string CleanFileName(string fileName) - { - return string.Concat(fileName.Split(Path.GetInvalidFileNameChars())); - } + return string.Concat(fileName.Split(Path.GetInvalidFileNameChars())); } } diff --git a/PKHeX.Core/Util/RandUtil.cs b/PKHeX.Core/Util/RandUtil.cs index e7a59eca2..92bfcc594 100644 --- a/PKHeX.Core/Util/RandUtil.cs +++ b/PKHeX.Core/Util/RandUtil.cs @@ -1,48 +1,47 @@ -using System; +using System; using System.Threading; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Util { - public static partial class Util + // Multithread safe rand, ha + public static Random Rand => _local.Value; + private static int randomSeed = Environment.TickCount; + + private static readonly ThreadLocal _local = new(() => { - // Multithread safe rand, ha - public static Random Rand => _local.Value; - private static int randomSeed = Environment.TickCount; + // threads should never really step on each other when starting, but we'll play it safe. + var seed = Interlocked.Increment(ref randomSeed); + var mix = (0x41C64E6D * seed) + 0x00006073; // why not + return new Random(mix); + }); - private static readonly ThreadLocal _local = new(() => + public static uint Rand32() => Rand32(Rand); + public static uint Rand32(this Random rnd) => ((uint)rnd.Next(1 << 30) << 2) | (uint)rnd.Next(1 << 2); + public static ulong Rand64(this Random rnd) => rnd.Rand32() | ((ulong)rnd.Rand32() << 32); + + /// + /// Shuffles the order of items within a collection of items. + /// + /// Item type + /// Item collection + public static void Shuffle(Span items) => Shuffle(items, 0, items.Length, Rand); + + /// + /// Shuffles the order of items within a collection of items. + /// + /// Item type + /// Item collection + /// Starting position + /// Ending position + /// RNG object to use + public static void Shuffle(Span items, int start, int end, Random rnd) + { + for (int i = start; i < end; i++) { - // threads should never really step on each other when starting, but we'll play it safe. - var seed = Interlocked.Increment(ref randomSeed); - var mix = (0x41C64E6D * seed) + 0x00006073; // why not - return new Random(mix); - }); - - public static uint Rand32() => Rand32(Rand); - public static uint Rand32(this Random rnd) => (uint)rnd.Next(1 << 30) << 2 | (uint)rnd.Next(1 << 2); - public static ulong Rand64(this Random rnd) => rnd.Rand32() | (ulong)rnd.Rand32() << 32; - - /// - /// Shuffles the order of items within a collection of items. - /// - /// Item type - /// Item collection - public static void Shuffle(Span items) => Shuffle(items, 0, items.Length, Rand); - - /// - /// Shuffles the order of items within a collection of items. - /// - /// Item type - /// Item collection - /// Starting position - /// Ending position - /// RNG object to use - public static void Shuffle(Span items, int start, int end, Random rnd) - { - for (int i = start; i < end; i++) - { - int index = i + rnd.Next(end - i); - (items[index], items[i]) = (items[i], items[index]); - } + int index = i + rnd.Next(end - i); + (items[index], items[i]) = (items[i], items[index]); } } } diff --git a/PKHeX.Core/Util/ReflectUtil.cs b/PKHeX.Core/Util/ReflectUtil.cs index 703b86ba9..f461fe8cf 100644 --- a/PKHeX.Core/Util/ReflectUtil.cs +++ b/PKHeX.Core/Util/ReflectUtil.cs @@ -5,153 +5,152 @@ using System.Linq; using System.Reflection; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class ReflectUtil { - public static class ReflectUtil + public static bool IsValueEqual(this PropertyInfo pi, object obj, object value) { - public static bool IsValueEqual(this PropertyInfo pi, object obj, object value) - { - var v = pi.GetValue(obj, null); - var c = ConvertValue(value, pi.PropertyType); - return v.Equals(c); - } + var v = pi.GetValue(obj, null); + var c = ConvertValue(value, pi.PropertyType); + return v.Equals(c); + } - public static void SetValue(PropertyInfo pi, object obj, object value) - { - var c = ConvertValue(value, pi.PropertyType); - pi.SetValue(obj, c, null); - } + public static void SetValue(PropertyInfo pi, object obj, object value) + { + var c = ConvertValue(value, pi.PropertyType); + pi.SetValue(obj, c, null); + } - public static object? GetValue(object obj, string name) => GetPropertyInfo(obj.GetType().GetTypeInfo(), name)?.GetValue(obj); - public static void SetValue(object obj, string name, object value) => GetPropertyInfo(obj.GetType().GetTypeInfo(), name)?.SetValue(obj, value, null); - public static object GetValue(Type t, string propertyName) => t.GetTypeInfo().GetDeclaredProperty(propertyName).GetValue(null); - public static void SetValue(Type t, string propertyName, object value) => t.GetTypeInfo().GetDeclaredProperty(propertyName).SetValue(null, value); + public static object? GetValue(object obj, string name) => GetPropertyInfo(obj.GetType().GetTypeInfo(), name)?.GetValue(obj); + public static void SetValue(object obj, string name, object value) => GetPropertyInfo(obj.GetType().GetTypeInfo(), name)?.SetValue(obj, value, null); + public static object GetValue(Type t, string propertyName) => t.GetTypeInfo().GetDeclaredProperty(propertyName).GetValue(null); + public static void SetValue(Type t, string propertyName, object value) => t.GetTypeInfo().GetDeclaredProperty(propertyName).SetValue(null, value); - public static IEnumerable GetPropertiesStartWithPrefix(Type type, string prefix) - { - return type.GetTypeInfo().GetAllTypeInfo().SelectMany(GetAllProperties) + public static IEnumerable GetPropertiesStartWithPrefix(Type type, string prefix) + { + return type.GetTypeInfo().GetAllTypeInfo().SelectMany(GetAllProperties) .Where(p => p.Name.StartsWith(prefix, StringComparison.Ordinal)) .Select(p => p.Name) .Distinct() - ; - } + ; + } - public static IEnumerable GetPropertiesCanWritePublic(Type type) - { - return GetAllPropertyInfoCanWritePublic(type).Select(p => p.Name) - .Distinct() - ; - } + public static IEnumerable GetPropertiesCanWritePublic(Type type) + { + return GetAllPropertyInfoCanWritePublic(type).Select(p => p.Name) + .Distinct() + ; + } - public static IEnumerable GetAllPropertyInfoCanWritePublic(Type type) - { - return type.GetTypeInfo().GetAllTypeInfo().SelectMany(GetAllProperties) - .Where(p => p.CanWrite && p.SetMethod.IsPublic); - } + public static IEnumerable GetAllPropertyInfoCanWritePublic(Type type) + { + return type.GetTypeInfo().GetAllTypeInfo().SelectMany(GetAllProperties) + .Where(p => p.CanWrite && p.SetMethod.IsPublic); + } - public static IEnumerable GetAllPropertyInfoPublic(Type type) - { - return type.GetTypeInfo().GetAllTypeInfo().SelectMany(GetAllProperties) - .Where(p => (p.CanRead && p.GetMethod.IsPublic) || (p.CanWrite && p.SetMethod.IsPublic)); - } + public static IEnumerable GetAllPropertyInfoPublic(Type type) + { + return type.GetTypeInfo().GetAllTypeInfo().SelectMany(GetAllProperties) + .Where(p => (p.CanRead && p.GetMethod.IsPublic) || (p.CanWrite && p.SetMethod.IsPublic)); + } - public static IEnumerable GetPropertiesPublic(Type type) - { - return GetAllPropertyInfoPublic(type).Select(p => p.Name) - .Distinct() - ; - } + public static IEnumerable GetPropertiesPublic(Type type) + { + return GetAllPropertyInfoPublic(type).Select(p => p.Name) + .Distinct() + ; + } - public static IEnumerable GetPropertiesCanWritePublicDeclared(Type type) - { - return type.GetTypeInfo().GetAllProperties() + public static IEnumerable GetPropertiesCanWritePublicDeclared(Type type) + { + return type.GetTypeInfo().GetAllProperties() .Where(p => p.CanWrite && p.SetMethod.IsPublic) .Select(p => p.Name) - .Distinct() - ; + .Distinct() + ; + } + + private static object? ConvertValue(object value, Type type) + { + if (type == typeof(DateTime?)) // Used for PKM.MetDate and other similar properties + { + return DateTime.TryParseExact(value.ToString(), "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateValue) + ? new DateTime?(dateValue) + : null; } - private static object? ConvertValue(object value, Type type) + if (type.IsEnum) { - if (type == typeof(DateTime?)) // Used for PKM.MetDate and other similar properties - { - return DateTime.TryParseExact(value.ToString(), "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateValue) - ? new DateTime?(dateValue) - : null; - } - - if (type.IsEnum) - { - var str = value.ToString(); - if (int.TryParse(str, out var integer)) - return Convert.ChangeType(integer, type); - return Enum.Parse(type, str, true); - } - // Convert.ChangeType is suitable for most things - return Convert.ChangeType(value, type); + var str = value.ToString(); + if (int.TryParse(str, out var integer)) + return Convert.ChangeType(integer, type); + return Enum.Parse(type, str, true); } + // Convert.ChangeType is suitable for most things + return Convert.ChangeType(value, type); + } - public static IEnumerable GetAllConstructors(this TypeInfo typeInfo) - => GetAll(typeInfo, ti => ti.DeclaredConstructors); + public static IEnumerable GetAllConstructors(this TypeInfo typeInfo) + => GetAll(typeInfo, ti => ti.DeclaredConstructors); - public static IEnumerable GetAllEvents(this TypeInfo typeInfo) - => GetAll(typeInfo, ti => ti.DeclaredEvents); + public static IEnumerable GetAllEvents(this TypeInfo typeInfo) + => GetAll(typeInfo, ti => ti.DeclaredEvents); - public static IEnumerable GetAllFields(this TypeInfo typeInfo) - => GetAll(typeInfo, ti => ti.DeclaredFields); + public static IEnumerable GetAllFields(this TypeInfo typeInfo) + => GetAll(typeInfo, ti => ti.DeclaredFields); - public static IEnumerable GetAllMembers(this TypeInfo typeInfo) - => GetAll(typeInfo, ti => ti.DeclaredMembers); + public static IEnumerable GetAllMembers(this TypeInfo typeInfo) + => GetAll(typeInfo, ti => ti.DeclaredMembers); - public static IEnumerable GetAllMethods(this TypeInfo typeInfo) - => GetAll(typeInfo, ti => ti.DeclaredMethods); + public static IEnumerable GetAllMethods(this TypeInfo typeInfo) + => GetAll(typeInfo, ti => ti.DeclaredMethods); - public static IEnumerable GetAllNestedTypes(this TypeInfo typeInfo) - => GetAll(typeInfo, ti => ti.DeclaredNestedTypes); + public static IEnumerable GetAllNestedTypes(this TypeInfo typeInfo) + => GetAll(typeInfo, ti => ti.DeclaredNestedTypes); - public static IEnumerable GetAllProperties(this TypeInfo typeInfo) - => GetAll(typeInfo, ti => ti.DeclaredProperties); + public static IEnumerable GetAllProperties(this TypeInfo typeInfo) + => GetAll(typeInfo, ti => ti.DeclaredProperties); - public static IEnumerable GetAllTypeInfo(this TypeInfo? typeInfo) + public static IEnumerable GetAllTypeInfo(this TypeInfo? typeInfo) + { + while (typeInfo != null) { - while (typeInfo != null) - { - yield return typeInfo; - typeInfo = typeInfo.BaseType?.GetTypeInfo(); - } - } - - /// - /// Checks if the has the requested property . - /// - /// Object to check for property existence. - /// Name of the property. - /// Reference to the property info for the object, if it exists. - /// True if has property, and false if does not have property. is null when returning false. - public static bool HasProperty(object obj, string name, [NotNullWhen(true)] out PropertyInfo? pi) => (pi = GetPropertyInfo(obj.GetType().GetTypeInfo(), name)) != null; - - public static PropertyInfo? GetPropertyInfo(this TypeInfo typeInfo, string name) - { - return typeInfo.GetAllTypeInfo().Select(t => t.GetDeclaredProperty(name)).FirstOrDefault(pi => pi != null); - } - - private static IEnumerable GetAll(this TypeInfo typeInfo, Func> accessor) - { - return GetAllTypeInfo(typeInfo).SelectMany(_ => accessor(typeInfo)); - } - - public static Dictionary GetAllConstantsOfType(this Type type) where T : struct - { - var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy); - var consts = fields.Where(fi => fi.IsLiteral && !fi.IsInitOnly && fi.FieldType == typeof(T)); - return consts.ToDictionary(x => (T)x.GetRawConstantValue(), z => z.Name); - } - - public static Dictionary GetAllPropertiesOfType(this Type type, object obj) where T : class - { - var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); - var ofType = props.Where(fi => typeof(T).IsAssignableFrom(fi.PropertyType)); - return ofType.ToDictionary(x => (T)x.GetValue(obj), z => z.Name); + yield return typeInfo; + typeInfo = typeInfo.BaseType?.GetTypeInfo(); } } + + /// + /// Checks if the has the requested property . + /// + /// Object to check for property existence. + /// Name of the property. + /// Reference to the property info for the object, if it exists. + /// True if has property, and false if does not have property. is null when returning false. + public static bool HasProperty(object obj, string name, [NotNullWhen(true)] out PropertyInfo? pi) => (pi = GetPropertyInfo(obj.GetType().GetTypeInfo(), name)) != null; + + public static PropertyInfo? GetPropertyInfo(this TypeInfo typeInfo, string name) + { + return typeInfo.GetAllTypeInfo().Select(t => t.GetDeclaredProperty(name)).FirstOrDefault(pi => pi != null); + } + + private static IEnumerable GetAll(this TypeInfo typeInfo, Func> accessor) + { + return GetAllTypeInfo(typeInfo).SelectMany(_ => accessor(typeInfo)); + } + + public static Dictionary GetAllConstantsOfType(this Type type) where T : struct + { + var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy); + var consts = fields.Where(fi => fi.IsLiteral && !fi.IsInitOnly && fi.FieldType == typeof(T)); + return consts.ToDictionary(x => (T)x.GetRawConstantValue(), z => z.Name); + } + + public static Dictionary GetAllPropertiesOfType(this Type type, object obj) where T : class + { + var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); + var ofType = props.Where(fi => typeof(T).IsAssignableFrom(fi.PropertyType)); + return ofType.ToDictionary(x => (T)x.GetValue(obj), z => z.Name); + } } diff --git a/PKHeX.Core/Util/ResourceUtil.cs b/PKHeX.Core/Util/ResourceUtil.cs index 69c3078c8..2fc79e077 100644 --- a/PKHeX.Core/Util/ResourceUtil.cs +++ b/PKHeX.Core/Util/ResourceUtil.cs @@ -3,226 +3,250 @@ using System.IO; using System.Reflection; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Util { - public static partial class Util + private static readonly Assembly thisAssembly = typeof(Util).GetTypeInfo().Assembly; + private static readonly Dictionary resourceNameMap = BuildLookup(thisAssembly.GetManifestResourceNames()); + + private static Dictionary BuildLookup(IReadOnlyCollection manifestNames) { - private static readonly Assembly thisAssembly = typeof(Util).GetTypeInfo().Assembly; - private static readonly Dictionary resourceNameMap = BuildLookup(thisAssembly.GetManifestResourceNames()); - - private static Dictionary BuildLookup(IReadOnlyCollection manifestNames) + var result = new Dictionary(manifestNames.Count); + foreach (var resName in manifestNames) { - var result = new Dictionary(manifestNames.Count); - foreach (var resName in manifestNames) - { - var period = resName.LastIndexOf('.', resName.Length - 5); - var start = period + 1; - System.Diagnostics.Debug.Assert(start != 0); + var fileName = GetFileName(resName); + result.Add(fileName, resName); + } + return result; + } - // text file fetch excludes ".txt" (mixed case...); other extensions are used (all lowercase). - var fileName = resName.EndsWith(".txt") ? resName[start..^4].ToLowerInvariant() : resName[start..]; - result.Add(fileName, resName); - } + private static string GetFileName(string resName) + { + var period = resName.LastIndexOf('.', resName.Length - 5); + var start = period + 1; + System.Diagnostics.Debug.Assert(start != 0); + + // text file fetch excludes ".txt" (mixed case...); other extensions are used (all lowercase). + return resName.EndsWith(".txt", StringComparison.Ordinal) ? resName[start..^4].ToLowerInvariant() : resName[start..]; + } + + private static readonly Dictionary stringListCache = new(); + + private static readonly object getStringListLoadLock = new(); + + #region String Lists + + /// + /// Gets a list of all Pokémon species names. + /// + /// Language of the Pokémon species names to select (e.g. "en", "fr", "jp", etc.) + /// An array of strings whose indexes correspond to the IDs of each Pokémon species name. + public static string[] GetSpeciesList(string language) => GetStringList("species", language); + + /// + /// Gets a list of all move names. + /// + /// Language of the move names to select (e.g. "en", "fr", "jp", etc.) + /// An array of strings whose indexes correspond to the IDs of each move name. + public static string[] GetMovesList(string language) => GetStringList("moves", language); + + /// + /// Gets a list of all Pokémon ability names. + /// + /// Language of the Pokémon ability names to select (e.g. "en", "fr", "jp", etc.) + /// An array of strings whose indexes correspond to the IDs of each Pokémon ability name. + public static string[] GetAbilitiesList(string language) => GetStringList("abilities", language); + + /// + /// Gets a list of all Pokémon nature names. + /// + /// Language of the Pokémon nature names to select (e.g. "en", "fr", "jp", etc.) + /// An array of strings whose indexes correspond to the IDs of each Pokémon nature name. + public static string[] GetNaturesList(string language) => GetStringList("natures", language); + + /// + /// Gets a list of all Pokémon form names. + /// + /// Language of the Pokémon form names to select (e.g. "en", "fr", "jp", etc.) + /// An array of strings whose indexes correspond to the IDs of each Pokémon form name. + public static string[] GetFormsList(string language) => GetStringList("forms", language); + + /// + /// Gets a list of all Pokémon type names. + /// + /// Language of the Pokémon type names to select (e.g. "en", "fr", "jp", etc.) + /// An array of strings whose indexes correspond to the IDs of each Pokémon type name. + public static string[] GetTypesList(string language) => GetStringList("types", language); + + /// + /// Gets a list of all Pokémon characteristic. + /// + /// Language of the Pokémon characteristic to select (e.g. "en", "fr", "jp", etc.) + /// An array of strings whose indexes correspond to the IDs of each Pokémon characteristic. + public static string[] GetCharacteristicsList(string language) => GetStringList("character", language); + + /// + /// Gets a list of all items. + /// + /// Language of the items to select (e.g. "en", "fr", "jp", etc.) + /// An array of strings whose indexes correspond to the IDs of each item. + public static string[] GetItemsList(string language) => GetStringList("items", language); + + /// + /// Retrieves the localization index list for all requested strings for the through Spanish. + /// + /// Base file name + /// Ignores Korean Language. + public static string[][] GetLanguageStrings7(string fileName) => new[] + { + Array.Empty(), // 0 - None + GetStringList(fileName, "ja"), // 1 + GetStringList(fileName, "en"), // 2 + GetStringList(fileName, "fr"), // 3 + GetStringList(fileName, "it"), // 4 + GetStringList(fileName, "de"), // 5 + Array.Empty(), // 6 - None + GetStringList(fileName, "es"), // 7 + }; + + /// + /// Retrieves the localization index list for all requested strings for the through Korean. + /// + /// Base file name + public static string[][] GetLanguageStrings8(string fileName) => new[] + { + Array.Empty(), // 0 - None + GetStringList(fileName, "ja"), // 1 + GetStringList(fileName, "en"), // 2 + GetStringList(fileName, "fr"), // 3 + GetStringList(fileName, "it"), // 4 + GetStringList(fileName, "de"), // 5 + Array.Empty(), // 6 - None + GetStringList(fileName, "es"), // 7 + GetStringList(fileName, "ko"), // 8 + }; + + /// + /// Retrieves the localization index list for all requested strings for the through Chinese. + /// + /// Base file name + /// String to use for the second Chinese localization. + public static string[][] GetLanguageStrings10(string fileName, string zh2 = "zh") => new[] + { + Array.Empty(), // 0 - None + GetStringList(fileName, "ja"), // 1 + GetStringList(fileName, "en"), // 2 + GetStringList(fileName, "fr"), // 3 + GetStringList(fileName, "it"), // 4 + GetStringList(fileName, "de"), // 5 + Array.Empty(), // 6 - None + GetStringList(fileName, "es"), // 7 + GetStringList(fileName, "ko"), // 8 + GetStringList(fileName, "zh"), // 9 + GetStringList(fileName, zh2), // 10 + }; + + #endregion + + public static string[] GetStringList(string fileName) + { + if (IsStringListCached(fileName, out var result)) return result; + var txt = GetStringResource(fileName); // Fetch File, \n to list. + return LoadStringList(fileName, txt); + } + + public static bool IsStringListCached(string fileName, out string[] result) + { + lock (getStringListLoadLock) // Make sure only one thread can read the cache + return stringListCache.TryGetValue(fileName, out result); + } + + /// + /// Loads a text into the program with a value of . + /// + /// Caches the result array for future fetches. + public static string[] LoadStringList(string file, string? txt) + { + if (txt == null) + return Array.Empty(); + string[] raw = FastSplit(txt.AsSpan()); + + lock (getStringListLoadLock) // Make sure only one thread can write to the cache + { + if (!stringListCache.ContainsKey(file)) // Check cache again in case of race condition + stringListCache.Add(file, raw); } - private static readonly Dictionary stringListCache = new(); + return raw; + } - private static readonly object getStringListLoadLock = new(); + public static string[] GetStringList(string fileName, string lang2char, string type = "text") => GetStringList(GetFullResourceName(fileName, lang2char, type)); - #region String Lists + private static string GetFullResourceName(string fileName, string lang2char, string type) => $"{type}_{fileName}_{lang2char}"; - /// - /// Gets a list of all Pokémon species names. - /// - /// Language of the Pokémon species names to select (e.g. "en", "fr", "jp", etc.) - /// An array of strings whose indexes correspond to the IDs of each Pokémon species name. - public static string[] GetSpeciesList(string language) => GetStringList("species", language); + public static byte[] GetBinaryResource(string name) + { + if (!resourceNameMap.TryGetValue(name, out var resName)) + return Array.Empty(); - /// - /// Gets a list of all move names. - /// - /// Language of the move names to select (e.g. "en", "fr", "jp", etc.) - /// An array of strings whose indexes correspond to the IDs of each move name. - public static string[] GetMovesList(string language) => GetStringList("moves", language); + using var resource = thisAssembly.GetManifestResourceStream(resName); + if (resource is null) + return Array.Empty(); - /// - /// Gets a list of all Pokémon ability names. - /// - /// Language of the Pokémon ability names to select (e.g. "en", "fr", "jp", etc.) - /// An array of strings whose indexes correspond to the IDs of each Pokémon ability name. - public static string[] GetAbilitiesList(string language) => GetStringList("abilities", language); + var buffer = new byte[resource.Length]; + _ = resource.Read(buffer, 0, (int)resource.Length); + return buffer; + } - /// - /// Gets a list of all Pokémon nature names. - /// - /// Language of the Pokémon nature names to select (e.g. "en", "fr", "jp", etc.) - /// An array of strings whose indexes correspond to the IDs of each Pokémon nature name. - public static string[] GetNaturesList(string language) => GetStringList("natures", language); + public static string? GetStringResource(string name) + { + if (!resourceNameMap.TryGetValue(name.ToLowerInvariant(), out var resourceName)) + return null; - /// - /// Gets a list of all Pokémon form names. - /// - /// Language of the Pokémon form names to select (e.g. "en", "fr", "jp", etc.) - /// An array of strings whose indexes correspond to the IDs of each Pokémon form name. - public static string[] GetFormsList(string language) => GetStringList("forms", language); + using var resource = thisAssembly.GetManifestResourceStream(resourceName); + if (resource is null) + return null; + using var reader = new StreamReader(resource); + return reader.ReadToEnd(); + } - /// - /// Gets a list of all Pokémon type names. - /// - /// Language of the Pokémon type names to select (e.g. "en", "fr", "jp", etc.) - /// An array of strings whose indexes correspond to the IDs of each Pokémon type name. - public static string[] GetTypesList(string language) => GetStringList("types", language); + private static string[] FastSplit(ReadOnlySpan s) + { + // Get Count + if (s.Length == 0) + return Array.Empty(); - /// - /// Gets a list of all Pokémon characteristic. - /// - /// Language of the Pokémon characteristic to select (e.g. "en", "fr", "jp", etc.) - /// An array of strings whose indexes correspond to the IDs of each Pokémon characteristic. - public static string[] GetCharacteristicsList(string language) => GetStringList("character", language); + var count = GetCount(s); + var result = new string[count]; - /// - /// Gets a list of all items. - /// - /// Language of the items to select (e.g. "en", "fr", "jp", etc.) - /// An array of strings whose indexes correspond to the IDs of each item. - public static string[] GetItemsList(string language) => GetStringList("items", language); - - /// - /// Retrieves the localization index list for all requested strings for the through Spanish. - /// - /// Base file name - /// Ignores Korean Language. - public static string[][] GetLanguageStrings7(string fileName) + var i = 0; + while (true) { - return new[] - { - Array.Empty(), // 0 - None - GetStringList(fileName, "ja"), // 1 - GetStringList(fileName, "en"), // 2 - GetStringList(fileName, "fr"), // 3 - GetStringList(fileName, "it"), // 4 - GetStringList(fileName, "de"), // 5 - Array.Empty(), // 6 - None - GetStringList(fileName, "es"), // 7 - }; - } + var index = s.IndexOf('\n'); + var length = index == -1 ? s.Length : index; + var slice = s[..length]; + if (slice.Length != 0 && slice[^1] == '\r') + slice = slice[..^1]; - /// - /// Retrieves the localization index list for all requested strings for the through Korean. - /// - /// Base file name - public static string[][] GetLanguageStrings8(string fileName) - { - return new[] - { - Array.Empty(), // 0 - None - GetStringList(fileName, "ja"), // 1 - GetStringList(fileName, "en"), // 2 - GetStringList(fileName, "fr"), // 3 - GetStringList(fileName, "it"), // 4 - GetStringList(fileName, "de"), // 5 - Array.Empty(), // 6 - None - GetStringList(fileName, "es"), // 7 - GetStringList(fileName, "ko"), // 8 - }; - } - - /// - /// Retrieves the localization index list for all requested strings for the through Chinese. - /// - /// Base file name - /// String to use for the second Chinese localization. - public static string[][] GetLanguageStrings10(string fileName, string zh2 = "zh") - { - return new[] - { - Array.Empty(), // 0 - None - GetStringList(fileName, "ja"), // 1 - GetStringList(fileName, "en"), // 2 - GetStringList(fileName, "fr"), // 3 - GetStringList(fileName, "it"), // 4 - GetStringList(fileName, "de"), // 5 - Array.Empty(), // 6 - None - GetStringList(fileName, "es"), // 7 - GetStringList(fileName, "ko"), // 8 - GetStringList(fileName, "zh"), // 9 - GetStringList(fileName, zh2), // 10 - }; - } - - #endregion - - public static string[] GetStringList(string fileName) - { - if (IsStringListCached(fileName, out var result)) + result[i++] = slice.ToString(); + if (i == count) return result; - var txt = GetStringResource(fileName); // Fetch File, \n to list. - return LoadStringList(fileName, txt); + s = s[(index + 1)..]; } + } - public static bool IsStringListCached(string fileName, out string[] result) + private static int GetCount(ReadOnlySpan s) + { + int count = 1; + while (true) { - lock (getStringListLoadLock) // Make sure only one thread can read the cache - return stringListCache.TryGetValue(fileName, out result); - } - - /// - /// Loads a text into the program with a value of . - /// - /// Caches the result array for future fetches. - public static string[] LoadStringList(string file, string? txt) - { - if (txt == null) - return Array.Empty(); - string[] raw = txt.Split('\n'); - for (int i = 0; i < raw.Length; i++) - { - // check for extra trimming; not all resources are "clean" with only \n line breaks. - var line = raw[i]; - if (line.Length == 0) - continue; - if (line[^1] == '\r') - raw[i] = line[..^1]; - } - - lock (getStringListLoadLock) // Make sure only one thread can write to the cache - { - if (!stringListCache.ContainsKey(file)) // Check cache again in case of race condition - stringListCache.Add(file, raw); - } - - return raw; - } - - public static string[] GetStringList(string fileName, string lang2char, string type = "text") => GetStringList(GetFullResourceName(fileName, lang2char, type)); - - private static string GetFullResourceName(string fileName, string lang2char, string type) => $"{type}_{fileName}_{lang2char}"; - - public static byte[] GetBinaryResource(string name) - { - if (!resourceNameMap.TryGetValue(name, out var resName)) - return Array.Empty(); - - using var resource = thisAssembly.GetManifestResourceStream(resName); - if (resource is null) - return Array.Empty(); - - var buffer = new byte[resource.Length]; - _ = resource.Read(buffer, 0, (int)resource.Length); - return buffer; - } - - public static string? GetStringResource(string name) - { - if (!resourceNameMap.TryGetValue(name.ToLowerInvariant(), out var resourceName)) - return null; - - using var resource = thisAssembly.GetManifestResourceStream(resourceName); - if (resource is null) - return null; - using var reader = new StreamReader(resource); - return reader.ReadToEnd(); + var index = s.IndexOf('\n'); + if (index == -1) + return count; + count++; + s = s[(index+1)..]; } } } diff --git a/PKHeX.Core/Util/StringUtil.cs b/PKHeX.Core/Util/StringUtil.cs index aa441d39a..36bde82b9 100644 --- a/PKHeX.Core/Util/StringUtil.cs +++ b/PKHeX.Core/Util/StringUtil.cs @@ -2,120 +2,119 @@ using System.Collections.Generic; using System.Globalization; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Utility for searching strings within arrays or within another string. +/// +public static class StringUtil { + private static readonly CompareInfo CompareInfo = CultureInfo.CurrentCulture.CompareInfo; + /// - /// Utility for searching strings within arrays or within another string. + /// Finds the index of the string within the array by ignoring casing, spaces, and punctuation. /// - public static class StringUtil + /// Array of strings to search in + /// Value to search for + /// Index within + public static int FindIndexIgnoreCase(string[] arr, string value) => Array.FindIndex(arr, z => IsMatchIgnoreCase(z, value)); + + /// + /// Gets the indexes by calling for all . + /// + /// Array of strings to search in + /// Items to search for + /// Index within + public static int[] GetIndexes(string[] arr, IReadOnlyList items) => GetIndexes(arr, items, 0, items.Count); + + /// + /// Gets the indexes by calling for all . + /// + /// Array of strings to search in + /// Items to search for + /// Starting index within + /// Amount to convert within + /// Index within + public static int[] GetIndexes(string[] arr, IReadOnlyList items, int start, int length) { - private static readonly CompareInfo CompareInfo = CultureInfo.CurrentCulture.CompareInfo; + if (length < 0) + length = items.Count - start; + var result = new int[length]; + for (int i = 0; i < result.Length; i++) + result[i] = FindIndexIgnoreCase(arr, items[start + i]); + return result; + } - /// - /// Finds the index of the string within the array by ignoring casing, spaces, and punctuation. - /// - /// Array of strings to search in - /// Value to search for - /// Index within - public static int FindIndexIgnoreCase(string[] arr, string value) => Array.FindIndex(arr, z => IsMatchIgnoreCase(z, value)); + public static bool IsMatchIgnoreCase(string string1, string string2) + { + if (string1.Length != string2.Length) + return false; + const CompareOptions options = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreWidth; + var compare = CompareInfo.Compare(string1, string2, options); + return compare == 0; + } - /// - /// Gets the indexes by calling for all . - /// - /// Array of strings to search in - /// Items to search for - /// Index within - public static int[] GetIndexes(string[] arr, IReadOnlyList items) => GetIndexes(arr, items, 0, items.Count); + /// + /// Gets the string entry within the input , based on the and position. + /// + public static string GetNthEntry(ReadOnlySpan line, int nth, int start, char separator = ',') + { + if (nth != 1) + start = line.IndexOfNth(separator, nth - 1, start + 1); + var end = line.IndexOfNth(separator, 1, start + 1); + if (end == -1) + return new string(line[(start + 1)..].ToArray()); + return new string(line[(start + 1)..end].ToArray()); + } - /// - /// Gets the indexes by calling for all . - /// - /// Array of strings to search in - /// Items to search for - /// Starting index within - /// Amount to convert within - /// Index within - public static int[] GetIndexes(string[] arr, IReadOnlyList items, int start, int length) + private static int IndexOfNth(this ReadOnlySpan s, char t, int n, int start) + { + int count = 0; + for (int i = start; i < s.Length; i++) { - if (length < 0) - length = items.Count - start; - var result = new int[length]; - for (int i = 0; i < result.Length; i++) - result[i] = FindIndexIgnoreCase(arr, items[start + i]); - return result; + if (s[i] != t) + continue; + if (++count == n) + return i; } + return -1; + } - public static bool IsMatchIgnoreCase(string string1, string string2) + /// + /// Converts an all-caps hex string to a byte array. + /// + public static byte[] ToByteArray(this string toTransform) + { + var result = new byte[toTransform.Length / 2]; + for (int i = 0; i < result.Length; i++) { - if (string1.Length != string2.Length) - return false; - const CompareOptions options = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreWidth; - var compare = CompareInfo.Compare(string1, string2, options); - return compare == 0; + var ofs = i << 1; + var _0 = toTransform[ofs + 0]; + var _1 = toTransform[ofs + 1]; + result[i] = DecodeTuple(_0, _1); } + return result; + } - /// - /// Gets the string entry within the input , based on the and position. - /// - public static string GetNthEntry(ReadOnlySpan line, int nth, int start, char separator = ',') - { - if (nth != 1) - start = line.IndexOfNth(separator, nth - 1, start + 1); - var end = line.IndexOfNth(separator, 1, start + 1); - if (end == -1) - return new string(line[(start + 1)..].ToArray()); - return new string(line[(start + 1)..end].ToArray()); - } + private static bool IsNum(char c) => (uint)(c - '0') <= 9; + private static bool IsHexUpper(char c) => (uint)(c - 'A') <= 5; - private static int IndexOfNth(this ReadOnlySpan s, char t, int n, int start) - { - int count = 0; - for (int i = start; i < s.Length; i++) - { - if (s[i] != t) - continue; - if (++count == n) - return i; - } - return -1; - } + private static byte DecodeTuple(char _0, char _1) + { + byte result; + if (IsNum(_0)) + result = (byte)((_0 - '0') << 4); + else if (IsHexUpper(_0)) + result = (byte)((_0 - 'A' + 10) << 4); + else + throw new ArgumentOutOfRangeException(nameof(_0)); - /// - /// Converts an all-caps hex string to a byte array. - /// - public static byte[] ToByteArray(this string toTransform) - { - var result = new byte[toTransform.Length / 2]; - for (int i = 0; i < result.Length; i++) - { - var ofs = i << 1; - var _0 = toTransform[ofs + 0]; - var _1 = toTransform[ofs + 1]; - result[i] = DecodeTuple(_0, _1); - } - return result; - } - - private static bool IsNum(char c) => (uint)(c - '0') <= 9; - private static bool IsHexUpper(char c) => (uint)(c - 'A') <= 5; - - private static byte DecodeTuple(char _0, char _1) - { - byte result; - if (IsNum(_0)) - result = (byte)((_0 - '0') << 4); - else if (IsHexUpper(_0)) - result = (byte)((_0 - 'A' + 10) << 4); - else - throw new ArgumentOutOfRangeException(nameof(_0)); - - if (IsNum(_1)) - result |= (byte)(_1 - '0'); - else if (IsHexUpper(_1)) - result |= (byte)(_1 - 'A' + 10); - else - throw new ArgumentOutOfRangeException(nameof(_1)); - return result; - } + if (IsNum(_1)) + result |= (byte)(_1 - '0'); + else if (IsHexUpper(_1)) + result |= (byte)(_1 - 'A' + 10); + else + throw new ArgumentOutOfRangeException(nameof(_1)); + return result; } } diff --git a/PKHeX.Core/Util/UpdateUtil.cs b/PKHeX.Core/Util/UpdateUtil.cs index 1bfe5f742..1d5de734f 100644 --- a/PKHeX.Core/Util/UpdateUtil.cs +++ b/PKHeX.Core/Util/UpdateUtil.cs @@ -1,35 +1,34 @@ using System; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static class UpdateUtil { - public static class UpdateUtil + /// + /// Gets the latest version of PKHeX according to the Github API + /// + /// A version representing the latest available version of PKHeX, or null if the latest version could not be determined + public static Version? GetLatestPKHeXVersion() { - /// - /// Gets the latest version of PKHeX according to the Github API - /// - /// A version representing the latest available version of PKHeX, or null if the latest version could not be determined - public static Version? GetLatestPKHeXVersion() - { - const string apiEndpoint = "https://api.github.com/repos/kwsch/pkhex/releases/latest"; - var responseJson = NetUtil.GetStringFromURL(apiEndpoint); - if (responseJson is null) - return null; + const string apiEndpoint = "https://api.github.com/repos/kwsch/pkhex/releases/latest"; + var responseJson = NetUtil.GetStringFromURL(new Uri(apiEndpoint)); + if (responseJson is null) + return null; - // Parse it manually; no need to parse the entire json to object. - const string tag = "tag_name"; - var index = responseJson.IndexOf(tag, StringComparison.Ordinal); - if (index == -1) - return null; + // Parse it manually; no need to parse the entire json to object. + const string tag = "tag_name"; + var index = responseJson.IndexOf(tag, StringComparison.Ordinal); + if (index == -1) + return null; - var first = responseJson.IndexOf('"', index + tag.Length + 1) + 1; - if (first == 0) - return null; - var second = responseJson.IndexOf('"', first); - if (second == -1) - return null; + var first = responseJson.IndexOf('"', index + tag.Length + 1) + 1; + if (first == 0) + return null; + var second = responseJson.IndexOf('"', first); + if (second == -1) + return null; - var tagString = responseJson[first..second]; - return !Version.TryParse(tagString, out var latestVersion) ? null : latestVersion; - } + var tagString = responseJson[first..second]; + return !Version.TryParse(tagString, out var latestVersion) ? null : latestVersion; } } diff --git a/PKHeX.Core/Util/Util.cs b/PKHeX.Core/Util/Util.cs index 9bd988e26..c6f6e6936 100644 --- a/PKHeX.Core/Util/Util.cs +++ b/PKHeX.Core/Util/Util.cs @@ -1,218 +1,217 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Text; -namespace PKHeX.Core +namespace PKHeX.Core; + +public static partial class Util { - public static partial class Util + /// + public static int ToInt32(string value) => ToInt32(value.AsSpan()); + + /// + public static uint ToUInt32(string value) => ToUInt32(value.AsSpan()); + + /// + public static uint GetHexValue(string value) => GetHexValue(value.AsSpan()); + + /// + public static ulong GetHexValue64(string value) => GetHexValue64(value.AsSpan()); + + /// + public static byte[] GetBytesFromHexString(string value) => GetBytesFromHexString(value.AsSpan()); + + /// + public static string GetHexStringFromBytes(byte[] data, int offset, int length) => GetHexStringFromBytes(data.AsSpan(offset, length)); + + /// + /// Parses the string into an , skipping all characters except for valid digits. + /// + /// String to parse + /// Parsed value + public static int ToInt32(ReadOnlySpan value) { - /// - public static int ToInt32(string value) => ToInt32(value.AsSpan()); + int result = 0; + if (value.Length == 0) + return result; - /// - public static uint ToUInt32(string value) => ToUInt32(value.AsSpan()); - - /// - public static uint GetHexValue(string value) => GetHexValue(value.AsSpan()); - - /// - public static ulong GetHexValue64(string value) => GetHexValue64(value.AsSpan()); - - /// - public static byte[] GetBytesFromHexString(string value) => GetBytesFromHexString(value.AsSpan()); - - /// - public static string GetHexStringFromBytes(byte[] data, int offset, int length) => GetHexStringFromBytes(data.AsSpan(offset, length)); - - /// - /// Parses the string into an , skipping all characters except for valid digits. - /// - /// String to parse - /// Parsed value - public static int ToInt32(ReadOnlySpan value) + bool negative = false; + foreach (var c in value) { - int result = 0; - if (value.Length == 0) - return result; - - bool negative = false; - foreach (var c in value) + if (IsNum(c)) { - if (IsNum(c)) - { - result *= 10; - result += c; - result -= '0'; - } - else if (c == '-' && result == 0) - { - negative = true; - } - } - return negative ? -result : result; - } - - /// - /// Parses the string into a , skipping all characters except for valid digits. - /// - /// String to parse - /// Parsed value - public static uint ToUInt32(ReadOnlySpan value) - { - uint result = 0; - if (value.Length == 0) - return result; - - foreach (var c in value) - { - if (!IsNum(c)) - continue; result *= 10; + result += c; + result -= '0'; + } + else if (c == '-' && result == 0) + { + negative = true; + } + } + return negative ? -result : result; + } + + /// + /// Parses the string into a , skipping all characters except for valid digits. + /// + /// String to parse + /// Parsed value + public static uint ToUInt32(ReadOnlySpan value) + { + uint result = 0; + if (value.Length == 0) + return result; + + foreach (var c in value) + { + if (!IsNum(c)) + continue; + result *= 10; + result += (uint)(c - '0'); + } + return result; + } + + /// + /// Parses the hex string into a , skipping all characters except for valid digits. + /// + /// Hex String to parse + /// Parsed value + public static uint GetHexValue(ReadOnlySpan value) + { + uint result = 0; + if (value.Length == 0) + return result; + + foreach (var c in value) + { + if (IsNum(c)) + { + result <<= 4; result += (uint)(c - '0'); } - return result; - } - - /// - /// Parses the hex string into a , skipping all characters except for valid digits. - /// - /// Hex String to parse - /// Parsed value - public static uint GetHexValue(ReadOnlySpan value) - { - uint result = 0; - if (value.Length == 0) - return result; - - foreach (var c in value) + else if (IsHexUpper(c)) { - if (IsNum(c)) - { - result <<= 4; - result += (uint)(c - '0'); - } - else if (IsHexUpper(c)) - { - result <<= 4; - result += (uint)(c - 'A' + 10); - } - else if (IsHexLower(c)) - { - result <<= 4; - result += (uint)(c - 'a' + 10); - } + result <<= 4; + result += (uint)(c - 'A' + 10); } - return result; - } - - /// - /// Parses the hex string into a , skipping all characters except for valid digits. - /// - /// Hex String to parse - /// Parsed value - public static ulong GetHexValue64(ReadOnlySpan value) - { - ulong result = 0; - if (value.Length == 0) - return result; - - foreach (var c in value) + else if (IsHexLower(c)) { - if (IsNum(c)) - { - result <<= 4; - result += (uint)(c - '0'); - } - else if (IsHexUpper(c)) - { - result <<= 4; - result += (uint)(c - 'A' + 10); - } - else if (IsHexLower(c)) - { - result <<= 4; - result += (uint)(c - 'a' + 10); - } - } - return result; - } - - public static byte[] GetBytesFromHexString(ReadOnlySpan seed) - { - byte[] result = new byte[seed.Length / 2]; - for (int i = 0; i < result.Length; i++) - { - var slice = seed.Slice(i * 2, 2); - result[^(i+1)] = (byte)GetHexValue(slice); - } - return result; - } - - public static string GetHexStringFromBytes(ReadOnlySpan arr) - { - var sb = new StringBuilder(arr.Length * 2); - for (int i = arr.Length - 1; i >= 0; i--) - sb.AppendFormat("{0:X2}", arr[i]); - return sb.ToString(); - } - - private static bool IsNum(char c) => (uint)(c - '0') <= 9; - private static bool IsHexUpper(char c) => (uint)(c - 'A') <= 5; - private static bool IsHexLower(char c) => (uint)(c - 'a') <= 5; - private static bool IsHex(char c) => IsNum(c) || IsHexUpper(c) || IsHexLower(c); - private static string TitleCase(string word) => char.ToUpper(word[0]) + word[1..].ToLower(); - - /// - /// Filters the string down to only valid hex characters, returning a new string. - /// - /// Input string to filter - public static string GetOnlyHex(string str) => string.IsNullOrWhiteSpace(str) ? string.Empty : string.Concat(str.Where(IsHex)); - - /// - /// Returns a new string with each word converted to its appropriate title case. - /// - /// Input string to modify - public static string ToTitleCase(string str) => string.IsNullOrWhiteSpace(str) ? string.Empty : string.Join(" ", str.Split(' ').Select(TitleCase)); - - /// - /// Trims a string at the first instance of a 0x0000 terminator. - /// - /// String to trim. - /// Trimmed string. - public static string TrimFromZero(string input) => TrimFromFirst(input, '\0'); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string TrimFromFirst(string input, char c) - { - int index = input.IndexOf(c); - return index < 0 ? input : input[..index]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void TrimFromFirst(StringBuilder input, char c) - { - for (int i = 0; i < input.Length; i++) - { - if (input[i] != c) - continue; - input.Remove(i, input.Length - i); - return; + result <<= 4; + result += (uint)(c - 'a' + 10); } } + return result; + } - public static Dictionary[] GetMultiDictionary(IReadOnlyList> nameArray) - { - var result = new Dictionary[nameArray.Count]; - for (int i = 0; i < result.Length; i++) - { - var dict = result[i] = new Dictionary(); - var names = nameArray[i]; - for (int j = 0; j < names.Count; j++) - dict.Add(names[j], j); - } + /// + /// Parses the hex string into a , skipping all characters except for valid digits. + /// + /// Hex String to parse + /// Parsed value + public static ulong GetHexValue64(ReadOnlySpan value) + { + ulong result = 0; + if (value.Length == 0) return result; + + foreach (var c in value) + { + if (IsNum(c)) + { + result <<= 4; + result += (uint)(c - '0'); + } + else if (IsHexUpper(c)) + { + result <<= 4; + result += (uint)(c - 'A' + 10); + } + else if (IsHexLower(c)) + { + result <<= 4; + result += (uint)(c - 'a' + 10); + } + } + return result; + } + + public static byte[] GetBytesFromHexString(ReadOnlySpan seed) + { + byte[] result = new byte[seed.Length / 2]; + for (int i = 0; i < result.Length; i++) + { + var slice = seed.Slice(i * 2, 2); + result[^(i+1)] = (byte)GetHexValue(slice); + } + return result; + } + + public static string GetHexStringFromBytes(ReadOnlySpan arr) + { + var sb = new StringBuilder(arr.Length * 2); + for (int i = arr.Length - 1; i >= 0; i--) + sb.AppendFormat("{0:X2}", arr[i]); + return sb.ToString(); + } + + private static bool IsNum(char c) => (uint)(c - '0') <= 9; + private static bool IsHexUpper(char c) => (uint)(c - 'A') <= 5; + private static bool IsHexLower(char c) => (uint)(c - 'a') <= 5; + private static bool IsHex(char c) => IsNum(c) || IsHexUpper(c) || IsHexLower(c); + private static string TitleCase(string word) => char.ToUpper(word[0]) + word[1..].ToLower(); + + /// + /// Filters the string down to only valid hex characters, returning a new string. + /// + /// Input string to filter + public static string GetOnlyHex(string str) => string.IsNullOrWhiteSpace(str) ? string.Empty : string.Concat(str.Where(IsHex)); + + /// + /// Returns a new string with each word converted to its appropriate title case. + /// + /// Input string to modify + public static string ToTitleCase(string str) => string.IsNullOrWhiteSpace(str) ? string.Empty : string.Join(" ", str.Split(' ').Select(TitleCase)); + + /// + /// Trims a string at the first instance of a 0x0000 terminator. + /// + /// String to trim. + /// Trimmed string. + public static string TrimFromZero(string input) => TrimFromFirst(input, '\0'); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string TrimFromFirst(string input, char c) + { + int index = input.IndexOf(c); + return index < 0 ? input : input[..index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void TrimFromFirst(StringBuilder input, char c) + { + for (int i = 0; i < input.Length; i++) + { + if (input[i] != c) + continue; + input.Remove(i, input.Length - i); + return; } } + + public static Dictionary[] GetMultiDictionary(IReadOnlyList> nameArray) + { + var result = new Dictionary[nameArray.Count]; + for (int i = 0; i < result.Length; i++) + { + var dict = result[i] = new Dictionary(); + var names = nameArray[i]; + for (int j = 0; j < names.Count; j++) + dict.Add(names[j], j); + } + return result; + } } diff --git a/PKHeX.Core/Util/ValueTypeTypeConverter.cs b/PKHeX.Core/Util/ValueTypeTypeConverter.cs index 3a6b1067a..f1c492cdf 100644 --- a/PKHeX.Core/Util/ValueTypeTypeConverter.cs +++ b/PKHeX.Core/Util/ValueTypeTypeConverter.cs @@ -2,58 +2,57 @@ using System.Collections; using System.ComponentModel; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// Used for allowing a struct to be mutated in a PropertyGrid. +/// +public sealed class ValueTypeTypeConverter : ExpandableObjectConverter { - /// - /// Used for allowing a struct to be mutated in a PropertyGrid. - /// - public sealed class ValueTypeTypeConverter : ExpandableObjectConverter + public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) => true; + + public override object? CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) { - public override bool GetCreateInstanceSupported(ITypeDescriptorContext context) => true; + var pd = context.PropertyDescriptor; + if (pd is null) + return null; - public override object? CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues) + object? boxed = pd.GetValue(context.Instance); + foreach (DictionaryEntry entry in propertyValues) { - var pd = context.PropertyDescriptor; - if (pd is null) - return null; - - object? boxed = pd.GetValue(context.Instance); - foreach (DictionaryEntry entry in propertyValues) - { - var pi = pd.PropertyType.GetProperty(entry.Key.ToString()); - if (pi?.CanWrite == true) - pi.SetValue(boxed, Convert.ChangeType(entry.Value, pi.PropertyType), null); - } - return boxed; - } - } - - public sealed class TypeConverterU64 : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); - } - - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - return destinationType == typeof(string) || base.CanConvertTo(context, destinationType); - } - - public override object? ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) - { - if (destinationType == typeof(string) && value is ulong u) - return $"{u:X16}"; // no 0x prefix - return base.ConvertTo(context, culture, value, destinationType); - } - - public override object? ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) - { - if (value is not string input) - return base.ConvertFrom(context, culture, value); - if (input.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) - input = input[2..]; - return ulong.TryParse(input, System.Globalization.NumberStyles.HexNumber, culture, out var result) ? result : 0ul; + var pi = pd.PropertyType.GetProperty(entry.Key.ToString()); + if (pi?.CanWrite == true) + pi.SetValue(boxed, Convert.ChangeType(entry.Value, pi.PropertyType), null); } + return boxed; + } +} + +public sealed class TypeConverterU64 : TypeConverter +{ + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return destinationType == typeof(string) || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string) && value is ulong u) + return $"{u:X16}"; // no 0x prefix + return base.ConvertTo(context, culture, value, destinationType); + } + + public override object? ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) + { + if (value is not string input) + return base.ConvertFrom(context, culture, value); + if (input.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + input = input[2..]; + return ulong.TryParse(input, System.Globalization.NumberStyles.HexNumber, culture, out var result) ? result : 0ul; } } diff --git a/PKHeX.Drawing.Misc/QR/QRDecode.cs b/PKHeX.Drawing.Misc/QR/QRDecode.cs index 69c0f6e7d..cda5acb70 100644 --- a/PKHeX.Drawing.Misc/QR/QRDecode.cs +++ b/PKHeX.Drawing.Misc/QR/QRDecode.cs @@ -24,7 +24,7 @@ public static QRDecodeResult GetQRData(string address, out byte[] result) string data; try { - var str = NetUtil.GetStringFromURL(url); + var str = NetUtil.GetStringFromURL(new Uri(url)); if (str is null) return QRDecodeResult.BadConnection; diff --git a/PKHeX.Drawing.Misc/QR/QREncode.cs b/PKHeX.Drawing.Misc/QR/QREncode.cs index 54762ca74..d84e32b32 100644 --- a/PKHeX.Drawing.Misc/QR/QREncode.cs +++ b/PKHeX.Drawing.Misc/QR/QREncode.cs @@ -7,7 +7,7 @@ namespace PKHeX.Drawing.Misc; public static class QREncode { public static Image GenerateQRCode(DataMysteryGift mg) => GenerateQRCode(QRMessageUtil.GetMessage(mg)); - public static Image GenerateQRCode(PKM pkm) => GenerateQRCode(QRMessageUtil.GetMessage(pkm)); + public static Image GenerateQRCode(PKM pk) => GenerateQRCode(QRMessageUtil.GetMessage(pk)); public static Image GenerateQRCode7(PK7 pk7, int box = 0, int slot = 0, int copies = 1) { diff --git a/PKHeX.Drawing.Misc/QR/QRImageUtil.cs b/PKHeX.Drawing.Misc/QR/QRImageUtil.cs index c66b4bfb8..2b0f857ae 100644 --- a/PKHeX.Drawing.Misc/QR/QRImageUtil.cs +++ b/PKHeX.Drawing.Misc/QR/QRImageUtil.cs @@ -7,7 +7,7 @@ public static class QRImageUtil { public static Bitmap GetQRImage(Image qr, Image preview) { - // create a small area with the pkm sprite, with a white background + // create a small area with the pk sprite, with a white background var foreground = new Bitmap(preview.Width + 4, preview.Height + 4); using (Graphics gfx = Graphics.FromImage(foreground)) { @@ -25,9 +25,9 @@ public static Bitmap GetQRImage(Image qr, Image preview) } } - public static Bitmap GetQRImageExtended(Font font, Image qr, Image pkm, int width, int height, string[] lines, string extraText) + public static Bitmap GetQRImageExtended(Font font, Image qr, Image pk, int width, int height, string[] lines, string extraText) { - var pic = GetQRImage(qr, pkm); + var pic = GetQRImage(qr, pk); return ExtendImage(font, qr, width, height, pic, lines, extraText); } diff --git a/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs b/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs index c1c620d9b..7b116b1f5 100644 --- a/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs +++ b/PKHeX.Drawing.PokeSprite/Util/SpriteUtil.cs @@ -104,14 +104,14 @@ private static Image GetSprite(PKM pk, SaveFile sav, int box, int slot, bool fla int team = flags.IsBattleTeam(); if (team >= 0) sprite = ImageUtil.LayerImage(sprite, Resources.team, SlotTeamShiftX, 0); - if (flags.HasFlagFast(StorageSlotFlag.Locked)) + if (flags.HasFlagFast(StorageSlotSource.Locked)) sprite = ImageUtil.LayerImage(sprite, Resources.locked, SlotLockShiftX, 0); // Some games store Party directly in the list of pokemon data (LGP/E). Indicate accordingly. int party = flags.IsParty(); if (party >= 0) sprite = ImageUtil.LayerImage(sprite, PartyMarks[party], PartyMarkShiftX, 0); - if (flags.HasFlagFast(StorageSlotFlag.Starter)) + if (flags.HasFlagFast(StorageSlotSource.Starter)) sprite = ImageUtil.LayerImage(sprite, Resources.starter, 0, 0); } diff --git a/PKHeX.WinForms/Controls/PKM Editor/BallBrowser.Designer.cs b/PKHeX.WinForms/Controls/PKM Editor/BallBrowser.Designer.cs index 98223ee91..136274b23 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/BallBrowser.Designer.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/BallBrowser.Designer.cs @@ -61,4 +61,4 @@ private void InitializeComponent() private System.Windows.Forms.FlowLayoutPanel flp; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Controls/PKM Editor/BallBrowser.cs b/PKHeX.WinForms/Controls/PKM Editor/BallBrowser.cs index e7df91e97..80e471c0d 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/BallBrowser.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/BallBrowser.cs @@ -6,54 +6,53 @@ using PKHeX.Core; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class BallBrowser : Form { - public partial class BallBrowser : Form + public BallBrowser() => InitializeComponent(); + + public int BallChoice { get; private set; } = -1; + + public void LoadBalls(Ball[] poss, ICollection legal, IReadOnlyList names) { - public BallBrowser() => InitializeComponent(); - - public int BallChoice { get; private set; } = -1; - - public void LoadBalls(Ball[] poss, ICollection legal, IReadOnlyList names) + for (int i = 0; i < poss.Length; i++) { - for (int i = 0; i < poss.Length; i++) - { - var pb = GetBallView(poss[i], legal, names); - flp.Controls.Add(pb); - const int width = 5; // balls wide - if (i % width == width - 1) - flp.SetFlowBreak(pb, true); - } - } - - public void LoadBalls(PKM pkm) - { - var legal = BallApplicator.GetLegalBalls(pkm).ToArray(); - var poss = ((Ball[])Enum.GetValues(typeof(Ball))).Skip(1) - .TakeWhile(z => (int)z <= pkm.MaxBallID).ToArray(); - var names = GameInfo.BallDataSource; - LoadBalls(poss, legal, names); - } - - private PictureBox GetBallView(Ball b, ICollection legal, IReadOnlyList names) - { - var img = SpriteUtil.GetBallSprite((int)b); - var pb = new PictureBox - { - Size = img.Size, - Image = img, - BackgroundImage = legal.Contains(b) ? SpriteUtil.Spriter.Set : SpriteUtil.Spriter.Delete, - BackgroundImageLayout = ImageLayout.Tile, - }; - pb.MouseEnter += (_, _) => Text = names.First(z => z.Value == (int)b).Text; - pb.Click += (_, _) => SelectBall(b); - return pb; - } - - private void SelectBall(Ball b) - { - BallChoice = (int)b; - Close(); + var pb = GetBallView(poss[i], legal, names); + flp.Controls.Add(pb); + const int width = 5; // balls wide + if (i % width == width - 1) + flp.SetFlowBreak(pb, true); } } + + public void LoadBalls(PKM pk) + { + var legal = BallApplicator.GetLegalBalls(pk).ToArray(); + var poss = ((Ball[])Enum.GetValues(typeof(Ball))).Skip(1) + .TakeWhile(z => (int)z <= pk.MaxBallID).ToArray(); + var names = GameInfo.BallDataSource; + LoadBalls(poss, legal, names); + } + + private PictureBox GetBallView(Ball b, ICollection legal, IReadOnlyList names) + { + var img = SpriteUtil.GetBallSprite((int)b); + var pb = new PictureBox + { + Size = img.Size, + Image = img, + BackgroundImage = legal.Contains(b) ? SpriteUtil.Spriter.Set : SpriteUtil.Spriter.Delete, + BackgroundImageLayout = ImageLayout.Tile, + }; + pb.MouseEnter += (_, _) => Text = names.First(z => z.Value == (int)b).Text; + pb.Click += (_, _) => SelectBall(b); + return pb; + } + + private void SelectBall(Ball b) + { + BallChoice = (int)b; + Close(); + } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/CatchRate.cs b/PKHeX.WinForms/Controls/PKM Editor/CatchRate.cs index a1f894cc3..4c28102a7 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/CatchRate.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/CatchRate.cs @@ -1,24 +1,32 @@ -using System; +using System; using System.Windows.Forms; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class CatchRate : UserControl { - public partial class CatchRate : UserControl + private PK1? Entity; + public CatchRate() => InitializeComponent(); + + public void LoadPK1(PK1 pk) => NUD_CatchRate.Value = (Entity = pk).Catch_Rate; + + private void ChangeValue(object sender, EventArgs e) { - private PK1 pk1 = new(); - public CatchRate() => InitializeComponent(); + if (Entity is null) + return; + Entity.Catch_Rate = (int)NUD_CatchRate.Value; + } - public void LoadPK1(PK1 pk) => NUD_CatchRate.Value = (pk1 = pk).Catch_Rate; - private void ChangeValue(object sender, EventArgs e) => pk1.Catch_Rate = (int)NUD_CatchRate.Value; - private void Clear(object sender, EventArgs e) => NUD_CatchRate.Value = 0; + private void Clear(object sender, EventArgs e) => NUD_CatchRate.Value = 0; - private void Reset(object sender, EventArgs e) - { - var sav = WinFormsUtil.FindFirstControlOfType(this)?.RequestSaveFile; - if (sav == null) - return; - NUD_CatchRate.Value = CatchRateApplicator.GetSuggestedCatchRate(pk1, sav); - } + private void Reset(object sender, EventArgs e) + { + if (Entity is null) + return; + var sav = WinFormsUtil.FindFirstControlOfType(this)?.RequestSaveFile; + if (sav == null) + return; + NUD_CatchRate.Value = CatchRateApplicator.GetSuggestedCatchRate(Entity, sav); } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/ContestStat.cs b/PKHeX.WinForms/Controls/PKM Editor/ContestStat.cs index 538e0ebeb..eb8071fa7 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/ContestStat.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/ContestStat.cs @@ -2,85 +2,84 @@ using System.Windows.Forms; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class ContestStat : UserControl, IContestStats, IContestStatsMutable { - public partial class ContestStat : UserControl, IContestStats, IContestStatsMutable + public ContestStat() { - public ContestStat() + InitializeComponent(); + } + + public byte CNT_Cool + { + get => (byte)Util.ToInt32(TB_Cool.Text); + set => TB_Cool.Text = value.ToString(); + } + + public byte CNT_Beauty + { + get => (byte)Util.ToInt32(TB_Beauty.Text); + set => TB_Beauty.Text = value.ToString(); + } + + public byte CNT_Cute + { + get => (byte)Util.ToInt32(TB_Cute.Text); + set => TB_Cute.Text = value.ToString(); + } + + public byte CNT_Smart + { + get => (byte)Util.ToInt32(TB_Smart.Text); + set => TB_Smart.Text = value.ToString(); + } + + public byte CNT_Tough + { + get => (byte)Util.ToInt32(TB_Tough.Text); + set => TB_Tough.Text = value.ToString(); + } + + public byte CNT_Sheen + { + get => (byte)Util.ToInt32(TB_Sheen.Text); + set => TB_Sheen.Text = value.ToString(); + } + + private void Update255_MTB(object sender, EventArgs e) + { + if (sender is not MaskedTextBox tb) return; + if (Util.ToInt32(tb.Text) > byte.MaxValue) + tb.Text = "255"; + } + + public void ToggleInterface(object o, EntityContext context) + { + if (o is not IContestStats) { - InitializeComponent(); + Visible = false; + return; } - public byte CNT_Cool - { - get => (byte)Util.ToInt32(TB_Cool.Text); - set => TB_Cool.Text = value.ToString(); - } + Visible = true; + bool smart = context.Generation() < 6; + Label_Smart.Visible = smart; // show smart gen3-5 + Label_Clever.Visible = !smart; // show clever gen6+ + } - public byte CNT_Beauty - { - get => (byte)Util.ToInt32(TB_Beauty.Text); - set => TB_Beauty.Text = value.ToString(); - } + private void ClickTextBox(object sender, EventArgs e) + { + var keys = ModifierKeys; + if (keys == Keys.None) + return; - public byte CNT_Cute - { - get => (byte)Util.ToInt32(TB_Cute.Text); - set => TB_Cute.Text = value.ToString(); - } + if (sender is not MaskedTextBox tb) + return; - public byte CNT_Smart - { - get => (byte)Util.ToInt32(TB_Smart.Text); - set => TB_Smart.Text = value.ToString(); - } - - public byte CNT_Tough - { - get => (byte)Util.ToInt32(TB_Tough.Text); - set => TB_Tough.Text = value.ToString(); - } - - public byte CNT_Sheen - { - get => (byte)Util.ToInt32(TB_Sheen.Text); - set => TB_Sheen.Text = value.ToString(); - } - - private void Update255_MTB(object sender, EventArgs e) - { - if (sender is not MaskedTextBox tb) return; - if (Util.ToInt32(tb.Text) > byte.MaxValue) - tb.Text = "255"; - } - - public void ToggleInterface(object o, EntityContext context) - { - if (o is not IContestStats) - { - Visible = false; - return; - } - - Visible = true; - bool smart = context.Generation() < 6; - Label_Smart.Visible = smart; // show smart gen3-5 - Label_Clever.Visible = !smart; // show clever gen6+ - } - - private void ClickTextBox(object sender, EventArgs e) - { - var keys = ModifierKeys; - if (keys == Keys.None) - return; - - if (sender is not MaskedTextBox tb) - return; - - if (keys == Keys.Control) - tb.Text = "255"; - else if (keys == Keys.Alt) - tb.Text = "0"; - } + if (keys == Keys.Control) + tb.Text = "255"; + else if (keys == Keys.Alt) + tb.Text = "0"; } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/ContextMenuPKM.cs b/PKHeX.WinForms/Controls/PKM Editor/ContextMenuPKM.cs index 4623fdbda..30f7308bd 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/ContextMenuPKM.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/ContextMenuPKM.cs @@ -1,17 +1,16 @@ using System; using System.Windows.Forms; -namespace PKHeX.WinForms.Controls -{ - public partial class ContextMenuPKM : UserControl - { - public ContextMenuPKM() => InitializeComponent(); +namespace PKHeX.WinForms.Controls; - public event EventHandler? RequestEditorLegality; - public event EventHandler? RequestEditorQR; - public event EventHandler? RequestEditorSaveAs; - private void ClickShowLegality(object sender, EventArgs e) => RequestEditorLegality?.Invoke(sender, e); - private void ClickShowQR(object sender, EventArgs e) => RequestEditorQR?.Invoke(sender, e); - private void ClickSaveAs(object sender, EventArgs e) => RequestEditorSaveAs?.Invoke(sender, e); - } +public partial class ContextMenuPKM : UserControl +{ + public ContextMenuPKM() => InitializeComponent(); + + public event EventHandler? RequestEditorLegality; + public event EventHandler? RequestEditorQR; + public event EventHandler? RequestEditorSaveAs; + private void ClickShowLegality(object sender, EventArgs e) => RequestEditorLegality?.Invoke(sender, e); + private void ClickShowQR(object sender, EventArgs e) => RequestEditorQR?.Invoke(sender, e); + private void ClickSaveAs(object sender, EventArgs e) => RequestEditorSaveAs?.Invoke(sender, e); } diff --git a/PKHeX.WinForms/Controls/PKM Editor/DrawConfig.cs b/PKHeX.WinForms/Controls/PKM Editor/DrawConfig.cs index ab7b4095d..69a7c1735 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/DrawConfig.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/DrawConfig.cs @@ -1,145 +1,144 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using PKHeX.Core; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +/// +/// Drawing Configuration for painting and updating controls +/// +[Serializable] +public sealed class DrawConfig : IDisposable { - /// - /// Drawing Configuration for painting and updating controls - /// - [Serializable] - public sealed class DrawConfig : IDisposable + private const string PKM = "Pokémon Editor"; + private const string Moves = "Moves"; + private const string Hovering = "Hovering"; + + [Category(Hovering), Description("Hovering over a PKM color 1.")] + public Color GlowInitial { get; set; } = Color.White; + + [Category(Hovering), Description("Hovering over a PKM color 2.")] + public Color GlowFinal { get; set; } = Color.LightSkyBlue; + + #region PKM + + [Category(PKM), Description("Background color of a ComboBox when the selected item is not valid.")] + public Color InvalidSelection { get; set; } = Color.DarkSalmon; + + [Category(PKM), Description("Default colored marking.")] + public Color MarkDefault { get; set; } = Color.Black; + + [Category(PKM), Description("Blue colored marking.")] + public Color MarkBlue { get; set; } = Color.FromArgb(000, 191, 255); + + [Category(PKM), Description("Pink colored marking.")] + public Color MarkPink { get; set; } = Color.FromArgb(255, 117, 179); + + [Category(PKM), Description("Male gender color.")] + public Color Male { get; set; } = Color.Blue; + + [Category(PKM), Description("Female gender color.")] + public Color Female { get; set; } = Color.Red; + + [Category(PKM), Description("Shiny star when using unicode characters.")] + public string ShinyUnicode { get; set; } = "☆"; + + [Category(PKM), Description("Shiny star when not using unicode characters.")] + public string ShinyDefault { get; set; } = "*"; + + #endregion + + #region Moves + + [Category(Moves), Description("Legal move choice background color.")] + public Color BackLegal { get; set; } = Color.FromArgb(200, 255, 200); + + [Category(Moves), Description("Legal move choice text color.")] + public Color TextColor { get; set; } = SystemColors.WindowText; + + [Category(Moves), Description("Illegal Legal move choice background color.")] + public Color BackColor { get; set; } = SystemColors.Window; + + [Category(Moves), Description("Highlighted move choice background color.")] + public Color BackHighlighted { get; set; } = SystemColors.Highlight; + + [Category(Moves), Description("Highlighted move choice text color.")] + public Color TextHighlighted { get; set; } = SystemColors.HighlightText; + + #endregion + + public DrawConfig() => LoadBrushes(); + + public Color GetGenderColor(int gender) => gender switch { - private const string PKM = "Pokémon Editor"; - private const string Moves = "Moves"; - private const string Hovering = "Hovering"; + 0 => Male, + 1 => Female, + _ => TextColor, + }; - [Category(Hovering), Description("Hovering over a PKM color 1.")] - public Color GlowInitial { get; set; } = Color.White; - - [Category(Hovering), Description("Hovering over a PKM color 2.")] - public Color GlowFinal { get; set; } = Color.LightSkyBlue; - - #region PKM - - [Category(PKM), Description("Background color of a ComboBox when the selected item is not valid.")] - public Color InvalidSelection { get; set; } = Color.DarkSalmon; - - [Category(PKM), Description("Default colored marking.")] - public Color MarkDefault { get; set; } = Color.Black; - - [Category(PKM), Description("Blue colored marking.")] - public Color MarkBlue { get; set; } = Color.FromArgb(000, 191, 255); - - [Category(PKM), Description("Pink colored marking.")] - public Color MarkPink { get; set; } = Color.FromArgb(255, 117, 179); - - [Category(PKM), Description("Male gender color.")] - public Color Male { get; set; } = Color.Blue; - - [Category(PKM), Description("Female gender color.")] - public Color Female { get; set; } = Color.Red; - - [Category(PKM), Description("Shiny star when using unicode characters.")] - public string ShinyUnicode { get; set; } = "☆"; - - [Category(PKM), Description("Shiny star when not using unicode characters.")] - public string ShinyDefault { get; set; } = "*"; - - #endregion - - #region Moves - - [Category(Moves), Description("Legal move choice background color.")] - public Color BackLegal { get; set; } = Color.FromArgb(200, 255, 200); - - [Category(Moves), Description("Legal move choice text color.")] - public Color TextColor { get; set; } = SystemColors.WindowText; - - [Category(Moves), Description("Illegal Legal move choice background color.")] - public Color BackColor { get; set; } = SystemColors.Window; - - [Category(Moves), Description("Highlighted move choice background color.")] - public Color BackHighlighted { get; set; } = SystemColors.Highlight; - - [Category(Moves), Description("Highlighted move choice text color.")] - public Color TextHighlighted { get; set; } = SystemColors.HighlightText; - - #endregion - - public DrawConfig() => LoadBrushes(); - - public Color GetGenderColor(int gender) => gender switch + public bool GetMarkingColor(int markval, out Color c) + { + switch (markval) { - 0 => Male, - 1 => Female, - _ => TextColor, - }; - - public bool GetMarkingColor(int markval, out Color c) - { - switch (markval) - { - case 1: c = MarkBlue; return true; - case 2: c = MarkPink; return true; - default: c = MarkDefault; return false; // recolor not required - } - } - - public Color GetText(bool highlight) => highlight ? TextHighlighted : TextColor; - public Color GetBackground(bool legal, bool highlight) => highlight ? BackHighlighted : (legal ? BackLegal : BackColor); - - [NonSerialized] - public readonly BrushSet Brushes = new(); - - public void LoadBrushes() - { - Brushes.BackLegal = new SolidBrush(BackLegal); - Brushes.Text = new SolidBrush(TextColor); - Brushes.BackDefault = new SolidBrush(BackColor); - Brushes.TextHighlighted = new SolidBrush(TextHighlighted); - Brushes.BackHighlighted = new SolidBrush(BackHighlighted); - } - - public void Dispose() => Brushes.Dispose(); - - public override string ToString() - { - var props = ReflectUtil.GetAllPropertyInfoCanWritePublic(typeof(DrawConfig)); - var lines = new List(); - foreach (var p in props) - { - if (p.PropertyType == typeof(BrushSet)) - continue; - - var name = p.Name; - var value = p.PropertyType == typeof(Color) ? ((Color)p.GetValue(this)!).ToArgb() : p.GetValue(this); - lines.Add($"{name}\t{value}"); - } - return string.Join("\n", lines); + case 1: c = MarkBlue; return true; + case 2: c = MarkPink; return true; + default: c = MarkDefault; return false; // recolor not required } } - public sealed class BrushSet : IDisposable + public Color GetText(bool highlight) => highlight ? TextHighlighted : TextColor; + public Color GetBackground(bool legal, bool highlight) => highlight ? BackHighlighted : (legal ? BackLegal : BackColor); + + [NonSerialized] + public readonly BrushSet Brushes = new(); + + public void LoadBrushes() { - public Brush Text { get; set; } = Brushes.Black; - public Brush BackLegal { get; set; } = Brushes.DarkSeaGreen; - public Brush BackDefault { get; set; } = Brushes.White; - public Brush TextHighlighted { get; set; } = Brushes.White; - public Brush BackHighlighted { get; set; } = Brushes.Blue; + Brushes.BackLegal = new SolidBrush(BackLegal); + Brushes.Text = new SolidBrush(TextColor); + Brushes.BackDefault = new SolidBrush(BackColor); + Brushes.TextHighlighted = new SolidBrush(TextHighlighted); + Brushes.BackHighlighted = new SolidBrush(BackHighlighted); + } - public Brush GetText(bool highlight) => highlight ? TextHighlighted : Text; - public Brush GetBackground(bool legal, bool highlight) => highlight ? BackHighlighted : (legal ? BackLegal : BackDefault); + public void Dispose() => Brushes.Dispose(); - public void Dispose() + public override string ToString() + { + var props = ReflectUtil.GetAllPropertyInfoCanWritePublic(typeof(DrawConfig)); + var lines = new List(); + foreach (var p in props) { - Text.Dispose(); - BackLegal.Dispose(); - BackDefault.Dispose(); - TextHighlighted.Dispose(); - BackHighlighted.Dispose(); + if (p.PropertyType == typeof(BrushSet)) + continue; + + var name = p.Name; + var value = p.PropertyType == typeof(Color) ? ((Color)p.GetValue(this)!).ToArgb() : p.GetValue(this); + lines.Add($"{name}\t{value}"); } + return string.Join("\n", lines); + } +} + +public sealed class BrushSet : IDisposable +{ + public Brush Text { get; set; } = Brushes.Black; + public Brush BackLegal { get; set; } = Brushes.DarkSeaGreen; + public Brush BackDefault { get; set; } = Brushes.White; + public Brush TextHighlighted { get; set; } = Brushes.White; + public Brush BackHighlighted { get; set; } = Brushes.Blue; + + public Brush GetText(bool highlight) => highlight ? TextHighlighted : Text; + public Brush GetBackground(bool legal, bool highlight) => highlight ? BackHighlighted : (legal ? BackLegal : BackDefault); + + public void Dispose() + { + Text.Dispose(); + BackLegal.Dispose(); + BackDefault.Dispose(); + TextHighlighted.Dispose(); + BackHighlighted.Dispose(); } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/EditPK1.cs b/PKHeX.WinForms/Controls/PKM Editor/EditPK1.cs index a1310af19..448f49106 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/EditPK1.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/EditPK1.cs @@ -1,37 +1,36 @@ using System; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PKMEditor { - public partial class PKMEditor + private void PopulateFieldsPK1() { - private void PopulateFieldsPK1() - { - if (Entity is not PK1 pk1) - throw new FormatException(nameof(Entity)); + if (Entity is not PK1 pk1) + throw new FormatException(nameof(Entity)); - LoadMisc1(pk1); - TID_Trainer.LoadIDValues(pk1); - CR_PK1.LoadPK1(pk1); + LoadMisc1(pk1); + TID_Trainer.LoadIDValues(pk1); + CR_PK1.LoadPK1(pk1); - // Attempt to detect language - CB_Language.SelectedValue = pk1.GuessedLanguage(); + // Attempt to detect language + CB_Language.SelectedValue = pk1.GuessedLanguage(); - LoadPartyStats(pk1); - UpdateStats(); - } + LoadPartyStats(pk1); + UpdateStats(); + } - private PK1 PreparePK1() - { - if (Entity is not PK1 pk1) - throw new FormatException(nameof(Entity)); + private PK1 PreparePK1() + { + if (Entity is not PK1 pk1) + throw new FormatException(nameof(Entity)); - SaveMisc1(pk1); + SaveMisc1(pk1); - SavePartyStats(pk1); - pk1.FixMoves(); - pk1.RefreshChecksum(); - return pk1; - } + SavePartyStats(pk1); + pk1.FixMoves(); + pk1.RefreshChecksum(); + return pk1; } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/EditPK2.cs b/PKHeX.WinForms/Controls/PKM Editor/EditPK2.cs index 4e5f7fa3a..66e134df8 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/EditPK2.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/EditPK2.cs @@ -1,71 +1,70 @@ using System; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PKMEditor { - public partial class PKMEditor + private void PopulateFieldsPK2() { - private void PopulateFieldsPK2() + if (Entity is not (GBPKM pk2 and ICaughtData2 c2)) + throw new FormatException(nameof(Entity)); + + if (Entity is SK2 sk2) { - if (Entity is not (GBPKM pk2 and ICaughtData2 c2)) - throw new FormatException(nameof(Entity)); - - if (Entity is SK2 sk2) - { - var sav = RequestSaveFile; - CoerceStadium2Language(sk2, sav); - } - LoadMisc1(pk2); - LoadMisc2(pk2); - - TID_Trainer.LoadIDValues(pk2); - TB_MetLevel.Text = c2.Met_Level.ToString(); - CB_MetLocation.SelectedValue = c2.Met_Location; - CB_MetTimeOfDay.SelectedIndex = c2.Met_TimeOfDay; - - // Attempt to detect language - CB_Language.SelectedValue = pk2.GuessedLanguage(); - - LoadPartyStats(pk2); - UpdateStats(); + var sav = RequestSaveFile; + CoerceStadium2Language(sk2, sav); } + LoadMisc1(pk2); + LoadMisc2(pk2); - private static void CoerceStadium2Language(SK2 sk2, SaveFile sav) - { - if (sk2.Japanese == (sav.Language == 1)) - return; + TID_Trainer.LoadIDValues(pk2); + TB_MetLevel.Text = c2.Met_Level.ToString(); + CB_MetLocation.SelectedValue = c2.Met_Location; + CB_MetTimeOfDay.SelectedIndex = c2.Met_TimeOfDay; - var la = new LegalityAnalysis(sk2); - if (la.Valid || !sk2.IsPossible(sav.Language == 1)) - return; + // Attempt to detect language + CB_Language.SelectedValue = pk2.GuessedLanguage(); + LoadPartyStats(pk2); + UpdateStats(); + } + + private static void CoerceStadium2Language(SK2 sk2, SaveFile sav) + { + if (sk2.Japanese == (sav.Language == 1)) + return; + + var la = new LegalityAnalysis(sk2); + if (la.Valid || !sk2.IsPossible(sav.Language == 1)) + return; + + sk2.SwapLanguage(); + la = new LegalityAnalysis(sk2); + if (la.Valid) + return; + + var lang = SpeciesName.GetSpeciesNameLanguage(sk2.Species, sk2.Nickname, 2); + if (lang >= 1 && (lang == 1 != sk2.Japanese)) // force match language sk2.SwapLanguage(); - la = new LegalityAnalysis(sk2); - if (la.Valid) - return; + else if (sk2.Japanese != (sav.Language == 1)) // force match save file + sk2.SwapLanguage(); + } - var lang = SpeciesName.GetSpeciesNameLanguage(sk2.Species, sk2.Nickname, 2); - if (lang >= 1 && (lang == 1 != sk2.Japanese)) // force match language - sk2.SwapLanguage(); - else if (sk2.Japanese != (sav.Language == 1)) // force match save file - sk2.SwapLanguage(); - } + private GBPKM PreparePK2() + { + if (Entity is not (GBPKM pk2 and ICaughtData2 c2)) + throw new FormatException(nameof(Entity)); - private GBPKM PreparePK2() - { - if (Entity is not (GBPKM pk2 and ICaughtData2 c2)) - throw new FormatException(nameof(Entity)); + SaveMisc1(pk2); + SaveMisc2(pk2); - SaveMisc1(pk2); - SaveMisc2(pk2); + c2.Met_Level = Util.ToInt32(TB_MetLevel.Text); + c2.Met_Location = WinFormsUtil.GetIndex(CB_MetLocation); + c2.Met_TimeOfDay = CB_MetTimeOfDay.SelectedIndex; - c2.Met_Level = Util.ToInt32(TB_MetLevel.Text); - c2.Met_Location = WinFormsUtil.GetIndex(CB_MetLocation); - c2.Met_TimeOfDay = CB_MetTimeOfDay.SelectedIndex; - - SavePartyStats(pk2); - pk2.FixMoves(); - return pk2; - } + SavePartyStats(pk2); + pk2.FixMoves(); + return pk2; } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/EditPK3.cs b/PKHeX.WinForms/Controls/PKM Editor/EditPK3.cs index 11028fdf9..4fe439318 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/EditPK3.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/EditPK3.cs @@ -1,44 +1,43 @@ using System; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PKMEditor { - public partial class PKMEditor + private void PopulateFieldsPK3() { - private void PopulateFieldsPK3() - { - if (Entity is not G3PKM pk3) - throw new FormatException(nameof(Entity)); + if (Entity is not G3PKM pk3) + throw new FormatException(nameof(Entity)); - LoadMisc1(pk3); - LoadMisc2(pk3); - LoadMisc3(pk3); + LoadMisc1(pk3); + LoadMisc2(pk3); + LoadMisc3(pk3); - CB_Ability.SelectedIndex = pk3.AbilityBit && CB_Ability.Items.Count > 1 ? 1 : 0; - if (pk3 is IShadowPKM s) - LoadShadow3(s); + CB_Ability.SelectedIndex = pk3.AbilityBit && CB_Ability.Items.Count > 1 ? 1 : 0; + if (pk3 is IShadowPKM s) + LoadShadow3(s); - LoadPartyStats(pk3); - UpdateStats(); - } + LoadPartyStats(pk3); + UpdateStats(); + } - private G3PKM PreparePK3() - { - if (Entity is not G3PKM pk3) - throw new FormatException(nameof(Entity)); + private G3PKM PreparePK3() + { + if (Entity is not G3PKM pk3) + throw new FormatException(nameof(Entity)); - SaveMisc1(pk3); - SaveMisc2(pk3); - SaveMisc3(pk3); + SaveMisc1(pk3); + SaveMisc2(pk3); + SaveMisc3(pk3); - pk3.AbilityBit = CB_Ability.SelectedIndex != 0; - if (Entity is IShadowPKM ck3) - SaveShadow3(ck3); + pk3.AbilityBit = CB_Ability.SelectedIndex != 0; + if (Entity is IShadowPKM ck3) + SaveShadow3(ck3); - SavePartyStats(pk3); - pk3.FixMoves(); - pk3.RefreshChecksum(); - return pk3; - } + SavePartyStats(pk3); + pk3.FixMoves(); + pk3.RefreshChecksum(); + return pk3; } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/EditPK4.cs b/PKHeX.WinForms/Controls/PKM Editor/EditPK4.cs index ffb78e741..f241c8882 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/EditPK4.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/EditPK4.cs @@ -1,54 +1,53 @@ using System; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PKMEditor { - public partial class PKMEditor + private void PopulateFieldsPK4() { - private void PopulateFieldsPK4() - { - if (Entity is not G4PKM pk4) - throw new FormatException(nameof(Entity)); + if (Entity is not G4PKM pk4) + throw new FormatException(nameof(Entity)); - LoadMisc1(pk4); - LoadMisc2(pk4); - LoadMisc3(pk4); - LoadMisc4(pk4); + LoadMisc1(pk4); + LoadMisc2(pk4); + LoadMisc3(pk4); + LoadMisc4(pk4); - CB_GroundTile.SelectedValue = pk4.Gen4 ? (int)pk4.GroundTile : 0; - CB_GroundTile.Visible = Label_GroundTile.Visible = Entity.Gen4; + CB_GroundTile.SelectedValue = pk4.Gen4 ? (int)pk4.GroundTile : 0; + CB_GroundTile.Visible = Label_GroundTile.Visible = Entity.Gen4; - if (HaX) - DEV_Ability.SelectedValue = pk4.Ability; - else - LoadAbility4(pk4); + if (HaX) + DEV_Ability.SelectedValue = pk4.Ability; + else + LoadAbility4(pk4); - // Minor properties - ShinyLeaf.SetValue(pk4.ShinyLeaf); + // Minor properties + ShinyLeaf.SetValue(pk4.ShinyLeaf); - LoadPartyStats(pk4); - UpdateStats(); - } + LoadPartyStats(pk4); + UpdateStats(); + } - private G4PKM PreparePK4() - { - if (Entity is not G4PKM pk4) - throw new FormatException(nameof(Entity)); + private G4PKM PreparePK4() + { + if (Entity is not G4PKM pk4) + throw new FormatException(nameof(Entity)); - SaveMisc1(pk4); - SaveMisc2(pk4); - SaveMisc3(pk4); - SaveMisc4(pk4); + SaveMisc1(pk4); + SaveMisc2(pk4); + SaveMisc3(pk4); + SaveMisc4(pk4); - pk4.GroundTile = (GroundTileType)WinFormsUtil.GetIndex(CB_GroundTile); + pk4.GroundTile = (GroundTileType)WinFormsUtil.GetIndex(CB_GroundTile); - // Minor properties - pk4.ShinyLeaf = ShinyLeaf.GetValue(); + // Minor properties + pk4.ShinyLeaf = ShinyLeaf.GetValue(); - SavePartyStats(pk4); - pk4.FixMoves(); - pk4.RefreshChecksum(); - return pk4; - } + SavePartyStats(pk4); + pk4.FixMoves(); + pk4.RefreshChecksum(); + return pk4; } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/EditPK5.cs b/PKHeX.WinForms/Controls/PKM Editor/EditPK5.cs index 0da324182..5571aa33c 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/EditPK5.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/EditPK5.cs @@ -1,53 +1,52 @@ using System; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PKMEditor { - public partial class PKMEditor + private void PopulateFieldsPK5() { - private void PopulateFieldsPK5() - { - if (Entity is not PK5 pk5) - throw new FormatException(nameof(Entity)); + if (Entity is not PK5 pk5) + throw new FormatException(nameof(Entity)); - LoadMisc1(pk5); - LoadMisc2(pk5); - LoadMisc3(pk5); - LoadMisc4(pk5); - CB_GroundTile.SelectedValue = pk5.Gen4 ? (int)pk5.GroundTile : 0; - CB_GroundTile.Visible = Label_GroundTile.Visible = pk5.Gen4; - CHK_NSparkle.Checked = pk5.NSparkle; + LoadMisc1(pk5); + LoadMisc2(pk5); + LoadMisc3(pk5); + LoadMisc4(pk5); + CB_GroundTile.SelectedValue = pk5.Gen4 ? (int)pk5.GroundTile : 0; + CB_GroundTile.Visible = Label_GroundTile.Visible = pk5.Gen4; + CHK_NSparkle.Checked = pk5.NSparkle; - if (HaX) - DEV_Ability.SelectedValue = pk5.Ability; - else if (pk5.HiddenAbility) - CB_Ability.SelectedIndex = CB_Ability.Items.Count - 1; - else - LoadAbility4(pk5); + if (HaX) + DEV_Ability.SelectedValue = pk5.Ability; + else if (pk5.HiddenAbility) + CB_Ability.SelectedIndex = CB_Ability.Items.Count - 1; + else + LoadAbility4(pk5); - LoadPartyStats(pk5); - UpdateStats(); - } + LoadPartyStats(pk5); + UpdateStats(); + } - private PK5 PreparePK5() - { - if (Entity is not PK5 pk5) - throw new FormatException(nameof(Entity)); + private PK5 PreparePK5() + { + if (Entity is not PK5 pk5) + throw new FormatException(nameof(Entity)); - SaveMisc1(pk5); - SaveMisc2(pk5); - SaveMisc3(pk5); - SaveMisc4(pk5); + SaveMisc1(pk5); + SaveMisc2(pk5); + SaveMisc3(pk5); + SaveMisc4(pk5); - pk5.GroundTile = (GroundTileType)WinFormsUtil.GetIndex(CB_GroundTile); - pk5.NSparkle = CHK_NSparkle.Checked; - if (!HaX) // specify via extra 0x42 instead - pk5.HiddenAbility = CB_Ability.SelectedIndex > 1; // not 0 or 1 + pk5.GroundTile = (GroundTileType)WinFormsUtil.GetIndex(CB_GroundTile); + pk5.NSparkle = CHK_NSparkle.Checked; + if (!HaX) // specify via extra 0x42 instead + pk5.HiddenAbility = CB_Ability.SelectedIndex > 1; // not 0 or 1 - SavePartyStats(pk5); - pk5.FixMoves(); - pk5.RefreshChecksum(); - return pk5; - } + SavePartyStats(pk5); + pk5.FixMoves(); + pk5.RefreshChecksum(); + return pk5; } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/EditPK6.cs b/PKHeX.WinForms/Controls/PKM Editor/EditPK6.cs index 00686ffee..74aefd73a 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/EditPK6.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/EditPK6.cs @@ -1,56 +1,55 @@ using System; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PKMEditor { - public partial class PKMEditor + private void PopulateFieldsPK6() { - private void PopulateFieldsPK6() - { - if (Entity is not PK6 pk6) - throw new FormatException(nameof(Entity)); + if (Entity is not PK6 pk6) + throw new FormatException(nameof(Entity)); - LoadMisc1(pk6); - LoadMisc2(pk6); - LoadMisc3(pk6); - LoadMisc4(pk6); - LoadMisc6(pk6); + LoadMisc1(pk6); + LoadMisc2(pk6); + LoadMisc3(pk6); + LoadMisc4(pk6); + LoadMisc6(pk6); - CB_GroundTile.SelectedValue = pk6.Gen4 ? (int)pk6.GroundTile : 0; - CB_GroundTile.Visible = Label_GroundTile.Visible = pk6.Gen4; + CB_GroundTile.SelectedValue = pk6.Gen4 ? (int)pk6.GroundTile : 0; + CB_GroundTile.Visible = Label_GroundTile.Visible = pk6.Gen4; - LoadPartyStats(pk6); - UpdateStats(); - } + LoadPartyStats(pk6); + UpdateStats(); + } - private PK6 PreparePK6() - { - if (Entity is not PK6 pk6) - throw new FormatException(nameof(Entity)); + private PK6 PreparePK6() + { + if (Entity is not PK6 pk6) + throw new FormatException(nameof(Entity)); - SaveMisc1(pk6); - SaveMisc2(pk6); - SaveMisc3(pk6); - SaveMisc4(pk6); - SaveMisc6(pk6); + SaveMisc1(pk6); + SaveMisc2(pk6); + SaveMisc3(pk6); + SaveMisc4(pk6); + SaveMisc6(pk6); - pk6.GroundTile = (GroundTileType)WinFormsUtil.GetIndex(CB_GroundTile); + pk6.GroundTile = (GroundTileType)WinFormsUtil.GetIndex(CB_GroundTile); - // Toss in Party Stats - SavePartyStats(pk6); + // Toss in Party Stats + SavePartyStats(pk6); - // Unneeded Party Stats (Status, Flags, Unused) - pk6.Data[0xE8] = pk6.Data[0xE9] = pk6.Data[0xEA] = pk6.Data[0xEB] = - pk6.Data[0xEF] = + // Unneeded Party Stats (Status, Flags, Unused) + pk6.Data[0xE8] = pk6.Data[0xE9] = pk6.Data[0xEA] = pk6.Data[0xEB] = + pk6.Data[0xEF] = pk6.Data[0xFE] = pk6.Data[0xFF] = pk6.Data[0x100] = - pk6.Data[0x101] = pk6.Data[0x102] = pk6.Data[0x103] = 0; + pk6.Data[0x101] = pk6.Data[0x102] = pk6.Data[0x103] = 0; - pk6.FixMoves(); - pk6.FixRelearn(); - if (ModifyPKM) - pk6.FixMemories(); - pk6.RefreshChecksum(); - return pk6; - } + pk6.FixMoves(); + pk6.FixRelearn(); + if (ModifyPKM) + pk6.FixMemories(); + pk6.RefreshChecksum(); + return pk6; } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/EditPK7.cs b/PKHeX.WinForms/Controls/PKM Editor/EditPK7.cs index 259f65ead..1956949bd 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/EditPK7.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/EditPK7.cs @@ -1,96 +1,95 @@ using System; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PKMEditor { - public partial class PKMEditor + private void PopulateFieldsPK7() { - private void PopulateFieldsPK7() - { - if (Entity is not PK7 pk7) - throw new FormatException(nameof(Entity)); + if (Entity is not PK7 pk7) + throw new FormatException(nameof(Entity)); - LoadMisc1(pk7); - LoadMisc2(pk7); - LoadMisc3(pk7); - LoadMisc4(pk7); - LoadMisc6(pk7); + LoadMisc1(pk7); + LoadMisc2(pk7); + LoadMisc3(pk7); + LoadMisc4(pk7); + LoadMisc6(pk7); - LoadPartyStats(pk7); - UpdateStats(); - } + LoadPartyStats(pk7); + UpdateStats(); + } - private PK7 PreparePK7() - { - if (Entity is not PK7 pk7) - throw new FormatException(nameof(Entity)); + private PK7 PreparePK7() + { + if (Entity is not PK7 pk7) + throw new FormatException(nameof(Entity)); - SaveMisc1(pk7); - SaveMisc2(pk7); - SaveMisc3(pk7); - SaveMisc4(pk7); - SaveMisc6(pk7); + SaveMisc1(pk7); + SaveMisc2(pk7); + SaveMisc3(pk7); + SaveMisc4(pk7); + SaveMisc6(pk7); - // Toss in Party Stats - SavePartyStats(pk7); + // Toss in Party Stats + SavePartyStats(pk7); - // Unneeded Party Stats (Status, Flags, Unused) - pk7.Status_Condition = pk7.DirtType = pk7.DirtLocation = - pk7.Data[0xEF] = + // Unneeded Party Stats (Status, Flags, Unused) + pk7.Status_Condition = pk7.DirtType = pk7.DirtLocation = + pk7.Data[0xEF] = pk7.Data[0xFE] = pk7.Data[0xFF] = pk7.Data[0x100] = - pk7.Data[0x101] = pk7.Data[0x102] = pk7.Data[0x103] = 0; + pk7.Data[0x101] = pk7.Data[0x102] = pk7.Data[0x103] = 0; - pk7.FixMoves(); - pk7.FixRelearn(); - if (ModifyPKM) - pk7.FixMemories(); - pk7.RefreshChecksum(); - return pk7; - } + pk7.FixMoves(); + pk7.FixRelearn(); + if (ModifyPKM) + pk7.FixMemories(); + pk7.RefreshChecksum(); + return pk7; + } - private void PopulateFieldsPB7() - { - if (Entity is not PB7 pk7) - throw new FormatException(nameof(Entity)); + private void PopulateFieldsPB7() + { + if (Entity is not PB7 pk7) + throw new FormatException(nameof(Entity)); - LoadMisc1(pk7); - LoadMisc2(pk7); - LoadMisc3(pk7); - LoadMisc4(pk7); - LoadMisc6(pk7); - LoadAVs(pk7); - SizeCP.LoadPKM(pk7); + LoadMisc1(pk7); + LoadMisc2(pk7); + LoadMisc3(pk7); + LoadMisc4(pk7); + LoadMisc6(pk7); + LoadAVs(pk7); + SizeCP.LoadPKM(pk7); - LoadPartyStats(pk7); - UpdateStats(); - } + LoadPartyStats(pk7); + UpdateStats(); + } - private PB7 PreparePB7() - { - if (Entity is not PB7 pk7) - throw new FormatException(nameof(Entity)); + private PB7 PreparePB7() + { + if (Entity is not PB7 pk7) + throw new FormatException(nameof(Entity)); - SaveMisc1(pk7); - SaveMisc2(pk7); - SaveMisc3(pk7); - SaveMisc4(pk7); - SaveMisc6(pk7); + SaveMisc1(pk7); + SaveMisc2(pk7); + SaveMisc3(pk7); + SaveMisc4(pk7); + SaveMisc6(pk7); - // Toss in Party Stats - SavePartyStats(pk7); + // Toss in Party Stats + SavePartyStats(pk7); - if (pk7.Stat_CP == 0) - pk7.ResetCP(); + if (pk7.Stat_CP == 0) + pk7.ResetCP(); - // heal values to original - pk7.FieldEventFatigue1 = pk7.FieldEventFatigue2 = 100; + // heal values to original + pk7.FieldEventFatigue1 = pk7.FieldEventFatigue2 = 100; - pk7.FixMoves(); - pk7.FixRelearn(); - if (ModifyPKM) - pk7.FixMemories(); - pk7.RefreshChecksum(); - return pk7; - } + pk7.FixMoves(); + pk7.FixRelearn(); + if (ModifyPKM) + pk7.FixMemories(); + pk7.RefreshChecksum(); + return pk7; } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/EditPK8.cs b/PKHeX.WinForms/Controls/PKM Editor/EditPK8.cs index d9303b3fb..9333b82a3 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/EditPK8.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/EditPK8.cs @@ -1,129 +1,128 @@ using System; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PKMEditor { - public partial class PKMEditor + private void PopulateFieldsPK8() { - private void PopulateFieldsPK8() - { - if (Entity is not PK8 pk8) - throw new FormatException(nameof(Entity)); + if (Entity is not PK8 pk8) + throw new FormatException(nameof(Entity)); - LoadMisc1(pk8); - LoadMisc2(pk8); - LoadMisc3(pk8); - LoadMisc4(pk8); - LoadMisc6(pk8); - SizeCP.LoadPKM(pk8); - LoadMisc8(pk8); + LoadMisc1(pk8); + LoadMisc2(pk8); + LoadMisc3(pk8); + LoadMisc4(pk8); + LoadMisc6(pk8); + SizeCP.LoadPKM(pk8); + LoadMisc8(pk8); - LoadPartyStats(pk8); - UpdateStats(); - } + LoadPartyStats(pk8); + UpdateStats(); + } - private PK8 PreparePK8() - { - if (Entity is not PK8 pk8) - throw new FormatException(nameof(Entity)); + private PK8 PreparePK8() + { + if (Entity is not PK8 pk8) + throw new FormatException(nameof(Entity)); - SaveMisc1(pk8); - SaveMisc2(pk8); - SaveMisc3(pk8); - SaveMisc4(pk8); - SaveMisc6(pk8); - SaveMisc8(pk8); + SaveMisc1(pk8); + SaveMisc2(pk8); + SaveMisc3(pk8); + SaveMisc4(pk8); + SaveMisc6(pk8); + SaveMisc8(pk8); - // Toss in Party Stats - SavePartyStats(pk8); + // Toss in Party Stats + SavePartyStats(pk8); - pk8.FixMoves(); - pk8.FixRelearn(); - if (ModifyPKM) - pk8.FixMemories(); - pk8.RefreshChecksum(); - return pk8; - } + pk8.FixMoves(); + pk8.FixRelearn(); + if (ModifyPKM) + pk8.FixMemories(); + pk8.RefreshChecksum(); + return pk8; + } - private PB8 PreparePB8() - { - if (Entity is not PB8 pk8) - throw new FormatException(nameof(Entity)); + private PB8 PreparePB8() + { + if (Entity is not PB8 pk8) + throw new FormatException(nameof(Entity)); - SaveMisc1(pk8); - SaveMisc2(pk8); - SaveMisc3(pk8); - SaveMisc4(pk8); - SaveMisc6(pk8); - SaveMisc8(pk8); + SaveMisc1(pk8); + SaveMisc2(pk8); + SaveMisc3(pk8); + SaveMisc4(pk8); + SaveMisc6(pk8); + SaveMisc8(pk8); - // Toss in Party Stats - SavePartyStats(pk8); + // Toss in Party Stats + SavePartyStats(pk8); - pk8.FixMoves(); - pk8.FixRelearn(); - if (ModifyPKM) - pk8.FixMemories(); - pk8.RefreshChecksum(); - return pk8; - } + pk8.FixMoves(); + pk8.FixRelearn(); + if (ModifyPKM) + pk8.FixMemories(); + pk8.RefreshChecksum(); + return pk8; + } - private void PopulateFieldsPB8() - { - if (Entity is not PB8 pk8) - throw new FormatException(nameof(Entity)); + private void PopulateFieldsPB8() + { + if (Entity is not PB8 pk8) + throw new FormatException(nameof(Entity)); - LoadMisc1(pk8); - LoadMisc2(pk8); - LoadMisc3(pk8); - LoadMisc4(pk8); - LoadMisc6(pk8); - SizeCP.LoadPKM(pk8); - LoadMisc8(pk8); + LoadMisc1(pk8); + LoadMisc2(pk8); + LoadMisc3(pk8); + LoadMisc4(pk8); + LoadMisc6(pk8); + SizeCP.LoadPKM(pk8); + LoadMisc8(pk8); - LoadPartyStats(pk8); - UpdateStats(); - } + LoadPartyStats(pk8); + UpdateStats(); + } - private PA8 PreparePA8() - { - if (Entity is not PA8 pk8) - throw new FormatException(nameof(Entity)); + private PA8 PreparePA8() + { + if (Entity is not PA8 pk8) + throw new FormatException(nameof(Entity)); - SaveMisc1(pk8); - SaveMisc2(pk8); - SaveMisc3(pk8); - SaveMisc4(pk8); - SaveMisc6(pk8); - SaveMisc8(pk8); + SaveMisc1(pk8); + SaveMisc2(pk8); + SaveMisc3(pk8); + SaveMisc4(pk8); + SaveMisc6(pk8); + SaveMisc8(pk8); - // Toss in Party Stats - SavePartyStats(pk8); + // Toss in Party Stats + SavePartyStats(pk8); - pk8.FixMoves(); - pk8.FixRelearn(); - if (ModifyPKM) - pk8.FixMemories(); - pk8.RefreshChecksum(); - return pk8; - } + pk8.FixMoves(); + pk8.FixRelearn(); + if (ModifyPKM) + pk8.FixMemories(); + pk8.RefreshChecksum(); + return pk8; + } - private void PopulateFieldsPA8() - { - if (Entity is not PA8 pk8) - throw new FormatException(nameof(Entity)); + private void PopulateFieldsPA8() + { + if (Entity is not PA8 pk8) + throw new FormatException(nameof(Entity)); - LoadMisc1(pk8); - LoadMisc2(pk8); - LoadMisc3(pk8); - LoadMisc4(pk8); - LoadMisc6(pk8); - LoadGVs(pk8); - SizeCP.LoadPKM(pk8); - LoadMisc8(pk8); + LoadMisc1(pk8); + LoadMisc2(pk8); + LoadMisc3(pk8); + LoadMisc4(pk8); + LoadMisc6(pk8); + LoadGVs(pk8); + SizeCP.LoadPKM(pk8); + LoadMisc8(pk8); - LoadPartyStats(pk8); - UpdateStats(); - } + LoadPartyStats(pk8); + UpdateStats(); } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/FormArgument.cs b/PKHeX.WinForms/Controls/PKM Editor/FormArgument.cs index d7898932b..272a59783 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/FormArgument.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/FormArgument.cs @@ -2,99 +2,98 @@ using System.Windows.Forms; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class FormArgument : UserControl { - public partial class FormArgument : UserControl + private bool IsRawMode; + private int CurrentSpecies; + private int CurrentForm; + private int CurrentGeneration; + private bool FieldsLoaded; + + public FormArgument() => InitializeComponent(); + + public void LoadArgument(IFormArgument f, int species, int form, int generation) { - private bool IsRawMode; - private int CurrentSpecies; - private int CurrentForm; - private int CurrentGeneration; - private bool FieldsLoaded; - - public FormArgument() => InitializeComponent(); - - public void LoadArgument(IFormArgument f, int species, int form, int generation) + FieldsLoaded = false; + var max = FormArgumentUtil.GetFormArgumentMax(species, form, generation); + if (max == 0) { - FieldsLoaded = false; - var max = FormArgumentUtil.GetFormArgumentMax(species, form, generation); - if (max == 0) - { - CurrentSpecies = species; - CurrentForm = form; - CurrentGeneration = generation; - NUD_FormArg.Value = CB_FormArg.SelectedIndex = 0; - CB_FormArg.Visible = false; - NUD_FormArg.Visible = false; - FieldsLoaded = true; - return; - } - - bool named = FormConverter.GetFormArgumentIsNamedIndex(species); - if (named) - { - if (CurrentSpecies == species && CurrentForm == form && CurrentGeneration == generation) - { - CurrentValue = f.FormArgument; - FieldsLoaded = true; - return; - } - IsRawMode = false; - - NUD_FormArg.Visible = false; - CB_FormArg.Items.Clear(); - var args = FormConverter.GetFormArgumentStrings(species); - CB_FormArg.Items.AddRange(args); - CB_FormArg.SelectedIndex = 0; - CB_FormArg.Visible = true; - } - else - { - IsRawMode = true; - - CB_FormArg.Visible = false; - NUD_FormArg.Maximum = max; - NUD_FormArg.Visible = true; - } CurrentSpecies = species; CurrentForm = form; CurrentGeneration = generation; - - bool isPair = FormArgumentUtil.IsFormArgumentTypeDatePair(species, form); - CurrentValue = isPair ? f.FormArgumentRemain : f.FormArgument; - + NUD_FormArg.Value = CB_FormArg.SelectedIndex = 0; + CB_FormArg.Visible = false; + NUD_FormArg.Visible = false; FieldsLoaded = true; + return; } - public void SaveArgument(IFormArgument f) + bool named = FormConverter.GetFormArgumentIsNamedIndex(species); + if (named) { - f.ChangeFormArgument(CurrentSpecies, CurrentForm, CurrentGeneration, CurrentValue); - } - - private uint CurrentValue - { - get => IsRawMode ? (uint) NUD_FormArg.Value : (uint) CB_FormArg.SelectedIndex; - set + if (CurrentSpecies == species && CurrentForm == form && CurrentGeneration == generation) { - if (IsRawMode) - NUD_FormArg.Value = Math.Min(NUD_FormArg.Maximum, value); - else - CB_FormArg.SelectedIndex = Math.Min(CB_FormArg.Items.Count - 1, (int)value); + CurrentValue = f.FormArgument; + FieldsLoaded = true; + return; } + IsRawMode = false; + + NUD_FormArg.Visible = false; + CB_FormArg.Items.Clear(); + var args = FormConverter.GetFormArgumentStrings(species); + CB_FormArg.Items.AddRange(args); + CB_FormArg.SelectedIndex = 0; + CB_FormArg.Visible = true; } - - public event EventHandler? ValueChanged; - - private void CB_FormArg_SelectedIndexChanged(object sender, EventArgs e) + else { - if (FieldsLoaded) - ValueChanged?.Invoke(sender, e); + IsRawMode = true; + + CB_FormArg.Visible = false; + NUD_FormArg.Maximum = max; + NUD_FormArg.Visible = true; } + CurrentSpecies = species; + CurrentForm = form; + CurrentGeneration = generation; - private void NUD_FormArg_ValueChanged(object sender, EventArgs e) + bool isPair = FormArgumentUtil.IsFormArgumentTypeDatePair(species, form); + CurrentValue = isPair ? f.FormArgumentRemain : f.FormArgument; + + FieldsLoaded = true; + } + + public void SaveArgument(IFormArgument f) + { + f.ChangeFormArgument(CurrentSpecies, CurrentForm, CurrentGeneration, CurrentValue); + } + + private uint CurrentValue + { + get => IsRawMode ? (uint) NUD_FormArg.Value : (uint) CB_FormArg.SelectedIndex; + set { - if (FieldsLoaded) - ValueChanged?.Invoke(sender, e); + if (IsRawMode) + NUD_FormArg.Value = Math.Min(NUD_FormArg.Maximum, value); + else + CB_FormArg.SelectedIndex = Math.Min(CB_FormArg.Items.Count - 1, (int)value); } } + + public event EventHandler? ValueChanged; + + private void CB_FormArg_SelectedIndexChanged(object sender, EventArgs e) + { + if (FieldsLoaded) + ValueChanged?.Invoke(sender, e); + } + + private void NUD_FormArg_ValueChanged(object sender, EventArgs e) + { + if (FieldsLoaded) + ValueChanged?.Invoke(sender, e); + } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs b/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs index 9d2f74018..1f6aa2e59 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/LoadSave.cs @@ -4,468 +4,467 @@ using PKHeX.Drawing; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PKMEditor { - public partial class PKMEditor + private void LoadNickname(PKM pk) { - private void LoadNickname(PKM pk) + CHK_Nicknamed.Checked = pk.IsNicknamed; + TB_Nickname.Text = pk.Nickname; + } + + private void SaveNickname(PKM pk) + { + pk.IsNicknamed = CHK_Nicknamed.Checked; + pk.Nickname = TB_Nickname.Text; + } + + private void LoadSpeciesLevelEXP(PKM pk) + { + if (!HaX) { - CHK_Nicknamed.Checked = pk.IsNicknamed; - TB_Nickname.Text = pk.Nickname; + // Sanity check level and EXP + var current = pk.CurrentLevel; + if (current == 100) // clamp back to max EXP + pk.CurrentLevel = 100; } - private void SaveNickname(PKM pk) + CB_Species.SelectedValue = pk.Species; + TB_Level.Text = pk.Stat_Level.ToString(); + TB_EXP.Text = pk.EXP.ToString(); + } + + private void SaveSpeciesLevelEXP(PKM pk) + { + pk.Species = WinFormsUtil.GetIndex(CB_Species); + pk.EXP = Util.ToUInt32(TB_EXP.Text); + pk.Stat_Level = Util.ToInt32(TB_Level.Text); + } + + private void LoadOT(PKM pk) + { + GB_OT.BackgroundImage = null; // clear the Current Handler indicator just in case we switched formats. + TB_OT.Text = pk.OT_Name; + UC_OTGender.Gender = pk.OT_Gender & 1; + } + + private void SaveOT(PKM pk) + { + pk.OT_Name = TB_OT.Text; + pk.OT_Gender = UC_OTGender.Gender; + } + + private void LoadPKRS(PKM pk) + { + Label_PKRS.Visible = CB_PKRSStrain.Visible = CHK_Infected.Checked = Label_PKRSdays.Visible = CB_PKRSDays.Visible = pk.PKRS_Infected; + LoadClamp(CB_PKRSStrain, pk.PKRS_Strain); + CHK_Cured.Checked = pk.PKRS_Cured; + LoadClamp(CB_PKRSDays, pk.PKRS_Days); // clamp to valid day values for the current strain + } + + private void SavePKRS(PKM pk) + { + pk.PKRS_Days = CB_PKRSDays.SelectedIndex; + pk.PKRS_Strain = CB_PKRSStrain.SelectedIndex; + } + + private void LoadIVs(PKM pk) => Stats.LoadIVs(pk.IVs); + private void LoadEVs(PKM pk) => Stats.LoadEVs(pk.EVs); + private void LoadAVs(IAwakened pk) => Stats.LoadAVs(pk); + private void LoadGVs(IGanbaru pk) => Stats.LoadGVs(pk); + + private static void LoadClamp(ComboBox cb, int value) + { + var max = cb.Items.Count - 1; + if (value > max) + value = max; + else if (value < -1) + value = 0; + cb.SelectedIndex = value; + } + + private void LoadMoves(PKM pk) + { + CB_Move1.SelectedValue = pk.Move1; + CB_Move2.SelectedValue = pk.Move2; + CB_Move3.SelectedValue = pk.Move3; + CB_Move4.SelectedValue = pk.Move4; + LoadClamp(CB_PPu1, pk.Move1_PPUps); + LoadClamp(CB_PPu2, pk.Move2_PPUps); + LoadClamp(CB_PPu3, pk.Move3_PPUps); + LoadClamp(CB_PPu4, pk.Move4_PPUps); + TB_PP1.Text = pk.Move1_PP.ToString(); + TB_PP2.Text = pk.Move2_PP.ToString(); + TB_PP3.Text = pk.Move3_PP.ToString(); + TB_PP4.Text = pk.Move4_PP.ToString(); + } + + private void SaveMoves(PKM pk) + { + pk.Move1 = WinFormsUtil.GetIndex(CB_Move1); + pk.Move2 = WinFormsUtil.GetIndex(CB_Move2); + pk.Move3 = WinFormsUtil.GetIndex(CB_Move3); + pk.Move4 = WinFormsUtil.GetIndex(CB_Move4); + pk.Move1_PP = WinFormsUtil.GetIndex(CB_Move1) > 0 ? Util.ToInt32(TB_PP1.Text) : 0; + pk.Move2_PP = WinFormsUtil.GetIndex(CB_Move2) > 0 ? Util.ToInt32(TB_PP2.Text) : 0; + pk.Move3_PP = WinFormsUtil.GetIndex(CB_Move3) > 0 ? Util.ToInt32(TB_PP3.Text) : 0; + pk.Move4_PP = WinFormsUtil.GetIndex(CB_Move4) > 0 ? Util.ToInt32(TB_PP4.Text) : 0; + pk.Move1_PPUps = WinFormsUtil.GetIndex(CB_Move1) > 0 ? CB_PPu1.SelectedIndex : 0; + pk.Move2_PPUps = WinFormsUtil.GetIndex(CB_Move2) > 0 ? CB_PPu2.SelectedIndex : 0; + pk.Move3_PPUps = WinFormsUtil.GetIndex(CB_Move3) > 0 ? CB_PPu3.SelectedIndex : 0; + pk.Move4_PPUps = WinFormsUtil.GetIndex(CB_Move4) > 0 ? CB_PPu4.SelectedIndex : 0; + } + + private void LoadShadow3(IShadowPKM pk) + { + NUD_ShadowID.Value = pk.ShadowID; + FLP_Purification.Visible = pk.ShadowID > 0; + if (pk.ShadowID > 0) { - pk.IsNicknamed = CHK_Nicknamed.Checked; - pk.Nickname = TB_Nickname.Text; + int value = pk.Purification; + if (value < NUD_Purification.Minimum) + value = (int)NUD_Purification.Minimum; + + NUD_Purification.Value = value; + CHK_Shadow.Checked = pk.IsShadow; + + NUD_ShadowID.Value = Math.Max(pk.ShadowID, (ushort)0); } - - private void LoadSpeciesLevelEXP(PKM pk) + else { - if (!HaX) - { - // Sanity check level and EXP - var current = pk.CurrentLevel; - if (current == 100) // clamp back to max EXP - pk.CurrentLevel = 100; - } - - CB_Species.SelectedValue = pk.Species; - TB_Level.Text = pk.Stat_Level.ToString(); - TB_EXP.Text = pk.EXP.ToString(); - } - - private void SaveSpeciesLevelEXP(PKM pk) - { - pk.Species = WinFormsUtil.GetIndex(CB_Species); - pk.EXP = Util.ToUInt32(TB_EXP.Text); - pk.Stat_Level = Util.ToInt32(TB_Level.Text); - } - - private void LoadOT(PKM pk) - { - GB_OT.BackgroundImage = null; // clear the Current Handler indicator just in case we switched formats. - TB_OT.Text = pk.OT_Name; - UC_OTGender.Gender = pk.OT_Gender & 1; - } - - private void SaveOT(PKM pk) - { - pk.OT_Name = TB_OT.Text; - pk.OT_Gender = UC_OTGender.Gender; - } - - private void LoadPKRS(PKM pk) - { - Label_PKRS.Visible = CB_PKRSStrain.Visible = CHK_Infected.Checked = Label_PKRSdays.Visible = CB_PKRSDays.Visible = pk.PKRS_Infected; - LoadClamp(CB_PKRSStrain, pk.PKRS_Strain); - CHK_Cured.Checked = pk.PKRS_Cured; - LoadClamp(CB_PKRSDays, pk.PKRS_Days); // clamp to valid day values for the current strain - } - - private void SavePKRS(PKM pk) - { - pk.PKRS_Days = CB_PKRSDays.SelectedIndex; - pk.PKRS_Strain = CB_PKRSStrain.SelectedIndex; - } - - private void LoadIVs(PKM pk) => Stats.LoadIVs(pk.IVs); - private void LoadEVs(PKM pk) => Stats.LoadEVs(pk.EVs); - private void LoadAVs(IAwakened pk) => Stats.LoadAVs(pk); - private void LoadGVs(IGanbaru pk) => Stats.LoadGVs(pk); - - private static void LoadClamp(ComboBox cb, int value) - { - var max = cb.Items.Count - 1; - if (value > max) - value = max; - else if (value < -1) - value = 0; - cb.SelectedIndex = value; - } - - private void LoadMoves(PKM pk) - { - CB_Move1.SelectedValue = pk.Move1; - CB_Move2.SelectedValue = pk.Move2; - CB_Move3.SelectedValue = pk.Move3; - CB_Move4.SelectedValue = pk.Move4; - LoadClamp(CB_PPu1, pk.Move1_PPUps); - LoadClamp(CB_PPu2, pk.Move2_PPUps); - LoadClamp(CB_PPu3, pk.Move3_PPUps); - LoadClamp(CB_PPu4, pk.Move4_PPUps); - TB_PP1.Text = pk.Move1_PP.ToString(); - TB_PP2.Text = pk.Move2_PP.ToString(); - TB_PP3.Text = pk.Move3_PP.ToString(); - TB_PP4.Text = pk.Move4_PP.ToString(); - } - - private void SaveMoves(PKM pk) - { - pk.Move1 = WinFormsUtil.GetIndex(CB_Move1); - pk.Move2 = WinFormsUtil.GetIndex(CB_Move2); - pk.Move3 = WinFormsUtil.GetIndex(CB_Move3); - pk.Move4 = WinFormsUtil.GetIndex(CB_Move4); - pk.Move1_PP = WinFormsUtil.GetIndex(CB_Move1) > 0 ? Util.ToInt32(TB_PP1.Text) : 0; - pk.Move2_PP = WinFormsUtil.GetIndex(CB_Move2) > 0 ? Util.ToInt32(TB_PP2.Text) : 0; - pk.Move3_PP = WinFormsUtil.GetIndex(CB_Move3) > 0 ? Util.ToInt32(TB_PP3.Text) : 0; - pk.Move4_PP = WinFormsUtil.GetIndex(CB_Move4) > 0 ? Util.ToInt32(TB_PP4.Text) : 0; - pk.Move1_PPUps = WinFormsUtil.GetIndex(CB_Move1) > 0 ? CB_PPu1.SelectedIndex : 0; - pk.Move2_PPUps = WinFormsUtil.GetIndex(CB_Move2) > 0 ? CB_PPu2.SelectedIndex : 0; - pk.Move3_PPUps = WinFormsUtil.GetIndex(CB_Move3) > 0 ? CB_PPu3.SelectedIndex : 0; - pk.Move4_PPUps = WinFormsUtil.GetIndex(CB_Move4) > 0 ? CB_PPu4.SelectedIndex : 0; - } - - private void LoadShadow3(IShadowPKM pk) - { - NUD_ShadowID.Value = pk.ShadowID; - FLP_Purification.Visible = pk.ShadowID > 0; - if (pk.ShadowID > 0) - { - int value = pk.Purification; - if (value < NUD_Purification.Minimum) - value = (int)NUD_Purification.Minimum; - - NUD_Purification.Value = value; - CHK_Shadow.Checked = pk.IsShadow; - - NUD_ShadowID.Value = Math.Max(pk.ShadowID, (ushort)0); - } - else - { - NUD_Purification.Value = 0; - CHK_Shadow.Checked = false; - NUD_ShadowID.Value = 0; - } - } - - private void SaveShadow3(IShadowPKM pk) - { - pk.ShadowID = (ushort)NUD_ShadowID.Value; - if (pk.ShadowID > 0) - pk.Purification = (int)NUD_Purification.Value; - } - - private void LoadRelearnMoves(PKM pk) - { - CB_RelearnMove1.SelectedValue = pk.RelearnMove1; - CB_RelearnMove2.SelectedValue = pk.RelearnMove2; - CB_RelearnMove3.SelectedValue = pk.RelearnMove3; - CB_RelearnMove4.SelectedValue = pk.RelearnMove4; - } - - private void SaveRelearnMoves(PKM pk) - { - pk.RelearnMove1 = WinFormsUtil.GetIndex(CB_RelearnMove1); - pk.RelearnMove2 = WinFormsUtil.GetIndex(CB_RelearnMove2); - pk.RelearnMove3 = WinFormsUtil.GetIndex(CB_RelearnMove3); - pk.RelearnMove4 = WinFormsUtil.GetIndex(CB_RelearnMove4); - } - - private void LoadMisc1(PKM pk) - { - LoadSpeciesLevelEXP(pk); - LoadNickname(pk); - LoadOT(pk); - LoadIVs(pk); - LoadEVs(pk); - LoadMoves(pk); - } - - private void SaveMisc1(PKM pk) - { - SaveSpeciesLevelEXP(pk); - SaveNickname(pk); - SaveOT(pk); - SaveMoves(pk); - } - - private void LoadMisc2(PKM pk) - { - LoadPKRS(pk); - CHK_IsEgg.Checked = pk.IsEgg; - CB_HeldItem.SelectedValue = pk.HeldItem; - LoadClamp(CB_Form, pk.Form); - if (pk is IFormArgument f) - FA_Form.LoadArgument(f, pk.Species, pk.Form, pk.Format); - - ReloadToFriendshipTextBox(pk); - - Label_HatchCounter.Visible = CHK_IsEgg.Checked; - Label_Friendship.Visible = !CHK_IsEgg.Checked; - } - - private void ReloadToFriendshipTextBox(PKM pk) - { - // Show OT friendship always if it is an egg. - var fs = (pk.IsEgg ? pk.OT_Friendship : pk.CurrentFriendship); - TB_Friendship.Text = fs.ToString(); - } - - private void SaveMisc2(PKM pk) - { - SavePKRS(pk); - pk.IsEgg = CHK_IsEgg.Checked; - pk.HeldItem = WinFormsUtil.GetIndex(CB_HeldItem); - pk.Form = CB_Form.Enabled ? CB_Form.SelectedIndex & 0x1F : 0; - if (Entity is IFormArgument f) - FA_Form.SaveArgument(f); - - var friendship = Util.ToInt32(TB_Friendship.Text); - UpdateFromFriendshipTextBox(pk, friendship); - } - - private static void UpdateFromFriendshipTextBox(PKM pk, int friendship) - { - if (pk.IsEgg) - pk.OT_Friendship = friendship; - else - pk.CurrentFriendship = friendship; - } - - private void LoadMisc3(PKM pk) - { - TB_PID.Text = $"{pk.PID:X8}"; - UC_Gender.Gender = pk.Gender; - CB_Nature.SelectedValue = pk.Nature; - CB_Language.SelectedValue = pk.Language; - CB_GameOrigin.SelectedValue = pk.Version; - CB_Ball.SelectedValue = pk.Ball; - CB_MetLocation.SelectedValue = pk.Met_Location; - TB_MetLevel.Text = pk.Met_Level.ToString(); - CHK_Fateful.Checked = pk.FatefulEncounter; - - if (pk is IContestStats s) - s.CopyContestStatsTo(Contest); - - TID_Trainer.LoadIDValues(pk); - - // Load Extrabyte Value - var offset = Convert.ToInt32(CB_ExtraBytes.Text, 16); - var value = pk.Data[offset]; - TB_ExtraByte.Text = value.ToString(); - } - - private void SaveMisc3(PKM pk) - { - pk.PID = Util.GetHexValue(TB_PID.Text); - pk.Nature = WinFormsUtil.GetIndex(CB_Nature); - pk.Gender = UC_Gender.Gender; - - if (pk is IContestStatsMutable s) - Contest.CopyContestStatsTo(s); - - pk.FatefulEncounter = CHK_Fateful.Checked; - pk.Ball = WinFormsUtil.GetIndex(CB_Ball); - pk.Version = WinFormsUtil.GetIndex(CB_GameOrigin); - pk.Language = WinFormsUtil.GetIndex(CB_Language); - pk.Met_Level = Util.ToInt32(TB_MetLevel.Text); - pk.Met_Location = WinFormsUtil.GetIndex(CB_MetLocation); - } - - private void LoadMisc4(PKM pk) - { - CAL_MetDate.Value = pk.MetDate ?? new DateTime(2000, 1, 1); - if (!Legal.IsMetAsEgg(pk)) - { - CHK_AsEgg.Checked = GB_EggConditions.Enabled = false; - CAL_EggDate.Value = new DateTime(2000, 01, 01); - } - else - { - // Was obtained initially as an egg. - CHK_AsEgg.Checked = GB_EggConditions.Enabled = true; - CAL_EggDate.Value = pk.EggMetDate ?? new DateTime(2000, 1, 1); - } - CB_EggLocation.SelectedValue = pk.Egg_Location; - } - - private void SaveMisc4(PKM pk) - { - if (CHK_AsEgg.Checked) // If encountered as an egg, load the Egg Met data from fields. - { - pk.EggMetDate = CAL_EggDate.Value; - pk.Egg_Location = WinFormsUtil.GetIndex(CB_EggLocation); - } - else // Default Dates - { - pk.EggMetDate = null; // clear - pk.Egg_Location = LocationEdits.GetNoneLocation(pk); - } - - // Met Data - if (pk.IsEgg && pk.Met_Location == LocationEdits.GetNoneLocation(pk)) // If still an egg, it has no hatch location/date. Zero it! - pk.MetDate = null; // clear - else - pk.MetDate = CAL_MetDate.Value; - - pk.Ability = WinFormsUtil.GetIndex(HaX ? DEV_Ability : CB_Ability); - } - - private void LoadMisc6(PKM pk) - { - TB_EC.Text = $"{pk.EncryptionConstant:X8}"; - DEV_Ability.SelectedValue = pk.Ability; - - // with some simple error handling - var bitNumber = pk.AbilityNumber; - int abilityIndex = AbilityVerifier.IsValidAbilityBits(bitNumber) ? bitNumber >> 1 : 0; - LoadClamp(CB_Ability, abilityIndex); - TB_AbilityNumber.Text = bitNumber.ToString(); - - LoadRelearnMoves(pk); - LoadHandlingTrainer(pk); - - if (pk is IRegionOrigin tr) - LoadGeolocation(tr); - } - - private void SaveMisc6(PKM pk) - { - pk.EncryptionConstant = Util.GetHexValue(TB_EC.Text); - if (PIDVerifier.GetTransferEC(pk, out var ec)) - pk.EncryptionConstant = ec; - - pk.AbilityNumber = Util.ToInt32(TB_AbilityNumber.Text); - - SaveRelearnMoves(pk); - SaveHandlingTrainer(pk); - - if (pk is IRegionOrigin tr) - SaveGeolocation(tr); - } - - private void LoadGeolocation(IRegionOrigin pk) - { - CB_Country.SelectedValue = (int)pk.Country; - CB_SubRegion.SelectedValue = (int)pk.Region; - CB_3DSReg.SelectedValue = (int)pk.ConsoleRegion; - } - - private void SaveGeolocation(IRegionOrigin pk) - { - pk.Country = (byte)WinFormsUtil.GetIndex(CB_Country); - pk.Region = (byte)WinFormsUtil.GetIndex(CB_SubRegion); - pk.ConsoleRegion = (byte)WinFormsUtil.GetIndex(CB_3DSReg); - } - - private void LoadHandlingTrainer(PKM pk) - { - var handler = pk.HT_Name; - int gender = pk.HT_Gender & 1; - - TB_HT.Text = handler; - UC_HTGender.Gender = gender; - if (handler.Length == 0) - UC_HTGender.Visible = false; - - // Indicate who is currently in possession of the PKM - UpadteHandlingTrainerBackground(pk.CurrentHandler); - } - - private void UpadteHandlingTrainerBackground(int handler) - { - var activeColor = ImageUtil.ChangeOpacity(SpriteUtil.Spriter.Set, 0.5); - if (handler == 0) // OT - { - GB_OT.BackgroundImage = activeColor; - GB_nOT.BackgroundImage = null; - } - else // Handling Trainer - { - GB_nOT.BackgroundImage = activeColor; - GB_OT.BackgroundImage = null; - } - } - - private void SaveHandlingTrainer(PKM pk) - { - pk.HT_Name = TB_HT.Text; - pk.HT_Gender = UC_HTGender.Gender; - } - - private void LoadAbility4(PKM pk) - { - var index = GetAbilityIndex4(pk); - LoadClamp(CB_Ability, index); - } - - private static int GetAbilityIndex4(PKM pk) - { - var pi = pk.PersonalInfo; - int abilityIndex = pi.GetAbilityIndex(pk.Ability); - if (abilityIndex < 0) - return 0; - if (abilityIndex >= 2) - return 2; - - var abils = pi.Abilities; - if (abils[0] == abils[1]) - return pk.PIDAbility; - return abilityIndex; - } - - private void LoadMisc8(PK8 pk8) - { - CB_StatNature.SelectedValue = pk8.StatNature; - LoadClamp(Stats.CB_DynamaxLevel, pk8.DynamaxLevel); - Stats.CHK_Gigantamax.Checked = pk8.CanGigantamax; - CB_HTLanguage.SelectedValue = (int)pk8.HT_Language; - TB_HomeTracker.Text = pk8.Tracker.ToString("X16"); - CB_BattleVersion.SelectedValue = (int)pk8.BattleVersion; - } - - private void SaveMisc8(PK8 pk8) - { - pk8.StatNature = WinFormsUtil.GetIndex(CB_StatNature); - pk8.DynamaxLevel = (byte)Math.Max(0, Stats.CB_DynamaxLevel.SelectedIndex); - pk8.CanGigantamax = Stats.CHK_Gigantamax.Checked; - pk8.HT_Language = (byte)WinFormsUtil.GetIndex(CB_HTLanguage); - pk8.BattleVersion = (byte)WinFormsUtil.GetIndex(CB_BattleVersion); - } - - private void LoadMisc8(PB8 pk8) - { - CB_StatNature.SelectedValue = pk8.StatNature; - LoadClamp(Stats.CB_DynamaxLevel, pk8.DynamaxLevel); - Stats.CHK_Gigantamax.Checked = pk8.CanGigantamax; - CB_HTLanguage.SelectedValue = (int)pk8.HT_Language; - TB_HomeTracker.Text = pk8.Tracker.ToString("X16"); - CB_BattleVersion.SelectedValue = (int)pk8.BattleVersion; - } - - private void SaveMisc8(PB8 pk8) - { - pk8.StatNature = WinFormsUtil.GetIndex(CB_StatNature); - pk8.DynamaxLevel = (byte)Math.Max(0, Stats.CB_DynamaxLevel.SelectedIndex); - pk8.CanGigantamax = Stats.CHK_Gigantamax.Checked; - pk8.HT_Language = (byte)WinFormsUtil.GetIndex(CB_HTLanguage); - pk8.BattleVersion = (byte)WinFormsUtil.GetIndex(CB_BattleVersion); - } - - private void LoadMisc8(PA8 pk8) - { - CB_StatNature.SelectedValue = pk8.StatNature; - LoadClamp(Stats.CB_DynamaxLevel, pk8.DynamaxLevel); - Stats.CHK_Gigantamax.Checked = pk8.CanGigantamax; - CB_HTLanguage.SelectedValue = (int)pk8.HT_Language; - TB_HomeTracker.Text = pk8.Tracker.ToString("X16"); - CB_BattleVersion.SelectedValue = (int)pk8.BattleVersion; - Stats.CHK_IsAlpha.Checked = pk8.IsAlpha; - Stats.CHK_IsNoble.Checked = pk8.IsNoble; - CB_AlphaMastered.SelectedValue = (int)pk8.AlphaMove; - } - - private void SaveMisc8(PA8 pk8) - { - pk8.StatNature = WinFormsUtil.GetIndex(CB_StatNature); - pk8.DynamaxLevel = (byte)Math.Max(0, Stats.CB_DynamaxLevel.SelectedIndex); - pk8.CanGigantamax = Stats.CHK_Gigantamax.Checked; - pk8.HT_Language = (byte)WinFormsUtil.GetIndex(CB_HTLanguage); - pk8.BattleVersion = (byte)WinFormsUtil.GetIndex(CB_BattleVersion); - pk8.IsAlpha = Stats.CHK_IsAlpha.Checked; - pk8.IsNoble = Stats.CHK_IsNoble.Checked; - pk8.AlphaMove = (ushort)WinFormsUtil.GetIndex(CB_AlphaMastered); + NUD_Purification.Value = 0; + CHK_Shadow.Checked = false; + NUD_ShadowID.Value = 0; } } + + private void SaveShadow3(IShadowPKM pk) + { + pk.ShadowID = (ushort)NUD_ShadowID.Value; + if (pk.ShadowID > 0) + pk.Purification = (int)NUD_Purification.Value; + } + + private void LoadRelearnMoves(PKM pk) + { + CB_RelearnMove1.SelectedValue = pk.RelearnMove1; + CB_RelearnMove2.SelectedValue = pk.RelearnMove2; + CB_RelearnMove3.SelectedValue = pk.RelearnMove3; + CB_RelearnMove4.SelectedValue = pk.RelearnMove4; + } + + private void SaveRelearnMoves(PKM pk) + { + pk.RelearnMove1 = WinFormsUtil.GetIndex(CB_RelearnMove1); + pk.RelearnMove2 = WinFormsUtil.GetIndex(CB_RelearnMove2); + pk.RelearnMove3 = WinFormsUtil.GetIndex(CB_RelearnMove3); + pk.RelearnMove4 = WinFormsUtil.GetIndex(CB_RelearnMove4); + } + + private void LoadMisc1(PKM pk) + { + LoadSpeciesLevelEXP(pk); + LoadNickname(pk); + LoadOT(pk); + LoadIVs(pk); + LoadEVs(pk); + LoadMoves(pk); + } + + private void SaveMisc1(PKM pk) + { + SaveSpeciesLevelEXP(pk); + SaveNickname(pk); + SaveOT(pk); + SaveMoves(pk); + } + + private void LoadMisc2(PKM pk) + { + LoadPKRS(pk); + CHK_IsEgg.Checked = pk.IsEgg; + CB_HeldItem.SelectedValue = pk.HeldItem; + LoadClamp(CB_Form, pk.Form); + if (pk is IFormArgument f) + FA_Form.LoadArgument(f, pk.Species, pk.Form, pk.Format); + + ReloadToFriendshipTextBox(pk); + + Label_HatchCounter.Visible = CHK_IsEgg.Checked; + Label_Friendship.Visible = !CHK_IsEgg.Checked; + } + + private void ReloadToFriendshipTextBox(PKM pk) + { + // Show OT friendship always if it is an egg. + var fs = (pk.IsEgg ? pk.OT_Friendship : pk.CurrentFriendship); + TB_Friendship.Text = fs.ToString(); + } + + private void SaveMisc2(PKM pk) + { + SavePKRS(pk); + pk.IsEgg = CHK_IsEgg.Checked; + pk.HeldItem = WinFormsUtil.GetIndex(CB_HeldItem); + pk.Form = CB_Form.Enabled ? CB_Form.SelectedIndex & 0x1F : 0; + if (Entity is IFormArgument f) + FA_Form.SaveArgument(f); + + var friendship = Util.ToInt32(TB_Friendship.Text); + UpdateFromFriendshipTextBox(pk, friendship); + } + + private static void UpdateFromFriendshipTextBox(PKM pk, int friendship) + { + if (pk.IsEgg) + pk.OT_Friendship = friendship; + else + pk.CurrentFriendship = friendship; + } + + private void LoadMisc3(PKM pk) + { + TB_PID.Text = $"{pk.PID:X8}"; + UC_Gender.Gender = pk.Gender; + CB_Nature.SelectedValue = pk.Nature; + CB_Language.SelectedValue = pk.Language; + CB_GameOrigin.SelectedValue = pk.Version; + CB_Ball.SelectedValue = pk.Ball; + CB_MetLocation.SelectedValue = pk.Met_Location; + TB_MetLevel.Text = pk.Met_Level.ToString(); + CHK_Fateful.Checked = pk.FatefulEncounter; + + if (pk is IContestStats s) + s.CopyContestStatsTo(Contest); + + TID_Trainer.LoadIDValues(pk); + + // Load Extrabyte Value + var offset = Convert.ToInt32(CB_ExtraBytes.Text, 16); + var value = pk.Data[offset]; + TB_ExtraByte.Text = value.ToString(); + } + + private void SaveMisc3(PKM pk) + { + pk.PID = Util.GetHexValue(TB_PID.Text); + pk.Nature = WinFormsUtil.GetIndex(CB_Nature); + pk.Gender = UC_Gender.Gender; + + if (pk is IContestStatsMutable s) + Contest.CopyContestStatsTo(s); + + pk.FatefulEncounter = CHK_Fateful.Checked; + pk.Ball = WinFormsUtil.GetIndex(CB_Ball); + pk.Version = WinFormsUtil.GetIndex(CB_GameOrigin); + pk.Language = WinFormsUtil.GetIndex(CB_Language); + pk.Met_Level = Util.ToInt32(TB_MetLevel.Text); + pk.Met_Location = WinFormsUtil.GetIndex(CB_MetLocation); + } + + private void LoadMisc4(PKM pk) + { + CAL_MetDate.Value = pk.MetDate ?? new DateTime(2000, 1, 1); + if (!Legal.IsMetAsEgg(pk)) + { + CHK_AsEgg.Checked = GB_EggConditions.Enabled = false; + CAL_EggDate.Value = new DateTime(2000, 01, 01); + } + else + { + // Was obtained initially as an egg. + CHK_AsEgg.Checked = GB_EggConditions.Enabled = true; + CAL_EggDate.Value = pk.EggMetDate ?? new DateTime(2000, 1, 1); + } + CB_EggLocation.SelectedValue = pk.Egg_Location; + } + + private void SaveMisc4(PKM pk) + { + if (CHK_AsEgg.Checked) // If encountered as an egg, load the Egg Met data from fields. + { + pk.EggMetDate = CAL_EggDate.Value; + pk.Egg_Location = WinFormsUtil.GetIndex(CB_EggLocation); + } + else // Default Dates + { + pk.EggMetDate = null; // clear + pk.Egg_Location = LocationEdits.GetNoneLocation(pk); + } + + // Met Data + if (pk.IsEgg && pk.Met_Location == LocationEdits.GetNoneLocation(pk)) // If still an egg, it has no hatch location/date. Zero it! + pk.MetDate = null; // clear + else + pk.MetDate = CAL_MetDate.Value; + + pk.Ability = WinFormsUtil.GetIndex(HaX ? DEV_Ability : CB_Ability); + } + + private void LoadMisc6(PKM pk) + { + TB_EC.Text = $"{pk.EncryptionConstant:X8}"; + DEV_Ability.SelectedValue = pk.Ability; + + // with some simple error handling + var bitNumber = pk.AbilityNumber; + int abilityIndex = AbilityVerifier.IsValidAbilityBits(bitNumber) ? bitNumber >> 1 : 0; + LoadClamp(CB_Ability, abilityIndex); + TB_AbilityNumber.Text = bitNumber.ToString(); + + LoadRelearnMoves(pk); + LoadHandlingTrainer(pk); + + if (pk is IRegionOrigin tr) + LoadGeolocation(tr); + } + + private void SaveMisc6(PKM pk) + { + pk.EncryptionConstant = Util.GetHexValue(TB_EC.Text); + if (PIDVerifier.GetTransferEC(pk, out var ec)) + pk.EncryptionConstant = ec; + + pk.AbilityNumber = Util.ToInt32(TB_AbilityNumber.Text); + + SaveRelearnMoves(pk); + SaveHandlingTrainer(pk); + + if (pk is IRegionOrigin tr) + SaveGeolocation(tr); + } + + private void LoadGeolocation(IRegionOrigin pk) + { + CB_Country.SelectedValue = (int)pk.Country; + CB_SubRegion.SelectedValue = (int)pk.Region; + CB_3DSReg.SelectedValue = (int)pk.ConsoleRegion; + } + + private void SaveGeolocation(IRegionOrigin pk) + { + pk.Country = (byte)WinFormsUtil.GetIndex(CB_Country); + pk.Region = (byte)WinFormsUtil.GetIndex(CB_SubRegion); + pk.ConsoleRegion = (byte)WinFormsUtil.GetIndex(CB_3DSReg); + } + + private void LoadHandlingTrainer(PKM pk) + { + var handler = pk.HT_Name; + int gender = pk.HT_Gender & 1; + + TB_HT.Text = handler; + UC_HTGender.Gender = gender; + if (handler.Length == 0) + UC_HTGender.Visible = false; + + // Indicate who is currently in possession of the PKM + UpadteHandlingTrainerBackground(pk.CurrentHandler); + } + + private void UpadteHandlingTrainerBackground(int handler) + { + var activeColor = ImageUtil.ChangeOpacity(SpriteUtil.Spriter.Set, 0.5); + if (handler == 0) // OT + { + GB_OT.BackgroundImage = activeColor; + GB_nOT.BackgroundImage = null; + } + else // Handling Trainer + { + GB_nOT.BackgroundImage = activeColor; + GB_OT.BackgroundImage = null; + } + } + + private void SaveHandlingTrainer(PKM pk) + { + pk.HT_Name = TB_HT.Text; + pk.HT_Gender = UC_HTGender.Gender; + } + + private void LoadAbility4(PKM pk) + { + var index = GetAbilityIndex4(pk); + LoadClamp(CB_Ability, index); + } + + private static int GetAbilityIndex4(PKM pk) + { + var pi = pk.PersonalInfo; + int abilityIndex = pi.GetAbilityIndex(pk.Ability); + if (abilityIndex < 0) + return 0; + if (abilityIndex >= 2) + return 2; + + var abils = pi.Abilities; + if (abils[0] == abils[1]) + return pk.PIDAbility; + return abilityIndex; + } + + private void LoadMisc8(PK8 pk8) + { + CB_StatNature.SelectedValue = pk8.StatNature; + LoadClamp(Stats.CB_DynamaxLevel, pk8.DynamaxLevel); + Stats.CHK_Gigantamax.Checked = pk8.CanGigantamax; + CB_HTLanguage.SelectedValue = (int)pk8.HT_Language; + TB_HomeTracker.Text = pk8.Tracker.ToString("X16"); + CB_BattleVersion.SelectedValue = (int)pk8.BattleVersion; + } + + private void SaveMisc8(PK8 pk8) + { + pk8.StatNature = WinFormsUtil.GetIndex(CB_StatNature); + pk8.DynamaxLevel = (byte)Math.Max(0, Stats.CB_DynamaxLevel.SelectedIndex); + pk8.CanGigantamax = Stats.CHK_Gigantamax.Checked; + pk8.HT_Language = (byte)WinFormsUtil.GetIndex(CB_HTLanguage); + pk8.BattleVersion = (byte)WinFormsUtil.GetIndex(CB_BattleVersion); + } + + private void LoadMisc8(PB8 pk8) + { + CB_StatNature.SelectedValue = pk8.StatNature; + LoadClamp(Stats.CB_DynamaxLevel, pk8.DynamaxLevel); + Stats.CHK_Gigantamax.Checked = pk8.CanGigantamax; + CB_HTLanguage.SelectedValue = (int)pk8.HT_Language; + TB_HomeTracker.Text = pk8.Tracker.ToString("X16"); + CB_BattleVersion.SelectedValue = (int)pk8.BattleVersion; + } + + private void SaveMisc8(PB8 pk8) + { + pk8.StatNature = WinFormsUtil.GetIndex(CB_StatNature); + pk8.DynamaxLevel = (byte)Math.Max(0, Stats.CB_DynamaxLevel.SelectedIndex); + pk8.CanGigantamax = Stats.CHK_Gigantamax.Checked; + pk8.HT_Language = (byte)WinFormsUtil.GetIndex(CB_HTLanguage); + pk8.BattleVersion = (byte)WinFormsUtil.GetIndex(CB_BattleVersion); + } + + private void LoadMisc8(PA8 pk8) + { + CB_StatNature.SelectedValue = pk8.StatNature; + LoadClamp(Stats.CB_DynamaxLevel, pk8.DynamaxLevel); + Stats.CHK_Gigantamax.Checked = pk8.CanGigantamax; + CB_HTLanguage.SelectedValue = (int)pk8.HT_Language; + TB_HomeTracker.Text = pk8.Tracker.ToString("X16"); + CB_BattleVersion.SelectedValue = (int)pk8.BattleVersion; + Stats.CHK_IsAlpha.Checked = pk8.IsAlpha; + Stats.CHK_IsNoble.Checked = pk8.IsNoble; + CB_AlphaMastered.SelectedValue = (int)pk8.AlphaMove; + } + + private void SaveMisc8(PA8 pk8) + { + pk8.StatNature = WinFormsUtil.GetIndex(CB_StatNature); + pk8.DynamaxLevel = (byte)Math.Max(0, Stats.CB_DynamaxLevel.SelectedIndex); + pk8.CanGigantamax = Stats.CHK_Gigantamax.Checked; + pk8.HT_Language = (byte)WinFormsUtil.GetIndex(CB_HTLanguage); + pk8.BattleVersion = (byte)WinFormsUtil.GetIndex(CB_BattleVersion); + pk8.IsAlpha = Stats.CHK_IsAlpha.Checked; + pk8.IsNoble = Stats.CHK_IsNoble.Checked; + pk8.AlphaMove = (ushort)WinFormsUtil.GetIndex(CB_AlphaMastered); + } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs index 11777c11c..88a13ae72 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -11,2050 +11,2049 @@ using PKHeX.Drawing.PokeSprite.Properties; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public sealed partial class PKMEditor : UserControl, IMainEditor { - public sealed partial class PKMEditor : UserControl, IMainEditor + public bool IsInitialized { get; private set; } + + public PKMEditor() { - public bool IsInitialized { get; private set; } + InitializeComponent(); - public PKMEditor() + // Groupbox doesn't show Click event in Designer... + GB_OT.Click += ClickGT; + GB_nOT.Click += ClickGT; + GB_CurrentMoves.Click += ClickMoves; + GB_RelearnMoves.Click += ClickMoves; + + var font = FontUtil.GetPKXFont(); + TB_Nickname.Font = TB_OT.Font = TB_HT.Font = font; + + // Commonly reused Control arrays + Moves = new[] { CB_Move1, CB_Move2, CB_Move3, CB_Move4 }; + Relearn = new[] { CB_RelearnMove1, CB_RelearnMove2, CB_RelearnMove3, CB_RelearnMove4 }; + PPUps = new[] { CB_PPu1, CB_PPu2, CB_PPu3, CB_PPu4 }; + MovePP = new[] { TB_PP1, TB_PP2, TB_PP3, TB_PP4 }; + Markings = new[] { PB_Mark1, PB_Mark2, PB_Mark3, PB_Mark4, PB_Mark5, PB_Mark6 }; + + // Legality Indicators + relearnPB = new[] { PB_WarnRelearn1, PB_WarnRelearn2, PB_WarnRelearn3, PB_WarnRelearn4 }; + movePB = new[] { PB_WarnMove1, PB_WarnMove2, PB_WarnMove3, PB_WarnMove4 }; + + // Validation of incompletely entered data fields + bool Criteria(Control c) => c.BackColor == Draw.InvalidSelection && c is ComboBox { Items.Count: not 0 }; + ValidatedControls = new ValidationRequiredSet[] { - InitializeComponent(); - - // Groupbox doesn't show Click event in Designer... - GB_OT.Click += ClickGT; - GB_nOT.Click += ClickGT; - GB_CurrentMoves.Click += ClickMoves; - GB_RelearnMoves.Click += ClickMoves; - - var font = FontUtil.GetPKXFont(); - TB_Nickname.Font = TB_OT.Font = TB_HT.Font = font; - - // Commonly reused Control arrays - Moves = new[] { CB_Move1, CB_Move2, CB_Move3, CB_Move4 }; - Relearn = new[] { CB_RelearnMove1, CB_RelearnMove2, CB_RelearnMove3, CB_RelearnMove4 }; - PPUps = new[] { CB_PPu1, CB_PPu2, CB_PPu3, CB_PPu4 }; - MovePP = new[] { TB_PP1, TB_PP2, TB_PP3, TB_PP4 }; - Markings = new[] { PB_Mark1, PB_Mark2, PB_Mark3, PB_Mark4, PB_Mark5, PB_Mark6 }; - - // Legality Indicators - relearnPB = new[] { PB_WarnRelearn1, PB_WarnRelearn2, PB_WarnRelearn3, PB_WarnRelearn4 }; - movePB = new[] { PB_WarnMove1, PB_WarnMove2, PB_WarnMove3, PB_WarnMove4 }; - - // Validation of incompletely entered data fields - bool Criteria(Control c) => c.BackColor == Draw.InvalidSelection && c is ComboBox { Items.Count: not 0 }; - ValidatedControls = new ValidationRequiredSet[] - { - new(Moves, _ => true, Criteria), - new(new[] {CB_Species}, _ => true, Criteria), - new(new[] {CB_HeldItem}, pk => pk.Format >= 2, Criteria), - new(new[] {CB_Ability, CB_Nature, CB_MetLocation, CB_Ball}, pk => pk.Format >= 3, Criteria), - new(new[] {CB_EggLocation}, pk => pk.Format >= 4, Criteria), - new(new[] {CB_Country, CB_SubRegion}, pk => pk is PK6 or PK7, Criteria), - new(Relearn, pk => pk.Format >= 6, Criteria), - new(new[] {CB_StatNature}, pk => pk.Format >= 8, Criteria), - new(new[] {CB_AlphaMastered}, pk => pk is PA8, Criteria), - }; - - foreach (var c in WinFormsUtil.GetAllControlsOfType(this)) - c.KeyDown += WinFormsUtil.RemoveDropCB; - - Stats.MainEditor = this; - LoadShowdownSet = LoadShowdownSetDefault; - TID_Trainer.UpdatedID += Update_ID; - - // Controls contained in a TabPage are not created until the tab page is shown - // Any data bindings in these controls are not activated until the tab page is shown. - FlickerInterface(); - } - - private sealed class ValidationRequiredSet - { - private readonly Control[] Controls; - private readonly Func ShouldCheck; - private readonly Func IsInvalidState; - - public Control? IsNotValid(PKM pk) - { - if (!ShouldCheck(pk)) - return null; - return Array.Find(Controls, z => IsInvalidState(z)); - } - - public ValidationRequiredSet(Control[] controls, Func shouldCheck, Func state) - { - Controls = controls; - ShouldCheck = shouldCheck; - IsInvalidState = state; - } - } - - public void InitializeBinding() - { - ComboBox[] cbs = - { - CB_Nature, CB_StatNature, - CB_Country, CB_SubRegion, CB_3DSReg, CB_Language, CB_Ball, CB_HeldItem, CB_Species, DEV_Ability, - CB_GroundTile, CB_GameOrigin, CB_BattleVersion, CB_Ability, CB_MetLocation, CB_EggLocation, CB_Language, CB_HTLanguage, - CB_AlphaMastered, - }; - foreach (var cb in cbs.Concat(Moves.Concat(Relearn))) - cb.InitializeBinding(); - - IsInitialized = true; - } - - private void UpdateStats() - { - Stats.UpdateStats(); - if (Entity is IScaledSizeAbsolute) - SizeCP.TryResetStats(); - } - - private void LoadPartyStats(PKM pk) => Stats.LoadPartyStats(pk); - - private void SavePartyStats(PKM pk) => Stats.SavePartyStats(pk); - - public PKM CurrentPKM { get => PreparePKM(); set => Entity = value; } - public bool ModifyPKM { private get; set; } = true; - private bool _hideSecret; - - public bool HideSecretValues - { - private get => _hideSecret; - set - { - _hideSecret = value; - var sav = RequestSaveFile; - ToggleSecrets(_hideSecret, sav.Generation); - } - } - - public DrawConfig Draw { private get; set; } = null!; - public bool Unicode { get; set; } = true; - private bool _hax; - public bool HaX { get => _hax; set => _hax = Stats.HaX = value; } - private byte[] LastData = Array.Empty(); - - public PKM Data { get => Entity; set => Entity = value; } - public PKM Entity { get; private set; } = null!; - public bool FieldsLoaded { get; private set; } - public bool ChangingFields { get; set; } - - /// - /// Currently loaded met location group that is populating Met and Egg location comboboxes - /// - private GameVersion origintrack; - - private EntityContext originFormat = EntityContext.None; - - /// - /// Action to perform when loading a PKM to the editor GUI. - /// - private Action GetFieldsfromPKM = null!; - - /// - /// Function that returns a from the loaded fields. - /// - private Func GetPKMfromFields = null!; - - /// - /// Latest legality check result used to show legality indication. - /// - private LegalityAnalysis Legality = null!; - - /// - /// List of legal moves for the latest . - /// - private readonly LegalMoveSource LegalMoveSource = new(new LegalMoveComboSource()); - - /// - /// Gender Symbols for showing Genders - /// - private IReadOnlyList gendersymbols = GameInfo.GenderSymbolUnicode; - - public event EventHandler? LegalityChanged; - public event EventHandler? UpdatePreviewSprite; - public event EventHandler? RequestShowdownImport; - public event EventHandler? RequestShowdownExport; - public event ReturnSAVEventHandler SaveFileRequested = null!; - public delegate SaveFile ReturnSAVEventHandler(object sender, EventArgs e); - - private readonly PictureBox[] movePB, relearnPB; - public SaveFile RequestSaveFile => SaveFileRequested.Invoke(this, EventArgs.Empty); - public bool PKMIsUnsaved => FieldsLoaded && LastData.Any(b => b != 0) && !LastData.SequenceEqual(CurrentPKM.Data); - - private readonly ComboBox[] Moves, Relearn, PPUps; - private readonly ValidationRequiredSet[] ValidatedControls; - private readonly MaskedTextBox[] MovePP; - private readonly PictureBox[] Markings; - - private bool forceValidation; - - public PKM PreparePKM(bool click = true) - { - if (click) - { - forceValidation = true; - ValidateChildren(); - forceValidation = false; - } - - var pk = GetPKMfromFields(); - LastData = pk.Data; - return pk.Clone(); - } - - public bool EditsComplete - { - get - { - if (ModifierKeys == (Keys.Control | Keys.Shift | Keys.Alt)) - return true; // Override - - // If any controls are partially filled out, find the first one so we can indicate as such. - Control? cb = null; - foreach (var type in ValidatedControls) - { - cb = type.IsNotValid(Entity); - if (cb is not null) - break; - } - - if (cb != null) - tabMain.SelectedTab = WinFormsUtil.FindFirstControlOfType(cb); - else if (!Stats.Valid) - tabMain.SelectedTab = Tab_Stats; - else if (WinFormsUtil.GetIndex(CB_Species) == 0 && !HaX) // can't set an empty slot... - tabMain.SelectedTab = Tab_Main; - else - return true; - - System.Media.SystemSounds.Exclamation.Play(); - return false; - } - } - - public void SetPKMFormatMode(PKM pk) - { - // Load Extra Byte List - SetPKMFormatExtraBytes(pk); - (GetFieldsfromPKM, GetPKMfromFields) = GetLoadSet(pk); - } - - private (Action, Func) GetLoadSet(PKM pk) => pk.Format switch - { - 1 => (PopulateFieldsPK1, PreparePK1), - 2 => (PopulateFieldsPK2, PreparePK2), - 3 => (PopulateFieldsPK3, PreparePK3), - 4 => (PopulateFieldsPK4, PreparePK4), - 5 => (PopulateFieldsPK5, PreparePK5), - 6 => (PopulateFieldsPK6, PreparePK6), - 7 when pk is PK7 => (PopulateFieldsPK7, PreparePK7), - 7 when pk is PB7 => (PopulateFieldsPB7, PreparePB7), - 8 when pk is PK8 => (PopulateFieldsPK8, PreparePK8), - 8 when pk is PA8 => (PopulateFieldsPA8, PreparePA8), - 8 when pk is PB8 => (PopulateFieldsPB8, PreparePB8), - _ => throw new FormatException($"Unrecognized Type: {pk.GetType()}"), + new(Moves, _ => true, Criteria), + new(new[] {CB_Species}, _ => true, Criteria), + new(new[] {CB_HeldItem}, pk => pk.Format >= 2, Criteria), + new(new[] {CB_Ability, CB_Nature, CB_MetLocation, CB_Ball}, pk => pk.Format >= 3, Criteria), + new(new[] {CB_EggLocation}, pk => pk.Format >= 4, Criteria), + new(new[] {CB_Country, CB_SubRegion}, pk => pk is PK6 or PK7, Criteria), + new(Relearn, pk => pk.Format >= 6, Criteria), + new(new[] {CB_StatNature}, pk => pk.Format >= 8, Criteria), + new(new[] {CB_AlphaMastered}, pk => pk is PA8, Criteria), }; - private void SetPKMFormatExtraBytes(PKM pk) + foreach (var c in WinFormsUtil.GetAllControlsOfType(this)) + c.KeyDown += WinFormsUtil.RemoveDropCB; + + Stats.MainEditor = this; + LoadShowdownSet = LoadShowdownSetDefault; + TID_Trainer.UpdatedID += Update_ID; + + // Controls contained in a TabPage are not created until the tab page is shown + // Any data bindings in these controls are not activated until the tab page is shown. + FlickerInterface(); + } + + private sealed class ValidationRequiredSet + { + private readonly Control[] Controls; + private readonly Func ShouldCheck; + private readonly Func IsInvalidState; + + public Control? IsNotValid(PKM pk) { - var extraBytes = pk.ExtraBytes; - GB_ExtraBytes.Visible = GB_ExtraBytes.Enabled = extraBytes.Count != 0; - CB_ExtraBytes.Items.Clear(); - foreach (var b in extraBytes) - CB_ExtraBytes.Items.Add($"0x{b:X2}"); - if (GB_ExtraBytes.Enabled) - CB_ExtraBytes.SelectedIndex = 0; + if (!ShouldCheck(pk)) + return null; + return Array.Find(Controls, z => IsInvalidState(z)); } - public void PopulateFields(PKM pk, bool focus = true, bool skipConversionCheck = false) => LoadFieldsFromPKM(pk, focus, skipConversionCheck); - - private void LoadFieldsFromPKM(PKM pk, bool focus = true, bool skipConversionCheck = true) + public ValidationRequiredSet(Control[] controls, Func shouldCheck, Func state) { - if (focus) - Tab_Main.Focus(); + Controls = controls; + ShouldCheck = shouldCheck; + IsInvalidState = state; + } + } - if (!skipConversionCheck && !EntityConverter.TryMakePKMCompatible(pk, Entity, out var c, out pk)) + public void InitializeBinding() + { + ComboBox[] cbs = + { + CB_Nature, CB_StatNature, + CB_Country, CB_SubRegion, CB_3DSReg, CB_Language, CB_Ball, CB_HeldItem, CB_Species, DEV_Ability, + CB_GroundTile, CB_GameOrigin, CB_BattleVersion, CB_Ability, CB_MetLocation, CB_EggLocation, CB_Language, CB_HTLanguage, + CB_AlphaMastered, + }; + foreach (var cb in cbs.Concat(Moves.Concat(Relearn))) + cb.InitializeBinding(); + + IsInitialized = true; + } + + private void UpdateStats() + { + Stats.UpdateStats(); + if (Entity is IScaledSizeAbsolute) + SizeCP.TryResetStats(); + } + + private void LoadPartyStats(PKM pk) => Stats.LoadPartyStats(pk); + + private void SavePartyStats(PKM pk) => Stats.SavePartyStats(pk); + + public PKM CurrentPKM { get => PreparePKM(); set => Entity = value; } + public bool ModifyPKM { private get; set; } = true; + private bool _hideSecret; + + public bool HideSecretValues + { + private get => _hideSecret; + set + { + _hideSecret = value; + var sav = RequestSaveFile; + ToggleSecrets(_hideSecret, sav.Generation); + } + } + + public DrawConfig Draw { private get; set; } = null!; + public bool Unicode { get; set; } = true; + private bool _hax; + public bool HaX { get => _hax; set => _hax = Stats.HaX = value; } + private byte[] LastData = Array.Empty(); + + public PKM Data { get => Entity; set => Entity = value; } + public PKM Entity { get; private set; } = null!; + public bool FieldsLoaded { get; private set; } + public bool ChangingFields { get; set; } + + /// + /// Currently loaded met location group that is populating Met and Egg location comboboxes + /// + private GameVersion origintrack; + + private EntityContext originFormat = EntityContext.None; + + /// + /// Action to perform when loading a PKM to the editor GUI. + /// + private Action GetFieldsfromPKM = null!; + + /// + /// Function that returns a from the loaded fields. + /// + private Func GetPKMfromFields = null!; + + /// + /// Latest legality check result used to show legality indication. + /// + private LegalityAnalysis Legality = null!; + + /// + /// List of legal moves for the latest . + /// + private readonly LegalMoveSource LegalMoveSource = new(new LegalMoveComboSource()); + + /// + /// Gender Symbols for showing Genders + /// + private IReadOnlyList gendersymbols = GameInfo.GenderSymbolUnicode; + + public event EventHandler? LegalityChanged; + public event EventHandler? UpdatePreviewSprite; + public event EventHandler? RequestShowdownImport; + public event EventHandler? RequestShowdownExport; + public event ReturnSAVEventHandler SaveFileRequested = null!; + public delegate SaveFile ReturnSAVEventHandler(object sender, EventArgs e); + + private readonly PictureBox[] movePB, relearnPB; + public SaveFile RequestSaveFile => SaveFileRequested.Invoke(this, EventArgs.Empty); + public bool PKMIsUnsaved => FieldsLoaded && LastData.Any(b => b != 0) && !LastData.SequenceEqual(CurrentPKM.Data); + + private readonly ComboBox[] Moves, Relearn, PPUps; + private readonly ValidationRequiredSet[] ValidatedControls; + private readonly MaskedTextBox[] MovePP; + private readonly PictureBox[] Markings; + + private bool forceValidation; + + public PKM PreparePKM(bool click = true) + { + if (click) + { + forceValidation = true; + ValidateChildren(); + forceValidation = false; + } + + var pk = GetPKMfromFields(); + LastData = pk.Data; + return pk.Clone(); + } + + public bool EditsComplete + { + get + { + if (ModifierKeys == (Keys.Control | Keys.Shift | Keys.Alt)) + return true; // Override + + // If any controls are partially filled out, find the first one so we can indicate as such. + Control? cb = null; + foreach (var type in ValidatedControls) { - var msg = c.GetDisplayString(pk, Entity.GetType()); - WinFormsUtil.Alert(msg); - return; + cb = type.IsNotValid(Entity); + if (cb is not null) + break; } - FieldsLoaded = false; + if (cb != null) + tabMain.SelectedTab = WinFormsUtil.FindFirstControlOfType(cb); + else if (!Stats.Valid) + tabMain.SelectedTab = Tab_Stats; + else if (WinFormsUtil.GetIndex(CB_Species) == 0 && !HaX) // can't set an empty slot... + tabMain.SelectedTab = Tab_Main; + else + return true; - Entity = pk.Clone(); + System.Media.SystemSounds.Exclamation.Play(); + return false; + } + } + + public void SetPKMFormatMode(PKM pk) + { + // Load Extra Byte List + SetPKMFormatExtraBytes(pk); + (GetFieldsfromPKM, GetPKMfromFields) = GetLoadSet(pk); + } + + private (Action, Func) GetLoadSet(PKM pk) => pk.Format switch + { + 1 => (PopulateFieldsPK1, PreparePK1), + 2 => (PopulateFieldsPK2, PreparePK2), + 3 => (PopulateFieldsPK3, PreparePK3), + 4 => (PopulateFieldsPK4, PreparePK4), + 5 => (PopulateFieldsPK5, PreparePK5), + 6 => (PopulateFieldsPK6, PreparePK6), + 7 when pk is PK7 => (PopulateFieldsPK7, PreparePK7), + 7 when pk is PB7 => (PopulateFieldsPB7, PreparePB7), + 8 when pk is PK8 => (PopulateFieldsPK8, PreparePK8), + 8 when pk is PA8 => (PopulateFieldsPA8, PreparePA8), + 8 when pk is PB8 => (PopulateFieldsPB8, PreparePB8), + _ => throw new FormatException($"Unrecognized Type: {pk.GetType()}"), + }; + + private void SetPKMFormatExtraBytes(PKM pk) + { + var extraBytes = pk.ExtraBytes; + GB_ExtraBytes.Visible = GB_ExtraBytes.Enabled = extraBytes.Count != 0; + CB_ExtraBytes.Items.Clear(); + foreach (var b in extraBytes) + CB_ExtraBytes.Items.Add($"0x{b:X2}"); + if (GB_ExtraBytes.Enabled) + CB_ExtraBytes.SelectedIndex = 0; + } + + public void PopulateFields(PKM pk, bool focus = true, bool skipConversionCheck = false) => LoadFieldsFromPKM(pk, focus, skipConversionCheck); + + private void LoadFieldsFromPKM(PKM pk, bool focus = true, bool skipConversionCheck = true) + { + if (focus) + Tab_Main.Focus(); + + if (!skipConversionCheck && !EntityConverter.TryMakePKMCompatible(pk, Entity, out var c, out pk)) + { + var msg = c.GetDisplayString(pk, Entity.GetType()); + WinFormsUtil.Alert(msg); + return; + } + + FieldsLoaded = false; + + Entity = pk.Clone(); #if !DEBUG try { GetFieldsfromPKM(); } catch { } #else - GetFieldsfromPKM(); + GetFieldsfromPKM(); #endif - Stats.UpdateIVs(this, EventArgs.Empty); - UpdatePKRSInfected(this, EventArgs.Empty); - UpdatePKRSCured(this, EventArgs.Empty); - UpdateNatureModification(CB_StatNature, Entity.StatNature); + Stats.UpdateIVs(this, EventArgs.Empty); + UpdatePKRSInfected(this, EventArgs.Empty); + UpdatePKRSCured(this, EventArgs.Empty); + UpdateNatureModification(CB_StatNature, Entity.StatNature); - if (HaX) - { - if (pk.PartyStatsPresent) // stats present - Stats.LoadPartyStats(pk); - } - FieldsLoaded = true; - - SetMarkings(); - UpdateLegality(); - UpdateSprite(); - LastData = PreparePKM().Data; - } - - public void UpdateLegality(LegalityAnalysis? la = null, bool skipMoveRepop = false) + if (HaX) { - if (!FieldsLoaded) - return; + if (pk.PartyStatsPresent) // stats present + Stats.LoadPartyStats(pk); + } + FieldsLoaded = true; - Legality = la ?? new LegalityAnalysis(Entity, RequestSaveFile.Personal); - if (!Legality.Parsed || HaX || Entity.Species == 0) - { - PB_WarnMove1.Visible = PB_WarnMove2.Visible = PB_WarnMove3.Visible = PB_WarnMove4.Visible = false; - PB_WarnRelearn1.Visible = PB_WarnRelearn2.Visible = PB_WarnRelearn3.Visible = PB_WarnRelearn4.Visible = false; - LegalityChanged?.Invoke(Legality.Valid, EventArgs.Empty); - return; - } + SetMarkings(); + UpdateLegality(); + UpdateSprite(); + LastData = PreparePKM().Data; + } - // Refresh Move Legality - var info = Legality.Info; - var moves = info.Moves; - for (int i = 0; i < 4; i++) - { - var pb = movePB[i]; - pb.Visible = true; - pb.Image = GetMoveImage(!moves[i].Valid, Entity, i); - } + public void UpdateLegality(LegalityAnalysis? la = null, bool skipMoveRepop = false) + { + if (!FieldsLoaded) + return; - if (Entity.Format >= 6) - { - var relearn = info.Relearn; - for (int i = 0; i < 4; i++) - relearnPB[i].Visible = !relearn[i].Valid; - } - - if (skipMoveRepop) - return; - // Resort moves - FieldsLoaded = false; - LegalMoveSource.ReloadMoves(Legality.GetSuggestedMovesAndRelearn()); - FieldsLoaded = true; + Legality = la ?? new LegalityAnalysis(Entity, RequestSaveFile.Personal); + if (!Legality.Parsed || HaX || Entity.Species == 0) + { + PB_WarnMove1.Visible = PB_WarnMove2.Visible = PB_WarnMove3.Visible = PB_WarnMove4.Visible = false; + PB_WarnRelearn1.Visible = PB_WarnRelearn2.Visible = PB_WarnRelearn3.Visible = PB_WarnRelearn4.Visible = false; LegalityChanged?.Invoke(Legality.Valid, EventArgs.Empty); + return; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Bitmap? GetMoveImage(bool isIllegal, PKM pkm, int index) + // Refresh Move Legality + var info = Legality.Info; + var moves = info.Moves; + for (int i = 0; i < 4; i++) { - if (isIllegal) - return Resources.warn; - if (pkm.Format >= 8 && MoveInfo.GetDummiedMovesHashSet(pkm.Context).Contains(pkm.GetMove(index))) - return Resources.hint; - return null; + var pb = movePB[i]; + pb.Visible = true; + pb.Image = GetMoveImage(!moves[i].Valid, Entity, i); } - public void UpdateUnicode(IReadOnlyList symbols) + if (Entity.Format >= 6) { - gendersymbols = symbols; - if (!Unicode) - { - BTN_Shinytize.Text = Draw.ShinyDefault; - TB_Nickname.Font = TB_OT.Font = TB_HT.Font = GB_OT.Font; - } - else - { - BTN_Shinytize.Text = Draw.ShinyUnicode; - TB_Nickname.Font = TB_OT.Font = TB_HT.Font = FontUtil.GetPKXFont(); - } + var relearn = info.Relearn; + for (int i = 0; i < 4; i++) + relearnPB[i].Visible = !relearn[i].Valid; } - internal void UpdateSprite() - { - if (FieldsLoaded && !forceValidation) - UpdatePreviewSprite?.Invoke(this, EventArgs.Empty); - } - - // General Use Functions // - private void SetDetailsOT(ITrainerInfo tr) - { - if (string.IsNullOrWhiteSpace(tr.OT)) - return; - - // Get Save Information - TB_OT.Text = tr.OT; - UC_OTGender.Gender = tr.Gender & 1; - TID_Trainer.LoadInfo(tr); - - if (tr.Game >= 0) - CB_GameOrigin.SelectedValue = tr.Game; - - var lang = tr.Language; - if (lang <= 0) - lang = (int)LanguageID.English; - CB_Language.SelectedValue = lang; - if (tr is IRegionOrigin o) - { - CB_3DSReg.SelectedValue = (int)o.ConsoleRegion; - CB_Country.SelectedValue = (int)o.Country; - CB_SubRegion.SelectedValue = (int)o.Region; - } - - // Copy OT trash bytes for sensitive games (Gen1/2) - if (tr is SAV1 s1 && Entity is PK1 p1) s1.OT_Trash.CopyTo(p1.OT_Trash); - else if (tr is SAV2 s2 && Entity is PK2 p2) s2.OT_Trash.CopyTo(p2.OT_Trash); - - UpdateNickname(this, EventArgs.Empty); - } - - private void SetDetailsHT(ITrainerInfo tr) - { - var trainer = tr.OT; - if (trainer.Length == 0) - return; - - if (!tr.IsOriginalHandler(Entity, false)) - { - TB_HT.Text = trainer; - UC_HTGender.Gender = tr.Gender & 1; - if (Entity is IHandlerLanguage) - CB_HTLanguage.SelectedValue = tr.Language; - } - else if (TB_HT.Text.Length != 0) - { - if (CB_HTLanguage.SelectedIndex == 0 && Entity is IHandlerLanguage) - CB_HTLanguage.SelectedValue = tr.Language; - } - } - - private void SetForms() - { - int species = Entity.Species; - var pi = RequestSaveFile.Personal[species]; - bool hasForms = FormInfo.HasFormSelection(pi, species, Entity.Format); - CB_Form.Enabled = CB_Form.Visible = Label_Form.Visible = hasForms; - - if (HaX && Entity.Format >= 4) - Label_Form.Visible = true; // show with value entry textbox - - if (!hasForms) - { - if (HaX) - return; - Entity.Form = 0; - if (CB_Form.Items.Count > 0) - CB_Form.SelectedIndex = 0; - return; - } - - var ds = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, gendersymbols, Entity.Format); - if (ds.Length == 1 && string.IsNullOrEmpty(ds[0])) // empty (Alolan Totems) - CB_Form.Enabled = CB_Form.Visible = Label_Form.Visible = false; - else - CB_Form.DataSource = ds; - } - - private void SetAbilityList() - { - if (Entity.Format < 3) // no abilities - return; - - if (Entity.Format > 3 && FieldsLoaded) // has forms - Entity.Form = CB_Form.SelectedIndex; // update pkm field for form specific abilities - - int abil = CB_Ability.SelectedIndex; - - bool tmp = FieldsLoaded; - FieldsLoaded = false; - CB_Ability.DataSource = GameInfo.FilteredSources.GetAbilityList(Entity); - CB_Ability.SelectedIndex = GetSafeIndex(CB_Ability, abil); // restore original index if available - FieldsLoaded = tmp; - } - - private static int GetSafeIndex(ComboBox cb, int index) => Math.Max(0, Math.Min(cb.Items.Count - 1, index)); - - private void UpdateIsShiny() - { - // Set the Controls - var type = ShinyExtensions.GetType(Entity); - BTN_Shinytize.Visible = BTN_Shinytize.Enabled = type == Shiny.Never; - PB_ShinyStar.Visible = type == Shiny.AlwaysStar; - PB_ShinySquare.Visible = type == Shiny.AlwaysSquare; - - // Refresh Markings (for Shiny Star if applicable) - SetMarkings(); - } - - private void SetMarkings() - { - var pba = Markings; - var count = Entity.MarkingCount; - for (int i = 0; i < pba.Length; i++) - pba[i].Image = GetMarkSprite(pba[i], i < count && Entity.GetMarking(i) != 0); - - PB_MarkShiny.Image = GetMarkSprite(PB_MarkShiny, !BTN_Shinytize.Enabled); - PB_MarkCured.Image = GetMarkSprite(PB_MarkCured, CHK_Cured.Checked); - - PB_Favorite.Image = GetMarkSprite(PB_Favorite, Entity is IFavorite {Favorite: true}); - PB_Origin.Image = GetOriginSprite(Entity); - - // Colored Markings - if (Entity.Format < 7) - return; - - for (int i = 0; i < count; i++) - { - if (!Draw.GetMarkingColor(Entity.GetMarking(i), out Color c)) - continue; - var pb = pba[i]; - pb.Image = ImageUtil.ChangeAllColorTo(pb.Image, c); - } - } - - private static Image? GetOriginSprite(PKM pkm) - { - if (pkm.Format < 6) - return null; - - // Specific Markings - if (pkm.VC) - return Properties.Resources.gen_vc; - if (pkm.GO) - return Properties.Resources.gen_go; - if (pkm.LGPE) - return Properties.Resources.gen_gg; - - // Lumped Generations - if (pkm.Gen6) - return Properties.Resources.gen_6; - if (pkm.Gen7) - return Properties.Resources.gen_7; - if (pkm.SWSH) - return Properties.Resources.gen_8; - if (pkm.BDSP) - return Properties.Resources.gen_bs; - if (pkm.LA) - return Properties.Resources.gen_la; - - return null; - } - - private static void SetCountrySubRegion(ComboBox cb, string type) - { - int oldIndex = cb.SelectedIndex; - cb.DataSource = Util.GetCountryRegionList(type, GameInfo.CurrentLanguage); - - if (oldIndex > 0 && oldIndex < cb.Items.Count) - cb.SelectedIndex = oldIndex; - } - - // Prompted Updates of PKM // - private void ClickFriendship(object sender, EventArgs e) - { - var pk = Entity; - bool worst = ModifierKeys == Keys.Control ^ pk.IsEgg; - var current = int.Parse(TB_Friendship.Text); - var value = worst - ? pk.IsEgg ? EggStateLegality.GetMinimumEggHatchCycles(pk) : 0 - : pk.IsEgg ? EggStateLegality.GetMaximumEggHatchCycles(pk) : current == 255 ? pk.PersonalInfo.BaseFriendship : 255; - TB_Friendship.Text = value.ToString(); - } - - private void ClickLevel(object sender, EventArgs e) - { - if (ModifierKeys == Keys.Control && sender is TextBoxBase tb) - tb.Text = "100"; - } - - private void ClickGender(object sender, EventArgs e) - { - if (!Entity.PersonalInfo.IsDualGender) - return; // can't toggle - - var (canToggle, gender) = UC_Gender.ToggleGender(); - if (!canToggle) - return; - if (Entity.Format <= 2) - { - Stats.SetATKIVGender(gender); - UpdateIsShiny(); - } - else if (Entity.Format <= 4) - { - Entity.Version = WinFormsUtil.GetIndex(CB_GameOrigin); - Entity.Nature = WinFormsUtil.GetIndex(CB_Nature); - Entity.Form = CB_Form.SelectedIndex; - - Entity.SetPIDGender(gender); - TB_PID.Text = Entity.PID.ToString("X8"); - } - Entity.Gender = gender; - - if (EntityGender.GetFromString(CB_Form.Text) < 2) // Gendered Forms - CB_Form.SelectedIndex = Math.Min(gender, CB_Form.Items.Count - 1); - - UpdatePreviewSprite?.Invoke(UC_Gender, EventArgs.Empty); - } - - private void ClickPP(object sender, EventArgs e) - { - for (int i = 0; i < MovePP.Length; i++) - RefreshMovePP(i); - } - - private void ClickPPUps(object sender, EventArgs e) - { - bool min = (ModifierKeys & Keys.Control) != 0 || !Legal.IsPPUpAvailable(Entity); - static int GetValue(ListControl cb, bool zero) => zero || WinFormsUtil.GetIndex(cb) == 0 ? 0 : 3; - CB_PPu1.SelectedIndex = GetValue(CB_Move1, min); - CB_PPu2.SelectedIndex = GetValue(CB_Move2, min); - CB_PPu3.SelectedIndex = GetValue(CB_Move3, min); - CB_PPu4.SelectedIndex = GetValue(CB_Move4, min); - } - - private void ClickMarking(object sender, EventArgs e) - { - int index = Array.IndexOf(Markings, (PictureBox)sender); - Entity.ToggleMarking(index); - SetMarkings(); - } - - private void ClickFavorite(object sender, EventArgs e) - { - if (Entity is IFavorite pb7) - pb7.Favorite ^= true; - SetMarkings(); - } - - private void ClickOT(object sender, EventArgs e) => SetDetailsOT(SaveFileRequested.Invoke(this, e)); - private void ClickCT(object sender, EventArgs e) => SetDetailsHT(SaveFileRequested.Invoke(this, e)); - - private void ClickBall(object sender, EventArgs e) - { - Entity.Ball = WinFormsUtil.GetIndex(CB_Ball); - if ((ModifierKeys & Keys.Alt) != 0) - { - CB_Ball.SelectedValue = (int)Ball.Poke; - return; - } - if ((ModifierKeys & Keys.Shift) != 0) - { - CB_Ball.SelectedValue = BallApplicator.ApplyBallLegalByColor(Entity); - return; - } - - using var frm = new BallBrowser(); - frm.LoadBalls(Entity); - frm.ShowDialog(); - if (frm.BallChoice >= 0) - CB_Ball.SelectedValue = frm.BallChoice; - } - - private void ClickShinyLeaf(object sender, EventArgs e) => ShinyLeaf.CheckAll(ModifierKeys != Keys.Control); - - private void ClickMetLocation(object sender, EventArgs e) - { - if (HaX) - return; - - Entity = PreparePKM(); - UpdateLegality(skipMoveRepop: true); - if (Legality.Valid) - return; - if (!SetSuggestedMetLocation()) - return; - - Entity = PreparePKM(); - UpdateLegality(); - } - - private void ClickGT(object? sender, EventArgs e) - { - if (!GB_nOT.Visible) - return; - - if (sender == GB_OT) - Entity.CurrentHandler = 0; - else if (TB_HT.Text.Length > 0) - Entity.CurrentHandler = 1; - UpadteHandlingTrainerBackground(Entity.CurrentHandler); - - ReloadToFriendshipTextBox(Entity); - } - - private void ClickNature(object sender, EventArgs e) - { - if (Entity.Format < 8) - return; - if (sender == Label_Nature) - CB_Nature.SelectedIndex = CB_StatNature.SelectedIndex; - else - CB_StatNature.SelectedIndex = CB_Nature.SelectedIndex; - } - - private void ClickMoves(object? sender, EventArgs e) - { - UpdateLegality(skipMoveRepop: true); - if (sender == GB_CurrentMoves) - { - bool random = ModifierKeys == Keys.Control; - if (!SetSuggestedMoves(random)) - return; - } - else if (sender == GB_RelearnMoves) - { - if (!SetSuggestedRelearnMoves()) - return; - } - else - { - return; - } - - UpdateLegality(); - } - - private bool SetSuggestedMoves(bool random = false, bool silent = false) - { - var m = Entity.GetMoveSet(random); - if (m.Length == 0 || m.All(z => z == 0)) - { - if (!silent) - WinFormsUtil.Alert(MsgPKMSuggestionFormat); - return false; - } - - if (Entity.Moves.SequenceEqual(m)) - return false; - - if (!silent) - { - var mv = GameInfo.Strings.Move; - var movestrings = m.Select(v => (uint)v >= mv.Count ? MsgProgramError : mv[v]); - var msg = string.Join(Environment.NewLine, movestrings); - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgPKMSuggestionMoves, msg)) - return false; - } - - Entity.SetMoves(m); - FieldsLoaded = false; - LoadMoves(Entity); - ClickPP(this, EventArgs.Empty); - FieldsLoaded = true; - return true; - } - - private bool SetSuggestedRelearnMoves(bool silent = false) - { - if (Entity.Format < 6) - return false; - - var m = Legality.GetSuggestedRelearnMoves(); - if (Entity.RelearnMoves.SequenceEqual(m) || m.Count != 4) - return false; - - if (!silent) - { - var mv = GameInfo.Strings.Move; - var movestrings = m.Select(v => (uint)v >= mv.Count ? MsgProgramError : mv[v]); - var msg = string.Join(Environment.NewLine, movestrings); - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgPKMSuggestionRelearn, msg)) - return false; - } + if (skipMoveRepop) + return; + // Resort moves + FieldsLoaded = false; + LegalMoveSource.ReloadMoves(Legality.GetSuggestedMovesAndRelearn()); + FieldsLoaded = true; + LegalityChanged?.Invoke(Legality.Valid, EventArgs.Empty); + } - CB_RelearnMove1.SelectedValue = m[0]; - CB_RelearnMove2.SelectedValue = m[1]; - CB_RelearnMove3.SelectedValue = m[2]; - CB_RelearnMove4.SelectedValue = m[3]; - return true; - } - - private bool SetSuggestedMetLocation(bool silent = false) - { - var encounter = EncounterSuggestion.GetSuggestedMetInfo(Entity); - if (encounter == null || (Entity.Format >= 3 && encounter.Location < 0)) - { - if (!silent) - WinFormsUtil.Alert(MsgPKMSuggestionNone); - return false; - } - - int level = encounter.LevelMin; - int location = encounter.Location; - int minlvl = EncounterSuggestion.GetLowestLevel(Entity, encounter.LevelMin); - if (minlvl == 0) - minlvl = level; - - if (Entity.CurrentLevel >= minlvl && Entity.Met_Level == level && Entity.Met_Location == location) - { - if (!encounter.HasGroundTile(Entity.Format) || WinFormsUtil.GetIndex(CB_GroundTile) == (int)encounter.GetSuggestedGroundTile()) - return false; - } - if (minlvl < level) - minlvl = level; - - if (!silent) - { - var suggestions = EntitySuggestionUtil.GetMetLocationSuggestionMessage(Entity, level, location, minlvl); - if (suggestions.Count <= 1) // no suggestion - return false; - - var msg = string.Join(Environment.NewLine, suggestions); - if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, msg) != DialogResult.Yes) - return false; - } - - if (Entity.Format >= 3) - { - Entity.Met_Location = location; - TB_MetLevel.Text = encounter.GetSuggestedMetLevel(Entity).ToString(); - CB_MetLocation.SelectedValue = location; - - if (encounter.HasGroundTile(Entity.Format)) - CB_GroundTile.SelectedValue = (int)encounter.GetSuggestedGroundTile(); - - if (Entity.Gen6 && Entity.WasEgg && ModifyPKM) - Entity.SetHatchMemory6(); - } - - if (Entity.CurrentLevel < minlvl) - TB_Level.Text = minlvl.ToString(); - - return true; - } - - public void UpdateIVsGB(bool skipForm) - { - if (!FieldsLoaded) - return; - UC_Gender.Gender = Entity.Gender; - if (Entity.Species == (int)Species.Unown && !skipForm) - CB_Form.SelectedIndex = Entity.Form; - - UpdateIsShiny(); - UpdateSprite(); - } - - private void UpdateBall(object sender, EventArgs e) - { - PB_Ball.Image = SpriteUtil.GetBallSprite(WinFormsUtil.GetIndex(CB_Ball)); - } - - private void UpdateEXPLevel(object sender, EventArgs e) - { - if (ChangingFields) - return; - ChangingFields = true; - if (sender == TB_EXP) - { - // Change the Level - var input = Util.ToUInt32(TB_EXP.Text); - var exp = input; - var gr = Entity.PersonalInfo.EXPGrowth; - int level = Experience.GetLevel(exp, gr); - if (level == 100) - exp = Experience.GetEXP(100, gr); - - if (level != Util.ToInt32(TB_Level.Text)) - TB_Level.Text = level.ToString(); - if (input != exp && !HaX) - TB_EXP.Text = exp.ToString(); - } - else - { - // Change the XP - int input = Util.ToInt32(TB_Level.Text); - int level = Math.Max(1, Math.Min(input, 100)); - - if (input != level) - TB_Level.Text = level.ToString(); - TB_EXP.Text = Experience.GetEXP(level, Entity.PersonalInfo.EXPGrowth).ToString(); - } - ChangingFields = false; - if (FieldsLoaded) // store values back - Entity.EXP = Util.ToUInt32(TB_EXP.Text); - UpdateStats(); - UpdateLegality(); - } - - private void UpdateRandomPID(object sender, EventArgs e) - { - if (Entity.Format < 3) - return; - if (FieldsLoaded) - Entity.PID = Util.GetHexValue(TB_PID.Text); - - if (sender == UC_Gender) - Entity.SetPIDGender(Entity.Gender); - else if (sender == CB_Nature && Entity.Nature != WinFormsUtil.GetIndex(CB_Nature)) - Entity.SetPIDNature(WinFormsUtil.GetIndex(CB_Nature)); - else if (sender == BTN_RerollPID) - Entity.SetPIDGender(Entity.Gender); - else if (sender == CB_Ability && CB_Ability.SelectedIndex != Entity.PIDAbility && Entity.PIDAbility > -1) - Entity.SetAbilityIndex(CB_Ability.SelectedIndex); - - TB_PID.Text = Entity.PID.ToString("X8"); - if (Entity.Format >= 6 && (Entity.Gen3 || Entity.Gen4 || Entity.Gen5)) - TB_EC.Text = TB_PID.Text; - Update_ID(TB_EC, e); - } - - private void UpdateRandomEC(object sender, EventArgs e) - { - if (Entity.Format < 6) - return; - - Entity.SetRandomEC(); - TB_EC.Text = Entity.EncryptionConstant.ToString("X8"); - Update_ID(TB_EC, e); - UpdateLegality(); - } - - private void Update255_MTB(object sender, EventArgs e) - { - if (sender is not MaskedTextBox tb) - return; - if (Util.ToInt32(tb.Text) > byte.MaxValue) - tb.Text = "255"; - if (sender == TB_Friendship && int.TryParse(TB_Friendship.Text, out var value)) - { - UpdateFromFriendshipTextBox(Entity, value); - UpdateStats(); - } - } - - private void UpdateFormArgument(object sender, EventArgs e) - { - if (FieldsLoaded && Entity.Species == (int)Species.Alcremie) - UpdateSprite(); - } - - private void UpdateForm(object sender, EventArgs e) - { - if (FieldsLoaded && sender == CB_Form) - { - Entity.Form = CB_Form.SelectedIndex; - uint EXP = Experience.GetEXP(Entity.CurrentLevel, Entity.PersonalInfo.EXPGrowth); - TB_EXP.Text = EXP.ToString(); - } - - UpdateStats(); - SetAbilityList(); - - // Gender Forms - if (WinFormsUtil.GetIndex(CB_Species) == (int)Species.Unown && FieldsLoaded) - { - if (Entity.Format == 3) - { - Entity.SetPIDUnown3(CB_Form.SelectedIndex); - TB_PID.Text = Entity.PID.ToString("X8"); - } - else if (Entity.Format == 2) - { - int desiredForm = CB_Form.SelectedIndex; - while (Entity.Form != desiredForm) - { - FieldsLoaded = false; - Stats.UpdateRandomIVs(sender, EventArgs.Empty); - FieldsLoaded = true; - } - } - } - else if (CB_Form.Enabled && EntityGender.GetFromString(CB_Form.Text) < 2) - { - if (CB_Form.Items.Count == 2) // actually M/F; Pumpkaboo formes in German are S,M,L,XL - { - Entity.Gender = CB_Form.SelectedIndex; - UC_Gender.Gender = Entity.GetSaneGender(); - } - } - else - { - UC_Gender.Gender = Entity.GetSaneGender(); - } - - RefreshFormArguments(); - if (ChangingFields) - return; - UpdateSprite(); - } - - private void RefreshFormArguments() - { - if (Entity is not IFormArgument f) - return; - - if (FieldsLoaded) - FA_Form.SaveArgument(f); - FA_Form.LoadArgument(f, Entity.Species, Entity.Form, Entity.Format); - } - - private void UpdatePP(object sender, EventArgs e) - { - if (sender is not ComboBox cb) - return; - int index = Array.IndexOf(Moves, cb); - if (index < 0) - index = Array.IndexOf(PPUps, cb); - if (index < 0) - return; - - RefreshMovePP(index); - } - - private void RefreshMovePP(int index) - { - int move = WinFormsUtil.GetIndex(Moves[index]); - var ppUpControl = PPUps[index]; - int ppUpCount = ppUpControl.SelectedIndex; - if (move <= 0) - { - ppUpControl.SelectedIndex = 0; - MovePP[index].Text = 0.ToString(); - } - else - { - MovePP[index].Text = Entity.GetMovePP(move, ppUpCount).ToString(); - } - } - - private void UpdatePKRSstrain(object sender, EventArgs e) - { - // Change the PKRS Days to the legal bounds. - int currentDuration = CB_PKRSDays.SelectedIndex; - CB_PKRSDays.Items.Clear(); - - var strain = CB_PKRSStrain.SelectedIndex; - int max = Pokerus.GetMaxDuration(strain); - for (int day = 0; day <= max; day++) - CB_PKRSDays.Items.Add(day.ToString()); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Bitmap? GetMoveImage(bool isIllegal, PKM pk, int index) + { + if (isIllegal) + return Resources.warn; + if (pk.Format >= 8 && MoveInfo.GetDummiedMovesHashSet(pk.Context).Contains(pk.GetMove(index))) + return Resources.hint; + return null; + } - // Set the days back if they're legal - CB_PKRSDays.SelectedIndex = strain == 0 ? 0 : Math.Min(max, currentDuration); - } - - private void UpdatePKRSdays(object sender, EventArgs e) + public void UpdateUnicode(IReadOnlyList symbols) + { + gendersymbols = symbols; + if (!Unicode) { - var days = CB_PKRSDays.SelectedIndex; - if (days != 0) - return; - - // If no days are selected - var strain = CB_PKRSStrain.SelectedIndex; - if (Pokerus.IsSusceptible(strain, days)) - CHK_Cured.Checked = CHK_Infected.Checked = false; // No Strain = Never Cured / Infected, triggers Strain update - else if (Pokerus.IsImmune(strain, days)) - CHK_Cured.Checked = true; // Any Strain = Cured + BTN_Shinytize.Text = Draw.ShinyDefault; + TB_Nickname.Font = TB_OT.Font = TB_HT.Font = GB_OT.Font; } - - private void UpdatePKRSCured(object sender, EventArgs e) + else { - // Cured PokeRus is toggled - if (CHK_Cured.Checked) - { - // Has Had PokeRus - Label_PKRSdays.Visible = CB_PKRSDays.Visible = false; - CB_PKRSDays.SelectedIndex = 0; - - Label_PKRS.Visible = CB_PKRSStrain.Visible = true; - CHK_Infected.Checked = true; - - // If we're cured we have to have a strain infection. - if (CB_PKRSStrain.SelectedIndex == 0) - CB_PKRSStrain.SelectedIndex = 1; - } - else if (!CHK_Infected.Checked) - { - // Not Infected, Disable the other - Label_PKRS.Visible = CB_PKRSStrain.Visible = false; - CB_PKRSStrain.SelectedIndex = 0; - } - else - { - // Still Infected for a duration - Label_PKRSdays.Visible = CB_PKRSDays.Visible = true; - CB_PKRSDays.SelectedValue = 1; - } - // if not cured yet, days > 0 - if (!CHK_Cured.Checked && CHK_Infected.Checked && CB_PKRSDays.SelectedIndex == 0) - CB_PKRSDays.SelectedIndex = 1; - - SetMarkings(); - } - - private void UpdatePKRSInfected(object sender, EventArgs e) - { - if (CHK_Cured.Checked) - { - if (!CHK_Infected.Checked) - CHK_Cured.Checked = false; - return; - } - - Label_PKRS.Visible = CB_PKRSStrain.Visible = CHK_Infected.Checked; - if (!CHK_Infected.Checked) - { - CB_PKRSStrain.SelectedIndex = 0; - CB_PKRSDays.SelectedIndex = 0; - Label_PKRSdays.Visible = CB_PKRSDays.Visible = false; - } - else if (CB_PKRSStrain.SelectedIndex == 0) - { - CB_PKRSStrain.SelectedIndex = CB_PKRSDays.SelectedIndex = 1; - Label_PKRSdays.Visible = CB_PKRSDays.Visible = true; - UpdatePKRSCured(sender, e); - } - } - - private void UpdateCountry(object sender, EventArgs e) - { - int index; - if (sender is ComboBox c && (index = WinFormsUtil.GetIndex(c)) > 0) - SetCountrySubRegion(CB_SubRegion, $"sr_{index:000}"); - } - - private void UpdateSpecies(object sender, EventArgs e) - { - // Get Species dependent information - if (FieldsLoaded) - Entity.Species = WinFormsUtil.GetIndex(CB_Species); - SpeciesIDTip.SetToolTip(CB_Species, Entity.Species.ToString("000")); - SetAbilityList(); - SetForms(); - UpdateForm(sender, EventArgs.Empty); - - if (!FieldsLoaded) - return; - - // Recalculate EXP for Given Level - uint EXP = Experience.GetEXP(Entity.CurrentLevel, Entity.PersonalInfo.EXPGrowth); - TB_EXP.Text = EXP.ToString(); - - // Check for Gender Changes - UC_Gender.Gender = Entity.GetSaneGender(); - - // If species changes and no nickname, set the new name == speciesName. - if (!CHK_Nicknamed.Checked) - UpdateNickname(sender, e); - - UpdateLegality(); - } - - private void UpdateOriginGame(object sender, EventArgs e) - { - GameVersion version = (GameVersion)WinFormsUtil.GetIndex(CB_GameOrigin); - if (version.IsValidSavedVersion()) - { - CheckMetLocationChange(version, Entity.Context); - if (FieldsLoaded) - Entity.Version = (int)version; - } - - // Visibility logic for Gen 4 ground tile; only show for Gen 4 Pokemon. - if (Entity is IGroundTile) - { - bool g4 = Entity.Gen4; - CB_GroundTile.Visible = Label_GroundTile.Visible = g4 && Entity.Format < 7; - if (!g4) - CB_GroundTile.SelectedValue = (int)GroundTileType.None; - } - - if (!FieldsLoaded) - return; - - PB_Origin.Image = GetOriginSprite(Entity); - TID_Trainer.LoadIDValues(Entity); - UpdateLegality(); - } - - private void CheckMetLocationChange(GameVersion version, EntityContext context) - { - // Does the list of locations need to be changed to another group? - var group = GameUtil.GetMetLocationVersionGroup(version); - if (group != origintrack || context != originFormat) - ReloadMetLocations(version, context); - origintrack = group; - originFormat = context; - } - - private void ReloadMetLocations(GameVersion version, EntityContext context) - { - var metList = GameInfo.GetLocationList(version, context, egg: false); - CB_MetLocation.DataSource = new BindingSource(metList, null); - CB_MetLocation.DropDownWidth = GetWidth(metList, CB_MetLocation.Font); - - var eggList = GameInfo.GetLocationList(version, context, egg: true); - CB_EggLocation.DataSource = new BindingSource(eggList, null); - CB_EggLocation.DropDownWidth = GetWidth(eggList, CB_EggLocation.Font); - - static int GetWidth(IEnumerable items, Font f) => - items.Max(z => TextRenderer.MeasureText(z.Text, f).Width) + - SystemInformation.VerticalScrollBarWidth; - - if (FieldsLoaded) - { - SetMarkings(); // Set/Remove the Nativity marking when gamegroup changes too - int metLoc = EncounterSuggestion.GetSuggestedTransferLocation(Entity); - int eggLoc = CHK_AsEgg.Checked - ? EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(Entity, true) - : LocationEdits.GetNoneLocation(Entity); - - CB_MetLocation.SelectedValue = Math.Max(0, metLoc); - CB_EggLocation.SelectedValue = eggLoc; - } - else - { - ValidateChildren(); // hacky validation forcing - } - } - - private void UpdateExtraByteValue(object sender, EventArgs e) - { - if (!FieldsLoaded || CB_ExtraBytes.Items.Count == 0 || sender is not MaskedTextBox mtb) - return; - // Changed Extra Byte's Value - var value = Util.ToInt32(mtb.Text); - if (value > byte.MaxValue) - { - mtb.Text = "255"; - return; // above statement triggers the event again. - } - - int offset = Convert.ToInt32(CB_ExtraBytes.Text, 16); - Entity.Data[offset] = (byte)value; - } - - private void UpdateExtraByteIndex(object sender, EventArgs e) - { - if (CB_ExtraBytes.Items.Count == 0) - return; - // Byte changed, need to refresh the Text box for the byte's value. - var offset = Convert.ToInt32(CB_ExtraBytes.Text, 16); - TB_ExtraByte.Text = Entity.Data[offset].ToString(); - } - - public void ChangeNature(int newNature) - { - if (Entity.Format < 3) - return; - - var cb = Entity.Format >= 8 ? CB_StatNature : CB_Nature; - cb.SelectedValue = newNature; - } - - private void UpdateNatureModification(ComboBox cb, int nature) - { - string text = Stats.UpdateNatureModification(nature); - NatureTip.SetToolTip(cb, text); - } - - private void UpdateIsNicknamed(object sender, EventArgs e) - { - if (!FieldsLoaded) - return; - - Entity.Nickname = TB_Nickname.Text; - if (CHK_Nicknamed.Checked) - return; - - int species = WinFormsUtil.GetIndex(CB_Species); - if (species < 1 || species > Entity.MaxSpeciesID) - return; - - if (CHK_IsEgg.Checked) - species = 0; // get the egg name. - - if (SpeciesName.IsNicknamedAnyLanguage(species, TB_Nickname.Text, Entity.Format)) - CHK_Nicknamed.Checked = true; - } - - private void UpdateNickname(object sender, EventArgs e) - { - if (sender == Label_Species) - { - switch (ModifierKeys) - { - case Keys.Control: RequestShowdownImport?.Invoke(sender, e); return; - case Keys.Alt: RequestShowdownExport?.Invoke(sender, e); return; - default: - return; - } - } - - if (CHK_Nicknamed.Checked) - return; - - // Fetch Current Species and set it as Nickname Text - int species = WinFormsUtil.GetIndex(CB_Species); - if ((uint)(species - 1) >= Entity.MaxSpeciesID) - { TB_Nickname.Text = string.Empty; return; } - - if (CHK_IsEgg.Checked) - species = 0; // get the egg name. - - // If name is that of another language, don't replace the nickname - if (sender != CB_Language && species != 0 && !SpeciesName.IsNicknamedAnyLanguage(species, TB_Nickname.Text, Entity.Format)) - return; - - int lang = WinFormsUtil.GetIndex(CB_Language); - TB_Nickname.Text = SpeciesName.GetSpeciesNameGeneration(species, lang, Entity.Format); - if (Entity is GBPKM pk) - pk.SetNotNicknamed(); - } - - private void UpdateNicknameClick(object sender, MouseEventArgs e) - { - TextBox tb = sender as TextBox ?? TB_Nickname; - // Special Character Form - if (ModifierKeys != Keys.Control) - return; - - var sav = RequestSaveFile; - - if (tb == TB_Nickname) - { - Entity.Nickname = tb.Text; - var span = Entity.Nickname_Trash; - var d = new TrashEditor(tb, span, sav); - d.ShowDialog(); - tb.Text = d.FinalString; - d.FinalBytes.CopyTo(span); - } - else if (tb == TB_OT) - { - Entity.OT_Name = tb.Text; - var span = Entity.OT_Trash; - var d = new TrashEditor(tb, span, sav); - d.ShowDialog(); - tb.Text = d.FinalString; - d.FinalBytes.CopyTo(span); - } - else if (tb == TB_HT) - { - Entity.HT_Name = tb.Text; - var span = Entity.HT_Trash; - var d = new TrashEditor(tb, span, sav); - d.ShowDialog(); - tb.Text = d.FinalString; - d.FinalBytes.CopyTo(span); - } - } - - private void UpdateNotOT(object sender, EventArgs e) - { - if (string.IsNullOrWhiteSpace(TB_HT.Text)) - { - ClickGT(GB_OT, EventArgs.Empty); // Switch CT over to OT. - UC_HTGender.Visible = false; - UC_HTGender.Gender = 0; - ReloadToFriendshipTextBox(Entity); - } - else if (!UC_HTGender.Visible) - { - UC_HTGender.Visible = true; - } - } - - private void UpdateIsEgg(object sender, EventArgs e) - { - // Display hatch counter if it is an egg, Display Friendship if it is not. - Label_HatchCounter.Visible = CHK_IsEgg.Checked && Entity.Format > 1; - Label_Friendship.Visible = !CHK_IsEgg.Checked && Entity.Format > 1; - - if (!FieldsLoaded) - return; - - if (Entity.Format == 3 && CHK_IsEgg.Checked) - Entity.OT_Name = TB_OT.Text; // going to be remapped - - Entity.IsEgg = CHK_IsEgg.Checked; - if (CHK_IsEgg.Checked) - { - TB_Friendship.Text = EggStateLegality.GetMinimumEggHatchCycles(Entity).ToString(); - - // If we are an egg, it won't have a met location. - CHK_AsEgg.Checked = true; - GB_EggConditions.Enabled = true; - - CAL_MetDate.Value = new DateTime(2000, 01, 01); - - // if egg wasn't originally obtained by OT => Link Trade, else => None - if (Entity.Format >= 4) - { - var sav = SaveFileRequested.Invoke(this, e); - bool isTraded = sav.OT != TB_OT.Text || sav.TID != Entity.TID || sav.SID != Entity.SID; - var loc = isTraded ? Locations.TradedEggLocation(sav.Generation, sav.Version) : LocationEdits.GetNoneLocation(Entity); - CB_MetLocation.SelectedValue = loc; - } - else if (Entity.Format == 3) - { - CB_Language.SelectedValue = Entity.Language; // JPN - TB_OT.Text = Entity.OT_Name; - } - - CHK_Nicknamed.Checked = EggStateLegality.IsNicknameFlagSet(Entity); - TB_Nickname.Text = SpeciesName.GetSpeciesNameGeneration(0, WinFormsUtil.GetIndex(CB_Language), Entity.Format); - - // Wipe egg memories - if (Entity.Format >= 6 && ModifyPKM) - Entity.ClearMemories(); - } - else // Not Egg - { - if (!CHK_Nicknamed.Checked) - UpdateNickname(this, EventArgs.Empty); - - TB_Friendship.Text = Entity.PersonalInfo.BaseFriendship.ToString(); - - if (CB_EggLocation.SelectedIndex == 0) - { - CAL_MetDate.Value = DateTime.Now; - CAL_EggDate.Value = new DateTime(2000, 01, 01); - CHK_AsEgg.Checked = false; - GB_EggConditions.Enabled = false; - } - else - { - CAL_MetDate.Value = CAL_EggDate.Value; - CB_MetLocation.SelectedValue = EncounterSuggestion.GetSuggestedEggMetLocation(Entity); - } - - if (TB_Nickname.Text == SpeciesName.GetSpeciesNameGeneration(0, WinFormsUtil.GetIndex(CB_Language), Entity.Format)) - CHK_Nicknamed.Checked = false; - } - - UpdateNickname(this, EventArgs.Empty); - UpdateSprite(); - } - - private void UpdateMetAsEgg(object sender, EventArgs e) - { - GB_EggConditions.Enabled = CHK_AsEgg.Checked; - if (CHK_AsEgg.Checked) - { - if (!FieldsLoaded) - return; - - CAL_EggDate.Value = DateTime.Now; - - bool isTradedEgg = Entity.IsEgg && Entity.Version != (int)RequestSaveFile.Version; - CB_EggLocation.SelectedValue = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(Entity, isTradedEgg); - return; - } - // Remove egg met data - CHK_IsEgg.Checked = false; - CAL_EggDate.Value = new DateTime(2000, 01, 01); - CB_EggLocation.SelectedValue = LocationEdits.GetNoneLocation(Entity); - - UpdateLegality(); - } - - private void UpdateShinyPID(object sender, EventArgs e) - { - var changePID = Entity.Format >= 3 && (ModifierKeys & Keys.Alt) == 0; - UpdateShiny(changePID); - } - - private void UpdateShiny(bool changePID) - { - Entity.PID = Util.GetHexValue(TB_PID.Text); - Entity.Nature = WinFormsUtil.GetIndex(CB_Nature); - Entity.Gender = UC_Gender.Gender; - Entity.Form = CB_Form.SelectedIndex; - Entity.Version = WinFormsUtil.GetIndex(CB_GameOrigin); - - if (Entity.Format > 2) - { - var type = (ModifierKeys & ~Keys.Alt) switch - { - Keys.Shift => Shiny.AlwaysSquare, - Keys.Control => Shiny.AlwaysStar, - _ => Shiny.Random, - }; - if (changePID) - { - CommonEdits.SetShiny(Entity, type); - TB_PID.Text = Entity.PID.ToString("X8"); - - int gen = Entity.Generation; - bool pre3DS = gen is 3 or 4 or 5; - if (pre3DS && Entity.Format >= 6) - TB_EC.Text = TB_PID.Text; - } - else - { - Entity.SetShinySID(type); - TID_Trainer.UpdateSID(); - } - } - else - { - Entity.SetShiny(); - Stats.LoadIVs(Entity.IVs); - Stats.UpdateIVs(this, EventArgs.Empty); - } - - UpdateIsShiny(); - UpdatePreviewSprite?.Invoke(this, EventArgs.Empty); - UpdateLegality(); - } - - private void UpdateTSV(object sender, EventArgs e) - { - if (Entity.Format <= 2) - return; - - TID_Trainer.UpdateTSV(); - - Entity.PID = Util.GetHexValue(TB_PID.Text); - var tip = $"PSV: {Entity.PSV:d4}"; - if (Entity.IsShiny) - tip += $" | Xor = {Entity.ShinyXor}"; - Tip3.SetToolTip(TB_PID, tip); - } - - private void Update_ID(object? sender, EventArgs e) - { - if (!FieldsLoaded) - return; - // Trim out nonhex characters - TB_PID.Text = (Entity.PID = Util.GetHexValue(TB_PID.Text)).ToString("X8"); - TB_EC.Text = (Entity.EncryptionConstant = Util.GetHexValue(TB_EC.Text)).ToString("X8"); - - UpdateIsShiny(); - UpdateSprite(); - Stats.UpdateCharacteristic(); // If the EC is changed, EC%6 (Characteristic) might be changed. - if (Entity.Format <= 4) - { - FieldsLoaded = false; - Entity.PID = Util.GetHexValue(TB_PID.Text); - CB_Nature.SelectedValue = Entity.Nature; - UC_Gender.Gender = Entity.Gender; - UpdateNatureModification(CB_Nature, Entity.Nature); - FieldsLoaded = true; - } - } - - private void Update_ID64(object sender, EventArgs e) - { - if (!FieldsLoaded) - return; - // Trim out nonhex characters - if (sender == TB_HomeTracker && Entity is IHomeTrack home) - { - var value = Util.GetHexValue64(TB_HomeTracker.Text); - home.Tracker = value; - TB_HomeTracker.Text = value.ToString("X16"); - } - } - - private void UpdateShadowID(object sender, EventArgs e) - { - if (!FieldsLoaded) - return; - FLP_Purification.Visible = NUD_ShadowID.Value > 0; - } - - private void UpdatePurification(object sender, EventArgs e) - { - if (!FieldsLoaded) - return; - FieldsLoaded = false; - var value = NUD_Purification.Value; - CHK_Shadow.Checked = Entity is CK3 ? value != CK3.Purified : value > 0; - FieldsLoaded = true; - } - - private void UpdateShadowCHK(object sender, EventArgs e) - { - if (!FieldsLoaded) - return; - FieldsLoaded = false; - NUD_Purification.Value = CHK_Shadow.Checked ? 1 : Entity is CK3 && NUD_ShadowID.Value != 0 ? CK3.Purified : 0; - ((IShadowPKM)Entity).Purification = (int)NUD_Purification.Value; - UpdatePreviewSprite?.Invoke(this, EventArgs.Empty); - FieldsLoaded = true; - } - - private void ValidateComboBox(ComboBox cb) - { - if (cb.Text.Length == 0 && cb.Items.Count > 0) - cb.SelectedIndex = 0; - else if (cb.SelectedValue == null) - cb.BackColor = Draw.InvalidSelection; - else - cb.ResetBackColor(); - } - - private void ValidateComboBox(object sender, CancelEventArgs e) - { - if (sender is not ComboBox cb) - return; - - ValidateComboBox(cb); - UpdateSprite(); - } - - private void ValidateComboBox2(object sender, EventArgs e) - { - if (!FieldsLoaded) - return; - - ValidateComboBox(sender, new CancelEventArgs()); - if (sender == CB_Ability) - { - if (Entity.Format >= 6) - TB_AbilityNumber.Text = (1 << CB_Ability.SelectedIndex).ToString(); - else if (Entity.Format <= 5 && CB_Ability.SelectedIndex < 2) // Format <= 5, not hidden - UpdateRandomPID(sender, e); - UpdateLegality(); - } - else if (sender == CB_Nature) - { - if (Entity.Format <= 4) - UpdateRandomPID(sender, e); - Entity.Nature = WinFormsUtil.GetIndex(CB_Nature); - UpdateNatureModification(CB_Nature, Entity.Nature); - Stats.UpdateIVs(sender, EventArgs.Empty); // updating Nature will trigger stats to update as well - UpdateLegality(); - } - else if (sender == CB_StatNature) - { - Entity.StatNature = WinFormsUtil.GetIndex(CB_StatNature); - UpdateNatureModification(CB_StatNature, Entity.StatNature); - Stats.UpdateIVs(sender, EventArgs.Empty); // updating Nature will trigger stats to update as well - UpdateLegality(); - } - else if (sender == CB_HeldItem) - { - UpdateLegality(); - } - } - - private void ValidateMove(object sender, EventArgs e) - { - if (!FieldsLoaded || sender is not ComboBox cb) - return; - - ValidateComboBox(cb); - - // Store value back, repopulate legality. - int value = WinFormsUtil.GetIndex(cb); - int index = Array.IndexOf(Moves, cb); - if (index != -1) - { - UpdatePP(sender, e); - Entity.SetMove(index, value); - } - else if ((index = Array.IndexOf(Relearn, cb)) != -1) - { - Entity.SetRelearnMove(index, value); - } - else if (cb == CB_AlphaMastered && Entity is PA8 pa8) - { - pa8.AlphaMove = (ushort)value; - } - else - { - // Shouldn't hit here. - throw new InvalidOperationException(); - } - UpdateLegality(skipMoveRepop: true); - } - - private void ValidateMovePaint(object sender, DrawItemEventArgs e) - { - if (e.Index < 0) - return; - - var (text, value) = (ComboItem)((ComboBox)sender).Items[e.Index]; - var valid = LegalMoveSource.Info.CanLearn(value) && !HaX; - - var current = (e.State & DrawItemState.Selected) != 0; - var brush = Draw.Brushes.GetBackground(valid, current); - var textColor = Draw.GetText(current); - - DrawMoveRectangle(e, brush, text, textColor); - } - - private static void DrawMoveRectangle(DrawItemEventArgs e, Brush brush, string text, Color textColor) - { - var rec = new Rectangle(e.Bounds.X - 1, e.Bounds.Y, e.Bounds.Width + 1, e.Bounds.Height + 0); // 1px left - e.Graphics.FillRectangle(brush, rec); - - const TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | TextFormatFlags.ExpandTabs | TextFormatFlags.SingleLine; - TextRenderer.DrawText(e.Graphics, text, e.Font, rec, textColor, flags); - } - - private void MeasureDropDownHeight(object sender, MeasureItemEventArgs e) => e.ItemHeight = CB_RelearnMove1.ItemHeight; - - private void ValidateMoveDropDown(object sender, EventArgs e) - { - var s = (ComboBox) sender; - var index = Array.IndexOf(Moves, s); - - // Populating the combobox drop-down list is deferred until the dropdown is entered into at least once. - // Saves some lag delays when viewing a pkm. - if (LegalMoveSource.Display.GetIsMoveBoxOrdered(index)) - return; - SetMoveDataSource(s); - LegalMoveSource.Display.SetIsMoveBoxOrdered(index, true); - } - - private void SetMoveDataSource(ComboBox c) - { - var index = WinFormsUtil.GetIndex(c); - c.DataSource = new BindingSource(LegalMoveSource.Display.DataSource, null); - c.SelectedValue = index; - } - - private void ValidateLocation(object sender, EventArgs e) - { - if (!FieldsLoaded) - return; - - ValidateComboBox((ComboBox)sender); - Entity.Met_Location = WinFormsUtil.GetIndex(CB_MetLocation); - Entity.Egg_Location = WinFormsUtil.GetIndex(CB_EggLocation); - UpdateLegality(); - } - - // Secondary Windows for Ribbons/Amie/Memories - private void OpenRibbons(object sender, EventArgs e) - { - using var form = new RibbonEditor(Entity); - form.ShowDialog(); - } - - private void OpenMedals(object sender, EventArgs e) - { - using var form = new SuperTrainingEditor(Entity); - form.ShowDialog(); - } - - private void OpenHistory(object sender, EventArgs e) - { - // Write back current values - Entity.HT_Name = TB_HT.Text; - Entity.OT_Name = TB_OT.Text; - Entity.IsEgg = CHK_IsEgg.Checked; - UpdateFromFriendshipTextBox(Entity, Util.ToInt32(TB_Friendship.Text)); - using var form = new MemoryAmie(Entity); - form.ShowDialog(); - ReloadToFriendshipTextBox(Entity); - } - - private void B_Records_Click(object sender, EventArgs e) - { - if (Entity is not ITechRecord8 t) - return; - - if (ModifierKeys == Keys.Shift) - { - t.SetRecordFlags(Entity.Moves); - UpdateLegality(); - return; - } - - using var form = new TechRecordEditor(t, Entity); - form.ShowDialog(); - UpdateLegality(); - } - - private void B_MoveShop_Click(object sender, EventArgs e) - { - if (Entity is not IMoveShop8Mastery m) - return; - - if (ModifierKeys == Keys.Shift) - { - m.ClearMoveShopFlags(); - if (Legality.EncounterMatch is IMasteryInitialMoveShop8 enc) - enc.SetInitialMastery(Entity); - m.SetMoveShopFlags(Entity); - UpdateLegality(); - return; - } - - using var form = new MoveShopEditor(m, m, Entity); - form.ShowDialog(); - UpdateLegality(); - } - - /// - /// Refreshes the interface for the current PKM format. - /// - /// Save File context the editor is editing for - /// Pokémon data to edit - public bool ToggleInterface(SaveFile sav, PKM pk) - { - Entity = sav.GetCompatiblePKM(pk); - ToggleInterface(Entity); - return FinalizeInterface(sav); - } - - private void ToggleInterface(PKM t) - { - var pb7 = t is PB7; - int gen = t.Format; - FLP_Purification.Visible = FLP_ShadowID.Visible = t is IShadowPKM; - bool sizeCP = gen >= 8 || pb7; - FLP_SizeCP.Visible = sizeCP; - if (sizeCP) - SizeCP.ToggleVisibility(t); - PB_Favorite.Visible = t is IFavorite; - PB_BattleVersion.Visible = FLP_BattleVersion.Visible = t is IBattleVersion; - BTN_History.Visible = gen >= 6 && !pb7; - BTN_Ribbons.Visible = gen >= 3 && !pb7; - BTN_Medals.Visible = gen is 6 or 7 && !pb7; - FLP_Country.Visible = FLP_SubRegion.Visible = FLP_3DSRegion.Visible = t is IRegionOrigin; - FLP_OriginalNature.Visible = gen >= 8; - B_Records.Visible = t is ITechRecord8; - B_MoveShop.Visible = t is IMoveShop8Mastery; - CB_HTLanguage.Visible = gen >= 8; - L_AlphaMastered.Visible = CB_AlphaMastered.Visible = t is PA8; - Contest.ToggleInterface(Entity, Entity.Context); - - ToggleInterface(Entity.Format); - } - - private void ToggleSecrets(bool hidden, int gen) - { - Label_EncryptionConstant.Visible = BTN_RerollEC.Visible = TB_EC.Visible = gen >= 6 && !hidden; - BTN_RerollPID.Visible = Label_PID.Visible = TB_PID.Visible = gen >= 3 && !hidden; - TB_HomeTracker.Visible = L_HomeTracker.Visible = gen >= 8 && !hidden; - } - - private void ToggleInterface(int gen) - { - ToggleSecrets(HideSecretValues, gen); - GB_nOT.Visible = GB_RelearnMoves.Visible = gen >= 6; - - PB_Origin.Visible = gen >= 6; - FLP_NSparkle.Visible = L_NSparkle.Visible = CHK_NSparkle.Visible = gen == 5; - - CHK_AsEgg.Visible = GB_EggConditions.Visible = PB_Mark5.Visible = PB_Mark6.Visible = gen >= 4; - FLP_ShinyLeaf.Visible = L_ShinyLeaf.Visible = ShinyLeaf.Visible = gen == 4; - - DEV_Ability.Enabled = DEV_Ability.Visible = gen > 3 && HaX; - CB_Ability.Visible = !DEV_Ability.Enabled && gen >= 3; - FLP_Nature.Visible = gen >= 3; - FLP_Ability.Visible = gen >= 3; - GB_ExtraBytes.Visible = GB_ExtraBytes.Enabled = gen >= 3; - GB_Markings.Visible = gen >= 3; - CB_Form.Enabled = gen >= 3; - FA_Form.Visible = gen >= 6; - - FLP_Friendship.Visible = FLP_Form.Visible = gen >= 2; - FLP_HeldItem.Visible = gen >= 2; - CHK_IsEgg.Visible = gen >= 2; - FLP_PKRS.Visible = FLP_EggPKRSRight.Visible = gen >= 2; - UC_Gender.Visible = gen >= 2; - FLP_CatchRate.Visible = gen == 1; - - // HaX override, needs to be after DEV_Ability enabled assignment. - TB_AbilityNumber.Visible = gen >= 6 && DEV_Ability.Enabled; - - // Met Tab - L_HomeTracker.Visible = TB_HomeTracker.Visible = gen >= 8; - FLP_MetDate.Visible = gen >= 4; - FLP_Fateful.Visible = FLP_Ball.Visible = FLP_OriginGame.Visible = gen >= 3; - FLP_MetLocation.Visible = FLP_MetLevel.Visible = gen >= 2; - FLP_GroundTile.Visible = gen is 4 or 5 or 6; - FLP_TimeOfDay.Visible = gen == 2; - - Stats.ToggleInterface(Entity, gen); - - CenterSubEditors(); - } - - private bool FinalizeInterface(SaveFile sav) - { - FieldsLoaded = false; - - bool TranslationRequired = false; - PopulateFilteredDataSources(sav); - PopulateFields(Entity); - - // Save File Specific Limits - TB_OT.MaxLength = Entity.OTLength; - TB_HT.MaxLength = Entity.OTLength; - TB_Nickname.MaxLength = Entity.NickLength; - - // Hide Unused Tabs - if (Entity.Format == 1 && tabMain.TabPages.Contains(Tab_Met)) - { - tabMain.TabPages.Remove(Tab_Met); - } - else if (Entity.Format != 1 && !tabMain.TabPages.Contains(Tab_Met)) - { - tabMain.TabPages.Insert(1, Tab_Met); - TranslationRequired = true; - } - - if (!HaX && sav is SAV7b) - { - FLP_HeldItem.Visible = false; - FLP_Country.Visible = false; - FLP_SubRegion.Visible = false; - FLP_3DSRegion.Visible = false; - } - - if (!HaX && sav is SAV8LA) - { - FLP_HeldItem.Visible = false; - } - - // pk2 save files do not have an Origin Game stored. Prompt the met location list to update. - if (Entity.Format == 2) - CheckMetLocationChange(GameVersion.C, Entity.Context); - return TranslationRequired; - } - - private void CenterSubEditors() - { - // Recenter PKM SubEditors - var firstTabArea = tabMain.TabPages[0]; // first is always initialized - FLP_PKMEditors.CenterWithin(firstTabArea); - FLP_MoveFlags.CenterWithin(firstTabArea); - } - - public void EnableDragDrop(DragEventHandler enter, DragEventHandler drop) - { - AllowDrop = true; - DragDrop += drop; - foreach (var tab in tabMain.TabPages.OfType()) - { - tab.AllowDrop = true; - tab.DragEnter += enter; - tab.DragDrop += drop; - } - } - - // ReSharper disable once FieldCanBeMadeReadOnly.Global - public Action LoadShowdownSet; - - private void LoadShowdownSetDefault(IBattleTemplate Set) - { - var pk = PreparePKM(); - pk.ApplySetDetails(Set); - PopulateFields(pk); - } - - private void CB_BattleVersion_SelectedValueChanged(object sender, EventArgs e) - { - if (Entity is not IBattleVersion b) - return; - var value = (byte)WinFormsUtil.GetIndex(CB_BattleVersion); - b.BattleVersion = value; - PB_BattleVersion.Image = GetMarkSprite(PB_BattleVersion, value != 0); - } - - private static Image GetMarkSprite(PictureBox p, bool opaque, double trans = 0.175) - { - var sprite = p.InitialImage; - return opaque ? sprite : ImageUtil.ChangeOpacity(sprite, trans); - } - - private void ClickVersionMarking(object sender, EventArgs e) - { - tabMain.SelectedTab = Tab_Met; - if (sender == PB_BattleVersion) - CB_BattleVersion.DroppedDown = true; - else - CB_GameOrigin.DroppedDown = true; - } - - public void ChangeLanguage(ITrainerInfo sav, PKM pk) - { - // Force an update to the met locations - origintrack = GameVersion.Invalid; - - InitializeLanguage(sav); - CenterSubEditors(); - } - - public void FlickerInterface() - { - tabMain.SelectedTab = Tab_Met; // parent tab of CB_GameOrigin - tabMain.SelectedTab = Tab_Main; // first tab - } - - private void InitializeLanguage(ITrainerInfo sav) - { - var source = GameInfo.FilteredSources; - // Set the various ComboBox DataSources up with their allowed entries - SetCountrySubRegion(CB_Country, "countries"); - CB_3DSReg.DataSource = source.ConsoleRegions; - - CB_GroundTile.DataSource = new BindingSource(source.G4GroundTiles, null); - CB_Nature.DataSource = new BindingSource(source.Natures, null); - CB_StatNature.DataSource = new BindingSource(source.Natures, null); - - // Sub editors - Stats.InitializeDataSources(); - - PopulateFilteredDataSources(sav, true); - } - - private static void SetIfDifferentCount(IReadOnlyCollection update, ComboBox exist, bool force = false) - { - if (!force && (exist.DataSource is BindingSource b && b.Count == update.Count)) - return; - exist.DataSource = new BindingSource(update, null); - } - - private void PopulateFilteredDataSources(ITrainerInfo sav, bool force = false) - { - var source = GameInfo.FilteredSources; - SetIfDifferentCount(source.Languages, CB_Language, force); - - if (sav.Generation >= 2) - { - var game = (GameVersion) sav.Game; - if (game <= 0) - game = Entity.Context.GetSingleGameVersion(); - CheckMetLocationChange(game, sav.Context); - SetIfDifferentCount(source.Items, CB_HeldItem, force); - } - - if (sav.Generation >= 3) - { - SetIfDifferentCount(source.Balls, CB_Ball, force); - SetIfDifferentCount(source.Games, CB_GameOrigin, force); - } - - if (sav.Generation >= 4) - SetIfDifferentCount(source.Abilities, DEV_Ability, force); - - if (sav.Generation >= 8) - { - var lang = source.Languages; - var langWith0 = new List(1 + lang.Count) {GameInfo.Sources.Empty}; - langWith0.AddRange(lang); - SetIfDifferentCount(langWith0, CB_HTLanguage, force); - - var game = source.Games; - var gamesWith0 = new List(1 + game.Count) {GameInfo.Sources.Empty}; - gamesWith0.AddRange(game); - SetIfDifferentCount(gamesWith0, CB_BattleVersion, force); - } - SetIfDifferentCount(source.Species, CB_Species, force); - - // Set the Move ComboBoxes too.. - LegalMoveSource.ChangeMoveSource(source.Moves); - foreach (var cb in Moves.Concat(Relearn)) - SetIfDifferentCount(source.Moves, cb, force); - if (sav is SAV8LA) - SetIfDifferentCount(source.Moves, CB_AlphaMastered, force); + BTN_Shinytize.Text = Draw.ShinyUnicode; + TB_Nickname.Font = TB_OT.Font = TB_HT.Font = FontUtil.GetPKXFont(); } } + + internal void UpdateSprite() + { + if (FieldsLoaded && !forceValidation) + UpdatePreviewSprite?.Invoke(this, EventArgs.Empty); + } + + // General Use Functions // + private void SetDetailsOT(ITrainerInfo tr) + { + if (string.IsNullOrWhiteSpace(tr.OT)) + return; + + // Get Save Information + TB_OT.Text = tr.OT; + UC_OTGender.Gender = tr.Gender & 1; + TID_Trainer.LoadInfo(tr); + + if (tr.Game >= 0) + CB_GameOrigin.SelectedValue = tr.Game; + + var lang = tr.Language; + if (lang <= 0) + lang = (int)LanguageID.English; + CB_Language.SelectedValue = lang; + if (tr is IRegionOrigin o) + { + CB_3DSReg.SelectedValue = (int)o.ConsoleRegion; + CB_Country.SelectedValue = (int)o.Country; + CB_SubRegion.SelectedValue = (int)o.Region; + } + + // Copy OT trash bytes for sensitive games (Gen1/2) + if (tr is SAV1 s1 && Entity is PK1 p1) s1.OT_Trash.CopyTo(p1.OT_Trash); + else if (tr is SAV2 s2 && Entity is PK2 p2) s2.OT_Trash.CopyTo(p2.OT_Trash); + + UpdateNickname(this, EventArgs.Empty); + } + + private void SetDetailsHT(ITrainerInfo tr) + { + var trainer = tr.OT; + if (trainer.Length == 0) + return; + + if (!tr.IsOriginalHandler(Entity, false)) + { + TB_HT.Text = trainer; + UC_HTGender.Gender = tr.Gender & 1; + if (Entity is IHandlerLanguage) + CB_HTLanguage.SelectedValue = tr.Language; + } + else if (TB_HT.Text.Length != 0) + { + if (CB_HTLanguage.SelectedIndex == 0 && Entity is IHandlerLanguage) + CB_HTLanguage.SelectedValue = tr.Language; + } + } + + private void SetForms() + { + int species = Entity.Species; + var pi = RequestSaveFile.Personal[species]; + bool hasForms = FormInfo.HasFormSelection(pi, species, Entity.Format); + CB_Form.Enabled = CB_Form.Visible = Label_Form.Visible = hasForms; + + if (HaX && Entity.Format >= 4) + Label_Form.Visible = true; // show with value entry textbox + + if (!hasForms) + { + if (HaX) + return; + Entity.Form = 0; + if (CB_Form.Items.Count > 0) + CB_Form.SelectedIndex = 0; + return; + } + + var ds = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, gendersymbols, Entity.Format); + if (ds.Length == 1 && string.IsNullOrEmpty(ds[0])) // empty (Alolan Totems) + CB_Form.Enabled = CB_Form.Visible = Label_Form.Visible = false; + else + CB_Form.DataSource = ds; + } + + private void SetAbilityList() + { + if (Entity.Format < 3) // no abilities + return; + + if (Entity.Format > 3 && FieldsLoaded) // has forms + Entity.Form = CB_Form.SelectedIndex; // update pk field for form specific abilities + + int abil = CB_Ability.SelectedIndex; + + bool tmp = FieldsLoaded; + FieldsLoaded = false; + CB_Ability.DataSource = GameInfo.FilteredSources.GetAbilityList(Entity); + CB_Ability.SelectedIndex = GetSafeIndex(CB_Ability, abil); // restore original index if available + FieldsLoaded = tmp; + } + + private static int GetSafeIndex(ComboBox cb, int index) => Math.Max(0, Math.Min(cb.Items.Count - 1, index)); + + private void UpdateIsShiny() + { + // Set the Controls + var type = ShinyExtensions.GetType(Entity); + BTN_Shinytize.Visible = BTN_Shinytize.Enabled = type == Shiny.Never; + PB_ShinyStar.Visible = type == Shiny.AlwaysStar; + PB_ShinySquare.Visible = type == Shiny.AlwaysSquare; + + // Refresh Markings (for Shiny Star if applicable) + SetMarkings(); + } + + private void SetMarkings() + { + var pba = Markings; + var count = Entity.MarkingCount; + for (int i = 0; i < pba.Length; i++) + pba[i].Image = GetMarkSprite(pba[i], i < count && Entity.GetMarking(i) != 0); + + PB_MarkShiny.Image = GetMarkSprite(PB_MarkShiny, !BTN_Shinytize.Enabled); + PB_MarkCured.Image = GetMarkSprite(PB_MarkCured, CHK_Cured.Checked); + + PB_Favorite.Image = GetMarkSprite(PB_Favorite, Entity is IFavorite {Favorite: true}); + PB_Origin.Image = GetOriginSprite(Entity); + + // Colored Markings + if (Entity.Format < 7) + return; + + for (int i = 0; i < count; i++) + { + if (!Draw.GetMarkingColor(Entity.GetMarking(i), out Color c)) + continue; + var pb = pba[i]; + pb.Image = ImageUtil.ChangeAllColorTo(pb.Image, c); + } + } + + private static Image? GetOriginSprite(PKM pk) + { + if (pk.Format < 6) + return null; + + // Specific Markings + if (pk.VC) + return Properties.Resources.gen_vc; + if (pk.GO) + return Properties.Resources.gen_go; + if (pk.LGPE) + return Properties.Resources.gen_gg; + + // Lumped Generations + if (pk.Gen6) + return Properties.Resources.gen_6; + if (pk.Gen7) + return Properties.Resources.gen_7; + if (pk.SWSH) + return Properties.Resources.gen_8; + if (pk.BDSP) + return Properties.Resources.gen_bs; + if (pk.LA) + return Properties.Resources.gen_la; + + return null; + } + + private static void SetCountrySubRegion(ComboBox cb, string type) + { + int oldIndex = cb.SelectedIndex; + cb.DataSource = Util.GetCountryRegionList(type, GameInfo.CurrentLanguage); + + if (oldIndex > 0 && oldIndex < cb.Items.Count) + cb.SelectedIndex = oldIndex; + } + + // Prompted Updates of PKM // + private void ClickFriendship(object sender, EventArgs e) + { + var pk = Entity; + bool worst = (ModifierKeys == Keys.Control) ^ pk.IsEgg; + var current = int.Parse(TB_Friendship.Text); + var value = worst + ? pk.IsEgg ? EggStateLegality.GetMinimumEggHatchCycles(pk) : 0 + : pk.IsEgg ? EggStateLegality.GetMaximumEggHatchCycles(pk) : current == 255 ? pk.PersonalInfo.BaseFriendship : 255; + TB_Friendship.Text = value.ToString(); + } + + private void ClickLevel(object sender, EventArgs e) + { + if (ModifierKeys == Keys.Control && sender is TextBoxBase tb) + tb.Text = "100"; + } + + private void ClickGender(object sender, EventArgs e) + { + if (!Entity.PersonalInfo.IsDualGender) + return; // can't toggle + + var (canToggle, gender) = UC_Gender.ToggleGender(); + if (!canToggle) + return; + if (Entity.Format <= 2) + { + Stats.SetATKIVGender(gender); + UpdateIsShiny(); + } + else if (Entity.Format <= 4) + { + Entity.Version = WinFormsUtil.GetIndex(CB_GameOrigin); + Entity.Nature = WinFormsUtil.GetIndex(CB_Nature); + Entity.Form = CB_Form.SelectedIndex; + + Entity.SetPIDGender(gender); + TB_PID.Text = Entity.PID.ToString("X8"); + } + Entity.Gender = gender; + + if (EntityGender.GetFromString(CB_Form.Text) < 2) // Gendered Forms + CB_Form.SelectedIndex = Math.Min(gender, CB_Form.Items.Count - 1); + + UpdatePreviewSprite?.Invoke(UC_Gender, EventArgs.Empty); + } + + private void ClickPP(object sender, EventArgs e) + { + for (int i = 0; i < MovePP.Length; i++) + RefreshMovePP(i); + } + + private void ClickPPUps(object sender, EventArgs e) + { + bool min = (ModifierKeys & Keys.Control) != 0 || !Legal.IsPPUpAvailable(Entity); + static int GetValue(ListControl cb, bool zero) => zero || WinFormsUtil.GetIndex(cb) == 0 ? 0 : 3; + CB_PPu1.SelectedIndex = GetValue(CB_Move1, min); + CB_PPu2.SelectedIndex = GetValue(CB_Move2, min); + CB_PPu3.SelectedIndex = GetValue(CB_Move3, min); + CB_PPu4.SelectedIndex = GetValue(CB_Move4, min); + } + + private void ClickMarking(object sender, EventArgs e) + { + int index = Array.IndexOf(Markings, (PictureBox)sender); + Entity.ToggleMarking(index); + SetMarkings(); + } + + private void ClickFavorite(object sender, EventArgs e) + { + if (Entity is IFavorite pb7) + pb7.Favorite ^= true; + SetMarkings(); + } + + private void ClickOT(object sender, EventArgs e) => SetDetailsOT(SaveFileRequested.Invoke(this, e)); + private void ClickCT(object sender, EventArgs e) => SetDetailsHT(SaveFileRequested.Invoke(this, e)); + + private void ClickBall(object sender, EventArgs e) + { + Entity.Ball = WinFormsUtil.GetIndex(CB_Ball); + if ((ModifierKeys & Keys.Alt) != 0) + { + CB_Ball.SelectedValue = (int)Ball.Poke; + return; + } + if ((ModifierKeys & Keys.Shift) != 0) + { + CB_Ball.SelectedValue = BallApplicator.ApplyBallLegalByColor(Entity); + return; + } + + using var frm = new BallBrowser(); + frm.LoadBalls(Entity); + frm.ShowDialog(); + if (frm.BallChoice >= 0) + CB_Ball.SelectedValue = frm.BallChoice; + } + + private void ClickShinyLeaf(object sender, EventArgs e) => ShinyLeaf.CheckAll(ModifierKeys != Keys.Control); + + private void ClickMetLocation(object sender, EventArgs e) + { + if (HaX) + return; + + Entity = PreparePKM(); + UpdateLegality(skipMoveRepop: true); + if (Legality.Valid) + return; + if (!SetSuggestedMetLocation()) + return; + + Entity = PreparePKM(); + UpdateLegality(); + } + + private void ClickGT(object? sender, EventArgs e) + { + if (!GB_nOT.Visible) + return; + + if (sender == GB_OT) + Entity.CurrentHandler = 0; + else if (TB_HT.Text.Length > 0) + Entity.CurrentHandler = 1; + UpadteHandlingTrainerBackground(Entity.CurrentHandler); + + ReloadToFriendshipTextBox(Entity); + } + + private void ClickNature(object sender, EventArgs e) + { + if (Entity.Format < 8) + return; + if (sender == Label_Nature) + CB_Nature.SelectedIndex = CB_StatNature.SelectedIndex; + else + CB_StatNature.SelectedIndex = CB_Nature.SelectedIndex; + } + + private void ClickMoves(object? sender, EventArgs e) + { + UpdateLegality(skipMoveRepop: true); + if (sender == GB_CurrentMoves) + { + bool random = ModifierKeys == Keys.Control; + if (!SetSuggestedMoves(random)) + return; + } + else if (sender == GB_RelearnMoves) + { + if (!SetSuggestedRelearnMoves()) + return; + } + else + { + return; + } + + UpdateLegality(); + } + + private bool SetSuggestedMoves(bool random = false, bool silent = false) + { + var m = Entity.GetMoveSet(random); + if (m.Length == 0 || m.All(z => z == 0)) + { + if (!silent) + WinFormsUtil.Alert(MsgPKMSuggestionFormat); + return false; + } + + if (Entity.Moves.SequenceEqual(m)) + return false; + + if (!silent) + { + var mv = GameInfo.Strings.Move; + var movestrings = m.Select(v => (uint)v >= mv.Count ? MsgProgramError : mv[v]); + var msg = string.Join(Environment.NewLine, movestrings); + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgPKMSuggestionMoves, msg)) + return false; + } + + Entity.SetMoves(m); + FieldsLoaded = false; + LoadMoves(Entity); + ClickPP(this, EventArgs.Empty); + FieldsLoaded = true; + return true; + } + + private bool SetSuggestedRelearnMoves(bool silent = false) + { + if (Entity.Format < 6) + return false; + + var m = Legality.GetSuggestedRelearnMoves(); + if (Entity.RelearnMoves.SequenceEqual(m) || m.Count != 4) + return false; + + if (!silent) + { + var mv = GameInfo.Strings.Move; + var movestrings = m.Select(v => (uint)v >= mv.Count ? MsgProgramError : mv[v]); + var msg = string.Join(Environment.NewLine, movestrings); + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgPKMSuggestionRelearn, msg)) + return false; + } + + CB_RelearnMove1.SelectedValue = m[0]; + CB_RelearnMove2.SelectedValue = m[1]; + CB_RelearnMove3.SelectedValue = m[2]; + CB_RelearnMove4.SelectedValue = m[3]; + return true; + } + + private bool SetSuggestedMetLocation(bool silent = false) + { + var encounter = EncounterSuggestion.GetSuggestedMetInfo(Entity); + if (encounter == null || (Entity.Format >= 3 && encounter.Location < 0)) + { + if (!silent) + WinFormsUtil.Alert(MsgPKMSuggestionNone); + return false; + } + + int level = encounter.LevelMin; + int location = encounter.Location; + int minlvl = EncounterSuggestion.GetLowestLevel(Entity, encounter.LevelMin); + if (minlvl == 0) + minlvl = level; + + if (Entity.CurrentLevel >= minlvl && Entity.Met_Level == level && Entity.Met_Location == location) + { + if (!encounter.HasGroundTile(Entity.Format) || WinFormsUtil.GetIndex(CB_GroundTile) == (int)encounter.GetSuggestedGroundTile()) + return false; + } + if (minlvl < level) + minlvl = level; + + if (!silent) + { + var suggestions = EntitySuggestionUtil.GetMetLocationSuggestionMessage(Entity, level, location, minlvl); + if (suggestions.Count <= 1) // no suggestion + return false; + + var msg = string.Join(Environment.NewLine, suggestions); + if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, msg) != DialogResult.Yes) + return false; + } + + if (Entity.Format >= 3) + { + Entity.Met_Location = location; + TB_MetLevel.Text = encounter.GetSuggestedMetLevel(Entity).ToString(); + CB_MetLocation.SelectedValue = location; + + if (encounter.HasGroundTile(Entity.Format)) + CB_GroundTile.SelectedValue = (int)encounter.GetSuggestedGroundTile(); + + if (Entity.Gen6 && Entity.WasEgg && ModifyPKM) + Entity.SetHatchMemory6(); + } + + if (Entity.CurrentLevel < minlvl) + TB_Level.Text = minlvl.ToString(); + + return true; + } + + public void UpdateIVsGB(bool skipForm) + { + if (!FieldsLoaded) + return; + UC_Gender.Gender = Entity.Gender; + if (Entity.Species == (int)Species.Unown && !skipForm) + CB_Form.SelectedIndex = Entity.Form; + + UpdateIsShiny(); + UpdateSprite(); + } + + private void UpdateBall(object sender, EventArgs e) + { + PB_Ball.Image = SpriteUtil.GetBallSprite(WinFormsUtil.GetIndex(CB_Ball)); + } + + private void UpdateEXPLevel(object sender, EventArgs e) + { + if (ChangingFields) + return; + ChangingFields = true; + if (sender == TB_EXP) + { + // Change the Level + var input = Util.ToUInt32(TB_EXP.Text); + var exp = input; + var gr = Entity.PersonalInfo.EXPGrowth; + int level = Experience.GetLevel(exp, gr); + if (level == 100) + exp = Experience.GetEXP(100, gr); + + if (level != Util.ToInt32(TB_Level.Text)) + TB_Level.Text = level.ToString(); + if (input != exp && !HaX) + TB_EXP.Text = exp.ToString(); + } + else + { + // Change the XP + int input = Util.ToInt32(TB_Level.Text); + int level = Math.Max(1, Math.Min(input, 100)); + + if (input != level) + TB_Level.Text = level.ToString(); + TB_EXP.Text = Experience.GetEXP(level, Entity.PersonalInfo.EXPGrowth).ToString(); + } + ChangingFields = false; + if (FieldsLoaded) // store values back + Entity.EXP = Util.ToUInt32(TB_EXP.Text); + UpdateStats(); + UpdateLegality(); + } + + private void UpdateRandomPID(object sender, EventArgs e) + { + if (Entity.Format < 3) + return; + if (FieldsLoaded) + Entity.PID = Util.GetHexValue(TB_PID.Text); + + if (sender == UC_Gender) + Entity.SetPIDGender(Entity.Gender); + else if (sender == CB_Nature && Entity.Nature != WinFormsUtil.GetIndex(CB_Nature)) + Entity.SetPIDNature(WinFormsUtil.GetIndex(CB_Nature)); + else if (sender == BTN_RerollPID) + Entity.SetPIDGender(Entity.Gender); + else if (sender == CB_Ability && CB_Ability.SelectedIndex != Entity.PIDAbility && Entity.PIDAbility > -1) + Entity.SetAbilityIndex(CB_Ability.SelectedIndex); + + TB_PID.Text = Entity.PID.ToString("X8"); + if (Entity.Format >= 6 && (Entity.Gen3 || Entity.Gen4 || Entity.Gen5)) + TB_EC.Text = TB_PID.Text; + Update_ID(TB_EC, e); + } + + private void UpdateRandomEC(object sender, EventArgs e) + { + if (Entity.Format < 6) + return; + + Entity.SetRandomEC(); + TB_EC.Text = Entity.EncryptionConstant.ToString("X8"); + Update_ID(TB_EC, e); + UpdateLegality(); + } + + private void Update255_MTB(object sender, EventArgs e) + { + if (sender is not MaskedTextBox tb) + return; + if (Util.ToInt32(tb.Text) > byte.MaxValue) + tb.Text = "255"; + if (sender == TB_Friendship && int.TryParse(TB_Friendship.Text, out var value)) + { + UpdateFromFriendshipTextBox(Entity, value); + UpdateStats(); + } + } + + private void UpdateFormArgument(object sender, EventArgs e) + { + if (FieldsLoaded && Entity.Species == (int)Species.Alcremie) + UpdateSprite(); + } + + private void UpdateForm(object sender, EventArgs e) + { + if (FieldsLoaded && sender == CB_Form) + { + Entity.Form = CB_Form.SelectedIndex; + uint EXP = Experience.GetEXP(Entity.CurrentLevel, Entity.PersonalInfo.EXPGrowth); + TB_EXP.Text = EXP.ToString(); + } + + UpdateStats(); + SetAbilityList(); + + // Gender Forms + if (WinFormsUtil.GetIndex(CB_Species) == (int)Species.Unown && FieldsLoaded) + { + if (Entity.Format == 3) + { + Entity.SetPIDUnown3(CB_Form.SelectedIndex); + TB_PID.Text = Entity.PID.ToString("X8"); + } + else if (Entity.Format == 2) + { + int desiredForm = CB_Form.SelectedIndex; + while (Entity.Form != desiredForm) + { + FieldsLoaded = false; + Stats.UpdateRandomIVs(sender, EventArgs.Empty); + FieldsLoaded = true; + } + } + } + else if (CB_Form.Enabled && EntityGender.GetFromString(CB_Form.Text) < 2) + { + if (CB_Form.Items.Count == 2) // actually M/F; Pumpkaboo formes in German are S,M,L,XL + { + Entity.Gender = CB_Form.SelectedIndex; + UC_Gender.Gender = Entity.GetSaneGender(); + } + } + else + { + UC_Gender.Gender = Entity.GetSaneGender(); + } + + RefreshFormArguments(); + if (ChangingFields) + return; + UpdateSprite(); + } + + private void RefreshFormArguments() + { + if (Entity is not IFormArgument f) + return; + + if (FieldsLoaded) + FA_Form.SaveArgument(f); + FA_Form.LoadArgument(f, Entity.Species, Entity.Form, Entity.Format); + } + + private void UpdatePP(object sender, EventArgs e) + { + if (sender is not ComboBox cb) + return; + int index = Array.IndexOf(Moves, cb); + if (index < 0) + index = Array.IndexOf(PPUps, cb); + if (index < 0) + return; + + RefreshMovePP(index); + } + + private void RefreshMovePP(int index) + { + int move = WinFormsUtil.GetIndex(Moves[index]); + var ppUpControl = PPUps[index]; + int ppUpCount = ppUpControl.SelectedIndex; + if (move <= 0) + { + ppUpControl.SelectedIndex = 0; + MovePP[index].Text = 0.ToString(); + } + else + { + MovePP[index].Text = Entity.GetMovePP(move, ppUpCount).ToString(); + } + } + + private void UpdatePKRSstrain(object sender, EventArgs e) + { + // Change the PKRS Days to the legal bounds. + int currentDuration = CB_PKRSDays.SelectedIndex; + CB_PKRSDays.Items.Clear(); + + var strain = CB_PKRSStrain.SelectedIndex; + int max = Pokerus.GetMaxDuration(strain); + for (int day = 0; day <= max; day++) + CB_PKRSDays.Items.Add(day.ToString()); + + // Set the days back if they're legal + CB_PKRSDays.SelectedIndex = strain == 0 ? 0 : Math.Min(max, currentDuration); + } + + private void UpdatePKRSdays(object sender, EventArgs e) + { + var days = CB_PKRSDays.SelectedIndex; + if (days != 0) + return; + + // If no days are selected + var strain = CB_PKRSStrain.SelectedIndex; + if (Pokerus.IsSusceptible(strain, days)) + CHK_Cured.Checked = CHK_Infected.Checked = false; // No Strain = Never Cured / Infected, triggers Strain update + else if (Pokerus.IsImmune(strain, days)) + CHK_Cured.Checked = true; // Any Strain = Cured + } + + private void UpdatePKRSCured(object sender, EventArgs e) + { + // Cured PokeRus is toggled + if (CHK_Cured.Checked) + { + // Has Had PokeRus + Label_PKRSdays.Visible = CB_PKRSDays.Visible = false; + CB_PKRSDays.SelectedIndex = 0; + + Label_PKRS.Visible = CB_PKRSStrain.Visible = true; + CHK_Infected.Checked = true; + + // If we're cured we have to have a strain infection. + if (CB_PKRSStrain.SelectedIndex == 0) + CB_PKRSStrain.SelectedIndex = 1; + } + else if (!CHK_Infected.Checked) + { + // Not Infected, Disable the other + Label_PKRS.Visible = CB_PKRSStrain.Visible = false; + CB_PKRSStrain.SelectedIndex = 0; + } + else + { + // Still Infected for a duration + Label_PKRSdays.Visible = CB_PKRSDays.Visible = true; + CB_PKRSDays.SelectedValue = 1; + } + // if not cured yet, days > 0 + if (!CHK_Cured.Checked && CHK_Infected.Checked && CB_PKRSDays.SelectedIndex == 0) + CB_PKRSDays.SelectedIndex = 1; + + SetMarkings(); + } + + private void UpdatePKRSInfected(object sender, EventArgs e) + { + if (CHK_Cured.Checked) + { + if (!CHK_Infected.Checked) + CHK_Cured.Checked = false; + return; + } + + Label_PKRS.Visible = CB_PKRSStrain.Visible = CHK_Infected.Checked; + if (!CHK_Infected.Checked) + { + CB_PKRSStrain.SelectedIndex = 0; + CB_PKRSDays.SelectedIndex = 0; + Label_PKRSdays.Visible = CB_PKRSDays.Visible = false; + } + else if (CB_PKRSStrain.SelectedIndex == 0) + { + CB_PKRSStrain.SelectedIndex = CB_PKRSDays.SelectedIndex = 1; + Label_PKRSdays.Visible = CB_PKRSDays.Visible = true; + UpdatePKRSCured(sender, e); + } + } + + private void UpdateCountry(object sender, EventArgs e) + { + int index; + if (sender is ComboBox c && (index = WinFormsUtil.GetIndex(c)) > 0) + SetCountrySubRegion(CB_SubRegion, $"sr_{index:000}"); + } + + private void UpdateSpecies(object sender, EventArgs e) + { + // Get Species dependent information + if (FieldsLoaded) + Entity.Species = WinFormsUtil.GetIndex(CB_Species); + SpeciesIDTip.SetToolTip(CB_Species, Entity.Species.ToString("000")); + SetAbilityList(); + SetForms(); + UpdateForm(sender, EventArgs.Empty); + + if (!FieldsLoaded) + return; + + // Recalculate EXP for Given Level + uint EXP = Experience.GetEXP(Entity.CurrentLevel, Entity.PersonalInfo.EXPGrowth); + TB_EXP.Text = EXP.ToString(); + + // Check for Gender Changes + UC_Gender.Gender = Entity.GetSaneGender(); + + // If species changes and no nickname, set the new name == speciesName. + if (!CHK_Nicknamed.Checked) + UpdateNickname(sender, e); + + UpdateLegality(); + } + + private void UpdateOriginGame(object sender, EventArgs e) + { + GameVersion version = (GameVersion)WinFormsUtil.GetIndex(CB_GameOrigin); + if (version.IsValidSavedVersion()) + { + CheckMetLocationChange(version, Entity.Context); + if (FieldsLoaded) + Entity.Version = (int)version; + } + + // Visibility logic for Gen 4 ground tile; only show for Gen 4 Pokemon. + if (Entity is IGroundTile) + { + bool g4 = Entity.Gen4; + CB_GroundTile.Visible = Label_GroundTile.Visible = g4 && Entity.Format < 7; + if (!g4) + CB_GroundTile.SelectedValue = (int)GroundTileType.None; + } + + if (!FieldsLoaded) + return; + + PB_Origin.Image = GetOriginSprite(Entity); + TID_Trainer.LoadIDValues(Entity); + UpdateLegality(); + } + + private void CheckMetLocationChange(GameVersion version, EntityContext context) + { + // Does the list of locations need to be changed to another group? + var group = GameUtil.GetMetLocationVersionGroup(version); + if (group != origintrack || context != originFormat) + ReloadMetLocations(version, context); + origintrack = group; + originFormat = context; + } + + private void ReloadMetLocations(GameVersion version, EntityContext context) + { + var metList = GameInfo.GetLocationList(version, context, egg: false); + CB_MetLocation.DataSource = new BindingSource(metList, null); + CB_MetLocation.DropDownWidth = GetWidth(metList, CB_MetLocation.Font); + + var eggList = GameInfo.GetLocationList(version, context, egg: true); + CB_EggLocation.DataSource = new BindingSource(eggList, null); + CB_EggLocation.DropDownWidth = GetWidth(eggList, CB_EggLocation.Font); + + static int GetWidth(IEnumerable items, Font f) => + items.Max(z => TextRenderer.MeasureText(z.Text, f).Width) + + SystemInformation.VerticalScrollBarWidth; + + if (FieldsLoaded) + { + SetMarkings(); // Set/Remove the Nativity marking when gamegroup changes too + int metLoc = EncounterSuggestion.GetSuggestedTransferLocation(Entity); + int eggLoc = CHK_AsEgg.Checked + ? EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(Entity, true) + : LocationEdits.GetNoneLocation(Entity); + + CB_MetLocation.SelectedValue = Math.Max(0, metLoc); + CB_EggLocation.SelectedValue = eggLoc; + } + else + { + ValidateChildren(); // hacky validation forcing + } + } + + private void UpdateExtraByteValue(object sender, EventArgs e) + { + if (!FieldsLoaded || CB_ExtraBytes.Items.Count == 0 || sender is not MaskedTextBox mtb) + return; + // Changed Extra Byte's Value + var value = Util.ToInt32(mtb.Text); + if (value > byte.MaxValue) + { + mtb.Text = "255"; + return; // above statement triggers the event again. + } + + int offset = Convert.ToInt32(CB_ExtraBytes.Text, 16); + Entity.Data[offset] = (byte)value; + } + + private void UpdateExtraByteIndex(object sender, EventArgs e) + { + if (CB_ExtraBytes.Items.Count == 0) + return; + // Byte changed, need to refresh the Text box for the byte's value. + var offset = Convert.ToInt32(CB_ExtraBytes.Text, 16); + TB_ExtraByte.Text = Entity.Data[offset].ToString(); + } + + public void ChangeNature(int newNature) + { + if (Entity.Format < 3) + return; + + var cb = Entity.Format >= 8 ? CB_StatNature : CB_Nature; + cb.SelectedValue = newNature; + } + + private void UpdateNatureModification(ComboBox cb, int nature) + { + string text = Stats.UpdateNatureModification(nature); + NatureTip.SetToolTip(cb, text); + } + + private void UpdateIsNicknamed(object sender, EventArgs e) + { + if (!FieldsLoaded) + return; + + Entity.Nickname = TB_Nickname.Text; + if (CHK_Nicknamed.Checked) + return; + + int species = WinFormsUtil.GetIndex(CB_Species); + if (species < 1 || species > Entity.MaxSpeciesID) + return; + + if (CHK_IsEgg.Checked) + species = 0; // get the egg name. + + if (SpeciesName.IsNicknamedAnyLanguage(species, TB_Nickname.Text, Entity.Format)) + CHK_Nicknamed.Checked = true; + } + + private void UpdateNickname(object sender, EventArgs e) + { + if (sender == Label_Species) + { + switch (ModifierKeys) + { + case Keys.Control: RequestShowdownImport?.Invoke(sender, e); return; + case Keys.Alt: RequestShowdownExport?.Invoke(sender, e); return; + default: + return; + } + } + + if (CHK_Nicknamed.Checked) + return; + + // Fetch Current Species and set it as Nickname Text + int species = WinFormsUtil.GetIndex(CB_Species); + if ((uint)(species - 1) >= Entity.MaxSpeciesID) + { TB_Nickname.Text = string.Empty; return; } + + if (CHK_IsEgg.Checked) + species = 0; // get the egg name. + + // If name is that of another language, don't replace the nickname + if (sender != CB_Language && species != 0 && !SpeciesName.IsNicknamedAnyLanguage(species, TB_Nickname.Text, Entity.Format)) + return; + + int lang = WinFormsUtil.GetIndex(CB_Language); + TB_Nickname.Text = SpeciesName.GetSpeciesNameGeneration(species, lang, Entity.Format); + if (Entity is GBPKM pk) + pk.SetNotNicknamed(); + } + + private void UpdateNicknameClick(object sender, MouseEventArgs e) + { + TextBox tb = sender as TextBox ?? TB_Nickname; + // Special Character Form + if (ModifierKeys != Keys.Control) + return; + + var sav = RequestSaveFile; + + if (tb == TB_Nickname) + { + Entity.Nickname = tb.Text; + var span = Entity.Nickname_Trash; + var d = new TrashEditor(tb, span, sav); + d.ShowDialog(); + tb.Text = d.FinalString; + d.FinalBytes.CopyTo(span); + } + else if (tb == TB_OT) + { + Entity.OT_Name = tb.Text; + var span = Entity.OT_Trash; + var d = new TrashEditor(tb, span, sav); + d.ShowDialog(); + tb.Text = d.FinalString; + d.FinalBytes.CopyTo(span); + } + else if (tb == TB_HT) + { + Entity.HT_Name = tb.Text; + var span = Entity.HT_Trash; + var d = new TrashEditor(tb, span, sav); + d.ShowDialog(); + tb.Text = d.FinalString; + d.FinalBytes.CopyTo(span); + } + } + + private void UpdateNotOT(object sender, EventArgs e) + { + if (string.IsNullOrWhiteSpace(TB_HT.Text)) + { + ClickGT(GB_OT, EventArgs.Empty); // Switch CT over to OT. + UC_HTGender.Visible = false; + UC_HTGender.Gender = 0; + ReloadToFriendshipTextBox(Entity); + } + else if (!UC_HTGender.Visible) + { + UC_HTGender.Visible = true; + } + } + + private void UpdateIsEgg(object sender, EventArgs e) + { + // Display hatch counter if it is an egg, Display Friendship if it is not. + Label_HatchCounter.Visible = CHK_IsEgg.Checked && Entity.Format > 1; + Label_Friendship.Visible = !CHK_IsEgg.Checked && Entity.Format > 1; + + if (!FieldsLoaded) + return; + + if (Entity.Format == 3 && CHK_IsEgg.Checked) + Entity.OT_Name = TB_OT.Text; // going to be remapped + + Entity.IsEgg = CHK_IsEgg.Checked; + if (CHK_IsEgg.Checked) + { + TB_Friendship.Text = EggStateLegality.GetMinimumEggHatchCycles(Entity).ToString(); + + // If we are an egg, it won't have a met location. + CHK_AsEgg.Checked = true; + GB_EggConditions.Enabled = true; + + CAL_MetDate.Value = new DateTime(2000, 01, 01); + + // if egg wasn't originally obtained by OT => Link Trade, else => None + if (Entity.Format >= 4) + { + var sav = SaveFileRequested.Invoke(this, e); + bool isTraded = sav.OT != TB_OT.Text || sav.TID != Entity.TID || sav.SID != Entity.SID; + var loc = isTraded ? Locations.TradedEggLocation(sav.Generation, sav.Version) : LocationEdits.GetNoneLocation(Entity); + CB_MetLocation.SelectedValue = loc; + } + else if (Entity.Format == 3) + { + CB_Language.SelectedValue = Entity.Language; // JPN + TB_OT.Text = Entity.OT_Name; + } + + CHK_Nicknamed.Checked = EggStateLegality.IsNicknameFlagSet(Entity); + TB_Nickname.Text = SpeciesName.GetSpeciesNameGeneration(0, WinFormsUtil.GetIndex(CB_Language), Entity.Format); + + // Wipe egg memories + if (Entity.Format >= 6 && ModifyPKM) + Entity.ClearMemories(); + } + else // Not Egg + { + if (!CHK_Nicknamed.Checked) + UpdateNickname(this, EventArgs.Empty); + + TB_Friendship.Text = Entity.PersonalInfo.BaseFriendship.ToString(); + + if (CB_EggLocation.SelectedIndex == 0) + { + CAL_MetDate.Value = DateTime.Now; + CAL_EggDate.Value = new DateTime(2000, 01, 01); + CHK_AsEgg.Checked = false; + GB_EggConditions.Enabled = false; + } + else + { + CAL_MetDate.Value = CAL_EggDate.Value; + CB_MetLocation.SelectedValue = EncounterSuggestion.GetSuggestedEggMetLocation(Entity); + } + + if (TB_Nickname.Text == SpeciesName.GetSpeciesNameGeneration(0, WinFormsUtil.GetIndex(CB_Language), Entity.Format)) + CHK_Nicknamed.Checked = false; + } + + UpdateNickname(this, EventArgs.Empty); + UpdateSprite(); + } + + private void UpdateMetAsEgg(object sender, EventArgs e) + { + GB_EggConditions.Enabled = CHK_AsEgg.Checked; + if (CHK_AsEgg.Checked) + { + if (!FieldsLoaded) + return; + + CAL_EggDate.Value = DateTime.Now; + + bool isTradedEgg = Entity.IsEgg && Entity.Version != (int)RequestSaveFile.Version; + CB_EggLocation.SelectedValue = EncounterSuggestion.GetSuggestedEncounterEggLocationEgg(Entity, isTradedEgg); + return; + } + // Remove egg met data + CHK_IsEgg.Checked = false; + CAL_EggDate.Value = new DateTime(2000, 01, 01); + CB_EggLocation.SelectedValue = LocationEdits.GetNoneLocation(Entity); + + UpdateLegality(); + } + + private void UpdateShinyPID(object sender, EventArgs e) + { + var changePID = Entity.Format >= 3 && (ModifierKeys & Keys.Alt) == 0; + UpdateShiny(changePID); + } + + private void UpdateShiny(bool changePID) + { + Entity.PID = Util.GetHexValue(TB_PID.Text); + Entity.Nature = WinFormsUtil.GetIndex(CB_Nature); + Entity.Gender = UC_Gender.Gender; + Entity.Form = CB_Form.SelectedIndex; + Entity.Version = WinFormsUtil.GetIndex(CB_GameOrigin); + + if (Entity.Format > 2) + { + var type = (ModifierKeys & ~Keys.Alt) switch + { + Keys.Shift => Shiny.AlwaysSquare, + Keys.Control => Shiny.AlwaysStar, + _ => Shiny.Random, + }; + if (changePID) + { + CommonEdits.SetShiny(Entity, type); + TB_PID.Text = Entity.PID.ToString("X8"); + + int gen = Entity.Generation; + bool pre3DS = gen is 3 or 4 or 5; + if (pre3DS && Entity.Format >= 6) + TB_EC.Text = TB_PID.Text; + } + else + { + Entity.SetShinySID(type); + TID_Trainer.UpdateSID(); + } + } + else + { + Entity.SetShiny(); + Stats.LoadIVs(Entity.IVs); + Stats.UpdateIVs(this, EventArgs.Empty); + } + + UpdateIsShiny(); + UpdatePreviewSprite?.Invoke(this, EventArgs.Empty); + UpdateLegality(); + } + + private void UpdateTSV(object sender, EventArgs e) + { + if (Entity.Format <= 2) + return; + + TID_Trainer.UpdateTSV(); + + Entity.PID = Util.GetHexValue(TB_PID.Text); + var tip = $"PSV: {Entity.PSV:d4}"; + if (Entity.IsShiny) + tip += $" | Xor = {Entity.ShinyXor}"; + Tip3.SetToolTip(TB_PID, tip); + } + + private void Update_ID(object? sender, EventArgs e) + { + if (!FieldsLoaded) + return; + // Trim out nonhex characters + TB_PID.Text = (Entity.PID = Util.GetHexValue(TB_PID.Text)).ToString("X8"); + TB_EC.Text = (Entity.EncryptionConstant = Util.GetHexValue(TB_EC.Text)).ToString("X8"); + + UpdateIsShiny(); + UpdateSprite(); + Stats.UpdateCharacteristic(); // If the EC is changed, EC%6 (Characteristic) might be changed. + if (Entity.Format <= 4) + { + FieldsLoaded = false; + Entity.PID = Util.GetHexValue(TB_PID.Text); + CB_Nature.SelectedValue = Entity.Nature; + UC_Gender.Gender = Entity.Gender; + UpdateNatureModification(CB_Nature, Entity.Nature); + FieldsLoaded = true; + } + } + + private void Update_ID64(object sender, EventArgs e) + { + if (!FieldsLoaded) + return; + // Trim out nonhex characters + if (sender == TB_HomeTracker && Entity is IHomeTrack home) + { + var value = Util.GetHexValue64(TB_HomeTracker.Text); + home.Tracker = value; + TB_HomeTracker.Text = value.ToString("X16"); + } + } + + private void UpdateShadowID(object sender, EventArgs e) + { + if (!FieldsLoaded) + return; + FLP_Purification.Visible = NUD_ShadowID.Value > 0; + } + + private void UpdatePurification(object sender, EventArgs e) + { + if (!FieldsLoaded) + return; + FieldsLoaded = false; + var value = NUD_Purification.Value; + CHK_Shadow.Checked = Entity is CK3 ? value != CK3.Purified : value > 0; + FieldsLoaded = true; + } + + private void UpdateShadowCHK(object sender, EventArgs e) + { + if (!FieldsLoaded) + return; + FieldsLoaded = false; + NUD_Purification.Value = CHK_Shadow.Checked ? 1 : Entity is CK3 && NUD_ShadowID.Value != 0 ? CK3.Purified : 0; + ((IShadowPKM)Entity).Purification = (int)NUD_Purification.Value; + UpdatePreviewSprite?.Invoke(this, EventArgs.Empty); + FieldsLoaded = true; + } + + private void ValidateComboBox(ComboBox cb) + { + if (cb.Text.Length == 0 && cb.Items.Count > 0) + cb.SelectedIndex = 0; + else if (cb.SelectedValue == null) + cb.BackColor = Draw.InvalidSelection; + else + cb.ResetBackColor(); + } + + private void ValidateComboBox(object sender, CancelEventArgs e) + { + if (sender is not ComboBox cb) + return; + + ValidateComboBox(cb); + UpdateSprite(); + } + + private void ValidateComboBox2(object sender, EventArgs e) + { + if (!FieldsLoaded) + return; + + ValidateComboBox(sender, new CancelEventArgs()); + if (sender == CB_Ability) + { + if (Entity.Format >= 6) + TB_AbilityNumber.Text = (1 << CB_Ability.SelectedIndex).ToString(); + else if (Entity.Format <= 5 && CB_Ability.SelectedIndex < 2) // Format <= 5, not hidden + UpdateRandomPID(sender, e); + UpdateLegality(); + } + else if (sender == CB_Nature) + { + if (Entity.Format <= 4) + UpdateRandomPID(sender, e); + Entity.Nature = WinFormsUtil.GetIndex(CB_Nature); + UpdateNatureModification(CB_Nature, Entity.Nature); + Stats.UpdateIVs(sender, EventArgs.Empty); // updating Nature will trigger stats to update as well + UpdateLegality(); + } + else if (sender == CB_StatNature) + { + Entity.StatNature = WinFormsUtil.GetIndex(CB_StatNature); + UpdateNatureModification(CB_StatNature, Entity.StatNature); + Stats.UpdateIVs(sender, EventArgs.Empty); // updating Nature will trigger stats to update as well + UpdateLegality(); + } + else if (sender == CB_HeldItem) + { + UpdateLegality(); + } + } + + private void ValidateMove(object sender, EventArgs e) + { + if (!FieldsLoaded || sender is not ComboBox cb) + return; + + ValidateComboBox(cb); + + // Store value back, repopulate legality. + int value = WinFormsUtil.GetIndex(cb); + int index = Array.IndexOf(Moves, cb); + if (index != -1) + { + UpdatePP(sender, e); + Entity.SetMove(index, value); + } + else if ((index = Array.IndexOf(Relearn, cb)) != -1) + { + Entity.SetRelearnMove(index, value); + } + else if (cb == CB_AlphaMastered && Entity is PA8 pa8) + { + pa8.AlphaMove = (ushort)value; + } + else + { + // Shouldn't hit here. + throw new InvalidOperationException(); + } + UpdateLegality(skipMoveRepop: true); + } + + private void ValidateMovePaint(object sender, DrawItemEventArgs e) + { + if (e.Index < 0) + return; + + var (text, value) = (ComboItem)((ComboBox)sender).Items[e.Index]; + var valid = LegalMoveSource.Info.CanLearn(value) && !HaX; + + var current = (e.State & DrawItemState.Selected) != 0; + var brush = Draw.Brushes.GetBackground(valid, current); + var textColor = Draw.GetText(current); + + DrawMoveRectangle(e, brush, text, textColor); + } + + private static void DrawMoveRectangle(DrawItemEventArgs e, Brush brush, string text, Color textColor) + { + var rec = new Rectangle(e.Bounds.X - 1, e.Bounds.Y, e.Bounds.Width + 1, e.Bounds.Height + 0); // 1px left + e.Graphics.FillRectangle(brush, rec); + + const TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.EndEllipsis | TextFormatFlags.ExpandTabs | TextFormatFlags.SingleLine; + TextRenderer.DrawText(e.Graphics, text, e.Font, rec, textColor, flags); + } + + private void MeasureDropDownHeight(object sender, MeasureItemEventArgs e) => e.ItemHeight = CB_RelearnMove1.ItemHeight; + + private void ValidateMoveDropDown(object sender, EventArgs e) + { + var s = (ComboBox) sender; + var index = Array.IndexOf(Moves, s); + + // Populating the combobox drop-down list is deferred until the dropdown is entered into at least once. + // Saves some lag delays when viewing a pk. + if (LegalMoveSource.Display.GetIsMoveBoxOrdered(index)) + return; + SetMoveDataSource(s); + LegalMoveSource.Display.SetIsMoveBoxOrdered(index, true); + } + + private void SetMoveDataSource(ComboBox c) + { + var index = WinFormsUtil.GetIndex(c); + c.DataSource = new BindingSource(LegalMoveSource.Display.DataSource, null); + c.SelectedValue = index; + } + + private void ValidateLocation(object sender, EventArgs e) + { + if (!FieldsLoaded) + return; + + ValidateComboBox((ComboBox)sender); + Entity.Met_Location = WinFormsUtil.GetIndex(CB_MetLocation); + Entity.Egg_Location = WinFormsUtil.GetIndex(CB_EggLocation); + UpdateLegality(); + } + + // Secondary Windows for Ribbons/Amie/Memories + private void OpenRibbons(object sender, EventArgs e) + { + using var form = new RibbonEditor(Entity); + form.ShowDialog(); + } + + private void OpenMedals(object sender, EventArgs e) + { + using var form = new SuperTrainingEditor(Entity); + form.ShowDialog(); + } + + private void OpenHistory(object sender, EventArgs e) + { + // Write back current values + Entity.HT_Name = TB_HT.Text; + Entity.OT_Name = TB_OT.Text; + Entity.IsEgg = CHK_IsEgg.Checked; + UpdateFromFriendshipTextBox(Entity, Util.ToInt32(TB_Friendship.Text)); + using var form = new MemoryAmie(Entity); + form.ShowDialog(); + ReloadToFriendshipTextBox(Entity); + } + + private void B_Records_Click(object sender, EventArgs e) + { + if (Entity is not ITechRecord8 t) + return; + + if (ModifierKeys == Keys.Shift) + { + t.SetRecordFlags(Entity.Moves); + UpdateLegality(); + return; + } + + using var form = new TechRecordEditor(t, Entity); + form.ShowDialog(); + UpdateLegality(); + } + + private void B_MoveShop_Click(object sender, EventArgs e) + { + if (Entity is not IMoveShop8Mastery m) + return; + + if (ModifierKeys == Keys.Shift) + { + m.ClearMoveShopFlags(); + if (Legality.EncounterMatch is IMasteryInitialMoveShop8 enc) + enc.SetInitialMastery(Entity); + m.SetMoveShopFlags(Entity); + UpdateLegality(); + return; + } + + using var form = new MoveShopEditor(m, m, Entity); + form.ShowDialog(); + UpdateLegality(); + } + + /// + /// Refreshes the interface for the current PKM format. + /// + /// Save File context the editor is editing for + /// Pokémon data to edit + public bool ToggleInterface(SaveFile sav, PKM pk) + { + Entity = sav.GetCompatiblePKM(pk); + ToggleInterface(Entity); + return FinalizeInterface(sav); + } + + private void ToggleInterface(PKM t) + { + var pb7 = t is PB7; + int gen = t.Format; + FLP_Purification.Visible = FLP_ShadowID.Visible = t is IShadowPKM; + bool sizeCP = gen >= 8 || pb7; + FLP_SizeCP.Visible = sizeCP; + if (sizeCP) + SizeCP.ToggleVisibility(t); + PB_Favorite.Visible = t is IFavorite; + PB_BattleVersion.Visible = FLP_BattleVersion.Visible = t is IBattleVersion; + BTN_History.Visible = gen >= 6 && !pb7; + BTN_Ribbons.Visible = gen >= 3 && !pb7; + BTN_Medals.Visible = gen is 6 or 7 && !pb7; + FLP_Country.Visible = FLP_SubRegion.Visible = FLP_3DSRegion.Visible = t is IRegionOrigin; + FLP_OriginalNature.Visible = gen >= 8; + B_Records.Visible = t is ITechRecord8; + B_MoveShop.Visible = t is IMoveShop8Mastery; + CB_HTLanguage.Visible = gen >= 8; + L_AlphaMastered.Visible = CB_AlphaMastered.Visible = t is PA8; + Contest.ToggleInterface(Entity, Entity.Context); + + ToggleInterface(Entity.Format); + } + + private void ToggleSecrets(bool hidden, int gen) + { + Label_EncryptionConstant.Visible = BTN_RerollEC.Visible = TB_EC.Visible = gen >= 6 && !hidden; + BTN_RerollPID.Visible = Label_PID.Visible = TB_PID.Visible = gen >= 3 && !hidden; + TB_HomeTracker.Visible = L_HomeTracker.Visible = gen >= 8 && !hidden; + } + + private void ToggleInterface(int gen) + { + ToggleSecrets(HideSecretValues, gen); + GB_nOT.Visible = GB_RelearnMoves.Visible = gen >= 6; + + PB_Origin.Visible = gen >= 6; + FLP_NSparkle.Visible = L_NSparkle.Visible = CHK_NSparkle.Visible = gen == 5; + + CHK_AsEgg.Visible = GB_EggConditions.Visible = PB_Mark5.Visible = PB_Mark6.Visible = gen >= 4; + FLP_ShinyLeaf.Visible = L_ShinyLeaf.Visible = ShinyLeaf.Visible = gen == 4; + + DEV_Ability.Enabled = DEV_Ability.Visible = gen > 3 && HaX; + CB_Ability.Visible = !DEV_Ability.Enabled && gen >= 3; + FLP_Nature.Visible = gen >= 3; + FLP_Ability.Visible = gen >= 3; + GB_ExtraBytes.Visible = GB_ExtraBytes.Enabled = gen >= 3; + GB_Markings.Visible = gen >= 3; + CB_Form.Enabled = gen >= 3; + FA_Form.Visible = gen >= 6; + + FLP_Friendship.Visible = FLP_Form.Visible = gen >= 2; + FLP_HeldItem.Visible = gen >= 2; + CHK_IsEgg.Visible = gen >= 2; + FLP_PKRS.Visible = FLP_EggPKRSRight.Visible = gen >= 2; + UC_Gender.Visible = gen >= 2; + FLP_CatchRate.Visible = gen == 1; + + // HaX override, needs to be after DEV_Ability enabled assignment. + TB_AbilityNumber.Visible = gen >= 6 && DEV_Ability.Enabled; + + // Met Tab + L_HomeTracker.Visible = TB_HomeTracker.Visible = gen >= 8; + FLP_MetDate.Visible = gen >= 4; + FLP_Fateful.Visible = FLP_Ball.Visible = FLP_OriginGame.Visible = gen >= 3; + FLP_MetLocation.Visible = FLP_MetLevel.Visible = gen >= 2; + FLP_GroundTile.Visible = gen is 4 or 5 or 6; + FLP_TimeOfDay.Visible = gen == 2; + + Stats.ToggleInterface(Entity, gen); + + CenterSubEditors(); + } + + private bool FinalizeInterface(SaveFile sav) + { + FieldsLoaded = false; + + bool TranslationRequired = false; + PopulateFilteredDataSources(sav); + PopulateFields(Entity); + + // Save File Specific Limits + TB_OT.MaxLength = Entity.OTLength; + TB_HT.MaxLength = Entity.OTLength; + TB_Nickname.MaxLength = Entity.NickLength; + + // Hide Unused Tabs + if (Entity.Format == 1 && tabMain.TabPages.Contains(Tab_Met)) + { + tabMain.TabPages.Remove(Tab_Met); + } + else if (Entity.Format != 1 && !tabMain.TabPages.Contains(Tab_Met)) + { + tabMain.TabPages.Insert(1, Tab_Met); + TranslationRequired = true; + } + + if (!HaX && sav is SAV7b) + { + FLP_HeldItem.Visible = false; + FLP_Country.Visible = false; + FLP_SubRegion.Visible = false; + FLP_3DSRegion.Visible = false; + } + + if (!HaX && sav is SAV8LA) + { + FLP_HeldItem.Visible = false; + } + + // pk2 save files do not have an Origin Game stored. Prompt the met location list to update. + if (Entity.Format == 2) + CheckMetLocationChange(GameVersion.C, Entity.Context); + return TranslationRequired; + } + + private void CenterSubEditors() + { + // Recenter PKM SubEditors + var firstTabArea = tabMain.TabPages[0]; // first is always initialized + FLP_PKMEditors.HorizontallyCenter(firstTabArea); + FLP_MoveFlags.HorizontallyCenter(firstTabArea); + } + + public void EnableDragDrop(DragEventHandler enter, DragEventHandler drop) + { + AllowDrop = true; + DragDrop += drop; + foreach (var tab in tabMain.TabPages.OfType()) + { + tab.AllowDrop = true; + tab.DragEnter += enter; + tab.DragDrop += drop; + } + } + + // ReSharper disable once FieldCanBeMadeReadOnly.Global + public Action LoadShowdownSet; + + private void LoadShowdownSetDefault(IBattleTemplate Set) + { + var pk = PreparePKM(); + pk.ApplySetDetails(Set); + PopulateFields(pk); + } + + private void CB_BattleVersion_SelectedValueChanged(object sender, EventArgs e) + { + if (Entity is not IBattleVersion b) + return; + var value = (byte)WinFormsUtil.GetIndex(CB_BattleVersion); + b.BattleVersion = value; + PB_BattleVersion.Image = GetMarkSprite(PB_BattleVersion, value != 0); + } + + private static Image GetMarkSprite(PictureBox p, bool opaque, double trans = 0.175) + { + var sprite = p.InitialImage; + return opaque ? sprite : ImageUtil.ChangeOpacity(sprite, trans); + } + + private void ClickVersionMarking(object sender, EventArgs e) + { + tabMain.SelectedTab = Tab_Met; + if (sender == PB_BattleVersion) + CB_BattleVersion.DroppedDown = true; + else + CB_GameOrigin.DroppedDown = true; + } + + public void ChangeLanguage(ITrainerInfo sav) + { + // Force an update to the met locations + origintrack = GameVersion.Invalid; + + InitializeLanguage(sav); + CenterSubEditors(); + } + + public void FlickerInterface() + { + tabMain.SelectedTab = Tab_Met; // parent tab of CB_GameOrigin + tabMain.SelectedTab = Tab_Main; // first tab + } + + private void InitializeLanguage(ITrainerInfo sav) + { + var source = GameInfo.FilteredSources; + // Set the various ComboBox DataSources up with their allowed entries + SetCountrySubRegion(CB_Country, "countries"); + CB_3DSReg.DataSource = source.ConsoleRegions; + + CB_GroundTile.DataSource = new BindingSource(source.G4GroundTiles, null); + CB_Nature.DataSource = new BindingSource(source.Natures, null); + CB_StatNature.DataSource = new BindingSource(source.Natures, null); + + // Sub editors + Stats.InitializeDataSources(); + + PopulateFilteredDataSources(sav, true); + } + + private static void SetIfDifferentCount(IReadOnlyCollection update, ComboBox exist, bool force = false) + { + if (!force && exist.DataSource is BindingSource b && b.Count == update.Count) + return; + exist.DataSource = new BindingSource(update, null); + } + + private void PopulateFilteredDataSources(ITrainerInfo sav, bool force = false) + { + var source = GameInfo.FilteredSources; + SetIfDifferentCount(source.Languages, CB_Language, force); + + if (sav.Generation >= 2) + { + var game = (GameVersion) sav.Game; + if (game <= 0) + game = Entity.Context.GetSingleGameVersion(); + CheckMetLocationChange(game, sav.Context); + SetIfDifferentCount(source.Items, CB_HeldItem, force); + } + + if (sav.Generation >= 3) + { + SetIfDifferentCount(source.Balls, CB_Ball, force); + SetIfDifferentCount(source.Games, CB_GameOrigin, force); + } + + if (sav.Generation >= 4) + SetIfDifferentCount(source.Abilities, DEV_Ability, force); + + if (sav.Generation >= 8) + { + var lang = source.Languages; + var langWith0 = new List(1 + lang.Count) {GameInfo.Sources.Empty}; + langWith0.AddRange(lang); + SetIfDifferentCount(langWith0, CB_HTLanguage, force); + + var game = source.Games; + var gamesWith0 = new List(1 + game.Count) {GameInfo.Sources.Empty}; + gamesWith0.AddRange(game); + SetIfDifferentCount(gamesWith0, CB_BattleVersion, force); + } + SetIfDifferentCount(source.Species, CB_Species, force); + + // Set the Move ComboBoxes too.. + LegalMoveSource.ChangeMoveSource(source.Moves); + foreach (var cb in Moves.Concat(Relearn)) + SetIfDifferentCount(source.Moves, cb, force); + if (sav is SAV8LA) + SetIfDifferentCount(source.Moves, CB_AlphaMastered, force); + } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/ShinyLeaf.cs b/PKHeX.WinForms/Controls/PKM Editor/ShinyLeaf.cs index c54e52eb5..a38489426 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/ShinyLeaf.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/ShinyLeaf.cs @@ -1,62 +1,61 @@ -using System; +using System; using System.Drawing; using System.Linq; using System.Windows.Forms; using PKHeX.Drawing; using PKHeX.WinForms.Properties; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class ShinyLeaf : UserControl { - public partial class ShinyLeaf : UserControl + public ShinyLeaf() { - public ShinyLeaf() + InitializeComponent(); + Flags = new[] { CHK_1, CHK_2, CHK_3, CHK_4, CHK_5, CHK_C }; + } + + private readonly CheckBox[] Flags; + + public void CheckAll(bool all = true) => SetValue(all ? 0b00111111 : 0); + + public int GetValue() + { + int value = 0; + for (int i = 0; i < Flags.Length; i++) { - InitializeComponent(); - Flags = new[] { CHK_1, CHK_2, CHK_3, CHK_4, CHK_5, CHK_C }; + if (Flags[i].Checked) + value |= 1 << i; } + return value; + } - private readonly CheckBox[] Flags; + public void SetValue(int value) + { + for (int i = 0; i < Flags.Length; i++) + Flags[i].Checked = ((value >> i) & 1) == 1; + } - public void CheckAll(bool all = true) => SetValue(all ? 0b00111111 : 0); + private void UpdateFlagState(object sender, EventArgs e) + { + if (sender is not CheckBox c) + return; - public int GetValue() + Image resource; + if (CHK_C == c) { - int value = 0; - for (int i = 0; i < Flags.Length; i++) - { - if (Flags[i].Checked) - value |= 1 << i; - } - return value; + resource = Resources.crown; } - - public void SetValue(int value) + else { - for (int i = 0; i < Flags.Length; i++) - Flags[i].Checked = (value >> i & 1) == 1; - } - - private void UpdateFlagState(object sender, EventArgs e) - { - if (sender is not CheckBox c) - return; - - Image resource; - if (CHK_C == c) - { - resource = Resources.crown; - } - else - { - resource = Resources.leaf; - if (!c.Checked) - CHK_C.Checked = CHK_C.Enabled = false; - else if (Flags.Take(5).All(z => z.Checked)) - CHK_C.Enabled = true; - } + resource = Resources.leaf; if (!c.Checked) - resource = ImageUtil.ChangeOpacity(resource, 0.4); - c.Image = resource; + CHK_C.Checked = CHK_C.Enabled = false; + else if (Flags.Take(5).All(z => z.Checked)) + CHK_C.Enabled = true; } + if (!c.Checked) + resource = ImageUtil.ChangeOpacity(resource, 0.4); + c.Image = resource; } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/SizeCP.cs b/PKHeX.WinForms/Controls/PKM Editor/SizeCP.cs index 9aec6a61d..dbd8060e9 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/SizeCP.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/SizeCP.cs @@ -3,158 +3,157 @@ using System.Windows.Forms; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class SizeCP : UserControl { - public partial class SizeCP : UserControl + private IScaledSize? ss; + private IScaledSizeValue? sv; + private ICombatPower? pk; + private bool Loading; + + public SizeCP() { - private IScaledSize? ss; - private IScaledSizeValue? sv; - private ICombatPower? pkm; - private bool Loading; + InitializeComponent(); + Initialized = true; + } - public SizeCP() - { - InitializeComponent(); - Initialized = true; - } + private readonly bool Initialized; + private static readonly string[] SizeClass = Enum.GetNames(typeof(PokeSize)); - private readonly bool Initialized; - private static readonly string[] SizeClass = Enum.GetNames(typeof(PokeSize)); + public void LoadPKM(PKM entity) + { + pk = entity as ICombatPower; + ss = entity as IScaledSize; + sv = entity as IScaledSizeValue; + if (ss == null) + return; + TryResetStats(); + } - public void LoadPKM(PKM pk) - { - pkm = pk as ICombatPower; - ss = pk as IScaledSize; - sv = pk as IScaledSizeValue; - if (ss == null) - return; - TryResetStats(); - } - - public void TryResetStats() - { - if (!Initialized) - return; - - if (CHK_Auto.Checked) - ResetCalculatedStats(); - LoadStoredValues(); - } - - private void ResetCalculatedStats() - { - sv?.ResetHeight(); - sv?.ResetWeight(); - pkm?.ResetCP(); - } - - private static string GetString(float value) => value.ToString("F6", CultureInfo.InvariantCulture); - - private void LoadStoredValues() - { - Loading = true; - if (ss != null) - { - if (NUD_HeightScalar.Focused || NUD_WeightScalar.Focused) - CHK_Auto.Focus(); - NUD_HeightScalar.Value = ss.HeightScalar; - NUD_WeightScalar.Value = ss.WeightScalar; - } - if (sv != null) - { - TB_HeightAbs.Text = GetString(sv.HeightAbsolute); - TB_WeightAbs.Text = GetString(sv.WeightAbsolute); - } - if (pkm != null) - { - MT_CP.Text = Math.Min(65535, pkm.Stat_CP).ToString(); - } - Loading = false; - } - - private void UpdateFlagState(object sender, EventArgs e) - { - if (!CHK_Auto.Checked) - return; + public void TryResetStats() + { + if (!Initialized) + return; + if (CHK_Auto.Checked) ResetCalculatedStats(); - LoadStoredValues(); - } + LoadStoredValues(); + } - private void MT_CP_TextChanged(object sender, EventArgs e) + private void ResetCalculatedStats() + { + sv?.ResetHeight(); + sv?.ResetWeight(); + pk?.ResetCP(); + } + + private static string GetString(float value) => value.ToString("F6", CultureInfo.InvariantCulture); + + private void LoadStoredValues() + { + Loading = true; + if (ss != null) { - if (pkm != null && int.TryParse(MT_CP.Text, out var cp)) - pkm.Stat_CP = Math.Min(65535, cp); + if (NUD_HeightScalar.Focused || NUD_WeightScalar.Focused) + CHK_Auto.Focus(); + NUD_HeightScalar.Value = ss.HeightScalar; + NUD_WeightScalar.Value = ss.WeightScalar; } - - private void NUD_HeightScalar_ValueChanged(object sender, EventArgs e) + if (sv != null) { - if (ss != null) - { - if (!Loading) - ss.HeightScalar = (byte) NUD_HeightScalar.Value; - L_SizeH.Text = SizeClass[(int)PokeSizeUtil.GetSizeRating(ss.HeightScalar)]; - } - - if (!CHK_Auto.Checked || Loading || sv == null) - return; - sv.ResetHeight(); - sv.ResetWeight(); TB_HeightAbs.Text = GetString(sv.HeightAbsolute); TB_WeightAbs.Text = GetString(sv.WeightAbsolute); - if (sv is PA8 a) - a.HeightScalarCopy = a.HeightScalar; + } + if (pk != null) + { + MT_CP.Text = Math.Min(65535, pk.Stat_CP).ToString(); + } + Loading = false; + } + + private void UpdateFlagState(object sender, EventArgs e) + { + if (!CHK_Auto.Checked) + return; + + ResetCalculatedStats(); + LoadStoredValues(); + } + + private void MT_CP_TextChanged(object sender, EventArgs e) + { + if (pk != null && int.TryParse(MT_CP.Text, out var cp)) + pk.Stat_CP = Math.Min(65535, cp); + } + + private void NUD_HeightScalar_ValueChanged(object sender, EventArgs e) + { + if (ss != null) + { + if (!Loading) + ss.HeightScalar = (byte) NUD_HeightScalar.Value; + L_SizeH.Text = SizeClass[(int)PokeSizeUtil.GetSizeRating(ss.HeightScalar)]; } - private void NUD_WeightScalar_ValueChanged(object sender, EventArgs e) - { - if (ss != null) - { - if (!Loading) - ss.WeightScalar = (byte) NUD_WeightScalar.Value; - L_SizeW.Text = SizeClass[(int)PokeSizeUtil.GetSizeRating(ss.WeightScalar)]; - } + if (!CHK_Auto.Checked || Loading || sv == null) + return; + sv.ResetHeight(); + sv.ResetWeight(); + TB_HeightAbs.Text = GetString(sv.HeightAbsolute); + TB_WeightAbs.Text = GetString(sv.WeightAbsolute); + if (sv is PA8 a) + a.HeightScalarCopy = a.HeightScalar; + } - if (!CHK_Auto.Checked || Loading || sv == null) - return; + private void NUD_WeightScalar_ValueChanged(object sender, EventArgs e) + { + if (ss != null) + { + if (!Loading) + ss.WeightScalar = (byte) NUD_WeightScalar.Value; + L_SizeW.Text = SizeClass[(int)PokeSizeUtil.GetSizeRating(ss.WeightScalar)]; + } + + if (!CHK_Auto.Checked || Loading || sv == null) + return; + sv.ResetWeight(); + TB_WeightAbs.Text = GetString(sv.WeightAbsolute); + } + + private void TB_HeightAbs_TextChanged(object sender, EventArgs e) + { + if (sv == null || Loading) + return; + if (CHK_Auto.Checked) + sv.ResetHeight(); + else if (float.TryParse(TB_HeightAbs.Text, out var result)) + sv.HeightAbsolute = result; + } + + private void TB_WeightAbs_TextChanged(object sender, EventArgs e) + { + if (sv == null || Loading) + return; + if (CHK_Auto.Checked) sv.ResetWeight(); - TB_WeightAbs.Text = GetString(sv.WeightAbsolute); - } + else if (float.TryParse(TB_WeightAbs.Text, out var result)) + sv.WeightAbsolute = result; + } - private void TB_HeightAbs_TextChanged(object sender, EventArgs e) - { - if (sv == null || Loading) - return; - if (CHK_Auto.Checked) - sv.ResetHeight(); - else if (float.TryParse(TB_HeightAbs.Text, out var result)) - sv.HeightAbsolute = result; - } + public void ToggleVisibility(PKM entity) + { + bool isCP = entity is ICombatPower; + bool isAbsolute = entity is IScaledSizeValue; + MT_CP.Visible = L_CP.Visible = isCP; + TB_HeightAbs.Visible = TB_WeightAbs.Visible = isAbsolute; + FLP_CP.Visible = isCP || isAbsolute; // Auto checkbox + } - private void TB_WeightAbs_TextChanged(object sender, EventArgs e) - { - if (sv == null || Loading) - return; - if (CHK_Auto.Checked) - sv.ResetWeight(); - else if (float.TryParse(TB_WeightAbs.Text, out var result)) - sv.WeightAbsolute = result; - } - - public void ToggleVisibility(PKM pk) - { - bool isCP = pk is ICombatPower; - bool isAbsolute = pk is IScaledSizeValue; - MT_CP.Visible = L_CP.Visible = isCP; - TB_HeightAbs.Visible = TB_WeightAbs.Visible = isAbsolute; - FLP_CP.Visible = isCP || isAbsolute; // Auto checkbox - } - - private void ClickScalarEntry(object sender, EventArgs e) - { - if (sender is not NumericUpDown nud || ModifierKeys != Keys.Control) - return; - nud.Value = PokeSizeUtil.GetRandomScalar(); - } + private void ClickScalarEntry(object sender, EventArgs e) + { + if (sender is not NumericUpDown nud || ModifierKeys != Keys.Control) + return; + nud.Value = PokeSizeUtil.GetRandomScalar(); } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/StatEditor.cs b/PKHeX.WinForms/Controls/PKM Editor/StatEditor.cs index 39a29d93b..eb688639d 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/StatEditor.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/StatEditor.cs @@ -5,654 +5,653 @@ using PKHeX.Core; using PKHeX.Drawing; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class StatEditor : UserControl { - public partial class StatEditor : UserControl + public StatEditor() { - public StatEditor() - { - InitializeComponent(); - MT_IVs = new[] {TB_IVHP, TB_IVATK, TB_IVDEF, TB_IVSPE, TB_IVSPA, TB_IVSPD}; - MT_EVs = new[] {TB_EVHP, TB_EVATK, TB_EVDEF, TB_EVSPE, TB_EVSPA, TB_EVSPD}; - MT_AVs = new[] {TB_AVHP, TB_AVATK, TB_AVDEF, TB_AVSPE, TB_AVSPA, TB_AVSPD}; - MT_GVs = new[] {TB_GVHP, TB_GVATK, TB_GVDEF, TB_GVSPE, TB_GVSPA, TB_GVSPD}; - MT_Stats = new[] {Stat_HP, Stat_ATK, Stat_DEF, Stat_SPE, Stat_SPA, Stat_SPD}; - L_Stats = new[] {Label_HP, Label_ATK, Label_DEF, Label_SPE, Label_SPA, Label_SPD}; - MT_Base = new[] {TB_BaseHP, TB_BaseATK, TB_BaseDEF, TB_BaseSPE, TB_BaseSPA, TB_BaseSPD}; + InitializeComponent(); + MT_IVs = new[] {TB_IVHP, TB_IVATK, TB_IVDEF, TB_IVSPE, TB_IVSPA, TB_IVSPD}; + MT_EVs = new[] {TB_EVHP, TB_EVATK, TB_EVDEF, TB_EVSPE, TB_EVSPA, TB_EVSPD}; + MT_AVs = new[] {TB_AVHP, TB_AVATK, TB_AVDEF, TB_AVSPE, TB_AVSPA, TB_AVSPD}; + MT_GVs = new[] {TB_GVHP, TB_GVATK, TB_GVDEF, TB_GVSPE, TB_GVSPA, TB_GVSPD}; + MT_Stats = new[] {Stat_HP, Stat_ATK, Stat_DEF, Stat_SPE, Stat_SPA, Stat_SPD}; + L_Stats = new[] {Label_HP, Label_ATK, Label_DEF, Label_SPE, Label_SPA, Label_SPD}; + MT_Base = new[] {TB_BaseHP, TB_BaseATK, TB_BaseDEF, TB_BaseSPE, TB_BaseSPA, TB_BaseSPD}; - TB_BST.ResetForeColor(); - TB_IVTotal.ForeColor = TB_EVTotal.ForeColor = MT_EVs[0].ForeColor; - } + TB_BST.ResetForeColor(); + TB_IVTotal.ForeColor = TB_EVTotal.ForeColor = MT_EVs[0].ForeColor; + } - public Color EVsInvalid { get; set; } = Color.Red; - public Color EVsMaxed { get; set; } = Color.Honeydew; - public Color EVsFishy { get; set; } = Color.LightYellow; - public Color StatIncreased { get; set; } = Color.Red; - public Color StatDecreased { get; set; } = Color.Blue; - public Color StatHyperTrained { get; set; } = Color.LightGreen; + public Color EVsInvalid { get; set; } = Color.Red; + public Color EVsMaxed { get; set; } = Color.Honeydew; + public Color EVsFishy { get; set; } = Color.LightYellow; + public Color StatIncreased { get; set; } = Color.Red; + public Color StatDecreased { get; set; } = Color.Blue; + public Color StatHyperTrained { get; set; } = Color.LightGreen; - public IMainEditor MainEditor { private get; set; } = null!; - public bool HaX { get => CHK_HackedStats.Enabled; set => CHK_HackedStats.Enabled = CHK_HackedStats.Visible = value; } + public IMainEditor MainEditor { private get; set; } = null!; + public bool HaX { get => CHK_HackedStats.Enabled; set => CHK_HackedStats.Enabled = CHK_HackedStats.Visible = value; } - public bool Valid - { - get - { - if (Entity.Format < 3) - return true; - if (CHK_HackedStats.Checked) - return true; - if (Entity is IAwakened a) - return a.AwakeningAllValid(); - return Convert.ToUInt32(TB_EVTotal.Text) <= 510; - } - } - - private readonly Label[] L_Stats; - private readonly MaskedTextBox[] MT_EVs, MT_IVs, MT_AVs, MT_GVs, MT_Stats, MT_Base; - private PKM Entity => MainEditor.Entity; - - private bool ChangingFields - { - get => MainEditor.ChangingFields; - set => MainEditor.ChangingFields = value; - } - - private void ClickIV(object sender, EventArgs e) - { - if (sender is not MaskedTextBox t) - return; - - switch (ModifierKeys) - { - case Keys.Alt: // Min - t.Text = 0.ToString(); - break; - - case Keys.Control: // Max - { - var index = Array.IndexOf(MT_IVs, t); - t.Text = Entity.GetMaximumIV(index, true).ToString(); - break; - } - - case Keys.Shift when Entity is IHyperTrain h: // HT - { - var index = Array.IndexOf(MT_IVs, t); - bool flag = h.HyperTrainInvert(index); - UpdateHyperTrainingFlag(index, flag); - UpdateStats(); - break; - } - } - } - - private void ClickEV(object sender, EventArgs e) - { - if (sender is not MaskedTextBox t) - return; - - if ((ModifierKeys & Keys.Control) != 0) // Max - { - int index = Array.IndexOf(MT_EVs, t); - int newEV = Entity.GetMaximumEV(index); - t.Text = newEV.ToString(); - } - else if ((ModifierKeys & Keys.Alt) != 0) // Min - { - t.Text = 0.ToString(); - } - } - - private void ClickAV(object sender, EventArgs e) - { - if (sender is not MaskedTextBox t) - return; - - if ((ModifierKeys & Keys.Control) != 0) // Max - { - var max = Legal.AwakeningMax.ToString(); - t.Text = t.Text == max ? 0.ToString() : max; - } - else if ((ModifierKeys & Keys.Alt) != 0) // Min - { - t.Text = 0.ToString(); - } - } - private void ClickGV(object sender, EventArgs e) - { - if (sender is not MaskedTextBox t || Entity is not IGanbaru g) - return; - - if ((ModifierKeys & Keys.Control) != 0) // Max - { - int index = Array.IndexOf(MT_GVs, t); - var max = g.GetMax(Entity, index).ToString(); - t.Text = t.Text == max ? 0.ToString() : max; - } - else if ((ModifierKeys & Keys.Alt) != 0) // Min - { - t.Text = 0.ToString(); - } - } - - public void UpdateIVs(object sender, EventArgs e) - { - if (sender is MaskedTextBox m) - { - int value = Util.ToInt32(m.Text); - if (value > Entity.MaxIV) - { - m.Text = Entity.MaxIV.ToString(); - return; // recursive on text set - } - - int index = Array.IndexOf(MT_IVs, m); - Entity.SetIV(index, value); - if (Entity is IGanbaru g) - RefreshGanbaru(Entity, g, index); - } - RefreshDerivedValues(e); - UpdateStats(); - } - - private void RefreshDerivedValues(object _) + public bool Valid + { + get { if (Entity.Format < 3) - { - TB_IVHP.Text = Entity.IV_HP.ToString(); - TB_IVSPD.Text = Entity.IV_SPD.ToString(); - - MainEditor.UpdateIVsGB(false); - } - - if (!ChangingFields) - { - ChangingFields = true; - CB_HPType.SelectedValue = Entity.HPType; - Label_HiddenPowerPower.Text = Entity.HPPower.ToString(); - ChangingFields = false; - } - - // Potential Reading - L_Potential.Text = Entity.GetPotentialString(MainEditor.Unicode); - - TB_IVTotal.Text = Entity.IVTotal.ToString(); - UpdateCharacteristic(Entity.Characteristic); - } - - private void UpdateEVs(object sender, EventArgs e) - { - if (sender is MaskedTextBox m) - { - int value = Util.ToInt32(m.Text); - if (value > Entity.MaxEV) - { - m.Text = Entity.MaxEV.ToString(); - return; // recursive on text set - } - - int index = Array.IndexOf(MT_EVs, m); - Entity.SetEV(index, value); - } - - UpdateEVTotals(); - - if (Entity.Format < 3) - { - ChangingFields = true; - TB_EVSPD.Text = TB_EVSPA.Text; - ChangingFields = false; - } - - UpdateStats(); - } - - private void UpdateAVs(object sender, EventArgs e) - { - if (Entity is not IAwakened a) - return; - if (sender is MaskedTextBox m) - { - var value = (byte)Math.Min(byte.MaxValue, Util.ToInt32(m.Text)); - if (value > Legal.AwakeningMax) - { - m.Text = Legal.AwakeningMax.ToString(); - return; // recursive on text set - } - - int index = Array.IndexOf(MT_AVs, m); - a.SetAV(index, value); - } - - UpdateAVTotals(); - UpdateStats(); - } - - private void UpdateGVs(object sender, EventArgs e) - { - if (Entity is not IGanbaru g) - return; - if (sender is MaskedTextBox m) - { - int value = Util.ToInt32(m.Text); - if (value > GanbaruExtensions.TrueMax) - { - m.Text = GanbaruExtensions.TrueMax.ToString(); - return; // recursive on text set - } - - int index = Array.IndexOf(MT_GVs, m); - g.SetGV(index, (byte)value); - RefreshGanbaru(Entity, g, index); - } - - UpdateStats(); - } - - private void UpdateRandomEVs(object sender, EventArgs e) - { - Span values = stackalloc int[6]; - switch (ModifierKeys) - { - case Keys.Control: - EffortValues.SetMax(values, Entity); - break; - case Keys.Alt: - EffortValues.Clear(values); - break; - default: - EffortValues.SetRandom(values, Entity.Format); - break; - } - LoadEVs(values); - UpdateEVs(sender, EventArgs.Empty); - } - - private void UpdateHackedStats(object sender, EventArgs e) - { - foreach (var s in MT_Stats) - s.Enabled = CHK_HackedStats.Checked; - if (!CHK_HackedStats.Checked) - UpdateStats(); - } - - private void UpdateHackedStatText(object sender, EventArgs e) - { - if (!CHK_HackedStats.Checked || sender is not TextBox tb) - return; - - string text = tb.Text; - if (string.IsNullOrWhiteSpace(text)) - tb.Text = "0"; - else if (Convert.ToUInt32(text) > ushort.MaxValue) - tb.Text = "65535"; - } - - private void UpdateHyperTrainingFlag(int index, bool value) - { - var tb = MT_IVs[index]; - if (value) - tb.BackColor = StatHyperTrained; - else - tb.ResetBackColor(); - } - - private void UpdateHPType(object sender, EventArgs e) - { - if (ChangingFields) - return; - - // Change IVs to match the new Hidden Power - var ivs = Entity.IVs; - int hpower = WinFormsUtil.GetIndex(CB_HPType); - if (Main.Settings.EntityEditor.HiddenPowerOnChangeMaxPower) - ivs.AsSpan().Fill(Entity.MaxIV); - HiddenPower.SetIVs(hpower, ivs, Entity.Format); - LoadIVs(ivs); - } - - private void ClickStatLabel(object sender, MouseEventArgs e) - { - if (Entity.Format < 3) - return; - - if (ModifierKeys == Keys.None) - return; - - int index = Array.IndexOf(L_Stats, sender as Label) - 1; - if (index < 0) - return; - - var request = ModifierKeys switch - { - Keys.Control => NatureAmpRequest.Neutral, - Keys.Alt => NatureAmpRequest.Decrease, - _ => NatureAmpRequest.Increase, - }; - - var newNature = request.GetNewNature(index, Entity.StatNature); - if (newNature == -1) - return; - - MainEditor.ChangeNature(newNature); - } - - private void LoadHyperTraining() - { - if (Entity is not IHyperTrain h) - { - foreach (var iv in MT_IVs) - iv.ResetBackColor(); - return; - } - - for (int i = 0; i < MT_IVs.Length; i++) - UpdateHyperTrainingFlag(i, h.IsHyperTrained(i)); - } - - private void UpdateAVTotals() - { - if (Entity is not IAwakened a) - return; - var total = a.AwakeningSum(); - TB_AVTotal.Text = total.ToString(); - } - - private void UpdateEVTotals() - { - var evtotal = Entity.EVTotal; - TB_EVTotal.BackColor = GetEVTotalColor(evtotal, TB_IVTotal.BackColor); - TB_EVTotal.Text = evtotal.ToString(); - EVTip.SetToolTip(TB_EVTotal, $"Remaining: {510 - evtotal}"); - } - - private Color GetEVTotalColor(int evtotal, Color defaultColor) => evtotal switch - { - > 510 => EVsInvalid, // Background turns Red - 510 => EVsMaxed, // Maximum EVs - 508 => EVsFishy, // Fishy EVs - _ => defaultColor, - }; - - public void UpdateStats() - { - // Generate the stats. - // Some entity formats don't store stat values regardless of Box/Party/Etc format. - // If its attack stat is zero, we need to generate party stats. - // PK1 format stores Current HP in the compact format, so we have to use attack stat! - if (!CHK_HackedStats.Checked || Entity.Stat_ATK == 0) - { - var pt = MainEditor.RequestSaveFile.Personal; - var pi = pt.GetFormEntry(Entity.Species, Entity.Form); - Entity.SetStats(Entity.GetStats(pi)); - LoadBST(pi); - LoadPartyStats(Entity); - } - } - - private void LoadBST(PersonalInfo pi) - { - var stats = pi.Stats; - for (int i = 0; i < stats.Count; i++) - { - MT_Base[i].Text = stats[i].ToString("000"); - MT_Base[i].BackColor = ColorUtil.ColorBaseStat(stats[i]); - } - var bst = pi.Stats.Sum(); - TB_BST.Text = bst.ToString("000"); - TB_BST.BackColor = ColorUtil.ColorBaseStatTotal(bst); - } - - public void UpdateRandomIVs(object sender, EventArgs e) - { - var IVs = ModifierKeys switch - { - Keys.Control => Entity.SetRandomIVs(6), - Keys.Alt => new int[6], - _ => Entity.SetRandomIVs(), - }; - LoadIVs(IVs); - if (Entity is IGanbaru g) - { - Entity.SetIVs(IVs); - if (ModifierKeys == Keys.Control) - g.SetSuggestedGanbaruValues(Entity); - else if (ModifierKeys == Keys.Alt) - g.ClearGanbaruValues(); - LoadGVs(g); - } - } - - private void UpdateRandomAVs(object sender, EventArgs e) - { - if (Entity is not IAwakened a) - return; - - switch (ModifierKeys) - { - case Keys.Control: - a.SetSuggestedAwakenedValues(Entity); - break; - case Keys.Alt: - a.AwakeningClear(); - break; - default: - a.AwakeningSetRandom(); - break; - } - LoadAVs(a); - } - - public void UpdateCharacteristic() => UpdateCharacteristic(Entity.Characteristic); - - private void UpdateCharacteristic(int characteristic) - { - L_Characteristic.Visible = Label_CharacteristicPrefix.Visible = characteristic > -1; - if (characteristic > -1) - L_Characteristic.Text = GameInfo.Strings.characteristics[characteristic]; - } - - public string UpdateNatureModification(int nature) - { - // Reset Label Colors - foreach (var l in L_Stats) - l.ResetForeColor(); - - // Set Colored StatLabels only if Nature isn't Neutral - var (up, dn) = NatureAmp.GetNatureModification(nature); - if (NatureAmp.IsNeutralOrInvalid(nature, up, dn)) - return "-/-"; - - var incr = L_Stats[up]; - var decr = L_Stats[dn]; - incr.ForeColor = StatIncreased; - decr.ForeColor = StatDecreased; - return $"+{incr.Text} / -{decr.Text}".Replace(":", ""); - } - - public void SetATKIVGender(int gender) - { - Entity.SetAttackIVFromGender(gender); - TB_IVATK.Text = Entity.IV_ATK.ToString(); - } - - public void LoadPartyStats(PKM pk) - { - Stat_HP.Text = pk.Stat_HPCurrent.ToString(); - Stat_ATK.Text = pk.Stat_ATK.ToString(); - Stat_DEF.Text = pk.Stat_DEF.ToString(); - Stat_SPA.Text = pk.Stat_SPA.ToString(); - Stat_SPD.Text = pk.Stat_SPD.ToString(); - Stat_SPE.Text = pk.Stat_SPE.ToString(); - } - - public void SavePartyStats(PKM pk) - { - pk.Stat_HPCurrent = Util.ToInt32(Stat_HP.Text); - pk.Stat_HPMax = Util.ToInt32(Stat_HP.Text); - pk.Stat_ATK = Util.ToInt32(Stat_ATK.Text); - pk.Stat_DEF = Util.ToInt32(Stat_DEF.Text); - pk.Stat_SPE = Util.ToInt32(Stat_SPE.Text); - pk.Stat_SPA = Util.ToInt32(Stat_SPA.Text); - pk.Stat_SPD = Util.ToInt32(Stat_SPD.Text); - if (!HaX) - pk.Stat_Level = pk.CurrentLevel; - } - - public void LoadEVs(ReadOnlySpan EVs) - { - ChangingFields = true; - TB_EVHP.Text = EVs[0].ToString(); - TB_EVATK.Text = EVs[1].ToString(); - TB_EVDEF.Text = EVs[2].ToString(); - TB_EVSPE.Text = EVs[3].ToString(); - TB_EVSPA.Text = EVs[4].ToString(); - TB_EVSPD.Text = EVs[5].ToString(); - ChangingFields = false; - UpdateStats(); - } - - public void LoadIVs(ReadOnlySpan IVs) - { - ChangingFields = true; - TB_IVHP.Text = IVs[0].ToString(); - TB_IVATK.Text = IVs[1].ToString(); - TB_IVDEF.Text = IVs[2].ToString(); - TB_IVSPE.Text = IVs[3].ToString(); - TB_IVSPA.Text = IVs[4].ToString(); - TB_IVSPD.Text = IVs[5].ToString(); - ChangingFields = false; - LoadHyperTraining(); - RefreshDerivedValues(TB_IVSPD); - UpdateStats(); - } - - public void LoadAVs(IAwakened a) - { - ChangingFields = true; - TB_AVHP.Text = a.AV_HP.ToString(); - TB_AVATK.Text = a.AV_ATK.ToString(); - TB_AVDEF.Text = a.AV_DEF.ToString(); - TB_AVSPE.Text = a.AV_SPE.ToString(); - TB_AVSPA.Text = a.AV_SPA.ToString(); - TB_AVSPD.Text = a.AV_SPD.ToString(); - ChangingFields = false; - UpdateStats(); - } - - public void LoadGVs(IGanbaru a) - { - ChangingFields = true; - TB_GVHP.Text = a.GV_HP.ToString(); - TB_GVATK.Text = a.GV_ATK.ToString(); - TB_GVDEF.Text = a.GV_DEF.ToString(); - TB_GVSPE.Text = a.GV_SPE.ToString(); - TB_GVSPA.Text = a.GV_SPA.ToString(); - TB_GVSPD.Text = a.GV_SPD.ToString(); - ChangingFields = false; - for (int i = 0; i < 6; i++) - RefreshGanbaru(Entity, a, i); - UpdateStats(); - } - - private void L_DynamaxLevel_Click(object sender, EventArgs e) - { - var cb = CB_DynamaxLevel; - bool isMin = cb.SelectedIndex == 0; - cb.SelectedIndex = isMin ? cb.Items.Count - 1 : 0; - } - - private void RefreshGanbaru(PKM entity, IGanbaru ganbaru, int i) - { - int current = ganbaru.GetGV(i); - var max = ganbaru.GetMax(entity, i); - var tb = MT_GVs[i]; - if (current > max) - tb.BackColor = EVsInvalid; - else if (current == max) - tb.BackColor = StatHyperTrained; - else - tb.ResetBackColor(); - } - - public void ToggleInterface(PKM pk, int gen) - { - FLP_StatsTotal.Visible = gen >= 3; - FLP_Characteristic.Visible = gen >= 3; - FLP_HPType.Visible = gen <= 7 || pk is PB8; - Label_HiddenPowerPower.Visible = gen <= 5; - FLP_DynamaxLevel.Visible = gen >= 8; - FLP_AlphaNoble.Visible = pk is PA8; - - switch (gen) - { - case 1: - FLP_SpD.Visible = false; - Label_SPA.Visible = false; - Label_SPC.Visible = true; - TB_IVHP.Enabled = false; - SetEVMaskSize(Stat_HP.Size, "00000", MT_EVs); - break; - case 2: - FLP_SpD.Visible = true; - Label_SPA.Visible = true; - Label_SPC.Visible = false; - TB_IVHP.Enabled = false; - SetEVMaskSize(Stat_HP.Size, "00000", MT_EVs); - TB_EVSPD.Enabled = TB_IVSPD.Enabled = false; - break; - default: - FLP_SpD.Visible = true; - Label_SPA.Visible = true; - Label_SPC.Visible = false; - TB_IVHP.Enabled = true; - SetEVMaskSize(TB_EVTotal.Size, "000", MT_EVs); - TB_EVSPD.Enabled = TB_IVSPD.Enabled = true; - break; - } - - var showAV = pk is IAwakened; - Label_AVs.Visible = TB_AVTotal.Visible = BTN_RandomAVs.Visible = showAV; - foreach (var mtb in MT_AVs) - mtb.Visible = showAV; - Label_EVs.Visible = TB_EVTotal.Visible = BTN_RandomEVs.Visible = !showAV; - foreach (var mtb in MT_EVs) - mtb.Visible = !showAV; - - var showGV = pk is IGanbaru; - Label_GVs.Visible = showGV; - foreach (var mtb in MT_GVs) - mtb.Visible = showGV; - - static void SetEVMaskSize(Size s, string Mask, MaskedTextBox[] arr) - { - foreach (var ctrl in arr) - { - ctrl.Size = s; - ctrl.Mask = Mask; - } - } - } - - public void InitializeDataSources() - { - ChangingFields = true; - CB_HPType.InitializeBinding(); - CB_HPType.DataSource = Util.GetCBList(GameInfo.Strings.types.AsSpan(1, 16)); - ChangingFields = false; - } - - private void CHK_Gigantamax_CheckedChanged(object sender, EventArgs e) - { - if (!ChangingFields) - ((PKMEditor) MainEditor).UpdateSprite(); - } - - private void CHK_IsAlpha_CheckedChanged(object sender, EventArgs e) - { - if (!ChangingFields) - ((PKMEditor) MainEditor).UpdateSprite(); + return true; + if (CHK_HackedStats.Checked) + return true; + if (Entity is IAwakened a) + return a.AwakeningAllValid(); + return Convert.ToUInt32(TB_EVTotal.Text) <= 510; } } + + private readonly Label[] L_Stats; + private readonly MaskedTextBox[] MT_EVs, MT_IVs, MT_AVs, MT_GVs, MT_Stats, MT_Base; + private PKM Entity => MainEditor.Entity; + + private bool ChangingFields + { + get => MainEditor.ChangingFields; + set => MainEditor.ChangingFields = value; + } + + private void ClickIV(object sender, EventArgs e) + { + if (sender is not MaskedTextBox t) + return; + + switch (ModifierKeys) + { + case Keys.Alt: // Min + t.Text = 0.ToString(); + break; + + case Keys.Control: // Max + { + var index = Array.IndexOf(MT_IVs, t); + t.Text = Entity.GetMaximumIV(index, true).ToString(); + break; + } + + case Keys.Shift when Entity is IHyperTrain h: // HT + { + var index = Array.IndexOf(MT_IVs, t); + bool flag = h.HyperTrainInvert(index); + UpdateHyperTrainingFlag(index, flag); + UpdateStats(); + break; + } + } + } + + private void ClickEV(object sender, EventArgs e) + { + if (sender is not MaskedTextBox t) + return; + + if ((ModifierKeys & Keys.Control) != 0) // Max + { + int index = Array.IndexOf(MT_EVs, t); + int newEV = Entity.GetMaximumEV(index); + t.Text = newEV.ToString(); + } + else if ((ModifierKeys & Keys.Alt) != 0) // Min + { + t.Text = 0.ToString(); + } + } + + private void ClickAV(object sender, EventArgs e) + { + if (sender is not MaskedTextBox t) + return; + + if ((ModifierKeys & Keys.Control) != 0) // Max + { + var max = Legal.AwakeningMax.ToString(); + t.Text = t.Text == max ? 0.ToString() : max; + } + else if ((ModifierKeys & Keys.Alt) != 0) // Min + { + t.Text = 0.ToString(); + } + } + private void ClickGV(object sender, EventArgs e) + { + if (sender is not MaskedTextBox t || Entity is not IGanbaru g) + return; + + if ((ModifierKeys & Keys.Control) != 0) // Max + { + int index = Array.IndexOf(MT_GVs, t); + var max = g.GetMax(Entity, index).ToString(); + t.Text = t.Text == max ? 0.ToString() : max; + } + else if ((ModifierKeys & Keys.Alt) != 0) // Min + { + t.Text = 0.ToString(); + } + } + + public void UpdateIVs(object sender, EventArgs e) + { + if (sender is MaskedTextBox m) + { + int value = Util.ToInt32(m.Text); + if (value > Entity.MaxIV) + { + m.Text = Entity.MaxIV.ToString(); + return; // recursive on text set + } + + int index = Array.IndexOf(MT_IVs, m); + Entity.SetIV(index, value); + if (Entity is IGanbaru g) + RefreshGanbaru(Entity, g, index); + } + RefreshDerivedValues(e); + UpdateStats(); + } + + private void RefreshDerivedValues(object _) + { + if (Entity.Format < 3) + { + TB_IVHP.Text = Entity.IV_HP.ToString(); + TB_IVSPD.Text = Entity.IV_SPD.ToString(); + + MainEditor.UpdateIVsGB(false); + } + + if (!ChangingFields) + { + ChangingFields = true; + CB_HPType.SelectedValue = Entity.HPType; + Label_HiddenPowerPower.Text = Entity.HPPower.ToString(); + ChangingFields = false; + } + + // Potential Reading + L_Potential.Text = Entity.GetPotentialString(MainEditor.Unicode); + + TB_IVTotal.Text = Entity.IVTotal.ToString(); + UpdateCharacteristic(Entity.Characteristic); + } + + private void UpdateEVs(object sender, EventArgs e) + { + if (sender is MaskedTextBox m) + { + int value = Util.ToInt32(m.Text); + if (value > Entity.MaxEV) + { + m.Text = Entity.MaxEV.ToString(); + return; // recursive on text set + } + + int index = Array.IndexOf(MT_EVs, m); + Entity.SetEV(index, value); + } + + UpdateEVTotals(); + + if (Entity.Format < 3) + { + ChangingFields = true; + TB_EVSPD.Text = TB_EVSPA.Text; + ChangingFields = false; + } + + UpdateStats(); + } + + private void UpdateAVs(object sender, EventArgs e) + { + if (Entity is not IAwakened a) + return; + if (sender is MaskedTextBox m) + { + var value = (byte)Math.Min(byte.MaxValue, Util.ToInt32(m.Text)); + if (value > Legal.AwakeningMax) + { + m.Text = Legal.AwakeningMax.ToString(); + return; // recursive on text set + } + + int index = Array.IndexOf(MT_AVs, m); + a.SetAV(index, value); + } + + UpdateAVTotals(); + UpdateStats(); + } + + private void UpdateGVs(object sender, EventArgs e) + { + if (Entity is not IGanbaru g) + return; + if (sender is MaskedTextBox m) + { + int value = Util.ToInt32(m.Text); + if (value > GanbaruExtensions.TrueMax) + { + m.Text = GanbaruExtensions.TrueMax.ToString(); + return; // recursive on text set + } + + int index = Array.IndexOf(MT_GVs, m); + g.SetGV(index, (byte)value); + RefreshGanbaru(Entity, g, index); + } + + UpdateStats(); + } + + private void UpdateRandomEVs(object sender, EventArgs e) + { + Span values = stackalloc int[6]; + switch (ModifierKeys) + { + case Keys.Control: + EffortValues.SetMax(values, Entity); + break; + case Keys.Alt: + EffortValues.Clear(values); + break; + default: + EffortValues.SetRandom(values, Entity.Format); + break; + } + LoadEVs(values); + UpdateEVs(sender, EventArgs.Empty); + } + + private void UpdateHackedStats(object sender, EventArgs e) + { + foreach (var s in MT_Stats) + s.Enabled = CHK_HackedStats.Checked; + if (!CHK_HackedStats.Checked) + UpdateStats(); + } + + private void UpdateHackedStatText(object sender, EventArgs e) + { + if (!CHK_HackedStats.Checked || sender is not TextBox tb) + return; + + string text = tb.Text; + if (string.IsNullOrWhiteSpace(text)) + tb.Text = "0"; + else if (Convert.ToUInt32(text) > ushort.MaxValue) + tb.Text = "65535"; + } + + private void UpdateHyperTrainingFlag(int index, bool value) + { + var tb = MT_IVs[index]; + if (value) + tb.BackColor = StatHyperTrained; + else + tb.ResetBackColor(); + } + + private void UpdateHPType(object sender, EventArgs e) + { + if (ChangingFields) + return; + + // Change IVs to match the new Hidden Power + var ivs = Entity.IVs; + int hpower = WinFormsUtil.GetIndex(CB_HPType); + if (Main.Settings.EntityEditor.HiddenPowerOnChangeMaxPower) + ivs.AsSpan().Fill(Entity.MaxIV); + HiddenPower.SetIVs(hpower, ivs, Entity.Format); + LoadIVs(ivs); + } + + private void ClickStatLabel(object sender, MouseEventArgs e) + { + if (Entity.Format < 3) + return; + + if (ModifierKeys == Keys.None) + return; + + int index = Array.IndexOf(L_Stats, sender as Label) - 1; + if (index < 0) + return; + + var request = ModifierKeys switch + { + Keys.Control => NatureAmpRequest.Neutral, + Keys.Alt => NatureAmpRequest.Decrease, + _ => NatureAmpRequest.Increase, + }; + + var newNature = request.GetNewNature(index, Entity.StatNature); + if (newNature == -1) + return; + + MainEditor.ChangeNature(newNature); + } + + private void LoadHyperTraining() + { + if (Entity is not IHyperTrain h) + { + foreach (var iv in MT_IVs) + iv.ResetBackColor(); + return; + } + + for (int i = 0; i < MT_IVs.Length; i++) + UpdateHyperTrainingFlag(i, h.IsHyperTrained(i)); + } + + private void UpdateAVTotals() + { + if (Entity is not IAwakened a) + return; + var total = a.AwakeningSum(); + TB_AVTotal.Text = total.ToString(); + } + + private void UpdateEVTotals() + { + var evtotal = Entity.EVTotal; + TB_EVTotal.BackColor = GetEVTotalColor(evtotal, TB_IVTotal.BackColor); + TB_EVTotal.Text = evtotal.ToString(); + EVTip.SetToolTip(TB_EVTotal, $"Remaining: {510 - evtotal}"); + } + + private Color GetEVTotalColor(int evtotal, Color defaultColor) => evtotal switch + { + > 510 => EVsInvalid, // Background turns Red + 510 => EVsMaxed, // Maximum EVs + 508 => EVsFishy, // Fishy EVs + _ => defaultColor, + }; + + public void UpdateStats() + { + // Generate the stats. + // Some entity formats don't store stat values regardless of Box/Party/Etc format. + // If its attack stat is zero, we need to generate party stats. + // PK1 format stores Current HP in the compact format, so we have to use attack stat! + if (!CHK_HackedStats.Checked || Entity.Stat_ATK == 0) + { + var pt = MainEditor.RequestSaveFile.Personal; + var pi = pt.GetFormEntry(Entity.Species, Entity.Form); + Entity.SetStats(Entity.GetStats(pi)); + LoadBST(pi); + LoadPartyStats(Entity); + } + } + + private void LoadBST(PersonalInfo pi) + { + var stats = pi.Stats; + for (int i = 0; i < stats.Count; i++) + { + MT_Base[i].Text = stats[i].ToString("000"); + MT_Base[i].BackColor = ColorUtil.ColorBaseStat(stats[i]); + } + var bst = pi.Stats.Sum(); + TB_BST.Text = bst.ToString("000"); + TB_BST.BackColor = ColorUtil.ColorBaseStatTotal(bst); + } + + public void UpdateRandomIVs(object sender, EventArgs e) + { + var IVs = ModifierKeys switch + { + Keys.Control => Entity.SetRandomIVs(6), + Keys.Alt => new int[6], + _ => Entity.SetRandomIVs(), + }; + LoadIVs(IVs); + if (Entity is IGanbaru g) + { + Entity.SetIVs(IVs); + if (ModifierKeys == Keys.Control) + g.SetSuggestedGanbaruValues(Entity); + else if (ModifierKeys == Keys.Alt) + g.ClearGanbaruValues(); + LoadGVs(g); + } + } + + private void UpdateRandomAVs(object sender, EventArgs e) + { + if (Entity is not IAwakened a) + return; + + switch (ModifierKeys) + { + case Keys.Control: + a.SetSuggestedAwakenedValues(Entity); + break; + case Keys.Alt: + a.AwakeningClear(); + break; + default: + a.AwakeningSetRandom(); + break; + } + LoadAVs(a); + } + + public void UpdateCharacteristic() => UpdateCharacteristic(Entity.Characteristic); + + private void UpdateCharacteristic(int characteristic) + { + L_Characteristic.Visible = Label_CharacteristicPrefix.Visible = characteristic > -1; + if (characteristic > -1) + L_Characteristic.Text = GameInfo.Strings.characteristics[characteristic]; + } + + public string UpdateNatureModification(int nature) + { + // Reset Label Colors + foreach (var l in L_Stats) + l.ResetForeColor(); + + // Set Colored StatLabels only if Nature isn't Neutral + var (up, dn) = NatureAmp.GetNatureModification(nature); + if (NatureAmp.IsNeutralOrInvalid(nature, up, dn)) + return "-/-"; + + var incr = L_Stats[up]; + var decr = L_Stats[dn]; + incr.ForeColor = StatIncreased; + decr.ForeColor = StatDecreased; + return $"+{incr.Text} / -{decr.Text}".Replace(":", ""); + } + + public void SetATKIVGender(int gender) + { + Entity.SetAttackIVFromGender(gender); + TB_IVATK.Text = Entity.IV_ATK.ToString(); + } + + public void LoadPartyStats(PKM pk) + { + Stat_HP.Text = pk.Stat_HPCurrent.ToString(); + Stat_ATK.Text = pk.Stat_ATK.ToString(); + Stat_DEF.Text = pk.Stat_DEF.ToString(); + Stat_SPA.Text = pk.Stat_SPA.ToString(); + Stat_SPD.Text = pk.Stat_SPD.ToString(); + Stat_SPE.Text = pk.Stat_SPE.ToString(); + } + + public void SavePartyStats(PKM pk) + { + pk.Stat_HPCurrent = Util.ToInt32(Stat_HP.Text); + pk.Stat_HPMax = Util.ToInt32(Stat_HP.Text); + pk.Stat_ATK = Util.ToInt32(Stat_ATK.Text); + pk.Stat_DEF = Util.ToInt32(Stat_DEF.Text); + pk.Stat_SPE = Util.ToInt32(Stat_SPE.Text); + pk.Stat_SPA = Util.ToInt32(Stat_SPA.Text); + pk.Stat_SPD = Util.ToInt32(Stat_SPD.Text); + if (!HaX) + pk.Stat_Level = pk.CurrentLevel; + } + + public void LoadEVs(ReadOnlySpan EVs) + { + ChangingFields = true; + TB_EVHP.Text = EVs[0].ToString(); + TB_EVATK.Text = EVs[1].ToString(); + TB_EVDEF.Text = EVs[2].ToString(); + TB_EVSPE.Text = EVs[3].ToString(); + TB_EVSPA.Text = EVs[4].ToString(); + TB_EVSPD.Text = EVs[5].ToString(); + ChangingFields = false; + UpdateStats(); + } + + public void LoadIVs(ReadOnlySpan IVs) + { + ChangingFields = true; + TB_IVHP.Text = IVs[0].ToString(); + TB_IVATK.Text = IVs[1].ToString(); + TB_IVDEF.Text = IVs[2].ToString(); + TB_IVSPE.Text = IVs[3].ToString(); + TB_IVSPA.Text = IVs[4].ToString(); + TB_IVSPD.Text = IVs[5].ToString(); + ChangingFields = false; + LoadHyperTraining(); + RefreshDerivedValues(TB_IVSPD); + UpdateStats(); + } + + public void LoadAVs(IAwakened a) + { + ChangingFields = true; + TB_AVHP.Text = a.AV_HP.ToString(); + TB_AVATK.Text = a.AV_ATK.ToString(); + TB_AVDEF.Text = a.AV_DEF.ToString(); + TB_AVSPE.Text = a.AV_SPE.ToString(); + TB_AVSPA.Text = a.AV_SPA.ToString(); + TB_AVSPD.Text = a.AV_SPD.ToString(); + ChangingFields = false; + UpdateStats(); + } + + public void LoadGVs(IGanbaru a) + { + ChangingFields = true; + TB_GVHP.Text = a.GV_HP.ToString(); + TB_GVATK.Text = a.GV_ATK.ToString(); + TB_GVDEF.Text = a.GV_DEF.ToString(); + TB_GVSPE.Text = a.GV_SPE.ToString(); + TB_GVSPA.Text = a.GV_SPA.ToString(); + TB_GVSPD.Text = a.GV_SPD.ToString(); + ChangingFields = false; + for (int i = 0; i < 6; i++) + RefreshGanbaru(Entity, a, i); + UpdateStats(); + } + + private void L_DynamaxLevel_Click(object sender, EventArgs e) + { + var cb = CB_DynamaxLevel; + bool isMin = cb.SelectedIndex == 0; + cb.SelectedIndex = isMin ? cb.Items.Count - 1 : 0; + } + + private void RefreshGanbaru(PKM entity, IGanbaru ganbaru, int i) + { + int current = ganbaru.GetGV(i); + var max = ganbaru.GetMax(entity, i); + var tb = MT_GVs[i]; + if (current > max) + tb.BackColor = EVsInvalid; + else if (current == max) + tb.BackColor = StatHyperTrained; + else + tb.ResetBackColor(); + } + + public void ToggleInterface(PKM pk, int gen) + { + FLP_StatsTotal.Visible = gen >= 3; + FLP_Characteristic.Visible = gen >= 3; + FLP_HPType.Visible = gen <= 7 || pk is PB8; + Label_HiddenPowerPower.Visible = gen <= 5; + FLP_DynamaxLevel.Visible = gen >= 8; + FLP_AlphaNoble.Visible = pk is PA8; + + switch (gen) + { + case 1: + FLP_SpD.Visible = false; + Label_SPA.Visible = false; + Label_SPC.Visible = true; + TB_IVHP.Enabled = false; + SetEVMaskSize(Stat_HP.Size, "00000", MT_EVs); + break; + case 2: + FLP_SpD.Visible = true; + Label_SPA.Visible = true; + Label_SPC.Visible = false; + TB_IVHP.Enabled = false; + SetEVMaskSize(Stat_HP.Size, "00000", MT_EVs); + TB_EVSPD.Enabled = TB_IVSPD.Enabled = false; + break; + default: + FLP_SpD.Visible = true; + Label_SPA.Visible = true; + Label_SPC.Visible = false; + TB_IVHP.Enabled = true; + SetEVMaskSize(TB_EVTotal.Size, "000", MT_EVs); + TB_EVSPD.Enabled = TB_IVSPD.Enabled = true; + break; + } + + var showAV = pk is IAwakened; + Label_AVs.Visible = TB_AVTotal.Visible = BTN_RandomAVs.Visible = showAV; + foreach (var mtb in MT_AVs) + mtb.Visible = showAV; + Label_EVs.Visible = TB_EVTotal.Visible = BTN_RandomEVs.Visible = !showAV; + foreach (var mtb in MT_EVs) + mtb.Visible = !showAV; + + var showGV = pk is IGanbaru; + Label_GVs.Visible = showGV; + foreach (var mtb in MT_GVs) + mtb.Visible = showGV; + + static void SetEVMaskSize(Size s, string Mask, MaskedTextBox[] arr) + { + foreach (var ctrl in arr) + { + ctrl.Size = s; + ctrl.Mask = Mask; + } + } + } + + public void InitializeDataSources() + { + ChangingFields = true; + CB_HPType.InitializeBinding(); + CB_HPType.DataSource = Util.GetCBList(GameInfo.Strings.types.AsSpan(1, 16)); + ChangingFields = false; + } + + private void CHK_Gigantamax_CheckedChanged(object sender, EventArgs e) + { + if (!ChangingFields) + ((PKMEditor) MainEditor).UpdateSprite(); + } + + private void CHK_IsAlpha_CheckedChanged(object sender, EventArgs e) + { + if (!ChangingFields) + ((PKMEditor) MainEditor).UpdateSprite(); + } } diff --git a/PKHeX.WinForms/Controls/PKM Editor/TrainerID.cs b/PKHeX.WinForms/Controls/PKM Editor/TrainerID.cs index d65fe3eb8..7e3b8e093 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/TrainerID.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/TrainerID.cs @@ -3,169 +3,168 @@ using System.Windows.Forms; using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class TrainerID : UserControl { - public partial class TrainerID : UserControl + public TrainerID() => InitializeComponent(); + public event EventHandler? UpdatedID; + + private int Format = -1; + private ITrainerID Trainer = null!; + + public void UpdateTSV() { - public TrainerID() => InitializeComponent(); - public event EventHandler? UpdatedID; + var tsv = GetTSV(); + if (tsv < 0) + return; - private int Format = -1; - private ITrainerID Trainer = null!; + string IDstr = $"TSV: {tsv:d4}{Environment.NewLine}{GetAlternateRepresentation(Trainer, Format)}"; + TSVTooltip.SetToolTip(TB_TID, IDstr); + TSVTooltip.SetToolTip(TB_SID, IDstr); + TSVTooltip.SetToolTip(TB_TID7, IDstr); + TSVTooltip.SetToolTip(TB_SID7, IDstr); + } - public void UpdateTSV() + private static string GetAlternateRepresentation(ITrainerID tr, int format) + { + if (format >= 7) + return $"ID: {tr.TID:D5}/{tr.SID:D5}"; + var repack = (uint)((tr.SID << 16) | tr.TID); // 32bit + return $"G7ID: ({repack / 1_000_000:D4}){repack % 1_000_000:D6}"; + } + + private int GetTSV() + { + if (Format <= 2) + return -1; + var xor = Trainer.SID ^ Trainer.TID; + if (Format <= 5) + return xor >> 3; + return xor >> 4; + } + + public void LoadIDValues(ITrainerID tr) + { + Trainer = tr; + int format = tr.GetTrainerIDFormat(); + SetFormat(format); + LoadValues(); + } + + public void UpdateSID() => LoadValues(); + + public void LoadInfo(ITrainerInfo info) + { + Trainer.TID = info.TID; + Trainer.SID = info.SID; + LoadValues(); + } + + private void LoadValues() + { + if (Format <= 2) + TB_TID.Text = Trainer.TID.ToString(); + else if (Format <= 6) + LoadTID(Trainer.TID, Trainer.SID); + else + LoadTID7(Trainer.TID, Trainer.SID); + } + + private void LoadTID(int tid, int sid) + { + TB_TID.Text = tid.ToString("D5"); + TB_SID.Text = sid.ToString("D5"); + } + + private void LoadTID7(int tid, int sid) + { + var repack = (uint)((sid << 16) | tid); + sid = (int)(repack / 1_000_000); + tid = (int)(repack % 1_000_000); + + TB_TID7.Text = tid.ToString("D6"); + TB_SID7.Text = sid.ToString("D4"); + } + + private void SetFormat(int format) + { + if (format == Format) + return; + + var controls = GetControlsForFormat(format); + FLP.Controls.Clear(); int i = 0; + foreach (var c in controls) { - var tsv = GetTSV(); - if (tsv < 0) + FLP.Controls.Add(c); + FLP.Controls.SetChildIndex(c, i++); // because you don't listen the first time + } + + Format = format; + } + + private IEnumerable GetControlsForFormat(int format) => format switch + { + >= 7 => new Control[] {Label_SID, TB_SID7, Label_TID, TB_TID7}, + >= 3 => new Control[] {Label_TID, TB_TID, Label_SID, TB_SID }, + _ => new Control[] {Label_TID, TB_TID}, // Gen1/2 + }; + + private void UpdateTSV(object sender, EventArgs e) => UpdateTSV(); + + private void Update_ID(object sender, EventArgs e) + { + if (sender is not MaskedTextBox mt) + return; + + if (!int.TryParse(mt.Text, out var value)) + value = 0; + if (mt == TB_TID7) + { + if (value > 999_999) + { + mt.Text = "999999"; return; - - string IDstr = $"TSV: {tsv:d4}{Environment.NewLine}{GetAlternateRepresentation(Trainer, Format)}"; - TSVTooltip.SetToolTip(TB_TID, IDstr); - TSVTooltip.SetToolTip(TB_SID, IDstr); - TSVTooltip.SetToolTip(TB_TID7, IDstr); - TSVTooltip.SetToolTip(TB_SID7, IDstr); + } + if (!int.TryParse(TB_SID7.Text, out var sid)) + sid = 0; + SanityCheckSID7(value, sid); } - - private static string GetAlternateRepresentation(ITrainerID tr, int format) + else if (mt == TB_SID7) { - if (format >= 7) - return $"ID: {tr.TID:D5}/{tr.SID:D5}"; - var repack = (uint)((tr.SID << 16) | tr.TID); // 32bit - return $"G7ID: ({repack / 1_000_000:D4}){repack % 1_000_000:D6}"; + if (value > 4294) // max 4 digits of 32bit int + { + mt.Text = "4294"; + return; + } + if (!int.TryParse(TB_TID7.Text, out var tid)) + tid = 0; + SanityCheckSID7(tid, value); } - - private int GetTSV() + else { - if (Format <= 2) - return -1; - var xor = Trainer.SID ^ Trainer.TID; - if (Format <= 5) - return xor >> 3; - return xor >> 4; - } + if (value > ushort.MaxValue) // prior to gen7 + mt.Text = (value = ushort.MaxValue).ToString(); - public void LoadIDValues(ITrainerID tr) - { - Trainer = tr; - int format = tr.GetTrainerIDFormat(); - SetFormat(format); - LoadValues(); - } - - public void UpdateSID() => LoadValues(); - - public void LoadInfo(ITrainerInfo info) - { - Trainer.TID = info.TID; - Trainer.SID = info.SID; - LoadValues(); - } - - private void LoadValues() - { - if (Format <= 2) - TB_TID.Text = Trainer.TID.ToString(); - else if (Format <= 6) - LoadTID(Trainer.TID, Trainer.SID); + if (mt == TB_TID) + Trainer.TID = value; else - LoadTID7(Trainer.TID, Trainer.SID); + Trainer.SID = value; } - private void LoadTID(int tid, int sid) + UpdatedID?.Invoke(sender, e); + } + + private void SanityCheckSID7(int tid, int sid) + { + var repack = ((long)sid * 1_000_000) + tid; + if (repack > uint.MaxValue) { - TB_TID.Text = tid.ToString("D5"); - TB_SID.Text = sid.ToString("D5"); + TB_SID7.Text = (sid - 1).ToString(); + return; } - private void LoadTID7(int tid, int sid) - { - var repack = (uint)((sid << 16) | tid); - sid = (int)(repack / 1_000_000); - tid = (int)(repack % 1_000_000); - - TB_TID7.Text = tid.ToString("D6"); - TB_SID7.Text = sid.ToString("D4"); - } - - private void SetFormat(int format) - { - if (format == Format) - return; - - var controls = GetControlsForFormat(format); - FLP.Controls.Clear(); int i = 0; - foreach (var c in controls) - { - FLP.Controls.Add(c); - FLP.Controls.SetChildIndex(c, i++); // because you don't listen the first time - } - - Format = format; - } - - private IEnumerable GetControlsForFormat(int format) => format switch - { - >= 7 => new Control[] {Label_SID, TB_SID7, Label_TID, TB_TID7}, - >= 3 => new Control[] {Label_TID, TB_TID, Label_SID, TB_SID }, - _ => new Control[] {Label_TID, TB_TID}, // Gen1/2 - }; - - private void UpdateTSV(object sender, EventArgs e) => UpdateTSV(); - - private void Update_ID(object sender, EventArgs e) - { - if (sender is not MaskedTextBox mt) - return; - - if (!int.TryParse(mt.Text, out var value)) - value = 0; - if (mt == TB_TID7) - { - if (value > 999_999) - { - mt.Text = "999999"; - return; - } - if (!int.TryParse(TB_SID7.Text, out var sid)) - sid = 0; - SanityCheckSID7(value, sid); - } - else if (mt == TB_SID7) - { - if (value > 4294) // max 4 digits of 32bit int - { - mt.Text = "4294"; - return; - } - if (!int.TryParse(TB_TID7.Text, out var tid)) - tid = 0; - SanityCheckSID7(tid, value); - } - else - { - if (value > ushort.MaxValue) // prior to gen7 - mt.Text = (value = ushort.MaxValue).ToString(); - - if (mt == TB_TID) - Trainer.TID = value; - else - Trainer.SID = value; - } - - UpdatedID?.Invoke(sender, e); - } - - private void SanityCheckSID7(int tid, int sid) - { - var repack = ((long)sid * 1_000_000) + tid; - if (repack > uint.MaxValue) - { - TB_SID7.Text = (sid - 1).ToString(); - return; - } - - Trainer.SID = (ushort)(repack >> 16); - Trainer.TID = (ushort)repack; - } + Trainer.SID = (ushort)(repack >> 16); + Trainer.TID = (ushort)repack; } } diff --git a/PKHeX.WinForms/Controls/SAV Editor/BitmapAnimator.cs b/PKHeX.WinForms/Controls/SAV Editor/BitmapAnimator.cs index 4b3fe3bc9..9b0469f18 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/BitmapAnimator.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/BitmapAnimator.cs @@ -5,115 +5,114 @@ using PKHeX.Drawing; using Timer = System.Timers.Timer; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public sealed class BitmapAnimator : Timer { - public sealed class BitmapAnimator : Timer + public BitmapAnimator() { - public BitmapAnimator() - { - Elapsed += TimerElapsed; - } + Elapsed += TimerElapsed; + } - private int imgWidth; - private int imgHeight; - private byte[]? GlowData; - private Image? ExtraLayer; - private Image?[]? GlowCache; - public Image? OriginalBackground; - private readonly object Lock = new(); + private int imgWidth; + private int imgHeight; + private byte[]? GlowData; + private Image? ExtraLayer; + private Image?[]? GlowCache; + public Image? OriginalBackground; + private readonly object Lock = new(); - private PictureBox? pb; - private int GlowInterval; - private int GlowCounter; + private PictureBox? pb; + private int GlowInterval; + private int GlowCounter; - public int GlowFps { get; set; } = 60; - public Color GlowToColor { get; set; } = Color.LightSkyBlue; - public Color GlowFromColor { get; set; } = Color.White; + public int GlowFps { get; set; } = 60; + public Color GlowToColor { get; set; } = Color.LightSkyBlue; + public Color GlowFromColor { get; set; } = Color.White; - public new static void Start() => throw new ArgumentException(); + public new static void Start() => throw new ArgumentException(); - public new void Stop() - { - if (pb == null || !Enabled) - return; + public new void Stop() + { + if (pb == null || !Enabled) + return; - lock (Lock) - { - Enabled = false; - pb.BackgroundImage = OriginalBackground; - } - - // reset logic - if (GlowCache == null) - throw new ArgumentNullException(nameof(GlowCache)); - GlowCounter = 0; - for (int i = 0; i < GlowCache.Length; i++) - GlowCache[i] = null; - } - - public void Start(PictureBox pbox, Image baseImage, byte[] glowData, Image original, Image extra) + lock (Lock) { Enabled = false; - imgWidth = baseImage.Width; - imgHeight = baseImage.Height; - GlowData = glowData; - GlowCounter = 0; - GlowCache = new Image[GlowFps]; - GlowInterval = 1000 / GlowFps; - Interval = GlowInterval; - lock (Lock) - { - pb = pbox; - ExtraLayer = extra; - OriginalBackground = original; - } - Enabled = true; + pb.BackgroundImage = OriginalBackground; } - private void TimerElapsed(object? sender, ElapsedEventArgs? elapsedEventArgs) + // reset logic + if (GlowCache == null) + throw new ArgumentNullException(nameof(GlowCache)); + GlowCounter = 0; + for (int i = 0; i < GlowCache.Length; i++) + GlowCache[i] = null; + } + + public void Start(PictureBox pbox, Image baseImage, byte[] glowData, Image original, Image extra) + { + Enabled = false; + imgWidth = baseImage.Width; + imgHeight = baseImage.Height; + GlowData = glowData; + GlowCounter = 0; + GlowCache = new Image[GlowFps]; + GlowInterval = 1000 / GlowFps; + Interval = GlowInterval; + lock (Lock) + { + pb = pbox; + ExtraLayer = extra; + OriginalBackground = original; + } + Enabled = true; + } + + private void TimerElapsed(object? sender, ElapsedEventArgs? elapsedEventArgs) + { + if (!Enabled) + return; // timer canceled, was waiting to proceed + GlowCounter = (GlowCounter + 1) % (GlowInterval * 2); // loop backwards + int frameIndex = GlowCounter >= GlowInterval ? (GlowInterval * 2) - GlowCounter : GlowCounter; + + lock (Lock) { if (!Enabled) - return; // timer canceled, was waiting to proceed - GlowCounter = (GlowCounter + 1) % (GlowInterval * 2); // loop backwards - int frameIndex = GlowCounter >= GlowInterval ? (GlowInterval * 2) - GlowCounter : GlowCounter; + return; - lock (Lock) - { - if (!Enabled) - return; - - if (pb == null) - return; - try { pb.BackgroundImage = GetFrame(frameIndex); } // drawing GDI can be silly sometimes #2072 - catch (AccessViolationException ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } - } + if (pb == null) + return; + try { pb.BackgroundImage = GetFrame(frameIndex); } // drawing GDI can be silly sometimes #2072 + catch (AccessViolationException ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } } - - private Image GetFrame(int frameIndex) - { - var cache = GlowCache; - if (cache == null) - throw new NullReferenceException(nameof(GlowCache)); - var frame = cache[frameIndex]; - if (frame != null) - return frame; - - var elapsedFraction = (double)frameIndex / GlowInterval; - var frameColor = GetFrameColor(elapsedFraction); - - if (GlowData == null) - throw new NullReferenceException(nameof(GlowData)); - var frameData = (byte[])GlowData.Clone(); - ImageUtil.ChangeAllColorTo(frameData, frameColor); - - frame = ImageUtil.GetBitmap(frameData, imgWidth, imgHeight); - if (ExtraLayer != null) - frame = ImageUtil.LayerImage(frame, ExtraLayer, 0, 0); - if (OriginalBackground != null) - frame = ImageUtil.LayerImage(OriginalBackground, frame, 0, 0); - return cache[frameIndex] = frame; - } - - private Color GetFrameColor(double elapsedFraction) => ColorUtil.Blend(GlowToColor, GlowFromColor, elapsedFraction); } + + private Image GetFrame(int frameIndex) + { + var cache = GlowCache; + if (cache == null) + throw new NullReferenceException(nameof(GlowCache)); + var frame = cache[frameIndex]; + if (frame != null) + return frame; + + var elapsedFraction = (double)frameIndex / GlowInterval; + var frameColor = GetFrameColor(elapsedFraction); + + if (GlowData == null) + throw new NullReferenceException(nameof(GlowData)); + var frameData = (byte[])GlowData.Clone(); + ImageUtil.ChangeAllColorTo(frameData, frameColor); + + frame = ImageUtil.GetBitmap(frameData, imgWidth, imgHeight); + if (ExtraLayer != null) + frame = ImageUtil.LayerImage(frame, ExtraLayer, 0, 0); + if (OriginalBackground != null) + frame = ImageUtil.LayerImage(OriginalBackground, frame, 0, 0); + return cache[frameIndex] = frame; + } + + private Color GetFrameColor(double elapsedFraction) => ColorUtil.Blend(GlowToColor, GlowFromColor, elapsedFraction); } diff --git a/PKHeX.WinForms/Controls/SAV Editor/BoxEditor.cs b/PKHeX.WinForms/Controls/SAV Editor/BoxEditor.cs index 5e2939f0c..22235110e 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/BoxEditor.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/BoxEditor.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Windows.Forms; using PKHeX.Core; @@ -8,252 +7,253 @@ using PKHeX.Drawing.PokeSprite; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class BoxEditor : UserControl, ISlotViewer { - public partial class BoxEditor : UserControl, ISlotViewer + public IList SlotPictureBoxes { get; private set; } = Array.Empty(); + public SaveFile SAV => M?.SE.SAV ?? throw new ArgumentNullException(nameof(SAV)); + + public int BoxSlotCount { get; private set; } + public SlotChangeManager? M { get; set; } + public bool FlagIllegal { get; set; } + public bool CanSetCurrentBox { get; set; } + + public BoxEdit Editor { get; set; } = null!; + + public BoxEditor() { - public IList SlotPictureBoxes { get; private set; } = Array.Empty(); - public SaveFile SAV => M?.SE.SAV ?? throw new ArgumentNullException(nameof(SAV)); + InitializeComponent(); + } - public int BoxSlotCount { get; private set; } - public SlotChangeManager? M { get; set; } - public bool FlagIllegal { get; set; } - public bool CanSetCurrentBox { get; set; } - - public BoxEdit Editor { get; set; } = null!; - - public BoxEditor() - { - InitializeComponent(); - } - - internal bool InitializeGrid() - { - var count = SAV.BoxSlotCount; - var width = count / 5; - var height = count / width; - if (!BoxPokeGrid.InitializeGrid(width, height, SpriteUtil.Spriter)) - return false; - RecenterControls(); - InitializeSlots(); - return true; - } - - public void RecenterControls() - { - if (Width < BoxPokeGrid.Width) - Width = BoxPokeGrid.Width; - BoxPokeGrid.HorizontallyCenter(this); - int p1 = CB_BoxSelect.Location.X; - CB_BoxSelect.HorizontallyCenter(this); - int p2 = CB_BoxSelect.Location.X; - if (p1 == p2) - return; - - B_BoxLeft.Location = new Point(B_BoxLeft.Location.X + p2 - p1, B_BoxLeft.Location.Y); - B_BoxRight.Location = new Point(B_BoxRight.Location.X + p2 - p1, B_BoxRight.Location.Y); - } - - private void InitializeSlots() - { - SlotPictureBoxes = BoxPokeGrid.Entries; - BoxSlotCount = SlotPictureBoxes.Count; - foreach (var pb in SlotPictureBoxes) - { - pb.MouseEnter += (o, args) => BoxSlot_MouseEnter(pb, args); - pb.MouseLeave += (o, args) => BoxSlot_MouseLeave(pb, args); - pb.MouseClick += BoxSlot_MouseClick; - pb.MouseMove += BoxSlot_MouseMove; - pb.MouseDown += BoxSlot_MouseDown; - pb.MouseUp += BoxSlot_MouseUp; - - pb.DragEnter += BoxSlot_DragEnter; - pb.DragDrop += BoxSlot_DragDrop; - pb.QueryContinueDrag += BoxSlot_QueryContinueDrag; - pb.GiveFeedback += (sender, e) => e.UseDefaultCursors = false; - pb.AllowDrop = true; - } - } - - public void NotifySlotOld(ISlotInfo previous) - { - if (previous is not SlotInfoBox b || b.Box != CurrentBox) - return; - - var pb = SlotPictureBoxes[previous.Slot]; - pb.BackgroundImage = null; - } - - public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pkm) - { - int index = GetViewIndex(slot); - if (index < 0) - return; - - var pb = SlotPictureBoxes[index]; - SlotUtil.UpdateSlot(pb, slot, pkm, SAV, FlagIllegal, type); - } - - public int GetViewIndex(ISlotInfo slot) - { - if (slot is not SlotInfoBox b || b.Box != CurrentBox) - return -1; - return slot.Slot; - } - - public ISlotInfo GetSlotData(PictureBox view) - { - int slot = GetSlot(view); - return new SlotInfoBox(ViewIndex, slot); - } - - private int GetSlot(PictureBox sender) => SlotPictureBoxes.IndexOf(sender); - public int ViewIndex => CurrentBox; - - public bool ControlsVisible - { - get => CB_BoxSelect.Enabled; - set => CB_BoxSelect.Enabled = CB_BoxSelect.Visible = B_BoxLeft.Visible = B_BoxRight.Visible = value; - } - - public bool ControlsEnabled - { - get => CB_BoxSelect.Enabled; - set => CB_BoxSelect.Enabled = B_BoxLeft.Enabled = B_BoxRight.Enabled = value; - } - - public int CurrentBox - { - get => CB_BoxSelect.SelectedIndex; - set - { - CB_BoxSelect.SelectedIndex = value; - if (value < 0) - return; - Editor.LoadBox(value); - } - } - - public string CurrentBoxName => CB_BoxSelect.Text; - - public void Setup(SlotChangeManager m) - { - M = m; - M.Boxes.Add(this); - FlagIllegal = M.SE.FlagIllegal; - } - - public void ResetBoxNames(int box = -1) - { - if (!SAV.HasBox) - return; - - CB_BoxSelect.Items.Clear(); - CB_BoxSelect.Items.AddRange(BoxUtil.GetBoxNames(SAV)); - - if (box < 0 && (uint)SAV.CurrentBox < CB_BoxSelect.Items.Count) - CurrentBox = SAV.CurrentBox; // restore selected box - else - CurrentBox = box; - } - - public void ResetSlots() - { - Editor.Reload(); - int box = CurrentBox; - BoxPokeGrid.SetBackground(SAV.WallpaperImage(box)); - M?.Hover.Stop(); - - int index = box * SAV.BoxSlotCount; - for (int i = 0; i < BoxSlotCount; i++) - { - var pb = SlotPictureBoxes[i]; - if (i >= SAV.BoxSlotCount || index + i >= SAV.SlotCount) - { - pb.Visible = false; - continue; - } - pb.Visible = true; - SlotUtil.UpdateSlot(pb, (SlotInfoBox) GetSlotData(pb), Editor[i], SAV, FlagIllegal); - } - - if (M?.Env.Slots.Publisher.Previous is SlotInfoBox b && b.Box == CurrentBox) - SlotPictureBoxes[b.Slot].BackgroundImage = SlotUtil.GetTouchTypeBackground(M.Env.Slots.Publisher.PreviousType); - } - - public bool SaveBoxBinary() - { - var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, - MsgSaveBoxExportYes + Environment.NewLine + - string.Format(MsgSaveBoxExportNo, CurrentBoxName, CurrentBox + 1) + Environment.NewLine + - MsgSaveBoxExportCancel); - - if (dr == DialogResult.Yes) - { - using var sfd = new SaveFileDialog { Filter = "Box Data|*.bin", FileName = "pcdata.bin" }; - if (sfd.ShowDialog() != DialogResult.OK) - return false; - File.WriteAllBytes(sfd.FileName, SAV.GetPCBinary()); - return true; - } - if (dr == DialogResult.No) - { - using var sfd = new SaveFileDialog { Filter = "Box Data|*.bin", FileName = $"boxdata {CurrentBoxName}.bin" }; - if (sfd.ShowDialog() != DialogResult.OK) - return false; - File.WriteAllBytes(sfd.FileName, SAV.GetBoxBinary(CurrentBox)); - return true; - } + internal bool InitializeGrid() + { + var count = SAV.BoxSlotCount; + var width = count / 5; + var height = count / width; + if (!BoxPokeGrid.InitializeGrid(width, height, SpriteUtil.Spriter)) return false; - } + RecenterControls(); + InitializeSlots(); + return true; + } - public void ClearEvents() + public void RecenterControls() + { + if (Width < BoxPokeGrid.Width) + Width = BoxPokeGrid.Width; + BoxPokeGrid.HorizontallyCenter(this); + int p1 = CB_BoxSelect.Location.X; + CB_BoxSelect.HorizontallyCenter(this); + int p2 = CB_BoxSelect.Location.X; + + var delta = p2 - p1; + if (delta == 0) + return; + + B_BoxLeft.SetBounds(B_BoxLeft.Location.X + delta, 0, 0, 0, BoundsSpecified.X); + B_BoxRight.SetBounds(B_BoxRight.Location.X + delta, 0, 0, 0, BoundsSpecified.X); + } + + private void InitializeSlots() + { + SlotPictureBoxes = BoxPokeGrid.Entries; + BoxSlotCount = SlotPictureBoxes.Count; + foreach (var pb in SlotPictureBoxes) { - B_BoxRight.Click -= ClickBoxRight; - B_BoxLeft.Click -= ClickBoxLeft; - CB_BoxSelect.SelectedIndexChanged -= GetBox; - } + pb.MouseEnter += (o, args) => BoxSlot_MouseEnter(pb, args); + pb.MouseLeave += (o, args) => BoxSlot_MouseLeave(pb, args); + pb.MouseClick += BoxSlot_MouseClick; + pb.MouseMove += BoxSlot_MouseMove; + pb.MouseDown += BoxSlot_MouseDown; + pb.MouseUp += BoxSlot_MouseUp; - public void Reset() - { - ResetBoxNames(); - ResetSlots(); - } - - private void GetBox(object? sender, EventArgs e) - { - CurrentBox = CB_BoxSelect.SelectedIndex; - if (SAV.CurrentBox != CurrentBox && CanSetCurrentBox) - SAV.CurrentBox = CurrentBox; - ResetSlots(); - M?.Hover.Stop(); - } - - private void ClickBoxLeft(object? sender, EventArgs e) => CurrentBox = Editor.MoveLeft(ModifierKeys == Keys.Control); - private void ClickBoxRight(object? sender, EventArgs e) => CurrentBox = Editor.MoveRight(ModifierKeys == Keys.Control); - - // Drag & Drop Handling - private void BoxSlot_MouseEnter(object? sender, EventArgs e) => M?.MouseEnter(sender, e); - private void BoxSlot_MouseLeave(object? sender, EventArgs e) => M?.MouseLeave(sender, e); - private void BoxSlot_MouseClick(object? sender, MouseEventArgs e) => M?.MouseClick(sender, e); - private void BoxSlot_MouseUp(object? sender, MouseEventArgs e) => M?.MouseUp(sender, e); - private void BoxSlot_MouseDown(object? sender, MouseEventArgs e) => M?.MouseDown(sender, e); - private void BoxSlot_MouseMove(object? sender, MouseEventArgs e) => M?.MouseMove(sender, e); - private void BoxSlot_DragEnter(object? sender, DragEventArgs e) => M?.DragEnter(sender, e); - private void BoxSlot_QueryContinueDrag(object? sender, QueryContinueDragEventArgs e) => M?.QueryContinueDrag(sender, e); - private void BoxSlot_DragDrop(object? sender, DragEventArgs e) => M?.DragDrop(sender, e); - - public bool InitializeFromSAV(SaveFile sav) - { - Editor = new BoxEdit(sav); - bool result = InitializeGrid(); - - int box = sav.CurrentBox; - if ((uint)box >= sav.BoxCount) - box = 0; - Editor.LoadBox(box); - ResetBoxNames(); // Display the Box Names - return result; + pb.DragEnter += BoxSlot_DragEnter; + pb.DragDrop += BoxSlot_DragDrop; + pb.QueryContinueDrag += BoxSlot_QueryContinueDrag; + pb.GiveFeedback += (sender, e) => e.UseDefaultCursors = false; + pb.AllowDrop = true; } } + + public void NotifySlotOld(ISlotInfo previous) + { + if (previous is not SlotInfoBox b || b.Box != CurrentBox) + return; + + var pb = SlotPictureBoxes[previous.Slot]; + pb.BackgroundImage = null; + } + + public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pk) + { + int index = GetViewIndex(slot); + if (index < 0) + return; + + var pb = SlotPictureBoxes[index]; + SlotUtil.UpdateSlot(pb, slot, pk, SAV, FlagIllegal, type); + } + + public int GetViewIndex(ISlotInfo slot) + { + if (slot is not SlotInfoBox b || b.Box != CurrentBox) + return -1; + return slot.Slot; + } + + public ISlotInfo GetSlotData(PictureBox view) + { + int slot = GetSlot(view); + return new SlotInfoBox(ViewIndex, slot); + } + + private int GetSlot(PictureBox sender) => SlotPictureBoxes.IndexOf(sender); + public int ViewIndex => CurrentBox; + + public bool ControlsVisible + { + get => CB_BoxSelect.Enabled; + set => CB_BoxSelect.Enabled = CB_BoxSelect.Visible = B_BoxLeft.Visible = B_BoxRight.Visible = value; + } + + public bool ControlsEnabled + { + get => CB_BoxSelect.Enabled; + set => CB_BoxSelect.Enabled = B_BoxLeft.Enabled = B_BoxRight.Enabled = value; + } + + public int CurrentBox + { + get => CB_BoxSelect.SelectedIndex; + set + { + CB_BoxSelect.SelectedIndex = value; + if (value < 0) + return; + Editor.LoadBox(value); + } + } + + public string CurrentBoxName => CB_BoxSelect.Text; + + public void Setup(SlotChangeManager m) + { + M = m; + M.Boxes.Add(this); + FlagIllegal = M.SE.FlagIllegal; + } + + public void ResetBoxNames(int box = -1) + { + if (!SAV.HasBox) + return; + + CB_BoxSelect.Items.Clear(); + CB_BoxSelect.Items.AddRange(BoxUtil.GetBoxNames(SAV)); + + if (box < 0 && (uint)SAV.CurrentBox < CB_BoxSelect.Items.Count) + CurrentBox = SAV.CurrentBox; // restore selected box + else + CurrentBox = box; + } + + public void ResetSlots() + { + Editor.Reload(); + int box = CurrentBox; + BoxPokeGrid.SetBackground(SAV.WallpaperImage(box)); + M?.Hover.Stop(); + + int index = box * SAV.BoxSlotCount; + for (int i = 0; i < BoxSlotCount; i++) + { + var pb = SlotPictureBoxes[i]; + if (i >= SAV.BoxSlotCount || index + i >= SAV.SlotCount) + { + pb.Visible = false; + continue; + } + pb.Visible = true; + SlotUtil.UpdateSlot(pb, (SlotInfoBox) GetSlotData(pb), Editor[i], SAV, FlagIllegal); + } + + if (M?.Env.Slots.Publisher.Previous is SlotInfoBox b && b.Box == CurrentBox) + SlotPictureBoxes[b.Slot].BackgroundImage = SlotUtil.GetTouchTypeBackground(M.Env.Slots.Publisher.PreviousType); + } + + public bool SaveBoxBinary() + { + var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, + MsgSaveBoxExportYes + Environment.NewLine + + string.Format(MsgSaveBoxExportNo, CurrentBoxName, CurrentBox + 1) + Environment.NewLine + + MsgSaveBoxExportCancel); + + if (dr == DialogResult.Yes) + { + using var sfd = new SaveFileDialog { Filter = "Box Data|*.bin", FileName = "pcdata.bin" }; + if (sfd.ShowDialog() != DialogResult.OK) + return false; + File.WriteAllBytes(sfd.FileName, SAV.GetPCBinary()); + return true; + } + if (dr == DialogResult.No) + { + using var sfd = new SaveFileDialog { Filter = "Box Data|*.bin", FileName = $"boxdata {CurrentBoxName}.bin" }; + if (sfd.ShowDialog() != DialogResult.OK) + return false; + File.WriteAllBytes(sfd.FileName, SAV.GetBoxBinary(CurrentBox)); + return true; + } + return false; + } + + public void ClearEvents() + { + B_BoxRight.Click -= ClickBoxRight; + B_BoxLeft.Click -= ClickBoxLeft; + CB_BoxSelect.SelectedIndexChanged -= GetBox; + } + + public void Reset() + { + ResetBoxNames(); + ResetSlots(); + } + + private void GetBox(object? sender, EventArgs e) + { + CurrentBox = CB_BoxSelect.SelectedIndex; + if (SAV.CurrentBox != CurrentBox && CanSetCurrentBox) + SAV.CurrentBox = CurrentBox; + ResetSlots(); + M?.Hover.Stop(); + } + + private void ClickBoxLeft(object? sender, EventArgs e) => CurrentBox = Editor.MoveLeft(ModifierKeys == Keys.Control); + private void ClickBoxRight(object? sender, EventArgs e) => CurrentBox = Editor.MoveRight(ModifierKeys == Keys.Control); + + // Drag & Drop Handling + private void BoxSlot_MouseEnter(object? sender, EventArgs e) => M?.MouseEnter(sender, e); + private void BoxSlot_MouseLeave(object? sender, EventArgs e) => M?.MouseLeave(sender, e); + private void BoxSlot_MouseClick(object? sender, MouseEventArgs e) => M?.MouseClick(sender, e); + private void BoxSlot_MouseUp(object? sender, MouseEventArgs e) => M?.MouseUp(sender, e); + private void BoxSlot_MouseDown(object? sender, MouseEventArgs e) => M?.MouseDown(sender, e); + private void BoxSlot_MouseMove(object? sender, MouseEventArgs e) => M?.MouseMove(sender, e); + private void BoxSlot_DragEnter(object? sender, DragEventArgs e) => M?.DragEnter(sender, e); + private void BoxSlot_QueryContinueDrag(object? sender, QueryContinueDragEventArgs e) => M?.QueryContinueDrag(sender, e); + private void BoxSlot_DragDrop(object? sender, DragEventArgs e) => M?.DragDrop(sender, e); + + public bool InitializeFromSAV(SaveFile sav) + { + Editor = new BoxEdit(sav); + bool result = InitializeGrid(); + + int box = sav.CurrentBox; + if ((uint)box >= sav.BoxCount) + box = 0; + Editor.LoadBox(box); + ResetBoxNames(); // Display the Box Names + return result; + } } diff --git a/PKHeX.WinForms/Controls/SAV Editor/BoxMenuStrip.cs b/PKHeX.WinForms/Controls/SAV Editor/BoxMenuStrip.cs index dd0cca2a6..a5e45273f 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/BoxMenuStrip.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/BoxMenuStrip.cs @@ -4,145 +4,144 @@ using PKHeX.Core; using PKHeX.WinForms.Properties; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public sealed class BoxMenuStrip : ContextMenuStrip { - public sealed class BoxMenuStrip : ContextMenuStrip + private readonly SAVEditor SAV; + private readonly List CustomItems = new(); + private readonly BoxManipulator Manipulator; + + public BoxMenuStrip(SAVEditor sav) { - private readonly SAVEditor SAV; - private readonly List CustomItems = new(); - private readonly BoxManipulator Manipulator; - - public BoxMenuStrip(SAVEditor sav) + Manipulator = new BoxManipulatorWF(sav); + SAV = sav; + var categories = BoxManipUtil.ManipCategories; + var names = BoxManipUtil.ManipCategoryNames; + for (int i = 0; i < categories.Length; i++) { - Manipulator = new BoxManipulatorWF(sav); - SAV = sav; - var categories = BoxManipUtil.ManipCategories; - var names = BoxManipUtil.ManipCategoryNames; - for (int i = 0; i < categories.Length; i++) - { - var category = categories[i]; - var sprite = TopLevelImages[i]; - var name = names[i]; - var parent = new ToolStripMenuItem {Name = $"mnu_{name}", Text = name, Image = sprite}; - foreach (var item in category) - AddItem(sav, parent, item); - Items.Add(parent); - } + var category = categories[i]; + var sprite = TopLevelImages[i]; + var name = names[i]; + var parent = new ToolStripMenuItem {Name = $"mnu_{name}", Text = name, Image = sprite}; + foreach (var item in category) + AddItem(sav, parent, item); + Items.Add(parent); } - - private void AddItem(ISaveFileProvider sav, ToolStripDropDownItem parent, IBoxManip item) - { - var name = item.Type.ToString(); - ManipTypeImage.TryGetValue(item.Type, out var img); - var tsi = new ToolStripMenuItem { Name = $"mnu_{name}", Text = name, Image = img }; - tsi.Click += (s, e) => Manipulator.Execute(item, sav.CurrentBox, All, Reverse); - parent.DropDownItems.Add(tsi); - CustomItems.Add(new ItemVisibility(tsi, item)); - } - - private static readonly Dictionary ManipTypeImage = new() - { - [BoxManipType.DeleteAll] = Resources.nocheck, - [BoxManipType.DeleteEggs] = Resources.about, - [BoxManipType.DeletePastGen] = Resources.bak, - [BoxManipType.DeleteForeign] = Resources.language, - [BoxManipType.DeleteUntrained] = Resources.gift, - [BoxManipType.DeleteItemless] = Resources.main, - [BoxManipType.DeleteIllegal] = Resources.export, - [BoxManipType.DeleteClones] = Resources.users, - - [BoxManipType.SortSpecies] = Resources.numlohi, - [BoxManipType.SortSpeciesReverse] = Resources.numhilo, - [BoxManipType.SortLevel] = Resources.vallohi, - [BoxManipType.SortLevelReverse] = Resources.valhilo, - [BoxManipType.SortDate] = Resources.date, - [BoxManipType.SortName] = Resources.alphaAZ, - [BoxManipType.SortFavorite] = Resources.heart, - [BoxManipType.SortParty] = Resources.users, - [BoxManipType.SortShiny] = Resources.showdown, - [BoxManipType.SortRandom] = Resources.wand, - - [BoxManipType.SortUsage] = Resources.heart, - [BoxManipType.SortPotential] = Resources.numhilo, - [BoxManipType.SortTraining] = Resources.showdown, - [BoxManipType.SortOwner] = Resources.users, - [BoxManipType.SortType] = Resources.main, - [BoxManipType.SortVersion] = Resources.numlohi, - [BoxManipType.SortBST] = Resources.vallohi, - [BoxManipType.SortCP] = Resources.vallohi, - [BoxManipType.SortLegal] = Resources.export, - [BoxManipType.SortEncounterType] = Resources.about, - - [BoxManipType.ModifyHatchEggs] = Resources.about, - [BoxManipType.ModifyMaxFriendship] = Resources.users, - [BoxManipType.ModifyMaxLevel] = Resources.showdown, - [BoxManipType.ModifyResetMoves] = Resources.date, - [BoxManipType.ModifyRandomMoves] = Resources.wand, - [BoxManipType.ModifyHyperTrain] = Resources.vallohi, - [BoxManipType.ModifyGanbaru] = Resources.vallohi, - [BoxManipType.ModifyRemoveNicknames] = Resources.alphaAZ, - [BoxManipType.ModifyRemoveItem] = Resources.gift, - [BoxManipType.ModifyHeal] = Resources.heart, - }; - - private sealed class ItemVisibility - { - private readonly ToolStripItem Item; - private readonly IBoxManip Manip; - - public ItemVisibility(ToolStripItem toolStripItem, IBoxManip visible) - { - Item = toolStripItem; - Manip = visible; - } - - public void SetVisibility(SaveFile s) => Item.Visible = Manip.Usable(s); - } - - public void ToggleVisibility() - { - foreach (var s in CustomItems) - s.SetVisibility(SAV.SAV); - } - - private static readonly Image[] TopLevelImages = - { - Resources.nocheck, - Resources.swapBox, - Resources.settings, - Resources.wand, - }; - - public void Clear() => Manipulator.Execute(BoxManipType.DeleteAll, SAV.CurrentBox, All); - public void Sort() => Manipulator.Execute(BoxManipType.SortSpecies, SAV.CurrentBox, All); - - private static bool All => (ModifierKeys & Keys.Shift) != 0; - private static bool Reverse => (ModifierKeys & Keys.Control) != 0; } - /// - /// Implementation of a WinForms box manipulator (using MessageBox prompts) - /// - public sealed class BoxManipulatorWF : BoxManipulator + private void AddItem(ISaveFileProvider sav, ToolStripDropDownItem parent, IBoxManip item) { - private readonly SAVEditor Editor; - protected override SaveFile SAV => Editor.SAV; + var name = item.Type.ToString(); + ManipTypeImage.TryGetValue(item.Type, out var img); + var tsi = new ToolStripMenuItem { Name = $"mnu_{name}", Text = name, Image = img }; + tsi.Click += (s, e) => Manipulator.Execute(item, sav.CurrentBox, All, Reverse); + parent.DropDownItems.Add(tsi); + CustomItems.Add(new ItemVisibility(tsi, item)); + } - public BoxManipulatorWF(SAVEditor editor) + private static readonly Dictionary ManipTypeImage = new() + { + [BoxManipType.DeleteAll] = Resources.nocheck, + [BoxManipType.DeleteEggs] = Resources.about, + [BoxManipType.DeletePastGen] = Resources.bak, + [BoxManipType.DeleteForeign] = Resources.language, + [BoxManipType.DeleteUntrained] = Resources.gift, + [BoxManipType.DeleteItemless] = Resources.main, + [BoxManipType.DeleteIllegal] = Resources.export, + [BoxManipType.DeleteClones] = Resources.users, + + [BoxManipType.SortSpecies] = Resources.numlohi, + [BoxManipType.SortSpeciesReverse] = Resources.numhilo, + [BoxManipType.SortLevel] = Resources.vallohi, + [BoxManipType.SortLevelReverse] = Resources.valhilo, + [BoxManipType.SortDate] = Resources.date, + [BoxManipType.SortName] = Resources.alphaAZ, + [BoxManipType.SortFavorite] = Resources.heart, + [BoxManipType.SortParty] = Resources.users, + [BoxManipType.SortShiny] = Resources.showdown, + [BoxManipType.SortRandom] = Resources.wand, + + [BoxManipType.SortUsage] = Resources.heart, + [BoxManipType.SortPotential] = Resources.numhilo, + [BoxManipType.SortTraining] = Resources.showdown, + [BoxManipType.SortOwner] = Resources.users, + [BoxManipType.SortType] = Resources.main, + [BoxManipType.SortVersion] = Resources.numlohi, + [BoxManipType.SortBST] = Resources.vallohi, + [BoxManipType.SortCP] = Resources.vallohi, + [BoxManipType.SortLegal] = Resources.export, + [BoxManipType.SortEncounterType] = Resources.about, + + [BoxManipType.ModifyHatchEggs] = Resources.about, + [BoxManipType.ModifyMaxFriendship] = Resources.users, + [BoxManipType.ModifyMaxLevel] = Resources.showdown, + [BoxManipType.ModifyResetMoves] = Resources.date, + [BoxManipType.ModifyRandomMoves] = Resources.wand, + [BoxManipType.ModifyHyperTrain] = Resources.vallohi, + [BoxManipType.ModifyGanbaru] = Resources.vallohi, + [BoxManipType.ModifyRemoveNicknames] = Resources.alphaAZ, + [BoxManipType.ModifyRemoveItem] = Resources.gift, + [BoxManipType.ModifyHeal] = Resources.heart, + }; + + private sealed class ItemVisibility + { + private readonly ToolStripItem Item; + private readonly IBoxManip Manip; + + public ItemVisibility(ToolStripItem toolStripItem, IBoxManip visible) { - Editor = editor; + Item = toolStripItem; + Manip = visible; } - protected override void FinishBoxManipulation(string message, bool all, int count) => Editor.FinishBoxManipulation(message, all, count); + public void SetVisibility(SaveFile s) => Item.Visible = Manip.Usable(s); + } - protected override bool CanManipulateRegion(int start, int end, string prompt, string fail) - { - if (!string.IsNullOrEmpty(prompt) && WinFormsUtil.Prompt(MessageBoxButtons.YesNo, prompt) != DialogResult.Yes) - return false; - bool canModify = base.CanManipulateRegion(start, end, prompt, fail); - if (!canModify && !string.IsNullOrEmpty(fail)) - WinFormsUtil.Alert(fail); - return canModify; - } + public void ToggleVisibility() + { + foreach (var s in CustomItems) + s.SetVisibility(SAV.SAV); + } + + private static readonly Image[] TopLevelImages = + { + Resources.nocheck, + Resources.swapBox, + Resources.settings, + Resources.wand, + }; + + public void Clear() => Manipulator.Execute(BoxManipType.DeleteAll, SAV.CurrentBox, All); + public void Sort() => Manipulator.Execute(BoxManipType.SortSpecies, SAV.CurrentBox, All); + + private static bool All => (ModifierKeys & Keys.Shift) != 0; + private static bool Reverse => (ModifierKeys & Keys.Control) != 0; +} + +/// +/// Implementation of a WinForms box manipulator (using MessageBox prompts) +/// +public sealed class BoxManipulatorWF : BoxManipulator +{ + private readonly SAVEditor Editor; + protected override SaveFile SAV => Editor.SAV; + + public BoxManipulatorWF(SAVEditor editor) + { + Editor = editor; + } + + protected override void FinishBoxManipulation(string message, bool all, int count) => Editor.FinishBoxManipulation(message, all, count); + + protected override bool CanManipulateRegion(int start, int end, string prompt, string fail) + { + if (!string.IsNullOrEmpty(prompt) && WinFormsUtil.Prompt(MessageBoxButtons.YesNo, prompt) != DialogResult.Yes) + return false; + bool canModify = base.CanManipulateRegion(start, end, prompt, fail); + if (!canModify && !string.IsNullOrEmpty(fail)) + WinFormsUtil.Alert(fail); + return canModify; } } diff --git a/PKHeX.WinForms/Controls/SAV Editor/ContextMenuSAV.cs b/PKHeX.WinForms/Controls/SAV Editor/ContextMenuSAV.cs index 864144418..557faa1ec 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/ContextMenuSAV.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/ContextMenuSAV.cs @@ -1,162 +1,160 @@ -using System; +using System; using System.ComponentModel; using System.Windows.Forms; using PKHeX.Core; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class ContextMenuSAV : UserControl { - public partial class ContextMenuSAV : UserControl + public ContextMenuSAV() => InitializeComponent(); + + public SaveDataEditor Editor { private get; set; } = null!; + public SlotChangeManager Manager { get; set; } = null!; + + public Action? RequestEditorLegality; + + public void OmniClick(object sender, EventArgs e, Keys z) { - public ContextMenuSAV() => InitializeComponent(); - - public SaveDataEditor Editor { private get; set; } = null!; - public SlotChangeManager Manager { get; set; } = null!; - - public Action? RequestEditorLegality; - public delegate void LegalityRequest(object sender, EventArgs e, LegalityAnalysis la); - - public void OmniClick(object sender, EventArgs e, Keys z) + switch (z) { - switch (z) - { - case Keys.Control: ClickView(sender, e); break; - case Keys.Shift: ClickSet(sender, e); break; - case Keys.Alt: ClickDelete(sender, e); break; - default: - return; - } - - // restart hovering since the mouse event isn't fired - Manager.MouseEnter(sender, e); - } - - private void ClickView(object sender, EventArgs e) - { - var info = GetSenderInfo(ref sender); - if ((sender as PictureBox)?.Image == null) - { System.Media.SystemSounds.Asterisk.Play(); return; } - - Manager.Hover.Stop(); - var pkm = Editor.Slots.Get(info.Slot); - Editor.PKMEditor.PopulateFields(pkm, false, true); - } - - private void ClickSet(object sender, EventArgs e) - { - var editor = Editor.PKMEditor; - if (!editor.EditsComplete) + case Keys.Control: ClickView(sender, e); break; + case Keys.Shift: ClickSet(sender, e); break; + case Keys.Alt: ClickDelete(sender, e); break; + default: return; - PKM pk = editor.PreparePKM(); - - var info = GetSenderInfo(ref sender); - var sav = info.View.SAV; - - if (!CheckDest(info, sav, pk)) - return; - - var errata = sav.EvaluateCompatibility(pk); - if (errata.Count > 0 && DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, string.Join(Environment.NewLine, errata), MsgContinue)) - return; - - Manager.Hover.Stop(); - Editor.Slots.Set(info.Slot, pk); - Manager.SE.UpdateUndoRedo(); } - private void ClickDelete(object sender, EventArgs e) + // restart hovering since the mouse event isn't fired + Manager.MouseEnter(sender, e); + } + + private void ClickView(object sender, EventArgs e) + { + var info = GetSenderInfo(ref sender); + if ((sender as PictureBox)?.Image == null) + { System.Media.SystemSounds.Asterisk.Play(); return; } + + Manager.Hover.Stop(); + var pk = Editor.Slots.Get(info.Slot); + Editor.PKMEditor.PopulateFields(pk, false, true); + } + + private void ClickSet(object sender, EventArgs e) + { + var editor = Editor.PKMEditor; + if (!editor.EditsComplete) + return; + PKM pk = editor.PreparePKM(); + + var info = GetSenderInfo(ref sender); + var sav = info.View.SAV; + + if (!CheckDest(info, sav, pk)) + return; + + var errata = sav.EvaluateCompatibility(pk); + if (errata.Count > 0 && DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, string.Join(Environment.NewLine, errata), MsgContinue)) + return; + + Manager.Hover.Stop(); + Editor.Slots.Set(info.Slot, pk); + Manager.SE.UpdateUndoRedo(); + } + + private void ClickDelete(object sender, EventArgs e) + { + var info = GetSenderInfo(ref sender); + if ((sender as PictureBox)?.Image == null) + { System.Media.SystemSounds.Asterisk.Play(); return; } + + var sav = info.View.SAV; + var pk = sav.BlankPKM; + if (!CheckDest(info, sav, pk)) + return; + + Manager.Hover.Stop(); + Editor.Slots.Delete(info.Slot); + Manager.SE.UpdateUndoRedo(); + } + + private static bool CheckDest(SlotViewInfo info, SaveFile sav, PKM pk) + { + var msg = info.Slot.CanWriteTo(sav, pk); + if (msg == WriteBlockedMessage.None) + return true; + + switch (msg) { - var info = GetSenderInfo(ref sender); - if ((sender as PictureBox)?.Image == null) - { System.Media.SystemSounds.Asterisk.Play(); return; } - - var sav = info.View.SAV; - var pk = sav.BlankPKM; - if (!CheckDest(info, sav, pk)) - return; - - Manager.Hover.Stop(); - Editor.Slots.Delete(info.Slot); - Manager.SE.UpdateUndoRedo(); + case WriteBlockedMessage.InvalidPartyConfiguration: + WinFormsUtil.Alert(MsgSaveSlotEmpty); + break; + case WriteBlockedMessage.IncompatibleFormat: + break; + case WriteBlockedMessage.InvalidDestination: + WinFormsUtil.Alert(MsgSaveSlotLocked); + break; + default: + throw new IndexOutOfRangeException(nameof(msg)); } + return false; + } - private static bool CheckDest(SlotViewInfo info, SaveFile sav, PKM pk) + private void ClickShowLegality(object sender, EventArgs e) + { + var info = GetSenderInfo(ref sender); + var sav = info.View.SAV; + var pk = info.Slot.Read(sav); + var type = info.Slot is SlotInfoBox ? SlotOrigin.Box : SlotOrigin.Party; + var la = new LegalityAnalysis(pk, sav.Personal, type); + RequestEditorLegality?.Invoke(la); + } + + private void MenuOpening(object sender, CancelEventArgs e) + { + var items = ((ContextMenuStrip)sender).Items; + + object ctrl = ((ContextMenuStrip)sender).SourceControl; + var info = GetSenderInfo(ref ctrl); + bool SlotFull = (ctrl as PictureBox)?.Image != null; + bool Editable = info.Slot.CanWriteTo(info.View.SAV); + bool legality = ModifierKeys == Keys.Control; + ToggleItem(items, mnuSet, Editable); + ToggleItem(items, mnuDelete, Editable && SlotFull); + ToggleItem(items, mnuLegality, legality && SlotFull && RequestEditorLegality != null); + ToggleItem(items, mnuView, SlotFull || !Editable, true); + + if (items.Count == 0) + e.Cancel = true; + } + + private static SlotViewInfo GetSenderInfo(ref object sender) + { + var pb = WinFormsUtil.GetUnderlyingControl(sender); + if (pb == null) + throw new InvalidCastException("Unable to find PictureBox"); + var view = WinFormsUtil.FindFirstControlOfType>(pb); + if (view == null) + throw new InvalidCastException("Unable to find View Parent"); + var loc = view.GetSlotData(pb); + sender = pb; + return new SlotViewInfo(loc, view); + } + + private static void ToggleItem(ToolStripItemCollection items, ToolStripItem item, bool visible, bool first = false) + { + if (visible) { - var msg = info.Slot.CanWriteTo(sav, pk); - if (msg == WriteBlockedMessage.None) - return true; - - switch (msg) - { - case WriteBlockedMessage.InvalidPartyConfiguration: - WinFormsUtil.Alert(MsgSaveSlotEmpty); - break; - case WriteBlockedMessage.IncompatibleFormat: - break; - case WriteBlockedMessage.InvalidDestination: - WinFormsUtil.Alert(MsgSaveSlotLocked); - break; - default: - throw new IndexOutOfRangeException(nameof(msg)); - } - return false; + if (first) + items.Insert(0, item); + else + items.Add(item); } - - private void ClickShowLegality(object sender, EventArgs e) + else if (items.Contains(item)) { - var info = GetSenderInfo(ref sender); - var sav = info.View.SAV; - var pk = info.Slot.Read(sav); - var type = info.Slot is SlotInfoBox ? SlotOrigin.Box : SlotOrigin.Party; - var la = new LegalityAnalysis(pk, sav.Personal, type); - RequestEditorLegality?.Invoke(la); - } - - private void MenuOpening(object sender, CancelEventArgs e) - { - var items = ((ContextMenuStrip)sender).Items; - - object ctrl = ((ContextMenuStrip)sender).SourceControl; - var info = GetSenderInfo(ref ctrl); - bool SlotFull = (ctrl as PictureBox)?.Image != null; - bool Editable = info.Slot.CanWriteTo(info.View.SAV); - bool legality = ModifierKeys == Keys.Control; - ToggleItem(items, mnuSet, Editable); - ToggleItem(items, mnuDelete, Editable && SlotFull); - ToggleItem(items, mnuLegality, legality && SlotFull && RequestEditorLegality != null); - ToggleItem(items, mnuView, SlotFull || !Editable, true); - - if (items.Count == 0) - e.Cancel = true; - } - - private static SlotViewInfo GetSenderInfo(ref object sender) - { - var pb = WinFormsUtil.GetUnderlyingControl(sender); - if (pb == null) - throw new InvalidCastException("Unable to find PictureBox"); - var view = WinFormsUtil.FindFirstControlOfType>(pb); - if (view == null) - throw new InvalidCastException("Unable to find View Parent"); - var loc = view.GetSlotData(pb); - sender = pb; - return new SlotViewInfo(loc, view); - } - - private static void ToggleItem(ToolStripItemCollection items, ToolStripItem item, bool visible, bool first = false) - { - if (visible) - { - if (first) - items.Insert(0, item); - else - items.Add(item); - } - else if (items.Contains(item)) - { - items.Remove(item); - } + items.Remove(item); } } } diff --git a/PKHeX.WinForms/Controls/SAV Editor/PartyEditor.cs b/PKHeX.WinForms/Controls/SAV Editor/PartyEditor.cs index b4bb80887..807a174fd 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/PartyEditor.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/PartyEditor.cs @@ -4,132 +4,131 @@ using PKHeX.Core; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PartyEditor : UserControl, ISlotViewer { - public partial class PartyEditor : UserControl, ISlotViewer + public IList SlotPictureBoxes { get; private set; } = Array.Empty(); + public SaveFile SAV => M?.SE.SAV ?? throw new ArgumentNullException(nameof(SAV)); + + public int BoxSlotCount { get; private set; } + public SlotChangeManager? M { get; set; } + public bool FlagIllegal { get; set; } + + public PartyEditor() { - public IList SlotPictureBoxes { get; private set; } = Array.Empty(); - public SaveFile SAV => M?.SE.SAV ?? throw new ArgumentNullException(nameof(SAV)); + InitializeComponent(); + } - public int BoxSlotCount { get; private set; } - public SlotChangeManager? M { get; set; } - public bool FlagIllegal { get; set; } + internal bool InitializeGrid() + { + const int width = 2; + const int height = 3; + if (!PartyPokeGrid.InitializeGrid(width, height, SpriteUtil.Spriter)) + return false; - public PartyEditor() + PartyPokeGrid.HorizontallyCenter(this); + InitializeSlots(); + return true; + } + + private void InitializeSlots() + { + SlotPictureBoxes = PartyPokeGrid.Entries; + BoxSlotCount = SlotPictureBoxes.Count; + foreach (var pb in SlotPictureBoxes) { - InitializeComponent(); - } + pb.MouseEnter += (o, args) => BoxSlot_MouseEnter(pb, args); + pb.MouseLeave += (o, args) => BoxSlot_MouseLeave(pb, args); + pb.MouseClick += BoxSlot_MouseClick; + pb.MouseMove += BoxSlot_MouseMove; + pb.MouseDown += BoxSlot_MouseDown; + pb.MouseUp += BoxSlot_MouseUp; - internal bool InitializeGrid() - { - const int width = 2; - const int height = 3; - if (!PartyPokeGrid.InitializeGrid(width, height, SpriteUtil.Spriter)) - return false; - - PartyPokeGrid.HorizontallyCenter(this); - InitializeSlots(); - return true; - } - - private void InitializeSlots() - { - SlotPictureBoxes = PartyPokeGrid.Entries; - BoxSlotCount = SlotPictureBoxes.Count; - foreach (var pb in SlotPictureBoxes) - { - pb.MouseEnter += (o, args) => BoxSlot_MouseEnter(pb, args); - pb.MouseLeave += (o, args) => BoxSlot_MouseLeave(pb, args); - pb.MouseClick += BoxSlot_MouseClick; - pb.MouseMove += BoxSlot_MouseMove; - pb.MouseDown += BoxSlot_MouseDown; - pb.MouseUp += BoxSlot_MouseUp; - - pb.DragEnter += BoxSlot_DragEnter; - pb.DragDrop += BoxSlot_DragDrop; - pb.QueryContinueDrag += BoxSlot_QueryContinueDrag; - pb.GiveFeedback += (sender, e) => e.UseDefaultCursors = false; - pb.AllowDrop = true; - } - } - - public void NotifySlotOld(ISlotInfo previous) - { - if (previous is not SlotInfoParty p) - return; - - var pb = SlotPictureBoxes[p.Slot]; - pb.BackgroundImage = null; - } - - public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pkm) - { - int index = GetViewIndex(slot); - if (index < 0) - return; - - if (type == SlotTouchType.Delete) - { - ResetSlots(); - return; - } - - var pb = SlotPictureBoxes[index]; - SlotUtil.UpdateSlot(pb, slot, pkm, SAV, FlagIllegal, type); - } - - public int GetViewIndex(ISlotInfo slot) - { - if (slot is not SlotInfoParty p) - return -1; - return p.Slot; - } - - public ISlotInfo GetSlotData(PictureBox view) - { - int slot = GetSlot(view); - return new SlotInfoParty(slot); - } - - private int GetSlot(PictureBox sender) => SlotPictureBoxes.IndexOf(sender); - public int ViewIndex => -999; - - public void Setup(SlotChangeManager m) - { - M = m; - FlagIllegal = M.SE.FlagIllegal; - } - - public void ResetSlots() - { - //pokeGrid1.SetBackground(SAV.WallpaperImage(0)); - M?.Hover.Stop(); - - foreach (var pb in SlotPictureBoxes) - { - var slot = (SlotInfoParty) GetSlotData(pb); - SlotUtil.UpdateSlot(pb, slot, slot.Read(SAV), SAV, FlagIllegal); - } - - if (M?.Env.Slots.Publisher.Previous is SlotInfoParty p) - SlotPictureBoxes[p.Slot].BackgroundImage = SlotUtil.GetTouchTypeBackground(M.Env.Slots.Publisher.PreviousType); - } - - // Drag & Drop Handling - private void BoxSlot_MouseEnter(object? sender, EventArgs e) => M?.MouseEnter(sender, e); - private void BoxSlot_MouseLeave(object? sender, EventArgs e) => M?.MouseLeave(sender, e); - private void BoxSlot_MouseClick(object? sender, MouseEventArgs e) => M?.MouseClick(sender, e); - private void BoxSlot_MouseUp(object? sender, MouseEventArgs e) => M?.MouseUp(sender, e); - private void BoxSlot_MouseDown(object? sender, MouseEventArgs e) => M?.MouseDown(sender, e); - private void BoxSlot_MouseMove(object? sender, MouseEventArgs e) => M?.MouseMove(sender, e); - private void BoxSlot_DragEnter(object? sender, DragEventArgs e) => M?.DragEnter(sender, e); - private void BoxSlot_QueryContinueDrag(object? sender, QueryContinueDragEventArgs e) => M?.QueryContinueDrag(sender, e); - private void BoxSlot_DragDrop(object? sender, DragEventArgs e) => M?.DragDrop(sender, e); - - public bool InitializeFromSAV(SaveFile sav) - { - Visible = sav.HasParty; - return InitializeGrid(); + pb.DragEnter += BoxSlot_DragEnter; + pb.DragDrop += BoxSlot_DragDrop; + pb.QueryContinueDrag += BoxSlot_QueryContinueDrag; + pb.GiveFeedback += (sender, e) => e.UseDefaultCursors = false; + pb.AllowDrop = true; } } + + public void NotifySlotOld(ISlotInfo previous) + { + if (previous is not SlotInfoParty p) + return; + + var pb = SlotPictureBoxes[p.Slot]; + pb.BackgroundImage = null; + } + + public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pk) + { + int index = GetViewIndex(slot); + if (index < 0) + return; + + if (type == SlotTouchType.Delete) + { + ResetSlots(); + return; + } + + var pb = SlotPictureBoxes[index]; + SlotUtil.UpdateSlot(pb, slot, pk, SAV, FlagIllegal, type); + } + + public int GetViewIndex(ISlotInfo slot) + { + if (slot is not SlotInfoParty p) + return -1; + return p.Slot; + } + + public ISlotInfo GetSlotData(PictureBox view) + { + int slot = GetSlot(view); + return new SlotInfoParty(slot); + } + + private int GetSlot(PictureBox sender) => SlotPictureBoxes.IndexOf(sender); + public int ViewIndex => -999; + + public void Setup(SlotChangeManager m) + { + M = m; + FlagIllegal = M.SE.FlagIllegal; + } + + public void ResetSlots() + { + //pokeGrid1.SetBackground(SAV.WallpaperImage(0)); + M?.Hover.Stop(); + + foreach (var pb in SlotPictureBoxes) + { + var slot = (SlotInfoParty) GetSlotData(pb); + SlotUtil.UpdateSlot(pb, slot, slot.Read(SAV), SAV, FlagIllegal); + } + + if (M?.Env.Slots.Publisher.Previous is SlotInfoParty p) + SlotPictureBoxes[p.Slot].BackgroundImage = SlotUtil.GetTouchTypeBackground(M.Env.Slots.Publisher.PreviousType); + } + + // Drag & Drop Handling + private void BoxSlot_MouseEnter(object? sender, EventArgs e) => M?.MouseEnter(sender, e); + private void BoxSlot_MouseLeave(object? sender, EventArgs e) => M?.MouseLeave(sender, e); + private void BoxSlot_MouseClick(object? sender, MouseEventArgs e) => M?.MouseClick(sender, e); + private void BoxSlot_MouseUp(object? sender, MouseEventArgs e) => M?.MouseUp(sender, e); + private void BoxSlot_MouseDown(object? sender, MouseEventArgs e) => M?.MouseDown(sender, e); + private void BoxSlot_MouseMove(object? sender, MouseEventArgs e) => M?.MouseMove(sender, e); + private void BoxSlot_DragEnter(object? sender, DragEventArgs e) => M?.DragEnter(sender, e); + private void BoxSlot_QueryContinueDrag(object? sender, QueryContinueDragEventArgs e) => M?.QueryContinueDrag(sender, e); + private void BoxSlot_DragDrop(object? sender, DragEventArgs e) => M?.DragDrop(sender, e); + + public bool InitializeFromSAV(SaveFile sav) + { + Visible = sav.HasParty; + return InitializeGrid(); + } } diff --git a/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs b/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs index 34042dc16..654ae7fc8 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Drawing; using System.IO; @@ -9,1258 +9,1257 @@ using PKHeX.Drawing; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class SAVEditor : UserControl, ISlotViewer, ISaveFileProvider { - public partial class SAVEditor : UserControl, ISlotViewer, ISaveFileProvider + public SaveDataEditor EditEnv = null!; + + public void SetEditEnvironment(SaveDataEditor value) { - public SaveDataEditor EditEnv = null!; + EditEnv = value; + M.Env = value; + menu.Editor = value; + SAV = value.SAV; + value.Slots.Publisher.Subscribers.Add(this); + value.Slots.Publisher.Subscribers.Add(SL_Party); + value.Slots.Publisher.Subscribers.Add(Box); + value.Slots.Publisher.Subscribers.Add(SL_Extra); + } - public void SetEditEnvironment(SaveDataEditor value) + public SaveFile SAV { get; private set; } = new FakeSaveFile(); + public int CurrentBox => Box.CurrentBox; + public SlotChangeManager M { get; } + public readonly ContextMenuSAV menu; + public readonly BoxMenuStrip SortMenu; + + public bool HaX; + public bool ModifyPKM { private get; set; } + private bool _hideSecret; + public bool HideSecretDetails { private get => _hideSecret; set => ToggleSecrets(SAV, _hideSecret = value); } + public ToolStripMenuItem Menu_Redo { get; set; } = null!; + public ToolStripMenuItem Menu_Undo { get; set; } = null!; + private bool FieldsLoaded; + + public IList SlotPictureBoxes { get; } + + public int ViewIndex { get; set; } = -1; + + public bool FlagIllegal + { + get => Box.FlagIllegal; + set { - EditEnv = value; - M.Env = value; - menu.Editor = value; - SAV = value.SAV; - value.Slots.Publisher.Subscribers.Add(this); - value.Slots.Publisher.Subscribers.Add(SL_Party); - value.Slots.Publisher.Subscribers.Add(Box); - value.Slots.Publisher.Subscribers.Add(SL_Extra); + SL_Extra.FlagIllegal = SL_Party.FlagIllegal = Box.FlagIllegal = value && !HaX; + if (SAV is FakeSaveFile) + return; + ReloadSlots(); } + } - public SaveFile SAV { get; private set; } = new FakeSaveFile(); - public int CurrentBox => Box.CurrentBox; - public SlotChangeManager M { get; } - public readonly ContextMenuSAV menu; - public readonly BoxMenuStrip SortMenu; + public void ReloadSlots() + { + UpdateBoxViewers(all: true); + ResetNonBoxSlots(); + } - public bool HaX; - public bool ModifyPKM { private get; set; } - private bool _hideSecret; - public bool HideSecretDetails { private get => _hideSecret; set => ToggleSecrets(SAV, _hideSecret = value); } - public ToolStripMenuItem Menu_Redo { get; set; } = null!; - public ToolStripMenuItem Menu_Undo { get; set; } = null!; - private bool FieldsLoaded; + public SAVEditor() + { + InitializeComponent(); - public IList SlotPictureBoxes { get; } + L_SlotOccupied = new[] { L_DC1, L_DC2 }; + TB_SlotEXP = new[] { TB_Daycare1XP, TB_Daycare2XP }; + L_SlotEXP = new[] { L_XP1, L_XP2 }; + SlotPictureBoxes = new[] { dcpkx1, dcpkx2 }; - public int ViewIndex { get; set; } = -1; + Tab_Box.ContextMenuStrip = SortMenu = new BoxMenuStrip(this); + M = new SlotChangeManager(this) {Env = EditEnv}; + Box.Setup(M); + SL_Party.Setup(M); - public bool FlagIllegal + SL_Extra.ViewIndex = -2; + menu = new ContextMenuSAV { Manager = M }; + InitializeEvents(); + } + + private void InitializeEvents() + { + foreach (PictureBox pb in SlotPictureBoxes) { - get => Box.FlagIllegal; - set - { - SL_Extra.FlagIllegal = SL_Party.FlagIllegal = Box.FlagIllegal = value && !HaX; - if (SAV is FakeSaveFile) - return; - ReloadSlots(); - } - } - - public void ReloadSlots() - { - UpdateBoxViewers(all: true); - ResetNonBoxSlots(); - } - - public SAVEditor() - { - InitializeComponent(); - - L_SlotOccupied = new[] { L_DC1, L_DC2 }; - TB_SlotEXP = new[] { TB_Daycare1XP, TB_Daycare2XP }; - L_SlotEXP = new[] { L_XP1, L_XP2 }; - SlotPictureBoxes = new[] { dcpkx1, dcpkx2 }; - - Tab_Box.ContextMenuStrip = SortMenu = new BoxMenuStrip(this); - M = new SlotChangeManager(this) {Env = EditEnv}; - Box.Setup(M); - SL_Party.Setup(M); - - SL_Extra.ViewIndex = -2; - menu = new ContextMenuSAV { Manager = M }; - InitializeEvents(); - } - - private void InitializeEvents() - { - foreach (PictureBox pb in SlotPictureBoxes) - { - InitializeDragDrop(pb); - pb.ContextMenuStrip = menu.mnuVSD; - } - - GiveFeedback += (sender, e) => e.UseDefaultCursors = false; - Tab_Box.MouseWheel += (s, e) => - { - if (menu.mnuVSD.Visible) - return; - Box.CurrentBox = e.Delta > 1 ? Box.Editor.MoveLeft() : Box.Editor.MoveRight(); - }; - - GB_Daycare.Click += (o, args) => SwitchDaycare(GB_Daycare, args); - FLP_SAVtools.Scroll += WinFormsUtil.PanelScroll; - SortMenu.Opening += (s, x) => x.Cancel = !tabBoxMulti.GetTabRect(tabBoxMulti.SelectedIndex).Contains(PointToClient(MousePosition)); - } - - private void InitializeDragDrop(Control pb) - { - pb.MouseEnter += M.MouseEnter; - pb.MouseLeave += M.MouseLeave; - pb.MouseClick += M.MouseClick; - pb.MouseMove += M.MouseMove; - pb.MouseDown += M.MouseDown; - pb.MouseUp += M.MouseUp; - - pb.DragEnter += M.DragEnter; - pb.DragDrop += M.DragDrop; - pb.QueryContinueDrag += M.QueryContinueDrag; - pb.GiveFeedback += (sender, e) => e.UseDefaultCursors = false; - pb.AllowDrop = true; + InitializeDragDrop(pb); pb.ContextMenuStrip = menu.mnuVSD; } - /// Occurs when the Control Collection requests a cloning operation to the current box. - public event EventHandler? RequestCloneData; - - /// Occurs when the Control Collection requests a save to be reloaded. - public event EventHandler? RequestReloadSave; - - public void EnableDragDrop(DragEventHandler enter, DragEventHandler drop) + GiveFeedback += (sender, e) => e.UseDefaultCursors = false; + Tab_Box.MouseWheel += (s, e) => { - AllowDrop = true; - DragDrop += drop; - foreach (var tab in tabBoxMulti.TabPages.OfType()) - { - tab.AllowDrop = true; - tab.DragEnter += enter; - tab.DragDrop += drop; - } - M.Drag.RequestExternalDragDrop += drop; - } - - // Generic Subfunctions // - public int GetSlotOffset(int slot) - { - return SAV.GetDaycareSlotOffset(SAV.DaycareIndex, slot); - } - - public int SwapBoxesViewer(int viewBox) - { - int mainBox = Box.CurrentBox; - Box.CurrentBox = viewBox; - return mainBox; - } - - public void UpdateBoxViewers(bool all = false) - { - foreach (var v in M.Boxes.Where(v => v.CurrentBox == Box.CurrentBox || all)) - { - v.FlagIllegal = Box.FlagIllegal; - v.ResetSlots(); - } - } - - public void NotifySlotOld(ISlotInfo previous) - { - var index = GetViewIndex(previous); - if (index < 0) + if (menu.mnuVSD.Visible) return; + Box.CurrentBox = e.Delta > 1 ? Box.Editor.MoveLeft() : Box.Editor.MoveRight(); + }; - var pb = SlotPictureBoxes[index]; - pb.BackgroundImage = null; - } + GB_Daycare.Click += (o, args) => SwitchDaycare(GB_Daycare, args); + FLP_SAVtools.Scroll += WinFormsUtil.PanelScroll; + SortMenu.Opening += (s, x) => x.Cancel = !tabBoxMulti.GetTabRect(tabBoxMulti.SelectedIndex).Contains(PointToClient(MousePosition)); + } - public int GetViewIndex(ISlotInfo slot) + private void InitializeDragDrop(Control pb) + { + pb.MouseEnter += M.MouseEnter; + pb.MouseLeave += M.MouseLeave; + pb.MouseClick += M.MouseClick; + pb.MouseMove += M.MouseMove; + pb.MouseDown += M.MouseDown; + pb.MouseUp += M.MouseUp; + + pb.DragEnter += M.DragEnter; + pb.DragDrop += M.DragDrop; + pb.QueryContinueDrag += M.QueryContinueDrag; + pb.GiveFeedback += (sender, e) => e.UseDefaultCursors = false; + pb.AllowDrop = true; + pb.ContextMenuStrip = menu.mnuVSD; + } + + /// Occurs when the Control Collection requests a cloning operation to the current box. + public event EventHandler? RequestCloneData; + + /// Occurs when the Control Collection requests a save to be reloaded. + public event EventHandler? RequestReloadSave; + + public void EnableDragDrop(DragEventHandler enter, DragEventHandler drop) + { + AllowDrop = true; + DragDrop += drop; + foreach (var tab in tabBoxMulti.TabPages.OfType()) { - for (int i = 0; i < SlotPictureBoxes.Count; i++) - { - var data = GetSlotData(i); - if (data.Equals(slot)) - return i; - } - return -1; + tab.AllowDrop = true; + tab.DragEnter += enter; + tab.DragDrop += drop; } + M.Drag.RequestExternalDragDrop += drop; + } - public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pkm) + // Generic Subfunctions // + public int GetSlotOffset(int slot) + { + return SAV.GetDaycareSlotOffset(SAV.DaycareIndex, slot); + } + + public int SwapBoxesViewer(int viewBox) + { + int mainBox = Box.CurrentBox; + Box.CurrentBox = viewBox; + return mainBox; + } + + public void UpdateBoxViewers(bool all = false) + { + foreach (var v in M.Boxes.Where(v => v.CurrentBox == Box.CurrentBox || all)) { - var index = GetViewIndex(slot); - if (index < 0) - return; - - if (type.IsContentChange() && slot is SlotInfoParty) - ResetParty(); // lots of slots change, just update - - var pb = SlotPictureBoxes[index]; - SlotUtil.UpdateSlot(pb, slot, pkm, SAV, Box.FlagIllegal, type); + v.FlagIllegal = Box.FlagIllegal; + v.ResetSlots(); } + } - public ISlotInfo GetSlotData(PictureBox view) + public void NotifySlotOld(ISlotInfo previous) + { + var index = GetViewIndex(previous); + if (index < 0) + return; + + var pb = SlotPictureBoxes[index]; + pb.BackgroundImage = null; + } + + public int GetViewIndex(ISlotInfo slot) + { + for (int i = 0; i < SlotPictureBoxes.Count; i++) { - var index = SlotPictureBoxes.IndexOf(view); - return GetSlotData(index); + var data = GetSlotData(i); + if (data.Equals(slot)) + return i; } + return -1; + } - public ISlotInfo GetSlotData(int index) - { - var ofs = GetSlotOffset(index); - return new SlotInfoMisc(SAV, index, ofs); - } + public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pk) + { + var index = GetViewIndex(slot); + if (index < 0) + return; - public void SetPKMBoxes() - { - if (SAV.HasBox) - Box.ResetSlots(); + if (type.IsContentChange() && slot is SlotInfoParty) + ResetParty(); // lots of slots change, just update - ResetNonBoxSlots(); - } + var pb = SlotPictureBoxes[index]; + SlotUtil.UpdateSlot(pb, slot, pk, SAV, Box.FlagIllegal, type); + } - private void ResetNonBoxSlots() - { - ResetParty(); - ResetDaycare(); - ResetMiscSlots(); - } + public ISlotInfo GetSlotData(PictureBox view) + { + var index = SlotPictureBoxes.IndexOf(view); + return GetSlotData(index); + } - private void ResetMiscSlots() - { - var slots = SL_Extra.SlotPictureBoxes; - for (int i = 0; i < SL_Extra.SlotCount; i++) - { - var info = SL_Extra.GetSlotData(i); - var pb = slots[i]; - SlotUtil.UpdateSlot(pb, info, info.Read(SAV), SAV, Box.FlagIllegal); - } - } + public ISlotInfo GetSlotData(int index) + { + var ofs = GetSlotOffset(index); + return new SlotInfoMisc(SAV, index, ofs); + } - private void ResetParty() - { - if (SAV.HasParty) - SL_Party.ResetSlots(); - } - - private readonly Label[] L_SlotOccupied; - private readonly TextBox[] TB_SlotEXP; - private readonly Label[] L_SlotEXP; - - private void ResetDaycare() - { - if (!SAV.HasDaycare) - return; - - for (int i = 0; i < 2; i++) - { - var relIndex = i; - var pb = UpdateSlot(relIndex); - - uint? exp = SAV.GetDaycareEXP(SAV.DaycareIndex, i); - TB_SlotEXP[i].Visible = L_SlotEXP[i].Visible = exp != null; - TB_SlotEXP[i].Text = exp.ToString(); - - bool? occ = SAV.IsDaycareOccupied(SAV.DaycareIndex, i); - L_SlotOccupied[i].Visible = occ != null; - if (occ == true) // If Occupied - { - L_SlotOccupied[i].Text = $"{i + 1}: ✓"; - } - else - { - L_SlotOccupied[i].Text = $"{i + 1}: ✘"; - var current = pb.Image; - if (current != null) - pb.Image = ImageUtil.ChangeOpacity(current, 0.6); - } - } - - bool? egg = SAV.IsDaycareHasEgg(SAV.DaycareIndex); - DayCare_HasEgg.Visible = egg != null; - DayCare_HasEgg.Checked = egg == true; - - var seed = SAV.GetDaycareRNGSeed(SAV.DaycareIndex); - bool hasSeed = !string.IsNullOrEmpty(seed); - if (hasSeed) - { - TB_RNGSeed.MaxLength = SAV.DaycareSeedSize; - TB_RNGSeed.Text = seed; - } - L_DaycareSeed.Visible = TB_RNGSeed.Visible = hasSeed; - } - - private PictureBox UpdateSlot(int relIndex) - { - var info = GetSlotData(relIndex); - var pb = SlotPictureBoxes[relIndex]; - SlotUtil.UpdateSlot(pb, info, info.Read(SAV), SAV, Box.FlagIllegal); - return pb; - } - - public void SetParty() => ResetParty(); - - public void ClickUndo() - { - EditEnv.Slots.Undo(); - UpdateUndoRedo(); - } - - public void ClickRedo() - { - EditEnv.Slots.Redo(); - UpdateUndoRedo(); - } - - public void UpdateUndoRedo() - { - Menu_Undo.Enabled = EditEnv.Slots.Changelog.CanUndo; - Menu_Redo.Enabled = EditEnv.Slots.Changelog.CanRedo; - } - - public void SetClonesToBox(PKM pk) - { - if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, string.Format(MsgSaveBoxCloneFromTabs, Box.CurrentBoxName)) != DialogResult.Yes) - return; - - int slotSkipped = SetClonesToCurrentBox(pk, Box.CurrentBox); - if (slotSkipped > 0) - WinFormsUtil.Alert(string.Format(MsgSaveBoxImportSkippedLocked, slotSkipped)); - - UpdateBoxViewers(); - } - - private int SetClonesToCurrentBox(PKM pk, int box) - { - var arr = new PKM[SAV.BoxSlotCount]; - for (int i = 0; i < SAV.BoxSlotCount; i++) // set to every slot in box - arr[i] = pk; - - int slotSkipped = SAV.SetBoxData(arr, box); + public void SetPKMBoxes() + { + if (SAV.HasBox) Box.ResetSlots(); - return slotSkipped; - } - public void ClickSlot(object sender, EventArgs e) + ResetNonBoxSlots(); + } + + private void ResetNonBoxSlots() + { + ResetParty(); + ResetDaycare(); + ResetMiscSlots(); + } + + private void ResetMiscSlots() + { + var slots = SL_Extra.SlotPictureBoxes; + for (int i = 0; i < SL_Extra.SlotCount; i++) { - switch (ModifierKeys) + var info = SL_Extra.GetSlotData(i); + var pb = slots[i]; + SlotUtil.UpdateSlot(pb, info, info.Read(SAV), SAV, Box.FlagIllegal); + } + } + + private void ResetParty() + { + if (SAV.HasParty) + SL_Party.ResetSlots(); + } + + private readonly Label[] L_SlotOccupied; + private readonly TextBox[] TB_SlotEXP; + private readonly Label[] L_SlotEXP; + + private void ResetDaycare() + { + if (!SAV.HasDaycare) + return; + + for (int i = 0; i < 2; i++) + { + var relIndex = i; + var pb = UpdateSlot(relIndex); + + uint? exp = SAV.GetDaycareEXP(SAV.DaycareIndex, i); + TB_SlotEXP[i].Visible = L_SlotEXP[i].Visible = exp != null; + TB_SlotEXP[i].Text = exp.ToString(); + + bool? occ = SAV.IsDaycareOccupied(SAV.DaycareIndex, i); + L_SlotOccupied[i].Visible = occ != null; + if (occ == true) // If Occupied { - case Keys.Control | Keys.Alt: - ClickClone(sender, e); - break; - default: // forward to contextmenu for default behavior - menu.OmniClick(sender, e, ModifierKeys); - break; + L_SlotOccupied[i].Text = $"{i + 1}: ✓"; } - } - - private void ClickBoxSort(object sender, MouseEventArgs e) - { - if (tabBoxMulti.SelectedTab != Tab_Box) - return; - if (!tabBoxMulti.GetTabRect(tabBoxMulti.SelectedIndex).Contains(PointToClient(MousePosition))) - return; - if ((e.Button & MouseButtons.Right) == 0) - { - if ((ModifierKeys & Keys.Alt) != 0) - SortMenu.Clear(); - else if ((ModifierKeys & Keys.Control) != 0) - SortMenu.Sort(); - return; - } - var pt = Tab_Box.PointToScreen(new Point(0, 0)); - SortMenu.Show(pt); - } - - public void FinishBoxManipulation(string message, bool all, int count) - { - SetPKMBoxes(); - UpdateBoxViewers(all); - if (!string.IsNullOrWhiteSpace(message)) - WinFormsUtil.Alert(message + $" ({count})"); else - SystemSounds.Asterisk.Play(); + { + L_SlotOccupied[i].Text = $"{i + 1}: ✘"; + var current = pb.Image; + if (current != null) + pb.Image = ImageUtil.ChangeOpacity(current, 0.6); + } } - private void ClickBoxDouble(object sender, MouseEventArgs e) + bool? egg = SAV.IsDaycareHasEgg(SAV.DaycareIndex); + DayCare_HasEgg.Visible = egg != null; + DayCare_HasEgg.Checked = egg == true; + + var seed = SAV.GetDaycareRNGSeed(SAV.DaycareIndex); + bool hasSeed = !string.IsNullOrEmpty(seed); + if (hasSeed) + { + TB_RNGSeed.MaxLength = SAV.DaycareSeedSize; + TB_RNGSeed.Text = seed; + } + L_DaycareSeed.Visible = TB_RNGSeed.Visible = hasSeed; + } + + private PictureBox UpdateSlot(int relIndex) + { + var info = GetSlotData(relIndex); + var pb = SlotPictureBoxes[relIndex]; + SlotUtil.UpdateSlot(pb, info, info.Read(SAV), SAV, Box.FlagIllegal); + return pb; + } + + public void SetParty() => ResetParty(); + + public void ClickUndo() + { + EditEnv.Slots.Undo(); + UpdateUndoRedo(); + } + + public void ClickRedo() + { + EditEnv.Slots.Redo(); + UpdateUndoRedo(); + } + + public void UpdateUndoRedo() + { + Menu_Undo.Enabled = EditEnv.Slots.Changelog.CanUndo; + Menu_Redo.Enabled = EditEnv.Slots.Changelog.CanRedo; + } + + public void SetClonesToBox(PKM pk) + { + if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, string.Format(MsgSaveBoxCloneFromTabs, Box.CurrentBoxName)) != DialogResult.Yes) + return; + + int slotSkipped = SetClonesToCurrentBox(pk, Box.CurrentBox); + if (slotSkipped > 0) + WinFormsUtil.Alert(string.Format(MsgSaveBoxImportSkippedLocked, slotSkipped)); + + UpdateBoxViewers(); + } + + private int SetClonesToCurrentBox(PKM pk, int box) + { + var arr = new PKM[SAV.BoxSlotCount]; + for (int i = 0; i < SAV.BoxSlotCount; i++) // set to every slot in box + arr[i] = pk; + + int slotSkipped = SAV.SetBoxData(arr, box); + Box.ResetSlots(); + return slotSkipped; + } + + public void ClickSlot(object sender, EventArgs e) + { + switch (ModifierKeys) + { + case Keys.Control | Keys.Alt: + ClickClone(sender, e); + break; + default: // forward to contextmenu for default behavior + menu.OmniClick(sender, e, ModifierKeys); + break; + } + } + + private void ClickBoxSort(object sender, MouseEventArgs e) + { + if (tabBoxMulti.SelectedTab != Tab_Box) + return; + if (!tabBoxMulti.GetTabRect(tabBoxMulti.SelectedIndex).Contains(PointToClient(MousePosition))) + return; + if ((e.Button & MouseButtons.Right) == 0) + { + if ((ModifierKeys & Keys.Alt) != 0) + SortMenu.Clear(); + else if ((ModifierKeys & Keys.Control) != 0) + SortMenu.Sort(); + return; + } + var pt = Tab_Box.PointToScreen(new Point(0, 0)); + SortMenu.Show(pt); + } + + public void FinishBoxManipulation(string message, bool all, int count) + { + SetPKMBoxes(); + UpdateBoxViewers(all); + if (!string.IsNullOrWhiteSpace(message)) + WinFormsUtil.Alert(message + $" ({count})"); + else + SystemSounds.Asterisk.Play(); + } + + private void ClickBoxDouble(object sender, MouseEventArgs e) + { + if (tabBoxMulti.SelectedTab == Tab_SAV) + { + RequestReloadSave?.Invoke(sender, e); + return; + } + if (tabBoxMulti.SelectedTab != Tab_Box) + return; + if (!SAV.HasBox) + return; + if (ModifierKeys == Keys.Shift) { - if (tabBoxMulti.SelectedTab == Tab_SAV) - { - RequestReloadSave?.Invoke(sender, e); - return; - } - if (tabBoxMulti.SelectedTab != Tab_Box) - return; - if (!SAV.HasBox) - return; - if (ModifierKeys == Keys.Shift) - { - if (M.Boxes.Count > 1) // subview open - { - // close all subviews - for (int i = 1; i < M.Boxes.Count; i++) - M.Boxes[i].ParentForm?.Close(); - } - new SAV_BoxList(this, M).Show(); - return; - } if (M.Boxes.Count > 1) // subview open { - var z = M.Boxes[1].ParentForm; - if (z == null) - return; - z.CenterToForm(ParentForm); - z.BringToFront(); + // close all subviews + for (int i = 1; i < M.Boxes.Count; i++) + M.Boxes[i].ParentForm?.Close(); + } + new SAV_BoxList(this, M).Show(); + return; + } + if (M.Boxes.Count > 1) // subview open + { + var z = M.Boxes[1].ParentForm; + if (z == null) return; - } - new SAV_BoxViewer(this, M).Show(); + z.CenterToForm(ParentForm); + z.BringToFront(); + return; } - - private void ClickClone(object sender, EventArgs e) - { - var detail = Box.GetSlotData((PictureBox) sender); - if (detail is SlotInfoBox) - RequestCloneData?.Invoke(sender, e); - } - - private void UpdateSaveSlot(object sender, EventArgs e) - { - if (SAV is not SAV4BR br) - return; - br.CurrentSlot = WinFormsUtil.GetIndex(CB_SaveSlot); - Box.ResetBoxNames(); // fix box names - SetPKMBoxes(); - UpdateBoxViewers(true); - } - - private void UpdateStringSeed(object sender, EventArgs e) - { - if (!FieldsLoaded) - return; - - if (sender is not TextBox tb) - return; - - if (string.IsNullOrWhiteSpace(tb.Text)) - { - tb.Undo(); - return; - } - - string filterText = Util.GetOnlyHex(tb.Text); - if (string.IsNullOrWhiteSpace(filterText) || filterText.Length != tb.Text.Length) - { - WinFormsUtil.Alert(MsgProgramErrorExpectedHex, tb.Text); - tb.Undo(); - return; - } - - // Write final value back to the save - if (tb == TB_RNGSeed) - { - var value = filterText.PadLeft(SAV.DaycareSeedSize, '0'); - SAV.SetDaycareRNGSeed(SAV.DaycareIndex, value); - SAV.State.Edited = true; - } - else if (tb == TB_GameSync && SAV is IGameSync sync) - { - var value = filterText.PadLeft(sync.GameSyncIDSize, '0'); - sync.GameSyncID = value; - SAV.State.Edited = true; - } - else if (SAV is ISecureValueStorage s) - { - var value = Convert.ToUInt64(filterText, 16); - if (tb == TB_Secure1) - s.TimeStampCurrent = value; - else if (tb == TB_Secure2) - s.TimeStampPrevious = value; - SAV.State.Edited = true; - } - } - - private void SwitchDaycare(object sender, EventArgs e) - { - if (!SAV.HasTwoDaycares) - return; - var current = string.Format(MsgSaveSwitchDaycareCurrent, SAV.DaycareIndex + 1); - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveSwitchDaycareView, current)) - return; - SAV.DaycareIndex ^= 1; - ResetDaycare(); - } - - private void B_SaveBoxBin_Click(object sender, EventArgs e) - { - if (!SAV.HasBox) - { WinFormsUtil.Alert(MsgSaveBoxFailNone); return; } - Box.SaveBoxBinary(); - } - - // Subfunction Save Buttons // - private static void OpenDialog(Form f) - { - f.ShowDialog(); - f.Dispose(); - } - - private void B_OpenWondercards_Click(object sender, EventArgs e) => OpenDialog(new SAV_Wondercard(SAV, sender as DataMysteryGift)); - private void B_OpenPokepuffs_Click(object sender, EventArgs e) => OpenDialog(new SAV_Pokepuff(SAV)); - private void B_OpenPokeBeans_Click(object sender, EventArgs e) => OpenDialog(new SAV_Pokebean(SAV)); - private void B_OpenItemPouch_Click(object sender, EventArgs e) => OpenDialog(new SAV_Inventory(SAV)); - private void B_OpenBerryField_Click(object sender, EventArgs e) => OpenDialog(new SAV_BerryFieldXY((SAV6XY)SAV)); - private void B_OpenPokeblocks_Click(object sender, EventArgs e) => OpenDialog(new SAV_PokeBlockORAS(SAV)); - private void B_OpenSuperTraining_Click(object sender, EventArgs e) => OpenDialog(new SAV_SuperTrain(SAV)); - private void B_OpenSecretBase_Click(object sender, EventArgs e) => OpenDialog(new SAV_SecretBase(SAV)); - private void B_CellsStickers_Click(object sender, EventArgs e) => OpenDialog(new SAV_ZygardeCell(SAV)); - private void B_LinkInfo_Click(object sender, EventArgs e) => OpenDialog(new SAV_Link6(SAV)); - private void B_Roamer_Click(object sender, EventArgs e) => OpenDialog(new SAV_Roamer3(SAV)); - private void B_OpenApricorn_Click(object sender, EventArgs e) => OpenDialog(new SAV_Apricorn((SAV4HGSS)SAV)); - private void B_CGearSkin_Click(object sender, EventArgs e) => OpenDialog(new SAV_CGearSkin(SAV)); - private void B_OpenTrainerInfo_Click(object sender, EventArgs e) => OpenDialog(GetTrainerEditor(SAV)); - private void B_OpenOPowers_Click(object sender, EventArgs e) => OpenDialog(new SAV_OPower((ISaveBlock6Main)SAV)); - private void B_OpenHoneyTreeEditor_Click(object sender, EventArgs e) => OpenDialog(new SAV_HoneyTree((SAV4Sinnoh)SAV)); - - private void B_OpenEventFlags_Click(object sender, EventArgs e) - { - using var form = SAV switch - { - SAV1 s => (Form) new SAV_EventReset1(s), - SAV7b s => new SAV_EventWork(s), - SAV8BS s => new SAV_FlagWork8b(s), - IEventFlag37 g37 => new SAV_EventFlags(g37), - SAV2 s => new SAV_EventFlags2(s), - _ => throw new Exception(), - }; - form.ShowDialog(); - } - - private void B_OpenBoxLayout_Click(object sender, EventArgs e) - { - OpenDialog(new SAV_BoxLayout(SAV, Box.CurrentBox)); - Box.ResetBoxNames(); // fix box names - Box.ResetSlots(); // refresh box background - UpdateBoxViewers(all: true); // update subviewers - } - - private static Form GetTrainerEditor(SaveFile sav) => sav switch - { - SAV6 s6 => new SAV_Trainer(s6), - SAV7 s7 => new SAV_Trainer7(s7), - SAV7b b7 => new SAV_Trainer7GG(b7), - SAV8SWSH swsh => new SAV_Trainer8(swsh), - SAV8BS bs => new SAV_Trainer8b(bs), - SAV8LA la => new SAV_Trainer8a(la), - _ => new SAV_SimpleTrainer(sav), - }; - - private void B_OpenRaids_Click(object sender, EventArgs e) - { - if (SAV is not SAV8SWSH swsh) - return; - if (sender == B_Raids) - OpenDialog(new SAV_Raid8(swsh, swsh.Raid)); - else if (sender == B_RaidArmor) - OpenDialog(new SAV_Raid8(swsh, swsh.RaidArmor)); - else - OpenDialog(new SAV_Raid8(swsh, swsh.RaidCrown)); - } - - private void B_OtherSlots_Click(object sender, EventArgs e) - { - void TryOpen(SaveFile sav, IReadOnlyList g) - { - var form = WinFormsUtil.FirstFormOfType(); - if (form != null) - form.CenterToForm(ParentForm); - else - form = new SAV_GroupViewer(sav, M.Env.PKMEditor, g); - form.BringToFront(); - form.Show(); - } - - if (SAV is SAV_STADIUM s0) - TryOpen(s0, s0.GetRegisteredTeams()); - } - - private void B_Blocks_Click(object sender, EventArgs e) - { - var form = GetAccessorForm(SAV); - form.ShowDialog(); - form.Dispose(); - } - - private static Form GetAccessorForm(SaveFile sav) => sav switch - { - SAV5BW s => new SAV_Accessor(s, s.Blocks), - SAV5B2W2 s => new SAV_Accessor(s, s.Blocks), - SAV6XY s => new SAV_Accessor(s, s.Blocks), - SAV6AO s => new SAV_Accessor(s, s.Blocks), - SAV6AODemo s => new SAV_Accessor(s, s.Blocks), - SAV7SM s => new SAV_Accessor(s, s.Blocks), - SAV7USUM s => new SAV_Accessor(s, s.Blocks), - SAV7b s => new SAV_Accessor(s, s.Blocks), - ISCBlockArray s => new SAV_BlockDump8(s), - _ => GetPropertyForm(sav), - }; - - private static Form GetPropertyForm(object sav) - { - var form = new Form - { - Text = "Simple Editor", - StartPosition = FormStartPosition.CenterParent, - MinimumSize = new Size(350, 380), - MinimizeBox = false, - MaximizeBox = false, - Icon = Properties.Resources.Icon, - }; - var pg = new PropertyGrid {SelectedObject = sav, Dock = DockStyle.Fill}; - form.Controls.Add(pg); - return form; - } - - private void B_OpenFriendSafari_Click(object sender, EventArgs e) - { - if (SAV is not SAV6XY xy) - return; - - var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveGen6FriendSafari, MsgSaveGen6FriendSafariCheatDesc); - if (dr == DialogResult.Yes) - xy.UnlockAllFriendSafariSlots(); - } - - private void B_OpenPokedex_Click(object sender, EventArgs e) - { - using var form = SAV switch - { - SAV1 s1 => new SAV_SimplePokedex(s1), - SAV2 s2 => new SAV_SimplePokedex(s2), - SAV3 s3 => new SAV_SimplePokedex(s3), - SAV4 s4 => new SAV_Pokedex4(s4), - SAV5 s5 => new SAV_Pokedex5(s5), - SAV6XY xy => new SAV_PokedexXY(xy), - SAV6AO ao => new SAV_PokedexORAS(ao), - SAV7 s7 => new SAV_PokedexSM(s7), - SAV7b b7 => new SAV_PokedexGG(b7), - SAV8SWSH swsh => new SAV_PokedexSWSH(swsh), - SAV8BS bs => new SAV_PokedexBDSP(bs), - SAV8LA la => new SAV_PokedexLA(la), - _ => (Form?)null, - }; - form?.ShowDialog(); - } - - private void B_OpenMiscEditor_Click(object sender, EventArgs e) - { - using var form = SAV.Generation switch - { - 3 => new SAV_Misc3(SAV), - 4 => new SAV_Misc4((SAV4) SAV), - 5 => new SAV_Misc5(SAV), - 8 when SAV is SAV8BS bs => new SAV_Misc8b(bs), - _ => (Form?)null, - }; - form?.ShowDialog(); - } - - private void B_OpenRTCEditor_Click(object sender, EventArgs e) - { - switch (SAV.Generation) - { - case 2: - var sav2 = ((SAV2) SAV); - var msg = MsgSaveGen2RTCResetBitflag; - if (!sav2.Japanese) // show Reset Key for non-Japanese saves - msg = string.Format(MsgSaveGen2RTCResetPassword, sav2.ResetKey) + Environment.NewLine + Environment.NewLine + msg; - var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, msg); - if (dr == DialogResult.Yes) - sav2.ResetRTC(); - break; - case 3: - OpenDialog(new SAV_RTC3(SAV)); - break; - } - } - - private void B_OUTPasserby_Click(object sender, EventArgs e) - { - if (SAV.Generation != 6) - return; - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveGen6Passerby)) - return; - var result = PSS6.GetPSSParse((SAV6)SAV); - WinFormsUtil.SetClipboardText(string.Join(Environment.NewLine, result)); - } - - private void B_OUTHallofFame_Click(object sender, EventArgs e) - { - using var form = SAV switch - { - SAV6 s6 => new SAV_HallOfFame(s6), - SAV7 s7 => new SAV_HallOfFame7(s7), - _ => (Form?)null, - }; - form?.ShowDialog(); - } - - private void B_JPEG_Click(object sender, EventArgs e) - { - var s6 = (SAV6)SAV; - byte[] jpeg = s6.GetJPEGData(); - if (jpeg.Length == 0) - { - WinFormsUtil.Alert(MsgSaveJPEGExportFail); - return; - } - string filename = $"{s6.JPEGTitle}'s picture"; - using var sfd = new SaveFileDialog { FileName = filename, Filter = "JPEG|*.jpeg" }; - if (sfd.ShowDialog() != DialogResult.OK) - return; - File.WriteAllBytes(sfd.FileName, jpeg); - } - - private void ClickVerifyCHK(object sender, EventArgs e) - { - if (SAV.State.Edited) - { - WinFormsUtil.Alert(MsgSaveChecksumFailEdited); - return; - } - if (SAV.ChecksumsValid) - { - WinFormsUtil.Alert(MsgSaveChecksumValid); - return; - } - - if (DialogResult.Yes == WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveChecksumFailExport)) - WinFormsUtil.SetClipboardText(SAV.ChecksumInfo); - } - - private void ClickVerifyStoredEntities(object sender, EventArgs e) - { - var bulk = new BulkAnalysis(SAV, Main.Settings.Bulk); - if (bulk.Parse.Count == 0) - { - WinFormsUtil.Alert("Clean!"); - return; - } - - if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgClipboardLegalityExport) != DialogResult.Yes) - return; - - var lines = bulk.Parse.Select(z => $"{z.Judgement}: {z.Comment}"); - var msg = string.Join(Environment.NewLine, lines); - WinFormsUtil.SetClipboardText(msg); - SystemSounds.Asterisk.Play(); - } - - // File I/O - public bool GetBulkImportSettings(out bool clearAll, out bool overwrite, out PKMImportSetting noSetb) - { - clearAll = false; noSetb = PKMImportSetting.UseDefault; overwrite = false; - var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, MsgSaveBoxImportClear, MsgSaveBoxImportClearNo); - if (dr == DialogResult.Cancel) - return false; - - clearAll = dr == DialogResult.Yes; - noSetb = GetPKMSetOverride(ModifyPKM); - return true; - } - - private static bool IsFolderPath(out string path) - { - using var fbd = new FolderBrowserDialog(); - var result = fbd.ShowDialog() == DialogResult.OK; - path = fbd.SelectedPath; - return result; - } - - public bool ExportSaveFile() - { - ValidateChildren(); - bool reload = SAV is SAV7b b && b.FixPreWrite(); - if (reload) - ReloadSlots(); - return WinFormsUtil.ExportSAVDialog(SAV, SAV.CurrentBox); - } - - public bool ExportBackup() - { - if (!SAV.State.Exportable || SAV.Metadata.FilePath is not { } file) - return false; - - if (!File.Exists(file)) - { - WinFormsUtil.Error(MsgSaveBackupNotFound, file); - return false; - } - - var suggestion = Util.CleanFileName(SAV.Metadata.BAKName); - using var sfd = new SaveFileDialog {FileName = suggestion}; - if (sfd.ShowDialog() != DialogResult.OK) - return false; - - string path = sfd.FileName; - if (!File.Exists(file)) // did they move it again? - { - WinFormsUtil.Error(MsgSaveBackupNotFound, file); - return false; - } - File.Copy(file, path); - WinFormsUtil.Alert(MsgSaveBackup, path); - - return true; - } - - public bool OpenPCBoxBin(byte[] input, out string c) - { - if (SAV.GetPCBinary().Length == input.Length) - { - if (SAV.IsAnySlotLockedInBox(0, SAV.BoxCount - 1)) - { c = MsgSaveBoxImportPCFailBattle; return false; } - if (!SAV.SetPCBinary(input)) - { c = string.Format(MsgSaveCurrentGeneration, SAV.Generation); return false; } - - c = MsgSaveBoxImportPCBinary; - } - else if (SAV.GetBoxBinary(Box.CurrentBox).Length == input.Length) - { - if (SAV.IsAnySlotLockedInBox(Box.CurrentBox, Box.CurrentBox)) - { c = MsgSaveBoxImportBoxFailBattle; return false; } - if (!SAV.SetBoxBinary(input, Box.CurrentBox)) - { c = string.Format(MsgSaveCurrentGeneration, SAV.Generation); return false; } - - c = MsgSaveBoxImportBoxBinary; - } - else - { - c = string.Format(MsgSaveCurrentGeneration, SAV.Generation); - return false; - } - SetPKMBoxes(); - UpdateBoxViewers(); - return true; - } - - public bool OpenGroup(IPokeGroup b, out string c) - { - var msg = string.Format(MsgSaveBoxImportGroup, Box.CurrentBoxName); - var prompt = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, msg, MsgSaveBoxImportOverwrite); - if (prompt != DialogResult.Yes) - { - c = string.Empty; - return false; - } - - var noSetb = GetPKMSetOverride(ModifyPKM); - var slotSkipped = ImportGroup(b.Contents, SAV, Box.CurrentBox, noSetb); - - SetPKMBoxes(); - UpdateBoxViewers(); - - c = slotSkipped > 0 ? string.Format(MsgSaveBoxImportSkippedLocked, slotSkipped) : MsgSaveBoxImportGroupSuccess; - - return true; - } - - private static int ImportGroup(IEnumerable data, SaveFile sav, int box, PKMImportSetting noSetb) - { - var type = sav.PKMType; - int slotSkipped = 0; - int index = 0; - foreach (var x in data) - { - var i = index++; - if (sav.IsSlotOverwriteProtected(box, i)) - { - slotSkipped++; - continue; - } - - var convert = EntityConverter.ConvertToType(x, type, out _); - if (convert?.GetType() != type) - { - slotSkipped++; - continue; - } - sav.SetBoxSlotAtIndex(x, box, i, noSetb); - } - - return slotSkipped; - } - - public bool DumpBoxes(out string result, string? path = null, bool separate = false) - { - if (path == null && !IsFolderPath(out path)) - { - result = path; - return false; - } - - Directory.CreateDirectory(path); - - var count = SAV.DumpBoxes(path, separate); - if (count < 0) - result = MsgSaveBoxExportInvalid; - else - result = string.Format(MsgSaveBoxExportPathCount, count) + Environment.NewLine + path; - return true; - } - - public bool DumpBox(out string result, string? path = null) - { - if (path == null && !IsFolderPath(out path)) - { - result = path; - return false; - } - - Directory.CreateDirectory(path); - - var count = SAV.DumpBox(path, Box.CurrentBox); - if (count < 0) - result = MsgSaveBoxExportInvalid; - else - result = string.Format(MsgSaveBoxExportPathCount, count) + Environment.NewLine + path; - return true; - } - - public bool LoadBoxes(out string result, string? path = null) - { - result = string.Empty; - if (!SAV.HasBox) - return false; - - if (path == null && !IsFolderPath(out path)) - { - result = path; - return false; - } - - if (!Directory.Exists(path)) - return false; - - if (!GetBulkImportSettings(out bool clearAll, out var overwrite, out var noSetb)) - return false; - - SAV.LoadBoxes(path, out result, Box.CurrentBox, clearAll, overwrite, noSetb); - SetPKMBoxes(); - UpdateBoxViewers(); - return true; - } - - public bool ToggleInterface() - { - FieldsLoaded = false; - - ToggleViewReset(); - ToggleViewSubEditors(SAV); - - bool WindowTranslationRequired = false; - WindowTranslationRequired |= ToggleViewBox(SAV); - int BoxTab = tabBoxMulti.TabPages.IndexOf(Tab_Box); - WindowTranslationRequired |= ToggleViewParty(SAV, BoxTab); - int PartyTab = tabBoxMulti.TabPages.IndexOf(Tab_PartyBattle); - WindowTranslationRequired |= ToggleViewDaycare(SAV, BoxTab, PartyTab); - SetPKMBoxes(); // Reload all of the PKX Windows - - ToggleViewMisc(SAV); - - FieldsLoaded = true; - return WindowTranslationRequired; - } - - private void ToggleViewReset() - { - // Close subforms that are save dependent - foreach (var z in M.Boxes.Skip(1).ToArray()) - z.FindForm()?.Close(); - - Box.M = M; - SL_Party.M = M; - if (SAV.HasBox) - { - bool newSlots = Box.InitializeFromSAV(SAV); - if (newSlots) - { - Box.HorizontallyCenter(Tab_Box); - foreach (var pb in Box.SlotPictureBoxes) - pb.ContextMenuStrip = menu.mnuVSD; - - var grid = Box.BoxPokeGrid; - var height = grid.Height + grid.Location.Y + Box.Location.Y; // needed height - var required = height + 16; - var allowed = Tab_Box.Height; - if (required > allowed) - { - var form = FindForm(); - if (form != null) - form.Height += required - allowed; - } - } - } - if (SAV.HasParty) - { - bool newSlots = SL_Party.InitializeFromSAV(SAV); - if (newSlots) - { - SL_Party.HorizontallyCenter(Tab_PartyBattle); - foreach (var pb in SL_Party.SlotPictureBoxes) - pb.ContextMenuStrip = menu.mnuVSD; - } - } - - SortMenu.ToggleVisibility(); - } - - private bool ToggleViewBox(SaveFile sav) - { - if (!sav.HasBox) - { - if (tabBoxMulti.TabPages.Contains(Tab_Box)) - tabBoxMulti.TabPages.Remove(Tab_Box); - B_SaveBoxBin.Enabled = false; - return false; - } - - B_SaveBoxBin.Enabled = true; - tabBoxMulti.SelectedIndex = 0; - - var box = sav.CurrentBox; - Box.CurrentBox = (uint)box >= sav.BoxCount ? 0 : box; - - if (tabBoxMulti.TabPages.Contains(Tab_Box)) - return false; - tabBoxMulti.TabPages.Insert(0, Tab_Box); - return true; - } - - private bool ToggleViewParty(SaveFile sav, int BoxTab) - { - if (!sav.HasParty || !sav.State.Exportable) - { - if (tabBoxMulti.TabPages.Contains(Tab_PartyBattle)) - tabBoxMulti.TabPages.Remove(Tab_PartyBattle); - return false; - } - - if (tabBoxMulti.TabPages.Contains(Tab_PartyBattle)) - return false; - - int index = BoxTab; - if (index < 0) - index = -1; - tabBoxMulti.TabPages.Insert(index + 1, Tab_PartyBattle); - return true; - } - - private bool ToggleViewDaycare(SaveFile sav, int BoxTab, int PartyTab) - { - if ((!sav.HasDaycare && SL_Extra.SlotCount == 0) || !sav.State.Exportable) - { - if (tabBoxMulti.TabPages.Contains(Tab_Other)) - tabBoxMulti.TabPages.Remove(Tab_Other); - return false; - } - - SlotPictureBoxes[1].Visible = sav.Generation >= 2; // Second daycare slot - if (tabBoxMulti.TabPages.Contains(Tab_Other)) - return false; - - int index = PartyTab; - if (index < 0) - index = BoxTab; - if (index < 0) - index = -1; - tabBoxMulti.TabPages.Insert(index + 1, Tab_Other); - return true; - } - - private void ToggleViewSubEditors(SaveFile sav) - { - if (!sav.State.Exportable || sav is BulkStorage) - { - GB_SAVtools.Visible = false; - B_JPEG.Visible = false; - SL_Extra.HideAllSlots(); - return; - } - - GB_Daycare.Visible = sav.HasDaycare; - B_OpenPokeblocks.Visible = sav is SAV6AO; - B_OpenSecretBase.Visible = sav is SAV6AO; - B_OpenPokepuffs.Visible = sav is ISaveBlock6Main; - B_JPEG.Visible = B_OpenLinkInfo.Visible = B_OpenSuperTraining.Visible = B_OUTPasserby.Visible = sav is ISaveBlock6Main; - B_OpenBoxLayout.Visible = sav.HasNamableBoxes; - B_OpenWondercards.Visible = sav.HasWondercards; - B_OpenHallofFame.Visible = sav is ISaveBlock6Main or SAV7; - B_OpenOPowers.Visible = sav is ISaveBlock6Main; - B_OpenPokedex.Visible = sav.HasPokeDex; - B_OpenBerryField.Visible = sav is SAV6XY; // oras undocumented - B_OpenFriendSafari.Visible = sav is SAV6XY; - B_OpenEventFlags.Visible = sav is IEventFlag37 or (SAV1 or SAV2 or SAV8BS or SAV7b); - B_CGearSkin.Visible = sav.Generation == 5; - B_OpenPokeBeans.Visible = B_CellsStickers.Visible = B_FestivalPlaza.Visible = sav is SAV7; - - B_OtherSlots.Visible = sav is SAV1StadiumJ or SAV1Stadium or SAV2Stadium; - B_OpenTrainerInfo.Visible = B_OpenItemPouch.Visible = (sav.HasParty && SAV is not SAV4BR) || SAV is SAV7b; // Box RS & Battle Revolution - B_OpenMiscEditor.Visible = sav is SAV3 or SAV4 or SAV5 or SAV8BS; - B_Roamer.Visible = sav is SAV3; - - B_OpenHoneyTreeEditor.Visible = sav is SAV4Sinnoh; - B_OpenUGSEditor.Visible = sav is SAV4Sinnoh or SAV8BS; - B_OpenSealStickers.Visible = B_Poffins.Visible = sav is SAV8BS; - B_OpenApricorn.Visible = sav is SAV4HGSS; - B_OpenRTCEditor.Visible = sav.Generation == 2 || sav is IGen3Hoenn; - B_MailBox.Visible = sav is SAV2 or SAV3 or SAV4 or SAV5; - - B_Raids.Visible = sav is SAV8SWSH; - B_RaidArmor.Visible = sav is SAV8SWSH {SaveRevision: >= 1}; - B_RaidCrown.Visible = sav is SAV8SWSH {SaveRevision: >= 2}; - GB_SAVtools.Visible = B_Blocks.Visible = true; - - var list = FLP_SAVtools.Controls.OfType().OrderBy(z => z.Text).ToArray(); - FLP_SAVtools.Controls.Clear(); - FLP_SAVtools.Controls.AddRange(list); - - SL_Extra.SAV = sav; - SL_Extra.Initialize(sav.GetExtraSlots(HaX), InitializeDragDrop); - } - - private void ToggleViewMisc(SaveFile sav) - { - // Generational Interface - ToggleSecrets(sav, HideSecretDetails); - B_VerifyCHK.Visible = SAV.State.Exportable; - Menu_ExportBAK.Visible = SAV.State.Exportable && SAV.Metadata.FilePath is not null; - - if (sav is SAV4BR br) - { - L_SaveSlot.Visible = CB_SaveSlot.Visible = true; - var current = br.CurrentSlot; - var list = br.SaveNames.Select((z, i) => new ComboItem(z, i)).ToList(); - CB_SaveSlot.InitializeBinding(); - CB_SaveSlot.DataSource = new BindingSource(list, null); - CB_SaveSlot.SelectedValue = current; - } - else - { - L_SaveSlot.Visible = CB_SaveSlot.Visible = false; - } - - if (sav is ISecureValueStorage s) - { - TB_Secure1.Text = s.TimeStampCurrent.ToString("X16"); - TB_Secure2.Text = s.TimeStampPrevious.ToString("X16"); - } - - if (sav is IGameSync sync) - { - var gsid = sync.GameSyncID; - TB_GameSync.Enabled = !string.IsNullOrEmpty(gsid); - TB_GameSync.MaxLength = sync.GameSyncIDSize; - TB_GameSync.Text = (string.IsNullOrEmpty(gsid) ? 0.ToString() : gsid).PadLeft(sync.GameSyncIDSize, '0'); - } - } - - private void ToggleSecrets(SaveFile sav, bool hide) - { - var shouldShow = sav.State.Exportable && !hide; - TB_Secure1.Visible = TB_Secure2.Visible = L_Secure1.Visible = L_Secure2.Visible = shouldShow && sav is ISecureValueStorage; - TB_GameSync.Visible = L_GameSync.Visible = shouldShow && sav is IGameSync; - } - - // DragDrop - private void MultiDragOver(object sender, DragEventArgs e) - { - // iterate over all tabs to see if a tab switch should occur when drag/dropping - Point pt = tabBoxMulti.PointToClient(new Point(e.X, e.Y)); - for (int i = 0; i < tabBoxMulti.TabCount; i++) - { - if (tabBoxMulti.SelectedIndex == i || !tabBoxMulti.GetTabRect(i).Contains(pt)) - continue; - tabBoxMulti.SelectedIndex = i; - return; - } - } - - public void ClickShowdownExportParty(object sender, EventArgs e) => ExportShowdownText(SAV, MsgSimulatorExportParty, sav => sav.PartyData); - - public void ClickShowdownExportCurrentBox(object sender, EventArgs e) - { - if (!SAV.HasBox) - return; - ExportShowdownText(SAV, MsgSimulatorExportList, - sav => (ModifierKeys & Keys.Control) != 0 ? sav.BoxData : sav.GetBoxData(CurrentBox)); - } - - private static void ExportShowdownText(SaveFile sav, string success, Func> fetch) - { - var list = fetch(sav); - var result = ShowdownParsing.GetShowdownSets(list, Environment.NewLine + Environment.NewLine); - if (string.IsNullOrWhiteSpace(result)) - return; - if (WinFormsUtil.SetClipboardText(result)) - WinFormsUtil.Alert(success); - } - - private void B_OpenUGSEditor_Click(object sender, EventArgs e) - { - Form form; - if (SAV is SAV4Sinnoh s) - form = new SAV_Underground(s); - else if (SAV is SAV8BS bs) - form = new SAV_Underground8b(bs); - else - return; - form.ShowDialog(); - form.Dispose(); - } - - private void B_OpenSealStickers_Click(object sender, EventArgs e) - { - if (SAV is not SAV8BS bs) - return; - using var form = new SAV_SealStickers8b(bs); - form.ShowDialog(); - } - - private void B_Poffins_Click(object sender, EventArgs e) - { - if (SAV is not SAV8BS bs) - return; - using var form = new SAV_Poffin8b(bs); - form.ShowDialog(); - } - - private void B_FestivalPlaza_Click(object sender, EventArgs e) - { - if (SAV is not SAV7 s) - return; - using var form = new SAV_FestivalPlaza(s); - form.ShowDialog(); - } - - private void B_MailBox_Click(object sender, EventArgs e) - { - using var form = new SAV_MailBox(SAV); - form.ShowDialog(); - ResetParty(); - } - - private static PKMImportSetting GetPKMSetOverride(bool currentSetting) - { - var yn = currentSetting ? MsgYes : MsgNo; - var choice = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, - MsgSaveBoxImportModifyIntro, - MsgSaveBoxImportModifyYes + Environment.NewLine + - MsgSaveBoxImportModifyNo + Environment.NewLine + - string.Format(MsgSaveBoxImportModifyCurrent, yn)); - return choice switch - { - DialogResult.Yes => PKMImportSetting.Update, - DialogResult.No => PKMImportSetting.Skip, - _ => PKMImportSetting.UseDefault, - }; - } - - private void Menu_ExportBAK_Click(object sender, EventArgs e) => ExportBackup(); + new SAV_BoxViewer(this, M).Show(); } + + private void ClickClone(object sender, EventArgs e) + { + var detail = Box.GetSlotData((PictureBox) sender); + if (detail is SlotInfoBox) + RequestCloneData?.Invoke(sender, e); + } + + private void UpdateSaveSlot(object sender, EventArgs e) + { + if (SAV is not SAV4BR br) + return; + br.CurrentSlot = WinFormsUtil.GetIndex(CB_SaveSlot); + Box.ResetBoxNames(); // fix box names + SetPKMBoxes(); + UpdateBoxViewers(true); + } + + private void UpdateStringSeed(object sender, EventArgs e) + { + if (!FieldsLoaded) + return; + + if (sender is not TextBox tb) + return; + + if (string.IsNullOrWhiteSpace(tb.Text)) + { + tb.Undo(); + return; + } + + string filterText = Util.GetOnlyHex(tb.Text); + if (string.IsNullOrWhiteSpace(filterText) || filterText.Length != tb.Text.Length) + { + WinFormsUtil.Alert(MsgProgramErrorExpectedHex, tb.Text); + tb.Undo(); + return; + } + + // Write final value back to the save + if (tb == TB_RNGSeed) + { + var value = filterText.PadLeft(SAV.DaycareSeedSize, '0'); + SAV.SetDaycareRNGSeed(SAV.DaycareIndex, value); + SAV.State.Edited = true; + } + else if (tb == TB_GameSync && SAV is IGameSync sync) + { + var value = filterText.PadLeft(sync.GameSyncIDSize, '0'); + sync.GameSyncID = value; + SAV.State.Edited = true; + } + else if (SAV is ISecureValueStorage s) + { + var value = Convert.ToUInt64(filterText, 16); + if (tb == TB_Secure1) + s.TimeStampCurrent = value; + else if (tb == TB_Secure2) + s.TimeStampPrevious = value; + SAV.State.Edited = true; + } + } + + private void SwitchDaycare(object sender, EventArgs e) + { + if (!SAV.HasTwoDaycares) + return; + var current = string.Format(MsgSaveSwitchDaycareCurrent, SAV.DaycareIndex + 1); + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveSwitchDaycareView, current)) + return; + SAV.DaycareIndex ^= 1; + ResetDaycare(); + } + + private void B_SaveBoxBin_Click(object sender, EventArgs e) + { + if (!SAV.HasBox) + { WinFormsUtil.Alert(MsgSaveBoxFailNone); return; } + Box.SaveBoxBinary(); + } + + // Subfunction Save Buttons // + private static void OpenDialog(Form f) + { + f.ShowDialog(); + f.Dispose(); + } + + private void B_OpenWondercards_Click(object sender, EventArgs e) => OpenDialog(new SAV_Wondercard(SAV, sender as DataMysteryGift)); + private void B_OpenPokepuffs_Click(object sender, EventArgs e) => OpenDialog(new SAV_Pokepuff(SAV)); + private void B_OpenPokeBeans_Click(object sender, EventArgs e) => OpenDialog(new SAV_Pokebean(SAV)); + private void B_OpenItemPouch_Click(object sender, EventArgs e) => OpenDialog(new SAV_Inventory(SAV)); + private void B_OpenBerryField_Click(object sender, EventArgs e) => OpenDialog(new SAV_BerryFieldXY((SAV6XY)SAV)); + private void B_OpenPokeblocks_Click(object sender, EventArgs e) => OpenDialog(new SAV_PokeBlockORAS(SAV)); + private void B_OpenSuperTraining_Click(object sender, EventArgs e) => OpenDialog(new SAV_SuperTrain(SAV)); + private void B_OpenSecretBase_Click(object sender, EventArgs e) => OpenDialog(new SAV_SecretBase(SAV)); + private void B_CellsStickers_Click(object sender, EventArgs e) => OpenDialog(new SAV_ZygardeCell(SAV)); + private void B_LinkInfo_Click(object sender, EventArgs e) => OpenDialog(new SAV_Link6(SAV)); + private void B_Roamer_Click(object sender, EventArgs e) => OpenDialog(new SAV_Roamer3(SAV)); + private void B_OpenApricorn_Click(object sender, EventArgs e) => OpenDialog(new SAV_Apricorn((SAV4HGSS)SAV)); + private void B_CGearSkin_Click(object sender, EventArgs e) => OpenDialog(new SAV_CGearSkin(SAV)); + private void B_OpenTrainerInfo_Click(object sender, EventArgs e) => OpenDialog(GetTrainerEditor(SAV)); + private void B_OpenOPowers_Click(object sender, EventArgs e) => OpenDialog(new SAV_OPower((ISaveBlock6Main)SAV)); + private void B_OpenHoneyTreeEditor_Click(object sender, EventArgs e) => OpenDialog(new SAV_HoneyTree((SAV4Sinnoh)SAV)); + + private void B_OpenEventFlags_Click(object sender, EventArgs e) + { + using var form = SAV switch + { + SAV1 s => (Form) new SAV_EventReset1(s), + SAV7b s => new SAV_EventWork(s), + SAV8BS s => new SAV_FlagWork8b(s), + IEventFlag37 g37 => new SAV_EventFlags(g37), + SAV2 s => new SAV_EventFlags2(s), + _ => throw new Exception(), + }; + form.ShowDialog(); + } + + private void B_OpenBoxLayout_Click(object sender, EventArgs e) + { + OpenDialog(new SAV_BoxLayout(SAV, Box.CurrentBox)); + Box.ResetBoxNames(); // fix box names + Box.ResetSlots(); // refresh box background + UpdateBoxViewers(all: true); // update subviewers + } + + private static Form GetTrainerEditor(SaveFile sav) => sav switch + { + SAV6 s6 => new SAV_Trainer(s6), + SAV7 s7 => new SAV_Trainer7(s7), + SAV7b b7 => new SAV_Trainer7GG(b7), + SAV8SWSH swsh => new SAV_Trainer8(swsh), + SAV8BS bs => new SAV_Trainer8b(bs), + SAV8LA la => new SAV_Trainer8a(la), + _ => new SAV_SimpleTrainer(sav), + }; + + private void B_OpenRaids_Click(object sender, EventArgs e) + { + if (SAV is not SAV8SWSH swsh) + return; + if (sender == B_Raids) + OpenDialog(new SAV_Raid8(swsh, swsh.Raid)); + else if (sender == B_RaidArmor) + OpenDialog(new SAV_Raid8(swsh, swsh.RaidArmor)); + else + OpenDialog(new SAV_Raid8(swsh, swsh.RaidCrown)); + } + + private void B_OtherSlots_Click(object sender, EventArgs e) + { + void TryOpen(SaveFile sav, IReadOnlyList g) + { + var form = WinFormsUtil.FirstFormOfType(); + if (form != null) + form.CenterToForm(ParentForm); + else + form = new SAV_GroupViewer(sav, M.Env.PKMEditor, g); + form.BringToFront(); + form.Show(); + } + + if (SAV is SAV_STADIUM s0) + TryOpen(s0, s0.GetRegisteredTeams()); + } + + private void B_Blocks_Click(object sender, EventArgs e) + { + var form = GetAccessorForm(SAV); + form.ShowDialog(); + form.Dispose(); + } + + private static Form GetAccessorForm(SaveFile sav) => sav switch + { + SAV5BW s => new SAV_Accessor(s, s.Blocks), + SAV5B2W2 s => new SAV_Accessor(s, s.Blocks), + SAV6XY s => new SAV_Accessor(s, s.Blocks), + SAV6AO s => new SAV_Accessor(s, s.Blocks), + SAV6AODemo s => new SAV_Accessor(s, s.Blocks), + SAV7SM s => new SAV_Accessor(s, s.Blocks), + SAV7USUM s => new SAV_Accessor(s, s.Blocks), + SAV7b s => new SAV_Accessor(s, s.Blocks), + ISCBlockArray s => new SAV_BlockDump8(s), + _ => GetPropertyForm(sav), + }; + + private static Form GetPropertyForm(object sav) + { + var form = new Form + { + Text = "Simple Editor", + StartPosition = FormStartPosition.CenterParent, + MinimumSize = new Size(350, 380), + MinimizeBox = false, + MaximizeBox = false, + Icon = Properties.Resources.Icon, + }; + var pg = new PropertyGrid {SelectedObject = sav, Dock = DockStyle.Fill}; + form.Controls.Add(pg); + return form; + } + + private void B_OpenFriendSafari_Click(object sender, EventArgs e) + { + if (SAV is not SAV6XY xy) + return; + + var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveGen6FriendSafari, MsgSaveGen6FriendSafariCheatDesc); + if (dr == DialogResult.Yes) + xy.UnlockAllFriendSafariSlots(); + } + + private void B_OpenPokedex_Click(object sender, EventArgs e) + { + using var form = SAV switch + { + SAV1 s1 => new SAV_SimplePokedex(s1), + SAV2 s2 => new SAV_SimplePokedex(s2), + SAV3 s3 => new SAV_SimplePokedex(s3), + SAV4 s4 => new SAV_Pokedex4(s4), + SAV5 s5 => new SAV_Pokedex5(s5), + SAV6XY xy => new SAV_PokedexXY(xy), + SAV6AO ao => new SAV_PokedexORAS(ao), + SAV7 s7 => new SAV_PokedexSM(s7), + SAV7b b7 => new SAV_PokedexGG(b7), + SAV8SWSH swsh => new SAV_PokedexSWSH(swsh), + SAV8BS bs => new SAV_PokedexBDSP(bs), + SAV8LA la => new SAV_PokedexLA(la), + _ => (Form?)null, + }; + form?.ShowDialog(); + } + + private void B_OpenMiscEditor_Click(object sender, EventArgs e) + { + using var form = SAV.Generation switch + { + 3 => new SAV_Misc3(SAV), + 4 => new SAV_Misc4((SAV4) SAV), + 5 => new SAV_Misc5(SAV), + 8 when SAV is SAV8BS bs => new SAV_Misc8b(bs), + _ => (Form?)null, + }; + form?.ShowDialog(); + } + + private void B_OpenRTCEditor_Click(object sender, EventArgs e) + { + switch (SAV.Generation) + { + case 2: + var sav2 = ((SAV2) SAV); + var msg = MsgSaveGen2RTCResetBitflag; + if (!sav2.Japanese) // show Reset Key for non-Japanese saves + msg = string.Format(MsgSaveGen2RTCResetPassword, sav2.ResetKey) + Environment.NewLine + Environment.NewLine + msg; + var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, msg); + if (dr == DialogResult.Yes) + sav2.ResetRTC(); + break; + case 3: + OpenDialog(new SAV_RTC3(SAV)); + break; + } + } + + private void B_OUTPasserby_Click(object sender, EventArgs e) + { + if (SAV.Generation != 6) + return; + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveGen6Passerby)) + return; + var result = PSS6.GetPSSParse((SAV6)SAV); + WinFormsUtil.SetClipboardText(string.Join(Environment.NewLine, result)); + } + + private void B_OUTHallofFame_Click(object sender, EventArgs e) + { + using var form = SAV switch + { + SAV6 s6 => new SAV_HallOfFame(s6), + SAV7 s7 => new SAV_HallOfFame7(s7), + _ => (Form?)null, + }; + form?.ShowDialog(); + } + + private void B_JPEG_Click(object sender, EventArgs e) + { + var s6 = (SAV6)SAV; + byte[] jpeg = s6.GetJPEGData(); + if (jpeg.Length == 0) + { + WinFormsUtil.Alert(MsgSaveJPEGExportFail); + return; + } + string filename = $"{s6.JPEGTitle}'s picture"; + using var sfd = new SaveFileDialog { FileName = filename, Filter = "JPEG|*.jpeg" }; + if (sfd.ShowDialog() != DialogResult.OK) + return; + File.WriteAllBytes(sfd.FileName, jpeg); + } + + private void ClickVerifyCHK(object sender, EventArgs e) + { + if (SAV.State.Edited) + { + WinFormsUtil.Alert(MsgSaveChecksumFailEdited); + return; + } + if (SAV.ChecksumsValid) + { + WinFormsUtil.Alert(MsgSaveChecksumValid); + return; + } + + if (DialogResult.Yes == WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSaveChecksumFailExport)) + WinFormsUtil.SetClipboardText(SAV.ChecksumInfo); + } + + private void ClickVerifyStoredEntities(object sender, EventArgs e) + { + var bulk = new BulkAnalysis(SAV, Main.Settings.Bulk); + if (bulk.Valid) + { + WinFormsUtil.Alert("Clean!"); + return; + } + + if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgClipboardLegalityExport) != DialogResult.Yes) + return; + + var lines = bulk.Parse.Select(z => $"{z.Judgement}: {z.Comment}"); + var msg = string.Join(Environment.NewLine, lines); + WinFormsUtil.SetClipboardText(msg); + SystemSounds.Asterisk.Play(); + } + + // File I/O + public bool GetBulkImportSettings(out bool clearAll, out bool overwrite, out PKMImportSetting noSetb) + { + clearAll = false; noSetb = PKMImportSetting.UseDefault; overwrite = false; + var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, MsgSaveBoxImportClear, MsgSaveBoxImportClearNo); + if (dr == DialogResult.Cancel) + return false; + + clearAll = dr == DialogResult.Yes; + noSetb = GetPKMSetOverride(ModifyPKM); + return true; + } + + private static bool IsFolderPath(out string path) + { + using var fbd = new FolderBrowserDialog(); + var result = fbd.ShowDialog() == DialogResult.OK; + path = fbd.SelectedPath; + return result; + } + + public bool ExportSaveFile() + { + ValidateChildren(); + bool reload = SAV is SAV7b b && b.FixPreWrite(); + if (reload) + ReloadSlots(); + return WinFormsUtil.ExportSAVDialog(SAV, SAV.CurrentBox); + } + + public bool ExportBackup() + { + if (!SAV.State.Exportable || SAV.Metadata.FilePath is not { } file) + return false; + + if (!File.Exists(file)) + { + WinFormsUtil.Error(MsgSaveBackupNotFound, file); + return false; + } + + var suggestion = Util.CleanFileName(SAV.Metadata.BAKName); + using var sfd = new SaveFileDialog {FileName = suggestion}; + if (sfd.ShowDialog() != DialogResult.OK) + return false; + + string path = sfd.FileName; + if (!File.Exists(file)) // did they move it again? + { + WinFormsUtil.Error(MsgSaveBackupNotFound, file); + return false; + } + File.Copy(file, path); + WinFormsUtil.Alert(MsgSaveBackup, path); + + return true; + } + + public bool OpenPCBoxBin(byte[] input, out string c) + { + if (SAV.GetPCBinary().Length == input.Length) + { + if (SAV.IsAnySlotLockedInBox(0, SAV.BoxCount - 1)) + { c = MsgSaveBoxImportPCFailBattle; return false; } + if (!SAV.SetPCBinary(input)) + { c = string.Format(MsgSaveCurrentGeneration, SAV.Generation); return false; } + + c = MsgSaveBoxImportPCBinary; + } + else if (SAV.GetBoxBinary(Box.CurrentBox).Length == input.Length) + { + if (SAV.IsAnySlotLockedInBox(Box.CurrentBox, Box.CurrentBox)) + { c = MsgSaveBoxImportBoxFailBattle; return false; } + if (!SAV.SetBoxBinary(input, Box.CurrentBox)) + { c = string.Format(MsgSaveCurrentGeneration, SAV.Generation); return false; } + + c = MsgSaveBoxImportBoxBinary; + } + else + { + c = string.Format(MsgSaveCurrentGeneration, SAV.Generation); + return false; + } + SetPKMBoxes(); + UpdateBoxViewers(); + return true; + } + + public bool OpenGroup(IPokeGroup b, out string c) + { + var msg = string.Format(MsgSaveBoxImportGroup, Box.CurrentBoxName); + var prompt = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, msg, MsgSaveBoxImportOverwrite); + if (prompt != DialogResult.Yes) + { + c = string.Empty; + return false; + } + + var noSetb = GetPKMSetOverride(ModifyPKM); + var slotSkipped = ImportGroup(b.Contents, SAV, Box.CurrentBox, noSetb); + + SetPKMBoxes(); + UpdateBoxViewers(); + + c = slotSkipped > 0 ? string.Format(MsgSaveBoxImportSkippedLocked, slotSkipped) : MsgSaveBoxImportGroupSuccess; + + return true; + } + + private static int ImportGroup(IEnumerable data, SaveFile sav, int box, PKMImportSetting noSetb) + { + var type = sav.PKMType; + int slotSkipped = 0; + int index = 0; + foreach (var x in data) + { + var i = index++; + if (sav.IsSlotOverwriteProtected(box, i)) + { + slotSkipped++; + continue; + } + + var convert = EntityConverter.ConvertToType(x, type, out _); + if (convert?.GetType() != type) + { + slotSkipped++; + continue; + } + sav.SetBoxSlotAtIndex(x, box, i, noSetb); + } + + return slotSkipped; + } + + public bool DumpBoxes(out string result, string? path = null, bool separate = false) + { + if (path == null && !IsFolderPath(out path)) + { + result = path; + return false; + } + + Directory.CreateDirectory(path); + + var count = SAV.DumpBoxes(path, separate); + if (count < 0) + result = MsgSaveBoxExportInvalid; + else + result = string.Format(MsgSaveBoxExportPathCount, count) + Environment.NewLine + path; + return true; + } + + public bool DumpBox(out string result, string? path = null) + { + if (path == null && !IsFolderPath(out path)) + { + result = path; + return false; + } + + Directory.CreateDirectory(path); + + var count = SAV.DumpBox(path, Box.CurrentBox); + if (count < 0) + result = MsgSaveBoxExportInvalid; + else + result = string.Format(MsgSaveBoxExportPathCount, count) + Environment.NewLine + path; + return true; + } + + public bool LoadBoxes(out string result, string? path = null) + { + result = string.Empty; + if (!SAV.HasBox) + return false; + + if (path == null && !IsFolderPath(out path)) + { + result = path; + return false; + } + + if (!Directory.Exists(path)) + return false; + + if (!GetBulkImportSettings(out bool clearAll, out var overwrite, out var noSetb)) + return false; + + SAV.LoadBoxes(path, out result, Box.CurrentBox, clearAll, overwrite, noSetb); + SetPKMBoxes(); + UpdateBoxViewers(); + return true; + } + + public bool ToggleInterface() + { + FieldsLoaded = false; + + ToggleViewReset(); + ToggleViewSubEditors(SAV); + + bool WindowTranslationRequired = false; + WindowTranslationRequired |= ToggleViewBox(SAV); + int BoxTab = tabBoxMulti.TabPages.IndexOf(Tab_Box); + WindowTranslationRequired |= ToggleViewParty(SAV, BoxTab); + int PartyTab = tabBoxMulti.TabPages.IndexOf(Tab_PartyBattle); + WindowTranslationRequired |= ToggleViewDaycare(SAV, BoxTab, PartyTab); + SetPKMBoxes(); // Reload all of the PKX Windows + + ToggleViewMisc(SAV); + + FieldsLoaded = true; + return WindowTranslationRequired; + } + + private void ToggleViewReset() + { + // Close subforms that are save dependent + foreach (var z in M.Boxes.Skip(1).ToArray()) + z.FindForm()?.Close(); + + Box.M = M; + SL_Party.M = M; + if (SAV.HasBox) + { + bool newSlots = Box.InitializeFromSAV(SAV); + if (newSlots) + { + Box.HorizontallyCenter(Tab_Box); + foreach (var pb in Box.SlotPictureBoxes) + pb.ContextMenuStrip = menu.mnuVSD; + + var grid = Box.BoxPokeGrid; + var height = grid.Height + grid.Location.Y + Box.Location.Y; // needed height + var required = height + 16; + var allowed = Tab_Box.Height; + if (required > allowed) + { + var form = FindForm(); + if (form != null) + form.Height += required - allowed; + } + } + } + if (SAV.HasParty) + { + bool newSlots = SL_Party.InitializeFromSAV(SAV); + if (newSlots) + { + SL_Party.HorizontallyCenter(Tab_PartyBattle); + foreach (var pb in SL_Party.SlotPictureBoxes) + pb.ContextMenuStrip = menu.mnuVSD; + } + } + + SortMenu.ToggleVisibility(); + } + + private bool ToggleViewBox(SaveFile sav) + { + if (!sav.HasBox) + { + if (tabBoxMulti.TabPages.Contains(Tab_Box)) + tabBoxMulti.TabPages.Remove(Tab_Box); + B_SaveBoxBin.Enabled = false; + return false; + } + + B_SaveBoxBin.Enabled = true; + tabBoxMulti.SelectedIndex = 0; + + var box = sav.CurrentBox; + Box.CurrentBox = (uint)box >= sav.BoxCount ? 0 : box; + + if (tabBoxMulti.TabPages.Contains(Tab_Box)) + return false; + tabBoxMulti.TabPages.Insert(0, Tab_Box); + return true; + } + + private bool ToggleViewParty(SaveFile sav, int BoxTab) + { + if (!sav.HasParty || !sav.State.Exportable) + { + if (tabBoxMulti.TabPages.Contains(Tab_PartyBattle)) + tabBoxMulti.TabPages.Remove(Tab_PartyBattle); + return false; + } + + if (tabBoxMulti.TabPages.Contains(Tab_PartyBattle)) + return false; + + int index = BoxTab; + if (index < 0) + index = -1; + tabBoxMulti.TabPages.Insert(index + 1, Tab_PartyBattle); + return true; + } + + private bool ToggleViewDaycare(SaveFile sav, int BoxTab, int PartyTab) + { + if ((!sav.HasDaycare && SL_Extra.SlotCount == 0) || !sav.State.Exportable) + { + if (tabBoxMulti.TabPages.Contains(Tab_Other)) + tabBoxMulti.TabPages.Remove(Tab_Other); + return false; + } + + SlotPictureBoxes[1].Visible = sav.Generation >= 2; // Second daycare slot + if (tabBoxMulti.TabPages.Contains(Tab_Other)) + return false; + + int index = PartyTab; + if (index < 0) + index = BoxTab; + if (index < 0) + index = -1; + tabBoxMulti.TabPages.Insert(index + 1, Tab_Other); + return true; + } + + private void ToggleViewSubEditors(SaveFile sav) + { + if (!sav.State.Exportable || sav is BulkStorage) + { + GB_SAVtools.Visible = false; + B_JPEG.Visible = false; + SL_Extra.HideAllSlots(); + return; + } + + GB_Daycare.Visible = sav.HasDaycare; + B_OpenPokeblocks.Visible = sav is SAV6AO; + B_OpenSecretBase.Visible = sav is SAV6AO; + B_OpenPokepuffs.Visible = sav is ISaveBlock6Main; + B_JPEG.Visible = B_OpenLinkInfo.Visible = B_OpenSuperTraining.Visible = B_OUTPasserby.Visible = sav is ISaveBlock6Main; + B_OpenBoxLayout.Visible = sav.HasNamableBoxes; + B_OpenWondercards.Visible = sav.HasWondercards; + B_OpenHallofFame.Visible = sav is ISaveBlock6Main or SAV7; + B_OpenOPowers.Visible = sav is ISaveBlock6Main; + B_OpenPokedex.Visible = sav.HasPokeDex; + B_OpenBerryField.Visible = sav is SAV6XY; // oras undocumented + B_OpenFriendSafari.Visible = sav is SAV6XY; + B_OpenEventFlags.Visible = sav is IEventFlag37 or SAV1 or SAV2 or SAV8BS or SAV7b; + B_CGearSkin.Visible = sav.Generation == 5; + B_OpenPokeBeans.Visible = B_CellsStickers.Visible = B_FestivalPlaza.Visible = sav is SAV7; + + B_OtherSlots.Visible = sav is SAV1StadiumJ or SAV1Stadium or SAV2Stadium; + B_OpenTrainerInfo.Visible = B_OpenItemPouch.Visible = (sav.HasParty && SAV is not SAV4BR) || SAV is SAV7b; // Box RS & Battle Revolution + B_OpenMiscEditor.Visible = sav is SAV3 or SAV4 or SAV5 or SAV8BS; + B_Roamer.Visible = sav is SAV3; + + B_OpenHoneyTreeEditor.Visible = sav is SAV4Sinnoh; + B_OpenUGSEditor.Visible = sav is SAV4Sinnoh or SAV8BS; + B_OpenSealStickers.Visible = B_Poffins.Visible = sav is SAV8BS; + B_OpenApricorn.Visible = sav is SAV4HGSS; + B_OpenRTCEditor.Visible = sav.Generation == 2 || sav is IGen3Hoenn; + B_MailBox.Visible = sav is SAV2 or SAV3 or SAV4 or SAV5; + + B_Raids.Visible = sav is SAV8SWSH; + B_RaidArmor.Visible = sav is SAV8SWSH {SaveRevision: >= 1}; + B_RaidCrown.Visible = sav is SAV8SWSH {SaveRevision: >= 2}; + GB_SAVtools.Visible = B_Blocks.Visible = true; + + var list = FLP_SAVtools.Controls.OfType().OrderBy(z => z.Text).ToArray(); + FLP_SAVtools.Controls.Clear(); + FLP_SAVtools.Controls.AddRange(list); + + SL_Extra.SAV = sav; + SL_Extra.Initialize(sav.GetExtraSlots(HaX), InitializeDragDrop); + } + + private void ToggleViewMisc(SaveFile sav) + { + // Generational Interface + ToggleSecrets(sav, HideSecretDetails); + B_VerifyCHK.Visible = SAV.State.Exportable; + Menu_ExportBAK.Visible = SAV.State.Exportable && SAV.Metadata.FilePath is not null; + + if (sav is SAV4BR br) + { + L_SaveSlot.Visible = CB_SaveSlot.Visible = true; + var current = br.CurrentSlot; + var list = br.SaveNames.Select((z, i) => new ComboItem(z, i)).ToList(); + CB_SaveSlot.InitializeBinding(); + CB_SaveSlot.DataSource = new BindingSource(list, null); + CB_SaveSlot.SelectedValue = current; + } + else + { + L_SaveSlot.Visible = CB_SaveSlot.Visible = false; + } + + if (sav is ISecureValueStorage s) + { + TB_Secure1.Text = s.TimeStampCurrent.ToString("X16"); + TB_Secure2.Text = s.TimeStampPrevious.ToString("X16"); + } + + if (sav is IGameSync sync) + { + var gsid = sync.GameSyncID; + TB_GameSync.Enabled = !string.IsNullOrEmpty(gsid); + TB_GameSync.MaxLength = sync.GameSyncIDSize; + TB_GameSync.Text = (string.IsNullOrEmpty(gsid) ? 0.ToString() : gsid).PadLeft(sync.GameSyncIDSize, '0'); + } + } + + private void ToggleSecrets(SaveFile sav, bool hide) + { + var shouldShow = sav.State.Exportable && !hide; + TB_Secure1.Visible = TB_Secure2.Visible = L_Secure1.Visible = L_Secure2.Visible = shouldShow && sav is ISecureValueStorage; + TB_GameSync.Visible = L_GameSync.Visible = shouldShow && sav is IGameSync; + } + + // DragDrop + private void MultiDragOver(object sender, DragEventArgs e) + { + // iterate over all tabs to see if a tab switch should occur when drag/dropping + Point pt = tabBoxMulti.PointToClient(new Point(e.X, e.Y)); + for (int i = 0; i < tabBoxMulti.TabCount; i++) + { + if (tabBoxMulti.SelectedIndex == i || !tabBoxMulti.GetTabRect(i).Contains(pt)) + continue; + tabBoxMulti.SelectedIndex = i; + return; + } + } + + public void ClickShowdownExportParty(object sender, EventArgs e) => ExportShowdownText(SAV, MsgSimulatorExportParty, sav => sav.PartyData); + + public void ClickShowdownExportCurrentBox(object sender, EventArgs e) + { + if (!SAV.HasBox) + return; + ExportShowdownText(SAV, MsgSimulatorExportList, + sav => (ModifierKeys & Keys.Control) != 0 ? sav.BoxData : sav.GetBoxData(CurrentBox)); + } + + private static void ExportShowdownText(SaveFile sav, string success, Func> fetch) + { + var list = fetch(sav); + var result = ShowdownParsing.GetShowdownSets(list, Environment.NewLine + Environment.NewLine); + if (string.IsNullOrWhiteSpace(result)) + return; + if (WinFormsUtil.SetClipboardText(result)) + WinFormsUtil.Alert(success); + } + + private void B_OpenUGSEditor_Click(object sender, EventArgs e) + { + Form form; + if (SAV is SAV4Sinnoh s) + form = new SAV_Underground(s); + else if (SAV is SAV8BS bs) + form = new SAV_Underground8b(bs); + else + return; + form.ShowDialog(); + form.Dispose(); + } + + private void B_OpenSealStickers_Click(object sender, EventArgs e) + { + if (SAV is not SAV8BS bs) + return; + using var form = new SAV_SealStickers8b(bs); + form.ShowDialog(); + } + + private void B_Poffins_Click(object sender, EventArgs e) + { + if (SAV is not SAV8BS bs) + return; + using var form = new SAV_Poffin8b(bs); + form.ShowDialog(); + } + + private void B_FestivalPlaza_Click(object sender, EventArgs e) + { + if (SAV is not SAV7 s) + return; + using var form = new SAV_FestivalPlaza(s); + form.ShowDialog(); + } + + private void B_MailBox_Click(object sender, EventArgs e) + { + using var form = new SAV_MailBox(SAV); + form.ShowDialog(); + ResetParty(); + } + + private static PKMImportSetting GetPKMSetOverride(bool currentSetting) + { + var yn = currentSetting ? MsgYes : MsgNo; + var choice = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, + MsgSaveBoxImportModifyIntro, + MsgSaveBoxImportModifyYes + Environment.NewLine + + MsgSaveBoxImportModifyNo + Environment.NewLine + + string.Format(MsgSaveBoxImportModifyCurrent, yn)); + return choice switch + { + DialogResult.Yes => PKMImportSetting.Update, + DialogResult.No => PKMImportSetting.Skip, + _ => PKMImportSetting.UseDefault, + }; + } + + private void Menu_ExportBAK_Click(object sender, EventArgs e) => ExportBackup(); } diff --git a/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs b/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs index 6f68fa4a9..bb4b955de 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs @@ -10,386 +10,385 @@ using PKHeX.Core; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +/// +/// Orchestrates the movement of slots within the GUI. +/// +public sealed class SlotChangeManager : IDisposable { - /// - /// Orchestrates the movement of slots within the GUI. - /// - public sealed class SlotChangeManager : IDisposable + public readonly SAVEditor SE; + public readonly SlotTrackerImage LastSlot = new(); + public readonly DragManager Drag = new(); + public SaveDataEditor Env { get; set; } = null!; + + public readonly List Boxes = new(); + public readonly SlotHoverHandler Hover = new(); + + public SlotChangeManager(SAVEditor se) => SE = se; + + public void Reset() { - public readonly SAVEditor SE; - public readonly SlotTrackerImage LastSlot = new(); - public readonly DragManager Drag = new(); - public SaveDataEditor Env { get; set; } = null!; + Drag.Initialize(); + LastSlot.Reset(); + } - public readonly List Boxes = new(); - public readonly SlotHoverHandler Hover = new(); + public void MouseEnter(object? sender, EventArgs e) + { + if (sender is not PictureBox pb) + return; + bool dataPresent = pb.Image is not null; + if (dataPresent) + Hover.Start(pb, LastSlot); + pb.Cursor = dataPresent ? Cursors.Hand : Cursors.Default; + } - public SlotChangeManager(SAVEditor se) => SE = se; + public void MouseLeave(object? sender, EventArgs e) + { + Hover.Stop(); + } - public void Reset() - { - Drag.Initialize(); - LastSlot.Reset(); - } + public void MouseClick(object? sender, MouseEventArgs e) + { + if (sender == null) + return; + if (!Drag.Info.DragDropInProgress) + SE.ClickSlot(sender, e); + } - public void MouseEnter(object? sender, EventArgs e) - { - if (sender is not PictureBox pb) - return; - bool dataPresent = pb.Image is not null; - if (dataPresent) - Hover.Start(pb, LastSlot); - pb.Cursor = dataPresent ? Cursors.Hand : Cursors.Default; - } - - public void MouseLeave(object? sender, EventArgs e) - { - Hover.Stop(); - } - - public void MouseClick(object? sender, MouseEventArgs e) - { - if (sender == null) - return; - if (!Drag.Info.DragDropInProgress) - SE.ClickSlot(sender, e); - } - - public void MouseUp(object? sender, MouseEventArgs e) - { - if (sender == null) - return; - if (e.Button == MouseButtons.Left) - Drag.Info.LeftMouseIsDown = false; - Drag.Info.Source = null; - } - - public void MouseDown(object? sender, MouseEventArgs e) - { - if (sender == null) - return; - if (e.Button == MouseButtons.Left) - { - Drag.Info.LeftMouseIsDown = true; - Drag.MouseDownPosition = Cursor.Position; - } - } - - public void QueryContinueDrag(object? sender, QueryContinueDragEventArgs e) - { - if (sender == null) - return; - if (e.Action != DragAction.Cancel && e.Action != DragAction.Drop) - return; + public void MouseUp(object? sender, MouseEventArgs e) + { + if (sender == null) + return; + if (e.Button == MouseButtons.Left) Drag.Info.LeftMouseIsDown = false; - Drag.Info.DragDropInProgress = false; - } + Drag.Info.Source = null; + } - public void DragEnter(object? sender, DragEventArgs e) + public void MouseDown(object? sender, MouseEventArgs e) + { + if (sender == null) + return; + if (e.Button == MouseButtons.Left) { - if (sender == null) - return; - if ((e.AllowedEffect & DragDropEffects.Copy) != 0) // external file - e.Effect = DragDropEffects.Copy; - else if (e.Data != null) // within - e.Effect = DragDropEffects.Move; - - if (Drag.Info.DragDropInProgress) - Drag.SetCursor(((Control)sender).FindForm(), Drag.Info.Cursor); + Drag.Info.LeftMouseIsDown = true; + Drag.MouseDownPosition = Cursor.Position; } + } - private static SlotViewInfo GetSlotInfo(T pb) where T : Control + public void QueryContinueDrag(object? sender, QueryContinueDragEventArgs e) + { + if (sender == null) + return; + if (e.Action != DragAction.Cancel && e.Action != DragAction.Drop) + return; + Drag.Info.LeftMouseIsDown = false; + Drag.Info.DragDropInProgress = false; + } + + public void DragEnter(object? sender, DragEventArgs e) + { + if (sender == null) + return; + if ((e.AllowedEffect & DragDropEffects.Copy) != 0) // external file + e.Effect = DragDropEffects.Copy; + else if (e.Data != null) // within + e.Effect = DragDropEffects.Move; + + if (Drag.Info.DragDropInProgress) + Drag.SetCursor(((Control)sender).FindForm(), Drag.Info.Cursor); + } + + private static SlotViewInfo GetSlotInfo(T pb) where T : Control + { + var view = WinFormsUtil.FindFirstControlOfType>(pb); + if (view == null) + throw new InvalidCastException("Unable to find View Parent"); + var src = view.GetSlotData(pb); + return new SlotViewInfo(src, view); + } + + public void MouseMove(object? sender, MouseEventArgs e) + { + if (sender == null) + return; + if (!Drag.CanStartDrag) + return; + + // Abort if there is no Pokemon in the given slot. + PictureBox pb = (PictureBox)sender; + if (pb.Image == null) + return; + bool encrypt = Control.ModifierKeys == Keys.Control; + HandleMovePKM(pb, encrypt); + } + + public void DragDrop(object? sender, DragEventArgs e) + { + if (sender == null) + return; + PictureBox pb = (PictureBox)sender; + var info = GetSlotInfo(pb); + if (!info.CanWriteTo() || Drag.Info.Source?.CanWriteTo() == false) { - var view = WinFormsUtil.FindFirstControlOfType>(pb); - if (view == null) - throw new InvalidCastException("Unable to find View Parent"); - var src = view.GetSlotData(pb); - return new SlotViewInfo(src, view); - } - - public void MouseMove(object? sender, MouseEventArgs e) - { - if (sender == null) - return; - if (!Drag.CanStartDrag) - return; - - // Abort if there is no Pokemon in the given slot. - PictureBox pb = (PictureBox)sender; - if (pb.Image == null) - return; - bool encrypt = Control.ModifierKeys == Keys.Control; - HandleMovePKM(pb, encrypt); - } - - public void DragDrop(object? sender, DragEventArgs e) - { - if (sender == null) - return; - PictureBox pb = (PictureBox)sender; - var info = GetSlotInfo(pb); - if (!info.CanWriteTo() || Drag.Info.Source?.CanWriteTo() == false) - { - SystemSounds.Asterisk.Play(); - e.Effect = DragDropEffects.Copy; - Drag.Reset(); - return; - } - - var mod = SlotUtil.GetDropModifier(); - Drag.Info.Destination = info; - HandleDropPKM(pb, e, mod); - } - - private void HandleMovePKM(PictureBox pb, bool encrypt) - { - // Create a temporary PKM file to perform a drag drop operation. - - // Set flag to prevent re-entering. - Drag.Info.DragDropInProgress = true; - - // Prepare Data - Drag.Info.Source = GetSlotInfo(pb); - Drag.Info.Destination = null; - - // Make a new file name based off the PID - string newfile = CreateDragDropPKM(pb, encrypt, out bool external); - - // drop finished, clean up - Drag.Info.Source = null; + SystemSounds.Asterisk.Play(); + e.Effect = DragDropEffects.Copy; Drag.Reset(); + return; + } + + var mod = SlotUtil.GetDropModifier(); + Drag.Info.Destination = info; + HandleDropPKM(pb, e, mod); + } + + private void HandleMovePKM(PictureBox pb, bool encrypt) + { + // Create a temporary PKM file to perform a drag drop operation. + + // Set flag to prevent re-entering. + Drag.Info.DragDropInProgress = true; + + // Prepare Data + Drag.Info.Source = GetSlotInfo(pb); + Drag.Info.Destination = null; + + // Make a new file name based off the PID + string newfile = CreateDragDropPKM(pb, encrypt, out bool external); + + // drop finished, clean up + Drag.Info.Source = null; + Drag.Reset(); + Drag.ResetCursor(pb.FindForm()); + + // Browser apps need time to load data since the file isn't moved to a location on the user's local storage. + // Tested 10ms -> too quick, 100ms was fine. 500ms should be safe? + // Keep it to 20 seconds; Discord upload only stores the file path until you click Upload. + int delay = external ? 20_000 : 0; + DeleteAsync(newfile, delay); + if (Drag.Info.DragIsParty) + SE.SetParty(); + } + + private async void DeleteAsync(string path, int delay) + { + await Task.Delay(delay).ConfigureAwait(true); + if (!File.Exists(path) || Drag.Info.CurrentPath == path) + return; + + try { File.Delete(path); } + catch (Exception ex) { Debug.WriteLine(ex.Message); } + } + + private string CreateDragDropPKM(PictureBox pb, bool encrypt, out bool external) + { + // Make File + var pk = Drag.Info.Source!.ReadCurrent(); + string newfile = FileUtil.GetPKMTempFileName(pk, encrypt); + try + { + var data = encrypt ? pk.EncryptedPartyData : pk.DecryptedPartyData; + external = TryMakeDragDropPKM(pb, data, newfile); + } + // Tons of things can happen with drag & drop; don't try to handle things, just indicate failure. + catch (Exception x) + { + WinFormsUtil.Error("Drag && Drop Error", x); + external = false; + } + + return newfile; + } + + private bool TryMakeDragDropPKM(PictureBox pb, byte[] data, string newfile) + { + File.WriteAllBytes(newfile, data); + var img = (Bitmap)pb.Image; + Drag.SetCursor(pb.FindForm(), new Cursor(img.GetHicon())); + Hover.Stop(); + pb.Image = null; + pb.BackgroundImage = SpriteUtil.Spriter.Drag; + + // Thread Blocks on DoDragDrop + Drag.Info.CurrentPath = newfile; + var result = pb.DoDragDrop(new DataObject(DataFormats.FileDrop, new[] { newfile }), DragDropEffects.Copy); + var external = Drag.Info.Destination == null || result != DragDropEffects.Link; + if (external || Drag.Info.SameLocation) // not dropped to another box slot, restore img + { + pb.Image = img; + pb.BackgroundImage = LastSlot.OriginalBackground; Drag.ResetCursor(pb.FindForm()); - - // Browser apps need time to load data since the file isn't moved to a location on the user's local storage. - // Tested 10ms -> too quick, 100ms was fine. 500ms should be safe? - // Keep it to 20 seconds; Discord upload only stores the file path until you click Upload. - int delay = external ? 20_000 : 0; - DeleteAsync(newfile, delay); - if (Drag.Info.DragIsParty) - SE.SetParty(); + return external; } - private async void DeleteAsync(string path, int delay) + if (result == DragDropEffects.Copy) // viewed in tabs or cloned { - await Task.Delay(delay).ConfigureAwait(true); - if (!File.Exists(path) || Drag.Info.CurrentPath == path) - return; - - try { File.Delete(path); } - catch (Exception ex) { Debug.WriteLine(ex.Message); } + if (Drag.Info.Destination == null) // apply 'view' highlight + Env.Slots.Get(Drag.Info.Source!.Slot); + return false; } + return true; + } - private string CreateDragDropPKM(PictureBox pb, bool encrypt, out bool external) + private void HandleDropPKM(PictureBox pb, DragEventArgs? e, DropModifier mod) + { + if (e?.Data?.GetData(DataFormats.FileDrop) is not string[] {Length: not 0} files) { - // Make File - var pk = Drag.Info.Source!.ReadCurrent(); - string newfile = FileUtil.GetPKMTempFileName(pk, encrypt); - try - { - var data = encrypt ? pk.EncryptedPartyData : pk.DecryptedPartyData; - external = TryMakeDragDropPKM(pb, data, newfile); - } - // Tons of things can happen with drag & drop; don't try to handle things, just indicate failure. - catch (Exception x) - { - WinFormsUtil.Error("Drag && Drop Error", x); - external = false; - } - - return newfile; - } - - private bool TryMakeDragDropPKM(PictureBox pb, byte[] data, string newfile) - { - File.WriteAllBytes(newfile, data); - var img = (Bitmap)pb.Image; - Drag.SetCursor(pb.FindForm(), new Cursor(img.GetHicon())); - Hover.Stop(); - pb.Image = null; - pb.BackgroundImage = SpriteUtil.Spriter.Drag; - - // Thread Blocks on DoDragDrop - Drag.Info.CurrentPath = newfile; - var result = pb.DoDragDrop(new DataObject(DataFormats.FileDrop, new[] { newfile }), DragDropEffects.Copy); - var external = Drag.Info.Destination == null || result != DragDropEffects.Link; - if (external || Drag.Info.SameLocation) // not dropped to another box slot, restore img - { - pb.Image = img; - pb.BackgroundImage = LastSlot.OriginalBackground; - Drag.ResetCursor(pb.FindForm()); - return external; - } - - if (result == DragDropEffects.Copy) // viewed in tabs or cloned - { - if (Drag.Info.Destination == null) // apply 'view' highlight - Env.Slots.Get(Drag.Info.Source!.Slot); - return false; - } - return true; - } - - private void HandleDropPKM(PictureBox pb, DragEventArgs? e, DropModifier mod) - { - if (e?.Data?.GetData(DataFormats.FileDrop) is not string[] {Length: not 0} files) - { - Drag.Reset(); - return; - } - - if (Directory.Exists(files[0])) // folder - { - SE.LoadBoxes(out string _, files[0]); - Drag.Reset(); - return; - } - - e.Effect = mod == DropModifier.Clone ? DragDropEffects.Copy : DragDropEffects.Link; - - // file - if (Drag.Info.SameLocation) - { - e.Effect = DragDropEffects.Link; - return; - } - - var dest = Drag.Info.Destination; - - if (Drag.Info.Source == null) // external source - { - bool badDest = !dest!.CanWriteTo(); - if (!TryLoadFiles(files, e, badDest)) - WinFormsUtil.Alert(MessageStrings.MsgSaveSlotBadData); - } - else if (!TrySetPKMDestination(pb, mod)) - { - WinFormsUtil.Alert(MessageStrings.MsgSaveSlotEmpty); - } Drag.Reset(); + return; } - /// - /// Tries to load the input - /// - /// Files to load - /// Args - /// Destination slot disallows eggs/blanks - /// True if loaded - private bool TryLoadFiles(IReadOnlyList files, DragEventArgs e, bool badDest) + if (Directory.Exists(files[0])) // folder { - if (files.Count == 0) - return false; + SE.LoadBoxes(out string _, files[0]); + Drag.Reset(); + return; + } - var sav = Drag.Info.Destination!.View.SAV; - var path = files[0]; - var temp = FileUtil.GetSingleFromPath(path, sav); - if (temp == null) - { - Drag.RequestDD(this, e); // pass thru - return true; // treat as handled - } + e.Effect = mod == DropModifier.Clone ? DragDropEffects.Copy : DragDropEffects.Link; - var pk = EntityConverter.ConvertToType(temp, sav.PKMType, out var result); - if (pk == null) + // file + if (Drag.Info.SameLocation) + { + e.Effect = DragDropEffects.Link; + return; + } + + var dest = Drag.Info.Destination; + + if (Drag.Info.Source == null) // external source + { + bool badDest = !dest!.CanWriteTo(); + if (!TryLoadFiles(files, e, badDest)) + WinFormsUtil.Alert(MessageStrings.MsgSaveSlotBadData); + } + else if (!TrySetPKMDestination(pb, mod)) + { + WinFormsUtil.Alert(MessageStrings.MsgSaveSlotEmpty); + } + Drag.Reset(); + } + + /// + /// Tries to load the input + /// + /// Files to load + /// Args + /// Destination slot disallows eggs/blanks + /// True if loaded + private bool TryLoadFiles(IReadOnlyList files, DragEventArgs e, bool badDest) + { + if (files.Count == 0) + return false; + + var sav = Drag.Info.Destination!.View.SAV; + var path = files[0]; + var temp = FileUtil.GetSingleFromPath(path, sav); + if (temp == null) + { + Drag.RequestDD(this, e); // pass thru + return true; // treat as handled + } + + var pk = EntityConverter.ConvertToType(temp, sav.PKMType, out var result); + if (pk == null) + { + var c = result.GetDisplayString(temp, sav.PKMType); + WinFormsUtil.Error(c); + Debug.WriteLine(c); + return false; + } + + if (badDest && (pk.Species == 0 || pk.IsEgg)) + return false; + + if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese)) + { + var str = EntityConverterResult.IncompatibleLanguageGB.GetIncompatibleGBMessage(pk, il.Japanese); + WinFormsUtil.Error(str); + Debug.WriteLine(str); + return false; + } + + var errata = sav.EvaluateCompatibility(pk); + if (errata.Count > 0) + { + string concat = string.Join(Environment.NewLine, errata); + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, concat, MessageStrings.MsgContinue)) { - var c = result.GetDisplayString(temp, sav.PKMType); - WinFormsUtil.Error(c); - Debug.WriteLine(c); + Debug.WriteLine(result.GetDisplayString(temp, sav.PKMType)); + Debug.WriteLine(concat); return false; } + } - if (badDest && (pk.Species == 0 || pk.IsEgg)) - return false; + Env.Slots.Set(Drag.Info.Destination!.Slot, pk); + Debug.WriteLine(result.GetDisplayString(temp, sav.PKMType)); + return true; + } - if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese)) - { - var str = EntityConverterResult.IncompatibleLanguageGB.GetIncompatibleGBMessage(pk, il.Japanese); - WinFormsUtil.Error(str); - Debug.WriteLine(str); - return false; - } + private bool TrySetPKMDestination(PictureBox pb, DropModifier mod) + { + var info = Drag.Info; + var pk = info.Source!.ReadCurrent(); + var msg = Drag.Info.Destination!.CanWriteTo(pk); + if (msg != WriteBlockedMessage.None) + return false; - var errata = sav.EvaluateCompatibility(pk); - if (errata.Count > 0) - { - string concat = string.Join(Environment.NewLine, errata); - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, concat, MessageStrings.MsgContinue)) - { - Debug.WriteLine(result.GetDisplayString(temp, sav.PKMType)); - Debug.WriteLine(concat); - return false; - } - } + if (Drag.Info.Source != null) + TrySetPKMSource(pb, mod); - Env.Slots.Set(Drag.Info.Destination!.Slot, pk); - Debug.WriteLine(result.GetDisplayString(temp, sav.PKMType)); + // Copy from temp to destination slot. + var type = info.DragIsSwap ? SlotTouchType.Swap : SlotTouchType.Set; + Env.Slots.Set(info.Destination!.Slot, pk, type); + Drag.ResetCursor(pb.FindForm()); + return true; + } + + private bool TrySetPKMSource(PictureBox sender, DropModifier mod) + { + var info = Drag.Info; + if (info.Destination == null || mod == DropModifier.Clone) + return false; + + if (sender.Image == null || mod == DropModifier.Overwrite) + { + Env.Slots.Delete(info.Source!.Slot); return true; } - private bool TrySetPKMDestination(PictureBox pb, DropModifier mod) + var type = info.DragIsSwap ? SlotTouchType.Swap : SlotTouchType.Set; + var pk = info.Destination.ReadCurrent(); + Env.Slots.Set(Drag.Info.Source!.Slot, pk, type); + return true; + } + + // Utility + public void SwapBoxes(int index, int other, SaveFile SAV) + { + if (index == other) + return; + SAV.SwapBox(index, other); + UpdateBoxViewAtBoxIndexes(index, other); + } + + public void Dispose() + { + Hover.Dispose(); + SE.Dispose(); + LastSlot.OriginalBackground?.Dispose(); + LastSlot.CurrentBackground?.Dispose(); + } + + private void UpdateBoxViewAtBoxIndexes(params int[] boxIndexes) + { + foreach (var box in Boxes) { - var info = Drag.Info; - var pk = info.Source!.ReadCurrent(); - var msg = Drag.Info.Destination!.CanWriteTo(pk); - if (msg != WriteBlockedMessage.None) - return false; - - if (Drag.Info.Source != null) - TrySetPKMSource(pb, mod); - - // Copy from temp to destination slot. - var type = info.DragIsSwap ? SlotTouchType.Swap : SlotTouchType.Set; - Env.Slots.Set(info.Destination!.Slot, pk, type); - Drag.ResetCursor(pb.FindForm()); - return true; - } - - private bool TrySetPKMSource(PictureBox sender, DropModifier mod) - { - var info = Drag.Info; - if (info.Destination == null || mod == DropModifier.Clone) - return false; - - if (sender.Image == null || mod == DropModifier.Overwrite) - { - Env.Slots.Delete(info.Source!.Slot); - return true; - } - - var type = info.DragIsSwap ? SlotTouchType.Swap : SlotTouchType.Set; - var pk = info.Destination.ReadCurrent(); - Env.Slots.Set(Drag.Info.Source!.Slot, pk, type); - return true; - } - - // Utility - public void SwapBoxes(int index, int other, SaveFile SAV) - { - if (index == other) - return; - SAV.SwapBox(index, other); - UpdateBoxViewAtBoxIndexes(index, other); - } - - public void Dispose() - { - Hover.Dispose(); - SE.Dispose(); - LastSlot.OriginalBackground?.Dispose(); - LastSlot.CurrentBackground?.Dispose(); - } - - private void UpdateBoxViewAtBoxIndexes(params int[] boxIndexes) - { - foreach (var box in Boxes) - { - var current = box.CurrentBox; - if (!boxIndexes.Contains(current)) - continue; - box.ResetSlots(); - box.ResetBoxNames(current); - } + var current = box.CurrentBox; + if (!boxIndexes.Contains(current)) + continue; + box.ResetSlots(); + box.ResetBoxNames(current); } } } diff --git a/PKHeX.WinForms/Controls/Slots/CryPlayer.cs b/PKHeX.WinForms/Controls/Slots/CryPlayer.cs index 50c0c44f5..c6ec3b40b 100644 --- a/PKHeX.WinForms/Controls/Slots/CryPlayer.cs +++ b/PKHeX.WinForms/Controls/Slots/CryPlayer.cs @@ -4,54 +4,53 @@ using PKHeX.Core; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public sealed class CryPlayer { - public sealed class CryPlayer + private readonly SoundPlayer Sounds = new(); + + public void PlayCry(ISpeciesForm pk, int format) { - private readonly SoundPlayer Sounds = new(); + if (pk.Species == 0) + return; - public void PlayCry(ISpeciesForm pk, int format) - { - if (pk.Species == 0) - return; + string path = GetCryPath(pk, Main.CryPath, format); + if (!File.Exists(path)) + return; - string path = GetCryPath(pk, Main.CryPath, format); - if (!File.Exists(path)) - return; + Sounds.SoundLocation = path; + try { Sounds.Play(); } + catch { Debug.WriteLine("Failed to play sound."); } + } - Sounds.SoundLocation = path; - try { Sounds.Play(); } - catch { Debug.WriteLine("Failed to play sound."); } - } + public void Stop() + { + if (string.IsNullOrWhiteSpace(Sounds.SoundLocation)) + return; - public void Stop() - { - if (string.IsNullOrWhiteSpace(Sounds.SoundLocation)) - return; + try { Sounds.Stop(); } + catch { Debug.WriteLine("Failed to stop sound."); } + } - try { Sounds.Stop(); } - catch { Debug.WriteLine("Failed to stop sound."); } - } + private static string GetCryPath(ISpeciesForm pk, string cryFolder, int format) + { + var name = GetCryFileName(pk, format); + var path = Path.Combine(cryFolder, $"{name}.wav"); + if (!File.Exists(path)) + path = Path.Combine(cryFolder, $"{pk.Species}.wav"); + return path; + } - private static string GetCryPath(ISpeciesForm pk, string cryFolder, int format) - { - var name = GetCryFileName(pk, format); - var path = Path.Combine(cryFolder, $"{name}.wav"); - if (!File.Exists(path)) - path = Path.Combine(cryFolder, $"{pk.Species}.wav"); - return path; - } + private static string GetCryFileName(ISpeciesForm pk, int format) + { + if (pk.Species == (int)Species.Urshifu && pk.Form == 1) // same sprite for both forms, but different cries + return "892-1"; - private static string GetCryFileName(ISpeciesForm pk, int format) - { - if (pk.Species == (int)Species.Urshifu && pk.Form == 1) // same sprite for both forms, but different cries - return "892-1"; + // don't grab sprite of pk, no gender specific cries + var res = SpriteName.GetResourceStringSprite(pk.Species, pk.Form, 0, 0, format); - // don't grab sprite of pkm, no gender specific cries - var res = SpriteName.GetResourceStringSprite(pk.Species, pk.Form, 0, 0, format); - - // people like - instead of _ file names ;) - return res.Replace('_', '-')[1..]; // skip leading underscore - } + // people like - instead of _ file names ;) + return res.Replace('_', '-')[1..]; // skip leading underscore } } diff --git a/PKHeX.WinForms/Controls/Slots/DragManager.cs b/PKHeX.WinForms/Controls/Slots/DragManager.cs index f695d53a6..1416d343e 100644 --- a/PKHeX.WinForms/Controls/Slots/DragManager.cs +++ b/PKHeX.WinForms/Controls/Slots/DragManager.cs @@ -1,34 +1,33 @@ using System.Drawing; using System.Windows.Forms; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public sealed class DragManager { - public sealed class DragManager + public SlotChangeInfo Info { get; private set; } = new(); + public event DragEventHandler? RequestExternalDragDrop; + public void RequestDD(object sender, DragEventArgs e) => RequestExternalDragDrop?.Invoke(sender, e); + + public void SetCursor(Form? f, Cursor? z) { - public SlotChangeInfo Info { get; private set; } = new(); - public event DragEventHandler? RequestExternalDragDrop; - public void RequestDD(object sender, DragEventArgs e) => RequestExternalDragDrop?.Invoke(sender, e); - - public void SetCursor(Form? f, Cursor? z) - { - if (f != null) - f.Cursor = z; - Info.Cursor = z; - } - - public void ResetCursor(Form? sender) - { - SetCursor(sender, Cursors.Default); - } - - public void Initialize() - { - Info = new SlotChangeInfo(); - } - - public void Reset() => Info.Reset(); - - public Point MouseDownPosition { private get; set; } - public bool CanStartDrag => Info.LeftMouseIsDown && !Cursor.Position.Equals(MouseDownPosition); + if (f != null) + f.Cursor = z; + Info.Cursor = z; } -} \ No newline at end of file + + public void ResetCursor(Form? sender) + { + SetCursor(sender, Cursors.Default); + } + + public void Initialize() + { + Info = new SlotChangeInfo(); + } + + public void Reset() => Info.Reset(); + + public Point MouseDownPosition { private get; set; } + public bool CanStartDrag => Info.LeftMouseIsDown && !Cursor.Position.Equals(MouseDownPosition); +} diff --git a/PKHeX.WinForms/Controls/Slots/DropModifier.cs b/PKHeX.WinForms/Controls/Slots/DropModifier.cs index 4a042cd50..a4c1bcdaf 100644 --- a/PKHeX.WinForms/Controls/Slots/DropModifier.cs +++ b/PKHeX.WinForms/Controls/Slots/DropModifier.cs @@ -1,9 +1,8 @@ -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public enum DropModifier { - public enum DropModifier - { - None, - Overwrite, - Clone, - } -} \ No newline at end of file + None, + Overwrite, + Clone, +} diff --git a/PKHeX.WinForms/Controls/Slots/PokeGrid.cs b/PKHeX.WinForms/Controls/Slots/PokeGrid.cs index 2fd5f0103..92f8ca2eb 100644 --- a/PKHeX.WinForms/Controls/Slots/PokeGrid.cs +++ b/PKHeX.WinForms/Controls/Slots/PokeGrid.cs @@ -1,89 +1,88 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Windows.Forms; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class PokeGrid : UserControl { - public partial class PokeGrid : UserControl + public PokeGrid() { - public PokeGrid() - { - InitializeComponent(); - } - - public readonly List Entries = new(); - public int Slots { get; private set; } - - private int sizeW = 68; - private int sizeH = 56; - - public bool InitializeGrid(int width, int height, SpriteBuilder info) - { - var newCount = width * height; - if (Slots == newCount) - { - if (info.Width == sizeW && info.Height == sizeH) - return false; - } - - sizeW = info.Width; - sizeH = info.Height; - Generate(width, height); - Slots = newCount; - - return true; - } - - private const int padEdge = 1; // edges - private const int border = 1; // between - - private void Generate(int width, int height) - { - SuspendLayout(); - Controls.Clear(); - foreach (var c in Entries) - c.Dispose(); - Entries.Clear(); - - int colWidth = sizeW; - int rowHeight = sizeH; - - Location = new Point(0, Location.Y); // prevent auto-expanding parent if position changes (centered) - for (int row = 0; row < height; row++) - { - var y = padEdge + (row * (rowHeight + border)); - for (int column = 0; column < width; column++) - { - var x = padEdge + (column * (colWidth + border)); - var pb = GetControl(sizeW, sizeH); - pb.Location = new Point(x, y); - Entries.Add(pb); - } - } - - int w = (2 * padEdge) + border + (width * (colWidth + border)); - int h = (2 * padEdge) + border + (height * (rowHeight + border)); - Size = new Size(w, h); - Controls.AddRange(Entries.Cast().ToArray()); - Debug.WriteLine($"{Name} -- Width: {Width}, Height: {Height}"); - ResumeLayout(); - } - - public void SetBackground(Image img) => BackgroundImage = img; - - public static PictureBox GetControl(int width, int height) => new() - { - AutoSize = false, - SizeMode = PictureBoxSizeMode.CenterImage, - BackColor = SlotUtil.GoodDataColor, - Width = width + (2 * border), - Height = height + (2 * border), - Padding = Padding.Empty, - Margin = Padding.Empty, - BorderStyle = BorderStyle.FixedSingle, - }; + InitializeComponent(); } + + public readonly List Entries = new(); + public int Slots { get; private set; } + + private int sizeW = 68; + private int sizeH = 56; + + public bool InitializeGrid(int width, int height, SpriteBuilder info) + { + var newCount = width * height; + if (Slots == newCount) + { + if (info.Width == sizeW && info.Height == sizeH) + return false; + } + + sizeW = info.Width; + sizeH = info.Height; + Generate(width, height); + Slots = newCount; + + return true; + } + + private const int padEdge = 1; // edges + private const int border = 1; // between + + private void Generate(int width, int height) + { + SuspendLayout(); + Controls.Clear(); + foreach (var c in Entries) + c.Dispose(); + Entries.Clear(); + + int colWidth = sizeW; + int rowHeight = sizeH; + + Location = Location with { X = 0 }; // prevent auto-expanding parent if position changes (centered) + for (int row = 0; row < height; row++) + { + var y = padEdge + (row * (rowHeight + border)); + for (int column = 0; column < width; column++) + { + var x = padEdge + (column * (colWidth + border)); + var pb = GetControl(sizeW, sizeH); + pb.Location = new Point(x, y); + Entries.Add(pb); + } + } + + int w = (2 * padEdge) + border + (width * (colWidth + border)); + int h = (2 * padEdge) + border + (height * (rowHeight + border)); + Size = new Size(w, h); + Controls.AddRange(Entries.Cast().ToArray()); + Debug.WriteLine($"{Name} -- Width: {Width}, Height: {Height}"); + ResumeLayout(); + } + + public void SetBackground(Image img) => BackgroundImage = img; + + public static PictureBox GetControl(int width, int height) => new() + { + AutoSize = false, + SizeMode = PictureBoxSizeMode.CenterImage, + BackColor = SlotUtil.GoodDataColor, + Width = width + (2 * border), + Height = height + (2 * border), + Padding = Padding.Empty, + Margin = Padding.Empty, + BorderStyle = BorderStyle.FixedSingle, + }; } diff --git a/PKHeX.WinForms/Controls/Slots/SlotChangeInfo.cs b/PKHeX.WinForms/Controls/Slots/SlotChangeInfo.cs index 770cec03b..2a29564b5 100644 --- a/PKHeX.WinForms/Controls/Slots/SlotChangeInfo.cs +++ b/PKHeX.WinForms/Controls/Slots/SlotChangeInfo.cs @@ -1,35 +1,34 @@ -using PKHeX.Core; +using PKHeX.Core; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public sealed class SlotChangeInfo where TCursor : class { - public sealed class SlotChangeInfo where TCursor : class + public bool LeftMouseIsDown { get; set; } + public bool DragDropInProgress { get; set; } + + public TCursor? Cursor { get; set; } + public string? CurrentPath { get; set; } + + public SlotViewInfo? Source { get; set; } + public SlotViewInfo? Destination { get; set; } + + public void Reset() { - public bool LeftMouseIsDown { get; set; } - public bool DragDropInProgress { get; set; } - - public TCursor? Cursor { get; set; } - public string? CurrentPath { get; set; } - - public SlotViewInfo? Source { get; set; } - public SlotViewInfo? Destination { get; set; } - - public void Reset() - { - LeftMouseIsDown = DragDropInProgress = false; - CurrentPath = null; - Cursor = default; - } - - public bool SameLocation => (Destination != null) && (Source?.Equals(Destination) ?? false); - - private bool SourceIsParty => Source?.Slot is SlotInfoParty; - private bool DestinationIsParty => Destination?.Slot is SlotInfoParty; - - /// - /// Used to indicate if the changes will alter the player's party data state. - /// - public bool DragIsParty => SourceIsParty || DestinationIsParty; - - public bool DragIsSwap => Source is not null && Destination is not null; + LeftMouseIsDown = DragDropInProgress = false; + CurrentPath = null; + Cursor = default; } + + public bool SameLocation => (Destination != null) && (Source?.Equals(Destination) ?? false); + + private bool SourceIsParty => Source?.Slot is SlotInfoParty; + private bool DestinationIsParty => Destination?.Slot is SlotInfoParty; + + /// + /// Used to indicate if the changes will alter the player's party data state. + /// + public bool DragIsParty => SourceIsParty || DestinationIsParty; + + public bool DragIsSwap => Source is not null && Destination is not null; } diff --git a/PKHeX.WinForms/Controls/Slots/SlotHoverHandler.cs b/PKHeX.WinForms/Controls/Slots/SlotHoverHandler.cs index 6d9406676..5601492c9 100644 --- a/PKHeX.WinForms/Controls/Slots/SlotHoverHandler.cs +++ b/PKHeX.WinForms/Controls/Slots/SlotHoverHandler.cs @@ -1,83 +1,82 @@ -using System; +using System; using System.Drawing; using System.Windows.Forms; using PKHeX.Core; using PKHeX.Drawing; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +/// +/// Handles Hovering operations for an editor, where only one (1) slot can be animated at a given time when hovering over it. +/// +public sealed class SlotHoverHandler : IDisposable { - /// - /// Handles Hovering operations for an editor, where only one (1) slot can be animated at a given time when hovering over it. - /// - public sealed class SlotHoverHandler : IDisposable + public DrawConfig Draw { private get; set; } = new(); + public bool GlowHover { private get; set; } = true; + + public static readonly CryPlayer CryPlayer = new(); + public static readonly SummaryPreviewer Preview = new(); + private static Bitmap Hover => SpriteUtil.Spriter.Hover; + + private readonly BitmapAnimator HoverWorker = new(); + + private PictureBox? Slot; + private SlotTrackerImage? LastSlot; + + public void Start(PictureBox pb, SlotTrackerImage lastSlot) { - public DrawConfig Draw { private get; set; } = new(); - public bool GlowHover { private get; set; } = true; + var view = WinFormsUtil.FindFirstControlOfType>(pb); + if (view == null) + throw new InvalidCastException(nameof(view)); + var data = view.GetSlotData(pb); + var pk = data.Read(view.SAV); + Slot = pb; + LastSlot = lastSlot; - public static readonly CryPlayer CryPlayer = new(); - public static readonly SummaryPreviewer Preview = new(); - private static Bitmap Hover => SpriteUtil.Spriter.Hover; + var orig = LastSlot.OriginalBackground = pb.BackgroundImage; - private readonly BitmapAnimator HoverWorker = new(); - - private PictureBox? Slot; - private SlotTrackerImage? LastSlot; - - public void Start(PictureBox pb, SlotTrackerImage lastSlot) + Bitmap bg; + if (GlowHover) { - var view = WinFormsUtil.FindFirstControlOfType>(pb); - if (view == null) - throw new InvalidCastException(nameof(view)); - var data = view.GetSlotData(pb); - var pk = data.Read(view.SAV); - Slot = pb; - LastSlot = lastSlot; - - var orig = LastSlot.OriginalBackground = pb.BackgroundImage; - - Bitmap bg; - if (GlowHover) - { - HoverWorker.Stop(); - var hover = Hover; - SpriteUtil.GetSpriteGlow(pk, Draw.GlowInitial.B, Draw.GlowInitial.G, Draw.GlowInitial.R, out var glowdata, out var GlowBase); - bg = ImageUtil.LayerImage(GlowBase, hover, 0, 0); - HoverWorker.GlowToColor = Draw.GlowFinal; - HoverWorker.GlowFromColor = Draw.GlowInitial; - HoverWorker.Start(pb, GlowBase, glowdata, orig, hover); - } - else - { - bg = Hover; - } - - if (orig != null) - bg = ImageUtil.LayerImage(orig, bg, 0, 0); - pb.BackgroundImage = LastSlot.CurrentBackground = bg; - - Preview.Show(pb, pk); + HoverWorker.Stop(); + var hover = Hover; + SpriteUtil.GetSpriteGlow(pk, Draw.GlowInitial.B, Draw.GlowInitial.G, Draw.GlowInitial.R, out var glowdata, out var GlowBase); + bg = ImageUtil.LayerImage(GlowBase, hover, 0, 0); + HoverWorker.GlowToColor = Draw.GlowFinal; + HoverWorker.GlowFromColor = Draw.GlowInitial; + HoverWorker.Start(pb, GlowBase, glowdata, orig, hover); + } + else + { + bg = Hover; } - public void Stop() - { - if (Slot != null) - { - if (HoverWorker.Enabled) - HoverWorker.Stop(); - else - Slot.BackgroundImage = LastSlot?.OriginalBackground; - Slot = null; - LastSlot = null; - } - Preview.Clear(); - } + if (orig != null) + bg = ImageUtil.LayerImage(orig, bg, 0, 0); + pb.BackgroundImage = LastSlot.CurrentBackground = bg; - public void Dispose() - { - HoverWorker.Dispose(); - Slot = null; - Draw.Dispose(); - } + Preview.Show(pb, pk); } -} \ No newline at end of file + + public void Stop() + { + if (Slot != null) + { + if (HoverWorker.Enabled) + HoverWorker.Stop(); + else + Slot.BackgroundImage = LastSlot?.OriginalBackground; + Slot = null; + LastSlot = null; + } + Preview.Clear(); + } + + public void Dispose() + { + HoverWorker.Dispose(); + Slot = null; + Draw.Dispose(); + } +} diff --git a/PKHeX.WinForms/Controls/Slots/SlotList.cs b/PKHeX.WinForms/Controls/Slots/SlotList.cs index 79b06a9c0..c012beab0 100644 --- a/PKHeX.WinForms/Controls/Slots/SlotList.cs +++ b/PKHeX.WinForms/Controls/Slots/SlotList.cs @@ -4,176 +4,175 @@ using PKHeX.Core; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public partial class SlotList : UserControl, ISlotViewer { - public partial class SlotList : UserControl, ISlotViewer + private static readonly string[] names = Enum.GetNames(typeof(StorageSlotType)); + private readonly LabelType[] Labels = new LabelType[names.Length]; + private readonly List slots = new(); + private List SlotOffsets = new(); + public int SlotCount { get; private set; } + public SaveFile SAV { get; set; } = null!; + public bool FlagIllegal { get; set; } + + public SlotList() { - private static readonly string[] names = Enum.GetNames(typeof(StorageSlotType)); - private readonly LabelType[] Labels = new LabelType[names.Length]; - private readonly List slots = new(); - private List SlotOffsets = new(); - public int SlotCount { get; private set; } - public SaveFile SAV { get; set; } = null!; - public bool FlagIllegal { get; set; } + InitializeComponent(); + AddLabels(); + } - public SlotList() + /// + /// Initializes the extra slot viewers with a list of offsets and sets up event handling. + /// + /// Extra slots to show + /// Events to set up + /// Uses an object pool for viewers (only generates as needed) + public void Initialize(List list, Action enableDragDropContext) + { + SlotOffsets = list; + LoadSlots(list.Count, enableDragDropContext); + } + + /// + /// Hides all slots from the . + /// + public void HideAllSlots() => LoadSlots(0, _ => { }); + + public void NotifySlotOld(ISlotInfo previous) + { + if (previous is not SlotInfoMisc m) + return; + var index = SlotOffsets.FindIndex(z => m.Equals(z)); + if (index < 0) + return; + var pb = slots[index]; + pb.BackgroundImage = null; + } + + public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pk) + { + if (slot is not SlotInfoMisc m) + return; + var index = GetViewIndex(m); + if (index < 0) + return; + var pb = slots[index]; + SlotUtil.UpdateSlot(pb, slot, pk, SAV, FlagIllegal, type); + } + + public int GetViewIndex(ISlotInfo info) => SlotOffsets.FindIndex(info.Equals); + + public ISlotInfo GetSlotData(PictureBox view) + { + int slot = GetSlot(view); + return GetSlotData(slot); + } + + public ISlotInfo GetSlotData(int slot) => SlotOffsets[slot]; + + public IList SlotPictureBoxes => slots; + + public int GetSlot(PictureBox sender) + { + var view = WinFormsUtil.GetUnderlyingControl(sender); + if (view == null) + return -1; + return slots.IndexOf(view); + } + + public int GetSlotOffset(int slot) => SlotOffsets[slot].Offset; + public int ViewIndex { get; set; } = -1; + + private IEnumerable LoadSlots(int after, Action enableDragDropContext) + { + var generated = new List(); + int before = SlotCount; + SlotCount = after; + int diff = after - before; + if (diff > 0) { - InitializeComponent(); - AddLabels(); + AddSlots(diff); + for (int i = before; i < after; i++) + { + var slot = slots[i]; + enableDragDropContext(slot); + FLP_Slots.Controls.Add(slot); + FLP_Slots.SetFlowBreak(slot, true); + generated.Add(slot); + } } - - /// - /// Initializes the extra slot viewers with a list of offsets and sets up event handling. - /// - /// Extra slots to show - /// Events to set up - /// Uses an object pool for viewers (only generates as needed) - public void Initialize(List list, Action enableDragDropContext) + else { - SlotOffsets = list; - LoadSlots(list.Count, enableDragDropContext); + for (int i = before - 1; i >= after; i--) + FLP_Slots.Controls.Remove(slots[i]); } + SetLabelVisibility(); + return generated; + } - /// - /// Hides all slots from the . - /// - public void HideAllSlots() => LoadSlots(0, _ => { }); + private void AddSlots(int count) + { + for (int i = 0; i < count; i++) + slots.Add(GetPictureBox(i, SpriteUtil.Spriter)); + } - public void NotifySlotOld(ISlotInfo previous) + private const int PadPixels = 2; + + private static PictureBox GetPictureBox(int index, SpriteBuilder s) => new() + { + BorderStyle = BorderStyle.FixedSingle, + Width = s.Width + 2, + Height = s.Height + 2, + AllowDrop = true, + Margin = new Padding(PadPixels), + SizeMode = PictureBoxSizeMode.CenterImage, + Name = $"bpkm{index}", + }; + + private sealed class LabelType : Label + { + public StorageSlotType Type; + } + + private void AddLabels() + { + for (var i = 0; i < names.Length; i++) { - if (previous is not SlotInfoMisc m) - return; - var index = SlotOffsets.FindIndex(z => m.Equals(z)); + var name = names[i]; + bool result = Enum.TryParse(name, out var value); + if (!result) + continue; + + var label = new LabelType + { + Name = $"L_{name}", + Text = name, + Type = value, + AutoSize = true, + Visible = false, + }; + Labels[i] = label; + FLP_Slots.Controls.Add(label); + FLP_Slots.SetFlowBreak(label, true); + } + } + + private void SetLabelVisibility() + { + foreach (var l in Labels) + { + int index = SlotOffsets.FindIndex(z => z.Type == l.Type); if (index < 0) - return; - var pb = slots[index]; - pb.BackgroundImage = null; - } - - public void NotifySlotChanged(ISlotInfo slot, SlotTouchType type, PKM pkm) - { - if (slot is not SlotInfoMisc m) - return; - var index = GetViewIndex(m); - if (index < 0) - return; - var pb = slots[index]; - SlotUtil.UpdateSlot(pb, slot, pkm, SAV, FlagIllegal, type); - } - - public int GetViewIndex(ISlotInfo info) => SlotOffsets.FindIndex(info.Equals); - - public ISlotInfo GetSlotData(PictureBox view) - { - int slot = GetSlot(view); - return GetSlotData(slot); - } - - public ISlotInfo GetSlotData(int slot) => SlotOffsets[slot]; - - public IList SlotPictureBoxes => slots; - - public int GetSlot(PictureBox sender) - { - var view = WinFormsUtil.GetUnderlyingControl(sender); - if (view == null) - return -1; - return slots.IndexOf(view); - } - - public int GetSlotOffset(int slot) => SlotOffsets[slot].Offset; - public int ViewIndex { get; set; } = -1; - - private IEnumerable LoadSlots(int after, Action enableDragDropContext) - { - var generated = new List(); - int before = SlotCount; - SlotCount = after; - int diff = after - before; - if (diff > 0) { - AddSlots(diff); - for (int i = before; i < after; i++) - { - var slot = slots[i]; - enableDragDropContext(slot); - FLP_Slots.Controls.Add(slot); - FLP_Slots.SetFlowBreak(slot, true); - generated.Add(slot); - } - } - else - { - for (int i = before - 1; i >= after; i--) - FLP_Slots.Controls.Remove(slots[i]); - } - SetLabelVisibility(); - return generated; - } - - private void AddSlots(int count) - { - for (int i = 0; i < count; i++) - slots.Add(GetPictureBox(i, SpriteUtil.Spriter)); - } - - private const int PadPixels = 2; - - private static PictureBox GetPictureBox(int index, SpriteBuilder s) => new() - { - BorderStyle = BorderStyle.FixedSingle, - Width = s.Width + 2, - Height = s.Height + 2, - AllowDrop = true, - Margin = new Padding(PadPixels), - SizeMode = PictureBoxSizeMode.CenterImage, - Name = $"bpkm{index}", - }; - - private sealed class LabelType : Label - { - public StorageSlotType Type; - } - - private void AddLabels() - { - for (var i = 0; i < names.Length; i++) - { - var name = names[i]; - bool result = Enum.TryParse(name, out var value); - if (!result) - continue; - - var label = new LabelType - { - Name = $"L_{name}", - Text = name, - Type = value, - AutoSize = true, - Visible = false, - }; - Labels[i] = label; - FLP_Slots.Controls.Add(label); - FLP_Slots.SetFlowBreak(label, true); - } - } - - private void SetLabelVisibility() - { - foreach (var l in Labels) - { - int index = SlotOffsets.FindIndex(z => z.Type == l.Type); - if (index < 0) - { - l.Visible = false; - continue; - } - int pos = FLP_Slots.Controls.IndexOf(slots[index]); - if (pos > FLP_Slots.Controls.IndexOf(l)) - pos--; - FLP_Slots.Controls.SetChildIndex(l, pos); - l.Visible = true; + l.Visible = false; + continue; } + int pos = FLP_Slots.Controls.IndexOf(slots[index]); + if (pos > FLP_Slots.Controls.IndexOf(l)) + pos--; + FLP_Slots.Controls.SetChildIndex(l, pos); + l.Visible = true; } } } diff --git a/PKHeX.WinForms/Controls/Slots/SlotTrackerImage.cs b/PKHeX.WinForms/Controls/Slots/SlotTrackerImage.cs index 39b0a153c..3bc21ac77 100644 --- a/PKHeX.WinForms/Controls/Slots/SlotTrackerImage.cs +++ b/PKHeX.WinForms/Controls/Slots/SlotTrackerImage.cs @@ -1,15 +1,14 @@ using System.Drawing; -namespace PKHeX.WinForms.Controls -{ - public sealed class SlotTrackerImage - { - public Image? OriginalBackground { get; set; } - public Image? CurrentBackground { get; set; } +namespace PKHeX.WinForms.Controls; - public void Reset() - { - OriginalBackground = CurrentBackground = null; - } +public sealed class SlotTrackerImage +{ + public Image? OriginalBackground { get; set; } + public Image? CurrentBackground { get; set; } + + public void Reset() + { + OriginalBackground = CurrentBackground = null; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Controls/Slots/SlotUtil.cs b/PKHeX.WinForms/Controls/Slots/SlotUtil.cs index 20f546370..3ae7d96ab 100644 --- a/PKHeX.WinForms/Controls/Slots/SlotUtil.cs +++ b/PKHeX.WinForms/Controls/Slots/SlotUtil.cs @@ -4,68 +4,67 @@ using PKHeX.Core; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +/// +/// Utility logic for drawing individual Slot views that represent underlying data. +/// +public static class SlotUtil { /// - /// Utility logic for drawing individual Slot views that represent underlying data. + /// Gets the background image for a slot based on the provided . /// - public static class SlotUtil + public static Image GetTouchTypeBackground(SlotTouchType type) => type switch { - /// - /// Gets the background image for a slot based on the provided . - /// - public static Image GetTouchTypeBackground(SlotTouchType type) => type switch + SlotTouchType.None => SpriteUtil.Spriter.Transparent, + SlotTouchType.Get => SpriteUtil.Spriter.View, + SlotTouchType.Set => SpriteUtil.Spriter.Set, + SlotTouchType.Delete => SpriteUtil.Spriter.Delete, + SlotTouchType.Swap => SpriteUtil.Spriter.Set, + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null), + }; + + /// + /// Gets the type of action that should be performed for a Drag & Drop request. + /// + public static DropModifier GetDropModifier() => Control.ModifierKeys switch + { + Keys.Shift => DropModifier.Clone, + Keys.Alt => DropModifier.Overwrite, + _ => DropModifier.None, + }; + + public static readonly Color GoodDataColor = Color.Transparent; + public static readonly Color BadDataColor = Color.Red; + + /// + /// Refreshes a with the appropriate display content. + /// + public static void UpdateSlot(PictureBox pb, ISlotInfo c, PKM p, SaveFile s, bool flagIllegal, SlotTouchType t = SlotTouchType.None) + { + pb.BackgroundImage = GetTouchTypeBackground(t); + if (p.Species == 0) // Nothing in slot { - SlotTouchType.None => SpriteUtil.Spriter.Transparent, - SlotTouchType.Get => SpriteUtil.Spriter.View, - SlotTouchType.Set => SpriteUtil.Spriter.Set, - SlotTouchType.Delete => SpriteUtil.Spriter.Delete, - SlotTouchType.Swap => SpriteUtil.Spriter.Set, - _ => throw new ArgumentOutOfRangeException(nameof(type), type, null), - }; - - /// - /// Gets the type of action that should be performed for a Drag & Drop request. - /// - public static DropModifier GetDropModifier() => Control.ModifierKeys switch - { - Keys.Shift => DropModifier.Clone, - Keys.Alt => DropModifier.Overwrite, - _ => DropModifier.None, - }; - - public static readonly Color GoodDataColor = Color.Transparent; - public static readonly Color BadDataColor = Color.Red; - - /// - /// Refreshes a with the appropriate display content. - /// - public static void UpdateSlot(PictureBox pb, ISlotInfo c, PKM p, SaveFile s, bool flagIllegal, SlotTouchType t = SlotTouchType.None) - { - pb.BackgroundImage = GetTouchTypeBackground(t); - if (p.Species == 0) // Nothing in slot - { - pb.Image = null; - pb.BackColor = GoodDataColor; - return; - } - if (!p.Valid) // Invalid - { - // Bad Egg present in slot. - pb.Image = null; - pb.BackColor = BadDataColor; - return; - } - - var img = c switch - { - SlotInfoBox b => p.Sprite(s, b.Box, b.Slot, flagIllegal), - SlotInfoParty ps => p.Sprite(s, -1, ps.Slot, flagIllegal), - _ => p.Sprite(s, -1, -1, flagIllegal), - }; - - pb.BackColor = Color.Transparent; - pb.Image = img; + pb.Image = null; + pb.BackColor = GoodDataColor; + return; } + if (!p.Valid) // Invalid + { + // Bad Egg present in slot. + pb.Image = null; + pb.BackColor = BadDataColor; + return; + } + + var img = c switch + { + SlotInfoBox b => p.Sprite(s, b.Box, b.Slot, flagIllegal), + SlotInfoParty ps => p.Sprite(s, -1, ps.Slot, flagIllegal), + _ => p.Sprite(s, -1, -1, flagIllegal), + }; + + pb.BackColor = Color.Transparent; + pb.Image = img; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs b/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs index c24c4c1eb..94b35809c 100644 --- a/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs +++ b/PKHeX.WinForms/Controls/Slots/SummaryPreviewer.cs @@ -6,97 +6,96 @@ using static PKHeX.Core.LegalityCheckStrings; -namespace PKHeX.WinForms.Controls +namespace PKHeX.WinForms.Controls; + +public sealed class SummaryPreviewer { - public sealed class SummaryPreviewer + private readonly ToolTip ShowSet = new() { InitialDelay = 200, IsBalloon = false, AutoPopDelay = 32_767 }; + private readonly CryPlayer Cry = new(); + + public void Show(Control pb, PKM pk) { - private readonly ToolTip ShowSet = new() { InitialDelay = 200, IsBalloon = false, AutoPopDelay = 32_767 }; - private readonly CryPlayer Cry = new(); - - public void Show(Control pb, PKM pk) + if (pk.Species == 0) { - if (pk.Species == 0) - { - Clear(); - return; - } - - if (Main.Settings.Hover.HoverSlotShowText) - { - var text = ShowdownParsing.GetLocalizedPreviewText(pk, Main.Settings.Startup.Language); - var la = new LegalityAnalysis(pk); - var result = new List { text, string.Empty }; - LegalityFormatting.AddEncounterInfo(la, result); - ShowSet.SetToolTip(pb, string.Join(Environment.NewLine, result)); - } - - if (Main.Settings.Hover.HoverSlotPlayCry) - Cry.PlayCry(pk, pk.Format); + Clear(); + return; } - public void Show(Control pb, IEncounterInfo enc) + if (Main.Settings.Hover.HoverSlotShowText) { - if (enc.Species == 0) - { - Clear(); - return; - } - - if (Main.Settings.Hover.HoverSlotShowText) - { - var lines = GetTextLines(enc); - var text = string.Join(Environment.NewLine, lines); - ShowSet.SetToolTip(pb, text); - } - - if (Main.Settings.Hover.HoverSlotPlayCry) - Cry.PlayCry(enc, enc.Generation); + var text = ShowdownParsing.GetLocalizedPreviewText(pk, Main.Settings.Startup.Language); + var la = new LegalityAnalysis(pk); + var result = new List { text, string.Empty }; + LegalityFormatting.AddEncounterInfo(la, result); + ShowSet.SetToolTip(pb, string.Join(Environment.NewLine, result)); } - public static IEnumerable GetTextLines(IEncounterInfo enc) - { - var lines = new List(); - var str = GameInfo.Strings.Species; - var name = (uint) enc.Species < str.Count ? str[enc.Species] : enc.Species.ToString(); - var EncounterName = $"{(enc is IEncounterable ie ? ie.LongName : "Special")} ({name})"; - lines.Add(string.Format(L_FEncounterType_0, EncounterName)); - if (enc is MysteryGift mg) - { - lines.AddRange(mg.GetDescription()); - } - else if (enc is IMoveset m) - { - var nonzero = m.Moves.Where(z => z != 0).ToList(); - if (nonzero.Count != 0) - lines.Add(string.Join(" / ", nonzero.Select(z => GameInfo.Strings.Move[z]))); - } + if (Main.Settings.Hover.HoverSlotPlayCry) + Cry.PlayCry(pk, pk.Format); + } - var el = enc as ILocation; - var loc = el?.GetEncounterLocation(enc.Generation, (int) enc.Version); - if (!string.IsNullOrEmpty(loc)) - lines.Add(string.Format(L_F0_1, "Location", loc)); - lines.Add(string.Format(L_F0_1, nameof(GameVersion), enc.Version)); - lines.Add(enc.LevelMin == enc.LevelMax - ? $"Level: {enc.LevelMin}" - : $"Level: {enc.LevelMin}-{enc.LevelMax}"); + public void Show(Control pb, IEncounterInfo enc) + { + if (enc.Species == 0) + { + Clear(); + return; + } + + if (Main.Settings.Hover.HoverSlotShowText) + { + var lines = GetTextLines(enc); + var text = string.Join(Environment.NewLine, lines); + ShowSet.SetToolTip(pb, text); + } + + if (Main.Settings.Hover.HoverSlotPlayCry) + Cry.PlayCry(enc, enc.Generation); + } + + public static IEnumerable GetTextLines(IEncounterInfo enc) + { + var lines = new List(); + var str = GameInfo.Strings.Species; + var name = (uint) enc.Species < str.Count ? str[enc.Species] : enc.Species.ToString(); + var EncounterName = $"{(enc is IEncounterable ie ? ie.LongName : "Special")} ({name})"; + lines.Add(string.Format(L_FEncounterType_0, EncounterName)); + if (enc is MysteryGift mg) + { + lines.AddRange(mg.GetDescription()); + } + else if (enc is IMoveset m) + { + var nonzero = m.Moves.Where(z => z != 0).ToList(); + if (nonzero.Count != 0) + lines.Add(string.Join(" / ", nonzero.Select(z => GameInfo.Strings.Move[z]))); + } + + var el = enc as ILocation; + var loc = el?.GetEncounterLocation(enc.Generation, (int) enc.Version); + if (!string.IsNullOrEmpty(loc)) + lines.Add(string.Format(L_F0_1, "Location", loc)); + lines.Add(string.Format(L_F0_1, nameof(GameVersion), enc.Version)); + lines.Add(enc.LevelMin == enc.LevelMax + ? $"Level: {enc.LevelMin}" + : $"Level: {enc.LevelMin}-{enc.LevelMax}"); #if DEBUG - // Record types! Can get a nice summary. - // Won't work neatly for Mystery Gift types since those aren't record types. - if (enc is not MysteryGift) - { - // ReSharper disable once ConstantNullCoalescingCondition - var raw = enc.ToString() ?? throw new ArgumentNullException(nameof(enc)); - lines.AddRange(raw.Split(',', '}', '{')); - } -#endif - return lines; - } - - public void Clear() + // Record types! Can get a nice summary. + // Won't work neatly for Mystery Gift types since those aren't record types. + if (enc is not MysteryGift) { - ShowSet.RemoveAll(); - Cry.Stop(); + // ReSharper disable once ConstantNullCoalescingCondition + var raw = enc.ToString() ?? throw new ArgumentNullException(nameof(enc)); + lines.AddRange(raw.Split(',', '}', '{')); } +#endif + return lines; + } + + public void Clear() + { + ShowSet.RemoveAll(); + Cry.Stop(); } } diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index 4e43aac8c..145dddb99 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; @@ -18,1253 +18,1252 @@ using PKHeX.WinForms.Controls; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class Main : Form { - public partial class Main : Form + private static readonly Version CurrentProgramVersion = Assembly.GetExecutingAssembly().GetName().Version!; + + public Main() { - private static readonly Version CurrentProgramVersion = Assembly.GetExecutingAssembly().GetName().Version!; + string[] args = Environment.GetCommandLineArgs(); + FormLoadInitialSettings(args, out bool showChangelog, out bool BAKprompt); - public Main() + InitializeComponent(); + C_SAV.SetEditEnvironment(new SaveDataEditor(new FakeSaveFile(), PKME_Tabs)); + FormLoadAddEvents(); +#if DEBUG // translation updater -- all controls are added at this point -- call translate now + if (DevUtil.IsUpdatingTranslations) + WinFormsUtil.TranslateInterface(this, CurrentLanguage); // Translate the UI to language. +#endif + FormInitializeSecond(); + FormLoadCheckForUpdates(); + + var startup = new StartupArguments(); + startup.ReadArguments(args); + startup.ReadSettings(Settings.Startup); + startup.ReadTemplateIfNoEntity(TemplatePath); + FormLoadInitialFiles(startup); + + if (Settings.Startup.PluginLoadMethod != PluginLoadSetting.DontLoad) + FormLoadPlugins(); + + if (HaX) { - string[] args = Environment.GetCommandLineArgs(); - FormLoadInitialSettings(args, out bool showChangelog, out bool BAKprompt); - - InitializeComponent(); - C_SAV.SetEditEnvironment(new SaveDataEditor(new FakeSaveFile(), PKME_Tabs)); - FormLoadAddEvents(); - #if DEBUG // translation updater -- all controls are added at this point -- call translate now - if (DevUtil.IsUpdatingTranslations) - WinFormsUtil.TranslateInterface(this, CurrentLanguage); // Translate the UI to language. - #endif - FormInitializeSecond(); - FormLoadCheckForUpdates(); - - var startup = new StartupArguments(); - startup.ReadArguments(args); - startup.ReadSettings(Settings.Startup); - startup.ReadTemplateIfNoEntity(TemplatePath); - FormLoadInitialFiles(startup); - - if (Settings.Startup.PluginLoadMethod != PluginLoadSetting.DontLoad) - FormLoadPlugins(); - - if (HaX) - { - EntityConverter.AllowIncompatibleConversion = EntityCompatibilitySetting.AllowIncompatibleAll; - WinFormsUtil.Alert(MsgProgramIllegalModeActive, MsgProgramIllegalModeBehave); - } - else if (showChangelog) - { - ShowAboutDialog(AboutPage.Changelog); - } - - if (BAKprompt && !Directory.Exists(BackupPath)) - PromptBackup(); - - BringToFront(); - WindowState = FormWindowState.Minimized; - Show(); - WindowState = FormWindowState.Normal; + EntityConverter.AllowIncompatibleConversion = EntityCompatibilitySetting.AllowIncompatibleAll; + WinFormsUtil.Alert(MsgProgramIllegalModeActive, MsgProgramIllegalModeBehave); + } + else if (showChangelog) + { + ShowAboutDialog(AboutPage.Changelog); } - #region Important Variables - public static string CurrentLanguage + if (BAKprompt && !Directory.Exists(BackupPath)) + PromptBackup(); + + BringToFront(); + WindowState = FormWindowState.Minimized; + Show(); + WindowState = FormWindowState.Normal; + } + + #region Important Variables + public static string CurrentLanguage + { + get => GameInfo.CurrentLanguage; + private set => GameInfo.CurrentLanguage = value; + } + + private static bool _unicode; + + public static bool Unicode + { + get => _unicode; + private set { - get => GameInfo.CurrentLanguage; - private set => GameInfo.CurrentLanguage = value; + _unicode = value; + GenderSymbols = value ? GameInfo.GenderSymbolUnicode : GameInfo.GenderSymbolASCII; } + } - private static bool _unicode; + public static IReadOnlyList GenderSymbols { get; private set; } = GameInfo.GenderSymbolUnicode; + public static bool HaX { get; private set; } - public static bool Unicode + private readonly string[] main_langlist = Enum.GetNames(typeof(ProgramLanguage)); + + private static readonly List Plugins = new(); + #endregion + + #region Path Variables + + public static readonly string WorkingDirectory = Application.StartupPath; + public static readonly string DatabasePath = Path.Combine(WorkingDirectory, "pkmdb"); + public static readonly string MGDatabasePath = Path.Combine(WorkingDirectory, "mgdb"); + public static readonly string ConfigPath = Path.Combine(WorkingDirectory, "cfg.json"); + public static readonly string BackupPath = Path.Combine(WorkingDirectory, "bak"); + public static readonly string CryPath = Path.Combine(WorkingDirectory, "sounds"); + private static readonly string TemplatePath = Path.Combine(WorkingDirectory, "template"); + private static readonly string TrainerPath = Path.Combine(WorkingDirectory, "trainers"); + private static readonly string PluginPath = Path.Combine(WorkingDirectory, "plugins"); + private const string ThreadPath = "https://projectpokemon.org/pkhex/"; + + public static readonly PKHeXSettings Settings = PKHeXSettings.GetSettings(ConfigPath); + + #endregion + + #region //// MAIN MENU FUNCTIONS //// + private static void FormLoadInitialSettings(IEnumerable args, out bool showChangelog, out bool BAKprompt) + { + showChangelog = false; + BAKprompt = false; + + FormLoadConfig(out BAKprompt, out showChangelog); + HaX = Settings.Startup.ForceHaXOnLaunch || GetIsHaX(args); + + WinFormsUtil.AddSaveFileExtensions(Settings.Backup.OtherSaveFileExtensions); + SaveFinder.CustomBackupPaths.Clear(); + SaveFinder.CustomBackupPaths.AddRange(Settings.Backup.OtherBackupPaths.Where(Directory.Exists)); + } + + private static bool GetIsHaX(IEnumerable args) + { + foreach (var x in args) { - get => _unicode; - private set - { - _unicode = value; - GenderSymbols = value ? GameInfo.GenderSymbolUnicode : GameInfo.GenderSymbolASCII; - } + if (string.Equals(x.Trim('-'), nameof(HaX), StringComparison.CurrentCultureIgnoreCase)) + return true; } - public static IReadOnlyList GenderSymbols { get; private set; } = GameInfo.GenderSymbolUnicode; - public static bool HaX { get; private set; } - - private readonly string[] main_langlist = Enum.GetNames(typeof(ProgramLanguage)); - - private static readonly List Plugins = new(); - #endregion - - #region Path Variables - - public static readonly string WorkingDirectory = Application.StartupPath; - public static readonly string DatabasePath = Path.Combine(WorkingDirectory, "pkmdb"); - public static readonly string MGDatabasePath = Path.Combine(WorkingDirectory, "mgdb"); - public static readonly string ConfigPath = Path.Combine(WorkingDirectory, "cfg.json"); - public static readonly string BackupPath = Path.Combine(WorkingDirectory, "bak"); - public static readonly string CryPath = Path.Combine(WorkingDirectory, "sounds"); - private static readonly string TemplatePath = Path.Combine(WorkingDirectory, "template"); - private static readonly string TrainerPath = Path.Combine(WorkingDirectory, "trainers"); - private static readonly string PluginPath = Path.Combine(WorkingDirectory, "plugins"); - private const string ThreadPath = "https://projectpokemon.org/pkhex/"; - - public static readonly PKHeXSettings Settings = PKHeXSettings.GetSettings(ConfigPath); - - #endregion - - #region //// MAIN MENU FUNCTIONS //// - private static void FormLoadInitialSettings(IEnumerable args, out bool showChangelog, out bool BAKprompt) - { - showChangelog = false; - BAKprompt = false; - - FormLoadConfig(out BAKprompt, out showChangelog); - HaX = Settings.Startup.ForceHaXOnLaunch || GetIsHaX(args); - - WinFormsUtil.AddSaveFileExtensions(Settings.Backup.OtherSaveFileExtensions); - SaveFinder.CustomBackupPaths.Clear(); - SaveFinder.CustomBackupPaths.AddRange(Settings.Backup.OtherBackupPaths.Where(Directory.Exists)); - } - - private static bool GetIsHaX(IEnumerable args) - { - foreach (var x in args) - { - if (string.Equals(x.Trim('-'), nameof(HaX), StringComparison.CurrentCultureIgnoreCase)) - return true; - } - #if !NET6_0_OR_GREATER - var path = Process.GetCurrentProcess().MainModule!.FileName!; + var path = Process.GetCurrentProcess().MainModule!.FileName!; #else var path = Environment.ProcessPath!; #endif - return Path.GetFileNameWithoutExtension(path).EndsWith(nameof(HaX)); - } + return Path.GetFileNameWithoutExtension(path).EndsWith(nameof(HaX)); + } - private void FormLoadAddEvents() + private void FormLoadAddEvents() + { + C_SAV.Menu_Redo = Menu_Redo; + C_SAV.Menu_Undo = Menu_Undo; + dragout.GiveFeedback += (sender, e) => e.UseDefaultCursors = false; + GiveFeedback += (sender, e) => e.UseDefaultCursors = false; + PKME_Tabs.EnableDragDrop(Main_DragEnter, Main_DragDrop); + C_SAV.EnableDragDrop(Main_DragEnter, Main_DragDrop); + + // ToolTips for Drag&Drop + toolTip.SetToolTip(dragout, "PKM QuickSave"); + + // Box to Tabs D&D + dragout.AllowDrop = true; + + // Add ContextMenus + var mnu = new ContextMenuPKM(); + mnu.RequestEditorLegality += (o, args) => ClickLegality(mnu, args); + mnu.RequestEditorQR += (o, args) => ClickQR(mnu, args); + mnu.RequestEditorSaveAs += (o, args) => MainMenuSave(mnu, args); + dragout.ContextMenuStrip = mnu.mnuL; + C_SAV.menu.RequestEditorLegality = DisplayLegalityReport; + } + + private void FormLoadInitialFiles(StartupArguments args) + { + var sav = args.SAV!; + var path = sav.Metadata.FilePath ?? string.Empty; + OpenSAV(sav, path); + + var pk = args.Entity!; + OpenPKM(pk); + + if (args.Error is { } ex) + ErrorWindow.ShowErrorDialog(MsgFileLoadFailAuto, ex, true); + } + + private void LoadBlankSaveFile(GameVersion ver) + { + var current = C_SAV?.SAV; + var lang = SaveUtil.GetSafeLanguage(current); + var tr = SaveUtil.GetSafeTrainerName(current, lang); + var sav = SaveUtil.GetBlankSAV(ver, tr, lang); + if (sav.Version == GameVersion.Invalid) // will fail to load { - C_SAV.Menu_Redo = Menu_Redo; - C_SAV.Menu_Undo = Menu_Undo; - dragout.GiveFeedback += (sender, e) => e.UseDefaultCursors = false; - GiveFeedback += (sender, e) => e.UseDefaultCursors = false; - PKME_Tabs.EnableDragDrop(Main_DragEnter, Main_DragDrop); - C_SAV.EnableDragDrop(Main_DragEnter, Main_DragDrop); - - // ToolTips for Drag&Drop - toolTip.SetToolTip(dragout, "PKM QuickSave"); - - // Box to Tabs D&D - dragout.AllowDrop = true; - - // Add ContextMenus - var mnu = new ContextMenuPKM(); - mnu.RequestEditorLegality += (o, args) => ClickLegality(mnu, args); - mnu.RequestEditorQR += (o, args) => ClickQR(mnu, args); - mnu.RequestEditorSaveAs += (o, args) => MainMenuSave(mnu, args); - dragout.ContextMenuStrip = mnu.mnuL; - C_SAV.menu.RequestEditorLegality = DisplayLegalityReport; + ver = (GameVersion)GameInfo.VersionDataSource.Max(z => z.Value); + sav = SaveUtil.GetBlankSAV(ver, tr, lang); } + OpenSAV(sav, string.Empty); + C_SAV!.SAV.State.Edited = false; // Prevents form close warning from showing until changes are made + } - private void FormLoadInitialFiles(StartupArguments args) + private void FormLoadCheckForUpdates() + { + Task.Run(async () => { - var sav = args.SAV!; - var path = sav.Metadata.FilePath ?? string.Empty; - OpenSAV(sav, path); - - var pkm = args.Entity!; - OpenPKM(pkm); - - if (args.Error is { } ex) - ErrorWindow.ShowErrorDialog(MsgFileLoadFailAuto, ex, true); - } - - private void LoadBlankSaveFile(GameVersion ver) - { - var current = C_SAV?.SAV; - var lang = SaveUtil.GetSafeLanguage(current); - var tr = SaveUtil.GetSafeTrainerName(current, lang); - var sav = SaveUtil.GetBlankSAV(ver, tr, lang); - if (sav.Version == GameVersion.Invalid) // will fail to load + Version? latestVersion; + // User might not be connected to the internet or with a flaky connection. + try { latestVersion = UpdateUtil.GetLatestPKHeXVersion(); } + catch (Exception ex) { - ver = (GameVersion)GameInfo.VersionDataSource.Max(z => z.Value); - sav = SaveUtil.GetBlankSAV(ver, tr, lang); - } - OpenSAV(sav, string.Empty); - C_SAV!.SAV.State.Edited = false; // Prevents form close warning from showing until changes are made - } - - private void FormLoadCheckForUpdates() - { - Task.Run(async () => - { - Version? latestVersion; - // User might not be connected to the internet or with a flaky connection. - try { latestVersion = UpdateUtil.GetLatestPKHeXVersion(); } - catch (Exception ex) - { - Debug.WriteLine($"Exception while checking for latest version: {ex}"); - return; - } - if (latestVersion is null || latestVersion <= CurrentProgramVersion) - return; - - while (!IsHandleCreated) // Wait for form to be ready - await Task.Delay(2_000).ConfigureAwait(false); - Invoke(() => NotifyNewVersionAvailable(latestVersion)); // invoke on GUI thread - }); - } - - private void NotifyNewVersionAvailable(Version ver) - { - var date = $"{2000 + ver.Major:00}{ver.Minor:00}{ver.Build:00}"; - L_UpdateAvailable.Text = $"{MsgProgramUpdateAvailable} {date}"; - L_UpdateAvailable.Click += (_, _) => Process.Start(ThreadPath); - L_UpdateAvailable.Visible = true; - } - - private static void FormLoadConfig(out bool BAKprompt, out bool showChangelog) - { - BAKprompt = false; - showChangelog = false; - - // Version Check - if (Settings.Startup.Version.Length > 0 && Settings.Startup.ShowChangelogOnUpdate) // already run on system - { - bool parsed = Version.TryParse(Settings.Startup.Version, out var lastrev); - showChangelog = parsed && lastrev < CurrentProgramVersion; - } - Settings.Startup.Version = CurrentProgramVersion.ToString(); // set current ver so this doesn't happen until the user updates next time - - // BAK Prompt - if (!Settings.Backup.BAKPrompt) - BAKprompt = Settings.Backup.BAKPrompt = true; - } - - public static DrawConfig Draw { get; private set; } = new(); - - private void FormInitializeSecond() - { - var settings = Settings; - Draw = C_SAV.M.Hover.Draw = PKME_Tabs.Draw = settings.Draw; - ReloadProgramSettings(settings); - CB_MainLanguage.Items.AddRange(main_langlist); - PB_Legal.Visible = !HaX; - C_SAV.HaX = PKME_Tabs.HaX = HaX; - - #if DEBUG - DevUtil.AddControl(Menu_Tools); - #endif - - // Select Language - CB_MainLanguage.SelectedIndex = GameLanguage.GetLanguageIndex(settings.Startup.Language); - } - - private void FormLoadPlugins() - { - #if !MERGED // merged should load dlls from within too, folder is no longer required - if (!Directory.Exists(PluginPath)) - return; - #endif - try - { - Plugins.AddRange(PluginLoader.LoadPlugins(PluginPath, Settings.Startup.PluginLoadMethod)); - } - catch (InvalidCastException c) - { - WinFormsUtil.Error(MsgPluginFailLoad, c); + Debug.WriteLine($"Exception while checking for latest version: {ex}"); return; } - foreach (var p in Plugins.OrderBy(z => z.Priority)) - p.Initialize(C_SAV, PKME_Tabs, menuStrip1, CurrentProgramVersion); - } - - // Main Menu Strip UI Functions - private void MainMenuOpen(object sender, EventArgs e) - { - if (WinFormsUtil.OpenSAVPKMDialog(C_SAV.SAV.PKMExtensions, out var path)) - OpenQuick(path!); - } - - private void MainMenuSave(object sender, EventArgs e) - { - if (!PKME_Tabs.EditsComplete) - return; - PKM pk = PreparePKM(); - WinFormsUtil.SavePKMDialog(pk); - } - - private void MainMenuExit(object sender, EventArgs e) - { - if (ModifierKeys == Keys.Control) // triggered via hotkey - { - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Quit PKHeX?")) - return; - } - - Close(); - } - - private void MainMenuAbout(object sender, EventArgs e) => ShowAboutDialog(AboutPage.Shortcuts); - - private static void ShowAboutDialog(AboutPage index) - { - using var form = new About(index); - form.ShowDialog(); - } - - // Sub Menu Options - private void MainMenuBoxReport(object sender, EventArgs e) - { - if (this.OpenWindowExists()) + if (latestVersion is null || latestVersion <= CurrentProgramVersion) return; - var report = new ReportGrid(); - report.Show(); - var list = new List(); - SlotInfoLoader.AddFromSaveFile(C_SAV.SAV, list); - report.PopulateData(list); - } + while (!IsHandleCreated) // Wait for form to be ready + await Task.Delay(2_000).ConfigureAwait(false); + Invoke(() => NotifyNewVersionAvailable(latestVersion)); // invoke on GUI thread + }); + } - private void MainMenuDatabase(object sender, EventArgs e) + private void NotifyNewVersionAvailable(Version ver) + { + var date = $"{2000 + ver.Major:00}{ver.Minor:00}{ver.Build:00}"; + L_UpdateAvailable.Text = $"{MsgProgramUpdateAvailable} {date}"; + L_UpdateAvailable.Click += (_, _) => Process.Start(ThreadPath); + L_UpdateAvailable.Visible = true; + } + + private static void FormLoadConfig(out bool BAKprompt, out bool showChangelog) + { + BAKprompt = false; + showChangelog = false; + + // Version Check + if (Settings.Startup.Version.Length > 0 && Settings.Startup.ShowChangelogOnUpdate) // already run on system { - if (ModifierKeys == Keys.Shift) - { - if (!this.OpenWindowExists()) - new KChart(C_SAV.SAV).Show(); + bool parsed = Version.TryParse(Settings.Startup.Version, out var lastrev); + showChangelog = parsed && lastrev < CurrentProgramVersion; + } + Settings.Startup.Version = CurrentProgramVersion.ToString(); // set current ver so this doesn't happen until the user updates next time + + // BAK Prompt + if (!Settings.Backup.BAKPrompt) + BAKprompt = Settings.Backup.BAKPrompt = true; + } + + public static DrawConfig Draw { get; private set; } = new(); + + private void FormInitializeSecond() + { + var settings = Settings; + Draw = C_SAV.M.Hover.Draw = PKME_Tabs.Draw = settings.Draw; + ReloadProgramSettings(settings); + CB_MainLanguage.Items.AddRange(main_langlist); + PB_Legal.Visible = !HaX; + C_SAV.HaX = PKME_Tabs.HaX = HaX; + +#if DEBUG + DevUtil.AddControl(Menu_Tools); +#endif + + // Select Language + CB_MainLanguage.SelectedIndex = GameLanguage.GetLanguageIndex(settings.Startup.Language); + } + + private void FormLoadPlugins() + { +#if !MERGED // merged should load dlls from within too, folder is no longer required + if (!Directory.Exists(PluginPath)) + return; +#endif + try + { + Plugins.AddRange(PluginLoader.LoadPlugins(PluginPath, Settings.Startup.PluginLoadMethod)); + } + catch (InvalidCastException c) + { + WinFormsUtil.Error(MsgPluginFailLoad, c); + return; + } + foreach (var p in Plugins.OrderBy(z => z.Priority)) + p.Initialize(C_SAV, PKME_Tabs, menuStrip1, CurrentProgramVersion); + } + + // Main Menu Strip UI Functions + private void MainMenuOpen(object sender, EventArgs e) + { + if (WinFormsUtil.OpenSAVPKMDialog(C_SAV.SAV.PKMExtensions, out var path)) + OpenQuick(path!); + } + + private void MainMenuSave(object sender, EventArgs e) + { + if (!PKME_Tabs.EditsComplete) + return; + PKM pk = PreparePKM(); + WinFormsUtil.SavePKMDialog(pk); + } + + private void MainMenuExit(object sender, EventArgs e) + { + if (ModifierKeys == Keys.Control) // triggered via hotkey + { + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Quit PKHeX?")) return; - } + } - if (!Directory.Exists(DatabasePath)) - { - WinFormsUtil.Alert(MsgDatabase, string.Format(MsgDatabaseAdvice, DatabasePath)); + Close(); + } + + private void MainMenuAbout(object sender, EventArgs e) => ShowAboutDialog(AboutPage.Shortcuts); + + private static void ShowAboutDialog(AboutPage index) + { + using var form = new About(index); + form.ShowDialog(); + } + + // Sub Menu Options + private void MainMenuBoxReport(object sender, EventArgs e) + { + if (this.OpenWindowExists()) + return; + + var report = new ReportGrid(); + report.Show(); + var list = new List(); + SlotInfoLoader.AddFromSaveFile(C_SAV.SAV, list); + report.PopulateData(list); + } + + private void MainMenuDatabase(object sender, EventArgs e) + { + if (ModifierKeys == Keys.Shift) + { + if (!this.OpenWindowExists()) + new KChart(C_SAV.SAV).Show(); + return; + } + + if (!Directory.Exists(DatabasePath)) + { + WinFormsUtil.Alert(MsgDatabase, string.Format(MsgDatabaseAdvice, DatabasePath)); + return; + } + + if (!this.OpenWindowExists()) + new SAV_Database(PKME_Tabs, C_SAV).Show(); + } + + private void Menu_EncDatabase_Click(object sender, EventArgs e) + { + if (this.OpenWindowExists()) + return; + + var db = new TrainerDatabase(); + var sav = C_SAV.SAV; + Task.Run(() => + { + var dir = TrainerPath; + if (!Directory.Exists(dir)) return; - } + var files = Directory.EnumerateFiles(TrainerPath, "*.*", SearchOption.AllDirectories); + var pk = BoxUtil.GetPKMsFromPaths(files, sav.Context); + foreach (var f in pk) + db.RegisterCopy(f); + }); + new SAV_Encounters(PKME_Tabs, db).Show(); + } - if (!this.OpenWindowExists()) - new SAV_Database(PKME_Tabs, C_SAV).Show(); + private void MainMenuMysteryDB(object sender, EventArgs e) + { + if (!this.OpenWindowExists()) + new SAV_MysteryGiftDB(PKME_Tabs, C_SAV).Show(); + } + + private static void ClosePopups() + { + var forms = Application.OpenForms.OfType
().Where(IsPopupFormType).ToArray(); + foreach (var f in forms) + f.Close(); + } + + private static bool IsPopupFormType(Form z) => z is not (Main or SplashScreen or SAV_FolderList); + + private void MainMenuSettings(object sender, EventArgs e) + { + var settings = Settings; + using var form = new SettingsEditor(settings); + form.ShowDialog(); + + // Reload text (if OT details hidden) + Text = GetProgramTitle(C_SAV.SAV); + // Update final settings + ReloadProgramSettings(Settings); + + if (form.BlankChanged) // changed by user + { + LoadBlankSaveFile(Settings.Startup.DefaultSaveVersion); + return; } - private void Menu_EncDatabase_Click(object sender, EventArgs e) + PKME_Tabs_UpdatePreviewSprite(sender, e); + if (C_SAV.SAV.HasBox) + C_SAV.ReloadSlots(); + } + + private void ReloadProgramSettings(PKHeXSettings settings) + { + Draw.LoadBrushes(); + PKME_Tabs.Unicode = Unicode = settings.Display.Unicode; + PKME_Tabs.UpdateUnicode(GenderSymbols); + SpriteName.AllowShinySprite = settings.Sprite.ShinySprites; + SpriteBuilderUtil.SpriterPreference = settings.Sprite.SpritePreference; + SaveFile.SetUpdateDex = settings.SlotWrite.SetUpdateDex ? PKMImportSetting.Update : PKMImportSetting.Skip; + SaveFile.SetUpdatePKM = settings.SlotWrite.SetUpdatePKM ? PKMImportSetting.Update : PKMImportSetting.Skip; + C_SAV.ModifyPKM = PKME_Tabs.ModifyPKM = settings.SlotWrite.SetUpdatePKM; + CommonEdits.ShowdownSetIVMarkings = settings.Import.ApplyMarkings; + CommonEdits.ShowdownSetBehaviorNature = settings.Import.ApplyNature; + C_SAV.FlagIllegal = settings.Display.FlagIllegal; + C_SAV.M.Hover.GlowHover = settings.Hover.HoverSlotGlowEdges; + ParseSettings.InitFromSettings(settings.Legality); + PKME_Tabs.HideSecretValues = C_SAV.HideSecretDetails = settings.Privacy.HideSecretDetails; + EntityConverter.AllowIncompatibleConversion = settings.Advanced.AllowIncompatibleConversion; + EntityConverter.RejuvenateHOME = settings.Advanced.AllowGuessRejuvenateHOME; + WinFormsUtil.DetectSaveFileOnFileOpen = settings.Startup.TryDetectRecentSave; + + SpriteBuilder.LoadSettings(settings.Sprite); + } + + private void MainMenuBoxLoad(object sender, EventArgs e) + { + string? path = null; + if (Directory.Exists(DatabasePath)) { - if (this.OpenWindowExists()) - return; - - var db = new TrainerDatabase(); - var sav = C_SAV.SAV; - Task.Run(() => - { - var dir = TrainerPath; - if (!Directory.Exists(dir)) - return; - var files = Directory.EnumerateFiles(TrainerPath, "*.*", SearchOption.AllDirectories); - var pkm = BoxUtil.GetPKMsFromPaths(files, sav.Context); - foreach (var f in pkm) - db.RegisterCopy(f); - }); - new SAV_Encounters(PKME_Tabs, db).Show(); - } - - private void MainMenuMysteryDB(object sender, EventArgs e) - { - if (!this.OpenWindowExists()) - new SAV_MysteryGiftDB(PKME_Tabs, C_SAV).Show(); - } - - private static void ClosePopups() - { - var forms = Application.OpenForms.OfType().Where(IsPopupFormType).ToArray(); - foreach (var f in forms) - f.Close(); - } - - private static bool IsPopupFormType(Form z) => z is not (Main or SplashScreen or SAV_FolderList); - - private void MainMenuSettings(object sender, EventArgs e) - { - var settings = Settings; - using var form = new SettingsEditor(settings); - form.ShowDialog(); - - // Reload text (if OT details hidden) - Text = GetProgramTitle(C_SAV.SAV); - // Update final settings - ReloadProgramSettings(Settings); - - if (form.BlankChanged) // changed by user - { - LoadBlankSaveFile(Settings.Startup.DefaultSaveVersion); - return; - } - - PKME_Tabs_UpdatePreviewSprite(sender, e); - if (C_SAV.SAV.HasBox) - C_SAV.ReloadSlots(); - } - - private void ReloadProgramSettings(PKHeXSettings settings) - { - Draw.LoadBrushes(); - PKME_Tabs.Unicode = Unicode = settings.Display.Unicode; - PKME_Tabs.UpdateUnicode(GenderSymbols); - SpriteName.AllowShinySprite = settings.Sprite.ShinySprites; - SpriteBuilderUtil.SpriterPreference = settings.Sprite.SpritePreference; - SaveFile.SetUpdateDex = settings.SlotWrite.SetUpdateDex ? PKMImportSetting.Update : PKMImportSetting.Skip; - SaveFile.SetUpdatePKM = settings.SlotWrite.SetUpdatePKM ? PKMImportSetting.Update : PKMImportSetting.Skip; - C_SAV.ModifyPKM = PKME_Tabs.ModifyPKM = settings.SlotWrite.SetUpdatePKM; - CommonEdits.ShowdownSetIVMarkings = settings.Import.ApplyMarkings; - CommonEdits.ShowdownSetBehaviorNature = settings.Import.ApplyNature; - C_SAV.FlagIllegal = settings.Display.FlagIllegal; - C_SAV.M.Hover.GlowHover = settings.Hover.HoverSlotGlowEdges; - ParseSettings.InitFromSettings(settings.Legality); - PKME_Tabs.HideSecretValues = C_SAV.HideSecretDetails = settings.Privacy.HideSecretDetails; - EntityConverter.AllowIncompatibleConversion = settings.Advanced.AllowIncompatibleConversion; - EntityConverter.RejuvenateHOME = settings.Advanced.AllowGuessRejuvenateHOME; - WinFormsUtil.DetectSaveFileOnFileOpen = settings.Startup.TryDetectRecentSave; - - SpriteBuilder.LoadSettings(settings.Sprite); - } - - private void MainMenuBoxLoad(object sender, EventArgs e) - { - string? path = null; - if (Directory.Exists(DatabasePath)) - { - var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDatabaseLoad); - if (dr == DialogResult.Yes) - path = DatabasePath; - } - if (C_SAV.LoadBoxes(out string result, path)) - WinFormsUtil.Alert(result); - } - - private void MainMenuBoxDump(object sender, EventArgs e) - { - // Dump all of box content to files. - string? path = null; - DialogResult ld = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDatabaseExport); - if (ld == DialogResult.Yes) + var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDatabaseLoad); + if (dr == DialogResult.Yes) path = DatabasePath; - else if (ld != DialogResult.No) - return; + } + if (C_SAV.LoadBoxes(out string result, path)) + WinFormsUtil.Alert(result); + } - if (C_SAV.DumpBoxes(out string result, path)) - WinFormsUtil.Alert(result); + private void MainMenuBoxDump(object sender, EventArgs e) + { + // Dump all of box content to files. + string? path = null; + DialogResult ld = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDatabaseExport); + if (ld == DialogResult.Yes) + path = DatabasePath; + else if (ld != DialogResult.No) + return; + + if (C_SAV.DumpBoxes(out string result, path)) + WinFormsUtil.Alert(result); + } + + private void MainMenuBoxDumpSingle(object sender, EventArgs e) + { + if (C_SAV.DumpBox(out string result)) + WinFormsUtil.Alert(result); + } + + private void MainMenuBatchEditor(object sender, EventArgs e) + { + using var form = new BatchEditor(PKME_Tabs.PreparePKM(), C_SAV.SAV); + form.ShowDialog(); + C_SAV.SetPKMBoxes(); // refresh + C_SAV.UpdateBoxViewers(); + } + + private void MainMenuFolder(object sender, EventArgs e) + { + if (this.OpenWindowExists()) + return; + var form = new SAV_FolderList(s => OpenSAV(s.Clone(), s.Metadata.FilePath!)); + form.Show(); + } + + // Misc Options + private void ClickShowdownImportPKM(object sender, EventArgs e) + { + if (!Clipboard.ContainsText()) + { WinFormsUtil.Alert(MsgClipboardFailRead); return; } + + // Get Simulator Data + var Set = new ShowdownSet(Clipboard.GetText()); + + if (Set.Species < 0) + { WinFormsUtil.Alert(MsgSimulatorFailClipboard); return; } + + var maxLength = C_SAV.SAV.NickLength; + if (Set.Nickname.Length > maxLength) + Set.Nickname = Set.Nickname[..maxLength]; + + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSimulatorLoad, Set.Text)) + return; + + if (Set.InvalidLines.Count > 0) + WinFormsUtil.Alert(MsgSimulatorInvalid, string.Join(Environment.NewLine, Set.InvalidLines)); + + // Set Species & Nickname + PKME_Tabs.LoadShowdownSet(Set); + } + + private void ClickShowdownExportPKM(object sender, EventArgs e) + { + if (!PKME_Tabs.EditsComplete) + { + WinFormsUtil.Alert(MsgSimulatorExportBadFields); + return; } - private void MainMenuBoxDumpSingle(object sender, EventArgs e) + var pk = PreparePKM(); + var text = ShowdownParsing.GetShowdownText(pk); + bool success = WinFormsUtil.SetClipboardText(text); + if (!success || !Clipboard.GetText().Equals(text)) + WinFormsUtil.Alert(MsgClipboardFailWrite, MsgSimulatorExportFail); + else + WinFormsUtil.Alert(MsgSimulatorExportSuccess, text); + } + + private void ClickShowdownExportParty(object sender, EventArgs e) => C_SAV.ClickShowdownExportParty(sender, e); + private void ClickShowdownExportCurrentBox(object sender, EventArgs e) => C_SAV.ClickShowdownExportCurrentBox(sender, e); + + // Main Menu Subfunctions + private void OpenQuick(string path) + { + if (!CanFocus) { - if (C_SAV.DumpBox(out string result)) - WinFormsUtil.Alert(result); + SystemSounds.Asterisk.Play(); + return; } + OpenFromPath(path); + } - private void MainMenuBatchEditor(object sender, EventArgs e) + private void OpenFromPath(string path) + { + if (Plugins.Any(p => p.TryLoadFile(path))) + return; // handled by plugin + + // detect if it is a folder (load into boxes or not) + if (Directory.Exists(path)) + { C_SAV.LoadBoxes(out string _, path); return; } + + var fi = new FileInfo(path); + if (!fi.Exists) + return; + + if (FileUtil.IsFileTooBig(fi.Length)) { - using var form = new BatchEditor(PKME_Tabs.PreparePKM(), C_SAV.SAV); - form.ShowDialog(); - C_SAV.SetPKMBoxes(); // refresh - C_SAV.UpdateBoxViewers(); + WinFormsUtil.Error(MsgFileSizeLarge + Environment.NewLine + string.Format(MsgFileSize, fi.Length), path); + return; } - - private void MainMenuFolder(object sender, EventArgs e) + if (FileUtil.IsFileTooSmall(fi.Length)) { - if (this.OpenWindowExists()) - return; - var form = new SAV_FolderList(s => OpenSAV(s.Clone(), s.Metadata.FilePath!)); - form.Show(); + WinFormsUtil.Error(MsgFileSizeSmall + Environment.NewLine + string.Format(MsgFileSize, fi.Length), path); + return; } + byte[] input; try { input = File.ReadAllBytes(path); } + catch (Exception e) { WinFormsUtil.Error(MsgFileInUse + path, e); return; } - // Misc Options - private void ClickShowdownImportPKM(object sender, EventArgs e) - { - if (!Clipboard.ContainsText()) - { WinFormsUtil.Alert(MsgClipboardFailRead); return; } - - // Get Simulator Data - var Set = new ShowdownSet(Clipboard.GetText()); - - if (Set.Species < 0) - { WinFormsUtil.Alert(MsgSimulatorFailClipboard); return; } - - var maxLength = C_SAV.SAV.NickLength; - if (Set.Nickname.Length > maxLength) - Set.Nickname = Set.Nickname[..maxLength]; - - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgSimulatorLoad, Set.Text)) - return; - - if (Set.InvalidLines.Count > 0) - WinFormsUtil.Alert(MsgSimulatorInvalid, string.Join(Environment.NewLine, Set.InvalidLines)); - - // Set Species & Nickname - PKME_Tabs.LoadShowdownSet(Set); - } - - private void ClickShowdownExportPKM(object sender, EventArgs e) - { - if (!PKME_Tabs.EditsComplete) - { - WinFormsUtil.Alert(MsgSimulatorExportBadFields); - return; - } - - var pk = PreparePKM(); - var text = ShowdownParsing.GetShowdownText(pk); - bool success = WinFormsUtil.SetClipboardText(text); - if (!success || !Clipboard.GetText().Equals(text)) - WinFormsUtil.Alert(MsgClipboardFailWrite, MsgSimulatorExportFail); - else - WinFormsUtil.Alert(MsgSimulatorExportSuccess, text); - } - - private void ClickShowdownExportParty(object sender, EventArgs e) => C_SAV.ClickShowdownExportParty(sender, e); - private void ClickShowdownExportCurrentBox(object sender, EventArgs e) => C_SAV.ClickShowdownExportCurrentBox(sender, e); - - // Main Menu Subfunctions - private void OpenQuick(string path) - { - if (!CanFocus) - { - SystemSounds.Asterisk.Play(); - return; - } - OpenFromPath(path); - } - - private void OpenFromPath(string path) - { - if (Plugins.Any(p => p.TryLoadFile(path))) - return; // handled by plugin - - // detect if it is a folder (load into boxes or not) - if (Directory.Exists(path)) - { C_SAV.LoadBoxes(out string _, path); return; } - - var fi = new FileInfo(path); - if (!fi.Exists) - return; - - if (FileUtil.IsFileTooBig(fi.Length)) - { - WinFormsUtil.Error(MsgFileSizeLarge + Environment.NewLine + string.Format(MsgFileSize, fi.Length), path); - return; - } - if (FileUtil.IsFileTooSmall(fi.Length)) - { - WinFormsUtil.Error(MsgFileSizeSmall + Environment.NewLine + string.Format(MsgFileSize, fi.Length), path); - return; - } - byte[] input; try { input = File.ReadAllBytes(path); } - catch (Exception e) { WinFormsUtil.Error(MsgFileInUse + path, e); return; } - - string ext = fi.Extension; - #if DEBUG - OpenFile(input, path, ext); - #else + string ext = fi.Extension; +#if DEBUG + OpenFile(input, path, ext); +#else try { OpenFile(input, path, ext); } catch (Exception e) { WinFormsUtil.Error(MsgFileLoadFail + "\nPath: " + path, e); } - #endif - } +#endif + } - private void OpenFile(byte[] input, string path, string ext) - { - var obj = FileUtil.GetSupportedFile(input, ext, C_SAV.SAV); - if (obj != null && LoadFile(obj, path)) - return; + private void OpenFile(byte[] input, string path, string ext) + { + var obj = FileUtil.GetSupportedFile(input, ext, C_SAV.SAV); + if (obj != null && LoadFile(obj, path)) + return; - bool isSAV = WinFormsUtil.IsFileExtensionSAV(path); - var msg = isSAV ? MsgFileUnsupported : MsgPKMUnsupported; - WinFormsUtil.Error(msg, - $"{MsgFileLoad}{Environment.NewLine}{path}", - $"{string.Format(MsgFileSize, input.Length)}{Environment.NewLine}{input.Length} bytes (0x{input.Length:X4})"); - } + bool isSAV = WinFormsUtil.IsFileExtensionSAV(path); + var msg = isSAV ? MsgFileUnsupported : MsgPKMUnsupported; + WinFormsUtil.Error(msg, + $"{MsgFileLoad}{Environment.NewLine}{path}", + $"{string.Format(MsgFileSize, input.Length)}{Environment.NewLine}{input.Length} bytes (0x{input.Length:X4})"); + } - private bool LoadFile(object? input, string path) - { - if (input == null) - return false; - - switch (input) - { - case PKM pk: return OpenPKM(pk); - case SaveFile s: return OpenSAV(s, path); - case IPokeGroup b: return OpenGroup(b); - case MysteryGift g: return OpenMysteryGift(g, path); - case IEnumerable pkms: return OpenPCBoxBin(pkms); - case IEncounterConvertible enc: return OpenPKM(enc.ConvertToPKM(C_SAV.SAV)); - - case SAV3GCMemoryCard gc: - if (!CheckGCMemoryCard(gc, path)) - return true; - var mcsav = SaveUtil.GetVariantSAV(gc); - if (mcsav is null) - return false; - return OpenSAV(mcsav, path); - } + private bool LoadFile(object? input, string path) + { + if (input == null) return false; - } - private bool OpenPKM(PKM pk) + switch (input) { - var destType = C_SAV.SAV.PKMType; - var tmp = EntityConverter.ConvertToType(pk, destType, out var c); - Debug.WriteLine(c.GetDisplayString(pk, destType)); - if (tmp == null) - return false; - C_SAV.SAV.AdaptPKM(tmp); - PKME_Tabs.PopulateFields(tmp); - return true; - } + case PKM pk: return OpenPKM(pk); + case SaveFile s: return OpenSAV(s, path); + case IPokeGroup b: return OpenGroup(b); + case MysteryGift g: return OpenMysteryGift(g, path); + case IEnumerable pkms: return OpenPCBoxBin(pkms); + case IEncounterConvertible enc: return OpenPKM(enc.ConvertToPKM(C_SAV.SAV)); - private bool OpenGroup(IPokeGroup b) - { - bool result = C_SAV.OpenGroup(b, out string c); - if (!string.IsNullOrWhiteSpace(c)) - WinFormsUtil.Alert(c); - Debug.WriteLine(c); - return result; - } - - private bool OpenMysteryGift(MysteryGift tg, string path) - { - if (!tg.IsEntity) - { - WinFormsUtil.Alert(MsgPKMMysteryGiftFail, path); - return true; - } - - var temp = tg.ConvertToPKM(C_SAV.SAV); - var destType = C_SAV.SAV.PKMType; - var pk = EntityConverter.ConvertToType(temp, destType, out var c); - - if (pk == null) - { - WinFormsUtil.Alert(c.GetDisplayString(temp, destType)); - return true; - } - - C_SAV.SAV.AdaptPKM(pk); - PKME_Tabs.PopulateFields(pk); - Debug.WriteLine(c); - return true; - } - - private bool OpenPCBoxBin(IEnumerable pkms) - { - var data = pkms.SelectMany(z => z).ToArray(); - if (!C_SAV.OpenPCBoxBin(data, out string c)) - { - WinFormsUtil.Alert(MsgFileLoadIncompatible, c); - return true; - } - - WinFormsUtil.Alert(c); - return true; - } - - private static GameVersion SelectMemoryCardSaveGame(SAV3GCMemoryCard memCard) - { - if (memCard.SaveGameCount == 1) - return memCard.SelectedGameVersion; - - var games = GetMemoryCardGameSelectionList(memCard); - var dialog = new SAV_GameSelect(games, MsgFileLoadSaveMultiple, MsgFileLoadSaveSelectGame); - dialog.ShowDialog(); - return dialog.Result; - } - - private static List GetMemoryCardGameSelectionList(SAV3GCMemoryCard memCard) - { - var games = new List(); - if (memCard.HasCOLO) games.Add(new ComboItem(MsgGameColosseum, (int) GameVersion.COLO)); - if (memCard.HasXD) games.Add(new ComboItem(MsgGameXD, (int) GameVersion.XD)); - if (memCard.HasRSBOX) games.Add(new ComboItem(MsgGameRSBOX, (int) GameVersion.RSBOX)); - return games; - } - - private static bool CheckGCMemoryCard(SAV3GCMemoryCard memCard, string path) - { - var state = memCard.GetMemoryCardState(); - switch (state) - { - case GCMemoryCardState.NoPkmSaveGame: - WinFormsUtil.Error(MsgFileGameCubeNoGames, path); - return false; - - case GCMemoryCardState.DuplicateCOLO: - case GCMemoryCardState.DuplicateXD: - case GCMemoryCardState.DuplicateRSBOX: - WinFormsUtil.Error(MsgFileGameCubeDuplicate, path); - return false; - - case GCMemoryCardState.MultipleSaveGame: - var game = SelectMemoryCardSaveGame(memCard); - if (game == GameVersion.Invalid) //Cancel - return false; - memCard.SelectSaveGame(game); - break; - - case GCMemoryCardState.SaveGameCOLO: memCard.SelectSaveGame(GameVersion.COLO); break; - case GCMemoryCardState.SaveGameXD: memCard.SelectSaveGame(GameVersion.XD); break; - case GCMemoryCardState.SaveGameRSBOX: memCard.SelectSaveGame(GameVersion.RSBOX); break; - - default: - WinFormsUtil.Error(!SaveUtil.IsSizeValid(memCard.Data.Length) ? MsgFileGameCubeBad : MsgFileLoadSaveLoadFail, path); - return false; - } - return true; - } - - private static void StoreLegalSaveGameData(SaveFile sav) - { - if (sav is SAV3 sav3) - EReaderBerrySettings.LoadFrom(sav3); - } - - private bool OpenSAV(SaveFile sav, string path) - { - if (sav.Version == GameVersion.Invalid) - { - WinFormsUtil.Error(MsgFileLoadSaveLoadFail, path); - return true; - } - - sav.Metadata.SetExtraInfo(path); - if (!SanityCheckSAV(ref sav)) - return true; - - if (C_SAV.SAV.State.Edited && Settings.SlotWrite.ModifyUnset) - { - var prompt = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgProgramCloseUnsaved, MsgProgramSaveFileConfirm); - if (prompt != DialogResult.Yes) + case SAV3GCMemoryCard gc: + if (!CheckGCMemoryCard(gc, path)) return true; - } + var mcsav = SaveUtil.GetVariantSAV(gc); + if (mcsav is null) + return false; + return OpenSAV(mcsav, path); + } + return false; + } - ClosePopups(); + private bool OpenPKM(PKM pk) + { + var destType = C_SAV.SAV.PKMType; + var tmp = EntityConverter.ConvertToType(pk, destType, out var c); + Debug.WriteLine(c.GetDisplayString(pk, destType)); + if (tmp == null) + return false; + C_SAV.SAV.AdaptPKM(tmp); + PKME_Tabs.PopulateFields(tmp); + return true; + } - PKME_Tabs.Focus(); // flush any pending changes - StoreLegalSaveGameData(sav); - RecentTrainerCache.SetRecentTrainer(sav); - SpriteUtil.Initialize(sav); // refresh sprite generator - dragout.Size = new Size(SpriteUtil.Spriter.Width, SpriteUtil.Spriter.Height); + private bool OpenGroup(IPokeGroup b) + { + bool result = C_SAV.OpenGroup(b, out string c); + if (!string.IsNullOrWhiteSpace(c)) + WinFormsUtil.Alert(c); + Debug.WriteLine(c); + return result; + } - // clean fields - Menu_ExportSAV.Enabled = sav.State.Exportable; - - // No changes made yet - Menu_Undo.Enabled = false; - Menu_Redo.Enabled = false; - - GameInfo.FilteredSources = new FilteredGameDataSource(sav, GameInfo.Sources, HaX); - ResetSAVPKMEditors(sav); - C_SAV.M.Reset(); - - Text = GetProgramTitle(sav); - TryBackupExportCheck(sav, path); - - Menu_ShowdownExportParty.Visible = sav.HasParty; - Menu_ShowdownExportCurrentBox.Visible = sav.HasBox; - - Settings.Startup.LoadSaveFile(path); - if (Settings.Sounds.PlaySoundSAVLoad) - SystemSounds.Asterisk.Play(); + private bool OpenMysteryGift(MysteryGift tg, string path) + { + if (!tg.IsEntity) + { + WinFormsUtil.Alert(MsgPKMMysteryGiftFail, path); return true; } - private void ResetSAVPKMEditors(SaveFile sav) + var temp = tg.ConvertToPKM(C_SAV.SAV); + var destType = C_SAV.SAV.PKMType; + var pk = EntityConverter.ConvertToType(temp, destType, out var c); + + if (pk == null) { - C_SAV.SetEditEnvironment(new SaveDataEditor(sav, PKME_Tabs)); - - var pk = sav.LoadTemplate(TemplatePath); - PKME_Tabs.CurrentPKM = pk; - - bool init = PKME_Tabs.IsInitialized; - if (!init) - { - PKME_Tabs.InitializeBinding(); - PKME_Tabs.SetPKMFormatMode(pk); - PKME_Tabs.ChangeLanguage(sav, pk); - } - else - { - PKME_Tabs.SetPKMFormatMode(pk); - } - PKME_Tabs.PopulateFields(pk); - - // Initialize Overall Info - Menu_LoadBoxes.Enabled = Menu_DumpBoxes.Enabled = Menu_DumpBox.Enabled = Menu_Report.Enabled = C_SAV.SAV.HasBox; - - // Initialize Subviews - bool WindowTranslationRequired = false; - WindowTranslationRequired |= PKME_Tabs.ToggleInterface(sav, pk); - WindowTranslationRequired |= C_SAV.ToggleInterface(); - if (WindowTranslationRequired) // force update -- re-added controls may be untranslated - WinFormsUtil.TranslateInterface(this, CurrentLanguage); - - PKME_Tabs.PopulateFields(pk); - - sav.State.Edited = false; - foreach (var p in Plugins) - p.NotifySaveLoaded(); + WinFormsUtil.Alert(c.GetDisplayString(temp, destType)); + return true; } - private static string GetProgramTitle() + C_SAV.SAV.AdaptPKM(pk); + PKME_Tabs.PopulateFields(pk); + Debug.WriteLine(c); + return true; + } + + private bool OpenPCBoxBin(IEnumerable pkms) + { + var data = pkms.SelectMany(z => z).ToArray(); + if (!C_SAV.OpenPCBoxBin(data, out string c)) { - #if DEBUG - var date = File.GetLastWriteTime(Assembly.GetEntryAssembly()!.Location); - string version = $"d-{date:yyyyMMdd}"; - #else + WinFormsUtil.Alert(MsgFileLoadIncompatible, c); + return true; + } + + WinFormsUtil.Alert(c); + return true; + } + + private static GameVersion SelectMemoryCardSaveGame(SAV3GCMemoryCard memCard) + { + if (memCard.SaveGameCount == 1) + return memCard.SelectedGameVersion; + + var games = GetMemoryCardGameSelectionList(memCard); + var dialog = new SAV_GameSelect(games, MsgFileLoadSaveMultiple, MsgFileLoadSaveSelectGame); + dialog.ShowDialog(); + return dialog.Result; + } + + private static List GetMemoryCardGameSelectionList(SAV3GCMemoryCard memCard) + { + var games = new List(); + if (memCard.HasCOLO) games.Add(new ComboItem(MsgGameColosseum, (int) GameVersion.COLO)); + if (memCard.HasXD) games.Add(new ComboItem(MsgGameXD, (int) GameVersion.XD)); + if (memCard.HasRSBOX) games.Add(new ComboItem(MsgGameRSBOX, (int) GameVersion.RSBOX)); + return games; + } + + private static bool CheckGCMemoryCard(SAV3GCMemoryCard memCard, string path) + { + var state = memCard.GetMemoryCardState(); + switch (state) + { + case GCMemoryCardState.NoPkmSaveGame: + WinFormsUtil.Error(MsgFileGameCubeNoGames, path); + return false; + + case GCMemoryCardState.DuplicateCOLO: + case GCMemoryCardState.DuplicateXD: + case GCMemoryCardState.DuplicateRSBOX: + WinFormsUtil.Error(MsgFileGameCubeDuplicate, path); + return false; + + case GCMemoryCardState.MultipleSaveGame: + var game = SelectMemoryCardSaveGame(memCard); + if (game == GameVersion.Invalid) //Cancel + return false; + memCard.SelectSaveGame(game); + break; + + case GCMemoryCardState.SaveGameCOLO: memCard.SelectSaveGame(GameVersion.COLO); break; + case GCMemoryCardState.SaveGameXD: memCard.SelectSaveGame(GameVersion.XD); break; + case GCMemoryCardState.SaveGameRSBOX: memCard.SelectSaveGame(GameVersion.RSBOX); break; + + default: + WinFormsUtil.Error(!SaveUtil.IsSizeValid(memCard.Data.Length) ? MsgFileGameCubeBad : MsgFileLoadSaveLoadFail, path); + return false; + } + return true; + } + + private static void StoreLegalSaveGameData(SaveFile sav) + { + if (sav is SAV3 sav3) + EReaderBerrySettings.LoadFrom(sav3); + } + + private bool OpenSAV(SaveFile sav, string path) + { + if (sav.Version == GameVersion.Invalid) + { + WinFormsUtil.Error(MsgFileLoadSaveLoadFail, path); + return true; + } + + sav.Metadata.SetExtraInfo(path); + if (!SanityCheckSAV(ref sav)) + return true; + + if (C_SAV.SAV.State.Edited && Settings.SlotWrite.ModifyUnset) + { + var prompt = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgProgramCloseUnsaved, MsgProgramSaveFileConfirm); + if (prompt != DialogResult.Yes) + return true; + } + + ClosePopups(); + + PKME_Tabs.Focus(); // flush any pending changes + StoreLegalSaveGameData(sav); + RecentTrainerCache.SetRecentTrainer(sav); + SpriteUtil.Initialize(sav); // refresh sprite generator + dragout.Size = new Size(SpriteUtil.Spriter.Width, SpriteUtil.Spriter.Height); + + // clean fields + Menu_ExportSAV.Enabled = sav.State.Exportable; + + // No changes made yet + Menu_Undo.Enabled = false; + Menu_Redo.Enabled = false; + + GameInfo.FilteredSources = new FilteredGameDataSource(sav, GameInfo.Sources, HaX); + ResetSAVPKMEditors(sav); + C_SAV.M.Reset(); + + Text = GetProgramTitle(sav); + TryBackupExportCheck(sav, path); + + Menu_ShowdownExportParty.Visible = sav.HasParty; + Menu_ShowdownExportCurrentBox.Visible = sav.HasBox; + + Settings.Startup.LoadSaveFile(path); + if (Settings.Sounds.PlaySoundSAVLoad) + SystemSounds.Asterisk.Play(); + return true; + } + + private void ResetSAVPKMEditors(SaveFile sav) + { + C_SAV.SetEditEnvironment(new SaveDataEditor(sav, PKME_Tabs)); + + var pk = sav.LoadTemplate(TemplatePath); + PKME_Tabs.CurrentPKM = pk; + + bool init = PKME_Tabs.IsInitialized; + if (!init) + { + PKME_Tabs.InitializeBinding(); + PKME_Tabs.SetPKMFormatMode(pk); + PKME_Tabs.ChangeLanguage(sav); + } + else + { + PKME_Tabs.SetPKMFormatMode(pk); + } + PKME_Tabs.PopulateFields(pk); + + // Initialize Overall Info + Menu_LoadBoxes.Enabled = Menu_DumpBoxes.Enabled = Menu_DumpBox.Enabled = Menu_Report.Enabled = C_SAV.SAV.HasBox; + + // Initialize Subviews + bool WindowTranslationRequired = false; + WindowTranslationRequired |= PKME_Tabs.ToggleInterface(sav, pk); + WindowTranslationRequired |= C_SAV.ToggleInterface(); + if (WindowTranslationRequired) // force update -- re-added controls may be untranslated + WinFormsUtil.TranslateInterface(this, CurrentLanguage); + + PKME_Tabs.PopulateFields(pk); + + sav.State.Edited = false; + foreach (var p in Plugins) + p.NotifySaveLoaded(); + } + + private static string GetProgramTitle() + { +#if DEBUG + var date = File.GetLastWriteTime(Assembly.GetEntryAssembly()!.Location); + string version = $"d-{date:yyyyMMdd}"; +#else var ver = CurrentProgramVersion; string version = $"{2000+ver.Major:00}{ver.Minor:00}{ver.Build:00}"; - #endif - return $"PKH{(HaX ? "a" : "e")}X ({version})"; - } +#endif + return $"PKH{(HaX ? "a" : "e")}X ({version})"; + } - private static string GetProgramTitle(SaveFile sav) + private static string GetProgramTitle(SaveFile sav) + { + string title = GetProgramTitle() + $" - {sav.GetType().Name}: "; + if (sav is ISaveFileRevision rev) + title = title.Insert(title.Length - 2, rev.SaveRevisionString); + var ver = GameInfo.GetVersionName(sav.Version); + if (Settings.Privacy.HideSAVDetails) + return title + $"[{ver}]"; + if (!sav.State.Exportable) // Blank save file + return title + $"{sav.Metadata.FileName} [{sav.OT} ({ver})]"; + return title + Path.GetFileNameWithoutExtension(Util.CleanFileName(sav.Metadata.BAKName)); // more descriptive + } + + private static bool TryBackupExportCheck(SaveFile sav, string path) + { + if (string.IsNullOrWhiteSpace(path) || !Settings.Backup.BAKEnabled) // not actual save + return false; + + // If backup folder exists, save a backup. + string backupName = Path.Combine(BackupPath, Util.CleanFileName(sav.Metadata.BAKName)); + if (sav.State.Exportable && Directory.Exists(BackupPath) && !File.Exists(backupName)) { - string title = GetProgramTitle() + $" - {sav.GetType().Name}: "; - if (sav is ISaveFileRevision rev) - title = title.Insert(title.Length - 2, rev.SaveRevisionString); - var ver = GameInfo.GetVersionName(sav.Version); - if (Settings.Privacy.HideSAVDetails) - return title + $"[{ver}]"; - if (!sav.State.Exportable) // Blank save file - return title + $"{sav.Metadata.FileName} [{sav.OT} ({ver})]"; - return title + Path.GetFileNameWithoutExtension(Util.CleanFileName(sav.Metadata.BAKName)); // more descriptive - } - - private static bool TryBackupExportCheck(SaveFile sav, string path) - { - if (string.IsNullOrWhiteSpace(path) || !Settings.Backup.BAKEnabled) // not actual save - return false; - - // If backup folder exists, save a backup. - string backupName = Path.Combine(BackupPath, Util.CleanFileName(sav.Metadata.BAKName)); - if (sav.State.Exportable && Directory.Exists(BackupPath) && !File.Exists(backupName)) + var src = sav.Metadata.FilePath; + if (src is { } x && File.Exists(x)) { - var src = sav.Metadata.FilePath; - if (src is { } x && File.Exists(x)) + try { - try - { - File.Copy(x, backupName); - } - catch (Exception ex) - { - WinFormsUtil.Error(MsgBackupUnable, ex); - return false; - } + File.Copy(x, backupName); + } + catch (Exception ex) + { + WinFormsUtil.Error(MsgBackupUnable, ex); + return false; } } - if (!FileUtil.IsFileLocked(path)) - return true; - - WinFormsUtil.Alert(MsgFileWriteProtected + Environment.NewLine + path, MsgFileWriteProtectedAdvice); - return false; } + if (!FileUtil.IsFileLocked(path)) + return true; - private static bool SanityCheckSAV(ref SaveFile sav) + WinFormsUtil.Alert(MsgFileWriteProtected + Environment.NewLine + path, MsgFileWriteProtectedAdvice); + return false; + } + + private static bool SanityCheckSAV(ref SaveFile sav) + { + ParseSettings.InitFromSaveFileData(sav); // physical GB, no longer used in logic + + if (sav.State.Exportable && sav is SAV3 s3) { - ParseSettings.InitFromSaveFileData(sav); // physical GB, no longer used in logic - - if (sav.State.Exportable && sav is SAV3 s3) + if (ModifierKeys == Keys.Control || s3.IsCorruptPokedexFF()) { - if (ModifierKeys == Keys.Control || s3.IsCorruptPokedexFF()) - { - var g = new[] { GameVersion.R, GameVersion.S, GameVersion.E, GameVersion.FR, GameVersion.LG }; - var games = g.Select(z => GameInfo.VersionDataSource.First(v => v.Value == (int)z)); - var msg = string.Format(MsgFileLoadVersionDetect, $"3 ({s3.Version})"); - using var dialog = new SAV_GameSelect(games, msg, MsgFileLoadSaveSelectVersion); - dialog.ShowDialog(); - if (dialog.Result is GameVersion.Invalid) - return false; + var g = new[] { GameVersion.R, GameVersion.S, GameVersion.E, GameVersion.FR, GameVersion.LG }; + var games = g.Select(z => GameInfo.VersionDataSource.First(v => v.Value == (int)z)); + var msg = string.Format(MsgFileLoadVersionDetect, $"3 ({s3.Version})"); + using var dialog = new SAV_GameSelect(games, msg, MsgFileLoadSaveSelectVersion); + dialog.ShowDialog(); + if (dialog.Result is GameVersion.Invalid) + return false; - var s = s3.ForceLoad(dialog.Result); - if (s is SAV3FRLG frlg) - { - bool result = frlg.ResetPersonal(dialog.Result); - if (!result) - return false; - } - var origin = sav.Metadata.FilePath; - if (origin is not null) - s.Metadata.SetExtraInfo(origin); - sav = s; - } - else if (s3 is SAV3FRLG frlg) // IndeterminateSubVersion + var s = s3.ForceLoad(dialog.Result); + if (s is SAV3FRLG frlg) { - string fr = GameInfo.GetVersionName(GameVersion.FR); - string lg = GameInfo.GetVersionName(GameVersion.LG); - string dual = "{1}/{2} " + MsgFileLoadVersionDetect; - var g = new[] { GameVersion.FR, GameVersion.LG }; - var games = g.Select(z => GameInfo.VersionDataSource.First(v => v.Value == (int)z)); - var msg = string.Format(dual, "3", fr, lg); - using var dialog = new SAV_GameSelect(games, msg, MsgFileLoadSaveSelectVersion); - dialog.ShowDialog(); bool result = frlg.ResetPersonal(dialog.Result); if (!result) return false; } + var origin = sav.Metadata.FilePath; + if (origin is not null) + s.Metadata.SetExtraInfo(origin); + sav = s; } - - return true; - } - - public static void SetCountrySubRegion(ComboBox CB, string type) - { - int index = CB.SelectedIndex; - string cl = GameInfo.CurrentLanguage; - CB.DataSource = Util.GetCountryRegionList(type, cl); - - if (index > 0 && index < CB.Items.Count) - CB.SelectedIndex = index; - } - - // Language Translation - private void ChangeMainLanguage(object sender, EventArgs e) - { - var index = CB_MainLanguage.SelectedIndex; - if ((uint)index < CB_MainLanguage.Items.Count) - CurrentLanguage = GameLanguage.Language2Char(index); - - // Set the culture (makes it easy to pass language to other forms) - var lang = CurrentLanguage; - Settings.Startup.Language = lang; - var ci = new CultureInfo(lang[..2]); - Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = ci; - - Menu_Options.DropDown.Close(); - - var sav = C_SAV.SAV; - LocalizeUtil.InitializeStrings(lang, sav, HaX); - WinFormsUtil.TranslateInterface(this, lang); // Translate the UI to language. - LocalizedDescriptionAttribute.Localizer = WinFormsTranslator.GetDictionary(lang); - - if (sav is not FakeSaveFile) + else if (s3 is SAV3FRLG frlg) // IndeterminateSubVersion { - var pk = PKME_Tabs.CurrentPKM.Clone(); - - PKME_Tabs.ChangeLanguage(sav, pk); - PKME_Tabs.PopulateFields(pk); // put data back in form - Text = GetProgramTitle(sav); - } - } - #endregion - - #region //// PKX WINDOW FUNCTIONS //// - private bool QR6Notified; - - private void ClickQR(object sender, EventArgs e) - { - if (ModifierKeys == Keys.Alt) - { - string url = Clipboard.GetText(); - if (!string.IsNullOrWhiteSpace(url)) - { - if (url.StartsWith("http") && !url.Contains('\n')) // qr payload - ImportQRToTabs(url); - else - ClickShowdownImportPKM(sender, e); - return; - } - } - ExportQRFromTabs(); - } - - private void ImportQRToTabs(string url) - { - var msg = QRDecode.GetQRData(url, out var input); - if (msg != 0) - { - WinFormsUtil.Alert(msg.ConvertMsg()); - return; - } - - if (input.Length == 0) - return; - - var sav = C_SAV.SAV; - if (FileUtil.TryGetPKM(input, out var pk, sav.Generation.ToString(), sav)) - { - OpenPKM(pk); - return; - } - if (FileUtil.TryGetMysteryGift(input, out var mg, url)) - { - OpenMysteryGift(mg, url); - return; - } - - WinFormsUtil.Alert(MsgQRDecodeFail, string.Format(MsgQRDecodeSize, input.Length)); - } - - private void ExportQRFromTabs() - { - if (!PKME_Tabs.EditsComplete) - return; - - PKM pk = PreparePKM(); - if (pk.Format == 6 && !QR6Notified) // hint that the user should not be using QR6 injection - { - WinFormsUtil.Alert(MsgQRDeprecated, MsgQRAlternative); - QR6Notified = true; - } - - var qr = QREncode.GenerateQRCode(pk); - - var sprite = dragout.Image; - var la = new LegalityAnalysis(pk, C_SAV.SAV.Personal); - if (la.Parsed && pk.Species != 0) - { - var img = SpriteUtil.GetLegalIndicator(la.Valid); - sprite = ImageUtil.LayerImage(sprite, img, sprite.Width - img.Width, 0); - } - - string[] r = pk.GetQRLines(); - string refer = GetProgramTitle(); - using var form = new QR(qr, sprite, pk, r[0], r[1], r[2], $"{refer} ({pk.GetType().Name})"); - form.ShowDialog(); - } - - private void ClickLegality(object sender, EventArgs e) - { - if (!PKME_Tabs.EditsComplete) - { SystemSounds.Hand.Play(); return; } - - var pk = PreparePKM(); - - if (pk.Species == 0 || !pk.ChecksumValid) - { SystemSounds.Hand.Play(); return; } - - var la = new LegalityAnalysis(pk, C_SAV.SAV.Personal); - PKME_Tabs.UpdateLegality(la); - DisplayLegalityReport(la); - } - - private static void DisplayLegalityReport(LegalityAnalysis la) - { - bool verbose = ModifierKeys == Keys.Control; - var report = la.Report(verbose); - if (verbose) - { - var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, report, MsgClipboardLegalityExport); - if (dr != DialogResult.Yes) - return; -#if DEBUG - var enc = SummaryPreviewer.GetTextLines(la.EncounterOriginal); - report += Environment.NewLine + Environment.NewLine + string.Join(Environment.NewLine, enc); -#endif - WinFormsUtil.SetClipboardText(report); - } - else if (Settings.Display.IgnoreLegalPopup && la.Valid) - { - if (Settings.Sounds.PlaySoundLegalityCheck) - SystemSounds.Asterisk.Play(); - } - else - { - WinFormsUtil.Alert(Settings.Sounds.PlaySoundLegalityCheck, report); + string fr = GameInfo.GetVersionName(GameVersion.FR); + string lg = GameInfo.GetVersionName(GameVersion.LG); + string dual = "{1}/{2} " + MsgFileLoadVersionDetect; + var g = new[] { GameVersion.FR, GameVersion.LG }; + var games = g.Select(z => GameInfo.VersionDataSource.First(v => v.Value == (int)z)); + var msg = string.Format(dual, "3", fr, lg); + using var dialog = new SAV_GameSelect(games, msg, MsgFileLoadSaveSelectVersion); + dialog.ShowDialog(); + bool result = frlg.ResetPersonal(dialog.Result); + if (!result) + return false; } } - private void ClickClone(object sender, EventArgs e) - { - if (!PKME_Tabs.EditsComplete) - return; // don't copy garbage to the box - PKM pk = PKME_Tabs.PreparePKM(); - C_SAV.SetClonesToBox(pk); - } - - private void GetPreview(PictureBox pb, PKM? pk = null) - { - pk ??= PreparePKM(false); // don't perform control loss click - - dragout.ContextMenuStrip.Enabled = pk.Species != 0 || HaX; // Species - - pb.Image = pk.Sprite(C_SAV.SAV, -1, -1, flagIllegal: false); - if (pb.BackColor == SlotUtil.BadDataColor) - pb.BackColor = SlotUtil.GoodDataColor; - } - - private void PKME_Tabs_UpdatePreviewSprite(object sender, EventArgs e) => GetPreview(dragout); - - private void PKME_Tabs_LegalityChanged(object sender, EventArgs e) - { - if (HaX) - { - PB_Legal.Visible = false; - return; - } - - PB_Legal.Visible = true; - bool isValid = sender as bool? != false; - PB_Legal.Image = SpriteUtil.GetLegalIndicator(isValid); - toolTip.SetToolTip(PB_Legal, isValid ? "Valid" : "Invalid: Click for more info"); - } - - private void PKME_Tabs_RequestShowdownExport(object sender, EventArgs e) => ClickShowdownExportPKM(sender, e); - private void PKME_Tabs_RequestShowdownImport(object sender, EventArgs e) => ClickShowdownImportPKM(sender, e); - private SaveFile PKME_Tabs_SaveFileRequested(object sender, EventArgs e) => C_SAV.SAV; - private PKM PreparePKM(bool click = true) => PKME_Tabs.PreparePKM(click); - - // Drag & Drop Events - private static void Main_DragEnter(object? sender, DragEventArgs? e) - { - if (e is null) - return; - if (e.AllowedEffect == (DragDropEffects.Copy | DragDropEffects.Link)) // external file - e.Effect = DragDropEffects.Copy; - else if (e.Data != null) // within - e.Effect = DragDropEffects.Copy; - } - - private void Main_DragDrop(object? sender, DragEventArgs? e) - { - if (e?.Data?.GetData(DataFormats.FileDrop) is not string[] { Length: not 0 } files) - return; - OpenQuick(files[0]); - e.Effect = DragDropEffects.Copy; - } - - private async void Dragout_MouseDown(object sender, MouseEventArgs e) - { - if (e.Button != MouseButtons.Left) - return; - - if (ModifierKeys is Keys.Alt or Keys.Shift) - { - ClickQR(sender, e); - return; - } - - if (!PKME_Tabs.EditsComplete) - return; - - // Gather data - var pk = PreparePKM(); - var encrypt = ModifierKeys == Keys.Control; - var data = encrypt ? pk.EncryptedPartyData : pk.DecryptedPartyData; - - // Create Temp File to Drag - var newfile = FileUtil.GetPKMTempFileName(pk, encrypt); - try - { - File.WriteAllBytes(newfile, data); - - var pb = (PictureBox)sender; - if (pb.Image is Bitmap img) - C_SAV.M.Drag.Info.Cursor = Cursor = new Cursor(img.GetHicon()); - - DoDragDrop(new DataObject(DataFormats.FileDrop, new[] { newfile }), DragDropEffects.Copy); - } - // Tons of things can happen with drag & drop; don't try to handle things, just indicate failure. - catch (Exception x) - { WinFormsUtil.Error("Drag && Drop Error", x); } - finally - { - C_SAV.M.Drag.ResetCursor(this); - await DeleteAsync(newfile, 20_000).ConfigureAwait(false); - } - } - - private static async Task DeleteAsync(string path, int delay) - { - await Task.Delay(delay).ConfigureAwait(true); - if (!File.Exists(path)) - return; - - try { File.Delete(path); } - catch (Exception ex) { Debug.WriteLine(ex.Message); } - } - - private void Dragout_DragOver(object sender, DragEventArgs e) => e.Effect = DragDropEffects.Copy; - - private void DragoutEnter(object sender, EventArgs e) - { - dragout.BackgroundImage = PKME_Tabs.Entity.Species > 0 ? SpriteUtil.Spriter.Set : SpriteUtil.Spriter.Delete; - Cursor = Cursors.Hand; - } - - private void DragoutLeave(object sender, EventArgs e) - { - dragout.BackgroundImage = SpriteUtil.Spriter.Transparent; - if (Cursor == Cursors.Hand) - Cursor = Cursors.Default; - } - - private void DragoutDrop(object? sender, DragEventArgs? e) - { - if (e?.Data?.GetData(DataFormats.FileDrop) is not string[] { Length: not 0 } files) - return; - OpenQuick(files[0]); - e.Effect = DragDropEffects.Copy; - - Cursor = DefaultCursor; - } - - private void Main_FormClosing(object sender, FormClosingEventArgs e) - { - if (C_SAV.SAV.State.Edited || PKME_Tabs.PKMIsUnsaved) - { - var prompt = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgProgramCloseUnsaved, MsgProgramCloseConfirm); - if (prompt != DialogResult.Yes) - { - e.Cancel = true; - return; - } - } - - PKHeXSettings.SaveSettings(ConfigPath, Settings); - } - - #endregion - - #region //// SAVE FILE FUNCTIONS //// - - private void ClickExportSAV(object sender, EventArgs e) - { - if (!Menu_ExportSAV.Enabled) - return; // hot-keys can't cheat the system! - - C_SAV.ExportSaveFile(); - Text = GetProgramTitle(C_SAV.SAV); - } - - private void ClickSaveFileName(object sender, EventArgs e) - { - try - { - if (!SaveFinder.TryDetectSaveFile(out var sav)) - return; - - var path = sav.Metadata.FilePath!; - var time = new FileInfo(path).CreationTime; - var timeStamp = time.ToString(CultureInfo.CurrentCulture); - if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgFileLoadSaveDetectReload, path, timeStamp) == DialogResult.Yes) - LoadFile(sav, path); // load save - } - catch (Exception ex) - { - WinFormsUtil.Error(ex.Message); // `path` contains the error message - } - } - - private static void PromptBackup() - { - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, string.Format(MsgBackupCreateLocation, BackupPath), MsgBackupCreateQuestion)) - return; - - try - { - Directory.CreateDirectory(BackupPath); - WinFormsUtil.Alert(MsgBackupSuccess, string.Format(MsgBackupDelete, BackupPath)); - } - catch (Exception ex) - // Maybe they put their exe in a folder that we can't create files/folders to. - { WinFormsUtil.Error($"{MsgBackupUnable} @ {BackupPath}", ex); } - } - - private void ClickUndo(object sender, EventArgs e) => C_SAV.ClickUndo(); - private void ClickRedo(object sender, EventArgs e) => C_SAV.ClickRedo(); - #endregion + return true; } + + public static void SetCountrySubRegion(ComboBox CB, string type) + { + int index = CB.SelectedIndex; + string cl = GameInfo.CurrentLanguage; + CB.DataSource = Util.GetCountryRegionList(type, cl); + + if (index > 0 && index < CB.Items.Count) + CB.SelectedIndex = index; + } + + // Language Translation + private void ChangeMainLanguage(object sender, EventArgs e) + { + var index = CB_MainLanguage.SelectedIndex; + if ((uint)index < CB_MainLanguage.Items.Count) + CurrentLanguage = GameLanguage.Language2Char(index); + + // Set the culture (makes it easy to pass language to other forms) + var lang = CurrentLanguage; + Settings.Startup.Language = lang; + var ci = new CultureInfo(lang[..2]); + Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = ci; + + Menu_Options.DropDown.Close(); + + var sav = C_SAV.SAV; + LocalizeUtil.InitializeStrings(lang, sav, HaX); + WinFormsUtil.TranslateInterface(this, lang); // Translate the UI to language. + LocalizedDescriptionAttribute.Localizer = WinFormsTranslator.GetDictionary(lang); + + if (sav is not FakeSaveFile) + { + var pk = PKME_Tabs.CurrentPKM.Clone(); + + PKME_Tabs.ChangeLanguage(sav); + PKME_Tabs.PopulateFields(pk); // put data back in form + Text = GetProgramTitle(sav); + } + } + #endregion + + #region //// PKX WINDOW FUNCTIONS //// + private bool QR6Notified; + + private void ClickQR(object sender, EventArgs e) + { + if (ModifierKeys == Keys.Alt) + { + string url = Clipboard.GetText(); + if (!string.IsNullOrWhiteSpace(url)) + { + if (url.StartsWith("http") && !url.Contains('\n')) // qr payload + ImportQRToTabs(url); + else + ClickShowdownImportPKM(sender, e); + return; + } + } + ExportQRFromTabs(); + } + + private void ImportQRToTabs(string url) + { + var msg = QRDecode.GetQRData(url, out var input); + if (msg != 0) + { + WinFormsUtil.Alert(msg.ConvertMsg()); + return; + } + + if (input.Length == 0) + return; + + var sav = C_SAV.SAV; + if (FileUtil.TryGetPKM(input, out var pk, sav.Generation.ToString(), sav)) + { + OpenPKM(pk); + return; + } + if (FileUtil.TryGetMysteryGift(input, out var mg, url)) + { + OpenMysteryGift(mg, url); + return; + } + + WinFormsUtil.Alert(MsgQRDecodeFail, string.Format(MsgQRDecodeSize, input.Length)); + } + + private void ExportQRFromTabs() + { + if (!PKME_Tabs.EditsComplete) + return; + + PKM pk = PreparePKM(); + if (pk.Format == 6 && !QR6Notified) // hint that the user should not be using QR6 injection + { + WinFormsUtil.Alert(MsgQRDeprecated, MsgQRAlternative); + QR6Notified = true; + } + + var qr = QREncode.GenerateQRCode(pk); + + var sprite = dragout.Image; + var la = new LegalityAnalysis(pk, C_SAV.SAV.Personal); + if (la.Parsed && pk.Species != 0) + { + var img = SpriteUtil.GetLegalIndicator(la.Valid); + sprite = ImageUtil.LayerImage(sprite, img, sprite.Width - img.Width, 0); + } + + string[] r = pk.GetQRLines(); + string refer = GetProgramTitle(); + using var form = new QR(qr, sprite, pk, r[0], r[1], r[2], $"{refer} ({pk.GetType().Name})"); + form.ShowDialog(); + } + + private void ClickLegality(object sender, EventArgs e) + { + if (!PKME_Tabs.EditsComplete) + { SystemSounds.Hand.Play(); return; } + + var pk = PreparePKM(); + + if (pk.Species == 0 || !pk.ChecksumValid) + { SystemSounds.Hand.Play(); return; } + + var la = new LegalityAnalysis(pk, C_SAV.SAV.Personal); + PKME_Tabs.UpdateLegality(la); + DisplayLegalityReport(la); + } + + private static void DisplayLegalityReport(LegalityAnalysis la) + { + bool verbose = ModifierKeys == Keys.Control; + var report = la.Report(verbose); + if (verbose) + { + var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, report, MsgClipboardLegalityExport); + if (dr != DialogResult.Yes) + return; +#if DEBUG + var enc = SummaryPreviewer.GetTextLines(la.EncounterOriginal); + report += Environment.NewLine + Environment.NewLine + string.Join(Environment.NewLine, enc); +#endif + WinFormsUtil.SetClipboardText(report); + } + else if (Settings.Display.IgnoreLegalPopup && la.Valid) + { + if (Settings.Sounds.PlaySoundLegalityCheck) + SystemSounds.Asterisk.Play(); + } + else + { + WinFormsUtil.Alert(Settings.Sounds.PlaySoundLegalityCheck, report); + } + } + + private void ClickClone(object sender, EventArgs e) + { + if (!PKME_Tabs.EditsComplete) + return; // don't copy garbage to the box + PKM pk = PKME_Tabs.PreparePKM(); + C_SAV.SetClonesToBox(pk); + } + + private void GetPreview(PictureBox pb, PKM? pk = null) + { + pk ??= PreparePKM(false); // don't perform control loss click + + dragout.ContextMenuStrip.Enabled = pk.Species != 0 || HaX; // Species + + pb.Image = pk.Sprite(C_SAV.SAV, -1, -1, flagIllegal: false); + if (pb.BackColor == SlotUtil.BadDataColor) + pb.BackColor = SlotUtil.GoodDataColor; + } + + private void PKME_Tabs_UpdatePreviewSprite(object sender, EventArgs e) => GetPreview(dragout); + + private void PKME_Tabs_LegalityChanged(object sender, EventArgs e) + { + if (HaX) + { + PB_Legal.Visible = false; + return; + } + + PB_Legal.Visible = true; + bool isValid = (sender as bool?) != false; + PB_Legal.Image = SpriteUtil.GetLegalIndicator(isValid); + toolTip.SetToolTip(PB_Legal, isValid ? "Valid" : "Invalid: Click for more info"); + } + + private void PKME_Tabs_RequestShowdownExport(object sender, EventArgs e) => ClickShowdownExportPKM(sender, e); + private void PKME_Tabs_RequestShowdownImport(object sender, EventArgs e) => ClickShowdownImportPKM(sender, e); + private SaveFile PKME_Tabs_SaveFileRequested(object sender, EventArgs e) => C_SAV.SAV; + private PKM PreparePKM(bool click = true) => PKME_Tabs.PreparePKM(click); + + // Drag & Drop Events + private static void Main_DragEnter(object? sender, DragEventArgs? e) + { + if (e is null) + return; + if (e.AllowedEffect == (DragDropEffects.Copy | DragDropEffects.Link)) // external file + e.Effect = DragDropEffects.Copy; + else if (e.Data != null) // within + e.Effect = DragDropEffects.Copy; + } + + private void Main_DragDrop(object? sender, DragEventArgs? e) + { + if (e?.Data?.GetData(DataFormats.FileDrop) is not string[] { Length: not 0 } files) + return; + OpenQuick(files[0]); + e.Effect = DragDropEffects.Copy; + } + + private async void Dragout_MouseDown(object sender, MouseEventArgs e) + { + if (e.Button != MouseButtons.Left) + return; + + if (ModifierKeys is Keys.Alt or Keys.Shift) + { + ClickQR(sender, e); + return; + } + + if (!PKME_Tabs.EditsComplete) + return; + + // Gather data + var pk = PreparePKM(); + var encrypt = ModifierKeys == Keys.Control; + var data = encrypt ? pk.EncryptedPartyData : pk.DecryptedPartyData; + + // Create Temp File to Drag + var newfile = FileUtil.GetPKMTempFileName(pk, encrypt); + try + { + File.WriteAllBytes(newfile, data); + + var pb = (PictureBox)sender; + if (pb.Image is Bitmap img) + C_SAV.M.Drag.Info.Cursor = Cursor = new Cursor(img.GetHicon()); + + DoDragDrop(new DataObject(DataFormats.FileDrop, new[] { newfile }), DragDropEffects.Copy); + } + // Tons of things can happen with drag & drop; don't try to handle things, just indicate failure. + catch (Exception x) + { WinFormsUtil.Error("Drag && Drop Error", x); } + finally + { + C_SAV.M.Drag.ResetCursor(this); + await DeleteAsync(newfile, 20_000).ConfigureAwait(false); + } + } + + private static async Task DeleteAsync(string path, int delay) + { + await Task.Delay(delay).ConfigureAwait(true); + if (!File.Exists(path)) + return; + + try { File.Delete(path); } + catch (Exception ex) { Debug.WriteLine(ex.Message); } + } + + private void Dragout_DragOver(object sender, DragEventArgs e) => e.Effect = DragDropEffects.Copy; + + private void DragoutEnter(object sender, EventArgs e) + { + dragout.BackgroundImage = PKME_Tabs.Entity.Species > 0 ? SpriteUtil.Spriter.Set : SpriteUtil.Spriter.Delete; + Cursor = Cursors.Hand; + } + + private void DragoutLeave(object sender, EventArgs e) + { + dragout.BackgroundImage = SpriteUtil.Spriter.Transparent; + if (Cursor == Cursors.Hand) + Cursor = Cursors.Default; + } + + private void DragoutDrop(object? sender, DragEventArgs? e) + { + if (e?.Data?.GetData(DataFormats.FileDrop) is not string[] { Length: not 0 } files) + return; + OpenQuick(files[0]); + e.Effect = DragDropEffects.Copy; + + Cursor = DefaultCursor; + } + + private void Main_FormClosing(object sender, FormClosingEventArgs e) + { + if (C_SAV.SAV.State.Edited || PKME_Tabs.PKMIsUnsaved) + { + var prompt = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgProgramCloseUnsaved, MsgProgramCloseConfirm); + if (prompt != DialogResult.Yes) + { + e.Cancel = true; + return; + } + } + + PKHeXSettings.SaveSettings(ConfigPath, Settings); + } + + #endregion + + #region //// SAVE FILE FUNCTIONS //// + + private void ClickExportSAV(object sender, EventArgs e) + { + if (!Menu_ExportSAV.Enabled) + return; // hot-keys can't cheat the system! + + C_SAV.ExportSaveFile(); + Text = GetProgramTitle(C_SAV.SAV); + } + + private void ClickSaveFileName(object sender, EventArgs e) + { + try + { + if (!SaveFinder.TryDetectSaveFile(out var sav)) + return; + + var path = sav.Metadata.FilePath!; + var time = new FileInfo(path).CreationTime; + var timeStamp = time.ToString(CultureInfo.CurrentCulture); + if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgFileLoadSaveDetectReload, path, timeStamp) == DialogResult.Yes) + LoadFile(sav, path); // load save + } + catch (Exception ex) + { + WinFormsUtil.Error(ex.Message); // `path` contains the error message + } + } + + private static void PromptBackup() + { + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, string.Format(MsgBackupCreateLocation, BackupPath), MsgBackupCreateQuestion)) + return; + + try + { + Directory.CreateDirectory(BackupPath); + WinFormsUtil.Alert(MsgBackupSuccess, string.Format(MsgBackupDelete, BackupPath)); + } + catch (Exception ex) + // Maybe they put their exe in a folder that we can't create files/folders to. + { WinFormsUtil.Error($"{MsgBackupUnable} @ {BackupPath}", ex); } + } + + private void ClickUndo(object sender, EventArgs e) => C_SAV.ClickUndo(); + private void ClickRedo(object sender, EventArgs e) => C_SAV.ClickRedo(); + #endregion } diff --git a/PKHeX.WinForms/Misc/About.Designer.cs b/PKHeX.WinForms/Misc/About.Designer.cs index 6671093a7..7c19a8fd5 100644 --- a/PKHeX.WinForms/Misc/About.Designer.cs +++ b/PKHeX.WinForms/Misc/About.Designer.cs @@ -133,4 +133,4 @@ private void InitializeComponent() private System.Windows.Forms.TabPage Tab_Changelog; private System.Windows.Forms.RichTextBox RTB_Changelog; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Misc/About.cs b/PKHeX.WinForms/Misc/About.cs index 468449818..2959d1ecc 100644 --- a/PKHeX.WinForms/Misc/About.cs +++ b/PKHeX.WinForms/Misc/About.cs @@ -1,22 +1,21 @@ -using System.Windows.Forms; +using System.Windows.Forms; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class About : Form { - public partial class About : Form + public About(AboutPage index = AboutPage.Changelog) { - public About(AboutPage index = AboutPage.Changelog) - { - InitializeComponent(); - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - RTB_Changelog.Text = Properties.Resources.changelog; - RTB_Shortcuts.Text = Properties.Resources.shortcuts; - TC_About.SelectedIndex = (int)index; - } - } - - public enum AboutPage - { - Shortcuts, - Changelog, + InitializeComponent(); + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + RTB_Changelog.Text = Properties.Resources.changelog; + RTB_Shortcuts.Text = Properties.Resources.shortcuts; + TC_About.SelectedIndex = (int)index; } } + +public enum AboutPage +{ + Shortcuts, + Changelog, +} diff --git a/PKHeX.WinForms/Misc/ErrorWindow.Designer.cs b/PKHeX.WinForms/Misc/ErrorWindow.Designer.cs index a9da49dc1..1c8c3e3a2 100644 --- a/PKHeX.WinForms/Misc/ErrorWindow.Designer.cs +++ b/PKHeX.WinForms/Misc/ErrorWindow.Designer.cs @@ -132,4 +132,4 @@ private void InitializeComponent() private System.Windows.Forms.Button B_Abort; private System.Windows.Forms.Button B_Continue; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Misc/ErrorWindow.cs b/PKHeX.WinForms/Misc/ErrorWindow.cs index da6a42dfd..ed974d69c 100644 --- a/PKHeX.WinForms/Misc/ErrorWindow.cs +++ b/PKHeX.WinForms/Misc/ErrorWindow.cs @@ -2,112 +2,111 @@ using System.Text; using System.Windows.Forms; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public sealed partial class ErrorWindow : Form { - public sealed partial class ErrorWindow : Form + public static DialogResult ShowErrorDialog(string friendlyMessage, Exception ex, bool allowContinue) { - public static DialogResult ShowErrorDialog(string friendlyMessage, Exception ex, bool allowContinue) + var lang = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; + using var dialog = new ErrorWindow(lang) { - var lang = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; - using var dialog = new ErrorWindow(lang) - { - ShowContinue = allowContinue, - Message = friendlyMessage, - Error = ex, - }; - var dialogResult = dialog.ShowDialog(); - if (dialogResult == DialogResult.Abort) - Environment.Exit(1); - return dialogResult; - } + ShowContinue = allowContinue, + Message = friendlyMessage, + Error = ex, + }; + var dialogResult = dialog.ShowDialog(); + if (dialogResult == DialogResult.Abort) + Environment.Exit(1); + return dialogResult; + } - private ErrorWindow() + private ErrorWindow() + { + InitializeComponent(); + } + + private ErrorWindow(string lang) : this() + { + WinFormsUtil.TranslateInterface(this, lang); + } + + /// + /// Gets or sets whether or not the "Continue" button is visible. + /// + /// For UI exceptions, continuing could be safe. + /// For application exceptions, continuing is not possible, so the button should not be shown. + private bool ShowContinue + { + set => B_Continue.Visible = value; + } + + /// + /// Friendly, context-specific method shown to the user. + /// + /// This property is intended to be a user-friendly context-specific message about what went wrong. + /// For example: "An error occurred while attempting to automatically load the save file." + private string Message + { + get => L_Message.Text; + set => L_Message.Text = value; + } + + private Exception? _error; + + public Exception Error + { + get => _error ?? throw new ArgumentNullException(nameof(_error)); + set { - InitializeComponent(); - } - - private ErrorWindow(string lang) : this() - { - WinFormsUtil.TranslateInterface(this, lang); - } - - /// - /// Gets or sets whether or not the "Continue" button is visible. - /// - /// For UI exceptions, continuing could be safe. - /// For application exceptions, continuing is not possible, so the button should not be shown. - private bool ShowContinue - { - set => B_Continue.Visible = value; - } - - /// - /// Friendly, context-specific method shown to the user. - /// - /// This property is intended to be a user-friendly context-specific message about what went wrong. - /// For example: "An error occurred while attempting to automatically load the save file." - private string Message - { - get => L_Message.Text; - set => L_Message.Text = value; - } - - private Exception? _error; - - public Exception Error - { - get => _error ?? throw new ArgumentNullException(nameof(_error)); - set - { - _error = value; - UpdateExceptionDetailsMessage(); - } - } - - private void UpdateExceptionDetailsMessage() - { - var details = new StringBuilder(); - details.AppendLine("Exception Details:"); - details.AppendLine(Error.ToString()); - details.AppendLine(); - - details.AppendLine("Loaded Assemblies:"); - details.AppendLine("--------------------"); - try - { - foreach (var item in AppDomain.CurrentDomain.GetAssemblies()) - { - details.AppendLine(item.FullName); - details.AppendLine(item.Location); - details.AppendLine(); - } - } - catch (Exception ex) - { - details.AppendLine("An error occurred while listing the Loaded Assemblies:"); - details.AppendLine(ex.ToString()); - } - details.AppendLine("--------------------"); - - // Include message in case it contains important information, like a file path. - details.AppendLine("User Message:"); - details.AppendLine(Message); - - T_ExceptionDetails.Text = details.ToString(); - } - - private void ClickCopyException(object sender, EventArgs e) => WinFormsUtil.SetClipboardText(T_ExceptionDetails.Text); - - private void ClickContinue(object sender, EventArgs e) - { - DialogResult = DialogResult.OK; - Close(); - } - - private void ClickAbort(object sender, EventArgs e) - { - DialogResult = DialogResult.Abort; - Close(); + _error = value; + UpdateExceptionDetailsMessage(); } } + + private void UpdateExceptionDetailsMessage() + { + var details = new StringBuilder(); + details.AppendLine("Exception Details:"); + details.AppendLine(Error.ToString()); + details.AppendLine(); + + details.AppendLine("Loaded Assemblies:"); + details.AppendLine("--------------------"); + try + { + foreach (var item in AppDomain.CurrentDomain.GetAssemblies()) + { + details.AppendLine(item.FullName); + details.AppendLine(item.Location); + details.AppendLine(); + } + } + catch (Exception ex) + { + details.AppendLine("An error occurred while listing the Loaded Assemblies:"); + details.AppendLine(ex.ToString()); + } + details.AppendLine("--------------------"); + + // Include message in case it contains important information, like a file path. + details.AppendLine("User Message:"); + details.AppendLine(Message); + + T_ExceptionDetails.Text = details.ToString(); + } + + private void ClickCopyException(object sender, EventArgs e) => WinFormsUtil.SetClipboardText(T_ExceptionDetails.Text); + + private void ClickContinue(object sender, EventArgs e) + { + DialogResult = DialogResult.OK; + Close(); + } + + private void ClickAbort(object sender, EventArgs e) + { + DialogResult = DialogResult.Abort; + Close(); + } } diff --git a/PKHeX.WinForms/Misc/QR.Designer.cs b/PKHeX.WinForms/Misc/QR.Designer.cs index 46bb4b621..c75883277 100644 --- a/PKHeX.WinForms/Misc/QR.Designer.cs +++ b/PKHeX.WinForms/Misc/QR.Designer.cs @@ -232,4 +232,4 @@ private void InitializeComponent() private Button B_Refresh; private SplitContainer splitContainer1; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Misc/QR.cs b/PKHeX.WinForms/Misc/QR.cs index bf653d1f5..5e950481d 100644 --- a/PKHeX.WinForms/Misc/QR.cs +++ b/PKHeX.WinForms/Misc/QR.cs @@ -5,87 +5,86 @@ using PKHeX.Drawing.Misc; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class QR : Form { - public partial class QR : Form + private readonly PKM? Entity; + private readonly Image icon; + private Image qr; + + private readonly string[] Lines; + private string extraText = string.Empty; + + public QR(Image qr, Image icon, params string[] lines) { - private readonly PKM? pkm; - private readonly Image icon; - private Image qr; + InitializeComponent(); + this.qr = qr; + this.icon = icon; + Lines = lines; + splitContainer1.Panel1Collapsed = true; + RefreshImage(); + ResizeWindow(); + } - private readonly string[] Lines; - private string extraText = string.Empty; + public QR(Image qr, Image icon, PKM pk, params string[] lines) + { + InitializeComponent(); + this.qr = qr; + this.icon = icon; + Lines = lines; - public QR(Image qr, Image icon, params string[] lines) - { - InitializeComponent(); - this.qr = qr; - this.icon = icon; - Lines = lines; + Entity = pk; + // Layer on Text + if (pk is PK7 pk7) + this.qr = ReloadQRData(pk7); + else splitContainer1.Panel1Collapsed = true; - RefreshImage(); - ResizeWindow(); - } - public QR(Image qr, Image icon, PKM pk, params string[] lines) - { - InitializeComponent(); - this.qr = qr; - this.icon = icon; - Lines = lines; + RefreshImage(); + ResizeWindow(); + splitContainer1.SplitterDistance = 34; + } - pkm = pk; - // Layer on Text - if (pkm is PK7 pk7) - this.qr = ReloadQRData(pk7); - else - splitContainer1.Panel1Collapsed = true; + private void ResizeWindow() + { + var img = PB_QR.Image; + splitContainer1.Height = splitContainer1.Panel1.Height + img.Height; + splitContainer1.Width = img.Width; + } - RefreshImage(); - ResizeWindow(); - splitContainer1.SplitterDistance = 34; - } + private Image ReloadQRData(PK7 pk7) + { + var box = (int)NUD_Box.Value - 1; + var slot = (int)NUD_Slot.Value - 1; + var copies = (int)NUD_Copies.Value; + extraText = $" (Box {box + 1}, Slot {slot + 1}, {copies} cop{(copies > 1 ? "ies" : "y")})"; + return QREncode.GenerateQRCode7(pk7, box, slot, copies); + } - private void ResizeWindow() - { - var img = PB_QR.Image; - splitContainer1.Height = splitContainer1.Panel1.Height + img.Height; - splitContainer1.Width = img.Width; - } + private void RefreshImage() + { + SuspendLayout(); + ResumeLayout(); + Font font = !Main.Unicode ? Font : FontUtil.GetPKXFont(8.25f); + var img = QRImageUtil.GetQRImageExtended(font, qr, icon, Math.Max(qr.Width, 370), qr.Height + 50, Lines, extraText); + PB_QR.Image = img; + } - private Image ReloadQRData(PK7 pk7) - { - var box = (int)NUD_Box.Value - 1; - var slot = (int)NUD_Slot.Value - 1; - var copies = (int)NUD_Copies.Value; - extraText = $" (Box {box + 1}, Slot {slot + 1}, {copies} cop{(copies > 1 ? "ies" : "y")})"; - return QREncode.GenerateQRCode7(pk7, box, slot, copies); - } + private void PB_QR_Click(object sender, EventArgs e) + { + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgQRClipboardImage)) + return; + try { Clipboard.SetImage(PB_QR.Image); } + // Clipboard can be locked periodically, just notify on failure. + catch { WinFormsUtil.Alert(MsgQRClipboardFail); } + } - private void RefreshImage() - { - SuspendLayout(); - ResumeLayout(); - Font font = !Main.Unicode ? Font : FontUtil.GetPKXFont(8.25f); - var img = QRImageUtil.GetQRImageExtended(font, qr, icon, Math.Max(qr.Width, 370), qr.Height + 50, Lines, extraText); - PB_QR.Image = img; - } - - private void PB_QR_Click(object sender, EventArgs e) - { - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgQRClipboardImage)) - return; - try { Clipboard.SetImage(PB_QR.Image); } - // Clipboard can be locked periodically, just notify on failure. - catch { WinFormsUtil.Alert(MsgQRClipboardFail); } - } - - private void UpdateBoxSlotCopies(object sender, EventArgs e) - { - if (pkm is not PK7 pk7) - throw new ArgumentException("Can't update QR7 if pkm isn't a PK7!"); - qr = ReloadQRData(pk7); - RefreshImage(); - } + private void UpdateBoxSlotCopies(object sender, EventArgs e) + { + if (Entity is not PK7 pk7) + throw new ArgumentException("Can't update QR7 if pk isn't a PK7!"); + qr = ReloadQRData(pk7); + RefreshImage(); } } diff --git a/PKHeX.WinForms/Misc/SplashScreen.Designer.cs b/PKHeX.WinForms/Misc/SplashScreen.Designer.cs index 16f0f107c..5bfbbd5bf 100644 --- a/PKHeX.WinForms/Misc/SplashScreen.Designer.cs +++ b/PKHeX.WinForms/Misc/SplashScreen.Designer.cs @@ -88,4 +88,4 @@ private void InitializeComponent() private System.Windows.Forms.PictureBox PB_Icon; public System.Windows.Forms.Label L_Status; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Properties/PKHeXSettings.cs b/PKHeX.WinForms/Properties/PKHeXSettings.cs index dd265dc85..9b94dd448 100644 --- a/PKHeX.WinForms/Properties/PKHeXSettings.cs +++ b/PKHeX.WinForms/Properties/PKHeXSettings.cs @@ -7,399 +7,398 @@ using PKHeX.Core; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +[Serializable] +public sealed class PKHeXSettings { - [Serializable] - public sealed class PKHeXSettings + public StartupSettings Startup { get; set; } = new(); + public BackupSettings Backup { get; set; } = new(); + + // General + public LegalitySettings Legality { get; set; } = new(); + public SetImportSettings Import { get; set; } = new(); + public SlotWriteSettings SlotWrite { get; set; } = new(); + public PrivacySettings Privacy { get; set; } = new(); + + // UI Tweaks + public DisplaySettings Display { get; set; } = new(); + public SpriteSettings Sprite { get; set; } = new(); + public SoundSettings Sounds { get; set; } = new(); + public HoverSettings Hover { get; set; } = new(); + + // GUI Specific + public DrawConfig Draw { get; set; } = new(); + public AdvancedSettings Advanced { get; set; } = new(); + public EntityEditorSettings EntityEditor { get; set; } = new(); + public EntityDatabaseSettings EntityDb { get; set; } = new(); + public EncounterDatabaseSettings EncounterDb { get; set; } = new(); + public MysteryGiftDatabaseSettings MysteryDb { get; set; } = new(); + public BulkAnalysisSettings Bulk { get; set; } = new(); + + public static PKHeXSettings GetSettings(string configPath) { - public StartupSettings Startup { get; set; } = new(); - public BackupSettings Backup { get; set; } = new(); + if (!File.Exists(configPath)) + return new PKHeXSettings(); - // General - public LegalitySettings Legality { get; set; } = new(); - public SetImportSettings Import { get; set; } = new(); - public SlotWriteSettings SlotWrite { get; set; } = new(); - public PrivacySettings Privacy { get; set; } = new(); - - // UI Tweaks - public DisplaySettings Display { get; set; } = new(); - public SpriteSettings Sprite { get; set; } = new(); - public SoundSettings Sounds { get; set; } = new(); - public HoverSettings Hover { get; set; } = new(); - - // GUI Specific - public DrawConfig Draw { get; set; } = new(); - public AdvancedSettings Advanced { get; set; } = new(); - public EntityEditorSettings EntityEditor { get; set; } = new(); - public EntityDatabaseSettings EntityDb { get; set; } = new(); - public EncounterDatabaseSettings EncounterDb { get; set; } = new(); - public MysteryGiftDatabaseSettings MysteryDb { get; set; } = new(); - public BulkAnalysisSettings Bulk { get; set; } = new(); - - public static PKHeXSettings GetSettings(string configPath) + try { - if (!File.Exists(configPath)) - return new PKHeXSettings(); - - try - { - var lines = File.ReadAllText(configPath); - return JsonConvert.DeserializeObject(lines) ?? new PKHeXSettings(); - } - catch (Exception x) - { - DumpConfigError(x); - return new PKHeXSettings(); - } + var lines = File.ReadAllText(configPath); + return JsonConvert.DeserializeObject(lines) ?? new PKHeXSettings(); } - - public static void SaveSettings(string configPath, PKHeXSettings cfg) + catch (Exception x) { - try - { - var settings = new JsonSerializerSettings - { - Formatting = Formatting.Indented, - DefaultValueHandling = DefaultValueHandling.Populate, - NullValueHandling = NullValueHandling.Ignore, - }; - var text = JsonConvert.SerializeObject(cfg, settings); - File.WriteAllText(configPath, text); - } - catch (Exception x) - { - DumpConfigError(x); - } - } - - private static void DumpConfigError(Exception x) - { - try - { - File.WriteAllLines("config error.txt", new[] { x.ToString() }); - } - catch (Exception) - { - Debug.WriteLine(x); // ??? - } + DumpConfigError(x); + return new PKHeXSettings(); } } - [Serializable] - public sealed class BackupSettings + public static void SaveSettings(string configPath, PKHeXSettings cfg) { - [LocalizedDescription("Automatic Backups of Save Files are copied to the backup folder when true.")] - public bool BAKEnabled { get; set; } = true; - - [LocalizedDescription("Tracks if the \"Create Backup\" prompt has been issued to the user.")] - public bool BAKPrompt { get; set; } - - [LocalizedDescription("List of extra locations to look for Save Files.")] - public string[] OtherBackupPaths { get; set; } = Array.Empty(); - - [LocalizedDescription("Save File file-extensions (no period) that the program should also recognize.")] - public string[] OtherSaveFileExtensions { get; set; } = Array.Empty(); - } - - [Serializable] - public sealed class StartupSettings : IStartupSettings - { - [Browsable(false)] - [LocalizedDescription("Last version that the program was run with.")] - public string Version { get; set; } = string.Empty; - - [LocalizedDescription("Force HaX mode on Program Launch")] - public bool ForceHaXOnLaunch { get; set; } - - [LocalizedDescription("Automatically locates the most recently saved Save File when opening a new file.")] - public bool TryDetectRecentSave { get; set; } = true; - - [LocalizedDescription("Automatically Detect Save File on Program Startup")] - public AutoLoadSetting AutoLoadSaveOnStartup { get; set; } = AutoLoadSetting.RecentBackup; - - [LocalizedDescription("Show the changelog when a new version of the program is run for the first time.")] - public bool ShowChangelogOnUpdate { get; set; } = true; - - [LocalizedDescription("Loads plugins from the plugins folder, assuming the folder exists. Try LoadFile to mitigate intermittent load failures.")] - public PluginLoadSetting PluginLoadMethod { get; set; } = PluginLoadSetting.LoadFrom; - - [Browsable(false)] - public List RecentlyLoaded { get; set; } = new(MaxRecentCount); - - // Don't let invalid values slip into the startup version. - private GameVersion _defaultSaveVersion = GameVersion.PLA; - private string _language = GameLanguage.DefaultLanguage; - - [Browsable(false)] - public string Language + try { - get => _language; - set + var settings = new JsonSerializerSettings { - if (GameLanguage.GetLanguageIndex(value) == -1) - return; - _language = value; - } + Formatting = Formatting.Indented, + DefaultValueHandling = DefaultValueHandling.Populate, + NullValueHandling = NullValueHandling.Ignore, + }; + var text = JsonConvert.SerializeObject(cfg, settings); + File.WriteAllText(configPath, text); } - - [Browsable(false)] - public GameVersion DefaultSaveVersion + catch (Exception x) { - get => _defaultSaveVersion; - set - { - if (!value.IsValidSavedVersion()) - return; - _defaultSaveVersion = value; - } + DumpConfigError(x); } + } - private const int MaxRecentCount = 10; - - public void LoadSaveFile(string path) + private static void DumpConfigError(Exception x) + { + try { - var recent = RecentlyLoaded; - // Remove from list if already present. - if (!recent.Remove(path) && recent.Count >= MaxRecentCount) - recent.RemoveAt(recent.Count - 1); - recent.Insert(0, path); + File.WriteAllLines("config error.txt", new[] { x.ToString() }); + } + catch (Exception) + { + Debug.WriteLine(x); // ??? } - } - - public enum PluginLoadSetting - { - DontLoad, - LoadFrom, - LoadFile, - UnsafeLoadFrom, - LoadFromMerged, - LoadFileMerged, - UnsafeMerged, - } - - [Serializable] - public sealed class LegalitySettings : IParseSettings - { - [LocalizedDescription("Checks player given Nicknames and Trainer Names for profanity. Bad words will be flagged using the 3DS console's regex lists.")] - public bool CheckWordFilter { get; set; } = true; - - [LocalizedDescription("Checks the last loaded player save file data and Current Handler state to determine if the Pokémon's Current Handler does not match the expected value.")] - public bool CheckActiveHandler { get; set; } - - [LocalizedDescription("GB: Allow Generation 2 tradeback learnsets for PK1 formats. Disable when checking RBY Metagame rules.")] - public bool AllowGen1Tradeback { get; set; } = true; - - [LocalizedDescription("Severity to flag a Legality Check if it is a nicknamed In-Game Trade the player cannot normally nickname.")] - public Severity NicknamedTrade { get; set; } = Severity.Invalid; - - [LocalizedDescription("Severity to flag a Legality Check if it is a nicknamed Mystery Gift the player cannot normally nickname.")] - public Severity NicknamedMysteryGift { get; set; } = Severity.Fishy; - - [LocalizedDescription("Severity to flag a Legality Check if the RNG Frame Checking logic does not find a match.")] - public Severity RNGFrameNotFound { get; set; } = Severity.Fishy; - - [LocalizedDescription("Severity to flag a Legality Check if Pokémon from Gen1/2 has a Star Shiny PID.")] - public Severity Gen7TransferStarPID { get; set; } = Severity.Fishy; - - [LocalizedDescription("Severity to flag a Legality Check if a Gen8 Memory is missing for the Handling Trainer.")] - public Severity Gen8MemoryMissingHT { get; set; } = Severity.Fishy; - - [LocalizedDescription("Severity to flag a Legality Check if the HOME Tracker is Missing")] - public Severity Gen8TransferTrackerNotPresent { get; set; } = Severity.Fishy; - - [LocalizedDescription("Severity to flag a Legality Check if Pokémon has a Nickname matching another Species.")] - public Severity NicknamedAnotherSpecies { get; set; } = Severity.Fishy; - - [LocalizedDescription("Severity to flag a Legality Check if Pokémon has a zero value for both Height and Weight.")] - public Severity ZeroHeightWeight { get; set; } = Severity.Fishy; - - [LocalizedDescription("Severity to flag a Legality Check if Pokémon's Current Handler does not match the expected value.")] - public Severity CurrentHandlerMismatch { get; set; } = Severity.Invalid; - } - - [Serializable] - public sealed class AdvancedSettings - { - [LocalizedDescription("Allow PKM file conversion paths that are not possible via official methods. Individual properties will be copied sequentially.")] - public EntityCompatibilitySetting AllowIncompatibleConversion { get; set; } = EntityCompatibilitySetting.DisallowIncompatible; - - [LocalizedDescription("Allow PKM file conversion paths to guess the legal original encounter data that is not stored in the format that it was converted from.")] - public EntityRejuvenationSetting AllowGuessRejuvenateHOME { get; set; } = EntityRejuvenationSetting.MissingDataHOME; - - [LocalizedDescription("Folder path that contains dump(s) of block hash-names. If a specific dump file does not exist, only names defined within the program's code will be loaded.")] - public string PathBlockKeyList { get; set; } = string.Empty; - - [LocalizedDescription("Hide event variables below this event type value. Removes event values from the GUI that the user doesn't care to view.")] - public NamedEventType HideEventTypeBelow { get; set; } - - [LocalizedDescription("Hide event variable names for that contain any of the comma-separated substrings below. Removes event values from the GUI that the user doesn't care to view.")] - public string HideEvent8Contains { get; set; } = string.Empty; - - [Browsable(false)] - public string[] GetExclusionList8() => Array.ConvertAll(HideEvent8Contains.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), z => z.Trim()); - } - - [Serializable] - public class EntityDatabaseSettings - { - [LocalizedDescription("When loading content for the PKM Database, search within backup save files.")] - public bool SearchBackups { get; set; } = true; - - [LocalizedDescription("When loading content for the PKM Database, search within OtherBackupPaths.")] - public bool SearchExtraSaves { get; set; } = true; - - [LocalizedDescription("When loading content for the PKM Database, search subfolders within OtherBackupPaths.")] - public bool SearchExtraSavesDeep { get; set; } = true; - - [LocalizedDescription("When loading content for the PKM database, the list will be ordered by this option.")] - public DatabaseSortMode InitialSortMode { get; set; } - - [LocalizedDescription("Hides unavailable Species if the currently loaded save file cannot import them.")] - public bool FilterUnavailableSpecies { get; set; } = true; - } - - public enum DatabaseSortMode - { - None, - SpeciesForm, - SlotIdentity, - } - - [Serializable] - public sealed class EntityEditorSettings - { - [LocalizedDescription("When changing the Hidden Power type, automatically maximize the IVs to ensure the highest Base Power result. Otherwise, keep the IVs as close as possible to the original.")] - public bool HiddenPowerOnChangeMaxPower { get; set; } = true; - } - - [Serializable] - public sealed class EncounterDatabaseSettings - { - [LocalizedDescription("Skips searching if the user forgot to enter Species / Move(s) into the search criteria.")] - public bool ReturnNoneIfEmptySearch { get; set; } = true; - - [LocalizedDescription("Hides unavailable Species if the currently loaded save file cannot import them.")] - public bool FilterUnavailableSpecies { get; set; } = true; - - [LocalizedDescription("Use properties from the PKM Editor tabs to specify criteria like Gender and Nature when generating an encounter.")] - public bool UseTabsAsCriteria { get; set; } = true; - - [LocalizedDescription("Use properties from the PKM Editor tabs even if the new encounter isn't the same evolution chain.")] - public bool UseTabsAsCriteriaAnySpecies { get; set; } = true; - } - - [Serializable] - public sealed class MysteryGiftDatabaseSettings - { - [LocalizedDescription("Hides gifts if the currently loaded save file cannot (indirectly) receive them.")] - public bool FilterUnavailableSpecies { get; set; } = true; - } - - [Serializable] - public sealed class HoverSettings - { - [LocalizedDescription("Show PKM Slot ToolTip on Hover")] - public bool HoverSlotShowText { get; set; } = true; - - [LocalizedDescription("Play PKM Slot Cry on Hover")] - public bool HoverSlotPlayCry { get; set; } = true; - - [LocalizedDescription("Show a Glow effect around the PKM on Hover")] - public bool HoverSlotGlowEdges { get; set; } = true; - } - - [Serializable] - public sealed class SoundSettings - { - [LocalizedDescription("Play Sound when loading a new Save File")] - public bool PlaySoundSAVLoad { get; set; } = true; - [LocalizedDescription("Play Sound when popping up Legality Report")] - public bool PlaySoundLegalityCheck { get; set; } = true; - } - - [Serializable] - public sealed class SetImportSettings - { - [LocalizedDescription("Apply StatNature to Nature on Import")] - public bool ApplyNature { get; set; } = true; - [LocalizedDescription("Apply Markings on Import")] - public bool ApplyMarkings { get; set; } = true; - } - - [Serializable] - public sealed class SlotWriteSettings - { - [LocalizedDescription("Automatically modify the Save File's Pokédex when injecting a PKM.")] - public bool SetUpdateDex { get; set; } = true; - - [LocalizedDescription("Automatically adapt the PKM Info to the Save File (Handler, Format)")] - public bool SetUpdatePKM { get; set; } = true; - - [LocalizedDescription("When enabled and closing/loading a save file, the program will alert if the current save file has been modified without saving.")] - public bool ModifyUnset { get; set; } = true; - } - - [Serializable] - public sealed class DisplaySettings - { - [LocalizedDescription("Show Unicode gender symbol characters, or ASCII when disabled.")] - public bool Unicode { get; set; } = true; - - [LocalizedDescription("Don't show the Legality popup if Legal!")] - public bool IgnoreLegalPopup { get; set; } - - [LocalizedDescription("Flag Illegal Slots in Save File")] - public bool FlagIllegal { get; set; } = true; - } - - [Serializable] - public sealed class SpriteSettings : ISpriteSettings - { - [LocalizedDescription("Choice for which sprite building mode to use.")] - public SpriteBuilderPreference SpritePreference { get; set; } = SpriteBuilderPreference.UseSuggested; - - [LocalizedDescription("Show fan-made shiny sprites when the PKM is shiny.")] - public bool ShinySprites { get; set; } = true; - - [LocalizedDescription("Show an Egg Sprite As Held Item rather than hiding the PKM")] - public bool ShowEggSpriteAsHeldItem { get; set; } = true; - - [LocalizedDescription("Show the required ball for an Encounter Template")] - public bool ShowEncounterBall { get; set; } = true; - - [LocalizedDescription("Show a background to differentiate an Encounter Template's type")] - public SpriteBackgroundType ShowEncounterColor { get; set; } = SpriteBackgroundType.FullBackground; - - [LocalizedDescription("Show a background to differentiate the recognized Encounter Template type for PKM slots")] - public SpriteBackgroundType ShowEncounterColorPKM { get; set; } - - [LocalizedDescription("Opacity for the Encounter Type background layer.")] - public byte ShowEncounterOpacityBackground { get; set; } = 0x3F; // kinda low - - [LocalizedDescription("Opacity for the Encounter Type stripe layer.")] - public byte ShowEncounterOpacityStripe { get; set; } = 0x5F; // 0xFF opaque - - [LocalizedDescription("Show a thin stripe to indicate the percent of level-up progress")] - public bool ShowExperiencePercent { get; set; } - - [LocalizedDescription("Amount of pixels thick to show when displaying the encounter type color stripe.")] - public int ShowEncounterThicknessStripe { get; set; } = 4; // pixels - } - - [Serializable] - public sealed class PrivacySettings - { - [LocalizedDescription("Hide Save File Details in Program Title")] - public bool HideSAVDetails { get; set; } - - [LocalizedDescription("Hide Secret Details in Editors")] - public bool HideSecretDetails { get; set; } - } - - [Serializable] - public sealed class BulkAnalysisSettings : IBulkAnalysisSettings - { - [LocalizedDescription("Checks the save file data and Current Handler state to determine if the Pokémon's Current Handler does not match the expected value.")] - public bool CheckActiveHandler { get; set; } = true; } } + +[Serializable] +public sealed class BackupSettings +{ + [LocalizedDescription("Automatic Backups of Save Files are copied to the backup folder when true.")] + public bool BAKEnabled { get; set; } = true; + + [LocalizedDescription("Tracks if the \"Create Backup\" prompt has been issued to the user.")] + public bool BAKPrompt { get; set; } + + [LocalizedDescription("List of extra locations to look for Save Files.")] + public string[] OtherBackupPaths { get; set; } = Array.Empty(); + + [LocalizedDescription("Save File file-extensions (no period) that the program should also recognize.")] + public string[] OtherSaveFileExtensions { get; set; } = Array.Empty(); +} + +[Serializable] +public sealed class StartupSettings : IStartupSettings +{ + [Browsable(false)] + [LocalizedDescription("Last version that the program was run with.")] + public string Version { get; set; } = string.Empty; + + [LocalizedDescription("Force HaX mode on Program Launch")] + public bool ForceHaXOnLaunch { get; set; } + + [LocalizedDescription("Automatically locates the most recently saved Save File when opening a new file.")] + public bool TryDetectRecentSave { get; set; } = true; + + [LocalizedDescription("Automatically Detect Save File on Program Startup")] + public AutoLoadSetting AutoLoadSaveOnStartup { get; set; } = AutoLoadSetting.RecentBackup; + + [LocalizedDescription("Show the changelog when a new version of the program is run for the first time.")] + public bool ShowChangelogOnUpdate { get; set; } = true; + + [LocalizedDescription("Loads plugins from the plugins folder, assuming the folder exists. Try LoadFile to mitigate intermittent load failures.")] + public PluginLoadSetting PluginLoadMethod { get; set; } = PluginLoadSetting.LoadFrom; + + [Browsable(false)] + public List RecentlyLoaded { get; set; } = new(MaxRecentCount); + + // Don't let invalid values slip into the startup version. + private GameVersion _defaultSaveVersion = GameVersion.PLA; + private string _language = GameLanguage.DefaultLanguage; + + [Browsable(false)] + public string Language + { + get => _language; + set + { + if (GameLanguage.GetLanguageIndex(value) == -1) + return; + _language = value; + } + } + + [Browsable(false)] + public GameVersion DefaultSaveVersion + { + get => _defaultSaveVersion; + set + { + if (!value.IsValidSavedVersion()) + return; + _defaultSaveVersion = value; + } + } + + private const int MaxRecentCount = 10; + + public void LoadSaveFile(string path) + { + var recent = RecentlyLoaded; + // Remove from list if already present. + if (!recent.Remove(path) && recent.Count >= MaxRecentCount) + recent.RemoveAt(recent.Count - 1); + recent.Insert(0, path); + } +} + +public enum PluginLoadSetting +{ + DontLoad, + LoadFrom, + LoadFile, + UnsafeLoadFrom, + LoadFromMerged, + LoadFileMerged, + UnsafeMerged, +} + +[Serializable] +public sealed class LegalitySettings : IParseSettings +{ + [LocalizedDescription("Checks player given Nicknames and Trainer Names for profanity. Bad words will be flagged using the 3DS console's regex lists.")] + public bool CheckWordFilter { get; set; } = true; + + [LocalizedDescription("Checks the last loaded player save file data and Current Handler state to determine if the Pokémon's Current Handler does not match the expected value.")] + public bool CheckActiveHandler { get; set; } + + [LocalizedDescription("GB: Allow Generation 2 tradeback learnsets for PK1 formats. Disable when checking RBY Metagame rules.")] + public bool AllowGen1Tradeback { get; set; } = true; + + [LocalizedDescription("Severity to flag a Legality Check if it is a nicknamed In-Game Trade the player cannot normally nickname.")] + public Severity NicknamedTrade { get; set; } = Severity.Invalid; + + [LocalizedDescription("Severity to flag a Legality Check if it is a nicknamed Mystery Gift the player cannot normally nickname.")] + public Severity NicknamedMysteryGift { get; set; } = Severity.Fishy; + + [LocalizedDescription("Severity to flag a Legality Check if the RNG Frame Checking logic does not find a match.")] + public Severity RNGFrameNotFound { get; set; } = Severity.Fishy; + + [LocalizedDescription("Severity to flag a Legality Check if Pokémon from Gen1/2 has a Star Shiny PID.")] + public Severity Gen7TransferStarPID { get; set; } = Severity.Fishy; + + [LocalizedDescription("Severity to flag a Legality Check if a Gen8 Memory is missing for the Handling Trainer.")] + public Severity Gen8MemoryMissingHT { get; set; } = Severity.Fishy; + + [LocalizedDescription("Severity to flag a Legality Check if the HOME Tracker is Missing")] + public Severity Gen8TransferTrackerNotPresent { get; set; } = Severity.Fishy; + + [LocalizedDescription("Severity to flag a Legality Check if Pokémon has a Nickname matching another Species.")] + public Severity NicknamedAnotherSpecies { get; set; } = Severity.Fishy; + + [LocalizedDescription("Severity to flag a Legality Check if Pokémon has a zero value for both Height and Weight.")] + public Severity ZeroHeightWeight { get; set; } = Severity.Fishy; + + [LocalizedDescription("Severity to flag a Legality Check if Pokémon's Current Handler does not match the expected value.")] + public Severity CurrentHandlerMismatch { get; set; } = Severity.Invalid; +} + +[Serializable] +public sealed class AdvancedSettings +{ + [LocalizedDescription("Allow PKM file conversion paths that are not possible via official methods. Individual properties will be copied sequentially.")] + public EntityCompatibilitySetting AllowIncompatibleConversion { get; set; } = EntityCompatibilitySetting.DisallowIncompatible; + + [LocalizedDescription("Allow PKM file conversion paths to guess the legal original encounter data that is not stored in the format that it was converted from.")] + public EntityRejuvenationSetting AllowGuessRejuvenateHOME { get; set; } = EntityRejuvenationSetting.MissingDataHOME; + + [LocalizedDescription("Folder path that contains dump(s) of block hash-names. If a specific dump file does not exist, only names defined within the program's code will be loaded.")] + public string PathBlockKeyList { get; set; } = string.Empty; + + [LocalizedDescription("Hide event variables below this event type value. Removes event values from the GUI that the user doesn't care to view.")] + public NamedEventType HideEventTypeBelow { get; set; } + + [LocalizedDescription("Hide event variable names for that contain any of the comma-separated substrings below. Removes event values from the GUI that the user doesn't care to view.")] + public string HideEvent8Contains { get; set; } = string.Empty; + + [Browsable(false)] + public string[] GetExclusionList8() => Array.ConvertAll(HideEvent8Contains.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries), z => z.Trim()); +} + +[Serializable] +public class EntityDatabaseSettings +{ + [LocalizedDescription("When loading content for the PKM Database, search within backup save files.")] + public bool SearchBackups { get; set; } = true; + + [LocalizedDescription("When loading content for the PKM Database, search within OtherBackupPaths.")] + public bool SearchExtraSaves { get; set; } = true; + + [LocalizedDescription("When loading content for the PKM Database, search subfolders within OtherBackupPaths.")] + public bool SearchExtraSavesDeep { get; set; } = true; + + [LocalizedDescription("When loading content for the PKM database, the list will be ordered by this option.")] + public DatabaseSortMode InitialSortMode { get; set; } + + [LocalizedDescription("Hides unavailable Species if the currently loaded save file cannot import them.")] + public bool FilterUnavailableSpecies { get; set; } = true; +} + +public enum DatabaseSortMode +{ + None, + SpeciesForm, + SlotIdentity, +} + +[Serializable] +public sealed class EntityEditorSettings +{ + [LocalizedDescription("When changing the Hidden Power type, automatically maximize the IVs to ensure the highest Base Power result. Otherwise, keep the IVs as close as possible to the original.")] + public bool HiddenPowerOnChangeMaxPower { get; set; } = true; +} + +[Serializable] +public sealed class EncounterDatabaseSettings +{ + [LocalizedDescription("Skips searching if the user forgot to enter Species / Move(s) into the search criteria.")] + public bool ReturnNoneIfEmptySearch { get; set; } = true; + + [LocalizedDescription("Hides unavailable Species if the currently loaded save file cannot import them.")] + public bool FilterUnavailableSpecies { get; set; } = true; + + [LocalizedDescription("Use properties from the PKM Editor tabs to specify criteria like Gender and Nature when generating an encounter.")] + public bool UseTabsAsCriteria { get; set; } = true; + + [LocalizedDescription("Use properties from the PKM Editor tabs even if the new encounter isn't the same evolution chain.")] + public bool UseTabsAsCriteriaAnySpecies { get; set; } = true; +} + +[Serializable] +public sealed class MysteryGiftDatabaseSettings +{ + [LocalizedDescription("Hides gifts if the currently loaded save file cannot (indirectly) receive them.")] + public bool FilterUnavailableSpecies { get; set; } = true; +} + +[Serializable] +public sealed class HoverSettings +{ + [LocalizedDescription("Show PKM Slot ToolTip on Hover")] + public bool HoverSlotShowText { get; set; } = true; + + [LocalizedDescription("Play PKM Slot Cry on Hover")] + public bool HoverSlotPlayCry { get; set; } = true; + + [LocalizedDescription("Show a Glow effect around the PKM on Hover")] + public bool HoverSlotGlowEdges { get; set; } = true; +} + +[Serializable] +public sealed class SoundSettings +{ + [LocalizedDescription("Play Sound when loading a new Save File")] + public bool PlaySoundSAVLoad { get; set; } = true; + [LocalizedDescription("Play Sound when popping up Legality Report")] + public bool PlaySoundLegalityCheck { get; set; } = true; +} + +[Serializable] +public sealed class SetImportSettings +{ + [LocalizedDescription("Apply StatNature to Nature on Import")] + public bool ApplyNature { get; set; } = true; + [LocalizedDescription("Apply Markings on Import")] + public bool ApplyMarkings { get; set; } = true; +} + +[Serializable] +public sealed class SlotWriteSettings +{ + [LocalizedDescription("Automatically modify the Save File's Pokédex when injecting a PKM.")] + public bool SetUpdateDex { get; set; } = true; + + [LocalizedDescription("Automatically adapt the PKM Info to the Save File (Handler, Format)")] + public bool SetUpdatePKM { get; set; } = true; + + [LocalizedDescription("When enabled and closing/loading a save file, the program will alert if the current save file has been modified without saving.")] + public bool ModifyUnset { get; set; } = true; +} + +[Serializable] +public sealed class DisplaySettings +{ + [LocalizedDescription("Show Unicode gender symbol characters, or ASCII when disabled.")] + public bool Unicode { get; set; } = true; + + [LocalizedDescription("Don't show the Legality popup if Legal!")] + public bool IgnoreLegalPopup { get; set; } + + [LocalizedDescription("Flag Illegal Slots in Save File")] + public bool FlagIllegal { get; set; } = true; +} + +[Serializable] +public sealed class SpriteSettings : ISpriteSettings +{ + [LocalizedDescription("Choice for which sprite building mode to use.")] + public SpriteBuilderPreference SpritePreference { get; set; } = SpriteBuilderPreference.UseSuggested; + + [LocalizedDescription("Show fan-made shiny sprites when the PKM is shiny.")] + public bool ShinySprites { get; set; } = true; + + [LocalizedDescription("Show an Egg Sprite As Held Item rather than hiding the PKM")] + public bool ShowEggSpriteAsHeldItem { get; set; } = true; + + [LocalizedDescription("Show the required ball for an Encounter Template")] + public bool ShowEncounterBall { get; set; } = true; + + [LocalizedDescription("Show a background to differentiate an Encounter Template's type")] + public SpriteBackgroundType ShowEncounterColor { get; set; } = SpriteBackgroundType.FullBackground; + + [LocalizedDescription("Show a background to differentiate the recognized Encounter Template type for PKM slots")] + public SpriteBackgroundType ShowEncounterColorPKM { get; set; } + + [LocalizedDescription("Opacity for the Encounter Type background layer.")] + public byte ShowEncounterOpacityBackground { get; set; } = 0x3F; // kinda low + + [LocalizedDescription("Opacity for the Encounter Type stripe layer.")] + public byte ShowEncounterOpacityStripe { get; set; } = 0x5F; // 0xFF opaque + + [LocalizedDescription("Show a thin stripe to indicate the percent of level-up progress")] + public bool ShowExperiencePercent { get; set; } + + [LocalizedDescription("Amount of pixels thick to show when displaying the encounter type color stripe.")] + public int ShowEncounterThicknessStripe { get; set; } = 4; // pixels +} + +[Serializable] +public sealed class PrivacySettings +{ + [LocalizedDescription("Hide Save File Details in Program Title")] + public bool HideSAVDetails { get; set; } + + [LocalizedDescription("Hide Secret Details in Editors")] + public bool HideSecretDetails { get; set; } +} + +[Serializable] +public sealed class BulkAnalysisSettings : IBulkAnalysisSettings +{ + [LocalizedDescription("Checks the save file data and Current Handler state to determine if the Pokémon's Current Handler does not match the expected value.")] + public bool CheckActiveHandler { get; set; } = true; +} diff --git a/PKHeX.WinForms/Resources/text/changelog.txt b/PKHeX.WinForms/Resources/text/changelog.txt index fc18887d1..4afaed37b 100644 --- a/PKHeX.WinForms/Resources/text/changelog.txt +++ b/PKHeX.WinForms/Resources/text/changelog.txt @@ -254,7 +254,7 @@ http://projectpokemon.org/pkhex/ - Added: More event flag data labels. Thanks @FeralFalcon! Event data can now be filtered via settings if you only want to see rebattle, etc. - Added: Rival name changing for all games that didn't yet have it. Thanks @FeralFalcon! - Changed: .NET 6 support added for WinForms builds. .NET Framework 4.6 build is still the main build option. - - Changed: Starting up the program with command line arguments (or opening via an associated file) will more intelligently source a parter sav/pkm. + - Changed: Starting up the program with command line arguments (or opening via an associated file) will more intelligently source a parter sav/pk. - Changed: Exporting a backup save file has been moved to the SAV tab, and the Export main has been merged in with the parent Export SAV item. - Changed: Gen1/2 mainline save files now indicate if they are VC era or GB era in the program title. - Changed: Gen2 modifying Hidden Power type now maximizes IVs for that type. @@ -286,7 +286,7 @@ http://projectpokemon.org/pkhex/ - Fixed: Gen3 Colosseum Fateful encounter flag is now read correctly. - Fixed: Gen5 BW entree forest randomizing now prevents B2W2-only templates from being loaded. Thanks @SunakazeKun! - Fixed: Event Const editor loads consts with a current value of zero correctly if it has a defined name. Thanks @CanoeHope! - - Fixed: Batch Editor no longer processes saved pkm files twice. + - Fixed: Batch Editor no longer processes saved pk files twice. - Fixed: PKM Database searching a specific format now filters correctly. - Updated: Chinese translation updated. Thanks @liketolike! @@ -700,5 +700,5 @@ http://projectpokemon.org/pkhex/ - Added: Setting to disable sounds on save file load and legality checks (nuisance). - Fixed: Gen8 Mimikyu Busted form now shows up in Pokédex editor. - Fixed: Gen4 Event Constants are now read correctly. - - Fixed: Disabled pkm slot glow effect will no longer leave the frosted effect behind. + - Fixed: Disabled pk slot glow effect will no longer leave the frosted effect behind. - Changed: GUI translation (winforms only) files moved to WinForms project (no longer in PKHeX.Core). Still need translations ;) diff --git a/PKHeX.WinForms/Resources/text/changelog_2018.txt b/PKHeX.WinForms/Resources/text/changelog_2018.txt index 9a1269cdd..43d416827 100644 --- a/PKHeX.WinForms/Resources/text/changelog_2018.txt +++ b/PKHeX.WinForms/Resources/text/changelog_2018.txt @@ -11,7 +11,7 @@ - Added: LGPE GP1 files can now be deleted from the GO Park, and can even clear the entire park. - Added: LGPE PB7 set to storage will now set Size record data when appropriate. - Fixed: LGPE CP now updates correctly when changing any stat-impacting value. - - Fixed: LGPE Party stats are now applied for all pkm set to storage, resulting in no more Level 0 stored pkm. + - Fixed: LGPE Party stats are now applied for all pk set to storage, resulting in no more Level 0 stored pk. - Fixed: LGPE starter experience-level is now calculated correctly (different Experience Group). - Fixed: LGPE box sorting now updates the party indexes as intended. - Fixed: LGPE suggested moves no longer contain egg moves (data sourced from US/UM). @@ -37,7 +37,7 @@ - - Fixed: Colosseum MATTLE Ho-Oh in different languages is now allowed as .ck3 to have the original too-long OT name. - - Fixed: Gen4 Shuckie (different between HG & SS) is now accounted for properly. - - Fixed: Gen4 KOR Pichu mystery gift is now accounted for properly. - - Added: Setting a pkm in gen6+ now updates the Captured/Hatched records for more legal storage count tracking. + - Added: Setting a pk in gen6+ now updates the Captured/Hatched records for more legal storage count tracking. - Added: Trainer Appearance editor for X/Y. - Added: Gen1 can now set the Catch Rate property directly from the PKM Editor. - Added: Gen3 FR/LG event flags are now editable correctly. @@ -58,7 +58,7 @@ - Added: Gen6 Trainer Stats can now be edited (full list of stats added). - Added: Gen4 Misc Editor can now grant all Pokéball Seals. Thanks @sora10pls! - Added: Custom Backup Paths are now included in the autodetect save file search. Thanks @ReignOfComputer! - - Added: User Settings can now hide sensitive pkm details (PID/EC text fields). + - Added: User Settings can now hide sensitive pk details (PID/EC text fields). - Added: User Settings can now hide sensitive savefile details (program title bar, game sync fields). - Added: Gen6/7 Can now unlock Mega Rayquaza early in the Trainer Editor. - Changed: Species / Form cries are now generation specific (Cosplay Pikachu) and follow the same rules as sprites. @@ -100,13 +100,13 @@ - Added: Gen3 now will set Spinda & Unown PID to the dex when registering, if the species has not yet been seen. - Added: Gen1/2 formats can now edit the Hidden Power type selection, which will update ATK/DEF according to the desired type. - Fixed: Gen1 Yellow Kadabra catch rate is now correctly retained when editing. - - Fixed: Batch Editor interface no longer errors for EggMetDate when the pkm loaded to tabs has no egg met data. + - Fixed: Batch Editor interface no longer errors for EggMetDate when the pk loaded to tabs has no egg met data. - Changed: Spanish translation updated. Thanks @XxPhoenix1996xX! 18/07/09 - New Update: (17086) [1250542] - Legality: - - Changed: Another round of legality check updates. Thanks @iiippppk, @ReZeroEmiria, @PrometheusG, @PlasticJustice, @host1126! - - - Changed: Pokespot pkm are no longer strictly checked for encounter slot legality due to lack of research. + - - Changed: Pokespot pk are no longer strictly checked for encounter slot legality due to lack of research. - Added: Can now export the current box to a ShowdownSet paste. Hold control to export all boxes! - Added: Gen4 Battle Revolution parties can now be viewed. - Added: Gen3 Ruby/Sapphire badges can now be toggled within the trainer editor. @@ -118,7 +118,7 @@ - Fixed: Fixed misc ShowdownSet import/export logic. Thanks @architdate, @DJPanda065! - Fixed: Gen4->4 Battle Revolution conversion now keeps Unown's alt forme ID. - Fixed: Gen3/4 ShowdownSet import no longer soft-locks the program looking for a suitable PID. - - Fixed: Gen3 Mirage Island removed from valid met location selection; pkm are met at Route 130 instead. + - Fixed: Gen3 Mirage Island removed from valid met location selection; pk are met at Route 130 instead. - Fixed: Gen3 Event Flags for Emerald now point to the correct indexes. Thanks @NinFanBoyFTW! - Fixed: Gen3 Colosseum key items with the same name are no longer merged when editing inventory. Thanks KonohaDaze & Asia81! - Fixed: Gen3 RSBOX save file checksums now save correctly. Thanks @iiippppk! @@ -133,7 +133,7 @@ - - Fixed: Gen3 Mirage Island Wynaut encounter is no longer incorrectly flagged as illegal. - Added: Box Sort/Modify/Delete quick action tools. Right click the Box tab to see available options. - Added: Hovering over a Pokémon slot (sprite) now shows a Showdown style text preview of the contents. - - Added: Hovering over a Pokémon slot (sprite) now plays the pkm's cry. + - Added: Hovering over a Pokémon slot (sprite) now plays the pk's cry. - - Put the (species#-form#.wav) files in a folder named "sounds" in the same folder as PKHeX.exe. - Added: Gen6 Memory Feeling legality. Only certain 'feeling' values are legal for a given memory index. - Added: Gen5 Entree Forest editor & randomizer. Populates with legal dream world encounters. @@ -141,7 +141,7 @@ - Added: Work in progress Encounter -> PKM generator updated to generate all encounters legally. More to come... - Changed: Gen7+ origin Pokémon now show the Gen7 TID/SID style instead of prior gen 16 bit values. - Changed: Batch Editor logic relocated to PKHeX.Core for reusability. - - Changed: Simple pkm editing logic relocated to PKHeX.Core (CommonEdits) for reusability. + - Changed: Simple pk editing logic relocated to PKHeX.Core (CommonEdits) for reusability. - Fixed: Gen3 FRLG 'Security' value offset fixed (was misidentified as the Berry Powder. Thanks HaiPhuong! - Fixed: Using the Gen4 Misc editor no longer corrupts Pokétch data. - Fixed: Misc Showdown set parsing/export issues. Now handles Gen2 & Gen3 context-sensitive exports (held items). @@ -196,7 +196,7 @@ - Legality: - - Added: Crystal transfer legality. - - Changed: Another round of legality check updates. Thanks (so many different users)! - - Added: Shiny recolored sprites for all pkm that are shiny. Thanks @msikma (dada)! + - Added: Shiny recolored sprites for all pk that are shiny. Thanks @msikma (dada)! - Added: All Box viewer (hold shift and double click the Box Tab). Can switch entire boxes! - Added: Gen5 C-Gear Skin editing. - Added: Gen5 Box Name/Wallpaper editing. @@ -209,7 +209,7 @@ - Fixed: Gen2 Unown Dex not setting a valid first-seen unown forme. Thanks BeyondTheHorizon! - Fixed: Database view search/delete behavior fixed. Thanks @NinFanBoyFTW! - Fixed: Loading non-mainseries games no longer causes some tabs to load incorrectly. - - Changed: Updated internals for faster data handling (pkm crypto, save loading). + - Changed: Updated internals for faster data handling (pk crypto, save loading). - Changed: Updated JP/KO translation files. Thanks @smileynation & lilymaniac! - Changed: Updated pkrs/pentagon sprites. Thanks @sora10pls! @@ -250,7 +250,7 @@ - Fixed: US/UM Z Crystals -> PKM sprite no longer chops the rightmost pixel. - Fixed: Unsaved fields in PKMEditor now correctly block saving of the PKM from tabs. - Fixed: Gen6 trainer coordinate saveback glitch fixed (similar to S/M's rumor). - - Fixed: Gen7 SM<->US/UM pkm compatibility checks updated to prevent invalid data from being set. + - Fixed: Gen7 SM<->US/UM pk compatibility checks updated to prevent invalid data from being set. - Fixed: Gen5-- dragdrop no longer gives AltForm <= 0 messages. - Changed: Mystery Gift sprite generation speed improved. - Changed: French GUI Translation data updated. Thanks Ntonio36! @@ -281,7 +281,7 @@ - - Fixed: Gen1/2 -> Gen7 transfers are now checked for met locations. - Added: Misc safeguards for setting PKM data not obtainable in the save file's origin game. Thanks sora10pls! - Added: Gen5 BP editing. Thanks sora10pls! - - Added: Can now load Mystery Gift pkm QR images to the Main Window tabs (same as importing PKM QRs). + - Added: Can now load Mystery Gift pk QR images to the Main Window tabs (same as importing PKM QRs). - Changed: Internal string resource fetch mechanism speed improved. Thanks evandixon! - Changed: Gen1/2 -> Gen7 transfer language detection improved for special characters in certain languages (German). - Fixed: Zygarde 50%-C showdown set importing. Thanks architdate! @@ -316,7 +316,7 @@ - Fixed: Current Poketch app now reads from the correct offset. Thanks sora10pls! - Changed: Increased form loading speed (translation now translates controls faster). - Changed: Folder browser now prioritizes available folders (sorting them to the top). - - Changed: Internal refactoring for pkm loading to tabs to standardize loading routines. + - Changed: Internal refactoring for pk loading to tabs to standardize loading routines. - Changed: PCD now caches the gift PKM for increased speed (Mystery Gift database or legality checks). - Changed: Gen1/2 save file version detection improved (can now differentiate RB from Y, and VC from emu). - Changed: Optimized crc method for faster save detection & saving. @@ -341,7 +341,7 @@ - Added: HGSS Pokewalker locations unlock all button. - Added: Gen1/2 PKM can now be made shiny in the batch editor (.PID=$shiny) - Added: Database/MGDB searches can now be made on the abstract class properties (PKM/MysteryGift). - - Added: PKM Database now loads pkm data from save files that have been backed up. Tons to search! + - Added: PKM Database now loads pk data from save files that have been backed up. Tons to search! - Added: Gen4 (DPPt) Underground Score editor. Thanks @egzonqj! - Changed: Box Legality checking setting is turned on by default for new users. Too many don't know it exists :) - Fixed: Gen6 OR/AS mega forms are now selectable again. Thanks @SwampyGator! @@ -359,8 +359,8 @@ - - Fixed: Air Cutter switched with Roost in OR/AS tutor compatibility list. Thanks @architdate! - - Changed: Updated Chinese translation files. Thanks @wwwwwwzx! - - Added: Gen2 headbutt encounters are now checked for headbutt legality. Thanks @javierhimura! - - - Added: Transferring a pkm from XD to any other game will now receive a Fateful Encounter flag if appropriate. - - Added: WC3 (gen3 event template) -> pkm conversion. Use the Mystery Gift Database. + - - Added: Transferring a pk from XD to any other game will now receive a Fateful Encounter flag if appropriate. + - Added: WC3 (gen3 event template) -> pk conversion. Use the Mystery Gift Database. - Added: Ability to batch edit trash bytes (.Nickname_Trash=$[]43,00,...) - Added: Inventory items can now be sorted by index number. - Changed: Event Flag/Constant editor redesigned to separate tabs. @@ -378,12 +378,12 @@ - - Added: Met location legality checks for GSC origin pk2's. Thanks pokecal! - - Added: Poke Pelago level range checks. Thanks SciresM! - - Changed: Another round of legality check updates. Thanks (so many different users)! - - Added: Gen4 shiny leaf editing (pkm edits while Generation Format = 4). Thanks sora10pls! - - Added: pkmdb now fetches pkm data from backed up save files (only if the program is compiled with debug flag) + - Added: Gen4 shiny leaf editing (pk edits while Generation Format = 4). Thanks sora10pls! + - Added: pkmdb now fetches pk data from backed up save files (only if the program is compiled with debug flag) - Added: More misc editing for gen3/4/5 save files. Thanks pokecal! - Added: Holding control when setting shiny will modify the SID instead of PID/EC. Thanks javierhimura! - Fixed: Honey Tree editor now saves checksums correctly. Thanks pokecal! - - Fixed: Batch Editor now accesses pkm properties for filtering correctly. Thanks sora10pls! + - Fixed: Batch Editor now accesses pk properties for filtering correctly. Thanks sora10pls! - Fixed: Can now load boxdata/pcdata binaries on Gen<=3. Thanks PKMWM1! - Changed: Ribbons in Ribbon Editor are now sorted alphabetically, and hovering over an image displays the ribbon name. - Changed: Various speed improvements for legality checking and program loading have been made. @@ -420,7 +420,7 @@ - Changed: Trash Byte & Special character editing window (added) now replaces the Text Entry window. - Changed: Mystery Gift DB now has Gen 4 & 5 event files to select from. - Changed: Mystery Gift DB and PKM DB browsing speed has been improved slightly. - - Changed: Hovering over a pkm storage slot now gives more visual feedback. + - Changed: Hovering over a pk storage slot now gives more visual feedback. - - Useful for viewing/editing trash bytes of any generation string, like A-Save. - Added: Can now load GameCube memory card files directly. Thanks javierhimura! - Added: More Gen3 misc editing features, such as Ferry Pass (event gift) adding. Thanks pokecal! @@ -448,7 +448,7 @@ - Added: Gen3 Misc Record editor. Thanks Aladore384! - Added: Alternate shiny icon for red box backgrounds (more visible with a halo). Thanks pokecal! - Fixed: Gen3 E/FRLG dex editing bitflag set no longer invalidates the save file. - - Fixed: Gen3 C/XD Spanish pkm no longer invalidated when editing. Thanks javierhimura! + - Fixed: Gen3 C/XD Spanish pk no longer invalidated when editing. Thanks javierhimura! 17/03/08 - New Update: (11343) [408829] - Legality: @@ -461,7 +461,7 @@ - Fixed: Gen3 Egg nickname handling. Thanks Sabresite! - Fixed: Ribbons should now appear in the Ribbons editor. - Changed: Beedrillite and Mawilite removed removed from unreleased items list (now legal). - - Changed: Batch Editor Any/All swapped position; red Type color indicates property is not present for tabs pkm. + - Changed: Batch Editor Any/All swapped position; red Type color indicates property is not present for tabs pk. 17/03/05 - New Update: (8420) [397486] - Legality: @@ -503,11 +503,11 @@ 17/02/07 - New Update: (64480) [360228] - Legality: - - Added: Legality indication for exported QR images (if legality check is available for origin). - - - Added: Legality indication for Box/Party pkm slots (^ + opt-in via Options->Set to SAV). + - - Added: Legality indication for Box/Party pk slots (^ + opt-in via Options->Set to SAV). - - Fixed: More edge cases for legality checks. - Added: More Generation 7 trainer stat record labels. Thanks skywalker25 & Holla! - Added: G7TID -> TID/SID generator for Generation 7 Trainer Editor. Thanks RoC! - - Added: QR exporting for non gen6/7 pkm. Only use is for sharing via image rather than file. + - Added: QR exporting for non gen6/7 pk. Only use is for sharing via image rather than file. - Fixed: Transferred markings are now applied correctly. - Fixed: Gen 2 inventory items TMHM cap raised from 1 to 99. - Fixed: Importing japanese pk1 files now transfer to generation 7 without errors. @@ -601,7 +601,7 @@ - Changed: Gen 2 filenames now save with shiny & forme indication text. - Fixed: ShowdownSet importing Type: Null with an item now imports correctly. - Fixed: Setting a traded egg now behaves properly (Gen6/7). - - Fixed: Bulk importing now transfers pkm files correctly. + - Fixed: Bulk importing now transfers pk files correctly. - Fixed: Species that have formes with different gender ratios (ie Pikachu cap) now use the correct ratio. - Fixed: GameSync ID now displays correctly (Gen7). - Fixed: Trainer Editor TSV text repitition in tooltip. @@ -683,7 +683,7 @@ 16/08/07 - New Update: (38200) - Added: TWLSaveTool folder auto-detection for past generation save files. - Changed: Save file auto-detection now detects the last saved file instead of a predefined order. Saving a NDS game after 3DS game will return the NDS save. - - Fixed: Multiple gen3/4 save/pkm/transfer bugs. Thanks BeyondTheHorizon, JHorbach, Destinyy, MichiS97, ashrobb, IamAVeryNicePereson & javier_himura! + - Fixed: Multiple gen3/4 save/pk/transfer bugs. Thanks BeyondTheHorizon, JHorbach, Destinyy, MichiS97, ashrobb, IamAVeryNicePereson & javier_himura! - Fixed: Manaphy egg (non event) now correctly flagged as illegal. Thanks RustInPeace! - Changed: Updated Spanish Translation. Thanks ajtudela! - Fixed: Drag&Drop between box slots tweaked and improved. Thanks Warsen! @@ -714,7 +714,7 @@ - Added: Pokémon Link block injector (like Wonder Cards). Thanks suloku! - Added: Clear Box with Alt-Click on Box Tab. Clear ALL Boxes with Alt-Shift-Click. - Added: Sort Box with Ctrl-Click on Box Tab. Sort ALL Boxes with Ctrl-Shift-Click. Sorts by Species#. - - Added: RSE/FRLG/DPPt/HGSS/BW/B2W2 Save Editing Support. Can read, modify, and export save files and pkm files. + - Added: RSE/FRLG/DPPt/HGSS/BW/B2W2 Save Editing Support. Can read, modify, and export save files and pk files. - Added: Trainer Info editor for Generation 3-5. - Changed: Many internal routines to handle multiple generations. - Changed: Mystery Gifts (Wonder Cards) Editor revised to handle multiple generations. @@ -770,7 +770,7 @@ - Changed: Clicking a menu checkbox (Set to Dex or Database Legality Filter) will stay open for more option toggling. - Changed: Clicking the relearn move groupbox will now suggest moves instead of copying current moves (illegal). - Fixed: Bad Egg background now clears when the slot is overwritten. - - Fixed: Dropping a past gen pkm file to a box slot will now have the sprite appear. Thanks poutros! + - Fixed: Dropping a past gen pk file to a box slot will now have the sprite appear. Thanks poutros! - Fixed: Nickname legality check severity. Thanks Eskuero! - Fixed: 5->6 transfer decapitalization. Thanks rinnegan! - Fixed: 3->4 Unown form is now retained. Thanks rinnegan! @@ -1064,7 +1064,7 @@ - Added: Unicode Gender text toggle in Options. - Fixed: Shiny Star will now appear in boxes. - Fixed: Gendered formes will now appear properly in boxes. - - Fixed: verifychecksum input length error when past gen pkm were imported. + - Fixed: verifychecksum input length error when past gen pk were imported. - Fixed: Bad egg setting via first slot drag (very uncommon, thanks Slashmolder for finding this!) - Improved: Bad egg detection for zeroed data in actual saves. - Changed: Secret Base Flag read offset to use the PSS stat 0x140 one, should be correct. Previous offset was the flag counter on QR code creation. @@ -1223,7 +1223,7 @@ 14/07/28 - New Update:(2): (350) - Added: Code Import to load Cyber Gadget Party/Box codes posted online. - [SAV Tabs] -> [Tools] -> [Import PK6 from Code] - - Added: Import of 3rd & 4th Gen PKM Files (.3gpkm/.pkm); PKHeX now does 3/4/5->6. + - Added: Import of 3rd & 4th Gen PKM Files (.3gpkm/.pk); PKHeX now does 3/4/5->6. - Functionality is from my Time Machine transfer tool ~ "pk2pk". - Changed: Import of past gen files will use the OT Data of the save file, if there is one loaded. diff --git a/PKHeX.WinForms/Resources/text/changelog_2019.txt b/PKHeX.WinForms/Resources/text/changelog_2019.txt index 7e807648b..189e7d719 100644 --- a/PKHeX.WinForms/Resources/text/changelog_2019.txt +++ b/PKHeX.WinForms/Resources/text/changelog_2019.txt @@ -143,7 +143,7 @@ - - Changed: Updated GO shiny banlist. - - Fixed: Silvally pledge moves are now restricted correctly. - - Fixed: Ribbon Missing/Invalid was incorrectly swapped, now indicates correct legality message. - - Added: New setting can skip the "Legal!" popup if the tab pkm is legal. Sound effect still plays. + - Added: New setting can skip the "Legal!" popup if the tab pk is legal. Sound effect still plays. - Added: Program Color settings can now be manipulated via settings. - Added: Box Sort by IEncounterable type. - Added: Box Delete of extra clones. @@ -180,7 +180,7 @@ - Fixed: Gen4 Pokédex Give All no longer causes an error. Thanks Nasz, Deadbolt! - Fixed: LGPE Box Sorting now no longer behaves incorrectly. Thanks @Ninjistix! - Fixed: LGPE pk7 setting to save file now no longer detects handling trainer incorrectly (resetting friendship). - - Fixed: XK3 pkm with two abilities no longer read incorrectly. Thanks TORNADO! + - Fixed: XK3 pk with two abilities no longer read incorrectly. Thanks TORNADO! - Fixed: Loading boxes now detects overriden settings correctly. Thanks @bpxhmemcpy! 19/01/02 - New Update: (27034) [1582970] @@ -205,7 +205,7 @@ - Fixed: LGPE saves no longer clamp fossils to count=1. Thanks @TheOGMew3! - Fixed: B/W Volcarona Event Flag now points to the correct flag ID. Thanks @Kxcii! - Changed: Encounter Database/Browser can now filter for certain encounter types, and different versions. - - Changed: Importing pkms from a folder/dump no longer overwrite slots that already have pkm data. + - Changed: Importing pkms from a folder/dump no longer overwrite slots that already have pk data. - Changed: SAV4 version detect speed improved by removing checksum checks in favor of block fingerprint detection. - Changed: Updated French Translation. Thanks @Ntonio36! - Changed: Updated Spanish Translation. Thanks @XxPhoenix1996xX! diff --git a/PKHeX.WinForms/Subforms/KChart.Designer.cs b/PKHeX.WinForms/Subforms/KChart.Designer.cs index 6d0b57080..82084ecda 100644 --- a/PKHeX.WinForms/Subforms/KChart.Designer.cs +++ b/PKHeX.WinForms/Subforms/KChart.Designer.cs @@ -250,4 +250,4 @@ private void InitializeComponent() private System.Windows.Forms.DataGridViewTextBoxColumn Ability1; private System.Windows.Forms.DataGridViewTextBoxColumn AbilityH; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/KChart.cs b/PKHeX.WinForms/Subforms/KChart.cs index 991abff93..dd3b824d8 100644 --- a/PKHeX.WinForms/Subforms/KChart.cs +++ b/PKHeX.WinForms/Subforms/KChart.cs @@ -7,92 +7,91 @@ using PKHeX.Drawing.Misc; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class KChart : Form { - public partial class KChart : Form + private readonly SaveFile SAV; + private readonly string[] species = GameInfo.Strings.specieslist; + private readonly string[] abilities = GameInfo.Strings.abilitylist; + private readonly int[] baseForm; + private readonly int[] formVal; + + public KChart(SaveFile sav) { - private readonly SaveFile SAV; - private readonly string[] species = GameInfo.Strings.specieslist; - private readonly string[] abilities = GameInfo.Strings.abilitylist; - private readonly int[] baseForm; - private readonly int[] formVal; + InitializeComponent(); + Icon = Properties.Resources.Icon; + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + SAV = sav; - public KChart(SaveFile sav) - { - InitializeComponent(); - Icon = Properties.Resources.Icon; - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - SAV = sav; + Array.Resize(ref species, SAV.Personal.TableLength); - Array.Resize(ref species, SAV.Personal.TableLength); + var forms = SAV.Personal.GetFormList(species, SAV.MaxSpeciesID); + species = SAV.Personal.GetPersonalEntryList(forms, species, SAV.MaxSpeciesID, out baseForm, out formVal); - var forms = SAV.Personal.GetFormList(species, SAV.MaxSpeciesID); - species = SAV.Personal.GetPersonalEntryList(forms, species, SAV.MaxSpeciesID, out baseForm, out formVal); + DGV.Rows.Clear(); + for (int i = 1; i < species.Length; i++) + PopEntry(i); - DGV.Rows.Clear(); - for (int i = 1; i < species.Length; i++) - PopEntry(i); + DGV.DoubleBuffered(true); - DGV.DoubleBuffered(true); - - DGV.Sort(DGV.Columns[0], ListSortDirection.Ascending); - } - - private void PopEntry(int index) - { - var p = SAV.Personal[index]; - if (p.HP == 0) - return; - - int s = index > SAV.MaxSpeciesID ? baseForm[index] : index; - var f = index <= SAV.MaxSpeciesID ? 0 : formVal[index]; - - var row = new DataGridViewRow(); - row.CreateCells(DGV); - - int r = 0; - row.Cells[r++].Value = s.ToString("000") + (f > 0 ? $"-{f:00}" : ""); - row.Cells[r++].Value = SpriteUtil.GetSprite(s, f, 0, 0, 0, false, false, SAV.Generation); - row.Cells[r++].Value = species[index]; - row.Cells[r++].Value = GetIsNative(p, s); - row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStatTotal(p.BST); - row.Cells[r++].Value = p.BST.ToString("000"); - row.Cells[r++].Value = p.CatchRate.ToString("000"); - row.Cells[r++].Value = TypeSpriteUtil.GetTypeSprite(p.Type1, SAV.Generation); - row.Cells[r++].Value = p.Type1 == p.Type2 ? SpriteUtil.Spriter.Transparent : TypeSpriteUtil.GetTypeSprite(p.Type2, SAV.Generation); - row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.HP); - row.Cells[r++].Value = p.HP.ToString("000"); - row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.ATK); - row.Cells[r++].Value = p.ATK.ToString("000"); - row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.DEF); - row.Cells[r++].Value = p.DEF.ToString("000"); - row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.SPA); - row.Cells[r++].Value = p.SPA.ToString("000"); - row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.SPD); - row.Cells[r++].Value = p.SPD.ToString("000"); - row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.SPE); - row.Cells[r++].Value = p.SPE.ToString("000"); - var abils = p.Abilities; - row.Cells[r++].Value = GetAbility(abils, 0); - row.Cells[r++].Value = GetAbility(abils, 1); - row.Cells[r].Value = GetAbility(abils, 2); - row.Height = SpriteUtil.Spriter.Height + 1; - DGV.Rows.Add(row); - } - - private string GetAbility(IReadOnlyList abilityIDs, int index) - { - if ((uint)index >= abilityIDs.Count) - return abilities[0]; - return abilities[abilityIDs[index]]; - } - - private static bool GetIsNative(PersonalInfo personalInfo, int s) => personalInfo switch - { - PersonalInfoSM => s > 721 || Legal.PastGenAlolanNatives.Contains(s), - PersonalInfoSWSH ss => ss.IsInDex, - PersonalInfoBDSP bs => bs.IsInDex, - _ => true, - }; + DGV.Sort(DGV.Columns[0], ListSortDirection.Ascending); } + + private void PopEntry(int index) + { + var p = SAV.Personal[index]; + if (p.HP == 0) + return; + + int s = index > SAV.MaxSpeciesID ? baseForm[index] : index; + var f = index <= SAV.MaxSpeciesID ? 0 : formVal[index]; + + var row = new DataGridViewRow(); + row.CreateCells(DGV); + + int r = 0; + row.Cells[r++].Value = s.ToString("000") + (f > 0 ? $"-{f:00}" : ""); + row.Cells[r++].Value = SpriteUtil.GetSprite(s, f, 0, 0, 0, false, false, SAV.Generation); + row.Cells[r++].Value = species[index]; + row.Cells[r++].Value = GetIsNative(p, s); + row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStatTotal(p.BST); + row.Cells[r++].Value = p.BST.ToString("000"); + row.Cells[r++].Value = p.CatchRate.ToString("000"); + row.Cells[r++].Value = TypeSpriteUtil.GetTypeSprite(p.Type1, SAV.Generation); + row.Cells[r++].Value = p.Type1 == p.Type2 ? SpriteUtil.Spriter.Transparent : TypeSpriteUtil.GetTypeSprite(p.Type2, SAV.Generation); + row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.HP); + row.Cells[r++].Value = p.HP.ToString("000"); + row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.ATK); + row.Cells[r++].Value = p.ATK.ToString("000"); + row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.DEF); + row.Cells[r++].Value = p.DEF.ToString("000"); + row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.SPA); + row.Cells[r++].Value = p.SPA.ToString("000"); + row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.SPD); + row.Cells[r++].Value = p.SPD.ToString("000"); + row.Cells[r].Style.BackColor = ColorUtil.ColorBaseStat(p.SPE); + row.Cells[r++].Value = p.SPE.ToString("000"); + var abils = p.Abilities; + row.Cells[r++].Value = GetAbility(abils, 0); + row.Cells[r++].Value = GetAbility(abils, 1); + row.Cells[r].Value = GetAbility(abils, 2); + row.Height = SpriteUtil.Spriter.Height + 1; + DGV.Rows.Add(row); + } + + private string GetAbility(IReadOnlyList abilityIDs, int index) + { + if ((uint)index >= abilityIDs.Count) + return abilities[0]; + return abilities[abilityIDs[index]]; + } + + private static bool GetIsNative(PersonalInfo personalInfo, int s) => personalInfo switch + { + PersonalInfoSM => s > 721 || Legal.PastGenAlolanNatives.Contains(s), + PersonalInfoSWSH ss => ss.IsInDex, + PersonalInfoBDSP bs => bs.IsInDex, + _ => true, + }; } diff --git a/PKHeX.WinForms/Subforms/Misc/EntitySummaryImage.cs b/PKHeX.WinForms/Subforms/Misc/EntitySummaryImage.cs index df4b11540..1a93b8c27 100644 --- a/PKHeX.WinForms/Subforms/Misc/EntitySummaryImage.cs +++ b/PKHeX.WinForms/Subforms/Misc/EntitySummaryImage.cs @@ -2,19 +2,18 @@ using PKHeX.Core; using PKHeX.Drawing.PokeSprite; -namespace PKHeX.WinForms -{ - /// - /// Bind-able summary object that can fetch sprite and strings that summarize a . - /// - public sealed class EntitySummaryImage : EntitySummary - { - public Image Sprite => pkm.Sprite(); - public override string Position { get; } +namespace PKHeX.WinForms; - public EntitySummaryImage(PKM p, GameStrings strings, string position) : base(p, strings) - { - Position = position; - } +/// +/// Bind-able summary object that can fetch sprite and strings that summarize a . +/// +public sealed class EntitySummaryImage : EntitySummary +{ + public Image Sprite => pk.Sprite(); + public override string Position { get; } + + public EntitySummaryImage(PKM p, GameStrings strings, string position) : base(p, strings) + { + Position = position; } } diff --git a/PKHeX.WinForms/Subforms/Misc/PropertyComparer.cs b/PKHeX.WinForms/Subforms/Misc/PropertyComparer.cs index 31a8cab5f..1abcf7108 100644 --- a/PKHeX.WinForms/Subforms/Misc/PropertyComparer.cs +++ b/PKHeX.WinForms/Subforms/Misc/PropertyComparer.cs @@ -5,48 +5,47 @@ using System.Globalization; using System.Reflection; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public sealed class PropertyComparer : IComparer where T : class { - public sealed class PropertyComparer : IComparer where T : class + private readonly IComparer comparer; + private PropertyDescriptor propertyDescriptor; + private int reverse; + + public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) { - private readonly IComparer comparer; - private PropertyDescriptor propertyDescriptor; - private int reverse; - - public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) - { - propertyDescriptor = property; - Type comparerForPropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType); - var ci = comparerForPropertyType.InvokeMember("Default", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public, null, null, null); - comparer = ci == null ? new Comparer(CultureInfo.InvariantCulture) : (IComparer) ci; - SetListSortDirection(direction); - } - - #region IComparer Members - - public int Compare(T? x, T? y) - { - if (x == null) throw new ArgumentNullException(nameof(x)); - if (y == null) throw new ArgumentNullException(nameof(y)); - return reverse * comparer.Compare(propertyDescriptor.GetValue(x), propertyDescriptor.GetValue(y)); - } - - #endregion - - private void SetPropertyDescriptor(PropertyDescriptor descriptor) - { - propertyDescriptor = descriptor; - } - - private void SetListSortDirection(ListSortDirection direction) - { - reverse = direction == ListSortDirection.Ascending ? 1 : -1; - } - - public void SetPropertyAndDirection(PropertyDescriptor descriptor, ListSortDirection direction) - { - SetPropertyDescriptor(descriptor); - SetListSortDirection(direction); - } + propertyDescriptor = property; + Type comparerForPropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType); + var ci = comparerForPropertyType.InvokeMember("Default", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public, null, null, null); + comparer = ci == null ? new Comparer(CultureInfo.InvariantCulture) : (IComparer) ci; + SetListSortDirection(direction); } -} \ No newline at end of file + + #region IComparer Members + + public int Compare(T? x, T? y) + { + if (x == null) throw new ArgumentNullException(nameof(x)); + if (y == null) throw new ArgumentNullException(nameof(y)); + return reverse * comparer.Compare(propertyDescriptor.GetValue(x), propertyDescriptor.GetValue(y)); + } + + #endregion + + private void SetPropertyDescriptor(PropertyDescriptor descriptor) + { + propertyDescriptor = descriptor; + } + + private void SetListSortDirection(ListSortDirection direction) + { + reverse = direction == ListSortDirection.Ascending ? 1 : -1; + } + + public void SetPropertyAndDirection(PropertyDescriptor descriptor, ListSortDirection direction) + { + SetPropertyDescriptor(descriptor); + SetListSortDirection(direction); + } +} diff --git a/PKHeX.WinForms/Subforms/Misc/SortableBindingList.cs b/PKHeX.WinForms/Subforms/Misc/SortableBindingList.cs index 843a4f009..263d63a71 100644 --- a/PKHeX.WinForms/Subforms/Misc/SortableBindingList.cs +++ b/PKHeX.WinForms/Subforms/Misc/SortableBindingList.cs @@ -2,72 +2,71 @@ using System.Collections.Generic; using System.ComponentModel; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public abstract class SortableBindingList : BindingList where T : class { - public abstract class SortableBindingList : BindingList where T : class + private readonly Dictionary> comparers; + private bool isSorted; + private ListSortDirection listSortDirection; + private PropertyDescriptor? propertyDescriptor; + + protected SortableBindingList() : base(new List()) { - private readonly Dictionary> comparers; - private bool isSorted; - private ListSortDirection listSortDirection; - private PropertyDescriptor? propertyDescriptor; - - protected SortableBindingList() : base(new List()) - { - comparers = new Dictionary>(); - } - - protected override bool SupportsSortingCore => true; - - protected override bool IsSortedCore => isSorted; - - protected override PropertyDescriptor? SortPropertyCore => propertyDescriptor; - - protected override ListSortDirection SortDirectionCore => listSortDirection; - - protected override bool SupportsSearchingCore => true; - - protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction) - { - List itemsList = (List)Items; - - Type propertyType = prop.PropertyType; - if (!comparers.TryGetValue(propertyType, out var comparer)) - { - comparer = new PropertyComparer(prop, direction); - comparers.Add(propertyType, comparer); - } - - comparer.SetPropertyAndDirection(prop, direction); - itemsList.Sort(comparer); - - propertyDescriptor = prop; - listSortDirection = direction; - isSorted = true; - - OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); - } - - protected override void RemoveSortCore() - { - isSorted = false; - propertyDescriptor = base.SortPropertyCore; - listSortDirection = base.SortDirectionCore; - - OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); - } - - protected override int FindCore(PropertyDescriptor prop, object key) - { - int count = Count; - for (int i = 0; i < count; ++i) - { - var obj = this[i]; - var value = prop.GetValue(obj); - if (value?.Equals(key) == true) - return i; - } - - return -1; - } + comparers = new Dictionary>(); } -} \ No newline at end of file + + protected override bool SupportsSortingCore => true; + + protected override bool IsSortedCore => isSorted; + + protected override PropertyDescriptor? SortPropertyCore => propertyDescriptor; + + protected override ListSortDirection SortDirectionCore => listSortDirection; + + protected override bool SupportsSearchingCore => true; + + protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction) + { + List itemsList = (List)Items; + + Type propertyType = prop.PropertyType; + if (!comparers.TryGetValue(propertyType, out var comparer)) + { + comparer = new PropertyComparer(prop, direction); + comparers.Add(propertyType, comparer); + } + + comparer.SetPropertyAndDirection(prop, direction); + itemsList.Sort(comparer); + + propertyDescriptor = prop; + listSortDirection = direction; + isSorted = true; + + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + + protected override void RemoveSortCore() + { + isSorted = false; + propertyDescriptor = base.SortPropertyCore; + listSortDirection = base.SortDirectionCore; + + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + + protected override int FindCore(PropertyDescriptor prop, object key) + { + int count = Count; + for (int i = 0; i < count; ++i) + { + var obj = this[i]; + var value = prop.GetValue(obj); + if (value?.Equals(key) == true) + return i; + } + + return -1; + } +} diff --git a/PKHeX.WinForms/Subforms/PKM Editors/BatchEditor.Designer.cs b/PKHeX.WinForms/Subforms/PKM Editors/BatchEditor.Designer.cs index 8a7ae9760..8bbd19463 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/BatchEditor.Designer.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/BatchEditor.Designer.cs @@ -271,4 +271,4 @@ private void InitializeComponent() private System.Windows.Forms.ToolTip toolTip2; private System.Windows.Forms.ToolTip toolTip3; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/PKM Editors/BatchEditor.cs b/PKHeX.WinForms/Subforms/PKM Editors/BatchEditor.cs index f05e1f611..c58377cfa 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/BatchEditor.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/BatchEditor.cs @@ -7,306 +7,305 @@ using PKHeX.Core; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class BatchEditor : Form { - public partial class BatchEditor : Form + private readonly SaveFile SAV; + private readonly PKM Entity; + private int currentFormat = -1; + + public BatchEditor(PKM pk, SaveFile sav) { - private readonly SaveFile SAV; - private readonly PKM pkm; - private int currentFormat = -1; + InitializeComponent(); + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + Entity = pk; + SAV = sav; + DragDrop += TabMain_DragDrop; + DragEnter += TabMain_DragEnter; - public BatchEditor(PKM pk, SaveFile sav) + CB_Format.Items.Clear(); + CB_Format.Items.Add(MsgAny); + foreach (Type t in BatchEditing.Types) + CB_Format.Items.Add(t.Name.ToLowerInvariant()); + CB_Format.Items.Add(MsgAll); + + CB_Format.SelectedIndex = CB_Require.SelectedIndex = 0; + toolTip1.SetToolTip(CB_Property, MsgBEToolTipPropName); + toolTip2.SetToolTip(L_PropType, MsgBEToolTipPropType); + toolTip3.SetToolTip(L_PropValue, MsgBEToolTipPropValue); + } + + private void B_Open_Click(object sender, EventArgs e) + { + if (!B_Go.Enabled) return; + using var fbd = new FolderBrowserDialog(); + if (fbd.ShowDialog() != DialogResult.OK) + return; + + TB_Folder.Text = fbd.SelectedPath; + TB_Folder.Visible = true; + } + + private void B_SAV_Click(object sender, EventArgs e) + { + TB_Folder.Text = string.Empty; + TB_Folder.Visible = false; + } + + private void B_Go_Click(object sender, EventArgs e) + { + RunBackgroundWorker(); + } + + private void B_Add_Click(object sender, EventArgs e) + { + if (CB_Property.SelectedIndex < 0) + { WinFormsUtil.Alert(MsgBEPropertyInvalid); return; } + + var prefix = StringInstruction.Prefixes; + string s = prefix[CB_Require.SelectedIndex] + CB_Property.Items[CB_Property.SelectedIndex].ToString() + StringInstruction.SplitInstruction; + if (RTB_Instructions.Lines.Length != 0 && RTB_Instructions.Lines[^1].Length > 0) + s = Environment.NewLine + s; + + RTB_Instructions.AppendText(s); + } + + private void CB_Format_SelectedIndexChanged(object sender, EventArgs e) + { + if (currentFormat == CB_Format.SelectedIndex) + return; + + int format = CB_Format.SelectedIndex; + CB_Property.Items.Clear(); + CB_Property.Items.AddRange(BatchEditing.Properties[format]); + CB_Property.SelectedIndex = 0; + currentFormat = format; + } + + private void CB_Property_SelectedIndexChanged(object sender, EventArgs e) + { + L_PropType.Text = BatchEditing.GetPropertyType(CB_Property.Text, CB_Format.SelectedIndex); + if (BatchEditing.TryGetHasProperty(Entity, CB_Property.Text, out var pi)) { - InitializeComponent(); - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - pkm = pk; - SAV = sav; - DragDrop += TabMain_DragDrop; - DragEnter += TabMain_DragEnter; - - CB_Format.Items.Clear(); - CB_Format.Items.Add(MsgAny); - foreach (Type t in BatchEditing.Types) - CB_Format.Items.Add(t.Name.ToLowerInvariant()); - CB_Format.Items.Add(MsgAll); - - CB_Format.SelectedIndex = CB_Require.SelectedIndex = 0; - toolTip1.SetToolTip(CB_Property, MsgBEToolTipPropName); - toolTip2.SetToolTip(L_PropType, MsgBEToolTipPropType); - toolTip3.SetToolTip(L_PropValue, MsgBEToolTipPropValue); + L_PropValue.Text = pi.GetValue(Entity)?.ToString(); + L_PropType.ForeColor = L_PropValue.ForeColor; // reset color } - - private void B_Open_Click(object sender, EventArgs e) + else // no property, flag { - if (!B_Go.Enabled) return; - using var fbd = new FolderBrowserDialog(); - if (fbd.ShowDialog() != DialogResult.OK) - return; - - TB_Folder.Text = fbd.SelectedPath; - TB_Folder.Visible = true; - } - - private void B_SAV_Click(object sender, EventArgs e) - { - TB_Folder.Text = string.Empty; - TB_Folder.Visible = false; - } - - private void B_Go_Click(object sender, EventArgs e) - { - RunBackgroundWorker(); - } - - private void B_Add_Click(object sender, EventArgs e) - { - if (CB_Property.SelectedIndex < 0) - { WinFormsUtil.Alert(MsgBEPropertyInvalid); return; } - - var prefix = StringInstruction.Prefixes; - string s = prefix[CB_Require.SelectedIndex] + CB_Property.Items[CB_Property.SelectedIndex].ToString() + StringInstruction.SplitInstruction; - if (RTB_Instructions.Lines.Length != 0 && RTB_Instructions.Lines[^1].Length > 0) - s = Environment.NewLine + s; - - RTB_Instructions.AppendText(s); - } - - private void CB_Format_SelectedIndexChanged(object sender, EventArgs e) - { - if (currentFormat == CB_Format.SelectedIndex) - return; - - int format = CB_Format.SelectedIndex; - CB_Property.Items.Clear(); - CB_Property.Items.AddRange(BatchEditing.Properties[format]); - CB_Property.SelectedIndex = 0; - currentFormat = format; - } - - private void CB_Property_SelectedIndexChanged(object sender, EventArgs e) - { - L_PropType.Text = BatchEditing.GetPropertyType(CB_Property.Text, CB_Format.SelectedIndex); - if (BatchEditing.TryGetHasProperty(pkm, CB_Property.Text, out var pi)) - { - L_PropValue.Text = pi.GetValue(pkm)?.ToString(); - L_PropType.ForeColor = L_PropValue.ForeColor; // reset color - } - else // no property, flag - { - L_PropValue.Text = string.Empty; - L_PropType.ForeColor = Color.Red; - } - } - - private static void TabMain_DragEnter(object? sender, DragEventArgs? e) - { - if (e?.Data is null) - return; - if (e.Data.GetDataPresent(DataFormats.FileDrop)) - e.Effect = DragDropEffects.Copy; - } - - private void TabMain_DragDrop(object? sender, DragEventArgs? e) - { - if (e?.Data?.GetData(DataFormats.FileDrop) is not string[] {Length: not 0} files) - return; - if (!Directory.Exists(files[0])) - return; - - TB_Folder.Text = files[0]; - TB_Folder.Visible = true; - RB_Boxes.Checked = RB_Party.Checked = false; - RB_Path.Checked = true; - } - - private void RunBackgroundWorker() - { - if (RTB_Instructions.Lines.Any(line => line.Length == 0)) - { WinFormsUtil.Error(MsgBEInstructionInvalid); return; } - - var sets = StringInstructionSet.GetBatchSets(RTB_Instructions.Lines).ToArray(); - if (sets.Any(s => s.Filters.Any(z => string.IsNullOrWhiteSpace(z.PropertyValue)))) - { WinFormsUtil.Error(MsgBEFilterEmpty); return; } - - if (sets.Any(z => z.Instructions.Count == 0)) - { WinFormsUtil.Error(MsgBEInstructionNone); return; } - - var emptyVal = sets.SelectMany(s => s.Instructions.Where(z => string.IsNullOrWhiteSpace(z.PropertyValue))).ToArray(); - if (emptyVal.Length > 0) - { - string props = string.Join(", ", emptyVal.Select(z => z.PropertyName)); - string invalid = MsgBEPropertyEmpty + Environment.NewLine + props; - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, invalid, MsgContinue)) - return; - } - - string? destPath = null; - if (RB_Path.Checked) - { - WinFormsUtil.Alert(MsgExportFolder, MsgExportFolderAdvice); - using var fbd = new FolderBrowserDialog(); - var dr = fbd.ShowDialog(); - if (dr != DialogResult.OK) - return; - - destPath = fbd.SelectedPath; - } - - FLP_RB.Enabled = RTB_Instructions.Enabled = B_Go.Enabled = false; - - foreach (var set in sets) - { - BatchEditing.ScreenStrings(set.Filters); - BatchEditing.ScreenStrings(set.Instructions); - } - RunBatchEdit(sets, TB_Folder.Text, destPath); - } - - private void RunBatchEdit(StringInstructionSet[] sets, string source, string? destination) - { - editor = new Core.BatchEditor(); - bool finished = false, displayed = false; // hack cuz DoWork event isn't cleared after completion - b.DoWork += (sender, e) => - { - if (finished) - return; - if (RB_Boxes.Checked) - RunBatchEditSaveFile(sets, boxes: true); - else if (RB_Party.Checked) - RunBatchEditSaveFile(sets, party: true); - else if (destination != null) - RunBatchEditFolder(sets, source, destination); - finished = true; - }; - b.ProgressChanged += (sender, e) => SetProgressBar(e.ProgressPercentage); - b.RunWorkerCompleted += (sender, e) => - { - string result = editor.GetEditorResults(sets); - if (!displayed) WinFormsUtil.Alert(result); - displayed = true; - FLP_RB.Enabled = RTB_Instructions.Enabled = B_Go.Enabled = true; - SetupProgressBar(0); - }; - b.RunWorkerAsync(); - } - - private void RunBatchEditFolder(IReadOnlyCollection sets, string source, string destination) - { - var files = Directory.GetFiles(source, "*", SearchOption.AllDirectories); - SetupProgressBar(files.Length * sets.Count); - foreach (var set in sets) - ProcessFolder(files, destination, set.Filters, set.Instructions); - } - - private void RunBatchEditSaveFile(IReadOnlyCollection sets, bool boxes = false, bool party = false) - { - var data = new List(); - if (party) - { - SlotInfoLoader.AddPartyData(SAV, data); - process(data); - SAV.PartyData = data.ConvertAll(z => z.Entity); - } - if (boxes) - { - SlotInfoLoader.AddBoxData(SAV, data); - process(data); - SAV.BoxData = data.ConvertAll(z => z.Entity); - } - void process(IList d) - { - SetupProgressBar(d.Count * sets.Count); - foreach (var set in sets) - ProcessSAV(d, set.Filters, set.Instructions); - } - } - - // Progress Bar - private void SetupProgressBar(int count) - { - MethodInvoker mi = () => { PB_Show.Minimum = 0; PB_Show.Step = 1; PB_Show.Value = 0; PB_Show.Maximum = count; }; - if (PB_Show.InvokeRequired) - PB_Show.Invoke(mi); - else - mi.Invoke(); - } - - private void SetProgressBar(int position) - { - if (PB_Show.InvokeRequired) - PB_Show.Invoke((MethodInvoker)(() => PB_Show.Value = position)); - else - PB_Show.Value = position; - } - - // Mass Editing - private Core.BatchEditor editor = new(); - - private void ProcessSAV(IList data, IReadOnlyList Filters, IReadOnlyList Instructions) - { - if (data.Count == 0) - return; - - var filterMeta = Filters.Where(f => BatchFilters.FilterMeta.Any(z => z.IsMatch(f.PropertyName))).ToArray(); - if (filterMeta.Length != 0) - Filters = Filters.Except(filterMeta).ToArray(); - - var max = data[0].Entity.MaxSpeciesID; - - for (int i = 0; i < data.Count; i++) - { - var entry = data[i]; - var pk = data[i].Entity; - - var spec = pk.Species; - if (spec <= 0 || spec > max) - continue; - - if (entry.Source is SlotInfoBox info && SAV.GetSlotFlags(info.Box, info.Slot).IsOverwriteProtected()) - editor.AddSkipped(); - else if (!BatchEditing.IsFilterMatchMeta(filterMeta, entry)) - editor.AddSkipped(); - else - editor.Process(pk, Filters, Instructions); - - b.ReportProgress(i); - } - } - - private void ProcessFolder(IReadOnlyList files, string destDir, IReadOnlyList pkFilters, IReadOnlyList instructions) - { - var filterMeta = pkFilters.Where(f => BatchFilters.FilterMeta.Any(z => z.IsMatch(f.PropertyName))).ToArray(); - if (filterMeta.Length != 0) - pkFilters = pkFilters.Except(filterMeta).ToArray(); - - for (int i = 0; i < files.Count; i++) - { - TryProcess(files[i], destDir, filterMeta, pkFilters, instructions); - b.ReportProgress(i); - } - } - - private void TryProcess(string source, string destDir, IReadOnlyList metaFilters, IReadOnlyList pkFilters, IReadOnlyList instructions) - { - var fi = new FileInfo(source); - if (!EntityDetection.IsSizePlausible(fi.Length)) - return; - - byte[] data = File.ReadAllBytes(source); - _ = FileUtil.TryGetPKM(data, out var pk, fi.Extension, SAV); - if (pk == null) - return; - - var info = new SlotInfoFile(source); - var entry = new SlotCache(info, pk); - if (!BatchEditing.IsFilterMatchMeta(metaFilters, entry)) - { - editor.AddSkipped(); - return; - } - - if (editor.Process(pk, pkFilters, instructions)) - File.WriteAllBytes(Path.Combine(destDir, Path.GetFileName(source)), pk.DecryptedPartyData); + L_PropValue.Text = string.Empty; + L_PropType.ForeColor = Color.Red; } } + + private static void TabMain_DragEnter(object? sender, DragEventArgs? e) + { + if (e?.Data is null) + return; + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + e.Effect = DragDropEffects.Copy; + } + + private void TabMain_DragDrop(object? sender, DragEventArgs? e) + { + if (e?.Data?.GetData(DataFormats.FileDrop) is not string[] {Length: not 0} files) + return; + if (!Directory.Exists(files[0])) + return; + + TB_Folder.Text = files[0]; + TB_Folder.Visible = true; + RB_Boxes.Checked = RB_Party.Checked = false; + RB_Path.Checked = true; + } + + private void RunBackgroundWorker() + { + if (RTB_Instructions.Lines.Any(line => line.Length == 0)) + { WinFormsUtil.Error(MsgBEInstructionInvalid); return; } + + var sets = StringInstructionSet.GetBatchSets(RTB_Instructions.Lines).ToArray(); + if (sets.Any(s => s.Filters.Any(z => string.IsNullOrWhiteSpace(z.PropertyValue)))) + { WinFormsUtil.Error(MsgBEFilterEmpty); return; } + + if (sets.Any(z => z.Instructions.Count == 0)) + { WinFormsUtil.Error(MsgBEInstructionNone); return; } + + var emptyVal = sets.SelectMany(s => s.Instructions.Where(z => string.IsNullOrWhiteSpace(z.PropertyValue))).ToArray(); + if (emptyVal.Length > 0) + { + string props = string.Join(", ", emptyVal.Select(z => z.PropertyName)); + string invalid = MsgBEPropertyEmpty + Environment.NewLine + props; + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, invalid, MsgContinue)) + return; + } + + string? destPath = null; + if (RB_Path.Checked) + { + WinFormsUtil.Alert(MsgExportFolder, MsgExportFolderAdvice); + using var fbd = new FolderBrowserDialog(); + var dr = fbd.ShowDialog(); + if (dr != DialogResult.OK) + return; + + destPath = fbd.SelectedPath; + } + + FLP_RB.Enabled = RTB_Instructions.Enabled = B_Go.Enabled = false; + + foreach (var set in sets) + { + BatchEditing.ScreenStrings(set.Filters); + BatchEditing.ScreenStrings(set.Instructions); + } + RunBatchEdit(sets, TB_Folder.Text, destPath); + } + + private void RunBatchEdit(StringInstructionSet[] sets, string source, string? destination) + { + editor = new Core.BatchEditor(); + bool finished = false, displayed = false; // hack cuz DoWork event isn't cleared after completion + b.DoWork += (sender, e) => + { + if (finished) + return; + if (RB_Boxes.Checked) + RunBatchEditSaveFile(sets, boxes: true); + else if (RB_Party.Checked) + RunBatchEditSaveFile(sets, party: true); + else if (destination != null) + RunBatchEditFolder(sets, source, destination); + finished = true; + }; + b.ProgressChanged += (sender, e) => SetProgressBar(e.ProgressPercentage); + b.RunWorkerCompleted += (sender, e) => + { + string result = editor.GetEditorResults(sets); + if (!displayed) WinFormsUtil.Alert(result); + displayed = true; + FLP_RB.Enabled = RTB_Instructions.Enabled = B_Go.Enabled = true; + SetupProgressBar(0); + }; + b.RunWorkerAsync(); + } + + private void RunBatchEditFolder(IReadOnlyCollection sets, string source, string destination) + { + var files = Directory.GetFiles(source, "*", SearchOption.AllDirectories); + SetupProgressBar(files.Length * sets.Count); + foreach (var set in sets) + ProcessFolder(files, destination, set.Filters, set.Instructions); + } + + private void RunBatchEditSaveFile(IReadOnlyCollection sets, bool boxes = false, bool party = false) + { + var data = new List(); + if (party) + { + SlotInfoLoader.AddPartyData(SAV, data); + process(data); + SAV.PartyData = data.ConvertAll(z => z.Entity); + } + if (boxes) + { + SlotInfoLoader.AddBoxData(SAV, data); + process(data); + SAV.BoxData = data.ConvertAll(z => z.Entity); + } + void process(IList d) + { + SetupProgressBar(d.Count * sets.Count); + foreach (var set in sets) + ProcessSAV(d, set.Filters, set.Instructions); + } + } + + // Progress Bar + private void SetupProgressBar(int count) + { + MethodInvoker mi = () => { PB_Show.Minimum = 0; PB_Show.Step = 1; PB_Show.Value = 0; PB_Show.Maximum = count; }; + if (PB_Show.InvokeRequired) + PB_Show.Invoke(mi); + else + mi.Invoke(); + } + + private void SetProgressBar(int position) + { + if (PB_Show.InvokeRequired) + PB_Show.Invoke((MethodInvoker)(() => PB_Show.Value = position)); + else + PB_Show.Value = position; + } + + // Mass Editing + private Core.BatchEditor editor = new(); + + private void ProcessSAV(IList data, IReadOnlyList Filters, IReadOnlyList Instructions) + { + if (data.Count == 0) + return; + + var filterMeta = Filters.Where(f => BatchFilters.FilterMeta.Any(z => z.IsMatch(f.PropertyName))).ToArray(); + if (filterMeta.Length != 0) + Filters = Filters.Except(filterMeta).ToArray(); + + var max = data[0].Entity.MaxSpeciesID; + + for (int i = 0; i < data.Count; i++) + { + var entry = data[i]; + var pk = data[i].Entity; + + var spec = pk.Species; + if (spec <= 0 || spec > max) + continue; + + if (entry.Source is SlotInfoBox info && SAV.GetSlotFlags(info.Box, info.Slot).IsOverwriteProtected()) + editor.AddSkipped(); + else if (!BatchEditing.IsFilterMatchMeta(filterMeta, entry)) + editor.AddSkipped(); + else + editor.Process(pk, Filters, Instructions); + + b.ReportProgress(i); + } + } + + private void ProcessFolder(IReadOnlyList files, string destDir, IReadOnlyList pkFilters, IReadOnlyList instructions) + { + var filterMeta = pkFilters.Where(f => BatchFilters.FilterMeta.Any(z => z.IsMatch(f.PropertyName))).ToArray(); + if (filterMeta.Length != 0) + pkFilters = pkFilters.Except(filterMeta).ToArray(); + + for (int i = 0; i < files.Count; i++) + { + TryProcess(files[i], destDir, filterMeta, pkFilters, instructions); + b.ReportProgress(i); + } + } + + private void TryProcess(string source, string destDir, IReadOnlyList metaFilters, IReadOnlyList pkFilters, IReadOnlyList instructions) + { + var fi = new FileInfo(source); + if (!EntityDetection.IsSizePlausible(fi.Length)) + return; + + byte[] data = File.ReadAllBytes(source); + _ = FileUtil.TryGetPKM(data, out var pk, fi.Extension, SAV); + if (pk == null) + return; + + var info = new SlotInfoFile(source); + var entry = new SlotCache(info, pk); + if (!BatchEditing.IsFilterMatchMeta(metaFilters, entry)) + { + editor.AddSkipped(); + return; + } + + if (editor.Process(pk, pkFilters, instructions)) + File.WriteAllBytes(Path.Combine(destDir, Path.GetFileName(source)), pk.DecryptedPartyData); + } } diff --git a/PKHeX.WinForms/Subforms/PKM Editors/MemoryAmie.Designer.cs b/PKHeX.WinForms/Subforms/PKM Editors/MemoryAmie.Designer.cs index 9eba85930..2757be9e6 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/MemoryAmie.Designer.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/MemoryAmie.Designer.cs @@ -887,4 +887,4 @@ private void InitializeComponent() private System.Windows.Forms.MaskedTextBox MT_Sociability; private System.Windows.Forms.Label L_Sociability; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/PKM Editors/MemoryAmie.cs b/PKHeX.WinForms/Subforms/PKM Editors/MemoryAmie.cs index fbe4a57ce..1d3fd347d 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/MemoryAmie.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/MemoryAmie.cs @@ -2,397 +2,396 @@ using System.Windows.Forms; using PKHeX.Core; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class MemoryAmie : Form { - public partial class MemoryAmie : Form + private readonly TextMarkup TextArgs; + private readonly MemoryStrings MemStrings; + + public MemoryAmie(PKM pk) { - private readonly TextMarkup TextArgs; - private readonly MemoryStrings MemStrings; + InitializeComponent(); + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + Entity = pk; + MemStrings = new MemoryStrings(GameInfo.Strings); + PrevCountries = new[] { CB_Country0, CB_Country1, CB_Country2, CB_Country3, CB_Country4 }; + PrevRegions = new[] { CB_Region0, CB_Region1, CB_Region2, CB_Region3, CB_Region4 }; + string[] arguments = L_Arguments.Text.Split(new[] {" ; "}, StringSplitOptions.None); - public MemoryAmie(PKM pk) + TextArgs = new TextMarkup(arguments); + foreach (ComboBox comboBox in PrevCountries) { - InitializeComponent(); - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - pkm = pk; - MemStrings = new MemoryStrings(GameInfo.Strings); - PrevCountries = new[] { CB_Country0, CB_Country1, CB_Country2, CB_Country3, CB_Country4 }; - PrevRegions = new[] { CB_Region0, CB_Region1, CB_Region2, CB_Region3, CB_Region4 }; - string[] arguments = L_Arguments.Text.Split(new[] {" ; "}, StringSplitOptions.None); + comboBox.InitializeBinding(); + Main.SetCountrySubRegion(comboBox, "countries"); + } + foreach (var region in PrevRegions) + region.InitializeBinding(); + GetLangStrings(); + LoadFields(); - TextArgs = new TextMarkup(arguments); - foreach (ComboBox comboBox in PrevCountries) - { - comboBox.InitializeBinding(); - Main.SetCountrySubRegion(comboBox, "countries"); - } - foreach (var region in PrevRegions) - region.InitializeBinding(); - GetLangStrings(); - LoadFields(); + if (pk is not IGeoTrack) + tabControl1.TabPages.Remove(Tab_Residence); + } - if (pkm is not IGeoTrack) - tabControl1.TabPages.Remove(Tab_Residence); + private bool init; + private readonly ComboBox[] PrevCountries; + private readonly ComboBox[] PrevRegions; + private readonly PKM Entity; + + // Load/Save Actions + private void LoadFields() + { + // Load the region/country values. + if (Entity is IGeoTrack g) + { + CB_Country0.SelectedValue = (int)g.Geo1_Country; + CB_Country1.SelectedValue = (int)g.Geo2_Country; + CB_Country2.SelectedValue = (int)g.Geo3_Country; + CB_Country3.SelectedValue = (int)g.Geo4_Country; + CB_Country4.SelectedValue = (int)g.Geo5_Country; + CB_Region0.SelectedValue = (int)g.Geo1_Region; + CB_Region1.SelectedValue = (int)g.Geo2_Region; + CB_Region2.SelectedValue = (int)g.Geo3_Region; + CB_Region3.SelectedValue = (int)g.Geo4_Region; + CB_Region4.SelectedValue = (int)g.Geo5_Region; } - private bool init; - private readonly ComboBox[] PrevCountries; - private readonly ComboBox[] PrevRegions; - private readonly PKM pkm; + // Load the Fullness, and Enjoyment + M_Fullness.Text = Entity.Fullness.ToString(); + M_Enjoyment.Text = Entity.Enjoyment.ToString(); - // Load/Save Actions - private void LoadFields() + M_OT_Friendship.Text = Entity.OT_Friendship.ToString(); + M_CT_Friendship.Text = Entity.HT_Friendship.ToString(); + + if (Entity is IAffection a) { - // Load the region/country values. - if (pkm is IGeoTrack g) - { - CB_Country0.SelectedValue = (int)g.Geo1_Country; - CB_Country1.SelectedValue = (int)g.Geo2_Country; - CB_Country2.SelectedValue = (int)g.Geo3_Country; - CB_Country3.SelectedValue = (int)g.Geo4_Country; - CB_Country4.SelectedValue = (int)g.Geo5_Country; - CB_Region0.SelectedValue = (int)g.Geo1_Region; - CB_Region1.SelectedValue = (int)g.Geo2_Region; - CB_Region2.SelectedValue = (int)g.Geo3_Region; - CB_Region3.SelectedValue = (int)g.Geo4_Region; - CB_Region4.SelectedValue = (int)g.Geo5_Region; - } + M_OT_Affection.Text = a.OT_Affection.ToString(); + M_CT_Affection.Text = a.HT_Affection.ToString(); + } - // Load the Fullness, and Enjoyment - M_Fullness.Text = pkm.Fullness.ToString(); - M_Enjoyment.Text = pkm.Enjoyment.ToString(); + if (Entity is ISociability s) + MT_Sociability.Text = Math.Min(byte.MaxValue, s.Sociability).ToString(); - M_OT_Friendship.Text = pkm.OT_Friendship.ToString(); - M_CT_Friendship.Text = pkm.HT_Friendship.ToString(); + if (Entity is ITrainerMemories m) + { + // Load the OT Memories + CB_OTQual.SelectedIndex = Math.Max(0, m.OT_Intensity - 1); + CB_OTMemory.SelectedValue = (int)m.OT_Memory; + CB_OTVar.SelectedValue = (int)m.OT_TextVar; + CB_OTFeel.SelectedIndex = m.OT_Feeling; - if (pkm is IAffection a) - { - M_OT_Affection.Text = a.OT_Affection.ToString(); - M_CT_Affection.Text = a.HT_Affection.ToString(); - } + // Load the HT Memories + CB_CTQual.SelectedIndex = Math.Max(0, m.HT_Intensity - 1); + CB_CTMemory.SelectedValue = (int)m.HT_Memory; + CB_CTVar.SelectedValue = (int)m.HT_TextVar; + CB_CTFeel.SelectedIndex = m.HT_Feeling; + } - if (pkm is ISociability s) - MT_Sociability.Text = Math.Min(byte.MaxValue, s.Sociability).ToString(); + CB_Handler.Items.Clear(); + CB_Handler.Items.Add($"{Entity.OT_Name} ({TextArgs.OT})"); // OTNAME : OT - if (pkm is ITrainerMemories m) - { - // Load the OT Memories - CB_OTQual.SelectedIndex = Math.Max(0, m.OT_Intensity - 1); - CB_OTMemory.SelectedValue = (int)m.OT_Memory; - CB_OTVar.SelectedValue = (int)m.OT_TextVar; - CB_OTFeel.SelectedIndex = m.OT_Feeling; + if (!string.IsNullOrEmpty(Entity.HT_Name)) + CB_Handler.Items.Add(Entity.HT_Name); + else + Entity.CurrentHandler = 0; - // Load the HT Memories - CB_CTQual.SelectedIndex = Math.Max(0, m.HT_Intensity - 1); - CB_CTMemory.SelectedValue = (int)m.HT_Memory; - CB_CTVar.SelectedValue = (int)m.HT_TextVar; - CB_CTFeel.SelectedIndex = m.HT_Feeling; - } + tabControl1.SelectedIndex = CB_Handler.SelectedIndex = Entity.CurrentHandler; - CB_Handler.Items.Clear(); - CB_Handler.Items.Add($"{pkm.OT_Name} ({TextArgs.OT})"); // OTNAME : OT - - if (!string.IsNullOrEmpty(pkm.HT_Name)) - CB_Handler.Items.Add(pkm.HT_Name); - else - pkm.CurrentHandler = 0; - - tabControl1.SelectedIndex = CB_Handler.SelectedIndex = pkm.CurrentHandler; - - GB_M_OT.Enabled = GB_M_CT.Enabled = GB_Residence.Enabled = + GB_M_OT.Enabled = GB_M_CT.Enabled = GB_Residence.Enabled = BTN_Save.Enabled = M_Fullness.Enabled = M_Enjoyment.Enabled = - L_Sociability.Enabled = MT_Sociability.Enabled = - L_Fullness.Enabled = L_Enjoyment.Enabled = !(pkm.IsEgg && pkm.IsUntraded && pkm.HT_Friendship == 0); + L_Sociability.Enabled = MT_Sociability.Enabled = + L_Fullness.Enabled = L_Enjoyment.Enabled = !(Entity.IsEgg && Entity.IsUntraded && Entity.HT_Friendship == 0); - if (!pkm.IsEgg) + if (!Entity.IsEgg) + { + bool enable; + if (Entity.Generation < 6) { - bool enable; - if (pkm.Generation < 6) + // Previous Generation Mon + GB_M_OT.Text = $"{TextArgs.PastGen} {Entity.OT_Name}: {TextArgs.OT}"; // Past Gen OT : OTNAME + GB_M_CT.Text = $"{TextArgs.MemoriesWith} {Entity.HT_Name}"; // Memories with : HTNAME + enable = false; + // Reset to no memory -- don't reset affection as ORAS can raise it + CB_OTQual.SelectedIndex = CB_OTFeel.SelectedIndex = 0; + CB_OTVar.SelectedValue = CB_OTMemory.SelectedValue = 0; + } + else + { + enable = true; + GB_M_OT.Text = $"{TextArgs.MemoriesWith} {Entity.OT_Name} ({TextArgs.OT})"; // Memories with : OTNAME + GB_M_CT.Text = $"{TextArgs.MemoriesWith} {Entity.HT_Name}"; // Memories with : HTNAME + if (Entity.HT_Name.Length == 0) { - // Previous Generation Mon - GB_M_OT.Text = $"{TextArgs.PastGen} {pkm.OT_Name}: {TextArgs.OT}"; // Past Gen OT : OTNAME - GB_M_CT.Text = $"{TextArgs.MemoriesWith} {pkm.HT_Name}"; // Memories with : HTNAME - enable = false; - // Reset to no memory -- don't reset affection as ORAS can raise it - CB_OTQual.SelectedIndex = CB_OTFeel.SelectedIndex = 0; - CB_OTVar.SelectedValue = CB_OTMemory.SelectedValue = 0; + CB_Country1.Enabled = CB_Country2.Enabled = CB_Country3.Enabled = CB_Country4.Enabled = + CB_Region1.Enabled = CB_Region2.Enabled = CB_Region3.Enabled = CB_Region4.Enabled = + GB_M_CT.Enabled = false; + GB_M_CT.Text = $"{TextArgs.NeverLeft} {TextArgs.OT} - {TextArgs.Disabled}"; // Never Left : OT : Disabled } else { - enable = true; - GB_M_OT.Text = $"{TextArgs.MemoriesWith} {pkm.OT_Name} ({TextArgs.OT})"; // Memories with : OTNAME - GB_M_CT.Text = $"{TextArgs.MemoriesWith} {pkm.HT_Name}"; // Memories with : HTNAME - if (pkm.HT_Name.Length == 0) - { - CB_Country1.Enabled = CB_Country2.Enabled = CB_Country3.Enabled = CB_Country4.Enabled = - CB_Region1.Enabled = CB_Region2.Enabled = CB_Region3.Enabled = CB_Region4.Enabled = - GB_M_CT.Enabled = false; - GB_M_CT.Text = $"{TextArgs.NeverLeft} {TextArgs.OT} - {TextArgs.Disabled}"; // Never Left : OT : Disabled - } - else - { - GB_M_CT.Text = $"{TextArgs.MemoriesWith} {pkm.HT_Name}"; - } + GB_M_CT.Text = $"{TextArgs.MemoriesWith} {Entity.HT_Name}"; } - RTB_OT.Visible = CB_OTQual.Enabled = CB_OTMemory.Enabled = CB_OTFeel.Enabled = CB_OTVar.Enabled = enable; - M_OT_Affection.Enabled = true; } - else - { - GB_M_OT.Text = GB_M_CT.Text = $"N/A: {GameInfo.Strings.EggName}"; - } - - init = true; - - // Manually load the Memory Parse - RTB_CT.Text = GetMemoryString(CB_CTMemory, CB_CTVar, CB_CTQual, CB_CTFeel, pkm.HT_Name); - RTB_OT.Text = GetMemoryString(CB_OTMemory, CB_OTVar, CB_OTQual, CB_OTFeel, pkm.OT_Name); - - // Affection no longer stored in gen8+, so only show in gen6/7. - L_OT_Affection.Visible = L_CT_Affection.Visible = M_OT_Affection.Visible = M_CT_Affection.Visible = pkm.Format <= 7; - L_Sociability.Visible = MT_Sociability.Visible = pkm.Format >= 8; + RTB_OT.Visible = CB_OTQual.Enabled = CB_OTMemory.Enabled = CB_OTFeel.Enabled = CB_OTVar.Enabled = enable; + M_OT_Affection.Enabled = true; + } + else + { + GB_M_OT.Text = GB_M_CT.Text = $"N/A: {GameInfo.Strings.EggName}"; } - private void SaveFields() + init = true; + + // Manually load the Memory Parse + RTB_CT.Text = GetMemoryString(CB_CTMemory, CB_CTVar, CB_CTQual, CB_CTFeel, Entity.HT_Name); + RTB_OT.Text = GetMemoryString(CB_OTMemory, CB_OTVar, CB_OTQual, CB_OTFeel, Entity.OT_Name); + + // Affection no longer stored in gen8+, so only show in gen6/7. + L_OT_Affection.Visible = L_CT_Affection.Visible = M_OT_Affection.Visible = M_CT_Affection.Visible = Entity.Format <= 7; + L_Sociability.Visible = MT_Sociability.Visible = Entity.Format >= 8; + } + + private void SaveFields() + { + // Save Region & Country Data + if (Entity is IGeoTrack g) { - // Save Region & Country Data - if (pkm is IGeoTrack g) - { - g.Geo1_Region = (byte)WinFormsUtil.GetIndex(CB_Region0); - g.Geo2_Region = (byte)WinFormsUtil.GetIndex(CB_Region1); - g.Geo3_Region = (byte)WinFormsUtil.GetIndex(CB_Region2); - g.Geo4_Region = (byte)WinFormsUtil.GetIndex(CB_Region3); - g.Geo5_Region = (byte)WinFormsUtil.GetIndex(CB_Region4); - g.Geo1_Country = (byte)WinFormsUtil.GetIndex(CB_Country0); - g.Geo2_Country = (byte)WinFormsUtil.GetIndex(CB_Country1); - g.Geo3_Country = (byte)WinFormsUtil.GetIndex(CB_Country2); - g.Geo4_Country = (byte)WinFormsUtil.GetIndex(CB_Country3); - g.Geo5_Country = (byte)WinFormsUtil.GetIndex(CB_Country4); - } - - // Save 0-255 stats - pkm.HT_Friendship = Util.ToInt32(M_CT_Friendship.Text); - pkm.OT_Friendship = Util.ToInt32(M_OT_Friendship.Text); - - if (pkm is IAffection a) - { - a.OT_Affection = (byte)Util.ToInt32(M_OT_Affection.Text); - a.HT_Affection = (byte)Util.ToInt32(M_CT_Affection.Text); - } - pkm.Fullness = (byte)Util.ToInt32(M_Fullness.Text); - pkm.Enjoyment = (byte)Util.ToInt32(M_Enjoyment.Text); - - // Save Memories - if (pkm is ITrainerMemories m) - { - m.OT_Memory = (byte)WinFormsUtil.GetIndex(CB_OTMemory); - m.OT_TextVar = CB_OTVar.Enabled ? (ushort)WinFormsUtil.GetIndex(CB_OTVar) : (ushort)0; - m.OT_Intensity = CB_OTFeel.Enabled ? (byte)(CB_OTQual.SelectedIndex + 1) : (byte)0; - m.OT_Feeling = CB_OTFeel.Enabled ? (byte)CB_OTFeel.SelectedIndex : (byte)0; - - m.HT_Memory = (byte)WinFormsUtil.GetIndex(CB_CTMemory); - m.HT_TextVar = CB_CTVar.Enabled ? (ushort)WinFormsUtil.GetIndex(CB_CTVar) : (ushort)0; - m.HT_Intensity = CB_CTFeel.Enabled ? (byte)(CB_CTQual.SelectedIndex + 1) : (byte)0; - m.HT_Feeling = CB_CTFeel.Enabled ? (byte)CB_CTFeel.SelectedIndex : (byte)0; - } - - if (pkm is G8PKM pk8) - pk8.Sociability = (byte)Util.ToInt32(MT_Sociability.Text); + g.Geo1_Region = (byte)WinFormsUtil.GetIndex(CB_Region0); + g.Geo2_Region = (byte)WinFormsUtil.GetIndex(CB_Region1); + g.Geo3_Region = (byte)WinFormsUtil.GetIndex(CB_Region2); + g.Geo4_Region = (byte)WinFormsUtil.GetIndex(CB_Region3); + g.Geo5_Region = (byte)WinFormsUtil.GetIndex(CB_Region4); + g.Geo1_Country = (byte)WinFormsUtil.GetIndex(CB_Country0); + g.Geo2_Country = (byte)WinFormsUtil.GetIndex(CB_Country1); + g.Geo3_Country = (byte)WinFormsUtil.GetIndex(CB_Country2); + g.Geo4_Country = (byte)WinFormsUtil.GetIndex(CB_Country3); + g.Geo5_Country = (byte)WinFormsUtil.GetIndex(CB_Country4); } - // Event Actions - private void B_Save_Click(object sender, EventArgs e) + // Save 0-255 stats + Entity.HT_Friendship = Util.ToInt32(M_CT_Friendship.Text); + Entity.OT_Friendship = Util.ToInt32(M_OT_Friendship.Text); + + if (Entity is IAffection a) { - SaveFields(); - Close(); + a.OT_Affection = (byte)Util.ToInt32(M_OT_Affection.Text); + a.HT_Affection = (byte)Util.ToInt32(M_CT_Affection.Text); + } + Entity.Fullness = (byte)Util.ToInt32(M_Fullness.Text); + Entity.Enjoyment = (byte)Util.ToInt32(M_Enjoyment.Text); + + // Save Memories + if (Entity is ITrainerMemories m) + { + m.OT_Memory = (byte)WinFormsUtil.GetIndex(CB_OTMemory); + m.OT_TextVar = CB_OTVar.Enabled ? (ushort)WinFormsUtil.GetIndex(CB_OTVar) : (ushort)0; + m.OT_Intensity = CB_OTFeel.Enabled ? (byte)(CB_OTQual.SelectedIndex + 1) : (byte)0; + m.OT_Feeling = CB_OTFeel.Enabled ? (byte)CB_OTFeel.SelectedIndex : (byte)0; + + m.HT_Memory = (byte)WinFormsUtil.GetIndex(CB_CTMemory); + m.HT_TextVar = CB_CTVar.Enabled ? (ushort)WinFormsUtil.GetIndex(CB_CTVar) : (ushort)0; + m.HT_Intensity = CB_CTFeel.Enabled ? (byte)(CB_CTQual.SelectedIndex + 1) : (byte)0; + m.HT_Feeling = CB_CTFeel.Enabled ? (byte)CB_CTFeel.SelectedIndex : (byte)0; } - private void B_Cancel_Click(object sender, EventArgs e) + if (Entity is G8PKM pk8) + pk8.Sociability = (byte)Util.ToInt32(MT_Sociability.Text); + } + + // Event Actions + private void B_Save_Click(object sender, EventArgs e) + { + SaveFields(); + Close(); + } + + private void B_Cancel_Click(object sender, EventArgs e) + { + Close(); + } + + private void GetLangStrings() + { + var strings = MemStrings; + CB_OTMemory.InitializeBinding(); + CB_CTMemory.InitializeBinding(); + CB_OTMemory.DataSource = new BindingSource(strings.Memory, null); + CB_CTMemory.DataSource = new BindingSource(strings.Memory, null); + + // Quality Chooser + foreach (var q in strings.GetMemoryQualities()) { - Close(); + CB_CTQual.Items.Add(q); + CB_OTQual.Items.Add(q); } - private void GetLangStrings() + // Feeling Chooser + foreach (var q in strings.GetMemoryFeelings(Entity.Generation)) + CB_OTFeel.Items.Add(q); + foreach (var q in strings.GetMemoryFeelings(Entity.Format)) + CB_CTFeel.Items.Add(q); + } + + private void UpdateMemoryDisplay(object sender) + { + if (sender == CB_OTMemory) { - var strings = MemStrings; - CB_OTMemory.InitializeBinding(); - CB_CTMemory.InitializeBinding(); - CB_OTMemory.DataSource = new BindingSource(strings.Memory, null); - CB_CTMemory.DataSource = new BindingSource(strings.Memory, null); + int memoryGen = Entity.Generation; + if (memoryGen < 0) + memoryGen = Entity.Format; - // Quality Chooser - foreach (var q in strings.GetMemoryQualities()) - { - CB_CTQual.Items.Add(q); - CB_OTQual.Items.Add(q); - } - - // Feeling Chooser - foreach (var q in strings.GetMemoryFeelings(pkm.Generation)) - CB_OTFeel.Items.Add(q); - foreach (var q in strings.GetMemoryFeelings(pkm.Format)) - CB_CTFeel.Items.Add(q); + int memory = WinFormsUtil.GetIndex((ComboBox)sender); + var memIndex = Memories.GetMemoryArgType(memory, memoryGen); + var argvals = MemStrings.GetArgumentStrings(memIndex, memoryGen); + CB_OTVar.InitializeBinding(); + CB_OTVar.DataSource = new BindingSource(argvals, null); + LOTV.Text = TextArgs.GetMemoryCategory(memIndex, memoryGen); + LOTV.Visible = CB_OTVar.Visible = CB_OTVar.Enabled = argvals.Count > 1; } - - private void UpdateMemoryDisplay(object sender) + else { - if (sender == CB_OTMemory) - { - int memoryGen = pkm.Generation; - if (memoryGen < 0) - memoryGen = pkm.Format; - - int memory = WinFormsUtil.GetIndex((ComboBox)sender); - var memIndex = Memories.GetMemoryArgType(memory, memoryGen); - var argvals = MemStrings.GetArgumentStrings(memIndex, memoryGen); - CB_OTVar.InitializeBinding(); - CB_OTVar.DataSource = new BindingSource(argvals, null); - LOTV.Text = TextArgs.GetMemoryCategory(memIndex, memoryGen); - LOTV.Visible = CB_OTVar.Visible = CB_OTVar.Enabled = argvals.Count > 1; - } - else - { - int memoryGen = pkm.Format; - int memory = WinFormsUtil.GetIndex((ComboBox)sender); - var memIndex = Memories.GetMemoryArgType(memory, memoryGen); - var argvals = MemStrings.GetArgumentStrings(memIndex, memoryGen); - CB_CTVar.InitializeBinding(); - CB_CTVar.DataSource = new BindingSource(argvals, null); - LCTV.Text = TextArgs.GetMemoryCategory(memIndex, memoryGen); - LCTV.Visible = CB_CTVar.Visible = CB_CTVar.Enabled = argvals.Count > 1; - } - } - - private string GetMemoryString(ComboBox m, Control arg, Control q, Control f, string tr) - { - string result; - bool enabled; - int mem = WinFormsUtil.GetIndex(m); - if (mem == 0) - { - string nn = pkm.Nickname; - result = string.Format(GameInfo.Strings.memories[0], nn); - enabled = false; - } - else - { - string nn = pkm.Nickname; - string a = arg.Text; - result = string.Format(GameInfo.Strings.memories[mem], nn, tr, a, f.Text, q.Text); - enabled = true; - } - - // Show labels if the memory allows for them. - if (q == CB_CTQual) - L_CT_Quality.Visible = L_CT_Feeling.Visible = enabled; - else - L_OT_Quality.Visible = L_OT_Feeling.Visible = enabled; - - // Show Quality and Feeling. - q.Visible = q.Enabled = f.Visible = f.Enabled = enabled; - - return result; - } - - private void ChangeMemory(object sender, EventArgs e) - { - ComboBox m = (ComboBox)sender; - if (m == CB_CTMemory || m == CB_OTMemory) - UpdateMemoryDisplay(m); - - if (!init) return; - RTB_OT.Text = GetMemoryString(CB_OTMemory, CB_OTVar, CB_OTQual, CB_OTFeel, pkm.OT_Name); - RTB_CT.Text = GetMemoryString(CB_CTMemory, CB_CTVar, CB_CTQual, CB_CTFeel, pkm.HT_Name); - } - - private void ChangeCountryIndex(object sender, EventArgs e) - { - int index = Array.IndexOf(PrevCountries, sender); - int val; - if (sender is ComboBox c && (val = WinFormsUtil.GetIndex(c)) > 0) - { - Main.SetCountrySubRegion(PrevRegions[index], $"sr_{val:000}"); - PrevRegions[index].Enabled = true; - } - else - { - PrevRegions[index].DataSource = new[] { new ComboItem("", 0) }; - PrevRegions[index].Enabled = false; - PrevRegions[index].SelectedValue = 0; - } - } - - private void ChangeCountryText(object sender, EventArgs e) - { - if (sender is not ComboBox cb || !string.IsNullOrWhiteSpace(cb.Text)) - return; - cb.SelectedValue = 0; - ChangeCountryIndex(sender, e); - } - - private void Update255_MTB(object sender, EventArgs e) - { - if (sender is not MaskedTextBox tb) return; - if (Util.ToInt32(tb.Text) > byte.MaxValue) - tb.Text = "255"; - } - - private void ClickResetLocation(object sender, EventArgs e) - { - Label[] senderarr = { L_Geo0, L_Geo1, L_Geo2, L_Geo3, L_Geo4 }; - int index = Array.IndexOf(senderarr, sender); - PrevCountries[index].SelectedValue = 0; - - PrevRegions[index].InitializeBinding(); - PrevRegions[index].DataSource = new[] { new ComboItem("", 0) }; - PrevRegions[index].SelectedValue = 0; - } - - private void B_ClearAll_Click(object sender, EventArgs e) - { - for (int i = 0; i < 5; i++) - PrevCountries[i].SelectedValue = 0; - } - - private sealed class TextMarkup - { - public string Disabled { get; } = nameof(Disabled); - public string NeverLeft { get; } = "Never left"; - public string OT { get; } = "OT"; - public string PastGen { get; } = "Past Gen"; - public string MemoriesWith { get; } = "Memories with"; - - private string Species { get; } = "Species:"; - private string Area { get; } = "Area:"; - private string Item { get; } = "Item:"; - private string Move { get; } = "Move:"; - private string Location { get; } = "Location:"; - - public TextMarkup(string[] args) - { - Array.Resize(ref args, 10); - if (!string.IsNullOrWhiteSpace(args[0])) Disabled = args[0]; - if (!string.IsNullOrWhiteSpace(args[1])) NeverLeft = args[1]; - if (!string.IsNullOrWhiteSpace(args[2])) OT = args[2]; - if (!string.IsNullOrWhiteSpace(args[3])) PastGen = args[3]; - if (!string.IsNullOrWhiteSpace(args[4])) MemoriesWith = args[4]; - - // Pokémon ; Area ; Item(s) ; Move ; Location - if (!string.IsNullOrWhiteSpace(args[5])) Species = args[5] + ":"; - if (!string.IsNullOrWhiteSpace(args[6])) Area = args[6] + ":"; - if (!string.IsNullOrWhiteSpace(args[7])) Item = args[7] + ":"; - if (!string.IsNullOrWhiteSpace(args[8])) Move = args[8] + ":"; - if (!string.IsNullOrWhiteSpace(args[9])) Location = args[9] + ":"; - } - - public string GetMemoryCategory(MemoryArgType type, int memoryGen) => type switch - { - MemoryArgType.GeneralLocation => Area, - MemoryArgType.SpecificLocation when memoryGen <= 7 => Location, - MemoryArgType.Species => Species, - MemoryArgType.Move => Move, - MemoryArgType.Item => Item, - _ => string.Empty, - }; + int memoryGen = Entity.Format; + int memory = WinFormsUtil.GetIndex((ComboBox)sender); + var memIndex = Memories.GetMemoryArgType(memory, memoryGen); + var argvals = MemStrings.GetArgumentStrings(memIndex, memoryGen); + CB_CTVar.InitializeBinding(); + CB_CTVar.DataSource = new BindingSource(argvals, null); + LCTV.Text = TextArgs.GetMemoryCategory(memIndex, memoryGen); + LCTV.Visible = CB_CTVar.Visible = CB_CTVar.Enabled = argvals.Count > 1; } } -} \ No newline at end of file + + private string GetMemoryString(ComboBox m, Control arg, Control q, Control f, string tr) + { + string result; + bool enabled; + int mem = WinFormsUtil.GetIndex(m); + if (mem == 0) + { + string nn = Entity.Nickname; + result = string.Format(GameInfo.Strings.memories[0], nn); + enabled = false; + } + else + { + string nn = Entity.Nickname; + string a = arg.Text; + result = string.Format(GameInfo.Strings.memories[mem], nn, tr, a, f.Text, q.Text); + enabled = true; + } + + // Show labels if the memory allows for them. + if (q == CB_CTQual) + L_CT_Quality.Visible = L_CT_Feeling.Visible = enabled; + else + L_OT_Quality.Visible = L_OT_Feeling.Visible = enabled; + + // Show Quality and Feeling. + q.Visible = q.Enabled = f.Visible = f.Enabled = enabled; + + return result; + } + + private void ChangeMemory(object sender, EventArgs e) + { + ComboBox m = (ComboBox)sender; + if (m == CB_CTMemory || m == CB_OTMemory) + UpdateMemoryDisplay(m); + + if (!init) return; + RTB_OT.Text = GetMemoryString(CB_OTMemory, CB_OTVar, CB_OTQual, CB_OTFeel, Entity.OT_Name); + RTB_CT.Text = GetMemoryString(CB_CTMemory, CB_CTVar, CB_CTQual, CB_CTFeel, Entity.HT_Name); + } + + private void ChangeCountryIndex(object sender, EventArgs e) + { + int index = Array.IndexOf(PrevCountries, sender); + int val; + if (sender is ComboBox c && (val = WinFormsUtil.GetIndex(c)) > 0) + { + Main.SetCountrySubRegion(PrevRegions[index], $"sr_{val:000}"); + PrevRegions[index].Enabled = true; + } + else + { + PrevRegions[index].DataSource = new[] { new ComboItem("", 0) }; + PrevRegions[index].Enabled = false; + PrevRegions[index].SelectedValue = 0; + } + } + + private void ChangeCountryText(object sender, EventArgs e) + { + if (sender is not ComboBox cb || !string.IsNullOrWhiteSpace(cb.Text)) + return; + cb.SelectedValue = 0; + ChangeCountryIndex(sender, e); + } + + private void Update255_MTB(object sender, EventArgs e) + { + if (sender is not MaskedTextBox tb) return; + if (Util.ToInt32(tb.Text) > byte.MaxValue) + tb.Text = "255"; + } + + private void ClickResetLocation(object sender, EventArgs e) + { + Label[] senderarr = { L_Geo0, L_Geo1, L_Geo2, L_Geo3, L_Geo4 }; + int index = Array.IndexOf(senderarr, sender); + PrevCountries[index].SelectedValue = 0; + + PrevRegions[index].InitializeBinding(); + PrevRegions[index].DataSource = new[] { new ComboItem("", 0) }; + PrevRegions[index].SelectedValue = 0; + } + + private void B_ClearAll_Click(object sender, EventArgs e) + { + for (int i = 0; i < 5; i++) + PrevCountries[i].SelectedValue = 0; + } + + private sealed class TextMarkup + { + public string Disabled { get; } = nameof(Disabled); + public string NeverLeft { get; } = "Never left"; + public string OT { get; } = "OT"; + public string PastGen { get; } = "Past Gen"; + public string MemoriesWith { get; } = "Memories with"; + + private string Species { get; } = "Species:"; + private string Area { get; } = "Area:"; + private string Item { get; } = "Item:"; + private string Move { get; } = "Move:"; + private string Location { get; } = "Location:"; + + public TextMarkup(string[] args) + { + Array.Resize(ref args, 10); + if (!string.IsNullOrWhiteSpace(args[0])) Disabled = args[0]; + if (!string.IsNullOrWhiteSpace(args[1])) NeverLeft = args[1]; + if (!string.IsNullOrWhiteSpace(args[2])) OT = args[2]; + if (!string.IsNullOrWhiteSpace(args[3])) PastGen = args[3]; + if (!string.IsNullOrWhiteSpace(args[4])) MemoriesWith = args[4]; + + // Pokémon ; Area ; Item(s) ; Move ; Location + if (!string.IsNullOrWhiteSpace(args[5])) Species = args[5] + ":"; + if (!string.IsNullOrWhiteSpace(args[6])) Area = args[6] + ":"; + if (!string.IsNullOrWhiteSpace(args[7])) Item = args[7] + ":"; + if (!string.IsNullOrWhiteSpace(args[8])) Move = args[8] + ":"; + if (!string.IsNullOrWhiteSpace(args[9])) Location = args[9] + ":"; + } + + public string GetMemoryCategory(MemoryArgType type, int memoryGen) => type switch + { + MemoryArgType.GeneralLocation => Area, + MemoryArgType.SpecificLocation when memoryGen <= 7 => Location, + MemoryArgType.Species => Species, + MemoryArgType.Move => Move, + MemoryArgType.Item => Item, + _ => string.Empty, + }; + } +} diff --git a/PKHeX.WinForms/Subforms/PKM Editors/MoveShopEditor.Designer.cs b/PKHeX.WinForms/Subforms/PKM Editors/MoveShopEditor.Designer.cs index ff39fd08d..f6ad975b7 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/MoveShopEditor.Designer.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/MoveShopEditor.Designer.cs @@ -134,4 +134,4 @@ private void InitializeComponent() private System.Windows.Forms.ToolTip tipName; private System.Windows.Forms.DataGridView dgv; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/PKM Editors/MoveShopEditor.cs b/PKHeX.WinForms/Subforms/PKM Editors/MoveShopEditor.cs index efb6f9115..70c653f85 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/MoveShopEditor.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/MoveShopEditor.cs @@ -7,15 +7,15 @@ namespace PKHeX.WinForms; public partial class MoveShopEditor : Form { - private readonly IMoveShop8 Entity; + private readonly IMoveShop8 Shop; private readonly IMoveShop8Mastery Master; - private readonly PKM pkm; + private readonly PKM Entity; public MoveShopEditor(IMoveShop8 s, IMoveShop8Mastery m, PKM pk) { - Entity = s; + Shop = s; Master = m; - pkm = pk; + Entity = pk; InitializeComponent(); WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); @@ -102,7 +102,7 @@ private void ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArg private void PopulateRecords() { var names = GameInfo.Strings.Move; - var indexes = Entity.MoveShopPermitIndexes; + var indexes = Shop.MoveShopPermitIndexes; dgv.Rows.Add(indexes.Length); for (int i = 0; i < indexes.Length; i++) { @@ -128,7 +128,7 @@ private void LoadRecords() var index = int.Parse((string)row.Cells[0].Value) - Bias; var purchased = row.Cells[2]; var mastered = row.Cells[3]; - purchased.Value = Entity.GetPurchasedRecordFlag(index); + purchased.Value = Shop.GetPurchasedRecordFlag(index); mastered.Value = Master.GetMasteredRecordFlag(index); } } @@ -141,7 +141,7 @@ private void Save() var index = int.Parse((string)row.Cells[0].Value) - Bias; var purchased = row.Cells[2]; var mastered = row.Cells[3]; - Entity.SetPurchasedRecordFlag(index, (bool)purchased.Value); + Shop.SetPurchasedRecordFlag(index, (bool)purchased.Value); Master.SetMasteredRecordFlag(index, (bool)mastered.Value); } } @@ -152,14 +152,14 @@ private void B_All_Click(object sender, EventArgs e) switch (ModifierKeys) { case Keys.Shift: - Master.SetMoveShopFlagsAll(pkm); + Master.SetMoveShopFlagsAll(Entity); break; case Keys.Control: - Entity.ClearMoveShopFlags(); - Master.SetMoveShopFlags(pkm); + Shop.ClearMoveShopFlags(); + Master.SetMoveShopFlags(Entity); break; default: - Master.SetMoveShopFlags(pkm); + Master.SetMoveShopFlags(Entity); break; } Close(); @@ -168,7 +168,7 @@ private void B_All_Click(object sender, EventArgs e) private void B_None_Click(object sender, EventArgs e) { Save(); - Entity.ClearMoveShopFlags(); + Shop.ClearMoveShopFlags(); LoadRecords(); Close(); } diff --git a/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.Designer.cs b/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.Designer.cs index f322f6761..034d20287 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.Designer.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.Designer.cs @@ -202,4 +202,4 @@ private void InitializeComponent() private System.Windows.Forms.ToolTip tipName; private System.Windows.Forms.ComboBox CB_Affixed; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.cs b/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.cs index 8ebd4a957..e9db5ac19 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/RibbonEditor.cs @@ -6,198 +6,201 @@ using PKHeX.Core; using PKHeX.Drawing.Misc; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class RibbonEditor : Form { - public partial class RibbonEditor : Form + private readonly PKM Entity; + private readonly IReadOnlyList riblist; + + private const string PrefixNUD = "NUD_"; + private const string PrefixLabel = "L_"; + private const string PrefixCHK = "CHK_"; + private const string PrefixPB = "PB_"; + + public RibbonEditor(PKM pk) { - public RibbonEditor(PKM pk) + Entity = pk; + InitializeComponent(); + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + riblist = RibbonInfo.GetRibbonInfo(pk); + int vertScrollWidth = SystemInformation.VerticalScrollBarWidth; + TLP_Ribbons.Padding = FLP_Ribbons.Padding = new Padding(0, 0, vertScrollWidth, 0); + + // Updating a Control display with autosized elements on every row addition is cpu intensive. Disable layout updates while populating. + TLP_Ribbons.SuspendLayout(); + FLP_Ribbons.Scroll += WinFormsUtil.PanelScroll; + TLP_Ribbons.Scroll += WinFormsUtil.PanelScroll; + PopulateRibbons(); + TLP_Ribbons.ResumeLayout(); + + InitializeAffixed(pk); + } + + private void InitializeAffixed(PKM pk) + { + if (pk is not IRibbonSetAffixed affixed) { - pkm = pk; - InitializeComponent(); - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - riblist = RibbonInfo.GetRibbonInfo(pkm); - int vertScrollWidth = SystemInformation.VerticalScrollBarWidth; - TLP_Ribbons.Padding = FLP_Ribbons.Padding = new Padding(0, 0, vertScrollWidth, 0); - - // Updating a Control display with autosized elements on every row addition is cpu intensive. Disable layout updates while populating. - TLP_Ribbons.SuspendLayout(); - FLP_Ribbons.Scroll += WinFormsUtil.PanelScroll; - TLP_Ribbons.Scroll += WinFormsUtil.PanelScroll; - PopulateRibbons(); - TLP_Ribbons.ResumeLayout(); - - if (pk is IRibbonSetAffixed affixed) - { - var ds = new List {new(GameInfo.GetStrings(Main.CurrentLanguage).Move[0], -1)}; - var list = Enumerable.Range(0, (int)RibbonIndex.MAX_COUNT) - .Select(z => new ComboItem(RibbonStrings.GetName($"Ribbon{(RibbonIndex)z}"), z)) - .OrderBy(z => z.Text); - ds.AddRange(list); - - CB_Affixed.InitializeBinding(); - CB_Affixed.DataSource = ds; - CB_Affixed.SelectedValue = (int)affixed.AffixedRibbon; - } - else - { - CB_Affixed.Visible = false; - } + CB_Affixed.Visible = false; + return; } - private readonly IReadOnlyList riblist; - private readonly PKM pkm; + var ds = new List { new(GameInfo.GetStrings(Main.CurrentLanguage).Move[0], -1) }; + var list = Enumerable.Range(0, (int)RibbonIndex.MAX_COUNT) + .Select(z => new ComboItem(RibbonStrings.GetName($"Ribbon{(RibbonIndex)z}"), z)) + .OrderBy(z => z.Text); + ds.AddRange(list); - private const string PrefixNUD = "NUD_"; - private const string PrefixLabel = "L_"; - private const string PrefixCHK = "CHK_"; - private const string PrefixPB = "PB_"; + CB_Affixed.InitializeBinding(); + CB_Affixed.DataSource = ds; + CB_Affixed.SelectedValue = (int)affixed.AffixedRibbon; + } - private void B_Cancel_Click(object sender, EventArgs e) => Close(); + private void B_Cancel_Click(object sender, EventArgs e) => Close(); - private void B_Save_Click(object sender, EventArgs e) + private void B_Save_Click(object sender, EventArgs e) + { + Save(); + Close(); + } + + private void PopulateRibbons() + { + TLP_Ribbons.ColumnCount = 2; + TLP_Ribbons.RowCount = 0; + + // Add Ribbons + foreach (var rib in riblist) + AddRibbonSprite(rib); + foreach (var rib in riblist.OrderBy(z => RibbonStrings.GetName(z.Name))) + AddRibbonChoice(rib); + + // Force auto-size + foreach (var style in TLP_Ribbons.RowStyles.OfType()) + style.SizeType = SizeType.AutoSize; + foreach (var style in TLP_Ribbons.ColumnStyles.OfType()) + style.SizeType = SizeType.AutoSize; + } + + private void AddRibbonSprite(RibbonInfo rib) + { + var name = rib.Name; + var pb = new PictureBox { AutoSize = false, Size = new Size(40,40), BackgroundImageLayout = ImageLayout.Center, Visible = false, Name = PrefixPB + name }; + var img = RibbonSpriteUtil.GetRibbonSprite(name); + pb.BackgroundImage = img; + + var display = RibbonStrings.GetName(name); + pb.MouseEnter += (s, e) => tipName.SetToolTip(pb, display); + FLP_Ribbons.Controls.Add(pb); + } + + private void AddRibbonChoice(RibbonInfo rib) + { + // Get row we add to + int row = TLP_Ribbons.RowCount; + TLP_Ribbons.RowCount++; + + var label = new Label { - Save(); + Anchor = AnchorStyles.Left, + Name = PrefixLabel + rib.Name, + Text = RibbonStrings.GetName(rib.Name), + Padding = Padding.Empty, + Margin = Padding.Empty, + AutoSize = true, + }; + TLP_Ribbons.Controls.Add(label, 1, row); + + if (rib.RibbonCount >= 0) // numeric count ribbon + AddRibbonNumericUpDown(rib, row); + else // boolean ribbon + AddRibbonCheckBox(rib, row, label); + } + + private void AddRibbonNumericUpDown(RibbonInfo rib, int row) + { + var nud = new NumericUpDown + { + Anchor = AnchorStyles.Right, + Name = PrefixNUD + rib.Name, + Minimum = 0, + Width = 35, + Increment = 1, + Padding = Padding.Empty, + Margin = Padding.Empty, + Maximum = rib.MaxCount, + }; + + nud.ValueChanged += (sender, e) => + { + FLP_Ribbons.Controls[PrefixPB + rib.Name].Visible = (rib.RibbonCount = (int)nud.Value) > 0; + FLP_Ribbons.Controls[PrefixPB + rib.Name].BackgroundImage = RibbonSpriteUtil.GetRibbonSprite(rib.Name, (int)nud.Maximum, (int)nud.Value); + }; + nud.Value = rib.RibbonCount > nud.Maximum ? nud.Maximum : rib.RibbonCount; + TLP_Ribbons.Controls.Add(nud, 0, row); + } + + private void AddRibbonCheckBox(RibbonInfo rib, int row, Control label) + { + var chk = new CheckBox + { + Anchor = AnchorStyles.Right, + Name = PrefixCHK + rib.Name, + AutoSize = true, + Padding = Padding.Empty, + Margin = Padding.Empty, + }; + chk.CheckedChanged += (sender, e) => + { + rib.HasRibbon = chk.Checked; + FLP_Ribbons.Controls[PrefixPB + rib.Name].Visible = rib.HasRibbon; + }; + chk.Checked = rib.HasRibbon; + TLP_Ribbons.Controls.Add(chk, 0, row); + + label.Click += (s, e) => chk.Checked ^= true; + } + + private void Save() + { + foreach (var rib in riblist) + ReflectUtil.SetValue(Entity, rib.Name, rib.RibbonCount < 0 ? rib.HasRibbon : rib.RibbonCount); + + if (Entity is IRibbonSetAffixed affixed) + affixed.AffixedRibbon = (sbyte)WinFormsUtil.GetIndex(CB_Affixed); + } + + private void B_All_Click(object sender, EventArgs e) + { + if (ModifierKeys == Keys.Shift) + { + RibbonApplicator.RemoveAllValidRibbons(Entity); + RibbonApplicator.SetAllValidRibbons(Entity); Close(); + return; } - private void PopulateRibbons() + foreach (var c in TLP_Ribbons.Controls.OfType()) + c.Checked = true; + foreach (var n in TLP_Ribbons.Controls.OfType()) + n.Value = n.Maximum; + } + + private void B_None_Click(object sender, EventArgs e) + { + if (ModifierKeys == Keys.Shift) { - TLP_Ribbons.ColumnCount = 2; - TLP_Ribbons.RowCount = 0; - - // Add Ribbons - foreach (var rib in riblist) - AddRibbonSprite(rib); - foreach (var rib in riblist.OrderBy(z => RibbonStrings.GetName(z.Name))) - AddRibbonChoice(rib); - - // Force auto-size - foreach (var style in TLP_Ribbons.RowStyles.OfType()) - style.SizeType = SizeType.AutoSize; - foreach (var style in TLP_Ribbons.ColumnStyles.OfType()) - style.SizeType = SizeType.AutoSize; + RibbonApplicator.RemoveAllValidRibbons(Entity); + if (Entity is IRibbonSetAffixed affixed) + affixed.AffixedRibbon = -1; + Close(); + return; } - private void AddRibbonSprite(RibbonInfo rib) - { - var name = rib.Name; - var pb = new PictureBox { AutoSize = false, Size = new Size(40,40), BackgroundImageLayout = ImageLayout.Center, Visible = false, Name = PrefixPB + name }; - var img = RibbonSpriteUtil.GetRibbonSprite(name); - pb.BackgroundImage = img; - - var display = RibbonStrings.GetName(name); - pb.MouseEnter += (s, e) => tipName.SetToolTip(pb, display); - FLP_Ribbons.Controls.Add(pb); - } - - private void AddRibbonChoice(RibbonInfo rib) - { - // Get row we add to - int row = TLP_Ribbons.RowCount; - TLP_Ribbons.RowCount++; - - var label = new Label - { - Anchor = AnchorStyles.Left, - Name = PrefixLabel + rib.Name, - Text = RibbonStrings.GetName(rib.Name), - Padding = Padding.Empty, - Margin = Padding.Empty, - AutoSize = true, - }; - TLP_Ribbons.Controls.Add(label, 1, row); - - if (rib.RibbonCount >= 0) // numeric count ribbon - AddRibbonNumericUpDown(rib, row); - else // boolean ribbon - AddRibbonCheckBox(rib, row, label); - } - - private void AddRibbonNumericUpDown(RibbonInfo rib, int row) - { - var nud = new NumericUpDown - { - Anchor = AnchorStyles.Right, - Name = PrefixNUD + rib.Name, - Minimum = 0, - Width = 35, - Increment = 1, - Padding = Padding.Empty, - Margin = Padding.Empty, - Maximum = rib.MaxCount, - }; - - nud.ValueChanged += (sender, e) => - { - FLP_Ribbons.Controls[PrefixPB + rib.Name].Visible = (rib.RibbonCount = (int)nud.Value) > 0; - FLP_Ribbons.Controls[PrefixPB + rib.Name].BackgroundImage = RibbonSpriteUtil.GetRibbonSprite(rib.Name, (int)nud.Maximum, (int)nud.Value); - }; - nud.Value = rib.RibbonCount > nud.Maximum ? nud.Maximum : rib.RibbonCount; - TLP_Ribbons.Controls.Add(nud, 0, row); - } - - private void AddRibbonCheckBox(RibbonInfo rib, int row, Control label) - { - var chk = new CheckBox - { - Anchor = AnchorStyles.Right, - Name = PrefixCHK + rib.Name, - AutoSize = true, - Padding = Padding.Empty, - Margin = Padding.Empty, - }; - chk.CheckedChanged += (sender, e) => - { - rib.HasRibbon = chk.Checked; - FLP_Ribbons.Controls[PrefixPB + rib.Name].Visible = rib.HasRibbon; - }; - chk.Checked = rib.HasRibbon; - TLP_Ribbons.Controls.Add(chk, 0, row); - - label.Click += (s, e) => chk.Checked ^= true; - } - - private void Save() - { - foreach (var rib in riblist) - ReflectUtil.SetValue(pkm, rib.Name, rib.RibbonCount < 0 ? rib.HasRibbon : rib.RibbonCount); - - if (pkm is IRibbonSetAffixed affixed) - affixed.AffixedRibbon = (sbyte)WinFormsUtil.GetIndex(CB_Affixed); - } - - private void B_All_Click(object sender, EventArgs e) - { - if (ModifierKeys == Keys.Shift) - { - RibbonApplicator.RemoveAllValidRibbons(pkm); - RibbonApplicator.SetAllValidRibbons(pkm); - Close(); - return; - } - - foreach (var c in TLP_Ribbons.Controls.OfType()) - c.Checked = true; - foreach (var n in TLP_Ribbons.Controls.OfType()) - n.Value = n.Maximum; - } - - private void B_None_Click(object sender, EventArgs e) - { - if (ModifierKeys == Keys.Shift) - { - RibbonApplicator.RemoveAllValidRibbons(pkm); - if (pkm is IRibbonSetAffixed affixed) - affixed.AffixedRibbon = -1; - Close(); - return; - } - - CB_Affixed.SelectedValue = -1; - foreach (var c in TLP_Ribbons.Controls.OfType()) - c.Checked = false; - foreach (var n in TLP_Ribbons.Controls.OfType()) - n.Value = 0; - } + CB_Affixed.SelectedValue = -1; + foreach (var c in TLP_Ribbons.Controls.OfType()) + c.Checked = false; + foreach (var n in TLP_Ribbons.Controls.OfType()) + n.Value = 0; } } diff --git a/PKHeX.WinForms/Subforms/PKM Editors/SuperTrainingEditor.Designer.cs b/PKHeX.WinForms/Subforms/PKM Editors/SuperTrainingEditor.Designer.cs index 611e407a4..af8df70c8 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/SuperTrainingEditor.Designer.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/SuperTrainingEditor.Designer.cs @@ -280,4 +280,4 @@ private void InitializeComponent() private System.Windows.Forms.SplitContainer SPLIT_Training; private System.Windows.Forms.CheckBox CHK_SecretComplete; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/PKM Editors/SuperTrainingEditor.cs b/PKHeX.WinForms/Subforms/PKM Editors/SuperTrainingEditor.cs index ebae16d83..53b1d9360 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/SuperTrainingEditor.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/SuperTrainingEditor.cs @@ -4,186 +4,185 @@ using System.Windows.Forms; using PKHeX.Core; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class SuperTrainingEditor : Form { - public partial class SuperTrainingEditor : Form + public SuperTrainingEditor(PKM pk) { - public SuperTrainingEditor(PKM pk) + Entity = (ISuperTrain)pk; + InitializeComponent(); + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + int vertScrollWidth = SystemInformation.VerticalScrollBarWidth; + TLP_SuperTrain.Padding = TLP_DistSuperTrain.Padding = new Padding(0, 0, vertScrollWidth, 0); + + // Updating a Control display with auto-sized elements on every row addition is cpu intensive. Disable layout updates while populating. + TLP_SuperTrain.SuspendLayout(); + TLP_DistSuperTrain.SuspendLayout(); + TLP_SuperTrain.Scroll += WinFormsUtil.PanelScroll; + TLP_DistSuperTrain.Scroll += WinFormsUtil.PanelScroll; + PopulateRegimens("SuperTrain", TLP_SuperTrain, reglist); + PopulateRegimens("DistSuperTrain", TLP_DistSuperTrain, distlist); + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + TLP_SuperTrain.ResumeLayout(); + TLP_DistSuperTrain.ResumeLayout(); + + CHK_SecretUnlocked.Checked = Entity.SecretSuperTrainingUnlocked; + CHK_SecretComplete.Checked = Entity.SecretSuperTrainingComplete; + + if (pk is PK6 pk6) { - pkm = (ISuperTrain)pk; - InitializeComponent(); - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - int vertScrollWidth = SystemInformation.VerticalScrollBarWidth; - TLP_SuperTrain.Padding = TLP_DistSuperTrain.Padding = new Padding(0, 0, vertScrollWidth, 0); + CB_Bag.Items.Clear(); + CB_Bag.Items.Add("---"); + for (int i = 1; i < GameInfo.Strings.trainingbags.Length - 1; i++) + CB_Bag.Items.Add(GameInfo.Strings.trainingbags[i]); - // Updating a Control display with auto-sized elements on every row addition is cpu intensive. Disable layout updates while populating. - TLP_SuperTrain.SuspendLayout(); - TLP_DistSuperTrain.SuspendLayout(); - TLP_SuperTrain.Scroll += WinFormsUtil.PanelScroll; - TLP_DistSuperTrain.Scroll += WinFormsUtil.PanelScroll; - PopulateRegimens("SuperTrain", TLP_SuperTrain, reglist); - PopulateRegimens("DistSuperTrain", TLP_DistSuperTrain, distlist); - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - TLP_SuperTrain.ResumeLayout(); - TLP_DistSuperTrain.ResumeLayout(); + CB_Bag.SelectedIndex = pk6.TrainingBag; + NUD_BagHits.Value = pk6.TrainingBagHits; - CHK_SecretUnlocked.Checked = pkm.SecretSuperTrainingUnlocked; - CHK_SecretComplete.Checked = pkm.SecretSuperTrainingComplete; - - if (pkm is PK6 pk6) - { - CB_Bag.Items.Clear(); - CB_Bag.Items.Add("---"); - for (int i = 1; i < GameInfo.Strings.trainingbags.Length - 1; i++) - CB_Bag.Items.Add(GameInfo.Strings.trainingbags[i]); - - CB_Bag.SelectedIndex = pk6.TrainingBag; - NUD_BagHits.Value = pk6.TrainingBagHits; - - if (!CHK_SecretUnlocked.Checked) // force update to disable checkboxes - CHK_Secret_CheckedChanged(this, EventArgs.Empty); - } - else - { - L_Bag.Visible = CB_Bag.Visible = L_Hits.Visible = NUD_BagHits.Visible = false; - CHK_SecretUnlocked.Visible = CHK_SecretComplete.Visible = false; - } + if (!CHK_SecretUnlocked.Checked) // force update to disable checkboxes + CHK_Secret_CheckedChanged(this, EventArgs.Empty); } - - private readonly List reglist = new(); - private readonly List distlist = new(); - private readonly ISuperTrain pkm; - private const string PrefixCHK = "CHK_"; - - private void B_Cancel_Click(object sender, EventArgs e) => Close(); - - private void B_Save_Click(object sender, EventArgs e) + else { - Save(); - Close(); + L_Bag.Visible = CB_Bag.Visible = L_Hits.Visible = NUD_BagHits.Visible = false; + CHK_SecretUnlocked.Visible = CHK_SecretComplete.Visible = false; } + } - private void PopulateRegimens(string propertyPrefix, TableLayoutPanel TLP, List list) + private readonly List reglist = new(); + private readonly List distlist = new(); + private readonly ISuperTrain Entity; + private const string PrefixCHK = "CHK_"; + + private void B_Cancel_Click(object sender, EventArgs e) => Close(); + + private void B_Save_Click(object sender, EventArgs e) + { + Save(); + Close(); + } + + private void PopulateRegimens(string propertyPrefix, TableLayoutPanel TLP, List list) + { + // Get a list of all Regimen Attregutes in the PKM + list.AddRange(GetBooleanRegimenNames(Entity, propertyPrefix)); + TLP.ColumnCount = 1; + TLP.RowCount = 0; + + // Add Regimens + foreach (var reg in list) + AddRegimenChoice(reg, TLP); + + // Force auto-size + foreach (var style in TLP.RowStyles.OfType()) + style.SizeType = SizeType.AutoSize; + foreach (var style in TLP.ColumnStyles.OfType()) + style.SizeType = SizeType.AutoSize; + } + + private static IEnumerable GetBooleanRegimenNames(ISuperTrain pk, string propertyPrefix) + { + var names = ReflectUtil.GetPropertiesStartWithPrefix(pk.GetType(), propertyPrefix); + foreach (var name in names) { - // Get a list of all Regimen Attregutes in the PKM - list.AddRange(GetBooleanRegimenNames(pkm, propertyPrefix)); - TLP.ColumnCount = 1; - TLP.RowCount = 0; - - // Add Regimens - foreach (var reg in list) - AddRegimenChoice(reg, TLP); - - // Force auto-size - foreach (var style in TLP.RowStyles.OfType()) - style.SizeType = SizeType.AutoSize; - foreach (var style in TLP.ColumnStyles.OfType()) - style.SizeType = SizeType.AutoSize; + var value = ReflectUtil.GetValue(pk, name); + if (value is bool state) + yield return new RegimenInfo(name, state); } + } - private static IEnumerable GetBooleanRegimenNames(ISuperTrain pkm, string propertyPrefix) + private static void AddRegimenChoice(RegimenInfo reg, TableLayoutPanel TLP) + { + // Get row we add to + int row = TLP.RowCount; + TLP.RowCount++; + + var chk = new CheckBox { - var names = ReflectUtil.GetPropertiesStartWithPrefix(pkm.GetType(), propertyPrefix); - foreach (var name in names) - { - var value = ReflectUtil.GetValue(pkm, name); - if (value is bool state) - yield return new RegimenInfo(name, state); - } + Anchor = AnchorStyles.Left, + Name = PrefixCHK + reg.Name, + Margin = new Padding(2), + Text = reg.Name, + AutoSize = true, + Padding = Padding.Empty, + }; + chk.CheckedChanged += (sender, e) => reg.CompletedRegimen = chk.Checked; + chk.Checked = reg.CompletedRegimen; + TLP.Controls.Add(chk, 0, row); + } + + private void Save() + { + foreach (var reg in reglist) + ReflectUtil.SetValue(Entity, reg.Name, reg.CompletedRegimen); + foreach (var reg in distlist) + ReflectUtil.SetValue(Entity, reg.Name, reg.CompletedRegimen); + + if (Entity is PK6 pk6) + { + pk6.SecretSuperTrainingUnlocked = CHK_SecretUnlocked.Checked; + pk6.SecretSuperTrainingComplete = CHK_SecretComplete.Checked; + pk6.TrainingBag = CB_Bag.SelectedIndex; + pk6.TrainingBagHits = (int)NUD_BagHits.Value; } - - private static void AddRegimenChoice(RegimenInfo reg, TableLayoutPanel TLP) + else // clear flags if manually cleared { - // Get row we add to - int row = TLP.RowCount; - TLP.RowCount++; - - var chk = new CheckBox - { - Anchor = AnchorStyles.Left, - Name = PrefixCHK + reg.Name, - Margin = new Padding(2), - Text = reg.Name, - AutoSize = true, - Padding = Padding.Empty, - }; - chk.CheckedChanged += (sender, e) => reg.CompletedRegimen = chk.Checked; - chk.Checked = reg.CompletedRegimen; - TLP.Controls.Add(chk, 0, row); + Entity.SecretSuperTrainingUnlocked &= CHK_SecretUnlocked.Checked; + Entity.SecretSuperTrainingComplete &= CHK_SecretComplete.Checked; } + } - private void Save() + private sealed class RegimenInfo + { + public readonly string Name; + public bool CompletedRegimen; + + internal RegimenInfo(string name, bool completedRegimen) { - foreach (var reg in reglist) - ReflectUtil.SetValue(pkm, reg.Name, reg.CompletedRegimen); - foreach (var reg in distlist) - ReflectUtil.SetValue(pkm, reg.Name, reg.CompletedRegimen); - - if (pkm is PK6 pk6) - { - pk6.SecretSuperTrainingUnlocked = CHK_SecretUnlocked.Checked; - pk6.SecretSuperTrainingComplete = CHK_SecretComplete.Checked; - pk6.TrainingBag = CB_Bag.SelectedIndex; - pk6.TrainingBagHits = (int)NUD_BagHits.Value; - } - else // clear flags if manually cleared - { - pkm.SecretSuperTrainingUnlocked &= CHK_SecretUnlocked.Checked; - pkm.SecretSuperTrainingComplete &= CHK_SecretComplete.Checked; - } + Name = name; + CompletedRegimen = completedRegimen; } + } - private sealed class RegimenInfo + private void B_All_Click(object sender, EventArgs e) + { + if (CHK_SecretUnlocked.Checked) // only give dist if Secret is Unlocked (None -> All -> All*) { - public readonly string Name; - public bool CompletedRegimen; - - internal RegimenInfo(string name, bool completedRegimen) - { - Name = name; - CompletedRegimen = completedRegimen; - } - } - - private void B_All_Click(object sender, EventArgs e) - { - if (CHK_SecretUnlocked.Checked) // only give dist if Secret is Unlocked (None -> All -> All*) - { - foreach (var c in TLP_DistSuperTrain.Controls.OfType()) - c.Checked = true; - } - - if (pkm is PK6) - { - CHK_SecretUnlocked.Checked = true; - CHK_SecretComplete.Checked = true; - } - foreach (var c in TLP_SuperTrain.Controls.OfType()) + foreach (var c in TLP_DistSuperTrain.Controls.OfType()) c.Checked = true; } - private void B_None_Click(object sender, EventArgs e) + if (Entity is PK6) { - CHK_SecretUnlocked.Checked = false; - CHK_SecretComplete.Checked = false; - foreach (var c in TLP_SuperTrain.Controls.OfType()) - c.Checked = false; - foreach (var c in TLP_DistSuperTrain.Controls.OfType()) - c.Checked = false; + CHK_SecretUnlocked.Checked = true; + CHK_SecretComplete.Checked = true; } + foreach (var c in TLP_SuperTrain.Controls.OfType()) + c.Checked = true; + } - private void CHK_Secret_CheckedChanged(object sender, EventArgs e) + private void B_None_Click(object sender, EventArgs e) + { + CHK_SecretUnlocked.Checked = false; + CHK_SecretComplete.Checked = false; + foreach (var c in TLP_SuperTrain.Controls.OfType()) + c.Checked = false; + foreach (var c in TLP_DistSuperTrain.Controls.OfType()) + c.Checked = false; + } + + private void CHK_Secret_CheckedChanged(object sender, EventArgs e) + { + if (Entity is not PK6) + return; + CHK_SecretComplete.Checked &= CHK_SecretUnlocked.Checked; + CHK_SecretComplete.Enabled = CHK_SecretUnlocked.Checked; + foreach (var c in TLP_SuperTrain.Controls.OfType().Where(chk => Convert.ToInt16(chk.Name[14]+"") >= 4)) { - if (pkm is not PK6) - return; - CHK_SecretComplete.Checked &= CHK_SecretUnlocked.Checked; - CHK_SecretComplete.Enabled = CHK_SecretUnlocked.Checked; - foreach (var c in TLP_SuperTrain.Controls.OfType().Where(chk => Convert.ToInt16(chk.Name[14]+"") >= 4)) - { - c.Enabled = CHK_SecretUnlocked.Checked; - if (!CHK_SecretUnlocked.Checked) - c.Checked = false; - } + c.Enabled = CHK_SecretUnlocked.Checked; + if (!CHK_SecretUnlocked.Checked) + c.Checked = false; } } } diff --git a/PKHeX.WinForms/Subforms/PKM Editors/TechRecordEditor.Designer.cs b/PKHeX.WinForms/Subforms/PKM Editors/TechRecordEditor.Designer.cs index 0bbe6ebd1..1019e6098 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/TechRecordEditor.Designer.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/TechRecordEditor.Designer.cs @@ -121,4 +121,4 @@ private void InitializeComponent() private System.Windows.Forms.ToolTip tipName; private System.Windows.Forms.CheckedListBox CLB_Flags; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/PKM Editors/TechRecordEditor.cs b/PKHeX.WinForms/Subforms/PKM Editors/TechRecordEditor.cs index 6eec4bd97..5486c31ee 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/TechRecordEditor.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/TechRecordEditor.cs @@ -2,73 +2,72 @@ using System.Windows.Forms; using PKHeX.Core; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class TechRecordEditor : Form { - public partial class TechRecordEditor : Form + private readonly ITechRecord8 Record; + private readonly PKM Entity; + + public TechRecordEditor(ITechRecord8 techRecord8, PKM pk) { - private readonly ITechRecord8 Entity; - private readonly PKM pkm; + Record = techRecord8; + Entity = pk; + InitializeComponent(); + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - public TechRecordEditor(ITechRecord8 techRecord8, PKM pk) - { - Entity = techRecord8; - pkm = pk; - InitializeComponent(); - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + PopulateRecords(); + LoadRecords(); + } - PopulateRecords(); - LoadRecords(); - } + private void PopulateRecords() + { + var names = GameInfo.Strings.Move; + var indexes = Record.TechRecordPermitIndexes; + var lines = new string[indexes.Length]; + for (int i = 0; i < lines.Length; i++) + lines[i] = $"{i:00} - {names[indexes[i]]}"; + CLB_Flags.Items.AddRange(lines); + } - private void PopulateRecords() - { - var names = GameInfo.Strings.Move; - var indexes = Entity.TechRecordPermitIndexes; - var lines = new string[indexes.Length]; - for (int i = 0; i < lines.Length; i++) - lines[i] = $"{i:00} - {names[indexes[i]]}"; - CLB_Flags.Items.AddRange(lines); - } + private void B_Cancel_Click(object sender, EventArgs e) => Close(); - private void B_Cancel_Click(object sender, EventArgs e) => Close(); + private void B_Save_Click(object sender, EventArgs e) + { + Save(); + Close(); + } - private void B_Save_Click(object sender, EventArgs e) - { - Save(); - Close(); - } + private void LoadRecords() + { + for (int i = 0; i < PersonalInfoSWSH.CountTR; i++) + CLB_Flags.SetItemChecked(i, Record.GetMoveRecordFlag(i)); + } - private void LoadRecords() - { - for (int i = 0; i < PersonalInfoSWSH.CountTR; i++) - CLB_Flags.SetItemChecked(i, Entity.GetMoveRecordFlag(i)); - } + private void Save() + { + for (int i = 0; i < PersonalInfoSWSH.CountTR; i++) + Record.SetMoveRecordFlag(i, CLB_Flags.GetItemChecked(i)); + } - private void Save() - { - for (int i = 0; i < PersonalInfoSWSH.CountTR; i++) - Entity.SetMoveRecordFlag(i, CLB_Flags.GetItemChecked(i)); - } + private void B_All_Click(object sender, EventArgs e) + { + Save(); + if (ModifierKeys == Keys.Shift) + Record.SetRecordFlags(true); + else if (ModifierKeys == Keys.Control) + Record.SetRecordFlags(Entity.Moves); + else + Record.SetRecordFlags(); + LoadRecords(); + Close(); + } - private void B_All_Click(object sender, EventArgs e) - { - Save(); - if (ModifierKeys == Keys.Shift) - Entity.SetRecordFlags(true); - else if (ModifierKeys == Keys.Control) - Entity.SetRecordFlags(pkm.Moves); - else - Entity.SetRecordFlags(); - LoadRecords(); - Close(); - } - - private void B_None_Click(object sender, EventArgs e) - { - Save(); - Entity.ClearRecordFlags(); - LoadRecords(); - Close(); - } + private void B_None_Click(object sender, EventArgs e) + { + Save(); + Record.ClearRecordFlags(); + LoadRecords(); + Close(); } } diff --git a/PKHeX.WinForms/Subforms/PKM Editors/Text.Designer.cs b/PKHeX.WinForms/Subforms/PKM Editors/Text.Designer.cs index 930c51a91..f2b0771da 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/Text.Designer.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/Text.Designer.cs @@ -257,4 +257,4 @@ private void InitializeComponent() private System.Windows.Forms.Button B_ClearTrash; private System.Windows.Forms.Label L_String; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/PKM Editors/Text.cs b/PKHeX.WinForms/Subforms/PKM Editors/Text.cs index dfaafb902..760fb7b43 100644 --- a/PKHeX.WinForms/Subforms/PKM Editors/Text.cs +++ b/PKHeX.WinForms/Subforms/PKM Editors/Text.cs @@ -4,210 +4,209 @@ using System.Windows.Forms; using PKHeX.Core; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class TrashEditor : Form { - public partial class TrashEditor : Form + private readonly SaveFile SAV; + + public TrashEditor(TextBoxBase TB_NN, SaveFile sav) : this(TB_NN, Array.Empty(), sav) { } + + public TrashEditor(TextBoxBase TB_NN, Span raw, SaveFile sav) { - private readonly SaveFile SAV; + InitializeComponent(); + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + SAV = sav; - public TrashEditor(TextBoxBase TB_NN, SaveFile sav) : this(TB_NN, Array.Empty(), sav) { } + FinalString = TB_NN.Text; + Raw = FinalBytes = raw.ToArray(); - public TrashEditor(TextBoxBase TB_NN, Span raw, SaveFile sav) + editing = true; + if (raw.Length != 0) + AddTrashEditing(raw.Length); + + var f = FontUtil.GetPKXFont(); + AddCharEditing(f); + TB_Text.MaxLength = TB_NN.MaxLength; + TB_Text.Text = TB_NN.Text; + TB_Text.Font = f; + + if (FLP_Characters.Controls.Count == 0) { - InitializeComponent(); - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - SAV = sav; - - FinalString = TB_NN.Text; - Raw = FinalBytes = raw.ToArray(); - - editing = true; - if (raw.Length != 0) - AddTrashEditing(raw.Length); - - var f = FontUtil.GetPKXFont(); - AddCharEditing(f); - TB_Text.MaxLength = TB_NN.MaxLength; - TB_Text.Text = TB_NN.Text; - TB_Text.Font = f; - - if (FLP_Characters.Controls.Count == 0) - { - FLP_Characters.Visible = false; - FLP_Hex.Height *= 2; - } - else if (FLP_Hex.Controls.Count == 0) - { - FLP_Characters.Location = FLP_Hex.Location; - FLP_Characters.Height *= 2; - } - - editing = false; - CenterToParent(); + FLP_Characters.Visible = false; + FLP_Hex.Height *= 2; + } + else if (FLP_Hex.Controls.Count == 0) + { + FLP_Characters.Location = FLP_Hex.Location; + FLP_Characters.Height *= 2; } - private readonly List Bytes = new(); - public string FinalString; - public byte[] FinalBytes; - private readonly byte[] Raw; - private bool editing; - private void B_Cancel_Click(object sender, EventArgs e) => Close(); - - private void B_Save_Click(object sender, EventArgs e) - { - FinalString = TB_Text.Text; - if (FinalBytes.Length == 0) - FinalBytes = Raw; - Close(); - } - - private void AddCharEditing(Font f) - { - ushort[] chars = GetChars(SAV.Generation); - if (chars.Length == 0) - return; - - FLP_Characters.Visible = true; - foreach (ushort c in chars) - { - var l = GetLabel(((char)c).ToString()); - l.Font = f; - l.AutoSize = false; - l.Size = new Size(20, 20); - l.Click += (s, e) => { if (TB_Text.Text.Length < TB_Text.MaxLength) TB_Text.AppendText(l.Text); }; - FLP_Characters.Controls.Add(l); - } - } - - private void AddTrashEditing(int count) - { - FLP_Hex.Visible = true; - GB_Trash.Visible = true; - NUD_Generation.Value = SAV.Generation; - for (int i = 0; i < count; i++) - { - var l = GetLabel($"${i:X2}"); - l.Font = NUD_Generation.Font; - var n = GetNUD(min: 0, max: 255, hex: true); - n.Click += (s, e) => - { - switch (ModifierKeys) - { - case Keys.Shift: n.Value = n.Maximum; break; - case Keys.Alt: n.Value = n.Minimum; break; - } - }; - n.Value = Raw[i]; - n.ValueChanged += (o, args) => UpdateNUD(n, args); - - FLP_Hex.Controls.Add(l); - FLP_Hex.Controls.Add(n); - Bytes.Add(n); - } - TB_Text.TextChanged += (o, args) => UpdateString(TB_Text, args); - - CB_Species.InitializeBinding(); - CB_Species.DataSource = new BindingSource(GameInfo.SpeciesDataSource, null); - - CB_Language.InitializeBinding(); - CB_Language.DataSource = GameInfo.LanguageDataSource(SAV.Generation); - } - - private void UpdateNUD(object sender, EventArgs e) - { - if (editing) - return; - editing = true; - // build bytes - if (sender is not NumericUpDown nud) - throw new Exception(); - int index = Bytes.IndexOf(nud); - Raw[index] = (byte)nud.Value; - - TB_Text.Text = GetString(); - editing = false; - } - - private void UpdateString(object sender, EventArgs e) - { - if (editing) - return; - editing = true; - // build bytes - byte[] data = SetString(TB_Text.Text); - Array.Copy(data, Raw, Math.Min(data.Length, Raw.Length)); - for (int i = 0; i < Raw.Length; i++) - Bytes[i].Value = Raw[i]; - editing = false; - } - - private void B_ApplyTrash_Click(object sender, EventArgs e) - { - string species = SpeciesName.GetSpeciesNameGeneration(WinFormsUtil.GetIndex(CB_Species), - WinFormsUtil.GetIndex(CB_Language), (int) NUD_Generation.Value); - - if (string.IsNullOrEmpty(species)) // no result - species = CB_Species.Text; - - byte[] current = SetString(TB_Text.Text); - byte[] data = SetString(species); - if (data.Length <= current.Length) - { - WinFormsUtil.Alert("Trash byte layer is hidden by current text.", - $"Current Bytes: {current.Length}" + Environment.NewLine + $"Layer Bytes: {data.Length}"); - return; - } - if (data.Length > Bytes.Count) - { - WinFormsUtil.Alert("Trash byte layer is too long to apply."); - return; - } - for (int i = current.Length; i < data.Length; i++) - Bytes[i].Value = data[i]; - } - - private void B_ClearTrash_Click(object sender, EventArgs e) - { - byte[] current = SetString(TB_Text.Text); - for (int i = current.Length; i < Bytes.Count; i++) - Bytes[i].Value = 0; - } - - private byte[] SetString(string text) - { - Span temp = stackalloc byte[Raw.Length]; - var written = SAV.SetString(temp, text.AsSpan(), text.Length, StringConverterOption.None); - return temp[..written].ToArray(); - } - - private string GetString() => SAV.GetString(Raw.AsSpan()); - - // Helpers - private static Label GetLabel(string str) => new() {Text = str, AutoSize = true}; - - private static NumericUpDown GetNUD(int min, int max, bool hex) => new() - { - Maximum = max, - Minimum = min, - Hexadecimal = hex, - Width = 36, - Padding = new Padding(0), - Margin = new Padding(0), - }; - - private static ushort[] GetChars(int generation) => generation switch - { - 6 => chars67, - 7 => chars67, - _ => Array.Empty(), // Undocumented - }; - - private static readonly ushort[] chars67 = - { - 0xE081, 0xE082, 0xE083, 0xE084, 0xE085, 0xE086, 0xE087, 0xE08D, - 0xE08E, 0xE08F, 0xE090, 0xE091, 0xE092, 0xE093, 0xE094, 0xE095, - 0xE096, 0xE097, 0xE098, 0xE099, 0xE09A, 0xE09B, 0xE09C, 0xE09D, - 0xE09E, 0xE09F, 0xE0A0, 0xE0A1, 0xE0A2, 0xE0A3, 0xE0A4, 0xE0A5, - }; + editing = false; + CenterToParent(); } + + private readonly List Bytes = new(); + public string FinalString; + public byte[] FinalBytes; + private readonly byte[] Raw; + private bool editing; + private void B_Cancel_Click(object sender, EventArgs e) => Close(); + + private void B_Save_Click(object sender, EventArgs e) + { + FinalString = TB_Text.Text; + if (FinalBytes.Length == 0) + FinalBytes = Raw; + Close(); + } + + private void AddCharEditing(Font f) + { + ushort[] chars = GetChars(SAV.Generation); + if (chars.Length == 0) + return; + + FLP_Characters.Visible = true; + foreach (ushort c in chars) + { + var l = GetLabel(((char)c).ToString()); + l.Font = f; + l.AutoSize = false; + l.Size = new Size(20, 20); + l.Click += (s, e) => { if (TB_Text.Text.Length < TB_Text.MaxLength) TB_Text.AppendText(l.Text); }; + FLP_Characters.Controls.Add(l); + } + } + + private void AddTrashEditing(int count) + { + FLP_Hex.Visible = true; + GB_Trash.Visible = true; + NUD_Generation.Value = SAV.Generation; + for (int i = 0; i < count; i++) + { + var l = GetLabel($"${i:X2}"); + l.Font = NUD_Generation.Font; + var n = GetNUD(min: 0, max: 255, hex: true); + n.Click += (s, e) => + { + switch (ModifierKeys) + { + case Keys.Shift: n.Value = n.Maximum; break; + case Keys.Alt: n.Value = n.Minimum; break; + } + }; + n.Value = Raw[i]; + n.ValueChanged += (o, args) => UpdateNUD(n, args); + + FLP_Hex.Controls.Add(l); + FLP_Hex.Controls.Add(n); + Bytes.Add(n); + } + TB_Text.TextChanged += (o, args) => UpdateString(TB_Text, args); + + CB_Species.InitializeBinding(); + CB_Species.DataSource = new BindingSource(GameInfo.SpeciesDataSource, null); + + CB_Language.InitializeBinding(); + CB_Language.DataSource = GameInfo.LanguageDataSource(SAV.Generation); + } + + private void UpdateNUD(object sender, EventArgs e) + { + if (editing) + return; + editing = true; + // build bytes + if (sender is not NumericUpDown nud) + throw new Exception(); + int index = Bytes.IndexOf(nud); + Raw[index] = (byte)nud.Value; + + TB_Text.Text = GetString(); + editing = false; + } + + private void UpdateString(object sender, EventArgs e) + { + if (editing) + return; + editing = true; + // build bytes + byte[] data = SetString(TB_Text.Text); + Array.Copy(data, Raw, Math.Min(data.Length, Raw.Length)); + for (int i = 0; i < Raw.Length; i++) + Bytes[i].Value = Raw[i]; + editing = false; + } + + private void B_ApplyTrash_Click(object sender, EventArgs e) + { + string species = SpeciesName.GetSpeciesNameGeneration(WinFormsUtil.GetIndex(CB_Species), + WinFormsUtil.GetIndex(CB_Language), (int) NUD_Generation.Value); + + if (string.IsNullOrEmpty(species)) // no result + species = CB_Species.Text; + + byte[] current = SetString(TB_Text.Text); + byte[] data = SetString(species); + if (data.Length <= current.Length) + { + WinFormsUtil.Alert("Trash byte layer is hidden by current text.", + $"Current Bytes: {current.Length}" + Environment.NewLine + $"Layer Bytes: {data.Length}"); + return; + } + if (data.Length > Bytes.Count) + { + WinFormsUtil.Alert("Trash byte layer is too long to apply."); + return; + } + for (int i = current.Length; i < data.Length; i++) + Bytes[i].Value = data[i]; + } + + private void B_ClearTrash_Click(object sender, EventArgs e) + { + byte[] current = SetString(TB_Text.Text); + for (int i = current.Length; i < Bytes.Count; i++) + Bytes[i].Value = 0; + } + + private byte[] SetString(string text) + { + Span temp = stackalloc byte[Raw.Length]; + var written = SAV.SetString(temp, text.AsSpan(), text.Length, StringConverterOption.None); + return temp[..written].ToArray(); + } + + private string GetString() => SAV.GetString(Raw.AsSpan()); + + // Helpers + private static Label GetLabel(string str) => new() {Text = str, AutoSize = true}; + + private static NumericUpDown GetNUD(int min, int max, bool hex) => new() + { + Maximum = max, + Minimum = min, + Hexadecimal = hex, + Width = 36, + Padding = new Padding(0), + Margin = new Padding(0), + }; + + private static ushort[] GetChars(int generation) => generation switch + { + 6 => chars67, + 7 => chars67, + _ => Array.Empty(), // Undocumented + }; + + private static readonly ushort[] chars67 = + { + 0xE081, 0xE082, 0xE083, 0xE084, 0xE085, 0xE086, 0xE087, 0xE08D, + 0xE08E, 0xE08F, 0xE090, 0xE091, 0xE092, 0xE093, 0xE094, 0xE095, + 0xE096, 0xE097, 0xE098, 0xE099, 0xE09A, 0xE09B, 0xE09C, 0xE09D, + 0xE09E, 0xE09F, 0xE0A0, 0xE0A1, 0xE0A2, 0xE0A3, 0xE0A4, 0xE0A5, + }; } diff --git a/PKHeX.WinForms/Subforms/ReportGrid.Designer.cs b/PKHeX.WinForms/Subforms/ReportGrid.Designer.cs index e0528b131..6c0678a86 100644 --- a/PKHeX.WinForms/Subforms/ReportGrid.Designer.cs +++ b/PKHeX.WinForms/Subforms/ReportGrid.Designer.cs @@ -68,4 +68,4 @@ private void InitializeComponent() private System.Windows.Forms.DataGridView dgData; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/ReportGrid.cs b/PKHeX.WinForms/Subforms/ReportGrid.cs index 9d631b5c2..42243eef7 100644 --- a/PKHeX.WinForms/Subforms/ReportGrid.cs +++ b/PKHeX.WinForms/Subforms/ReportGrid.cs @@ -8,161 +8,160 @@ using PKHeX.Drawing.PokeSprite; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class ReportGrid : Form { - public partial class ReportGrid : Form + public ReportGrid() { - public ReportGrid() + InitializeComponent(); + dgData.DoubleBuffered(true); + CenterToParent(); + GetContextMenu(); + } + + private void GetContextMenu() + { + var mnuHide = new ToolStripMenuItem { Name = "mnuHide", Text = MsgReportColumnHide }; + mnuHide.Click += (sender, e) => { - InitializeComponent(); - dgData.DoubleBuffered(true); - CenterToParent(); - GetContextMenu(); - } + int c = dgData.SelectedCells.Count; + if (c == 0) + { WinFormsUtil.Alert(MsgReportColumnHideFail); return; } - private void GetContextMenu() + for (int i = 0; i < c; i++) + dgData.Columns[dgData.SelectedCells[i].ColumnIndex].Visible = false; + }; + var mnuRestore = new ToolStripMenuItem { Name = "mnuRestore", Text = MsgReportColumnRestore }; + mnuRestore.Click += (sender, e) => { - var mnuHide = new ToolStripMenuItem { Name = "mnuHide", Text = MsgReportColumnHide }; - mnuHide.Click += (sender, e) => - { - int c = dgData.SelectedCells.Count; - if (c == 0) - { WinFormsUtil.Alert(MsgReportColumnHideFail); return; } + int c = dgData.ColumnCount; + for (int i = 0; i < c; i++) + dgData.Columns[i].Visible = true; - for (int i = 0; i < c; i++) - dgData.Columns[dgData.SelectedCells[i].ColumnIndex].Visible = false; - }; - var mnuRestore = new ToolStripMenuItem { Name = "mnuRestore", Text = MsgReportColumnRestore }; - mnuRestore.Click += (sender, e) => - { - int c = dgData.ColumnCount; - for (int i = 0; i < c; i++) - dgData.Columns[i].Visible = true; + WinFormsUtil.Alert(MsgReportColumnRestoreSuccess); + }; - WinFormsUtil.Alert(MsgReportColumnRestoreSuccess); - }; + ContextMenuStrip mnu = new(); + mnu.Items.Add(mnuHide); + mnu.Items.Add(mnuRestore); - ContextMenuStrip mnu = new(); - mnu.Items.Add(mnuHide); - mnu.Items.Add(mnuRestore); + dgData.ContextMenuStrip = mnu; + } - dgData.ContextMenuStrip = mnu; - } + private sealed class PokemonList : SortableBindingList where T : class { } - private sealed class PokemonList : SortableBindingList where T : class { } - - public void PopulateData(IList Data) + public void PopulateData(IList Data) + { + SuspendLayout(); + var PL = new PokemonList(); + var strings = GameInfo.Strings; + foreach (var entry in Data) { - SuspendLayout(); - var PL = new PokemonList(); - var strings = GameInfo.Strings; - foreach (var entry in Data) + var pk = entry.Entity; + if ((uint)(pk.Species - 1) >= pk.MaxSpeciesID) { - var pkm = entry.Entity; - if ((uint)(pkm.Species - 1) >= pkm.MaxSpeciesID) - { - continue; - } - pkm.Stat_Level = pkm.CurrentLevel; // recalc Level - PL.Add(new EntitySummaryImage(pkm, strings, entry.Identify())); + continue; } - - dgData.DataSource = PL; - dgData.AutoGenerateColumns = true; - for (int i = 0; i < dgData.Columns.Count; i++) - { - var col = dgData.Columns[i]; - if (col is DataGridViewImageColumn) - continue; // Don't add sorting for Sprites - col.SortMode = DataGridViewColumnSortMode.Automatic; - } - - // Trigger Resizing - dgData.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells; - for (int i = 0; i < dgData.Columns.Count; i++) - { - int w = dgData.Columns[i].Width; - dgData.Columns[i].AutoSizeMode = DataGridViewAutoSizeColumnMode.None; - dgData.Columns[i].Width = w; - } - dgData.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None; - Data_Sorted(this, EventArgs.Empty); // trigger row resizing - - ResumeLayout(); + pk.Stat_Level = pk.CurrentLevel; // recalc Level + PL.Add(new EntitySummaryImage(pk, strings, entry.Identify())); } - private void Data_Sorted(object sender, EventArgs e) + dgData.DataSource = PL; + dgData.AutoGenerateColumns = true; + for (int i = 0; i < dgData.Columns.Count; i++) { - int height = SpriteUtil.Spriter.Height + 1; // max height of a row, +1px - for (int i = 0; i < dgData.Rows.Count; i++) - dgData.Rows[i].Height = height; + var col = dgData.Columns[i]; + if (col is DataGridViewImageColumn) + continue; // Don't add sorting for Sprites + col.SortMode = DataGridViewColumnSortMode.Automatic; } - private void PromptSaveCSV(object sender, FormClosingEventArgs e) + // Trigger Resizing + dgData.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells; + for (int i = 0; i < dgData.Columns.Count; i++) { - if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgReportExportCSV) != DialogResult.Yes) - return; - using var savecsv = new SaveFileDialog - { - Filter = "Spreadsheet|*.csv", - FileName = "Box Data Dump.csv", - }; - if (savecsv.ShowDialog() == DialogResult.OK) - { - Hide(); - var path = savecsv.FileName; - var t = Task.Run(() => Export_CSV(path)); - t.Wait(); // don't start disposing until the saving is complete - } + int w = dgData.Columns[i].Width; + dgData.Columns[i].AutoSizeMode = DataGridViewAutoSizeColumnMode.None; + dgData.Columns[i].Width = w; } + dgData.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None; + Data_Sorted(this, EventArgs.Empty); // trigger row resizing - private async Task Export_CSV(string path) + ResumeLayout(); + } + + private void Data_Sorted(object sender, EventArgs e) + { + int height = SpriteUtil.Spriter.Height + 1; // max height of a row, +1px + for (int i = 0; i < dgData.Rows.Count; i++) + dgData.Rows[i].Height = height; + } + + private void PromptSaveCSV(object sender, FormClosingEventArgs e) + { + if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgReportExportCSV) != DialogResult.Yes) + return; + using var savecsv = new SaveFileDialog { - using var fs = new FileStream(path, FileMode.Create); - using var s = new StreamWriter(fs); - - var headers = dgData.Columns.Cast(); - await s.WriteLineAsync(string.Join(",", headers.Skip(1).Select(column => $"\"{column.HeaderText}\""))).ConfigureAwait(false); - - foreach (var cells in dgData.Rows.Cast().Select(row => row.Cells.Cast())) - await s.WriteLineAsync(string.Join(",", cells.Skip(1).Select(cell => $"\"{cell.Value}\""))).ConfigureAwait(false); + Filter = "Spreadsheet|*.csv", + FileName = "Box Data Dump.csv", + }; + if (savecsv.ShowDialog() == DialogResult.OK) + { + Hide(); + var path = savecsv.FileName; + var t = Task.Run(() => Export_CSV(path)); + t.Wait(); // don't start disposing until the saving is complete } + } - protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + private async Task Export_CSV(string path) + { + using var fs = new FileStream(path, FileMode.Create); + using var s = new StreamWriter(fs); + + var headers = dgData.Columns.Cast(); + await s.WriteLineAsync(string.Join(",", headers.Skip(1).Select(column => $"\"{column.HeaderText}\""))).ConfigureAwait(false); + + foreach (var cells in dgData.Rows.Cast().Select(row => row.Cells.Cast())) + await s.WriteLineAsync(string.Join(",", cells.Skip(1).Select(cell => $"\"{cell.Value}\""))).ConfigureAwait(false); + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + bool cp = keyData == (Keys.Control | Keys.C) && ActiveControl is DataGridView; + if (!cp) + return base.ProcessCmdKey(ref msg, keyData); + + var content = dgData.GetClipboardContent(); + if (content == null) + return base.ProcessCmdKey(ref msg, keyData); + + string data = content.GetText(); + var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgReportExportTable); + if (dr != DialogResult.Yes) { - bool cp = keyData == (Keys.Control | Keys.C) && ActiveControl is DataGridView; - if (!cp) - return base.ProcessCmdKey(ref msg, keyData); - - var content = dgData.GetClipboardContent(); - if (content == null) - return base.ProcessCmdKey(ref msg, keyData); - - string data = content.GetText(); - var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgReportExportTable); - if (dr != DialogResult.Yes) - { - WinFormsUtil.SetClipboardText(data); - return true; - } - - // Reformat datagrid clipboard content - string[] lines = data.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - string[] newlines = ConvertTabbedToRedditTable(lines); - WinFormsUtil.SetClipboardText(string.Join(Environment.NewLine, newlines)); + WinFormsUtil.SetClipboardText(data); return true; } - private static string[] ConvertTabbedToRedditTable(string[] lines) - { - string[] newlines = new string[lines.Length + 1]; - int tabcount = lines[0].Count(c => c == '\t'); + // Reformat datagrid clipboard content + string[] lines = data.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + string[] newlines = ConvertTabbedToRedditTable(lines); + WinFormsUtil.SetClipboardText(string.Join(Environment.NewLine, newlines)); + return true; + } - newlines[0] = lines[0].Replace('\t', '|'); - newlines[1] = string.Join(":--:", Enumerable.Repeat('|', tabcount + 2)); // 2 pipes for each end - for (int i = 1; i < lines.Length; i++) - newlines[i + 1] = lines[i].Replace('\t', '|'); - return newlines; - } + private static string[] ConvertTabbedToRedditTable(string[] lines) + { + string[] newlines = new string[lines.Length + 1]; + int tabcount = lines[0].Count(c => c == '\t'); + + newlines[0] = lines[0].Replace('\t', '|'); + newlines[1] = string.Join(":--:", Enumerable.Repeat('|', tabcount + 2)); // 2 pipes for each end + for (int i = 1; i < lines.Length; i++) + newlines[i + 1] = lines[i].Replace('\t', '|'); + return newlines; } } diff --git a/PKHeX.WinForms/Subforms/SAV_Database.Designer.cs b/PKHeX.WinForms/Subforms/SAV_Database.Designer.cs index 262bf0e01..2ff264159 100644 --- a/PKHeX.WinForms/Subforms/SAV_Database.Designer.cs +++ b/PKHeX.WinForms/Subforms/SAV_Database.Designer.cs @@ -1107,4 +1107,4 @@ private void InitializeComponent() private System.Windows.Forms.RichTextBox RTB_Instructions; private System.Windows.Forms.ToolStripMenuItem Menu_SearchBackups; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/SAV_Database.cs b/PKHeX.WinForms/Subforms/SAV_Database.cs index 2a4b4ff62..7c92de848 100644 --- a/PKHeX.WinForms/Subforms/SAV_Database.cs +++ b/PKHeX.WinForms/Subforms/SAV_Database.cs @@ -14,701 +14,698 @@ using PKHeX.WinForms.Properties; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class SAV_Database : Form { - public partial class SAV_Database : Form + private readonly SaveFile SAV; + private readonly SAVEditor BoxView; + private readonly PKMEditor PKME_Tabs; + + public SAV_Database(PKMEditor f1, SAVEditor saveditor) { - private readonly SaveFile SAV; - private readonly SAVEditor BoxView; - private readonly PKMEditor PKME_Tabs; + InitializeComponent(); - public SAV_Database(PKMEditor f1, SAVEditor saveditor) + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + + SAV = saveditor.SAV; + BoxView = saveditor; + PKME_Tabs = f1; + + // Preset Filters to only show PKM available for loaded save + CB_FormatComparator.SelectedIndex = 3; // <= + + var grid = DatabasePokeGrid; + var smallWidth = grid.Width; + var smallHeight = grid.Height; + grid.InitializeGrid(6, 11, SpriteUtil.Spriter); + grid.SetBackground(Resources.box_wp_clean); + var newWidth = grid.Width; + var newHeight = grid.Height; + var wdelta = newWidth - smallWidth; + if (wdelta != 0) + Width += wdelta; + var hdelta = newHeight - smallHeight; + if (hdelta != 0) + Height += hdelta; + PKXBOXES = grid.Entries.ToArray(); + + // Enable Scrolling when hovered over + foreach (var slot in PKXBOXES) { - InitializeComponent(); - - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - - SAV = saveditor.SAV; - BoxView = saveditor; - PKME_Tabs = f1; - - // Preset Filters to only show PKM available for loaded save - CB_FormatComparator.SelectedIndex = 3; // <= - - var grid = DatabasePokeGrid; - var smallWidth = grid.Width; - var smallHeight = grid.Height; - grid.InitializeGrid(6, 11, SpriteUtil.Spriter); - grid.SetBackground(Resources.box_wp_clean); - var newWidth = grid.Width; - var newHeight = grid.Height; - var wdelta = newWidth - smallWidth; - if (wdelta != 0) - Width += wdelta; - var hdelta = newHeight - smallHeight; - if (hdelta != 0) - Height += hdelta; - PKXBOXES = grid.Entries.ToArray(); - - // Enable Scrolling when hovered over - foreach (var slot in PKXBOXES) + // Enable Click + slot.MouseClick += (sender, e) => { - // Enable Click - slot.MouseClick += (sender, e) => + if (sender == null) + return; + switch (ModifierKeys) { - if (sender == null) - return; - switch (ModifierKeys) - { - case Keys.Control: ClickView(sender, e); break; - case Keys.Alt: ClickDelete(sender, e); break; - case Keys.Shift: ClickSet(sender, e); break; - } - }; - - slot.ContextMenuStrip = mnu; - if (Main.Settings.Hover.HoverSlotShowText) - slot.MouseEnter += (o, args) => ShowHoverTextForSlot(slot, args); - } - - Counter = L_Count.Text; - Viewed = L_Viewed.Text; - L_Viewed.Text = string.Empty; // invisible for now - PopulateComboBoxes(); - - // Load Data - B_Search.Enabled = false; - L_Count.Text = "Loading..."; - var task = new Task(LoadDatabase); - task.ContinueWith(z => - { - if (!z.IsFaulted) - return; - Invoke((MethodInvoker)(() => L_Count.Text = "Failed.")); - if (z.Exception == null) - return; - WinFormsUtil.Error("Loading database failed.", z.Exception.InnerException ?? new Exception(z.Exception.Message)); - }); - task.Start(); - - Menu_SearchSettings.DropDown.Closing += (sender, e) => - { - if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked) - e.Cancel = true; - }; - CB_Format.Items[0] = MsgAny; - CenterToParent(); - } - - private readonly PictureBox[] PKXBOXES; - private readonly string DatabasePath = Main.DatabasePath; - private List Results = new(); - private List RawDB = new(); - private int slotSelected = -1; // = null; - private Image? slotColor; - private const int RES_MAX = 66; - private const int RES_MIN = 6; - private readonly string Counter; - private readonly string Viewed; - private const int MAXFORMAT = PKX.Generation; - private readonly SummaryPreviewer ShowSet = new(); - - // Important Events - private void ClickView(object sender, EventArgs e) - { - var pb = WinFormsUtil.GetUnderlyingControl(sender); - int index = Array.IndexOf(PKXBOXES, pb); - if (!GetShiftedIndex(ref index)) - { - System.Media.SystemSounds.Exclamation.Play(); - return; - } - - if (sender == mnu) - mnu.Hide(); - - slotSelected = index; - slotColor = SpriteUtil.Spriter.View; - FillPKXBoxes(SCR_Box.Value); - L_Viewed.Text = string.Format(Viewed, Results[index].Identify()); - PKME_Tabs.PopulateFields(Results[index].Entity, false); - } - - private void ClickDelete(object sender, EventArgs e) - { - var pb = WinFormsUtil.GetUnderlyingControl(sender); - int index = Array.IndexOf(PKXBOXES, pb); - if (!GetShiftedIndex(ref index)) - { - System.Media.SystemSounds.Exclamation.Play(); - return; - } - - var entry = Results[index]; - var pk = entry.Entity; - - if (entry.Source is SlotInfoFile f) - { - // Data from Database: Delete file from disk - var path = f.Path; - if (File.Exists(path)) - File.Delete(path); - } - else if (entry.Source is SlotInfoBox(var box, var slot) && entry.SAV == SAV) - { - // Data from Box: Delete from save file - var change = new SlotInfoBox(box, slot); - var pkSAV = change.Read(SAV); - - if (!pkSAV.DecryptedBoxData.SequenceEqual(pk.DecryptedBoxData)) // data still exists in SAV, unmodified - { - WinFormsUtil.Error(MsgDBDeleteFailModified, MsgDBDeleteFailWarning); - return; + case Keys.Control: ClickView(sender, e); break; + case Keys.Alt: ClickDelete(sender, e); break; + case Keys.Shift: ClickSet(sender, e); break; } - BoxView.EditEnv.Slots.Delete(change); - } - else - { - WinFormsUtil.Error(MsgDBDeleteFailBackup, MsgDBDeleteFailWarning); - return; - } - // Remove from database. - RawDB.Remove(entry); - Results.Remove(entry); - // Refresh database view. - L_Count.Text = string.Format(Counter, Results.Count); - slotSelected = -1; - FillPKXBoxes(SCR_Box.Value); - System.Media.SystemSounds.Asterisk.Play(); + }; + + slot.ContextMenuStrip = mnu; + if (Main.Settings.Hover.HoverSlotShowText) + slot.MouseEnter += (o, args) => ShowHoverTextForSlot(slot, args); } - private void ClickSet(object sender, EventArgs e) + Counter = L_Count.Text; + Viewed = L_Viewed.Text; + L_Viewed.Text = string.Empty; // invisible for now + PopulateComboBoxes(); + + // Load Data + B_Search.Enabled = false; + L_Count.Text = "Loading..."; + var task = new Task(LoadDatabase); + task.ContinueWith(z => { - // Don't care what slot was clicked, just add it to the database - if (!PKME_Tabs.EditsComplete) + if (!z.IsFaulted) return; + Invoke((MethodInvoker)(() => L_Count.Text = "Failed.")); + if (z.Exception == null) + return; + WinFormsUtil.Error("Loading database failed.", z.Exception.InnerException ?? new Exception(z.Exception.Message)); + }); + task.Start(); - PKM pk = PKME_Tabs.PreparePKM(); - Directory.CreateDirectory(DatabasePath); + Menu_SearchSettings.DropDown.Closing += (sender, e) => + { + if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked) + e.Cancel = true; + }; + CB_Format.Items[0] = MsgAny; + CenterToParent(); + } - string path = Path.Combine(DatabasePath, Util.CleanFileName(pk.FileName)); + private readonly PictureBox[] PKXBOXES; + private readonly string DatabasePath = Main.DatabasePath; + private List Results = new(); + private List RawDB = new(); + private int slotSelected = -1; // = null; + private Image? slotColor; + private const int RES_MAX = 66; + private const int RES_MIN = 6; + private readonly string Counter; + private readonly string Viewed; + private const int MAXFORMAT = PKX.Generation; + private readonly SummaryPreviewer ShowSet = new(); + private void ClickView(object sender, EventArgs e) + { + var pb = WinFormsUtil.GetUnderlyingControl(sender); + int index = Array.IndexOf(PKXBOXES, pb); + if (!GetShiftedIndex(ref index)) + { + System.Media.SystemSounds.Exclamation.Play(); + return; + } + + if (sender == mnu) + mnu.Hide(); + + slotSelected = index; + slotColor = SpriteUtil.Spriter.View; + FillPKXBoxes(SCR_Box.Value); + L_Viewed.Text = string.Format(Viewed, Results[index].Identify()); + PKME_Tabs.PopulateFields(Results[index].Entity, false); + } + + private void ClickDelete(object sender, EventArgs e) + { + var pb = WinFormsUtil.GetUnderlyingControl(sender); + int index = Array.IndexOf(PKXBOXES, pb); + if (!GetShiftedIndex(ref index)) + { + System.Media.SystemSounds.Exclamation.Play(); + return; + } + + var entry = Results[index]; + var pk = entry.Entity; + + if (entry.Source is SlotInfoFile f) + { + // Data from Database: Delete file from disk + var path = f.Path; if (File.Exists(path)) + File.Delete(path); + } + else if (entry.Source is SlotInfoBox(var box, var slot) && entry.SAV == SAV) + { + // Data from Box: Delete from save file + var change = new SlotInfoBox(box, slot); + var pkSAV = change.Read(SAV); + + if (!pkSAV.DecryptedBoxData.SequenceEqual(pk.DecryptedBoxData)) // data still exists in SAV, unmodified { - WinFormsUtil.Alert(MsgDBAddFailExistsFile); + WinFormsUtil.Error(MsgDBDeleteFailModified, MsgDBDeleteFailWarning); return; } + BoxView.EditEnv.Slots.Delete(change); + } + else + { + WinFormsUtil.Error(MsgDBDeleteFailBackup, MsgDBDeleteFailWarning); + return; + } + // Remove from database. + RawDB.Remove(entry); + Results.Remove(entry); + // Refresh database view. + L_Count.Text = string.Format(Counter, Results.Count); + slotSelected = -1; + FillPKXBoxes(SCR_Box.Value); + System.Media.SystemSounds.Asterisk.Play(); + } - File.WriteAllBytes(path, pk.DecryptedBoxData); + private void ClickSet(object sender, EventArgs e) + { + // Don't care what slot was clicked, just add it to the database + if (!PKME_Tabs.EditsComplete) + return; - var info = new SlotInfoFile(path); - var entry = new SlotCache(info, pk); - Results.Add(entry); + PKM pk = PKME_Tabs.PreparePKM(); + Directory.CreateDirectory(DatabasePath); - // Refresh database view. - L_Count.Text = string.Format(Counter, Results.Count); - slotSelected = Results.Count - 1; - slotColor = SpriteUtil.Spriter.Set; - if ((SCR_Box.Maximum+1)*6 < Results.Count) - SCR_Box.Maximum++; - SCR_Box.Value = Math.Max(0, SCR_Box.Maximum - (PKXBOXES.Length/6) + 1); - FillPKXBoxes(SCR_Box.Value); - WinFormsUtil.Alert(MsgDBAddFromTabsSuccess); + string path = Path.Combine(DatabasePath, Util.CleanFileName(pk.FileName)); + + if (File.Exists(path)) + { + WinFormsUtil.Alert(MsgDBAddFailExistsFile); + return; } - private bool GetShiftedIndex(ref int index) + File.WriteAllBytes(path, pk.DecryptedBoxData); + + var info = new SlotInfoFile(path); + var entry = new SlotCache(info, pk); + Results.Add(entry); + + // Refresh database view. + L_Count.Text = string.Format(Counter, Results.Count); + slotSelected = Results.Count - 1; + slotColor = SpriteUtil.Spriter.Set; + if ((SCR_Box.Maximum + 1) * 6 < Results.Count) + SCR_Box.Maximum++; + SCR_Box.Value = Math.Max(0, SCR_Box.Maximum - (PKXBOXES.Length / 6) + 1); + FillPKXBoxes(SCR_Box.Value); + WinFormsUtil.Alert(MsgDBAddFromTabsSuccess); + } + + private bool GetShiftedIndex(ref int index) + { + if ((uint)index >= RES_MAX) + return false; + index += SCR_Box.Value * RES_MIN; + return index < Results.Count; + } + + private void PopulateComboBoxes() + { + // Set the Text + CB_HeldItem.InitializeBinding(); + CB_Species.InitializeBinding(); + CB_Ability.InitializeBinding(); + CB_Nature.InitializeBinding(); + CB_GameOrigin.InitializeBinding(); + CB_HPType.InitializeBinding(); + + var comboAny = new ComboItem(MsgAny, -1); + + var species = new List(GameInfo.SpeciesDataSource); + species.RemoveAt(0); + species.Insert(0, comboAny); + CB_Species.DataSource = species; + + var items = new List(GameInfo.ItemDataSource); + items.Insert(0, comboAny); + CB_HeldItem.DataSource = items; + + var natures = new List(GameInfo.NatureDataSource); + natures.Insert(0, comboAny); + CB_Nature.DataSource = natures; + + var abilities = new List(GameInfo.AbilityDataSource); + abilities.Insert(0, comboAny); + CB_Ability.DataSource = abilities; + + var versions = new List(GameInfo.VersionDataSource); + versions.Insert(0, comboAny); + CB_GameOrigin.DataSource = versions; + + string[] hptypes = new string[GameInfo.Strings.types.Length - 2]; + Array.Copy(GameInfo.Strings.types, 1, hptypes, 0, hptypes.Length); + var types = Util.GetCBList(hptypes); + types.Insert(0, comboAny); + CB_HPType.DataSource = types; + + // Set the Move ComboBoxes too.. + var moves = new List(GameInfo.MoveDataSource); + moves.RemoveAt(0); + moves.Insert(0, comboAny); { - if ((uint)index >= RES_MAX) - return false; - index += SCR_Box.Value * RES_MIN; - return index < Results.Count; - } - - private void PopulateComboBoxes() - { - // Set the Text - CB_HeldItem.InitializeBinding(); - CB_Species.InitializeBinding(); - CB_Ability.InitializeBinding(); - CB_Nature.InitializeBinding(); - CB_GameOrigin.InitializeBinding(); - CB_HPType.InitializeBinding(); - - var Any = new ComboItem(MsgAny, -1); - - var DS_Species = new List(GameInfo.SpeciesDataSource); - DS_Species.RemoveAt(0); DS_Species.Insert(0, Any); CB_Species.DataSource = DS_Species; - - var DS_Item = new List(GameInfo.ItemDataSource); - DS_Item.Insert(0, Any); CB_HeldItem.DataSource = DS_Item; - - var DS_Nature = new List(GameInfo.NatureDataSource); - DS_Nature.Insert(0, Any); CB_Nature.DataSource = DS_Nature; - - var DS_Ability = new List(GameInfo.AbilityDataSource); - DS_Ability.Insert(0, Any); CB_Ability.DataSource = DS_Ability; - - var DS_Version = new List(GameInfo.VersionDataSource); - DS_Version.Insert(0, Any); CB_GameOrigin.DataSource = DS_Version; - - string[] hptypes = new string[GameInfo.Strings.types.Length - 2]; Array.Copy(GameInfo.Strings.types, 1, hptypes, 0, hptypes.Length); - var DS_Type = Util.GetCBList(hptypes); - DS_Type.Insert(0, Any); CB_HPType.DataSource = DS_Type; - - // Set the Move ComboBoxes too.. - var DS_Move = new List(GameInfo.MoveDataSource); - DS_Move.RemoveAt(0); DS_Move.Insert(0, Any); + foreach (ComboBox cb in new[] { CB_Move1, CB_Move2, CB_Move3, CB_Move4 }) { - foreach (ComboBox cb in new[] { CB_Move1, CB_Move2, CB_Move3, CB_Move4 }) - { - cb.InitializeBinding(); - cb.DataSource = new BindingSource(DS_Move, null); - } - } - - // Trigger a Reset - ResetFilters(this, EventArgs.Empty); - } - - private void ResetFilters(object sender, EventArgs e) - { - CHK_Shiny.Checked = CHK_IsEgg.Checked = true; - CHK_Shiny.CheckState = CHK_IsEgg.CheckState = CheckState.Indeterminate; - MT_ESV.Text = string.Empty; - CB_HeldItem.SelectedIndex = 0; - CB_Species.SelectedIndex = 0; - CB_Ability.SelectedIndex = 0; - CB_Nature.SelectedIndex = 0; - CB_HPType.SelectedIndex = 0; - - CB_Level.SelectedIndex = 0; - TB_Level.Text = string.Empty; - CB_EVTrain.SelectedIndex = 0; - CB_IV.SelectedIndex = 0; - - CB_Move1.SelectedIndex = CB_Move2.SelectedIndex = CB_Move3.SelectedIndex = CB_Move4.SelectedIndex = 0; - - CB_GameOrigin.SelectedIndex = 0; - CB_Generation.SelectedIndex = 0; - - MT_ESV.Visible = L_ESV.Visible = false; - RTB_Instructions.Clear(); - - if (sender != this) - System.Media.SystemSounds.Asterisk.Play(); - } - - private void GenerateDBReport(object sender, EventArgs e) - { - if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDBCreateReportPrompt, MsgDBCreateReportWarning) != DialogResult.Yes) - return; - - if (this.OpenWindowExists()) - return; - - ReportGrid reportGrid = new(); - reportGrid.Show(); - reportGrid.PopulateData(Results); - } - - private sealed class SearchFolderDetail - { - public string Path { get; } - public bool IgnoreBackupFiles { get; } - - public SearchFolderDetail(string path, bool ignoreBackupFiles) - { - Path = path; - IgnoreBackupFiles = ignoreBackupFiles; + cb.InitializeBinding(); + cb.DataSource = new BindingSource(moves, null); } } - private void LoadDatabase() - { - var settings = Main.Settings; - var otherPaths = new List(); - if (settings.EntityDb.SearchExtraSaves) - otherPaths.AddRange(settings.Backup.OtherBackupPaths.Where(Directory.Exists).Select(z => new SearchFolderDetail(z, true))); - if (settings.EntityDb.SearchBackups) - otherPaths.Add(new SearchFolderDetail(Main.BackupPath, false)); + // Trigger a Reset + ResetFilters(this, EventArgs.Empty); + } - RawDB = LoadPKMSaves(DatabasePath, SAV, otherPaths, settings.EntityDb.SearchExtraSavesDeep); + private void ResetFilters(object sender, EventArgs e) + { + CHK_Shiny.Checked = CHK_IsEgg.Checked = true; + CHK_Shiny.CheckState = CHK_IsEgg.CheckState = CheckState.Indeterminate; + MT_ESV.Text = string.Empty; + CB_HeldItem.SelectedIndex = 0; + CB_Species.SelectedIndex = 0; + CB_Ability.SelectedIndex = 0; + CB_Nature.SelectedIndex = 0; + CB_HPType.SelectedIndex = 0; - // Load stats for pkm who do not have any - foreach (var entry in RawDB) - { - var pk = entry.Entity; - pk.ForcePartyData(); - } + CB_Level.SelectedIndex = 0; + TB_Level.Text = string.Empty; + CB_EVTrain.SelectedIndex = 0; + CB_IV.SelectedIndex = 0; - try - { - while (!IsHandleCreated) { } - BeginInvoke(new MethodInvoker(() => SetResults(RawDB))); - } - catch { /* Window Closed? */ } - } + CB_Move1.SelectedIndex = CB_Move2.SelectedIndex = CB_Move3.SelectedIndex = CB_Move4.SelectedIndex = 0; - private static List LoadPKMSaves(string pkmdb, SaveFile SAV, List otherPaths, bool otherDeep) - { - var dbTemp = new ConcurrentBag(); - var extensions = new HashSet(PKM.Extensions.Select(z => $".{z}")); + CB_GameOrigin.SelectedIndex = 0; + CB_Generation.SelectedIndex = 0; - var files = Directory.EnumerateFiles(pkmdb, "*", SearchOption.AllDirectories); - Parallel.ForEach(files, file => SlotInfoLoader.AddFromLocalFile(file, dbTemp, SAV, extensions)); + MT_ESV.Visible = L_ESV.Visible = false; + RTB_Instructions.Clear(); - foreach (var folder in otherPaths) - { - if (!SaveUtil.GetSavesFromFolder(folder.Path, otherDeep, out IEnumerable paths, folder.IgnoreBackupFiles)) - continue; - - Parallel.ForEach(paths, file => TryAddPKMsFromSaveFilePath(dbTemp, file)); - } - - // Fetch from save file - SlotInfoLoader.AddFromSaveFile(SAV, dbTemp); - var result = new List(dbTemp); - result.RemoveAll(z => !z.IsDataValid()); - - if (Main.Settings.EntityDb.FilterUnavailableSpecies) - { - static bool IsPresentInGameSWSH(ISpeciesForm pk) => pk is PK8 || PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form); - static bool IsPresentInGameBDSP(ISpeciesForm pk) => pk is PB8 || PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form); - static bool IsPresentInGamePLA (ISpeciesForm pk) => pk is PA8 || PersonalTable.LA .IsPresentInGame(pk.Species, pk.Form); - if (SAV is SAV8SWSH) - result.RemoveAll(z => !IsPresentInGameSWSH(z.Entity)); - else if (SAV is SAV8BS) - result.RemoveAll(z => !IsPresentInGameBDSP(z.Entity)); - else if (SAV is SAV8LA) - result.RemoveAll(z => !IsPresentInGamePLA(z.Entity)); - } - - var sort = Main.Settings.EntityDb.InitialSortMode; - if (sort is DatabaseSortMode.SlotIdentity) - result.Sort(); - else if (sort is DatabaseSortMode.SpeciesForm) - result.Sort((first, second) => first.CompareToSpeciesForm(second)); - - // Finalize the Database - return result; - } - - private static void TryAddPKMsFromSaveFilePath(ConcurrentBag dbTemp, string file) - { - var sav = SaveUtil.GetVariantSAV(file); - if (sav == null) - { - Debug.WriteLine("Unable to load SaveFile: " + file); - return; - } - - SlotInfoLoader.AddFromSaveFile(sav, dbTemp); - } - - // IO Usage - private void OpenDB(object sender, EventArgs e) - { - if (Directory.Exists(DatabasePath)) - Process.Start("explorer.exe", DatabasePath); - } - - private void Menu_Export_Click(object sender, EventArgs e) - { - if (Results.Count == 0) - { WinFormsUtil.Alert(MsgDBCreateReportFail); return; } - - if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDBExportResultsPrompt)) - return; - - using var fbd = new FolderBrowserDialog(); - if (DialogResult.OK != fbd.ShowDialog()) - return; - - string path = fbd.SelectedPath; - Directory.CreateDirectory(path); - - foreach (var pkm in Results.Select(z => z.Entity)) - File.WriteAllBytes(Path.Combine(path, Util.CleanFileName(pkm.FileName)), pkm.DecryptedPartyData); - } - - private void Menu_Import_Click(object sender, EventArgs e) - { - if (!BoxView.GetBulkImportSettings(out var clearAll, out var overwrite, out var noSetb)) - return; - - int box = BoxView.Box.CurrentBox; - int ctr = SAV.LoadBoxes(Results.Select(z => z.Entity), out var result, box, clearAll, overwrite, noSetb); - if (ctr <= 0) - return; - - BoxView.SetPKMBoxes(); - BoxView.UpdateBoxViewers(); - WinFormsUtil.Alert(result); - } - - // View Updates - private IEnumerable SearchDatabase() - { - var settings = GetSearchSettings(); - - IEnumerable res = RawDB; - - // pre-filter based on the file path (if specified) - if (!Menu_SearchBoxes.Checked) - res = res.Where(z => z.SAV != SAV); - if (!Menu_SearchDatabase.Checked) - res = res.Where(z => !IsIndividualFilePKMDB(z)); - if (!Menu_SearchBackups.Checked) - res = res.Where(z => !IsBackupSaveFile(z)); - - // return filtered results - return settings.Search(res); - } - - private SearchSettings GetSearchSettings() - { - var settings = new SearchSettings - { - Format = MAXFORMAT - CB_Format.SelectedIndex + 1, // 0->(n-1) => 1->n - SearchFormat = (SearchComparison)CB_FormatComparator.SelectedIndex, - Generation = CB_Generation.SelectedIndex, - - Version = WinFormsUtil.GetIndex(CB_GameOrigin), - HiddenPowerType = WinFormsUtil.GetIndex(CB_HPType), - - Species = WinFormsUtil.GetIndex(CB_Species), - Ability = WinFormsUtil.GetIndex(CB_Ability), - Nature = WinFormsUtil.GetIndex(CB_Nature), - Item = WinFormsUtil.GetIndex(CB_HeldItem), - - BatchInstructions = RTB_Instructions.Lines, - - Level = int.TryParse(TB_Level.Text, out var lvl) ? lvl : null, - SearchLevel = (SearchComparison)CB_Level.SelectedIndex, - EVType = CB_EVTrain.SelectedIndex, - IVType = CB_IV.SelectedIndex, - }; - - settings.AddMove(WinFormsUtil.GetIndex(CB_Move1)); - settings.AddMove(WinFormsUtil.GetIndex(CB_Move2)); - settings.AddMove(WinFormsUtil.GetIndex(CB_Move3)); - settings.AddMove(WinFormsUtil.GetIndex(CB_Move4)); - - if (CHK_Shiny.CheckState != CheckState.Indeterminate) - settings.SearchShiny = CHK_Shiny.CheckState == CheckState.Checked; - - if (CHK_IsEgg.CheckState != CheckState.Indeterminate) - { - settings.SearchEgg = CHK_IsEgg.CheckState == CheckState.Checked; - if (int.TryParse(MT_ESV.Text, out int esv)) - settings.ESV = esv; - } - - if (Menu_SearchLegal.Checked != Menu_SearchIllegal.Checked) - settings.SearchLegal = Menu_SearchLegal.Checked; - - if (Menu_SearchClones.Checked) - { - settings.SearchClones = ModifierKeys switch - { - Keys.Control => CloneDetectionMethod.HashPID, - _ => CloneDetectionMethod.HashDetails, - }; - } - - return settings; - } - - private async void B_Search_Click(object sender, EventArgs e) - { - B_Search.Enabled = false; - var search = SearchDatabase(); - - bool legalSearch = Menu_SearchLegal.Checked ^ Menu_SearchIllegal.Checked; - bool wordFilter = ParseSettings.CheckWordFilter; - if (wordFilter && legalSearch && WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDBSearchLegalityWordfilter) == DialogResult.No) - ParseSettings.CheckWordFilter = false; - var results = await Task.Run(() => search.ToList()).ConfigureAwait(true); - ParseSettings.CheckWordFilter = wordFilter; - - if (results.Count == 0) - { - if (!Menu_SearchBoxes.Checked && !Menu_SearchDatabase.Checked && !Menu_SearchBackups.Checked) - WinFormsUtil.Alert(MsgDBSearchFail, MsgDBSearchNone); - else - WinFormsUtil.Alert(MsgDBSearchNone); - } - SetResults(results); // updates Count Label as well. + if (sender != this) System.Media.SystemSounds.Asterisk.Play(); - B_Search.Enabled = true; - } + } - private void UpdateScroll(object sender, ScrollEventArgs e) + private void GenerateDBReport(object sender, EventArgs e) + { + if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDBCreateReportPrompt, MsgDBCreateReportWarning) != DialogResult.Yes) + return; + + if (this.OpenWindowExists()) + return; + + ReportGrid reportGrid = new(); + reportGrid.Show(); + reportGrid.PopulateData(Results); + } + + private sealed class SearchFolderDetail + { + public string Path { get; } + public bool IgnoreBackupFiles { get; } + + public SearchFolderDetail(string path, bool ignoreBackupFiles) { - if (e.OldValue != e.NewValue) - FillPKXBoxes(e.NewValue); - } - - private void SetResults(List res) - { - Results = res; - ShowSet.Clear(); - - SCR_Box.Maximum = (int)Math.Ceiling((decimal)Results.Count / RES_MIN); - if (SCR_Box.Maximum > 0) SCR_Box.Maximum--; - - slotSelected = -1; // reset the slot last viewed - SCR_Box.Value = 0; - FillPKXBoxes(0); - - L_Count.Text = string.Format(Counter, Results.Count); - B_Search.Enabled = true; - } - - private void FillPKXBoxes(int start) - { - if (Results.Count == 0) - { - for (int i = 0; i < RES_MAX; i++) - { - PKXBOXES[i].Image = null; - PKXBOXES[i].BackgroundImage = null; - } - return; - } - int begin = start*RES_MIN; - int end = Math.Min(RES_MAX, Results.Count - begin); - for (int i = 0; i < end; i++) - PKXBOXES[i].Image = Results[i + begin].Entity.Sprite(SAV, -1, -1, true); - for (int i = end; i < RES_MAX; i++) - PKXBOXES[i].Image = null; - - for (int i = 0; i < RES_MAX; i++) - PKXBOXES[i].BackgroundImage = SpriteUtil.Spriter.Transparent; - if (slotSelected != -1 && slotSelected >= begin && slotSelected < begin + RES_MAX) - PKXBOXES[slotSelected - begin].BackgroundImage = slotColor ?? SpriteUtil.Spriter.View; - } - - // Misc Update Methods - private void ToggleESV(object sender, EventArgs e) - { - L_ESV.Visible = MT_ESV.Visible = CHK_IsEgg.CheckState == CheckState.Checked; - } - - private void ChangeLevel(object sender, EventArgs e) - { - if (CB_Level.SelectedIndex == 0) - TB_Level.Text = string.Empty; - } - - private void ChangeGame(object sender, EventArgs e) - { - if (CB_GameOrigin.SelectedIndex != 0) - CB_Generation.SelectedIndex = 0; - } - - private void ChangeGeneration(object sender, EventArgs e) - { - if (CB_Generation.SelectedIndex != 0) - CB_GameOrigin.SelectedIndex = 0; - } - - private void Menu_Exit_Click(object sender, EventArgs e) => Close(); - - protected override void OnMouseWheel(MouseEventArgs e) - { - if (!DatabasePokeGrid.RectangleToScreen(DatabasePokeGrid.ClientRectangle).Contains(MousePosition)) - return; - int oldval = SCR_Box.Value; - int newval = oldval + (e.Delta < 0 ? 1 : -1); - if (newval >= SCR_Box.Minimum && SCR_Box.Maximum >= newval) - FillPKXBoxes(SCR_Box.Value = newval); - } - - private void ChangeFormatFilter(object sender, EventArgs e) - { - if (CB_FormatComparator.SelectedIndex == 0) - { - CB_Format.Visible = false; // !any - CB_Format.SelectedIndex = 0; - } - else - { - CB_Format.Visible = true; - int index = MAXFORMAT - SAV.Generation + 1; - CB_Format.SelectedIndex = index < CB_Format.Items.Count ? index : 0; // SAV generation (offset by 1 for "Any") - } - } - - private void Menu_DeleteClones_Click(object sender, EventArgs e) - { - var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, - MsgDBDeleteCloneWarning + Environment.NewLine + - MsgDBDeleteCloneAdvice, MsgContinue); - - if (dr != DialogResult.Yes) - return; - - var deleted = 0; - var db = RawDB.Where(IsIndividualFilePKMDB) - .OrderByDescending(GetRevisedTime); - - const CloneDetectionMethod method = CloneDetectionMethod.HashDetails; - var hasher = SearchUtil.GetCloneDetectMethod(method); - var duplicates = SearchUtil.GetExtraClones(db, z => hasher(z.Entity)); - foreach (var entry in duplicates) - { - var src = entry.Source; - var path = ((SlotInfoFile)src).Path; - if (!File.Exists(path)) - continue; - - try { File.Delete(path); ++deleted; } - catch (Exception ex) { WinFormsUtil.Error(MsgDBDeleteCloneFail + Environment.NewLine + ex.Message + Environment.NewLine + path); } - } - - var boxClear = new BoxManipClearDuplicate(BoxManipType.DeleteClones, pk => SearchUtil.GetCloneDetectMethod(method)(pk)); - var param = new BoxManipParam(0, SAV.BoxCount); - int count = boxClear.Execute(SAV, param); - deleted += count; - - if (deleted == 0) - { WinFormsUtil.Alert(MsgDBDeleteCloneNone); return; } - - WinFormsUtil.Alert(string.Format(MsgFileDeleteCount, deleted), MsgWindowClose); - BoxView.ReloadSlots(); - - Close(); - } - - private static DateTime GetRevisedTime(SlotCache arg) - { - var src = arg.Source; - if (src is not SlotInfoFile f) - return DateTime.Now; - return File.GetLastWriteTimeUtc(f.Path); - } - - private bool IsBackupSaveFile(SlotCache pk) - { - return pk.SAV is not FakeSaveFile && pk.SAV != SAV; - } - - private bool IsIndividualFilePKMDB(SlotCache pk) - { - return pk.Source is SlotInfoFile f && f.Path.StartsWith(DatabasePath + Path.DirectorySeparatorChar, StringComparison.Ordinal); - } - - private void L_Viewed_MouseEnter(object sender, EventArgs e) => hover.SetToolTip(L_Viewed, L_Viewed.Text); - - private void ShowHoverTextForSlot(object sender, EventArgs e) - { - var pb = (PictureBox)sender; - int index = Array.IndexOf(PKXBOXES, pb); - if (!GetShiftedIndex(ref index)) - return; - - ShowSet.Show(pb, Results[index].Entity); + Path = path; + IgnoreBackupFiles = ignoreBackupFiles; } } + + private void LoadDatabase() + { + var settings = Main.Settings; + var otherPaths = new List(); + if (settings.EntityDb.SearchExtraSaves) + otherPaths.AddRange(settings.Backup.OtherBackupPaths.Where(Directory.Exists).Select(z => new SearchFolderDetail(z, true))); + if (settings.EntityDb.SearchBackups) + otherPaths.Add(new SearchFolderDetail(Main.BackupPath, false)); + + RawDB = LoadPKMSaves(DatabasePath, SAV, otherPaths, settings.EntityDb.SearchExtraSavesDeep); + + // Load stats for pk who do not have any + foreach (var entry in RawDB) + { + var pk = entry.Entity; + pk.ForcePartyData(); + } + + try + { + while (!IsHandleCreated) { } + BeginInvoke(new MethodInvoker(() => SetResults(RawDB))); + } + catch { /* Window Closed? */ } + } + + private static List LoadPKMSaves(string pkmdb, SaveFile sav, List otherPaths, bool otherDeep) + { + var dbTemp = new ConcurrentBag(); + var extensions = new HashSet(PKM.Extensions.Select(z => $".{z}")); + + var files = Directory.EnumerateFiles(pkmdb, "*", SearchOption.AllDirectories); + Parallel.ForEach(files, file => SlotInfoLoader.AddFromLocalFile(file, dbTemp, sav, extensions)); + + foreach (var folder in otherPaths) + { + if (!SaveUtil.GetSavesFromFolder(folder.Path, otherDeep, out IEnumerable paths, folder.IgnoreBackupFiles)) + continue; + + Parallel.ForEach(paths, file => TryAddPKMsFromSaveFilePath(dbTemp, file)); + } + + // Fetch from save file + SlotInfoLoader.AddFromSaveFile(sav, dbTemp); + var result = new List(dbTemp); + result.RemoveAll(z => !z.IsDataValid()); + + if (Main.Settings.EntityDb.FilterUnavailableSpecies) + { + static bool IsPresentInGameSWSH(ISpeciesForm pk) => pk is PK8 || PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form); + static bool IsPresentInGameBDSP(ISpeciesForm pk) => pk is PB8 || PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form); + static bool IsPresentInGamePLA (ISpeciesForm pk) => pk is PA8 || PersonalTable.LA .IsPresentInGame(pk.Species, pk.Form); + if (sav is SAV8SWSH) + result.RemoveAll(z => !IsPresentInGameSWSH(z.Entity)); + else if (sav is SAV8BS) + result.RemoveAll(z => !IsPresentInGameBDSP(z.Entity)); + else if (sav is SAV8LA) + result.RemoveAll(z => !IsPresentInGamePLA(z.Entity)); + } + + var sort = Main.Settings.EntityDb.InitialSortMode; + if (sort is DatabaseSortMode.SlotIdentity) + result.Sort(); + else if (sort is DatabaseSortMode.SpeciesForm) + result.Sort((first, second) => first.CompareToSpeciesForm(second)); + + // Finalize the Database + return result; + } + + private static void TryAddPKMsFromSaveFilePath(ConcurrentBag dbTemp, string file) + { + var sav = SaveUtil.GetVariantSAV(file); + if (sav == null) + { + Debug.WriteLine("Unable to load SaveFile: " + file); + return; + } + + SlotInfoLoader.AddFromSaveFile(sav, dbTemp); + } + + // IO Usage + private void OpenDB(object sender, EventArgs e) + { + if (Directory.Exists(DatabasePath)) + Process.Start("explorer.exe", DatabasePath); + } + + private void Menu_Export_Click(object sender, EventArgs e) + { + if (Results.Count == 0) + { WinFormsUtil.Alert(MsgDBCreateReportFail); return; } + + if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDBExportResultsPrompt)) + return; + + using var fbd = new FolderBrowserDialog(); + if (DialogResult.OK != fbd.ShowDialog()) + return; + + string path = fbd.SelectedPath; + Directory.CreateDirectory(path); + + foreach (var pk in Results.Select(z => z.Entity)) + File.WriteAllBytes(Path.Combine(path, Util.CleanFileName(pk.FileName)), pk.DecryptedPartyData); + } + + private void Menu_Import_Click(object sender, EventArgs e) + { + if (!BoxView.GetBulkImportSettings(out var clearAll, out var overwrite, out var noSetb)) + return; + + int box = BoxView.Box.CurrentBox; + int ctr = SAV.LoadBoxes(Results.Select(z => z.Entity), out var result, box, clearAll, overwrite, noSetb); + if (ctr <= 0) + return; + + BoxView.SetPKMBoxes(); + BoxView.UpdateBoxViewers(); + WinFormsUtil.Alert(result); + } + + // View Updates + private IEnumerable SearchDatabase() + { + var settings = GetSearchSettings(); + + IEnumerable res = RawDB; + + // pre-filter based on the file path (if specified) + if (!Menu_SearchBoxes.Checked) + res = res.Where(z => z.SAV != SAV); + if (!Menu_SearchDatabase.Checked) + res = res.Where(z => !IsIndividualFilePKMDB(z)); + if (!Menu_SearchBackups.Checked) + res = res.Where(z => !IsBackupSaveFile(z)); + + // return filtered results + return settings.Search(res); + } + + private SearchSettings GetSearchSettings() + { + var settings = new SearchSettings + { + Format = MAXFORMAT - CB_Format.SelectedIndex + 1, // 0->(n-1) => 1->n + SearchFormat = (SearchComparison)CB_FormatComparator.SelectedIndex, + Generation = CB_Generation.SelectedIndex, + + Version = WinFormsUtil.GetIndex(CB_GameOrigin), + HiddenPowerType = WinFormsUtil.GetIndex(CB_HPType), + + Species = WinFormsUtil.GetIndex(CB_Species), + Ability = WinFormsUtil.GetIndex(CB_Ability), + Nature = WinFormsUtil.GetIndex(CB_Nature), + Item = WinFormsUtil.GetIndex(CB_HeldItem), + + BatchInstructions = RTB_Instructions.Lines, + + Level = int.TryParse(TB_Level.Text, out var lvl) ? lvl : null, + SearchLevel = (SearchComparison)CB_Level.SelectedIndex, + EVType = CB_EVTrain.SelectedIndex, + IVType = CB_IV.SelectedIndex, + }; + + settings.AddMove(WinFormsUtil.GetIndex(CB_Move1)); + settings.AddMove(WinFormsUtil.GetIndex(CB_Move2)); + settings.AddMove(WinFormsUtil.GetIndex(CB_Move3)); + settings.AddMove(WinFormsUtil.GetIndex(CB_Move4)); + + if (CHK_Shiny.CheckState != CheckState.Indeterminate) + settings.SearchShiny = CHK_Shiny.CheckState == CheckState.Checked; + + if (CHK_IsEgg.CheckState != CheckState.Indeterminate) + { + settings.SearchEgg = CHK_IsEgg.CheckState == CheckState.Checked; + if (int.TryParse(MT_ESV.Text, out int esv)) + settings.ESV = esv; + } + + if (Menu_SearchLegal.Checked != Menu_SearchIllegal.Checked) + settings.SearchLegal = Menu_SearchLegal.Checked; + + if (Menu_SearchClones.Checked) + { + settings.SearchClones = ModifierKeys switch + { + Keys.Control => CloneDetectionMethod.HashPID, + _ => CloneDetectionMethod.HashDetails, + }; + } + + return settings; + } + + private async void B_Search_Click(object sender, EventArgs e) + { + B_Search.Enabled = false; + var search = SearchDatabase(); + + bool legalSearch = Menu_SearchLegal.Checked ^ Menu_SearchIllegal.Checked; + bool wordFilter = ParseSettings.CheckWordFilter; + if (wordFilter && legalSearch && WinFormsUtil.Prompt(MessageBoxButtons.YesNo, MsgDBSearchLegalityWordfilter) == DialogResult.No) + ParseSettings.CheckWordFilter = false; + var results = await Task.Run(() => search.ToList()).ConfigureAwait(true); + ParseSettings.CheckWordFilter = wordFilter; + + if (results.Count == 0) + { + if (!Menu_SearchBoxes.Checked && !Menu_SearchDatabase.Checked && !Menu_SearchBackups.Checked) + WinFormsUtil.Alert(MsgDBSearchFail, MsgDBSearchNone); + else + WinFormsUtil.Alert(MsgDBSearchNone); + } + SetResults(results); // updates Count Label as well. + System.Media.SystemSounds.Asterisk.Play(); + B_Search.Enabled = true; + } + + private void UpdateScroll(object sender, ScrollEventArgs e) + { + if (e.OldValue != e.NewValue) + FillPKXBoxes(e.NewValue); + } + + private void SetResults(List res) + { + Results = res; + ShowSet.Clear(); + + SCR_Box.Maximum = (int)Math.Ceiling((decimal)Results.Count / RES_MIN); + if (SCR_Box.Maximum > 0) SCR_Box.Maximum--; + + slotSelected = -1; // reset the slot last viewed + SCR_Box.Value = 0; + FillPKXBoxes(0); + + L_Count.Text = string.Format(Counter, Results.Count); + B_Search.Enabled = true; + } + + private void FillPKXBoxes(int start) + { + if (Results.Count == 0) + { + for (int i = 0; i < RES_MAX; i++) + { + PKXBOXES[i].Image = null; + PKXBOXES[i].BackgroundImage = null; + } + return; + } + int begin = start*RES_MIN; + int end = Math.Min(RES_MAX, Results.Count - begin); + for (int i = 0; i < end; i++) + PKXBOXES[i].Image = Results[i + begin].Entity.Sprite(SAV, -1, -1, true); + for (int i = end; i < RES_MAX; i++) + PKXBOXES[i].Image = null; + + for (int i = 0; i < RES_MAX; i++) + PKXBOXES[i].BackgroundImage = SpriteUtil.Spriter.Transparent; + if (slotSelected != -1 && slotSelected >= begin && slotSelected < begin + RES_MAX) + PKXBOXES[slotSelected - begin].BackgroundImage = slotColor ?? SpriteUtil.Spriter.View; + } + + // Misc Update Methods + private void ToggleESV(object sender, EventArgs e) => L_ESV.Visible = MT_ESV.Visible = CHK_IsEgg.CheckState == CheckState.Checked; + + private void ChangeLevel(object sender, EventArgs e) + { + if (CB_Level.SelectedIndex == 0) + TB_Level.Text = string.Empty; + } + + private void ChangeGame(object sender, EventArgs e) + { + if (CB_GameOrigin.SelectedIndex != 0) + CB_Generation.SelectedIndex = 0; + } + + private void ChangeGeneration(object sender, EventArgs e) + { + if (CB_Generation.SelectedIndex != 0) + CB_GameOrigin.SelectedIndex = 0; + } + + private void Menu_Exit_Click(object sender, EventArgs e) => Close(); + + protected override void OnMouseWheel(MouseEventArgs e) + { + if (!DatabasePokeGrid.RectangleToScreen(DatabasePokeGrid.ClientRectangle).Contains(MousePosition)) + return; + int oldval = SCR_Box.Value; + int newval = oldval + (e.Delta < 0 ? 1 : -1); + if (newval >= SCR_Box.Minimum && SCR_Box.Maximum >= newval) + FillPKXBoxes(SCR_Box.Value = newval); + } + + private void ChangeFormatFilter(object sender, EventArgs e) + { + if (CB_FormatComparator.SelectedIndex == 0) + { + CB_Format.Visible = false; // !any + CB_Format.SelectedIndex = 0; + } + else + { + CB_Format.Visible = true; + int index = MAXFORMAT - SAV.Generation + 1; + CB_Format.SelectedIndex = index < CB_Format.Items.Count ? index : 0; // SAV generation (offset by 1 for "Any") + } + } + + private void Menu_DeleteClones_Click(object sender, EventArgs e) + { + var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, + MsgDBDeleteCloneWarning + Environment.NewLine + + MsgDBDeleteCloneAdvice, MsgContinue); + + if (dr != DialogResult.Yes) + return; + + var deleted = 0; + var db = RawDB.Where(IsIndividualFilePKMDB) + .OrderByDescending(GetRevisedTime); + + const CloneDetectionMethod method = CloneDetectionMethod.HashDetails; + var hasher = SearchUtil.GetCloneDetectMethod(method); + var duplicates = SearchUtil.GetExtraClones(db, z => hasher(z.Entity)); + foreach (var entry in duplicates) + { + var src = entry.Source; + var path = ((SlotInfoFile)src).Path; + if (!File.Exists(path)) + continue; + + try { File.Delete(path); ++deleted; } + catch (Exception ex) { WinFormsUtil.Error(MsgDBDeleteCloneFail + Environment.NewLine + ex.Message + Environment.NewLine + path); } + } + + var boxClear = new BoxManipClearDuplicate(BoxManipType.DeleteClones, pk => SearchUtil.GetCloneDetectMethod(method)(pk)); + var param = new BoxManipParam(0, SAV.BoxCount); + int count = boxClear.Execute(SAV, param); + deleted += count; + + if (deleted == 0) + { WinFormsUtil.Alert(MsgDBDeleteCloneNone); return; } + + WinFormsUtil.Alert(string.Format(MsgFileDeleteCount, deleted), MsgWindowClose); + BoxView.ReloadSlots(); + + Close(); + } + + private static DateTime GetRevisedTime(SlotCache arg) + { + var src = arg.Source; + if (src is not SlotInfoFile f) + return DateTime.Now; + return File.GetLastWriteTimeUtc(f.Path); + } + + private bool IsBackupSaveFile(SlotCache pk) => pk.SAV is not FakeSaveFile && pk.SAV != SAV; + private bool IsIndividualFilePKMDB(SlotCache pk) => pk.Source is SlotInfoFile f && f.Path.StartsWith(DatabasePath + Path.DirectorySeparatorChar, StringComparison.Ordinal); + + private void L_Viewed_MouseEnter(object sender, EventArgs e) => hover.SetToolTip(L_Viewed, L_Viewed.Text); + + private void ShowHoverTextForSlot(object sender, EventArgs e) + { + var pb = (PictureBox)sender; + int index = Array.IndexOf(PKXBOXES, pb); + if (!GetShiftedIndex(ref index)) + return; + + ShowSet.Show(pb, Results[index].Entity); + } } diff --git a/PKHeX.WinForms/Subforms/SAV_Encounters.Designer.cs b/PKHeX.WinForms/Subforms/SAV_Encounters.Designer.cs index af1212a80..27bc12869 100644 --- a/PKHeX.WinForms/Subforms/SAV_Encounters.Designer.cs +++ b/PKHeX.WinForms/Subforms/SAV_Encounters.Designer.cs @@ -562,4 +562,4 @@ private void InitializeComponent() public System.Windows.Forms.CheckBox CHK_IsEgg; public System.Windows.Forms.CheckBox CHK_Shiny; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/SAV_Encounters.cs b/PKHeX.WinForms/Subforms/SAV_Encounters.cs index 041ae15e1..2c34212ba 100644 --- a/PKHeX.WinForms/Subforms/SAV_Encounters.cs +++ b/PKHeX.WinForms/Subforms/SAV_Encounters.cs @@ -13,435 +13,434 @@ using PKHeX.WinForms.Properties; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class SAV_Encounters : Form { - public partial class SAV_Encounters : Form + private readonly PKMEditor PKME_Tabs; + private SaveFile SAV => PKME_Tabs.RequestSaveFile; + private readonly SummaryPreviewer ShowSet = new(); + private readonly TrainerDatabase Trainers; + private readonly CancellationTokenSource TokenSource = new(); + + public SAV_Encounters(PKMEditor f1, TrainerDatabase db) { - private readonly PKMEditor PKME_Tabs; - private SaveFile SAV => PKME_Tabs.RequestSaveFile; - private readonly SummaryPreviewer ShowSet = new(); - private readonly TrainerDatabase Trainers; - private readonly CancellationTokenSource TokenSource = new(); + InitializeComponent(); - public SAV_Encounters(PKMEditor f1, TrainerDatabase db) + PKME_Tabs = f1; + Trainers = db; + + var grid = EncounterPokeGrid; + var smallWidth = grid.Width; + var smallHeight = grid.Height; + grid.InitializeGrid(6, 11, SpriteUtil.Spriter); + grid.SetBackground(Resources.box_wp_clean); + var newWidth = grid.Width; + var newHeight = grid.Height; + var wdelta = newWidth - smallWidth; + if (wdelta != 0) + Width += wdelta; + var hdelta = newHeight - smallHeight; + if (hdelta != 0) + Height += hdelta; + + PKXBOXES = grid.Entries.ToArray(); + + // Enable Scrolling when hovered over + foreach (var slot in PKXBOXES) { - InitializeComponent(); - - PKME_Tabs = f1; - Trainers = db; - - var grid = EncounterPokeGrid; - var smallWidth = grid.Width; - var smallHeight = grid.Height; - grid.InitializeGrid(6, 11, SpriteUtil.Spriter); - grid.SetBackground(Resources.box_wp_clean); - var newWidth = grid.Width; - var newHeight = grid.Height; - var wdelta = newWidth - smallWidth; - if (wdelta != 0) - Width += wdelta; - var hdelta = newHeight - smallHeight; - if (hdelta != 0) - Height += hdelta; - - PKXBOXES = grid.Entries.ToArray(); - - // Enable Scrolling when hovered over - foreach (var slot in PKXBOXES) + // Enable Click + slot.MouseClick += (sender, e) => { - // Enable Click - slot.MouseClick += (sender, e) => - { - if (sender == null) - return; - if (ModifierKeys == Keys.Control) - ClickView(sender, e); - }; - slot.ContextMenuStrip = mnu; - if (Main.Settings.Hover.HoverSlotShowText) - slot.MouseEnter += (o, args) => ShowHoverTextForSlot(slot, args); - } - - Counter = L_Count.Text; - L_Viewed.Text = string.Empty; // invis for now - L_Viewed.MouseEnter += (sender, e) => hover.SetToolTip(L_Viewed, L_Viewed.Text); - PopulateComboBoxes(); - - WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); - GetTypeFilters(); - - // Load Data - L_Count.Text = "Ready..."; - - CenterToParent(); - } - - private void GetTypeFilters() - { - var types = (EncounterOrder[])Enum.GetValues(typeof(EncounterOrder)); - var checks = types.Select(z => new CheckBox - { - Name = z.ToString(), - Text = z.ToString(), - AutoSize = true, - Checked = true, - Padding = Padding.Empty, - Margin = Padding.Empty, - }).ToArray(); - foreach (var chk in checks) - { - TypeFilters.Controls.Add(chk); - TypeFilters.SetFlowBreak(chk, true); - } - } - - private EncounterOrder[] GetTypes() - { - return TypeFilters.Controls.OfType().Where(z => z.Checked).Select(z => z.Name) - .Select(z => (EncounterOrder)Enum.Parse(typeof(EncounterOrder), z)).ToArray(); - } - - private readonly PictureBox[] PKXBOXES; - private List Results = new(); - private int slotSelected = -1; // = null; - private Image? slotColor; - private const int RES_MAX = 66; - private const int RES_MIN = 6; - private readonly string Counter; - - private bool GetShiftedIndex(ref int index) - { - if (index >= RES_MAX) - return false; - index += SCR_Box.Value * RES_MIN; - return index < Results.Count; - } - - // Important Events - private void ClickView(object sender, EventArgs e) - { - var pb = WinFormsUtil.GetUnderlyingControl(sender); - int index = Array.IndexOf(PKXBOXES, pb); - if (index >= RES_MAX) - { - System.Media.SystemSounds.Exclamation.Play(); - return; - } - index += SCR_Box.Value * RES_MIN; - if (index >= Results.Count) - { - System.Media.SystemSounds.Exclamation.Play(); - return; - } - - var enc = Results[index]; - var criteria = GetCriteria(enc, Main.Settings.EncounterDb); - var trainer = Trainers.GetTrainer(enc.Version, enc.Generation <= 2 ? (LanguageID)SAV.Language : null) ?? SAV; - var pk = enc.ConvertToPKM(trainer, criteria); - pk.RefreshChecksum(); - PKME_Tabs.PopulateFields(pk, false); - slotSelected = index; - slotColor = SpriteUtil.Spriter.View; - FillPKXBoxes(SCR_Box.Value); - } - - private EncounterCriteria GetCriteria(ISpeciesForm enc, EncounterDatabaseSettings settings) - { - if (!settings.UseTabsAsCriteria) - return EncounterCriteria.Unrestricted; - - var editor = PKME_Tabs.Data; - var tree = EvolutionTree.GetEvolutionTree(editor.Context); - bool isInChain = tree.IsSpeciesDerivedFrom(editor.Species, editor.Form, enc.Species, enc.Form); - - if (!settings.UseTabsAsCriteriaAnySpecies) - { - if (!isInChain) - return EncounterCriteria.Unrestricted; - } - - var set = new ShowdownSet(editor); - var criteria = EncounterCriteria.GetCriteria(set, editor.PersonalInfo); - if (!isInChain) - criteria = criteria with {Gender = -1}; // Genderless tabs and a gendered enc -> let's play safe. - return criteria; - } - - private void PopulateComboBoxes() - { - // Set the Text - CB_Species.InitializeBinding(); - CB_GameOrigin.InitializeBinding(); - - var Any = new ComboItem(MsgAny, -1); - - var DS_Species = new List(GameInfo.SpeciesDataSource); - DS_Species.RemoveAt(0); DS_Species.Insert(0, Any); CB_Species.DataSource = DS_Species; - - // Set the Move ComboBoxes too.. - var DS_Move = new List(GameInfo.MoveDataSource); - DS_Move.RemoveAt(0); DS_Move.Insert(0, Any); - { - foreach (ComboBox cb in new[] { CB_Move1, CB_Move2, CB_Move3, CB_Move4 }) - { - cb.InitializeBinding(); - cb.DataSource = new BindingSource(DS_Move, null); - } - } - - var DS_Version = new List(GameInfo.VersionDataSource); - DS_Version.Insert(0, Any); CB_GameOrigin.DataSource = DS_Version; - - // Trigger a Reset - ResetFilters(this, EventArgs.Empty); - } - - private void ResetFilters(object sender, EventArgs e) - { - CB_Species.SelectedIndex = 0; - CB_Move1.SelectedIndex = CB_Move2.SelectedIndex = CB_Move3.SelectedIndex = CB_Move4.SelectedIndex = 0; - CB_GameOrigin.SelectedIndex = 0; - - RTB_Instructions.Clear(); - if (sender == this) - return; // still starting up - foreach (var chk in TypeFilters.Controls.OfType()) - chk.Checked = true; - - System.Media.SystemSounds.Asterisk.Play(); - } - - // View Updates - private IEnumerable SearchDatabase(CancellationToken token) - { - var settings = GetSearchSettings(); - var moves = settings.Moves.ToArray(); - - // If nothing is specified, instead of just returning all possible encounters, just return nothing. - if (settings.Species <= 0 && moves.Length == 0 && Main.Settings.EncounterDb.ReturnNoneIfEmptySearch) - return Array.Empty(); - var pk = SAV.BlankPKM; - - var versions = settings.GetVersions(SAV); - var species = settings.Species <= 0 ? Enumerable.Range(1, SAV.MaxSpeciesID) : new[] { settings.Species }; - var results = GetAllSpeciesFormEncounters(species, SAV.Personal, versions, moves, pk, token); - if (settings.SearchEgg != null) - results = results.Where(z => z.EggEncounter == settings.SearchEgg); - if (settings.SearchShiny != null) - results = results.Where(z => z.IsShiny == settings.SearchShiny); - - // return filtered results - var comparer = new ReferenceComparer(); - results = results.Distinct(comparer); // only distinct objects - - if (Main.Settings.EncounterDb.FilterUnavailableSpecies) - { - static bool IsPresentInGameSWSH(ISpeciesForm pk) => PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form); - static bool IsPresentInGameBDSP(ISpeciesForm pk) => PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form); - static bool IsPresentInGameLA (ISpeciesForm pk) => PersonalTable.LA .IsPresentInGame(pk.Species, pk.Form); - results = SAV switch - { - SAV8SWSH => results.Where(IsPresentInGameSWSH), - SAV8BS => results.Where(IsPresentInGameBDSP), - SAV8LA => results.Where(IsPresentInGameLA), - _ => results.Where(z => z.Generation <= 7), - }; - } - - if (token.IsCancellationRequested) - return results; - - if (RTB_Instructions.Lines.Any(line => line.Length > 0)) - { - var filters = StringInstruction.GetFilters(RTB_Instructions.Lines).ToArray(); - BatchEditing.ScreenStrings(filters); - results = results.Where(enc => BatchEditing.IsFilterMatch(filters, enc)); // Compare across all filters - } - - return results; - } - - private static IEnumerable GetAllSpeciesFormEncounters(IEnumerable species, PersonalTable pt, IReadOnlyList versions, int[] moves, PKM pk, CancellationToken token) - { - foreach (var s in species) - { - if (token.IsCancellationRequested) - break; - - var pi = pt.GetFormEntry(s, 0); - var fc = pi.FormCount; - if (fc == 0 && !Main.Settings.EncounterDb.FilterUnavailableSpecies) // not present in game - { - // try again using past-gen table - pi = PersonalTable.USUM.GetFormEntry(s, 0); - fc = pi.FormCount; - } - for (int f = 0; f < fc; f++) - { - if (FormInfo.IsBattleOnlyForm(s, f, pk.Format)) - continue; - var encs = GetEncounters(s, f, moves, pk, versions); - foreach (var enc in encs) - yield return enc; - } - } - } - - private sealed class ReferenceComparer : IEqualityComparer where T : class - { - public bool Equals(T? x, T? y) - { - if (x == null) - return false; - if (y == null) - return false; - return RuntimeHelpers.GetHashCode(x).Equals(RuntimeHelpers.GetHashCode(y)); - } - - public int GetHashCode(T obj) - { - if (obj == null) throw new ArgumentNullException(nameof(obj)); - return RuntimeHelpers.GetHashCode(obj); - } - } - - private static IEnumerable GetEncounters(int species, int form, int[] moves, PKM pk, IReadOnlyList vers) - { - pk.Species = species; - pk.Form = form; - pk.SetGender(pk.GetSaneGender()); - return EncounterMovesetGenerator.GenerateEncounters(pk, moves, vers); - } - - private SearchSettings GetSearchSettings() - { - var settings = new SearchSettings - { - Format = SAV.Generation, // 0->(n-1) => 1->n - Generation = SAV.Generation, - - Species = WinFormsUtil.GetIndex(CB_Species), - - BatchInstructions = RTB_Instructions.Lines, - Version = WinFormsUtil.GetIndex(CB_GameOrigin), + if (sender == null) + return; + if (ModifierKeys == Keys.Control) + ClickView(sender, e); }; - - settings.AddMove(WinFormsUtil.GetIndex(CB_Move1)); - settings.AddMove(WinFormsUtil.GetIndex(CB_Move2)); - settings.AddMove(WinFormsUtil.GetIndex(CB_Move3)); - settings.AddMove(WinFormsUtil.GetIndex(CB_Move4)); - - if (CHK_IsEgg.CheckState != CheckState.Indeterminate) - settings.SearchEgg = CHK_IsEgg.CheckState == CheckState.Checked; - - if (CHK_Shiny.CheckState != CheckState.Indeterminate) - settings.SearchShiny = CHK_Shiny.CheckState == CheckState.Checked; - - return settings; + slot.ContextMenuStrip = mnu; + if (Main.Settings.Hover.HoverSlotShowText) + slot.MouseEnter += (o, args) => ShowHoverTextForSlot(slot, args); } - private async void B_Search_Click(object sender, EventArgs e) - { - B_Search.Enabled = false; - EncounterMovesetGenerator.PriorityList = GetTypes(); + Counter = L_Count.Text; + L_Viewed.Text = string.Empty; // invis for now + L_Viewed.MouseEnter += (sender, e) => hover.SetToolTip(L_Viewed, L_Viewed.Text); + PopulateComboBoxes(); - var token = TokenSource.Token; - var search = SearchDatabase(token); - if (token.IsCancellationRequested) - return; + WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage); + GetTypeFilters(); - var results = await Task.Run(() => search.ToList(), token).ConfigureAwait(true); - if (token.IsCancellationRequested) - return; + // Load Data + L_Count.Text = "Ready..."; - if (results.Count == 0) - WinFormsUtil.Alert(MsgDBSearchNone); - - SetResults(results); // updates Count Label as well. - System.Media.SystemSounds.Asterisk.Play(); - B_Search.Enabled = true; - EncounterMovesetGenerator.ResetFilters(); - } - - private void UpdateScroll(object sender, ScrollEventArgs e) - { - if (e.OldValue != e.NewValue) - FillPKXBoxes(e.NewValue); - } - - private void SetResults(List res) - { - Results = res; - ShowSet.Clear(); - - SCR_Box.Maximum = (int)Math.Ceiling((decimal)Results.Count / RES_MIN); - if (SCR_Box.Maximum > 0) SCR_Box.Maximum--; - - slotSelected = -1; // reset the slot last viewed - SCR_Box.Value = 0; - FillPKXBoxes(0); - - L_Count.Text = string.Format(Counter, Results.Count); - B_Search.Enabled = true; - } - - private void FillPKXBoxes(int start) - { - var boxes = PKXBOXES; - if (Results.Count == 0) - { - for (int i = 0; i < RES_MAX; i++) - { - boxes[i].Image = null; - boxes[i].BackgroundImage = null; - } - return; - } - - // Load new sprites - int begin = start*RES_MIN; - int end = Math.Min(RES_MAX, Results.Count - begin); - for (int i = 0; i < end; i++) - { - var enc = Results[i + begin]; - boxes[i].Image = enc.Sprite(); - } - - // Clear empty slots - for (int i = end; i < RES_MAX; i++) - boxes[i].Image = null; - - // Reset backgrounds for all - for (int i = 0; i < RES_MAX; i++) - boxes[i].BackgroundImage = SpriteUtil.Spriter.Transparent; - - // Reload last viewed index's background if still within view - if (slotSelected != -1 && slotSelected >= begin && slotSelected < begin + RES_MAX) - boxes[slotSelected - begin].BackgroundImage = slotColor ?? SpriteUtil.Spriter.View; - } - - private void Menu_Exit_Click(object sender, EventArgs e) => Close(); - - protected override void OnMouseWheel(MouseEventArgs e) - { - if (!EncounterPokeGrid.RectangleToScreen(EncounterPokeGrid.ClientRectangle).Contains(MousePosition)) - return; - int oldval = SCR_Box.Value; - int newval = oldval + (e.Delta < 0 ? 1 : -1); - if (newval >= SCR_Box.Minimum && SCR_Box.Maximum >= newval) - FillPKXBoxes(SCR_Box.Value = newval); - } - - private void ShowHoverTextForSlot(object sender, EventArgs e) - { - var pb = (PictureBox)sender; - int index = Array.IndexOf(PKXBOXES, pb); - if (!GetShiftedIndex(ref index)) - return; - - ShowSet.Show(pb, Results[index]); - } - - private void SAV_Encounters_FormClosing(object sender, FormClosingEventArgs e) => TokenSource.Cancel(); + CenterToParent(); } + + private void GetTypeFilters() + { + var types = (EncounterOrder[])Enum.GetValues(typeof(EncounterOrder)); + var checks = types.Select(z => new CheckBox + { + Name = z.ToString(), + Text = z.ToString(), + AutoSize = true, + Checked = true, + Padding = Padding.Empty, + Margin = Padding.Empty, + }).ToArray(); + foreach (var chk in checks) + { + TypeFilters.Controls.Add(chk); + TypeFilters.SetFlowBreak(chk, true); + } + } + + private EncounterOrder[] GetTypes() + { + return TypeFilters.Controls.OfType().Where(z => z.Checked).Select(z => z.Name) + .Select(z => (EncounterOrder)Enum.Parse(typeof(EncounterOrder), z)).ToArray(); + } + + private readonly PictureBox[] PKXBOXES; + private List Results = new(); + private int slotSelected = -1; // = null; + private Image? slotColor; + private const int RES_MAX = 66; + private const int RES_MIN = 6; + private readonly string Counter; + + private bool GetShiftedIndex(ref int index) + { + if (index >= RES_MAX) + return false; + index += SCR_Box.Value * RES_MIN; + return index < Results.Count; + } + + // Important Events + private void ClickView(object sender, EventArgs e) + { + var pb = WinFormsUtil.GetUnderlyingControl(sender); + int index = Array.IndexOf(PKXBOXES, pb); + if (index >= RES_MAX) + { + System.Media.SystemSounds.Exclamation.Play(); + return; + } + index += SCR_Box.Value * RES_MIN; + if (index >= Results.Count) + { + System.Media.SystemSounds.Exclamation.Play(); + return; + } + + var enc = Results[index]; + var criteria = GetCriteria(enc, Main.Settings.EncounterDb); + var trainer = Trainers.GetTrainer(enc.Version, enc.Generation <= 2 ? (LanguageID)SAV.Language : null) ?? SAV; + var pk = enc.ConvertToPKM(trainer, criteria); + pk.RefreshChecksum(); + PKME_Tabs.PopulateFields(pk, false); + slotSelected = index; + slotColor = SpriteUtil.Spriter.View; + FillPKXBoxes(SCR_Box.Value); + } + + private EncounterCriteria GetCriteria(ISpeciesForm enc, EncounterDatabaseSettings settings) + { + if (!settings.UseTabsAsCriteria) + return EncounterCriteria.Unrestricted; + + var editor = PKME_Tabs.Data; + var tree = EvolutionTree.GetEvolutionTree(editor.Context); + bool isInChain = tree.IsSpeciesDerivedFrom(editor.Species, editor.Form, enc.Species, enc.Form); + + if (!settings.UseTabsAsCriteriaAnySpecies) + { + if (!isInChain) + return EncounterCriteria.Unrestricted; + } + + var set = new ShowdownSet(editor); + var criteria = EncounterCriteria.GetCriteria(set, editor.PersonalInfo); + if (!isInChain) + criteria = criteria with {Gender = -1}; // Genderless tabs and a gendered enc -> let's play safe. + return criteria; + } + + private void PopulateComboBoxes() + { + // Set the Text + CB_Species.InitializeBinding(); + CB_GameOrigin.InitializeBinding(); + + var Any = new ComboItem(MsgAny, -1); + + var DS_Species = new List(GameInfo.SpeciesDataSource); + DS_Species.RemoveAt(0); DS_Species.Insert(0, Any); CB_Species.DataSource = DS_Species; + + // Set the Move ComboBoxes too.. + var DS_Move = new List(GameInfo.MoveDataSource); + DS_Move.RemoveAt(0); DS_Move.Insert(0, Any); + { + foreach (ComboBox cb in new[] { CB_Move1, CB_Move2, CB_Move3, CB_Move4 }) + { + cb.InitializeBinding(); + cb.DataSource = new BindingSource(DS_Move, null); + } + } + + var DS_Version = new List(GameInfo.VersionDataSource); + DS_Version.Insert(0, Any); CB_GameOrigin.DataSource = DS_Version; + + // Trigger a Reset + ResetFilters(this, EventArgs.Empty); + } + + private void ResetFilters(object sender, EventArgs e) + { + CB_Species.SelectedIndex = 0; + CB_Move1.SelectedIndex = CB_Move2.SelectedIndex = CB_Move3.SelectedIndex = CB_Move4.SelectedIndex = 0; + CB_GameOrigin.SelectedIndex = 0; + + RTB_Instructions.Clear(); + if (sender == this) + return; // still starting up + foreach (var chk in TypeFilters.Controls.OfType()) + chk.Checked = true; + + System.Media.SystemSounds.Asterisk.Play(); + } + + // View Updates + private IEnumerable SearchDatabase(CancellationToken token) + { + var settings = GetSearchSettings(); + var moves = settings.Moves.ToArray(); + + // If nothing is specified, instead of just returning all possible encounters, just return nothing. + if (settings.Species <= 0 && moves.Length == 0 && Main.Settings.EncounterDb.ReturnNoneIfEmptySearch) + return Array.Empty(); + var pk = SAV.BlankPKM; + + var versions = settings.GetVersions(SAV); + var species = settings.Species <= 0 ? Enumerable.Range(1, SAV.MaxSpeciesID) : new[] { settings.Species }; + var results = GetAllSpeciesFormEncounters(species, SAV.Personal, versions, moves, pk, token); + if (settings.SearchEgg != null) + results = results.Where(z => z.EggEncounter == settings.SearchEgg); + if (settings.SearchShiny != null) + results = results.Where(z => z.IsShiny == settings.SearchShiny); + + // return filtered results + var comparer = new ReferenceComparer(); + results = results.Distinct(comparer); // only distinct objects + + if (Main.Settings.EncounterDb.FilterUnavailableSpecies) + { + static bool IsPresentInGameSWSH(ISpeciesForm pk) => PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form); + static bool IsPresentInGameBDSP(ISpeciesForm pk) => PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form); + static bool IsPresentInGameLA (ISpeciesForm pk) => PersonalTable.LA .IsPresentInGame(pk.Species, pk.Form); + results = SAV switch + { + SAV8SWSH => results.Where(IsPresentInGameSWSH), + SAV8BS => results.Where(IsPresentInGameBDSP), + SAV8LA => results.Where(IsPresentInGameLA), + _ => results.Where(z => z.Generation <= 7), + }; + } + + if (token.IsCancellationRequested) + return results; + + if (RTB_Instructions.Lines.Any(line => line.Length > 0)) + { + var filters = StringInstruction.GetFilters(RTB_Instructions.Lines).ToArray(); + BatchEditing.ScreenStrings(filters); + results = results.Where(enc => BatchEditing.IsFilterMatch(filters, enc)); // Compare across all filters + } + + return results; + } + + private static IEnumerable GetAllSpeciesFormEncounters(IEnumerable species, PersonalTable pt, IReadOnlyList versions, int[] moves, PKM pk, CancellationToken token) + { + foreach (var s in species) + { + if (token.IsCancellationRequested) + break; + + var pi = pt.GetFormEntry(s, 0); + var fc = pi.FormCount; + if (fc == 0 && !Main.Settings.EncounterDb.FilterUnavailableSpecies) // not present in game + { + // try again using past-gen table + pi = PersonalTable.USUM.GetFormEntry(s, 0); + fc = pi.FormCount; + } + for (int f = 0; f < fc; f++) + { + if (FormInfo.IsBattleOnlyForm(s, f, pk.Format)) + continue; + var encs = GetEncounters(s, f, moves, pk, versions); + foreach (var enc in encs) + yield return enc; + } + } + } + + private sealed class ReferenceComparer : IEqualityComparer where T : class + { + public bool Equals(T? x, T? y) + { + if (x == null) + return false; + if (y == null) + return false; + return RuntimeHelpers.GetHashCode(x).Equals(RuntimeHelpers.GetHashCode(y)); + } + + public int GetHashCode(T obj) + { + if (obj == null) throw new ArgumentNullException(nameof(obj)); + return RuntimeHelpers.GetHashCode(obj); + } + } + + private static IEnumerable GetEncounters(int species, int form, int[] moves, PKM pk, IReadOnlyList vers) + { + pk.Species = species; + pk.Form = form; + pk.SetGender(pk.GetSaneGender()); + return EncounterMovesetGenerator.GenerateEncounters(pk, moves, vers); + } + + private SearchSettings GetSearchSettings() + { + var settings = new SearchSettings + { + Format = SAV.Generation, // 0->(n-1) => 1->n + Generation = SAV.Generation, + + Species = WinFormsUtil.GetIndex(CB_Species), + + BatchInstructions = RTB_Instructions.Lines, + Version = WinFormsUtil.GetIndex(CB_GameOrigin), + }; + + settings.AddMove(WinFormsUtil.GetIndex(CB_Move1)); + settings.AddMove(WinFormsUtil.GetIndex(CB_Move2)); + settings.AddMove(WinFormsUtil.GetIndex(CB_Move3)); + settings.AddMove(WinFormsUtil.GetIndex(CB_Move4)); + + if (CHK_IsEgg.CheckState != CheckState.Indeterminate) + settings.SearchEgg = CHK_IsEgg.CheckState == CheckState.Checked; + + if (CHK_Shiny.CheckState != CheckState.Indeterminate) + settings.SearchShiny = CHK_Shiny.CheckState == CheckState.Checked; + + return settings; + } + + private async void B_Search_Click(object sender, EventArgs e) + { + B_Search.Enabled = false; + EncounterMovesetGenerator.PriorityList = GetTypes(); + + var token = TokenSource.Token; + var search = SearchDatabase(token); + if (token.IsCancellationRequested) + return; + + var results = await Task.Run(() => search.ToList(), token).ConfigureAwait(true); + if (token.IsCancellationRequested) + return; + + if (results.Count == 0) + WinFormsUtil.Alert(MsgDBSearchNone); + + SetResults(results); // updates Count Label as well. + System.Media.SystemSounds.Asterisk.Play(); + B_Search.Enabled = true; + EncounterMovesetGenerator.ResetFilters(); + } + + private void UpdateScroll(object sender, ScrollEventArgs e) + { + if (e.OldValue != e.NewValue) + FillPKXBoxes(e.NewValue); + } + + private void SetResults(List res) + { + Results = res; + ShowSet.Clear(); + + SCR_Box.Maximum = (int)Math.Ceiling((decimal)Results.Count / RES_MIN); + if (SCR_Box.Maximum > 0) SCR_Box.Maximum--; + + slotSelected = -1; // reset the slot last viewed + SCR_Box.Value = 0; + FillPKXBoxes(0); + + L_Count.Text = string.Format(Counter, Results.Count); + B_Search.Enabled = true; + } + + private void FillPKXBoxes(int start) + { + var boxes = PKXBOXES; + if (Results.Count == 0) + { + for (int i = 0; i < RES_MAX; i++) + { + boxes[i].Image = null; + boxes[i].BackgroundImage = null; + } + return; + } + + // Load new sprites + int begin = start*RES_MIN; + int end = Math.Min(RES_MAX, Results.Count - begin); + for (int i = 0; i < end; i++) + { + var enc = Results[i + begin]; + boxes[i].Image = enc.Sprite(); + } + + // Clear empty slots + for (int i = end; i < RES_MAX; i++) + boxes[i].Image = null; + + // Reset backgrounds for all + for (int i = 0; i < RES_MAX; i++) + boxes[i].BackgroundImage = SpriteUtil.Spriter.Transparent; + + // Reload last viewed index's background if still within view + if (slotSelected != -1 && slotSelected >= begin && slotSelected < begin + RES_MAX) + boxes[slotSelected - begin].BackgroundImage = slotColor ?? SpriteUtil.Spriter.View; + } + + private void Menu_Exit_Click(object sender, EventArgs e) => Close(); + + protected override void OnMouseWheel(MouseEventArgs e) + { + if (!EncounterPokeGrid.RectangleToScreen(EncounterPokeGrid.ClientRectangle).Contains(MousePosition)) + return; + int oldval = SCR_Box.Value; + int newval = oldval + (e.Delta < 0 ? 1 : -1); + if (newval >= SCR_Box.Minimum && SCR_Box.Maximum >= newval) + FillPKXBoxes(SCR_Box.Value = newval); + } + + private void ShowHoverTextForSlot(object sender, EventArgs e) + { + var pb = (PictureBox)sender; + int index = Array.IndexOf(PKXBOXES, pb); + if (!GetShiftedIndex(ref index)) + return; + + ShowSet.Show(pb, Results[index]); + } + + private void SAV_Encounters_FormClosing(object sender, FormClosingEventArgs e) => TokenSource.Cancel(); } diff --git a/PKHeX.WinForms/Subforms/SAV_FolderList.Designer.cs b/PKHeX.WinForms/Subforms/SAV_FolderList.Designer.cs index 4f29414cf..872fb7b2a 100644 --- a/PKHeX.WinForms/Subforms/SAV_FolderList.Designer.cs +++ b/PKHeX.WinForms/Subforms/SAV_FolderList.Designer.cs @@ -192,4 +192,4 @@ private void InitializeComponent() private System.Windows.Forms.ComboBox CB_FilterColumn; private System.Windows.Forms.TextBox TB_FilterTextContains; } -} \ No newline at end of file +} diff --git a/PKHeX.WinForms/Subforms/SAV_FolderList.cs b/PKHeX.WinForms/Subforms/SAV_FolderList.cs index cb37d11fd..008e95598 100644 --- a/PKHeX.WinForms/Subforms/SAV_FolderList.cs +++ b/PKHeX.WinForms/Subforms/SAV_FolderList.cs @@ -10,369 +10,368 @@ using PKHeX.WinForms.Properties; using static PKHeX.Core.MessageStrings; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public partial class SAV_FolderList : Form { - public partial class SAV_FolderList : Form + private readonly Action OpenSaveFile; + private readonly List Paths; + private readonly SortableBindingList Recent; + private readonly SortableBindingList Backup; + private readonly List